mirror of
https://github.com/sle118/squeezelite-esp32.git
synced 2025-12-12 14:37:21 +03:00
move to new cspot
This commit is contained in:
39
components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp
Normal file
39
components/spotify/cspot/bell/main/audio-dsp/AudioMixer.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
#include "AudioMixer.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
AudioMixer::AudioMixer() {
|
||||
}
|
||||
|
||||
std::unique_ptr<StreamInfo> AudioMixer::process(std::unique_ptr<StreamInfo> info) {
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
if (info->numChannels != from) {
|
||||
throw std::runtime_error("AudioMixer: Input channel count does not match configuration");
|
||||
}
|
||||
info->numChannels = to;
|
||||
|
||||
for (auto &singleConf : mixerConfig) {
|
||||
if (singleConf.source.size() == 1) {
|
||||
if (singleConf.source[0] == singleConf.destination) {
|
||||
continue;
|
||||
}
|
||||
// Copy channel
|
||||
for (int i = 0; i < info->numSamples; i++) {
|
||||
info->data[singleConf.destination][i] = info->data[singleConf.source[0]][i];
|
||||
}
|
||||
} else {
|
||||
// Mix channels
|
||||
float sample = 0.0f;
|
||||
for (int i = 0; i < info->numSamples; i++) {
|
||||
sample = 0.0;
|
||||
for (auto &source : singleConf.source) {
|
||||
sample += info->data[source][i];
|
||||
}
|
||||
|
||||
info->data[singleConf.destination][i] = sample / (float) singleConf.source.size();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#include "AudioPipeline.h"
|
||||
#include <iostream>
|
||||
#include "BellLogger.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
AudioPipeline::AudioPipeline() {
|
||||
// this->headroomGainTransform = std::make_shared<Gain>(Channels::LEFT_RIGHT);
|
||||
// this->transforms.push_back(this->headroomGainTransform);
|
||||
};
|
||||
|
||||
void AudioPipeline::addTransform(std::shared_ptr<AudioTransform> transform) {
|
||||
transforms.push_back(transform);
|
||||
recalculateHeadroom();
|
||||
}
|
||||
|
||||
void AudioPipeline::recalculateHeadroom() {
|
||||
float headroom = 0.0f;
|
||||
|
||||
// Find largest headroom required by any transform down the chain, and apply it
|
||||
for (auto transform : transforms) {
|
||||
if (headroom < transform->calculateHeadroom()) {
|
||||
headroom = transform->calculateHeadroom();
|
||||
}
|
||||
}
|
||||
|
||||
// headroomGainTransform->configure(-headroom);
|
||||
}
|
||||
|
||||
void AudioPipeline::volumeUpdated(int volume) {
|
||||
BELL_LOG(debug, "AudioPipeline", "Requested");
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
for (auto transform : transforms) {
|
||||
transform->config->currentVolume = volume;
|
||||
transform->reconfigure();
|
||||
}
|
||||
BELL_LOG(debug, "AudioPipeline", "Volume applied, DSP reconfigured");
|
||||
}
|
||||
|
||||
std::unique_ptr<StreamInfo> AudioPipeline::process(std::unique_ptr<StreamInfo> data) {
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
for (auto &transform : transforms) {
|
||||
data = transform->process(std::move(data));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
120
components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp
Normal file
120
components/spotify/cspot/bell/main/audio-dsp/BellDSP.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include "BellDSP.h"
|
||||
#include <iostream>
|
||||
#include "CentralAudioBuffer.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
BellDSP::FadeEffect::FadeEffect(size_t duration, bool isFadeIn,
|
||||
std::function<void()> onFinish) {
|
||||
this->duration = duration;
|
||||
this->onFinish = onFinish;
|
||||
this->isFadeIn = isFadeIn;
|
||||
}
|
||||
|
||||
void BellDSP::FadeEffect::apply(float* audioData, size_t samples,
|
||||
size_t relativePosition) {
|
||||
float effect = (this->duration - relativePosition) / (float)this->duration;
|
||||
|
||||
if (isFadeIn) {
|
||||
effect = relativePosition / (float)this->duration;
|
||||
}
|
||||
|
||||
for (int x = 0; x <= samples; x++) {
|
||||
audioData[x] *= effect;
|
||||
}
|
||||
|
||||
if (relativePosition + samples > this->duration && onFinish != nullptr) {
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
BellDSP::BellDSP(std::shared_ptr<CentralAudioBuffer> buffer) {
|
||||
this->buffer = buffer;
|
||||
};
|
||||
|
||||
void BellDSP::applyPipeline(std::shared_ptr<AudioPipeline> pipeline) {
|
||||
std::scoped_lock lock(accessMutex);
|
||||
activePipeline = pipeline;
|
||||
}
|
||||
|
||||
void BellDSP::queryInstantEffect(std::unique_ptr<AudioEffect> instantEffect) {
|
||||
this->instantEffect = std::move(instantEffect);
|
||||
samplesSinceInstantQueued = 0;
|
||||
}
|
||||
|
||||
size_t BellDSP::process(uint8_t* data, size_t bytes, int channels,
|
||||
uint32_t sampleRate, BitWidth bitWidth) {
|
||||
if (bytes > 1024 * 2 * channels) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t bytesPerSample = channels * 2;
|
||||
|
||||
size_t samplesLeftInBuffer = buffer->audioBuffer->size() / bytesPerSample;
|
||||
|
||||
// Create a StreamInfo object to pass to the pipeline
|
||||
auto streamInfo = std::make_unique<StreamInfo>();
|
||||
streamInfo->numChannels = channels;
|
||||
streamInfo->sampleRate = static_cast<bell::SampleRate>(sampleRate);
|
||||
streamInfo->bitwidth = bitWidth;
|
||||
streamInfo->numSamples = bytes / channels / 2;
|
||||
|
||||
std::scoped_lock lock(accessMutex);
|
||||
|
||||
int16_t* data16Bit = (int16_t*)data;
|
||||
|
||||
int length16 = bytes / 4;
|
||||
|
||||
for (size_t i = 0; i < length16; i++) {
|
||||
dataLeft[i] = (data16Bit[i * 2] / (float)MAX_INT16); // Normalize left
|
||||
dataRight[i] =
|
||||
(data16Bit[i * 2 + 1] / (float)MAX_INT16); // Normalize right
|
||||
}
|
||||
float* sampleData[] = {&dataLeft[0], &dataRight[0]};
|
||||
streamInfo->data = sampleData;
|
||||
|
||||
if (activePipeline) {
|
||||
streamInfo = activePipeline->process(std::move(streamInfo));
|
||||
}
|
||||
|
||||
if (this->instantEffect != nullptr) {
|
||||
this->instantEffect->apply(dataLeft.data(), length16,
|
||||
samplesSinceInstantQueued);
|
||||
|
||||
if (streamInfo->numSamples > 1) {
|
||||
this->instantEffect->apply(dataRight.data(), length16,
|
||||
samplesSinceInstantQueued);
|
||||
}
|
||||
|
||||
samplesSinceInstantQueued += length16;
|
||||
|
||||
if (this->instantEffect->duration <= samplesSinceInstantQueued) {
|
||||
this->instantEffect = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < length16; i++) {
|
||||
if (dataLeft[i] > 1.0f) {
|
||||
dataLeft[i] = 1.0f;
|
||||
}
|
||||
|
||||
// Data has been downmixed to mono
|
||||
if (streamInfo->numChannels == 1) {
|
||||
|
||||
data16Bit[i] = dataLeft[i] * MAX_INT16; // Denormalize left
|
||||
} else {
|
||||
data16Bit[i * 2] = dataLeft[i] * MAX_INT16; // Denormalize left
|
||||
data16Bit[i * 2 + 1] = dataRight[i] * MAX_INT16; // Denormalize right
|
||||
}
|
||||
}
|
||||
|
||||
if (streamInfo->numChannels == 1) {
|
||||
return bytes / 2;
|
||||
}
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
std::shared_ptr<AudioPipeline> BellDSP::getActivePipeline() {
|
||||
return activePipeline;
|
||||
}
|
||||
466
components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp
Normal file
466
components/spotify/cspot/bell/main/audio-dsp/Biquad.cpp
Normal file
@@ -0,0 +1,466 @@
|
||||
#include "Biquad.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
Biquad::Biquad()
|
||||
{
|
||||
this->filterType = "biquad";
|
||||
}
|
||||
|
||||
void Biquad::sampleRateChanged(uint32_t sampleRate)
|
||||
{
|
||||
this->sampleRate = sampleRate;
|
||||
//this->configure(this->type, this->currentConfig);
|
||||
}
|
||||
|
||||
void Biquad::configure(Type type, std::map<std::string, float> &newConf)
|
||||
{
|
||||
this->type = type;
|
||||
this->currentConfig = newConf;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Type::Free:
|
||||
coeffs[0] = newConf["a1"];
|
||||
coeffs[1] = newConf["a2"];
|
||||
coeffs[2] = newConf["b0"];
|
||||
coeffs[3] = newConf["b1"];
|
||||
coeffs[4] = newConf["b2"];
|
||||
break;
|
||||
case Type::Highpass:
|
||||
highPassCoEffs(newConf["freq"], newConf["q"]);
|
||||
break;
|
||||
case Type::HighpassFO:
|
||||
highPassFOCoEffs(newConf["freq"]);
|
||||
break;
|
||||
case Type::Lowpass:
|
||||
lowPassCoEffs(newConf["freq"], newConf["q"]);
|
||||
break;
|
||||
case Type::LowpassFO:
|
||||
lowPassFOCoEffs(newConf["freq"]);
|
||||
break;
|
||||
case Type::Highshelf:
|
||||
// check if config has slope key
|
||||
if (newConf.find("slope") != newConf.end())
|
||||
{
|
||||
highShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
highShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]);
|
||||
}
|
||||
break;
|
||||
case Type::HighshelfFO:
|
||||
highShelfFOCoEffs(newConf["freq"], newConf["gain"]);
|
||||
break;
|
||||
case Type::Lowshelf:
|
||||
// check if config has slope key
|
||||
if (newConf.find("slope") != newConf.end())
|
||||
{
|
||||
lowShelfCoEffsSlope(newConf["freq"], newConf["gain"], newConf["slope"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
lowShelfCoEffs(newConf["freq"], newConf["gain"], newConf["q"]);
|
||||
}
|
||||
break;
|
||||
case Type::LowshelfFO:
|
||||
lowShelfFOCoEffs(newConf["freq"], newConf["gain"]);
|
||||
break;
|
||||
case Type::Peaking:
|
||||
// check if config has bandwidth key
|
||||
if (newConf.find("bandwidth") != newConf.end())
|
||||
{
|
||||
peakCoEffsBandwidth(newConf["freq"], newConf["gain"], newConf["bandwidth"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
peakCoEffs(newConf["freq"], newConf["gain"], newConf["q"]);
|
||||
}
|
||||
break;
|
||||
case Type::Notch:
|
||||
if (newConf.find("bandwidth") != newConf.end())
|
||||
{
|
||||
notchCoEffsBandwidth(newConf["freq"], newConf["gain"], newConf["bandwidth"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
notchCoEffs(newConf["freq"], newConf["gain"], newConf["q"]);
|
||||
}
|
||||
break;
|
||||
case Type::Bandpass:
|
||||
if (newConf.find("bandwidth") != newConf.end())
|
||||
{
|
||||
bandPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
bandPassCoEffs(newConf["freq"], newConf["q"]);
|
||||
}
|
||||
break;
|
||||
case Type::Allpass:
|
||||
if (newConf.find("bandwidth") != newConf.end())
|
||||
{
|
||||
allPassCoEffsBandwidth(newConf["freq"], newConf["bandwidth"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
allPassCoEffs(newConf["freq"], newConf["q"]);
|
||||
}
|
||||
break;
|
||||
case Type::AllpassFO:
|
||||
allPassFOCoEffs(newConf["freq"]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// coefficients for a high pass biquad filter
|
||||
void Biquad::highPassCoEffs(float f, float q)
|
||||
{
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s / (2 * q);
|
||||
|
||||
float b0 = (1 + c) / 2;
|
||||
float b1 = -(1 + c);
|
||||
float b2 = b0;
|
||||
float a0 = 1 + alpha;
|
||||
float a1 = -2 * c;
|
||||
float a2 = 1 - alpha;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
// coefficients for a high pass first order biquad filter
|
||||
void Biquad::highPassFOCoEffs(float f)
|
||||
{
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float k = tanf(w0 / 2.0);
|
||||
|
||||
float alpha = 1.0 + k;
|
||||
|
||||
float b0 = 1.0 / alpha;
|
||||
float b1 = -1.0 / alpha;
|
||||
float b2 = 0.0;
|
||||
float a0 = 1.0;
|
||||
float a1 = -(1.0 - k) / alpha;
|
||||
float a2 = 0.0;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
// coefficients for a low pass biquad filter
|
||||
void Biquad::lowPassCoEffs(float f, float q)
|
||||
{
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s / (2 * q);
|
||||
|
||||
float b0 = (1 - c) / 2;
|
||||
float b1 = 1 - c;
|
||||
float b2 = b0;
|
||||
float a0 = 1 + alpha;
|
||||
float a1 = -2 * c;
|
||||
float a2 = 1 - alpha;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
// coefficients for a low pass first order biquad filter
|
||||
void Biquad::lowPassFOCoEffs(float f)
|
||||
{
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float k = tanf(w0 / 2.0);
|
||||
|
||||
float alpha = 1.0 + k;
|
||||
|
||||
float b0 = k / alpha;
|
||||
float b1 = k / alpha;
|
||||
float b2 = 0.0;
|
||||
float a0 = 1.0;
|
||||
float a1 = -(1.0 - k) / alpha;
|
||||
float a2 = 0.0;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
// coefficients for a peak biquad filter
|
||||
void Biquad::peakCoEffs(float f, float gain, float q)
|
||||
{
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s / (2 * q);
|
||||
|
||||
float ampl = std::pow(10.0f, gain / 40.0f);
|
||||
float b0 = 1.0 + (alpha * ampl);
|
||||
float b1 = -2.0 * c;
|
||||
float b2 = 1.0 - (alpha * ampl);
|
||||
float a0 = 1 + (alpha / ampl);
|
||||
float a1 = -2 * c;
|
||||
float a2 = 1 - (alpha / ampl);
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
void Biquad::peakCoEffsBandwidth(float f, float gain, float bandwidth)
|
||||
{
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s);
|
||||
|
||||
float ampl = std::pow(10.0f, gain / 40.0f);
|
||||
float b0 = 1.0 + (alpha * ampl);
|
||||
float b1 = -2.0 * c;
|
||||
float b2 = 1.0 - (alpha * ampl);
|
||||
float a0 = 1 + (alpha / ampl);
|
||||
float a1 = -2 * c;
|
||||
float a2 = 1 - (alpha / ampl);
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
void Biquad::highShelfCoEffs(float f, float gain, float q)
|
||||
{
|
||||
float A = std::pow(10.0f, gain / 40.0f);
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s / (2 * q);
|
||||
float beta = s * sqrtf(A) / q;
|
||||
float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta);
|
||||
float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c);
|
||||
float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta);
|
||||
float a0 = (A + 1.0) - (A - 1.0) * c + beta;
|
||||
float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c);
|
||||
float a2 = (A + 1.0) - (A - 1.0) * c - beta;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
void Biquad::highShelfCoEffsSlope(float f, float gain, float slope)
|
||||
{
|
||||
float A = std::pow(10.0f, gain / 40.0f);
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha =
|
||||
s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0);
|
||||
float beta = 2.0 * sqrtf(A) * alpha;
|
||||
float b0 = A * ((A + 1.0) + (A - 1.0) * c + beta);
|
||||
float b1 = -2.0 * A * ((A - 1.0) + (A + 1.0) * c);
|
||||
float b2 = A * ((A + 1.0) + (A - 1.0) * c - beta);
|
||||
float a0 = (A + 1.0) - (A - 1.0) * c + beta;
|
||||
float a1 = 2.0 * ((A - 1.0) - (A + 1.0) * c);
|
||||
float a2 = (A + 1.0) - (A - 1.0) * c - beta;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
void Biquad::highShelfFOCoEffs(float f, float gain)
|
||||
{
|
||||
float A = std::pow(10.0f, gain / 40.0f);
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float tn = tanf(w0 / 2.0);
|
||||
|
||||
float b0 = A * tn + std::pow(A, 2);
|
||||
float b1 = A * tn - std::pow(A, 2);
|
||||
float b2 = 0.0;
|
||||
float a0 = A * tn + 1.0;
|
||||
float a1 = A * tn - 1.0;
|
||||
float a2 = 0.0;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
void Biquad::lowShelfCoEffs(float f, float gain, float q) {
|
||||
float A = std::pow(10.0f, gain / 40.0f);
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float beta = s * sqrtf(A) / q;
|
||||
|
||||
float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta);
|
||||
float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c);
|
||||
float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta);
|
||||
float a0 = (A + 1.0) + (A - 1.0) * c + beta;
|
||||
float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c);
|
||||
float a2 = (A + 1.0) + (A - 1.0) * c - beta;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
void Biquad::lowShelfCoEffsSlope(float f, float gain, float slope) {
|
||||
float A = std::pow(10.0f, gain / 40.0f);
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha =
|
||||
s / 2.0 * sqrtf((A + 1.0 / A) * (1.0 / (slope / 12.0) - 1.0) + 2.0);
|
||||
float beta = 2.0 * sqrtf(A) * alpha;
|
||||
|
||||
float b0 = A * ((A + 1.0) - (A - 1.0) * c + beta);
|
||||
float b1 = 2.0 * A * ((A - 1.0) - (A + 1.0) * c);
|
||||
float b2 = A * ((A + 1.0) - (A - 1.0) * c - beta);
|
||||
float a0 = (A + 1.0) + (A - 1.0) * c + beta;
|
||||
float a1 = -2.0 * ((A - 1.0) + (A + 1.0) * c);
|
||||
float a2 = (A + 1.0) + (A - 1.0) * c - beta;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
void Biquad::lowShelfFOCoEffs(float f, float gain) {
|
||||
float A = std::pow(10.0f, gain / 40.0f);
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float tn = tanf(w0 / 2.0);
|
||||
|
||||
float b0 = std::pow(A, 2) * tn + A;
|
||||
float b1 = std::pow(A, 2) * tn - A;
|
||||
float b2 = 0.0;
|
||||
float a0 = tn + A;
|
||||
float a1 = tn - A;
|
||||
float a2 = 0.0;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
void Biquad::notchCoEffs(float f, float gain, float q) {
|
||||
float A = std::pow(10.0f, gain / 40.0f);
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s / (2.0 * q);
|
||||
|
||||
float b0 = 1.0;
|
||||
float b1 = -2.0 * c;
|
||||
float b2 = 1.0;
|
||||
float a0 = 1.0 + alpha;
|
||||
float a1 = -2.0 * c;
|
||||
float a2 = 1.0 - alpha;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
void Biquad::notchCoEffsBandwidth(float f, float gain, float bandwidth) {
|
||||
float A = std::pow(10.0f, gain / 40.0f);
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s);
|
||||
|
||||
float b0 = 1.0;
|
||||
float b1 = -2.0 * c;
|
||||
float b2 = 1.0;
|
||||
float a0 = 1.0 + alpha;
|
||||
float a1 = -2.0 * c;
|
||||
float a2 = 1.0 - alpha;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
void Biquad::bandPassCoEffs(float f, float q) {
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s / (2.0 * q);
|
||||
|
||||
float b0 = alpha;
|
||||
float b1 = 0.0;
|
||||
float b2 = -alpha;
|
||||
float a0 = 1.0 + alpha;
|
||||
float a1 = -2.0 * c;
|
||||
float a2 = 1.0 - alpha;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
void Biquad::bandPassCoEffsBandwidth(float f, float bandwidth) {
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s);
|
||||
|
||||
float b0 = alpha;
|
||||
float b1 = 0.0;
|
||||
float b2 = -alpha;
|
||||
float a0 = 1.0 + alpha;
|
||||
float a1 = -2.0 * c;
|
||||
float a2 = 1.0 - alpha;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
void Biquad::allPassCoEffs(float f, float q) {
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s / (2.0 * q);
|
||||
|
||||
float b0 = 1.0 - alpha;
|
||||
float b1 = -2.0 * c;
|
||||
float b2 = 1.0 + alpha;
|
||||
float a0 = 1.0 + alpha;
|
||||
float a1 = -2.0 * c;
|
||||
float a2 = 1.0 - alpha;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
void Biquad::allPassCoEffsBandwidth(float f, float bandwidth) {
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float c = cosf(w0);
|
||||
float s = sinf(w0);
|
||||
float alpha = s * sinh(logf(2.0) / 2.0 * bandwidth * w0 / s);
|
||||
|
||||
float b0 = 1.0 - alpha;
|
||||
float b1 = -2.0 * c;
|
||||
float b2 = 1.0 + alpha;
|
||||
float a0 = 1.0 + alpha;
|
||||
float a1 = -2.0 * c;
|
||||
float a2 = 1.0 - alpha;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
void Biquad::allPassFOCoEffs(float f) {
|
||||
float w0 = 2 * M_PI * f / this->sampleRate;
|
||||
float tn = tanf(w0 / 2.0);
|
||||
|
||||
float alpha = (tn + 1.0) / (tn - 1.0);
|
||||
|
||||
float b0 = 1.0;
|
||||
float b1 = alpha;
|
||||
float b2 = 0.0;
|
||||
float a0 = alpha;
|
||||
float a1 = 1.0;
|
||||
float a2 = 0.0;
|
||||
|
||||
this->normalizeCoEffs(a0, a1, a2, b0, b1, b2);
|
||||
}
|
||||
|
||||
void Biquad::normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, float b2)
|
||||
{
|
||||
coeffs[0] = b0 / a0;
|
||||
coeffs[1] = b1 / a0;
|
||||
coeffs[2] = b2 / a0;
|
||||
coeffs[3] = a1 / a0;
|
||||
coeffs[4] = a2 / a0;
|
||||
}
|
||||
|
||||
std::unique_ptr<StreamInfo> Biquad::process(std::unique_ptr<StreamInfo> stream)
|
||||
{
|
||||
std::scoped_lock lock(accessMutex);
|
||||
|
||||
auto input = stream->data[this->channel];
|
||||
auto numSamples = stream->numSamples;
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
dsps_biquad_f32_ae32(input, input, numSamples, coeffs, w);
|
||||
#else
|
||||
// Apply the set coefficients
|
||||
for (int i = 0; i < numSamples; i++)
|
||||
{
|
||||
float d0 = input[i] - coeffs[3] * w[0] - coeffs[4] * w[1];
|
||||
input[i] = coeffs[0] * d0 + coeffs[1] * w[0] + coeffs[2] * w[1];
|
||||
w[1] = w[0];
|
||||
w[0] = d0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return stream;
|
||||
};
|
||||
109
components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp
Normal file
109
components/spotify/cspot/bell/main/audio-dsp/BiquadCombo.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "BiquadCombo.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
BiquadCombo::BiquadCombo()
|
||||
{
|
||||
}
|
||||
|
||||
void BiquadCombo::sampleRateChanged(uint32_t sampleRate)
|
||||
{
|
||||
for (auto &biquad : biquads)
|
||||
{
|
||||
biquad->sampleRateChanged(sampleRate);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<float> BiquadCombo::calculateBWQ(int order)
|
||||
{
|
||||
|
||||
std::vector<float> qValues;
|
||||
for (int n = 0; n < order / 2; n++)
|
||||
{
|
||||
float q = 1.0f / (2.0f * sinf(M_PI / order * (((float)n) + 0.5)));
|
||||
qValues.push_back(q);
|
||||
}
|
||||
|
||||
if (order % 2 > 0)
|
||||
{
|
||||
qValues.push_back(-1.0);
|
||||
}
|
||||
|
||||
printf("%d\n", qValues.size());
|
||||
|
||||
return qValues;
|
||||
}
|
||||
|
||||
std::vector<float> BiquadCombo::calculateLRQ(int order)
|
||||
{
|
||||
auto qValues = calculateBWQ(order / 2);
|
||||
|
||||
if (order % 4 > 0)
|
||||
{
|
||||
qValues.pop_back();
|
||||
qValues.insert(qValues.end(), qValues.begin(), qValues.end());
|
||||
qValues.push_back(0.5);
|
||||
}
|
||||
else
|
||||
{
|
||||
qValues.insert(qValues.end(), qValues.begin(), qValues.end());
|
||||
}
|
||||
|
||||
return qValues;
|
||||
}
|
||||
|
||||
void BiquadCombo::butterworth(float freq, int order, FilterType type)
|
||||
{
|
||||
std::vector<float> qValues = calculateBWQ(order);
|
||||
for (auto &q : qValues)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
void BiquadCombo::linkwitzRiley(float freq, int order, FilterType type)
|
||||
{
|
||||
std::vector<float> qValues = calculateLRQ(order);
|
||||
for (auto &q : qValues)
|
||||
{
|
||||
auto filter = std::make_unique<Biquad>();
|
||||
filter->channel = channel;
|
||||
|
||||
auto config = std::map<std::string, float>();
|
||||
config["freq"] = freq;
|
||||
config["q"] = q;
|
||||
|
||||
if (q >= 0.0)
|
||||
{
|
||||
if (type == FilterType::Highpass)
|
||||
{
|
||||
filter->configure(Biquad::Type::Highpass, config);
|
||||
}
|
||||
else
|
||||
{
|
||||
filter->configure(Biquad::Type::Lowpass, config);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type == FilterType::Highpass)
|
||||
{
|
||||
filter->configure(Biquad::Type::HighpassFO, config);
|
||||
}
|
||||
else
|
||||
{
|
||||
filter->configure(Biquad::Type::LowpassFO, config);
|
||||
}
|
||||
}
|
||||
|
||||
this->biquads.push_back(std::move(filter));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<StreamInfo> BiquadCombo::process(std::unique_ptr<StreamInfo> data) {
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
for (auto &transform : this->biquads) {
|
||||
data = transform->process(std::move(data));
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
88
components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp
Normal file
88
components/spotify/cspot/bell/main/audio-dsp/Compressor.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "Compressor.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
float log2f_approx(float X) {
|
||||
float Y, F;
|
||||
int E;
|
||||
F = frexpf(fabsf(X), &E);
|
||||
Y = 1.23149591368684f;
|
||||
Y *= F;
|
||||
Y += -4.11852516267426f;
|
||||
Y *= F;
|
||||
Y += 6.02197014179219f;
|
||||
Y *= F;
|
||||
Y += -3.13396450166353f;
|
||||
Y += E;
|
||||
return (Y);
|
||||
}
|
||||
|
||||
Compressor::Compressor() {}
|
||||
|
||||
void Compressor::sumChannels(std::unique_ptr<StreamInfo> &data) {
|
||||
tmp.resize(data->numSamples);
|
||||
for (int i = 0; i < data->numSamples; i++) {
|
||||
float sum = 0.0f;
|
||||
for (auto &channel : channels) {
|
||||
sum += data->data[channel][i];
|
||||
}
|
||||
tmp[i] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
void Compressor::calLoudness() {
|
||||
for (auto &value : tmp) {
|
||||
value = 20 * log10f_fast(std::abs(value) + 1.0e-9f);
|
||||
if (value >= lastLoudness) {
|
||||
value = attack * lastLoudness + (1.0 - attack) * value;
|
||||
} else {
|
||||
value = release * lastLoudness + (1.0 - release) * value;
|
||||
}
|
||||
|
||||
lastLoudness = value;
|
||||
}
|
||||
}
|
||||
|
||||
void Compressor::calGain() {
|
||||
for (auto &value : tmp) {
|
||||
if (value > threshold) {
|
||||
value = -(value - threshold) * (factor - 1.0) / factor;
|
||||
} else {
|
||||
value = 0.0f;
|
||||
}
|
||||
|
||||
value += makeupGain;
|
||||
|
||||
// convert to linear
|
||||
value = pow10f(value / 20.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void Compressor::applyGain(std::unique_ptr<StreamInfo> &data) {
|
||||
for (int i = 0; i < data->numSamples; i++) {
|
||||
for (auto &channel : channels) {
|
||||
data->data[channel][i] *= tmp[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Compressor::configure(std::vector<int> channels, float attack,
|
||||
float release, float threshold, float factor,
|
||||
float makeupGain) {
|
||||
this->channels = channels;
|
||||
this->attack = expf(-1000.0 / this->sampleRate / attack);
|
||||
this->release = expf(-1000.0 / this->sampleRate / release);
|
||||
this->threshold = threshold;
|
||||
this->factor = factor;
|
||||
this->makeupGain = makeupGain;
|
||||
}
|
||||
|
||||
std::unique_ptr<StreamInfo> Compressor::process(
|
||||
std::unique_ptr<StreamInfo> data) {
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
sumChannels(data);
|
||||
calLoudness();
|
||||
calGain();
|
||||
applyGain(data);
|
||||
return data;
|
||||
}
|
||||
31
components/spotify/cspot/bell/main/audio-dsp/Gain.cpp
Normal file
31
components/spotify/cspot/bell/main/audio-dsp/Gain.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#include "Gain.h"
|
||||
|
||||
using namespace bell;
|
||||
|
||||
Gain::Gain() : AudioTransform()
|
||||
{
|
||||
this->gainFactor = 1.0f;
|
||||
this->filterType = "gain";
|
||||
}
|
||||
|
||||
void Gain::configure(std::vector<int> channels, float gainDB)
|
||||
{
|
||||
this->channels = channels;
|
||||
this->gainDb = gainDB;
|
||||
this->gainFactor = std::pow(10.0f, gainDB / 20.0f);
|
||||
}
|
||||
|
||||
std::unique_ptr<StreamInfo> Gain::process(std::unique_ptr<StreamInfo> data)
|
||||
{
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
for (int i = 0; i < data->numSamples; i++)
|
||||
{
|
||||
// Apply gain to all channels
|
||||
for (auto &channel : channels)
|
||||
{
|
||||
data->data[channel][i] *= gainFactor;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cJSON.h>
|
||||
|
||||
#include "AudioTransform.h"
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class AudioMixer : public bell::AudioTransform
|
||||
{
|
||||
public:
|
||||
enum DownmixMode
|
||||
{
|
||||
DEFAULT
|
||||
};
|
||||
|
||||
struct MixerConfig
|
||||
{
|
||||
std::vector<int> source;
|
||||
int destination;
|
||||
};
|
||||
|
||||
AudioMixer();
|
||||
~AudioMixer(){};
|
||||
// Amount of channels in the input
|
||||
int from;
|
||||
|
||||
// Amount of channels in the output
|
||||
int to;
|
||||
|
||||
// Configuration of each channels in the mixer
|
||||
std::vector<MixerConfig> mixerConfig;
|
||||
|
||||
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override;
|
||||
|
||||
void reconfigure() override
|
||||
{
|
||||
}
|
||||
|
||||
void fromJSON(cJSON *json)
|
||||
{
|
||||
cJSON *mappedChannels = cJSON_GetObjectItem(json, "mapped_channels");
|
||||
|
||||
if (mappedChannels == NULL || !cJSON_IsArray(mappedChannels))
|
||||
{
|
||||
throw std::invalid_argument("Mixer configuration invalid");
|
||||
}
|
||||
|
||||
this->mixerConfig = std::vector<MixerConfig>();
|
||||
|
||||
cJSON *iterator = NULL;
|
||||
cJSON_ArrayForEach(iterator, mappedChannels)
|
||||
{
|
||||
std::vector<int> sources(0);
|
||||
cJSON *iteratorNested = NULL;
|
||||
cJSON_ArrayForEach(iteratorNested, cJSON_GetObjectItem(iterator, "source"))
|
||||
{
|
||||
sources.push_back(iteratorNested->valueint);
|
||||
}
|
||||
|
||||
int destination = cJSON_GetObjectItem(iterator, "destination")->valueint;
|
||||
|
||||
this->mixerConfig.push_back(MixerConfig{
|
||||
.source = sources,
|
||||
.destination = destination
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<uint8_t> sources(0);
|
||||
|
||||
for (auto &config : mixerConfig)
|
||||
{
|
||||
|
||||
for (auto &source : config.source)
|
||||
{
|
||||
if (std::find(sources.begin(), sources.end(), source) == sources.end())
|
||||
{
|
||||
sources.push_back(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this->from = sources.size();
|
||||
this->to = mixerConfig.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "AudioTransform.h"
|
||||
#include "StreamInfo.h"
|
||||
#include <memory>
|
||||
#include "Gain.h"
|
||||
#include <mutex>
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class AudioPipeline
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<Gain> headroomGainTransform;
|
||||
|
||||
public:
|
||||
AudioPipeline();
|
||||
~AudioPipeline(){};
|
||||
|
||||
std::mutex accessMutex;
|
||||
std::vector<std::shared_ptr<AudioTransform>> transforms;
|
||||
|
||||
void recalculateHeadroom();
|
||||
void addTransform(std::shared_ptr<AudioTransform> transform);
|
||||
void volumeUpdated(int volume);
|
||||
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data);
|
||||
};
|
||||
}; // namespace bell
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include "StreamInfo.h"
|
||||
#include "TransformConfig.h"
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class AudioTransform
|
||||
{
|
||||
protected:
|
||||
std::mutex accessMutex;
|
||||
|
||||
public:
|
||||
virtual std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) = 0;
|
||||
virtual void sampleRateChanged(uint32_t sampleRate){};
|
||||
virtual float calculateHeadroom() { return 0; };
|
||||
|
||||
virtual void reconfigure() {};
|
||||
|
||||
std::string filterType;
|
||||
std::unique_ptr<TransformConfig> config;
|
||||
|
||||
AudioTransform() = default;
|
||||
virtual ~AudioTransform() = default;
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include "AudioPipeline.h"
|
||||
#include "CentralAudioBuffer.h"
|
||||
|
||||
namespace bell {
|
||||
#define MAX_INT16 32767
|
||||
|
||||
class BellDSP {
|
||||
public:
|
||||
BellDSP(std::shared_ptr<CentralAudioBuffer> centralAudioBuffer);
|
||||
~BellDSP() {};
|
||||
|
||||
class AudioEffect {
|
||||
public:
|
||||
AudioEffect() = default;
|
||||
~AudioEffect() = default;
|
||||
size_t duration;
|
||||
virtual void apply(float* sampleData, size_t samples, size_t relativePosition) = 0;
|
||||
};
|
||||
|
||||
class FadeEffect: public AudioEffect {
|
||||
private:
|
||||
std::function<void()> onFinish;
|
||||
bool isFadeIn;
|
||||
public:
|
||||
FadeEffect(size_t duration, bool isFadeIn, std::function<void()> onFinish = nullptr);
|
||||
~FadeEffect() {};
|
||||
|
||||
void apply(float* sampleData, size_t samples, size_t relativePosition);
|
||||
};
|
||||
|
||||
void applyPipeline(std::shared_ptr<AudioPipeline> pipeline);
|
||||
void queryInstantEffect(std::unique_ptr<AudioEffect> instantEffect);
|
||||
|
||||
std::shared_ptr<AudioPipeline> getActivePipeline();
|
||||
|
||||
size_t process(uint8_t* data, size_t bytes, int channels,
|
||||
uint32_t sampleRate, BitWidth bitWidth);
|
||||
|
||||
private:
|
||||
std::shared_ptr<AudioPipeline> activePipeline;
|
||||
std::shared_ptr<CentralAudioBuffer> buffer;
|
||||
std::mutex accessMutex;
|
||||
std::vector<float> dataLeft = std::vector<float>(1024);
|
||||
std::vector<float> dataRight = std::vector<float>(1024);
|
||||
|
||||
|
||||
std::unique_ptr<AudioEffect> underflowEffect = nullptr;
|
||||
std::unique_ptr<AudioEffect> startEffect = nullptr;
|
||||
std::unique_ptr<AudioEffect> instantEffect = nullptr;
|
||||
|
||||
size_t samplesSinceInstantQueued;
|
||||
};
|
||||
}; // namespace bell
|
||||
158
components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h
Normal file
158
components/spotify/cspot/bell/main/audio-dsp/include/Biquad.h
Normal file
@@ -0,0 +1,158 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "AudioTransform.h"
|
||||
extern "C" int dsps_biquad_f32_ae32(const float *input, float *output, int len, float *coef, float *w);
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class Biquad : public bell::AudioTransform
|
||||
{
|
||||
public:
|
||||
Biquad();
|
||||
~Biquad(){};
|
||||
|
||||
enum class Type
|
||||
{
|
||||
Free,
|
||||
Highpass,
|
||||
Lowpass,
|
||||
HighpassFO,
|
||||
LowpassFO,
|
||||
|
||||
Peaking,
|
||||
Highshelf,
|
||||
HighshelfFO,
|
||||
Lowshelf,
|
||||
LowshelfFO,
|
||||
Notch,
|
||||
Bandpass,
|
||||
Allpass,
|
||||
AllpassFO
|
||||
};
|
||||
|
||||
std::map<std::string, float> currentConfig;
|
||||
|
||||
std::unordered_map<std::string, Type> const strMapType = {
|
||||
{"free", Type::Free},
|
||||
{"highpass", Type::Highpass},
|
||||
{"lowpass", Type::Lowpass},
|
||||
{"highpass_fo", Type::HighpassFO},
|
||||
{"lowpass_fo", Type::LowpassFO},
|
||||
{"peaking", Type::Peaking},
|
||||
{"highshelf", Type::Highshelf},
|
||||
{"highshelf_fo", Type::HighpassFO},
|
||||
{"lowshelf", Type::Lowshelf},
|
||||
{"lowshelf_fo", Type::LowpassFO},
|
||||
{"notch", Type::Notch},
|
||||
{"bandpass", Type::Bandpass},
|
||||
{"allpass", Type::Allpass},
|
||||
{"allpass_fo", Type::AllpassFO},
|
||||
};
|
||||
|
||||
float freq, q, gain;
|
||||
int channel;
|
||||
Biquad::Type type;
|
||||
|
||||
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override;
|
||||
|
||||
void configure(Type type, std::map<std::string, float> &config);
|
||||
|
||||
void sampleRateChanged(uint32_t sampleRate) override;
|
||||
|
||||
void reconfigure() override
|
||||
{
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
std::map<std::string, float> biquadConfig;
|
||||
this->channel = config->getChannels()[0];
|
||||
|
||||
float invalid = -0x7C;
|
||||
|
||||
auto type = config->getString("biquad_type");
|
||||
float bandwidth = config->getFloat("bandwidth", false, invalid);
|
||||
float slope = config->getFloat("slope", false, invalid);
|
||||
float gain = config->getFloat("gain", false, invalid);
|
||||
float frequency = config->getFloat("frequency", false, invalid);
|
||||
float q = config->getFloat("q", false, invalid);
|
||||
|
||||
if (currentConfig["bandwidth"] == bandwidth &&
|
||||
currentConfig["slope"] == slope &&
|
||||
currentConfig["gain"] == gain &&
|
||||
currentConfig["frequency"] == frequency &&
|
||||
currentConfig["q"] == q)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (bandwidth != invalid)
|
||||
biquadConfig["bandwidth"] = bandwidth;
|
||||
if (slope != invalid)
|
||||
biquadConfig["slope"] = slope;
|
||||
if (gain != invalid)
|
||||
biquadConfig["gain"] = gain;
|
||||
if (frequency != invalid)
|
||||
biquadConfig["freq"] = frequency;
|
||||
if (q != invalid)
|
||||
biquadConfig["q"] = q;
|
||||
|
||||
if (type == "free")
|
||||
{
|
||||
biquadConfig["a1"] = config->getFloat("a1");
|
||||
biquadConfig["a2"] = config->getFloat("a2");
|
||||
biquadConfig["b0"] = config->getFloat("b0");
|
||||
biquadConfig["b1"] = config->getFloat("b1");
|
||||
biquadConfig["b2"] = config->getFloat("b2");
|
||||
}
|
||||
|
||||
auto typeElement = strMapType.find(type);
|
||||
if (typeElement != strMapType.end())
|
||||
{
|
||||
this->configure(typeElement->second, biquadConfig);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::invalid_argument("No biquad of type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
float coeffs[5];
|
||||
float w[2] = {1.0, 1.0};
|
||||
|
||||
float sampleRate = 44100;
|
||||
|
||||
// Generator methods for different filter types
|
||||
void highPassCoEffs(float f, float q);
|
||||
void highPassFOCoEffs(float f);
|
||||
void lowPassCoEffs(float f, float q);
|
||||
void lowPassFOCoEffs(float f);
|
||||
|
||||
void peakCoEffs(float f, float gain, float q);
|
||||
void peakCoEffsBandwidth(float f, float gain, float bandwidth);
|
||||
|
||||
void highShelfCoEffs(float f, float gain, float q);
|
||||
void highShelfCoEffsSlope(float f, float gain, float slope);
|
||||
void highShelfFOCoEffs(float f, float gain);
|
||||
|
||||
void lowShelfCoEffs(float f, float gain, float q);
|
||||
void lowShelfCoEffsSlope(float f, float gain, float slope);
|
||||
void lowShelfFOCoEffs(float f, float gain);
|
||||
|
||||
void notchCoEffs(float f, float gain, float q);
|
||||
void notchCoEffsBandwidth(float f, float gain, float bandwidth);
|
||||
|
||||
void bandPassCoEffs(float f, float q);
|
||||
void bandPassCoEffsBandwidth(float f, float bandwidth);
|
||||
|
||||
void allPassCoEffs(float f, float q);
|
||||
void allPassCoEffsBandwidth(float f, float bandwidth);
|
||||
void allPassFOCoEffs(float f);
|
||||
|
||||
void normalizeCoEffs(float a0, float a1, float a2, float b0, float b1, float b2);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
|
||||
#include "Biquad.h"
|
||||
#include "AudioTransform.h"
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class BiquadCombo : public bell::AudioTransform
|
||||
{
|
||||
private:
|
||||
std::vector<std::unique_ptr<bell::Biquad>> biquads;
|
||||
|
||||
// Calculates Q values for Nth order Butterworth / Linkwitz-Riley filters
|
||||
std::vector<float> calculateBWQ(int order);
|
||||
std::vector<float> calculateLRQ(int order);
|
||||
|
||||
public:
|
||||
BiquadCombo();
|
||||
~BiquadCombo(){};
|
||||
int channel;
|
||||
|
||||
std::map<std::string, float> paramCache = {
|
||||
{"order", 0.0f},
|
||||
{"frequency", 0.0f}
|
||||
};
|
||||
|
||||
enum class FilterType
|
||||
{
|
||||
Highpass,
|
||||
Lowpass
|
||||
};
|
||||
|
||||
void linkwitzRiley(float freq, int order, FilterType type);
|
||||
void butterworth(float freq, int order, FilterType type);
|
||||
|
||||
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override;
|
||||
void sampleRateChanged(uint32_t sampleRate) override;
|
||||
|
||||
void reconfigure() override
|
||||
{
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
|
||||
float freq = config->getFloat("frequency");
|
||||
int order = config->getInt("order");
|
||||
|
||||
if (paramCache["frequency"] == freq && paramCache["order"] == order)
|
||||
{
|
||||
return;
|
||||
} else {
|
||||
paramCache["frequency"] = freq;
|
||||
paramCache["order"] = order;
|
||||
}
|
||||
|
||||
this->channel = config->getChannels()[0];
|
||||
this->biquads = std::vector<std::unique_ptr<bell::Biquad>>();
|
||||
auto type = config->getString("combo_type");
|
||||
if (type == "lr_lowpass")
|
||||
{
|
||||
this->linkwitzRiley(freq, order, FilterType::Lowpass);
|
||||
}
|
||||
else if (type == "lr_highpass")
|
||||
{
|
||||
this->linkwitzRiley(freq, order, FilterType::Highpass);
|
||||
}
|
||||
else if (type == "bw_highpass")
|
||||
{
|
||||
this->butterworth(freq, order, FilterType::Highpass);
|
||||
}
|
||||
else if (type == "bw_lowpass")
|
||||
{
|
||||
this->butterworth(freq, order, FilterType::Highpass);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw std::invalid_argument("Invalid combo filter type");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,172 @@
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <functional>
|
||||
|
||||
#include "BellUtils.h"
|
||||
#include "CircularBuffer.h"
|
||||
#include "StreamInfo.h"
|
||||
#include "WrappedSemaphore.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define __attribute__(X)
|
||||
#endif
|
||||
|
||||
typedef std::function<void(std::string)> shutdownEventHandler;
|
||||
|
||||
namespace bell {
|
||||
class CentralAudioBuffer {
|
||||
private:
|
||||
std::mutex accessMutex;
|
||||
|
||||
std::atomic<bool> isLocked = false;
|
||||
std::mutex dataAccessMutex;
|
||||
|
||||
public:
|
||||
static const size_t PCM_CHUNK_SIZE = 4096;
|
||||
std::unique_ptr<bell::WrappedSemaphore> chunkReady;
|
||||
|
||||
// Audio marker for track change detection, and DSP autoconfig
|
||||
struct AudioChunk {
|
||||
// Timeval
|
||||
int32_t sec;
|
||||
int32_t usec;
|
||||
|
||||
// Unique track hash, used for track change detection
|
||||
size_t trackHash;
|
||||
|
||||
// Audio format
|
||||
uint32_t sampleRate;
|
||||
uint8_t channels;
|
||||
uint8_t bitWidth;
|
||||
|
||||
// PCM data size
|
||||
size_t pcmSize;
|
||||
|
||||
// PCM data
|
||||
uint8_t pcmData[PCM_CHUNK_SIZE];
|
||||
} __attribute__((packed));
|
||||
|
||||
CentralAudioBuffer(size_t chunks) {
|
||||
audioBuffer = std::make_shared<CircularBuffer>(chunks * sizeof(AudioChunk));
|
||||
chunkReady = std::make_unique<bell::WrappedSemaphore>(50);
|
||||
}
|
||||
|
||||
std::shared_ptr<bell::CircularBuffer> audioBuffer;
|
||||
uint32_t currentSampleRate = 44100;
|
||||
|
||||
/**
|
||||
* Returns current sample rate
|
||||
* @return sample rate
|
||||
*/
|
||||
uint32_t getSampleRate() { return currentSampleRate; }
|
||||
|
||||
/**
|
||||
* Clears input buffer, to be called for track change and such
|
||||
*/
|
||||
void clearBuffer() {
|
||||
std::scoped_lock lock(this->dataAccessMutex);
|
||||
//size_t exceptSize = currentSampleRate + (sizeof(AudioChunk) - (currentSampleRate % sizeof(AudioChunk)));
|
||||
audioBuffer->emptyBuffer();
|
||||
}
|
||||
|
||||
void emptyCompletely() {
|
||||
std::scoped_lock lock(this->dataAccessMutex);
|
||||
audioBuffer->emptyBuffer();
|
||||
}
|
||||
|
||||
bool hasAtLeast(size_t chunks) {
|
||||
return this->audioBuffer->size() >= chunks * sizeof(AudioChunk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locks access to audio buffer. Call after starting playback
|
||||
*/
|
||||
void lockAccess() {
|
||||
if (!isLocked) {
|
||||
clearBuffer();
|
||||
this->accessMutex.lock();
|
||||
isLocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Frees access to the audio buffer. Call during shutdown
|
||||
*/
|
||||
void unlockAccess() {
|
||||
if (isLocked) {
|
||||
clearBuffer();
|
||||
this->accessMutex.unlock();
|
||||
isLocked = false;
|
||||
}
|
||||
}
|
||||
|
||||
AudioChunk currentChunk = { };
|
||||
bool hasChunk = false;
|
||||
|
||||
AudioChunk lastReadChunk = { };
|
||||
|
||||
AudioChunk* readChunk() {
|
||||
std::scoped_lock lock(this->dataAccessMutex);
|
||||
if (audioBuffer->size() < sizeof(AudioChunk)) {
|
||||
lastReadChunk.pcmSize = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto readBytes =
|
||||
audioBuffer->read((uint8_t*)&lastReadChunk, sizeof(AudioChunk));
|
||||
currentSampleRate = static_cast<uint32_t>(lastReadChunk.sampleRate);
|
||||
return &lastReadChunk;
|
||||
}
|
||||
|
||||
size_t writePCM(const uint8_t* data, size_t dataSize, size_t hash,
|
||||
uint32_t sampleRate = 44100, uint8_t channels = 2,
|
||||
BitWidth bitWidth = BitWidth::BW_16, int32_t sec = 0,
|
||||
int32_t usec = 0) {
|
||||
std::scoped_lock lock(this->dataAccessMutex);
|
||||
if (hasChunk && (currentChunk.trackHash != hash ||
|
||||
currentChunk.pcmSize >= PCM_CHUNK_SIZE)) {
|
||||
|
||||
if ((audioBuffer->size() - audioBuffer->capacity()) <
|
||||
sizeof(AudioChunk)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Track changed or buf full, return current chunk
|
||||
hasChunk = false;
|
||||
this->audioBuffer->write((uint8_t*)¤tChunk, sizeof(AudioChunk));
|
||||
// this->chunkReady->give();
|
||||
}
|
||||
|
||||
// New chunk requested, initialize
|
||||
if (!hasChunk) {
|
||||
currentChunk.trackHash = hash;
|
||||
currentChunk.sampleRate = sampleRate;
|
||||
currentChunk.channels = channels;
|
||||
currentChunk.bitWidth = 16;
|
||||
currentChunk.sec = sec;
|
||||
currentChunk.usec = usec;
|
||||
currentChunk.pcmSize = 0;
|
||||
hasChunk = true;
|
||||
}
|
||||
|
||||
// Calculate how much data we can write
|
||||
size_t toWriteSize = dataSize;
|
||||
|
||||
if (currentChunk.pcmSize + toWriteSize > PCM_CHUNK_SIZE) {
|
||||
toWriteSize = PCM_CHUNK_SIZE - currentChunk.pcmSize;
|
||||
}
|
||||
|
||||
// Copy it over :)
|
||||
memcpy(currentChunk.pcmData + currentChunk.pcmSize, data, toWriteSize);
|
||||
currentChunk.pcmSize += toWriteSize;
|
||||
|
||||
return toWriteSize;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace bell
|
||||
@@ -0,0 +1,103 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <math.h>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <map>
|
||||
|
||||
#include "Biquad.h"
|
||||
#include "AudioTransform.h"
|
||||
|
||||
#define pow10f(x) expf(2.302585092994046f*x)
|
||||
|
||||
// This is a fast approximation to log2()
|
||||
// Y = C[0]*F*F*F + C[1]*F*F + C[2]*F + C[3] + E;
|
||||
float log2f_approx(float X);
|
||||
|
||||
#define log10f_fast(x) (log2f_approx(x)*0.3010299956639812f)
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class Compressor : public bell::AudioTransform
|
||||
{
|
||||
private:
|
||||
std::vector<int> channels;
|
||||
std::vector<float> tmp;
|
||||
|
||||
std::map<std::string, float> paramCache;
|
||||
|
||||
float attack;
|
||||
float release;
|
||||
float threshold;
|
||||
float factor;
|
||||
float clipLimit;
|
||||
float makeupGain;
|
||||
|
||||
float lastLoudness = -100.0f;
|
||||
|
||||
float sampleRate = 44100;
|
||||
|
||||
public:
|
||||
Compressor();
|
||||
~Compressor(){};
|
||||
|
||||
void configure(std::vector<int> channels, float attack, float release, float threshold, float factor, float makeupGain);
|
||||
|
||||
void sumChannels(std::unique_ptr<StreamInfo> &data);
|
||||
void calLoudness();
|
||||
void calGain();
|
||||
|
||||
void applyGain(std::unique_ptr<StreamInfo> &data);
|
||||
|
||||
void reconfigure() override
|
||||
{
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
auto newChannels = config->getChannels();
|
||||
|
||||
float newAttack = config->getFloat("attack");
|
||||
float newRelease = config->getFloat("release");
|
||||
float newThreshold = config->getFloat("threshold");
|
||||
float newFactor = config->getFloat("factor");
|
||||
float newMakeupGain = config->getFloat("makeup_gain");
|
||||
|
||||
if (paramCache["attack"] == newAttack &&
|
||||
paramCache["release"] == newRelease &&
|
||||
paramCache["threshold"] == newThreshold &&
|
||||
paramCache["factor"] == newFactor &&
|
||||
paramCache["makeup_gain"] == newMakeupGain)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
paramCache["attack"] = newAttack;
|
||||
paramCache["release"] = newRelease;
|
||||
paramCache["threshold"] = newThreshold;
|
||||
paramCache["factor"] = newFactor;
|
||||
paramCache["makeup_gain"] = newMakeupGain;
|
||||
}
|
||||
|
||||
this->configure(newChannels, newAttack, newRelease, newThreshold, newFactor, newMakeupGain);
|
||||
}
|
||||
|
||||
// void fromJSON(cJSON* json) override {
|
||||
// // get field channels
|
||||
// channels = jsonGetChannels(json);
|
||||
// float attack = jsonGetNumber<float>(json, "attack", false, 0);
|
||||
// float release = jsonGetNumber<float>(json, "release", false, 0);
|
||||
// float factor = jsonGetNumber<float>(json, "factor", false, 4);
|
||||
// float makeupGain = jsonGetNumber<float>(json, "makeup_gain", false, 0);
|
||||
// float threshold = jsonGetNumber<float>(json, "threshold", false, 0);
|
||||
|
||||
// this->configure(attack, release, clipLimit, threshold, factor, makeupGain);
|
||||
// }
|
||||
|
||||
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override;
|
||||
void sampleRateChanged(uint32_t sampleRate) override { this->sampleRate = sampleRate; };
|
||||
};
|
||||
};
|
||||
40
components/spotify/cspot/bell/main/audio-dsp/include/Gain.h
Normal file
40
components/spotify/cspot/bell/main/audio-dsp/include/Gain.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <mutex>
|
||||
#include <iostream>
|
||||
|
||||
#include "AudioTransform.h"
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class Gain : public bell::AudioTransform
|
||||
{
|
||||
private:
|
||||
float gainFactor = 1.0f;
|
||||
|
||||
std::vector<int> channels;
|
||||
|
||||
public:
|
||||
Gain();
|
||||
~Gain() {};
|
||||
|
||||
float gainDb = 0.0;
|
||||
|
||||
void configure(std::vector<int> channels, float gainDB);
|
||||
|
||||
std::unique_ptr<StreamInfo> process(std::unique_ptr<StreamInfo> data) override;
|
||||
|
||||
void reconfigure() override {
|
||||
std::scoped_lock lock(this->accessMutex);
|
||||
float gain = config->getFloat("gain");
|
||||
this->channels = config->getChannels();
|
||||
|
||||
if (gainDb == gain) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->configure(channels, gain);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include "TransformConfig.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class JSONTransformConfig : public bell::TransformConfig
|
||||
{
|
||||
private:
|
||||
cJSON *json;
|
||||
|
||||
public:
|
||||
JSONTransformConfig(cJSON *body)
|
||||
{
|
||||
this->json = body;
|
||||
};
|
||||
~JSONTransformConfig(){};
|
||||
|
||||
std::string rawGetString(const std::string &field) override
|
||||
{
|
||||
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
|
||||
|
||||
if (value != NULL && cJSON_IsString(value))
|
||||
{
|
||||
return std::string(value->valuestring);
|
||||
}
|
||||
|
||||
return "invalid";
|
||||
}
|
||||
|
||||
std::vector<int> rawGetIntArray(const std::string &field) override
|
||||
{
|
||||
std::vector<int> result;
|
||||
|
||||
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
|
||||
|
||||
if (value != NULL && cJSON_IsArray(value))
|
||||
{
|
||||
for (int i = 0; i < cJSON_GetArraySize(value); i++)
|
||||
{
|
||||
cJSON *item = cJSON_GetArrayItem(value, i);
|
||||
if (item != NULL && cJSON_IsNumber(item))
|
||||
{
|
||||
result.push_back(item->valueint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<float> rawGetFloatArray(const std::string &field) override
|
||||
{
|
||||
std::vector<float> result;
|
||||
|
||||
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
|
||||
|
||||
if (value != NULL && cJSON_IsArray(value))
|
||||
{
|
||||
for (int i = 0; i < cJSON_GetArraySize(value); i++)
|
||||
{
|
||||
cJSON *item = cJSON_GetArrayItem(value, i);
|
||||
if (item != NULL && cJSON_IsNumber(item))
|
||||
{
|
||||
result.push_back(item->valuedouble);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int rawGetInt(const std::string &field) override
|
||||
{
|
||||
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
|
||||
|
||||
if (value != NULL && cJSON_IsNumber(value))
|
||||
{
|
||||
return (int)value->valueint;
|
||||
}
|
||||
|
||||
return invalidInt;
|
||||
}
|
||||
|
||||
bool isArray(const std::string &field) override
|
||||
{
|
||||
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
|
||||
|
||||
if (value != NULL && cJSON_IsArray(value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
float rawGetFloat(const std::string &field) override
|
||||
{
|
||||
cJSON *value = cJSON_GetObjectItem(json, field.c_str());
|
||||
|
||||
if (value != NULL && cJSON_IsNumber(value))
|
||||
{
|
||||
return (float)value->valuedouble;
|
||||
}
|
||||
|
||||
return invalidInt;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
namespace bell
|
||||
{
|
||||
enum class Channels {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
LEFT_RIGHT
|
||||
};
|
||||
|
||||
enum class SampleRate : uint32_t
|
||||
{
|
||||
SR_44100 = 44100,
|
||||
SR_48000 = 48000,
|
||||
};
|
||||
|
||||
enum class BitWidth : uint32_t
|
||||
{
|
||||
BW_16 = 16,
|
||||
BW_24 = 24,
|
||||
BW_32 = 32,
|
||||
};
|
||||
|
||||
typedef struct
|
||||
{
|
||||
float** data;
|
||||
BitWidth bitwidth;
|
||||
int numChannels;
|
||||
SampleRate sampleRate;
|
||||
size_t numSamples;
|
||||
} StreamInfo;
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <variant>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
namespace bell
|
||||
{
|
||||
class TransformConfig
|
||||
{
|
||||
protected:
|
||||
int invalidInt = -0x7C;
|
||||
std::string invalidString = "_invalid";
|
||||
|
||||
public:
|
||||
TransformConfig() = default;
|
||||
virtual ~TransformConfig() = default;
|
||||
|
||||
int currentVolume = 60;
|
||||
|
||||
virtual std::string rawGetString(const std::string &field) = 0;
|
||||
|
||||
virtual int rawGetInt(const std::string &field) = 0;
|
||||
virtual bool isArray(const std::string &field) = 0;
|
||||
|
||||
virtual float rawGetFloat(const std::string &field) = 0;
|
||||
virtual std::vector<float> rawGetFloatArray(const std::string &field) = 0;
|
||||
virtual std::vector<int> rawGetIntArray(const std::string &field) = 0;
|
||||
|
||||
typedef std::variant<int, float, std::string> Value;
|
||||
std::map<std::string, std::vector<Value>> rawValues;
|
||||
|
||||
Value getRawValue(const std::string &field)
|
||||
{
|
||||
int index = this->currentVolume * (rawValues[field].size()) / 100;
|
||||
if (index >= rawValues[field].size())
|
||||
index = rawValues[field].size() - 1;
|
||||
return rawValues[field][index];
|
||||
}
|
||||
|
||||
std::string getString(const std::string &field, bool isRequired = false, std::string defaultValue = "")
|
||||
{
|
||||
if (rawValues.count(field) == 0)
|
||||
{
|
||||
rawValues[field] = std::vector<Value>({Value(rawGetString(field))});
|
||||
}
|
||||
auto val = std::get<std::string>(getRawValue(field));
|
||||
if (val == invalidString)
|
||||
{
|
||||
if (isRequired)
|
||||
throw std::invalid_argument("Field " + field + " is required");
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
else
|
||||
return val;
|
||||
}
|
||||
|
||||
int getInt(const std::string &field, bool isRequired = false, int defaultValue = 0)
|
||||
{
|
||||
if (rawValues.count(field) == 0)
|
||||
{
|
||||
if (isArray(field))
|
||||
{
|
||||
rawValues[field] = std::vector<Value>();
|
||||
for (auto f : rawGetIntArray(field))
|
||||
{
|
||||
rawValues[field].push_back(f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rawValues[field] = std::vector<Value>({Value(rawGetInt(field))});
|
||||
}
|
||||
}
|
||||
|
||||
auto val = std::get<int>(getRawValue(field));
|
||||
if (val == invalidInt)
|
||||
{
|
||||
if (isRequired)
|
||||
throw std::invalid_argument("Field " + field + " is required");
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
else
|
||||
return val;
|
||||
}
|
||||
|
||||
float getFloat(const std::string &field, bool isRequired = false, float defaultValue = 0)
|
||||
{
|
||||
if (rawValues.count(field) == 0)
|
||||
{
|
||||
if (isArray(field))
|
||||
{
|
||||
|
||||
rawValues[field] = std::vector<Value>();
|
||||
for (auto f : rawGetFloatArray(field))
|
||||
{
|
||||
rawValues[field].push_back(f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
rawValues[field] = std::vector<Value>({ Value(rawGetFloat(field)) });
|
||||
}
|
||||
}
|
||||
auto val = std::get<float>(getRawValue(field));
|
||||
if (val == invalidInt)
|
||||
{
|
||||
if (isRequired)
|
||||
throw std::invalid_argument("Field " + field + " is required");
|
||||
else
|
||||
return defaultValue;
|
||||
}
|
||||
else
|
||||
return val;
|
||||
}
|
||||
|
||||
std::vector<int> getChannels()
|
||||
{
|
||||
auto channel = getInt("channel", false, invalidInt);
|
||||
|
||||
if (channel != invalidInt)
|
||||
{
|
||||
return std::vector<int>({channel});
|
||||
}
|
||||
|
||||
return rawGetIntArray("channels");
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user