Files
2019-06-16 17:18:57 +01:00

647 lines
19 KiB
C++

#include "FrameLib_DSP.h"
// Constructor / Destructor
FrameLib_DSP::FrameLib_DSP(ObjectType type, FrameLib_Context context, FrameLib_Proxy *proxy, FrameLib_Parameters::Info *info, unsigned long nIns, unsigned long nOuts, unsigned long nAudioChans)
: FrameLib_Block(type, context, proxy), mSamplingRate(44100.0), mMaxBlockSize(4096), mParameters(context, proxy, info), mProcessingQueue(context), mNext(nullptr), mNoLiveInputs(true), mInUpdate(false)
{
// Set IO
setIO(nIns, nOuts, nAudioChans);
}
// Destructor
FrameLib_DSP::~FrameLib_DSP()
{
// Free output
freeOutputMemory();
// Delete fixed inputs
for (auto ins = mInputs.begin(); ins != mInputs.end(); ins++)
delete[] ins->mFixedInput;
}
// Set Fixed Input
void FrameLib_DSP::setFixedInput(unsigned long idx, const double *input, unsigned long size)
{
// This is not threadsafe with the processing thread and should not be called concurrently...
mInputs[idx].mSize = 0;
if (mInputs[idx].mFixedInput)
delete[] mInputs[idx].mFixedInput;
mInputs[idx].mFixedInput = new double[size];
if (mInputs[idx].mFixedInput)
{
copyVector(mInputs[idx].mFixedInput, input, size);
mInputs[idx].mSize = size;
}
}
const double *FrameLib_DSP::getFixedInput(unsigned long idx, unsigned long *size)
{
*size = mInputs[idx].mSize;
return mInputs[idx].mFixedInput;
}
// Audio Processing
// Block updates for objects with audio IO
void FrameLib_DSP::blockUpdate(const double * const *ins, double **outs, unsigned long blockSize)
{
// Update block time and process the block
mBlockStartTime = mBlockEndTime;
mBlockEndTime += blockSize;
blockProcess(ins, outs, blockSize);
// If the object is handling audio updates (but is not an output object) then notify
if (requiresAudioNotification())
dependencyNotify(false, true);
}
// Reset
void FrameLib_DSP::reset(double samplingRate, unsigned long maxBlockSize)
{
// Store sample rate / max block size and call object specific reset
mSamplingRate = samplingRate > 0 ? samplingRate : 44100.0;
mMaxBlockSize = maxBlockSize;
LocalQueue(this, &FrameLib_DSP::reset);
mProcessingQueue->reset();
}
void FrameLib_DSP::reset(LocalQueue *queue)
{
bool prevNoLiveInputs = mNoLiveInputs;
// Object specific reset
objectReset();
// Reset dependency counts
mUpdatingInputs = false;
mInputCount = 0;
mOutputMemoryCount = 0;
mDependencyCount = ((requiresAudioNotification()) ? 1 : 0);
for (auto it = mInputDependencies.begin(); it != mInputDependencies.end(); it++)
if (!(*it)->mNoLiveInputs)
mDependencyCount++;
mNoLiveInputs = mDependencyCount == 0;
// Remove info about the processing queue
mNext = nullptr;
// Reset output
freeOutputMemory();
mOutputDone = false;
// Reset times (Note that the first sample is 1 so that we can start the frames *before* this with non-negative values)
mFrameTime = 0.0;
mInputTime = mNoLiveInputs ? FrameLib_TimeFormat::largest() : FrameLib_TimeFormat(1.0);
mValidTime = mNoLiveInputs ? FrameLib_TimeFormat::largest() : FrameLib_TimeFormat(1.0);
mBlockStartTime = 1.0;
mBlockEndTime = 1.0;
// Update output dependencies for changes in live input status
if (mNoLiveInputs != prevNoLiveInputs)
for (auto it = mOutputDependencies.begin(); it != mOutputDependencies.end(); it++)
queue->add(*it);
}
// Setup and IO Modes
void FrameLib_DSP::setIO(unsigned long nIns, unsigned long nOuts, unsigned long nAudioChans)
{
// Free output memory
freeOutputMemory();
// Call the base class to store new sizes
FrameLib_Block::setIO(nIns, nOuts, nAudioChans);
// Resize inputs and outputs
mInputs.resize(getNumIns());
mOutputs.resize(getNumOuts());
// Reset for audio
FrameLib_DSP::reset(mSamplingRate, mMaxBlockSize);
}
// Call this from your constructor only (unsafe elsewhere)
void FrameLib_DSP::setInputMode(unsigned long idx, bool update, bool trigger, bool switchable, FrameType type)
{
mInputs[idx].mUpdate = update;
mInputs[idx].mTrigger = trigger;
mInputs[idx].mSwitchable = switchable;
mInputs[idx].mType = type;
}
// Call this from your constructor only (unsafe elsewhere)
void FrameLib_DSP::setParameterInput(unsigned long idx)
{
setInputMode(idx, true, false, false, kFrameTagged);
mInputs[idx].mParameters = true;
}
// Call this from your constructor only (unsafe elsewhere)
void FrameLib_DSP::addParameterInput()
{
unsigned long nIns = getNumIns();
unsigned long nOuts = getNumOuts();
unsigned long nAudioChans = getNumAudioChans();
setIO(nIns + 1, nOuts, nAudioChans);
setParameterInput(nIns);
}
// Call this from your constructor only (unsafe elsewhere)
void FrameLib_DSP::setOutputType(unsigned long idx, FrameType type)
{
mOutputs[idx].mType = type;
mOutputs[idx].mCurrentType = type != kFrameAny ? type : kFrameNormal;
mOutputs[idx].mRequestedType = mOutputs[idx].mCurrentType;
}
// You should only call this from your process method (it is unsafe anywhere else)
void FrameLib_DSP::setCurrentOutputType(unsigned long idx, FrameType type)
{
mOutputs[idx].mRequestedType = type;
}
// You should only call this from your update method
void FrameLib_DSP::updateTrigger(unsigned long idx, bool trigger)
{
// Update trigger only if switchable and we are in the update method
mInputs[idx].mTrigger = mInputs[idx].mSwitchable && mInUpdate ? trigger : mInputs[idx].mTrigger;
}
// Output Allocation
bool FrameLib_DSP::allocateOutputs()
{
size_t allocationSize = 0;
for (auto outs = mOutputs.begin(); outs != mOutputs.end(); outs++)
{
// Update type
if (outs->mRequestedType != outs->mCurrentType)
{
if (outs->mType == kFrameAny)
outs->mCurrentType = outs->mRequestedType;
else
outs->mRequestedSize = 0;
}
// Calculate allocation size, including necessary alignment padding and assuming success
size_t unalignedSize = outs->mCurrentType == kFrameNormal ? outs->mRequestedSize * sizeof(double) : Serial::inPlaceSize(outs->mRequestedSize);
size_t alignedSize = FrameLib_LocalAllocator::alignSize(unalignedSize);
outs->mCurrentSize = outs->mRequestedSize;
outs->mPointerOffset = allocationSize;
allocationSize += alignedSize;
}
// Free then allocate memory
freeOutputMemory();
BytePointer pointer = alloc<Byte>(allocationSize);
if (pointer)
{
// Store pointers and create tagged outputs
for (auto outs = mOutputs.begin(); outs != mOutputs.end(); outs++)
{
outs->mMemory = pointer + outs->mPointerOffset;
if (outs->mCurrentType == kFrameTagged)
Serial::newInPlace(outs->mMemory, outs->mCurrentSize);
}
return true;
}
// Reset outputs on failure or zero size
for (auto outs = mOutputs.begin(); outs != mOutputs.end(); outs++)
{
outs->mMemory = nullptr;
outs->mCurrentSize = 0;
}
return false;
}
// Get Inputs and Outputs
const double *FrameLib_DSP::getInput(unsigned long idx, unsigned long *size) const
{
if (mInputs[idx].mObject)
return mInputs[idx].mObject->getOutput(mInputs[idx].mIndex, size);
*size = mInputs[idx].mSize;
return mInputs[idx].mFixedInput;
}
const FrameLib_Parameters::Serial *FrameLib_DSP::getInput(unsigned long idx) const
{
if (mInputs[idx].mObject)
return mInputs[idx].mObject->getOutput(mInputs[idx].mIndex);
return nullptr;
}
double *FrameLib_DSP::getOutput(unsigned long idx, unsigned long *size) const
{
if (mOutputs[0].mMemory && mOutputs[idx].mCurrentType == kFrameNormal)
{
*size = mOutputs[idx].mCurrentSize;
return (double *) mOutputs[idx].mMemory;
}
*size = 0;
return nullptr;
}
FrameLib_Parameters::Serial *FrameLib_DSP::getOutput(unsigned long idx) const
{
if (mOutputs[0].mMemory && mOutputs[idx].mCurrentType == kFrameTagged)
return (Serial *) mOutputs[idx].mMemory;
return nullptr;
}
// Convience methods for copying and zeroing
void FrameLib_DSP::prepareCopyInputToOutput(unsigned long inIdx, unsigned long outIdx)
{
FrameType requestType = mInputs[inIdx].getCurrentType();
unsigned long size = 0;
setCurrentOutputType(outIdx, requestType);
if (requestType == kFrameNormal)
getInput(inIdx, &size);
else
size = getInput(inIdx)->size();
requestOutputSize(outIdx, size);
}
void FrameLib_DSP::copyInputToOutput(unsigned long inIdx, unsigned long outIdx)
{
if (mOutputs[outIdx].mCurrentType == kFrameNormal)
{
unsigned long inSize, outSize;
const double *input = getInput(inIdx, &inSize);
double *output = getOutput(outIdx, &outSize);
copyVector(output, input, std::min(inSize, outSize));
}
else
{
FrameLib_Parameters::Serial *output = getOutput(outIdx);
if (output)
output->write(getInput(inIdx));
}
}
// Dependency Notification
inline void FrameLib_DSP::dependencyNotify(bool releaseMemory, bool fromInput)
{
if (mProcessingQueue->isTimedOut())
return;
assert(((mDependencyCount > 0) || (mUpdatingInputs && (mInputCount > 0))) && "Dependency count is already zero");
if (releaseMemory)
releaseOutputMemory();
// If ready add to queue
if (fromInput && mUpdatingInputs)
{
if (--mInputCount == 0)
mProcessingQueue->add(this);
}
else if (--mDependencyCount == 0 && !mUpdatingInputs)
mProcessingQueue->add(this);
// N.B. For multithreading re-entrancy needs to be avoided by increasing the dependency count before adding to the queue (with matching notification)
}
// For updating the correct input count
void FrameLib_DSP::incrementInputDependency()
{
if (mUpdatingInputs)
mInputCount++;
else
mDependencyCount++;
}
// Main code to control time flow (called when all input/output dependencies are ready)
void FrameLib_DSP::dependenciesReady()
{
#ifndef NDEBUG
FrameLib_TimeFormat prevInputTime = mInputTime;
#endif
mDependencyCount++;
bool timeUpdated = false;
bool callUpdate = false;
// Check for inputs at the current frame time that update (update parameters if requested)
for (auto ins = mInputs.begin(); ins != mInputs.end(); ins++)
{
if (ins->mObject && ins->mUpdate && mInputTime == ins->mObject->mFrameTime)
{
callUpdate = true;
if (ins->mParameters)
mParameters.set(ins->mObject->getOutput(ins->mIndex));
}
}
// Custom Update
if (callUpdate)
{
mInUpdate = true;
update();
mInUpdate = false;
}
if (getType() == kScheduler)
{
// Find the input time (the min valid time of all inputs)
mInputTime = FrameLib_TimeFormat::largest();
for (auto ins = mInputs.begin(); ins != mInputs.end(); ins++)
if (ins->mObject && ins->mObject->mValidTime < mInputTime)
mInputTime = ins->mObject->mValidTime;
// Schedule
bool upToDate = (mValidTime >= mBlockEndTime) || mUpdatingInputs;
SchedulerInfo scheduleInfo = schedule(mOutputDone && !upToDate, upToDate);
// Check if time has been updated (limiting to positive advances only), and if so set output times
if ((timeUpdated = !upToDate && scheduleInfo.mTimeAdvance.greaterThanZero()))
{
if (scheduleInfo.mNewFrame || mOutputDone)
{
resetOutputDependencyCount();
mFrameTime = mValidTime;
}
mValidTime += scheduleInfo.mTimeAdvance;
mOutputDone = scheduleInfo.mOutputDone;
}
// Revise the input time to take account of the end of the current frame (in order that we don't free anything we might still need)
if (mValidTime < mInputTime)
mInputTime = mValidTime;
}
else
{
// Find the valid till time (the min valid time of connected inputs that can trigger) and input time (the min valid time of all inputs)
// Check for inputs at the current time that trigger (N.B. this is done after any update)
FrameLib_TimeFormat prevValidTime = mValidTime;
bool trigger = false;
mInputTime = FrameLib_TimeFormat::largest();
mValidTime = FrameLib_TimeFormat::largest();
for (auto ins = mInputs.begin(); ins != mInputs.end(); ins++)
{
if (ins->mObject && (ins->mTrigger || ins->mSwitchable) && ins->mObject->mValidTime < mValidTime)
mValidTime = ins->mObject->mValidTime;
if (ins->mObject && ins->mObject->mValidTime < mInputTime)
mInputTime = ins->mObject->mValidTime;
trigger |= (ins->mObject && ins->mTrigger && prevValidTime == ins->mObject->mFrameTime);
}
// If triggered update the frame time, process and release the inputs if we only have one dependency
if (trigger)
{
mFrameTime = prevValidTime;
process();
resetOutputDependencyCount();
if (mInputDependencies.size() == 1)
(*mInputDependencies.begin())->releaseOutputMemory();
}
// Check for the frame times updating and if so check for completion of the frame
if (mValidTime != prevValidTime)
{
timeUpdated = true;
mOutputDone = true;
for (auto ins = mInputs.begin(); ins != mInputs.end(); ins++)
{
if (ins->mObject && ((ins->mTrigger && !ins->mSwitchable) || (!ins->mObject->mOutputDone && ins->mSwitchable)) && (mValidTime == ins->mObject->mValidTime))
{
if ((mOutputDone = ins->mObject->mOutputDone))
break;
}
}
}
}
// Check for host alignment for objects requiring audio notification (treating the audio notification as a time dependency)
bool hostAligned = requiresAudioNotification() && mInputTime >= mBlockEndTime;
if (hostAligned)
mInputTime = mBlockEndTime;
// Check if we have reached the end of time or need to just update inputs
bool endOfTime = mInputTime == FrameLib_TimeFormat::largest();
bool prevUpdatingInputs = mUpdatingInputs;
mUpdatingInputs = mInputTime < mValidTime;
// Increment the input dependency for the audio update if necessary (must be after we know if we are updating inputs only)
if (hostAligned)
incrementInputDependency();
// Update dependency count for outputs and updating input state starting
mDependencyCount += ((timeUpdated ? getNumOuputDependencies() : 0)) + ((mUpdatingInputs > prevUpdatingInputs) ? 1 : 0);
// Notify input dependencies that can be released as they are up to date (releasing memory where relevant for objects with more than one input dependency)
if (!endOfTime)
{
// Inputs cannot move beyond the end of time...
for (auto it = mInputDependencies.begin(); it != mInputDependencies.end(); it++)
{
if (mInputTime == (*it)->mValidTime)
{
incrementInputDependency();
(*it)->dependencyNotify((getType() == kScheduler || mInputDependencies.size() != 1) && (*it)->mOutputDone, false);
}
}
}
// If time has updated then notify output dependencies (of updates to their inputs)
if (timeUpdated)
for (auto it = mOutputDependencies.begin(); it != mOutputDependencies.end(); it++)
(*it)->dependencyNotify(false, true);
// See if the updating input status has expired (must be done after resolving all other dependencies)
if (mUpdatingInputs < prevUpdatingInputs)
dependencyNotify(false, false);
// Allow self-triggering if we haven't reached the end of time
if (!endOfTime)
dependencyNotify(false, false);
// Debug
if (requiresAudioNotification())
assert(prevInputTime >= mBlockStartTime && prevInputTime < mBlockEndTime && "Out of sync with host");
assert(mInputTime > prevInputTime && "Failed to move time forward");
assert(mInputTime <= mValidTime && "Inputs are ahead of output");
assert(mFrameTime <= mInputTime && "Output is ahead of input dependencies");
}
void FrameLib_DSP::resetOutputDependencyCount()
{
mOutputMemoryCount = getNumOuputDependencies();
}
// Manage Output Memory
inline void FrameLib_DSP::freeOutputMemory()
{
if (getNumOuts() && mOutputs[0].mMemory)
{
// Call the destructor for any serial outputs
for (auto outs = mOutputs.begin(); outs != mOutputs.end(); outs++)
if (outs->mCurrentType == kFrameTagged)
((Serial *)outs->mMemory)->Serial::~Serial();
// Then deallocate (will also set to nullptr)
dealloc(mOutputs[0].mMemory);
}
}
inline void FrameLib_DSP::releaseOutputMemory()
{
if (--mOutputMemoryCount == 0)
freeOutputMemory();
}
// Connection Updating
void FrameLib_DSP::connectionUpdate(Queue *queue)
{
std::vector<FrameLib_DSP *>::iterator it;
// Clear dependencies
mInputDependencies.clear();
mOutputDependencies.clear();
// Build the input list
mInputs.resize(getNumIns() + getNumOrderingConnections());
for (unsigned long i = 0; i < getNumIns() + getNumOrderingConnections(); i++)
{
// Make sure that ordering inputs are set correctly
if (i >= getNumIns())
setInputMode(i, false, false, false);
// Add the DSP object connection details to the input
Connection connection = i < getNumIns() ? getConnectionInternal(i) : getOrderingConnectionInternal(i - getNumIns());
mInputs[i].mObject = dynamic_cast<FrameLib_DSP *>(connection.mObject);
mInputs[i].mIndex = mInputs[i].mObject ? connection.mIndex : 0;
// Build the input dependency list
if (mInputs[i].mObject)
addUniqueItem(mInputDependencies, mInputs[i].mObject);
}
// Build the output dependency list
addOutputDependencies(mOutputDependencies);
}
void FrameLib_DSP::autoOrderingConnections()
{
if (supportsOrderingConnections())
LocalQueue(this, &FrameLib_DSP::autoOrderingConnections);
}
void FrameLib_DSP::autoOrderingConnections(LocalQueue *queue)
{
if (supportsOrderingConnections() && queue->getFirst())
addOrderingConnection(Connection(queue->getFirst(), 0));
for (auto it = mOutputDependencies.begin(); it != mOutputDependencies.end(); it++)
queue->add(*it);
}
void FrameLib_DSP::clearAutoOrderingConnections()
{
callConnectionUpdate();
}