Crie uma aplicação de piano com JavaScript

Meu objetivo é fazer um piano viável. Uma vez que fiz o piano, quero construir um banco de dados de músicas reproduzíveis, para poder jogar. Nada extravagante, como banda de rock, mas apenas um simples aplicativo de piano com músicas com as quais você pode jogar. Se você quiser acompanhar, aqui está o meu protótipo útil para o qual estaremos trabalhando: https://my-little-piano-app.herokuapp.com/

Este é um projeto divertido, e seria um pequeno jogo divertido para os seus filhos. Então vamos começar. Estou quebrando esse projeto em 3 componentes principais:

1 O HTML

2 O JavaScript

3 O banco de dados Node.JS

Para o HTML, eu originalmente queria fazer cada chave clicável, então a nota seria reproduzida no clique. Mais tarde, descobri que clicar em cada tecla conseguiu uma jogada muito mais lenta e um som não realista. Então eu liguei e atribuí cada som a uma tecla pressionada individualmente. Isso permitiu melodias mais complexas e um som mais realista. No entanto, há vantagens em fazer os dois, então neste tutorial, vou mostrar como você pode fazer qualquer um dos métodos, caso você queira acompanhar.

1a – teclas HTML

Se você decidir fazer as teclas clicáveis, um problema em que você pode correr muito rapidamente é que as chaves são formas complexas. Eles se encaixam um ao outro como peças de quebra-cabeças. Em HTML e CSS, é fácil criar quadrados e retângulos, mas um pouco mais difícil de criar formas mais complexas. Como eu queria ter efeitos de mouseover, eu precisava que cada chave fosse separada. Em vez de tentar fazer retângulos simples e depois descobrir o alinhamento e os índices z, fui com uma abordagem muito mais difícil e desnecessária: polígonos svg. A tag "SVG" permite que você crie formas que, de outra forma, seriam impossíveis. Então eu criei cada chave com pontos específicos nas formas que eu queria. Para que você não tenha que descobrir isso, estou incluindo esses elementos aqui:

 <svg class = "piano" height = "230" width = "1000"> 
<pontos de polígono = "200,10 230,10 230,100 245,100 245,220 200,220 200,10" classe = "branco" id = "c" chave de dados = "65" />


<pontos de polígono = "245,100 260,100 260,10 275,10 275,100 290,100 290,220 245,220 245,100" class = "branco" chave de dados = "83" id = "d" />


<pontos de polígono = "305,10 335,10 335,220 290,220 290,100 305,100 305,10" classe = "branco" chave de dados = "68" id = "e" />


<pontos de polígono = "335,10 365,10 365,100 380,100 380,220 335,220 335,10" classe = "branco" chave de dados = "70" id = "f" />


<pontos de polígono = "380,100 395,100 395,10 410,10 410,100 425,100 425,220 380,220 380,100" classe = "branco" chave de dados = "71" id = "g" />


<pontos de polígono = "425,100 440,100 440,10 455,10 455,100 470,100 470,220 425,220 425,100" classe = "branco" chave de dados = "72" id = "a" />

<pontos de polígono = "470,100 485,100 485,10 515,10 515,220 470,220 470,100" classe = "branco" chave de dados = "74" id = "b" />

<pontos de polígono = "515,10 545,10 545,100 560,100 560,220 515,220 515,10" classe = "branco" chave de dados = "82" id = "chave5" />

<pontos de polígono = "560,100 575,100 575,10 590,10 590,100 605,100 605,220 560,220" classe = "branco" chave de dados = "84" id = "chave5" />

<pontos de polígono = "605,100 620,100 620,10 650,10 650,220 605,220 605,100" classe = "branco" chave de dados = "89" id = "chave5" />

<pontos de polígono = "650,10 680,10 680,100 695,100 695,220 650,220 650,10" classe = "branco" chave de dados = "85" id = "chave5" />

<pontos de polígono = "695,100 710,100 710,10 725,10 725,100 740,100 740,220 695,220 695,100" class = "branco" chave de dados = "73" id = "key5" />

<pontos de polígono = "740,100 755,100 755,10 770,10 770,100 785,100 785,220 740,220 740,100" class = "branco" chave de dados = "79" id = "key5" />

<pontos de polígono = "785,100 800,100 800,10 830,10 830,220 785,220 785,100" classe = "branco" chave de dados = "80" id = "chave5" />

<pontos de polígono = "230,10 260,10 260,100 230,100 230,10" classe = "preto" chave de dados = "49" id = "c_sharp" />
<pontos de polígono = "275,10 305,10 305,100 275,100 275,10" classe = "preto" chave de dados = "50" id = "d_sharp" />
<pontos de polígono = "365,10 395,10 395,100 365,100 365,10" classe = "preto" chave de dados = "51" id = "f_sharp" />
<pontos de polígono = "410,10 440,10 440,100 410,100 410,10" classe = "preto" chave de dados = "52" id = "g_sharp" />
<pontos de polígono = "455,10 485,10 485,100 455,100 455,10" classe = "preto" chave de dados = "53" id = "a_sharp" />
<pontos de polígono = "545,10 575,10 575,100 545,100 545,10" classe = "preto" chave de dados = "54" id = "chave4" />
<pontos de polígono = "590,10 620,10 620,100 590,100 590,10" classe = "preto" chave de dados = "55" id = "chave4" />
<pontos de polígono = "680,10 710,10 710,100 680,100 680,10" classe = "preto" chave de dados = "56" id = "chave4" />
<pontos de polígono = "725,10 755,10 755,100 725,100 725,10" classe = "preto" chave de dados = "57" id = "chave4" />
<pontos de polígono = "770,10 800,10 800,100 770,100 770,10" classe = chave de dados "preta" = "48" id = "chave4" />
</ svg>

Cada elemento poligonal está incluído na etiqueta SVG e é definido por uma série de pontos. O polígono Eacn também é atribuído a uma classe, seja preto ou branco, correspondente a chaves pretas ou brancas. Aqui estão os estilos de aula que fiz para aqueles:

 .white { 
preencher: branco;
acidente vascular cerebral: preto;
Largura do curso: 1;
cursor: ponteiro;
margem: 2px;
}
 .white: hover { 
preencher: # 9e9e9e;
acidente vascular cerebral: lightblue;
cursor: ponteiro;
Largura do curso: 1;
Esboço: sólido preto 1px;
}
 .Preto { 
preencher: preto;
Largura do curso: 1;
cursor: ponteiro;
margem: 2px;
}
 .black: hover { 
preencher: # 515151;
acidente vascular cerebral: lightblue;
Largura do curso: 1;
Esboço: sólido preto 1px;
}

Isso define a cor e a borda, bem como os efeitos de deslocamento para cada uma das chaves. Mesmo que as teclas em preto e branco estejam próximas um ao outro em um teclado, para mim era mais fácil agrupar as chaves preto e branco separadamente no documento HTML. Cada polígono também possui uma ID. A maioria das IDs corresponde à nota associada a esse polígono, começando pelo meio "C" todo o caminho até "B" da próxima oitava. Você também notará que, além de um ID, cada elemento de polígono também possui uma chave de dados, com essa aparência:

 chave de dados = "80" 

O que isso faz, é associar cada elemento com pressionar a tecla. Cada tecla no teclado possui um número. Então, neste momento, podemos clicar na tecla na tela ou pressionar uma tecla do teclado para que o programa possa tocar um som para nós. No entanto, uma vez que o usuário não saberá instintivamente qual tecla de teclado corresponde a cada som, incluí uma exibição no HTML para mostrar isso ao usuário.

 <div class = "keysNotes"> 
<h3> nota </ ??h3>
<h3> c </ h3>
<h3> d </ h3>
<h3> e </ h3>
<h3> f </ h3>
<h3> g </ h3>
<h3> a </ h3>
<h3> b </ h3>
<h3> c </ h3>
<h3> d </ h3>
<h3> e </ h3>
<h3> f </ h3>
<h3> g </ h3>
<h3> a </ h3>
<h3> b </ h3>
</ div>
 <div id = "keysshow" class = "keysNumbers"> 
<h3> chaves </ h3>
<h3> A </ h3>
<h3> S </ h3>
<h3> D </ h3>
<h3> F </ h3>
<h3> G </ h3>
<h3> H </ h3>
<h3> J </ h3>
<h3> R </ h3>
<h3> T </ h3>
<h3> Y </ h3>
<h3> U </ h3>
<h3> I </ h3>
<h3> O </ h3>
<h3> P </ h3>
</ div>

Então, no CSS para essas classes, eu tenho o seguinte:

 .keysNumbers { 
cor: lightgray;
font-size: 40px;
font-family: monospace;
intensidade da fonte: Negrito;
índice z: 10;
largura: 740px;
exibir: flex;
flex-flow: row nowrap;
justify-content: space-between;
margem esquerda: 80px;
margem superior: -110px;
}
 .keysNotes { 
cor: cinza;
font-size: 40px;
font-family: monospace;
intensidade da fonte: Negrito;
índice z: 13;
largura: 740px;
exibir: flex;
flex-flow: row nowrap;
justify-content: space-between;
margem esquerda: 80px;
margem superior: -150px;
}

Isto alinha perfeitamente o número e o visor de teclas sobrepostos no topo das teclas na tela. Agora, precisamos trazer nossos arquivos de áudio. Eu procurei na internet e encontrei notas de piano para cada chave que eu precisava, exceto para F sharp, A sharp e ambos Fs. Como eu tive os outros sons-chave, tirei essas chaves em um programa gratuito chamado audácia, e alterei o campo para o tom mais alto ou mais baixo, e revendei o arquivo para obter o som que eu precisava. A Audacity é uma ótima ferramenta para isso, especialmente se você quisesse fazer algo como um guitarrista em vez de um piano, mas tinha gravações limitadas disponíveis para fazer seus sons iniciais. Em qualquer caso, não quero que você tenha que passar por todos esses problemas, então estou fornecendo os sons que eu reuni aqui de graça: https://www.dropbox.com/sh/buuc6h937s62vw2/AADHdRxmmDCfcatwjxvdwAvNa?dl= 0

Em seguida, eu estou colocando cada um desses arquivos no HTML com tags de áudio e também incluindo os códigos de teclas pressionados anteriormente nos elementos de polígono.

 <audio data-key = "65" id = "c_octave1_audio" src = "/ middle_c.mp3"> </ audio> 
<audio data-key = "49" id = "c_octave1_sharp_audio" src = "/ mid_c_sharp.mp3"> </ audio>
<audio data-key = "83" id = "d_octave1_audio" src = "/ middle_d.mp3"> </ audio>
<audio data-key = "50" id = "d_octave1_sharp_audio" src = "/ mid_d_sharp.mp3"> </ audio>
<audio data-key = "68" id = "e_octave1_audio" src = "/ middle_e.mp3"> </ audio>
<audio data-key = "70" id = "f_octave1_audio" src = "/ middle_f.mp3"> </ audio>
<audio data-key = "51" id = "f_octave1_sharp_audio" src = "/ mid_f_sharp.mp3"> </ audio>
<audio data-key = "71" id = "g_octave1_audio" src = "/ middle_g.mp3"> </ audio>
<audio data-key = "52" id = "g_octave1_sharp_audio" src = "/ mid_g_sharp.mp3"> </ audio>
<audio data-key = "72" id = "a_octave1_audio" src = "/ middle_a.mp3"> </ audio>
<audio data-key = "53" id = "a_octave1_sharp_audio" src = "/ mid_a_sharp.mp3"> </ audio>
<audio data-key = "74" id = "b_octave1_audio" src = "/ middle_b.mp3"> </ audio>

<audio data-key = "82" id = "c_octave2_audio" src = "/ high_c.mp3"> </ audio>
<audio data-key = "54" id = "c_octave2_sharp_audio" src = "/ high_c_sharp.mp3"> </ audio>
<audio data-key = "84" id = "d_octave2_audio" src = "/ high_d.mp3"> </ audio>
<audio data-key = "55" id = "d_octave2_sharp_audio" src = "/ high_d_sharp.mp3"> </ audio>
<audio data-key = "89" id = "e_octave2_audio" src = "/ high_e.mp3"> </ audio>
<audio data-key = "85" id = "f_octave2_audio" src = "/ high_f.mp3"> </ audio>
<audio data-key = "56" id = "f_octave2_sharp_audio" src = "/ high_f_sharp.mp3"> </ audio>
<audio data-key = "73" id = "g_octave2_audio" src = "/ high_g.mp3"> </ audio>
<audio data-key = "57" id = "g_octave2_sharp_audio" src = "/ high_g_sharp.mp3"> </ audio>
<audio data-key = "79" id = "a_octave2_audio" src = "/ high_a.mp3"> </ audio>
<audio data-key = "48" id = "a_octave2_sharp_audio" src = "/ high_a_sharp.mp3"> </ audio>
<audio data-key = "80" id = "b_octave2_audio" src = "/ high_b.mp3"> </ audio>

