SuperCollider - generating MIDI from audio [SOLVED]

Hi. I wanted to use SuperCollider for realtime audio analysis/machine-listening-type stuff and controlling hardware effects, via MIDI CC messages, in response. However, I keep running into this error:
ERROR: Operator 'asInteger' applied to a UGen is not supported in scsynth.

The audio input is always a float and just about any kind of analysis outputs floats, but the MIDIOut.control() function takes integers. OK, fine, but every way I’ve tried to cast or truncate or otherwise select an appropriate integer has triggered either that error or something startlingly opaque[^1]. The SC docs have nothing relevant and I haven’t been able to find anything elsewhere.

Am I missing something simple? Is there a hacky workaround? Or is this literally impossible?

[^1]: Example: I tried to use a conditional structure to choose one of several fixed control() commands and got ERROR: binary operator '&&' failed. RECEIVER: false. ???

Can you not just round the float to the nearest int, and use it as-is?

I tried several variations on that. The result of .rounding a float is still a float. control() applies .asInteger to all its arguments automatically, but if it’s coming from a UGen rather than a constant, you get the error I quoted. And the entire point is that I’m trying to use values that depend on the incoming audio, so UGens have to be involved somehow.

EDIT: In fact, the fact that control() applies .asInteger automatically when that will always fail for UGens in the synth engine makes me wonder whether I’m supposed to have MIDIOut on the language side only…somehow…

Would using busses help at all?

Grasping at straws, because I’m just starting out with Supercollider myself.

I hit a similar problem with .set setting args for a SynthDef instance. It only seemed to work with constant values. As soon as a UGen was involved, it failed.

I stumbled across another thread on the scsynth forum that suggested control-rate busses were the solution for sending realtime parameter changes to an instance of a SynthDef object, after instantiation.

Seems a bit “sledgehammer to crack a nut”, but if it works…

yes, MIDIOut is a sclang class, not a UGen

yes, Synth is the client-side representation of a UGen graph running on the server. Synth.set is called from the client.

it’s just what control busses are for - connecting synth inputs and outputs to each other. a bus isn’t a complicated object - it’s really just a location in the server’s memory that holds a value. control and audio busses basically differ only in the rate at which they are updated.

but sure, Synth.set (or Pattern bindings) is sufficient if you only ever want to change synth arguments from the client.

@nonsmeddy would you be able to post a stripped down example of what you tried /. what you want?

ok well here is an example of:

  • a synth writing to a control bus
  • client reading from that control bus, and sending the value to a midi device
  • making another synth and using it to modulate the first, through a 2nd bus

here it is verbosely with lots of comments

MIDIClient.init; // initialize MIDI
m = MIDIOut(0);  // get the first output device

Routine { // use a Routine (aka a thread) for synchronization with the server
	s = Server.default;
	s.boot;
	s.sync; // wait for the server to be ready

	// a control-rate bus which our synth will write to
	~lfo_out_bus = Bus.control(s, 1);

	// make a new synthdef (a control-rate sine)...
	~lfo = SynthDef.new(\lfo, {
		arg hz=1, out, mul=1, add=0;
		Out.kr(out, SinOsc.kr(hz, mul:mul, add:add));
	}).play(target:s, ///... and play it on the server...
		args: [   //... with some arguments...
			\out, ~lfo_out_bus.index // ... including the target bus
		]
	);


	// now there are different things we could do to read the bus.
	// here, we'll just explicitly read values from it in an endless loop.
	inf.do {
		~lfo_out_bus.get(
			// getting a bus value is asnychronous, so the method takes a callback...
			{
				arg val; // ...whose argument is the bus value.
				var scaled; // the value is in [-1, 1], so we want to rescale it
				scaled = (val+1)/2 * 127;
				postln(scaled);
				m.control(1, 10, scaled); // send it out on CC 10, channel 1 (
                                                           /// or - gotcha! - 2 if receiver uses 1-based indexing.)
			}
		);
		0.1.wait; // initiate a read every 100 milliseconds
		// (we could go faster but this is so we don't choke on the print statements)
	}

}.play; // make everything go




/// if we want to, say, modulate the LFO speed with another lfo,
/// another control bus is the way to do it

// this is a bit hacky, but we'll just make another routine here...
Routine {
	8.wait; // ... and use it to wait for some arbitrary time for the earlier stuff to happen
	~lfo_hz_bus = Bus.control(s, 1); // make another bus
	~lfo.map(\hz, ~lfo_hz_bus); // map it to the synth
	~lfo_mod = Synth.new(\lfo, // we already sent this def to the server
		args: [
			\hz, 0.2,        // slower
			\add:1.1,          // scaled up to [0.1, 2.1]
			\out, ~lfo_hz_bus.index // and sending to the modulation bus
		],
		target: ~lfo, addAction:\addBefore // make sure the modulator executes before its destination
	);
}.play; // make it go

and here it is written more compactly. with faster polling.

MIDIClient.init;
m = MIDIOut(0);

Routine {
	s = Server.default;
	s.boot;
	s.sync;

	~lfo_out_bus = Bus.control(s, 1);

	~lfo = SynthDef.new(\lfo, {
		arg hz=1, out, mul=1, add=0;
		Out.kr(out, SinOsc.kr(hz, mul:mul, add:add));
	}).play(s, [\out, ~lfo_out_bus.index]);

	s.sync;
	
	~lfo_hz_bus = Bus.control(s, 1);
	~lfo.map(\hz, ~lfo_hz_bus);

	~lfo_mod = Synth.new(\lfo, [\hz, 0.2, \add:1.1, \out, ~lfo_hz_bus.index], ~lfo, \addBefore);

	inf.do {
		~lfo_out_bus.get({ |val| m.control(1, 10, (val+1)/2 * 127); });
		0.01.wait;
	}
}.play;

many tasks in SC can be solved with multiple techniques. the other main way that i’d get stuff from server->client is with SendTrig in a synth and OscFunc (or OSCdef) in sclang. this is appropriate when you want the synth to initiate the transmission instead of polling it.

(side note, i believe there are some newer syntax shortcuts that don’t require you to use .map at all, but i guess my brain is too fossilized to learn or remember them.)

HTH


oh and in keeping with the Questions philosophy, here is the “solution” on github, more memoriously

2 Likes

YES. This works. Many many thanks! I was basically trying to put everything (except starting the server and creating the MIDIOut) inside of a SynthDef. Clearly I need to reread about Routines, Tasks, etc, since I retained none of it.

(Since this category is for discrete solvable questions, do we change the topic title or anything like that when there’s a solution?)

1 Like

great, glad to hear.

if you haven’t read it yet (or would like to again,) the help page on client-server architecture is probably the most important one in the SC docs

oh that seems like a good idea.

2 Likes