scummvm/audio/rate.cpp
2023-11-19 11:35:35 +01:00

327 lines
10 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
/*
* The code in this file is based on code with Copyright 1998 Fabrice Bellard
* Fabrice original code is part of SoX (http://sox.sourceforge.net).
* Max Horn adapted that code to the needs of ScummVM and rewrote it partial,
* in the process removing any use of floating point arithmetic. Various other
* improvements over the original code were made.
*/
#include "audio/audiostream.h"
#include "audio/rate.h"
#include "audio/mixer.h"
#include "common/util.h"
namespace Audio {
/**
* The default fractional type in frac.h (with 16 fractional bits) limits
* the rate conversion code to 65536Hz audio: we need to able to handle
* 96kHz audio, so we use fewer fractional bits in this code.
*/
enum {
FRAC_BITS_LOW = 15,
FRAC_ONE_LOW = (1L << FRAC_BITS_LOW),
FRAC_HALF_LOW = (1L << (FRAC_BITS_LOW-1))
};
template<bool inStereo, bool outStereo, bool reverseStereo>
class RateConverter_Impl : public RateConverter {
private:
/** Input and output rates */
st_rate_t _inRate, _outRate;
/**
* The intermediate input cache. Bigger values may increase performance,
* but only until some point (depends largely on cache size, target
* processor and various other factors), at which it will decrease again.
*/
st_sample_t _buffer[512];
/** Current position inside the buffer */
const st_sample_t *_bufferPos;
/** Size of data currently loaded into the buffer */
int _bufferSize;
/** How far output is ahead of input when doing simple conversion */
frac_t _outPos;
/** Fractional position of the output stream in input stream unit */
frac_t _outPosFrac;
/** Last sample(s) in the input stream (left/right channel) */
st_sample_t _inLastL, _inLastR;
/** Current sample(s) in the input stream (left/right channel) */
st_sample_t _inCurL, _inCurR;
int copyConvert(AudioStream &input, st_sample_t *outBuffer, st_size_t numSamples, st_volume_t vol_l, st_volume_t vol_r);
int simpleConvert(AudioStream &input, st_sample_t *outBuffer, st_size_t numSamples, st_volume_t vol_l, st_volume_t vol_r);
int interpolateConvert(AudioStream &input, st_sample_t *outBuffer, st_size_t numSamples, st_volume_t vol_l, st_volume_t vol_r);
public:
RateConverter_Impl(st_rate_t inputRate, st_rate_t outputRate);
virtual ~RateConverter_Impl() {}
int convert(AudioStream &input, st_sample_t *outBuffer, st_size_t numSamples, st_volume_t vol_l, st_volume_t vol_r) override;
void setInputRate(st_rate_t inputRate) override { _inRate = inputRate; }
void setOutputRate(st_rate_t outputRate) override { _outRate = outputRate; }
st_rate_t getInputRate() const override { return _inRate; }
st_rate_t getOutputRate() const override { return _outRate; }
bool needsDraining() const override { return _bufferSize != 0; }
};
template<bool inStereo, bool outStereo, bool reverseStereo>
int RateConverter_Impl<inStereo, outStereo, reverseStereo>::copyConvert(AudioStream &input, st_sample_t *outBuffer, st_size_t numSamples, st_volume_t volL, st_volume_t volR) {
st_sample_t *outStart, *outEnd;
outStart = outBuffer;
outEnd = outBuffer + numSamples * (outStereo ? 2 : 1);
while (outBuffer < outEnd) {
// Check if we have to refill the buffer
if (_bufferSize == 0) {
_bufferPos = _buffer;
_bufferSize = input.readBuffer(_buffer, ARRAYSIZE(_buffer));
if (_bufferSize <= 0)
return (outBuffer - outStart) / (outStereo ? 2 : 1);
}
// Mix the data into the output buffer
st_sample_t inL, inR;
inL = *_bufferPos++;
inR = (inStereo ? *_bufferPos++ : inL);
_bufferSize -= (inStereo ? 2 : 1);
st_sample_t outL, outR;
outL = (inL * (int)volL) / Audio::Mixer::kMaxMixerVolume;
outR = (inR * (int)volR) / Audio::Mixer::kMaxMixerVolume;
if (inStereo && outStereo) {
// Output left channel
clampedAdd(outBuffer[reverseStereo ], outL);
// Output right channel
clampedAdd(outBuffer[reverseStereo ^ 1], outR);
outBuffer += 2;
} else {
// Output mono channel
st_sample_t val = clampedAdd(outBuffer[0], (outL + outR) / 2);
outBuffer += 1;
if (outStereo)
*outBuffer++ = val;
}
}
return (outBuffer - outStart) / (outStereo ? 2 : 1);
}
template<bool inStereo, bool outStereo, bool reverseStereo>
int RateConverter_Impl<inStereo, outStereo, reverseStereo>::simpleConvert(AudioStream &input, st_sample_t *outBuffer, st_size_t numSamples, st_volume_t volL, st_volume_t volR) {
// How much to increment _outPos by
frac_t outPos_inc = _inRate / _outRate;
st_sample_t *outStart, *outEnd;
outStart = outBuffer;
outEnd = outBuffer + numSamples * (outStereo ? 2 : 1);
while (outBuffer < outEnd) {
// Read enough input samples so that _outPos >= 0
do {
// Check if we have to refill the buffer
if (_bufferSize == 0) {
_bufferPos = _buffer;
_bufferSize = input.readBuffer(_buffer, ARRAYSIZE(_buffer));
if (_bufferSize <= 0)
return (outBuffer - outStart) / (outStereo ? 2 : 1);
}
_bufferSize -= (inStereo ? 2 : 1);
_outPos--;
if (_outPos >= 0) {
_bufferPos += (inStereo ? 2 : 1);
}
} while (_outPos >= 0);
st_sample_t inL, inR;
inL = *_bufferPos++;
inR = (inStereo ? *_bufferPos++ : inL);
// Increment output position
_outPos += outPos_inc;
st_sample_t outL, outR;
outL = (inL * (int)volL) / Audio::Mixer::kMaxMixerVolume;
outR = (inR * (int)volR) / Audio::Mixer::kMaxMixerVolume;
if (inStereo && outStereo) {
// output left channel
clampedAdd(outBuffer[reverseStereo ], outL);
// output right channel
clampedAdd(outBuffer[reverseStereo ^ 1], outR);
outBuffer += 2;
} else {
// output mono channel
st_sample_t val = clampedAdd(outBuffer[0], (outL + outR) / 2);
outBuffer += 1;
if (outStereo)
*outBuffer++ = val;
}
}
return (outBuffer - outStart) / (outStereo ? 2 : 1);
}
template<bool inStereo, bool outStereo, bool reverseStereo>
int RateConverter_Impl<inStereo, outStereo, reverseStereo>::interpolateConvert(AudioStream &input, st_sample_t *outBuffer, st_size_t numSamples, st_volume_t volL, st_volume_t volR) {
// How much to increment _outPosFrac by
frac_t outPos_inc = (_inRate << FRAC_BITS_LOW) / _outRate;
st_sample_t *outStart, *outEnd;
outStart = outBuffer;
outEnd = outBuffer + numSamples * (outStereo ? 2 : 1);
while (outBuffer < outEnd) {
// Read enough input samples so that _outPosFrac < 0
while ((frac_t)FRAC_ONE_LOW <= _outPosFrac) {
// Check if we have to refill the buffer
if (_bufferSize == 0) {
_bufferPos = _buffer;
_bufferSize = input.readBuffer(_buffer, ARRAYSIZE(_buffer));
if (_bufferSize <= 0)
return (outBuffer - outStart) / (outStereo ? 2 : 1);
}
_bufferSize -= (inStereo ? 2 : 1);
_inLastL = _inCurL;
_inCurL = *_bufferPos++;
if (inStereo) {
_inLastR = _inCurR;
_inCurR = *_bufferPos++;
}
_outPosFrac -= FRAC_ONE_LOW;
}
// Loop as long as the _outPos trails behind, and as long as there is
// still space in the output buffer.
while (_outPosFrac < (frac_t)FRAC_ONE_LOW && outBuffer < outEnd) {
// Interpolate
st_sample_t inL, inR;
inL = (st_sample_t)(_inLastL + (((_inCurL - _inLastL) * _outPosFrac + FRAC_HALF_LOW) >> FRAC_BITS_LOW));
inR = (inStereo ?
(st_sample_t)(_inLastR + (((_inCurR - _inLastR) * _outPosFrac + FRAC_HALF_LOW) >> FRAC_BITS_LOW)) :
inL);
st_sample_t outL, outR;
outL = (inL * (int)volL) / Audio::Mixer::kMaxMixerVolume;
outR = (inR * (int)volR) / Audio::Mixer::kMaxMixerVolume;
if (inStereo && outStereo) {
// Output left channel
clampedAdd(outBuffer[reverseStereo ], outL);
// Output right channel
clampedAdd(outBuffer[reverseStereo ^ 1], outR);
outBuffer += 2;
} else {
// Output mono channel
st_sample_t val = clampedAdd(outBuffer[0], (outL + outR) / 2);
outBuffer += 1;
if (outStereo)
*outBuffer++ = val;
}
// Increment output position
_outPosFrac += outPos_inc;
}
}
return (outBuffer - outStart) / (outStereo ? 2 : 1);
}
template<bool inStereo, bool outStereo, bool reverseStereo>
RateConverter_Impl<inStereo, outStereo, reverseStereo>::RateConverter_Impl(st_rate_t inputRate, st_rate_t outputRate) :
_inRate(inputRate),
_outRate(outputRate),
_outPos(1),
_outPosFrac(FRAC_ONE_LOW),
_inLastL(0),
_inLastR(0),
_inCurL(0),
_inCurR(0),
_bufferSize(0),
_bufferPos(nullptr) {}
template<bool inStereo, bool outStereo, bool reverseStereo>
int RateConverter_Impl<inStereo, outStereo, reverseStereo>::convert(AudioStream &input, st_sample_t *outBuffer, st_size_t numSamples, st_volume_t volL, st_volume_t volR) {
assert(input.isStereo() == inStereo);
if (_inRate == _outRate) {
return copyConvert(input, outBuffer, numSamples, volL, volR);
} else {
if ((_inRate % _outRate) == 0 && (_inRate < 65536)) {
return simpleConvert(input, outBuffer, numSamples, volL, volR);
} else {
return interpolateConvert(input, outBuffer, numSamples, volL, volR);
}
}
}
RateConverter *makeRateConverter(st_rate_t inRate, st_rate_t outRate, bool inStereo, bool outStereo, bool reverseStereo) {
if (inStereo) {
if (outStereo) {
if (reverseStereo)
return new RateConverter_Impl<true, true, true>(inRate, outRate);
else
return new RateConverter_Impl<true, true, false>(inRate, outRate);
} else
return new RateConverter_Impl<true, false, false>(inRate, outRate);
} else {
if (outStereo) {
return new RateConverter_Impl<false, true, false>(inRate, outRate);
} else
return new RateConverter_Impl<false, false, false>(inRate, outRate);
}
}
} // End of namespace Audio