/* 23.05.2020
 *
 *  Detection d'un rythme
 *  
 *  A partir d'un signal d'entrée inconnu
 *  (ex : pression sur l'interrupteur, mesure d'une source lumineuse),
 *  determination de la durée d'un temps ( = durée d'un point)
 *  et aide au réglage du rythme de l'appareil
 *  
 *  Le programme s'execute en trois étapes :
 *  1) L'acquisition du signal (i.e. de la durée des segments points et trait)
 *  2) Calcul de la durée d'un temps
 *  3) Réglage par l'utilisateur du potentiomètre du tempo, assisté par la led rythme
 *  
 *  AMELIORATION
 *  - choix par l'utilisateur de la durée d'acquisition
 *  - led témoin à ajouter (différente de la led signal)
 *  - Reglage de la précision
 */

// #### PARAMETRES DE MONTAGES
// a adapter au montage réel

const byte m_pinLed = 2; // branchement de la led
const byte m_pinSwitch = 3; // branchement de l'interrupteur

const byte m_pinLedRythme = 13; // branchement de la led rythme
const byte m_pinPotoRythme = A0; // branchement du potentiomètre



// #### PARAMETRES D'UTILISATIONS
// peuvent être ajustés manuellement

const int m_dt_min = 0; // plus petit dt pouvant être entré par l'utilisateur (en milisecondes)
const int m_dt_max = 500; // plus grand dt .... (en milisecondes)

const float m_tolerence = 0.75; // marge sur la duree du signal (long -> (3 - m_tolerence)*m_dt


const byte m_nbrPoints = 50;
  // nombre de points maximum pour determiner dt
  // determine la taille du tableau stockant les mesures
  // à ajuster pour ne pas utiliser trop de mémoire
 
const unsigned long m_dureeMax = 10000; // duree maximale du test de mesure du rythme (ms)



// #### PARAMETRES INTERNES
// ne pas modifier

unsigned long m_startAt; // date à laquelle la mesure est lancée
unsigned long m_duree[m_nbrPoints]; // duree des ., - au cours du temps
bool m_switchState; // état de l'interrupteur
int m_k = 0; // compte les points acquis

unsigned long m_pressedFor;
  // date a laquelle l'interrupteur a été pressé / relaché
  
unsigned long m_pressedAt;
  // duree pendant laquelle est presse l'interrupteur

unsigned long m_rythmeChangedAt = millis();
  // date a laquelle a été changé l'état de la led pour la dernière fois

bool m_rythmeState = false;

float m_dt;
  // duree d'un temps, correspondant a un point (en milisecondes)
  // calculée par l'entrée du signal inconnu
  


void trierDonnees(unsigned long arr[], int nbrElt = 50){
  /* trie le tableau par ordre ascendant
  
  arr : array[int] -> tableau de donnees
  nbrElt : int, nombre d'élément du tableau
  */

  bool arrChanged = true; // indique que le tableau a subi des modifications
  while (arrChanged){ // tant que le tableau a été modifié au tour précédent
    arrChanged = false; // initialisation au début de tour de l'état des modifications
    for (int k = 0 ; k < nbrElt - 1; k++){
      if (arr[k] > arr[k + 1]){ // inverse les éléments adjacents
        int temp = arr[k]; 
        arr[k] = arr[k+1];
        arr[k+1] = temp;
        arrChanged = true; // indique que le tableau a été modifié
      }
    }
  }
}



