Skip to content

sound/flt_biquad: Added calculator functions for RC-based band-pass filters. #13888

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/devices/sound/flt_biquad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,101 @@ filter_biquad_device::biquad_params filter_biquad_device::opamp_diff_bandpass_ca
}


/* RC-based band-pass filters:
*
* RR variation: the two resistors are connected to each other:
*
* Vin -- C1 -- R1 -+-----+- Vout
* | |
* R2 C2
* | |
* GND (V)GND
*
*
* CC variation: The two capacitors are connected to each other:
*
* Vin -- R1 -+- C2 -+- Vout
* | |
* C1 R2
* | |
* GND (V)GND
*
* (V)GND could be a virtual ground.
*
* BPF transfer function: H(s) = (A * s) / (s ^ 2 + B * s + C)
* In the RR configuration, we have:
* A = 1 / (R1 * C2)
* B = (R1 * C1 + R2 * C2 + R2 * C1) / (R1 * R2 * C1 * C2)
* C = 1 / (R1 * R2 * C1 * C2)
* In the CC configuration, we have:
* A = 1 / (R1 * C1)
* B = (R1 * C1 + R2 * C2 + R1 * C2) / (R1 * R2 * C1 * C2)
* C = 1 / (R1 * R2 * C1 * C2)
* From the standard transfer function for BPFs, we have:
* A = gain * (w / Q)
* B = w / Q
* C = w ^ 2
* The calculations of Fc, Q and gain in the *_calc functions below are derived
* from the equations above, with some algebra.
*/

filter_biquad_device& filter_biquad_device::rc_rr_bandpass_setup(double r1, double r2, double c1, double c2)
{
return setup(rc_rr_bandpass_calc(r1, r2, c1, c2));
}

void filter_biquad_device::rc_rr_bandpass_modify(double r1, double r2, double c1, double c2)
{
modify(rc_rr_bandpass_calc(r1, r2, c1, c2));
}

filter_biquad_device::biquad_params filter_biquad_device::rc_rr_bandpass_calc(double r1, double r2, double c1, double c2)
{
if ((r1 == 0) || (r2 == 0) || (c1 == 0) || (c2 == 0))
{
fatalerror("filter_biquad_device::rc_rr_bandpass_calc() - no parameters can be 0; parameters were: r1: %f, r2: %f, c1: %f, c2: %f", r1, r2, c1, c2);
}
const double x = sqrt(r1 * r2 * c1 * c2);
const double y = r1 * c1 + r2 * c2 + r2 * c1;
const double z = r2 * c1;
filter_biquad_device::biquad_params p;
p.type = filter_biquad_device::biquad_type::BANDPASS;
p.fc = 1.0 / (2.0 * M_PI * x);
p.q = x / y;
p.gain = z / y;
LOGMASKED(LOG_SETUP, "filter_biquad_device::rc_rr_bandpass_calc(%f %f %f %f) yields: fc = %f, Q = %f, gain = %f\n", r1, r2, c1, c2, p.fc, p.q, p.gain);
return p;
}

filter_biquad_device& filter_biquad_device::rc_cc_bandpass_setup(double r1, double r2, double c1, double c2)
{
return setup(rc_cc_bandpass_calc(r1, r2, c1, c2));
}

void filter_biquad_device::rc_cc_bandpass_modify(double r1, double r2, double c1, double c2)
{
modify(rc_cc_bandpass_calc(r1, r2, c1, c2));
}

filter_biquad_device::biquad_params filter_biquad_device::rc_cc_bandpass_calc(double r1, double r2, double c1, double c2)
{
if ((r1 == 0) || (r2 == 0) || (c1 == 0) || (c2 == 0))
{
fatalerror("filter_biquad_device::rc_cc_bandpass_calc() - no parameters can be 0; parameters were: r1: %f, r2: %f, c1: %f, c2: %f", r1, r2, c1, c2);
}
const double x = sqrt(r1 * r2 * c1 * c2);
const double y = r1 * c1 + r2 * c2 + r1 * c2;
const double z = r2 * c2;
filter_biquad_device::biquad_params p;
p.type = filter_biquad_device::biquad_type::BANDPASS;
p.fc = 1.0 / (2.0 * M_PI * x);
p.q = x / y;
p.gain = z / y;
LOGMASKED(LOG_SETUP, "filter_biquad_device::rc_cc_bandpass_calc(%f %f %f %f) yields: fc = %f, Q = %f, gain = %f\n", r1, r2, c1, c2, p.fc, p.q, p.gain);
return p;
}


//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
Expand Down
9 changes: 9 additions & 0 deletions src/devices/sound/flt_biquad.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ class filter_biquad_device : public device_t, public device_sound_interface
void opamp_diff_bandpass_modify(double r1, double r2, double c1, double c2);
biquad_params opamp_diff_bandpass_calc(double r1, double r2, double c1, double c2);

// RC-based band-pass, resistors connected to each other.
filter_biquad_device& rc_rr_bandpass_setup(double r1, double r2, double c1, double c2);
void rc_rr_bandpass_modify(double r1, double r2, double c1, double c2);
biquad_params rc_rr_bandpass_calc(double r1, double r2, double c1, double c2);

// RC-based band-pass, capacitors connected to each other.
filter_biquad_device& rc_cc_bandpass_setup(double r1, double r2, double c1, double c2);
void rc_cc_bandpass_modify(double r1, double r2, double c1, double c2);
biquad_params rc_cc_bandpass_calc(double r1, double r2, double c1, double c2);

protected:
// device-level overrides
Expand Down
47 changes: 13 additions & 34 deletions src/mame/linn/linndrum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,8 @@ void linndrum_audio_device::update_volume_and_pan(int channel)
// Using -gain_*, because the summing op-amps are inverting.
mixer_input->set_route_gain(0, m_left_mixer, 0, -gain_left);
mixer_input->set_route_gain(0, m_right_mixer, 0, -gain_right);
LOGMASKED(LOG_MIX, "Gain update for %s - left: %f, right: %f\n",
MIXER_CHANNEL_NAMES[channel], gain_left, gain_right);

if (channel == MIX_CLICK)
{
Expand All @@ -931,59 +933,36 @@ void linndrum_audio_device::update_volume_and_pan(int channel)
// For now, scale by some number to match the volume of other voices.
static constexpr const float CLICK_GAIN_CORRECTION = 5;

float fc = 0;
float q = 1;
float gain = 1;
if (volume >= 100)
{
// HPF transfer function: H(s) = (g * s) / (s + w) where
// g = C1 / (C1 + C2)
// w = 1 / (R * (C1 + C2))
fc = 1.0F / (2 * float(M_PI) * r_voice_gnd * (C_CLICK_DCBLOCK + C_CLICK_WIPER));
gain = C_CLICK_DCBLOCK / (C_CLICK_DCBLOCK + C_CLICK_WIPER);
const float fc = 1.0F / (2 * float(M_PI) * r_voice_gnd * (C_CLICK_DCBLOCK + C_CLICK_WIPER));
const float gain = C_CLICK_DCBLOCK / (C_CLICK_DCBLOCK + C_CLICK_WIPER);
m_click_bpf->modify(filter_biquad_device::biquad_type::HIGHPASS1P1Z, fc, 1, gain * CLICK_GAIN_CORRECTION);
LOGMASKED(LOG_MIX, "- HPF cutoff: %.2f Hz, Gain: %.3f\n", fc, gain);
}
else if (volume > 0)
{
// BPF transfer function: H(s) = (A * s) / (s ^ 2 + B * s + C)
// Where:
// A = 1 / (R1 * C2)
// B = (C1 * R1 + C1 * R2 + C2 * R2) / (R1 * R2 * C1 * C2)
// C = 1 / (R1 * R2 * C1 * C2).
// From the standard transfer function for BPFs, we have:
// A = gain * (w / Q)
// B = w / Q
// C = w ^ 2
// The calculations of Fc, Q and gain below are derived from the
// equations above, with some algebra.

const float x = sqrtf(r0 * r_wiper_gnd * C_CLICK_DCBLOCK * C_CLICK_WIPER);
const float y = C_CLICK_DCBLOCK * r0 + C_CLICK_DCBLOCK * r_wiper_gnd + C_CLICK_WIPER * r_wiper_gnd;
fc = 1.0F / (2 * float(M_PI) * x);
q = x / y;
gain = r_wiper_gnd * C_CLICK_DCBLOCK / y;

// The transfer function above includes the effect of the volume
filter_biquad_device::biquad_params p = m_click_bpf->rc_rr_bandpass_calc(r0, r_wiper_gnd, C_CLICK_DCBLOCK, C_CLICK_WIPER);
// The filter params above include the effect of the volume
// potentiometer. But `gain_left` and `gain_right` already incorporate
// that effect. So it needs to be undone from the filter's gain.
gain *= 1.0F / RES_VOLTAGE_DIVIDER(r0, r_wiper_gnd);

m_click_bpf->modify(filter_biquad_device::biquad_type::BANDPASS, fc, q, gain * CLICK_GAIN_CORRECTION);
p.gain /= RES_VOLTAGE_DIVIDER(r0, r_wiper_gnd);
// See comments for CLICK_GAIN_CORRECTION.
p.gain *= CLICK_GAIN_CORRECTION;
m_click_bpf->modify(p);
LOGMASKED(LOG_MIX, "- BPF cutoff: %.2f Hz, Q: %.3f, Gain: %.3f\n", p.fc, p.q, p.gain);
}
// Else, if the volume is 0, don't change the BPF's configuration to avoid divisions by 0.

LOGMASKED(LOG_MIX, "Gain update for %s - left: %f, right: %f, %s cutoff: %.2f Hz, Q: %.3f, Gain: %.3f\n",
MIXER_CHANNEL_NAMES[MIX_CLICK], gain_left, gain_right,
(volume >= 100) ? "HPF" : "BPF", fc, q, gain);
}
else
{
// The rest of the voices just have a DC-blocking filter. Its exact cutoff
// will depend on the volume and pan settings, but it won't be audible.
m_voice_hpf[channel]->filter_rc_set_RC(filter_rc_device::HIGHPASS, r_voice_gnd, 0, 0, C_VOICE);
LOGMASKED(LOG_MIX, "Gain update for %s - left: %f, right: %f, HPF cutoff: %.2f Hz\n",
MIXER_CHANNEL_NAMES[channel], gain_left, gain_right,
1.0F / (2 * float(M_PI) * r_voice_gnd * C_VOICE));
LOGMASKED(LOG_MIX, "- HPF cutoff: %.2f Hz\n", 1.0F / (2 * float(M_PI) * r_voice_gnd * C_VOICE));
}
}

