Video Tutorials on MIDI based music production with Supercollider, jackd and DAW

Hi guys! These are the first 2:

Introduction and setup:

MIDI controlled Ndef Instrument with FX Cascade and GUI:

8 Likes

Epic!

The mididef + ndef combo is brilliant
(also your discussion of the importance of interfaces and hearing)

2 Likes

hey, ive tried out the setup and SC receives MIDI from my Faderfox EC4.


MIDI: device 0 5 -297297504  (Microsoft GS Wavetable Synth)
MIDI: device 1 6 -297297496  (MOTU Pro Audio Midi Out)
MIDI: device 2 7 -297297488  (Ableton to SC)
MIDI: device 3 8 -297297480  (Faderfox EC4)
MIDI Sources:
	MIDIEndPoint("MOTU Pro Audio Midi In", "MOTU Pro Audio Midi In")
	MIDIEndPoint("MOTU Pro Audio LTC Sync In", "MOTU Pro Audio LTC Sync In")
	MIDIEndPoint("Ableton to SC", "Ableton to SC")
	MIDIEndPoint("Faderfox EC4", "Faderfox EC4")
MIDI Destinations:
	MIDIEndPoint("Microsoft GS Wavetable Synth", "Microsoft GS Wavetable Synth")
	MIDIEndPoint("MOTU Pro Audio Midi Out", "MOTU Pro Audio Midi Out")
	MIDIEndPoint("Ableton to SC", "Ableton to SC")
	MIDIEndPoint("Faderfox EC4", "Faderfox EC4")
MIDI Message Received:
	type: control
	src: 3
	chan: 0
	num: 0
	val: 13

But im not able to control the Faders from the Ndef. Already set srcID: 3 in the MIDIdef.cc. Do you have an idea whats wrong?. thanks for theses fantastic videos :slight_smile:

Hi! Could you post the code of MIDIdef?

вт, 20 июл. 2021 г., 20:23 dietcv via scsynth <noreply@m.scsynth.org>:

sure :slight_smile:

(
MIDIdef.noteOn(
	( ~grainsin_pulsar[\name] ++ "_On").asSymbol,
	{ |vel, note, chan|
		Ndef(~grainsin_pulsar[\name]).put(
			note,
			Ndef(~grainsin_pulsar[\name]).source,
			0,
			[\freq, note.midicps, \gt, 1, \lev, vel/127]
		);
		\on.postln;
	},
	chan: ~grainsin_pulsar[\midiChannel],
	//srcID:8388608
).fix;



MIDIdef.noteOff(
	( ~grainsin_pulsar[\name] ++ "_Off" ).asSymbol,
	{ |vel, note, chan|
		Ndef( ~grainsin_pulsar[\name] ).removeAt( note );
	},
	chan: ~grainsin_pulsar[\midiChannel],
).fix;


~ccControlled = Order.newFromIndices(
	[
		\lev, \freq, \formantA, \formantB, \overlapA, \overlapB, \levA, \levB,
	], (21..28)
);


// Ndef( ~grainsin_pulsar[\name] ).getSpec(\freq).map(1)

MIDIdef.cc(
	( ~grainsin_pulsar[\name] ++ "_CC" ).asSymbol,
	{arg val, num;
		var control = ~ccControlled[ num ];

		Ndef(~grainsin_pulsar[\name]).set(
			control,
			Ndef( ~grainsin_pulsar[\name] ).getSpec(control).map(val/127)
		)

		// Ndef( ~grainsin_pulsar[\name] ).softSet(
		// 	control,
		// 	Ndef( ~grainsin_pulsar[\name] ).getSpec(control).map(val/127),
		// 	mapped: true,
		// 	spec: Ndef( ~grainsin_pulsar[\name] ).getSpec( control )
		// )

	},
	ccNum: ~ccControlled.indices,
	chan: ~grainsin_pulsar.midiChannel,
	//srcID: 3
).fix;
)

The knob (or slider) that sends this message has value num equal to 0. But the MIDIdef.cc is filtering out everything but (21…28) range (those are the ones my controller sends). So changing that array to array of nums from your controller should do it

perfect! its working, thanks alot! :slight_smile:

Im sending MIDI from Ableton into SC playing a SynthDef, using the Midicontroller to change the different parameters and routing multichannel Audio back into Ableton for further processing. this is really awesome :slight_smile:

2 Likes

I know that feeling )))

1 Like

Wow. Ndefs are awesome!!

Thanks for the videos. Your explanations are great, and a nice view into your process.

1 Like

I liked this approach so much I created a simple class that encapsulates the setup.

Example:

/////////////////////////////////////////////
// create a synth
(
SynthDef(\sine, {
    var freq = \freq.kr(220);
    var gate = \gate.kr(1);
    var sig = SinOsc.ar(freq);
    var aeg = Env.adsr(
        \atk.kr(0.01),
        \dec.kr(0.3),
        \suslevel.kr(0.5),
        \rel.kr(1),
        curve:\curve.kr(-4)
    ).ar(gate:gate, doneAction:Done.freeSelf);
    sig = Splay.ar(sig) * aeg * \amp.kr(0.3) * \vel.kr(1);
    Out.ar(\out.kr(0), sig);
}).add
)

