summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/audio_effects/eq.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/audio_effects/eq.cpp')
-rw-r--r--src/emu/audio_effects/eq.cpp331
1 files changed, 331 insertions, 0 deletions
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);
+}