diff options
Diffstat (limited to 'audio')
51 files changed, 4777 insertions, 619 deletions
diff --git a/audio/softsynth/adlib.cpp b/audio/adlib.cpp index 98519343b4..f609164495 100644 --- a/audio/softsynth/adlib.cpp +++ b/audio/adlib.cpp @@ -927,18 +927,20 @@ static void createLookupTable() { // //////////////////////////////////////// -class MidiDriver_ADLIB : public MidiDriver_Emulated { +class MidiDriver_ADLIB : public MidiDriver { friend class AdLibPart; friend class AdLibPercussionChannel; public: - MidiDriver_ADLIB(Audio::Mixer *mixer); + MidiDriver_ADLIB(); int open(); void close(); void send(uint32 b); void send(byte channel, uint32 b); // Supports higher than channel 15 uint32 property(int prop, uint32 param); + bool isOpen() const { return _isOpen; } + uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; } void setPitchBendRange(byte channel, uint range); void sysEx_customInstrument(byte channel, uint32 type, const byte *instr); @@ -946,10 +948,7 @@ public: MidiChannel *allocateChannel(); MidiChannel *getPercussionChannel() { return &_percussion; } // Percussion partially supported - - // AudioStream API - bool isStereo() const { return _opl->isStereo(); } - int getRate() const { return _mixer->getOutputRate(); } + virtual void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc); private: bool _scummSmallHeader; // FIXME: This flag controls a special mode for SCUMM V3 games @@ -963,6 +962,9 @@ private: byte *_regCacheSecondary; #endif + Common::TimerManager::TimerProc _adlibTimerProc; + void *_adlibTimerParam; + int _timerCounter; uint16 _channelTable2[9]; @@ -974,7 +976,8 @@ private: AdLibPart _parts[32]; AdLibPercussionChannel _percussion; - void generateSamples(int16 *buf, int len); + bool _isOpen; + void onTimer(); void partKeyOn(AdLibPart *part, const AdLibInstrument *instr, byte note, byte velocity, const AdLibInstrument *second, byte pan); void partKeyOff(AdLibPart *part, byte note); @@ -1376,8 +1379,7 @@ void AdLibPercussionChannel::sysEx_customInstrument(uint32 type, const byte *ins // MidiDriver method implementations -MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer) - : MidiDriver_Emulated(mixer) { +MidiDriver_ADLIB::MidiDriver_ADLIB() { uint i; _scummSmallHeader = false; @@ -1403,13 +1405,16 @@ MidiDriver_ADLIB::MidiDriver_ADLIB(Audio::Mixer *mixer) _timerIncrease = 0xD69; _timerThreshold = 0x411B; _opl = 0; + _adlibTimerProc = 0; + _adlibTimerParam = 0; + _isOpen = false; } int MidiDriver_ADLIB::open() { if (_isOpen) return MERR_ALREADY_OPEN; - MidiDriver_Emulated::open(); + _isOpen = true; int i; AdLibVoice *voice; @@ -1434,7 +1439,7 @@ int MidiDriver_ADLIB::open() { _opl3Mode = false; } #endif - _opl->init(getRate()); + _opl->init(); _regCache = (byte *)calloc(256, 1); @@ -1452,8 +1457,7 @@ int MidiDriver_ADLIB::open() { } #endif - _mixer->playStream(Audio::Mixer::kPlainSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); - + _opl->start(new Common::Functor0Mem<void, MidiDriver_ADLIB>(this, &MidiDriver_ADLIB::onTimer)); return 0; } @@ -1462,7 +1466,8 @@ void MidiDriver_ADLIB::close() { return; _isOpen = false; - _mixer->stopHandle(_mixerSoundHandle); + // Stop the OPL timer + _opl->stop(); uint i; for (i = 0; i < ARRAYSIZE(_voices); ++i) { @@ -1616,14 +1621,10 @@ void MidiDriver_ADLIB::adlibWriteSecondary(byte reg, byte value) { } #endif -void MidiDriver_ADLIB::generateSamples(int16 *data, int len) { - if (_opl->isStereo()) { - len *= 2; - } - _opl->readBuffer(data, len); -} - void MidiDriver_ADLIB::onTimer() { + if (_adlibTimerProc) + (*_adlibTimerProc)(_adlibTimerParam); + _timerCounter += _timerIncrease; while (_timerCounter >= _timerThreshold) { _timerCounter -= _timerThreshold; @@ -1655,6 +1656,11 @@ void MidiDriver_ADLIB::onTimer() { } } +void MidiDriver_ADLIB::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { + _adlibTimerProc = timerProc; + _adlibTimerParam = timerParam; +} + void MidiDriver_ADLIB::mcOff(AdLibVoice *voice) { AdLibVoice *tmp; @@ -2300,7 +2306,7 @@ MusicDevices AdLibEmuMusicPlugin::getDevices() const { } Common::Error AdLibEmuMusicPlugin::createInstance(MidiDriver **mididriver, MidiDriver::DeviceHandle) const { - *mididriver = new MidiDriver_ADLIB(g_system->getMixer()); + *mididriver = new MidiDriver_ADLIB(); return Common::kNoError; } diff --git a/audio/alsa_opl.cpp b/audio/alsa_opl.cpp new file mode 100644 index 0000000000..6b9e48e987 --- /dev/null +++ b/audio/alsa_opl.cpp @@ -0,0 +1,349 @@ +/* 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. + * + */ + +/* OPL implementation for hardware OPL using ALSA Direct FM API. + * + * Caveats and limitations: + * - Pretends to be a softsynth (emitting silence). + * - Dual OPL2 mode requires OPL3 hardware. + * - Every register write leads to a series of register writes on the hardware, + * due to the lack of direct register access in the ALSA Direct FM API. + * - No timers + */ + +#define FORBIDDEN_SYMBOL_ALLOW_ALL +#include "common/scummsys.h" + +#include "common/debug.h" +#include "common/str.h" +#include "audio/fmopl.h" + +#include <sys/ioctl.h> +#include <alsa/asoundlib.h> +#include <sound/asound_fm.h> + +namespace OPL { +namespace ALSA { + +class OPL : public ::OPL::RealOPL { +private: + enum { + kOpl2Voices = 9, + kVoices = 18, + kOpl2Operators = 18, + kOperators = 36 + }; + + Config::OplType _type; + int _iface; + snd_hwdep_t *_opl; + snd_dm_fm_voice _oper[kOperators]; + snd_dm_fm_note _voice[kVoices]; + snd_dm_fm_params _params; + int index[2]; + static const int voiceToOper0[kVoices]; + static const int regOffsetToOper[0x20]; + + void writeOplReg(int c, int r, int v); + void clear(); + +public: + OPL(Config::OplType type); + ~OPL(); + + bool init(); + void reset(); + + void write(int a, int v); + byte read(int a); + + void writeReg(int r, int v); +}; + +const int OPL::voiceToOper0[OPL::kVoices] = + { 0, 1, 2, 6, 7, 8, 12, 13, 14, 18, 19, 20, 24, 25, 26, 30, 31, 32 }; + +const int OPL::regOffsetToOper[0x20] = + { 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9, 10, 11, -1, -1, + 12, 13, 14, 15, 16, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1 }; + +OPL::OPL(Config::OplType type) : _type(type), _opl(nullptr), _iface(0) { +} + +OPL::~OPL() { + stop(); + + if (_opl) { + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_RESET, nullptr); + snd_hwdep_close(_opl); + } +} + +void OPL::clear() { + index[0] = index[1] = 0; + + memset(_oper, 0, sizeof(_oper)); + memset(_voice, 0, sizeof(_voice)); + memset(&_params, 0, sizeof(_params)); + + for (int i = 0; i < kOperators; ++i) { + _oper[i].op = (i / 3) % 2; + _oper[i].voice = (i / 6) * 3 + (i % 3); + } + + for (int i = 0; i < kVoices; ++i) + _voice[i].voice = i; + + // For OPL3 hardware we need to set up the panning in OPL2 modes + if (_iface == SND_HWDEP_IFACE_OPL3) { + if (_type == Config::kDualOpl2) { + for (int i = 0; i < kOpl2Operators; ++i) + _oper[i].left = 1; // FIXME below + for (int i = kOpl2Operators; i < kOperators; ++i) + _oper[i].right = 1; + } else if (_type == Config::kOpl2) { + for (int i = 0; i < kOpl2Operators; ++i) { + _oper[i].left = 1; + _oper[i].right = 1; + } + } + } +} + +bool OPL::init() { + clear(); + + int card = -1; + snd_ctl_t *ctl; + snd_hwdep_info_t *info; + snd_hwdep_info_alloca(&info); + + int iface = SND_HWDEP_IFACE_OPL3; + if (_type == Config::kOpl2) + iface = SND_HWDEP_IFACE_OPL2; + + // Look for OPL hwdep interface + while (!snd_card_next(&card) && card >= 0) { + int dev = -1; + Common::String name = Common::String::format("hw:%d", card); + + if (snd_ctl_open(&ctl, name.c_str(), 0) < 0) + continue; + + while (!snd_ctl_hwdep_next_device(ctl, &dev) && dev >= 0) { + name = Common::String::format("hw:%d,%d", card, dev); + + if (snd_hwdep_open(&_opl, name.c_str(), SND_HWDEP_OPEN_WRITE) < 0) + continue; + + if (!snd_hwdep_info(_opl, info)) { + int found = snd_hwdep_info_get_iface(info); + // OPL3 can be used for (Dual) OPL2 mode + if (found == iface || found == SND_HWDEP_IFACE_OPL3) { + snd_ctl_close(ctl); + _iface = found; + reset(); + return true; + } + } + + // Wrong interface, try next device + snd_hwdep_close(_opl); + _opl = nullptr; + } + + snd_ctl_close(ctl); + } + + return false; +} + +void OPL::reset() { + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_RESET, nullptr); + if (_iface == SND_HWDEP_IFACE_OPL3) + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_MODE, (void *)SNDRV_DM_FM_MODE_OPL3); + + clear(); + + // Sync up with the hardware + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params); + for (uint i = 0; i < (_iface == SND_HWDEP_IFACE_OPL3 ? kVoices : kOpl2Voices); ++i) + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[i]); + for (uint i = 0; i < (_iface == SND_HWDEP_IFACE_OPL3 ? kOperators : kOpl2Operators); ++i) + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[i]); +} + +void OPL::write(int port, int val) { + val &= 0xff; + int chip = (port & 2) >> 1; + + if (port & 1) { + switch(_type) { + case Config::kOpl2: + writeOplReg(0, index[0], val); + break; + case Config::kDualOpl2: + if (port & 8) { + writeOplReg(0, index[0], val); + writeOplReg(1, index[1], val); + } else + writeOplReg(chip, index[chip], val); + break; + case Config::kOpl3: + writeOplReg(chip, index[chip], val); + } + } else { + switch(_type) { + case Config::kOpl2: + index[0] = val; + break; + case Config::kDualOpl2: + if (port & 8) { + index[0] = val; + index[1] = val; + } else + index[chip] = val; + break; + case Config::kOpl3: + index[chip] = val; + } + } +} + +byte OPL::read(int port) { + return 0; +} + +void OPL::writeReg(int r, int v) { + switch (_type) { + case Config::kOpl2: + writeOplReg(0, r, v); + break; + case Config::kDualOpl2: + writeOplReg(0, r, v); + writeOplReg(1, r, v); + break; + case Config::kOpl3: + writeOplReg(r >= 0x100, r & 0xff, v); + } +} + +void OPL::writeOplReg(int c, int r, int v) { + if (r == 0x04 && c == 1 && _type == Config::kOpl3) { + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_CONNECTION, reinterpret_cast<void *>(v & 0x3f)); + } else if (r == 0x08 && c == 0) { + _params.kbd_split = (v >> 6) & 0x1; + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params); + } else if (r == 0xbd && c == 0) { + _params.hihat = v & 0x1; + _params.cymbal = (v >> 1) & 0x1; + _params.tomtom = (v >> 2) & 0x1; + _params.snare = (v >> 3) & 0x1; + _params.bass = (v >> 4) & 0x1; + _params.rhythm = (v >> 5) & 0x1; + _params.vib_depth = (v >> 6) & 0x1; + _params.am_depth = (v >> 7) & 0x1; + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_PARAMS, (void *)&_params); + } else if (r < 0xa0 || r >= 0xe0) { + // Operator + int idx = regOffsetToOper[r & 0x1f]; + + if (idx == -1) + return; + + if (c == 1) + idx += kOpl2Operators; + + switch (r & 0xf0) { + case 0x20: + case 0x30: + _oper[idx].harmonic = v & 0xf; + _oper[idx].kbd_scale = (v >> 4) & 0x1; + _oper[idx].do_sustain = (v >> 5) & 0x1; + _oper[idx].vibrato = (v >> 6) & 0x1; + _oper[idx].am = (v >> 7) & 0x1; + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]); + break; + case 0x40: + case 0x50: + _oper[idx].volume = ~v & 0x3f; + _oper[idx].scale_level = (v >> 6) & 0x3; + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]); + break; + case 0x60: + case 0x70: + _oper[idx].decay = v & 0xf; + _oper[idx].attack = (v >> 4) & 0xf; + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]); + break; + case 0x80: + case 0x90: + _oper[idx].release = v & 0xf; + _oper[idx].sustain = (v >> 4) & 0xf; + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]); + break; + case 0xe0: + case 0xf0: + _oper[idx].waveform = v & (_type == Config::kOpl3 ? 0x7 : 0x3); + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[idx]); + } + } else { + // Voice + int idx = r & 0xf; + + if (idx >= kOpl2Voices) + return; + + if (c == 1) + idx += kOpl2Voices; + + int opIdx = voiceToOper0[idx]; + + switch (r & 0xf0) { + case 0xa0: + _voice[idx].fnum = (_voice[idx].fnum & 0x300) | (v & 0xff); + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[idx]); + break; + case 0xb0: + _voice[idx].fnum = ((v << 8) & 0x300) | (_voice[idx].fnum & 0xff); + _voice[idx].octave = (v >> 2) & 0x7; + _voice[idx].key_on = (v >> 5) & 0x1; + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_PLAY_NOTE, (void *)&_voice[idx]); + break; + case 0xc0: + _oper[opIdx].connection = _oper[opIdx + 3].connection = v & 0x1; + _oper[opIdx].feedback = _oper[opIdx + 3].feedback = (v >> 1) & 0x7; + if (_type == Config::kOpl3) { + _oper[opIdx].left = _oper[opIdx + 3].left = (v >> 4) & 0x1; + _oper[opIdx].right = _oper[opIdx + 3].right = (v >> 5) & 0x1; + } + snd_hwdep_ioctl(_opl, SNDRV_DM_FM_IOCTL_SET_VOICE, (void *)&_oper[opIdx]); + } + } +} + +OPL *create(Config::OplType type) { + return new OPL(type); +} + +} // End of namespace ALSA +} // End of namespace OPL diff --git a/audio/decoders/3do.cpp b/audio/decoders/3do.cpp new file mode 100644 index 0000000000..6d558d4c8c --- /dev/null +++ b/audio/decoders/3do.cpp @@ -0,0 +1,343 @@ +/* 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 "common/textconsole.h" +#include "common/stream.h" +#include "common/util.h" + +#include "audio/decoders/3do.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/adpcm_intern.h" + +namespace Audio { + +// Reuses ADPCM table +#define audio_3DO_ADP4_stepSizeTable Ima_ADPCMStream::_imaTable +#define audio_3DO_ADP4_stepSizeIndex ADPCMStream::_stepAdjustTable + +RewindableAudioStream *make3DO_ADP4AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace) { + if (stereo) { + warning("make3DO_ADP4Stream(): stereo currently not supported"); + return 0; + } + + if (audioLengthMSecsPtr) { + // Caller requires the milliseconds of audio + uint32 audioLengthMSecs = stream->size() * 2 * 1000 / sampleRate; // 1 byte == 2 16-bit sample + if (stereo) { + audioLengthMSecs /= 2; + } + *audioLengthMSecsPtr = audioLengthMSecs; + } + + return new Audio3DO_ADP4_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace); +} + +Audio3DO_ADP4_Stream::Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace) + : _sampleRate(sampleRate), _stereo(stereo), + _stream(stream, disposeAfterUse) { + + _callerDecoderData = persistentSpace; + memset(&_initialDecoderData, 0, sizeof(_initialDecoderData)); + _initialRead = true; + + reset(); +} + +void Audio3DO_ADP4_Stream::reset() { + memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData)); + _streamBytesLeft = _stream->size(); + _stream->seek(0); +} + +bool Audio3DO_ADP4_Stream::rewind() { + reset(); + return true; +} + +int16 Audio3DO_ADP4_Stream::decodeSample(byte compressedNibble) { + int16 currentStep = audio_3DO_ADP4_stepSizeTable[_curDecoderData.stepIndex]; + int32 decodedSample = _curDecoderData.lastSample; + int16 delta = currentStep >> 3; + + if (compressedNibble & 1) + delta += currentStep >> 2; + + if (compressedNibble & 2) + delta += currentStep >> 1; + + if (compressedNibble & 4) + delta += currentStep; + + if (compressedNibble & 8) { + decodedSample -= delta; + } else { + decodedSample += delta; + } + + _curDecoderData.lastSample = CLIP<int32>(decodedSample, -32768, 32767); + + _curDecoderData.stepIndex += audio_3DO_ADP4_stepSizeIndex[compressedNibble & 0x07]; + _curDecoderData.stepIndex = CLIP<int16>(_curDecoderData.stepIndex, 0, ARRAYSIZE(audio_3DO_ADP4_stepSizeTable) - 1); + + return _curDecoderData.lastSample; +} + +// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written +int Audio3DO_ADP4_Stream::readBuffer(int16 *buffer, const int numSamples) { + int8 byteCache[AUDIO_3DO_CACHE_SIZE]; + int8 *byteCachePtr = NULL; + int byteCacheSize = 0; + int requestedBytesLeft = 0; + int decodedSamplesCount = 0; + + int8 compressedByte = 0; + + if (endOfData()) + return 0; // no more bytes left + + if (_callerDecoderData) { + // copy caller decoder data over + memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData)); + if (_initialRead) { + _initialRead = false; + memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData)); + } + } + + requestedBytesLeft = numSamples >> 1; // 1 byte for 2 16-bit sample + if (requestedBytesLeft > _streamBytesLeft) + requestedBytesLeft = _streamBytesLeft; // not enough bytes left + + // in case caller requests an uneven amount of samples, we will return an even amount + + // buffering, so that direct decoding of files and such runs way faster + while (requestedBytesLeft) { + if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) { + byteCacheSize = AUDIO_3DO_CACHE_SIZE; + } else { + byteCacheSize = requestedBytesLeft; + } + + requestedBytesLeft -= byteCacheSize; + _streamBytesLeft -= byteCacheSize; + + // Fill our byte cache + _stream->read(byteCache, byteCacheSize); + + byteCachePtr = byteCache; + + // Mono + while (byteCacheSize) { + compressedByte = *byteCachePtr++; + byteCacheSize--; + + buffer[decodedSamplesCount] = decodeSample(compressedByte >> 4); + decodedSamplesCount++; + buffer[decodedSamplesCount] = decodeSample(compressedByte & 0x0f); + decodedSamplesCount++; + } + } + + if (_callerDecoderData) { + // copy caller decoder data back + memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData)); + } + + return decodedSamplesCount; +} + +// ============================================================================ +static int16 audio_3DO_SDX2_SquareTable[256] = { + -32768,-32258,-31752,-31250,-30752,-30258,-29768,-29282,-28800,-28322, + -27848,-27378,-26912,-26450,-25992,-25538,-25088,-24642,-24200,-23762, + -23328,-22898,-22472,-22050,-21632,-21218,-20808,-20402,-20000,-19602, + -19208,-18818,-18432,-18050,-17672,-17298,-16928,-16562,-16200,-15842, + -15488,-15138,-14792,-14450,-14112,-13778,-13448,-13122,-12800,-12482, + -12168,-11858,-11552,-11250,-10952,-10658,-10368,-10082, -9800, -9522, + -9248, -8978, -8712, -8450, -8192, -7938, -7688, -7442, -7200, -6962, + -6728, -6498, -6272, -6050, -5832, -5618, -5408, -5202, -5000, -4802, + -4608, -4418, -4232, -4050, -3872, -3698, -3528, -3362, -3200, -3042, + -2888, -2738, -2592, -2450, -2312, -2178, -2048, -1922, -1800, -1682, + -1568, -1458, -1352, -1250, -1152, -1058, -968, -882, -800, -722, + -648, -578, -512, -450, -392, -338, -288, -242, -200, -162, + -128, -98, -72, -50, -32, -18, -8, -2, 0, 2, + 8, 18, 32, 50, 72, 98, 128, 162, 200, 242, + 288, 338, 392, 450, 512, 578, 648, 722, 800, 882, + 968, 1058, 1152, 1250, 1352, 1458, 1568, 1682, 1800, 1922, + 2048, 2178, 2312, 2450, 2592, 2738, 2888, 3042, 3200, 3362, + 3528, 3698, 3872, 4050, 4232, 4418, 4608, 4802, 5000, 5202, + 5408, 5618, 5832, 6050, 6272, 6498, 6728, 6962, 7200, 7442, + 7688, 7938, 8192, 8450, 8712, 8978, 9248, 9522, 9800, 10082, + 10368, 10658, 10952, 11250, 11552, 11858, 12168, 12482, 12800, 13122, + 13448, 13778, 14112, 14450, 14792, 15138, 15488, 15842, 16200, 16562, + 16928, 17298, 17672, 18050, 18432, 18818, 19208, 19602, 20000, 20402, + 20808, 21218, 21632, 22050, 22472, 22898, 23328, 23762, 24200, 24642, + 25088, 25538, 25992, 26450, 26912, 27378, 27848, 28322, 28800, 29282, + 29768, 30258, 30752, 31250, 31752, 32258 +}; + +Audio3DO_SDX2_Stream::Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace) + : _sampleRate(sampleRate), _stereo(stereo), + _stream(stream, disposeAfterUse) { + + _callerDecoderData = persistentSpace; + memset(&_initialDecoderData, 0, sizeof(_initialDecoderData)); + _initialRead = true; + + reset(); +} + +void Audio3DO_SDX2_Stream::reset() { + memcpy(&_curDecoderData, &_initialDecoderData, sizeof(_curDecoderData)); + _streamBytesLeft = _stream->size(); + _stream->seek(0); +} + +bool Audio3DO_SDX2_Stream::rewind() { + reset(); + return true; +} + +// Writes the requested amount (or less) of samples into buffer and returns the amount of samples, that got written +int Audio3DO_SDX2_Stream::readBuffer(int16 *buffer, const int numSamples) { + int8 byteCache[AUDIO_3DO_CACHE_SIZE]; + int8 *byteCachePtr = NULL; + int byteCacheSize = 0; + int requestedBytesLeft = numSamples; // 1 byte per 16-bit sample + int decodedSamplesCount = 0; + + int8 compressedByte = 0; + uint8 squareTableOffset = 0; + int16 decodedSample = 0; + + if (endOfData()) + return 0; // no more bytes left + + if (_stereo) { + // We expect numSamples to be even in case of Stereo audio + assert((numSamples & 1) == 0); + } + + if (_callerDecoderData) { + // copy caller decoder data over + memcpy(&_curDecoderData, _callerDecoderData, sizeof(_curDecoderData)); + if (_initialRead) { + _initialRead = false; + memcpy(&_initialDecoderData, &_curDecoderData, sizeof(_initialDecoderData)); + } + } + + requestedBytesLeft = numSamples; + if (requestedBytesLeft > _streamBytesLeft) + requestedBytesLeft = _streamBytesLeft; // not enough bytes left + + // buffering, so that direct decoding of files and such runs way faster + while (requestedBytesLeft) { + if (requestedBytesLeft > AUDIO_3DO_CACHE_SIZE) { + byteCacheSize = AUDIO_3DO_CACHE_SIZE; + } else { + byteCacheSize = requestedBytesLeft; + } + + requestedBytesLeft -= byteCacheSize; + _streamBytesLeft -= byteCacheSize; + + // Fill our byte cache + _stream->read(byteCache, byteCacheSize); + + byteCachePtr = byteCache; + + if (!_stereo) { + // Mono + while (byteCacheSize) { + compressedByte = *byteCachePtr++; + byteCacheSize--; + squareTableOffset = compressedByte + 128; + + if (!(compressedByte & 1)) + _curDecoderData.lastSample1 = 0; + + decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset]; + _curDecoderData.lastSample1 = decodedSample; + + buffer[decodedSamplesCount] = decodedSample; + decodedSamplesCount++; + } + } else { + // Stereo + while (byteCacheSize) { + compressedByte = *byteCachePtr++; + byteCacheSize--; + squareTableOffset = compressedByte + 128; + + if (!(decodedSamplesCount & 1)) { + // First channel + if (!(compressedByte & 1)) + _curDecoderData.lastSample1 = 0; + + decodedSample = _curDecoderData.lastSample1 + audio_3DO_SDX2_SquareTable[squareTableOffset]; + _curDecoderData.lastSample1 = decodedSample; + } else { + // Second channel + if (!(compressedByte & 1)) + _curDecoderData.lastSample2 = 0; + + decodedSample = _curDecoderData.lastSample2 + audio_3DO_SDX2_SquareTable[squareTableOffset]; + _curDecoderData.lastSample2 = decodedSample; + } + + buffer[decodedSamplesCount] = decodedSample; + decodedSamplesCount++; + } + } + } + + if (_callerDecoderData) { + // copy caller decoder data back + memcpy(_callerDecoderData, &_curDecoderData, sizeof(_curDecoderData)); + } + + return decodedSamplesCount; +} + +RewindableAudioStream *make3DO_SDX2AudioStream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, uint32 *audioLengthMSecsPtr, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpace) { + if (stereo) { + if (stream->size() & 1) { + warning("make3DO_SDX2Stream(): stereo data is uneven size"); + return 0; + } + } + + if (audioLengthMSecsPtr) { + // Caller requires the milliseconds of audio + uint32 audioLengthMSecs = stream->size() * 1000 / sampleRate; // 1 byte == 1 16-bit sample + if (stereo) { + audioLengthMSecs /= 2; + } + *audioLengthMSecsPtr = audioLengthMSecs; + } + + return new Audio3DO_SDX2_Stream(stream, sampleRate, stereo, disposeAfterUse, persistentSpace); +} + +} // End of namespace Audio diff --git a/audio/decoders/3do.h b/audio/decoders/3do.h new file mode 100644 index 0000000000..7524358543 --- /dev/null +++ b/audio/decoders/3do.h @@ -0,0 +1,158 @@ +/* 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. + * + */ + +/** + * @file + * Sound decoder used in engines: + * - sherlock (3DO version of Serrated Scalpel) + */ + +#ifndef AUDIO_3DO_SDX2_H +#define AUDIO_3DO_SDX2_H + +#include "common/scummsys.h" +#include "common/types.h" +#include "common/substream.h" + +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +namespace Common { +class SeekableReadStream; +} + +namespace Audio { + +class SeekableAudioStream; + +// amount of bytes to be used within the decoder classes as buffers +#define AUDIO_3DO_CACHE_SIZE 1024 + +// persistent spaces +struct audio_3DO_ADP4_PersistentSpace { + int16 lastSample; + int16 stepIndex; +}; + +struct audio_3DO_SDX2_PersistentSpace { + int16 lastSample1; + int16 lastSample2; +}; + +class Audio3DO_ADP4_Stream : public RewindableAudioStream { +public: + Audio3DO_ADP4_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_ADP4_PersistentSpace *persistentSpace); + +protected: + const uint16 _sampleRate; + const bool _stereo; + + Common::DisposablePtr<Common::SeekableReadStream> _stream; + int32 _streamBytesLeft; + + void reset(); + bool rewind(); + bool endOfData() const { return (_stream->pos() >= _stream->size()); } + bool isStereo() const { return _stereo; } + int getRate() const { return _sampleRate; } + + int readBuffer(int16 *buffer, const int numSamples); + + bool _initialRead; + audio_3DO_ADP4_PersistentSpace *_callerDecoderData; + audio_3DO_ADP4_PersistentSpace _initialDecoderData; + audio_3DO_ADP4_PersistentSpace _curDecoderData; + +private: + int16 decodeSample(byte compressedNibble); +}; + +class Audio3DO_SDX2_Stream : public RewindableAudioStream { +public: + Audio3DO_SDX2_Stream(Common::SeekableReadStream *stream, uint16 sampleRate, bool stereo, DisposeAfterUse::Flag disposeAfterUse, audio_3DO_SDX2_PersistentSpace *persistentSpacePtr); + +protected: + const uint16 _sampleRate; + const bool _stereo; + + Common::DisposablePtr<Common::SeekableReadStream> _stream; + int32 _streamBytesLeft; + + void reset(); + bool rewind(); + bool endOfData() const { return (_stream->pos() >= _stream->size()); } + bool isStereo() const { return _stereo; } + int getRate() const { return _sampleRate; } + + int readBuffer(int16 *buffer, const int numSamples); + + bool _initialRead; + audio_3DO_SDX2_PersistentSpace *_callerDecoderData; + audio_3DO_SDX2_PersistentSpace _initialDecoderData; + audio_3DO_SDX2_PersistentSpace _curDecoderData; +}; + +/** + * Try to decode 3DO ADP4 data from the given seekable stream and create a SeekableAudioStream + * from that data. + * + * @param stream the SeekableReadStream from which to read the 3DO SDX2 data + * @sampleRate sample rate + * @stereo if it's stereo or mono + * @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds + * @disposeAfterUse disposeAfterUse whether to delete the stream after use + * @persistentSpacePtr pointer to the persistent space structure + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *make3DO_ADP4AudioStream( + Common::SeekableReadStream *stream, + uint16 sampleRate, + bool stereo, + uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES, + audio_3DO_ADP4_PersistentSpace *persistentSpacePtr = NULL +); + +/** + * Try to decode 3DO SDX2 data from the given seekable stream and create a SeekableAudioStream + * from that data. + * + * @param stream the SeekableReadStream from which to read the 3DO SDX2 data + * @sampleRate sample rate + * @stereo if it's stereo or mono + * @audioLengthMSecsPtr pointer to a uint32 variable, that is supposed to get the length of the audio in milliseconds + * @disposeAfterUse disposeAfterUse whether to delete the stream after use + * @persistentSpacePtr pointer to the persistent space structure + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *make3DO_SDX2AudioStream( + Common::SeekableReadStream *stream, + uint16 sampleRate, + bool stereo, + uint32 *audioLengthMSecsPtr = NULL, // returns the audio length in milliseconds + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES, + audio_3DO_SDX2_PersistentSpace *persistentSpacePtr = NULL +); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/aiff.cpp b/audio/decoders/aiff.cpp index b714721c02..72baf84582 100644 --- a/audio/decoders/aiff.cpp +++ b/audio/decoders/aiff.cpp @@ -24,16 +24,19 @@ * The code in this file is based on information found at * http://www.borg.com/~jglatt/tech/aiff.htm * - * We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX - * (http://sox.sourceforge.net) may be a good place to start from. + * Also partially based on libav's aiffdec.c */ +#include "common/debug.h" #include "common/endian.h" #include "common/stream.h" +#include "common/substream.h" #include "common/textconsole.h" +#include "audio/audiostream.h" #include "audio/decoders/aiff.h" #include "audio/decoders/raw.h" +#include "audio/decoders/3do.h" namespace Audio { @@ -62,23 +65,34 @@ uint32 readExtended(Common::SeekableReadStream &stream) { return mantissa; } -bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) { - byte buf[4]; +// AIFF versions +static const uint32 kVersionAIFF = MKTAG('A', 'I', 'F', 'F'); +static const uint32 kVersionAIFC = MKTAG('A', 'I', 'F', 'C'); - stream.read(buf, 4); - if (memcmp(buf, "FORM", 4) != 0) { - warning("loadAIFFFromStream: No 'FORM' header"); - return false; +// Codecs +static const uint32 kCodecPCM = MKTAG('N', 'O', 'N', 'E'); // very original + +RewindableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + if (stream->readUint32BE() != MKTAG('F', 'O', 'R', 'M')) { + warning("makeAIFFStream: No 'FORM' header"); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + return 0; } - stream.readUint32BE(); + stream->readUint32BE(); // file size + + uint32 version = stream->readUint32BE(); - // This could be AIFC, but we don't handle that case. + if (version != kVersionAIFF && version != kVersionAIFC) { + warning("makeAIFFStream: No 'AIFF' or 'AIFC' header"); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; - stream.read(buf, 4); - if (memcmp(buf, "AIFF", 4) != 0) { - warning("loadAIFFFromStream: No 'AIFF' header"); - return false; + return 0; } // From here on, we only care about the COMM and SSND chunks, which are @@ -87,95 +101,131 @@ bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate bool foundCOMM = false; bool foundSSND = false; - uint16 numChannels = 0, bitsPerSample = 0; - uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0; + uint16 channels = 0, bitsPerSample = 0; + uint32 blockAlign = 0, rate = 0; + uint32 codec = kCodecPCM; // AIFF default + Common::SeekableReadStream *dataStream = 0; - while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) { - uint32 length, pos; + while (!(foundCOMM && foundSSND) && !stream->err() && !stream->eos()) { + uint32 tag = stream->readUint32BE(); + uint32 length = stream->readUint32BE(); + uint32 pos = stream->pos(); - stream.read(buf, 4); - length = stream.readUint32BE(); - pos = stream.pos(); + if (stream->eos() || stream->err()) + break; - if (memcmp(buf, "COMM", 4) == 0) { + switch (tag) { + case MKTAG('C', 'O', 'M', 'M'): foundCOMM = true; - numChannels = stream.readUint16BE(); - numSampleFrames = stream.readUint32BE(); - bitsPerSample = stream.readUint16BE(); - rate = readExtended(stream); - size = numSampleFrames * numChannels * (bitsPerSample / 8); - } else if (memcmp(buf, "SSND", 4) == 0) { + channels = stream->readUint16BE(); + /* frameCount = */ stream->readUint32BE(); + bitsPerSample = stream->readUint16BE(); + rate = readExtended(*stream); + + if (version == kVersionAIFC) + codec = stream->readUint32BE(); + break; + case MKTAG('S', 'S', 'N', 'D'): foundSSND = true; - offset = stream.readUint32BE(); - blockSize = stream.readUint32BE(); - soundOffset = stream.pos(); + /* uint32 offset = */ stream->readUint32BE(); + blockAlign = stream->readUint32BE(); + dataStream = new Common::SeekableSubReadStream(stream, stream->pos(), stream->pos() + length - 8, disposeAfterUse); + break; + case MKTAG('F', 'V', 'E', 'R'): + switch (stream->readUint32BE()) { + case 0: + version = kVersionAIFF; + break; + case 0xA2805140: + version = kVersionAIFC; + break; + default: + warning("Unknown AIFF version chunk version"); + break; + } + break; + case MKTAG('w', 'a', 'v', 'e'): + warning("Found unhandled AIFF-C extra data chunk"); + + if (!dataStream && disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + delete dataStream; + return 0; + default: + debug(1, "Skipping AIFF '%s' chunk", tag2str(tag)); + break; } - stream.seek(pos + length); + stream->seek(pos + length + (length & 1)); // ensure we're also word-aligned } if (!foundCOMM) { - warning("loadAIFFFromStream: Cound not find 'COMM' chunk"); - return false; - } - - if (!foundSSND) { - warning("loadAIFFFromStream: Cound not find 'SSND' chunk"); - return false; - } - - // We only implement a subset of the AIFF standard. - - if (numChannels < 1 || numChannels > 2) { - warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels); - return false; - } + warning("makeAIFFStream: Cound not find 'COMM' chunk"); - if (bitsPerSample != 8 && bitsPerSample != 16) { - warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample); - return false; - } + if (!dataStream && disposeAfterUse == DisposeAfterUse::YES) + delete stream; - if (offset != 0 || blockSize != 0) { - warning("loadAIFFFromStream: Block-aligned data is not supported"); - return false; + delete dataStream; + return 0; } - // Samples are always signed, and big endian. - - flags = 0; - if (bitsPerSample == 16) - flags |= Audio::FLAG_16BITS; - if (numChannels == 2) - flags |= Audio::FLAG_STEREO; - - stream.seek(soundOffset); - - // Stream now points at the sample data - - return true; -} - -SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, - DisposeAfterUse::Flag disposeAfterUse) { - int size, rate; - byte *data, flags; + if (!foundSSND) { + warning("makeAIFFStream: Cound not find 'SSND' chunk"); - if (!loadAIFFFromStream(*stream, size, rate, flags)) { if (disposeAfterUse == DisposeAfterUse::YES) delete stream; + return 0; } - data = (byte *)malloc(size); - assert(data); - stream->read(data, size); + // We only implement a subset of the AIFF standard. - if (disposeAfterUse == DisposeAfterUse::YES) - delete stream; + if (channels < 1 || channels > 2) { + warning("makeAIFFStream: Only 1 or 2 channels are supported, not %d", channels); + delete dataStream; + return 0; + } + + // Seek to the start of dataStream, required for at least FileStream + dataStream->seek(0); + + switch (codec) { + case kCodecPCM: + case MKTAG('t', 'w', 'o', 's'): + case MKTAG('s', 'o', 'w', 't'): { + // PCM samples are always signed. + byte rawFlags = 0; + if (bitsPerSample == 16) + rawFlags |= Audio::FLAG_16BITS; + if (channels == 2) + rawFlags |= Audio::FLAG_STEREO; + if (codec == MKTAG('s', 'o', 'w', 't')) + rawFlags |= Audio::FLAG_LITTLE_ENDIAN; + + return makeRawStream(dataStream, rate, rawFlags); + } + case MKTAG('i', 'm', 'a', '4'): + // TODO: Use QT IMA ADPCM + warning("Unhandled AIFF-C QT IMA ADPCM compression"); + break; + case MKTAG('Q', 'D', 'M', '2'): + // TODO: Need to figure out how to integrate this + // (But hopefully never needed) + warning("Unhandled AIFF-C QDM2 compression"); + break; + case MKTAG('A', 'D', 'P', '4'): + // ADP4 on 3DO + return make3DO_ADP4AudioStream(dataStream, rate, channels == 2); + case MKTAG('S', 'D', 'X', '2'): + // SDX2 on 3DO + return make3DO_SDX2AudioStream(dataStream, rate, channels == 2); + default: + warning("Unhandled AIFF-C compression tag '%s'", tag2str(codec)); + } - // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. - return makeRawStream(data, size, rate, flags); + delete dataStream; + return 0; } } // End of namespace Audio diff --git a/audio/decoders/aiff.h b/audio/decoders/aiff.h index afb0342cfd..3af2efb4c9 100644 --- a/audio/decoders/aiff.h +++ b/audio/decoders/aiff.h @@ -23,6 +23,7 @@ /** * @file * Sound decoder used in engines: + * - bbvs * - pegasus * - saga * - sci @@ -41,28 +42,17 @@ class SeekableReadStream; namespace Audio { -class SeekableAudioStream; - -/** - * Try to load an AIFF from the given seekable stream. Returns true if - * successful. In that case, the stream's seek position will be set to the - * start of the audio data, and size, rate and flags contain information - * necessary for playback. Currently this function only supports uncompressed - * raw PCM. - */ -extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags); +class RewindableAudioStream; /** * Try to load an AIFF from the given seekable stream and create an AudioStream * from that data. * - * This function uses loadAIFFFromStream() internally. - * * @param stream the SeekableReadStream from which to read the AIFF data * @param disposeAfterUse whether to delete the stream after use * @return a new SeekableAudioStream, or NULL, if an error occurred */ -SeekableAudioStream *makeAIFFStream( +RewindableAudioStream *makeAIFFStream( Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse); diff --git a/audio/decoders/mp3.cpp b/audio/decoders/mp3.cpp index c1b3faaeb1..feb531f5ae 100644 --- a/audio/decoders/mp3.cpp +++ b/audio/decoders/mp3.cpp @@ -246,6 +246,23 @@ void MP3Stream::initStream() { _inStream->seek(0, SEEK_SET); _curTime = mad_timer_zero; _posInFrame = 0; + + // Skip ID3 TAG if any + // ID3v1 (beginning with with 'TAG') is located at the end of files. So we can ignore those. + // ID3v2 can be located at the start of files and begins with a 10 bytes header, the first 3 bytes being 'ID3'. + // The tag size is coded on the last 4 bytes of the 10 bytes header as a 32 bit synchsafe integer. + // See http://id3.org/id3v2.4.0-structure for details. + char data[10]; + _inStream->read(data, 10); + if (data[0] == 'I' && data[1] == 'D' && data[2] == '3') { + uint32 size = data[9] + 128 * (data[8] + 128 * (data[7] + 128 * data[6])); + // This size does not include an optional 10 bytes footer. Check if it is present. + if (data[5] & 0x10) + size += 10; + debug("Skipping ID3 TAG (%d bytes)", size + 10); + _inStream->seek(size, SEEK_CUR); + } else + _inStream->seek(0, SEEK_SET); // Update state _state = MP3_STATE_READY; diff --git a/audio/decoders/quicktime.cpp b/audio/decoders/quicktime.cpp index 331c850b1a..ff87e7a9f8 100644 --- a/audio/decoders/quicktime.cpp +++ b/audio/decoders/quicktime.cpp @@ -241,6 +241,15 @@ void QuickTimeAudioDecoder::QuickTimeAudioTrack::queueAudio(const Timestamp &len // If we have any samples that we need to skip (ie. we seeked into // the middle of a chunk), skip them here. if (_skipSamples != Timestamp()) { + if (_skipSamples > chunkLength) { + // If the amount we need to skip is greater than the size + // of the chunk, just skip it altogether. + _curMediaPos = _curMediaPos + chunkLength; + _skipSamples = _skipSamples - chunkLength; + delete stream; + continue; + } + skipSamples(_skipSamples, stream); _curMediaPos = _curMediaPos + _skipSamples; chunkLength = chunkLength - _skipSamples; diff --git a/audio/decoders/wave.h b/audio/decoders/wave.h index 1dcaefd845..6bc9f72101 100644 --- a/audio/decoders/wave.h +++ b/audio/decoders/wave.h @@ -23,15 +23,25 @@ /** * @file * Sound decoder used in engines: + * - access * - agos + * - cge + * - cge2 + * - fullpipe * - gob + * - hopkins * - mohawk + * - prince * - saga * - sci * - scumm + * - sherlock * - sword1 * - sword2 + * - tony * - tucker + * - wintermute + * - zvision */ #ifndef AUDIO_WAVE_H diff --git a/audio/fmopl.cpp b/audio/fmopl.cpp index c18e544410..cc00ace264 100644 --- a/audio/fmopl.cpp +++ b/audio/fmopl.cpp @@ -22,21 +22,33 @@ #include "audio/fmopl.h" +#include "audio/mixer.h" #include "audio/softsynth/opl/dosbox.h" #include "audio/softsynth/opl/mame.h" #include "common/config-manager.h" +#include "common/system.h" #include "common/textconsole.h" +#include "common/timer.h" #include "common/translation.h" namespace OPL { +// Factory functions + +#ifdef USE_ALSA +namespace ALSA { + OPL *create(Config::OplType type); +} // End of namespace ALSA +#endif // USE_ALSA + // Config implementation enum OplEmulator { kAuto = 0, kMame = 1, - kDOSBox = 2 + kDOSBox = 2, + kALSA = 3 }; OPL::OPL() { @@ -51,6 +63,9 @@ const Config::EmulatorDescription Config::_drivers[] = { #ifndef DISABLE_DOSBOX_OPL { "db", _s("DOSBox OPL emulator"), kDOSBox, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 }, #endif +#ifdef USE_ALSA + { "alsa", _s("ALSA Direct FM"), kALSA, kFlagOpl2 | kFlagDualOpl2 | kFlagOpl3 }, +#endif { 0, 0, 0, 0 } }; @@ -63,6 +78,15 @@ Config::DriverId Config::parse(const Common::String &name) { return -1; } +const Config::EmulatorDescription *Config::findDriver(DriverId id) { + for (int i = 0; _drivers[i].name; ++i) { + if (_drivers[i].id == id) + return &_drivers[i]; + } + + return 0; +} + Config::DriverId Config::detect(OplType type) { uint32 flags = 0; switch (type) { @@ -80,12 +104,21 @@ Config::DriverId Config::detect(OplType type) { } DriverId drv = parse(ConfMan.get("opl_driver")); + if (drv == kAuto) { + // Since the "auto" can be explicitly set for a game, and this + // driver shows up in the GUI as "<default>", check if there is + // a global setting for it before resorting to auto-detection. + drv = parse(ConfMan.get("opl_driver", Common::ConfigManager::kApplicationDomain)); + } // When a valid driver is selected, check whether it supports // the requested OPL chip. if (drv != -1 && drv != kAuto) { + const EmulatorDescription *driverDesc = findDriver(drv); // If the chip is supported, just use the driver. - if ((flags & _drivers[drv].flags)) { + if (!driverDesc) { + warning("The selected OPL driver %d could not be found", drv); + } else if ((flags & driverDesc->flags)) { return drv; } else { // Else we will output a warning and just @@ -145,6 +178,11 @@ OPL *Config::create(DriverId driver, OplType type) { return new DOSBox::OPL(type); #endif +#ifdef USE_ALSA + case kALSA: + return ALSA::create(type); +#endif + default: warning("Unsupported OPL emulator %d", driver); // TODO: Maybe we should add some dummy emulator too, which just outputs @@ -153,43 +191,143 @@ OPL *Config::create(DriverId driver, OplType type) { } } +void OPL::start(TimerCallback *callback, int timerFrequency) { + _callback.reset(callback); + startCallbacks(timerFrequency); +} + +void OPL::stop() { + stopCallbacks(); + _callback.reset(); +} + bool OPL::_hasInstance = false; -} // End of namespace OPL +RealOPL::RealOPL() : _baseFreq(0), _remainingTicks(0) { +} -void OPLDestroy(FM_OPL *OPL) { - delete OPL; +RealOPL::~RealOPL() { + // Stop callbacks, just in case. If it's still playing at this + // point, there's probably a bigger issue, though. The subclass + // needs to call stop() or the pointer can still use be used in + // the mixer thread at the same time. + stop(); } -void OPLResetChip(FM_OPL *OPL) { - OPL->reset(); +void RealOPL::setCallbackFrequency(int timerFrequency) { + stopCallbacks(); + startCallbacks(timerFrequency); } -void OPLWrite(FM_OPL *OPL, int a, int v) { - OPL->write(a, v); +void RealOPL::startCallbacks(int timerFrequency) { + _baseFreq = timerFrequency; + assert(_baseFreq > 0); + + // We can't request more a timer faster than 100Hz. We'll handle this by calling + // the proc multiple times in onTimer() later on. + if (timerFrequency > kMaxFreq) + timerFrequency = kMaxFreq; + + _remainingTicks = 0; + g_system->getTimerManager()->installTimerProc(timerProc, 1000000 / timerFrequency, this, "RealOPL"); } -unsigned char OPLRead(FM_OPL *OPL, int a) { - return OPL->read(a); +void RealOPL::stopCallbacks() { + g_system->getTimerManager()->removeTimerProc(timerProc); + _baseFreq = 0; + _remainingTicks = 0; } -void OPLWriteReg(FM_OPL *OPL, int r, int v) { - OPL->writeReg(r, v); +void RealOPL::timerProc(void *refCon) { + static_cast<RealOPL *>(refCon)->onTimer(); } -void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length) { - OPL->readBuffer(buffer, length); +void RealOPL::onTimer() { + uint callbacks = 1; + + if (_baseFreq > kMaxFreq) { + // We run faster than our max, so run the callback multiple + // times to approximate the actual timer callback frequency. + uint totalTicks = _baseFreq + _remainingTicks; + callbacks = totalTicks / kMaxFreq; + _remainingTicks = totalTicks % kMaxFreq; + } + + // Call the callback multiple times. The if is on the inside of the + // loop in case the callback removes itself. + for (uint i = 0; i < callbacks; i++) + if (_callback && _callback->isValid()) + (*_callback)(); } -FM_OPL *makeAdLibOPL(int rate) { - FM_OPL *opl = OPL::Config::create(); +EmulatedOPL::EmulatedOPL() : + _nextTick(0), + _samplesPerTick(0), + _baseFreq(0), + _handle(new Audio::SoundHandle()) { +} - if (opl) { - if (!opl->init(rate)) { - delete opl; - opl = 0; +EmulatedOPL::~EmulatedOPL() { + // Stop callbacks, just in case. If it's still playing at this + // point, there's probably a bigger issue, though. The subclass + // needs to call stop() or the pointer can still use be used in + // the mixer thread at the same time. + stop(); + + delete _handle; +} + +int EmulatedOPL::readBuffer(int16 *buffer, const int numSamples) { + const int stereoFactor = isStereo() ? 2 : 1; + int len = numSamples / stereoFactor; + int step; + + do { + step = len; + if (step > (_nextTick >> FIXP_SHIFT)) + step = (_nextTick >> FIXP_SHIFT); + + generateSamples(buffer, step * stereoFactor); + + _nextTick -= step << FIXP_SHIFT; + if (!(_nextTick >> FIXP_SHIFT)) { + if (_callback && _callback->isValid()) + (*_callback)(); + + _nextTick += _samplesPerTick; } - } - return opl; + buffer += step * stereoFactor; + len -= step; + } while (len); + + return numSamples; +} + +int EmulatedOPL::getRate() const { + return g_system->getMixer()->getOutputRate(); } + +void EmulatedOPL::startCallbacks(int timerFrequency) { + setCallbackFrequency(timerFrequency); + g_system->getMixer()->playStream(Audio::Mixer::kPlainSoundType, _handle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +void EmulatedOPL::stopCallbacks() { + g_system->getMixer()->stopHandle(*_handle); +} + +void EmulatedOPL::setCallbackFrequency(int timerFrequency) { + _baseFreq = timerFrequency; + assert(_baseFreq != 0); + + int d = getRate() / _baseFreq; + int r = getRate() % _baseFreq; + + // This is equivalent to (getRate() << FIXP_SHIFT) / BASE_FREQ + // but less prone to arithmetic overflow. + + _samplesPerTick = (d << FIXP_SHIFT) + (r << FIXP_SHIFT) / _baseFreq; +} + +} // End of namespace OPL diff --git a/audio/fmopl.h b/audio/fmopl.h index 85ac606c7a..ba0872d87b 100644 --- a/audio/fmopl.h +++ b/audio/fmopl.h @@ -23,8 +23,16 @@ #ifndef AUDIO_FMOPL_H #define AUDIO_FMOPL_H +#include "audio/audiostream.h" + +#include "common/func.h" +#include "common/ptr.h" #include "common/scummsys.h" +namespace Audio { +class SoundHandle; +} + namespace Common { class String; } @@ -71,6 +79,12 @@ public: static DriverId parse(const Common::String &name); /** + * @return The driver description for the given id or 0 in case it is not + * available. + */ + static const EmulatorDescription *findDriver(DriverId id); + + /** * Detects a driver for the specific type. * * @return Returns a valid driver id on success, -1 otherwise. @@ -92,6 +106,14 @@ private: static const EmulatorDescription _drivers[]; }; +/** + * The type of the OPL timer callback functor. + */ +typedef Common::Functor0<void> TimerCallback; + +/** + * A representation of a Yamaha OPL chip. + */ class OPL { private: static bool _hasInstance; @@ -102,10 +124,9 @@ public: /** * Initializes the OPL emulator. * - * @param rate output sample rate * @return true on success, false on failure */ - virtual bool init(int rate) = 0; + virtual bool init() = 0; /** * Reinitializes the OPL emulator @@ -140,6 +161,101 @@ public: virtual void writeReg(int r, int v) = 0; /** + * Start the OPL with callbacks. + */ + void start(TimerCallback *callback, int timerFrequency = kDefaultCallbackFrequency); + + /** + * Stop the OPL + */ + void stop(); + + /** + * Change the callback frequency. This must only be called from a + * timer proc. + */ + virtual void setCallbackFrequency(int timerFrequency) = 0; + + enum { + /** + * The default callback frequency that start() uses + */ + kDefaultCallbackFrequency = 250 + }; + +protected: + /** + * Start the callbacks. + */ + virtual void startCallbacks(int timerFrequency) = 0; + + /** + * Stop the callbacks. + */ + virtual void stopCallbacks() = 0; + + /** + * The functor for callbacks. + */ + Common::ScopedPtr<TimerCallback> _callback; +}; + +/** + * An OPL that represents a real OPL, as opposed to an emulated one. + * + * This will use an actual timer instead of using one calculated from + * the number of samples in an AudioStream::readBuffer call. + */ +class RealOPL : public OPL { +public: + RealOPL(); + virtual ~RealOPL(); + + // OPL API + void setCallbackFrequency(int timerFrequency); + +protected: + // OPL API + void startCallbacks(int timerFrequency); + void stopCallbacks(); + +private: + static void timerProc(void *refCon); + void onTimer(); + + uint _baseFreq; + uint _remainingTicks; + + enum { + kMaxFreq = 100 + }; +}; + +/** + * An OPL that represents an emulated OPL. + * + * This will send callbacks based on the number of samples + * decoded in readBuffer(). + */ +class EmulatedOPL : public OPL, protected Audio::AudioStream { +public: + EmulatedOPL(); + virtual ~EmulatedOPL(); + + // OPL API + void setCallbackFrequency(int timerFrequency); + + // AudioStream API + int readBuffer(int16 *buffer, const int numSamples); + int getRate() const; + bool endOfData() const { return false; } + +protected: + // OPL API + void startCallbacks(int timerFrequency); + void stopCallbacks(); + + /** * Read up to 'length' samples. * * Data will be in native endianess, 16 bit per sample, signed. @@ -149,33 +265,21 @@ public: * So if you request 4 samples from a stereo OPL, you will get * a total of two left channel and two right channel samples. */ - virtual void readBuffer(int16 *buffer, int length) = 0; - - /** - * Returns whether the setup OPL mode is stereo or not - */ - virtual bool isStereo() const = 0; -}; + virtual void generateSamples(int16 *buffer, int numSamples) = 0; -} // End of namespace OPL +private: + int _baseFreq; -// Legacy API -// !You should not write any new code using the legacy API! -typedef OPL::OPL FM_OPL; + enum { + FIXP_SHIFT = 16 + }; -void OPLDestroy(FM_OPL *OPL); + int _nextTick; + int _samplesPerTick; -void OPLResetChip(FM_OPL *OPL); -void OPLWrite(FM_OPL *OPL, int a, int v); -unsigned char OPLRead(FM_OPL *OPL, int a); -void OPLWriteReg(FM_OPL *OPL, int r, int v); -void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length); + Audio::SoundHandle *_handle; +}; -/** - * Legacy factory to create an AdLib (OPL2) chip. - * - * !You should not write any new code using the legacy API! - */ -FM_OPL *makeAdLibOPL(int rate); +} // End of namespace OPL #endif diff --git a/audio/midiparser.h b/audio/midiparser.h index 9c10462cd7..2cca56b14c 100644 --- a/audio/midiparser.h +++ b/audio/midiparser.h @@ -370,6 +370,7 @@ public: public: typedef void (*XMidiCallbackProc)(byte eventData, void *refCon); + typedef void (*XMidiNewTimbreListProc)(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize); MidiParser(); virtual ~MidiParser() { allNotesOff(); } @@ -395,7 +396,7 @@ public: static void defaultXMidiCallback(byte eventData, void *refCon); static MidiParser *createParser_SMF(); - static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0); + static MidiParser *createParser_XMIDI(XMidiCallbackProc proc = defaultXMidiCallback, void *refCon = 0, XMidiNewTimbreListProc newTimbreListProc = NULL, MidiDriver_BASE *newTimbreListDriver = NULL); static MidiParser *createParser_QT(); static void timerCallback(void *data) { ((MidiParser *) data)->onTimer(); } }; diff --git a/audio/midiparser_xmidi.cpp b/audio/midiparser_xmidi.cpp index 95aa5d72f3..8742d7aad1 100644 --- a/audio/midiparser_xmidi.cpp +++ b/audio/midiparser_xmidi.cpp @@ -43,6 +43,22 @@ protected: XMidiCallbackProc _callbackProc; void *_callbackData; + // TODO: + // This should possibly get cleaned up at some point, but it's very tricks. + // We need to support XMIDI TIMB for 7th guest, which uses + // Miles Audio drivers. The MT32 driver needs to get the TIMB chunk, so that it + // can install all required timbres before the song starts playing. + // But we can't easily implement this directly like for example creating + // a special Miles Audio class for usage in this XMIDI-class, because other engines use this + // XMIDI-parser but w/o using Miles Audio drivers. + XMidiNewTimbreListProc _newTimbreListProc; + MidiDriver_BASE *_newTimbreListDriver; + + byte *_tracksTimbreList[120]; ///< Timbre-List for each track. + uint32 _tracksTimbreListSize[120]; ///< Size of the Timbre-List for each track. + byte *_activeTrackTimbreList; + uint32 _activeTrackTimbreListSize; + protected: uint32 readVLQ2(byte * &data); void parseNextEvent(EventInfo &info); @@ -53,7 +69,17 @@ protected: } public: - MidiParser_XMIDI(XMidiCallbackProc proc, void *data) : _callbackProc(proc), _callbackData(data), _loopCount(-1) {} + MidiParser_XMIDI(XMidiCallbackProc proc, void *data, XMidiNewTimbreListProc newTimbreListProc, MidiDriver_BASE *newTimbreListDriver) { + _callbackProc = proc; + _callbackData = data; + _loopCount = -1; + _newTimbreListProc = newTimbreListProc; + _newTimbreListDriver = newTimbreListDriver; + memset(_tracksTimbreList, 0, sizeof(_tracksTimbreList)); + memset(_tracksTimbreListSize, 0, sizeof(_tracksTimbreListSize)); + _activeTrackTimbreList = NULL; + _activeTrackTimbreListSize = 0; + } ~MidiParser_XMIDI() { } bool loadMusic(byte *data, uint32 size); @@ -322,11 +348,16 @@ bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) { // Skip this. pos += 4; } else if (!memcmp(pos, "TIMB", 4)) { - // Custom timbres? - // We don't support them. - // Read the length, skip it, and hope there was nothing there. + // Custom timbres + // chunk data is as follows: + // UINT16LE timbre count (amount of custom timbres used by this track) + // BYTE patchId + // BYTE bankId + // * timbre count pos += 4; len = read4high(pos); + _tracksTimbreList[tracksRead] = pos; // Skip the length bytes + _tracksTimbreListSize[tracksRead] = len; pos += (len + 1) & ~1; } else if (!memcmp(pos, "EVNT", 4)) { // Ahh! What we're looking for at last. @@ -350,6 +381,12 @@ bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) { resetTracking(); setTempo(500000); setTrack(0); + _activeTrackTimbreList = _tracksTimbreList[0]; + _activeTrackTimbreListSize = _tracksTimbreListSize[0]; + + if (_newTimbreListProc) + _newTimbreListProc(_newTimbreListDriver, _activeTrackTimbreList, _activeTrackTimbreListSize); + return true; } @@ -360,6 +397,6 @@ void MidiParser::defaultXMidiCallback(byte eventData, void *data) { warning("MidiParser: defaultXMidiCallback(%d)", eventData); } -MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data) { - return new MidiParser_XMIDI(proc, data); +MidiParser *MidiParser::createParser_XMIDI(XMidiCallbackProc proc, void *data, XMidiNewTimbreListProc newTimbreListProc, MidiDriver_BASE *newTimbreListDriver) { + return new MidiParser_XMIDI(proc, data, newTimbreListProc, newTimbreListDriver); } diff --git a/audio/miles.h b/audio/miles.h new file mode 100644 index 0000000000..23d5998fba --- /dev/null +++ b/audio/miles.h @@ -0,0 +1,83 @@ +/* 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. + * + */ + +#ifndef AUDIO_MILES_MIDIDRIVER_H +#define AUDIO_MILES_MIDIDRIVER_H + +#include "audio/mididrv.h" +#include "common/error.h" +#include "common/stream.h" + +namespace Audio { + +#define MILES_MIDI_CHANNEL_COUNT 16 + +// Miles Audio supported controllers for control change messages +#define MILES_CONTROLLER_SELECT_PATCH_BANK 114 +#define MILES_CONTROLLER_PROTECT_VOICE 112 +#define MILES_CONTROLLER_PROTECT_TIMBRE 113 +#define MILES_CONTROLLER_MODULATION 1 +#define MILES_CONTROLLER_VOLUME 7 +#define MILES_CONTROLLER_EXPRESSION 11 +#define MILES_CONTROLLER_PANNING 10 +#define MILES_CONTROLLER_SUSTAIN 64 +#define MILES_CONTROLLER_PITCH_RANGE 6 +#define MILES_CONTROLLER_RESET_ALL 121 +#define MILES_CONTROLLER_ALL_NOTES_OFF 123 +#define MILES_CONTROLLER_PATCH_REVERB 59 +#define MILES_CONTROLLER_PATCH_BENDER 60 +#define MILES_CONTROLLER_REVERB_MODE 61 +#define MILES_CONTROLLER_REVERB_TIME 62 +#define MILES_CONTROLLER_REVERB_LEVEL 63 +#define MILES_CONTROLLER_RHYTHM_KEY_TIMBRE 58 + +// 3 SysEx controllers, each range 5 +// 32-36 for 1st queue +// 37-41 for 2nd queue +// 42-46 for 3rd queue +#define MILES_CONTROLLER_SYSEX_RANGE_BEGIN 32 +#define MILES_CONTROLLER_SYSEX_RANGE_END 46 + +#define MILES_CONTROLLER_SYSEX_QUEUE_COUNT 3 +#define MILES_CONTROLLER_SYSEX_QUEUE_SIZE 32 + +#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1 0 +#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2 1 +#define MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3 2 +#define MILES_CONTROLLER_SYSEX_COMMAND_DATA 3 +#define MILES_CONTROLLER_SYSEX_COMMAND_SEND 4 + +#define MILES_CONTROLLER_XMIDI_RANGE_BEGIN 110 +#define MILES_CONTROLLER_XMIDI_RANGE_END 120 + +// Miles Audio actually used 0x4000, because they didn't shift the 2 bytes properly +#define MILES_PITCHBENDER_DEFAULT 0x2000 + +extern MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String &filenameAdLib, const Common::String &filenameOPL3, Common::SeekableReadStream *streamAdLib = nullptr, Common::SeekableReadStream *streamOPL3 = nullptr); + +extern MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename); + +extern void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize); + +} // End of namespace Audio + +#endif // AUDIO_MILES_MIDIDRIVER_H diff --git a/audio/miles_adlib.cpp b/audio/miles_adlib.cpp new file mode 100644 index 0000000000..bf5c9d4a73 --- /dev/null +++ b/audio/miles_adlib.cpp @@ -0,0 +1,1274 @@ +/* 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 "audio/miles.h" + +#include "common/file.h" +#include "common/system.h" +#include "common/textconsole.h" + +#include "audio/fmopl.h" +#include "audio/softsynth/emumidi.h" + +namespace Audio { + +// Miles Audio AdLib/OPL3 driver +// +// TODO: currently missing: OPL3 4-op voices +// +// Special cases (great for testing): +// - sustain feature is used by Return To Zork (demo) right at the start +// - sherlock holmes 2 does lots of priority sorts right at the start of the intro + +#define MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX 20 +#define MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX 18 + +#define MILES_ADLIB_PERCUSSION_BANK 127 + +#define MILES_ADLIB_STEREO_PANNING_THRESHOLD_LEFT 27 +#define MILES_ADLIB_STEREO_PANNING_THRESHOLD_RIGHT 100 + +enum kMilesAdLibUpdateFlags { + kMilesAdLibUpdateFlags_None = 0, + kMilesAdLibUpdateFlags_Reg_20 = 1 << 0, + kMilesAdLibUpdateFlags_Reg_40 = 1 << 1, + kMilesAdLibUpdateFlags_Reg_60 = 1 << 2, // register 0x6x + 0x8x + kMilesAdLibUpdateFlags_Reg_C0 = 1 << 3, + kMilesAdLibUpdateFlags_Reg_E0 = 1 << 4, + kMilesAdLibUpdateFlags_Reg_A0 = 1 << 5, // register 0xAx + 0xBx + kMilesAdLibUpdateFlags_Reg_All = 0x3F +}; + +uint16 milesAdLibOperator1Register[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = { + 0x0000, 0x0001, 0x0002, 0x0008, 0x0009, 0x000A, 0x0010, 0x0011, 0x0012, + 0x0100, 0x0101, 0x0102, 0x0108, 0x0109, 0x010A, 0x0110, 0x0111, 0x0112 +}; + +uint16 milesAdLibOperator2Register[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = { + 0x0003, 0x0004, 0x0005, 0x000B, 0x000C, 0x000D, 0x0013, 0x0014, 0x0015, + 0x0103, 0x0104, 0x0105, 0x010B, 0x010C, 0x010D, 0x0113, 0x0114, 0x0115 +}; + +uint16 milesAdLibChannelRegister[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX] = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, + 0x0100, 0x0101, 0x0102, 0x0103, 0x0104, 0x0105, 0x0106, 0x0107, 0x0108 +}; + +struct InstrumentEntry { + byte bankId; + byte patchId; + int16 transposition; + byte reg20op1; + byte reg40op1; + byte reg60op1; + byte reg80op1; + byte regE0op1; + byte reg20op2; + byte reg40op2; + byte reg60op2; + byte reg80op2; + byte regE0op2; + byte regC0; +}; + +// hardcoded, dumped from ADLIB.MDI +uint16 milesAdLibFrequencyLookUpTable[] = { + 0x02B2, 0x02B4, 0x02B7, 0x02B9, 0x02BC, 0x02BE, 0x02C1, 0x02C3, 0x02C6, 0x02C9, 0x02CB, 0x02CE, + 0x02D0, 0x02D3, 0x02D6, 0x02D8, 0x02DB, 0x02DD, 0x02E0, 0x02E3, 0x02E5, 0x02E8, 0x02EB, 0x02ED, + 0x02F0, 0x02F3, 0x02F6, 0x02F8, 0x02FB, 0x02FE, 0x0301, 0x0303, 0x0306, 0x0309, 0x030C, 0x030F, + 0x0311, 0x0314, 0x0317, 0x031A, 0x031D, 0x0320, 0x0323, 0x0326, 0x0329, 0x032B, 0x032E, 0x0331, + 0x0334, 0x0337, 0x033A, 0x033D, 0x0340, 0x0343, 0x0346, 0x0349, 0x034C, 0x034F, 0x0352, 0x0356, + 0x0359, 0x035C, 0x035F, 0x0362, 0x0365, 0x0368, 0x036B, 0x036F, 0x0372, 0x0375, 0x0378, 0x037B, + 0x037F, 0x0382, 0x0385, 0x0388, 0x038C, 0x038F, 0x0392, 0x0395, 0x0399, 0x039C, 0x039F, 0x03A3, + 0x03A6, 0x03A9, 0x03AD, 0x03B0, 0x03B4, 0x03B7, 0x03BB, 0x03BE, 0x03C1, 0x03C5, 0x03C8, 0x03CC, + 0x03CF, 0x03D3, 0x03D7, 0x03DA, 0x03DE, 0x03E1, 0x03E5, 0x03E8, 0x03EC, 0x03F0, 0x03F3, 0x03F7, + 0x03FB, 0x03FE, 0xFE01, 0xFE03, 0xFE05, 0xFE07, 0xFE08, 0xFE0A, 0xFE0C, 0xFE0E, 0xFE10, 0xFE12, + 0xFE14, 0xFE16, 0xFE18, 0xFE1A, 0xFE1C, 0xFE1E, 0xFE20, 0xFE21, 0xFE23, 0xFE25, 0xFE27, 0xFE29, + 0xFE2B, 0xFE2D, 0xFE2F, 0xFE31, 0xFE34, 0xFE36, 0xFE38, 0xFE3A, 0xFE3C, 0xFE3E, 0xFE40, 0xFE42, + 0xFE44, 0xFE46, 0xFE48, 0xFE4A, 0xFE4C, 0xFE4F, 0xFE51, 0xFE53, 0xFE55, 0xFE57, 0xFE59, 0xFE5C, + 0xFE5E, 0xFE60, 0xFE62, 0xFE64, 0xFE67, 0xFE69, 0xFE6B, 0xFE6D, 0xFE6F, 0xFE72, 0xFE74, 0xFE76, + 0xFE79, 0xFE7B, 0xFE7D, 0xFE7F, 0xFE82, 0xFE84, 0xFE86, 0xFE89, 0xFE8B, 0xFE8D, 0xFE90, 0xFE92, + 0xFE95, 0xFE97, 0xFE99, 0xFE9C, 0xFE9E, 0xFEA1, 0xFEA3, 0xFEA5, 0xFEA8, 0xFEAA, 0xFEAD, 0xFEAF +}; + +// hardcoded, dumped from ADLIB.MDI +uint16 milesAdLibVolumeSensitivityTable[] = { + 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 127 +}; + + +class MidiDriver_Miles_AdLib : public MidiDriver { +public: + MidiDriver_Miles_AdLib(InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount); + virtual ~MidiDriver_Miles_AdLib(); + + // MidiDriver + int open(); + void close(); + void send(uint32 b); + MidiChannel *allocateChannel() { return NULL; } + MidiChannel *getPercussionChannel() { return NULL; } + + bool isOpen() const { return _isOpen; } + uint32 getBaseTempo() { return 1000000 / OPL::OPL::kDefaultCallbackFrequency; } + + void setVolume(byte volume); + virtual uint32 property(int prop, uint32 param); + + void setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc); + +private: + bool _modeOPL3; + byte _modePhysicalFmVoicesCount; + byte _modeVirtualFmVoicesCount; + bool _modeStereo; + + // Structure to hold information about current status of MIDI Channels + struct MidiChannelEntry { + byte currentPatchBank; + const InstrumentEntry *currentInstrumentPtr; + uint16 currentPitchBender; + byte currentPitchRange; + byte currentVoiceProtection; + + byte currentVolume; + byte currentVolumeExpression; + + byte currentPanning; + + byte currentModulation; + byte currentSustain; + + byte currentActiveVoicesCount; + + MidiChannelEntry() : currentPatchBank(0), + currentInstrumentPtr(NULL), + currentPitchBender(MILES_PITCHBENDER_DEFAULT), + currentPitchRange(0), + currentVoiceProtection(0), + currentVolume(0), currentVolumeExpression(0), + currentPanning(0), + currentModulation(0), + currentSustain(0), + currentActiveVoicesCount(0) { } + }; + + // Structure to hold information about current status of virtual FM Voices + struct VirtualFmVoiceEntry { + bool inUse; + byte actualMidiChannel; + + const InstrumentEntry *currentInstrumentPtr; + + bool isPhysical; + byte physicalFmVoice; + + uint16 currentPriority; + + byte currentOriginalMidiNote; + byte currentNote; + int16 currentTransposition; + byte currentVelocity; + + bool sustained; + + VirtualFmVoiceEntry(): inUse(false), + actualMidiChannel(0), + currentInstrumentPtr(NULL), + isPhysical(false), physicalFmVoice(0), + currentPriority(0), + currentOriginalMidiNote(0), + currentNote(0), + currentTransposition(0), + currentVelocity(0), + sustained(false) { } + }; + + // Structure to hold information about current status of physical FM Voices + struct PhysicalFmVoiceEntry { + bool inUse; + byte virtualFmVoice; + + byte currentB0hReg; + + PhysicalFmVoiceEntry(): inUse(false), + virtualFmVoice(0), + currentB0hReg(0) { } + }; + + OPL::OPL *_opl; + int _masterVolume; + + Common::TimerManager::TimerProc _adlibTimerProc; + void *_adlibTimerParam; + + bool _isOpen; + + // stores information about all MIDI channels (not the actual OPL FM voice channels!) + MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT]; + + // stores information about all virtual OPL FM voices + VirtualFmVoiceEntry _virtualFmVoices[MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX]; + + // stores information about all physical OPL FM voices + PhysicalFmVoiceEntry _physicalFmVoices[MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX]; + + // holds all instruments + InstrumentEntry *_instrumentTablePtr; + uint16 _instrumentTableCount; + + bool circularPhysicalAssignment; + byte circularPhysicalAssignmentFmVoice; + + void onTimer(); + + void resetData(); + void resetAdLib(); + void resetAdLibOperatorRegisters(byte baseRegister, byte value); + void resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value); + + void setRegister(int reg, int value); + + int16 searchFreeVirtualFmVoiceChannel(); + int16 searchFreePhysicalFmVoiceChannel(); + + void noteOn(byte midiChannel, byte note, byte velocity); + void noteOff(byte midiChannel, byte note); + + void prioritySort(); + + void releaseFmVoice(byte virtualFmVoice); + + void releaseSustain(byte midiChannel); + + void updatePhysicalFmVoice(byte virtualFmVoice, bool keyOn, uint16 registerUpdateFlags); + + void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue); + void programChange(byte midiChannel, byte patchId); + + const InstrumentEntry *searchInstrument(byte bankId, byte patchId); + + void pitchBendChange(byte MIDIchannel, byte parameter1, byte parameter2); +}; + +MidiDriver_Miles_AdLib::MidiDriver_Miles_AdLib(InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) + : _masterVolume(15), _opl(0), + _adlibTimerProc(0), _adlibTimerParam(0), _isOpen(false) { + + _instrumentTablePtr = instrumentTablePtr; + _instrumentTableCount = instrumentTableCount; + + // Set up for OPL3, we will downgrade in case we can't create OPL3 emulator + // regular AdLib (OPL2) card + _modeOPL3 = true; + _modeVirtualFmVoicesCount = 20; + _modePhysicalFmVoicesCount = 18; + _modeStereo = true; + + // Older Miles Audio drivers did not do a circular assign for physical FM-voices + // Sherlock Holmes 2 used the circular assign + circularPhysicalAssignment = true; + // this way the first circular physical FM-voice search will start at FM-voice 0 + circularPhysicalAssignmentFmVoice = MILES_ADLIB_PHYSICAL_FMVOICES_COUNT_MAX; + + resetData(); +} + +MidiDriver_Miles_AdLib::~MidiDriver_Miles_AdLib() { + delete[] _instrumentTablePtr; // is created in factory MidiDriver_Miles_AdLib_create() +} + +int MidiDriver_Miles_AdLib::open() { + if (_modeOPL3) { + // Try to create OPL3 first + _opl = OPL::Config::create(OPL::Config::kOpl3); + } + if (!_opl) { + // not created yet, downgrade to OPL2 + _modeOPL3 = false; + _modeVirtualFmVoicesCount = 16; + _modePhysicalFmVoicesCount = 9; + _modeStereo = false; + + _opl = OPL::Config::create(OPL::Config::kOpl2); + } + + if (!_opl) { + // We still got nothing -> can't do anything anymore + return -1; + } + + _opl->init(); + + _isOpen = true; + + _opl->start(new Common::Functor0Mem<void, MidiDriver_Miles_AdLib>(this, &MidiDriver_Miles_AdLib::onTimer)); + + resetAdLib(); + + return 0; +} + +void MidiDriver_Miles_AdLib::close() { + delete _opl; + _isOpen = false; +} + +void MidiDriver_Miles_AdLib::setVolume(byte volume) { + _masterVolume = volume; + //renewNotes(-1, true); +} + +void MidiDriver_Miles_AdLib::onTimer() { + if (_adlibTimerProc) + (*_adlibTimerProc)(_adlibTimerParam); +} + +void MidiDriver_Miles_AdLib::resetData() { + memset(_midiChannels, 0, sizeof(_midiChannels)); + memset(_virtualFmVoices, 0, sizeof(_virtualFmVoices)); + memset(_physicalFmVoices, 0, sizeof(_physicalFmVoices)); + + for (byte midiChannel = 0; midiChannel < MILES_MIDI_CHANNEL_COUNT; midiChannel++) { + // defaults, were sent to driver during driver initialization + _midiChannels[midiChannel].currentVolume = 0x7F; + _midiChannels[midiChannel].currentPanning = 0x40; // center + _midiChannels[midiChannel].currentVolumeExpression = 127; + + // Miles Audio 2: hardcoded pitch range as a global (not channel specific), set to 12 + // Miles Audio 3: pitch range per MIDI channel + _midiChannels[midiChannel].currentPitchBender = MILES_PITCHBENDER_DEFAULT; + _midiChannels[midiChannel].currentPitchRange = 12; + } + +} + +void MidiDriver_Miles_AdLib::resetAdLib() { + if (_modeOPL3) { + setRegister(0x105, 1); // enable OPL3 + setRegister(0x104, 0); // activate 18 2-operator FM-voices + } + + setRegister(0x01, 0x20); // enable waveform control on both operators + setRegister(0x04, 0xE0); // Timer control + + setRegister(0x08, 0); // select FM music mode + setRegister(0xBD, 0); // disable Rhythm + + // reset FM voice instrument data + resetAdLibOperatorRegisters(0x20, 0); + resetAdLibOperatorRegisters(0x60, 0); + resetAdLibOperatorRegisters(0x80, 0); + resetAdLibFMVoiceChannelRegisters(0xA0, 0); + resetAdLibFMVoiceChannelRegisters(0xB0, 0); + resetAdLibFMVoiceChannelRegisters(0xC0, 0); + resetAdLibOperatorRegisters(0xE0, 0); + resetAdLibOperatorRegisters(0x40, 0x3F); +} + +void MidiDriver_Miles_AdLib::resetAdLibOperatorRegisters(byte baseRegister, byte value) { + byte physicalFmVoice = 0; + + for (physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) { + setRegister(baseRegister + milesAdLibOperator1Register[physicalFmVoice], value); + setRegister(baseRegister + milesAdLibOperator2Register[physicalFmVoice], value); + } +} + +void MidiDriver_Miles_AdLib::resetAdLibFMVoiceChannelRegisters(byte baseRegister, byte value) { + byte physicalFmVoice = 0; + + for (physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) { + setRegister(baseRegister + milesAdLibChannelRegister[physicalFmVoice], value); + } +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Miles_AdLib::send(uint32 b) { + byte command = b & 0xf0; + byte channel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + switch (command) { + case 0x80: + noteOff(channel, op1); + break; + case 0x90: + noteOn(channel, op1, op2); + break; + case 0xb0: // Control change + controlChange(channel, op1, op2); + break; + case 0xc0: // Program Change + programChange(channel, op1); + break; + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + // Aftertouch doesn't seem to be implemented in the Miles Audio AdLib driver + break; + case 0xe0: + pitchBendChange(channel, op1, op2); + break; + case 0xf0: // SysEx + warning("MILES-ADLIB: SysEx: %x", b); + break; + default: + warning("MILES-ADLIB: Unknown event %02x", command); + } +} + +void MidiDriver_Miles_AdLib::setTimerCallback(void *timerParam, Common::TimerManager::TimerProc timerProc) { + _adlibTimerProc = timerProc; + _adlibTimerParam = timerParam; +} + +int16 MidiDriver_Miles_AdLib::searchFreeVirtualFmVoiceChannel() { + for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) { + if (!_virtualFmVoices[virtualFmVoice].inUse) + return virtualFmVoice; + } + return -1; +} + +int16 MidiDriver_Miles_AdLib::searchFreePhysicalFmVoiceChannel() { + if (!circularPhysicalAssignment) { + // Older assign logic + for (byte physicalFmVoice = 0; physicalFmVoice < _modePhysicalFmVoicesCount; physicalFmVoice++) { + if (!_physicalFmVoices[physicalFmVoice].inUse) + return physicalFmVoice; + } + } else { + // Newer one + // Remembers last physical FM-voice and searches from that spot + byte physicalFmVoice = circularPhysicalAssignmentFmVoice; + for (byte physicalFmVoiceCount = 0; physicalFmVoiceCount < _modePhysicalFmVoicesCount; physicalFmVoiceCount++) { + physicalFmVoice++; + if (physicalFmVoice >= _modePhysicalFmVoicesCount) + physicalFmVoice = 0; + if (!_physicalFmVoices[physicalFmVoice].inUse) { + circularPhysicalAssignmentFmVoice = physicalFmVoice; + return physicalFmVoice; + } + } + } + return -1; +} + +void MidiDriver_Miles_AdLib::noteOn(byte midiChannel, byte note, byte velocity) { + const InstrumentEntry *instrumentPtr = NULL; + + if (velocity == 0) { + noteOff(midiChannel, note); + return; + } + + if (midiChannel == 9) { + // percussion channel + // search for instrument according to given note + instrumentPtr = searchInstrument(MILES_ADLIB_PERCUSSION_BANK, note); + } else { + // directly get instrument of channel + instrumentPtr = _midiChannels[midiChannel].currentInstrumentPtr; + } + if (!instrumentPtr) { + warning("MILES-ADLIB: noteOn: invalid instrument"); + return; + } + + //warning("Note On: channel %d, note %d, velocity %d, instrument %d/%d", midiChannel, note, velocity, instrumentPtr->bankId, instrumentPtr->patchId); + + // look for free virtual FM voice + int16 virtualFmVoice = searchFreeVirtualFmVoiceChannel(); + + if (virtualFmVoice == -1) { + // Out of virtual voices, can't do anything about it + return; + } + + // Scale back velocity + velocity = (velocity & 0x7F) >> 3; + velocity = milesAdLibVolumeSensitivityTable[velocity]; + + if (midiChannel != 9) { + _virtualFmVoices[virtualFmVoice].currentNote = note; + _virtualFmVoices[virtualFmVoice].currentTransposition = instrumentPtr->transposition; + } else { + // Percussion channel + _virtualFmVoices[virtualFmVoice].currentNote = instrumentPtr->transposition; + _virtualFmVoices[virtualFmVoice].currentTransposition = 0; + } + + _virtualFmVoices[virtualFmVoice].inUse = true; + _virtualFmVoices[virtualFmVoice].actualMidiChannel = midiChannel; + _virtualFmVoices[virtualFmVoice].currentOriginalMidiNote = note; + _virtualFmVoices[virtualFmVoice].currentInstrumentPtr = instrumentPtr; + _virtualFmVoices[virtualFmVoice].currentVelocity = velocity; + _virtualFmVoices[virtualFmVoice].isPhysical = false; + _virtualFmVoices[virtualFmVoice].sustained = false; + _virtualFmVoices[virtualFmVoice].currentPriority = 32767; + + int16 physicalFmVoice = searchFreePhysicalFmVoiceChannel(); + if (physicalFmVoice == -1) { + // None found + // go through priorities and reshuffle voices + prioritySort(); + return; + } + + // Another voice active on this MIDI channel + _midiChannels[midiChannel].currentActiveVoicesCount++; + + // Mark virtual FM-Voice as being connected to physical FM-Voice + _virtualFmVoices[virtualFmVoice].isPhysical = true; + _virtualFmVoices[virtualFmVoice].physicalFmVoice = physicalFmVoice; + + // Mark physical FM-Voice as being connected to virtual FM-Voice + _physicalFmVoices[physicalFmVoice].inUse = true; + _physicalFmVoices[physicalFmVoice].virtualFmVoice = virtualFmVoice; + + // Update the physical FM-Voice + updatePhysicalFmVoice(virtualFmVoice, true, kMilesAdLibUpdateFlags_Reg_All); +} + +void MidiDriver_Miles_AdLib::noteOff(byte midiChannel, byte note) { + //warning("Note Off: channel %d, note %d", midiChannel, note); + + // Search through all virtual FM-Voices for current midiChannel + note + for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) { + if (_virtualFmVoices[virtualFmVoice].inUse) { + if ((_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) && (_virtualFmVoices[virtualFmVoice].currentOriginalMidiNote == note)) { + // found one + if (_midiChannels[midiChannel].currentSustain >= 64) { + _virtualFmVoices[virtualFmVoice].sustained = true; + continue; + } + // + releaseFmVoice(virtualFmVoice); + } + } + } +} + +void MidiDriver_Miles_AdLib::prioritySort() { + byte virtualFmVoice = 0; + uint16 virtualPriority = 0; + uint16 virtualPriorities[MILES_ADLIB_VIRTUAL_FMVOICES_COUNT_MAX]; + uint16 virtualFmVoicesCount = 0; + byte midiChannel = 0; + + memset(&virtualPriorities, 0, sizeof(virtualPriorities)); + + //warning("prioritysort"); + + // First calculate priorities for all virtual FM voices, that are in use + for (virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) { + if (_virtualFmVoices[virtualFmVoice].inUse) { + virtualFmVoicesCount++; + + midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel; + if (_midiChannels[midiChannel].currentVoiceProtection >= 64) { + // Voice protection enabled + virtualPriority = 0xFFFF; + } else { + virtualPriority = _virtualFmVoices[virtualFmVoice].currentPriority; + } + byte currentActiveVoicesCount = _midiChannels[midiChannel].currentActiveVoicesCount; + if (virtualPriority >= currentActiveVoicesCount) { + virtualPriority -= _midiChannels[midiChannel].currentActiveVoicesCount; + } else { + virtualPriority = 0; // overflow, should never happen + } + virtualPriorities[virtualFmVoice] = virtualPriority; + } + } + + // + while (virtualFmVoicesCount) { + uint16 unvoicedHighestPriority = 0; + byte unvoicedHighestFmVoice = 0; + uint16 voicedLowestPriority = 65535; + byte voicedLowestFmVoice = 0; + + for (virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) { + if (_virtualFmVoices[virtualFmVoice].inUse) { + virtualPriority = virtualPriorities[virtualFmVoice]; + if (!_virtualFmVoices[virtualFmVoice].isPhysical) { + // currently not physical, so unvoiced + if (virtualPriority >= unvoicedHighestPriority) { + unvoicedHighestPriority = virtualPriority; + unvoicedHighestFmVoice = virtualFmVoice; + } + } else { + // currently physical, so voiced + if (virtualPriority <= voicedLowestPriority) { + voicedLowestPriority = virtualPriority; + voicedLowestFmVoice = virtualFmVoice; + } + } + } + } + + if (unvoicedHighestPriority < voicedLowestPriority) + break; // We are done + + if (unvoicedHighestPriority == 0) + break; + + // Safety checks + assert(_virtualFmVoices[voicedLowestFmVoice].isPhysical); + assert(!_virtualFmVoices[unvoicedHighestFmVoice].isPhysical); + + // Steal this physical voice + byte physicalFmVoice = _virtualFmVoices[voicedLowestFmVoice].physicalFmVoice; + + //warning("MILES-ADLIB: stealing physical FM-Voice %d from virtual FM-Voice %d for virtual FM-Voice %d", physicalFmVoice, voicedLowestFmVoice, unvoicedHighestFmVoice); + //warning("priority old %d, priority new %d", unvoicedHighestPriority, voicedLowestPriority); + + releaseFmVoice(voicedLowestFmVoice); + + // Get some data of the unvoiced highest priority virtual FM Voice + midiChannel = _virtualFmVoices[unvoicedHighestFmVoice].actualMidiChannel; + + // Another voice active on this MIDI channel + _midiChannels[midiChannel].currentActiveVoicesCount++; + + // Mark virtual FM-Voice as being connected to physical FM-Voice + _virtualFmVoices[unvoicedHighestFmVoice].isPhysical = true; + _virtualFmVoices[unvoicedHighestFmVoice].physicalFmVoice = physicalFmVoice; + + // Mark physical FM-Voice as being connected to virtual FM-Voice + _physicalFmVoices[physicalFmVoice].inUse = true; + _physicalFmVoices[physicalFmVoice].virtualFmVoice = unvoicedHighestFmVoice; + + // Update the physical FM-Voice + updatePhysicalFmVoice(unvoicedHighestFmVoice, true, kMilesAdLibUpdateFlags_Reg_All); + + virtualFmVoicesCount--; + } +} + +void MidiDriver_Miles_AdLib::releaseFmVoice(byte virtualFmVoice) { + // virtual Voice not actually played? -> exit + if (!_virtualFmVoices[virtualFmVoice].isPhysical) { + _virtualFmVoices[virtualFmVoice].inUse = false; + return; + } + + byte midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel; + byte physicalFmVoice = _virtualFmVoices[virtualFmVoice].physicalFmVoice; + + // stop note from playing + updatePhysicalFmVoice(virtualFmVoice, false, kMilesAdLibUpdateFlags_Reg_A0); + + // this virtual FM voice isn't physical anymore + _virtualFmVoices[virtualFmVoice].isPhysical = false; + _virtualFmVoices[virtualFmVoice].inUse = false; + + // Remove physical FM-Voice from being active + _physicalFmVoices[physicalFmVoice].inUse = false; + + // One less voice active on this MIDI channel + assert(_midiChannels[midiChannel].currentActiveVoicesCount); + _midiChannels[midiChannel].currentActiveVoicesCount--; +} + +void MidiDriver_Miles_AdLib::releaseSustain(byte midiChannel) { + // Search through all virtual FM-Voices for currently sustained notes and call noteOff on them + for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) { + if (_virtualFmVoices[virtualFmVoice].inUse) { + if ((_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) && (_virtualFmVoices[virtualFmVoice].sustained)) { + // is currently sustained + // so do a noteOff (which will check current sustain controller) + noteOff(midiChannel, _virtualFmVoices[virtualFmVoice].currentOriginalMidiNote); + } + } + } +} + +void MidiDriver_Miles_AdLib::updatePhysicalFmVoice(byte virtualFmVoice, bool keyOn, uint16 registerUpdateFlags) { + byte midiChannel = _virtualFmVoices[virtualFmVoice].actualMidiChannel; + + if (!_virtualFmVoices[virtualFmVoice].isPhysical) { + // virtual FM-Voice has no physical FM-Voice assigned? -> ignore + return; + } + + byte physicalFmVoice = _virtualFmVoices[virtualFmVoice].physicalFmVoice; + const InstrumentEntry *instrumentPtr = _virtualFmVoices[virtualFmVoice].currentInstrumentPtr; + + uint16 op1Reg = milesAdLibOperator1Register[physicalFmVoice]; + uint16 op2Reg = milesAdLibOperator2Register[physicalFmVoice]; + uint16 channelReg = milesAdLibChannelRegister[physicalFmVoice]; + + uint16 compositeVolume = 0; + + if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_40) { + // Calculate new volume + byte midiVolume = _midiChannels[midiChannel].currentVolume; + byte midiVolumeExpression = _midiChannels[midiChannel].currentVolumeExpression; + compositeVolume = midiVolume * midiVolumeExpression * 2; + + compositeVolume = compositeVolume >> 8; // get upmost 8 bits + if (compositeVolume) + compositeVolume++; // round up in case result wasn't 0 + + compositeVolume = compositeVolume * _virtualFmVoices[virtualFmVoice].currentVelocity * 2; + compositeVolume = compositeVolume >> 8; // get upmost 8 bits + if (compositeVolume) + compositeVolume++; // round up in case result wasn't 0 + } + + if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_20) { + // Amplitude Modulation / Vibrato / Envelope Generator Type / Keyboard Scaling Rate / Modulator Frequency Multiple + byte reg20op1 = instrumentPtr->reg20op1; + byte reg20op2 = instrumentPtr->reg20op2; + + if (_midiChannels[midiChannel].currentModulation >= 64) { + // set bit 6 (Vibrato) + reg20op1 |= 0x40; + reg20op2 |= 0x40; + } + setRegister(0x20 + op1Reg, reg20op1); + setRegister(0x20 + op2Reg, reg20op2); + } + + if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_40) { + // Volume (Level Key Scaling / Total Level) + byte reg40op1 = instrumentPtr->reg40op1; + byte reg40op2 = instrumentPtr->reg40op2; + + uint16 volumeOp1 = (~reg40op1) & 0x3F; + uint16 volumeOp2 = (~reg40op2) & 0x3F; + + if (instrumentPtr->regC0 & 1) { + // operator 2 enabled + // scale volume factor + volumeOp1 = (volumeOp1 * compositeVolume) / 127; + // 2nd operator always scaled + } + + volumeOp2 = (volumeOp2 * compositeVolume) / 127; + + volumeOp1 = (~volumeOp1) & 0x3F; // negate it, so we get the proper value for the register + volumeOp2 = (~volumeOp2) & 0x3F; // ditto + reg40op1 = (reg40op1 & 0xC0) | volumeOp1; // keep "scaling level" and merge in our volume + reg40op2 = (reg40op2 & 0xC0) | volumeOp2; + + setRegister(0x40 + op1Reg, reg40op1); + setRegister(0x40 + op2Reg, reg40op2); + } + + if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_60) { + // Attack Rate / Decay Rate + // Sustain Level / Release Rate + byte reg60op1 = instrumentPtr->reg60op1; + byte reg60op2 = instrumentPtr->reg60op2; + byte reg80op1 = instrumentPtr->reg80op1; + byte reg80op2 = instrumentPtr->reg80op2; + + setRegister(0x60 + op1Reg, reg60op1); + setRegister(0x60 + op2Reg, reg60op2); + setRegister(0x80 + op1Reg, reg80op1); + setRegister(0x80 + op2Reg, reg80op2); + } + + if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_E0) { + // Waveform Select + byte regE0op1 = instrumentPtr->regE0op1; + byte regE0op2 = instrumentPtr->regE0op2; + + setRegister(0xE0 + op1Reg, regE0op1); + setRegister(0xE0 + op2Reg, regE0op2); + } + + if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_C0) { + // Feedback / Algorithm + byte regC0 = instrumentPtr->regC0; + + if (_modeOPL3) { + // Panning for OPL3 + byte panning = _midiChannels[midiChannel].currentPanning; + + if (panning <= MILES_ADLIB_STEREO_PANNING_THRESHOLD_LEFT) { + regC0 |= 0x20; // left speaker only + } else if (panning >= MILES_ADLIB_STEREO_PANNING_THRESHOLD_RIGHT) { + regC0 |= 0x10; // right speaker only + } else { + regC0 |= 0x30; // center + } + } + + setRegister(0xC0 + channelReg, regC0); + } + + if (registerUpdateFlags & kMilesAdLibUpdateFlags_Reg_A0) { + // Frequency / Key-On + // Octave / F-Number / Key-On + if (!keyOn) { + // turn off note + byte regB0 = _physicalFmVoices[physicalFmVoice].currentB0hReg & 0x1F; // remove bit 5 "key on" + setRegister(0xB0 + channelReg, regB0); + + } else { + // turn on note, calculate frequency, octave... + int16 pitchBender = _midiChannels[midiChannel].currentPitchBender; + byte pitchRange = _midiChannels[midiChannel].currentPitchRange; + int16 currentNote = _virtualFmVoices[virtualFmVoice].currentNote; + int16 physicalNote = 0; + int16 halfTone = 0; + uint16 frequency = 0; + uint16 frequencyIdx = 0; + byte octave = 0; + + pitchBender -= 0x2000; + pitchBender = pitchBender >> 5; // divide by 32 + pitchBender = pitchBender * pitchRange; // pitchrange 12: now +0x0C00 to -0xC00 + // difference between Miles Audio 2 + 3 + // Miles Audio 2 used a pitch range of 12, which was basically hardcoded + // Miles Audio 3 used an array, which got set by control change events + + currentNote += _virtualFmVoices->currentTransposition; + + // Normalize note + currentNote -= 24; + do { + currentNote += 12; + } while (currentNote < 0); + currentNote += 12; + + do { + currentNote -= 12; + } while (currentNote > 95); + + // combine note + pitchbender, also adjust by 8 for rounding + currentNote = (currentNote << 8) + pitchBender + 8; + + currentNote = currentNote >> 4; // get actual note + + // Normalize + currentNote -= (12 * 16); + do { + currentNote += (12 * 16); + } while (currentNote < 0); + + currentNote += (12 * 16); + do { + currentNote -= (12 * 16); + } while (currentNote > ((96 * 16) - 1)); + + physicalNote = currentNote >> 4; + + halfTone = physicalNote % 12; // remainder of physicalNote / 12 + + frequencyIdx = (halfTone << 4) + (currentNote & 0x0F); + assert(frequencyIdx < sizeof(milesAdLibFrequencyLookUpTable)); + frequency = milesAdLibFrequencyLookUpTable[frequencyIdx]; + + octave = (physicalNote / 12) - 1; + + if (frequency & 0x8000) + octave++; + + if (octave & 0x80) { + octave++; + frequency = frequency >> 1; + } + + byte regA0 = frequency & 0xFF; + byte regB0 = ((frequency >> 8) & 0x03) | (octave << 2) | 0x20; + + setRegister(0xA0 + channelReg, regA0); + setRegister(0xB0 + channelReg, regB0); + + _physicalFmVoices[physicalFmVoice].currentB0hReg = regB0; + } + } + + //warning("end of update voice"); +} + +void MidiDriver_Miles_AdLib::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) { + uint16 registerUpdateFlags = kMilesAdLibUpdateFlags_None; + + switch (controllerNumber) { + case MILES_CONTROLLER_SELECT_PATCH_BANK: + //warning("patch bank channel %d, bank %x", midiChannel, controllerValue); + _midiChannels[midiChannel].currentPatchBank = controllerValue; + break; + + case MILES_CONTROLLER_PROTECT_VOICE: + _midiChannels[midiChannel].currentVoiceProtection = controllerValue; + break; + + case MILES_CONTROLLER_PROTECT_TIMBRE: + // It seems that this can get ignored, because we don't cache timbres at all + break; + + case MILES_CONTROLLER_MODULATION: + _midiChannels[midiChannel].currentModulation = controllerValue; + registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20; + break; + + case MILES_CONTROLLER_VOLUME: + _midiChannels[midiChannel].currentVolume = controllerValue; + registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40; + break; + + case MILES_CONTROLLER_EXPRESSION: + _midiChannels[midiChannel].currentVolumeExpression = controllerValue; + registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_40; + break; + + case MILES_CONTROLLER_PANNING: + _midiChannels[midiChannel].currentPanning = controllerValue; + if (_modeStereo) { + // Update register only in case we are in stereo mode + registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_C0; + } + break; + + case MILES_CONTROLLER_SUSTAIN: + _midiChannels[midiChannel].currentSustain = controllerValue; + if (controllerValue < 64) { + releaseSustain(midiChannel); + } + break; + + case MILES_CONTROLLER_PITCH_RANGE: + // Miles Audio 3 feature + _midiChannels[midiChannel].currentPitchRange = controllerValue; + break; + + case MILES_CONTROLLER_RESET_ALL: + _midiChannels[midiChannel].currentSustain = 0; + releaseSustain(midiChannel); + _midiChannels[midiChannel].currentModulation = 0; + _midiChannels[midiChannel].currentVolumeExpression = 127; + _midiChannels[midiChannel].currentPitchBender = MILES_PITCHBENDER_DEFAULT; + registerUpdateFlags = kMilesAdLibUpdateFlags_Reg_20 | kMilesAdLibUpdateFlags_Reg_40 | kMilesAdLibUpdateFlags_Reg_A0; + break; + + case MILES_CONTROLLER_ALL_NOTES_OFF: + for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) { + if (_virtualFmVoices[virtualFmVoice].inUse) { + // used + if (_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) { + // by our current MIDI channel -> noteOff + noteOff(midiChannel, _virtualFmVoices[virtualFmVoice].currentNote); + } + } + } + break; + + default: + //warning("MILES-ADLIB: Unsupported control change %d", controllerNumber); + break; + } + + if (registerUpdateFlags) { + for (byte virtualFmVoice = 0; virtualFmVoice < _modeVirtualFmVoicesCount; virtualFmVoice++) { + if (_virtualFmVoices[virtualFmVoice].inUse) { + // used + if (_virtualFmVoices[virtualFmVoice].actualMidiChannel == midiChannel) { + // by our current MIDI channel -> update + updatePhysicalFmVoice(virtualFmVoice, true, registerUpdateFlags); + } + } + } + } +} + +void MidiDriver_Miles_AdLib::programChange(byte midiChannel, byte patchId) { + const InstrumentEntry *instrumentPtr = NULL; + byte patchBank = _midiChannels[midiChannel].currentPatchBank; + + //warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, patchBank); + + // we check, if we actually have data for the requested instrument... + instrumentPtr = searchInstrument(patchBank, patchId); + if (!instrumentPtr) { + warning("MILES-ADLIB: unknown instrument requested (%d, %d)", patchBank, patchId); + return; + } + + // and remember it in that case for the current MIDI-channel + _midiChannels[midiChannel].currentInstrumentPtr = instrumentPtr; +} + +const InstrumentEntry *MidiDriver_Miles_AdLib::searchInstrument(byte bankId, byte patchId) { + const InstrumentEntry *instrumentPtr = _instrumentTablePtr; + + for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) { + if ((instrumentPtr->bankId == bankId) && (instrumentPtr->patchId == patchId)) { + return instrumentPtr; + } + instrumentPtr++; + } + + return NULL; +} + +void MidiDriver_Miles_AdLib::pitchBendChange(byte midiChannel, byte parameter1, byte parameter2) { + // Miles Audio actually didn't shift parameter 2 1 down in here + // which means in memory it used a 15-bit pitch bender, which also means the default was 0x4000 + if ((parameter1 & 0x80) || (parameter2 & 0x80)) { + warning("MILES-ADLIB: invalid pitch bend change"); + return; + } + _midiChannels[midiChannel].currentPitchBender = parameter1 | (parameter2 << 7); +} + +void MidiDriver_Miles_AdLib::setRegister(int reg, int value) { + if (!(reg & 0x100)) { + _opl->write(0x220, reg); + _opl->write(0x221, value); + //warning("OPL write %x %x (%d)", reg, value, value); + } else { + _opl->write(0x222, reg & 0xFF); + _opl->write(0x223, value); + //warning("OPL3 write %x %x (%d)", reg & 0xFF, value, value); + } +} + +uint32 MidiDriver_Miles_AdLib::property(int prop, uint32 param) { + return 0; +} + +MidiDriver *MidiDriver_Miles_AdLib_create(const Common::String &filenameAdLib, const Common::String &filenameOPL3, Common::SeekableReadStream *streamAdLib, Common::SeekableReadStream *streamOPL3) { + // Load adlib instrument data from file SAMPLE.AD (OPL3: SAMPLE.OPL) + Common::String timbreFilename; + Common::SeekableReadStream *timbreStream = nullptr; + + bool preferOPL3 = false; + + Common::File *fileStream = new Common::File(); + uint32 fileSize = 0; + uint32 fileDataOffset = 0; + uint32 fileDataLeft = 0; + + + uint32 streamSize = 0; + byte *streamDataPtr = nullptr; + + byte curBankId = 0; + byte curPatchId = 0; + + InstrumentEntry *instrumentTablePtr = nullptr; + uint16 instrumentTableCount = 0; + InstrumentEntry *instrumentPtr = nullptr; + uint32 instrumentOffset = 0; + uint16 instrumentDataSize = 0; + + // Logic: + // We prefer OPL3 timbre data in case OPL3 is available in ScummVM + // If it's not or OPL3 timbre data is not available, we go for AdLib timbre data + // And if OPL3 is not available in ScummVM and also AdLib timbre data is not available, + // we then still go for OPL3 timbre data. + // + // Note: for most games OPL3 timbre data + AdLib timbre data is the same. + // And at least in theory we should still be able to use OPL3 timbre data even for AdLib. + // However there is a special OPL3-specific timbre format, which is currently not supported. + // In this case the error message "unsupported instrument size" should appear. I haven't found + // a game that uses it, which is why I haven't implemented it yet. + + if (OPL::Config::detect(OPL::Config::kOpl3) >= 0) { + // OPL3 available, prefer OPL3 timbre data because of this + preferOPL3 = true; + } + + // Check if streams were passed to us and select one of them + if ((streamAdLib) || (streamOPL3)) { + // At least one stream was passed by caller + if (preferOPL3) { + // Prefer OPL3 timbre stream in case OPL3 is available + timbreStream = streamOPL3; + } + if (!timbreStream) { + // Otherwise prefer AdLib timbre stream first + if (streamAdLib) { + timbreStream = streamAdLib; + } else { + // If not available, use OPL3 timbre stream + if (streamOPL3) { + timbreStream = streamOPL3; + } + } + } + } + + // Now check if any filename was passed to us + if ((!filenameAdLib.empty()) || (!filenameOPL3.empty())) { + // If that's the case, check if one of those exists + if (preferOPL3) { + // OPL3 available + if (!filenameOPL3.empty()) { + if (fileStream->exists(filenameOPL3)) { + // If OPL3 available, prefer OPL3 timbre file in case file exists + timbreFilename = filenameOPL3; + } + } + if (timbreFilename.empty()) { + if (!filenameAdLib.empty()) { + if (fileStream->exists(filenameAdLib)) { + // otherwise use AdLib timbre file, if it exists + timbreFilename = filenameAdLib; + } + } + } + } else { + // OPL3 not available + // Prefer the AdLib one for now + if (!filenameAdLib.empty()) { + if (fileStream->exists(filenameAdLib)) { + // if AdLib file exists, use it + timbreFilename = filenameAdLib; + } + } + if (timbreFilename.empty()) { + if (!filenameOPL3.empty()) { + if (fileStream->exists(filenameOPL3)) { + // if OPL3 file exists, use it + timbreFilename = filenameOPL3; + } + } + } + } + if (timbreFilename.empty() && (!timbreStream)) { + // If none of them exists and also no stream was passed, we can't do anything about it + if (!filenameAdLib.empty()) { + if (!filenameOPL3.empty()) { + error("MILES-ADLIB: could not open timbre file (%s or %s)", filenameAdLib.c_str(), filenameOPL3.c_str()); + } else { + error("MILES-ADLIB: could not open timbre file (%s)", filenameAdLib.c_str()); + } + } else { + error("MILES-ADLIB: could not open timbre file (%s)", filenameOPL3.c_str()); + } + } + } + + if (!timbreFilename.empty()) { + // Filename was passed to us and file exists (this is the common case for most games) + // We prefer this situation + + if (!fileStream->open(timbreFilename)) + error("MILES-ADLIB: could not open timbre file (%s)", timbreFilename.c_str()); + + streamSize = fileStream->size(); + + streamDataPtr = new byte[streamSize]; + + if (fileStream->read(streamDataPtr, streamSize) != streamSize) + error("MILES-ADLIB: error while reading timbre file (%s)", timbreFilename.c_str()); + fileStream->close(); + + } else if (timbreStream) { + // Timbre data was passed directly (possibly read from resource file by caller) + // Currently used by "Amazon Guardians of Eden", "Simon 2" and "Return To Zork" + streamSize = timbreStream->size(); + + streamDataPtr = new byte[streamSize]; + + if (timbreStream->read(streamDataPtr, streamSize) != streamSize) + error("MILES-ADLIB: error while reading timbre stream"); + + } else { + error("MILES-ADLIB: timbre filenames nor timbre stream were passed"); + } + + delete fileStream; + + // File is like this: + // [patch:BYTE] [bank:BYTE] [patchoffset:UINT32] + // ... + // until patch + bank are both 0xFF, which signals end of header + + // First we check how many entries there are + fileDataOffset = 0; + fileDataLeft = streamSize; + while (1) { + if (fileDataLeft < 6) + error("MILES-ADLIB: unexpected EOF in instrument file"); + + curPatchId = streamDataPtr[fileDataOffset++]; + curBankId = streamDataPtr[fileDataOffset++]; + + if ((curBankId == 0xFF) && (curPatchId == 0xFF)) + break; + + fileDataOffset += 4; // skip over offset + instrumentTableCount++; + } + + if (instrumentTableCount == 0) + error("MILES-ADLIB: no instruments in instrument file"); + + // Allocate space for instruments + instrumentTablePtr = new InstrumentEntry[instrumentTableCount]; + + // Now actually read all entries + instrumentPtr = instrumentTablePtr; + + fileDataOffset = 0; + fileDataLeft = fileSize; + while (1) { + curPatchId = streamDataPtr[fileDataOffset++]; + curBankId = streamDataPtr[fileDataOffset++]; + + if ((curBankId == 0xFF) && (curPatchId == 0xFF)) + break; + + instrumentOffset = READ_LE_UINT32(streamDataPtr + fileDataOffset); + fileDataOffset += 4; + + instrumentPtr->bankId = curBankId; + instrumentPtr->patchId = curPatchId; + + instrumentDataSize = READ_LE_UINT16(streamDataPtr + instrumentOffset); + if (instrumentDataSize != 14) + error("MILES-ADLIB: unsupported instrument size"); + + instrumentPtr->transposition = (signed char)streamDataPtr[instrumentOffset + 2]; + instrumentPtr->reg20op1 = streamDataPtr[instrumentOffset + 3]; + instrumentPtr->reg40op1 = streamDataPtr[instrumentOffset + 4]; + instrumentPtr->reg60op1 = streamDataPtr[instrumentOffset + 5]; + instrumentPtr->reg80op1 = streamDataPtr[instrumentOffset + 6]; + instrumentPtr->regE0op1 = streamDataPtr[instrumentOffset + 7]; + instrumentPtr->regC0 = streamDataPtr[instrumentOffset + 8]; + instrumentPtr->reg20op2 = streamDataPtr[instrumentOffset + 9]; + instrumentPtr->reg40op2 = streamDataPtr[instrumentOffset + 10]; + instrumentPtr->reg60op2 = streamDataPtr[instrumentOffset + 11]; + instrumentPtr->reg80op2 = streamDataPtr[instrumentOffset + 12]; + instrumentPtr->regE0op2 = streamDataPtr[instrumentOffset + 13]; + + // Instrument read, next instrument please + instrumentPtr++; + } + + // Free instrument file/stream data + delete[] streamDataPtr; + + return new MidiDriver_Miles_AdLib(instrumentTablePtr, instrumentTableCount); +} + +} // End of namespace Audio diff --git a/audio/miles_mt32.cpp b/audio/miles_mt32.cpp new file mode 100644 index 0000000000..dff863f119 --- /dev/null +++ b/audio/miles_mt32.cpp @@ -0,0 +1,912 @@ +/* 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 "audio/miles.h" + +#include "common/config-manager.h" +#include "common/file.h" +#include "common/mutex.h" +#include "common/system.h" +#include "common/textconsole.h" + +namespace Audio { + +// Miles Audio MT32 driver +// + +#define MILES_MT32_PATCHES_COUNT 128 +#define MILES_MT32_CUSTOMTIMBRE_COUNT 64 + +#define MILES_MT32_TIMBREBANK_STANDARD_ROLAND 0 +#define MILES_MT32_TIMBREBANK_MELODIC_MODULE 127 + +#define MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE 14 +#define MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE 58 +#define MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT 4 +#define MILES_MT32_PATCHDATA_TOTAL_SIZE (MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + (MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE * MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT)) + +#define MILES_MT32_SYSEX_TERMINATOR 0xFF + +struct MilesMT32InstrumentEntry { + byte bankId; + byte patchId; + byte commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE + 1]; + byte partialParameters[MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE + 1]; +}; + +const byte milesMT32SysExResetParameters[] = { + 0x01, MILES_MT32_SYSEX_TERMINATOR +}; + +const byte milesMT32SysExChansSetup[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, MILES_MT32_SYSEX_TERMINATOR +}; + +const byte milesMT32SysExPartialReserveTable[] = { + 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x03, 0x04, 0x04, MILES_MT32_SYSEX_TERMINATOR +}; + +const byte milesMT32SysExInitReverb[] = { + 0x00, 0x03, 0x02, MILES_MT32_SYSEX_TERMINATOR // Reverb mode 0, reverb time 3, reverb level 2 +}; + +class MidiDriver_Miles_MT32 : public MidiDriver { +public: + MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount); + virtual ~MidiDriver_Miles_MT32(); + + // MidiDriver + int open(); + void close(); + bool isOpen() const { return _isOpen; } + + void send(uint32 b); + + MidiChannel *allocateChannel() { + if (_driver) + return _driver->allocateChannel(); + return NULL; + } + MidiChannel *getPercussionChannel() { + if (_driver) + return _driver->getPercussionChannel(); + return NULL; + } + + void setTimerCallback(void *timer_param, Common::TimerManager::TimerProc timer_proc) { + if (_driver) + _driver->setTimerCallback(timer_param, timer_proc); + } + + uint32 getBaseTempo() { + if (_driver) { + return _driver->getBaseTempo(); + } + return 1000000 / _baseFreq; + } + +protected: + Common::Mutex _mutex; + MidiDriver *_driver; + bool _MT32; + bool _nativeMT32; + + bool _isOpen; + int _baseFreq; + +public: + void processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize); + +private: + void resetMT32(); + + void MT32SysEx(const uint32 targetAddress, const byte *dataPtr); + + uint32 calculateSysExTargetAddress(uint32 baseAddress, uint32 index); + + void writeRhythmSetup(byte note, byte customTimbreId); + void writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId); + void writePatchByte(byte patchId, byte index, byte patchValue); + void writeToSystemArea(byte index, byte value); + + void controlChange(byte midiChannel, byte controllerNumber, byte controllerValue); + void programChange(byte midiChannel, byte patchId); + + const MilesMT32InstrumentEntry *searchCustomInstrument(byte patchBank, byte patchId); + int16 searchCustomTimbre(byte patchBank, byte patchId); + + void setupPatch(byte patchBank, byte patchId); + int16 installCustomTimbre(byte patchBank, byte patchId); + +private: + struct MidiChannelEntry { + byte currentPatchBank; + byte currentPatchId; + + bool usingCustomTimbre; + byte currentCustomTimbreId; + + MidiChannelEntry() : currentPatchBank(0), + currentPatchId(0), + usingCustomTimbre(false), + currentCustomTimbreId(0) { } + }; + + struct MidiCustomTimbreEntry { + bool used; + bool protectionEnabled; + byte currentPatchBank; + byte currentPatchId; + + uint32 lastUsedNoteCounter; + + MidiCustomTimbreEntry() : used(false), + protectionEnabled(false), + currentPatchBank(0), + currentPatchId(0), + lastUsedNoteCounter(0) {} + }; + + struct MilesMT32SysExQueueEntry { + uint32 targetAddress; + byte dataPos; + byte data[MILES_CONTROLLER_SYSEX_QUEUE_SIZE + 1]; // 1 extra byte for terminator + + MilesMT32SysExQueueEntry() : targetAddress(0), + dataPos(0) { + memset(data, 0, sizeof(data)); + } + }; + + // stores information about all MIDI channels + MidiChannelEntry _midiChannels[MILES_MIDI_CHANNEL_COUNT]; + + // stores information about all custom timbres + MidiCustomTimbreEntry _customTimbres[MILES_MT32_CUSTOMTIMBRE_COUNT]; + + byte _patchesBank[MILES_MT32_PATCHES_COUNT]; + + // holds all instruments + MilesMT32InstrumentEntry *_instrumentTablePtr; + uint16 _instrumentTableCount; + + uint32 _noteCounter; // used to figure out, which timbres are outdated + + // SysEx Queues + MilesMT32SysExQueueEntry _sysExQueues[MILES_CONTROLLER_SYSEX_QUEUE_COUNT]; +}; + +MidiDriver_Miles_MT32::MidiDriver_Miles_MT32(MilesMT32InstrumentEntry *instrumentTablePtr, uint16 instrumentTableCount) { + _instrumentTablePtr = instrumentTablePtr; + _instrumentTableCount = instrumentTableCount; + + _driver = NULL; + _isOpen = false; + _MT32 = false; + _nativeMT32 = false; + _baseFreq = 250; + + _noteCounter = 0; + + memset(_patchesBank, 0, sizeof(_patchesBank)); +} + +MidiDriver_Miles_MT32::~MidiDriver_Miles_MT32() { + Common::StackLock lock(_mutex); + if (_driver) { + _driver->setTimerCallback(0, 0); + _driver->close(); + delete _driver; + } + _driver = NULL; +} + +int MidiDriver_Miles_MT32::open() { + assert(!_driver); + + // Setup midi driver + MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_PREFER_MT32); + MusicType musicType = MidiDriver::getMusicType(dev); + + switch (musicType) { + case MT_MT32: + _nativeMT32 = true; + break; + case MT_GM: + if (ConfMan.getBool("native_mt32")) { + _nativeMT32 = true; + } + break; + default: + break; + } + + if (!_nativeMT32) { + error("MILES-MT32: non-mt32 currently not supported!"); + } + + _driver = MidiDriver::createMidi(dev); + if (!_driver) + return 255; + + if (_nativeMT32) + _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + + int ret = _driver->open(); + if (ret) + return ret; + + if (_nativeMT32) { + _driver->sendMT32Reset(); + + resetMT32(); + } + + return 0; +} + +void MidiDriver_Miles_MT32::close() { + if (_driver) { + _driver->close(); + } +} + +void MidiDriver_Miles_MT32::resetMT32() { + // reset all internal parameters / patches + MT32SysEx(0x7F0000, milesMT32SysExResetParameters); + + // init part/channel assignments + MT32SysEx(0x10000D, milesMT32SysExChansSetup); + + // partial reserve table + MT32SysEx(0x100004, milesMT32SysExPartialReserveTable); + + // init reverb + MT32SysEx(0x100001, milesMT32SysExInitReverb); +} + +void MidiDriver_Miles_MT32::MT32SysEx(const uint32 targetAddress, const byte *dataPtr) { + byte sysExMessage[270]; + uint16 sysExPos = 0; + byte sysExByte = 0; + uint16 sysExChecksum = 0; + + memset(&sysExMessage, 0, sizeof(sysExMessage)); + + sysExMessage[0] = 0x41; // Roland + sysExMessage[1] = 0x10; + sysExMessage[2] = 0x16; // Model MT32 + sysExMessage[3] = 0x12; // Command DT1 + + sysExChecksum = 0; + + sysExMessage[4] = (targetAddress >> 16) & 0xFF; + sysExMessage[5] = (targetAddress >> 8) & 0xFF; + sysExMessage[6] = targetAddress & 0xFF; + + for (byte targetAddressByte = 4; targetAddressByte < 7; targetAddressByte++) { + assert(sysExMessage[targetAddressByte] < 0x80); // security check + sysExChecksum -= sysExMessage[targetAddressByte]; + } + + sysExPos = 7; + while (1) { + sysExByte = *dataPtr++; + if (sysExByte == MILES_MT32_SYSEX_TERMINATOR) + break; // Message done + + assert(sysExPos < sizeof(sysExMessage)); + assert(sysExByte < 0x80); // security check + sysExMessage[sysExPos++] = sysExByte; + sysExChecksum -= sysExByte; + } + + // Calculate checksum + assert(sysExPos < sizeof(sysExMessage)); + sysExMessage[sysExPos++] = sysExChecksum & 0x7f; + + // Send SysEx + _driver->sysEx(sysExMessage, sysExPos); + + // Wait the time it takes to send the SysEx data + uint32 delay = (sysExPos + 2) * 1000 / 3125; + + // Plus an additional delay for the MT-32 rev00 + if (_nativeMT32) + delay += 40; + + g_system->delayMillis(delay); +} + +// MIDI messages can be found at http://www.midi.org/techspecs/midimessages.php +void MidiDriver_Miles_MT32::send(uint32 b) { + byte command = b & 0xf0; + byte midiChannel = b & 0xf; + byte op1 = (b >> 8) & 0xff; + byte op2 = (b >> 16) & 0xff; + + switch (command) { + case 0x80: // note off + case 0x90: // note on + case 0xa0: // Polyphonic key pressure (aftertouch) + case 0xd0: // Channel pressure (aftertouch) + case 0xe0: // pitch bend change + _noteCounter++; + if (_midiChannels[midiChannel].usingCustomTimbre) { + // Remember that this timbre got used now + _customTimbres[_midiChannels[midiChannel].currentCustomTimbreId].lastUsedNoteCounter = _noteCounter; + } + _driver->send(b); + break; + case 0xb0: // Control change + controlChange(midiChannel, op1, op2); + break; + case 0xc0: // Program Change + programChange(midiChannel, op1); + break; + case 0xf0: // SysEx + warning("MILES-MT32: SysEx: %x", b); + break; + default: + warning("MILES-MT32: Unknown event %02x", command); + } +} + +void MidiDriver_Miles_MT32::controlChange(byte midiChannel, byte controllerNumber, byte controllerValue) { + byte channelPatchId = 0; + byte channelCustomTimbreId = 0; + + switch (controllerNumber) { + case MILES_CONTROLLER_SELECT_PATCH_BANK: + _midiChannels[midiChannel].currentPatchBank = controllerValue; + return; + + case MILES_CONTROLLER_PATCH_REVERB: + channelPatchId = _midiChannels[midiChannel].currentPatchId; + + writePatchByte(channelPatchId, 6, controllerValue); + _driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change + return; + + case MILES_CONTROLLER_PATCH_BENDER: + channelPatchId = _midiChannels[midiChannel].currentPatchId; + + writePatchByte(channelPatchId, 4, controllerValue); + _driver->send(0xC0 | midiChannel | (channelPatchId << 8)); // execute program change + return; + + case MILES_CONTROLLER_REVERB_MODE: + writeToSystemArea(1, controllerValue); + return; + + case MILES_CONTROLLER_REVERB_TIME: + writeToSystemArea(2, controllerValue); + return; + + case MILES_CONTROLLER_REVERB_LEVEL: + writeToSystemArea(3, controllerValue); + return; + + case MILES_CONTROLLER_RHYTHM_KEY_TIMBRE: + if (_midiChannels[midiChannel].usingCustomTimbre) { + // custom timbre is set on current channel + writeRhythmSetup(controllerValue, _midiChannels[midiChannel].currentCustomTimbreId); + } + return; + + case MILES_CONTROLLER_PROTECT_TIMBRE: + if (_midiChannels[midiChannel].usingCustomTimbre) { + // custom timbre set on current channel + channelCustomTimbreId = _midiChannels[midiChannel].currentCustomTimbreId; + if (controllerValue >= 64) { + // enable protection + _customTimbres[channelCustomTimbreId].protectionEnabled = true; + } else { + // disable protection + _customTimbres[channelCustomTimbreId].protectionEnabled = false; + } + } + return; + + default: + break; + } + + if ((controllerNumber >= MILES_CONTROLLER_SYSEX_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_SYSEX_RANGE_END)) { + // send SysEx + byte sysExQueueNr = 0; + + // figure out which queue is accessed + controllerNumber -= MILES_CONTROLLER_SYSEX_RANGE_BEGIN; + while (controllerNumber > MILES_CONTROLLER_SYSEX_COMMAND_SEND) { + sysExQueueNr++; + controllerNumber -= (MILES_CONTROLLER_SYSEX_COMMAND_SEND + 1); + } + assert(sysExQueueNr < MILES_CONTROLLER_SYSEX_QUEUE_COUNT); + + byte sysExPos = _sysExQueues[sysExQueueNr].dataPos; + bool sysExSend = false; + + switch(controllerNumber) { + case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS1: + _sysExQueues[sysExQueueNr].targetAddress &= 0x00FFFF; + _sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 16); + break; + case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS2: + _sysExQueues[sysExQueueNr].targetAddress &= 0xFF00FF; + _sysExQueues[sysExQueueNr].targetAddress |= (controllerValue << 8); + break; + case MILES_CONTROLLER_SYSEX_COMMAND_ADDRESS3: + _sysExQueues[sysExQueueNr].targetAddress &= 0xFFFF00; + _sysExQueues[sysExQueueNr].targetAddress |= controllerValue; + break; + case MILES_CONTROLLER_SYSEX_COMMAND_DATA: + if (sysExPos < MILES_CONTROLLER_SYSEX_QUEUE_SIZE) { + // Space left? put current byte into queue + _sysExQueues[sysExQueueNr].data[sysExPos] = controllerValue; + sysExPos++; + _sysExQueues[sysExQueueNr].dataPos = sysExPos; + if (sysExPos >= MILES_CONTROLLER_SYSEX_QUEUE_SIZE) { + // overflow? -> send it now + sysExSend = true; + } + } + break; + case MILES_CONTROLLER_SYSEX_COMMAND_SEND: + sysExSend = true; + break; + default: + assert(0); + } + + if (sysExSend) { + if (sysExPos > 0) { + // data actually available? -> send it + _sysExQueues[sysExQueueNr].data[sysExPos] = MILES_MT32_SYSEX_TERMINATOR; // put terminator + + // Execute SysEx + MT32SysEx(_sysExQueues[sysExQueueNr].targetAddress, _sysExQueues[sysExQueueNr].data); + + // adjust target address to point at the end of the current data + _sysExQueues[sysExQueueNr].targetAddress += sysExPos; + // reset queue data buffer + _sysExQueues[sysExQueueNr].dataPos = 0; + } + } + return; + } + + if ((controllerNumber >= MILES_CONTROLLER_XMIDI_RANGE_BEGIN) && (controllerNumber <= MILES_CONTROLLER_XMIDI_RANGE_END)) { + // XMIDI controllers? ignore those + return; + } + + _driver->send(0xB0 | midiChannel | (controllerNumber << 8) | (controllerValue << 16)); +} + +void MidiDriver_Miles_MT32::programChange(byte midiChannel, byte patchId) { + byte channelPatchBank = _midiChannels[midiChannel].currentPatchBank; + byte activePatchBank = _patchesBank[patchId]; + + //warning("patch channel %d, patch %x, bank %x", midiChannel, patchId, channelPatchBank); + + // remember patch id for the current MIDI-channel + _midiChannels[midiChannel].currentPatchId = patchId; + + if (channelPatchBank != activePatchBank) { + // associate patch with timbre + setupPatch(channelPatchBank, patchId); + } + + // If this is a custom patch, remember customTimbreId + int16 customTimbre = searchCustomTimbre(channelPatchBank, patchId); + if (customTimbre >= 0) { + _midiChannels[midiChannel].usingCustomTimbre = true; + _midiChannels[midiChannel].currentCustomTimbreId = customTimbre; + } else { + _midiChannels[midiChannel].usingCustomTimbre = false; + } + + // Finally send program change to MT32 + _driver->send(0xC0 | midiChannel | (patchId << 8)); +} + +int16 MidiDriver_Miles_MT32::searchCustomTimbre(byte patchBank, byte patchId) { + byte customTimbreId = 0; + + for (customTimbreId = 0; customTimbreId < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreId++) { + if (_customTimbres[customTimbreId].used) { + if ((_customTimbres[customTimbreId].currentPatchBank == patchBank) && (_customTimbres[customTimbreId].currentPatchId == patchId)) { + return customTimbreId; + } + } + } + return -1; +} + +const MilesMT32InstrumentEntry *MidiDriver_Miles_MT32::searchCustomInstrument(byte patchBank, byte patchId) { + const MilesMT32InstrumentEntry *instrumentPtr = _instrumentTablePtr; + + for (uint16 instrumentNr = 0; instrumentNr < _instrumentTableCount; instrumentNr++) { + if ((instrumentPtr->bankId == patchBank) && (instrumentPtr->patchId == patchId)) + return instrumentPtr; + instrumentPtr++; + } + return NULL; +} + +void MidiDriver_Miles_MT32::setupPatch(byte patchBank, byte patchId) { + _patchesBank[patchId] = patchBank; + + if (patchBank) { + // non-built-in bank + int16 customTimbreId = searchCustomTimbre(patchBank, patchId); + if (customTimbreId >= 0) { + // now available? -> use this timbre + writePatchTimbre(patchId, 2, customTimbreId); // Group MEMORY + return; + } + } + + // for built-in bank (or timbres, that are not available) use default MT32 timbres + byte timbreId = patchId & 0x3F; + if (!(patchId & 0x40)) { + writePatchTimbre(patchId, 0, timbreId); // Group A + } else { + writePatchTimbre(patchId, 1, timbreId); // Group B + } +} + +void MidiDriver_Miles_MT32::processXMIDITimbreChunk(const byte *timbreListPtr, uint32 timbreListSize) { + uint16 timbreCount = 0; + uint32 expectedSize = 0; + const byte *timbreListSeeker = timbreListPtr; + + if (timbreListSize < 2) { + warning("MILES-MT32: XMIDI-TIMB chunk - not enough bytes in chunk"); + return; + } + + timbreCount = READ_LE_UINT16(timbreListPtr); + expectedSize = timbreCount * 2; + if (expectedSize > timbreListSize) { + warning("MILES-MT32: XMIDI-TIMB chunk - size mismatch"); + return; + } + + timbreListSeeker += 2; + + while (timbreCount) { + const byte patchId = *timbreListSeeker++; + const byte patchBank = *timbreListSeeker++; + int16 customTimbreId = 0; + + switch (patchBank) { + case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: + case MILES_MT32_TIMBREBANK_MELODIC_MODULE: + // ignore those 2 banks + break; + + default: + // Check, if this timbre was already loaded + customTimbreId = searchCustomTimbre(patchBank, patchId); + + if (customTimbreId < 0) { + // currently not loaded, try to install it + installCustomTimbre(patchBank, patchId); + } + } + timbreCount--; + } +} + +// +int16 MidiDriver_Miles_MT32::installCustomTimbre(byte patchBank, byte patchId) { + switch(patchBank) { + case MILES_MT32_TIMBREBANK_STANDARD_ROLAND: // Standard Roland MT32 bank + case MILES_MT32_TIMBREBANK_MELODIC_MODULE: // Reserved for melodic mode + return -1; + default: + break; + } + + // Original driver did a search for custom timbre here + // and in case it was found, it would call setup_patch() + // we are called from within setup_patch(), so this isn't needed + + int16 customTimbreId = -1; + int16 leastUsedTimbreId = -1; + uint32 leastUsedTimbreNoteCounter = _noteCounter; + const MilesMT32InstrumentEntry *instrumentPtr = NULL; + + // Check, if requested instrument is actually available + instrumentPtr = searchCustomInstrument(patchBank, patchId); + if (!instrumentPtr) { + warning("MILES-MT32: instrument not found during installCustomTimbre()"); + return -1; // not found -> bail out + } + + // Look for an empty timbre slot + // or get the least used non-protected slot + for (byte customTimbreNr = 0; customTimbreNr < MILES_MT32_CUSTOMTIMBRE_COUNT; customTimbreNr++) { + if (!_customTimbres[customTimbreNr].used) { + // found an empty slot -> use this one + customTimbreId = customTimbreNr; + break; + } else { + // used slot + if (!_customTimbres[customTimbreNr].protectionEnabled) { + // not protected + uint32 customTimbreNoteCounter = _customTimbres[customTimbreNr].lastUsedNoteCounter; + if (customTimbreNoteCounter < leastUsedTimbreNoteCounter) { + leastUsedTimbreId = customTimbreNr; + leastUsedTimbreNoteCounter = customTimbreNoteCounter; + } + } + } + } + + if (customTimbreId < 0) { + // no empty slot found, check if we got a least used non-protected slot + if (leastUsedTimbreId < 0) { + // everything is protected, bail out + warning("MILES-MT32: no non-protected timbre slots available during installCustomTimbre()"); + return -1; + } + customTimbreId = leastUsedTimbreId; + } + + // setup timbre slot + _customTimbres[customTimbreId].used = true; + _customTimbres[customTimbreId].currentPatchBank = patchBank; + _customTimbres[customTimbreId].currentPatchId = patchId; + _customTimbres[customTimbreId].lastUsedNoteCounter = _noteCounter; + _customTimbres[customTimbreId].protectionEnabled = false; + + uint32 targetAddress = 0x080000 | (customTimbreId << 9); + uint32 targetAddressCommon = targetAddress + 0x000000; + uint32 targetAddressPartial1 = targetAddress + 0x00000E; + uint32 targetAddressPartial2 = targetAddress + 0x000048; + uint32 targetAddressPartial3 = targetAddress + 0x000102; + uint32 targetAddressPartial4 = targetAddress + 0x00013C; + +#if 0 + byte parameterData[MILES_MT32_PATCHDATA_TOTAL_SIZE + 1]; + uint16 parameterDataPos = 0; + + memcpy(parameterData, instrumentPtr->commonParameter, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE; + memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[0], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[1], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[2], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + memcpy(parameterData + parameterDataPos, instrumentPtr->partialParameters[3], MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + parameterDataPos += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + parameterData[parameterDataPos] = MILES_MT32_SYSEX_TERMINATOR; + + MT32SysEx(targetAddressCommon, parameterData); +#endif + + // upload common parameter data + MT32SysEx(targetAddressCommon, instrumentPtr->commonParameter); + // upload partial parameter data + MT32SysEx(targetAddressPartial1, instrumentPtr->partialParameters[0]); + MT32SysEx(targetAddressPartial2, instrumentPtr->partialParameters[1]); + MT32SysEx(targetAddressPartial3, instrumentPtr->partialParameters[2]); + MT32SysEx(targetAddressPartial4, instrumentPtr->partialParameters[3]); + + setupPatch(patchBank, patchId); + + return customTimbreId; +} + +uint32 MidiDriver_Miles_MT32::calculateSysExTargetAddress(uint32 baseAddress, uint32 index) { + uint16 targetAddressLSB = baseAddress & 0xFF; + uint16 targetAddressKSB = (baseAddress >> 8) & 0xFF; + uint16 targetAddressMSB = (baseAddress >> 16) & 0xFF; + + // add index to it, but use 7-bit of the index for each byte + targetAddressLSB += (index & 0x7F); + targetAddressKSB += ((index >> 7) & 0x7F); + targetAddressMSB += ((index >> 14) & 0x7F); + + // adjust bytes, so that none of them is above or equal 0x80 + while (targetAddressLSB >= 0x80) { + targetAddressLSB -= 0x80; + targetAddressKSB++; + } + while (targetAddressKSB >= 0x80) { + targetAddressKSB -= 0x80; + targetAddressMSB++; + } + assert(targetAddressMSB < 0x80); + + // put everything together + return targetAddressLSB | (targetAddressKSB << 8) | (targetAddressMSB << 16); +} + +void MidiDriver_Miles_MT32::writeRhythmSetup(byte note, byte customTimbreId) { + byte sysExData[2]; + uint32 targetAddress = 0; + + targetAddress = calculateSysExTargetAddress(0x030110, ((note - 24) << 2)); + + sysExData[0] = customTimbreId; + sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator + + MT32SysEx(targetAddress, sysExData); +} + +void MidiDriver_Miles_MT32::writePatchTimbre(byte patchId, byte timbreGroup, byte timbreId) { + byte sysExData[3]; + uint32 targetAddress = 0; + + // write to patch memory (starts at 0x050000, each entry is 8 bytes) + targetAddress = calculateSysExTargetAddress(0x050000, patchId << 3); + + sysExData[0] = timbreGroup; // 0 - group A, 1 - group B, 2 - memory, 3 - rhythm + sysExData[1] = timbreId; // timbre number (0-63) + sysExData[2] = MILES_MT32_SYSEX_TERMINATOR; // terminator + + MT32SysEx(targetAddress, sysExData); +} + +void MidiDriver_Miles_MT32::writePatchByte(byte patchId, byte index, byte patchValue) { + byte sysExData[2]; + uint32 targetAddress = 0; + + targetAddress = calculateSysExTargetAddress(0x050000, (patchId << 3) + index); + + sysExData[0] = patchValue; + sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator + + MT32SysEx(targetAddress, sysExData); +} + +void MidiDriver_Miles_MT32::writeToSystemArea(byte index, byte value) { + byte sysExData[2]; + uint32 targetAddress = 0; + + targetAddress = calculateSysExTargetAddress(0x100000, index); + + sysExData[0] = value; + sysExData[1] = MILES_MT32_SYSEX_TERMINATOR; // terminator + + MT32SysEx(targetAddress, sysExData); +} + +MidiDriver *MidiDriver_Miles_MT32_create(const Common::String &instrumentDataFilename) { + MilesMT32InstrumentEntry *instrumentTablePtr = NULL; + uint16 instrumentTableCount = 0; + + if (!instrumentDataFilename.empty()) { + // Load MT32 instrument data from file SAMPLE.MT + Common::File *fileStream = new Common::File(); + uint32 fileSize = 0; + byte *fileDataPtr = NULL; + uint32 fileDataOffset = 0; + uint32 fileDataLeft = 0; + + byte curBankId = 0; + byte curPatchId = 0; + + MilesMT32InstrumentEntry *instrumentPtr = NULL; + uint32 instrumentOffset = 0; + uint16 instrumentDataSize = 0; + + if (!fileStream->open(instrumentDataFilename)) + error("MILES-MT32: could not open instrument file '%s'", instrumentDataFilename.c_str()); + + fileSize = fileStream->size(); + + fileDataPtr = new byte[fileSize]; + + if (fileStream->read(fileDataPtr, fileSize) != fileSize) + error("MILES-MT32: error while reading instrument file"); + fileStream->close(); + delete fileStream; + + // File is like this: + // [patch:BYTE] [bank:BYTE] [patchoffset:UINT32] + // ... + // until patch + bank are both 0xFF, which signals end of header + + // First we check how many entries there are + fileDataOffset = 0; + fileDataLeft = fileSize; + while (1) { + if (fileDataLeft < 6) + error("MILES-MT32: unexpected EOF in instrument file"); + + curPatchId = fileDataPtr[fileDataOffset++]; + curBankId = fileDataPtr[fileDataOffset++]; + + if ((curBankId == 0xFF) && (curPatchId == 0xFF)) + break; + + fileDataOffset += 4; // skip over offset + instrumentTableCount++; + } + + if (instrumentTableCount == 0) + error("MILES-MT32: no instruments in instrument file"); + + // Allocate space for instruments + instrumentTablePtr = new MilesMT32InstrumentEntry[instrumentTableCount]; + + // Now actually read all entries + instrumentPtr = instrumentTablePtr; + + fileDataOffset = 0; + fileDataLeft = fileSize; + while (1) { + curPatchId = fileDataPtr[fileDataOffset++]; + curBankId = fileDataPtr[fileDataOffset++]; + + if ((curBankId == 0xFF) && (curPatchId == 0xFF)) + break; + + instrumentOffset = READ_LE_UINT32(fileDataPtr + fileDataOffset); + fileDataOffset += 4; + + instrumentPtr->bankId = curBankId; + instrumentPtr->patchId = curPatchId; + + instrumentDataSize = READ_LE_UINT16(fileDataPtr + instrumentOffset); + if (instrumentDataSize != (MILES_MT32_PATCHDATA_TOTAL_SIZE + 2)) + error("MILES-MT32: unsupported instrument size"); + + instrumentOffset += 2; + // Copy common parameter data + memcpy(instrumentPtr->commonParameter, fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE); + instrumentPtr->commonParameter[MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator + instrumentOffset += MILES_MT32_PATCHDATA_COMMONPARAMETER_SIZE; + + // Copy partial parameter data + for (byte partialNr = 0; partialNr < MILES_MT32_PATCHDATA_PARTIALPARAMETERS_COUNT; partialNr++) { + memcpy(&instrumentPtr->partialParameters[partialNr], fileDataPtr + instrumentOffset, MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE); + instrumentPtr->partialParameters[partialNr][MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE] = MILES_MT32_SYSEX_TERMINATOR; // Terminator + instrumentOffset += MILES_MT32_PATCHDATA_PARTIALPARAMETER_SIZE; + } + + // Instrument read, next instrument please + instrumentPtr++; + } + + // Free instrument file data + delete[] fileDataPtr; + } + + return new MidiDriver_Miles_MT32(instrumentTablePtr, instrumentTableCount); +} + +void MidiDriver_Miles_MT32_processXMIDITimbreChunk(MidiDriver_BASE *driver, const byte *timbreListPtr, uint32 timbreListSize) { + MidiDriver_Miles_MT32 *driverMT32 = dynamic_cast<MidiDriver_Miles_MT32 *>(driver); + + if (driverMT32) { + driverMT32->processXMIDITimbreChunk(timbreListPtr, timbreListSize); + } +} + +} // End of namespace Audio diff --git a/audio/mods/protracker.cpp b/audio/mods/protracker.cpp index 82067f67bd..2578e9488a 100644 --- a/audio/mods/protracker.cpp +++ b/audio/mods/protracker.cpp @@ -219,11 +219,10 @@ void ProtrackerStream::updateRow() { case 0x0: if (exy) { _track[track].arpeggio = true; - if (note.period) { - _track[track].arpeggioNotes[0] = note.note; - _track[track].arpeggioNotes[1] = note.note + ex; - _track[track].arpeggioNotes[2] = note.note + ey; - } + byte trackNote = _module.periodToNote(_track[track].period); + _track[track].arpeggioNotes[0] = trackNote; + _track[track].arpeggioNotes[1] = trackNote + ex; + _track[track].arpeggioNotes[2] = trackNote + ey; } break; case 0x1: diff --git a/audio/module.mk b/audio/module.mk index 4e1c031c83..9e002d57ba 100644 --- a/audio/module.mk +++ b/audio/module.mk @@ -1,6 +1,7 @@ MODULE := audio MODULE_OBJS := \ + adlib.o \ audiostream.o \ fmopl.o \ mididrv.o \ @@ -9,11 +10,14 @@ MODULE_OBJS := \ midiparser_xmidi.o \ midiparser.o \ midiplayer.o \ + miles_adlib.o \ + miles_mt32.o \ mixer.o \ mpu401.o \ musicplugin.o \ null.o \ timestamp.o \ + decoders/3do.o \ decoders/aac.o \ decoders/adpcm.o \ decoders/aiff.o \ @@ -36,7 +40,6 @@ MODULE_OBJS := \ mods/rjp1.o \ mods/soundfx.o \ mods/tfmx.o \ - softsynth/adlib.o \ softsynth/cms.o \ softsynth/opl/dbopl.o \ softsynth/opl/dosbox.o \ @@ -55,6 +58,11 @@ MODULE_OBJS := \ softsynth/sid.o \ softsynth/wave6581.o +ifdef USE_ALSA +MODULE_OBJS += \ + alsa_opl.o +endif + ifndef USE_ARM_SOUND_ASM MODULE_OBJS += \ rate.o diff --git a/audio/softsynth/mt32/Analog.cpp b/audio/softsynth/mt32/Analog.cpp new file mode 100644 index 0000000000..8ac28e401a --- /dev/null +++ b/audio/softsynth/mt32/Analog.cpp @@ -0,0 +1,348 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +//#include <cstring> +#include "Analog.h" + +namespace MT32Emu { + +#if MT32EMU_USE_FLOAT_SAMPLES + +/* FIR approximation of the overall impulse response of the cascade composed of the sample & hold circuit and the low pass filter + * of the MT-32 first generation. + * The coefficients below are found by windowing the inverse DFT of the 1024 pin frequency response converted to the minimum phase. + * The frequency response of the LPF is computed directly, the effect of the S&H is approximated by multiplying the LPF frequency + * response by the corresponding sinc. Although, the LPF has DC gain of 3.2, we ignore this in the emulation and use normalised model. + * The peak gain of the normalised cascade appears about 1.7 near 11.8 kHz. Relative error doesn't exceed 1% for the frequencies + * below 12.5 kHz. In the higher frequency range, the relative error is below 8%. Peak error value is at 16 kHz. + */ +static const float COARSE_LPF_TAPS_MT32[] = { + 1.272473681f, -0.220267785f, -0.158039905f, 0.179603785f, -0.111484097f, 0.054137498f, -0.023518029f, 0.010997169f, -0.006935698f +}; + +// Similar approximation for new MT-32 and CM-32L/LAPC-I LPF. As the voltage controlled amplifier was introduced, LPF has unity DC gain. +// The peak gain value shifted towards higher frequencies and a bit higher about 1.83 near 13 kHz. +static const float COARSE_LPF_TAPS_CM32L[] = { + 1.340615635f, -0.403331694f, 0.036005517f, 0.066156844f, -0.069672532f, 0.049563806f, -0.031113416f, 0.019169774f, -0.012421368f +}; + +#else + +static const unsigned int COARSE_LPF_FRACTION_BITS = 14; + +// Integer versions of the FIRs above multiplied by (1 << 14) and rounded. +static const SampleEx COARSE_LPF_TAPS_MT32[] = { + 20848, -3609, -2589, 2943, -1827, 887, -385, 180, -114 +}; + +static const SampleEx COARSE_LPF_TAPS_CM32L[] = { + 21965, -6608, 590, 1084, -1142, 812, -510, 314, -204 +}; + +#endif + +/* Combined FIR that both approximates the impulse response of the analogue circuits of sample & hold and the low pass filter + * in the audible frequency range (below 20 kHz) and attenuates unwanted mirror spectra above 28 kHz as well. It is a polyphase + * filter intended for resampling the signal to 48 kHz yet for applying high frequency boost. + * As with the filter above, the analogue LPF frequency response is obtained for 1536 pin grid for range up to 96 kHz and multiplied + * by the corresponding sinc. The result is further squared, windowed and passed to generalised Parks-McClellan routine as a desired response. + * Finally, the minimum phase factor is found that's essentially the coefficients below. + * Relative error in the audible frequency range doesn't exceed 0.0006%, attenuation in the stopband is better than 100 dB. + * This level of performance makes it nearly bit-accurate for standard 16-bit sample resolution. + */ + +// FIR version for MT-32 first generation. +static const float ACCURATE_LPF_TAPS_MT32[] = { + 0.003429281f, 0.025929869f, 0.096587777f, 0.228884848f, 0.372413431f, 0.412386503f, 0.263980018f, + -0.014504962f, -0.237394528f, -0.257043496f, -0.103436603f, 0.063996095f, 0.124562333f, 0.083703206f, + 0.013921662f, -0.033475018f, -0.046239712f, -0.029310921f, 0.00126585f, 0.021060961f, 0.017925605f, + 0.003559874f, -0.005105248f, -0.005647917f, -0.004157918f, -0.002065664f, 0.00158747f, 0.003762585f, + 0.001867137f, -0.001090028f, -0.001433979f, -0.00022367f, 4.34308E-05f, -0.000247827f, 0.000157087f, + 0.000605823f, 0.000197317f, -0.000370511f, -0.000261202f, 9.96069E-05f, 9.85073E-05f, -5.28754E-05f, + -1.00912E-05f, 7.69943E-05f, 2.03162E-05f, -5.67967E-05f, -3.30637E-05f, 1.61958E-05f, 1.73041E-05f +}; + +// FIR version for new MT-32 and CM-32L/LAPC-I. +static const float ACCURATE_LPF_TAPS_CM32L[] = { + 0.003917452f, 0.030693861f, 0.116424199f, 0.275101674f, 0.43217361f, 0.431247894f, 0.183255659f, + -0.174955671f, -0.354240244f, -0.212401714f, 0.072259178f, 0.204655344f, 0.108336211f, -0.039099027f, + -0.075138174f, -0.026261906f, 0.00582663f, 0.003052193f, 0.00613657f, 0.017017951f, 0.008732535f, + -0.011027427f, -0.012933664f, 0.001158097f, 0.006765958f, 0.00046778f, -0.002191106f, 0.001561017f, + 0.001842871f, -0.001996876f, -0.002315836f, 0.000980965f, 0.001817454f, -0.000243272f, -0.000972848f, + 0.000149941f, 0.000498886f, -0.000204436f, -0.000347415f, 0.000142386f, 0.000249137f, -4.32946E-05f, + -0.000131231f, 3.88575E-07f, 4.48813E-05f, -1.31906E-06f, -1.03499E-05f, 7.71971E-06f, 2.86721E-06f +}; + +// According to the CM-64 PCB schematic, there is a difference in the values of the LPF entrance resistors for the reverb and non-reverb channels. +// This effectively results in non-unity LPF DC gain for the reverb channel of 0.68 while the LPF has unity DC gain for the LA32 output channels. +// In emulation, the reverb output gain is multiplied by this factor to compensate for the LPF gain difference. +static const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f; + +static const unsigned int OUTPUT_GAIN_FRACTION_BITS = 8; +static const float OUTPUT_GAIN_MULTIPLIER = float(1 << OUTPUT_GAIN_FRACTION_BITS); + +static const unsigned int COARSE_LPF_DELAY_LINE_LENGTH = 8; // Must be a power of 2 +static const unsigned int ACCURATE_LPF_DELAY_LINE_LENGTH = 16; // Must be a power of 2 +static const unsigned int ACCURATE_LPF_NUMBER_OF_PHASES = 3; // Upsampling factor +static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_REGULAR = 2; // Downsampling factor +static const unsigned int ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED = 1; // No downsampling +static const Bit32u ACCURATE_LPF_DELTAS_REGULAR[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 1, 0 }, { 1, 2, 1 } }; +static const Bit32u ACCURATE_LPF_DELTAS_OVERSAMPLED[][ACCURATE_LPF_NUMBER_OF_PHASES] = { { 0, 0, 0 }, { 1, 0, 0 }, { 1, 0, 1 } }; + +class AbstractLowPassFilter { +public: + static AbstractLowPassFilter &createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF); + static void muteRingBuffer(SampleEx *ringBuffer, unsigned int length); + + virtual ~AbstractLowPassFilter() {} + virtual SampleEx process(SampleEx sample) = 0; + virtual bool hasNextSample() const; + virtual unsigned int getOutputSampleRate() const; + virtual unsigned int estimateInSampleCount(unsigned int outSamples) const; + virtual void addPositionIncrement(unsigned int) {} +}; + +class NullLowPassFilter : public AbstractLowPassFilter { +public: + SampleEx process(SampleEx sample); +}; + +class CoarseLowPassFilter : public AbstractLowPassFilter { +private: + const SampleEx * const LPF_TAPS; + SampleEx ringBuffer[COARSE_LPF_DELAY_LINE_LENGTH]; + unsigned int ringBufferPosition; + +public: + CoarseLowPassFilter(bool oldMT32AnalogLPF); + SampleEx process(SampleEx sample); +}; + +class AccurateLowPassFilter : public AbstractLowPassFilter { +private: + const float * const LPF_TAPS; + const Bit32u (* const deltas)[ACCURATE_LPF_NUMBER_OF_PHASES]; + const unsigned int phaseIncrement; + const unsigned int outputSampleRate; + + SampleEx ringBuffer[ACCURATE_LPF_DELAY_LINE_LENGTH]; + unsigned int ringBufferPosition; + unsigned int phase; + +public: + AccurateLowPassFilter(bool oldMT32AnalogLPF, bool oversample); + SampleEx process(SampleEx sample); + bool hasNextSample() const; + unsigned int getOutputSampleRate() const; + unsigned int estimateInSampleCount(unsigned int outSamples) const; + void addPositionIncrement(unsigned int positionIncrement); +}; + +Analog::Analog(const AnalogOutputMode mode, const ControlROMFeatureSet *controlROMFeatures) : + leftChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, controlROMFeatures->isOldMT32AnalogLPF())), + rightChannelLPF(AbstractLowPassFilter::createLowPassFilter(mode, controlROMFeatures->isOldMT32AnalogLPF())), + synthGain(0), + reverbGain(0) +{} + +Analog::~Analog() { + delete &leftChannelLPF; + delete &rightChannelLPF; +} + +void Analog::process(Sample **outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, Bit32u outLength) { + if (outStream == NULL) { + leftChannelLPF.addPositionIncrement(outLength); + rightChannelLPF.addPositionIncrement(outLength); + return; + } + + while (0 < (outLength--)) { + SampleEx outSampleL; + SampleEx outSampleR; + + if (leftChannelLPF.hasNextSample()) { + outSampleL = leftChannelLPF.process(0); + outSampleR = rightChannelLPF.process(0); + } else { + SampleEx inSampleL = ((SampleEx)*(nonReverbLeft++) + (SampleEx)*(reverbDryLeft++)) * synthGain + (SampleEx)*(reverbWetLeft++) * reverbGain; + SampleEx inSampleR = ((SampleEx)*(nonReverbRight++) + (SampleEx)*(reverbDryRight++)) * synthGain + (SampleEx)*(reverbWetRight++) * reverbGain; + +#if !MT32EMU_USE_FLOAT_SAMPLES + inSampleL >>= OUTPUT_GAIN_FRACTION_BITS; + inSampleR >>= OUTPUT_GAIN_FRACTION_BITS; +#endif + + outSampleL = leftChannelLPF.process(inSampleL); + outSampleR = rightChannelLPF.process(inSampleR); + } + + *((*outStream)++) = Synth::clipSampleEx(outSampleL); + *((*outStream)++) = Synth::clipSampleEx(outSampleR); + } +} + +unsigned int Analog::getOutputSampleRate() const { + return leftChannelLPF.getOutputSampleRate(); +} + +Bit32u Analog::getDACStreamsLength(Bit32u outputLength) const { + return leftChannelLPF.estimateInSampleCount(outputLength); +} + +void Analog::setSynthOutputGain(float useSynthGain) { +#if MT32EMU_USE_FLOAT_SAMPLES + synthGain = useSynthGain; +#else + if (OUTPUT_GAIN_MULTIPLIER < useSynthGain) useSynthGain = OUTPUT_GAIN_MULTIPLIER; + synthGain = SampleEx(useSynthGain * OUTPUT_GAIN_MULTIPLIER); +#endif +} + +void Analog::setReverbOutputGain(float useReverbGain, bool mt32ReverbCompatibilityMode) { + if (!mt32ReverbCompatibilityMode) useReverbGain *= CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR; +#if MT32EMU_USE_FLOAT_SAMPLES + reverbGain = useReverbGain; +#else + if (OUTPUT_GAIN_MULTIPLIER < useReverbGain) useReverbGain = OUTPUT_GAIN_MULTIPLIER; + reverbGain = SampleEx(useReverbGain * OUTPUT_GAIN_MULTIPLIER); +#endif +} + +AbstractLowPassFilter &AbstractLowPassFilter::createLowPassFilter(AnalogOutputMode mode, bool oldMT32AnalogLPF) { + switch (mode) { + case AnalogOutputMode_COARSE: + return *new CoarseLowPassFilter(oldMT32AnalogLPF); + case AnalogOutputMode_ACCURATE: + return *new AccurateLowPassFilter(oldMT32AnalogLPF, false); + case AnalogOutputMode_OVERSAMPLED: + return *new AccurateLowPassFilter(oldMT32AnalogLPF, true); + default: + return *new NullLowPassFilter; + } +} + +void AbstractLowPassFilter::muteRingBuffer(SampleEx *ringBuffer, unsigned int length) { + +#if MT32EMU_USE_FLOAT_SAMPLES + + SampleEx *p = ringBuffer; + while (length--) { + *(p++) = 0.0f; + } + +#else + + memset(ringBuffer, 0, length * sizeof(SampleEx)); + +#endif + +} + +bool AbstractLowPassFilter::hasNextSample() const { + return false; +} + +unsigned int AbstractLowPassFilter::getOutputSampleRate() const { + return SAMPLE_RATE; +} + +unsigned int AbstractLowPassFilter::estimateInSampleCount(unsigned int outSamples) const { + return outSamples; +} + +SampleEx NullLowPassFilter::process(const SampleEx inSample) { + return inSample; +} + +CoarseLowPassFilter::CoarseLowPassFilter(bool oldMT32AnalogLPF) : + LPF_TAPS(oldMT32AnalogLPF ? COARSE_LPF_TAPS_MT32 : COARSE_LPF_TAPS_CM32L), + ringBufferPosition(0) +{ + muteRingBuffer(ringBuffer, COARSE_LPF_DELAY_LINE_LENGTH); +} + +SampleEx CoarseLowPassFilter::process(const SampleEx inSample) { + static const unsigned int DELAY_LINE_MASK = COARSE_LPF_DELAY_LINE_LENGTH - 1; + + SampleEx sample = LPF_TAPS[COARSE_LPF_DELAY_LINE_LENGTH] * ringBuffer[ringBufferPosition]; + ringBuffer[ringBufferPosition] = Synth::clipSampleEx(inSample); + + for (unsigned int i = 0; i < COARSE_LPF_DELAY_LINE_LENGTH; i++) { + sample += LPF_TAPS[i] * ringBuffer[(i + ringBufferPosition) & DELAY_LINE_MASK]; + } + + ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK; + +#if !MT32EMU_USE_FLOAT_SAMPLES + sample >>= COARSE_LPF_FRACTION_BITS; +#endif + + return sample; +} + +AccurateLowPassFilter::AccurateLowPassFilter(const bool oldMT32AnalogLPF, const bool oversample) : + LPF_TAPS(oldMT32AnalogLPF ? ACCURATE_LPF_TAPS_MT32 : ACCURATE_LPF_TAPS_CM32L), + deltas(oversample ? ACCURATE_LPF_DELTAS_OVERSAMPLED : ACCURATE_LPF_DELTAS_REGULAR), + phaseIncrement(oversample ? ACCURATE_LPF_PHASE_INCREMENT_OVERSAMPLED : ACCURATE_LPF_PHASE_INCREMENT_REGULAR), + outputSampleRate(SAMPLE_RATE * ACCURATE_LPF_NUMBER_OF_PHASES / phaseIncrement), + ringBufferPosition(0), + phase(0) +{ + muteRingBuffer(ringBuffer, ACCURATE_LPF_DELAY_LINE_LENGTH); +} + +SampleEx AccurateLowPassFilter::process(const SampleEx inSample) { + static const unsigned int DELAY_LINE_MASK = ACCURATE_LPF_DELAY_LINE_LENGTH - 1; + + float sample = (phase == 0) ? LPF_TAPS[ACCURATE_LPF_DELAY_LINE_LENGTH * ACCURATE_LPF_NUMBER_OF_PHASES] * ringBuffer[ringBufferPosition] : 0.0f; + if (!hasNextSample()) { + ringBuffer[ringBufferPosition] = inSample; + } + + for (unsigned int tapIx = phase, delaySampleIx = 0; delaySampleIx < ACCURATE_LPF_DELAY_LINE_LENGTH; delaySampleIx++, tapIx += ACCURATE_LPF_NUMBER_OF_PHASES) { + sample += LPF_TAPS[tapIx] * ringBuffer[(delaySampleIx + ringBufferPosition) & DELAY_LINE_MASK]; + } + + phase += phaseIncrement; + if (ACCURATE_LPF_NUMBER_OF_PHASES <= phase) { + phase -= ACCURATE_LPF_NUMBER_OF_PHASES; + ringBufferPosition = (ringBufferPosition - 1) & DELAY_LINE_MASK; + } + + return SampleEx(ACCURATE_LPF_NUMBER_OF_PHASES * sample); +} + +bool AccurateLowPassFilter::hasNextSample() const { + return phaseIncrement <= phase; +} + +unsigned int AccurateLowPassFilter::getOutputSampleRate() const { + return outputSampleRate; +} + +unsigned int AccurateLowPassFilter::estimateInSampleCount(unsigned int outSamples) const { + Bit32u cycleCount = outSamples / ACCURATE_LPF_NUMBER_OF_PHASES; + Bit32u remainder = outSamples - cycleCount * ACCURATE_LPF_NUMBER_OF_PHASES; + return cycleCount * phaseIncrement + deltas[remainder][phase]; +} + +void AccurateLowPassFilter::addPositionIncrement(const unsigned int positionIncrement) { + phase = (phase + positionIncrement * phaseIncrement) % ACCURATE_LPF_NUMBER_OF_PHASES; +} + +} diff --git a/audio/softsynth/mt32/Analog.h b/audio/softsynth/mt32/Analog.h new file mode 100644 index 0000000000..a48db72485 --- /dev/null +++ b/audio/softsynth/mt32/Analog.h @@ -0,0 +1,57 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MT32EMU_ANALOG_H +#define MT32EMU_ANALOG_H + +#include "mt32emu.h" + +namespace MT32Emu { + +class AbstractLowPassFilter; + +/* Analog class is dedicated to perform fair emulation of analogue circuitry of hardware units that is responsible + * for processing output signal after the DAC. It appears that the analogue circuit labeled "LPF" on the schematic + * also applies audible changes to the signal spectra. There is a significant boost of higher frequencies observed + * aside from quite poor attenuation of the mirror spectra above 16 kHz which is due to a relatively low filter order. + * + * As the final mixing of multiplexed output signal is performed after the DAC, this function is migrated here from Synth. + * Saying precisely, mixing is performed within the LPF as the entrance resistors are actually components of a LPF + * designed using the multiple feedback topology. Nevertheless, the schematic separates them. + */ +class Analog { +public: + Analog(AnalogOutputMode mode, const ControlROMFeatureSet *controlROMFeatures); + ~Analog(); + void process(Sample **outStream, const Sample *nonReverbLeft, const Sample *nonReverbRight, const Sample *reverbDryLeft, const Sample *reverbDryRight, const Sample *reverbWetLeft, const Sample *reverbWetRight, const Bit32u outLength); + unsigned int getOutputSampleRate() const; + Bit32u getDACStreamsLength(Bit32u outputLength) const; + void setSynthOutputGain(float synthGain); + void setReverbOutputGain(float reverbGain, bool mt32ReverbCompatibilityMode); + +private: + AbstractLowPassFilter &leftChannelLPF; + AbstractLowPassFilter &rightChannelLPF; + SampleEx synthGain; + SampleEx reverbGain; + + Analog(Analog &); +}; + +} + +#endif diff --git a/audio/softsynth/mt32/BReverbModel.cpp b/audio/softsynth/mt32/BReverbModel.cpp index 37b3e9670d..5e02db8f99 100644 --- a/audio/softsynth/mt32/BReverbModel.cpp +++ b/audio/softsynth/mt32/BReverbModel.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -//#include <memory.h> +//#include <cstring> #include "mt32emu.h" #include "BReverbModel.h" @@ -501,9 +501,9 @@ void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample * * Analysing of the algorithm suggests that the overflow is most probable when the combs output is added below. * So, despite this isn't actually accurate, we only add the check here for performance reasons. */ - Sample outSample = Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s((Bit32s)outL1 + Bit32s(outL1 >> 1)) + (Bit32s)outL2) + Bit32s(outL2 >> 1)) + (Bit32s)outL3); + Sample outSample = Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx((SampleEx)outL1 + SampleEx(outL1 >> 1)) + (SampleEx)outL2) + SampleEx(outL2 >> 1)) + (SampleEx)outL3); #else - Sample outSample = Synth::clipBit16s((Bit32s)outL1 + Bit32s(outL1 >> 1) + (Bit32s)outL2 + Bit32s(outL2 >> 1) + (Bit32s)outL3); + Sample outSample = Synth::clipSampleEx((SampleEx)outL1 + SampleEx(outL1 >> 1) + (SampleEx)outL2 + SampleEx(outL2 >> 1) + (SampleEx)outL3); #endif *(outLeft++) = weirdMul(outSample, wetLevel, 0xFF); } @@ -515,9 +515,9 @@ void BReverbModel::process(const Sample *inLeft, const Sample *inRight, Sample * Sample outSample = 1.5f * (outR1 + outR2) + outR3; #elif MT32EMU_BOSS_REVERB_PRECISE_MODE // See the note above for the left channel output. - Sample outSample = Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s(Synth::clipBit16s((Bit32s)outR1 + Bit32s(outR1 >> 1)) + (Bit32s)outR2) + Bit32s(outR2 >> 1)) + (Bit32s)outR3); + Sample outSample = Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx(Synth::clipSampleEx((SampleEx)outR1 + SampleEx(outR1 >> 1)) + (SampleEx)outR2) + SampleEx(outR2 >> 1)) + (SampleEx)outR3); #else - Sample outSample = Synth::clipBit16s((Bit32s)outR1 + Bit32s(outR1 >> 1) + (Bit32s)outR2 + Bit32s(outR2 >> 1) + (Bit32s)outR3); + Sample outSample = Synth::clipSampleEx((SampleEx)outR1 + SampleEx(outR1 >> 1) + (SampleEx)outR2 + SampleEx(outR2 >> 1) + (SampleEx)outR3); #endif *(outRight++) = weirdMul(outSample, wetLevel, 0xFF); } diff --git a/audio/softsynth/mt32/BReverbModel.h b/audio/softsynth/mt32/BReverbModel.h index 9b840900c3..764daf1a9e 100644 --- a/audio/softsynth/mt32/BReverbModel.h +++ b/audio/softsynth/mt32/BReverbModel.h @@ -95,7 +95,6 @@ class BReverbModel { const bool tapDelayMode; Bit32u dryAmp; Bit32u wetLevel; - void mute(); static const BReverbSettings &getCM32L_LAPCSettings(const ReverbMode mode); static const BReverbSettings &getMT32Settings(const ReverbMode mode); @@ -107,6 +106,7 @@ public: void open(); // May be called multiple times without an open() in between. void close(); + void mute(); void setParameters(Bit8u time, Bit8u level); void process(const Sample *inLeft, const Sample *inRight, Sample *outLeft, Sample *outRight, unsigned long numSamples); bool isActive() const; diff --git a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp index 9265d80c88..42d820ebad 100644 --- a/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32FloatWaveGenerator.cpp @@ -18,7 +18,7 @@ //#include <cmath> #include "mt32emu.h" #include "mmath.h" -#include "LA32FloatWaveGenerator.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/LA32Ramp.cpp b/audio/softsynth/mt32/LA32Ramp.cpp index 454612c842..2b31a330d2 100644 --- a/audio/softsynth/mt32/LA32Ramp.cpp +++ b/audio/softsynth/mt32/LA32Ramp.cpp @@ -50,8 +50,8 @@ We haven't fully explored: //#include <cmath> #include "mt32emu.h" -#include "LA32Ramp.h" #include "mmath.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/LA32WaveGenerator.cpp b/audio/softsynth/mt32/LA32WaveGenerator.cpp index 7ac7cc6aaa..765f75fa61 100644 --- a/audio/softsynth/mt32/LA32WaveGenerator.cpp +++ b/audio/softsynth/mt32/LA32WaveGenerator.cpp @@ -15,15 +15,15 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -//#include <cmath> -#include "mt32emu.h" -#include "mmath.h" -#include "LA32WaveGenerator.h" - #if MT32EMU_USE_FLOAT_SAMPLES #include "LA32FloatWaveGenerator.cpp" #else +//#include <cmath> +#include "mt32emu.h" +#include "mmath.h" +#include "internals.h" + namespace MT32Emu { static const Bit32u SINE_SEGMENT_RELATIVE_LENGTH = 1 << 18; diff --git a/audio/softsynth/mt32/MemoryRegion.h b/audio/softsynth/mt32/MemoryRegion.h new file mode 100644 index 0000000000..c0cb041e11 --- /dev/null +++ b/audio/softsynth/mt32/MemoryRegion.h @@ -0,0 +1,124 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MT32EMU_MEMORY_REGION_H +#define MT32EMU_MEMORY_REGION_H + +namespace MT32Emu { + +enum MemoryRegionType { + MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset +}; + +class MemoryRegion { +private: + Synth *synth; + Bit8u *realMemory; + Bit8u *maxTable; +public: + MemoryRegionType type; + Bit32u startAddr, entrySize, entries; + + MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) { + synth = useSynth; + realMemory = useRealMemory; + maxTable = useMaxTable; + type = useType; + startAddr = useStartAddr; + entrySize = useEntrySize; + entries = useEntries; + } + int lastTouched(Bit32u addr, Bit32u len) const { + return (offset(addr) + len - 1) / entrySize; + } + int firstTouchedOffset(Bit32u addr) const { + return offset(addr) % entrySize; + } + int firstTouched(Bit32u addr) const { + return offset(addr) / entrySize; + } + Bit32u regionEnd() const { + return startAddr + entrySize * entries; + } + bool contains(Bit32u addr) const { + return addr >= startAddr && addr < regionEnd(); + } + int offset(Bit32u addr) const { + return addr - startAddr; + } + Bit32u getClampedLen(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) + return regionEnd() - addr; + return len; + } + Bit32u next(Bit32u addr, Bit32u len) const { + if (addr + len > regionEnd()) { + return regionEnd() - addr; + } + return 0; + } + Bit8u getMaxValue(int off) const { + if (maxTable == NULL) + return 0xFF; + return maxTable[off % entrySize]; + } + Bit8u *getRealMemory() const { + return realMemory; + } + bool isReadable() const { + return getRealMemory() != NULL; + } + void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const; + void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const; +}; + +class PatchTempMemoryRegion : public MemoryRegion { +public: + PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {} +}; +class RhythmTempMemoryRegion : public MemoryRegion { +public: + RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {} +}; +class TimbreTempMemoryRegion : public MemoryRegion { +public: + TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {} +}; +class PatchesMemoryRegion : public MemoryRegion { +public: + PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {} +}; +class TimbresMemoryRegion : public MemoryRegion { +public: + TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {} +}; +class SystemMemoryRegion : public MemoryRegion { +public: + SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {} +}; +class DisplayMemoryRegion : public MemoryRegion { +public: + DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1) {} +}; +class ResetMemoryRegion : public MemoryRegion { +public: + ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {} +}; + +} + +#endif diff --git a/audio/softsynth/mt32/MidiEventQueue.h b/audio/softsynth/mt32/MidiEventQueue.h new file mode 100644 index 0000000000..b1948c5f8e --- /dev/null +++ b/audio/softsynth/mt32/MidiEventQueue.h @@ -0,0 +1,67 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MT32EMU_MIDI_EVENT_QUEUE_H +#define MT32EMU_MIDI_EVENT_QUEUE_H + +namespace MT32Emu { + +/** + * Used to safely store timestamped MIDI events in a local queue. + */ +struct MidiEvent { + Bit32u shortMessageData; + const Bit8u *sysexData; + Bit32u sysexLength; + Bit32u timestamp; + + ~MidiEvent(); + void setShortMessage(Bit32u shortMessageData, Bit32u timestamp); + void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); +}; + +/** + * Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it. + * It is intended to: + * - get rid of prerenderer while retaining graceful partial abortion + * - add fair emulation of the MIDI interface delays + * - extend the synth interface with the default implementation of a typical rendering loop. + * THREAD SAFETY: + * It is safe to use either in a single thread environment or when there are only two threads - one performs only reading + * and one performs only writing. More complicated usage requires external synchronisation. + */ +class MidiEventQueue { +private: + MidiEvent * const ringBuffer; + const Bit32u ringBufferMask; + volatile Bit32u startPosition; + volatile Bit32u endPosition; + +public: + MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE); // Must be a power of 2 + ~MidiEventQueue(); + void reset(); + bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp); + bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); + const MidiEvent *peekMidiEvent(); + void dropMidiEvent(); + bool isFull() const; +}; + +} + +#endif diff --git a/audio/softsynth/mt32/Part.cpp b/audio/softsynth/mt32/Part.cpp index d92473b5db..cffc3ed744 100644 --- a/audio/softsynth/mt32/Part.cpp +++ b/audio/softsynth/mt32/Part.cpp @@ -19,6 +19,7 @@ //#include <cstring> #include "mt32emu.h" +#include "internals.h" #include "PartialManager.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/Partial.cpp b/audio/softsynth/mt32/Partial.cpp index 7dcc6e945a..7348087509 100644 --- a/audio/softsynth/mt32/Partial.cpp +++ b/audio/softsynth/mt32/Partial.cpp @@ -21,6 +21,7 @@ #include "mt32emu.h" #include "mmath.h" +#include "internals.h" namespace MT32Emu { @@ -312,8 +313,8 @@ bool Partial::produceOutput(Sample *leftBuf, Sample *rightBuf, unsigned long len // Though, it is unknown whether this overflow is exploited somewhere. Sample leftOut = Sample((sample * leftPanValue) >> 8); Sample rightOut = Sample((sample * rightPanValue) >> 8); - *leftBuf = Synth::clipBit16s((Bit32s)*leftBuf + (Bit32s)leftOut); - *rightBuf = Synth::clipBit16s((Bit32s)*rightBuf + (Bit32s)rightOut); + *leftBuf = Synth::clipSampleEx((SampleEx)*leftBuf + (SampleEx)leftOut); + *rightBuf = Synth::clipSampleEx((SampleEx)*rightBuf + (SampleEx)rightOut); leftBuf++; rightBuf++; #endif diff --git a/audio/softsynth/mt32/PartialManager.cpp b/audio/softsynth/mt32/PartialManager.cpp index fe73087581..8ca6e4e3d7 100644 --- a/audio/softsynth/mt32/PartialManager.cpp +++ b/audio/softsynth/mt32/PartialManager.cpp @@ -18,6 +18,7 @@ //#include <cstring> #include "mt32emu.h" +#include "internals.h" #include "PartialManager.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/Poly.cpp b/audio/softsynth/mt32/Poly.cpp index e07ceb4231..badcd8fb96 100644 --- a/audio/softsynth/mt32/Poly.cpp +++ b/audio/softsynth/mt32/Poly.cpp @@ -16,6 +16,7 @@ */ #include "mt32emu.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/Poly.h b/audio/softsynth/mt32/Poly.h index 9c6431ce36..e2614369bb 100644 --- a/audio/softsynth/mt32/Poly.h +++ b/audio/softsynth/mt32/Poly.h @@ -21,6 +21,7 @@ namespace MT32Emu { class Part; +class Partial; enum PolyState { POLY_Playing, diff --git a/audio/softsynth/mt32/ROMInfo.cpp b/audio/softsynth/mt32/ROMInfo.cpp index eb9622620f..7c0127078b 100644 --- a/audio/softsynth/mt32/ROMInfo.cpp +++ b/audio/softsynth/mt32/ROMInfo.cpp @@ -21,8 +21,8 @@ namespace MT32Emu { static const ROMInfo *getKnownROMInfoFromList(unsigned int index) { - static const ControlROMFeatureSet MT32_COMPATIBLE(true); - static const ControlROMFeatureSet CM32L_COMPATIBLE(false); + static const ControlROMFeatureSet MT32_COMPATIBLE(true, true); + static const ControlROMFeatureSet CM32L_COMPATIBLE(false, false); // Known ROMs static const ROMInfo CTRL_MT32_V1_04 = {65536, "5a5cb5a77d7d55ee69657c2f870416daed52dea7", ROMInfo::Control, "ctrl_mt32_1_04", "MT-32 Control v1.04", ROMInfo::Full, NULL, &MT32_COMPATIBLE}; @@ -106,7 +106,6 @@ void ROMImage::freeROMImage(const ROMImage *romImage) { delete romImage; } - Common::File* ROMImage::getFile() const { return file; } @@ -115,11 +114,17 @@ const ROMInfo* ROMImage::getROMInfo() const { return romInfo; } -ControlROMFeatureSet::ControlROMFeatureSet(bool useDefaultReverbMT32Compatible) : defaultReverbMT32Compatible(useDefaultReverbMT32Compatible) { -} +ControlROMFeatureSet::ControlROMFeatureSet(bool useDefaultReverbMT32Compatible, bool useOldMT32AnalogLPF) : + defaultReverbMT32Compatible(useDefaultReverbMT32Compatible), + oldMT32AnalogLPF(useOldMT32AnalogLPF) +{} bool ControlROMFeatureSet::isDefaultReverbMT32Compatible() const { return defaultReverbMT32Compatible; } +bool ControlROMFeatureSet::isOldMT32AnalogLPF() const { + return oldMT32AnalogLPF; +} + } diff --git a/audio/softsynth/mt32/ROMInfo.h b/audio/softsynth/mt32/ROMInfo.h index cecbb6054f..4682620a15 100644 --- a/audio/softsynth/mt32/ROMInfo.h +++ b/audio/softsynth/mt32/ROMInfo.h @@ -77,10 +77,12 @@ public: struct ControlROMFeatureSet { private: unsigned int defaultReverbMT32Compatible : 1; + unsigned int oldMT32AnalogLPF : 1; public: - ControlROMFeatureSet(bool defaultReverbMT32Compatible); + ControlROMFeatureSet(bool defaultReverbMT32Compatible, bool oldMT32AnalogLPF); bool isDefaultReverbMT32Compatible() const; + bool isOldMT32AnalogLPF() const; }; } diff --git a/audio/softsynth/mt32/Structures.h b/audio/softsynth/mt32/Structures.h index 35dcee90d6..4dada3a847 100644 --- a/audio/softsynth/mt32/Structures.h +++ b/audio/softsynth/mt32/Structures.h @@ -31,19 +31,6 @@ namespace MT32Emu { #define MT32EMU_ALIGN_PACKED __attribute__((packed)) #endif -typedef unsigned int Bit32u; -typedef signed int Bit32s; -typedef unsigned short int Bit16u; -typedef signed short int Bit16s; -typedef unsigned char Bit8u; -typedef signed char Bit8s; - -#if MT32EMU_USE_FLOAT_SAMPLES -typedef float Sample; -#else -typedef Bit16s Sample; -#endif - // The following structures represent the MT-32's memory // Since sysex allows this memory to be written to in blocks of bytes, // we keep this packed so that we can copy data into the various @@ -184,7 +171,37 @@ struct MemParams { #pragma pack() #endif -struct ControlROMPCMStruct; +struct ControlROMMap { + Bit16u idPos; + Bit16u idLen; + const char *idBytes; + Bit16u pcmTable; // 4 * pcmCount bytes + Bit16u pcmCount; + Bit16u timbreAMap; // 128 bytes + Bit16u timbreAOffset; + bool timbreACompressed; + Bit16u timbreBMap; // 128 bytes + Bit16u timbreBOffset; + bool timbreBCompressed; + Bit16u timbreRMap; // 2 * timbreRCount bytes + Bit16u timbreRCount; + Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes + Bit16u rhythmSettingsCount; + Bit16u reserveSettings; // 9 bytes + Bit16u panSettings; // 8 bytes + Bit16u programSettings; // 8 bytes + Bit16u rhythmMaxTable; // 4 bytes + Bit16u patchMaxTable; // 16 bytes + Bit16u systemMaxTable; // 23 bytes + Bit16u timbreMaxTable; // 72 bytes +}; + +struct ControlROMPCMStruct { + Bit8u pos; + Bit8u len; + Bit8u pitchLSB; + Bit8u pitchMSB; +}; struct PCMWaveEntry { Bit32u addr; @@ -216,8 +233,6 @@ struct PatchCache { const TimbreParam::PartialParam *partialParam; }; -class Partial; // Forward reference for class defined in partial.h - } #endif diff --git a/audio/softsynth/mt32/Synth.cpp b/audio/softsynth/mt32/Synth.cpp index 3bff429875..6df7eb9e31 100644 --- a/audio/softsynth/mt32/Synth.cpp +++ b/audio/softsynth/mt32/Synth.cpp @@ -22,12 +22,19 @@ #include "mt32emu.h" #include "mmath.h" -#include "PartialManager.h" +#include "internals.h" + +#include "Analog.h" #include "BReverbModel.h" -#include "common/debug.h" +#include "MemoryRegion.h" +#include "MidiEventQueue.h" +#include "PartialManager.h" namespace MT32Emu { +// MIDI interface data transfer rate in samples. Used to simulate the transfer delay. +static const double MIDI_DATA_TRANSFER_RATE = (double)SAMPLE_RATE / 31250.0 * 8.0; + static const ControlROMMap ControlROMMaps[7] = { // ID IDc IDbytes PCMmap PCMc tmbrA tmbrAO, tmbrAC tmbrB tmbrBO, tmbrBC tmbrR trC rhythm rhyC rsrv panpot prog rhyMax patMax sysMax timMax {0x4014, 22, "\000 ver1.04 14 July 87 ", 0x3000, 128, 0x8000, 0x0000, false, 0xC000, 0x4000, false, 0x3200, 30, 0x73A6, 85, 0x57C7, 0x57E2, 0x57D0, 0x5252, 0x525E, 0x526E, 0x520A}, @@ -46,18 +53,15 @@ static inline void advanceStreamPosition(Sample *&stream, Bit32u posDelta) { } } -Bit8u Synth::calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum) { +Bit8u Synth::calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum) { + unsigned int checksum = -initChecksum; for (unsigned int i = 0; i < len; i++) { - checksum = checksum + data[i]; + checksum -= data[i]; } - checksum = checksum & 0x7f; - if (checksum) { - checksum = 0x80 - checksum; - } - return checksum; + return Bit8u(checksum & 0x7f); } -Synth::Synth(ReportHandler *useReportHandler) { +Synth::Synth(ReportHandler *useReportHandler) : mt32ram(*new MemParams()), mt32default(*new MemParams()) { isOpen = false; reverbOverridden = false; partialCount = DEFAULT_MAX_PARTIALS; @@ -75,6 +79,7 @@ Synth::Synth(ReportHandler *useReportHandler) { reverbModels[i] = NULL; } reverbModel = NULL; + analog = NULL; setDACInputMode(DACInputMode_NICE); setMIDIDelayMode(MIDIDelayMode_DELAY_SHORT_MESSAGES_ONLY); setOutputGain(1.0f); @@ -92,6 +97,8 @@ Synth::~Synth() { if (isDefaultReportHandler) { delete reportHandler; } + delete &mt32ram; + delete &mt32default; } void ReportHandler::showLCDMessage(const char *data) { @@ -126,7 +133,7 @@ void Synth::printDebug(const char *fmt, ...) { va_list ap; va_start(ap, fmt); #if MT32EMU_DEBUG_SAMPLESTAMPS > 0 - reportHandler->printDebug("[%u] ", renderedSampleCount); + reportHandler->printDebug("[%u] ", (char *)&renderedSampleCount); #endif reportHandler->printDebug(fmt, ap); va_end(ap); @@ -211,10 +218,7 @@ MIDIDelayMode Synth::getMIDIDelayMode() const { void Synth::setOutputGain(float newOutputGain) { if (newOutputGain < 0.0f) newOutputGain = -newOutputGain; outputGain = newOutputGain; -#if !MT32EMU_USE_FLOAT_SAMPLES - if (256.0f < newOutputGain) newOutputGain = 256.0f; - effectiveOutputGain = int(newOutputGain * 256.0f); -#endif + if (analog != NULL) analog->setSynthOutputGain(newOutputGain); } float Synth::getOutputGain() const { @@ -224,13 +228,7 @@ float Synth::getOutputGain() const { void Synth::setReverbOutputGain(float newReverbOutputGain) { if (newReverbOutputGain < 0.0f) newReverbOutputGain = -newReverbOutputGain; reverbOutputGain = newReverbOutputGain; - if (!isMT32ReverbCompatibilityMode()) newReverbOutputGain *= CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR; -#if MT32EMU_USE_FLOAT_SAMPLES - effectiveReverbOutputGain = newReverbOutputGain; -#else - if (256.0f < newReverbOutputGain) newReverbOutputGain = 256.0f; - effectiveReverbOutputGain = int(newReverbOutputGain * 256.0f); -#endif + if (analog != NULL) analog->setReverbOutputGain(newReverbOutputGain, isMT32ReverbCompatibilityMode()); } float Synth::getReverbOutputGain() const { @@ -393,7 +391,11 @@ bool Synth::initTimbres(Bit16u mapAddress, Bit16u offset, int count, int startTi return true; } -bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount) { +bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode) { + return open(controlROMImage, pcmROMImage, DEFAULT_MAX_PARTIALS, analogOutputMode); +} + +bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount, AnalogOutputMode analogOutputMode) { if (isOpen) { return false; } @@ -548,6 +550,10 @@ bool Synth::open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, u midiQueue = new MidiEventQueue(); + analog = new Analog(analogOutputMode, controlROMFeatures); + setOutputGain(outputGain); + setReverbOutputGain(reverbOutputGain); + isOpen = true; isEnabled = false; @@ -565,6 +571,9 @@ void Synth::close(bool forced) { delete midiQueue; midiQueue = NULL; + delete analog; + analog = NULL; + delete partialManager; partialManager = NULL; @@ -603,16 +612,37 @@ void Synth::flushMIDIQueue() { } } -void Synth::setMIDIEventQueueSize(Bit32u useSize) { - if (midiQueue != NULL) { - flushMIDIQueue(); - delete midiQueue; - midiQueue = new MidiEventQueue(useSize); +Bit32u Synth::setMIDIEventQueueSize(Bit32u useSize) { + static const Bit32u MAX_QUEUE_SIZE = (1 << 24); // This results in about 256 Mb - much greater than any reasonable value + + if (midiQueue == NULL) return 0; + flushMIDIQueue(); + + // Find a power of 2 that is >= useSize + Bit32u binarySize = 1; + if (useSize < MAX_QUEUE_SIZE) { + // Using simple linear search as this isn't time critical + while (binarySize < useSize) binarySize <<= 1; + } else { + binarySize = MAX_QUEUE_SIZE; } + delete midiQueue; + midiQueue = new MidiEventQueue(binarySize); + return binarySize; } Bit32u Synth::getShortMessageLength(Bit32u msg) { - if ((msg & 0xF0) == 0xF0) return 1; + if ((msg & 0xF0) == 0xF0) { + switch (msg & 0xFF) { + case 0xF1: + case 0xF3: + return 2; + case 0xF2: + return 3; + default: + return 1; + } + } // NOTE: This calculation isn't quite correct // as it doesn't consider the running status byte return ((msg & 0xE0) == 0xC0) ? 2 : 3; @@ -638,6 +668,7 @@ bool Synth::playMsg(Bit32u msg, Bit32u timestamp) { if (midiDelayMode != MIDIDelayMode_IMMEDIATE) { timestamp = addMIDIInterfaceDelay(getShortMessageLength(msg), timestamp); } + if (!isEnabled) isEnabled = true; return midiQueue->pushShortMessage(msg, timestamp); } @@ -650,16 +681,19 @@ bool Synth::playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp) { if (midiDelayMode == MIDIDelayMode_DELAY_ALL) { timestamp = addMIDIInterfaceDelay(len, timestamp); } + if (!isEnabled) isEnabled = true; return midiQueue->pushSysex(sysex, len, timestamp); } void Synth::playMsgNow(Bit32u msg) { - // FIXME: Implement active sensing + // NOTE: Active sense IS implemented in real hardware. However, realtime processing is clearly out of the library scope. + // It is assumed that realtime consumers of the library respond to these MIDI events as appropriate. + unsigned char code = (unsigned char)((msg & 0x0000F0) >> 4); unsigned char chan = (unsigned char)(msg & 0x00000F); unsigned char note = (unsigned char)((msg & 0x007F00) >> 8); unsigned char velocity = (unsigned char)((msg & 0x7F0000) >> 16); - isEnabled = true; + if (!isEnabled) isEnabled = true; //printDebug("Playing chan %d, code 0x%01x note: 0x%02x", chan, code, note); @@ -831,7 +865,7 @@ void Synth::playSysexWithoutHeader(unsigned char device, unsigned char command, printDebug("playSysexWithoutHeader: Message is too short (%d bytes)!", len); return; } - unsigned char checksum = calcSysexChecksum(sysex, len - 1, 0); + Bit8u checksum = calcSysexChecksum(sysex, len - 1); if (checksum != sysex[len - 1]) { printDebug("playSysexWithoutHeader: Message checksum is incorrect (provided: %02x, expected: %02x)!", sysex[len - 1], checksum); return; @@ -1410,9 +1444,8 @@ void MidiEvent::setSysex(const Bit8u *useSysexData, Bit32u useSysexLength, Bit32 memcpy(dstSysexData, useSysexData, sysexLength); } -MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize) : ringBufferSize(useRingBufferSize) { - ringBuffer = new MidiEvent[ringBufferSize]; - memset(ringBuffer, 0, ringBufferSize * sizeof(MidiEvent)); +MidiEventQueue::MidiEventQueue(Bit32u useRingBufferSize) : ringBuffer(new MidiEvent[useRingBufferSize]), ringBufferMask(useRingBufferSize - 1) { + memset(ringBuffer, 0, useRingBufferSize * sizeof(MidiEvent)); reset(); } @@ -1426,7 +1459,7 @@ void MidiEventQueue::reset() { } bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp) { - unsigned int newEndPosition = (endPosition + 1) % ringBufferSize; + Bit32u newEndPosition = (endPosition + 1) & ringBufferMask; // Is ring buffer full? if (startPosition == newEndPosition) return false; ringBuffer[endPosition].setShortMessage(shortMessageData, timestamp); @@ -1435,7 +1468,7 @@ bool MidiEventQueue::pushShortMessage(Bit32u shortMessageData, Bit32u timestamp) } bool MidiEventQueue::pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp) { - unsigned int newEndPosition = (endPosition + 1) % ringBufferSize; + Bit32u newEndPosition = (endPosition + 1) & ringBufferMask; // Is ring buffer full? if (startPosition == newEndPosition) return false; ringBuffer[endPosition].setSysex(sysexData, sysexLength, timestamp); @@ -1450,31 +1483,36 @@ const MidiEvent *MidiEventQueue::peekMidiEvent() { void MidiEventQueue::dropMidiEvent() { // Is ring buffer empty? if (startPosition != endPosition) { - startPosition = (startPosition + 1) % ringBufferSize; + startPosition = (startPosition + 1) & ringBufferMask; } } +bool MidiEventQueue::isFull() const { + return startPosition == ((endPosition + 1) & ringBufferMask); +} + +unsigned int Synth::getStereoOutputSampleRate() const { + return (analog == NULL) ? SAMPLE_RATE : analog->getOutputSampleRate(); +} + void Synth::render(Sample *stream, Bit32u len) { - Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN]; - Sample tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN]; - Sample tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; + if (!isEnabled) { + renderedSampleCount += analog->getDACStreamsLength(len); + analog->process(NULL, NULL, NULL, NULL, NULL, NULL, NULL, len); + muteSampleBuffer(stream, len << 1); + return; + } + + // As in AnalogOutputMode_ACCURATE mode output is upsampled, buffer size MAX_SAMPLES_PER_RUN is more than enough. + Sample tmpNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpNonReverbRight[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpReverbDryRight[MAX_SAMPLES_PER_RUN]; + Sample tmpReverbWetLeft[MAX_SAMPLES_PER_RUN], tmpReverbWetRight[MAX_SAMPLES_PER_RUN]; while (len > 0) { - Bit32u thisLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; - renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisLen); - for (Bit32u i = 0; i < thisLen; i++) { -#if MT32EMU_USE_FLOAT_SAMPLES - *(stream++) = tmpNonReverbLeft[i] + tmpReverbDryLeft[i] + tmpReverbWetLeft[i]; - *(stream++) = tmpNonReverbRight[i] + tmpReverbDryRight[i] + tmpReverbWetRight[i]; -#else - *(stream++) = clipBit16s((Bit32s)tmpNonReverbLeft[i] + (Bit32s)tmpReverbDryLeft[i] + (Bit32s)tmpReverbWetLeft[i]); - *(stream++) = clipBit16s((Bit32s)tmpNonReverbRight[i] + (Bit32s)tmpReverbDryRight[i] + (Bit32s)tmpReverbWetRight[i]); -#endif - } - len -= thisLen; + Bit32u thisPassLen = len > MAX_SAMPLES_PER_RUN ? MAX_SAMPLES_PER_RUN : len; + renderStreams(tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, analog->getDACStreamsLength(thisPassLen)); + analog->process(&stream, tmpNonReverbLeft, tmpNonReverbRight, tmpReverbDryLeft, tmpReverbDryRight, tmpReverbWetLeft, tmpReverbWetRight, thisPassLen); + len -= thisPassLen; } } @@ -1518,7 +1556,10 @@ void Synth::renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample // In GENERATION2 units, the output from LA32 goes to the Boss chip already bit-shifted. // In NICE mode, it's also better to increase volume before the reverb processing to preserve accuracy. void Synth::produceLA32Output(Sample *buffer, Bit32u len) { -#if !MT32EMU_USE_FLOAT_SAMPLES +#if MT32EMU_USE_FLOAT_SAMPLES + (void)buffer; + (void)len; +#else switch (dacInputMode) { case DACInputMode_GENERATION2: while (len--) { @@ -1528,7 +1569,7 @@ void Synth::produceLA32Output(Sample *buffer, Bit32u len) { break; case DACInputMode_NICE: while (len--) { - *buffer = clipBit16s(Bit32s(*buffer) << 1); + *buffer = clipSampleEx(SampleEx(*buffer) << 1); ++buffer; } break; @@ -1538,26 +1579,16 @@ void Synth::produceLA32Output(Sample *buffer, Bit32u len) { #endif } -void Synth::convertSamplesToOutput(Sample *buffer, Bit32u len, bool reverb) { - if (dacInputMode == DACInputMode_PURE) return; - +void Synth::convertSamplesToOutput(Sample *buffer, Bit32u len) { #if MT32EMU_USE_FLOAT_SAMPLES - float gain = reverb ? effectiveReverbOutputGain : outputGain; - while (len--) { - *(buffer++) *= gain; - } + (void)buffer; + (void)len; #else - int gain = reverb ? effectiveReverbOutputGain : effectiveOutputGain; if (dacInputMode == DACInputMode_GENERATION1) { while (len--) { - Bit32s target = Bit16s((*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE)); - *(buffer++) = clipBit16s((target * gain) >> 8); + *buffer = Sample((*buffer & 0x8000) | ((*buffer << 1) & 0x7FFE)); + ++buffer; } - return; - } - while (len--) { - *buffer = clipBit16s((Bit32s(*buffer) * gain) >> 8); - ++buffer; } #endif } @@ -1566,18 +1597,18 @@ void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sampl // Even if LA32 output isn't desired, we proceed anyway with temp buffers Sample tmpBufNonReverbLeft[MAX_SAMPLES_PER_RUN], tmpBufNonReverbRight[MAX_SAMPLES_PER_RUN]; if (nonReverbLeft == NULL) nonReverbLeft = tmpBufNonReverbLeft; - if (nonReverbLeft == NULL) nonReverbRight = tmpBufNonReverbRight; + if (nonReverbRight == NULL) nonReverbRight = tmpBufNonReverbRight; Sample tmpBufReverbDryLeft[MAX_SAMPLES_PER_RUN], tmpBufReverbDryRight[MAX_SAMPLES_PER_RUN]; if (reverbDryLeft == NULL) reverbDryLeft = tmpBufReverbDryLeft; if (reverbDryRight == NULL) reverbDryRight = tmpBufReverbDryRight; - muteSampleBuffer(nonReverbLeft, len); - muteSampleBuffer(nonReverbRight, len); - muteSampleBuffer(reverbDryLeft, len); - muteSampleBuffer(reverbDryRight, len); - if (isEnabled) { + muteSampleBuffer(nonReverbLeft, len); + muteSampleBuffer(nonReverbRight, len); + muteSampleBuffer(reverbDryLeft, len); + muteSampleBuffer(reverbDryRight, len); + for (unsigned int i = 0; i < getPartialCount(); i++) { if (partialManager->shouldReverb(i)) { partialManager->produceOutput(i, reverbDryLeft, reverbDryRight, len); @@ -1591,8 +1622,8 @@ void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sampl if (isReverbEnabled()) { reverbModel->process(reverbDryLeft, reverbDryRight, reverbWetLeft, reverbWetRight, len); - if (reverbWetLeft != NULL) convertSamplesToOutput(reverbWetLeft, len, true); - if (reverbWetRight != NULL) convertSamplesToOutput(reverbWetRight, len, true); + if (reverbWetLeft != NULL) convertSamplesToOutput(reverbWetLeft, len); + if (reverbWetRight != NULL) convertSamplesToOutput(reverbWetRight, len); } else { muteSampleBuffer(reverbWetLeft, len); muteSampleBuffer(reverbWetRight, len); @@ -1601,15 +1632,20 @@ void Synth::doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sampl // Don't bother with conversion if the output is going to be unused if (nonReverbLeft != tmpBufNonReverbLeft) { produceLA32Output(nonReverbLeft, len); - convertSamplesToOutput(nonReverbLeft, len, false); + convertSamplesToOutput(nonReverbLeft, len); } if (nonReverbRight != tmpBufNonReverbRight) { produceLA32Output(nonReverbRight, len); - convertSamplesToOutput(nonReverbRight, len, false); + convertSamplesToOutput(nonReverbRight, len); } - if (reverbDryLeft != tmpBufReverbDryLeft) convertSamplesToOutput(reverbDryLeft, len, false); - if (reverbDryRight != tmpBufReverbDryRight) convertSamplesToOutput(reverbDryRight, len, false); + if (reverbDryLeft != tmpBufReverbDryLeft) convertSamplesToOutput(reverbDryLeft, len); + if (reverbDryRight != tmpBufReverbDryRight) convertSamplesToOutput(reverbDryRight, len); } else { + // Avoid muting buffers that wasn't requested + if (nonReverbLeft != tmpBufNonReverbLeft) muteSampleBuffer(nonReverbLeft, len); + if (nonReverbRight != tmpBufNonReverbRight) muteSampleBuffer(nonReverbRight, len); + if (reverbDryLeft != tmpBufReverbDryLeft) muteSampleBuffer(reverbDryLeft, len); + if (reverbDryRight != tmpBufReverbDryRight) muteSampleBuffer(reverbDryRight, len); muteSampleBuffer(reverbWetLeft, len); muteSampleBuffer(reverbWetRight, len); } @@ -1651,14 +1687,48 @@ bool Synth::isActive() const { return false; } -const Partial *Synth::getPartial(unsigned int partialNum) const { - return partialManager->getPartial(partialNum); -} - unsigned int Synth::getPartialCount() const { return partialCount; } +void Synth::getPartStates(bool *partStates) const { + for (int partNumber = 0; partNumber < 9; partNumber++) { + const Part *part = parts[partNumber]; + partStates[partNumber] = part->getActiveNonReleasingPartialCount() > 0; + } +} + +void Synth::getPartialStates(PartialState *partialStates) const { + static const PartialState partialPhaseToState[8] = { + PartialState_ATTACK, PartialState_ATTACK, PartialState_ATTACK, PartialState_ATTACK, + PartialState_SUSTAIN, PartialState_SUSTAIN, PartialState_RELEASE, PartialState_INACTIVE + }; + + for (unsigned int partialNum = 0; partialNum < getPartialCount(); partialNum++) { + const Partial *partial = partialManager->getPartial(partialNum); + partialStates[partialNum] = partial->isActive() ? partialPhaseToState[partial->getTVA()->getPhase()] : PartialState_INACTIVE; + } +} + +unsigned int Synth::getPlayingNotes(unsigned int partNumber, Bit8u *keys, Bit8u *velocities) const { + unsigned int playingNotes = 0; + if (isOpen && (partNumber < 9)) { + const Part *part = parts[partNumber]; + const Poly *poly = part->getFirstActivePoly(); + while (poly != NULL) { + keys[playingNotes] = (Bit8u)poly->getKey(); + velocities[playingNotes] = (Bit8u)poly->getVelocity(); + playingNotes++; + poly = poly->getNext(); + } + } + return playingNotes; +} + +const char *Synth::getPatchName(unsigned int partNumber) const { + return (!isOpen || partNumber > 8) ? NULL : parts[partNumber]->getCurrentInstr(); +} + const Part *Synth::getPart(unsigned int partNum) const { if (partNum > 8) { return NULL; diff --git a/audio/softsynth/mt32/Synth.h b/audio/softsynth/mt32/Synth.h index 37fb7b280a..97d4644ee2 100644 --- a/audio/softsynth/mt32/Synth.h +++ b/audio/softsynth/mt32/Synth.h @@ -19,15 +19,31 @@ #define MT32EMU_SYNTH_H //#include <cstdarg> +//#include <cstring> namespace MT32Emu { -class TableInitialiser; +class Analog; +class BReverbModel; +class MemoryRegion; +class MidiEventQueue; +class Part; +class Poly; class Partial; class PartialManager; -class Part; -class ROMImage; -class BReverbModel; + +class PatchTempMemoryRegion; +class RhythmTempMemoryRegion; +class TimbreTempMemoryRegion; +class PatchesMemoryRegion; +class TimbresMemoryRegion; +class SystemMemoryRegion; +class DisplayMemoryRegion; +class ResetMemoryRegion; + +struct ControlROMMap; +struct PCMWaveEntry; +struct MemParams; /** * Methods for emulating the connection between the LA32 and the DAC, which involves @@ -43,8 +59,7 @@ enum DACInputMode { // Produces samples that exactly match the bits output from the emulated LA32. // * Nicer overdrive characteristics than the DAC hacks (it simply clips samples within range) // * Much less likely to overdrive than any other mode. - // * Half the volume of any of the other modes, meaning its volume relative to the reverb - // output when mixed together directly will sound wrong. + // * Half the volume of any of the other modes. // * Output gain is ignored for both LA32 and reverb output. // * Perfect for developers while debugging :) DACInputMode_PURE, @@ -60,6 +75,7 @@ enum DACInputMode { DACInputMode_GENERATION2 }; +// Methods for emulating the effective delay of incoming MIDI messages introduced by a MIDI interface. enum MIDIDelayMode { // Process incoming MIDI events immediately. MIDIDelayMode_IMMEDIATE, @@ -72,6 +88,35 @@ enum MIDIDelayMode { MIDIDelayMode_DELAY_ALL }; +// Methods for emulating the effects of analogue circuits of real hardware units on the output signal. +enum AnalogOutputMode { + // Only digital path is emulated. The output samples correspond to the digital signal at the DAC entrance. + AnalogOutputMode_DIGITAL_ONLY, + // Coarse emulation of LPF circuit. High frequencies are boosted, sample rate remains unchanged. + AnalogOutputMode_COARSE, + // Finer emulation of LPF circuit. Output signal is upsampled to 48 kHz to allow emulation of audible mirror spectra above 16 kHz, + // which is passed through the LPF circuit without significant attenuation. + AnalogOutputMode_ACCURATE, + // Same as AnalogOutputMode_ACCURATE mode but the output signal is 2x oversampled, i.e. the output sample rate is 96 kHz. + // This makes subsequent resampling easier. Besides, due to nonlinear passband of the LPF emulated, it takes fewer number of MACs + // compared to a regular LPF FIR implementations. + AnalogOutputMode_OVERSAMPLED +}; + +enum ReverbMode { + REVERB_MODE_ROOM, + REVERB_MODE_HALL, + REVERB_MODE_PLATE, + REVERB_MODE_TAP_DELAY +}; + +enum PartialState { + PartialState_INACTIVE, + PartialState_ATTACK, + PartialState_SUSTAIN, + PartialState_RELEASE +}; + const Bit8u SYSEX_MANUFACTURER_ROLAND = 0x41; const Bit8u SYSEX_MDL_MT32 = 0x16; @@ -87,148 +132,10 @@ const Bit8u SYSEX_CMD_EOD = 0x45; // End of data const Bit8u SYSEX_CMD_ERR = 0x4E; // Communications error const Bit8u SYSEX_CMD_RJC = 0x4F; // Rejection -const int MAX_SYSEX_SIZE = 512; +const int MAX_SYSEX_SIZE = 512; // FIXME: Does this correspond to a real MIDI buffer used in h/w devices? const unsigned int CONTROL_ROM_SIZE = 64 * 1024; -struct ControlROMPCMStruct { - Bit8u pos; - Bit8u len; - Bit8u pitchLSB; - Bit8u pitchMSB; -}; - -struct ControlROMMap { - Bit16u idPos; - Bit16u idLen; - const char *idBytes; - Bit16u pcmTable; // 4 * pcmCount bytes - Bit16u pcmCount; - Bit16u timbreAMap; // 128 bytes - Bit16u timbreAOffset; - bool timbreACompressed; - Bit16u timbreBMap; // 128 bytes - Bit16u timbreBOffset; - bool timbreBCompressed; - Bit16u timbreRMap; // 2 * timbreRCount bytes - Bit16u timbreRCount; - Bit16u rhythmSettings; // 4 * rhythmSettingsCount bytes - Bit16u rhythmSettingsCount; - Bit16u reserveSettings; // 9 bytes - Bit16u panSettings; // 8 bytes - Bit16u programSettings; // 8 bytes - Bit16u rhythmMaxTable; // 4 bytes - Bit16u patchMaxTable; // 16 bytes - Bit16u systemMaxTable; // 23 bytes - Bit16u timbreMaxTable; // 72 bytes -}; - -enum MemoryRegionType { - MR_PatchTemp, MR_RhythmTemp, MR_TimbreTemp, MR_Patches, MR_Timbres, MR_System, MR_Display, MR_Reset -}; - -enum ReverbMode { - REVERB_MODE_ROOM, - REVERB_MODE_HALL, - REVERB_MODE_PLATE, - REVERB_MODE_TAP_DELAY -}; - -class MemoryRegion { -private: - Synth *synth; - Bit8u *realMemory; - Bit8u *maxTable; -public: - MemoryRegionType type; - Bit32u startAddr, entrySize, entries; - - MemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable, MemoryRegionType useType, Bit32u useStartAddr, Bit32u useEntrySize, Bit32u useEntries) { - synth = useSynth; - realMemory = useRealMemory; - maxTable = useMaxTable; - type = useType; - startAddr = useStartAddr; - entrySize = useEntrySize; - entries = useEntries; - } - int lastTouched(Bit32u addr, Bit32u len) const { - return (offset(addr) + len - 1) / entrySize; - } - int firstTouchedOffset(Bit32u addr) const { - return offset(addr) % entrySize; - } - int firstTouched(Bit32u addr) const { - return offset(addr) / entrySize; - } - Bit32u regionEnd() const { - return startAddr + entrySize * entries; - } - bool contains(Bit32u addr) const { - return addr >= startAddr && addr < regionEnd(); - } - int offset(Bit32u addr) const { - return addr - startAddr; - } - Bit32u getClampedLen(Bit32u addr, Bit32u len) const { - if (addr + len > regionEnd()) - return regionEnd() - addr; - return len; - } - Bit32u next(Bit32u addr, Bit32u len) const { - if (addr + len > regionEnd()) { - return regionEnd() - addr; - } - return 0; - } - Bit8u getMaxValue(int off) const { - if (maxTable == NULL) - return 0xFF; - return maxTable[off % entrySize]; - } - Bit8u *getRealMemory() const { - return realMemory; - } - bool isReadable() const { - return getRealMemory() != NULL; - } - void read(unsigned int entry, unsigned int off, Bit8u *dst, unsigned int len) const; - void write(unsigned int entry, unsigned int off, const Bit8u *src, unsigned int len, bool init = false) const; -}; - -class PatchTempMemoryRegion : public MemoryRegion { -public: - PatchTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_PatchTemp, MT32EMU_MEMADDR(0x030000), sizeof(MemParams::PatchTemp), 9) {} -}; -class RhythmTempMemoryRegion : public MemoryRegion { -public: - RhythmTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_RhythmTemp, MT32EMU_MEMADDR(0x030110), sizeof(MemParams::RhythmTemp), 85) {} -}; -class TimbreTempMemoryRegion : public MemoryRegion { -public: - TimbreTempMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_TimbreTemp, MT32EMU_MEMADDR(0x040000), sizeof(TimbreParam), 8) {} -}; -class PatchesMemoryRegion : public MemoryRegion { -public: - PatchesMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Patches, MT32EMU_MEMADDR(0x050000), sizeof(PatchParam), 128) {} -}; -class TimbresMemoryRegion : public MemoryRegion { -public: - TimbresMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_Timbres, MT32EMU_MEMADDR(0x080000), sizeof(MemParams::PaddedTimbre), 64 + 64 + 64 + 64) {} -}; -class SystemMemoryRegion : public MemoryRegion { -public: - SystemMemoryRegion(Synth *useSynth, Bit8u *useRealMemory, Bit8u *useMaxTable) : MemoryRegion(useSynth, useRealMemory, useMaxTable, MR_System, MT32EMU_MEMADDR(0x100000), sizeof(MemParams::System), 1) {} -}; -class DisplayMemoryRegion : public MemoryRegion { -public: - DisplayMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Display, MT32EMU_MEMADDR(0x200000), MAX_SYSEX_SIZE - 1, 1) {} -}; -class ResetMemoryRegion : public MemoryRegion { -public: - ResetMemoryRegion(Synth *useSynth) : MemoryRegion(useSynth, NULL, NULL, MR_Reset, MT32EMU_MEMADDR(0x7F0000), 0x3FFF, 1) {} -}; - class ReportHandler { friend class Synth; @@ -254,47 +161,6 @@ protected: virtual void onProgramChanged(int /* partNum */, int /* bankNum */, const char * /* patchName */) {} }; -/** - * Used to safely store timestamped MIDI events in a local queue. - */ -struct MidiEvent { - Bit32u shortMessageData; - const Bit8u *sysexData; - Bit32u sysexLength; - Bit32u timestamp; - - ~MidiEvent(); - void setShortMessage(Bit32u shortMessageData, Bit32u timestamp); - void setSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); -}; - -/** - * Simple queue implementation using a ring buffer to store incoming MIDI event before the synth actually processes it. - * It is intended to: - * - get rid of prerenderer while retaining graceful partial abortion - * - add fair emulation of the MIDI interface delays - * - extend the synth interface with the default implementation of a typical rendering loop. - * THREAD SAFETY: - * It is safe to use either in a single thread environment or when there are only two threads - one performs only reading - * and one performs only writing. More complicated usage requires external synchronisation. - */ -class MidiEventQueue { -private: - MidiEvent *ringBuffer; - Bit32u ringBufferSize; - volatile Bit32u startPosition; - volatile Bit32u endPosition; - -public: - MidiEventQueue(Bit32u ringBufferSize = DEFAULT_MIDI_EVENT_QUEUE_SIZE); - ~MidiEventQueue(); - void reset(); - bool pushShortMessage(Bit32u shortMessageData, Bit32u timestamp); - bool pushSysex(const Bit8u *sysexData, Bit32u sysexLength, Bit32u timestamp); - const MidiEvent *peekMidiEvent(); - void dropMidiEvent(); -}; - class Synth { friend class Part; friend class RhythmPart; @@ -335,7 +201,7 @@ private: volatile Bit32u lastReceivedMIDIEventTimestamp; volatile Bit32u renderedSampleCount; - MemParams mt32ram, mt32default; + MemParams &mt32ram, &mt32default; BReverbModel *reverbModels[4]; BReverbModel *reverbModel; @@ -346,12 +212,6 @@ private: float outputGain; float reverbOutputGain; -#if MT32EMU_USE_FLOAT_SAMPLES - float effectiveReverbOutputGain; -#else - int effectiveOutputGain; - int effectiveReverbOutputGain; -#endif bool reversedStereoEnabled; @@ -368,11 +228,12 @@ private: // We emulate this by delaying new MIDI events processing until abortion finishes. Poly *abortingPoly; - Bit32u getShortMessageLength(Bit32u msg); + Analog *analog; + Bit32u addMIDIInterfaceDelay(Bit32u len, Bit32u timestamp); void produceLA32Output(Sample *buffer, Bit32u len); - void convertSamplesToOutput(Sample *buffer, Bit32u len, bool reverb); + void convertSamplesToOutput(Sample *buffer, Bit32u len); bool isAbortingPoly() const; void doRenderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len); @@ -404,13 +265,20 @@ private: void newTimbreSet(int partNum, Bit8u timbreGroup, const char patchName[]); void printDebug(const char *fmt, ...); + // partNum should be 0..7 for Part 1..8, or 8 for Rhythm + const Part *getPart(unsigned int partNum) const; + public: - static inline Bit16s clipBit16s(Bit32s sample) { + static inline Sample clipSampleEx(SampleEx sampleEx) { +#if MT32EMU_USE_FLOAT_SAMPLES + return sampleEx; +#else // Clamp values above 32767 to 32767, and values below -32768 to -32768 // FIXME: Do we really need this stuff? I think these branches are very well predicted. Instead, this introduces a chain. // The version below is actually a bit faster on my system... - //return ((sample + 0x8000) & ~0xFFFF) ? (sample >> 31) ^ 0x7FFF : (Bit16s)sample; - return ((-0x8000 <= sample) && (sample <= 0x7FFF)) ? (Bit16s)sample : (sample >> 31) ^ 0x7FFF; + //return ((sampleEx + 0x8000) & ~0xFFFF) ? (sampleEx >> 31) ^ 0x7FFF : (Sample)sampleEx; + return ((-0x8000 <= sampleEx) && (sampleEx <= 0x7FFF)) ? (Sample)sampleEx : (sampleEx >> 31) ^ 0x7FFF; +#endif } static inline void muteSampleBuffer(Sample *buffer, Bit32u len) { @@ -426,7 +294,8 @@ public: #endif } - static Bit8u calcSysexChecksum(const Bit8u *data, Bit32u len, Bit8u checksum); + static Bit32u getShortMessageLength(Bit32u msg); + static Bit8u calcSysexChecksum(const Bit8u *data, const Bit32u len, const Bit8u initChecksum = 0); // Optionally sets callbacks for reporting various errors, information and debug messages Synth(ReportHandler *useReportHandler = NULL); @@ -435,8 +304,12 @@ public: // Used to initialise the MT-32. Must be called before any other function. // Returns true if initialization was sucessful, otherwise returns false. // controlROMImage and pcmROMImage represent Control and PCM ROM images for use by synth. - // usePartialCount sets the maximum number of partials playing simultaneously for this session. - bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS); + // usePartialCount sets the maximum number of partials playing simultaneously for this session (optional). + // analogOutputMode sets the mode for emulation of analogue circuitry of the hardware units (optional). + bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, unsigned int usePartialCount = DEFAULT_MAX_PARTIALS, AnalogOutputMode analogOutputMode = AnalogOutputMode_COARSE); + + // Overloaded method which opens the synth with default partial count. + bool open(const ROMImage &controlROMImage, const ROMImage &pcmROMImage, AnalogOutputMode analogOutputMode); // Closes the MT-32 and deallocates any memory used by the synthesizer void close(bool forced = false); @@ -444,29 +317,34 @@ public: // All the enqueued events are processed by the synth immediately. void flushMIDIQueue(); - // Sets size of the internal MIDI event queue. + // Sets size of the internal MIDI event queue. The queue size is set to the minimum power of 2 that is greater or equal to the size specified. // The queue is flushed before reallocation. - void setMIDIEventQueueSize(Bit32u); + // Returns the actual queue size being used. + Bit32u setMIDIEventQueueSize(Bit32u); // Enqueues a MIDI event for subsequent playback. - // The minimum delay involves the delay introduced while the event is transferred via MIDI interface + // The MIDI event will be processed not before the specified timestamp. + // The timestamp is measured as the global rendered sample count since the synth was created (at the native sample rate 32000 Hz). + // The minimum delay involves emulation of the delay introduced while the event is transferred via MIDI interface // and emulation of the MCU busy-loop while it frees partials for use by a new Poly. - // Calls from multiple threads must be synchronised, although, - // no synchronisation is required with the rendering thread. + // Calls from multiple threads must be synchronised, although, no synchronisation is required with the rendering thread. + // The methods return false if the MIDI event queue is full and the message cannot be enqueued. - // The MIDI event will be processed not before the specified timestamp. - // The timestamp is measured as the global rendered sample count since the synth was created. + // Enqueues a single short MIDI message. The message must contain a status byte. bool playMsg(Bit32u msg, Bit32u timestamp); + // Enqueues a single well formed System Exclusive MIDI message. bool playSysex(const Bit8u *sysex, Bit32u len, Bit32u timestamp); - // The MIDI event will be processed ASAP. + + // Overloaded methods for the MIDI events to be processed ASAP. bool playMsg(Bit32u msg); bool playSysex(const Bit8u *sysex, Bit32u len); // WARNING: // The methods below don't ensure minimum 1-sample delay between sequential MIDI events, // and a sequence of NoteOn and immediately succeeding NoteOff messages is always silent. + // A thread that invokes these methods must be explicitly synchronised with the thread performing sample rendering. - // Sends a 4-byte MIDI message to the MT-32 for immediate playback. + // Sends a short MIDI message to the synth for immediate playback. The message must contain a status byte. void playMsgNow(Bit32u msg); void playMsgOnPart(unsigned char part, unsigned char code, unsigned char note, unsigned char velocity); @@ -495,12 +373,17 @@ public: void setMIDIDelayMode(MIDIDelayMode mode); MIDIDelayMode getMIDIDelayMode() const; - // Sets output gain factor. Applied to all output samples and unrelated with the synth's Master volume. + // Sets output gain factor for synth output channels. Applied to all output samples and unrelated with the synth's Master volume, + // it rather corresponds to the gain of the output analog circuitry of the hardware units. However, together with setReverbOutputGain() + // it offers to the user a capability to control the gain of reverb and non-reverb output channels independently. // Ignored in DACInputMode_PURE void setOutputGain(float); float getOutputGain() const; - // Sets output gain factor for the reverb wet output. setOutputGain() doesn't change reverb output gain. + // Sets output gain factor for the reverb wet output channels. It rather corresponds to the gain of the output + // analog circuitry of the hardware units. However, together with setOutputGain() it offers to the user a capability + // to control the gain of reverb and non-reverb output channels independently. + // // Note: We're currently emulate CM-32L/CM-64 reverb quite accurately and the reverb output level closely // corresponds to the level of digital capture. Although, according to the CM-64 PCB schematic, // there is a difference in the reverb analogue circuit, and the resulting output gain is 0.68 @@ -512,12 +395,21 @@ public: void setReversedStereoEnabled(bool enabled); bool isReversedStereoEnabled(); - // Renders samples to the specified output stream. - // The length is in frames, not bytes (in 16-bit stereo, - // one frame is 4 bytes). + // Returns actual sample rate used in emulation of stereo analog circuitry of hardware units. + // See comment for render() below. + unsigned int getStereoOutputSampleRate() const; + + // Renders samples to the specified output stream as if they were sampled at the analog stereo output. + // When AnalogOutputMode is set to ACCURATE, the output signal is upsampled to 48 kHz in order + // to retain emulation accuracy in whole audible frequency spectra. Otherwise, native digital signal sample rate is retained. + // getStereoOutputSampleRate() can be used to query actual sample rate of the output signal. + // The length is in frames, not bytes (in 16-bit stereo, one frame is 4 bytes). void render(Sample *stream, Bit32u len); - // Renders samples to the specified output streams (any or all of which may be NULL). + // Renders samples to the specified output streams as if they appeared at the DAC entrance. + // No further processing performed in analog circuitry emulation is applied to the signal. + // NULL may be specified in place of any or all of the stream buffers. + // The length is in samples, not bytes. void renderStreams(Sample *nonReverbLeft, Sample *nonReverbRight, Sample *reverbDryLeft, Sample *reverbDryRight, Sample *reverbWetLeft, Sample *reverbWetRight, Bit32u len); // Returns true when there is at least one active partial, otherwise false. @@ -526,15 +418,28 @@ public: // Returns true if hasActivePartials() returns true, or reverb is (somewhat unreliably) detected as being active. bool isActive() const; - const Partial *getPartial(unsigned int partialNum) const; - // Returns the maximum number of partials playing simultaneously. unsigned int getPartialCount() const; - void readMemory(Bit32u addr, Bit32u len, Bit8u *data); + // Fills in current states of all the parts into the array provided. The array must have at least 9 entries to fit values for all the parts. + // If the value returned for a part is true, there is at least one active non-releasing partial playing on this part. + // This info is useful in emulating behaviour of LCD display of the hardware units. + void getPartStates(bool *partStates) const; - // partNum should be 0..7 for Part 1..8, or 8 for Rhythm - const Part *getPart(unsigned int partNum) const; + // Fills in current states of all the partials into the array provided. The array must be large enough to accommodate states of all the partials. + void getPartialStates(PartialState *partialStates) const; + + // Fills in information about currently playing notes on the specified part into the arrays provided. The arrays must be large enough + // to accommodate data for all the playing notes. The maximum number of simultaneously playing notes cannot exceed the number of partials. + // Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm. + // Returns the number of currently playing notes on the specified part. + unsigned int getPlayingNotes(unsigned int partNumber, Bit8u *keys, Bit8u *velocities) const; + + // Returns name of the patch set on the specified part. + // Argument partNumber should be 0..7 for Part 1..8, or 8 for Rhythm. + const char *getPatchName(unsigned int partNumber) const; + + void readMemory(Bit32u addr, Bit32u len, Bit8u *data); }; } diff --git a/audio/softsynth/mt32/TVA.cpp b/audio/softsynth/mt32/TVA.cpp index 3fefb791f2..894e53f14a 100644 --- a/audio/softsynth/mt32/TVA.cpp +++ b/audio/softsynth/mt32/TVA.cpp @@ -23,6 +23,7 @@ #include "mt32emu.h" #include "mmath.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/TVF.cpp b/audio/softsynth/mt32/TVF.cpp index bf8d50a7c9..164cf2b4cb 100644 --- a/audio/softsynth/mt32/TVF.cpp +++ b/audio/softsynth/mt32/TVF.cpp @@ -19,6 +19,7 @@ #include "mt32emu.h" #include "mmath.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/TVP.cpp b/audio/softsynth/mt32/TVP.cpp index 374646e5f1..a8003d96dc 100644 --- a/audio/softsynth/mt32/TVP.cpp +++ b/audio/softsynth/mt32/TVP.cpp @@ -19,6 +19,7 @@ //#include <cstdlib> #include "mt32emu.h" +#include "internals.h" namespace MT32Emu { diff --git a/audio/softsynth/mt32/Tables.cpp b/audio/softsynth/mt32/Tables.cpp index ae9f11fff0..7e165b5a7a 100644 --- a/audio/softsynth/mt32/Tables.cpp +++ b/audio/softsynth/mt32/Tables.cpp @@ -16,14 +16,15 @@ */ //#include <cmath> -//#include <cstdlib> -//#include <cstring> #include "mt32emu.h" #include "mmath.h" +#include "Tables.h" namespace MT32Emu { +// UNUSED: const int MIDDLEC = 60; + const Tables &Tables::getInstance() { static const Tables instance; return instance; diff --git a/audio/softsynth/mt32/Tables.h b/audio/softsynth/mt32/Tables.h index e7b97af515..8865c7fac8 100644 --- a/audio/softsynth/mt32/Tables.h +++ b/audio/softsynth/mt32/Tables.h @@ -20,24 +20,11 @@ namespace MT32Emu { -// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent. -// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator. -// The output from the synth is supposed to be resampled to convert the sample rate. -const unsigned int SAMPLE_RATE = 32000; - -// MIDI interface data transfer rate in samples. Used to simulate the transfer delay. -const double MIDI_DATA_TRANSFER_RATE = (double)SAMPLE_RATE / 31250.0 * 8.0; - -const float CM32L_REVERB_TO_LA32_ANALOG_OUTPUT_GAIN_FACTOR = 0.68f; - -const int MIDDLEC = 60; - -class Synth; - class Tables { private: Tables(); Tables(Tables &); + ~Tables() {} public: static const Tables &getInstance(); diff --git a/audio/softsynth/mt32/Types.h b/audio/softsynth/mt32/Types.h new file mode 100644 index 0000000000..934b1a1173 --- /dev/null +++ b/audio/softsynth/mt32/Types.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MT32EMU_TYPES_H +#define MT32EMU_TYPES_H + +namespace MT32Emu { + +typedef unsigned int Bit32u; +typedef signed int Bit32s; +typedef unsigned short int Bit16u; +typedef signed short int Bit16s; +typedef unsigned char Bit8u; +typedef signed char Bit8s; + +#if MT32EMU_USE_FLOAT_SAMPLES +typedef float Sample; +typedef float SampleEx; +#else +typedef Bit16s Sample; +typedef Bit32s SampleEx; +#endif + +} + +#endif diff --git a/audio/softsynth/mt32/internals.h b/audio/softsynth/mt32/internals.h new file mode 100644 index 0000000000..ef56819a42 --- /dev/null +++ b/audio/softsynth/mt32/internals.h @@ -0,0 +1,83 @@ +/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher + * Copyright (C) 2011, 2012, 2013, 2014 Dean Beeler, Jerome Fisher, Sergey V. Mikayev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MT32EMU_INTERNALS_H +#define MT32EMU_INTERNALS_H + +// Debugging + +// 0: Standard debug output is not stamped with the rendered sample count +// 1: Standard debug output is stamped with the rendered sample count +// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run. +// This is important to bear in mind for debug output that occurs during a run. +#define MT32EMU_DEBUG_SAMPLESTAMPS 0 + +// 0: No debug output for initialisation progress +// 1: Debug output for initialisation progress +#define MT32EMU_MONITOR_INIT 0 + +// 0: No debug output for MIDI events +// 1: Debug output for weird MIDI events +#define MT32EMU_MONITOR_MIDI 0 + +// 0: No debug output for note on/off +// 1: Basic debug output for note on/off +// 2: Comprehensive debug output for note on/off +#define MT32EMU_MONITOR_INSTRUMENTS 0 + +// 0: No debug output for partial allocations +// 1: Show partial stats when an allocation fails +// 2: Show partial stats with every new poly +// 3: Show individual partial allocations/deactivations +#define MT32EMU_MONITOR_PARTIALS 0 + +// 0: No debug output for sysex +// 1: Basic debug output for sysex +#define MT32EMU_MONITOR_SYSEX 0 + +// 0: No debug output for sysex writes to the timbre areas +// 1: Debug output with the name and location of newly-written timbres +// 2: Complete dump of timbre parameters for newly-written timbres +#define MT32EMU_MONITOR_TIMBRES 0 + +// 0: No TVA/TVF-related debug output. +// 1: Shows changes to TVA/TVF target, increment and phase. +#define MT32EMU_MONITOR_TVA 0 +#define MT32EMU_MONITOR_TVF 0 + +// Configuration + +// If non-zero, deletes reverb buffers that are not in use to save memory. +// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path. +#define MT32EMU_REDUCE_REVERB_MEMORY 1 + +// 0: Maximum speed at the cost of a bit lower emulation accuracy. +// 1: Maximum achievable emulation accuracy. +#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0 + +#include "Structures.h" +#include "Tables.h" +#include "Poly.h" +#include "LA32Ramp.h" +#include "LA32WaveGenerator.h" +#include "TVA.h" +#include "TVP.h" +#include "TVF.h" +#include "Partial.h" +#include "Part.h" + +#endif diff --git a/audio/softsynth/mt32/module.mk b/audio/softsynth/mt32/module.mk index 1c8aa125ab..f966da8d08 100644 --- a/audio/softsynth/mt32/module.mk +++ b/audio/softsynth/mt32/module.mk @@ -1,6 +1,7 @@ MODULE := audio/softsynth/mt32 MODULE_OBJS := \ + Analog.o \ BReverbModel.o \ LA32Ramp.o \ LA32WaveGenerator.o \ diff --git a/audio/softsynth/mt32/mt32emu.h b/audio/softsynth/mt32/mt32emu.h index d738a5de35..1574c08f0d 100644 --- a/audio/softsynth/mt32/mt32emu.h +++ b/audio/softsynth/mt32/mt32emu.h @@ -18,63 +18,20 @@ #ifndef MT32EMU_MT32EMU_H #define MT32EMU_MT32EMU_H -// Debugging - -// 0: Standard debug output is not stamped with the rendered sample count -// 1: Standard debug output is stamped with the rendered sample count -// NOTE: The "samplestamp" corresponds to the end of the last completed rendering run. -// This is important to bear in mind for debug output that occurs during a run. -#define MT32EMU_DEBUG_SAMPLESTAMPS 0 - -// 0: No debug output for initialisation progress -// 1: Debug output for initialisation progress -#define MT32EMU_MONITOR_INIT 0 - -// 0: No debug output for MIDI events -// 1: Debug output for weird MIDI events -#define MT32EMU_MONITOR_MIDI 0 - -// 0: No debug output for note on/off -// 1: Basic debug output for note on/off -// 2: Comprehensive debug output for note on/off -#define MT32EMU_MONITOR_INSTRUMENTS 0 - -// 0: No debug output for partial allocations -// 1: Show partial stats when an allocation fails -// 2: Show partial stats with every new poly -// 3: Show individual partial allocations/deactivations -#define MT32EMU_MONITOR_PARTIALS 0 - -// 0: No debug output for sysex -// 1: Basic debug output for sysex -#define MT32EMU_MONITOR_SYSEX 0 - -// 0: No debug output for sysex writes to the timbre areas -// 1: Debug output with the name and location of newly-written timbres -// 2: Complete dump of timbre parameters for newly-written timbres -#define MT32EMU_MONITOR_TIMBRES 0 - -// 0: No TVA/TVF-related debug output. -// 1: Shows changes to TVA/TVF target, increment and phase. -#define MT32EMU_MONITOR_TVA 0 -#define MT32EMU_MONITOR_TVF 0 - // Configuration -// If non-zero, deletes reverb buffers that are not in use to save memory. -// If zero, keeps reverb buffers for all modes around all the time to avoid allocating/freeing in the critical path. -#define MT32EMU_REDUCE_REVERB_MEMORY 1 - -// 0: Maximum speed at the cost of a bit lower emulation accuracy. -// 1: Maximum achievable emulation accuracy. -#define MT32EMU_BOSS_REVERB_PRECISE_MODE 0 - // 0: Use 16-bit signed samples and refined wave generator based on logarithmic fixed-point computations and LUTs. Maximum emulation accuracy and speed. // 1: Use float samples in the wave generator and renderer. Maximum output quality and minimum noise. #define MT32EMU_USE_FLOAT_SAMPLES 0 namespace MT32Emu { +// Sample rate to use in mixing. With the progress of development, we've found way too many thing dependent. +// In order to achieve further advance in emulation accuracy, sample rate made fixed throughout the emulator, +// except the emulation of analogue path. +// The output from the synth is supposed to be resampled externally in order to convert to the desired sample rate. +const unsigned int SAMPLE_RATE = 32000; + // The default value for the maximum number of partials playing simultaneously. const unsigned int DEFAULT_MAX_PARTIALS = 32; @@ -97,17 +54,7 @@ const unsigned int MAX_SAMPLES_PER_RUN = 4096; const unsigned int DEFAULT_MIDI_EVENT_QUEUE_SIZE = 1024; } -#include "Structures.h" -#include "common/file.h" -#include "Tables.h" -#include "Poly.h" -#include "LA32Ramp.h" -#include "LA32WaveGenerator.h" -#include "TVA.h" -#include "TVP.h" -#include "TVF.h" -#include "Partial.h" -#include "Part.h" +#include "Types.h" #include "ROMInfo.h" #include "Synth.h" diff --git a/audio/softsynth/opl/dosbox.cpp b/audio/softsynth/opl/dosbox.cpp index 5c3d833f54..3d90ec93d0 100644 --- a/audio/softsynth/opl/dosbox.cpp +++ b/audio/softsynth/opl/dosbox.cpp @@ -32,6 +32,7 @@ #include "dosbox.h" #include "dbopl.h" +#include "audio/mixer.h" #include "common/system.h" #include "common/scummsys.h" #include "common/util.h" @@ -148,6 +149,7 @@ OPL::OPL(Config::OplType type) : _type(type), _rate(0), _emulator(0) { } OPL::~OPL() { + stop(); free(); } @@ -156,7 +158,7 @@ void OPL::free() { _emulator = 0; } -bool OPL::init(int rate) { +bool OPL::init() { free(); memset(&_reg, 0, sizeof(_reg)); @@ -167,19 +169,19 @@ bool OPL::init(int rate) { return false; DBOPL::InitTables(); - _emulator->Setup(rate); + _rate = g_system->getMixer()->getOutputRate(); + _emulator->Setup(_rate); if (_type == Config::kDualOpl2) { // Setup opl3 mode in the hander _emulator->WriteReg(0x105, 1); } - _rate = rate; return true; } void OPL::reset() { - init(_rate); + init(); } void OPL::write(int port, int val) { @@ -307,7 +309,7 @@ void OPL::dualWrite(uint8 index, uint8 reg, uint8 val) { _emulator->WriteReg(fullReg, val); } -void OPL::readBuffer(int16 *buffer, int length) { +void OPL::generateSamples(int16 *buffer, int length) { // For stereo OPL cards, we divide the sample count by 2, // to match stereo AudioStream behavior. if (_type != Config::kOpl2) diff --git a/audio/softsynth/opl/dosbox.h b/audio/softsynth/opl/dosbox.h index 513a49f6b8..c52f06761a 100644 --- a/audio/softsynth/opl/dosbox.h +++ b/audio/softsynth/opl/dosbox.h @@ -69,7 +69,7 @@ namespace DBOPL { struct Chip; } // end of namespace DBOPL -class OPL : public ::OPL::OPL { +class OPL : public ::OPL::EmulatedOPL { private: Config::OplType _type; uint _rate; @@ -87,7 +87,7 @@ public: OPL(Config::OplType type); ~OPL(); - bool init(int rate); + bool init(); void reset(); void write(int a, int v); @@ -95,8 +95,10 @@ public: void writeReg(int r, int v); - void readBuffer(int16 *buffer, int length); bool isStereo() const { return _type != Config::kOpl2; } + +protected: + void generateSamples(int16 *buffer, int length); }; } // End of namespace DOSBox diff --git a/audio/softsynth/opl/mame.cpp b/audio/softsynth/opl/mame.cpp index da75ba76ba..696169be09 100644 --- a/audio/softsynth/opl/mame.cpp +++ b/audio/softsynth/opl/mame.cpp @@ -31,6 +31,8 @@ #include "mame.h" +#include "audio/mixer.h" +#include "common/system.h" #include "common/textconsole.h" #include "common/util.h" @@ -46,15 +48,19 @@ namespace OPL { namespace MAME { OPL::~OPL() { + stop(); MAME::OPLDestroy(_opl); _opl = 0; } -bool OPL::init(int rate) { - if (_opl) +bool OPL::init() { + if (_opl) { + stopCallbacks(); MAME::OPLDestroy(_opl); + } + + _opl = MAME::makeAdLibOPL(g_system->getMixer()->getOutputRate()); - _opl = MAME::makeAdLibOPL(rate); return (_opl != 0); } @@ -74,7 +80,7 @@ void OPL::writeReg(int r, int v) { MAME::OPLWriteReg(_opl, r, v); } -void OPL::readBuffer(int16 *buffer, int length) { +void OPL::generateSamples(int16 *buffer, int length) { MAME::YM3812UpdateOne(_opl, buffer, length); } diff --git a/audio/softsynth/opl/mame.h b/audio/softsynth/opl/mame.h index bd479d9e45..67d80bb193 100644 --- a/audio/softsynth/opl/mame.h +++ b/audio/softsynth/opl/mame.h @@ -174,14 +174,14 @@ void YM3812UpdateOne(FM_OPL *OPL, int16 *buffer, int length); FM_OPL *makeAdLibOPL(int rate); // OPL API implementation -class OPL : public ::OPL::OPL { +class OPL : public ::OPL::EmulatedOPL { private: FM_OPL *_opl; public: OPL() : _opl(0) {} ~OPL(); - bool init(int rate); + bool init(); void reset(); void write(int a, int v); @@ -189,8 +189,10 @@ public: void writeReg(int r, int v); - void readBuffer(int16 *buffer, int length); bool isStereo() const { return false; } + +protected: + void generateSamples(int16 *buffer, int length); }; } // End of namespace MAME diff --git a/audio/timestamp.cpp b/audio/timestamp.cpp index 1ce971631c..63752812e1 100644 --- a/audio/timestamp.cpp +++ b/audio/timestamp.cpp @@ -39,12 +39,10 @@ Timestamp::Timestamp(uint ms, uint fr) { Timestamp::Timestamp(uint s, uint frames, uint fr) { assert(fr > 0); - _secs = s; + _secs = s + (frames / fr); _framerateFactor = 1000 / Common::gcd<uint>(1000, fr); _framerate = fr * _framerateFactor; - _numFrames = frames * _framerateFactor; - - normalize(); + _numFrames = (frames % fr) * _framerateFactor; } Timestamp Timestamp::convertToFramerate(uint newFramerate) const { |