feat(audio): suport d'efectes (eco i reverb) per a sons puntuals
Afig una capa d'efectes oberta a la unitat d'àudio. Els sons puntuals
es poden reproduir processats per un eco o un reverb (Freeverb 8C/4AP)
sense afectar la música ni canviar el camí ràpid existent (playSound
sense efecte segueix sent una sola crida sense cost extra).
- data/sounds.yaml: catàleg de presets nominats (echo: cave/short,
reverb: stone_hall/small_room).
- core/audio/audio_effects: processadors purs que prenen un Ja::Sound
(U8/S16, mono o estèreo) i tornen PCM S16 amb la cua de l'efecte
(cap a 800 ms eco, 1500 ms reverb).
- core/audio/sound_effects_config: Meyers singleton que parseja
sounds.yaml a EchoParams/ReverbParams indexats per nom; l'API
pública d'Audio fa el lookup per nom.
- jail_audio::Engine: nou camí playSoundWith{Echo,Reverb} que empenta
el buffer ja processat a un canal lliure marcat com a has_effect.
El cap MAX_EFFECT_CHANNELS limita a 4 sons amb efecte simultanis;
si està al límit, l'engine retorna -1 i Audio cau a la versió seca.
- Audio::playSoundWith{Echo,Reverb}(name, preset, group) com a única
via pública. Si el preset no existeix, fallback dry amb avís.
- director: load("data/sounds.yaml") abans d'Audio::init.
This commit is contained in:
1 parent
42062dca55
commit
d3698a1504
11 files changed
+593
-5
No files matched your search
@@ -4,6 +4,10 @@
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include "core/audio/audio_effects.hpp"
|
||||
|
||||
// Només declaracions de stb_vorbis: STB_VORBIS_HEADER_ONLY omet el bloc
|
||||
// d'implementació. Les definicions les aporta source/external/stb_vorbis_impl.cpp
|
||||
@@ -397,13 +401,17 @@ namespace Ja {
|
||||
}
|
||||
|
||||
void Engine::stopChannel(const int channel) {
|
||||
forEachTargetChannel(channel, [](Channel& ch) {
|
||||
forEachTargetChannel(channel, [this](Channel& ch) {
|
||||
if (ch.state != ChannelState::FREE) {
|
||||
if (ch.stream != nullptr) { SDL_DestroyAudioStream(ch.stream); }
|
||||
ch.stream = nullptr;
|
||||
ch.state = ChannelState::FREE;
|
||||
ch.pos = 0;
|
||||
ch.sound = nullptr;
|
||||
if (ch.has_effect) {
|
||||
ch.has_effect = false;
|
||||
if (effect_channels_active_ > 0) { --effect_channels_active_; }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -438,6 +446,55 @@ namespace Ja {
|
||||
}
|
||||
}
|
||||
|
||||
auto Engine::playProcessedOnFreeChannel(const std::vector<std::uint8_t>& bytes, const SDL_AudioSpec& spec, const int group) -> int {
|
||||
// El cap de canals amb efecte es valida abans de reservar slot —
|
||||
// així evitem crear i destruir un stream només per descartar el play.
|
||||
if (effect_channels_active_ >= MAX_EFFECT_CHANNELS) { return -1; }
|
||||
|
||||
int channel = 0;
|
||||
while (channel < MAX_SIMULTANEOUS_CHANNELS && channels_[channel].state != ChannelState::FREE) { ++channel; }
|
||||
if (channel == MAX_SIMULTANEOUS_CHANNELS) { channel = 0; }
|
||||
|
||||
stopChannel(channel);
|
||||
|
||||
// El stream es crea contra l'spec del buffer processat (S16, ...)
|
||||
// perquè SDL faci el resampling cap a audio_spec_ del device.
|
||||
channels_[channel].stream = SDL_CreateAudioStream(&spec, &audio_spec_);
|
||||
if (channels_[channel].stream == nullptr) {
|
||||
std::fprintf(stderr, "Ja::Engine::playProcessedOnFreeChannel: Failed to create audio stream!\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
channels_[channel].sound = nullptr; // El buffer no és propietat de cap Ja::Sound.
|
||||
channels_[channel].times = 0;
|
||||
channels_[channel].pos = 0;
|
||||
const int CLAMPED_GROUP = (group >= 0 && group < MAX_GROUPS) ? group : 0;
|
||||
channels_[channel].group = CLAMPED_GROUP;
|
||||
channels_[channel].state = ChannelState::PLAYING;
|
||||
channels_[channel].has_effect = true;
|
||||
++effect_channels_active_;
|
||||
|
||||
SDL_PutAudioStreamData(channels_[channel].stream, bytes.data(), static_cast<int>(bytes.size()));
|
||||
SDL_SetAudioStreamGain(channels_[channel].stream, sound_volume_[CLAMPED_GROUP]);
|
||||
SDL_BindAudioStream(sdl_audio_device_, channels_[channel].stream);
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
auto Engine::playSoundWithEcho(const Sound* sound, const EchoParams& params, const int group) -> int {
|
||||
if (sound == nullptr) { return -1; }
|
||||
auto processed = AudioEffects::applyEcho(*sound, params);
|
||||
if (!processed) { return -1; }
|
||||
return playProcessedOnFreeChannel(processed->bytes, processed->spec, group);
|
||||
}
|
||||
|
||||
auto Engine::playSoundWithReverb(const Sound* sound, const ReverbParams& params, const int group) -> int {
|
||||
if (sound == nullptr) { return -1; }
|
||||
auto processed = AudioEffects::applyReverb(*sound, params);
|
||||
if (!processed) { return -1; }
|
||||
return playProcessedOnFreeChannel(processed->bytes, processed->spec, group);
|
||||
}
|
||||
|
||||
// --- Factories i destructors (permanents) ---
|
||||
|
||||
auto loadMusic(const Uint8* buffer, Uint32 length) -> Music* {
|
||||
|
||||
Reference in new issue
Block a user