Glitch/repater synth - porting from PureData

Hi guys,

a few days ago I stumbled upon a nice playlist of PureData tutorials by SoundSimulator and, in particular, I was impressed by the video regarding the glitch and wanted to bring the PureData code that is shown in the video, into SuperCollider.

The part of the PD code that I tried to translate is as follows:

and, at the moment, my current translation in SC language is following:

(
SynthDef(\bplay, {
	|out=0, amp=1.0, buf, fb=1.0|
	var glitch = MouseButton.kr(0, 1);
	var localBuf = LocalBuf.new( SampleRate.ir(), 1);
	var original = PlayBuf.ar(1, buf, BufRateScale.kr(buf), 1, loop:1.0) * (1-glitch);

	var tapPhase = LocalIn.ar(1);
	var delay = DelTapRd.ar(localBuf, tapPhase, MouseY.kr(0.01, 0.7), 2, mul:fb) * glitch;

	var sig = original + delay;
	LocalOut.ar( DelTapWr.ar(localBuf, sig) );

	sig = sig * amp;
	Out.ar(out, sig!2);
}).add;
)

In my synth it is the mouse button that activates or deactivates the “repeater” effect while the drag allows you to shorten or lengthen the “repeated” portion of the audio.

You can try yourself following the steps below:

// 1. boot the server
s.boot;

// 2. load your audiofile
~sample = Buffer.readChannel(s, "/path/to/my/sound/file.wav", channels:0);

// 3. instantiate the synth
x = Synth(\bplay, [\buf, ~sample]);

// 4. play with the mouse :)

// 5. eventually free the synth
x.free;

While the general sound behavior of the synthesizer in SC seems similar to that in Puredata, I noticed one important difference: when I change the delay duration - dragging with the mouse - the sound undergoes drifts in pitch. The pitch increases when the delay time is shortened and decreases instead when you allug the delay time. Just as if it were a tape delay.

While I have researched this effect in some cases in the past, in this particular context I would like it not to be there instead.

How do I modify my implementation to achieve the desired effect?
Thank you so much for your support

2 Likes

Would Warp1 work here?

One thing is that the Pd patch isn’t ramping the delay time – it’s just instantaneously jumping. (delread~ can’t smooth out delay time changes because it’s a control input, not a signal input. delread4~ can.)

But MouseY has a built in lag.

So at minimum, you might try disabling the lag by setting it to 0, and see if the sound is closer to the original.

hjh

2 Likes

Thank you guys for your support,
@jamshark70 thank you for pointing out the delread~ object behaviour in PureData.
I was eventually able to get quite near to the same acoustic effect setting the lag to 0.0 for the MouseY object.
n

Just realized that this post can be related to one of my previous posts.
Placing the link here just for an additional reference.

Hi, I’m coming back to this topic almost a year later to ask your opinion on this beatRepeater/glitcher I’m trying to implement.

Following @jamshark70 suggestion I had actually managed to make a synth that didn’t incur the unwanted pitch-shift when the delay time changed (by setting the lag value to 0 for MouseY).

(
SynthDef(\glitcher, {
	|out=0, amp=1.0, buf, fb=1.0|
	var glitch = MouseButton.kr(0, 1);
	var localBuf = LocalBuf.new( SampleRate.ir(), 1);
	var original = PlayBuf.ar(1, buf, BufRateScale.kr(buf), 1, loop:1.0) * (1-glitch);

	var tapPhase = LocalIn.ar(1);
	var delay = DelTapRd.ar(localBuf, tapPhase, MouseY.kr(0.01, 0.7, lag:0.0), 2, mul:fb) * glitch;

	var sig = original + delay;
	LocalOut.ar( DelTapWr.ar(localBuf, sig) );

	sig = sig * amp;
	Out.ar(out, sig!2);
}).add;
)

However, as I continue to test the synth, it is evident how essentially different the sound quality of the glitch it produces is compared to the sound that can be obtained from the PureData patch.

