# Professor Cristiano Teixeira
# Sob Licença Apache 2.0
# Falso Theremin Versão 6.0 "Efeito Fantasmagórico"
# Objetivo: Simulação de um falso Theremin com efeitos de
# vibrato, tremolo e portamento para som característico.
import machine, time, math
from machine import Pin, PWM
# ── Configuração dos buzzers ────────────────────────────────
buzzer1 = PWM(Pin(26), freq=1000, duty=0)
buzzer2 = PWM(Pin(25), freq=1000, duty=0)
# ── Frequências das notas musicais (em Hz) ──────────────────
B0 = 31; C1 = 33; CS1 = 35; D1 = 37; DS1 = 39
E1 = 41; F1 = 44; FS1 = 46; G1 = 49; GS1 = 52
A1 = 55; AS1 = 58; B1 = 62; C2 = 65; CS2 = 69
D2 = 73; DS2 = 78; E2 = 82; F2 = 87; FS2 = 93
G2 = 98; GS2 = 104; A2 = 110; AS2 = 117; B2 = 123
C3 = 131; CS3 = 139; D3 = 147; DS3 = 156; E3 = 165
F3 = 175; FS3 = 185; G3 = 196; GS3 = 208; A3 = 220
AS3 = 233; B3 = 247; C4 = 262; CS4 = 277; D4 = 294
DS4 = 311; E4 = 330; F4 = 349; FS4 = 370; G4 = 392
GS4 = 415; A4 = 440; AS4 = 466; B4 = 494; C5 = 523
CS5 = 554; D5 = 587; DS5 = 622; E5 = 659; F5 = 698
FS5 = 740; G5 = 784; GS5 = 831; A5 = 880; AS5 = 932
B5 = 988; C6 = 1047; CS6 = 1109; D6 = 1175; DS6 = 1245
E6 = 1319; F6 = 1397; FS6 = 1480; G6 = 1568; GS6 = 1661
A6 = 1760; AS6 = 1865; B6 = 1976; C7 = 2093; CS7 = 2217
D7 = 2349; DS7 = 2489; E7 = 2637; F7 = 2794; FS7 = 2960
G7 = 3136; GS7 = 3322; A7 = 3520; AS7 = 3729; B7 = 3951
# ── Tabelas de notas ────────────────────────────────────────
toque = (
"B0",
"C1","CS1","D1","DS1","E1","F1","FS1","G1","GS1","A1","AS1","B1",
"C2","CS2","D2","DS2","E2","F2","FS2","G2","GS2","A2","AS2","B2",
"C3","CS3","D3","DS3","E3","F3","FS3","G3","GS3","A3","AS3","B3",
"C4","CS4","D4","DS4","E4","F4","FS4","G4","GS4","A4","AS4","B4",
"C5","CS5","D5","DS5","E5","F5","FS5","G5","GS5","A5","AS5","B5",
"C6","CS6","D6","DS6","E6","F6","FS6","G6","GS6","A6","AS6","B6",
"C7","CS7","D7","DS7","E7","F7","FS7","G7","GS7","A7","AS7","B7"
)
notas = (
B0,
C1,CS1,D1,DS1,E1,F1,FS1,G1,GS1,A1,AS1,B1,
C2,CS2,D2,DS2,E2,F2,FS2,G2,GS2,A2,AS2,B2,
C3,CS3,D3,DS3,E3,F3,FS3,G3,GS3,A3,AS3,B3,
C4,CS4,D4,DS4,E4,F4,FS4,G4,GS4,A4,AS4,B4,
C5,CS5,D5,DS5,E5,F5,FS5,G5,GS5,A5,AS5,B5,
C6,CS6,D6,DS6,E6,F6,FS6,G6,GS6,A6,AS6,B6,
C7,CS7,D7,DS7,E7,F7,FS7,G7,GS7,A7,AS7,B7
)
# ── Estado do portamento ────────────────────────────────────
# Inicializa nas notas do meio do range de cada sensor
freq_suave1 = float(notas[30]) # região de C3
freq_suave2 = float(notas[42]) # região de C4
# ── Parâmetros dos efeitos (ajuste ao seu gosto) ───────────
VIBRATO_HZ = 6.5 # velocidade do vibrato (Hz)
VIBRATO_CENTS = 9 # intensidade do vibrato (Hz de desvio)
TREMOLO_HZ = 4.5 # velocidade do tremolo (Hz)
TREMOLO_PROF = 0.22 # profundidade do tremolo (0.0 a 0.5)
PORTAMENTO = 0.08 # suavidade do deslize (0.04=lento, 0.15=rápido)
DUTY_BASE = 512 # volume base (0-1023)
# ── Distâncias ──────────────────────────────────────────────
dmin = 8
dmax = 56
# ── Funções utilitárias ─────────────────────────────────────
def map_val(valor, de_min, de_max, para_min, para_max):
if valor < de_min:
valor = de_min
elif valor > de_max:
valor = de_max
return int((valor - de_min) * (para_max - para_min) / (de_max - de_min) + para_min)
def distancia1():
echo_timeout = 500 * 2 * 30
trigger = Pin(2, Pin.OUT, pull=None)
echo = Pin(4, Pin.IN, pull=None)
trigger.value(0)
time.sleep_us(5)
trigger.value(1)
time.sleep_us(10)
trigger.value(0)
try:
pulse_time = machine.time_pulse_us(echo, 1, echo_timeout)
except OSError as ex:
if ex.args[0] == 110:
return None # fora de alcance → silêncio
raise ex
return (pulse_time / 2) / 29.1
def distancia2():
echo_timeout = 500 * 2 * 30
trigger = Pin(19, Pin.OUT, pull=None)
echo = Pin(18, Pin.IN, pull=None)
trigger.value(0)
time.sleep_us(5)
trigger.value(1)
time.sleep_us(10)
trigger.value(0)
try:
pulse_time = machine.time_pulse_us(echo, 1, echo_timeout)
except OSError as ex:
if ex.args[0] == 110:
return None
raise ex
return (pulse_time / 2) / 29.1
# ── Loop principal ──────────────────────────────────────────
nota1 = 30
nota2 = 42
while True:
t = time.ticks_ms() / 1000.0 # tempo em segundos (float)
d1 = distancia1()
d2 = distancia2()
# ── Sensor 1 (grave: C3–B3, índices 25–36) ─────────────
if d1 is None or d1 < dmin or d1 > dmax + 8:
print("Silêncio 1!")
buzzer1.duty(0)
# portamento continua deslizando para a nota atual
# (mantém freq_suave1 parada — não atualiza nota1)
else:
nota1 = map_val(d1, dmin, dmax, 25, 36)
# ── Sensor 2 (agudo: C4–B4, índices 37–48) ─────────────
if d2 is None or d2 < dmin or d2 > dmax + 8:
print("Silêncio 2!")
buzzer2.duty(0)
else:
nota2 = map_val(d2, dmin, dmax, 37, 48)
# ── Portamento: deslize suave até a nota alvo ───────────
freq_alvo1 = float(notas[nota1])
freq_alvo2 = float(notas[nota2])
freq_suave1 = freq_suave1 * (1 - PORTAMENTO) + freq_alvo1 * PORTAMENTO
freq_suave2 = freq_suave2 * (1 - PORTAMENTO) + freq_alvo2 * PORTAMENTO
# ── Vibrato: oscilação leve na frequência ───────────────
vibrato = math.sin(2 * math.pi * VIBRATO_HZ * t) * VIBRATO_CENTS
freq1 = max(20, int(freq_suave1 + vibrato))
freq2 = max(20, int(freq_suave2 + vibrato))
# ── Tremolo: oscilação no volume ────────────────────────
tremolo = 1.0 - TREMOLO_PROF + TREMOLO_PROF * math.sin(2 * math.pi * TREMOLO_HZ * t)
duty = int(DUTY_BASE * tremolo)
# ── Aplica nos buzzers (apenas se não estiver em silêncio) ─
if d1 is not None and dmin <= d1 <= dmax + 8:
buzzer1.freq(freq1)
buzzer1.duty(duty)
print(f"Nota1: {nota1} ({toque[nota1]}) | freq={freq1}Hz | duty={duty}")
if d2 is not None and dmin <= d2 <= dmax + 8:
buzzer2.freq(freq2)
buzzer2.duty(duty)
print(f"Nota2: {nota2} ({toque[nota2]}) | freq={freq2}Hz | duty={duty}")
time.sleep_ms(20) # 50 ciclos/segundo para o vibrato ficar suave