// Copyright 2009 Olivier Gillet.
//
// Author: Olivier Gillet (ol.gillet@gmail.com)
//
// 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 3 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, see .
//
// -----------------------------------------------------------------------------
//
// Decoding of MIDI messages.
#ifndef EDGES_MIDI_H_
#define EDGES_MIDI_H_
namespace midi {
const uint8_t kBankMsb = 0x00;
const uint8_t kBankLsb = 0x20;
const uint8_t kModulationWheelMsb = 0x01;
const uint8_t kBreathController = 0x02;
const uint8_t kFootPedalMsb = 0x04;
const uint8_t kDataEntryMsb = 0x06;
const uint8_t kVolume = 0x07;
const uint8_t kFootPedalLsb = 0x24;
const uint8_t kDataEntryLsb = 0x26;
const uint8_t kPortamentoTimeMsb = 0x05;
const uint8_t kHoldPedal = 0x40;
const uint8_t kHarmonicIntensity = 0x47;
const uint8_t kRelease = 0x48;
const uint8_t kAttack = 0x49;
const uint8_t kBrightness = 0x4a;
const uint8_t kDataIncrement = 0x60;
const uint8_t kDataDecrement = 0x61;
const uint8_t kNrpnMsb = 0x63;
const uint8_t kNrpnLsb = 0x62;
const uint8_t kAssignableCcA = 0x10;
const uint8_t kAssignableCcB = 0x11;
const uint8_t kAssignableCcC = 0x12;
const uint8_t kAssignableCcD = 0x13;
// A device that responds to MIDI messages should implement this interface.
// Everything is static - this is because the main synth class is a "static
// singleton". Note that this allows all the MIDI processing code to be inlined!
struct MidiDevice {
static void NoteOn(uint8_t channel, uint8_t note, uint8_t velocity) { }
static void NoteOff(uint8_t channel, uint8_t note, uint8_t velocity) { }
static void Aftertouch(uint8_t channel, uint8_t note, uint8_t velocity) { }
static void Aftertouch(uint8_t channel, uint8_t velocity) { }
static void ControlChange(uint8_t channel, uint8_t controller,
uint8_t value) { }
static void ProgramChange(uint8_t channel, uint8_t program) { }
static void PitchBend(uint8_t channel, uint16_t pitch_bend) { }
static void AllSoundOff(uint8_t channel) { }
static void ResetAllControllers(uint8_t channel) { }
static void LocalControl(uint8_t channel, uint8_t state) { }
static void AllNotesOff(uint8_t channel) { }
static void OmniModeOff(uint8_t channel) { }
static void OmniModeOn(uint8_t channel) { }
static void MonoModeOn(uint8_t channel, uint8_t num_channels) { }
static void PolyModeOn(uint8_t channel) { }
static void SysExStart() { }
static void SysExByte(uint8_t sysex_byte) { }
static void SysExEnd() { }
static void BozoByte(uint8_t bozo_byte) { }
static void Clock() { }
static void Start() { }
static void Continue() { }
static void Stop() { }
static void ActiveSensing() { }
static void Reset() { }
static uint8_t CheckChannel(uint8_t channel) { return 1; }
static void RawByte(uint8_t byte) { }
static void RawMidiData(
uint8_t status,
uint8_t* data,
uint8_t data_size,
uint8_t accepted_channel) { }
};
template
class MidiStreamParser {
public:
MidiStreamParser();
void PushByte(uint8_t byte);
private:
void MessageReceived(uint8_t status);
uint8_t running_status_;
uint8_t data_[3];
uint8_t data_size_; // Number of non-status byte received.
uint8_t expected_data_size_; // Expected number of non-status bytes.
DISALLOW_COPY_AND_ASSIGN(MidiStreamParser);
};
template
MidiStreamParser::MidiStreamParser() {
running_status_ = 0;
data_size_ = 0;
expected_data_size_ = 0;
}
template
void MidiStreamParser::PushByte(uint8_t byte) {
// Realtime messages are immediately passed-through, and do not modify the
// state of the parser.
Device::RawByte(byte);
if (byte >= 0xf8) {
MessageReceived(byte);
} else {
if (byte >= 0x80) {
uint8_t hi = byte & 0xf0;
uint8_t lo = byte & 0x0f;
data_size_ = 0;
expected_data_size_ = 1;
switch (hi) {
case 0x80:
case 0x90:
case 0xa0:
case 0xb0:
expected_data_size_ = 2;
break;
case 0xc0:
case 0xd0:
break; // default data size of 1.
case 0xe0:
expected_data_size_ = 2;
break;
case 0xf0:
if (lo > 0 && lo < 3) {
expected_data_size_ = 2;
} else if (lo >= 4) {
expected_data_size_ = 0;
}
break;
}
if (byte == 0xf7) {
if (running_status_ == 0xf0) {
Device::SysExEnd();
}
running_status_ = 0;
} else if (byte == 0xf0) {
running_status_ = 0xf0;
Device::SysExStart();
} else {
running_status_ = byte;
}
} else {
data_[data_size_++] = byte;
}
if (data_size_ >= expected_data_size_) {
MessageReceived(running_status_);
data_size_ = 0;
if (running_status_ > 0xf0) {
expected_data_size_ = 0;
running_status_ = 0;
}
}
}
}
template
void MidiStreamParser::MessageReceived(uint8_t status) {
if (!status) {
Device::BozoByte(data_[0]);
}
uint8_t hi = status & 0xf0;
uint8_t lo = status & 0x0f;
// If this is a channel-specific message, check first that the receiver is
// tune to this channel.
if (hi != 0xf0 && !Device::CheckChannel(lo)) {
Device::RawMidiData(status, data_, data_size_, 0);
return;
}
Device::RawMidiData(status, data_, data_size_, 1);
switch (hi) {
case 0x80:
Device::NoteOff(lo, data_[0], data_[1]);
break;
case 0x90:
if (data_[1]) {
Device::NoteOn(lo, data_[0], data_[1]);
} else {
Device::NoteOff(lo, data_[0], 0);
}
break;
case 0xa0:
Device::Aftertouch(lo, data_[0], data_[1]);
break;
case 0xb0:
switch (data_[0]) {
case 0x78:
Device::AllSoundOff(lo);
break;
case 0x79:
Device::ResetAllControllers(lo);
break;
case 0x7a:
Device::LocalControl(lo, data_[1]);
break;
case 0x7b:
Device::AllNotesOff(lo);
break;
case 0x7c:
Device::OmniModeOff(lo);
break;
case 0x7d:
Device::OmniModeOn(lo);
break;
case 0x7e:
Device::MonoModeOn(lo, data_[1]);
break;
case 0x7f:
Device::PolyModeOn(lo);
break;
default:
Device::ControlChange(lo, data_[0], data_[1]);
break;
}
break;
case 0xc0:
Device::ProgramChange(lo, data_[0]);
break;
case 0xd0:
Device::Aftertouch(lo, data_[0]);
break;
case 0xe0:
Device::PitchBend(lo, (static_cast(data_[1]) << 7) + data_[0]);
break;
case 0xf0:
switch(lo) {
case 0x0:
Device::SysExByte(data_[0]);
break;
case 0x1:
case 0x2:
case 0x3:
case 0x4:
case 0x5:
case 0x6:
// TODO(pichenettes): implement this if it makes sense.
break;
case 0x8:
Device::Clock();
break;
case 0x9:
break;
case 0xa:
Device::Start();
break;
case 0xb:
Device::Continue();
break;
case 0xc:
Device::Stop();
break;
case 0xe:
Device::ActiveSensing();
break;
case 0xf:
Device::Reset();
break;
}
break;
}
}
} // namespace midi
#endif // EDGES_MIDI_H_