"merge" (i.e. wholesale import) 2.0-ongoing Mackie code and then fix to compile in 3.0 context

git-svn-id: svn://localhost/ardour2/branches/3.0@4315 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Paul Davis
2008-12-12 22:55:03 +00:00
parent f03a87a132
commit 51625b2474
43 changed files with 5093 additions and 3618 deletions

View File

@@ -0,0 +1,18 @@
For usage, see
http://www.ardour.org/files/manual/sn-mackie.html
unfortunately it's a bit outdated, so to get the latest manual, go to the manual
directory in the ardour source tree, and say "make html" to build the latest.
Then point your favourite browser at tmp/index.html
NOTES:
* support for alsa/sequencer ports is currently broken. We're working on it.
* you'll need to make port changes in etc/ardour2/ardour_system.rc and
etc/ardour2/ardour.rc, otherwise they don't stay changed in ~/.ardour2/ardour.rc
John Anderson
panic@semiosix.com

View File

@@ -14,12 +14,16 @@ mackie = env.Clone()
domain = 'ardour_mackie'
mackie.Append(DOMAIN = domain, MAJOR = 1, MINOR = 0, MICRO = 0)
mackie.Append(DOMAIN = domain, MAJOR = 1, MINOR = 1, MICRO = 0)
mackie.Append(CXXFLAGS = "-DPACKAGE=\\\"" + domain + "\\\"")
mackie.Append(CXXFLAGS="-DLIBSIGC_DISABLE_DEPRECATED")
mackie.Append(PACKAGE = domain)
mackie.Append(POTFILE = domain + '.pot')
if mackie['DEBUG'] == 1:
mackie.Append(CXXFLAGS="-DDEBUG")
mackie.Append(CXXFLAGS="-DPORT_DEBUG")
if mackie['IS_OSX']:
mackie.Append (LINKFLAGS="-Xlinker -headerpad -Xlinker 2048")
@@ -27,17 +31,21 @@ mackie_files=Split("""
interface.cc
midi_byte_array.cc
controls.cc
surface_port.cc
dummy_port.cc
mackie_port.cc
route_signal.cc
mackie_midi_builder.cc
mackie_button_handler.cc
mackie_control_protocol_poll.cc
surface_port.cc
mackie_port.cc
types.cc
surface.cc
mackie_control_protocol.cc
bcf_surface.cc
bcf_surface_generated.cc
mackie_surface.cc
mackie_surface_generated.cc
mackie_jog_wheel.cc
""")
mackie.Append(CCFLAGS="-D_REENTRANT -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE")
@@ -53,7 +61,6 @@ mackie.Merge ([
libraries['sigc2'],
libraries['pbd'],
libraries['midi++2'],
libraries['evoral'],
libraries['xml'],
libraries['glib2'],
libraries['glibmm2'],
@@ -72,7 +79,7 @@ if mackie['SURFACES']:
Default(libardour_mackie)
if env['NLS']:
i18n (mackie, mackie_files, env)
env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour3','surfaces'), libardour_mackie))
env.Alias('install', env.Install(os.path.join(install_prefix, env['LIBDIR'], 'ardour2','surfaces'), libardour_mackie))
env.Alias('tarball', env.Distribute (env['DISTTREE'],
[ 'SConscript' ] +

View File

@@ -1,20 +1,48 @@
* how long can UI signal callbacks take to execute? What happens if they block?
where ENSURE_CORRECT_THREAD is a macro that is modelled on ENSURE_GUI_THREAD
if the handler is not called in the "correct thread", it will use a pseudo-RT-safe-enough technique to get the correct thread to recall "handler" later on, and return.
* jog with transport rolling doesn't work properly. My use of ScrollTimeline also doesn't work.
* make loop button sensitive to current transport state
* make sure rew button can go past the previous if pressed twice, relatively quickly.
* finish button mapping. Only shifted buttons left for bcf.
* implement handle_port_inactive properly
* two bcf doesn't work
* remappable buttons (OSC or Surfax?)
* 7/1 configurable to 8
* need an object that can encapsulate different port types, ie BCF vs MCU. Not at the surface level.
* finish button implementations.
* concurrency for bank switching? And make sure "old" events aren't sent to "new" faders
* TODOs in code
* removal of a route results in a strip that isn't dead, but doesn't have any effect on the session
* use i18n. see string_compose
* docs in manual, including button assignment diagram
MCU
---
* if mackie wheel moves too fast, it's ignored.
* gain/panner display in second line
* metering on second line
* per-strip signal led
* midi bandwidth?
* Zoom buttons, from Jean-Martin Barbut. In fact I'm a bit desappointed with those functions, because I was used
to use the 4 "N-E-S-W" buttons to zoom in and out with horizontal
buttons and zoom in/out on track height with vertical buttons. That is
with the zoom button in. with the zoom button off, it selects tracks
with vertical buttons and selects regions with horizontal buttons. This
was quite useful. The fact that the zoom button switches the wheel to
zoom is fine, but it would be great to also have the track/region
selection and the track height available from those buttons. I.e : you
select a track with the vertical arrows and you increase/decrease the
eight of the track with the zoom btn on. This combined with the region
selection allows to zoom in a region (vertical and horizontal zoom) very
easily.
Later
-----
* how long can UI signal callbacks take to execute? What happens if they block?
where ENSURE_CORRECT_THREAD is a macro that is modelled on ENSURE_GUI_THREAD
if the handler is not called in the "correct thread", it will use a pseudo-RT-safe-enough technique to get the correct thread to recall "handler" later on, and return.
* alsa/sequencer ports unstable. possibly problems with use of ::poll
* use glib::Timer instead of mine. Actually don't because it's not very useable.
* crash when mmc port set to mcu?
* remove commented couts
* Perhaps MackieControlProtocol shouldn't implement MackieButtonHandler
* Need a HostAdapter class to encapsulate ardour calls
* talk to route plugins
* check for excessiveness (ie too many events making other subsystems work too hard)
* Queueing of writes?
* Generic surface code to common location
* bulk remote id changes cause too many surface updates. use Config->remote_model.
@@ -25,21 +53,16 @@ Later
* mix busses and/or a "bus-only" bank/mode
* what about surfaces like Mackie C4 and BCR2000?
Need UI integration
-------------------
UI integration
--------------
* maybe use current snap state for jog wheel and ffwd/rew
* Some indication on the UI of currently bank-switched-in routes?
Useful for surfaces that don't have a scribble strip.
* use current zoom setting and snap state for shuttle wheel
Actual Mackie
-------------
* docs claim that unit will send a host query on init.
* test Mackie surface object. Apparently led rings don't work. Stereo busses?
* timecode & 55 char displays
* midi bandwidth
Bugs
----
* definitely something wrong with remote_id assignment on session create
(master strip assigned 0).
* when using alsa/sequencer, some midi events intended for mcu port end up being
read by the seq port.
* MIDI::Port::type() returns _type, which is undefined. It might be in the descriptor
* auditioner doesn't connect to master 1 and master 2 - it loses the spaces.

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
#ifndef mackie_surface_bcf_h
#define mackie_surface_bcf_h
/*
Generated by scripts/generate-surface.rb
Initially generated by scripts/generate-surface.rb
*/
#include "surface.h"
@@ -20,6 +20,15 @@ public:
virtual void handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button );
virtual void init_controls();
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 );
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -45,6 +45,76 @@ Strip::Strip( const std::string & name, int index )
{
}
/**
TODO could optimise this to use enum, but it's only
called during the protocol class instantiation.
generated using
irb -r controls.rb
sf=Surface.new
sf.parse
controls = sf.groups.find{|x| x[0] =~ /strip/}.each{|x| puts x[1]}
controls[1].each {|x| puts "\telse if ( control.name() == \"#{x.name}\" )\n\t{\n\t\t_#{x.name} = reinterpret_cast<#{x.class.name}*>(&control);\n\t}\n"}
*/
void Strip::add( Control & control )
{
Group::add( control );
if ( control.name() == "gain" )
{
_gain = reinterpret_cast<Fader*>(&control);
}
else if ( control.name() == "vpot" )
{
_vpot = reinterpret_cast<Pot*>(&control);
}
else if ( control.name() == "recenable" )
{
_recenable = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "solo" )
{
_solo = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "mute" )
{
_mute = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "select" )
{
_select = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "vselect" )
{
_vselect = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "fader_touch" )
{
_fader_touch = reinterpret_cast<Button*>(&control);
}
else if ( control.type() == Control::type_led || control.type() == Control::type_led_ring )
{
// do nothing
cout << "Strip::add not adding " << control << endl;
}
else
{
ostringstream os;
os << "Strip::add: unknown control type " << control;
throw MackieControlException( os.str() );
}
}
Control::Control( int id, int ordinal, std::string name, Group & group )
: _id( id )
, _ordinal( ordinal )
, _name( name )
, _group( group )
, _in_use( false )
, _in_use_timeout( 250 )
{
}
/**
generated with
@@ -107,3 +177,58 @@ Button & Strip::fader_touch()
throw MackieControlException( "fader_touch is null" );
return *_fader_touch;
}
bool Control::in_use() const
{
return _in_use;
}
Control & Control::in_use( bool rhs )
{
_in_use = rhs;
return *this;
}
ostream & Mackie::operator << ( ostream & os, const Mackie::Control & control )
{
os << typeid( control ).name();
os << " { ";
os << "name: " << control.name();
os << ", ";
os << "id: " << "0x" << setw(4) << setfill('0') << hex << control.id() << setfill(' ');
os << ", ";
os << "type: " << "0x" << setw(2) << setfill('0') << hex << control.type() << setfill(' ');
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 << " }";
return os;
}
std::ostream & Mackie::operator << ( std::ostream & os, const Strip & strip )
{
os << typeid( strip ).name();
os << " { ";
os << "has_solo: " << boolalpha << strip.has_solo();
os << ", ";
os << "has_recenable: " << boolalpha << strip.has_recenable();
os << ", ";
os << "has_mute: " << boolalpha << strip.has_mute();
os << ", ";
os << "has_select: " << boolalpha << strip.has_select();
os << ", ";
os << "has_vselect: " << boolalpha << strip.has_vselect();
os << ", ";
os << "has_fader_touch: " << boolalpha << strip.has_fader_touch();
os << ", ";
os << "has_vpot: " << boolalpha << strip.has_vpot();
os << ", ";
os << "has_gain: " << boolalpha << strip.has_gain();
os << " }";
return os;
}

View File

@@ -18,6 +18,8 @@
#ifndef mackie_controls_h
#define mackie_controls_h
#include <sigc++/sigc++.h>
#include <map>
#include <vector>
#include <string>
@@ -83,6 +85,9 @@ class Fader;
class Strip : public Group
{
public:
/**
\param is the index of the strip. 0-based.
*/
Strip( const std::string & name, int index );
virtual bool is_strip() const
@@ -92,10 +97,11 @@ public:
virtual void add( Control & control );
/// This is the index of the strip
/// This is the index of the strip. zero-based.
int index() const { return _index; }
/// This is for Surface only
/// index is zero-based
void index( int rhs ) { _index = rhs; }
Button & solo();
@@ -107,14 +113,14 @@ public:
Pot & vpot();
Fader & gain();
bool has_solo() { return _solo != 0; }
bool has_recenable() { return _recenable != 0; }
bool has_mute() { return _mute != 0; }
bool has_select() { return _select != 0; }
bool has_vselect() { return _vselect != 0; }
bool has_fader_touch() { return _fader_touch != 0; }
bool has_vpot() { return _vpot != 0; }
bool has_gain() { return _gain != 0; }
bool has_solo() const { return _solo != 0; }
bool has_recenable() const { return _recenable != 0; }
bool has_mute() const { return _mute != 0; }
bool has_select() const { return _select != 0; }
bool has_vselect() const { return _vselect != 0; }
bool has_fader_touch() const { return _fader_touch != 0; }
bool has_vpot() const { return _vpot != 0; }
bool has_gain() const { return _gain != 0; }
private:
Button * _solo;
@@ -128,6 +134,8 @@ private:
int _index;
};
std::ostream & operator << ( std::ostream &, const Strip & );
class MasterStrip : public Strip
{
public:
@@ -151,13 +159,9 @@ class Led;
class Control
{
public:
enum type_t { type_fader, type_button, type_pot, type_led, type_led_ring };
Control( int id, int ordinal, std::string name, Group & group )
: _id( id ), _ordinal( ordinal ), _name( name ), _group( group )
{
}
enum type_t { type_led, type_led_ring, type_fader = 0xe0, type_button = 0x90, type_pot = 0xb0 };
Control( int id, int ordinal, std::string name, Group & group );
virtual ~Control() {}
virtual const Led & led() const
@@ -165,17 +169,20 @@ public:
throw MackieControlException( "no led available" );
}
/// The midi id of the control
/// type() << 8 + midi id of the control. This
/// provides a unique id for any control on the surface.
int id() const
{
return _id;
return ( type() << 8 ) + _id;
}
/// the value of the second bytes of the message. It's
/// the id of the control, but only guaranteed to be
/// unique within the control type.
int raw_id() const { return _id; }
/// The 1-based number of the control
int ordinal() const
{
return _ordinal;
}
int ordinal() const { return _ordinal; }
const std::string & name() const
{
@@ -204,11 +211,32 @@ public:
virtual type_t type() const = 0;
/// Return true if this control is the one and only Jog Wheel
virtual bool is_jog() const { return false; }
/**
Return true if the controlis in use, or false otherwise. For buttons
this returns true if the button is currently being held down. For
faders, the touch button has not been released. For pots, this returns
true from the first move event until a timeout after the last move event.
*/
virtual bool in_use() const;
virtual Control & in_use( bool );
/// The timeout value for this control. Normally defaulted to 250ms, but
/// certain controls (ie jog wheel) may want to override it.
virtual unsigned int in_use_timeout() { return _in_use_timeout; }
/// Keep track of the timeout so it can be updated with more incoming events
sigc::connection in_use_connection;
private:
int _id;
int _ordinal;
std::string _name;
Group & _group;
bool _in_use;
unsigned int _in_use_timeout;
};
std::ostream & operator << ( std::ostream & os, const Control & control );
@@ -218,18 +246,10 @@ class Fader : public Control
public:
Fader( int id, int ordinal, std::string name, Group & group )
: Control( id, ordinal, name, group )
, _touch( false )
{
}
bool touch() const { return _touch; }
void touch( bool yn ) { _touch = yn; }
virtual type_t type() const { return type_fader; }
private:
bool _touch;
};
class Led : public Control
@@ -260,7 +280,7 @@ public:
}
virtual type_t type() const { return type_button; };
private:
Led _led;
};
@@ -296,6 +316,17 @@ private:
LedRing _led_ring;
};
class Jog : public Pot
{
public:
Jog( int id, int ordinal, std::string name, Group & group )
: Pot( id, ordinal, name, group )
{
}
virtual bool is_jog() const { return true; }
};
}
#endif

