Fox dot midi messages

can FoxDot send midi cc? or is possible send only note on and velocity?

Thanks

Currently just note on/off and velocity but there’s potential to add it in the future. Unfortunately I don’t have any midi gear currently to test it out :frowning:

It can be easily tested using virtual synthesizers. I don’t have midi gear either, but I own an iPad and there are plenty of midi enabled apps, which can receive CC .

I did some experiments and look promising. As a quick&dirt proof of concept, I changed the code of FoxDot.sc (downloaded quark) so that OSCFunc now detects if the message is MidiOut or MidiCC, and performs the appropriate action:

	OSCFunc(
		{
		arg msg, time, addr, port;

		if (msg[1].asString == "MidiOut", {
			var note, vel, sus, channel, nudge;
			// listen for specific MIDI trigger messages from FoxDot
			note    = msg[2];
			vel     = msg[3];
			sus     = msg[4];
			channel = msg[5];
			nudge   = msg[6];

			SystemClock.schedAbs(time + nudge, {midiout.noteOn(channel, note, vel)});
			SystemClock.schedAbs(time + nudge + sus, {midiout.noteOff(channel, note, vel)});
		}, {
			var channel, number, value;
			// listen for specific MIDI trigger messages from FoxDot
			channel = msg[2];
			number = msg[3];
			value = msg[4];
			SystemClock.schedAbs(time, {midiout.control(channel, number, value)})
		})},
		'foxdot_midi'
	);

After re-compiling this in SuperCollider, I’m now able to do the following in FoxDot:

class MidiCC:
    def __init__(self, number, value, channel=0):
        self.msg = OSCMessage("/foxdot_midi")
        self.msg.append(["MidiCC", channel, number, value])
    def send(self):
        Server.sendOSC(self.msg)

Clock.schedule(MidiCC(50, 70).send, beat=Clock.now()+8)

This sends a CC command with number 50 and value 70, 8 beats after now. I can see on the iPad that the CC is received, and I can assign it to a knob in a virtual synth and see the knob moving. Awesome!

Now, the next step would be to be able to treat these cc commands as any other parameter in default instruments (I’m thinking on lpf, pan, etc) so that they can be assigned to var, or linvar, etc. so they can be easily automated. However, I have no idea where to start for doing this. The FoxDot codebase is huge and I don’t fully understand how all pieces fit.

This is pretty cool - I’ll have a look into apps that can take MIDI input. The codebase is pretty sprawling, yeah, and the ways it works is a bit hacky at times. Currently all synth defs behave the same way and there’s a an if statement in the Server object that checks if the SynthDef is a midi synth and changes some parameters there but that’s a bit silly. I think I’d like to move that logic into the SynthDef objects themselves so I could then create a MIDI SynthDef subclass that just changes the destination and adds any parameters specific parameters.

I’ve been saying I would do this for years now though so we’ll see when I actually get round to it :sweat_smile:

2 Likes

I continue experimenting with midi, and I think I’ve found a bug.

I ran a midi monitor in the iPad, and noticed that each midi note triggered by FoxDot, causes two Note-On in the same instant, and later, when the duration of the note expires, two Note-Off, also in the same instant. Sometimes the note-on/note-off event appears replicated three and even four times, apparently randomly.

This is a bug. Some instruments behave the same when several note-on in the same note are triggered, but other instruments play the same note several times, building up the volume which in some cases causes saturation and clipping.

Trying to investigate further why this could be, I wrote the following mini-program, and debugged it step-by-step in VSCode:

from FoxDot import OSCMessage, Server
import time

msg = OSCMessage("/foxdot_midi")
msg.append(["MidiOut", 60.0, 120, 1.0, 1, 0])
Server.sendOSC(msg)

time.sleep(2)

I also included a line (at line 58) in FoxDot.sc, to print out the OSC message when it comes:

*midi
{
    arg port=0;
    MIDIClient.init;
    midiout = MIDIOut(port);

    OSCFunc(
        {
            arg msg, time, addr, port;
            var note, vel, sus, channel, nudge;
            ("MidiOut:" + msg).postln;   // <------------ This one

Results:

When the line Server.sendOSC(msg) is executed, two (and sometimes more) messages appear in SuperCollider output (when only one was expected):

MidiOut: [ /foxdot_midi, MidiOut, 60.0, 120, 1.0, 1, 0 ]
MidiOut: [ /foxdot_midi, MidiOut, 60.0, 120, 1.0, 1, 0 ]

This, of course, causes the two midi events I see in the iPad.

Digging a bit further, I stepped in the Server.sendOSC() line, to debug the ServerManager.py module. I’ve ran it step by step, until arriving to the line which actually sends the OSC packet over a socket, at line 1237:

        self.socket.sendall(msg.getBinary())

At this moment, I can see in the debugger that msg has the value ['/foxdot_midi', ',sfifii', 'MidiOut', 60.0, 120, 1.0, 1, 0] and that msg.getBytes() returns b'/foxdot_midi\x00\x00\x00\x00,sfifii\x00MidiOut\x00Bp\x00\x00\x00\x00\x00x?\x80\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00' which looks ok.

Then, when I run that line, which sends a single OSC message to SuperCollider, in the SC log I can see that apparently two messages are received. And in the iPad two Note-On events appear.

Apparently FoxDot is doing it right, but for some reason SuperCollider processes several times the same event, which causes the extra midi events. Any clue?

Perhaps I should post this in the github bugtrack?

This could happen if the OSCFunc is being created twice.

hjh

Oh! Of course, thank you! Silly of me…

I had the following setup in supercollider:

FoxDot.start
FoxDot.midi
FoxDot.midi(1)

I thought that FoxDot.midi without arguments simply started the midi server, and with the numeric argument simply changed the midi device to which the messages are sent. But apparently both register the same OSCFunc.

The other cases in which I saw the events repeated three or four times, was probably me running one of those lines again, to change again the midi output device.