390 lines
11 KiB
C++
390 lines
11 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/>.
|
|
*
|
|
*/
|
|
|
|
#ifdef ENABLE_EOB
|
|
|
|
#include "kyra/sound/drivers/pc_base.h"
|
|
#include "audio/audiostream.h"
|
|
#include "common/mutex.h"
|
|
|
|
namespace Kyra {
|
|
|
|
class PCSpeakerDriver : public PCSoundDriver, public Audio::AudioStream {
|
|
public:
|
|
PCSpeakerDriver(Audio::Mixer *mixer, bool pcJRMode);
|
|
~PCSpeakerDriver() override;
|
|
|
|
void initDriver() override;
|
|
void setSoundData(uint8 *data, uint32 size) override;
|
|
void startSound(int id, int) override;
|
|
bool isChannelPlaying(int channel) const override;
|
|
void stopAllChannels() override;
|
|
|
|
void setMusicVolume(uint8 volume) override;
|
|
void setSfxVolume(uint8) override {}
|
|
|
|
void update();
|
|
|
|
// AudioStream interface
|
|
int readBuffer(int16 *buffer, const int numSamples) override;
|
|
bool isStereo() const override { return false; }
|
|
int getRate() const override { return _outputRate; }
|
|
bool endOfData() const override { return false; }
|
|
|
|
private:
|
|
void noteOn(int chan, uint16 period);
|
|
void chanOff(int chan);
|
|
void generateSamples(int16 *buffer, int numSamples);
|
|
|
|
struct Channel {
|
|
Channel(uint8 attnDB) : curSample(32767.0 / pow(2.0, (double)attnDB / 6.0)),
|
|
dataPtr(0), timer(0), timerScale(0), repeatCounter1(0), repeatCounter2(0), period(-1), samplesLeft(0) {}
|
|
const uint8 *dataPtr;
|
|
int16 timer;
|
|
uint8 timerScale;
|
|
uint8 repeatCounter1;
|
|
uint8 repeatCounter2;
|
|
int32 period;
|
|
int32 curSample;
|
|
uint32 samplesLeft;
|
|
};
|
|
|
|
Channel **_channels;
|
|
int _numChannels;
|
|
|
|
const uint8 *_newTrackData;
|
|
const uint8 *_trackData;
|
|
|
|
Common::Mutex _mutex;
|
|
Audio::Mixer *_mixer;
|
|
Audio::SoundHandle _handle;
|
|
|
|
uint _outputRate;
|
|
int _samplesUpdateIntv;
|
|
int _samplesUpdateIntvRem;
|
|
int _samplesUpdateTmr;
|
|
int _samplesUpdateTmrRem;
|
|
|
|
int _masterVolume;
|
|
bool _ready;
|
|
|
|
const int _clock;
|
|
const int _updateRate;
|
|
const bool _pcJR;
|
|
const int _periodDiv;
|
|
const int _levelAdjust;
|
|
const uint16 * const _periodsTable;
|
|
|
|
static const uint16 _periodsPCSpk[96];
|
|
static const uint16 _periodsPCjr[96];
|
|
};
|
|
|
|
PCSpeakerDriver::PCSpeakerDriver(Audio::Mixer *mixer, bool pcJRMode) : PCSoundDriver(), _mixer(mixer), _samplesUpdateIntv(0), _samplesUpdateIntvRem(0),
|
|
_outputRate(0), _samplesUpdateTmr(0), _samplesUpdateTmrRem(0), _newTrackData(0), _trackData(0), _pcJR(pcJRMode), _numChannels(pcJRMode ? 3 : 1), _channels(0),
|
|
_clock(pcJRMode ? 111860 : 1193180), _updateRate(292), _masterVolume(63), _periodsTable(pcJRMode ? _periodsPCjr : _periodsPCSpk), _periodDiv(2),
|
|
_levelAdjust(pcJRMode ? 1 : 0), _ready(false) {
|
|
_outputRate = _mixer->getOutputRate();
|
|
_samplesUpdateIntv = _outputRate / _updateRate;
|
|
_samplesUpdateIntvRem = _outputRate % _updateRate;
|
|
|
|
_channels = new Channel*[_numChannels];
|
|
assert(_channels);
|
|
for (int i = 0; i < _numChannels; ++i) {
|
|
_channels[i] = new Channel(i * 10);
|
|
assert(_channels[i]);
|
|
}
|
|
}
|
|
|
|
PCSpeakerDriver::~PCSpeakerDriver() {
|
|
_ready = false;
|
|
_mixer->stopHandle(_handle);
|
|
|
|
if (_channels) {
|
|
for (int i = 0; i < _numChannels; ++i)
|
|
delete _channels[i];
|
|
delete[] _channels;
|
|
}
|
|
}
|
|
|
|
void PCSpeakerDriver::initDriver() {
|
|
if (_ready)
|
|
return;
|
|
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
|
|
_ready = true;
|
|
}
|
|
|
|
void PCSpeakerDriver::setSoundData(uint8 *data, uint32 size) {
|
|
Common::StackLock lock(_mutex);
|
|
if (!_ready)
|
|
return;
|
|
|
|
_soundData = data;
|
|
_soundDataSize = size;
|
|
}
|
|
|
|
void PCSpeakerDriver::startSound(int id, int) {
|
|
Common::StackLock lock(_mutex);
|
|
if (!_ready)
|
|
return;
|
|
_newTrackData = getProgram(id & 0x7F);
|
|
}
|
|
|
|
bool PCSpeakerDriver::isChannelPlaying(int channel) const {
|
|
Common::StackLock lock(_mutex);
|
|
if (!_ready)
|
|
return false;
|
|
return _trackData;
|
|
}
|
|
|
|
void PCSpeakerDriver::stopAllChannels() {
|
|
Common::StackLock lock(_mutex);
|
|
if (!_ready)
|
|
return;
|
|
for (int i = 0; i < _numChannels; ++i)
|
|
chanOff(i);
|
|
_trackData = 0;
|
|
}
|
|
|
|
void PCSpeakerDriver::setMusicVolume(uint8 volume) {
|
|
Common::StackLock lock(_mutex);
|
|
_masterVolume = volume >> 2;
|
|
}
|
|
|
|
void PCSpeakerDriver::update() {
|
|
Common::StackLock lock(_mutex);
|
|
if (!_ready)
|
|
return;
|
|
|
|
if (_newTrackData) {
|
|
_trackData = _newTrackData;
|
|
_newTrackData = 0;
|
|
|
|
for (int i = _numChannels - 1; i >= 0; --i) {
|
|
_channels[i]->dataPtr = _trackData;
|
|
_channels[i]->timer = i * 35;
|
|
_channels[i]->timerScale = 1;
|
|
}
|
|
}
|
|
|
|
for (int i = _numChannels - 1; i >= 0; --i) {
|
|
const uint8 *pos = _channels[i]->dataPtr;
|
|
if (!pos)
|
|
continue;
|
|
|
|
for (bool runloop = true; runloop; ) {
|
|
if (--_channels[i]->timer > -1)
|
|
break;
|
|
_channels[i]->timer = 0;
|
|
|
|
int8 cmd = (int8)*pos++;
|
|
if (cmd >= 0) {
|
|
if (cmd > 95)
|
|
cmd = 0;
|
|
|
|
noteOn(i, _periodsTable[cmd]);
|
|
uint8 nextTimer = 1 + *pos++;
|
|
_channels[i]->timer = _channels[i]->timerScale * nextTimer;
|
|
|
|
} else {
|
|
switch (cmd) {
|
|
case -23: {
|
|
uint16 ts = _channels[i]->timerScale + *pos++;
|
|
_channels[i]->timerScale = (uint8)MIN<uint16>(ts, 0xFF);
|
|
} break;
|
|
|
|
case -24: {
|
|
int16 ts = _channels[i]->timerScale - *pos++;
|
|
_channels[i]->timerScale = (uint8)MAX<int16>(ts, 1);
|
|
} break;
|
|
|
|
case -26: {
|
|
uint16 prd = _clock / READ_LE_UINT16(pos);
|
|
if (_pcJR && prd >= 0x400)
|
|
prd = 0x3FF;
|
|
pos += 2;
|
|
noteOn(i, prd);
|
|
uint8 nextTimer = 1 + *pos++;
|
|
_channels[i]->timer = _channels[i]->timerScale * nextTimer;
|
|
} break;
|
|
|
|
case -30: {
|
|
_channels[i]->timerScale = *pos++;
|
|
if (!_channels[i]->timerScale)
|
|
_channels[i]->timerScale = 1;
|
|
} break;
|
|
|
|
case -46: {
|
|
if (--_channels[i]->repeatCounter2)
|
|
pos -= *pos;
|
|
else
|
|
pos += 2;
|
|
} break;
|
|
|
|
case -47: {
|
|
_channels[i]->repeatCounter2 = *pos++;
|
|
if (!_channels[i]->repeatCounter2)
|
|
_channels[i]->repeatCounter2 = 1;
|
|
} break;
|
|
|
|
case -50: {
|
|
if (--_channels[i]->repeatCounter1)
|
|
pos -= *pos;
|
|
else
|
|
pos += 2;
|
|
} break;
|
|
|
|
case -51: {
|
|
_channels[i]->repeatCounter1 = *pos++;
|
|
if (!_channels[i]->repeatCounter1)
|
|
_channels[i]->repeatCounter1 = 1;
|
|
} break;
|
|
|
|
default:
|
|
chanOff(i);
|
|
pos = 0;
|
|
runloop = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
_channels[i]->dataPtr = pos;
|
|
}
|
|
}
|
|
|
|
int PCSpeakerDriver::readBuffer(int16 *buffer, const int numSamples) {
|
|
Common::StackLock lock(_mutex);
|
|
if (!_ready)
|
|
return 0;
|
|
|
|
int render = 0;
|
|
|
|
for (int samplesLeft = numSamples; samplesLeft; samplesLeft -= render) {
|
|
if (_samplesUpdateTmr <= 0) {
|
|
_samplesUpdateTmr += _samplesUpdateIntv;
|
|
update();
|
|
}
|
|
|
|
_samplesUpdateTmrRem += _samplesUpdateIntvRem;
|
|
while (_samplesUpdateTmrRem >= _updateRate) {
|
|
_samplesUpdateTmr++;
|
|
_samplesUpdateTmrRem -= _updateRate;
|
|
}
|
|
|
|
render = MIN<int>(_samplesUpdateTmr, samplesLeft);
|
|
_samplesUpdateTmr -= render;
|
|
|
|
generateSamples(buffer, render);
|
|
buffer += render;
|
|
}
|
|
|
|
return numSamples;
|
|
}
|
|
|
|
void PCSpeakerDriver::noteOn(int chan, uint16 period) {
|
|
if (chan >= _numChannels)
|
|
return;
|
|
|
|
if (period == 0) {
|
|
chanOff(chan);
|
|
return;
|
|
}
|
|
|
|
uint32 p = (_outputRate << 10) / ((_clock << 10) / period);
|
|
if (_channels[chan]->period == -1 || _channels[chan]->samplesLeft == 0)
|
|
_channels[chan]->samplesLeft = p / _periodDiv;
|
|
_channels[chan]->period = p & 0xFFFF;
|
|
}
|
|
|
|
void PCSpeakerDriver::chanOff(int chan) {
|
|
if (chan >= _numChannels)
|
|
return;
|
|
_channels[chan]->period = -1;
|
|
}
|
|
|
|
void PCSpeakerDriver::generateSamples(int16 *buffer, int numSamples) {
|
|
int render = 0;
|
|
for (int samplesLeft = numSamples; samplesLeft; samplesLeft -= render) {
|
|
render = samplesLeft;
|
|
|
|
for (int i = _numChannels - 1; i >= 0; --i)
|
|
if (_channels[i]->period != -1)
|
|
render = MIN<int>(render, _channels[i]->samplesLeft);
|
|
|
|
int32 smp = 0;
|
|
for (int i = _numChannels - 1; i >= 0; --i)
|
|
if (_channels[i]->period != -1)
|
|
smp += _channels[i]->curSample;
|
|
smp = (smp * _masterVolume) >> (8 + _levelAdjust);
|
|
|
|
Common::fill<int16*, int16>(buffer, &buffer[render], smp);
|
|
buffer += render;
|
|
|
|
for (int i = _numChannels - 1; i >= 0; --i) {
|
|
if (_channels[i]->period == -1)
|
|
continue;
|
|
|
|
_channels[i]->samplesLeft -= render;
|
|
if (_channels[i]->samplesLeft == 0) {
|
|
_channels[i]->samplesLeft = _channels[i]->period / _periodDiv;
|
|
_channels[i]->curSample = ~_channels[i]->curSample;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const uint16 PCSpeakerDriver::_periodsPCSpk[96] = {
|
|
0x0000, 0xfdff, 0xefa2, 0xe241, 0xd582, 0xc998, 0xbe3d, 0xb38a,
|
|
0xa97c, 0x9ff2, 0x96fc, 0x8e89, 0x8683, 0x7ef7, 0x77d9, 0x7121,
|
|
0x6ac7, 0x64c6, 0x5f1f, 0x59ca, 0x54be, 0x4ffd, 0x4b7e, 0x4742,
|
|
0x4342, 0x3f7b, 0x3bdb, 0x388f, 0x3562, 0x3263, 0x2f8f, 0x2ce4,
|
|
0x2a5f, 0x27fe, 0x25c0, 0x23a1, 0x21a1, 0x1fbe, 0x1df6, 0x1c48,
|
|
0x1ab1, 0x1932, 0x17c8, 0x1672, 0x1530, 0x13ff, 0x12e0, 0x11d1,
|
|
0x10d1, 0x0fdf, 0x0efb, 0x0e24, 0x0d59, 0x0c99, 0x0be4, 0x0b39,
|
|
0x0a98, 0x0a00, 0x0970, 0x08e8, 0x0868, 0x07f0, 0x077e, 0x0712,
|
|
0x06ac, 0x064c, 0x05f2, 0x059d, 0x054c, 0x0500, 0x04b8, 0x0474,
|
|
0x0434, 0x03f8, 0x03bf, 0x0382, 0x0356, 0x0326, 0x02f9, 0x02ce,
|
|
0x02a6, 0x0280, 0x025c, 0x023a, 0x021a, 0x01fc, 0x01df, 0x01c4,
|
|
0x01ab, 0x0193, 0x017c, 0x0167, 0x0153, 0x0140, 0x012e, 0x011d
|
|
};
|
|
|
|
const uint16 PCSpeakerDriver::_periodsPCjr[96] = {
|
|
0x0000, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
|
|
0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
|
|
0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
|
|
0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff, 0x03ff,
|
|
0x03f9, 0x03c0, 0x038a, 0x0357, 0x0327, 0x02fa, 0x02cf, 0x02a7,
|
|
0x0281, 0x025d, 0x023b, 0x021b, 0x01fc, 0x01e0, 0x01c5, 0x01ac,
|
|
0x0194, 0x017d, 0x0168, 0x0153, 0x0140, 0x012e, 0x011d, 0x010d,
|
|
0x00fe, 0x00f0, 0x00e2, 0x00d6, 0x00ca, 0x00be, 0x00b4, 0x00aa,
|
|
0x00a0, 0x0097, 0x008f, 0x0087, 0x007f, 0x0078, 0x0071, 0x006b,
|
|
0x0065, 0x005f, 0x005a, 0x0054, 0x0050, 0x004c, 0x0047, 0x0043,
|
|
0x0040, 0x003c, 0x0039, 0x0035, 0x0032, 0x0030, 0x002d, 0x002a,
|
|
0x0028, 0x0026, 0x0024, 0x0022, 0x0020, 0x001e, 0x001c, 0x001b
|
|
};
|
|
|
|
PCSoundDriver *PCSoundDriver::createPCSpk(Audio::Mixer *mixer, bool pcJRMode) {
|
|
return new PCSpeakerDriver(mixer, pcJRMode);
|
|
}
|
|
|
|
} // End of namespace Kyra
|
|
|
|
#endif
|