3.17 Final drum machine code - standards compliant version4.1 Algorithmic music learning outcomes

note : bugue si mis en pause je crois (va vouloir rattraper toutes les notes non jouées entre le moment mis en pause et la reprise)

exercice.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>exercice : drum machine</title>
  </head>
  <body>

    <canvas id="impulsions" nx="button" label="impulsions"></canvas>
    <canvas id="matrix" nx="matrix" label="séquenceur" width="500" height="500"></canvas>

    <script src="nexusUI.js" charset="utf-8"></script>
    <script src="Samples_set.js" charset="utf-8"></script>
    <script src="Sequenceur.js" charset="utf-8"></script>
    <script src="impulseur.js" charset="utf-8"></script>
    <script src="main.js" charset="utf-8"></script>

  </body>
</html>

Samples_set.js

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);
    }
  }
};

Sequenceur.js

function Sequenceur ( contexte, base_rythmique, taille, marge ) {
  /* on renseigne une base rythmique en secondes, une intervalle en secondes pour le rafraichissement, et une taille.
  Number, Object, Number, Number, Number, Number  */

  /*
    les paramètres
  */

  // le contexte audio
  this.contexte = contexte;

  // l'élément matrix d'ui
  this.matrix;

  // la base rythmique, une étape toutes les n secondes
  this.base_rythmique = base_rythmique;

  // le nombre d'étapes
  this.taille = taille;

  // la marge qu'on se donne pour le traitement
  this.marge = marge;

  /*
    l'objet séquenceur
  */

  // l'objet séquenceur
  this.sequenceur = {};

  // les séquences
  this.sequenceur.sequences = [];

  // les sets d'instruments
  this.sequenceur.sets = [];

  /*
    variables de travail
    on travaillera en complément de manière local avec les variables locales
    t : le timecode actuel à l'éxécution
    t_max_a_traite : limite temporelle
  */

  // compteur tops
  this.compteur_tops = 0;

  // le précédent top traité
  this.timecode_top_precedent;

  // le prochain top à traiter
  this.timecode_top_suivant = 0;

  this.tmp;

}

Sequenceur.prototype.ajouter_sequence = function ( samples_set, nom ) {
  /* Permet d'ajouter une ligne au séquenceur, liée à une fonction jouant ledit instrument
  Array, Function -> Void */

  // on créé une nouvelle séquence
  var sequence = [];

  // que l'on dimensionne et initialise
  for (var i = 0; i < this.taille; i++) {
    sequence.push(0);
  }

  // on l'ajoute au séquenceur
  this.sequenceur.sequences.push( sequence );

  // ainsi que la référence à l'instrument
  this.sequenceur.sets.push( { samples_set: samples_set, nom: nom} );

  if ( matrix ) {
    matrix.row = this.row();
  }
};

Sequenceur.prototype.impulsion = function ( frequence ) {
  /* Lorsque le séquenceur reçoit une impulsion, il regarde le temps actuel, la limite jusqu'où il va traiter le temps, et tant qu'il trouve des timecode dans cette fenêtre à traiter, il les traite

  La limite se calcule à partir du paramètre en entrée (le nombre de ms entre les impulsions en entrée) et la marge prévue pour éviter les problèmes dus à setInterval.

  À noter : on pourrait avoir des problèmes en cas de réduction de la fréquence

  Number -> Void */
  var t = this.contexte.currentTime;
  var t_max_a_traiter = t + ( frequence * ( 1 + this.marge ) );

  // on met à jour l'ui si applicable
  if ( matrix !== undefined ) {
    this.matrix.jumpToCol( (this.compteur_tops - 1) % this.taille );
  } else { console.log('matrix not defined');}

  // tant que le prochain timecode est dans l'intervalle
  while ( t_max_a_traiter >= this.timecode_top_suivant ) {
    this.traiter_etape();
  }

};

Sequenceur.prototype.traiter_etape = function () {
  /* on traite une étape :
  on prend le prochain timecode, et pour chaque séquence, on regarde s'il y a a quelque chose à déclarer (et si c'est le cas on déclare), puis on met à jour les timecode précédent/suivant ainsi que l'étape */

  // pour tous les sets,

  // on détermine l'étape vis à vis de la taille du contrôleur
  console.log('traiter_etape()');
  var etape = this.compteur_tops % this.taille;
  var t = this.timecode_top_suivant;

  // on traite les différents sets
  for (var i = 0; i < this.sequenceur.sequences.length; i++) {

    if ( this.sequenceur.sequences[ i ][ etape % this.taille ] ) {
      this.sequenceur.sets[ i ].samples_set.jouer( this.sequenceur.sets[ i ].nom, t );
      console.log('jouer', this.sequenceur.sets[ i ].nom, t);
    }

  }

  // on met à jour les timecode
  this.timecode_top_precedent = this.timecode_top_suivant;
  this.timecode_top_suivant += this.base_rythmique;

  // on met à jour le compteur de tops
  this.compteur_tops++;
};

Sequenceur.prototype.entree = function ( data ) {
  this.sequenceur.sequences[ data.row ][ data.col ] = data.level;
};

