Newbie problems

Hi, so I’m very new to SC so this is probably an incredibly stupid problem I am encountering but I cannot, for the love of myself, understand how to solve it. I’m exercising with the code and trying to understand scheduling, now, my problem is I created a GUI where a ‘start/stop’ button should manage a routine, only that whenever I press the button sclang.exe stops responding. I guess I’m missing some vital details and I just created an infinite loop so if anyone has a minute to spend helping a kind, and surely dumb, soul I’ll be very glad. Next is the entire code and as you can see it’s very basic because, again, I just started from zero with SuperCollider


SynthDef(\mySynth, {
    |freq = 440, amp = 0.5, atk = 0.01, rel = 1|
    var sig, env;

    sig = SinOsc.ar(freq);

    sig = LPF.ar(sig, 2000);

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

    sig = sig * env * amp;

    Out.ar(0, sig!2);
}).add;

~synthRoutine = nil;

// GUI
(
var window, startButton, freqSlider, ampSlider, atkSlider, relSlider;

window = Window("Synth Controller", Rect(100, 100, 300, 400));
window.view.decorator = FlowLayout(window.view.bounds);

startButton = Button(window, Rect(0, 0, 280, 20))
    .states_([["Start", Color.black, Color.green], ["Stop", Color.black, Color.red]])
    .action_({ |button|
        if (button.value == 0) {
            button.valueAction = 1;
            ~synthRoutine = Routine({
                loop {
                    if (button.value == 0, {
                        "Stopping routine".postln;
                        ~synthRoutine.stop;
                    });
                    Synth(\mySynth, [
                        \freq, freqSlider.value,
                        \amp, ampSlider.value,
                        \atk, atkSlider.value,
                        \rel, relSlider.value
                    ]);
                    (0.5 + 1.5.rand).wait;
                }
            }).play(AppClock);
        } {
            button.valueAction = 0;
            ~synthRoutine.stop;
        }
    });

freqSlider = Slider(window, Rect(0, 0, 280, 20))
    .value_(440)
    .action_({ |sl| sl.value_(sl.value.clip(20, 2000)); });

StaticText(window, Rect(0, 0, 280, 20)).string_("Frequency");

ampSlider = Slider(window, Rect(0, 0, 280, 20))
    .value_(0.5)
    .action_({ |sl| sl.value_(sl.value.clip(0, 1)); });

StaticText(window, Rect(0, 0, 280, 20)).string_("Amplitude");

atkSlider = Slider(window, Rect(0, 0, 280, 20))
    .value_(0.01)
    .action_({ |sl| sl.value_(sl.value.clip(0.001, 1)); });

StaticText(window, Rect(0, 0, 280, 20)).string_("Attack");

relSlider = Slider(window, Rect(0, 0, 280, 20))
    .value_(1)
    .action_({ |sl| sl.value_(sl.value.clip(0.1, 10)); });

StaticText(window, Rect(0, 0, 280, 20)).string_("Release");

window.front;
)

Don’t use valueAction inside the action function. That’s causing infinite recursion – you click the button, which calls the action function, then the action function calls valueAction, which calls the action function, which calls valueAction.

IMO valueAction is generally to be avoided (but for another reason).

hjh

thank you so much I really didn’t considered it. May I also ask what would be the best way to fix the code? Because I’m not sure (it’s really the first time that I have tried to use a routine so…)

I’d simply delete the valueAction lines.

Clicking a button in SC automatically advances to the next state; there’s no need for you to do it by hand. (And if you did, you’d just use .value = and not valueAction.)

BTW I suggest to avoid valueAction because (IMO) it’s better to design your code so that every operation can be triggered without any GUI at all – then the GUI is a transparent shell over the top of that. One problem in your code now is, if you want to start the process from code at an exact time, you have to call into the Button, which you can do only on AppClock, where timing is not as precise – so you lose functionality by going through the GUI. valueAction is then a sign of embedding key program logic into a GUI = probably a design flaw.

In your case, I’d have a ~start and ~stop function outside the button, and then call these inside the button. I’m not at the computer now but could illustrate later.

hjh

Actually there were more problems –

