[#_lesson_27_music_and_midi] == Lezione 27: Musica e MIDI image::learn/27-midi.png[La lezione finita, 480] Questa lezione è fatta di due lezioni che per caso abitano insieme. La prima metà è la musica: come un gioco riproduce come si deve un brano musicale, perché non è la stessa cosa che riprodurre un effetto sonoro, e che cosa fare quando la musica deve restare a tempo con il video. La seconda metà è il MIDI, che quasi nessuno ha mai incontrato e che dà a un gioco qualcosa che nient'altro gli dà. Alla fine, una tastiera collegata al tuo computer suonerà il tuo gioco. [#_a_sound_effect_and_a_piece_of_music_are_different_things] === Un effetto sonoro e un brano musicale sono cose diverse Sai già come riprodurre un suono. Nella decima lezione hai caricato un `.wav` con `soundLoad` e l'hai fatto partire con `soundPlay`, e se punti quelle due chiamate su una canzone di quattro minuti la suoneranno. Non farlo. Singe tiene separati gli effetti sonori e la musica, e la separazione conviene per quattro motivi. * *Hanno volumi propri.* `soundSetVolume` non tocca mai la musica e `musicSetVolume` non tocca mai gli spari. Ogni gioco mai uscito ne ha avuto bisogno, perché i giocatori abbassano la musica e alzano gli effetti. * *La musica non ha un limite di canali.* Gli effetti sonori si dividono sedici canali, e quando sono occupati tutti e sedici il diciassettesimo non suona. Un brano musicale non sta su un canale, quindi niente può scalzarlo. * *Un handle è un brano*, e si mette in pausa, riprende, si ferma e sfuma per conto suo senza disturbare nient'altro. * *La musica va in pausa con il gioco.* Premi il tasto di pausa e resta dov'è. Torna e riprende. Non devi scrivere niente perché succeda. Una cosa che *non* è diversa vale la pena saperla, perché si tende a pensare il contrario: Singe decodifica un file musicale in memoria fin dall'inizio, esattamente come fa con un effetto sonoro. Un brano lungo costa memoria. Non viene letto dal disco un pezzo alla volta. Cinque minuti di musica sono qualche decina di megabyte di memoria finché restano caricati, quindi carica il brano per il livello in cui sei e scaricalo con `musicUnload` quando lo lasci. [#_playing_a_track] === Riprodurre un brano Ogni lettore di questo libro ha già un brano musicale, perché il motore ne ha estratto uno nella tua cartella di lavoro la prima volta che hai eseguito qualcosa. Comincia un file con queste tre righe: [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 ---- Eseguilo con `Singe -R music`. Dieci secondi di musica, poi ricomincia, e continua a ricominciare. È questo che fa il `-1`. `musicPlay` prende un numero di *ripetizioni*: omettilo o passa `0` e il brano suona una volta, passa `3` e suona quattro volte in tutto, passa `-1` e si ripete finché qualcosa non lo ferma. Un menu, una schermata del titolo o il sottofondo di un livello sono quasi sempre `-1`. In quel piccolo script ci sono due dettagli che meritano due parole. `musicLoad` prende un percorso normale e non un percorso con `DIR ..`, perché `Singe/menuIntro.flac` è uno dei file del motore e sta nella cartella che il motore ha estratto nella tua cartella di lavoro. `musicIsPlaying` risponde `true` o `false`, e la coppia `and ... or ...` in mezzo a quel `overlayPrint` è la scorciatoia di Lua vista nella terza lezione per scegliere uno tra due valori. [#_the_volume_scale_is_not_the_one_you_know] === La scala del volume non è quella che conosci `musicSetVolume(64)` lo imposta più o meno a metà. Gli effetti sonori vanno da `0` a `63`; la musica va da `0` a `128`, cioè il doppio. Non è un errore e non è uno scherzo. Singe può eseguire giochi scritti per Hypseus, un motore il cui volume della musica è sempre stato su una scala da `0` a `128`, e cambiare i numeri renderebbe in silenzio la musica di quei giochi due volte più forte di quanto volevano i loro autori. Così Singe tiene tutte e due le scale, ognuna legata alla famiglia di chiamate a cui appartiene. Effetti: da `0` a `63`. Musica: da `0` a `128`. Dai a `musicSetVolume` un numero fuori dal suo intervallo e lo script si ferma, come vedrai tu stesso alla fine di questa lezione. [#_what_singe_will_play] === Che cosa riproduce Singe Quasi tutto. WAV, Ogg Vorbis, Opus, MP3, FLAC, WavPack, AAC e un lungo elenco oltre a questi, tutti con lo stesso `musicLoad`. Due famiglie sono più interessanti di quelle normali, ed esistono tutte e due perché sono minuscole. I *moduli tracker* -- `.mod`, `.s3m`, `.xm`, `.it` e una cinquantina di parenti -- contengono una manciata di brevi strumenti registrati e un elenco delle note su cui suonarli. Un motivo di quattro minuti può stare sotto i cento kilobyte. Le *chiptune* -- `.nsf`, `.spc`, `.vgm`, `.gbs` e simili -- vanno oltre: contengono le *scritture nei registri* per il vero chip sonoro di un Nintendo, di un Mega Drive o di uno Spectrum, e Singe emula il chip per suonarle. Se vuoi che un gioco suoni come il 1989 e non come una registrazione del 1989, si fa così. Le une e gli altri si caricano con `musicLoad` come qualsiasi altra cosa. La sezione *Formati video, audio e dei contenitori* del Riferimento ha l'elenco completo, compreso quale contenitore va con quale codec. [#_stopping_pausing_and_fading] === Fermare, mettere in pausa e sfumare [source,lua] ---- musicPause(theme) musicResume(theme) musicStop(theme) musicStop(theme, 2000) ---- `musicPause` ferma il brano dov'è e `musicResume` riprende da quel punto. `musicStop` lo termina, e la volta dopo che lo riproduci il brano riparte dall'inizio. Con un secondo numero, `musicStop` sfuma il brano in uscita in quel numero di millisecondi invece di troncarlo di netto -- qui due secondi. Quella sfumatura è in assoluto la cosa più facile che puoi fare per dare a un gioco un'aria finita. Una musica che si interrompe a metà battuta quando il giocatore attraversa una porta sembra un bug, anche a chi non saprebbe dire perché. Chiama una qualsiasi delle quattro senza nessun handle e agisce su tutta la musica insieme, che è quello che vuole una schermata di pausa. Ora il limite, detto onestamente, perché lo cercherai e non c'è: *Singe non sa fare una dissolvenza incrociata tra due brani.* `musicSetVolume` è un unico guadagno su tutta la musica, non un volume per brano, quindi non c'è modo di alzare un brano mentre un altro si abbassa. Quello che puoi fare è sfumare in uscita quello vecchio e far partire quello nuovo mentre se ne va, il che basta per un cambio di livello e, a orecchio, non è la stessa cosa per un tema di battaglia che scivola sotto un tema di esplorazione. Se oggi ti serve una vera dissolvenza incrociata, il modo per ottenerla è fare dei due pezzi un solo file e saltare al suo interno, o accettare la sovrapposizione. [#_music_video_and_staying_in_step] === Musica, video e restare a tempo Se il tuo gioco riproduce video -- l'argomento della terza parte -- c'è una cosa da sapere sul suono, e non è affatto una chiamata della musica. Singe mostra un fotogramma video quando si sente il suono che gli appartiene, non a colpi di timer. L'audio è l'orologio e l'immagine lo segue. È il verso giusto, perché un orecchio si accorge di un quinto di secondo di scarto in una voce, e un occhio non si accorge dello stesso scarto in una bocca. Il motore misura il proprio percorso audio quando parte. Quello che non può misurare è tutto ciò che viene dopo che il sistema operativo ha consegnato il suono: un amplificatore, un televisore, un ricevitore HDMI, delle cuffie Bluetooth. Ognuno aggiunge un ritardo suo, diverso su ogni macchina, e si nota come un suono che arriva un attimo dopo la cosa che l'ha prodotto. La soluzione è un numero: [source,lua] ---- singeSetAudioDelay(45) ---- Quarantacinque millisecondi di «l'audio è in ritardo; trattieni l'immagine per andargli dietro». Puoi anche passarlo come `--audiodelay=45` sulla riga di comando o metterlo nella voce del gioco in `games.dat`. `singeGetAudioDelay` rilegge il valore in vigore, ed è quello che un menu di servizio mostra accanto al suo cursore. Non devi tirare a indovinare il numero. Il menu di Singe ha una schermata di calibrazione: fa un clic una volta al secondo e fa lampeggiare lo schermo di bianco, tu regoli finché il lampo e il clic non avvengono insieme, e lei salva il risultato per tutti i giochi di quella macchina. La sezione *Sincronizzazione dell'audio* del Riferimento la descrive e spiega perché funziona. Se costruisci un cabinato, eseguila una volta dopo aver collegato gli altoparlanti, e non pensarci mai più. [#_the_other_thing_called_midi] === L'altra cosa che si chiama MIDI MIDI è il nome di due cose che non hanno niente a che fare l'una con l'altra, e tenerle separate è quasi tutto quello che serve per capirlo. Ecco la prima, in un paragrafo. Una registrazione -- un `.wav`, un `.mp3` -- è una misura di un suono, presa quarantaquattromila volte al secondo. Il MIDI non è questo. Un file MIDI contiene *che cosa è stato suonato*: in questo momento, su questo canale, premi il tasto sessanta con questa forza; poco dopo, lascialo andare. È più vicino a uno spartito, o al rullo perforato di una pianola, che a una registrazione. Ecco perché un file MIDI pesa pochi kilobyte quando la stessa musica come audio pesa qualche megabyte, ed è anche il motivo per cui un file MIDI non contiene proprio nessun suono. Qualcosa deve suonare le note. Un file MIDI è musica, quindi si riproduce come la musica: [source,lua] ---- local tune = musicLoad("music/overworld.mid") musicPlay(tune, -1) ---- Ma la cosa che suona le note deve venire da qualche parte, e quella cosa è un *soundfont*: un file pieno di strumenti registrati -- un pianoforte, una tromba, un rullante -- con le note e i punti di loop segnati, così il motore può costruirci qualsiasi nota di qualsiasi strumento. Singe non ne include uno. Un buon soundfont pesa decine di megabyte, e sarebbero decine di megabyte portate in giro da ogni copia del motore per un formato che la maggior parte dei giochi non tocca mai. Quindi ne indichi uno con `--soundfont`, o ne metti uno in `Singe/soundfont.sf2` così che un gioco impacchettato si porti il suo, o lasci che Singe trovi quello installato dalla tua distribuzione Linux. Se non c'è un soundfont da nessuna parte, caricare un `.mid` fallisce e lo dice. Ecco perché lo stesso file MIDI suona diverso su due macchine, e perché suonava così male sui computer degli anni novanta. Il file è un elenco di note. Quello che senti è il soundfont che si è trovato a suonarle: uno buono è una stanza piena di strumenti, uno scadente è una stanza piena di giocattoli, e le note sono identiche in entrambi. Se nel tuo gioco va un file MIDI, distribuisci il soundfont insieme. Altrimenti non hai scelto come suona la tua musica; l'hai lasciato a qualunque cosa sia installata sulla macchina del giocatore. [#_midi_as_a_wire] === Il MIDI come cavo Ora la seconda cosa, che è quella interessante. Il MIDI è anche un cavo, e i messaggi che ci viaggiano sopra. Collega una tastiera musicale al tuo computer e ogni tasto che premi manda un messaggio: *note on, canale 1, tasto 60, velocity 90*. Gira una manopola su una superficie di controllo e manda *il controller 7 sul canale 1 adesso vale 40*. Questi messaggi esistono che ci sia o no qualcosa che suona musica. Sono un altro dispositivo di input, come un joystick con ottantotto pulsanti che sanno quanto forte li colpisci. E funziona anche al contrario. Il tuo gioco può mandare fuori quei messaggi, a un sintetizzatore, a un modulo sonoro, a un controller per luci da palco o a qualsiasi altra cosa sul cavo. Le porte sono numerate da zero, e Singe non apre niente finché non glielo chiedi -- un gioco che non nomina mai il MIDI non paga mai per il MIDI. Possono essere aperte una porta di ingresso e una di uscita alla volta. Fai l'inventario della macchina così: [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 ---- Su una macchina senza nessun MIDI i conteggi sono zero, le chiamate di apertura rispondono `false` e niente fallisce. Questo conta: vuol dire che puoi scrivere questo codice in un vero gioco e distribuirlo, e i novantanove giocatori su cento senza una tastiera MIDI non sapranno mai che c'è. I nomi servono per una schermata delle impostazioni che lasci scegliere al giocatore quale porta, perché la porta zero di rado è quella che vuole. `midiRescan` cerca di nuovo qualcosa che è stato collegato mentre il gioco è in esecuzione. [#_listening] === Ascoltare Ogni messaggio che arriva va a una callback, così come ogni pressione di un tasto 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 maggior parte dei messaggi MIDI è lunga tre byte, e i tre numeri che ricevi sono quei byte esattamente come li ha mandati il dispositivo. Un messaggio con meno byte ti passa uno `0` al posto di ognuno che non ha. `status` dice di che tipo di messaggio si tratta e da quale canale è arrivato, mescolati insieme: il tipo sono i quattro bit alti e il canale i quattro bassi. `0x90` è note on, e una nota sul canale 1 arriva come `0x90` mentre la stessa nota sul canale 5 arriva come `0x94`. `status & 0xF0` tiene la metà alta e butta via il canale, che è quello che vuoi a meno che non ti importi da quale canale veniva. (`&` è l'and bit a bit di Lua, e `0x90` è un numero scritto in esadecimale -- base sedici -- perché è così che li scrive ogni documento MIDI del mondo.) `data1` e `data2` sono gli altri due byte, e che cosa vogliono dire dipende dal tipo. Per un note on sono il tasto e la velocity: il tasto `60` è il Do centrale, e la velocity è quanto forte è stato colpito, da `1` a `127`. Il controllo `data2 == 0` non è pignoleria. Un note on con velocity zero vuol dire *note off*: è il modo in cui quasi ogni tastiera mai costruita dice «ho lasciato andare», e se non lo filtri il tuo gioco scatterà due volte per ogni tasto. `bytes` è l'intero messaggio come stringa, ed è così che leggi un messaggio più lungo di tre byte. [#_playing] === Suonare Mandare è più facile che ricevere. [source,lua] ---- midiNoteOn(1, 60, 100) midiNoteOff(1, 60) ---- Do centrale sul canale 1, colpito a cento, e poi rilasciato. Qui i canali vanno da `1` a `16` come li conta una persona, non da `0` a `15` come fa il cavo: è Singe che è gentile con te. Ogni `midiNoteOn` ha bisogno di un `midiNoteOff`. Una nota che non viene mai rilasciata suona finché qualcosa non la ferma, e il classico primo bug MIDI è un gioco che va in crash o si chiude con tre note premute e lascia un accordo sospeso nella stanza. Quindi abbinale, e abbinale con un timer invece che 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` fa un lavoro vero. `timerAfter` esegue la sua funzione quattro decimi di secondo dopo, e a quel punto `target` potrebbe essere passato a un'altra nota; copiarlo in una variabile locale vuol dire che il timer rilascia la nota che è stata davvero premuta. Copia tutto ciò che servirà più tardi a un timer. Le altre chiamate di invio fanno il resto di quello che è il MIDI: `midiProgramChange` sceglie quale strumento suona un canale, `midiControlChange` muove un controller (il volume è 7, il pan è 10, il pedale di sustain è 64), `midiPitchBend` piega l'intonazione di un canale, e `midiSend` manda qualsiasi messaggio come byte grezzi per le cose che le chiamate con un nome non coprono. [#_a_reason_to_care] === Un motivo per interessarsene Tutto questo è meccanica. Ecco a che cosa serve. Metti insieme le due metà e il gioco chiede una nota, il giocatore la suona su un vero strumento, e il gioco sa se l'ha azzeccata: [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 ---- Undici righe, e hai lo scheletro di un gioco per imparare la musica. Aggiungi un orologio e hai un gioco ritmico che dà punti a chi suona davvero invece che a quattro pulsanti di plastica. Aggiungi `data2` alla verifica e al gioco importa quanto forte colpisci la nota. Confronta il momento in cui è arrivata la nota con quello in cui era attesa, e stai valutando la tempistica al millisecondo. E siccome il MIDI esce oltre che entrare, un cabinato può fare cose che nient'altro può fare. Un vero strumento collegato alla macchina. Un modulo sonoro esterno che suona la partitura, così il motivo ha una profondità che nessun banco di campioni gli dà. Luci da palco sul cabinato che lampeggiano a tempo perché il gioco ha mandato loro il tempo. Se sei il tipo di persona che costruisce cabinati, questa è la chiamata che permette al cabinato di essere una cosa e non una scatola attorno a un computer. Lo script finito è `learn/27-midi.singe`: i comandi della musica, l'elenco delle porte e il gioco a indovinare, in un centinaio di righe. La barra spaziatrice manda fuori la nota, Alt sinistro mette in pausa la musica, Maiusc sinistro la sfuma, e le frecce su e giù cambiano il volume. Eseguilo senza hardware MIDI e la metà della musica funziona, mentre la metà MIDI dice `none` due volte, che è proprio come dovrebbe comportarsi sulla macchina di un giocatore. [#_cleaning_up] === Fare pulizia [source,lua] ---- function onShutdown() midiCloseInput() midiCloseOutput() musicUnload(theme) end ---- `onShutdown` viene chiamata una volta quando il gioco finisce, ed è il punto in cui uno script restituisce ciò che ha preso. Chiudere una porta che non è aperta non fa niente, quindi non c'è niente da controllare prima. [#_try_it_27] === Prova tu . *Fallo ripetere quattro volte invece che all'infinito.* Cambia `musicPlay(theme, -1)` in `musicPlay(theme, 3)` e conta. Il numero è quante volte si *ripete*, non quante volte suona. . *Sfuma più a lungo.* Cambia la dissolvenza del pulsante che ferma la musica in `musicStop(theme, 4000)` e ascolta la differenza che fanno quattro secondi. Poi prova `musicStop(theme, 100)`. . *Mostra il canale.* In `onMidiMessage`, ricava il canale con `(status & 0x0F) + 1` e stampalo. Suona la tua tastiera e guarda su quale canale è -- poi trova le sue impostazioni e cambia quel canale, per dimostrare a te stesso che il numero è vero. . *Dai punti alla velocity.* Dai un punto per la nota giusta, e tre punti se `data2` è sopra cento, così il gioco premia chi suona con convinzione. . *Suona la risposta.* Quando il giocatore azzecca una nota, fai suonare al gioco un piccolo accordo di risposta con tre chiamate a `midiNoteOn` a un paio di note di distanza e tre timer per rilasciarle. Per questo ti serve una vera porta di uscita o un sintetizzatore software, e su Linux va bene qualsiasi programma che si registri con ALSA. [#_break_it_on_purpose_26] === Rompilo apposta Le due scale del volume sono facili da confondere, e Singe preferisce fermarsi piuttosto che suonare la tua musica a un livello che non intendevi. Aggiungi una riga sotto `musicPlay(theme, -1)`, usando il numero che sarebbe il volume massimo di un effetto sonoro se lo raddoppiassi a mente e sbagliassi il conto: [source,lua] ---- musicSetVolume(200) ---- Singe si ferma prima che il gioco parta, indicando la riga del tuo file in cui si trova quella chiamata: ---- 107:musicSetVolume: Invalid music volume value: 200 ---- La riga, la chiamata che ha protestato e la lamentela, che qui ti ripete il numero, così non c'è discussione su che cosa è arrivato. La musica va da `0` a `128`. Gli effetti sonori vanno da `0` a `63`. Quando una chiamata del volume ferma il tuo gioco, la domanda da farti è a quale delle due famiglie stai parlando. [#_what_you_learned_27] === Cosa hai imparato * In Singe la musica e gli effetti sonori sono separati: volumi propri, nessun limite di canali per la musica e una pausa che segue la pausa del gioco. * Un file musicale viene decodificato in memoria fin dall'inizio, quindi un brano lungo costa memoria e va scaricato quando lasci il livello. * `musicPlay(track, -1)` si ripete all'infinito; un numero positivo è quante volte si ripete. * Il volume della musica va da `0` a `128` e quello degli effetti da `0` a `63`, e la differenza è per compatibilità con Hypseus, non un errore. * `musicStop(track, milliseconds)` sfuma in uscita, e una sfumatura è la rifinitura più economica che ci sia. Non c'è la dissolvenza incrociata, perché un unico guadagno copre tutta la musica. * Singe mostra il video seguendo l'orologio dell'audio, e `singeSetAudioDelay` corregge il ritardo di qualunque cosa da cui esca il suono. * Un file MIDI contiene note, non suono, e ha bisogno di un soundfont per farsi sentire. Singe non ne include nessuno, quindi distribuisci il tuo, o la tua musica sarà qualunque cosa la macchina del giocatore si trovi ad avere. * Le porte MIDI sono tutt'altra cosa: messaggi dal vivo in entrata e in uscita, su un cavo, che non hanno niente a che fare con i file MIDI. * `onMidiMessage` ti passa i byte grezzi. I quattro bit alti di `status` sono il tipo, i quattro bassi sono il canale, e un note on con velocity zero vuol dire note off. * Ogni `midiNoteOn` ha bisogno di un `midiNoteOff`, e un timer è il modo per abbinarli. * Su una macchina senza MIDI ogni chiamata risponde `false` o zero e niente fallisce, quindi il supporto MIDI si può distribuire tranquillamente. [#_next_time_27] === La prossima volta Il tuo gioco ha la musica, e un punteggio che una tastiera MIDI può far salire. La ventottesima lezione porta quel punteggio fuori dalla macchina: account, classifiche dei punteggi record che possono vedere anche gli altri, e il catalogo che fa conoscere il tuo gioco al mondo.