View File

@@ -0,0 +1,58 @@
#include "dummy_port.h"
#include "midi_byte_array.h"
#include <midi++/port.h>
#include <midi++/types.h>
#include <iostream>
using namespace Mackie;
using namespace std;
DummyPort::DummyPort()
{
}
DummyPort::~DummyPort()
{
}
void DummyPort::open()
{
cout << "DummyPort::open" << endl;
}
void DummyPort::close()
{
cout << "DummyPort::close" << endl;
}
MidiByteArray DummyPort::read()
{
cout << "DummyPort::read" << endl;
return MidiByteArray();
}
void DummyPort::write( const MidiByteArray & mba )
{
cout << "DummyPort::write " << mba << endl;
}
MidiByteArray empty_midi_byte_array;
const MidiByteArray & DummyPort::sysex_hdr() const
{
cout << "DummyPort::sysex_hdr" << endl;
return empty_midi_byte_array;
}
int DummyPort::strips() const
{
cout << "DummyPort::strips" << endl;
return 0;
}

View File

@@ -0,0 +1,60 @@
/*
Copyright (C) 2008 John Anderson
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef dummy_port_h
#define dummy_port_h
#include "surface_port.h"
#include "midi_byte_array.h"
namespace MIDI {
class Port;
}
namespace Mackie
{
/**
A Dummy Port, to catch things that shouldn't be sent.
*/
class DummyPort : public SurfacePort
{
public:
DummyPort();
virtual ~DummyPort();
// when this is successful, active() should return true
virtual void open();
// subclasses should call this before doing their own close
virtual void close();
/// read bytes from the port. They'll either end up in the
/// parser, or if that's not active they'll be returned
virtual MidiByteArray read();
/// an easier way to output bytes via midi
virtual void write( const MidiByteArray & );
virtual const MidiByteArray & sysex_hdr() const;
virtual int strips() const;
};
}
#endif

View File