Sequenceur.prototype.matrix = function ( matrix ) {
  /* configure un élément matrix comme ui */
  this.matrix = matrix;
};

Sequenceur.prototype.col = function () {
  /* Renvoie le nombre de colonnes du séquenceur */
  return this.taille;
};

Sequenceur.prototype.row = function () {
  /* Renvoie le nombre de rangs du séquenceur */
  return this.sequenceur.sequences.length;
};

Sequenceur.prototype.dessiner = function () {
  var output
};

impulseur.js

var impulseur = (function(){
  var composant = {};

  var liste_sequenceurs = [];

  var etape = 0;

  var duree_intervalle = 100; // millisecondes

  var intervalle;

  var actif = false;

  composant.ajouter_sequenceur = function ( sequenceur ) {
    /*  */
    liste_sequenceurs.push( sequenceur );
  }

  function impulsion () {
    /* envoie */

    console.log( 'impulsion envoyée' );

    for (var i = 0; i < liste_sequenceurs.length; i++) {
      liste_sequenceurs[i].impulsion( duree_intervalle / 1000 );
    }

  }

  composant.duree_intervalle = function ( _duree_intervalle ) {
    duree_intervalle = _duree_intervalle * 1000;
  }

  composant.lancer = function () {
    /* lance les impulsions */
    actif = true;
    intervalle = window.setInterval( impulsion, duree_intervalle );
  }

  composant.stopper = function () {
    /* stoppe les impulsions */
    actif = false;
    window.clearInterval ( intervalle );
  }

  composant.actif = function () {
    return actif;
  }

  return composant;
})();

main.js

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

var contexte = new contexte_audio();

/* sample set */


var batterie = new Samples_set( contexte );

batterie.charger( 'HT0D0', 'HT0D0.wav');
batterie.charger( 'HT0D3', 'HT0D3.wav');
batterie.charger( 'HT0D7', 'HT0D7.wav');
batterie.charger( 'HT0DA', 'HT0DA.wav');
batterie.charger( 'HT3D0', 'HT3D0.wav');
batterie.charger( 'HT3D3', 'HT3D3.wav');
batterie.charger( 'HT3D7', 'HT3D7.wav');
batterie.charger( 'HT3DA', 'HT3DA.wav');
batterie.charger( 'HT7D0', 'HT7D0.wav');
batterie.charger( 'HT7D3', 'HT7D3.wav');
batterie.charger( 'HT7D7', 'HT7D7.wav');
batterie.charger( 'HT7DA', 'HT7DA.wav');
batterie.charger( 'HTAD0', 'HTAD0.wav');
batterie.charger( 'HTAD3', 'HTAD3.wav');
batterie.charger( 'HTAD7', 'HTAD7.wav');
batterie.charger( 'HTADA', 'HTADA.wav');

/* */

var intervalle_duree = 0.1;
var base_rythmique = .25;
var marge = 0.5;

var sequenceur = new Sequenceur ( contexte, base_rythmique, 8, marge );

impulseur.ajouter_sequenceur( sequenceur );
impulseur.duree_intervalle( intervalle_duree );

sequenceur.ajouter_sequence( batterie, 'HT0D0');
sequenceur.ajouter_sequence( batterie, 'HT0D3');
sequenceur.ajouter_sequence( batterie, 'HT0D7');
sequenceur.ajouter_sequence( batterie, 'HT0DA');
sequenceur.ajouter_sequence( batterie, 'HT3D0');
sequenceur.ajouter_sequence( batterie, 'HT3D3');
sequenceur.ajouter_sequence( batterie, 'HT3D7');
sequenceur.ajouter_sequence( batterie, 'HT3DA');
sequenceur.ajouter_sequence( batterie, 'HT7D0');
sequenceur.ajouter_sequence( batterie, 'HT7D3');
sequenceur.ajouter_sequence( batterie, 'HT7D7');
sequenceur.ajouter_sequence( batterie, 'HT7DA');
sequenceur.ajouter_sequence( batterie, 'HTAD0');
sequenceur.ajouter_sequence( batterie, 'HTAD3');
sequenceur.ajouter_sequence( batterie, 'HTAD7');
sequenceur.ajouter_sequence( batterie, 'HTADA');

var matrix, impulsions;

nx.onload = function () {
  matrix.row = sequenceur.row();
  matrix.col = sequenceur.col();
  matrix.init();
  sequenceur.matrix( matrix );
  impulsions.on('*', function ( data ) {
    if ( data.press == 1) {
      if ( impulseur.actif() ) {
        impulseur.stopper();
      } else {
        impulseur.lancer();
      }
    }
  });
  matrix.on('*', function ( data ) {

    // comme ligne 122 on utilise jumpToCol, cela fire un event
    // on ne veut toutefois que la ligne suivante ne s'exécute que si on est dans le cas d'un événement qui refile data.row, data.col, data.level, on fait un test
    if ( data.row !== undefined ) {
      //console.log(data);
      sequenceur.entree(data);
      // sequenceur[data.row][data.col] = data.level;
    }
  });
};