///////////////////////////////////////////////////////////////
// Laserharfe mit I2C LCD Display und 2 Taster
// Ulrich Schmerold
// 10/2016
// veröffentlicht in der MAKE 1/2017
///////////////////////////////////////////////////////////////
#include "Wire.h"

const int sensor_pin    = 2;  // Pin für den Lichtsensor
const int laser_pin     = 3;  // Pin für den Lasertreiber
const int galvo_pin2    = 5;  // Pin für Galovo-Spiegel rechts
const int galvo_pin1    = 6;  // Pin für Galovo-Spiegel links
const int button_pin_1  = 11; // Pin für ein Fußpedal (optional)
const int button_pin_2  = 12; // Pin für ein weiteres Fußpedal (optional)

const int   move_delay    = 100; // Wartezeit, bis der Galvo-Spiegel seine Position erreicht hat                 
const float max_value     = 511; // Je größer dieser Wert ist, desto weiter werden die Strahlen aufgefächert (maximal 511!)
const int   laser_on_time = 80;  // Wie lange soll jeder Strahl leuchten

const int  anzahl_beams = 10;        // Anzahl der Harfensaiten
boolean    beam[anzahl_beams];       // In diesem Array werden die Strahlen gespeichert, die gerade reflektiert werden
boolean    beam_saved[anzahl_beams]; // Array zum Vergleichen, ob sich seit dem letzten Durchlauf etwas geändert hat
byte       actual_beam;              // Variable für den aktuell angezeigten Strahl
int        midle = max_value/2;      // Berechnen der Mittelposition des Galvo-Spiegels

int last_button_state_1 = LOW;   
int last_button_state_2 = LOW;   
byte instrument =0;

// -----------  Instrumenten Set ---------------------------------------------------------
// Der erste Wert ist jeweils das Midi-Instrument,
// die weiteren Werte sind die Noten, die den einzelnen Lasersaiten zugeordnet werden
// So lassen sich auch verschiedene Tonarten spielen
const int max_program = 6;
const byte beam_note[max_program][14]= {
{101 ,69,68,67,66,65,64,63,62,61,60,59,58,57},
{19  ,69,68,67,66,65,64,63,62,61,60,59,58,57},
{50  ,69,68,67,66,65,64,63,62,61,60,59,58,57},
{38  ,69,68,67,66,65,64,63,62,61,60,59,58,57},
{34  ,69,68,67,66,65,64,63,62,61,60,59,58,57},
{44  ,69,68,67,66,65,64,63,62,61,60,59,58,57}
};
//------------------------------------------------------------------------------------------

void setup()   {               
  pinMode(sensor_pin, INPUT);
  pinMode(laser_pin, OUTPUT);
  pinMode(galvo_pin1, OUTPUT);
  pinMode(galvo_pin2, OUTPUT);
  pinMode(button_pin_1, INPUT_PULLUP);
  pinMode(button_pin_2, INPUT_PULLUP);

  setPwmFrequency(galvo_pin2, 1);               // Setzt die PWM-Frequenz vom Galvo_pin  auf 62500 Hz (62500/1 = 62500)
  attachInterrupt(0, sensor_interrupt, RISING); // Pin 2 löst am Arduino Nano den INT0 aus
  Serial.begin(31250);                          // Serial-Geschwindigkeit für MIDI-Signale
  
  change_instrument(0);  // Das erste Instrument auswählen
  
  for (int i=0; i<anzahl_beams ; i++) // Initialisierung der Arrays für die Lasersaiten ==> alle erst einmal ausschalten
  {
   beam[i]=0;
   beam_saved[i]=0;
  }
  // Startanimationen ==> können auch auskommentiert werden
  Warten_auf_Startsignal();  // Es wird ein Strahl eingeschaltet und gewartet, bis er reflektiert wird
  Harfe_ausklappen();        // Animation zum Auffächern der Harfensaiten 
}

void loop()
{ 
 for(int i=0 ; i < anzahl_beams ; i++)  beam[i] = false;                      // Array für  MIDI-Signale leeren
 for (actual_beam = 0 ; actual_beam < anzahl_beams; actual_beam++)            // Anzahl der Lasersaiten darstellen
  {
     make_beam(actual_beam);      // Spiegel zur Beam-Position fahren, Laser einschalten, warten, Laser wieder ausschalten
     read_buttons();              // Status der Fußtaster abfragen zum Umschalten der Instrumente
  } 
  make_midi();                    // Erst jetzt werden die gespeicherten Sensorsignale ausgewertet und die MIDI-Signale gesendet
}


void galvo_move (int target)
{  
  int pwm_value;
  
  // Um die volle Auslenkung zu erreichen, müssen wir den Galvo + Pin und den Galvo - Pin bedienen
  if (target <= midle ) pwm_value = midle - target; else pwm_value=0; // Jede Position, die kleiner als die Mittelposition ist, wird an den - Pin gegeben,
   analogWrite(galvo_pin1, pwm_value);                                // PWM Wert setzen
  if (target > midle ) pwm_value = target - midle; else pwm_value=0;  // Jede Position, die größer als die Mittelposition ist, wird an den + Pin gegeben,
   analogWrite(galvo_pin2, pwm_value);                                // PWM Wert setzen
  own_delay(move_delay);                                              // Warten, bis der Spiegel seine Position eingenommen hat (experimentell)
}


void make_beam(int Beam){
   beam[actual_beam]=false;                  // Erst einmal davon ausgeben, dass der Sensor nichts registriert
   galvo_move (max_value/anzahl_beams*Beam); // Den Galvospiegel auf die gewünschte Position bewegen
   laser_on();                               // Laser einschalten
   own_delay(laser_on_time) ;                // Warten...
   laser_off();                              // Laser wieder ausschalten
}

