I created the SerialOSCClient library so that’s what I prefer. I elaborated on the differences between SerialOSCClient and MonoM here: SerialOSCClient for SuperCollider

Also note that the GRMonome* classes - the SerialOSCClient based monome classes - are actually not part of Grrr. If you look in the zip they’re in a different folder. The only grid that’s part of Grrr is the virtual one: GRScreenGrid. I tried to design Grrr as controller agnostic. That’s why I refer to it as a UI lib for grid based controllers.

To make your own controller you just subclass GRController. It’s pretty easy for you to create a controller that uses MonoM, if you like.

The core Grrr lib is quite stable.

The GRMonome* classes and SerialOSCClient lib are fairly young projects. There are a lot of unimplemented features I want to add when I get the time. My previous Grrr monome classes were based on the MonomeSerial protocol.

1 Like

super helpful library! thanks for sharing it.

Lovely stuff @jah

Gonna dig it and report back :slight_smile:

Thanks.

Just for the record. There are issues and design tradeoffs in the current Grrr, namely:

  • Currently only possible to instantiate one GRMonome at a time. Using Grrr with monomes via the serialosc protocol is of importance to me so this will be very much improved.
  • No vari-bright support in the framework, only led on/off. I started this project many years ago and I did not own a vari-bright unit back then.
  • Widgets cannot overlap. Framework could be improved to facilitate this, would require some work. However, there are ways of making pages of widgets.
  • Performance: readability of code has so far been prioritized over speed. Do not expect ultra-high refresh rates.

Still, with Grrr you can throw together stuff quickly. Below you can find a step sequencer with four pages of modulation. Evaluate and press top right-most button to play:

(
s.boot;
s.latency = 0.02;
~grid=GRMonome64.new;
~grid.spawnGui;
~playpos=GRHToggle.newDecoupled(~grid, 0@7, 8, 1, nillable: true).value_(nil);
~pages=GRSwitcher(~grid, 0@1, 8, 6);
~pageselect=GRHToggle(~grid, 0@0, 4, 1).action_({ |view, value| ~pages.value = value });
~transport=GRHToggle(~grid, 6@0, 2, 1);
~transport.action = { |view, value| if (value == 1) { ~player.play } { ~player.stop } };
~scramble=GRButton.newDecoupled(~grid, 4@0).value_(true);
~scramble.buttonPressedAction = { 8.do { |togglenum|
	var mtv= ~pages.currentView;
	if (mtv.toggleValue(togglenum).notNil) {
		mtv.setToggleValue(togglenum, 6.rand);
	}
} };
~shuffle=GRButton(~grid, 5@0);

~degrees=GRMultiToggleView(~pages, 0@0, 8, 6, nillable: true);
~degrees.valuesAreInverted = true;
~octaves=GRMultiToggleView(~pages, 0@0, 8, 6);
~octaves.valuesAreInverted = true;
~cutoffs=GRMultiToggleView(~pages, 0@0, 8, 6);
~cutoffs.valuesAreInverted = true;
~variation=GRMultiToggleView(~pages, 0@0, 8, 6);
~variation.valuesAreInverted = true;

8.do { |i| ~octaves.setToggleValue(i, 3) };

SynthDef(
	\grrr_test,
	{ |freq, cutoff_freq, variation, gate=1, amp|
		var sig;
		var osc1 = VarSaw.ar(freq, width: variation.linlin(0, 1, 0, 0.25));
		var osc2 = VarSaw.ar(((freq.cpsmidi)-12).midicps, width: 0);
		var amp_env = EnvGen.ar(Env.perc(0.01, variation.linlin(0, 1, 0.9, 0.1)), gate, amp, doneAction: 2);
		var filter_env = EnvGen.ar(Env.perc(0.01, variation.linlin(0, 1, 0.01, 0.6)), gate);
		sig = RLPF.ar((osc1 * 0.7) + (osc2 * 0.3), cutoff_freq.linexp(0, 1, 20, 4000) + filter_env.linexp(-1, 1, 20, 3000)) * amp_env;
		Out.ar(0, sig ! 2);
	}
).add;

~multi_toggle_view_as_prout = { |view, func|
	Prout({
		loop {
			8.do { |togglenum| func.(view.toggleValue(togglenum)).yield }
		}
	})
};

~pattern=Pbind(*[
	\instrument, \grrr_test,
	\degree, ~multi_toggle_view_as_prout.(~degrees, { |degree| if (degree.notNil, degree, \rest) }),
	\octave, ~multi_toggle_view_as_prout.(~octaves, { |toggle_value| toggle_value+2 }),
	\cutoff_freq, ~multi_toggle_view_as_prout.(~cutoffs, { |toggle_value| toggle_value/6 }),
	\variation, ~multi_toggle_view_as_prout.(~variation, { |toggle_value| toggle_value/6 }),
	\dur, Prout({ loop {
		if (~shuffle.value) { 0.16.yield; 0.1.yield } { 0.13.yield; 0.13.yield }
	}}),
	\playpos, Prout( { loop { 8.do { |playpos| (~playpos.value = playpos).yield } } })
]);
~player=~pattern.asEventStreamPlayer;
)

