• 12 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 20/11/2019

Développez l’interface du pad musical

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Ce document vous guidera pas-à-pas dans le développement de l’interface. Le code vous est donné par morceaux. Nos recommandations sont d’y aller progressivement, en testant votre application à chaque étape.

Écran d’accueil

Au départ, tous les éléments potentiels s'affichent : le titre, une image et le bouton. Ceci se fait grâce à une fonction  afficheEcranAccueil(), qui affichera seulement l'écran d'accueil.

Comme ces éléments ne changeront pas lors de l’utilisation de l’interface, nous les avons définis dans le fichier HTML.

<body>
<div id="ecranAccueil">
<div id="titre">MusicPad</div>
<div id="imageIntro">ici une image ou un logo</div>
<div id="boutonAcces"><input type="submit" value="Acceder"></div>
</div>
<div id="ecranJeu">
<div id="samples"></div>
<div id="padControlsVisu">
<div id="padControls">
<div id="controlsL">(controles gauche)</div>
<div id="pad">
<div id="padL"></div>
<div id="padR"></div>
</div>
<div id="controlsR">(controlsR)</div>
</div>
<div id="visu">
<canvas id="visuCanvas">
Ce navigateur ne supporte pas la balise canvas.
</canvas>
</div>
</div>
<div id="boutonQuitter"><input type="submit" value="Quitter"></div>
</div>
<div id="ecranFin">
<div id="imageFin">ici une image ou un logo</div>
</div>

Il n'y a pas de règle pour cet écran.

Seule fonctionnalité de cet écran : si le bouton #boutonJeu est cliqué, alors on passe à l'écran de jeu. Pour assurer cette fonctionnalité, on installe un gestionnaire sur le bouton #boutonJeu.

// gestionnaires
$('#boutonAcces').click(function() {
afficheEcranJeu();
});

Que fait ce gestionnaire ? En cas de clic, il affiche l'écran de jeu. Ceci se fait grâce à une fonction  afficheEcranJeu()  qui masquera l'écran d'accueil et affichera l'écran de jeu.

function afficheEcranAccueil(){
// affichage de l'écran et masquage des autres écrans
ecranCourant = "ecranAccueil";
$('#ecranAccueil').show();
$('#ecranJeu').hide();
$('#ecranFin').hide();
}

Écran de jeu

Du point de vue HTML :

  • nous créons la liste de samples : une boîte de type div, contenant une liste verticale de div portant les identifiants sample1, sample2, etc. ; afin de dupliquer à la main les parties de code HTML, ceci peut opportunément être créé grâce au code JavaScript ;  une classe sample est introduite, elle permet de définir l’aspect de chacune de ces boîtes.

Code JS :

// structure
nombreSamples = 6;
t = "";
for (var i = 1; i<=nombreSamples; i++) {
t += "<div id=\"sample"+i+"\" class=\"sample\" draggable=\"true\">Sample "+i+"</div>";
}
$("#samples").html(t);

Bien entendu, à chaque sample sera associé un son.

Je vous propose de le faire à travers un tableau listeSons et de le déclarer dans la partie “données/paramètres” du code JavaScript ;

listeSons = {"1" : "hat.mp3", "2" : "kick.mp3", "3" : "snare.mp3", "4" : "tin.mp3"};
  • une zone portant l’identifiant #padControlsVisu est créée. Elle contient :

    • la première zone de contrôle, c’est-à-dire une boîte de type div, portant l’identifiant #controlsL, positionnée en ligne à droite de la liste de samples et contenant une liste verticale de 2 div portant les identifiants #controlsTL et #controlsBL,

    • le pad, consistant en une boîte de type div, positionnée en ligne à droite de la première zone de contrôle et contenant 4 div nommés #boutonTL, #boutonTR, #boutonBL et #boutonBR,

    • la seconde zone de contrôle est une boîte de type div, portant l’identifiant #controlsR, positionnée en ligne à droite du pad et contenant une liste verticale de 2 div portant les identifiants #controlsTL et controlsBL,

    • la zone de visualisation : un canvas nommé #visu, de taille 500*200 et situé en dessous des 3 boîtes précédentes.

Pour ces éléments, et pour éviter de reproduire des lignes de code HTML, il est possible de créer le tableau  controlsArray  présenté ci-dessous et de créer dynamiquement le code HTML correspondant. Voici le code JS :

