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

1492 lines
42 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 "common/macresman.h"
#include "common/punycode.h"
#include "scumm/detection.h"
#include "scumm/file.h"
#include "scumm/scumm.h"
#include "scumm/players/mac_sound_lowlevel.h"
#include "scumm/players/player_mac_new.h"
#include "scumm/imuse/drivers/macintosh.h"
#include "scumm/imuse/imuse.h"
namespace IMSMacintosh {
using namespace Scumm;
enum : byte {
kRhythmPart = 9
};
struct ChanControlNode;
struct DeviceChannel {
DeviceChannel(const uint32 *pitchtable) : pitchTable(pitchtable), frequency(0), phase(0), end(nullptr), pos(nullptr), smpBuffStart(nullptr),
smpBuffEnd(nullptr), loopStart(nullptr), loopEnd(nullptr), pitch(0), mute(true), release(false), instr(nullptr), rhtm(false),
prog(0), baseFreq(0), note(0), volumeL(0), volumeR(0), rate(0), totalLevelL(0), totalLevelR(0), node(nullptr), prev(nullptr), next(nullptr) {}
~DeviceChannel() {}
void recalcFrequency();
uint32 frequency;
uint32 phase;
const byte *end;
const byte *pos;
const byte *smpBuffStart;
const byte *smpBuffEnd;
const byte *loopStart;
const byte *loopEnd;
uint16 pitch;
byte volumeL;
byte volumeR;
byte totalLevelL;
byte totalLevelR;
byte baseFreq;
uint32 rate;
uint16 prog;
byte note;
bool rhtm;
bool mute;
bool release;
const ChanControlNode *node;
Common::SharedPtr<MacSndResource> instr;
const uint32 *pitchTable;
DeviceChannel *prev;
DeviceChannel *next;
};
class IMSMacSoundSystem : public VblTaskClientDriver, public MacLowLevelPCMDriver::CallbackClient {
public:
IMSMacSoundSystem(Audio::Mixer *mixer, byte version);
virtual ~IMSMacSoundSystem();
bool init(const char *const *instrFileNames, int numInstrFileNames, bool stereo, bool internal16Bit);
void deinit();
virtual bool start() = 0;
void stop();
virtual void setQuality(int qual) = 0;
void noteOn(const ChanControlNode *node);
void noteOff(const ChanControlNode *node);
void voiceOff(const ChanControlNode *node);
void setVolume(const ChanControlNode *node);
void setPitchBend(const ChanControlNode *node);
void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc);
void vblCallback() override;
void generateData(int8 *dst, uint32 byteSize, Audio::Mixer::SoundType type, bool expectStereo) const override;
const MacSoundDriver::Status &getDriverStatus(Audio::Mixer::SoundType type) const override;
void dblBuffCallback(MacLowLevelPCMDriver::DoubleBuffer *dblBuffer) override;
void setMasterVolume(Audio::Mixer::SoundType type, uint16 volume);
protected:
bool setupResourceFork(Common::MacResManager &rm, const char *const *fileNames, int numFileNames);
Common::SharedPtr<MacSndResource> getSndResource(uint16 id);
void fillPitchTable();
Common::Array<Common::SharedPtr<MacSndResource> > _smpResources;
MacLowLevelPCMDriver::ChanHandle _musicChan;
MacLowLevelPCMDriver::ChanHandle _sfxChan;
MacLowLevelPCMDriver *_sdrv;
bool _internal16Bit;
bool _stereo;
uint16 _feedBufferSize;
uint16 _defaultInstrID;
byte _quality;
const uint16 _numChannels;
Audio::Mixer *_mixer;
DeviceChannel **_channels;
private:
virtual bool loadInstruments(const char *const *fileNames, int numFileNames) = 0;
virtual void setInstrument(DeviceChannel *chan) = 0;
virtual void recalcFrequency(DeviceChannel *chan) = 0;
virtual void recalcVolume(DeviceChannel *chan) = 0;
virtual void noteOffIntern(DeviceChannel *chan) = 0;
virtual DeviceChannel *allocateChannel(const ChanControlNode *node) = 0;
uint32 *_pitchTable;
byte *_ampTable;
int16 *_mixTable;
int16 *_mixBuffer16Bit;
MacPlayerAudioStream *_macstr;
Audio::SoundHandle _soundHandle;
MacPlayerAudioStream::CallbackProc _vblTskProc;
void *_timerParam;
Common::TimerManager::TimerProc _timerProc;
const byte _version;
};
class DJMSoundSystem final : public IMSMacSoundSystem {
public:
DJMSoundSystem(Audio::Mixer *mixer);
~DJMSoundSystem() override {}
bool start() override;
void setQuality(int qual) override;
private:
bool loadInstruments(const char *const *fileNames, int numFileNames) override;
void setInstrument(DeviceChannel *chan) override;
void recalcFrequency(DeviceChannel *chan) override;
void recalcVolume(DeviceChannel *chan) override;
void noteOffIntern(DeviceChannel *chan) override;
DeviceChannel *allocateChannel(const ChanControlNode *node) override;
MacLowLevelPCMDriver::DBCallback _dbCbProc;
};
class NewMacSoundSystem final : public IMSMacSoundSystem {
public:
NewMacSoundSystem(ScummEngine *vm, Audio::Mixer *mixer);
~NewMacSoundSystem() override;
bool start() override;
void setQuality(int qual) override {}
private:
bool loadInstruments(const char *const *fileNames, int numFileNames) override;
Common::SharedPtr<MacSndResource> getNoteRangeSndResource(uint16 id, byte note);
void setInstrument(DeviceChannel *chan) override;
void recalcFrequency(DeviceChannel *chan) override;
void recalcVolume(DeviceChannel *chan) override;
void noteOffIntern(DeviceChannel *chan) override;
DeviceChannel *allocateChannel(const ChanControlNode *node) override;
MacLowLevelPCMDriver::DBCallback _dbCbProc;
ScummFile *_fileMan;
Common::Path &_container;
Common::Path _dummy;
struct Instrument {
Instrument(uint16 resId) : id(resId), noteSmplsMapping(nullptr) {
noteSmplsMapping = new byte[128]();
memset(noteSmplsMapping, 0xFF, 128);
}
~Instrument() {
delete[] noteSmplsMapping;
}
uint16 id;
byte *noteSmplsMapping;
Common::Array<Common::SharedPtr<MacSndResource> > sndRes;
};
Common::Array<Common::SharedPtr<Instrument> > _instruments;
};
class IMuseChannel_Macintosh final : public MidiChannel {
public:
IMuseChannel_Macintosh(IMuseDriver_Macintosh *drv, int number);
~IMuseChannel_Macintosh() override {}
MidiDriver *device() override { return _drv; }
byte getNumber() override { return _number; }
bool allocate();
void release() override { _allocated = false; }
void send(uint32 b) override { if (_drv) _drv->send((b & ~0x0F) | _number); }
// Regular messages
void noteOff(byte note) override;
void noteOn(byte note, byte velocity) override;
void controlChange(byte control, byte value) override;
void programChange(byte program) override;
void pitchBend(int16 bend) override;
// Control Change and SCUMM specific functions
void pitchBendFactor(byte value) override { pitchBend(0); _pitchBendSensitivity = value; }
void transpose(int8 value) override { _transpose = value; pitchBend(_pitchBendSet); }
void detune(int16 value) override { _detune = value; pitchBend(_pitchBendSet); }
void priority(byte value) override { _prio = value; }
void sustain(bool value) override;
void bankSelect(byte bank) override { _bank = bank; }
void allNotesOff() override;
void sysEx_customInstrument(uint32 type, const byte *instr, uint32 dataSize) override {}
private:
void dataEntry(byte value);
void updateVolume();
bool _allocated;
byte _prio;
byte _bank;
byte _volume;
byte _panPos;
int16 _detune;
int8 _transpose;
byte _polyphony;
byte _usage;
bool _overuse;
int16 _pitchBendSet;
byte _pitchBendSensitivity;
int16 _pitchBendEff;
uint16 _pitchBendRange;
byte _rpn;
bool _sustain;
uint16 _prog;
const byte _number;
ChanControlNode *allocateNode(int prio);
ChanControlNode **_channels;
ChanControlNode *_rtmChannel;
ChanControlNode *_out;
static byte _allocCur;
IMuseDriver_Macintosh *_drv;
IMSMacSoundSystem *_device;
const byte _numChannels;
const int8 _version;
};
struct ChanControlNode {
ChanControlNode(byte num) : in(nullptr), number(num), note(0), sustain(false), rhythmPart(false), prio(0x7F), volume(0x7F), panPos(0x40), velocity(0), pitchBend(0), prog(0), prev(nullptr), next(nullptr) {}
IMuseChannel_Macintosh *in;
const byte number;
bool sustain;
bool rhythmPart;
byte prio;
byte note;
byte volume;
byte panPos;
byte velocity;
int16 pitchBend;
uint16 prog;
ChanControlNode *prev;
ChanControlNode *next;
};
void connect(ChanControlNode *&chain, ChanControlNode *node) {
if (!node || node->prev || node->next)
return;
if ((node->next = chain))
chain->prev = node;
chain = node;
}
void disconnect(ChanControlNode *&chain, ChanControlNode *node) {
if (!node || !chain)
return;
const ChanControlNode *ch = chain;
while (ch && ch != node)
ch = ch->next;
if (!ch)
return;
if (node->next)
node->next->prev = node->prev;
if (node->prev)
node->prev->next = node->next;
else
chain = node->next;
node->in = nullptr;
node->next = node->prev = nullptr;
}
void DeviceChannel::recalcFrequency() {
int cpos = (pitch >> 7) + 60 - baseFreq;
if (cpos < 0)
frequency = (uint32)-1;
else if ((pitch & 0x7F) && cpos < 0x7F)
frequency = pitchTable[cpos] + (((pitchTable[cpos + 1] - pitchTable[cpos]) * (pitch & 0x7F)) >> 7);
else
frequency = pitchTable[cpos];
}
IMSMacSoundSystem::IMSMacSoundSystem(Audio::Mixer *mixer, byte version) : VblTaskClientDriver(), _mixer(mixer), _macstr(nullptr), _sdrv(nullptr), _vblTskProc(this, &VblTaskClientDriver::vblCallback),
_musicChan(0), _sfxChan(0), _quality(22), _feedBufferSize(1024), _channels(nullptr), _timerParam(nullptr), _timerProc(nullptr), _pitchTable(nullptr),
_ampTable(nullptr), _mixTable(nullptr), _numChannels(version ? 12 : 8), _defaultInstrID(0), _version(version), _stereo(false) {
_pitchTable = new uint32[128]();
assert(_pitchTable);
_channels = new DeviceChannel*[_numChannels];
assert(_channels);
for (int i = 0; i < _numChannels; ++i)
_channels[i] = new DeviceChannel(_pitchTable);
_mixBuffer16Bit = new int16[_feedBufferSize]();
assert(_mixBuffer16Bit);
fillPitchTable();
_ampTable = new byte[8192]();
assert(_ampTable);
_mixTable = new int16[_numChannels << 8]();
assert(_mixTable);
}
IMSMacSoundSystem::~IMSMacSoundSystem() {
deinit();
if (_channels) {
for (int i = 0; i < _numChannels; ++i)
delete _channels[i];
delete[] _channels;
}
delete[] _pitchTable;
delete[] _mixTable;
delete[] _mixBuffer16Bit;
}
bool IMSMacSoundSystem::init(const char *const *instrFileNames, int numInstrFileNames, bool stereo, bool internal16Bit) {
if (!loadInstruments(instrFileNames, numInstrFileNames))
return false;
_internal16Bit = internal16Bit;
_stereo = (_version) > 0 && stereo;
uint16 feedBufferSize = _stereo ? 2048 : 1024;
if (_feedBufferSize != feedBufferSize) {
delete[] _mixBuffer16Bit;
_feedBufferSize = feedBufferSize;
_mixBuffer16Bit = new int16[_feedBufferSize]();
assert(_mixBuffer16Bit);
}
static const byte ampTableV0[32] = {
0x00, 0x00, 0x01, 0x01, 0x02, 0x03, 0x05, 0x06,
0x08, 0x0b, 0x0d, 0x10, 0x13, 0x16, 0x1a, 0x1e,
0x22, 0x26, 0x2b, 0x30, 0x35, 0x3a, 0x40, 0x46,
0x4c, 0x53, 0x59, 0x60, 0x68, 0x6f, 0x77, 0x7f
};
byte *dst = _ampTable;
for (int i = 0; i < 32; ++i) {
byte mul = (_version == 0) ? ampTableV0[i] : i << 2;
for (int ii = 0; ii < 256; ++ii)
*dst++ = (((ii - 128) * mul) / 127) + 128;
}
uint32 m = _numChannels << 7;
int16 *d1 = &_mixTable[m];
int16 *d2 = &_mixTable[m - 1];
if (_version == 0) {
byte sh = _internal16Bit ? 5 : 0;
byte div = _internal16Bit ? 1 : _numChannels;
byte base = _internal16Bit? 0 : 128;
for (uint32 i = 0; i < m; ++i) {
uint16 val = (i << sh) / div;
*d1++ = base + val;
*d2-- = base - val - 1;
}
} else if (_internal16Bit) {
for (uint32 i = 0; i < m; ++i) {
uint16 val = (((i * _numChannels * 127) << 6) / (((_numChannels >> 1) - 1) * i + _numChannels * 127)) << 1;
*d1++ = val;
*d2-- = -val - 1;
}
} else {
for (uint32 i = 0; i < m; ++i) {
uint16 val = ((((i * _numChannels * 127) << 7) >> 1) / (((_numChannels >> 1) - 1) * i + _numChannels * 127) + 128) >> 7;
*d1++ = (byte)(128 + val);
*d2-- = (byte)(128 - val - 1);
}
}
_macstr = new MacPlayerAudioStream(this, _mixer->getOutputRate(), _stereo, false, internal16Bit);
if (!_macstr)
return false;
// 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.
_sdrv = new MacLowLevelPCMDriver(_mixer->mutex(), _mixer->getOutputRate() << 16/*ASC_DEVICE_RATE*/, internal16Bit);
if (!_sdrv)
return false;
_macstr->initBuffers(1024);
_macstr->addVolumeGroup(Audio::Mixer::kMusicSoundType);
// Only MI2 and FOA have MIDI sound effects. Also, the later versions use a different update method.
if (_version == 0) {
_macstr->addVolumeGroup(Audio::Mixer::kSFXSoundType);
_macstr->setVblCallback(&_vblTskProc);
}
// The stream will by default expect 8-bit data and scale that to 16-bit. Or at least only low amplitude 16-bit data that only needs minor adjustment.
// For full range 16-bit input, we need some post-process downscaling, otherwise the application of the ScummVM global volume setting will cause overflows.
if (internal16Bit)
_macstr->scaleVolume(0, 5);
_mixer->playStream(Audio::Mixer::kPlainSoundType, &_soundHandle, _macstr, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
return true;
}
void IMSMacSoundSystem::deinit() {
Common::StackLock lock(_mixer->mutex());
stop();
_mixer->stopHandle(_soundHandle);
_timerProc = nullptr;
delete _macstr;
_macstr = nullptr;
delete _sdrv;
_sdrv = nullptr;
delete[] _ampTable;
_ampTable = nullptr;
}
void IMSMacSoundSystem::stop() {
Common::StackLock lock(_mixer->mutex());
if (_sdrv) {
_sdrv->disposeChannel(_musicChan);
if (_sfxChan)
_sdrv->disposeChannel(_sfxChan);
}
}
void IMSMacSoundSystem::noteOn(const ChanControlNode *node) {
assert(node);
Common::StackLock lock(_mixer->mutex());
DeviceChannel *c = allocateChannel(node);
if (!c)
return;
setInstrument(c);
if (c->instr == nullptr)
return;
debug(6, "NOTE ON: ims part %d, chan node %d, note %d, instr id %d (%s)", node->in ? node->in->getNumber() : node->number, node->number, node->note, node->prog, c->instr.get()->name());
// SAMNMAX: This is a workaround to fix a hanging note in the final credits track. The issue is present in the original game,too.
bool fixHangingNote = (c->instr.get()->id() == 321 && c->note == 40);
if (fixHangingNote)
debug(7, "%s:() Triggered hanging note workaround.", __FUNCTION__);
recalcVolume(c);
const MacLowLevelPCMDriver::PCMSound *s = c->instr.get()->data();
c->baseFreq = !node->rhythmPart ? s->baseFreq : 60;
c->rate = s->rate;
c->smpBuffStart = s->data.get();
c->smpBuffEnd = c->smpBuffStart + s->len;
if ((_version == 0 && (int32)s->loopst >= (int32)s->loopend - 12) || (_version > 0 && (fixHangingNote || !s->loopst || !s->loopend || node->rhythmPart || (int32)s->loopst > (int32)s->len - 10))) {
c->loopStart = nullptr;
c->loopEnd = c->smpBuffEnd;
} else {
c->loopStart = c->smpBuffStart + s->loopst;
c->loopEnd = c->smpBuffStart + s->loopend;
}
c->pitch = (node->note << 7) + node->pitchBend;
c->mute = c->release = false;
c->end = c->loopEnd;
c->pos = c->smpBuffStart;
c->phase = 0;
recalcFrequency(c);
}
void IMSMacSoundSystem::noteOff(const ChanControlNode *node) {
Common::StackLock lock(_mixer->mutex());
for (int i = 0; i < _numChannels; ++i) {
DeviceChannel *c = _channels[i];
if (c->node == node) {
noteOffIntern(c);
c->node = nullptr;
}
}
}
void IMSMacSoundSystem::voiceOff(const ChanControlNode *node) {
Common::StackLock lock(_mixer->mutex());
for (int i = 0; i < _numChannels; ++i) {
DeviceChannel *c = _channels[i];
if (c->node == node) {
c->mute = true;
c->release = false;
c->node = nullptr;
}
}
}
void IMSMacSoundSystem::setVolume(const ChanControlNode *node) {
Common::StackLock lock(_mixer->mutex());
for (int i = 0; i < _numChannels; ++i) {
DeviceChannel *c = _channels[i];
if (c->node == node)
recalcVolume(c);
}
}
void IMSMacSoundSystem::setPitchBend(const ChanControlNode *node) {
Common::StackLock lock(_mixer->mutex());
for (int i = 0; i < _numChannels; ++i) {
DeviceChannel *c = _channels[i];
if (c->node == node) {
c->pitch = (node->note << 7) + node->pitchBend;
recalcFrequency(c);
}
}
}
void IMSMacSoundSystem::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
_timerParam = timerParam;
_timerProc = timerProc;
}
void IMSMacSoundSystem::vblCallback() {
if (_timerProc)
_timerProc(_timerParam);
}
void IMSMacSoundSystem::generateData(int8 *dst, uint32 len, Audio::Mixer::SoundType type, bool expectStereo) const {
assert(dst);
memset(dst, 0, len);
_sdrv->feed(dst, len, type, expectStereo);
}
const MacSoundDriver::Status &IMSMacSoundSystem::getDriverStatus(Audio::Mixer::SoundType type) const {
return _sdrv->getStatus(type);
}
void IMSMacSoundSystem::dblBuffCallback(MacLowLevelPCMDriver::DoubleBuffer *dblBuffer) {
if (_version > 0 && _timerProc)
_timerProc(_timerParam);
uint16 sil = 0;
memset(_mixBuffer16Bit, 0, _feedBufferSize * sizeof(uint16));
uint16 frameSize = _stereo ? 2 : 1;
for (int i = 0; i < _numChannels; ++i) {
DeviceChannel &c = *_channels[i];
if (c.release) {
c.totalLevelL = (c.totalLevelL > 12) ? c.totalLevelL - 12 : 0;
c.totalLevelR = (c.totalLevelR > 12) ? c.totalLevelR - 12 : 0;
if (c.totalLevelL == 0 && c.totalLevelR == 0) {
c.release = false;
c.mute = true;
}
}
if (c.mute || c.frequency == (uint32)-1 ||
// This is our "trick" for telling apart music and sfx in MI2/INDY4: We look at the sound bank that
// is currently in use, the musical one (1000 - 1127) or the sfx one (2000 - 2255). Unfortunately,
// most sound effects seem to use the musical bank, though. So, in the end we can't distinguish all that much...
(_version == 0 && ((dblBuffer->chanHandle == _musicChan && c.instr.get()->id() >= 2000) ||
(dblBuffer->chanHandle == _sfxChan && c.instr.get()->id() < 2000)))) {
++sil;
continue;
}
const byte *a1 = &_ampTable[(c.totalLevelL & ~3) << 6];
const byte *a2 = &_ampTable[(c.totalLevelR & ~3) << 6];
int16 *t = _mixBuffer16Bit;
for (int ii = 0; ii < _feedBufferSize; ii += frameSize) {
if (!c.pos) {
*t++ += 0x80;
if (_stereo)
*t++ += 0x80;
continue;
}
c.phase += c.frequency;
if (c.phase >> 16) {
c.pos += (c.phase >> 16);
c.phase &= 0xFFFF;
if (c.loopEnd <= c.pos) {
if (c.loopStart) {
c.pos -= (c.loopEnd - c.loopStart);
} else {
c.pos = nullptr;
c.mute = true;
c.release = false;
ii -= frameSize;
continue;
}
}
}
*t++ += a1[*c.pos];
if (_stereo)
*t++ += a2[*c.pos];
}
}
const int16 *s = _mixBuffer16Bit;
sil <<= 7;
if (_internal16Bit) {
int16 *d = reinterpret_cast<int16*>(dblBuffer->data);
for (int i = 0; i < _feedBufferSize; ++i)
*d++ = _mixTable[sil + *s++];
} else {
byte *d = dblBuffer->data;
for (int i = 0; i < _feedBufferSize; ++i)
*d++ = _mixTable[sil + *s++];
}
dblBuffer->numFrames = _feedBufferSize / frameSize;
dblBuffer->flags |= MacLowLevelPCMDriver::DoubleBuffer::kBufferReady;
}
void IMSMacSoundSystem::setMasterVolume(Audio::Mixer::SoundType type, uint16 volume) {
if (_macstr)
_macstr->setMasterVolume(type, volume);
}
bool IMSMacSoundSystem::setupResourceFork(Common::MacResManager &rm, const char *const *fileNames, int numFileNames) {
const Common::CodePage tryCodePages[] = {
Common::kMacRoman,
Common::kISO8859_1
};
Common::Path resFile;
for (int i = 0; resFile.empty() && i < numFileNames && fileNames[i] != nullptr; ++i) {
for (int ii = 0; resFile.empty() && ii < ARRAYSIZE(tryCodePages); ++ii) {
Common::U32String fn(fileNames[i], tryCodePages[ii]);
resFile = Common::Path(fn.encode(Common::kUtf8));
if (!rm.exists(resFile) || !rm.open(resFile) || !rm.hasResFork()) {
rm.close();
resFile = Common::Path(Common::punycode_encodefilename(fn));
if (!rm.exists(resFile) || !rm.open(resFile) || !rm.hasResFork()) {
rm.close();
resFile.clear();
}
}
}
}
if (resFile.empty()) {
warning("%s(): Resource fork not found", __FUNCTION__);
return false;
}
return true;
}
Common::SharedPtr<MacSndResource> IMSMacSoundSystem::getSndResource(uint16 id) {
Common::SharedPtr<MacSndResource> res;
Common::SharedPtr<MacSndResource> *def = nullptr;
for (Common::Array<Common::SharedPtr<MacSndResource> >::iterator it = _smpResources.begin(); res == nullptr && it != _smpResources.end(); ++it) {
uint16 cid = (*it)->id();
if (cid == id)
res = *it;
else if (cid == _defaultInstrID)
def = it;
}
if (res == nullptr) {
if (def != nullptr)
res = *def;
else
error("%s(): Failure (instrument id %d)", __FUNCTION__, id);
}
return res;
}
void IMSMacSoundSystem::fillPitchTable() {
static const double ptbl[12] = {
1664510.645469, 1763487.599042, 1868350.028545, 1979447.902589,
2097152.000000, 2221855.147262, 2353973.529536, 2493948.079642,
2642245.949629, 2799362.069852, 2965820.800758, 3142177.682886
};
for (int i = 0; i < 12; ++i)
_pitchTable[116 + i] = (uint32)trunc(ptbl[i]);
for (int i = 115; i >= 0; --i)
_pitchTable[i] = _pitchTable[i + 12] >> 1;
if (_quality != 11)
return;
for (int i = 0; i < 128; ++i)
_pitchTable[i] <<= 1;
}
DJMSoundSystem::DJMSoundSystem(Audio::Mixer *mixer) : IMSMacSoundSystem(mixer, 0), _dbCbProc(this, &MacLowLevelPCMDriver::CallbackClient::dblBuffCallback) {
_defaultInstrID = 999;
}
bool DJMSoundSystem::start() {
_musicChan = _sdrv->createChannel(Audio::Mixer::kMusicSoundType, MacLowLevelPCMDriver::kSampledSynth, 0x8C, nullptr);
_sfxChan = _sdrv->createChannel(Audio::Mixer::kSFXSoundType, MacLowLevelPCMDriver::kSampledSynth, 0x8C, nullptr);
if (!_musicChan || !_sfxChan)
return false;
uint32 rate = 0;
switch (_quality) {
case 5:
rate = 0x15BBA2E8;
break;
case 7:
rate = 0x1CFA2E8B;
break;
case 11:
rate = 0x2B7745D1;
break;
case 22:
rate = 0x56EE8BA3;
break;
default:
warning("%s(): Invalid quality setting %d", __FUNCTION__, _quality);
return false;
}
return _sdrv->playDoubleBuffer(_musicChan, 1, _internal16Bit ? 16 : 8, rate, &_dbCbProc, _internal16Bit ? _numChannels : 1) &&
_sdrv->playDoubleBuffer(_sfxChan, 1, _internal16Bit ? 16 : 8, rate, &_dbCbProc, _internal16Bit ? _numChannels : 1);
}
void DJMSoundSystem::setQuality(int qual) {
Common::StackLock lock(_mixer->mutex());
qual = qual > 1 ? 22 : 11;
if (qual == _quality)
return;
_quality = qual;
_feedBufferSize = (qual == 22) ? 1024 : 512;
fillPitchTable();
for (int i = 0; i < _numChannels; ++i)
_channels[i]->recalcFrequency();
stop();
if (!start())
error("%s(): Unknown error", __FUNCTION__);
}
bool DJMSoundSystem::loadInstruments(const char *const *fileNames, int numFileNames) {
Common::MacResManager resMan;
if (!setupResourceFork(resMan, fileNames, numFileNames))
return false;
_smpResources.clear();
Common::MacResIDArray ids = resMan.getResIDArray(MKTAG('s', 'n', 'd', ' '));
for (Common::MacResIDArray::const_iterator i = ids.begin(); i != ids.end(); ++i) {
Common::SeekableReadStream *str = resMan.getResource(MKTAG('s', 'n', 'd', ' '), *i);
uint16 type = str ? str->readUint16BE() : 0;
if (type == 1 || type == 2)
_smpResources.push_back(Common::SharedPtr<MacSndResource>(new MacSndResource(*i, str, resMan.getResName(MKTAG('s', 'n', 'd', ' '), *i))));
delete str;
}
return !_smpResources.empty();
}
void DJMSoundSystem::setInstrument(DeviceChannel *chan) {
assert(chan && chan->node);
if (chan->instr == nullptr || chan->prog != chan->node->prog) {
chan->instr = getSndResource(chan->node->prog);
chan->prog = chan->node->prog;
}
}
void DJMSoundSystem::recalcFrequency(DeviceChannel *chan) {
assert(chan);
chan->recalcFrequency();
if (chan->instr != nullptr)
chan->mute = false;
}
void DJMSoundSystem::recalcVolume(DeviceChannel *c) {
assert(c && c->node);
c->totalLevelL = c->node->volume;
if (c->instr != nullptr)
c->mute = false;
}
void DJMSoundSystem::noteOffIntern(DeviceChannel *chan) {
if (chan->loopStart)
chan->mute = true;
chan->note = 0;
}
DeviceChannel *DJMSoundSystem::allocateChannel(const ChanControlNode *node) {
assert(node && node->number < _numChannels);
DeviceChannel *c = _channels[node->number];
c->node = node;
return c;
}
NewMacSoundSystem::NewMacSoundSystem(ScummEngine *vm, Audio::Mixer *mixer) : IMSMacSoundSystem(mixer, 1), // It is sufficient to distinguish version 0 and 1 here.
_fileMan(nullptr), _container(vm ? vm->_containerFile : _dummy), _dbCbProc(this, &MacLowLevelPCMDriver::CallbackClient::dblBuffCallback) {
assert(vm);
_defaultInstrID = 0xFFFF;
_fileMan = new ScummFile(vm);
}
NewMacSoundSystem::~NewMacSoundSystem() {
delete _fileMan;
}
bool NewMacSoundSystem::start() {
_musicChan = _sdrv->createChannel(Audio::Mixer::kMusicSoundType, MacLowLevelPCMDriver::kSampledSynth, _stereo ? 0xCC : 0x8C, nullptr);
return _musicChan ? _sdrv->playDoubleBuffer(_musicChan, _stereo ? 2 : 1, _internal16Bit ? 16 : 8, 0x56220000, &_dbCbProc, _internal16Bit ? _numChannels : 1) : false;
}
bool NewMacSoundSystem::loadInstruments(const char *const *fileNames, int numFileNames) {
if ((_container.empty() && !_fileMan->open(fileNames[0])) || (!_container.empty() && (!_fileMan->open(_container) || !_fileMan->openSubFile(fileNames[0]))))
return false;
uint32 num = _fileMan->readUint32BE();
uint32 size = _fileMan->readUint32BE();
byte *buff = new byte[size]();
_fileMan->read(buff, size);
_fileMan->close();
_smpResources.clear();
const byte *s = buff;
for (uint i = 0; i < num; ++i) {
uint32 offset = READ_BE_UINT32(s + 4);
uint32 resSize = (i < num - 1 ? READ_BE_UINT32(s + 12) : size) - offset;
_smpResources.push_back(Common::SharedPtr<MacSndResource>(new MacSndResource(READ_BE_UINT16(s + 2), buff + offset, resSize)));
s += 8;
}
delete[] buff;
if (_smpResources.empty())
return false;
Common::MacResManager resMan;
if (!setupResourceFork(resMan, &fileNames[1], numFileNames - 1))
return false;
Common::MacResIDArray instIDs = resMan.getResIDArray(MKTAG('I', 'N', 'S', 'T'));
for (Common::MacResIDArray::const_iterator i = instIDs.begin(); i != instIDs.end(); ++i) {
Common::SeekableReadStream *str = resMan.getResource(MKTAG('I', 'N', 'S', 'T'), *i);
if (!str)
error("%s(): Failed to load instrument resource (type 'INST', id '%d')", __FUNCTION__, *i);
uint sz = str->size();
byte *b = new byte[sz]();
str->read(b, sz);
delete str;
Instrument *ins = new Instrument(*i);
ins->sndRes.push_back(getSndResource(READ_BE_UINT16(b)));
if (ins->sndRes[0] != nullptr)
memset(ins->noteSmplsMapping, 0, 128);
byte numRanges = CLIP<int8>(b[13], 0, 7);
assert(sz >= 16u + numRanges * 8u);
for (int ii = 0; ii < numRanges; ++ii) {
ins->sndRes.push_back(getSndResource(READ_BE_INT16(b + 16 + ii * 8)));
if (ins->sndRes.back() != nullptr) {
byte rl = b[14 + ii * 8];
byte rh = b[15 + ii * 8];
assert(rl < 128 && rh < 128);
for (int iii = rl; iii <= rh; ++iii)
ins->noteSmplsMapping[iii] = ii + 1;
}
}
delete[] b;
_instruments.push_back(Common::SharedPtr<Instrument>(ins));
}
return !_instruments.empty();
}
Common::SharedPtr<MacSndResource> NewMacSoundSystem::getNoteRangeSndResource(uint16 id, byte note) {
assert(note < 128);
Common::SharedPtr<MacSndResource> res;
for (Common::Array<Common::SharedPtr<Instrument> >::const_iterator it = _instruments.begin(); res == nullptr && it != _instruments.end(); ++it) {
uint16 cid = (*it)->id;
if (cid == id)
res = (*it)->sndRes[(*it)->noteSmplsMapping[note]];
}
return res;
}
void NewMacSoundSystem::setInstrument(DeviceChannel *chan) {
assert(chan && chan->node);
if (chan->instr == nullptr || chan->node->rhythmPart != chan->rhtm || (!chan->node->rhythmPart && chan->prog != chan->node->prog) || chan->note != chan->node->note) {
chan->note = chan->node->note;
chan->prog = chan->node->rhythmPart ? 0 : chan->node->prog;
chan->rhtm = chan->node->rhythmPart;
chan->instr = chan->node->rhythmPart ? getSndResource(6000 + chan->note) : getNoteRangeSndResource(chan->prog, chan->note);
}
}
void NewMacSoundSystem::recalcFrequency(DeviceChannel *chan) {
assert(chan && chan->node);
if (chan->node->rhythmPart)
chan->frequency = 0x8000;
else
chan->recalcFrequency();
chan->frequency = MacLowLevelPCMDriver::calcRate(0x56220000, chan->rate , chan->frequency);
//if (chan->frequency == (uint32)-1)
// error("%s(): Frequency calculation failed", __FUNCTION__);
}
void NewMacSoundSystem::recalcVolume(DeviceChannel *chan) {
assert(chan && chan->node);
static const byte volumeTable[] = {
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02,
0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05,
0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07,
0x07, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x0a,
0x0a, 0x0a, 0x0b, 0x0b, 0x0c, 0x0c, 0x0c, 0x0d,
0x0d, 0x0e, 0x0e, 0x0f, 0x0f, 0x10, 0x10, 0x11,
0x11, 0x12, 0x12, 0x13, 0x14, 0x14, 0x15, 0x16,
0x16, 0x17, 0x18, 0x18, 0x19, 0x1a, 0x1b, 0x1b,
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
0x2d, 0x2e, 0x2f, 0x31, 0x32, 0x33, 0x35, 0x36,
0x38, 0x39, 0x3b, 0x3c, 0x3e, 0x40, 0x42, 0x43,
0x45, 0x47, 0x49, 0x4b, 0x4d, 0x4f, 0x51, 0x53,
0x56, 0x58, 0x5a, 0x5d, 0x5f, 0x62, 0x64, 0x67,
0x6a, 0x6c, 0x6f, 0x72, 0x75, 0x78, 0x7c, 0x7f
};
if (_stereo) {
chan->volumeL = (volumeTable[127 - (chan->node->panPos >> 2)] * chan->node->volume) >> 7;
chan->volumeR = (volumeTable[96 + (chan->node->panPos >> 2)] * chan->node->volume) >> 7;
} else {
chan->volumeL = chan->node->volume;
}
static const byte veloTable[] = {
0x41, 0x43, 0x45, 0x46, 0x47, 0x49, 0x4a, 0x4b,
0x4c, 0x4d, 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53,
0x53, 0x54, 0x55, 0x55, 0x56, 0x57, 0x57, 0x58,
0x58, 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5b, 0x5b,
0x5b, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5d,
0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5f, 0x5f,
0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60, 0x60,
0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61,
0x61, 0x61, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62,
0x63, 0x63, 0x63, 0x63, 0x64, 0x64, 0x64, 0x65,
0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68,
0x68, 0x69, 0x69, 0x6a, 0x6b, 0x6b, 0x6c, 0x6d,
0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74,
0x75, 0x76, 0x77, 0x79, 0x7a, 0x7b, 0x7d, 0x7f
};
if (chan->release)
return;
chan->totalLevelL = volumeTable[(veloTable[chan->node->velocity] * chan->volumeL) >> 7];
chan->totalLevelR = volumeTable[(veloTable[chan->node->velocity] * chan->volumeR) >> 7];
}
void NewMacSoundSystem::noteOffIntern(DeviceChannel *chan) {
assert(chan && chan->node);
if (!chan->node->rhythmPart)
chan->release = true;
chan->loopStart = nullptr;
chan->loopEnd = chan->smpBuffEnd;
}
DeviceChannel *NewMacSoundSystem::allocateChannel(const ChanControlNode *node) {
assert(node);
DeviceChannel *res = nullptr;
DeviceChannel *res2 = nullptr;
DeviceChannel *res3 = nullptr;
byte lovol = 0x7F;
byte lopri = node->prio;
for (byte i = 0; i < _numChannels; ++i) {
DeviceChannel *c = _channels[i];
if (c->mute) {
res = c;
break;
}
if (c->release) {
if (c->node && c->node->volume < lovol) {
res2 = c;
lovol = c->node->volume;
}
}
if (c->node && c->node->prio < lopri) {
res3 = c;
lopri = c->node->prio;
}
}
if (!res)
res = res2;
if (!res)
res = res3;
if (res) {
res->release = false;
res->mute = true;
res->node = node;
}
return res;
}
byte IMuseChannel_Macintosh::_allocCur = 0;
IMuseChannel_Macintosh::IMuseChannel_Macintosh(IMuseDriver_Macintosh *drv, int number) : MidiChannel(), _drv(drv), _number(number), _allocated(false), _polyphony(1), _usage(0),
_sustain(false), _bank(0), _panPos(0x40), _pitchBendEff(0), _prio(0x80), _detune(0), _transpose(0), _pitchBendSet(0), _pitchBendSensitivity(2), _volume(0x7F), _overuse(false),
_rpn(0), _pitchBendRange(0), _channels(drv ? drv->_channels : nullptr), _prog(0), _out(nullptr), _device(drv ? drv->_device : nullptr), _version(drv ? drv->_version : -1),
_rtmChannel((drv && drv->_version == 1) ? drv->_channels[drv->_numChannels - 1] : nullptr), _numChannels(drv ? (drv->_version != 1 ? drv->_numChannels : drv->_numChannels - 1) : 0) {
assert(_drv);
assert(_channels);
assert(_device);
_allocCur = 0;
}
bool IMuseChannel_Macintosh::allocate() {
if (_allocated)
return false;
_usage = 0;
_overuse = false;
return (_allocated = true);
}
void IMuseChannel_Macintosh::noteOff(byte note) {
for (ChanControlNode *node = (_version == 1 && _number == kRhythmPart) ? _rtmChannel : _out; node; ) {
ChanControlNode *n = node->next;
if (node->note == note) {
if (_sustain && node != _rtmChannel) {
node->sustain = true;
} else {
_device->noteOff(node);
disconnect(_out, node);
if (_version == 2)
_overuse = (--_usage > _polyphony);
}
}
node = n;
}
}
void IMuseChannel_Macintosh::noteOn(byte note, byte velocity) {
ChanControlNode *node = allocateNode(_prio);
if (node == nullptr)
return;
if (_version == 0 || _number != kRhythmPart) {
node->in = this;
node->prio = _prio;
node->prog = _prog;
node->pitchBend = _pitchBendEff;
node->volume = _volume;
node->rhythmPart = false;
} else {
if (_version == 2) {
node->in = this;
node->prio = _prio;
}
node->prog = 0;
node->volume = _volume * 6 / 7;
node->rhythmPart = true;
}
node->note = note;
node->sustain = false;
node->panPos = _panPos;
node->velocity = velocity;
if (_version == 2)
_overuse = (++_usage > _polyphony);
_device->noteOn(node);
}
void IMuseChannel_Macintosh::controlChange(byte control, byte value) {
switch (control) {
case 0:
// The original MI2/INDY4 code doesn't have that. It will just call a different
// programChange() method from the sysex handler. Only DOTT and SAMNMAX call a
// bank select, but the actual selection is not implemented, since it is not needed.
_bank = value;
break;
case 6:
dataEntry(value);
break;
case 7:
case 10: {
byte &param = (control == 7) ? _volume : _panPos;
param = value;
updateVolume();
} break;
case 17:
if (_version == 2)
_polyphony = value;
else
detune(value);
break;
case 18:
priority(value);
break;
case 64:
sustain(value);
break;
case 100:
_rpn = value;
break;
case 101:
_rpn = value | 0x80;
break;
case 123:
allNotesOff();
break;
default:
break;
}
}
void IMuseChannel_Macintosh::programChange(byte program) {
if (_version == 0)
_prog = (_bank ? 2000 : 1000) + program;
else
_prog = program;
}
void IMuseChannel_Macintosh::pitchBend(int16 bend) {
_pitchBendSet = bend;
if (_version == 2)
bend = (((bend * _pitchBendSensitivity) >> 5) + _detune + (_transpose << 8)) << 1; // SAMNMAX formula
else
bend = ((bend * _pitchBendSensitivity) >> 6) + _detune + (_transpose << 7); // DOTT, INDY4 and MI2 formula
if (_version > 0) {
if (_version == 1)
bend = CLIP<int16>(bend, -2048, 2047) << 2;
if (bend > 7936)
bend = 8192;
else if (bend < -7936)
bend = -8192;
bend = (bend * _pitchBendRange) >> 13;
}
_pitchBendEff = bend;
if (_pitchBendSet)
debug(6, "PITCH BEND: ims part %d, pb para %d, pb snstvty %d, pb range %d, _detune %d, transpose %d, pb eff %d", _number, _pitchBendSet, _pitchBendSensitivity, _pitchBendRange, _detune, _transpose, _pitchBendEff);
for (ChanControlNode *node = _out; node; node = node->next) {
node->pitchBend = _pitchBendEff;
_device->setPitchBend(node);
}
}
void IMuseChannel_Macintosh::sustain(bool value) {
_sustain = value;
if (value)
return;
for (ChanControlNode *node = _out; node; ) {
ChanControlNode *n = node->next;
if (node->sustain) {
_device->noteOff(node);
disconnect(_out, node);
if (_version == 2)
_overuse = (--_usage > _polyphony);
}
node = n;
}
}
void IMuseChannel_Macintosh::allNotesOff() {
for (ChanControlNode *node = (_version == 1 && _number == kRhythmPart) ? _rtmChannel : _out; node; ) {
_device->voiceOff(node);
ChanControlNode *n = node->next;
disconnect(_out, node);
if (_version == 2)
_overuse = (--_usage > _polyphony);
node = n;
}
}
void IMuseChannel_Macintosh::dataEntry(byte value) {
uint16 m = 0x7F;
uint16 v = value;
if (_rpn & 0x80) {
m <<= 7;
v <<= 7;
}
if (!(_rpn & 0x7F)) {
_pitchBendRange = (_pitchBendRange & ~m) + v;
pitchBend(_pitchBendSet);
}
_rpn = 0;
}
void IMuseChannel_Macintosh::updateVolume() {
for (ChanControlNode *node = _out; node; node = node->next) {
node->panPos = _panPos;
node->volume = (_version > 0 && _number == kRhythmPart) ? _volume * 6 / 7 : _volume;
_device->setVolume(node);
}
}
ChanControlNode *IMuseChannel_Macintosh::allocateNode(int prio) {
if (_version > 0) {
_allocCur = 0;
if (_version == 1 && _number == kRhythmPart)
return _rtmChannel;
}
if (_version < 1 && _prog == 0)
return nullptr;
IMuseChannel_Macintosh *best = this;
ChanControlNode *res = nullptr;
for (byte i = 0; i < _numChannels; ++i) {
_allocCur = (_allocCur + 1) % _numChannels;
ChanControlNode *node = _channels[_allocCur];
if (node->in == nullptr) {
res = node;
best = nullptr;
break;
}
if ((_version == 2 && ((best->_overuse == node->in->_overuse && best->_prio >= node->prio) || (!best->_overuse && node->in->_overuse))) ||
(_version < 2 && (!node->next && node->in && node->in->_prio <= prio))) {
res = node;
best = node->in;
prio = best->_prio;
}
}
if (res && best) {
if (_version == 2)
best->_overuse = (--best->_usage > best->_polyphony);
_device->voiceOff(res);
disconnect(best->_out, res);
}
assert(!(_out && _out == res));
connect(_out, res);
return res;
}
} // End of namespace IMSMacintosh
namespace Scumm {
using namespace IMSMacintosh;
IMuseDriver_Macintosh::IMuseDriver_Macintosh(ScummEngine *vm, Audio::Mixer *mixer, byte gameID) : MidiDriver(), _isOpen(false), _device(nullptr), _imsParts(nullptr), _channels(nullptr),
_numParts(32), _numChannels(8), _baseTempo(16666), _quality(1), _musicVolume(0xFFFFFFFF), _sfxVolume(0xFFFFFFFF), _version(-1) {
switch (gameID) {
case GID_TENTACLE:
case GID_SAMNMAX:
if (gameID == GID_SAMNMAX) {
_version = 2;
_numChannels = 12;
} else {
_version = 1;
_numChannels = 16;
}
_baseTempo = 46439;
_device = new NewMacSoundSystem(vm, mixer);
break;
case GID_INDY4:
// TODO: Detect the PowerPC version. Which version should it be?
if (vm->_isModernMacVersion) {
_version = 1;
_numChannels = 16;
_baseTempo = 46439;
_device = new NewMacSoundSystem(vm, mixer);
} else {
_version = 0;
_device = new DJMSoundSystem(mixer);
}
break;
case GID_MONKEY2:
_version = 0;
_device = new DJMSoundSystem(mixer);
break;
default:
error("%s(): Unsupported game ID %d", __FUNCTION__, gameID);
break;
}
}
IMuseDriver_Macintosh::~IMuseDriver_Macintosh() {
close();
delete _device;
}
int IMuseDriver_Macintosh::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
if (_version < 0 || _version > 2)
return MERR_DEVICE_NOT_AVAILABLE;
createChannels();
static const char *const fileNames[3][4] = {
{
"iMUSE Setups",
nullptr,
nullptr,
nullptr
},
{
"Instruments",
"Day of the Tentacle Demo",
"Day of the Tentacle",
"Fate of Atlantis PowerPC"
},
{
"Instruments",
"Sam & Max Demo",
"Sam & Max",
nullptr
}
};
if (!_device->init(fileNames[_version], ARRAYSIZE(fileNames[_version]), false, true) || !_device->start())
return MERR_DEVICE_NOT_AVAILABLE;
_isOpen = true;
for (int i = 0; i < _numParts; ++i) {
_imsParts[i]->controlChange(0x64, 0x00);
_imsParts[i]->controlChange(0x65, 0x00);
_imsParts[i]->controlChange(0x06, 0x10);
_imsParts[i]->controlChange(0x07, 0x7F);
_imsParts[i]->controlChange(0x0A, 0x40);
_imsParts[i]->controlChange(0x01, 0x00);
_imsParts[i]->controlChange(0x40, 0x00);
_imsParts[i]->controlChange(0x5B, 0x40);
_imsParts[i]->controlChange(0x5D, 0x00);
_imsParts[i]->controlChange(0x00, 0x00);
_imsParts[i]->controlChange(0x7B, 0x00);
_imsParts[i]->programChange(0);
_imsParts[i]->pitchBend(0);
}
return 0;
}
void IMuseDriver_Macintosh::close() {
if (!_isOpen)
return;
_isOpen = false;
_device->deinit();
releaseChannels();
}
uint32 IMuseDriver_Macintosh::property(int prop, uint32 param) {
uint32 res = 0;
switch (prop) {
case IMuse::PROP_QUALITY:
res = _quality;
if (param != (uint32)-1 && param != _quality) {
_quality = param;
if (_device)
_device->setQuality(param == MacSound::kQualityAuto || param == MacSound::kQualityHighest ? 2: 0);
}
break;
case IMuse::PROP_MUSICVOLUME:
res = _musicVolume;
if (param != (uint32)-1 && param != _musicVolume) {
_musicVolume = param;
if (_device)
_device->setMasterVolume(Audio::Mixer::kMusicSoundType, param);
}
break;
case IMuse::PROP_SFXVOLUME:
res = _sfxVolume;
if (param != (uint32)-1 && param != _sfxVolume) {
_sfxVolume = param;
if (_device)
_device->setMasterVolume(Audio::Mixer::kSFXSoundType, param);
}
break;
default:
break;
}
return res;
}
void IMuseDriver_Macintosh::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) {
if (_device)
_device->setTimerCallback(timerParam, timerProc);
}
MidiChannel *IMuseDriver_Macintosh::allocateChannel() {
if (!_isOpen)
return nullptr;
for (int i = 0; i < _numParts; ++i) {
IMuseChannel_Macintosh *ch = _imsParts[i];
if (ch && !(_version > 0 && i == kRhythmPart) && ch->allocate())
return ch;
}
return nullptr;
}
MidiChannel *IMuseDriver_Macintosh::getPercussionChannel() {
return (_isOpen && _version > 0) ? _imsParts[kRhythmPart] : nullptr;
}
void IMuseDriver_Macintosh::createChannels() {
releaseChannels();
_channels = new ChanControlNode*[_numChannels];
assert(_channels);
for (int i = 0; i < _numChannels; ++i) {
_channels[i] = new ChanControlNode(i < kRhythmPart ? i : (i == _numChannels - 1 ? kRhythmPart : i + 1));
assert(_channels[i]);
}
_imsParts = new IMuseChannel_Macintosh*[_numParts];
assert(_imsParts);
for (int i = 0; i < _numParts; ++i)
_imsParts[i] = new IMuseChannel_Macintosh(this, i);
}
void IMuseDriver_Macintosh::releaseChannels() {
if (_imsParts) {
for (int i = 0; i < _numParts; ++i)
delete _imsParts[i];
delete[] _imsParts;
_imsParts = nullptr;
}
if (_channels) {
for (int i = 0; i < _numChannels; ++i)
delete _channels[i];
delete[] _channels;
_channels = nullptr;
}
}
} // End of namespace Scumm