Same thing as a gist: https://gist.github.com/antonhornquist/82e39ba6a2f332674ceee18f638618fa

Hi,
I was
How do I manipulate elements in a GRMultiToggleView? I want to be able to turn the LEDs off from SC but setToggleValue and setToggleValueAction both fail. I was basically trying to turn this code verticle and have the falling LEDs represent nodes ending but it doesn’t seem to work at all so far:

(
c.togglePressedAction = nil;
c.toggleValuePressedAction = { |view, value| // toggleValuePressedAction is triggered anytime a toggle value is pressed
var playFunc;
playFunc = { |degree| (degree: degree, sustain: 0.05).play };
r.stop;
r = Routine({
loop { //counts down from pressed key and plays descending line
(value…0).do { |val|
0.1.wait;
playFunc.(val);
view.value = val;
};
};
}).play;
};
)

Maybe a general help file for all methods would help?

thanks

Also with a multitoggleview how do you do a b.action = {|value| } and use the value for each index?

Hi,

Yeah I need to write docs.

Most GRMultiToggleView actions include an index that refer to affected toggle. See example below where I’ve also transposed each toggle and included a way to stop cycles. Let me know if this clears things up.

a=GRScreenGrid.new;
c=GRMultiToggleView.newDecoupled(a, 0@0, 8, 8);
c.nillable = true;
c.numToggles.do { |index| c.setToggleValue(index, nil) };
s.boot;
x = Array.newClear(c.numToggles);
(
c.toggleValuePressedAction = { |view, index, value|
    var playFunc;
    playFunc = { |degree, mtranspose| (degree: degree, mtranspose: mtranspose, octave: 4, sustain: 0.05).play };
    x[index].stop;
    if (value == 0) {
        view.setToggleValue(index, nil);
    } {
        x[index] = Routine({
            loop {
                (value..0).do { |val|
                    0.1.wait;
                    playFunc.(val, index*2);
                    view.setToggleValue(index, val);
                };
            };
        }).play;
    }
};
)

You can use toggleValueChangedAction for this:

d=GRScreenGrid.new;
e=GRMultiToggleView.new(d, 0@0, 8, 8);
e.toggleValueChangedAction = { |view, index, value| "toggle % value was changed to %".format(index, value).postln };

Thanks. Where in the classes the index method defined? I looked in the class for GRMultiToggleView and didn’t see it.

Toggle index is included as the second argument to any function acting as a callback to actions that concern individual toggles, ie toggleValuePressedAction and toggleValueChangedAction (not action which is triggered by all toggles). Index is an integer ranging from 0 to number of toggles minus 1. So it’s not a method call.

You typically use that index in your logic. In the GRMultiButtonView example code I posted above you can see that the index argument is used to set value (which in turn means what led to display) for the corresponding toggle with setToggleValue(index, val). The loop cycles a Routine that decrements val and invokes setToggleValue(index, val) every 0.1 seconds.

Oh good.

Another question:
Is there anything for doing momentary buttons, where the LED is on for as long as you’re pressing the button? I’m wanting to use it just to trigger one shot samples, so it only needs one state. I was thinking that button could do this but it seems it’s just on and off. Also if this is possible how would I make a 4 by 4 array of these and use the x and y, or index and value of whatever i press, to map to a parameter?

