Extracting pattern's analytic expression from already executed code

Hi everyone!

I am trying to convert a midi file into a regular Pattern expression, so that I can change some of the it values and behaviors, e.g. I would like to load a MIDI file and have a general Pbind code whose pairs represents the MIDI values.

When using SimpleMIDIFile it is possible to create a pattern (a Ppar) that executes the MIDI file appropriately. Is it possible to extract the raw user side code that have originated this pattern?

// download a Bach midi file:
"curl http://www.bachcentral.com/BachCentral/ORGAN/toccata1.mid -o ~/Desktop/toccata1.mid".unixCmd;

// read it
m = SimpleMIDIFile.read( "~/Desktop/toccata1.mid" );

// play it (cmd-. to stop)
m.p.play; // takes a few seconds to start because this midifile starts with a rest
m.p.postln;

I know that the contents of this Ppar are available when I execute m.p.inspect, but is it possible to obtain the original expression like this?

Pbind(
\instrument, \default,
\dur, Pseq ([1, 2, 3, 4], 1),
\chan, Pseq([1, 2, 3, 4], 1),
\midinote, Pseq([1, 2, 3, 4], 1),
\amp, Pseq([1, 2, 3, 4], 1),
\sustain, Pseq([1, 2, 3, 4], 1),
);

All the best,
Fellipe

If you’re willing to hack around some, this is code that I wrote for a project to ingest MIDI files so they can be used with patterns:

The LOADER loads one or more midi files (according to the path passed in, see example), merges the content onto a single timeline, and returns an OSequence of Events. From there, you can use OSequence:asStream to turn it into a regular Event stream (ala a Pbind). LOADER does some smart things, like combining several notes at the same point in time into one Event. There may be some parameters to tweak to control this, you’ll have to look at the source.

PBIND_PHRASE takes the events returned by LOADER and turns them into separate streams of values, and an actual Pbind filled with Pseq’s as you described. It also does things like unwraps \midinote values back into a \degree plus \octave. I think it even adds an appropriate \scale value - it’s debatable whether this is useful… :slight_smile:

These require a few Quarks, specified in the example. You’ll have to read the docs of those to clarify what those components do. Though there’s no real need to use them extensively, OSequence is a useful way to perform operations on sequences of events.

1 Like

Hi @scztt , thanks for sharing this!!

Is there something missing? when running the lines

var metaphrase, metabind;
#metaphrase, metabind = Require("PBIND_PHRASE").(~eventStream);

I am getting the error message:

ERROR: Message 'minItem' not understood.

I just fixed in my gist, try grabbing PBIND_PHRASE again. Though, you’ll also see this error if ~eventStream is nil, so make sure you’ve successfully loading something via the previous LOADER step.

Would it be possible to extract the resultant analytical expression of an sclang defined function (not only patterns expressions) ?

For instance, on Fredrik Olofsson recursive tweet0011 :

play{f={|o,i|if(i>0,{SinOsc.ar([i,i+1e-4]**2*f.(o,i-1),f.(o,i-1)*1e-4,f.(o,i-1))},o)};f.(60,6)/60}

goes on generating expressions like:

f(6,1) = SinOsc.ar(freq: [1,1+1e-4]**2*6, phase: 6*1e-4, mul: 6)

f(6,2) = SinOsc.ar(freq: [2,2+1e-4]**2*SinOsc.ar(freq: [6.0,6.00120006], phase: 0.0006, mul: 6), phase: SinOsc.ar(freq: [6.0,6.00120006], phase: 0.0006, mul: 6)*1e-4, mul: SinOsc.ar(freq: [6.0,6.00120006], phase: 0.0006, mul: 6))

Would it be possible to extract this style of language code ?

In my opinion, this is so difficult as to be not really worth the effort.

I did try just now with a SynthDef. There are so many special cases – it’s just very painful, and not readable… time spent making that work really well would be time not spent making music. I’d personally advise against it.

EDIT: Partially working sketch.

