diff --git a/stepmania/src/arch/Sound/RageSoundDriver_QT.cpp b/stepmania/src/arch/Sound/RageSoundDriver_QT.cpp index 8db113395f..6e4dc02fe6 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_QT.cpp +++ b/stepmania/src/arch/Sound/RageSoundDriver_QT.cpp @@ -13,42 +13,50 @@ #include "RageUtil.h" #include "RageLog.h" #include -#include +#include +#include /* Ugh */ using namespace QT; const unsigned channels = 2; const unsigned samplesize = channels*16; -const unsigned samples = 512; +const unsigned samples = 4096; const unsigned freq = 44100; const unsigned buffersize = samples*samplesize/8; +const unsigned SndCommand_qLength = 2; +const unsigned initialQTSoundChannels = 8; -/* Oh boy! dealing with memory at inturupt time. What fun! */ #pragma options align=power -static volatile Uint32 fill_me = 0; -static UInt8 *buffer[2]; -static CmpSoundHeader header; +struct channelInfo { + RageSound *snd; + bool stopping; + int flush_pos; + int last_pos; + int got; + Uint64 start_time; + volatile Uint32 fill_me; + SndChannelPtr channel; + CmpSoundHeader header; + Uint8 *buffer[2]; + channelInfo() { snd=NULL; channel=NULL; } + void reset() { stopping=false; flush_pos=0; last_pos=0; start_time=0; fill_me=0; } +}; +static queuefree_channels; +static vectorplaying_channels; +static ComponentInstance soundClock; + RageSound_QT::RageSound_QT() { +#if 1 RageException::ThrowNonfatal("Class not finished!"); - - /*SndCallBackUPP callback; - callback = NewSndCallBackUPP(GetData); - memset(&header, 0, sizeof(header)); - header.numChannels = channels; - header.sampleSize = samplesize / channels; - header.sampleRate = rate44khz; /* really 44.1kHz * / - header.numFrames = samples; - header.encode = cmpSH; +#endif - buffer[0] = new Uint8[buffersize]; - buffer[1] = new Uint8[buffersize]; - memset(buffer[0], 0, buffersize); - memset(buffer[1], 0, buffersize); + if (!CanPlayMultiChannels()) + RageException::ThrowNonfatal("Multiple Sound Manager channels not supported. Fallback"); - channel = new SndChannel; - channel->userInfo = reinterpret_cast(this); - channel->qLength = 2; + channelInfo *info; + SndCallBackUPP callback = NewSndCallBackUPP(GetData); + OSErr err; soundOutput = OpenDefaultComponent(kSoundOutputDeviceType, NULL); ASSERT(soundOutput != NULL); @@ -56,160 +64,287 @@ RageSound_QT::RageSound_QT() { TimeRecord record; SoundComponentGetInfo(soundOutput, NULL, siOutputLatency, &record); latency = record.value.lo / record.scale; - latency += samples / freq; /* double buffer * / - OSErr err = SndNewChannel(&channel, sampledSynth, initStereo, callback); + playing_channels.reserve(initialQTSoundChannels); - if (err != noErr) { - delete channel; - channel = NULL; - RageException::ThrowNonfatal("Unable to create audio channel"); + for (unsigned i=0; ibuffer[0] = new Uint8[buffersize]; + info->buffer[1] = new Uint8[buffersize]; + info->channel = new SndChannel; + info->channel->userInfo = reinterpret_cast(this); + info->channel->qLength = SndCommand_qLength; + info->header.numChannels = channels; + info->header.sampleSize = samplesize / channels; + info->header.sampleRate = rate44khz; + info->header.encode = cmpSH; + + err = SndNewChannel(&info->channel, sampledSynth, initStereo, callback); + if (err != noErr) { + SAFE_DELETE(info->channel); + RageException::ThrowNonfatal("Unable to create audio channel. THIS IS A BUG!"); + } + free_channels.push(info); } SndCommand cmd; cmd.cmd = clockComponentCmd; cmd.param1 = true; cmd.param2 = 0; - err |= SndDoImmediate(channel, &cmd); + err = SndDoImmediate(info->channel, &cmd); cmd.cmd = getClockComponentCmd; cmd.param1 = 0; - cmd.param2 = reinterpret_cast(&clock); - err |= SndDoImmediate(channel, &cmd); - last_pos = 0; - - cmd.cmd = callBackCmd; - cmd.param2 = 0; - err |= SndDoCommand(channel, &cmd, false); + cmd.param2 = reinterpret_cast(&soundClock); + err |= SndDoImmediate(info->channel, &cmd); if (err != noErr) - RageException::ThrowNonfatal("Unable to create audio channel");*/ + RageException::ThrowNonfatal("Unable to create audio channel. THIS IS A BUG!"); } RageSound_QT::~RageSound_QT() { - /*if (channel) - SndDisposeChannel(channel, true); - SAFE_DELETE_ARRAY(buffer[0]); - SAFE_DELETE_ARRAY(buffer[1]); - if (soundOutput) - CloseComponent(soundOutput);*/ -} + unsigned size = playing_channels.size(); -void RageSound_QT::GetData(SndChannel *chan, SndCommand *cmd_passed) { - /*while (!SOUNDMAN) - SDL_Delay(10); - LockMutex L(SOUNDMAN->lock); - static bool gettingData=false; - static int recursiveCalls = 0; - if (gettingData) { - LOG->Warn( "GetData() called recursively %D times.", ++recursiveCalls); - if (recursiveCalls >=10) - RageException::Throw("GetData() called recursively too many times."); - } else - recursiveCalls = 0; - gettingData = true; - static SoundMixBuffer mix; - RageSound_QT *P = reinterpret_cast(chan->userInfo); - - fill_me = cmd_passed->param2; - UInt32 play_me = !fill_me; - - if (!P->last_pos){ - TimeRecord tr; - ClockGetTime(P->clock, &tr); - UInt64 temp = tr.value.hi; - temp <<= 32; - temp |= tr.value.lo; - double d = static_cast(temp)/tr.scale*freq; - temp = static_cast(d); - P->last_pos = static_cast(temp % 0x00000000FFFFFFFFLL); - } else - P->last_pos += samples; - - /* Swap buffers * / - header.samplePtr = reinterpret_cast(buffer[play_me]); - SndCommand cmd; - cmd.cmd = bufferCmd; - cmd.param1 = 0; - cmd.param2 = reinterpret_cast(&header); - SndDoCommand(chan, &cmd, 0); - - /* Clear the fill buffer * / - memset(buffer[fill_me], 0, buffersize); - - for (unsigned i=0; isounds.size(); ++i) { - if (P->sounds[i]->stopping) - continue; - - unsigned got = P->sounds[i]->snd->GetPCM(reinterpret_cast(buffer[fill_me]), buffersize, P->last_pos); - mix.write(reinterpret_cast(buffer[fill_me]), got / 2); - if (got < buffersize) { - P->sounds[i]->stopping = true; - P->sounds[i]->flush_pos = P->last_pos + (got * 8 / samplesize); - } + for (unsigned i=0; ichannel) + SndDisposeChannel(info->channel, true); + SAFE_DELETE_ARRAY(info->buffer[0]); + SAFE_DELETE_ARRAY(info->buffer[1]); + playing_channels.pop_back(); } - mix.read(reinterpret_cast(buffer[fill_me])); + while (!free_channels.empty()) { + channelInfo *info = free_channels.front(); - cmd.cmd = callBackCmd; - cmd.param2 = play_me; - SndDoCommand(chan, &cmd, 0); - gettingData=false;*/ + if (info->channel) + SndDisposeChannel(info->channel, true); + SAFE_DELETE_ARRAY(info->buffer[0]); + SAFE_DELETE_ARRAY(info->buffer[1]); + free_channels.pop(); + } + + if (soundOutput) + CloseComponent(soundOutput); + if (soundClock) + CloseComponent(soundClock); +} + +void RageSound_QT::GetData(SndChannelPtr chan, SndCommand *cmd_passed) { + LockMutex L(SOUNDMAN->lock); + + channelInfo *info = reinterpret_cast(chan->userInfo); + ASSERT(info); + ASSERT(chan == info->channel); + SndCommand cmd; + Uint32 play_me; + OSErr err; + int got; + + if (cmd_passed) { + info->fill_me = cmd_passed->param2; + play_me = !info->fill_me; + info->header.samplePtr = reinterpret_cast(info->buffer[play_me]); + info->header.numFrames = info->got; + cmd.cmd = bufferCmd; + cmd.param1 = 0; + cmd.param2 = reinterpret_cast(&info->header); + err = SndDoCommand(chan, &cmd, false); + if (err != noErr) + goto bail; + } + + if (info->stopping) + return; + + memset(info->buffer[info->fill_me], 0, buffersize); + if (!info->last_pos) { + TimeRecord tr; + ClockGetTime(soundClock, &tr); + info->start_time = tr.value.hi; + info->start_time <<= 32; + info->start_time |= tr.value.lo; + } + + got = info->snd->GetPCM(reinterpret_cast(info->buffer[info->fill_me]), buffersize, info->last_pos); + if (static_cast(got) < buffersize) { + info->stopping = true; + info->flush_pos = info->last_pos + (got * 8 / samplesize); + } + info->got = got * 8 / samplesize; /* got / (samplesize / 8) */ + info->last_pos += samples; + + if (cmd_passed) { + cmd.cmd = callBackCmd; + cmd.param2 = play_me; + err = SndDoCommand(chan, &cmd, false); + if (err != noErr) + goto bail; + } + return; + +bail: + RageException::Throw("SndDoCommand failed with error %d", err); } void RageSound_QT::StartMixing(RageSound *snd) { - /*sound *s = new sound; - s->snd = snd; - LockMutex L(SOUNDMAN->lock); - sounds.push_back(s); - LOG->Trace("There are %D sounds playing", sounds.size());*/ + SndCommand cmd; + channelInfo *info; + + if (free_channels.size()) { + info = free_channels.front(); + free_channels.pop(); + } else { /* No free audio channels, create another */ + LOG->Warn("Out of audio channels, creating channel number %D", playing_channels.size()+1); + info = new channelInfo; + + memset(&info->header, 0, sizeof(&info->header)); + info->header.numChannels = channels; + info->header.sampleSize = samplesize / channels; + info->header.sampleRate = rate44khz; /* really 44.1kHz */ + info->header.numFrames = samples; + info->header.encode = cmpSH; + + info->buffer[0] = new Uint8[buffersize]; + info->buffer[1] = new Uint8[buffersize]; + memset(info->buffer[0], 0, buffersize); + memset(info->buffer[1], 0, buffersize); + + info->channel = new SndChannel; + info->channel->userInfo = reinterpret_cast(info); + info->channel->qLength = SndCommand_qLength; + OSErr err = SndNewChannel(&(info->channel), sampledSynth, initStereo, NewSndCallBackUPP(GetData)); + + if (err != noErr) { + SAFE_DELETE(info->channel); + RageException::Throw("Unable to create audio channel. THIS IS A BUG."); + } + } + + info->reset(); + info->snd = snd; + /* Fill the first buffer with data */ + playing_channels.push_back(info); + GetData(info->channel, NULL); + cmd.cmd = callBackCmd; + cmd.param1 = 0; + cmd.param2 = !info->fill_me; + SndDoCommand(info->channel, &cmd, 0); /* command queue is empty */ } void RageSound_QT::StopMixing(RageSound *snd) { - /*LockMutex L(SOUNDMAN->lock); + LockMutex L(SOUNDMAN->lock); + LOG->Trace("StopMixing(). Stopping sound 0x%X", reinterpret_cast(snd)); + /* Find the sound. */ + int size = playing_channels.size(); + int i; + channelInfo *info; - /* Find the sound. * / - unsigned i; - for(i = 0; i < sounds.size(); ++i) - if(sounds[i]->snd == snd) break; - if(i == sounds.size()) - { - LOG->Trace("not stopping a sound because it's not playing"); + for (i=0; i<=size; ++i) + if ((info=playing_channels[i])->snd == snd) + break; + if (i==size) { + LOG->Trace("Not stopping a sound because it's not playing."); return; } - delete sounds[i]; - sounds.erase(sounds.begin()+i, sounds.begin()+i+1); - LOG->Trace("There are %D sounds playing", sounds.size());*/ + playing_channels.erase(playing_channels.begin()+i); + SndCommand cmd; + cmd.cmd = flushCmd; + cmd.param1 = 0; + cmd.param2 = 0; + LOG->Trace("Flushing channel with song %X", reinterpret_cast(snd)); + SndDoImmediate(info->channel, &cmd); + cmd.cmd = quietCmd; + SndDoImmediate(info->channel, &cmd); + free_channels.push(info); } void RageSound_QT::Update(float delta) { #pragma unused (delta) - /*LockMutex L(SOUNDMAN->lock); + LockMutex L(SOUNDMAN->lock); - vectorsnds = sounds; - for (unsigned i = 0; i < snds.size(); ++i) { - if (!sounds[i]->stopping) + vector playing = playing_channels; + int size = playing.size(); + for (int i=0; istopping != 0) continue; - if (GetPosition(snds[i]->snd) < sounds[i]->flush_pos) + if (GetPosition(info->snd) < info->flush_pos) continue; - snds[i]->snd->StopPlaying(); - }*/ + info->snd->StopPlaying(); + } } int RageSound_QT::GetPosition(const RageSound *snd) const { -#pragma unused (snd) - /*TimeRecord tr; - ClockGetTime(clock, &tr); - UInt64 temp = tr.value.hi; - temp <<= 32; - temp |= tr.value.lo; - double d = static_cast(temp)/tr.scale*freq; - temp = static_cast(d); - return static_cast(temp % 0x00000000FFFFFFFFLL);*/ + LockMutex L(SOUNDMAN->lock); + TimeRecord tr; + ClockGetTime(soundClock, &tr); + UInt64 time = tr.value.hi; + time <<= 32; + time |= tr.value.lo; + + /* Find the info */ + int size = playing_channels.size(); + int i; + channelInfo *info; + for (i=0; isnd == snd) + break; + if (i == size) + RageException::Throw("Can't get the position of a sound that isn't playing"); + + LOG->Trace("GetPosition() sound 0x%X", reinterpret_cast(snd)); + + time -= info->start_time; /* Now time contains the total time the sound was played. */ + time = time * freq / tr.scale; /* Now time contains the number of samples played. */ + return static_cast(time % 0x00000000FFFFFFFFLL); } -float RageSound_QT::GetPlayLatency() const { - //return latency; +/* Pascal code from SoundManager docs. + +FUNCTION MyCanPlayMultiChannels: Boolean; +VAR +myResponse: LongInt; +myResult: Boolean; +myErr: OSErr; +myVersion: NumVersion; +BEGIN +myResult := FALSE; +myVersion := SndSoundManagerVersion; +myErr := Gestalt(gestaltSoundAttr, myResponse); +IF myVersion.majorRev >= 3 THEN +IF (myErr = noErr) AND (BTst(myResponse, gestaltMultiChannels)) THEN +myResult := TRUE +ELSE +BEGIN +myErr := Gestalt(gestaltHardwareAttr, myResponse); +IF (myErr = noErr) AND (BTst(myResponse, gestaltHasASC)) THEN +myResult := TRUE +END; +MyCanPlayMultiChannels := myResult; +END; +*/ + +bool RageSound_QT::CanPlayMultiChannels(){ + register long response; + register OSErr err; + NumVersion version; + register bool result; + + result = false; + version = SndSoundManagerVersion(); + err = Gestalt(gestaltSoundAttr, &response); + if (version.majorRev >= 3){ + if((err == noErr) && (response & gestaltMultiChannels == gestaltMultiChannels)) + result = true; + } else { + err = Gestalt(gestaltHardwareAttr, &response); + if ((err == noErr) && (response & gestaltHasASC == gestaltHasASC)) + result = true; + } + return result; } diff --git a/stepmania/src/arch/Sound/RageSoundDriver_QT.h b/stepmania/src/arch/Sound/RageSoundDriver_QT.h index ec5dbfbd29..45195883cc 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_QT.h +++ b/stepmania/src/arch/Sound/RageSoundDriver_QT.h @@ -25,26 +25,17 @@ namespace QT { class RageSound_QT: public RageSoundDriver { private: - struct sound { - RageSound *snd; - bool stopping; - int flush_pos; - sound() { snd=NULL; stopping=false; flush_pos=0; } - }; - - vector sounds; - QT::ComponentInstance clock; QT::ComponentInstance soundOutput; - QT::SndChannelPtr channel; - int last_pos; float latency; + bool CanPlayMultiChannels(); + protected: virtual void StartMixing(RageSound *snd); virtual void StopMixing(RageSound *snd); virtual int GetPosition(const RageSound *snd) const; virtual void Update (float delta); - virtual float GetPlayLatency() const; + virtual float GetPlayLatency() const { return latency; } public: RageSound_QT();