/*
 *  VERSIONS LOG
 *
 *  0.1   - display of noteOn, noteOff, and ControlChange MIDI messages
 *
 *  0.2   - generation of the note CV (DAC0)
 *        - generation of the velocity CV (DAC1)
 *        - generation of the GATE signal (D24)
 */


#include <UHS2-MIDI.h>
#include <LiquidCrystal.h>

#define DEBUG 1     // serial monitor active
//#define DEBUG 0   // serial monitor disabled

// Conversion table
// MIDI Note number -> 12 bit DAC input value
const int MIDI2DAC[]
{
  0x0000, 0x0020, 0x0040, 0x0060, 0x0080, 0x00A1, 0x00C1, 0x00E1, 0x0101, 0x0122, 0x0142, 0x0162,  // octave -1
  0x0182, 0x01A3, 0x01C3, 0x01E3, 0x0203, 0x0224, 0x0244, 0x0264, 0x0284, 0x02A5, 0x02C5, 0x02E5,  // octave 0
  0x0305, 0x0326, 0x0346, 0x0366, 0x0386, 0x03A7, 0x03C7, 0x03E7, 0x0407, 0x0428, 0x0448, 0x0468,  // octave 1
  0x0488, 0x04A9, 0x04C9, 0x04E9, 0x0509, 0x052A, 0x054A, 0x056A, 0x058A, 0x05AA, 0x05CB, 0x05EB,  // octave 2
  0x060B, 0x062B, 0x064C, 0x066C, 0x068C, 0x06AC, 0x06CD, 0x06ED, 0x070D, 0x072D, 0x074E, 0x076E,  // octave 3
  0x078E, 0x07AE, 0x07CF, 0x07EF, 0x080F, 0x082F, 0x0850, 0x0870, 0x0890, 0x08B0, 0x08D1, 0x08F1,  // octave 4
  0x0911, 0x0931, 0x0952, 0x0972, 0x0992, 0x09B2, 0x09D3, 0x09F3, 0x0A13, 0x0A33, 0x0A54, 0x0A74,  // octave 5
  0x0A94, 0x0AB4, 0x0AD4, 0x0AF5, 0x0B15, 0x0B35, 0x0B55, 0x0B76, 0x0B96, 0x0BB6, 0x0BD6, 0x0BF7,  // octave 6
  0x0C17, 0x0C37, 0x0C57, 0x0C78, 0x0C98, 0x0CB8, 0x0CD8, 0x0CF9, 0x0D19, 0x0D39, 0x0D59, 0x0D7A,  // octave 7
  0x0D9A, 0x0DBA, 0x0DDA, 0x0DFB, 0x0E1B, 0x0E3B, 0x0E5B, 0x0E7C, 0x0E9C, 0x0EBC, 0x0EDC, 0x0EFD,  // octave 8
  0x0F1D, 0x0F3D, 0x0F5D, 0x0F7E, 0x0F9E, 0x0FBE, 0x0FDE, 0x0FFF                                   // octave 9
};



// Define Usb object, able to read on all channels
USB Usb;
UHS2MIDI_CREATE_DEFAULT_INSTANCE(&Usb);

// Define 2x16 LCD unit using the following pins:
//
// LCD RS => digital pin 23
// LCD EN => digital pin 25     (Enable)
// LCD D4 => 27
// LCD D5 => 29
// LCD D6 => 31
// LCD D7 => 33
// LCD R/W => GND               (ground)
// LCD VSS => GND               (ground)
// LCD VDD => +5V
// LCD contrast: 10k lin pot:
//               end pin 1 => GND
//               end pin 2 => +5V
//               center pin => LCD pin 3  (cursor/wiper)
const int lcd_rows = 2;     // number of lcd rows
const int lcd_cols = 16;    // number of lcd columns
const int lcd_RS = 23;
const int lcd_EN = 25;
const int lcd_D4 = 27;
const int lcd_D5 = 29;
const int lcd_D6 = 31;
const int lcd_D7 = 33;
LiquidCrystal lcd(lcd_RS, lcd_EN, lcd_D4, lcd_D5, lcd_D6, lcd_D7);

///////////////////////////////////////
// general constants
///////////////////////////////////////
const int DACS_RESOLUTION = 12;         	// DACs resolution
const int NOTE_CV  = DAC0;              	// DAC number for note CV
const int VELOCITY_CV = DAC1;           	// DAC number for velocity CV
const int GATE_PIN = 24;                	// Digital pin used to generate the gate signal
const long VELOCITY_MULTIPLIER = 4097/127;	// conversion factor for velocity input on DAC1 (0x0FFF/0x7F)

///////////////////////////////////////
// global variables
///////////////////////////////////////
int active_notes;


/////////////////////
// USB MIDI handlers
/////////////////////
void handleNoteOff(byte channel, byte pitch, byte velocity)
{
  if (DEBUG == 1)
  {
    Serial.print("Received Note Off: ");
    Serial.print(channel, HEX);
    Serial.print(" ");
    Serial.print(pitch, HEX);
    Serial.print(" ");
    Serial.println(velocity, HEX);
  }

  // print received info on LCD
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Note Off - ch ");
  lcd.print(channel, HEX);
  lcd.setCursor(0,1);
  lcd.print("pitch ");
  lcd.print(pitch, HEX);
  lcd.print(" vel.");
  lcd.print(velocity, HEX);

  // terminate note and gate
  if (active_notes >0)
  {
    active_notes--;
  }
  analogWrite(NOTE_CV, MIDI2DAC[0]);
  if (active_notes == 0)
  {
    digitalWrite(GATE_PIN, LOW);
  }
}

void handleNoteOn(byte channel, byte pitch, byte velocity)
{
  long mapped_velocity;

  if (DEBUG ==1)
  {
    Serial.print("Received Note On: ");
    Serial.print(channel, HEX);
    Serial.print(" ");
    Serial.print(pitch, HEX);
    Serial.print(" ");
    Serial.println(velocity, HEX);
  }

  // print received info on LCD
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Note On  - ch ");
  lcd.print(channel, HEX);
  lcd.setCursor(0,1);
  lcd.print("pitch ");
  lcd.print(pitch, HEX);
  lcd.print(" vel.");
  lcd.print(velocity, HEX);

  // generate note, velocity, gate
  active_notes++;
  mapped_velocity = velocity * VELOCITY_MULTIPLIER;
  analogWrite(NOTE_CV, MIDI2DAC[pitch]);
  digitalWrite(GATE_PIN, HIGH);
  analogWrite(VELOCITY_CV, mapped_velocity);
  if (DEBUG ==1)
  {
	  Serial.print("Pitch = ");
	  Serial.print(pitch, HEX);
	  Serial.print("	Velocity == ");
	  Serial.println(mapped_velocity, HEX);
  }
}

void handleControlChange(byte channel, byte data1, byte data2)
{
  if (DEBUG == 1)
  {
    Serial.print("Received Control change message: ");
    Serial.print(channel, HEX);
    Serial.print(" ");
    Serial.print(data1, HEX);
    Serial.print(" ");
    Serial.println(data2, HEX);
  }

  // print received info on LCD
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Ctrl Chg - ch ");
  lcd.print(channel, HEX);
  lcd.setCursor(0,1);
  lcd.print("dat1 ");
  lcd.print(data1, HEX);
  lcd.print(" dat2 ");
  lcd.print(data2, HEX);
}


///////////////////////////
// Main Arduino Functions
///////////////////////////

void setup()
{
  // Serial communications initialization
  if (DEBUG == 1)
  {
    Serial.begin(9600);
  }

  // LCD initialization
  lcd.begin(lcd_cols,lcd_rows);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Initializing...");

  // MIDI callback functions definition
  MIDI.setHandleNoteOff(handleNoteOff);
  MIDI.setHandleNoteOn(handleNoteOn);
  MIDI.setHandleControlChange(handleControlChange);

  // MIDI initialization
  MIDI.begin(MIDI_CHANNEL_OMNI);  // use omni channel

  // USB Host interface initialization
  if (Usb.Init() == -1)
  {
    if (DEBUG == 1)
    {
      Serial.println("Failed to initialize USB HOST");
    }

      lcd.clear();
      lcd.print("Init Error");

    while(1);
  }

  // set DACs resolution
  analogWriteResolution(DACS_RESOLUTION);

  // pin settings
  pinMode(GATE_PIN, OUTPUT);
  pinMode(NOTE_CV, OUTPUT);
  pinMode(VELOCITY_CV, OUTPUT);

  // initialize DAC outputs to 0V
  analogWrite(NOTE_CV, 0);
  analogWrite(VELOCITY_CV, 0);

  // global variables intialization
  active_notes = 0;

  // give some time to the attached devices
  // to complete the initialization
  delay(1000);

  if (DEBUG == 1)
  {
    Serial.println("Initialization completed");
  }

  // tell user that initialization is completed
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("Initialization");
  lcd.setCursor(0, 1);
  lcd.print("completed");
}

void loop()
{
  Usb.Task();   // USB Host main task
  MIDI.read();  // Polling MIDI inputs
}

// CALLBACKS REFERENCE (doc)
/*
ErrorCallback                = void (*)(int8_t);
NoteOffCallback              = void (*)(Channel channel, byte note, byte velocity);
NoteOnCallback               = void (*)(Channel channel, byte note, byte velocity);
AfterTouchPolyCallback       = void (*)(Channel channel, byte note, byte velocity);
ControlChangeCallback        = void (*)(Channel channel, byte, byte);
ProgramChangeCallback        = void (*)(Channel channel, byte);
AfterTouchChannelCallback    = void (*)(Channel channel, byte);
PitchBendCallback            = void (*)(Channel channel, int);
SystemExclusiveCallback      = void (*)(byte * array, unsigned size);
TimeCodeQuarterFrameCallback = void (*)(byte data);
SongPositionCallback         = void (*)(unsigned beats);
SongSelectCallback           = void (*)(byte songnumber);
TuneRequestCallback          = void (*)(void);
ClockCallback                = void (*)(void);
StartCallback                = void (*)(void);
TickCallback                 = void (*)(void);
ContinueCallback             = void (*)(void);
StopCallback                 = void (*)(void);
ActiveSensingCallback        = void (*)(void);
SystemResetCallback          = void (*)(void);
*/
