Skip to content

Commit

Permalink
feat: implemented getFrequencyResponse
Browse files Browse the repository at this point in the history
  • Loading branch information
Maciej Makowski committed Oct 25, 2024
1 parent d54a7de commit ede0652
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ std::vector<jsi::PropNameID> BiquadFilterNodeHostObject::getPropertyNames(
propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "Q"));
propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "gain"));
propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "type"));
propertyNames.push_back(jsi::PropNameID::forAscii(runtime, "getFrequencyResponse"));
return propertyNames;
}

Expand Down Expand Up @@ -60,6 +61,43 @@ jsi::Value BiquadFilterNodeHostObject::get(
return jsi::String::createFromUtf8(runtime, waveType);
}

if (propName == "getFrequencyResponse") {
return jsi::Function::createFromHostFunction(
runtime,
propNameId,
3,
[this](
jsi::Runtime &rt,
const jsi::Value &thisVal,
const jsi::Value *args,
size_t count) -> jsi::Value {
auto frequencyArray = args[0].getObject(rt).asArray(rt);
auto magResponseOut = args[1].getObject(rt).asArray(rt);
auto phaseResponseOut = args[2].getObject(rt).asArray(rt);

std::vector<float> frequencyArrayVector(frequencyArray.length(rt));
for (size_t i = 0; i < frequencyArray.length(rt); i++) {
frequencyArrayVector[i] = static_cast<float>(frequencyArray.getValueAtIndex(rt, i).getNumber());
}

std::vector<float> magResponseOutVector(magResponseOut.length(rt));
std::vector<float> phaseResponseOutVector(phaseResponseOut.length(rt));

auto wrapper = getBiquadFilterNodeWrapperFromAudioNodeWrapper();
wrapper->getFrequencyResponse(frequencyArrayVector, magResponseOutVector, phaseResponseOutVector);

for (size_t i = 0; i < magResponseOutVector.size(); i++) {
magResponseOut.setValueAtIndex(rt, i, magResponseOutVector[i]);
}

for (size_t i = 0; i < phaseResponseOutVector.size(); i++) {
phaseResponseOut.setValueAtIndex(rt, i, phaseResponseOutVector[i]);
}

return jsi::Value::undefined();
});
}

