1/*
2 * Copyright (C) 2003-2004 Kare Sjolander <kare@speech.kth.se>
3 *
4 * This file is part of the Snack Sound Toolkit.
5 * The latest version can be found at http://www.speech.kth.se/snack/
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
22#include "tcl.h"
23#include "jkAudIO.h"
24#include <stdio.h>
25#include <fcntl.h>
26#include <unistd.h>
27#include <CoreServices/CoreServices.h>
28#include <CoreAudio/AudioHardware.h>
29
30extern void Snack_WriteLog(char *s);
31extern void Snack_WriteLogInt(char *s, int n);
32
33#ifndef min
34#define min(a,b) ((a)<(b)?(a):(b))
35#define max(a,b) ((a)>(b)?(a):(b))
36#endif
37
38#define SNACK_NUMBER_MIXERS 1
39
40struct MixerLink mixerLinks[SNACK_NUMBER_MIXERS][2];
41
42#define BUFLEN (44100*2)
43static short otmp[BUFLEN];
44static float itmp[BUFLEN];
45static int usageCount = 0;
46static float rate;
47static ADesc *AO = NULL;
48static ADesc *AI = NULL;
49
50OSStatus
51appIOProc(AudioDeviceID inDevice, const AudioTimeStamp* inNow,
52	 const AudioBufferList* inInputData, const AudioTimeStamp* inInputTime,
53	  AudioBufferList* outOutputData, const AudioTimeStamp* inOutputTime,
54	  void* adesc)
55{
56  ADesc *A = adesc;
57  int i;
58  int numFrames = A->deviceBufferSize / A->deviceFormat.mBytesPerFrame;
59  float *out = outOutputData->mBuffers[0].mData;
60  float *in  = inInputData->mBuffers[0].mData;
61
62  /*  printf("w %d r %d  %d %d\n", A->wpos, A->rpos, numFrames*2, A->nChannels);*/
63  if (AO != NULL && AO->mode == PLAY) {
64    for (i = 0; i < numFrames*A->nChannels; ++i) {
65      *out++ = (float) (otmp[(A->rpos*A->nChannels + i) % BUFLEN] / 32768.0);
66    if (A->nChannels == 1) {
67      *out++ = (float) (otmp[(A->rpos*A->nChannels + i) % BUFLEN] / 32768.0);
68    }
69    }
70    A->rpos = (A->rpos + numFrames) % (BUFLEN/A->nChannels);
71  }
72  /*  printf("appIOProc wpos %d   %d %d\n",
73      A->wpos,numFrames,inInputData->mNumberBuffers);*/
74  if (AI != NULL && AI->mode == RECORD) {
75    if (A->wpos + numFrames < BUFLEN/2) {
76      memcpy(&itmp[A->wpos*2], in, numFrames*2*sizeof(float));
77      A->wpos += numFrames;
78    } else {
79      memcpy(&itmp[A->wpos*2], in, (BUFLEN/2 - A->wpos)*2*sizeof(float));
80      memcpy(itmp, &in[(BUFLEN/2 - A->wpos)*2],
81	     (numFrames-(BUFLEN/2-A->wpos))*2*sizeof(float));
82      A->wpos = (A->wpos + numFrames) % (BUFLEN/2);
83    }
84    A->tot += numFrames;
85  }
86
87  return (kAudioHardwareNoError);
88}
89
90int
91SnackAudioOpen(ADesc *A, Tcl_Interp *interp, char *device, int mode, int freq,
92	       int nchannels, int encoding)
93{
94  OSStatus err = kAudioHardwareNoError;
95  UInt32 count = sizeof(AudioDeviceID);
96  UInt32 bufferSize;
97  AudioStreamBasicDescription format;
98
99  if (mode == PLAY) {
100    AO = A;
101    A->time = SnackCurrentTime();
102    A->wpos = 0;
103    A->rpos = 0;
104    A->tot = 0;
105  } else {
106    AI = A;
107    A->rpos = 0;
108    A->wpos = 0;
109    A->tot = 0;
110  }
111  if (usageCount == 1) {
112    usageCount = 2;
113    return TCL_OK;
114  }
115
116  err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
117				 &count, (void *) &A->device);
118
119  count = sizeof(bufferSize);
120  err = AudioDeviceGetProperty(A->device, 0, false,
121			       kAudioDevicePropertyBufferSize,
122			       &count, &bufferSize);
123
124  count = sizeof(format);
125  err = AudioDeviceGetProperty(A->device, 0, false,
126			       kAudioDevicePropertyStreamFormat,
127			       &count, &format);
128
129  if ((err = AudioDeviceAddIOProc(A->device, appIOProc, (void *)A))
130      != -kAudioHardwareNoError) {
131    Tcl_AppendResult(interp, "AudioDeviceAddIOProc failed", NULL);
132    return TCL_ERROR;
133  }
134
135  if ((err = AudioDeviceStart(A->device, appIOProc))
136      != -kAudioHardwareNoError) {
137    Tcl_AppendResult(interp, "AudioDeviceStart failed", NULL);
138    return TCL_ERROR;
139  }
140
141  A->deviceBufferSize = bufferSize;
142  A->deviceFormat.mBytesPerFrame = format.mBytesPerFrame;
143  A->nChannels = nchannels;
144  A->mode = mode;
145  A->encoding = encoding;
146  rate = (float) freq;
147  usageCount = 1;
148
149  switch (encoding) {
150  case LIN24:
151    A->bytesPerSample = sizeof(int);
152    break;
153  case LIN16:
154    A->bytesPerSample = sizeof(short);
155    break;
156  }
157
158  return TCL_OK;
159}
160
161int
162SnackAudioClose(ADesc *A)
163{
164  OSStatus err = kAudioHardwareNoError;
165
166  if (A->mode == PLAY) {
167    AO = NULL;
168  } else {
169    AI = NULL;
170  }
171  if (usageCount == 2) {
172    usageCount = 1;
173    return(0);
174  }
175  usageCount = 0;
176
177  /*  printf("SnackAudioClose\n");*/
178
179  if ((err = AudioDeviceStop(A->device, appIOProc))
180      != -kAudioHardwareNoError) {
181    /*    printf("AudioDeviceStop failed\n");*/
182    return TCL_ERROR;
183  }
184
185  if ((err = AudioDeviceRemoveIOProc(A->device, appIOProc))
186      != -kAudioHardwareNoError) {
187    /*    printf("AudioDeviceRemoveIOProc failed\n");*/
188    return TCL_ERROR;
189  }
190
191  return(0);
192}
193
194long
195SnackAudioPause(ADesc *A)
196{
197  return(-1);
198}
199
200void
201SnackAudioResume(ADesc *A)
202{
203}
204
205void
206SnackAudioFlush(ADesc *A)
207{
208}
209
210void
211SnackAudioPost(ADesc *A)
212{
213  if (A->mode == PLAY) {
214    int i;
215
216    for (i = A->wpos*A->nChannels; i < BUFLEN; i++) otmp[i] = 0;
217    for (i = 0; i < A->rpos*A->nChannels; i++) otmp[i] = 0;
218  }
219}
220
221int
222SnackAudioRead(ADesc *A, void *buf, int nFrames)
223{
224  int i, c;
225  float frac = rate / 44100.0f;
226  int tot = (int) (frac * A->tot);
227
228  /*  printf("SnackAudioRead frames %d rpos %d tot %d\n", nFrames, A->rpos, A->tot);*/
229
230  if (nFrames > tot) {
231    nFrames = tot;
232  }
233  for (c = 0; c < A->nChannels; c++) {
234    for (i = 0; i < nFrames; i++) {
235      int ij, pos;
236      float smp1 = 0.0, smp2, f, dj;
237
238      dj = i / frac;
239      ij = (int) dj;
240      f = dj - ij;
241      pos = ij * 2 + c;
242      switch (A->encoding) {
243      case LIN24:
244      case LIN24PACKED:
245	smp1 = (8388607.0*itmp[(A->rpos*2 + pos) % (BUFLEN)]);
246	smp2 = (8388607.0*itmp[(A->rpos*2 + pos + A->nChannels)%(BUFLEN)]);
247	((int *)buf)[i * A->nChannels + c] = smp1 * (1.0f - f) + smp2 * f;
248	break;
249      case LIN32:
250      case SNACK_FLOAT:
251	smp1 = (2147483647.0*itmp[(A->rpos*2 + pos) % (BUFLEN)]);
252	smp2 = (2147483647.0*itmp[(A->rpos*2 + pos + A->nChannels)%(BUFLEN)]);
253	((int *)buf)[i * A->nChannels + c] = smp1 * (1.0f - f) + smp2 * f;
254	break;
255      case LIN16:
256      case MULAW:
257      case ALAW:
258	smp1 = (short) (32767.0*itmp[(A->rpos*2 + pos) % (BUFLEN)]);
259	smp2 = (short) (32767.0*itmp[(A->rpos*2 + pos + A->nChannels)%(BUFLEN)]);
260	((short *)buf)[i * A->nChannels + c] = smp1 * (1.0f - f) + smp2 * f;
261	break;
262      }
263    }
264  }
265  A->rpos = (A->rpos + (int)(nFrames/frac)) % (BUFLEN/2);
266  A->tot -= (int) (nFrames/frac);
267
268  return(nFrames);
269}
270
271int
272SnackAudioWrite(ADesc *A, void *buf, int nFrames)
273{
274  /*  printf("SnackAudioWrite %d frames %d (%d %d)\n", A->wpos, nFrames,nFrames*4,&otmp[A->wpos*2]);*/
275
276  if (A->wpos + nFrames < BUFLEN/A->nChannels) {
277    memcpy(&otmp[A->wpos*A->nChannels], buf, nFrames*A->nChannels*2);
278    A->wpos += nFrames;
279  } else {
280    memcpy(&otmp[A->wpos*A->nChannels], buf, (BUFLEN/A->nChannels - A->wpos)*A->nChannels*2);
281    memcpy(otmp, &((short *)buf)[(BUFLEN/A->nChannels - A->wpos)*A->nChannels],(nFrames-(BUFLEN/A->nChannels-A->wpos))*A->nChannels*2);
282    A->wpos = (A->wpos + nFrames) % (BUFLEN/A->nChannels);
283  }
284
285  return(nFrames);
286}
287
288int
289SnackAudioReadable(ADesc *A)
290{
291  return((int) (A->tot * rate / 44100.0f));
292}
293
294int
295SnackAudioWriteable(ADesc *A)
296{
297  return -1;
298}
299
300long
301SnackAudioPlayed(ADesc *A)
302{
303  long res;
304
305  res = (int) (44100 * (SnackCurrentTime() - A->time) +.5);
306
307  /*  printf("SnackAudioPlayed %d\n", res);*/
308
309  return(res);
310}
311
312void
313SnackAudioInit()
314{
315  /*
316  OSStatus  err = noErr;
317  UInt32 count, bufferSize;
318  AudioDeviceID	device = kAudioDeviceUnknown;
319  AudioStreamBasicDescription format;
320
321  count = sizeof(device);
322  err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultOutputDevice,
323				 &count, (void *) &device);
324  fprintf(stderr, "kAudioHardwarePropertyDefaultOutputDevice %d\n", err);
325  if (err != noErr) goto Bail;
326
327  count = sizeof(bufferSize);
328  err = AudioDeviceGetProperty(device, 0, false,
329			       kAudioDevicePropertyBufferSize,
330			       &count, &bufferSize);
331  fprintf(stderr, "kAudioDevicePropertyBufferSize %d %d\n", err, bufferSize);
332  if (err != noErr) goto Bail;
333
334  count = sizeof(format);
335  err = AudioDeviceGetProperty(device, 0, false,
336			       kAudioDevicePropertyStreamFormat,
337			       &count, &format);
338  fprintf(stderr, "kAudioDevicePropertyStreamFormat %d\n", err);
339  fprintf(stderr, "sampleRate %g\n", format.mSampleRate);
340  fprintf(stderr, "mFormatFlags %08X\n", format.mFormatFlags);
341  fprintf(stderr, "mBytesPerPacket %d\n", format.mBytesPerPacket);
342  fprintf(stderr, "mFramesPerPacket %d\n", format.mFramesPerPacket);
343  fprintf(stderr, "mChannelsPerFrame %d\n", format.mChannelsPerFrame);
344  fprintf(stderr, "mBytesPerFrame %d\n", format.mBytesPerFrame);
345  fprintf(stderr, "mBitsPerChannel %d\n", format.mBitsPerChannel);
346  fprintf(stderr, "mFormatID !=  %d %d %d\n", format.mFormatID != kAudioFormatLinearPCM, format.mFormatID, kAudioFormatLinearPCM);
347  if (err != kAudioHardwareNoError) goto Bail;*/
348  /*  FailWithAction(format.mFormatID != kAudioFormatLinearPCM, err = paramErr, Bail);*/
349  /*
350  memset(&format, 0, sizeof(AudioStreamBasicDescription));
351  format.mSampleRate = 44100.0;
352  err = AudioDeviceSetProperty(device, 0, 0, 0,
353			       kAudioDevicePropertyStreamFormat,
354			       sizeof(format), &format);
355  fprintf(stderr, "kAudioDevicePropertyStreamFormat %d\n", err);
356
357  memset(&format, 0, sizeof(AudioStreamBasicDescription));
358  format.mChannelsPerFrame = 2;
359  err = AudioDeviceSetProperty(device, 0, 0, 0,
360			       kAudioDevicePropertyStreamFormat,
361			       sizeof(format), &format);
362  fprintf(stderr, "kAudioDevicePropertyStreamFormat %d\n", err);
363  */
364  /*
365 Bail:
366 fprintf(stderr, "done\n");*/
367}
368
369void
370SnackAudioFree()
371{
372  int i, j;
373
374  for (i = 0; i < SNACK_NUMBER_MIXERS; i++) {
375    for (j = 0; j < 2; j++) {
376      if (mixerLinks[i][j].mixer != NULL) {
377	ckfree(mixerLinks[i][j].mixer);
378      }
379      if (mixerLinks[i][j].mixerVar != NULL) {
380	ckfree(mixerLinks[i][j].mixerVar);
381      }
382    }
383    if (mixerLinks[i][0].jack != NULL) {
384      ckfree(mixerLinks[i][0].jack);
385    }
386    if (mixerLinks[i][0].jackVar != NULL) {
387      ckfree((char *)mixerLinks[i][0].jackVar);
388    }
389  }
390}
391
392void
393ASetRecGain(int gain)
394{
395  int g = min(max(gain, 0), 100);
396}
397
398void
399ASetPlayGain(int gain)
400{
401  int g = min(max(gain, 0), 100);
402}
403
404int
405AGetRecGain()
406{
407  int g = 0;
408
409  return(g);
410}
411
412int
413AGetPlayGain()
414{
415  int g = 0;
416
417  return(g);
418}
419
420int
421SnackAudioGetEncodings(char *device)
422{
423  return(LIN16);
424}
425
426void
427SnackAudioGetRates(char *device, char *buf, int n)
428{
429  strncpy(buf, "8000 11025 16000 22050 32000 44100 48000 96000", n);
430  buf[n-1] = '\0';
431}
432
433int
434SnackAudioMaxNumberChannels(char *device)
435{
436  return(64);
437}
438
439int
440SnackAudioMinNumberChannels(char *device)
441{
442  return(1);
443}
444
445void
446SnackMixerGetInputJackLabels(char *buf, int n)
447{
448  buf[0] = '\0';
449}
450
451void
452SnackMixerGetOutputJackLabels(char *buf, int n)
453{
454  buf[0] = '\0';
455}
456
457void
458SnackMixerGetInputJack(char *buf, int n)
459{
460  buf[0] = '\0';
461}
462
463int
464SnackMixerSetInputJack(Tcl_Interp *interp, char *jack, CONST84 char *status)
465{
466  return 1;
467}
468
469void
470SnackMixerGetOutputJack(char *buf, int n)
471{
472  buf[0] = '\0';
473}
474
475void
476SnackMixerSetOutputJack(char *jack, char *status)
477{
478}
479
480void
481SnackMixerGetChannelLabels(char *line, char *buf, int n)
482{
483  strncpy(buf, "Mono", n);
484  buf[n-1] = '\0';
485}
486
487void
488SnackMixerGetVolume(char *line, int channel, char *buf, int n)
489{
490  if (strncasecmp(line, "Play", strlen(line)) == 0) {
491    sprintf(buf, "%d", AGetPlayGain());
492  }
493}
494
495void
496SnackMixerSetVolume(char *line, int channel, int volume)
497{
498  if (strncasecmp(line, "Play", strlen(line)) == 0) {
499    ASetPlayGain(volume);
500  }
501}
502
503void
504SnackMixerLinkJacks(Tcl_Interp *interp, char *jack, Tcl_Obj *var)
505{
506}
507
508static char *
509VolumeVarProc(ClientData clientData, Tcl_Interp *interp, CONST84 char *name1,
510	      CONST84 char *name2, int flags)
511{
512  MixerLink *mixLink = (MixerLink *) clientData;
513  CONST84 char *stringValue;
514
515  if (flags & TCL_TRACE_UNSETS) {
516    if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
517      Tcl_Obj *obj, *var;
518      char tmp[VOLBUFSIZE];
519
520      SnackMixerGetVolume(mixLink->mixer, mixLink->channel, tmp, VOLBUFSIZE);
521      obj = Tcl_NewIntObj(atoi(tmp));
522      var = Tcl_NewStringObj(mixLink->mixerVar, -1);
523      Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
524      Tcl_TraceVar(interp, mixLink->mixerVar,
525		   TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
526		   VolumeVarProc, (int *)mixLink);
527    }
528    return (char *) NULL;
529  }
530  stringValue = Tcl_GetVar(interp, mixLink->mixerVar, TCL_GLOBAL_ONLY);
531  if (stringValue != NULL) {
532    SnackMixerSetVolume(mixLink->mixer, mixLink->channel, atoi(stringValue));
533  }
534
535  return (char *) NULL;
536}
537
538void
539SnackMixerLinkVolume(Tcl_Interp *interp, char *line, int n,
540		     Tcl_Obj *CONST objv[])
541{
542  char *mixLabels[] = { "Play" };
543  int i, j, channel;
544  CONST84 char *value;
545  char tmp[VOLBUFSIZE];
546
547  for (i = 0; i < SNACK_NUMBER_MIXERS; i++) {
548    if (strncasecmp(line, mixLabels[i], strlen(line)) == 0) {
549      for (j = 0; j < n; j++) {
550	if (n == 1) {
551	  channel = -1;
552	} else {
553	  channel = j;
554	}
555	mixerLinks[i][j].mixer = (char *)SnackStrDup(line);
556	mixerLinks[i][j].mixerVar = (char *)SnackStrDup(Tcl_GetStringFromObj(objv[j+3], NULL));
557	mixerLinks[i][j].channel = j;
558	value = Tcl_GetVar(interp, mixerLinks[i][j].mixerVar, TCL_GLOBAL_ONLY);
559	if (value != NULL) {
560	  SnackMixerSetVolume(line, channel, atoi(value));
561	} else {
562	  Tcl_Obj *obj;
563	  SnackMixerGetVolume(line, channel, tmp, VOLBUFSIZE);
564	  obj = Tcl_NewIntObj(atoi(tmp));
565	  Tcl_ObjSetVar2(interp, objv[j+3], NULL, obj,
566			 TCL_GLOBAL_ONLY | TCL_PARSE_PART1);
567	}
568	Tcl_TraceVar(interp, mixerLinks[i][j].mixerVar,
569		     TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
570		     VolumeVarProc, (ClientData) &mixerLinks[i][j]);
571      }
572    }
573  }
574}
575
576void
577SnackMixerUpdateVars(Tcl_Interp *interp)
578{
579  int i, j;
580  char tmp[VOLBUFSIZE];
581  Tcl_Obj *obj, *var;
582
583  for (i = 0; i < SNACK_NUMBER_MIXERS; i++) {
584    for (j = 0; j < 2; j++) {
585      if (mixerLinks[i][j].mixerVar != NULL) {
586	SnackMixerGetVolume(mixerLinks[i][j].mixer, mixerLinks[i][j].channel,
587			    tmp, VOLBUFSIZE);
588	obj = Tcl_NewIntObj(atoi(tmp));
589	var = Tcl_NewStringObj(mixerLinks[i][j].mixerVar, -1);
590	Tcl_ObjSetVar2(interp, var, NULL, obj, TCL_GLOBAL_ONLY|TCL_PARSE_PART1);
591      }
592    }
593  }
594}
595
596void
597SnackMixerGetLineLabels(char *buf, int n)
598{
599  strncpy(buf, "Play", n);
600  buf[n-1] = '\0';
601}
602
603int
604SnackGetOutputDevices(char **arr, int n)
605{
606  arr[0] = (char *) SnackStrDup("default");
607
608  return 1;
609}
610
611int
612SnackGetInputDevices(char **arr, int n)
613{
614  arr[0] = (char *) SnackStrDup("default");
615
616  return 1;
617}
618
619int
620SnackGetMixerDevices(char **arr, int n)
621{
622  arr[0] = (char *) SnackStrDup("default");
623
624  return 1;
625}
626