3.10 Multiple sounds3.14 Visual step display

[code](https://live.codecircle.com/d/PGr4xwdqbzmwqqhZs)

https://live.codecircle.com/d/9k8raocXrwGw7mP9J

html

<canvas id="matrix" nx="matrix" label="séquenceur"></canvas>

<br>

<!-- bouton pour activer le son sur mobile -->
<canvas id="son_mobile" nx="button" label="mobile ♪"></canvas>

javascript

function Samples_set ( contexte ) {
  /* Set de samples permettant de charger simplement des samples et de les jouer */
  this.contexte = contexte;
  this.samples_set = {};
}

Samples_set.prototype.charger = function ( nom, url ) {
  /* Charge le sample et l'ajoute au set
  String, String -> Void */

  // on créé une référence à this (notre occurence de Sample_set)
  var self = this;

  // on créé l'objet requête
  var requete = new XMLHttpRequest();

  // que l'on paramètre :

  // 1 : requête GET, url de la requête, requête asynchrone : true
  requete.open('GET', url, true);

  // 2 : type de réponse
  requete.responseType = 'arraybuffer';

  // 3 : écouteur onload et fonction à exécuter alors
  requete.onload = function () {

    // les données audio
    var donnees_audio = requete.response;

    // sont passées pour décodage au contexte audio
    contexte.decodeAudioData( donnees_audio, function( buffer ) {

      // on ajoute le buffer à notre objet instruments
      self.samples_set[nom] = buffer;
    });
  };

  // on envoie la requête
  requete.send();
};

Samples_set.prototype.jouer = function ( nom, instant ) {
  /* Joue le sample à un instant t s'il est trouvé
    String, Number -> Void */

  // si le sample existe, a été chargé
  if ( this.samples_set[nom] ) {
    try {

      //on créé un BufferSource
      var player = contexte.createBufferSource();

      // on y charge notre sample
      player.buffer = this.samples_set[nom];

      // on spécifie qu'on ne le joue pas en boucle
      player.loop = false;

      // on le connecte au contexte
      player.connect(this.contexte.destination);

      // on le lancera à l'instant t
      player.start( instant );

    // en cas d'erreur
    } catch (e) {
      console.error('erreur : Samples_set.jouer :', e);
    }
  }
};

var contexte_audio = window.AudioContext || window.webkitAudioContext;

var contexte = new contexte_audio();

var batterie = new Samples_set( contexte );

batterie.charger( 'charleston', 'cl_hat_3001.wav');
batterie.charger( 'caisse claire', 'brp_snrim1.wav');

// la séquence que l'on souhaite jouer
var sequenceur = [
  [0,0,0,0],
  [0,0,0,0],
];

// référence à l'étape où l'on en est dans la séquence
var etape = 0;

// base_rythmique : temps entre deux étapes de la séquence *en secondes*
// car on va se baser sur le temps tel que donné par l'API audio en secondes
var base_rythmique = 0.125;

// intervalle entre deux appel à setInterval
// on continue à travailler en secondes et on multipliera pour setInterval qui prends de millisecondes en paramètre
var intervalle = 0.5;

// référence à l'instant t max déjà traité
var t_max_traite;

// toutes les 0.5 secondes (intervalle)
setInterval( function(){

  // on va travailler à partir de l'instant t qui est égal à maintenant
  var t = contexte.currentTime;

  // on va déclarer tous les événement entre t et t_max qui est égal à 1.5 fois le temps entre deux base_rythmique
  // cela nous donne une marge de jusqu'à 0.5 fois notre base_rythmique
  // pour absorber les cas où setInterval serait en retard
  // si on change quelque chose on aura aussi une latence allant jusqu'à intervalle * 1.5
  var t_max = t + (intervalle * 1.5);

  // si on a déjà traité au-delà de t
  if ( t_max_traite > t ) {

    // on passe déjà à l'instant t où l'on avait précédemment déjà été
    t = t_max_traite;
  }

  // de l'instant t à notre limite t_max
  while ( t <= t_max ){
    // on passe à l'étape suivant dans notre séquenceur
    etape ++;

    // si on doit jouer le sample à cette étape
    if ( sequenceur[0] [ etape % sequenceur[0].length ] ) {

      // on programme cela pour l'instant t correspondant
      batterie.jouer('charleston', t);
    }

    // si on doit jouer le sample à cette étape
    if ( sequenceur[1] [ etape % sequenceur[1].length ] ) {

      // on programme cela pour l'instant t correspondant
      batterie.jouer('caisse claire', t);
    }

    // on incrémente t pour passer à l'étape suivant
    t += base_rythmique;
  }

  // quant on a fini, on garde en mémoire le dernier instant t traité
  t_max_traite = t;

/// tous les intervalle * 1000 car setInterval prend en paramètre des millisecondes et intervalle est en secondes
}, intervalle * 1000);

var matrix, son_mobile;

nx.onload = function () {
  matrix.row = sequenceur.length;
  matrix.col = sequenceur[0].length;
  matrix.init();
  matrix.on('*', function ( data ) {
    sequenceur[data.row][data.col] = data.level;
  });
  // sur mobile, on doit déclencher un son avec une action utilisateur avant de pouvoir utiliser le son
  son_mobile.on('*', function(){
        batterie.jouer('charleston', contexte.currentTime);
  });
};