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ámetroTipoDescripción
filenamestrNombre del recurso musical, sin extensión
volumefloatGanancia inicial. Por defecto 1.0
panfloatPosición estéreo inicial, de -1.0 a 1.0. Por defecto 0.0
loopboolVolver 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:

PropiedadTipoL/EDescripción
loopboolL/EActivar bucle
volumefloatL/EGanancia (limitada >= 0.0)
panfloatL/EPaneo estéreo (-1.0 a 1.0)
timefloatLPosición actual de reproducción (segundos)
durationfloatLDuración total (segundos)
finishedboolLTrue cuando la reproducción ha terminado
channelssimilar a listaLLas partes de la pista, cada una con su propio volume — consulta la sección Canales
beatintL/EPulsos desde que empezó la reproducción. Asignarlo salta a esa posición — consulta Tiempo musical
barintL/ECompases desde que empezó la reproducción. Asignarlo salta a esa posición
phasefloatLCuánto ha avanzado el pulso actual, de 0.0 a 1.0
beats_per_barintLPulsos por compás
frames_to_beatintLFrames hasta el siguiente pulso
tempofloatL/EMultiplicador de la velocidad de reproducción — consulta Tempo
bpmintLPulsos 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:

PropiedadTipoL/EDescripción
indexintLEl número del canal
volumefloatL/EGanancia 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:

FormatoCanales
Módulo trackerUno por cada canal del módulo (un .mod típico tiene 4)
MIDISiempre 16, uno por canal MIDI. Un canal que el archivo no usa simplemente está en silencio
OGGNinguno: 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()
PropiedadSignificado
beatPulsos 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
barCompases completos desde que empezó la reproducción, también monótono
phasePosició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_barPulsos en un compás
frames_to_beatFrames 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:

Formatobeat, bar, phase, frames_to_beat, beats_per_bar, bpm
Módulo trackerDisponibles. Consulta más abajo
MIDIDisponibles, leídos del archivo. Ver más abajo
OGGNo 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).