1 /* -*- c-file-style: "gnu" -*-
2     TiMidity++ -- MIDI to WAVE converter and player
3     Copyright (C) 1999-2002 Masanao Izumo <mo@goice.co.jp>
4     Copyright (C) 1995 Tuukka Toivonen <tt@cgs.fi>
5     ALSA 0.[56] support by Katsuhiro Ueno <katsu@blue.sky.or.jp>
6 
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the Free Software
19     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21     darwin_a.c
22 
23     Functions to play sound on the Darwin audio driver
24     by T.Nogami <t-nogami@happy.email.ne.jp>
25 */
26 
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif /* HAVE_CONFIG_H */
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 
35 #ifndef NO_STRING_H
36 #include <string.h>
37 #else
38 #include <strings.h>
39 #endif
40 
41 #include <CoreAudio/AudioHardware.h>
42 
43 #include "timidity.h"
44 #include "common.h"
45 #include "output.h"
46 #include "controls.h"
47 #include "timer.h"
48 #include "instrum.h"
49 #include "playmidi.h"
50 #include "miditrace.h"
51 
52 static int open_output(void); /* 0=success, 1=warning, -1=fatal error */
53 static void close_output(void);
54 static int output_data(char *buf, int32 in_bytes);
55 static int acntl(int request, void *arg);
56 static int total_bytes;
57 float output_volume = 1.0;
58 
59 int	    mac_buf_using_num;
60 
61 /* export the playback mode */
62 
63 /*********************************************************************/
64 #define dpm darwin_play_mode
65 
66 PlayMode dpm = {
67   DEFAULT_RATE, PE_16BIT|PE_SIGNED, PF_PCM_STREAM|PF_CAN_TRACE,
68   -1,
69   {0}, /* default: get all the buffer fragments you can */
70   "Mac OS X pcm device", 'd',
71   "",
72   open_output,
73   close_output,
74   output_data,
75   acntl
76 };
77 
78 #define FailWithAction(cond, action, handler)				\
79 	if (cond) {							\
80 		{ action; }						\
81 		goto handler;						\
82 	}
83 
84 //#define DEBUG_DARWIN_A(x)   /*nothing*/
85 #define DEBUG_DARWIN_A(x)   ctl->cmsg x;
86 
87 /*********************************************************************/
88 
89 typedef struct {
90     Boolean		soundPlaying;
91     AudioDeviceID	device;			// the default device
92     UInt32	        deviceBufferSize;
93                   // bufferSize returned by kAudioDevicePropertyBufferSize
94     AudioStreamBasicDescription		deviceFormat;
95                   // info about the default device
96 #define BUFNUM 256
97 #define BUFLEN 4096
98     char             buffer[BUFNUM][BUFLEN];   //1 buffer size = 4096bytes
99     unsigned short   buffer_len[BUFNUM];
100     volatile int     currBuf, nextBuf;
101     int		     samples, get_samples;
102 } appGlobals, *appGlobalsPtr, **appGlobalsHandle;
103 
104 static appGlobals	globals;
105 
106 
107 /*********************************************************************/
appIOProc(AudioDeviceID inDevice,const AudioTimeStamp * inNow,const AudioBufferList * inInputData,const AudioTimeStamp * inInputTime,AudioBufferList * outOutputData,const AudioTimeStamp * inOutputTime,void * dummy)108 OSStatus appIOProc (AudioDeviceID  inDevice, const AudioTimeStamp*  inNow,
109                     const AudioBufferList*  inInputData,
110 		    const AudioTimeStamp*  inInputTime,
111                     AudioBufferList*  outOutputData,
112 		    const AudioTimeStamp* inOutputTime, void* dummy )
113 {
114     int       next_curr;
115     int       trans_len;
116 
117     if( globals.currBuf==globals.nextBuf ){  //end of play
118         outOutputData->mNumberBuffers=0;
119         memset(outOutputData->mBuffers[0].mData, 0, globals.deviceBufferSize);
120         globals.soundPlaying=0;
121         return 0;
122     }
123 
124     trans_len = globals.buffer_len[globals.currBuf];
125     if( output_volume==1.0 ){
126         memcpy(  outOutputData->mBuffers[0].mData,
127              &globals.buffer[globals.currBuf][0],
128              trans_len);   // move data into output data buffer
129     }else{
130         float   *src = (float*)&globals.buffer[globals.currBuf][0],
131                 *dst = outOutputData->mBuffers[0].mData;
132         int     i, quant = trans_len/sizeof(float);
133         for(i=0; i<quant; i++){
134             *dst++ = (*src++)*output_volume;
135         }
136     }
137     outOutputData->mBuffers[0].mDataByteSize = trans_len;
138 
139     outOutputData->mNumberBuffers=1;
140     globals.samples += trans_len
141                               / globals.deviceFormat.mBytesPerPacket;
142 
143     next_curr = globals.currBuf+1;
144     next_curr %= BUFNUM;
145     mac_buf_using_num--;
146     globals.currBuf = next_curr;
147 
148     return 0; //no err
149 }
150 
151 /*********************************************************************/
init_variable()152 static void init_variable()
153 {
154     globals.samples = globals.get_samples = 0;
155     globals.currBuf = globals.nextBuf = 0;
156     globals.soundPlaying = 0;
157     mac_buf_using_num = 0;
158 }
159 
160 /*********************************************************************/
161 /*return value == 0 sucess
162  *             == -1 fails
163  */
open_output(void)164 static int open_output(void)
165 {
166     OSStatus				err = 0; //no err
167     UInt32				count,
168                                         bufferSize;
169     AudioDeviceID			device = kAudioDeviceUnknown;
170     AudioStreamBasicDescription		format;
171 
172     // get the default output device for the HAL
173     count = sizeof(globals.device);
174     // it is required to pass the size of the data to be returned
175     err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
176                                    &count, (void *) &device);
177     if (err != 0) goto Bail;
178 
179     // get the buffersize that the default device uses for IO
180     count = sizeof(globals.deviceBufferSize);
181               // it is required to pass the size of the data to be returned
182     err = AudioDeviceGetProperty(device, 0, 0, kAudioDevicePropertyBufferSize,
183                                  &count, &bufferSize);
184     if (err != 0) goto Bail;
185 
186     if( globals.deviceBufferSize>BUFLEN ){
187         fprintf(stderr, "globals.deviceBufferSize NG: %ld\n",
188                 globals.deviceBufferSize);
189         exit(1);
190     }
191 
192          // get a description of the data format used by the default device
193     count = sizeof(globals.deviceFormat);
194          // it is required to pass the size of the data to be returned
195     err = AudioDeviceGetProperty(device, 0, 0,
196                                  kAudioDevicePropertyStreamFormat,
197                                  &count, &format);
198     if (err != 0) goto Bail;
199     FailWithAction(format.mFormatID != kAudioFormatLinearPCM, err = -1, Bail);
200                     // bail if the format is not linear pcm
201 
202     // everything is ok so fill in these globals
203     globals.device = device;
204     globals.deviceBufferSize = bufferSize;
205     globals.deviceFormat = format;
206     init_variable();
207 
208     err = AudioDeviceAddIOProc(globals.device, appIOProc, 0 );
209                     // setup our device with an IO proc
210     if (err != 0) goto Bail;
211 
212     globals.deviceFormat.mSampleRate = dpm.rate;
213 
214 #if 0
215     globals.deviceFormat.mFormatFlags =  kLinearPCMFormatFlagIsBigEndian
216                                        | kLinearPCMFormatFlagIsPacked
217                                        | kLinearPCMFormatFlagIsSignedInteger;
218     globals.deviceFormat.mBytesPerPacket = 4;
219     globals.deviceFormat.mBytesPerFrame = 4;
220     globals.deviceFormat.mBitsPerChannel = 0x10;
221 
222     err = AudioDeviceSetProperty(device, &inWhen, 0, 0,
223                                  kAudioDevicePropertyStreamFormat,
224                                  count, &globals.deviceFormat);
225     if (err != 0) goto Bail;
226 #endif
227 
228 #if 0
229     fprintf(stderr, "deviceBufferSize = %d\n", globals.deviceBufferSize);
230     fprintf(stderr, "mSampleRate = %g\n", globals.deviceFormat.mSampleRate);
231     fprintf(stderr, "mFormatID = 0x%08x\n", globals.deviceFormat.mFormatID);
232     fprintf(stderr, "mFormatFlags = 0x%08x\n",
233             globals.deviceFormat.mFormatFlags);
234     fprintf(stderr, "mBytesPerPacket = 0x%08x\n",
235             globals.deviceFormat.mBytesPerPacket);
236     fprintf(stderr, "mBytesPerFrame = 0x%08x\n",
237             globals.deviceFormat.mBytesPerFrame);
238     fprintf(stderr, "mBitsPerChannel = 0x%08x\n",
239             globals.deviceFormat.mBitsPerChannel);
240 #endif
241 
242 Bail:
243     return (err);
244 }
245 
246 /*********************************************************************/
output_data(char * buf,int32 in_bytes)247 static int output_data(char *buf, int32 in_bytes)
248 {
249     OSStatus		err = 0;
250     int         next_nextbuf, out_bytes;
251     int         inBytesPerQuant, max_quant, max_outbytes, out_quant, i;
252     float       maxLevel;
253 
254 
255     //quant  : 1 value
256     //packet : 1 pair of quant(stereo data)
257 
258     if (dpm.encoding & PE_16BIT){
259             inBytesPerQuant = 2;
260             maxLevel=32768.0;
261     }else if(dpm.encoding & PE_24BIT){
262             inBytesPerQuant = 3;
263             maxLevel=8388608.0;
264     }else{
265             ctl->cmsg(CMSG_ERROR, VERB_NORMAL,
266                 "Sorry, not support 8bit sound.");
267             exit(1);
268     }
269 
270     max_outbytes = globals.deviceBufferSize;
271     max_quant    = max_outbytes / sizeof(float);
272 
273 
274  redo:
275     out_quant = in_bytes/inBytesPerQuant;
276     out_bytes= out_quant * sizeof(float);
277     if( out_bytes > max_outbytes ){
278         out_bytes = max_outbytes;
279     }
280     out_quant = out_bytes/sizeof(float);
281     out_quant &=0xfffffffe;  //trunc to eaven number for stereo
282     out_bytes = out_quant*sizeof(float);
283 
284     next_nextbuf = globals.nextBuf+1;
285     next_nextbuf %= BUFNUM;
286 
287     while( globals.currBuf==next_nextbuf ){ //queue full
288         usleep(100000); //0.1sec
289     }
290 
291     switch( inBytesPerQuant ){
292 
293       case 2:
294         for( i=0; i<out_quant; i++){
295             ((float*)(globals.buffer[globals.nextBuf]))[i] =
296                               ((short*)buf)[i]/maxLevel;
297         }
298 	   break;
299 
300       case 3:
301 	    for( i=0; i<out_quant; i++ ){
302 	              ((float*)(globals.buffer[globals.nextBuf]))[i] =
303                              (*(int32*)&buf[i*3]>>8) / maxLevel;
304 	    }
305 	    break;
306     }
307 
308     globals.buffer_len[globals.nextBuf] = out_bytes;
309 
310     if( globals.soundPlaying == 0){
311       err = AudioDeviceStart(globals.device, appIOProc);
312       if (err != 0) goto Bail;
313                         // start playing sound through the device
314       globals.soundPlaying = 1;   // set the playing status global to true
315 
316     }
317 
318 
319 
320     globals.nextBuf = next_nextbuf;
321 
322 
323     in_bytes -= out_quant*inBytesPerQuant;
324     buf += out_quant*inBytesPerQuant;
325     mac_buf_using_num++;
326     globals.get_samples += out_bytes/globals.deviceFormat.mBytesPerPacket;
327 
328     if( in_bytes ){
329         goto redo;
330     }
331 
332  Bail:
333     return (err);
334 }
335 
336 /*********************************************************************/
close_output(void)337 static void close_output(void)
338 {
339     OSStatus 	err = 0;
340 
341     err = AudioDeviceStop(globals.device, appIOProc);
342                             // stop playing sound through the device
343     if (err != 0) goto Bail;
344 
345     err = AudioDeviceRemoveIOProc(globals.device, appIOProc);
346                        // remove the IO proc from the device
347     if (err != 0) goto Bail;
348 
349     globals.soundPlaying = 0;
350                     // set the playing status global to false
351 Bail:;
352 }
353 
354 /*********************************************************************/
acntl(int request,void * arg)355 static int acntl(int request, void *arg)
356 {
357     switch (request){
358       case PM_REQ_GETFRAGSIZ:
359 	if( dpm.encoding & PE_24BIT ){
360 	  *((int *)arg) = 3072;
361 	}else{
362 	  *((int *)arg) = BUFLEN;
363 	}
364 	return 0;
365 
366 
367     case PM_REQ_GETSAMPLES:
368           *((int *)arg) = globals.samples;
369           return 0;
370 
371     case PM_REQ_DISCARD:
372           AudioDeviceStop(globals.device, appIOProc);
373 	  init_variable();
374           return 0;
375 
376     case PM_REQ_PLAY_START:
377 	init_variable();
378 	return 0;
379 
380     case PM_REQ_FLUSH:
381     case PM_REQ_OUTPUT_FINISH:
382         while( globals.soundPlaying ){
383 	    trace_loop();
384 	    usleep(1000);
385 	}
386 	init_variable();
387 	return 0;
388 
389     default:
390         break;
391     }
392     return -1;
393 }
394 
395