Files
ardour/libs/ardour/session_midi.cc
David Robillard e0aaed6d65 *** NEW CODING POLICY ***
All #include statements that include a header that is a part of a library
bundled with ardour MUST use quotes, not angle brackets.

Do this:

#include "ardour/types.h"

NOT this:

#include <ardour/types.h>

Rationale:

This is best practice in general, to ensure we include the local version
and not the system version.  That quotes mean "local" (in some sense)
and angle brackets mean "system" (in some sense) is a ubiquitous
convention and IIRC right in the C spec somewhere.

More pragmatically, this is required by (my) waf (stuff) for dependencies
to work correctly.  That is:

!!! FAILURE TO DO THIS CAN RESULT IN BROKEN BUILDS !!!

Failure to comply is punishable by death by torture. :)

P.S. It's not that dramatic in all cases, but this (in combination with some
GCC flags specific to the include type) is the best way I have found to be
absolutely 100% positive the local ones are being used (and we definitely
want to be absolutely 100% positive on that one).


git-svn-id: svn://localhost/ardour2/branches/3.0@4655 d708f5d6-7413-0410-9779-e7cbd77b26cf
2009-02-25 18:26:51 +00:00

1302 lines
30 KiB
C++

/*
Copyright (C) 1999-2002 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <string>
#include <cmath>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <boost/shared_ptr.hpp>
#include "midi++/mmc.h"
#include "midi++/port.h"
#include "midi++/manager.h"
#include "pbd/error.h"
#include <glibmm/thread.h>
#include "pbd/pthread_utils.h"
#include "ardour/configuration.h"
#include "ardour/audioengine.h"
#include "ardour/session.h"
#include "ardour/audio_track.h"
#include "ardour/midi_track.h"
#include "ardour/audio_diskstream.h"
#include "ardour/slave.h"
#include "ardour/cycles.h"
#include "ardour/smpte.h"
#include "i18n.h"
using namespace std;
using namespace ARDOUR;
using namespace PBD;
using namespace MIDI;
MachineControl::CommandSignature MMC_CommandSignature;
MachineControl::ResponseSignature MMC_ResponseSignature;
void
Session::midi_panic()
{
{
boost::shared_ptr<RouteList> r = routes.reader ();
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
MidiTrack *track = dynamic_cast<MidiTrack*>((*i).get());
if (track != 0) {
track->midi_panic();
}
}
}
}
int
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 {
set_mtc_port ("");
}
if (default_midi_port) {
set_midi_port (default_midi_port->name());
} else {
set_midi_port ("");
}
if (default_midi_clock_port) {
set_midi_clock_port (default_midi_clock_port->name());
} else {
set_midi_clock_port ("");
}
return 0;
}
/***********************************************************************
MTC, MMC, etc.
**********************************************************************/
int
Session::set_mtc_port (string port_tag)
{
MTC_Slave *ms;
if (port_tag.length() == 0) {
if (_slave && ((ms = dynamic_cast<MTC_Slave*> (_slave)) != 0)) {
error << _("Ardour is slaved to MTC - port cannot be reset") << endmsg;
return -1;
}
if (_mtc_port == 0) {
return 0;
}
_mtc_port = 0;
goto out;
}
MIDI::Port* port;
if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
error << string_compose (_("unknown port %1 requested for MTC"), port_tag) << endl;
return -1;
}
_mtc_port = port;
if (_slave && ((ms = dynamic_cast<MTC_Slave*> (_slave)) != 0)) {
ms->rebind (*port);
}
Config->set_mtc_port_name (port_tag);
out:
MTC_PortChanged(); /* EMIT SIGNAL */
change_midi_ports ();
set_dirty();
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
(mem_fun (*this, &Session::mmc_deferred_play));
mmc->DeferredPlay.connect
(mem_fun (*this, &Session::mmc_deferred_play));
mmc->Stop.connect
(mem_fun (*this, &Session::mmc_stop));
mmc->FastForward.connect
(mem_fun (*this, &Session::mmc_fast_forward));
mmc->Rewind.connect
(mem_fun (*this, &Session::mmc_rewind));
mmc->Pause.connect
(mem_fun (*this, &Session::mmc_pause));
mmc->RecordPause.connect
(mem_fun (*this, &Session::mmc_record_pause));
mmc->RecordStrobe.connect
(mem_fun (*this, &Session::mmc_record_strobe));
mmc->RecordExit.connect
(mem_fun (*this, &Session::mmc_record_exit));
mmc->Locate.connect
(mem_fun (*this, &Session::mmc_locate));
mmc->Step.connect
(mem_fun (*this, &Session::mmc_step));
mmc->Shuttle.connect
(mem_fun (*this, &Session::mmc_shuttle));
mmc->TrackRecordStatusChange.connect
(mem_fun (*this, &Session::mmc_record_enable));
/* also handle MIDI SPP because its so common */
_mmc_port->input()->start.connect (mem_fun (*this, &Session::spp_start));
_mmc_port->input()->contineu.connect (mem_fun (*this, &Session::spp_continue));
_mmc_port->input()->stop.connect (mem_fun (*this, &Session::spp_stop));
Config->set_mmc_port_name (port_tag);
out:
MMC_PortChanged(); /* EMIT SIGNAL */
change_midi_ports ();
set_dirty();
return 0;
}
int
Session::set_midi_port (string port_tag)
{
#if 0
if (port_tag.length() == 0) {
if (_midi_port == 0) {
return 0;
}
_midi_port = 0;
goto out;
}
MIDI::Port* port;
if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
return -1;
}
_midi_port = port;
/* XXX need something to forward this to control protocols ? or just
use the signal below
*/
Config->set_midi_port_name (port_tag);
out:
#endif
MIDI_PortChanged(); /* EMIT SIGNAL */
change_midi_ports ();
set_dirty();
return 0;
}
int
Session::set_midi_clock_port (string port_tag)
{
MIDIClock_Slave *ms;
if (port_tag.length() == 0) {
if (_slave && ((ms = dynamic_cast<MIDIClock_Slave*> (_slave)) != 0)) {
error << _("Ardour is slaved to MIDI Clock - port cannot be reset") << endmsg;
return -1;
}
if (_midi_clock_port == 0) {
return 0;
}
_midi_clock_port = 0;
goto out;
}
MIDI::Port* port;
if ((port = MIDI::Manager::instance()->port (port_tag)) == 0) {
error << string_compose (_("unknown port %1 requested for MIDI Clock"), port_tag) << endl;
return -1;
}
_midi_clock_port = port;
if (_slave && ((ms = dynamic_cast<MIDIClock_Slave*> (_slave)) != 0)) {
ms->rebind (*port);
}
Config->set_midi_clock_port_name (port_tag);
out:
MIDIClock_PortChanged(); /* EMIT SIGNAL */
change_midi_ports ();
set_dirty();
return 0;
}
void
Session::set_trace_midi_input (bool yn, MIDI::Port* port)
{
MIDI::Parser* input_parser;
cerr << "enabling tracing: " << yn << " for input port " << port->name() << endl;
if (port) {
if ((input_parser = port->input()) != 0) {
input_parser->trace (yn, &cout, "input: ");
}
} else {
if (_mmc_port) {
if ((input_parser = _mmc_port->input()) != 0) {
input_parser->trace (yn, &cout, "input: ");
}
}
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 ((input_parser = _midi_port->input()) != 0) {
input_parser->trace (yn, &cout, "input: ");
}
}
if (_midi_clock_port
&& _midi_clock_port != _mmc_port
&& _midi_clock_port != _mtc_port
&& _midi_clock_port != _midi_port) {
if ((input_parser = _midi_clock_port->input()) != 0) {
input_parser->trace (yn, &cout, "input: ");
}
}
}
Config->set_trace_midi_input (yn);
}
void
Session::set_trace_midi_output (bool yn, MIDI::Port* port)
{
MIDI::Parser* output_parser;
if (port) {
if ((output_parser = port->output()) != 0) {
output_parser->trace (yn, &cout, "output: ");
}
} else {
if (_mmc_port) {
if ((output_parser = _mmc_port->output()) != 0) {
output_parser->trace (yn, &cout, "output: ");
}
}
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 ((output_parser = _midi_port->output()) != 0) {
output_parser->trace (yn, &cout, "output: ");
}
}
}
Config->set_trace_midi_output (yn);
}
bool
Session::get_trace_midi_input(MIDI::Port *port)
{
MIDI::Parser* input_parser;
if (port) {
if ((input_parser = port->input()) != 0) {
return input_parser->tracing();
}
}
else {
if (_mmc_port) {
if ((input_parser = _mmc_port->input()) != 0) {
return input_parser->tracing();
}
}
if (_mtc_port) {
if ((input_parser = _mtc_port->input()) != 0) {
return input_parser->tracing();
}
}
if (_midi_port) {
if ((input_parser = _midi_port->input()) != 0) {
return input_parser->tracing();
}
}
}
return false;
}
bool
Session::get_trace_midi_output(MIDI::Port *port)
{
MIDI::Parser* output_parser;
if (port) {
if ((output_parser = port->output()) != 0) {
return output_parser->tracing();
}
}
else {
if (_mmc_port) {
if ((output_parser = _mmc_port->output()) != 0) {
return output_parser->tracing();
}
}
if (_mtc_port) {
if ((output_parser = _mtc_port->output()) != 0) {
return output_parser->tracing();
}
}
if (_midi_port) {
if ((output_parser = _midi_port->output()) != 0) {
return output_parser->tracing();
}
}
}
return false;
}
void
Session::setup_midi_control ()
{
outbound_mtc_smpte_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;
mtc_msg[2] = 0xf1;
mtc_msg[4] = 0xf1;
mtc_msg[6] = 0xf1;
mtc_msg[8] = 0xf1;
mtc_msg[10] = 0xf1;
mtc_msg[12] = 0xf1;
mtc_msg[14] = 0xf1;
}
void
Session::spp_start (Parser& ignored, nframes_t timestamp)
{
if (Config->get_mmc_control() && (Config->get_slave_source() != MTC)) {
request_transport_speed (1.0);
}
}
void
Session::spp_continue (Parser& ignored, nframes_t timestamp)
{
spp_start (ignored, timestamp);
}
void
Session::spp_stop (Parser& ignored, nframes_t timestamp)
{
if (Config->get_mmc_control()) {
request_stop ();
}
}
/*
void
Session::midi_clock_start (Parser& ignored, nframes_t timestamp)
{
if (Config->get_slave_source() == MIDIClock) {
request_transport_speed (1.0);
}
}
void
Session::midi_clock_continue (Parser& parser, nframes_t timestamp)
{
midi_clock_start (parser, 0);
}
void
Session::midi_clock_stop (Parser& ignored, nframes_t timestamp)
{
if (Config->get_slave_source() == MIDIClock) {
request_stop ();
}
}
*/
void
Session::mmc_deferred_play (MIDI::MachineControl &mmc)
{
if (Config->get_mmc_control() && (Config->get_slave_source() != MTC)) {
request_transport_speed (1.0);
}
}
void
Session::mmc_record_pause (MIDI::MachineControl &mmc)
{
if (Config->get_mmc_control()) {
maybe_enable_record();
}
}
void
Session::mmc_record_strobe (MIDI::MachineControl &mmc)
{
if (!Config->get_mmc_control())
return;
/* record strobe does an implicit "Play" command */
if (_transport_speed != 1.0) {
/* start_transport() will move from Enabled->Recording, so we
don't need to do anything here except enable recording.
its not the same as maybe_enable_record() though, because
that *can* switch to Recording, which we do not want.
*/
save_state ("", true);
g_atomic_int_set (&_record_status, Enabled);
RecordStateChanged (); /* EMIT SIGNAL */
request_transport_speed (1.0);
} else {
enable_record ();
}
}
void
Session::mmc_record_exit (MIDI::MachineControl &mmc)
{
if (Config->get_mmc_control()) {
disable_record (false);
}
}
void
Session::mmc_stop (MIDI::MachineControl &mmc)
{
if (Config->get_mmc_control()) {
request_stop ();
}
}
void
Session::mmc_pause (MIDI::MachineControl &mmc)
{
if (Config->get_mmc_control()) {
/* We support RECORD_PAUSE, so the spec says that
we must interpret PAUSE like RECORD_PAUSE if
recording.
*/
if (actively_recording()) {
maybe_enable_record ();
} else {
request_stop ();
}
}
}
static bool step_queued = false;
void
Session::mmc_step (MIDI::MachineControl &mmc, int steps)
{
if (!Config->get_mmc_control()) {
return;
}
struct timeval now;
struct timeval diff = { 0, 0 };
gettimeofday (&now, 0);
timersub (&now, &last_mmc_step, &diff);
gettimeofday (&now, 0);
timersub (&now, &last_mmc_step, &diff);
if (last_mmc_step.tv_sec != 0 && (diff.tv_usec + (diff.tv_sec * 1000000)) < _engine.usecs_per_cycle()) {
return;
}
double diff_secs = diff.tv_sec + (diff.tv_usec / 1000000.0);
double cur_speed = (((steps * 0.5) * smpte_frames_per_second()) / diff_secs) / smpte_frames_per_second();
if (_transport_speed == 0 || cur_speed * _transport_speed < 0) {
/* change direction */
step_speed = cur_speed;
} else {
step_speed = (0.6 * step_speed) + (0.4 * cur_speed);
}
step_speed *= 0.25;
#if 0
cerr << "delta = " << diff_secs
<< " ct = " << _transport_speed
<< " steps = " << steps
<< " new speed = " << cur_speed
<< " speed = " << step_speed
<< endl;
#endif
request_transport_speed (step_speed);
last_mmc_step = now;
if (!step_queued) {
midi_timeouts.push_back (mem_fun (*this, &Session::mmc_step_timeout));
step_queued = true;
}
}
void
Session::mmc_rewind (MIDI::MachineControl &mmc)
{
if (Config->get_mmc_control()) {
request_transport_speed(-8.0f);
}
}
void
Session::mmc_fast_forward (MIDI::MachineControl &mmc)
{
if (Config->get_mmc_control()) {
request_transport_speed(8.0f);
}
}
void
Session::mmc_locate (MIDI::MachineControl &mmc, const MIDI::byte* mmc_tc)
{
if (!Config->get_mmc_control()) {
return;
}
nframes_t target_frame;
SMPTE::Time smpte;
smpte.hours = mmc_tc[0] & 0xf;
smpte.minutes = mmc_tc[1];
smpte.seconds = mmc_tc[2];
smpte.frames = mmc_tc[3];
smpte.rate = smpte_frames_per_second();
smpte.drop = smpte_drop_frames();
// Also takes smpte offset into account:
smpte_to_sample( smpte, target_frame, true /* use_offset */, false /* use_subframes */ );
if (target_frame > max_frames) {
target_frame = max_frames;
}
/* Some (all?) MTC/MMC devices do not send a full MTC frame
at the end of a locate, instead sending only an MMC
locate command. This causes the current position
of an MTC slave to become out of date. Catch this.
*/
MTC_Slave* mtcs = dynamic_cast<MTC_Slave*> (_slave);
if (mtcs != 0) {
// cerr << "Locate *with* MTC slave\n";
mtcs->handle_locate (mmc_tc);
} else {
// cerr << "Locate without MTC slave\n";
request_locate (target_frame, false);
}
}
void
Session::mmc_shuttle (MIDI::MachineControl &mmc, float speed, bool forw)
{
if (!Config->get_mmc_control()) {
return;
}
if (Config->get_shuttle_speed_threshold() >= 0 && speed > Config->get_shuttle_speed_threshold()) {
speed *= Config->get_shuttle_speed_factor();
}
if (forw) {
request_transport_speed (speed);
} else {
request_transport_speed (-speed);
}
}
void
Session::mmc_record_enable (MIDI::MachineControl &mmc, size_t trk, bool enabled)
{
if (Config->get_mmc_control()) {
RouteList::iterator i;
boost::shared_ptr<RouteList> r = routes.reader();
for (i = r->begin(); i != r->end(); ++i) {
AudioTrack *at;
if ((at = dynamic_cast<AudioTrack*>((*i).get())) != 0) {
if (trk == at->remote_control_id()) {
at->set_record_enable (enabled, &mmc);
break;
}
}
}
}
}
void
Session::change_midi_ports ()
{
MIDIRequest* request = new MIDIRequest;
request->type = MIDIRequest::PortChange;
midi_requests.write (&request, 1);
poke_midi_thread ();
}
/** Send MTC Full Frame message (complete SMPTE time) for the start of this cycle.
* This resets the MTC code, the next quarter frame message that is sent will be
* the first one with the beginning of this cycle as the new start point.
*/
int
Session::send_full_time_code(nframes_t nframes)
{
/* This function could easily send at a given frame offset, but would
* that be useful? Does ardour do sub-block accurate locating? [DR] */
MIDI::byte msg[10];
SMPTE::Time smpte;
_send_smpte_update = false;
if (_mtc_port == 0 || !session_send_mtc) {
return 0;
}
// Get smpte time for this transport frame
sample_to_smpte(_transport_frame, smpte, true /* use_offset */, false /* no subframes */);
transmitting_smpte_time = smpte;
outbound_mtc_smpte_frame = _transport_frame;
// I don't understand this bit yet.. [DR]
if (((mtc_smpte_bits >> 5) != MIDI::MTC_25_FPS) && (transmitting_smpte_time.frames % 2)) {
// start MTC quarter frame transmission on an even frame
SMPTE::increment( transmitting_smpte_time );
outbound_mtc_smpte_frame += (nframes_t) _frames_per_smpte_frame;
}
// Compensate for audio latency
outbound_mtc_smpte_frame += _worst_output_latency;
next_quarter_frame_to_send = 0;
// Sync slave to the same SMPTE time as we are on
msg[0] = 0xf0;
msg[1] = 0x7f;
msg[2] = 0x7f;
msg[3] = 0x1;
msg[4] = 0x1;
msg[9] = 0xf7;
msg[5] = mtc_smpte_bits | smpte.hours;
msg[6] = smpte.minutes;
msg[7] = smpte.seconds;
msg[8] = smpte.frames;
cerr << "MTC: Sending full time code at " << outbound_mtc_smpte_frame << endl;
// Send message at offset 0, sent time is for the start of this cycle
if (_mtc_port->midimsg (msg, sizeof (msg), 0)) {
error << _("Session: could not send full MIDI time code") << endmsg;
return -1;
}
return 0;
}
/** Send MTC (quarter-frame) messages for this cycle.
* Must be called exactly once per cycle from the audio thread. Realtime safe.
* This function assumes the state of full SMPTE is sane, eg. the slave is
* expecting quarter frame messages and has the right frame of reference (any
* full MTC SMPTE time messages that needed to be sent should have been sent
* earlier already this cycle by send_full_time_code)
*/
int
Session::send_midi_time_code_for_cycle(nframes_t nframes)
{
assert (next_quarter_frame_to_send >= 0);
assert (next_quarter_frame_to_send <= 7);
if (_mtc_port == 0 || !session_send_mtc || transmitting_smpte_time.negative
/*|| (next_quarter_frame_to_send < 0)*/ ) {
// cerr << "(MTC) Not sending MTC\n";
return 0;
}
/* Duration of one quarter frame */
nframes_t quarter_frame_duration = ((long) _frames_per_smpte_frame) >> 2;
// cerr << "(MTC) TR: " << _transport_frame << " - SF: " << outbound_mtc_smpte_frame
// << " - NQ: " << next_quarter_frame_to_send << " - FD" << quarter_frame_duration << endl;
// FIXME: this should always be true
//assert((outbound_mtc_smpte_frame + (next_quarter_frame_to_send * quarter_frame_duration))
// > _transport_frame);
// Send quarter frames for this cycle
while (_transport_frame + nframes > (outbound_mtc_smpte_frame +
(next_quarter_frame_to_send * quarter_frame_duration))) {
// cerr << "(MTC) Next frame to send: " << next_quarter_frame_to_send << endl;
switch (next_quarter_frame_to_send) {
case 0:
mtc_msg[1] = 0x00 | (transmitting_smpte_time.frames & 0xf);
break;
case 1:
mtc_msg[1] = 0x10 | ((transmitting_smpte_time.frames & 0xf0) >> 4);
break;
case 2:
mtc_msg[1] = 0x20 | (transmitting_smpte_time.seconds & 0xf);
break;
case 3:
mtc_msg[1] = 0x30 | ((transmitting_smpte_time.seconds & 0xf0) >> 4);
break;
case 4:
mtc_msg[1] = 0x40 | (transmitting_smpte_time.minutes & 0xf);
break;
case 5:
mtc_msg[1] = 0x50 | ((transmitting_smpte_time.minutes & 0xf0) >> 4);
break;
case 6:
mtc_msg[1] = 0x60 | ((mtc_smpte_bits|transmitting_smpte_time.hours) & 0xf);
break;
case 7:
mtc_msg[1] = 0x70 | (((mtc_smpte_bits|transmitting_smpte_time.hours) & 0xf0) >> 4);
break;
}
const nframes_t msg_time = (outbound_mtc_smpte_frame
+ (quarter_frame_duration * next_quarter_frame_to_send));
// This message must fall within this block or something is broken
assert(msg_time >= _transport_frame);
assert(msg_time < _transport_frame + nframes);
nframes_t out_stamp = msg_time - _transport_frame;
assert(out_stamp < nframes);
if (_mtc_port->midimsg (mtc_msg, 2, out_stamp)) {
error << string_compose(_("Session: cannot send quarter-frame MTC message (%1)"), strerror (errno))
<< endmsg;
return -1;
}
/*cerr << "(MTC) SMPTE: " << transmitting_smpte_time.hours
<< ":" << transmitting_smpte_time.minutes
<< ":" << transmitting_smpte_time.seconds
<< ":" << transmitting_smpte_time.frames
<< ", qfm = " << next_quarter_frame_to_send
<< ", stamp = " << out_stamp
<< ", delta = " << _transport_frame + out_stamp - last_time << endl;*/
// Increment quarter frame counter
next_quarter_frame_to_send++;
if (next_quarter_frame_to_send >= 8) {
// Wrap quarter frame counter
next_quarter_frame_to_send = 0;
// Increment smpte time twice
SMPTE::increment( transmitting_smpte_time );
SMPTE::increment( transmitting_smpte_time );
// Re-calculate timing of first quarter frame
//smpte_to_sample( transmitting_smpte_time, outbound_mtc_smpte_frame, true /* use_offset */, false );
outbound_mtc_smpte_frame += 8 * quarter_frame_duration;
// Compensate for audio latency
outbound_mtc_smpte_frame += _worst_output_latency;
}
}
return 0;
}
/***********************************************************************
OUTBOUND MMC STUFF
**********************************************************************/
void
Session::deliver_mmc (MIDI::MachineControl::Command cmd, nframes_t where)
{
using namespace MIDI;
int nbytes = 4;
SMPTE::Time smpte;
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:
smpte_time_subframes (where, smpte);
mmc_buffer[nbytes++] = 0x6; // byte count
mmc_buffer[nbytes++] = 0x1; // "TARGET" subcommand
mmc_buffer[nbytes++] = smpte.hours;
mmc_buffer[nbytes++] = smpte.minutes;
mmc_buffer[nbytes++] = smpte.seconds;
mmc_buffer[nbytes++] = smpte.frames;
mmc_buffer[nbytes++] = smpte.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 ()
{
struct timeval now;
struct timeval diff;
double diff_usecs;
gettimeofday (&now, 0);
timersub (&now, &last_mmc_step, &diff);
diff_usecs = diff.tv_sec * 1000000 + diff.tv_usec;
if (diff_usecs > 1000000.0 || fabs (_transport_speed) < 0.0000001) {
/* too long or too slow, stop transport */
request_transport_speed (0.0);
step_queued = false;
return false;
}
if (diff_usecs < 250000.0) {
/* too short, just keep going */
return true;
}
/* slow it down */
request_transport_speed (_transport_speed * 0.75);
return true;
}
/*---------------------------------------------------------------------------
MIDI THREAD
---------------------------------------------------------------------------*/
int
Session::start_midi_thread ()
{
if (pipe (midi_request_pipe)) {
error << string_compose(_("Cannot create transport request signal pipe (%1)"), strerror (errno)) << endmsg;
return -1;
}
if (fcntl (midi_request_pipe[0], F_SETFL, O_NONBLOCK)) {
error << string_compose(_("UI: cannot set O_NONBLOCK on " "signal read pipe (%1)"), strerror (errno)) << endmsg;
return -1;
}
if (fcntl (midi_request_pipe[1], F_SETFL, O_NONBLOCK)) {
error << string_compose(_("UI: cannot set O_NONBLOCK on " "signal write pipe (%1)"), strerror (errno)) << endmsg;
return -1;
}
if (pthread_create_and_store ("transport", &midi_thread, 0, _midi_thread_work, this)) {
error << _("Session: could not create transport thread") << endmsg;
return -1;
}
return 0;
}
void
Session::terminate_midi_thread ()
{
if (midi_thread) {
MIDIRequest* request = new MIDIRequest;
void* status;
request->type = MIDIRequest::Quit;
midi_requests.write (&request, 1);
poke_midi_thread ();
pthread_join (midi_thread, &status);
}
}
void
Session::poke_midi_thread ()
{
static char c = 0;
if (write (midi_request_pipe[1], &c, 1) != 1) {
error << string_compose(_("cannot send signal to midi thread! (%1)"), strerror (errno)) << endmsg;
}
}
void *
Session::_midi_thread_work (void* arg)
{
pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, 0);
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, 0);
((Session *) arg)->midi_thread_work ();
return 0;
}
void
Session::midi_thread_work ()
{
MIDIRequest* request;
struct pollfd pfd[4];
int nfds = 0;
int timeout;
int fds_ready;
struct sched_param rtparam;
int x;
bool restart;
vector<MIDI::Port*> ports;
PBD::notify_gui_about_thread_creation (pthread_self(), X_("MIDI"), 2048);
memset (&rtparam, 0, sizeof (rtparam));
rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
if ((x = pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam)) != 0) {
// do we care? not particularly.
}
/* set up the port vector; 5 is the largest possible size for now */
ports.assign (5, (MIDI::Port*) 0);
while (1) {
nfds = 0;
pfd[nfds].fd = midi_request_pipe[0];
pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
nfds++;
if (Config->get_mmc_control() && _mmc_port && _mmc_port->selectable() >= 0) {
pfd[nfds].fd = _mmc_port->selectable();
pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
ports[nfds] = _mmc_port;
//cerr << "MIDI port " << nfds << " = MMC @ " << _mmc_port << endl;
nfds++;
}
/* if MTC is being handled on a different port from MMC
or we are not handling MMC at all, poll
the relevant port.
*/
if (_mtc_port && (_mtc_port != _mmc_port || !Config->get_mmc_control()) && _mtc_port->selectable() >= 0) {
pfd[nfds].fd = _mtc_port->selectable();
pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
ports[nfds] = _mtc_port;
//cerr << "MIDI port " << nfds << " = MTC @ " << _mtc_port << endl;
nfds++;
}
if (_midi_clock_port && (_midi_clock_port != _mmc_port || !Config->get_mmc_control()) && _midi_clock_port->selectable() >= 0) {
pfd[nfds].fd = _midi_clock_port->selectable();
pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
ports[nfds] = _midi_clock_port;
nfds++;
}
/* if we are using MMC control, we obviously have to listen
the relevant port.
*/
if (_midi_port && (_midi_port != _mmc_port || !Config->get_mmc_control()) && (_midi_port != _mtc_port) && _midi_port->selectable() >= 0) {
pfd[nfds].fd = _midi_port->selectable();
pfd[nfds].events = POLLIN|POLLHUP|POLLERR;
ports[nfds] = _midi_port;
// cerr << "MIDI port " << nfds << " = MIDI @ " << _midi_port << endl;
nfds++;
}
if (!midi_timeouts.empty()) {
timeout = 100; /* 10msecs */
} else {
timeout = -1; /* if there is no data, we don't care */
}
again:
// cerr << "MIDI poll on " << nfds << " for " << timeout << endl;
if (poll (pfd, nfds, timeout) < 0) {
if (errno == EINTR) {
/* gdb at work, perhaps */
goto again;
}
error << string_compose(_("MIDI thread poll failed (%1)"), strerror (errno)) << endmsg;
break;
}
// cerr << "MIDI thread wakes at " << get_cycles () << endl;
fds_ready = 0;
/* check the transport request pipe */
if (pfd[0].revents & ~POLLIN) {
error << _("Error on transport thread request pipe") << endmsg;
break;
}
if (pfd[0].revents & POLLIN) {
char foo[16];
// cerr << "MIDI request FIFO ready\n";
fds_ready++;
/* empty the pipe of all current requests */
while (1) {
size_t nread = read (midi_request_pipe[0], &foo, sizeof (foo));
if (nread > 0) {
if ((size_t) nread < sizeof (foo)) {
break;
} else {
continue;
}
} else if (nread == 0) {
break;
} else if (errno == EAGAIN) {
break;
} else {
fatal << _("Error reading from transport request pipe") << endmsg;
/*NOTREACHED*/
}
}
while (midi_requests.read (&request, 1) == 1) {
switch (request->type) {
case MIDIRequest::PortChange:
/* restart poll with new ports */
// cerr << "rebind\n";
restart = true;
break;
case MIDIRequest::Quit:
delete request;
pthread_exit_pbd (0);
/*NOTREACHED*/
break;
default:
break;
}
delete request;
}
}
if (restart) {
continue;
}
/* now read the rest of the ports */
for (int p = 1; p < nfds; ++p) {
if ((pfd[p].revents & ~POLLIN)) {
// error << string_compose(_("Transport: error polling MIDI port %1 (revents =%2%3%4"), p, &hex, pfd[p].revents, &dec) << endmsg;
break;
}
if (pfd[p].revents & POLLIN) {
fds_ready++;
ports[p]->parse ();
}
}
/* timeout driven */
if (fds_ready < 2 && timeout != -1) {
for (MidiTimeoutList::iterator i = midi_timeouts.begin(); i != midi_timeouts.end(); ) {
MidiTimeoutList::iterator tmp;
tmp = i;
++tmp;
if (!(*i)()) {
midi_timeouts.erase (i);
}
i = tmp;
}
}
}
}