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