GUI Questions, calling Synths/Patterns from inside a GUI

I’ve been trying to train myself to hear in just intonation recently. SuperCollider has been quite helpful for this and I thought I might try my hand at making a GUI so that my friends, who don’t use SC, could also try it out. I am not 100% sure but I think the basic design seems ok, but there are two major problems and one very minor problem.

Here’s the code:

// GUI

(
var numButtons = 4;
var numSliders = 4;
var numNumberBoxes = 4;
var numLabels0 = 4;
var numLabels1 = 4;
var buttons, sliders, numberBoxes, labels0, labels1, sliderLayout, buttonLayout, numberBoxesLayout, labelsLayout0, labelsLayout1, layout;

w = Window.new;

// objects

buttons = numButtons.collect{
	Button.new
};

sliders = numSliders.collect{
	Slider.new.orientation_(\horizontal)
};

numberBoxes = numNumberBoxes.collect{
	NumberBox.new
};

labels0 = numLabels0.collect{
	StaticText.new
};

labels1 = numLabels1.collect{
	StaticText.new
};

// layout

buttonLayout = HLayout.new(*buttons);
sliderLayout = VLayout.new(*sliders);
numberBoxesLayout = HLayout.new(*numberBoxes);
labelsLayout0 = VLayout.new(*labels0);
labelsLayout1 = HLayout.new(*labels1);

//layout = VLayout(sliderLayout, buttonLayout, numberBoxesLayout);
layout = VLayout(HLayout(labelsLayout0, sliderLayout), buttonLayout, VLayout(numberBoxesLayout, labelsLayout1));

// buttons

// Start pattern
buttons[0].states_([["Start Pattern", Color.black, Color.gray]]).action_({|obj|
	Pdef(\pattern).play
});


// Stop pattern
buttons[1].states_([["Stop Pattern", Color.black, Color.gray]]).action_({|obj|
	Pdef(\pattern).stop;
});

// Start bass note
buttons[2].states_([["Start Bass Note", Color.black, Color.gray]]).action_({|obj|
	~bassNote
});


// Stop bass note
buttons[3].states_([["Stop Bass Note", Color.black, Color.gray]]).action_({|obj|
	~bassNote.free;
});

// sliders

// Slider 0 - change dur
~s0 = sliders[0].action_({|obj|
	var val = obj.value;

	// Scale duration, make sure it is not zero
	val = val.linlin(0.0, 1.0, 0.05, 1.0);
	Pdefn(\pDur, val)
});

// Slider 1 - change atk
~s1 = sliders[1].action_({|obj|
	var val = obj.value;
	val = val.linlin(0.0, 1.0, 0.01, 0.3);
	Pdefn(\pAtk, val)
});

// Slider 2 - change rel
~s2 = sliders[2].action_({|obj|
	var val = obj.value;
	val = val.linlin(0.0, 1.0, 0.5, 1);

	Pdefn(\pRel, val)
});

// Slider 3 - change amp
~s3 = sliders[3].action_({|obj|
	var val = obj.value;
	val = val.linlin(0.0, 1.0, 0.0, 0.75);
	Pdefn(\pAmp, val)
});

// numberBoxes

~bassFreq = numberBoxes[0].value_(55)
.clipLo_(30)
.clipHi_(100);


~bassAmp = numberBoxes[1].value_(0.1)
.clipLo_(0)
.clipHi_(0.75);

~startPartial = numberBoxes[2].value_(10)
.step_(1)
.clipLo_(1)
.clipHi_(25);

~numNotes = numberBoxes[3].value_(5)
.step_(1)
.clipLo_(2)
.clipHi_(30);

// labels - fund, bass amp, start partial, num notes

labels0[0].string_("Speed");
labels0[1].string_("Attack");
labels0[2].string_("Release");
labels0[3].string_("Volume");
labels1[0].string_("Fundamental");
labels1[1].string_("Bass Volume");
labels1[2].string_("Start Partial");
labels1[3].string_("Scale Length");


// tidying up

w.layout = layout;
w.front;

)


// SynthDefs

(
SynthDef(\blip, {
	arg baseFreq=55, ratio=10, amp=0.25, pan=0, atk=0.02, rel=0.1, out=0;
	var sig, env;

	env = Env.perc(atk, rel);
	env = EnvGen.ar(env, doneAction: 2);

	sig = SinOsc.ar(baseFreq * ratio);
	sig = sig * env * amp;
	sig = Pan2.ar(sig, pan);
	sig = FreeVerb.ar(sig);

	Out.ar(out, sig);
}).add;
)



