MATLAB
Il progetto richiede l’analisi audio effettuata via MATLAB offline attraverso il MIRToolBox, reperibile gratuitamente a questo indirizzo, che fornisce degli ottimi strumenti di analisi dei segnali su matlab. Il tool si basa su una struttura dati personalizzata che deve essere tradotta in dato puro per poter essere manipolata con gli strumenti esterni al pacchetto, mentre ogni oggetto “miraudio” può essere manipolato in ogni modo con gli strumenti forniti. Per effettuare delle analisi complesse bisogna prima creare l’oggetto miraudio dal file audio:
files = dir('*.wav'); mir_a = miraudio(file.name,'Normal','Mono','Label',0);
Il tool riceve sia i files wav che in MP3 (solo su alcuni sistemi operativi) e li traduce in oggetti: da qui si può cambiare il campionamento della traccia, operare separazione di frames, analisi del pitch e pure studiare le features per machine learning.
Fs = 2048; FsFb = 512; Nch = 10; mir_env = mirenvelope(mir_a,'Sampling',Fs); mir_a = miraudio(mir_a,'Sampling',FsFb); mir_fb = mirfilterbank(mir_a,'NbChannels',Nch);
Per non eccedere come quantità di dati e rendere fruibile l’esperienza ho dovuto decimare i campioni ottenuti, quindi sottocampionare la traccia a seconda dell’analisi che dovevo fare. Un’altra operazione di cui voglio sgravare l’esecuzione è la normalizzazione dei campioni: portando i campioni in intervalli tra 0 e 1 o tra -1 e +1 semplifico le operazioni di scalatura che si applicheranno ai campioni per la generazione di eventi grafici:
env_norm = mirgetdata(temp_env); env_norm = round(env_norm/max(abs(env_norm)),4); fb_anorm = mirgetdata(temp_fbenv); fb_norm(:,:) = fb_anorm(:,1,:); for i = 1:Nch fb_max = max(fb_norm(:,i)); fb_norm(:,i) = round(fb_norm(:,i)./fb_max,4); end pitch = mirgetdata(temp_pitch); pitch = mean(pitch); % se ho più pitch diversi ne ho uno medio while(pitch > 440) pitch = pitch - 440; end pitch = round((pitch/440)*255);
Una volta che ho le feature che voglio usare per la rappresentazione grafica, vado a creare una struttura dati accessibile dallo script: i file JSON sono delle strutture dati ben precise atte alla comunicazione di dati tra server e client. Da MATLAB è possibile esportare variabili, array, matrici e struct formattate bene secondo questo standard in un file .JSON
temp_s = struct('title', get(mir_a,'Label'),'Fs',Fs,'env',env_norm,'FsFb',FsFb,'Nch',Nch,'filterbank',fb_norm); temp_name = sprintf('%s.json', tracks(file,:)); savejson('track',temp_s,temp_name);
Javascript: WEBGL
In HTML5 è stato definito un metodo per poter rappresentare graficamente eventi senza dover ricorrere a plugins esterni ed affidandosi alla capacità grafica del computer e del browser. Il codice viene scritto in Javascript e integrato nella pagina html grazie al tag <canvas>. In questa applicazione ho voluto sfruttare per la riproduzione audio le funzionalità del browser, usando il tag HTML5 <audio> per caricare le tracce da riprodurre: le prestazioni di esecuzione sono quindi legate alla capacità del browser e alla connessione per il download dell’audio.
Per semplificare l’organizzazione delle tracce ho scritto uno script di “loading” che mi organizza il caricamento asincrono dei dati di analisi al caricamento della pagina. Purtroppo il metodo di javascript “parseJSON()” non sembra funzionare con il caricamento asincrono del file, per questo ho ripiegato sul metodo di jQuery $.getJSON per recuperare le strutture dati generate da MATLAB. La libreria jQuery non verrà usata ulteriormente in questo progetto, se si riuscisse a sistemare il caricamento e il parsing dei files JSON se ne potrebbe fare a meno.
Il problema principale di un’applicazione audio/video è la sincronia tra gli elementi riprodotti e l’audio eseguito: come mantenere la coerenza? come possiamo accedere alle informazioni sonore? La semplice esecuzione grafica delle features estratte può essere risolta dal computer in pochi secondi ma vorremmo che ciò che viene rappresentato a video sia coerente con il campione audio eseguito in quell’istante. Javascript e HTML5 non forniscono dei metodi ufficiali per capire a che punto della canzone si è, bisogna accettare un minimo di imprecisione.
var timeNow = (Math.trunc(audio.currentTime*100))/100; var sampleNow = Math.round(timeNow * Fs) - Fs;
Il metodo “currentTime” mi fornisce a quale secondo di riproduzione siamo della traccia correntemente eseguita dal browser: è un metodo molto poco preciso e asincrono che potrebbe portare a risultati erronei per l’analisi in tempo reale. La precisione è garantita al centesimo di secondo (ma spesso anche solo al decimo). Per eliminare dati spuri che vengono proposti ho deciso di trocanre le misurazioni al centesimo di secondo, successivamente per capire a quale campione di esecuzione siamo, moltiplico il frammento temporale per la frequena di campionamento: la sottrazione mi serve per poter avere la rappresentazione grafica coordinata con l’esecuzione audio. Normalmente vengono usati dei metodi di analisi in tempo reale per questo tipo di applicazioni, basandosi su buffer temporanei in modo da non dover avere una struttura dati complessa e pesante ma potersi basare solo su quello che effettivamente è in riproduzione in quel momento. I metodi per questo tipo di analisi vengono forniti da Web Audio integrato in HTML5.
-
Linea d’Onda
Per la prima rappresentazione richiesta, ho sfruttato il metodo “line” di jvascript, che disegna una linea
canvasCtx.lineWidth = 2; canvasCtx.strokeStyle = 'rgb(0,0,0)'; canvasCtx.beginPath();var sliceWidth = WIDTH * 1.0 / Fs; var x = 0; for(var i = 0; i < Fs; i++) { if ( timeNow == 0) { var v = 2; } else { var v = parseFloat(track.env[i+sampleNow]); v = (v * -2) + 2; } var y = v * HEIGHT/2; if(i === 0) { canvasCtx.moveTo(x, y); } else { canvasCtx.lineTo(x, y); } x += sliceWidth; } canvasCtx.lineTo(canvas.width, y); canvasCtx.stroke();
-
Spettrogramma 2D
In questo caso andiamo a colorare dei rettangoli, proporzionalmente al numero di campioni e di canali, per ogni canale ad ogni campione in base alla sua intensità. La scala cromatica va dal blu intenso per valori molto bassi al giallo per valori alti, passando dal verdino. Essendo i colori in base 256 è normale che ci sia dell’aliasing tra i dati.
var pxWidth = Math.trunc((WIDTH / ( trackLength * FsFb))*1000)/1000; var pxHeight = HEIGHT / chCount; var xNext = Math.trunc(audio.currentTime * FsFb) * pxWidth; function draw() { drawVisual = requestAnimationFrame(draw); // Gestione del tempo di riproduzione var timeNow = (Math.trunc(audio.currentTime*100))/100; // Campione attuale: var sampleNow = Math.round(timeNow * FsFb); var pxColor = 0; var xNow = xNext; xNext = sampleNow * pxWidth; var y = 0; for(var i = 0; i < chCount; i++) { if(timeNow == 0){ // situazione iniziale, cavas pulito. canvasCtx.fillStyle = 'rgb(248,248,248)'; canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); } else { pxColor = Math.trunc(parseFloat(track.filterbank[sampleNow][i])*255); } // colore in base all'intensità if(pxColor <= 128){ var colorR = 0; var colorG = pxColor*2; } else { var colorR = (pxColor-127)*2; var colorG = 255; } var colorB = 255 - pxColor; canvasCtx.fillStyle = 'rgb('+colorR+','+colorG+','+colorB+')'; canvasCtx.fillRect(xNow, y, xNext-xNow, pxHeight); y += pxHeight; } }
-
Istogramma Spettrale
Semplificazione, in quanto rappresento soltato i valori attuali dei campioni. L’altezza è proporzionale all’intesità sul canale.
var barWidth = (WIDTH / chCount) - 1; var barHeight; var valueSample; var x = 0; for(var i = 0; i < chCount; i++) { if(timeNow == 0){ valueSample = 0.0; // valore minimo in entrata } else { valueSample = parseFloat(track.filterbank[sampleNow][i]); } if(valueSample <= 0.500){ // da blu, valore basso, a verdino var colorR = 0; var colorG = Math.trunc(valueSample*255)*2; } else { var colorR = Math.trunc((valueSample-0.5)*255)*2; var colorG = 255; } var colorB = Math.trunc(255-valueSample*255); // scalo su tutto il blu indipendentemente. canvasCtx.fillStyle = 'rgb('+colorR+','+colorG+','+colorB+')'; barHeight = valueSample * HEIGHT; canvasCtx.fillRect(x,HEIGHT - barHeight, barWidth , barHeight); x += barWidth + 1; }
-
Rappresentazione Personalizzata
Per questa rappresentazione ho usato le librerie THREE.js e il codice è troppo lungo e scomodo da leggere per essere messo qui, potete analizzare il codice javascript contenuto nel file app.js .
Questo progetto mi ha permesso di dare uno sguardo alla grafica in tempo reale web, come ci si muove e quali possibilità si abbiano, spero che queste pagine possano essere d’aiuto per il futuro.