diff options
Diffstat (limited to 'engines/scumm/imuse/mac_m68k.cpp')
-rw-r--r-- | engines/scumm/imuse/mac_m68k.cpp | 510 |
1 files changed, 510 insertions, 0 deletions
diff --git a/engines/scumm/imuse/mac_m68k.cpp b/engines/scumm/imuse/mac_m68k.cpp new file mode 100644 index 0000000000..4d7a6a64c0 --- /dev/null +++ b/engines/scumm/imuse/mac_m68k.cpp @@ -0,0 +1,510 @@ +/* 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "scumm/imuse/mac_m68k.h" + +#include "common/util.h" +#include "common/macresman.h" +#include "common/stream.h" + +namespace Scumm { + +MacM68kDriver::MacM68kDriver(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer) { +} + +MacM68kDriver::~MacM68kDriver() { +} + +int MacM68kDriver::open() { + if (_isOpen) { + return MERR_ALREADY_OPEN; + } + + const int error = MidiDriver_Emulated::open(); + if (error) { + return error; + } + + for (uint i = 0; i < ARRAYSIZE(_channels); ++i) { + _channels[i].init(this, i); + } + + memset(_voiceChannels, 0, sizeof(_voiceChannels)); + _lastUsedVoiceChannel = 0; + + loadAllInstruments(); + + _pitchTable[116] = 1664510; + _pitchTable[117] = 1763487; + _pitchTable[118] = 1868350; + _pitchTable[119] = 1979447; + _pitchTable[120] = 2097152; + _pitchTable[121] = 2221855; + _pitchTable[122] = 2353973; + _pitchTable[123] = 2493948; + _pitchTable[124] = 2642246; + _pitchTable[125] = 2799362; + _pitchTable[126] = 2965820; + _pitchTable[127] = 3142177; + for (int i = 115; i >= 0; --i) { + _pitchTable[i] = _pitchTable[i + 12] / 2; + } + + _volumeTable = new byte[8192]; + for (int i = 0; i < 32; ++i) { + for (int j = 0; j < 256; ++j) { + _volumeTable[i * 256 + j] = ((-128 + j) * _volumeBaseTable[i]) / 127 - 128; + } + } + + _mixBuffer = 0; + _mixBufferLength = 0; + + // We set the output sound type to music here to allow sound volume + // adjustment. The drawback here is that we can not control the music and + // sfx separately here. But the AdLib output has the same issue so it + // should not be that bad. + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + + return 0; +} + +void MacM68kDriver::close() { + if (!_isOpen) { + return; + } + + _mixer->stopHandle(_mixerSoundHandle); + _isOpen = false; + for (InstrumentMap::iterator i = _instruments.begin(); i != _instruments.end(); ++i) { + delete[] i->_value.data; + } + _instruments.clear(); + delete[] _volumeTable; + _volumeTable = 0; + delete[] _mixBuffer; + _mixBuffer = 0; + _mixBufferLength = 0; +} + +void MacM68kDriver::send(uint32 d) { + assert(false); +} + +void MacM68kDriver::sysEx_customInstrument(byte channel, uint32 type, const byte *instr) { + assert(false); +} + +MidiChannel *MacM68kDriver::allocateChannel() { + for (uint i = 0; i < ARRAYSIZE(_channels); ++i) { + if (_channels[i].allocate()) { + return &_channels[i]; + } + } + + return 0; +} + +MacM68kDriver::Instrument MacM68kDriver::getInstrument(int idx) const { + InstrumentMap::const_iterator i = _instruments.find(idx); + if (i != _instruments.end()) { + return i->_value; + } else { + return _defaultInstrument; + } +} + +void MacM68kDriver::generateSamples(int16 *buf, int len) { + int silentChannels = 0; + + if (_mixBufferLength < len) { + delete[] _mixBuffer; + + _mixBufferLength = len; + _mixBuffer = new int[_mixBufferLength]; + assert(_mixBuffer); + } + memset(_mixBuffer, 0, sizeof(int) * _mixBufferLength); + + for (int i = 0; i < kChannelCount; ++i) { + OutputChannel &out = _voiceChannels[i].out; + if (out.isFinished) { + ++silentChannels; + continue; + } + + byte *volumeTable = &_volumeTable[(out.volume / 4) * 256]; + int *buffer = _mixBuffer; + + int samplesLeft = len; + while (samplesLeft) { + out.subPos += out.pitchModifier; + while (out.subPos >= 0x10000) { + out.subPos -= 0x10000; + out.instrument++; + } + + if (out.instrument >= out.end) { + if (!out.start) { + break; + } + + out.instrument = out.start; + out.subPos = 0; + } + + *buffer++ += volumeTable[*out.instrument]; + --samplesLeft; + } + + if (samplesLeft) { + out.isFinished = true; + while (samplesLeft--) { + *buffer++ += 0x80; + } + } + } + + const int *buffer = _mixBuffer; + const int silenceAdd = silentChannels << 7; + while (len--) { + *buf++ = (((*buffer++ + silenceAdd) >> 3) << 8) ^ 0x8000; + } +} + +void MacM68kDriver::loadAllInstruments() { + Common::MacResManager resource; + if (resource.open("iMUSE Setups")) { + for (int i = 0x3E7; i < 0x468; ++i) { + Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i); + if (stream) { + addInstrument(i, stream); + delete stream; + } + } + + for (int i = 0x7D0; i < 0x8D0; ++i) { + Common::SeekableReadStream *stream = resource.getResource(MKTAG('s', 'n', 'd', ' '), i); + if (stream) { + addInstrument(i, stream); + delete stream; + } + } + + InstrumentMap::iterator inst = _instruments.find(kDefaultInstrument); + if (inst != _instruments.end()) { + _defaultInstrument = inst->_value; + } else { + error("MacM68kDriver::loadAllInstruments: Could not load default instrument"); + } + } else { + error("MacM68kDriver::loadAllInstruments: Could not load \"iMUSE Setups\""); + } +} + +void MacM68kDriver::addInstrument(int idx, Common::SeekableReadStream *data) { + // We parse the "SND" files manually here, since we need special data + // from their header and need to work on them raw while mixing. + data->skip(2); + int count = data->readUint16BE(); + data->skip(2 * (3 * count)); + count = data->readUint16BE(); + data->skip(2 * (4 * count)); + + Instrument inst; + // Skip (optional) pointer to data + data->skip(4); + inst.length = data->readUint32BE(); + inst.sampleRate = data->readUint32BE(); + inst.loopStart = data->readUint32BE(); + inst.loopEnd = data->readUint32BE(); + // Skip encoding + data->skip(1); + inst.baseFrequency = data->readByte(); + + inst.data = new byte[inst.length]; + assert(inst.data); + data->read(inst.data, inst.length); + _instruments[idx] = inst; +} + +void MacM68kDriver::setPitch(OutputChannel *out, int frequency) { + out->frequency = frequency; + out->isFinished = false; + + const int pitchIdx = (frequency >> 7) + 60 - out->baseFrequency; + assert(pitchIdx >= 0); + + const int low7Bits = frequency & 0x7F; + if (low7Bits) { + out->pitchModifier = _pitchTable[pitchIdx] + (((_pitchTable[pitchIdx + 1] - _pitchTable[pitchIdx]) * low7Bits) >> 7); + } else { + out->pitchModifier = _pitchTable[pitchIdx]; + } +} + +void MacM68kDriver::VoiceChannel::off() { + if (out.start) { + out.isFinished = true; + } + + part->removeVoice(this); + part = 0; +} + +void MacM68kDriver::MidiChannel_MacM68k::release() { + _allocated = false; + while (_voice) { + _voice->off(); + } +} + +void MacM68kDriver::MidiChannel_MacM68k::send(uint32 b) { + uint8 type = b & 0xF0; + uint8 p1 = (b >> 8) & 0xFF; + uint8 p2 = (b >> 16) & 0xFF; + + switch (type) { + case 0x80: + noteOff(p1); + break; + + case 0x90: + if (p2) { + noteOn(p1, p2); + } else { + noteOff(p1); + } + break; + + case 0xB0: + controlChange(p1, p2); + break; + + case 0xE0: + pitchBend((p1 | (p2 << 7)) - 0x2000); + break; + + default: + break; + } +} + +void MacM68kDriver::MidiChannel_MacM68k::noteOff(byte note) { + for (VoiceChannel *i = _voice; i; i = i->next) { + if (i->note == note) { + if (_sustain) { + i->sustainNoteOff = true; + } else { + i->off(); + } + } + } +} + +void MacM68kDriver::MidiChannel_MacM68k::noteOn(byte note, byte velocity) { + // Do not start a not unless there is an instrument set up + if (!_instrument.data) { + return; + } + + // Allocate a voice channel + VoiceChannel *voice = _owner->allocateVoice(_priority); + if (!voice) { + return; + } + addVoice(voice); + + voice->note = note; + // This completly ignores the note's volume, but is in accordance + // to the original. + voice->out.volume = _volume; + + // Set up the instrument data + voice->out.baseFrequency = _instrument.baseFrequency; + voice->out.soundStart = _instrument.data; + voice->out.soundEnd = _instrument.data + _instrument.length; + if (_instrument.loopEnd && _instrument.loopEnd - 12 > _instrument.loopStart) { + voice->out.loopStart = _instrument.data + _instrument.loopStart; + voice->out.loopEnd = _instrument.data + _instrument.loopEnd; + } else { + voice->out.loopStart = 0; + voice->out.loopEnd = voice->out.soundEnd; + } + + voice->out.start = voice->out.loopStart; + voice->out.end = voice->out.loopEnd; + + // Set up the pitch + _owner->setPitch(&voice->out, (note << 7) + _pitchBend); + + // Set up the sample position + voice->out.instrument = voice->out.soundStart; + voice->out.subPos = 0; +} + +void MacM68kDriver::MidiChannel_MacM68k::programChange(byte program) { + _instrument = _owner->getInstrument(program + kProgramChangeBase); +} + +void MacM68kDriver::MidiChannel_MacM68k::pitchBend(int16 bend) { + _pitchBend = (bend * _pitchBendFactor) >> 6; + for (VoiceChannel *i = _voice; i; i = i->next) { + _owner->setPitch(&i->out, (i->note << 7) + _pitchBend); + } +} + +void MacM68kDriver::MidiChannel_MacM68k::controlChange(byte control, byte value) { + switch (control) { + // volume change + case 7: + _volume = value; + for (VoiceChannel *i = _voice; i; i = i->next) { + i->out.volume = value; + i->out.isFinished = false; + } + break; + + // sustain + case 64: + _sustain = value; + if (!_sustain) { + for (VoiceChannel *i = _voice; i; i = i->next) { + if (i->sustainNoteOff) { + i->off(); + } + } + } + break; + + // all notes off + case 123: + for (VoiceChannel *i = _voice; i; i = i->next) { + i->off(); + } + break; + + default: + break; + } +} + +void MacM68kDriver::MidiChannel_MacM68k::pitchBendFactor(byte value) { + _pitchBendFactor = value; +} + +void MacM68kDriver::MidiChannel_MacM68k::priority(byte value) { + _priority = value; +} + +void MacM68kDriver::MidiChannel_MacM68k::sysEx_customInstrument(uint32 type, const byte *instr) { + assert(instr); + if (type == 'MAC ') { + _instrument = _owner->getInstrument(*instr + kSysExBase); + } +} + +void MacM68kDriver::MidiChannel_MacM68k::init(MacM68kDriver *owner, byte channel) { + _owner = owner; + _number = channel; + _allocated = false; +} + +bool MacM68kDriver::MidiChannel_MacM68k::allocate() { + if (_allocated) { + return false; + } + + _allocated = true; + _voice = 0; + _priority = 0; + memset(&_instrument, 0, sizeof(_instrument)); + _pitchBend = 0; + _pitchBendFactor = 0; + _volume = 0; + return true; +} + +void MacM68kDriver::MidiChannel_MacM68k::addVoice(VoiceChannel *voice) { + voice->next = _voice; + voice->prev = 0; + voice->part = this; + if (_voice) { + _voice->prev = voice; + } + _voice = voice; +} + +void MacM68kDriver::MidiChannel_MacM68k::removeVoice(VoiceChannel *voice) { + VoiceChannel *i = _voice; + while (i && i != voice) { + i = i->next; + } + + if (i) { + if (i->next) { + i->next->prev = i->prev; + } + + if (i->prev) { + i->prev->next = i->next; + } else { + _voice = i->next; + } + } +} + +MacM68kDriver::VoiceChannel *MacM68kDriver::allocateVoice(int priority) { + VoiceChannel *channel = 0; + for (int i = 0; i < kChannelCount; ++i) { + if (++_lastUsedVoiceChannel == kChannelCount) { + _lastUsedVoiceChannel = 0; + } + + VoiceChannel *cur = &_voiceChannels[_lastUsedVoiceChannel]; + if (!cur->part) { + memset(cur, 0, sizeof(*cur)); + return cur; + } else if (!cur->next) { + if (cur->part->_priority <= priority) { + priority = cur->part->_priority; + channel = cur; + } + } + } + + if (channel) { + channel->off(); + memset(channel, 0, sizeof(*channel)); + } + + return channel; +} + +const int MacM68kDriver::_volumeBaseTable[32] = { + 0, 0, 1, 1, 2, 3, 5, 6, + 8, 11, 13, 16, 19, 22, 26, 30, + 34, 38, 43, 48, 53, 58, 64, 70, + 76, 83, 89, 96, 104, 111, 119, 127 +}; + +} // End of namespace Scumm |