From 0a1a9194064a96673b45d856f06dbb689838d746 Mon Sep 17 00:00:00 2001 From: Eugene Sandulenko Date: Tue, 15 Jun 2010 10:34:45 +0000 Subject: AGI: Implement FR #1913900. FR #1913900: "AGI: MIDI support". Currently it gets selected when Adlib is chosen. Finding a better way to do it is a todo. Also default instrument mapping is plain. Based on original code by Jens. Christian Restemeier. svn-id: r49751 --- engines/agi/agi.cpp | 5 +- engines/agi/loader_v2.cpp | 2 +- engines/agi/loader_v3.cpp | 2 +- engines/agi/module.mk | 1 + engines/agi/sound.cpp | 71 +++++++--- engines/agi/sound.h | 17 ++- engines/agi/sound_midi.cpp | 340 +++++++++++++++++++++++++++++++++++++++++++++ engines/agi/sound_midi.h | 113 +++++++++++++++ 8 files changed, 524 insertions(+), 27 deletions(-) create mode 100755 engines/agi/sound_midi.cpp create mode 100755 engines/agi/sound_midi.h diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index c2c6d10bfe..d89b10bb32 100644 --- a/engines/agi/agi.cpp +++ b/engines/agi/agi.cpp @@ -583,10 +583,13 @@ void AgiEngine::initialize() { } else if (getPlatform() == Common::kPlatformCoCo3) { _soundemu = SOUND_EMU_COCO3; } else { - switch (MidiDriver::detectMusicDriver(MDT_PCSPK)) { + switch (MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_ADLIB)) { case MD_PCSPK: _soundemu = SOUND_EMU_PC; break; + case MD_ADLIB: + _soundemu = SOUND_EMU_MIDI; + break; default: _soundemu = SOUND_EMU_NONE; break; diff --git a/engines/agi/loader_v2.cpp b/engines/agi/loader_v2.cpp index 3d1c4fa2cf..de6f8d0653 100644 --- a/engines/agi/loader_v2.cpp +++ b/engines/agi/loader_v2.cpp @@ -232,7 +232,7 @@ int AgiLoader_v2::loadResource(int t, int n) { if (data != NULL) { // Freeing of the raw resource from memory is delegated to the createFromRawResource-function - _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound); + _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound, _vm->_soundemu); _vm->_game.dirSound[n].flags |= RES_LOADED; } else { ec = errBadResource; diff --git a/engines/agi/loader_v3.cpp b/engines/agi/loader_v3.cpp index 4227745c5f..4ee35d73eb 100644 --- a/engines/agi/loader_v3.cpp +++ b/engines/agi/loader_v3.cpp @@ -316,7 +316,7 @@ int AgiLoader_v3::loadResource(int t, int n) { data = loadVolRes(&_vm->_game.dirSound[n]); if (data != NULL) { // Freeing of the raw resource from memory is delegated to the createFromRawResource-function - _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound); + _vm->_game.sounds[n] = AgiSound::createFromRawResource(data, _vm->_game.dirSound[n].len, n, *_vm->_sound, _vm->_soundemu); _vm->_game.dirSound[n].flags |= RES_LOADED; } else { ec = errBadResource; diff --git a/engines/agi/module.mk b/engines/agi/module.mk index 3b7741443d..4f27447dbd 100644 --- a/engines/agi/module.mk +++ b/engines/agi/module.mk @@ -31,6 +31,7 @@ MODULE_OBJS := \ saveload.o \ sound.o \ sound_2gs.o \ + sound_midi.o \ sprite.o \ text.o \ view.o \ diff --git a/engines/agi/sound.cpp b/engines/agi/sound.cpp index 014a3dba1a..b9a118c95f 100644 --- a/engines/agi/sound.cpp +++ b/engines/agi/sound.cpp @@ -29,9 +29,12 @@ #include "common/random.h" #include "common/str-array.h" +#include "sound/mididrv.h" + #include "agi/agi.h" #include "agi/sound_2gs.h" +#include "agi/sound_midi.h" namespace Agi { @@ -41,7 +44,7 @@ namespace Agi { // TODO: add support for variable sampling rate in the output device // -AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager) { +AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager, int soundemu) { if (data == NULL || len < 2) // Check for too small resource or no resource at all return NULL; uint16 type = READ_LE_UINT16(data); @@ -50,9 +53,13 @@ AgiSound *AgiSound::createFromRawResource(uint8 *data, uint32 len, int resnum, S case AGI_SOUND_SAMPLE: return new IIgsSample(data, len, resnum, manager); case AGI_SOUND_MIDI: - return new IIgsMidi (data, len, resnum, manager); + return new IIgsMidi(data, len, resnum, manager); case AGI_SOUND_4CHN: - return new PCjrSound (data, len, resnum, manager); + if (soundemu == SOUND_EMU_MIDI) { + return new MIDISound(data, len, resnum, manager); + } else { + return new PCjrSound(data, len, resnum, manager); + } } warning("Sound resource (%d) has unknown type (0x%04x). Not using the sound", resnum, type); @@ -165,24 +172,29 @@ void SoundMgr::startSound(int resnum, int flag) { ((IIgsMidi *) _vm->_game.sounds[_playingSound])->rewind(); break; case AGI_SOUND_4CHN: - PCjrSound *pcjrSound = (PCjrSound *) _vm->_game.sounds[resnum]; + if (_vm->_soundemu == SOUND_EMU_MIDI) { + _musicPlayer->playMIDI((MIDISound *)_vm->_game.sounds[resnum]); + } else { - // Initialize channel info - for (i = 0; i < NUM_CHANNELS; i++) { - _chn[i].type = type; - _chn[i].flags = AGI_SOUND_LOOP; + PCjrSound *pcjrSound = (PCjrSound *) _vm->_game.sounds[resnum]; - if (_env) { - _chn[i].flags |= AGI_SOUND_ENVELOPE; - _chn[i].adsr = AGI_SOUND_ENV_ATTACK; - } + // Initialize channel info + for (i = 0; i < NUM_CHANNELS; i++) { + _chn[i].type = type; + _chn[i].flags = AGI_SOUND_LOOP; + + if (_env) { + _chn[i].flags |= AGI_SOUND_ENVELOPE; + _chn[i].adsr = AGI_SOUND_ENV_ATTACK; + } - _chn[i].ins = _waveform; - _chn[i].size = WAVEFORM_SIZE; - _chn[i].ptr = pcjrSound->getVoicePointer(i % 4); - _chn[i].timer = 0; - _chn[i].vol = 0; - _chn[i].end = 0; + _chn[i].ins = _waveform; + _chn[i].size = WAVEFORM_SIZE; + _chn[i].ptr = pcjrSound->getVoicePointer(i % 4); + _chn[i].timer = 0; + _chn[i].vol = 0; + _chn[i].end = 0; + } } break; } @@ -213,6 +225,10 @@ void SoundMgr::stopSound() { _gsSound->stopSounds(); } + if (_vm->_soundemu == SOUND_EMU_MIDI) { + _musicPlayer->stop(); + } + _playingSound = -1; } } @@ -240,6 +256,8 @@ int SoundMgr::initSound() { break; case SOUND_EMU_COCO3: break; + case SOUND_EMU_MIDI: + break; } report("Initializing sound:\n"); @@ -251,7 +269,8 @@ int SoundMgr::initSound() { report("disabled\n"); } - _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); + if (_vm->_soundemu != SOUND_EMU_MIDI) + _mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); return r; } @@ -607,12 +626,21 @@ SoundMgr::SoundMgr(AgiBase *agi, Audio::Mixer *pMixer) : _chn() { _waveform = 0; _disabledMidi = false; _useChorus = true; // FIXME: Currently always true? + _midiDriver = 0; _gsSound = new IIgsSoundMgr; + + if (_vm->_soundemu == SOUND_EMU_MIDI) { + MidiDriverType midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB); + + _midiDriver = MidiDriver::createMidi(midiDriver); + _musicPlayer = new MusicPlayer(_midiDriver, this); + } } void SoundMgr::premixerCall(int16 *data, uint len) { - fillAudio(this, data, len); + if (_vm->_soundemu != SOUND_EMU_MIDI) + fillAudio(this, data, len); } void SoundMgr::setVolume(uint8 volume) { @@ -622,6 +650,9 @@ void SoundMgr::setVolume(uint8 volume) { SoundMgr::~SoundMgr() { free(_sndBuffer); delete _gsSound; + + delete _musicPlayer; + delete _midiDriver; } } // End of namespace Agi diff --git a/engines/agi/sound.h b/engines/agi/sound.h index ab0c9e20e5..16ec81d19a 100644 --- a/engines/agi/sound.h +++ b/engines/agi/sound.h @@ -29,6 +29,8 @@ #include "sound/audiostream.h" #include "sound/mixer.h" +class MidiDriver; + namespace Agi { #define BUFFER_SIZE 410 @@ -40,6 +42,7 @@ namespace Agi { #define SOUND_EMU_AMIGA 4 #define SOUND_EMU_APPLE2GS 5 #define SOUND_EMU_COCO3 6 +#define SOUND_EMU_MIDI 7 #define WAVEFORM_SIZE 64 #define ENV_ATTACK 10000 /**< envelope attack rate */ @@ -143,7 +146,7 @@ public: * from memory using free() or delegate the responsibility onwards to some other * function! */ - static AgiSound *createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + static AgiSound *createFromRawResource(uint8 *data, uint32 len, int resnum, SoundMgr &manager, int soundemu); protected: SoundMgr &_manager; ///< AGI sound manager object @@ -154,7 +157,7 @@ protected: class PCjrSound : public AgiSound { public: PCjrSound(uint8 *data, uint32 len, int resnum, SoundMgr &manager); - ~PCjrSound() { if (_data != NULL) free(_data); } + ~PCjrSound() { free(_data); } virtual uint16 type() { return _type; } const uint8 *getVoicePointer(uint voiceNum); protected: @@ -166,11 +169,11 @@ protected: class AgiEngine; class AgiBase; class IIgsSoundMgr; +class MusicPlayer; struct IIgsExeInfo; class SoundMgr : public Audio::AudioStream { - AgiBase *_vm; public: SoundMgr(AgiBase *agi, Audio::Mixer *pMixer); @@ -196,15 +199,21 @@ public: return 22050; } + int _endflag; + AgiBase *_vm; + private: Audio::Mixer *_mixer; Audio::SoundHandle _soundHandle; + + MusicPlayer *_musicPlayer; + MidiDriver *_midiDriver; + uint32 _sampleRate; bool _playing; ChannelInfo _chn[NUM_CHANNELS]; IIgsSoundMgr *_gsSound; - int _endflag; int _playingSound; uint8 _env; bool _disabledMidi; diff --git a/engines/agi/sound_midi.cpp b/engines/agi/sound_midi.cpp new file mode 100755 index 0000000000..b31a4228fb --- /dev/null +++ b/engines/agi/sound_midi.cpp @@ -0,0 +1,340 @@ +/* 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$ + * + */ + +// Code is based on: +// +// A very simple program, that converts an AGI-song into a MIDI-song. +// Feel free to use it for anything. +// +// The default instrument is "piano" for all the channels, what gives +// good results on most games. But I found, that some songs are interesting +// with other instruments. If you want to experiment, modify the "instr" +// array. +// +// Timing is not perfect, yet. It plays correct, when I use the +// Gravis-Midiplayer, but the songs are too fast when I use playmidi on +// Linux. +// +// Original program developed by Jens. Christian Restemeier +// + +// MIDI and digital music class + +#include "sound/audiostream.h" +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "common/config-manager.h" +#include "common/file.h" +#include "common/stream.h" + +#include "agi/agi.h" + +#include "agi/sound.h" +#include "agi/sound_midi.h" + +#define SPEED_FACTOR 6 + +namespace Agi { + +static uint32 convertSND2MIDI(byte *snddata, byte **data); + +MIDISound::MIDISound(uint8 *data, uint32 len, int resnum, SoundMgr &manager) : AgiSound(manager) { + _data = data; // Save the resource pointer + _len = len; // Save the resource's length + _type = READ_LE_UINT16(data); // Read sound resource's type + _isValid = (_type == AGI_SOUND_4CHN) && (_data != NULL) && (_len >= 2); + + if (!_isValid) // Check for errors + warning("Error creating MIDI sound from resource %d (Type %d, length %d)", resnum, _type, len); +} + +MusicPlayer::MusicPlayer(MidiDriver *driver, SoundMgr *manager) : _parser(0), _driver(driver), _isPlaying(false), _passThrough(false), _isGM(false), _manager(manager) { + memset(_channel, 0, sizeof(_channel)); + memset(_channelVolume, 255, sizeof(_channelVolume)); + _masterVolume = 0; + this->open(); + _smfParser = MidiParser::createParser_SMF(); + _midiMusicData = NULL; +} + +MusicPlayer::~MusicPlayer() { + _driver->setTimerCallback(NULL, NULL); + stop(); + this->close(); + _smfParser->setMidiDriver(NULL); + delete _smfParser; + delete[] _midiMusicData; +} + +void MusicPlayer::setChannelVolume(int channel) { + int newVolume = _channelVolume[channel] * _masterVolume / 255; + _channel[channel]->volume(newVolume); +} + +void MusicPlayer::setVolume(int volume) { + Common::StackLock lock(_mutex); + + volume = CLIP(volume, 0, 255); + if (_masterVolume == volume) + return; + _masterVolume = volume; + + for (int i = 0; i < 16; ++i) { + if (_channel[i]) { + setChannelVolume(i); + } + } +} + +int MusicPlayer::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 MusicPlayer::close() { + stop(); + if (_driver) + _driver->close(); + _driver = 0; +} + +void MusicPlayer::send(uint32 b) { + if (_passThrough) { + _driver->send(b); + return; + } + + byte channel = (byte)(b & 0x0F); + if ((b & 0xFFF0) == 0x07B0) { + // Adjust volume changes by master volume + byte volume = (byte)((b >> 16) & 0x7F); + _channelVolume[channel] = volume; + volume = volume * _masterVolume / 255; + b = (b & 0xFF00FFFF) | (volume << 16); + } else if ((b & 0xF0) == 0xC0 && !_isGM && !_nativeMT32) { + 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[b & 0x0F]) + return; + } + + if (!_channel[channel]) { + _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel(); + // If a new channel is allocated during the playback, make sure + // its volume is correctly initialized. + if (_channel[channel]) + setChannelVolume(channel); + } + + if (_channel[channel]) + _channel[channel]->send(b); +} + +void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) { + + switch (type) { + case 0x2F: // End of Track + stop(); + break; + default: + //warning("Unhandled meta event: %02x", type); + break; + } +} + +void MusicPlayer::onTimer(void *refCon) { + MusicPlayer *music = (MusicPlayer *)refCon; + Common::StackLock lock(music->_mutex); + + if (music->_parser) + music->_parser->onTimer(); +} + +void MusicPlayer::playMIDI(MIDISound *track) { + stop(); + + _isGM = true; + + // Convert AGI Sound data to MIDI + int midiMusicSize = convertSND2MIDI(track->_data, &_midiMusicData); + + if (_smfParser->loadMusic(_midiMusicData, midiMusicSize)) { + MidiParser *parser = _smfParser; + parser->setTrack(0); + parser->setMidiDriver(this); + parser->setTimerRate(getBaseTempo()); + parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1); + + _parser = parser; + + syncVolume(); + + _isPlaying = true; + } +} + +void MusicPlayer::stop() { + Common::StackLock lock(_mutex); + + if (!_isPlaying) + return; + + _isPlaying = false; + if (_parser) { + _parser->unloadMusic(); + _parser = NULL; + } + + if (_manager->_endflag != -1) + _manager->_vm->setflag(_manager->_endflag, true); +} + +void MusicPlayer::pause() { + setVolume(-1); + _isPlaying = false; +} + +void MusicPlayer::resume() { + syncVolume(); + _isPlaying = true; +} + +void MusicPlayer::syncVolume() { + int volume = ConfMan.getInt("music_volume"); + if (ConfMan.getBool("mute")) { + volume = -1; + } + setVolume(volume); +} + +/* channel / intrument setup: */ + +/* most songs are good with this: */ +unsigned char instr[] = {0, 0, 0}; + +/* cool for sq2: +unsigned char instr[] = {50, 51, 19}; +*/ + +static void writeDelta(Common::MemoryWriteStreamDynamic *st, int32 delta) { + int32 i; + + i = delta >> 21; if (i > 0) st->writeByte((i & 127) | 128); + i = delta >> 14; if (i > 0) st->writeByte((i & 127) | 128); + i = delta >> 7; if (i > 0) st->writeByte((i & 127) | 128); + st->writeByte(delta & 127); +} + +static uint32 convertSND2MIDI(byte *snddata, byte **data) { + int32 lp, ep; + int n; + double ll; + + Common::MemoryWriteStreamDynamic st; + + ll = log10(pow(2.0, 1.0 / 12.0)); + + /* Header */ + st.write("MThd", 4); + st.writeUint32BE(6); + st.writeUint16BE(1); /* mode */ + st.writeUint16BE(3); /* number of tracks */ + st.writeUint16BE(192); /* ticks / quarter */ + + for (n = 0; n < 3; n++) { + uint16 start, end, pos; + + st.write("MTrk", 4); + lp = st.pos(); + st.writeUint32BE(0); /* chunklength */ + writeDelta(&st, 0); /* set instrument */ + st.writeByte(0xc0 + n); + st.writeByte(instr[n]); + start = snddata[n * 2 + 0] | (snddata[n * 2 + 1] << 8); + end = ((snddata[n * 2 + 2] | (snddata[n * 2 + 3] << 8))) - 5; + + for (pos = start; pos < end; pos += 5) { + uint16 freq, dur; + dur = (snddata[pos + 0] | (snddata[pos + 1] << 8)) * SPEED_FACTOR; + freq = ((snddata[pos + 2] & 0x3F) << 4) + (snddata[pos + 3] & 0x0F); + if (snddata[pos + 2] > 0) { + double fr; + int note; + /* I don't know, what frequency equals midi note 0 ... */ + /* This moves the song 4 octaves down: */ + fr = (log10(111860.0 / (double)freq) / ll) - 48; + note = (int)floor(fr + 0.5); + if (note < 0) note = 0; + if (note > 127) note = 127; + /* note on */ + writeDelta(&st, 0); + st.writeByte(144 + n); + st.writeByte(note); + st.writeByte(100); + /* note off */ + writeDelta(&st, dur); + st.writeByte(128 + n); + st.writeByte(note); + st.writeByte(0); + } else { + /* note on */ + writeDelta(&st, 0); + st.writeByte(144 + n); + st.writeByte(0); + st.writeByte(0); + /* note off */ + writeDelta(&st, dur); + st.writeByte(128 + n); + st.writeByte(0); + st.writeByte(0); + } + } + writeDelta(&st, 0); + st.writeByte(0xff); + st.writeByte(0x2f); + st.writeByte(0x0); + ep = st.pos(); + st.seek(lp, SEEK_SET); + st.writeUint32BE((ep - lp) - 4); + st.seek(ep, SEEK_SET); + } + + *data = st.getData(); + + return st.pos(); +} + +} // End of namespace Agi diff --git a/engines/agi/sound_midi.h b/engines/agi/sound_midi.h new file mode 100755 index 0000000000..48216ceb12 --- /dev/null +++ b/engines/agi/sound_midi.h @@ -0,0 +1,113 @@ +/* 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$ + * + */ + +// Music class + +#ifndef AGI_SOUND_MIDI_H +#define AGI_SOUND_MIDI_H + +#include "sound/mididrv.h" +#include "sound/midiparser.h" +#include "common/mutex.h" + +namespace Agi { + +class MIDISound : public AgiSound { +public: + MIDISound(uint8 *data, uint32 len, int resnum, SoundMgr &manager); + ~MIDISound() { free(_data); } + virtual uint16 type() { return _type; } + uint8 *_data; ///< Raw sound resource data + uint32 _len; ///< Length of the raw sound resource + +protected: + uint16 _type; ///< Sound resource type +}; + +class MusicPlayer : public MidiDriver { +public: + MusicPlayer(MidiDriver *driver, SoundMgr *manager); + ~MusicPlayer(); + + bool isPlaying() { return _isPlaying; } + void setPlaying(bool playing) { _isPlaying = playing; } + + void setVolume(int volume); + int getVolume() { return _masterVolume; } + void syncVolume(); + + void setNativeMT32(bool b) { _nativeMT32 = b; } + bool hasNativeMT32() { return _nativeMT32; } + void playMIDI(MIDISound *track); + void stop(); + void pause(); + void resume(); + void setLoop(bool loop) { _looping = loop; } + void setPassThrough(bool b) { _passThrough = b; } + + void setGM(bool isGM) { _isGM = isGM; } + + // 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() { return _driver ? _driver->getBaseTempo() : 0; } + + // Channel allocation functions + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + MidiParser *_parser; + Common::Mutex _mutex; + +protected: + + static void onTimer(void *data); + void setChannelVolume(int channel); + + MidiChannel *_channel[16]; + MidiDriver *_driver; + MidiParser *_smfParser; + byte _channelVolume[16]; + bool _nativeMT32; + bool _isGM; + bool _passThrough; + + bool _isPlaying; + bool _looping; + byte _masterVolume; + + byte *_midiMusicData; + + SoundMgr *_manager; +}; + +} // End of namespace Agi + +#endif -- cgit v1.2.3