javascript:electronic-music-tools:3.18_programming_exercise
                This is an old revision of the document!
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;
    }
  });
};
                    
                                    javascript/electronic-music-tools/3.18_programming_exercise.1500498880.txt.gz · Last modified: 2017/07/19 23:14 by leo