PmonoArtic crossfade and synthDef switch

Hello,

Maybe, someone here knows how to answer these 2 questions concerning PmonoArtic :

  1. If there a way to switch between different synthDefs with PmonoArtic during the rearticulation (since a new synth is triggered) ?

  2. Is there a way to crossfade between these 2 PmonoArtic with different legato in a Pdef without errors ?

Pdef(\one).fadeTime_(2);
Pdef(\one, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,8) * 100 , \legato, 1)).play
Pdef(\one, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,8) * 100, \legato, 0.2 )).play // the crossfade is ok
Pdef(\one, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,8) * 100 , \legato, 1)).play // the synth is released, resulting in a lot of FAILURE IN SERVER /n_set Node 4831 not found…

Many thanks,

Christophe

Regarding your first question: you can change/set the synthName member variable asynchronously. E.g.

p = PmonoArtic(\default, \dur, 5, \amp, 0.8, \freq, Pseq([555, 666], inf), \legato, 0.6)
e = p.play

SynthDef(\sine, {  |out=0, amp=1, gate=1, freq=555| Out.ar(out, amp!2 * EnvGen.ar(Env.asr(), gate, doneAction: 2) * SinOsc.ar(freq) )}).add;

p.synthName = \sine // this takes effect on the next note with legato < 1

e.stop

If you want this to happen from the event stream, you’d have to insert some kind of pseudo event that just does this (e.g. with \amp set to zero) and some Plazy that does synthName change as a side effect, e.g.

~pma = PmonoArtic(\default, \dur, 1, \amp, Pseq([0.8, 0], 2), \freq, Pseq([555, 666], 2), \hack, Pseq([0, Plazy { ~pma.synthName = \sine }], 2))

~pma.play

Many thanks for this solution,

But is there a way a to integrate PmonoArtic within Pdef (for crossfade) and play with the legato (< 1 or > 1) without generating errors in the post window, as shown in my first post ?

Best,
Christophe

I’m not sure. This question of mine is might be related: Is Pmono resume supposed to fail?

Although I’ve stopped using PmonoArtic for more complicated reasons, this issue is still quite befuddling to me. Pdef with crossfade (non-nil fadeTime) does this:

				Ppar([
					EmbedOnce(
						PfadeOut(stream, fadeTime, delta, tolerance),
						cleanup
					),
					PfadeIn(newStream, fadeTime, delta, tolerance)
				]).asStream

But amusingly, the bug doesn’t seem to be in that code:

x = PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(9,16) * 100, \legato, 0.2, \amp, 0.05);
y = PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,5) * 100 , \legato, 1, \amp, 0.2);

p = Ppar([x, y]).play // ok
p = Ppar([PfadeOut(x, 5), y]).play // ok 
p = Ppar([x, PfadeIn(y, 5)]).play // ok too
p = Ppar([PfadeOut(x, 5), PfadeIn(y, 5)]).play // ok in combo

// even this works!
p = Ppar([EmbedOnce(PfadeOut(x, 5), EventStreamCleanup.new), PfadeIn(y, 5)]).play

At least that gives a workaround to using Pdef’s own crossfade.

I’m not sure what EmbedOnce is for, by the way. It actually seems to prevent some such failures, at a least on stop

p = Ppar([PfadeOut(y, 5),  PfadeIn(x, 5)]).play // ok
p.stop // prints some failure if called after y fully fades

p = Ppar([EmbedOnce(PfadeOut(y, 5), EventStreamCleanup.new), PfadeIn(x, 5)]).play
p.stop // seems to prevent that failure

Also, an attempt at a workaround “a Pdef layer below” didn’t quite work as expected

v = Pbind(\dur, 0.2, \freq, Pwhite(9,16) * 100, \legato, 0.2, \amp, 0.05)
w = Pbind(\dur, 0.2, \freq, Pwhite(2,4) * 100 , \legato, 1, \amp, 0.2)

Pdef(\two, PmonoArtic(\default) <> Pdef(\data))
Pdef(\data).fadeTime_(5)
Pdef(\data, v)
Pdef(\two).play
Pdef(\data, w) // v fadeout but not w fade in...

I suspect the actual problem is that EventPatternProxy reuses its cleanup object when you change its source (does not re-init the cleanup), so it might be contaminated with stuff from previously set sources. I’ve (finally) managed to repro it by doing what Pdef is doing internally on a source swap:

(p = Prout({ |inval|
	var cleanup = EventStreamCleanup.new, stream = x.asStream, cnt = 15, outval;
	loop {
		cnt = cnt-1;
		if (cnt == 0) {
			"source swap".postln;
			stream = Ppar([EmbedOnce(PfadeOut(x, 5), cleanup), PfadeIn(y, 5)]).asStream;
		};
		outval = stream.next(inval);
		outval = cleanup.update(outval);
		inval = outval.yield;
	};
}).play)

I doesn’t bomb immediately on source swap though, but after the fading in Ppar finishes.

The cleanup system in SC quite buggy, so the actual problem may be elsewhere. For now, it’s best to avoid patterns that use cleanup, even internally (like Pmono series).

Actually what happens is that cleanup for the stuff being cross-faded in gets called as soon as the cross-fade is done. The next code doesn’t stop on cross fade, but cleanup gets called (which in this case is harmless, unlike for Pmono or PmonoArtic with legato >= 1)

Somewhat simpler code to show where the problem is, namely the cleanup gets called prematurely:

Pdef(\two).fadeTime_(2);
Pdef(\two, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(2,4) * 100 , \legato, 1)).play
// no stops with the next, but the cleanup gets called!
Pdef(\two, Pfset({}, Pbind(\dur, 0.2, \freq, 440), { "bye".postln })).play

Filed as a bug:

Hello,

Thank you so much for reporting this issue on Github and trying to solve it.

I’ve downloaded the latest build of SC with the pull request that should solve this issue, but I still have problems with a lot of FAILURE IN SERVER /n_set Node XXXX not found…

Unfortunately, I don’t have the knowledge why there is still this error ?

Many thanks,

Christophe

“the latest build of SC” meaning a devel build? Open Pdef.sc and see if you find the fadeOutCleanup string in it.

N.B. You can just patch the Pdef.sc file in any build. I did that with my “production 3.10.4” and I can tell you the example you’ve posted at the top of this thread works fine now.

I’ve tested with the latest devel build and I can find within Pdef.sc “fadeOutCleanup”,
but I still have errors. don’t know what I am doing wrong… ?

Christophe

Errors with the same example you’ve posted above?

Yes, exactly the same example and error in the post window

Ok, are you sure you’re actually running the build you’ve downloaded and it’s not reading the class files from somewhere else? Because it’s in the “works for me” variety…

What happens when you try the following?

TestEventPatternProxy.new.test_EventPatternProxy_xfade_cleanupNotPremature

By evaluating:

TestEventPatternProxy.new.test_EventPatternProxy_xfade_cleanupNotPremature

I’ve got :

ERROR: Class not defined.
in interpreted text
line 1 char 21:
TestEventPatternProxy.new.test_EventPatternProxy_xfade_cleanupNotPremature
-----------------------------------
→ nil

However, I am sure that I run the latest devel build, since when I open Pdef.sc, I can see “fadeOutCleanup”. I also tried without my extensions in case.

You need include the testsuite in your sclang_conf.yaml (for the unit tests to usable).

Speaking of extensions… you could drop a new file (with a .sc extension) in any of them, containing

