diff --git a/stepmania/src/arch/Sound/RageSoundDriver_QT.cpp b/stepmania/src/arch/Sound/RageSoundDriver_QT.cpp index 5078ffa83f..cdaad9c392 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_QT.cpp +++ b/stepmania/src/arch/Sound/RageSoundDriver_QT.cpp @@ -19,184 +19,410 @@ 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 initialQTSoundChannels = 8; +const unsigned SndCommand_qLength = 2; + +static ComponentInstance soundClock; +static bool useMultipleChannels; -/* 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; + volatile bool stopping; + volatile int last_pos; + volatile int flush_pos; + volatile Uint64 start_time; + volatile Uint32 fill_me; + SndChannelPtr channel; + CmpSoundHeader header; + Uint8 *buffer[2]; /* Double Buffer */ + channelInfo() { snd=NULL; channel=NULL; } + void reset() { stopping=false; flush_pos=0; last_pos=0; start_time=0; fill_me=0; } +}; + +static dequefree_channels; +static vectorplaying_channels; RageSound_QT::RageSound_QT() { - 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; + useMultipleChannels = canPlayMultiChannels(); + LOG->Trace("Can we use multiple channels? %s", (useMultipleChannels ? "yes" : "no")); + soundOutput = OpenDefaultComponent(kSoundOutputDeviceType, NULL); + ASSERT(soundOutput != NULL); + TimeRecord record; + ComponentInstance soundOutput; + SoundComponentGetInfo(soundOutput, NULL, siOutputLatency, &record); + latency = record.value.lo / record.scale; + if (!useMultipleChannels) + latency += samples / freq; /* double buffer */ - buffer[0] = new Uint8[buffersize]; - buffer[1] = new Uint8[buffersize]; - memset(buffer[0], 0, buffersize); - memset(buffer[1], 0, buffersize); + SndCallBackUPP callback = NewSndCallBackUPP(GetData); - channel = new SndChannel; - channel->userInfo = reinterpret_cast(this); - channel->qLength = 2; + soundClock = NULL; + SndChannelPtr chan = NULL; + SndCommand cmd; + OSErr err; + Uint8 *zero = NULL; + Uint8 *one = NULL; - soundOutput = OpenDefaultComponent(kSoundOutputDeviceType, NULL); - ASSERT(soundOutput != NULL); + for (unsigned i=0; iheader, 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; - OSErr err = SndNewChannel(&channel, sampledSynth, initStereo, callback); + if (useMultipleChannels || zero == NULL) { + zero = new Uint8[buffersize]; + one = new Uint8[buffersize]; + memset(zero, 0, buffersize); + memset(one, 0, buffersize); + } + info->buffer[0] = zero; + info->buffer[1] = one; - if (err != noErr) { - delete channel; - channel = NULL; - RageException::ThrowNonfatal("Unable to create audio channel"); - } + if (useMultipleChannels || chan == NULL) { + chan = new SndChannel; + chan->userInfo = reinterpret_cast(info); + chan->qLength = SndCommand_qLength; + err = SndNewChannel(&chan, sampledSynth, initStereo, callback); + if (err != noErr) { + SAFE_DELETE(info->channel); + RageException::ThrowNonfatal("Unable to create audio channel. THIS IS A BUG!"); + } + } + info->channel = chan; - SndCommand cmd; - cmd.cmd = clockComponentCmd; - cmd.param1 = true; - cmd.param2 = 0; - err |= SndDoImmediate(channel, &cmd); + if (!soundClock) { + cmd.cmd = clockComponentCmd; + cmd.param1 = true; + cmd.param2 = 0; + 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.cmd = getClockComponentCmd; + cmd.param1 = 0; + cmd.param2 = reinterpret_cast(&soundClock); + err |= SndDoImmediate(info->channel, &cmd); + } - if (err != noErr) - RageException::ThrowNonfatal("Unable to create audio channel"); + if (err != noErr) + RageException::ThrowNonfatal("Unable to create audio channel. THIS IS A BUG!"); + free_channels.push_back(info); + } + + ASSERT(free_channels.size() == initialQTSoundChannels); + playing_channels.reserve(initialQTSoundChannels); + if (!useMultipleChannels) { /* start the callbacks */ + cmd.cmd = callBackCmd; + cmd.param1 = 0; + cmd.param2 = 0; + err |= SndDoCommand(chan, &cmd, false); + } } RageSound_QT::~RageSound_QT() { - if (channel) - SndDisposeChannel(channel, true); - SAFE_DELETE_ARRAY(buffer[0]); - SAFE_DELETE_ARRAY(buffer[1]); - if (soundOutput) - CloseComponent(soundOutput); + LockMutex L(SOUNDMAN->lock); + int size = playing_channels.size(); + + for (int i=0; ichannel) + SndDisposeChannel(info->channel, true); + SAFE_DELETE_ARRAY(info->buffer[0]); + SAFE_DELETE_ARRAY(info->buffer[1]); + } + + size = free_channels.size(); + for (int i=0; ichannel) + SndDisposeChannel(info->channel, true); + SAFE_DELETE_ARRAY(info->buffer[0]); + SAFE_DELETE_ARRAY(info->buffer[1]); + if (!useMultipleChannels) + break; + } + + if (soundOutput) + CloseComponent(soundOutput); + if (soundClock) + CloseComponent(soundClock); } -void RageSound_QT::GetData(SndChannel *chan, SndCommand *cmd_passed) { - while (!SOUNDMAN) - SDL_Delay(10); - LockMutex L(SOUNDMAN->lock); - static SoundMixBuffer mix; - RageSound_QT *P = reinterpret_cast(chan->userInfo); +void RageSound_QT::GetData(SndChannelPtr chan, SndCommand *cmd_passed) { + LockMutex L(SOUNDMAN->lock); + register OSErr err = noErr; + 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; + channelInfo *info = reinterpret_cast(chan->userInfo); + ASSERT(info); + LOG->Trace("GetData() sound 0x%X", reinterpret_cast(info->snd)); + SndCommand cmd; + register UInt32 play_me; - fill_me = cmd_passed->param2; - UInt32 play_me = !fill_me; + if (cmd_passed != NULL) { + info->fill_me = cmd_passed->param2; + play_me = !info->fill_me; + info->header.samplePtr = reinterpret_cast(info->buffer[play_me]); + cmd.cmd = bufferCmd; + cmd.param1 = 0; + cmd.param2 = reinterpret_cast(&info->header); + LOG->Trace("Sending bufferCmd sound 0x%X", reinterpret_cast(info->snd)); + err = SndDoCommand(chan, &cmd, false); + if (err != noErr) + goto bail; + } - 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; + if (info->stopping){ + LOG->Trace("Stopping"); + return; /* We're done */ + } - /* 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(info->buffer[info->fill_me], 0, buffersize); - /* Clear the fill buffer */ - memset(buffer[fill_me], 0, buffersize); - - for (unsigned i=0; isounds.size(); ++i) { - if (P->sounds[i]->stopping) - continue; + if (useMultipleChannels) { + 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; + } - 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 / samplesize / 8); - } - } + LOG->Trace("About to GetPCM() sound 0x%X", reinterpret_cast(info->snd)); + register int got = info->snd->GetPCM(reinterpret_cast(info->buffer[info->fill_me]), buffersize, info->last_pos); + LOG->Trace("Got %D bytes", got); + if (static_cast(got) < buffersize) { + info->stopping = true; + info->flush_pos = info->last_pos + (got * 8 / samplesize); /* (got / (samplesize / 8)) */ + } + info->last_pos += samples; + } else { + static SoundMixBuffer mix; + register int size = playing_channels.size(); + for (int i=0; ilast_pos){ + TimeRecord tr; + ClockGetTime(soundClock, &tr); + info2->start_time = tr.value.hi; + info2->start_time <<= 32; + info2->start_time |= tr.value.lo; + } - mix.read(reinterpret_cast(buffer[fill_me])); + LOG->Trace("About to GetPCM() sound 0x%X", reinterpret_cast(info2->snd)); + register int got = info2->snd->GetPCM(reinterpret_cast(info->buffer[info->fill_me]), buffersize, info2->last_pos); + LOG->Trace("Got %D bytes", got); + if (static_cast(got) < buffersize) { + info2->stopping = true; + info2->flush_pos = info2->last_pos + (got * 8 / samplesize); + } + mix.write(reinterpret_cast(info->buffer[info->fill_me]), got / 2); + info2->last_pos += samples; + } + mix.read(reinterpret_cast(info->buffer[info->fill_me])); + } - cmd.cmd = callBackCmd; - cmd.param2 = play_me; - SndDoCommand(chan, &cmd, 0); + if (cmd_passed != NULL) { + cmd.cmd = callBackCmd; + cmd.param2 = play_me; + LOG->Trace("Sending callBackCmd sound 0x%X", reinterpret_cast(info->snd)); + err = SndDoCommand(chan, &cmd, false); + if (err != noErr) + goto bail; + } + gettingData=false; + 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); + LOG->Trace("StartMixing(). Mixing sound 0x%X", reinterpret_cast(snd)); + SndCommand cmd; + channelInfo *info; - LockMutex L(SOUNDMAN->lock); - sounds.push_back(s); + if (free_channels.size()) { + info = free_channels.front(); + free_channels.pop_front(); + } 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); + + if (useMultipleChannels) { + info->channel = new SndChannel; + info->channel->userInfo = reinterpret_cast(info); + info->channel->qLength = SndCommand_qLength; + register 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); + if (useMultipleChannels) { + 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"); - return; - } + 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); + playing_channels.erase(playing_channels.begin()+i); + if (useMultipleChannels) { + 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_back(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) - continue; - if (GetPosition(snds[i]->snd) < sounds[i]->flush_pos) - continue; - snds[i]->snd->StopPlaying(); - } + vector playing = playing_channels; + int size = playing.size(); + for (int i=0; istopping != 0) + continue; + if (GetPosition(info->snd) < info->flush_pos) + continue; + 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 */ + register int size = playing_channels.size(); + register 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; + return latency; } - \ No newline at end of file + +/* 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 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 0ac967eb66..8753946574 100644 --- a/stepmania/src/arch/Sound/RageSoundDriver_QT.h +++ b/stepmania/src/arch/Sound/RageSoundDriver_QT.h @@ -8,7 +8,7 @@ * Copyright (c) 2003 Steve Checkoway. All rights reserved. * */ - +#include "global.h" /* ugh, what a hack! QT includes Carbon/Carbon.h which * has defs for a few things defined in RageUtil.h which * is included in arch.cpp along w/ (eventually) this. @@ -18,23 +18,13 @@ namespace QT { #include } + #include "RageSound.h" #include "RageSoundDriver.h" 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; protected: @@ -50,5 +40,7 @@ public: static void GetData(QT::SndChannel *chan, QT::SndCommand *cmd_passed); }; +bool canPlayMultiChannels(); + #endif /* RAGE_SOUND_QT */ \ No newline at end of file