mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-16 19:56:31 +01:00
drastic, fundamental redesign of MCP code
git-svn-id: svn://localhost/ardour2/branches/3.0@11861 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
ac7ade93bd
commit
5ace191bff
37 changed files with 1364 additions and 2256 deletions
|
|
@ -1,46 +0,0 @@
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include "bcf_surface.h"
|
|
||||||
#include "controls.h"
|
|
||||||
#include "mackie_midi_builder.h"
|
|
||||||
#include "surface_port.h"
|
|
||||||
#include "jog.h"
|
|
||||||
#include "pot.h"
|
|
||||||
|
|
||||||
using namespace Mackie;
|
|
||||||
|
|
||||||
void
|
|
||||||
BcfSurface::display_bank_start (SurfacePort & port, MackieMidiBuilder & builder, uint32_t current_bank)
|
|
||||||
{
|
|
||||||
if (current_bank == 0) {
|
|
||||||
// send Ar. to 2-char display on the master
|
|
||||||
port.write (builder.two_char_display ("Ar", ".."));
|
|
||||||
} else {
|
|
||||||
// write the current first remote_id to the 2-char display
|
|
||||||
port.write (builder.two_char_display (current_bank));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
BcfSurface::zero_all (SurfacePort & port, MackieMidiBuilder & builder)
|
|
||||||
{
|
|
||||||
// clear 2-char display
|
|
||||||
port.write (builder.two_char_display ("LC"));
|
|
||||||
|
|
||||||
// and the led ring for the master strip
|
|
||||||
blank_jog_ring (port, builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
BcfSurface::blank_jog_ring (SurfacePort & port, MackieMidiBuilder & builder)
|
|
||||||
{
|
|
||||||
Control & control = *controls_by_name["jog"];
|
|
||||||
port.write (builder.build_led_ring (dynamic_cast<Pot &> (control), off));
|
|
||||||
}
|
|
||||||
|
|
||||||
float
|
|
||||||
BcfSurface::scaled_delta (const ControlState & state, float current_speed)
|
|
||||||
{
|
|
||||||
return state.sign * (std::pow (float(state.ticks + 1), 2) + current_speed) / 100.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
#ifndef mackie_surface_bcf_h
|
|
||||||
#define mackie_surface_bcf_h
|
|
||||||
/*
|
|
||||||
Initially generated by scripts/generate-surface.rb
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "surface.h"
|
|
||||||
|
|
||||||
namespace Mackie
|
|
||||||
{
|
|
||||||
|
|
||||||
class MackieButtonHandler;
|
|
||||||
|
|
||||||
class BcfSurface : public Surface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
BcfSurface (uint32_t max_strips) : Surface (max_strips, 7) {}
|
|
||||||
|
|
||||||
virtual void display_bank_start( SurfacePort & port, MackieMidiBuilder & builder, uint32_t current_bank );
|
|
||||||
virtual void zero_all( SurfacePort & port, MackieMidiBuilder & builder );
|
|
||||||
virtual void blank_jog_ring( SurfacePort & port, MackieMidiBuilder & builder );
|
|
||||||
virtual bool has_timecode_display() const { return false; }
|
|
||||||
|
|
||||||
virtual float scrub_scaling_factor() { return 50.0; }
|
|
||||||
virtual float scaled_delta( const ControlState & state, float current_speed );
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -24,9 +24,9 @@
|
||||||
using namespace Mackie;
|
using namespace Mackie;
|
||||||
|
|
||||||
Control*
|
Control*
|
||||||
Button::factory (Surface& surface, int id, int ordinal, const char* name, Group& group)
|
Button::factory (Surface& surface, int id, const char* name, Group& group)
|
||||||
{
|
{
|
||||||
Button* b = new Button (id, ordinal, name, group);
|
Button* b = new Button (id, name, group);
|
||||||
surface.buttons[id] = b;
|
surface.buttons[id] = b;
|
||||||
surface.controls.push_back (b);
|
surface.controls.push_back (b);
|
||||||
group.add (*b);
|
group.add (*b);
|
||||||
|
|
|
||||||
|
|
@ -97,15 +97,15 @@ public:
|
||||||
ButtonUserB = 0x67,
|
ButtonUserB = 0x67,
|
||||||
};
|
};
|
||||||
|
|
||||||
Button (int id, int ordinal, std::string name, Group & group)
|
Button (int id, std::string name, Group & group)
|
||||||
: Control (id, ordinal, name, group)
|
: Control (id, name, group)
|
||||||
, _led (id, ordinal, name + "_led", group) {}
|
, _led (id, name + "_led", group) {}
|
||||||
|
|
||||||
virtual const Led & led() const { return _led; }
|
virtual const Led & led() const { return _led; }
|
||||||
|
|
||||||
virtual type_t type() const { return type_button; };
|
virtual type_t type() const { return type_button; };
|
||||||
|
|
||||||
static Control* factory (Surface&, int id, int ordinal, const char*, Group&);
|
static Control* factory (Surface&, int id, const char*, Group&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Led _led;
|
Led _led;
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,8 @@ void Group::add (Control& control)
|
||||||
_controls.push_back (&control);
|
_controls.push_back (&control);
|
||||||
}
|
}
|
||||||
|
|
||||||
Control::Control (int id, int ordinal, std::string name, Group & group)
|
Control::Control (int id, std::string name, Group & group)
|
||||||
: _id (id)
|
: _id (id)
|
||||||
, _ordinal (ordinal)
|
|
||||||
, _name (name)
|
, _name (name)
|
||||||
, _group (group)
|
, _group (group)
|
||||||
, _in_use (false)
|
, _in_use (false)
|
||||||
|
|
@ -82,8 +81,6 @@ ostream & Mackie::operator << (ostream & os, const Mackie::Control & control)
|
||||||
os << ", ";
|
os << ", ";
|
||||||
os << "raw_id: " << "0x" << setw(2) << setfill('0') << hex << control.raw_id() << setfill(' ');
|
os << "raw_id: " << "0x" << setw(2) << setfill('0') << hex << control.raw_id() << setfill(' ');
|
||||||
os << ", ";
|
os << ", ";
|
||||||
os << "ordinal: " << dec << control.ordinal();
|
|
||||||
os << ", ";
|
|
||||||
os << "group: " << control.group().name();
|
os << "group: " << control.group().name();
|
||||||
os << " }";
|
os << " }";
|
||||||
|
|
||||||
|
|
@ -91,9 +88,9 @@ ostream & Mackie::operator << (ostream & os, const Mackie::Control & control)
|
||||||
}
|
}
|
||||||
|
|
||||||
Control*
|
Control*
|
||||||
Pot::factory (Surface& surface, int id, int ordinal, const char* name, Group& group)
|
Pot::factory (Surface& surface, int id, const char* name, Group& group)
|
||||||
{
|
{
|
||||||
Pot* p = new Pot (id, ordinal, name, group);
|
Pot* p = new Pot (id, name, group);
|
||||||
surface.pots[id] = p;
|
surface.pots[id] = p;
|
||||||
surface.controls.push_back (p);
|
surface.controls.push_back (p);
|
||||||
group.add (*p);
|
group.add (*p);
|
||||||
|
|
@ -101,9 +98,9 @@ Pot::factory (Surface& surface, int id, int ordinal, const char* name, Group& gr
|
||||||
}
|
}
|
||||||
|
|
||||||
Control*
|
Control*
|
||||||
Led::factory (Surface& surface, int id, int ordinal, const char* name, Group& group)
|
Led::factory (Surface& surface, int id, const char* name, Group& group)
|
||||||
{
|
{
|
||||||
Led* l = new Led (id, ordinal, name, group);
|
Led* l = new Led (id, name, group);
|
||||||
surface.leds[id] = l;
|
surface.leds[id] = l;
|
||||||
surface.controls.push_back (l);
|
surface.controls.push_back (l);
|
||||||
group.add (*l);
|
group.add (*l);
|
||||||
|
|
@ -111,9 +108,9 @@ Led::factory (Surface& surface, int id, int ordinal, const char* name, Group& gr
|
||||||
}
|
}
|
||||||
|
|
||||||
Control*
|
Control*
|
||||||
Jog::factory (Surface& surface, int id, int ordinal, const char* name, Group& group)
|
Jog::factory (Surface& surface, int id, const char* name, Group& group)
|
||||||
{
|
{
|
||||||
Jog* j = new Jog (id, ordinal, name, group);
|
Jog* j = new Jog (id, name, group);
|
||||||
surface.controls.push_back (j);
|
surface.controls.push_back (j);
|
||||||
surface.controls_by_name["jog"] = j;
|
surface.controls_by_name["jog"] = j;
|
||||||
group.add (*j);
|
group.add (*j);
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ public:
|
||||||
meter_base_id = 0xd0,
|
meter_base_id = 0xd0,
|
||||||
};
|
};
|
||||||
|
|
||||||
Control (int id, int ordinal, std::string name, Group& group);
|
Control (int id, std::string name, Group& group);
|
||||||
virtual ~Control() {}
|
virtual ~Control() {}
|
||||||
|
|
||||||
virtual const Led & led() const { throw MackieControlException ("no led available"); }
|
virtual const Led & led() const { throw MackieControlException ("no led available"); }
|
||||||
|
|
@ -76,15 +76,8 @@ public:
|
||||||
/// unique within the control type.
|
/// unique within the control type.
|
||||||
int raw_id() const { return _id; }
|
int raw_id() const { return _id; }
|
||||||
|
|
||||||
/* this identifies a given control within its MCU "bank of 8"
|
|
||||||
*/
|
|
||||||
int control_id() const { return _id % 8; }
|
|
||||||
|
|
||||||
/// The 1-based number of the control
|
|
||||||
int ordinal() const { return _ordinal; }
|
|
||||||
|
|
||||||
const std::string & name() const { return _name; }
|
const std::string & name() const { return _name; }
|
||||||
const Group & group() const { return _group; }
|
Group & group() const { return _group; }
|
||||||
virtual bool accepts_feedback() const { return true; }
|
virtual bool accepts_feedback() const { return true; }
|
||||||
|
|
||||||
virtual type_t type() const = 0;
|
virtual type_t type() const = 0;
|
||||||
|
|
@ -105,7 +98,6 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int _id;
|
int _id;
|
||||||
int _ordinal;
|
|
||||||
std::string _name;
|
std::string _name;
|
||||||
Group& _group;
|
Group& _group;
|
||||||
bool _in_use;
|
bool _in_use;
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
#include "dummy_port.h"
|
|
||||||
|
|
||||||
#include "midi_byte_array.h"
|
|
||||||
|
|
||||||
#include <midi++/port.h>
|
|
||||||
#include <midi++/types.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
using namespace Mackie;
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
DummyPort::DummyPort()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
DummyPort::~DummyPort()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void DummyPort::open()
|
|
||||||
{
|
|
||||||
cout << "DummyPort::open" << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void DummyPort::close()
|
|
||||||
{
|
|
||||||
cout << "DummyPort::close" << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
MidiByteArray DummyPort::read()
|
|
||||||
{
|
|
||||||
cout << "DummyPort::read" << endl;
|
|
||||||
return MidiByteArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void DummyPort::write( const MidiByteArray & mba )
|
|
||||||
{
|
|
||||||
cout << "DummyPort::write " << mba << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
MidiByteArray empty_midi_byte_array;
|
|
||||||
|
|
||||||
const MidiByteArray & DummyPort::sysex_hdr() const
|
|
||||||
{
|
|
||||||
cout << "DummyPort::sysex_hdr" << endl;
|
|
||||||
return empty_midi_byte_array;
|
|
||||||
}
|
|
||||||
|
|
||||||
int DummyPort::strips() const
|
|
||||||
{
|
|
||||||
cout << "DummyPort::strips" << endl;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2008 John Anderson
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
#ifndef dummy_port_h
|
|
||||||
#define dummy_port_h
|
|
||||||
|
|
||||||
#include "surface_port.h"
|
|
||||||
|
|
||||||
#include "midi_byte_array.h"
|
|
||||||
|
|
||||||
namespace MIDI {
|
|
||||||
class Port;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Mackie
|
|
||||||
{
|
|
||||||
|
|
||||||
/**
|
|
||||||
A Dummy Port, to catch things that shouldn't be sent.
|
|
||||||
*/
|
|
||||||
class DummyPort : public SurfacePort
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
DummyPort();
|
|
||||||
virtual ~DummyPort();
|
|
||||||
|
|
||||||
// when this is successful, active() should return true
|
|
||||||
virtual void open();
|
|
||||||
|
|
||||||
// subclasses should call this before doing their own close
|
|
||||||
virtual void close();
|
|
||||||
|
|
||||||
/// read bytes from the port. They'll either end up in the
|
|
||||||
/// parser, or if that's not active they'll be returned
|
|
||||||
virtual MidiByteArray read();
|
|
||||||
|
|
||||||
/// an easier way to output bytes via midi
|
|
||||||
virtual void write( const MidiByteArray & );
|
|
||||||
|
|
||||||
virtual const MidiByteArray & sysex_hdr() const;
|
|
||||||
virtual int strips() const;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -24,11 +24,9 @@
|
||||||
using namespace Mackie;
|
using namespace Mackie;
|
||||||
|
|
||||||
Control*
|
Control*
|
||||||
Fader::factory (Surface& surface, int id, int ordinal, const char* name, Group& group)
|
Fader::factory (Surface& surface, int id, const char* name, Group& group)
|
||||||
{
|
{
|
||||||
Fader* f = new Fader (id, ordinal, name, group);
|
Fader* f = new Fader (id, name, group);
|
||||||
|
|
||||||
std::cerr << "Registering fader " << id << " ord " << ordinal << std::endl;
|
|
||||||
|
|
||||||
surface.faders[id] = f;
|
surface.faders[id] = f;
|
||||||
surface.controls.push_back (f);
|
surface.controls.push_back (f);
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,15 @@ namespace Mackie {
|
||||||
|
|
||||||
class Fader : public Control
|
class Fader : public Control
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Fader (int id, int ordinal, std::string name, Group & group)
|
Fader (int id, std::string name, Group & group)
|
||||||
: Control (id, ordinal, name, group)
|
: Control (id, name, group)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual type_t type() const { return type_fader; }
|
virtual type_t type() const { return type_fader; }
|
||||||
|
|
||||||
static Control* factory (Surface&, int id, int ordinal, const char*, Group&);
|
static Control* factory (Surface&, int id, const char*, Group&);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,14 +28,14 @@ namespace Mackie {
|
||||||
class Jog : public Pot
|
class Jog : public Pot
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Jog (int id, int ordinal, std::string name, Group & group)
|
Jog (int id, std::string name, Group & group)
|
||||||
: Pot (id, ordinal, name, group)
|
: Pot (id, name, group)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool is_jog() const { return true; }
|
virtual bool is_jog() const { return true; }
|
||||||
|
|
||||||
static Control* factory (Surface&, int id, int ordinal, const char*, Group&);
|
static Control* factory (Surface&, int id, const char*, Group&);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ namespace Mackie {
|
||||||
class Led : public Control
|
class Led : public Control
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Led (int id, int ordinal, std::string name, Group & group)
|
Led (int id, std::string name, Group & group)
|
||||||
: Control (id, ordinal, name, group)
|
: Control (id, name, group)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +36,7 @@ public:
|
||||||
|
|
||||||
virtual type_t type() const { return type_led; }
|
virtual type_t type() const { return type_led; }
|
||||||
|
|
||||||
static Control* factory (Surface&, int id, int ordinal, const char*, Group&);
|
static Control* factory (Surface&, int id, const char*, Group&);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@ namespace Mackie {
|
||||||
class LedRing : public Led
|
class LedRing : public Led
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LedRing (int id, int ordinal, std::string name, Group & group)
|
LedRing (int id, std::string name, Group & group)
|
||||||
: Led (id, ordinal, name, group)
|
: Led (id, name, group)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -23,22 +23,22 @@
|
||||||
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
|
#include <boost/smart_ptr.hpp>
|
||||||
|
|
||||||
#include <glibmm/thread.h>
|
#include <glibmm/thread.h>
|
||||||
|
|
||||||
#include "pbd/abstract_ui.h"
|
#include "pbd/abstract_ui.h"
|
||||||
|
|
||||||
#include "ardour/types.h"
|
|
||||||
#include "ardour/midi_ui.h"
|
|
||||||
#include "midi++/types.h"
|
#include "midi++/types.h"
|
||||||
|
|
||||||
|
#include "ardour/types.h"
|
||||||
|
|
||||||
#include "control_protocol/control_protocol.h"
|
#include "control_protocol/control_protocol.h"
|
||||||
#include "midi_byte_array.h"
|
#include "midi_byte_array.h"
|
||||||
#include "controls.h"
|
#include "controls.h"
|
||||||
#include "dummy_port.h"
|
|
||||||
#include "route_signal.h"
|
|
||||||
#include "mackie_port.h"
|
#include "mackie_port.h"
|
||||||
#include "mackie_jog_wheel.h"
|
#include "mackie_jog_wheel.h"
|
||||||
|
#include "mackie_midi_builder.h"
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
|
|
||||||
namespace MIDI {
|
namespace MIDI {
|
||||||
|
|
@ -92,36 +92,28 @@ class MackieControlProtocol
|
||||||
|
|
||||||
static bool probe();
|
static bool probe();
|
||||||
|
|
||||||
Mackie::Surface & surface();
|
typedef std::list<boost::shared_ptr<Mackie::Surface> > Surfaces;
|
||||||
|
Surfaces surfaces;
|
||||||
|
|
||||||
std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
|
std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
|
||||||
|
|
||||||
|
uint32_t n_strips () const;
|
||||||
|
|
||||||
bool has_editor () const { return true; }
|
bool has_editor () const { return true; }
|
||||||
void* get_gui () const;
|
void* get_gui () const;
|
||||||
void tear_down_gui ();
|
void tear_down_gui ();
|
||||||
|
|
||||||
// control events
|
|
||||||
void handle_control_event (Mackie::SurfacePort & port, Mackie::Control & control, const Mackie::ControlState & state);
|
|
||||||
void handle_button_event (Mackie::Button& button, Mackie::ButtonState);
|
|
||||||
|
|
||||||
// strip/route related stuff
|
void select_track (boost::shared_ptr<ARDOUR::Route> r);
|
||||||
public:
|
|
||||||
void notify_solo_changed (Mackie::RouteSignal *);
|
void handle_button_event (Mackie::Surface&, Mackie::Button& button, Mackie::ButtonState);
|
||||||
void notify_mute_changed (Mackie::RouteSignal *);
|
|
||||||
void notify_record_enable_changed (Mackie::RouteSignal *);
|
|
||||||
void notify_gain_changed (Mackie::RouteSignal *, bool force_update = true);
|
|
||||||
void notify_property_changed (const PBD::PropertyChange&, Mackie::RouteSignal *);
|
|
||||||
void notify_panner_changed (Mackie::RouteSignal *, bool force_update = true);
|
|
||||||
void notify_route_added (ARDOUR::RouteList &);
|
void notify_route_added (ARDOUR::RouteList &);
|
||||||
void notify_active_changed (Mackie::RouteSignal *);
|
|
||||||
|
|
||||||
void notify_remote_id_changed();
|
void notify_remote_id_changed();
|
||||||
|
|
||||||
/// rebuild the current bank. Called on route added/removed and
|
/// rebuild the current bank. Called on route added/removed and
|
||||||
/// remote id changed.
|
/// remote id changed.
|
||||||
void refresh_current_bank();
|
void refresh_current_bank();
|
||||||
|
|
||||||
public:
|
|
||||||
// button-related signals
|
// button-related signals
|
||||||
void notify_record_state_changed();
|
void notify_record_state_changed();
|
||||||
void notify_transport_state_changed();
|
void notify_transport_state_changed();
|
||||||
|
|
@ -134,7 +126,7 @@ class MackieControlProtocol
|
||||||
void update_timecode_beats_led();
|
void update_timecode_beats_led();
|
||||||
|
|
||||||
/// this is called to generate the midi to send in response to a button press.
|
/// this is called to generate the midi to send in response to a button press.
|
||||||
void update_led(Mackie::Button & button, Mackie::LedState);
|
void update_led(Mackie::Surface&, Mackie::Button & button, Mackie::LedState);
|
||||||
|
|
||||||
void update_global_button(const std::string & name, Mackie::LedState);
|
void update_global_button(const std::string & name, Mackie::LedState);
|
||||||
void update_global_led(const std::string & name, Mackie::LedState);
|
void update_global_led(const std::string & name, Mackie::LedState);
|
||||||
|
|
@ -280,30 +272,17 @@ class MackieControlProtocol
|
||||||
Mackie::LedState fader_touch_press (Mackie::Button &);
|
Mackie::LedState fader_touch_press (Mackie::Button &);
|
||||||
Mackie::LedState fader_touch_release (Mackie::Button &);
|
Mackie::LedState fader_touch_release (Mackie::Button &);
|
||||||
|
|
||||||
|
|
||||||
/// This is the main MCU port, ie not an extender port
|
|
||||||
/// Only for use by JogWheel
|
|
||||||
const Mackie::SurfacePort & mcu_port() const;
|
|
||||||
Mackie::SurfacePort & mcu_port();
|
|
||||||
ARDOUR::Session & get_session() { return *session; }
|
ARDOUR::Session & get_session() { return *session; }
|
||||||
|
|
||||||
void add_in_use_timeout (Mackie::SurfacePort& port, Mackie::Control& in_use_control, Mackie::Control* touch_control);
|
void add_in_use_timeout (Mackie::Surface& surface, Mackie::Control& in_use_control, Mackie::Control* touch_control);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// create instances of MackiePort, depending on what's found in ardour.rc
|
|
||||||
void create_ports();
|
|
||||||
|
|
||||||
// shut down the surface
|
// shut down the surface
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
// create the Surface object, with the correct number
|
|
||||||
// of strips for the currently connected ports and
|
|
||||||
// hook up the control event notification
|
|
||||||
void initialize_surface();
|
|
||||||
|
|
||||||
// This sets up the notifications and sets the
|
// This sets up the notifications and sets the
|
||||||
// controls to the correct values
|
// controls to the correct values
|
||||||
void update_surface();
|
void update_surfaces();
|
||||||
|
|
||||||
// connects global (not strip) signals from the Session to here
|
// connects global (not strip) signals from the Session to here
|
||||||
// so the surface can be notified of changes from the other UIs.
|
// so the surface can be notified of changes from the other UIs.
|
||||||
|
|
@ -320,54 +299,16 @@ class MackieControlProtocol
|
||||||
Sorted get_sorted_routes();
|
Sorted get_sorted_routes();
|
||||||
|
|
||||||
// bank switching
|
// bank switching
|
||||||
void switch_banks(int initial);
|
void switch_banks (uint32_t first_remote_id, bool force = false);
|
||||||
void prev_track();
|
void prev_track ();
|
||||||
void next_track();
|
void next_track ();
|
||||||
|
|
||||||
// delete all RouteSignal objects connecting Routes to Strips
|
|
||||||
void clear_route_signals();
|
|
||||||
|
|
||||||
typedef std::vector<Mackie::RouteSignal*> RouteSignals;
|
|
||||||
RouteSignals route_signals;
|
|
||||||
Glib::Mutex route_signals_lock;
|
|
||||||
|
|
||||||
// return which of the ports a particular route_table
|
|
||||||
// index belongs to
|
|
||||||
Mackie::MackiePort & port_for_id(uint32_t index);
|
|
||||||
|
|
||||||
/**
|
|
||||||
Handle a button press for the control and return whether
|
|
||||||
the corresponding light should be on or off.
|
|
||||||
*/
|
|
||||||
bool handle_strip_button (Mackie::SurfacePort &, Mackie::Control &, Mackie::ButtonState, boost::shared_ptr<ARDOUR::Route>);
|
|
||||||
|
|
||||||
void add_port (MIDI::Port &, MIDI::Port &, int number, Mackie::MackiePort::port_type_t);
|
|
||||||
|
|
||||||
// called from poll_automation to figure out which automations need to be sent
|
|
||||||
void update_automation(Mackie::RouteSignal &);
|
|
||||||
|
|
||||||
// also called from poll_automation to update timecode display
|
// also called from poll_automation to update timecode display
|
||||||
void update_timecode_display();
|
void update_timecode_display();
|
||||||
|
|
||||||
std::string format_bbt_timecode (ARDOUR::framepos_t now_frame);
|
std::string format_bbt_timecode (ARDOUR::framepos_t now_frame);
|
||||||
std::string format_timecode_timecode (ARDOUR::framepos_t now_frame);
|
std::string format_timecode_timecode (ARDOUR::framepos_t now_frame);
|
||||||
|
|
||||||
/**
|
|
||||||
notification that the port is about to start it's init sequence.
|
|
||||||
We must make sure that before this exits, the port is being polled
|
|
||||||
for new data.
|
|
||||||
*/
|
|
||||||
void handle_port_init(Mackie::SurfacePort *);
|
|
||||||
|
|
||||||
/// notification from a MackiePort that it's now active
|
|
||||||
void handle_port_active(Mackie::SurfacePort *);
|
|
||||||
|
|
||||||
/// notification from a MackiePort that it's now inactive
|
|
||||||
void handle_port_inactive(Mackie::SurfacePort *);
|
|
||||||
|
|
||||||
boost::shared_ptr<ARDOUR::Route> master_route();
|
|
||||||
Mackie::Strip & master_strip();
|
|
||||||
|
|
||||||
void do_request (MackieControlUIRequest*);
|
void do_request (MackieControlUIRequest*);
|
||||||
int stop ();
|
int stop ();
|
||||||
|
|
||||||
|
|
@ -375,23 +316,13 @@ class MackieControlProtocol
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
void create_surfaces ();
|
||||||
void port_connected_or_disconnected (std::string, std::string, bool);
|
void port_connected_or_disconnected (std::string, std::string, bool);
|
||||||
bool control_in_use_timeout (Mackie::SurfacePort*, Mackie::Control *, Mackie::Control *);
|
bool control_in_use_timeout (Mackie::Surface*, Mackie::Control *, Mackie::Control *);
|
||||||
|
|
||||||
bool periodic();
|
bool periodic();
|
||||||
sigc::connection periodic_connection;
|
sigc::connection periodic_connection;
|
||||||
|
|
||||||
boost::shared_ptr<Mackie::RouteSignal> master_route_signal;
|
|
||||||
|
|
||||||
static const char * default_port_name;
|
|
||||||
|
|
||||||
/// The Midi port(s) connected to the units
|
|
||||||
typedef std::vector<Mackie::MackiePort*> MackiePorts;
|
|
||||||
MackiePorts _ports;
|
|
||||||
|
|
||||||
/// Sometimes the real port goes away, and we want to contain the breakage
|
|
||||||
Mackie::DummyPort _dummy_port;
|
|
||||||
|
|
||||||
/// The initial remote_id of the currently switched in bank.
|
/// The initial remote_id of the currently switched in bank.
|
||||||
uint32_t _current_initial_bank;
|
uint32_t _current_initial_bank;
|
||||||
|
|
||||||
|
|
@ -403,16 +334,11 @@ class MackieControlProtocol
|
||||||
PBD::ScopedConnectionList port_connections;
|
PBD::ScopedConnectionList port_connections;
|
||||||
PBD::ScopedConnectionList route_connections;
|
PBD::ScopedConnectionList route_connections;
|
||||||
|
|
||||||
/// The representation of the physical controls on the surface.
|
|
||||||
Mackie::Surface * _surface;
|
|
||||||
|
|
||||||
bool _transport_previously_rolling;
|
bool _transport_previously_rolling;
|
||||||
|
|
||||||
// timer for two quick marker left presses
|
// timer for two quick marker left presses
|
||||||
Mackie::Timer _frm_left_last;
|
Mackie::Timer _frm_left_last;
|
||||||
|
|
||||||
Mackie::JogWheel _jog_wheel;
|
|
||||||
|
|
||||||
// last written timecode string
|
// last written timecode string
|
||||||
std::string _timecode_last;
|
std::string _timecode_last;
|
||||||
|
|
||||||
|
|
@ -437,6 +363,8 @@ class MackieControlProtocol
|
||||||
static const int MODIFIER_CMDALT;
|
static const int MODIFIER_CMDALT;
|
||||||
|
|
||||||
int _modifier_state;
|
int _modifier_state;
|
||||||
|
|
||||||
|
Mackie::MackieMidiBuilder builder;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ardour_mackie_control_protocol_h
|
#endif // ardour_mackie_control_protocol_h
|
||||||
|
|
|
||||||
|
|
@ -25,57 +25,3 @@ using namespace std;
|
||||||
using namespace Mackie;
|
using namespace Mackie;
|
||||||
using namespace PBD;
|
using namespace PBD;
|
||||||
|
|
||||||
const char * MackieControlProtocol::default_port_name = "mcu";
|
|
||||||
|
|
||||||
bool MackieControlProtocol::probe()
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MackieControlProtocol::handle_port_inactive( SurfacePort * port )
|
|
||||||
{
|
|
||||||
// port gone away. So stop polling it ASAP
|
|
||||||
{
|
|
||||||
// delete the port instance
|
|
||||||
Glib::Mutex::Lock lock( update_mutex );
|
|
||||||
MackiePorts::iterator it = find( _ports.begin(), _ports.end(), port );
|
|
||||||
if ( it != _ports.end() )
|
|
||||||
{
|
|
||||||
delete *it;
|
|
||||||
_ports.erase( it );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO all the rebuilding of surfaces and so on
|
|
||||||
}
|
|
||||||
|
|
||||||
void MackieControlProtocol::handle_port_active (SurfacePort *)
|
|
||||||
{
|
|
||||||
// no need to re-add port because it was already added
|
|
||||||
// during the init phase. So just update the local surface
|
|
||||||
// representation and send the representation to
|
|
||||||
// all existing ports
|
|
||||||
|
|
||||||
// TODO update bank size
|
|
||||||
|
|
||||||
// TODO rebuild surface, to have new units
|
|
||||||
|
|
||||||
// finally update session state to the surface
|
|
||||||
// TODO but this is also done in set_active, and
|
|
||||||
// in fact update_surface won't execute unless
|
|
||||||
#ifdef DEBUG
|
|
||||||
cout << "update_surface in handle_port_active" << endl;
|
|
||||||
#endif
|
|
||||||
// _active == true
|
|
||||||
update_surface();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MackieControlProtocol::handle_port_init (Mackie::SurfacePort *)
|
|
||||||
{
|
|
||||||
#ifdef DEBUG
|
|
||||||
cout << "MackieControlProtocol::handle_port_init" << endl;
|
|
||||||
#endif
|
|
||||||
#ifdef DEBUG
|
|
||||||
cout << "MackieControlProtocol::handle_port_init finish" << endl;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -14,17 +14,17 @@
|
||||||
using namespace Mackie;
|
using namespace Mackie;
|
||||||
using std::isnan;
|
using std::isnan;
|
||||||
|
|
||||||
JogWheel::JogWheel( MackieControlProtocol & mcp )
|
JogWheel::JogWheel (MackieControlProtocol & mcp)
|
||||||
: _mcp( mcp )
|
: _mcp (mcp)
|
||||||
, _transport_speed( 4.0 )
|
, _transport_speed (4.0)
|
||||||
, _transport_direction( 0 )
|
, _transport_direction (0)
|
||||||
, _shuttle_speed( 0.0 )
|
, _shuttle_speed (0.0)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
JogWheel::State JogWheel::jog_wheel_state() const
|
JogWheel::State JogWheel::jog_wheel_state() const
|
||||||
{
|
{
|
||||||
if ( !_jog_wheel_states.empty() )
|
if (!_jog_wheel_states.empty())
|
||||||
return _jog_wheel_states.top();
|
return _jog_wheel_states.top();
|
||||||
else
|
else
|
||||||
return scroll;
|
return scroll;
|
||||||
|
|
@ -49,28 +49,28 @@ void JogWheel::scroll_event (SurfacePort &, Control &, const ControlState &)
|
||||||
void JogWheel::jog_event (SurfacePort &, Control &, const ControlState & state)
|
void JogWheel::jog_event (SurfacePort &, Control &, const ControlState & state)
|
||||||
{
|
{
|
||||||
// TODO use current snap-to setting?
|
// TODO use current snap-to setting?
|
||||||
switch ( jog_wheel_state() )
|
switch (jog_wheel_state())
|
||||||
{
|
{
|
||||||
case scroll:
|
case scroll:
|
||||||
_mcp.ScrollTimeline( state.delta * state.sign );
|
_mcp.ScrollTimeline (state.delta * state.sign);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case zoom:
|
case zoom:
|
||||||
// Chunky Zoom.
|
// Chunky Zoom.
|
||||||
// TODO implement something similar to ScrollTimeline which
|
// TODO implement something similar to ScrollTimeline which
|
||||||
// ends up in Editor::control_scroll for smoother zooming.
|
// ends up in Editor::control_scroll for smoother zooming.
|
||||||
if ( state.sign > 0 )
|
if (state.sign > 0)
|
||||||
for ( unsigned int i = 0; i < state.ticks; ++i ) _mcp.ZoomIn();
|
for (unsigned int i = 0; i < state.ticks; ++i) _mcp.ZoomIn();
|
||||||
else
|
else
|
||||||
for ( unsigned int i = 0; i < state.ticks; ++i ) _mcp.ZoomOut();
|
for (unsigned int i = 0; i < state.ticks; ++i) _mcp.ZoomOut();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case speed:
|
case speed:
|
||||||
// locally, _transport_speed is an positive value
|
// locally, _transport_speed is an positive value
|
||||||
_transport_speed += _mcp.surface().scaled_delta( state, _mcp.get_session().transport_speed() );
|
_transport_speed += _mcp.surfaces.front()->scaled_delta (state, _mcp.get_session().transport_speed());
|
||||||
|
|
||||||
// make sure no weirdness gets to the session
|
// make sure no weirdness gets to the session
|
||||||
if ( _transport_speed < 0 || isnan( _transport_speed ) )
|
if (_transport_speed < 0 || isnan (_transport_speed))
|
||||||
{
|
{
|
||||||
_transport_speed = 0.0;
|
_transport_speed = 0.0;
|
||||||
}
|
}
|
||||||
|
|
@ -81,11 +81,11 @@ void JogWheel::jog_event (SurfacePort &, Control &, const ControlState & state)
|
||||||
|
|
||||||
case scrub:
|
case scrub:
|
||||||
{
|
{
|
||||||
if ( state.sign != 0 )
|
if (state.sign != 0)
|
||||||
{
|
{
|
||||||
add_scrub_interval( _scrub_timer.restart() );
|
add_scrub_interval (_scrub_timer.restart());
|
||||||
// x clicks per second => speed == 1.0
|
// x clicks per second => speed == 1.0
|
||||||
float speed = _mcp.surface().scrub_scaling_factor() / average_scrub_interval() * state.ticks;
|
float speed = _mcp.surfaces.front()->scrub_scaling_factor() / average_scrub_interval() * state.ticks;
|
||||||
_mcp.get_session().request_transport_speed_nonzero (speed * state.sign);
|
_mcp.get_session().request_transport_speed_nonzero (speed * state.sign);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -98,7 +98,7 @@ void JogWheel::jog_event (SurfacePort &, Control &, const ControlState & state)
|
||||||
|
|
||||||
case shuttle:
|
case shuttle:
|
||||||
_shuttle_speed = _mcp.get_session().transport_speed();
|
_shuttle_speed = _mcp.get_session().transport_speed();
|
||||||
_shuttle_speed += _mcp.surface().scaled_delta( state, _mcp.get_session().transport_speed() );
|
_shuttle_speed += _mcp.surfaces.front()->scaled_delta (state, _mcp.get_session().transport_speed());
|
||||||
_mcp.get_session().request_transport_speed_nonzero (_shuttle_speed);
|
_mcp.get_session().request_transport_speed_nonzero (_shuttle_speed);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
@ -111,21 +111,21 @@ void JogWheel::jog_event (SurfacePort &, Control &, const ControlState & state)
|
||||||
void JogWheel::check_scrubbing()
|
void JogWheel::check_scrubbing()
|
||||||
{
|
{
|
||||||
// if the last elapsed is greater than the average + std deviation, then stop
|
// if the last elapsed is greater than the average + std deviation, then stop
|
||||||
if ( !_scrub_intervals.empty() && _scrub_timer.elapsed() > average_scrub_interval() + std_dev_scrub_interval() )
|
if (!_scrub_intervals.empty() && _scrub_timer.elapsed() > average_scrub_interval() + std_dev_scrub_interval())
|
||||||
{
|
{
|
||||||
_mcp.get_session().request_transport_speed( 0.0 );
|
_mcp.get_session().request_transport_speed (0.0);
|
||||||
_scrub_intervals.clear();
|
_scrub_intervals.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void JogWheel::push( State state )
|
void JogWheel::push (State state)
|
||||||
{
|
{
|
||||||
_jog_wheel_states.push( state );
|
_jog_wheel_states.push (state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void JogWheel::pop()
|
void JogWheel::pop()
|
||||||
{
|
{
|
||||||
if ( _jog_wheel_states.size() > 0 )
|
if (_jog_wheel_states.size() > 0)
|
||||||
{
|
{
|
||||||
_jog_wheel_states.pop();
|
_jog_wheel_states.pop();
|
||||||
}
|
}
|
||||||
|
|
@ -133,23 +133,23 @@ void JogWheel::pop()
|
||||||
|
|
||||||
void JogWheel::zoom_state_toggle()
|
void JogWheel::zoom_state_toggle()
|
||||||
{
|
{
|
||||||
if ( jog_wheel_state() == zoom )
|
if (jog_wheel_state() == zoom)
|
||||||
pop();
|
pop();
|
||||||
else
|
else
|
||||||
push( zoom );
|
push (zoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
JogWheel::State JogWheel::scrub_state_cycle()
|
JogWheel::State JogWheel::scrub_state_cycle()
|
||||||
{
|
{
|
||||||
State top = jog_wheel_state();
|
State top = jog_wheel_state();
|
||||||
if ( top == scrub )
|
if (top == scrub)
|
||||||
{
|
{
|
||||||
// stop scrubbing and go to shuttle
|
// stop scrubbing and go to shuttle
|
||||||
pop();
|
pop();
|
||||||
push( shuttle );
|
push (shuttle);
|
||||||
_shuttle_speed = 0.0;
|
_shuttle_speed = 0.0;
|
||||||
}
|
}
|
||||||
else if ( top == shuttle )
|
else if (top == shuttle)
|
||||||
{
|
{
|
||||||
// default to scroll, or the last selected
|
// default to scroll, or the last selected
|
||||||
pop();
|
pop();
|
||||||
|
|
@ -157,25 +157,25 @@ JogWheel::State JogWheel::scrub_state_cycle()
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// start with scrub
|
// start with scrub
|
||||||
push( scrub );
|
push (scrub);
|
||||||
}
|
}
|
||||||
|
|
||||||
return jog_wheel_state();
|
return jog_wheel_state();
|
||||||
}
|
}
|
||||||
|
|
||||||
void JogWheel::add_scrub_interval( unsigned long elapsed )
|
void JogWheel::add_scrub_interval (unsigned long elapsed)
|
||||||
{
|
{
|
||||||
if ( _scrub_intervals.size() > 5 )
|
if (_scrub_intervals.size() > 5)
|
||||||
{
|
{
|
||||||
_scrub_intervals.pop_front();
|
_scrub_intervals.pop_front();
|
||||||
}
|
}
|
||||||
_scrub_intervals.push_back( elapsed );
|
_scrub_intervals.push_back (elapsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
float JogWheel::average_scrub_interval()
|
float JogWheel::average_scrub_interval()
|
||||||
{
|
{
|
||||||
float sum = 0.0;
|
float sum = 0.0;
|
||||||
for ( std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it )
|
for (std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it)
|
||||||
{
|
{
|
||||||
sum += *it;
|
sum += *it;
|
||||||
}
|
}
|
||||||
|
|
@ -188,9 +188,9 @@ float JogWheel::std_dev_scrub_interval()
|
||||||
|
|
||||||
// calculate standard deviation
|
// calculate standard deviation
|
||||||
float sum = 0.0;
|
float sum = 0.0;
|
||||||
for ( std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it )
|
for (std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it)
|
||||||
{
|
{
|
||||||
sum += pow( *it - average, 2 );
|
sum += pow (*it - average, 2);
|
||||||
}
|
}
|
||||||
return sqrt( sum / _scrub_intervals.size() -1 );
|
return sqrt (sum / _scrub_intervals.size() -1);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@
|
||||||
#include "meter.h"
|
#include "meter.h"
|
||||||
#include "midi_byte_array.h"
|
#include "midi_byte_array.h"
|
||||||
#include "mackie_port.h"
|
#include "mackie_port.h"
|
||||||
|
#include "surface.h"
|
||||||
|
|
||||||
using namespace PBD;
|
using namespace PBD;
|
||||||
using namespace Mackie;
|
using namespace Mackie;
|
||||||
|
|
@ -77,7 +78,7 @@ MidiByteArray MackieMidiBuilder::build_led_ring (const LedRing & led_ring, const
|
||||||
// the control type
|
// the control type
|
||||||
, midi_pot_id
|
, midi_pot_id
|
||||||
// the id
|
// the id
|
||||||
, 0x20 + led_ring.control_id()
|
, 0x20 + led_ring.raw_id()
|
||||||
// the value
|
// the value
|
||||||
, calculate_pot_value (mode, state)
|
, calculate_pot_value (mode, state)
|
||||||
);
|
);
|
||||||
|
|
@ -101,7 +102,7 @@ MidiByteArray MackieMidiBuilder::build_led (const Led & led, LedState ls)
|
||||||
|
|
||||||
return MidiByteArray (3
|
return MidiByteArray (3
|
||||||
, midi_button_id
|
, midi_button_id
|
||||||
, led.control_id()
|
, led.raw_id()
|
||||||
, state
|
, state
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -111,7 +112,7 @@ MidiByteArray MackieMidiBuilder::build_fader (const Fader & fader, float pos)
|
||||||
int posi = int (0x3fff * pos);
|
int posi = int (0x3fff * pos);
|
||||||
|
|
||||||
return MidiByteArray (3
|
return MidiByteArray (3
|
||||||
, midi_fader_id | fader.control_id()
|
, midi_fader_id | fader.raw_id()
|
||||||
// lower-order bits
|
// lower-order bits
|
||||||
, posi & 0x7f
|
, posi & 0x7f
|
||||||
// higher-order bits
|
// higher-order bits
|
||||||
|
|
@ -119,7 +120,7 @@ MidiByteArray MackieMidiBuilder::build_fader (const Fader & fader, float pos)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
MidiByteArray MackieMidiBuilder::zero_strip (SurfacePort & port, const Strip & strip)
|
MidiByteArray MackieMidiBuilder::zero_strip (Surface& surface, const Strip & strip)
|
||||||
{
|
{
|
||||||
Group::Controls::const_iterator it = strip.controls().begin();
|
Group::Controls::const_iterator it = strip.controls().begin();
|
||||||
MidiByteArray retval;
|
MidiByteArray retval;
|
||||||
|
|
@ -134,8 +135,8 @@ MidiByteArray MackieMidiBuilder::zero_strip (SurfacePort & port, const Strip & s
|
||||||
|
|
||||||
/* XXX: not sure about this check to only display stuff for strips of index < 8 */
|
/* XXX: not sure about this check to only display stuff for strips of index < 8 */
|
||||||
if (strip.index() < 8) {
|
if (strip.index() < 8) {
|
||||||
retval << strip_display_blank (port, strip, 0);
|
retval << strip_display_blank (surface, strip, 0);
|
||||||
retval << strip_display_blank (port, strip, 1);
|
retval << strip_display_blank (surface, strip, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
|
|
@ -202,23 +203,23 @@ MidiByteArray MackieMidiBuilder::two_char_display (unsigned int value, const std
|
||||||
return two_char_display (os.str());
|
return two_char_display (os.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
MidiByteArray MackieMidiBuilder::strip_display_blank (SurfacePort & port, const Strip & strip, unsigned int line_number)
|
MidiByteArray MackieMidiBuilder::strip_display_blank (Surface& surface, const Strip & strip, unsigned int line_number)
|
||||||
{
|
{
|
||||||
// 6 spaces, not 7 because strip_display adds a space where appropriate
|
// 6 spaces, not 7 because strip_display adds a space where appropriate
|
||||||
return strip_display (port, strip, line_number, " ");
|
return strip_display (surface, strip, line_number, " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
MidiByteArray MackieMidiBuilder::strip_display (SurfacePort & port, const Strip & strip, unsigned int line_number, const std::string & line)
|
MidiByteArray MackieMidiBuilder::strip_display (Surface& surface, const Strip & strip, unsigned int line_number, const std::string & line)
|
||||||
{
|
{
|
||||||
assert (line_number <= 1);
|
assert (line_number <= 1);
|
||||||
|
|
||||||
MidiByteArray retval;
|
MidiByteArray retval;
|
||||||
uint32_t index = strip.index() % port.strips();
|
uint32_t index = strip.index() % 8;
|
||||||
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieMidiBuilder::strip_display index: %1, line %2 = %3\n", strip.index(), line_number, line));
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieMidiBuilder::strip_display index: %1, line %2 = %3\n", strip.index(), line_number, line));
|
||||||
|
|
||||||
// sysex header
|
// sysex header
|
||||||
retval << port.sysex_hdr();
|
retval << surface.sysex_hdr();
|
||||||
|
|
||||||
// code for display
|
// code for display
|
||||||
retval << 0x12;
|
retval << 0x12;
|
||||||
|
|
@ -254,7 +255,8 @@ MidiByteArray MackieMidiBuilder::all_strips_display (SurfacePort & /*port*/, std
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
MidiByteArray MackieMidiBuilder::timecode_display (SurfacePort & port, const std::string & timecode, const std::string & last_timecode)
|
MidiByteArray
|
||||||
|
MackieMidiBuilder::timecode_display (Surface& surface, const std::string & timecode, const std::string & last_timecode)
|
||||||
{
|
{
|
||||||
// if there's no change, send nothing, not even sysex header
|
// if there's no change, send nothing, not even sysex header
|
||||||
if (timecode == last_timecode) return MidiByteArray();
|
if (timecode == last_timecode) return MidiByteArray();
|
||||||
|
|
@ -278,7 +280,7 @@ MidiByteArray MackieMidiBuilder::timecode_display (SurfacePort & port, const std
|
||||||
MidiByteArray retval;
|
MidiByteArray retval;
|
||||||
|
|
||||||
// sysex header
|
// sysex header
|
||||||
retval << port.sysex_hdr();
|
retval << surface.sysex_hdr();
|
||||||
|
|
||||||
// code for timecode display
|
// code for timecode display
|
||||||
retval << 0x10;
|
retval << 0x10;
|
||||||
|
|
|
||||||
|
|
@ -41,69 +41,72 @@ class LedRing;
|
||||||
class MackieMidiBuilder
|
class MackieMidiBuilder
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
MackieMidiBuilder () {}
|
||||||
|
~MackieMidiBuilder() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The first byte of a midi message from the surface
|
The first byte of a midi message from the surface
|
||||||
will contain one of these, sometimes bitmasked
|
will contain one of these, sometimes bitmasked
|
||||||
with the control id
|
with the control id
|
||||||
*/
|
*/
|
||||||
enum midi_types {
|
enum midi_types {
|
||||||
midi_fader_id = Control::type_fader
|
midi_fader_id = Control::type_fader,
|
||||||
, midi_button_id = Control::type_button
|
midi_button_id = Control::type_button,
|
||||||
, midi_pot_id = Control::type_pot
|
midi_pot_id = Control::type_pot,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The LED rings have these modes.
|
The LED rings have these modes.
|
||||||
*/
|
*/
|
||||||
enum midi_pot_mode {
|
enum midi_pot_mode {
|
||||||
midi_pot_mode_dot = 0
|
midi_pot_mode_dot = 0,
|
||||||
, midi_pot_mode_boost_cut = 1
|
midi_pot_mode_boost_cut = 1,
|
||||||
, midi_pot_mode_wrap = 2
|
midi_pot_mode_wrap = 2,
|
||||||
, midi_pot_mode_spread = 3
|
midi_pot_mode_spread = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
MidiByteArray build_led_ring( const Pot & pot, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot );
|
MidiByteArray build_led_ring (const Pot & pot, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot);
|
||||||
MidiByteArray build_led_ring( const LedRing & led_ring, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot );
|
MidiByteArray build_led_ring (const LedRing & led_ring, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot);
|
||||||
|
|
||||||
MidiByteArray build_led( const Led & led, LedState ls );
|
MidiByteArray build_led (const Led & led, LedState ls);
|
||||||
MidiByteArray build_led( const Button & button, LedState ls );
|
MidiByteArray build_led (const Button & button, LedState ls);
|
||||||
|
|
||||||
MidiByteArray build_fader( const Fader & fader, float pos );
|
MidiByteArray build_fader (const Fader & fader, float pos);
|
||||||
|
|
||||||
/// return bytes that will reset all controls to their zero positions
|
/// return bytes that will reset all controls to their zero positions
|
||||||
/// And blank the display for the strip. Pass SurfacePort so we know which sysex header to use.
|
/// And blank the display for the strip. Pass Surface so we know which sysex header to use.
|
||||||
MidiByteArray zero_strip( SurfacePort &, const Strip & strip );
|
MidiByteArray zero_strip (Surface&, const Strip & strip);
|
||||||
|
|
||||||
// provide bytes to zero the given control
|
// provide bytes to zero the given control
|
||||||
MidiByteArray zero_control( const Control & control );
|
MidiByteArray zero_control (const Control & control);
|
||||||
|
|
||||||
// display the first 2 chars of the msg in the 2 char display
|
// display the first 2 chars of the msg in the 2 char display
|
||||||
// . is appended to the previous character, so A.B. would
|
// . is appended to the previous character, so A.B. would
|
||||||
// be two characters
|
// be two characters
|
||||||
MidiByteArray two_char_display( const std::string & msg, const std::string & dots = " " );
|
MidiByteArray two_char_display (const std::string & msg, const std::string & dots = " ");
|
||||||
MidiByteArray two_char_display( unsigned int value, const std::string & dots = " " );
|
MidiByteArray two_char_display (unsigned int value, const std::string & dots = " ");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Timecode display. Only the difference between timecode and last_timecode will
|
Timecode display. Only the difference between timecode and last_timecode will
|
||||||
be encoded, to save midi bandwidth. If they're the same, an empty array will
|
be encoded, to save midi bandwidth. If they're the same, an empty array will
|
||||||
be returned
|
be returned
|
||||||
*/
|
*/
|
||||||
MidiByteArray timecode_display( SurfacePort &, const std::string & timecode, const std::string & last_timecode = "" );
|
MidiByteArray timecode_display (Surface&, const std::string & timecode, const std::string & last_timecode = "");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
for displaying characters on the strip LCD
|
for displaying characters on the strip LCD
|
||||||
pass SurfacePort so we know which sysex header to use
|
pass SurfacePort so we know which sysex header to use
|
||||||
*/
|
*/
|
||||||
MidiByteArray strip_display( SurfacePort &, const Strip & strip, unsigned int line_number, const std::string & line );
|
MidiByteArray strip_display (Surface &, const Strip & strip, unsigned int line_number, const std::string & line);
|
||||||
|
|
||||||
/// blank the strip LCD, ie write all spaces. Pass SurfacePort so we know which sysex header to use.
|
/// blank the strip LCD, ie write all spaces. Pass Surface so we know which sysex header to use.
|
||||||
MidiByteArray strip_display_blank( SurfacePort &, const Strip & strip, unsigned int line_number );
|
MidiByteArray strip_display_blank (Surface&, const Strip & strip, unsigned int line_number);
|
||||||
|
|
||||||
/// for generating all strip names. Pass SurfacePort so we know which sysex header to use.
|
/// for generating all strip names. Pass SurfacePort so we know which sysex header to use.
|
||||||
MidiByteArray all_strips_display( SurfacePort &, std::vector<std::string> & lines1, std::vector<std::string> & lines2 );
|
MidiByteArray all_strips_display (SurfacePort &, std::vector<std::string> & lines1, std::vector<std::string> & lines2);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
static MIDI::byte calculate_pot_value( midi_pot_mode mode, const ControlState & );
|
static MIDI::byte calculate_pot_value (midi_pot_mode mode, const ControlState &);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,416 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2006,2007 John Anderson
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include <glibmm/main.h>
|
|
||||||
#include <boost/shared_array.hpp>
|
|
||||||
|
|
||||||
#include "mackie_port.h"
|
|
||||||
|
|
||||||
#include "mackie_control_exception.h"
|
|
||||||
#include "mackie_control_protocol.h"
|
|
||||||
#include "mackie_midi_builder.h"
|
|
||||||
#include "controls.h"
|
|
||||||
#include "surface.h"
|
|
||||||
|
|
||||||
#include "fader.h"
|
|
||||||
#include "button.h"
|
|
||||||
#include "strip.h"
|
|
||||||
#include "pot.h"
|
|
||||||
#include "control_group.h"
|
|
||||||
|
|
||||||
#include "midi++/types.h"
|
|
||||||
#include "midi++/port.h"
|
|
||||||
|
|
||||||
#include "ardour/debug.h"
|
|
||||||
#include "ardour/rc_configuration.h"
|
|
||||||
|
|
||||||
#include "i18n.h"
|
|
||||||
|
|
||||||
using namespace std;
|
|
||||||
using namespace Mackie;
|
|
||||||
using namespace ARDOUR;
|
|
||||||
using namespace PBD;
|
|
||||||
|
|
||||||
// The MCU sysex header
|
|
||||||
MidiByteArray mackie_sysex_hdr (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x10);
|
|
||||||
|
|
||||||
// The MCU extender sysex header
|
|
||||||
MidiByteArray mackie_sysex_hdr_xt (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x11);
|
|
||||||
|
|
||||||
MackiePort::MackiePort (MackieControlProtocol & mcp, MIDI::Port & input_port, MIDI::Port & output_port, int number, port_type_t port_type)
|
|
||||||
: SurfacePort (input_port, output_port, number)
|
|
||||||
, _mcp (mcp)
|
|
||||||
, _port_type (port_type)
|
|
||||||
, _emulation (none)
|
|
||||||
, _initialising (true)
|
|
||||||
, _connected (false)
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::MackiePort\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
MackiePort::~MackiePort()
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::~MackiePort\n");
|
|
||||||
close();
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "~MackiePort finished\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
int MackiePort::strips() const
|
|
||||||
{
|
|
||||||
if (_port_type == mcu)
|
|
||||||
{
|
|
||||||
switch (_emulation)
|
|
||||||
{
|
|
||||||
// BCF2000 only has 8 faders, so reserve one for master
|
|
||||||
case bcf2000: return 7;
|
|
||||||
case mackie: return 8;
|
|
||||||
case none:
|
|
||||||
default:
|
|
||||||
throw MackieControlException ("MackiePort::strips: don't know what emulation we're using");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// must be an extender, ie no master fader
|
|
||||||
return 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// should really be in MackiePort
|
|
||||||
void MackiePort::open()
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackiePort::open %1\n", *this));
|
|
||||||
|
|
||||||
input_port().parser()->sysex.connect_same_thread (sysex_connection, boost::bind (&MackiePort::handle_midi_sysex, this, _1, _2, _3));
|
|
||||||
|
|
||||||
// make sure the device is connected
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MackiePort::close()
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::close\n");
|
|
||||||
|
|
||||||
// disconnect signals
|
|
||||||
|
|
||||||
sysex_connection.disconnect();
|
|
||||||
ScopedConnectionList::drop_connections ();
|
|
||||||
_connected = false;
|
|
||||||
|
|
||||||
// TODO emit a "closing" signal?
|
|
||||||
}
|
|
||||||
|
|
||||||
const MidiByteArray & MackiePort::sysex_hdr() const
|
|
||||||
{
|
|
||||||
switch (_port_type)
|
|
||||||
{
|
|
||||||
case mcu: return mackie_sysex_hdr;
|
|
||||||
case ext: return mackie_sysex_hdr_xt;
|
|
||||||
}
|
|
||||||
cout << "MackiePort::sysex_hdr _port_type not known" << endl;
|
|
||||||
return mackie_sysex_hdr;
|
|
||||||
}
|
|
||||||
|
|
||||||
MidiByteArray calculate_challenge_response (MidiByteArray::iterator begin, MidiByteArray::iterator end)
|
|
||||||
{
|
|
||||||
MidiByteArray l;
|
|
||||||
back_insert_iterator<MidiByteArray> back (l);
|
|
||||||
copy (begin, end, back);
|
|
||||||
|
|
||||||
MidiByteArray retval;
|
|
||||||
|
|
||||||
// this is how to calculate the response to the challenge.
|
|
||||||
// from the Logic docs.
|
|
||||||
retval << (0x7f & (l[0] + (l[1] ^ 0xa) - l[3]));
|
|
||||||
retval << (0x7f & ( (l[2] >> l[3]) ^ (l[0] + l[3])));
|
|
||||||
retval << (0x7f & ((l[3] - (l[2] << 2)) ^ (l[0] | l[1])));
|
|
||||||
retval << (0x7f & (l[1] - l[2] + (0xf0 ^ (l[3] << 4))));
|
|
||||||
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not used right now
|
|
||||||
MidiByteArray MackiePort::host_connection_query (MidiByteArray & bytes)
|
|
||||||
{
|
|
||||||
MidiByteArray response;
|
|
||||||
|
|
||||||
// handle host connection query
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host connection query: %1\n", bytes));
|
|
||||||
|
|
||||||
if (bytes.size() != 18) {
|
|
||||||
finalise_init (false);
|
|
||||||
cerr << "expecting 18 bytes, read " << bytes << " from " << input_port().name() << endl;
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// build and send host connection reply
|
|
||||||
response << 0x02;
|
|
||||||
copy (bytes.begin() + 6, bytes.begin() + 6 + 7, back_inserter (response));
|
|
||||||
response << calculate_challenge_response (bytes.begin() + 6 + 7, bytes.begin() + 6 + 7 + 4);
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
// not used right now
|
|
||||||
MidiByteArray MackiePort::host_connection_confirmation (const MidiByteArray & bytes)
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host_connection_confirmation: %1\n", bytes));
|
|
||||||
|
|
||||||
// decode host connection confirmation
|
|
||||||
if (bytes.size() != 14) {
|
|
||||||
finalise_init (false);
|
|
||||||
ostringstream os;
|
|
||||||
os << "expecting 14 bytes, read " << bytes << " from " << input_port().name();
|
|
||||||
throw MackieControlException (os.str());
|
|
||||||
}
|
|
||||||
|
|
||||||
// send version request
|
|
||||||
return MidiByteArray (2, 0x13, 0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MackiePort::probe_emulation (const MidiByteArray &)
|
|
||||||
{
|
|
||||||
#if 0
|
|
||||||
cout << "MackiePort::probe_emulation: " << bytes.size() << ", " << bytes << endl;
|
|
||||||
|
|
||||||
MidiByteArray version_string;
|
|
||||||
|
|
||||||
for (int i = 6; i < 11; ++i) {
|
|
||||||
version_string << bytes[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
cout << "version_string: " << version_string << endl;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// TODO investigate using serial number. Also, possibly size of bytes might
|
|
||||||
// give an indication. Also, apparently MCU sends non-documented messages
|
|
||||||
// sometimes.
|
|
||||||
if (!_initialising) {
|
|
||||||
//cout << "MackiePort::probe_emulation out of sequence." << endl;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
finalise_init (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MackiePort::init()
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::init\n");
|
|
||||||
|
|
||||||
init_mutex.lock();
|
|
||||||
_initialising = true;
|
|
||||||
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::init lock acquired\n");
|
|
||||||
|
|
||||||
// emit pre-init signal
|
|
||||||
init_event();
|
|
||||||
|
|
||||||
// kick off initialisation. See docs in header file for init()
|
|
||||||
|
|
||||||
// bypass the init sequence because sometimes the first
|
|
||||||
// message doesn't get to the unit, and there's no way
|
|
||||||
// to do a timed lock in Glib.
|
|
||||||
//write_sysex (MidiByteArray (2, 0x13, 0x00));
|
|
||||||
|
|
||||||
finalise_init (true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MackiePort::finalise_init (bool yn)
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::finalise_init\n");
|
|
||||||
|
|
||||||
bool emulation_ok = false;
|
|
||||||
|
|
||||||
// probing doesn't work very well, so just use a config variable
|
|
||||||
// to set the emulation mode
|
|
||||||
// TODO This might have to be specified on a per-port basis
|
|
||||||
// in the config file
|
|
||||||
// if an mcu and a bcf are needed to work as one surface
|
|
||||||
if (_emulation == none) {
|
|
||||||
|
|
||||||
// TODO same as code in mackie_control_protocol.cc
|
|
||||||
if (ARDOUR::Config->get_mackie_emulation() == "bcf") {
|
|
||||||
_emulation = bcf2000;
|
|
||||||
emulation_ok = true;
|
|
||||||
} else if (ARDOUR::Config->get_mackie_emulation() == "mcu") {
|
|
||||||
_emulation = mackie;
|
|
||||||
emulation_ok = true;
|
|
||||||
} else {
|
|
||||||
cout << "unknown mackie emulation: " << ARDOUR::Config->get_mackie_emulation() << endl;
|
|
||||||
emulation_ok = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
yn = yn && emulation_ok;
|
|
||||||
|
|
||||||
SurfacePort::active (yn);
|
|
||||||
|
|
||||||
if (yn) {
|
|
||||||
active_event();
|
|
||||||
|
|
||||||
// start handling messages from controls
|
|
||||||
connect_to_signals ();
|
|
||||||
}
|
|
||||||
|
|
||||||
_initialising = false;
|
|
||||||
init_cond.signal();
|
|
||||||
init_mutex.unlock();
|
|
||||||
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::finalise_init lock released\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void MackiePort::connect_to_signals ()
|
|
||||||
{
|
|
||||||
if (!_connected) {
|
|
||||||
|
|
||||||
MIDI::Parser* p = input_port().parser();
|
|
||||||
|
|
||||||
/* V-Pot messages are Controller */
|
|
||||||
p->controller.connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_controller_message, this, _1, _2));
|
|
||||||
/* Button messages are NoteOn */
|
|
||||||
p->note_on.connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_note_on_message, this, _1, _2));
|
|
||||||
/* Fader messages are Pitchbend */
|
|
||||||
p->channel_pitchbend[0].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 0U));
|
|
||||||
p->channel_pitchbend[1].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 1U));
|
|
||||||
p->channel_pitchbend[2].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 2U));
|
|
||||||
p->channel_pitchbend[3].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 3U));
|
|
||||||
p->channel_pitchbend[4].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 4U));
|
|
||||||
p->channel_pitchbend[5].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 5U));
|
|
||||||
p->channel_pitchbend[6].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 6U));
|
|
||||||
p->channel_pitchbend[7].connect_same_thread (*this, boost::bind (&MackiePort::handle_midi_pitchbend_message, this, _1, _2, 7U));
|
|
||||||
|
|
||||||
_connected = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MackiePort::wait_for_init()
|
|
||||||
{
|
|
||||||
Glib::Mutex::Lock lock (init_mutex);
|
|
||||||
while (_initialising) {
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::wait_for_active waiting\n");
|
|
||||||
init_cond.wait (init_mutex);
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::wait_for_active released\n");
|
|
||||||
}
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "MackiePort::wait_for_active returning\n");
|
|
||||||
return SurfacePort::active();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MackiePort::handle_midi_sysex (MIDI::Parser &, MIDI::byte * raw_bytes, size_t count)
|
|
||||||
{
|
|
||||||
MidiByteArray bytes (count, raw_bytes);
|
|
||||||
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi_sysex: %1\n", bytes));
|
|
||||||
|
|
||||||
switch (bytes[5])
|
|
||||||
{
|
|
||||||
case 0x01:
|
|
||||||
write_sysex (host_connection_query (bytes));
|
|
||||||
break;
|
|
||||||
case 0x03:
|
|
||||||
// not used right now
|
|
||||||
write_sysex (host_connection_confirmation (bytes));
|
|
||||||
break;
|
|
||||||
case 0x04:
|
|
||||||
inactive_event ();
|
|
||||||
cout << "host connection error" << bytes << endl;
|
|
||||||
break;
|
|
||||||
case 0x14:
|
|
||||||
probe_emulation (bytes);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
cout << "unknown sysex: " << bytes << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MackiePort::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb, uint32_t fader_id)
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi pitchbend on port %3 (number %4), fader = %1 value = %2\n",
|
|
||||||
(8*number()) + fader_id, pb, *this, number()));
|
|
||||||
|
|
||||||
Control* control = _mcp.surface().faders[(8*number()) + fader_id];
|
|
||||||
|
|
||||||
if (control) {
|
|
||||||
float midi_pos = pb >> 4; // only the top 10 bytes are used
|
|
||||||
_mcp.handle_control_event (*this, *control, midi_pos / 1023.0);
|
|
||||||
} else {
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "fader not found\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MackiePort::handle_midi_note_on_message (MIDI::Parser &, MIDI::EventTwoBytes* ev)
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackiePort::handle_note_on %1 = %2\n", ev->note_number, ev->velocity));
|
|
||||||
|
|
||||||
Control* control = _mcp.surface().buttons[(8*number()) + ev->note_number];
|
|
||||||
|
|
||||||
if (control) {
|
|
||||||
ControlState control_state (ev->velocity == 0x7f ? press : release);
|
|
||||||
control->set_in_use (control_state.button_state == press);
|
|
||||||
control_event (*this, *control, control_state);
|
|
||||||
} else {
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "button not found\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MackiePort::handle_midi_controller_message (MIDI::Parser &, MIDI::EventTwoBytes* ev)
|
|
||||||
{
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackiePort::handle_midi_controller %1 = %2\n", ev->controller_number, ev->value));
|
|
||||||
|
|
||||||
Control* control = _mcp.surface().pots[(8*number()) + ev->controller_number];
|
|
||||||
|
|
||||||
if (!control && ev->controller_number == Control::jog_base_id) {
|
|
||||||
control = _mcp.surface().controls_by_name["jog"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (control) {
|
|
||||||
ControlState state;
|
|
||||||
|
|
||||||
// bytes[2] & 0b01000000 (0x40) give sign
|
|
||||||
state.sign = (ev->value & 0x40) == 0 ? 1 : -1;
|
|
||||||
// bytes[2] & 0b00111111 (0x3f) gives delta
|
|
||||||
state.ticks = (ev->value & 0x3f);
|
|
||||||
if (state.ticks == 0) {
|
|
||||||
/* euphonix and perhaps other devices send zero
|
|
||||||
when they mean 1, we think.
|
|
||||||
*/
|
|
||||||
state.ticks = 1;
|
|
||||||
}
|
|
||||||
state.delta = float (state.ticks) / float (0x3f);
|
|
||||||
|
|
||||||
/* Pots only emit events when they move, not when they
|
|
||||||
stop moving. So to get a stop event, we need to use a timeout.
|
|
||||||
*/
|
|
||||||
|
|
||||||
control->set_in_use (true);
|
|
||||||
_mcp.add_in_use_timeout (*this, *control, control);
|
|
||||||
|
|
||||||
control_event (*this, *control, state);
|
|
||||||
} else {
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "pot not found\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
MackiePort::control_event (SurfacePort& sp, Control& c, const ControlState& cs)
|
|
||||||
{
|
|
||||||
_mcp.handle_control_event (sp, c, cs);
|
|
||||||
}
|
|
||||||
|
|
@ -1,125 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2006,2007 John Anderson
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
#ifndef mackie_port_h
|
|
||||||
#define mackie_port_h
|
|
||||||
|
|
||||||
#include <midi++/types.h>
|
|
||||||
#include <glibmm/thread.h>
|
|
||||||
|
|
||||||
#include "pbd/signals.h"
|
|
||||||
|
|
||||||
#include "surface_port.h"
|
|
||||||
#include "midi_byte_array.h"
|
|
||||||
#include "types.h"
|
|
||||||
|
|
||||||
namespace MIDI {
|
|
||||||
class Port;
|
|
||||||
class Parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MackieControlProtocol;
|
|
||||||
|
|
||||||
namespace Mackie
|
|
||||||
{
|
|
||||||
|
|
||||||
class MackiePort : public SurfacePort
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
enum port_type_t { mcu, ext };
|
|
||||||
enum emulation_t { none, mackie, bcf2000 };
|
|
||||||
|
|
||||||
MackiePort (MackieControlProtocol & mcp, MIDI::Port & input_port, MIDI::Port & output_port, int number, port_type_t = mcu);
|
|
||||||
~MackiePort();
|
|
||||||
|
|
||||||
virtual void open();
|
|
||||||
virtual void close();
|
|
||||||
|
|
||||||
/// MCU and extenders have different sysex headers
|
|
||||||
virtual const MidiByteArray & sysex_hdr() const;
|
|
||||||
|
|
||||||
/// Handle device initialisation
|
|
||||||
void handle_midi_sysex( MIDI::Parser &, MIDI::byte *, size_t count );
|
|
||||||
void handle_midi_pitchbend_message (MIDI::Parser &, MIDI::pitchbend_t, uint32_t channel_id);
|
|
||||||
void handle_midi_controller_message (MIDI::Parser &, MIDI::EventTwoBytes*);
|
|
||||||
void handle_midi_note_on_message (MIDI::Parser &, MIDI::EventTwoBytes*);
|
|
||||||
|
|
||||||
/// return the number of strips associated with this port
|
|
||||||
virtual int strips() const;
|
|
||||||
|
|
||||||
/// Block until the port has finished initialising, and then return
|
|
||||||
/// whether the intialisation succeeded
|
|
||||||
bool wait_for_init();
|
|
||||||
|
|
||||||
emulation_t emulation() const { return _emulation; }
|
|
||||||
|
|
||||||
/// Connect the any signal from the parser to handle_midi_any
|
|
||||||
/// unless it's already connected
|
|
||||||
void connect_to_signals ();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
/**
|
|
||||||
The initialisation sequence is fairly complex. First a lock is acquired
|
|
||||||
so that a condition can be used to signal the end of the init process.
|
|
||||||
Then a sysex is sent to the device. The response to the sysex
|
|
||||||
is handled by a switch in handle_midi_sysex which calls one of the
|
|
||||||
other methods.
|
|
||||||
|
|
||||||
However, windows DAWs ignore the documented init sequence and so we
|
|
||||||
do too. Thanks to Essox for helping with this.
|
|
||||||
|
|
||||||
So we use the version firmware to figure out what device is on
|
|
||||||
the other end of the cable.
|
|
||||||
*/
|
|
||||||
void init();
|
|
||||||
|
|
||||||
/**
|
|
||||||
Once the device is initialised, finalise_init(true) is called, which
|
|
||||||
releases the lock and signals the condition, and starts handling incoming
|
|
||||||
messages. finalise_init(false) will also release the lock but doesn't
|
|
||||||
start handling messages.
|
|
||||||
*/
|
|
||||||
void finalise_init( bool yn );
|
|
||||||
|
|
||||||
MidiByteArray host_connection_query( MidiByteArray & bytes );
|
|
||||||
MidiByteArray host_connection_confirmation( const MidiByteArray & bytes );
|
|
||||||
|
|
||||||
/**
|
|
||||||
Will set _emulation to what it thinks is correct, based
|
|
||||||
on responses from the device. Or get/set parameters. Or
|
|
||||||
environment variables. Or existence of a file.
|
|
||||||
*/
|
|
||||||
void probe_emulation( const MidiByteArray & bytes );
|
|
||||||
|
|
||||||
void control_event (SurfacePort &, Control &, const ControlState &);
|
|
||||||
|
|
||||||
private:
|
|
||||||
MackieControlProtocol & _mcp;
|
|
||||||
port_type_t _port_type;
|
|
||||||
PBD::ScopedConnection any_connection;
|
|
||||||
PBD::ScopedConnection sysex_connection;
|
|
||||||
emulation_t _emulation;
|
|
||||||
|
|
||||||
bool _initialising;
|
|
||||||
bool _connected;
|
|
||||||
Glib::Cond init_cond;
|
|
||||||
Glib::Mutex init_mutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
#include <cmath>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
#include "controls.h"
|
|
||||||
#include "mackie_surface.h"
|
|
||||||
#include "mackie_midi_builder.h"
|
|
||||||
#include "surface_port.h"
|
|
||||||
|
|
||||||
using namespace Mackie;
|
|
||||||
|
|
||||||
void
|
|
||||||
MackieSurface::display_timecode (SurfacePort & port, MackieMidiBuilder & builder, const std::string & timecode, const std::string & timecode_last)
|
|
||||||
{
|
|
||||||
port.write (builder.timecode_display (port, timecode, timecode_last));
|
|
||||||
}
|
|
||||||
|
|
||||||
float
|
|
||||||
MackieSurface::scaled_delta (const ControlState & state, float current_speed)
|
|
||||||
{
|
|
||||||
return state.sign * (std::pow (float(state.ticks + 1), 2) + current_speed) / 100.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
#ifndef mackie_surface_mackie_h
|
|
||||||
#define mackie_surface_mackie_h
|
|
||||||
/*
|
|
||||||
Generated by scripts/generate-surface.rb
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "surface.h"
|
|
||||||
|
|
||||||
namespace Mackie
|
|
||||||
{
|
|
||||||
|
|
||||||
class MackieButtonHandler;
|
|
||||||
class MackieSurface : public Surface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MackieSurface (uint32_t max_strips) : Surface (max_strips, 8) {}
|
|
||||||
|
|
||||||
virtual bool has_timecode_display() const { return true; }
|
|
||||||
virtual void display_timecode (SurfacePort &, MackieMidiBuilder &, const std::string & timecode, const std::string & timecode_last);
|
|
||||||
|
|
||||||
virtual float scrub_scaling_factor() { return 100.0; }
|
|
||||||
virtual float scaled_delta (const ControlState & state, float current_speed);
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
#include "ardour/rc_configuration.h"
|
#include "ardour/rc_configuration.h"
|
||||||
|
|
||||||
#include "mackie_control_protocol.h"
|
#include "mackie_control_protocol.h"
|
||||||
|
#include "surface.h"
|
||||||
|
|
||||||
#include "i18n.h"
|
#include "i18n.h"
|
||||||
|
|
||||||
|
|
@ -89,8 +90,8 @@ LedState
|
||||||
MackieControlProtocol::left_press (Button &)
|
MackieControlProtocol::left_press (Button &)
|
||||||
{
|
{
|
||||||
Sorted sorted = get_sorted_routes();
|
Sorted sorted = get_sorted_routes();
|
||||||
if (sorted.size() > route_table.size()) {
|
if (sorted.size() > n_strips()) {
|
||||||
int new_initial = _current_initial_bank - route_table.size();
|
int new_initial = _current_initial_bank - n_strips();
|
||||||
if (new_initial < 0) {
|
if (new_initial < 0) {
|
||||||
new_initial = 0;
|
new_initial = 0;
|
||||||
}
|
}
|
||||||
|
|
@ -116,11 +117,13 @@ LedState
|
||||||
MackieControlProtocol::right_press (Button &)
|
MackieControlProtocol::right_press (Button &)
|
||||||
{
|
{
|
||||||
Sorted sorted = get_sorted_routes();
|
Sorted sorted = get_sorted_routes();
|
||||||
if (sorted.size() > route_table.size()) {
|
uint32_t strip_cnt = n_strips();
|
||||||
uint32_t delta = sorted.size() - (route_table.size() + _current_initial_bank);
|
|
||||||
|
|
||||||
if (delta > route_table.size()) {
|
if (sorted.size() > strip_cnt) {
|
||||||
delta = route_table.size();
|
uint32_t delta = sorted.size() - (strip_cnt + _current_initial_bank);
|
||||||
|
|
||||||
|
if (delta > strip_cnt) {
|
||||||
|
delta = strip_cnt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
|
|
@ -230,7 +233,7 @@ LedState
|
||||||
MackieControlProtocol::channel_left_press (Button &)
|
MackieControlProtocol::channel_left_press (Button &)
|
||||||
{
|
{
|
||||||
Sorted sorted = get_sorted_routes();
|
Sorted sorted = get_sorted_routes();
|
||||||
if (sorted.size() > route_table.size()) {
|
if (sorted.size() > n_strips()) {
|
||||||
prev_track();
|
prev_track();
|
||||||
return on;
|
return on;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -248,7 +251,7 @@ LedState
|
||||||
MackieControlProtocol::channel_right_press (Button &)
|
MackieControlProtocol::channel_right_press (Button &)
|
||||||
{
|
{
|
||||||
Sorted sorted = get_sorted_routes();
|
Sorted sorted = get_sorted_routes();
|
||||||
if (sorted.size() > route_table.size()) {
|
if (sorted.size() > n_strips()) {
|
||||||
next_track();
|
next_track();
|
||||||
return on;
|
return on;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -505,17 +508,21 @@ MackieControlProtocol::record_release (Button &)
|
||||||
LedState
|
LedState
|
||||||
MackieControlProtocol::rewind_press (Button &)
|
MackieControlProtocol::rewind_press (Button &)
|
||||||
{
|
{
|
||||||
_jog_wheel.push (JogWheel::speed);
|
JogWheel* jog = surfaces.front()->jog_wheel();
|
||||||
_jog_wheel.transport_direction (-1);
|
assert (jog);
|
||||||
session->request_transport_speed (-_jog_wheel.transport_speed());
|
jog->push (JogWheel::speed);
|
||||||
|
jog->transport_direction (-1);
|
||||||
|
session->request_transport_speed (-jog->transport_speed());
|
||||||
return on;
|
return on;
|
||||||
}
|
}
|
||||||
|
|
||||||
LedState
|
LedState
|
||||||
MackieControlProtocol::rewind_release (Button &)
|
MackieControlProtocol::rewind_release (Button &)
|
||||||
{
|
{
|
||||||
_jog_wheel.pop();
|
JogWheel* jog = surfaces.front()->jog_wheel();
|
||||||
_jog_wheel.transport_direction (0);
|
assert (jog);
|
||||||
|
jog->pop();
|
||||||
|
jog->transport_direction (0);
|
||||||
if (_transport_previously_rolling) {
|
if (_transport_previously_rolling) {
|
||||||
session->request_transport_speed (1.0);
|
session->request_transport_speed (1.0);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -527,17 +534,21 @@ MackieControlProtocol::rewind_release (Button &)
|
||||||
LedState
|
LedState
|
||||||
MackieControlProtocol::ffwd_press (Button &)
|
MackieControlProtocol::ffwd_press (Button &)
|
||||||
{
|
{
|
||||||
_jog_wheel.push (JogWheel::speed);
|
JogWheel* jog = surfaces.front()->jog_wheel();
|
||||||
_jog_wheel.transport_direction (1);
|
assert (jog);
|
||||||
session->request_transport_speed (_jog_wheel.transport_speed());
|
jog->push (JogWheel::speed);
|
||||||
|
jog->transport_direction (1);
|
||||||
|
session->request_transport_speed (jog->transport_speed());
|
||||||
return on;
|
return on;
|
||||||
}
|
}
|
||||||
|
|
||||||
LedState
|
LedState
|
||||||
MackieControlProtocol::ffwd_release (Button &)
|
MackieControlProtocol::ffwd_release (Button &)
|
||||||
{
|
{
|
||||||
_jog_wheel.pop();
|
JogWheel* jog = surfaces.front()->jog_wheel();
|
||||||
_jog_wheel.transport_direction (0);
|
assert (jog);
|
||||||
|
jog->pop();
|
||||||
|
jog->transport_direction (0);
|
||||||
if (_transport_previously_rolling) {
|
if (_transport_previously_rolling) {
|
||||||
session->request_transport_speed (1.0);
|
session->request_transport_speed (1.0);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -31,9 +31,9 @@ using namespace Mackie;
|
||||||
using namespace PBD;
|
using namespace PBD;
|
||||||
|
|
||||||
Control*
|
Control*
|
||||||
Meter::factory (Surface& surface, int id, int ordinal, const char* name, Group& group)
|
Meter::factory (Surface& surface, int id, const char* name, Group& group)
|
||||||
{
|
{
|
||||||
Meter* m = new Meter (id, ordinal, name, group);
|
Meter* m = new Meter (id, name, group);
|
||||||
surface.meters[id] = m;
|
surface.meters[id] = m;
|
||||||
surface.controls.push_back (m);
|
surface.controls.push_back (m);
|
||||||
group.add (*m);
|
group.add (*m);
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ class SurfacePort;
|
||||||
class Meter : public Control
|
class Meter : public Control
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Meter (int id, int ordinal, std::string name, Group & group)
|
Meter (int id, std::string name, Group & group)
|
||||||
: Control (id, ordinal, name, group)
|
: Control (id, name, group)
|
||||||
, last_segment_value_sent (-1)
|
, last_segment_value_sent (-1)
|
||||||
, overload_on (false) {}
|
, overload_on (false) {}
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ public:
|
||||||
|
|
||||||
MidiByteArray update_message (float dB);
|
MidiByteArray update_message (float dB);
|
||||||
|
|
||||||
static Control* factory (Surface&, int id, int ordinal, const char*, Group&);
|
static Control* factory (Surface&, int id, const char*, Group&);
|
||||||
|
|
||||||
int last_segment_value_sent;
|
int last_segment_value_sent;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,15 @@ namespace Mackie {
|
||||||
class Pot : public Control
|
class Pot : public Control
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Pot (int id, int ordinal, std::string name, Group & group)
|
Pot (int id, std::string name, Group & group)
|
||||||
: Control (id, ordinal, name, group)
|
: Control (id, name, group)
|
||||||
, _led_ring (id, ordinal, name + "_ring", group) {}
|
, _led_ring (id, name + "_ring", group) {}
|
||||||
|
|
||||||
virtual type_t type() const { return type_pot; }
|
virtual type_t type() const { return type_pot; }
|
||||||
|
|
||||||
virtual const LedRing & led_ring() const {return _led_ring; }
|
virtual const LedRing & led_ring() const {return _led_ring; }
|
||||||
|
|
||||||
static Control* factory (Surface&, int id, int ordinal, const char*, Group&);
|
static Control* factory (Surface&, int id, const char*, Group&);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LedRing _led_ring;
|
LedRing _led_ring;
|
||||||
|
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2006,2007 John Anderson
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#include "ardour/route.h"
|
|
||||||
#include "ardour/track.h"
|
|
||||||
#include "ardour/midi_ui.h"
|
|
||||||
#include "ardour/pannable.h"
|
|
||||||
#include "ardour/session_object.h" // for Properties::name
|
|
||||||
|
|
||||||
#include "mackie_control_protocol.h"
|
|
||||||
#include "route_signal.h"
|
|
||||||
#include "strip.h"
|
|
||||||
|
|
||||||
using namespace ARDOUR;
|
|
||||||
using namespace Mackie;
|
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
#define midi_ui_context() MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
|
|
||||||
#define ui_bind(f, ...) boost::protect (boost::bind (f, __VA_ARGS__))
|
|
||||||
|
|
||||||
void RouteSignal::connect()
|
|
||||||
{
|
|
||||||
if (_strip.has_solo()) {
|
|
||||||
_route->solo_control()->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_solo_changed, &_mcp, this), midi_ui_context());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_strip.has_mute()) {
|
|
||||||
_route->mute_control()->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_mute_changed, &_mcp, this), midi_ui_context());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_strip.has_gain()) {
|
|
||||||
_route->gain_control()->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_gain_changed, &_mcp, this, false), midi_ui_context());
|
|
||||||
}
|
|
||||||
|
|
||||||
_route->PropertyChanged.connect (connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_property_changed, &_mcp, _1, this), midi_ui_context());
|
|
||||||
|
|
||||||
if (_route->pannable()) {
|
|
||||||
_route->pannable()->pan_azimuth_control->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_panner_changed, &_mcp, this, false), midi_ui_context());
|
|
||||||
_route->pannable()->pan_width_control->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_panner_changed, &_mcp, this, false), midi_ui_context());
|
|
||||||
}
|
|
||||||
|
|
||||||
boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<ARDOUR::Track>(_route);
|
|
||||||
if (trk) {
|
|
||||||
trk->rec_enable_control()->Changed .connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_record_enable_changed, &_mcp, this), midi_ui_context());
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO this works when a currently-banked route is made inactive, but not
|
|
||||||
// when a route is activated which should be currently banked.
|
|
||||||
_route->active_changed.connect (connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_active_changed, &_mcp, this), midi_ui_context());
|
|
||||||
|
|
||||||
_route->DropReferences.connect (connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::refresh_current_bank, &_mcp), midi_ui_context());
|
|
||||||
|
|
||||||
// TODO
|
|
||||||
// SelectedChanged
|
|
||||||
// RemoteControlIDChanged. Better handled at Session level.
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
RouteSignal::disconnect()
|
|
||||||
{
|
|
||||||
connections.drop_connections ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
RouteSignal::notify_all()
|
|
||||||
{
|
|
||||||
if (_strip.has_solo()) {
|
|
||||||
_mcp.notify_solo_changed (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_strip.has_mute()) {
|
|
||||||
_mcp.notify_mute_changed (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_strip.has_gain()) {
|
|
||||||
_mcp.notify_gain_changed (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
_mcp.notify_property_changed (PBD::PropertyChange (ARDOUR::Properties::name), this);
|
|
||||||
|
|
||||||
if (_strip.has_vpot()) {
|
|
||||||
_mcp.notify_panner_changed (this);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_strip.has_recenable()) {
|
|
||||||
_mcp.notify_record_enable_changed (this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2006,2007 John Anderson
|
|
||||||
|
|
||||||
This program is free software; you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program; if not, write to the Free Software
|
|
||||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
||||||
*/
|
|
||||||
#ifndef route_signal_h
|
|
||||||
#define route_signal_h
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <boost/shared_ptr.hpp>
|
|
||||||
|
|
||||||
#include "pbd/signals.h"
|
|
||||||
|
|
||||||
#include "midi_byte_array.h"
|
|
||||||
|
|
||||||
class MackieControlProtocol;
|
|
||||||
|
|
||||||
namespace ARDOUR {
|
|
||||||
class Route;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Mackie
|
|
||||||
{
|
|
||||||
|
|
||||||
class Strip;
|
|
||||||
class SurfacePort;
|
|
||||||
|
|
||||||
/**
|
|
||||||
This class is intended to easily create and destroy the set of
|
|
||||||
connections from a route to a control surface strip. Instantiating
|
|
||||||
it will connect the signals, and destructing it will disconnect
|
|
||||||
the signals.
|
|
||||||
*/
|
|
||||||
class RouteSignal
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
RouteSignal(boost::shared_ptr<ARDOUR::Route> route, MackieControlProtocol & mcp, Strip & strip, SurfacePort & port )
|
|
||||||
: _route( route ), _mcp( mcp ), _strip( strip ), _port( port ), _last_gain_written(0.0)
|
|
||||||
{
|
|
||||||
connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
~RouteSignal()
|
|
||||||
{
|
|
||||||
disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
void connect();
|
|
||||||
void disconnect();
|
|
||||||
|
|
||||||
// call all signal handlers manually
|
|
||||||
void notify_all();
|
|
||||||
|
|
||||||
boost::shared_ptr<const ARDOUR::Route> route() const { return _route; }
|
|
||||||
Strip & strip() { return _strip; }
|
|
||||||
SurfacePort & port() { return _port; }
|
|
||||||
|
|
||||||
float last_gain_written() const { return _last_gain_written; }
|
|
||||||
void last_gain_written( float other ) { _last_gain_written = other; }
|
|
||||||
|
|
||||||
const MidiByteArray & last_pan_written() const { return _last_pan_written; }
|
|
||||||
void last_pan_written( const MidiByteArray & other ) { _last_pan_written = other; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
boost::shared_ptr<ARDOUR::Route> _route;
|
|
||||||
MackieControlProtocol & _mcp;
|
|
||||||
Strip & _strip;
|
|
||||||
SurfacePort & _port;
|
|
||||||
|
|
||||||
PBD::ScopedConnectionList connections;
|
|
||||||
|
|
||||||
// Last written values for the gain and pan, to avoid overloading
|
|
||||||
// the midi connection to the surface
|
|
||||||
float _last_gain_written;
|
|
||||||
MidiByteArray _last_pan_written;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
@ -21,6 +21,22 @@
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "strip.h"
|
#include "strip.h"
|
||||||
|
|
||||||
|
#include "midi++/port.h"
|
||||||
|
|
||||||
|
#include "pbd/compose.h"
|
||||||
|
#include "pbd/convert.h"
|
||||||
|
|
||||||
|
#include "ardour/debug.h"
|
||||||
|
#include "ardour/midi_ui.h"
|
||||||
|
#include "ardour/route.h"
|
||||||
|
#include "ardour/track.h"
|
||||||
|
#include "ardour/pannable.h"
|
||||||
|
#include "ardour/panner.h"
|
||||||
|
#include "ardour/rc_configuration.h"
|
||||||
|
#include "ardour/meter.h"
|
||||||
|
|
||||||
|
#include "mackie_control_protocol.h"
|
||||||
|
#include "surface.h"
|
||||||
#include "button.h"
|
#include "button.h"
|
||||||
#include "led.h"
|
#include "led.h"
|
||||||
#include "ledring.h"
|
#include "ledring.h"
|
||||||
|
|
@ -31,23 +47,16 @@
|
||||||
|
|
||||||
using namespace Mackie;
|
using namespace Mackie;
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace ARDOUR;
|
||||||
|
using namespace PBD;
|
||||||
|
|
||||||
Strip::Strip (const std::string& name, int index)
|
#define midi_ui_context() ARDOUR::MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
|
||||||
: Group (name)
|
#define ui_bind(f, ...) boost::protect (boost::bind (f, __VA_ARGS__))
|
||||||
, _solo (0)
|
|
||||||
, _recenable (0)
|
extern PBD::EventLoop::InvalidationRecord* __invalidator (sigc::trackable& trackable, const char*, int);
|
||||||
, _mute (0)
|
#define invalidator(x) __invalidator (*(MidiControlUI::instance()), __FILE__, __LINE__)
|
||||||
, _select (0)
|
|
||||||
, _vselect (0)
|
Strip::Strip (Surface& s, const std::string& name, int index, StripControlDefinition* ctls)
|
||||||
, _fader_touch (0)
|
|
||||||
, _vpot (0)
|
|
||||||
, _gain (0)
|
|
||||||
, _index (index)
|
|
||||||
{
|
|
||||||
/* master strip only */
|
|
||||||
}
|
|
||||||
|
|
||||||
Strip::Strip (Surface& surface, const std::string& name, int surface_number, int index, int unit_index, StripControlDefinition* ctls)
|
|
||||||
: Group (name)
|
: Group (name)
|
||||||
, _solo (0)
|
, _solo (0)
|
||||||
, _recenable (0)
|
, _recenable (0)
|
||||||
|
|
@ -58,16 +67,22 @@ Strip::Strip (Surface& surface, const std::string& name, int surface_number, int
|
||||||
, _vpot (0)
|
, _vpot (0)
|
||||||
, _gain (0)
|
, _gain (0)
|
||||||
, _index (index)
|
, _index (index)
|
||||||
|
, _surface (&s)
|
||||||
{
|
{
|
||||||
/* build the controls for this track, which will automatically add them
|
/* build the controls for this track, which will automatically add them
|
||||||
to the Group
|
to the Group
|
||||||
*/
|
*/
|
||||||
|
|
||||||
for (uint32_t i = 0; ctls[i].name[0]; ++i) {
|
for (uint32_t i = 0; ctls[i].name[0]; ++i) {
|
||||||
ctls[i].factory (surface, ctls[i].base_id + (8*surface_number) + unit_index, unit_index+1, ctls[i].name, *this);
|
ctls[i].factory (*_surface, ctls[i].base_id + index, ctls[i].name, *this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Strip::~Strip ()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
TODO could optimise this to use enum, but it's only
|
TODO could optimise this to use enum, but it's only
|
||||||
called during the protocol class instantiation.
|
called during the protocol class instantiation.
|
||||||
|
|
@ -206,3 +221,268 @@ std::ostream & Mackie::operator << (std::ostream & os, const Strip & strip)
|
||||||
|
|
||||||
return os;
|
return os;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::set_route (boost::shared_ptr<Route> r)
|
||||||
|
{
|
||||||
|
route_connections.drop_connections ();
|
||||||
|
|
||||||
|
_route = r;
|
||||||
|
|
||||||
|
if (r) {
|
||||||
|
|
||||||
|
if (has_solo()) {
|
||||||
|
_route->solo_control()->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_solo_changed, this), midi_ui_context());
|
||||||
|
}
|
||||||
|
if (has_mute()) {
|
||||||
|
_route->mute_control()->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_mute_changed, this), midi_ui_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_gain()) {
|
||||||
|
_route->gain_control()->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_gain_changed, this, false), midi_ui_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
_route->PropertyChanged.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_property_changed, this, _1), midi_ui_context());
|
||||||
|
|
||||||
|
if (_route->pannable()) {
|
||||||
|
_route->pannable()->pan_azimuth_control->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_panner_changed, this, false), midi_ui_context());
|
||||||
|
_route->pannable()->pan_width_control->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_panner_changed, this, false), midi_ui_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<ARDOUR::Track>(_route);
|
||||||
|
|
||||||
|
if (trk) {
|
||||||
|
trk->rec_enable_control()->Changed .connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_record_enable_changed, this), midi_ui_context());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this works when a currently-banked route is made inactive, but not
|
||||||
|
// when a route is activated which should be currently banked.
|
||||||
|
|
||||||
|
_route->active_changed.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_active_changed, this), midi_ui_context());
|
||||||
|
_route->DropReferences.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_route_deleted, this), midi_ui_context());
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// SelectedChanged
|
||||||
|
// RemoteControlIDChanged. Better handled at Session level.
|
||||||
|
|
||||||
|
/* Update */
|
||||||
|
|
||||||
|
notify_all ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::notify_all()
|
||||||
|
{
|
||||||
|
if (has_solo()) {
|
||||||
|
notify_solo_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_mute()) {
|
||||||
|
notify_mute_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_gain()) {
|
||||||
|
notify_gain_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_property_changed (PBD::PropertyChange (ARDOUR::Properties::name));
|
||||||
|
|
||||||
|
if (has_vpot()) {
|
||||||
|
notify_panner_changed ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_recenable()) {
|
||||||
|
notify_record_enable_changed ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::notify_solo_changed ()
|
||||||
|
{
|
||||||
|
if (_route) {
|
||||||
|
Button& button = solo();
|
||||||
|
_surface->write (builder.build_led (button, _route->soloed()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::notify_mute_changed ()
|
||||||
|
{
|
||||||
|
if (_route) {
|
||||||
|
Button & button = mute();
|
||||||
|
_surface->write (builder.build_led (button, _route->muted()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::notify_record_enable_changed ()
|
||||||
|
{
|
||||||
|
if (_route) {
|
||||||
|
Button & button = recenable();
|
||||||
|
_surface->write (builder.build_led (button, _route->record_enabled()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::notify_active_changed ()
|
||||||
|
{
|
||||||
|
_surface->mcp().refresh_current_bank();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::notify_route_deleted ()
|
||||||
|
{
|
||||||
|
_surface->mcp().refresh_current_bank();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::notify_gain_changed (bool force_update)
|
||||||
|
{
|
||||||
|
if (_route) {
|
||||||
|
Fader & fader = gain();
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("route %1 gain change, update fader %2 on port %3\n",
|
||||||
|
_route->name(),
|
||||||
|
fader.raw_id(),
|
||||||
|
_surface->port().output_port().name()));
|
||||||
|
if (!fader.in_use()) {
|
||||||
|
float gain_value = gain_to_slider_position (_route->gain_control()->get_value());
|
||||||
|
// check that something has actually changed
|
||||||
|
if (force_update || gain_value != _last_gain_written) {
|
||||||
|
_surface->write (builder.build_fader (fader, gain_value));
|
||||||
|
_last_gain_written = gain_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::notify_property_changed (const PropertyChange& what_changed)
|
||||||
|
{
|
||||||
|
if (!what_changed.contains (ARDOUR::Properties::name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_route) {
|
||||||
|
string line1;
|
||||||
|
string fullname = _route->name();
|
||||||
|
|
||||||
|
if (fullname.length() <= 6) {
|
||||||
|
line1 = fullname;
|
||||||
|
} else {
|
||||||
|
line1 = PBD::short_version (fullname, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
_surface->write (builder.strip_display (*_surface, *this, 0, line1));
|
||||||
|
_surface->write (builder.strip_display_blank (*_surface, *this, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::notify_panner_changed (bool force_update)
|
||||||
|
{
|
||||||
|
if (_route) {
|
||||||
|
Pot & pot = vpot();
|
||||||
|
boost::shared_ptr<Panner> panner = _route->panner();
|
||||||
|
if (panner) {
|
||||||
|
double pos = panner->position ();
|
||||||
|
|
||||||
|
// cache the MidiByteArray here, because the mackie led control is much lower
|
||||||
|
// resolution than the panner control. So we save lots of byte
|
||||||
|
// sends in spite of more work on the comparison
|
||||||
|
MidiByteArray bytes = builder.build_led_ring (pot, ControlState (on, pos), MackieMidiBuilder::midi_pot_mode_dot);
|
||||||
|
// check that something has actually changed
|
||||||
|
if (force_update || bytes != _last_pan_written)
|
||||||
|
{
|
||||||
|
_surface->write (bytes);
|
||||||
|
_last_pan_written = bytes;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_surface->write (builder.zero_control (pot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Strip::handle_button (SurfacePort & port, Control & control, ButtonState bs)
|
||||||
|
{
|
||||||
|
if (!_route) {
|
||||||
|
// no route so always switch the light off
|
||||||
|
// because no signals will be emitted by a non-route
|
||||||
|
_surface->write (builder.build_led (control.led(), off));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool state = false;
|
||||||
|
|
||||||
|
if (bs == press) {
|
||||||
|
if (control.name() == "recenable") {
|
||||||
|
state = !_route->record_enabled();
|
||||||
|
_route->set_record_enabled (state, this);
|
||||||
|
} else if (control.name() == "mute") {
|
||||||
|
state = !_route->muted();
|
||||||
|
_route->set_mute (state, this);
|
||||||
|
} else if (control.name() == "solo") {
|
||||||
|
state = !_route->soloed();
|
||||||
|
_route->set_solo (state, this);
|
||||||
|
} else if (control.name() == "select") {
|
||||||
|
_surface->mcp().select_track (_route);
|
||||||
|
} else if (control.name() == "vselect") {
|
||||||
|
// TODO could be used to select different things to apply the pot to?
|
||||||
|
//state = default_button_press (dynamic_cast<Button&> (control));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control.name() == "fader_touch") {
|
||||||
|
|
||||||
|
state = (bs == press);
|
||||||
|
|
||||||
|
gain().set_in_use (state);
|
||||||
|
|
||||||
|
if (ARDOUR::Config->get_mackie_emulation() == "bcf" && state) {
|
||||||
|
|
||||||
|
/* BCF faders don't support touch, so add a timeout to reset
|
||||||
|
their `in_use' state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
_surface->mcp().add_in_use_timeout (*_surface, gain(), &fader_touch());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::periodic ()
|
||||||
|
{
|
||||||
|
if (!_route) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_automation ();
|
||||||
|
update_meter ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::update_automation ()
|
||||||
|
{
|
||||||
|
ARDOUR::AutoState gain_state = _route->gain_control()->automation_state();
|
||||||
|
|
||||||
|
if (gain_state == Touch || gain_state == Play) {
|
||||||
|
notify_gain_changed (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_route->panner()) {
|
||||||
|
ARDOUR::AutoState panner_state = _route->panner()->automation_state();
|
||||||
|
if (panner_state == Touch || panner_state == Play) {
|
||||||
|
notify_panner_changed (false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Strip::update_meter ()
|
||||||
|
{
|
||||||
|
float dB = const_cast<PeakMeter&> (_route->peak_meter()).peak_power (0);
|
||||||
|
_surface->write (meter().update_message (dB));
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,14 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
|
#include "pbd/property_basics.h"
|
||||||
|
|
||||||
#include "control_group.h"
|
#include "control_group.h"
|
||||||
|
#include "mackie_midi_builder.h"
|
||||||
|
|
||||||
|
namespace ARDOUR {
|
||||||
|
class Route;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Mackie {
|
namespace Mackie {
|
||||||
|
|
||||||
|
|
@ -18,13 +25,13 @@ class Meter;
|
||||||
struct StripControlDefinition {
|
struct StripControlDefinition {
|
||||||
const char* name;
|
const char* name;
|
||||||
uint32_t base_id;
|
uint32_t base_id;
|
||||||
Control* (*factory)(Surface&, int index, int ordinal, const char* name, Group&);
|
Control* (*factory)(Surface&, int index, const char* name, Group&);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct GlobalControlDefinition {
|
struct GlobalControlDefinition {
|
||||||
const char* name;
|
const char* name;
|
||||||
uint32_t id;
|
uint32_t id;
|
||||||
Control* (*factory)(Surface&, int index, int ordinal, const char* name, Group&);
|
Control* (*factory)(Surface&, int index, const char* name, Group&);
|
||||||
const char* group_name;
|
const char* group_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -34,11 +41,12 @@ struct GlobalControlDefinition {
|
||||||
class Strip : public Group
|
class Strip : public Group
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Strip (const std::string& name, int index); /* master strip only */
|
Strip (Surface&, const std::string & name, int index, StripControlDefinition* ctls);
|
||||||
Strip (Surface&, const std::string & name, int surface_number, int index, int unit_index, StripControlDefinition* ctls);
|
~Strip();
|
||||||
|
|
||||||
virtual bool is_strip() const { return true; }
|
boost::shared_ptr<ARDOUR::Route> route() const { return _route; }
|
||||||
virtual void add (Control & control);
|
|
||||||
|
void add (Control & control);
|
||||||
int index() const { return _index; } // zero based
|
int index() const { return _index; } // zero based
|
||||||
|
|
||||||
Button & solo();
|
Button & solo();
|
||||||
|
|
@ -60,6 +68,16 @@ public:
|
||||||
bool has_vpot() const { return _vpot != 0; }
|
bool has_vpot() const { return _vpot != 0; }
|
||||||
bool has_gain() const { return _gain != 0; }
|
bool has_gain() const { return _gain != 0; }
|
||||||
bool has_meter() const { return _meter != 0; }
|
bool has_meter() const { return _meter != 0; }
|
||||||
|
|
||||||
|
void set_route (boost::shared_ptr<ARDOUR::Route>);
|
||||||
|
|
||||||
|
// call all signal handlers manually
|
||||||
|
void notify_all();
|
||||||
|
|
||||||
|
bool handle_button (SurfacePort & port, Control & control, ButtonState bs);
|
||||||
|
|
||||||
|
void periodic ();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Button* _solo;
|
Button* _solo;
|
||||||
Button* _recenable;
|
Button* _recenable;
|
||||||
|
|
@ -71,19 +89,35 @@ private:
|
||||||
Fader* _gain;
|
Fader* _gain;
|
||||||
Meter* _meter;
|
Meter* _meter;
|
||||||
int _index;
|
int _index;
|
||||||
|
Surface* _surface;
|
||||||
|
|
||||||
|
MackieMidiBuilder builder;
|
||||||
|
|
||||||
|
boost::shared_ptr<ARDOUR::Route> _route;
|
||||||
|
PBD::ScopedConnectionList route_connections;
|
||||||
|
|
||||||
|
// Last written values for the gain and pan, to avoid overloading
|
||||||
|
// the midi connection to the surface
|
||||||
|
float _last_gain_written;
|
||||||
|
MidiByteArray _last_pan_written;
|
||||||
|
|
||||||
|
|
||||||
|
void notify_solo_changed ();
|
||||||
|
void notify_mute_changed ();
|
||||||
|
void notify_record_enable_changed ();
|
||||||
|
void notify_gain_changed (bool force_update = true);
|
||||||
|
void notify_property_changed (const PBD::PropertyChange&);
|
||||||
|
void notify_panner_changed (bool force_update = true);
|
||||||
|
void notify_active_changed ();
|
||||||
|
void notify_route_deleted ();
|
||||||
|
|
||||||
|
void update_automation ();
|
||||||
|
void update_meter ();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream &, const Strip &);
|
std::ostream & operator << (std::ostream &, const Strip &);
|
||||||
|
|
||||||
class MasterStrip : public Strip
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MasterStrip (const std::string & name, int index)
|
|
||||||
: Strip (name, index) {}
|
|
||||||
|
|
||||||
virtual bool is_master() const { return true; }
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* __ardour_mackie_control_protocol_strip_h__ */
|
#endif /* __ardour_mackie_control_protocol_strip_h__ */
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,24 @@
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "midi++/port.h"
|
||||||
|
#include "midi++/manager.h"
|
||||||
|
|
||||||
#include "ardour/debug.h"
|
#include "ardour/debug.h"
|
||||||
|
#include "ardour/route.h"
|
||||||
|
#include "ardour/panner.h"
|
||||||
|
#include "ardour/panner_shell.h"
|
||||||
|
#include "ardour/rc_configuration.h"
|
||||||
|
|
||||||
#include "control_group.h"
|
#include "control_group.h"
|
||||||
#include "surface_port.h"
|
#include "surface_port.h"
|
||||||
#include "surface.h"
|
#include "surface.h"
|
||||||
#include "strip.h"
|
#include "strip.h"
|
||||||
|
#include "mackie_midi_builder.h"
|
||||||
|
#include "mackie_control_protocol.h"
|
||||||
|
#include "mackie_jog_wheel.h"
|
||||||
|
|
||||||
#include "strip.h"
|
#include "strip.h"
|
||||||
#include "button.h"
|
#include "button.h"
|
||||||
|
|
@ -19,29 +30,73 @@
|
||||||
#include "jog.h"
|
#include "jog.h"
|
||||||
#include "meter.h"
|
#include "meter.h"
|
||||||
|
|
||||||
|
#include "i18n.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace PBD;
|
using namespace PBD;
|
||||||
using namespace Mackie;
|
using namespace Mackie;
|
||||||
|
using ARDOUR::Route;
|
||||||
|
using ARDOUR::Panner;
|
||||||
|
using ARDOUR::Pannable;
|
||||||
|
using ARDOUR::PannerShell;
|
||||||
|
|
||||||
Surface::Surface (uint32_t max_strips, uint32_t unit_strips)
|
// The MCU sysex header
|
||||||
: _max_strips (max_strips)
|
static MidiByteArray mackie_sysex_hdr (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x10);
|
||||||
, _unit_strips( unit_strips )
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void Surface::init ()
|
// The MCU extender sysex header
|
||||||
|
static MidiByteArray mackie_sysex_hdr_xt (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x11);
|
||||||
|
|
||||||
|
static MidiByteArray empty_midi_byte_array;
|
||||||
|
|
||||||
|
Surface::Surface (MackieControlProtocol& mcp, jack_client_t* jack, const std::string& device_name, uint32_t number, surface_type_t stype)
|
||||||
|
: _mcp (mcp)
|
||||||
|
, _stype (stype)
|
||||||
|
, _number (number)
|
||||||
|
, _active (false)
|
||||||
|
, _connected (false)
|
||||||
|
, _jog_wheel (0)
|
||||||
{
|
{
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "Surface::init\n");
|
DEBUG_TRACE (DEBUG::MackieControl, "Surface::init\n");
|
||||||
|
|
||||||
|
MIDI::Manager * mm = MIDI::Manager::instance();
|
||||||
|
MIDI::Port * input = mm->add_port (new MIDI::Port (string_compose (_("%1 in"), device_name), MIDI::Port::IsInput, jack));
|
||||||
|
MIDI::Port * output = mm->add_port (new MIDI::Port (string_compose (_("%1 out"), device_name), MIDI::Port::IsOutput, jack));
|
||||||
|
|
||||||
strips.resize (_max_strips);
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("surface has ports named %1 and %2\n",
|
||||||
init_controls ();
|
input->name(), output->name()));
|
||||||
init_strips ();
|
|
||||||
|
_port = new SurfacePort (*this, *input, *output);
|
||||||
|
_port->open();
|
||||||
|
_port->inactive_event.connect_same_thread (*this, boost::bind (&Surface::handle_port_inactive, this, _port));
|
||||||
|
|
||||||
|
switch (stype) {
|
||||||
|
case mcu:
|
||||||
|
init_controls ();
|
||||||
|
_jog_wheel = new Mackie::JogWheel (_mcp);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (stype) {
|
||||||
|
case mcu:
|
||||||
|
case ext:
|
||||||
|
strips.resize (8);
|
||||||
|
init_strips ();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
DEBUG_TRACE (DEBUG::MackieControl, "Surface::init finish\n");
|
DEBUG_TRACE (DEBUG::MackieControl, "Surface::init finish\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
Surface::~Surface ()
|
Surface::~Surface ()
|
||||||
{
|
{
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, "Surface: destructor\n");
|
||||||
|
|
||||||
|
zero_all ();
|
||||||
|
|
||||||
// delete groups
|
// delete groups
|
||||||
for (Groups::iterator it = groups.begin(); it != groups.end(); ++it) {
|
for (Groups::iterator it = groups.begin(); it != groups.end(); ++it) {
|
||||||
delete it->second;
|
delete it->second;
|
||||||
|
|
@ -51,6 +106,20 @@ Surface::~Surface ()
|
||||||
for (Controls::iterator it = controls.begin(); it != controls.end(); ++it) {
|
for (Controls::iterator it = controls.begin(); it != controls.end(); ++it) {
|
||||||
delete *it;
|
delete *it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete _jog_wheel;
|
||||||
|
delete _port;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MidiByteArray&
|
||||||
|
Surface::sysex_hdr() const
|
||||||
|
{
|
||||||
|
switch (_stype) {
|
||||||
|
case mcu: return mackie_sysex_hdr;
|
||||||
|
case ext: return mackie_sysex_hdr_xt;
|
||||||
|
}
|
||||||
|
cout << "SurfacePort::sysex_hdr _port_type not known" << endl;
|
||||||
|
return mackie_sysex_hdr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GlobalControlDefinition mackie_global_controls[] = {
|
static GlobalControlDefinition mackie_global_controls[] = {
|
||||||
|
|
@ -143,14 +212,11 @@ Surface::init_controls()
|
||||||
groups["none"] = new Group ("none");
|
groups["none"] = new Group ("none");
|
||||||
groups["transport"] = new Group ("transport");
|
groups["transport"] = new Group ("transport");
|
||||||
groups["user"] = new Group ("user");
|
groups["user"] = new Group ("user");
|
||||||
|
groups["master"] = new Group ("master");
|
||||||
group = new MasterStrip ("master", 0);
|
|
||||||
groups["master"] = group;
|
|
||||||
strips[0] = dynamic_cast<Strip*> (group);
|
|
||||||
|
|
||||||
for (uint32_t n = 0; mackie_global_controls[n].name[0]; ++n) {
|
for (uint32_t n = 0; mackie_global_controls[n].name[0]; ++n) {
|
||||||
group = groups[mackie_global_controls[n].group_name];
|
group = groups[mackie_global_controls[n].group_name];
|
||||||
Control* control = mackie_global_controls[n].factory (*this, mackie_global_controls[n].id, 1, mackie_global_controls[n].name, *group);
|
Control* control = mackie_global_controls[n].factory (*this, mackie_global_controls[n].id, mackie_global_controls[n].name, *group);
|
||||||
controls_by_name[mackie_global_controls[n].name] = control;
|
controls_by_name[mackie_global_controls[n].name] = control;
|
||||||
group->add (*control);
|
group->add (*control);
|
||||||
}
|
}
|
||||||
|
|
@ -172,20 +238,366 @@ static StripControlDefinition mackie_strip_controls[] = {
|
||||||
void
|
void
|
||||||
Surface::init_strips ()
|
Surface::init_strips ()
|
||||||
{
|
{
|
||||||
for (uint32_t i = 0; i < _max_strips; ++i) {
|
for (uint32_t i = 0; i < 8; ++i) {
|
||||||
|
|
||||||
char name[32];
|
char name[32];
|
||||||
|
|
||||||
uint32_t unit_index = i % _unit_strips;
|
snprintf (name, sizeof (name), "strip_%d", (8* _number) + i);
|
||||||
|
|
||||||
snprintf (name, sizeof (name), "strip_%d", unit_index+1);
|
|
||||||
|
|
||||||
cerr << "Register strip " << i << " unit index " << unit_index << endl;
|
cerr << "Register strip " << i << endl;
|
||||||
|
|
||||||
Strip* strip = new Strip (*this, name, i/8, i, unit_index, mackie_strip_controls);
|
Strip* strip = new Strip (*this, name, i, mackie_strip_controls);
|
||||||
|
|
||||||
groups[name] = strip;
|
groups[name] = strip;
|
||||||
strips[i] = strip;
|
strips[i] = strip;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::display_timecode (const std::string & timecode, const std::string & timecode_last)
|
||||||
|
{
|
||||||
|
if (has_timecode_display()) {
|
||||||
|
_port->write (builder.timecode_display (*this, timecode, timecode_last));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float
|
||||||
|
Surface::scaled_delta (const ControlState & state, float current_speed)
|
||||||
|
{
|
||||||
|
return state.sign * (std::pow (float(state.ticks + 1), 2) + current_speed) / 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::display_bank_start (uint32_t current_bank)
|
||||||
|
{
|
||||||
|
if (current_bank == 0) {
|
||||||
|
// send Ar. to 2-char display on the master
|
||||||
|
_port->write (builder.two_char_display ("Ar", ".."));
|
||||||
|
} else {
|
||||||
|
// write the current first remote_id to the 2-char display
|
||||||
|
_port->write (builder.two_char_display (current_bank));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::blank_jog_ring ()
|
||||||
|
{
|
||||||
|
Control* control = controls_by_name["jog"];
|
||||||
|
|
||||||
|
if (control) {
|
||||||
|
_port->write (builder.build_led_ring (*(dynamic_cast<Pot*> (control)), off));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
Surface::has_timecode_display () const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
float
|
||||||
|
Surface::scrub_scaling_factor () const
|
||||||
|
{
|
||||||
|
return 100.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::connect_to_signals ()
|
||||||
|
{
|
||||||
|
if (!_connected) {
|
||||||
|
|
||||||
|
MIDI::Parser* p = _port->input_port().parser();
|
||||||
|
|
||||||
|
/* V-Pot messages are Controller */
|
||||||
|
p->controller.connect_same_thread (*this, boost::bind (&Surface::handle_midi_controller_message, this, _1, _2));
|
||||||
|
/* Button messages are NoteOn */
|
||||||
|
p->note_on.connect_same_thread (*this, boost::bind (&Surface::handle_midi_note_on_message, this, _1, _2));
|
||||||
|
/* Fader messages are Pitchbend */
|
||||||
|
p->channel_pitchbend[0].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 0U));
|
||||||
|
p->channel_pitchbend[1].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 1U));
|
||||||
|
p->channel_pitchbend[2].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 2U));
|
||||||
|
p->channel_pitchbend[3].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 3U));
|
||||||
|
p->channel_pitchbend[4].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 4U));
|
||||||
|
p->channel_pitchbend[5].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 5U));
|
||||||
|
p->channel_pitchbend[6].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 6U));
|
||||||
|
p->channel_pitchbend[7].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 7U));
|
||||||
|
|
||||||
|
_connected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb, uint32_t fader_id)
|
||||||
|
{
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi pitchbend on port %3, fader = %1 value = %2\n",
|
||||||
|
fader_id, pb, _number));
|
||||||
|
|
||||||
|
Control* control = faders[fader_id];
|
||||||
|
|
||||||
|
if (control) {
|
||||||
|
float midi_pos = pb >> 4; // only the top 10 bytes are used
|
||||||
|
handle_control_event (*control, midi_pos / 1023.0);
|
||||||
|
} else {
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, "fader not found\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::handle_midi_note_on_message (MIDI::Parser &, MIDI::EventTwoBytes* ev)
|
||||||
|
{
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::handle_note_on %1 = %2\n", ev->note_number, ev->velocity));
|
||||||
|
|
||||||
|
Control* control = buttons[ev->note_number];
|
||||||
|
|
||||||
|
if (control) {
|
||||||
|
ControlState control_state (ev->velocity == 0x7f ? press : release);
|
||||||
|
control->set_in_use (control_state.button_state == press);
|
||||||
|
handle_control_event (*control, control_state);
|
||||||
|
} else {
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, "button not found\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::handle_midi_controller_message (MIDI::Parser &, MIDI::EventTwoBytes* ev)
|
||||||
|
{
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::handle_midi_controller %1 = %2\n", ev->controller_number, ev->value));
|
||||||
|
|
||||||
|
Control* control = pots[ev->controller_number];
|
||||||
|
|
||||||
|
if (!control && ev->controller_number == Control::jog_base_id) {
|
||||||
|
control = controls_by_name["jog"];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (control) {
|
||||||
|
ControlState state;
|
||||||
|
|
||||||
|
// bytes[2] & 0b01000000 (0x40) give sign
|
||||||
|
state.sign = (ev->value & 0x40) == 0 ? 1 : -1;
|
||||||
|
// bytes[2] & 0b00111111 (0x3f) gives delta
|
||||||
|
state.ticks = (ev->value & 0x3f);
|
||||||
|
if (state.ticks == 0) {
|
||||||
|
/* euphonix and perhaps other devices send zero
|
||||||
|
when they mean 1, we think.
|
||||||
|
*/
|
||||||
|
state.ticks = 1;
|
||||||
|
}
|
||||||
|
state.delta = float (state.ticks) / float (0x3f);
|
||||||
|
|
||||||
|
/* Pots only emit events when they move, not when they
|
||||||
|
stop moving. So to get a stop event, we need to use a timeout.
|
||||||
|
*/
|
||||||
|
|
||||||
|
control->set_in_use (true);
|
||||||
|
_mcp.add_in_use_timeout (*this, *control, control);
|
||||||
|
|
||||||
|
handle_control_event (*control, state);
|
||||||
|
} else {
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, "pot not found\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::handle_control_event (Control & control, const ControlState & state)
|
||||||
|
{
|
||||||
|
// find the route for the control, if there is one
|
||||||
|
boost::shared_ptr<Route> route;
|
||||||
|
Strip* strip;
|
||||||
|
|
||||||
|
if ((strip = dynamic_cast<Strip*> (&control.group())) != 0) {
|
||||||
|
route = strip->route ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This handles control element events from the surface
|
||||||
|
// the state of the controls on the surface is usually updated
|
||||||
|
// from UI events.
|
||||||
|
|
||||||
|
switch (control.type()) {
|
||||||
|
case Control::type_fader:
|
||||||
|
// find the route in the route table for the id
|
||||||
|
// if the route isn't available, skip it
|
||||||
|
// at which point the fader should just reset itself
|
||||||
|
if (route != 0) {
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("fader to %1\n", state.pos));
|
||||||
|
|
||||||
|
route->gain_control()->set_value (slider_position_to_gain (state.pos));
|
||||||
|
|
||||||
|
if (ARDOUR::Config->get_mackie_emulation() == "bcf") {
|
||||||
|
/* reset the timeout while we're still moving the fader */
|
||||||
|
_mcp.add_in_use_timeout (*this, control, control.in_use_touch_control);
|
||||||
|
}
|
||||||
|
|
||||||
|
// must echo bytes back to slider now, because
|
||||||
|
// the notifier only works if the fader is not being
|
||||||
|
// touched. Which it is if we're getting input.
|
||||||
|
_port->write (builder.build_fader ((Fader&)control, state.pos));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Control::type_button:
|
||||||
|
if (strip) {
|
||||||
|
strip->handle_button (*_port, control, state.button_state);
|
||||||
|
} else {
|
||||||
|
// handle all non-strip buttons
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("global button %1\n", control.id()));
|
||||||
|
_mcp.handle_button_event (*this, dynamic_cast<Button&>(control), state.button_state);
|
||||||
|
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// pot (jog wheel, external control)
|
||||||
|
case Control::type_pot:
|
||||||
|
if (strip) {
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip pot %1\n", control.id()));
|
||||||
|
if (route) {
|
||||||
|
boost::shared_ptr<Panner> panner = route->panner_shell()->panner();
|
||||||
|
// pan for mono input routes, or stereo linked panners
|
||||||
|
if (panner) {
|
||||||
|
double p = panner->position ();
|
||||||
|
|
||||||
|
// calculate new value, and adjust
|
||||||
|
p += state.delta * state.sign;
|
||||||
|
p = min (1.0, p);
|
||||||
|
p = max (0.0, p);
|
||||||
|
panner->set_position (p);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// it's a pot for an umnapped route, so turn all the lights off
|
||||||
|
_port->write (builder.build_led_ring (dynamic_cast<Pot &> (control), off));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (control.is_jog()) {
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Jog wheel moved %1\n", state.ticks));
|
||||||
|
if (_jog_wheel) {
|
||||||
|
_jog_wheel->jog_event (*_port, control, state);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("External controller moved %1\n", state.ticks));
|
||||||
|
cout << "external controller" << state.ticks * state.sign << endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::handle_port_inactive (SurfacePort * port)
|
||||||
|
{
|
||||||
|
_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::write_sysex (const MidiByteArray & mba)
|
||||||
|
{
|
||||||
|
if (mba.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MidiByteArray buf;
|
||||||
|
buf << sysex_hdr() << mba << MIDI::eox;
|
||||||
|
_port->write (buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::write_sysex (MIDI::byte msg)
|
||||||
|
{
|
||||||
|
MidiByteArray buf;
|
||||||
|
buf << sysex_hdr() << msg << MIDI::eox;
|
||||||
|
_port->write (buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::drop_routes ()
|
||||||
|
{
|
||||||
|
for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
|
||||||
|
(*s)->set_route (boost::shared_ptr<Route>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t
|
||||||
|
Surface::n_strips () const
|
||||||
|
{
|
||||||
|
return strips.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Strip*
|
||||||
|
Surface::nth_strip (uint32_t n) const
|
||||||
|
{
|
||||||
|
if (n > n_strips()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return strips[n];
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::zero_all ()
|
||||||
|
{
|
||||||
|
// TODO turn off Timecode displays
|
||||||
|
|
||||||
|
// zero all strips
|
||||||
|
for (Strips::iterator it = strips.begin(); it != strips.end(); ++it) {
|
||||||
|
_port->write (builder.zero_strip (*this, **it));
|
||||||
|
}
|
||||||
|
|
||||||
|
// turn off global buttons and leds
|
||||||
|
// global buttons are only ever on mcu_port, so we don't have
|
||||||
|
// to figure out which port.
|
||||||
|
|
||||||
|
for (Controls::iterator it = controls.begin(); it != controls.end(); ++it) {
|
||||||
|
Control & control = **it;
|
||||||
|
if (!control.group().is_strip() && control.accepts_feedback()) {
|
||||||
|
_port->write (builder.zero_control (control));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// any hardware-specific stuff
|
||||||
|
// clear 2-char display
|
||||||
|
_port->write (builder.two_char_display ("LC"));
|
||||||
|
|
||||||
|
// and the led ring for the master strip
|
||||||
|
blank_jog_ring ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::periodic ()
|
||||||
|
{
|
||||||
|
for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) {
|
||||||
|
(*s)->periodic ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::write (const MidiByteArray& data)
|
||||||
|
{
|
||||||
|
_port->write (data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Surface::jog_wheel_state_display (JogWheel::State state)
|
||||||
|
{
|
||||||
|
switch (state) {
|
||||||
|
case JogWheel::zoom:
|
||||||
|
_port->write (builder.two_char_display ("Zm"));
|
||||||
|
break;
|
||||||
|
case JogWheel::scroll:
|
||||||
|
_port->write (builder.two_char_display ("Sc"));
|
||||||
|
break;
|
||||||
|
case JogWheel::scrub:
|
||||||
|
_port->write (builder.two_char_display ("Sb"));
|
||||||
|
break;
|
||||||
|
case JogWheel::shuttle:
|
||||||
|
_port->write (builder.two_char_display ("Sh"));
|
||||||
|
break;
|
||||||
|
case JogWheel::speed:
|
||||||
|
_port->write (builder.two_char_display ("Sp"));
|
||||||
|
break;
|
||||||
|
case JogWheel::select:
|
||||||
|
_port->write (builder.two_char_display ("Se"));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,21 @@
|
||||||
#ifndef mackie_surface_h
|
#ifndef mackie_surface_h
|
||||||
#define mackie_surface_h
|
#define mackie_surface_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "midi++/types.h"
|
||||||
|
|
||||||
#include "controls.h"
|
#include "controls.h"
|
||||||
#include "types.h"
|
#include "types.h"
|
||||||
#include <stdint.h>
|
#include "mackie_midi_builder.h"
|
||||||
|
#include "mackie_jog_wheel.h"
|
||||||
|
|
||||||
|
namespace MIDI {
|
||||||
|
class Parser;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MidiByteArray;
|
||||||
|
class MackieControlProtocol;
|
||||||
|
|
||||||
namespace Mackie
|
namespace Mackie
|
||||||
{
|
{
|
||||||
|
|
@ -19,53 +31,23 @@ class Pot;
|
||||||
class Led;
|
class Led;
|
||||||
class LedRing;
|
class LedRing;
|
||||||
|
|
||||||
/**
|
class Surface : public PBD::ScopedConnectionList
|
||||||
This represents an entire control surface, made up of Groups,
|
|
||||||
Strips and Controls. There are several collections for
|
|
||||||
ease of addressing in different ways, but only one collection
|
|
||||||
has definitive ownership.
|
|
||||||
|
|
||||||
It handles mapping button ids to press_ and release_ calls.
|
|
||||||
|
|
||||||
There are various emulations of the Mackie around, so specific
|
|
||||||
emulations will inherit from this to change button mapping, or
|
|
||||||
have 7 fader channels instead of 8, or whatever.
|
|
||||||
|
|
||||||
Currently there are BcfSurface and MackieSurface.
|
|
||||||
|
|
||||||
TODO maybe make Group inherit from Control, for ease of ownership.
|
|
||||||
*/
|
|
||||||
class Surface
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/**
|
Surface (MackieControlProtocol&, jack_client_t* jack, const std::string& device_name, uint32_t number, surface_type_t stype);
|
||||||
A Surface can be made up of multiple units. eg one Mackie MCU plus
|
|
||||||
one or more Mackie MCU extenders.
|
|
||||||
|
|
||||||
\param max_strips is the number of strips for the entire surface.
|
|
||||||
\param unit_strips is the number of strips per unit.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Surface (uint32_t max_strips, uint32_t unit_strips);
|
|
||||||
virtual ~Surface();
|
virtual ~Surface();
|
||||||
|
|
||||||
/// Calls the virtual initialisation methods. This *must* be called after
|
surface_type_t type() const { return _stype; }
|
||||||
/// construction, because c++ is too dumb to call virtual methods from
|
uint32_t number() const { return _number; }
|
||||||
/// inside a constructor
|
|
||||||
void init();
|
MackieControlProtocol& mcp() const { return _mcp; }
|
||||||
|
|
||||||
|
bool active() const { return _active; }
|
||||||
|
void drop_routes ();
|
||||||
|
|
||||||
typedef std::vector<Control*> Controls;
|
typedef std::vector<Control*> Controls;
|
||||||
|
|
||||||
/// This collection has ownership of all the controls
|
|
||||||
Controls controls;
|
Controls controls;
|
||||||
|
|
||||||
/**
|
|
||||||
These are alternative addressing schemes
|
|
||||||
They use maps because the indices aren't always
|
|
||||||
0-based.
|
|
||||||
|
|
||||||
Indexed by raw_id not by id. @see Control for the distinction.
|
|
||||||
*/
|
|
||||||
std::map<int,Fader*> faders;
|
std::map<int,Fader*> faders;
|
||||||
std::map<int,Pot*> pots;
|
std::map<int,Pot*> pots;
|
||||||
std::map<int,Button*> buttons;
|
std::map<int,Button*> buttons;
|
||||||
|
|
@ -75,39 +57,63 @@ public:
|
||||||
/// no strip controls in here because they usually
|
/// no strip controls in here because they usually
|
||||||
/// have the same names.
|
/// have the same names.
|
||||||
std::map<std::string,Control*> controls_by_name;
|
std::map<std::string,Control*> controls_by_name;
|
||||||
|
|
||||||
|
Mackie::JogWheel* jog_wheel() const { return _jog_wheel; }
|
||||||
|
|
||||||
/// The collection of all numbered strips. No master
|
/// The collection of all numbered strips. No master
|
||||||
/// strip in here.
|
/// strip in here.
|
||||||
typedef std::vector<Strip*> Strips;
|
typedef std::vector<Strip*> Strips;
|
||||||
Strips strips;
|
Strips strips;
|
||||||
|
|
||||||
|
uint32_t n_strips () const;
|
||||||
|
Strip* nth_strip (uint32_t n) const;
|
||||||
|
|
||||||
/// This collection owns the groups
|
/// This collection owns the groups
|
||||||
typedef std::map<std::string,Group*> Groups;
|
typedef std::map<std::string,Group*> Groups;
|
||||||
Groups groups;
|
Groups groups;
|
||||||
|
|
||||||
uint32_t max_strips() const { return _max_strips; }
|
SurfacePort& port() const { return *_port; }
|
||||||
|
|
||||||
public:
|
const MidiByteArray& sysex_hdr() const;
|
||||||
|
|
||||||
|
void periodic ();
|
||||||
|
|
||||||
|
void handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t, uint32_t channel_id);
|
||||||
|
void handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes*);
|
||||||
|
void handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes*);
|
||||||
|
|
||||||
|
/// Connect the any signal from the parser to handle_midi_any
|
||||||
|
/// unless it's already connected
|
||||||
|
void connect_to_signals ();
|
||||||
|
|
||||||
|
/// notification from a MackiePort that it's now inactive
|
||||||
|
void handle_port_inactive(Mackie::SurfacePort *);
|
||||||
|
|
||||||
|
/// write a sysex message
|
||||||
|
void write_sysex (const MidiByteArray& mba);
|
||||||
|
void write_sysex (MIDI::byte msg);
|
||||||
|
/// proxy write for port
|
||||||
|
void write (const MidiByteArray&);
|
||||||
|
|
||||||
/// display an indicator of the first switched-in Route. Do nothing by default.
|
/// display an indicator of the first switched-in Route. Do nothing by default.
|
||||||
virtual void display_bank_start( SurfacePort &, MackieMidiBuilder &, uint32_t /*current_bank*/ ) {};
|
void display_bank_start (uint32_t /*current_bank*/);
|
||||||
|
|
||||||
/// called from MackieControlPRotocol::zero_all to turn things off
|
/// called from MackieControlProtocol::zero_all to turn things off
|
||||||
virtual void zero_all( SurfacePort &, MackieMidiBuilder & ) {};
|
void zero_all ();
|
||||||
|
|
||||||
/// turn off leds around the jog wheel. This is for surfaces that use a pot
|
/// turn off leds around the jog wheel. This is for surfaces that use a pot
|
||||||
/// pretending to be a jog wheel.
|
/// pretending to be a jog wheel.
|
||||||
virtual void blank_jog_ring( SurfacePort &, MackieMidiBuilder & ) {};
|
void blank_jog_ring ();
|
||||||
|
|
||||||
|
bool has_timecode_display() const;
|
||||||
|
void display_timecode (const std::string & /*timecode*/, const std::string & /*timecode_last*/);
|
||||||
|
|
||||||
virtual bool has_timecode_display() const = 0;
|
|
||||||
virtual void display_timecode( SurfacePort &, MackieMidiBuilder &, const std::string & /*timecode*/, const std::string & /*timecode_last*/) {};
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
/**
|
||||||
This is used to calculate the clicks per second that define
|
This is used to calculate the clicks per second that define
|
||||||
a transport speed of 1.0 for the jog wheel. 100.0 is 10 clicks
|
a transport speed of 1.0 for the jog wheel. 100.0 is 10 clicks
|
||||||
per second, 50.5 is 5 clicks per second.
|
per second, 50.5 is 5 clicks per second.
|
||||||
*/
|
*/
|
||||||
virtual float scrub_scaling_factor() = 0;
|
float scrub_scaling_factor() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
The scaling factor function for speed increase and decrease. At
|
The scaling factor function for speed increase and decrease. At
|
||||||
|
|
@ -116,14 +122,25 @@ public:
|
||||||
high definition control at low speeds and quick speed changes to/from
|
high definition control at low speeds and quick speed changes to/from
|
||||||
higher speeds.
|
higher speeds.
|
||||||
*/
|
*/
|
||||||
virtual float scaled_delta( const ControlState & state, float current_speed ) = 0;
|
float scaled_delta (const ControlState & state, float current_speed);
|
||||||
|
|
||||||
protected:
|
void handle_control_event (Mackie::Control & control, const Mackie::ControlState & state);
|
||||||
virtual void init_controls();
|
|
||||||
virtual void init_strips ();
|
|
||||||
|
|
||||||
const uint32_t _max_strips;
|
protected:
|
||||||
const uint32_t _unit_strips;
|
void init_controls();
|
||||||
|
void init_strips ();
|
||||||
|
|
||||||
|
private:
|
||||||
|
MackieControlProtocol& _mcp;
|
||||||
|
SurfacePort* _port;
|
||||||
|
surface_type_t _stype;
|
||||||
|
uint32_t _number;
|
||||||
|
bool _active;
|
||||||
|
bool _connected;
|
||||||
|
Mackie::JogWheel* _jog_wheel;
|
||||||
|
MackieMidiBuilder builder;
|
||||||
|
|
||||||
|
void jog_wheel_state_display (Mackie::JogWheel::State state);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,49 +15,48 @@
|
||||||
along with this program; if not, write to the Free Software
|
along with this program; if not, write to the Free Software
|
||||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
*/
|
*/
|
||||||
#include "surface_port.h"
|
|
||||||
|
|
||||||
#include "mackie_control_exception.h"
|
#include <sstream>
|
||||||
#include "controls.h"
|
#include <cstring>
|
||||||
|
#include <cerrno>
|
||||||
|
|
||||||
|
#include <sigc++/sigc++.h>
|
||||||
|
#include <boost/shared_array.hpp>
|
||||||
|
|
||||||
#include "midi++/types.h"
|
#include "midi++/types.h"
|
||||||
#include "midi++/port.h"
|
#include "midi++/port.h"
|
||||||
#include "midi++/manager.h"
|
#include "midi++/manager.h"
|
||||||
#include <sigc++/sigc++.h>
|
|
||||||
#include <boost/shared_array.hpp>
|
#include "ardour/debug.h"
|
||||||
|
#include "ardour/rc_configuration.h"
|
||||||
|
|
||||||
|
#include "controls.h"
|
||||||
|
#include "mackie_control_exception.h"
|
||||||
|
#include "surface.h"
|
||||||
|
#include "surface_port.h"
|
||||||
|
|
||||||
|
|
||||||
#include "i18n.h"
|
#include "i18n.h"
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <cerrno>
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace Mackie;
|
using namespace Mackie;
|
||||||
|
using namespace PBD;
|
||||||
SurfacePort::SurfacePort()
|
|
||||||
: _input_port (0), _output_port (0), _number (0), _active (false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param input_port Input MIDI::Port; this object takes responsibility for removing it from
|
/** @param input_port Input MIDI::Port; this object takes responsibility for removing it from
|
||||||
* the MIDI::Manager and destroying it.
|
* the MIDI::Manager and destroying it.
|
||||||
* @param output_port Output MIDI::Port; responsibility similarly taken.
|
* @param output_port Output MIDI::Port; responsibility similarly taken.
|
||||||
*/
|
*/
|
||||||
SurfacePort::SurfacePort (MIDI::Port & input_port, MIDI::Port & output_port, int number)
|
SurfacePort::SurfacePort (Surface& s, MIDI::Port & input_port, MIDI::Port & output_port)
|
||||||
: _input_port (&input_port), _output_port (&output_port), _number (number), _active (false)
|
: _surface (&s)
|
||||||
|
, _input_port (&input_port)
|
||||||
|
, _output_port (&output_port)
|
||||||
|
, _active (false)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
SurfacePort::~SurfacePort()
|
SurfacePort::~SurfacePort()
|
||||||
{
|
{
|
||||||
#ifdef PORT_DEBUG
|
close ();
|
||||||
cout << "~SurfacePort::SurfacePort()" << endl;
|
|
||||||
#endif
|
|
||||||
// make sure another thread isn't reading or writing as we close the port
|
|
||||||
Glib::RecMutex::Lock lock (_rwlock);
|
|
||||||
_active = false;
|
|
||||||
|
|
||||||
MIDI::Manager* mm = MIDI::Manager::instance ();
|
MIDI::Manager* mm = MIDI::Manager::instance ();
|
||||||
|
|
||||||
|
|
@ -70,10 +69,6 @@ SurfacePort::~SurfacePort()
|
||||||
mm->remove_port (_output_port);
|
mm->remove_port (_output_port);
|
||||||
delete _output_port;
|
delete _output_port;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef PORT_DEBUG
|
|
||||||
cout << "~SurfacePort::SurfacePort() finished" << endl;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// wrapper for one day when strerror_r is working properly
|
// wrapper for one day when strerror_r is working properly
|
||||||
|
|
@ -96,18 +91,7 @@ MidiByteArray SurfacePort::read()
|
||||||
}
|
}
|
||||||
|
|
||||||
// return nothing read if the lock isn't acquired
|
// return nothing read if the lock isn't acquired
|
||||||
#if 0
|
|
||||||
Glib::RecMutex::Lock lock (_rwlock, Glib::TRY_LOCK);
|
|
||||||
|
|
||||||
if (!lock.locked()) {
|
|
||||||
cout << "SurfacePort::read not locked" << endl;
|
|
||||||
return retval;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check active again - destructor sequence
|
|
||||||
if (!active()) return retval;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// read port and copy to return value
|
// read port and copy to return value
|
||||||
int nread = input_port().read (buf, sizeof (buf));
|
int nread = input_port().read (buf, sizeof (buf));
|
||||||
|
|
||||||
|
|
@ -150,8 +134,6 @@ void SurfacePort::write (const MidiByteArray & mba)
|
||||||
// that the destructor doesn't destroy the mutex while
|
// that the destructor doesn't destroy the mutex while
|
||||||
// it's still in use
|
// it's still in use
|
||||||
if (!active()) return;
|
if (!active()) return;
|
||||||
Glib::RecMutex::Lock lock (_rwlock);
|
|
||||||
if (!active()) return;
|
|
||||||
|
|
||||||
int count = output_port().write (mba.bytes().get(), mba.size(), 0);
|
int count = output_port().write (mba.bytes().get(), mba.size(), 0);
|
||||||
if (count != (int)mba.size()) {
|
if (count != (int)mba.size()) {
|
||||||
|
|
@ -171,24 +153,114 @@ void SurfacePort::write (const MidiByteArray & mba)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SurfacePort::write_sysex (const MidiByteArray & mba)
|
|
||||||
|
void SurfacePort::open()
|
||||||
{
|
{
|
||||||
if (mba.empty()) {
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::open %1\n", *this));
|
||||||
return;
|
input_port().parser()->sysex.connect_same_thread (sysex_connection, boost::bind (&SurfacePort::handle_midi_sysex, this, _1, _2, _3));
|
||||||
|
_active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SurfacePort::close()
|
||||||
|
{
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, "SurfacePort::close\n");
|
||||||
|
sysex_connection.disconnect();
|
||||||
|
|
||||||
|
if (_surface) {
|
||||||
|
// faders to minimum
|
||||||
|
_surface->write_sysex (0x61);
|
||||||
|
// All LEDs off
|
||||||
|
_surface->write_sysex (0x62);
|
||||||
|
// Reset (reboot into offline mode)
|
||||||
|
_surface->write_sysex (0x63);
|
||||||
}
|
}
|
||||||
|
|
||||||
MidiByteArray buf;
|
_active = false;
|
||||||
buf << sysex_hdr() << mba << MIDI::eox;
|
|
||||||
write (buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SurfacePort::write_sysex (MIDI::byte msg)
|
void
|
||||||
|
SurfacePort::handle_midi_sysex (MIDI::Parser &, MIDI::byte * raw_bytes, size_t count)
|
||||||
{
|
{
|
||||||
MidiByteArray buf;
|
MidiByteArray bytes (count, raw_bytes);
|
||||||
buf << sysex_hdr() << msg << MIDI::eox;
|
|
||||||
write (buf);
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi_sysex: %1\n", bytes));
|
||||||
|
|
||||||
|
switch (bytes[5])
|
||||||
|
{
|
||||||
|
case 0x01:
|
||||||
|
_surface->write_sysex (host_connection_query (bytes));
|
||||||
|
break;
|
||||||
|
case 0x03:
|
||||||
|
// not used right now
|
||||||
|
_surface->write_sysex (host_connection_confirmation (bytes));
|
||||||
|
break;
|
||||||
|
case 0x04:
|
||||||
|
inactive_event ();
|
||||||
|
cout << "host connection error" << bytes << endl;
|
||||||
|
break;
|
||||||
|
case 0x14:
|
||||||
|
// probe_emulation (bytes);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cout << "unknown sysex: " << bytes << endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MidiByteArray calculate_challenge_response (MidiByteArray::iterator begin, MidiByteArray::iterator end)
|
||||||
|
{
|
||||||
|
MidiByteArray l;
|
||||||
|
back_insert_iterator<MidiByteArray> back (l);
|
||||||
|
copy (begin, end, back);
|
||||||
|
|
||||||
|
MidiByteArray retval;
|
||||||
|
|
||||||
|
// this is how to calculate the response to the challenge.
|
||||||
|
// from the Logic docs.
|
||||||
|
retval << (0x7f & (l[0] + (l[1] ^ 0xa) - l[3]));
|
||||||
|
retval << (0x7f & ( (l[2] >> l[3]) ^ (l[0] + l[3])));
|
||||||
|
retval << (0x7f & ((l[3] - (l[2] << 2)) ^ (l[0] | l[1])));
|
||||||
|
retval << (0x7f & (l[1] - l[2] + (0xf0 ^ (l[3] << 4))));
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not used right now
|
||||||
|
MidiByteArray SurfacePort::host_connection_query (MidiByteArray & bytes)
|
||||||
|
{
|
||||||
|
MidiByteArray response;
|
||||||
|
|
||||||
|
// handle host connection query
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host connection query: %1\n", bytes));
|
||||||
|
|
||||||
|
if (bytes.size() != 18) {
|
||||||
|
cerr << "expecting 18 bytes, read " << bytes << " from " << input_port().name() << endl;
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// build and send host connection reply
|
||||||
|
response << 0x02;
|
||||||
|
copy (bytes.begin() + 6, bytes.begin() + 6 + 7, back_inserter (response));
|
||||||
|
response << calculate_challenge_response (bytes.begin() + 6 + 7, bytes.begin() + 6 + 7 + 4);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not used right now
|
||||||
|
MidiByteArray SurfacePort::host_connection_confirmation (const MidiByteArray & bytes)
|
||||||
|
{
|
||||||
|
DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host_connection_confirmation: %1\n", bytes));
|
||||||
|
|
||||||
|
// decode host connection confirmation
|
||||||
|
if (bytes.size() != 14) {
|
||||||
|
ostringstream os;
|
||||||
|
os << "expecting 14 bytes, read " << bytes << " from " << input_port().name();
|
||||||
|
throw MackieControlException (os.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
// send version request
|
||||||
|
return MidiByteArray (2, 0x13, 0x00);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ostream & Mackie::operator << (ostream & os, const SurfacePort & port)
|
ostream & Mackie::operator << (ostream & os, const SurfacePort & port)
|
||||||
{
|
{
|
||||||
os << "{ ";
|
os << "{ ";
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@
|
||||||
#ifndef surface_port_h
|
#ifndef surface_port_h
|
||||||
#define surface_port_h
|
#define surface_port_h
|
||||||
|
|
||||||
|
#include <midi++/types.h>
|
||||||
#include <glibmm/thread.h>
|
#include <glibmm/thread.h>
|
||||||
|
|
||||||
#include "pbd/signals.h"
|
#include "pbd/signals.h"
|
||||||
|
|
@ -26,82 +27,62 @@
|
||||||
|
|
||||||
namespace MIDI {
|
namespace MIDI {
|
||||||
class Port;
|
class Port;
|
||||||
|
class Parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MackieControlProtocol;
|
||||||
|
|
||||||
namespace Mackie
|
namespace Mackie
|
||||||
{
|
{
|
||||||
|
|
||||||
|
class Surface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Make a relationship between a midi port and a Mackie device.
|
Make a relationship between a midi port and a Mackie device.
|
||||||
*/
|
*/
|
||||||
class SurfacePort : public PBD::ScopedConnectionList
|
|
||||||
|
class SurfacePort
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SurfacePort (MIDI::Port & input_port, MIDI::Port & output_port, int number);
|
SurfacePort (Mackie::Surface&, MIDI::Port& input_port, MIDI::Port& output_port);
|
||||||
virtual ~SurfacePort();
|
virtual ~SurfacePort();
|
||||||
|
|
||||||
// when this is successful, active() should return true
|
void open();
|
||||||
virtual void open() = 0;
|
void close();
|
||||||
|
|
||||||
// subclasses should call this before doing their own close
|
|
||||||
virtual void close() = 0;
|
|
||||||
|
|
||||||
/// read bytes from the port. They'll either end up in the
|
/// read bytes from the port. They'll either end up in the
|
||||||
/// parser, or if that's not active they'll be returned
|
/// parser, or if that's not active they'll be returned
|
||||||
virtual MidiByteArray read();
|
MidiByteArray read();
|
||||||
|
|
||||||
/// an easier way to output bytes via midi
|
/// an easier way to output bytes via midi
|
||||||
virtual void write( const MidiByteArray & );
|
void write (const MidiByteArray&);
|
||||||
|
|
||||||
/// write a sysex message
|
MIDI::Port& input_port() { return *_input_port; }
|
||||||
void write_sysex( const MidiByteArray & mba );
|
const MIDI::Port& input_port() const { return *_input_port; }
|
||||||
void write_sysex( MIDI::byte msg );
|
MIDI::Port& output_port() { return *_output_port; }
|
||||||
|
const MIDI::Port& output_port() const { return *_output_port; }
|
||||||
/// return the correct sysex header for this port
|
|
||||||
virtual const MidiByteArray & sysex_hdr() const = 0;
|
|
||||||
|
|
||||||
MIDI::Port & input_port() { return *_input_port; }
|
|
||||||
const MIDI::Port & input_port() const { return *_input_port; }
|
|
||||||
MIDI::Port & output_port() { return *_output_port; }
|
|
||||||
const MIDI::Port & output_port() const { return *_output_port; }
|
|
||||||
|
|
||||||
// emitted just before the port goes into initialisation
|
|
||||||
// where it tries to establish that its device is connected
|
|
||||||
PBD::Signal0<void> init_event;
|
|
||||||
|
|
||||||
// emitted when the port completes initialisation successfully
|
|
||||||
PBD::Signal0<void> active_event;
|
|
||||||
|
|
||||||
// emitted when the port goes inactive (ie a read or write failed)
|
// emitted when the port goes inactive (ie a read or write failed)
|
||||||
PBD::Signal0<void> inactive_event;
|
PBD::Signal0<void> inactive_event;
|
||||||
|
|
||||||
// the port number - master is 0(extenders are 1((,4
|
void handle_midi_sysex (MIDI::Parser&, MIDI::byte *, size_t count);
|
||||||
virtual int number() const { return _number; }
|
|
||||||
|
|
||||||
// number of strips handled by this port. Usually 8.
|
|
||||||
virtual int strips() const = 0;
|
|
||||||
|
|
||||||
virtual bool active() const { return _active; }
|
bool active() const { return _active; }
|
||||||
virtual void active( bool yn ) { _active = yn; }
|
|
||||||
|
|
||||||
void add_in_use_timeout (Control &, Control *);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Only for use by DummyPort
|
MidiByteArray host_connection_query (MidiByteArray& bytes);
|
||||||
SurfacePort();
|
MidiByteArray host_connection_confirmation (const MidiByteArray& bytes);
|
||||||
|
|
||||||
virtual void control_event (SurfacePort &, Control &, const ControlState &) {}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
MIDI::Port * _input_port;
|
Mackie::Surface* _surface;
|
||||||
MIDI::Port * _output_port;
|
MIDI::Port* _input_port;
|
||||||
int _number;
|
MIDI::Port* _output_port;
|
||||||
bool _active;
|
bool _active;
|
||||||
|
|
||||||
Glib::RecMutex _rwlock;
|
PBD::ScopedConnection sysex_connection;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream & operator << ( std::ostream & , const SurfacePort & port );
|
std::ostream& operator << (std::ostream& , const SurfacePort& port);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@
|
||||||
namespace Mackie
|
namespace Mackie
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum surface_type_t {
|
||||||
|
mcu,
|
||||||
|
ext,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
This started off as an enum, but it got really annoying
|
This started off as an enum, but it got really annoying
|
||||||
typing ? on : off
|
typing ? on : off
|
||||||
|
|
|
||||||
|
|
@ -21,23 +21,17 @@ def configure(conf):
|
||||||
def build(bld):
|
def build(bld):
|
||||||
obj = bld(features = 'cxx cxxshlib')
|
obj = bld(features = 'cxx cxxshlib')
|
||||||
obj.source = '''
|
obj.source = '''
|
||||||
bcf_surface.cc
|
|
||||||
button.cc
|
button.cc
|
||||||
controls.cc
|
controls.cc
|
||||||
dummy_port.cc
|
|
||||||
fader.cc
|
fader.cc
|
||||||
gui.cc
|
gui.cc
|
||||||
interface.cc
|
interface.cc
|
||||||
mackie_control_protocol.cc
|
mackie_control_protocol.cc
|
||||||
mackie_control_protocol_poll.cc
|
|
||||||
mackie_jog_wheel.cc
|
mackie_jog_wheel.cc
|
||||||
mackie_midi_builder.cc
|
mackie_midi_builder.cc
|
||||||
mackie_port.cc
|
|
||||||
mackie_surface.cc
|
|
||||||
mcp_buttons.cc
|
mcp_buttons.cc
|
||||||
meter.cc
|
meter.cc
|
||||||
midi_byte_array.cc
|
midi_byte_array.cc
|
||||||
route_signal.cc
|
|
||||||
strip.cc
|
strip.cc
|
||||||
surface.cc
|
surface.cc
|
||||||
surface_port.cc
|
surface_port.cc
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue