diff options
-rw-r--r-- | engines/kyra/module.mk | 1 | ||||
-rw-r--r-- | engines/kyra/sound.cpp | 319 | ||||
-rw-r--r-- | engines/kyra/sound.h | 57 | ||||
-rw-r--r-- | engines/kyra/sound_midi.cpp | 563 | ||||
-rw-r--r-- | sound/midiparser.cpp | 14 | ||||
-rw-r--r-- | sound/midiparser.h | 7 |
6 files changed, 590 insertions, 371 deletions
diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk index b38661ada5..8169b7b33f 100644 --- a/engines/kyra/module.mk +++ b/engines/kyra/module.mk @@ -53,6 +53,7 @@ MODULE_OBJS := \ sequences_mr.o \ sound_adlib.o \ sound_digital.o \ + sound_midi.o \ sound_towns.o \ sound.o \ sound_lok.o \ diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp index 073639e4ca..63a97185c8 100644 --- a/engines/kyra/sound.cpp +++ b/engines/kyra/sound.cpp @@ -159,325 +159,6 @@ uint32 Sound::voicePlayedTime(const char *file) { #pragma mark - -SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver) : Sound(vm, mixer) { - _driver = driver; - _passThrough = false; - - _musicParser = _sfxParser = 0; - _isMusicPlaying = _isSfxPlaying = false; - _eventFromMusic = false; - - _nativeMT32 = _useC55 = false; - - _fadeStartTime = 0; - _fadeMusicOut = false; - - memset(_channel, 0, sizeof(_channel)); - memset(_channelVolume, 50, sizeof(_channelVolume)); - _channelVolume[10] = 100; - for (int i = 0; i < 16; ++i) - _virChannel[i] = i; - - int ret = open(); - if (ret != MERR_ALREADY_OPEN && ret != 0) - error("Couldn't open midi driver"); -} - -SoundMidiPC::~SoundMidiPC() { - Common::StackLock lock(_mutex); - - delete _musicParser; - delete _sfxParser; - - _driver->setTimerCallback(0, 0); - close(); -} - -bool SoundMidiPC::init() { - _musicParser = MidiParser::createParser_XMIDI(); - _sfxParser = MidiParser::createParser_XMIDI(); - - if (!_musicParser || !_sfxParser) - return false; - - _musicParser->setMidiDriver(this); - _sfxParser->setMidiDriver(this); - - return true; -} - -void SoundMidiPC::updateVolumeSettings() { - _musicVolume = CLIP(ConfMan.getInt("music_volume"), 0, 255); - _sfxVolume = CLIP(ConfMan.getInt("sfx_volume"), 0, 255); - - updateChannelVolume(_musicVolume); -} - -void SoundMidiPC::hasNativeMT32(bool nativeMT32) { - _nativeMT32 = nativeMT32; - - // C55 appears to be XMIDI for General MIDI instruments - if (!_nativeMT32 && _vm->game() == GI_KYRA2) - _useC55 = true; - else - _useC55 = false; -} - -void SoundMidiPC::updateChannelVolume(uint8 volume) { - for (int i = 0; i < 32; ++i) { - if (_channel[i]) { - if (i >= 16) - _channel[i]->volume(_channelVolume[i - 16] * volume / 255); - else - _channel[i]->volume(_channelVolume[i] * volume / 255); - } - } -} - -int SoundMidiPC::open() { - // Don't ever call open without first setting the output driver! - if (!_driver) - return 255; - - int ret = _driver->open(); - if (ret) - return ret; - - _driver->setTimerCallback(this, &onTimer); - return 0; -} - -void SoundMidiPC::close() { - if (_driver) { - _driver->close(); - delete _driver; - } - _driver = 0; -} - -void SoundMidiPC::send(uint32 b) { - if (_passThrough) { - if ((b & 0xFFF0) == 0x007BB0) - return; - _driver->send(b); - return; - } - - const int volume = /*_eventFromMusic ? */_musicVolume/* : _sfxVolume*/; - - uint8 channel = (byte)(b & 0x0F); - if (((b & 0xFFF0) == 0x6FB0 || (b & 0xFFF0) == 0x6EB0) && channel != 9) { - if (_virChannel[channel] == channel) { - _virChannel[channel] = channel + 16; - if (!_channel[_virChannel[channel]]) - _channel[_virChannel[channel]] = _driver->allocateChannel(); - if (_channel[_virChannel[channel]]) - _channel[_virChannel[channel]]->volume(_channelVolume[channel] * volume / 255); - } - return; - } - - if ((b & 0xFFF0) == 0x07B0) { - // Adjust volume changes by master volume - uint8 vol = (uint8)((b >> 16) & 0x7F); - _channelVolume[channel] = vol; - vol = vol * volume / 255; - b = (b & 0xFF00FFFF) | (vol << 16); - } else if ((b & 0xF0) == 0xC0 && !_nativeMT32 && !_useC55) { - b = (b & 0xFFFF00FF) | (MidiDriver::_mt32ToGm[(b >> 8) & 0xFF] << 8); - } else if ((b & 0xFFF0) == 0x007BB0) { - //Only respond to All Notes Off if this channel - //has currently been allocated - if (!_channel[/*_virChannel[channel]*/channel]) - return; - } - - if (!_channel[_virChannel[channel]]) { - _channel[_virChannel[channel]] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); - if (_channel[_virChannel[channel]]) - _channel[_virChannel[channel]]->volume(_channelVolume[channel] * volume / 255); - } - if (_channel[_virChannel[channel]]) - _channel[_virChannel[channel]]->send(b); -} - -void SoundMidiPC::metaEvent(byte type, byte *data, uint16 length) { - switch (type) { - case 0x2F: // End of Track - if (_eventFromMusic) { - // remap all channels - for (int i = 0; i < 16; ++i) - _virChannel[i] = i; - } else { - _isSfxPlaying = false; - } - break; - default: - _driver->metaEvent(type, data, length); - break; - } -} - - -struct DeleterArray { - void operator ()(byte *ptr) { - delete[] ptr; - } -}; - -void SoundMidiPC::loadSoundFile(uint file) { - Common::StackLock lock(_mutex); - - internalLoadFile(fileListEntry(file)); -} - -void SoundMidiPC::loadSoundFile(Common::String file) { - Common::StackLock lock(_mutex); - - internalLoadFile(file); -} - -void SoundMidiPC::internalLoadFile(Common::String file) { - Common::String filename = file; - filename += "."; - filename += _useC55 ? "C55" : "XMI"; - - if (filename == _currentTrack) { - _isMusicPlaying = _isSfxPlaying = false; - _fadeStartTime = 0; - _fadeMusicOut = false; - updateChannelVolume(_musicVolume); - _musicParser->setTempo(0); - _sfxParser->setTempo(0); - _musicParser->property(MidiParser::mpAutoLoop, false); - _sfxParser->property(MidiParser::mpAutoLoop, false); - return; - } - - uint32 size; - uint8 *data = (_vm->resource())->fileData(filename.c_str(), &size); - - if (!data) { - warning("Couldn't load soundfile '%s'", filename.c_str()); - return; - } - - _currentTrack = filename; - - _musicParser->unloadMusic(); - _sfxParser->unloadMusic(); - _midiFile = Common::SharedPtr<byte>(data, DeleterArray()); - - _isMusicPlaying = _isSfxPlaying = false; - _fadeStartTime = 0; - _fadeMusicOut = false; - updateChannelVolume(_musicVolume); - - if (_musicParser->loadMusic(_midiFile.get(), size)) { - _musicParser->setTimerRate(getBaseTempo()); - _musicParser->setTempo(0); - _musicParser->property(MidiParser::mpAutoLoop, false); - } else { - warning("Error parsing music track '%s'", filename.c_str()); - } - - if (_sfxParser->loadMusic(_midiFile.get(), size)) { - _sfxParser->setTimerRate(getBaseTempo()); - _sfxParser->setTempo(0); - _sfxParser->property(MidiParser::mpAutoLoop, false); - } else { - warning("Error parsing sfx track '%s'", filename.c_str()); - } -} - -void SoundMidiPC::onTimer(void *refCon) { - SoundMidiPC *sound = (SoundMidiPC *)refCon; - Common::StackLock lock(sound->_mutex); - - // this should be set to the fadeToBlack value - static const uint32 musicFadeTime = 1 * 1000; - - if (sound->_fadeMusicOut) { - if (sound->_fadeStartTime + musicFadeTime > sound->_vm->_system->getMillis()) { - byte volume = (byte)((musicFadeTime - (sound->_vm->_system->getMillis() - sound->_fadeStartTime)) * sound->_musicVolume / musicFadeTime); - sound->updateChannelVolume(volume); - } else { - sound->_fadeStartTime = 0; - sound->_fadeMusicOut = false; - sound->_isMusicPlaying = false; - - sound->_eventFromMusic = true; - // from sound/midiparser.cpp - for (int i = 0; i < 128; ++i) { - for (int j = 0; j < 16; ++j) { - sound->send(0x80 | j | i << 8); - } - } - - for (int i = 0; i < 16; ++i) - sound->send(0x007BB0 | i); - } - } - - if (sound->_isMusicPlaying) { - sound->_eventFromMusic = true; - sound->_musicParser->onTimer(); - } - - if (sound->_isSfxPlaying) { - sound->_eventFromMusic = false; - sound->_sfxParser->onTimer(); - } -} - -void SoundMidiPC::playTrack(uint8 track) { - Common::StackLock lock(_mutex); - - if (_musicParser && (track != 0 || _nativeMT32) && _musicEnabled) { - _isMusicPlaying = true; - _fadeMusicOut = false; - _fadeStartTime = 0; - updateChannelVolume(_musicVolume); - _musicParser->setTrack(track); - _musicParser->jumpToTick(0); - _musicParser->setTempo(1); - _musicParser->property(MidiParser::mpAutoLoop, false); - } -} - -void SoundMidiPC::haltTrack() { - Common::StackLock lock(_mutex); - - if (_musicParser) { - _isMusicPlaying = false; - _fadeMusicOut = false; - _fadeStartTime = 0; - updateChannelVolume(_musicVolume); - _musicParser->setTrack(0); - _musicParser->jumpToTick(0); - _musicParser->setTempo(0); - } -} - -void SoundMidiPC::playSoundEffect(uint8 track) { - Common::StackLock lock(_mutex); - - if (_sfxParser && _sfxEnabled) { - _isSfxPlaying = true; - _sfxParser->setTrack(track); - _sfxParser->jumpToTick(0); - _sfxParser->setTempo(1); - _sfxParser->property(MidiParser::mpAutoLoop, false); - } -} - -void SoundMidiPC::beginFadeOut() { - _fadeMusicOut = true; - _fadeStartTime = _vm->_system->getMillis(); -} - -#pragma mark - - void KyraEngine_v1::snd_playTheme(int file, int track) { debugC(9, kDebugLevelMain | kDebugLevelSound, "KyraEngine_v1::snd_playTheme(%d, %d)", file, track); if (_curMusicTheme == file) diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h index 2f7ac27ac5..338da08d21 100644 --- a/engines/kyra/sound.h +++ b/engines/kyra/sound.h @@ -300,24 +300,20 @@ private: static const int _kyra1SoundTriggers[]; }; +class MidiOutput; + /** * MIDI output device. * * This device supports both MT-32 MIDI, as used in * Kyrandia 1 and 2, and GM MIDI, as used in Kyrandia 2. - * - * Currently it does not initialize the MT-32 output properly, - * so MT-32 output does sound a bit odd in some cases. - * - * TODO: this code needs some serious cleanup and rework - * to support MT-32 and GM properly. */ -class SoundMidiPC : public MidiDriver, public Sound { +class SoundMidiPC : public Sound { public: SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver); ~SoundMidiPC(); - kType getMusicType() const { return isMT32() ? kMidiMT32 : kMidiGM; } + kType getMusicType() const { return _nativeMT32 ? kMidiMT32 : kMidiGM; } bool init(); @@ -333,37 +329,11 @@ public: void beginFadeOut(); - //MidiDriver interface implementation - int open(); - void close(); - void send(uint32 b); - void metaEvent(byte type, byte *data, uint16 length); - - void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { } - uint32 getBaseTempo(void) { return _driver ? _driver->getBaseTempo() : 0; } - - //Channel allocation functions - MidiChannel *allocateChannel() { return 0; } - MidiChannel *getPercussionChannel() { return 0; } - - void setPassThrough(bool b) { _passThrough = b; } - void hasNativeMT32(bool nativeMT32); - bool isMT32() const { return _nativeMT32; } - private: - void internalLoadFile(Common::String file); - void updateChannelVolume(uint8 vol); - static void onTimer(void *data); // Our channel handling - uint8 _virChannel[16]; - uint8 _channelVolume[16]; - - MidiChannel *_channel[32]; - - // Music/Sfx volume uint8 _musicVolume; uint8 _sfxVolume; @@ -371,25 +341,18 @@ private: bool _fadeMusicOut; // Midi file related - Common::SharedPtr<byte> _midiFile; - Common::String _currentTrack; - - // Music related - MidiParser *_musicParser; + byte *_musicFile, *_sfxFile; + uint32 _musicFileSize, _sfxFileSize; - bool _isMusicPlaying; - bool _eventFromMusic; - - // Sfx related - MidiParser *_sfxParser; - - bool _isSfxPlaying; + MidiParser *_music; + MidiParser *_sfx[3]; // misc bool _nativeMT32; bool _useC55; MidiDriver *_driver; - bool _passThrough; + MidiOutput *_output; + Common::Mutex _mutex; }; diff --git a/engines/kyra/sound_midi.cpp b/engines/kyra/sound_midi.cpp new file mode 100644 index 0000000000..6b323bf7e3 --- /dev/null +++ b/engines/kyra/sound_midi.cpp @@ -0,0 +1,563 @@ +/* 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. + * + * $URL$ + * $Id$ + * + */ + +#include "kyra/sound.h" +#include "kyra/resource.h" + +#include "common/system.h" + +namespace Kyra { + +struct Controller { + byte controller; + byte value; +}; + +class MidiOutput : public MidiDriver { +public: + MidiOutput(OSystem *system, MidiDriver *output, bool isMT32, bool defaultMT32); + ~MidiOutput(); + + void initSource(int source); + void deinitSource(int source); + void stopNotesOnChannel(int channel); + + void setSoundSource(int source) { _curSource = source; } + + void send(uint32 b); + void sysEx(const byte *msg, uint16 length); + void metaEvent(byte type, byte *data, uint16 length); + + void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { _output->setTimerCallback(timerParam, timerProc); } + uint32 getBaseTempo(void) { return _output->getBaseTempo(); } + + // DUMMY + int open() { return 0; } + void close() {} + + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } +private: + void sendIntern(const byte event, const byte channel, byte param1, const byte param2); + void sendSysEx(const byte p1, const byte p2, const byte p3, const byte *buffer, const int size); + + OSystem *_system; + MidiDriver *_output; + + uint32 _lastSysEx; + bool _isMT32; + bool _defaultMT32; + + enum { + kChannelLocked = 0x80, + kChannelProtected = 0x40 + }; + + struct Channel { + byte flags; + + byte program; + int16 pitchWheel; + + byte noteCount; + + Controller controllers[9]; + } _channels[16]; + + int lockChannel(); + void unlockChannel(int channel); + + int _curSource; + + struct SoundSource { + int8 channelMap[16]; + byte channelProgram[16]; + int16 channelPW[16]; + Controller controllers[16][9]; + + struct Note { + byte channel; + byte note; + }; + + Note notes[32]; + } _sources[4]; +}; + +MidiOutput::MidiOutput(OSystem *system, MidiDriver *output, bool isMT32, bool defaultMT32) : _system(system), _output(output), _lastSysEx(0) { + _isMT32 = isMT32; + _defaultMT32 = defaultMT32; + + int ret = _output->open(); + if (ret != MidiDriver::MERR_ALREADY_OPEN && ret != 0) + error("Couldn't open midi driver"); + + static const Controller defaultControllers[] = { + { 0x07, 0x7F }, { 0x01, 0x00 }, { 0x0A, 0x40 }, + { 0x0B, 0x7F }, { 0x40, 0x00 }, { 0x72, 0x00 }, + { 0x6E, 0x00 }, { 0x6F, 0x00 }, { 0x70, 0x00 } + }; + + static const byte defaultPrograms[] = { + 0x00, 0x44, 0x30, 0x5F, 0x4E, 0x29, 0x03, 0x6E, 0x7A, 0xFF + }; + + static const byte sysEx1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + static const byte sysEx2[] = { 3, 4, 3, 4, 3, 4, 3, 4, 4 }; + static const byte sysEx3[] = { 0, 3, 2 }; + + sendSysEx(0x7F, 0x00, 0x00, sysEx1, 1); + sendSysEx(0x10, 0x00, 0x0D, sysEx1, 9); + sendSysEx(0x10, 0x00, 0x04, sysEx2, 9); + sendSysEx(0x10, 0x00, 0x01, sysEx3, 3); + + memset(_channels, 0, sizeof(_channels)); + for (int i = 0; i < 16; ++i) { + for (int j = 0; j < 9; ++j) + _channels[i].controllers[j] = defaultControllers[j]; + _channels[i].pitchWheel = -1; + _channels[i].program = 0xFF; + } + + for (int i = 0; i < 9; ++i) { + for (int j = 1; j <= 9; ++j) + sendIntern(0xB0, j, defaultControllers[i].controller, defaultControllers[i].value); + } + + for (int i = 1; i <= 9; ++i) { + sendIntern(0xE0, i, 0x00, 0x40); + if (defaultPrograms[i] != 0xFF) + sendIntern(0xC0, i, defaultPrograms[i], 0x00); + } +} + + +MidiOutput::~MidiOutput() { + _output->close(); + delete _output; +} + +void MidiOutput::send(uint32 b) { + const byte event = b & 0xF0; + const byte channel = b & 0x0F; + const byte param1 = (b >> 8) & 0xFF; + const byte param2 = (b >> 16) & 0xFF; + + if (event == 0xE0) { // Pitch-Wheel + _channels[channel].pitchWheel = + _sources[_curSource].channelPW[channel] = (param2 << 8) | param1; + } else if (event == 0xC0) { // Program change + _channels[channel].program = + _sources[_curSource].channelProgram[channel] = param1; + } else if (event == 0xB0) { // Controller change + for (int i = 0; i < 9; ++i) { + Controller &cont = _sources[_curSource].controllers[channel][i]; + if (cont.controller == param1) { + cont.value = param2; + break; + } + } + + if (param1 == 0x6E) { // Lock Channel + if (param2 >= 0x40) { // Lock Channel + int chan = lockChannel(); + if (chan < 0) + chan = channel; + _sources[_curSource].channelMap[channel] = chan; + } else { // Unlock Channel + stopNotesOnChannel(channel); + unlockChannel(_sources[_curSource].channelMap[channel]); + _sources[_curSource].channelMap[channel] = channel; + } + } else if (param1 == 0x6F) { // Protect Channel + if (param2 >= 0x40) { // Protect Channel + _channels[channel].flags |= kChannelProtected; + } else { // Unprotect Channel + _channels[channel].flags &= ~kChannelProtected; + } + } else if (param1 == 0x7B) { // All notes off + // FIXME: Since the XMIDI parsers sends this + // on track change, we simply ignore it. + return; + } + } else if (event == 0x90 || event == 0x80) { // Note On/Off + if (!(_channels[channel].flags & kChannelLocked)) { + const bool remove = (event == 0x80) || (param2 == 0x00); + int note = -1; + + for (int i = 0; i < 32; ++i) { + if (remove) { + if (_sources[_curSource].notes[i].channel == channel && + _sources[_curSource].notes[i].note == param1) { + note = i; + break; + } + } else { + if (_sources[_curSource].notes[i].channel == 0xFF) { + note = i; + break; + } + } + } + + if (note != -1) { + if (remove) { + _sources[_curSource].notes[note].channel = 0xFF; + + --_channels[_sources[_curSource].channelMap[channel]].noteCount; + } else { + _sources[_curSource].notes[note].channel = channel; + _sources[_curSource].notes[note].note = param1; + + ++_channels[_sources[_curSource].channelMap[channel]].noteCount; + } + + sendIntern(event, _sources[_curSource].channelMap[channel], param1, param2); + } + } + return; + } + + if (!(_channels[channel].flags & kChannelLocked)) + sendIntern(event, _sources[_curSource].channelMap[channel], param1, param2); +} + +void MidiOutput::sendIntern(const byte event, const byte channel, byte param1, const byte param2) { + if (event == 0xE0) { + _channels[channel].pitchWheel = (param2 << 8) | param1; + } else if (event == 0xC0) { + _channels[channel].program = param1; + + // MT32 -> GM conversion + if (!_isMT32 && _defaultMT32) + param1 = _mt32ToGm[param1]; + } + + _output->send(event | channel, param1, param2); +} + +void MidiOutput::sysEx(const byte *msg, uint16 length) { + uint32 curTime = _system->getMillis(); + + if (_lastSysEx + 40 > curTime) + _system->delayMillis(_lastSysEx + 40 - curTime); + + _output->sysEx(msg, length); + + _lastSysEx = _system->getMillis(); +} + +void MidiOutput::sendSysEx(const byte p1, const byte p2, const byte p3, const byte *buffer, const int size) { + int bufferSize = 8 + size; + byte *outBuffer = new byte[bufferSize]; + assert(outBuffer); + + outBuffer[0] = 0x41; + outBuffer[1] = 0x10; + outBuffer[2] = 0x16; + outBuffer[3] = 0x12; + + outBuffer[4] = p1; + outBuffer[5] = p2; + outBuffer[6] = p3; + + memcpy(outBuffer + 7, buffer, size); + + uint16 checkSum = p1 + p2 + p3; + for (int i = 0; i < size; ++i) + checkSum += buffer[i]; + checkSum &= 0x7F; + checkSum -= 0x80; + checkSum = -checkSum; + checkSum &= 0x7F; + + outBuffer[7+size] = checkSum; + + sysEx(outBuffer, bufferSize); + + delete[] outBuffer; +} + +void MidiOutput::metaEvent(byte type, byte *data, uint16 length) { + if (type == 0x2F) { // End of Track + deinitSource(_curSource); + //XXX + } + + _output->metaEvent(type, data, length); +} + +void MidiOutput::initSource(int source) { + memset(_sources[source].notes, -1, sizeof(_sources[source].notes)); + + for (int i = 0; i < 16; ++i) { + _sources[source].channelMap[i] = i; + _sources[source].channelProgram[i] = 0xFF; + _sources[source].channelPW[i] = -1; + + for (int j = 0; j < 9; ++j) + _sources[source].controllers[i][j] = _channels[i].controllers[j]; + } +} + +void MidiOutput::deinitSource(int source) { + for (int i = 0; i < 16; ++i) { + for (int j = 0; j < 9; ++j) { + const Controller &cont = _sources[source].controllers[i][j]; + + if (cont.controller == 0x40) { + if (cont.value >= 0x40) + sendIntern(0xB0, i, 0x40, 0); + } else if (cont.controller == 0x6E) { + if (cont.value >= 0x40) { + stopNotesOnChannel(/*_sources[source].channelMap[i]*/i); + unlockChannel(_sources[source].channelMap[i]); + _sources[source].channelMap[i] = i; + } + } else if (cont.controller == 0x6F) { + if (cont.value >= 0x40) + _channels[i].flags &= ~kChannelProtected; + } else if (cont.controller == 0x70) { + if (cont.value >= 0x40) + sendIntern(0xB0, i, 0x70, 0); + } + } + } +} + +int MidiOutput::lockChannel() { + int channel = -1; + int notes = 0xFF; + byte flags = kChannelLocked | kChannelProtected; + + while (channel == -1) { + for (int i = _isMT32 ? 8 : 15; i >= 1; --i) { + if (_channels[i].flags & flags) + continue; + if (_channels[i].noteCount < notes) { + channel = i; + notes = _channels[i].noteCount; + } + } + + if (channel == -1) { + if (flags & kChannelProtected) + flags &= ~kChannelProtected; + else + break; + } + } + + if (channel == -1) + return -1; + + sendIntern(0xB0, channel, 0x40, 0); + stopNotesOnChannel(channel); + _channels[channel].noteCount = 0; + _channels[channel].flags |= kChannelLocked; + + return channel; +} + +void MidiOutput::unlockChannel(int channel) { + if (!(_channels[channel].flags & kChannelLocked)) + return; + + _channels[channel].flags &= ~kChannelLocked; + _channels[channel].noteCount = 0; + sendIntern(0xB0, channel, 0x40, 0); + sendIntern(0xB0, channel, 0x7B, 0); + + for (int i = 0; i < 9; ++i) { + if (_channels[channel].controllers[i].value != 0xFF) + sendIntern(0xB0, channel, _channels[channel].controllers[i].controller, _channels[channel].controllers[i].value); + } + + if (_channels[channel].program != 0xFF) + sendIntern(0xC0, channel, _channels[channel].program, 0); + + if (_channels[channel].pitchWheel != -1) + sendIntern(0xE0, channel, _channels[channel].pitchWheel & 0xFF, (_channels[channel].pitchWheel >> 8) & 0xFF); +} + +void MidiOutput::stopNotesOnChannel(int channel) { + for (int i = 0; i < 4; ++i) { + SoundSource &sound = _sources[i]; + for (int j = 0; j < 32; ++j) { + if (sound.notes[j].channel == channel) { + sound.notes[j].channel = 0xFF; + sendIntern(0x80, sound.channelMap[channel], sound.notes[j].note, 0); + --_channels[sound.channelMap[channel]].noteCount; + } + } + } +} + +#pragma mark - + +SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver) : Sound(vm, mixer) { + _driver = driver; + _output = 0; + + _musicFile = _sfxFile = 0; + _musicFileSize = _sfxFileSize = 0; + + _music = MidiParser::createParser_XMIDI(); + assert(_music); + for (int i = 0; i < 3; ++i) { + _sfx[i] = MidiParser::createParser_XMIDI(); + assert(_sfx[i]); + } +} + +SoundMidiPC::~SoundMidiPC() { + Common::StackLock lock(_mutex); + _output->setTimerCallback(0, 0); + + delete _music; + for (int i = 0; i < 3; ++i) + delete _sfx[i]; + + delete _output; // This automatically frees _driver (!) + + if (_musicFile != _sfxFile) + delete[] _sfxFile; + + delete[] _musicFile; + + _nativeMT32 = false; + _useC55 = false; +} + +void SoundMidiPC::hasNativeMT32(bool nativeMT32) { + _nativeMT32 = nativeMT32; + + // C55 is XMIDI for General MIDI instruments + if (!_nativeMT32 && _vm->game() != GI_KYRA1) + _useC55 = true; + else + _useC55 = false; +} + +bool SoundMidiPC::init() { + _output = new MidiOutput(_vm->_system, _driver, _nativeMT32, !_useC55); + assert(_output); + _output->setTimerCallback(this, SoundMidiPC::onTimer); + + /*loadSoundFile("INTRO"); + playTrack(0); + while (_music->isPlaying()) + _vm->_system->delayMillis(10);*/ + + return true; +} + +void SoundMidiPC::updateVolumeSettings() { + //XXX +} + +void SoundMidiPC::loadSoundFile(uint file) { + loadSoundFile(fileListEntry(file)); +} + +void SoundMidiPC::loadSoundFile(Common::String file) { + Common::StackLock lock(_mutex); + + file += _useC55 ? ".C55" : ".XMI"; + if (!_vm->resource()->exists(file.c_str())) + return; + + if (_sfxFile != _musicFile) + delete[] _sfxFile; + delete[] _musicFile; + + _musicFile = _sfxFile = _vm->resource()->fileData(file.c_str(), &_musicFileSize); + _sfxFileSize = _musicFileSize; + + _music->loadMusic(_musicFile, _musicFileSize); + _music->setMidiDriver(_output); + _music->setTempo(_output->getBaseTempo()); + _music->setTimerRate(_output->getBaseTempo()); + _music->stopPlaying(); + + for (int i = 0; i < 3; ++i) { + _sfx[i]->loadMusic(_musicFile, _musicFileSize); + _sfx[i]->setMidiDriver(_output); + _sfx[i]->setTempo(_output->getBaseTempo()); + _sfx[i]->setTimerRate(_output->getBaseTempo()); + _sfx[i]->stopPlaying(); + } +} + +void SoundMidiPC::playTrack(uint8 track) { + Common::StackLock lock(_mutex); + _output->initSource(0); + _music->setTrack(track); +} + +void SoundMidiPC::haltTrack() { + Common::StackLock lock(_mutex); + + // Some handling we need to interrupt a track + // properly + for (int i = 0; i < 16; ++i) + _output->stopNotesOnChannel(i); + _output->deinitSource(0); + + _music->stopPlaying(); +} + +void SoundMidiPC::playSoundEffect(uint8 track) { + Common::StackLock lock(_mutex); + for (int i = 0; i < 3; ++i) { + if (!_sfx[i]->isPlaying()) { + _output->initSource(i+1); + _sfx[i]->setTrack(track); + break; + } + } +} + +void SoundMidiPC::beginFadeOut() { + //FIXME: implement properly + haltTrack(); +} + +void SoundMidiPC::onTimer(void *data) { + SoundMidiPC *midi = (SoundMidiPC *)data; + + Common::StackLock lock(midi->_mutex); + + midi->_output->setSoundSource(0); + midi->_music->onTimer(); + + for (int i = 0; i < 3; ++i) { + midi->_output->setSoundSource(i+1); + midi->_sfx[i]->onTimer(); + } +} + +} // end of namespace Kyra + diff --git a/sound/midiparser.cpp b/sound/midiparser.cpp index b2897dadfa..389de58ea4 100644 --- a/sound/midiparser.cpp +++ b/sound/midiparser.cpp @@ -209,8 +209,7 @@ void MidiParser::onTimer() { jumpToTick(0); parseNextEvent(_next_event); } else { - allNotesOff(); - resetTracking(); + stopPlaying(); _driver->metaEvent(info.ext.type, info.ext.data, (uint16)info.length); } return; @@ -302,6 +301,17 @@ bool MidiParser::setTrack(int track) { return true; } +void MidiParser::stopPlaying() { + if (_smartJump) + hangAllActiveNotes(); + else + allNotesOff(); + resetTracking(); + + _active_track = _num_tracks+1; + memset(_active_notes, 0, sizeof(_active_notes)); +} + void MidiParser::hangAllActiveNotes() { // Search for note off events until we have // accounted for every active note. diff --git a/sound/midiparser.h b/sound/midiparser.h index 304a9d9f82..0c0b9cdb8d 100644 --- a/sound/midiparser.h +++ b/sound/midiparser.h @@ -283,9 +283,7 @@ protected: bool _smartJump; //!< Support smart expiration of hanging notes when jumping bool _centerPitchWheelOnUnload; //!< Center the pitch wheels when unloading a song - // FIXME: Was 32 here, Kyra tracks use 120(!!!) which seems wrong. this is a hacky - // workaround until situation is investigated. - byte * _tracks[120]; //!< Multi-track MIDI formats are supported, up to 120 tracks. + byte *_tracks[120]; //!< Multi-track MIDI formats are supported, up to 120 tracks. byte _num_tracks; //!< Count of total tracks for multi-track MIDI formats. 1 for single-track formats. byte _active_track; //!< Keeps track of the currently active track, in multi-track formats. @@ -366,6 +364,9 @@ public: void setTempo(uint32 tempo); void onTimer(); + bool isPlaying() const { return (_position._play_pos != 0); } + void stopPlaying(); + bool setTrack(int track); bool jumpToTick(uint32 tick, bool fireEvents = false); |