// controles et pad
controlsArray = {"L" : ["TL", "BL"], "R" : ["TR", "BR"]};
c = "";
p = "";
for (var p in controlsArray) {
textC = "";
textP = "";
for (var pp in controlsArray[p]){
l = controlsArray[p][pp]
// controle courant
inter = "<input type='checkbox' class='cb' id='cbLoop"+l+"'>Loop<br>";
inter += "<p>Tempo</p>"
inter += "<div id='sliderTempo"+l+"' class='padSlider'></div>";
inter += "<p>Volume</p>"
inter += "<div id='sliderVolume"+l+"' class='padSlider'></div>";
textC += "<div id=\"controls"+l+"\" class=\"controls\">Controls "+l+"<br>"+inter+"</div>";
// pad courant
textP += "<div id=\"pad"+l+"\" class=\"pad\">Pad "+l+"</div>";
}
$("#controls"+p).html(textC);
$("#pad"+p).html(textP);
}

Et voici le code CSS pour la partie layout :

#samples, #padControlsVisu, #controlsL, #pad, #controlsR {
display: inline-block;
}
#padL, #padR {
display: inline-block;
}
  • enfin, on crée le bouton permettant de quitter l’interface, situé encore en dessous et nommé #boutonQuitter.

Aucune difficulté pour créer ces éléments dans le fichier HTML avec un peu de CSS.

À ce stade,  nous vous proposons de vérifier l’affichage.

Structure écran
Structure écran

Intéressons-nous maintenant aux zones de contrôle. Chaque zone contient :

  • une case à cocher ;

  • un slider pour le tempo ;

  • un slider pour le volume.

La case à cocher est un objet HTML classique :

<input type='checkbox' class='cb' id='cbLoop"+l+"'>Loop

Pour le slider, nous allons utiliser un composant jQuery UI. Pour ce faire, il suffit :

  • de prévoir dans le code HTML un div pour ce slider ;

<div id=”sliderTempoTL”></div>
  •  d’instancier le composant slider depuis le code JavaScript ; ainsi pour un seul slider :

