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.
78 lines
3.0 KiB
C++
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;
|
|
}
|