3 de febrero de 2020

Taller de iniciación a ToneJS, una librería JavaScript para programar música en el navegador


Hace poco he ayudado a montar el CoderDojo de Carabanchel, que es un club de programación donde niños y niñas de entre 7 y 17 años aprenden a programar ayudados por mentores. Nos reunimos los sábados por la tarde, y yo intento ir siempre que puedo con mis dos hijas. Además, a veces preparo talleres. Al principio preparé un taller de p5js, que es una librería de JavaScript para crear arte interactivo en el navegador que gustó bastante, y ahora he preparado un taller de ToneJS una librería JavaScript para crear música interactiva en el navegador. Este último taller está muy relacionado con un artículo que escribí hace justo un año en dónde explicaba cómo programar la música de Star Wars con Gibber.

Tenéis todo el código y las librerías que necesitáis en un repositorio de mi GitHub.

Este es el código más básico para crear un botón que al pulsarlo haga sonar una nota, en este caso un DO, en el navegador:

<!DOCTYPE html>
<html>
  <head>
    <title>ToneJS - 01 una nota</title>
    <script src="../lib/Tone.js"></script>
  </head>

  <body>
    <button id="do">UNA NOTA</button>
  </body>

  <script type="text/javascript">
    const synth = new Tone.Synth().toMaster();
    Tone.Transport.start();

    document.querySelector("#do")
      .addEventListener("click", async () => {
        synth.triggerAttackRelease("C4", "4n");
    });
  </script>
</html>
El resultado es el siguiente (pulsa el botón para probar):

En este punto hay que remarcar que las notas se ponen en el estilo anglosajón:
  • DO=C
  • RE=D
  • MI=E
  • FA=F
  • SOL=G
  • LA=A
  • SI=B
El número que sigue a la nota hace referencia a la octava (ya sea esta más grave o más aguda).

A continuación, parte del código para hacer una especie de piano con botones:

<!DOCTYPE html>
<html>
  <head>
    <title>ToneJS - 02 varias notas</title>
    <script src="../lib/Tone.js"></script>
  </head>

  <body>
    <button id="do">DO</button>
    <button id="re">RE</button>
    ...
  </body>

  <script type="text/javascript">
    const synth = new Tone.Synth().toMaster();
    Tone.Transport.start();

    document.querySelector("#do")
      .addEventListener("click", async () => {
        synth.triggerAttackRelease("C4", "4n");
    });

    document.querySelector("#re")
      .addEventListener("click", async () => {
        synth.triggerAttackRelease("D4", "4n");
    });

    ...
  </script>
</html>
El resultado es el siguiente (pulsa los botones para probar):

A continuación mostramos el código para que suene una escala:

<!DOCTYPE html>
<html>
  <head>
    <title>ToneJS - 03 escala</title>
    <script src="../lib/Tone.js"></script>
  </head>

  <body>
    <button id="escala">ESCALA</button>
  </body>

  <script type="text/javascript">
    const synth = new Tone.Synth().toMaster();
    Tone.Transport.start();

    document.querySelector("#escala")
      .addEventListener("click", async () => {
        synth.triggerAttackRelease("C4", "8n", 1);
        synth.triggerAttackRelease("D4", "8n", 2);
        synth.triggerAttackRelease("E4", "8n", 3);
        synth.triggerAttackRelease("F4", "8n", 4);
        synth.triggerAttackRelease("G4", "8n", 5);
        synth.triggerAttackRelease("A4", "8n", 6);
        synth.triggerAttackRelease("B4", "8n", 7);
        synth.triggerAttackRelease("C5", "8n", 8);
    });
  </script>
</html>
El resultado es el siguiente (pulsa el botón para probar):

Ahora hacemos algo un poco más elaborado utilizamos 2 arrays: uno para las notas y otro para las duraciones de las notas.

Aquí hay que remarcar que para los tiempos hay que tener en cuenta que:
  • 1n hace referencia a una redonda (4 tiempos)
  • 2n hace referencia a una blanca (2 tiempos)
  • 4n hace referencia a una negra (1 tiempo)
  • 8n hace referencia a una corchea (0,5 tiempos)
  • 16n hace referencia a una semicorchea (0,25 tiempos)
Teniendo en cuenta esto, a continuación el código para hacer una escala con diferentes tiempos:

<!DOCTYPE html>
<html>
  <head>
    <title>ToneJS - 04 notas</title>
    <script src="../lib/Tone.js"></script>
    <script src="../lib/Rhythm.js"></script>
  </head>

  <body>
    <button id="notas">NOTAS</button>
  </body>

  <script type="text/javascript">
    var synth = new Tone.Synth().toMaster();

    document.querySelector("#notas").addEventListener("click", async () => {

      var notas = [
        "C4","D4","E4","F4","G4","A4","B4","C5"];
      var duraciones = [
        "2n","4n","8n","16n","16n","8n","4n","2n"];

      var cancion = Rhythm.mergeDurationsAndPitch(duraciones, notas);

      var part = new Tone.Part(function(time, value){
        console.log(value.note + " " + value.duration);
        synth.triggerAttackRelease(value.note, value.duration, time);
      }, cancion );
      
      part.start(0);
      Tone.Transport.start();
    });
  </script>
</html>
El resultado es el siguiente (pulsa el botón para probar):

Ahora vamos a coger una partitura sencilla, la del cumpleaños feliz:

Y la codificamos con ToneJS:

<!DOCTYPE html>
<html>
  <head>
    <title>ToneJS - 05 cumpleaños feliz</title>
    <script src="../lib/Tone.js"></script>
    <script src="../lib/Rhythm.js"></script>
  </head>

  <body>
    <button id="notas">CUMPLEAÑOS FELIZ</button>
  </body>

  <script type="text/javascript">
    var synth = new Tone.Synth().toMaster();

    document.querySelector("#notas").addEventListener("click", async () => {

      var notas = [
        "C4", "C4", 
        "D4", "C4", "F4", 
        "E4", "C4", "C4", 
        "D4", "C4", "G4",
        "F4", "C4", "C4",
        "C5", "A4", "F4",
        "E4", "D4", "Bb4", "Bb4",
        "A4", "F4", "G4",
        "F4"];
      var duraciones = [
        "8n", "8n", 
        "4n", "4n", "4n",
        "2n", "8n", "8n",
        "4n", "4n", "4n",
        "2n", "8n", "8n", 
        "4n", "4n", "4n", 
        "4n", "4n", "8n", "8n",
        "4n", "4n", "4n",
        "2n"];

      var cancion = Rhythm.mergeDurationsAndPitch(duraciones, notas);

      var part = new Tone.Part(function(time, value){
        console.log(time + " " + value.note + " " + value.duration);
        synth.triggerAttackRelease(value.note, value.duration, time);
      }, cancion );
      
      part.start(0);
      Tone.Transport.start();
    });
  </script>
</html>
El resultado es el siguiente (pulsa el botón para probar):

Y para terminar, hacemos lo mismo con la partitura de Star Wars:

Aquí hay que tener en cuenta que los trisillos se codifican con una t, en vez de con una n (ejemplo 8t). Y para los puntillos hay que disminuuir su número (ejemplo 3n).

<!DOCTYPE html>
<html>
  <head>
    <title>ToneJS - 06 Star Wars</title>
    <script src="../lib/Tone.js"></script>
    <script src="../lib/Rhythm.js"></script>
  </head>

  <body>
    <button id="notas">Star Wars</button>
  </body>

  <script type="text/javascript">
    var synth = new Tone.Synth().toMaster();

    document.querySelector("#notas").addEventListener("click", async () => {

var notas = [
        "F3", "C4",
        "Bb3", "A3", "G3", "F4", "C4",
        "Bb3", "A3", "G3", "F4", "C4",
        "Bb3", "A3", "Bb3", "G3", "C3", "C3", "C3",
        "F3", "C4",
        "Bb3", "A3", "G3", "F4", "C4",
        "Bb3", "A3", "Bb3", "G3", "C3", "C3",
        "D3", "D3", "Bb3", "A3", "G3", "F3",
        "F3", "G3", "A3", "G3", "D3", "E3", "C3", "C3",
        "D3", "D3", "Bb3", "A3", "G3", "F3",
        "C4", "G3", "G3", "C3", "C3",
        "D3", "D3", "Bb3", "A3", "G3", "F3",
        "F3", "G3", "A3", "G3", "D3", "E3", "C4", "C4",
        "F4", "F3",
        "C4", "C3", "C3", "C3",
        "F3", "C4",
        "Bb3", "A3", "G3", "F4", "C4",
        "Bb3", "A3", "G3", "F4", "C4",
        "Bb3", "A3", "Bb3", "G3", "C3", "C3", "C3",
        "F3", "C4",
        "Bb3", "A3", "G3", "F4", "C4",
        "Bb3", "A3", "G3", "F4", "C4",
        "Bb3", "A3", "Bb3", "G3", "C3", "C3", "C3",
        "F4", "F4", "F4", "F4", "F4"];
      var duraciones = [
        "2n", "2n",
        "8t", "8t", "8t", "2n", "4n",
        "8t", "8t", "8t", "2n", "4n",
        "8t", "8t", "8t", "2n", "8t", "8t", "8t",
        "2n", "2n",
        "8t", "8t", "8t", "2n", "4n",
        "8t", "8t", "8t", "2n", "8n", "8n",
        "3n", "8n", "8n", "8n", "8n", "8n",
        "8t", "8t", "8t", "8n", "8n", "4n", "8n", "8n",
        "3n", "8n", "8n", "8n", "8n", "8n",
        "8n", "8n", "2n", "8n", "8n",
        "3n", "8n", "8n", "8n", "8n", "8n",
        "8t", "8t", "8t", "8n", "8n", "4n", "8n", "8n",
        "2n", "2n",
        "1n", "8t", "8t", "8t",
        "2n", "2n",
        "8t", "8t", "8t", "2n", "4n",
        "8t", "8t", "8t", "2n", "4n",
        "8t", "8t", "8t", "2n", "8t", "8t", "8t",
        "2n", "2n",
        "8t", "8t", "8t", "2n", "4n",
        "8t", "8t", "8t", "2n", "4n",
        "8t", "8t", "8t", "2n", "8t", "8t", "8t",
        "4n", "8t", "8t", "8t", "4n"];

      var cancion = Rhythm.mergeDurationsAndPitch(duraciones, notas);

      var part = new Tone.Part(function(time, value){
        console.log(time + " " + value.note + " " + value.duration);
        synth.triggerAttackRelease(value.note, value.duration, time);
      }, cancion );
      
      part.start(0);
      Tone.Transport.start();
    });
  </script>
</html>
El resultado es el siguiente (pulsa el botón para probar):

Espero os haya gustado ;-)
Comparte:    Facebook Twitter

0 comentarios:

Publicar un comentario