JX - a quark for realtime interactive systems

I’ve made a quark for helping make realtime interactive systems, where the interaction isn’t happening in code, but coming from a performer, or otherwise over OSC.
This project is a culmination of the past few years of my PhD making works for live audiovisual + performer works. I hope to have some art available to share in the next few months.

In such work, OSC maps are used to defined relationship between visual and audio, human and computer, etc. Each map is a relationship between elements; JX, seeks to allow these relationship to be mutated and combined as objects in there own right.

Hopefully someone might find this useful?!

run Quarks.install("https://github.com/JordanHendersonMusic/JX-supercollider.git") to check it out.

The documentation is limited right now, but there is one tutorial guide (a copy of the github page) under Libraries>JX. It is probably full of spelling and grammatical mistakes as I haven’t had a chance to double check it yet.

There is a lot more info on Github, but here are some highlight:

Synth owned resources

Busses are given as examples, but buffers also exist.

s.waitForBoot {
	var from = JXSynthDef('/from', {
		JXOut.ar(\out, SinOsc.ar(2));   
	});

	var to = JXSynthDef('/to', {
		JXIn.ar(\in, 1)
	})
	.connect( from[\out] -> \in );  
}

flexible connections

// Many 2 one connections
.connect(
	from[\outA] -> \in
	from[\outB] -> \in,
	from[\outC] -> \in,
);
// or like this
.connect( from[\outA, \outB, \outC] -> \in );

// one to many...
.connect( from[\outA] -> [\inA, \inB] );

// many to many - creates 6 connections
.connect( from[\outA, \outB, \outC] -> [\inA, \inB] );

// with a scaling function
.connect( from[\outA] -> _.exprange(200, 400) -> \freq );

importing files as functions

main.scd

JXAssertOpenedFromDirectory("/home/user/my_project_dir");

s.waitForBoot {
	var generators = JXImport.cwd("noises.scd").(
		\groupName: '/noises'
	);
	var outputter = JXSynthDef('/out', {
		JXIn.ar(\in, 2, JXReduce.mean, JXReshape.circular).poll
	})
	.connect( generators[\out] -> \in );

	outputter.getResource(\in).bus.scope;
}

noise.scd

{   |groupName|
	JXGroup(groupName, { |self|
		var pink = JXSynthDef('/pink', {
			JXOut.ar(\out, PinkNoise.ar(0.1!4))
		});
		var white = JXSynthDef('/white', {
			JXOut.ar(\out, WhiteNoise.ar(0.3!4))
		});

		var sum = JXSynthDef('/sum', {
			var in = JXIn.ar(\in, 2);
			JXOut.ar(\out, in);
		})
		.connect(
			pink[\out]  ->  \in,
			white[\out] ->  \in
		);
        
        // makes \out available from outside the group
        // all other resources are encapsulated
		self.forwardResource( sum[\out] -> \out );
	});
}

osc maps between sources and sinks

JXSynthDef('/src', {
	var sig = ....;
    // an internal osc source of data
 	JXOscSrc.kr('/amp', Amplitude.kr(sig, 0.3, 0.1) )
});
JXSynthDef('/sink', {
    // an internal osc sink for data
	var amp = JXOscSink.kr('/amp', 1);
	...
});
var mapAmaker = JXOscMapMk({ |srcs|
	JXOscMap((
		'/sink/amp' : srcs['/src/amp'] * LFNoise2.kr(2),
		'/beep/amp' : -10.dbamp,
        '/some/other/sink/for/data' : srcs['/some/data/src']
	))
});
JXOscMapperSynth({
	var srcs = JXOscStore.getMapSources();
	var mapa = mapAmaker.makeMap(srcs)
	JXOscMapOutput.kr(mapa); 
});

JXOscRelay.init(sendingRate: 120);

mutating and combining osc maps

JXOscMapperSynth({|src|
	var a = mapAmk.makeMap(src);
	var b = mapBmk.makeMap(src);
	var c = mapCmk.makeMap(src);
	var d = mapDmk.makeMap(src);
    // linearly interpolate between maps
	var lerp = JXOscMapLinSelectX(MouseX.kr(0, 3), a, b, c, d);
	
	JXOscMapOutput.kr(lerp);
});

composing osc maps

var mapCmaker = JXOscMapMk({ |srcs|
	var a = mapAmaker.makeMap(srcs);
	var b = mapBmaker.makeMap(srcs);

    // control other osc maps with osc data.
	JXOscMapLinSelectX(srcs['/beep/out/amp'], a, b)
	<+/>         // when similar keys exist, take their mean.
	JXOscMap(( '/beep/freq' : LFNoise0.kr(0.1) ))
});
4 Likes

It does seem like a nice way of setting up the audio processing graph. What is the type of context?

I like to think of supercollider as an audio graph on the server, with a client side running gui thread, and a logical component that is the hook into the external physical interfaces.

I don’t understand, do you mean musical context?

The whole project is really designed to make mutating and sequencing relationships between audio and visual elements easier (or anything kind of parameter and data source).

If all of the data sources and sinks are exposed over OSC, then I wanted to be able to join sources of data to sinks of data using signal processing terminology, for example, take the /perform/mic/pitch/hz, apply a low pass filter to it, rescale into a new range, apply a gate depending on some other data, and then output it to /visual/colour/brightness.

Then I wanted to be able to take that mapping and cross fade it with another.

That way, each map can be thought of as an relationship between elements, which can then be sequenced and otherwise combined.

It’s from the github

var bleeps = JX2SC('/bleeps',
	owner: {
		JXIn.kr(\amp, 1, JXReduce.mean);
		JXIn.kr(\freq, 1, JXReduce.mean);
		JXOut.ar(\out, DC.ar(0!2));
	},
	func: { |context|
		SynthDef(\bleeper, {
			var sin = SinOsc.ar(\freq.kr) * EnvGen.ar(Env.perc, doneAction: 2);
			var out = sin * context.getResource(\amp).asUgen;
			context.getResource(\out).asUgen(out!2);
		}).add;
		s.sync;

There’s a context, but was wondering what that was.

Oh, its the JX2SC instance. It can get the resources from the owner context.getResource(\name), and return a group with context.asGroup — thats it.

I’m going to be redoing a bunch of the documentation soon, as quite a few things aren’t named correctly.

What do associations do anyway behind the hood? For example sum[\out] -> \out ? I noticed you did that in another thread for function signature disambiguation.

Its just a single key value pair, I’m just using the syntax of the arrow to make it clear which way around things go, it could be an array, but that would be messy with nested arrays, and since they don’t use commas, it makes having multiple of them in a function call easier to read.
Here are some examples:

(\a -> \b).key == \a
(\a -> \b).value == \b
\a -> \b -> \c == ((\a -> \b) -> \c)
(4.dup(5) -> 2.dup(2)) == ([ 4, 4, 4, 4, 4 ] -> [ 2, 2 ])
1 Like