Commit Graph

8 Commits

Author SHA1 Message Date
Daniel Samson 5758106b1e
gate plugin on Godot 4.6+; drop stale gd-options autoload registrations
plugin.gd now checks Engine.get_version_info() in _enter_tree and pushes a
loud error if the engine is older than 4.6. Doesn't actually disable the
addon's class_names (Godot parses those independently of EditorPlugin), but
gives users a clear "this is the cause" signal before any cryptic downstream
errors land.

Also removes four add_autoload_singleton calls for gd-options-owned scripts
(GameSettings, InputSettings, GraphicsSettings, AudioSettings). They were
copy-paste leftovers from when this addon was forked; gd-options/plugin.gd
already registers them, so this addon was double-registering — gd-dynamic-
sound has no business owning those autoloads.

README documents the 4.6 requirement near the top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:45:43 +01:00
Daniel Samson 59f1d93c6f
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>
2026-04-29 16:36:50 +01:00
Daniel Samson df3e73b32e
add SkipMode enum (UseReverbTail / CrossFade / Jump)
advance_to_next_song / advance_to_track now branch on the new SkipMode export:

- UseReverbTail (default): existing behavior. Accelerate the outgoing song so
  remaining time hits ReverbTail, both songs play during the overlap, outgoing
  stops naturally.
- CrossFade: programmatic volume crossfade. Outgoing fades from current volume
  to silent over CrossFadeDuration seconds (then stops), incoming starts silent
  and fades up to full over the same duration. Independent of any global fade.
- Jump: hard cut. Outgoing song's streams are stopped immediately and removed
  from the active list before the incoming starts.

For CrossFade we needed per-song fade state (the global _fade_db can't drive
two songs in opposite directions at once), so _ActiveSong gained fade_db /
fade_from_db / fade_to_db / fade_duration / fade_elapsed / fading /
stop_after_fade fields. _apply_volumes_to_song now adds both the global and
per-song fade_db to volume_db; process() advances each song's fade per-frame
and stops songs whose fades reach the silent target with stop_after_fade set.

CrossFadeDuration is hidden in the inspector via _validate_property unless
SkipMode == CrossFade. The natural end-of-song advance is unchanged — it still
uses the song's reverb tail. SkipMode only affects programmatic skips.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:31:37 +01:00
Daniel Samson c989fc4eea
implement reverb tail, LoopMode, and playlist navigation
DynamicSound.ReverbTail now drives a seamless transition: when the current
song is within ReverbTail seconds of its full length, the next song is started
so its opening covers the outgoing tail. Both songs are audible during the
overlap; the outgoing one stops when its full length has elapsed.

API changes (continuous players only):
- Replaces `Loop : bool` with `LoopMode : enum { Single, All }`. Single keeps
  repeating the current song; All advances through the playlist and wraps
  back to index 0. There is no longer a "play once and stop" mode.
- The playlist resource is no longer mutated. The player tracks an internal
  index instead of popping songs off the front.
- Adds `get_current_song_index() -> int`, `advance_to_next_song()`, and
  `advance_to_track(index: int)` on each continuous wrapper. Both advance
  methods accelerate the outgoing song so its remaining time hits ReverbTail
  immediately, giving a smooth transition rather than a hard cut.

Internals: DynamicSoundPlayerCore now keeps an `_active_songs` array of an
inner _ActiveSong record (ids, start_time, song_length, reverb_tail,
started_next). _process advances time globally and runs the start-next /
stop-old conditions per active song, mirroring the OvaniPlayer reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 16:07:06 +01:00
Daniel Samson e8224e3913
respect autoplay; add play_playlist() for manual start
DynamicSoundPlayerCore.ready() always called play() if a playlist had songs,
ignoring the inherited AudioStreamPlayer.autoplay property. Now early-returns
when autoplay is false. Adds play_playlist() (exposed on all three continuous
wrappers) so users with autoplay=false have a non-fade way to start playback.
fade_in() now uses the same _start_playback() helper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:29:08 +01:00
Daniel Samson 36350ab8d5
ship a sample bus layout with Music and FX
Adds default_bus_layout.tres at the addon root containing a Music bus and an
FX bus, each routing into Master. Users can point Project Settings → Audio →
Default Bus Layout at this file (or copy it to their project root) to get the
buses the players auto-route to in the editor. README documents the wiring.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:09:10 +01:00
Daniel Samson dd805a4cd9
move PitchMode to FX players and implement random pitch
The continuous music players had PitchMode/RandomMin/MaxPitchScale exports but
no logic referenced them. Pitch variation only makes sense per-trigger, which
the FX family supports natively. So:

- Remove PitchMode, RandomMinPitchScale, RandomMaxPitchScale, and the
  _validate_property hook from DynamicSoundPlayer / 2D / 3D.
- Add the same exports + _validate_property to DynamicSoundFXPlayer / 2D / 3D.
- Implement pitch resolution in DynamicSoundFXPlayerCore.play_fx: each trigger
  samples a fresh pitch from PitchMode and passes it as the pitch_scale
  argument to AudioStreamPlaybackPolyphonic.play_stream.
- Random pitch uses 1.0 + randf_range(min, max) so the existing -0.5/0.5
  defaults give a useful [0.5, 1.5] pitch range.
- README updated to reflect the move.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 15:03:03 +01:00
Daniel Samson cb47d89377
Initial commit: dynamic music & FX players
Continuous music players (DynamicSoundPlayer, 2D, 3D) with intensity-blended
layers, playlist queue, Loop, fade in/out, pause/resume. One-shot SFX players
(DynamicSoundFXPlayer, 2D, 3D) for overlapping triggers. All variants share
RefCounted core classes via duck-typed property access.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-29 14:46:48 +01:00