TimeStretch icon indicating copy to clipboard operation
TimeStretch copied to clipboard

Real time usage

Open fmiramar opened this issue 3 years ago • 4 comments

Hi!

Is the TimeStrech2 supposed to work in this way? or is it only allowed to worked on SC's NRT mode ? (NessWindow.ar substituted for debbuging)


b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav"); // remember to free the buffer later.

//NessStretch (improved paulstretch version)
(
SynthDef(\pb_monoStretch2_Overlap2, { |out = 0, bufnum, stretch = 100, startPos = 0, fftSize = 8192, fftMax = 65536, hiPass = 0, lowPass=0, amp = 1, gate = 1|
	var trigPeriod, sig, chain, trig, trig1, trig2, pos, jump, trigEnv, fftDelay, bigEnv, window0, window1, rVal, correlation, sum, localIn, rVal1, rVal2, outSig, analSig, fs0, fs1;

	trigPeriod = (fftSize/SampleRate.ir);
	trig = Impulse.ar(2/trigPeriod);

	trig1 = PulseDivider.ar(trig, 2, 1);
	trig2 = PulseDivider.ar(trig, 2, 0);

	startPos = (startPos%1);
	pos = Line.ar(startPos*BufFrames.kr(bufnum), BufFrames.kr(bufnum), BufDur.kr(bufnum)*stretch*(1-startPos));

	jump = fftSize/stretch/2;
	pos = [pos, pos + jump];

	sig = PlayBuf.ar(1, bufnum, 1, trig1, pos, 1)*SinOsc.ar(1/(2*trigPeriod)).abs*0.5;

	sig = sig.collect({ |item, i|
		chain = FFT(LocalBuf(fftSize), item, hop: 1.0, wintype: 0);
		chain = PV_Diffuser(chain, 1-trig1);
		chain = PV_BrickWall(chain, hiPass);
		chain = PV_BrickWall(chain, lowPass-1);
		item = IFFT(chain, wintype: -1);
	}).flatten;

	//delay the signal so that all fftSizes line up (the will already be delayed by the fftSize
	sig = DelayC.ar(sig, (3*fftMax/2)-(3*fftSize/2)+BlockSize.ir/SampleRate.ir, fftMax-fftSize+BlockSize.ir/SampleRate.ir);

	sig[1] = DelayC.ar(sig[1], trigPeriod/2, trigPeriod/2);

	sum = RunningSum.ar((sig[0]*sig[1]), fftSize/2)/RunningSum.ar((sig[0]*sig[0]), fftSize/2);

	rVal = Latch.ar(sum, DelayC.ar(trig1+trig2, (3*fftMax/2)-(3*fftSize/2)+BlockSize.ir/SampleRate.ir, fftMax-fftSize+BlockSize.ir/SampleRate.ir)).clip(-1,1);

	rVal = DelayC.ar(rVal, trigPeriod/2, trigPeriod/2);

	localIn = LocalIn.ar(1).clip(-1,1);
	localIn = DelayC.ar(localIn, trigPeriod/2-(BlockSize.ir/SampleRate.ir), trigPeriod/2-(BlockSize.ir/SampleRate.ir));

	rVal1 = (Latch.ar(rVal, trig1)>=0).linlin(0,1,-1,1)*
	Latch.ar(XFade2.ar(K2A.ar(1), localIn, EnvGen.kr(Env([-1,-1, 1], [trigPeriod, 0]), 1)), trig1);

	rVal2 = (Latch.ar(rVal, trig2)>=0).linlin(0,1,-1,1)*
	Latch.ar(XFade2.ar(K2A.ar(1), DelayC.ar(rVal1.clip(-1,1), trigPeriod/2, trigPeriod/2), EnvGen.kr(Env([-1,-1, 1], [trigPeriod, 0]), 1)), trig2);

	LocalOut.ar(rVal2);

	/*	window0 = NessWindow.ar(trig1, rVal.abs, fftSize)*rVal1;
	window1 = NessWindow.ar(trig2, rVal.abs, fftSize)*rVal2;*/

	fs0 = (1-(Slew.ar(
		1-Trig1.ar(trig1, fftSize/2/SampleRate.ir),
		SampleRate.ir/(fftSize/2),
		SampleRate.ir/(fftSize/2))));
	fs0 = (fs0*pi/2).tan**2;
	//fs0 = fs0*((1/(1+(2*fs0*(rVal.abs))+(fs0**2))).sqrt);

	fs1 = (1-(Slew.ar(
		1-Trig1.ar(trig2, fftSize/2/SampleRate.ir),
		SampleRate.ir/(fftSize/2),
		SampleRate.ir/(fftSize/2))));
	fs1 = (fs1*pi/2).tan**2;
	//fs1 = fs1*((1/(1+(2*fs1*(rVal.abs))+(fs1**2))).sqrt);

	//window0 = fs0*rVal1;
	//window1 = fs1*rVal2;

	window0 = fs0*((1/(1+(2*fs0*(rVal.abs))+(fs0**2))).sqrt) * rVal1;
	window1 = fs1*((1/(1+(2*fs1*(rVal.abs))+(fs1**2))).sqrt) * rVal2;

	sig = DelayC.ar(sig, fftMax/SampleRate.ir, fftMax/SampleRate.ir);

	outSig = [sig[0]*window0, sig[1]*window1];

	bigEnv = EnvGen.kr(Env.asr(0,1,0), gate, doneAction:2);

	hiPass = hiPass*SampleRate.ir/2;
	lowPass = lowPass*SampleRate.ir/2;

	outSig = HPF.ar(HPF.ar(outSig, (hiPass).clip(20, SampleRate.ir/2)), (hiPass).clip(20, SampleRate.ir/2));
	outSig = LPF.ar(LPF.ar(outSig, (lowPass).clip(20, SampleRate.ir/2)), (lowPass).clip(20, SampleRate.ir/2));


	Out.ar(out, Mix.new(outSig)*bigEnv*amp);

	//Out.ar(out, [sig[0], sig[1], rVal1, window0, window1])
}).add;
)

