From 2bcf5c0552cf0778eb2642608134c6c0e4f3028e Mon Sep 17 00:00:00 2001 From: Johannes Schickel Date: Wed, 27 May 2009 14:40:37 +0000 Subject: - Moved Sound implementation declarations to the newly added file sound_intern.h - Added support for PC Speaker sound in all Kyra1, Kyra2 and Lands of Lore - Slight cleanup svn-id: r40939 --- engines/kyra/kyra_hof.cpp | 4 +- engines/kyra/kyra_hof.h | 2 + engines/kyra/kyra_v1.cpp | 30 +++- engines/kyra/lol.cpp | 2 +- engines/kyra/module.mk | 1 + engines/kyra/script_hof.cpp | 7 +- engines/kyra/sound.h | 245 +-------------------------- engines/kyra/sound_adlib.cpp | 2 +- engines/kyra/sound_intern.h | 353 +++++++++++++++++++++++++++++++++++++++ engines/kyra/sound_lol.cpp | 2 + engines/kyra/sound_midi.cpp | 57 ++++--- engines/kyra/sound_pcspk.cpp | 390 +++++++++++++++++++++++++++++++++++++++++++ engines/kyra/sound_towns.cpp | 2 +- engines/kyra/staticres.cpp | 32 ++++ 14 files changed, 852 insertions(+), 277 deletions(-) create mode 100644 engines/kyra/sound_intern.h create mode 100644 engines/kyra/sound_pcspk.cpp diff --git a/engines/kyra/kyra_hof.cpp b/engines/kyra/kyra_hof.cpp index eabc3ecb93..7e032fb46d 100644 --- a/engines/kyra/kyra_hof.cpp +++ b/engines/kyra/kyra_hof.cpp @@ -1529,7 +1529,9 @@ void KyraEngine_HoF::snd_playSoundEffect(int track, int volume) { if (_sound->getSfxType() == Sound::kMidiMT32) track = track < _mt32SfxMapSize ? _mt32SfxMap[track] - 1 : -1; else if (_sound->getSfxType() == Sound::kMidiGM) - track = track < _gmSfxMapSize ? _gmSfxMap[track] - 1: -1; + track = track < _gmSfxMapSize ? _gmSfxMap[track] - 1 : -1; + else if (_sound->getSfxType() == Sound::kPCSpkr) + track = track < _pcSpkSfxMapSize ? _pcSpkSfxMap[track] - 1 : -1; if (track != -1) KyraEngine_v1::snd_playSoundEffect(track); diff --git a/engines/kyra/kyra_hof.h b/engines/kyra/kyra_hof.h index 09a5906e31..6edcd364bc 100644 --- a/engines/kyra/kyra_hof.h +++ b/engines/kyra/kyra_hof.h @@ -309,6 +309,8 @@ protected: static const int _mt32SfxMapSize; static const int8 _gmSfxMap[]; static const int _gmSfxMapSize; + static const int8 _pcSpkSfxMap[]; + static const int _pcSpkSfxMapSize; AudioDataStruct _soundData[3]; protected: diff --git a/engines/kyra/kyra_v1.cpp b/engines/kyra/kyra_v1.cpp index d642918667..d5e0308a09 100644 --- a/engines/kyra/kyra_v1.cpp +++ b/engines/kyra/kyra_v1.cpp @@ -29,7 +29,7 @@ #include "sound/mixer.h" #include "kyra/kyra_v1.h" -#include "kyra/sound.h" +#include "kyra/sound_intern.h" #include "kyra/resource.h" #include "kyra/screen.h" #include "kyra/text.h" @@ -103,7 +103,7 @@ Common::Error KyraEngine_v1::init() { if (!_flags.useDigSound) { // We prefer AdLib over MIDI, since generally AdLib is better supported - int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB); + int midiDriver = MidiDriver::detectMusicDriver(MDT_PCSPK | MDT_MIDI | MDT_ADLIB); if (_flags.platform == Common::kPlatformFMTowns) { if (_flags.gameID == GI_KYRA1) @@ -119,17 +119,31 @@ Common::Error KyraEngine_v1::init() { _sound = new SoundAdlibPC(this, _mixer); assert(_sound); } else { - bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32")); + Sound::kType type; + + if (midiDriver == MD_MT32 || ConfMan.getBool("native_mt32")) + type = Sound::kMidiMT32; + else if (midiDriver == MD_PCSPK) + type = Sound::kPCSpkr; + else + type = Sound::kMidiGM; + + + MidiDriver *driver = 0; + + if (midiDriver == MD_PCSPK) { + driver = new MidiDriver_PCSpeaker(_mixer); + } else { + driver = MidiDriver::createMidi(midiDriver); + if (type == Sound::kMidiMT32) + driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); + } - MidiDriver *driver = MidiDriver::createMidi(midiDriver); assert(driver); - if (native_mt32) - driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE); - SoundMidiPC *soundMidiPc = new SoundMidiPC(this, _mixer, driver); + SoundMidiPC *soundMidiPc = new SoundMidiPC(this, _mixer, driver, type); _sound = soundMidiPc; assert(_sound); - soundMidiPc->hasNativeMT32(native_mt32); // Unlike some SCUMM games, it's not that the MIDI sounds are // missing. It's just that at least at the time of writing they diff --git a/engines/kyra/lol.cpp b/engines/kyra/lol.cpp index 887c7a4e62..6a8bb3d3ff 100644 --- a/engines/kyra/lol.cpp +++ b/engines/kyra/lol.cpp @@ -432,7 +432,7 @@ Common::Error LoLEngine::init() { KyraEngine_v1::init(); initStaticResource(); - _envSfxDistThreshold = (MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB) == MD_ADLIB || ConfMan.getBool("multi_midi")) ? 15 : 3; + _envSfxDistThreshold = _sound->getSfxType() == Sound::kAdlib ? 15 : 3; _gui = new GUI_LoL(this); assert(_gui); diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk index 6ff6cf115e..7b0c0dfb68 100644 --- a/engines/kyra/module.mk +++ b/engines/kyra/module.mk @@ -52,6 +52,7 @@ MODULE_OBJS := \ sound_adlib.o \ sound_digital.o \ sound_midi.o \ + sound_pcspk.o \ sound_towns.o \ sound.o \ sound_lok.o \ diff --git a/engines/kyra/script_hof.cpp b/engines/kyra/script_hof.cpp index 0327516b2e..23ec3bd406 100644 --- a/engines/kyra/script_hof.cpp +++ b/engines/kyra/script_hof.cpp @@ -829,7 +829,8 @@ int KyraEngine_HoF::o2_showLetter(EMCState *script) { int KyraEngine_HoF::o2_playFireflyScore(EMCState *script) { debugC(3, kDebugLevelScriptFuncs, "KyraEngine_HoF::o2_playFireflyScore(%p) ()", (const void *)script); - if (_sound->getSfxType() == Sound::kAdlib || _sound->getSfxType() == Sound::kMidiMT32 || _sound->getSfxType() == Sound::kMidiGM) { + if (_sound->getSfxType() == Sound::kAdlib || _sound->getSfxType() == Sound::kPCSpkr || + _sound->getSfxType() == Sound::kMidiMT32 || _sound->getSfxType() == Sound::kMidiGM) { snd_playWanderScoreViaMap(86, 1); return 1; } else { @@ -1328,6 +1329,8 @@ int KyraEngine_HoF::o2_getSfxDriver(EMCState *script) { debugC(3, kDebugLevelScriptFuncs, "KyraEngine_HoF::o2_getSfxDriver(%p) ()", (const void *)script); if (_sound->getSfxType() == Sound::kAdlib) return 1; + else if (_sound->getSfxType() == Sound::kPCSpkr) + return 4; else if (_sound->getSfxType() == Sound::kMidiMT32) return 6; else if (_sound->getSfxType() == Sound::kMidiGM) @@ -1346,6 +1349,8 @@ int KyraEngine_HoF::o2_getMusicDriver(EMCState *script) { debugC(3, kDebugLevelScriptFuncs, "KyraEngine_HoF::o2_getMusicDriver(%p) ()", (const void *)script); if (_sound->getMusicType() == Sound::kAdlib) return 1; + else if (_sound->getMusicType() == Sound::kPCSpkr) + return 4; else if (_sound->getMusicType() == Sound::kMidiMT32) return 6; else if (_sound->getMusicType() == Sound::kMidiGM) diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h index edacdb0411..eb40e40ab9 100644 --- a/engines/kyra/sound.h +++ b/engines/kyra/sound.h @@ -45,13 +45,9 @@ #include "kyra/kyra_v1.h" #include "common/scummsys.h" -#include "common/file.h" -#include "common/mutex.h" -#include "common/ptr.h" +#include "common/str.h" -#include "sound/midiparser.h" #include "sound/mixer.h" -#include "sound/softsynth/ym2612.h" namespace Audio { class AudioStream; @@ -74,7 +70,8 @@ public: kMidiMT32, kMidiGM, kTowns, - kPC98 + kPC98, + kPCSpkr }; virtual kType getMusicType() const = 0; @@ -261,242 +258,6 @@ private: static const SpeechCodecs _supportedCodecs[]; }; -class AdlibDriver; - -/** - * AdLib implementation of the sound output device. - * - * It uses a special sound file format special to - * Dune II, Kyrandia 1 and 2. While Dune II and - * Kyrandia 1 are using exact the same format, the - * one of Kyrandia 2 slightly differs. - * - * See AdlibDriver for more information. - * @see AdlibDriver - */ -class SoundAdlibPC : public Sound { -public: - SoundAdlibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer); - ~SoundAdlibPC(); - - kType getMusicType() const { return kAdlib; } - - bool init(); - void process(); - - void loadSoundFile(uint file); - void loadSoundFile(Common::String file); - - void playTrack(uint8 track); - void haltTrack(); - bool isPlaying(); - - void playSoundEffect(uint8 track); - - void beginFadeOut(); -private: - void internalLoadFile(Common::String file); - - void play(uint8 track); - - void unk1(); - void unk2(); - - AdlibDriver *_driver; - - bool _v2; - uint8 _trackEntries[500]; - uint8 *_soundDataPtr; - int _sfxPlayingSound; - - Common::String _soundFileLoaded; - - uint8 _sfxPriority; - uint8 _sfxFourthByteOfSong; - - int _numSoundTriggers; - const int *_soundTriggers; - - static const int _kyra1NumSoundTriggers; - 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. - */ -class SoundMidiPC : public Sound { -public: - SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver); - ~SoundMidiPC(); - - kType getMusicType() const { return _nativeMT32 ? kMidiMT32 : kMidiGM; } - - bool init(); - - void updateVolumeSettings(); - - void loadSoundFile(uint file); - void loadSoundFile(Common::String file); - void loadSfxFile(Common::String file); - - void playTrack(uint8 track); - void haltTrack(); - bool isPlaying(); - - void playSoundEffect(uint8 track); - void stopAllSoundEffects(); - - void beginFadeOut(); - - void hasNativeMT32(bool nativeMT32); -private: - static void onTimer(void *data); - - // Our channel handling - int _musicVolume, _sfxVolume; - - uint32 _fadeStartTime; - bool _fadeMusicOut; - - // Midi file related - Common::String _mFileName, _sFileName; - byte *_musicFile, *_sfxFile; - - MidiParser *_music; - MidiParser *_sfx[3]; - - // misc - bool _nativeMT32; - bool _useC55; - MidiDriver *_driver; - MidiOutput *_output; - - Common::Mutex _mutex; -}; - -class Towns_EuphonyDriver; -class TownsPC98_OpnDriver; - -class SoundTowns : public MidiDriver, public Sound { -public: - SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer); - ~SoundTowns(); - - kType getMusicType() const { return kTowns; } - - bool init(); - void process(); - - void loadSoundFile(uint file); - void loadSoundFile(Common::String) {} - - void playTrack(uint8 track); - void haltTrack(); - - void playSoundEffect(uint8); - - 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); - - //Channel allocation functions - MidiChannel *allocateChannel() { return 0; } - MidiChannel *getPercussionChannel() { return 0; } - - static float calculatePhaseStep(int8 semiTone, int8 semiToneRootkey, - uint32 sampleRate, uint32 outputRate, int32 pitchWheel); - -private: - bool loadInstruments(); - void playEuphonyTrack(uint32 offset, int loop); - - static void onTimer(void *data); - - int _lastTrack; - Audio::AudioStream *_currentSFX; - Audio::SoundHandle _sfxHandle; - - uint _sfxFileIndex; - uint8 *_sfxFileData; - - Towns_EuphonyDriver * _driver; - MidiParser * _parser; - - Common::Mutex _mutex; - - const uint8 *_sfxBTTable; - const uint8 *_sfxWDTable; -}; - -class SoundPC98 : public Sound { -public: - SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer); - ~SoundPC98(); - - virtual kType getMusicType() const { return kPC98; } - - bool init(); - - void process() {} - void loadSoundFile(uint file) {} - void loadSoundFile(Common::String) {} - - void playTrack(uint8 track); - void haltTrack(); - void beginFadeOut(); - - int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, bool isSfx) { return -1; } - void playSoundEffect(uint8); - -protected: - int _lastTrack; - uint8 *_musicTrackData; - uint8 *_sfxTrackData; - TownsPC98_OpnDriver *_driver; -}; - -class SoundTownsPC98_v2 : public Sound { -public: - SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer); - ~SoundTownsPC98_v2(); - - kType getMusicType() const { return _vm->gameFlags().platform == Common::kPlatformFMTowns ? kTowns : kPC98; } - - bool init(); - void process(); - - void loadSoundFile(uint file) {} - void loadSoundFile(Common::String file); - - void playTrack(uint8 track); - void haltTrack(); - void beginFadeOut(); - - int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, bool isSfx); - void playSoundEffect(uint8 track); - -protected: - Audio::AudioStream *_currentSFX; - int _lastTrack; - bool _useFmSfx; - - uint8 *_musicTrackData; - uint8 *_sfxTrackData; - TownsPC98_OpnDriver *_driver; -}; - class MixedSoundDriver : public Sound { public: MixedSoundDriver(KyraEngine_v1 *vm, Audio::Mixer *mixer, Sound *music, Sound *sfx) : Sound(vm, mixer), _music(music), _sfx(sfx) {} diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp index d3dfd2b6af..78500eead7 100644 --- a/engines/kyra/sound_adlib.cpp +++ b/engines/kyra/sound_adlib.cpp @@ -43,7 +43,7 @@ #include "common/system.h" #include "common/mutex.h" #include "kyra/resource.h" -#include "kyra/sound.h" +#include "kyra/sound_intern.h" #include "sound/mixer.h" #include "sound/fmopl.h" diff --git a/engines/kyra/sound_intern.h b/engines/kyra/sound_intern.h new file mode 100644 index 0000000000..cb33c66f47 --- /dev/null +++ b/engines/kyra/sound_intern.h @@ -0,0 +1,353 @@ +/* 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$ + * + */ + +#ifndef KYRA_SOUND_INTERN_H +#define KYRA_SOUND_INTERN_H + +#include "kyra/sound.h" + +#include "common/mutex.h" + +#include "sound/softsynth/ym2612.h" +#include "sound/softsynth/emumidi.h" +#include "sound/midiparser.h" + +namespace Audio { +class PCSpeaker; +} // end of namespace Audio + +namespace Kyra { +class AdlibDriver; + +/** + * AdLib implementation of the sound output device. + * + * It uses a special sound file format special to + * Dune II, Kyrandia 1 and 2. While Dune II and + * Kyrandia 1 are using exact the same format, the + * one of Kyrandia 2 slightly differs. + * + * See AdlibDriver for more information. + * @see AdlibDriver + */ +class SoundAdlibPC : public Sound { +public: + SoundAdlibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer); + ~SoundAdlibPC(); + + kType getMusicType() const { return kAdlib; } + + bool init(); + void process(); + + void loadSoundFile(uint file); + void loadSoundFile(Common::String file); + + void playTrack(uint8 track); + void haltTrack(); + bool isPlaying(); + + void playSoundEffect(uint8 track); + + void beginFadeOut(); +private: + void internalLoadFile(Common::String file); + + void play(uint8 track); + + void unk1(); + void unk2(); + + AdlibDriver *_driver; + + bool _v2; + uint8 _trackEntries[500]; + uint8 *_soundDataPtr; + int _sfxPlayingSound; + + Common::String _soundFileLoaded; + + uint8 _sfxPriority; + uint8 _sfxFourthByteOfSong; + + int _numSoundTriggers; + const int *_soundTriggers; + + static const int _kyra1NumSoundTriggers; + 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. + */ +class SoundMidiPC : public Sound { +public: + SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver, kType type); + ~SoundMidiPC(); + + kType getMusicType() const { return _type; } + + bool init(); + + void updateVolumeSettings(); + + void loadSoundFile(uint file); + void loadSoundFile(Common::String file); + void loadSfxFile(Common::String file); + + void playTrack(uint8 track); + void haltTrack(); + bool isPlaying(); + + void playSoundEffect(uint8 track); + void stopAllSoundEffects(); + + void beginFadeOut(); +private: + static void onTimer(void *data); + + // Our channel handling + int _musicVolume, _sfxVolume; + + uint32 _fadeStartTime; + bool _fadeMusicOut; + + // Midi file related + Common::String _mFileName, _sFileName; + byte *_musicFile, *_sfxFile; + + MidiParser *_music; + MidiParser *_sfx[3]; + + // misc + kType _type; + Common::String getFileName(const Common::String &str); + + bool _nativeMT32; + MidiDriver *_driver; + MidiOutput *_output; + + Common::Mutex _mutex; +}; + +class Towns_EuphonyDriver; +class TownsPC98_OpnDriver; + +class SoundTowns : public MidiDriver, public Sound { +public: + SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer); + ~SoundTowns(); + + kType getMusicType() const { return kTowns; } + + bool init(); + void process(); + + void loadSoundFile(uint file); + void loadSoundFile(Common::String) {} + + void playTrack(uint8 track); + void haltTrack(); + + void playSoundEffect(uint8); + + 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); + + //Channel allocation functions + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + static float calculatePhaseStep(int8 semiTone, int8 semiToneRootkey, + uint32 sampleRate, uint32 outputRate, int32 pitchWheel); + +private: + bool loadInstruments(); + void playEuphonyTrack(uint32 offset, int loop); + + static void onTimer(void *data); + + int _lastTrack; + Audio::AudioStream *_currentSFX; + Audio::SoundHandle _sfxHandle; + + uint _sfxFileIndex; + uint8 *_sfxFileData; + + Towns_EuphonyDriver * _driver; + MidiParser * _parser; + + Common::Mutex _mutex; + + const uint8 *_sfxBTTable; + const uint8 *_sfxWDTable; +}; + +class SoundPC98 : public Sound { +public: + SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer); + ~SoundPC98(); + + virtual kType getMusicType() const { return kPC98; } + + bool init(); + + void process() {} + void loadSoundFile(uint file) {} + void loadSoundFile(Common::String) {} + + void playTrack(uint8 track); + void haltTrack(); + void beginFadeOut(); + + int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, bool isSfx) { return -1; } + void playSoundEffect(uint8); + +protected: + int _lastTrack; + uint8 *_musicTrackData; + uint8 *_sfxTrackData; + TownsPC98_OpnDriver *_driver; +}; + +class SoundTownsPC98_v2 : public Sound { +public: + SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer); + ~SoundTownsPC98_v2(); + + kType getMusicType() const { return _vm->gameFlags().platform == Common::kPlatformFMTowns ? kTowns : kPC98; } + + bool init(); + void process(); + + void loadSoundFile(uint file) {} + void loadSoundFile(Common::String file); + + void playTrack(uint8 track); + void haltTrack(); + void beginFadeOut(); + + int32 voicePlay(const char *file, Audio::SoundHandle *handle, uint8 volume, bool isSfx); + void playSoundEffect(uint8 track); + +protected: + Audio::AudioStream *_currentSFX; + int _lastTrack; + bool _useFmSfx; + + uint8 *_musicTrackData; + uint8 *_sfxTrackData; + TownsPC98_OpnDriver *_driver; +}; + +// PC Speaker MIDI driver +class MidiDriver_PCSpeaker : public MidiDriver_Emulated { +public: + MidiDriver_PCSpeaker(Audio::Mixer *mixer); + ~MidiDriver_PCSpeaker(); + + // MidiDriver interface + void close() {} + + void send(uint32 data); + + MidiChannel *allocateChannel() { return 0; } + MidiChannel *getPercussionChannel() { return 0; } + + // MidiDriver_Emulated interface + void generateSamples(int16 *buffer, int numSamples); + + // AudioStream interface + bool isStereo() const { return false; } + int getRate() const { return _rate; } +private: + Common::Mutex _mutex; + Audio::PCSpeaker *_speaker; + int _rate; + + struct Channel { + uint8 volume; + uint8 pitchBendLow, pitchBendHigh; + uint8 expression; + uint8 hold; + uint8 modulation; + uint8 voiceProtect; + uint8 noteCount; + } _channel[16]; + + void resetController(int channel); + + struct Note { + bool enabled; + uint8 hardwareChannel; + uint8 midiChannel; + uint8 note1, note2; + uint8 velocity; + bool processHold; + uint8 flags; + uint8 hardwareFlags; + uint16 priority; + int16 modulation; + uint16 precedence; + } _note[2]; + + void noteOn(int channel, int note, int velocity); + void noteOff(int channel, int note); + + void turnNoteOn(int note); + void overwriteNote(int note); + void turnNoteOff(int note); + + void setupTone(int note); + + uint16 _countdown; + uint8 _hardwareChannel[1]; + bool _modulationFlag; + + uint8 _timerValue; + void onTimer(); + + static const uint8 _velocityTable[]; + static const uint8 _noteTable1[]; + static const uint8 _noteTable2[]; +}; + +} // end of namespace Kyra + +#endif + diff --git a/engines/kyra/sound_lol.cpp b/engines/kyra/sound_lol.cpp index 72624ef789..e641c3ecac 100644 --- a/engines/kyra/sound_lol.cpp +++ b/engines/kyra/sound_lol.cpp @@ -29,6 +29,8 @@ #include "kyra/lol.h" #include "kyra/resource.h" +#include "sound/audiostream.h" + namespace Kyra { bool LoLEngine::snd_playCharacterSpeech(int id, int8 speaker, int) { diff --git a/engines/kyra/sound_midi.cpp b/engines/kyra/sound_midi.cpp index 039ee76eba..218b0d161d 100644 --- a/engines/kyra/sound_midi.cpp +++ b/engines/kyra/sound_midi.cpp @@ -23,7 +23,7 @@ * */ -#include "kyra/sound.h" +#include "kyra/sound_intern.h" #include "kyra/resource.h" #include "common/system.h" @@ -438,7 +438,7 @@ void MidiOutput::stopNotesOnChannel(int channel) { #pragma mark - -SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver) : Sound(vm, mixer) { +SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *driver, kType type) : Sound(vm, mixer) { _driver = driver; _output = 0; @@ -453,6 +453,20 @@ SoundMidiPC::SoundMidiPC(KyraEngine_v1 *vm, Audio::Mixer *mixer, MidiDriver *dri _musicVolume = _sfxVolume = 0; _fadeMusicOut = false; + + _type = type; + assert(_type == kMidiMT32 || _type == kMidiGM || _type == kPCSpkr); + + // Only General MIDI isn't a Roland MT-32 MIDI implemenation, + // even the PC Speaker driver is a Roland MT-32 based MIDI implementation. + // Thus we set "_nativeMT32" for all types except Gerneral MIDI to true. + _nativeMT32 = (_type != kMidiGM); + + // KYRA1 does not include any General MIDI tracks, thus we have + // to overwrite the internal type with MT32 to get the correct + // file extension. + if (_vm->game() == GI_KYRA1 && _type == kMidiGM) + _type = kMidiMT32; } SoundMidiPC::~SoundMidiPC() { @@ -469,23 +483,10 @@ SoundMidiPC::~SoundMidiPC() { 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); + _output = new MidiOutput(_vm->_system, _driver, _nativeMT32, (_type != kMidiGM)); assert(_output); updateVolumeSettings(); @@ -502,7 +503,7 @@ bool SoundMidiPC::init() { _output->setTimerCallback(this, SoundMidiPC::onTimer); - if (_nativeMT32) { + if (_nativeMT32 && _type == kMidiMT32) { const char *midiFile = 0; const char *pakFile = 0; if (_vm->gameFlags().gameID == GI_KYRA1) { @@ -581,9 +582,7 @@ void SoundMidiPC::loadSoundFile(uint file) { void SoundMidiPC::loadSoundFile(Common::String file) { Common::StackLock lock(_mutex); - - file += _useC55 ? ".C55" : ".XMI"; - file.toUppercase(); + file = getFileName(file); if (_mFileName == file) return; @@ -624,8 +623,7 @@ void SoundMidiPC::loadSfxFile(Common::String file) { if (_vm->gameFlags().gameID == GI_KYRA1) return; - file += _useC55 ? ".C55" : ".XMI"; - file.toUppercase(); + file = getFileName(file); if (_sFileName == file) return; @@ -740,5 +738,20 @@ void SoundMidiPC::onTimer(void *data) { } } +Common::String SoundMidiPC::getFileName(const Common::String &str) { + Common::String file = str; + if (_type == kMidiMT32) + file += ".XMI"; + else if (_type == kMidiGM) + file += ".C55"; + else if (_type == kPCSpkr) + file += ".PCS"; + + if (_vm->resource()->exists(file.c_str())) + return file; + + return str + ".XMI"; +} + } // end of namespace Kyra diff --git a/engines/kyra/sound_pcspk.cpp b/engines/kyra/sound_pcspk.cpp new file mode 100644 index 0000000000..98569db5e2 --- /dev/null +++ b/engines/kyra/sound_pcspk.cpp @@ -0,0 +1,390 @@ +/* + * 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. + * + * LGPL License + * + * This library 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 library 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 library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * $URL$ + * $Id$ + * + */ + +#include "kyra/sound_intern.h" + +#include "sound/mixer.h" +#include "sound/softsynth/pcspk.h" + +namespace Kyra { + +MidiDriver_PCSpeaker::MidiDriver_PCSpeaker(Audio::Mixer *mixer) + : MidiDriver_Emulated(mixer), _rate(mixer->getOutputRate()) { + _timerValue = 0; + memset(_channel, 0, sizeof(_channel)); + memset(_note, 0, sizeof(_note)); + + for (int i = 0; i < 2; ++i) + _note[i].hardwareChannel = 0xFF; + + _speaker = new Audio::PCSpeaker(_rate); + assert(_speaker); + _mixer->playInputStream(Audio::Mixer::kMusicSoundType, &_mixerSoundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, false, true); + + _countdown = 0xFFFF; + _hardwareChannel[0] = 0xFF; + _modulationFlag = false; +} + +MidiDriver_PCSpeaker::~MidiDriver_PCSpeaker() { + _mixer->stopHandle(_mixerSoundHandle); + delete _speaker; + _speaker = 0; +} + +void MidiDriver_PCSpeaker::send(uint32 data) { + Common::StackLock lock(_mutex); + + uint8 channel = data & 0x0F; + uint8 param1 = (data >> 8) & 0xFF; + uint8 param2 = (data >> 16) & 0xFF; + + uint8 flags = 0; + + switch (data & 0xF0) { + case 0x80: // note off + noteOff(channel, param1); + return; + + case 0x90: // note on + if (channel > 1) + return; + + if (param2) + noteOn(channel, param1, param2); + else + noteOff(channel, param1); + return; + + case 0xB0: // controller + switch (param1) { + case 0x01: // modulation + _channel[channel].modulation = param2; + flags = 0x00; + break; + + case 0x07: // volume + _channel[channel].volume = param2; + flags = 0x40; + break; + + case 0x40: // hold + _channel[channel].hold = param2; + if (param2 < 0x40) + resetController(channel); + return; + + case 0x70: // voice protect + _channel[channel].voiceProtect = param2; + return; + + case 0x79: // all notes off + _channel[channel].hold = 0; + resetController(channel); + _channel[channel].modulation = 0; + _channel[channel].expression = 0x7F; + _channel[channel].pitchBendLow = 0; + _channel[channel].pitchBendHigh = 0x40; + flags = 0x41; + break; + + case 0xB0: // expression + _channel[channel].expression = param2; + flags = 0x40; + break; + + default: + return; + } + break; + + case 0xE0: // pitch bend + flags = 0x01; + _channel[channel].pitchBendLow = param1; + _channel[channel].pitchBendHigh = param2; + break; + + default: + return; + } + + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled && _note[i].midiChannel == channel) { + _note[i].flags |= flags; + setupTone(i); + } + } +} + +void MidiDriver_PCSpeaker::resetController(int channel) { + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled && _note[i].midiChannel == channel && _note[i].processHold) + noteOff(channel, _note[i].note2); + } +} + +void MidiDriver_PCSpeaker::noteOn(int channel, int note, int velocity) { + int n = 0; + + while (n < 2 && _note[n].enabled) + ++n; + + if (n >= 2) + return; + + _note[n].midiChannel = channel; + _note[n].note1 = _note[n].note2 = note; + _note[n].velocity = _velocityTable[((uint8)velocity) >> 3]; + _note[n].enabled = true; + _note[n].processHold = false; + _note[n].hardwareFlags = 0x20; + _note[n].priority = 0x7FFF; + _note[n].flags = 0x41; + + turnNoteOn(n); +} + +void MidiDriver_PCSpeaker::turnNoteOn(int note) { + if (_hardwareChannel[0] == 0xFF) { + _note[note].hardwareChannel = 0; + ++_channel[_note[note].midiChannel].noteCount; + _hardwareChannel[0] = _note[note].midiChannel; + _note[note].flags = 0x41; + + setupTone(note); + } else { + overwriteNote(note); + } +} + +void MidiDriver_PCSpeaker::overwriteNote(int note) { + int totalNotes = 0; + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled) { + ++totalNotes; + const int channel = _note[i].midiChannel; + + uint16 priority = 0xFFFF; + if (_channel[channel].voiceProtect < 0x40) + priority = _note[i].priority; + + if (_channel[channel].noteCount > priority) + priority = 0; + else + priority -= _channel[channel].noteCount; + + _note[i].precedence = priority; + } + } + + if (totalNotes <= 1) + return; + + do { + uint16 maxValue = 0; + uint16 minValue = 0xFFFF; + int newNote = 0; + + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled) { + if (_note[i].hardwareChannel == 0xFF) { + if (_note[i].precedence >= maxValue) { + maxValue = _note[i].precedence; + newNote = i; + } + } else { + if (_note[i].precedence <= minValue) { + minValue = _note[i].precedence; + note = i; + } + } + } + } + + if (maxValue < minValue) + return; + + turnNoteOff(_note[note].hardwareChannel); + _note[note].enabled = false; + + _note[newNote].hardwareChannel = _note[note].hardwareChannel; + ++_channel[_note[newNote].midiChannel].noteCount; + _hardwareChannel[_note[note].hardwareChannel] = _note[newNote].midiChannel; + _note[newNote].flags = 0x41; + + setupTone(newNote); + } while (--totalNotes); +} + +void MidiDriver_PCSpeaker::noteOff(int channel, int note) { + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled && _note[i].note1 == note && _note[i].midiChannel == channel) { + if (_channel[i].hold < 0x40) { + turnNoteOff(i); + _note[i].enabled = false; + } else { + _note[i].processHold = true; + } + } + } +} + +void MidiDriver_PCSpeaker::turnNoteOff(int note) { + if (_note[note].hardwareChannel != 0xFF) { + _note[note].hardwareFlags &= 0xDF; + _note[note].flags |= 1; + + setupTone(note); + + --_channel[_note[note].midiChannel].noteCount; + + _hardwareChannel[_note[note].hardwareChannel] = 0xFF; + _note[note].hardwareChannel = 0xFF; + } +} + +void MidiDriver_PCSpeaker::setupTone(int note) { + if (_note[note].hardwareChannel == 0xFF) + return; + + if (_note[note].flags & 0x40) + _note[note].flags &= 0xBF; + if (!(_note[note].flags & 0x01)) + return; + + if (!(_note[note].hardwareFlags & 0x20)) { + _speaker->stop(); + } else { + const int midiChannel = _note[note].midiChannel; + uint16 pitchBend = (_channel[midiChannel].pitchBendHigh << 7) | _channel[midiChannel].pitchBendLow; + + int noteValue = _note[note].note2; + + noteValue -= 24; + do { + noteValue += 12; + } while (noteValue < 0); + + noteValue += 12; + do { + noteValue -= 12; + } while (noteValue > 95); + + int16 modulation = _note[note].modulation; + + int tableIndex = MAX(noteValue - 12, 0); + uint16 note1 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex]; + tableIndex = MIN(noteValue + 12, 95); + uint16 note2 = (_noteTable2[tableIndex] << 8) | _noteTable1[tableIndex]; + uint16 note3 = (_noteTable2[noteValue] << 8) | _noteTable1[noteValue]; + + int32 countdown = pitchBend - 0x2000; + countdown += modulation; + + if (countdown >= 0) + countdown *= (note2 - note3); + else + countdown *= (note3 - note1); + + countdown /= 0x2000; + countdown += note3; + + countdown = uint16(countdown & 0xFFFF); + if (countdown != _countdown) + _countdown = countdown; + + _speaker->play(Audio::PCSpeaker::kWaveFormSquare, 1193180 / _countdown, -1); + } + + _note[note].flags &= 0xFE; +} + +void MidiDriver_PCSpeaker::generateSamples(int16 *buffer, int numSamples) { + Common::StackLock lock(_mutex); + _speaker->readBuffer(buffer, numSamples); +} + +void MidiDriver_PCSpeaker::onTimer() { + /*Common::StackLock lock(_mutex); + + _timerValue += 20; + if (_timerValue < 120) + return; + _timerValue -= 120; + + _modulationFlag = !_modulationFlag; + for (int i = 0; i < 2; ++i) { + if (_note[i].enabled) { + uint16 modValue = 5 * _channel[_note[i].midiChannel].modulation; + if (_modulationFlag) + modValue = -modValue; + _note[i].modulation = modValue; + _note[i].flags |= 1; + + setupTone(i); + } + }*/ +} + +const uint8 MidiDriver_PCSpeaker::_velocityTable[] = { + 0x52, 0x55, 0x58, 0x5B, 0x5E, 0x61, 0x64, 0x67, + 0x6A, 0x6D, 0x70, 0x73, 0x76, 0x79, 0x7C, 0x7F +}; + +const uint8 MidiDriver_PCSpeaker::_noteTable1[] = { + 0x88, 0xB5, 0x4E, 0x40, 0x41, 0xCD, 0xC4, 0x3D, + 0x43, 0x7C, 0x2A, 0xD6, 0x88, 0xB5, 0xFF, 0xD1, + 0x20, 0xA7, 0xE2, 0x1E, 0xCE, 0xBE, 0xF2, 0x8A, + 0x44, 0x41, 0x7F, 0xE8, 0x90, 0x63, 0x63, 0x8F, + 0xE7, 0x5F, 0x01, 0xBD, 0xA2, 0xA0, 0xBF, 0xF4, + 0x48, 0xB1, 0x31, 0xC7, 0x70, 0x2F, 0xFE, 0xE0, + 0xD1, 0xD0, 0xDE, 0xFB, 0x24, 0x58, 0x98, 0xE3, + 0x39, 0x97, 0xFF, 0x6F, 0xE8, 0x68, 0xEF, 0x7D, + 0x11, 0xAC, 0x4C, 0xF1, 0x9C, 0x4B, 0xFF, 0xB7, + 0x74, 0x34, 0xF7, 0xBE, 0x88, 0x56, 0x26, 0xF8, + 0xCE, 0xA5, 0x7F, 0x5B, 0x3A, 0x1A, 0xFB, 0xDF, + 0xC4, 0xAB, 0x93, 0x7C, 0x67, 0x52, 0x3F, 0x2D +}; + +const uint8 MidiDriver_PCSpeaker::_noteTable2[] = { + 0x8E, 0x86, 0xFD, 0xF0, 0xE2, 0xD5, 0xC9, 0xBE, + 0xB3, 0xA9, 0xA0, 0x96, 0x8E, 0x86, 0x7E, 0x77, + 0x71, 0x6A, 0x64, 0x5F, 0x59, 0x54, 0x4F, 0x4B, + 0x47, 0x43, 0x3F, 0x3B, 0x38, 0x35, 0x32, 0x2F, + 0x2C, 0x2A, 0x28, 0x25, 0x23, 0x21, 0x1F, 0x1D, + 0x1C, 0x1A, 0x19, 0x17, 0x16, 0x15, 0x13, 0x12, + 0x11, 0x10, 0x0F, 0x0E, 0x0E, 0x0D, 0x0C, 0x0B, + 0x0B, 0x0A, 0x09, 0x09, 0x08, 0x08, 0x07, 0x07, + 0x07, 0x06, 0x06, 0x05, 0x05, 0x05, 0x04, 0x04, + 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01 +}; + +} // end of namespace Kyra + diff --git a/engines/kyra/sound_towns.cpp b/engines/kyra/sound_towns.cpp index 443506a511..d6a1bacafc 100644 --- a/engines/kyra/sound_towns.cpp +++ b/engines/kyra/sound_towns.cpp @@ -25,7 +25,7 @@ #include "common/system.h" #include "kyra/resource.h" -#include "kyra/sound.h" +#include "kyra/sound_intern.h" #include "kyra/screen.h" #include "sound/audiocd.h" diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp index 02c432df2a..4572ffee69 100644 --- a/engines/kyra/staticres.cpp +++ b/engines/kyra/staticres.cpp @@ -2390,6 +2390,38 @@ const int8 KyraEngine_HoF::_gmSfxMap[] = { const int KyraEngine_HoF::_gmSfxMapSize = ARRAYSIZE(KyraEngine_HoF::_gmSfxMap); +const int8 KyraEngine_HoF::_pcSpkSfxMap[] = { + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, 0, 1, 2, 3, 4, + 5, 6, -1, 7, 8, 9, 10, -1, + 6, -1, -1, 11, -1, 12, -1, -1, + -1, -1, -1, 13, -1, 39, 14, 15, + 3, 16, 16, -1, -1, -1, 17, 18, + 5, -1, -1, -1, -1, -1, 19, 20, + 21, -1, 22, 23, -1, -1, -1, -1, + -1, -1, 39, -1, 24, 24, 25, 26, + 27, 28, 29, 30, 31, 32, -1, -1, + -1, 2, -1, -1, -1, -1, -1, 21, + 10, -1, -1, -1, -1, 17, -1, 17, + 40, -1, 18, 38, -1, 40, 33, -1, + 34, 35, 36, 37, 38, 39, 40, 41, + -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, 42, 43, 44, 45, -1, -1, + -1, -1, -1, -1, 46, -1, 5, 47, + 48, -1, -1, -1, -1, -1, 49, 50, + -1, 40, -1, 24, -1, -1, 43, -1, + -1, 38, -1, -1, -1, 51, -1, -1, + -1, -1, -1, -1, -1, 9, -1, 52, + 53, 40, -1, -1, -1, -1, -1, -1, + -1, -1, 50, -1, -1, -1, 11, 54, + 5, -1, -1, -1, -1, 11, 7, 55, + 8, 36, -1, -1, -1, -1, -1, -1, + 11, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 24 +}; + +const int KyraEngine_HoF::_pcSpkSfxMapSize = ARRAYSIZE(KyraEngine_HoF::_pcSpkSfxMap); + void KyraEngine_HoF::initInventoryButtonList() { delete[] _inventoryButtons; -- cgit v1.2.3