Expand Down
50 changes: 3 additions & 47 deletions src/mame/roland/roland_tr707.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,6 @@ class tr707_audio_device : public device_t
static inline constexpr double VBE = 0.6; // BJT base-emitter voltage drop.
static inline constexpr u16 MAX_CYMBAL_COUNTER = 0x8000;

static filter_biquad_device::biquad_params rc_bpf(double r1, double r2, double c1, double c2, bool crrc);
static double mux_dac_v(double v_eg, u8 data);

void advance_sample_w(offs_t offset, u16 data);
Expand Down Expand Up @@ -602,7 +601,7 @@ void tr707_audio_device::device_add_mconfig(machine_config &config)
for (int i = 0; i < MC_COUNT; ++i)
{
FILTER_BIQUAD(config, m_voice_bpf[i]);
m_voice_bpf[i]->setup(rc_bpf(bpf_comps[i][0], bpf_comps[i][1], bpf_comps[i][2], bpf_comps[i][3], false));
m_voice_bpf[i]->rc_cc_bandpass_setup(bpf_comps[i][0], bpf_comps[i][1], bpf_comps[i][2], bpf_comps[i][3]);
voices[i]->add_route(0, m_voice_bpf[i], 1.0);

FILTER_VOLUME(config, m_level[i]);
Expand All @@ -622,8 +621,8 @@ void tr707_audio_device::device_add_mconfig(machine_config &config)
// making it to the left and right output sockets.
auto &left_bpf = FILTER_BIQUAD(config, "left_out_bpf");
auto &right_bpf = FILTER_BIQUAD(config, "right_out_bpf");
left_bpf.setup(rc_bpf(RES_K(1), RES_K(47), CAP_U(10), CAP_U(0.01), true)); // R114, R112, C79, C76
right_bpf.setup(rc_bpf(RES_K(1), RES_K(47), CAP_U(10), CAP_U(0.01), true)); // R113, R111, C80, C77
left_bpf.rc_rr_bandpass_setup(RES_K(1), RES_K(47), CAP_U(10), CAP_U(0.01)); // R114, R112, C79, C76
right_bpf.rc_rr_bandpass_setup(RES_K(1), RES_K(47), CAP_U(10), CAP_U(0.01)); // R113, R111, C80, C77
m_left_mixer->add_route(0, left_bpf, 1.0);
m_right_mixer->add_route(0, right_bpf, 1.0);

Expand Down Expand Up @@ -655,49 +654,6 @@ void tr707_audio_device::device_reset()
update_mix(channel);
}

// Biquad parameters for an RC-based bandpass filter.
// crrc == true:
// Vin -- C1 -- R1 -+---+- Vout
// | |
// R2 C2
// | |
// GND GND
// crrc == false:
// Vin -- R1 -+- C2 -+- Vout
// | |
// C1 R2
// | |
// GND GND
// TODO: move to sound/flt_biquad.
filter_biquad_device::biquad_params tr707_audio_device::rc_bpf(double r1, double r2, double c1, double c2, bool crrc)
{
// BPF transfer function: H(s) = (A * s) / (s ^ 2 + B * s + C)
// In the C-R-R-C configuration, we have:
// A = 1 / (R1 * C2)
// B = (R1 * C1 + R2 * C2 + R2 * C1) / (R1 * R2 * C1 * C2)
// C = 1 / (R1 * R2 * C1 * C2)
// In the R-C-C-R configuration, we have:
// A = 1 / (R1 * C1)
// B = (R1 * C1 + R2 * C2 + R1 * C2) / (R1 * R2 * C1 * C2)
// C = 1 / (R1 * R2 * C1 * C2)
// From the standard transfer function for BPFs, we have:
// A = gain * (w / Q)
// B = w / Q
// C = w ^ 2
// The calculations of Fc, Q and gain below are derived from the
// equations above, with some algebra.

const double x = sqrt(r1 * r2 * c1 * c2);
const double y = crrc ? (r1 * c1 + r2 * c2 + r2 * c1) : (r1 * c1 + r2 * c2 + r1 * c2);
const double z = crrc ? (r2 * c1) : (r2 * c2);
filter_biquad_device::biquad_params params;
params.type = filter_biquad_device::biquad_type::BANDPASS;
params.fc = 1.0 / (2.0 * M_PI * x);
params.q = x / y;
params.gain = z / y;
return params;
}

// Computes the output voltage of the mux DAC circuit, given the voltage at the
// output of the envelope generator and the DAC data value.
double tr707_audio_device::mux_dac_v(double v_eg, u8 data)
Expand Down
Loading