Files
2026-02-02 04:50:13 +01:00

1077 lines
27 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/kyra_v1.h"
#include "kyra/sound/drivers/segacd.h"
#include "audio/softsynth/fmtowns_pc98/sega_audio.h"
#include "common/endian.h"
#include "common/ptr.h"
#define SEGA_SND_DEBUG_EXT 0
namespace Kyra {
class SegaAudioDriverInternal;
class SegaAudioChannel {
public:
SegaAudioChannel(uint8 id, SegaAudioInterface *sai);
virtual ~SegaAudioChannel() {}
void initTrack();
bool update();
void lock() { _lock = true; }
void unlock() { _lock = false; }
protected:
uint8 setCountDown();
void startVbr();
uint16 getFrequency(uint8 note);
void cmd_setTempo();
void cmd_programChange();
void cmd_setVolume();
void cmd_incVolume();
void cmd_decVolume();
void cmd_modVolume();
void cmd_setOctave();
void cmd_incOctave();
void cmd_decOctave();
void cmd_modOctave();
void cmd_setNoteLen();
void cmd_setReleaseTimer();
void cmd_writeReg();
void cmd_setRepeatMarker();
void cmd_repeatFromMarker();
void cmd_removeRepeatMarker();
void cmd_beginRepeatSection();
void cmd_jump();
void cmd_jumpToSubroutine();
void cmd_returnFromSubroutine();
void cmd_initVbr();
void cmd_pitchBend();
void cmd_initCstVbr();
void cmd_enableTwoChanMode();
void cmd_disableTwoChanMode();
void cmd_panCenter();
void cmd_panLeft();
void cmd_panRight();
void cmd_UNK29();
void cmd_void() {}
void cmd_transpose();
#if SEGA_SND_DEBUG_EXT
typedef Common::Functor0Mem<void, SegaAudioChannel> SegaSndFunc;
struct SegaSndOpcode {
SegaSndOpcode(SegaSndFunc *func, const char *desc, int dataLen) : _func(func), _desc(desc), _dataLen(dataLen) {}
~SegaSndOpcode() { delete _func; }
bool isValid() { return _func->isValid(); }
void operator()(int dbgInfoChan, const uint8 *dbgInfoData) const {
Common::String dstr = "";
for (int i = 0; i < _dataLen; ++i)
dstr += Common::String::format("0x%02x ", dbgInfoData[i]);
debugC(3, kDebugLevelSound, "Channel %d: %s() [ %s]", dbgInfoChan, _desc, dstr.c_str());
(*_func)();
}
SegaSndFunc *_func;
const char *_desc;
const int _dataLen;
};
#else
typedef Common::Functor0Mem<void, SegaAudioChannel> SegaSndOpcode;
#endif
typedef Common::SharedPtr<SegaSndOpcode> PSegaSndOpcode;
Common::Array<PSegaSndOpcode> _opcodes;
uint8 _id;
uint8 _para1;
uint8 _countDown;
uint8 _releaseTimer;
uint8 _noteLen;
uint8 _volume;
uint8 _octave;
uint16 _frequency;
int16 _pitchBend;
int8 _transpose;
uint8 _vbrTempo;
int16 _vbrState;
int16 _vbrIncStart;
uint8 _vbrSteps;
uint8 _vbrDelay;
uint8 _vbrTempoCurState;
int16 _vbrInc;
uint8 _vbrStepsCounter;
uint8 _vbrDelayCountDown;
uint8 _cstVbrEnable;
uint8 _cstVbrDelay;
uint8 _cstVbrDelayCountDown;
const uint8 *_cstVbrData;
const uint8 *_cstVbrDataCur;
const uint8 *_cstVbrDataTmp;
bool _lock;
const uint8 *_dataPtr;
SegaAudioInterface *_sga;
private:
virtual void keyOff() = 0;
virtual void restoreTone() {}
virtual void fadeUpdate() {}
virtual bool setupTone() = 0;
virtual void setVolume(uint8 vol) = 0;
virtual void sendVolume(uint8 vol) = 0;
virtual void programChange() = 0;
virtual void setPanPos(uint8 pan) {}
virtual void sendFrequency(uint16 freq) = 0;
virtual void toggleSpecialMode(bool enable) {}
virtual void updateEnvelope() {}
virtual const uint16 *freqTable() = 0;
struct Marker {
Marker() : counter(0), pos(0) {}
Marker(uint8 c, const uint8 *p) : counter(c), pos(p) {}
bool operator==(const uint8 *p) const { return (pos == p); }
uint8 counter;
const uint8 *pos;
};
Common::Array<Marker> _repeatMarkers;
Common::Array<Marker> _sectionMarkers;
Common::Array<const uint8*> _returnMarkers;
};
class SegaAudioChannel_FM : public SegaAudioChannel {
public:
SegaAudioChannel_FM(uint8 id, SegaAudioInterface *sai, uint8 part, uint8 regOffs);
~SegaAudioChannel_FM() override {}
private:
void keyOff() override;
void fadeUpdate() override;
bool setupTone() override;
void setVolume(uint8 vol) override;
void sendVolume(uint8 vol) override;
void programChange() override;
void setPanPos(uint8 pan) override;
void sendFrequency(uint16 freq) override;
void toggleSpecialMode(bool enable) override;
const uint16 *freqTable() override { return _freqTable; }
uint8 _algorithm;
uint8 _program;
uint8 _cfreqReg;
const uint8 _part;
const uint8 _regOffs;
const uint8 _regKeyOn;
const uint8 _regKeyOff;
static const uint16 _freqTable[97];
};
class SegaAudioChannel_SG : public SegaAudioChannel {
public:
SegaAudioChannel_SG(uint8 id, SegaAudioInterface *sai, uint8 regOffs);
~SegaAudioChannel_SG() override {}
private:
void keyOff() override;
void restoreTone() override;
bool setupTone() override;
void setVolume(uint8 vol) override {}
void sendVolume(uint8 vol) override;
void programChange() override;
void sendFrequency(uint16 freq) override;
void updateEnvelope() override;
const uint16 *freqTable() override { return _freqTable; }
uint8 _envDelay;
uint8 _volume2;
const uint8 *_envDataAtt;
const uint8 *_envDataRel;
const uint8 *_envDataCur;
const uint8 _regOffs;
enum EnvState {
kDisabled = 0,
kAttack,
kSustain,
kRelease,
kReady
};
int _envState;
static const uint16 _freqTable[97];
};
class SegaAudioChannel_NG : public SegaAudioChannel_FM {
public:
SegaAudioChannel_NG(uint8 id, SegaAudioInterface *sai);
~SegaAudioChannel_NG() override {}
private:
};
class SegaAudioDriverInternal : public SegaAudioPluginDriver {
public:
SegaAudioDriverInternal(Audio::Mixer *mixer);
~SegaAudioDriverInternal();
static SegaAudioDriverInternal *open(Audio::Mixer *mixer);
static void close();
void startFMSound(const uint8 *trackData, uint8 volume, uint8 prioFlags);
void loadPCMData(uint16 address, const uint8 *data, uint16 dataSize);
void startPCMSound(uint8 channel, uint8 dataStart, uint16 loopStart, uint16 step, uint8 pan, uint8 vol);
void setMusicVolume(int volume);
void setSoundEffectVolume(int volume);
void timerCallbackA() override;
void timerCallbackB() override;
static uint8 calcVolume(int vol);
static int getFadeState();
static const uint8 *getTrack();
static const uint8 *getTrack(int channel);
static const uint8 *getProgram(int instrument);
private:
void start();
void stop();
void fade();
void update();
SegaAudioChannel **_channels;
bool _priority;
uint8 _isPlaying;
uint8 _sfxInternal_a;
static const uint8 *_trackData;
static uint8 _fadeTicker;
static uint8 _attenuation;
static uint8 _fadeAttenuation;
SegaAudioInterface *_sga;
static SegaAudioDriverInternal *_refInstance;
static int _refCount;
bool _ready;
};
SegaAudioChannel::SegaAudioChannel(uint8 id, SegaAudioInterface *sai) : _id(id), _sga(sai), _para1(0), _countDown(0), _dataPtr(0),
_volume(0), _octave(0), _releaseTimer(0), _noteLen(0), _vbrTempo(0), _vbrState(0), _vbrIncStart(0), _vbrSteps(0),
_vbrDelay(0), _vbrTempoCurState(0), _vbrInc(0), _vbrStepsCounter(0), _vbrDelayCountDown(0), _frequency(0), _pitchBend(0),
_transpose(0), _cstVbrEnable(0), _cstVbrDelay(0), _cstVbrDelayCountDown(0), _cstVbrData(0), _cstVbrDataCur(0), _lock(false),
_cstVbrDataTmp(0) {
#if SEGA_SND_DEBUG_EXT
#define SGC(x, y) _opcodes.push_back(PSegaSndOpcode(new SegaSndOpcode(new SegaSndFunc(this, &SegaAudioChannel::x), #x, y)))
#else
#define SGC(x, y) _opcodes.push_back(PSegaSndOpcode(new SegaSndOpcode(this, &SegaAudioChannel::x)))
#endif
#define SGCR(x) _opcodes.push_back(_opcodes[x])
SGC(cmd_setTempo, 1);
SGC(cmd_programChange, 1);
SGCR(1);
SGC(cmd_setVolume, 1);
SGC(cmd_incVolume, 0);
SGC(cmd_decVolume, 0);
SGC(cmd_modVolume, 1);
SGC(cmd_setOctave, 1);
SGC(cmd_incOctave, 0);
SGC(cmd_decOctave, 0);
SGC(cmd_modOctave, 1);
SGC(cmd_setNoteLen, 1);
SGC(cmd_setReleaseTimer, 1);
SGC(cmd_writeReg, 2);
SGC(cmd_setRepeatMarker, 1);
SGC(cmd_repeatFromMarker, 0);
SGC(cmd_removeRepeatMarker, 0);
SGC(cmd_beginRepeatSection, 2);
SGC(cmd_jump, 2);
SGC(cmd_jumpToSubroutine, 2);
SGC(cmd_returnFromSubroutine, 0);
SGC(cmd_initVbr, 5);
SGC(cmd_pitchBend, 1);
SGC(cmd_initCstVbr, 2);
SGC(cmd_enableTwoChanMode, 0);
SGC(cmd_disableTwoChanMode, 0);
SGC(cmd_panCenter, 0);
SGC(cmd_panLeft, 0);
SGC(cmd_panRight, 0);
SGC(cmd_UNK29, 1);
SGC(cmd_void, 0);
SGC(cmd_transpose, 1);
#undef SGC
#undef SGCR
}
void SegaAudioChannel::initTrack() {
_dataPtr = SegaAudioDriverInternal::getTrack(_id);
_para1 = 0;
_countDown = 1;
_repeatMarkers.clear();
_returnMarkers.clear();
_sectionMarkers.clear();
_cstVbrData = _cstVbrDataCur = _cstVbrDataTmp = 0;
_cstVbrEnable = _vbrTempo = 0;
_pitchBend = _vbrState = 0;
_transpose = 0;
_volume = 0;
_lock = false;
}
bool SegaAudioChannel::update() {
/*
not implemented/required: sfx channel lock handling
restoreTone();
*/
fadeUpdate();
if (!_dataPtr)
return true;
if (!--_countDown) {
do {
uint8 cmd = *_dataPtr;
while (!(cmd & 0x80)) {
++_dataPtr;
if (_opcodes[cmd]->isValid())
#if SEGA_SND_DEBUG_EXT
(*_opcodes[cmd])(_id, _dataPtr);
#else
(*_opcodes[cmd])();
#endif
cmd = *_dataPtr;
}
if (cmd == 0xFF)
return false;
} while (setupTone());
}
//if (_type != 2) {
if (!(_para1 & 0x10) && _countDown == _releaseTimer) {
keyOff();
}
//}
uint8 vbrSet = 0;
if (_vbrTempo) {
if (_vbrDelayCountDown) {
--_vbrDelayCountDown;
} else if (!--_vbrTempoCurState) {
vbrSet = _vbrTempoCurState = _vbrTempo;
_vbrState += _vbrInc;
if (!--_vbrStepsCounter) {
_vbrStepsCounter += (_vbrSteps + _vbrSteps);
_vbrInc = -_vbrInc;
}
}
}
if (_cstVbrEnable & 1) {
if (_cstVbrDelayCountDown) {
--_cstVbrDelayCountDown;
} else {
assert(_cstVbrDataCur);
const uint8 *in = _cstVbrDataCur;
for (bool cont = true; cont; ) {
uint8 lo = *in++;
uint8 hi = *in++;
if ((hi & 0xF8) != 0x80) {
_vbrState += (int16)((hi << 8) | lo);
cont = false;
vbrSet = 1;
} else {
switch (hi & 0x0F) {
case 0:
_cstVbrDataTmp = in;
break;
case 1:
assert(_cstVbrDataTmp);
in = _cstVbrDataTmp;
break;
case 2:
_cstVbrDelayCountDown = lo;
cont = false;
break;
case 3:
_cstVbrEnable += _para1;
cont = false;
break;
default:
error("SegaAudioChannel::update(): Unknown error");
break;
}
}
}
_cstVbrDataCur = in;
}
}
if (vbrSet)
sendFrequency(_frequency + _vbrState);
updateEnvelope();
_lock = false;
return true;
}
uint8 SegaAudioChannel::setCountDown() {
_para1 = *_dataPtr++;
_countDown = (_para1 & 0x20) ? *_dataPtr++ : _noteLen;
debugC(3, kDebugLevelSound, "Channel %d: Note %d, Duration %d", _id, _octave * 12 + (_para1 & 0x0F), _countDown);
return _para1;
}
void SegaAudioChannel::startVbr() {
if (_cstVbrEnable) {
_cstVbrEnable = 1;
_cstVbrDataCur = _cstVbrData;
_cstVbrDelayCountDown = _cstVbrDelay;
}
if (_vbrTempo) {
_vbrTempoCurState = _vbrTempo;
_vbrInc = _vbrIncStart;
_vbrStepsCounter = _vbrSteps;
_vbrDelayCountDown = _vbrDelay;
}
_vbrState = 0;
}
uint16 SegaAudioChannel::getFrequency(uint8 note) {
_frequency = freqTable()[_octave * 12 + note + _transpose] + _pitchBend;
return _frequency;
}
void SegaAudioChannel::cmd_setTempo() {
_sga->writeReg(0, 0x26, *_dataPtr++);
}
void SegaAudioChannel::cmd_programChange() {
programChange();
}
void SegaAudioChannel::cmd_setVolume() {
_volume = *_dataPtr++;
setVolume(_volume);
}
void SegaAudioChannel::cmd_incVolume() {
setVolume(++_volume);
}
void SegaAudioChannel::cmd_decVolume() {
setVolume(--_volume);
}
void SegaAudioChannel::cmd_modVolume() {
_volume += *_dataPtr++;
setVolume(_volume);
}
void SegaAudioChannel::cmd_setOctave() {
_octave = *_dataPtr++ - 1;
_transpose = 0;
}
void SegaAudioChannel::cmd_incOctave() {
_octave++;
}
void SegaAudioChannel::cmd_decOctave() {
_octave--;
}
void SegaAudioChannel::cmd_modOctave() {
_octave += *_dataPtr++;
}
void SegaAudioChannel::cmd_setNoteLen() {
_noteLen = *_dataPtr++;
}
void SegaAudioChannel::cmd_setReleaseTimer() {
_releaseTimer = *_dataPtr++;
}
void SegaAudioChannel::cmd_writeReg() {
uint8 reg = *_dataPtr++;
uint8 val = *_dataPtr++;
if (!_lock)
_sga->writeReg(_id >= 7 ? 1 : 0, reg, val);
}
void SegaAudioChannel::cmd_setRepeatMarker() {
_repeatMarkers.push_back(Marker(*_dataPtr, _dataPtr + 1));
_dataPtr++;
}
void SegaAudioChannel::cmd_repeatFromMarker() {
assert(!_repeatMarkers.empty());
if (--_repeatMarkers.back().counter)
_dataPtr = _repeatMarkers.back().pos;
else
_repeatMarkers.pop_back();
}
void SegaAudioChannel::cmd_removeRepeatMarker() {
assert(!_repeatMarkers.empty());
_repeatMarkers.pop_back();
}
void SegaAudioChannel::cmd_beginRepeatSection() {
uint8 totalReps = *_dataPtr++;
Common::Array<Marker>::iterator i = Common::find(_sectionMarkers.begin(), _sectionMarkers.end(), _dataPtr);
if (i == _sectionMarkers.end()) {
_sectionMarkers.push_back(Marker(*_dataPtr, _dataPtr));
i = _sectionMarkers.end() - 1;
}
_dataPtr++;
if (++i->counter == totalReps) {
_sectionMarkers.erase(i);
cmd_jump();
} else {
_dataPtr += 2;
}
}
void SegaAudioChannel::cmd_jump() {
uint16 offset = READ_LE_UINT16(_dataPtr);
_dataPtr = SegaAudioDriverInternal::getTrack() + offset;
}
void SegaAudioChannel::cmd_jumpToSubroutine() {
_returnMarkers.push_back(_dataPtr + 2);
cmd_jump();
}
void SegaAudioChannel::cmd_returnFromSubroutine() {
_dataPtr = _returnMarkers.back();
assert(_dataPtr);
_returnMarkers.pop_back();
}
void SegaAudioChannel::cmd_initVbr() {
_vbrTempo = *_dataPtr++;
if (!_vbrTempo)
return;
_vbrIncStart = READ_LE_INT16(_dataPtr);
_dataPtr += 2;
_vbrInc = 0;
_vbrSteps = *_dataPtr++;
_vbrDelay = *_dataPtr++;
}
void SegaAudioChannel::cmd_pitchBend() {
_pitchBend = (int8)*_dataPtr++;
if (_id == 10)
_pitchBend <<= 4;
}
void SegaAudioChannel::cmd_initCstVbr() {
_cstVbrEnable = *_dataPtr++;
if (!_cstVbrEnable)
return;
_cstVbrData = SegaAudioDriverInternal::getProgram(_cstVbrEnable);
_cstVbrEnable = 2;
_cstVbrDelay = *_dataPtr++;
}
void SegaAudioChannel::cmd_enableTwoChanMode() {
toggleSpecialMode(true);
}
void SegaAudioChannel::cmd_disableTwoChanMode() {
toggleSpecialMode(false);
}
void SegaAudioChannel::cmd_panCenter() {
setPanPos(0xC0);
}
void SegaAudioChannel::cmd_panLeft() {
setPanPos(0x80);
}
void SegaAudioChannel::cmd_panRight() {
setPanPos(0x40);
}
void SegaAudioChannel::cmd_UNK29() {
_dataPtr++;
}
void SegaAudioChannel::cmd_transpose() {
_transpose += (int8)*_dataPtr++;
}
SegaAudioChannel_FM::SegaAudioChannel_FM(uint8 id, SegaAudioInterface *sai, uint8 part, uint8 regOffs) : SegaAudioChannel(id, sai), _part(part), _regOffs(regOffs),
_regKeyOn(0xF0 | regOffs | (part ? 4 : 0)), _regKeyOff(regOffs | (part ? 4 : 0)), _algorithm(0), _program(0), _cfreqReg(0) {
}
void SegaAudioChannel_FM::keyOff() {
debugC(5, kDebugLevelSound, "Channel %d: Key Off", _id);
if (!_lock)
_sga->writeReg(0, 0x28, _regKeyOff);
}
void SegaAudioChannel_FM::fadeUpdate() {
if (SegaAudioDriverInternal::getFadeState() == _id)
sendVolume(_volume);
}
bool SegaAudioChannel_FM::setupTone() {
if (_cfreqReg > 0 && _cfreqReg < 4) {
uint8 note = *_dataPtr++ & 0x0F;
if (note) {
static uint8 frqreg[] = { 0xAD, 0xA9, 0xAE, 0xAA, 0xAC, 0xA8 };
uint16 f = _freqTable[_octave * 12 + note + _transpose];
if (!_lock) {
_sga->writeReg(_part, frqreg[(_cfreqReg - 1) << 1], f >> 8);
_sga->writeReg(_part, frqreg[((_cfreqReg - 1) << 1) + 1], f & 0xFF);
}
}
_cfreqReg++;
return true;
} else if (_cfreqReg == 4) {
_cfreqReg = 1;
}
uint8 para = _para1;
if (!(_para1 & 0x10))
keyOff();
uint8 note = setCountDown() & 0x0F;
if (!note)
return false;
sendFrequency(getFrequency(note));
if (!(para & 0x10)) {
if (!_lock)
_sga->writeReg(0, 0x28, _regKeyOn);
startVbr();
}
return false;
}
void SegaAudioChannel_FM::setVolume(uint8 vol) {
sendVolume(vol);
}
void SegaAudioChannel_FM::sendVolume(uint8 vol) {
if (_lock)
return;
static const uint8 carrier[8] = { 1, 1, 1, 1, 2, 3, 3, 4 };
vol = SegaAudioDriverInternal::calcVolume(vol);
const int8 *in = (const int8*)SegaAudioDriverInternal::getProgram(_program) + 7;
for (uint8 c = 0; c < carrier[_algorithm]; ++c)
_sga->writeReg(_part, 0x4C + _regOffs - (c << 2), vol + *in--);
}
void SegaAudioChannel_FM::programChange() {
_program = *_dataPtr++;
const uint8 *in = SegaAudioDriverInternal::getProgram(_program);
if (!_lock) {
for (uint8 reg = 0x30 + _regOffs; reg < 0x8F; reg += 4)
_sga->writeReg(_part, reg, *in++);
} else {
in += 24;
}
_algorithm = *in & 7;
if (!_lock)
_sga->writeReg(_part, 0xB0 + _regOffs, *in++);
sendVolume(_volume);
}
void SegaAudioChannel_FM::setPanPos(uint8 pan) {
if (!_lock)
_sga->writeReg(_part, 0xB4 + _regOffs, pan);
}
void SegaAudioChannel_FM::sendFrequency(uint16 freq) {
if (_lock)
return;
_sga->writeReg(_part, 0xA4 + _regOffs, freq >> 8);
_sga->writeReg(_part, 0xA0 + _regOffs, freq & 0xFF);
}
void SegaAudioChannel_FM::toggleSpecialMode(bool enable) {
_cfreqReg = enable ? 1 : 0;
_sga->writeReg(0, 0x27, (_cfreqReg << 6) | 0x0F);
}
const uint16 SegaAudioChannel_FM::_freqTable[97] = {
0x0000,
0x0284, 0x02ab, 0x02d3, 0x02fe, 0x032d, 0x035c, 0x038f, 0x03c5, 0x03ff, 0x043c, 0x047c, 0x04c0,
0x0a84, 0x0aab, 0x0ad3, 0x0afe, 0x0b2d, 0x0b5c, 0x0b8f, 0x0bc5, 0x0bff, 0x0c3c, 0x0c7c, 0x0cc0,
0x1284, 0x12ab, 0x12d3, 0x12fe, 0x132d, 0x135c, 0x138f, 0x13c5, 0x13ff, 0x143c, 0x147c, 0x14c0,
0x1a84, 0x1aab, 0x1ad3, 0x1afe, 0x1b2d, 0x1b5c, 0x1b8f, 0x1bc5, 0x1bff, 0x1c3c, 0x1c7c, 0x1cc0,
0x2284, 0x22ab, 0x22d3, 0x22fe, 0x232d, 0x235c, 0x238f, 0x23c5, 0x23ff, 0x243c, 0x247c, 0x24c0,
0x2a84, 0x2aab, 0x2ad3, 0x2afe, 0x2b2d, 0x2b5c, 0x2b8f, 0x2bc5, 0x2bff, 0x2c3c, 0x2c7c, 0x2cc0,
0x3284, 0x32ab, 0x32d3, 0x32fe, 0x332d, 0x335c, 0x338f, 0x33c5, 0x33ff, 0x343c, 0x347c, 0x34c0,
0x3a84, 0x3aab, 0x3ad3, 0x3afe, 0x3b2d, 0x3b5c, 0x3b8f, 0x3bc5, 0x3bff, 0x3c3c, 0x3c7c, 0x3cc0
};
SegaAudioChannel_SG::SegaAudioChannel_SG(uint8 id, SegaAudioInterface *sai, uint8 regOffs) : SegaAudioChannel(id, sai), _regOffs(regOffs), _envDataAtt(0), _envDataRel(0),
_envDataCur(0), _envDelay(0), _envState(0), _volume2(0) {
}
void SegaAudioChannel_SG::keyOff() {
if (_envState == kDisabled) {
sendVolume(0);
} else if (_envState < kRelease) {
_envState = kRelease;
_envDelay = 1;
_envDataCur = _envDataRel;
}
}
void SegaAudioChannel_SG::restoreTone() {
_sga->psgWrite(0x80 | _regOffs | (_frequency & 0x0F));
_sga->psgWrite((_frequency >> 4) & 0x3F);
_sga->psgWrite((0x90 | _regOffs | (_volume2 & 0x0F)) ^ 0x0F);
}
bool SegaAudioChannel_SG::setupTone() {
uint8 para = _para1;
uint8 note = setCountDown() & 0x0F;
if (!note) {
keyOff();
return false;
}
sendFrequency(getFrequency(note));
if (!(para & 0x10)) {
startVbr();
if (_envState == kDisabled) {
sendVolume(SegaAudioDriverInternal::calcVolume(_volume));
} else {
_envState = kAttack;
_envDelay = 1;
_envDataCur = _envDataAtt;
}
}
return false;
}
void SegaAudioChannel_SG::sendVolume(uint8 vol) {
_volume2 = vol;
if (!_lock)
_sga->psgWrite((0x90 | _regOffs | (vol & 0x0F)) ^ 0x0F);
}
void SegaAudioChannel_SG::programChange() {
uint8 prg = *_dataPtr++;
if (!prg) {
_envState = kDisabled;
return;
}
_envState = kReady;
_envDataAtt = SegaAudioDriverInternal::getProgram(prg);
_envDataRel = SegaAudioDriverInternal::getProgram(*_dataPtr++);
sendVolume(0);
}
void SegaAudioChannel_SG::sendFrequency(uint16 freq) {
if (_lock)
return;
_sga->psgWrite(0x80 | _regOffs | (freq & 0x0F));
_sga->psgWrite((freq >> 4) & 0x3F);
}
void SegaAudioChannel_SG::updateEnvelope() {
if (_envState != kAttack && _envState != kRelease)
return;
if (--_envDelay)
return;
if (*_envDataCur == 0xFF) {
_envState++;
return;
}
sendVolume(SegaAudioDriverInternal::calcVolume(_volume + (int8)*_envDataCur++));
_envDelay = *_envDataCur++;
}
const uint16 SegaAudioChannel_SG::_freqTable[97] = {
0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x03f7, 0x03be, 0x0388,
0x0356, 0x0326, 0x02f9, 0x02ce, 0x02a5, 0x0280, 0x025c, 0x023a, 0x021a, 0x01fb, 0x01df, 0x01c4,
0x01ab, 0x0193, 0x017d, 0x0167, 0x0153, 0x0140, 0x012e, 0x011d, 0x010d, 0x00fe, 0x00ef, 0x00e2,
0x00d6, 0x00c9, 0x00be, 0x00b4, 0x00a9, 0x00a0, 0x0097, 0x008f, 0x0087, 0x007f, 0x0078, 0x0071,
0x006b, 0x0065, 0x005f, 0x005a, 0x0055, 0x0050, 0x004b, 0x0047, 0x0043, 0x0040, 0x003c, 0x0039,
0x0036, 0x0033, 0x0030, 0x002d, 0x002b, 0x0028, 0x0026, 0x0024, 0x0022, 0x0020, 0x001f, 0x001d,
0x001b, 0x001a, 0x0018, 0x0017, 0x0016, 0x0015, 0x0013, 0x0012, 0x0011, 0x0010, 0x000f, 0x000e
};
SegaAudioChannel_NG::SegaAudioChannel_NG(uint8 id, SegaAudioInterface *sai) : SegaAudioChannel_FM(id, sai, 0, 0) {
}
SegaAudioDriverInternal *SegaAudioDriverInternal::_refInstance = 0;
int SegaAudioDriverInternal::_refCount = 0;
uint8 SegaAudioDriverInternal::_attenuation = 0;
uint8 SegaAudioDriverInternal::_fadeTicker = 0;
uint8 SegaAudioDriverInternal::_fadeAttenuation = 0;
const uint8 *SegaAudioDriverInternal::_trackData = 0;
SegaAudioDriverInternal::SegaAudioDriverInternal(Audio::Mixer *mixer) : SegaAudioPluginDriver(), _sga(0), _priority(false), _channels(0), _isPlaying(0), _sfxInternal_a(0), _ready(false) {
_sga = new SegaAudioInterface(mixer, this);
_sga->init();
// Setup all channels as sound effect channels. Some FM tunes (like the gameover tune) are actual music pieces and not just sfx.
// Unfortunately there isn't any good way to distinguish the tune types. Most tracks are classical sfx. So for the volume control
// we treat the CD Audio as music and all FM and PCM output as sound effects.
_sga->setSoundEffectChanMask(-1);
_channels = new SegaAudioChannel*[10];
for (int i = 0; i < 3; ++i)
_channels[i] = new SegaAudioChannel_FM(i, _sga, 0, i);
for (int i = 3; i < 6; ++i)
_channels[i] = new SegaAudioChannel_SG(i, _sga, (i - 3) << 5);
_channels[6] = new SegaAudioChannel_NG(6, _sga);
for (int i = 7; i < 10; ++i)
_channels[i] = new SegaAudioChannel_FM(i, _sga, 1, i - 7);
_sga->writeReg(0, 0x27, 0x3F);
_ready = true;
}
SegaAudioDriverInternal::~SegaAudioDriverInternal() {
_ready = false;
delete _sga;
if (_channels) {
for (int i = 0; i < 10; ++i)
delete _channels[i];
delete[] _channels;
}
}
SegaAudioDriverInternal *SegaAudioDriverInternal::open(Audio::Mixer *mixer) {
_refCount++;
if (_refCount == 1 && _refInstance == 0)
_refInstance = new SegaAudioDriverInternal(mixer);
else if (_refCount < 2 || _refInstance == 0)
error("SegaAudioDriverInternal::open(): Internal instance management failure");
return _refInstance;
}
void SegaAudioDriverInternal::close() {
if (!_refCount)
return;
_refCount--;
if (!_refCount) {
delete _refInstance;
_refInstance = 0;
}
}
void SegaAudioDriverInternal::startFMSound(const uint8 *trackData, uint8 volume, uint8 prioFlags) {
if (!_isPlaying)
_priority = false;
if (prioFlags & SegaAudioDriver::kPrioHigh)
_priority = true;
else if (_isPlaying && (_priority || (prioFlags & SegaAudioDriver::kPrioLow)))
return;
SegaAudioInterface::MutexLock lock = _sga->stackLockMutex();
stop();
_trackData = trackData;
_attenuation = (uint8)-((volume & 0x0F) - 0x0F);
_sfxInternal_a = 0;
start();
}
void SegaAudioDriverInternal::loadPCMData(uint16 address, const uint8 *data, uint16 dataSize) {
_sga->loadPCMData(address, data, dataSize);
}
void SegaAudioDriverInternal::startPCMSound(uint8 channel, uint8 dataStart, uint16 loopStart, uint16 rate, uint8 pan, uint8 vol) {
_sga->playPCMChannel(channel, dataStart, loopStart, rate, pan, vol);
}
void SegaAudioDriverInternal::setMusicVolume(int volume) {
_sga->setMusicVolume(volume);
}
void SegaAudioDriverInternal::setSoundEffectVolume(int volume) {
_sga->setSoundEffectVolume(volume);
}
void SegaAudioDriverInternal::timerCallbackA() {
if (_ready && _isPlaying != 0xFF)
update();
}
void SegaAudioDriverInternal::timerCallbackB() {
}
uint8 SegaAudioDriverInternal::calcVolume(int vol) {
static const uint8 volTable[18] = { 0x5F, 0x2A, 0x28, 0x25, 0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10, 0x0D, 0x0A, 0x08, 0x05, 0x02, 0x00 };
vol = MAX<int>((int8)(vol & 0xFF) - _attenuation - _fadeAttenuation, 0) & 0xFF;
assert(vol < ARRAYSIZE(volTable));
return volTable[vol];
}
int SegaAudioDriverInternal::getFadeState() {
return _fadeAttenuation ? _fadeTicker - 1 : -1;
}
const uint8 *SegaAudioDriverInternal::getTrack() {
return _trackData;
}
const uint8 *SegaAudioDriverInternal::getTrack(int channel) {
return _trackData ? _trackData + READ_LE_UINT16(_trackData + 1 + (channel << 1)) : 0;
}
const uint8 *SegaAudioDriverInternal::getProgram(int instrument) {
return getTrack(12 + instrument);
}
void SegaAudioDriverInternal::start() {
if (_isPlaying)
stop();
debugC(3, kDebugLevelSound, "%s", "\nStarting sound...");
_isPlaying = 1;
for (int i = 0; i < 10; ++i)
_channels[i]->initTrack();
_sga->writeReg(0, 0x26, 0xE9);
}
void SegaAudioDriverInternal::stop() {
_fadeAttenuation = 15;
_channels[0]->unlock();
/*
*/
update();
_fadeAttenuation = 0;
_fadeTicker = 0;
_isPlaying = 0;
//_unkByte = 0;
//_sai->writeReg(0, 0x27, 0);
}
void SegaAudioDriverInternal::fade() {
if (!_isPlaying || _fadeAttenuation)
return;
_fadeAttenuation = 1;
_fadeTicker = 12;
}
void SegaAudioDriverInternal::update() {
if (_fadeAttenuation) {
if (!--_fadeTicker) {
_fadeTicker = 12;
if (++_fadeAttenuation == 14)
stop();
}
}
for (int i = 0; i < 10; ++i) {
if (!_channels[i]->update())
stop();
}
}
SegaAudioDriver::SegaAudioDriver(Audio::Mixer *mixer) {
_drv = SegaAudioDriverInternal::open(mixer);
}
SegaAudioDriver::~SegaAudioDriver() {
SegaAudioDriverInternal::close();
_drv = 0;
}
void SegaAudioDriver::startFMSound(const uint8 *trackData, uint8 volume, PrioFlags prioFlags) {
_drv->startFMSound(trackData, volume, (uint8)prioFlags);
}
void SegaAudioDriver::loadPCMData(uint16 address, const uint8 *data, uint16 dataLen) {
_drv->loadPCMData(address, data, dataLen);
}
void SegaAudioDriver::startPCMSound(uint8 channel, uint8 dataStart, uint16 loopStart, uint16 rate, uint8 pan, uint8 vol) {
_drv->startPCMSound(channel, dataStart, loopStart, rate, pan, vol);
}
void SegaAudioDriver::setMusicVolume(int volume) {
_drv->setMusicVolume(volume);
}
void SegaAudioDriver::setSoundEffectVolume(int volume) {
_drv->setSoundEffectVolume(volume);
}
} // End of namespace Kyra
#endif