Notas Musicais Artificiais (Revisitado — Parte 2)
Hoje você vai entender a diferença musical entre um “tom” — uma nota pura que possui uma amplitude constante ao longo do tempo — e uma nota tocada em um instrumento real.
Em processamento de sinais, nós costumamos diferenciar esses dois tipos de sinais como “sinal de potência” ou “sinal de energia”, respectivamente. Um sinal de potência é aquele que se repete periodicamente sem fim, ou seja, possui uma energia infinita. Já um sinal de energia é bem delimitado no domínio do tempo, possui um início e um final bem definidos, pois sua energia é finita!
Siga a leitura para ver como isso se aplica na música.
No meu último post, eu mostrei a ideia básica de que uma nota musical é formada por uma onda senoidal “pura”. Ao fim do artigo, geramos uma nota “Lá 440 Hz”. Na notação científica de altura do som (aqui, “altura” significa frequência: quanto mais “alto”, mais agudo), essa nota é conhecida como A4, e é usada para afinação de instrumentos.
É claro que a nota A4 não aparece nas músicas desse jeito “sem graça”, como um tom puro de telefone analógico (eu sei, #cringe, mas quem viveu a experiência de puxar um telefone do gancho e ouvir o tom de linha disponível vai sentir uma certa nostalgia).
Eles o chamam de Muddy Waters
Quando Muddy Waters toca o A4 na sua guitarra, aos 26 segundos de Mannish Boy, essa frequência tem um efeito muito mais “vivo” do que a nossa nota pura gerada no post anterior. Ouça você mesmo:
Você pode confirmar que essa nota é mesmo o A4 usando o simulador virtual do braço de guitarra abaixo. Vá direto na primeira corda (de cima pra baixo) e clique na 5ª posição da grade formada no braço (guitarristas costumam chamar de “5ª casa”). Enxergando o braço guitarra com uma tabela, seria a primeira linha, quinta coluna.
Na verdade, todas as notas do braço desse instrumento possuem uma frequência bem definida. Fisicamente, na guitarra, isso acontece pela vibração das cordas. Ao pressionar a corda em alguma posição do braço, você acaba restringindo o comprimento restante que a corda pode vibrar desde o corpo do instrumento até a posição do seu dedo. Além disso, a tensão com que cada corda está presa no braço faz com que essas vibrações sejam diferentes entre elas, mesmo tocando as cordas soltas. Algumas cordas estão com maior tensão (como se estivessem mais esticadas), gerando vibrações de frequências mais altas (sons mais agudos), enquanto cordas mais frouxas geram frequências mais baixas (sons mais graves).
Ponto de vista: olhando a forma de onda “de perto” e “de longe”
Mas afinal, por que o A4 da guitarra do Muddy Waters é diferente do tom puro que geramos com o nosso código da última publicação? Bom, existem vários fatores. Mas dois deles são muito importantes, e um deles será tratado nesta publicação.
Primeiramente, vamos olhar o formato da onda completa dos dois sinais. Eu usei o software Reaper para visualizar as formas de onda dos dois arquivos de áudio — nosso tom puro de 440 Hz e a nota que eu mencionei acima, tocada pelo Muddy Waters:
Primeiro, despreze o fato de que a faixa de baixo está “duplicada” (em stereo, dois canais, um para cada lado do seu fone de ouvido), enquanto a de cima não está (mono, só um canal foi gerado e será reproduzido da mesma maneira nos dois lados do seu fone).
Olhando assim, “de longe”, podemos ver que tem algo muito diferente entre os dois arquivos em relação ao “desenho” geral do som. Observe que a nossa nota mantém uma amplitude constante: ela aumenta e diminui a sua intensidade sempre oscilando entre os mesmos valores. Já a nota tocada pelo Muddy Waters começa numa certa amplitude, mas vai decrescendo seus limites superior e inferior de oscilação conforme o tempo passa.
Essa diferença é crucial, pois a nota tocada pelo Muddy Waters tem uma energia finita! Ele acerta a nota no instrumento, mas a energia que ele insere vai se dissipar e desaparecer, a corda vai parando de vibrar com o tempo. É um sinal de energia finita! Por outro lado, nosso tom artificial nunca acaba, ele tem energia infinita — em todos os períodos da onda, ele tem a mesma amplitude, não decai com o tempo.
Quando analisamos o sinal de som no domínio do tempo (a vibração ao longo do tempo), consideramos que a diferença entre as duas ondas está no envelope da nota.
Na música, o envelope descreve como um determinado som varia sua potência ao longo do tempo (em outras palavras, como a energia se distribui ao longo do tempo).
Neste artigo, vamos explorar o envelope das notas e melhorar nosso gerador de notas para considerar esse efeito!
Mas antes de entrar em detalhes sobre o envelope, preciso deixar mais uma coisa clara para você. Essa não é a única diferença entre as duas notas. Para entender isso, precisamos olhar a vibração das ondas “mais de perto”. Lembre-se dos conceitos apresentados na parte 1: uma frequência de 440 Hz executa 440 ciclos em 1 segundo, portanto cada um dos ciclos tem um período (duração) aproximado de 2,27 milissegundos (o inverso da sua frequência). Como as notas mostradas duram em torno de 1,5 segundos, precisamos dar um “zoom+” muito mais próximo para ver o que está acontecendo em cada uma delas. Veja a figura abaixo:
Surprise surprise, veja só. Os sons dos instrumentos não são tons puros! Isso porque cada instrumento é único e possui seu próprio timbre. Porém, o timbre não é o escopo desta publicação, e vamos ignorá-lo por enquanto (spoiler alert: esse vai ser o assunto da parte 3!).
O modelo de envelope ADSR
Agora que entendemos que está faltando uma peça importante no nosso gerador de notas — o envelope — vamos analisar com mais detalhes como podemos construí-lo. Queremos formatar o envelope do nosso tom puro de forma que ele fique mais próximo de uma nota gerada por um instrumento real.
Existe um modelo muito simples para o envelope de uma nota isolada, chamado de ADSR. As letras do acrônimo significam Ataque, Decaimento, Sustentação e Relaxação (sim, eu me puxei para forçar uma palavra com R em português na tradução de Release).
Em poucas palavras:
- o ataque é o crescimento da intensidade de som da nota desde o momento que o instrumentista a toca (seja uma corda ou uma batida de percussão) até o valor máximo (ou de pico) da sua amplitude; para quem estuda controle de sistemas ou algo semelhante, seria algo parecido com o efeito do overshoot;
- já o decaimento corresponde a uma queda da potência do sinal desde o seu valor máximo até um patamar de menor intensidade sobre o qual a nota será mantida por um tempo — o que nos leva ao próximo conceito, que é a
- sustentação, ou seja, um volume constante em que a vibração se mantém sem variar a sua potência — note que a intensidade de sustentação é sempre menor ou igual à intensidade máxima que o ataque chega, pois não seria natural que o volume voltasse a crescer (exceto no caso de microfonia, o efeito mágico da ressonância, mas isso é um assunto para o futuro);
- por fim, o efeito natural é que o intervalo de sustentação seja interrompido em algum momento com o relaxamento, ou seja, a energia inserida no instrumento começa a desvanecer, ao ponto de que a potência do sinal começa a diminuir até que a vibração termine completamente — a palavra release desse termo faz referência ao instrumentista soltando o pedal de sustentação do piano, por exemplo, e deixando a vibração da corda dissipar o restante da sua energia até que ela acabe (num violino, seria o fim do movimento do arco).
Para você entender o quanto esse modelo simples é capaz de transformar completamente o envelope do nosso tom puro, recomendo que você veja o vídeo do Justin DeLay (canal Reverb no YouTube):
Ok, muito interessante. Mas cadê o CÓDIGO?
Agora que você compreendeu o que está faltando no nosso tom puro, vamos ao que interessa. Como transformar o nosso código da parte 1 e adicionar um envelope?
Para fazer isso de forma mais eficiente, vamos precisar reformular o nosso código anterior e deixá-lo um pouco mais elegante.
A primeira mudança que eu fiz foi usar um pouco de orientação a objetos em Python e transformar a nossa nota musical em um objeto instanciado de uma classe própria. Isso quer dizer que poderemos reaproveitar o nosso código e gerar notas de uma forma muito mais fácil, algo tão simples como
A4 ← Nota(440 Hz) # para o lá 440 Hz
ou
C3 ← Nota(130.81 Hz) # para uma oitava abaixo do Dó central de um piano
Abaixo segue o trecho de código em Python para criação da classe NotaMusical.
A partir dessa classe, o nosso código principal que gera uma nota e salva um arquivo WAV fica tão simples quanto o código abaixo:
Veja que basta escolher os parâmetros básicos de frequência (TONE_FREQ), duração (NOTE_SPAN) e amplitude (MAX_AMPLITUDE) para instanciar um objeto da nota musical desejada. Se desejado, ainda salvamos o arquivo WAV da nota com o seu método write_wave implementado na própria classe.
Agora que temos o objeto de uma nota musical (e podemos instanciar qualquer outro objeto da mesma maneira, alterando apenas os parâmetros), podemos prosseguir para o envelope.
Como vimos antes, o envelope dá um “formato” para a nota ao longo do tempo. Pensando nisso, a minha solução para gerar o envelope vai ser criarmos uma “onda formatadora”, ou seja, um sinal com a mesma duração da Nota que foi gerada, mas que vai multiplicar a nota original e “desenhar” o espalhamento dela ao longo do tempo. A ideia é que possamos controlar os intervalos de ataque (A), decaimento (D), sustentação (S) e relaxamento (R) com parâmetros de entrada.
Abaixo segue uma proposta de classe que cria um modelo ADSR a partir de uma nota musical e outros parâmetros de entrada. Para que a publicação não fique muito longa, eu vou entregar para você o código da classe de envelope ADSR sem descrever como eu cheguei na solução. Mas logo a seguir, vou explicar alguns detalhes do que está presente neste código.
Observe que os principais parâmetros para inicializar um envelope são constantes numéricas para determinar a duração proporcional dos intervalos de A, D, S e R. Note que a soma deve ser igual a 1 (ou 100%), pois o envelope precisa ter duração máxima de uma duração da nota que está sendo formatada. Observe também que a própria nota musical é uma entrada. Logo isso vai fazer mais sentido, mas a intenção é que possamos extrair alguns parâmetros importantes da própria nota que vai ser formatada, como a sua duração máxima e amplitude máxima. O princípio básico do código é identificar o número de amostras correspondente para criar o pulso formatador em 4 partes (A, D, S e R) a partir dos índices de início e fim de cada parte, concatenando as partes em uma única onda formatadora no final.
Agora vem a parte interessante. Vamos adicionar um método formatador de envelope dentro da nossa classe NotaMusical. Esse método vai chamar a nossa classe do envelope ADSR sempre que for solicitado. Assim, ao instanciar uma nota, podemos logo a seguir determinar se queremos (ou não) formatar algum envelope no seu tom fundamental.
Note que o método adicionado (generate_ADSR) recebe como entrada a própria nota e mais um dicionário de parâmetros (adsr_dict), o qual de conter os parâmetros relevantes para o envelope que será gerado.
Gerando alguns exemplos de notas com envelope ADSR
Vamos ver agora como usar esses códigos. Abaixo, o nosso código principal do gerador de notas está modificado para que você acrescente os parâmetros importantes do envelope ADSR. No trecho abaixo, já está configurado para tentar gerar uma nota próxima do A4 do Muddy Waters.
Veja o resultado, e compare diretamente com a nota tocada em Mannish Boy:
Nesse caso particular, eu usei apenas Ataque e Decaimento, pois a nota da guitarra (sem pedaleiras, apenas ligada a um amplificador simples) não tem um tempo de sustentação bem definido (nem precisa de relaxamento se o guitarrista mantém o dedo pressionado sem puxar a corda de novo, como é o caso da nota que estamos tentando reproduzir).
Outra coisa que faz um pouco de diferença é a “suavidade do formatador”. Por isso eu criei dois tipos de envelope: um linear (são retas crescentes ou decrescentes que formatam os intervalos ADSR) e outro exponencial (os crescimentos e decaimentos são mais suaves, formatados por funções exponenciais). Veja o envelope com formatação exponencial logo abaixo:
Agora ouça e veja a no vídeo abaixo o resultado das duas notas que geramos, tocadas sempre após a nota original da guitarra do Muddy Waters:
Não é perfeito, mas ficou melhor que o nosso tom puro, certo? Será que você consegue ajustar melhor os parâmetros do ADSR pra tornar o som mais parecido? No próximo post vamos adicionar o timbre pra finalizar essa síntese. :)
Exemplos com outros envelopes
Abaixo vou deixar alguns exemplos usando outros tipos de envelope. Você pode usar o meu código e configurar da maneira que achar melhor para gerar a nota que você desejar. Para padronizar, vou deixar sempre a nota C4 (dó central do piano) com duração de 2 segundos e amplitude máxima 0.99. O restante dos parâmetros ADSR estarão indicados na legenda seguindo o formato da tupla (ATTACK_SPAN, DECAY_SPAN, SUSTAIN_SPAN, RELEASE_SPAN, SUSTAIN_GAIN, ENV_TYPE). Note também que eu não me preocupo em garantir que a soma dos parâmetros seja 100, uma vez que o código fornecido faz uma normalização da soma dos parâmetros antes de gerar o envelope.
Repositório no Github
A fim de organizar isso melhor, criei um repositório dedicado para este projeto. Está no Github, por esse link:
https://github.com/fontanads/music_generator
Por enquanto não tem nada mais lá além do que já foi mostrado nessa publicação. Mas a ideia é que eu siga atualizando o repositório nos próximos posts e traga a versão atualizada dos códigos através dele.
Para onde isso vai?
Quero fazer um disclaimer aqui. Eu não sou nenhum especialista em música ou processamento de áudio. Porém, esses são dois assuntos dos quais eu sou um entusiasta. Estou usando isso para exercitar um pouco de programação em Python e como forma de divulgação de conteúdos de tecnologia.
Essa energia para compartilhar coisas novas e originais aqui está vindo do projeto #AluraStars. Agradecimento especial ao Gabs Ferreira e à Alura por me motivarem a começar os meus próprios conteúdos.
No futuro também pretendo gerar conteúdo em vídeo e em podcast, relacionando com as publicações do blog — assim que sair algo diferente, eu anuncio aqui também. Portanto, se você gostou, inscreva-se para acompanhar mais e para receber notificação de novas publicações!
Deixe seu comentário caso você tenha gostado do post e escreva sugestões de assuntos de processamento de sinais ou ciência de dados que você gostaria de ver neste blog. :)