1 /* MikMod sound library
2 (c) 1998, 1999, 2000 Miodrag Vallat and others - see file AUTHORS for
3 complete list.
4
5 This library is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Library General Public License as
7 published by the Free Software Foundation; either version 2 of
8 the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18 02111-1307, USA.
19 */
20
21 /*==============================================================================
22
23 $Id$
24
25 Driver for output to the Macintosh Sound Manager
26
27 ==============================================================================*/
28
29 /*
30 Written by Anders F Bjoerklund <afb@algonet.se>
31
32 Based on free code by:
33 - Antoine Rosset <RossetAntoine@bluewin.ch> (author of PlayerPRO)
34 - John Stiles <stiles@emulation.net>
35 - Pierre-Olivier Latour <pol@french-touch.net>
36
37 This code uses two different ways of filling the buffers:
38 - Classic code uses SndPlayDoubleBuffer callbacks
39 - Carbon code uses SndCallBacks with Deferred Tasks
40
41 Updated by Axel Wefers <awe@fruitz-of-dojo.de>:
42 - changed code for compatibility with ProjectBuilder/OSX:
43 - "NewSndCallBackProc()" to "NewSndCallBackUPP()".
44 - "NewDeferredTaskProc()" to "NewDeferredTaskUPP()".
45 - added some conditionals to avoid compiler warnings.
46
47 Updated again in 2004 by afb, to fix some bugs:
48 - deadlock in Player_Paused, when using HAVE_PTHREAD
49 (since it is now using the global "vars" MUTEX too)
50 - playback was wrong speed when running under CarbonLib
51 (due to Deferred Tasks having lame latencies there)
52 - proper playing of partially filled buffers too
53 */
54
55 #ifdef HAVE_CONFIG_H
56 #include "config.h"
57 #endif
58
59 #include "mikmod_internals.h"
60
61 #ifdef DRV_MAC
62
63 #if defined(__APPLE__) && defined(__MACH__)
64
65 #include <Carbon/Carbon.h>
66
67 #elif TARGET_API_MAC_CARBON && (UNIVERSAL_INTERFACES_VERSION >= 0x0335)
68
69 #include <Carbon.h>
70
71 #else
72
73 #include <Sound.h>
74 #include <OSUtils.h>
75 #include <Gestalt.h>
76
77 #endif
78
79 #ifndef TARGET_API_MAC_CARBON
80 #define TARGET_API_MAC_CARBON TARGET_CARBON
81 #endif
82
83 #ifndef TARGET_CPU_68K
84 #define TARGET_CPU_68K GENERATING68K
85 #endif
86
87 #if TARGET_API_MAC_CARBON
88 #define USE_SNDDOUBLEBUFFER 0
89 #else
90 #define USE_SNDDOUBLEBUFFER 1
91 #endif
92
93 #if TARGET_API_MAC_CARBON
94 #define USE_DEFERREDTASKS 0
95 #else
96 #define USE_DEFERREDTASKS 1
97 #endif
98
99 #define SOUND_BUFFER_SIZE 4096L
100
101 static SndChannelPtr soundChannel = NULL; /* pointer to a sound channel */
102
103 #if USE_SNDDOUBLEBUFFER
104 static SndDoubleBufferHeader doubleHeader; /* pointer to double buffers */
105 #else
106 static SndCallBackUPP sndCallBack = NULL;
107 static ExtSoundHeader sndHeader; /* a sound manager bufferCmd header */
108
109 static Ptr sndBuffer1 = NULL;
110 static Ptr sndBuffer2 = NULL;
111 static Ptr currentBuffer;
112 static long currentFrames;
113
114 #if USE_DEFERREDTASKS
115 static DeferredTask dtask; /* deferred task record */
116 static volatile Boolean deferredTaskFired = true;
117 static volatile Boolean deferredTaskDone = true;
118 #endif
119
120 #endif /* USE_SNDDOUBLEBUFFER */
121
122 #define FILL_BUFFER(_buffer_data,_buffer_size,_bytes) \
123 MUTEX_LOCK(vars); \
124 if (Player_Paused_internal()) \
125 _bytes=VC_SilenceBytes((SBYTE*)_buffer_data,(ULONG)_buffer_size); \
126 else _bytes=VC_WriteBytes((SBYTE*)_buffer_data,(ULONG)_buffer_size); \
127 MUTEX_UNLOCK(vars);
128
129
130 #if USE_SNDDOUBLEBUFFER
131
132 /* DoubleBackProc, called at interrupt time */
MyDoubleBackProc(SndChannelPtr channel,SndDoubleBufferPtr doubleBuffer)133 static pascal void MyDoubleBackProc(SndChannelPtr channel,SndDoubleBufferPtr doubleBuffer)
134 {
135 #ifndef GCC
136 #pragma unused(channel)
137 #endif
138 long written;
139 #if TARGET_CPU_68K
140 long oldA5=SetA5(doubleBuffer->dbUserInfo[0]);
141 #endif
142
143 FILL_BUFFER(doubleBuffer->dbSoundData, SOUND_BUFFER_SIZE, written)
144
145 if (doubleHeader.dbhNumChannels==2) written>>=1;
146 if (doubleHeader.dbhSampleSize==16) written>>=1;
147
148 doubleBuffer->dbNumFrames=written;
149 doubleBuffer->dbFlags|=dbBufferReady;
150
151 #if TARGET_CPU_68K
152 SetA5(oldA5);
153 #endif
154 }
155
156 #else /* USE_SNDDOUBLEBUFFER */
157
158 #if USE_DEFERREDTASKS
159 /* DeferredTask, called at almost-interrupt time (not for 68K - doesn't set A5) */
DeferredTaskCallback(long param)160 static pascal void DeferredTaskCallback(long param)
161 {
162 long written;
163
164 deferredTaskFired = true;
165
166 FILL_BUFFER(param, SOUND_BUFFER_SIZE, written)
167
168 deferredTaskDone = true;
169 }
170 #endif /* USE_DEFERREDTASKS */
171
172 /* SoundCallback, called at interrupt time (not for 68K - doesn't set A5) */
SoundCallback(SndChannelPtr channel,SndCommand * command)173 static pascal void SoundCallback(SndChannelPtr channel, SndCommand *command)
174 {
175 #ifndef GCC
176 #pragma unused(channel,command)
177 #endif
178 SndCommand buffer = { bufferCmd, 0, (long) &sndHeader };
179 SndCommand callback = { callBackCmd, 0, 0 };
180
181 /* Install current buffer */
182 sndHeader.samplePtr = currentBuffer;
183 sndHeader.numFrames = currentFrames;
184 SndDoImmediate(soundChannel, &buffer);
185
186 #if USE_DEFERREDTASKS
187 /* Setup deferred task to fill next buffer */
188 if(deferredTaskFired)
189 {
190 currentBuffer = (currentBuffer == sndBuffer1) ? sndBuffer2 : sndBuffer1;
191 if (currentBuffer != NULL)
192 {
193 deferredTaskFired = false;
194 deferredTaskDone = false;
195 dtask.dtParam = (long) currentBuffer;
196 DTInstall((DeferredTaskPtr) &dtask);
197 }
198 }
199 #else
200 {
201 long bytes;
202
203 currentBuffer = (currentBuffer == sndBuffer1) ? sndBuffer2 : sndBuffer1;
204 FILL_BUFFER(currentBuffer, SOUND_BUFFER_SIZE, bytes)
205
206 if (sndHeader.numChannels == 2) bytes >>= 1;
207 if (sndHeader.sampleSize == 16) bytes >>= 1;
208
209 currentFrames = bytes;
210 }
211 #endif /* USE_DEFERREDTASKS */
212
213 /* Queue next callback */
214 SndDoCommand(soundChannel, &callback, true);
215 }
216
217 #endif /* USE_SNDDOUBLEBUFFER */
218
MAC_IsThere(void)219 static BOOL MAC_IsThere(void)
220 {
221 NumVersion nVers;
222
223 nVers=SndSoundManagerVersion();
224 if (nVers.majorRev>=2)
225 return 1; /* need SoundManager 2.0+ */
226 else
227 return 0;
228 }
229
MAC_Init(void)230 static int MAC_Init(void)
231 {
232 OSErr err,iErr;
233 #if USE_SNDDOUBLEBUFFER
234 int i;
235 SndDoubleBufferPtr doubleBuffer;
236 #endif
237 long rate,maxrate,maxbits;
238 long gestaltAnswer;
239 NumVersion nVers;
240 Boolean Stereo,StereoMixing,Audio16;
241 Boolean NewSoundManager,NewSoundManager31;
242
243 NewSoundManager31=NewSoundManager=false;
244
245 nVers=SndSoundManagerVersion();
246 if (nVers.majorRev>=3) {
247 NewSoundManager=true;
248 if (nVers.minorAndBugRev>=0x10)
249 NewSoundManager31=true;
250 } else
251 if (nVers.majorRev<2)
252 return 1; /* failure, need SoundManager 2.0+ */
253
254 iErr=Gestalt(gestaltSoundAttr,&gestaltAnswer);
255 if (iErr==noErr) {
256 Stereo=(gestaltAnswer & (1<<gestaltStereoCapability))!=0;
257 StereoMixing=(gestaltAnswer & (1<<gestaltStereoMixing))!=0;
258 Audio16=(gestaltAnswer & (1<<gestalt16BitSoundIO))!=0;
259 } else {
260 /* failure, couldn't get any sound info at all ? */
261 Stereo=StereoMixing=Audio16=false;
262 }
263
264 #if !TARGET_CPU_68K || !TARGET_RT_MAC_CFM
265 if (NewSoundManager31) {
266 iErr=GetSoundOutputInfo(0L,siSampleRate,(void*)&maxrate);
267 if (iErr==noErr)
268 iErr=GetSoundOutputInfo(0L,siSampleSize,(void*)&maxbits);
269 }
270
271 if (iErr!=noErr) {
272 #endif
273 maxrate=rate22khz;
274
275 if (NewSoundManager && Audio16)
276 maxbits=16;
277 else
278 maxbits=8;
279 #if !TARGET_CPU_68K || !TARGET_RT_MAC_CFM
280 }
281 #endif
282
283 switch (md_mixfreq) {
284 case 48000:rate=rate48khz;break;
285 case 44100:rate=rate44khz;break;
286 case 22254:rate=rate22khz;break;
287 case 22050:rate=rate22050hz;break;
288 case 11127:rate=rate11khz;break;
289 case 11025:rate=rate11025hz;break;
290 default: rate=0;break;
291 }
292
293 if (!rate) {
294 _mm_errno=MMERR_MAC_SPEED;
295 return 1;
296 }
297
298 md_mode|=DMODE_SOFT_MUSIC|DMODE_SOFT_SNDFX;
299
300 if ((md_mode&DMODE_16BITS)&&(maxbits<16))
301 md_mode&=~DMODE_16BITS;
302
303 if (!Stereo || !StereoMixing)
304 md_mode&=~DMODE_STEREO;
305
306 if (rate>maxrate)
307 rate=maxrate;
308 if (md_mixfreq>(maxrate>>16))
309 md_mixfreq=maxrate>>16;
310
311 #if USE_SNDDOUBLEBUFFER
312 err=SndNewChannel(&soundChannel,sampledSynth,
313 (md_mode&DMODE_STEREO)?initStereo:initMono, NULL);
314 if(err!=noErr) {
315 _mm_errno=MMERR_OPENING_AUDIO;
316 return 1;
317 }
318
319 doubleHeader.dbhCompressionID=0;
320 doubleHeader.dbhPacketSize =0;
321 doubleHeader.dbhSampleRate =rate;
322 doubleHeader.dbhSampleSize =(md_mode&DMODE_16BITS)?16:8;
323 doubleHeader.dbhNumChannels =(md_mode&DMODE_STEREO)?2:1;
324 doubleHeader.dbhDoubleBack =NewSndDoubleBackProc(&MyDoubleBackProc);
325
326 for(i=0;i<2;i++) {
327 doubleBuffer=(SndDoubleBufferPtr)NewPtrClear(sizeof(SndDoubleBuffer)+
328 SOUND_BUFFER_SIZE);
329 if(!doubleBuffer) {
330 _mm_errno=MMERR_OUT_OF_MEMORY;
331 return 1;
332 }
333
334 doubleBuffer->dbNumFrames=0;
335 doubleBuffer->dbFlags=0;
336 doubleBuffer->dbUserInfo[0]=SetCurrentA5();
337 doubleBuffer->dbUserInfo[1]=0;
338
339 doubleHeader.dbhBufferPtr[i]=doubleBuffer;
340 }
341
342 #else /* USE_SNDDOUBLEBUFFER */
343 if(sndCallBack == NULL)
344 sndCallBack = NewSndCallBackUPP(SoundCallback); /* <AWE> was "NewSndCallBackProc()" */
345
346 err=SndNewChannel(&soundChannel,sampledSynth,
347 (md_mode&DMODE_STEREO)?initStereo:initMono, sndCallBack);
348 if(err!=noErr) {
349 _mm_errno=MMERR_OPENING_AUDIO;
350 return 1;
351 }
352
353 sndBuffer1 = NewPtrClear(SOUND_BUFFER_SIZE);
354 sndBuffer2 = NewPtrClear(SOUND_BUFFER_SIZE);
355 if (sndBuffer1 == NULL || sndBuffer2 == NULL) {
356 _mm_errno=MMERR_OUT_OF_MEMORY;
357 return 1;
358 }
359 currentBuffer = sndBuffer1;
360
361 /* Setup sound header */
362 memset(&sndHeader, 0, sizeof(sndHeader));
363 sndHeader.numChannels = (md_mode&DMODE_STEREO)? 2: 1;
364 sndHeader.sampleRate = rate;
365 sndHeader.encode = extSH;
366 sndHeader.baseFrequency = kMiddleC;
367 sndHeader.numFrames = SOUND_BUFFER_SIZE >> (((md_mode&DMODE_STEREO)? 1: 0) + ((md_mode&DMODE_16BITS)?1: 0));
368 sndHeader.sampleSize = (md_mode&DMODE_16BITS)? 16: 8;
369 sndHeader.samplePtr = currentBuffer;
370
371 #if USE_DEFERREDTASKS
372 /* Setup deferred task record */
373 memset(&dtask, 0, sizeof(dtask));
374 dtask.qType = dtQType;
375 dtask.dtFlags = 0;
376 dtask.dtAddr = NewDeferredTaskUPP(DeferredTaskCallback); /* <AWE> was "NewDeferredTaskProc()" */
377 dtask.dtReserved = 0;
378 deferredTaskFired = true;
379 #endif /* USE_DEFERREDTASKS */
380
381 #endif /* USE_SNDDOUBLEBUFFER */
382
383 return VC_Init();
384 }
385
MAC_Exit(void)386 static void MAC_Exit(void)
387 {
388 #if USE_SNDDOUBLEBUFFER
389 int i;
390 #else
391 Ptr temp1,temp2;
392 #endif
393
394 if (soundChannel != NULL)
395 {
396 SndDisposeChannel(soundChannel,true); /* "true" means to flush and quiet */
397 soundChannel=NULL;
398 }
399
400 #if USE_SNDDOUBLEBUFFER
401 DisposeRoutineDescriptor((UniversalProcPtr)doubleHeader.dbhDoubleBack);
402 doubleHeader.dbhDoubleBack=NULL;
403
404 for(i=0;i<doubleHeader.dbhNumChannels;i++) {
405 DisposePtr((Ptr)doubleHeader.dbhBufferPtr[i]);
406 doubleHeader.dbhBufferPtr[i]=NULL;
407 }
408
409 #else /* USE_SNDDOUBLEBUFFER */
410 if (sndCallBack != NULL)
411 {
412 DisposeSndCallBackUPP(sndCallBack);
413 sndCallBack = NULL;
414 }
415
416 temp1 = sndBuffer1;
417 sndBuffer1 = NULL;
418 temp2 = sndBuffer2;
419 sndBuffer2 = NULL;
420
421 #if USE_DEFERREDTASKS
422 /* <afb> we can't dispose of the buffers until the DT is done with them */
423 while (!deferredTaskDone)
424 ;
425 #endif
426 DisposePtr(temp1);
427 DisposePtr(temp2);
428 #endif /* USE_SNDDOUBLEBUFFER */
429
430 VC_Exit();
431 }
432
MAC_PlayStart(void)433 static int MAC_PlayStart(void)
434 {
435 OSErr err;
436
437 #if USE_SNDDOUBLEBUFFER
438 MyDoubleBackProc(soundChannel,doubleHeader.dbhBufferPtr[0]);
439 MyDoubleBackProc(soundChannel,doubleHeader.dbhBufferPtr[1]);
440
441 err=SndPlayDoubleBuffer(soundChannel,&doubleHeader);
442 if(err!=noErr) {
443 _mm_errno=MMERR_MAC_START;
444 return 1;
445 }
446
447 #else /* USE_SNDDOUBLEBUFFER */
448 SndCommand callback = { callBackCmd, 0, 0 };
449
450 err=SndDoCommand(soundChannel, &callback, true);
451 if(err!=noErr) {
452 _mm_errno=MMERR_MAC_START;
453 return 1;
454 }
455 #endif /* USE_SNDDOUBLEBUFFER */
456
457 return VC_PlayStart();
458 }
459
MAC_PlayStop(void)460 static void MAC_PlayStop(void)
461 {
462 SndCommand flush = { flushCmd, 0, 0 };
463 SndCommand quiet = { quietCmd, 0, 0 };
464
465 /* <afb> IM:Sound says we should issue the flushCmd before the quietCmd. */
466 SndDoImmediate(soundChannel,&flush);
467 SndDoImmediate(soundChannel,&quiet);
468
469 VC_PlayStop();
470 }
471
MAC_Update(void)472 static void MAC_Update(void)
473 {
474 return;
475 }
476
477 MIKMODAPI MDRIVER drv_mac={
478 NULL,
479 "Mac Driver (Carbonized)",
480 "Macintosh Sound Manager Driver v2.1",
481 0,255,
482 "mac",
483 NULL,
484 NULL,
485 MAC_IsThere,
486 VC_SampleLoad,
487 VC_SampleUnload,
488 VC_SampleSpace,
489 VC_SampleLength,
490 MAC_Init,
491 MAC_Exit,
492 NULL,
493 VC_SetNumVoices,
494 MAC_PlayStart,
495 MAC_PlayStop,
496 MAC_Update,
497 NULL,
498 VC_VoiceSetVolume,
499 VC_VoiceGetVolume,
500 VC_VoiceSetFrequency,
501 VC_VoiceGetFrequency,
502 VC_VoiceSetPanning,
503 VC_VoiceGetPanning,
504 VC_VoicePlay,
505 VC_VoiceStop,
506 VC_VoiceStopped,
507 VC_VoiceGetPosition,
508 VC_VoiceRealVolume
509 };
510
511 #else /* ifdef DRV_MAC */
512
513 MISSING(drv_mac);
514
515 #endif
516
517 /* ex:set ts=4: */
518
519