275 lines
7.4 KiB
C++
275 lines
7.4 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/>.
|
|
*
|
|
*/
|
|
|
|
#include "awe/sfx_player.h"
|
|
#include "awe/resource.h"
|
|
#include "awe/sound.h"
|
|
#include "awe/system_stub.h"
|
|
|
|
namespace Awe {
|
|
|
|
void SfxInstrument::clear() {
|
|
data = nullptr;
|
|
volume = 0;
|
|
}
|
|
|
|
void SfxModule::clear() {
|
|
data = nullptr;
|
|
curPos = 0;
|
|
curOrder = 0;
|
|
numOrder = 0;
|
|
orderTable = nullptr;
|
|
|
|
clearSamples();
|
|
}
|
|
|
|
void SfxModule::clearSamples() {
|
|
for (int i = 0; i < 15; ++i)
|
|
samples[i].clear();
|
|
}
|
|
|
|
SfxPlayer::SfxPlayer(Resource *res)
|
|
: _res(res), _delay(0) {
|
|
_playing = false;
|
|
}
|
|
|
|
void SfxPlayer::setEventsDelay(uint16 delay) {
|
|
debugC(kDebugSound, "SfxPlayer::setEventsDelay(%d)", delay);
|
|
_delay = delay;
|
|
}
|
|
|
|
void SfxPlayer::loadSfxModule(uint16 resNum, uint16 delay, uint8 pos) {
|
|
debugC(kDebugSound, "SfxPlayer::loadSfxModule(0x%X, %d, %d)", resNum, delay, pos);
|
|
MemEntry *me = &_res->_memList[resNum];
|
|
if (me->status == Resource::STATUS_LOADED && me->type == Resource::RT_MUSIC) {
|
|
_sfxMod.clear();
|
|
|
|
_sfxMod.curOrder = pos;
|
|
_sfxMod.numOrder = me->bufPtr[0x3F];
|
|
debugC(kDebugSound, "SfxPlayer::loadSfxModule() curOrder = 0x%X numOrder = 0x%X", _sfxMod.curOrder, _sfxMod.numOrder);
|
|
_sfxMod.orderTable = me->bufPtr + 0x40;
|
|
if (delay == 0) {
|
|
_delay = READ_BE_UINT16(me->bufPtr);
|
|
} else {
|
|
_delay = delay;
|
|
}
|
|
_sfxMod.data = me->bufPtr + 0xC0;
|
|
debugC(kDebugSound, "SfxPlayer::loadSfxModule() eventDelay = %d ms", _delay);
|
|
prepareInstruments(me->bufPtr + 2);
|
|
} else {
|
|
warning("SfxPlayer::loadSfxModule() ec=0x%X", 0xF8);
|
|
}
|
|
}
|
|
|
|
void SfxPlayer::prepareInstruments(const uint8 *p) {
|
|
_sfxMod.clearSamples();
|
|
|
|
for (int i = 0; i < 15; ++i) {
|
|
SfxInstrument *ins = &_sfxMod.samples[i];
|
|
const uint16 resNum = READ_BE_UINT16(p); p += 2;
|
|
if (resNum != 0) {
|
|
ins->volume = READ_BE_UINT16(p);
|
|
MemEntry *me = &_res->_memList[resNum];
|
|
if (me->status == Resource::STATUS_LOADED && me->type == Resource::RT_SOUND) {
|
|
ins->data = me->bufPtr;
|
|
debugC(kDebugSound, "Loaded instrument 0x%X n=%d volume=%d", resNum, i, ins->volume);
|
|
} else {
|
|
error("Error loading instrument 0x%X", resNum);
|
|
}
|
|
}
|
|
p += 2; // skip volume
|
|
}
|
|
}
|
|
|
|
void SfxPlayer::play(int rate) {
|
|
_playing = true;
|
|
_rate = rate;
|
|
_samplesLeft = 0;
|
|
|
|
for (int i = 0; i < NUM_CHANNELS; i++) {
|
|
_channels[i].sampleData = nullptr;
|
|
_channels[i].sampleLen = 0;
|
|
_channels[i].sampleLoopPos = 0;
|
|
_channels[i].sampleLoopLen = 0;
|
|
_channels[i].volume = 0;
|
|
_channels[i].pos.inc = 0;
|
|
_channels[i].pos.offset = 0;
|
|
}
|
|
}
|
|
|
|
static int16 toS16(int a) {
|
|
if (a <= -128) {
|
|
return -32768;
|
|
} else if (a >= 127) {
|
|
return 32767;
|
|
} else {
|
|
const uint8 u8 = (a ^ 0x80);
|
|
return ((u8 << 8) | u8) - 32768;
|
|
}
|
|
}
|
|
|
|
static void mixChannel(int16 &s, SfxChannel *ch) {
|
|
if (ch->sampleLen == 0) {
|
|
return;
|
|
}
|
|
const int pos1 = ch->pos.offset >> Frac::BITS;
|
|
ch->pos.offset += ch->pos.inc;
|
|
int pos2 = pos1 + 1;
|
|
if (ch->sampleLoopLen != 0) {
|
|
if (pos1 >= ch->sampleLoopPos + ch->sampleLoopLen - 1) {
|
|
pos2 = ch->sampleLoopPos;
|
|
ch->pos.offset = pos2 << Frac::BITS;
|
|
}
|
|
} else {
|
|
if (pos1 >= ch->sampleLen - 1) {
|
|
ch->sampleLen = 0;
|
|
return;
|
|
}
|
|
}
|
|
int sample = ch->pos.interpolate((int8)ch->sampleData[pos1], (int8)ch->sampleData[pos2]);
|
|
sample = s + toS16(sample * ch->volume / 64);
|
|
s = (sample < -32768 ? -32768 : (sample > 32767 ? 32767 : sample));
|
|
}
|
|
|
|
void SfxPlayer::mixSamples(int16 *buf, int len) {
|
|
while (len != 0) {
|
|
if (_samplesLeft == 0) {
|
|
handleEvents();
|
|
const int samplesPerTick = _rate * (_delay * 60 * 1000 / kPaulaFreq) / 1000;
|
|
_samplesLeft = samplesPerTick;
|
|
}
|
|
int count = _samplesLeft;
|
|
if (count > len) {
|
|
count = len;
|
|
}
|
|
_samplesLeft -= count;
|
|
len -= count;
|
|
for (int i = 0; i < count; ++i) {
|
|
mixChannel(*buf, &_channels[0]);
|
|
mixChannel(*buf, &_channels[3]);
|
|
++buf;
|
|
mixChannel(*buf, &_channels[1]);
|
|
mixChannel(*buf, &_channels[2]);
|
|
++buf;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SfxPlayer::readSamples(int16 *buf, int len) {
|
|
if (_delay != 0) {
|
|
mixSamples(buf, len / 2);
|
|
}
|
|
}
|
|
|
|
void SfxPlayer::start() {
|
|
debugC(kDebugSound, "SfxPlayer::start()");
|
|
_sfxMod.curPos = 0;
|
|
}
|
|
|
|
void SfxPlayer::stop() {
|
|
debugC(kDebugSound, "SfxPlayer::stop()");
|
|
_playing = false;
|
|
}
|
|
|
|
void SfxPlayer::handleEvents() {
|
|
uint8 order = _sfxMod.orderTable[_sfxMod.curOrder];
|
|
const uint8 *patternData = _sfxMod.data + _sfxMod.curPos + order * 1024;
|
|
for (uint8 ch = 0; ch < 4; ++ch) {
|
|
handlePattern(ch, patternData);
|
|
patternData += 4;
|
|
}
|
|
_sfxMod.curPos += 4 * 4;
|
|
debugC(kDebugSound, "SfxPlayer::handleEvents() order = 0x%X curPos = 0x%X", order, _sfxMod.curPos);
|
|
if (_sfxMod.curPos >= 1024) {
|
|
_sfxMod.curPos = 0;
|
|
order = _sfxMod.curOrder + 1;
|
|
if (order == _sfxMod.numOrder) {
|
|
_playing = false;
|
|
}
|
|
_sfxMod.curOrder = order;
|
|
}
|
|
}
|
|
|
|
void SfxPlayer::handlePattern(uint8 channel, const uint8 *data) {
|
|
SfxPattern pat;
|
|
pat.note_1 = READ_BE_UINT16(data + 0);
|
|
pat.note_2 = READ_BE_UINT16(data + 2);
|
|
if (pat.note_1 != 0xFFFD) {
|
|
const uint16 sample = (pat.note_2 & 0xF000) >> 12;
|
|
if (sample != 0) {
|
|
uint8 *ptr = _sfxMod.samples[sample - 1].data;
|
|
if (ptr != nullptr) {
|
|
debugC(kDebugSound, "SfxPlayer::handlePattern() preparing sample %d", sample);
|
|
pat.sampleVolume = _sfxMod.samples[sample - 1].volume;
|
|
pat.sampleStart = 8;
|
|
pat.sampleBuffer = ptr;
|
|
pat.sampleLen = READ_BE_UINT16(ptr) * 2;
|
|
const uint16 loopLen = READ_BE_UINT16(ptr + 2) * 2;
|
|
if (loopLen != 0) {
|
|
pat.loopPos = pat.sampleLen;
|
|
pat.loopLen = loopLen;
|
|
} else {
|
|
pat.loopPos = 0;
|
|
pat.loopLen = 0;
|
|
}
|
|
int16 m = pat.sampleVolume;
|
|
const uint8 effect = (pat.note_2 & 0x0F00) >> 8;
|
|
if (effect == 5) { // volume up
|
|
const uint8 volume = (pat.note_2 & 0xFF);
|
|
m += volume;
|
|
if (m > 0x3F) {
|
|
m = 0x3F;
|
|
}
|
|
} else if (effect == 6) { // volume down
|
|
const uint8 volume = (pat.note_2 & 0xFF);
|
|
m -= volume;
|
|
if (m < 0) {
|
|
m = 0;
|
|
}
|
|
}
|
|
_channels[channel].volume = m;
|
|
pat.sampleVolume = m;
|
|
}
|
|
}
|
|
}
|
|
if (pat.note_1 == 0xFFFD) {
|
|
debugC(kDebugSound, "SfxPlayer::handlePattern() _syncVar = 0x%X", pat.note_2);
|
|
*_syncVar = pat.note_2;
|
|
} else if (pat.note_1 == 0xFFFE) {
|
|
_channels[channel].sampleLen = 0;
|
|
} else if (pat.note_1 != 0 && pat.sampleBuffer != nullptr) {
|
|
assert(pat.note_1 >= 0x37 && pat.note_1 < 0x1000);
|
|
// convert Amiga period value to hz
|
|
const int freq = kPaulaFreq / (pat.note_1 * 2);
|
|
debugC(kDebugSound, "SfxPlayer::handlePattern() adding sample freq = 0x%X", freq);
|
|
SfxChannel *ch = &_channels[channel];
|
|
ch->sampleData = pat.sampleBuffer + pat.sampleStart;
|
|
ch->sampleLen = pat.sampleLen;
|
|
ch->sampleLoopPos = pat.loopPos;
|
|
ch->sampleLoopLen = pat.loopLen;
|
|
ch->volume = pat.sampleVolume;
|
|
ch->pos.offset = 0;
|
|
ch->pos.inc = (freq << Frac::BITS) / _rate;
|
|
}
|
|
}
|
|
|
|
} // namespace Awe
|