Uncommented beaudio code
[pwlib.git] / src / ptlib / unix / macosaudio / SoundMangler.cxx
blobbfd72c894b664db8ca7041657290ad7609bf9d92
1 #include <string.h>
2 #include <stdio.h>
3 #include <unistd.h>
5 #include <ptlib.h>
7 // goddamn apple headers
8 #undef TCP_NODELAY
9 #undef TCP_MAXSEG
10 #include <Carbon/Carbon.h>
12 #include "MacMain.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;
45 #define debugcrap
46 #ifdef debugcrap
47 static int bcount;
48 static Nanoseconds gStartTime;
49 static Nanoseconds gMostRecentTime;
50 static int playcount;
51 static Nanoseconds gPlayStartTime, gPlayMostRecentTime;
52 static int misses;
53 #endif
55 static pascal void theCallbackFunction(SndChannelPtr chan,
56 SndCommand *cmd)
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
61 // from 1 to 0.
62 if (!OTCompareAndSwapPtr((void*)1, (void*)0,
63 (void **)&gSemaphores[cmd->param1]))
65 (void)MPSignalSemaphore(*gSemaphores[cmd->param1]);
66 gSemaphores[cmd->param1] = (MPSemaphoreID *)0;
69 #ifdef debugcrap
70 if (bcount == 0)
71 gStartTime = AbsoluteToNanoseconds(UpTime());
72 else
73 gMostRecentTime = AbsoluteToNanoseconds(UpTime());
75 if (++bcount == 200)
76 fprintf(stderr,"200 buffers played\n");
77 #endif
78 } else if (cmd->param1 == (short)-1) {
79 // "thunk!"
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
88 // Open time.
89 OSStatus SoundManagerInitialize()
91 OSErr err = noErr;
93 // first create the UPP for the callback function
94 if ((myUPP = NewSndCallBackUPP(theCallbackFunction)) == 0) {
95 err = memFullErr;
96 goto bail;
98 gNextBuffer = 0;
99 for (int i = 0; i < MAXHEADERS; i++) {
100 ExtSoundHeaderPtr p;
101 if ((p = (ExtSoundHeaderPtr)NewPtrClear( sizeof(ExtSoundHeader) - 1 + BLOCKSIZE )) == 0) {
102 err = memFullErr;
103 break;
105 mySoundHeaders[i] = p;
106 gSemaphores[i] = 0;
107 // channels, rate, and size get set at play time
108 p->encode = extSH;
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!
114 bail:
115 return err;
118 OSStatus SoundManagerOpenPlayer(const char *name, const soundParams *sp,
119 unsigned long *handlep)
121 OSStatus err = noErr;
122 if (mySndChannelPtr != 0)
123 return fBsyErr;
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,
137 myUPP);
138 if (err == 0) {
139 gNumChannels = sp->sp_channels;
140 gSampleSize = sp->sp_samplesize;
141 gSampleRate = Long2Fix(sp->sp_samplerate);
142 *handlep = (unsigned long)mySndChannelPtr;
144 return err;
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 */);
167 return err;
170 OSStatus SoundManagerPlaySample(unsigned long /*h*/,
171 const unsigned char *buf,
172 size_t len,
173 MPSemaphoreID *awaken_me)
175 #ifdef debugcrap
176 if (playcount == 0) {
177 playcount = 1;
178 gPlayStartTime = AbsoluteToNanoseconds(UpTime());
180 else {
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);
188 #endif
190 OSErr err = noErr;
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);
196 if (len > BLOCKSIZE)
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 */
205 YieldToAnyThread();
206 #if P_MACOSX
207 usleep(1000);
208 #endif // P_MACOSX
209 #ifdef debugcrap
210 #define DEBUGME
211 #ifdef DEBUGME
212 if ((misses & 127) == 0)
213 fprintf(stderr,"missed scoring a buffer!\n");
214 misses++;
215 #endif // DEBUGME
216 #endif // debugcrap
218 // OK, we've made our reservation, copy the data
219 ExtSoundHeaderPtr p;
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!)
230 do { // break-scope
231 SndCommand theCommand;
232 theCommand.cmd = bufferCmd;
233 theCommand.param1 = 0;
234 theCommand.param2 = (long)p;
235 err = SndDoCommand(&mySndChannel, &theCommand, FALSE);
236 if (err) break;
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);
243 if (err) break;
245 // now, attempt to reserve callback
246 if (awaken_me) {
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)
258 gNextBuffer = 0;
259 } while (0); // end break scope
260 if (err) {
261 gSemaphores[gNextBuffer] = 0; // give it back
262 goto bail;
264 bail:
265 if (err) {
266 fprintf(stderr,"SndDoCommand failed with err %d\n", err);
268 return err;
271 OSStatus SoundManagerClosePlayer(unsigned long /*h*/)
273 if (mySndChannelPtr) {
274 // XXX what about all of the waiting semaphores?
275 SndDisposeChannel(&mySndChannel, FALSE);
277 mySndChannelPtr = 0;
278 return 0;
281 OSStatus SoundManagerStopPlayer(unsigned long /*h*/)
283 OSErr err;
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?
290 if (err == 0) {
291 theCommand.cmd = quietCmd;
292 err = SndDoImmediate(&mySndChannel, &theCommand);
294 return err;
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);
303 return 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);
317 return err;
320 OSStatus SoundManagerTeardown()
322 if (myUPP) {
323 // better not be running!
324 DisposeSndCallBackUPP(myUPP);
325 myUPP = 0;
327 for (int i = 0; i < MAXHEADERS; i++) {
328 if (mySoundHeaders[i]) {
329 DisposePtr((char *)mySoundHeaders[i]);
330 mySoundHeaders[i] = 0;
333 return 0;