(
SynthDef(\bass, {
	arg freq=55, amp=0.25, pan=0, out=0, lfo=0.1, lpf=10000;
	var sig, moog;

	moog = LFNoise1.ar(lfo).exprange(freq*4, freq*120);
	
	sig = LFSaw.ar(freq);
	sig = MoogFF.ar(sig, moog);
	sig = LPF.ar(sig, lpf);
	sig = sig * amp;
	sig = Pan2.ar(sig, pan);
	sig = FreeVerb.ar(sig);

	Out.ar(out, sig);
}).add;
)

~bassNote = Synth(\bass, [\freq, ~bassFreq.value, \amp, ~bassAmp.value]);
Synth(\bass, [\lpf, 1000])


// basic pattern to control

(
Pdef(\pattern,
	Pbind(
		\instrument, \blip,
		\dur, Pdefn(\pDur, 1),
		//\ratio, 5, // this works
                \ratio, Pdefn(\pRatio, Pseries(~startPartial, 1, ~numNotes)), // this doesn't
		\amp, Pdefn(\pAmp, 0.25),
		\atk, Pdefn(\pAtk, 0.01),
		\rel, Pdefn(\pRel, 0.8),
		\pan, 0
	)
);
)

I’m guessing there are problems with how I’m calling the Bass synth, but I’m mostly frustrated by the following error message regarding the Pattern:

ERROR: Primitive '_QObject_GetProperty' failed.
Failed.
RECEIVER:
Instance of NumberBox {    (0x5618580c82d8, gc=E0, fmt=00, flg=00, set=06)
  instance variables [46]
    qObject : RawPointer 0x561852c8d500
    finalizer : instance of Finalizer (0x5618580f7488, size=2, set=1)
    virtualSlots : nil
    wasRemoved : false
    font : nil
    resize : Integer 1
    alpha : Float 1.000000   00000000 3FF00000
    decorator : nil
    layout : nil
    userCanClose : true
    deleteOnClose : true
    action : nil
    mouseDownAction : nil
    mouseUpAction : nil
    mouseEnterAction : nil
    mouseLeaveAction : nil
    mouseMoveAction : nil
    mouseOverAction : nil
    mouseWheelAction : nil
    keyDownAction : nil
    keyUpAction : nil
    keyModifiersChangedAction : nil
    keyTyped : nil
    focusGainedAction : nil
    focusLostAction : nil
    dragLabel : nil
    beginDragAction : nil
    canReceiveDragHandler : nil
    receiveDragHandler : nil
    toFrontAction : nil
    endFrontAction : nil
    onClose : nil
    onResize : nil
    onMove : nil
    step : Integer 1
    shift_scale : Float 100.000000   00000000 40590000
    ctrl_scale : Float 10.000000   00000000 40240000
    alt_scale : Float 0.100000   9999999A 3FB99999
    scroll : true
    scroll_step : Integer 1
    align : nil
    buttonsVisible : false
    normalColor : instance of Color (0x56185760dfa8, size=4, set=2)
    typingColor : instance of Color (0x561857681018, size=4, set=2)
    object : nil
    setBoth : true
}
CALL STACK:
	MethodError:reportError
		arg this = <instance of PrimitiveFailedError>
	Nil:handleError
		arg this = nil
		arg error = <instance of PrimitiveFailedError>
	Thread:handleError
		arg this = <instance of Routine>
		arg error = <instance of PrimitiveFailedError>
	Thread:handleError
		arg this = <instance of Routine>
		arg error = <instance of PrimitiveFailedError>
	Thread:handleError
		arg this = <instance of Routine>
		arg error = <instance of PrimitiveFailedError>
	Thread:handleError
		arg this = <instance of Routine>
		arg error = <instance of PrimitiveFailedError>
	Object:throw
		arg this = <instance of PrimitiveFailedError>
	Object:primitiveFailed
		arg this = <instance of NumberBox>
	NumberBox:value
		arg this = <instance of NumberBox>
		var type = nil
		var val = nil
	Pseries:embedInStream
		arg this = <instance of Pseries>
		arg inval = <instance of Event>
		var outval = nil
		var counter = 0
		var cur = nil
		var len = nil
		var stepStr = nil
		var stepVal = nil
	Routine:prStart
		arg this = <instance of Routine>
		arg inval = <instance of Event>
^^ The preceding error dump is for ERROR: Primitive '_QObject_GetProperty' failed.
Failed.
RECEIVER: a NumberBox

