Toggle in SC (midi sequencing)

Maybe silly newbie question, but what’s the way to do a toggle in SC?

For instance, if you want to make such a step sequencer in SC with input from a controller, would that be a Routine with a lot of if statements, checking whether a variable is set to true or false?

Sort of, instead of checking for input, in supercollider we respond to them and you’d want to use language features (an array) to implement the timeline.

First, you would store the whole timeline in an array.

~num_steps = 10;
~timeline = 0!~num_steps;

Then tell supercollider what to do when it gets a midi message.

MIDIdef.cc(\respond_to_cc_1, {|val| 
	~timeline[1] = val;
}, 1);

Then walk the timeline, there are many ways to do this however!

Here is the simplest way I could think of…

~timeline_running = true;
~step_duration = 0.25;
fork {
	while {~timeline_running} {
		~timeline.do({ |val|
			if (val >= 1) {
				/* do something with the value */
			}
			~step_duration.wait;
		})
	}
}

…but you can also do it in a pattern.

Pbind(
	\step, Pn(Pseries(0, 1, ~num_steps)),
	\freq, Pfunc({|ev| 
		var v = ~timeline.at(ev[\step]);
		if (v >= 1) { v } { Rest() }
	}),
	\dur, 1
).trace.play;

~timeline[1] = 1.24; // see it change
2 Likes

These (mini) controllers don’t have sequencer capabilities itself, the led lights are set by the software. A led can turn on or off by sending a midi note. So for displaying the ‘playhead’ SC should turn on and off the consecutive leds. If one led is toggled on by the user via the hardware, SC should do something with it.

Thanks, your examples gives a good start. What is exactly the difference between fork and Routine?
Interesting to see the use of while (there is a syntax error isn’t it? Curly brackets instead of parenthesis).

In the pbind, step is a symbol you defined yourself right? The events are generated by Pbind in the background by default I guess.

No, curlies are correct here.

Inside the Pfunc or the timeline.do is where this should take place. You’d send a midi message from there.

Press CTL+i on fork and you can go to the implementation in Function — fork makes and plays a routine.

You can define any symbol you like, it is used in the Pfunc.

1 Like

Ok first step is to make it work. This seems to work. Curious if I am on the right track and coding feedback is welcome.

It works with the Launchpad MK3 mini. One step of scrolling as test is possible.



(
  ~lpNoteOffset = 11;  // First midi notenumber of the LP in programmer mode.
  ~lpStep = 10;  // New sample is 10 steps
  ~ledCol = 87;  // active note color
  ~collapseCol = 84; //104; // when the playhead meets a set led.
  ~playCol = 13; // playhead color
  ~scrollUp = ( 0 * ~lpStep );
  ~upCol = 119;  // scrollUp/Down buttons init color
)


(
  // MIDI init
  MIDIClient.list;
  ~midiOut = MIDIOut.newByName("Launchpad Mini MK3", "Launchpad Mini MK3 LPMiniMK3 MI"); //
  //~midiOut = MIDIOut.newByName("Launchpad X", "Launchpad X LPX MIDI In");
  m = ~midiOut;
  MIDIIn.connectAll;
  MIDIFunc.trace(true);
)


(
  // mini programmer / live mode select, 0 live, 1 programmer mode.
  ~programmerMode = 1;
  m.sysex(Int8Array[240, 0, 32, 41, 2, 13, 14, ~programmerMode, 247]);
)


(
  // Synthdef to play the samples
  SynthDef(\playBuf, {
      var sig;
      var buf = \buf.kr(4);
      var rate = \rate.kr(1) * BufRateScale.kr(buf);
      sig = PlayBuf.ar(2, buf, rate, \loop.kr(0), doneAction: 2);
      Out.ar(\out.kr(0), sig!2);
  }).add;


  // load the samples
  ~samplePath = PathName( thisProcess.nowExecutingPath ).pathOnly;
  b = (
      \none: nil, // TODO? 
      \0: Buffer.read( s, ~samplePath ++ "/KICK75.wav" ),
      \1: Buffer.read( s, ~samplePath ++ "/SNARE0.wav" ),
      \2: Buffer.read( s, ~samplePath ++ "/002.wav" ),
      \3: Buffer.read( s, ~samplePath ++ "/CLAP62.wav" ),
      \4: Buffer.read( s, ~samplePath ++ "/hihat1.wav" ),
      \5: Buffer.read( s, ~samplePath ++ "/bass1.wav" ),
      \6: Buffer.read( s, ~samplePath ++ "/bell.wav" ),
      \7: Buffer.read( s, ~samplePath ++ "/snareroll.wav" ),
      \8: Buffer.read( s, ~samplePath ++ "/RS.WAV" ), // TODO buffer has 1 channel.
  );


  // Make Grid
  // 8 * 10 = 80; 1x pageup = 90;
  ~grid = Array.fill(90, { Dictionary.newFrom([\num, 0, \chan, 0, \vel, 0, \sample, b[\0]]) }; );


  // assign samples
  // TODO switch or case
  ~grid.do{ | val ,  i | 
      //val.postln;
      i.postln;
      case (
        { ( i <= 9 ) }, { ~grid[i][\sample] = b[\0] },
        { ( i > 9 ) && ( i <= 18 ) } ,{ ~grid[i][\sample] = b[\1] },
        { ( i > 18 ) && ( i <= 28 ) }, { ~grid[i][\sample] = b[\2] },
        { ( i > 28 ) && ( i <= 38 ) }, { ~grid[i][\sample] = b[\3] },
        { ( i > 38 ) && ( i <= 48 ) }, { ~grid[i][\sample] = b[\4] },
        { ( i > 48 ) && ( i <= 58 ) }, { ~grid[i][\sample] = b[\5] },
        { ( i > 58 ) && ( i <= 68 ) }, { ~grid[i][\sample] = b[\6] },
        { ( i > 68 ) && ( i <= 78 ) }, { ~grid[i][\sample] = b[\7] },
        { ( i > 78 ) && ( i <= 88 ) }, { ~grid[i][\sample] = b[\8] };
      );
    };


  // clear all note leds
  // TODO which function names are allowed?
  c = {
    "clear grid".postln;
    ~grid.do{ | v, i |
      //m.noteOn(0, i, 0);
      m.sysex(Int8Array[144, i, 0]);
    };
  };

  
  g = {
    "redraw grid".postln;
    ~grid.do{ | v, i |
      var upBoundary = ~scrollUp + 80; // 80 is amount of midi note numbers (8 * 10);
      var index = ( i - ~scrollUp ) + ~lpNoteOffset; // 11 -0; 21 - 10 = 11
      if ( ( i >= ~scrollUp ) && ( i <= upBoundary ) && ( v[\vel] > 0 ), 
        { m.sysex(Int8Array[144, index, ~ledCol]); }
      );
    };
  }
)


