1492 lines
42 KiB
C++
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 ¶m = (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
|