Skip to content

Commit dca5884

Browse files
committed
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.
1 parent efc0d67 commit dca5884

File tree

3 files changed

+39
-5
lines changed

3 files changed

+39
-5
lines changed

Sources/CSoundpipeAudioKit/Generators/PhaseLockedVocoderDSP.mm

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "SoundpipeDSPBase.h"
44
#include "ParameterRamper.h"
55
#include "Soundpipe.h"
6+
#include "CSoundpipeAudioKit.h"
67
#include <vector>
78

89
enum PhaseLockedVocoderParameter : AUParameterAddress {
@@ -16,6 +17,7 @@
1617
sp_mincer *mincer;
1718
sp_ftbl *ftbl;
1819
std::vector<float> wavetable;
20+
int mincerSize = 2048;
1921

2022
ParameterRamper positionRamp;
2123
ParameterRamper amplitudeRamp;
@@ -42,7 +44,7 @@ void init(int channelCount, double sampleRate) override {
4244
sp_ftbl_create(sp, &ftbl, wavetable.size());
4345
std::copy(wavetable.cbegin(), wavetable.cend(), ftbl->tbl);
4446
sp_mincer_create(&mincer);
45-
sp_mincer_init(sp, mincer, ftbl, 2048);
47+
sp_mincer_init(sp, mincer, ftbl, mincerSize);
4648
}
4749

4850
void deinit() override {
@@ -56,7 +58,12 @@ void reset() override {
5658
if (!isInitialized) return;
5759
sp_mincer_destroy(&mincer);
5860
sp_mincer_create(&mincer);
59-
sp_mincer_init(sp, mincer, ftbl, 2048);
61+
sp_mincer_init(sp, mincer, ftbl, mincerSize);
62+
}
63+
64+
void setMincerSize(int size) {
65+
mincerSize = size;
66+
reset();
6067
}
6168

6269
void process(FrameRange range) override {
@@ -73,6 +80,12 @@ void process(FrameRange range) override {
7380
}
7481
};
7582

83+
void akPhaseLockedVocoderSetMincerSize(DSPRef dspRef, int size) {
84+
auto dsp = dynamic_cast<PhaseLockedVocoderDSP *>(dspRef);
85+
assert(dsp);
86+
dsp->setMincerSize(size);
87+
}
88+
7689
AK_REGISTER_DSP(PhaseLockedVocoderDSP, "minc")
7790
AK_REGISTER_PARAMETER(PhaseLockedVocoderParameterPosition)
7891
AK_REGISTER_PARAMETER(PhaseLockedVocoderParameterAmplitude)

Sources/CSoundpipeAudioKit/include/CSoundpipeAudioKit.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ void akCombFilterReverbSetLoopDuration(DSPRef dsp, float duration);
1313
void akConvolutionSetPartitionLength(DSPRef dsp, int length);
1414
void akFlatFrequencyResponseSetLoopDuration(DSPRef dsp, float duration);
1515
void akVariableDelaySetMaximumTime(DSPRef dsp, float maximumTime);
16-
CF_EXTERN_C_END
16+
void akPhaseLockedVocoderSetMincerSize(DSPRef dspRef, int size);
17+
CF_EXTERN_C_END

Sources/SoundpipeAudioKit/Generators/PhaseLockedVocoder.swift

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import AudioKit
44
import AudioKitEX
55
import AVFoundation
66
import CAudioKitEX
7+
import CSoundpipeAudioKit
78

89
/// This is a phase locked vocoder. It has the ability to play back an audio
910
/// file loaded into an ftable like a sampler would. Unlike a typical sampler,
@@ -25,7 +26,7 @@ public class PhaseLockedVocoder: Node {
2526
range: 0 ... 100_000,
2627
unit: .generic
2728
)
28-
29+
2930
/// Position in time. When non-changing it will do a spectral freeze of a the current point in time.
3031
@Parameter(positionDef) public var position: AUValue
3132

@@ -69,16 +70,35 @@ public class PhaseLockedVocoder: Node {
6970
file: AVAudioFile,
7071
position: AUValue = positionDef.defaultValue,
7172
amplitude: AUValue = amplitudeDef.defaultValue,
72-
pitchRatio: AUValue = pitchRatioDef.defaultValue
73+
pitchRatio: AUValue = pitchRatioDef.defaultValue,
74+
grainSize: Int32 = 2048
7375
) {
7476
setupParameters()
7577

7678
loadFile(file)
79+
80+
let safeGrainSize = roundUpToPowerOfTwo(grainSize)
81+
akPhaseLockedVocoderSetMincerSize(au.dsp, safeGrainSize)
7782

7883
self.position = position
7984
self.amplitude = amplitude
8085
self.pitchRatio = pitchRatio
8186
}
87+
88+
/// 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
89+
/// (should we warn the user if they submit a value which is not in that range or is not a power of two?)
90+
func roundUpToPowerOfTwo(_ value: Int32) -> Int32 {
91+
let range: ClosedRange<Int32> = 128...8192
92+
guard range.contains(value) else { return min(max(value, range.lowerBound), range.upperBound) }
93+
var result = value - 1
94+
result |= result >> 1
95+
result |= result >> 2
96+
result |= result >> 4
97+
result |= result >> 8
98+
result |= result >> 16
99+
result += 1
100+
return result
101+
}
82102

83103
/// Call this function after you are done with the node, to reset the au wavetable to prevent memory leaks
84104
public func dispose() {

0 commit comments

Comments
 (0)