Workflow from .Midi File (Musescore into SuperCollider)

I would like to generate a .midi file from composed sheet music (ex. Musescore) then send the .midi file into Supercollider. I want to be able to do the following with it

  1. Access different bars/sections of the piece (ex. only play bars 2-4)
  2. Assign different synths to different parts (the bass part should be synthA and the melody synthB or use synthA for bars 2-4 then synthB for bars 5-7.
  3. Ideally use different .midi files at once so I could have .midi pattern 1 and .midi pattern 2.
  4. Write a trigger to affect different parts. ex (if a=1 do this, if a=2 do this)

I tried the SimpleMIDIn but I found it slightly confusing to work with beyond importing the .midi into Supercollider. I would appreciate any assistance you can offer on these issues.

SimpleMIDIFile does the hard/annoying work of unpacking the bytes in the MIDI file format, but it does almost nothing to dress up the data in a musically useful way. I’m not aware of an existing SC class that parses a MIDI file into time units… so you might have to do it on your own.

This is, at root, a data representation problem. SimpleMIDIFile gives you the deltas (IOIs = inter-onset intervals) between MIDI messages but this doesn’t represent notes, or their metrical positions within bars, in an ideally useful way. After reading, you would have to post-process the SimpleMIDIFile data:

  • Accumulate time: “now” is the sum of all previous IOIs. Store this absolute time with every MIDI event. With this absolute time, you can easily locate the start of bar 2 etc. (or further subdivide the data structure – maybe instead of a flat list of notes with onset times, you might want an array of bars – this is up to you, whatever is convenient for your usage).

  • Note off messages should search backward for the latest note-on with the same pitch. Then the note duration is “now” minus the note-on’s absolute onset time. Might be good to store the absolute release time as well, to help find notes that are holding over from before.

Divide up the data based on MIDI channel. I think SimpleMIDIFile has methods for this.

Once you’ve acquired data from multiple MIDI files, you can use separate players to play lines from them simultaneously, or even merge them. After they’re in SC, the data are just data; you can do what you want with them.

Certainly possible in SC, but not specifically about MIDI.

hjh

Maybe more basically

I think after looking into this further another way to ask this question might be. How to extract the pitch/rythm data from a midi into an array
ex.
import midiFile
Pseq([notesFromMIDIfile])
\dur, Pseq([RhythmFromMIDIFile]

Do you have any suggestions on doing this?

Programming boils down to three questions:

A. What data do I have?
B. What data do I want?
C. What sequence of operations will transform 1 into 2?

C is, of course, a very complex question :laughing: but I like to state it in this way because it’s usually impossible to start to answer C without knowing A and B (but it’s very tempting to just dive into coding without understanding A and B, which is a great way to waste time).

“How to extract the pitch/rythm data from a midi into an array”

A. Data we have – first step is to look at the data format in SimpleMIDIFile.

m = SimpleMIDIFile.read("~/share/sc3-plugins/external_libraries/stk/projects/examples/midifiles/tango.mid".standardizePath);

m.tracks;  // 17

m.tempo;  // 127.00025400051
m.division;  // 384 ticks per quarter

m.noteEvents(0, 0) // nothing -- reserved for meta-events
m.noteEvents(0, 1) // a lot...

-> [ [ 1, 1296, noteOn, 0, 50, 39 ], [ 1, 1296, noteOn, 0, 38, 39 ], [ 1, 1460, noteOff, 0, 38, 0 ], [ 1, 1468, noteOff, 0, 50, 0 ], [ 1, 1848, noteOn, 0, 57, 51 ], [ 1, 1936, noteOff, 0, 57, 0 ], ...]

So for notes, we see arrays: [ 1, 1296, noteOn, 0, 50, 39 ] means track number 1, time = 1296 (in clock ticks, 384 ticks = 1 quarter note), event type = note-on, channel number 0, note number 50, velocity 39.

Now… how long does this D natural last? Well… that MIDI event doesn’t tell you. The end time of this node is determined by the next noteOff with the same note number.

Oh, reading the help file further, there is a noteSustainEvents method that will include a duration:

m.noteSustainEvents(0, 1);
-> [ [ 1, 1296, noteOn, 0, 50, 39, 172, 0 ], [ 1, 1296, noteOn, 0, 38, 39, 164, 0 ], [ 1, 1848, noteOn, 0, 57, 51, 88, 0 ], [ 1, 1996, noteOn, 0, 69, 58, 52, 0 ], ...]

So that note 50 takes 172 ticks. This method would be more useful.

B. Data we want – This is up to you, depending on your requirements.

I would suggest that separating notes from the rhythm will create complication later. I think it would be better to produce an array of Event objects, which you can then stream out as whole events with Pseq. (It’s a common misconception with patterns that you must have a Pbind to produce events.)

So maybe the output format is like (midinote: ..., velocity: ..., amp: ..., dur: ..., sustain: ..., absTime: ...).

C. How to change the noteSustainEvents into that? Well, the noteSustainEvents (in theory) are already pretty close. Maybe a simple collect would do it…

(
f = { |smf, channel, track|
	var tempo = smf.tempo;
	var ppq = smf.division;
	var lastTime = 0;
	
	smf.noteSustainEvents(channel, track).collect { |array|
		var event = (
			midinote: array[4],
			velocity: array[5],
			amp: array[5] / 127,
			dur: (array[1] - lastTime) / ppq,
			absTime: array[1] / ppq,
			sustain: array[6] / ppq
		);
		lastTime = array[1];
		event
	};
};
)

a = f.(m, 0, 1);

// in theory Pseq(a, 1).play should work
// but see below about the time values, which might need some massaging

but this is giving me quite weird time values that seem not exactly related to the meter (or, I don’t understand yet what the is relationship between ticks and metrical note values – I’m seeing 64th notes being returned here, from this MIDI file – maybe yours would be simpler). I’m basically out of time for now (this is already a long post), so you would have to look into that yourself, or maybe someone else is interested and can help you.

With the absTime in the events, then it would be possible to scan through the array to find specific beats, etc…

hjh

Recently i’ve still been trying to run Musescore into SuperCollider except this time doing it directly by either virtual MIDI cables or JACK audio. (On Windows or Ubuntu)

  1. Using LoopBe1 virtual Midi cable I just set the Musescore Midi out as the cable and the SC Midi in as the cable and then in supercollider I ran

MIDIClient.init;
MIDIIn.connect(0,MIDIClient.sources[10]);
MIDIdef.noteOn(\noteOnTest, {“key down”.postln}) ;

When I played audio from Musescore I actually got several posts of “key down” in supercollider, but then Loopbe1 threw an error about muting due to a midi feedback loop and cut the connection. I tried doing the same thing with “Springbeats” virtual midi cable and was unable to get any signal into Supercollider. I tried doing this with JACK audio on windows and linux but I was unable to figure out how to get them properly connected.

Do you have any suggestions on this? I feel like it would be amazing potential to be able to write sheet music in Musescore and modify and design the sound with all of the power of Supercollider.

That’s interesting. Feedback means something is sending MIDI into a device or app that is forwarding the MIDI to somewhere that sends it back to the first one. MIDI feedback is impossible without some kind of forwarding; if all the devices/apps are consuming their messages, then there’s nothing to feed back.

SuperCollider will definitely not forward MIDI unless you ask it to. SC can’t send MIDI out without a MIDIOut object, and the default class library doesn’t create one of those.

That leaves two possibilities: 1/ either something else in the MIDI chain is forwarding messages, or 2/ maybe a SuperCollider extension class is forwarding messages. I’m pretty sure I never heard of an extension that activates MIDI thru by default, so I think it’s probably 1/ (i.e. outside of SC).

There’s another Windows MIDI virtual cable called loopmidi. Maybe it would work better?

hjh

Hmm, I have Tidal Cycles installed and it boots by default on launch of SC. Do you think that could do it?

Here is my SC startup file

//SuperDirt.start;

//"C:/Users/twilightcourier/AppData/Local/SuperCollider/downloaded-quarks/SuperDirt/superdirt_startup.scd"


(
s.reboot { // server options are only updated on reboot
	// configure the sound server: here you could add hardware specific options
	// see http://doc.sccode.org/Classes/ServerOptions.html
	s.options.numBuffers = 1024 * 256; // increase this if you need to load more samples
	s.options.memSize = 8192 * 32; // increase this if you get "alloc failed" messages
	s.options.numWireBufs = 64; // increase this if you get "exceeded number of interconnect buffers" messages
	s.options.maxNodes = 1024 * 32; // increase this if you are getting drop outs and the message "too many nodes"
	s.options.numOutputBusChannels = 2; // set this to your hardware output channel size, if necessary
	s.options.numInputBusChannels = 2; // set this to your hardware output channel size, if necessary
	// boot the server and start SuperDirt
	s.waitForBoot {
		~dirt = SuperDirt(2, s); // two output channels, increase if you want to pan across more channels
		~dirt.loadSoundFiles;   // load samples (path containing a wildcard can be passed in)
		~dirt.loadSoundFiles("C:/Users/twilightcourier/animation_sc/strobe-collab/sound/samples/samples-extra/*");

		// for example: ~dirt.loadSoundFiles("/Users/myUserName/Dirt/samples/*");
		// s.sync; // optionally: wait for samples to be read
		~dirt.start(57120, 0 ! 12);   // start listening on port 57120, create two busses each sending audio to channel 0

		// optional, needed for convenient access from sclang:
		(
			~d1 = ~dirt.orbits[0]; ~d2 = ~dirt.orbits[1]; ~d3 = ~dirt.orbits[2];
			~d4 = ~dirt.orbits[3]; ~d5 = ~dirt.orbits[4]; ~d6 = ~dirt.orbits[5];
			~d7 = ~dirt.orbits[6]; ~d8 = ~dirt.orbits[7]; ~d9 = ~dirt.orbits[8];
			~d10 = ~dirt.orbits[9]; ~d11 = ~dirt.orbits[10]; ~d12 = ~dirt.orbits[11];
		);
	};

	s.latency = 0.3; // increase this if you get "late" messages
};
);

I accidentally broke my installation of loopmidi and now cannot properly reinstall it as it has a permission issue with the windows registry.

No. The SuperDirt quark doesn’t create any MIDIOut objects by default:

hjh