ardour/libs/midi++2/mmc.cc

722 lines
15 KiB
C++

/*
* Copyright (C) 2000-2017 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2009-2010 David Robillard <d@drobilla.net>
* Copyright (C) 2009-2012 Carl Hetherington <carl@carlh.net>
* Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
*
* 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <fcntl.h>
#include <map>
#include "temporal/time.h"
#include "temporal/bbt_time.h"
#include "pbd/error.h"
#include "midi++/mmc.h"
#include "midi++/port.h"
#include "midi++/parser.h"
#ifndef __INT_MAX__ // 'ssize_t' won't be defined yet
// (except in later versions of MSVC)
#if !defined (_MSC_VER) || (_MSC_VER < 1800)
typedef long ssize_t;
#endif
#endif
using namespace std;
using namespace MIDI;
using namespace PBD;
static std::map<int,string> mmc_cmd_map;
static void build_mmc_cmd_map ()
{
pair<int,string> newpair;
newpair.first = 0x1;
newpair.second = "Stop";
mmc_cmd_map.insert (newpair);
newpair.first = 0x2;
newpair.second = "Play";
mmc_cmd_map.insert (newpair);
newpair.first = 0x3;
newpair.second = "DeferredPlay";
mmc_cmd_map.insert (newpair);
newpair.first = 0x4;
newpair.second = "FastForward";
mmc_cmd_map.insert (newpair);
newpair.first = 0x5;
newpair.second = "Rewind";
mmc_cmd_map.insert (newpair);
newpair.first = 0x6;
newpair.second = "RecordStrobe";
mmc_cmd_map.insert (newpair);
newpair.first = 0x7;
newpair.second = "RecordExit";
mmc_cmd_map.insert (newpair);
newpair.first = 0x8;
newpair.second = "RecordPause";
mmc_cmd_map.insert (newpair);
newpair.first = 0x9;
newpair.second = "Pause";
mmc_cmd_map.insert (newpair);
newpair.first = 0xA;
newpair.second = "Eject";
mmc_cmd_map.insert (newpair);
newpair.first = 0xB;
newpair.second = "Chase";
mmc_cmd_map.insert (newpair);
newpair.first = 0xC;
newpair.second = "CommandErrorReset";
mmc_cmd_map.insert (newpair);
newpair.first = 0xD;
newpair.second = "MmcReset";
mmc_cmd_map.insert (newpair);
newpair.first = 0x20;
newpair.second = "Illegal Mackie Jog Start";
mmc_cmd_map.insert (newpair);
newpair.first = 0x21;
newpair.second = "Illegal Mackie Jog Stop";
mmc_cmd_map.insert (newpair);
newpair.first = 0x40;
newpair.second = "Write";
mmc_cmd_map.insert (newpair);
newpair.first = 0x41;
newpair.second = "MaskedWrite";
mmc_cmd_map.insert (newpair);
newpair.first = 0x42;
newpair.second = "Read";
mmc_cmd_map.insert (newpair);
newpair.first = 0x43;
newpair.second = "Update";
mmc_cmd_map.insert (newpair);
newpair.first = 0x44;
newpair.second = "Locate";
mmc_cmd_map.insert (newpair);
newpair.first = 0x45;
newpair.second = "VariablePlay";
mmc_cmd_map.insert (newpair);
newpair.first = 0x46;
newpair.second = "Search";
mmc_cmd_map.insert (newpair);
newpair.first = 0x47;
newpair.second = "Shuttle";
mmc_cmd_map.insert (newpair);
newpair.first = 0x48;
newpair.second = "Step";
mmc_cmd_map.insert (newpair);
newpair.first = 0x49;
newpair.second = "AssignSystemMaster";
mmc_cmd_map.insert (newpair);
newpair.first = 0x4A;
newpair.second = "GeneratorCommand";
mmc_cmd_map.insert (newpair);
newpair.first = 0x4B;
newpair.second = "MtcCommand";
mmc_cmd_map.insert (newpair);
newpair.first = 0x4C;
newpair.second = "Move";
mmc_cmd_map.insert (newpair);
newpair.first = 0x4D;
newpair.second = "Add";
mmc_cmd_map.insert (newpair);
newpair.first = 0x4E;
newpair.second = "Subtract";
mmc_cmd_map.insert (newpair);
newpair.first = 0x4F;
newpair.second = "DropFrameAdjust";
mmc_cmd_map.insert (newpair);
newpair.first = 0x50;
newpair.second = "Procedure";
mmc_cmd_map.insert (newpair);
newpair.first = 0x51;
newpair.second = "Event";
mmc_cmd_map.insert (newpair);
newpair.first = 0x52;
newpair.second = "Group";
mmc_cmd_map.insert (newpair);
newpair.first = 0x53;
newpair.second = "CommandSegment";
mmc_cmd_map.insert (newpair);
newpair.first = 0x54;
newpair.second = "DeferredVariablePlay";
mmc_cmd_map.insert (newpair);
newpair.first = 0x55;
newpair.second = "RecordStrobeVariable";
mmc_cmd_map.insert (newpair);
newpair.first = 0x7C;
newpair.second = "Wait";
mmc_cmd_map.insert (newpair);
newpair.first = 0x7F;
newpair.second = "Resume";
mmc_cmd_map.insert (newpair);
}
MachineControl::MachineControl ()
{
build_mmc_cmd_map ();
_receive_device_id = 0x7f;
_send_device_id = 0x7f;
}
void
MachineControl::set_ports (MIDI::Port* ip, MIDI::Port* op)
{
port_connections.drop_connections ();
_input_port = ip;
_output_port = op;
_input_port->parser()->mmc.connect_same_thread (port_connections, boost::bind (&MachineControl::process_mmc_message, this, _1, _2, _3));
_input_port->parser()->start.connect_same_thread (port_connections, boost::bind (&MachineControl::spp_start, this));
_input_port->parser()->contineu.connect_same_thread (port_connections, boost::bind (&MachineControl::spp_continue, this));
_input_port->parser()->stop.connect_same_thread (port_connections, boost::bind (&MachineControl::spp_stop, this));
}
void
MachineControl::set_receive_device_id (MIDI::byte id)
{
_receive_device_id = id & 0x7f;
}
void
MachineControl::set_send_device_id (MIDI::byte id)
{
_send_device_id = id & 0x7f;
}
bool
MachineControl::is_mmc (MIDI::byte *sysex_buf, size_t len)
{
if (len < 4 || len > 48) {
return false;
}
if (sysex_buf[1] != 0x7f) {
return false;
}
if (sysex_buf[3] != 0x6 && /* MMC Command */
sysex_buf[3] != 0x7) { /* MMC Response */
return false;
}
return true;
}
void
MachineControl::process_mmc_message (Parser &, MIDI::byte *msg, size_t len)
{
size_t skiplen;
byte *mmc_msg;
bool single_byte;
/* Reject if its not for us. 0x7f is the "all-call" device ID */
/* msg[0] = 0x7f (MMC sysex ID(
msg[1] = device ID
msg[2] = 0x6 (MMC command) or 0x7 (MMC response)
msg[3] = MMC command code
msg[4] = (typically) byte count for following part of command
*/
#if 0
cerr << "*** me = " << (int) _receive_device_id << " MMC message: len = " << len << "\n\t";
for (size_t i = 0; i < len; i++) {
cerr << hex << (int) msg[i] << dec << ' ';
}
cerr << endl;
#endif
if (_receive_device_id != 0x7f && msg[1] != 0x7f && msg[1] != _receive_device_id) {
return;
}
mmc_msg = &msg[3];
len -= 3;
do {
single_byte = false;
/* this works for all non-single-byte "counted"
commands. we set it to 1 for the exceptions.
*/
std::map<int,string>::iterator x = mmc_cmd_map.find ((int)mmc_msg[0]);
string cmdname = "unknown";
if (x != mmc_cmd_map.end()) {
cmdname = (*x).second;
}
#if 0
cerr << "+++ MMC type "
<< hex
<< ((int) *mmc_msg)
<< dec
<< " \"" << cmdname << "\" "
<< " len = " << len
<< endl;
#endif
switch (*mmc_msg) {
/* SINGLE-BYTE, UNCOUNTED COMMANDS */
case cmdStop:
Stop (*this);
single_byte = true;
break;
case cmdPlay:
Play (*this);
single_byte = true;
break;
case cmdDeferredPlay:
DeferredPlay (*this);
single_byte = true;
break;
case cmdFastForward:
FastForward (*this);
single_byte = true;
break;
case cmdRewind:
Rewind (*this);
single_byte = true;
break;
case cmdRecordStrobe:
RecordStrobe (*this);
single_byte = true;
break;
case cmdRecordExit:
RecordExit (*this);
single_byte = true;
break;
case cmdRecordPause:
RecordPause (*this);
single_byte = true;
break;
case cmdPause:
Pause (*this);
single_byte = true;
break;
case cmdEject:
Eject (*this);
single_byte = true;
break;
case cmdChase:
Chase (*this);
single_byte = true;
break;
case cmdCommandErrorReset:
CommandErrorReset (*this);
single_byte = true;
break;
case cmdMmcReset:
MmcReset (*this);
single_byte = true;
break;
case cmdIllegalMackieJogStart:
JogStart (*this);
single_byte = true;
break;
case cmdIllegalMackieJogStop:
JogStop (*this);
single_byte = true;
break;
/* END OF SINGLE-BYTE, UNCOUNTED COMMANDS */
case cmdMaskedWrite:
do_masked_write (mmc_msg, len);
break;
case cmdLocate:
do_locate (mmc_msg, len);
break;
case cmdShuttle:
do_shuttle (mmc_msg, len);
break;
case cmdStep:
do_step (mmc_msg, len);
break;
case cmdWrite:
case cmdRead:
case cmdUpdate:
case cmdVariablePlay:
case cmdSearch:
case cmdAssignSystemMaster:
case cmdGeneratorCommand:
case cmdMtcCommand:
case cmdMove:
case cmdAdd:
case cmdSubtract:
case cmdDropFrameAdjust:
case cmdProcedure:
case cmdEvent:
case cmdGroup:
case cmdCommandSegment:
case cmdDeferredVariablePlay:
case cmdRecordStrobeVariable:
case cmdWait:
case cmdResume:
error << "MIDI::MachineControl: unimplemented MMC command "
<< hex << (int) *mmc_msg << dec
<< endmsg;
break;
default:
error << "MIDI::MachineControl: unknown MMC command "
<< hex << (int) *mmc_msg << dec
<< endmsg;
break;
}
/* increase skiplen to cover the command byte and
count byte (if it existed).
*/
if (!single_byte) {
skiplen = mmc_msg[1] + 2;
} else {
skiplen = 1;
}
if (len <= skiplen) {
break;
}
mmc_msg += skiplen;
len -= skiplen;
} while (len > 1); /* skip terminating EOX byte */
}
int
MachineControl::do_masked_write (MIDI::byte *msg, size_t len)
{
/* return the number of bytes "consumed" */
int retval = msg[1] + 2; /* bytes following + 2 */
switch (msg[2]) {
case 0x4f: /* Track Record Ready Status */
write_track_status (&msg[3], len - 3, msg[2]);
break;
case 0x62: /* track mute */
write_track_status (&msg[3], len - 3, msg[2]);
break;
default:
warning << "MIDI::MachineControl: masked write to "
<< hex << (int) msg[2] << dec
<< " not implemented"
<< endmsg;
}
return retval;
}
void
MachineControl::write_track_status (MIDI::byte *msg, size_t /*len*/, MIDI::byte reg)
{
size_t n;
ssize_t base_track;
/* Bits 0-4 of the first byte are for special tracks:
bit 0: video
bit 1: reserved
bit 2: time code
bit 3: aux track a
bit 4: aux track b
the format of the message (its an MMC Masked Write) is:
0x41 Command Code
<count> byte count of following data
<name> byte value of the field being written
<byte #> byte number of target byte in the
bitmap being written to
<mask> ones in the mask indicate which bits will be changed
<data> new data for the byte being written
by the time this code is executing, msg[0] is the
byte number of the target byte. if its zero, we
are writing to a special byte in the standard
track bitmap, in which the first 5 bits are
special. hence the bits for tracks 1 + 2 are bits
5 and 6 of the first byte of the track
bitmap. so:
change track 1: msg[0] = 0; << first byte of track bitmap
msg[1] = 0100000; << binary: bit 5 set
change track 2: msg[0] = 0; << first byte of track bitmap
msg[1] = 1000000; << binary: bit 6 set
change track 3: msg[0] = 1; << second byte of track bitmap
msg[1] = 0000001; << binary: bit 0 set
the (msg[0] * 8) - 6 computation is an attempt to
extract the value of the first track: ie. the one
that would be indicated by bit 0 being set.
so, if msg[0] = 0, msg[1] = 0100000 (binary),
what happens is that base_track = -5, but by the
time we check the correct bit, n = 5, and so the
computed track for the status change is 0 (first
track).
if msg[0] = 1, then the base track for any change is 2 (the third track), and so on.
*/
/* XXX check needed to make sure we don't go outside the
supported number of tracks.
*/
if (msg[0] == 0) {
base_track = -5;
} else {
base_track = (msg[0] * 8) - 6;
}
for (n = 0; n < 7; n++) {
if (msg[1] & (1<<n)) {
/* Only touch tracks that have the "mask"
bit set.
*/
bool val = (msg[2] & (1<<n));
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;
}
}
}
}
int
MachineControl::do_locate (MIDI::byte *msg, size_t /*msglen*/)
{
if (msg[2] == 0) {
warning << "MIDI::MMC: locate [I/F] command not supported"
<< endmsg;
return 0;
}
/* regular "target" locate command */
Locate (*this, &msg[3]);
return 0;
}
int
MachineControl::do_step (MIDI::byte *msg, size_t /*msglen*/)
{
int steps = msg[2] & 0x3f;
if (msg[2] & 0x40) {
steps = -steps;
}
Step (*this, steps);
return 0;
}
int
MachineControl::do_shuttle (MIDI::byte *msg, size_t /*msglen*/)
{
bool forward;
byte sh = msg[2];
byte sm = msg[3];
byte sl = msg[4];
size_t left_shift;
size_t integral;
size_t fractional;
float shuttle_speed;
if (sh & (1<<6)) {
forward = false;
} else {
forward = true;
}
left_shift = (sh & 0x38);
integral = ((sh & 0x7) << left_shift) | (sm >> (7 - left_shift));
fractional = ((sm << left_shift) << 7) | sl;
shuttle_speed = integral +
((float)fractional / (1 << (14 - left_shift)));
Shuttle (*this, shuttle_speed, forward);
return 0;
}
void
MachineControl::enable_send (bool yn)
{
_enable_send = yn;
}
/** Send a MMC command to a the MMC port.
* @param c command.
*/
void
MachineControl::send (MachineControlCommand const & c, timestamp_t when)
{
if (_output_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 (_output_port->midimsg (buffer, b - buffer, when)) {
error << "MMC: cannot send command" << endmsg;
}
}
void
MachineControl::spp_start ()
{
SPPStart (); /* EMIT SIGNAL */
}
void
MachineControl::spp_continue ()
{
SPPContinue (); /* EMIT SIGNAL */
}
void
MachineControl::spp_stop ()
{
SPPStop (); /* EMIT SIGNAL */
}
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 % 24;
*b++ = _time.minutes;
*b++ = _time.seconds;
*b++ = _time.frames;
*b++ = _time.subframes;
}
*b++ = 0xf7;
return b;
}