Música, maestro Raspberry Pi!

"A música é a matemática que canta. A matemática, a música que conta."

Se eu tenho uma frustração na vida e nunca ter aprendido a tocar um instrumento decentemente. Estudei piano por cinco longos anos e não toco nem o bife, estudei clarineta, tenho um violão (nesse até fiz minhas serenatas em Viçosa, mas tb não toco). Não desisto, agora mesmo ando pensando em comprar um teclado melhor. Como sou um cara bom de teoria, leio partitura, tenho noções de arranjo e composição, ou seja, o que dei conta de aprender, aprendi. E de vez em quando a música traz alguma diversão além da audição e aplauso, que é o que normalmente me cabe nessa hostória.

Estou ajudando um aluno num projeto legal, uma harpa laser. Como ele é baseado no Pi, fomos estudar as alternativas pro bichinho lidar com o assunto, e como Python é a "melhor linguagem de programação da última semana", demos preferência a bibliotecas escritas para essa linguagem.

De cara achamos um negócio chamado SuperCollider. É simplesmente sensacional! Basicamente são três troços:

1)  scsynth, um sintetizador em tempo real muito sofisticado com um monte de plugins e outros gadgets.

2) sclang, uma linguagem de programação interpretada específica para esse ambiente.

3) scide, um IDE (editor de programas) que, claro, integra as duas outras ferramentas.

A instalação do SuperCollider no Pi foi bem fácil, seguimos basicamente o que está aqui. Como tem que compilar, é demorado, então se vc quiser brincar num dia convém colocar pra intalar na noite anterior... ;)

Pra fazer o Pi se comunicar com o SC estamos usando o pythonosc. A comunicação se dá no protocolo UDP, então eu acho que qualquer lib de UDP deve dar legal.

Ontem foi o dia em que comecei a me exercitar com a parte de software. Resolvi fazer um "pograminha" que, uma vez fornecido um tom e a escala (altura da nota), calcula-se a escala maior ou menor correspondente e toca-se a escala.

Usei aquela notação musical que chama as notas pelas letras A (lá) a G(sol), seguida de um # indicando o sustenido. Assim, D# é o nosso ré sustenido. Usei números para indicar a oitava, sendo que C0 é o dó grave da escala do piano e o A4 é o lá do "dó da chave", aquela oitava que fica no meio do piano, perto da chave de sua tampa. Como existem diferentes tipos de escala menor, escolhi a escala menor natural, mas se alguém quiser trocar, é só alterar a variável minorinterval para a estrutura correspondente.

Abaixo o código Python devidamente comentado.

import serial
import time
from pythonosc import osc_message_builder
from pythonosc import udp_client
from math import log2, pow

C0 = 16.352 #C0 pitch frequency im Hz
semitone = pow(2,1/12) #one semitone 
octave = semitone * 12

# SuperCollider UDP client, default port=57120
client = udp_client.SimpleUDPClient("127.0.0.1", 57120)

# Chromatic scale
pitchname = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#','G', 'G#', 'A', 'A#', 'B']

# scales intervals, 1 for tone, 0.5 for semitone. 
majorinterval = [1,1,0.5,1,1,1,0.5]

minorinterval = [1,0.5,1,1,0.5,1,1]

# stops SC sound
def silent():
    client.send_message("/print",0)

# Calculate corresponding frequency for a pitch (A..F) and a octave, using:
# f=2^(pich/12)*(C0frequency)
def freq(pitch, octv):
    global pitchname,C0
    p=pitchname.index(pitch) + (12 * octv)
    return pow(2,p/12)*C0

# play a note (pich) in a octave for duration (dur) in secs.
def play(pitch,octave,dur=0.5):
    client.send_message("/print",freq(pitch,octave))
    time.sleep(dur)

# return a scale based in tonic (fundamental note), octave and scale type (major False=minor scale')
# Example: if you call getScale('C',4,False) it returns:
# [('C',4),('D',4),('D#',4),('E',4),('F,4),('F#',]
def getScale(tonic,octv,major):
    interval = majorinterval if major else minorinterval
    s=[]
    n=pitchname.index(tonic)
    for i in range(0,7):
        s.append((pitchname[n],octv))
  # if we reach the end of chromatic scale, should go back to beginning and increment scale
        if n == 11:
            n=-1
            octv+=1
        n+=int(2 * interval[i % 7])
    return s

# Play a scale generate by getScale
def playScale(scale):
    for pitch,octave in scale:
        play(pitch,octave)
    silent() 

s=getScale('C',4,True)
print(s)
playScale(s)

s=getScale('A',3,False)
print(s)
playScale(s)

s=getScale('F#',6,False)
print(s)
playScale(s)

s=getScale('D',3,True)
print(s)
playScale(s)

Abaixo, a saída da rotina acima rodando, escalas de CM, Am, F#m, DM, respectivamente na quarta, terceira, sexta e terceira oitavas a partir do dó mais grave do piano.


[('C', 4), ('D', 4), ('E', 4), ('F', 4), ('G', 4), ('A', 4), ('B', 4)]
[('A', 3), ('B', 3), ('C', 4), ('D', 4), ('E', 4), ('F', 4), ('G', 4)]
[('F#', 6), ('G#', 6), ('A', 6), ('B', 6), ('C#', 7), ('D', 7), ('E', 7)]
[('D', 3), ('E', 3), ('F#', 3), ('G', 3), ('A', 3), ('B', 3), ('C#', 4)]

Aqui o código SuperCollider, que basicamente recebe a mensagem de frequência e a toca:

(
SynthDef.new(\teste, {|
 freq = 440, gate = 1,
amp = 0.5, out = 0|
 var synt1;

 synt1 = SinOsc.ar(freq,0);

 Out.ar(out,synt1);
}).add;

~x=Synth(\teste, [\freq, 440]);

~a = OSCdef(\oscTeste,
 {
   | ... msg | msg.postln;

   ~x.set(\freq, msg);
 },
 '/print'
);
)

Por fim, um videozinho da bagaça a funcionar:


É isso! À medida que o projeto for avançando, posto outras novidades.

Ah, e enquanto escrevia essas maltraçadas estava a escutar esse primor, #ficadica.

Abracadabrass!

Comentários

Postagens mais visitadas deste blog

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte III

Dividindo um programa (sketch) do Arduino em mais de um arquivo

Controle PID de Potência em Corrente Alternada - Arduino e TRIAC - Parte I