(
var printControl = { |ugen, stream(Post)|
	stream << "Control.names("
	<< ugen.channels.collect(_.name).asCompileString
	<< ")."
	<< ugen.methodSelectorForRate
	<< "("
	<< ugen.values.asCompileString
	<< ")"
};

var ugenName = { |ugen| ugen.class.name.asString.toLower };

var printOutputProxy = { |ugen, ugens, stream(Post)|
	var src = ugen.source;
	var name = ugenName.(src);
	var list = ugens[name];
	var srcIndex = list.asArray.indexOf(src);
	if(srcIndex.isNil) {
		Error("Unknown proxy source" ++ src).throw;
	};
	stream << name << srcIndex << "[" << ugen.outputIndex << "]";
};

var printUGen = { |ugen, ugens, stream(Post)|
	stream << ugen.class.name << "." << ugen.methodSelectorForRate;
	if(ugen.inputs.size > 0) {
		stream << "(";
		ugen.inputs.do { |in, i|
			var name, j;
			if(i > 0) { stream << ", " };
			case
			{ in.isKindOf(OutputProxy) } {
				printOutputProxy.(in, ugens, stream);
			}
			{ in.isUGen } {
				name = ugenName.(in);
				j = ugens[name].asArray.indexOf(in);
				if(j.isNil) {
					ugen.inspect;
					Error("Unknown input" ++ in).throw;
				};
				stream << name << j;
			}
			{
				stream << in;
			};
		};
		stream << ")";
	};
};

f = { |def, stream(Post)|
	var ugens = Dictionary.new;
	stream << "SynthDef('" << def.name << "', {\n";
	def.children.do { |ugen|
		var class = ugenName.(ugen);
		var count = ugens[class].size;
		stream << "\tvar " << class << count << " = ";
		if(ugen.isKindOf(Control)) {
			printControl.(ugen, stream);
		} {
			printUGen.(ugen, ugens, stream);
		};
		ugens[class] = ugens[class].add(ugen);
		stream << ";\n";
	};
	stream << "})"
};
);

(
d = SynthDef(\test, { |out, gate = 1, freq = 440, amp = 0.1|
	var eg = EnvGen.kr(Env.adsr, gate, doneAction: 2);
	var osc = SinOsc.ar(freq);
	Out.ar(out, (osc * eg * amp).dup);
}).add;
)

f.(d)

SynthDef('test', {
	var control0 = Control.names([ 'out', 'gate', 'freq', 'amp' ]).kr([ 0.0, 1, 440, 0.1 ]);
	var envgen0 = EnvGen.kr(control0[1], 1.0, 0.0, 1.0, 2, 0.0, 3, 2, -99, 1.0, 0.01, 5, -4.0, 0.5, 0.3, 5, -4.0, 0.0, 1.0, 5, -4.0);
	var sinosc0 = SinOsc.ar(control0[2], 0.0);
	var binaryopugen0 = BinaryOpUGen.ar(sinosc0, envgen0);
	var binaryopugen1 = BinaryOpUGen.ar(binaryopugen0, control0[3]);
	var out0 = Out.ar(control0[0], binaryopugen1, binaryopugen1);
})

But EnvGen is really a special case – that won’t work as shown. Oh and *OpUGens are also special cases (needs the operator) which I didn’t do because I ran out of time. Anyway maybe this illustrates what you’re up against…

hjh

1 Like

Thanks! Really interesting! Specifically in this case, I would like to illustrate with hard code how complex it would be to write some expressions without recursive or iterative solution, and also test how the interpreter would perform with such a long expression.

In that case, I think it would be easier to create a few custom objects, with two jobs: 1/ represent recursive calculation structures, and 2/ write code for them.

With existing pattern or UGen objects, you lose too much of the original expression, e.g. [1,1+1e-4]**2*6 will fold down to an array of two numbers immediately – even if you write code for the resulting array, the de-compiled code would lose the squared-times-six relationship. I’m skeptical that you’ll be able to recover structure accurately from objects that aren’t designed to execute operations immediately when possible.

But there is nothing to stop you from building your own object structure, and including methods for each part of that structure to write its own code. I’m doing that in my live coding system.

hjh

1 Like

thanks for this implementation.

i have two questions:

1.) the instrument which is assigned to the Pattern is always named after the MIDI file, when running this code:

(
var metaphrase, metabind;
#metaphrase, metabind = Require("PBIND_PHRASE").(~eventStream);

metabind.play; // <-- a Pbind representing the original event stream
metaphrase; // <-- each key as a separeate array?
)

-> ( 'instrument': Fill1, 'octave': 3, 'degree': [ -6.0 ], 'scale': Scale([ 0 ], 12, Tuning([ 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0 ], 2.0, "ET12")), 
  'amp': 0.2, 'sustain': 0.056770833333333, 'delta': 0.011458333333333, 'chan': 9 )

after loading:

~loaded = Require("LOADER").("~/Fill1.mid");
can this be changed to an instrument of my choice somewhere in the Loader.scd?

2.) is it possible to split the different voices? my goal would be to have the rhythm extracted from the midi file and separate it for the different voices to have one array of durations for each voice.
thanks :slight_smile: