diff options
Diffstat (limited to 'src/emu/audio_effects')
-rw-r--r-- | src/emu/audio_effects/aeffect.cpp | 46 | ||||
-rw-r--r-- | src/emu/audio_effects/aeffect.h | 43 | ||||
-rw-r--r-- | src/emu/audio_effects/compressor.cpp | 28 | ||||
-rw-r--r-- | src/emu/audio_effects/compressor.h | 24 | ||||
-rw-r--r-- | src/emu/audio_effects/eq.cpp | 331 | ||||
-rw-r--r-- | src/emu/audio_effects/eq.h | 84 | ||||
-rw-r--r-- | src/emu/audio_effects/filter.cpp | 259 | ||||
-rw-r--r-- | src/emu/audio_effects/filter.h | 78 | ||||
-rw-r--r-- | src/emu/audio_effects/reverb.cpp | 28 | ||||
-rw-r--r-- | src/emu/audio_effects/reverb.h | 24 |
10 files changed, 945 insertions, 0 deletions
diff --git a/src/emu/audio_effects/aeffect.cpp b/src/emu/audio_effects/aeffect.cpp new file mode 100644 index 00000000000..dcff278099f --- /dev/null +++ b/src/emu/audio_effects/aeffect.cpp @@ -0,0 +1,46 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "aeffect.h" +#include "filter.h" +#include "compressor.h" +#include "reverb.h" +#include "eq.h" + +const char *const audio_effect::effect_names[COUNT] = { + "Filters", + "Compressor", + "Reverb", + "Equalizer" +}; + +audio_effect *audio_effect::create(int type, u32 sample_rate, audio_effect *def) +{ + switch(type) { + case FILTER: return new audio_effect_filter (sample_rate, def); + case COMPRESSOR: return new audio_effect_compressor(sample_rate, def); + case REVERB: return new audio_effect_reverb (sample_rate, def); + case EQ: return new audio_effect_eq (sample_rate, def); + } + return nullptr; +} + + +void audio_effect::copy(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) const +{ + u32 samples = src.available_samples(); + dest.prepare_space(samples); + u32 channels = src.channels(); + for(u32 channel = 0; channel != channels; channel++) { + const sample_t *srcd = src.ptrs(channel, 0); + sample_t *destd = dest.ptrw(channel, 0); + std::copy(srcd, srcd + samples, destd); + } + dest.commit(samples); +} + +u32 audio_effect::history_size() const +{ + return 0; +} diff --git a/src/emu/audio_effects/aeffect.h b/src/emu/audio_effects/aeffect.h new file mode 100644 index 00000000000..72e6279f1e6 --- /dev/null +++ b/src/emu/audio_effects/aeffect.h @@ -0,0 +1,43 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_AEFFECT_H +#define MAME_EMU_AUDIO_EFFECTS_AEFFECT_H + +class audio_effect +{ +public: + using sample_t = sound_stream::sample_t; + + enum { + FILTER, + COMPRESSOR, + REVERB, + EQ, + COUNT + }; + + static const char *const effect_names[COUNT]; + + static audio_effect *create(int type, u32 sample_rate, audio_effect *def = nullptr); + + audio_effect(u32 sample_rate, audio_effect *def) : m_default(def), m_sample_rate(sample_rate) {} + virtual ~audio_effect() = default; + + void copy(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) const; + + virtual int type() const = 0; + virtual u32 history_size() const; + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) = 0; + virtual void config_load(util::xml::data_node const *ef_node) = 0; + virtual void config_save(util::xml::data_node *ef_node) const = 0; + virtual void default_changed() = 0; + +protected: + audio_effect *m_default; + u32 m_sample_rate; +}; + +#endif diff --git a/src/emu/audio_effects/compressor.cpp b/src/emu/audio_effects/compressor.cpp new file mode 100644 index 00000000000..06c054a4837 --- /dev/null +++ b/src/emu/audio_effects/compressor.cpp @@ -0,0 +1,28 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "compressor.h" +#include "xmlfile.h" + +audio_effect_compressor::audio_effect_compressor(u32 sample_rate, audio_effect *def) : audio_effect(sample_rate, def) +{ +} + + +void audio_effect_compressor::config_load(util::xml::data_node const *ef_node) +{ +} + +void audio_effect_compressor::config_save(util::xml::data_node *ef_node) const +{ +} + +void audio_effect_compressor::default_changed() +{ +} + +void audio_effect_compressor::apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) +{ + copy(src, dest); +} diff --git a/src/emu/audio_effects/compressor.h b/src/emu/audio_effects/compressor.h new file mode 100644 index 00000000000..551212f34e5 --- /dev/null +++ b/src/emu/audio_effects/compressor.h @@ -0,0 +1,24 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_COMPRESSOR_H +#define MAME_EMU_AUDIO_EFFECTS_COMPRESSOR_H + +#include "aeffect.h" + +class audio_effect_compressor : public audio_effect +{ +public: + audio_effect_compressor(u32 sample_rate, audio_effect *def); + virtual ~audio_effect_compressor() = default; + + virtual int type() const override { return COMPRESSOR; } + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override; + virtual void config_load(util::xml::data_node const *ef_node) override; + virtual void config_save(util::xml::data_node *ef_node) const override; + virtual void default_changed() override; +}; + +#endif diff --git a/src/emu/audio_effects/eq.cpp b/src/emu/audio_effects/eq.cpp new file mode 100644 index 00000000000..799b2f8c9b5 --- /dev/null +++ b/src/emu/audio_effects/eq.cpp @@ -0,0 +1,331 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "eq.h" +#include "xmlfile.h" + +// This effect implements a parametric EQ using peak and shelf filters + +// Formulas taken from (with some fixes): + +// [Zölzer 2011] "DAFX: Digital Audio Effects", Udo Zölzer, Second Edition, Wiley publishing, 2011 (Tables 2.3 and 2.4) +// [Zölzer 2008] "Digital Audio Signal Processing", Udo Zölzer, Second Edition, Wiley publishing, 2008 (Tables 5.3, 5.4 and 5.5) + +audio_effect_eq::audio_effect_eq(u32 sample_rate, audio_effect *def) : audio_effect(sample_rate, def) +{ + // Minimal init to avoid using uninitialized values when reset_* + // recomputes filters + + for(u32 band = 0; band != BANDS; band++) { + m_q[band] = 0.7; + m_f[band] = 1000; + m_db[band] = 0; + } + + reset_mode(); + reset_low_shelf(); + reset_high_shelf(); + for(u32 band = 0; band != BANDS; band++) { + reset_q(band); + reset_f(band); + reset_db(band); + } +} + +void audio_effect_eq::reset_mode() +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_mode = false; + m_mode = d ? d->mode() : 1; +} + +void audio_effect_eq::reset_q(u32 band) +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_q[band] = false; + m_q[band] = d ? d->q(band) : 0.7; + build_filter(band); +} + +void audio_effect_eq::reset_f(u32 band) +{ + static const u32 defs[BANDS] = { 80, 200, 500, 3200, 8000 }; + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_f[band] = false; + m_f[band] = d ? d->f(band) : defs[band]; + build_filter(band); +} + +void audio_effect_eq::reset_db(u32 band) +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_db[band] = false; + m_db[band] = d ? d->db(band) : 0; + build_filter(band); +} + +void audio_effect_eq::reset_low_shelf() +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_low_shelf = false; + m_low_shelf = d ? d->low_shelf() : true; + build_filter(0); +} + +void audio_effect_eq::reset_high_shelf() +{ + audio_effect_eq *d = static_cast<audio_effect_eq *>(m_default); + m_isset_high_shelf = false; + m_high_shelf = d ? d->high_shelf() : true; + build_filter(BANDS-1); +} + +void audio_effect_eq::config_load(util::xml::data_node const *ef_node) +{ + if(ef_node->has_attribute("mode")) { + m_mode = ef_node->get_attribute_int("mode", 0); + m_isset_mode = true; + } else + reset_mode(); + + if(ef_node->has_attribute("low_shelf")) { + m_low_shelf = ef_node->get_attribute_int("low_shelf", 0); + m_isset_low_shelf = true; + } else + reset_low_shelf(); + + if(ef_node->has_attribute("high_shelf")) { + m_high_shelf = ef_node->get_attribute_int("high_shelf", 0); + m_isset_high_shelf = true; + } else + reset_high_shelf(); + + for(u32 band = 0; band != BANDS; band++) { + if(ef_node->has_attribute(util::string_format("q%d", band+1).c_str())) { + m_q[band] = ef_node->get_attribute_float(util::string_format("q%d", band+1).c_str(), 0); + m_isset_q[band] = true; + } else + reset_q(band); + + if(ef_node->has_attribute(util::string_format("f%d", band+1).c_str())) { + m_f[band] = ef_node->get_attribute_float(util::string_format("f%d", band+1).c_str(), 0); + m_isset_f[band] = true; + } else + reset_f(band); + + if(ef_node->has_attribute(util::string_format("db%d", band+1).c_str())) { + m_db[band] = ef_node->get_attribute_float(util::string_format("db%d", band+1).c_str(), 0); + m_isset_db[band] = true; + } else + reset_db(band); + } +} + +void audio_effect_eq::config_save(util::xml::data_node *ef_node) const +{ + if(m_isset_mode) + ef_node->set_attribute_int("mode", m_mode); + if(m_isset_low_shelf) + ef_node->set_attribute_int("low_shelf", m_low_shelf); + if(m_isset_high_shelf) + ef_node->set_attribute_int("high_shelf", m_high_shelf); + for(u32 band = 0; band != BANDS; band++) { + if(m_isset_q[band]) + ef_node->set_attribute_float(util::string_format("q%d", band+1).c_str(), m_q[band]); + if(m_isset_f[band]) + ef_node->set_attribute_float(util::string_format("f%d", band+1).c_str(), m_f[band]); + if(m_isset_db[band]) + ef_node->set_attribute_float(util::string_format("db%d", band+1).c_str(), m_db[band]); + } +} + +void audio_effect_eq::default_changed() +{ + if(!m_default) + return; + if(!m_isset_mode) + reset_mode(); + if(!m_isset_low_shelf) + reset_low_shelf(); + if(!m_isset_high_shelf) + reset_high_shelf(); + for(u32 band = 0; band != BANDS; band++) { + if(!m_isset_q[band]) + reset_q(band); + if(!m_isset_f[band]) + reset_f(band); + if(!m_isset_db[band]) + reset_db(band); + } +} + +void audio_effect_eq::set_mode(u32 mode) +{ + m_isset_mode = true; + m_mode = mode; +} + +void audio_effect_eq::set_q(u32 band, float q) +{ + m_isset_q[band] = true; + m_q[band] = q; + build_filter(band); +} + +void audio_effect_eq::set_f(u32 band, float f) +{ + m_isset_f[band] = true; + m_f[band] = f; + build_filter(band); +} + +void audio_effect_eq::set_db(u32 band, float db) +{ + m_isset_db[band] = true; + m_db[band] = db; + build_filter(band); +} + +void audio_effect_eq::set_low_shelf(bool active) +{ + m_isset_low_shelf = true; + m_low_shelf = active; + build_filter(0); +} + +void audio_effect_eq::set_high_shelf(bool active) +{ + m_isset_high_shelf = true; + m_high_shelf = active; + build_filter(BANDS-1); +} + +void audio_effect_eq::build_filter(u32 band) +{ + if(band == 0 && m_low_shelf) { + build_low_shelf(band); + return; + } + if(band == BANDS-1 && m_high_shelf) { + build_high_shelf(band); + return; + } + build_peak(band); +} + +void audio_effect_eq::build_low_shelf(u32 band) +{ + auto &fi = m_filter[band]; + if(m_db[band] == 0) { + fi.clear(); + return; + } + + float V = pow(10, abs(m_db[band])/20); + float K = tan(M_PI*m_f[band]/m_sample_rate); + float K2 = K*K; + + if(m_db[band] > 0) { + float d = 1 + sqrt(2)*K + K2; + fi.m_b0 = (1 + sqrt(2*V)*K + V*K2)/d; + fi.m_b1 = 2*(V*K2-1)/d; + fi.m_b2 = (1 - sqrt(2*V)*K + V*K2)/d; + fi.m_a1 = 2*(K2-1)/d; + fi.m_a2 = (1 - sqrt(2)*K + K2)/d; + } else { + float d = 1 + sqrt(2*V)*K + V*K2; + fi.m_b0 = (1 + sqrt(2)*K + K2)/d; + fi.m_b1 = 2*(K2-1)/d; + fi.m_b2 = (1 - sqrt(2)*K + K2)/d; + fi.m_a1 = 2*(V*K2-1)/d; + fi.m_a2 = (1 - sqrt(2*V)*K + V*K2)/d; + } +} + +void audio_effect_eq::build_high_shelf(u32 band) +{ + auto &fi = m_filter[band]; + if(m_db[band] == 0) { + fi.clear(); + return; + } + + float V = pow(10, m_db[band]/20); + float K = tan(M_PI*m_f[band]/m_sample_rate); + float K2 = K*K; + + if(m_db[band] > 0) { + float d = 1 + sqrt(2)*K + K2; + fi.m_b0 = (V + sqrt(2*V)*K + K2)/d; + fi.m_b1 = 2*(K2-V)/d; + fi.m_b2 = (V - sqrt(2*V)*K + K2)/d; + fi.m_a1 = 2*(K2-1)/d; + fi.m_a2 = (1 - sqrt(2)*K + K2)/d; + } else { + float d = 1 + sqrt(2*V)*K + V*K2; + fi.m_b0 = V*(1 + sqrt(2)*K + K2)/d; + fi.m_b1 = 2*V*(K2-1)/d; + fi.m_b2 = V*(1 - sqrt(2)*K + K2)/d; + fi.m_a1 = 2*(V*K2-1)/d; + fi.m_a2 = (1 - sqrt(2*V)*K + V*K2)/d; + } +} + +void audio_effect_eq::build_peak(u32 band) +{ + auto &fi = m_filter[band]; + if(m_db[band] == 0) { + fi.clear(); + return; + } + + float V = pow(10, m_db[band]/20); + float K = tan(M_PI*m_f[band]/m_sample_rate); + float K2 = K*K; + float Q = m_q[band]; + + if(m_db[band] > 0) { + float d = 1 + K/Q + K2; + fi.m_b0 = (1 + V*K/Q + K2)/d; + fi.m_b1 = 2*(K2-1)/d; + fi.m_b2 = (1 - V*K/Q + K2)/d; + fi.m_a1 = fi.m_b1; + fi.m_a2 = (1 - K/Q + K2)/d; + } else { + float d = 1 + K/(V*Q) + K2; + fi.m_b0 = (1 + K/Q + K2)/d; + fi.m_b1 = 2*(K2-1)/d; + fi.m_b2 = (1 - K/Q + K2)/d; + fi.m_a1 = fi.m_b1; + fi.m_a2 = (1 - K/(V*Q) + K2)/d; + } +} + + +void audio_effect_eq::apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) +{ + if(m_mode == 0) { + copy(src, dest); + return; + } + + u32 samples = src.available_samples(); + dest.prepare_space(samples); + u32 channels = src.channels(); + if(m_history.empty()) + m_history.resize(channels); + + for(u32 channel = 0; channel != channels; channel++) { + const sample_t *srcd = src.ptrs(channel, 0); + sample_t *destd = dest.ptrw(channel, 0); + for(u32 sample = 0; sample != samples; sample++) { + m_history[channel][0].push(*srcd++); + for(u32 band = 0; band != BANDS; band++) + m_filter[band].apply(m_history[channel][band], m_history[channel][band+1]); + *destd++ = m_history[channel][BANDS].m_v0; + } + } + + dest.commit(samples); +} diff --git a/src/emu/audio_effects/eq.h b/src/emu/audio_effects/eq.h new file mode 100644 index 00000000000..919d5292837 --- /dev/null +++ b/src/emu/audio_effects/eq.h @@ -0,0 +1,84 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_EQ_H +#define MAME_EMU_AUDIO_EFFECTS_EQ_H + +#include "aeffect.h" + +class audio_effect_eq : public audio_effect +{ +public: + enum { BANDS = 5 }; + + audio_effect_eq(u32 sample_rate, audio_effect *def); + virtual ~audio_effect_eq() = default; + + virtual int type() const override { return EQ; } + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override; + virtual void config_load(util::xml::data_node const *ef_node) override; + virtual void config_save(util::xml::data_node *ef_node) const override; + virtual void default_changed() override; + + void set_mode(u32 mode); + void set_q(u32 band, float q); + void set_f(u32 band, float f); + void set_db(u32 band, float db); + void set_low_shelf(bool active); + void set_high_shelf(bool active); + + u32 mode() const { return m_mode; } + float q(u32 band) const { return m_q[band]; } + float f(u32 band) const { return m_f[band]; } + float db(u32 band) const { return m_db[band]; } + bool low_shelf() const { return m_low_shelf; } + bool high_shelf() const { return m_high_shelf; } + + bool isset_mode() const { return m_isset_mode; } + bool isset_q(u32 band) const { return m_isset_q[band]; } + bool isset_f(u32 band) const { return m_isset_f[band]; } + bool isset_db(u32 band) const { return m_isset_db[band]; } + bool isset_low_shelf() const { return m_isset_low_shelf; } + bool isset_high_shelf() const { return m_isset_high_shelf; } + + void reset_mode(); + void reset_q(u32 band); + void reset_f(u32 band); + void reset_db(u32 band); + void reset_low_shelf(); + void reset_high_shelf(); + +private: + struct history { + float m_v0, m_v1, m_v2; + history() { m_v0 = m_v1 = m_v2 = 0; } + void push(float v) { m_v2 = m_v1; m_v1 = m_v0; m_v0 = v; } + }; + + struct filter { + float m_a1, m_a2, m_b0, m_b1, m_b2; + void clear() { m_a1 = 0; m_a2 = 0; m_b0 = 1; m_b1 = 0; m_b2 = 0; } + void apply(history &x, history &y) const { + y.push(m_b0 * x.m_v0 + m_b1 * x.m_v1 + m_b2 * x.m_v2 - m_a1 * y.m_v0 - m_a2 * y.m_v1); + } + }; + + u32 m_mode; + float m_q[BANDS], m_f[BANDS], m_db[BANDS]; + bool m_low_shelf, m_high_shelf; + std::array<filter, BANDS> m_filter; + std::vector<std::array<history, BANDS+1>> m_history; + + bool m_isset_mode, m_isset_low_shelf, m_isset_high_shelf; + bool m_isset_q[BANDS], m_isset_f[BANDS], m_isset_db[BANDS]; + + void build_filter(u32 band); + + void build_low_shelf(u32 band); + void build_high_shelf(u32 band); + void build_peak(u32 band); +}; + +#endif diff --git a/src/emu/audio_effects/filter.cpp b/src/emu/audio_effects/filter.cpp new file mode 100644 index 00000000000..70cf7ddc967 --- /dev/null +++ b/src/emu/audio_effects/filter.cpp @@ -0,0 +1,259 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "filter.h" +#include "xmlfile.h" + +// This effect implements a couple of very standard biquad filters, +// one lowpass and one highpass. + +// Formulas taken from: + +// [Zölzer 2011] "DAFX: Digital Audio Effects", Udo Zölzer, Second Edition, Wiley publishing, 2011 (Table 2.2) + + +audio_effect_filter::audio_effect_filter(u32 sample_rate, audio_effect *def) : audio_effect(sample_rate, def) +{ + // Minimal init to avoid using uninitialized values when reset_* + // recomputes filters + m_fl = m_fh = 1000; + m_ql = m_qh = 0.7; + + reset_lowpass_active(); + reset_highpass_active(); + reset_fl(); + reset_fh(); + reset_ql(); + reset_qh(); +} + +void audio_effect_filter::reset_lowpass_active() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_lowpass_active = false; + m_lowpass_active = d ? d->lowpass_active() : false; + build_lowpass(); +} + +void audio_effect_filter::reset_highpass_active() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_highpass_active = false; + m_highpass_active = d ? d->highpass_active() : true; + build_highpass(); +} + +void audio_effect_filter::reset_fl() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_fl = false; + m_fl = d ? d->fl() : 8000; + build_lowpass(); +} + +void audio_effect_filter::reset_ql() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_ql = false; + m_ql = d ? d->ql() : 0.7; + build_lowpass(); +} + +void audio_effect_filter::reset_fh() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_fh = false; + m_fh = d ? d->fh() : 40; + build_highpass(); +} + +void audio_effect_filter::reset_qh() +{ + audio_effect_filter *d = static_cast<audio_effect_filter *>(m_default); + m_isset_qh = false; + m_qh = d ? d->qh() : 0.7; + build_highpass(); +} + +void audio_effect_filter::config_load(util::xml::data_node const *ef_node) +{ + if(ef_node->has_attribute("lowpass_active")) { + m_lowpass_active = ef_node->get_attribute_int("lowpass_active", 0); + m_isset_lowpass_active = true; + } else + reset_lowpass_active(); + + if(ef_node->has_attribute("fl")) { + m_fl = ef_node->get_attribute_float("fl", 0); + m_isset_fl = true; + } else + reset_fl(); + + if(ef_node->has_attribute("ql")) { + m_ql = ef_node->get_attribute_float("ql", 0); + m_isset_ql = true; + } else + reset_ql(); + + if(ef_node->has_attribute("highpass_active")) { + m_highpass_active = ef_node->get_attribute_int("highpass_active", 0); + m_isset_highpass_active = true; + } else + reset_highpass_active(); + + if(ef_node->has_attribute("fh")) { + m_fh = ef_node->get_attribute_float("fh", 0); + m_isset_fh = true; + } else + reset_fh(); + + if(ef_node->has_attribute("qh")) { + m_qh = ef_node->get_attribute_float("qh", 0); + m_isset_qh = true; + } else + reset_qh(); +} + +void audio_effect_filter::config_save(util::xml::data_node *ef_node) const +{ + if(m_isset_lowpass_active) + ef_node->set_attribute_int("lowpass_active", m_lowpass_active); + if(m_isset_fl) + ef_node->set_attribute_float("fl", m_fl); + if(m_isset_ql) + ef_node->set_attribute_float("ql", m_ql); + if(m_isset_highpass_active) + ef_node->set_attribute_int("highpass_active", m_highpass_active); + if(m_isset_fh) + ef_node->set_attribute_float("fh", m_fh); + if(m_isset_qh) + ef_node->set_attribute_float("qh", m_qh); +} + +void audio_effect_filter::default_changed() +{ + if(!m_isset_lowpass_active) + reset_lowpass_active(); + if(!m_isset_highpass_active) + reset_highpass_active(); + if(!m_isset_fl) + reset_fl(); + if(!m_isset_fh) + reset_fh(); + if(!m_isset_ql) + reset_ql(); + if(!m_isset_qh) + reset_qh(); +} + +void audio_effect_filter::apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) +{ + if(!m_lowpass_active && !m_highpass_active) { + copy(src, dest); + return; + } + + u32 samples = src.available_samples(); + dest.prepare_space(samples); + u32 channels = src.channels(); + if(m_history.empty()) + m_history.resize(channels); + + for(u32 channel = 0; channel != channels; channel++) { + const sample_t *srcd = src.ptrs(channel, 0); + sample_t *destd = dest.ptrw(channel, 0); + for(u32 sample = 0; sample != samples; sample++) { + m_history[channel][0].push(*srcd++); + m_filter[0].apply(m_history[channel][0], m_history[channel][1]); + m_filter[1].apply(m_history[channel][1], m_history[channel][2]); + *destd++ = m_history[channel][2].m_v0; + } + } + + dest.commit(samples); + +} + +void audio_effect_filter::set_lowpass_active(bool active) +{ + m_isset_lowpass_active = true; + m_lowpass_active = active; + build_lowpass(); +} + +void audio_effect_filter::set_highpass_active(bool active) +{ + m_isset_highpass_active = true; + m_highpass_active = active; + build_highpass(); +} + +void audio_effect_filter::set_fl(float f) +{ + m_isset_fl = true; + m_fl = f; + build_lowpass(); +} + +void audio_effect_filter::set_fh(float f) +{ + m_isset_fh = true; + m_fh = f; + build_highpass(); +} + +void audio_effect_filter::set_ql(float q) +{ + m_isset_ql = true; + m_ql = q; + build_lowpass(); +} + +void audio_effect_filter::set_qh(float q) +{ + m_isset_qh = true; + m_qh = q; + build_highpass(); +} + +void audio_effect_filter::build_highpass() +{ + auto &fi = m_filter[0]; + if(!m_highpass_active) { + fi.clear(); + return; + } + + float K = tan(M_PI*m_fh/m_sample_rate); + float K2 = K*K; + float Q = m_qh; + + float d = K2*Q + K + Q; + fi.m_b0 = Q/d; + fi.m_b1 = -2*Q/d; + fi.m_b2 = fi.m_b0; + fi.m_a1 = 2*Q*(K2-1)/d; + fi.m_a2 = (K2*Q - K + Q)/d; +} + +void audio_effect_filter::build_lowpass() +{ + auto &fi = m_filter[1]; + if(!m_lowpass_active) { + fi.clear(); + return; + } + + float K = tan(M_PI*m_fl/m_sample_rate); + float K2 = K*K; + float Q = m_ql; + + float d = K2*Q + K + Q; + fi.m_b0 = K2*Q/d; + fi.m_b1 = 2*K2*Q /d; + fi.m_b2 = fi.m_b0; + fi.m_a1 = 2*Q*(K2-1)/d; + fi.m_a2 = (K2*Q - K + Q)/d; +} + diff --git a/src/emu/audio_effects/filter.h b/src/emu/audio_effects/filter.h new file mode 100644 index 00000000000..13463bd6b4b --- /dev/null +++ b/src/emu/audio_effects/filter.h @@ -0,0 +1,78 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_FILTER_H +#define MAME_EMU_AUDIO_EFFECTS_FILTER_H + +#include "aeffect.h" + +class audio_effect_filter : public audio_effect +{ +public: + audio_effect_filter(u32 sample_rate, audio_effect *def); + virtual ~audio_effect_filter() = default; + + virtual int type() const override { return FILTER; } + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override; + virtual void config_load(util::xml::data_node const *ef_node) override; + virtual void config_save(util::xml::data_node *ef_node) const override; + virtual void default_changed() override; + + void set_lowpass_active(bool active); + void set_highpass_active(bool active); + void set_fl(float f); + void set_fh(float f); + void set_ql(float q); + void set_qh(float q); + + bool lowpass_active() const { return m_lowpass_active; } + bool highpass_active() const { return m_highpass_active; } + float fl() const { return m_fl; } + float fh() const { return m_fh; } + float ql() const { return m_ql; } + float qh() const { return m_qh; } + + bool isset_lowpass_active() const { return m_isset_lowpass_active; } + bool isset_highpass_active() const { return m_isset_highpass_active; } + bool isset_fl() const { return m_isset_fl; } + bool isset_fh() const { return m_isset_fh; } + bool isset_ql() const { return m_isset_ql; } + bool isset_qh() const { return m_isset_qh; } + + void reset_lowpass_active(); + void reset_highpass_active(); + void reset_fl(); + void reset_fh(); + void reset_ql(); + void reset_qh(); + +private: + struct history { + float m_v0, m_v1, m_v2; + history() { m_v0 = m_v1 = m_v2 = 0; } + void push(float v) { m_v2 = m_v1; m_v1 = m_v0; m_v0 = v; } + }; + + struct filter { + float m_a1, m_a2, m_b0, m_b1, m_b2; + void clear() { m_a1 = 0; m_a2 = 0; m_b0 = 1; m_b1 = 0; m_b2 = 0; } + void apply(history &x, history &y) const { + y.push(m_b0 * x.m_v0 + m_b1 * x.m_v1 + m_b2 * x.m_v2 - m_a1 * y.m_v0 - m_a2 * y.m_v1); + } + }; + + bool m_isset_lowpass_active, m_isset_highpass_active; + bool m_isset_fl, m_isset_fh, m_isset_ql, m_isset_qh; + + bool m_lowpass_active, m_highpass_active; + float m_fl, m_fh, m_ql, m_qh; + std::array<filter, 2> m_filter; + std::vector<std::array<history, 3>> m_history; + + void build_lowpass(); + void build_highpass(); +}; + +#endif diff --git a/src/emu/audio_effects/reverb.cpp b/src/emu/audio_effects/reverb.cpp new file mode 100644 index 00000000000..c7f231a9d92 --- /dev/null +++ b/src/emu/audio_effects/reverb.cpp @@ -0,0 +1,28 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#include "emu.h" +#include "reverb.h" +#include "xmlfile.h" + +audio_effect_reverb::audio_effect_reverb(u32 sample_rate, audio_effect *def) : audio_effect(sample_rate, def) +{ +} + + +void audio_effect_reverb::config_load(util::xml::data_node const *ef_node) +{ +} + +void audio_effect_reverb::config_save(util::xml::data_node *ef_node) const +{ +} + +void audio_effect_reverb::default_changed() +{ +} + +void audio_effect_reverb::apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) +{ + copy(src, dest); +} diff --git a/src/emu/audio_effects/reverb.h b/src/emu/audio_effects/reverb.h new file mode 100644 index 00000000000..36aaabb697b --- /dev/null +++ b/src/emu/audio_effects/reverb.h @@ -0,0 +1,24 @@ +// license:BSD-3-Clause +// copyright-holders:Olivier Galibert + +#pragma once + +#ifndef MAME_EMU_AUDIO_EFFECTS_REVERB_H +#define MAME_EMU_AUDIO_EFFECTS_REVERB_H + +#include "aeffect.h" + +class audio_effect_reverb : public audio_effect +{ +public: + audio_effect_reverb(u32 sample_rate, audio_effect *def); + virtual ~audio_effect_reverb() = default; + + virtual int type() const override { return REVERB; } + virtual void apply(const emu::detail::output_buffer_flat<sample_t> &src, emu::detail::output_buffer_flat<sample_t> &dest) override; + virtual void config_load(util::xml::data_node const *ef_node) override; + virtual void config_save(util::xml::data_node *ef_node) const override; + virtual void default_changed() override; +}; + +#endif |