578 lines
11 KiB
C++
578 lines
11 KiB
C++
|
/*
|
||
|
Copyright (C) 2000 Paul Barton-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.
|
||
|
|
||
|
$Id$
|
||
|
*/
|
||
|
|
||
|
#include <map>
|
||
|
|
||
|
#include <pbd/error.h>
|
||
|
#include <midi++/mmc.h>
|
||
|
#include <midi++/port.h>
|
||
|
#include <midi++/parser.h>
|
||
|
|
||
|
using namespace std;
|
||
|
using namespace MIDI;
|
||
|
|
||
|
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 (Port &p, float version,
|
||
|
CommandSignature &csig,
|
||
|
ResponseSignature &rsig)
|
||
|
|
||
|
: _port (p)
|
||
|
{
|
||
|
Parser *parser;
|
||
|
|
||
|
build_mmc_cmd_map ();
|
||
|
|
||
|
_device_id = 1;
|
||
|
|
||
|
if ((parser = _port.input()) != 0) {
|
||
|
parser->mmc.connect
|
||
|
(mem_fun (*this, &MachineControl::process_mmc_message));
|
||
|
} else {
|
||
|
warning << "MMC connected to a non-input port: useless!"
|
||
|
<< endmsg;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
MachineControl::set_device_id (byte id)
|
||
|
|
||
|
{
|
||
|
_device_id = id & 0x7f;
|
||
|
}
|
||
|
|
||
|
bool
|
||
|
MachineControl::is_mmc (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 &p, 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 << "*** MMC message: len = " << len << "\n\t";
|
||
|
for (size_t i = 0; i < len; i++) {
|
||
|
cerr << hex << (int) msg[i] << dec << ' ';
|
||
|
}
|
||
|
cerr << endl;
|
||
|
#endif
|
||
|
|
||
|
if (msg[1] != 0x7f && msg[1] != _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 (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_record_ready (&msg[3], len - 3);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
warning << "MIDI::MachineControl: masked write to "
|
||
|
<< hex << (int) msg[2] << dec
|
||
|
<< " not implemented"
|
||
|
<< endmsg;
|
||
|
}
|
||
|
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
MachineControl::write_track_record_ready (byte *msg, size_t len)
|
||
|
|
||
|
{
|
||
|
size_t n;
|
||
|
size_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
|
||
|
|
||
|
*/
|
||
|
|
||
|
/* XXX check needed to make sure we don't go outside the
|
||
|
support number of tracks.
|
||
|
*/
|
||
|
|
||
|
base_track = (msg[0] * 7) - 5;
|
||
|
|
||
|
for (n = 0; n < 7; n++) {
|
||
|
if (msg[1] & (1<<n)) {
|
||
|
|
||
|
/* Only touch tracks that have the "mask"
|
||
|
bit set.
|
||
|
*/
|
||
|
|
||
|
if (msg[2] & (1<<n)) {
|
||
|
trackRecordStatus[base_track+n] = true;
|
||
|
TrackRecordStatusChange (*this, base_track+n,
|
||
|
true);
|
||
|
} else {
|
||
|
trackRecordStatus[base_track+n] = false;
|
||
|
TrackRecordStatusChange (*this, base_track+n,
|
||
|
false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int
|
||
|
MachineControl::do_locate (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 (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 (byte *msg, size_t msglen)
|
||
|
|
||
|
{
|
||
|
size_t 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;
|
||
|
}
|
||
|
|