Any feedback would be super helpful. Oh, and the last question: is there a simple way to make the labels at the bottom of the GUI smaller?

Thanks!
Jordan

GUI objects are not values, and there’s a restriction on access from a thread: only from AppClock, not from SystemClock or TempoClock.

Your current model is pattern <-- pulls from GUI.

A better model is:

  • GUI --> writes to variable
  • pattern <-- pulls from variable

The variable may be accessed from any thread at any time, getting around the restriction.

hjh

Thanks for the help. I think I understood what you suggested and If I understand correctly, I’ve gotten to the point that the GUI is writing to the universal variables OK. I am for some reason having the problem that the Pattern isn’t pulling these values.

Here’s my code:

// GUI

(
var numButtons = 5;
var numSliders = 4;
var numNumberBoxes = 4;
var numLabels0 = 4;
var numLabels1 = 4;
var buttons, sliders, numberBoxes, labels0, labels1, sliderLayout, buttonLayout, numberBoxesLayout, labelsLayout0, labelsLayout1, layout;

w = Window.new;

// objects

buttons = numButtons.collect{
	Button.new
};

sliders = numSliders.collect{
	Slider.new.orientation_(\horizontal)
};

numberBoxes = numNumberBoxes.collect{
	NumberBox.new
};

labels0 = numLabels0.collect{
	StaticText.new
};

labels1 = numLabels1.collect{
	StaticText.new
};

// layout

buttonLayout = HLayout.new(*buttons);
sliderLayout = VLayout.new(*sliders);
numberBoxesLayout = HLayout.new(*numberBoxes);
labelsLayout0 = VLayout.new(*labels0);
labelsLayout1 = HLayout.new(*labels1);

//layout = VLayout(sliderLayout, buttonLayout, numberBoxesLayout);
layout = VLayout(HLayout(labelsLayout0, sliderLayout), buttonLayout, VLayout(numberBoxesLayout, labelsLayout1));


// sliders

// Slider 0 - change dur
sliders[0].action_({|obj|
	var val = obj.value;

	// Scale duration, make sure it is not zero
	val = val.linlin(0.0, 1.0, 0.05, 1.0);
	~dur = val;
	//Pdefn(\pDur, val)
});

// Slider 1 - change atk
sliders[1].action_({|obj|
	var val = obj.value;
	val = val.linlin(0.0, 1.0, 0.01, 0.3);
	~atk = val;
	//Pdefn(\pAtk, val)
});

// Slider 2 - change rel
sliders[2].action_({|obj|
	var val = obj.value;
	val = val.linlin(0.0, 1.0, 0.5, 1);
	~rel = val;

	//Pdefn(\pRel, val)
});

// Slider 3 - change amp
sliders[3].action_({|obj|
	var val = obj.value;
	val = val.linlin(0.0, 1.0, 0.0, 0.75);
	~amp = val;
	//Pdefn(\pAmp, val)
});

// numberBoxes

~fundamental = numberBoxes[0].value_(55)
.clipLo_(30)
.clipHi_(100);

~bassAmp = numberBoxes[1].value_(0.1)
.clipLo_(0)
.clipHi_(0.75);

~startPartial = numberBoxes[2].value_(10)
.step_(1)
.clipLo_(1)
.clipHi_(25);

~numNotes = numberBoxes[3].value_(5)
.step_(1)
.clipLo_(2)
.clipHi_(30);

// buttons

// Start pattern
buttons[0].states_([["Start Pattern", Color.black, Color.gray]]).action_({|obj|
	Pdef(\pattern).play
});


// Stop pattern
buttons[1].states_([["Stop Pattern", Color.black, Color.gray]]).action_({|obj|
	Pdef(\pattern).stop;
});


// Start bass note - to do, try out pmono
buttons[2].states_([["Start Bass Note", Color.black, Color.gray]]).action_({|obj|
	x = Synth(\bass, [\fundamental, ~fundamental.value, \amp, ~bassAmp.value])
});


// Stop bass note
buttons[3].states_([["Stop Bass Note", Color.black, Color.gray]]).action_({|obj|
	x.free;
});

// Scale or arpeggio

buttons[4].states_([["Scale", Color.black, Color.gray], ["Arpeggio", Color.black, Color.gray]]).action_({
	|obj| var val = obj.value;
	~step = val + 1;

});

// labels - fund, bass amp, start partial, num notes

labels0[0].string_("Speed");
labels0[1].string_("Attack");
labels0[2].string_("Release");
labels0[3].string_("Volume");
labels1[0].string_("Fundamental");
labels1[1].string_("Bass Volume");
labels1[2].string_("Start Partial");
labels1[3].string_("Scale Length");


// tidying up

w.layout = layout;
w.front;

)


// SynthDefs

(
SynthDef(\blip, {
	arg fundamental=55, ratio=10, amp=0.25, pan=0, atk=0.02, rel=0.1, out=0;
	var sig, env;

	env = Env.perc(atk, rel);
	env = EnvGen.ar(env, doneAction: 2);

	sig = SinOsc.ar(fundamental * ratio);
	sig = sig * env * amp;
	sig = Pan2.ar(sig, pan);
	sig = FreeVerb.ar(sig);

	Out.ar(out, sig);
}).add;
)

(
SynthDef(\bass, {
	arg fundamental=55, amp=0.25, pan=0, out=0, lfo=0.1, lpf=1000;
	var sig, moog;

	moog = LFNoise1.ar(lfo).exprange(fundamental*4, fundamental*50);

	sig = LFSaw.ar(fundamental);
	sig = MoogFF.ar(sig, moog);
	sig = LPF.ar(sig, lpf);
	sig = sig * amp;
	sig = Pan2.ar(sig, pan);
	sig = FreeVerb.ar(sig);

	Out.ar(out, sig);
}).add;
)



// basic pattern to control

(
Pdef(\pattern,
	Pbind(
		\instrument, \blip,
		\dur, Pdefn(\pDur, ~dur.value, inf),
		\ratio, Pdefn(\pRatio, Pseq([Pseries(~startPartial.value, ~step.value, ~numNotes.value)], inf)),
		\baseFreq, Pdefn(\baseFreq, ~bassFreq.value, inf),
		\amp, Pdefn(\pAmp, ~amp.value, inf),
		\atk, Pdefn(\pAtk, ~atk.value, inf),
		\rel, Pdefn(\pRel, ~rel.value, inf),
		\pan, 0
	)
);
)

EDIT: This works, to some extent. However, I need to do the following:

  1. Open the GUI
  2. Compile the pattern (if this terminology is correct - that is, running the Pdef code)
  3. Start the Pattern from inside the GUI

If I want to change the pattern I have to:

  1. Stop the pattern
  2. Modify the parameters
  3. Re-compile the pattern
  4. Start the pattern again

This is cumbersome and I have a feeling it could be done much better, would be very happy if someone could point me in the right direction!

Cheers,
Jordan

