#ifdef SOUND_SUPPORT
#include <sstream>
#include <cassert>
#include <cmath>
#include "SDL.h"
#include "TIASnd.hxx"
#include "Serializer.hxx"
#include "Deserializer.hxx"
#include "Settings.hxx"
#include "System.hxx"
#include "OSystem.hxx"
#include "SoundSDL.hxx"
#include "Log.hpp"
using namespace std;
using namespace ale;
SoundSDL::SoundSDL(OSystem* osystem)
: Sound(osystem),
myIsEnabled(osystem->settings().getBool("sound")),
myIsInitializedFlag(false),
myLastRegisterSetCycle(0),
myDisplayFrameRate(60),
myNumChannels(1),
myFragmentSizeLogBase2(0),
myIsMuted(false),
myVolume(100),
myNumRecordSamplesNeeded(0)
{
if (osystem->settings().getString("record_sound_filename").size() > 0) {
std::string filename = osystem->settings().getString("record_sound_filename");
mySoundExporter.reset(new ale::sound::SoundExporter(filename, myNumChannels));
}
}
SoundSDL::~SoundSDL()
{
close();
}
void SoundSDL::setEnabled(bool state)
{
myIsEnabled = state;
myOSystem->settings().setBool("sound", state);
}
void SoundSDL::initialize()
{
if(!myIsEnabled)
{
close();
if(myOSystem->settings().getBool("showinfo"))
cerr << "Sound disabled." << endl << endl;
return;
}
myRegWriteQueue.clear();
myTIASound.reset();
if(!((SDL_WasInit(SDL_INIT_AUDIO) & SDL_INIT_AUDIO) > 0))
{
myIsInitializedFlag = false;
myIsMuted = false;
myLastRegisterSetCycle = 0;
if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0)
{
Logger::Warning << "WARNING: Couldn't initialize SDL audio system! " << endl;
Logger::Warning << " " << SDL_GetError() << endl;
return;
}
else
{
uInt32 fragsize = myOSystem->settings().getInt("fragsize");
Int32 frequency = myOSystem->settings().getInt("freq");
Int32 tiafreq = myOSystem->settings().getInt("tiafreq");
SDL_AudioSpec desired;
desired.freq = frequency;
#ifndef GP2X
desired.format = AUDIO_U8;
#else
desired.format = AUDIO_U16;
#endif
desired.channels = myNumChannels;
desired.samples = fragsize;
desired.callback = callback;
desired.userdata = (void*)this;
if(SDL_OpenAudio(&desired, &myHardwareSpec) < 0)
{
Logger::Warning << "WARNING: Couldn't open SDL audio system! " << endl;
Logger::Warning << " " << SDL_GetError() << endl;
return;
}
if(((float)myHardwareSpec.samples / (float)myHardwareSpec.freq) >= 0.25)
{
Logger::Warning << "WARNING: Sound device doesn't support realtime audio! Make ";
Logger::Warning << "sure a sound" << endl;
Logger::Warning << " server isn't running. Audio is disabled." << endl;
SDL_CloseAudio();
return;
}
myIsInitializedFlag = true;
myIsMuted = false;
myFragmentSizeLogBase2 = log((double)myHardwareSpec.samples) / log(2.0);
myTIASound.outputFrequency(myHardwareSpec.freq);
myTIASound.tiaFrequency(tiafreq);
myTIASound.channels(myHardwareSpec.channels);
bool clipvol = myOSystem->settings().getBool("clipvol");
myTIASound.clipVolume(clipvol);
myVolume = myOSystem->settings().getInt("volume");
setVolume(myVolume);
if(myOSystem->settings().getBool("showinfo"))
cerr << "Sound enabled:" << endl
<< " Volume : " << myVolume << endl
<< " Frag size : " << fragsize << endl
<< " Frequency : " << myHardwareSpec.freq << endl
<< " Format : " << myHardwareSpec.format << endl
<< " TIA Freq. : " << tiafreq << endl
<< " Channels : " << myNumChannels << endl
<< " Clip volume: " << (int)clipvol << endl << endl;
}
}
if(myIsInitializedFlag)
{
SDL_PauseAudio(0);
}
}
void SoundSDL::close()
{
if(myIsInitializedFlag)
{
SDL_CloseAudio();
myIsInitializedFlag = false;
}
}
bool SoundSDL::isSuccessfullyInitialized() const
{
return myIsInitializedFlag;
}
void SoundSDL::mute(bool state)
{
if(myIsInitializedFlag)
{
if(myIsMuted == state)
{
return;
}
myIsMuted = state;
SDL_PauseAudio(myIsMuted ? 1 : 0);
myRegWriteQueue.clear();
}
}
void SoundSDL::reset()
{
if(myIsInitializedFlag)
{
SDL_PauseAudio(1);
myIsMuted = false;
myLastRegisterSetCycle = 0;
myTIASound.reset();
myRegWriteQueue.clear();
SDL_PauseAudio(0);
}
}
void SoundSDL::setVolume(Int32 percent)
{
if(myIsInitializedFlag)
{
if((percent >= 0) && (percent <= 100))
{
myOSystem->settings().setInt("volume", percent);
SDL_LockAudio();
myVolume = percent;
myTIASound.volume(percent);
SDL_UnlockAudio();
}
}
}
void SoundSDL::adjustVolume(Int8 direction)
{
ostringstream strval;
string message;
Int32 percent = myVolume;
if(direction == -1)
percent -= 2;
else if(direction == 1)
percent += 2;
if((percent < 0) || (percent > 100))
return;
setVolume(percent);
}
void SoundSDL::adjustCycleCounter(Int32 amount)
{
myLastRegisterSetCycle += amount;
}
void SoundSDL::setChannels(uInt32 channels)
{
if(channels == 1 || channels == 2)
myNumChannels = channels;
}
void SoundSDL::setFrameRate(uInt32 framerate)
{
myDisplayFrameRate = framerate;
myLastRegisterSetCycle = 0;
}
void SoundSDL::set(uInt16 addr, uInt8 value, Int32 cycle)
{
SDL_LockAudio();
double delta = (((double)(cycle - myLastRegisterSetCycle)) /
(1193191.66666667));
delta = delta * (myDisplayFrameRate / (double)myOSystem->frameRate());
RegWrite info;
info.addr = addr;
info.value = value;
info.delta = delta;
myRegWriteQueue.enqueue(info);
myLastRegisterSetCycle = cycle;
SDL_UnlockAudio();
}
void SoundSDL::processFragment(uInt8* stream, Int32 length)
{
if(!myIsInitializedFlag)
return;
uInt32 channels = myHardwareSpec.channels;
length = length / channels;
if(myRegWriteQueue.duration() >
(myFragmentSizeLogBase2 / myDisplayFrameRate))
{
double removed = 0.0;
while(removed < ((myFragmentSizeLogBase2 - 1) / myDisplayFrameRate))
{
RegWrite& info = myRegWriteQueue.front();
removed += info.delta;
myTIASound.set(info.addr, info.value);
myRegWriteQueue.dequeue();
}
}
double position = 0.0;
double remaining = length;
while(remaining > 0.0)
{
if(myRegWriteQueue.size() == 0)
{
myTIASound.process(stream + ((uInt32)position * channels),
length - (uInt32)position);
myLastRegisterSetCycle = 0;
break;
}
else
{
RegWrite& info = myRegWriteQueue.front();
double duration = remaining / (double)myHardwareSpec.freq;
if(info.delta <= duration)
{
if(info.delta > 0.0)
{
double samples = (myHardwareSpec.freq * info.delta);
myTIASound.process(stream + ((uInt32)position * channels),
(uInt32)samples + (uInt32)(position + samples) -
((uInt32)position + (uInt32)samples));
position += samples;
remaining -= samples;
}
myTIASound.set(info.addr, info.value);
myRegWriteQueue.dequeue();
}
else
{
myTIASound.process(stream + ((uInt32)position * channels),
length - (uInt32)position);
info.delta -= duration;
break;
}
}
}
if (mySoundExporter.get() != NULL && myNumRecordSamplesNeeded > 0) {
mySoundExporter->addSamples(stream, length);
myNumRecordSamplesNeeded -= length;
}
}
void SoundSDL::recordNextFrame() {
if (mySoundExporter.get() != NULL)
myNumRecordSamplesNeeded += ale::sound::SoundExporter::SamplesPerFrame;
}
void SoundSDL::callback(void* udata, uInt8* stream, int len)
{
SoundSDL* sound = (SoundSDL*)udata;
sound->processFragment(stream, (Int32)len);
#ifdef ATARIVOX_SUPPORT
Logger::Info << "SoundSDL::callback(): len==" << len << endl;
AtariVox *vox = sound->myOSystem->console().atariVox();
if(vox)
{
uInt8 *s = stream;
for(int i=0; i<len/OUTPUT_BUFFER_SIZE; i++)
{
int count;
uInt8 *voxSamples = vox->getSpeakJet()->getSamples(&count);
if(!count)
break;
SDL_MixAudio(s, voxSamples, OUTPUT_BUFFER_SIZE, SDL_MIX_MAXVOLUME);
s += OUTPUT_BUFFER_SIZE;
}
}
#endif
}
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.getInt();
reg2 = (uInt8) in.getInt();
reg3 = (uInt8) in.getInt();
reg4 = (uInt8) in.getInt();
reg5 = (uInt8) in.getInt();
reg6 = (uInt8) in.getInt();
myLastRegisterSetCycle = (Int32) in.getInt();
if(myIsInitializedFlag)
{
SDL_PauseAudio(1);
myRegWriteQueue.clear();
myTIASound.set(0x15, reg1);
myTIASound.set(0x16, reg2);
myTIASound.set(0x17, reg3);
myTIASound.set(0x18, reg4);
myTIASound.set(0x19, reg5);
myTIASound.set(0x1a, reg6);
SDL_PauseAudio(0);
}
}
catch(char *msg)
{
Logger::Error << msg << endl;
return false;
}
catch(...)
{
Logger::Error << "Unknown error in load state for " << device << endl;
return false;
}
return true;
}
bool SoundSDL::save(Serializer& out)
{
string device = "TIASound";
try
{
out.putString(device);
uInt8 reg1 = 0, reg2 = 0, reg3 = 0, reg4 = 0, reg5 = 0, reg6 = 0;
if(myIsInitializedFlag)
{
reg1 = myTIASound.get(0x15);
reg2 = myTIASound.get(0x16);
reg3 = myTIASound.get(0x17);
reg4 = myTIASound.get(0x18);
reg5 = myTIASound.get(0x19);
reg6 = myTIASound.get(0x1a);
}
out.putInt(reg1);
out.putInt(reg2);
out.putInt(reg3);
out.putInt(reg4);
out.putInt(reg5);
out.putInt(reg6);
out.putInt(myLastRegisterSetCycle);
}
catch(char *msg)
{
Logger::Error << msg << endl;
return false;
}
catch(...)
{
Logger::Error << "Unknown error in save state for " << device << endl;
return false;
}
return true;
}
SoundSDL::RegWriteQueue::RegWriteQueue(uInt32 capacity)
: myCapacity(capacity),
myBuffer(0),
mySize(0),
myHead(0),
myTail(0)
{
myBuffer = new RegWrite[myCapacity];
}
SoundSDL::RegWriteQueue::~RegWriteQueue()
{
delete[] myBuffer;
}
void SoundSDL::RegWriteQueue::clear()
{
myHead = myTail = mySize = 0;
}
void SoundSDL::RegWriteQueue::dequeue()
{
if(mySize > 0)
{
myHead = (myHead + 1) % myCapacity;
--mySize;
}
}
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(mySize == myCapacity)
{
grow();
}
myBuffer[myTail] = info;
myTail = (myTail + 1) % myCapacity;
++mySize;
}
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;
}
#endif