Saltar al contenido principal

Módulo Sound

El módulo Sound proporciona síntesis básica, reproducción de samples PCM y reproducción de música para los programas JARU. En Windows usa SDL Audio. En ESP32, ESP32S3 y ESP32P4 puede usar el backend configurado en boot.cfg, normalmente i2s, buzzer o null.

Uso

use Sound

Antes de reproducir sonido se debe iniciar el sistema de audio:

Sound.init()

Al terminar, Sound.close() detiene la salida de audio y libera los recursos del backend:

Sound.close()

Frecuencia de muestreo

El mixer de JARU puede trabajar a 22050 Hz (por defecto) o a 44100 Hz. La frecuencia se elige por placa, en el campo audio.sample_rate del perfil de hardware (boot.cfg): dos placas del mismo proyecto pueden usar frecuencias distintas. El valor efectivo está disponible en la constante:

Sound.SAMPLE_RATE
{
"audio": {
"enable": true,
"backend": "i2s",
"sample_rate": 44100
}
}

Si el valor configurado no es 22050 ni 44100, la VM muestra un aviso al arrancar y continúa a 22050 Hz.

Una misma canción o efecto suena igual a ambas frecuencias: el tono de las notas, el tempo, las envolventes y el timbre del canal de ruido no dependen de la frecuencia de muestreo (el reloj del generador de ruido está fijado a 22050 Hz, como el reloj maestro de un chip de sonido clásico). Elegir 44100 Hz solo mejora la fidelidad: menos aliasing en los armónicos de los tonos y mejor reproducción de samples PCM, a cambio del doble de trabajo de mezcla por segundo.

Preview del IDE

El editor de música del IDE reproduce el preview a la frecuencia del perfil de placa activo en ese momento, así que lo que oyes en el tracker es lo que sonará en esa placa.

Configuración de audio en ESP32

Los campos más habituales dentro de boot.cfg.audio son:

CampoDescripción
enableActiva o desactiva el sistema de audio
backendBackend de salida: i2s, buzzer o null
sample_rate22050 (defecto) o 44100; otros valores generan un aviso y se usa 22050
block_framesTamaño del bloque de mezcla de la tarea de audio (64..512, defecto 256). Bloques menores = menos latencia de los SFX pero menos colchón anti-cortes: 128 recomendado para juegos a 22050 Hz (256 a 44100 Hz)
i2s_portPuerto I2S que se usará para la salida
mclk, bclk, ws, dout, dinPines del bus I2S
buzzer_pinPin usado por el backend buzzer
stereoDuplica la mezcla mono en dos canales cuando está activado
task_core, task_priority, task_stackConfiguración de la tarea de audio en ESP32

Funciones principales

FunciónDescripción
init()Inicializa el mixer y arranca el backend de audio
close()Detiene el backend y libera recursos
tone(channel, freq, volume [, waveform, durationMs, priority])Reproduce un tono
pulse(channel, freq, volume, duty [, durationMs, priority])Reproduce una onda de pulso
noise(volume, period [, mode, durationMs, priority])Reproduce ruido
fade(channel, targetVolume, durationMs)Desliza el volumen de un canal hasta targetVolume
slideFreq(channel, targetFreq, durationMs)Desliza la frecuencia de un canal (portamento)
slideDuty(channel, targetDuty, durationMs)Desliza el duty del pulso hacia targetDuty (1..99)
slideNoise(targetPeriod, durationMs)Desliza el periodo del canal de ruido
setRelease(channel, releaseMs)Cola de release al parar una nota (canales 0-4)
envelope(channel, attackMs, decayMs, sustainVol, releaseMs)Envolvente ADSR persistente del canal (0-4)
vibrato(channel, depth, rateTenthsHz [, delayMs])LFO de tono en canales 0-3; depth 0..255 (0 lo apaga), rate en décimas de Hz
pwm(channel, depth, rateTenthsHz [, delayMs])LFO del duty en canales 0-3, audible con onda PULSE; a fondo barre ±40 puntos de duty
tremolo(channel, depth, rateTenthsHz [, delayMs])LFO de amplitud en canales 0-4 (tonos y ruido); a fondo baja hasta el silencio
stop(channel)Detiene un canal
stopAll()Detiene todos los canales
setVolume(channel, volume)Cambia el volumen de un canal
setMasterVolume(volume)Cambia el volumen maestro
isPlaying(channel)Indica si un canal está activo
loadSample(id, bytes, originalFrequency)Carga un sample PCM
playSample(channel, id, playbackFrequency, loop)Reproduce un sample
unloadSample(id)Descarga un sample
loadMusic(bytes | ruta)Carga datos de música desde un buffer o desde un fichero
playMusic([loop])Reproduce la música cargada desde el principio
seekMusic(ms)Mueve el cabezal a ms milisegundos desde el inicio
pauseMusic()Pausa la música
resumeMusic()Reanuda la música
stopMusic()Detiene la música
setMusicVolume(volume)Volumen global de la música (0..255)
isMusicPlaying()Indica si la música está sonando
unloadMusic()Descarga la música
Frecuencia con decimales

