diff --git a/src/common/AudioQueue.cxx b/src/common/AudioQueue.cxx new file mode 100644 index 000000000..0f12f4244 --- /dev/null +++ b/src/common/AudioQueue.cxx @@ -0,0 +1,133 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#include "AudioQueue.hxx" + +using std::mutex; +using std::lock_guard; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioQueue::AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate) + : myFragmentSize(fragmentSize), + myIsStereo(isStereo), + myFragmentQueue(capacity), + myAllFragments(capacity + 2), + mySize(0), + myNextFragment(0) +{ + const uInt8 sampleSize = myIsStereo ? 2 : 1; + + myFragmentBuffer = new Int16[myFragmentSize * sampleSize * (capacity + 2)]; + + for (uInt8 i = 0; i < capacity; i++) + myFragmentQueue[i] = myAllFragments[i] = myFragmentBuffer + i * sampleSize * myFragmentSize; + + myAllFragments[capacity] = myFirstFragmentForEnqueue = + myFragmentBuffer + capacity * sampleSize * myFragmentSize; + + myAllFragments[capacity + 1] = myFirstFragmentForDequeue = + myFragmentBuffer + (capacity + 1) * sampleSize * myFragmentSize; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +AudioQueue::~AudioQueue() +{ + delete[]myFragmentBuffer; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt8 AudioQueue::capacity() const +{ + return myFragmentQueue.size(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt8 AudioQueue::size() +{ + lock_guard guard(myMutex); + + return mySize; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool AudioQueue::isStereo() const +{ + return myIsStereo; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 AudioQueue::fragmentSize() const +{ + return myFragmentSize; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt16 AudioQueue::sampleRate() const +{ + return mySampleRate; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int16* AudioQueue::enqueue(Int16* fragment) +{ + lock_guard guard(myMutex); + + Int16* newFragment; + + if (!fragment) { + if (!myFirstFragmentForEnqueue) throw runtime_error("enqueue called empty"); + + newFragment = myFirstFragmentForEnqueue; + myFirstFragmentForEnqueue = 0; + + return newFragment; + } + + const uInt8 capacity = myFragmentQueue.size(); + const uInt8 fragmentIndex = (myNextFragment + mySize) % capacity; + + newFragment = myFragmentQueue.at(fragmentIndex); + myFragmentQueue.at(fragmentIndex) = fragment; + + if (mySize < capacity) mySize++; + else myNextFragment = (myNextFragment + 1) % capacity; + + return newFragment; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Int16* AudioQueue::dequeue(Int16* fragment) +{ + lock_guard guard(myMutex); + + if (mySize == 0) return 0; + + if (!fragment) { + if (!myFirstFragmentForDequeue) throw runtime_error("dequeue called empty"); + + fragment = myFirstFragmentForDequeue; + myFirstFragmentForDequeue = 0; + } + + Int16* nextFragment = myFragmentQueue.at(myNextFragment); + myFragmentQueue.at(myNextFragment) = fragment; + + mySize--; + myNextFragment = (myNextFragment + 1) % myFragmentQueue.size(); + + return nextFragment; +} diff --git a/src/common/AudioQueue.hxx b/src/common/AudioQueue.hxx new file mode 100644 index 000000000..ec1637dd8 --- /dev/null +++ b/src/common/AudioQueue.hxx @@ -0,0 +1,141 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony +// and the Stella Team +// +// See the file "License.txt" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +//============================================================================ + +#ifndef AUDIO_QUEUE_HXX +#define AUDIO_QUEUE_HXX + +#include + +#include "bspf.hxx" + +/** + This class implements a an audio queue that acts both like a ring buffer + and a pool of audio fragments. The TIA emulation core fills a fragment + with samples and then returns it to the queue, receiving a new fragment + in return. The sound driver removes fragments for playback from the + queue and returns the used fragment in this process. + + The queue needs to be threadsafe as the (SDL) audio driver runs on a + separate thread. Samples are stored as signed 16 bit integers + (platform endian). + */ +class AudioQueue +{ + public: + + /** + Create a new AudioQueue. + + @param fragmentSize The size (in stereo / mono samples) of each fragment + @param capacaity The number of fragments that can be queued before wrapping. + @param isStereo Whether samples are stereo or mono. + @param sampleRate The sample rate. This is not used, but can be queried. + */ + AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate); + + /** + We need a destructor to deallocate the individual fragment buffers. + */ + ~AudioQueue(); + + /** + Capacity getter. + */ + uInt8 capacity() const; + + /** + Size getter. + */ + uInt8 size(); + + /** + Stereo / mono getter. + */ + bool isStereo() const; + + /** + Fragment size getter. + */ + uInt32 fragmentSize() const; + + /** + Sample rate getter. + */ + uInt16 sampleRate() const; + + /** + Enqueue a new fragment and get a new fragmen to fill. + + @param fragment The returned fragment. This must be empty on the first call (when + there is nothing to return) + */ + Int16* enqueue(Int16* fragment = 0); + + /** + * Dequeue a fragment for playback and return the played fragment. This may + * return 0 if there is no queued fragment to return (in this case, the returned + * fragment is not enqueued and must be passed in the next invocation). + * + * @param fragment The returned fragment. This must be empty on the first call (when + * there is nothing to return). + */ + Int16* dequeue(Int16* fragment = 0); + + private: + + // The size of an individual fragment (in stereo / mono samples) + uInt32 myFragmentSize; + + // Are we using stereo samples? + bool myIsStereo; + + // The sample rate + uInt16 mySampleRate; + + // The fragment queue + vector myFragmentQueue; + + // All fragments, including the two fragments that are in circulation. + vector myAllFragments; + + // We allocate a consecutive slice of memory for the fragments. + Int16* myFragmentBuffer; + + // The nubmer if queued fragments + uInt8 mySize; + + // The next fragment. + uInt8 myNextFragment; + + // We need a mutex for thread safety. + std::mutex myMutex; + + // The first (empty) enqueue call returns this fragment. + Int16* myFirstFragmentForEnqueue; + // The first (empty) dequeue call replaces the returned fragment with this fragment. + Int16* myFirstFragmentForDequeue; + + private: + + AudioQueue() = delete; + AudioQueue(const AudioQueue&) = delete; + AudioQueue(AudioQueue&&) = delete; + AudioQueue& operator=(const AudioQueue&) = delete; + AudioQueue& operator=(AudioQueue&&) = delete; +}; + +#endif // AUDIO_QUEUE_HXX diff --git a/src/common/module.mk b/src/common/module.mk index 37690f7dd..beea041b5 100644 --- a/src/common/module.mk +++ b/src/common/module.mk @@ -12,7 +12,8 @@ MODULE_OBJS := \ src/common/MouseControl.o \ src/common/RewindManager.o \ src/common/StateManager.o \ - src/common/ZipHandler.o + src/common/ZipHandler.o \ + src/common/AudioQueue.o MODULE_DIRS += \ src/common