$( #sliderTL” ).slider();

Grâce au tableau défini plus haut, il vous sera facile d’instancier les 8 sliders.

Bien entendu avant cela, il ne faut pas oublier d’introduire dans l’en-tête du fichier HTML les liens nécessaires (on peut aussi télécharger le dossier jQuery UI, le dézipper et le placer dans le répertoire js de l’application).

<link rel="stylesheet" href="http://code.jquery.com/ui/1.12.1/themes/smoothness/jquery-ui.css">
<script src="http://code.jquery.com/jquery-1.12.4.js"></script>
<script src="http://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>

Attention à bien introduire le lien à jQuery avant celui à jQuery UI !

Avec un peu de CSS, vous devriez obtenir ceci :

Application des CSS
Application des CSS

À vous de consulter l’API pour :

  • associer aux sliders des valeurs minimales, maximales et initiales ;

  • désactiver au départ le slider tempo.

Du point de vue des règles et fonctionnalités

Première fonctionnalité :

  • si un glisser-déposer d’un sample depuis la liste des samples sur un des boutons du Pad est effectué, alors le son est associé à ce bouton (bien entendu, si une association existait avant ce glisser-déposer, elle est remplacée).

Pour assurer cette fonctionnalité, nous installons un gestionnaire de glisser-déposer avec les fonctions  dragStart  et  dragEnd  sur chaque sample, en utilisant la classe CSS .sample :

// glisser-déposer des samples
$('.sample').on({
dragstart: function(e) {
// id du sample
var id = $(this).attr('id').substring(6);
// mémorisation
e.originalEvent.dataTransfer.setData('text/html', id);
},
dragend: function(e) {
// id du sample
var id = $(this).attr('id');
//alert ("sample "+id.substring(6));
}
});

Symétriquement, un gestionnaire de glisser-déposer est installé, avec les fonctions  dragOver  et  drop  sur chaque bouton du pad, en utilisant la classe .pad associée à chaque bouton.

// glisser-déposer sur les pad
$('.pad').on({
dragover: function(e) {
e.preventDefault();
},
drop: function(e) {
var data = e.originalEvent.dataTransfer.getData('text/html');
$(this).html("sample "+data);
}
});

Au moment du drop, on associe ici provisoirement la chaîne “sample i” au bouton qui a reçu le sample.

Règles et fonctionnalités suivantes :

  • fonctionnalité : si un des boutons du pad est cliqué, alors :

    • si ce son ne fait pas déjà l’objet d’une boucle :

      • si la case loop du contrôle associé à ce bouton n’est pas cochée, le son sera joué une fois (au volume défini par le curseur associé au volume) et visualisé dans la zone de visualisation,

      • si la case loop est cochée, le son sera joué en boucle selon le tempo et le volume définis, et visualisé dans la zone de visualisation.

Nous constatons qu’il est important de savoir si un son est en cours de boucle ou pas. Définissons donc pour chaque son :

  • une variable correspondant au moment où le pad correspondant à ce son a été cliqué, le tableau  listeTempsInitiaux  :

listeTempsInitiaux = {"TL" : null, "BL" : null, "TR" : null, "BR" : null};
  • un tableau qui va contenir les sons joués.

// sons
audios={};

Pour assurer la fonctionnalité précédente, nous installons un gestionnaire de clic sur chaque bouton du pad, par exemple à travers la classe .pad. À ce gestionnaire, nous associons une fonction  jouePad()  à laquelle sont déléguées les actions à effectuer en cas de clic :

  • déterminer lequel des 4 pads vient d’être cliqué : facile à travers l’attribut id de ce pad ;

  • définir le moment initial pour ce son, si un moment initial n’avait encore été défini.

function jouePad(pad){
pp = $("#"+pad).attr('id').substring(3);
sample = $("#pad"+pp).text();
idSample = parseInt(sample.substring(7));
// si le temps initial est null et si un son est associé
if ( listeTempsInitiaux[pp] === null && Number.isInteger(idSample) ){
listeTempsInitiaux[pp] = temps;
}
}

Nous allons voir plus loin quelle sera l’utilisation de ce moment initial.

  • Règle n°1 : dans l’écran de jeu, en continu, boucler sur les différents contrôles :

    • si la case loop est cochée et que le son est en train d’être joué ou vient de s’arrêter, alors ce son continuera à être joué selon le tempo choisi,

    • si la case loop n’est pas cochée, mais qu’un clic sur le pas correspondant vient de se produire, alors ce son est joué une fois ;

  • règle n°2 : la zone de visualisation affiche en continu le son qui est joué ; dans cette application, nous faisons le choix d’afficher simplement une barre verticale (dont la hauteur est liée au volume du son et la couleur est associée à la couleur du bouton du pad) pour chaque couleur.

Mettons en oeuvre la règle n°1 grâce au moteur de règles et à la fonction règles() :

// moteur de règles
inter = setInterval(regles, intervalleRafraichissement);
// lancement : affichage de la page d'accueil
afficheEcranAccueil();
}
function regles(){
if (ecranCourant === "ecranJeu"){
// sons : boucle sur les pad
for (var p in controlsArray) {
for (var pp in controlsArray[p]){
l = controlsArray[p][pp]
// valeurs son courant
loop = false || $("#cbLoop"+l).is(':checked');
tempo = $("#sliderTempo"+l).slider( "option", "value" );
volume = $("#sliderVolume"+l).slider( "option", "value" )/10;
sample = $("#pad"+l).text();
idSample = sample.substring(7);
if (tempo !== 0) {
intervalle = 2*Math.floor(1/tempo*10)*intervalleRafraichissement; // entier en millisecondes multiple de intervalleRafraichissement
}
// test : si le temps initial n'est pas null et si le moment est venu compte tenu du tempo
t0 = listeTempsInitiaux[l];
if( (tempo !== 0 ) && (t0 !== null) && ( (temps - t0) % intervalle === 0) ){
// jeu du son
nomFichier = "./sons/"+listeSons[idSample];
audios[l] = new Audio(nomFichier);
audios[l].volume = volume;
audios[l].play();
// si le son n'est pas en boucle, on ne le joue qu'une fois
if(!loop){
listeTempsInitiaux[l] = null;
}
} else {
audios[l] = null;
}
}
}
// animation
animer();
// incrémentation
temps += intervalleRafraichissement;
}
}

Cette fonction fait une boucle sur les 4 zones de contrôle :

  • pour chaque zone, la valeur loop est récupérée, le volume et le nom du sample associés au pad sont liés à cette zone (puisque l’on connaît l’id du pad et le texte associé par glisser-déposer) ; puis un test est effectué :

  • si un temps initial est défini pour ce son (ce qui signifie que le pad a été cliqué) ET si la durée passée depuis le temps initial est un multiple de l’intervalle de temps correspondant au tempo demandé, alors :

    • ce son est joué,

    • si la case loop n’est pas cochée (cela signifie que l’on joue le son une fois), alors le temps initial pour ce son est supprimé ;  

       

  • si ce n’est pas le cas, ce son est annulé dans le tableau des sons joués.

