diff --git a/stella/src/common/SoundSDL.cxx b/stella/src/common/SoundSDL.cxx index 7623b5537..00859969e 100644 --- a/stella/src/common/SoundSDL.cxx +++ b/stella/src/common/SoundSDL.cxx @@ -13,26 +13,24 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: SoundSDL.cxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +// $Id: SoundSDL.cxx,v 1.2 2004-06-13 05:03:26 bwmott Exp $ //============================================================================ +#include #include #include "TIASound.h" +#include "Console.hxx" #include "Serializer.hxx" #include "Deserializer.hxx" #include "System.hxx" - #include "SoundSDL.hxx" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SoundSDL::SoundSDL(uInt32 fragsize, uInt32 queuesize) - : myCurrentVolume(SDL_MIX_MAXVOLUME), - myFragmentSize(fragsize), - myIsInitializedFlag(false), +SoundSDL::SoundSDL(uInt32 fragsize) + : myIsInitializedFlag(false), myIsMuted(false), - mySampleRate(31400), - mySampleQueue(queuesize) + myVolume(100) { if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { @@ -43,10 +41,10 @@ SoundSDL::SoundSDL(uInt32 fragsize, uInt32 queuesize) else { SDL_AudioSpec desired; - desired.freq = mySampleRate; + desired.freq = 31400; desired.format = AUDIO_U8; desired.channels = 1; - desired.samples = myFragmentSize; + desired.samples = fragsize; desired.callback = callback; desired.userdata = (void*)this; @@ -61,7 +59,7 @@ SoundSDL::SoundSDL(uInt32 fragsize, uInt32 queuesize) // will not work so we'll need to disable the audio support) if(((float)myHardwareSpec.samples / (float)myHardwareSpec.freq) >= 0.25) { - cerr << "WARNING: Audio device doesn't support real time audio! Make "; + cerr << "WARNING: Audio device doesn't support realtime audio! Make "; cerr << "sure a sound" << endl; cerr << " server isn't running. Audio is disabled..." << endl; @@ -71,18 +69,18 @@ SoundSDL::SoundSDL(uInt32 fragsize, uInt32 queuesize) myIsInitializedFlag = true; myIsMuted = false; - mySampleRate = myHardwareSpec.freq; - myFragmentSize = myHardwareSpec.samples; +/* cerr << "Freq: " << (int)myHardwareSpec.freq << endl; cerr << "Format: " << (int)myHardwareSpec.format << endl; cerr << "Channels: " << (int)myHardwareSpec.channels << endl; cerr << "Silence: " << (int)myHardwareSpec.silence << endl; cerr << "Samples: " << (int)myHardwareSpec.samples << endl; cerr << "Size: " << (int)myHardwareSpec.size << endl; +*/ // Now initialize the TIASound object which will actually generate sound - Tia_sound_init(31400, mySampleRate); + Tia_sound_init(31400, myHardwareSpec.freq); // And start the SDL sound subsystem ... SDL_PauseAudio(0); @@ -92,8 +90,10 @@ SoundSDL::SoundSDL(uInt32 fragsize, uInt32 queuesize) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SoundSDL::~SoundSDL() { + // Close the SDL audio system if it's initialized if(myIsInitializedFlag) { + SDL_PauseAudio(1); SDL_CloseAudio(); } @@ -120,37 +120,186 @@ void SoundSDL::mute(bool state) myIsMuted = state; SDL_PauseAudio(myIsMuted ? 1 : 0); + myRegWriteQueue.clear(); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::reset() +{ + if(myIsInitializedFlag) + { + SDL_PauseAudio(1); + myIsMuted = false; + myLastRegisterSetCycle = 0; + myRegWriteQueue.clear(); + SDL_PauseAudio(0); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void SoundSDL::setVolume(Int32 percent) { - // TODO: Verify that this works... if(myIsInitializedFlag) { if((percent >= 0) && (percent <= 100)) { SDL_LockAudio(); - myCurrentVolume = (uInt32)(((float)percent / 100.0) * SDL_MIX_MAXVOLUME); + myVolume = percent; + Tia_volume(percent); SDL_UnlockAudio(); } - else if(percent == -1) // If -1 has been specified, play sound at default volume - { - myCurrentVolume = SDL_MIX_MAXVOLUME; - } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL::update() +void SoundSDL::set(uInt16 addr, uInt8 value, Int32 cycle) { + SDL_LockAudio(); + + // First, calulate how many seconds would have past since the last + // register write on a real 2600 + double delta = (((double)(cycle - myLastRegisterSetCycle)) / + (1193191.66666667)); + + // Now, adjust the time based on the frame rate the user has selected + delta = delta * (60.0 / (double)myConsole->frameRate()); + + RegWrite info; + info.addr = addr; + info.value = value; + info.delta = delta; + myRegWriteQueue.enqueue(info); + + // Update last cycle counter to the current cycle + myLastRegisterSetCycle = cycle; + + SDL_UnlockAudio(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL::set(uInt16 addr, uInt8 value) +void SoundSDL::processFragment(uInt8* stream, Int32 length) { - Update_tia_sound(addr, value); + if(!myIsInitializedFlag) + { + return; + } + + // If there are excessive items on the queue then we'll remove some + if(myRegWriteQueue.duration() > (10.0 / 60.0)) + { + double removed = 0.0; + while(removed < (8.0 / 60.0)) + { + RegWrite& info = myRegWriteQueue.front(); + removed += info.delta; + Update_tia_sound(info.addr, info.value); + myRegWriteQueue.dequeue(); + } +// cout << "Removed Items from RegWriteQueue!" << endl; + } + + double position = 0.0; + double remaining = length; + + while(remaining > 0.0) + { + if(myRegWriteQueue.size() == 0) + { + // There are no more pending TIA sound register updates so we'll + // use the current settings to finish filling the sound fragment + Tia_process(stream + (uInt32)position, length - (uInt32)position); + + // Since we had to fill the fragment we'll reset the cycle counter + // to zero. NOTE: This isn't 100% correct, however, it'll do for + // now. We should really remember the overrun and remove it from + // the delta of the next write. + myLastRegisterSetCycle = 0; + break; + } + else + { + // There are pending TIA sound register updates so we need to + // update the sound buffer to the point of the next register update + RegWrite& info = myRegWriteQueue.front(); + + // How long will the remaing samples in the fragment take to play + double duration = remaining / (double)myHardwareSpec.freq; + + // Does the register update occur before the end of the fragment? + if(info.delta <= duration) + { + if(info.delta > 0.0) + { + // Process the fragment upto the next TIA register write + double samples = (myHardwareSpec.freq * info.delta); + Tia_process(stream + (uInt32)position, (uInt32)samples + + (uInt32)(position + samples) - + ((uInt32)position + (uInt32)samples)); + position += samples; + remaining -= samples; + } + Update_tia_sound(info.addr, info.value); + myRegWriteQueue.dequeue(); + } + else + { + // The next register update occurs in the next fragment so finish + // this fragment with the current TIA settings and reduce the register + // update delay by the corresponding amount of time + Tia_process(stream + (uInt32)position, length - (uInt32)position); + info.delta -= duration; + break; + } + } + } + + //cout << myRegWriteQueue.size() << endl; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::callback(void* udata, uInt8* stream, int len) +{ + SoundSDL* sound = (SoundSDL*)udata; + sound->processFragment(stream, (Int32)len); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool SoundSDL::load(Deserializer& in) +{ + string device = "TIASound"; + + try + { + if(in.getString() != device) + return false; + + uInt8 reg1 = 0, reg2 = 0, reg3 = 0, reg4 = 0, reg5 = 0, reg6 = 0; + reg1 = (uInt8) in.getLong(); + reg2 = (uInt8) in.getLong(); + reg3 = (uInt8) in.getLong(); + reg4 = (uInt8) in.getLong(); + reg5 = (uInt8) in.getLong(); + reg6 = (uInt8) in.getLong(); + + myLastRegisterSetCycle = (Int32) in.getLong(); + + // Only update the TIA sound registers if sound is enabled + if(myIsInitializedFlag) + Tia_set_registers(reg1, reg2, reg3, reg4, reg5, reg6); + } + catch(char *msg) + { + cerr << msg << endl; + return false; + } + catch(...) + { + cerr << "Unknown error in load state for " << device << endl; + return false; + } + + return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -175,7 +324,7 @@ bool SoundSDL::save(Serializer& out) out.putLong(reg5); out.putLong(reg6); - out.putLong(myLastSoundUpdateCycle); + out.putLong(myLastRegisterSetCycle); } catch(char *msg) { @@ -192,143 +341,88 @@ bool SoundSDL::save(Serializer& out) } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool SoundSDL::load(Deserializer& in) -{ - string device = "TIASound"; - - try - { - if(in.getString() != device) - return false; - - uInt8 reg1 = 0, reg2 = 0, reg3 = 0, reg4 = 0, reg5 = 0, reg6 = 0; - reg1 = (uInt8) in.getLong(); - reg2 = (uInt8) in.getLong(); - reg3 = (uInt8) in.getLong(); - reg4 = (uInt8) in.getLong(); - reg5 = (uInt8) in.getLong(); - reg6 = (uInt8) in.getLong(); - - myLastSoundUpdateCycle = (Int32) in.getLong(); - - // Only update the TIA sound registers if sound is enabled - if(myIsInitializedFlag) - Tia_set_registers(reg1, reg2, reg3, reg4, reg5, reg6); - } - catch(char *msg) - { - cerr << msg << endl; - return false; - } - catch(...) - { - cerr << "Unknown error in load state for " << device << endl; - return false; - } - - return true; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL::callback(void* udata, uInt8* stream, int len) -{ - SoundSDL* sound = (SoundSDL*)udata; - - if(sound->isSuccessfullyInitialized()) - { - Tia_process(stream, len); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SoundSDL::SampleQueue::SampleQueue(uInt32 capacity) +SoundSDL::RegWriteQueue::RegWriteQueue(uInt32 capacity) : myCapacity(capacity), myBuffer(0), mySize(0), myHead(0), myTail(0) { - myBuffer = new uInt8[myCapacity]; + myBuffer = new RegWrite[myCapacity]; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SoundSDL::SampleQueue::~SampleQueue() +SoundSDL::RegWriteQueue::~RegWriteQueue() { delete[] myBuffer; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL::SampleQueue::clear() +void SoundSDL::RegWriteQueue::clear() { myHead = myTail = mySize = 0; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 SoundSDL::SampleQueue::dequeue(uInt8* buffer, uInt32 size) +void SoundSDL::RegWriteQueue::dequeue() { - // We can only dequeue up to the number of items in the queue - if(size > mySize) + if(mySize > 0) { - size = mySize; + myHead = (myHead + 1) % myCapacity; + --mySize; } - - if((myHead + size) < myCapacity) - { - memcpy((void*)buffer, (const void*)(myBuffer + myHead), size); - myHead += size; - } - else - { - uInt32 s1 = myCapacity - myHead; - uInt32 s2 = size - s1; - memcpy((void*)buffer, (const void*)(myBuffer + myHead), s1); - memcpy((void*)(buffer + s1), (const void*)myBuffer, s2); - myHead = (myHead + size) % myCapacity; - } - - mySize -= size; - - return size; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundSDL::SampleQueue::enqueue(uInt8* buffer, uInt32 size) +double SoundSDL::RegWriteQueue::duration() +{ + double duration = 0.0; + for(uInt32 i = 0; i < mySize; ++i) + { + duration += myBuffer[(myHead + i) % myCapacity].delta; + } + return duration; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::RegWriteQueue::enqueue(const RegWrite& info) { // If an attempt is made to enqueue more than the queue can hold then - // we'll only enqueue the last myCapacity elements. - if(size > myCapacity) + // we'll enlarge the queue's capacity. + if(mySize == myCapacity) { - buffer += (size - myCapacity); - size = myCapacity; + grow(); } - if((myTail + size) < myCapacity) - { - memcpy((void*)(myBuffer + myTail), (const void*)buffer, size); - myTail += size; - } - else - { - uInt32 s1 = myCapacity - myTail; - uInt32 s2 = size - s1; - memcpy((void*)(myBuffer + myTail), (const void*)buffer, s1); - memcpy((void*)myBuffer, (const void*)(buffer + s1), s2); - myTail = (myTail + size) % myCapacity; - } - - if((mySize + size) > myCapacity) - { - myHead = (myHead + ((mySize + size) - myCapacity)) % myCapacity; - mySize = myCapacity; - } - else - { - mySize += size; - } + myBuffer[myTail] = info; + myTail = (myTail + 1) % myCapacity; + ++mySize; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 SoundSDL::SampleQueue::size() const +SoundSDL::RegWrite& SoundSDL::RegWriteQueue::front() +{ + assert(mySize != 0); + return myBuffer[myHead]; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 SoundSDL::RegWriteQueue::size() const { return mySize; } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::RegWriteQueue::grow() +{ + RegWrite* buffer = new RegWrite[myCapacity * 2]; + for(uInt32 i = 0; i < mySize; ++i) + { + buffer[i] = myBuffer[(myHead + i) % myCapacity]; + } + myHead = 0; + myTail = mySize; + myCapacity = myCapacity * 2; + delete[] myBuffer; + myBuffer = buffer; +} diff --git a/stella/src/common/SoundSDL.hxx b/stella/src/common/SoundSDL.hxx index 0d4b3770c..58731f0b1 100644 --- a/stella/src/common/SoundSDL.hxx +++ b/stella/src/common/SoundSDL.hxx @@ -8,12 +8,12 @@ // SS SS tt ee ll ll aa aa // SSSS ttt eeeee llll llll aaaaa // -// Copyright (c) 1995-2002 by Bradford W. Mott +// Copyright (c) 1995-2004 by Bradford W. Mott // // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: SoundSDL.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +// $Id: SoundSDL.hxx,v 1.2 2004-06-13 05:03:26 bwmott Exp $ //============================================================================ #ifndef SOUNDSDL_HXX @@ -29,155 +29,162 @@ This class implements the sound API for SDL. @author Stephen Anthony and Bradford W. Mott - @version $Id: SoundSDL.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ + @version $Id: SoundSDL.hxx,v 1.2 2004-06-13 05:03:26 bwmott Exp $ */ class SoundSDL : public Sound { public: /** - Create a new sound object + Create a new sound object. The init method must be invoked before + using the object. */ - SoundSDL(uInt32 fragsize, uInt32 queuesize); + SoundSDL(uInt32 fragsize); /** Destructor */ virtual ~SoundSDL(); - public: - /** - Closes the sound device - */ - void closeDevice(); - - /** - Return the playback sample rate for the sound device. - - @return The playback sample rate - */ - uInt32 getSampleRate() const; - + public: /** Return true iff the sound device was successfully initialized. @return true iff the sound device was successfully initialized */ - bool isSuccessfullyInitialized() const; + virtual bool isSuccessfullyInitialized() const; /** - Set the mute state of the sound object. + Set the mute state of the sound object. While muted no sound is played. @param state Mutes sound if true, unmute if false */ - void mute(bool state); + virtual void mute(bool state); /** - Sets the volume of the sound device to the specified level. The - volume is given as a percentage from 0 to 100. A -1 indicates - that the volume shouldn't be changed at all. - - @param percent The new volume percentage level for the sound device + Resets the sound device. */ - void setVolume(Int32 percent); - - /** - Generates audio samples to fill the sample queue. - */ - void update(); + virtual void reset(); /** Sets the sound register to a given value. - @param addr The register address + @param addr The register address @param value The value to save into the register + @param cycle The CPU cycle at which the register is being updated */ - void set(uInt16 addr, uInt8 value); + virtual void set(uInt16 addr, uInt8 value, Int32 cycle); + + /** + Sets the volume of the sound device to the specified level. The + volume is given as a percentage from 0 to 100. Values outside + this range indicate that the volume shouldn't be changed at all. + + @param percent The new volume percentage level for the sound device + */ + virtual void setVolume(Int32 percent); + + public: + /** + Loads the current state of this device from the given Deserializer. + + @param in The deserializer device to load from. + @return The result of the load. True on success, false on failure. + */ + bool load(Deserializer& in); /** Saves the current state of this device to the given Serializer. - @param out The serializer device to save to. - @return The result of the save. True on success, false on failure. + @param out The serializer device to save to. + @return The result of the save. True on success, false on failure. */ bool save(Serializer& out); + protected: /** - Loads the current state of this device from the given Deserializer. + Invoked by the sound callback to process the next sound fragment. - @param in The deserializer device to load from. - @return The result of the load. True on success, false on failure. + @param stream Pointer to the start of the fragment + @param length Length of the fragment */ - bool load(Deserializer& in); + void processFragment(uInt8* stream, Int32 length); + + protected: + // Struct to hold information regarding a TIA sound register write + struct RegWrite + { + uInt16 addr; + uInt8 value; + double delta; + }; - private: /** - A bounded queue class used to hold audio samples after they are - produced by the MediaSource. + A queue class used to hold TIA sound register writes before being + processed while creating a sound fragment. */ - class SampleQueue + class RegWriteQueue { public: /** - Create a new SampleQueue instance which can hold the specified - number of samples. If the queue ever reaches its capacity then - older samples are discarded. + Create a new queue instance with the specified initial + capacity. If the queue ever reaches its capacity then it will + automatically increase its size. */ - SampleQueue(uInt32 capacity); + RegWriteQueue(uInt32 capacity = 512); /** - Destroy this SampleQueue instance. + Destroy this queue instance. */ - virtual ~SampleQueue(); + virtual ~RegWriteQueue(); public: /** - Clear any samples stored in the queue. + Clear any items stored in the queue. */ void clear(); /** - Dequeue the upto the specified number of samples and store them - in the buffer. Returns the actual number of samples removed from - the queue. - - @return the actual number of samples removed from the queue. + Dequeue the first object in the queue. */ - uInt32 dequeue(uInt8* buffer, uInt32 size); + void dequeue(); /** - Enqueue the specified number of samples from the buffer. + Return the duration of all the items in the queue. */ - void enqueue(uInt8* buffer, uInt32 size); + double duration(); /** - Answers the number of samples currently in the queue. + Enqueue the specified object. + */ + void enqueue(const RegWrite& info); - @return The number of samples in the queue. + /** + Return the item at the front on the queue. + + @return The item at the front of the queue. + */ + RegWrite& front(); + + /** + Answers the number of items currently in the queue. + + @return The number of items in the queue. */ uInt32 size() const; - /** - Answers the maximum number of samples the queue can hold. - - @return The maximum number of samples in the queue. - */ - uInt32 capacity() const { return myCapacity; } + private: + // Increase the size of the queue + void grow(); private: - const uInt32 myCapacity; - uInt8* myBuffer; + uInt32 myCapacity; + RegWrite* myBuffer; uInt32 mySize; uInt32 myHead; uInt32 myTail; }; private: - // Current volume - uInt32 myCurrentVolume; - - // SDL fragment size - uInt32 myFragmentSize; - // Audio specification structure SDL_AudioSpec myHardwareSpec; @@ -187,18 +194,15 @@ class SoundSDL : public Sound // Indicates if the sound is currently muted bool myIsMuted; - // DSP sample rate - uInt32 mySampleRate; + // Current volume as a percentage (0 - 100) + uInt32 myVolume; - // The sample queue size (which is auto-adapting) - uInt32 mySampleQueueSize; - - // Queue which holds samples from the media source before they are played - SampleQueue mySampleQueue; + // Queue of TIA register writes + RegWriteQueue myRegWriteQueue; private: // Callback function invoked by the SDL Audio library when it needs data static void callback(void* udata, uInt8* stream, int len); }; -#endif +#endif \ No newline at end of file