Arpegiador con voz y TensorFlow Lite en Python

por | agosto 15, 2026
A modern home music studio with a laptop running Python code, a MIDI keyboard, studio monitors and a microphone on a boom arm. The screen shows waveform and code, soft ambient lighting, photorealistic

Voy a mostrarte cómo construir un arpegiador generativo que obedece a tu voz. No es magia: es Python, TensorFlow Lite para reconocer palabras sueltas y un motor de cadenas de Markov para las notas. El resultado lo puedes mandar por MIDI o escucharlo con síntesis FM. Suena ambicioso, pero con los trozos de código que siguen lo tienes listo en una tarde.

¿Qué necesitas?

Antes de escribir código, asegúrate de tener Python 3.8 o superior. Instala las dependencias con pip:

  • tensorflow-lite (el intérprete ligero para el modelo de voz)
  • sounddevice (captura de audio en tiempo real)
  • numpy (procesamiento de señales y matemática)
  • mido (enviar mensajes MIDI a tu DAW o sintetizador virtual)
  • python-rtmidi (backend para mido, a veces necesario)

Si quieres escuchar sin DAW, añade también pyfluidsynth y un SoundFont. El micrófono de tu portátil sirve para empezar.

Arpegiador con voz y TensorFlow Lite en Python

Paso 1: Capturar audio y reconocer comandos con TensorFlow Lite

El modelo que usaremos es el de comandos de voz preentrenado de TensorFlow Lite. Es pequeño (menos de 1 MB) y funciona sin conexión. Reconoce palabras como ‘up’, ‘down’, ‘left’, ‘right’, ‘go’, ‘stop’, entre otras. Para nuestros comandos en español tendrás que entrenar el tuyo o mapear palabras en inglés: por ejemplo, ‘faster’ puede ser ‘up’, ‘slower’ es ‘down’, ‘random’ es ‘left’, ‘scale’ es ‘right’. No es perfecto, pero para una demo es suficiente.

El siguiente fragmento carga el modelo, graba un segundo de audio y devuelve la etiqueta reconocida:

import sounddevice as sd
import numpy as np
import tflite_runtime.interpreter as tflite

interpreter = tflite.Interpreter(model_path='speech_commands.tflite')
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()

def record_audio(duration=1.0, sr=16000):
    audio = sd.rec(int(duration * sr), samplerate=sr, channels=1, dtype='float32')
    sd.wait()
    return audio.flatten()

def recognize_command(audio):
    # Normalizar y ajustar al tamaño esperado por el modelo
    audio = np.pad(audio, (0, 16000 - len(audio)), 'constant')
    input_data = audio.reshape(input_details[0]['shape'])
    interpreter.set_tensor(input_details[0]['index'], input_data)
    interpreter.invoke()
    output_data = interpreter.get_tensor(output_details[0]['index'])
    label_index = np.argmax(output_data)
    # Supón que tienes una lista de etiquetas en order
    labels = ['silence', 'unknown', 'up', 'down', 'left', 'right', 'go', 'stop', 'yes', 'no']
    return labels[label_index]

Aquí record_audio captura justo un segundo; en el bucle principal lo llamarás repetidamente. El modelo espera muestras a 16 kHz, por eso la frecuencia de sampleo está fijada.

Paso 2: Generar patrones con cadenas de Markov

Una cadena de Markov decide la siguiente nota basándose solo en la nota actual. Para un arpegiador, definimos estados como grados de la escala (por ejemplo, 0=tónica, 1=segunda, etc.) y una matriz de transición que indica la probabilidad de saltar de un estado a otro. Si quieres que suene musical, pon más probabilidad en movimientos por terceras o quintas.

import random

# Grados de la escala mayor (semitones desde la tónica)
scale_degrees = [0, 2, 4, 5, 7, 9, 11, 12]

# Matriz de transición para 8 estados (filas suman 1)
transition_matrix = [
    [0.1, 0.2, 0.3, 0.1, 0.2, 0.05, 0.04, 0.01],
    [0.3, 0.1, 0.2, 0.2, 0.1, 0.05, 0.04, 0.01],
    [0.2, 0.3, 0.1, 0.2, 0.1, 0.05, 0.04, 0.01],
    [0.1, 0.2, 0.3, 0.1, 0.2, 0.05, 0.04, 0.01],
    [0.2, 0.1, 0.2, 0.3, 0.1, 0.05, 0.04, 0.01],
    [0.1, 0.1, 0.1, 0.2, 0.4, 0.05, 0.04, 0.01],
    [0.1, 0.1, 0.1, 0.1, 0.2, 0.3, 0.1, 0.0],
    [0.3, 0.2, 0.2, 0.1, 0.1, 0.05, 0.04, 0.01],
]

def next_note(current_state):
    return random.choices(range(8), weights=transition_matrix[current_state])[0]

Cuando el usuario dice ‘modo aleatorio’, simplemente ignoras la matriz y eliges un grado uniformemente. Para ‘cambiar escala’, rotas scale_degrees o cargas otra lista (menor, pentatónica, etc.).

Paso 3: Sintetizar sonido con FM y enviar MIDI

