A WYSIWYG GUI Editor for SuperCollider?

Hello everyone,
I hope you’re all doing well. :slightly_smiling_face:
I know this topic has probably been discussed before, and I’m aware that GUI designers/Editors for SuperCollider may have existed in the past, and that this might not be a top priority for the community.

However, as a SuperCollider user, having a GUI design environment similar to WYSIWYG GUI Editor in Cabbage has always been a wish of mine: something that would let us place interface elements easily, save time on GUI work, and focus more on the core code.

I’m probably too much of a beginner to contribute something like this to the community myself, but I wanted to ask for your thoughts. Is there any ongoing project or existing tool that provides this kind of functionality?

Thank you,
Mass

You could look into NdefGUI, TdefGUI, PdefGUI and ProxyMixer.
https://doc.sccode.org/Classes/JITGui.html

There is also a manager for sclang’s application-level menu
https://doc.sccode.org/Classes/MainMenu.html

And there is also Batuhan Bozkurt’s patching interface for SuperCollider (don-t. know if it still works).
original - GitHub - earslap/Hadron: A composition and live performance framework for SuperCollider
james harkins fork - GitHub - jamshark70/hadron: A fork (from sourceforge svn) of Batuhan Bozkurt's patching interface for SuperCollider
Introducing new Quark, Hadron!

and i think crucial-lib also has some gui-classes:

1 Like

Thanks mstep

I’m already using JITGui and although they are fine , but very limited in terms of something like WYSIWYG. Gui2 is something new and fine but still limited.

the rest of the list , seems more or less deprecated.Some of them are 13 years old!

Is it possible to create a GUI Designer/Editor suite with JavaScript and HTML in browser? to design and get the code and use in super collider?
I know it might be a lazy thing to do but it would be pretty awesome .
WsGUI is something close but different.

Hi,

I think it’s worth considering AI tools for this task. I’ve seen impressive things done by students. Although, it’s a double-edged sword. You need to have some fluency in SC to polish the solutions you get as they might not be perfect (though generated SC code now looks much better than only a short while ago, it’s amazing!).
Also, you could consider Processing for more dynamic gui stuff and link it with SC. Again, AI helps and again, it needs some familiarity with Processing then.

Aside from that, you might want to check VarGui from the miSCellaneous_lib quark extension.

best

Daniel

1 Like

for those interested in this class:
there has to be a window in front, otherwise all menu-items are greyed out and inactive (at least here on macos 15.6.1). so the example from the help-file gets:

(
~testTone = MenuAction("Test Tone", {
    { SinOsc.ar(400) * 0.1 }.play;
});

MainMenu.register(~testTone, "Tests");

Window.new.front;
)

MainMenu.unregister(~testTone); // to remove

the info is also documented here:

1 Like

Thanks dkmayer

You’re right , it really is a double-edged sword.
I’ve realized that relying too much on AI can take away some of the creativity involved in designing a GUI and connecting it to the sound. But for simple tasks and everyday exercises, it definitely helps avoid repetitive work and saves time.
Thanks again for your helpful input!
WYSIWYG(1)

Hi,

From the first post I thought maybe you were looking for a Max/PD/Audiomulch etc solution where you place and “connect” GUI elements that have audio functions. But if you’re looking for something to sketch out GUI elements by placing and dragging around GUI elements and get the code for it (as Rects) then I have something that could be of use, looks like this:

It should be pretty self-explanatory - drag GUI elements from the “palette” window to the “sketch” with cmd-drag, modify them and in the end post the result to the post window.

I have found it useful to sketch out odd looking GUIs with a lot of space between the elements, like for ex a gamepad. Or lowering the alpha value of the sketch window and place it above another window where I need to find the exact Rect values of something.

