mirror of
https://github.com/mattingalls/Soundflower.git
synced 2026-02-27 18:23:23 +01:00
663 lines
23 KiB
C++
Executable File
663 lines
23 KiB
C++
Executable File
/*
|
|
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 <IOKit/audio/IOAudioControl.h>
|
|
#include <IOKit/audio/IOAudioLevelControl.h>
|
|
#include <IOKit/audio/IOAudioToggleControl.h>
|
|
#include <IOKit/audio/IOAudioDefines.h>
|
|
#include <IOKit/IOLib.h>
|
|
#include <IOKit/IOWorkLoop.h>
|
|
#include <IOKit/IOTimerEventSource.h>
|
|
|
|
#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;
|
|
}
|