Ah, I see I wasn’t clear about that… I should have anticipated this but didn’t. (It’s the #1 most common misunderstanding about variables – see here – another user had exactly the same question, 2 days ago.)

A little experiment:

a = 1;
b = Pwhite(0, 7, inf);
c = [\dur, a + 1, \degree, b + 1];
-> [ \dur, 2, \degree, a Pbinop ];

This array is just like the list of pairs for Pbind – you could replace [ with Pbind( and ] with ) and the behavior is the same.

This is putting in a + 1 and getting 2. That’s important – the Pbind array does not store the expression a + 1. It stores the result of the expression.

In the Pbind, a is gone. It doesn’t exist. It was resolved to its concrete value once, at the time of building the Pbind, but the Pbind has no idea where that value came from.

In this case, you wanted Pbind to memorize a construction that retrieves the variable’s value anew on every cycle… but instead wrote to reduce it immediately and forget the source.

The quickest expression to do that is Pfunc { a } or in your case Pfunc { ~amp }.

Another way would be to use Pdefn: in the GUI, set it by Pdefn(\amp, theNewValue) (substitute your own value) and use it in the pattern by Pdefn(\amp) (not setting a value here).

hjh

1 Like

You might check VarGui from the miSCellaneous_lib quark. It’s for control of synths and environmental variables, and – via these – for pattern control.

There are examples in the doc files ‘VarGui’ and ‘VarGui shortcut builds’, but I assume the quickest approach is described in the chapters Tour 1-3 of ‘Introduction to miSCellaneous’.

Thankyou! This is awesome.

I am now at the point where almost everything works, however I am having trouble with the \ratio key in my pattern - is there a logical way to nest patterns here?

// TO DO: bassamp - maybe a pmono? fix ratio somehow, can I nest pdefns?


// GUI

(
var numButtons = 5;
var numSliders = 5;
var numNumberBoxes = 3;
var numLabels0 = 5;
var numLabels1 = 3;
var buttons, sliders, numberBoxes, labels0, labels1, sliderLayout, buttonLayout, numberBoxesLayout, labelsLayout0, labelsLayout1, layout;

w = Window.new;

// objects

buttons = numButtons.collect{
	Button.new
};

sliders = numSliders.collect{
	Slider.new.orientation_(\horizontal)
};

numberBoxes = numNumberBoxes.collect{
	NumberBox.new
};

labels0 = numLabels0.collect{
	StaticText.new
};

labels1 = numLabels1.collect{
	StaticText.new
};

// layout

buttonLayout = HLayout.new(*buttons);
sliderLayout = VLayout.new(*sliders);
numberBoxesLayout = HLayout.new(*numberBoxes);
labelsLayout0 = VLayout.new(*labels0);
labelsLayout1 = HLayout.new(*labels1);

//layout = VLayout(sliderLayout, buttonLayout, numberBoxesLayout);
layout = VLayout(HLayout(labelsLayout0, sliderLayout), buttonLayout, VLayout(numberBoxesLayout, labelsLayout1));


// sliders

// Slider 0 - change dur
sliders[0].action_({|obj|
	var val = obj.value;

	// Scale duration, make sure it is not zero
	val = val.linlin(0.0, 1.0, 2.0, 0.1);
	~dur = val;
	Pdefn(\pDur, ~dur)
});

// Slider 1 - change atk
sliders[1].action_({|obj|
	var val = obj.value;
	val = val.linlin(0.0, 1.0, 0.01, 0.3);
	~atk = val;
	Pdefn(\pAtk, ~atk)
});

// Slider 2 - change rel
sliders[2].action_({|obj|
	var val = obj.value;
	val = val.linlin(0.0, 1.0, 0.5, 5);
	~rel = val;

	Pdefn(\pRel, ~rel)
});

// Slider 3 - change amp
sliders[3].action_({|obj|
	var val = obj.value;
	val = val.linlin(0.0, 1.0, 0.0, 0.75);
	~amp = val;
	Pdefn(\pAmp, ~amp)
});

// Slider 4 - change bass amp
sliders[4].action_({|obj|
	var bassAmp = obj.value;
	bassAmp = bassAmp.linlin(0.0, 1.0, 0.0, 0.75);
	if(
		x.isPlaying,
		{x.set(\amp, bassAmp)}
	)
});

// numberBoxes

numberBoxes[0].value_(55)
.clipLo_(30)
.clipHi_(100)
.action_({
	arg obj; var val;
	val = obj.value;
	~fundamental = val;
	Pdefn(\fundamental, ~fundamental)

})
//.action_({
	//arg obj;
	//var amp;
	//amp = obj.value;
	//if(
		//x.isPlaying,
		//{x.set(\amp, amp)}
	//)
//}
//);
;
/*
numberBoxes[1].value_(0.1)
.clipLo_(0)
.clipHi_(0.75)
.action_({
	arg obj;
	var val;
	val = obj.value;
	~bassAmp = val;
	if(
		~x.isPlaying,
		{~x.set(\amp, ~bassAmp)}
	)
})
;
*/
numberBoxes[1].value_(10)
.step_(1)
.clipLo_(1)
.clipHi_(25)
.action_({
	arg obj;
	var val = obj.value;
	~startPartial = val;
	Pdefn(\startPartial, ~startPartial)
});

numberBoxes[2].value_(5)
.step_(1)
.clipLo_(2)
.clipHi_(30)
.action_({
	arg obj;
	var val = obj.value;
	~numNotes = val;
	Pdefn(\numNotes, ~numNotes)
});

// buttons

// Start pattern
buttons[0].states_([["Start Pattern", Color.black, Color.gray]]).action_({|obj|
	Pdef(\pattern).play
});


// Stop pattern
buttons[1].states_([["Stop Pattern", Color.black, Color.gray]]).action_({|obj|
	Pdef(\pattern).stop;
});


// Start bass note - to do, try out pmono
buttons[2].states_([["Start Bass Note", Color.black, Color.gray]]).action_({|obj|
	x = Synth(\bass, [\fundamental, ~fundamental.value])
});


// Stop bass note
buttons[3].states_([["Stop Bass Note", Color.black, Color.gray]]).action_({|obj|
	x.free;
});

// Scale or arpeggio

buttons[4].states_([["Scale", Color.black, Color.gray], ["Arpeggio", Color.black, Color.gray]]).action_({
	|obj| var val = obj.value;
	~step = val + 1;
	Pdefn(\step, ~step)

});

// labels - fund, bass amp, start partial, num notes

labels0[0].string_("Speed");
labels0[1].string_("Attack");
labels0[2].string_("Release");
labels0[3].string_("Volume");
labels0[4].string_("Bass Volume");
labels1[0].string_("Fundamental");
labels1[1].string_("Start Partial");
labels1[2].string_("Scale Length");


// tidying up

w.layout = layout;
w.front;

)


// SynthDefs

(
SynthDef(\blip, {
	arg fundamental=55, ratio=10, amp=0.25, pan=0, atk=0.02, rel=0.1, out=0;
	var sig, env;

	env = Env.perc(atk, rel);
	env = EnvGen.ar(env);

	sig = SinOsc.ar(fundamental * ratio);
	sig = sig * env * amp;
	sig = Pan2.ar(sig, pan);
	sig = FreeVerb.ar(sig);
	DetectSilence.ar(sig, doneAction: 2);
	Out.ar(out, sig);
}).add;
)

(
SynthDef(\bass, {
	arg fundamental=55, amp=0.25, pan=0, out=0, lfo=0.1, lpf=1000;
	var sig, moog;

	moog = LFNoise1.ar(lfo).exprange(fundamental*4, fundamental*50);

	sig = LFSaw.ar(fundamental);
	sig = MoogFF.ar(sig, moog);
	sig = LPF.ar(sig, lpf);
	sig = sig * amp;
	sig = Pan2.ar(sig, pan);
	sig = FreeVerb.ar(sig);

	Out.ar(out, sig);
}).add;
)


// basic pattern to control


(
Pbindef(\pattern,

	\instrument, \blip,
	\dur, Pdefn(\pDur),
	\ratio, Pseq([5,6,7,8], inf), // this works
	//\ratio, Pdefn(\pRatio, Pseq([Pseries(Pdefn(\startPartial), Pdefn(\step), Pdefn(\numNotes))], inf)), // this doesn't
	\fundamental, Pdefn(\fundamental),
	\amp, Pdefn(\pAmp),
	\atk, Pdefn(\pAtk),
	\rel, Pdefn(\pRel),
	\pan, 0

);
)

Thanks, I will check this out, but I think for my purposes here (trying to make something for friends who don’t have SC installed) including quarks might just make the installation process more complicated…

I see, for this purpose you could also check Bruno Ruviaro’s 4 additive synthesis gui demos, simply copy and paste from the links there:

http://sccode.org/tag/category/additive%20synthesis

1 Like

This is one of those sticky, tricky things about patterns: Some arguments may be patterns, while others must be numbers, functions or streams.

If you’re generating random numbers – Pwhite(low, high, n) – then low and high can be different for every value. (Usually we don’t change them, so that the range is consistent over time, but there’s no inherent reason why they have to be consistent.) So these can be patterns, and the stream executing the pattern calls asStream on them.

But n can’t change in the middle of a Pwhite stream. Why?

p = Pwhite(0.0, 1.0, 10);
q = p.asStream;

q.nextN(5);

// now the stream has used up 5 of its 10 values

// then...
p.length = 2;

// now what should q do?
q.next;

The answer in SC is that a stream, entering Pwhite, reads length once at the beginning and sticks with that. So q can return a value there and still have 4 to go. The next stream made from p will yield 2 numbers and stop.

The stream uses value for this… not .asStream.value. So you have to give it something that can answer value with a number: generally a number, function or stream.

In Pseries, step makes sense as a “change every time” value but start and length don’t. (How can a numeric series start with 0 on the first step and start with 5 on the next step?)

So, Pseries(Pdefn(\startPartial).asStream, Pdefn(\step), Pdefn(\numNotes).asStream).

How are you supposed to know that…? Well… that’s why I said it’s tricky. There isn’t any way for you to guess. Sometimes even after almost 20 years, I have to pop open the class library code to check. In hindsight it would have been better to identify the special arguments, maybe by a prefix, but we didn’t.

Understandable to get caught out by that. It’s a hidden distinction.

hjh

1 Like

@jamshark70

thankyou for the patient and thorough explanation! I’ll mark this as solved once I’m sure everything is working smoothly.

@dkmayer thanks, looking at these has also been very helpful!

Cool, problems basically solved, it’s not running perfectly but I’m more or less happy with it:

http://sccode.org/1-5ep

Edit: I was going to mark this as solved but can’t find the option somehow, sorry!