return AudioNodeHostObject::get(runtime, propNameId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

// https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html - math
// formulas for filters
// https://github.com/LabSound/LabSound/blob/main/src/internal/src/Biquad.cpp -
// implementation of filters on which I based mine

namespace audioapi {

Expand Down Expand Up @@ -44,6 +42,45 @@ std::shared_ptr<AudioParam> BiquadFilterNode::getGainParam() const {
return gainParam_;
}

// Compute Z-transform of the filter
// https://www.dsprelated.com/freebooks/filters/Frequency_Response_Analysis.html
// https://www.dsprelated.com/freebooks/filters/Transfer_Function_Analysis.html
//
// frequency response - H(z) = (b0 + b1*z^(-1) + b2*z^(-2))/(1 + a1*z^(-1) + a2*z^(-2))
// = ((b0 * z + b1) * z + b2) / ((z + a1) * z + a2)
// phase response - angle of the frequency response

void BiquadFilterNode::getFrequencyResponse(
const std::vector<float> &frequencyArray,
std::vector<float> &magResponseOutput,
std::vector<float> &phaseResponseOutput) {
applyFilter();

auto frequencyArraySize = frequencyArray.size();
auto magResponseOutputSize = magResponseOutput.size();
auto phaseResponseOutputSize = phaseResponseOutput.size();

if (magResponseOutputSize != frequencyArraySize ||
phaseResponseOutputSize != frequencyArraySize) {
throw std::invalid_argument("Output arrays must have the same size");
}

float b0 = b0_;
float b1 = b1_;
float b2 = b2_;
float a1 = a1_;
float a2 = a2_;

for (size_t i = 0; i < frequencyArraySize; i++)
{
auto omega = static_cast<float>(M_PI) * frequencyArray[i] / NYQUIST_FREQUENCY;
auto z = std::complex<float>(cos(omega), sin(omega));
auto response = ((b0 * z + b1) * z + b2) / ((z + a1) * z + a2);
magResponseOutput[i] = static_cast<float>(abs(response));
phaseResponseOutput[i] = static_cast<float>(atan2(imag(response), real(response)));
}
}

float BiquadFilterNode::clamp(float value, float min, float max) {
return std::min(std::max(value, min), max);
}
Expand Down Expand Up @@ -83,16 +120,14 @@ void BiquadFilterNode::setLowpassCoefficients(float frequency, float Q) {

Q = std::max(0.0f, Q);
float g = std::pow(10.0f, 0.05f * Q);
float d = std::sqrt((4 - std::sqrt(16 - 16 / (g * g))) / 2);

float theta = M_PI * frequency;
float sn = 0.5f * d * std::sin(theta);
float beta = 0.5f * (1 - sn) / (1 + sn);
float gamma = (0.5f + beta) * std::cos(theta);
float alpha = 0.25f * (0.5f + beta - gamma);
float alpha = std::sin(theta) / (2 * g);
float cosw = std::cos(theta);
float beta = (1 - cosw) / 2;

setNormalizedCoefficients(
2 * alpha, 4 * alpha, 2 * alpha, 1.0f, -2 * gamma, 2 * beta);
beta, 2 * beta, beta, 1 + alpha, -2 * cosw, 1 - alpha);
}

void BiquadFilterNode::setHighpassCoefficients(float frequency, float Q) {
Expand All @@ -108,16 +143,14 @@ void BiquadFilterNode::setHighpassCoefficients(float frequency, float Q) {

Q = std::max(0.0f, Q);
float g = std::pow(10.0f, 0.05f * Q);
float d = std::sqrt((4 - std::sqrt(16 - 16 / (g * g))) / 2);

float theta = M_PI * frequency;
float sn = 0.5f * d * std::sin(theta);
float beta = 0.5f * (1 - sn) / (1 + sn);
float gamma = (0.5f + beta) * std::cos(theta);
float alpha = 0.25f * (0.5f + beta - gamma);
float theta = M_PI * frequency;
float alpha = std::sin(theta) / (2 * g);
float cosw = std::cos(theta);
float beta = (1 - cosw) / 2;

setNormalizedCoefficients(
2 * alpha, -4 * alpha, 2 * alpha, 1.0f, -2 * gamma, 2 * beta);
setNormalizedCoefficients(
beta, -2 * beta, beta, 1 + alpha, -2 * cosw, 1 - alpha);
}

void BiquadFilterNode::setBandpassCoefficients(float frequency, float Q) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
#include <complex>

#include "AudioNode.h"
#include "AudioParam.h"
Expand All @@ -21,6 +23,9 @@ class BiquadFilterNode : public AudioNode {
std::shared_ptr<AudioParam> getDetuneParam() const;
std::shared_ptr<AudioParam> getQParam() const;
std::shared_ptr<AudioParam> getGainParam() const;
void getFrequencyResponse(const std::vector<float> &frequencyArray,
std::vector<float> &magResponseOutput,
std::vector<float> &phaseResponseOutput);

protected:
bool processAudio(float *audioData, int32_t numFrames) override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,13 @@ void BiquadFilterNodeWrapper::setType(const std::string &filterType) {
auto biquadFilterNode_ = getBiquadFilterNodeFromAudioNode();
biquadFilterNode_->setType(filterType);
}

void BiquadFilterNodeWrapper::getFrequencyResponse(
const std::vector<float> &frequencyArray,
std::vector<float> &magResponseOutput,
std::vector<float> &phaseResponseOutput) {
auto biquadFilterNode_ = getBiquadFilterNodeFromAudioNode();
biquadFilterNode_->getFrequencyResponse(frequencyArray, magResponseOutput,
phaseResponseOutput);
}
} // namespace audioapi
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class BiquadFilterNodeWrapper : public AudioNodeWrapper {
std::shared_ptr<AudioParamWrapper> getGainParam() const;
std::string getType();
void setType(const std::string &filterType);
void getFrequencyResponse(const std::vector<float> &frequencyArray,
std::vector<float> &magResponseOutput,
std::vector<float> &phaseResponseOutput);

private:
std::shared_ptr<AudioParamWrapper> frequencyParam_;
Expand Down
5 changes: 5 additions & 0 deletions packages/react-native-audio-api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ export interface BiquadFilterNode extends AudioNode {
Q: AudioParam;
gain: AudioParam;
type: FilterType;
getFrequencyResponse(
frequencyArray: number[],
magResponseOut: number[],
phaseResponseOut: number[]
): void;
}

export type ContextState = 'running' | 'closed' | 'suspended';
Expand Down

0 comments on commit ede0652

Please sign in to comment.