Hope that makes sense. Thanks for all your help so far, it’s making using the monome with SC much easier!

Default behavior for buttons is to toggle. You can change it:

// assuming b is a GRButton or GRMultiButtonView
b.behavior = \momentary;

For a 4x4 grid of buttons use GRMultiButtonView.

Thanks for the feedback. It’s much appreciated. Let me know if you need any more help on this.

this is awesome. i am glad people are attaching this to SuperCollider. Thank you.

One more thing I can’t seem to figure out. I want to take this code:

~drone = GRButton(a, 0@4, 4, 3);
~drone.action = {|view, value|
if(value,
{Pbindef(\gran, \sndBuf, b[\cuts][rrand(0, ~ssize)], \bpWet, 0.1).play;},
{Pbindef(\gran).stop}
);
};

and be able to access the x and y coordinates to use for mapping, while still keeping the whole square lighting up as it is now. Is that possible? I haven’t been able to figure it out.

thanks again!

I guess you’re interested in what individual monome button within the GRButton’s bounds triggered the change of GRButton value to true and thus starts playing the pattern? You can use the lastPressed() method GRButton inherits from GRView for that.

~drone = GRButton(a, 0@4, 4, 3);
~drone.action = {|view, value|
	if(value,
		{
			var pressedButton;
			pressedButton = view.lastPressed;
			pressedButton.postln; // a Point representing x, y
			pressedButton.x.postln; // x position, an Integer
			pressedButton.y.postln; // y position, an Integer
			Pbindef(\gran, \sndBuf, b[\cuts][rrand(0, ~ssize)], \bpWet, 0.1).play;
		},
		{Pbindef(\gran).stop}
	);
};

Note that above x, y is only posted when the GRButton is changed from unlit to lit since the postln calls are in the GRButton action function and the first if clause. A GRButton is handled as a whole so pressing two monome buttons within a GRButton bounds after each other without releasing in between will not trigger the GRButton action twice. This is by design.

Triggering something every time a monome button of a GRButton is pressed regardless of whether it changes the value (leds) is also possible but then you need to use another action. Let me know if I should elaborate on that.

That’s exactly what I wanted, thanks! I’m using this to build something to improvise with soundscapes and dancers on Tuesday. If you’re interested I can post the code and samples so you or others can see what all these questions were about.

1 Like

Very happy to hear you’re using Grrr for a performance. I’d love to see what you’re building.

This makes me motivated to finalize docs too.

Still working on the code. Another question:

How would you turn off an LED when a pattern is done.

Pbindef(\klank,
    \droneMix, msg[1],
    \warpRate, msg[2].linlin(0.0, 1.0, 0.1, 1.0),
    \lpffreq, msg[3].linlin(0.0, 1.0, 100, 400),
    \wdrop, msg[4].linlin(0.0, 1.0, 1, 35),
    \wmode, msg[5].linlin(0, 1, 1, 2),
    \dur, Pseq([msg[6].linlin(0, 1, 1, 20)], 1),
    \count, Prout({
        var n = msg[6].linlin(0, 1, 1, 20);
        loop({
            n.do{    n = n-1;
            n.postln;

            if(n == 0,
                {~klank.value = false}
            );
            1.yield}
        })
    })
).play(quant: 0)

This was an attempt but it isn’t working. I also tried polling .isPlaying which didn’t work for this pattern either. Any ideas? Thanks again.

I believe there are several ways of doing this but the one that comes to my mind is to listen to updates from the EventStreamPlayer that is spawned on pattern-play. Simple example code using a coupled GRButton:

(
a = Pbind(*[
  degree: Pseq([0, 3, 2, 5], 4),
  dur: 0.1
]);
b = GRScreenGrid.new;
c = GRButton.new(b, 0@0);
c.action = { |button, value|
  if (value) {
    d = a.play;
    d.addDependant { |what, state|
      if (state == \stopped) { c.value = false }
    };
  } {
    d.stop;
    d.releaseDependants;
  }
};
)

I have never used Pbindef so I’m not sure how it works but I guess this is applicable for Pbindef too.

/Anton

Hey, just wanted to say thanks for sharing this. Grrr made it super easy to prototype the grid interface for this arpeggiator in supercollider.

5 Likes