Comb-like effect with pitch shifting

I am trying to make an effect to a live signal, such that the a signal of a short duration (like a hit on a drum) is repeated (in a way like what Comb family do) and in each repetition the pitch of the signal is modified. What I don’t like about the combs is that they lose on energy, whereas I want to have the echo parts be as strong as the original input signal. Following is what I started to experimenting, but the result is not satisfying me. Here I was thinking to trigger some value on each echo-time as the pitch ratio, and feed that ratio to the pitch-shifter.
I would appreciate it, if some one could give me some tips on how to implement what I described.

my code so far:

(
x = {
    arg t_reset=0;
    var in;
    var lfn = LFNoise1.kr(0.4);
	var my = MouseY.kr(0.01, 0.5, 1);
	var winsz = 0.3;
    var dectime = 2, deltime = 0.5;
    var ampthreshold = 0.3;
    var imp = Impulse.kr(deltime.reciprocal);
    var dem;
	in = SoundIn.ar([0, 1]);
    dem = Demand.kr(
        imp, Amplitude.ar(in) > ampthreshold,
        Drand([
            Dgeom(4, 0.5, 5),
            Dgeom(3, 0.3, 5),
            Dgeom(0.3, 1.3, 5)
        ])
    );
	in = CombC.ar(in, 1, delaytime: deltime, decaytime: dectime, add: in * 0.7);
	in = PitchShift.ar(
		in: in,
        pitchRatio: dem 
	);
	in = in * EnvGen.kr(Env.perc(releaseTime: dectime, level: 0.5), 
    gate: (Amplitude.ar(in)> ampthreshold),
    doneAction: 0);
	}.play
)

One way to get that kind of effect is to use LocalIn/LocalOut and put a DelayC and Pitchshift in the feedback loop.
Here’s an example where I’ve boosted the signal to compensate for signal loss with pitch shifting.:

(
{
    var source, local, feedback, out, fbBoost;
​
	source = Decay.ar(Impulse.ar(1), 0.2) * WhiteNoise.ar(0.2);
	// source = Decay.ar(Impulse.ar(1), 0.2) * SinOsc.ar(440, 0.0, 0.2);

	// read feedback, add to source
	feedback = LocalIn.ar(2);
	local = feedback + source;
    local = DelayC.ar(local, 1, 0.1);
	// pitch shift and boost the signal level
	​local = PitchShift.ar(local, pitchRatio: 1.midiratio, mul: 1.5);
	
	// often useful to add LeakDC when using feedback
	​local = LeakDC.ar(local);
	
    LocalOut.ar(local);

	fbBoost = 1.5; // boost the feedback level in the output
	out = source + (fbBoost * local);
​
    local ! 2;
}.play;
)

You could also add a Limiter into the feedback loop for safety.
Hope that helps.
Paul

2 Likes

I don’t understand the concept of LocalIn/Out (also read the documentation). Does a LocalIn automatically read what a synth has produced in one cycle, and sends it back (through LocalOut) to the LocalIn again, thus creating a feed back loop?
Also why should always be something after a LocalOut? These are return values of the function, where do these go?

Also what is your out parameter doing? It is going nowhere! :thinking:

Oh, my mistake! The out should have been used on the last line. The code should have read:

(
{
    var source, local, feedback, out, fbBoost;
​
	source = Decay.ar(Impulse.ar(1), 0.2) * WhiteNoise.ar(0.2);
	// source = Decay.ar(Impulse.ar(1), 0.2) * SinOsc.ar(440, 0.0, 0.2);

	// read feedback, add to source
	feedback = LocalIn.ar(2);
	local = feedback + source;
    local = DelayC.ar(local, 1, 0.1);
	// pitch shift and boost the signal level
	​local = PitchShift.ar(local, pitchRatio: 1.midiratio, mul: 1.5);

	// often useful to add LeakDC when using feedback
	​local = LeakDC.ar(local);

    LocalOut.ar(local);

	fbBoost = 1.5; // boost the feedback level in the output
	out = source + (fbBoost * local);

    out ! 2;  // mono to stereo
}.play;
)

