pauseChannel, resumeChannel i stopChannel repetien tres vegades la
cadena "if channel == -1 forEach(channels) else if 0<=channel<MAX
apply()". S'extreu a forEachTargetChannel(channel, fn) i cada funció es
queda només amb la lògica per canal — pausar, reprendre, destruir.
Substitueix codi gairebé idèntic triplicat per una declaració de per
què fa el que fa cada funció.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fadeOutMusic i crossfadeMusic repetien el bloc "destruir outgoing
anterior + preFillOutgoing + robar stream + reset state+vorbis". Es
treu a stealCurrentIntoOutgoing(duration_ms) i els dos callers deleguen.
Com a efecte secundari, homogeneïtza la neteja: abans fadeOutMusic no
posava outgoing.fade.active = false en destruir l'anterior i
crossfadeMusic sí. L'helper fa sempre les dues coses. El comportament
observable no canvia perquè la fade s'assigna completament just després,
però elimina una inconsistència latent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
updateOutgoingFade i updateIncomingFade calculaven el mateix PERCENT
lineal (ELAPSED / duration_ms) per separat i prenien decisions sobre si
el fade havia acabat comparant bytes. Ara fadeProgress(FadeState)
retorna [0..1] i concentra la corba del fade — si cal passar-la a
logarítmica o quadràtica, es toca en un sol lloc.
També protegeix duration_ms<=0 com a "fade completat" (abans el divisor
donava una divisió per zero en debug o un progrés >> 1 en release).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
setSoundVolume i setMusicVolume repetien la mateixa lògica de clamp +
gate (enabled_ && channel_enabled_) + multiplicació pel master
config_.volume. S'extreu effectiveVolume(volume, channel_enabled) privat
i els dos setters es limiten a la crida Ja::*.
Any canvi futur a la política (p.ex. una corba logarítmica) queda en un
sol lloc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Els dos playMusic compartien el dispatch crossfade-vs-stop+play i
l'actualització de music_.state/loop. S'extreu playMusicInternal privat
que rep el Ja::Music* ja resolt i fa el camí comú.
Cada overload es queda només amb el seu gating propi: la variant per
nom fa early-return si ja sona la mateixa pista i posa music_.name; la
variant per punter buida el nom (PATCH-02 recuperarà el filename del
propi Ja::Music).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Abans, toggleEnabled/toggleSound/toggleMusic i les variants sense bool
només flipaven el flag intern; els canals actius no notaven el canvi
fins a la pròxima crida explícita a setSoundVolume/setMusicVolume —
silenciós i fàcil de perdre.
Ara:
- toggleEnabled passa per enable(bool), que ja reaplicava els volums.
- enableSound(bool) i enableMusic(bool) es mouen a audio.cpp i
reapliquen el volum gated cap a Ja::*.
- Les variants enableSound()/disableSound()/enableMusic()/disableMusic()
i els toggles passen per aquestes dues funcions, així que qualsevol
camí d'activació produeix el mateix efecte sobre l'estat sonor.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sound_volume[] és un inline float[] amb zero-init per defecte i la capa
Audio crida setSoundVolume(config_.sound_volume) via enable() just
després de Ja::init, així que el fill(0.5F) no arribava mai a l'usuari.
Era un valor màgic heretat.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MUSIC_CHUNK_SHORTS i MUSIC_LOW_WATER_SECONDS són paràmetres del streaming
de vorbis/SDL; viuen dins de jail_audio i moure'ls a Defaults acoplaria
el motor al projecte. Mateix criteri que HC-03.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ja::MAX_SIMULTANEOUS_CHANNELS i Ja::MAX_GROUPS són límits interns del
motor. La proposta inicial era moure'ls a Defaults::Audio, però jail_audio
és reutilitzable entre projectes (no depèn d'Options ni d'utils/); moure
els límits trencaria aquesta independència.
El valor real del hallazgo era la invariant Audio::Group ↔ MAX_GROUPS,
que cap codi protegia. S'afegeix un static_assert a audio.cpp que falla
en compilació si s'afegeix una entrada nova a Group sense tocar
MAX_GROUPS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
{SDL_AUDIO_S16, 2, 48000} apareixia com a literal a tres llocs de
jail_audio (Sound/Music/audio_spec) i audio.cpp:208 passava SDL_AUDIO_S16LE
i 2 canals per separat — dues convencions de format conviuen i els 2
canals eren número màgic.
Defaults::Audio guanya FORMAT (SDL_AUDIO_S16) i CHANNELS (2). Audio::
initSDLAudio passa ambdós junts a Ja::init. jail_audio obté un fallback
local Ja::DEFAULT_SPEC que consolida les tres instàncies; es queda
independent del projecte (no inclou utils/defaults.hpp) perquè l'engine
és reutilitzable.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
VOLUME_STEP, FREQUENCY i DEFAULT_CROSSFADE_MS vivien com a membres
constexpr d'Audio. Eren valors de configuració — no invariants de la
classe — i la convenció del projecte és que tots els paràmetres
ajustables visquin a utils/defaults.hpp.
Passen a Defaults::Audio::{VOLUME_STEP, FREQUENCY, CROSSFADE_MS}. L'únic
ús intern (Ja::init al constructor d'Audio) es reapunta al Defaults. Cap
ús extern dels tres símbols.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Plantilla d'auditoria per a core/audio/ amb els IDs estables (DUP/HC/
PATCH/COUP/INCONS/DEAD/CONV) i el top-5 prioritari. Mateix format que
l'auditoria de core/rendering/; les resolucions s'aniran anotant a
mesura que s'apliquin els canvis, commit a commit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>