Rhythmus-Spiel: Tastendrücke gegen die Audio-Uhr auswerten #438

Closed
opened 2026-10-30 21:45:34 +01:00 by meleki · 1 comment
Owner

Übrig aus #330 („Was der DAW bewusst noch fehlt“). Im Wiki steht es unter Songs → Not yet: „it cannot follow the keys a player presses against the clock of the sound for a rhythm game“.

Ausgangslage

  • Song position (#338) sagt, wo ein Song oder Pattern hörbar steht, die Ausgabe-Latenz ist abgezogen. Gelesen wird es aber im Frame, nicht im Moment des Tastendrucks.
  • Input (src/runtime/systems/Input.ts) merkt sich nur, dass eine Taste seit dem letzten Frame gedrückt wurde, nicht wann. Ein Druck ist damit nur auf einen Frame genau: 16,7 ms bei 60 Hz, bei einem Ruckler mehr.
  • Übliche Fenster in Rhythmus-Spielen sind ±20 bis 50 ms für einen sauberen Treffer. Ein Frame Fehler ist da schon ein Drittel bis fast das ganze Fenster.
  • Heute müsste man die Abweichung im Graph aus fraction von Song position zusammenbauen. Das hat den Frame-Fehler und ist umständlich.

Vorschlag

  1. Zeitpunkt des Drucks mitschreiben: Input hält zu jedem Druck KeyboardEvent.timeStamp. Die Laufzeit rechnet ihn auf die Audio-Uhr um, über AudioContext.getOutputTimestamp(), und daraus auf die Stelle im Song, wie Song position es tut.
  2. Neuer Event-Node On key hit (Arbeitsname): Feld Key, dazu Song, Pattern und der Eingang playback wie bei On beat. Ausgänge:
    • offset in s zum nächsten Rasterpunkt, negativ = zu früh,
    • bar und beat des Rasterpunkts,
    • seconds, die Stelle im Song zum Zeitpunkt des Drucks.
  3. Raster als Feld: Schlag, 1/8 oder 1/16. Bei einem Pattern mit Swing (#372) liegen die Rasterpunkte dort, wo der Swing die Noten hinschiebt.
  4. Kalibrierung: ein Eingang calibration in s, der auf jeden Druck aufgeschlagen wird. Die gemeldete Ausgabe-Latenz stimmt nicht überall, bei Bluetooth-Kopfhörern kann sie deutlich danebenliegen. Ältere Safari- und Firefox-Versionen melden gar keine (src/viewport/SoundLibrary.ts:2226). Ein Spiel kann so einen Kalibrierungs-Bildschirm bauen und den Wert in einer Variablen halten.
  5. Pause: Eine angehaltene Partie hält die Audio-Uhr. Drücke während der Pause lösen On key hit nicht aus.
  6. Exportiertes Spiel: src/player/ bekommt denselben Weg wie der Editor-Lauf.

Die Bewertung (Perfect, Good, Miss) bleibt im Graph, mit Compare auf offset. Der Node liefert nur die Messung.

Offene Fragen

  • Raster oder Chart? Gemessen wird gegen ein festes Raster. Ein Rhythmus-Spiel will oft gegen die Noten eines Patterns messen („Chart“), also nur dort, wo wirklich eine Note steht. Vorschlag: zuerst das Raster, die Chart als eigener Schritt, falls gebraucht.
  • Maus und Touch: Vorschlag: der Mausklick gleich mit, Touch später.
  • Drücke ohne Musik: Spielt nichts, kommt On key hit nicht. Alternativ käme es mit playing aus. Vorschlag: gar nicht.

Tests

  • Unit: Umrechnung von Zeitstempel über Audio-Uhr auf Song-Sekunden mit einer Fake-Uhr. Dazu Raster mit und ohne Swing, Kalibrierung und Pause.
  • e2e 65: Tastendruck mit bekanntem Zeitstempel zu einem Song mit bekanntem Tempo. Geprüft wird, dass offset auf wenige ms genau stimmt, auch wenn der Druck mitten in einen Frame fällt.
Übrig aus #330 („Was der DAW bewusst noch fehlt“). Im Wiki steht es unter *Songs → Not yet*: „it cannot follow the keys a player presses against the clock of the sound for a rhythm game“. ## Ausgangslage - **`Song position`** (#338) sagt, wo ein Song oder Pattern hörbar steht, die Ausgabe-Latenz ist abgezogen. Gelesen wird es aber im Frame, nicht im Moment des Tastendrucks. - **`Input`** (`src/runtime/systems/Input.ts`) merkt sich nur, *dass* eine Taste seit dem letzten Frame gedrückt wurde, nicht *wann*. Ein Druck ist damit nur auf einen Frame genau: 16,7 ms bei 60 Hz, bei einem Ruckler mehr. - Übliche Fenster in Rhythmus-Spielen sind ±20 bis 50 ms für einen sauberen Treffer. Ein Frame Fehler ist da schon ein Drittel bis fast das ganze Fenster. - Heute müsste man die Abweichung im Graph aus `fraction` von `Song position` zusammenbauen. Das hat den Frame-Fehler und ist umständlich. ## Vorschlag 1. **Zeitpunkt des Drucks mitschreiben:** `Input` hält zu jedem Druck `KeyboardEvent.timeStamp`. Die Laufzeit rechnet ihn auf die Audio-Uhr um, über `AudioContext.getOutputTimestamp()`, und daraus auf die Stelle im Song, wie `Song position` es tut. 2. **Neuer Event-Node *On key hit*** (Arbeitsname): Feld *Key*, dazu *Song*, *Pattern* und der Eingang `playback` wie bei *On beat*. Ausgänge: - `offset` in s zum nächsten Rasterpunkt, negativ = zu früh, - `bar` und `beat` des Rasterpunkts, - `seconds`, die Stelle im Song zum Zeitpunkt des Drucks. 3. **Raster** als Feld: Schlag, 1/8 oder 1/16. Bei einem Pattern mit Swing (#372) liegen die Rasterpunkte dort, wo der Swing die Noten hinschiebt. 4. **Kalibrierung:** ein Eingang `calibration` in s, der auf jeden Druck aufgeschlagen wird. Die gemeldete Ausgabe-Latenz stimmt nicht überall, bei Bluetooth-Kopfhörern kann sie deutlich danebenliegen. Ältere Safari- und Firefox-Versionen melden gar keine (`src/viewport/SoundLibrary.ts:2226`). Ein Spiel kann so einen Kalibrierungs-Bildschirm bauen und den Wert in einer Variablen halten. 5. **Pause:** Eine angehaltene Partie hält die Audio-Uhr. Drücke während der Pause lösen *On key hit* nicht aus. 6. **Exportiertes Spiel:** `src/player/` bekommt denselben Weg wie der Editor-Lauf. Die Bewertung (Perfect, Good, Miss) bleibt im Graph, mit *Compare* auf `offset`. Der Node liefert nur die Messung. ## Offene Fragen - **Raster oder Chart?** Gemessen wird gegen ein festes Raster. Ein Rhythmus-Spiel will oft gegen die Noten eines Patterns messen („Chart“), also nur dort, wo wirklich eine Note steht. Vorschlag: zuerst das Raster, die Chart als eigener Schritt, falls gebraucht. - **Maus und Touch:** Vorschlag: der Mausklick gleich mit, Touch später. - **Drücke ohne Musik:** Spielt nichts, kommt *On key hit* nicht. Alternativ käme es mit `playing` aus. Vorschlag: gar nicht. ## Tests - Unit: Umrechnung von Zeitstempel über Audio-Uhr auf Song-Sekunden mit einer Fake-Uhr. Dazu Raster mit und ohne Swing, Kalibrierung und Pause. - e2e 65: Tastendruck mit bekanntem Zeitstempel zu einem Song mit bekanntem Tempo. Geprüft wird, dass `offset` auf wenige ms genau stimmt, auch wenn der Druck mitten in einen Frame fällt.
admin added this to the Development project 2026-10-30 21:53:11 +01:00
admin moved this to Next in Development on 2026-10-30 21:53:12 +01:00
admin moved this to Done in Development on 2026-10-30 22:54:45 +01:00
Author
Owner

Erledigt mit 5c0d52d, Wiki d745d11 (Songs § Hitting it in time, Behaviors, Patterns). Gebaut nach den drei Vorschlägen: festes Raster, Mausklick gleich mit, ohne Musik kein Ereignis.

Neuer Event-Node On key hit

  • Einstellungen: Hit by (Key oder Click), Key (Standard Space), Grid (Beat, 1/8, 1/16), Song, Pattern.
  • Eingänge: playback, calibration (Sekunden, verschiebt den Druck zum Ausgleich der Latenz).
  • Ausgänge: playback, offset (negativ = zu früh), bar, beat, seconds.

Wie gemessen wird

  • Input und der Zeiger merken sich den Zeitpunkt jedes echten Drucks (KeyboardEvent.timeStamp). Auto-Wiederholung zählt nicht. Zwei Drücke im selben Frame werden getrennt gemessen.
  • Die SoundLibrary rechnet den Druck über getOutputTimestamp auf die gehörte Song-Stelle um, der Limiter wird abgezogen. Safari kennt die Methode nicht, dort gilt die Audio-Uhr minus Latenz, zurückgerechnet auf den Druck.
  • Bei einem Pattern mit Swing (#372) liegen die Rasterpunkte dort, wo der Swing die Noten hinschiebt.
  • Das exportierte Spiel braucht keinen eigenen Code, Input, Zeiger und SoundLibrary sind dieselben.

Festlegungen

  • Ein Druck, der beim Frame älter als 0,25 s ist, wird verworfen (z. B. während der Pause der Toolbar).
  • Genau in der Mitte zwischen zwei Punkten zählt der Druck als zu spät für den ersten.
  • Das Ende eines Patterns ohne Loop ist kein Rasterpunkt.
  • Die Bewertung (Perfect/Good/Miss) bleibt im Graph.

Tests

Prüfung Ergebnis
Unit, 23 neu grün
Gegenproben 23 Verbiegungen, alle rot
e2e 65 ALL PASSED, 79 Checks. Neu 65k7: H dreimal in einem Frame, 4,4 und 4,1 ms auseinander, die Offsets ebenso. Ohne Zeitstempel bekommen alle drei denselben Offset und nur 65k7 wird rot.
Gate grün bis auf eine nachgezogene Zählung in event-order.test.ts

Noch nicht: eine Chart (Noten eines Patterns statt festes Raster) und Touch. Dafür gibt es noch kein Issue.

Im selben Push: 9f8203a stellt die CI-Suite zurück auf feuerwerk-wsl, und der 400-Schritte-Test in tilemap-patch.test.ts hat eine Grenze von 30 s (rot in Gate 761, als Gate und Suite sich stein2 teilten).

Erledigt mit `5c0d52d`, Wiki `d745d11` (*Songs* § *Hitting it in time*, *Behaviors*, *Patterns*). Gebaut nach den drei Vorschlägen: festes Raster, Mausklick gleich mit, ohne Musik kein Ereignis. **Neuer Event-Node *On key hit*** - Einstellungen: *Hit by* (Key oder Click), *Key* (Standard Space), *Grid* (Beat, 1/8, 1/16), *Song*, *Pattern*. - Eingänge: `playback`, `calibration` (Sekunden, verschiebt den Druck zum Ausgleich der Latenz). - Ausgänge: `playback`, `offset` (negativ = zu früh), `bar`, `beat`, `seconds`. **Wie gemessen wird** - `Input` und der Zeiger merken sich den Zeitpunkt jedes echten Drucks (`KeyboardEvent.timeStamp`). Auto-Wiederholung zählt nicht. Zwei Drücke im selben Frame werden getrennt gemessen. - Die `SoundLibrary` rechnet den Druck über `getOutputTimestamp` auf die gehörte Song-Stelle um, der Limiter wird abgezogen. Safari kennt die Methode nicht, dort gilt die Audio-Uhr minus Latenz, zurückgerechnet auf den Druck. - Bei einem Pattern mit Swing (#372) liegen die Rasterpunkte dort, wo der Swing die Noten hinschiebt. - Das exportierte Spiel braucht keinen eigenen Code, `Input`, Zeiger und `SoundLibrary` sind dieselben. **Festlegungen** - Ein Druck, der beim Frame älter als 0,25 s ist, wird verworfen (z. B. während der Pause der Toolbar). - Genau in der Mitte zwischen zwei Punkten zählt der Druck als zu spät für den ersten. - Das Ende eines Patterns ohne Loop ist kein Rasterpunkt. - Die Bewertung (Perfect/Good/Miss) bleibt im Graph. **Tests** | Prüfung | Ergebnis | |---|---| | Unit, 23 neu | grün | | Gegenproben | 23 Verbiegungen, alle rot | | e2e 65 | ALL PASSED, 79 Checks. Neu 65k7: H dreimal in einem Frame, 4,4 und 4,1 ms auseinander, die Offsets ebenso. Ohne Zeitstempel bekommen alle drei denselben Offset und nur 65k7 wird rot. | | Gate | grün bis auf eine nachgezogene Zählung in `event-order.test.ts` | **Noch nicht:** eine Chart (Noten eines Patterns statt festes Raster) und Touch. Dafür gibt es noch kein Issue. Im selben Push: `9f8203a` stellt die CI-Suite zurück auf `feuerwerk-wsl`, und der 400-Schritte-Test in `tilemap-patch.test.ts` hat eine Grenze von 30 s (rot in Gate 761, als Gate und Suite sich stein2 teilten).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: meleki/feuerwerk#438