From 3a2cd5a223bb7097acddc7acda95298db20bf1f5 Mon Sep 17 00:00:00 2001 From: Paul Gilbert Date: Sun, 5 Feb 2012 11:02:29 +1100 Subject: MORTEVIELLE: In progress implementation of PC Speaker music player --- engines/mortevielle/mortevielle.cpp | 19 ++++- engines/mortevielle/mortevielle.h | 3 + engines/mortevielle/ovd1.cpp | 4 +- engines/mortevielle/parole.cpp | 3 +- engines/mortevielle/parole2.cpp | 4 +- engines/mortevielle/sound.cpp | 144 +++++++++++++++++++++++++++++++++++- engines/mortevielle/sound.h | 76 ++++++++++++++++++- engines/mortevielle/var_mor.cpp | 5 -- engines/mortevielle/var_mor.h | 4 - 9 files changed, 246 insertions(+), 16 deletions(-) diff --git a/engines/mortevielle/mortevielle.cpp b/engines/mortevielle/mortevielle.cpp index 88a3ecd9e5..fde3d424c5 100644 --- a/engines/mortevielle/mortevielle.cpp +++ b/engines/mortevielle/mortevielle.cpp @@ -37,7 +37,8 @@ namespace Mortevielle { MortevielleEngine *g_vm; MortevielleEngine::MortevielleEngine(OSystem *system, const ADGameDescription *gameDesc): - Engine(system), _gameDescription(gameDesc), _randomSource("mortevielle") { + Engine(system), _gameDescription(gameDesc), _randomSource("mortevielle"), + _soundManager(_mixer) { g_vm = this; _lastGameFrame = 0; _mouseClick = false; @@ -298,6 +299,22 @@ void MortevielleEngine::setMousePos(const Common::Point &pt) { _mousePos = newPoint; } +/** + * Delay by a given amount + */ +void MortevielleEngine::delay(int amount) { + uint32 endTime = g_system->getMillis() + amount; + + while (g_system->getMillis() < endTime) { + if (g_system->getMillis() > (_lastGameFrame + GAME_FRAME_DELAY)) { + _lastGameFrame = g_system->getMillis(); + g_vm->_screenSurface.updateScreen(); + } + + g_system->delayMillis(10); + } +} + /*-------------------------------------------------------------------------*/ Common::Error MortevielleEngine::run() { diff --git a/engines/mortevielle/mortevielle.h b/engines/mortevielle/mortevielle.h index 75aaeda580..86ec947200 100644 --- a/engines/mortevielle/mortevielle.h +++ b/engines/mortevielle/mortevielle.h @@ -33,6 +33,7 @@ #include "common/error.h" #include "graphics/surface.h" #include "mortevielle/graphics.h" +#include "mortevielle/sound.h" namespace Mortevielle { @@ -67,6 +68,7 @@ public: PaletteManager _paletteManager; GfxSurface _backgroundSurface; Common::RandomSource _randomSource; + SoundManager _soundManager; public: MortevielleEngine(OSystem *system, const ADGameDescription *gameDesc); ~MortevielleEngine(); @@ -80,6 +82,7 @@ public: void setMousePos(const Common::Point &pt); bool getMouseClick() const { return _mouseClick; } void setMouseClick(bool v) { _mouseClick = v; } + void delay(int amount); }; extern MortevielleEngine *g_vm; diff --git a/engines/mortevielle/ovd1.cpp b/engines/mortevielle/ovd1.cpp index 3c23a62b28..bfa6f47790 100644 --- a/engines/mortevielle/ovd1.cpp +++ b/engines/mortevielle/ovd1.cpp @@ -339,7 +339,7 @@ void ani50() { fic.read(&mem[0x47a0 * 16 + 0], 123); fic.close(); - demus(&mem[0x3800 * 16], &mem[0x5000 * 16], 623); + g_vm->_soundManager.demus(&mem[0x3800 * 16], &mem[0x5000 * 16], 623); addfix = (float)((tempo_mus - addv[1])) / 256; cctable(tbi); @@ -347,7 +347,7 @@ void ani50() { k = 0; do { fin = keypressed(); - musyc(tbi, 9958 , tempo_mus); + g_vm->_soundManager.musyc(tbi, 9958 , tempo_mus); k = k + 1; fin = fin | keypressed() | (k >= 5); } while (!fin); diff --git a/engines/mortevielle/parole.cpp b/engines/mortevielle/parole.cpp index 768b021454..ba43b5955d 100644 --- a/engines/mortevielle/parole.cpp +++ b/engines/mortevielle/parole.cpp @@ -28,6 +28,7 @@ #include "common/file.h" #include "mortevielle/parole.h" #include "mortevielle/sound.h" +#include "mortevielle/mortevielle.h" namespace Mortevielle { @@ -129,7 +130,7 @@ void veracf(byte b) { f.read(&mem[0x7414 * 16 + 0], 273); /*blockread(f,mem[adson * 16+0],300); blockread(f,mem[adson * 16+2400+0],245);*/ - demus(&mem[0x7414 * 16], &mem[adson * 16], 273); + g_vm->_soundManager.demus(&mem[0x7414 * 16], &mem[adson * 16], 273); f.close(); } diff --git a/engines/mortevielle/parole2.cpp b/engines/mortevielle/parole2.cpp index cba5588ffc..e0aaf83af0 100644 --- a/engines/mortevielle/parole2.cpp +++ b/engines/mortevielle/parole2.cpp @@ -29,6 +29,8 @@ #include "mortevielle/level15.h" #include "mortevielle/parole2.h" #include "mortevielle/parole.h" +#include "mortevielle/mortevielle.h" +#include "mortevielle/sound.h" #include "mortevielle/var_mor.h" namespace Mortevielle { @@ -116,7 +118,7 @@ void parole(int rep, int ht, int typ) { break; } trait_ph(); - litph(tbi, typ, tempo); + g_vm->_soundManager.litph(tbi, typ, tempo); if (typlec != 0) for (i = 0; i <= 500; i ++) { t_cph[i] = savph[i]; diff --git a/engines/mortevielle/sound.cpp b/engines/mortevielle/sound.cpp index f2b70cf8ee..08dbe0ba02 100644 --- a/engines/mortevielle/sound.cpp +++ b/engines/mortevielle/sound.cpp @@ -27,15 +27,125 @@ #include "common/scummsys.h" #include "mortevielle/sound.h" +#include "mortevielle/mortevielle.h" namespace Mortevielle { +/** + * Constructor + */ +PCSpeaker::PCSpeaker(int rate) { + _rate = rate; + _oscLength = 0; + _oscSamples = 0; + _remainingSamples = 0; + _volume = 255; +} + +/** + * Destructor + */ +PCSpeaker::~PCSpeaker() { +} + +/** + * Adds a new note to the queue of notes to be played. + */ +void PCSpeaker::play(int freq, uint32 length) { + assert((freq > 0) && (length > 0)); + Common::StackLock lock(_mutex); + + _pendingNotes.push(SpeakerNote(freq, length)); +} + +/** + * Stops the currently playing song + */ +void PCSpeaker::stop() { + Common::StackLock lock(_mutex); + + _remainingSamples = 0; + _pendingNotes.clear(); +} + +void PCSpeaker::setVolume(byte volume) { + _volume = volume; +} + +/** + * Return true if a song is currently playing + */ +bool PCSpeaker::isPlaying() const { + return !_pendingNotes.empty() || (_remainingSamples != 0); +} + +/** + * Method used by the mixer to pull off pending samples to play + */ +int PCSpeaker::readBuffer(int16 *buffer, const int numSamples) { + Common::StackLock lock(_mutex); + + int i; + + for (i = 0; (_remainingSamples || !_pendingNotes.empty()) && (i < numSamples); i++) { + if (!_remainingSamples) + // Used up the current note, so queue the next one + dequeueNote(); + + buffer[i] = generateSquare(_oscSamples, _oscLength) * _volume; + if (_oscSamples++ >= _oscLength) + _oscSamples = 0; + + _remainingSamples--; + } + + // Clear the rest of the buffer + if (i < numSamples) + memset(buffer + i, 0, (numSamples - i) * sizeof(int16)); + + return numSamples; +} + +/** + * Dequeues a note from the pending note list + */ +void PCSpeaker::dequeueNote() { + SpeakerNote note = _pendingNotes.pop(); + + _oscLength = _rate / note.freq; + _oscSamples = 0; + _remainingSamples = (_rate * note.length) / 1000000; + assert((_oscLength > 0) && (_remainingSamples > 0)); +} + +/** + * Support method for generating a square wave + */ +int8 PCSpeaker::generateSquare(uint32 x, uint32 oscLength) { + return (x < (oscLength / 2)) ? 127 : -128; +} + +/*-------------------------------------------------------------------------*/ + const int tab[16] = { -96, -72, -48, -32, -20, -12, -8, -4, 0, 4, 8, 12, 20, 32, 48, 72 }; +SoundManager::SoundManager(Audio::Mixer *mixer) { + _mixer = mixer; + _speakerStream = new PCSpeaker(mixer->getOutputRate()); + _mixer->playStream(Audio::Mixer::kSFXSoundType, &_speakerHandle, + _speakerStream, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true); +} + +SoundManager::~SoundManager() { + _mixer->stopHandle(_speakerHandle); + delete _speakerStream; + +} + /** * Decode music data */ -void demus(const byte *PSrc, byte *PDest, int NbreSeg) { +void SoundManager::demus(const byte *PSrc, byte *PDest, int NbreSeg) { int seed = 128; int v; @@ -53,4 +163,36 @@ void demus(const byte *PSrc, byte *PDest, int NbreSeg) { } } +void SoundManager::litph(tablint &t, int typ, int tempo) { + return; +} + +void SoundManager::playNote(int frequency, int32 length) { + _speakerStream->play(frequency, length); +} + + +void SoundManager::musyc(tablint &tb, int nbseg, int att) { +#ifdef DEBUG + const byte *pSrc = &mem[0x5000 * 16]; + + // Convert the countdown amount to a tempo rate, and then to note length in microseconds + int tempo = 1193180 / att; + int length = 1000000 / tempo; + + for (int noteIndex = 0; noteIndex < (nbseg * 16); ++noteIndex) { + int lookupValue = *pSrc++; + int noteCountdown = tb[lookupValue]; + int noteFrequency = 1193180 / noteCountdown; + + playNote(noteFrequency, length); + } + + // Keep waiting until the song has been finished + while (_speakerStream->isPlaying() && !g_vm->shouldQuit()) { + g_vm->delay(10); + } +#endif +} + } // End of namespace Mortevielle diff --git a/engines/mortevielle/sound.h b/engines/mortevielle/sound.h index 6e4f7b2a4d..f089974c6b 100644 --- a/engines/mortevielle/sound.h +++ b/engines/mortevielle/sound.h @@ -28,9 +28,83 @@ #ifndef MORTEVIELLE_SOUND_H #define MORTEVIELLE_SOUND_H +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "common/mutex.h" +#include "common/queue.h" +#include "mortevielle/var_mor.h" + namespace Mortevielle { -extern void demus(const byte *PSrc, byte *PDest, int NbreSeg); +/** + * Structure used to store pending notes to play + */ +struct SpeakerNote { + int freq; + uint32 length; + + SpeakerNote(int noteFreq, uint32 noteLength) { + freq = noteFreq; + length = noteLength; + } +}; + +/** + * This is a modified PC Speaker class that allows the queueing of an entire song + * sequence one note at a time. + */ +class PCSpeaker : public Audio::AudioStream { +private: + Common::Queue _pendingNotes; + Common::Mutex _mutex; + + int _rate; + uint32 _oscLength; + uint32 _oscSamples; + uint32 _remainingSamples; + uint32 _mixedSamples; + byte _volume; + + void dequeueNote(); +protected: + static int8 generateSquare(uint32 x, uint32 oscLength); +public: + PCSpeaker(int rate = 44100); + ~PCSpeaker(); + + /** Play a note for length microseconds. + */ + void play(int freq, uint32 length); + /** Stop the currently playing sequence */ + void stop(); + /** Adjust the volume. */ + void setVolume(byte volume); + + bool isPlaying() const; + + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return false; } + bool endOfData() const { return false; } + bool endOfStream() const { return false; } + int getRate() const { return _rate; } +}; + +class SoundManager { +private: + Audio::Mixer *_mixer; + PCSpeaker *_speakerStream; + Audio::SoundHandle _speakerHandle; +public: + SoundManager(Audio::Mixer *mixer); + ~SoundManager(); + + void playNote(int frequency, int32 length); + + void demus(const byte *PSrc, byte *PDest, int NbreSeg); + void litph(tablint &t, int typ, int tempo); + void musyc(tablint &tb, int nbseg, int att); +}; } // End of namespace Mortevielle diff --git a/engines/mortevielle/var_mor.cpp b/engines/mortevielle/var_mor.cpp index 38a374e82b..bd432124db 100644 --- a/engines/mortevielle/var_mor.cpp +++ b/engines/mortevielle/var_mor.cpp @@ -359,9 +359,4 @@ void musyc(tablint &tb, int nbseg, int att) { warning("TODO: musyc"); } -// (* external 'c:\mc\phint.com'; *) -void litph(tablint &t, int typ, int tempo) { - warning("TODO: litph"); -} - } // End of namespace Mortevielle diff --git a/engines/mortevielle/var_mor.h b/engines/mortevielle/var_mor.h index b8a18a1253..5d62e77a1c 100644 --- a/engines/mortevielle/var_mor.h +++ b/engines/mortevielle/var_mor.h @@ -454,10 +454,6 @@ extern void box(int c, int Gd, int xo, int yo, int xi, int yi, int patt); extern void decomp(int seg, int dep); // (* external 'c:\mc\affich.com'; *) extern void afff(int Gd, int seg, int dep, int x, int y); -// (* external 'c:\mc\reusint.com'; *) -extern void musyc(tablint &tb, int nbseg, int att); -// (* external 'c:\mc\phint.com'; *) -extern void litph(tablint &t, int typ, int tempo); } // End of namespace Mortevielle -- cgit v1.2.3