diff options
author | Carl Hetherington <carl@carlh.net> | 2009-09-15 16:49:15 +0000 |
---|---|---|
committer | Carl Hetherington <carl@carlh.net> | 2009-09-15 16:49:15 +0000 |
commit | 127b8a62b790c7515a27934b920e88b7de04b7fc (patch) | |
tree | d60be81f6393477eb7335fc1dc0549cfa7571dc3 /libs/midi++2/parser.cc | |
parent | 7b14a4230b37a3cdd3af4ed84027957b7da60849 (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.cc | 789 |
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; + } +} + |