In order to test this difference, I prepared a setup to play and record the two implementations of the ‘glitcher’ simultaneously.

I connected an audio stream to the input of both Synths and used a MIDI controller to send the two implementations:

  • the signal to activate/deactivate the ‘glitching’ effect;
  • a value to vary the delay time;

The two implementations are shown below, the PureData on (please find the patch also attached)
PD_implementation_w_MIDI_and_SoundInput.pd (3.5 KB)

And the SuperCollider one:

s.boot;
(
SynthDef(\glitcher, {
	|out=1, amp=0.75, dly=0.25, fb=1.0, glitch=0|
	var localBuf = LocalBuf.new( SampleRate.ir(), 1); // 1ch, 1 second of buffer
	var original = In.ar(2,1) * (1-glitch);

	var tapPhase = LocalIn.ar(1);
	var delay = DelTapRd.ar(localBuf, tapPhase, dly, mul:fb) * glitch;

	var sig = original + delay;
	LocalOut.ar( DelTapWr.ar(localBuf, sig) );

	sig = sig * amp;
	Out.ar(out, sig);
}).add;
)

x = Synth(\glitcher);


// MIDI Controller to drive the synth
// (the same controls will be used to control thje PureData implementation)
MIDIClient.init;
MIDIIn.connectAll;

~glitch_status = 0;
~glitch_dly = 0.25;

(
MIDIdef.noteOn(\activate_effect, {
	arg ...args;
	if( args[1] == 9, {
		~glitch_status = (~glitch_status + 1) % 2;
		("glitch activate: " + ~glitch_status).postln;
		// Emit a pulse to be recorded in order to visually
		// see where the effect was activated/deactivated
		{Out.ar(0, EnvGen.ar(Env.perc(0.0, 1), doneAction:2) * Impulse.ar(0)!2)}.play;
		x.set(\glitch, ~glitch_status);
	});
});

MIDIdef.cc(\change_delay_time, {
	arg ...args;
	if(args[1] == 21, {
		var value = args[0] / 127;
		value = (value * 490) + 10;
		value = value / 1000.0; // expressed in seconds
		~glitch_dly = value;
		("glitch delay time: " + ~glitch_dly).postln;
		x.set(\dly, ~glitch_dly);
	});
});
)

I immediately started sending a sine wave to the two synths and recording the output within Reaper. You can see, and hear very well, the auditory differences between the two audio outputs.

In the images below, obtained after recording the outputs, you can see how the signal output from SuperCollider (bottom track) is essentially different from that obtained from PureData (top track).

The audio result can also be heard in this audio file:

  1. in the left channel the output of PureData;
  2. in the right channel the output from SuperCollider;

It is evident that the artefacts/glitches in PureData’s output are ‘cleaner’ while SuperCollider’s output is muddier, greasier, more imprecise and overloaded with harmonics.

Why this difference?

I suppose it could be some difference in the implementation I have done in SuperCollider.

Perhaps something to do with order-of-execution of the various elements in the audio chain?
Or maybe something to do with not taking into account the inherent block-size delay between writing and reading the local buffer by DelTapWr and DelTapRd?

Thank you very much for your support.

TL;DR Make sure that the delay time input to DelTapRd is audio rate, not control rate.

I had a feeling yesterday this was probably the case, but wasn’t sure enough to post speculation that might not have panned out. But now I can prove it :wink:

SC plugins’ convention when using a control rate input to an audio rate UGen is to perform a linear ramp from the old value to the new value within each control block.

Following @jamshark70’s suggestion I had actually managed to make a synth that didn’t incur the unwanted pitch-shift when the delay time changed (by setting the lag value to 0 for MouseY).

From this, I take it that your intention is to avoid any sort of ramp in the delay time. But, DelTapRd’s internal up-sampling of the control-rate delay time adds its own ramp.

s.boot;

b = Buffer.alloc(s, s.sampleRate * 0.02, 1);