Nous examinerons la règle n° 2 plus tard.

Deux prochaines fonctionnalités :

  • si la case à cocher loop non cochée préalablement est cliquée, alors elle devient cochée et rend actif le curseur permettant de changer le tempo ;

  • si la case à cocher loop déjà cochée préalablement est cliquée, alors elle devient décochée et rend inactif le curseur permettant de changer le tempo (si un son était en train d’être joué, alors il s’arrête).

Ces deux fonctionnalités sont faciles à mettre en œuvre : un gestionnaire de clic sur chaque case à cocher est installé — par exemple en introduisant une classe .cb sur chaque case à cocher — auquel nous associons une fonction  gereCB()  à laquelle sont déléguées les actions à effectuer en cas de clic ; à savoir :

  • rendre actif le curseur tempo si la case loop était non cochée préalablement et vice-versa ;

  • arrêter le son associé à ce contrôle si la case loop était cochée préalablement. Oui, mais comment arrêter précisément le son associé à cette zone de contrôle ? Une façon de faire est de déclarer un tableau.

Et voici le code de la fonction  gereCB()  :

function gereCB(cb){
pp = $("#"+cb).attr('id').substring(6);
cbLoopChecked = $("#cbLoop"+pp).is(':checked');
$("#sliderTempo"+pp).slider({disabled: !cbLoopChecked});
}

Fonctionnalité suivante :

  • si le curseur associé au tempo est modifié, alors le tempo du son joué en boucle est ou sera modifié ;

  • si le curseur associé au volume est modifié, alors le volume du son est ou sera modifié.

Eh bien, bonne nouvelle : avec les fonctions développées précédemment, ces deux fonctionnalités sont automatiquement assurées !

Enfin,

  • si le bouton #boutonQuitter est cliqué, alors l'écran de fin est affiché.

Pour assurer cette fonctionnalité, un gestionnaire sur ce bouton est installé ; ce gestionnaire lance la fonction  afficheEcranFin()  qui affiche l'écran de fin et masque les autres écrans.

Affichage en continu :

Revenons à présent à la règle n° 2 : la zone de visualisation affiche en continu le son qui est joué.

Une barre verticale (dont la hauteur est liée au volume du son et la couleur est associée à la couleur du bouton du pad) est affichée pour chaque couleur.

Volumes sonores et pads
Volumes sonores et pads

Comment mettre en œuvre cette règle ? C’est à nouveau la fonction  regles()  qui va à chaque intervalle de temps lancer la fonction  — appelons-la  animer()  — qui met à jour le contenu du canvas.

Ce que fait cette fonction animer() située tout à la fin de la fonction regles() :

  • elle boucle sur le tableau audios contenant les sons qui sont joués ;

  • pour chaque son, elle dessine une barre de hauteur proportionnelle au volume et de couleur correspondante.

function animer() {
// effaçage
ctx.clearRect(0,0, monCanvas.width,monCanvas.height);
// boucle sur les sons
for (var s in audios) {
if(audios[s] !== null){
position = listePositions[s]*monCanvas.width/5;
dessineSon(s, position);
}
}
}

Que fait ce code ? Il commence classiquement par effacer le canvas, puis il boucle sur les sons ; si le son est joué, alors il est joué à la position définie par le tableau  listePositions.

listePositions = {"TL" : 1, "BL" : 2, "TR" : 3, "BR" : 4};

Le travail restant

Il vous restera dans ce projet à écrire le code de la fonction  dessineSon()  !

En résumé

Cette application est fonctionnelle, mais largement perfectible. Les choix d’architecture, de fonctionnalités, de règles et d’algorithme qui ont été faits ici sont destinés à être compris par un public le plus large possible.

 Vous êtes arrivé à la fin de ce cours, félicitations ! Vous avez maintenant toutes les cartes en main pour :

  • utiliser les balises HTML5 liées à la structure et au contenu embarqué ;

  • utiliser la balise canvas en HTML5 ;

  • ajouter de l'interactivité à des pages web avec HTML5.

N'oubliez pas de réaliser les exercices à la fin de chaque partie pour valider ces compétences. Je vous souhaite une très bonne continuation !

Exemple de certificat de réussite
Exemple de certificat de réussite