aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/kyra/module.mk1
-rw-r--r--engines/kyra/sound.cpp319
-rw-r--r--engines/kyra/sound.h57
-rw-r--r--engines/kyra/sound_midi.cpp563
-rw-r--r--sound/midiparser.cpp14
-rw-r--r--sound/midiparser.h7
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);