mardi 24 avril 2012

Diagramme d'accords de guitare en canvas HTML5

Mon dernier billet sur la programmation avec le canvas HTML5 datait d'un bout de temps déjà. Je me demandais quel genre de petit projet pratique je pourrais développer en l'espace d'une soirée et comme j'ai amorcé un cours de guitare, je me suis dit que ça pourrait être intéressant d'apprendre à dessiner un diagramme d'accord de guitare.

Qu'est-ce que le diagramme doit afficher ?
  • le nom de la note
  • le sillet de tête (communément appelé nut - la barre horizonale foncée)
  • pour faire simple, les 4 premières frettes (pour illustrer des open chords)
  • les 6 cordes (de gauche à droite EADGBE)
  • la position des doigts (cercles noirs)
  • les cordes ouvertes à jouer (cercles blancs)
  • les cordes à ne pas jouer (avec un X au bout du sillet)
Voici le résultat affiché par ce tutoriel pour un accord de DO (ainsi que quelques contrôles permettant de rafraîchir la note sur le diagramme) :

Code HTML :
<canvas id="myCanvas" width="250" height="300">
    Le canvas HTML5 ne fonctionne pas avec ton browser déficient :-(
</canvas>

<p>
    <button type="button" id="btnA">A</button>
    <button type="button" id="btnB">B</button>
    <button type="button" id="btnC">C</button>
    <button type="button" id="btnD">D</button>
    <button type="button" id="btnE">E</button>
    <button type="button" id="btnF">F</button>
    <button type="button" id="btnG">G</button>
</p>

<p>
    <button type="button" id="btnC7">C7</button>
    <button type="button" id="btnAsus4">Asus4</button>
</p>
Définition de quelques formules d'accords sous forme de tableau :
// chaque array contient dans l'ordre la position des doigts de la corde 6 à 1 (mi grave à mi aigu)
var a = { "name" : "A", "notes" : [null,0,2,2,2,0] };
var b = { "name" : "B", "notes" : [null,2,4,4,4,2] };
var c = { "name" : "C", "notes" : [null,3,2,0,1,0] };
var d = { "name" : "D", "notes" : [null,null,0,2,3,2] };
var e = { "name" : "E", "notes" : [0,2,2,1,0,0] };
var f = { "name" : "F", "notes" : [1,3,3,2,1,1] };
var g = { "name" : "G", "notes" : [3,2,0,0,0,3] };

var c7 = { "name" : "C7", "notes" : [null,3,2,3,1,0] };
var asus4 = { "name" : "Asus4", "notes" : [null,0,2,2,3,0] };
Initialisation JavaScript des boutons :
// inclure jQuery
$(document).ready(
    function(){
        $('#btnA').click( function(){ drawChordDiagram(a); } );
        $('#btnB').click( function(){ drawChordDiagram(b); } );
        $('#btnC').click( function(){ drawChordDiagram(c); } );
        $('#btnD').click( function(){ drawChordDiagram(d); } );
        $('#btnE').click( function(){ drawChordDiagram(e); } );
        $('#btnF').click( function(){ drawChordDiagram(f); } );
        $('#btnG').click( function(){ drawChordDiagram(g); } );

        $('#btnC7').click( function(){ drawChordDiagram(c7); } );
        $('#btnAsus4').click( function(){ drawChordDiagram(asus4); } );
    }
);
La fonction qui dessine le diagramme ainsi que la note :
function drawChordDiagram(chord){

    var canvas = $('#myCanvas');

    if(canvas && canvas.get(0).getContext){

        var context = canvas.get(0).getContext('2d');

        // initialiser la surface de dessin
        context.setTransform(1, 0, 0, 1, 0, 0);
        context.clearRect(0, 0, canvas.width(), canvas.height());

        context.translate(50, 0);

        var nbFrets = 4;
        var nutWidth = 120;
        var stringLength = 140;
        var nbStrings = 6;
        var fretWidth = stringLength/nbFrets;

        // nom de l'accord
        context.font = "30px arial";
        context.fillText(chord.name, 0, 70);

        // décalage sous le nom de la note
        context.translate(0, 100);

        // "nut"
        context.strokeRect(0,0,nutWidth,5);
        context.fillRect(0,0,nutWidth,5);

        // déplacement vers le bas avant de dessiner les frettes
        context.translate(0, 3);

        // frettes
        context.lineWidth = 2;

        for(var fret=1 ; fret<=nbFrets; fret++){
            context.beginPath();
            context.moveTo(0, fret*fretWidth);
            context.lineTo(nutWidth, fret*fretWidth);
            context.closePath()
            context.stroke();
        }

        // cordes
        for(var string=0; string<nbStrings ; string++){
            context.beginPath();
            context.moveTo(string*(nutWidth/(nbStrings-1)), 0);
            context.lineTo(string*(nutWidth/(nbStrings-1)), stringLength);
            context.closePath()
            context.stroke();
        }

        // taille pour le X
        context.font = "24px arial";

        // l'accord
        $.each(chord.notes,
            function(string, fret){
                var x = nutWidth/(nbStrings-1) * string
                var y = (stringLength/nbFrets) * fret - (stringLength/nbFrets)/2;

                if(fret == null){
                    // position approximative du X
                    context.fillText('x', string*(nutWidth/(nbStrings-1))-7, -10);
                }
                else{
                    context.beginPath();
                    context.arc(x, y, 8, 0, Math.PI*2, false);
                    context.closePath()

                    if(fret > 0){
                        // cercle plein (noir)
                        context.fill();
                    }
                    else{
                        // cercle vide (blanc)
                        context.stroke();
                    }
                }
            }
        );
    }
}
Avec ces quelques lignes de code, nous voilà avec un gabarit de diagramme capable d'illustrer un bon nombre d'accords en position ouverte. Dans ce prototype, plusieurs validations sont manquantes (entre autre lorsqu'on sort des limites du manche) et il reste à implémenter le décalage de la position sur le manche (généralement indiqué par le numéro de la frette à gauche). Je reparlerai sans doute des améliorations apportées lors de mes prochaines expérimentations.

1 commentaire: