A fairly major rewrite of the class to improve the sound support. The

enhanced sound in games like Pitfall II and Quadrun should be working
now.  Also the overall sound in most has should be improved since the
TIA sound register writes and sample generation are somewhat synchronized.


git-svn-id: svn://svn.code.sf.net/p/stella/code/trunk@262 8b62c5a3-ac7e-4cc8-8f21-d9a121418aba
This commit is contained in:
bwmott 2004-06-13 05:03:26 +00:00
parent 3d231acd53
commit 2e09e2c00c
2 changed files with 309 additions and 211 deletions

View File

@ -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 <cassert>
#include <SDL.h>
#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;
}

View File

@ -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