// Copyright 2013 Emilie Gillet.
//
// Author: Emilie Gillet (emilie.o.gillet@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// See http://creativecommons.org/licenses/MIT/ for more information.
//
// -----------------------------------------------------------------------------
//
// Part.

#ifndef YARNS_PART_H_
#define YARNS_PART_H_

#include <algorithm>

#include "stmlib/stmlib.h"
#include "stmlib/algorithms/voice_allocator.h"
#include "stmlib/algorithms/note_stack.h"

namespace yarns {

class Voice;

const uint8_t kNumSteps = 64;
const uint8_t kMaxNumVoices = 4;

enum ArpeggiatorDirection {
  ARPEGGIATOR_DIRECTION_UP,
  ARPEGGIATOR_DIRECTION_DOWN,
  ARPEGGIATOR_DIRECTION_UP_DOWN,
  ARPEGGIATOR_DIRECTION_RANDOM,
  ARPEGGIATOR_DIRECTION_AS_PLAYED,
  ARPEGGIATOR_DIRECTION_CHORD,
  ARPEGGIATOR_DIRECTION_LAST
};

enum VoiceAllocationMode {
  VOICE_ALLOCATION_MODE_MONO,
  VOICE_ALLOCATION_MODE_POLY,
  VOICE_ALLOCATION_MODE_POLY_CYCLIC,
  VOICE_ALLOCATION_MODE_POLY_RANDOM,
  VOICE_ALLOCATION_MODE_POLY_VELOCITY,
  VOICE_ALLOCATION_MODE_POLY_SORTED,
  VOICE_ALLOCATION_MODE_POLY_UNISON_1,
  VOICE_ALLOCATION_MODE_POLY_UNISON_2,
  VOICE_ALLOCATION_MODE_POLY_STEAL_MOST_RECENT,
  VOICE_ALLOCATION_MODE_LAST
};

enum VoiceAllocationFlags {
  VOICE_ALLOCATION_NOT_FOUND = 0xff
};

enum MidiOutMode {
  MIDI_OUT_MODE_OFF,
  MIDI_OUT_MODE_THRU,
  MIDI_OUT_MODE_GENERATED_EVENTS
};

enum TuningSystem {
  TUNING_SYSTEM_EQUAL,
  TUNING_SYSTEM_JUST_INTONATION,
  TUNING_SYSTEM_PYTHAGOREAN,
  TUNING_SYSTEM_QUARTER_EB,
  TUNING_SYSTEM_QUARTER_E,
  TUNING_SYSTEM_QUARTER_EA,
  TUNING_SYSTEM_RAGA_1,
  TUNING_SYSTEM_RAGA_27 = TUNING_SYSTEM_RAGA_1 + 26,
  TUNING_SYSTEM_CUSTOM,
  TUNING_SYSTEM_LAST
};

struct MidiSettings {
  uint8_t channel;
  uint8_t min_note;
  uint8_t max_note;
  uint8_t min_velocity;
  uint8_t max_velocity;
  uint8_t out_mode;
  uint8_t padding[10];
};

struct VoicingSettings {
  uint8_t allocation_mode;
  uint8_t allocation_priority;
  uint8_t portamento;
  uint8_t legato_mode;
  uint8_t pitch_bend_range;
  uint8_t vibrato_range;
  uint8_t modulation_rate;
  int8_t tuning_transpose;
  int8_t tuning_fine;
  int8_t tuning_root;
  uint8_t tuning_system;
  uint8_t trigger_duration;
  uint8_t trigger_scale;
  uint8_t trigger_shape;
  uint8_t aux_cv;
  uint8_t audio_mode;
  uint8_t aux_cv_2;
  uint8_t tuning_factor;
  uint8_t padding[14];
};


enum PartSetting {
  PART_MIDI_CHANNEL,
  PART_MIDI_MIN_NOTE,
  PART_MIDI_MAX_NOTE,
  PART_MIDI_MIN_VELOCITY,
  PART_MIDI_MAX_VELOCITY,
  PART_MIDI_OUT_MODE,
  PART_MIDI_LAST = PART_MIDI_CHANNEL + sizeof(MidiSettings) - 1,
  PART_VOICING_ALLOCATION_MODE,
  PART_VOICING_ALLOCATION_PRIORITY,
  PART_VOICING_PORTAMENTO,
  PART_VOICING_LEGATO_MODE,
  PART_VOICING_PITCH_BEND_RANGE,
  PART_VOICING_VIBRATO_RANGE,
  PART_VOICING_MODULATION_RATE,
  PART_VOICING_TUNING_TRANSPOSE,
  PART_VOICING_TUNING_FINE,
  PART_VOICING_TUNING_ROOT,
  PART_VOICING_TUNING_SYSTEM,
  PART_VOICING_TRIGGER_DURATION,
  PART_VOICING_TRIGGER_SCALE,
  PART_VOICING_TRIGGER_SHAPE,
  PART_VOICING_AUX_CV,
  PART_VOICING_AUDIO_MODE,
  PART_VOICING_AUX_CV_2,
  PART_VOICING_TUNING_FACTOR,
  PART_VOICING_LAST = PART_VOICING_ALLOCATION_MODE + sizeof(VoicingSettings) - 1,
  PART_SEQUENCER_CLOCK_DIVISION,
  PART_SEQUENCER_GATE_LENGTH,
  PART_SEQUENCER_ARP_RANGE,
  PART_SEQUENCER_ARP_DIRECTION,
  PART_SEQUENCER_ARP_PATTERN,
  PART_SEQUENCER_EUCLIDEAN_LENGTH,
  PART_SEQUENCER_EUCLIDEAN_FILL,
  PART_SEQUENCER_EUCLIDEAN_ROTATE
};

enum SequencerStepFlags {
  SEQUENCER_STEP_REST = 0x80,
  SEQUENCER_STEP_TIE = 0x81
};

struct SequencerStep {
  // BYTE 0:
  // 0x00 to 0x7f: note
  // 0x80: rest
  // 0x81: tie
  //
  // BYTE 1:
  // 7 bits of velocity + 1 bit for slide flag.
  SequencerStep() { }
  SequencerStep(uint8_t data_0, uint8_t data_1) {
    data[0] = data_0;
    data[1] = data_1;
  }
  
