From c9579fe6b53fbdf70900101ed8dadd35a3fc63a3 Mon Sep 17 00:00:00 2001 From: Steve Checkoway Date: Sat, 5 Jul 2003 07:38:03 +0000 Subject: [PATCH] Not quite working but I can work out a bug or two it _should_ play back the audio much better for computers that support multitple channels (I assume that they all can!) and it should play the same for those computers that don't support it. Right now I'm getting an error of -223 from the bufferCmd but I haven't figured out what the hell that actually is. If you want a working sound driver for OS X, just use the previous version of these files. --- .../src/arch/Sound/RageSoundDriver_QT.cpp | 492 +++++++++++++----- stepmania/src/arch/Sound/RageSoundDriver_QT.h | 16 +- 2 files changed, 363 insertions(+), 145 deletions(-) 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