Cycling through specific values in an array for a MIDIFunc.cc

Hello, I’m trying to having a MIDIFunc.cc cycle through specific values for a ‘frequency’ argument in a basic SynthDef that plays a Pulse wave. Basically, I’d like to cycle through specific chords with a knob. The first thing that comes to mind is to have arrays that represent the chords.

~cmajor = [130.81, 164.81, 196.00];

~fmajor = [174.61, 220.00, 261.63];

~gmajor = [196.00, 246.94, 293.66];

Then I have the SynthDef:

(

SynthDef.new(\pulse, {

arg freq=100, amp=0.5;

var env, sig;

env = EnvGen.kr(Env([0,1,0], [0.1, 120]), doneAction:2);

sig = {Pulse.ar(freq, 0.5, 0.1)}.dup;

Out.ar(0, sig*env.dup);

}).add;

)

a = Synth.new(\pulse);

a.free;

Then I have the MIDIFunc:

~knob = MIDIFunc.cc({arg val; a.set(\freq, val.linexp(0, 127, 100, 1000))}, 10, 0);

Where I get stuck is how to incorporate the arrays into the MIDIFunc. Or if I need an entirely different approach, like setting up “if” arguments in the MIDIFunc, like “if the values of the knob are between 0-42 play ~cmajor”, etc.

I’d appreciate any help.

It is easier if you keep all the values in one array instead of 3 separate arrays:

// I prefer writing frequencies as midinote numbers, 
// the numbers below correspond to your frequencies. 
// If you like to use frequencies instead of midinote number, remove .midicps in the MIDIFunc.

~notes = [
	[48, 52, 55],
	[53, 57, 60],
	[55, 59, 62]
];

a = Synth(\pulse)
/// Now you have 3 x 3 = 9 values. MIDIFunc:

MIDIFunc.cc({arg val; a.set(\freq, ~notes.flat[ val.linlin(0, 127, 0, ~notes.flat.size - 1).round ].midicps)}, 10, 0);

/// now.  because the way the MIDIFunc is set up, you can add more frequences to ~notes as needed without having to alter the MIDIFunc

This is untested with a MIDI device but it should work, otherwise let me know.

I appreciate this! But I get an error message when I turn knob “midicps not understood”, so I took the midicps out and went back to frequencies. I don’t get any error messages but when I turn the knob it just shuts off unless the knob is turned all the way to the left, all I hear is the value substantiated in the SynthDef, freq=100. Not hearing chords.

This is what I’m working with right now.

(
SynthDef.new(\pulse, {
arg freq=100, amp=0.5;
var env, sig;
env = EnvGen.kr(Env([0,1,0], [0.1, 120]), doneAction:2);
sig = Pulse.ar(freq, 0.5, 0.1);
Out.ar(0, sig*env.dup);
}).add;
)

~notes = [
[130.81, 164.81, 196.00],
[174.61, 220.00, 261.63],
[196.00, 246.94, 293.66]
];

MIDIFunc.cc({arg val; a.set(\freq, ~notes.flat[ (val * ~notes.flat.size).round ])}, 1, 0);

a = Synth.new(\pulse);
a.free;

Ah I didn’t know you wanted chords, but makes sense, then you should do something like

~notes = [
[130.81, 164.81, 196.00],
[174.61, 220.00, 261.63],
[196.00, 246.94, 293.66]
];

a = ~notes[0].collect{|n| Synth(\pulse, [\freq, n])}

// simulate a random value from your midifunc everytime executed
(
var val = 127.rand.linlin(0, 127, 0, ~notes.size - 1).round.debug(\incomingvalToIndex);
a.do{|n, i| n.set(\freq, ~notes[val][i]) }
)

I assume ‘val’ is in the normal midirange [0…127]. Also, not that is should matter here, but I always use MIDIdef.cc(\cc, {…func…}). In general I find that MIDIdefs are the most hassle free way to use SC with MIDI devices, much in the same way I prefer to use Pdefs over explicitly assigning Pbinds to variables

It works every time I substantiate

(
var val = 127.rand.linlin(0, 127, 0, ~notes.size - 1).round.debug(\incomingvalToIndex);
a.do{|n, i| n.set(\freq, ~notes[val][i]) }
)

But how could I put this in a MIDIFunc.cc so I can have more control? There are three chords here, if I wanted to have the first chord be played when the cc # is between 0-43, then the second chord be played when 44-87, etc. How would I go about doing that?

Like this:

MIDIdef.cc(\cc, {|val, ccNum|
	var index = val.linlin(0, 127, 0, ~notes.size - 1).round.debug(\incomingvalToIndex);
	// if you have more knobs you can do case { ccNum == ~someCCnumber } { bla, bla } { ccNum == ~someOtherNumber } { bla bla }
	a.do{|n, i| n.set(\freq, ~notes[index][i]) }
})

Wow, thank you so much! It works

So a lot of what you did went over my head. Gonna have to really dissect each line to work out the logic

I appreciate it, thanks again

Glad it worked. So to elaborate a bit more:

50.midicps = 146.8323839587
146.8323839587.cpsmidi = 50.0. So when converting in this direction, the result will be a float.

The ‘def family’ includes Pdef for patterns, Tdef for tasks and routines and MIDIdef for MIDI communication. My advice is to use these when you can. They have some advantages that you can read about in the docs, for instance, you can play a Pdef, change some stuff inside it and re-evaluate it while it is playing and things will be updated on the fly.

When dealing with midi I suggest using these convenient methods from the MIDIdef class:

MIDIdef.cc
MIDIdef.noteOn
MIDIdef.noteOff

If you don’t already know, command D (on a mac) will call up the documentation of whatever class or method your curser is on. So if you type ‘MIDIdef’ and hit command + D (or equivalent) you are taken straight to the help files.

Like with almost everything in SC, MIDI can be done in different ways, MIDIdef is the easiest to me (and to most people I suspect).

There are two ways (probably many more) to play chords in SC: The one used above is not very intuitive at first, but it really highlights the power of SC. Instead of thinking like you would in the analog world or in a DAW ‘I need a polyphonic synth to play a chord’ the SC-way is to use one instance of a synth for each note. Once you wrap your head around it, you really realize how powerful this concept is. You don’t have to worry about max number of voices, voice stealing etc. and it is very easy to address each individual voice and customize parameters and so on. Keep your synths in arrays (or lists) opens up for all kinds of interesting ways of manipulation the individual voices through array manipulation (.collect, .do, .flop, .difference, .diff, .select, and a ton of more methods). The documentation is spread out over several help files. There is a lot of good stuff in ArrayedCollection which is not obvious at first.

The other way, more in line with an analog- or DAW mindset is to build SynthDefs which can handle chords directly. The problems here is that you have to define a max number of voices and that if you use less than the maximum number of voices all unused or rather silent voices are still there eating at your cpu. Plus added (and often unneeded) complexity of the SynthDefs.

1 Like

I appreciate all of that info!

If I wanted to control a different parameter on the SynthDef, like the pulse width, so I create an arg called ‘width’. Then I make another MIDIdef:

MIDIdef.cc(\cc2, {arg val; a.set(\width, val.linexp(0, 127, 0.1, 0.9))}, 2, 0);

I get an error message: “ERROR: Message ‘set’ not understood.”
How come this doesn’t work?

Using all of this, below:

(
SynthDef.new(\pulse, {
arg freq, width=0.1;
var env, sig;
env = EnvGen.kr(Env([0,1,0], [0.1, 120]), doneAction:2);
sig = Pulse.ar(freq, width, 0.1);
Out.ar(0, sig*env.dup);
}).add;
)

~notes = [
[130.81, 164.81, 196.00],
[174.61, 220.00, 261.63],
[196.00, 246.94, 293.66]
];

a = ~notes[0].collect{|n| Synth(\pulse, [\freq, n, \width, 0.1])}

MIDIdef.cc(\cc, {|val, ccNum|
var index = val.linlin(0, 127, 0, ~notes.size - 1).round.debug(\incomingvalToIndex);
// if you have more knobs you can do case { ccNum == ~someCCnumber } { bla, bla } { ccNum == ~someOtherNumber } { bla bla }
a.do{|n, i| n.set(\freq, ~notes[index][i]) }
}, 1, 0)

So the SynthDef is playing but when I try to turn the second knob for the pulse width, I get that error message.

Quick note on forum usage: it looks like you are using the blockquote button for code formatting. Code should really be a preformatted text block (</> icon in the formatting bar).

Now, the question:

Notice that in the working MIDIdef, it’s performing the operation by a.do – to do it on every synth contained in the array.

In the non-working one, you’re just doing a.set – but a isn’t a synth – it’s an array of synths. Because it’s an array, it’s better to follow the same formula: do over the array.

MIDIdef.cc(\cc2, { arg val; a.do { | synth| synth.set(\width, val.linexp(0, 127, 0.1, 0.9)) } }, 2, 0);

(Minor optimization would be to calculate the linexp once at the beginning of the MIDIdef function and save it in a variable, then reuse the variable in the do loop.)

hjh

1 Like

Thank you James! I appreciate it