void setup() {
  // initialisation des pins
  pinMode(m_pinLed, OUTPUT);
  digitalWrite(m_pinLed, LOW);

  pinMode(m_pinLedRythme, OUTPUT);
  digitalWrite(m_pinLedRythme, m_rythmeState);
  
  pinMode(m_pinSwitch, INPUT);
  
  pinMode(m_pinPotoRythme, INPUT);

  // initialisation du port serie
  Serial.begin(9600);

  // initialisation des variables
  m_switchState = digitalRead(m_pinSwitch);
  m_startAt = millis();
  m_pressedAt = millis();

  // ### ACQUISITON des durees des segments
  
  while (millis() - m_startAt < m_dureeMax && m_k < m_nbrPoints){
    
    bool newSwitchState = digitalRead(m_pinSwitch);
    
    if (newSwitchState != m_switchState){       // si l'état de l'interrupteur a changé
      m_pressedFor = millis() - m_pressedAt;    // calcul de la durée de la pression / relachement
      m_pressedAt = millis();                   // mise a jour de la date de pression et relachement
      digitalWrite(m_pinLed, newSwitchState);   // et de l'état de la led de signal
      if (m_switchState == 1){                  // si l'interrupteur était appuyé
        m_duree[m_k] = m_pressedFor;            // enregistrement de la durée
        m_k++;                                  // incrémentation du nombre de points acquis
      }
    }    
    m_switchState = newSwitchState ;    // mise a jour de l'état de l'interrupteur
  }

  digitalWrite(m_pinLed, LOW);  // a la fin de l'acquisition, la led signal devient inactive
  Serial.println(millis() - m_startAt);
  Serial.println(m_k);

  // ### EXPLOITATION
  /* - tri du tableau par ordre croissant
   * - determintation de l'indice marquant la séparation entre les points et les traits
   * - calcul du temps moyen sur la durée des points et des tirets
   */
 
  // ## les durées des segments sont classées par ordre croissants
  trierDonnees(m_duree, m_k);


  // ## determination de l'indice marquant la séparation entre les points et les traits
  /* la première partie du tableau (jusque limitePointTiret exclu) contiennent la durée d'un point
   * la seconde partie du tableau (à partir de limitePointTiret inclu) contient la durée des traits
   */
  
  int limitePointTiret = 0;
  float ratio = 0;
    /* rapport des durées de deux cases consécutives
     *  entre deux . ou deux - => environ 1
     *  entre . et - => environ 3 
     */

  
  while (ratio < 2 && limitePointTiret < m_k - 1){  // tant que la limite point-tiret n'a pas été trouvée
                                                    // et que le tableau n'a pas été entièrement parcourru
    if (m_duree[limitePointTiret] > m_dt_min){      // evite les rares valeurs abérrantes
      ratio = float(m_duree[limitePointTiret + 1]) / m_duree[limitePointTiret];
    }
    limitePointTiret++;   // incrémentation de l'indice parcourrant le tableau
  }



  // ## calcul du temps moyen à partir de la durée des points et des tirets
  
  float nbrElt = 0; // nombres de temps ayant un signal
                    //(un temps pour un point, trois pour un segment)
  int somme = 0;    // somme des durées des points et des segments
  
  for (int m = 0 ; m < limitePointTiret ; m++){ // ajout des durées des points
    if (m_duree[m] > m_dt_min){ // suppression eventuelle des rebonds
      somme += m_duree[m];
      nbrElt++;
    }
  }
  for (int m = limitePointTiret ; m < m_k ; m++){ // ajout des durées des traits
    somme += m_duree[m] ;
    nbrElt += 3;
  }

  m_dt = somme / nbrElt; // durée moyenne d'un temps
  
  Serial.print("* ryhme calculé (ms) : ");
  Serial.println(m_dt);

}



void loop() {

  // ### REGLAGE ASSISTE par la led rythme du potentiomètre
  
  // mesures des entrées
  int valPoto = analogRead(m_pinPotoRythme);
  
  // calcul du rythme affiché par le potentiomètre
  float dtPoto = map(valPoto, 0, 1024, m_dt_min, m_dt_max);

  // si le rythme affiché par le potentiomètre 
  // est suffisament proche du rythme mesuré
  // la led rythme s'affiche
  if (abs(dtPoto - m_dt)/m_dt < 0.05){  // suffisament proche => moins de 5% d'écart
    digitalWrite(m_pinLedRythme, HIGH);
  } else  
    digitalWrite(m_pinLedRythme, LOW);
}