Synth(\pb_monoStretch2_Overlap2, [\bufnum, b, \stretch, 2])

fmiramar avatar Sep 10 '21 19:09 fmiramar

Hi!

Is the TimeStrech2 supposed to work in this way? or is it only allowed to worked on SC's NRT mode ? (NessWindow.ar substituted for debbuging)


b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav"); // remember to free the buffer later.

//NessStretch (improved paulstretch version)
(
SynthDef(\pb_monoStretch2_Overlap2, { |out = 0, bufnum, stretch = 100, startPos = 0, fftSize = 8192, fftMax = 65536, hiPass = 0, lowPass=0, amp = 1, gate = 1|
	var trigPeriod, sig, chain, trig, trig1, trig2, pos, jump, trigEnv, fftDelay, bigEnv, window0, window1, rVal, correlation, sum, localIn, rVal1, rVal2, outSig, analSig, fs0, fs1;

	trigPeriod = (fftSize/SampleRate.ir);
	trig = Impulse.ar(2/trigPeriod);

	trig1 = PulseDivider.ar(trig, 2, 1);
	trig2 = PulseDivider.ar(trig, 2, 0);

	startPos = (startPos%1);
	pos = Line.ar(startPos*BufFrames.kr(bufnum), BufFrames.kr(bufnum), BufDur.kr(bufnum)*stretch*(1-startPos));

	jump = fftSize/stretch/2;
	pos = [pos, pos + jump];

	sig = PlayBuf.ar(1, bufnum, 1, trig1, pos, 1)*SinOsc.ar(1/(2*trigPeriod)).abs*0.5;

	sig = sig.collect({ |item, i|
		chain = FFT(LocalBuf(fftSize), item, hop: 1.0, wintype: 0);
		chain = PV_Diffuser(chain, 1-trig1);
		chain = PV_BrickWall(chain, hiPass);
		chain = PV_BrickWall(chain, lowPass-1);
		item = IFFT(chain, wintype: -1);
	}).flatten;

	//delay the signal so that all fftSizes line up (the will already be delayed by the fftSize
	sig = DelayC.ar(sig, (3*fftMax/2)-(3*fftSize/2)+BlockSize.ir/SampleRate.ir, fftMax-fftSize+BlockSize.ir/SampleRate.ir);

	sig[1] = DelayC.ar(sig[1], trigPeriod/2, trigPeriod/2);

	sum = RunningSum.ar((sig[0]*sig[1]), fftSize/2)/RunningSum.ar((sig[0]*sig[0]), fftSize/2);

	rVal = Latch.ar(sum, DelayC.ar(trig1+trig2, (3*fftMax/2)-(3*fftSize/2)+BlockSize.ir/SampleRate.ir, fftMax-fftSize+BlockSize.ir/SampleRate.ir)).clip(-1,1);

	rVal = DelayC.ar(rVal, trigPeriod/2, trigPeriod/2);

	localIn = LocalIn.ar(1).clip(-1,1);
	localIn = DelayC.ar(localIn, trigPeriod/2-(BlockSize.ir/SampleRate.ir), trigPeriod/2-(BlockSize.ir/SampleRate.ir));

	rVal1 = (Latch.ar(rVal, trig1)>=0).linlin(0,1,-1,1)*
	Latch.ar(XFade2.ar(K2A.ar(1), localIn, EnvGen.kr(Env([-1,-1, 1], [trigPeriod, 0]), 1)), trig1);

	rVal2 = (Latch.ar(rVal, trig2)>=0).linlin(0,1,-1,1)*
	Latch.ar(XFade2.ar(K2A.ar(1), DelayC.ar(rVal1.clip(-1,1), trigPeriod/2, trigPeriod/2), EnvGen.kr(Env([-1,-1, 1], [trigPeriod, 0]), 1)), trig2);

	LocalOut.ar(rVal2);

	/*	window0 = NessWindow.ar(trig1, rVal.abs, fftSize)*rVal1;
	window1 = NessWindow.ar(trig2, rVal.abs, fftSize)*rVal2;*/

	fs0 = (1-(Slew.ar(
		1-Trig1.ar(trig1, fftSize/2/SampleRate.ir),
		SampleRate.ir/(fftSize/2),
		SampleRate.ir/(fftSize/2))));
	fs0 = (fs0*pi/2).tan**2;
	//fs0 = fs0*((1/(1+(2*fs0*(rVal.abs))+(fs0**2))).sqrt);

	fs1 = (1-(Slew.ar(
		1-Trig1.ar(trig2, fftSize/2/SampleRate.ir),
		SampleRate.ir/(fftSize/2),
		SampleRate.ir/(fftSize/2))));
	fs1 = (fs1*pi/2).tan**2;
	//fs1 = fs1*((1/(1+(2*fs1*(rVal.abs))+(fs1**2))).sqrt);

	//window0 = fs0*rVal1;
	//window1 = fs1*rVal2;

	window0 = fs0*((1/(1+(2*fs0*(rVal.abs))+(fs0**2))).sqrt) * rVal1;
	window1 = fs1*((1/(1+(2*fs1*(rVal.abs))+(fs1**2))).sqrt) * rVal2;

	sig = DelayC.ar(sig, fftMax/SampleRate.ir, fftMax/SampleRate.ir);

	outSig = [sig[0]*window0, sig[1]*window1];

	bigEnv = EnvGen.kr(Env.asr(0,1,0), gate, doneAction:2);

	hiPass = hiPass*SampleRate.ir/2;
	lowPass = lowPass*SampleRate.ir/2;

	outSig = HPF.ar(HPF.ar(outSig, (hiPass).clip(20, SampleRate.ir/2)), (hiPass).clip(20, SampleRate.ir/2));
	outSig = LPF.ar(LPF.ar(outSig, (lowPass).clip(20, SampleRate.ir/2)), (lowPass).clip(20, SampleRate.ir/2));


	Out.ar(out, Mix.new(outSig)*bigEnv*amp);

	//Out.ar(out, [sig[0], sig[1], rVal1, window0, window1])
}).add;
)

