Clean up MMC transmission a bit, and make sure that it is all done from one thread.

git-svn-id: svn://localhost/ardour2/branches/3.0@7324 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Carl Hetherington 2010-06-29 13:47:53 +00:00
parent b47524ded2
commit 9469d6b26a
10 changed files with 232 additions and 339 deletions

View File

@ -41,7 +41,6 @@
#include "pbd/signals.h"
#include "pbd/undo.h"
#include "midi++/mmc.h"
#include "midi++/types.h"
#include "ardour/ardour.h"
@ -64,6 +63,8 @@ class AEffect;
namespace MIDI {
class Port;
class MachineControl;
class Parser;
}
namespace PBD {
@ -640,16 +641,13 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
void midi_panic(void);
int set_mtc_port (std::string port_tag);
int set_mmc_port (std::string port_tag);
int set_midi_port (std::string port_tag);
int set_midi_clock_port (std::string port_tag);
MIDI::Port *mtc_port() const { return _mtc_port; }
MIDI::Port *mmc_port() const { return _mmc_port; }
MIDI::Port *midi_port() const { return _midi_port; }
MIDI::Port *midi_clock_port() const { return _midi_clock_port; }
PBD::Signal0<void> MTC_PortChanged;
PBD::Signal0<void> MMC_PortChanged;
PBD::Signal0<void> MIDI_PortChanged;
PBD::Signal0<void> MIDIClock_PortChanged;
@ -659,9 +657,6 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
bool get_trace_midi_input(MIDI::Port *port = 0);
bool get_trace_midi_output(MIDI::Port *port = 0);
void set_mmc_receive_device_id (uint32_t id);
void set_mmc_send_device_id (uint32_t id);
/* Scrubbing */
void start_scrub (nframes_t where);
@ -951,15 +946,13 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
void check_declick_out ();
MIDI::MachineControl* mmc;
MIDI::Port* _mmc_port;
MIDI::MachineControl* _mmc;
MIDI::Port* _mtc_port;
MIDI::Port* _midi_port;
MIDI::Port* _midi_clock_port;
std::string _path;
std::string _name;
bool _is_new;
bool session_send_mmc;
bool session_send_mtc;
bool session_midi_feedback;
bool play_loop;
@ -1092,8 +1085,6 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
/* MIDI Machine Control */
void deliver_mmc (MIDI::MachineControl::Command, nframes_t);
void spp_start (MIDI::Parser&, nframes_t timestamp);
void spp_continue (MIDI::Parser&, nframes_t timestamp);
void spp_stop (MIDI::Parser&, nframes_t timestamp);
@ -1121,7 +1112,6 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
MidiTimeoutList midi_timeouts;
bool mmc_step_timeout ();
MIDI::byte mmc_buffer[32];
MIDI::byte mtc_msg[16];
MIDI::byte mtc_timecode_bits; /* encoding of SMTPE type for MTC */
MIDI::byte midi_msg[16];
@ -1444,7 +1434,7 @@ class Session : public PBD::StatefulDestructible, public PBD::ScopedConnectionLi
void add_session_range_location (nframes_t, nframes_t);
void setup_midi_machine_control ();
};
} // namespace ARDOUR

View File

@ -33,6 +33,7 @@
#include "pbd/unknown_type.h"
#include "midi++/jack.h"
#include "midi++/mmc.h"
#include "ardour/amp.h"
#include "ardour/audio_port.h"
@ -146,6 +147,7 @@ _thread_init_callback (void * /*arg*/)
SessionEvent::create_per_thread_pool (X_("Audioengine"), 512);
MIDI::JACK_MidiPort::set_process_thread (pthread_self());
MIDI::MachineControl::set_sending_thread (pthread_self ());
}
typedef void (*_JackInfoShutdownCallback)(jack_status_t code, const char* reason, void *arg);

View File