(
SynthDef(\mySynth, {
    |freq = 440, amp = 0.5, atk = 0.01, rel = 1|
    var sig, env;

    sig = SinOsc.ar(freq);

    sig = LPF.ar(sig, 2000);

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

    sig = sig * env * amp;

    Out.ar(0, sig!2);
}).add;

~synthRoutine = nil;

// GUIs are not for data storage:
// avoid Max-patcher-itis, keep your values in proper vars
~freq = 440;
~amp = 0.5;
~atk = 0.01;
~rel = 1;

~atkSpec = ControlSpec(0.001, 1, \exp);
~relSpec = ControlSpec(0.01, 2, \exp);

~startRoutine = {
    ~synthRoutine = Routine({
        loop {
            // unnecessary to check button state here
            // the button will force-stop the routine
            // so you drop that whole bit and now you
            // don't have to use AppClock anymore

            Synth(\mySynth, [
                \freq, ~freq,
                \amp, ~amp,
                \atk, ~atk,
                \rel, ~rel
            ]);
            (0.5 + 1.5.rand).wait;
        }
    }).play;  // btw: *not* AppClock for musical timing
};

~stopRoutine = {
    ~synthRoutine.stop;
};
)

// GUI
(
var window, startButton, freqSlider, ampSlider, atkSlider, relSlider;

window = Window("Synth Controller", Rect(100, 100, 300, 400));
window.view.decorator = FlowLayout(window.view.bounds);

startButton = Button(window, Rect(0, 0, 280, 20))
    .states_([["Start", Color.black, Color.green], ["Stop", Color.black, Color.red]])
    .action_({ |button|
        // if your action function is much more complex than this,
        // then there's too much logic in it
        if (button.value == 0) {
            ~stopRoutine.value;
        } {
            ~startRoutine.value;
        }
    });

freqSlider = Slider(window, Rect(0, 0, 280, 20))
    .value_(\freq.asSpec.unmap(~freq))
    .action_({ |sl| ~freq = \freq.asSpec.map(sl.value) });

StaticText(window, Rect(0, 0, 280, 20)).string_("Frequency");

ampSlider = Slider(window, Rect(0, 0, 280, 20))
    .value_(\amp.asSpec.unmap(~amp))
    .action_({ |sl| ~amp = \amp.asSpec.map(sl.value) });

StaticText(window, Rect(0, 0, 280, 20)).string_("Amplitude");

atkSlider = Slider(window, Rect(0, 0, 280, 20))
    .value_(~atkSpec.unmap(~atk))
    .action_({ |sl| ~atk = ~atkSpec.map(sl.value) });

StaticText(window, Rect(0, 0, 280, 20)).string_("Attack");

relSlider = Slider(window, Rect(0, 0, 280, 20))
    .value_(~relSpec.unmap(~rel))
    .action_({ |sl| ~rel = ~relSpec.map(sl.value) });

StaticText(window, Rect(0, 0, 280, 20)).string_("Release");

window.front;
)

Also, FlowLayout is a rather old way to do it – Qt Layouts are easier, faster, and adapt to resizing better.

(
var window, startButton, freqSlider, ampSlider, atkSlider, relSlider;

window = Window("Synth Controller", Rect(100, 100, 300, 400));

startButton = Button.new
    .states_([["Start", Color.black, Color.green], ["Stop", Color.black, Color.red]])
    .action_({ |button|
        if (button.value == 0) {
            ~stopRoutine.value;
        } {
            ~startRoutine.value;
        }
    });

freqSlider = Slider.new
    .value_(\freq.asSpec.unmap(~freq))
    .action_({ |sl| ~freq = \freq.asSpec.map(sl.value) });

ampSlider = Slider.new
    .value_(\amp.asSpec.unmap(~amp))
    .action_({ |sl| ~amp = \amp.asSpec.map(sl.value) });

atkSlider = Slider.new
    .value_(~atkSpec.unmap(~atk))
    .action_({ |sl| ~atk = ~atkSpec.map(sl.value) });

relSlider = Slider.new
    .value_(~relSpec.unmap(~rel))
    .action_({ |sl| ~rel = ~relSpec.map(sl.value) });


window.layout = VLayout(
    startButton,
    freqSlider,
    StaticText.new.string_("Frequency").align_(\center),
    ampSlider,
    StaticText.new.string_("Amplitude").align_(\center),
    atkSlider,
    StaticText.new.string_("Attack").align_(\center),
    relSlider,
    StaticText.new.string_("Release").align_(\center)
 );

window.front;
)

hjh

Oh wow, thank you so much for taking the time for all of this, I really appreciate it (also for pointing out other errors)