thanks for all your suggestions I thought it would be possible to transfer the following sketch i made which is using numPartials
Dust
triggers to an implementation which is just using a single Impulse
trigger with an additional “partial mask”, which would enable me to implement it in my current additive synthesis granulation approach. I thought masking the spectrum to a single partial would do it, but didnt know that it would be that tricky…
(
var makeStretchedHarmonicSeries = { |numPartials, freq, inharmonicity|
var chain = (
numPartials: numPartials,
ratios: (1..numPartials),
amps: 1 ! numPartials,
);
var ratios = (1..numPartials);
var stretchedRatios = ratios * (1 + (inharmonicity * ratios * ratios)).sqrt;
chain[\freqs] = stretchedRatios * freq;
chain;
};
var addSpectralTilt = { |chain, tiltPerOctave|
chain[\amps] = chain[\amps] * (chain[\ratios].log2 * tiltPerOctave).dbamp;
chain;
};
var granulate = { |chain, blend, rate|
var trig = Dust.kr(rate ! chain[\numPartials]);
var grainWindow = blend(
DC.kr(1),
EnvGen.kr(Env([0, 1, 0], [0.001, 0.999], [4.0, -4.0]), trig, doneAction: Done.none),
blend
);
chain[\amps] = chain[\amps] * grainWindow;
};
SynthDef(\additive_granulation, {
var numPartials = 50;
var chain, sig;
chain = makeStretchedHarmonicSeries.(numPartials, \freq.kr(440), \inharmonicity.kr(0.005));
chain = addSpectralTilt.(chain, \tiltPerOctaveDb.kr(-3));
chain = granulate.(chain, \grainOn.kr(1), \grainRate.kr(1));
sig = SinOsc.ar(
freq: chain[\freqs],
phase: { Rand(0, 2pi) } ! chain[\numPartials],
mul: chain[\amps]
).sum;
sig = Pan2.ar(sig, \pan.kr(0));
sig = sig * \amp.kr(-15.dbamp);
sig = sig * Env.asr(0.001, 1, 0.001).ar(Done.freeSelf, \gate.kr(1));
OffsetOut.ar(\out.kr(0), sig);
}).add;
)
Synth(\additive_granulation, [\freq, 44.midicps]);
This is my additive granulation setup where i cant easily implement the granulate
function, because you have already granulation in place with timingInformation
+ IEnvGen
here. I think it makes no sense to also add the granulate
function to the chain
, then the IEnvGen
grain window and the granulate
grain window are not related to each other.
I would like to have the option to crossfade between granulation of the summed spectrum and granulation of the spectrum itself whithout adding another unrelated envelope / window with EnvGen
and using additional unrelated triggers with Dust
. It should all work with Impulse
+ timingInformation
+ IEnvGen
.
(
var makeStretchedHarmonicSeries = { |numPartials, freq, inharmonicity|
var chain = (
numPartials: numPartials,
ratios: (1..numPartials),
amps: 1 ! numPartials,
);
var ratios = (1..numPartials);
var stretchedRatios = ratios * (1 + (inharmonicity * ratios * ratios)).sqrt;
chain[\freqs] = stretchedRatios * freq;
chain;
};
var addSpectralTilt = { |chain, tiltPerOctave|
chain[\amps] = chain[\amps] * (chain[\ratios].log2 * tiltPerOctave).dbamp;
chain;
};
var timingInformation = { |numChannels, trig, grainRate|
var rate = if(trig.rate == \audio, \ar, \kr);
var arrayOfTrigsAndPhases = numChannels.collect{ |i|
var localTrig = PulseDivider.perform(rate, trig, numChannels, i);
var hasTriggered = PulseCount.perform(rate, localTrig) > 0;
var localPhase = Sweep.perform(rate, localTrig, grainRate * hasTriggered);
[localTrig, localPhase];
};
var trigsAndPhasesArray = arrayOfTrigsAndPhases.flop;
(\trigger : trigsAndPhasesArray[0], \phase: trigsAndPhasesArray[1]);
};
var addPartialMask = { |chain, trig, maskOn|
var selectedPartial = Demand.ar(trig, 0, Dxrand(chain[\ratios] - 1, inf));
var mask = chain[\numPartials].collect{ |partial|
InRange.ar(selectedPartial, partial, partial);
};
selectedPartial.poll(trig, \selectedPartial);
chain[\amps] = chain[\amps] * blend(DC.ar(1), mask, maskOn);
chain;
};
SynthDef(\additive_granulation, {
var numChannels = 5;
var numPartials = 50;
var tFreq = \tFreq.kr(1);
var trig = Impulse.ar(tFreq);
var grainRate = tFreq / \overlap.kr(1);
var timings = timingInformation.(numChannels, trig, grainRate);
var grainWindows = IEnvGen.ar(Env([0, 1, 0], [0.001, 0.999], [4.0, -4.0]), timings.phase);
var chain, sigs, sig;
chain = makeStretchedHarmonicSeries.(numPartials, \freq.kr(440), \inharmonicity.kr(0.005));
chain = addSpectralTilt.(chain, \tiltPerOctaveDb.kr(-3));
chain = addPartialMask.(chain, trig, \maskOn.kr(1));
sigs = SinOsc.ar(
freq: chain[\freqs],
phase: { Rand(0, 2pi) } ! chain[\numPartials],
mul: chain[\amps]
).sum;
sigs = sigs * grainWindows;
sigs = PanAz.ar(2, sigs, \pan.kr(0));
sig = sigs.sum;
sig = sig * \amp.kr(-15.dbamp);
sig = sig * Env.asr(0.001, 1, 0.001).ar(Done.freeSelf, \gate.kr(1));
OffsetOut.ar(\out.kr(0), sig);
}).add;
)
(
Synth(\additive_granulation, [
\tFreq, 10,
\overlap, 5,
\maskOn, 1,
\freq, 44.midicps,
]);
)
I have used Demand
and InRange
to select a single Partial and set all the other amplitudes to zero. But this sounds really different compared to the numPartial
times Dust
triggers because you dont get the overlapping windows.