@@ -64,9 +64,22 @@ new_mackie_protocol (ControlProtocolDescriptor* descriptor, Session* s)
void
delete_mackie_protocol (ControlProtocolDescriptor* descriptor, ControlProtocol* cp)
{
delete cp;
try
{
delete cp;
}
catch ( exception & e )
{
cout << "Exception caught trying to destroy MackieControlProtocol: " << e.what() << endl;
}
}
/**
This is called on startup to check whether the lib should be loaded.
So anything that can be changed in the UI should not be used here to
prevent loading of the lib.
*/
bool
probe_mackie_protocol (ControlProtocolDescriptor* descriptor)
{
@@ -79,7 +92,11 @@ static ControlProtocolDescriptor mackie_descriptor = {
ptr : 0,
module : 0,
mandatory : 0,
supports_feedback : true,
// actually, the surface does support feedback, but all this
// flag does is show a submenu on the UI, which is useless for the mackie
// because feedback is always on. In any case, who'd want to use the
// mcu without the motorised sliders doing their thing?
supports_feedback : false,
probe : probe_mackie_protocol,
initialize : new_mackie_protocol,
destroy : delete_mackie_protocol

View File

@@ -689,3 +689,23 @@ LedState MackieButtonHandler::global_solo_release( Button & button )
{
return default_button_press( button );
}
LedState MackieButtonHandler::drop_press( Button & button )
{
return default_button_press( button );
}
LedState MackieButtonHandler::drop_release( Button & button )
{
return default_button_press( button );
}
LedState MackieButtonHandler::save_press( Button & button )
{
return default_button_press( button );
}
LedState MackieButtonHandler::save_release( Button & button )
{
return default_button_press( button );
}

View File

@@ -220,6 +220,12 @@ public:
virtual LedState global_solo_press( Button & );
virtual LedState global_solo_release( Button & );
virtual LedState drop_press( Button & );
virtual LedState drop_release( Button & );
virtual LedState save_press( Button & );
virtual LedState save_release( Button & );
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -32,9 +32,12 @@
#include <control_protocol/control_protocol.h>
#include "midi_byte_array.h"
#include "controls.h"
#include "dummy_port.h"
#include "route_signal.h"
#include "mackie_button_handler.h"
#include "mackie_port.h"
#include "mackie_jog_wheel.h"
#include "timer.h"
namespace MIDI {
class Port;
@@ -93,14 +96,16 @@ class MackieControlProtocol
/// Signal handler for Route::record_enable_changed
void notify_record_enable_changed( Mackie::RouteSignal * );
/// Signal handler for Route::gain_changed ( from IO )
void notify_gain_changed( Mackie::RouteSignal * );
void notify_gain_changed( Mackie::RouteSignal *, bool force_update = true );
/// Signal handler for Route::name_change
void notify_name_changed( Mackie::RouteSignal * );
/// Signal handler from Panner::Change
void notify_panner_changed( Mackie::RouteSignal * );
void notify_panner_changed( Mackie::RouteSignal *, bool force_update = true );
/// Signal handler for new routes added
void notify_route_added( ARDOUR::Session::RouteList & );
/// Signal handler for Route::active_changed
void notify_active_changed( Mackie::RouteSignal * );
void notify_remote_id_changed();
/// rebuild the current bank. Called on route added/removed and
@@ -116,12 +121,15 @@ class MackieControlProtocol
void notify_parameter_changed( const char * );
void notify_solo_active_changed( bool );
// this is called to generate the midi to send in response to
// a button press.
/// Turn smpte on and beats off, or vice versa, depending
/// on state of _timecode_type
void update_smpte_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 );
// calls update_led, but looks up the button by name
void update_global_button( const std::string & name, Mackie::LedState );
void update_global_led( const std::string & name, Mackie::LedState );
// transport button handler methods from MackieButtonHandler
virtual Mackie::LedState frm_left_press( Mackie::Button & );
@@ -183,6 +191,28 @@ class MackieControlProtocol
virtual Mackie::LedState marker_press( Mackie::Button & );
virtual Mackie::LedState marker_release( Mackie::Button & );
virtual Mackie::LedState drop_press( Mackie::Button & );
virtual Mackie::LedState drop_release( Mackie::Button & );
virtual Mackie::LedState save_press( Mackie::Button & );
virtual Mackie::LedState save_release( Mackie::Button & );
virtual Mackie::LedState smpte_beats_press( Mackie::Button & );
virtual Mackie::LedState smpte_beats_release( Mackie::Button & );
// jog wheel states
virtual Mackie::LedState zoom_press( Mackie::Button & );
virtual Mackie::LedState zoom_release( Mackie::Button & );
virtual Mackie::LedState scrub_press( Mackie::Button & );
virtual Mackie::LedState scrub_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; }
protected:
// create instances of MackiePort, depending on what's found in ardour.rc
void create_ports();
@@ -221,10 +251,6 @@ class MackieControlProtocol
// delete all RouteSignal objects connecting Routes to Strips
void clear_route_signals();
/// This is the main MCU port, ie not an extender port
const Mackie::MackiePort & mcu_port() const;
Mackie::MackiePort & mcu_port();
typedef std::vector<Mackie::RouteSignal*> RouteSignals;
RouteSignals route_signals;
@@ -260,14 +286,17 @@ class MackieControlProtocol
automation from the currently active routes and
timecode displays.
*/
void poll_automation ();
void poll_session_data();
// 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( nframes_t now_frame );
std::string format_smpte_timecode( nframes_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
@@ -293,6 +322,9 @@ class MackieControlProtocol
typedef vector<Mackie::MackiePort*> MackiePorts;
MackiePorts _ports;
/// Sometimes the real port goes away, and we want to contain the breakage
Mackie::DummyPort _dummy_port;
// the thread that polls the ports for incoming midi data
pthread_t thread;
@@ -321,6 +353,20 @@ class MackieControlProtocol
int nfds;
bool _transport_previously_rolling;
// timer for two quick marker left presses
Mackie::Timer _frm_left_last;
Mackie::JogWheel _jog_wheel;
// Timer for controlling midi bandwidth used by automation polls
Mackie::Timer _automation_last;
// last written timecode string
std::string _timecode_last;
// Which timecode are we displaying? BBT or SMPTE
ARDOUR::AnyTime::Type _timecode_type;
};
#endif // ardour_mackie_control_protocol_h

View File

@@ -28,7 +28,15 @@ const char * MackieControlProtocol::default_port_name = "mcu";
bool MackieControlProtocol::probe()
{
return MIDI::Manager::instance()->port( default_port_name ) != 0;
if ( MIDI::Manager::instance()->port( default_port_name ) == 0 )
{
error << "No port called mcu. Add it to ardour.rc." << endmsg;
return false;
}
else
{
return true;
}
}
void * MackieControlProtocol::monitor_work()
@@ -52,8 +60,8 @@ void * MackieControlProtocol::monitor_work()
update_ports();
}
}
// poll for automation data from the routes
poll_automation();
// poll for session data that needs to go to the unit
poll_session_data();
}
catch ( exception & e )
{
@@ -71,30 +79,51 @@ void * MackieControlProtocol::monitor_work()
void MackieControlProtocol::update_ports()
{
#ifdef DEBUG
cout << "MackieControlProtocol::update_ports" << endl;
#endif
if ( _ports_changed )
{
Glib::Mutex::Lock ul( update_mutex );
// yes, this is a double-test locking paradigm, or whatever it's called
// because we don't *always* need to acquire the lock for the first test
#ifdef DEBUG
cout << "MackieControlProtocol::update_ports lock acquired" << endl;
#endif
if ( _ports_changed )
{
// create new pollfd structures
if ( pfd != 0 ) delete[] pfd;
// TODO This might be a memory leak. How does thread cancellation cleanup work?
if ( pfd != 0 )
{
delete[] pfd;
pfd = 0;
}
pfd = new pollfd[_ports.size()];
#ifdef DEBUG
cout << "pfd: " << pfd << endl;
#endif
nfds = 0;
for( MackiePorts::iterator it = _ports.begin(); it != _ports.end(); ++it )
{
//cout << "adding port " << (*it)->port().name() << " to pollfd" << endl;
// add the port any handler
(*it)->connect_any();
#ifdef DEBUG
cout << "adding pollfd for port " << (*it)->port().name() << " to pollfd " << nfds << endl;
#endif
pfd[nfds].fd = (*it)->port().selectable();
pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
++nfds;
}
_ports_changed = false;
}
#ifdef DEBUG
cout << "MackieControlProtocol::update_ports signal" << endl;
#endif
update_cond.signal();
}
#ifdef DEBUG
cout << "MackieControlProtocol::update_ports finish" << endl;
#endif
}
void MackieControlProtocol::read_ports()
@@ -127,12 +156,14 @@ bool MackieControlProtocol::poll_ports()
if ( nfds < 1 )
{
lock.release();
//cout << "poll_ports no ports" << endl;
#ifdef DEBUG
cout << "poll_ports no ports" << endl;
#endif
usleep( no_ports_sleep * 1000 );
return false;
}
int retval = poll( pfd, nfds, timeout );
int retval = ::poll( pfd, nfds, timeout );
if ( retval < 0 )
{
// gdb at work, perhaps
@@ -179,14 +210,21 @@ void MackieControlProtocol::handle_port_active( SurfacePort * port )
// 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
//cout << "update_surface in handle_port_active" << endl;
update_surface();
}
void MackieControlProtocol::handle_port_init( Mackie::SurfacePort * sport )
{
//cout << "MackieControlProtocol::handle_port_init" << endl;
#ifdef DEBUG
cout << "MackieControlProtocol::handle_port_init" << endl;
#endif
_ports_changed = true;
update_ports();
#ifdef DEBUG
cout << "MackieControlProtocol::handle_port_init finish" << endl;
#endif
}

View File

@@ -0,0 +1,194 @@
#include <cmath>
#include "mackie_jog_wheel.h"
#include "mackie_control_protocol.h"
#include "surface_port.h"
#include "controls.h"
#include "surface.h"
#include <algorithm>
using namespace Mackie;
using std::isnan;
JogWheel::JogWheel( MackieControlProtocol & mcp )
: _mcp( mcp )
, _transport_speed( 4.0 )
, _transport_direction( 0 )
, _shuttle_speed( 0.0 )
{
}
JogWheel::State JogWheel::jog_wheel_state() const
{
if ( !_jog_wheel_states.empty() )
return _jog_wheel_states.top();
else
return scroll;
}
void JogWheel::zoom_event( SurfacePort & port, Control & control, const ControlState & state )
{
}
void JogWheel::scrub_event( SurfacePort & port, Control & control, const ControlState & state )
{
}
void JogWheel::speed_event( SurfacePort & port, Control & control, const ControlState & state )
{
}
void JogWheel::scroll_event( SurfacePort & port, Control & control, const ControlState & state )
{
}
void JogWheel::jog_event( SurfacePort & port, Control & control, const ControlState & state )
{
// TODO use current snap-to setting?
switch ( jog_wheel_state() )
{
case scroll:
_mcp.ScrollTimeline( state.delta * state.sign );
break;
case zoom:
// Chunky Zoom.
// TODO implement something similar to ScrollTimeline which
// ends up in Editor::control_scroll for smoother zooming.
if ( state.sign > 0 )
for ( unsigned int i = 0; i < state.ticks; ++i ) _mcp.ZoomIn();
else
for ( unsigned int i = 0; i < state.ticks; ++i ) _mcp.ZoomOut();
break;
case speed:
// locally, _transport_speed is an positive value
_transport_speed += _mcp.surface().scaled_delta( state, _mcp.get_session().transport_speed() );
// make sure no weirdness gets to the session
if ( _transport_speed < 0 || isnan( _transport_speed ) )
{
_transport_speed = 0.0;
}
// translate _transport_speed speed to a signed transport velocity
_mcp.get_session().request_transport_speed( transport_speed() * transport_direction() );
break;
case scrub:
{
if ( state.sign != 0 )
{
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;
_mcp.get_session().request_transport_speed( speed * state.sign );
}
else
{
// we have a stop event
check_scrubbing();
}
break;
}
case shuttle:
_shuttle_speed = _mcp.get_session().transport_speed();
_shuttle_speed += _mcp.surface().scaled_delta( state, _mcp.get_session().transport_speed() );
_mcp.get_session().request_transport_speed( _shuttle_speed );
break;
case select:
cout << "JogWheel select not implemented" << endl;
break;
}
}
void JogWheel::check_scrubbing()
{
// if the last elapsed is greater than the average + std deviation, then stop
if ( !_scrub_intervals.empty() && _scrub_timer.elapsed() > average_scrub_interval() + std_dev_scrub_interval() )
{
_mcp.get_session().request_transport_speed( 0.0 );
_scrub_intervals.clear();
}
}
void JogWheel::push( State state )
{
_jog_wheel_states.push( state );
}
void JogWheel::pop()
{
if ( _jog_wheel_states.size() > 0 )
{
_jog_wheel_states.pop();
}
}
void JogWheel::zoom_state_toggle()
{
if ( jog_wheel_state() == zoom )
pop();
else
push( zoom );
}
JogWheel::State JogWheel::scrub_state_cycle()
{
State top = jog_wheel_state();
if ( top == scrub )
{
// stop scrubbing and go to shuttle
pop();
push( shuttle );
_shuttle_speed = 0.0;
}
else if ( top == shuttle )
{
// default to scroll, or the last selected
pop();
}
else
{
// start with scrub
push( scrub );
}
return jog_wheel_state();
}
void JogWheel::add_scrub_interval( unsigned long elapsed )
{
if ( _scrub_intervals.size() > 5 )
{
_scrub_intervals.pop_front();
}
_scrub_intervals.push_back( elapsed );
}
float JogWheel::average_scrub_interval()
{
float sum = 0.0;
for ( std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it )
{
sum += *it;
}
return sum / _scrub_intervals.size();
}
float JogWheel::std_dev_scrub_interval()
{
float average = average_scrub_interval();
// calculate standard deviation
float sum = 0.0;
for ( std::deque<unsigned long>::iterator it = _scrub_intervals.begin(); it != _scrub_intervals.end(); ++it )
{
sum += pow( *it - average, 2 );
}
return sqrt( sum / _scrub_intervals.size() -1 );
}

View File

@@ -0,0 +1,102 @@
#ifndef mackie_jog_wheel
#define mackie_jog_wheel
#include "timer.h"
#include <stack>
#include <deque>
#include <queue>
class MackieControlProtocol;
namespace Mackie
{
class SurfacePort;
class Control;
class ControlState;
/**
A jog wheel can be used to control many things. This
handles all of the states and state transitions.
Mainly it exists to avoid putting a bunch of messy
stuff in MackieControlProtocol.
But it doesn't really know who it is, with stacks, queues and various
boolean state variables.
*/
class JogWheel
{
public:
enum State { scroll, zoom, speed, scrub, shuttle, select };
JogWheel( MackieControlProtocol & mcp );
/// As the wheel turns...
void jog_event( SurfacePort & port, Control & control, const ControlState & state );
// These are for incoming button presses that change the internal state
// but they're not actually used at the moment.
void zoom_event( SurfacePort & port, Control & control, const ControlState & state );
void scrub_event( SurfacePort & port, Control & control, const ControlState & state );
void speed_event( SurfacePort & port, Control & control, const ControlState & state );
void scroll_event( SurfacePort & port, Control & control, const ControlState & state );
/// Return the current jog wheel mode, which defaults to Scroll
State jog_wheel_state() const;
/// The current transport speed for ffwd and rew. Can be
/// set by wheel when they're pressed.
float transport_speed() const { return _transport_speed; }
/// one of -1,0,1
int transport_direction() const { return _transport_direction; }
void transport_direction( int rhs ) { _transport_direction = rhs; }
void push( State state );
void pop();
/// Turn zoom mode on and off
void zoom_state_toggle();
/**
Cycle scrub -> shuttle -> previous
*/
State scrub_state_cycle();
/// Check to see when the last scrub event was
/// And stop scrubbing if it was too long ago.
/// Intended to be called from a periodic timer of
/// some kind.
void check_scrubbing();
protected:
void add_scrub_interval( unsigned long elapsed );
float average_scrub_interval();
float std_dev_scrub_interval();
private:
MackieControlProtocol & _mcp;
/// transport speed for ffwd and rew, controller by jog
float _transport_speed;
int _transport_direction;
/// Speed for shuttle
float _shuttle_speed;
/// a stack for keeping track of states
std::stack<State> _jog_wheel_states;
/// So we know how fast to set the transport speed while scrubbing
Timer _scrub_timer;
/// to keep track of what the current scrub rate is
/// so we can calculate a moving average
std::deque<unsigned long> _scrub_intervals;
};
}
#endif

View File

@@ -20,9 +20,11 @@
#include <typeinfo>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include "controls.h"
#include "midi_byte_array.h"
#include "mackie_port.h"
using namespace Mackie;
using namespace std;
@@ -44,12 +46,12 @@ MIDI::byte MackieMidiBuilder::calculate_pot_value( midi_pot_mode mode, const Con
return retval;
}
MidiByteArray MackieMidiBuilder::build_led_ring( const Pot & pot, const ControlState & state )
MidiByteArray MackieMidiBuilder::build_led_ring( const Pot & pot, const ControlState & state, midi_pot_mode mode )
{
return build_led_ring( pot.led_ring(), state );
return build_led_ring( pot.led_ring(), state, mode );
}
MidiByteArray MackieMidiBuilder::build_led_ring( const LedRing & led_ring, const ControlState & state )
MidiByteArray MackieMidiBuilder::build_led_ring( const LedRing & led_ring, const ControlState & state, midi_pot_mode mode )
{
// The other way of doing this:
// 0x30 + pot/ring number (0-7)
@@ -58,9 +60,9 @@ MidiByteArray MackieMidiBuilder::build_led_ring( const LedRing & led_ring, const
// the control type
, midi_pot_id
// the id
, 0x20 + led_ring.id()
, 0x20 + led_ring.raw_id()
// the value
, calculate_pot_value( midi_pot_mode_dot, state )
, calculate_pot_value( mode, state )
);
}
@@ -82,7 +84,7 @@ MidiByteArray MackieMidiBuilder::build_led( const Led & led, LedState ls )
return MidiByteArray ( 3
, midi_button_id
, led.id()
, led.raw_id()
, state
);
}
@@ -92,7 +94,7 @@ MidiByteArray MackieMidiBuilder::build_fader( const Fader & fader, float pos )
int posi = int( 0x3fff * pos );
return MidiByteArray ( 3
, midi_fader_id | fader.id()
, midi_fader_id | fader.raw_id()
// lower-order bits
, posi & 0x7f
// higher-order bits
@@ -100,7 +102,7 @@ MidiByteArray MackieMidiBuilder::build_fader( const Fader & fader, float pos )
);
}
MidiByteArray MackieMidiBuilder::zero_strip( const Strip & strip )
MidiByteArray MackieMidiBuilder::zero_strip( SurfacePort & port, const Strip & strip )
{
Group::Controls::const_iterator it = strip.controls().begin();
MidiByteArray retval;
@@ -110,6 +112,10 @@ MidiByteArray MackieMidiBuilder::zero_strip( const Strip & strip )
if ( control.accepts_feedback() )
retval << zero_control( control );
}
// These must have sysex headers
retval << strip_display_blank( port, strip, 0 );
retval << strip_display_blank( port, strip, 1 );
return retval;
}
@@ -171,3 +177,98 @@ MidiByteArray MackieMidiBuilder::two_char_display( unsigned int value, const std
os << setfill('0') << setw(2) << value % 100;
return two_char_display( os.str() );
}
MidiByteArray MackieMidiBuilder::strip_display_blank( SurfacePort & port, 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, " " );
}
MidiByteArray MackieMidiBuilder::strip_display( SurfacePort & port, const Strip & strip, unsigned int line_number, const std::string & line )
{
if ( line_number > 1 )
{
throw runtime_error( "line_number must be 0 or 1" );
}
if ( strip.index() > 7 )
{
throw runtime_error( "strip.index() must be between 0 and 7" );
}
#ifdef DEBUG
cout << "MackieMidiBuilder::strip_display index: " << strip.index() << ", line " << line_number << ": " << line << endl;
#endif
MidiByteArray retval;
// sysex header
retval << port.sysex_hdr();
// code for display
retval << 0x12;
// offset (0 to 0x37 first line, 0x38 to 0x6f for second line )
retval << ( strip.index() * 7 + ( line_number * 0x38 ) );
// ascii data to display
retval << line;
// pad with " " out to 6 chars
for ( int i = line.length(); i < 6; ++i ) retval << ' ';
// column spacer, unless it's the right-hand column
if ( strip.index() < 7 ) retval << ' ';
// sysex trailer
retval << MIDI::eox;
#ifdef DEBUG
cout << "MackieMidiBuilder::strip_display midi: " << retval << endl;
#endif
return retval;
}
MidiByteArray MackieMidiBuilder::all_strips_display( SurfacePort & port, std::vector<std::string> & lines1, std::vector<std::string> & lines2 )
{
MidiByteArray retval;
retval << 0x12 << 0;
// NOTE remember max 112 bytes per message, including sysex headers
retval << "Not working yet";
return retval;
}
MidiByteArray MackieMidiBuilder::timecode_display( SurfacePort & port, 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();
// length sanity checking
string local_timecode = timecode;
// truncate to 10 characters
if ( local_timecode.length() > 10 ) local_timecode = local_timecode.substr( 0, 10 );
// pad to 10 characters
while ( local_timecode.length() < 10 ) local_timecode += " ";
// find the suffix of local_timecode that differs from last_timecode
std::pair<string::const_iterator,string::iterator> pp = mismatch( last_timecode.begin(), last_timecode.end(), local_timecode.begin() );
MidiByteArray retval;
// sysex header
retval << port.sysex_hdr();
// code for timecode display
retval << 0x10;
// translate characters. These are sent in reverse order of display
// hence the reverse iterators
string::reverse_iterator rend = reverse_iterator<string::iterator>( pp.second );
for ( string::reverse_iterator it = local_timecode.rbegin(); it != rend; ++it )
{
retval << translate_seven_segment( *it );
}
// sysex trailer
retval << MIDI::eox;
return retval;
}

