diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index c9ef5fc774..79bdd4d966 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -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 MTC_PortChanged; - PBD::Signal0 MMC_PortChanged; PBD::Signal0 MIDI_PortChanged; PBD::Signal0 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 diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index 79aa5d9ff8..0058acc7e2 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -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); diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 7a4a4c4e9f..423cc8cb9a 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -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 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 */ } diff --git a/libs/ardour/session_midi.cc b/libs/ardour/session_midi.cc index 0a6b490b5a..201c913588 100644 --- a/libs/ardour/session_midi.cc +++ b/libs/ardour/session_midi.cc @@ -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 () diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index ba5db152cb..e555350088 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -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 (); diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 2842357db8..85d6a1689b 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -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)); +} diff --git a/libs/ardour/session_transport.cc b/libs/ardour/session_transport.cc index 2ea53c6a5d..74eafa0f15 100644 --- a/libs/ardour/session_transport.cc +++ b/libs/ardour/session_transport.cc @@ -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 */ } diff --git a/libs/midi++2/midi++/mmc.h b/libs/midi++2/midi++/mmc.h index 687d0e14d8..5b3bc663e6 100644 --- a/libs/midi++2/midi++/mmc.h +++ b/libs/midi++2/midi++/mmc.h @@ -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 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 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 _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__ */ diff --git a/libs/midi++2/mmc.cc b/libs/midi++2/mmc.cc index 6030230108..b8aa253f80 100644 --- a/libs/midi++2/mmc.cc +++ b/libs/midi++2/mmc.cc @@ -20,6 +20,7 @@ #include +#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 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; +} diff --git a/libs/midi++2/wscript b/libs/midi++2/wscript index 6eaa606776..3be03ca2d4 100644 --- a/libs/midi++2/wscript +++ b/libs/midi++2/wscript @@ -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'