mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-29 13:16:18 +00:00
82c98e9803
AUDIO: Add support for sample rates >65kHz.
370 lines
10 KiB
C++
370 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 2
|
|
* 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, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* 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/frac.h"
|
|
#include "common/textconsole.h"
|
|
#include "common/util.h"
|
|
|
|
namespace Audio {
|
|
|
|
|
|
/**
|
|
* The size of 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.
|
|
*/
|
|
#define INTERMEDIATE_BUFFER_SIZE 512
|
|
|
|
/**
|
|
* 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))
|
|
};
|
|
|
|
/**
|
|
* Audio rate converter based on simple resampling. Used when no
|
|
* interpolation is required.
|
|
*
|
|
* Limited to sampling frequency <= 65535 Hz.
|
|
*/
|
|
template<bool stereo, bool reverseStereo>
|
|
class SimpleRateConverter : public RateConverter {
|
|
protected:
|
|
st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE];
|
|
const st_sample_t *inPtr;
|
|
int inLen;
|
|
|
|
/** position of how far output is ahead of input */
|
|
/** Holds what would have been opos-ipos */
|
|
long opos;
|
|
|
|
/** fractional position increment in the output stream */
|
|
long opos_inc;
|
|
|
|
public:
|
|
SimpleRateConverter(st_rate_t inrate, st_rate_t outrate);
|
|
int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r);
|
|
int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) {
|
|
return ST_SUCCESS;
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
* Prepare processing.
|
|
*/
|
|
template<bool stereo, bool reverseStereo>
|
|
SimpleRateConverter<stereo, reverseStereo>::SimpleRateConverter(st_rate_t inrate, st_rate_t outrate) {
|
|
if ((inrate % outrate) != 0) {
|
|
error("Input rate must be a multiple of output rate to use rate effect");
|
|
}
|
|
|
|
if (inrate >= 65536 || outrate >= 65536) {
|
|
error("rate effect can only handle rates < 65536");
|
|
}
|
|
|
|
opos = 1;
|
|
|
|
/* increment */
|
|
opos_inc = inrate / outrate;
|
|
|
|
inLen = 0;
|
|
}
|
|
|
|
/*
|
|
* Processed signed long samples from ibuf to obuf.
|
|
* Return number of sample pairs processed.
|
|
*/
|
|
template<bool stereo, bool reverseStereo>
|
|
int SimpleRateConverter<stereo, reverseStereo>::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) {
|
|
st_sample_t *ostart, *oend;
|
|
|
|
ostart = obuf;
|
|
oend = obuf + osamp * 2;
|
|
|
|
while (obuf < oend) {
|
|
|
|
// read enough input samples so that opos >= 0
|
|
do {
|
|
// Check if we have to refill the buffer
|
|
if (inLen == 0) {
|
|
inPtr = inBuf;
|
|
inLen = input.readBuffer(inBuf, ARRAYSIZE(inBuf));
|
|
if (inLen <= 0)
|
|
return (obuf - ostart) / 2;
|
|
}
|
|
inLen -= (stereo ? 2 : 1);
|
|
opos--;
|
|
if (opos >= 0) {
|
|
inPtr += (stereo ? 2 : 1);
|
|
}
|
|
} while (opos >= 0);
|
|
|
|
st_sample_t out0, out1;
|
|
out0 = *inPtr++;
|
|
out1 = (stereo ? *inPtr++ : out0);
|
|
|
|
// Increment output position
|
|
opos += opos_inc;
|
|
|
|
// output left channel
|
|
clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume);
|
|
|
|
// output right channel
|
|
clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume);
|
|
|
|
obuf += 2;
|
|
}
|
|
return (obuf - ostart) / 2;
|
|
}
|
|
|
|
/**
|
|
* Audio rate converter based on simple linear Interpolation.
|
|
*
|
|
* The use of fractional increment allows us to use no buffer. It
|
|
* avoid the problems at the end of the buffer we had with the old
|
|
* method which stored a possibly big buffer of size
|
|
* lcm(in_rate,out_rate).
|
|
*
|
|
* Limited to sampling frequency <= 65535 Hz.
|
|
*/
|
|
|
|
template<bool stereo, bool reverseStereo>
|
|
class LinearRateConverter : public RateConverter {
|
|
protected:
|
|
st_sample_t inBuf[INTERMEDIATE_BUFFER_SIZE];
|
|
const st_sample_t *inPtr;
|
|
int inLen;
|
|
|
|
/** fractional position of the output stream in input stream unit */
|
|
frac_t opos;
|
|
|
|
/** fractional position increment in the output stream */
|
|
frac_t opos_inc;
|
|
|
|
/** last sample(s) in the input stream (left/right channel) */
|
|
st_sample_t ilast0, ilast1;
|
|
/** current sample(s) in the input stream (left/right channel) */
|
|
st_sample_t icur0, icur1;
|
|
|
|
public:
|
|
LinearRateConverter(st_rate_t inrate, st_rate_t outrate);
|
|
int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r);
|
|
int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) {
|
|
return ST_SUCCESS;
|
|
}
|
|
};
|
|
|
|
|
|
/*
|
|
* Prepare processing.
|
|
*/
|
|
template<bool stereo, bool reverseStereo>
|
|
LinearRateConverter<stereo, reverseStereo>::LinearRateConverter(st_rate_t inrate, st_rate_t outrate) {
|
|
if (inrate >= 131072 || outrate >= 131072) {
|
|
error("rate effect can only handle rates < 131072");
|
|
}
|
|
|
|
opos = FRAC_ONE_LOW;
|
|
|
|
// Compute the linear interpolation increment.
|
|
// This will overflow if inrate >= 2^17, and underflow if outrate >= 2^17.
|
|
// Also, if the quotient of the two rate becomes too small / too big, that
|
|
// would cause problems, but since we rarely scale from 1 to 65536 Hz or vice
|
|
// versa, I think we can live with that limitation ;-).
|
|
opos_inc = (inrate << FRAC_BITS_LOW) / outrate;
|
|
|
|
ilast0 = ilast1 = 0;
|
|
icur0 = icur1 = 0;
|
|
|
|
inLen = 0;
|
|
}
|
|
|
|
/*
|
|
* Processed signed long samples from ibuf to obuf.
|
|
* Return number of sample pairs processed.
|
|
*/
|
|
template<bool stereo, bool reverseStereo>
|
|
int LinearRateConverter<stereo, reverseStereo>::flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) {
|
|
st_sample_t *ostart, *oend;
|
|
|
|
ostart = obuf;
|
|
oend = obuf + osamp * 2;
|
|
|
|
while (obuf < oend) {
|
|
|
|
// read enough input samples so that opos < 0
|
|
while ((frac_t)FRAC_ONE_LOW <= opos) {
|
|
// Check if we have to refill the buffer
|
|
if (inLen == 0) {
|
|
inPtr = inBuf;
|
|
inLen = input.readBuffer(inBuf, ARRAYSIZE(inBuf));
|
|
if (inLen <= 0)
|
|
return (obuf - ostart) / 2;
|
|
}
|
|
inLen -= (stereo ? 2 : 1);
|
|
ilast0 = icur0;
|
|
icur0 = *inPtr++;
|
|
if (stereo) {
|
|
ilast1 = icur1;
|
|
icur1 = *inPtr++;
|
|
}
|
|
opos -= FRAC_ONE_LOW;
|
|
}
|
|
|
|
// Loop as long as the outpos trails behind, and as long as there is
|
|
// still space in the output buffer.
|
|
while (opos < (frac_t)FRAC_ONE_LOW && obuf < oend) {
|
|
// interpolate
|
|
st_sample_t out0, out1;
|
|
out0 = (st_sample_t)(ilast0 + (((icur0 - ilast0) * opos + FRAC_HALF_LOW) >> FRAC_BITS_LOW));
|
|
out1 = (stereo ?
|
|
(st_sample_t)(ilast1 + (((icur1 - ilast1) * opos + FRAC_HALF_LOW) >> FRAC_BITS_LOW)) :
|
|
out0);
|
|
|
|
// output left channel
|
|
clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume);
|
|
|
|
// output right channel
|
|
clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume);
|
|
|
|
obuf += 2;
|
|
|
|
// Increment output position
|
|
opos += opos_inc;
|
|
}
|
|
}
|
|
return (obuf - ostart) / 2;
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
|
|
|
|
/**
|
|
* Simple audio rate converter for the case that the inrate equals the outrate.
|
|
*/
|
|
template<bool stereo, bool reverseStereo>
|
|
class CopyRateConverter : public RateConverter {
|
|
st_sample_t *_buffer;
|
|
st_size_t _bufferSize;
|
|
public:
|
|
CopyRateConverter() : _buffer(0), _bufferSize(0) {}
|
|
~CopyRateConverter() {
|
|
free(_buffer);
|
|
}
|
|
|
|
virtual int flow(AudioStream &input, st_sample_t *obuf, st_size_t osamp, st_volume_t vol_l, st_volume_t vol_r) {
|
|
assert(input.isStereo() == stereo);
|
|
|
|
st_sample_t *ptr;
|
|
st_size_t len;
|
|
|
|
st_sample_t *ostart = obuf;
|
|
|
|
if (stereo)
|
|
osamp *= 2;
|
|
|
|
// Reallocate temp buffer, if necessary
|
|
if (osamp > _bufferSize) {
|
|
free(_buffer);
|
|
_buffer = (st_sample_t *)malloc(osamp * 2);
|
|
_bufferSize = osamp;
|
|
}
|
|
|
|
if (!_buffer)
|
|
error("[CopyRateConverter::flow] Cannot allocate memory for temp buffer");
|
|
|
|
// Read up to 'osamp' samples into our temporary buffer
|
|
len = input.readBuffer(_buffer, osamp);
|
|
|
|
// Mix the data into the output buffer
|
|
ptr = _buffer;
|
|
for (; len > 0; len -= (stereo ? 2 : 1)) {
|
|
st_sample_t out0, out1;
|
|
out0 = *ptr++;
|
|
out1 = (stereo ? *ptr++ : out0);
|
|
|
|
// output left channel
|
|
clampedAdd(obuf[reverseStereo ], (out0 * (int)vol_l) / Audio::Mixer::kMaxMixerVolume);
|
|
|
|
// output right channel
|
|
clampedAdd(obuf[reverseStereo ^ 1], (out1 * (int)vol_r) / Audio::Mixer::kMaxMixerVolume);
|
|
|
|
obuf += 2;
|
|
}
|
|
return (obuf - ostart) / 2;
|
|
}
|
|
|
|
virtual int drain(st_sample_t *obuf, st_size_t osamp, st_volume_t vol) {
|
|
return ST_SUCCESS;
|
|
}
|
|
};
|
|
|
|
|
|
#pragma mark -
|
|
|
|
template<bool stereo, bool reverseStereo>
|
|
RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate) {
|
|
if (inrate != outrate) {
|
|
if ((inrate % outrate) == 0 && (inrate < 65536)) {
|
|
return new SimpleRateConverter<stereo, reverseStereo>(inrate, outrate);
|
|
} else {
|
|
return new LinearRateConverter<stereo, reverseStereo>(inrate, outrate);
|
|
}
|
|
} else {
|
|
return new CopyRateConverter<stereo, reverseStereo>();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create and return a RateConverter object for the specified input and output rates.
|
|
*/
|
|
RateConverter *makeRateConverter(st_rate_t inrate, st_rate_t outrate, bool stereo, bool reverseStereo) {
|
|
if (stereo) {
|
|
if (reverseStereo)
|
|
return makeRateConverter<true, true>(inrate, outrate);
|
|
else
|
|
return makeRateConverter<true, false>(inrate, outrate);
|
|
} else
|
|
return makeRateConverter<false, false>(inrate, outrate);
|
|
}
|
|
|
|
} // End of namespace Audio
|