1145 lines
33 KiB
C++
1145 lines
33 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 "scumm/players/player_mac_new.h"
|
|
#include "scumm/players/player_mac_intern.h"
|
|
#include "scumm/resource.h"
|
|
#include "scumm/scumm.h"
|
|
|
|
#include "audio/audiostream.h"
|
|
#include "audio/mixer.h"
|
|
#include "common/endian.h"
|
|
|
|
namespace Scumm {
|
|
|
|
#define ASC_DEVICE_RATE 0x56EE8BA3
|
|
#define PCM_BUFFER_SIZE 1024
|
|
|
|
extern const uint8 *const g_pv2ModTbl;
|
|
extern const uint32 g_pv2ModTblSize;
|
|
|
|
class LegacyMusicDriver : public MacSoundDriver {
|
|
public:
|
|
LegacyMusicDriver(uint16 numChannels, Audio::Mixer::SoundType sndType, Common::Mutex &mutex, bool canInterpolate, bool internal16Bit) :
|
|
MacSoundDriver(mutex, ASC_DEVICE_RATE, internal16Bit ? numChannels : 1, canInterpolate, internal16Bit),
|
|
_numChan(numChannels), _sndType(sndType) {
|
|
for (int i = 0; i < ARRAYSIZE(_status); ++i) {
|
|
if (sndType != i)
|
|
_status[i].flags |= kStatusDisabled;
|
|
}
|
|
}
|
|
virtual void start() = 0;
|
|
virtual void stop() = 0;
|
|
|
|
enum SendDataType {
|
|
kDuration = 0,
|
|
kChanRate = 1,
|
|
kChanWaveform = 2,
|
|
kSquareWaveTriplet = 3
|
|
};
|
|
|
|
virtual void send(int dataType, ...) = 0;
|
|
uint16 numChannels() const { return _numChan; }
|
|
|
|
protected:
|
|
void putSample(int8 *&dst, int16 smp, bool expectStereo) {
|
|
if (_smpSize == 2) {
|
|
smp = CLIP<int16>(smp, _smpMin, _smpMax);
|
|
*reinterpret_cast<int16*>(dst) += smp;
|
|
dst += _smpSize;
|
|
if (expectStereo) {
|
|
*reinterpret_cast<int16*>(dst) += smp;
|
|
dst += _smpSize;
|
|
}
|
|
} else {
|
|
smp = CLIP<int16>(smp / _numChan, _smpMin, _smpMax);
|
|
*dst++ += smp;
|
|
if (expectStereo)
|
|
*dst++ += smp;
|
|
}
|
|
}
|
|
const uint16 _numChan;
|
|
const Audio::Mixer::SoundType _sndType;
|
|
};
|
|
|
|
class FourToneSynthDriver final : public LegacyMusicDriver {
|
|
public:
|
|
FourToneSynthDriver(Audio::Mixer::SoundType sndType, Common::Mutex &mutex, bool internal16bit);
|
|
~FourToneSynthDriver() override;
|
|
|
|
void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) override;
|
|
void start() override;
|
|
void stop() override;
|
|
void send(int dataType, ...) override;
|
|
|
|
private:
|
|
void setWaveForm(uint8 chan, const uint8 *data, uint32 dataSize);
|
|
void setDuration(uint16 duration);
|
|
void setRate(uint8 chan, uint16 rate);
|
|
|
|
uint32 _pos;
|
|
uint16 _duration;
|
|
|
|
struct Channel {
|
|
Channel() : rate(0), phase(0), waveform(nullptr) {}
|
|
uint32 rate;
|
|
uint32 phase;
|
|
const int8 *waveform;
|
|
};
|
|
|
|
Channel *_chan;
|
|
};
|
|
|
|
class SquareWaveSynthDriver final : public LegacyMusicDriver {
|
|
public:
|
|
SquareWaveSynthDriver(Audio::Mixer::SoundType sndType, Common::Mutex &mutex, bool internal16Bit);
|
|
~SquareWaveSynthDriver() override {};
|
|
|
|
void feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) override;
|
|
void start() override;
|
|
void stop() override;
|
|
void send(int dataType, ...) override;
|
|
private:
|
|
void addTriplet(uint16 frequency, uint16 amplitude);
|
|
|
|
struct Triplet {
|
|
Triplet(uint16 cnt, uint16 ampl, uint16 dur) : count(cnt), amplitude(ampl), duration(dur) {}
|
|
Triplet() : Triplet(0xffff, 0xffff, 1) {}
|
|
Triplet &&fromScumm() { count = (count / 3) * 2; duration = MAX<uint8>(duration, 1); return Common::move(*this); }
|
|
void toHardware(uint32 &dstCount, uint8 &dstAmpl, uint16 &dstDur) { dstCount = count ? 0x58000000 / (5 * count) : 0; dstAmpl = (amplitude & 0xff) >> 1; dstDur = duration; }
|
|
uint16 count;
|
|
uint16 amplitude;
|
|
uint16 duration;
|
|
};
|
|
|
|
Common::Array<SquareWaveSynthDriver::Triplet> _tripletsQueue;
|
|
Triplet _lastPara;
|
|
uint16 _pos;
|
|
uint32 _count;
|
|
uint16 _duration;
|
|
uint8 _amplitude;
|
|
uint32 _phase;
|
|
};
|
|
|
|
FourToneSynthDriver::FourToneSynthDriver(Audio::Mixer::SoundType sndType, Common::Mutex &mutex, bool internal16Bit) : LegacyMusicDriver(4, sndType, mutex, false, internal16Bit), _duration(0), _pos(0), _chan(nullptr) {
|
|
_chan = new Channel[_numChan];
|
|
}
|
|
|
|
FourToneSynthDriver::~FourToneSynthDriver() {
|
|
Common::StackLock lock(_mutex);
|
|
for (int i = 0; i < _numChan; ++i)
|
|
setWaveForm(i, 0, 0);
|
|
delete[] _chan;
|
|
}
|
|
|
|
void FourToneSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
|
|
if (dst == nullptr || type != _sndType)
|
|
return;
|
|
|
|
const int8 *end = &dst[byteSize];
|
|
|
|
while (_duration && dst < end) {
|
|
if (_pos == 0)
|
|
--_duration;
|
|
|
|
int16 smp = 0;
|
|
for (int i = 0; i < _numChan; ++i) {
|
|
_chan[i].phase += _chan[i].rate;
|
|
if (_chan[i].waveform != nullptr)
|
|
smp += _chan[i].waveform[(_chan[i].phase >> 16) & 0xff];
|
|
}
|
|
|
|
putSample(dst, smp, expectStereo);
|
|
|
|
if (++_pos == 370) {
|
|
_pos = 0;
|
|
if (!_duration)
|
|
setFlags(kStatusDone);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FourToneSynthDriver::start() {
|
|
Common::StackLock lock(_mutex);
|
|
stop();
|
|
setDuration(50);
|
|
}
|
|
|
|
void FourToneSynthDriver::stop() {
|
|
Common::StackLock lock(_mutex);
|
|
for (int i = 0; i < _numChan; ++i) {
|
|
_chan[i].phase = 0;
|
|
_chan[i].rate = 0;
|
|
}
|
|
setDuration(0);
|
|
}
|
|
|
|
void FourToneSynthDriver::send(int dataType, ...) {
|
|
Common::StackLock lock(_mutex);
|
|
va_list arg;
|
|
va_start(arg, dataType);
|
|
int chan = (dataType == kChanRate || dataType == kChanWaveform) ? va_arg(arg, int) : 0;
|
|
const uint8 *ptr = (dataType == kChanWaveform) ? va_arg(arg, const uint8*) : nullptr;
|
|
|
|
switch (dataType) {
|
|
case kDuration:
|
|
setDuration((uint16)va_arg(arg, uint));
|
|
break;
|
|
case kChanRate:
|
|
setRate(chan, (uint16)va_arg(arg, uint));
|
|
break;
|
|
case kChanWaveform:
|
|
setWaveForm(chan, ptr, va_arg(arg, uint32));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
setFlags(kStatusOverflow);
|
|
|
|
va_end(arg);
|
|
}
|
|
|
|
void FourToneSynthDriver::setWaveForm(uint8 chan, const uint8 *data, uint32 dataSize) {
|
|
assert(chan < _numChan);
|
|
delete[] _chan[chan].waveform;
|
|
_chan[chan].waveform = nullptr;
|
|
if (data == nullptr || dataSize == 0)
|
|
return;
|
|
dataSize = MIN<uint32>(256, dataSize);
|
|
int8 *wf = new int8[256];
|
|
memset(wf, 0, 256);
|
|
for (uint32 i = 0; i < dataSize; ++i)
|
|
wf[i] = data[i] ^ 0x80;
|
|
_chan[chan].waveform = wf;
|
|
}
|
|
|
|
void FourToneSynthDriver::setDuration(uint16 duration) {
|
|
_duration = duration;
|
|
_pos = 0;
|
|
clearFlags(kStatusDone);
|
|
}
|
|
|
|
void FourToneSynthDriver::setRate(uint8 chan, uint16 rate) {
|
|
assert(chan < _numChan);
|
|
_chan[chan].rate = rate ? (0x5060000 / (rate >> ((rate < 1600) ? 4 : 6))) : 0;
|
|
}
|
|
|
|
SquareWaveSynthDriver::SquareWaveSynthDriver(Audio::Mixer::SoundType sndType, Common::Mutex &mutex, bool internal16Bit) :
|
|
LegacyMusicDriver(1, sndType, mutex, false, internal16Bit), _count(0xffff), _duration(0), _amplitude(0), _phase(0), _pos(0) {
|
|
}
|
|
|
|
void SquareWaveSynthDriver::feed(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) {
|
|
if (dst == nullptr || type != _sndType)
|
|
return;
|
|
|
|
Common::Array<Triplet>::iterator t = _tripletsQueue.begin();
|
|
const int8 *end = &dst[byteSize];
|
|
|
|
while (dst < end && (_count != 0xffff || t != _tripletsQueue.end())) {
|
|
if (_pos == 0 && (_count == 0xffff || (_duration && !--_duration) || !_duration)) {
|
|
if (t == _tripletsQueue.end()) {
|
|
_count = 0xffff;
|
|
_duration = 1;
|
|
setFlags(kStatusDone);
|
|
break;
|
|
} else {
|
|
t->toHardware(_count, _amplitude, _duration);
|
|
t = _tripletsQueue.erase(t);
|
|
_phase = _count;
|
|
}
|
|
}
|
|
|
|
_phase += _count;
|
|
putSample(dst, _phase ? -(int8)((_phase >> 23) & 1) ^ _amplitude : 0, expectStereo);
|
|
|
|
if (++_pos == 370)
|
|
_pos = 0;
|
|
}
|
|
}
|
|
|
|
void SquareWaveSynthDriver::start() {
|
|
Common::StackLock lock(_mutex);
|
|
stop();
|
|
setFlags(kStatusPlaying | kStatusStartup);
|
|
}
|
|
|
|
void SquareWaveSynthDriver::stop() {
|
|
Common::StackLock lock(_mutex);
|
|
_lastPara = Triplet();
|
|
_count = 0xffff;
|
|
_duration = 1;
|
|
_tripletsQueue.clear();
|
|
clearFlags(kStatusPlaying);
|
|
setFlags(kStatusDone);
|
|
}
|
|
|
|
void SquareWaveSynthDriver::send(int dataType, ...) {
|
|
Common::StackLock lock(_mutex);
|
|
va_list arg;
|
|
va_start(arg, dataType);
|
|
uint16 a = (uint16)va_arg(arg, uint);
|
|
|
|
switch (dataType) {
|
|
case kSquareWaveTriplet:
|
|
addTriplet(a, (uint16)va_arg(arg, uint));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
va_end(arg);
|
|
}
|
|
|
|
void SquareWaveSynthDriver::addTriplet(uint16 frequency, uint16 amplitude) {
|
|
if ((getStatus().flags & kStatusStartup) && frequency < 3)
|
|
return;
|
|
|
|
clearFlags(kStatusStartup);
|
|
|
|
if (_lastPara.count == 0xffff)
|
|
_lastPara.count = frequency;
|
|
if (_lastPara.amplitude == 0xffff)
|
|
_lastPara.amplitude = amplitude;
|
|
|
|
if ((getStatus().flags & kStatusPlaying) && _tripletsQueue.size() < 176) {
|
|
if (frequency >> 3 != _lastPara.count >> 3 || amplitude != _lastPara.amplitude) {
|
|
_tripletsQueue.push_back(_lastPara.fromScumm());
|
|
_lastPara = Triplet(frequency, amplitude, 0);
|
|
clearFlags(kStatusDone);
|
|
}
|
|
_lastPara.duration++;
|
|
}
|
|
|
|
if (!(getStatus().flags & kStatusPlaying) || _tripletsQueue.size() >= 176)
|
|
setFlags(kStatusOverflow);
|
|
}
|
|
|
|
Common::WeakPtr<Indy3MacSnd> *Indy3MacSnd::_inst = nullptr;
|
|
|
|
Indy3MacSnd::Indy3MacSnd(ScummEngine *vm, Audio::Mixer *mixer) : VblTaskClientDriver(),
|
|
_vm(vm), _mixer(mixer), _musicChannels(nullptr), _curSound(0), _curSong(0), _lastSoundEffectPrio(0), _idRangeMax(86), _soundEffectNumLoops(-1),
|
|
_musicIDTable(nullptr), _macstr(nullptr), _musicIDTableLen(0), _soundUsage(0), _mdrv(nullptr), _sdrv(nullptr), _nextTickProc(this, &VblTaskClientDriver::vblCallback),
|
|
_soundEffectPlaying(false), _songTimer(0), _songTimerInternal(0), _qmode(0), _16bit(false), _qualHi(false), _mixerThread(false), _activeChanCount(0),
|
|
_songUnfinished(false), _numMusicChannels(8), _numMusicTracks(4), _sfxChan(0), _disableFlags(0), _soundEffectReschedule(false) {
|
|
assert(_vm);
|
|
assert(_mixer);
|
|
|
|
if (_vm->_game.id == GID_INDY3) {
|
|
static const byte table[] = { 0x1D, 0x23, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x36, 0x3B, 0x42, 0x43, 0x45, 0x46, 0x53 };
|
|
_musicIDTable = table;
|
|
_musicIDTableLen = ARRAYSIZE(table);
|
|
}
|
|
|
|
_soundUsage = new uint8[_idRangeMax]();
|
|
|
|
_musicChannels = new MusicChannel*[_numMusicChannels];
|
|
assert(_musicChannels);
|
|
for (int i = 0; i < _numMusicChannels; ++i)
|
|
_musicChannels[i] = new MusicChannel(this);
|
|
}
|
|
|
|
Indy3MacSnd::~Indy3MacSnd() {
|
|
_mixer->stopHandle(_soundHandle);
|
|
delete _macstr;
|
|
delete[] _soundUsage;
|
|
|
|
_sdrv->disposeChannel(_sfxChan);
|
|
|
|
for (Common::Array<MacSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
|
|
delete *i;
|
|
_drivers.clear();
|
|
|
|
if (_musicChannels) {
|
|
for (int i = 0; i < _numMusicChannels; ++i)
|
|
delete _musicChannels[i];
|
|
delete[] _musicChannels;
|
|
}
|
|
|
|
delete _inst;
|
|
_inst = nullptr;
|
|
}
|
|
|
|
Common::SharedPtr<Indy3MacSnd> Indy3MacSnd::open(ScummEngine *vm, Audio::Mixer *mixer) {
|
|
Common::SharedPtr<Indy3MacSnd> scp = nullptr;
|
|
|
|
if (_inst == nullptr) {
|
|
scp = Common::SharedPtr<Indy3MacSnd>(new Indy3MacSnd(vm, mixer));
|
|
_inst = new Common::WeakPtr<Indy3MacSnd>(scp);
|
|
// We can start this with the ScummVM mixer output rate instead of the ASC rate. The Mac sample rate converter can handle it (at
|
|
// least for up to 48 KHz, I haven't tried higher settings) and we don't have to do another rate conversion in the ScummVM mixer.
|
|
if ((_inst == nullptr) || (mixer == nullptr) || !(scp->startDevices(mixer->getOutputRate(), mixer->getOutputRate() << 16/*ASC_DEVICE_RATE*/, PCM_BUFFER_SIZE, true, false, true)))
|
|
error("Indy3MacSnd::open(): Failed to start player");
|
|
}
|
|
|
|
return _inst->lock();
|
|
}
|
|
|
|
bool Indy3MacSnd::startDevices(uint32 outputRate, uint32 pcmDeviceRate, uint32 feedBufferSize, bool enableInterpolation, bool stereo, bool internal16Bit) {
|
|
_macstr = new MacPlayerAudioStream(this, outputRate, stereo, enableInterpolation, internal16Bit);
|
|
if (!_macstr || !_mixer)
|
|
return false;
|
|
|
|
_sdrv = new MacLowLevelPCMDriver(_mixer->mutex(), pcmDeviceRate, internal16Bit);
|
|
FourToneSynthDriver *mdrv = new FourToneSynthDriver(Audio::Mixer::kMusicSoundType, _mixer->mutex(), internal16Bit);
|
|
if (!mdrv || !_sdrv)
|
|
return false;
|
|
|
|
for (int i = 0; i < mdrv->numChannels(); ++i)
|
|
mdrv->send(LegacyMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
|
|
_qualHi = true;
|
|
_16bit = internal16Bit;
|
|
_mdrv = mdrv;
|
|
|
|
_sfxChan = _sdrv->createChannel(Audio::Mixer::kSFXSoundType, MacLowLevelPCMDriver::kSampledSynth, 0, nullptr);
|
|
_sdrv->setFlags(MacSoundDriver::kStatusDisabled, Audio::Mixer::kMusicSoundType);
|
|
|
|
_drivers.push_back(_mdrv);
|
|
_drivers.push_back(_sdrv);
|
|
|
|
_macstr->initBuffers(feedBufferSize);
|
|
_macstr->addVolumeGroup(Audio::Mixer::kMusicSoundType);
|
|
_macstr->addVolumeGroup(Audio::Mixer::kSFXSoundType);
|
|
_macstr->setVblCallback(&_nextTickProc);
|
|
|
|
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, _macstr, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Indy3MacSnd::setMusicVolume(int vol) {
|
|
Common::StackLock lock(_mixer->mutex());
|
|
if (_macstr)
|
|
_macstr->setMasterVolume(Audio::Mixer::kMusicSoundType, vol);
|
|
}
|
|
|
|
void Indy3MacSnd::setSfxVolume(int vol) {
|
|
Common::StackLock lock(_mixer->mutex());
|
|
if (_macstr)
|
|
_macstr->setMasterVolume(Audio::Mixer::kSFXSoundType, vol);
|
|
}
|
|
|
|
void Indy3MacSnd::startSound(int id) {
|
|
if (id < 0 || id >= _idRangeMax)
|
|
return;
|
|
|
|
if (isSong(id))
|
|
startSong(id);
|
|
else
|
|
startSoundEffect(id);
|
|
}
|
|
|
|
void Indy3MacSnd::stopSound(int id) {
|
|
if (id < 0 || id >= _idRangeMax) {
|
|
warning("Indy3MacSnd::stopSound(): sound id '%d' out of range (0 - 85)", id);
|
|
return;
|
|
}
|
|
|
|
Common::StackLock lock(_mixer->mutex());
|
|
_soundUsage[id] = 0;
|
|
|
|
if (id == _curSound)
|
|
stopActiveSound();
|
|
}
|
|
|
|
void Indy3MacSnd::stopAllSounds() {
|
|
Common::StackLock lock(_mixer->mutex());
|
|
memset(_soundUsage, 0, _idRangeMax);
|
|
stopActiveSound();
|
|
}
|
|
|
|
int Indy3MacSnd::getMusicTimer() {
|
|
Common::StackLock lock(_mixer->mutex());
|
|
return _songTimer;
|
|
}
|
|
|
|
int Indy3MacSnd::getSoundStatus(int id) const {
|
|
if (id < 0 || id >= _idRangeMax) {
|
|
warning("Indy3MacSnd::getSoundStatus(): sound id '%d' out of range (0 - 85)", id);
|
|
return 0;
|
|
}
|
|
Common::StackLock lock(_mixer->mutex());
|
|
return _soundUsage[id];
|
|
}
|
|
|
|
void Indy3MacSnd::setQuality(int qual) {
|
|
if (qual != MacSound::kQualityAuto && qual != MacSound::kQualityLowest && qual != MacSound::kQualityHighest) {
|
|
warning ("Indy3MacSnd::setQuality(): Indy 3 supports only the following quality settings:"
|
|
" kQualityAuto, kQualityLowest and kQualityHighest. Setting is now changed to kQualityAuto");
|
|
qual = MacSound::kQualityAuto;
|
|
}
|
|
|
|
while (_qualHi == isHiQuality()) {
|
|
if (_qmode == qual)
|
|
return;
|
|
_qmode = qual;
|
|
}
|
|
|
|
Common::StackLock lock(_mixer->mutex());
|
|
Common::Array<MacSoundDriver*>::iterator dr = Common::find(_drivers.begin(), _drivers.end(), _mdrv);
|
|
delete _mdrv;
|
|
_mdrv = nullptr;
|
|
_qmode = qual;
|
|
|
|
if (isHiQuality()) {
|
|
FourToneSynthDriver *mdrv = new FourToneSynthDriver(Audio::Mixer::kMusicSoundType, _mixer->mutex(), _16bit);
|
|
assert(mdrv);
|
|
for (int i = 0; i < mdrv->numChannels(); ++i)
|
|
mdrv->send(LegacyMusicDriver::kChanWaveform, i, _fourToneSynthWaveForm, sizeof(_fourToneSynthWaveForm));
|
|
_mdrv = mdrv;
|
|
_qualHi = true;
|
|
} else {
|
|
_mdrv = new SquareWaveSynthDriver(Audio::Mixer::kMusicSoundType, _mixer->mutex(), _16bit);
|
|
_qualHi = false;
|
|
assert(_mdrv);
|
|
}
|
|
|
|
if (dr != _drivers.end())
|
|
*dr = _mdrv;
|
|
else if (_drivers.empty())
|
|
_drivers.push_back(_mdrv);
|
|
else
|
|
error("Indy3MacSnd::setQuality(): Invalid usage");
|
|
|
|
assert(_macstr);
|
|
_macstr->initDrivers();
|
|
}
|
|
|
|
void Indy3MacSnd::saveLoadWithSerializer(Common::Serializer &ser) {
|
|
ser.syncBytes(_soundUsage, _idRangeMax, VER(113));
|
|
}
|
|
|
|
void Indy3MacSnd::restoreAfterLoad() {
|
|
stopActiveSound();
|
|
for (int i = 0; i < _idRangeMax; ++i) {
|
|
if (_soundUsage[i] && isSong(i)) {
|
|
--_soundUsage[i];
|
|
startSound(i);
|
|
}
|
|
}
|
|
_soundEffectReschedule = true;
|
|
}
|
|
|
|
void Indy3MacSnd::toggleMusic(bool enable) {
|
|
if ((_disableFlags & 1) == (enable ? 0 : 1))
|
|
return;
|
|
if (enable)
|
|
_mdrv->start();
|
|
else
|
|
_mdrv->stop();
|
|
_disableFlags ^= 1;
|
|
}
|
|
|
|
void Indy3MacSnd::toggleSoundEffects(bool enable) {
|
|
if ((_disableFlags & 2) == (enable ? 0 : 2))
|
|
return;
|
|
if (enable)
|
|
_soundEffectReschedule = true;
|
|
else
|
|
stopSoundEffect();
|
|
_disableFlags ^= 2;
|
|
}
|
|
|
|
void Indy3MacSnd::vblCallback() {
|
|
if (_songTimerInternal++ == 29) {
|
|
_songTimerInternal = 0;
|
|
++_songTimer;
|
|
}
|
|
|
|
_mixerThread = true;
|
|
|
|
if (!_curSong && ((_sdrv->getChannelStatus(_sfxChan) & MacSoundDriver::kStatusDone) || _soundEffectReschedule))
|
|
updateSoundEffect();
|
|
else if (_curSong)
|
|
updateSong();
|
|
else if (_songUnfinished && (_mdrv->getStatus().flags & MacSoundDriver::kStatusDone))
|
|
stopSong();
|
|
|
|
_mixerThread = false;
|
|
}
|
|
|
|
void Indy3MacSnd::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const {
|
|
assert(dst);
|
|
memset(dst, 0, len);
|
|
for (Common::Array<MacSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i)
|
|
(*i)->feed(dst, len, type, expectStereo);
|
|
}
|
|
|
|
const MacSoundDriver::Status &Indy3MacSnd::getDriverStatus(Audio::Mixer::SoundType type) const {
|
|
Common::Array<MacSoundDriver*>::const_iterator res = _drivers.end();
|
|
for (Common::Array<MacSoundDriver*>::const_iterator i = _drivers.begin(); i != _drivers.end(); ++i) {
|
|
if ((*i)->getStatus(type).flags & MacSoundDriver::kStatusDisabled)
|
|
continue;
|
|
if (res != _drivers.end())
|
|
warning("%s(): Multiple drivers for sound type %d", __FUNCTION__, type);
|
|
res = i;
|
|
}
|
|
if (res == _drivers.end())
|
|
error("%s(): No sound driver for type %d", __FUNCTION__, type);
|
|
|
|
return (*res)->getStatus(type);
|
|
}
|
|
|
|
void Indy3MacSnd::startSong(int id) {
|
|
if (_mdrv == nullptr || id < 0 || id >= _idRangeMax) {
|
|
warning("Indy3MacSnd::startSong(): sound id '%d' out of range (0 - 85)", id);
|
|
return;
|
|
}
|
|
|
|
Common::StackLock lock(_mixer->mutex());
|
|
|
|
stopActiveSound();
|
|
|
|
uint32 sz = _vm->getResourceSize(rtSound, id);
|
|
const byte *ptr = _vm->getResourceAddress(rtSound, id);
|
|
assert(ptr);
|
|
byte *buff = new byte[sz];
|
|
memcpy(buff, ptr, sz);
|
|
Common::SharedPtr<const byte> sres(buff, Common::ArrayDeleter<const byte>());
|
|
|
|
_songTimer = 0;
|
|
++_soundUsage[id];
|
|
if (_curSong > 0)
|
|
--_soundUsage[_curSong];
|
|
_curSong = _curSound = id;
|
|
|
|
// This applies if the quality mode is set to kQualAuto
|
|
// and the VAR_SOUNDCARD setting changes.
|
|
if (_qualHi != isHiQuality())
|
|
setQuality(_qmode);
|
|
|
|
if (isHiQuality()) {
|
|
_qualHi = true;
|
|
ptr += 14;
|
|
} else {
|
|
_qualHi = false;
|
|
ptr += 6;
|
|
}
|
|
|
|
_mdrv->start();
|
|
|
|
_activeChanCount = 0;
|
|
for (int i = 0; i < _numMusicTracks; ++i) {
|
|
uint16 offs = READ_LE_UINT16(ptr);
|
|
ptr += 2;
|
|
if (offs)
|
|
++_activeChanCount;
|
|
_musicChannels[i]->start(sres, offs, _qualHi);
|
|
}
|
|
}
|
|
|
|
void Indy3MacSnd::startSoundEffect(int id) {
|
|
if (_sdrv == nullptr || id < 0 || id >= _idRangeMax) {
|
|
warning("Indy3MacSnd::startSoundEffect(): sound id '%d' out of range (0 - 85)", id);
|
|
return;
|
|
}
|
|
|
|
Common::StackLock lock(_mixer->mutex());
|
|
|
|
const uint8 *ptr = _vm->getResourceAddress(rtSound, id);
|
|
assert(ptr);
|
|
|
|
if (READ_LE_UINT16(ptr) < 28) {
|
|
warning("Indy3MacSnd::startSoundEffect(%d): invalid resource", id);
|
|
return;
|
|
}
|
|
|
|
if (_curSong)
|
|
return;
|
|
|
|
uint16 prio = READ_BE_UINT16(ptr + 4);
|
|
|
|
if (_curSound) {
|
|
if (prio < _lastSoundEffectPrio)
|
|
return;
|
|
const uint8 *ptr2 = _vm->getResourceAddress(rtSound, _curSound);
|
|
assert(ptr2);
|
|
if (READ_BE_UINT16(ptr2 + 6) == 0)
|
|
_soundUsage[_curSound] = 0;
|
|
}
|
|
|
|
stopActiveSound();
|
|
|
|
// Two-byte prio always gets through.
|
|
_lastSoundEffectPrio = prio & 0xff;
|
|
_soundEffectNumLoops = (int8)ptr[27];
|
|
|
|
_soundUsage[id]++;
|
|
if (_disableFlags & 2)
|
|
return;
|
|
|
|
int offs = (READ_BE_UINT16(ptr + 14) >= READ_BE_UINT16(ptr + 12)) ? 2 : 0;
|
|
uint16 numSamples = READ_BE_UINT16(ptr + 12 + offs);
|
|
uint16 spos = READ_BE_UINT16(ptr + 8 + offs);
|
|
if (spos <= 20)
|
|
return;
|
|
|
|
byte *buff = new byte[numSamples - 22];
|
|
for (uint16 i = 0; i < numSamples - 22; ++i)
|
|
buff[i] = ptr[spos + 22 + i] ^ 0x80;
|
|
|
|
_pcmSnd.rate = 0x4E200000 / (READ_BE_UINT16(ptr + 20 + offs) >> 7);
|
|
_pcmSnd.data = Common::SharedPtr<const byte> (buff, Common::ArrayDeleter<const byte>());
|
|
_pcmSnd.len = numSamples - 23;
|
|
_pcmSnd.loopst = numSamples - 2;
|
|
_pcmSnd.loopend = numSamples - 1;
|
|
_pcmSnd.baseFreq = 60;
|
|
|
|
_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kImmediate, &_pcmSnd);
|
|
|
|
if (READ_BE_UINT16(ptr + 6) || _soundEffectNumLoops == -1) {
|
|
_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kEnqueue, &_pcmSnd);
|
|
_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kEnqueue, &_pcmSnd);
|
|
_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kEnqueue, &_pcmSnd);
|
|
}
|
|
|
|
_curSound = id;
|
|
_soundEffectPlaying = true;
|
|
}
|
|
|
|
void Indy3MacSnd::stopSong() {
|
|
Common::StackLock lock(_mixer->mutex());
|
|
_mdrv->stop();
|
|
finishSong();
|
|
_curSound = 0;
|
|
}
|
|
|
|
void Indy3MacSnd::stopSoundEffect() {
|
|
Common::StackLock lock(_mixer->mutex());
|
|
_sdrv->quiet(_sfxChan, MacLowLevelPCMDriver::kImmediate);
|
|
_sdrv->flush(_sfxChan, MacLowLevelPCMDriver::kImmediate);
|
|
_soundEffectPlaying = false;
|
|
_lastSoundEffectPrio = 0;
|
|
_curSound = 0;
|
|
}
|
|
|
|
void Indy3MacSnd::stopActiveSound() {
|
|
if (_soundEffectPlaying)
|
|
stopSoundEffect();
|
|
else if (_curSong || _songUnfinished)
|
|
stopSong();
|
|
_songUnfinished = false;
|
|
}
|
|
|
|
void Indy3MacSnd::finishSong() {
|
|
if (_soundUsage[_curSong])
|
|
--_soundUsage[_curSong];
|
|
_curSong = 0;
|
|
_songUnfinished = !(_mdrv->getStatus().flags & MacSoundDriver::kStatusDone);
|
|
}
|
|
|
|
void Indy3MacSnd::updateSong() {
|
|
if (_curSong && (_qualHi || (_mdrv->getStatus().flags & MacSoundDriver::kStatusDone))) {
|
|
_mdrv->clearFlags(MacSoundDriver::kStatusOverflow);
|
|
while (_curSong && !(_mdrv->getStatus().flags & MacSoundDriver::kStatusOverflow)) {
|
|
for (int i = 4; i; --i) {
|
|
for (int ii = 0; ii < _numMusicTracks && _curSong; ++ii)
|
|
_musicChannels[ii]->nextTick();
|
|
}
|
|
|
|
if (_disableFlags)
|
|
break;
|
|
|
|
if (_qualHi) {
|
|
for (int i = 0; i < _mdrv->numChannels(); ++i)
|
|
_mdrv->send(LegacyMusicDriver::kChanRate, i, _curSong ? _musicChannels[i]->checkPeriod() : 0);
|
|
if (_curSong)
|
|
_mdrv->send(LegacyMusicDriver::kDuration, 10);
|
|
} else {
|
|
MusicChannel *ch = nullptr;
|
|
for (int i = 0; i < _numMusicTracks && ch == nullptr && _curSong; ++i) {
|
|
if (_musicChannels[i]->checkPeriod())
|
|
ch = _musicChannels[i];
|
|
}
|
|
_mdrv->send(LegacyMusicDriver::kSquareWaveTriplet, ch ? ch->checkPeriod() : 0, 0xff);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Indy3MacSnd::updateSoundEffect() {
|
|
_sdrv->clearChannelFlags(_sfxChan, MacSoundDriver::kStatusDone);
|
|
bool chkRestart = false;
|
|
_soundEffectReschedule = false;
|
|
|
|
if (!_soundEffectPlaying || !_curSound) {
|
|
chkRestart = true;
|
|
} else {
|
|
if (_soundEffectNumLoops > 0)
|
|
--_soundEffectNumLoops;
|
|
if (_soundEffectNumLoops)
|
|
_sdrv->playSamples(_sfxChan, MacLowLevelPCMDriver::kImmediate, &_pcmSnd);
|
|
else
|
|
--_soundUsage[_curSound];
|
|
chkRestart = (_soundEffectNumLoops == 0);
|
|
}
|
|
|
|
if (chkRestart) {
|
|
_curSound = 0;
|
|
_lastSoundEffectPrio = 0;
|
|
checkRestartSoundEffects();
|
|
}
|
|
}
|
|
|
|
void Indy3MacSnd::checkRestartSoundEffects() {
|
|
for (int i = 1; i < _idRangeMax; ++i) {
|
|
if (!_soundUsage[i] || isSong(i))
|
|
continue;
|
|
|
|
const uint8 *ptr = _vm->getResourceAddress(rtSound, i);
|
|
assert(ptr);
|
|
if (READ_BE_UINT16(ptr + 6) == 0)
|
|
continue;
|
|
|
|
_soundUsage[i] = 1;
|
|
startSoundEffect(i);
|
|
}
|
|
}
|
|
|
|
void Indy3MacSnd::endOfTrack() {
|
|
if (!_activeChanCount || !--_activeChanCount)
|
|
finishSong();
|
|
}
|
|
|
|
bool Indy3MacSnd::isSong(int id) const {
|
|
return (Common::find(_musicIDTable, &_musicIDTable[_musicIDTableLen], id) != &_musicIDTable[_musicIDTableLen]);
|
|
}
|
|
|
|
bool Indy3MacSnd::isHiQuality() const {
|
|
return _mixerThread ? _qualHi : (_qmode == MacSound::kQualityAuto && (_vm->VAR_SOUNDCARD == 0xff || _vm->VAR(_vm->VAR_SOUNDCARD) == 11)) || (_qmode == MacSound::kQualityHighest);
|
|
}
|
|
|
|
Indy3MacSnd::MusicChannel *Indy3MacSnd::getMusicChannel(uint8 id) const {
|
|
return (id < _numMusicChannels) ? _musicChannels[id] : 0;
|
|
}
|
|
|
|
uint16 savedOffset = 0;
|
|
Indy3MacSnd::MusicChannel *Indy3MacSnd::MusicChannel::_ctrlChan = nullptr;
|
|
|
|
Indy3MacSnd::MusicChannel::MusicChannel(Indy3MacSnd *pl) : _player(pl), _vars(nullptr), _numVars(0), _ctrlProc(nullptr),
|
|
_resSize(0), _savedOffset(savedOffset), _modShapes(g_pv2ModTbl), _modShapesTableSize(g_pv2ModTblSize) {
|
|
static const CtrlProc ctrl[8] {
|
|
&Indy3MacSnd::MusicChannel::ctrl_setShape,
|
|
&Indy3MacSnd::MusicChannel::ctrl_modPara,
|
|
&Indy3MacSnd::MusicChannel::ctrl_init,
|
|
&Indy3MacSnd::MusicChannel::ctrl_returnFromSubroutine,
|
|
&Indy3MacSnd::MusicChannel::ctrl_jumpToSubroutine,
|
|
&Indy3MacSnd::MusicChannel::ctrl_initOther,
|
|
&Indy3MacSnd::MusicChannel::ctrl_decrJumpIf,
|
|
&Indy3MacSnd::MusicChannel::ctrl_writeVar
|
|
};
|
|
|
|
const uint16 *mVars[] = {
|
|
/* 0 */ &_frameLen, &_curPos, &_freqCur, &_freqIncr, &_freqEff,
|
|
/* 5 */ &_envPhase, &_envRate, &_tempo, &_envSust, (uint16*)&_transpose,
|
|
/* 10 */ &_envAtt, &_envShape, &_envStep, &_envStepLen, &_modType,
|
|
/* 15 */ &_modState, &_modStep, &_modSensitivity, &_modRange, &_localVars[0],
|
|
/* 20 */ &_localVars[1], &_localVars[2], &_localVars[3], &_localVars[4]
|
|
};
|
|
|
|
_ctrlProc = ctrl;
|
|
_vars = new uint16*[ARRAYSIZE(mVars)];
|
|
memcpy(_vars, mVars, sizeof(mVars));
|
|
_numVars = ARRAYSIZE(mVars);
|
|
_savedOffset = 0;
|
|
_ctrlChan = nullptr;
|
|
|
|
clear();
|
|
}
|
|
|
|
Indy3MacSnd::MusicChannel::~MusicChannel() {
|
|
clear();
|
|
delete[] _vars;
|
|
_vars = nullptr;
|
|
_numVars = 0;
|
|
}
|
|
|
|
void Indy3MacSnd::MusicChannel::clear() {
|
|
for (int i = 0; i < _numVars; ++i)
|
|
getMemberRef(i) = 0;
|
|
_resource.reset();
|
|
_resSize = 0;
|
|
_hq = false;
|
|
}
|
|
|
|
void Indy3MacSnd::MusicChannel::start(Common::SharedPtr<const byte> &songRes, uint16 offset, bool hq) {
|
|
clear();
|
|
_resource = songRes;
|
|
_resSize = READ_LE_UINT16(_resource.get());
|
|
_curPos = offset;
|
|
_frameLen = 1;
|
|
_hq = hq;
|
|
}
|
|
|
|
void Indy3MacSnd::MusicChannel::nextTick() {
|
|
if (!_frameLen)
|
|
return;
|
|
|
|
_ctrlChan = this;
|
|
_envPhase += _envRate;
|
|
_freqCur += _freqIncr;
|
|
|
|
uint16 v = _modState + _modStep;
|
|
uint16 frqAdjust = 0;
|
|
|
|
if (v != 0) {
|
|
if (v >= _modRange)
|
|
v -= _modRange;
|
|
_modState = v;
|
|
uint16 ix = (_modType + (v >> 4)) >> 4;
|
|
assert(ix < _modShapesTableSize);
|
|
frqAdjust = (((_modShapes[ix] << 7) * _modSensitivity) >> 16);
|
|
}
|
|
|
|
_freqEff = _freqCur + frqAdjust;
|
|
|
|
if (_envAtt && !--_envAtt) {
|
|
_envStep = 16;
|
|
_envStepLen = 1;
|
|
}
|
|
|
|
if (!--_frameLen)
|
|
parseNextEvents();
|
|
|
|
if (!_envStepLen || --_envStepLen)
|
|
return;
|
|
|
|
int ix = _envShape + (_envStep >> 2);
|
|
assert(ix < ARRAYSIZE(_envShapes));
|
|
const uint32 *in = &_envShapes[ix];
|
|
|
|
for (_envStep += 4; (*in & 0xffff) == 0xffff; ++in) {
|
|
_envPhase = *in >> 16;
|
|
if (_envPhase == 0)
|
|
_envRate = 0;
|
|
_envStep += 4;
|
|
}
|
|
|
|
_envStepLen = *in & 0xffff;
|
|
_envRate = *in >> 16;
|
|
}
|
|
|
|
void Indy3MacSnd::MusicChannel::parseNextEvents() {
|
|
if (_resSize && _curPos >= _resSize) {
|
|
warning("Indy3MacSnd::MusicChannel::parseNext(): playback error");
|
|
_frameLen = 0;
|
|
_curPos = 0;
|
|
_player->stopSong();
|
|
}
|
|
|
|
if (_curPos == 0)
|
|
return;
|
|
|
|
const byte *in = _resource.get() + _curPos;
|
|
|
|
for (bool loop = true, loop2 = false; loop; ) {
|
|
uint8 cmd = *in++;
|
|
|
|
if (in - _resource.get() >= _resSize)
|
|
break;
|
|
|
|
if (cmd >= 0xf8 && !loop2) {
|
|
if (!ctrlProc(cmd - 0xf8, in))
|
|
loop = false;
|
|
|
|
if (in - _resource.get() >= _resSize)
|
|
break;
|
|
|
|
} else {
|
|
loop2 = true;
|
|
MusicChannel *ch = _player->getMusicChannel(cmd >> 5);
|
|
setFrameLen(cmd);
|
|
cmd = *in++;
|
|
|
|
if (in - _resource.get() >= _resSize)
|
|
break;
|
|
|
|
if (ch != nullptr && ((cmd & 0x7f) != 0x7f))
|
|
ch->noteOn(_ctrlChan->_frameLen, cmd & 0x7f);
|
|
|
|
if (cmd & 0x80)
|
|
loop = false;
|
|
}
|
|
}
|
|
|
|
int cp = in - _resource.get();
|
|
if ((cp >= _resSize && _frameLen) || cp & ~0xffff) {
|
|
warning("Indy3MacSnd::MusicChannel::parseNext(): playback error");
|
|
_frameLen = 0;
|
|
_player->stopSong();
|
|
}
|
|
|
|
_curPos = _frameLen ? cp : 0;
|
|
if (!_frameLen)
|
|
_player->endOfTrack();
|
|
}
|
|
|
|
void Indy3MacSnd::MusicChannel::noteOn(uint16 duration, uint8 note) {
|
|
static const uint16 noteFreqTable[2][12] = {
|
|
{ 0xFFC0, 0xF140, 0xE3C0, 0xD700, 0xCB40, 0xBF80, 0xB4C0, 0xAA80, 0xA100, 0x9800, 0x8F80, 0x8740 },
|
|
{ 0x8E84, 0x8684, 0x7EF7, 0x77D7, 0x714F, 0x6AC4, 0x64C6, 0x5F1E, 0x59C7, 0x54BD, 0x4FFC, 0x4B7E }
|
|
};
|
|
_envStep = 0;
|
|
_envStepLen = 1;
|
|
_frameLen = duration;
|
|
_envAtt = _frameLen - _envSust;
|
|
int n = note + _transpose;
|
|
while (n < 0)
|
|
n += 12;
|
|
|
|
_freqEff = _freqCur = noteFreqTable[_hq ? 0 : 1][n % 12] >> (n / 12);
|
|
}
|
|
|
|
uint16 Indy3MacSnd::MusicChannel::checkPeriod() const {
|
|
return (_frameLen && _envPhase) ? _freqEff : 0;
|
|
}
|
|
|
|
bool Indy3MacSnd::MusicChannel::ctrl_setShape(const byte *&pos) {
|
|
static const uint16 offsets[15] = { 0, 6, 12, 18, 24, 30, 36, 44, 52, 60, 68, 82, 76, 82, 90 };
|
|
uint8 i = (*pos++) >> 1;
|
|
assert(i < ARRAYSIZE(offsets));
|
|
_envShape = offsets[i];
|
|
return true;
|
|
}
|
|
|
|
bool Indy3MacSnd::MusicChannel::ctrl_modPara(const byte *&pos) {
|
|
static const uint16 table[10] = { 0x0000, 0x1000, 0x1000, 0x1000, 0x2000, 0x0020, 0x3020, 0x2000, 0x2020, 0x1000 };
|
|
int ix = (*pos++);
|
|
if ((ix & 1) || ((ix >> 1) + 1 >= ARRAYSIZE(table)))
|
|
error("Indy3MacSnd::MusicChannel::ctrl_modPara(): data error");
|
|
ix >>= 1;
|
|
_modType = table[ix];
|
|
_modRange = table[ix + 1];
|
|
return true;
|
|
}
|
|
|
|
bool Indy3MacSnd::MusicChannel::ctrl_init(const byte *&pos) {
|
|
limitedClear();
|
|
return true;
|
|
}
|
|
|
|
bool Indy3MacSnd::MusicChannel::ctrl_returnFromSubroutine(const byte *&pos) {
|
|
pos = _resource.get() + _savedOffset;
|
|
if (pos >= _resource.get() + _resSize)
|
|
error("Indy3MacSnd::MusicChannel::ctrl_returnFromSubroutine(): invalid address");
|
|
return true;
|
|
}
|
|
|
|
bool Indy3MacSnd::MusicChannel::ctrl_jumpToSubroutine(const byte *&pos) {
|
|
uint16 offs = READ_LE_UINT16(pos);
|
|
_savedOffset = pos + 2 - _resource.get();
|
|
if (offs >= _resSize)
|
|
error("Indy3MacSnd::MusicChannel::ctrl_jumpToSubroutine(): invalid address");
|
|
pos = _resource.get() + offs;
|
|
return true;
|
|
}
|
|
|
|
bool Indy3MacSnd::MusicChannel::ctrl_initOther(const byte *&pos) {
|
|
uint16 val = READ_LE_UINT16(pos);
|
|
pos += 2;
|
|
if (val % 50)
|
|
error("Indy3MacSnd::MusicChannel::ctrl_initOther(): data error");
|
|
_ctrlChan = _player->getMusicChannel(val / 50);
|
|
assert(_ctrlChan);
|
|
_ctrlChan->limitedClear();
|
|
return true;
|
|
}
|
|
|
|
bool Indy3MacSnd::MusicChannel::ctrl_decrJumpIf(const byte *&pos) {
|
|
uint16 &var = getMemberRef(*pos++ >> 1);
|
|
int16 offs = READ_LE_INT16(pos);
|
|
pos += 2;
|
|
if (var == 0) {
|
|
pos += offs;
|
|
if (pos < _resource.get() || pos >= _resource.get() + _resSize)
|
|
error("Indy3MacSnd::MusicChannel::ctrl_decrJumpIf(): invalid address");
|
|
} else {
|
|
--var;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Indy3MacSnd::MusicChannel::ctrl_writeVar(const byte *&pos) {
|
|
byte ix = *pos++;
|
|
uint16 val = READ_LE_UINT16(pos);
|
|
pos += 2;
|
|
(getMemberRef(ix >> 1)) = val;
|
|
return (bool)ix;
|
|
}
|
|
|
|
bool Indy3MacSnd::MusicChannel::ctrlProc(int procId, const byte *&arg) {
|
|
return (_ctrlChan && _ctrlProc && procId >= 0 && procId <= 7) ? (_ctrlChan->*_ctrlProc[procId])(arg) : false;
|
|
}
|
|
|
|
void Indy3MacSnd::MusicChannel::setFrameLen(uint8 len) {
|
|
static const uint8 durationTicks[22] = {
|
|
0x00, 0x00, 0x00, 0x02, 0x00, 0x03, 0x04, 0x05, 0x06, 0x08, 0x09,
|
|
0x0C, 0x10, 0x12, 0x18, 0x20, 0x26, 0x30, 0x40, 0x48, 0x60, 0x00
|
|
};
|
|
|
|
assert(_ctrlChan);
|
|
len &= 0x1f;
|
|
if (len >= ARRAYSIZE(durationTicks))
|
|
error("Indy3MacSnd::MusicChannel::setFrameLen(): Out of range (val %d, range 0 - %d)", len, ARRAYSIZE(durationTicks) - 1);
|
|
_ctrlChan->_frameLen = MAX<uint16>(_ctrlChan->_tempo, 1) * durationTicks[len];
|
|
}
|
|
|
|
void Indy3MacSnd::MusicChannel::limitedClear() {
|
|
for (int i = 1; i < 7; ++i)
|
|
getMemberRef(i) = 0;
|
|
for (int i = 8; i < 10; ++i)
|
|
getMemberRef(i) = 0;
|
|
for (int i = 11; i < 15; ++i)
|
|
getMemberRef(i) = 0;
|
|
for (int i = 15; i < 19; ++i)
|
|
getMemberRef(i) = 0;
|
|
}
|
|
|
|
uint16 &Indy3MacSnd::MusicChannel::getMemberRef(int pos) {
|
|
assert(_vars);
|
|
if (pos < 0 || pos >= _numVars)
|
|
error("Indy3MacSnd::MusicChannel::getMemberRef(): attempting invalid access (var: %d, valid range: %d - %d)", pos, 0, _numVars - 1);
|
|
return *_vars[pos];
|
|
}
|
|
|
|
const uint32 Indy3MacSnd::MusicChannel::_envShapes[98] = {
|
|
0x0003ffff, 0x00000000, 0x00000000, 0x00000000, 0x0000ffff, 0x00000000,
|
|
0x0003ffff, 0x00000020, 0x0000ffff, 0x00000000, 0x0000ffff, 0x00000000,
|
|
0x0003ffff, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
|
|
0x0003ffff, 0x00000002, 0x0000ffff, 0x00000000, 0x0000ffff, 0x00000000,
|
|
0x0003ffff, 0x00000006, 0x0000ffff, 0x00000000, 0x0000ffff, 0x00000000,
|
|
0x0003ffff, 0x00000010, 0x0000ffff, 0x00000000, 0x0000ffff, 0x00000000,
|
|
0xea60ffff, 0xfc180014, 0x00000000, 0x00000000, 0x9c40ffff, 0xec780005, 0x0000ffff, 0x00000000,
|
|
0xc350ffff, 0x00000008, 0x7530ffff, 0x00000000, 0x6d60ffff, 0xec780005, 0x0000ffff, 0x00000000,
|
|
0xea60ffff, 0xf8300010, 0x00000000, 0x00000000, 0x6d60ffff, 0xe8900005, 0x0000ffff, 0x00000000,
|
|
0xd6d8ffff, 0x00000008, 0x88b8ffff, 0x00000000, 0x9c40ffff, 0xf63c000a, 0x0000ffff, 0x00000000,
|
|
0xea60ffff, 0x00000004, 0xf63c0008, 0x00000000, 0x9c40ffff, 0xe8900005, 0x0000ffff, 0x00000000,
|
|
0x0000ffff, 0x00960154, 0xff6a0154, 0x0000ffff, 0x0000ffff, 0x00000000,
|
|
0x4e20ffff, 0x0fa00007, 0x03e8000f, 0x00000000, 0x88b8ffff, 0xf830000f, 0x0000ffff, 0x00000000,
|
|
0x88b8ffff, 0x01f40014, 0x00000000, 0x00000000, 0xafc8ffff, 0xfe0c003c, 0x0000ffff, 0x00000000
|
|
};
|
|
|
|
#undef ASC_DEVICE_RATE
|
|
#undef PCM_BUFFER_SIZE
|
|
|
|
} // End of namespace Scumm
|