Los parámetros freq de tone y pulse y targetFreq de slideFreq admiten decimales en hercios (por ejemplo tone(0, 65.406, 200) o slideFreq(0, 27.5, 300)) para afinar con precisión sub-hercio. Los enteros siguen funcionando igual que siempre.

LFOs por canal (vibrato, pwm y tremolo)

vibrato, pwm y tremolo son configuración persistente del canal: afectan a la nota que suena y a las siguientes hasta que se cambian (depth 0 los apaga). Cada nota nueva re-arma la fase del LFO y su retardo opcional delayMs. playMusic() limpia esta configuración, porque la música trae la suya propia por instrumento.

Cargar música desde fichero

loadMusic() acepta el formato binario propio de música de JARU. El fichero debe contener exactamente los mismos bytes que antes se pasaban a Sound.loadMusic(bytes): cabecera de versión, flags, tempo/volumen y eventos compactos del secuenciador.

El formato actual de música es .jmu v1 (única versión soportada; el destino de los slides de tono viaja en mili-hercios, con la misma precisión que las notas). El tracker puede emitir controles específicos para el canal de ruido: modo blanco/periódico/corto/oscuro, decay de percusión en ticks, slide de periodo y cambio inmediato de periodo.

use Sound

Sound.init()

if (Sound.loadMusic("Music/theme.jmu")) then
Sound.playMusic(true)
end

También se puede cargar desde una unidad concreta:

Sound.loadMusic("flash:Music/theme.jmu")
Sound.loadMusic("sd:Music/theme.jmu")
info

Esta función no decodifica WAV, MP3, OGG ni otros formatos de audio. Para música usa el formato compacto del secuenciador de JARU. Para PCM crudo sigue usando loadSample(id, bytes, originalFrequency).

Reproducir música desde una posición

playMusic() siempre arranca desde el principio. Para empezar (o saltar) a un punto concreto se usa seekMusic(ms), indicando la posición en milisegundos desde el inicio de la canción.

seekMusic() reconstruye el estado del secuenciador hasta ese punto (instrumento por canal, tempo, transpose, volumen, envolventes y vibrato) y conserva el estado de transporte:

Estado al llamarResultado
SonandoSalta a ms y sigue sonando (seek en vivo)
PausadaSe queda en ms pausada; resumeMusic() continúa desde ahí
Parada (cargada sin arrancar)Queda armada en pausa en ms; resumeMusic() la arranca desde ahí
use Sound

Sound.init()
Sound.loadMusic("Music/theme.jmu")

// Empezar la canción a partir del segundo 15
Sound.seekMusic(15000)
Sound.resumeMusic()
// Saltar mientras suena (por ejemplo, ir al estribillo)
Sound.playMusic(true)
Sound.seekMusic(30000) // salta a 0:30 y continúa
info

seekMusic() se mide en milisegundos (coherente con el resto del módulo) y respeta los cambios de tempo intermedios de la canción. Si ms supera la duración: si la canción tiene bucle, da la vuelta; si no, queda al final. Tras el salto, los canales permanecen en silencio hasta la siguiente nota del punto de destino. Llamar a playMusic() después de seekMusic() reinicia desde el principio a propósito; para arrancar desde el punto buscado usa resumeMusic().