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 */
QuartzAudioEnvelope(UInt32 curFrame,UInt32 totalFrames,UInt32 fadeLength)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 */
QuartzFillBuffer(AudioBuffer * audiobuffer,QuartzAudioRec * data)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
QuartzAudioIOProc(AudioDeviceID inDevice,const AudioTimeStamp * inNow,const AudioBufferList * inInputData,const AudioTimeStamp * inInputTime,AudioBufferList * outOutputData,const AudioTimeStamp * inOutputTime,void * inClientData)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 */
QuartzCoreAudioBell(int volume,int pitch,int duration)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 */
QuartzBell(int volume,DeviceIntPtr pDevice,pointer ctrl,int class)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 */
QuartzAudioInit(void)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 }
343