summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/audio_effects
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/audio_effects')
-rw-r--r--src/emu/audio_effects/aeffect.cpp46
-rw-r--r--src/emu/audio_effects/aeffect.h43
-rw-r--r--src/emu/audio_effects/compressor.cpp28
-rw-r--r--src/emu/audio_effects/compressor.h24
-rw-r--r--src/emu/audio_effects/eq.cpp331
-rw-r--r--src/emu/audio_effects/eq.h84
-rw-r--r--src/emu/audio_effects/filter.cpp259
-rw-r--r--src/emu/audio_effects/filter.h78
-rw-r--r--src/emu/audio_effects/reverb.cpp28
-rw-r--r--src/emu/audio_effects/reverb.h24
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