+ EventPatternProxy {

	constrainStream { arg stream, newStream, inval, cleanup;
		var delta, tolerance, fadeOutCleanup;
		var quantBeat, catchUp, deltaTillCatchUp, forwardTime, quant = this.quant;

		^if(this.quant.isNil) {
			cleanup !? { cleanup.exit(inval) };
			newStream
		} {
			quantBeat = this.quantBeat ? 0;
			catchUp = this.outset;

			delta = thisThread.clock.timeToNextBeat(quant);
			tolerance = quantBeat % delta % 0.125;

			if(catchUp.notNil) {
				deltaTillCatchUp = thisThread.clock.timeToNextBeat(catchUp);
				forwardTime = quantBeat - delta + deltaTillCatchUp;
				delta = newStream.fastForward(forwardTime, tolerance) + deltaTillCatchUp;
			};

			if(fadeTime.isNil) {
				if(delta == 0) {
					cleanup !? { cleanup.exit(inval) };
					newStream
				} {
					Pseq([
						EmbedOnce(
							Pfindur(delta, stream, tolerance).asStream,
							cleanup
						),
						newStream
					]).asStream
				}
			}{
				fadeOutCleanup = cleanup.copy;
				cleanup.clear; // change need be seen by caller function, i.e. embedInStream
				Ppar([
					EmbedOnce(
						PfadeOut(stream, fadeTime, delta, tolerance),
						fadeOutCleanup
					),
					PfadeIn(newStream, fadeTime, delta, tolerance)
				]).asStream
			}
		}
	}
}

Then recompile your classlib. (You’ll get a warning about overrides, but that’s ok.) This is equivalent to the patch from github.

You can also do

EventPatternProxy.findMethod(\constrainStream).filenameSymbol

to see where’s it’s loaded from.

I dropped your code in a .sc extension, recompiled, saw the overwrites.
But I still have the error. very strange. don’t know why we have different results.

And when I evaluate:

EventPatternProxy.findMethod(\constrainStream).filenameSymbol

I’ve got :

→ /Users/xon/Library/Application Support/SuperCollider/Extensions/test.sc

which is the file where I pasted your code.

Concerning the testsuite, I don’t know exactly how to include it in my sclang_conf.yaml.

Ok, you should probably file a new ticket on github and/or leave a comment for the old one to be reopened. But you’ll have to provide more details than “it doesn’t work for me”.

I’d be curious to hear what @jamshark70 thinks happens here. Because he also tested some earlier versions of the patch (in fact he came up with the solution). And I suspect Julian (telephon on github) tested it too; he was the initial code reviewer for the PR.

Do try to get the testuite running on your box. You need edit sclang_conf.yaml either manually or from the scide (if that’s what your’re using as SC front-end); with the latter it’s in the Edit > Preferences > Interpreter > Include and add a full path to the test suite… which would be the testsuite subdir of wherever you’ve downloaded the source for the SC develop tree, i.e. something like

/<your path to develop SC tree>/testsuite

There should be a TestEventPatternProxy.sc in testsuite/classlibrary; this was committed recently to develop.

(You should see your installed quarks listed in the yaml file and/or scide preference too.)

You could also run the code below, which is “manual” version of the unit test, with more debugging printed:

(
{
	var test_EventPatternProxy_xfade_cleanup_not_premature = {

		var assert = { |what, which, important = true|
			var msg = if(what) { "PASS" } { "FAIL" };
			(msg ++ ":" + which).postln;
		};
		var stream2_cleanup_called = false, p = EventPatternProxy.new.fadeTime_(2), r;
		p.source = Pbind(\dur, 1.1, \degree, 3, \amp, 0.1);
		r = p.asStream;
		// pulling one event is necessary to prime the old stream
		assert.(r.next(())[\degree] == 3,
			"EventPatternProxy xfade 1st source 1st pull shold be executed transparently.");
		p.source = Pfset({},
			Pbind(\dur, 1.3, \freq, 440, \amp, 0.4),
			{ stream2_cleanup_called = true });
		// Pull enough events to exhaust the xfade.
		// There should NOT be a cleanup of the 2nd stream executed,
		// since it's an infinite stream (of the same event).
		// Three more events should be enough to pull here to
		// end the cross-fade, but "just in case" we pull 6.
		6 do: { r.next(()).postln };
		assert.(stream2_cleanup_called.not,
			"EventPatternProxy xfade 2nd source cleanup should be not called prematurely.");
		// todo: might also wanna check that the cleanup was sent downstream (addToCleanup seen)
	};

	test_EventPatternProxy_xfade_cleanup_not_premature.();
}.value;
)