@ -99,6 +99,7 @@
#include "ardour/graph.h"
#include "midi++/jack.h"
#include "midi++/mmc.h"
#include "i18n.h"
@ -135,8 +136,7 @@ Session::Session (AudioEngine &eng,
: _engine (eng),
_target_transport_speed (0.0),
_requested_return_frame (-1),
mmc (0),
_mmc_port (default_mmc_port),
_mmc (0),
_mtc_port (default_mtc_port),
_midi_port (default_midi_port),
_midi_clock_port (default_midi_clock_port),
@ -305,7 +305,7 @@ Session::destroy ()
Crossfade::set_buffer_size (0);
delete mmc;
delete _mmc;
/* not strictly necessary, but doing it here allows the shared_ptr debugging to work */
playlists.reset ();
@ -961,7 +961,7 @@ Session::enable_record ()
if (g_atomic_int_get (&_record_status) != Recording) {
g_atomic_int_set (&_record_status, Recording);
_last_record_location = _transport_frame;
deliver_mmc(MIDI::MachineControl::cmdRecordStrobe, _last_record_location);
_mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordStrobe));
if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
@ -987,19 +987,13 @@ Session::disable_record (bool rt_context, bool force)
if ((!Config->get_latched_record_enable () && !play_loop) || force) {
g_atomic_int_set (&_record_status, Disabled);
_mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordExit));
} else {
if (rs == Recording) {
g_atomic_int_set (&_record_status, Enabled);
}
}
// FIXME: timestamp correct? [DR]
// FIXME FIXME FIXME: rt_context? this must be called in the process thread.
// does this /need/ to be sent in all cases?
if (rt_context) {
deliver_mmc (MIDI::MachineControl::cmdRecordExit, _transport_frame);
}
if (Config->get_monitoring_model() == HardwareMonitoring && config.get_auto_input()) {
boost::shared_ptr<RouteList> rl = routes.reader ();
@ -1053,7 +1047,7 @@ Session::maybe_enable_record ()
enable_record ();
}
} else {
deliver_mmc (MIDI::MachineControl::cmdRecordPause, _transport_frame);
_mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdRecordPause));
RecordStateChanged (); /* EMIT SIGNAL */
}

View File

