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