| 1 |
/* |
| 2 |
* MDAudio_MacOSX.c |
| 3 |
* Alchemusica |
| 4 |
* |
| 5 |
* Created by Toshi Nagata on 08/01/06. |
| 6 |
* Copyright 2008-2016 Toshi Nagata. All rights reserved. |
| 7 |
* |
| 8 |
|
| 9 |
This program is free software; you can redistribute it and/or modify |
| 10 |
it under the terms of the GNU General Public License as published by |
| 11 |
the Free Software Foundation version 2 of the License. |
| 12 |
|
| 13 |
This program is distributed in the hope that it will be useful, |
| 14 |
but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 |
GNU General Public License for more details. |
| 17 |
*/ |
| 18 |
|
| 19 |
#include "MDHeaders.h" |
| 20 |
#include "MDAudioUtility.h" |
| 21 |
|
| 22 |
#include <unistd.h> /* For getcwd() */ |
| 23 |
#include <sys/param.h> /* For MAXPATHLEN */ |
| 24 |
//#include <CoreServices/CoreServices.h> /* Use Audio Component Services instead */ |
| 25 |
#include <AudioUnit/AudioComponent.h> |
| 26 |
#include <AudioUnit/AudioUnit.h> |
| 27 |
#include <AudioToolbox/AUGraph.h> /* for AUNode output */ |
| 28 |
#include <AudioToolbox/AUMIDIController.h> /* for routing MIDI to DLS synth */ |
| 29 |
#include <AudioToolbox/AudioConverter.h> |
| 30 |
#include <AudioUnit/MusicDevice.h> |
| 31 |
|
| 32 |
struct MDAudio { |
| 33 |
/* Canonical audio format */ |
| 34 |
MDAudioFormat preferredFormat; |
| 35 |
|
| 36 |
/* AUGraph and AUNodes */ |
| 37 |
AUGraph graph; |
| 38 |
AUNode mixer, output; |
| 39 |
/* AUNode synth, synth2, mixer, output, converter, splitter; */ |
| 40 |
/* int isInputRunning; |
| 41 |
int isRunning; */ |
| 42 |
|
| 43 |
/* Audio Units */ |
| 44 |
AudioUnit mixerUnit, outputUnit; |
| 45 |
/* AudioUnit inputUnit, outputUnit, mixerUnit, converterUnit; |
| 46 |
MusicDeviceComponent musicDevice, musicDevice2; |
| 47 |
AUMIDIControllerRef midiCon, midiCon2; */ |
| 48 |
|
| 49 |
/* Audio/Music device infos */ |
| 50 |
MDArray *inputDeviceInfos, *outputDeviceInfos; |
| 51 |
MDArray *musicDeviceInfos, *effectDeviceInfos; |
| 52 |
|
| 53 |
/* IO information (the mixer input/output) */ |
| 54 |
MDAudioIOStreamInfo ioStreamInfos[kMDAudioNumberOfStreams]; |
| 55 |
int isAudioThruEnabled; |
| 56 |
|
| 57 |
/* Feeding audio from external device */ |
| 58 |
/* AudioBufferList *inputBufferList; */ |
| 59 |
|
| 60 |
/* Recording to file */ |
| 61 |
ExtAudioFileRef audioFile; |
| 62 |
int isRecording; |
| 63 |
UInt64 recordingStartTime; |
| 64 |
UInt64 recordingDuration; |
| 65 |
|
| 66 |
/* Play thru */ |
| 67 |
/* MDAudioDeviceInfo inputDeviceInfoCache, outputDeviceInfoCache; |
| 68 |
MDSampleTime firstInputTime, firstOutputTime, inToOutSampleOffset; */ |
| 69 |
|
| 70 |
/* Audio through */ |
| 71 |
/* MDRingBuffer *ring; |
| 72 |
int isAudioThruEnabled; */ |
| 73 |
}; |
| 74 |
|
| 75 |
struct MDAudio *gAudio; |
| 76 |
|
| 77 |
#pragma mark ====== Internal Functions ====== |
| 78 |
|
| 79 |
int |
| 80 |
MDAudioShowError(OSStatus sts, const char *file, int line) |
| 81 |
{ |
| 82 |
if (sts != 0) |
| 83 |
fprintf(stderr, "Error OSStatus = %d at %s:%d\n", (int)sts, file, line); |
| 84 |
return (int)sts; |
| 85 |
} |
| 86 |
|
| 87 |
static int |
| 88 |
sMDAudioCompareFormat(const AudioStreamBasicDescription *format1, const AudioStreamBasicDescription *format2) |
| 89 |
{ |
| 90 |
if (format1->mSampleRate != format2->mSampleRate) |
| 91 |
return 0; |
| 92 |
if (format1->mFormatID != format2->mFormatID) |
| 93 |
return 0; |
| 94 |
if (format1->mFormatFlags != format2->mFormatFlags) |
| 95 |
return 0; |
| 96 |
if (format1->mBytesPerPacket != format2->mBytesPerPacket) |
| 97 |
return 0; |
| 98 |
if (format1->mFramesPerPacket != format2->mFramesPerPacket) |
| 99 |
return 0; |
| 100 |
if (format1->mBytesPerFrame != format2->mBytesPerFrame) |
| 101 |
return 0; |
| 102 |
if (format1->mChannelsPerFrame != format2->mChannelsPerFrame) |
| 103 |
return 0; |
| 104 |
if (format1->mBitsPerChannel != format2->mBitsPerChannel) |
| 105 |
return 0; |
| 106 |
return 1; |
| 107 |
} |
| 108 |
|
| 109 |
static void |
| 110 |
sMDAudioReleaseMyBufferList(AudioBufferList *list) |
| 111 |
{ |
| 112 |
UInt32 i; |
| 113 |
if (list != NULL) { |
| 114 |
for (i = 0; i < list->mNumberBuffers; i++) { |
| 115 |
if (list->mBuffers[i].mData != NULL) |
| 116 |
free(list->mBuffers[i].mData); |
| 117 |
} |
| 118 |
free(list); |
| 119 |
} |
| 120 |
} |
| 121 |
|
| 122 |
static AudioBufferList * |
| 123 |
sMDAudioAllocateMyBufferList(UInt32 channelsPerFrame, UInt32 bytesPerFrame, UInt32 numberOfFrames) |
| 124 |
{ |
| 125 |
int i; |
| 126 |
AudioBufferList *list; |
| 127 |
list = (AudioBufferList *)calloc(1, sizeof(AudioBufferList) + channelsPerFrame * sizeof(AudioBuffer)); |
| 128 |
if (list == NULL) |
| 129 |
return NULL; |
| 130 |
list->mNumberBuffers = channelsPerFrame; /* Assumes non-interleaved stream */ |
| 131 |
for(i = 0; i < channelsPerFrame; i++) { |
| 132 |
list->mBuffers[i].mNumberChannels = 1; /* Assumes non-interleaved stream */ |
| 133 |
list->mBuffers[i].mDataByteSize = numberOfFrames * bytesPerFrame; |
| 134 |
list->mBuffers[i].mData = calloc(bytesPerFrame, numberOfFrames); |
| 135 |
if (list->mBuffers[i].mData == NULL) { |
| 136 |
sMDAudioReleaseMyBufferList(list); |
| 137 |
return NULL; |
| 138 |
} |
| 139 |
} |
| 140 |
return list; |
| 141 |
} |
| 142 |
|
| 143 |
/* |
| 144 |
static void |
| 145 |
sComputeThruOffset(MDAudioIOStreamInfo *info) |
| 146 |
{ |
| 147 |
// The initial latency will at least be the saftey offset's of the devices + the buffer sizes |
| 148 |
|
| 149 |
info->inToOutSampleOffset = 0; |
| 150 |
// audio->inToOutSampleOffset = (MDSampleTime)( |
| 151 |
// gAudio->inputDeviceInfoCache.safetyOffset + |
| 152 |
// gAudio->inputDeviceInfoCache.bufferSizeFrames + |
| 153 |
// gAudio->outputDeviceInfoCache.safetyOffset + |
| 154 |
// gAudio->outputDeviceInfoCache.bufferSizeFrames); |
| 155 |
} |
| 156 |
*/ |
| 157 |
|
| 158 |
static void |
| 159 |
sMakeBufferSilent(AudioBufferList * ioData) |
| 160 |
{ |
| 161 |
UInt32 i; |
| 162 |
for (i = 0; i < ioData->mNumberBuffers; i++) |
| 163 |
memset(ioData->mBuffers[i].mData, 0, ioData->mBuffers[i].mDataByteSize); |
| 164 |
} |
| 165 |
|
| 166 |
/* Callback proc for input from AUHAL and write to audio-through buffer */ |
| 167 |
static OSStatus |
| 168 |
sMDAudioInputProc(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) |
| 169 |
{ |
| 170 |
MDAudioIOStreamInfo *info = (MDAudioIOStreamInfo *)inRefCon; |
| 171 |
OSStatus err = noErr; |
| 172 |
|
| 173 |
// { static int count = 99; if (++count == 100) { fprintf(stderr, "sMDAudioInputProc called at %f, ioData->mNumberBuffers = %d\n", (double)inTimeStamp->mSampleTime, (info->bufferList == NULL ? 0 : (int)info->bufferList->mNumberBuffers)); count = 0; } } |
| 174 |
|
| 175 |
if (info->firstInputTime < 0) { |
| 176 |
info->firstInputTime = inTimeStamp->mSampleTime; |
| 177 |
// fprintf(stderr, "firstInputTime = %f\n", (double)audio->firstInputTime); |
| 178 |
} |
| 179 |
// fprintf(stderr, "inputTimeStamp = %f\n", (double)inTimeStamp->mSampleTime); |
| 180 |
|
| 181 |
/* Render into audio buffer */ |
| 182 |
err = AudioUnitRender(info->unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, info->bufferList); |
| 183 |
if (err != noErr) { |
| 184 |
// fprintf(stderr, "AudioUnitRender() failed with error %d\n", (int)err); |
| 185 |
return err; |
| 186 |
} |
| 187 |
|
| 188 |
/* Write to ring buffer */ |
| 189 |
err = MDRingBufferStore(info->ring, info->bufferList, inNumberFrames, (MDSampleTime)inTimeStamp->mSampleTime); |
| 190 |
if (err != noErr) { |
| 191 |
// fprintf(stderr, "MDRingBufferStore() failed with error %d\n", (int)err); |
| 192 |
return err; |
| 193 |
} |
| 194 |
|
| 195 |
return noErr; |
| 196 |
} |
| 197 |
|
| 198 |
/* Callback proc for reading data from ring buffer and put into AUGraph input */ |
| 199 |
static OSStatus |
| 200 |
sMDAudioPassProc(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList * ioData) |
| 201 |
{ |
| 202 |
MDAudioIOStreamInfo *info = (MDAudioIOStreamInfo *)inRefCon; |
| 203 |
OSStatus err = noErr; |
| 204 |
|
| 205 |
// { static int count = 0; if (++count == 100) { fprintf(stderr, "sMDAudioPassProc called at %f, ioData->mNumberBuffers = %d\n", (double)inTimeStamp->mSampleTime, (int)ioData->mNumberBuffers); count = 0; } } |
| 206 |
|
| 207 |
if (info->firstInputTime < 0) { |
| 208 |
/* The input has not arrived yet */ |
| 209 |
sMakeBufferSilent(ioData); |
| 210 |
return noErr; |
| 211 |
} |
| 212 |
/* if ((err = AudioDeviceGetCurrentTime(This->mInputDevice.mID, &inTS)) != 0) { |
| 213 |
MakeBufferSilent(ioData); |
| 214 |
return noErr; |
| 215 |
} |
| 216 |
|
| 217 |
CHECK_ERR(AudioDeviceGetCurrentTime(This->mOutputDevice.mID, &outTS)); */ |
| 218 |
|
| 219 |
// fprintf(stderr, "outputTimeStamp = %f\n", (double)inTimeStamp->mSampleTime); |
| 220 |
|
| 221 |
// get Delta between the devices and add it to the offset |
| 222 |
if (info->firstOutputTime < 0) { |
| 223 |
info->firstOutputTime = inTimeStamp->mSampleTime; |
| 224 |
info->inToOutSampleOffset = info->firstOutputTime - info->firstInputTime; |
| 225 |
/* TODO: modify offset to account for the latency */ |
| 226 |
/* sComputeThruOffset(audio); |
| 227 |
// Is this really correct?? |
| 228 |
if (delta < 0.0) |
| 229 |
info->inToOutSampleOffset -= delta; |
| 230 |
else |
| 231 |
info->inToOutSampleOffset = -delta + info->inToOutSampleOffset; */ |
| 232 |
// fprintf(stderr, "offset = %f\n", (double)audio->inToOutSampleOffset); |
| 233 |
sMakeBufferSilent(ioData); |
| 234 |
return noErr; |
| 235 |
} |
| 236 |
|
| 237 |
// copy the data from the buffers |
| 238 |
err = MDRingBufferFetch(info->ring, ioData, inNumberFrames, (MDSampleTime)inTimeStamp->mSampleTime - info->inToOutSampleOffset, false); |
| 239 |
if (err != kMDRingBufferError_OK) { |
| 240 |
MDSampleTime bufferStartTime, bufferEndTime; |
| 241 |
// fprintf(stderr, "err = %d at line %d\n", (int)err, __LINE__); |
| 242 |
sMakeBufferSilent(ioData); |
| 243 |
if (err == 1 || err == -1) { |
| 244 |
MDRingBufferGetTimeBounds(info->ring, &bufferStartTime, &bufferEndTime); |
| 245 |
info->inToOutSampleOffset = inTimeStamp->mSampleTime - bufferStartTime; |
| 246 |
// fprintf(stderr, "buffer = (%f,%f) offset = %f\n", (double)bufferStartTime, (double)bufferEndTime, (double)audio->inToOutSampleOffset); |
| 247 |
} else { |
| 248 |
info->firstInputTime = info->firstOutputTime = -1; |
| 249 |
} |
| 250 |
} |
| 251 |
|
| 252 |
return noErr; |
| 253 |
} |
| 254 |
|
| 255 |
/* Callback proc for recording to audio file */ |
| 256 |
static OSStatus |
| 257 |
sMDAudioRecordProc(void* inRefCon, AudioUnitRenderActionFlags* ioActionFlags, const AudioTimeStamp* inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList* ioData) |
| 258 |
{ |
| 259 |
OSStatus err = noErr; |
| 260 |
MDAudioIOStreamInfo *info = (MDAudioIOStreamInfo *)inRefCon; |
| 261 |
MDSampleTime startTime, endTime, renderTime; |
| 262 |
|
| 263 |
{ static int count = 0; if (++count == 100) { dprintf(3, "sMDAudioRecordProc called at %f, ioData->mNumberBuffers = %d\n", (double)inTimeStamp->mSampleTime, (int)ioData->mNumberBuffers); count = 0; } } |
| 264 |
|
| 265 |
#if 1 |
| 266 |
/* Sample time range to handle during this callback */ |
| 267 |
startTime = inTimeStamp->mSampleTime; |
| 268 |
endTime = startTime + inNumberFrames; |
| 269 |
if (info->firstInputTime < 0) { |
| 270 |
info->firstInputTime = inTimeStamp->mSampleTime; |
| 271 |
} |
| 272 |
|
| 273 |
/* Fill the ring buffer until enough data is present in the ring buffer */ |
| 274 |
while ((renderTime = MDRingBufferEndTime(info->ring)) < endTime) { |
| 275 |
AudioTimeStamp timeStamp = {0}; |
| 276 |
int i, n; |
| 277 |
timeStamp.mSampleTime = renderTime; |
| 278 |
timeStamp.mRateScalar = inTimeStamp->mRateScalar; |
| 279 |
timeStamp.mFlags = kAudioTimeStampSampleTimeValid | kAudioTimeStampRateScalarValid; |
| 280 |
n = info->bufferSizeFrames; |
| 281 |
for (i = 0; i < info->bufferList->mNumberBuffers; i++) |
| 282 |
info->bufferList->mBuffers[i].mDataByteSize = n * info->ring->bytesPerFrame; |
| 283 |
err = AudioUnitRender(gAudio->mixerUnit, ioActionFlags, inTimeStamp, inBusNumber, n, info->bufferList); |
| 284 |
if (err != noErr) { |
| 285 |
dprintf(0, "AudioUnitRender() failed with error %d in sMDAudioRecordProc\n", (int)err); |
| 286 |
return err; |
| 287 |
} |
| 288 |
/* Write to ring buffer */ |
| 289 |
err = MDRingBufferStore(info->ring, info->bufferList, n, renderTime); |
| 290 |
if (err != noErr) { |
| 291 |
dprintf(0, "MDRingBufferStore() failed with error %d in sMDAudioRecordProc\n", (int)err); |
| 292 |
if (err < 0) { |
| 293 |
/* Throw everything away from the ring buffer */ |
| 294 |
MDRingBufferSetTimeBounds(info->ring, startTime, startTime); |
| 295 |
} |
| 296 |
return err; |
| 297 |
} |
| 298 |
} |
| 299 |
|
| 300 |
/* Fill ioData from the ring buffer */ |
| 301 |
err = MDRingBufferFetch(info->ring, ioData, inNumberFrames, startTime, false); |
| 302 |
if (err != noErr) { |
| 303 |
dprintf(0, "MDRingBufferFetch() failed with error %d in sMDAudioRecordProc\n", (int)err); |
| 304 |
if (err < 0) { |
| 305 |
/* Throw everything away from the ring buffer */ |
| 306 |
MDRingBufferSetTimeBounds(info->ring, startTime, startTime); |
| 307 |
} |
| 308 |
return err; |
| 309 |
} |
| 310 |
|
| 311 |
#else |
| 312 |
/* Render into audio buffer */ |
| 313 |
err = AudioUnitRender(gAudio->mixerUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); |
| 314 |
if (err != noErr) { |
| 315 |
dprintf(0, "AudioUnitRender() failed with error %d in sMDAudioRecordProc\n", (int)err); |
| 316 |
return err; |
| 317 |
} |
| 318 |
#endif |
| 319 |
|
| 320 |
/* Write to file */ |
| 321 |
if (gAudio->isRecording) { |
| 322 |
if (gAudio->recordingStartTime == 0) |
| 323 |
gAudio->recordingStartTime = inTimeStamp->mHostTime; |
| 324 |
if (gAudio->recordingDuration == 0 || inTimeStamp->mHostTime < gAudio->recordingStartTime + gAudio->recordingDuration) { |
| 325 |
err = ExtAudioFileWriteAsync(gAudio->audioFile, inNumberFrames, ioData); |
| 326 |
if (err != noErr) { |
| 327 |
dprintf(0, "ExtAudioFileWrite() failed with error %d in sMDAudioRecordProc\n", (int)err); |
| 328 |
return err; |
| 329 |
} |
| 330 |
} |
| 331 |
} |
| 332 |
|
| 333 |
if (err != noErr) { |
| 334 |
// fprintf(stderr, "sMDAudioRecordProc() failed with error %d\n", (int)err); |
| 335 |
return err; |
| 336 |
} |
| 337 |
|
| 338 |
if (!gAudio->isAudioThruEnabled) |
| 339 |
return 1; /* Discard the input data */ |
| 340 |
|
| 341 |
return noErr; |
| 342 |
} |
| 343 |
|
| 344 |
/* Callback to send MIDI events to Music Device */ |
| 345 |
static OSStatus |
| 346 |
sMDAudioSendMIDIProc(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) |
| 347 |
{ |
| 348 |
MDAudioIOStreamInfo *ip = (MDAudioIOStreamInfo *)inRefCon; |
| 349 |
int readOffset = ip->midiBufferReadOffset; |
| 350 |
int writeOffset = ip->midiBufferWriteOffset; |
| 351 |
int dataSize = (writeOffset + kMDAudioMaxMIDIBytesToSendPerDevice - readOffset) % kMDAudioMaxMIDIBytesToSendPerDevice; |
| 352 |
int numChannelEvents = 0; |
| 353 |
int readPos = readOffset; |
| 354 |
if ((*ioActionFlags & kAudioUnitRenderAction_PreRender) != kAudioUnitRenderAction_PreRender) |
| 355 |
return noErr; /* No action */ |
| 356 |
if (ip->requestFlush) { |
| 357 |
/* Flush is requested: skip all unread bytes */ |
| 358 |
ip->requestFlush = 0; |
| 359 |
ip->midiBufferReadOffset = ip->midiBufferWriteOffset; |
| 360 |
return noErr; |
| 361 |
} |
| 362 |
while (readPos - readOffset < dataSize) { |
| 363 |
UInt64 timeStamp = 0; |
| 364 |
UInt32 offset; |
| 365 |
int i, len; |
| 366 |
unsigned char c; |
| 367 |
for (i = 0; i < 8; i++) { |
| 368 |
c = ip->midiBuffer[(readPos + i) % kMDAudioMaxMIDIBytesToSendPerDevice]; |
| 369 |
timeStamp += (((UInt64)c) << (i * 8)); |
| 370 |
} |
| 371 |
if (timeStamp > inTimeStamp->mHostTime) { |
| 372 |
offset = (UInt32)((double)(timeStamp - inTimeStamp->mHostTime) * (1.0 / ip->format.mSampleRate)); |
| 373 |
} else offset = 0; |
| 374 |
if (offset >= inNumberFrames) { |
| 375 |
/* This event is scheduled in the next or later frame, so it |
| 376 |
should be processed in later callback */ |
| 377 |
break; |
| 378 |
} |
| 379 |
len = ip->midiBuffer[(readPos + 8) % kMDAudioMaxMIDIBytesToSendPerDevice]; |
| 380 |
c = ip->midiBuffer[(readPos + 9) % kMDAudioMaxMIDIBytesToSendPerDevice]; |
| 381 |
if (c == 0xff || c == 0xf0) { |
| 382 |
/* System Exclusive: in this case, no channel events should be scheduled |
| 383 |
in this callback session; otherwise, the scheduled channel events will be |
| 384 |
sent _after_ sending sysex. (There is no mechanism to schedule a sysex |
| 385 |
event to MusicDevice.) |
| 386 |
So, if we already scheduled any channel events, then we stop processing |
| 387 |
here and try to send sysex in the next session. */ |
| 388 |
if (numChannelEvents > 0) |
| 389 |
break; |
| 390 |
if (c == 0xff) { |
| 391 |
if (ip->sysexData != NULL) { |
| 392 |
MusicDeviceSysEx(ip->unit, ip->sysexData, ip->sysexLength); |
| 393 |
ip->sysexData = NULL; |
| 394 |
} |
| 395 |
readPos += 10; /* 8 (timeStamp) + 1 (length) + 1 (0xff) */ |
| 396 |
} else { |
| 397 |
static unsigned char tempBuffer[256]; |
| 398 |
for (i = 0; i < len; i++) { |
| 399 |
tempBuffer[i] = ip->midiBuffer[(readPos + 9 + i) % kMDAudioMaxMIDIBytesToSendPerDevice]; |
| 400 |
} |
| 401 |
MusicDeviceSysEx(ip->unit, tempBuffer, len); |
| 402 |
/* printf("sysex %d\n", len); */ |
| 403 |
readPos += 9 + len; |
| 404 |
} |
| 405 |
} else { |
| 406 |
unsigned char c2, c3; |
| 407 |
c2 = c3 = 0; |
| 408 |
if (len >= 2) { |
| 409 |
c2 = ip->midiBuffer[(readPos + 10) % kMDAudioMaxMIDIBytesToSendPerDevice]; |
| 410 |
if (len >= 3) { |
| 411 |
c3 = ip->midiBuffer[(readPos + 11) % kMDAudioMaxMIDIBytesToSendPerDevice]; |
| 412 |
} |
| 413 |
} |
| 414 |
MusicDeviceMIDIEvent(ip->unit, c, c2, c3, offset); |
| 415 |
/* printf("%08x %02x %02x %02x %d\n", (UInt32)ip->unit, c, c2, c3, offset); */ |
| 416 |
readPos += 9 + len; |
| 417 |
numChannelEvents++; |
| 418 |
} |
| 419 |
} |
| 420 |
ip->midiBufferReadOffset = readPos % kMDAudioMaxMIDIBytesToSendPerDevice; |
| 421 |
return noErr; |
| 422 |
} |
| 423 |
|
| 424 |
int |
| 425 |
MDAudioScheduleMIDIToStream(MDAudioIOStreamInfo *ip, UInt64 timeStamp, int length, unsigned char *midiData, int isSysEx) |
| 426 |
{ |
| 427 |
int readOffset, writeOffset, spaceSize; |
| 428 |
int i, length2; |
| 429 |
if (ip->midiBuffer == NULL) |
| 430 |
return 0; /* Not active */ |
| 431 |
readOffset = ip->midiBufferReadOffset; |
| 432 |
writeOffset = ip->midiBufferWriteOffset; |
| 433 |
spaceSize = (readOffset + kMDAudioMaxMIDIBytesToSendPerDevice - 1 - writeOffset) % kMDAudioMaxMIDIBytesToSendPerDevice + 1; |
| 434 |
if (isSysEx) { |
| 435 |
/* Schedule sysex */ |
| 436 |
if (ip->sysexData != NULL) |
| 437 |
return 1; /* Sysex is waiting: cannot schedule until this is done */ |
| 438 |
ip->sysexLength = length; |
| 439 |
ip->sysexData = midiData; |
| 440 |
return 0; |
| 441 |
} |
| 442 |
length2 = length + sizeof(timeStamp) + 1; |
| 443 |
if (spaceSize <= length2) |
| 444 |
return 1; /* Buffer overflow */ |
| 445 |
for (i = 0; i < length2; i++) { |
| 446 |
unsigned char c; |
| 447 |
if (i < sizeof(timeStamp)) |
| 448 |
c = (timeStamp >> (i * 8)) & 0xff; |
| 449 |
else if (i == sizeof(timeStamp)) |
| 450 |
c = length & 0xff; /* Should be length <= 255 */ |
| 451 |
else |
| 452 |
c = midiData[i - sizeof(timeStamp) - 1]; |
| 453 |
ip->midiBuffer[(writeOffset + i) % kMDAudioMaxMIDIBytesToSendPerDevice] = c; |
| 454 |
} |
| 455 |
ip->midiBufferWriteOffset = (writeOffset + length2) % kMDAudioMaxMIDIBytesToSendPerDevice; |
| 456 |
return 0; |
| 457 |
} |
| 458 |
|
| 459 |
#pragma mark ====== Device information ====== |
| 460 |
|
| 461 |
static void |
| 462 |
sMDAudioDeviceInfoDestructor(void *p) |
| 463 |
{ |
| 464 |
MDAudioDeviceInfo *info = (MDAudioDeviceInfo *)p; |
| 465 |
if (info->name != NULL) |
| 466 |
free(info->name); |
| 467 |
} |
| 468 |
|
| 469 |
static int |
| 470 |
sMDAudioDeviceCountChannels(MDAudioDeviceID deviceID, int isInput) |
| 471 |
{ |
| 472 |
OSStatus err; |
| 473 |
UInt32 propSize, i; |
| 474 |
int result; |
| 475 |
AudioBufferList *buflist; |
| 476 |
AudioObjectPropertyAddress address; |
| 477 |
|
| 478 |
address.mSelector = kAudioDevicePropertyStreamConfiguration; |
| 479 |
address.mScope = (isInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput); |
| 480 |
address.mElement = kAudioObjectPropertyElementMaster; |
| 481 |
|
| 482 |
err = AudioObjectGetPropertyDataSize(deviceID, &address, 0, NULL, &propSize); |
| 483 |
|
| 484 |
// err = AudioDeviceGetPropertyInfo(deviceID, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, NULL); |
| 485 |
if (err != noErr) |
| 486 |
return 0; |
| 487 |
|
| 488 |
if (propSize == 0) |
| 489 |
return 0; |
| 490 |
|
| 491 |
buflist = (AudioBufferList *)malloc(propSize); |
| 492 |
if (buflist == NULL) |
| 493 |
return 0; |
| 494 |
|
| 495 |
err = AudioObjectGetPropertyData(deviceID, &address, 0, NULL, &propSize, buflist); |
| 496 |
// err = AudioDeviceGetProperty(deviceID, 0, isInput, kAudioDevicePropertyStreamConfiguration, &propSize, buflist); |
| 497 |
result = 0; |
| 498 |
if (err == noErr) { |
| 499 |
for (i = 0; i < buflist->mNumberBuffers; ++i) { |
| 500 |
result += buflist->mBuffers[i].mNumberChannels; |
| 501 |
} |
| 502 |
} |
| 503 |
|
| 504 |
free(buflist); |
| 505 |
return result; |
| 506 |
} |
| 507 |
|
| 508 |
static void |
| 509 |
sMDAudioMusicDeviceInfoDestructor(void *p) |
| 510 |
{ |
| 511 |
MDAudioMusicDeviceInfo *info = (MDAudioMusicDeviceInfo *)p; |
| 512 |
if (info->name != NULL) |
| 513 |
free(info->name); |
| 514 |
} |
| 515 |
|
| 516 |
static MDStatus |
| 517 |
sMDAudioUpdateHardwareDeviceInfo(void) |
| 518 |
{ |
| 519 |
UInt32 propsize; |
| 520 |
MDStatus err = noErr; |
| 521 |
int i, ndevs, isInput; |
| 522 |
AudioDeviceID *devs; |
| 523 |
MDArray *ary; |
| 524 |
|
| 525 |
AudioObjectPropertyAddress address; |
| 526 |
|
| 527 |
address.mSelector = kAudioHardwarePropertyDevices; |
| 528 |
address.mScope = kAudioObjectPropertyScopeGlobal; |
| 529 |
address.mElement = kAudioObjectPropertyElementMaster; |
| 530 |
|
| 531 |
err = AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, &address, 0, NULL, &propsize); |
| 532 |
if (err != noErr) |
| 533 |
return err; |
| 534 |
ndevs = propsize / sizeof(AudioDeviceID); |
| 535 |
devs = (AudioDeviceID *)malloc(sizeof(AudioDeviceID) * ndevs); |
| 536 |
if (devs == NULL) |
| 537 |
return kMDErrorOutOfMemory; |
| 538 |
|
| 539 |
err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &address, 0, NULL, &propsize, devs); |
| 540 |
if (err != noErr) |
| 541 |
goto exit; |
| 542 |
if (gAudio->inputDeviceInfos == NULL) { |
| 543 |
gAudio->inputDeviceInfos = MDArrayNewWithDestructor(sizeof(MDAudioDeviceInfo), sMDAudioDeviceInfoDestructor); |
| 544 |
if (gAudio->inputDeviceInfos == NULL) { |
| 545 |
err = kMDErrorOutOfMemory; |
| 546 |
goto exit; |
| 547 |
} |
| 548 |
} |
| 549 |
if (gAudio->outputDeviceInfos == NULL) { |
| 550 |
gAudio->outputDeviceInfos = MDArrayNewWithDestructor(sizeof(MDAudioDeviceInfo), sMDAudioDeviceInfoDestructor); |
| 551 |
if (gAudio->outputDeviceInfos == NULL) { |
| 552 |
err = kMDErrorOutOfMemory; |
| 553 |
goto exit; |
| 554 |
} |
| 555 |
} |
| 556 |
for (isInput = 0; isInput < 2; isInput++) { |
| 557 |
MDAudioDeviceInfo info, *ip; |
| 558 |
ary = (isInput ? gAudio->inputDeviceInfos : gAudio->outputDeviceInfos); |
| 559 |
/* Raise the internal flag for all registered deivces */ |
| 560 |
for (i = MDArrayCount(ary) - 1; i >= 0; i--) { |
| 561 |
ip = MDArrayFetchPtr(ary, i); |
| 562 |
ip->flags |= 1; |
| 563 |
} |
| 564 |
for (i = 0; i < ndevs; i++) { |
| 565 |
int nchan = sMDAudioDeviceCountChannels(devs[i], isInput); |
| 566 |
if (nchan == 0) |
| 567 |
continue; |
| 568 |
ip = MDAudioDeviceInfoForDeviceID(devs[i], isInput, NULL); |
| 569 |
if (ip != NULL) { |
| 570 |
/* Already known */ |
| 571 |
ip->nChannels = nchan; |
| 572 |
ip->flags &= ~1; |
| 573 |
continue; |
| 574 |
} else { |
| 575 |
/* Unknown device */ |
| 576 |
char buf[256]; |
| 577 |
UInt32 maxlen = sizeof(buf) - 1; |
| 578 |
memset(&info, 0, sizeof(info)); |
| 579 |
info.deviceID = devs[i]; |
| 580 |
info.nChannels = sMDAudioDeviceCountChannels(devs[i], isInput); |
| 581 |
address.mSelector = kAudioDevicePropertyDeviceName; |
| 582 |
address.mScope = (isInput ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput); |
| 583 |
err = AudioObjectGetPropertyData(devs[i], &address, 0, NULL, &maxlen, buf); |
| 584 |
// err = AudioDeviceGetProperty(devs[i], 0, isInput, kAudioDevicePropertyDeviceName, &maxlen, buf); |
| 585 |
if (err != noErr) |
| 586 |
goto exit; |
| 587 |
buf[maxlen] = 0; |
| 588 |
info.name = strdup(buf); |
| 589 |
MyAppCallback_startupMessage("Initializing %s...", info.name); |
| 590 |
propsize = sizeof(UInt32); |
| 591 |
address.mSelector = kAudioDevicePropertySafetyOffset; |
| 592 |
if ((err = AudioObjectGetPropertyData(devs[i], &address, 0, NULL, &propsize, &info.safetyOffset)) != noErr) |
| 593 |
goto exit; |
| 594 |
propsize = sizeof(UInt32); |
| 595 |
address.mSelector = kAudioDevicePropertyBufferFrameSize; |
| 596 |
if ((err = AudioObjectGetPropertyData(devs[i], &address, 0, NULL, &propsize, &info.bufferSizeFrames)) != noErr) |
| 597 |
goto exit; |
| 598 |
propsize = sizeof(AudioStreamBasicDescription); |
| 599 |
address.mSelector = kAudioDevicePropertyStreamFormat; |
| 600 |
if ((err = AudioObjectGetPropertyData(devs[i], &address, 0, NULL, &propsize, &info.format)) != noErr) |
| 601 |
goto exit; |
| 602 |
if ((err = MDArrayInsert(ary, MDArrayCount(ary), 1, &info)) != kMDNoError) |
| 603 |
goto exit; |
| 604 |
} |
| 605 |
} |
| 606 |
/* Remove non-present devices */ |
| 607 |
for (i = MDArrayCount(ary) - 1; i >= 0; i--) { |
| 608 |
ip = MDArrayFetchPtr(ary, i); |
| 609 |
if (ip->flags & 1) |
| 610 |
MDArrayDelete(ary, i, 1); |
| 611 |
} |
| 612 |
} |
| 613 |
|
| 614 |
exit: |
| 615 |
free(devs); |
| 616 |
return err; |
| 617 |
} |
| 618 |
|
| 619 |
static MDStatus |
| 620 |
sMDAudioUpdateSoftwareDeviceInfo(int music_or_effect) |
| 621 |
{ |
| 622 |
AudioComponentDescription ccd, fcd; |
| 623 |
AudioComponent cmp = NULL; |
| 624 |
char *cName; |
| 625 |
int n, i; |
| 626 |
UInt32 propSize; |
| 627 |
MDAudioMusicDeviceInfo info, *ip; |
| 628 |
MDArray **basep; |
| 629 |
OSStatus err; |
| 630 |
MDStatus status = kMDNoError; |
| 631 |
|
| 632 |
basep = (music_or_effect ? &(gAudio->musicDeviceInfos) : &(gAudio->effectDeviceInfos)); |
| 633 |
if (*basep == NULL) { |
| 634 |
*basep = MDArrayNewWithDestructor(sizeof(MDAudioMusicDeviceInfo), sMDAudioMusicDeviceInfoDestructor); |
| 635 |
if (*basep == NULL) { |
| 636 |
return kMDErrorOutOfMemory; |
| 637 |
} |
| 638 |
} |
| 639 |
|
| 640 |
memset(&fcd, 0, sizeof(fcd)); |
| 641 |
fcd.componentType = (music_or_effect ? kAudioUnitType_MusicDevice : kAudioUnitType_Effect); |
| 642 |
n = 0; |
| 643 |
while ((cmp = AudioComponentFindNext(cmp, &fcd)) != 0) { |
| 644 |
AudioUnit unit; |
| 645 |
CFStringRef nameRef; |
| 646 |
CFIndex cflen; |
| 647 |
|
| 648 |
/* Get the component information */ |
| 649 |
memset(&info, 0, sizeof(info)); |
| 650 |
err = AudioComponentGetDescription(cmp, &ccd); |
| 651 |
if (err != noErr) |
| 652 |
continue; /* Cannot get valid description */ |
| 653 |
info.code = (((UInt64)ccd.componentSubType) << 32) + ((UInt64)ccd.componentManufacturer); |
| 654 |
|
| 655 |
/* Get the component name */ |
| 656 |
err = AudioComponentCopyName(cmp, &nameRef); |
| 657 |
if (err != noErr) |
| 658 |
continue; /* Cannot get valid name */ |
| 659 |
cflen = CFStringGetLength(nameRef); |
| 660 |
if (cflen == 0) |
| 661 |
continue; /* Empty device name */ |
| 662 |
cName = malloc(cflen * 4 + 1); |
| 663 |
if (!CFStringGetCString(nameRef, cName, cflen * 4, kCFStringEncodingUTF8)) |
| 664 |
continue; /* Cannot get valid name */ |
| 665 |
|
| 666 |
info.name = cName; |
| 667 |
|
| 668 |
for (i = 0; (ip = MDArrayFetchPtr(*basep, i)) != NULL; i++) { |
| 669 |
if (ip->code == info.code && strncmp(ip->name, cName, cflen) == 0) { |
| 670 |
free(info.name); |
| 671 |
info.name = NULL; |
| 672 |
break; |
| 673 |
} |
| 674 |
} |
| 675 |
if (ip != NULL) |
| 676 |
continue; /* This device is already known */ |
| 677 |
|
| 678 |
MyAppCallback_startupMessage("Loading %s...", info.name); |
| 679 |
|
| 680 |
/* Get the audio output format */ |
| 681 |
err = AudioComponentInstanceNew(cmp, &unit); |
| 682 |
if (err == noErr) { |
| 683 |
propSize = sizeof(MDAudioFormat); |
| 684 |
err = AudioUnitGetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &info.format, &propSize); |
| 685 |
info.acceptsCanonicalFormat = sMDAudioCompareFormat(&info.format, &gAudio->preferredFormat); |
| 686 |
} else { |
| 687 |
unit = NULL; |
| 688 |
} |
| 689 |
if (err == noErr) { |
| 690 |
err = AudioUnitGetPropertyInfo(unit, kAudioUnitProperty_CocoaUI, kAudioUnitScope_Global, 0, &propSize, NULL); |
| 691 |
if (err == noErr && propSize > 0) |
| 692 |
info.hasCustomView = kMDAudioHasCocoaView; |
| 693 |
else { |
| 694 |
err = AudioUnitGetPropertyInfo(unit, kAudioUnitProperty_GetUIComponentList, kAudioUnitScope_Global, 0, &propSize, NULL); |
| 695 |
if (err == noErr && propSize > 0) |
| 696 |
info.hasCustomView = kMDAudioHasCarbonView; |
| 697 |
else { |
| 698 |
info.hasCustomView = 0; |
| 699 |
err = noErr; |
| 700 |
} |
| 701 |
} |
| 702 |
} |
| 703 |
if (err == noErr) { |
| 704 |
status = MDArrayInsert(*basep, MDArrayCount(*basep), 1, &info); |
| 705 |
} else { |
| 706 |
status = kMDErrorCannotSetupAudio; |
| 707 |
} |
| 708 |
if (unit != NULL) |
| 709 |
AudioComponentInstanceDispose(unit); |
| 710 |
if (status == kMDNoError) |
| 711 |
n++; |
| 712 |
else { |
| 713 |
free(info.name); |
| 714 |
info.name = NULL; |
| 715 |
} |
| 716 |
} |
| 717 |
MyAppCallback_startupMessage(""); |
| 718 |
return status; |
| 719 |
} |
| 720 |
|
| 721 |
MDStatus |
| 722 |
MDAudioUpdateDeviceInfo(void) |
| 723 |
{ |
| 724 |
MDStatus err; |
| 725 |
err = sMDAudioUpdateHardwareDeviceInfo(); |
| 726 |
if (err == kMDNoError) |
| 727 |
err = sMDAudioUpdateSoftwareDeviceInfo(1); /* Music device */ |
| 728 |
if (err == kMDNoError) |
| 729 |
err = sMDAudioUpdateSoftwareDeviceInfo(0); /* Effects */ |
| 730 |
return err; |
| 731 |
} |
| 732 |
|
| 733 |
int |
| 734 |
MDAudioDeviceCountInfo(int isInput) |
| 735 |
{ |
| 736 |
MDArray *ary = (isInput ? gAudio->inputDeviceInfos : gAudio->outputDeviceInfos); |
| 737 |
if (ary == NULL) |
| 738 |
return 0; |
| 739 |
return MDArrayCount(ary); |
| 740 |
} |
| 741 |
|
| 742 |
MDAudioDeviceInfo * |
| 743 |
MDAudioDeviceInfoAtIndex(int idx, int isInput) |
| 744 |
{ |
| 745 |
MDArray *ary = (isInput ? gAudio->inputDeviceInfos : gAudio->outputDeviceInfos); |
| 746 |
if (ary == NULL) |
| 747 |
return NULL; |
| 748 |
return MDArrayFetchPtr(ary, idx); |
| 749 |
} |
| 750 |
|
| 751 |
MDAudioDeviceInfo * |
| 752 |
MDAudioDeviceInfoForDeviceID(int deviceID, int isInput, int *deviceIndex) |
| 753 |
{ |
| 754 |
MDAudioDeviceInfo *ip; |
| 755 |
int i; |
| 756 |
MDArray *ary = (isInput ? gAudio->inputDeviceInfos : gAudio->outputDeviceInfos); |
| 757 |
if (ary == NULL) |
| 758 |
return NULL; |
| 759 |
for (i = 0; (ip = MDArrayFetchPtr(ary, i)) != NULL; i++) { |
| 760 |
if (ip->deviceID == deviceID) { |
| 761 |
if (deviceIndex != NULL) |
| 762 |
*deviceIndex = i; |
| 763 |
return ip; |
| 764 |
} |
| 765 |
} |
| 766 |
return NULL; |
| 767 |
} |
| 768 |
|
| 769 |
MDAudioDeviceInfo * |
| 770 |
MDAudioDeviceInfoWithName(const char *name, int isInput, int *deviceIndex) |
| 771 |
{ |
| 772 |
MDAudioDeviceInfo *ip; |
| 773 |
int i; |
| 774 |
MDArray *ary = (isInput ? gAudio->inputDeviceInfos : gAudio->outputDeviceInfos); |
| 775 |
if (ary == NULL) |
| 776 |
return NULL; |
| 777 |
for (i = 0; (ip = MDArrayFetchPtr(ary, i)) != NULL; i++) { |
| 778 |
if (strcmp(ip->name, name) == 0) { |
| 779 |
if (deviceIndex != NULL) |
| 780 |
*deviceIndex = i; |
| 781 |
return ip; |
| 782 |
} |
| 783 |
} |
| 784 |
return NULL; |
| 785 |
} |
| 786 |
|
| 787 |
int |
| 788 |
MDAudioMusicDeviceCountInfo(void) |
| 789 |
{ |
| 790 |
if (gAudio->musicDeviceInfos != NULL) |
| 791 |
return MDArrayCount(gAudio->musicDeviceInfos); |
| 792 |
else return 0; |
| 793 |
} |
| 794 |
|
| 795 |
MDAudioMusicDeviceInfo * |
| 796 |
MDAudioMusicDeviceInfoAtIndex(int idx) |
| 797 |
{ |
| 798 |
if (gAudio->musicDeviceInfos == NULL) |
| 799 |
return NULL; |
| 800 |
return MDArrayFetchPtr(gAudio->musicDeviceInfos, idx); |
| 801 |
} |
| 802 |
|
| 803 |
MDAudioMusicDeviceInfo * |
| 804 |
MDAudioMusicDeviceInfoForCode(UInt64 code, int *outIndex) |
| 805 |
{ |
| 806 |
MDAudioMusicDeviceInfo *ip; |
| 807 |
int i; |
| 808 |
if (gAudio->musicDeviceInfos == NULL) |
| 809 |
return NULL; |
| 810 |
for (i = 0; (ip = MDArrayFetchPtr(gAudio->musicDeviceInfos, i)) != NULL; i++) { |
| 811 |
if (ip->code == code) { |
| 812 |
if (outIndex != NULL) |
| 813 |
*outIndex = i; |
| 814 |
return ip; |
| 815 |
} |
| 816 |
} |
| 817 |
if (outIndex != NULL) |
| 818 |
*outIndex = -1; |
| 819 |
return NULL; |
| 820 |
} |
| 821 |
|
| 822 |
int |
| 823 |
MDAudioEffectDeviceCountInfo(void) |
| 824 |
{ |
| 825 |
if (gAudio->effectDeviceInfos != NULL) |
| 826 |
return MDArrayCount(gAudio->effectDeviceInfos); |
| 827 |
else return 0; |
| 828 |
} |
| 829 |
|
| 830 |
MDAudioMusicDeviceInfo * |
| 831 |
MDAudioEffectDeviceInfoAtIndex(int idx) |
| 832 |
{ |
| 833 |
if (gAudio->effectDeviceInfos == NULL) |
| 834 |
return NULL; |
| 835 |
return MDArrayFetchPtr(gAudio->effectDeviceInfos, idx); |
| 836 |
} |
| 837 |
|
| 838 |
MDAudioMusicDeviceInfo * |
| 839 |
MDAudioEffectDeviceInfoForCode(UInt64 code, int *outIndex) |
| 840 |
{ |
| 841 |
MDAudioMusicDeviceInfo *ip; |
| 842 |
int i; |
| 843 |
if (gAudio->effectDeviceInfos == NULL) |
| 844 |
return NULL; |
| 845 |
for (i = 0; (ip = MDArrayFetchPtr(gAudio->effectDeviceInfos, i)) != NULL; i++) { |
| 846 |
if (ip->code == code) { |
| 847 |
if (outIndex != NULL) |
| 848 |
*outIndex = i; |
| 849 |
return ip; |
| 850 |
} |
| 851 |
} |
| 852 |
if (outIndex != NULL) |
| 853 |
*outIndex = -1; |
| 854 |
return NULL; |
| 855 |
} |
| 856 |
|
| 857 |
MDAudioIOStreamInfo * |
| 858 |
MDAudioGetIOStreamInfoAtIndex(int idx) |
| 859 |
{ |
| 860 |
if (idx < 0 || idx >= kMDAudioNumberOfStreams) |
| 861 |
return NULL; |
| 862 |
return &(gAudio->ioStreamInfos[idx]); |
| 863 |
} |
| 864 |
|
| 865 |
#if 0 |
| 866 |
#pragma mark ====== Managing Audio Graph ====== |
| 867 |
#endif |
| 868 |
|
| 869 |
/* |
| 870 |
MDAudioIOStreamInfo *ip; |
| 871 |
MDAudioEffectChain *cp; |
| 872 |
MDAudioEffect *ep; |
| 873 |
|
| 874 |
ip->unit |
| 875 |
| |
| 876 |
+- cp->converterUnit |
| 877 |
| |--> [ ep->effect |
| 878 |
| | |--> {ep->converterUnit --->} ]n--+-->ip->effectMixerUnit |
| 879 |
| | (optional) | (optional) | |
| 880 |
| |--> [ ep->effect | | |
| 881 |
| | |--> {ep->converterUnit --->} ]n--+ | |
| 882 |
| ... (optional) | |
| 883 |
+- cp->converterUnit |
| 884 |
| |--> [ ep->effect | |
| 885 |
| | |--> {ep->converterUnit --->} ]n--+-->ip->effectMixerUnit |
| 886 |
| | (optional) | (optional) | |
| 887 |
| |--> [ ep->effect | | |
| 888 |
| | |--> {ep->converterUnit --->} ]n--+ | |
| 889 |
| ... (optional) | |
| 890 |
| v |
| 891 |
gAudio->mixer |
| 892 |
v |
| 893 |
gAudio->output |
| 894 |
*/ |
| 895 |
|
| 896 |
/* Disconnect the device output within the graph */ |
| 897 |
static int |
| 898 |
sMDAudioDisconnectDeviceOutput(int streamIndex) |
| 899 |
{ |
| 900 |
int i, result; |
| 901 |
AURenderCallbackStruct callback; |
| 902 |
MDAudioIOStreamInfo *ip = MDAudioGetIOStreamInfoAtIndex(streamIndex); |
| 903 |
MDAudioEffectChain *cp; |
| 904 |
if (ip == NULL) |
| 905 |
return -1; |
| 906 |
if (ip->deviceIndex >= kMDAudioMusicDeviceIndexOffset) { |
| 907 |
/* Music Device */ |
| 908 |
CHECK_ERR(result, AudioUnitRemoveRenderNotify(ip->unit, sMDAudioSendMIDIProc, ip)); |
| 909 |
for (i = 0; i < ip->nchains; i++) { |
| 910 |
cp = ip->chains + i; |
| 911 |
if (cp->alive) { |
| 912 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, cp->converterNode, 0)); |
| 913 |
} |
| 914 |
} |
| 915 |
} else { |
| 916 |
/* Audio Device */ |
| 917 |
/* Disable callback */ |
| 918 |
callback.inputProc = NULL; |
| 919 |
callback.inputProcRefCon = NULL; |
| 920 |
/* If effect chains are active, then remove the callback from |
| 921 |
each effect chain; otherwise, from the mixer. */ |
| 922 |
for (i = 0; i < ip->nchains; i++) { |
| 923 |
cp = ip->chains + i; |
| 924 |
if (cp->alive) { |
| 925 |
CHECK_ERR(result, AudioUnitSetProperty(cp->converterUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callback, sizeof(AURenderCallbackStruct))); |
| 926 |
} |
| 927 |
} |
| 928 |
/* Dispose the input AudioUnit */ |
| 929 |
// CHECK_ERR(result, AudioComponentInstanceDispose(ip->unit)); |
| 930 |
} |
| 931 |
return kMDNoError; |
| 932 |
exit: |
| 933 |
return kMDErrorCannotSetupAudio; |
| 934 |
} |
| 935 |
|
| 936 |
MDStatus |
| 937 |
MDAudioSelectIOStreamDevice(int idx, int deviceIndex) |
| 938 |
{ |
| 939 |
int i, n, len; |
| 940 |
OSStatus result = noErr; |
| 941 |
MDAudioIOStreamInfo *ip; |
| 942 |
MDAudioDeviceInfo *dp = NULL; |
| 943 |
MDAudioMusicDeviceInfo *mp = NULL; |
| 944 |
MDAudioEffectChain *cp; |
| 945 |
MDStatus sts; |
| 946 |
AudioDeviceID audioDeviceID; |
| 947 |
AURenderCallbackStruct callback; |
| 948 |
unsigned char midiSetupChanged = 0; |
| 949 |
|
| 950 |
ip = MDAudioGetIOStreamInfoAtIndex(idx); |
| 951 |
if (ip == NULL) |
| 952 |
return kMDErrorCannotSetupAudio; |
| 953 |
|
| 954 |
/* No change required? */ |
| 955 |
if (ip->deviceIndex == deviceIndex && ip->busIndex == idx) |
| 956 |
return kMDNoError; |
| 957 |
|
| 958 |
CHECK_ERR(result, AUGraphStop(gAudio->graph)); |
| 959 |
|
| 960 |
if (idx >= kMDAudioFirstIndexForOutputStream) { |
| 961 |
/* Output stream */ |
| 962 |
dp = MDAudioDeviceInfoAtIndex(deviceIndex, 0); |
| 963 |
ip->deviceIndex = -1; /* Will be overwritten later */ |
| 964 |
if (dp == NULL) { |
| 965 |
/* Leave the node as it is, just disabling the audio thru */ |
| 966 |
gAudio->isAudioThruEnabled = 0; |
| 967 |
ip->busIndex = -1; |
| 968 |
} else { |
| 969 |
if ((UInt64)(dp->deviceID) != ip->deviceID) { |
| 970 |
/* Set the output device to the output unit */ |
| 971 |
audioDeviceID = dp->deviceID; |
| 972 |
result = AudioUnitSetProperty(gAudio->outputUnit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &audioDeviceID, sizeof(AudioDeviceID)); |
| 973 |
if (result == noErr) { |
| 974 |
MDAudioFormat format; |
| 975 |
UInt32 propSize; |
| 976 |
/* Initialize the audio unit */ |
| 977 |
//CHECK_ERR(result, AudioUnitInitialize(gAudio->outputUnit)); |
| 978 |
/* Set the preferred format of the output unit to the output side of the converter unit */ |
| 979 |
propSize = sizeof(MDAudioFormat); |
| 980 |
CHECK_ERR(result, AudioUnitGetProperty(gAudio->outputUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Global, 0, &format, &propSize)); |
| 981 |
gAudio->isAudioThruEnabled = 1; |
| 982 |
ip->busIndex = idx; |
| 983 |
ip->deviceID = (UInt64)(dp->deviceID); |
| 984 |
ip->deviceIndex = deviceIndex; |
| 985 |
/* Reallocate buffer list */ |
| 986 |
ip->bufferSizeFrames = dp->bufferSizeFrames; /* The buffer size of the underlying audio device; NOTE: dp must be alive until here! */ |
| 987 |
if (ip->bufferList != NULL) |
| 988 |
sMDAudioReleaseMyBufferList(ip->bufferList); |
| 989 |
ip->bufferList = sMDAudioAllocateMyBufferList(gAudio->preferredFormat.mChannelsPerFrame, gAudio->preferredFormat.mBytesPerFrame, ip->bufferSizeFrames); |
| 990 |
/* Reallocate ring buffer */ |
| 991 |
if (ip->ring != NULL) |
| 992 |
MDRingBufferRelease(ip->ring); |
| 993 |
ip->ring = MDRingBufferNew(); |
| 994 |
MDRingBufferAllocate(ip->ring, gAudio->preferredFormat.mChannelsPerFrame, gAudio->preferredFormat.mBytesPerFrame, dp->bufferSizeFrames * 4); |
| 995 |
ip->firstInputTime = ip->firstOutputTime = -1; |
| 996 |
} else { |
| 997 |
gAudio->isAudioThruEnabled = 0; |
| 998 |
ip->busIndex = -1; |
| 999 |
} |
| 1000 |
} else { |
| 1001 |
gAudio->isAudioThruEnabled = 1; |
| 1002 |
ip->busIndex = idx; |
| 1003 |
ip->deviceIndex = deviceIndex; |
| 1004 |
} |
| 1005 |
} |
| 1006 |
} else { |
| 1007 |
/* Input stream */ |
| 1008 |
UInt64 newDeviceID; |
| 1009 |
AudioComponentDescription desc; |
| 1010 |
MDAudioFormat format = {0}; |
| 1011 |
if (deviceIndex >= kMDAudioMusicDeviceIndexOffset) { |
| 1012 |
mp = MDAudioMusicDeviceInfoAtIndex(deviceIndex - kMDAudioMusicDeviceIndexOffset); |
| 1013 |
newDeviceID = (mp != NULL ? mp->code : kMDAudioMusicDeviceUnknown); |
| 1014 |
if (mp != NULL) |
| 1015 |
format = mp->format; |
| 1016 |
} else { |
| 1017 |
dp = MDAudioDeviceInfoAtIndex(deviceIndex, 1); |
| 1018 |
newDeviceID = (dp != NULL ? (UInt64)(dp->deviceID) : kMDAudioMusicDeviceUnknown); |
| 1019 |
if (dp != NULL) |
| 1020 |
format = dp->format; |
| 1021 |
} |
| 1022 |
|
| 1023 |
if (ip->deviceID != kMDAudioMusicDeviceUnknown) { |
| 1024 |
/* Disable the current input */ |
| 1025 |
CHECK_ERR(result, sMDAudioDisconnectDeviceOutput(idx)); |
| 1026 |
if (ip->deviceIndex >= kMDAudioMusicDeviceIndexOffset) { |
| 1027 |
/* Remove from the graph */ |
| 1028 |
CHECK_ERR(result, AUGraphRemoveNode(gAudio->graph, ip->node)); |
| 1029 |
/* Dispose MIDI controller name and MIDI buffers */ |
| 1030 |
if (ip->midiControllerName != NULL) { |
| 1031 |
free(ip->midiControllerName); |
| 1032 |
ip->midiControllerName = NULL; |
| 1033 |
} |
| 1034 |
if (ip->midiBuffer != NULL) { |
| 1035 |
free(ip->midiBuffer); |
| 1036 |
ip->midiBuffer = NULL; |
| 1037 |
} |
| 1038 |
if (ip->bufferList != NULL) { |
| 1039 |
sMDAudioReleaseMyBufferList(ip->bufferList); |
| 1040 |
ip->bufferList = NULL; |
| 1041 |
} |
| 1042 |
if (ip->ring != NULL) { |
| 1043 |
MDRingBufferDeallocate(ip->ring); |
| 1044 |
ip->ring = NULL; |
| 1045 |
} |
| 1046 |
midiSetupChanged = 1; |
| 1047 |
} else { |
| 1048 |
/* Dispose the input AudioUnit */ |
| 1049 |
CHECK_ERR(result, AudioComponentInstanceDispose(ip->unit)); |
| 1050 |
} |
| 1051 |
ip->deviceID = kMDAudioMusicDeviceUnknown; |
| 1052 |
ip->node = 0; |
| 1053 |
ip->unit = NULL; |
| 1054 |
ip->deviceIndex = -1; |
| 1055 |
ip->busIndex = -1; |
| 1056 |
} |
| 1057 |
|
| 1058 |
if (newDeviceID != kMDAudioMusicDeviceUnknown) { |
| 1059 |
/* Enable the new input */ |
| 1060 |
/* Allocate the first converter unit if not present */ |
| 1061 |
if (ip->nchains == 0) { |
| 1062 |
ip->chains = (MDAudioEffectChain *)calloc(sizeof(MDAudioEffectChain), 8); |
| 1063 |
ip->nchains = 1; |
| 1064 |
} |
| 1065 |
if (ip->chains->converterUnit == NULL) { |
| 1066 |
/* Create converter for the effector chain 0 */ |
| 1067 |
cp = ip->chains; |
| 1068 |
desc.componentType = kAudioUnitType_FormatConverter; |
| 1069 |
desc.componentSubType = kAudioUnitSubType_AUConverter; |
| 1070 |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 1071 |
desc.componentFlags = desc.componentFlagsMask = 0; |
| 1072 |
CHECK_ERR(result, AUGraphAddNode(gAudio->graph, &desc, &cp->converterNode)); |
| 1073 |
/* Open component */ |
| 1074 |
CHECK_ERR(result, AUGraphOpen(gAudio->graph)); |
| 1075 |
CHECK_ERR(result, AUGraphNodeInfo(gAudio->graph, cp->converterNode, NULL, &cp->converterUnit)); |
| 1076 |
/* Set output format and connect to the mixer */ |
| 1077 |
CHECK_ERR(result, AudioUnitSetProperty(cp->converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &gAudio->preferredFormat, sizeof(AudioStreamBasicDescription))); |
| 1078 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, cp->converterNode, 0, gAudio->mixer, idx)); |
| 1079 |
} |
| 1080 |
if (deviceIndex >= kMDAudioMusicDeviceIndexOffset) { |
| 1081 |
/* Music Device */ |
| 1082 |
/* Create input node */ |
| 1083 |
desc.componentType = kAudioUnitType_MusicDevice; |
| 1084 |
desc.componentSubType = (UInt32)(newDeviceID >> 32); |
| 1085 |
desc.componentManufacturer = (UInt32)(newDeviceID); |
| 1086 |
desc.componentFlags = desc.componentFlagsMask = 0; |
| 1087 |
CHECK_ERR(result, AUGraphAddNode(gAudio->graph, &desc, &ip->node)); |
| 1088 |
/* Open component */ |
| 1089 |
CHECK_ERR(result, AUGraphOpen(gAudio->graph)); |
| 1090 |
CHECK_ERR(result, AUGraphNodeInfo(gAudio->graph, ip->node, NULL, &ip->unit)); |
| 1091 |
/* Connect the output to the converter unit(s) and set format */ |
| 1092 |
for (i = 0; i < ip->nchains; i++) { |
| 1093 |
cp = ip->chains + i; |
| 1094 |
result = AUGraphConnectNodeInput(gAudio->graph, ip->node, i, cp->converterNode, 0); |
| 1095 |
cp->alive = (result == noErr); |
| 1096 |
CHECK_ERR(result, AudioUnitSetProperty(cp->converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof(AudioStreamBasicDescription))); |
| 1097 |
} |
| 1098 |
/* Create the MIDI controller name */ |
| 1099 |
len = (int)strlen(mp->name) + 5; |
| 1100 |
ip->midiControllerName = (char *)malloc(len); |
| 1101 |
strcpy(ip->midiControllerName, mp->name); |
| 1102 |
/* Check the duplicate */ |
| 1103 |
for (i = 0, n = 2; i < kMDAudioNumberOfInputStreams; i++) { |
| 1104 |
MDAudioIOStreamInfo *ip2 = MDAudioGetIOStreamInfoAtIndex(i); |
| 1105 |
if (ip == ip2 || ip2->midiControllerName == NULL) |
| 1106 |
continue; |
| 1107 |
if (strcmp(ip->midiControllerName, ip2->midiControllerName) == 0) { |
| 1108 |
snprintf(ip->midiControllerName, len, "%s %d", mp->name, n); |
| 1109 |
n++; |
| 1110 |
i = -1; |
| 1111 |
continue; |
| 1112 |
} |
| 1113 |
} |
| 1114 |
/* Allocate buffer for MIDI scheduling */ |
| 1115 |
ip->midiBuffer = (unsigned char *)malloc(kMDAudioMIDIBufferSize); |
| 1116 |
ip->midiBufferWriteOffset = 0; |
| 1117 |
ip->midiBufferReadOffset = 0; |
| 1118 |
/* Set render notify callback */ |
| 1119 |
CHECK_ERR(result, AudioUnitAddRenderNotify(ip->unit, sMDAudioSendMIDIProc, ip)); |
| 1120 |
midiSetupChanged = 1; |
| 1121 |
} else { |
| 1122 |
/* Audio Device */ |
| 1123 |
/* Create HAL input unit (not connected to AUGraph) */ |
| 1124 |
/* Cf. Apple Technical Note 2091 */ |
| 1125 |
AudioComponent comp; |
| 1126 |
UInt32 unum; |
| 1127 |
desc.componentType = kAudioUnitType_Output; |
| 1128 |
desc.componentSubType = kAudioUnitSubType_HALOutput; |
| 1129 |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 1130 |
desc.componentFlags = 0; |
| 1131 |
desc.componentFlagsMask = 0; |
| 1132 |
comp = AudioComponentFindNext(NULL, &desc); |
| 1133 |
if (comp == NULL) |
| 1134 |
return kMDErrorCannotSetupAudio; |
| 1135 |
CHECK_ERR(result, AudioComponentInstanceNew(comp, &ip->unit)); |
| 1136 |
/* Enable input */ |
| 1137 |
unum = 1; |
| 1138 |
CHECK_ERR(result, AudioUnitSetProperty(ip->unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &unum, sizeof(UInt32))); |
| 1139 |
/* Disable output */ |
| 1140 |
unum = 0; |
| 1141 |
CHECK_ERR(result, AudioUnitSetProperty(ip->unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &unum, sizeof(UInt32))); |
| 1142 |
/* Set the HAL AU output format to the canonical format */ |
| 1143 |
CHECK_ERR(result, AudioUnitSetProperty(ip->unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &gAudio->preferredFormat, sizeof(AudioStreamBasicDescription))); |
| 1144 |
/* Set the AU callback function */ |
| 1145 |
callback.inputProc = sMDAudioInputProc; |
| 1146 |
callback.inputProcRefCon = ip; |
| 1147 |
CHECK_ERR(result, AudioUnitSetProperty(ip->unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callback, sizeof(AURenderCallbackStruct))); |
| 1148 |
/* Connect the output to the converter unit(s) and set format */ |
| 1149 |
callback.inputProc = sMDAudioPassProc; |
| 1150 |
callback.inputProcRefCon = ip; |
| 1151 |
for (i = 0; i < ip->nchains; i++) { |
| 1152 |
cp = ip->chains + i; |
| 1153 |
result = AudioUnitSetProperty(cp->converterUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, i, &callback, sizeof(AURenderCallbackStruct)); |
| 1154 |
cp->alive = (result == noErr); |
| 1155 |
CHECK_ERR(result, AudioUnitSetProperty(cp->converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &gAudio->preferredFormat, sizeof(AudioStreamBasicDescription))); |
| 1156 |
} |
| 1157 |
/* Set the input device */ |
| 1158 |
audioDeviceID = (AudioDeviceID)newDeviceID; |
| 1159 |
CHECK_ERR(result, AudioUnitSetProperty(ip->unit, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &audioDeviceID, sizeof(AudioDeviceID))); |
| 1160 |
/* Reallocate buffer list */ |
| 1161 |
ip->bufferSizeFrames = dp->bufferSizeFrames; /* The buffer size of the underlying audio device; NOTE: dp must be alive until here! */ |
| 1162 |
ip->bufferList = sMDAudioAllocateMyBufferList(gAudio->preferredFormat.mChannelsPerFrame, gAudio->preferredFormat.mBytesPerFrame, ip->bufferSizeFrames); |
| 1163 |
|
| 1164 |
/* Reallocate ring buffer */ |
| 1165 |
ip->ring = MDRingBufferNew(); |
| 1166 |
MDRingBufferAllocate(ip->ring, gAudio->preferredFormat.mChannelsPerFrame, gAudio->preferredFormat.mBytesPerFrame, ip->bufferSizeFrames * 20); |
| 1167 |
|
| 1168 |
ip->firstInputTime = ip->firstOutputTime = -1; |
| 1169 |
/* Initialize and start the AUHAL */ |
| 1170 |
CHECK_ERR(result, AudioUnitInitialize(ip->unit)); |
| 1171 |
CHECK_ERR(result, AudioOutputUnitStart(ip->unit)); |
| 1172 |
} |
| 1173 |
ip->deviceIndex = deviceIndex; |
| 1174 |
ip->busIndex = idx; |
| 1175 |
ip->format = format; |
| 1176 |
} |
| 1177 |
} |
| 1178 |
exit: |
| 1179 |
sts = (result == noErr ? kMDNoError : kMDErrorCannotSetupAudio); |
| 1180 |
if (sts == kMDNoError && midiSetupChanged) |
| 1181 |
MDPlayerNotificationCallback(); |
| 1182 |
result = AUGraphStart(gAudio->graph); |
| 1183 |
return (sts == kMDNoError && sts == noErr ? kMDNoError : kMDErrorCannotSetupAudio); |
| 1184 |
} |
| 1185 |
|
| 1186 |
MDStatus |
| 1187 |
MDAudioGetIOStreamDevice(int idx, int *outDeviceIndex) |
| 1188 |
{ |
| 1189 |
if (idx < 0 || idx >= kMDAudioNumberOfStreams) { |
| 1190 |
if (outDeviceIndex != NULL) |
| 1191 |
*outDeviceIndex = -1; |
| 1192 |
return kMDErrorCannotSetupAudio; |
| 1193 |
} |
| 1194 |
if (idx >= kMDAudioFirstIndexForOutputStream) { |
| 1195 |
if (!gAudio->isAudioThruEnabled) { |
| 1196 |
/* Behave as if no device is set */ |
| 1197 |
*outDeviceIndex = -1; |
| 1198 |
return kMDNoError; |
| 1199 |
} |
| 1200 |
} |
| 1201 |
if (outDeviceIndex != NULL) |
| 1202 |
*outDeviceIndex = gAudio->ioStreamInfos[idx].deviceIndex; |
| 1203 |
return kMDNoError; |
| 1204 |
} |
| 1205 |
|
| 1206 |
#pragma mark ====== Audio Effects ====== |
| 1207 |
|
| 1208 |
MDStatus |
| 1209 |
MDAudioAppendEffectChain(int streamIndex) |
| 1210 |
{ |
| 1211 |
int sts, result; |
| 1212 |
MDAudioIOStreamInfo *ip; |
| 1213 |
MDAudioEffectChain *cp; |
| 1214 |
MDAudioEffect *ep; |
| 1215 |
AudioComponentDescription desc; |
| 1216 |
AUNode node; |
| 1217 |
|
| 1218 |
if (streamIndex < 0 || streamIndex >= kMDAudioNumberOfInputStreams) |
| 1219 |
return -1; /* Invalid streamIndex */ |
| 1220 |
ip = MDAudioGetIOStreamInfoAtIndex(streamIndex); |
| 1221 |
if (ip->nchains >= 64) |
| 1222 |
return -2; /* Too many chains */ |
| 1223 |
if (ip->nchains % 8 == 0) { |
| 1224 |
/* Allocate storage */ |
| 1225 |
void *p = realloc(ip->chains, sizeof(MDAudioEffectChain) * (ip->nchains + 8)); |
| 1226 |
if (p == NULL) |
| 1227 |
return -3; /* Out of memory */ |
| 1228 |
ip->chains = p; |
| 1229 |
} |
| 1230 |
cp = ip->chains + ip->nchains; |
| 1231 |
memset(cp, 0, sizeof(MDAudioEffectChain)); |
| 1232 |
ip->nchains++; |
| 1233 |
|
| 1234 |
CHECK_ERR(result, AUGraphStop(gAudio->graph)); |
| 1235 |
|
| 1236 |
/* Create a converter (which is the entrance of the chain) */ |
| 1237 |
desc.componentType = kAudioUnitType_FormatConverter; |
| 1238 |
desc.componentSubType = kAudioUnitSubType_AUConverter; |
| 1239 |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 1240 |
desc.componentFlags = desc.componentFlagsMask = 0; |
| 1241 |
CHECK_ERR(result, AUGraphAddNode(gAudio->graph, &desc, &cp->converterNode)); |
| 1242 |
/* Open component */ |
| 1243 |
CHECK_ERR(result, AUGraphOpen(gAudio->graph)); |
| 1244 |
CHECK_ERR(result, AUGraphNodeInfo(gAudio->graph, cp->converterNode, NULL, &cp->converterUnit)); |
| 1245 |
/* Set output format */ |
| 1246 |
CHECK_ERR(result, AudioUnitSetProperty(cp->converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &gAudio->preferredFormat, sizeof(AudioStreamBasicDescription))); |
| 1247 |
/* Create a mixer for this chain (when nchains == 2) */ |
| 1248 |
if (ip->nchains == 2) { |
| 1249 |
desc.componentType = kAudioUnitType_Mixer; |
| 1250 |
desc.componentSubType = kAudioUnitSubType_StereoMixer; |
| 1251 |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 1252 |
desc.componentFlags = desc.componentFlagsMask = 0; |
| 1253 |
CHECK_ERR(result, AUGraphAddNode(gAudio->graph, &desc, &ip->effectMixerNode)); |
| 1254 |
CHECK_ERR(result, AUGraphOpen(gAudio->graph)); |
| 1255 |
CHECK_ERR(result, AUGraphNodeInfo(gAudio->graph, ip->effectMixerNode, NULL, &ip->effectMixerUnit)); |
| 1256 |
CHECK_ERR(result, AudioUnitSetProperty(ip->effectMixerUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Global, 0, &gAudio->preferredFormat, sizeof(AudioStreamBasicDescription))); |
| 1257 |
/* Disconnect the last unit in chain 0 from gAudio->mixer and |
| 1258 |
reconnect to the ip->effectMixerNode */ |
| 1259 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, gAudio->mixer, streamIndex)); |
| 1260 |
if (ip->chains->neffects == 0) |
| 1261 |
node = ip->chains->converterNode; |
| 1262 |
else { |
| 1263 |
/* Last effect entry */ |
| 1264 |
ep = ip->chains->effects + ip->chains->neffects - 1; |
| 1265 |
if (ep->converterUnit != NULL) |
| 1266 |
node = ep->converterNode; |
| 1267 |
else node = ep->node; |
| 1268 |
} |
| 1269 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, node, 0, ip->effectMixerNode, 0)); |
| 1270 |
/* Connect ip->effectMixerNode to gAudio->mixer */ |
| 1271 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, ip->effectMixerNode, 0, gAudio->mixer, streamIndex)); |
| 1272 |
} |
| 1273 |
/* Connect cp->converterNode to ip->effectMixerNode */ |
| 1274 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, cp->converterNode, 0, ip->effectMixerNode, ip->nchains - 1)); |
| 1275 |
/* Connect ip->unit to cp->converterNode */ |
| 1276 |
if (ip->deviceIndex >= kMDAudioMusicDeviceIndexOffset) { |
| 1277 |
/* Music device */ |
| 1278 |
UInt32 propSize, count; |
| 1279 |
propSize = sizeof(UInt32); |
| 1280 |
CHECK_ERR(result, AudioUnitGetProperty(ip->unit, kAudioUnitProperty_ElementCount, kAudioUnitScope_Output, ip->node, &count, &propSize)); |
| 1281 |
if (count >= ip->nchains) { |
| 1282 |
AudioStreamBasicDescription format; |
| 1283 |
cp->alive = 1; |
| 1284 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, ip->node, ip->nchains - 1, cp->converterNode, 0)); |
| 1285 |
propSize = sizeof(AudioStreamBasicDescription); |
| 1286 |
CHECK_ERR(result, AudioUnitGetProperty(ip->unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, ip->nchains - 1, &format, &propSize)); |
| 1287 |
CHECK_ERR(result, AudioUnitSetProperty(cp->converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, propSize)); |
| 1288 |
} else cp->alive = 0; |
| 1289 |
} else { |
| 1290 |
/* HAL: we do not support more than one stereo bus (for now) */ |
| 1291 |
cp->alive = 0; |
| 1292 |
} |
| 1293 |
|
| 1294 |
exit: |
| 1295 |
sts = (result == noErr ? kMDNoError : kMDErrorCannotSetupAudio); |
| 1296 |
result = AUGraphStart(gAudio->graph); |
| 1297 |
return (result == noErr && sts == kMDNoError ? 0 : kMDErrorCannotSetupAudio); |
| 1298 |
} |
| 1299 |
|
| 1300 |
MDStatus |
| 1301 |
MDAudioRemoveLastEffectChain(int streamIndex) |
| 1302 |
{ |
| 1303 |
int sts, result; |
| 1304 |
MDAudioIOStreamInfo *ip; |
| 1305 |
MDAudioEffectChain *cp; |
| 1306 |
MDAudioEffect *ep; |
| 1307 |
AUNode node; |
| 1308 |
|
| 1309 |
if (streamIndex < 0 || streamIndex >= kMDAudioNumberOfInputStreams) |
| 1310 |
return -1; /* Invalid streamIndex */ |
| 1311 |
ip = MDAudioGetIOStreamInfoAtIndex(streamIndex); |
| 1312 |
if (ip->nchains <= 1) |
| 1313 |
return -2; /* At least one chain should be present */ |
| 1314 |
cp = ip->chains + (ip->nchains - 1); |
| 1315 |
if (cp->neffects > 0) |
| 1316 |
return -3; /* The chain should be empty */ |
| 1317 |
ip->nchains--; |
| 1318 |
|
| 1319 |
CHECK_ERR(result, AUGraphStop(gAudio->graph)); |
| 1320 |
|
| 1321 |
/* Disonnect cp->converterNode from ip->effectMixerNode */ |
| 1322 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, ip->effectMixerNode, ip->nchains)); |
| 1323 |
/* Disonnect ip->unit from cp->converterNode */ |
| 1324 |
if (cp->alive != 0) { |
| 1325 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, cp->converterNode, 0)); |
| 1326 |
} |
| 1327 |
/* Dispose cp->converterNode */ |
| 1328 |
CHECK_ERR(result, AUGraphRemoveNode(gAudio->graph, cp->converterNode)); |
| 1329 |
cp->converterNode = 0; |
| 1330 |
cp->converterUnit = NULL; |
| 1331 |
|
| 1332 |
/* Dispose the mixer for this chain (when nchains was 2) */ |
| 1333 |
if (ip->nchains == 1) { |
| 1334 |
/* Disonnect ip->effectMixerNode from gAudio->mixer */ |
| 1335 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, gAudio->mixer, streamIndex)); |
| 1336 |
/* Disconnect the last unit in chain 0 from ip->effectMixerNode and |
| 1337 |
reconnect to gAudio->mixer */ |
| 1338 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, ip->effectMixerNode, 0)); |
| 1339 |
if (ip->chains->neffects == 0) |
| 1340 |
node = ip->chains->converterNode; |
| 1341 |
else { |
| 1342 |
/* Last effect entry */ |
| 1343 |
ep = ip->chains->effects + ip->chains->neffects - 1; |
| 1344 |
if (ep->converterUnit != NULL) |
| 1345 |
node = ep->converterNode; |
| 1346 |
else node = ep->node; |
| 1347 |
} |
| 1348 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, node, 0, gAudio->mixer, streamIndex)); |
| 1349 |
/* Dispose the mixer */ |
| 1350 |
CHECK_ERR(result, AUGraphRemoveNode(gAudio->graph, ip->effectMixerNode)); |
| 1351 |
ip->effectMixerNode = 0; |
| 1352 |
ip->effectMixerUnit = NULL; |
| 1353 |
} |
| 1354 |
|
| 1355 |
exit: |
| 1356 |
sts = (result == noErr ? kMDNoError : kMDErrorCannotSetupAudio); |
| 1357 |
result = AUGraphStart(gAudio->graph); |
| 1358 |
return (result == noErr && sts == kMDNoError ? 0 : kMDErrorCannotSetupAudio); |
| 1359 |
} |
| 1360 |
|
| 1361 |
MDStatus |
| 1362 |
MDAudioChangeEffect(int streamIndex, int chainIndex, int effectIndex, int effectID, int insert) |
| 1363 |
{ |
| 1364 |
int sts, result; |
| 1365 |
MDAudioIOStreamInfo *ip; |
| 1366 |
MDAudioEffectChain *cp; |
| 1367 |
MDAudioEffect *ep; |
| 1368 |
MDAudioMusicDeviceInfo *mp, *lastmp, *nextmp; |
| 1369 |
AudioComponentDescription desc; |
| 1370 |
AUNode lastNode, nextNode; |
| 1371 |
int nextNodeBus; |
| 1372 |
|
| 1373 |
if (streamIndex < 0 || streamIndex >= kMDAudioNumberOfInputStreams) |
| 1374 |
return -1; /* Invalid streamIndex */ |
| 1375 |
ip = MDAudioGetIOStreamInfoAtIndex(streamIndex); |
| 1376 |
if (chainIndex < 0 || chainIndex >= ip->nchains) |
| 1377 |
return -2; /* Invalid chainIndex */ |
| 1378 |
mp = MDAudioEffectDeviceInfoAtIndex(effectID); |
| 1379 |
if (mp == NULL) |
| 1380 |
return -3; /* effectID out of range */ |
| 1381 |
cp = ip->chains + chainIndex; |
| 1382 |
if (effectIndex < 0 || effectIndex > cp->neffects) |
| 1383 |
return -4; /* Invalid effectIndex */ |
| 1384 |
if (effectIndex == cp->neffects && insert == 0) |
| 1385 |
return -4; /* Invalid effectIndex */ |
| 1386 |
if (insert) { |
| 1387 |
if (cp->neffects % 8 == 0) { |
| 1388 |
/* Allocate storage */ |
| 1389 |
void *p = realloc(cp->effects, sizeof(MDAudioEffect) * (cp->neffects + 8)); |
| 1390 |
if (p == NULL) |
| 1391 |
return -5; /* Out of memory */ |
| 1392 |
cp->effects = p; |
| 1393 |
} |
| 1394 |
ep = cp->effects + effectIndex; |
| 1395 |
if (effectIndex < cp->neffects) { |
| 1396 |
memmove(ep + 1, ep, sizeof(MDAudioEffect) * (cp->neffects - effectIndex)); |
| 1397 |
} |
| 1398 |
cp->neffects++; |
| 1399 |
memset(ep, 0, sizeof(MDAudioEffect)); |
| 1400 |
} else { |
| 1401 |
ep = cp->effects + effectIndex; |
| 1402 |
if (ep->effectDeviceIndex == effectID) |
| 1403 |
return 0; /* Same effect: no action */ |
| 1404 |
} |
| 1405 |
|
| 1406 |
CHECK_ERR(result, AUGraphStop(gAudio->graph)); |
| 1407 |
|
| 1408 |
/* Specify next nodes and disable the existing connection to it */ |
| 1409 |
if (effectIndex == cp->neffects - 1) { |
| 1410 |
if (ip->effectMixerUnit != NULL) { |
| 1411 |
nextNode = ip->effectMixerNode; |
| 1412 |
nextNodeBus = chainIndex; |
| 1413 |
} else { |
| 1414 |
nextNode = gAudio->mixer; |
| 1415 |
nextNodeBus = streamIndex; |
| 1416 |
} |
| 1417 |
} else { |
| 1418 |
nextNode = ep[1].node; |
| 1419 |
nextNodeBus = 0; |
| 1420 |
} |
| 1421 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, nextNode, nextNodeBus)); |
| 1422 |
|
| 1423 |
if (!insert) { |
| 1424 |
/* Disable the connection to the converter, if present */ |
| 1425 |
if (ep->converterUnit != NULL) { |
| 1426 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, ep->converterNode, 0)); |
| 1427 |
/* Note: the converter unit will be disposed later, if it is unnecessary */ |
| 1428 |
} |
| 1429 |
/* Disable the existing connection to the present node */ |
| 1430 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, ep->node, 0)); |
| 1431 |
/* Dispose the present node */ |
| 1432 |
CHECK_ERR(result, AUGraphRemoveNode(gAudio->graph, ep->node)); |
| 1433 |
ep->node = 0; |
| 1434 |
ep->unit = NULL; |
| 1435 |
} |
| 1436 |
|
| 1437 |
/* Create a new effect node */ |
| 1438 |
desc.componentType = kAudioUnitType_Effect; |
| 1439 |
desc.componentSubType = (UInt32)(mp->code >> 32); |
| 1440 |
desc.componentManufacturer = (UInt32)(mp->code); |
| 1441 |
desc.componentFlags = desc.componentFlagsMask = 0; |
| 1442 |
CHECK_ERR(result, AUGraphAddNode(gAudio->graph, &desc, &ep->node)); |
| 1443 |
CHECK_ERR(result, AUGraphOpen(gAudio->graph)); |
| 1444 |
CHECK_ERR(result, AUGraphNodeInfo(gAudio->graph, ep->node, NULL, &ep->unit)); |
| 1445 |
|
| 1446 |
if (effectIndex == 0) { |
| 1447 |
lastNode = cp->converterNode; |
| 1448 |
lastmp = NULL; |
| 1449 |
/* Set output format of the converter unit */ |
| 1450 |
CHECK_ERR(result, AudioUnitSetProperty(cp->converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &mp->format, sizeof(AudioStreamBasicDescription))); |
| 1451 |
} else { |
| 1452 |
lastmp = MDAudioEffectDeviceInfoAtIndex(ep[-1].effectDeviceIndex); |
| 1453 |
if (sMDAudioCompareFormat(&mp->format, &lastmp->format)) { |
| 1454 |
/* We do not need converter in the last effect block */ |
| 1455 |
if (ep[-1].converterUnit != NULL) { |
| 1456 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, ep[-1].converterNode, 0)); |
| 1457 |
CHECK_ERR(result, AUGraphRemoveNode(gAudio->graph, ep[-1].converterNode)); |
| 1458 |
ep[-1].converterNode = 0; |
| 1459 |
ep[-1].converterUnit = NULL; |
| 1460 |
} |
| 1461 |
lastNode = ep[-1].node; |
| 1462 |
} else { |
| 1463 |
/* We do need converter */ |
| 1464 |
if (ep[-1].converterUnit == NULL) { |
| 1465 |
/* Create ep->converterNode */ |
| 1466 |
desc.componentType = kAudioUnitType_FormatConverter; |
| 1467 |
desc.componentSubType = kAudioUnitSubType_AUConverter; |
| 1468 |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 1469 |
desc.componentFlags = desc.componentFlagsMask = 0; |
| 1470 |
CHECK_ERR(result, AUGraphAddNode(gAudio->graph, &desc, &ep[-1].converterNode)); |
| 1471 |
/* Open component */ |
| 1472 |
CHECK_ERR(result, AUGraphOpen(gAudio->graph)); |
| 1473 |
CHECK_ERR(result, AUGraphNodeInfo(gAudio->graph, ep[-1].converterNode, NULL, &ep[-1].converterUnit)); |
| 1474 |
/* Set input format */ |
| 1475 |
CHECK_ERR(result, AudioUnitSetProperty(ep[-1].converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &lastmp->format, sizeof(AudioStreamBasicDescription))); |
| 1476 |
/* Reconnect effect->converter->next */ |
| 1477 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, ep[-1].node, 0, ep[-1].converterNode, 0)); |
| 1478 |
} |
| 1479 |
lastNode = ep[-1].converterNode; |
| 1480 |
/* Set output format of the converter unit */ |
| 1481 |
CHECK_ERR(result, AudioUnitSetProperty(ep[-1].converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, &mp->format, sizeof(AudioStreamBasicDescription))); |
| 1482 |
} |
| 1483 |
} |
| 1484 |
/* Connect last node */ |
| 1485 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, lastNode, 0, ep->node, 0)); |
| 1486 |
/* Do we need a converter after the effect? */ |
| 1487 |
if (effectIndex >= cp->neffects - 1) { |
| 1488 |
nextmp = NULL; |
| 1489 |
result = mp->acceptsCanonicalFormat; |
| 1490 |
} else { |
| 1491 |
nextmp = MDAudioEffectDeviceInfoAtIndex(ep[1].effectDeviceIndex); |
| 1492 |
result = sMDAudioCompareFormat(&mp->format, &nextmp->format); |
| 1493 |
} |
| 1494 |
if (result == 0) { |
| 1495 |
/* We do need a converter */ |
| 1496 |
if (ep->converterUnit == NULL) { |
| 1497 |
desc.componentType = kAudioUnitType_FormatConverter; |
| 1498 |
desc.componentSubType = kAudioUnitSubType_AUConverter; |
| 1499 |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 1500 |
desc.componentFlags = desc.componentFlagsMask = 0; |
| 1501 |
CHECK_ERR(result, AUGraphAddNode(gAudio->graph, &desc, &ep->converterNode)); |
| 1502 |
/* Open component */ |
| 1503 |
CHECK_ERR(result, AUGraphOpen(gAudio->graph)); |
| 1504 |
CHECK_ERR(result, AUGraphNodeInfo(gAudio->graph, ep->converterNode, NULL, &ep->converterUnit)); |
| 1505 |
} |
| 1506 |
/* Set output format */ |
| 1507 |
CHECK_ERR(result, AudioUnitSetProperty(ep->converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, (nextmp == NULL ? &gAudio->preferredFormat : &nextmp->format), sizeof(AudioStreamBasicDescription))); |
| 1508 |
/* Set input format */ |
| 1509 |
CHECK_ERR(result, AudioUnitSetProperty(ep->converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &mp->format, sizeof(AudioStreamBasicDescription))); |
| 1510 |
/* Connect effect to converter */ |
| 1511 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, ep->node, 0, ep->converterNode, 0)); |
| 1512 |
/* Connect converter to the next node */ |
| 1513 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, ep->converterNode, 0, nextNode, nextNodeBus)); |
| 1514 |
} else { |
| 1515 |
/* Connect effect to the next node */ |
| 1516 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, ep->node, 0, nextNode, nextNodeBus)); |
| 1517 |
/* Dispose the existing converter, as we do not need it */ |
| 1518 |
if (ep->converterUnit != NULL) { |
| 1519 |
CHECK_ERR(result, AUGraphRemoveNode(gAudio->graph, ep->converterNode)); |
| 1520 |
ep->converterNode = 0; |
| 1521 |
ep->converterUnit = NULL; |
| 1522 |
} |
| 1523 |
} |
| 1524 |
ep->effectDeviceIndex = effectID; |
| 1525 |
ep->name = strdup(mp->name); |
| 1526 |
|
| 1527 |
exit: |
| 1528 |
sts = (result == noErr ? kMDNoError : kMDErrorCannotSetupAudio); |
| 1529 |
result = AUGraphStart(gAudio->graph); |
| 1530 |
return (result == noErr && sts == kMDNoError ? 0 : kMDErrorCannotSetupAudio); |
| 1531 |
} |
| 1532 |
|
| 1533 |
MDStatus |
| 1534 |
MDAudioRemoveEffect(int streamIndex, int chainIndex, int effectIndex) |
| 1535 |
{ |
| 1536 |
int sts, result; |
| 1537 |
MDAudioIOStreamInfo *ip; |
| 1538 |
MDAudioEffectChain *cp; |
| 1539 |
MDAudioEffect *ep; |
| 1540 |
MDAudioMusicDeviceInfo *mp, *lastmp; |
| 1541 |
AudioStreamBasicDescription *fmtp; |
| 1542 |
AudioComponentDescription desc; |
| 1543 |
AUNode lastNode, nextNode; |
| 1544 |
int nextNodeBus; |
| 1545 |
|
| 1546 |
if (streamIndex < 0 || streamIndex >= kMDAudioNumberOfInputStreams) |
| 1547 |
return -1; /* Invalid streamIndex */ |
| 1548 |
ip = MDAudioGetIOStreamInfoAtIndex(streamIndex); |
| 1549 |
if (chainIndex < 0 || chainIndex >= ip->nchains) |
| 1550 |
return -2; /* Invalid chainIndex */ |
| 1551 |
cp = ip->chains + chainIndex; |
| 1552 |
if (effectIndex < 0 || effectIndex >= cp->neffects) |
| 1553 |
return -4; /* Invalid effectIndex */ |
| 1554 |
ep = cp->effects + effectIndex; |
| 1555 |
|
| 1556 |
CHECK_ERR(result, AUGraphStop(gAudio->graph)); |
| 1557 |
|
| 1558 |
/* Specify next nodes and disable the existing connection to it */ |
| 1559 |
if (effectIndex == cp->neffects - 1) { |
| 1560 |
mp = NULL; |
| 1561 |
if (ip->effectMixerUnit != NULL) { |
| 1562 |
nextNode = ip->effectMixerNode; |
| 1563 |
nextNodeBus = chainIndex; |
| 1564 |
} else { |
| 1565 |
nextNode = gAudio->mixer; |
| 1566 |
nextNodeBus = streamIndex; |
| 1567 |
} |
| 1568 |
fmtp = &gAudio->preferredFormat; |
| 1569 |
} else { |
| 1570 |
nextNode = ep[1].node; |
| 1571 |
nextNodeBus = 0; |
| 1572 |
mp = MDAudioEffectDeviceInfoAtIndex(ep[1].effectDeviceIndex); |
| 1573 |
fmtp = &mp->format; |
| 1574 |
} |
| 1575 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, nextNode, nextNodeBus)); |
| 1576 |
|
| 1577 |
/* Disable the connection to the converter, if present */ |
| 1578 |
if (ep->converterUnit != NULL) { |
| 1579 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, ep->converterNode, 0)); |
| 1580 |
CHECK_ERR(result, AUGraphRemoveNode(gAudio->graph, ep->converterNode)); |
| 1581 |
} |
| 1582 |
|
| 1583 |
/* Disable the existing connection to the present node */ |
| 1584 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, ep->node, 0)); |
| 1585 |
/* Dispose the present node */ |
| 1586 |
CHECK_ERR(result, AUGraphRemoveNode(gAudio->graph, ep->node)); |
| 1587 |
|
| 1588 |
/* Specify the last node */ |
| 1589 |
if (effectIndex == 0) { |
| 1590 |
lastNode = cp->converterNode; |
| 1591 |
lastmp = NULL; |
| 1592 |
/* Set output format of the converter unit */ |
| 1593 |
CHECK_ERR(result, AudioUnitSetProperty(cp->converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, fmtp, sizeof(AudioStreamBasicDescription))); |
| 1594 |
} else { |
| 1595 |
lastmp = MDAudioEffectDeviceInfoAtIndex(ep[-1].effectDeviceIndex); |
| 1596 |
if (sMDAudioCompareFormat(fmtp, &lastmp->format)) { |
| 1597 |
/* We do not need converter in the last effect block */ |
| 1598 |
if (ep[-1].converterUnit != NULL) { |
| 1599 |
CHECK_ERR(result, AUGraphDisconnectNodeInput(gAudio->graph, ep[-1].converterNode, 0)); |
| 1600 |
CHECK_ERR(result, AUGraphRemoveNode(gAudio->graph, ep[-1].converterNode)); |
| 1601 |
ep[-1].converterNode = 0; |
| 1602 |
ep[-1].converterUnit = NULL; |
| 1603 |
} |
| 1604 |
lastNode = ep[-1].node; |
| 1605 |
} else { |
| 1606 |
/* We do need converter */ |
| 1607 |
if (ep[-1].converterUnit == NULL) { |
| 1608 |
/* Create ep->converterNode */ |
| 1609 |
desc.componentType = kAudioUnitType_FormatConverter; |
| 1610 |
desc.componentSubType = kAudioUnitSubType_AUConverter; |
| 1611 |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 1612 |
desc.componentFlags = desc.componentFlagsMask = 0; |
| 1613 |
CHECK_ERR(result, AUGraphAddNode(gAudio->graph, &desc, &ep[-1].converterNode)); |
| 1614 |
/* Open component */ |
| 1615 |
CHECK_ERR(result, AUGraphOpen(gAudio->graph)); |
| 1616 |
CHECK_ERR(result, AUGraphNodeInfo(gAudio->graph, ep[-1].converterNode, NULL, &ep[-1].converterUnit)); |
| 1617 |
/* Set input format */ |
| 1618 |
CHECK_ERR(result, AudioUnitSetProperty(ep[-1].converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &lastmp->format, sizeof(AudioStreamBasicDescription))); |
| 1619 |
/* Reconnect effect->converter->next */ |
| 1620 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, ep[-1].node, 0, ep[-1].converterNode, 0)); |
| 1621 |
} |
| 1622 |
lastNode = ep[-1].converterNode; |
| 1623 |
/* Set output format of the converter unit */ |
| 1624 |
CHECK_ERR(result, AudioUnitSetProperty(ep[-1].converterUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 0, fmtp, sizeof(AudioStreamBasicDescription))); |
| 1625 |
} |
| 1626 |
} |
| 1627 |
/* Connect last node */ |
| 1628 |
CHECK_ERR(result, AUGraphConnectNodeInput(gAudio->graph, lastNode, 0, nextNode, nextNodeBus)); |
| 1629 |
|
| 1630 |
exit: |
| 1631 |
if (effectIndex < cp->neffects - 1) { |
| 1632 |
memmove(ep, ep + 1, sizeof(MDAudioEffect) * (cp->neffects - effectIndex - 1)); |
| 1633 |
} |
| 1634 |
cp->neffects--; |
| 1635 |
sts = (result == noErr ? kMDNoError : kMDErrorCannotSetupAudio); |
| 1636 |
result = AUGraphStart(gAudio->graph); |
| 1637 |
return (result == noErr && sts == kMDNoError ? 0 : kMDErrorCannotSetupAudio); |
| 1638 |
} |
| 1639 |
|
| 1640 |
#if 0 |
| 1641 |
#pragma mark ====== Initialize Audio ====== |
| 1642 |
#endif |
| 1643 |
|
| 1644 |
MDStatus |
| 1645 |
MDAudioInitialize(void) |
| 1646 |
{ |
| 1647 |
AudioComponentDescription desc; |
| 1648 |
AURenderCallbackStruct callback; |
| 1649 |
OSStatus err; |
| 1650 |
UInt32 unum; |
| 1651 |
int i; |
| 1652 |
|
| 1653 |
if (gAudio != NULL) |
| 1654 |
return kMDNoError; |
| 1655 |
gAudio = (MDAudio *)malloc(sizeof(MDAudio)); |
| 1656 |
if (gAudio == NULL) |
| 1657 |
return kMDErrorOutOfMemory; |
| 1658 |
memset(gAudio, 0, sizeof(MDAudio)); |
| 1659 |
|
| 1660 |
/* The preferred audio format */ |
| 1661 |
MDAudioFormatSetCanonical(&gAudio->preferredFormat, 44100.0f, 2, 0); |
| 1662 |
|
| 1663 |
/* Initialize IOStreamInfo */ |
| 1664 |
for (i = 0; i < kMDAudioNumberOfStreams; i++) { |
| 1665 |
MDAudioIOStreamInfo *ip = &(gAudio->ioStreamInfos[i]); |
| 1666 |
ip->deviceIndex = -1; |
| 1667 |
ip->busIndex = -1; |
| 1668 |
ip->pan = 0.5; |
| 1669 |
ip->volume = 1.0; |
| 1670 |
} |
| 1671 |
|
| 1672 |
/* Load audio device info */ |
| 1673 |
MDAudioUpdateDeviceInfo(); |
| 1674 |
|
| 1675 |
MyAppCallback_startupMessage("Creating AudioUnit Graph..."); |
| 1676 |
|
| 1677 |
/* Create AUGraph */ |
| 1678 |
CHECK_ERR(err, NewAUGraph(&gAudio->graph)); |
| 1679 |
|
| 1680 |
/* Mixer */ |
| 1681 |
desc.componentType = kAudioUnitType_Mixer; |
| 1682 |
desc.componentSubType = kAudioUnitSubType_StereoMixer; |
| 1683 |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 1684 |
desc.componentFlags = desc.componentFlagsMask = 0; |
| 1685 |
CHECK_ERR(err, AUGraphAddNode(gAudio->graph, &desc, &gAudio->mixer)); |
| 1686 |
|
| 1687 |
/* Output */ |
| 1688 |
desc.componentType = kAudioUnitType_Output; |
| 1689 |
desc.componentSubType = kAudioUnitSubType_DefaultOutput; |
| 1690 |
desc.componentManufacturer = kAudioUnitManufacturer_Apple; |
| 1691 |
CHECK_ERR(err, AUGraphAddNode(gAudio->graph, &desc, &gAudio->output)); |
| 1692 |
|
| 1693 |
/* Open graph and load components */ |
| 1694 |
CHECK_ERR(err, AUGraphOpen(gAudio->graph)); |
| 1695 |
CHECK_ERR(err, AUGraphNodeInfo(gAudio->graph, gAudio->mixer, &desc, &gAudio->mixerUnit)); |
| 1696 |
CHECK_ERR(err, AUGraphNodeInfo(gAudio->graph, gAudio->output, &desc, &gAudio->outputUnit)); |
| 1697 |
|
| 1698 |
/* Set the canonical format to the mixer unit */ |
| 1699 |
CHECK_ERR(err, AudioUnitSetProperty(gAudio->mixerUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Global, 0, &gAudio->preferredFormat, sizeof(AudioStreamBasicDescription))); |
| 1700 |
|
| 1701 |
/* Set the AU callback function for the output unit */ |
| 1702 |
/* (Read output from the converter and pass to the output _and_ record to the file) */ |
| 1703 |
callback.inputProc = sMDAudioRecordProc; |
| 1704 |
callback.inputProcRefCon = &(gAudio->ioStreamInfos[kMDAudioFirstIndexForOutputStream]); |
| 1705 |
CHECK_ERR(err, AudioUnitSetProperty(gAudio->outputUnit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callback, sizeof(AURenderCallbackStruct))); |
| 1706 |
|
| 1707 |
/* Enable metering for the stereo mixer */ |
| 1708 |
unum = 1; |
| 1709 |
CHECK_ERR(err, AudioUnitSetProperty(gAudio->mixerUnit, kAudioUnitProperty_MeteringMode, kAudioUnitScope_Global, 0, &unum, sizeof(UInt32))); |
| 1710 |
|
| 1711 |
CHECK_ERR(err, AUGraphInitialize(gAudio->graph)); |
| 1712 |
CHECK_ERR(err, AUGraphStart(gAudio->graph)); |
| 1713 |
|
| 1714 |
{ |
| 1715 |
int deviceIndex; |
| 1716 |
MDStatus sts; |
| 1717 |
UInt64 code; |
| 1718 |
/* Open 2 instances of DLS synthesizer */ |
| 1719 |
MyAppCallback_startupMessage("Initializing Internal Synthesizer..."); |
| 1720 |
code = ((UInt64)kAudioUnitSubType_DLSSynth << 32) + (UInt64)kAudioUnitManufacturer_Apple; |
| 1721 |
MDAudioMusicDeviceInfoForCode(code, &deviceIndex); |
| 1722 |
if (deviceIndex >= 0) { |
| 1723 |
sts = MDAudioSelectIOStreamDevice(0, deviceIndex + kMDAudioMusicDeviceIndexOffset); |
| 1724 |
if (sts == 0) |
| 1725 |
sts = MDAudioSelectIOStreamDevice(1, deviceIndex + kMDAudioMusicDeviceIndexOffset); |
| 1726 |
if (sts != 0) |
| 1727 |
return sts; |
| 1728 |
} |
| 1729 |
} |
| 1730 |
|
| 1731 |
/* Set built-in output as the audio output */ |
| 1732 |
CHECK_ERR(err, MDAudioSelectIOStreamDevice(kMDAudioFirstIndexForOutputStream, 0)); |
| 1733 |
|
| 1734 |
return 0; |
| 1735 |
|
| 1736 |
exit: |
| 1737 |
return kMDErrorCannotSetupAudio; |
| 1738 |
} |
| 1739 |
|
| 1740 |
MDStatus |
| 1741 |
MDAudioDispose(void) |
| 1742 |
{ |
| 1743 |
int idx; |
| 1744 |
OSStatus err; |
| 1745 |
for (idx = 0; idx < kMDAudioNumberOfStreams; idx++) |
| 1746 |
MDAudioSelectIOStreamDevice(idx, -1); |
| 1747 |
CHECK_ERR(err, AUGraphStop(gAudio->graph)); |
| 1748 |
CHECK_ERR(err, AUGraphClose(gAudio->graph)); |
| 1749 |
gAudio->graph = NULL; |
| 1750 |
return kMDNoError; |
| 1751 |
exit: |
| 1752 |
return kMDErrorCannotSetupAudio; |
| 1753 |
} |
| 1754 |
|
| 1755 |
#pragma mark ====== Start/Stop Audio input/output ====== |
| 1756 |
|
| 1757 |
MDStatus |
| 1758 |
MDAudioGetMixerBusAttributes(int idx, float *outPan, float *outVolume, float *outAmpLeft, float *outAmpRight, float *outPeakLeft, float *outPeakRight) |
| 1759 |
{ |
| 1760 |
OSStatus err; |
| 1761 |
Float32 f32; |
| 1762 |
int scope, mixerIndex; |
| 1763 |
if (idx >= 0 && idx < kMDAudioNumberOfInputStreams) { |
| 1764 |
mixerIndex = idx; |
| 1765 |
scope = kAudioUnitScope_Input; |
| 1766 |
} else if (idx >= kMDAudioFirstIndexForOutputStream && idx < kMDAudioNumberOfStreams) { |
| 1767 |
mixerIndex = idx - kMDAudioFirstIndexForOutputStream; |
| 1768 |
scope = kAudioUnitScope_Output; |
| 1769 |
} else return kMDErrorCannotSetupAudio; |
| 1770 |
if (scope == kAudioUnitScope_Input) { |
| 1771 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_Pan, scope, mixerIndex, &f32)); |
| 1772 |
} else f32 = 0.5f; |
| 1773 |
gAudio->ioStreamInfos[idx].pan = f32; |
| 1774 |
if (outPan != NULL) |
| 1775 |
*outPan = f32; |
| 1776 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_Volume, scope, mixerIndex, &f32)); |
| 1777 |
gAudio->ioStreamInfos[idx].volume = f32; |
| 1778 |
if (outVolume != NULL) |
| 1779 |
*outVolume = f32; |
| 1780 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_PostAveragePower, scope, mixerIndex, &f32)); |
| 1781 |
if (outAmpLeft != NULL) |
| 1782 |
*outAmpLeft = f32; |
| 1783 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_PostAveragePower + 1, scope, mixerIndex, &f32)); |
| 1784 |
if (outAmpRight != NULL) |
| 1785 |
*outAmpRight = f32; |
| 1786 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_PostPeakHoldLevel, scope, mixerIndex, &f32)); |
| 1787 |
if (outPeakLeft != NULL) |
| 1788 |
*outPeakLeft = f32; |
| 1789 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_PostPeakHoldLevel + 1, scope, mixerIndex, &f32)); |
| 1790 |
if (outPeakRight != NULL) |
| 1791 |
*outPeakRight = f32; |
| 1792 |
return kMDNoError; |
| 1793 |
exit: |
| 1794 |
return kMDErrorCannotSetupAudio; |
| 1795 |
} |
| 1796 |
|
| 1797 |
MDStatus |
| 1798 |
MDAudioSetMixerVolume(int idx, float volume) |
| 1799 |
{ |
| 1800 |
OSStatus err; |
| 1801 |
Float32 f32 = volume; |
| 1802 |
int scope, mixerIndex; |
| 1803 |
if (idx >= 0 && idx < kMDAudioNumberOfInputStreams) { |
| 1804 |
mixerIndex = idx; |
| 1805 |
scope = kAudioUnitScope_Input; |
| 1806 |
} else if (idx >= kMDAudioFirstIndexForOutputStream && idx < kMDAudioNumberOfStreams) { |
| 1807 |
mixerIndex = idx - kMDAudioFirstIndexForOutputStream; |
| 1808 |
scope = kAudioUnitScope_Output; |
| 1809 |
} else return kMDErrorCannotSetupAudio; |
| 1810 |
CHECK_ERR(err, AudioUnitSetParameter(gAudio->mixerUnit, kStereoMixerParam_Volume, scope, mixerIndex, f32, 0)); |
| 1811 |
gAudio->ioStreamInfos[idx].volume = volume; |
| 1812 |
return kMDNoError; |
| 1813 |
exit: |
| 1814 |
return kMDErrorCannotSetupAudio; |
| 1815 |
} |
| 1816 |
|
| 1817 |
MDStatus |
| 1818 |
MDAudioSetMixerPan(int idx, float pan) |
| 1819 |
{ |
| 1820 |
OSStatus err; |
| 1821 |
Float32 f32 = pan; |
| 1822 |
int scope, mixerIndex; |
| 1823 |
if (idx >= 0 && idx < kMDAudioNumberOfInputStreams) { |
| 1824 |
mixerIndex = idx; |
| 1825 |
scope = kAudioUnitScope_Input; |
| 1826 |
} else if (idx >= kMDAudioFirstIndexForOutputStream && idx < kMDAudioNumberOfStreams) { |
| 1827 |
mixerIndex = idx - kMDAudioFirstIndexForOutputStream; |
| 1828 |
scope = kAudioUnitScope_Output; |
| 1829 |
} else return kMDErrorCannotSetupAudio; |
| 1830 |
CHECK_ERR(err, AudioUnitSetParameter(gAudio->mixerUnit, kStereoMixerParam_Pan, scope, mixerIndex, f32, 0)); |
| 1831 |
gAudio->ioStreamInfos[idx].pan = pan; |
| 1832 |
return kMDNoError; |
| 1833 |
exit: |
| 1834 |
return kMDErrorCannotSetupAudio; |
| 1835 |
} |
| 1836 |
|
| 1837 |
#if 0 |
| 1838 |
MDStatus |
| 1839 |
MDAudioStartInput(void) |
| 1840 |
{ |
| 1841 |
OSStatus err; |
| 1842 |
if (!gAudio->isInputRunning) { |
| 1843 |
// return kMDErrorCannotSetupAudio; |
| 1844 |
CHECK_ERR(err, AudioOutputUnitStart(gAudio->inputUnit)); |
| 1845 |
gAudio->isInputRunning = 1; |
| 1846 |
gAudio->firstInputTime = -1; |
| 1847 |
} |
| 1848 |
|
| 1849 |
return kMDNoError; |
| 1850 |
exit: |
| 1851 |
return kMDErrorCannotSetupAudio; |
| 1852 |
} |
| 1853 |
|
| 1854 |
MDStatus |
| 1855 |
MDAudioStopInput(void) |
| 1856 |
{ |
| 1857 |
OSStatus err; |
| 1858 |
if (!gAudio->isInputRunning) { |
| 1859 |
// return kMDErrorCannotSetupAudio; |
| 1860 |
CHECK_ERR(err, AudioOutputUnitStop(gAudio->inputUnit)); |
| 1861 |
gAudio->isInputRunning = 0; |
| 1862 |
gAudio->firstInputTime = -1; |
| 1863 |
} |
| 1864 |
return kMDNoError; |
| 1865 |
exit: |
| 1866 |
return kMDErrorCannotSetupAudio; |
| 1867 |
} |
| 1868 |
|
| 1869 |
MDStatus |
| 1870 |
MDAudioEnablePlayThru(int flag) |
| 1871 |
{ |
| 1872 |
MDStatus sts; |
| 1873 |
if (flag && !gAudio->isAudioThruEnabled) { |
| 1874 |
/* if (!gAudio->isInputRunning) { |
| 1875 |
if ((sts = MDAudioStartInput()) != kMDNoError) |
| 1876 |
return sts; |
| 1877 |
} */ |
| 1878 |
gAudio->isAudioThruEnabled = 1; |
| 1879 |
} else if (!flag && gAudio->isAudioThruEnabled) { |
| 1880 |
/* if (!gAudio->isRecording) { |
| 1881 |
if ((sts = MDAudioStopInput()) != kMDNoError) |
| 1882 |
return sts; |
| 1883 |
} */ |
| 1884 |
gAudio->isAudioThruEnabled = 0; |
| 1885 |
} |
| 1886 |
return kMDNoError; |
| 1887 |
} |
| 1888 |
|
| 1889 |
int |
| 1890 |
MDAudioIsPlayThruEnabled(void) |
| 1891 |
{ |
| 1892 |
return gAudio->isAudioThruEnabled; |
| 1893 |
} |
| 1894 |
|
| 1895 |
int |
| 1896 |
MDAudioGetInputVolumeAndAmplitudes(float *outVolume, float *outAmpLeft, float *outAmpRight, float *outPeakLeft, float *outPeakRight) |
| 1897 |
{ |
| 1898 |
OSStatus err; |
| 1899 |
Float32 f32; |
| 1900 |
if (gAudio != NULL && gAudio->mixerUnit != NULL) { |
| 1901 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_Volume, kAudioUnitScope_Input, 2, &f32)); |
| 1902 |
if (outVolume != NULL) |
| 1903 |
*outVolume = f32; |
| 1904 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_PostAveragePower, kAudioUnitScope_Input, 2, &f32)); |
| 1905 |
if (outAmpLeft != NULL) |
| 1906 |
*outAmpLeft = f32; |
| 1907 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_PostAveragePower + 1, kAudioUnitScope_Input, 2, &f32)); |
| 1908 |
if (outAmpRight != NULL) |
| 1909 |
*outAmpRight = f32; |
| 1910 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_PostPeakHoldLevel, kAudioUnitScope_Input, 2, &f32)); |
| 1911 |
if (outPeakLeft != NULL) |
| 1912 |
*outPeakLeft = f32; |
| 1913 |
CHECK_ERR(err, AudioUnitGetParameter(gAudio->mixerUnit, kStereoMixerParam_PostPeakHoldLevel + 1, kAudioUnitScope_Input, 2, &f32)); |
| 1914 |
if (outPeakRight != NULL) |
| 1915 |
*outPeakRight = f32; |
| 1916 |
} |
| 1917 |
return kMDNoError; |
| 1918 |
exit: |
| 1919 |
return kMDErrorCannotSetupAudio; |
| 1920 |
} |
| 1921 |
|
| 1922 |
int |
| 1923 |
MDAudioSetInputVolume(float volume) |
| 1924 |
{ |
| 1925 |
OSStatus err; |
| 1926 |
Float32 f32 = volume; |
| 1927 |
if (gAudio != NULL && gAudio->mixerUnit != NULL) { |
| 1928 |
CHECK_ERR(err, AudioUnitSetParameter(gAudio->mixerUnit, kStereoMixerParam_Volume, kAudioUnitScope_Input, 2, f32, 0)); |
| 1929 |
} |
| 1930 |
return kMDNoError; |
| 1931 |
exit: |
| 1932 |
return kMDErrorCannotSetupAudio; |
| 1933 |
} |
| 1934 |
#endif |
| 1935 |
|
| 1936 |
#pragma mark ====== Audio Recording ====== |
| 1937 |
|
| 1938 |
MDStatus |
| 1939 |
MDAudioPrepareRecording(const char *filename, const MDAudioFormat *format, int audioFileType, UInt64 recordingDuration) |
| 1940 |
{ |
| 1941 |
OSStatus err; |
| 1942 |
/* FSRef parentDir; */ |
| 1943 |
/* CFStringRef filenameStr; */ |
| 1944 |
CFURLRef urlRef; |
| 1945 |
/* const char *p; */ |
| 1946 |
|
| 1947 |
if (gAudio->isRecording) |
| 1948 |
return kMDErrorCannotSetupAudio; |
| 1949 |
|
| 1950 |
/* Prepare CFURLRef from the filename (assuming it is a full path) */ |
| 1951 |
urlRef = CFURLCreateFromFileSystemRepresentation(NULL, (const unsigned char *)filename, strlen(filename), 0); |
| 1952 |
|
| 1953 |
/* Prepare FSRef/CFStringRef representation of the filename */ |
| 1954 |
/* if ((p = strrchr(filename, '/')) == NULL) { |
| 1955 |
char buf[MAXPATHLEN]; |
| 1956 |
getcwd(buf, sizeof buf); |
| 1957 |
err = FSPathMakeRef((unsigned char *)buf, &parentDir, NULL); |
| 1958 |
if (err != noErr) |
| 1959 |
return kMDErrorCannotSetupAudio; |
| 1960 |
filenameStr = CFStringCreateWithCString(NULL, p, kCFStringEncodingUTF8); |
| 1961 |
} else { |
| 1962 |
char *pp; |
| 1963 |
pp = malloc(p - filename + 1); |
| 1964 |
strncpy(pp, filename, p - filename); |
| 1965 |
pp[p - filename] = 0; |
| 1966 |
err = FSPathMakeRef((unsigned char *)pp, &parentDir, NULL); |
| 1967 |
free(pp); |
| 1968 |
if (err != noErr) |
| 1969 |
return kMDErrorCannotSetupAudio; |
| 1970 |
filenameStr = CFStringCreateWithCString(NULL, p + 1, kCFStringEncodingUTF8); |
| 1971 |
} */ |
| 1972 |
|
| 1973 |
|
| 1974 |
/* Create a new audio file */ |
| 1975 |
|
| 1976 |
err = ExtAudioFileCreateWithURL(urlRef, audioFileType, format, NULL, kAudioFileFlags_EraseFile, &(gAudio->audioFile)); |
| 1977 |
// err = ExtAudioFileCreateNew(&parentDir, filenameStr, audioFileType, format, NULL, &(gAudio->audioFile)); |
| 1978 |
if (err != noErr) |
| 1979 |
return kMDErrorCannotSetupAudio; |
| 1980 |
CFRelease(urlRef); |
| 1981 |
|
| 1982 |
/* Set the client data format */ |
| 1983 |
err = ExtAudioFileSetProperty(gAudio->audioFile, kExtAudioFileProperty_ClientDataFormat, sizeof(AudioStreamBasicDescription), &gAudio->preferredFormat); |
| 1984 |
if (err != noErr) |
| 1985 |
return kMDErrorCannotSetupAudio; |
| 1986 |
|
| 1987 |
/* Initialize AudioFile IO */ |
| 1988 |
err = ExtAudioFileWriteAsync(gAudio->audioFile, 0, NULL); |
| 1989 |
if (err != noErr) |
| 1990 |
return kMDErrorCannotSetupAudio; |
| 1991 |
|
| 1992 |
|
| 1993 |
|
| 1994 |
/* // If we're recording from a mono source, setup a simple channel map to split to stereo |
| 1995 |
if (fDeviceFormat.mChannelsPerFrame == 1 && fOutputFormat.mChannelsPerFrame == 2) |
| 1996 |
{ |
| 1997 |
// Get the underlying AudioConverterRef |
| 1998 |
UInt32 size = sizeof(AudioConverterRef); |
| 1999 |
err = ExtAudioFileGetProperty(fOutputAudioFile, kExtAudioFileProperty_AudioConverter, &size, &conv); |
| 2000 |
if (conv) |
| 2001 |
{ |
| 2002 |
// This should be as large as the number of output channels, |
| 2003 |
// each element specifies which input channel's data is routed to that output channel |
| 2004 |
SInt32 channelMap[] = { 0, 0 }; |
| 2005 |
err = AudioConverterSetProperty(conv, kAudioConverterChannelMap, 2*sizeof(SInt32), channelMap); |
| 2006 |
} |
| 2007 |
} |
| 2008 |
*/ |
| 2009 |
|
| 2010 |
gAudio->recordingDuration = recordingDuration; |
| 2011 |
|
| 2012 |
return kMDNoError; |
| 2013 |
} |
| 2014 |
|
| 2015 |
MDStatus |
| 2016 |
MDAudioStartRecording(void) |
| 2017 |
{ |
| 2018 |
/* MDStatus sts; */ |
| 2019 |
if (gAudio->isRecording) |
| 2020 |
return kMDErrorCannotSetupAudio; |
| 2021 |
/* if (sts != kMDNoError) |
| 2022 |
return sts; */ |
| 2023 |
gAudio->recordingStartTime = 0; /* Set at the first call to the recording callback */ |
| 2024 |
gAudio->isRecording = 1; |
| 2025 |
return kMDNoError; |
| 2026 |
} |
| 2027 |
|
| 2028 |
MDStatus |
| 2029 |
MDAudioStopRecording(void) |
| 2030 |
{ |
| 2031 |
OSStatus err; |
| 2032 |
if (!gAudio->isRecording) |
| 2033 |
return kMDErrorCannotProcessAudio; |
| 2034 |
gAudio->isRecording = 0; |
| 2035 |
CHECK_ERR(err, ExtAudioFileDispose(gAudio->audioFile)); |
| 2036 |
gAudio->audioFile = NULL; |
| 2037 |
/* if (!gAudio->isAudioThruEnabled) { |
| 2038 |
MDStatus sts = MDAudioStopInput(); |
| 2039 |
if (sts != kMDNoError) |
| 2040 |
return sts; |
| 2041 |
} */ |
| 2042 |
return kMDNoError; |
| 2043 |
exit: |
| 2044 |
return kMDErrorCannotProcessAudio; |
| 2045 |
} |
| 2046 |
|
| 2047 |
/* |
| 2048 |
MDStatus |
| 2049 |
MDAudioStop(void) |
| 2050 |
{ |
| 2051 |
MDStatus status = kMDNoError; |
| 2052 |
if (inAudio != NULL) { |
| 2053 |
if (inAudio->isRecording) |
| 2054 |
status = MDAudioStopRecording(inAudio); |
| 2055 |
} |
| 2056 |
return status; |
| 2057 |
} |
| 2058 |
*/ |
| 2059 |
|
| 2060 |
int |
| 2061 |
MDAudioIsRecording(void) |
| 2062 |
{ |
| 2063 |
if (gAudio->isRecording) |
| 2064 |
return 1; |
| 2065 |
else return 0; |
| 2066 |
} |
| 2067 |
|
| 2068 |
#pragma mark ====== MDAudioFormat accessors ====== |
| 2069 |
|
| 2070 |
void |
| 2071 |
MDAudioFormatSetCanonical(MDAudioFormat *fmt, float sampleRate, int nChannels, int interleaved) |
| 2072 |
{ |
| 2073 |
if (sampleRate != 0.0) |
| 2074 |
fmt->mSampleRate = sampleRate; |
| 2075 |
fmt->mFormatID = kAudioFormatLinearPCM; |
| 2076 |
fmt->mFormatFlags = kAudioFormatFlagsNativeFloatPacked; |
| 2077 |
fmt->mBitsPerChannel = 32; |
| 2078 |
fmt->mChannelsPerFrame = nChannels; |
| 2079 |
fmt->mFramesPerPacket = 1; |
| 2080 |
if (interleaved) |
| 2081 |
fmt->mBytesPerPacket = fmt->mBytesPerFrame = nChannels * sizeof(Float32); |
| 2082 |
else { |
| 2083 |
fmt->mBytesPerPacket = fmt->mBytesPerFrame = sizeof(Float32); |
| 2084 |
fmt->mFormatFlags |= kAudioFormatFlagIsNonInterleaved; |
| 2085 |
} |
| 2086 |
} |
| 2087 |
|
| 2088 |
/* Not used yet |
| 2089 |
MDAudioFormatSetSampleRate(MDAudioFormat *fmt, float sampleRate) |
| 2090 |
{ fmt->mSampleRate = sampleRate; } |
| 2091 |
|
| 2092 |
float |
| 2093 |
MDAudioFormatGetSampleRate(MDAudioFormat *fmt) |
| 2094 |
{ return fmt->mSampleRate; } |
| 2095 |
|
| 2096 |
void |
| 2097 |
MDAudioFormatSetFormatID(MDAudioFormat *fmt, UInt32 formatID) |
| 2098 |
{ fmt->mFormatID = formatID; } |
| 2099 |
|
| 2100 |
UInt32 |
| 2101 |
MDAudioFormatGetFormatID(MDAudioFormat *fmt) |
| 2102 |
{ return fmt->formatID; } |
| 2103 |
*/ |
| 2104 |
|