pyxen.Music
Una pista de audio en streaming adjunta a un Entity como componente.
La música se decodifica bajo demanda, lo que la hace adecuada para pistas largas como música de fondo y ambientación.
Para conceptos de audio, consulta Audio.
Constructor
Music(filename, *, volume=1.0, pan=0.0, loop=False)
filename es el único argumento posicional: el nombre de un recurso musical del proyecto, sin su extensión de archivo. Los otros tres son solo por palabra clave y fijan el valor inicial de la propiedad del mismo nombre (consulta Propiedades del componente).
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
filename | str | Nombre del recurso musical, sin extensión |
volume | float | Ganancia inicial. Por defecto 1.0 |
pan | float | Posición estéreo inicial, de -1.0 a 1.0. Por defecto 0.0 |
loop | bool | Volver a empezar al terminar la pista. Por defecto False |
Ejemplo
theme = Music("theme", volume=0.6, loop=True)
Adjuntar Music
Durante spawn:
bgm = world.spawn(music=Music("theme"))
O después:
entity.music = Music("theme")
Asignar música inicia la reproducción inmediatamente.
Propiedades del componente
Acceso mediante entity.music:
| Propiedad | Tipo | L/E | Descripción |
|---|---|---|---|
loop | bool | L/E | Activar bucle |
volume | float | L/E | Ganancia (limitada >= 0.0) |
pan | float | L/E | Paneo estéreo (-1.0 a 1.0) |
time | float | L | Posición actual de reproducción (segundos) |
duration | float | L | Duración total (segundos) |
finished | bool | L | True cuando la reproducción ha terminado |
channels | similar a lista | L | Las partes de la pista, cada una con su propio volume — consulta la sección Canales |
beat | int | L/E | Pulsos desde que empezó la reproducción. Asignarlo salta a esa posición — consulta Tiempo musical |
bar | int | L/E | Compases desde que empezó la reproducción. Asignarlo salta a esa posición |
phase | float | L | Cuánto ha avanzado el pulso actual, de 0.0 a 1.0 |
beats_per_bar | int | L | Pulsos por compás |
frames_to_beat | int | L | Frames hasta el siguiente pulso |
tempo | float | L/E | Multiplicador de la velocidad de reproducción — consulta Tempo |
bpm | int | L | Pulsos por minuto, tal como los indica el archivo |
loop
entity.music.loop = True
Cuando está activado, la pista se reinicia automáticamente al terminar. Típico para música de fondo.
volume
entity.music.volume = 0.5
Los valores por debajo de 0.0 se limitan a 0.0.
pan
entity.music.pan = -1.0 # totalmente a la izquierda
entity.music.pan = 1.0 # totalmente a la derecha
entity.music.pan = 0.0 # centro
Limitado al rango -1.0 a 1.0.
time
t = entity.music.time
Solo lectura. Posición actual de reproducción en segundos.
duration
d = entity.music.duration
Solo lectura. Duración total de la pista en segundos.
finished
if entity.music.finished:
world.destroy(entity)
Solo lectura. True cuando la pista ha terminado de reproducirse (y loop es False).
Canales
music = entity.music
for i in range(len(music.channels)):
print(i, music.channels[i].volume)
music.channels[1].volume = 0.0 # silenciar la parte 1
Un módulo tracker o un archivo MIDI es un conjunto de partes independientes que suenan con un único reloj. music.channels te da un manejador por parte, y cada uno tiene:
| Propiedad | Tipo | L/E | Descripción |
|---|---|---|---|
index | int | L | El número del canal |
volume | float | L/E | Ganancia de esta parte sola, limitada a 0.0–1.0 |
Todos los canales empiezan en 1.0. Como todas las partes comparten un único cabezal de reproducción, subir o bajar una nunca se desfasa del resto. Así se cambia la intensidad de una banda sonora: entra una parte cuando la acción se acelera y se quita cuando el jugador muere.
Lo que es un canal depende del formato:
| Formato | Canales |
|---|---|
| Módulo tracker | Uno por cada canal del módulo (un .mod típico tiene 4) |
| MIDI | Siempre 16, uno por canal MIDI. Un canal que el archivo no usa simplemente está en silencio |
| OGG | Ninguno: len(music.channels) es 0 |
Los canales se numeran desde 0. Pedir uno más allá del último lanza IndexError, y no se puede asignar a music.channels[i] en sí, solo a su volume. Un índice negativo cuenta desde el final, como en cualquier lista de Python: music.channels[-1] es el último canal.
En MIDI, poner un canal a 0.0 también corta las notas de ese canal que aún suenan.
Tiempo musical
if music.beat != last_beat:
last_beat = music.beat
pulse()
| Propiedad | Significado |
|---|---|
beat | Pulsos completos desde que empezó la reproducción. Es monótono: sigue contando a través de un bucle en lugar de volver a 0, así que puedes compararlo con el valor del frame anterior sin tratar el salto |
bar | Compases completos desde que empezó la reproducción, también monótono |
phase | Posición dentro del pulso actual, de 0.0 hasta (sin incluir) 1.0. Donde beat cambia a saltos, phase es continua: úsala para todo lo que deba suavizarse o decaer entre pulsos |
beats_per_bar | Pulsos en un compás |
frames_to_beat | Frames hasta que llegue el siguiente pulso, redondeado hacia arriba. Úsalo para lanzar algo en el pulso en lugar de justo después |
Asignar beat o bar salta a esa posición. music.bar = 8 salta al inicio del compás 8 con una sola escritura, y todos los canales siguen sincronizados. El frame siguiente lee la nueva posición.
Los contadores miden el tiempo a partir del archivo, así que el formato importa:
| Formato | beat, bar, phase, frames_to_beat, beats_per_bar, bpm |
|---|---|
| Módulo tracker | Disponibles. Consulta más abajo |
| MIDI | Disponibles, leídos del archivo. Ver más abajo |
| OGG | No disponibles: beat, bar, beats_per_bar, frames_to_beat y bpm valen -1, phase vale 0.0, y asignar beat o bar no hace nada |
Por tanto, un juego que se sincroniza con una música que puede ser OGG debería comprobar una vez que music.beat >= 0 y recurrir a su propio temporizador cuando valga -1.
Pulsos en un archivo MIDI
Un archivo MIDI indica su propio tiempo musical, y Pyxen lo lee. Un pulso es la negra del archivo, bpm y la duración de un pulso siguen los cambios de tempo del archivo mientras suena, y beats_per_bar sale del primer compás que declara el archivo, contado en negras: 3/4 da 3, y 6/8 también. Un archivo que no declara compás está en cuatro.
Cuando una pista MIDI se repite en bucle, la cuenta pasa al siguiente compás entero: beat y bar siguen aumentando, y cada pasada empieza en el primer pulso de un compás. Un salto cae exactamente en el pulso; una nota que ya sonaba antes de ese punto no se toca.
Pulsos en un módulo tracker
Un módulo no indica compás, así que Pyxen usa la única definición que sí lleva un módulo: su BPM. El tick de un tracker dura 2.5 / BPM segundos, lo que hace que el BPM del archivo sea exacto justo cuando un pulso son 24 ticks: cuatro filas a velocidad 6, u ocho filas a velocidad 3. Así cuenta Pyxen. Contar ticks en lugar de filas mantiene el pulso estable cuando el módulo cambia de velocidad.
Un compás tiene cuatro pulsos. Es una convención, no un dato del archivo: un módulo en 3/4 seguirá informando beats_per_bar como 4. Si tu tema está escrito con otro número de ticks por pulso, el contador no coincidirá con lo que oyes.
Tempo
music.tempo = 1.5 # 50 % más rápido, mismo tono
music.tempo = 1.0 # normal
tempo multiplica la velocidad que pide la pista. No se remuestrea nada, así que el tono no cambia: las partes simplemente llegan antes o después. bpm sigue informando el valor propio del archivo sea cual sea tempo.
Límites: 8.0 como máximo. Por abajo, un módulo tracker no baja de 0.25, y un valor que rechaza se ignora (la propiedad conserva su valor anterior); MIDI acepta hasta 0.05. tempo no tiene efecto en OGG, donde siempre vale 1.0.
beat, phase y frames_to_beat siguen a tempo, de modo que un juego sincronizado con la música sigue sincronizado cuando la aceleras.
Ejemplos completos
Un pulso en cada tiempo
Este ejemplo supone un módulo tracker llamado theme.mod en el proyecto.
from pyxen import *
last_beat = -1
def start():
world.spawn(name="song", music=Music("theme", loop=True))
world.spawn(name="pulse", x=160, y=90,
shape=Circle(24, fill=(1.0, 0.5, 0.2, 1.0)))
def update():
global last_beat
music = world.get(name="song").music
pulse = world.get(name="pulse")
# phase va de 0 a 1 dentro de cada pulso: empieza grande en el pulso y se calma.
s = 1.0 + 0.5 * (1.0 - music.phase)
pulse.scale = (s, s)
# beat solo cambia una vez por pulso.
if music.beat != last_beat:
last_beat = music.beat
print("beat", last_beat, "bar", music.bar)
Silenciar una parte
Haz clic para apagar y volver a encender la segunda parte de la pista.
from pyxen import *
def start():
world.spawn(name="song", music=Music("theme", loop=True))
def update():
music = world.get(name="song").music
if input.mouse.left.pressed:
part = music.channels[1]
if part.volume > 0.0:
part.volume = 0.0
else:
part.volume = 1.0
Saltar a un compás
from pyxen import *
def start():
world.spawn(name="song", music=Music("theme", loop=True))
def update():
music = world.get(name="song").music
if input.mouse.left.pressed:
music.bar = 4 # saltar directamente al compás 4
Formato
Un recurso musical puede ser OGG Vorbis (.ogg), MIDI (.mid, .midi) o un módulo de tracker: ProTracker (.mod), FastTracker II (.xm), Impulse Tracker (.it) o Scream Tracker 3 (.s3m).
El recurso se busca por su nombre base: Music("theme") encuentra theme.ogg, theme.mod o la de esas extensiones que exista en el proyecto. Pasar la extensión es el error más frecuente: Music("theme.ogg") busca theme.ogg.ogg y lanza una excepción.
Un módulo de tracker suele ocupar unos pocos kilobytes donde la misma pieza en OGG ocupa megabytes, y se repite sin costuras por construcción — ideal para un juego pequeño.
También es el formato que más ofrece aquí: canales, pulsos, compases y tempo funcionan todos en un módulo. MIDI tiene canales y tempo; OGG no tiene ninguno de los dos (consulta las secciones Canales y Tiempo musical).
Los módulos respetan el paneo: el comando 8xx de ProTracker mueve un canal en el campo estéreo mientras suena la canción.
A diferencia de Sound (WAV, cargado completamente en memoria), Music se transmite y decodifica bajo demanda — más adecuado para audio largo.
Errores comunes
Nombre de archivo faltante
Music()
ValueError: Music must have a file
Recurso inválido
Music("nonexistent")
Music("theme.ogg") # la extensión va en el archivo, no en el nombre
ValueError: Music 'nonexistent' not found. Use the asset name without its extension (.ogg, .mid, .mod, .xm, .it, .s3m).