[#_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.