// MIDIdef

// set velocity value and / or change color led.
(
  ~srcID = nil; //1572865;  // src id of the LP mini NOTE is not consistent?

  MIDIdef.noteOn(\b, { | vel, num, chan | 
    var index = (num - ~lpNoteOffset) + ~scrollUp;
    num.postln;
    if ( ~grid[index][\vel] > 0,
      { ~grid[index][\vel] = 0; m.sysex(Int8Array[144, num, 0]) },  // LED off
      { ~grid[index][\vel] = vel; m.sysex(Int8Array[144, num, ~ledCol]); }  // LED on
    );
    },
      noteNum: ( ~lpNoteOffset..88 ), // TODO
      chan: nil,
      srcID: ~srcID, //nil,
    ).permanent_(true);

  // scrollUp
  MIDIdef.cc(\sUp, { | val, num, chan|
    if ( ( val > 0 ) && ( ~scrollUp < ~lpStep ),  // only when page up is 0;
      {  
        ~scrollUp = ( ~scrollUp + ~lpStep ); 
        ~upCol = ( ~upCol + 1 );
        c.(); // clear grid
        g.(); // draw grid
        m.control(0, 91, ~upCol);  // scroll up button
      });
    },
      ccNum: 91,
      chan: nil,
      srcID: ~srcID, //nil,
    ).permanent_(true);


  // scrollDown
  MIDIdef.cc(\sDown, { | val, num, chan |
    val.postln;
    if ( ( val > 0 ) && ( ~scrollUp >= ~lpStep ), 
      {  
        ~scrollUp = ( ~scrollUp - ~lpStep ); 
        ~upCol = ( ~upCol - 1 );
        c.(); // clear grid
        g.(); // draw grid
        m.control( 0, 91, ~upCol );  
   });
    },
      ccNum: 92,
      chan: nil,
      srcID: ~srcID, //nil,
    ).permanent_(true);
)


/// Tempoclock
(
t = TempoClock(120/60).permanent_(true);
)


// clear velocities
(
  ~grid.do{ | v, i |
    v[\vel] = 0;
  };
  c.() // clear screen
)


// Playhead 
(
 p = {   
    Routine{ 
      loop{
        ~timeline.do{ | v, i |
          var led = i + ~lpNoteOffset;
          var index = i + ~scrollUp; // always on bottom line
          [v, i].postln;
          if ( ~grid[index][\vel] == 0, 
            { 
              m.sysex(Int8Array[144, led, ~playCol]);
              ~dur.wait;
              m.sysex(Int8Array[144, led, 0]); 
            }, 
            {
              m.sysex(Int8Array[144, led, ~collapseCol]);
              ~dur.wait;
              m.sysex(Int8Array[144, led, ~ledCol]); 
            });
        };
      };
    }.play(t);
  };
)


// Sequences
(
  r = { | seqNum |
    var num = seqNum;
    Routine{ 
      loop{
        ~timeline.do{ | v, i |
          var index = i + ( num * ~lpStep); 
          [v, i].postln;
          if ( ~grid[index][\vel] > 0,
            {  Synth(\playBuf, [\buf, ~grid[index][\sample].bufnum])}
          );
          ~dur.wait; 
        };
      };
    }.play(t);
  };
)


// Play 
(
  ~timeline = 0!8;
  ~dur = 0.5;
  ~scrollUp = 0;
  ~upCol = 119;
  m.control( 0, 91, ~upCol );
  m.control( 0, 92, ~upCol ); // scroll button default static col
  c.(); // clear grid
  g.(); // draw grid


  // playhead
  p.();


  // play one routine per sample sequence
  (0..8).do{ | i | 
    r.(i); 
  };
)


t.tempo_(80/60);

What if I want to use a Pbjorklund for \dur, with 3 dynamic values, in a Pbind, instead of a single value like in your Pfunc example?

var n = ~timeline.at(ev[\step]).numHits;
Pbjorklund(n, 8, inf, 0)/4,

I tried to fiddle with Plazy, but that doesn’t work like with Pfunc in a Pbind it seems.

Edit: Found a answer here: