mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-12-11 06:55:38 +00:00
316 lines
8.2 KiB
C++
316 lines
8.2 KiB
C++
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
///
|
||
|
/// Sample rate transposer. Changes sample rate by using linear interpolation
|
||
|
/// together with anti-alias filtering (first order interpolation with anti-
|
||
|
/// alias filtering should be quite adequate for this application)
|
||
|
///
|
||
|
/// Author : Copyright (c) Olli Parviainen
|
||
|
/// Author e-mail : oparviai 'at' iki.fi
|
||
|
/// SoundTouch WWW: http://www.surina.net/soundtouch
|
||
|
///
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// License :
|
||
|
//
|
||
|
// SoundTouch audio processing library
|
||
|
// Copyright (c) Olli Parviainen
|
||
|
//
|
||
|
// This library is free software; you can redistribute it and/or
|
||
|
// modify it under the terms of the GNU Lesser General Public
|
||
|
// License as published by the Free Software Foundation; either
|
||
|
// version 2.1 of the License, or (at your option) any later version.
|
||
|
//
|
||
|
// This library 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
|
||
|
// Lesser General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Lesser General Public
|
||
|
// License along with this library; if not, write to the Free Software
|
||
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
//
|
||
|
////////////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
#include <memory.h>
|
||
|
#include <assert.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stdio.h>
|
||
|
#include "RateTransposer.h"
|
||
|
#include "InterpolateLinear.h"
|
||
|
#include "InterpolateCubic.h"
|
||
|
#include "InterpolateShannon.h"
|
||
|
#include "AAFilter.h"
|
||
|
|
||
|
using namespace soundtouch;
|
||
|
|
||
|
// Define default interpolation algorithm here
|
||
|
TransposerBase::ALGORITHM TransposerBase::algorithm = TransposerBase::CUBIC;
|
||
|
|
||
|
|
||
|
// Constructor
|
||
|
RateTransposer::RateTransposer() : FIFOProcessor(&outputBuffer)
|
||
|
{
|
||
|
bUseAAFilter =
|
||
|
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER
|
||
|
true;
|
||
|
#else
|
||
|
// Disable Anti-alias filter if desirable to avoid click at rate change zero value crossover
|
||
|
false;
|
||
|
#endif
|
||
|
|
||
|
// Instantiates the anti-alias filter
|
||
|
pAAFilter = new AAFilter(64);
|
||
|
pTransposer = TransposerBase::newInstance();
|
||
|
clear();
|
||
|
}
|
||
|
|
||
|
|
||
|
RateTransposer::~RateTransposer()
|
||
|
{
|
||
|
delete pAAFilter;
|
||
|
delete pTransposer;
|
||
|
}
|
||
|
|
||
|
|
||
|
/// Enables/disables the anti-alias filter. Zero to disable, nonzero to enable
|
||
|
void RateTransposer::enableAAFilter(bool newMode)
|
||
|
{
|
||
|
#ifndef SOUNDTOUCH_PREVENT_CLICK_AT_RATE_CROSSOVER
|
||
|
// Disable Anti-alias filter if desirable to avoid click at rate change zero value crossover
|
||
|
bUseAAFilter = newMode;
|
||
|
clear();
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/// Returns nonzero if anti-alias filter is enabled.
|
||
|
bool RateTransposer::isAAFilterEnabled() const
|
||
|
{
|
||
|
return bUseAAFilter;
|
||
|
}
|
||
|
|
||
|
|
||
|
AAFilter *RateTransposer::getAAFilter()
|
||
|
{
|
||
|
return pAAFilter;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Sets new target iRate. Normal iRate = 1.0, smaller values represent slower
|
||
|
// iRate, larger faster iRates.
|
||
|
void RateTransposer::setRate(double newRate)
|
||
|
{
|
||
|
double fCutoff;
|
||
|
|
||
|
pTransposer->setRate(newRate);
|
||
|
|
||
|
// design a new anti-alias filter
|
||
|
if (newRate > 1.0)
|
||
|
{
|
||
|
fCutoff = 0.5 / newRate;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fCutoff = 0.5 * newRate;
|
||
|
}
|
||
|
pAAFilter->setCutoffFreq(fCutoff);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Adds 'nSamples' pcs of samples from the 'samples' memory position into
|
||
|
// the input of the object.
|
||
|
void RateTransposer::putSamples(const SAMPLETYPE *samples, uint nSamples)
|
||
|
{
|
||
|
processSamples(samples, nSamples);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Transposes sample rate by applying anti-alias filter to prevent folding.
|
||
|
// Returns amount of samples returned in the "dest" buffer.
|
||
|
// The maximum amount of samples that can be returned at a time is set by
|
||
|
// the 'set_returnBuffer_size' function.
|
||
|
void RateTransposer::processSamples(const SAMPLETYPE *src, uint nSamples)
|
||
|
{
|
||
|
uint count;
|
||
|
|
||
|
if (nSamples == 0) return;
|
||
|
|
||
|
// Store samples to input buffer
|
||
|
inputBuffer.putSamples(src, nSamples);
|
||
|
|
||
|
// If anti-alias filter is turned off, simply transpose without applying
|
||
|
// the filter
|
||
|
if (bUseAAFilter == false)
|
||
|
{
|
||
|
count = pTransposer->transpose(outputBuffer, inputBuffer);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
assert(pAAFilter);
|
||
|
|
||
|
// Transpose with anti-alias filter
|
||
|
if (pTransposer->rate < 1.0f)
|
||
|
{
|
||
|
// If the parameter 'Rate' value is smaller than 1, first transpose
|
||
|
// the samples and then apply the anti-alias filter to remove aliasing.
|
||
|
|
||
|
// Transpose the samples, store the result to end of "midBuffer"
|
||
|
pTransposer->transpose(midBuffer, inputBuffer);
|
||
|
|
||
|
// Apply the anti-alias filter for transposed samples in midBuffer
|
||
|
pAAFilter->evaluate(outputBuffer, midBuffer);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// If the parameter 'Rate' value is larger than 1, first apply the
|
||
|
// anti-alias filter to remove high frequencies (prevent them from folding
|
||
|
// over the lover frequencies), then transpose.
|
||
|
|
||
|
// Apply the anti-alias filter for samples in inputBuffer
|
||
|
pAAFilter->evaluate(midBuffer, inputBuffer);
|
||
|
|
||
|
// Transpose the AA-filtered samples in "midBuffer"
|
||
|
pTransposer->transpose(outputBuffer, midBuffer);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Sets the number of channels, 1 = mono, 2 = stereo
|
||
|
void RateTransposer::setChannels(int nChannels)
|
||
|
{
|
||
|
if (!verifyNumberOfChannels(nChannels) ||
|
||
|
(pTransposer->numChannels == nChannels)) return;
|
||
|
|
||
|
pTransposer->setChannels(nChannels);
|
||
|
inputBuffer.setChannels(nChannels);
|
||
|
midBuffer.setChannels(nChannels);
|
||
|
outputBuffer.setChannels(nChannels);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Clears all the samples in the object
|
||
|
void RateTransposer::clear()
|
||
|
{
|
||
|
outputBuffer.clear();
|
||
|
midBuffer.clear();
|
||
|
inputBuffer.clear();
|
||
|
pTransposer->resetRegisters();
|
||
|
|
||
|
// prefill buffer to avoid losing first samples at beginning of stream
|
||
|
int prefill = getLatency();
|
||
|
inputBuffer.addSilent(prefill);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Returns nonzero if there aren't any samples available for outputting.
|
||
|
int RateTransposer::isEmpty() const
|
||
|
{
|
||
|
int res;
|
||
|
|
||
|
res = FIFOProcessor::isEmpty();
|
||
|
if (res == 0) return 0;
|
||
|
return inputBuffer.isEmpty();
|
||
|
}
|
||
|
|
||
|
|
||
|
/// Return approximate initial input-output latency
|
||
|
int RateTransposer::getLatency() const
|
||
|
{
|
||
|
return pTransposer->getLatency() +
|
||
|
((bUseAAFilter) ? (pAAFilter->getLength() / 2) : 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// TransposerBase - Base class for interpolation
|
||
|
//
|
||
|
|
||
|
// static function to set interpolation algorithm
|
||
|
void TransposerBase::setAlgorithm(TransposerBase::ALGORITHM a)
|
||
|
{
|
||
|
TransposerBase::algorithm = a;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Transposes the sample rate of the given samples using linear interpolation.
|
||
|
// Returns the number of samples returned in the "dest" buffer
|
||
|
int TransposerBase::transpose(FIFOSampleBuffer &dest, FIFOSampleBuffer &src)
|
||
|
{
|
||
|
int numSrcSamples = src.numSamples();
|
||
|
int sizeDemand = (int)((double)numSrcSamples / rate) + 8;
|
||
|
int numOutput;
|
||
|
SAMPLETYPE *psrc = src.ptrBegin();
|
||
|
SAMPLETYPE *pdest = dest.ptrEnd(sizeDemand);
|
||
|
|
||
|
#ifndef USE_MULTICH_ALWAYS
|
||
|
if (numChannels == 1)
|
||
|
{
|
||
|
numOutput = transposeMono(pdest, psrc, numSrcSamples);
|
||
|
}
|
||
|
else if (numChannels == 2)
|
||
|
{
|
||
|
numOutput = transposeStereo(pdest, psrc, numSrcSamples);
|
||
|
}
|
||
|
else
|
||
|
#endif // USE_MULTICH_ALWAYS
|
||
|
{
|
||
|
assert(numChannels > 0);
|
||
|
numOutput = transposeMulti(pdest, psrc, numSrcSamples);
|
||
|
}
|
||
|
dest.putSamples(numOutput);
|
||
|
src.receiveSamples(numSrcSamples);
|
||
|
return numOutput;
|
||
|
}
|
||
|
|
||
|
|
||
|
TransposerBase::TransposerBase()
|
||
|
{
|
||
|
numChannels = 0;
|
||
|
rate = 1.0f;
|
||
|
}
|
||
|
|
||
|
|
||
|
TransposerBase::~TransposerBase()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
void TransposerBase::setChannels(int channels)
|
||
|
{
|
||
|
numChannels = channels;
|
||
|
resetRegisters();
|
||
|
}
|
||
|
|
||
|
|
||
|
void TransposerBase::setRate(double newRate)
|
||
|
{
|
||
|
rate = newRate;
|
||
|
}
|
||
|
|
||
|
|
||
|
// static factory function
|
||
|
TransposerBase *TransposerBase::newInstance()
|
||
|
{
|
||
|
#ifdef SOUNDTOUCH_INTEGER_SAMPLES
|
||
|
// Notice: For integer arithmetic support only linear algorithm (due to simplest calculus)
|
||
|
return ::new InterpolateLinearInteger;
|
||
|
#else
|
||
|
switch (algorithm)
|
||
|
{
|
||
|
case LINEAR:
|
||
|
return new InterpolateLinearFloat;
|
||
|
|
||
|
case CUBIC:
|
||
|
return new InterpolateCubic;
|
||
|
|
||
|
case SHANNON:
|
||
|
return new InterpolateShannon;
|
||
|
|
||
|
default:
|
||
|
assert(false);
|
||
|
return NULL;
|
||
|
}
|
||
|
#endif
|
||
|
}
|