You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The current AudioProcessor class is a monolithic base that mixes domain-agnostic concerns (name, parameters, state, playhead, lock, presets) with audio-domain specifics (bus layout, sample rate, processBlock). Introducing SpectralProcessor requires extracting these concerns cleanly so both domains can share infrastructure without coupling. AudioGraphProcessor requires no changes — the graph sees all nodes polymorphically through ProcessorBase; mixed-domain support is achieved via a FFTBridgeNode that is a plain AudioProcessor containing a spectral subgraph.
processBlock(AudioBuffer<float>&, MidiBuffer&) stays pure-virtual. process(AudioProcessContext&) is added as a final non-overridable that builds the context and delegates to processBlock. Existing subclasses compile unchanged. New code continues to override processBlock.
// AudioProcessContext — new, in yup_audio_processorsstructAudioProcessContext
{
AudioBuffer<float>& audio;
MidiBuffer& midi;
uint64_t samplePosition = 0;
};
// In AudioProcessorvoidprocess (AudioProcessContext& ctx) overridefinal// new entry point from DomainProcessor
{
processBlock (ctx.audio, ctx.midi);
}
AudioPrepareContext wraps (sampleRate, maxBlockSize) for symmetry; prepare() on AudioProcessor stores both then calls prepareToPlay().
AudioGraphProcessor unchanged
AudioGraphProcessor currently stores shared_ptr<AudioProcessor> and calls processBlock. This remains. The spectral domain enters the audio graph only via FFTBridgeNode, which is a regular AudioProcessor node. No changes to graph scheduling, routing, or compilation.
SpectralProcessor prepare vs constructor
FFT size and hop size live in SpectralPrepareContext (not the constructor) so they can change between prepareSpectral() calls without recreating nodes. This matches the pattern of AudioProcessor::prepareToPlay.
Phase 1 — ProcessorBase + DomainProcessor (in yup_audio_processors)
DomainProcessor<AudioProcessContext, AudioPrepareContext> concrete subclass dispatches to process()
Phase 2 — SpectralBuffer + SpectralProcessor (new yup_spectral_processors module)
The new module depends on yup_audio_processors (for DomainProcessor, ProcessorBase) and yup_dsp (for FFTProcessor). It lives separately rather than in yup_dsp to avoid a circular dependency.
New module directory: modules/yup_spectral_processors/
yup_SpectralBufferView.h — non-owning view, wraps SpectralBuffer or raw float** pointers.
yup_SpectralPrepareContext.h
structSpectralPrepareContext
{
float sampleRate = 44100.0f;
int fftSize = 2048; // must be power of 2; numBins = fftSize/2+1 (real FFT)int hopSize = 512; // how often process() fires in samplesint numChannels = 2;
};
yup_SpectralProcessContext.h
structSpectralProcessContext
{
SpectralBufferView spectrum; // bins to read/modify in-placeuint64_t frameIndex = 0; // monotonically increasing since last reset()uint64_t samplePosition = 0; // audio-sample position of this hop's first sample
};
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
-
Plan: ProcessorBase / DomainProcessor / SpectralProcessor Hierarchy
Context
The current
AudioProcessorclass is a monolithic base that mixes domain-agnostic concerns (name, parameters, state, playhead, lock, presets) with audio-domain specifics (bus layout, sample rate,processBlock). IntroducingSpectralProcessorrequires extracting these concerns cleanly so both domains can share infrastructure without coupling.AudioGraphProcessorrequires no changes — the graph sees all nodes polymorphically throughProcessorBase; mixed-domain support is achieved via aFFTBridgeNodethat is a plainAudioProcessorcontaining a spectral subgraph.Architecture Overview
Key Design Decisions
Backward compatibility on AudioProcessor
processBlock(AudioBuffer<float>&, MidiBuffer&)stays pure-virtual.process(AudioProcessContext&)is added as afinalnon-overridable that builds the context and delegates toprocessBlock. Existing subclasses compile unchanged. New code continues to overrideprocessBlock.AudioPrepareContextwraps(sampleRate, maxBlockSize)for symmetry;prepare()onAudioProcessorstores both then callsprepareToPlay().AudioGraphProcessor unchanged
AudioGraphProcessorcurrently storesshared_ptr<AudioProcessor>and callsprocessBlock. This remains. The spectral domain enters the audio graph only viaFFTBridgeNode, which is a regularAudioProcessornode. No changes to graph scheduling, routing, or compilation.SpectralProcessor prepare vs constructor
FFT size and hop size live in
SpectralPrepareContext(not the constructor) so they can change betweenprepareSpectral()calls without recreating nodes. This matches the pattern ofAudioProcessor::prepareToPlay.Phase 1 — ProcessorBase + DomainProcessor (in
yup_audio_processors)New files
modules/yup_audio_processors/processors/yup_ProcessorBase.hExtracts from
AudioProcessor:processorName,getName()parameters,parameterMap,addParameter(),getParameters()playHead,setPlayHead(),getPlayHead()processLock,processIsSuspended,getProcessLock(),isSuspended(),suspendProcessing()getCurrentPreset,setCurrentPreset,getNumPresets,getPresetName,setPresetName,loadStateFromMemory,saveStateIntoMemory,hasEditorcreateEditor()modules/yup_audio_processors/processors/yup_ProcessorBase.cppImplementations currently in
yup_AudioProcessor.cppfor the above members.modules/yup_audio_processors/processors/yup_DomainProcessor.h(header-only template)modules/yup_audio_processors/processors/yup_AudioProcessContext.hModified files
modules/yup_audio_processors/processors/yup_AudioProcessor.hclass AudioProcessor→class AudioProcessor : public DomainProcessor<AudioProcessContext, AudioPrepareContext>processorName,parameters,parameterMap,playHead,processLock,processIsSuspended(now in ProcessorBase)busLayout,sampleRate,samplesPerBlock,processingPrecisionprepare()final → stores sampleRate/samplesPerBlock, callssetPlaybackConfigurationthenprepareToPlayreset()final → callsreleaseResources()process(AudioProcessContext&)final → callsprocessBlock(ctx.audio, ctx.midi)prepareToPlay,releaseResources,processBlock, etc.)modules/yup_audio_processors/processors/yup_AudioProcessor.cppyup_ProcessorBase.cppmodules/yup_audio_processors/yup_audio_processors.h— add includes beforeyup_AudioBus.h:modules/yup_audio_processors/yup_audio_processors.cpp— add:Phase 1 tests
tests/yup_audio_processors/ProcessorBase.cppaddParameter/getParametersround-tripssuspendProcessing/isSuspendedround-tripssetPlayHead/getPlayHeadround-tripsDomainProcessor<AudioProcessContext, AudioPrepareContext>concrete subclass dispatches toprocess()Phase 2 — SpectralBuffer + SpectralProcessor (new
yup_spectral_processorsmodule)The new module depends on
yup_audio_processors(forDomainProcessor,ProcessorBase) andyup_dsp(forFFTProcessor). It lives separately rather than inyup_dspto avoid a circular dependency.New module directory:
modules/yup_spectral_processors/yup_SpectralBuffer.hOwning multi-channel complex float buffer.
Layout: per-channel interleaved
[re0, im0, re1, im1, ...]matchingFFTProcessor::performRealFFTForwardoutput convention.yup_SpectralBufferView.h— non-owning view, wrapsSpectralBufferor rawfloat**pointers.yup_SpectralPrepareContext.hyup_SpectralProcessContext.hyup_SpectralProcessor.hPhase 2 tests
tests/yup_spectral_processors/SpectralBuffer.cppsetSizeallocates;getNumChannels/Binscorrectclear()zeros all dataaddFrom/copyFromproduce correct valuesSpectralBufferViewoverSpectralBufferyields correct pointerstests/yup_spectral_processors/SpectralProcessor.cppframeIndexprepare()storessampleRate_,fftSize_,hopSize_,numChannels_reset()callsreleaseSpectralResources()getLatencyFrames()defaults to 0Phase 3 — SpectralGraphProcessor + FFTBridgeNode
New files in
modules/yup_spectral_processors/yup_SpectralGraphProcessor— mirrorsAudioGraphProcessorarchitecture:SpectralProcessornodescommitChanges()→ compiled immutable planSpectralProcessor(can be nested inside anotherSpectralGraphProcessor)AudioBusLayout— I/O topology is(fftSize, numChannels)fromSpectralPrepareContextyup_FFTBridgeNode— is anAudioProcessor, enabling mixed-domain graphs:processBlockalgorithm (no allocation):hopCounter_hopCounter_ >= hopSize_: extractfftSize_samples with analysis window →FFTProcessor::performRealFFTForwardper channel → buildSpectralProcessContext→spectralGraph_->process()→performRealFFTInverseper channel → overlap-add into output FIFO → decrementhopCounter_byhopSize_, incrementframeIndex_numSamplesfrom output FIFO intoaudioBufferPhase 3 tests
tests/yup_spectral_processors/SpectralGraphProcessor.cpptests/yup_spectral_processors/FFTBridgeNode.cppprocessBlockcall boundariesgetLatencySamples()== fftSizeprocessBlock(verified via custom allocator instrumentation)tests/yup_spectral_processors/SpectralIntegration.cppAudioGraphProcessorwithFFTBridgeNode(identity spectral graph) produces output matching input within float toleranceAudioGraphProcessor::getAllocationStats().delayLines == 1(latency compensation forFFTBridgeNode)Critical Files
modules/yup_audio_processors/processors/yup_AudioProcessor.hmodules/yup_audio_processors/processors/yup_AudioProcessor.cppmodules/yup_audio_processors/yup_audio_processors.hmodules/yup_audio_processors/yup_audio_processors.cppmodules/yup_audio_graph/graph/yup_AudioGraphProcessor.cppmodules/yup_dsp/frequency/yup_FFTProcessor.hVerification
Phase 1: All existing
AudioProcessorsubclasses (includingAudioGraphProcessor) compile without changes. Existing tests pass. NewProcessorBasetests pass.Phase 2: New
SpectralBufferandSpectralProcessortests pass. Module builds standalone.Phase 3: Integration test confirms
AudioGraphProcessor+FFTBridgeNoderound-trip produces identity output.FFTBridgeNodetests confirm real-time safety (no allocation inprocessBlock).Beta Was this translation helpful? Give feedback.
All reactions