Files
aee-arcade/source/core/audio/sound_effects_config.cpp
T
JailDesigner d3698a1504 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.
2026-04-27 08:16:16 +02:00

78 lines
3.0 KiB
C++

#include "core/audio/sound_effects_config.hpp"
#include <iostream>
#include <string>
#include "core/resources/resource_helper.hpp"
#include "external/fkyaml_node.hpp"
namespace {
// Lector de camp amb fallback: deixa el destí intacte si la clau no
// existeix (els defaults dels Ja::*Params s'inicialitzen al ctor del
// struct, així que el comportament és "preset parcial = preset complet
// amb defaults per als camps que falten").
template <typename T>
void readField(const fkyaml::node& node, const char* key, T& dst) {
if (node.contains(key)) { dst = node[key].get_value<T>(); }
}
} // namespace
auto SoundEffectsConfig::get() -> SoundEffectsConfig& {
static SoundEffectsConfig instance_;
return instance_;
}
void SoundEffectsConfig::load(const std::string& file_path) {
auto bytes = Resource::Helper::loadFile(file_path);
if (bytes.empty()) {
std::cerr << "SoundEffectsConfig: no s'ha pogut obrir " << file_path << " — cap preset d'efecte disponible\n";
return;
}
try {
const auto* begin = reinterpret_cast<const char*>(bytes.data());
const auto* end = begin + bytes.size();
auto yaml = fkyaml::node::deserialize(begin, end);
if (yaml.contains("echo") && yaml["echo"].is_mapping()) {
for (auto it = yaml["echo"].begin(); it != yaml["echo"].end(); ++it) {
const auto NAME = it.key().get_value<std::string>();
const auto& node = it.value();
Ja::EchoParams params{};
readField(node, "delay_ms", params.delay_ms);
readField(node, "feedback", params.feedback);
readField(node, "wet", params.wet);
echoes_[NAME] = params;
}
}
if (yaml.contains("reverb") && yaml["reverb"].is_mapping()) {
for (auto it = yaml["reverb"].begin(); it != yaml["reverb"].end(); ++it) {
const auto NAME = it.key().get_value<std::string>();
const auto& node = it.value();
Ja::ReverbParams params{};
readField(node, "room_size", params.room_size);
readField(node, "damping", params.damping);
readField(node, "wet", params.wet);
reverbs_[NAME] = params;
}
}
std::cout << "SoundEffectsConfig: " << echoes_.size() << " preset(s) d'eco i " << reverbs_.size() << " de reverb des de " << file_path << '\n';
} catch (const std::exception& e) {
std::cerr << "SoundEffectsConfig: error parsejant " << file_path << ": " << e.what() << '\n';
}
}
auto SoundEffectsConfig::findEcho(const std::string& name) const -> const Ja::EchoParams* {
const auto IT = echoes_.find(name);
return (IT == echoes_.end()) ? nullptr : &IT->second;
}
auto SoundEffectsConfig::findReverb(const std::string& name) const -> const Ja::ReverbParams* {
const auto IT = reverbs_.find(name);
return (IT == reverbs_.end()) ? nullptr : &IT->second;
}