aboutsummaryrefslogtreecommitdiff
path: root/externals/oboe/src/flowgraph/resampler/MultiChannelResampler.cpp
diff options
context:
space:
mode:
authorswurl <swurl@swurl.xyz>2025-05-31 02:33:02 -0400
committerswurl <swurl@swurl.xyz>2025-05-31 02:33:02 -0400
commit6c655321e6b04b8648802e296bb5ca11d290ae68 (patch)
tree5c906511e4a2910ce8b806bb9d763b524c28619e /externals/oboe/src/flowgraph/resampler/MultiChannelResampler.cpp
parentc0cceff365f754f8859f4c9a0362ebdba7ea388b (diff)
Move dead submodules in-tree
Signed-off-by: swurl <swurl@swurl.xyz>
Diffstat (limited to 'externals/oboe/src/flowgraph/resampler/MultiChannelResampler.cpp')
-rw-r--r--externals/oboe/src/flowgraph/resampler/MultiChannelResampler.cpp171
1 files changed, 171 insertions, 0 deletions
diff --git a/externals/oboe/src/flowgraph/resampler/MultiChannelResampler.cpp b/externals/oboe/src/flowgraph/resampler/MultiChannelResampler.cpp
new file mode 100644
index 0000000000..245b669db4
--- /dev/null
+++ b/externals/oboe/src/flowgraph/resampler/MultiChannelResampler.cpp
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <math.h>
+
+#include "IntegerRatio.h"
+#include "LinearResampler.h"
+#include "MultiChannelResampler.h"
+#include "PolyphaseResampler.h"
+#include "PolyphaseResamplerMono.h"
+#include "PolyphaseResamplerStereo.h"
+#include "SincResampler.h"
+#include "SincResamplerStereo.h"
+
+using namespace RESAMPLER_OUTER_NAMESPACE::resampler;
+
+MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder)
+ : mNumTaps(builder.getNumTaps())
+ , mX(static_cast<size_t>(builder.getChannelCount())
+ * static_cast<size_t>(builder.getNumTaps()) * 2)
+ , mSingleFrame(builder.getChannelCount())
+ , mChannelCount(builder.getChannelCount())
+ {
+ // Reduce sample rates to the smallest ratio.
+ // For example 44100/48000 would become 147/160.
+ IntegerRatio ratio(builder.getInputRate(), builder.getOutputRate());
+ ratio.reduce();
+ mNumerator = ratio.getNumerator();
+ mDenominator = ratio.getDenominator();
+ mIntegerPhase = mDenominator; // so we start with a write needed
+}
+
+// static factory method
+MultiChannelResampler *MultiChannelResampler::make(int32_t channelCount,
+ int32_t inputRate,
+ int32_t outputRate,
+ Quality quality) {
+ Builder builder;
+ builder.setInputRate(inputRate);
+ builder.setOutputRate(outputRate);
+ builder.setChannelCount(channelCount);
+
+ switch (quality) {
+ case Quality::Fastest:
+ builder.setNumTaps(2);
+ break;
+ case Quality::Low:
+ builder.setNumTaps(4);
+ break;
+ case Quality::Medium:
+ default:
+ builder.setNumTaps(8);
+ break;
+ case Quality::High:
+ builder.setNumTaps(16);
+ break;
+ case Quality::Best:
+ builder.setNumTaps(32);
+ break;
+ }
+
+ // Set the cutoff frequency so that we do not get aliasing when down-sampling.
+ if (inputRate > outputRate) {
+ builder.setNormalizedCutoff(kDefaultNormalizedCutoff);
+ }
+ return builder.build();
+}
+
+MultiChannelResampler *MultiChannelResampler::Builder::build() {
+ if (getNumTaps() == 2) {
+ // Note that this does not do low pass filteringh.
+ return new LinearResampler(*this);
+ }
+ IntegerRatio ratio(getInputRate(), getOutputRate());
+ ratio.reduce();
+ bool usePolyphase = (getNumTaps() * ratio.getDenominator()) <= kMaxCoefficients;
+ if (usePolyphase) {
+ if (getChannelCount() == 1) {
+ return new PolyphaseResamplerMono(*this);
+ } else if (getChannelCount() == 2) {
+ return new PolyphaseResamplerStereo(*this);
+ } else {
+ return new PolyphaseResampler(*this);
+ }
+ } else {
+ // Use less optimized resampler that uses a float phaseIncrement.
+ // TODO mono resampler
+ if (getChannelCount() == 2) {
+ return new SincResamplerStereo(*this);
+ } else {
+ return new SincResampler(*this);
+ }
+ }
+}
+
+void MultiChannelResampler::writeFrame(const float *frame) {
+ // Move cursor before write so that cursor points to last written frame in read.
+ if (--mCursor < 0) {
+ mCursor = getNumTaps() - 1;
+ }
+ float *dest = &mX[static_cast<size_t>(mCursor) * static_cast<size_t>(getChannelCount())];
+ int offset = getNumTaps() * getChannelCount();
+ for (int channel = 0; channel < getChannelCount(); channel++) {
+ // Write twice so we avoid having to wrap when reading.
+ dest[channel] = dest[channel + offset] = frame[channel];
+ }
+}
+
+float MultiChannelResampler::sinc(float radians) {
+ if (fabsf(radians) < 1.0e-9f) return 1.0f; // avoid divide by zero
+ return sinf(radians) / radians; // Sinc function
+}
+
+// Generate coefficients in the order they will be used by readFrame().
+// This is more complicated but readFrame() is called repeatedly and should be optimized.
+void MultiChannelResampler::generateCoefficients(int32_t inputRate,
+ int32_t outputRate,
+ int32_t numRows,
+ double phaseIncrement,
+ float normalizedCutoff) {
+ mCoefficients.resize(static_cast<size_t>(getNumTaps()) * static_cast<size_t>(numRows));
+ int coefficientIndex = 0;
+ double phase = 0.0; // ranges from 0.0 to 1.0, fraction between samples
+ // Stretch the sinc function for low pass filtering.
+ const float cutoffScaler = (outputRate < inputRate)
+ ? (normalizedCutoff * (float)outputRate / inputRate)
+ : 1.0f; // Do not filter when upsampling.
+ const int numTapsHalf = getNumTaps() / 2; // numTaps must be even.
+ const float numTapsHalfInverse = 1.0f / numTapsHalf;
+ for (int i = 0; i < numRows; i++) {
+ float tapPhase = phase - numTapsHalf;
+ float gain = 0.0; // sum of raw coefficients
+ int gainCursor = coefficientIndex;
+ for (int tap = 0; tap < getNumTaps(); tap++) {
+ float radians = tapPhase * M_PI;
+
+#if MCR_USE_KAISER
+ float window = mKaiserWindow(tapPhase * numTapsHalfInverse);
+#else
+ float window = mCoshWindow(static_cast<double>(tapPhase) * numTapsHalfInverse);
+#endif
+ float coefficient = sinc(radians * cutoffScaler) * window;
+ mCoefficients.at(coefficientIndex++) = coefficient;
+ gain += coefficient;
+ tapPhase += 1.0;
+ }
+ phase += phaseIncrement;
+ while (phase >= 1.0) {
+ phase -= 1.0;
+ }
+
+ // Correct for gain variations.
+ float gainCorrection = 1.0 / gain; // normalize the gain
+ for (int tap = 0; tap < getNumTaps(); tap++) {
+ mCoefficients.at(gainCursor + tap) *= gainCorrection;
+ }
+ }
+}