diff --git a/Sources/CSoundpipeAudioKit/Generators/PhaseLockedVocoderDSP.mm b/Sources/CSoundpipeAudioKit/Generators/PhaseLockedVocoderDSP.mm index 6b255b0..aab0c57 100644 --- a/Sources/CSoundpipeAudioKit/Generators/PhaseLockedVocoderDSP.mm +++ b/Sources/CSoundpipeAudioKit/Generators/PhaseLockedVocoderDSP.mm @@ -3,6 +3,7 @@ #include "SoundpipeDSPBase.h" #include "ParameterRamper.h" #include "Soundpipe.h" +#include "CSoundpipeAudioKit.h" #include enum PhaseLockedVocoderParameter : AUParameterAddress { @@ -16,6 +17,7 @@ sp_mincer *mincer; sp_ftbl *ftbl; std::vector wavetable; + int mincerSize = 2048; ParameterRamper positionRamp; ParameterRamper amplitudeRamp; @@ -42,7 +44,7 @@ void init(int channelCount, double sampleRate) override { sp_ftbl_create(sp, &ftbl, wavetable.size()); std::copy(wavetable.cbegin(), wavetable.cend(), ftbl->tbl); sp_mincer_create(&mincer); - sp_mincer_init(sp, mincer, ftbl, 2048); + sp_mincer_init(sp, mincer, ftbl, mincerSize); } void deinit() override { @@ -56,7 +58,12 @@ void reset() override { if (!isInitialized) return; sp_mincer_destroy(&mincer); sp_mincer_create(&mincer); - sp_mincer_init(sp, mincer, ftbl, 2048); + sp_mincer_init(sp, mincer, ftbl, mincerSize); + } + + void setMincerSize(int size) { + mincerSize = size; + reset(); } void process(FrameRange range) override { @@ -73,6 +80,12 @@ void process(FrameRange range) override { } }; +void akPhaseLockedVocoderSetMincerSize(DSPRef dspRef, int size) { + auto dsp = dynamic_cast(dspRef); + assert(dsp); + dsp->setMincerSize(size); +} + AK_REGISTER_DSP(PhaseLockedVocoderDSP, "minc") AK_REGISTER_PARAMETER(PhaseLockedVocoderParameterPosition) AK_REGISTER_PARAMETER(PhaseLockedVocoderParameterAmplitude) diff --git a/Sources/CSoundpipeAudioKit/include/CSoundpipeAudioKit.h b/Sources/CSoundpipeAudioKit/include/CSoundpipeAudioKit.h index ffcef4f..f33b192 100644 --- a/Sources/CSoundpipeAudioKit/include/CSoundpipeAudioKit.h +++ b/Sources/CSoundpipeAudioKit/include/CSoundpipeAudioKit.h @@ -13,4 +13,5 @@ void akCombFilterReverbSetLoopDuration(DSPRef dsp, float duration); void akConvolutionSetPartitionLength(DSPRef dsp, int length); void akFlatFrequencyResponseSetLoopDuration(DSPRef dsp, float duration); void akVariableDelaySetMaximumTime(DSPRef dsp, float maximumTime); -CF_EXTERN_C_END \ No newline at end of file +void akPhaseLockedVocoderSetMincerSize(DSPRef dspRef, int size); +CF_EXTERN_C_END diff --git a/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift b/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift index 7781005..c2c1b54 100644 --- a/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift +++ b/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift @@ -4,6 +4,7 @@ import AudioKit import AudioKitEX import AVFoundation import CAudioKitEX +import CSoundpipeAudioKit /// This is a phase locked vocoder. It has the ability to play back an audio /// file loaded into an ftable like a sampler would. Unlike a typical sampler, @@ -69,16 +70,36 @@ public class PhaseLockedVocoder: Node { file: AVAudioFile, position: AUValue = positionDef.defaultValue, amplitude: AUValue = amplitudeDef.defaultValue, - pitchRatio: AUValue = pitchRatioDef.defaultValue + pitchRatio: AUValue = pitchRatioDef.defaultValue, + grainSize: Int32 = 2048 ) { setupParameters() loadFile(file) + let safeGrainSize = roundUpToPowerOfTwo(grainSize) + akPhaseLockedVocoderSetMincerSize(au.dsp, safeGrainSize) + self.position = position self.amplitude = amplitude self.pitchRatio = pitchRatio } + + /// The grain size range is 128 - 8192 and it must be a power of two. + /// If it isn't one already, this function will round it up to the next power of two + /// (should we warn the user if they submit a value which is not in that range or is not a power of two?) + func roundUpToPowerOfTwo(_ value: Int32) -> Int32 { + let range: ClosedRange = 128...8192 + guard range.contains(value) else { return min(max(value, range.lowerBound), range.upperBound) } + var result = value - 1 + result |= result >> 1 + result |= result >> 2 + result |= result >> 4 + result |= result >> 8 + result |= result >> 16 + result += 1 + return result + } /// Call this function after you are done with the node, to reset the au wavetable to prevent memory leaks public func dispose() {