Yes that’s right. LocalOut uses an internal bus to write a signal to, and LocalIn read’s that signal in the next cycle. To begin with LocalIn will be set to it’s default value (0 by default) and then every cycle it is overwritten with LocalOut’s value.

LocalOut is writing the delayed and pitch-shifted signal “local” to the internal bus, but it not outputting a signal from the synth.
The code after that is mixing “local” with “source”, the original unaffected signal.
The last line of the function: out ! 2; gets returned, which is then what you hear from the synth.

I hope that makes sense.
Best,
Paul

2 Likes

@amt side note: there are lots of ways to achieve feedback in a SynthDef. This is one of them. Using Ndefs is very popular. InFeedback is another.

I don’t think you need DelayC here though since the delay time isn’t being modulated. At least that’s my understanding of how that works, but please correct me if I’m wrong here!

1 Like

I am wondering, as the Localout and localin ugens write and read to a local bus, is not the use of a delay inside the synth kind of needless? I mean, if there would be something like a Wait Ugen which one could put inside a synth to postpone it’s happening (sounding), then local in/out would suffice, it would be a recursive Synth which could last until a condition becomes false. Is it possible to run synths recursively?

It works without the delay, although there will always be a short delay of 1 cycle (i.e. 1 block of samples) - this is expained in LocalOut help LocalOut | SuperCollider 3.12.2 Help
Here’s the code with the delay removed and a Limiter added for safety. It sounds different - smoother, less rhythmic:

(
{
    var source, local, feedback, out, fbBoost;
​
	source = Decay.ar(Impulse.ar(1), 0.2) * WhiteNoise.ar(0.2);
	// source = Decay.ar(Impulse.ar(1), 0.2) * SinOsc.ar(440, 0.0, 0.2);

	// read feedback, add to source
	feedback = LocalIn.ar(1);
	local = feedback + source;

	// Delay removed
	// local = DelayC.ar(local, 1, 0.1);

	// pitch shift and boost the signal level
	​local = PitchShift.ar(local, pitchRatio: 1.midiratio, mul: 1.5);

	// add a limiter for safety
	​local = Limiter.ar(local,0.8);

	// often useful to add LeakDC when using feedback
	​local = LeakDC.ar(local);

    LocalOut.ar(local);

	fbBoost = 1.5; // boost the feedback level in the output
	out = source + (fbBoost * local);

    out ! 2;
}.play;
)

In my understanding, using feedback is recursive - every cycle you are pitchshifting, again and again.
Best,
Paul

1 Like

Thanks for the new code.
A feedback (e.g. recursive) structure runs for a certain number of times (at least my mathematical and programming understanding of recursion tells me this). When we put the output of a LocalOut in a local bus, then read the content of that bus with LocalIn and feed it back to LocalOut, now how many times does this happen (in your code for instance). I ask this, because I don’t see any point in the code where this process is instructed to be broken.
This can’t be an infinite loop, isn’t it?

Or maybe my intuition is wrong here. The signal in the LocalIn/Out chain is becoming weaker on each iteration (in your code this happens by Decay if I understand it correctly). So the signal becomes <= 0 at some time. But is the recursion chain broken at that point too, when the signal is almost vanished?

Recursion means only that a process is repeatedly invoked. In the classical computer science sense, the repetition occurs by calling into a function from within the same function (distinguishing it from a while or for loop). The stop condition isn’t a formal requirement, although in practice, without a stop condition, bad things happen (hanging, or in signal processing, accumulation of energy overloading the +/-1 boundary).

Signal processing logic is different from imperative code logic. I don’t think it’s meaningful to try to understand signal feedback recursion in terms of classical computer science recursive functions.

Reducing the amplitude of the feedback chain does mean that the feedback will eventually be quiet enough that it disappears. In theory the numbers will get infinitely small; in practice, when they get too small, they will be flushed to zero (to avoid the poor performance of floating-point denormal numbers).

FWIW Miller Puckette’s Theory and Practice book uses the term “recursive filter” to describe what other authors call an infinite impulse response filter (because of the presence of feedback terms).

hjh

1 Like