OpenMusic ((((LISP)))) Rhythm Trees into sclang

Hi!

I am trying to convert the OpenMusic LISP way to notate rhythm into a typical array of durations in SuperCollider. (I am using an OM LISP old syntax style, not the new one).

In a brief, a 4/4 is written like this (4 (1 1 1 1)) were the 4 is the number of beats and the first parenthesis divide the beat into four 1 duration figures (crotchet).

(4 (1 1 1 1))
image

For making a subdivision one need to put the corresponding beat into a parenthesis and add another parenthesis for the internal subdivision:

(4 (1 (1 (1 1) ) 1 1) )
image

Dividing the first two beats (minim - 2 value) into a crotchet triplet, the third beat into a semiquaver and a dotted quaver (1 (1/2 3/2) ) and the last beat into a quaver triplet

( (4 ( (2 (1 1 1) ) (1 (1/2 3/2) ) (1 (1 1 1 ) ) ) ) )
image

Although I think this (((((((LISP)))))))) style is almost illegible, the rhythm tree structure is really usefull for creating complex nested tuplets with simple numbers, in the style of Brian Ferneyhough:

(4 (6 (1 (2 (3 4) ) 5) ) )
image

So I would like to have a general procedure that converts [4 [1, 2, [1,1,1], 1] ] or [1, 2, [1,1,1], 1] (number of beats can be implicit because I am not converting them score notation) into a duration array like [1, 2/3, 2/3, 2/3, 1].

Any ideas about how to do it?

I thought about using .deepCollect but I could not figure out how to read the previous index/item for calculating the subdivision…

By any chance, is there any Pattern or Quark that help using this kind of structure?

1 Like

Maybe something like?


