Interfacing SC with external software: MIDI or OSC?


#1

Helo Synths,

I was trying to send data from SC to Processing and find some issues.

Firstly, in terms of communication speed, does it really matter if I send MIDI or OSC? Is it all under UDP or TCP ?

Secondly, I had some problem when trying to send MIDI from SC to Processing, because the processing library for that (The Midi Bus) lacks documentation and seems to be incomplete.

For instance, I was only able to send NoteOn, NoteOff, Pitch and tradicional Control messages, which means that everything was constrained to values between 0 and 127. I tried to send PitchBend messages or SongPtr which could allow 16383, but the Processing library doesn’t recognized them.

I was sending MIDI messages exactly in this way:

    // Pattern Guide Cookbook 04: Sending MIDI

MIDIClient.init;    // if not already done

(
    // substitute your own device here
var    mOut = MIDIOut.newByName("FastLane USB", "Port A").latency_(Server.default.latency);

p = Pbind(
    \type, \midi,
        // this line is optional b/c noteOn is the default
        // just making it explicit here
    \midicmd, \noteOn,
    \midiout, mOut,    // must provide the MIDI target here
    \chan, 0,
        // degree is converted to midinote, not just frequency
    \degree, Pwhite(-7, 12, inf),
    \dur, Pwrand([0.25, Pn(0.125, 2)], #[0.8, 0.2], inf),
    \legato, sin(Ptime(inf) * 0.5).linexp(-1, 1, 1/3, 3),
    \amp, Pexprand(0.5, 1.0, inf)
).play(quant: 1);
)

p.stop;

Is it possible to use this same Pattern's template of sending MIDI to send OSC instead?

Is there a way of sendinh MIDI from SC to Processing with "any" range of values?

[]'s
ZĂ© Craum


#2

Okay, I can’t speak to any of the particulars of Processing or the way it receives and parses midi or osc, but based on your questions I can think of a couple things that might get you started in the right direction…

Unless some piece of hardware or software specifically prevents you from doing so, the best way to represent values which require greater than 7-bit (0-127) precision using MIDI is generally with NRPNs (“Non-Registered Parameter Numbers”). NRPNs are made up of four CC messages, the first two of which specify the parameter number, and the second two of which specify a value, both at 14-bit precision (0-16383). That way you don’t have to settle for hacking something up around pitch bend messages like you’d be stuck doing otherwise. It’s all documented in the MIDI standard if you’re interested in learning more.

That said (again, not having any specific knowledge about Processing), I would strongly suspect that OSC is a better solution than MIDI here.

Because you’re ultimately just calling play on every event in a stream, you can use patterns to do literally anything you can do with sclang. It’s just a matter of what an event type’s play function does with the contents of that event, and new event types can be defined easily to do anything that you’d like to be able to do with patterns.

As to whether you can send OSC, if you look at the source code to see how Event is actually implemented, you’ll notice that most of the common event types have play functions that work by parsing the contents of an event to one or more bundles of OSC messages and sending them to the SC server. This is the basic mechanism through which patterns can create nodes, set parameters, etc.

Sorry I can’t be of any specific help regarding Processing, but hopefully this will give you some ideas.


#3

I’ve used oscP5 library in processing many times with no issues, I would recommend very highly over MIDI… can look for some example code if you need it.

Here’s a recent mailing list thread on adding a custom \osc event type: https://www.listarc.bham.ac.uk/lists/sc-users/msg62772.html


#4

Could you please share some those codes?


#5

Use OSC.

MIDI was designed at a time when high-speed data transmission was expensive, so they made a lot of compromises to reduce the amount of data to be transferred.

Those technical limits have been obliterated.

Here’s what OSC can do (that MIDI can’t):

  • Timestamped messages. You can use timestamps to compensate for timing jitter in message transmission. The MIDI standard has no timestamps. If a message is delayed, you lose accuracy.

  • Integer, float and string data types (and others in the OSC standard, but these are the main ones that SC supports). Meanwhile MIDI gives you… 7-bit integers. Whoopee.

MIDI is outdated. It’s pretty much always the wrong answer today (unless you’re dealing with MIDI hardware).

hjh


#6

Some basic code you can try to get OSC communication between SC and Processing, you’ll have to install the oscP5 library first from Sketch > Import Library.

First run this in SC:

OSCdef(\processing, { |msg| msg.postln }, "/processing");
n = NetAddr("127.0.0.1", 5001);

Also in SC, evaluate NetAddr.langPort to find the port that sclang is listening on. For me it is 57120.

Now run this Processing sketch:

import oscP5.*;
import netP5.*;

OscP5 oscP5;
NetAddress myRemoteLocation;

int localPort = 5001;
int remotePort = 57120; // change this to sclang port found above if different

void setup() {
  oscP5 = new OscP5(this, localPort);
  myRemoteLocation = new NetAddress("127.0.0.1", remotePort);
  
  OscMessage myMessage = new OscMessage("/processing");
  myMessage.add("communication established");
  oscP5.send(myMessage, myRemoteLocation);
}

void oscEvent(OscMessage theOscMessage) {
  if (theOscMessage.checkAddrPattern("/value") == true) {
    if (theOscMessage.checkTypetag("f")) { // float value
      print("float value recieved: ");
      float val = theOscMessage.get(0).floatValue();
      println(val);
    }
    if (theOscMessage.checkTypetag("i")) { // int value
      print("int value recieved: ");
      float val = theOscMessage.get(0).intValue();
      println(val);
    }
  }
}

You should see [ /processing, communication established ] in the SC post window once this sketch is running. Now in SC, evaluate:

n.sendMsg("/value", 5.4);
n.sendMsg("/value", 8);

You should see in the Processing console the output:

float value recieved: 5.4
int value recieved: 8.0

#7

Hey Eric,

Until this point it is very clear for me, but it get really confusing when I try to send a whole Event as an OSC message.

Do you have one code example that you define an OSC Event for sending typical Note or Midi keys used in Patterns?


#8

I found this partial solution on sccode.org :

  // declare an event type which sends OSC commands to Renoise

(
Event.addEventType(\renoise, { |server|
  var renoiseOscServer = NetAddr("127.0.0.1", 8000);
  var notes = [~midinote.value, ~ctranspose.value, ~velocity.value, ~sustain.value, ~lag.value, ~timingOffset.value, ~instr.value, ~track.value].flop;
  var timeNoteOn, timeNoteOff, instrument, track, velocity;
  var serverLatency;

  serverLatency = server.latency ? 0;

  notes.do {|note|
    instrument = note[6] ? -1;
    track = note[7] ? -1;
    velocity = note[2].asInt.clip(0,127);

    // sustain and timingOffset are in beats, lag is in seconds
    timeNoteOn = (thisThread.clock.tempo.reciprocal*note[5])+note[4]+server.latency;
    timeNoteOff = (thisThread.clock.tempo.reciprocal*(note[3]+note[5]))+note[4]+server.latency;
    SystemClock.sched(timeNoteOn, {renoiseOscServer.sendMsg("/renoise/trigger/note_on", instrument.asInt, track.asInt, (note[0]+note[1]).asInt, velocity )});
    SystemClock.sched(timeNoteOff, {renoiseOscServer.sendMsg("/renoise/trigger/note_off", instrument.asInt, track.asInt, (note[0]+note[1]).asInt)});
  }
});
)

// Now start Renoise OSC server, load a sample, and try some patterns

// straight timing
(
Pbind(*[
  type: \renoise,
  legato: Pgauss(0.2,0.05,inf),
  dur: 0.2,
  degree: [2,5,12],
  track: Prand([0,1], inf),
  ctranspose: Pseq([0,0,0,0,4,4,4,4,5,5,5,5],inf),
  velocity: Pgauss(64,10,inf),
]).play;
)

http://sccode.org/1-4SN

But what I couldn’t find there was a simple syntax for sending Note Events or Midi Events as OSC messages… Do I really need to re-implement the whole SC’s defaultParentEvent as new OSC Event to send it using regular pattern’s syntax?


#9

There is no existing event type for sending OSC events - this would be a great feature, but it definitely doesn’t exist now.

There might be a simpler way to override here - when a regular note Event is being assembled, the last thing it does is call ~schedBundleArray with the assembled OSC message - this is responsible for sending the bundle with e.g. the /n_new message to the server. This can be overridden by your Event or Pbind - so, you can re-use all of the existing setup logic for note events, and just fix-up and send the bundle to P5 in your custom version of this function. I would try experimenting with this - for example:

Pbind(
    \type, \set,
	\args, [\degree],
    \degree, Pseq([0, 4, 6, 4], inf),
    \schedBundleArray, {
        | lag, offset, server, bundleArray, latency |
        "The bundle looks like: %".format([lag, offset, server, bundleArray, latency]).postln;
    }
).play

You get something like this:

The bundle looks like: [ 0, 0, localhost, [ [ 15, nil, degree, 0 ] ], nil ]
The bundle looks like: [ 0, 0, localhost, [ [ 15, nil, degree, 4 ] ], nil ]

These have some extraneous information - the things you’d be interested in are the part where \degree is specified - but you could extract that part, and reconstruct it into whatever format you wanted, and then send to P4 in your \schedBundleArray function. This isn’t the BEST design, but it’s simple and should solve the problem in a way that’s pretty simple.


#10

I don’t know how complicated your pattern wants to be, but here’s a simple solution:

(
Event.addEventType(\osc, {
  if (~addr.notNil) {
    ~addr.sendMsg(~path, ~message)
  };
})
)

OSCFunc.trace


(
Pbind(
  \type, \osc,
  \addr, NetAddr.localAddr,
  \path, "/values",
  \message, Pseq([1, 2, 3, 4])
).play
)

#11

And here’s a better version that allows you to pick what event keys to send and uses the default event’s abstractions:

(
Event.addEventType(\osc, {
  if (~addr.notNil) {
    ~addr.sendMsg(~path, *~keys.collect({ |key| currentEnvironment[key].value }))
  };
}, Event.parentEvents.default)
)

OSCFunc.trace


(
Pbind(
  \type, \osc,
  \addr, NetAddr.localAddr,
  \path, "/values",
  \keys, `[\freq, \amp],
  \degree, Pseq([1, 2, 3, 4]),
  \db, Pseq([0, -6, -12, -18])
).play
)

#12

Are there any plans from the dev community for doing so? This would be a really awesome feature!


#13

They are quite complicate indeed, but I will try both solution and see how far can I go.
Thanks guys!!!


#14

I don’t believe it’s necessary.

If you’re sending a custom OSC message, then the format is up to you to decide. That means the developers don’t know the format you want – so, the developers can’t do it for you.

As Eric already pointed out, we already have addEventType – so it’s already possible to write a function that picks up whichever values you want from the event, packs them into an OSC array in whatever structure you want, and sends them to whatever NetAddr you need.

Within your event type function, you have access to the default event type’s calculations (e.g. degree --> note --> midinote --> freq etc.) – in fact, Eric already did that (currentEnvironment[key].value).

TL;DR Just use addEventType – that’s why it exists – specifically because the developers can’t anticipate every possible OSC message structure that every user will ever want (so sclang gives you the power to make your own).

hjh