scummvm/sound/mixer.cpp
2003-07-26 01:44:37 +00:00

1112 lines
28 KiB
C++

/* ScummVM - Scumm Interpreter
* Copyright (C) 2001 Ludvig Strigeus
* Copyright (C) 2001-2003 The ScummVM project
*
* 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
* $Header$
*
*/
#include "stdafx.h"
#include "mixer.h"
#include "common/engine.h" // for warning/error/debug
#include "common/file.h"
#include "common/util.h"
class Channel {
protected:
SoundMixer *_mixer;
PlayingSoundHandle *_handle;
public:
int _id;
Channel(SoundMixer *mixer, PlayingSoundHandle *handle)
: _mixer(mixer), _handle(handle), _id(-1) {
assert(mixer);
}
virtual ~Channel() {
if (_handle)
*_handle = 0;
}
/* len indicates the number of sample *pairs*. So a value of
10 means that the buffer contains twice 10 sample, each
16 bits, for a total of 40 bytes.
*/
virtual void mix(int16 *data, uint len) = 0;
void destroy() {
for (int i = 0; i != SoundMixer::NUM_CHANNELS; i++)
if (_mixer->_channels[i] == this)
_mixer->_channels[i] = 0;
delete this;
}
virtual bool isActive();
virtual bool isMusicChannel() = 0;
};
class ChannelRaw : public Channel {
byte *_ptr;
byte _flags;
uint32 _pos;
uint32 _size;
uint32 _fpSpeed;
uint32 _fpPos;
uint32 _realSize, _rate;
byte *_loop_ptr;
uint32 _loop_size;
public:
ChannelRaw(SoundMixer *mixer, PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, int id);
~ChannelRaw();
void mix(int16 *data, uint len);
bool isMusicChannel() {
return false; // TODO: Is this correct? Or does anything use ChannelRaw for music?
}
};
class ChannelStream : public Channel {
byte *_ptr;
byte *_endOfData;
byte *_endOfBuffer;
byte *_pos;
uint32 _fpSpeed;
uint32 _fpPos;
uint32 _bufferSize;
uint32 _rate;
byte _flags;
bool _finished;
public:
ChannelStream(SoundMixer *mixer, PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, uint32 buffer_size);
~ChannelStream();
void mix(int16 *data, uint len);
void append(void *sound, uint32 size);
bool isMusicChannel() {
return true;
}
void finish() { _finished = true; }
};
#ifdef USE_MAD
class ChannelMP3Common : public Channel {
protected:
byte *_ptr;
bool _releasePtr;
struct mad_stream _stream;
struct mad_frame _frame;
struct mad_synth _synth;
uint32 _posInFrame;
uint32 _size;
public:
ChannelMP3Common(SoundMixer *mixer, PlayingSoundHandle *handle, uint32 rate);
~ChannelMP3Common();
};
class ChannelMP3 : public ChannelMP3Common {
uint32 _silenceCut;
uint32 _position;
public:
ChannelMP3(SoundMixer *mixer, PlayingSoundHandle *handle, uint32 rate, void *sound, uint size, byte flags);
void mix(int16 *data, uint len);
bool isMusicChannel() { return false; }
};
class ChannelMP3CDMusic : public ChannelMP3Common {
uint32 _bufferSize;
mad_timer_t _duration;
File *_file;
bool _initialized;
public:
ChannelMP3CDMusic(SoundMixer *mixer, PlayingSoundHandle *handle, uint32 rate, File *file, mad_timer_t duration);
void mix(int16 *data, uint len);
bool isActive();
bool isMusicChannel() { return true; }
};
#endif
#ifdef USE_VORBIS
class ChannelVorbis : public Channel {
OggVorbis_File *_ov_file;
int _end_pos;
bool _eof_flag, _is_cd_track;
public:
ChannelVorbis(SoundMixer *mixer, PlayingSoundHandle *handle, OggVorbis_File *ov_file, int duration, bool is_cd_track);
void mix(int16 *data, uint len);
bool isActive();
bool isMusicChannel() {
return _is_cd_track;
}
};
#endif
SoundMixer::SoundMixer() {
_syst = 0;
_mutex = 0;
_premixParam = 0;
_premixProc = 0;
int i = 0;
_outputRate = 0;
_globalVolume = 0;
_musicVolume = 0;
_paused = false;
for (i = 0; i != NUM_CHANNELS; i++)
_channels[i] = NULL;
}
SoundMixer::~SoundMixer() {
_syst->clear_sound_proc();
for (int i = 0; i != NUM_CHANNELS; i++) {
delete _channels[i];
}
_syst->delete_mutex(_mutex);
}
int SoundMixer::newStream(void *sound, uint32 size, uint rate, byte flags, uint32 buffer_size) {
StackLock lock(_mutex);
return insertChannel(NULL, new ChannelStream(this, 0, sound, size, rate, flags, buffer_size));
}
void SoundMixer::appendStream(int index, void *sound, uint32 size) {
StackLock lock(_mutex);
ChannelStream *chan;
#if !defined(_WIN32_WCE) && !defined(__PALM_OS__)
chan = dynamic_cast<ChannelStream *>(_channels[index]);
#else
chan = (ChannelStream*)_channels[index];
#endif
if (!chan) {
error("Trying to append to a nonexistant stream %d", index);
} else {
chan->append(sound, size);
}
}
void SoundMixer::endStream(int index) {
StackLock lock(_mutex);
ChannelStream *chan;
#if !defined(_WIN32_WCE) && !defined(__PALM_OS__)
chan = dynamic_cast<ChannelStream *>(_channels[index]);
#else
chan = (ChannelStream*)_channels[index];
#endif
if (!chan) {
error("Trying to end a nonexistant streamer : %d", index);
} else {
chan->finish();
}
}
int SoundMixer::insertChannel(PlayingSoundHandle *handle, Channel *chan) {
int index = -1;
for (int i = 0; i != NUM_CHANNELS; i++) {
if (_channels[i] == NULL) {
index = i;
break;
}
}
if(index == -1) {
warning("SoundMixer::out of mixer slots");
delete chan;
return -1;
}
_channels[index] = chan;
if (handle)
*handle = index + 1;
return index;
}
int SoundMixer::playRaw(PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, int id) {
StackLock lock(_mutex);
// Prevent duplicate sounds
if (id != -1) {
for (int i = 0; i != NUM_CHANNELS; i++)
if (_channels[i] != NULL && _channels[i]->_id == id)
return -1;
}
return insertChannel(handle, new ChannelRaw(this, handle, sound, size, rate, flags, id));
}
#ifdef USE_MAD
int SoundMixer::playMP3(PlayingSoundHandle *handle, void *sound, uint32 size, byte flags) {
StackLock lock(_mutex);
return insertChannel(handle, new ChannelMP3(this, handle, _syst->property(OSystem::PROP_GET_SAMPLE_RATE, 0), sound, size, flags));
}
int SoundMixer::playMP3CDTrack(PlayingSoundHandle *handle, File *file, mad_timer_t duration) {
StackLock lock(_mutex);
return insertChannel(handle, new ChannelMP3CDMusic(this, handle, _syst->property(OSystem::PROP_GET_SAMPLE_RATE, 0), file, duration));
}
#endif
#ifdef USE_VORBIS
int SoundMixer::playVorbis(PlayingSoundHandle *handle, OggVorbis_File *ov_file, int duration, bool is_cd_track) {
StackLock lock(_mutex);
return insertChannel(handle, new ChannelVorbis(this, handle, ov_file, duration, is_cd_track));
}
#endif
void SoundMixer::mix(int16 *buf, uint len) {
StackLock lock(_mutex);
if (_premixProc && !_paused) {
int i;
_premixProc(_premixParam, buf, len);
for (i = (len - 1); i >= 0; i--) {
buf[2 * i] = buf[2 * i + 1] = buf[i];
}
} else {
// zero the buf out
memset(buf, 0, 2 * len * sizeof(int16));
}
if (!_paused) {
// now mix all channels
for (int i = 0; i != NUM_CHANNELS; i++)
if (_channels[i])
_channels[i]->mix(buf, len);
}
}
void SoundMixer::mixCallback(void *s, byte *samples, int len) {
assert(s);
assert(samples);
// Len is the number of bytes in the buffer; we divide it by
// four to get the number of samples (stereo 16 bit).
((SoundMixer *)s)->mix((int16 *)samples, len >> 2);
}
bool SoundMixer::bindToSystem(OSystem *syst) {
uint rate = (uint) syst->property(OSystem::PROP_GET_SAMPLE_RATE, 0);
_outputRate = rate;
_syst = syst;
_mutex = _syst->create_mutex();
if (rate == 0)
error("OSystem returned invalid sample rate");
return syst->set_sound_proc(mixCallback, this, OSystem::SOUND_16BIT);
}
void SoundMixer::stopAll() {
StackLock lock(_mutex);
for (int i = 0; i != NUM_CHANNELS; i++)
if (_channels[i])
_channels[i]->destroy();
}
void SoundMixer::stop(int index) {
if ((index < 0) || (index >= NUM_CHANNELS)) {
warning("soundMixer::stop has invalid index %d", index);
return;
}
StackLock lock(_mutex);
if (_channels[index])
_channels[index]->destroy();
}
void SoundMixer::stopID(int id) {
StackLock lock(_mutex);
for (int i = 0; i != NUM_CHANNELS; i++) {
if (_channels[i] != NULL && _channels[i]->_id == id) {
_channels[i]->destroy();
return;
}
}
}
void SoundMixer::stopHandle(PlayingSoundHandle handle) {
StackLock lock(_mutex);
// Simply ignore stop requests for handles of sounds that already terminated
if (handle == 0)
return;
int index = handle - 1;
if ((index < 0) || (index >= NUM_CHANNELS)) {
warning("soundMixer::stopHandle has invalid index %d", index);
return;
}
if (_channels[index])
_channels[index]->destroy();
}
void SoundMixer::pause(bool paused) {
_paused = paused;
}
bool SoundMixer::hasActiveSFXChannel() {
// FIXME/TODO: We need to distinguish between SFX and music channels
// (and maybe also voice) here to work properly in iMuseDigital
// games. In the past that was achieve using the _beginSlots hack.
// Since we don't have that anymore, it's not that simple anymore.
StackLock lock(_mutex);
for (int i = 0; i != NUM_CHANNELS; i++)
if (_channels[i] && !_channels[i]->isMusicChannel())
return true;
return false;
}
bool SoundMixer::isChannelActive(int index) {
StackLock lock(_mutex);
if (_channels[index])
return _channels[index]->isActive();
return false;
}
bool SoundMixer::isChannelUsed(int index) {
StackLock lock(_mutex);
return (_channels[index] != NULL);
}
void SoundMixer::setupPremix(void *param, PremixProc *proc) {
StackLock lock(_mutex);
_premixParam = param;
_premixProc = proc;
}
void SoundMixer::setVolume(int volume) {
// Check range
if (volume > 256)
volume = 256;
else if (volume < 0)
volume = 0;
_globalVolume = volume;
}
void SoundMixer::setMusicVolume(int volume) {
// Check range
if (volume > 256)
volume = 256;
else if (volume < 0)
volume = 0;
_musicVolume = volume;
}
/*
* Class that performs cubic interpolation on integer data.
* It is expected that the data is equidistant, i.e. all have the same
* horizontal distance. This is obviously the case for sampled audio.
*/
class CubicInterpolator {
protected:
int x0, x1, x2, x3;
int a, b, c, d;
public:
CubicInterpolator(int8 a0, int8 b0, int8 c0) : x0(2 * a0 - b0), x1(a0), x2(b0), x3(c0) {
// We use a simple linear interpolation for x0
updateCoefficients();
}
inline void feedData() {
x0 = x1;
x1 = x2;
x2 = x3;
x3 = 2 * x2 - x1; // Simple linear interpolation
updateCoefficients();
}
inline void feedData(int8 xNew) {
x0 = x1;
x1 = x2;
x2 = x3;
x3 = xNew;
updateCoefficients();
}
/* t must be a 16.16 fixed point number between 0 and 1 */
inline int interpolate(uint32 fpPos) {
int result = 0;
int t = fpPos >> 8;
result = (a * t + b) >> 8;
result = (result * t + c) >> 8;
result = (result * t + d) >> 8;
result = (result / 3 + 1) >> 1;
return result;
}
protected:
inline void updateCoefficients() {
a = ((-x0 * 2) + (x1 * 5) - (x2 * 4) + x3);
b = ((x0 + x2 - (2 * x1)) * 6) << 8;
c = ((-4 * x0) + x1 + (x2 * 4) - x3) << 8;
d = (x1 * 6) << 8;
}
};
static inline void clamped_add_16(int16& a, int b) {
int val = a + b;
if (val > 32767)
a = 32767;
else if (val < -32768)
a = -32768;
else
a = val;
}
static void mix_signed_mono_8(int16 *data, uint &len, byte *&s, uint32 &fp_pos,
int fp_speed, int volume, byte *s_end, bool reverse_stereo) {
int inc = 1, result;
CubicInterpolator interp(*s, *(s + 1), *(s + 2));
do {
do {
result = interp.interpolate(fp_pos) * volume;
clamped_add_16(*data++, result);
clamped_add_16(*data++, result);
fp_pos += fp_speed;
inc = fp_pos >> 16;
s += inc;
len--;
fp_pos &= 0x0000FFFF;
} while (!inc && len && (s < s_end));
if (s + 2 < s_end)
interp.feedData(*(s + 2));
else
interp.feedData();
} while (len && (s < s_end));
}
static void mix_unsigned_mono_8(int16 *data, uint &len, byte *&s, uint32 &fp_pos,
int fp_speed, int volume, byte *s_end, bool reverse_stereo) {
int inc = 1, result;
CubicInterpolator interp(*s ^ 0x80, *(s + 1) ^ 0x80, *(s + 2) ^ 0x80);
do {
do {
result = interp.interpolate(fp_pos) * volume;
clamped_add_16(*data++, result);
clamped_add_16(*data++, result);
fp_pos += fp_speed;
inc = fp_pos >> 16;
s += inc;
len--;
fp_pos &= 0x0000FFFF;
} while (!inc && len && (s < s_end));
if (s + 2 < s_end)
interp.feedData(*(s + 2) ^ 0x80);
else
interp.feedData();
} while (len && (s < s_end));
}
static void mix_signed_stereo_8(int16 *data, uint &len, byte *&s, uint32 &fp_pos,
int fp_speed, int volume, byte *s_end, bool reverse_stereo) {
warning("Mixing stereo signed 8 bit is not supported yet ");
}
static void mix_unsigned_stereo_8(int16 *data, uint &len, byte *&s, uint32 &fp_pos,
int fp_speed, int volume, byte *s_end, bool reverse_stereo) {
int inc = 1;
CubicInterpolator left(*s ^ 0x80, *(s + 2) ^ 0x80, *(s + 4) ^ 0x80);
CubicInterpolator right(*(s + 1) ^ 0x80, *(s + 3) ^ 0x80, *(s + 5) ^ 0x80);
do {
do {
if (!reverse_stereo) {
clamped_add_16(*data++, left.interpolate(fp_pos) * volume);
clamped_add_16(*data++, right.interpolate(fp_pos) * volume);
} else {
clamped_add_16(*data++, right.interpolate(fp_pos) * volume);
clamped_add_16(*data++, left.interpolate(fp_pos) * volume);
}
fp_pos += fp_speed;
inc = (fp_pos >> 16) << 1;
s += inc;
len--;
fp_pos &= 0x0000FFFF;
} while (!inc && len && (s < s_end));
if (s + 5 < s_end) {
left.feedData(*(s + 4) ^ 0x80);
right.feedData(*(s + 5) ^ 0x80);
} else {
left.feedData();
right.feedData();
}
} while (len && (s < s_end));
}
static void mix_signed_mono_16(int16 *data, uint &len, byte *&s, uint32 &fp_pos,
int fp_speed, int volume, byte *s_end, bool reverse_stereo) {
do {
int16 sample = ((int16)READ_BE_UINT16(s) * volume) / 256;
fp_pos += fp_speed;
clamped_add_16(*data++, sample);
clamped_add_16(*data++, sample);
s += (fp_pos >> 16) << 1;
fp_pos &= 0x0000FFFF;
} while ((--len) && (s < s_end));
}
static void mix_unsigned_mono_16(int16 *data, uint &len, byte *&s, uint32 &fp_pos,
int fp_speed, int volume, byte *s_end, bool reverse_stereo) {
warning("Mixing mono unsigned 16 bit is not supported yet ");
}
static void mix_signed_stereo_16(int16 *data, uint &len, byte *&s, uint32 &fp_pos,
int fp_speed, int volume, byte *s_end, bool reverse_stereo) {
do {
int16 leftS = ((int16)READ_BE_UINT16(s) * volume) / 256;
int16 rightS = ((int16)READ_BE_UINT16(s+2) * volume) / 256;
fp_pos += fp_speed;
if (!reverse_stereo) {
clamped_add_16(*data++, leftS);
clamped_add_16(*data++, rightS);
} else {
clamped_add_16(*data++, rightS);
clamped_add_16(*data++, leftS);
}
s += (fp_pos >> 16) << 2;
fp_pos &= 0x0000FFFF;
} while ((--len) && (s < s_end));
}
static void mix_unsigned_stereo_16(int16 *data, uint &len, byte *&s, uint32 &fp_pos,
int fp_speed, int volume, byte *s_end, bool reverse_stereo) {
warning("Mixing stereo unsigned 16 bit is not supported yet ");
}
typedef void MixProc(int16 *data, uint &len, byte *&s,
uint32 &fp_pos, int fp_speed, int volume,
byte *s_end, bool reverse_stereo);
static MixProc *mixer_helper_table[8] = {
mix_signed_mono_8, mix_unsigned_mono_8,
mix_signed_stereo_8, mix_unsigned_stereo_8,
mix_signed_mono_16, mix_unsigned_mono_16,
mix_signed_stereo_16, mix_unsigned_stereo_16
};
static int16 mixer_element_size[] = {
1, 1,
2, 2,
2, 2,
4, 4
};
bool Channel::isActive() {
error("isActive should never be called on a non-MP3 mixer ");
return true;
}
/* RAW mixer */
ChannelRaw::ChannelRaw(SoundMixer *mixer, PlayingSoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, int id)
: Channel(mixer, handle) {
_id = id;
_ptr = (byte *)sound;
_flags = flags;
_pos = 0;
_fpPos = 0;
_fpSpeed = (1 << 16) * rate / mixer->getOutputRate();
_realSize = size;
// adjust the magnitude to prevent division error
while (size & 0xFFFF0000)
size >>= 1, rate = (rate >> 1) + 1;
_rate = rate;
_size = size * mixer->getOutputRate() / rate;
if (_flags & SoundMixer::FLAG_16BITS)
_size = _size >> 1;
if (_flags & SoundMixer::FLAG_STEREO)
_size = _size >> 1;
if (flags & SoundMixer::FLAG_LOOP) {
_loop_ptr = _ptr;
_loop_size = _size;
}
}
ChannelRaw::~ChannelRaw() {
if (_flags & SoundMixer::FLAG_AUTOFREE)
free(_ptr);
}
void ChannelRaw::mix(int16 *data, uint len) {
byte *s, *end;
if (len > _size)
len = _size;
_size -= len;
s = _ptr + _pos;
end = _ptr + _realSize;
mixer_helper_table[_flags & 0x07] (data, len, s, _fpPos, _fpSpeed, _mixer->getVolume(), end, (_flags & SoundMixer::FLAG_REVERSE_STEREO) ? true : false);
_pos = s - _ptr;
if (_size <= 0) {
if (_flags & SoundMixer::FLAG_LOOP) {
_ptr = _loop_ptr;
_size = _loop_size;
_pos = 0;
_fpPos = 0;
} else {
destroy();
}
}
}
#define WARP_WORKAROUND 50000
ChannelStream::ChannelStream(SoundMixer *mixer, PlayingSoundHandle *handle, void *sound, uint32 size, uint rate,
byte flags, uint32 buffer_size)
: Channel(mixer, handle) {
assert(size <= buffer_size);
_flags = flags;
_bufferSize = buffer_size;
_ptr = (byte *)malloc(_bufferSize + WARP_WORKAROUND);
memcpy(_ptr, sound, size);
_endOfData = _ptr + size;
_endOfBuffer = _ptr + _bufferSize;
_pos = _ptr;
_fpPos = 0;
_fpSpeed = (1 << 16) * rate / mixer->getOutputRate();
_finished = false;
// adjust the magnitude to prevent division error
while (size & 0xFFFF0000)
size >>= 1, rate = (rate >> 1) + 1;
_rate = rate;
}
ChannelStream::~ChannelStream() {
free(_ptr);
}
void ChannelStream::append(void *data, uint32 len) {
if (_endOfData + len > _endOfBuffer) {
/* Wrap-around case */
uint32 size_to_end_of_buffer = _endOfBuffer - _endOfData;
uint32 new_size = len - size_to_end_of_buffer;
if ((_endOfData < _pos) || (_ptr + new_size >= _pos)) {
debug(2, "Mixer full... Trying to not break too much (A)");
return;
}
memcpy(_endOfData, (byte*)data, size_to_end_of_buffer);
memcpy(_ptr, (byte *)data + size_to_end_of_buffer, new_size);
_endOfData = _ptr + new_size;
} else {
if ((_endOfData < _pos) && (_endOfData + len >= _pos)) {
debug(2, "Mixer full... Trying to not break too much (B)");
return;
}
memcpy(_endOfData, data, len);
_endOfData += len;
}
}
void ChannelStream::mix(int16 *data, uint len) {
if (_pos == _endOfData) {
// Normally, the stream stays around even if all its data is used up.
// This is in case more data is streamed into it. To make the stream
// go away, one can either stop() it (which takes effect immediately,
// ignoring any remaining sound data), or finish() it, which means
// it will finish playing before it terminates itself.
if (_finished) {
destroy();
} else {
// Since the buffer is empty now, reset the position to the start
_pos = _endOfData = _ptr;
_fpPos = 0;
}
return;
}
MixProc *mixProc = mixer_helper_table[_flags & 0x07];
if (_pos < _endOfData) {
mixProc(data, len, _pos, _fpPos, _fpSpeed, _mixer->getVolume(), _endOfData, (_flags & SoundMixer::FLAG_REVERSE_STEREO) ? true : false);
} else {
int wrapOffset = 0;
const uint32 outLen = mixer_element_size[_flags & 0x07] * len;
// see if we will wrap
if (_pos + outLen > _endOfBuffer) {
wrapOffset = _pos + outLen - _endOfBuffer;
debug(2, "using wrap workaround for %d bytes", wrapOffset);
assert(wrapOffset <= WARP_WORKAROUND);
memcpy(_endOfBuffer, _ptr, wrapOffset);
}
mixProc(data, len, _pos, _fpPos, _fpSpeed, _mixer->getVolume(), _endOfBuffer + wrapOffset, (_flags & SoundMixer::FLAG_REVERSE_STEREO) ? true : false);
// recover from wrap
if (wrapOffset)
_pos = _ptr + wrapOffset;
// shouldn't happen anymore
if (len != 0) {
//FIXME: what is wrong ?
warning("bad play sound in stream (wrap around)");
_pos = _ptr;
mixProc(data, len, _pos, _fpPos, _fpSpeed, _mixer->getVolume(), _endOfData, (_flags & SoundMixer::FLAG_REVERSE_STEREO) ? true : false);
}
}
}
#ifdef USE_MAD
ChannelMP3Common::ChannelMP3Common(SoundMixer *mixer, PlayingSoundHandle *handle, uint32 rate)
: Channel(mixer, handle) {
mad_stream_init(&_stream);
#ifdef _WIN32_WCE
// Lower sample rate to 11 kHz on WinCE if necessary
if (rate != 22050)
mad_stream_options(&_stream, MAD_OPTION_HALFSAMPLERATE);
#endif
mad_frame_init(&_frame);
mad_synth_init(&_synth);
}
ChannelMP3Common::~ChannelMP3Common() {
if (_releasePtr)
free(_ptr);
mad_synth_finish(&_synth);
mad_frame_finish(&_frame);
mad_stream_finish(&_stream);
}
ChannelMP3::ChannelMP3(SoundMixer *mixer, PlayingSoundHandle *handle, uint32 rate, void *sound, uint size, byte flags)
: ChannelMP3Common(mixer, handle, rate) {
_posInFrame = 0xFFFFFFFF;
_position = 0;
_size = size;
_ptr = (byte *)sound;
_releasePtr = (flags & SoundMixer::FLAG_AUTOFREE) != 0;
/* This variable is the number of samples to cut at the start of the MP3
file. This is needed to have lip-sync as the MP3 file have some miliseconds
of blank at the start (as, I suppose, the MP3 compression algorithm need to
have some silence at the start to really be efficient and to not distort
too much the start of the sample).
This value was found by experimenting out. If you recompress differently your
.SO3 file, you may have to change this value.
When using Lame, it seems that the sound starts to have some volume about 50 ms
from the start of the sound => we skip about 2 frames (at 22.05 khz).
*/
_silenceCut = 576 * 2;
}
static inline int scale_sample(mad_fixed_t sample) {
/* round */
sample += (1L << (MAD_F_FRACBITS - 16));
/* clip */
if (sample >= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample < -MAD_F_ONE)
sample = -MAD_F_ONE;
/* quantize and scale to not saturate when mixing a lot of channels */
return sample >> (MAD_F_FRACBITS + 1 - 16);
}
void ChannelMP3::mix(int16 *data, uint len) {
const int volume = _mixer->getVolume();
while (1) {
/* Skip _silence_cut a the start */
if ((_posInFrame < _synth.pcm.length) && (_silenceCut > 0)) {
uint32 diff = _synth.pcm.length - _posInFrame;
if (diff > _silenceCut)
diff = _silenceCut;
_silenceCut -= diff;
_posInFrame += diff;
}
int16 sample;
while ((_posInFrame < _synth.pcm.length) && (len > 0)) {
sample = (int16)((scale_sample(_synth.pcm.samples[0][_posInFrame]) * volume) / 256);
clamped_add_16(*data++, sample);
if (_synth.pcm.channels > 1)
sample = (int16)((scale_sample(_synth.pcm.samples[1][_posInFrame]) * volume) / 256);
clamped_add_16(*data++, sample);
len--;
_posInFrame++;
}
if (len == 0)
return;
if (_position >= _size) {
destroy();
return;
}
mad_stream_buffer(&_stream, _ptr + _position,
_size + MAD_BUFFER_GUARD - _position);
if (mad_frame_decode(&_frame, &_stream) == -1) {
/* End of audio... */
if (_stream.error == MAD_ERROR_BUFLEN) {
destroy();
return;
} else if (!MAD_RECOVERABLE(_stream.error)) {
error("MAD frame decode error !");
}
}
mad_synth_frame(&_synth, &_frame);
_posInFrame = 0;
_position = _stream.next_frame - _ptr;
}
}
#define MP3CD_BUFFERING_SIZE 131072
ChannelMP3CDMusic::ChannelMP3CDMusic(SoundMixer *mixer, PlayingSoundHandle *handle, uint32 rate, File *file, mad_timer_t duration)
: ChannelMP3Common(mixer, handle, rate) {
_file = file;
_duration = duration;
_initialized = false;
_bufferSize = MP3CD_BUFFERING_SIZE;
_ptr = (byte *)malloc(MP3CD_BUFFERING_SIZE);
_releasePtr = true;
}
void ChannelMP3CDMusic::mix(int16 *data, uint len) {
mad_timer_t frame_duration;
int volume = _mixer->getMusicVolume();
if (!_initialized) {
int skip_loop;
// just skipped
memset(_ptr, 0, _bufferSize);
_size = _file->read(_ptr, _bufferSize);
if (_size <= 0) {
destroy();
return;
}
// Resync
mad_stream_buffer(&_stream, _ptr, _size);
skip_loop = 2;
while (skip_loop != 0) {
if (mad_frame_decode(&_frame, &_stream) == 0) {
/* Do not decrease duration - see if it's a problem */
skip_loop--;
if (skip_loop == 0) {
mad_synth_frame(&_synth, &_frame);
}
} else {
if (!MAD_RECOVERABLE(_stream.error)) {
debug(1, "Unrecoverable error while skipping !");
destroy();
return;
}
}
}
// We are supposed to be in synch
mad_frame_mute(&_frame);
mad_synth_mute(&_synth);
// Resume decoding
if (mad_frame_decode(&_frame, &_stream) == 0) {
_posInFrame = 0;
_initialized = true;
} else {
debug(1, "Cannot resume decoding");
destroy();
return;
}
}
while (1) {
// TODO: Check _synth.pcm.samplerate and perform rate conversion of appropriate
// TODO: Check _synth.pcm.channels to support stereo
// Get samples, play samples ...
int16 sample;
while ((_posInFrame < _synth.pcm.length) && (len > 0)) {
sample = (int16)((scale_sample(_synth.pcm.samples[0][_posInFrame]) * volume) / 256);
clamped_add_16(*data++, sample);
if (_synth.pcm.channels > 1)
sample = (int16)((scale_sample(_synth.pcm.samples[1][_posInFrame]) * volume) / 256);
clamped_add_16(*data++, sample);
len--;
_posInFrame++;
}
if (len == 0)
return;
// See if we have finished
// May be incorrect to check the size at the end of a frame but I suppose
// they are short enough :)
frame_duration = _frame.header.duration;
mad_timer_negate(&frame_duration);
mad_timer_add(&_duration, frame_duration);
if (mad_frame_decode(&_frame, &_stream) == -1) {
if (_stream.error == MAD_ERROR_BUFLEN) {
int not_decoded;
if (!_stream.next_frame) {
memset(_ptr, 0, _bufferSize + MAD_BUFFER_GUARD);
_size = _file->read(_ptr, _bufferSize);
not_decoded = 0;
} else {
not_decoded = _stream.bufend - _stream.next_frame;
memcpy(_ptr, _stream.next_frame, not_decoded);
_size = _file->read(_ptr + not_decoded, _bufferSize - not_decoded);
}
_stream.error = (enum mad_error)0;
// Restream
mad_stream_buffer(&_stream, _ptr, _size + not_decoded);
if (mad_frame_decode(&_frame, &_stream) == -1) {
debug(1, "Error decoding after restream %d !", _stream.error);
}
} else if (!MAD_RECOVERABLE(_stream.error)) {
error("MAD frame decode error in MP3 CDMUSIC !");
}
}
mad_synth_frame(&_synth, &_frame);
_posInFrame = 0;
}
}
bool ChannelMP3CDMusic::isActive() {
return mad_timer_compare(_duration, mad_timer_zero) > 0;
}
#endif
#ifdef USE_VORBIS
ChannelVorbis::ChannelVorbis(SoundMixer *mixer, PlayingSoundHandle *handle, OggVorbis_File *ov_file, int duration, bool is_cd_track)
: Channel(mixer, handle) {
_ov_file = ov_file;
if (duration)
_end_pos = ov_pcm_tell(ov_file) + duration;
else
_end_pos = 0;
_eof_flag = false;
_is_cd_track = is_cd_track;
}
#ifdef CHUNKSIZE
#define VORBIS_TREMOR
#endif
void ChannelVorbis::mix(int16 *data, uint len) {
if (_eof_flag) {
return;
}
int channels = ov_info(_ov_file, -1)->channels;
uint len_left = len * channels * 2;
int16 *samples = new int16[len_left / 2];
char *read_pos = (char *) samples;
int volume = _is_cd_track ? _mixer->getMusicVolume() : _mixer->getVolume();
// Read the samples
while (len_left > 0) {
long result = ov_read(_ov_file, read_pos, len_left,
#ifndef VORBIS_TREMOR
#ifdef SCUMM_BIG_ENDIAN
1,
#else
0,
#endif
2, 1,
#endif
NULL);
if (result == 0) {
_eof_flag = true;
memset(read_pos, 0, len_left);
len_left = 0;
} else if (result == OV_HOLE) {
// Possibly recoverable, just warn about it
warning("Corrupted data in Vorbis file");
} else if (result < 0) {
debug(1, "Decode error %d in Vorbis file", result);
// Don't delete it yet, that causes problems in
// the CD player emulation code.
_eof_flag = true;
memset(read_pos, 0, len_left);
len_left = 0;
} else {
len_left -= result;
read_pos += result;
}
}
// Mix the samples in
for (uint i = 0; i < len; i++) {
int16 sample = (int16) ((int32) samples[i * channels] * volume / 256);
clamped_add_16(*data++, sample);
if (channels > 1)
sample = (int16) ((int32) samples[i * channels + 1] * volume / 256);
clamped_add_16(*data++, sample);
}
delete [] samples;
if (_eof_flag && !_is_cd_track)
destroy();
}
bool ChannelVorbis::isActive() {
return !_eof_flag && !(_end_pos > 0 && ov_pcm_tell(_ov_file) >= _end_pos);
}
#endif