  uint8_t data[2];
  
  inline bool has_note() const { return !(data[0] & 0x80); }
  inline bool is_rest() const { return data[0] == SEQUENCER_STEP_REST; }
  inline bool is_tie() const { return data[0] == SEQUENCER_STEP_TIE; }
  inline uint8_t note() const { return data[0] & 0x7f; }
  
  inline bool is_slid() const { return data[1] & 0x80; }
  inline uint8_t velocity() const { return data[1] & 0x7f; }
};

struct SequencerSettings {
  uint8_t clock_division;
  uint8_t gate_length;
  uint8_t arp_range;
  uint8_t arp_direction;
  uint8_t arp_pattern;
  uint8_t euclidean_length;
  uint8_t euclidean_fill;
  uint8_t euclidean_rotate;
  uint8_t num_steps;
  SequencerStep step[kNumSteps];
  uint8_t padding[7];
  
  int16_t first_note() {
    for (uint8_t i = 0; i < num_steps; ++i) {
      if (step[i].has_note()) {
        return step[i].note();
      }
    }
    return 60;
  }
};

class Part {
 public:
  Part() { }
  ~Part() { }
  
  void Init();
  
  // The return value indicates whether the message can be forwarded to the
  // MIDI out (soft-thru). For example, when the arpeggiator is on, NoteOn
  // or NoteOff can return false to make sure that the chord that triggers
  // the arpeggiator does not find its way to the MIDI out. Instead it will 
  // be sent note by note within InternalNoteOn and InternalNoteOff.
  //
  // Also, note that channel / keyrange / velocity range filtering is not
  // applied here. It is up to the caller to call accepts() first to check
  // whether the message should be sent to the part.
  bool NoteOn(uint8_t channel, uint8_t note, uint8_t velocity);
  bool NoteOff(uint8_t channel, uint8_t note);
  bool ControlChange(uint8_t channel, uint8_t controller, uint8_t value);
  bool PitchBend(uint8_t channel, uint16_t pitch_bend);
  bool Aftertouch(uint8_t channel, uint8_t note, uint8_t velocity);
  bool Aftertouch(uint8_t channel, uint8_t velocity);
  void AllNotesOff();
  void Reset();
  void Clock();
  void Start(bool started_by_keyboard);
  void Stop();
  void StopRecording() {
    seq_recording_ = false;
  }
  void StartRecording();
  
  inline void RecordStep(const SequencerStep& step) {
    if (seq_recording_) {
      seq_.step[seq_rec_step_].data[0] = step.data[0];
      seq_.step[seq_rec_step_].data[1] |= step.data[1];
      ++seq_rec_step_;
      uint8_t last_step = seq_overdubbing_ ? seq_.num_steps : kNumSteps;
      // Extend sequence.
      if (!seq_overdubbing_ && seq_rec_step_ > seq_.num_steps) {
        seq_.num_steps = seq_rec_step_;
      }
      // Wrap to first step.
      if (seq_rec_step_ >= last_step) {
        seq_rec_step_ = 0;
      }
    }
  }

  inline void ModifyNoteAtCurrentStep(uint8_t note) {
    if (seq_recording_) {
      seq_.step[seq_rec_step_].data[0] = note;
    }
  }
  
  inline void RecordStep(SequencerStepFlags flag) {
    RecordStep(SequencerStep(flag, 0));
  }
  
  inline bool accepts(uint8_t channel) const {
    if (!(transposable_ || seq_recording_)) {
      return false;
    }
    return midi_.channel == 0x10 || midi_.channel == channel;
  }
  