(
{
	var sig = SinOsc.ar(440);
	var phase = DelTapWr.ar(b, sig);
	var tfreq = SampleRate.ir * 0.01;
	var trig = Impulse.kr(tfreq);

	// kr delay time, analogous to your current synth
	var deltimeK = TRand.kr(0, b.duration - 0.001, trig);

	// a trick to upsample K-->A without any ramp
	// you can't use K2A for this because it ramps
	var deltimeA = Demand.ar(T2A.ar(trig), 0, deltimeK);

	var delayK = DelTapRd.ar(b, phase, deltimeK, 4);
	var delayA = DelTapRd.ar(b, phase, deltimeA, 4);
	[sig, deltimeA * 40, delayK, delayA]
}.plot(duration: 0.05);
)

… and delayK looks like your SC result, while delayA looks like your Pd result.

(DelTapRd source code: The k-rate delaytime functions have delTime += delTimeInc – that’s the linear interpolation up to ar.)

hjh

1 Like

Thank you so much @jamshark70 ,
I will absolutely give it a try as soon as I can.
I’ll let you know
n

Thank you @jamshark70 ,
I was eventually able to integrate the code inside the synthDef and now it looks something like this

(
SynthDef(\glitcher, {
	|out=0, amp=0.5, deltime=0.25, fb=1.0, glitch=0|
	var localBuf = LocalBuf.new( SampleRate.ir()*1.0, 1);

	var tfreq = SampleRate.ir * 0.01;
	var trig = Impulse.kr( tfreq );

	var original = In.ar(2,1);

	var phase = LocalIn.ar(1);
	var deltimeA = Demand.ar(T2A.ar(trig), 0, deltime);
	var delay = DelTapRd.ar(localBuf, phase, deltimeA, mul:fb);

	var sig = (original * (1-glitch)) + (delay * glitch);
	LocalOut.ar( DelTapWr.ar(localBuf, sig) );

	sig = sig * amp;
	Out.ar(out, sig!2);
}).add;
)

which in comparison to the PD patch, at a first try, it seems to me to sound and look quite similar

A thing is not perfectly clear to me in your implementation:
why you have user a fequency of SampleRate.ir * 0.01 for the trigger to be used T2A.ar ?

Maybe that PD’s [delread~] object samples the input (without ramps) at a frequency corresponding to one hundredth of the SR?

In my head, without consulting the source, this sampling should take place at each block, so I tried using something like this

var trig = Impulse.kr( ControlRate.ir() );

but this ended up not working and, if possible, complimenting my understanding a little more :slight_smile:
Thank you

That was only to produce a sensible plot, no other reason… meaning, you’re not required to use that exact frequency.

First, a trigger is defined as a value > 0 coming immediately after a value <= 0. So the fastest possible triggering is 1, 0, 1, 0 – half of the sample rate or control rate.

Second, the pd patch provides a new delay time only when a message goes through the send/receive pair, which isn’t the same as sampling every control block in SC. You’re looking for a delay time signal that looks like a sample/hold.

hjh

1 Like

I think the changed UGen can do the job in a more proper way:

(
SynthDef(\glitcher, {
	|out=0, amp=0.5, deltime=0.25, fb=1.0, glitch=0|
	var localBuf = LocalBuf.new( SampleRate.ir()*1.0, 1); 

	var trig = Changed.kr( deltime );
	var original = In.ar(2,1); // In.ar(2,1)

	var phase = LocalIn.ar(1);
	var deltimeA = Demand.ar(T2A.ar(trig), 0, deltime);
	var delay = DelTapRd.ar(localBuf, phase, deltimeA, mul:fb);

    // in the mean time I added also a little cross-fade (to better simulate the PD patch)
	var sig = SelectX.ar(glitch.lag(0.125), [original, delay]);
	LocalOut.ar( DelTapWr.ar(localBuf, sig) );

	sig = sig * amp;
	Out.ar(out, sig!2);
}).add;
)