// Copyright 2009 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.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 <http://www.gnu.org/licenses/>.
//
// -----------------------------------------------------------------------------
//
// 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<typename Device>
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<typename Device>
MidiStreamParser<Device>::MidiStreamParser() {
  running_status_ = 0;
  data_size_ = 0;
  expected_data_size_ = 0;
}

template<typename Device>
void MidiStreamParser<Device>::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<typename Device>
void MidiStreamParser<Device>::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<uint16_t>(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_