/////////////////////////////////////////////
// show ui
n = NdefMixer(Server.default)
ProxyMeter.addMixer(n)

/////////////////////////////////////////////
// connect midi
MIDIClient.init()
MIDIIn.connectAll()

/////////////////////////////////////////////
// midi synth setup (see class below)
MidiSynth(\m1).synth(\sine) // primes the ndef with specified synth
MidiSynth(\m1).note(noteChan:2).cc(ctrl:[\rel, \sat], ccNum:[0, 1], ccChan:0)
MidiSynth(\m1).play()
MidiSynth(\m1).objects.first.set(\gate, 0) // silence the primed synth
MidiSynth(\m1).addSpec(\sat, [1, 4])
MidiSynth(\m1).addSpec(\rel, [1, 4])
MidiSynth(\m1).filter(200, {|in| PitchShift.ar(in, 2, 2, 0.01, 0.1)}).set(\wet200, 0.3)
(
MidiSynth(\m1).filter(210, {|in|
    var sat = \sat.kr(1);
    in = (in * sat).tanh * sat.sqrt.reciprocal;
    JPverb.ar(HPF.ar(in, 100), 5, 0, 5)
}).set(\wet210, 0.3)
)
//MidiSynth(\m1).disconnect


/////////////////////////////////////////////
// test with pattern
m = MIDIOut.newByName("IAC Driver", "Bus 1") // or whatever
(
Pdef(\midi,
    Pbind(
        \type, \midi,
        \midiout, m,
        \chan, 2,
        \degree, Pseq([0, 1, 3, 5].pyramid(2), inf),
        \scale, Scale.dorian,
        \dur, 0.5,
        \octave, Pseq([ 6, 5, 5, 6, 5, 5, 6, 5 ], inf),
        \legato, 0.2,
        \tempo, 1,
        \amp, 1)
)
)
Pdef(\midi).play
Pdef(\midi).stop

The class which you can place in Platform.systemExtensionDir

MidiSynth : Ndef {

    var <synthdef, <hasGate, <instrument;
    var <noteonkey, <noteoffkey, <cckey;

    *new {|key|
        var res = Ndef.dictFor(Server.default).envir[key];
        if (res.isNil) {
            res = super.new(key).prInit;
        };
        ^res;
    }

    prInit {
        noteonkey = "%_noteon".format(this.key).asSymbol;
        noteoffkey = "%_noteff".format(this.key).asSymbol;
        cckey = "%_cc".format(this.key).asSymbol;
        ^this;
    }

    note {|noteChan, note|
        MIDIdef.noteOn(noteonkey, {|vel, note, chan|
            if (this.hasGate) {
                this.put(note, instrument, extraArgs:[\freq, note.midicps, \vel, vel/127, \gate, 1])
            } {
                this.put(note, instrument, extraArgs:[\freq, note.midicps, \vel, vel/127])
            }
        }, noteNum:note, chan:noteChan)
        .fix;

        MIDIdef.noteOff(noteoffkey, {|vel, note, chan|
            if (this.hasGate) {
                this.objects[note].set(\gate, 0);
            }
        }, noteNum:note, chan:noteChan)
        .fix;
    }

    synth {|synth|
        synthdef = SynthDescLib.global.at(synth);
        instrument = synth;
        hasGate = synthdef.hasGate;
        this.prime(synth);
    }

    cc {|ctrl, ccNum, ccChan=0|
        var order = Order.newFromIndices(ctrl.asArray, ccNum.asArray);
        MIDIdef.cc(cckey, {|val, num|
            var ctrl = order[num];
            var spec = if (this.getSpec(ctrl).notNil) {
                this.getSpec(ctrl)
            }{
                [0, 1].asSpec;
            };
            var mapped = spec.map(val/127);
            this.set(ctrl, mapped);
        }, ccNum:ccNum, chan:ccChan)
        .fix;
    }

    disconnect {
        MIDIdef.noteOn(noteonkey).permanent_(false).free;
        MIDIdef.noteOff(noteoffkey).permanent_(false).free;
        MIDIdef.cc(cckey).permanent_(false).free;
    }
}
2 Likes

hey, im sending midinotes from the DAW to SC and would like to convert the incoming MIDI Data into mictrotonal values. i was trying to use the microtuning function from this thread MIDI and Tuning (microtuning) - #4 by jamshark70 together with the MidiSynth Class.
Any ideas how i can implement it? thanks :slight_smile:


f = { |midinote, root(0), tuning|
	midinote = midinote - root;
	tuning.wrapAt(midinote)  // look up, without considering octave
	+ midinote.trunc(tuning.stepsPerOctave)
	+ root
};

f.(64, 2, Tuning.at(\werckmeister))

// how can i apply it to the note argument? 
MidiSynth(\m1).note(noteChan:2, note: f.(64, 2, Tuning.at(\werckmeister)));

any ideas how to implement a microtuning function to the MidiSynth class?

AFAICS this is the only real problem with the MidiSynth class for fractional note values. If you can solve this, you should be OK.

Sorry that I’m personally too busy this week to do it for you. Maybe that points you in a good direction.

hjh

hey, thanks :slight_smile:

i think this is working (I used the Tuner from Ableton to have a look at the cents for the midinotes of a G Dorian Scale with just intonation).
G: 0.0 ct
A: +3.9 ct
Bb: +15.6 ct
C: -2.0 ct
D: +2.0 ct
E: -15.6 ct
F: +17.6 ct
G: 0.0 ct

now its possible to:
MidiSynth(\m1).note(noteChan:0, root: 7, tuning: Tuning.at(\just));

But im not really happy with declaring the variable tunedNote twice in the MIDIdef.noteOn and in the MIDIdef.noteOff. i also think that the result should be named tunedNote. Lets see what i can do to fix it.

	note {|noteChan, note, root(0), tuning|

		MIDIdef.noteOn(noteonkey, {|vel, note, chan|
			
			var tunedNote = note - root;
			note = tuning.wrapAt(tunedNote)
			+ tunedNote.trunc(tuning.stepsPerOctave)
			+ root;

			if (this.hasGate) {
				this.put(note, instrument, extraArgs:[
					\freq, note.midicps, \vel, vel/127, \gate, 1])
			} {
                this.put(note, instrument, extraArgs:[
					\freq, note.midicps, \vel, vel/127])
            }
        }, noteNum:note, chan:noteChan)
        .fix;

        MIDIdef.noteOff(noteoffkey, {|vel, note, chan|

			var tunedNote = note - root;
			note = tuning.wrapAt(tunedNote)
			+ tunedNote.trunc(tuning.stepsPerOctave)
			+ root;

            if (this.hasGate) {
                this.objects[note].set(\gate, 0);
            }
        }, noteNum:note, chan:noteChan)
        .fix;
    }

Here is the problem in a nutshell: In Werckmeister III, C# is tuned slightly below ET.

a = Tuning.at(\werckmeister);

n = Array.newClear(128);  // indexed by note

f = { |note| a.wrapAt(note) + note.trunc(a.stepsPerOctave) };

b = f.(60);  // 60.0

n[b] = \synthForNote60;

b = f.(61);  // 60.92

n[b] = \synthForNote61;

n[60]  // synthForNote61 -- so synthForNote60 has been dropped

If you play C, then C# together, if you use a simple this.put(note, ...), then you can’t release the C anymore.

It might be enough to say this.put(note.round, ...) (unless there is some tuning that lowers a pitch by more than 50 cents).

EDIT: A better solution would be to finish the entire tuning calculation in tunedNote and do not overwrite note's value. Then always use the input MIDI note number for indexing, and only use the tuned value for the synth argument. Then you wouldn’t need the tuning calculation for noteOff.

hjh

many thanks for the explanation :slight_smile:

i was trying to implement it like this and get the ERROR: binary operator '>' failed.

	note {|noteChan, note, root(0), tuning|

		var keys = Array.newClear(128);

		MIDIdef.noteOn(noteonkey, {|vel, num, chan|

			var note = keys.at(num);

			var tunedNote = num - root;
			tunedNote = tuning.wrapAt(tunedNote)
			+ tunedNote.trunc(tuning.stepsPerOctave)
			+ root;

			if (this.hasGate) {
				this.put(note, instrument, extraArgs:[
					\freq, tunedNote.midicps, \vel, vel/127, \gate, 1])
			} {
                this.put(note, instrument, extraArgs:[
					\freq, tunedNote.midicps, \vel, vel/127])
            }
        }, noteNum:note, chan:noteChan)
        .fix;

        MIDIdef.noteOff(noteoffkey, {|vel, num, chan|

			var note = keys.at(num);

            if (this.hasGate) {
                this.objects[note].set(\gate, 0);
            }
        }, noteNum:note, chan:noteChan)
        .fix;
    }

EDIT: ok i think i overcomplicated it. this seems to work:

	note {|noteChan, note, root(0), tuning|
		MIDIdef.noteOn(noteonkey, {|vel, note, chan|

			var tunedNote = note - root;
			tunedNote = tuning.wrapAt(tunedNote)
			+ tunedNote.trunc(tuning.stepsPerOctave)
			+ root;

			if (this.hasGate) {
				this.put(note, instrument, extraArgs:[
					\freq, tunedNote.midicps, \vel, vel/127, \gate, 1])
			} {
                this.put(note, instrument, extraArgs:[
					\freq, tunedNote.midicps, \vel, vel/127])
            }
        }, noteNum:note, chan:noteChan)
        .fix;

        MIDIdef.noteOff(noteoffkey, {|vel, note, chan|
            if (this.hasGate) {
                this.objects[note].set(\gate, 0);
            }
        }, noteNum:note, chan:noteChan)
        .fix;
    }