Você provavelmente quer incluir os arquivos de áudio em algum lugar do seu projeto, para que seu HTML realmente tenha acesso a eles. Agora que temos o nosso áudio, podemos codificar as teclas pressionadas ou os onclicks para disparar o som, ou ambos.

Se for a rota onclick, nosso JavaScript pode ser algo assim:

 function play(){ 
var audio = document.getElementById("audio");
audio.play();
}

Claro, substituindo "áudio" por qualquer elemento que desejemos desencadear a função. Uma nota de lado, se não fizermos nada além disso, você irá encontrar um problema, onde, se você clicar em um botão e depois um segundo, o segundo não será reproduzido até que o primeiro termine. Então, é bom incluir um pouco mais na função para parar qualquer som anterior, se um botão é clicado e, em seguida, reproduza apenas o som que foi clicado.

Qual caminho é melhor? OnClick ou KeyPress? Isso depende inteiramente de você, mas aqui estão os argumentos para ambos. O OnClick é melhor se o seu objetivo é ajudar você ou outra pessoa a aprender o piano. E é por isso que: se o seu programa depende de pressionar a tecla, o usuário mais freqüentemente irá associar a chave usada com a letra exibida na chave e não a letra associada à nota. Então, você poderia ter a tecla "C" tocar o "meio C" e assim por diante, mas isso só funciona se você tiver apenas uma oitava. E então "C sharp" etc também é complicado. Eventualmente, você terá o usuário pressionando "G" para tocar "F alto" e "S" para tocar "A sharp". Você pode ver como isso ficaria confuso para alguém que está tentando aprender o piano. O OnClick permite que o usuário apenas associe o som à letra exibida, e não a letra no teclado. No entanto, clicar para cada som leva a um som de piano muito mais complicado, que não é muito fluido ou fácil de jogar. Na minha opinião, tampouco é realmente uma ótima ferramenta para aprender um instrumento musical, razão pela qual fui com a abordagem da tecla pressionada, o que é mais divertido de jogar. Peguei um pouco do JavaScript para isso de um Kit de bateria em um dos projetos da Wes Bos, mas é uma ótima abordagem para esse tipo de projeto. Aqui está o que parece:

 window.addEventListener ('keydown', função (e) { 
const audio = document.querySelector (`audio [data-key =" $ {e.keyCode} "]`);
const key = document.querySelector (`.key [data-key =" $ {e.keyCode} "]`);
se (! audio) retornar;
audio.currentTime = 0;
audio.play ();
key.classList.add ('ativo')

Em vez de ter que obter cada elemento por ID, você simplesmente escuta uma pressão de tecla e combina com a chave de dados correspondente, que já temos em nosso HTML. Ele reproduz o som associado a essa tecla, que já temos em nosso HTML também. Finalmente, estamos dizendo se não há nenhum áudio, jogá-lo e estamos configurando o audio.currentTime para 0, o que ajuda com a espera que mencionei anteriormente, permitindo que a tecla pressionada para tocar e parar outros sons Atualmente ainda está sendo reproduzido.

Neste ponto, você deve poder trabalhar o seu piano com teclas pressionadas ou com os onclicks, o que você preferir, e seu piano é jogável, espero. A única outra coisa que fiz extra foi iniciar um banco de dados de "partituras", para que você pudesse selecionar uma música para jogar, e isso lhe dará as notas para essa música. Você pode ver isso no meu exemplo anteriormente.

A parte difícil para isso, é isso, não há um atalho fácil. Se você criar um banco de dados semelhante, você deve adicionar manualmente as notas para cada música. No entanto, se alguém tiver uma ótima maneira de transpor as partituras para dados JSON … Eu sou "ouvidos".

Uma vez que eu tinha as notas guardadas em um banco de dados, eu poderia apenas fazer uma chamada api quando a música é selecionada e recuperar as notas associadas à música selecionada. Você também pode dar um passo adiante, e deixar a música ser tocada primeiro, ou qualquer número de complementos adicionais. De qualquer forma, espero que goste, e divirta-se tanto com o que fiz. Se você tiver algum comentário, por favor me avise!

Texto original em inglês.