From 14fe7a0ae8f30bc25203b3d69b7bdcdaa8416ef5 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Mon, 23 Nov 2015 10:44:40 -0500 Subject: add (N)RPN handling to libmidi++ --- libs/midi++2/channel.cc | 201 ++++++++++++++++++++++++++++++++++++++++-- libs/midi++2/midi++/channel.h | 34 +++++++ libs/midi++2/midi++/parser.h | 7 ++ 3 files changed, 235 insertions(+), 7 deletions(-) diff --git a/libs/midi++2/channel.cc b/libs/midi++2/channel.cc index b3cb42f593..fd9a0d1026 100644 --- a/libs/midi++2/channel.cc +++ b/libs/midi++2/channel.cc @@ -27,9 +27,14 @@ using namespace MIDI; Channel::Channel (MIDI::byte channelnum, Port &p) : _port (p) + , _channel_number (channelnum) + , _rpn_msb (0) + , _rpn_lsb (0) + , _nrpn_msb (0) + , _nrpn_lsb (0) + , _rpn_state (RPNState (0)) + , _nrpn_state (RPNState (0)) { - _channel_number = channelnum; - reset (0, 1, false); } @@ -75,10 +80,8 @@ Channel::reset (timestamp_t timestamp, framecnt_t /*nframes*/, bool notes_off) _controller_14bit[n] = false; } - _rpn_msb = 0; - _rpn_lsb = 0; - _nrpn_msb = 0; - _nrpn_lsb = 0; + rpn_reset (); + nrpn_reset (); _omni = true; _poly = false; @@ -86,6 +89,26 @@ Channel::reset (timestamp_t timestamp, framecnt_t /*nframes*/, bool notes_off) _notes_on = 0; } +void +Channel::rpn_reset () +{ + _rpn_msb = 0; + _rpn_lsb = 0; + _rpn_val_msb = 0; + _rpn_val_lsb = 0; + _rpn_state = RPNState (0); +} + +void +Channel::nrpn_reset () +{ + _nrpn_msb = 0; + _nrpn_lsb = 0; + _nrpn_val_msb = 0; + _nrpn_val_lsb = 0; + _nrpn_state = RPNState (0); +} + void Channel::process_note_off (Parser & /*parser*/, EventTwoBytes *tb) { @@ -105,8 +128,130 @@ Channel::process_note_on (Parser & /*parser*/, EventTwoBytes *tb) _notes_on++; } +const Channel::RPNState Channel::RPN_READY_FOR_VALUE = RPNState (HaveLSB|HaveMSB); +const Channel::RPNState Channel::RPN_VALUE_READY = RPNState (HaveLSB|HaveMSB|HaveValue); + +bool +Channel::maybe_process_rpns (Parser& parser, EventTwoBytes *tb) +{ + switch (tb->controller_number) { + case 0x62: + _rpn_state = RPNState (_rpn_state|HaveMSB); + _rpn_lsb = tb->value; + if (_rpn_msb == 0x7f && _rpn_lsb == 0x7f) { + rpn_reset (); + } + return true; + case 0x63: + _rpn_state = RPNState (_rpn_state|HaveLSB); + _rpn_msb = tb->value; + if (_rpn_msb == 0x7f && _rpn_lsb == 0x7f) { + rpn_reset (); + } + return true; + + case 0x64: + _nrpn_state = RPNState (_rpn_state|HaveMSB); + _rpn_lsb = tb->value; + if (_nrpn_msb == 0x7f && _nrpn_lsb == 0x7f) { + nrpn_reset (); + } + return true; + case 0x65: + _nrpn_state = RPNState (_rpn_state|HaveLSB); + _rpn_msb = tb->value; + if (_rpn_msb == 0x7f && _rpn_lsb == 0x7f) { + nrpn_reset (); + } + return true; + } + + if ((_nrpn_state & RPN_READY_FOR_VALUE) == RPN_READY_FOR_VALUE) { + + uint16_t rpn_id = (_rpn_msb << 7)|_rpn_lsb; + + switch (tb->controller_number) { + case 0x60: + /* data increment */ + _nrpn_state = RPNState (_nrpn_state|HaveValue); + parser.channel_nrpn_increment[_channel_number] (parser, rpn_id); /* EMIT SIGNAL */ + return true; + case 0x61: + /* data decrement */ + _nrpn_state = RPNState (_nrpn_state|HaveValue); + parser.channel_nrpn_decrement[_channel_number] (parser, rpn_id); /* EMIT SIGNAL */ + return true; + case 0x06: + /* data entry MSB */ + _nrpn_state = RPNState (_nrpn_state|HaveValue); + _nrpn_val_msb = tb->value; + break; + case 0x26: + /* data entry LSB */ + _nrpn_state = RPNState (_nrpn_state|HaveValue); + _nrpn_val_lsb = tb->value; + } + + if (_nrpn_state == RPN_VALUE_READY) { + + float rpn_val = ((_rpn_val_msb << 7)|_rpn_val_lsb)/16384.0; + + std::pair result = nrpns.insert (std::make_pair (rpn_id, rpn_val)); + + if (!result.second) { + result.first->second = rpn_val; + } + + parser.channel_nrpn[_channel_number] (parser, rpn_id); /* EMIT SIGNAL */ + return true; + } + + } else if ((_rpn_state & RPN_READY_FOR_VALUE) == RPN_READY_FOR_VALUE) { + + uint16_t rpn_id = (_rpn_msb << 7)|_rpn_lsb; + + switch (tb->controller_number) { + case 0x60: + /* data increment */ + _rpn_state = RPNState (_rpn_state|HaveValue); + parser.channel_rpn_increment[_channel_number] (parser, rpn_id); /* EMIT SIGNAL */ + return true; + case 0x61: + /* data decrement */ + _rpn_state = RPNState (_rpn_state|HaveValue); + parser.channel_rpn_decrement[_channel_number] (parser, rpn_id); /* EMIT SIGNAL */ + return true; + case 0x06: + /* data entry MSB */ + _rpn_state = RPNState (_rpn_state|HaveValue); + _rpn_val_msb = tb->value; + break; + case 0x26: + /* data entry LSB */ + _rpn_state = RPNState (_rpn_state|HaveValue); + _rpn_val_lsb = tb->value; + } + + if (_rpn_state == RPN_VALUE_READY) { + + float rpn_val = ((_rpn_val_msb << 7)|_rpn_val_lsb)/16384.0; + + std::pair result = rpns.insert (std::make_pair (rpn_id, rpn_val)); + + if (!result.second) { + result.first->second = rpn_val; + } + + parser.channel_rpn[_channel_number] (parser, rpn_id); /* EMIT SIGNAL */ + return true; + } + } + + return false; +} + void -Channel::process_controller (Parser & /*parser*/, EventTwoBytes *tb) +Channel::process_controller (Parser & parser, EventTwoBytes *tb) { unsigned short cv; @@ -115,6 +260,16 @@ Channel::process_controller (Parser & /*parser*/, EventTwoBytes *tb) all changes *are* atomic. */ + if (maybe_process_rpns (parser, tb)) { + return; + } + + /* Note: if RPN data controllers (0x60, 0x61, 0x6, 0x26) are received + * without a previous RPN parameter ID message, or after the RPN ID + * has been reset, they will be treated like ordinary CC messages. + */ + + if (tb->controller_number < 32) { /* unsigned: no test for >= 0 */ /* if this controller is already known to use 14 bits, @@ -272,3 +427,35 @@ Channel::channel_msg (MIDI::byte id, MIDI::byte val1, MIDI::byte val2, timestamp return _port.midimsg (msg, len, timestamp); } + +float +Channel::rpn_value (uint16_t rpn) const +{ + return rpn_value_absolute (rpn) / 16384.0f; +} + +float +Channel::rpn_value_absolute (uint16_t rpn) const +{ + RPNList::const_iterator r = rpns.find (rpn); + if (r == rpns.end()) { + return 0.0; + } + return r->second; +} + +float +Channel::nrpn_value (uint16_t nrpn) const +{ + return nrpn_value_absolute (nrpn) / 16384.0f; +} + +float +Channel::nrpn_value_absolute (uint16_t nrpn) const +{ + RPNList::const_iterator r = nrpns.find (nrpn); + if (r == nrpns.end()) { + return 0.0; + } + return r->second; +} diff --git a/libs/midi++2/midi++/channel.h b/libs/midi++2/midi++/channel.h index 6f4928209a..6bd08073b3 100644 --- a/libs/midi++2/midi++/channel.h +++ b/libs/midi++2/midi++/channel.h @@ -21,6 +21,7 @@ #define __midichannel_h__ #include +#include #include "pbd/signals.h" #include "midi++/parser.h" @@ -75,6 +76,9 @@ class LIBMIDIPP_API Channel : public PBD::ScopedConnectionList { _controller_val[n%128] = val; } + controller_value_t rpn_value (uint16_t rpn_id); + controller_value_t nrpn_value (uint16_t rpn_id); + bool channel_msg (byte id, byte val1, byte val2, timestamp_t timestamp); bool all_notes_off (timestamp_t timestamp) { return channel_msg (MIDI::controller, 123, 0, timestamp); @@ -108,6 +112,11 @@ class LIBMIDIPP_API Channel : public PBD::ScopedConnectionList { return channel_msg (MIDI::pitchbend, lsb, msb, timestamp); } + float rpn_value (uint16_t rpn) const; + float nrpn_value (uint16_t nrpn) const; + float rpn_value_absolute (uint16_t rpn) const; + float nrpn_value_absolute (uint16_t nrpn) const; + protected: friend class Port; void connect_signals (); @@ -115,14 +124,26 @@ class LIBMIDIPP_API Channel : public PBD::ScopedConnectionList { private: Port& _port; + enum RPNState { + HaveLSB = 0x1, + HaveMSB = 0x2, + HaveValue = 0x4 + }; + /* Current channel values */ byte _channel_number; unsigned short _bank_number; byte _program_number; byte _rpn_msb; byte _rpn_lsb; + byte _rpn_val_msb; + byte _rpn_val_lsb; byte _nrpn_msb; byte _nrpn_lsb; + byte _nrpn_val_lsb; + byte _nrpn_val_msb; + RPNState _rpn_state; + RPNState _nrpn_state; byte _chanpress; byte _polypress[128]; bool _controller_14bit[128]; @@ -139,6 +160,11 @@ class LIBMIDIPP_API Channel : public PBD::ScopedConnectionList { bool _mono; size_t _notes_on; + typedef std::map RPNList; + + RPNList rpns; + RPNList nrpns; + void reset (timestamp_t timestamp, framecnt_t nframes, bool notes_off = true); void process_note_off (Parser &, EventTwoBytes *); @@ -149,6 +175,14 @@ class LIBMIDIPP_API Channel : public PBD::ScopedConnectionList { void process_chanpress (Parser &, byte); void process_pitchbend (Parser &, pitchbend_t); void process_reset (Parser &); + bool maybe_process_rpns (Parser&, EventTwoBytes *); + + void rpn_reset (); + void nrpn_reset (); + + static const RPNState RPN_READY_FOR_VALUE; + static const RPNState RPN_VALUE_READY; + }; } // namespace MIDI diff --git a/libs/midi++2/midi++/parser.h b/libs/midi++2/midi++/parser.h index 0f2ecf49fd..7eb690d402 100644 --- a/libs/midi++2/midi++/parser.h +++ b/libs/midi++2/midi++/parser.h @@ -39,6 +39,7 @@ typedef PBD::Signal2 TimestampedSignal; typedef PBD::Signal2 OneByteSignal; typedef PBD::Signal2 TwoByteSignal; typedef PBD::Signal2 PitchBendSignal; +typedef PBD::Signal2 RPNSignal; typedef PBD::Signal3 Signal; class LIBMIDIPP_API Parser { @@ -75,6 +76,12 @@ class LIBMIDIPP_API Parser { TwoByteSignal channel_controller[16]; ZeroByteSignal channel_active_preparse[16]; ZeroByteSignal channel_active_postparse[16]; + RPNSignal channel_rpn[16]; + RPNSignal channel_nrpn[16]; + RPNSignal channel_rpn_increment[16]; + RPNSignal channel_rpn_decrement[16]; + RPNSignal channel_nrpn_increment[16]; + RPNSignal channel_nrpn_decrement[16]; OneByteSignal mtc_quarter_frame; /* see below for more useful signals */ Signal mtc; -- cgit v1.2.3