diff --git a/src/common/SoundSDL2.cxx b/src/common/SoundSDL2.cxx index 2a7e9a61f..cf5632f8d 100644 --- a/src/common/SoundSDL2.cxx +++ b/src/common/SoundSDL2.cxx @@ -72,6 +72,10 @@ SoundSDL2::~SoundSDL2() { ASSERT_MAIN_THREAD; + stopWav(); + if(myWavDevice) + SDL_CloseAudioDevice(myWavDevice); + if (!myIsInitializedFlag) return; SDL_CloseAudioDevice(myDevice); @@ -194,6 +198,7 @@ void SoundSDL2::open(shared_ptr audioQueue, // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL2::close() { + stopWav(); if(!myIsInitializedFlag) return; mute(true); @@ -209,6 +214,8 @@ bool SoundSDL2::mute(bool state) const bool oldstate = SDL_GetAudioDeviceStatus(myDevice) == SDL_AUDIO_PAUSED; if(myIsInitializedFlag) SDL_PauseAudioDevice(myDevice, state ? 1 : 0); + if(myWavDevice) + SDL_PauseAudioDevice(myWavDevice, state ? 1 : 0); return oldstate; } @@ -395,4 +402,57 @@ void SoundSDL2::callback(void* udata, uInt8* stream, int len) SDL_memset(stream, 0, len); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool SoundSDL2::playWav(const char* fileName, uInt32 position, uInt32 length) +{ + // ToDos: + // - volume (requires callback using SDL_MixAudio) + // - (un)mute + SDL_AudioSpec wavSpec; + uInt32 wavLength; + + // Stop any playing WAVs + stopWav(); + + // Load WAV file + SDL_AudioSpec* result = SDL_LoadWAV(fileName, &wavSpec, &myWavBuffer, &wavLength); + if(result == NULL || position > wavLength) + return false; + + length = length + ? std::min(length, wavLength - position) + : wavLength; + + // Open audio device + const char* device = myDeviceId ? myDevices.at(myDeviceId).first.c_str() : nullptr; + + myWavDevice = SDL_OpenAudioDevice(device, 0, &wavSpec, NULL, 0); + if(!myWavDevice) + return false; + + // Play audio + int success = SDL_QueueAudio(myWavDevice, myWavBuffer + position, length); + SDL_PauseAudioDevice(myWavDevice, 0); + + return success == 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL2::stopWav() +{ + if(myWavBuffer) + { + // Clean up + SDL_CloseAudioDevice(myWavDevice); + SDL_FreeWAV(myWavBuffer); + + myWavBuffer = nullptr; + } +} + +uInt32 SoundSDL2::wavSize() const +{ + return myWavBuffer ? SDL_GetQueuedAudioSize(myWavDevice) : 0; +} + #endif // SOUND_SUPPORT diff --git a/src/common/SoundSDL2.hxx b/src/common/SoundSDL2.hxx index 8c91b5cca..20a58429a 100644 --- a/src/common/SoundSDL2.hxx +++ b/src/common/SoundSDL2.hxx @@ -107,6 +107,29 @@ class SoundSDL2 : public Sound */ string about() const override; + /** + Play a WAV file. + + @param fileName The name of the WAV file + @param position The position to start playing + @param length The played length + + @return True, if the WAV file can be played + */ + bool playWav(const char* fileName, uInt32 position = 0, uInt32 length = 0) override; + + /** + Stop any currently playing WAV file. + */ + void stopWav() override; + + /** + Get the size of the WAV file which remains to be played. + + @return The remaining number of bytes + */ + uInt32 wavSize() const override; + protected: /** This method is called to query the audio devices. @@ -161,6 +184,9 @@ class SoundSDL2 : public Sound AudioSettings& myAudioSettings; + SDL_AudioDeviceID myWavDevice{0}; + uInt8* myWavBuffer{nullptr}; + string myAboutString; private: diff --git a/src/emucore/Console.cxx b/src/emucore/Console.cxx index b6e9f792d..9e34d0ed8 100644 --- a/src/emucore/Console.cxx +++ b/src/emucore/Console.cxx @@ -1028,7 +1028,8 @@ unique_ptr Console::getControllerPort(const Controller::Type type, break; case Controller::Type::KidVid: - controller = make_unique(port, myEvent, *mySystem, myOSystem.baseDir().getPath(), romMd5); + controller = make_unique(port, myEvent, *mySystem, myOSystem.baseDir().getPath(), + myOSystem.sound(), romMd5); break; case Controller::Type::MindLink: diff --git a/src/emucore/KidVid.cxx b/src/emucore/KidVid.cxx index c72261ac4..9d4a6e137 100644 --- a/src/emucore/KidVid.cxx +++ b/src/emucore/KidVid.cxx @@ -16,14 +16,16 @@ //============================================================================ #include "Event.hxx" +#include "Sound.hxx" #include "KidVid.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - KidVid::KidVid(Jack jack, const Event& event, const System& system, - const string& baseDir, const string& romMd5) + const string& baseDir, Sound& sound, const string& romMd5) : Controller(jack, event, system, Controller::Type::KidVid), myEnabled{myJack == Jack::Right}, - myBaseDir{baseDir} + myBaseDir{baseDir}, + mySound{sound} { // Right now, there are only two games that use the KidVid if(romMd5 == "ee6665683ebdb539e89ba620981cb0f6") @@ -59,7 +61,8 @@ void KidVid::update() if(myEvent.get(Event::ConsoleReset)) { myTape = 0; // rewind Kid Vid tape - closeSampleFiles(); + myFilesFound = mySongPlaying = false; + mySound.stopWav(); } if(!myTape) { @@ -71,9 +74,8 @@ void KidVid::update() myTape = myGame == BBears ? 4 : 1; // Berenstain Bears or Smurfs Save The Day? if(myTape) { - //cerr << "myTape = " << myTape << endl;0 - myIdx = myGame == BBears ? BlockBits : 0; - myBlockIdx = BlockBits; + myIdx = myGame == BBears ? NumBlockBits : 0; + myBlockIdx = NumBlockBits; myBlock = 0; openSampleFiles(); } @@ -92,49 +94,58 @@ void KidVid::update() if(!myBlockIdx) { if(!myBlock) - myIdx = ((myTape * 6) + 12 - Blocks) * 8; //KVData00-KVData=12 + myIdx = ((myTape * 6) + 12 - NumBlocks) * 8; //KVData00-KVData=12 else { - if(myGame == Smurfs) - { - if(myBlock >= ourBlocks[myTape - 1]) - myIdx = 42 * 8; //KVData80-KVData=42 - else - { - myIdx = 36 * 8;//KVPause-KVData=36 - setNextSong(); - } - } + const uInt32 lastBlock = myGame == Smurfs + ? ourBlocks[myTape - 1] + : ourBlocks[myTape + 2 - 1]; + if(myBlock >= lastBlock) + myIdx = 42 * 8; //KVData80-KVData=42 else { - if(myBlock >= ourBlocks[myTape + 2 - 1]) - myIdx = 42 * 8; //KVData80-KVData=42 - else - { - myIdx = 36 * 8;//KVPause-KVData=36 - setNextSong(); - } + myIdx = 36 * 8;//KVPause-KVData=36 + setNextSong(); } } ++myBlock; - myBlockIdx = BlockBits; + myBlockIdx = NumBlockBits; + } + } + + if(myFilesFound) + { + if(mySongPlaying) + { + myTapeBusy = (mySound.wavSize() > 262 * 48) || !myBeep; + // Check for end of played sample + if(mySound.wavSize() == 0) + { + mySongPlaying = false; + myTapeBusy = !myBeep; + if(!myBeep) + setNextSong(); + } + } + } + else + { + if(mySongLength) + { + --mySongLength; + myTapeBusy = (mySongLength > 48); } } - // emulate playing the songs (but MUCH faster!) - // TODO: this has to be done by an Audio class - for(int i = mySongCounter / 8; i >= 0; --i) - getNextSampleByte(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void KidVid::openSampleFiles() { -#ifdef KID_TAPE static constexpr const char* fileNames[6] = { "KVS3.WAV", "KVS1.WAV", "KVS2.WAV", "KVB3.WAV", "KVB1.WAV", "KVB2.WAV" }; - static constexpr uInt32 startSong[6] = { + static constexpr uInt32 firstSongPointer[6] = { 44 + 38, 0, 44, @@ -143,114 +154,62 @@ void KidVid::openSampleFiles() 44 + 38 + 42 + 62 }; - if(!myFilesOpened) + if(!myFilesFound) { int i = myGame == Smurfs ? myTape - 1 : myTape + 2; if(myTape == 4) i = 3; - mySampleFile.open(myBaseDir + fileNames[i], std::ios::binary); - if(mySampleFile.is_open()) - { -cerr << "opened file: " << fileNames[i] << endl; - mySharedSampleFile.open(myBaseDir + "KVSHARED.WAV", std::ios::binary); - if(!mySharedSampleFile.is_open()) - mySampleFile.close(); - else - { -cerr << "opened file: " << "kvshared.wav" << endl; - mySampleFile.seekg(45); - myFilesOpened = true; - } - } - mySongCounter = 0; - myFilePointer = startSong[i]; + mySampleFile = myBaseDir + fileNames[i]; + + std::ifstream f1, f2; + f1.open(mySampleFile, std::ios::binary); + f2.open(myBaseDir + "KVSHARED.WAV", std::ios::binary); + + myFilesFound = f1.is_open() && f2.is_open(); + + if(myFilesFound) + cerr << "found file: " << fileNames[i] << endl + << "found file: " << "KVSHARED.WAV" << endl; + + mySongLength = 0; + mySongPointer = firstSongPointer[i]; } -#endif myTapeBusy = false; } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void KidVid::closeSampleFiles() -{ -#ifdef KID_TAPE - if(mySampleFile.is_open()) - mySampleFile.close(); - if(mySharedSampleFile.is_open()) - mySharedSampleFile.close(); - - myFilesOpened = false; -#endif -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void KidVid::setNextSong() { -#ifdef KID_TAPE - if(myFilesOpened) + if(myFilesFound) { - //cerr << endl << std::dec << mySongCounter << ", " << myFilePointer << endl; - myBeep = (ourSongPositions[myFilePointer] & 0x80) == 0; + myBeep = (ourSongPositions[mySongPointer] & 0x80) == 0; - const uInt8 temp = ourSongPositions[myFilePointer] & 0x7f; - mySharedData = (temp < 10); - mySongCounter = ourSongStart[temp+1] - ourSongStart[temp]; + const uInt8 temp = ourSongPositions[mySongPointer] & 0x7f; + mySongLength = ourSongStart[temp+1] - ourSongStart[temp]; - if(mySharedData) - mySharedSampleFile.seekg(ourSongStart[temp]); - else - mySampleFile.seekg(ourSongStart[temp]); + // Play the WAV file + const string fileName = (temp < 10) ? myBaseDir + "KVSHARED.WAV" : mySampleFile; + mySound.playWav(fileName.c_str(), ourSongStart[temp], mySongLength); - ++myFilePointer; - myTapeBusy = true; + mySongPlaying = myTapeBusy = true; + ++mySongPointer; } else -#endif { myBeep = true; myTapeBusy = true; - mySongCounter = 10 * 80*262; /* delay needed for Harmony without tape */ + mySongLength = 80; /* delay needed for Harmony without tape */ } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void KidVid::getNextSampleByte() -{ - static bool oddeven = false; - - if(mySongCounter == 0) - { -#ifdef KID_TAPE - mySampleByte = 0x80; -#endif - } - else - { - oddeven = !oddeven; - if(oddeven) - { - mySongCounter--; - myTapeBusy = (mySongCounter > 262 * 48) || !myBeep; - -#ifdef KID_TAPE - mySampleByte = myFilesOpened - ? (mySharedData ? mySharedSampleFile.get() : mySampleFile.get()) - : 0x80; -#endif - - if(!myBeep && (mySongCounter == 0)) - setNextSong(); - } - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const std::array KidVid::ourBlocks = { +const std::array KidVid::ourBlocks = { 2+40, 2+21, 2+35, /* Smurfs tapes 3, 1, 2 */ 42+60, 42+78, 42+60 /* BBears tapes 1, 2, 3 (40 extra blocks for intro) */ }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -const std::array KidVid::ourData = { +const std::array KidVid::ourData = { /* KVData44 */ 0x7b, // 0111 1011b ; (1)0 0x1e, // 0001 1110b ; 1 @@ -409,7 +368,7 @@ const std::array KidVid::ourSongStart = { 3720920, /* kvb1 */ - 44, /* 3 */ + 44, /* 3 */ // can be one too late! 592749, /* 5 */ 936142, /* 2 */ 1465343, /* 4 */ diff --git a/src/emucore/KidVid.hxx b/src/emucore/KidVid.hxx index 394ce9bc3..86d2880e1 100644 --- a/src/emucore/KidVid.hxx +++ b/src/emucore/KidVid.hxx @@ -18,9 +18,8 @@ #ifndef KIDVID_HXX #define KIDVID_HXX -//#define KID_TAPE - class Event; +class Sound; #include "bspf.hxx" #include "Control.hxx" @@ -48,7 +47,7 @@ class KidVid : public Controller @param romMd5 The md5 of the ROM using this controller */ KidVid(Jack jack, const Event& event, const System& system, - const string& baseDir, const string& romMd5); + const string& baseDir, Sound& sound, const string& romMd5); ~KidVid() override = default; public: @@ -76,21 +75,16 @@ class KidVid : public Controller private: // Open/close a WAV sample file void openSampleFiles(); - void closeSampleFiles(); // Jump to next song in the sequence void setNextSong(); - // Generate next sample byte - // TODO - rework this, perhaps send directly to sound class - void getNextSampleByte(); - private: static constexpr uInt32 - Smurfs = 0x44, - BBears = 0x48, - Blocks = 6, // number of bytes / block - BlockBits = Blocks*8, // number of bits / block + Smurfs = 0x44, + BBears = 0x48, + NumBlocks = 6, // number of bytes / block + NumBlockBits = NumBlocks*8, // number of bits / block SongPosSize = 44+38+42+62+80+62, SongStartSize = 104 ; @@ -100,28 +94,27 @@ class KidVid : public Controller bool myEnabled{false}; string myBaseDir; -#ifdef KID_TAPE - // The file streams for the WAV files - std::ifstream mySampleFile, mySharedSampleFile; - // Indicates if sample files have been successfully opened - bool myFilesOpened{false}; + Sound& mySound; - uInt32 myFilePointer{0}; - bool mySharedData{false}; - uInt8 mySampleByte{0}; -#endif + // Path and name of the current sample file + string mySampleFile; + // Indicates if the sample files have been found + bool myFilesFound{false}; + + uInt32 mySongPointer{0}; // Is the tape currently 'busy' / in use? bool myTapeBusy{false}; - uInt32 mySongCounter{0}; + bool mySongPlaying{false}; + uInt32 mySongLength{0}; bool myBeep{false}; uInt32 myGame{0}, myTape{0}; uInt32 myIdx{0}, myBlock{0}, myBlockIdx{0}; // Number of blocks and data on tape - static const std::array ourBlocks; - static const std::array ourData; + static const std::array ourBlocks; + static const std::array ourData; static const std::array ourSongPositions; static const std::array ourSongStart; diff --git a/src/emucore/Sound.hxx b/src/emucore/Sound.hxx index 8ddb2dda6..d01a7606d 100644 --- a/src/emucore/Sound.hxx +++ b/src/emucore/Sound.hxx @@ -23,6 +23,7 @@ class AudioQueue; class EmulationTiming; #include "bspf.hxx" +#include "Variant.hxx" /** This class is an abstract base class for the various sound objects. @@ -104,6 +105,29 @@ class Sound */ const VariantList& supportedDevices() const {return myDevices;} + /** + Play a WAV file. + + @param fileName The name of the WAV file + @param position The position to start playing + @param length The played length + + @return True, if the WAV file can be played + */ + virtual bool playWav(const char* fileName, uInt32 position = 0, uInt32 length = 0) = 0; + + /** + Stop any currently playing WAV file. + */ + virtual void stopWav() = 0; + + /** + Get the size of the WAV file which remains to be played. + + @return The remaining number of bytes + */ + virtual uInt32 wavSize() const = 0; + protected: /** This method is called to query the audio devices. @@ -112,6 +136,7 @@ class Sound */ virtual void queryHardware(VariantList& devices) = 0; + protected: // The OSystem for this sound object OSystem& myOSystem;