How to set my MidiSprout with Supercollider

Hi everyone in the SC community! :slight_smile:

I’m currently stuck trying to connect my MidiSprout from Plantwave to SuperCollider. I want to use it as a MIDI input and create a modular synth without overloading my CPU (I use a lot of plugins in Ableton, but… you know how it is). Also, I’d love to record everything created in SC directly into Ableton.

Is there a SuperCollider professional around here? :slight_smile:

Thanks a lot!

Tom

I have not used MidiSprout but this should show readings in the post window if anything is being transmitted via cc messages from Midi Sprout.

(
MIDIClient.init;
MIDIIn.connectAll;
MIDIdef.cc(\cc, {|val, ccNum| 
	val.debug(\val); 
	ccNum.debug(\ccNum);
});
)

Here are corresponding MIDIdefs for other types of midi events:

MIDIdef.noteOn(\noteOn, {|vel, nn| vel.debug(\vel); nn.debug(\nn) });
MIDIdef.noteOff(\noteOff, {|vel, nn| vel.debug(\vel); nn.debug(\nn) });
MIDIdef.bend(\bend, {|val| val.debug(\val) });
MIDIdef.program(\pg, {|n| n.debug(\n) });

Yeah it works :slight_smile: Thanks ! i see on the post windows
what’s next ? do i need just a synthDef ? ^^Sorry i’m learning

Yes. A typical setup for mapping controllers to a synth is (in pseudo code):

Define a synthdef

SynthDef(\bioSynth, {arg out = 0...<the arguments you want to control with MidiSprout>
var sig = < do something here >
...
...
Out.ar(out, sig)
}).add;

Add an instance of the synth to the server (server must be running first). Reuse this synth each time you get an incoming midi messages, so if you are using eg. an Env in the SynthDef, make sure it’s DoneAction is set to 0, so that the synth does not remove itself from the server:

x = Synth(\bioSynth);

Init midi connections and add the mididef. I am normalizing the incoming values with linlin. You can also do this inside the synthdef but I like to do it in the mididef.

(
MIDIClient.init;
MIDIIn.connectAll;
MIDIdef.cc(\cc, {|val, ccNum| 
	case
	{ ccNum == <some cc number> }
	{ x.set(\someNameOfParameter, val.linlin(0, 127, 0, 1)) } 
	{ ccNum == <some other cc number> }
	{ x.set(\someOtherParameter, val.linlin(0, 127, 0, 1)) } 
	... (put as many cases as needed)
});
)

That’s about it. If you have many midi controllers to keep track of you can also create a lookup table for mapping the controller numbers to argument names, but if you are new to SC I would suggest the above approach which is easier to decipher.

It is also possible to create all of the above code in one code block using a routine with s.sync messages…but again, this is probably not the VERY first thing you want to do if you are new to SC, we can come back to that later if needed.

1 Like

Maybe this is a little more helpful. Change it as needed for your purposes.

(
SynthDef(\bioSynth, { 
	arg out = 0, amp = 0, cutoff = 0.5, freq = 110, pan = 0.5;
	var sig = LFSaw.ar(freq) * 0.1; // scale the signal down not to overload the output
	sig = LPF.ar(sig, cutoff.linlin(0, 1, 100, 2000)); // re-map the normalized value to a sensible range
	sig = Pan2.ar(sig, pan.linlin(0, 1, -1, 1), amp); // amp does not need remapping, convert mono signal to stereo with Pan2
	Out.ar(out, sig)
}).add
)

x = Synth(\bioSynth) // synth does not make sound, amp is set to 0

// test that it actually works

x.set(\amp, 0.5)
x.set(\pan, 0) // pan left 
x.set(\pan, 1) // pan right
x.set(\pan, 0.5) // pan center
x.set(\cutoff, 0.8) // open the filter more
x.set(\cutoff, 0.2) // close it
x.set(\cutoff, 0.5) // 'default' filter value
x.set(\amp, 0) // quiet the synth, it is still active and on the server, just no sound now.

(
/// here a setup using randomly chosen ccvals: ccNum 4 = amp, ccNum 7 = cutoff, ccNum 9 = pan
MIDIdef.cc(\cc, {|val, ccNum| 
	var normalizedVal = val.linlin(0, 127, 0, 1); 
	case
	{ ccNum == 4 }
	{ x.set(\amp, normalizedVal.debug(\amp)) } 
	{ ccNum == 7 }
	{ x.set(\cutoff, normalizedVal.debug(\cutoff)) } 
	{ ccNum == 9 }
	{ x.set(\pan, normalizedVal.debug(\pan)) }
});
)

// Test the midi setup. This is what your Midi Sprout should do for you, this is just for testing without a midi interface connected. I am sending MIDI out of SC which loops back and is received by the mididef

