O documento descreve o desenvolvimento de um projeto chamado Retro Audio JS, que permite tocar músicas retro usando apenas JavaScript. O autor criou uma notação musical simplificada em JSON e um loop que interpreta as notas a cada ciclo, permitindo reproduzir canções completas como o tema do Super Mario Bros. A implementação enfrentou desafios como manter o ritmo e melhorar o desempenho, mas o autor conseguiu reproduzir com sucesso a música usando apenas o navegador.
4. Tudo começou na palestra do @almirfilho no OlhóSEO em Floripa sobre Web Audio API. O
@fnando falou "Cara, dá pra fazer o JavaScript tocar o tema do Mario, imagina que foda?".
Challenge accepted.
12. about me
Estudei piano dos 8 aos 15 anos. Sou apaixonado por música clássica, e gosto de ler
partituras. Nada como juntar um hobby antigo com uma paixão. =)
24. {
"title" : "Imperial March",
"tempo" : 120,
"time_signature" : "4/4",
"score" : [
{
"instrument" : "oscillator-square",
"volume" : 0.5,
"sheet" : [
"G.8D", "-.16", "G.8D", "-.16", "G.8D", "-.16", "Eb.8D", "Bb.16",
"G.8D", "-.16", "Eb.8D", "Bb.16", "G.4", "-.4"
]
}
]
}
Uma partitura, bem simplificada, tem o título, tempo, assinatura de tempo, e as notas. A
implementação em JSON que bolei ficou assim.
25. [
"G.8D", "-.16", "G.8D", "-.16", "G.8D", "-.16", "Eb.8D", "Bb.16",
"G.8D", "-.16", "Eb.8D", "Bb.16", "G.4", "-.4"
]
Uma música é um array de notas. Mas como é a sintaxe dessas notas?
40. 1000ms = 16 cycles de 62.5ms
Se uma música tem 60 bpm, uma semínima (uma "batida") tem um segundo. Uma semínima
tem 16 semifusas, então tem 16 ciclos. Cada ciclo, portanto, tem 1000/16 = 62.5ms
41. parsedTrack["0"] = new Note("C.4");
parsedTrack["16"] = new Note("D.4");
parsedTrack["32"] = new Note("E.4");
parsedTrack["64"] = new Note("F.4");
Tracks são hashes que contêm como índice o ciclo, e a nota que deve ser tocada nesse ciclo.
42. parsedTrack["0"] = new Note("C.4");
parsedTrack["16"] = new Note("D.4");
parsedTrack["32"] = new Note("E.4");
parsedTrack["64"] = new Note("F.4");
var cycleDuration = 62.5
, currentCycle = 0
;
function renderCycle () {
parsedTrack[currentCycle].play()
currentCycle = currentCycle + 1;
setTimeout(renderCycle, cycleDuration);
};
Uma loop incrementa os ciclos e checa se há notas a serem tocadas ali.
45. parsedTrack0["0"] = new Note("C.4");
parsedTrack0["16"] = new Note("D.4");
parsedTrack0["32"] = new Note("E.4");
parsedTrack0["64"] = new Note("F.4");
parsedTrack1["0"] = new Note("G.4");
parsedTrack1["16"] = new Note("A.4");
tracks[0] = parsedTrack0;
tracks[1] = parsedTrack1;
var cycleDuration = 62.5
, currentCycle = 0
;
function renderCycle () {
for (var i = 0, l = tracks.length; i < l; i++) {
tracks[i][currentCycle].play();
}
currentCycle = currentCycle + 1;
setTimeout(renderCycle, cycleDuration);
};
Se o parâmetro de indexação de uma nota na música é o mesmo entre todos os tracks, é fácil
fazer músicas com múltiplos tracks.
49. quartifusas (1/128)
performance ficou baixa
Com múltiplos tracks, executar ciclos tomando uma nota quartifusa (1/128) de duração
como base ficou muito pesado. Tive que voltar para uma semifusa (1/64).