View File

@@ -20,10 +20,13 @@
#include "midi_byte_array.h"
#include "types.h"
#include "controls.h"
namespace Mackie
{
class SurfacePort;
/**
This knows how to build midi messages given a control and
a state.
@@ -37,9 +40,9 @@ public:
with the control id
*/
enum midi_types {
midi_fader_id = 0xe0
, midi_button_id = 0x90
, midi_pot_id = 0xb0
midi_fader_id = Control::type_fader
, midi_button_id = Control::type_button
, midi_pot_id = Control::type_pot
};
/**
@@ -52,8 +55,8 @@ public:
, midi_pot_mode_spread = 3
};
MidiByteArray build_led_ring( const Pot & pot, const ControlState & );
MidiByteArray build_led_ring( const LedRing & led_ring, const ControlState & );
MidiByteArray build_led_ring( const Pot & pot, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot );
MidiByteArray build_led_ring( const LedRing & led_ring, const ControlState &, midi_pot_mode mode = midi_pot_mode_dot );
MidiByteArray build_led( const Led & led, LedState ls );
MidiByteArray build_led( const Button & button, LedState ls );
@@ -61,7 +64,8 @@ public:
MidiByteArray build_fader( const Fader & fader, float pos );
/// return bytes that will reset all controls to their zero positions
MidiByteArray zero_strip( const Strip & strip );
/// And blank the display for the strip. Pass SurfacePort so we know which sysex header to use.
MidiByteArray zero_strip( SurfacePort &, const Strip & strip );
// provide bytes to zero the given control
MidiByteArray zero_control( const Control & control );
@@ -72,6 +76,25 @@ public:
MidiByteArray two_char_display( const std::string & msg, const std::string & dots = " " );
MidiByteArray two_char_display( unsigned int value, const std::string & dots = " " );
/**
Timecode display. Only the difference between timecode and last_timecode will
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 = "" );
/**
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 );
/// 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 );
/// 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 );
protected:
static MIDI::byte calculate_pot_value( midi_pot_mode mode, const ControlState & );
};

View File

@@ -23,6 +23,8 @@
#include "controls.h"
#include "surface.h"
#include <glibmm/main.h>
#include <midi++/types.h>
#include <midi++/port.h>
#include <sigc++/sigc++.h>
@@ -49,14 +51,20 @@ MackiePort::MackiePort( MackieControlProtocol & mcp, MIDI::Port & port, int numb
, _emulation( none )
, _initialising( true )
{
//cout << "MackiePort::MackiePort" <<endl;
#ifdef PORT_DEBUG
cout << "MackiePort::MackiePort" <<endl;
#endif
}
MackiePort::~MackiePort()
{
//cout << "~MackiePort" << endl;
#ifdef PORT_DEBUG
cout << "~MackiePort" << endl;
#endif
close();
//cout << "~MackiePort finished" << endl;
#ifdef PORT_DEBUG
cout << "~MackiePort finished" << endl;
#endif
}
int MackiePort::strips() const
@@ -83,7 +91,9 @@ int MackiePort::strips() const
// should really be in MackiePort
void MackiePort::open()
{
//cout << "MackiePort::open " << *this << endl;
#ifdef PORT_DEBUG
cout << "MackiePort::open " << *this << endl;
#endif
_sysex = port().input()->sysex.connect( ( mem_fun (*this, &MackiePort::handle_midi_sysex) ) );
// make sure the device is connected
@@ -92,14 +102,18 @@ void MackiePort::open()
void MackiePort::close()
{
//cout << "MackiePort::close" << endl;
#ifdef PORT_DEBUG
cout << "MackiePort::close" << endl;
#endif
// disconnect signals
_any.disconnect();
_sysex.disconnect();
// TODO emit a "closing" signal?
//cout << "MackiePort::close finished" << endl;
#ifdef PORT_DEBUG
cout << "MackiePort::close finished" << endl;
#endif
}
const MidiByteArray & MackiePort::sysex_hdr() const
@@ -113,57 +127,6 @@ const MidiByteArray & MackiePort::sysex_hdr() const
return mackie_sysex_hdr;
}
Control & MackiePort::lookup_control( const MidiByteArray & bytes )
{
Control * control = 0;
int midi_id = -1;
MIDI::byte midi_type = bytes[0] & 0xf0; //0b11110000
switch( midi_type )
{
// fader
case MackieMidiBuilder::midi_fader_id:
midi_id = bytes[0] & 0x0f;
control = _mcp.surface().faders[midi_id];
if ( control == 0 )
{
ostringstream os;
os << "control for fader" << midi_id << " is null";
throw MackieControlException( os.str() );
}
break;
// button
case MackieMidiBuilder::midi_button_id:
midi_id = bytes[1];
control = _mcp.surface().buttons[midi_id];
if ( control == 0 )
{
ostringstream os;
os << "control for button" << midi_id << " is null";
throw MackieControlException( os.str() );
}
break;
// pot (jog wheel, external control)
case MackieMidiBuilder::midi_pot_id:
midi_id = bytes[1] & 0x1f;
control = _mcp.surface().pots[midi_id];
if ( control == 0 )
{
ostringstream os;
os << "control for button" << midi_id << " is null";
throw MackieControlException( os.str() );
}
break;
default:
ostringstream os;
os << "Cannot find control for " << bytes;
throw MackieControlException( os.str() );
}
return *control;
}
MidiByteArray calculate_challenge_response( MidiByteArray::iterator begin, MidiByteArray::iterator end )
{
MidiByteArray l;
@@ -186,7 +149,9 @@ MidiByteArray calculate_challenge_response( MidiByteArray::iterator begin, MidiB
MidiByteArray MackiePort::host_connection_query( MidiByteArray & bytes )
{
// handle host connection query
//cout << "host connection query: " << bytes << endl;
#ifdef PORT_DEBUG
cout << "host connection query: " << bytes << endl;
#endif
if ( bytes.size() != 18 )
{
@@ -207,7 +172,9 @@ MidiByteArray MackiePort::host_connection_query( MidiByteArray & bytes )
// not used right now
MidiByteArray MackiePort::host_connection_confirmation( const MidiByteArray & bytes )
{
//cout << "host_connection_confirmation: " << bytes << endl;
#ifdef PORT_DEBUG
cout << "host_connection_confirmation: " << bytes << endl;
#endif
// decode host connection confirmation
if ( bytes.size() != 14 )
@@ -224,17 +191,20 @@ MidiByteArray MackiePort::host_connection_confirmation( const MidiByteArray & by
void MackiePort::probe_emulation( const MidiByteArray & bytes )
{
//cout << "MackiePort::probe_emulation: " << bytes.size() << ", " << bytes << endl;
string version_string;
for ( int i = 6; i < 11; ++i ) version_string.append( 1, (char)bytes[i] );
//cout << "version_string: " << version_string << endl;
#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;
//cout << "MackiePort::probe_emulation out of sequence." << endl;
return;
}
@@ -243,11 +213,15 @@ void MackiePort::probe_emulation( const MidiByteArray & bytes )
void MackiePort::init()
{
//cout << "MackiePort::init" << endl;
#ifdef PORT_DEBUG
cout << "MackiePort::init" << endl;
#endif
init_mutex.lock();
_initialising = true;
//cout << "MackiePort::lock acquired" << endl;
#ifdef PORT_DEBUG
cout << "MackiePort::init lock acquired" << endl;
#endif
// emit pre-init signal
init_event();
@@ -263,13 +237,19 @@ void MackiePort::init()
void MackiePort::finalise_init( bool yn )
{
//cout << "MackiePort::finalise_init" << endl;
#ifdef PORT_DEBUG
cout << "MackiePort::finalise_init" << endl;
#endif
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;
@@ -296,11 +276,39 @@ void MackiePort::finalise_init( bool yn )
active_event();
// start handling messages from controls
_any = port().input()->any.connect( ( mem_fun (*this, &MackiePort::handle_midi_any) ) );
connect_any();
}
_initialising = false;
init_cond.signal();
init_mutex.unlock();
#ifdef PORT_DEBUG
cout << "MackiePort::finalise_init lock released" << endl;
#endif
}
void MackiePort::connect_any()
{
/*
Doesn't work because there isn't an == operator for slots
MIDI::Signal::slot_list_type slots = port().input()->any.slots();
if ( find( slots.begin(), slots.end(), mem_fun( *this, &MackiePort::handle_midi_any ) ) == slots.end() )
*/
// TODO but this will break if midi tracing is turned on
if ( port().input()->any.empty() )
{
#ifdef DEBUG
cout << "connect input parser " << port().input() << " to handle_midi_any" << endl;
#endif
_any = port().input()->any.connect( mem_fun( *this, &MackiePort::handle_midi_any ) );
#ifdef DEBUG
cout << "input parser any connections: " << port().input()->any.size() << endl;
#endif
}
else
{
cout << "MackiePort::connect_any already connected" << endl;
}
}
bool MackiePort::wait_for_init()
@@ -308,18 +316,26 @@ bool MackiePort::wait_for_init()
Glib::Mutex::Lock lock( init_mutex );
while ( _initialising )
{
//cout << "MackiePort::wait_for_active waiting" << endl;
#ifdef PORT_DEBUG
cout << "MackiePort::wait_for_active waiting" << endl;
#endif
init_cond.wait( init_mutex );
//cout << "MackiePort::wait_for_active released" << endl;
#ifdef PORT_DEBUG
cout << "MackiePort::wait_for_active released" << endl;
#endif
}
//cout << "MackiePort::wait_for_active returning" << endl;
#ifdef PORT_DEBUG
cout << "MackiePort::wait_for_active returning" << endl;
#endif
return SurfacePort::active();
}
void MackiePort::handle_midi_sysex (MIDI::Parser & parser, MIDI::byte * raw_bytes, size_t count )
{
MidiByteArray bytes( count, raw_bytes );
//cout << "handle_midi_sysex: " << bytes << endl;
#ifdef PORT_DEBUG
cout << "handle_midi_sysex: " << bytes << endl;
#endif
switch( bytes[5] )
{
case 0x01:
@@ -342,16 +358,99 @@ void MackiePort::handle_midi_sysex (MIDI::Parser & parser, MIDI::byte * raw_byte
}
}
Control & MackiePort::lookup_control( MIDI::byte * bytes, size_t count )
{
// Don't instantiate a MidiByteArray here unless it's needed for exceptions.
// Reason being that this method is called for every single incoming
// midi event, and it needs to be as efficient as possible.
Control * control = 0;
MIDI::byte midi_type = bytes[0] & 0xf0; //0b11110000
switch( midi_type )
{
// fader
case MackieMidiBuilder::midi_fader_id:
{
int midi_id = bytes[0] & 0x0f;
control = _mcp.surface().faders[midi_id];
if ( control == 0 )
{
MidiByteArray mba( count, bytes );
ostringstream os;
os << "control for fader" << bytes << " id " << midi_id << " is null";
throw MackieControlException( os.str() );
}
break;
}
// button
case MackieMidiBuilder::midi_button_id:
control = _mcp.surface().buttons[bytes[1]];
if ( control == 0 )
{
MidiByteArray mba( count, bytes );
ostringstream os;
os << "control for button " << bytes << " is null";
throw MackieControlException( os.str() );
}
break;
// pot (jog wheel, external control)
case MackieMidiBuilder::midi_pot_id:
control = _mcp.surface().pots[bytes[1]];
if ( control == 0 )
{
MidiByteArray mba( count, bytes );
ostringstream os;
os << "control for rotary " << mba << " is null";
throw MackieControlException( os.str() );
}
break;
default:
MidiByteArray mba( count, bytes );
ostringstream os;
os << "Cannot find control for " << bytes;
throw MackieControlException( os.str() );
}
return *control;
}
bool MackiePort::handle_control_timeout_event ( Control * control )
{
// empty control_state
ControlState control_state;
control->in_use( false );
control_event( *this, *control, control_state );
// only call this method once from the timer
return false;
}
// converts midi messages into control_event signals
// it might be worth combining this with lookup_control
// because they have similar logic flows.
void MackiePort::handle_midi_any (MIDI::Parser & parser, MIDI::byte * raw_bytes, size_t count )
{
#ifdef DEBUG
MidiByteArray bytes( count, raw_bytes );
cout << "MackiePort::handle_midi_any " << bytes << endl;
#endif
try
{
// ignore sysex messages
if ( bytes[0] == MIDI::sysex ) return;
if ( raw_bytes[0] == MIDI::sysex ) return;
Control & control = lookup_control( bytes );
// sanity checking
if ( count != 3 )
{
ostringstream os;
MidiByteArray mba( count, raw_bytes );
os << "MackiePort::handle_midi_any needs 3 bytes, but received " << mba;
throw MackieControlException( os.str() );
}
Control & control = lookup_control( raw_bytes, count );
control.in_use( true );
// This handles incoming bytes. Outgoing bytes
// are sent by the signal handlers.
@@ -359,41 +458,74 @@ void MackiePort::handle_midi_any (MIDI::Parser & parser, MIDI::byte * raw_bytes,
{
// fader
case Control::type_fader:
{
// for a BCF2000, max is 7f for high-order byte and 0x70 for low-order byte
// According to the Logic docs, these should both be 0x7f.
// Although it does mention something about only the top-order
// 10 bits out of 14 being used
int midi_pos = ( bytes[2] << 7 ) + bytes[1];
control_event( *this, control, float(midi_pos) / float(0x3fff) );
}
break;
{
// only the top-order 10 bits out of 14 are used
int midi_pos = ( ( raw_bytes[2] << 7 ) + raw_bytes[1] ) >> 4;
// in_use is set by the MackieControlProtocol::handle_strip_button
// relies on implicit ControlState constructor
control_event( *this, control, float(midi_pos) / float(0x3ff) );
}
break;
// button
case Control::type_button:
control_event( *this, control, bytes[2] == 0x7f ? press : release );
{
ControlState control_state( raw_bytes[2] == 0x7f ? press : release );
control.in_use( control_state.button_state == press );
control_event( *this, control, control_state );
break;
}
// pot (jog wheel, external control)
case Control::type_pot:
{
ControlState state;
// bytes[2] & 0b01000000 (0x40) give sign
int sign = ( bytes[2] & 0x40 ) == 0 ? 1 : -1;
// bytes[2] & 0b00111111 (0x3f) gives delta
state.ticks = ( bytes[2] & 0x3f) * sign;
state.delta = float( state.ticks ) / float( 0x3f );
control_event( *this, control, state );
}
{
ControlState state;
// bytes[2] & 0b01000000 (0x40) give sign
state.sign = ( raw_bytes[2] & 0x40 ) == 0 ? 1 : -1;
// bytes[2] & 0b00111111 (0x3f) gives delta
state.ticks = ( raw_bytes[2] & 0x3f);
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.
*/
// this is set to false ...
control.in_use( true );
// ... by this timeout
// first disconnect any previous timeouts
control.in_use_connection.disconnect();
// now connect a new timeout to call handle_control_timeout_event
sigc::slot<bool> timeout_slot = sigc::bind(
mem_fun( *this, &MackiePort::handle_control_timeout_event )
, &control
);
control.in_use_connection = Glib::signal_timeout().connect(
timeout_slot
, control.in_use_timeout()
);
// emit the control event
control_event( *this, control, state );
break;
}
default:
cerr << "Do not understand control type " << control;
}
}
catch( MackieControlException & e )
{
//cout << bytes << ' ' << e.what() << endl;
MidiByteArray bytes( count, raw_bytes );
cout << bytes << ' ' << e.what() << endl;
}
#ifdef DEBUG
cout << "finished MackiePort::handle_midi_any " << bytes << endl;
#endif
}

View File

@@ -55,12 +55,12 @@ public:
virtual const MidiByteArray & sysex_hdr() const;
/// Handle device initialisation
void handle_midi_sysex( MIDI::Parser &, MIDI::byte *, size_t );
void handle_midi_sysex( MIDI::Parser &, MIDI::byte *, size_t count );
/// Handle all control messags
void handle_midi_any( MIDI::Parser &, MIDI::byte *, size_t );
void handle_midi_any( MIDI::Parser &, MIDI::byte *, size_t count );
Control & lookup_control( const MidiByteArray & bytes );
Control & lookup_control( MIDI::byte *, size_t count );
/// return the number of strips associated with this port
virtual int strips() const;
@@ -71,6 +71,10 @@ public:
emulation_t emulation() const { return _emulation; }
/// Connect the any signal from the parser to handle_midi_any
/// unless it's already connected
void connect_any();
protected:
/**
The initialisation sequence is fairly complex. First a lock is acquired
@@ -105,6 +109,10 @@ protected:
*/
void probe_emulation( const MidiByteArray & bytes );
/// Handle timeout events set for controls that don't emit
/// an off event
bool handle_control_timeout_event ( Control * );
private:
MackieControlProtocol & _mcp;
port_type_t _port_type;

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,6 @@ namespace Mackie
{
class MackieButtonHandler;
class MackieSurface : public Surface
{
public:
@@ -20,6 +19,12 @@ public:
virtual void handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button );
virtual void init_controls();
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 );
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -24,6 +24,7 @@
#include <algorithm>
#include <cstdarg>
#include <iomanip>
#include <stdexcept>
using namespace std;
@@ -96,3 +97,12 @@ ostream & operator << ( ostream & os, const MidiByteArray & mba )
os << "]";
return os;
}
MidiByteArray & operator << ( MidiByteArray & mba, const std::string & st )
{
for ( string::const_iterator it = st.begin(); it != st.end(); ++it )
{
mba << *it;
}
return mba;
}

View File

@@ -67,6 +67,9 @@ public:
/// append the given byte to the end of the array
MidiByteArray & operator << ( MidiByteArray & mba, const MIDI::byte & b );
/// append the given string to the end of the array
MidiByteArray & operator << ( MidiByteArray & mba, const std::string & );
/// append the given array to the end of this array
MidiByteArray & operator << ( MidiByteArray & mba, const MidiByteArray & barr );

View File

@@ -20,37 +20,41 @@
#include <ardour/route.h>
#include <ardour/track.h>
#include <ardour/panner.h>
#include <ardour/types.h>
#include "mackie_control_protocol.h"
#include <stdexcept>
using namespace Mackie;
using namespace std;
void RouteSignal::connect()
{
back_insert_iterator<Connections> cins = back_inserter( _connections );
if ( _strip.has_solo() )
_solo_changed_connection = _route.solo_control()->Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_solo_changed ), this ) );
cins = _route.solo_control()->Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_solo_changed ), this ) );
if ( _strip.has_mute() )
_mute_changed_connection = _route.mute_control()->Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_mute_changed ), this ) );
cins = _route.mute_control()->Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_mute_changed ), this ) );
if ( _strip.has_gain() )
_gain_changed_connection = _route.gain_control()->Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_gain_changed ), this ) );
cins = _route.gain_control()->Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_gain_changed ), this, true ) );
_name_changed_connection = _route.NameChanged.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_name_changed ), this ) );
cins = _route.NameChanged.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_name_changed ), this ) );
if ( _route.panner().npanners() == 1 )
cins = _route.panner().Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_panner_changed ), this, true ) );
for ( unsigned int i = 0; i < _route.panner().npanners(); ++i )
{
_panner_changed_connection = _route.panner().pan_control(0)->Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_panner_changed ), this ) );
cins = _route.panner().streampanner (i).Changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_panner_changed ), this, true ) );
}
try
{
_record_enable_changed_connection =
dynamic_cast<ARDOUR::Track&>( _route ).rec_enable_control()->Changed
.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_record_enable_changed ), this ) )
cins = dynamic_cast<ARDOUR::Track&>( _route )
.rec_enable_control()
->Changed
.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_record_enable_changed ), this ) )
;
}
catch ( std::bad_cast & )
@@ -59,24 +63,28 @@ void RouteSignal::connect()
// with can't be record-enabled
}
// TODO this works when a currently-banked route is made inactive, but not
// when a route is activated which should be currently banked.
cins = _route.active_changed.connect( sigc::bind ( mem_fun ( _mcp, &MackieControlProtocol::notify_active_changed ), this ) );
// TODO
// active_changed
// SelectedChanged
// RemoteControlIDChanged. Better handled at Session level.
}
void RouteSignal::disconnect()
{
_solo_changed_connection.disconnect();
_mute_changed_connection.disconnect();
_gain_changed_connection.disconnect();
_name_changed_connection.disconnect();
_panner_changed_connection.disconnect();
_record_enable_changed_connection.disconnect();
for ( Connections::iterator it = _connections.begin(); it != _connections.end(); ++it )
{
it->disconnect();
}
}
void RouteSignal::notify_all()
{
#ifdef DEBUG
cout << "RouteSignal::notify_all for " << _strip << endl;
#endif
if ( _strip.has_solo() )
_mcp.notify_solo_changed( this );
@@ -93,4 +101,7 @@ void RouteSignal::notify_all()
if ( _strip.has_recenable() )
_mcp.notify_record_enable_changed( this );
#ifdef DEBUG
cout << "RouteSignal::notify_all finish" << endl;
#endif
}

