Trying to Figure Out GUI

I’m trying to code GUI for this synth. I would like to call the synth on the server, then use my GUI to set the parameters, then when I hit a button, it will play the synth. I’m having trouble when it comes to the “which buttons”. I’m getting syntax errors. Not sure why. I don’t have much experience with GUI, I’d appreciate any help.

   (
    SynthDef.new(\envelope1, {
    	arg s, f, freq, amp, width, which, which2, freqfilter;
    	var env, selectsig, selectfilter;
    	env = EnvGen.kr(Env([0,1,0], [s, f]), doneAction:2);
    	selectsig = Select.ar(which, [SinOsc.ar(freq, 0, amp), Saw.ar(freq, amp), Pulse.ar(freq, width, amp), LFTri.ar(freq, 0, amp)])*env;
    	selectfilter = Select.ar(which2, [BLowPass.ar(selectsig, freqfilter), BHiPass.ar(selectsig, freqfilter), BBandPass.ar(selectsig, freqfilter)]);
    	Out.ar(0, selectfilter.dup);
    }).add;
    )


    ~envelope1 = Synth.new(\envelope1, [\s, 1, \f, 1, \freq, 400, \amp, 0.5, \which, 2, \width, 0.5, \which2, 0, \freqfilter, 200]);




     ~startenvelope = Slider.new(w.view, Rect.new(780,502,150,20))
    .background_(Color(0.05, 0.2, 0.0))
    .thumbSize_(10)
    .knobColor_(Color.black)
    .action_({
    	arg obj;
    	var cf;
    	cf = obj.value.linexp(0,1,0.01,5).postln;
    	if(
    		~envelope1.isPlaying,
    	~envelope1.set(\s, cf);
    	);
    });



    ~finishenvelope = Slider.new(w.view, Rect.new(780,552,150,20))
    .background_(Color(0.05, 0.2, 0.0))
    .thumbSize_(10)
    .knobColor_(Color.black)
    .action_({
    	arg obj;
    	var cf;
    	cf = obj.value.linexp(0,1,0.01,5).postln;
    	if(
    		~envelope1.isPlaying,
    	~envelope1.set(\f, cf);
    	);
    });


    ~pulsewidthslider = Slider.new(w.view, Rect.new(780,742,150,20))
    .thumbSize_(10)
    .knobColor_(Color.black)
    .action_({
    	arg obj;
    	var cf;
    	cf = obj.value.linexp(0,1,0.1,0.9).postln;
    	if(
    		~envelope1.isPlaying,
    	~envelope1.set(\width, cf);
    	);
    });


    ~freqfilter = Slider.new(w.view, Rect.new(980,742,20,150))
    .thumbSize_(10)
    .orientation_(\vertical)
    .knobColor_(Color.black)
    .action_({
    	arg obj;
    	var cf;
    	cf = obj.value.linexp(0,1,100,3500).postln;
    	if(
    		~envelope1.isPlaying,
    	~envelope1.set(\filterfreq, cf);
    	);
    });



    ~whichbutton1 = Button(w, Rect(100, 100, 50, 30))
    .states_([
    	["OFF", Color.white, Color(0.0, 0.2, 0.2)],
        ["sine", Color.white, Color(0.0, 0.2, 0.2)],
        ["saw", Color.white, Color(0.0, 0.2, 0.2)],
    	["pulse", Color.white, Color(0.0, 0.2, 0.2)],
    	["triangle", Color.white, Color(0.0, 0.2, 0.2)]
    ])
    .font_(Font("Futura", 18))
    .action_({
    arg obj;
  	if(
    		obj.value == 1,
    		{
    			\which, 1
    		},
    		
    		obj.value == 2,
    		{
    			\which, 2
    		},
    		obj.value == 3,
    		{
    			\which, 3
                    },
             obj.value == 4
    		{
    			\which, 4
                    }
    });




    ~whichbutton2 = Button(w, Rect(100, 100, 50, 30))
    .states_([
        ["OFF", Color.white, Color(0.0, 0.2, 0.2)],
        ["lowpass", Color.white, Color(0.0, 0.2, 0.2)],
        ["highpass", Color.white, Color(0.0, 0.2, 0.2)],
        ["bandpass", Color.white, Color(0.0, 0.2, 0.2)],
    ])
    .font_(Font("Futura", 18))
    .action_({
    	arg obj;
    	if(
    		obj.value == 1,
    		{
    			\which2, 1
    		},
    		
    		obj.value == 2,
    		{
    			\which2, 2
    		},
    		obj.value == 3,
    		{
    			\which, 3
                     }
    	})
    });




    ~button = Button(w, Rect(700, 498, 50, 30))
    .states_([
    	["ON", Color.white, Color(0.0, 0.2, 0.2)],
    	["OFF", Color.white, Color(0.0, 0.75, 0.55)]
    ])
    .font_(Font("totally bogus", 18))
    .action_({
    	arg obj;
    	if(
    		obj.value == 1,
    		{
    			~envelope1 = Synth.new(
    				\envelope,
    				[
    					\s, ~startenvelope
    					\f, ~finishenvelope
    					\freq, 65.41,
    					\amp, ~sineampslider.value.linexp(0,1,0.1, 1.0),
    					\width, ~pulsewidthslider
    					\freqfilter, ~freqfilter
    					\which, ~whichbutton1
    					\which2, ~whichbutton2
    				]
    			).register;
    		},
    		{~envelope1.free}
    	)
    });
  1. Your envelope start/stop buttons use if(condition, action) but should be if(condition, { action }) (curly braces are not optional). Or, the equivalent if(condition) { action } (looks more like other languages).

  2. The “which” buttons use if for multiple conditions – but if accepts only one condition and two branches. So this isn’t doing what you think.

