4 // X Window bell support using CoreAudio or AppKit.
5 // Greg Parker gparker@cs.stanford.edu 19 Feb 2001
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
14 * Copyright (c) 2001 Greg Parker. All Rights Reserved.
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:
23 * The above copyright notice and this permission notice shall be included in
24 * all copies or substantial portions of the Software.
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.
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.
38 #ifdef HAVE_XORG_CONFIG_H
39 #include <xorg-config.h>
41 #include "quartzCommon.h"
42 #include "quartzAudio.h"
44 #include <CoreAudio/CoreAudio.h>
48 #include <X11/extensions/XI.h>
53 typedef struct QuartzAudioRec
{
58 UInt32 remainingFrames
;
64 UInt32 bufferByteCount
;
68 // used to fade out interrupted sound and avoid 'pop'
74 static AudioDeviceID quartzAudioDevice
= kAudioDeviceUnknown
;
75 static QuartzAudioRec data
;
80 * Fade sound in and out to avoid pop.
81 * Sounds with shorter duration will never reach full amplitude. Deal.
83 static double QuartzAudioEnvelope(
88 double fadeFrames
= min(fadeLength
, totalFrames
/ 2);
89 if (fadeFrames
< 1) return 0;
91 if (curFrame
< fadeFrames
) {
92 return curFrame
/ fadeFrames
;
93 } else if (curFrame
> totalFrames
- fadeFrames
) {
94 return (totalFrames
-curFrame
) / fadeFrames
;
103 * Fill this buffer with data and update the data position.
104 * FIXME: this is ugly
106 static void QuartzFillBuffer(
107 AudioBuffer
*audiobuffer
,
108 QuartzAudioRec
*data
)
111 unsigned int frame
, frameCount
;
112 unsigned int bufferFrameCount
;
116 buffer
= (float *)audiobuffer
->mData
;
117 bufferFrameCount
= audiobuffer
->mDataByteSize
/ data
->bytesPerFrame
;
119 frameCount
= min(bufferFrameCount
, data
->remainingFrames
);
121 // Fade out previous sine wave, if any.
123 if (data
->prevFrame
) {
124 multiplier
= 2*M_PI
*(data
->prevFrequency
/data
->sampleRate
);
125 for (frame
= 0; frame
< data
->fadeLength
; frame
++) {
126 v
= data
->prevAmplitude
*
127 QuartzAudioEnvelope(frame
+data
->fadeLength
,
130 sin(multiplier
* (data
->prevFrame
+frame
));
131 for (i
= 0; i
< audiobuffer
->mNumberChannels
; i
++) {
138 // adjust for space eaten by prev fade
139 buffer
+= audiobuffer
->mNumberChannels
*frame
;
140 bufferFrameCount
-= frame
;
141 frameCount
= min(bufferFrameCount
, data
->remainingFrames
);
144 // Write a sine wave with the specified frequency and amplitude
145 multiplier
= 2*M_PI
*(data
->frequency
/data
->sampleRate
);
146 for (frame
= 0; frame
< frameCount
; frame
++) {
147 v
= data
->amplitude
*
148 QuartzAudioEnvelope(data
->curFrame
+frame
, data
->totalFrames
,
150 sin(multiplier
* (data
->curFrame
+frame
));
151 for (i
= 0; i
< audiobuffer
->mNumberChannels
; i
++) {
156 // Zero out the rest of the buffer, if any
157 memset(b
, 0, sizeof(float) * audiobuffer
->mNumberChannels
*
158 (bufferFrameCount
-frame
));
160 data
->curFrame
+= frameCount
;
161 data
->remainingFrames
-= frameCount
;
162 if (data
->remainingFrames
== 0) {
163 data
->playing
= FALSE
;
171 * Callback function for audio playback.
172 * FIXME: use inOutputTime to correct for skipping
176 AudioDeviceID inDevice
,
177 const AudioTimeStamp
*inNow
,
178 const AudioBufferList
*inInputData
,
179 const AudioTimeStamp
*inInputTime
,
180 AudioBufferList
*outOutputData
,
181 const AudioTimeStamp
*inOutputTime
,
184 QuartzAudioRec
*data
= (QuartzAudioRec
*)inClientData
;
188 pthread_mutex_lock(&data
->lock
);
189 wasPlaying
= data
->playing
;
190 for (i
= 0; i
< outOutputData
->mNumberBuffers
; i
++) {
192 QuartzFillBuffer(outOutputData
->mBuffers
+i
, data
);
195 memset(outOutputData
->mBuffers
[i
].mData
, 0,
196 outOutputData
->mBuffers
[i
].mDataByteSize
);
199 if (wasPlaying
&& !data
->playing
) {
201 err
= AudioDeviceStop(inDevice
, QuartzAudioIOProc
);
203 pthread_mutex_unlock(&data
->lock
);
209 * QuartzCoreAudioBell
210 * Play a tone using the CoreAudio API
212 static void QuartzCoreAudioBell(
213 int volume
, // volume is % of max
214 int pitch
, // pitch is Hz
215 int duration
) // duration is milliseconds
217 if (quartzAudioDevice
== kAudioDeviceUnknown
) return;
219 pthread_mutex_lock(&data
.lock
);
221 // fade previous sound, if any
222 data
.prevFrequency
= data
.frequency
;
223 data
.prevAmplitude
= data
.amplitude
;
224 data
.prevFrame
= data
.curFrame
;
227 data
.frequency
= pitch
;
228 data
.amplitude
= volume
/ 100.0;
230 data
.totalFrames
= (int)(data
.sampleRate
* duration
/ 1000.0);
231 data
.remainingFrames
= data
.totalFrames
;
233 if (! data
.playing
) {
235 status
= AudioDeviceStart(quartzAudioDevice
, QuartzAudioIOProc
);
237 ErrorF("QuartzAudioBell: AudioDeviceStart returned %ld\n", status
);
242 pthread_mutex_unlock(&data
.lock
);
251 int volume
, // volume in percent of max
252 DeviceIntPtr pDevice
,
256 int pitch
; // pitch in Hz
257 int duration
; // duration in milliseconds
259 if (class == BellFeedbackClass
) {
260 pitch
= ((BellCtrl
*)ctrl
)->pitch
;
261 duration
= ((BellCtrl
*)ctrl
)->duration
;
262 } else if (class == KbdFeedbackClass
) {
263 pitch
= ((KeybdCtrl
*)ctrl
)->bell_pitch
;
264 duration
= ((KeybdCtrl
*)ctrl
)->bell_duration
;
266 ErrorF("QuartzBell: bad bell class %d\n", class);
270 if (quartzUseSysBeep
) {
274 QuartzCoreAudioBell(volume
, pitch
, duration
);
281 * Prepare to play the bell with the CoreAudio API
283 void QuartzAudioInit(void)
287 AudioDeviceID outputDevice
;
288 AudioStreamBasicDescription outputStreamDescription
;
291 // Get the default output device
292 propertySize
= sizeof(outputDevice
);
293 status
= AudioHardwareGetProperty(
294 kAudioHardwarePropertyDefaultOutputDevice
,
295 &propertySize
, &outputDevice
);
297 ErrorF("QuartzAudioInit: AudioHardwareGetProperty returned %ld\n",
301 if (outputDevice
== kAudioDeviceUnknown
) {
302 ErrorF("QuartzAudioInit: No audio output devices available.\n");
306 // Get the basic device description
307 propertySize
= sizeof(outputStreamDescription
);
308 status
= AudioDeviceGetProperty(outputDevice
, 0, FALSE
,
309 kAudioDevicePropertyStreamFormat
,
310 &propertySize
, &outputStreamDescription
);
312 ErrorF("QuartzAudioInit: GetProperty(stream format) returned %ld\n",
316 sampleRate
= outputStreamDescription
.mSampleRate
;
318 // Fill in the playback data
322 data
.remainingFrames
= 0;
323 data
.bytesPerFrame
= outputStreamDescription
.mBytesPerFrame
;
324 data
.sampleRate
= sampleRate
;
325 // data.bufferByteCount = bufferByteCount;
326 data
.playing
= FALSE
;
327 data
.prevAmplitude
= 0;
329 data
.prevFrequency
= 0;
330 data
.fadeLength
= data
.sampleRate
/ 200;
331 pthread_mutex_init(&data
.lock
, NULL
); // fixme error check
333 // fixme assert fadeLength<framesPerBuffer
335 // Prepare for playback
336 status
= AudioDeviceAddIOProc(outputDevice
, QuartzAudioIOProc
, &data
);
338 ErrorF("QuartzAudioInit: AddIOProc returned %ld\n", status
);
343 quartzAudioDevice
= outputDevice
;