  inline bool accepts(uint8_t channel, uint8_t note) const {
    if (!accepts(channel)) {
      return false;
    }
    if (midi_.min_note <= midi_.max_note) {
      return note >= midi_.min_note && note <= midi_.max_note;
    } else {
      return note <= midi_.max_note || note >= midi_.min_note;
    }
  }
  
  inline bool accepts(uint8_t channel, uint8_t note, uint8_t velocity) const {
    return accepts(channel, note) && \
        velocity >= midi_.min_velocity && \
        velocity <= midi_.max_velocity;
  }
  
  void AllocateVoices(Voice* voice, uint8_t num_voices, bool polychain);
  inline void set_custom_pitch_table(int8_t* table) {
    custom_pitch_table_ = table;
  }
  
  inline uint8_t tx_channel() const {
    return midi_.channel == 0x10 ? 0 : midi_.channel;
  }
  inline bool direct_thru() const {
    return midi_.out_mode == MIDI_OUT_MODE_THRU && !polychained_;
  }
  
  inline uint8_t FindVoiceForNote(uint8_t note) const {
    for (uint8_t i = 0; i < num_voices_; ++i) {
      if (active_note_[i] == note) {
        return i;
      }
    }
    return VOICE_ALLOCATION_NOT_FOUND;
  }
  
  void Set(uint8_t address, uint8_t value);
  inline uint8_t Get(uint8_t address) const {
    const uint8_t* bytes;
    bytes = static_cast<const uint8_t*>(static_cast<const void*>(&midi_));
    return bytes[address];
  }

  inline const MidiSettings& midi_settings() const { return midi_; }
  inline const VoicingSettings& voicing_settings() const { return voicing_; }
  inline const SequencerSettings& sequencer_settings() const { return seq_; }
  inline MidiSettings* mutable_midi_settings() { return &midi_; }
  inline VoicingSettings* mutable_voicing_settings() { return &voicing_; }
  inline SequencerSettings* mutable_sequencer_settings() { return &seq_; }

  inline bool has_notes() const { return pressed_keys_.size() != 0; }
  
  inline bool recording() const { return seq_recording_; }
  inline bool overdubbing() const { return seq_overdubbing_; }
  inline uint8_t recording_step() const { return seq_rec_step_; }
  inline uint8_t num_steps() const { return seq_.num_steps; }
  inline void set_recording_step(uint8_t n) { seq_rec_step_ = n; }
  
  void Touch() {
    TouchVoices();
    TouchVoiceAllocation();
  }
  
  inline void Latch() {
    ignore_note_off_messages_ = true;
    release_latched_keys_on_next_note_on_ = true;
  }
  inline void Unlatch() {
    ignore_note_off_messages_ = false;
    release_latched_keys_on_next_note_on_ = true;
  }
  
  void set_siblings(bool has_siblings) {
    has_siblings_ = has_siblings;
  }
  
  void set_transposable(bool transposable) {
    transposable_ = transposable;
  }
  
 private:
  int16_t Tune(int16_t note);
  void ResetAllControllers();
  void TouchVoiceAllocation();
  void TouchVoices();
  void InternalNoteOn(uint8_t note, uint8_t velocity);
  void InternalNoteOff(uint8_t note);
  
  void ReleaseLatchedNotes();
  void DispatchSortedNotes(bool unison);
  void KillAllInstancesOfNote(uint8_t note);
  
  void ClockSequencer();
  void ClockArpeggiator();
  void StopSequencerArpeggiatorNotes();

  MidiSettings midi_;
  VoicingSettings voicing_;
  SequencerSettings seq_;
  
  Voice* voice_[kMaxNumVoices];
  int8_t* custom_pitch_table_;
  uint8_t num_voices_;
  bool polychained_;
  
  bool ignore_note_off_messages_;
  bool release_latched_keys_on_next_note_on_;
  
  stmlib::NoteStack<12> pressed_keys_;
  stmlib::NoteStack<12> generated_notes_;  // by sequencer or arpeggiator.
  stmlib::NoteStack<12> mono_allocator_;
  stmlib::VoiceAllocator<kMaxNumVoices * 2> poly_allocator_;
  uint8_t active_note_[kMaxNumVoices];
  uint8_t cyclic_allocation_note_counter_;
  
  uint8_t arp_seq_prescaler_;
  
  uint8_t arp_step_;
  int8_t arp_note_;
  int8_t arp_octave_;
  int8_t arp_direction_;
  
  bool seq_running_;
  bool seq_recording_;
  bool seq_overdubbing_;
  uint8_t seq_step_;
  uint8_t seq_rec_step_;
  
  uint16_t gate_length_counter_;
  uint16_t lfo_counter_;
  
  bool has_siblings_;
  bool transposable_;
  
  DISALLOW_COPY_AND_ASSIGN(Part);
};

}  // namespace yarns

#endif // YARNS_PART_H_
