Skip to content

Commit

Permalink
Adding a working implementation of a Biquad filter adapted for use wi…
Browse files Browse the repository at this point in the history
…th the fixed-point CPU of the ESP8266 (earlephilhower#370)
  • Loading branch information
wberube authored Feb 13, 2021
1 parent 6ecb6af commit 714e8cf
Show file tree
Hide file tree
Showing 2 changed files with 325 additions and 0 deletions.
245 changes: 245 additions & 0 deletions src/AudioOutputFilterBiquad.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
/*
AudioOutputFilterBiquad
Implements a Biquad filter
Copyright (C) 2012 Nigel Redmon
Copyright (C) 2021 William Bérubé
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#include <Arduino.h>
#include "AudioOutputFilterBiquad.h"

AudioOutputFilterBiquad::AudioOutputFilterBiquad(AudioOutput *sink)
{
this->sink = sink;

type = bq_type_lowpass;
a0 = 1.0;
a1 = a2 = b1 = b2 = 0.0;
Fc = 0.50;
Q = 0.707;
peakGain = 0.0;
z1 = z2 = 0.0;
}

AudioOutputFilterBiquad::AudioOutputFilterBiquad(int type, float Fc, float Q, float peakGain, AudioOutput *sink)
{
this->sink = sink;

SetBiquad(type, Fc, Q, peakGain);
z1 = z2 = 0.0;
}

AudioOutputFilterBiquad::~AudioOutputFilterBiquad() {}

bool AudioOutputFilterBiquad::SetRate(int hz)
{
return sink->SetRate(hz);
}

bool AudioOutputFilterBiquad::SetBitsPerSample(int bits)
{
return sink->SetBitsPerSample(bits);
}

bool AudioOutputFilterBiquad::SetChannels(int channels)
{
return sink->SetChannels(channels);
}

bool AudioOutputFilterBiquad::SetGain(float gain)
{
return sink->SetGain(gain);
}

void AudioOutputFilterBiquad::SetType(int type)
{
this->type = type;
CalcBiquad();
}

void AudioOutputFilterBiquad::SetFc(float Fc)
{
this->Fc = Fc;
CalcBiquad();
}

void AudioOutputFilterBiquad::SetQ(float Q)
{
this->Q = Q;
CalcBiquad();
}

void AudioOutputFilterBiquad::SetPeakGain(float peakGain)
{
this->peakGain = peakGain;
CalcBiquad();
}

void AudioOutputFilterBiquad::SetBiquad(int type, float Fc, float Q, float peakGain)
{
this->type = type;
this->Fc = Fc;
this->Q = Q;
this->peakGain = peakGain;
CalcBiquad();
}

void AudioOutputFilterBiquad::CalcBiquad()
{
float norm;
float V = pow(10, fabs(peakGain) / 20.0);
float K = tan(M_PI * Fc);

switch (this->type) {
case bq_type_lowpass:
norm = 1 / (1 + K / Q + K * K);
a0 = K * K * norm;
a1 = 2 * a0;
a2 = a0;
b1 = 2 * (K * K - 1) * norm;
b2 = (1 - K / Q + K * K) * norm;
break;

case bq_type_highpass:
norm = 1 / (1 + K / Q + K * K);
a0 = 1 * norm;
a1 = -2 * a0;
a2 = a0;
b1 = 2 * (K * K - 1) * norm;
b2 = (1 - K / Q + K * K) * norm;
break;

case bq_type_bandpass:
norm = 1 / (1 + K / Q + K * K);
a0 = K / Q * norm;
a1 = 0;
a2 = -a0;
b1 = 2 * (K * K - 1) * norm;
b2 = (1 - K / Q + K * K) * norm;
break;

case bq_type_notch:
norm = 1 / (1 + K / Q + K * K);
a0 = (1 + K * K) * norm;
a1 = 2 * (K * K - 1) * norm;
a2 = a0;
b1 = a1;
b2 = (1 - K / Q + K * K) * norm;
break;

case bq_type_peak:
if (peakGain >= 0) { // boost
norm = 1 / (1 + 1/Q * K + K * K);
a0 = (1 + V/Q * K + K * K) * norm;
a1 = 2 * (K * K - 1) * norm;
a2 = (1 - V/Q * K + K * K) * norm;
b1 = a1;
b2 = (1 - 1/Q * K + K * K) * norm;
} else { // cut
norm = 1 / (1 + V/Q * K + K * K);
a0 = (1 + 1/Q * K + K * K) * norm;
a1 = 2 * (K * K - 1) * norm;
a2 = (1 - 1/Q * K + K * K) * norm;
b1 = a1;
b2 = (1 - V/Q * K + K * K) * norm;
}
break;

case bq_type_lowshelf:
if (peakGain >= 0) { // boost
norm = 1 / (1 + sqrt(2) * K + K * K);
a0 = (1 + sqrt(2*V) * K + V * K * K) * norm;
a1 = 2 * (V * K * K - 1) * norm;
a2 = (1 - sqrt(2*V) * K + V * K * K) * norm;
b1 = 2 * (K * K - 1) * norm;
b2 = (1 - sqrt(2) * K + K * K) * norm;
}
else { // cut
norm = 1 / (1 + sqrt(2*V) * K + V * K * K);
a0 = (1 + sqrt(2) * K + K * K) * norm;
a1 = 2 * (K * K - 1) * norm;
a2 = (1 - sqrt(2) * K + K * K) * norm;
b1 = 2 * (V * K * K - 1) * norm;
b2 = (1 - sqrt(2*V) * K + V * K * K) * norm;
}
break;

case bq_type_highshelf:
if (peakGain >= 0) { // boost
norm = 1 / (1 + sqrt(2) * K + K * K);
a0 = (V + sqrt(2*V) * K + K * K) * norm;
a1 = 2 * (K * K - V) * norm;
a2 = (V - sqrt(2*V) * K + K * K) * norm;
b1 = 2 * (K * K - 1) * norm;
b2 = (1 - sqrt(2) * K + K * K) * norm;
}
else { // cut
norm = 1 / (V + sqrt(2*V) * K + K * K);
a0 = (1 + sqrt(2) * K + K * K) * norm;
a1 = 2 * (K * K - 1) * norm;
a2 = (1 - sqrt(2) * K + K * K) * norm;
b1 = 2 * (K * K - V) * norm;
b2 = (V - sqrt(2*V) * K + K * K) * norm;
}
break;
}

i_a0 = a0 * BQ_DECAL;
i_a1 = a1 * BQ_DECAL;
i_a2 = a2 * BQ_DECAL;

i_b1 = b1 * BQ_DECAL;
i_b2 = b2 * BQ_DECAL;

i_lz1 = i_rz1 = z1 * BQ_DECAL;
i_lz2 = i_rz2 = z2 * BQ_DECAL;

i_Fc = Fc * BQ_DECAL;
i_Q = Q * BQ_DECAL;
i_peakGain = peakGain * BQ_DECAL;
}

bool AudioOutputFilterBiquad::begin()
{
return sink->begin();
}

bool AudioOutputFilterBiquad::ConsumeSample(int16_t sample[2])
{

int32_t leftSample = (sample[LEFTCHANNEL] << BQ_SHIFT) / 2;
int32_t rightSample = (sample[RIGHTCHANNEL] << BQ_SHIFT) / 2;

int64_t leftOutput = ((leftSample * i_a0) >> BQ_SHIFT) + i_lz1;
i_lz1 = ((leftSample * i_a1) >> BQ_SHIFT) + i_lz2 - ((i_b1 * leftOutput) >> BQ_SHIFT);
i_lz2 = ((leftSample * i_a2) >> BQ_SHIFT) - ((i_b2 * leftOutput) >> BQ_SHIFT);

int64_t rightOutput = ((rightSample * i_a0) >> BQ_SHIFT) + i_rz1;
i_rz1 = ((rightSample * i_a1) >> BQ_SHIFT) + i_rz2 - ((i_b1 * rightOutput) >> BQ_SHIFT);
i_rz2 = ((rightSample * i_a2) >> BQ_SHIFT) - ((i_b2 * rightOutput) >> BQ_SHIFT);

int16_t out[2];
out[LEFTCHANNEL] = (int16_t)(leftOutput >> BQ_SHIFT);
out[RIGHTCHANNEL] = (int16_t)(rightOutput >> BQ_SHIFT);

return sink->ConsumeSample(out);
}

bool AudioOutputFilterBiquad::stop()
{
return sink->stop();
}
80 changes: 80 additions & 0 deletions src/AudioOutputFilterBiquad.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
AudioOutputFilterBiquad
Implements a Biquad filter
Copyright (C) 2012 Nigel Redmon
Copyright (C) 2021 William Bérubé
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef _AudioOutputFilterBiquad_H
#define _AudioOutputFilterBiquad_H

#include "AudioOutput.h"

#define BQ_SHIFT 16
#define BQ_DECAL 65536

enum {
bq_type_lowpass = 0,
bq_type_highpass,
bq_type_bandpass,
bq_type_notch,
bq_type_peak,
bq_type_lowshelf,
bq_type_highshelf
};

class AudioOutputFilterBiquad : public AudioOutput
{
public:
AudioOutputFilterBiquad(AudioOutput *sink);
AudioOutputFilterBiquad(int type, float Fc, float Q, float peakGain, AudioOutput *sink);
virtual ~AudioOutputFilterBiquad() override;
virtual bool SetRate(int hz) override;
virtual bool SetBitsPerSample(int bits) override;
virtual bool SetChannels(int chan) override;
virtual bool SetGain(float f) override;
virtual bool begin() override;
virtual bool ConsumeSample(int16_t sample[2]) override;
virtual bool stop() override;

private:
void SetType(int type);
void SetFc(float Fc);
void SetQ(float Q);
void SetPeakGain(float peakGain);
void SetBiquad(int type, float Fc, float Q, float peakGain);

protected:
AudioOutput *sink;
int buffSize;
int16_t *leftSample;
int16_t *rightSample;
int writePtr;
int readPtr;
bool filled;
int type;
void CalcBiquad();
int64_t i_a0, i_a1, i_a2, i_b1, i_b2;
int64_t i_Fc, i_Q, i_peakGain;
int64_t i_lz1, i_lz2, i_rz1, i_rz2;
float a0, a1, a2, b1, b2;
float Fc, Q, peakGain;
float z1, z2;
};

#endif

0 comments on commit 714e8cf

Please sign in to comment.