For multiple conditions, use one of these:

case
{ first condition } { action }
{ second condition } { action }
{ third condition } { action }
{ if an odd number of functions, the last is a default to fire if all conditions were false };

Or (you can use this here because button values are always integers – switch is sensitive to datatype):

switch(value,  // i.e. view.value
    // match value === 0
    0, { first action },
    // match value === 1, etc
    1, { second action },
    2, { third action },
    { final odd numbered function is a default action }
);

hjh

Thanks for your help!

I have this for my which button:

~whichbutton1 = Button(w, Rect(100, 100, 50, 30))
.states_([
	["OFF", Color.white, Color(0.0, 0.2, 0.2)],
    ["sine", Color.white, Color(0.0, 0.2, 0.2)],
    ["saw", Color.white, Color(0.0, 0.2, 0.2)],
	["pulse", Color.white, Color(0.0, 0.2, 0.2)],
	["triangle", Color.white, Color(0.0, 0.2, 0.2)]
])
.font_(Font("Futura", 18))
.action_({
	arg obj;
	case
		{obj.value == 1} {\which, 1}
		{obj.value == 2} {\which, 2}
		{obj.value == 3} {\which, 3}
		{obj.value == 4} {\which, 4};
});

then I get this syntax error message:

ERROR: syntax error, unexpected ',', expecting '}'
  in interpreted text
  line 13 char 27:

  		{obj.value == 1} {\which, 1}
                            ^
  		{obj.value == 2} {\which, 2}
-----------------------------------
ERROR: Command line parse failed
-> nil

The arrow should be pointing toward the “==” next to the “1”. Not sure why the arrow moved to the “\which”

And I’m confused about your first point. Isn’t my conditional in the format you suggested? I see a curly bracket after the “obj.value == 1,”

if(
    		obj.value == 1,
    		{
    			~envelope1 = Synth.new(
    				\envelope,
    				[
    					\s, ~startenvelope
    					\f, ~finishenvelope
    					\freq, 65.41,
    					\amp, ~sineampslider.value.linexp(0,1,0.1, 1.0),
    					\width, ~pulsewidthslider
    					\freqfilter, ~freqfilter
    					\which, ~whichbutton1
    					\which2, ~whichbutton2
    				]
    			).register;
    		},
    		{~envelope1.free}
    	)
    });

