|<- [[3.17 Final drum machine code - standards compliant version]]|[[4.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
exercice : drum machine
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;
}
});
};