526 lines
23 KiB
Text
526 lines
23 KiB
Text
[#_lesson_27_music_and_midi]
|
||
== Leçon 27 : Musique et MIDI
|
||
|
||
image::learn/27-midi.png[La leçon terminée, 480]
|
||
|
||
Cette leçon, ce sont deux leçons qui se trouvent vivre ensemble.
|
||
|
||
La première moitié, c'est la musique : comment un jeu joue correctement un
|
||
morceau de musique, pourquoi ce n'est pas la même chose que jouer un effet
|
||
sonore, et que faire quand la musique doit rester au pas de la vidéo.
|
||
|
||
La seconde moitié, c'est le MIDI, que la plupart des gens n'ont jamais croisé
|
||
et qui donne à un jeu quelque chose que rien d'autre ne donne. À la fin, un
|
||
clavier musical branché sur votre ordinateur jouera à votre jeu.
|
||
|
||
[#_a_sound_effect_and_a_piece_of_music_are_different_things]
|
||
=== Un effet sonore et un morceau de musique sont deux choses différentes
|
||
|
||
Vous savez déjà jouer un son. La leçon dix a chargé un `.wav` avec `soundLoad`
|
||
et l'a déclenché avec `soundPlay`, et si vous donnez à ces deux appels une
|
||
chanson de quatre minutes, ils la joueront.
|
||
|
||
Ne le faites pas. Singe sépare les effets sonores et la musique, et cette
|
||
séparation vaut la peine pour quatre raisons.
|
||
|
||
* *Ils ont chacun leur volume.* `soundSetVolume` ne touche jamais à la musique
|
||
et `musicSetVolume` ne touche jamais aux coups de feu. Tous les jeux jamais
|
||
sortis en ont eu besoin, parce que les joueurs baissent la musique et montent
|
||
les effets.
|
||
* *La musique n'a pas de limite de canaux.* Les effets sonores se partagent
|
||
seize canaux, et quand les seize sont occupés, le dix-septième ne joue pas.
|
||
Un morceau de musique n'est pas sur un canal, alors rien ne peut l'évincer.
|
||
* *Un handle, c'est un morceau*, et il se met en pause, reprend, s'arrête et
|
||
s'estompe tout seul sans rien déranger d'autre.
|
||
* *La musique se met en pause avec le jeu.* Appuyez sur la touche de pause et
|
||
elle reste là où elle en est. Revenez et elle repart. Vous n'écrivez rien
|
||
pour cela.
|
||
|
||
Une chose qui n'est *pas* différente mérite d'être connue, parce qu'on suppose
|
||
souvent le contraire : Singe décode un fichier de musique en mémoire dès le
|
||
départ, exactement comme un effet sonore. Un long morceau coûte de la mémoire.
|
||
Il n'est pas lu sur le disque petit à petit. Cinq minutes de musique, ce sont
|
||
quelques dizaines de mégaoctets de mémoire tant qu'elles sont chargées, alors
|
||
chargez le morceau du niveau où vous êtes et déchargez-le avec `musicUnload`
|
||
quand vous en sortez.
|
||
|
||
[#_playing_a_track]
|
||
=== Jouer un morceau
|
||
|
||
Chaque lecteur de ce livre a déjà un morceau de musique, parce que le moteur en
|
||
a extrait un dans votre dossier de travail la première fois que vous avez
|
||
lancé quoi que ce soit. Commencez un fichier avec ces trois lignes :
|
||
|
||
[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
|
||
----
|
||
|
||
Lancez-le avec `Singe -R music`. Dix secondes de musique, puis elle
|
||
recommence, et elle n'arrête pas de recommencer.
|
||
|
||
C'est ce que fait le `-1`. `musicPlay` prend un nombre de *boucles* : omettez-le
|
||
ou passez `0` et le morceau joue une fois, passez `3` et il joue quatre fois en
|
||
tout, et passez `-1` et il se répète jusqu'à ce que quelque chose l'arrête. Un
|
||
menu, un écran titre ou le fond sonore d'un niveau, c'est presque toujours
|
||
`-1`.
|
||
|
||
Deux détails de ce petit script méritent qu'on s'y arrête. `musicLoad` prend un
|
||
chemin, et pas un chemin en `DIR ..`, parce que `Singe/menuIntro.flac` est l'un
|
||
des fichiers du moteur lui-même et se trouve dans le dossier que le moteur a
|
||
extrait dans votre dossier de travail. `musicIsPlaying` répond `true` ou
|
||
`false`, et le `and ... or ...` au milieu de cet `overlayPrint` est le raccourci
|
||
Lua de la leçon trois pour choisir entre deux valeurs.
|
||
|
||
[#_the_volume_scale_is_not_the_one_you_know]
|
||
=== L'échelle de volume n'est pas celle que vous connaissez
|
||
|
||
`musicSetVolume(64)` le règle à peu près à la moitié. Les effets sonores vont
|
||
de `0` à `63` ; la musique va de `0` à `128`, soit deux fois plus loin.
|
||
|
||
Ce n'est pas une erreur, et ce n'est pas une blague. Singe sait faire tourner
|
||
des jeux écrits pour Hypseus, un moteur dont le volume de la musique a toujours
|
||
été sur une échelle de `0` à `128`, et changer les nombres rendrait, sans
|
||
prévenir, la musique de ces jeux deux fois plus forte que ne le voulaient leurs
|
||
auteurs. Alors Singe garde les deux échelles, chacune attachée à la famille
|
||
d'appels à laquelle elle appartient. Effets : de `0` à `63`. Musique : de `0` à
|
||
`128`. Donnez à `musicSetVolume` un nombre hors de sa plage et le script
|
||
s'arrête, ce que vous verrez par vous-même à la fin de cette leçon.
|
||
|
||
[#_what_singe_will_play]
|
||
=== Ce que Singe sait jouer
|
||
|
||
Presque tout. WAV, Ogg Vorbis, Opus, MP3, FLAC, WavPack, AAC, et une longue
|
||
liste au-delà, tous par le même `musicLoad`.
|
||
|
||
Deux familles sont plus intéressantes que les formats ordinaires, et toutes
|
||
deux existent parce qu'elles sont minuscules.
|
||
|
||
Les *modules de tracker* -- `.mod`, `.s3m`, `.xm`, `.it`, et une cinquantaine
|
||
de cousins -- contiennent une poignée de courts instruments enregistrés et la
|
||
liste des notes auxquelles les jouer. Un air de quatre minutes peut tenir en
|
||
moins de cent kilo-octets.
|
||
|
||
Les *chiptunes* -- `.nsf`, `.spc`, `.vgm`, `.gbs`, et leur famille -- vont plus
|
||
loin : elles contiennent les *écritures de registres* destinées à la vraie puce
|
||
sonore d'une Nintendo, d'une Mega Drive ou d'un Spectrum, et Singe émule la
|
||
puce pour les jouer. Si vous voulez qu'un jeu sonne comme 1989 plutôt que comme
|
||
un enregistrement de 1989, c'est comme ça.
|
||
|
||
Les uns comme les autres se chargent avec `musicLoad`, comme tout le reste. La
|
||
section *Formats vidéo, audio et de conteneur* de la Référence donne la liste
|
||
complète, y compris quel conteneur va avec quel codec.
|
||
|
||
[#_stopping_pausing_and_fading]
|
||
=== Arrêter, mettre en pause, estomper
|
||
|
||
[source,lua]
|
||
----
|
||
musicPause(theme)
|
||
musicResume(theme)
|
||
musicStop(theme)
|
||
musicStop(theme, 2000)
|
||
----
|
||
|
||
`musicPause` immobilise le morceau là où il en est et `musicResume` repart de
|
||
cet endroit. `musicStop` le termine, et le morceau revient à son début la
|
||
prochaine fois que vous le jouez. Avec un second nombre, `musicStop` fait
|
||
disparaître le morceau en fondu pendant ce nombre de millisecondes au lieu de
|
||
le couper net -- deux secondes ici. Ce fondu est la chose la plus facile que
|
||
vous puissiez faire pour qu'un jeu ait l'air fini. Une musique qui s'arrête au
|
||
milieu d'une mesure quand le joueur franchit une porte ressemble à un bug, même
|
||
pour quelqu'un qui ne saurait pas dire pourquoi.
|
||
|
||
Appelez l'un des quatre sans aucun handle et il agit sur toute la musique à la
|
||
fois, ce qui est exactement ce qu'il faut à un écran de pause.
|
||
|
||
Maintenant, la limite, en toute honnêteté, parce que vous allez la chercher et
|
||
qu'elle n'est pas là : *Singe ne sait pas faire de fondu enchaîné entre deux
|
||
morceaux.* `musicSetVolume` est un seul gain pour toute la musique, pas un
|
||
volume par morceau, alors il n'y a aucun moyen de faire monter un morceau
|
||
pendant qu'un autre descend. Ce que vous pouvez faire, c'est estomper l'ancien
|
||
et démarrer le nouveau pendant qu'il s'éteint, ce qui suffit pour un changement
|
||
de niveau et ne donne pas du tout le même résultat, à l'oreille, pour un thème
|
||
de combat qui se glisse sous un thème d'exploration. S'il vous faut aujourd'hui
|
||
un vrai fondu enchaîné, le moyen de l'obtenir est de faire des deux morceaux un
|
||
seul fichier et de sauter à l'intérieur, ou d'accepter le chevauchement.
|
||
|
||
[#_music_video_and_staying_in_step]
|
||
=== La musique, la vidéo et la synchronisation
|
||
|
||
Si votre jeu lit de la vidéo -- le sujet de la troisième partie -- il y a une
|
||
chose à savoir sur le son, et ce n'est pas du tout un appel de musique.
|
||
|
||
Singe affiche une image de la vidéo quand on entend le son qui va avec, pas
|
||
d'après un minuteur. Le son est l'horloge, et l'image le suit. C'est le bon
|
||
sens, parce qu'une oreille remarque un décalage d'un cinquième de seconde dans
|
||
une voix alors qu'un œil ne remarque pas le même décalage sur une bouche.
|
||
|
||
Le moteur mesure son propre chemin audio au démarrage. Ce qu'il ne peut pas
|
||
mesurer, c'est tout ce qui vient après que le système d'exploitation a laissé
|
||
partir le son : un amplificateur, un téléviseur, un récepteur HDMI, un casque
|
||
Bluetooth. Chacun ajoute son propre retard, il est différent sur chaque
|
||
machine, et il se manifeste par un son qui arrive un peu après la chose qui l'a
|
||
produit.
|
||
|
||
Le remède tient en un nombre :
|
||
|
||
[source,lua]
|
||
----
|
||
singeSetAudioDelay(45)
|
||
----
|
||
|
||
Quarante-cinq millisecondes de « le son est en retard ; retiens l'image
|
||
d'autant ». Vous pouvez aussi le passer en ligne de commande avec
|
||
`--audiodelay=45`, ou le mettre dans l'entrée du jeu dans `games.dat`.
|
||
`singeGetAudioDelay` relit la valeur en vigueur, ce qu'un menu de service
|
||
affiche à côté de son curseur.
|
||
|
||
Vous n'avez pas à deviner ce nombre. Le menu de Singe lui-même a un écran de
|
||
calibrage : il joue un clic une fois par seconde et fait clignoter l'écran en
|
||
blanc, vous réglez jusqu'à ce que l'éclair et le clic arrivent ensemble, et il
|
||
enregistre le résultat pour tous les jeux de cette machine. La section
|
||
*Synchronisation audio* de la Référence le décrit et explique pourquoi ça
|
||
marche. Si vous construisez une borne, lancez-le une fois après avoir câblé les
|
||
haut-parleurs, et n'y pensez plus jamais.
|
||
|
||
[#_the_other_thing_called_midi]
|
||
=== L'autre chose qui s'appelle MIDI
|
||
|
||
Le MIDI, ce sont deux choses sans rapport sous un seul nom, et les distinguer,
|
||
c'est déjà presque tout comprendre.
|
||
|
||
Voici la première, en un paragraphe. Un enregistrement -- un `.wav`, un
|
||
`.mp3` -- est une mesure d'un son, prise quarante-quatre mille fois par
|
||
seconde. Le MIDI, ce n'est pas ça. Un fichier MIDI contient *ce qui a été
|
||
joué* : à ce moment, sur ce canal, enfoncez la touche soixante avec cette
|
||
force ; un peu plus tard, relâchez-la. C'est plus proche d'une partition, ou du
|
||
rouleau perforé d'un piano mécanique, que d'un enregistrement. C'est pour cela
|
||
qu'un fichier MIDI fait quelques kilo-octets quand la même musique en audio
|
||
fait quelques mégaoctets, et c'est aussi pour cela qu'un fichier MIDI ne
|
||
contient aucun son. Il faut que quelque chose joue les notes.
|
||
|
||
Un fichier MIDI est de la musique, alors il se joue comme de la musique :
|
||
|
||
[source,lua]
|
||
----
|
||
local tune = musicLoad("music/overworld.mid")
|
||
musicPlay(tune, -1)
|
||
----
|
||
|
||
Mais ce qui joue les notes doit bien venir de quelque part, et cette chose,
|
||
c'est une *SoundFont* : un fichier plein d'instruments enregistrés -- un piano,
|
||
une trompette, une caisse claire -- avec les notes et les points de boucle
|
||
marqués, pour que le moteur puisse en tirer n'importe quelle note de n'importe
|
||
quel instrument.
|
||
|
||
Singe n'en fournit pas. Une bonne SoundFont pèse des dizaines de mégaoctets, et
|
||
ce seraient des dizaines de mégaoctets transportés par chaque copie du moteur
|
||
pour un format que la plupart des jeux n'utilisent jamais. Alors vous en
|
||
désignez une avec `--soundfont`, ou vous en mettez une en
|
||
`Singe/soundfont.sf2` pour qu'un jeu empaqueté porte la sienne, ou vous laissez
|
||
Singe trouver celle que votre distribution Linux a installée. Sans SoundFont
|
||
nulle part, le chargement d'un `.mid` échoue et le dit.
|
||
|
||
Voilà pourquoi le même fichier MIDI sonne différemment sur deux machines, et
|
||
pourquoi il sonnait si mal sur les ordinateurs des années quatre-vingt-dix. Le
|
||
fichier est une liste de notes. Ce que vous entendez, c'est la SoundFont qui
|
||
s'est trouvée les jouer : une bonne, c'est une salle pleine d'instruments, une
|
||
mauvaise, une salle pleine de jouets, et les notes sont identiques dans les
|
||
deux. Si un fichier MIDI va dans votre jeu, livrez la SoundFont avec. Sinon,
|
||
vous n'avez pas choisi le son de votre musique ; vous l'avez laissé à ce qui
|
||
est installé sur la machine du joueur.
|
||
|
||
[#_midi_as_a_wire]
|
||
=== Le MIDI comme un câble
|
||
|
||
Maintenant, la deuxième chose, qui est la plus intéressante.
|
||
|
||
Le MIDI, c'est aussi un câble, et des messages qui circulent dessus. Branchez
|
||
un clavier musical sur votre ordinateur et chaque touche que vous enfoncez
|
||
envoie un message : *note on, canal 1, touche 60, vélocité 90*. Tournez un
|
||
bouton sur une surface de contrôle et elle envoie *le contrôleur 7 du canal 1
|
||
vaut maintenant 40*. Ces messages existent, que quelque chose joue de la
|
||
musique ou non. C'est un périphérique d'entrée de plus, comme un joystick à
|
||
quatre-vingt-huit boutons qui savent avec quelle force vous les frappez.
|
||
|
||
Et ça marche dans l'autre sens. Votre jeu peut envoyer ces messages, vers un
|
||
synthétiseur, un module de sons, un contrôleur d'éclairage de scène, ou
|
||
n'importe quoi d'autre sur le câble.
|
||
|
||
Les ports sont numérotés à partir de zéro, et Singe n'ouvre rien tant que vous
|
||
ne le demandez pas -- un jeu qui ne parle jamais de MIDI ne paie jamais pour le
|
||
MIDI. Un port d'entrée et un port de sortie peuvent être ouverts à la fois.
|
||
Faites l'inventaire de la machine comme ceci :
|
||
|
||
[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
|
||
----
|
||
|
||
Sur une machine sans aucun MIDI, les nombres valent zéro, les appels
|
||
d'ouverture répondent `false`, et rien n'échoue. C'est important : cela veut
|
||
dire que vous pouvez écrire ce code dans un vrai jeu et le publier, et les
|
||
quatre-vingt-dix-neuf joueurs sur cent qui n'ont pas de clavier MIDI ne sauront
|
||
jamais qu'il est là. Les noms servent à un écran de réglages qui laisse le
|
||
joueur choisir le port, parce que le port zéro est rarement celui qu'il veut.
|
||
`midiRescan` cherche à nouveau ce qui a été branché pendant que le jeu tourne.
|
||
|
||
[#_listening]
|
||
=== Écouter
|
||
|
||
Chaque message qui arrive va à un callback, de la même façon que chaque appui
|
||
sur une touche va à `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 plupart des messages MIDI font trois octets, et les trois nombres qu'on vous
|
||
donne sont ces octets, exactement tels que l'appareil les a envoyés. Un message
|
||
plus court vous donne un `0` à la place de chaque octet qu'il n'a pas.
|
||
|
||
`status` dit de quelle sorte de message il s'agit et de quel canal il vient,
|
||
les deux mélangés : la sorte est dans les quatre bits du haut et le canal dans
|
||
les quatre du bas. `0x90`, c'est note on, et une note sur le canal 1 arrive
|
||
comme `0x90` alors que la même note sur le canal 5 arrive comme `0x94`.
|
||
`status & 0xF0` garde la moitié du haut et jette le canal, ce que vous voulez,
|
||
sauf si le canal vous importe. (`&` est le « et » bit à bit de Lua, et `0x90`
|
||
est un nombre écrit en hexadécimal -- en base seize -- parce que c'est ainsi
|
||
que tous les documents MIDI du monde les écrivent.)
|
||
|
||
`data1` et `data2` sont les deux autres octets, et leur sens dépend de la
|
||
sorte. Pour un note on, ce sont la touche et la vélocité : la touche `60` est
|
||
le do central, et la vélocité, c'est la force avec laquelle elle a été frappée,
|
||
de `1` à `127`.
|
||
|
||
Le test de `data2 == 0` n'est pas de la maniaquerie. Un note on avec une
|
||
vélocité de zéro veut dire *note off* : c'est ainsi que presque tous les
|
||
claviers jamais fabriqués disent « j'ai lâché », et si vous ne le filtrez pas,
|
||
votre jeu réagira deux fois pour chaque touche.
|
||
|
||
`bytes` est le message entier sous forme de chaîne ; c'est ainsi que vous lisez
|
||
un message de plus de trois octets.
|
||
|
||
[#_playing]
|
||
=== Jouer
|
||
|
||
Envoyer est plus facile que recevoir.
|
||
|
||
[source,lua]
|
||
----
|
||
midiNoteOn(1, 60, 100)
|
||
midiNoteOff(1, 60)
|
||
----
|
||
|
||
Le do central sur le canal 1, frappé à cent, puis relâché. Ici, les canaux vont
|
||
de `1` à `16`, comme les compte une personne, et non de `0` à `15` comme le
|
||
câble ; c'est Singe qui est gentil avec vous.
|
||
|
||
Chaque `midiNoteOn` a besoin d'un `midiNoteOff`. Une note jamais relâchée sonne
|
||
jusqu'à ce que quelque chose l'arrête, et le premier bug MIDI classique, c'est
|
||
un jeu qui plante ou qui quitte avec trois notes enfoncées et laisse un accord
|
||
suspendu dans la pièce. Alors associez-les par paires, et faites-le avec un
|
||
minuteur plutôt qu'à la main :
|
||
|
||
[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` fait un vrai travail. `timerAfter` exécute sa fonction
|
||
quatre dixièmes de seconde plus tard, et d'ici là `target` a pu changer pour
|
||
une autre note ; la copier dans une variable locale garantit que le minuteur
|
||
relâche la note qui a vraiment été enfoncée. Copiez tout ce dont un minuteur
|
||
aura besoin plus tard.
|
||
|
||
Les autres appels d'envoi font le reste de ce qu'est le MIDI :
|
||
`midiProgramChange` choisit l'instrument que joue un canal, `midiControlChange`
|
||
bouge un contrôleur (le volume, c'est 7, le panoramique 10, la pédale de
|
||
sustain 64), `midiPitchBend` infléchit la hauteur d'un canal, et `midiSend`
|
||
envoie n'importe quel message sous forme d'octets bruts, pour ce que les appels
|
||
nommés ne couvrent pas.
|
||
|
||
[#_a_reason_to_care]
|
||
=== Une raison de s'y intéresser
|
||
|
||
Tout cela, c'est de la mécanique. Voici à quoi ça sert.
|
||
|
||
Réunissez les deux moitiés et le jeu demande une note, le joueur la joue sur
|
||
un vrai instrument, et le jeu sait s'il a juste :
|
||
|
||
[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
|
||
----
|
||
|
||
Onze lignes, et vous avez le squelette d'un jeu pour apprendre la musique.
|
||
Ajoutez une horloge et vous avez un jeu de rythme qui note un vrai jeu
|
||
d'instrument plutôt que quatre boutons en plastique. Ajoutez `data2` au test
|
||
et il tient compte de la force avec laquelle vous frappez la note. Comparez le
|
||
moment où la note est arrivée au moment où elle était attendue et vous notez la
|
||
précision rythmique à la milliseconde près.
|
||
|
||
Et comme le MIDI sort autant qu'il entre, une borne peut faire des choses que
|
||
rien d'autre ne peut. Un vrai instrument câblé à la machine. Un module de sons
|
||
externe qui joue la partition, pour que l'air ait une profondeur qu'aucune
|
||
banque d'échantillons ne lui donne. Des lumières de scène sur la borne qui
|
||
clignotent en rythme parce que le jeu leur a envoyé le rythme. Si vous êtes du
|
||
genre à construire des bornes d'arcade, c'est l'appel qui permet à la borne
|
||
d'être une vraie chose plutôt qu'une boîte autour d'un ordinateur.
|
||
|
||
Le script terminé est `learn/27-midi.singe` : les commandes de la musique, la
|
||
liste des ports et le jeu de devinette, en une centaine de lignes. La barre
|
||
d'espace envoie la note, Alt gauche met la musique en pause, Maj gauche la fait
|
||
s'estomper, et les flèches haut et bas règlent le volume. Lancez-le sans
|
||
matériel MIDI : la moitié musique marche et la moitié MIDI dit deux fois
|
||
`none`, et c'est ainsi qu'il doit se comporter sur la machine d'un joueur.
|
||
|
||
[#_cleaning_up]
|
||
=== Faire le ménage
|
||
|
||
[source,lua]
|
||
----
|
||
function onShutdown()
|
||
midiCloseInput()
|
||
midiCloseOutput()
|
||
musicUnload(theme)
|
||
end
|
||
----
|
||
|
||
`onShutdown` est appelé une fois quand le jeu se termine, et c'est là qu'un
|
||
script rend ce qu'il a emprunté. Fermer un port qui n'est pas ouvert ne fait
|
||
rien, alors il n'y a rien à vérifier avant.
|
||
|
||
[#_try_it_27]
|
||
=== Essayez
|
||
|
||
. *Faites-la boucler quatre fois au lieu d'indéfiniment.* Changez
|
||
`musicPlay(theme, -1)` en `musicPlay(theme, 3)` et comptez. Le nombre dit
|
||
combien de fois elle se *répète*, pas combien de fois elle joue.
|
||
. *Estompez plus longtemps.* Changez le fondu du bouton qui arrête la musique
|
||
pour `musicStop(theme, 4000)` et écoutez la différence que font quatre secondes.
|
||
Puis essayez `musicStop(theme, 100)`.
|
||
. *Affichez le canal.* Dans `onMidiMessage`, calculez le canal avec
|
||
`(status & 0x0F) + 1` et affichez-le. Jouez sur votre clavier et voyez sur
|
||
quel canal il est -- puis trouvez ses réglages et changez ce canal, pour vous
|
||
prouver que le nombre est réel.
|
||
. *Notez la vélocité.* Donnez un point pour la bonne note, et trois points si
|
||
`data2` dépasse cent, pour que le jeu récompense ceux qui jouent avec
|
||
conviction.
|
||
. *Jouez la réponse.* Quand le joueur trouve la bonne note, faites jouer au jeu
|
||
un petit accord en retour, avec trois appels à `midiNoteOn` espacés de
|
||
quelques notes et trois minuteurs pour les relâcher. Il vous faudra pour cela
|
||
un vrai port de sortie ou un synthétiseur logiciel, et sous Linux, c'est
|
||
n'importe quel programme qui s'enregistre auprès d'ALSA.
|
||
|
||
[#_break_it_on_purpose_26]
|
||
=== Cassez-le exprès
|
||
|
||
Les deux échelles de volume sont faciles à confondre, et Singe préfère
|
||
s'arrêter plutôt que de jouer votre musique à un niveau que vous ne vouliez
|
||
pas. Ajoutez une ligne sous `musicPlay(theme, -1)`, avec le nombre qui serait
|
||
le plein volume d'un effet sonore si vous le doubliez de tête en vous
|
||
trompant :
|
||
|
||
[source,lua]
|
||
----
|
||
musicSetVolume(200)
|
||
----
|
||
|
||
Singe s'arrête avant que le jeu démarre, en donnant la ligne de votre
|
||
fichier où se trouve cet appel :
|
||
|
||
----
|
||
107:musicSetVolume: Invalid music volume value: 200
|
||
----
|
||
|
||
La ligne, l'appel qui a protesté, et la plainte, qui vous renvoie ici le nombre
|
||
pour qu'il n'y ait pas de discussion sur ce qui est arrivé. La musique, c'est
|
||
de `0` à `128`. Les effets sonores, de `0` à `63`. Quand un appel de volume
|
||
arrête votre jeu, la question à se poser, c'est à laquelle des deux familles
|
||
vous parlez.
|
||
|
||
[#_what_you_learned_27]
|
||
=== Ce que vous avez appris
|
||
|
||
* La musique et les effets sonores sont séparés dans Singe : chacun son volume,
|
||
pas de limite de canaux pour la musique, et une pause qui suit celle du jeu.
|
||
* Un fichier de musique est décodé en mémoire dès le départ, si bien qu'un long
|
||
morceau coûte de la mémoire et doit être déchargé quand vous quittez le
|
||
niveau.
|
||
* `musicPlay(track, -1)` boucle indéfiniment ; un nombre positif dit combien de
|
||
fois il se répète.
|
||
* Le volume de la musique va de `0` à `128` et celui des effets de `0` à `63`,
|
||
et cette différence vient de la compatibilité avec Hypseus ; ce n'est pas une
|
||
erreur.
|
||
* `musicStop(track, milliseconds)` fait un fondu de sortie, et un fondu est la
|
||
finition la moins chère qui soit. Il n'y a pas de fondu enchaîné, parce qu'un
|
||
seul gain couvre toute la musique.
|
||
* Singe présente la vidéo d'après l'horloge audio, et `singeSetAudioDelay`
|
||
corrige le retard de ce par quoi sort le son.
|
||
* Un fichier MIDI contient des notes, pas du son, et il lui faut une SoundFont
|
||
pour qu'on l'entende. Singe n'en fournit aucune, alors livrez la vôtre, sinon
|
||
votre musique sera ce que la machine du joueur se trouve avoir.
|
||
* Les ports MIDI sont tout autre chose : des messages en direct, qui entrent et
|
||
sortent, sur un câble, sans rapport avec les fichiers MIDI.
|
||
* `onMidiMessage` vous donne les octets bruts. Les quatre bits du haut de
|
||
`status` sont la sorte, les quatre du bas sont le canal, et un note on de
|
||
vélocité zéro veut dire note off.
|
||
* Chaque `midiNoteOn` a besoin d'un `midiNoteOff`, et c'est un minuteur qui les
|
||
associe.
|
||
* Sur une machine sans MIDI, chaque appel répond `false` ou zéro et rien
|
||
n'échoue, alors la prise en charge du MIDI peut être publiée sans risque.
|
||
|
||
[#_next_time_27]
|
||
=== La prochaine fois
|
||
|
||
Votre jeu a de la musique, et un score qu'un clavier MIDI peut faire grimper. La
|
||
leçon vingt-huit emmène ce score hors de la machine : les comptes, des tableaux
|
||
de meilleurs scores que d'autres peuvent voir, et le catalogue qui présente
|
||
votre jeu au monde entier.
|