summaryrefslogtreecommitdiff
path: root/libs/midi++2/parser.cc
diff options
context:
space:
mode:
authorCarl Hetherington <carl@carlh.net>2009-09-15 16:49:15 +0000
committerCarl Hetherington <carl@carlh.net>2009-09-15 16:49:15 +0000
commit127b8a62b790c7515a27934b920e88b7de04b7fc (patch)
treed60be81f6393477eb7335fc1dc0549cfa7571dc3 /libs/midi++2/parser.cc
parent7b14a4230b37a3cdd3af4ed84027957b7da60849 (diff)
Normalise names of .cc and .h files.
git-svn-id: svn://localhost/ardour2/branches/3.0@5664 d708f5d6-7413-0410-9779-e7cbd77b26cf
Diffstat (limited to 'libs/midi++2/parser.cc')
-rw-r--r--libs/midi++2/parser.cc789
1 files changed, 789 insertions, 0 deletions
diff --git a/libs/midi++2/parser.cc b/libs/midi++2/parser.cc
new file mode 100644
index 0000000000..8f416a9717
--- /dev/null
+++ b/libs/midi++2/parser.cc
@@ -0,0 +1,789 @@
+/*
+ Copyright (C) 1998 Paul Barton-Davis
+
+ This file was inspired by the MIDI parser for KeyKit by
+ Tim Thompson.
+
+ 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 <cstring>
+#include <cstdlib>
+#include <unistd.h>
+#include <cstring>
+#include <iostream>
+#include <iterator>
+
+#include "midi++/types.h"
+#include "midi++/parser.h"
+#include "midi++/port.h"
+#include "midi++/mmc.h"
+#include "pbd/transmitter.h"
+
+using namespace std;
+using namespace sigc;
+using namespace MIDI;
+
+const char *
+Parser::midi_event_type_name (eventType t)
+
+{
+ switch (t) {
+ case none:
+ return "no midi messages";
+
+ case raw:
+ return "raw midi data";
+
+ case MIDI::any:
+ return "any midi message";
+
+ case off:
+ return "note off";
+
+ case on:
+ return "note on";
+
+ case polypress:
+ return "aftertouch";
+
+ case MIDI::controller:
+ return "controller";
+
+ case program:
+ return "program change";
+
+ case chanpress:
+ return "channel pressure";
+
+ case MIDI::pitchbend:
+ return "pitch bend";
+
+ case MIDI::sysex:
+ return "system exclusive";
+
+ case MIDI::song:
+ return "song position";
+
+ case MIDI::tune:
+ return "tune";
+
+ case MIDI::eox:
+ return "end of sysex";
+
+ case MIDI::timing:
+ return "timing";
+
+ case MIDI::start:
+ return "start";
+
+ case MIDI::stop:
+ return "continue";
+
+ case MIDI::contineu:
+ return "stop";
+
+ case active:
+ return "active sense";
+
+ default:
+ return "unknow MIDI event type";
+ }
+}
+
+Parser::Parser (Port &p)
+ : _port (p)
+
+{
+ trace_stream = 0;
+ trace_prefix = "";
+ memset (message_counter, 0, sizeof (message_counter[0]) * 256);
+ msgindex = 0;
+ msgtype = none;
+ msglen = 256;
+ msgbuf = (unsigned char *) malloc (msglen);
+ msgbuf[msgindex++] = 0x90;
+ _mmc_forward = false;
+ reset_mtc_state ();
+ _offline = false;
+
+ /* this hack deals with the possibility of our first MIDI
+ bytes being running status messages.
+ */
+
+ channel_msg (0x90);
+ state = NEEDSTATUS;
+
+ pre_variable_state = NEEDSTATUS;
+ pre_variable_msgtype = none;
+}
+
+Parser::~Parser ()
+
+{
+ delete msgbuf;
+}
+
+void
+Parser::trace_event (Parser &, byte *msg, size_t len)
+{
+ eventType type;
+ ostream *o;
+
+ if ((o = trace_stream) == NULL) { /* can be asynchronously removed */
+ return;
+ }
+
+ type = (eventType) (msg[0]&0xF0);
+
+ switch (type) {
+ case off:
+ *o << trace_prefix
+ << "Channel "
+ << (msg[0]&0xF)+1
+ << " NoteOff NoteNum "
+ << (int) msg[1]
+ << " Vel "
+ << (int) msg[2]
+ << endmsg;
+ break;
+
+ case on:
+ *o << trace_prefix
+ << "Channel "
+ << (msg[0]&0xF)+1
+ << " NoteOn NoteNum "
+ << (int) msg[1]
+ << " Vel "
+ << (int) msg[2]
+ << endmsg;
+ break;
+
+ case polypress:
+ *o << trace_prefix
+ << "Channel "
+ << (msg[0]&0xF)+1
+ << " PolyPressure"
+ << (int) msg[1]
+ << endmsg;
+ break;
+
+ case MIDI::controller:
+ *o << trace_prefix
+ << "Channel "
+ << (msg[0]&0xF)+1
+ << " Controller "
+ << (int) msg[1]
+ << " Value "
+ << (int) msg[2]
+ << endmsg;
+ break;
+
+ case program:
+ *o << trace_prefix
+ << "Channel "
+ << (msg[0]&0xF)+1
+ << " Program Change ProgNum "
+ << (int) msg[1]
+ << endmsg;
+ break;
+
+ case chanpress:
+ *o << trace_prefix
+ << "Channel "
+ << (msg[0]&0xF)+1
+ << " Channel Pressure "
+ << (int) msg[1]
+ << endmsg;
+ break;
+
+ case MIDI::pitchbend:
+ *o << trace_prefix
+ << "Channel "
+ << (msg[0]&0xF)+1
+ << " Pitch Bend "
+ << ((msg[2]<<7)|msg[1])
+ << endmsg;
+ break;
+
+ case MIDI::sysex:
+ if (len == 1) {
+ switch (msg[0]) {
+ case 0xf8:
+ *o << trace_prefix
+ << "Clock"
+ << endmsg;
+ break;
+ case 0xfa:
+ *o << trace_prefix
+ << "Start"
+ << endmsg;
+ break;
+ case 0xfb:
+ *o << trace_prefix
+ << "Continue"
+ << endmsg;
+ break;
+ case 0xfc:
+ *o << trace_prefix
+ << "Stop"
+ << endmsg;
+ break;
+ case 0xfe:
+ *o << trace_prefix
+ << "Active Sense"
+ << endmsg;
+ break;
+ case 0xff:
+ *o << trace_prefix
+ << "System Reset"
+ << endmsg;
+ break;
+ default:
+ *o << trace_prefix
+ << "System Exclusive (1 byte : " << hex << (int) *msg << dec << ')'
+ << endmsg;
+ break;
+ }
+ } else {
+ *o << trace_prefix
+ << "System Exclusive (" << len << ") = [ " << hex;
+ for (unsigned int i = 0; i < len; ++i) {
+ *o << (int) msgbuf[i] << ' ';
+ }
+ *o << dec << ']' << endmsg;
+
+ }
+ break;
+
+ case MIDI::song:
+ *o << trace_prefix << "Song" << endmsg;
+ break;
+
+ case MIDI::tune:
+ *o << trace_prefix << "Tune" << endmsg;
+ break;
+
+ case MIDI::eox:
+ *o << trace_prefix << "End-of-System Exclusive" << endmsg;
+ break;
+
+ case MIDI::timing:
+ *o << trace_prefix << "Timing" << endmsg;
+ break;
+
+ case MIDI::start:
+ *o << trace_prefix << "Start" << endmsg;
+ break;
+
+ case MIDI::stop:
+ *o << trace_prefix << "Stop" << endmsg;
+ break;
+
+ case MIDI::contineu:
+ *o << trace_prefix << "Continue" << endmsg;
+ break;
+
+ case active:
+ *o << trace_prefix << "Active Sense" << endmsg;
+ break;
+
+ default:
+ *o << trace_prefix << "Unrecognized MIDI message" << endmsg;
+ break;
+ }
+}
+
+void
+Parser::trace (bool onoff, ostream *o, const string &prefix)
+{
+ trace_connection.disconnect ();
+
+ if (onoff) {
+ cerr << "enabling tracing for port " << _port.name() << endl;
+ trace_stream = o;
+ trace_prefix = prefix;
+ trace_connection = any.connect (mem_fun (*this, &Parser::trace_event));
+ } else {
+ trace_prefix = "";
+ trace_stream = 0;
+ }
+}
+
+void
+Parser::scanner (unsigned char inbyte)
+{
+ bool statusbit;
+
+ // cerr << "parse: " << hex << (int) inbyte << dec << " state = " << state << " msgindex = " << msgindex << " runnable = " << runnable << endl;
+
+ /* Check active sensing early, so it doesn't interrupt sysex.
+
+ NOTE: active sense messages are not considered to fit under
+ "any" for the purposes of callbacks. If a caller wants
+ active sense messages handled, which is unlikely, then
+ they can just ask for it specifically. They are so unlike
+ every other MIDI message in terms of semantics that its
+ counter-productive to treat them similarly.
+ */
+
+ if (inbyte == 0xfe) {
+ message_counter[inbyte]++;
+ if (!_offline) {
+ active_sense (*this);
+ }
+ return;
+ }
+
+ /* If necessary, allocate larger message buffer. */
+
+ if (msgindex >= msglen) {
+ msglen *= 2;
+ msgbuf = (unsigned char *) realloc (msgbuf, msglen);
+ }
+
+ /*
+ Real time messages can occur ANYPLACE,
+ but do not interrupt running status.
+ */
+
+ bool rtmsg = false;
+
+ switch (inbyte) {
+ case 0xf8:
+ rtmsg = true;
+ break;
+ case 0xfa:
+ rtmsg = true;
+ break;
+ case 0xfb:
+ rtmsg = true;
+ break;
+ case 0xfc:
+ rtmsg = true;
+ break;
+ case 0xfd:
+ rtmsg = true;
+ break;
+ case 0xfe:
+ rtmsg = true;
+ break;
+ case 0xff:
+ rtmsg = true;
+ break;
+ }
+
+ if (rtmsg) {
+ if (edit (&inbyte, 1) >= 0 && !_offline) {
+ realtime_msg (inbyte);
+ }
+
+ return;
+ }
+
+ statusbit = (inbyte & 0x80);
+
+ /*
+ * Variable length messages (ie. the 'system exclusive')
+ * can be terminated by the next status byte, not necessarily
+ * an EOX. Actually, since EOX is a status byte, this
+ * code ALWAYS handles the end of a VARIABLELENGTH message.
+ */
+
+ if (state == VARIABLELENGTH && statusbit) {
+ /* The message has ended, so process it */
+
+ /* add EOX to any sysex message */
+
+ if (inbyte == MIDI::eox) {
+ msgbuf[msgindex++] = inbyte;
+ }
+
+#if 0
+ cerr << "SYSEX: " << hex;
+ for (unsigned int i = 0; i < msgindex; ++i) {
+ cerr << (int) msgbuf[i] << ' ';
+ }
+ cerr << dec << endl;
+#endif
+ if (msgindex > 0 && edit (msgbuf, msgindex) >= 0) {
+ if (!possible_mmc (msgbuf, msgindex) || _mmc_forward) {
+ if (!possible_mtc (msgbuf, msgindex) || _mtc_forward) {
+ if (!_offline) {
+ sysex (*this, msgbuf, msgindex);
+ }
+ }
+ }
+ if (!_offline) {
+ any (*this, msgbuf, msgindex);
+ }
+ }
+ }
+
+ /*
+ * Status bytes always start a new message, except EOX
+ */
+
+ if (statusbit) {
+
+ msgindex = 0;
+
+ if (inbyte == MIDI::eox) {
+ /* return to the state we had pre-sysex */
+
+ state = pre_variable_state;
+ runnable = was_runnable;
+ msgtype = pre_variable_msgtype;
+
+ if (state != NEEDSTATUS && runnable) {
+ msgbuf[msgindex++] = last_status_byte;
+ }
+ } else {
+ msgbuf[msgindex++] = inbyte;
+ if ((inbyte & 0xf0) == 0xf0) {
+ system_msg (inbyte);
+ runnable = false;
+ } else {
+ channel_msg (inbyte);
+ }
+ }
+
+ return;
+ }
+
+ /*
+ * We've got a Data byte.
+ */
+
+ msgbuf[msgindex++] = inbyte;
+
+ switch (state) {
+ case NEEDSTATUS:
+ /*
+ * We shouldn't get here, since in NEEDSTATUS mode
+ * we're expecting a new status byte, NOT any
+ * data bytes. On the other hand, some equipment
+ * with leaky modwheels and the like might be
+ * sending data bytes as part of running controller
+ * messages, so just handle it silently.
+ */
+ break;
+
+ case NEEDTWOBYTES:
+ /* wait for the second byte */
+ if (msgindex < 3)
+ return;
+ /*FALLTHRU*/
+
+ case NEEDONEBYTE:
+ /* We've completed a 1 or 2 byte message. */
+
+ if (edit (msgbuf, msgindex) == 0) {
+
+ /* message not cancelled by an editor */
+
+ message_counter[msgbuf[0] & 0xF0]++;
+
+ if (!_offline) {
+ signal (msgbuf, msgindex);
+ }
+ }
+
+ if (runnable) {
+ /* In Runnable mode, we reset the message
+ index, but keep the callbacks_pending and state the
+ same. This provides the "running status
+ byte" feature.
+ */
+ msgindex = 1;
+ } else {
+ /* If not Runnable, reset to NEEDSTATUS mode */
+ state = NEEDSTATUS;
+ }
+ break;
+
+ case VARIABLELENGTH:
+ /* nothing to do */
+ break;
+ }
+ return;
+}
+
+/** Call the real-time function for the specified byte, immediately.
+ * These can occur anywhere, so they don't change the state.
+ */
+void
+Parser::realtime_msg(unsigned char inbyte)
+
+{
+ message_counter[inbyte]++;
+
+ if (_offline) {
+ return;
+ }
+
+ switch (inbyte) {
+ case 0xf8:
+ timing (*this, _midi_clock_timestamp);
+ break;
+ case 0xfa:
+ start (*this, _midi_clock_timestamp);
+ break;
+ case 0xfb:
+ contineu (*this, _midi_clock_timestamp);
+ break;
+ case 0xfc:
+ stop (*this, _midi_clock_timestamp);
+ break;
+ case 0xfe:
+ /* !!! active sense message in realtime_msg: should not reach here
+ */
+ break;
+ case 0xff:
+ reset (*this);
+ break;
+ }
+
+ any (*this, &inbyte, 1);
+}
+
+
+/** Interpret a Channel (voice or mode) Message status byte.
+ */
+void
+Parser::channel_msg(unsigned char inbyte)
+{
+ last_status_byte = inbyte;
+ runnable = true; /* Channel messages can use running status */
+
+ /* The high 4 bits, which determine the type of channel message. */
+
+ switch (inbyte&0xF0) {
+ case 0x80:
+ msgtype = off;
+ state = NEEDTWOBYTES;
+ break;
+ case 0x90:
+ msgtype = on;
+ state = NEEDTWOBYTES;
+ break;
+ case 0xa0:
+ msgtype = polypress;
+ state = NEEDTWOBYTES;
+ break;
+ case 0xb0:
+ msgtype = MIDI::controller;
+ state = NEEDTWOBYTES;
+ break;
+ case 0xc0:
+ msgtype = program;
+ state = NEEDONEBYTE;
+ break;
+ case 0xd0:
+ msgtype = chanpress;
+ state = NEEDONEBYTE;
+ break;
+ case 0xe0:
+ msgtype = MIDI::pitchbend;
+ state = NEEDTWOBYTES;
+ break;
+ }
+}
+
+/** Initialize (and possibly emit) the signals for the
+ * specified byte. Set the state that the state-machine
+ * should go into. If the signal is not emitted
+ * immediately, it will be when the state machine gets to
+ * the end of the MIDI message.
+ */
+void
+Parser::system_msg (unsigned char inbyte)
+{
+ message_counter[inbyte]++;
+
+ switch (inbyte) {
+ case 0xf0:
+ pre_variable_msgtype = msgtype;
+ pre_variable_state = state;
+ was_runnable = runnable;
+ msgtype = MIDI::sysex;
+ state = VARIABLELENGTH;
+ break;
+ case 0xf1:
+ msgtype = MIDI::mtc_quarter;
+ state = NEEDONEBYTE;
+ break;
+ case 0xf2:
+ msgtype = MIDI::position;
+ state = NEEDTWOBYTES;
+ break;
+ case 0xf3:
+ msgtype = MIDI::song;
+ state = NEEDONEBYTE;
+ break;
+ case 0xf6:
+ if (!_offline) {
+ tune (*this);
+ }
+ state = NEEDSTATUS;
+ break;
+ case 0xf7:
+ break;
+ }
+
+ // all these messages will be sent via any()
+ // when they are complete.
+ // any (*this, &inbyte, 1);
+}
+
+void
+Parser::signal (byte *msg, size_t len)
+{
+ channel_t chan = msg[0]&0xF;
+ int chan_i = chan;
+
+ switch (msgtype) {
+ case none:
+ break;
+
+ case off:
+ channel_active_preparse[chan_i] (*this);
+ note_off (*this, (EventTwoBytes *) &msg[1]);
+ channel_note_off[chan_i]
+ (*this, (EventTwoBytes *) &msg[1]);
+ channel_active_postparse[chan_i] (*this);
+ break;
+
+ case on:
+ channel_active_preparse[chan_i] (*this);
+
+ /* Hack to deal with MIDI sources that use velocity=0
+ instead of noteOff.
+ */
+
+ if (msg[2] == 0) {
+ note_off (*this, (EventTwoBytes *) &msg[1]);
+ channel_note_off[chan_i]
+ (*this, (EventTwoBytes *) &msg[1]);
+ } else {
+ note_on (*this, (EventTwoBytes *) &msg[1]);
+ channel_note_on[chan_i]
+ (*this, (EventTwoBytes *) &msg[1]);
+ }
+
+ channel_active_postparse[chan_i] (*this);
+ break;
+
+ case MIDI::controller:
+ channel_active_preparse[chan_i] (*this);
+ controller (*this, (EventTwoBytes *) &msg[1]);
+ channel_controller[chan_i]
+ (*this, (EventTwoBytes *) &msg[1]);
+ channel_active_postparse[chan_i] (*this);
+ break;
+
+ case program:
+ channel_active_preparse[chan_i] (*this);
+ program_change (*this, msg[1]);
+ channel_program_change[chan_i] (*this, msg[1]);
+ channel_active_postparse[chan_i] (*this);
+ break;
+
+ case chanpress:
+ channel_active_preparse[chan_i] (*this);
+ pressure (*this, msg[1]);
+ channel_pressure[chan_i] (*this, msg[1]);
+ channel_active_postparse[chan_i] (*this);
+ break;
+
+ case polypress:
+ channel_active_preparse[chan_i] (*this);
+ poly_pressure (*this, (EventTwoBytes *) &msg[1]);
+ channel_poly_pressure[chan_i]
+ (*this, (EventTwoBytes *) &msg[1]);
+ channel_active_postparse[chan_i] (*this);
+ break;
+
+ case MIDI::pitchbend:
+ channel_active_preparse[chan_i] (*this);
+ pitchbend (*this, (msg[2]<<7)|msg[1]);
+ channel_pitchbend[chan_i] (*this, (msg[2]<<7)|msg[1]);
+ channel_active_postparse[chan_i] (*this);
+ break;
+
+ case MIDI::sysex:
+ sysex (*this, msg, len);
+ break;
+
+ case MIDI::mtc_quarter:
+ process_mtc_quarter_frame (msg);
+ mtc_quarter_frame (*this, *msg);
+ break;
+
+ case MIDI::position:
+ position (*this, msg, len);
+ break;
+
+ case MIDI::song:
+ song (*this, msg, len);
+ break;
+
+ case MIDI::tune:
+ tune (*this);
+
+ default:
+ /* XXX some kind of warning ? */
+ break;
+ }
+
+ any (*this, msg, len);
+}
+
+bool
+Parser::possible_mmc (byte *msg, size_t msglen)
+{
+ if (!MachineControl::is_mmc (msg, msglen)) {
+ return false;
+ }
+
+ /* hand over the just the interior MMC part of
+ the sysex msg without the leading 0xF0
+ */
+
+ if (!_offline) {
+ mmc (*this, &msg[1], msglen - 1);
+ }
+
+ return true;
+}
+
+void
+Parser::set_offline (bool yn)
+{
+ if (_offline != yn) {
+ _offline = yn;
+ OfflineStatusChanged ();
+
+ /* this hack deals with the possibility of our first MIDI
+ bytes being running status messages.
+ */
+
+ channel_msg (0x90);
+ state = NEEDSTATUS;
+ }
+}
+