How to pluck MiRings from using MIDI

I’m quite new to SuperCollider so I hope my questions isn’t too naive.

I’ve been loading the MiRings ugen from here and I have it up and running with MIDI which I send from a keyboard and orca.

The Rings module takes a pitch and a trigger and can play up to four notes. Timing is a therefore a problem because the pitch change and trigger for two notes cannot happen at the same time, or the module won’t play both.

This is not a problem when I play on the keyboard because my timing isn’t perfect so there is never an overlap. But when sequencing from Orca the notes often fall at the same time but they can’t both ring out.

Here’s a stripped down example of my approach:

(
SynthDef(\dong, { arg pit = 60, trig = 0;
    Out.ar(0, MiRings.ar(pit: pit, trig: trig, poly: 4, model: 2));
}).add;
)

(
var notes, on, off, theRings;

MIDIClient.init;
MIDIIn.connectAll;

theRings = Synth(\dong);

on = MIDIFunc.noteOn({ |veloc, num, chan, src|
	theRings.set(\pit, num, \trig, 1);
});

off = MIDIFunc.noteOff({ |veloc, num, chan, src|
	theRings.set(\trig, 0);
});

q = { on.free; off.free; theRings.free; };
)

So my question is, can I somehow queue up or delay the pitch changes and triggers? And can I send an impulse on noteOn instead of manually setting the trig back to 0 (because that is obviously also horrible…).

Again, I hope this isn’t too naive. Thank you!

See SynthDef help for “trigger rate” arguments.

Of course. An array could serve as the queue and you would need a routine to read one item from the queue, send it, wait a short time, then check if there’s still anything in the queue.

hjh

1 Like

Thanks for the tip about trigger rates! That was exactly what I was looking for!

I think I’ll have to study to figure out how to fill/empty an array then. I still find that part of the language and system intimidating. Any pointers are highly appreciated.

Would it make sense (if order doesn’t matter) to simply ask a routine to wait a random amount of time and then use the .next function on a routine to send in the notes to be played? I don’t know if having while true loops in routines in SuperCollider is frowned upon.

Arrays and other collections dramatically expand the range of techniques you can conceive. It’s well worth it.

a = Array.new;

a = a.add(1);

a = a.add(2);

// now a is [1, 2]

a.do { |item| item.postln };  // prints 1 and 2 on separate lines

a[0]  // is 1

a[0] = 5;

a[0]  // now it's 5

a.removeAt(0);  // now only 2 is left in a

Hm, no, the routine can be playing on a clock or passively waiting for a next call, but not both.

But order indeed doesn’t matter – we can use that to remove one possible inefficiency. If you find later that order does matter, use removeAt instead of takeAt.

var pendingNotes = Array.new;

var runQueue = Routine {
    loop {
        if(pendingNotes.notEmpty) {
            theRings.set(\pit, pendingNotes[0], \t_trig, 1);
            pendingNotes.takeAt(0);
            0.05.wait;
        } {
            nil.yield;  // stop
        }
    }
};

on = MIDIFunc.noteOn({ |veloc, num, chan, src|
    pendingNotes = pendingNotes.add(num);
    if(runQueue.isPlaying.not) {
        runQueue.reset.play;
    };
});

Should be something like this, though it’s untested code.

hjh

Well… it’s not quite right. Routine’s meaning of isPlaying disagrees with every other class’s meaning of the same method, so Routine can’t be used in this way. That’s annoying and I’ve filed an issue about it. (I think that Routine should use a different name because it doesn’t exactly report playing/not-playing state.)

Task behaves as I expected, so…

var pendingNotes = Array.new;

var runQueue = Task {
	while { pendingNotes.notEmpty } {
		theRings.set(\pit, pendingNotes[0], \t_trig, 1);
		pendingNotes.takeAt(0);
		0.05.wait;
	}
};

on = MIDIFunc.noteOn({ |veloc, num, chan, src|
    pendingNotes = pendingNotes.add(num);
    if(runQueue.isPlaying.not) {
        runQueue.reset.play;
    };
});

Simulating a test of simultaneous notes, this code block does spread them out over time.

hjh

Ah great! Thanks for updating. I was trying out the Routine and couldn’t get it to work. I just assumed I was being slow. I can’t quite figure out where the error-output comes in the scide but I assume it would have complained about isPlaying.

Thanks once again!