7 // goddamn apple headers
10 #include <Carbon/Carbon.h>
13 #include "SoundMangler.h"
15 // allocate the SndChannel statically here rather than using NewPtr
16 static SndChannel mySndChannel
;
17 static SndChannelPtr mySndChannelPtr
= 0;
18 static SndCallBackUPP myUPP
;
19 // each SoundHeader has an attached BLOCKSIZE buffer. if a single
20 // sound block is larger than that, it will be divided without any
21 // promise of being played atomically...
22 enum { BLOCKSIZE
= 1024 };
23 enum { MAXHEADERS
= 8 };
24 // SoundHeader buffers are now used strictly round-robin fashion,
25 // and when a buffer is queued, we do not return until the buffer
26 // starts playing (approximately, anyway; what happens is that the
27 // caller passes in an MPSemaphoreID which is to be tickled when
28 // some previous buffer completes; previously this means the current
29 // buffer minus one...).
30 // A buffer is marked busy by writing 1 into the corresponding gSemaphore
31 // pointer, and marked free by writing 0. If you can change the semaphore
32 // pointer atomically from 1 to a nonzero value, the Sound Manager Fairy
33 // will awaken you when the corresponding sound completes, otherwise it's
34 // your problem (it's an error if the value is nonzero already). When
35 // marking a buffer free, if you can atomically turn 1 into 0, you're off
36 // the hook for signalling, otherwise it contains a semaphore pointer to ring.
37 static ExtSoundHeaderPtr mySoundHeaders
[MAXHEADERS
];
38 static MPSemaphoreID
*gSemaphores
[MAXHEADERS
];
39 static int gNextBuffer
, gNumBuffers
= 3;
40 static volatile UInt32 gHeaderMask
;
41 static short gNumChannels
;
42 static short gSampleSize
;
43 static Fixed gSampleRate
;
48 static Nanoseconds gStartTime
;
49 static Nanoseconds gMostRecentTime
;
51 static Nanoseconds gPlayStartTime
, gPlayMostRecentTime
;
55 static pascal void theCallbackFunction(SndChannelPtr chan
,
58 // negative param1 is special (means check gCallBackWhenDone)
59 if (cmd
->param1
>= 0) {
60 // Try to give the buffer back by changing the semaphore pointer
62 if (!OTCompareAndSwapPtr((void*)1, (void*)0,
63 (void **)&gSemaphores
[cmd
->param1
]))
65 (void)MPSignalSemaphore(*gSemaphores
[cmd
->param1
]);
66 gSemaphores
[cmd
->param1
] = (MPSemaphoreID
*)0;
71 gStartTime
= AbsoluteToNanoseconds(UpTime());
73 gMostRecentTime
= AbsoluteToNanoseconds(UpTime());
76 fprintf(stderr
,"200 buffers played\n");
78 } else if (cmd
->param1
== (short)-1) {
80 callbackThunk
*thunk
= (callbackThunk
*)cmd
->param2
;
81 thunk
->func((void *)thunk
);
82 // thunk you very much.
86 // Not much to do during the Initialize phase. I allocate the memory for
87 // the SndChannel statically, and we don't set up the parameters until
89 OSStatus
SoundManagerInitialize()
93 // first create the UPP for the callback function
94 if ((myUPP
= NewSndCallBackUPP(theCallbackFunction
)) == 0) {
99 for (int i
= 0; i
< MAXHEADERS
; i
++) {
101 if ((p
= (ExtSoundHeaderPtr
)NewPtrClear( sizeof(ExtSoundHeader
) - 1 + BLOCKSIZE
)) == 0) {
105 mySoundHeaders
[i
] = p
;
107 // channels, rate, and size get set at play time
109 p
->baseFrequency
= 60; // ??
110 // fill in numFrames at the appropriate time
111 // according to the CarbonSndPlayDB.c code snippet, AIFFSampleRate
112 // is unused. well, that's a logical place to document that fact!
118 OSStatus
SoundManagerOpenPlayer(const char *name
, const soundParams
*sp
,
119 unsigned long *handlep
)
121 OSStatus err
= noErr
;
122 if (mySndChannelPtr
!= 0)
125 mySndChannel
.qLength
= 128;
126 mySndChannel
.userInfo
= (long)0;
127 // Actually create the channel
128 // XXX To open a sound channel which is not the default device,
129 // XXX obtain the component id of the output device, then pass
130 // XXX kUseOptionalOutputDevice as the synth parameter and the
131 // XXX ComponentInstance as the init parameter; I guess you'd then
132 // XXX do a reInitCmd to set stereo or mono, and nothing works but
133 // XXX sampledSynth nowadays anyway...
134 mySndChannelPtr
= &mySndChannel
;
135 err
= SndNewChannel(&mySndChannelPtr
, sampledSynth
,
136 sp
->sp_channels
== 2 ? initStereo
: initMono
,
139 gNumChannels
= sp
->sp_channels
;
140 gSampleSize
= sp
->sp_samplesize
;
141 gSampleRate
= Long2Fix(sp
->sp_samplerate
);
142 *handlep
= (unsigned long)mySndChannelPtr
;
147 OSStatus
SoundManagerSetPlayerFormat(unsigned long /*h*/, const soundParams
*sp
)
149 OSStatus err
= noErr
;
150 // These values just get copied into each bufferCmd. Clever Sound Manager.
151 gNumChannels
= sp
->sp_channels
;
152 gSampleSize
= sp
->sp_samplesize
;
153 gSampleRate
= Long2Fix(sp
->sp_samplerate
);
154 // Interesting question; I wonder if I should "correct" integral sample
155 // rates which are near standard non-integral sample rates? Bah; I'm
156 // only doing this for a task that uses 8KHz sampling, anyway...
158 // I'm not sure if a stereo-capable channel needs to be warned that mono
159 // data is coming (or vice versa?), but I suppose that a component-selected
160 // output channel should be told of the channel choice sometime...
162 SndCommand theCommand
;
163 theCommand
.cmd
= reInitCmd
;
164 theCommand
.param1
= 0;
165 theCommand
.param2
= gNumChannels
== 2 ? initStereo
: initMono
;
166 err
= SndDoCommand(&mySndChannel
, &theCommand
, FALSE
/* wait */);
170 OSStatus
SoundManagerPlaySample(unsigned long /*h*/,
171 const unsigned char *buf
,
173 MPSemaphoreID
*awaken_me
)
176 if (playcount
== 0) {
178 gPlayStartTime
= AbsoluteToNanoseconds(UpTime());
181 if (playcount
== 1) playcount
= len
;
182 else playcount
+= len
;
183 if ((playcount
- len
) < 240000 && playcount
>= 240000)
184 fprintf(stderr
,"~240000 bytes played\n");
185 gPlayMostRecentTime
= AbsoluteToNanoseconds(UpTime());
187 // if (len != 480) fprintf(stderr,"wierd len %d\n", len);
192 // the insufficiently abstract API we present loses the size of the
193 // sample frames, so we have to reinvent that here.
194 int frameSize
= gNumChannels
* (gSampleSize
== 8 ? 1 : 2);
195 assert(len
%frameSize
== 0);
197 return notEnoughBufferSpace
;
199 // busy-wait until the sound header we want is free
200 // Remember -- only one thread queues requests!
202 while (!OTCompareAndSwap32(0, 1,
203 (UInt32
*)&gSemaphores
[gNextBuffer
])) {
204 /* the assumption is we're a Blue thread, so play nice */
212 if ((misses
& 127) == 0)
213 fprintf(stderr
,"missed scoring a buffer!\n");
218 // OK, we've made our reservation, copy the data
220 p
= mySoundHeaders
[gNextBuffer
];
221 // someone set up us the buffer!
222 memcpy(&p
->sampleArea
[0], buf
, len
);
224 p
->numFrames
= len
/ frameSize
;
225 p
->numChannels
= gNumChannels
;
226 p
->sampleRate
= gSampleRate
;
227 p
->sampleSize
= gSampleSize
;
229 // Queue the commands (release the hounds!)
231 SndCommand theCommand
;
232 theCommand
.cmd
= bufferCmd
;
233 theCommand
.param1
= 0;
234 theCommand
.param2
= (long)p
;
235 err
= SndDoCommand(&mySndChannel
, &theCommand
, FALSE
);
238 // And arrange for it to be picked up
239 theCommand
.cmd
= callBackCmd
;
240 theCommand
.param1
= gNextBuffer
; // we're single threaded, right???
241 theCommand
.param2
= 0;
242 err
= SndDoCommand(&mySndChannel
, &theCommand
, FALSE
);
245 // now, attempt to reserve callback
247 int prevBuffer
= gNextBuffer
-1;
248 if (prevBuffer
== -1)
249 prevBuffer
= gNumBuffers
-1;
250 if (!OTCompareAndSwapPtr((void *)1, (void *)awaken_me
,
251 (void **)&gSemaphores
[prevBuffer
])) {
252 PAssert(gSemaphores
[prevBuffer
] == 0, "screwup in buffer handling");
253 MPSignalSemaphore(*awaken_me
);
256 // OK, advance buffer pointer
257 if (++gNextBuffer
== gNumBuffers
)
259 } while (0); // end break scope
261 gSemaphores
[gNextBuffer
] = 0; // give it back
266 fprintf(stderr
,"SndDoCommand failed with err %d\n", err
);
271 OSStatus
SoundManagerClosePlayer(unsigned long /*h*/)
273 if (mySndChannelPtr
) {
274 // XXX what about all of the waiting semaphores?
275 SndDisposeChannel(&mySndChannel
, FALSE
);
281 OSStatus
SoundManagerStopPlayer(unsigned long /*h*/)
284 SndCommand theCommand
;
285 theCommand
.cmd
= flushCmd
;
286 theCommand
.param1
= 0;
287 theCommand
.param2
= 0;
288 err
= SndDoImmediate(&mySndChannel
, &theCommand
);
289 // XXX what about all of the waiting semaphores?
291 theCommand
.cmd
= quietCmd
;
292 err
= SndDoImmediate(&mySndChannel
, &theCommand
);
297 OSStatus
SoundManagerIsPlaying(unsigned long /*h*/, unsigned long *answer
)
299 // If the "previous" buffer is free, it's not busy.
300 int prevBuffer
= gNextBuffer
-1;
301 if (prevBuffer
== -1) prevBuffer
= gNumBuffers
-1;
302 *answer
= (gSemaphores
[prevBuffer
] != 0);
306 OSStatus
SoundManagerWaitForPlayCompletion(unsigned long,
307 const callbackThunk
*thunk
)
309 // Register a callback which will be called when the sounds are done
310 // playing. It will be inserted into the SM queue, so if you queue
311 // sounds while it is waiting, it may return "early".
312 SndCommand theCommand
;
313 theCommand
.cmd
= callBackCmd
;
314 theCommand
.param1
= -1; // special index value
315 theCommand
.param2
= (unsigned long)thunk
;
316 OSStatus err
= SndDoCommand(&mySndChannel
, &theCommand
, FALSE
);
320 OSStatus
SoundManagerTeardown()
323 // better not be running!
324 DisposeSndCallBackUPP(myUPP
);
327 for (int i
= 0; i
< MAXHEADERS
; i
++) {
328 if (mySoundHeaders
[i
]) {
329 DisposePtr((char *)mySoundHeaders
[i
]);
330 mySoundHeaders
[i
] = 0;