mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-27 20:28:54 +01:00
ad86c297fb
Closes #1626
172 lines
6.5 KiB
C++
Vendored
172 lines
6.5 KiB
C++
Vendored
/*
|
|
* 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 "oboe_flowgraph_resampler_IntegerRatio_android.h"
|
|
#include "oboe_flowgraph_resampler_LinearResampler_android.h"
|
|
#include "oboe_flowgraph_resampler_MultiChannelResampler_android.h"
|
|
#include "oboe_flowgraph_resampler_PolyphaseResampler_android.h"
|
|
#include "oboe_flowgraph_resampler_PolyphaseResamplerMono_android.h"
|
|
#include "oboe_flowgraph_resampler_PolyphaseResamplerStereo_android.h"
|
|
#include "oboe_flowgraph_resampler_SincResampler_android.h"
|
|
#include "oboe_flowgraph_resampler_SincResamplerStereo_android.h"
|
|
|
|
using namespace resampler;
|
|
|
|
MultiChannelResampler::MultiChannelResampler(const MultiChannelResampler::Builder &builder)
|
|
: mNumTaps(builder.getNumTaps())
|
|
, mX(builder.getChannelCount() * 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;
|
|
}
|
|
|
|
// 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[mCursor * 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 (abs(radians) < 1.0e-9) 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(getNumTaps() * 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 = normalizedCutoff *
|
|
((outputRate < inputRate)
|
|
? ((float)outputRate / inputRate)
|
|
: ((float)inputRate / outputRate));
|
|
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(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;
|
|
}
|
|
}
|
|
}
|