m = MIDIOut(0); // define the midiout
m.control(0, 4, 64) // send a cc message on channel 0, ccNum = 4, val = 64
m.control(0, 9, 5) // send a cc message on channel 0, ccNum = 7, val = 5 -> pan almost all the way to the left

(
// making a filter-and-pan sweep 
Routine{
	m.control(0, 4, 64);
	(128 * 2).do{|i|
		m.control(0, 7, i.fold(0, 127));
		m.control(0, 9, i.fold(0, 127));
		0.02.wait
	}
}.play
)
1 Like

Wow, thank you so much!
I can get the SynthDef and the rest of the code you gave me to work!
I’ve learned a bit about SynthDef, but when it comes to all the surrounding code needed to connect everything together… that’s another story ^^

I can hear the cutoff with the piece of code you sent me, but how do I connect the MidiSprout with all these lines of code?

I’m receiving the information, but I can’t figure out how to connect the MidiSprout to the SynthDef and, more importantly… how to scale the plant that’s playing all the imaginable harmonies? ^^

Or should i just use Midi Pbind ?

The Midisprout should already be connected with the last code I sent you. Now you need to examine the cc numbers used by Midisprout. You can insert these lines in the MIDIdef (like my first code example) to show you this:

val.debug; 
ccNum.debug

Now you just need to change which ccNums the MIDIdef reacts to correspondingly. So change the statements in case { ccNum == number } etc. If the vals being transmitted are for instance much smaller than the full range of the MIDI control, e.g. only using the lower part of the range, you can scale the values differently inside the synthdef with the linlin. expressions.

1 Like

Yes with that code =>

(
MIDIClient.init;
MIDIIn.connectAll;
MIDIdef.cc(\cc, {|val, ccNum| 
	val.debug(\val); 
	ccNum.debug(\ccNum);
});
)

I can see what the plant give to me :slight_smile: (look at the photo :slight_smile: )

THEN i write a Synthdef =>

(
SynthDef(\bioSynth, { 
	arg out = 0, amp = 10, cutoff = 0.5, freq = 432, pan = 0.5, pulseFreq(3);
	var pulse= Dust.kr(pulseFreq);
	var sig = SinOsc.ar(freq) * 0.1; 
	sig = LPF.ar(sig, cutoff.linlin(0, 1, 1000, 2000)); 
	sig = Pan2.ar(sig, pan.linlin(0, 1, -1, 1), amp);
	Out.ar(out, sig)
}).add
)

I play the SynthDef but i can only hear only the SinOsc freq…

But the plant has no effect with the SynthDef…

Sorry maybe i’m not so far away to understand how to do it really… I see it’s not really complicated but for me … haha

I just want to hear what she plays, scaling a bit and modify the SynthDef to have a perfect ModularLine Synth ^^

So your MIDIsprout is sending on ccNum 80 and as far as I can tell from the photo, this is the only ccNum being used. So for at start you can connect ccNum = 80 to the amp argument. In the photo the values range from 38 to 50, if you let it run for a while, what is the actual range? You might need to adjust the linlin mapping to really be able to hear the difference. For instance, if the actual range being transmitted is 30-70 (as opposed to the full range of 0 - 127), you could change the mapping inside the MIDIdef to val.linlin(30, 70, 0, 1). This results in value below 30 being clipped to 0 and values above 70 being clipped to 1.

1 Like

Okay, I changed the code based on your suggestion! I can hear the tone, and with the filter effect, I notice a difference, but there’s no change in the MIDI Sprout’s note…

(
MIDIClient.init;
MIDIIn.connectAll;
MIDIdef.cc(\cc, {|val, ccNum|
	val.debug(\val);
	ccNum.debug(\ccNum);
});
)

MIDIdef.noteOn(\noteOn, {|vel, nn| vel.debug(\vel); nn.debug(\nn) });
MIDIdef.noteOff(\noteOff, {|vel, nn| vel.debug(\vel); nn.debug(\nn) });
MIDIdef.bend(\bend, {|val| val.debug(\val) });
MIDIdef.program(\pg, {|n| n.debug(\n) });

(
SynthDef(\bioSynth, {
	arg out = 0, amp = 10, cutoff = 0.5, freq = 420, pan = 0.5, pulseFreq(3);
	var pulse= Dust.kr(pulseFreq);
	var sig = SinOsc.ar(freq) * 0.1; // scale the signal down not to overload the output
	sig = LPF.ar(sig, cutoff.linlin(0, 1, 1000, 2000)); // re-map the normalized value to a sensible range
	sig = Pan2.ar(sig, pan.linlin(0, 1, -1, 1), amp); // amp does not need remapping, convert mono signal to stereo with Pan2
	Out.ar(out, sig)
}).add
)

x = Synth(\bioSynth)

(MIDIdef.cc(\cc, {|val, ccNum|
	var normalizedVal = val.linlin(30, 70, 0, 1);
	case
	{ ccNum == 80 }
	{ x.set(\amp, normalizedVal.debug(\amp)) }
	{ ccNum == 10 }
	{ x.set(\cutoff, normalizedVal.debug(\cutoff)) }
	{ ccNum == 10 }
	{ x.set(\pan, normalizedVal.debug(\pan)) }
});
)