Hi,

With the manual “version” of the unit test, I’ve got in the print window:

PASS: EventPatternProxy xfade 1st source 1st pull shold be executed transparently.
( ‘degree’: 3, ‘dur’: 1.1, ‘delta’: 0.0, ‘amp’: 0.045 )
( ‘addToCleanup’: [ a Function ], ‘freq’: 440, ‘dur’: 1.3, ‘delta’: 1.1,
‘amp’: 0.26 )
( ‘delta’: 0.2, ‘dur’: Rest(0.2) )
( ‘freq’: 440, ‘dur’: 1.3, ‘delta’: 1.3, ‘amp’: 0.4 )
( ‘freq’: 440, ‘dur’: 1.3, ‘delta’: 1.3, ‘amp’: 0.4 )
( ‘freq’: 440, ‘dur’: 1.3, ‘delta’: 1.3, ‘amp’: 0.4 )
PASS: EventPatternProxy xfade 2nd source cleanup should be not called prematurely.
→ PASS: EventPatternProxy xfade 2nd source cleanup should be not called prematurely.

And by evaluating :

TestEventPatternProxy.new.test_EventPatternProxy_xfade_cleanupNotPremature

I’ve got :

PASS: a TestEventPatternProxy: new - EventPatternProxy xfade 1st source 1st pull shold be executed transparently.
Is:
3
Should be:
3
PASS: a TestEventPatternProxy: new - EventPatternProxy xfade 2nd source cleanup should be not called prematurely.
→ a TestEventPatternProxy

But, still, when I evaluate the last line of the below code with the latest develop build, I still have errors with FAILURE IN SERVER /n_set Node :

Pdef(\one).fadeTime_(2);
Pdef(\one, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,8) * 100 , \legato, 1)).play
Pdef(\one, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,8) * 100, \legato, 0.2 )).play // ok
Pdef(\one, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,8) * 100 , \legato, 1)).play // stops w/err

Can someone test it with the latest develop build of SuperCollider ?
to know if the problem is me…

Many thanks,
Christophe

1 Like

I’ll try a Linux build later today or tomorrow. In the meantime, manually applying just that patch to 3.11.0 on a Windows 10 installation “fixed it”, i.e. your last example bombs without the patch but works fine after reloading the class-lib with the patch (meaning the one from above overriding that constrainStream.) Likewise for the Canonical-provided SC 3.10.0 on Lubuntu 20. I don’t have a Mac, so I cannot try it there.

Can you try just patching a 3.11.0? Does only develop fail for you?


I did a linux build and I can confirm the bugfix doesn’t fully fix develop, event though it does fix 3.11.0!! I’ll try to have the bug report reopened.

Specifically, this sequence is ok on develop

Pdef(\one).fadeTime_(2);
Pdef(\one, Pbind(\dur, 0.3, \degree, 1)).play
Pdef(\one, Pfset({}, Pbind(\dur, 0.2, \freq, 440), { "bye".postln })).play

but if you follow it with

Pdef(\one, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,8) * 100 , \legato, 1)).play
Pdef(\one, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,8) * 100, \legato, 0.2 )).play 
Pdef(\one, PmonoArtic(\default, \dur, 0.2, \freq, Pwhite(1,8) * 100 , \legato, 1)).play 

the last line is still bugged on develop. It looks like it will take more investigation to find out why.

If your’re willing to beta-test new cleanup system (which fixes several more bugs) it seems it solves this one too in develop (and yeah, I’ve checked it wasn’t actually enabled in other non-develop tests–it’s easy to see because the new system doesn’t pass bare functions in addToCleanup).

Thank you so much!

I can confirm there is no more error in the post window by using the latest build develop and the following file with CleanupThunk.

Before I had only tested with the latest build. and I can confirm also your first patch did work with 3.11, but not develop.

Many thanks for finding a solution with the SC community!
Christophe

1 Like