View File

@@ -20,6 +20,10 @@
#include <sigc++/sigc++.h>
#include <vector>
#include "midi_byte_array.h"
class MackieControlProtocol;
namespace ARDOUR {
@@ -30,7 +34,7 @@ namespace Mackie
{
class Strip;
class MackiePort;
class SurfacePort;
/**
This class is intended to easily create and destroy the set of
@@ -41,8 +45,8 @@ class MackiePort;
class RouteSignal
{
public:
RouteSignal( ARDOUR::Route & route, MackieControlProtocol & mcp, Strip & strip, MackiePort & port )
: _route( route ), _mcp( mcp ), _strip( strip ), _port( port )
RouteSignal( ARDOUR::Route & route, MackieControlProtocol & mcp, Strip & strip, SurfacePort & port )
: _route( route ), _mcp( mcp ), _strip( strip ), _port( port ), _last_gain_written(0.0)
{
connect();
}
@@ -60,20 +64,27 @@ public:
const ARDOUR::Route & route() const { return _route; }
Strip & strip() { return _strip; }
MackiePort & port() { return _port; }
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:
ARDOUR::Route & _route;
MackieControlProtocol & _mcp;
Strip & _strip;
MackiePort & _port;
SurfacePort & _port;
sigc::connection _solo_changed_connection;
sigc::connection _mute_changed_connection;
sigc::connection _record_enable_changed_connection;
sigc::connection _gain_changed_connection;
sigc::connection _name_changed_connection;
sigc::connection _panner_changed_connection;
typedef std::vector<sigc::connection> Connections;
Connections _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;
};
}

View File

@@ -20,13 +20,13 @@ button,1,assignment,io,1,1,0x28
button,1,assignment,sends,1,1,0x5a
button,1,assignment,pan,1,1,0x59
button,1,assignment,plugin,1,1,0x57
button,1,assignment,eq,1,1,0x58
button,1,assignment,dyn,1,1,0x2d
button,1,functions,drop,1,1,0x58 # was eq
button,1,assignment,zoom,1,1,0x2d
button,1,bank,left,1,0,0x2e
button,1,bank,right,1,0,0x2f
button,1,bank,channel_left,1,0,0x30
button,1,bank,channel_right,1,0,0x31
button,1,,flip,1,1,0x32
button,1,,scrub,1,1,0x32
button,1,,edit,1,1,0x56
button,1,display,name_value,1,0,0x34
@@ -54,12 +54,13 @@ button,1,modifiers,cmd_alt,1,0,0x49
button,1,automation,on,1,1,0x4a
button,1,automation,rec_ready,1,1,0x4b
button,1,functions,undo,1,1,0x4c
button,1,automation,snapshot,1,1,0x4d
button,1,automation,snapshot,1,1,0x5f
button,1,functions,redo,1,1,0x4f
button,1,functions,marker,1,1,0x47
button,1,functions,enter,1,1,0x51
button,1,functions,cancel,1,0,0x52
button,1,functions,mixer,1,0,0x53
button,1,functions,save,1,0,0x4d
# transport buttons
button,1,transport,frm_left,1,1,0x5b
@@ -78,8 +79,8 @@ button,1,cursor,"cursor_up",1,0,0x60
button,1,cursor,"cursor_down",1,0,0x61
button,1,cursor,"cursor_left",1,0,0x62
button,1,cursor,"cursor_right",1,0,0x63
button,1,,"zoom",1,1,0x64
button,1,,"scrub",1,1,0x65
button,1,,"dyn",1,1,0x64
button,1,,"flip",1,1,0x65
button,1,user,"user_a",1,0,0x66
button,1,user,"user_b",1,0,0x67
1 type count group name switch led id
20 button 1 assignment functions eq drop 1 1 0x58 0x58 # was eq
21 button 1 assignment dyn zoom 1 1 0x2d
22 button 1 bank left 1 0 0x2e
23 button 1 bank right 1 0 0x2f
24 button 1 bank channel_left 1 0 0x30
25 button 1 bank channel_right 1 0 0x31
26 button 1 flip scrub 1 1 0x32
27 button 1 edit 1 1 0x56
28 button 1 display name_value 1 0 0x34
29 button 1 display smpte_beats 1 0 0x35
30 button 1 F1 1 0 0x36
31 button 1 F2 1 0 0x37
32 button 1 F3 1 0 0x38
54 button 1 functions redo 1 1 0x4f
55 button 1 functions marker 1 1 0x47
56 button 1 functions enter 1 1 0x51
57 button 1 functions cancel 1 0 0x52
58 button 1 functions mixer 1 0 0x53
59 # transport buttons button 1 functions save 1 0 0x4d
60 button # transport buttons
61 button 1 transport frm_right frm_left 1 1 0x5c 0x5b
62 button 1 transport loop frm_right 1 1 0x46 0x5c
63 button 1 transport loop 1 1 0x46
64 button 1 transport punch_in 1 1 0x48
65 button 1 transport punch_out 1 1 0x4e
66 button 1 transport home 1 1 0x2a
79 button 1 user user_a 1 0 0x66
80 button 1 user user_b 1 0 0x67
81 button 7 strip fader_touch 1 0 0x68
82 button 1 master fader_touch 1 0 0x6f
83 button 1 master mute 1 0 0x17
84 button 1 clicking 1 1 0x33
85 button 1 smpte 0 1 0x71
86 button 1 beats 0 1 0x72

View File

@@ -15,8 +15,10 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
this_dir = File.dirname(__FILE__)
require 'faster_csv'
require 'mackie.rb'
require "#{this_dir}/mackie.rb"
class Control
attr_accessor :id, :led, :group, :name, :ordinal, :switch
@@ -191,6 +193,12 @@ class Surface
end
# add the new control to the various lookups
# but first print a warning if the id is duplicated
if @controls_by_id.has_key?( row.id ) && control.group.class != Strip
duplicated = @controls_by_id[row.id]
puts "duplicate id #{control.id}:#{control.name} of #{duplicated.id}:#{duplicated.name}"
end
@controls_by_id[row.id] = control
@controls << control
group << control

View File

@@ -18,6 +18,11 @@
require 'controls.rb'
require 'mackie.rb'
if ARGV.size != 2
puts "#$0 /dev/snd/midiXXXX control-file.csv"
exit 1
end
while !File.exist? ARGV[0]
sleep 0.010
end
@@ -30,46 +35,14 @@ puts ""
file = File.open ARGV[0], 'r+'
mck = Mackie.new( file )
# send device query
response = mck.sysex( "\x00" )
puts "response: #{response.to_hex}"
# decode host connection query
status = response[0]
if status != 1
puts "expected 01, got " + response.to_hex.inspect
exit(1)
end
serial = response[1..7]
challenge = response[8..11]
puts <<EOF
serial: #{serial.to_hex.inspect}
challenge: #{challenge.to_hex.inspect}
EOF
# send host connection reply
response = mck.sysex( "\x02" + serial.pack('C*') + challenge.pack('C*') )
# decode host connection confirmation
status = response[0]
if status != 3
puts "expected 03, got " + response.to_hex.inspect
exit(1)
end
serial = response[1..7]
puts <<EOF
serial: #{serial.to_hex.inspect}
EOF
# faders to minimum. bcf2000 doesn't respond
#file.write( hdr + "\x61\xf7" )
mck.write_sysex "\x61"
# all leds off. bcf2000 doesn't respond
#file.write( hdr + "\x62\xf7" )
mck.write_sysex "\x62"
# get version. comes back as ASCII bytes
version = mck.sysex( "\x13\x00" )
version = mck.sysex "\x13\x00"
puts "version: #{version.map{|x| x.chr}}"
# write a welcome message. bcf2000 responds with exact
@@ -126,7 +99,7 @@ while bytes = mck.file.read( 3 )
control = sf.midis[midi_type][control_id]
print " Control Type: %-7s, " % sf.types[midi_type]
print "id: %4i" % control_id
print "id: %4x" % control_id
print ", control: %15s" % ( control ? control.name : "nil control" )
print ", %15s" % ( control ? control.group.name : "nil group" )
print "\n"

Binary file not shown.

View File

@@ -25,17 +25,8 @@ while !File.exist? ARGV[0]
end
file = File.open( ARGV[0], 'r+' )
mck = Mackie.new( file )
# faders to minimum. bcf2000 doesn't respond
mck.write_sysex "\x61"
# all leds off. bcf2000 doesn't respond
mck.write_sysex "\x62"
# get version. comes back as ASCII bytes
version = mck.sysex "\x13\x00"
puts "version: #{version.map{|x| x.chr}}"
#mck = Mackie.new( file )
device = false
# respond to control movements
while bytes = file.read( 3 )
@@ -121,7 +112,7 @@ while bytes = file.read( 3 )
end
# output bytes
if output
if device && output
#sleep 0.1
puts "sending: %02.x %02.x %02.x" % [ output[0], output[1], output[2] ]
begin

View File

@@ -52,17 +52,29 @@ void Mackie::<%= sf.name %>Surface::init_controls()
% end
// initialise controls
Control * control = 0;
Fader * fader = 0;
Pot * pot = 0;
Button * button = 0;
Led * led = 0;
% sf.controls.each do |control|
<%-
variable_name = control.class.name.downcase
class_name =
if control.name == 'jog'
'Jog'
else
control.class.name
end
-%>
group = groups["<%=control.group.name%>"];
control = new <%= control.class.name %> ( <%= control.id %>, <%= control.ordinal %>, "<%=control.name%>", *group );
<%=control.class.name.downcase%>s[0x<%=control.id.to_hex %>] = control;
controls.push_back( control );
<%= variable_name %> = new <%= class_name %> ( <%= control.id %>, <%= control.ordinal %>, "<%=control.name%>", *group );
<%= variable_name %>s[0x<%=control.id.to_hex %>] = <%= variable_name %>;
controls.push_back( <%= variable_name %> );
<%- if control.group.class != Strip -%>
controls_by_name["<%= control.name %>"] = control;
controls_by_name["<%= control.name %>"] = <%= variable_name %>;
<%- end -%>
group->add( *control );
group->add( *<%= variable_name %> );
% end
}
@@ -82,7 +94,7 @@ void Mackie::<%= sf.name %>Surface::handle_button( MackieButtonHandler & mbh, Bu
buttons = sf.controls.find_all{|x| x.class == Button && x.group.class != Strip}
buttons.each do |button|
%>
case 0x<%= button.id.to_hex %>: // <%= button.name %>
case 0x<%= ( button.class.midi_zero_byte << 8 | button.id ).to_hex %>: // <%= button.name %>
switch ( bs ) {
case press: ls = mbh.<%= button.name %>_press( button ); break;
case release: ls = mbh.<%= button.name %>_release( button ); break;

View File

@@ -4,6 +4,6 @@ require 'controls.rb'
require 'pp'
sf = Surface.new
sf.parse
sf.parse( ARGV[0] )
sf.types.each{|k,v| puts "%02.x #{v}" % k}

View File

@@ -3,7 +3,6 @@
#include <sstream>
#include <iomanip>
#include <iostream>
#include <typeinfo>
using namespace std;
using namespace Mackie;
@@ -15,8 +14,14 @@ Surface::Surface( uint32_t max_strips, uint32_t unit_strips )
void Surface::init()
{
#ifdef DEBUG
cout << "Surface::init" << endl;
#endif
init_controls();
init_strips( _max_strips, _unit_strips );
#ifdef DEBUG
cout << "Surface::init finish" << endl;
#endif
}
Surface::~Surface()
@@ -52,6 +57,8 @@ void Surface::init_strips( uint32_t max_strips, uint32_t unit_strips )
// shallow copy existing strip
// which works because the controls
// have the same ids across units
// TODO this needs to be a deep copy because
// controls hold state now - in_use
Strip * strip = new Strip( *strips[i % unit_strips] );
// update the relevant values
@@ -64,80 +71,3 @@ void Surface::init_strips( uint32_t max_strips, uint32_t unit_strips )
}
}
}
ostream & Mackie::operator << ( ostream & os, const Mackie::Control & control )
{
os << typeid( control ).name();
os << " { ";
os << "name: " << control.name();
os << ", ";
os << "id: " << "0x" << setw(2) << setfill('0') << hex << control.id() << setfill(' ');
os << ", ";
os << "ordinal: " << dec << control.ordinal();
os << ", ";
os << "group: " << control.group().name();
os << " }";
return os;
}
/**
TODO could optimise this to use enum, but it's only
called during the protocol class instantiation.
generated using
irb -r controls.rb
sf=Surface.new
sf.parse
controls = sf.groups.find{|x| x[0] =~ /strip/}.each{|x| puts x[1]}
controls[1].each {|x| puts "\telse if ( control.name() == \"#{x.name}\" )\n\t{\n\t\t_#{x.name} = reinterpret_cast<#{x.class.name}*>(&control);\n\t}\n"}
*/
void Strip::add( Control & control )
{
Group::add( control );
if ( control.name() == "gain" )
{
_gain = reinterpret_cast<Fader*>(&control);
}
else if ( control.name() == "vpot" )
{
_vpot = reinterpret_cast<Pot*>(&control);
}
else if ( control.name() == "recenable" )
{
_recenable = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "solo" )
{
_solo = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "mute" )
{
_mute = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "select" )
{
_select = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "vselect" )
{
_vselect = reinterpret_cast<Button*>(&control);
}
else if ( control.name() == "fader_touch" )
{
_fader_touch = reinterpret_cast<Button*>(&control);
}
else if ( control.type() == Control::type_led || control.type() == Control::type_led_ring )
{
// do nothing
cout << "Strip::add not adding " << control << endl;
}
else
{
ostringstream os;
os << "Strip::add: unknown control type " << control;
throw MackieControlException( os.str() );
}
}

View File

@@ -9,6 +9,8 @@ namespace Mackie
{
class MackieButtonHandler;
class SurfacePort;
class MackieMidiBuilder;
/**
This represents an entire control surface, made up of Groups,
@@ -53,11 +55,13 @@ public:
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,Control*> faders;
std::map<int,Control*> pots;
std::map<int,Control*> buttons;
std::map<int,Control*> leds;
std::map<int,Fader*> faders;
std::map<int,Pot*> pots;
std::map<int,Button*> buttons;
std::map<int,Led*> leds;
/// no strip controls in here because they usually
/// have the same names.
@@ -79,7 +83,38 @@ public:
/// map button ids to calls to press_ and release_ in mbh
virtual void handle_button( MackieButtonHandler & mbh, ButtonState bs, Button & button ) = 0;
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 ) {};
/// called from MackieControlPRotocol::zero_all to turn things off
virtual void zero_all( SurfacePort &, MackieMidiBuilder & ) {};
/// 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 & ) {};
virtual bool has_timecode_display() const = 0;
virtual void display_timecode( SurfacePort &, MackieMidiBuilder &, const std::string & timecode, const std::string & timecode_last ) {};
public:
/**
This is used to calculate the clicks per second that define
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;
/**
The scaling factor function for speed increase and decrease. At
low transport speeds this should return a small value, for high transport
speeds, this should return an exponentially larger value. This provides
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;
protected:
virtual void init_controls() = 0;
virtual void init_strips( uint32_t max_strips, uint32_t unit_strips );

View File

@@ -35,18 +35,27 @@
using namespace std;
using namespace Mackie;
SurfacePort::SurfacePort()
: _port( 0 ), _number( 0 ), _active( false )
{
}
SurfacePort::SurfacePort( MIDI::Port & port, int number )
: _port( port ), _number( number ), _active( false )
: _port( &port ), _number( number ), _active( false )
{
}
SurfacePort::~SurfacePort()
{
//cout << "~SurfacePort::SurfacePort()" << endl;
#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;
//cout << "~SurfacePort::SurfacePort() finished" << endl;
#ifdef PORT_DEBUG
cout << "~SurfacePort::SurfacePort() finished" << endl;
#endif
}
// wrapper for one day when strerror_r is working properly
@@ -67,24 +76,29 @@ MidiByteArray SurfacePort::read()
if ( !active() ) return retval;
// 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;
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 = port().read (buf, sizeof (buf));
int nread = port().read( buf, sizeof (buf) );
if (nread >= 0) {
retval.copy( nread, buf );
if ((size_t) nread == sizeof (buf))
{
#ifdef PORT_DEBUG
cout << "SurfacePort::read recursive" << endl;
#endif
retval << read();
}
}
@@ -101,13 +115,17 @@ MidiByteArray SurfacePort::read()
throw MackieControlException( os.str() );
}
}
#ifdef PORT_DEBUG
cout << "SurfacePort::read: " << retval << endl;
#endif
return retval;
}
void SurfacePort::write( const MidiByteArray & mba )
{
//if ( mba[0] == 0xf0 ) cout << "SurfacePort::write: " << mba << endl;
//cout << "SurfacePort::write: " << mba << endl;
#ifdef PORT_DEBUG
cout << "SurfacePort::write: " << mba << endl;
#endif
// check active before and after lock - to make sure
// that the destructor doesn't destroy the mutex while
@@ -116,21 +134,26 @@ void SurfacePort::write( const MidiByteArray & mba )
Glib::RecMutex::Lock lock( _rwlock );
if ( !active() ) return;
int count = port().write( mba.bytes().get(), mba.size(), 0 );
int count = port().write( mba.bytes().get(), mba.size(), 0);
if ( count != (int)mba.size() )
{
if ( errno != EAGAIN )
if ( errno == 0 )
{
cout << "port overflow on " << port().name() << ". Did not write all of " << mba << endl;
}
else if ( errno != EAGAIN )
{
ostringstream os;
os << "Surface: couldn't write to port " << port().name();
os << ": " << errno << fetch_errmsg( errno );
os << ", error: " << fetch_errmsg( errno ) << "(" << errno << ")";
cout << os.str();
cout << os.str() << endl;
inactive_event();
throw MackieControlException( os.str() );
}
}
//if ( mba[0] == 0xf0 ) cout << "SurfacePort::write " << count << endl;
#ifdef PORT_DEBUG
cout << "SurfacePort::wrote " << count << endl;
#endif
}
void SurfacePort::write_sysex( const MidiByteArray & mba )
@@ -147,22 +170,6 @@ void SurfacePort::write_sysex( MIDI::byte msg )
write( buf );
}
// This should be moved to midi++ at some point
ostream & operator << ( ostream & os, const MIDI::Port & port )
{
os << "device: " << port.device();
os << "; ";
os << "name: " << port.name();
os << "; ";
os << "type: " << port.type();
os << "; ";
os << "mode: " << port.mode();
os << "; ";
os << "ok: " << port.ok();
os << "; ";
return os;
}
ostream & Mackie::operator << ( ostream & os, const SurfacePort & port )
{
os << "{ ";

View File

@@ -48,20 +48,20 @@ public:
/// read bytes from the port. They'll either end up in the
/// parser, or if that's not active they'll be returned
MidiByteArray read();
virtual MidiByteArray read();
/// an easier way to output bytes via midi
void write( const MidiByteArray & );
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
/// return the correct sysex header for this port
virtual const MidiByteArray & sysex_hdr() const = 0;
MIDI::Port & port() { return _port; }
const MIDI::Port & port() const { return _port; }
MIDI::Port & port() { return *_port; }
const MIDI::Port & port() const { return *_port; }
// all control notofications are sent from here
sigc::signal<void, SurfacePort &, Control &, const ControlState &> control_event;
@@ -85,8 +85,12 @@ public:
virtual bool active() const { return _active; }
virtual void active( bool yn ) { _active = yn; }
protected:
/// Only for use by DummyPort
SurfacePort();
private:
MIDI::Port & _port;
MIDI::Port * _port;
int _number;
bool _active;

View File

@@ -0,0 +1,136 @@
/*
Copyright (C) 1998, 1999, 2000, 2007 John Anderson
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU Library 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 Library General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef timer_h
#define timer_h
#ifdef _WIN32
#include "windows.h"
#else
#include <sys/time.h>
#endif
namespace Mackie
{
/**
millisecond timer class.
*/
class Timer
{
public:
/**
start the timer running if true, or just create the
object if false.
*/
Timer( bool shouldStart = true )
{
if ( shouldStart )
start();
}
/**
Start the timer running. Return the current timestamp, in milliseconds
*/
unsigned long start()
{
#ifdef _WIN32
_start = (unsigned long)::GetTickCount();
#else
gettimeofday ( &_start, 0 );
#endif
running = true;
#ifdef _WIN32
return _start;
#else
return ( _start.tv_sec * 1000000 + _start.tv_usec ) / 1000;
#endif
}
/**
returns the number of milliseconds since start
also stops the timer running
*/
unsigned long stop()
{
#ifdef _WIN32
_stop = (unsigned long)::GetTickCount();
#else
gettimeofday ( &_stop, 0 );
#endif
running = false;
return elapsed();
}
/**
returns the number of milliseconds since start
*/
unsigned long elapsed() const
{
if ( running )
{
#ifdef _WIN32
DWORD current = ::GetTickCount();
return current - _start;
#else
struct timeval current;
gettimeofday ( &current, 0 );
return (
( current.tv_sec * 1000000 + current.tv_usec ) - ( _start.tv_sec * 1000000 + _start.tv_usec )
) / 1000
;
#endif
}
else
{
#ifdef _WIN32
return _stop - _start;
#else
return (
( _stop.tv_sec * 1000000 + _stop.tv_usec ) - ( _start.tv_sec * 1000000 + _start.tv_usec )
) / 1000
;
#endif
}
}
/**
Call stop and then start. Return the value from stop.
*/
unsigned long restart()
{
unsigned long retval = stop();
start();
return retval;
}
private:
#ifdef _WIN32
unsigned long _start;
unsigned long _stop;
#else
struct timeval _start;
struct timeval _stop;
#endif
bool running;
};
}
#endif

View File

@@ -2,8 +2,28 @@
namespace Mackie
{
LedState on( LedState::on );
LedState off( LedState::off );
LedState flashing( LedState::flashing );
LedState none( LedState::none );
LedState on( LedState::on );
LedState off( LedState::off );
LedState flashing( LedState::flashing );
LedState none( LedState::none );
std::ostream & operator << ( std::ostream & os, const ControlState & cs )
{
os << "ControlState { ";
os << "pos: " << cs.pos;
os << ", ";
os << "sign: " << cs.sign;
os << ", ";
os << "delta: " << cs.delta;
os << ", ";
os << "ticks: " << cs.ticks;
os << ", ";
os << "led_state: " << cs.led_state.state();
os << ", ";
os << "button_state: " << cs.button_state;
os << " }";
return os;
}
}

View File

@@ -18,6 +18,8 @@
#ifndef mackie_types_h
#define mackie_types_h
#include <iostream>
namespace Mackie
{
@@ -62,23 +64,34 @@ enum ButtonState { neither = -1, release = 0, press = 1 };
*/
struct ControlState
{
ControlState(): pos(0.0), delta(0.0), button_state(neither) {}
ControlState(): pos(0.0), sign(0), delta(0.0), ticks(0), led_state(off), button_state(neither) {}
ControlState( LedState ls ): pos(0.0), delta(0.0), led_state(ls), button_state(neither) {}
// Note that this sets both pos and delta to the flt value
ControlState( LedState ls, float flt ): pos(flt), delta(flt), ticks(0), led_state(ls), button_state(neither) {}
ControlState( float flt ): pos(flt), delta(flt), ticks(0), led_state(none), button_state(neither) {}
ControlState( float flt, int tcks ): pos(flt), delta(flt), ticks(tcks), led_state(none), button_state(neither) {}
ControlState( float flt, unsigned int tcks ): pos(flt), delta(flt), ticks(tcks), led_state(none), button_state(neither) {}
ControlState( ButtonState bs ): pos(0.0), delta(0.0), ticks(0), led_state(none), button_state(bs) {}
/// For faders. Between 0 and 1.
float pos;
/// For pots. Sign. Either -1 or 1;
int sign;
/// For pots. Signed value of total movement. Between 0 and 1
float delta;
int ticks;
/// For pots. Unsigned number of ticks. Usually between 1 and 16.
unsigned int ticks;
LedState led_state;
ButtonState button_state;
};
std::ostream & operator << ( std::ostream &, const ControlState & );
class Control;
class Fader;
class Button;