@ -56,10 +56,6 @@ using namespace PBD;
using namespace MIDI;
using namespace Glib;
MachineControl::CommandSignature MMC_CommandSignature;
MachineControl::ResponseSignature MMC_ResponseSignature;
void
Session::midi_panic()
{
@ -80,12 +76,6 @@ Session::use_config_midi_ports ()
{
string port_name;
if (default_mmc_port) {
set_mmc_port (default_mmc_port->name());
} else {
set_mmc_port ("");
}
if (default_mtc_port) {
set_mtc_port (default_mtc_port->name());
} else {
@ -153,90 +143,6 @@ Session::set_mtc_port (string port_tag)
return 0;
}
void
Session::set_mmc_receive_device_id (uint32_t device_id)
{
if (mmc) {
mmc->set_receive_device_id (device_id);
}
}
void
Session::set_mmc_send_device_id (uint32_t device_id)
{
if (mmc) {
mmc->set_send_device_id (device_id);
}
}
int
Session::set_mmc_port (string port_tag)
{
MIDI::byte old_recv_device_id = 0;
MIDI::byte old_send_device_id = 0;
bool reset_id = false;
if (port_tag.length() == 0) {
if (_mmc_port == 0) {
return 0;
}
_mmc_port = 0;
goto out;
}
MIDI::Port* port;
if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
return -1;
}
_mmc_port = port;
if (mmc) {
old_recv_device_id = mmc->receive_device_id();
old_recv_device_id = mmc->send_device_id();
reset_id = true;
delete mmc;
}
mmc = new MIDI::MachineControl (*_mmc_port, 1.0,
MMC_CommandSignature,
MMC_ResponseSignature);
if (reset_id) {
mmc->set_receive_device_id (old_recv_device_id);
mmc->set_send_device_id (old_send_device_id);
}
mmc->Play.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
mmc->DeferredPlay.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
mmc->Stop.connect_same_thread (*this, boost::bind (&Session::mmc_stop, this, _1));
mmc->FastForward.connect_same_thread (*this, boost::bind (&Session::mmc_fast_forward, this, _1));
mmc->Rewind.connect_same_thread (*this, boost::bind (&Session::mmc_rewind, this, _1));
mmc->Pause.connect_same_thread (*this, boost::bind (&Session::mmc_pause, this, _1));
mmc->RecordPause.connect_same_thread (*this, boost::bind (&Session::mmc_record_pause, this, _1));
mmc->RecordStrobe.connect_same_thread (*this, boost::bind (&Session::mmc_record_strobe, this, _1));
mmc->RecordExit.connect_same_thread (*this, boost::bind (&Session::mmc_record_exit, this, _1));
mmc->Locate.connect_same_thread (*this, boost::bind (&Session::mmc_locate, this, _1, _2));
mmc->Step.connect_same_thread (*this, boost::bind (&Session::mmc_step, this, _1, _2));
mmc->Shuttle.connect_same_thread (*this, boost::bind (&Session::mmc_shuttle, this, _1, _2, _3));
mmc->TrackRecordStatusChange.connect_same_thread (*this, boost::bind (&Session::mmc_record_enable, this, _1, _2, _3));
/* also handle MIDI SPP because its so common */
_mmc_port->input()->start.connect_same_thread (*this, boost::bind (&Session::spp_start, this, _1, _2));
_mmc_port->input()->contineu.connect_same_thread (*this, boost::bind (&Session::spp_continue, this, _1, _2));
_mmc_port->input()->stop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this, _1, _2));
Config->set_mmc_port_name (port_tag);
out:
MMC_PortChanged(); /* EMIT SIGNAL */
set_dirty();
return 0;
}
int
Session::set_midi_port (string /*port_tag*/)
{
@ -324,26 +230,26 @@ Session::set_trace_midi_input (bool yn, MIDI::Port* port)
}
} else {
if (_mmc_port) {
if ((input_parser = _mmc_port->input()) != 0) {
if (_mmc->port()) {
if ((input_parser = _mmc->port()->input()) != 0) {
input_parser->trace (yn, &cout, "input: ");
}
}
if (_mtc_port && _mtc_port != _mmc_port) {
if (_mtc_port && _mtc_port != _mmc->port()) {
if ((input_parser = _mtc_port->input()) != 0) {
input_parser->trace (yn, &cout, "input: ");
}
}
if (_midi_port && _midi_port != _mmc_port && _midi_port != _mtc_port ) {
if (_midi_port && _midi_port != _mmc->port() && _midi_port != _mtc_port ) {
if ((input_parser = _midi_port->input()) != 0) {
input_parser->trace (yn, &cout, "input: ");
}
}
if (_midi_clock_port
&& _midi_clock_port != _mmc_port
&& _midi_clock_port != _mmc->port()
&& _midi_clock_port != _mtc_port
&& _midi_clock_port != _midi_port) {
if ((input_parser = _midi_clock_port->input()) != 0) {
@ -365,19 +271,19 @@ Session::set_trace_midi_output (bool yn, MIDI::Port* port)
output_parser->trace (yn, &cout, "output: ");
}
} else {
if (_mmc_port) {
if ((output_parser = _mmc_port->output()) != 0) {
if (_mmc->port()) {
if ((output_parser = _mmc->port()->output()) != 0) {
output_parser->trace (yn, &cout, "output: ");
}
}
if (_mtc_port && _mtc_port != _mmc_port) {
if (_mtc_port && _mtc_port != _mmc->port()) {
if ((output_parser = _mtc_port->output()) != 0) {
output_parser->trace (yn, &cout, "output: ");
}
}
if (_midi_port && _midi_port != _mmc_port && _midi_port != _mtc_port ) {
if (_midi_port && _midi_port != _mmc->port() && _midi_port != _mtc_port ) {
if ((output_parser = _midi_port->output()) != 0) {
output_parser->trace (yn, &cout, "output: ");
}
@ -398,8 +304,8 @@ Session::get_trace_midi_input(MIDI::Port *port)
}
}
else {
if (_mmc_port) {
if ((input_parser = _mmc_port->input()) != 0) {
if (_mmc->port()) {
if ((input_parser = _mmc->port()->input()) != 0) {
return input_parser->tracing();
}
}
@ -430,8 +336,8 @@ Session::get_trace_midi_output(MIDI::Port *port)
}
}
else {
if (_mmc_port) {
if ((output_parser = _mmc_port->output()) != 0) {
if (_mmc->port()) {
if ((output_parser = _mmc->port()->output()) != 0) {
return output_parser->tracing();
}
}
@ -459,13 +365,6 @@ Session::setup_midi_control ()
outbound_mtc_timecode_frame = 0;
next_quarter_frame_to_send = 0;
/* setup the MMC buffer */
mmc_buffer[0] = 0xf0; // SysEx
mmc_buffer[1] = 0x7f; // Real Time SysEx ID for MMC
mmc_buffer[2] = (mmc ? mmc->send_device_id() : 0x7f);
mmc_buffer[3] = 0x6; // MCC
/* Set up the qtr frame message */
mtc_msg[0] = 0xf1;
@ -896,69 +795,6 @@ Session::send_midi_time_code_for_cycle(nframes_t nframes)
OUTBOUND MMC STUFF
**********************************************************************/
void
Session::deliver_mmc (MIDI::MachineControl::Command cmd, nframes_t where)
{
using namespace MIDI;
int nbytes = 4;
Timecode::Time timecode;
if (_mmc_port == 0 || !session_send_mmc) {
// cerr << "Not delivering MMC " << _mmc_port << " - " << session_send_mmc << endl;
return;
}
mmc_buffer[nbytes++] = cmd;
// cerr << "delivering MMC, cmd = " << hex << (int) cmd << dec << endl;
switch (cmd) {
case MachineControl::cmdLocate:
timecode_time_subframes (where, timecode);
mmc_buffer[nbytes++] = 0x6; // byte count
mmc_buffer[nbytes++] = 0x1; // "TARGET" subcommand
mmc_buffer[nbytes++] = timecode.hours;
mmc_buffer[nbytes++] = timecode.minutes;
mmc_buffer[nbytes++] = timecode.seconds;
mmc_buffer[nbytes++] = timecode.frames;
mmc_buffer[nbytes++] = timecode.subframes;
break;
case MachineControl::cmdStop:
break;
case MachineControl::cmdPlay:
/* always convert Play into Deferred Play */
/* Why? [DR] */
mmc_buffer[4] = MachineControl::cmdDeferredPlay;
break;
case MachineControl::cmdDeferredPlay:
break;
case MachineControl::cmdRecordStrobe:
break;
case MachineControl::cmdRecordExit:
break;
case MachineControl::cmdRecordPause:
break;
default:
nbytes = 0;
};
if (nbytes) {
mmc_buffer[nbytes++] = 0xf7; // terminate SysEx/MMC message
if (_mmc_port->midimsg (mmc_buffer, nbytes, 0)) {
error << string_compose(_("MMC: cannot send command %1%2%3"), &hex, cmd, &dec) << endmsg;
}
}
}
bool
Session::mmc_step_timeout ()

View File

@ -40,6 +40,7 @@
#include "ardour/port.h"
#include "midi++/manager.h"
#include "midi++/mmc.h"
#include "i18n.h"
@ -67,6 +68,8 @@ Session::process (nframes_t nframes)
post_transport ();
}
}
_mmc->flush_pending ();
_engine.main_thread()->get_buffers ();

View File

@ -204,7 +204,6 @@ Session::first_stage_init (string fullpath, string snapshot_name)
_state_of_the_state = StateOfTheState(CannotSave|InitialConnecting|Loading);
_was_seamless = Config->get_seamless_loop ();
_slave = 0;
session_send_mmc = false;
session_send_mtc = false;
g_atomic_int_set (&_playback_load, 100);
g_atomic_int_set (&_capture_load, 100);
@ -299,6 +298,8 @@ Session::second_stage_init ()
return -1;
}
setup_midi_machine_control ();
// set_state() will call setup_raid_path(), but if it's a new session we need
// to call setup_raid_path() here.
@ -322,7 +323,6 @@ Session::second_stage_init ()
_state_of_the_state = StateOfTheState (_state_of_the_state|CannotSave|Loading);
_locations.changed.connect_same_thread (*this, boost::bind (&Session::locations_changed, this));
_locations.added.connect_same_thread (*this, boost::bind (&Session::locations_added, this, _1));
setup_click_sounds (0);
@ -352,8 +352,8 @@ Session::second_stage_init ()
send_full_time_code (0);
_engine.transport_locate (0);
deliver_mmc (MIDI::MachineControl::cmdMmcReset, 0);
deliver_mmc (MIDI::MachineControl::cmdLocate, 0);
_mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdMmcReset));
_mmc->send (MIDI::MachineControlCommand (Timecode::Time ()));
MidiClockTicker::instance().set_session (this);
MIDI::Name::MidiPatchManager::instance().set_session (this);
@ -1242,6 +1242,8 @@ Session::set_state (const XMLNode& node, int version)
error << _("Session: XML state has no options section") << endmsg;
}
setup_midi_machine_control ();
if (use_config_midi_ports ()) {
}
@ -3180,15 +3182,11 @@ Session::config_changed (std::string p, bool ours)
} else if (p == "mmc-device-id" || p == "mmc-receive-id") {
if (mmc) {
mmc->set_receive_device_id (Config->get_mmc_receive_device_id());
}
_mmc->set_receive_device_id (Config->get_mmc_receive_device_id());
} else if (p == "mmc-send-id") {
if (mmc) {
mmc->set_send_device_id (Config->get_mmc_send_device_id());
}
_mmc->set_send_device_id (Config->get_mmc_send_device_id());
} else if (p == "midi-control") {
@ -3254,16 +3252,7 @@ Session::config_changed (std::string p, bool ours)
} else if (p == "send-mmc") {
/* only set the internal flag if we have
a port.
*/
if (_mmc_port != 0) {
session_send_mmc = Config->get_send_mmc();
} else {
mmc = 0;
session_send_mmc = false;
}
_mmc->enable_send (Config->get_send_mmc ());
} else if (p == "midi-feedback") {
@ -3311,17 +3300,17 @@ Session::config_changed (std::string p, bool ours)
sync_order_keys ("session");
} else if (p == "initial-program-change") {
if (_mmc_port && Config->get_initial_program_change() >= 0) {
if (_mmc->port() && Config->get_initial_program_change() >= 0) {
MIDI::byte buf[2];
buf[0] = MIDI::program; // channel zero by default
buf[1] = (Config->get_initial_program_change() & 0x7f);
_mmc_port->midimsg (buf, sizeof (buf), 0);
_mmc->port()->midimsg (buf, sizeof (buf), 0);
}
} else if (p == "initial-program-change") {
if (_mmc_port && Config->get_initial_program_change() >= 0) {
if (_mmc->port() && Config->get_initial_program_change() >= 0) {
MIDI::byte* buf = new MIDI::byte[2];
buf[0] = MIDI::program; // channel zero by default
@ -3374,3 +3363,31 @@ Session::load_diskstreams_2X (XMLNode const & node, int)
return 0;
}
/** Create our MachineControl object and connect things to it */
void
Session::setup_midi_machine_control ()
{
_mmc = new MIDI::MachineControl;
_mmc->set_port (default_mmc_port);
_mmc->Play.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
_mmc->DeferredPlay.connect_same_thread (*this, boost::bind (&Session::mmc_deferred_play, this, _1));
_mmc->Stop.connect_same_thread (*this, boost::bind (&Session::mmc_stop, this, _1));
_mmc->FastForward.connect_same_thread (*this, boost::bind (&Session::mmc_fast_forward, this, _1));
_mmc->Rewind.connect_same_thread (*this, boost::bind (&Session::mmc_rewind, this, _1));
_mmc->Pause.connect_same_thread (*this, boost::bind (&Session::mmc_pause, this, _1));
_mmc->RecordPause.connect_same_thread (*this, boost::bind (&Session::mmc_record_pause, this, _1));
_mmc->RecordStrobe.connect_same_thread (*this, boost::bind (&Session::mmc_record_strobe, this, _1));
_mmc->RecordExit.connect_same_thread (*this, boost::bind (&Session::mmc_record_exit, this, _1));
_mmc->Locate.connect_same_thread (*this, boost::bind (&Session::mmc_locate, this, _1, _2));
_mmc->Step.connect_same_thread (*this, boost::bind (&Session::mmc_step, this, _1, _2));
_mmc->Shuttle.connect_same_thread (*this, boost::bind (&Session::mmc_shuttle, this, _1, _2, _3));
_mmc->TrackRecordStatusChange.connect_same_thread (*this, boost::bind (&Session::mmc_record_enable, this, _1, _2, _3));
/* also handle MIDI SPP because its so common */
_mmc->port()->input()->start.connect_same_thread (*this, boost::bind (&Session::spp_start, this, _1, _2));
_mmc->port()->input()->contineu.connect_same_thread (*this, boost::bind (&Session::spp_continue, this, _1, _2));
_mmc->port()->input()->stop.connect_same_thread (*this, boost::bind (&Session::spp_stop, this, _1, _2));
}

View File

@ -183,8 +183,10 @@ Session::realtime_stop (bool abort, bool clear_state)
// FIXME: where should this really be? [DR]
//send_full_time_code();
deliver_mmc (MIDI::MachineControl::cmdStop, 0);
deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
_mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdStop));
Timecode::Time time;
timecode_time_subframes (_transport_frame, time);
_mmc->send (MIDI::MachineControlCommand (time));
if (_transport_speed < 0.0f) {
todo = (PostTransportWork (todo | PostTransportStop | PostTransportReverse));
@ -892,7 +894,9 @@ Session::locate (nframes64_t target_frame, bool with_roll, bool with_flush, bool
_send_timecode_update = true;
if (with_mmc) {
deliver_mmc (MIDI::MachineControl::cmdLocate, _transport_frame);
Timecode::Time time;
timecode_time_subframes (_transport_frame, time);
_mmc->send (MIDI::MachineControlCommand (time));
}
Located (); /* EMIT SIGNAL */
@ -1129,7 +1133,9 @@ Session::start_transport ()
}
}
deliver_mmc(MIDI::MachineControl::cmdDeferredPlay, _transport_frame);
Timecode::Time time;
timecode_time_subframes (_transport_frame, time);
_mmc->send (MIDI::MachineControlCommand (MIDI::MachineControl::cmdDeferredPlay));
TransportStateChange (); /* EMIT SIGNAL */
}

View File

@ -20,20 +20,22 @@
#ifndef __midipp_mmc_h_h__
#define __midipp_mmc_h_h__
#include "control_protocol/timecode.h"
#include "pbd/signals.h"
#include "pbd/ringbuffer.h"
#include "midi++/types.h"
namespace MIDI {
class Port;
class Parser;
class MachineControlCommand;
/** Class to handle incoming and outgoing MIDI machine control messages */
class MachineControl
{
public:
typedef PBD::Signal1<void,MachineControl&> MMCSignal;
typedef byte CommandSignature[60];
typedef byte ResponseSignature[60];
enum Command {
cmdStop = 0x1,
@ -84,19 +86,21 @@ class MachineControl
cmdResume = 0x7F
};
MachineControl (Port &port,
float MMCVersion,
CommandSignature &cs,
ResponseSignature &rs);
MachineControl ();
void set_port (Port* p);
Port &port() { return _port; }
Port* port() { return _port; }
void set_receive_device_id (byte id);
void set_send_device_id (byte id);
byte receive_device_id () const { return _receive_device_id; }
byte send_device_id () const { return _send_device_id; }
void enable_send (bool);
void send (MachineControlCommand const &);
void flush_pending ();
static bool is_mmc (byte *sysex_buf, size_t len);
static void set_sending_thread (pthread_t);
/* Signals to connect to if you want to run "callbacks"
when certain MMC commands are received.
@ -170,102 +174,50 @@ class MachineControl
PBD::Signal2<void,MachineControl &, int> Step;
protected:
#define MMC_NTRACKS 48
/* MMC Information fields (think "registers") */
CommandSignature commandSignature;
ResponseSignature responseSignature;
byte updateRate;
byte responseError;
byte commandError;
byte commandErrorLevel;
byte motionControlTally;
byte velocityTally;
byte stopMode;
byte fastMode;
byte recordMode;
byte recordStatus;
bool trackRecordStatus[MMC_NTRACKS];
bool trackRecordReady[MMC_NTRACKS];
byte globalMonitor;
byte recordMonitor;
byte trackSyncMonitor;
byte trackInputMonitor;
byte stepLength;
byte playSpeedReference;
byte fixedSpeed;
byte lifterDefeat;
byte controlDisable;
byte trackMute[MMC_NTRACKS];
byte failure;
byte selectedTimeCode;
byte shortSelectedTimeCode;
byte timeStandard;
byte selectedTimeCodeSource;
byte selectedTimeCodeUserbits;
byte selectedMasterCode;
byte requestedOffset;
byte actualOffset;
byte lockDeviation;
byte shortSelectedMasterCode;
byte shortRequestedOffset;
byte shortActualOffset;
byte shortLockDeviation;
byte resolvedPlayMode;
byte chaseMode;
byte generatorTimeCode;
byte shortGeneratorTimeCode;
byte generatorCommandTally;
byte generatorSetUp;
byte generatorUserbits;
byte vitcInsertEnable;
byte midiTimeCodeInput;
byte shortMidiTimeCodeInput;
byte midiTimeCodeCommandTally;
byte midiTimeCodeSetUp;
byte gp0;
byte gp1;
byte gp2;
byte gp3;
byte gp4;
byte gp5;
byte gp6;
byte gp7;
byte shortGp0;
byte shortGp1;
byte shortGp2;
byte shortGp3;
byte shortGp4;
byte shortGp5;
byte shortGp6;
byte shortGp7;
byte procedureResponse;
byte eventResponse;
byte responseSegment;
byte wait;
byte resume;
private:
byte _receive_device_id;
byte _send_device_id;
MIDI::Port &_port;
Port* _port;
bool _enable_send; ///< true if MMC sending is enabled
/** A ringbuffer of MMC commands that were `sent' from the wrong thread, which
are queued up and sent when flush_pending() is called.
*/
RingBuffer<MachineControlCommand> _pending;
/** The thread to use for sending MMC commands */
static pthread_t _sending_thread;
void process_mmc_message (Parser &p, byte *, size_t len);
PBD::ScopedConnection mmc_connection;
PBD::ScopedConnection mmc_connection; ///< connection to our parser for incoming data
int do_masked_write (byte *, size_t len);
int do_locate (byte *, size_t len);
int do_step (byte *, size_t len);
int do_shuttle (byte *, size_t len);
void send_immediately (MachineControlCommand const &);
void write_track_status (byte *, size_t len, byte reg);
};
/** Class to describe a MIDI machine control command to be sent.
* In an ideal world we might use a class hierarchy for this, but objects of this type
* have to be allocated off the stack for thread safety.
*/
class MachineControlCommand
{
public:
MachineControlCommand () : _command (MachineControl::Command (0)) {}
MachineControlCommand (MachineControl::Command);
MachineControlCommand (Timecode::Time);
MIDI::byte* fill_buffer (MachineControl *mmc, MIDI::byte *) const;
private:
MachineControl::Command _command;
Timecode::Time _time;
};
} // namespace MIDI
#endif /* __midipp_mmc_h_h__ */

View File

@ -20,6 +20,7 @@
#include <map>
#include "control_protocol/timecode.h"
#include "pbd/error.h"
#include "midi++/mmc.h"
#include "midi++/port.h"
@ -29,6 +30,8 @@ using namespace std;
using namespace MIDI;
using namespace PBD;
pthread_t MachineControl::_sending_thread;
static std::map<int,string> mmc_cmd_map;
static void build_mmc_cmd_map ()
{
@ -192,24 +195,27 @@ static void build_mmc_cmd_map ()
}
MachineControl::MachineControl (Port &p, float /*version*/,
CommandSignature & /*csig*/,
ResponseSignature & /*rsig*/)
: _port (p)
MachineControl::MachineControl ()
: _port (0)
, _pending (16)
{
Parser *parser;
build_mmc_cmd_map ();
_receive_device_id = 0;
_send_device_id = 0x7f;
if ((parser = _port.input()) != 0) {
parser->mmc.connect_same_thread (mmc_connection, boost::bind (&MachineControl::process_mmc_message, this, _1, _2, _3));
}
void
MachineControl::set_port (Port* p)
{
_port = p;
mmc_connection.disconnect ();
if (_port->input()) {
_port->input()->mmc.connect_same_thread (mmc_connection, boost::bind (&MachineControl::process_mmc_message, this, _1, _2, _3));
} else {
warning << "MMC connected to a non-input port: useless!"
<< endmsg;
warning << "MMC connected to a non-input port: useless!" << endmsg;
}
}
@ -227,7 +233,6 @@ MachineControl::set_send_device_id (byte id)
bool
MachineControl::is_mmc (byte *sysex_buf, size_t len)
{
if (len < 4 || len > 48) {
return false;
@ -247,7 +252,6 @@ MachineControl::is_mmc (byte *sysex_buf, size_t len)
void
MachineControl::process_mmc_message (Parser &, byte *msg, size_t len)
{
size_t skiplen;
byte *mmc_msg;
@ -455,7 +459,6 @@ MachineControl::process_mmc_message (Parser &, byte *msg, size_t len)
int
MachineControl::do_masked_write (byte *msg, size_t len)
{
/* return the number of bytes "consumed" */
@ -555,12 +558,10 @@ MachineControl::write_track_status (byte *msg, size_t /*len*/, byte reg)
switch (reg) {
case 0x4f:
trackRecordStatus[base_track+n] = val;
TrackRecordStatusChange (*this, base_track+n, val);
break;
case 0x62:
trackMute[base_track+n] = val;
TrackMuteChange (*this, base_track+n, val);
break;
}
@ -571,7 +572,6 @@ MachineControl::write_track_status (byte *msg, size_t /*len*/, byte reg)
int
MachineControl::do_locate (byte *msg, size_t /*msglen*/)
{
if (msg[2] == 0) {
warning << "MIDI::MMC: locate [I/F] command not supported"
@ -600,7 +600,6 @@ MachineControl::do_step (byte *msg, size_t /*msglen*/)
int
MachineControl::do_shuttle (byte *msg, size_t /*msglen*/)
{
size_t forward;
byte sh = msg[2];
@ -630,3 +629,97 @@ MachineControl::do_shuttle (byte *msg, size_t /*msglen*/)
return 0;
}
void
MachineControl::enable_send (bool yn)
{
_enable_send = yn;
}
/** Send a MMC command. It will be sent immediately if the call is made in _sending_thread,
* otherwise it will be queued and sent next time flush_pending()
* is called.
* @param c command, which this method takes ownership of.
*/
void
MachineControl::send (MachineControlCommand const & c)
{
if (pthread_self() == _sending_thread) {
send_immediately (c);
} else {
_pending.write (&c, 1);
}
}
/** Send any pending MMC commands immediately. Must be called from _sending_thread */
void
MachineControl::flush_pending ()
{
MachineControlCommand c;
while (_pending.read (&c, 1) == 1) {
send_immediately (c);
}
}
/** Send a MMC immediately. Must be called from _sending_thread.
* @param c command, which this method takes ownership of.
*/
void
MachineControl::send_immediately (MachineControlCommand const & c)
{
if (_port == 0 || !_enable_send) {
// cerr << "Not delivering MMC " << _mmc->port() << " - " << session_send_mmc << endl;
return;
}
MIDI::byte buffer[32];
MIDI::byte* b = c.fill_buffer (this, buffer);
if (_port->midimsg (buffer, b - buffer, 0)) {
error << "MMC: cannot send command" << endmsg;
}
}
/** Set the thread that we should send MMC in */
void
MachineControl::set_sending_thread (pthread_t t)
{
_sending_thread = t;
}
MachineControlCommand::MachineControlCommand (MachineControl::Command c)
: _command (c)
{
}
MachineControlCommand::MachineControlCommand (Timecode::Time t)
: _command (MachineControl::cmdLocate)
, _time (t)
{
}
MIDI::byte *
MachineControlCommand::fill_buffer (MachineControl* mmc, MIDI::byte* b) const
{
*b++ = 0xf0; // SysEx
*b++ = 0x7f; // Real-time SysEx ID for MMC
*b++ = mmc->send_device_id();
*b++ = 0x6; // MMC command
*b++ = _command;
if (_command == MachineControl::cmdLocate) {
*b++ = 0x6; // byte count
*b++ = 0x1; // "TARGET" subcommand
*b++ = _time.hours;
*b++ = _time.minutes;
*b++ = _time.seconds;
*b++ = _time.frames;
*b++ = _time.subframes;
}
*b++ = 0xf7;
return b;
}

View File

@ -70,7 +70,7 @@ def build(bld):
obj.source += ' alsa_sequencer_midiport.cc '
obj.cxxflags += [ '-DWITH_ALSA' ]
obj.export_incdirs = ['.']
obj.includes = ['.']
obj.includes = ['.', '../surfaces/control_protocol']
obj.name = 'libmidipp'
obj.target = 'midipp'
obj.uselib = 'GLIBMM SIGCPP XML JACK OSX COREAUDIO'