/* File:SoundflowerEngine.cpp Version: 1.0.1, ma++ ingalls Copyright (c) 2004 Cycling '74 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "SoundflowerEngine.h" #include #include #include #include #include #include #include #define INITIAL_SAMPLE_RATE 44100 #define BLOCK_SIZE 512 // Sample frames #define NUM_BLOCKS 32 #define NUM_STREAMS 1 #define super IOAudioEngine OSDefineMetaClassAndStructors(SoundflowerEngine, IOAudioEngine) bool SoundflowerEngine::init(OSDictionary *properties) { bool result = false; OSNumber *number = NULL; //IOLog("SoundflowerEngine[%p]::init()\n", this); if (!super::init(properties)) { goto Done; } // Do class-specific initialization here // If no non-hardware initialization is needed, this function can be removed /* The below clojure code creates the lookup table. You can run it in the online repl at http://try-clojure.org/, although it seems copy and paste doesn't work. The easiest way to get running with clojure on your own machine is to install https://github.com/technomancy/leiningen and type "lien repl" (defn dB->scale [ind dB] [ind (float (Math/pow 10.0 (/ dB 10.0)))]) (defn val->dB [min-dB v] (+ (/ (* (- min-dB) v) 99.0) min-dB)) (doseq [[i v] (map-indexed dB->scale (map #(val->dB -40.0 %) (range 0 100)))] (println "\tlogTable[" i "] = " v ";")) To adjust the minimum volume, change the -40 (in dB) value in the last line and also the corresponding visual aid in SoundflowerDevice.cpp Do not change the number of volume points without also changing the minVolume/minGain constants in SoundflowerDevice.cpp. Initially, I used -71 as the minimum volume, but in reality my setup seems to reach zero muchbefore -71. A floor of -40 seems to work *ok* for my setup. */ logTable[ 0 ] = 1.0E-4 ; logTable[ 1 ] = 1.09749875E-4 ; logTable[ 2 ] = 1.2045036E-4 ; logTable[ 3 ] = 1.3219411E-4 ; logTable[ 4 ] = 1.4508287E-4 ; logTable[ 5 ] = 1.5922828E-4 ; logTable[ 6 ] = 1.7475284E-4 ; logTable[ 7 ] = 1.9179103E-4 ; logTable[ 8 ] = 2.1049041E-4 ; logTable[ 9 ] = 2.3101296E-4 ; logTable[ 10 ] = 2.5353645E-4 ; logTable[ 11 ] = 2.7825593E-4 ; logTable[ 12 ] = 3.0538556E-4 ; logTable[ 13 ] = 3.3516026E-4 ; logTable[ 14 ] = 3.67838E-4 ; logTable[ 15 ] = 4.0370174E-4 ; logTable[ 16 ] = 4.4306213E-4 ; logTable[ 17 ] = 4.8626016E-4 ; logTable[ 18 ] = 5.336699E-4 ; logTable[ 19 ] = 5.857021E-4 ; logTable[ 20 ] = 6.4280734E-4 ; logTable[ 21 ] = 7.054802E-4 ; logTable[ 22 ] = 7.742637E-4 ; logTable[ 23 ] = 8.4975344E-4 ; logTable[ 24 ] = 9.326034E-4 ; logTable[ 25 ] = 0.0010235311 ; logTable[ 26 ] = 0.001123324 ; logTable[ 27 ] = 0.0012328468 ; logTable[ 28 ] = 0.0013530478 ; logTable[ 29 ] = 0.0014849682 ; logTable[ 30 ] = 0.0016297508 ; logTable[ 31 ] = 0.0017886495 ; logTable[ 32 ] = 0.0019630406 ; logTable[ 33 ] = 0.0021544348 ; logTable[ 34 ] = 0.0023644895 ; logTable[ 35 ] = 0.0025950242 ; logTable[ 36 ] = 0.002848036 ; logTable[ 37 ] = 0.0031257158 ; logTable[ 38 ] = 0.0034304692 ; logTable[ 39 ] = 0.0037649358 ; logTable[ 40 ] = 0.0041320124 ; logTable[ 41 ] = 0.0045348783 ; logTable[ 42 ] = 0.0049770237 ; logTable[ 43 ] = 0.005462277 ; logTable[ 44 ] = 0.0059948424 ; logTable[ 45 ] = 0.006579332 ; logTable[ 46 ] = 0.007220809 ; logTable[ 47 ] = 0.007924829 ; logTable[ 48 ] = 0.00869749 ; logTable[ 49 ] = 0.009545485 ; logTable[ 50 ] = 0.010476157 ; logTable[ 51 ] = 0.01149757 ; logTable[ 52 ] = 0.012618569 ; logTable[ 53 ] = 0.013848864 ; logTable[ 54 ] = 0.015199111 ; logTable[ 55 ] = 0.016681006 ; logTable[ 56 ] = 0.018307382 ; logTable[ 57 ] = 0.02009233 ; logTable[ 58 ] = 0.022051308 ; logTable[ 59 ] = 0.024201283 ; logTable[ 60 ] = 0.026560878 ; logTable[ 61 ] = 0.02915053 ; logTable[ 62 ] = 0.03199267 ; logTable[ 63 ] = 0.03511192 ; logTable[ 64 ] = 0.038535286 ; logTable[ 65 ] = 0.042292427 ; logTable[ 66 ] = 0.046415888 ; logTable[ 67 ] = 0.05094138 ; logTable[ 68 ] = 0.055908103 ; logTable[ 69 ] = 0.061359074 ; logTable[ 70 ] = 0.06734151 ; logTable[ 71 ] = 0.07390722 ; logTable[ 72 ] = 0.081113085 ; logTable[ 73 ] = 0.08902151 ; logTable[ 74 ] = 0.097701 ; logTable[ 75 ] = 0.10722672 ; logTable[ 76 ] = 0.1176812 ; logTable[ 77 ] = 0.12915497 ; logTable[ 78 ] = 0.14174742 ; logTable[ 79 ] = 0.15556762 ; logTable[ 80 ] = 0.17073527 ; logTable[ 81 ] = 0.18738174 ; logTable[ 82 ] = 0.20565122 ; logTable[ 83 ] = 0.22570197 ; logTable[ 84 ] = 0.24770764 ; logTable[ 85 ] = 0.2718588 ; logTable[ 86 ] = 0.29836473 ; logTable[ 87 ] = 0.32745492 ; logTable[ 88 ] = 0.35938138 ; logTable[ 89 ] = 0.3944206 ; logTable[ 90 ] = 0.43287614 ; logTable[ 91 ] = 0.47508103 ; logTable[ 92 ] = 0.5214008 ; logTable[ 93 ] = 0.5722368 ; logTable[ 94 ] = 0.62802917 ; logTable[ 95 ] = 0.6892612 ; logTable[ 96 ] = 0.75646335 ; logTable[ 97 ] = 0.83021754 ; logTable[ 98 ] = 0.91116273 ; logTable[ 99 ] = 1.0 ; number = OSDynamicCast(OSNumber, getProperty(NUM_BLOCKS_KEY)); if (number) { numBlocks = number->unsigned32BitValue(); } else { numBlocks = NUM_BLOCKS; } number = OSDynamicCast(OSNumber, getProperty(BLOCK_SIZE_KEY)); if (number) { blockSize = number->unsigned32BitValue(); } else { blockSize = BLOCK_SIZE; } inputStream = outputStream = NULL; duringHardwareInit = FALSE; mLastValidSampleFrame = 0; result = true; Done: return result; } bool SoundflowerEngine::initHardware(IOService *provider) { bool result = false; IOAudioSampleRate initialSampleRate; IOWorkLoop *wl; //IOLog("SoundflowerEngine[%p]::initHardware(%p)\n", this, provider); duringHardwareInit = TRUE; if (!super::initHardware(provider)) { goto Done; } initialSampleRate.whole = 0; initialSampleRate.fraction = 0; if (!createAudioStreams(&initialSampleRate)) { IOLog("SoundflowerEngine::initHardware() failed\n"); goto Done; } if (initialSampleRate.whole == 0) { goto Done; } // calculate our timeout in nanosecs, taking care to keep 64bits blockTimeoutNS = blockSize; blockTimeoutNS *= 1000000000; blockTimeoutNS /= initialSampleRate.whole; setSampleRate(&initialSampleRate); // Set the number of sample frames in each buffer setNumSampleFramesPerBuffer(blockSize * numBlocks); wl = getWorkLoop(); if (!wl) { goto Done; } timerEventSource = IOTimerEventSource::timerEventSource(this, ourTimerFired); if (!timerEventSource) { goto Done; } workLoop->addEventSource(timerEventSource); result = true; Done: duringHardwareInit = FALSE; return result; } bool SoundflowerEngine::createAudioStreams(IOAudioSampleRate *initialSampleRate) { bool result = false; OSNumber* number = NULL; UInt32 numStreams; UInt32 streamNum; OSArray* formatArray = NULL; OSArray* sampleRateArray = NULL; UInt32 startingChannelID = 1; OSString* desc; desc = OSDynamicCast(OSString, getProperty(DESCRIPTION_KEY)); if (desc) setDescription(desc->getCStringNoCopy()); number = OSDynamicCast(OSNumber, getProperty(NUM_STREAMS_KEY)); if (number) numStreams = number->unsigned32BitValue(); else numStreams = NUM_STREAMS; formatArray = OSDynamicCast(OSArray, getProperty(FORMATS_KEY)); if (formatArray == NULL) { IOLog("SF formatArray is NULL\n"); goto Done; } sampleRateArray = OSDynamicCast(OSArray, getProperty(SAMPLE_RATES_KEY)); if (sampleRateArray == NULL) { IOLog("SF sampleRateArray is NULL\n"); goto Done; } for (streamNum = 0; streamNum < numStreams; streamNum++) { UInt32 maxBitWidth = 0; UInt32 maxNumChannels = 0; OSCollectionIterator* formatIterator = NULL; OSCollectionIterator* sampleRateIterator = NULL; OSDictionary* formatDict; IOAudioSampleRate sampleRate; IOAudioStreamFormat initialFormat; bool initialFormatSet; UInt32 channelID; char outputStreamName[64]; char inputStreamName[64]; initialFormatSet = false; sampleRate.whole = 0; sampleRate.fraction = 0; inputStream = new IOAudioStream; if (inputStream == NULL) { IOLog("SF could not create new input IOAudioStream\n"); goto Error; } outputStream = new IOAudioStream; if (outputStream == NULL) { IOLog("SF could not create new output IOAudioStream\n"); goto Error; } snprintf(inputStreamName, 64, "Soundflower Input Stream #%u", (unsigned int)streamNum + 1); snprintf(outputStreamName, 64, "Soundflower Output Stream #%u", (unsigned int)streamNum + 1); if (!inputStream->initWithAudioEngine(this, kIOAudioStreamDirectionInput, startingChannelID, inputStreamName) || !outputStream->initWithAudioEngine(this, kIOAudioStreamDirectionOutput, startingChannelID, outputStreamName)) { IOLog("SF could not init one of the streams with audio engine. \n"); goto Error; } formatIterator = OSCollectionIterator::withCollection(formatArray); if (!formatIterator) { IOLog("SF NULL formatIterator\n"); goto Error; } sampleRateIterator = OSCollectionIterator::withCollection(sampleRateArray); if (!sampleRateIterator) { IOLog("SF NULL sampleRateIterator\n"); goto Error; } formatIterator->reset(); while ((formatDict = (OSDictionary *)formatIterator->getNextObject())) { IOAudioStreamFormat format; if (OSDynamicCast(OSDictionary, formatDict) == NULL) { IOLog("SF error casting formatDict\n"); goto Error; } if (IOAudioStream::createFormatFromDictionary(formatDict, &format) == NULL) { IOLog("SF error in createFormatFromDictionary()\n"); goto Error; } if (!initialFormatSet) { initialFormat = format; } sampleRateIterator->reset(); while ((number = (OSNumber *)sampleRateIterator->getNextObject())) { if (!OSDynamicCast(OSNumber, number)) { IOLog("SF error iterating sample rates\n"); goto Error; } sampleRate.whole = number->unsigned32BitValue(); inputStream->addAvailableFormat(&format, &sampleRate, &sampleRate); outputStream->addAvailableFormat(&format, &sampleRate, &sampleRate); if (format.fNumChannels > maxNumChannels) { maxNumChannels = format.fNumChannels; } if (format.fBitWidth > maxBitWidth) { maxBitWidth = format.fBitWidth; } if (initialSampleRate->whole == 0) { initialSampleRate->whole = sampleRate.whole; } } } mBufferSize = blockSize * numBlocks * maxNumChannels * maxBitWidth / 8; //IOLog("Soundflower streamBufferSize: %ld\n", mBufferSize); if (mBuffer == NULL) { mBuffer = (void *)IOMalloc(mBufferSize); if (!mBuffer) { IOLog("Soundflower: Error allocating output buffer - %lu bytes.\n", (unsigned long)mBufferSize); goto Error; } mThruBuffer = (float*)IOMalloc(mBufferSize); if (!mThruBuffer) { IOLog("Soundflower: Error allocating thru buffer - %lu bytes.\n", (unsigned long)mBufferSize); goto Error; } memset((UInt8*)mThruBuffer, 0, mBufferSize); } inputStream->setFormat(&initialFormat); inputStream->setSampleBuffer(mBuffer, mBufferSize); addAudioStream(inputStream); inputStream->release(); outputStream->setFormat(&initialFormat); outputStream->setSampleBuffer(mBuffer, mBufferSize); addAudioStream(outputStream); outputStream->release(); formatIterator->release(); sampleRateIterator->release(); for (channelID = startingChannelID; channelID < (startingChannelID + maxNumChannels); channelID++) { char channelName[20]; snprintf(channelName, 20, "Channel %u", (unsigned int)channelID); } startingChannelID += maxNumChannels; continue; Error: IOLog("SoundflowerEngine[%p]::createAudioStreams() - ERROR\n", this); if (inputStream) inputStream->release(); if (outputStream) outputStream->release(); if (formatIterator) formatIterator->release(); if (sampleRateIterator) sampleRateIterator->release(); goto Done; } result = true; Done: if (!result) IOLog("SoundflowerEngine[%p]::createAudioStreams() - failed!\n", this); return result; } void SoundflowerEngine::free() { //IOLog("SoundflowerEngine[%p]::free()\n", this); if (mBuffer) { IOFree(mBuffer, mBufferSize); mBuffer = NULL; } if (mThruBuffer) { IOFree(mThruBuffer, mBufferSize); mThruBuffer = NULL; } super::free(); } IOReturn SoundflowerEngine::performAudioEngineStart() { //IOLog("SoundflowerEngine[%p]::performAudioEngineStart()\n", this); // When performAudioEngineStart() gets called, the audio engine should be started from the beginning // of the sample buffer. Because it is starting on the first sample, a new timestamp is needed // to indicate when that sample is being read from/written to. The function takeTimeStamp() // is provided to do that automatically with the current time. // By default takeTimeStamp() will increment the current loop count in addition to taking the current // timestamp. Since we are starting a new audio engine run, and not looping, we don't want the loop count // to be incremented. To accomplish that, false is passed to takeTimeStamp(). // The audio engine will also have to take a timestamp each time the buffer wraps around // How that is implemented depends on the type of hardware - PCI hardware will likely // receive an interrupt to perform that task takeTimeStamp(false); currentBlock = 0; timerEventSource->setTimeout(blockTimeoutNS); uint64_t time; clock_get_uptime(&time); absolutetime_to_nanoseconds(time, &nextTime); nextTime += blockTimeoutNS; return kIOReturnSuccess; } IOReturn SoundflowerEngine::performAudioEngineStop() { //IOLog("SoundflowerEngine[%p]::performAudioEngineStop()\n", this); timerEventSource->cancelTimeout(); return kIOReturnSuccess; } UInt32 SoundflowerEngine::getCurrentSampleFrame() { //IOLog("SoundflowerEngine[%p]::getCurrentSampleFrame() - currentBlock = %lu\n", this, currentBlock); // In order for the erase process to run properly, this function must return the current location of // the audio engine - basically a sample counter // It doesn't need to be exact, but if it is inexact, it should err towards being before the current location // rather than after the current location. The erase head will erase up to, but not including the sample // frame returned by this function. If it is too large a value, sound data that hasn't been played will be // erased. return currentBlock * blockSize; } IOReturn SoundflowerEngine::performFormatChange(IOAudioStream *audioStream, const IOAudioStreamFormat *newFormat, const IOAudioSampleRate *newSampleRate) { if (!duringHardwareInit) { // IOLog("SoundflowerEngine[%p]::peformFormatChange(%p, %p, %p)\n", this, audioStream, newFormat, newSampleRate); } // It is possible that this function will be called with only a format or only a sample rate // We need to check for NULL for each of the parameters if (newFormat) { if (!duringHardwareInit) { // #### do we need to make sure output format == input format?? } } if (newSampleRate) { if (!duringHardwareInit) { UInt64 newblockTime = blockSize; newblockTime *= 1000000000; blockTimeoutNS = newblockTime / newSampleRate->whole; } } return kIOReturnSuccess; } void SoundflowerEngine::ourTimerFired(OSObject *target, IOTimerEventSource *sender) { if (target) { SoundflowerEngine *audioEngine = OSDynamicCast(SoundflowerEngine, target); UInt64 thisTimeNS; uint64_t time; SInt64 diff; if (audioEngine) { // make sure we have a client, and thus new data so we don't keep on // just looping around the last client's last buffer! IOAudioStream *outStream = audioEngine->getAudioStream(kIOAudioStreamDirectionOutput, 1); if (outStream->numClients == 0) { // it has, so clean the buffer memset((UInt8*)audioEngine->mThruBuffer, 0, audioEngine->mBufferSize); } audioEngine->currentBlock++; if (audioEngine->currentBlock >= audioEngine->numBlocks) { audioEngine->currentBlock = 0; audioEngine->takeTimeStamp(); } // calculate next time to fire, by taking the time and comparing it to the time we requested. clock_get_uptime(&time); absolutetime_to_nanoseconds(time, &thisTimeNS); // this next calculation must be signed or we will introduce distortion after only a couple of vectors diff = ((SInt64)audioEngine->nextTime - (SInt64)thisTimeNS); sender->setTimeout(audioEngine->blockTimeoutNS + diff); audioEngine->nextTime += audioEngine->blockTimeoutNS; } } } IOReturn SoundflowerEngine::clipOutputSamples(const void *mixBuf, void *sampleBuf, UInt32 firstSampleFrame, UInt32 numSampleFrames, const IOAudioStreamFormat *streamFormat, IOAudioStream *audioStream) { UInt32 channelCount = streamFormat->fNumChannels; UInt32 offset = firstSampleFrame * channelCount; UInt32 byteOffset = offset * sizeof(float); UInt32 numBytes = numSampleFrames * channelCount * sizeof(float); SoundflowerDevice* device = (SoundflowerDevice*)audioDevice; #if 0 IOLog("SoundflowerEngine[%p]::clipOutputSamples() -- channelCount:%u \n", this, (uint)channelCount); IOLog(" input -- numChannels: %u", (uint)inputStream->format.fNumChannels); IOLog(" bitDepth: %u", (uint)inputStream->format.fBitDepth); IOLog(" bitWidth: %u", (uint)inputStream->format.fBitWidth); IOLog(" \n"); IOLog(" output -- numChannels: %u", (uint)inputStream->format.fNumChannels); IOLog(" bitDepth: %u", (uint)inputStream->format.fBitDepth); IOLog(" bitWidth: %u", (uint)inputStream->format.fBitWidth); IOLog(" \n"); #endif #if 0 IOLog("INPUT: firstSampleFrame: %u numSampleFrames: %u \n", (uint)firstSampleFrame, (uint)numSampleFrames); #endif mLastValidSampleFrame = firstSampleFrame+numSampleFrames; // TODO: where is the sampleFrame wrapped? // TODO: try to put a mutex around reading and writing // TODO: why is the reading always trailing by at least 512 frames? (when 512 is the input framesize)? if (device->mMuteIn[0]) { memset((UInt8*)mThruBuffer + byteOffset, 0, numBytes); } else { memcpy((UInt8*)mThruBuffer + byteOffset, (UInt8 *)mixBuf + byteOffset, numBytes); float masterGain = logTable[ device->mGain[0] ]; float masterVolume = logTable[ device->mVolume[0] ]; for (UInt32 channel = 0; channel < channelCount; channel++) { SInt32 channelMute = device->mMuteIn[channel+1]; float channelGain = logTable[ device->mGain[channel+1] ]; float channelVolume = logTable[ device->mVolume[channel+1] ]; float adjustment = masterVolume * channelVolume * masterGain * channelGain; for (UInt32 channelBufferIterator = 0; channelBufferIterator < numSampleFrames; channelBufferIterator++) { if (channelMute) mThruBuffer[offset + channelBufferIterator*channelCount + channel] = 0; else mThruBuffer[offset + channelBufferIterator*channelCount + channel] *= adjustment; } } } return kIOReturnSuccess; } // This is called when client apps need input audio. Here we give them saved audio from the clip routine. IOReturn SoundflowerEngine::convertInputSamples(const void *sampleBuf, void *destBuf, UInt32 firstSampleFrame, UInt32 numSampleFrames, const IOAudioStreamFormat *streamFormat, IOAudioStream *audioStream) { UInt32 frameSize = streamFormat->fNumChannels * sizeof(float); UInt32 offset = firstSampleFrame * frameSize; SoundflowerDevice* device = (SoundflowerDevice*)audioDevice; #if 0 //IOLog("SoundflowerEngine[%p]::convertInputSamples() -- channelCount:%u \n", this, (uint)streamFormat->fNumChannels); IOLog("OUTPUT: firstSampleFrame: %u numSampleFrames: %u \n", (uint)firstSampleFrame, (uint)numSampleFrames); IOLog(" mLastValidSampleFrame: %u (diff: %ld) \n", (uint)mLastValidSampleFrame, long(mLastValidSampleFrame) - long(firstSampleFrame+numSampleFrames)); #endif if (device->mMuteOut[0]) memset((UInt8*)destBuf, 0, numSampleFrames * frameSize); else memcpy((UInt8*)destBuf, (UInt8*)mThruBuffer + offset, numSampleFrames * frameSize); return kIOReturnSuccess; }