This part isn’t:

    	if(
    		~envelope1.isPlaying,
    	~envelope1.set(\s, cf);
    	);

case { obj.value == 1 } { \which, 1 }

The problem is, \which, 1 is not an action.

What do you want to do with those items? You want to use those as arguments to a synth set message.

So the action is setting the synth.

case { obj.value == 1 } { ~envelope1.set(\which, 1) } ...

hjh

I really appreciate your help, I almost have it. I keep getting:

FAILURE IN SERVER /n_set Node 1040 not found

for the “which buttons”.

Then I get:

*** ERROR: SynthDef not found
FAILURE IN SERVER /s_new SynthDef not found

For when I hit the “on/off” button

SynthDef.new(\envelope1, ...)

but

~envelope1 = Synth.new(\envelope, ...)

hjh

Sorry I’m confused. Do I take out the “~envelope1 =” and change it to “SynthDef.new(\envelope,” in the on/off button code? I tried changing it to “SynthDef” but I got error messages. I did catch your previous message, and I would like to retrigger the synth, not initiate a new synth each time I hit the button.

~button = Button(w, Rect(100, 498, 50, 30))
.states_([
	["C", Color.white, Color(0.0, 0.2, 0.2)],
	["C", Color.white, Color(0.0, 0.75, 0.55)]
])
.font_(Font("totally bogus", 18))
.action_({
	arg obj;
	if( obj.value == 1,
		{
			SynthDef.new(
				\envelope1,
				[
					\s, ~startenvelope,
					\f, ~finishenvelope,
					\freq, 65.41,
					\amp, ~ampslider.value.linexp(0,1,0.1, 1.0),
					\width, ~pulsewidthslider,
					\freqfilter, ~freqfilter,
					\which, ~whichbutton1,
					\which2, ~whichbutton2,
				]
			).register;
		},
		{~envelope1.free}
	)
});
)

Sorry I’m confused. Do I take out the “~envelope1 =” and change it to “SynthDef.new(\envelope,” in the on/off button code?

No… it’s just that the SynthDef and Synth names don’t match. That’s why you were getting a “SynthDef not found” error.

You already know how to create a SynthDef and play a Synth from it:

SynthDef(\envelope1, { ... }).add;

x = Synth(\envelope1, [... args...]);

This does not change just because GUI objects are now involved. You still need to use SynthDef in the definition place and Synth in the playing place, and you still need to save your Synth in a variable (so, no, don’t take out ~envelope1 = ...).

I tried changing it to “SynthDef” but I got error messages

In any other context, if you wrote SynthDef(\name, [... arg list...]), you would not be surprised to encounter errors. Even though GUI is a new context for you, you can still make use of principles that you already learned.

I did catch your previous message, and I would like to retrigger the synth, not initiate a new synth each time I hit the button.

Your code is actually written (AFAICS, mostly correctly) for initiating a new synth.

First thing for retriggering is that you do not want the envelope to free the synth. (If the envelope frees the synth, then there is nothing to retrigger.) So if you really want to retrigger, doneAction: 2 is not correct for this use case – take it out.

Second thing is that the “on” button should retrigger, but you wrote Synth.new into the on button. That’s not retriggering – that’s a new synth (which should work with doneAction: 2 in the def btw). So the on button should set a gate or trigger argument to 1. But your SynthDef doesn’t have a gate or trigger (making me doubt that retriggering is really what you have in mind).

Probably the easiest thing to do now is to just keep going with the new-synth model, as is.

hjh

Hm, on second thought, there is one good reason to switch it to a retrigger model, rather than a new-synth model: In the latter, if you set controls while the synth isn’t playing, those values might be forgotten when you start the new synth. (But, if you’re retriggering, then the single “forever” synth would remember values whether the envelope is opened or closed.)

If you want to go that route, I’d strongly suggest to get the logic working outside of the GUI first.

  1. Currently the envelope is fixed-duration. So you would need to add a t_trig = 0 argument to the SynthDef, and EnvGen.kr(Env([0,1,0], [s, f]), t_trig) (no doneAction because retriggering means the synth should persist).

  2. Create ~envelope1 early (which your code is already doing).

  3. “On” action = ~envelope1.set(\t_trig, 1). And, in this case, you wouldn’t need an “off” action because the envelope is fixed-duration (you don’t have control over when it ends). The envelope will close by itself.

(
SynthDef.new(\envelope1, {
	arg s, f, freq, amp, width, which, which2, freqfilter, t_trig = 0;
	var env, selectsig, selectfilter;
	env = EnvGen.kr(Env([0,1,0], [s, f]), t_trig);
	selectsig = Select.ar(which, [SinOsc.ar(freq, 0, amp), Saw.ar(freq, amp), Pulse.ar(freq, width, amp), LFTri.ar(freq, 0, amp)]) * env;
	selectfilter = Select.ar(which2, [BLowPass.ar(selectsig, freqfilter), BHiPass.ar(selectsig, freqfilter), BBandPass.ar(selectsig, freqfilter)]);
	Out.ar(0, selectfilter.dup);
}).add;
)

// '.register' because you're using isPlaying elsewhere
~envelope1 = Synth.new(\envelope1, [\s, 1, \f, 1, \freq, 400, \amp, 0.1, \which, 2, \width, 0.5, \which2, 0, \freqfilter, 200]).register;

// play it
~envelope1.set(\t_trig, 1);

With that test, the basic logic is OK.

Then, to GUIfy it, put the “play” bit ~envelope1.set(\t_trig, 1); into the “on” button action.

hjh

Thanks, that makes sense. I changed my code to this:

~button = Button(w, Rect(100, 498, 50, 30))
.states_([
	["C", Color.white, Color(0.0, 0.2, 0.2)],
	["C", Color.white, Color(0.0, 0.75, 0.55)]
])
.font_(Font("totally bogus", 18))
.action_({
	arg obj;
	if( obj.value == 1,
		{
			~envelope.set(\t_trig, 1)};
	)
});
)