+Collection {
	asLispString {
		var out="(";
		this.do({ arg item;
			if ( item.isKindOf(Number), { out = out ++ item ++ " " });
			if ( item.isKindOf(Array), { out = out ++ item.asLispString ++ " " });
		});
		^(out ++ ")")
	}

The other way around:


~fromLispList = { |str|
    while { str.contains("  ") } {str = str.replace("  ", " ")};
    str = str.replace("( ", "(").replace("(", "[").replace(")", "]").replace(" ", ", ");
    str.interpret;
};

a = ~fromLispList.("((1 2)    ( 3 4    5))");

Also, I wrote a Rational numbers quark (“Rational”) that can be useful for this kind of thing, and avoid floating point errors. Take a look.

I have written different implementations of RTM rhythmic notation, but haven’t published them as quark yet. You basically need a Tree structure to calculate the correct durations and tuplets.

1 Like

If you just want to translate the RTM into a list of duration (floating-point numbers), James included that in the core library. It works if you want to just convert RTM into durations, I’m not sure that’s what you want to do.


SequenceableCollection : Collection {

// supports a variation of Mikael Laurson's rhythm list RTM-notation.
	convertRhythm {
		var list, tie;
		list = List.new;
		tie = this.convertOneRhythm(list);
		if (tie > 0.0, { list.add(tie) });  // check for tie at end of rhythm
		^list
	}
	sumRhythmDivisions {
		var sum = 0;
		this.do {|beats|
			sum = sum + abs(if (beats.isSequenceableCollection) {
				beats[0];
			}{
				beats
			});
		};
		^sum
	}
	convertOneRhythm { arg list, tie = 0.0, stretch = 1.0;
		var beats, divisions, repeats;
		#beats, divisions, repeats = this;
		repeats = repeats ? 1;
		stretch = stretch * beats / divisions.sumRhythmDivisions;
		repeats.do({
			divisions.do { |val|
				if (val.isSequenceableCollection) {
					tie = val.convertOneRhythm(list, tie, stretch)
				}{
					val = val * stretch;
					if (val > 0.0) {
						list.add(val + tie);
						tie = 0.0;
					}{
						tie = tie - val
					};
				};
			};
		});
		^tie
	}
2 Likes

I think this kind of data structure only reaches its potential if you have a tree structure (with nodes etc) where you can actually manipulate it. If you try to do this with Arrays, I think the job gets a bit messier.

1 Like

Enormous thanks!

This was pretty impossible to find in the docs because it was hidden as RTM and does not mentions explicitly the word tree. Even when searching for RTM it does not show .convertRhythm.

Can you please provide some example that shows when the truncation error between Rational and Float becomes evident? I would like to know until each order one could work with floats properly.

1 Like

SC posts 0.6 but it’s not really honest, all languages have the same problem with floating-point numbers.
Unless it converts to rational numbers, like Perl.

if you want to work with (metric) durations as in music notation, use rational numbers. For me, that’s quite clear.

0.1 + 0.2 + 0.3 == 0.6

false

1 Like

Thanks, this is nice! I somehow missed that built-in convertRhythm method (and the existence of RTM notation itself ;-). I wrote something pretty similar as part of my Bacalao live coding tools in 2019, but it embeds “values” (e.g. degrees, midinotes, whatever) along with the durations. Mine is an “internal” method I use when parsing the pattern notation, but if you happened to call it directly, it would look like this:

BacalaoParser.calculateDurations([0->2, 1->1, 2->3, [3->3, 3->1]->1, 4->3] -> 2)
// -> [ [ 0, 0.4 ], [ 1, 0.2 ], [ 2, 0.6 ], [ 3, 0.15 ], [ 3, 0.05 ], [ 4, 0.6 ] ]

The equivalent with the RTM notation would be (durations only, no degrees here):

[2, [2,1,3,[1, [3,1]],3]].convertRhythm
// -> List[ 0.4, 0.2, 0.6, 0.15, 0.05, 0.6 ]

BTW, you wouldn’t normally call the helper method; instead, in a live coding context, you might create the Pbind pattern like this:

[deg: "[0@2 1 2@3 [3@3 3] 4@3]@2"].pb
// -> Pbind('degree', Ppatlace([ 0, 1, 2, 3, 3, 4 ]), 'dur', Pseq([ 0.4, 0.2, 0.6, 0.15, 0.05, 0.6 ]))
1 Like

By accident I found the existence of .partition which can be used to generate any possible subdivision of a rhythm.

4.partition(3, 0.125);
-> [ 2.125, 1.125, 0.75 ]
-> [ 0.125, 0.125, 3.75 ]
-> [ 0.125, 0.125, 3.75 ]
4.partition(4, 1/8).do({|x|x.partition(4, 1/16).postln})
[ 0.0625, 0.0625, 0.9375 ]
[ 0.0625, 0.0625, 0.0625, 0.9375 ]
[ 0.0625, 0.0625, 0.0625, 0.9375 ]
[ 0.0625, 0.0625, 0.0625, 1.4375 ]
-> [ 0.125, 1.125, 1.125, 1.625 ]

If you write this kind of thing with, for instance, list comprehensions, you can get all possible combinations.

Example:

all {:x, x <- [1, 2, 3].dup(4).allTuples, x.sum == 6 };

-> [ [ 1, 1, 1, 3 ], [ 1, 1, 2, 2 ], [ 1, 1, 3, 1 ], [ 1, 2, 1, 2 ], [ 1, 2, 2, 1 ], [ 1, 3, 1, 1 ], [ 2, 1, 1, 2 ], [ 2, 1, 2, 1 ], [ 2, 2, 1, 1 ], [ 3, 1, 1, 1 ] ]
1 Like

What I can’t figure out is how to use List Comprehension to generate all the permutations of a nested tuplet structure, like Ferneyhough style:

(4 (1 (2 (3 (4 5) ) 6) ) )
(4 (6 (1 (2 (3 4) ) 5) ) )
(4 (5 (6 (1 (2 3) ) 4) ) )
(4 (4 (5 (6 (1 2) ) 3) ) )
(4 (3 (2 (1 (6 5) ) 4) ) )
(4 (2 (1 (6 (5 4) ) 3) ) )

Any ideas about that?

Maybe you can combine rotate with reshapeLike?

[3, 2, 3, 2, 3].reshapeLike([1, [2, [1, 1, 1]]])

Or rotateStruct? (from my local ext)

+ SequenceableCollection {

   rotateStruct { arg nRotations = 1 ;
		
        var newStruct;
        newStruct = this.flat.rotate(nRotations).bubble;
        ^newStruct.reshapeLike(this);
        
    }
}

Example:

[ 1, 2, 1, 3, [ 1, [ 1, 2, 1, 1, 1 ] ], 1, 1, 4 ].rotateStruct
[ 1, 2, 1, 3, [ 1, [ 1, 2, 1, 1, 1 ] ], 1, 1, 4 ].rotateStruct(2)
2 Likes

I have been experimenting with these ideas in Haskell and have found it to be quite enjoyable. I have been gradually documenting and sharing my progress, as I learn. If there are others who are also interested in exploring these concepts, I would be happy to continue sharing what I have written thus far and am open to collaborations.

Things like “rotate tree” aren’t as concise (magic?) as in SC, but often can open the door to other possibilities too.

1 Like

I have no knowledge at all about haskell, except for a little contact from Tidal… Would this ever be possible to do in SC? Maybe in Tidal?

If you can at least talk a little bit about what are the results that you achieved with this it would be really interesting!

You can create Tree data structures, or classes, in SuperCollider. It’s everywhere, actually.

Although I find it particularly easy/clear to define it in Haskell. Same with parsers.