Lettura e visualizzazione di un file MIDI

Un file MIDI non contiene suoni ma note: l’altezza, l’inizio, la durata e l’intensità di ognuna. Lette con la libreria @tonejs/midi, le stesse note vengono suonate da un sintetizzatore di Tone.js e disegnate da p5.js, nello stesso momento.

    let brano;       // le note del file MIDI, divise in tracce
let strumento;   // il sintetizzatore che le suona

function suona( tempo, nota ) {
    strumento.triggerAttackRelease( nota.name, nota.duration,
                                    tempo, nota.velocity );
}

async function setup() {
    createCanvas( 670, 200 );
    colorMode( HSL );
    noStroke();
    brano = await Midi.fromUrl( 'mozk545a.mid' );
    strumento = new Tone.PolySynth( Tone.Synth ).toDestination();
    strumento.volume.value = -12;
    for (let traccia of brano.tracks) {
        new Tone.Part( suona, traccia.notes ).start( 0 );
    }
    Tone.getTransport().loop = true;
    Tone.getTransport().loopEnd = brano.duration;
}

function draw() {
    background( 100 );
    if (Tone.getTransport().state != "started") {
        fill( 0 );
        text( "clic per avviare il brano", 20, 30 );
    }
    let ora = Tone.getTransport().getSecondsAtTime( Tone.immediate() );
    for (let traccia of brano.tracks) {
        for (let nota of traccia.notes) {
            if (nota.time <= ora && ora < nota.time + nota.duration) {
                let x = map( nota.midi, 36, 96, 20, 650 );
                let resto = 1 - (ora - nota.time) / nota.duration;
                let tinta = map( nota.midi, 36, 96, 270, 600 ) % 360;
                fill( tinta, 100, 50, 0.5 );
                circle( x, height/2, nota.velocity * 90 * resto );
            }
        }
    }
}

function mousePressed() {
    Tone.start();
    if (Tone.getTransport().state == "started") {
        Tone.getTransport().pause();
    } else {
        Tone.getTransport().start();
    }
}
  

Files

  

Algoritmo

all'avvio:                                                        setup()
    leggere le note del file MIDI                                 Midi.fromUrl()
    creare un sintetizzatore che suona più note insieme           Tone.PolySynth
    affidare le note di ogni traccia a una "parte" di Tone.js,    Tone.Part
    che chiama suona() al momento giusto per ognuna

a ogni fotogramma:                                                draw()
    leggere la posizione raggiunta nel brano
    per ogni nota che sta suonando in quel momento:               if()
        disegnare un cerchio, più a destra per le note più acute,
        che si rimpicciolisce mentre la nota finisce

a ogni clic:                                                      mousePressed()
    avviare il brano, o metterlo in pausa

Istruzioni


  

Legge il file e ne restituisce le tracce (brano.tracks), ognuna con un array di note. Ogni nota ha l’altezza (midi, da 0 a 127, e name, come "C4"), l’inizio e la durata in secondi (time e duration) e l’intensità (velocity, da 0 a 1).


  

Una "parte" di Tone.js chiama suona() all’inizio di ogni nota, passandole l’istante esatto in cui deve suonare (tempo) e la nota stessa. Le note vengono così programmate sull’orologio dell’audio, indipendente dai fotogrammi di p5.js.


  

Restituisce la posizione nel brano di quello che si sente in questo momento. Tone.js programma i suoni con un decimo di secondo di anticipo, e Tone.getTransport().seconds sarebbe quindi un po’ avanti rispetto al suono.


  

Vale 1 quando la nota comincia e scende fino a 0 quando finisce: moltiplicato per il diametro, fa rimpicciolire il cerchio.

Variazioni

Con strumento.volume.value = -6 il volume aumenta, ma quando suonano molte note insieme il segnale supera il limite e si distorce: con questo brano, a -12 decibel, arriva al massimo a metà del limite.

Il sintetizzatore può avere un timbro diverso: con queste righe al posto di quella che lo crea, le note suonano con un’onda quadra, più aspra.


  

Note

La versione precedente di questa slide, Visualizzazione di file MIDI, usa la libreria Heartbeat.js con p5.js 1.