In progress. Added header
This commit is contained in:
@@ -9,346 +9,194 @@
|
|||||||
|
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
#include "RageSoundDriver_QT.h"
|
#include "RageSoundDriver_QT.h"
|
||||||
|
#include "RageSoundManager.h"
|
||||||
|
#include "RageSound.h"
|
||||||
#include "RageLog.h"
|
#include "RageLog.h"
|
||||||
#include "RageUtil.h"
|
#include "RageThreads.h"
|
||||||
#include "RageLog.h"
|
|
||||||
#include <QuickTime/QuickTime.h>
|
#include <QuickTime/QuickTime.h>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
/* Ugh */
|
#include <pthread.h>
|
||||||
using namespace QT;
|
#include <sys/types.h>
|
||||||
|
|
||||||
const unsigned channels = 2;
|
const unsigned samplesize = 4;
|
||||||
const unsigned samplesize = channels*16;
|
|
||||||
const unsigned samples = 4096;
|
const unsigned samples = 4096;
|
||||||
const unsigned freq = 44100;
|
const unsigned freq = 44100;
|
||||||
const unsigned buffersize = samples*samplesize/8;
|
const unsigned buffersize = samples * samplesize;
|
||||||
const unsigned SndCommand_qLength = 2;
|
|
||||||
const unsigned initialQTSoundChannels = 1;
|
|
||||||
|
|
||||||
#pragma options align=power
|
typedef struct soundInfo{
|
||||||
struct channelInfo {
|
RageSound *snd;
|
||||||
RageSound *snd;
|
Movie movie[2];
|
||||||
bool stopping;
|
long fillme;
|
||||||
int flush_pos;
|
long stopPos;
|
||||||
int last_pos;
|
long lastPos;
|
||||||
int got;
|
long finishedBuffers;
|
||||||
Uint64 start_time;
|
} *soundInfoPtr, **soundInfoHdl;
|
||||||
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 queue<channelInfo *>free_channels;
|
|
||||||
static vector<channelInfo *>playing_channels;
|
|
||||||
static ComponentInstance soundClock;
|
|
||||||
|
|
||||||
|
static void CallBack(QTCallBack cb, long refCon);
|
||||||
|
static inline void GetData(soundInfoPtr s);
|
||||||
|
|
||||||
|
static vector<soundInfoHdl> sounds;
|
||||||
|
static MovieImportComponent miComponent;
|
||||||
|
|
||||||
RageSound_QT::RageSound_QT() {
|
RageSound_QT::RageSound_QT() {
|
||||||
#if 1
|
#if 1
|
||||||
RageException::ThrowNonfatal("Class not finished!");
|
RageException::ThrowNonfatal("Class not finished!");
|
||||||
#endif
|
#endif
|
||||||
|
if (EnterMovies() != noErr)
|
||||||
if (!CanPlayMultiChannels())
|
RageException::ThrowNonfatal("Could not start movies. Falling back.");
|
||||||
RageException::ThrowNonfatal("Multiple Sound Manager channels not supported. Fallback");
|
miComponent = OpenDefaultComponent(MovieImportType, kQTFileTypeWave);
|
||||||
|
|
||||||
channelInfo *info;
|
|
||||||
SndCallBackUPP callback = NewSndCallBackUPP(GetData);
|
|
||||||
OSErr err;
|
|
||||||
|
|
||||||
soundOutput = OpenDefaultComponent(kSoundOutputDeviceType, NULL);
|
|
||||||
ASSERT(soundOutput != NULL);
|
|
||||||
|
|
||||||
TimeRecord record;
|
|
||||||
SoundComponentGetInfo(soundOutput, NULL, siOutputLatency, &record);
|
|
||||||
latency = record.value.lo / record.scale;
|
|
||||||
|
|
||||||
playing_channels.reserve(initialQTSoundChannels);
|
|
||||||
|
|
||||||
for (unsigned i=0; i<initialQTSoundChannels; ++i) {
|
|
||||||
info = new channelInfo;
|
|
||||||
|
|
||||||
info->buffer[0] = new Uint8[buffersize];
|
|
||||||
info->buffer[1] = new Uint8[buffersize];
|
|
||||||
info->channel = new SndChannel;
|
|
||||||
info->channel->userInfo = reinterpret_cast<long>(info);
|
|
||||||
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(info->channel, &cmd);
|
|
||||||
|
|
||||||
cmd.cmd = getClockComponentCmd;
|
|
||||||
cmd.param1 = 0;
|
|
||||||
cmd.param2 = reinterpret_cast<long>(&soundClock);
|
|
||||||
err |= SndDoImmediate(info->channel, &cmd);
|
|
||||||
|
|
||||||
if (err != noErr)
|
|
||||||
RageException::ThrowNonfatal("Unable to create audio channel. THIS IS A BUG!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RageSound_QT::~RageSound_QT() {
|
RageSound_QT::~RageSound_QT() {
|
||||||
unsigned size = playing_channels.size();
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
while (!sounds.empty()) {
|
||||||
for (unsigned i=0; i<size; ++i) {
|
soundInfoHdl sHdl = sounds.back();
|
||||||
channelInfo *info = playing_channels.back();
|
soundInfoPtr s = *sHdl;
|
||||||
|
if (s->movie[0]) {
|
||||||
if (info->channel)
|
StopMovie(s->movie[0]);
|
||||||
SndDisposeChannel(info->channel, true);
|
DisposeMovie(s->movie[0]);
|
||||||
SAFE_DELETE_ARRAY(info->buffer[0]);
|
}
|
||||||
SAFE_DELETE_ARRAY(info->buffer[1]);
|
if (s->movie[1]) {
|
||||||
playing_channels.pop_back();
|
StopMovie(s->movie[1]);
|
||||||
}
|
DisposeMovie(s->movie[1]);
|
||||||
|
}
|
||||||
while (!free_channels.empty()) {
|
DisposeHandle(Handle(sHdl));
|
||||||
channelInfo *info = free_channels.front();
|
sounds.pop_back();
|
||||||
|
}
|
||||||
if (info->channel)
|
if (miComponent)
|
||||||
SndDisposeChannel(info->channel, true);
|
CloseComponent(miComponent);
|
||||||
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<channelInfo *>(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<Ptr>(info->buffer[play_me]);
|
|
||||||
info->header.numFrames = info->got;
|
|
||||||
cmd.cmd = bufferCmd;
|
|
||||||
cmd.param1 = 0;
|
|
||||||
cmd.param2 = reinterpret_cast<long>(&info->header);
|
|
||||||
LOG->Trace("bufferCmd");
|
|
||||||
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<char *>(info->buffer[info->fill_me]), buffersize, info->last_pos);
|
|
||||||
if (static_cast<unsigned>(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;
|
|
||||||
LOG->Trace("callBackCmd");
|
|
||||||
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) {
|
void RageSound_QT::StartMixing(RageSound *snd) {
|
||||||
LockMutex L(SOUNDMAN->lock);
|
LockMutex L(SOUNDMAN->lock);
|
||||||
SndCommand cmd;
|
soundInfoHdl sHdl = (soundInfoHdl)NewHandle(sizeof(soundInfo));
|
||||||
channelInfo *info;
|
HLockHi(Handle(sHdl)); /* Move to the top of the heap */
|
||||||
|
soundInfoPtr s = *sHdl;
|
||||||
|
|
||||||
if (free_channels.size()) {
|
s->fillme = 0;
|
||||||
info = free_channels.front();
|
s->movie[0] = NULL;
|
||||||
free_channels.pop();
|
s->movie[1] = NULL;
|
||||||
} else { /* No free audio channels, create another */
|
s->stopPos = 0;
|
||||||
LOG->Warn("Out of audio channels, creating channel number %D", playing_channels.size()+1);
|
s->lastPos = 0;
|
||||||
info = new channelInfo;
|
s->snd = snd;
|
||||||
|
s->finishedBuffers = -1;
|
||||||
memset(&info->header, 0, sizeof(&info->header));
|
sounds.push_back(sHdl);
|
||||||
info->header.numChannels = channels;
|
CallBack(NULL, long(s));
|
||||||
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<long>(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;
|
|
||||||
OSErr err = SndDoCommand(info->channel, &cmd, 0); /* command queue is empty */
|
|
||||||
if (err != noErr)
|
|
||||||
RageException::Throw("Couldn't start the callback");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RageSound_QT::StopMixing(RageSound *snd) {
|
void RageSound_QT::StopMixing(RageSound *snd) {
|
||||||
LockMutex L(SOUNDMAN->lock);
|
LockMutex L(SOUNDMAN->lock);
|
||||||
LOG->Trace("StopMixing(). Stopping sound 0x%X", reinterpret_cast<long>(snd));
|
unsigned size = sounds.size();
|
||||||
/* Find the sound. */
|
|
||||||
int size = playing_channels.size();
|
|
||||||
int i;
|
|
||||||
channelInfo *info;
|
|
||||||
|
|
||||||
for (i=0; i<=size; ++i)
|
for (unsigned i=0; i<size; ++i) {
|
||||||
if ((info=playing_channels[i])->snd == snd)
|
soundInfoHdl sHdl = sounds[i];
|
||||||
break;
|
soundInfoPtr s = *sHdl;
|
||||||
if (i==size) {
|
if (s->snd != snd)
|
||||||
LOG->Trace("Not stopping a sound because it's not playing.");
|
continue;
|
||||||
return;
|
if (s->movie[0]) {
|
||||||
}
|
StopMovie(s->movie[0]);
|
||||||
|
DisposeMovie(s->movie[0]);
|
||||||
playing_channels.erase(playing_channels.begin()+i);
|
}
|
||||||
SndCommand cmd;
|
if (s->movie[1]) {
|
||||||
cmd.cmd = flushCmd;
|
StopMovie(s->movie[1]);
|
||||||
cmd.param1 = 0;
|
DisposeMovie(s->movie[1]);
|
||||||
cmd.param2 = 0;
|
}
|
||||||
LOG->Trace("Flushing channel with song %X", reinterpret_cast<long>(snd));
|
DisposeHandle(Handle(sHdl));
|
||||||
SndDoImmediate(info->channel, &cmd);
|
}
|
||||||
cmd.cmd = quietCmd;
|
|
||||||
SndDoImmediate(info->channel, &cmd);
|
|
||||||
free_channels.push(info);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RageSound_QT::Update(float delta) {
|
void RageSound_QT::Update(float delta) {
|
||||||
#pragma unused (delta)
|
#pragma unused (delta)
|
||||||
LockMutex L(SOUNDMAN->lock);
|
LockMutex L(SOUNDMAN->lock);
|
||||||
|
unsigned size = sounds.size();
|
||||||
|
vector<soundInfoHdl> snds = sounds;
|
||||||
|
|
||||||
vector<channelInfo *> playing = playing_channels;
|
for (unsigned i=0; i<size; ++i) {
|
||||||
int size = playing.size();
|
soundInfoPtr s = *(snds[i]);
|
||||||
for (int i=0; i<size; ++i) {
|
if ((s->movie[0] && !IsMovieDone(s->movie[0])) ||
|
||||||
channelInfo *info = playing[i];
|
(s->movie[1] && !IsMovieDone(s->movie[1])))
|
||||||
if (info->stopping != 0)
|
continue;
|
||||||
continue;
|
s->snd->StopPlaying();
|
||||||
if (GetPosition(info->snd) < info->flush_pos)
|
}
|
||||||
continue;
|
|
||||||
info->snd->StopPlaying();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int RageSound_QT::GetPosition(const RageSound *snd) const {
|
int RageSound_QT::GetPosition(const RageSound *snd) const {
|
||||||
LockMutex L(SOUNDMAN->lock);
|
LockMutex L(SOUNDMAN->lock);
|
||||||
TimeRecord tr;
|
soundInfoPtr s;
|
||||||
ClockGetTime(soundClock, &tr);
|
unsigned size = sounds.size();
|
||||||
UInt64 time = tr.value.hi;
|
/* Find the info */
|
||||||
time <<= 32;
|
for (unsigned i=0; i<size; ++i) {
|
||||||
time |= tr.value.lo;
|
s = *(sounds[i]);
|
||||||
|
if (s->snd != snd)
|
||||||
/* Find the info */
|
continue;
|
||||||
int size = playing_channels.size();
|
if (s->finishedBuffers == -1)
|
||||||
int i;
|
return 0; /* It hasn't started yet. */
|
||||||
channelInfo *info;
|
return s->finishedBuffers * samples + GetMovieTime(s->movie[!s->fillme], NULL);
|
||||||
for (i=0; i<size; ++i)
|
}
|
||||||
if ((info = playing_channels[i])->snd == snd)
|
RageException::Throw("Can't get the position of a sound that isn't playing");
|
||||||
break;
|
return 0; /* unrechable statement */
|
||||||
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<long>(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<int>(time % 0x00000000FFFFFFFFLL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pascal code from SoundManager docs.
|
|
||||||
|
|
||||||
FUNCTION MyCanPlayMultiChannels: Boolean;
|
void CallBack(QTCallBack cb, long refCon) {
|
||||||
VAR
|
LockMutex L(SOUNDMAN->lock);
|
||||||
myResponse: LongInt;
|
soundInfoPtr s = soundInfoPtr(refCon);
|
||||||
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(){
|
s->finishedBuffers++;
|
||||||
register long response;
|
if (!cb)
|
||||||
register OSErr err;
|
GetData(s);
|
||||||
NumVersion version;
|
long playme = !s->fillme;
|
||||||
register bool result;
|
SetMovieVolume(s->movie[playme], kFullVolume);
|
||||||
|
GoToBeginningOfMovie(s->movie[playme]);
|
||||||
result = false;
|
StartMovie(s->movie[playme]);
|
||||||
version = SndSoundManagerVersion();
|
if (s->stopPos == 0) {
|
||||||
err = Gestalt(gestaltSoundAttr, &response);
|
/* Not stopping */
|
||||||
if (version.majorRev >= 3){
|
GetData(s);
|
||||||
if((err == noErr) && (response & gestaltMultiChannels == gestaltMultiChannels))
|
QTCallBack callBack = NewCallBack(GetMovieTimeBase(s->movie[playme]), callBackAtExtremes);
|
||||||
result = true;
|
CallMeWhen(callBack, CallBack, long(s), 0, 0, 0);
|
||||||
} else {
|
}
|
||||||
err = Gestalt(gestaltHardwareAttr, &response);
|
DisposeCallBack(cb);
|
||||||
if ((err == noErr) && (response & gestaltHasASC == gestaltHasASC))
|
|
||||||
result = true;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline void GetData(soundInfoPtr s) {
|
||||||
|
static long flags = newMovieActive;
|
||||||
|
Handle hdl, dataRef = NULL;
|
||||||
|
Track targetTrack = NULL;
|
||||||
|
TimeValue addedDuration = 0;
|
||||||
|
long outFlags = 0;
|
||||||
|
OSErr err;
|
||||||
|
ComponentResult result;
|
||||||
|
Movie &movie = s->movie[s->fillme];
|
||||||
|
char buffer[buffersize];
|
||||||
|
int got = s->snd->GetPCM(buffer, buffersize, s->lastPos);
|
||||||
|
|
||||||
|
if (got < buffersize) {
|
||||||
|
s->stopPos = got / samplesize + s->lastPos;
|
||||||
|
if (got == 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s->lastPos += samples;
|
||||||
|
|
||||||
|
/* I really don' understand this part. Oh well. */
|
||||||
|
hdl = NewHandle(got);
|
||||||
|
HLock(hdl);
|
||||||
|
BlockMove(buffer, *hdl, got);
|
||||||
|
err = PtrToHand(&hdl, &dataRef, sizeof(Handle)); /* um... */
|
||||||
|
ASSERT(err == noErr);
|
||||||
|
HUnlock(hdl);
|
||||||
|
/* end unknown section. */
|
||||||
|
|
||||||
|
if (movie)
|
||||||
|
DisposeMovie(movie);
|
||||||
|
|
||||||
|
movie = NewMovie(flags);
|
||||||
|
SetMovieTimeScale(movie, freq);
|
||||||
|
if (GetMoviesError() != noErr)
|
||||||
|
RageException::Throw("Couldn't create new movie");
|
||||||
|
result = MovieImportDataRef(miComponent, dataRef, HandleDataHandlerSubType,
|
||||||
|
movie, NULL, &targetTrack, NULL, &addedDuration,
|
||||||
|
movieImportCreateTrack, &outFlags);
|
||||||
|
s->fillme = !s->fillme;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user