(
// GUI SKETCHER - blindmanonacid
var win, guis, v;
var snap= 10, sf;
var a, ax= 0, ay= 0, bx= 0, by= 0;
var d, alphaSpec, previousBounds;
var w;

alphaSpec= [0, 1, -4].asSpec;
sf= Array.fill(99, {|i| (pi*i).wrap(-1.0, 1.0)*((i-99).abs/99) });

win= Window("Palette", Rect(Window.screenBounds.width-280, Window.screenBounds.height, 280, 500)).front;

StaticText(win, Rect(0, 0, win.bounds.width, 40))
.string_("Drag&drop to sketch:")
.font_(Font.default.size_(24))
.align_(\center);

v= View(win, Rect(0, 40, 280, 260));
v.decorator = FlowLayout(win.view.bounds);
v.background= Color.grey.alpha_(0.3);

guis= [
Slider(v, 20@100).value_(0.2),
RangeSlider(v, 20@100).lo_(0.3).hi_(0.7),
Slider2D(v, 100@100),
MultiSliderView(v, 100@100).size_(30).isFilled_(1).value_(Array.exprand(30, 0.2, 0.9)),
Slider(v, 100@20).value_(0.2),
RangeSlider(v, 100@20).lo_(0.3).hi_(0.7),
StaticText(v, 60@20)
	.string_("staticText")
	.background_(Color.grey.alpha_(0.2)),
PopUpMenu(v, 100@20).items_(["pop", "up", "menu", "with", "some", "items"]),
TextField(v, 60@20).string_("textField"),
NumberBox(v, 60@20),
Button(v, 60@40).states_([["but"]]),
Knob(v, 40@40),
ListView(v, 60@40).items_(["list", "view", "with", "some", "items"]),
SoundFileView(v, 120@40).data_(sf),
EnvelopeView(v, 120@40).value_([[0.0, 0.1, 0.5, 1.0],[0.1,1.0,0.8,0.0]]).thumbSize_(12)
];

guis.do({|it|
	it.beginDragAction_({|v, x, y| v });
});

StaticText(win, Rect(0, 310, 280, 100))
.align_(\center)
.string_("cmd-drag objects to copy,
click+drag to move,
alt-click+drag to resize,
backspace to delete,
double-click StaticText to change text")
.font_(Font.default.size_(15));

w= Window("sketch", Rect(Window.screenBounds.width-780, Window.screenBounds.height, 400, 400)).front;

d= DragSink(w, Rect(0, 0, w.bounds.width, w.bounds.height))
.background_(Color.grey.alpha_(0.0))
.receiveDragHandler_({
	|v, x, y|
	var drag, gui;
	drag= View.currentDrag;
	//drag.class.postln;
	//[drag, v, x, y].postln;
	gui= drag.class.new(w, Rect((x-bx).round(snap),(y-by).round(snap),drag.bounds.width,drag.bounds.height));

	gui.mouseDownAction_({|v, x, y, mod, but, click|
	//[x, y].postln;
	a= v;
	ax= x;
	ay= y;
		if(click==2, {
			if(v.class == StaticText, {
				TextField(w, Rect(gui.bounds.left, gui.bounds.top, 120, 20), false, false)
				.focusLostAction_({|v| v.remove })
				.string_("input text, hit enter")
				.focus
				.keyDownAction_({|v, c, mod, uni, key|
					if(key==36,
						{gui.string= v.string;
							v.remove; });
				});
			});
		});
	false;
	});
	gui.mouseMoveAction_({|v, x, y, mod|
		if (mod.isAlt, {
			v.resizeTo(x.round(snap).clip(1, 10000), y.round(snap).clip(1, 10000));
			if (v.class == StaticText, {
				v.font= Font.default.size_(v.bounds.height*0.6);
			});
		});
	false;
	});
	gui.beginDragAction_({|v, x, y|
		bx= x; by= y; v; });
	{gui.items= drag.items}.try;
	{gui.data= sf}.try;
	{gui.string= drag.string}.try;
	{gui.value= drag.value}.try;
	{gui.x= drag.x; gui.x= drag.x; }.try;
	{gui.lo= drag.lo; gui.hi= drag.hi; }.try;
	{gui.background= drag.background; }.try;
	{gui.thumbSize= 12; }.try;
	{gui.isFilled= 1; }.try;
	gui.canFocus= true;

	gui.keyDownAction= {|v, c, mod, uni, key|
		if(key == 51, {v.remove});
	};
	bx= 0; by= 0;
});
w.view.mouseMoveAction_({|v, x, y, mod|
	//[x, y].postln;
	if (mod.isAlt.not, {
		a.moveTo((x-ax).round(snap), (y-ay).round(snap));
	});
	if(previousBounds != a.bounds, {
		(a.class.asString++"(w, "++a.bounds.asString++")").postln;
	});
	previousBounds= a.bounds;
});

w.view.onResize= ({d.resizeTo(w.bounds.width, w.bounds.height)});

Button(win, Rect(20, win.bounds.height-25, 240, 20))
.states_([["post sketch content"]])
.action_({
	var winVar= "w", guis;

	guis= w.view.children;
	guis.removeAt(0);
	guis.sort({|a, b| a.bounds.left > b.bounds.left });
	guis.sort({|a, b| a.bounds.top < b.bounds.top });

	"*** Copy/paste window contents ***".postln;
	"(".postln;
	(winVar++"= Window("++"title".asCompileString++","++w.bounds.asString++");").postln;

	guis.do({|it, i|
		(it.class.asString++"("++winVar++","++ it.bounds.asString++");").postln;
	});
	")".postln;
});

StaticText(win, Rect(180, win.bounds.height-50, 60, 20))
.string_("Snap to:");

NumberBox(win, Rect(230, win.bounds.height-50, 30, 20))
.step_(1)
.clipLo_(1)
.value_(snap)
.align_(\center)
.action_({|v| snap= v.value });

StaticText(win, Rect(20, win.bounds.height-80, 60, 20))
.string_("Alpha:");

Slider(win, Rect(60, win.bounds.height-77, 200, 15))
.value_(1)
.action_({|v| w.alpha= alphaSpec.map(v.value) });

CheckBox(win, Rect(20, win.bounds.height-50, 200, 15))
.string_("Show ruler")
.action_({|v|
	if(v.value, {
		w.drawFunc = {
			Pen.strokeColor = Color.black.alpha_(0.5);
			((w.bounds.height/10).asInteger).do({|i| //vertical
		Pen.moveTo(0@(i*10));
		if(i%10 == 0, {
			Pen.lineTo(8@(i*10));
			Pen.stringAtPoint((10*i).asString,10@((i*10)-5), Font("monaco", 8), Color.black.alpha_(0.8));
			Pen.strokeColor = Color.grey.alpha_(0.2);
			Pen.lineTo(w.bounds.width@i*10);
			Pen.strokeColor = Color.black.alpha_(0.5);
		},{
			Pen.lineTo(5@(i*10));
		});
	});
	((w.bounds.width/10).asInteger).do({|i| //horizontal
		Pen.moveTo((i*10)@0);
		if(i%10 == 0, {
			Pen.lineTo((i*10)@8);
					Pen.stringAtPoint((10*i).asString,((i*10)-5)@10, Font("monaco", 8), Color.black.alpha_(0.8));
			Pen.strokeColor = Color.grey.alpha_(0.2);
			Pen.lineTo((i*10)@w.bounds.height);
			Pen.strokeColor = Color.black.alpha_(0.5);
		},{
			Pen.lineTo((i*10)@5);
		});
	});
Pen.stroke;
};
		w.refresh;
	}, {
		w.drawFunc = {}; w.refresh;
	});
});

w.onClose= { {win.close}.try; };
win.onClose= { {w.close}.try; };


)
2 Likes

Just for a heads up, SC 3.15 will probably have a way to include GitHub - capital-G/sclang_websocket: Websocket implementation for sclang using gluon interface which introduces the ability to exchange data with a web browser using web sockets, which would allow to tap into writing reactive GUIs using HTML/JS.

1 Like

This is exactly what I was looking for!
Of course, to have something closer to a «patcher-style» environment, we also need to bring UGens into it and that take a lot of time, and it might even interfere with the creative process… I’m not sure!
Actually, what I’m really looking for is a way to design simple hardware-style synthesizers, similar to the workflow shown in this tutorial.
Could you tell me how It works ? And have you tested it across different platforms?

Thanks again . this looks very helpful!

With browsers and web Socket, it becomes possible to explore much more dynamic approaches to GUI design.
I’m definitely looking forward to this .

I have only used it in MacOS but it should work fine on Linux/Win. If I remember correctly the modifier for drag and drop there is Ctrl, so that would be the only difference.

As for “how it works”, do you mean the drag and dropping part? Well the GUIs on the “palette” side are in an array called gui, when it starts to be dragged the beginDragAction_ is called that returns its View

guis.do({|it|
	it.beginDragAction_({|v, x, y| v });
});

The “sketch” window has a DragSink covering it. When it receives the dragged item (the View of the dragged GUI) I can query which class it is with .class and such make a new instance of that class at the x/y mouse coordinates given by receiveDragHandler_

d= DragSink(w, Rect(0, 0, w.bounds.width, w.bounds.height))
.background_(Color.grey.alpha_(0.0))
.receiveDragHandler_({
	|v, x, y|
	var drag, gui;
	drag= View.currentDrag;
	gui= drag.class.new(w, Rect( x, y, drag.bounds.width, drag.bounds.height));
});

(code is shortened for readability)