(
// making a filter-and-pan sweep
Routine{
	m.control(0, 80, 80);
	(128 * 2).do{|i|
		m.control(0, 7, i.fold(0, 127));
		m.control(0, 9, i.fold(0, 127));
		0.02.wait
	}
}.play
)

Is that what i wrote from you :slight_smile: the pitch doesn’t want to change… :laughing:

There is no ccNum associated with freq in the MIDIdef, but you could create one by linking a ccNum to the \freq argument. You can also set the pitch manually by running x.set(\freq, 440) - substitute 440 for any number in the audible frequency range, so roughly 20 Hz and up… If you want to specify the frequency as a midinote number, you set it with x.set(\freq, 69.midicps) (here midinote 69 corresponds to a frequency of 440 Hz, whereas eg. 61.midicps = 277.18 Hz). For mapping frequency it is probably easier to do the mapping from the mididef. Here is an example of mapping ccNum = 10 to the discrete frequency of a piano:

(
MIDIdef.cc(\cc, {|val, ccNum|
	case
	{ ccNum == 80 }
	{ x.set(\amp, val.linlin(30, 70, 0, 1).debug(\amp)) }
	{ ccNum == 10 }
	{ x.set(\freq, val.linlin(0, 127, 25, 80).round.midicps.debug(\freq)) }
});
)

Thanks! I replaced the old MIDIdef with your new one.
I can see everything in the console and hear the 440 Hz tone, but when I touch the sensors, there is no sound. I didn’t realize it would be so complicated to get it to work properly and i’ve checked the server and rebooted everything I can haha, but I’m still having trouble.

My guess is that touching the sensor sets amp to 0. Try removing the amp argument from the Mididef and set the default amp of the synthdef to 0.5.

1 Like

Yes ! it works, i removed the \amp part from the MIDIdef and i hear what the plant plays :slight_smile:
I think i need and EnvGen in the Synthdef because she plays a lot !
How could i scale the plant ?

(
SynthDef(\bioSynth, {
	arg out = 0, amp = 1, cutoff = 0.5, freq = 420, pan = 0.5;
	var sig = SinOsc.ar(freq);
	sig = Pan2.ar(sig, pan.linlin(0, 1, -1, 1), amp); 
	Out.ar(out, sig)
}).add
)

x = Synth(\bioSynth)

(
MIDIdef.cc(\cc, {|val, ccNum|
	var normalizedVal = val.linlin(30, 80, 0, 1);
	case
	{ ccNum == 10 }
	{ x.set(\cutoff, normalizedVal.debug(\cutoff)) }
	{ ccNum == 10 }
	{ x.set(\pan, normalizedVal.debug(\pan)) }
	{ x.set(\freq, val.linlin(0, 127, 69, 60).round.midicps.debug(\freq))}
}));

Glad it is working. The easy part is adding the envelope:

(
SynthDef(\bioSynth, {
	arg out = 0, amp = 1, cutoff = 0.5, freq = 420, pan = 0.5, atk = 1, rel = 1, gate = 0;
	var sig = SinOsc.ar(freq);
	var env = Env.asr(atk, 1, rel).kr(0, gate);
	// above env is shortcut for:
	// var env = EnvGen.kr(Env.asr(atk, 1, rel), gate, doneAction: 0);
	// if find the shortcut easier to read 
	sig = sig * env;
	sig = Pan2.ar(sig, pan.linlin(0, 1, -1, 1), amp); 
	Out.ar(out, sig)
}).add
)

x = Synth(\bioSynth)

x.set(\gate, 1)
x.set(\gate, 0)
x.set(\gate, 1)

The harder part is determining when to open and close the gate. How fast are the midi cc messages coming in? Are they continuous or do they pause some times? Also, how many different ccNums are being transmitted and what are the actual ccNums?

Wow, yeah, the MIDI CC messages come in super fast and chaotically. The plant can take a pause sometimes, but the nots are staying. I’d like to build a normal synth with delay and other effects using that plant.
I get it with the gate thanks ! :ok_hand:

I think you asked how many frequency responses there are? Going from one note to another could be faster, but I think the minimum is 20 times (I just read that) and from freq:554,… to 523,… 23 Times)

(
MIDIdef.cc(\cc, {|val, ccNum|
	var normalizedVal = val.linlin(30, 80, 0, 1);
	case
	{ ccNum == 10 }
	{ x.set(\cutoff, normalizedVal.debug(\cutoff)) }
	{ ccNum == 10 }
	{ x.set(\pan, normalizedVal.debug(\pan)) }
	{ x.set(\freq, val.linlin(0, 127, 70, 80).round.midicps.debug(\freq))}
}));
1 Like