validate that intensity layers have matching lengths

The three IntensityN streams of a DynamicSound play in parallel, and the
player keys end-of-song timing off Intensity1's length — mismatched lengths
cause silent gaps in the higher intensity layers and broken advancement.

DynamicSound:
- Setters on Intensity1/2/3 push_warning when assigning a stream whose length
  doesn't match the others (editor-time feedback).
- Public has_matching_layer_lengths() -> bool for callers wanting to validate
  songs themselves.

DynamicSoundPlayerCore:
- _start_song_at_index pushes a runtime error if the song fails the check
  before queuing its layers on the polyphonic playback.

The FX core deliberately doesn't add a runtime check — play_fx fires per
trigger and would spam, while the editor warning already covers authoring
mistakes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Daniel Samson
2026-04-29 16:36:50 +01:00
co-authored by Claude Opus 4.7
parent 60047069fd
commit 59f1d93c6f
3 changed files with 61 additions and 10 deletions
+7 -3
View File
@@ -28,13 +28,17 @@ A ready-made layout containing both buses (each routing into `Master`) ships wit
A single piece of audio with up to three intensity layers that play simultaneously and are blended at runtime.
> **All assigned layers must have identical lengths.** The three streams play in parallel; if `Intensity2` ends a beat earlier than `Intensity1`, the blend becomes silence on that layer for the remainder of the song and the player's end-of-song timing (which keys off `Intensity1.get_length()`) misfires. The resource pushes a warning in the editor when you assign mismatched-length streams, and `DynamicSoundPlayer` pushes a runtime error when it tries to play one. Trim or pad your stems to match before exporting.
| Property | Type | Description |
| --- | --- | --- |
| `Intensity1` | `AudioStream` | Least-intense layer (e.g. ambient). Required for length tracking. |
| `Intensity2` | `AudioStream` | Mid-intensity layer. Optional. |
| `Intensity3` | `AudioStream` | Most-intense layer. Optional. |
| `Intensity1` | `AudioStream` | Least-intense layer (e.g. ambient). Required — the player uses its length to time song advancement. |
| `Intensity2` | `AudioStream` | Mid-intensity layer. Optional. Must match `Intensity1`'s length if assigned. |
| `Intensity3` | `AudioStream` | Most-intense layer. Optional. Must match `Intensity1`'s length if assigned. |
| `ReverbTail` | `float` | Length (seconds) of the song's audio that is pure reverb tail. The next song is started this far ahead of the current song's full length, so the new opening covers the outgoing tail without a perceptible gap. Set to `0` to disable overlap (hard cut). |
The resource exposes `has_matching_layer_lengths() -> bool` if you want to validate songs yourself (e.g. on load, in tests).
### `DynamicSoundPlaylist` (Resource)
An ordered list of `DynamicSound`s.