void make_midi()    // Array mit den gespeicherten Lasersaiten durchlaufen und ggf. MIDI-Signale ausgeben
{
 for (int i=0 ; i<anzahl_beams ; i++) 
 {
   if (beam[i] != beam_saved[i]) 
   {
     beam_saved[i] = beam[i] ;
     if (beam[i]==true)  
     {
       play_note(144, (beam_note[instrument][i+1]), 127); 
     } else {
       play_note(144, (beam_note[instrument][i+1]), 0);
     }
   }
 }
}

// Überprüfen, ob einer der (Fuß-)Taster gedrückt wurde
// und ggf. das Instrument wechseln
void read_buttons()
{
  int reading = digitalRead(button_pin_1);
    if (reading != last_button_state_1) 
    {
       last_button_state_1 = reading;
       if (reading == false) change_instrument(++instrument);        
    } 
  reading = digitalRead(button_pin_2);
    if (reading != last_button_state_2) 
    {
       last_button_state_2 = reading;
       if (reading == false) change_instrument(--instrument);        
    } 
}

void change_instrument(int program)
{
    if (program >= max_program)
    { 
        Harfe_einklappen();              // Harfe einklappen
        asm volatile ("  jmp 0");        // Programmablauf von vorne starten (SoftReset)
    }
    Serial.write(0xC0);                  // MIDI Statusbyte Instrumentenwechsel
    Serial.write(beam_note[program][0]); // MIDI Datenbyte
}

// Startanimation
// Es wird ein Strahl eingeschaltet und gewartet bis er reflektiert wird 
void Warten_auf_Startsignal()
{
  actual_beam = 0;
  beam[actual_beam] = false;
  do{
     galvo_move (0);
     laser_on();
     own_delay(laser_on_time) ;
     laser_off();
    }
  while (beam[actual_beam] == false); 
}

// Startanimation
// Die Harfensaiten werden langsam aufgefächert
void Harfe_ausklappen()
{ 
  int beam_position_tmp[anzahl_beams];
  for (int i=0 ; i < anzahl_beams; i++) beam_position_tmp[i] = 0;
  for( int i=0 ; i < max_value ; i++)
  {
   for ( int j=0 ; j< anzahl_beams; j++) 
     if (beam_position_tmp[j] < (max_value/anzahl_beams*j)) beam_position_tmp[j]++;
   for (actual_beam = 0 ; actual_beam<anzahl_beams; actual_beam++)
   { 
     galvo_move (beam_position_tmp[actual_beam]);
     laser_on();
       own_delay(laser_on_time) ;
     laser_off();
   }  
  } 
}

// Schlussanimation
// Die Harfensaiten werden langsam zusammengefahren

void Harfe_einklappen()
{ 
  int beam_position_tmp[anzahl_beams];
  for (int i = 0 ; i < anzahl_beams; i++) beam_position_tmp[i] = max_value/anzahl_beams*i;
  for( int i = 0 ; i < max_value ; i++)
  {
   for ( int j = 0 ; j < anzahl_beams; j++) 
     if (beam_position_tmp[j] >0 ) beam_position_tmp[j]--;
   for (actual_beam = 0 ; actual_beam < anzahl_beams; actual_beam++)
   { 
     galvo_move (beam_position_tmp[actual_beam]);
     laser_on();
       own_delay(laser_on_time) ;
     laser_off();
   }  
  } 
}


// Da wir die PWM-Frequenz erhöhen, ändern sich auch die Zeiten für delay()
// Wir nutzen somit eine eigene Wartefunktion 
void own_delay(int duration)
{
 unsigned long start = millis();
 while  (millis() < (start+duration))  {};
}


void play_note(int cmd, int pitch, int velocity)     // Erzeugt ein MIDI-Signal
{
  Serial.write(cmd);
  Serial.write(pitch);
  Serial.write(velocity);
}

void laser_on()
{
  digitalWrite(laser_pin, HIGH);  
}

void laser_off()
{
  digitalWrite(laser_pin, LOW);
}


/*
  Der folgende Code stammt von der Internetseite   http://playground.arduino.cc/Code/PwmFrequency
  Er verändert die PWM Frequenz mittels eines Teilers
  Dabei gilt es zu beachten, dass anschließend auch Zeitfunktionen wie delay() und millis() nicht mehr korrekt funktionieren
  Genauere Informationen finden Sie auf der genannten Internetseite
*/
void setPwmFrequency(int pin, int divisor) {
  byte mode;
  if(pin == 5 || pin == 6 || pin == 9 || pin == 10) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 64: mode = 0x03; break;
      case 256: mode = 0x04; break;
      case 1024: mode = 0x05; break;
      default: return;
    }
    if(pin == 5 || pin == 6) {
      TCCR0B = TCCR0B & 0b11111000 | mode;
    } else {
      TCCR1B = TCCR1B & 0b11111000 | mode;
    }
  } else if(pin == 3 || pin == 11) {
    switch(divisor) {
      case 1: mode = 0x01; break;
      case 8: mode = 0x02; break;
      case 32: mode = 0x03; break;
      case 64: mode = 0x04; break;
      case 128: mode = 0x05; break;
      case 256: mode = 0x06; break;
      case 1024: mode = 0x7; break;
      default: return;
    }
    TCCR2B = TCCR2B & 0b11111000 | mode;
  }
}


// Interruptbehandlungsroutine für den Sensorinterrupt
// Wird ein Lichtsignal erkannt, wird der Speicherplatz der momentanen Lasersaite auf true gesetzt
void sensor_interrupt() 
{
  beam[actual_beam] = true;
}


