515 lines
22 KiB
Text
515 lines
22 KiB
Text
[#_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.
|