La síntesis por modulación de frecuencia (FM) es ideal aquí porque se puede hacer con matemática pura en tiempo real usando numpy. Generas una portadora y una moduladora, y la señal resultante tiene armónicos ricos. Si prefieres usar un sintetizador virtual o DAW, envía mensajes MIDI con mido. Te muestro ambas opciones.

import mido
from mido import Message

# Abre un puerto MIDI virtual (en Windows usa loopMIDI, en macOS IAC Driver, en Linux ALSA)
outport = mido.open_output('mi_puerto_midi')

def send_midi_note(note_number, velocity=100, duration=0.2):
    outport.send(Message('note_on', note=note_number, velocity=velocity))
    time.sleep(duration)
    outport.send(Message('note_off', note=note_number, velocity=0))

# Para audio directo con FM:
import sounddevice as sd

def fm_synth(frequency, duration=0.5, sr=44100):
    t = np.linspace(0, duration, int(sr * duration), endpoint=False)
    modulator = np.sin(2 * np.pi * frequency * 2 * t) * (frequency * 1.5)
    carrier = np.sin(2 * np.pi * frequency * t + modulator)
    envelope = np.exp(-3 * t / duration)  # decaimiento exponencial
    audio = carrier * envelope * 0.3
    sd.play(audio, samplerate=sr)
    sd.wait()

Con mido necesitas configurar un puerto MIDI virtual. Si no, usa fm_synth para escuchar directamente por los altavoces.

Paso 4: Integrar todo en un bucle principal

Ahora unimos las piezas. El bucle graba audio constantemente, reconoce comandos cada segundo y ajusta los parámetros del arpegiador. Mientras tanto, un temporizador lanza notas según el tempo actual. Usa time.time() para no bloquear el audio.

import time

tempo = 120  # BPM
current_state = 0
scale = scale_degrees
mode = 'markov'
running = True

last_note_time = time.time()

while running:
    # Captura y reconoce cada 1 segundo
    audio = record_audio(duration=1.0)
    command = recognize_command(audio)

    if command == 'up':
        tempo = min(240, tempo + 10)
        print('Tempo sube a', tempo, 'BPM')
    elif command == 'down':
        tempo = max(60, tempo - 10)
        print('Tempo baja a', tempo, 'BPM')
    elif command == 'right':
        # Cambiar a escala menor
        scale = [0, 2, 3, 5, 7, 8, 10, 12]
        print('Escala menor activada')
    elif command == 'left':
        mode = 'random'
        print('Modo aleatorio')
    elif command == 'go':
        mode = 'markov'
        print('Modo Markov')
    elif command == 'stop':
        running = False
        print('Deteniendo')

    # Generar nota si ha pasado el tiempo según tempo
    beat_duration = 60 / tempo / 2  # corcheas
    if time.time() - last_note_time >= beat_duration:
        last_note_time = time.time()
        if mode == 'random':
            state = random.randint(0, len(scale)-1)
        else:
            state = next_note(current_state)
            current_state = state
        note_number = 60 + scale[state]  # base Do4
        # Enviar por MIDI o sintetizar
        # send_midi_note(note_number)
        fm_synth(261.63 * (2 ** (scale[state]/12)), duration=0.1)  # frecuencia de Do4
        print('Nota:', note_number)

Este bucle es simplificado: el reconocimiento de voz tarda 1 segundo, así que la respuesta no es instantánea. Para una versión en vivo, usa un buffer circular y un hilo separado para el audio.

Código completo

Puedes juntar todos los fragmentos anteriores en un solo script. Asegúrate de descargar el modelo speech_commands.tflite y el archivo de etiquetas desde el repositorio oficial de TensorFlow. Si quieres comandos en español, tendrás que entrenar un modelo pequeño con tus propias grabaciones o usar un servicio en la nube, pero eso rompe la idea de local y ligero.

Posibles problemas y soluciones

  • Latencia alta: reduce la duración del audio capturado o usa un modelo aún más pequeño.
  • Reconocimiento pobre: prueba a normalizar el volumen, usar un micrófono mejor o entrenar con tu voz.
  • Sin sonido: comprueba que el puerto MIDI esté bien configurado o que el volumen no esté a cero en fm_synth.
  • Notas que se pisan: implementa un manejo de notas solapadas con note_off diferido.

Preguntas frecuentes

¿Puedo usar esto en una actuación en vivo?

Sí, pero necesitas hardware dedicado y un modelo de voz más robusto. El código es un punto de partida.

¿Funciona sin conexión a internet?

Totalmente. Una de las ventajas de TensorFlow Lite es que todo corre en local.

¿Puedo reconocer comandos en español?

Sí, pero tendrás que entrenar tu propio modelo con grabaciones de comandos en español. Hay tutoriales para eso.

¿Puedo usar otros sintetizadores?

Claro, cualquier sintetizador que acepte MIDI funcionará. Incluso puedes conectar un hardware externo.

Con esto ya tienes un arpegiador que reacciona a tu voz y genera secuencias con un toque de aleatoriedad controlada. Pruébalo, modifica las matrices de Markov, añade más comandos y verás lo divertido que es tocar sin tocar.