511 lines
22 KiB
Text
511 lines
22 KiB
Text
[#_lesson_27_music_and_midi]
|
|
== Lección 27: Música y MIDI
|
|
|
|
image::learn/27-midi.png[La lección terminada, 480]
|
|
|
|
Esta lección son dos lecciones que da la casualidad de que viven juntas.
|
|
|
|
La primera mitad es música: cómo reproduce un juego una pieza de música como es
|
|
debido, por qué eso no es lo mismo que reproducir un efecto de sonido y qué
|
|
hacer cuando la música tiene que ir a compás del vídeo.
|
|
|
|
La segunda mitad es MIDI, que la mayoría de la gente no ha visto nunca y que le
|
|
da a un juego algo que no le da nada más. Al final, un teclado enchufado a tu
|
|
ordenador estará tocando tu juego.
|
|
|
|
[#_a_sound_effect_and_a_piece_of_music_are_different_things]
|
|
=== Un efecto de sonido y una pieza de música son cosas distintas
|
|
|
|
Ya sabes reproducir un sonido. La lección diez cargó un `.wav` con `soundLoad`
|
|
y lo lanzó con `soundPlay`, y si apuntas esas dos llamadas a una canción de
|
|
cuatro minutos, la reproducirán.
|
|
|
|
No lo hagas. Singe mantiene separados los efectos de sonido y la música, y la
|
|
separación merece la pena por cuatro razones.
|
|
|
|
* *Tienen sus propios volúmenes.* `soundSetVolume` nunca toca la música y
|
|
`musicSetVolume` nunca toca los disparos. Todos los juegos que se han
|
|
publicado lo han necesitado, porque los jugadores bajan la música y suben los
|
|
efectos.
|
|
* *La música no tiene límite de canales.* Los efectos de sonido comparten
|
|
dieciséis canales, y cuando los dieciséis están ocupados el decimoséptimo no
|
|
suena. Una pista de música no va en un canal, así que nada puede quitarle el
|
|
sitio.
|
|
* *Un handle es una pista*, y se pausa, se reanuda, se para y se desvanece por
|
|
su cuenta sin molestar a nada más.
|
|
* *La música se pausa con el juego.* Pulsa la tecla de pausa y se queda donde
|
|
está. Vuelve y sigue. No escribes nada para que eso pase.
|
|
|
|
Hay una cosa que *no* es distinta y que merece la pena saber, porque la gente
|
|
da por hecho lo contrario: Singe decodifica un archivo de música en memoria de
|
|
entrada, exactamente igual que un efecto de sonido. Una pista larga cuesta
|
|
memoria. No se va leyendo del disco a trozos. Cinco minutos de música son unas
|
|
cuantas decenas de megabytes de memoria mientras está cargada, así que carga la
|
|
pista del nivel en el que estás y descárgala con `musicUnload` cuando salgas.
|
|
|
|
[#_playing_a_track]
|
|
=== Reproducir una pista
|
|
|
|
Todos los lectores de este libro tienen ya una pieza de música, porque el motor
|
|
desempaquetó una en tu carpeta de trabajo la primera vez que ejecutaste algo.
|
|
Empieza un archivo con estas tres líneas:
|
|
|
|
[source,lua]
|
|
----
|
|
local theme = musicLoad("Singe/menuIntro.flac")
|
|
musicSetVolume(64)
|
|
musicPlay(theme, -1)
|
|
|
|
|
|
function onOverlayUpdate()
|
|
overlayClear()
|
|
overlayPrint(2, 2, "Music: " .. (musicIsPlaying(theme) and "playing" or "stopped"))
|
|
return OVERLAY_UPDATED
|
|
end
|
|
----
|
|
|
|
Ejecútalo con `Singe -R music`. Diez segundos de música, y luego vuelve a
|
|
empezar, y sigue volviendo a empezar.
|
|
|
|
Eso es lo que hace el `-1`. `musicPlay` recibe un número de *repeticiones*:
|
|
omítelo o pasa `0` y la pista suena una vez, pasa `3` y suena cuatro veces en
|
|
total, y pasa `-1` y se repite hasta que algo la pare. Un menú, una pantalla de
|
|
título o el fondo de un nivel casi siempre llevan `-1`.
|
|
|
|
Dos detalles de ese pequeño script merecen la tinta. `musicLoad` recibe una
|
|
ruta y no una ruta `DIR ..`, porque `Singe/menuIntro.flac` es uno de los
|
|
archivos propios del motor y está en la carpeta que el motor desempaquetó en tu
|
|
carpeta de trabajo. `musicIsPlaying` responde `true` o `false`, y el
|
|
`and ... or ...` en medio de ese `overlayPrint` es la abreviatura de Lua de la
|
|
lección tres para elegir uno de dos valores.
|
|
|
|
[#_the_volume_scale_is_not_the_one_you_know]
|
|
=== La escala de volumen no es la que conoces
|
|
|
|
`musicSetVolume(64)` lo pone más o menos a la mitad. Los efectos de sonido van
|
|
de `0` a `63`; la música va de `0` a `128`, que es el doble.
|
|
|
|
Eso no es un error y no es una broma. Singe puede ejecutar juegos escritos para
|
|
Hypseus, un motor cuyo volumen de música siempre ha sido una escala de `0` a
|
|
`128`, y cambiar los números haría, sin avisar, que la música de esos juegos
|
|
sonara el doble de fuerte de lo que pretendían sus autores. Así que Singe
|
|
mantiene las dos escalas, cada una unida a la familia de llamadas a la que
|
|
pertenece. Efectos: de `0` a `63`. Música: de `0` a `128`. Dale a
|
|
`musicSetVolume` un número fuera de su rango y el script se para, como verás
|
|
por ti mismo al final de esta lección.
|
|
|
|
[#_what_singe_will_play]
|
|
=== Qué reproduce Singe
|
|
|
|
Casi cualquier cosa. WAV, Ogg Vorbis, Opus, MP3, FLAC, WavPack, AAC y una larga
|
|
lista más, todo con el mismo `musicLoad`.
|
|
|
|
Hay dos familias más interesantes que las corrientes, y las dos existen porque
|
|
son diminutas.
|
|
|
|
Los *módulos de tracker* -- `.mod`, `.s3m`, `.xm`, `.it` y otros cincuenta y
|
|
tantos parientes -- guardan un puñado de instrumentos grabados cortos y una
|
|
lista de qué notas tocar con ellos. Una melodía de cuatro minutos puede ocupar
|
|
menos de cien kilobytes.
|
|
|
|
Los *chiptunes* -- `.nsf`, `.spc`, `.vgm`, `.gbs` y su familia -- van más allá:
|
|
guardan las *escrituras en registros* del chip de sonido real de una Nintendo,
|
|
una Mega Drive o un Spectrum, y Singe emula el chip para reproducirlos. Si
|
|
quieres que un juego suene a 1989 en lugar de sonar a una grabación de 1989,
|
|
así es como se hace.
|
|
|
|
Cualquiera de los dos se carga con `musicLoad` como todo lo demás. La sección
|
|
*Formatos de vídeo, audio y contenedor* de la referencia tiene la lista completa,
|
|
incluido qué contenedor va con qué códec.
|
|
|
|
[#_stopping_pausing_and_fading]
|
|
=== Parar, pausar y desvanecer
|
|
|
|
[source,lua]
|
|
----
|
|
musicPause(theme)
|
|
musicResume(theme)
|
|
musicStop(theme)
|
|
musicStop(theme, 2000)
|
|
----
|
|
|
|
`musicPause` deja la pista donde está y `musicResume` sigue desde ese punto.
|
|
`musicStop` la termina, y la pista vuelve a su principio la próxima vez que la
|
|
reproduzcas. Con un segundo número, `musicStop` desvanece la pista durante esos
|
|
milisegundos en lugar de cortarla en seco -- aquí, dos segundos. Ese
|
|
desvanecimiento es lo más fácil que puedes hacer para que un juego parezca
|
|
terminado. La música que se para a mitad de compás cuando el jugador cruza una
|
|
puerta suena a fallo, incluso para alguien que no sabría decir por qué.
|
|
|
|
Llama a cualquiera de las cuatro sin ningún handle y actúan sobre todas las
|
|
piezas de música a la vez, que es lo que quiere una pantalla de pausa.
|
|
|
|
Ahora el límite sincero, porque lo vas a buscar y no está: *Singe no puede
|
|
hacer un fundido cruzado entre dos pistas.* `musicSetVolume` es una sola
|
|
ganancia para toda la música, no un volumen por pista, así que no hay forma de
|
|
subir una pista mientras otra baja. Lo que sí puedes hacer es desvanecer la
|
|
vieja y empezar la nueva mientras se va, que basta para un cambio de nivel y se
|
|
nota que no es lo mismo para un tema de batalla que entra por debajo de un tema
|
|
de exploración. Si hoy necesitas un fundido cruzado de verdad, la forma de
|
|
conseguirlo es hacer de las dos piezas un solo archivo y saltar dentro de él, o
|
|
aceptar el solapamiento.
|
|
|
|
[#_music_video_and_staying_in_step]
|
|
=== Música, vídeo e ir a compás
|
|
|
|
Si tu juego reproduce vídeo -- el tema de la tercera parte -- hay una cosa que
|
|
saber sobre el sonido, y no es una llamada de música en absoluto.
|
|
|
|
Singe muestra un fotograma de vídeo cuando se oye el sonido que le corresponde,
|
|
no según un temporizador. El audio es el reloj y la imagen lo sigue. Ese es el
|
|
orden correcto, porque un oído nota una quinta parte de segundo de desfase en
|
|
una voz y un ojo no nota el mismo desfase en una boca.
|
|
|
|
El motor mide su propio camino de audio al arrancar. Lo que no puede medir es
|
|
todo lo que viene después de que el sistema operativo entregue el sonido: un
|
|
amplificador, un televisor, un receptor HDMI, unos auriculares Bluetooth. Cada
|
|
uno de ellos añade un retraso propio, es distinto en cada máquina, y se nota
|
|
como un sonido que llega un poco después de lo que lo produjo.
|
|
|
|
El arreglo es un número:
|
|
|
|
[source,lua]
|
|
----
|
|
singeSetAudioDelay(45)
|
|
----
|
|
|
|
Cuarenta y cinco milisegundos de «el audio va tarde; retrasa la imagen para que
|
|
coincida». También puedes pasarlo como `--audiodelay=45` en la línea de
|
|
comandos o ponerlo en la entrada del juego en `games.dat`. `singeGetAudioDelay`
|
|
lee el valor que esté en vigor, que es lo que muestra un menú de servicio junto
|
|
a su deslizador.
|
|
|
|
No tienes que adivinar el número. El propio menú de Singe tiene una pantalla de
|
|
calibración: reproduce un clic una vez por segundo y pone la pantalla en blanco
|
|
un instante, tú ajustas hasta que el destello y el clic ocurren a la vez, y
|
|
guarda el resultado para todos los juegos de esa máquina. La sección
|
|
*Sincronización del audio* de la referencia la describe y explica por qué funciona.
|
|
Si construyes una recreativa, pásala una vez después de cablear los altavoces y
|
|
no vuelvas a pensar en ello.
|
|
|
|
[#_the_other_thing_called_midi]
|
|
=== La otra cosa llamada MIDI
|
|
|
|
MIDI son dos cosas sin relación con un mismo nombre, y separarlas es casi todo
|
|
lo que hace falta para entenderlo.
|
|
|
|
Aquí va la primera, en un párrafo. Una grabación -- un `.wav`, un `.mp3` -- es
|
|
una medida de un sonido, tomada cuarenta y cuatro mil veces por segundo. MIDI no
|
|
es eso. Un archivo MIDI guarda *lo que se tocó*: en este momento, en este canal,
|
|
pulsa la tecla sesenta con esta fuerza; un poco después, suéltala. Se parece más
|
|
a una partitura, o al rollo perforado de una pianola, que a una grabación. Por
|
|
eso un archivo MIDI ocupa unos pocos kilobytes cuando la misma música en audio
|
|
ocupa unos pocos megabytes, y también por eso un archivo MIDI no contiene ningún
|
|
sonido en absoluto. Algo tiene que tocar las notas.
|
|
|
|
Un archivo MIDI es música, así que se reproduce como la música:
|
|
|
|
[source,lua]
|
|
----
|
|
local tune = musicLoad("music/overworld.mid")
|
|
musicPlay(tune, -1)
|
|
----
|
|
|
|
Pero lo que toca las notas tiene que salir de algún sitio, y eso es un
|
|
*soundfont*: un archivo lleno de instrumentos grabados -- un piano, una
|
|
trompeta, una caja de batería -- con las notas y los puntos de bucle marcados,
|
|
para que el motor pueda construir cualquier nota de cualquier instrumento a
|
|
partir de ellos.
|
|
|
|
Singe no trae ninguno. Un buen soundfont ocupa decenas de megabytes, y serían
|
|
decenas de megabytes que cargaría cada copia del motor para un formato que la
|
|
mayoría de los juegos no tocan nunca. Así que indicas uno con `--soundfont`, o
|
|
pones uno en `Singe/soundfont.sf2` para que un juego empaquetado lleve el suyo,
|
|
o dejas que Singe encuentre el que instaló tu distribución de Linux. Sin ningún
|
|
soundfont por ninguna parte, cargar un `.mid` falla y lo dice.
|
|
|
|
Por eso el mismo archivo MIDI suena distinto en dos máquinas y por eso sonaba
|
|
tan mal en los ordenadores de los años noventa. El archivo es una lista de
|
|
notas. Lo que oyes es el soundfont que haya tocado esas notas: uno bueno es una
|
|
sala llena de instrumentos, uno barato es una sala llena de juguetes, y las
|
|
notas son idénticas en los dos. Si un archivo MIDI va en tu juego, distribuye
|
|
el soundfont con él. Si no, no has elegido cómo suena tu música; lo has dejado
|
|
en manos de lo que tenga instalado la máquina del jugador.
|
|
|
|
[#_midi_as_a_wire]
|
|
=== MIDI como cable
|
|
|
|
Ahora la segunda cosa, que es la interesante.
|
|
|
|
MIDI también es un cable, y los mensajes que viajan por él. Enchufa un teclado
|
|
musical a tu ordenador y cada tecla que pulses envía un mensaje: *note on,
|
|
canal 1, tecla 60, velocidad 90*. Gira un potenciómetro de una superficie de
|
|
control y envía *el controlador 7 del canal 1 vale ahora 40*. Estos mensajes
|
|
existen tanto si algo está reproduciendo música como si no. Son otro
|
|
dispositivo de entrada, como un joystick con ochenta y ocho botones que saben
|
|
con qué fuerza los golpeas.
|
|
|
|
Y funciona en el otro sentido. Tu juego puede enviar esos mensajes hacia fuera,
|
|
a un sintetizador, un módulo de sonido, un controlador de luces de escenario o
|
|
cualquier otra cosa que haya en el cable.
|
|
|
|
Los puertos se numeran desde cero, y Singe no abre nada hasta que se lo pides --
|
|
un juego que nunca menciona MIDI nunca paga por MIDI. Puede haber un puerto de
|
|
entrada y un puerto de salida abiertos a la vez. Haz inventario de la máquina
|
|
así:
|
|
|
|
[source,lua]
|
|
----
|
|
local inPort = "none"
|
|
local outPort = "none"
|
|
|
|
if midiInputCount() > 0 and midiOpenInput(0) then
|
|
inPort = midiInputName(0)
|
|
end
|
|
if midiOutputCount() > 0 and midiOpenOutput(0) then
|
|
outPort = midiOutputName(0)
|
|
end
|
|
----
|
|
|
|
En una máquina sin nada de MIDI los recuentos son cero, las llamadas que abren
|
|
responden `false` y nada falla. Eso importa: significa que puedes escribir este
|
|
código en un juego de verdad y publicarlo, y los noventa y nueve de cada cien
|
|
jugadores que no tienen teclado MIDI nunca sabrán que está ahí. Los nombres son
|
|
para una pantalla de ajustes que deje al jugador elegir qué puerto, porque el
|
|
puerto cero rara vez es el que quiere. `midiRescan` vuelve a buscar algo
|
|
enchufado mientras el juego está en marcha.
|
|
|
|
[#_listening]
|
|
=== Escuchar
|
|
|
|
Cada mensaje que llega va a un callback, igual que cada pulsación de tecla va a
|
|
`onInputPressed`:
|
|
|
|
[source,lua]
|
|
----
|
|
local MIDI_NOTE_ON = 0x90
|
|
|
|
|
|
function onMidiMessage(status, data1, data2, bytes)
|
|
if (status & 0xF0) ~= MIDI_NOTE_ON or data2 == 0 then
|
|
return
|
|
end
|
|
heard = noteName(data1)
|
|
end
|
|
----
|
|
|
|
La mayoría de los mensajes MIDI tienen tres bytes, y los tres números que
|
|
recibes son esos bytes tal y como los envió el dispositivo. Un mensaje con menos
|
|
bytes te da un `0` en lugar de cada uno que no tiene.
|
|
|
|
`status` dice qué tipo de mensaje es y de qué canal viene, mezclado: el tipo son
|
|
los cuatro bits de arriba y el canal los cuatro de abajo. `0x90` es note on, y
|
|
una nota en el canal 1 llega como `0x90` mientras que la misma nota en el canal
|
|
5 llega como `0x94`. `status & 0xF0` se queda con la mitad de arriba y tira el
|
|
canal, que es lo que quieres a menos que te importe de qué canal venía. (`&` es
|
|
el «y» bit a bit de Lua, y `0x90` es un número escrito en hexadecimal -- base
|
|
dieciséis -- porque así los escriben todos los documentos MIDI del mundo.)
|
|
|
|
`data1` y `data2` son los otros dos bytes, y lo que significan depende del tipo.
|
|
Para un note on son la tecla y la velocidad: la tecla `60` es el do central, y
|
|
la velocidad es con qué fuerza se tocó, de `1` a `127`.
|
|
|
|
La comprobación de `data2 == 0` no es por manía. Un note on con velocidad cero
|
|
significa *note off*: así dice casi cualquier teclado que se haya fabricado
|
|
«he soltado», y si no lo filtras tu juego saltará dos veces por cada tecla.
|
|
|
|
`bytes` es el mensaje entero como cadena, que es como lees un mensaje de más de
|
|
tres bytes.
|
|
|
|
[#_playing]
|
|
=== Tocar
|
|
|
|
Enviar es más fácil que recibir.
|
|
|
|
[source,lua]
|
|
----
|
|
midiNoteOn(1, 60, 100)
|
|
midiNoteOff(1, 60)
|
|
----
|
|
|
|
Do central en el canal 1, tocado a cien y luego soltado. Aquí los canales van
|
|
de `1` a `16` como los cuenta una persona, no de `0` a `15` como el cable, que
|
|
es Singe siendo amable contigo.
|
|
|
|
Cada `midiNoteOn` necesita un `midiNoteOff`. Una nota que nunca se suelta suena
|
|
hasta que algo la para, y el primer fallo MIDI clásico es un juego que se cuelga
|
|
o se cierra con tres notas pulsadas y deja un acorde flotando en la habitación.
|
|
Así que emparéjalas, y emparéjalas con un temporizador en lugar de a mano:
|
|
|
|
[source,lua]
|
|
----
|
|
function playTarget()
|
|
if not midiIsOutputOpen() then
|
|
return
|
|
end
|
|
local key = target
|
|
midiNoteOn(1, key, 100)
|
|
timerAfter(HOLD_MS, function()
|
|
midiNoteOff(1, key)
|
|
end)
|
|
end
|
|
----
|
|
|
|
`local key = target` hace un trabajo de verdad. `timerAfter` ejecuta su función
|
|
cuatro décimas de segundo después, y para entonces `target` puede haber cambiado
|
|
a otra nota; copiarlo en una variable local significa que el temporizador suelta
|
|
la nota que se pulsó de verdad. Copia todo lo que un temporizador vaya a
|
|
necesitar más tarde.
|
|
|
|
Las demás llamadas de envío hacen el resto de lo que es MIDI:
|
|
`midiProgramChange` elige qué instrumento toca un canal, `midiControlChange`
|
|
mueve un controlador (el volumen es el 7, el panorama el 10, el pedal de
|
|
sostenido el 64), `midiPitchBend` dobla la afinación de un canal y `midiSend`
|
|
envía cualquier mensaje como bytes en bruto para lo que no cubren las llamadas
|
|
con nombre.
|
|
|
|
[#_a_reason_to_care]
|
|
=== Un motivo para que te importe
|
|
|
|
Todo eso es maquinaria. Esto es para lo que sirve.
|
|
|
|
Junta las dos mitades y el juego pide una nota, el jugador la toca en un
|
|
instrumento de verdad y el juego sabe si ha acertado:
|
|
|
|
[source,lua]
|
|
----
|
|
function onMidiMessage(status, data1, data2, bytes)
|
|
if (status & 0xF0) ~= MIDI_NOTE_ON or data2 == 0 then
|
|
return
|
|
end
|
|
heard = noteName(data1)
|
|
if data1 == target then
|
|
score = score + 1
|
|
pickTarget()
|
|
playTarget()
|
|
end
|
|
end
|
|
----
|
|
|
|
Once líneas, y tienes el esqueleto de un juego para enseñar música. Añade un
|
|
reloj y tienes un juego de ritmo que puntúa a alguien tocando de verdad en lugar
|
|
de cuatro botones de plástico. Añade `data2` a la comprobación y le importará
|
|
con qué fuerza tocas la nota. Compara la hora a la que llegó la nota con la hora
|
|
a la que tocaba y estarás evaluando la sincronización al milisegundo.
|
|
|
|
Y como MIDI sale además de entrar, una recreativa puede hacer cosas que nada
|
|
más puede. Un instrumento de verdad conectado a la máquina. Un módulo de sonido
|
|
externo tocando la partitura para que la melodía tenga una profundidad que
|
|
ningún banco de muestras le da. Luces de escenario en la recreativa que
|
|
destellan al ritmo porque el juego les ha enviado el ritmo. Si eres de los que
|
|
construyen máquinas recreativas, esta es la llamada que permite que la
|
|
recreativa sea algo en vez de una caja alrededor de un ordenador.
|
|
|
|
El script terminado es `learn/27-midi.singe`: los controles de la música, la
|
|
lista de puertos y el juego de adivinar, en unas cien líneas. La barra
|
|
espaciadora envía la nota, la tecla Alt izquierda pausa la música, la tecla
|
|
Mayús izquierda la desvanece y las teclas de flecha arriba y abajo mueven el
|
|
volumen. Ejecútalo sin hardware MIDI y la mitad musical funciona y la mitad MIDI
|
|
dice `none` dos veces, que es como debe comportarse en la máquina de un jugador.
|
|
|
|
[#_cleaning_up]
|
|
=== Recoger
|
|
|
|
[source,lua]
|
|
----
|
|
function onShutdown()
|
|
midiCloseInput()
|
|
midiCloseOutput()
|
|
musicUnload(theme)
|
|
end
|
|
----
|
|
|
|
`onShutdown` se llama una vez cuando el juego termina, y es donde un script
|
|
devuelve las cosas. Cerrar un puerto que no está abierto no hace nada, así que
|
|
no hay nada que comprobar antes.
|
|
|
|
[#_try_it_27]
|
|
=== Pruébalo
|
|
|
|
. *Que dé cuatro vueltas en lugar de infinitas.* Cambia `musicPlay(theme, -1)`
|
|
por `musicPlay(theme, 3)` y cuenta. El número es cuántas veces se *repite*,
|
|
no cuántas veces suena.
|
|
. *Desvanece durante más tiempo.* Cambia el desvanecimiento del botón que para
|
|
la música a
|
|
`musicStop(theme, 4000)` y escucha la diferencia que marcan cuatro segundos.
|
|
Luego prueba `musicStop(theme, 100)`.
|
|
. *Muestra el canal.* En `onMidiMessage`, calcula el canal con
|
|
`(status & 0x0F) + 1` e imprímelo. Toca tu teclado y mira en qué canal está --
|
|
y luego busca sus ajustes y cambia ese canal, para demostrarte que el número
|
|
es real.
|
|
. *Puntúa la velocidad.* Da un punto por la nota correcta, y tres puntos si
|
|
`data2` pasa de cien, para que el juego premie tocarla con ganas.
|
|
. *Toca la respuesta.* Cuando el jugador acierte una nota, haz que el juego le
|
|
devuelva un pequeño acorde con tres llamadas a `midiNoteOn` separadas un par
|
|
de notas y tres temporizadores para soltarlas. Para esto necesitarás un
|
|
puerto de salida de verdad o un sintetizador por software, y en Linux eso es
|
|
cualquier programa que se registre en ALSA.
|
|
|
|
[#_break_it_on_purpose_26]
|
|
=== Rómpelo a propósito
|
|
|
|
Las dos escalas de volumen son fáciles de confundir, y Singe prefiere pararse
|
|
antes que reproducir tu música a un nivel que no querías. Añade una línea debajo
|
|
de `musicPlay(theme, -1)`, usando el número que sería el volumen máximo de un
|
|
efecto de sonido si lo duplicaras de cabeza y te equivocaras:
|
|
|
|
[source,lua]
|
|
----
|
|
musicSetVolume(200)
|
|
----
|
|
|
|
Singe se para antes de que arranque el juego, nombrando la línea de tu archivo
|
|
en la que está esa llamada:
|
|
|
|
----
|
|
107:musicSetVolume: Invalid music volume value: 200
|
|
----
|
|
|
|
La línea, la llamada que protestó y la queja, que aquí te repite el número para
|
|
que no haya discusión sobre lo que llegó. La música va de `0` a `128`. Los
|
|
efectos de sonido van de `0` a `63`. Cuando una llamada de volumen para tu
|
|
juego, la pregunta que tienes que hacerte es con cuál de las dos familias estás
|
|
hablando.
|
|
|
|
[#_what_you_learned_27]
|
|
=== Lo que has aprendido
|
|
|
|
* La música y los efectos de sonido van por separado en Singe: sus propios
|
|
volúmenes, sin límite de canales para la música y una pausa que sigue a la
|
|
pausa del juego.
|
|
* Un archivo de música se decodifica en memoria de entrada, así que una pista
|
|
larga cuesta memoria y conviene descargarla cuando sales del nivel.
|
|
* `musicPlay(track, -1)` se repite para siempre; un número positivo es cuántas
|
|
veces se repite.
|
|
* El volumen de la música va de `0` a `128` y el de los efectos de `0` a `63`,
|
|
y la diferencia es por compatibilidad con Hypseus, no un error.
|
|
* `musicStop(track, milliseconds)` desvanece la música, y un desvanecimiento es
|
|
el pulido más barato que hay. No hay fundido cruzado, porque una sola ganancia
|
|
cubre toda la música.
|
|
* Singe presenta el vídeo según el reloj del audio, y `singeSetAudioDelay`
|
|
corrige el retraso de lo que sea por donde sale el sonido.
|
|
* Un archivo MIDI guarda notas, no sonido, y necesita un soundfont para que se
|
|
oiga algo. Singe no trae ninguno, así que distribuye el tuyo o tu música será
|
|
lo que la máquina del jugador tenga a mano.
|
|
* Los puertos MIDI son otra cosa por completo: mensajes en directo que entran y
|
|
salen, por un cable, sin nada que ver con los archivos MIDI.
|
|
* `onMidiMessage` te da los bytes en bruto. Los cuatro bits de arriba de
|
|
`status` son el tipo, los cuatro de abajo son el canal, y un note on con
|
|
velocidad cero significa note off.
|
|
* Cada `midiNoteOn` necesita un `midiNoteOff`, y un temporizador es la forma de
|
|
emparejarlos.
|
|
* En una máquina sin MIDI, todas las llamadas responden `false` o cero y nada
|
|
falla, así que el soporte de MIDI es seguro de publicar.
|
|
|
|
[#_next_time_27]
|
|
=== La próxima vez
|
|
|
|
Tu juego tiene música, y una puntuación que un teclado MIDI puede hacer subir.
|
|
La lección veintiocho saca esa puntuación de la máquina: cuentas, tablas de
|
|
récords que otras personas pueden ver y el catálogo que presenta tu juego al
|
|
mundo.
|