From dca58842ceb691de27adbdc82f3d9186dd0b3325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Weibezahn?= Date: Thu, 5 Jun 2025 22:11:08 +0200 Subject: [PATCH 1/4] Add a user-definable "grain size" to PhaseLockedVocoder The size of the mincer in the DSP can be set on init of PhaseLockedVocoder in a range of 128...8192. A smaller value makes the sound crisper when the sample position is moved. But a greater mincer size might also be desirable because the spectral freeze changes audibly. --- .../Generators/PhaseLockedVocoderDSP.mm | 17 +++++++++++-- .../include/CSoundpipeAudioKit.h | 3 ++- .../Generators/PhaseLockedVocoder.swift | 24 +++++++++++++++++-- 3 files changed, 39 insertions(+), 5 deletions(-) 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..9c1eaa1 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, @@ -25,7 +26,7 @@ public class PhaseLockedVocoder: Node { range: 0 ... 100_000, unit: .generic ) - + /// Position in time. When non-changing it will do a spectral freeze of a the current point in time. @Parameter(positionDef) public var position: AUValue @@ -69,16 +70,35 @@ 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() { From 4ee255834098a0fe7bfce0dcd920c4095a0f54b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Weibezahn?= Date: Thu, 5 Jun 2025 22:15:18 +0200 Subject: [PATCH 2/4] Formatting --- .../SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift b/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift index 9c1eaa1..c2c1b54 100644 --- a/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift +++ b/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift @@ -26,7 +26,7 @@ public class PhaseLockedVocoder: Node { range: 0 ... 100_000, unit: .generic ) - + /// Position in time. When non-changing it will do a spectral freeze of a the current point in time. @Parameter(positionDef) public var position: AUValue @@ -76,7 +76,7 @@ public class PhaseLockedVocoder: Node { setupParameters() loadFile(file) - + let safeGrainSize = roundUpToPowerOfTwo(grainSize) akPhaseLockedVocoderSetMincerSize(au.dsp, safeGrainSize) @@ -85,7 +85,8 @@ public class PhaseLockedVocoder: Node { 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 + /// 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 From c6ce5c02f224b00a22640ef0c39f47eea21fe7b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Weibezahn?= Date: Fri, 6 Jun 2025 09:42:33 +0200 Subject: [PATCH 3/4] Added grain size param to the comments --- Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift b/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift index c2c1b54..a2c0a2d 100644 --- a/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift +++ b/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift @@ -65,6 +65,7 @@ public class PhaseLockedVocoder: Node { /// - position: Position in time. When non-changing it will do a spectral freeze of a the current point in time. /// - amplitude: Amplitude. /// - pitchRatio: Pitch ratio. A value of. 1 normal, 2 is double speed, 0.5 is halfspeed, etc. + /// - grainSize: The size in samples (power of 2) of the spectral freeze. Lower sizes make for a crisper sound when moving the playhead. /// public init( file: AVAudioFile, From b3ee6baa1c0ebf9c2678c0df32a30d5dbd373275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Weibezahn?= Date: Fri, 6 Jun 2025 09:49:05 +0200 Subject: [PATCH 4/4] Line length --- Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift b/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift index a2c0a2d..c573ee2 100644 --- a/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift +++ b/Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift @@ -65,7 +65,7 @@ public class PhaseLockedVocoder: Node { /// - position: Position in time. When non-changing it will do a spectral freeze of a the current point in time. /// - amplitude: Amplitude. /// - pitchRatio: Pitch ratio. A value of. 1 normal, 2 is double speed, 0.5 is halfspeed, etc. - /// - grainSize: The size in samples (power of 2) of the spectral freeze. Lower sizes make for a crisper sound when moving the playhead. + /// - grainSize: Size (samples, pow 2) of the spectral freeze. Lower sizes – crisper sound when moving position. /// public init( file: AVAudioFile,