Synth(\pb_monoStretch2_Overlap2, [\bufnum, b, \stretch, 2])

I'm looking into making a realtime version of it.

madskjeldgaard avatar Jan 31 '22 15:01 madskjeldgaard

Hi!

Is the TimeStrech2 supposed to work in this way? or is it only allowed to worked on SC's NRT mode ? (NessWindow.ar substituted for debbuging)


b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav"); // remember to free the buffer later.

//NessStretch (improved paulstretch version)
(
SynthDef(\pb_monoStretch2_Overlap2, { |out = 0, bufnum, stretch = 100, startPos = 0, fftSize = 8192, fftMax = 65536, hiPass = 0, lowPass=0, amp = 1, gate = 1|
	var trigPeriod, sig, chain, trig, trig1, trig2, pos, jump, trigEnv, fftDelay, bigEnv, window0, window1, rVal, correlation, sum, localIn, rVal1, rVal2, outSig, analSig, fs0, fs1;

	trigPeriod = (fftSize/SampleRate.ir);
	trig = Impulse.ar(2/trigPeriod);

	trig1 = PulseDivider.ar(trig, 2, 1);
	trig2 = PulseDivider.ar(trig, 2, 0);

	startPos = (startPos%1);
	pos = Line.ar(startPos*BufFrames.kr(bufnum), BufFrames.kr(bufnum), BufDur.kr(bufnum)*stretch*(1-startPos));

	jump = fftSize/stretch/2;
	pos = [pos, pos + jump];

	sig = PlayBuf.ar(1, bufnum, 1, trig1, pos, 1)*SinOsc.ar(1/(2*trigPeriod)).abs*0.5;

	sig = sig.collect({ |item, i|
		chain = FFT(LocalBuf(fftSize), item, hop: 1.0, wintype: 0);
		chain = PV_Diffuser(chain, 1-trig1);
		chain = PV_BrickWall(chain, hiPass);
		chain = PV_BrickWall(chain, lowPass-1);
		item = IFFT(chain, wintype: -1);
	}).flatten;

	//delay the signal so that all fftSizes line up (the will already be delayed by the fftSize
	sig = DelayC.ar(sig, (3*fftMax/2)-(3*fftSize/2)+BlockSize.ir/SampleRate.ir, fftMax-fftSize+BlockSize.ir/SampleRate.ir);

	sig[1] = DelayC.ar(sig[1], trigPeriod/2, trigPeriod/2);

	sum = RunningSum.ar((sig[0]*sig[1]), fftSize/2)/RunningSum.ar((sig[0]*sig[0]), fftSize/2);

	rVal = Latch.ar(sum, DelayC.ar(trig1+trig2, (3*fftMax/2)-(3*fftSize/2)+BlockSize.ir/SampleRate.ir, fftMax-fftSize+BlockSize.ir/SampleRate.ir)).clip(-1,1);

	rVal = DelayC.ar(rVal, trigPeriod/2, trigPeriod/2);

	localIn = LocalIn.ar(1).clip(-1,1);
	localIn = DelayC.ar(localIn, trigPeriod/2-(BlockSize.ir/SampleRate.ir), trigPeriod/2-(BlockSize.ir/SampleRate.ir));

	rVal1 = (Latch.ar(rVal, trig1)>=0).linlin(0,1,-1,1)*
	Latch.ar(XFade2.ar(K2A.ar(1), localIn, EnvGen.kr(Env([-1,-1, 1], [trigPeriod, 0]), 1)), trig1);

	rVal2 = (Latch.ar(rVal, trig2)>=0).linlin(0,1,-1,1)*
	Latch.ar(XFade2.ar(K2A.ar(1), DelayC.ar(rVal1.clip(-1,1), trigPeriod/2, trigPeriod/2), EnvGen.kr(Env([-1,-1, 1], [trigPeriod, 0]), 1)), trig2);

	LocalOut.ar(rVal2);

	/*	window0 = NessWindow.ar(trig1, rVal.abs, fftSize)*rVal1;
	window1 = NessWindow.ar(trig2, rVal.abs, fftSize)*rVal2;*/

	fs0 = (1-(Slew.ar(
		1-Trig1.ar(trig1, fftSize/2/SampleRate.ir),
		SampleRate.ir/(fftSize/2),
		SampleRate.ir/(fftSize/2))));
	fs0 = (fs0*pi/2).tan**2;
	//fs0 = fs0*((1/(1+(2*fs0*(rVal.abs))+(fs0**2))).sqrt);

	fs1 = (1-(Slew.ar(
		1-Trig1.ar(trig2, fftSize/2/SampleRate.ir),
		SampleRate.ir/(fftSize/2),
		SampleRate.ir/(fftSize/2))));
	fs1 = (fs1*pi/2).tan**2;
	//fs1 = fs1*((1/(1+(2*fs1*(rVal.abs))+(fs1**2))).sqrt);

	//window0 = fs0*rVal1;
	//window1 = fs1*rVal2;

	window0 = fs0*((1/(1+(2*fs0*(rVal.abs))+(fs0**2))).sqrt) * rVal1;
	window1 = fs1*((1/(1+(2*fs1*(rVal.abs))+(fs1**2))).sqrt) * rVal2;

	sig = DelayC.ar(sig, fftMax/SampleRate.ir, fftMax/SampleRate.ir);

	outSig = [sig[0]*window0, sig[1]*window1];

	bigEnv = EnvGen.kr(Env.asr(0,1,0), gate, doneAction:2);

	hiPass = hiPass*SampleRate.ir/2;
	lowPass = lowPass*SampleRate.ir/2;

	outSig = HPF.ar(HPF.ar(outSig, (hiPass).clip(20, SampleRate.ir/2)), (hiPass).clip(20, SampleRate.ir/2));
	outSig = LPF.ar(LPF.ar(outSig, (lowPass).clip(20, SampleRate.ir/2)), (lowPass).clip(20, SampleRate.ir/2));


	Out.ar(out, Mix.new(outSig)*bigEnv*amp);

	//Out.ar(out, [sig[0], sig[1], rVal1, window0, window1])
}).add;
)

Synth(\pb_monoStretch2_Overlap2, [\bufnum, b, \stretch, 2])

The way this is designed, you need to set a value for lowpass and highpass. Each of these Synth instances are a slice of the spectrum as far as I understand. And by default it's fraom 0 to 0 hz in the above.

madskjeldgaard avatar Jan 31 '22 15:01 madskjeldgaard

It will work in real-time. It probably isn’t making sound because the hiPass and lowpass are both 0. If you set lowpass to 1 it works. See the calculations in TimeStretch2.sc file to get the values for each band of the NessStretch algorithm.

I fear there is a bug in this code somewhere having to do with the windowing…hopefully not.

Sam

From: mads kjeldgaard @.> Date: Monday, January 31, 2022 at 10:22 AM To: spluta/TimeStretch @.> Cc: Subscribed @.***> Subject: Re: [spluta/TimeStretch] Real time usage (#15)

Hi!

Is the TimeStrech2 supposed to work in this way? or is it only allowed to worked on SC's NRT mode ? (NessWindow.ar substituted for debbuging)

b = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav"); // remember to free the buffer later.

//NessStretch (improved paulstretch version)

(

SynthDef(\pb_monoStretch2_Overlap2, { |out = 0, bufnum, stretch = 100, startPos = 0, fftSize = 8192, fftMax = 65536, hiPass = 0, lowPass=0, amp = 1, gate = 1|

var trigPeriod, sig, chain, trig, trig1, trig2, pos, jump, trigEnv, fftDelay, bigEnv, window0, window1, rVal, correlation, sum, localIn, rVal1, rVal2, outSig, analSig, fs0, fs1;

trigPeriod = (fftSize/SampleRate.ir);

trig = Impulse.ar(2/trigPeriod);

trig1 = PulseDivider.ar(trig, 2, 1);

trig2 = PulseDivider.ar(trig, 2, 0);

startPos = (startPos%1);

pos = Line.ar(startPos*BufFrames.kr(bufnum), BufFrames.kr(bufnum), BufDur.kr(bufnum)stretch(1-startPos));

jump = fftSize/stretch/2;

pos = [pos, pos + jump];

sig = PlayBuf.ar(1, bufnum, 1, trig1, pos, 1)SinOsc.ar(1/(2trigPeriod)).abs*0.5;

sig = sig.collect({ |item, i|

     chain = FFT(LocalBuf(fftSize), item, hop: 1.0, wintype: 0);

     chain = PV_Diffuser(chain, 1-trig1);

     chain = PV_BrickWall(chain, hiPass);

     chain = PV_BrickWall(chain, lowPass-1);

     item = IFFT(chain, wintype: -1);

}).flatten;

//delay the signal so that all fftSizes line up (the will already be delayed by the fftSize

sig = DelayC.ar(sig, (3fftMax/2)-(3fftSize/2)+BlockSize.ir/SampleRate.ir, fftMax-fftSize+BlockSize.ir/SampleRate.ir);

sig[1] = DelayC.ar(sig[1], trigPeriod/2, trigPeriod/2);

sum = RunningSum.ar((sig[0]*sig[1]), fftSize/2)/RunningSum.ar((sig[0]*sig[0]), fftSize/2);

rVal = Latch.ar(sum, DelayC.ar(trig1+trig2, (3fftMax/2)-(3fftSize/2)+BlockSize.ir/SampleRate.ir, fftMax-fftSize+BlockSize.ir/SampleRate.ir)).clip(-1,1);

rVal = DelayC.ar(rVal, trigPeriod/2, trigPeriod/2);

localIn = LocalIn.ar(1).clip(-1,1);

localIn = DelayC.ar(localIn, trigPeriod/2-(BlockSize.ir/SampleRate.ir), trigPeriod/2-(BlockSize.ir/SampleRate.ir));

rVal1 = (Latch.ar(rVal, trig1)>=0).linlin(0,1,-1,1)*

Latch.ar(XFade2.ar(K2A.ar(1), localIn, EnvGen.kr(Env([-1,-1, 1], [trigPeriod, 0]), 1)), trig1);

rVal2 = (Latch.ar(rVal, trig2)>=0).linlin(0,1,-1,1)*

Latch.ar(XFade2.ar(K2A.ar(1), DelayC.ar(rVal1.clip(-1,1), trigPeriod/2, trigPeriod/2), EnvGen.kr(Env([-1,-1, 1], [trigPeriod, 0]), 1)), trig2);

LocalOut.ar(rVal2);

/* window0 = NessWindow.ar(trig1, rVal.abs, fftSize)*rVal1;

window1 = NessWindow.ar(trig2, rVal.abs, fftSize)rVal2;/

fs0 = (1-(Slew.ar(

     1-Trig1.ar(trig1, fftSize/2/SampleRate.ir),

     SampleRate.ir/(fftSize/2),

     SampleRate.ir/(fftSize/2))));

fs0 = (fs0*pi/2).tan**2;

//fs0 = fs0*((1/(1+(2fs0(rVal.abs))+(fs0**2))).sqrt);

fs1 = (1-(Slew.ar(

     1-Trig1.ar(trig2, fftSize/2/SampleRate.ir),

     SampleRate.ir/(fftSize/2),

     SampleRate.ir/(fftSize/2))));

fs1 = (fs1*pi/2).tan**2;

//fs1 = fs1*((1/(1+(2fs1(rVal.abs))+(fs1**2))).sqrt);

//window0 = fs0*rVal1;

//window1 = fs1*rVal2;

window0 = fs0*((1/(1+(2fs0(rVal.abs))+(fs0**2))).sqrt) * rVal1;

window1 = fs1*((1/(1+(2fs1(rVal.abs))+(fs1**2))).sqrt) * rVal2;

sig = DelayC.ar(sig, fftMax/SampleRate.ir, fftMax/SampleRate.ir);

outSig = [sig[0]*window0, sig[1]*window1];

bigEnv = EnvGen.kr(Env.asr(0,1,0), gate, doneAction:2);

hiPass = hiPass*SampleRate.ir/2;

lowPass = lowPass*SampleRate.ir/2;

outSig = HPF.ar(HPF.ar(outSig, (hiPass).clip(20, SampleRate.ir/2)), (hiPass).clip(20, SampleRate.ir/2));

outSig = LPF.ar(LPF.ar(outSig, (lowPass).clip(20, SampleRate.ir/2)), (lowPass).clip(20, SampleRate.ir/2));

Out.ar(out, Mix.new(outSig)bigEnvamp);

//Out.ar(out, [sig[0], sig[1], rVal1, window0, window1])

}).add;

)

Synth(\pb_monoStretch2_Overlap2, [\bufnum, b, \stretch, 2])

The way this is designed, you need to set a value for lowpass and highpass. Each of these Synth instances are a slice of the spectrum as far as I understand. And by default it's fraom 0 to 0 hz in the above.

— Reply to this email directly, view it on GitHubhttps://github.com/spluta/TimeStretch/issues/15#issuecomment-1025882894, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAWN2RTPVXEHGP5GBT4OIR3UY2SKPANCNFSM5D2AFSDA. Triage notifications on the go with GitHub Mobile for iOShttps://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675 or Androidhttps://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub. You are receiving this because you are subscribed to this thread.Message ID: @.***>

spluta avatar Jan 31 '22 15:01 spluta

There's a pull request here that makes it easier to use in realtime (at least for me). It includes an example:

https://github.com/spluta/TimeStretch/pull/17

madskjeldgaard avatar Jan 31 '22 16:01 madskjeldgaard