singe/docs/de/lessons/27-midi.adoc

476 lines
23 KiB
Text

[#_lesson_27_music_and_midi]
== Lektion 27: Musik und MIDI
image::learn/27-midi.png[Die fertige Lektion, 480]
Diese Lektion besteht aus zwei Lektionen, die zufällig zusammenwohnen.
Die erste Hälfte ist Musik: wie ein Spiel ein Musikstück richtig abspielt, warum das nicht dasselbe
ist, wie einen Soundeffekt abzuspielen, und was zu tun ist, wenn die Musik mit dem Video im Takt
bleiben muss.
Die zweite Hälfte ist MIDI, dem die meisten Leute noch nie begegnet sind und das einem Spiel etwas
gibt, was nichts anderes kann. Am Ende spielt ein Keyboard, das an deinen Computer angeschlossen
ist, dein Spiel.
[#_a_sound_effect_and_a_piece_of_music_are_different_things]
=== Ein Soundeffekt und ein Musikstück sind verschiedene Dinge
Wie man einen Sound abspielt, weißt du schon. Lektion zehn hat eine `.wav` mit `soundLoad` geladen
und mit `soundPlay` abgefeuert, und wenn du diese beiden Aufrufe auf einen vier Minuten langen Song
richtest, spielen sie ihn ab.
Lass es. Singe hält Soundeffekte und Musik auseinander, und diese Trennung lohnt sich aus vier
Gründen.
* *Sie haben ihre eigenen Lautstärken.* `soundSetVolume` rührt die Musik nie an, und
`musicSetVolume` rührt die Schüsse nie an. Jedes Spiel, das je veröffentlicht wurde, hat das
gebraucht, denn Spieler drehen die Musik leiser und die Effekte lauter.
* *Musik hat keine Kanalgrenze.* Soundeffekte teilen sich sechzehn Kanäle, und wenn alle sechzehn
belegt sind, spielt der siebzehnte nicht. Ein Musiktrack liegt auf keinem Kanal, also kann ihn
nichts verdrängen.
* *Ein Handle ist ein Track*, und er pausiert, läuft weiter, stoppt und blendet aus, ganz für sich,
ohne etwas anderes zu stören.
* *Musik pausiert mit dem Spiel.* Drück die Pausetaste, und sie hält an, wo sie ist. Komm zurück,
und sie macht weiter. Du schreibst nichts, damit das passiert.
Eine Sache, die *nicht* anders ist, solltest du kennen, weil die Leute das Gegenteil annehmen:
Singe dekodiert eine Musikdatei vorab in den Speicher, genau wie einen Soundeffekt. Ein langer Track
kostet Speicher. Er wird nicht Stück für Stück von der Festplatte gestreamt. Fünf Minuten Musik sind
ein paar Dutzend Megabyte Speicher, solange sie geladen sind, also lade den Track für das Level, in
dem du gerade bist, und gib ihn mit `musicUnload` frei, wenn du es verlässt.
[#_playing_a_track]
=== Einen Track abspielen
Jeder Leser dieses Buchs hat schon ein Musikstück, denn die Engine hat eins in deinen Arbeitsordner
entpackt, als du zum ersten Mal irgendetwas gestartet hast. Fang eine Datei mit diesen drei Zeilen
an:
[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
----
Starte es mit `Singe -R music`. Zehn Sekunden Musik, dann fängt sie von vorn an, und sie fängt immer
wieder von vorn an.
Das macht die `-1`. `musicPlay` nimmt eine Anzahl von *Wiederholungen*: Lass sie weg oder übergib
`0`, und der Track spielt einmal; übergib `3`, und er spielt insgesamt viermal; übergib `-1`, und er
wiederholt sich, bis ihn etwas stoppt. Ein Menü, ein Titelbildschirm oder der Hintergrund eines
Levels ist fast immer `-1`.
Zwei Details in diesem kleinen Skript sind die Tinte wert. `musicLoad` nimmt einen Pfad und keinen
Pfad mit `DIR ..`, weil `Singe/menuIntro.flac` eine der eigenen Dateien der Engine ist und in dem
Ordner liegt, den die Engine in deinen Arbeitsordner entpackt hat. `musicIsPlaying` antwortet `true`
oder `false`, und das `and ... or ...` mitten in diesem `overlayPrint` ist die Lua-Kurzform aus
Lektion drei, um einen von zwei Werten auszuwählen.
[#_the_volume_scale_is_not_the_one_you_know]
=== Die Lautstärkeskala ist nicht die, die du kennst
`musicSetVolume(64)` stellt sie auf etwa die Hälfte. Soundeffekte gehen von `0` bis `63`; Musik geht
von `0` bis `128`, also doppelt so weit.
Das ist kein Fehler und auch kein Witz. Singe kann Spiele ausführen, die für Hypseus geschrieben
wurden, eine Engine, deren Musiklautstärke schon immer auf einer Skala von `0` bis `128` lag, und
die Zahlen zu ändern würde die Musik dieser Spiele still und heimlich doppelt so laut machen, wie
ihre Autoren es wollten. Also behält Singe beide Skalen, jede an die Familie von Aufrufen gebunden,
zu der sie gehört. Effekte: `0` bis `63`. Musik: `0` bis `128`. Gib `musicSetVolume` eine Zahl
außerhalb dieses Bereichs, und das Skript hält an; das siehst du am Ende dieser Lektion selbst.
[#_what_singe_will_play]
=== Was Singe abspielt
Fast alles. WAV, Ogg Vorbis, Opus, MP3, FLAC, WavPack, AAC und darüber hinaus eine lange Liste,
alles über dasselbe `musicLoad`.
Zwei Familien sind interessanter als die gewöhnlichen, und beide gibt es, weil sie winzig sind.
*Tracker-Module* -- `.mod`, `.s3m`, `.xm`, `.it` und gut fünfzig Verwandte -- enthalten eine Handvoll
kurzer aufgenommener Instrumente und eine Liste, auf welchen Noten sie gespielt werden sollen. Eine
vier Minuten lange Melodie kann unter hundert Kilobyte bleiben.
*Chiptunes* -- `.nsf`, `.spc`, `.vgm`, `.gbs` und ihre Verwandtschaft -- gehen noch weiter: Sie
enthalten die *Registerschreibzugriffe* für den echten Soundchip eines Nintendo, eines Mega Drive
oder eines Spectrum, und Singe emuliert den Chip, um sie abzuspielen. Wenn ein Spiel nach 1989
klingen soll und nicht nach einer Aufnahme von 1989, geht es so.
Beide lassen sich wie alles andere mit `musicLoad` laden. Der Abschnitt *Video-, Audio- und
Containerformate* in der Referenz hat die vollständige Liste, auch welcher Container zu welchem Codec
gehört.
[#_stopping_pausing_and_fading]
=== Stoppen, Pausieren und Ausblenden
[source,lua]
----
musicPause(theme)
musicResume(theme)
musicStop(theme)
musicStop(theme, 2000)
----
`musicPause` hält den Track an, wo er ist, und `musicResume` macht an dieser Stelle weiter.
`musicStop` beendet ihn, und beim nächsten Abspielen fängt der Track wieder von vorn an. Mit einer
zweiten Zahl blendet `musicStop` den Track über so viele Millisekunden aus, statt ihn abrupt
abzuschneiden -- hier zwei Sekunden. Dieses Ausblenden ist das Einfachste überhaupt, was du tun
kannst, damit sich ein Spiel fertig anfühlt. Musik, die mitten im Takt aufhört, wenn der Spieler
durch eine Tür geht, klingt wie ein Bug, selbst für jemanden, der nicht sagen könnte, warum.
Ruf einen der vier ganz ohne Handle auf, und er wirkt auf alle Musik auf einmal; genau das will ein
Pausenbildschirm.
Jetzt die ehrliche Grenze, denn du wirst nach dieser Funktion suchen, und sie ist nicht da: *Singe
kann keine zwei Tracks überblenden.* `musicSetVolume` ist eine einzige Verstärkung über alle Musik,
keine Lautstärke pro Track, also gibt es keine Möglichkeit, einen Track lauter zu machen, während ein
anderer leiser wird. Was du tun kannst, ist, den alten auszublenden und den neuen zu starten, während
er verklingt; für einen Levelwechsel ist das nah genug dran, und für ein Kampfthema, das unter ein
Erkundungsthema gleitet, hörbar nicht dasselbe. Wenn du heute eine echte Überblendung brauchst,
machst du aus den beiden Stücken eine einzige Datei und springst darin, oder du nimmst die
Überlappung in Kauf.
[#_music_video_and_staying_in_step]
=== Musik, Video und im Takt bleiben
Wenn dein Spiel Video abspielt -- das Thema von Teil drei -- gibt es eine Sache über Sound zu
wissen, und sie ist überhaupt kein Musik-Aufruf.
Singe zeigt einen Frame des Videos dann, wenn der Sound zu hören ist, der dazugehört, nicht nach
einem Timer. Der Ton ist die Uhr, und das Bild folgt ihm. So herum ist es richtig, denn ein Ohr
bemerkt eine Fünftelsekunde Versatz in einer Stimme, und ein Auge bemerkt denselben Versatz an
einem Mund nicht.
Die Engine misst beim Start ihren eigenen Audioweg. Was sie nicht messen kann, ist alles, nachdem
das Betriebssystem den Ton weitergereicht hat: ein Verstärker, ein Fernseher, ein HDMI-Receiver,
ein Bluetooth-Headset. Jedes davon fügt seine eigene Verzögerung hinzu, sie ist auf jedem Rechner
anders, und sie zeigt sich darin, dass der Ton etwas später ankommt als das Ding, das ihn gemacht
hat.
Die Lösung ist eine einzige Zahl:
[source,lua]
----
singeSetAudioDelay(45)
----
Fünfundvierzig Millisekunden „der Ton ist spät; halte das Bild entsprechend zurück“. Du kannst sie
auch als `--audiodelay=45` auf der Befehlszeile übergeben oder in den Eintrag des Spiels in
`games.dat` schreiben. `singeGetAudioDelay` liest zurück, was gerade gilt, und das zeigt ein
Service-Menü neben seinem Schieberegler an.
Du musst die Zahl nicht raten. Singes eigenes Menü hat einen Kalibrierungsbildschirm: Er spielt
einmal pro Sekunde einen Klick und lässt den Bildschirm weiß aufblitzen, du stellst nach, bis Blitz
und Klick gleichzeitig passieren, und er speichert das Ergebnis für jedes Spiel auf diesem Rechner.
Der Abschnitt *Audio-Synchronisation* in der Referenz beschreibt ihn und erklärt, warum er funktioniert.
Wenn du einen Automaten baust, lass ihn einmal laufen, nachdem du die Lautsprecher angeschlossen
hast, und denk nie wieder daran.
[#_the_other_thing_called_midi]
=== Das andere Ding namens MIDI
MIDI sind zwei Dinge, die nichts miteinander zu tun haben, unter einem Namen, und sie
auseinanderzuhalten ist schon der größte Teil davon, es zu verstehen.
Hier ist das erste, in einem Absatz. Eine Aufnahme -- eine `.wav`, eine `.mp3` -- ist eine Messung
eines Klangs, vierundvierzigtausendmal pro Sekunde genommen. MIDI ist das nicht. Eine MIDI-Datei
enthält, *was gespielt wurde*: in diesem Moment, auf diesem Kanal, drück Taste sechzig so fest;
etwas später lass sie los. Sie ist näher an Noten oder an der Lochrolle eines selbstspielenden
Klaviers als an einer Aufnahme. Darum hat eine MIDI-Datei ein paar Kilobyte, wenn dieselbe Musik als
Audio ein paar Megabyte hat, und darum enthält eine MIDI-Datei auch überhaupt keinen Klang.
Irgendetwas muss die Noten spielen.
Eine MIDI-Datei ist Musik, also spielt sie wie Musik:
[source,lua]
----
local tune = musicLoad("music/overworld.mid")
musicPlay(tune, -1)
----
Aber das Ding, das die Noten spielt, muss irgendwoher kommen, und dieses Ding ist ein *SoundFont*:
eine Datei voller aufgenommener Instrumente -- ein Klavier, eine Trompete, eine Snare-Drum -- mit
markierten Noten und Schleifenpunkten, damit die Engine daraus jede Note jedes Instruments bauen
kann.
Singe liefert keinen mit. Ein guter SoundFont hat Dutzende Megabyte, und das wären Dutzende
Megabyte, die jede Kopie der Engine mit sich herumträgt, für ein Format, das die meisten Spiele nie
anfassen. Also nennst du einen mit `--soundfont`, oder du legst einen nach `Singe/soundfont.sf2`,
damit ein gepacktes Spiel seinen eigenen mitbringt, oder du lässt Singe den finden, den deine
Linux-Distribution installiert hat. Gibt es nirgends einen SoundFont, schlägt das Laden einer `.mid`
fehl und sagt das auch.
Darum klingt dieselbe MIDI-Datei auf zwei Rechnern verschieden, und darum klang sie auf den
Computern der Neunzigerjahre so schlecht. Die Datei ist eine Liste von Noten. Was du hörst, ist der
SoundFont, der sie gerade gespielt hat: Ein guter ist ein Raum voller Instrumente, ein billiger ist
ein Raum voller Spielzeug, und die Noten sind in beiden gleich. Wenn eine MIDI-Datei in dein Spiel
kommt, liefere den SoundFont mit. Sonst hast du nicht entschieden, wie deine Musik klingt; du hast
es dem überlassen, was auf dem Rechner des Spielers installiert ist.
[#_midi_as_a_wire]
=== MIDI als Leitung
Jetzt das zweite Ding, und das ist das interessante.
MIDI ist auch ein Kabel, und Nachrichten, die darüber laufen. Schließ ein Musik-Keyboard an deinen
Computer an, und jede Taste, die du drückst, schickt eine Nachricht: *Note On, Kanal 1, Taste 60,
Anschlagstärke 90*. Dreh an einem Regler einer Controller-Oberfläche, und sie schickt *Controller 7
auf Kanal 1 steht jetzt auf 40*. Diese Nachrichten gibt es, ob gerade Musik läuft oder nicht. Sie
sind ein weiteres Eingabegerät, wie ein Joystick mit achtundachtzig Knöpfen, die wissen, wie fest du
sie anschlägst.
Und es geht auch in die andere Richtung. Dein Spiel kann solche Nachrichten hinausschicken, an einen
Synthesizer, ein Soundmodul, eine Bühnenlichtsteuerung oder irgendetwas anderes an der Leitung.
Ports werden ab null gezählt, und Singe öffnet nichts, bis du darum bittest -- ein Spiel, das MIDI
nie erwähnt, zahlt auch nie für MIDI. Ein Eingangsport und ein Ausgangsport dürfen gleichzeitig
offen sein. Mach so eine Bestandsaufnahme des Rechners:
[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
----
Auf einem Rechner ganz ohne MIDI sind die Anzahlen null, die Aufrufe zum Öffnen antworten `false`,
und nichts schlägt fehl. Das ist wichtig: Es heißt, dass du diesen Code in ein echtes Spiel
schreiben und es ausliefern kannst, und die neunundneunzig von hundert Spielern ohne MIDI-Keyboard
merken nie, dass er da ist. Die Namen sind für einen Einstellungsbildschirm, auf dem ein Spieler den
Port auswählt, denn Port null ist selten der, den er will. `midiRescan` sucht noch einmal nach
etwas, das angeschlossen wurde, während das Spiel läuft.
[#_listening]
=== Zuhören
Jede ankommende Nachricht geht an einen Callback, genauso wie jeder Tastendruck an
`onInputPressed` geht:
[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
----
Die meisten MIDI-Nachrichten sind drei Bytes lang, und die drei Zahlen, die du bekommst, sind genau
diese Bytes, so wie das Gerät sie geschickt hat. Eine Nachricht mit weniger Bytes gibt dir für jedes,
das ihr fehlt, eine `0`.
`status` sagt, welche Art Nachricht es ist und von welchem Kanal sie kam, beides vermischt: Die Art
steht in den oberen vier Bits und der Kanal in den unteren vier. `0x90` ist Note On, und eine Note
auf Kanal 1 kommt als `0x90` an, während dieselbe Note auf Kanal 5 als `0x94` ankommt.
`status & 0xF0` behält die obere Hälfte und wirft den Kanal weg, und das willst du, außer es ist dir
wichtig, welcher Kanal es war. (`&` ist das bitweise Und von Lua, und `0x90` ist eine Zahl in
hexadezimaler Schreibweise -- zur Basis sechzehn -- weil jedes MIDI-Dokument der Welt sie so
schreibt.)
`data1` und `data2` sind die anderen beiden Bytes, und was sie bedeuten, hängt von der Art ab. Bei
einem Note On sind sie die Taste und die Anschlagstärke: Taste `60` ist das mittlere C, und die
Anschlagstärke ist, wie fest sie angeschlagen wurde, `1` bis `127`.
Die Prüfung auf `data2 == 0` ist keine Pedanterie. Ein Note On mit einer Anschlagstärke von null
bedeutet *Note Off*: So sagt fast jedes Keyboard, das je gebaut wurde, „ich habe losgelassen“, und
wenn du das nicht herausfilterst, löst dein Spiel bei jeder Taste zweimal aus.
`bytes` ist die ganze Nachricht als String; so liest du eine Nachricht, die länger als drei Bytes
ist.
[#_playing]
=== Spielen
Senden ist leichter als Empfangen.
[source,lua]
----
midiNoteOn(1, 60, 100)
midiNoteOff(1, 60)
----
Das mittlere C auf Kanal 1, mit hundert angeschlagen und dann losgelassen. Kanäle gehen hier von `1`
bis `16`, wie ein Mensch sie zählt, nicht von `0` bis `15` wie auf der Leitung; damit ist Singe nett
zu dir.
Jedes `midiNoteOn` braucht ein `midiNoteOff`. Eine Note, die nie losgelassen wird, klingt, bis etwas
sie stoppt, und der klassische erste MIDI-Bug ist ein Spiel, das abstürzt oder sich beendet, während
drei Noten gedrückt sind, und einen Akkord im Raum hängen lässt. Also bilde Paare, und zwar über
einen Timer statt von Hand:
[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` leistet echte Arbeit. `timerAfter` führt seine Funktion vier Zehntelsekunden
später aus, und bis dahin kann sich `target` in eine andere Note geändert haben; wenn du es in eine
lokale Variable kopierst, lässt der Timer die Note los, die wirklich gedrückt wurde. Kopier alles,
was ein Timer später braucht.
Die anderen Sende-Aufrufe erledigen den Rest dessen, was MIDI ist: `midiProgramChange` wählt, welches
Instrument ein Kanal spielt, `midiControlChange` bewegt einen Controller (Lautstärke ist 7, Panorama
ist 10, das Haltepedal ist 64), `midiPitchBend` verbiegt die Tonhöhe eines Kanals, und `midiSend`
schickt jede beliebige Nachricht als rohe Bytes, für die Dinge, die die benannten Aufrufe nicht
abdecken.
[#_a_reason_to_care]
=== Ein Grund, sich dafür zu interessieren
Das alles ist Maschinerie. Hier ist, wofür sie da ist.
Setz die beiden Hälften zusammen, und das Spiel verlangt eine Note, der Spieler spielt sie auf einem
echten Instrument, und das Spiel weiß, ob sie richtig war:
[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
----
Elf Zeilen, und du hast das Gerüst eines Lernspiels für Musik. Füg eine Uhr hinzu, und du hast ein
Rhythmusspiel, das echtes Spielen bewertet statt vier Plastikknöpfe. Nimm `data2` in die Prüfung
auf, und es achtet darauf, wie fest du die Note anschlägst. Vergleich die Zeit, zu der die Note
ankam, mit der Zeit, zu der sie fällig war, und du bewertest das Timing auf die Millisekunde genau.
Und weil MIDI nicht nur hereinkommt, sondern auch hinausgeht, kann ein Automat Dinge, die nichts
anderes kann. Ein echtes Instrument, das an den Automaten angeschlossen ist. Ein externes Soundmodul,
das die Partitur spielt, damit die Melodie eine Tiefe hat, die ihr keine Sample-Bank gibt.
Bühnenlichter am Automaten, die im Takt blinken, weil das Spiel ihnen den Takt geschickt hat. Wenn
du zu den Leuten gehörst, die Arcade-Automaten bauen, ist das der Aufruf, mit dem der Automat ein
eigenes Ding wird statt eines Kastens um einen Computer.
Das fertige Skript ist `learn/27-midi.singe`: die Musiksteuerung, die Portliste und das Ratespiel,
in etwa hundert Zeilen. Die Leertaste schickt die Note hinaus, die linke Alt-Taste pausiert die
Musik, die linke Umschalttaste blendet sie aus, und die Pfeiltasten nach oben und unten ändern die
Lautstärke. Starte es ohne MIDI-Hardware, und die Musikhälfte funktioniert, und die MIDI-Hälfte sagt
zweimal `none`, und genau so soll es sich auf dem Rechner eines Spielers verhalten.
[#_cleaning_up]
=== Aufräumen
[source,lua]
----
function onShutdown()
midiCloseInput()
midiCloseOutput()
musicUnload(theme)
end
----
`onShutdown` wird einmal aufgerufen, wenn das Spiel endet, und dort gibt ein Skript Dinge zurück.
Einen Port zu schließen, der nicht offen ist, bewirkt nichts, also musst du vorher nichts prüfen.
[#_try_it_27]
=== Probier es aus
. *Lass es viermal statt für immer laufen.* Ändere `musicPlay(theme, -1)` in
`musicPlay(theme, 3)` und zähl mit. Die Zahl gibt an, wie oft er sich *wiederholt*, nicht, wie
oft er spielt.
. *Blende länger aus.* Ändere das Ausblenden am Knopf, der die Musik stoppt, in
`musicStop(theme, 4000)` und hör dir an, was für einen Unterschied vier Sekunden machen. Dann
probier `musicStop(theme, 100)`.
. *Zeig den Kanal.* Rechne in `onMidiMessage` den Kanal mit `(status & 0x0F) + 1` aus und gib ihn
aus. Spiel auf deinem Keyboard und sieh nach, auf welchem Kanal es ist -- und dann such seine
Einstellungen und ändere diesen Kanal, um dir selbst zu beweisen, dass die Zahl echt ist.
. *Bewerte die Anschlagstärke.* Gib einen Punkt für die richtige Note und drei Punkte, wenn `data2`
über hundert liegt, damit das Spiel belohnt, wer die Note spielt, als meinte er es ernst.
. *Spiel die Antwort.* Wenn der Spieler eine Note richtig trifft, lass das Spiel ihm einen kleinen
Akkord zurückspielen, mit drei Aufrufen von `midiNoteOn`, ein paar Noten auseinander, und drei
Timern, die sie wieder loslassen. Dafür brauchst du einen echten Ausgangsport oder einen
Software-Synthesizer, und unter Linux ist das jedes Programm, das sich bei ALSA anmeldet.
[#_break_it_on_purpose_26]
=== Mach es absichtlich kaputt
Die beiden Lautstärkeskalen kann man leicht verwechseln, und Singe hält lieber an, als deine Musik
in einer Lautstärke abzuspielen, die du nicht wolltest. Füg unter `musicPlay(theme, -1)` eine Zeile
ein, mit der Zahl, die volle Lautstärke für einen Soundeffekt wäre, wenn du sie im Kopf verdoppelt
und dich dabei verrechnet hättest:
[source,lua]
----
musicSetVolume(200)
----
Singe hält an, bevor das Spiel startet, und nennt die Zeile in deiner Datei, in der dieser
Aufruf steht:
----
107:musicSetVolume: Invalid music volume value: 200
----
Die Zeile, der Aufruf, der sich gewehrt hat, und die Beschwerde, die dir hier die Zahl wörtlich
zurückgibt, damit es keinen Streit darüber gibt, was angekommen ist. Musik ist `0` bis `128`.
Soundeffekte sind `0` bis `63`. Wenn ein Lautstärke-Aufruf dein Spiel anhält, lautet die Frage, mit
welcher der beiden Familien du gerade sprichst.
[#_what_you_learned_27]
=== Was du gelernt hast
* Musik und Soundeffekte sind in Singe getrennt: eigene Lautstärken, keine Kanalgrenze für Musik
und eine Pause, die der Pause des Spiels folgt.
* Eine Musikdatei wird vorab in den Speicher dekodiert, also kostet ein langer Track Speicher und
sollte freigegeben werden, wenn du das Level verlässt.
* `musicPlay(track, -1)` wiederholt für immer; eine positive Zahl gibt an, wie oft sich der Track
wiederholt.
* Die Musiklautstärke geht von `0` bis `128` und die Effektlautstärke von `0` bis `63`, und der
Unterschied ist Kompatibilität mit Hypseus, kein Fehler.
* `musicStop(track, milliseconds)` blendet aus, und ein Ausblenden ist der billigste Feinschliff,
den es gibt. Es gibt keine Überblendung, weil eine einzige Verstärkung für alle Musik gilt.
* Singe zeigt Video nach der Uhr des Tons, und `singeSetAudioDelay` gleicht die Verzögerung dessen
aus, woraus der Ton kommt.
* Eine MIDI-Datei enthält Noten, keinen Klang, und braucht einen SoundFont, um überhaupt hörbar zu
sein. Singe liefert keinen mit, also liefere deinen eigenen mit, sonst ist deine Musik das, was der
Rechner des Spielers zufällig hat.
* MIDI-Ports sind etwas völlig anderes: Live-Nachrichten hinein und hinaus, auf einer Leitung, ohne
irgendetwas mit MIDI-Dateien zu tun.
* `onMidiMessage` gibt dir die rohen Bytes. Die oberen vier Bits von `status` sind die Art, die
unteren vier der Kanal, und ein Note On mit Anschlagstärke null bedeutet Note Off.
* Jedes `midiNoteOn` braucht ein `midiNoteOff`, und mit einem Timer bildest du die Paare.
* Auf einem Rechner ohne MIDI antwortet jeder Aufruf `false` oder null, und nichts schlägt fehl,
also kannst du MIDI-Unterstützung gefahrlos ausliefern.
[#_next_time_27]
=== Beim nächsten Mal
Dein Spiel hat Musik und einen Punktestand, den ein MIDI-Keyboard hochtreiben kann. Lektion
achtundzwanzig bringt diesen Punktestand vom Rechner weg: Konten, Highscore-Tabellen, die andere
Leute sehen können, und der Katalog, der dein Spiel der Welt zeigt.