MIDI and Tuning (microtuning)

Has anyone translated incoming MIDI notes to microtonal values? All the documentation on Tuning and Scales in SC use Pbind, which i am not using.

I receive MIDI from an external sequencer, like this:

 MIDIFunc.noteOn({|vel, midinote|
    ~synths[midinote] = Synth(\tx81z, args:[kfreq:midinote.midicps, vel:vel]);
 });

Ideally, i want to load a tuning and make the midinote.midicps adhere to that new tuning before it updates the “freq” parameter in the synth. But i can’t wrap my head around it.

Thanks for all help.

1 Like

As I understand it, midi notes have a defined meaning in the standard tuning – that’s what .midicps does. If you want to go to another tuning, or even microtonal, you’d first have to specify the new mapping – which frequency you’d like the midinote to go to instead. Once you’ve defined that, say in a dictionary, it’ll be easy to convert incoming midinotes to freqs for your synth arg.

Perhaps I’m misunderstanding …

Cheers,

eddi

https://soundcloud.com/all-n4tural

https://alln4tural.bandcamp.com

I finally managed to figure this out! If everyone else is wondering, here is how:

> (
> var scale;
> scale = Scale.chromatic;
> scale.tuning_(\reinhard);
> ~synths = Array.fill(128, nil);
> MIDIFunc.noteOn({|vel, midinote|
> 	
> 	~synths[midinote] = Synth(\tx81z, args:[kfreq:midinote.keyToDegree(scale, 12).degreeToKey(scale).midicps,
> 		vel:vel, algorithm:1]);
> });
> MIDIFunc.noteOff({|vel, midinote|
>     ~synths[midinote].set(\gate, 0);
> });
> )

the important bit is:

scale = Scale.chromatic;
scale.tuning_(\reinhard);
kfreq:midinote.keyToDegree(scale, 12).degreeToKey(scale).midicps

where “kfreq” is the frequency argument of the synth.

Beautiful!

4 Likes

Step one, I would think, would be to investigate what the Tuning object looks like and does.

Tuning.at(\werckmeister)

-> Tuning([ 0, 0.92, 1.93, 2.94, 3.915, 4.98, 5.9, 6.965, 7.93, 8.895, 9.96, 10.935 ], 2, "Werckmeister III")

That looks like C = normal, C# = 8 cents flat, D = 7 cents flat and so on. So if you look up the chromatic note value in this array, then you know how much to deviate from equal temperament.

In other words, these are midicps values.

That leaves octave transposition. Given a note number, what is the octave? Well… divide by the number of semitones in the octave and chop off any fraction – ‘div’ is integer division, useful here. Then multiply that back by the number of semitones. EDIT: trunc may be quicker.

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

f.(62, Tuning.at(\werckmeister))

-> 61.93

And then you can use this with midicps.

EDIT: I forgot about scale root. You can subtract the root before looking up the tuning, and then add the root back:

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))

Tuning has other variables – this function doesn’t account for an octave ratio != 2 – but if it’s not relevant to your use case then there’s no reason to overcomplicate.

hjh

5 Likes

Hello!

I’m searching for the opposite - how to go from a Pbind which is using scale, degree, etc. with an arbitrary tuning and output MIDI data with a note number and appropriate pitch bend amount? (Assuming monophonic)

I remember writing some code for this. Keep in mind the pitch bend range on a midi instrument is not standardized, so you will need to adjust the code below for your situation

(
var channel = 0; // midi channel
var external_pitchbend_range = 2; //semitones - change this as needed for your situation
var m =  MIDIOut.newByName("CASIO USB-MIDI", "CASIO USB-MIDI MIDI 1"); // change this
Pbind(
	\type, \midi,
	\chan, channel,
	\noteval, Pseq([40, 40.5, 41, Rest()]-24, inf),
	\note, Pfunc({ | event | event[\noteval].floor }),
	\dur, 0.3,
	\midiout, m,
	\bend, Pfunc({
		| event |
		if (event[\note].isRest.not) {
			var pitchbendvalue = event[\noteval].frac.linlin(0,external_pitchbend_range,8192,8192*2).asInteger.postln;
			m.bend(channel, pitchbendvalue);
		};
		0; // return something other than nil to avoid stopping the pattern
	}),
	).play;
)


2 Likes

Thank you for posting this! It took a little bit of modification, but I was able to get it to work with my code.

One question though: how do you set your root fundamental frequency?

It seems that SuperCollider defaults to middle C = 261.6255653006. I want to make G = 98.0 my root fundamental frequency. It is possible to use Scale.degreeToFreq(degree, 98, octave) to set 98 as the root, but then it is still based on the C 60.0 key on the keyboard, which is ok, but it seems like there should be a more elegant solution. Any ideas?

Thanks!

See the bottom of the Event helpfile where conversion conventions are explained. You could e.g. use ctranspose:

(ctranspose: 98.cpsmidi - 60, midinote: 72).play;

(ctranspose: 0, midinote: 72).play;