I’m not getting any error messages about my synth anymore, but when I use the “which” buttons, I’m still getting:
FAILURE IN SERVER /n_set Node 1040 not found

And I have a feeling I wasn’t supposed to take out the rest of the arguments and replace it with just the t_trig argument.

No, certainly not… I had said “add” a t_trig argument, which implies keeping the other ones. And I posted a whole working retriggering SynthDef.

If ~envelope.set(\t_trig, 1); is working but the “which” set statements are not, then there is some difference between them. But I don’t know the current state of your “which” code so I don’t know.

hjh

I apologize… I feel like I should know this, but I can’t get passed the syntax. I have this code now:

~button = Button(w, Rect(100, 498, 50, 30))
.states_([
	["C", Color.white, Color(0.0, 0.2, 0.2)],
	["C", Color.white, Color(0.0, 0.75, 0.55)]
])
.font_(Font("totally bogus", 18))
.action_({
	arg obj;
	if( obj.value == 1,
		{
			~envelope1 = Synth.new(
				\envelope1,
				[
					\s, ~startenvelope,
					\f, ~finishenvelope,
					\freq, 65.41,
					\amp, ~ampslider.value.linexp(0,1,0.1, 1.0),
					\width, ~pulsewidthslider,
					\freqfilter, ~freqfilter,
					\which, ~whichbutton1,
					\which2, ~whichbutton2,
				]
			).register;
		~envelope1.set(\t_trig, 1)
		},
		{~envelope1.free}
	)
});

And I get this error message when I hit the “ON”:

*** ERROR: SynthDef  not found
FAILURE IN SERVER /s_new SynthDef not found
FAILURE IN SERVER /n_set Node 1054 not found

My “which” buttons look like:

~whichbutton1 = Button(w, Rect(100, 100, 50, 30))
.states_([
	["OFF", Color.white, Color(0.0, 0.2, 0.2)],
    ["sine", Color.white, Color(0.0, 0.2, 0.2)],
    ["saw", Color.white, Color(0.0, 0.2, 0.2)],
	["pulse", Color.white, Color(0.0, 0.2, 0.2)],
	["triangle", Color.white, Color(0.0, 0.2, 0.2)]
])
.font_(Font("Futura", 18))
.action_({
	arg obj;
	case
	    {obj.value == 1} {~envelope1.set(\which, 1)}
		{obj.value == 2} {~envelope1.set(\which, 2)}
		{obj.value == 3} {~envelope1.set(\which, 3)}
		{obj.value == 4} {~envelope1.set(\which, 4)};
});


~whichbutton2 = Button(w, Rect(100, 300, 80, 30))
.states_([
	["OFF", Color.white, Color(0.0, 0.2, 0.2)],
    ["lowpass", Color.white, Color(0.0, 0.2, 0.2)],
    ["hipass", Color.white, Color(0.0, 0.2, 0.2)],
	["bandpass", Color.white, Color(0.0, 0.2, 0.2)],
])
.font_(Font("Futura", 18))
.action_({
	arg obj;
	case
	    {obj.value == 1} {~envelope1.set(\which2, 1)}
		{obj.value == 2} {~envelope1.set(\which2, 2)}
		{obj.value == 3} {~envelope1.set(\which2, 3)}
});

I feel like I should know this, but I can’t get passed the syntax

TBH this isn’t really a syntax problem.

I’ll go back to one thing I said before: take the logic out of the GUI and make sure all the actions are doing what they should, first. Then put the interface around the functioning logic.

I’ll extract your current logic, and add a couple of OSC receivers to track synth state:

(
SynthDef.new(\envelope1, {
	arg s, f, freq, amp, width, which, which2, freqfilter;
	var env, selectsig, selectfilter;
	env = EnvGen.kr(Env([0,1,0], [s, f]), doneAction:2);
	selectsig = Select.ar(which, [SinOsc.ar(freq, 0, amp), Saw.ar(freq, amp), Pulse.ar(freq, width, amp), LFTri.ar(freq, 0, amp)])*env;
	selectfilter = Select.ar(which2, [BLowPass.ar(selectsig, freqfilter), BHiPass.ar(selectsig, freqfilter), BBandPass.ar(selectsig, freqfilter)]);
	Out.ar(0, selectfilter.dup);
}).add;
)

// debugging: track synth on/off
(
OSCdef(\n_go, { |msg|
	"Synth % started at %\n".postf(msg[1], SystemClock.seconds)
}, '/n_go', s.addr);

OSCdef(\n_end, { |msg|
	"Synth % stopped at %\n".postf(msg[1], SystemClock.seconds)
}, '/n_end', s.addr);
)

// now, create your synth
~envelope1 = Synth.new(\envelope1, [\s, 1, \f, 1, \freq, 400, \amp, 0.5, \which, 2, \width, 0.5, \which2, 0, \freqfilter, 200]);

// prints:
Synth 1000 started at 180.013975417
Synth 1000 stopped at 182.010628047

^^ All of that is part of the initialization in your code. Except the OSCdefs, I haven’t changed a thing.

// ok, now pretend you hit the \which button
~envelope1.set(\which, 1);
-> Synth('envelope1' : 1000)
FAILURE IN SERVER /n_set Node 1000 not found

Now, why would it do that?

“Synth 1000 stopped at 182.010628047”

If the synth has stopped, then you can’t set it.

Why did it stop? Because you told it to: doneAction: 2.

This is kind of like those times in lessons where I catch a student, say, writing a Gm7 chord in bar 27 but accidentally writing B-natural in one instrument – because they didn’t have a clear idea of the harmony in that bar, when writing some of the parts, they forgot which kind of G chord it is.

Here, you have two parts of the code that disagree on the design: The SynthDef, and ~button, think that the synth will exist only while it’s playing, but the parameter buttons think it will always be there.

And the solution is to choose one and be consistent about it. (Note that this is before considering GUI code at all.)

For this use case, I think you want the parameter buttons to be able to choose values even when there’s silence. So it’s easier to keep the synth running continuously (so you can set it at any time).

So, make the SynthDef continuous, and trigger-able:

(
SynthDef.new(\envelope1, {
	arg s, f, freq, amp, width, which, which2, freqfilter,
	// for retriggering (continues 'arg')
	t_trig = 0;
	var env, selectsig, selectfilter;
	// 1. DO NOT free synth at envelope end! No doneAction!!
	// 2. Use the trigger
	env = EnvGen.kr(Env([0, 1, 0], [s, f]), t_trig);
	selectsig = Select.ar(which, [SinOsc.ar(freq, 0, amp), Saw.ar(freq, amp), Pulse.ar(freq, width, amp), LFTri.ar(freq, 0, amp)])*env;
	selectfilter = Select.ar(which2, [BLowPass.ar(selectsig, freqfilter), BHiPass.ar(selectsig, freqfilter), BBandPass.ar(selectsig, freqfilter)]);
	Out.ar(0, selectfilter.dup);
}).add;
)

~envelope1 = Synth.new(\envelope1, [\s, 1, \f, 1, \freq, 400, \amp, 0.5, \which, 2, \width, 0.5, \which2, 0, \freqfilter, 200]);

Synth 1001 started at 310.924793428

^^ You don’t hear anything yet (the envelope hasn’t been triggered), but the synth exists (no “Synth *** stopped”).

// trigger: plays sound, no on/off change
~envelope1.set(\t_trig, 1);

// ok, now pretend you hit the \which button: no error
~envelope1.set(\which, 1);

// trigger: sound is different
~envelope1.set(\t_trig, 1);

So that’s the basic logic. Then put these elements into the GUI.

~button does not need to do Synth.new – the synth already exists. Value 1 should just set the trigger. Value 0 should… do nothing. Do NOT free the synth here. (Actually, in this case, you don’t need two states for the button. It’s a fixed-length envelope.)

Your current code for the parameter buttons assumes that the synth is there. I’m suggesting to change the design so that this assumption is met – therefore, you shouldn’t have to change the code for these.

hjh

1 Like

Now… there’s one more problem:

~whichbutton1 = Button(w, Rect(100, 100, 50, 30))
.states_([
	["OFF", Color.white, Color(0.0, 0.2, 0.2)],
	["sine", Color.white, Color(0.0, 0.2, 0.2)],
	["saw", Color.white, Color(0.0, 0.2, 0.2)],
	["pulse", Color.white, Color(0.0, 0.2, 0.2)],
	["triangle", Color.white, Color(0.0, 0.2, 0.2)]
])

0 = off, 1 = sine, 2 = saw, 3 = pulse, 4 = triangle.

But in the SynthDef: Select.ar(which, [SinOsc.ar(freq, 0, amp), Saw.ar(freq, amp), Pulse.ar(freq, width, amp), LFTri.ar(freq, 0, amp)]). Remember that arrays index from 0. So this is 0 = sine, 1 = saw, 2 = pulse, 3 = triangle (and there is no 4), not consistent with the GUI.

If you want 0 to be “off,” then the first element of the Select array should be a silent signal: Select.ar(which, [DC.ar(0), SinOsc.ar(freq, 0, amp), Saw.ar(freq, amp), Pulse.ar(freq, width, amp), LFTri.ar(freq, 0, amp)])

Similar for the filter: if 0 = no filter, then the first array element should pass the signal through directly: Select.ar(which2, [selectsig, BLowPass.ar(selectsig, freqfilter), BHiPass.ar(selectsig, freqfilter), BBandPass.ar(selectsig, freqfilter)])

hjh

Also note redundancy:

.action_({
	arg obj;
	case
	    {obj.value == 1} {~envelope1.set(\which, 1)}
		{obj.value == 2} {~envelope1.set(\which, 2)}
		{obj.value == 3} {~envelope1.set(\which, 3)}
		{obj.value == 4} {~envelope1.set(\which, 4)};
});

And, obj.value == 0 will send nothing in this formulation.

You can just do

.action_({
	arg obj;
	~envelope1.set(\which, obj.value);
});

hjh

Thank you so much, I have it working now! I learned a lot from this. I appreciate the time you took to explain the logic to me, that was helpful.