Separated several classes into header files from tool main.cpp
[synfig.git] / synfig-osx / launcher / quartz-audio.c
1 //
2 // QuartzAudio.m
3 //
4 // X Window bell support using CoreAudio or AppKit.
5 // Greg Parker  gparker@cs.stanford.edu  19 Feb 2001
6 //
7 // Info about sine wave sound playback:
8 // CoreAudio code derived from macosx-dev posting by Tim Wood
9 //  http://www.omnigroup.com/mailman/archive/macosx-dev/2000-May/002004.html
10 // Smoothing transitions between sounds
11 //  http://www.wam.umd.edu/~mphoenix/dss/dss.html
12 //
13 /*
14  * Copyright (c) 2001 Greg Parker. All Rights Reserved.
15  *
16  * Permission is hereby granted, free of charge, to any person obtaining a
17  * copy of this software and associated documentation files (the "Software"),
18  * to deal in the Software without restriction, including without limitation
19  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
20  * and/or sell copies of the Software, and to permit persons to whom the
21  * Software is furnished to do so, subject to the following conditions:
22  *
23  * The above copyright notice and this permission notice shall be included in
24  * all copies or substantial portions of the Software.
25  *
26  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
29  * THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
30  * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
31  * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32  * DEALINGS IN THE SOFTWARE.
33  *
34  * Except as contained in this notice, the name(s) of the above copyright
35  * holders shall not be used in advertising or otherwise to promote the sale,
36  * use or other dealings in this Software without prior written authorization.
37  */
38 /* $XFree86: xc/programs/Xserver/hw/darwin/quartz/quartzAudio.c,v 1.1 2002/03/28 02:21:18 torrey Exp $ */
39
40 #include "quartz.h"
41 #include "quartz-audio.h"
42
43 #include <CoreAudio/CoreAudio.h>
44 #include <pthread.h>
45
46 #include "inputstr.h"
47 #include "extensions/XI.h"
48
49 void NSBeep();
50
51 typedef struct QuartzAudioRec {
52     double frequency;
53     double amplitude;
54
55     UInt32 curFrame;
56     UInt32 remainingFrames;
57     UInt32 totalFrames;
58     UInt32 bytesPerFrame;
59     double sampleRate;
60     UInt32 fadeLength;
61
62     UInt32 bufferByteCount;
63     Boolean playing;
64     pthread_mutex_t lock;
65
66     // used to fade out interrupted sound and avoid 'pop'
67     double prevFrequency;
68     double prevAmplitude;
69     UInt32 prevFrame; 
70 } QuartzAudioRec;
71
72 static AudioDeviceID quartzAudioDevice = kAudioDeviceUnknown;
73 static QuartzAudioRec data;
74
75
76 /*
77  * QuartzAudioEnvelope
78  *  Fade sound in and out to avoid pop.
79  *  Sounds with shorter duration will never reach full amplitude. Deal.
80  */
81 static double QuartzAudioEnvelope(
82     UInt32 curFrame,
83     UInt32 totalFrames,
84     UInt32 fadeLength )
85 {
86     double fadeFrames = min(fadeLength, totalFrames / 2);
87     if (fadeFrames < 1) return 0;
88
89     if (curFrame < fadeFrames) {
90         return curFrame / fadeFrames;
91     } else if (curFrame > totalFrames - fadeFrames) {
92         return (totalFrames-curFrame) / fadeFrames;
93     } else {
94         return 1.0;
95     }
96 }
97
98
99 /*
100  * QuartzFillBuffer
101  *  Fill this buffer with data and update the data position.
102  *  FIXME: this is ugly
103  */
104 static void QuartzFillBuffer(
105     AudioBuffer *audiobuffer,
106     QuartzAudioRec *data )
107 {
108     float *buffer, *b;
109     unsigned int frame, frameCount;
110     unsigned int bufferFrameCount;
111     float multiplier, v;
112     int i;
113
114     buffer = (float *)audiobuffer->mData;
115     bufferFrameCount = audiobuffer->mDataByteSize / data->bytesPerFrame;
116
117     frameCount = min(bufferFrameCount, data->remainingFrames);
118
119     // Fade out previous sine wave, if any.
120     b = buffer;
121     if (data->prevFrame) {
122         multiplier = 2*M_PI*(data->prevFrequency/data->sampleRate);
123         for (frame = 0; frame < data->fadeLength; frame++) {
124             v = data->prevAmplitude *
125                 QuartzAudioEnvelope(frame+data->fadeLength,
126                                     2*data->fadeLength,
127                                     data->fadeLength) *
128                 sin(multiplier * (data->prevFrame+frame));
129             for (i = 0; i < audiobuffer->mNumberChannels; i++) {
130                 *b++ = v;
131             }
132         }
133         // no more prev fade
134         data->prevFrame = 0;
135
136         // adjust for space eaten by prev fade
137         buffer += audiobuffer->mNumberChannels*frame;
138         bufferFrameCount -= frame;
139         frameCount = min(bufferFrameCount, data->remainingFrames);
140     }
141
142     // Write a sine wave with the specified frequency and amplitude
143     multiplier = 2*M_PI*(data->frequency/data->sampleRate);
144     for (frame = 0; frame < frameCount; frame++) {
145         v = data->amplitude * 
146             QuartzAudioEnvelope(data->curFrame+frame, data->totalFrames,
147                                 data->fadeLength) *
148             sin(multiplier * (data->curFrame+frame));
149         for (i = 0; i < audiobuffer->mNumberChannels; i++) {
150             *b++ = v;
151         }
152     }
153
154     // Zero out the rest of the buffer, if any
155     memset(b, 0, sizeof(float) * audiobuffer->mNumberChannels *
156            (bufferFrameCount-frame));
157
158     data->curFrame += frameCount;
159     data->remainingFrames -= frameCount;
160     if (data->remainingFrames == 0) {
161         data->playing = FALSE;
162         data->curFrame = 0;
163     }
164 }
165
166
167 /*
168  * QuartzAudioIOProc
169  *  Callback function for audio playback.
170  *  FIXME: use inOutputTime to correct for skipping
171  */
172 static OSStatus 
173 QuartzAudioIOProc(
174     AudioDeviceID inDevice, 
175     const AudioTimeStamp *inNow, 
176     const AudioBufferList *inInputData, 
177     const AudioTimeStamp *inInputTime, 
178     AudioBufferList *outOutputData, 
179     const AudioTimeStamp *inOutputTime, 
180     void *inClientData )
181 {
182     QuartzAudioRec *data = (QuartzAudioRec *)inClientData;
183     int i;
184     Boolean wasPlaying;
185
186     pthread_mutex_lock(&data->lock);
187     wasPlaying = data->playing;
188     for (i = 0; i < outOutputData->mNumberBuffers; i++) {
189         if (data->playing) {
190             QuartzFillBuffer(outOutputData->mBuffers+i, data); 
191         }
192         else {
193             memset(outOutputData->mBuffers[i].mData, 0, 
194                    outOutputData->mBuffers[i].mDataByteSize);
195         }
196     }
197     if (wasPlaying  &&  !data->playing) {
198         OSStatus err;
199         err = AudioDeviceStop(inDevice, QuartzAudioIOProc);
200     }
201     pthread_mutex_unlock(&data->lock);
202     return 0;
203 }
204
205
206 /*
207  * QuartzCoreAudioBell
208  *  Play a tone using the CoreAudio API
209  */
210 static void QuartzCoreAudioBell(
211     int volume,         // volume is % of max
212     int pitch,          // pitch is Hz
213     int duration )      // duration is milliseconds
214 {
215     if (quartzAudioDevice == kAudioDeviceUnknown) return;
216
217     pthread_mutex_lock(&data.lock);
218
219     // fade previous sound, if any
220     data.prevFrequency = data.frequency;
221     data.prevAmplitude = data.amplitude;
222     data.prevFrame = data.curFrame;
223
224     // set new sound
225     data.frequency = pitch;
226     data.amplitude = volume / 100.0;
227     data.curFrame = 0;
228     data.totalFrames = (int)(data.sampleRate * duration / 1000.0);
229     data.remainingFrames = data.totalFrames;
230
231     if (! data.playing) {
232         OSStatus status;
233         status = AudioDeviceStart(quartzAudioDevice, QuartzAudioIOProc);
234         if (status) {
235             ErrorF("QuartzAudioBell: AudioDeviceStart returned %d\n", status);
236         } else {
237             data.playing = TRUE;
238         }
239     }
240     pthread_mutex_unlock(&data.lock);
241 }
242
243
244 /*
245  * QuartzBell
246  *  Ring the bell
247  */
248 void QuartzBell(
249     int volume,             // volume in percent of max
250     DeviceIntPtr pDevice,
251     pointer ctrl,
252     int class )
253 {
254     int pitch;              // pitch in Hz
255     int duration;           // duration in milliseconds
256
257     if (class == BellFeedbackClass) {
258         pitch = ((BellCtrl*)ctrl)->pitch;
259         duration = ((BellCtrl*)ctrl)->duration;
260     } else if (class == KbdFeedbackClass) {
261         pitch = ((KeybdCtrl*)ctrl)->bell_pitch;
262         duration = ((KeybdCtrl*)ctrl)->bell_duration;    
263     } else {
264         ErrorF("QuartzBell: bad bell class %d\n", class);
265         return;
266     }
267
268     if (quartzUseSysBeep) {
269         if (volume)
270             NSBeep();
271     } else {
272         QuartzCoreAudioBell(volume, pitch, duration);
273     }
274 }
275
276
277 /*
278  * QuartzAudioInit
279  *  Prepare to play the bell with the CoreAudio API
280  */
281 void QuartzAudioInit(void) 
282 {
283     UInt32 propertySize;
284     OSStatus status;
285     AudioDeviceID outputDevice;
286     AudioStreamBasicDescription outputStreamDescription;
287     double sampleRate;
288
289     // Get the default output device
290     propertySize = sizeof(outputDevice);
291     status = AudioHardwareGetProperty(
292                     kAudioHardwarePropertyDefaultOutputDevice, 
293                     &propertySize, &outputDevice);
294     if (status) {
295         ErrorF("QuartzAudioInit: AudioHardwareGetProperty returned %d\n",
296                status);
297         return;
298     }
299     if (outputDevice == kAudioDeviceUnknown) {
300         ErrorF("QuartzAudioInit: No audio output devices available.\n");
301         return;
302     }
303
304     // Get the basic device description
305     propertySize = sizeof(outputStreamDescription);
306     status = AudioDeviceGetProperty(outputDevice, 0, FALSE, 
307                                     kAudioDevicePropertyStreamFormat, 
308                                     &propertySize, &outputStreamDescription);
309     if (status) {
310         ErrorF("QuartzAudioInit: GetProperty(stream format) returned %d\n",
311                status);
312         return;
313     }
314     sampleRate = outputStreamDescription.mSampleRate;
315
316     // Fill in the playback data
317     data.frequency = 0;
318     data.amplitude = 0;
319     data.curFrame = 0;
320     data.remainingFrames = 0; 
321     data.bytesPerFrame = outputStreamDescription.mBytesPerFrame;
322     data.sampleRate = sampleRate;
323     // data.bufferByteCount = bufferByteCount;
324     data.playing = FALSE;
325     data.prevAmplitude = 0;
326     data.prevFrame = 0;
327     data.prevFrequency = 0;
328     data.fadeLength = data.sampleRate / 200;
329     pthread_mutex_init(&data.lock, NULL); // fixme error check
330
331     // fixme assert fadeLength<framesPerBuffer
332
333     // Prepare for playback
334     status = AudioDeviceAddIOProc(outputDevice, QuartzAudioIOProc, &data);
335     if (status) {
336         ErrorF("QuartzAudioInit: AddIOProc returned %d\n", status);
337         return;
338     }
339
340     // success!
341     quartzAudioDevice = outputDevice;
342 }