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:
Paul Davis 2012-04-10 14:27:44 +00:00
parent ac7ade93bd
commit 5ace191bff
37 changed files with 1364 additions and 2256 deletions

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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;

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -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&);
}; };
} }

View file

@ -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&);
}; };
} }

View file

@ -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&);
}; };
} }

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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);
} }

View file

@ -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;

View file

@ -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 &);
}; };
} }

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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

View file

@ -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 {

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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

View file

@ -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));
}

View file

@ -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__ */

View file

@ -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;
}
}

View file

@ -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);
}; };
} }

View file

@ -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 << "{ ";

View file

@ -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);
} }

View file

@ -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

View file

@ -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