This is really cool, thanks for sharing! Your parser code is simple and elegant (unlike mine, which evolved over time and could probably use some revisiting ;-). For my own interest (and maybe yours?), I thought Iād compare your Pdv
to what I support in Bacalaoās pattern notation. Itās actually pretty similarā¦
The difference is I assign the duration 1 to the whole pattern sequence, treating it like āone barā instead of one beat, so youād need to stretch it 4 times to get the same play speed yours would give by default. (Playing these patterns in Bacalao using b.p()
or b.once()
handles this for you, so it actually fits into a bar duration.)
Also, I donāt have any āmarkerā key at the start of a pattern (to use with Pgate
), which is a neat idea! Iād be curious to see how you use it when live coding or composingā¦I guess to make some longer timescale changes (latching on one new change per loop through the pattern).
Here is how I would write some of your examples using Bacalao:
// basic sequence
~p = [degree: "0 1 2 3"].pb.asStream;
~p.next(())
// sequence with rest
~p = [deg: "0 1 ~ 2"].pb.asStream; // note that you can abbreviate many keys (deg)
~p.next(())
// sequence with sub division
~p = [deg: "0 1 [2 3]"].pb.asStream;
~p.next(())
// sequence with nested sub divisions
~p = [deg: "0 1 [2 [3 4]]"].pb.asStream;
~p.next(())
// sequence with irregular sub division
~p = [deg: "0 1 [2 3 4]"].pb.asStream; // another way to write it
~p = "0 1 [2 3 4]".bparse(\degree).asStream; // another way to write it
~p.next(())
// sequence with sub division stretched (Bacalao uses '@' for stretching instead of '^')
~p = [deg: "0 1 [2 3 4]@2"].pb.asStream;
~p.next(())
// sequence with repeated value (we interpret this differently)
// For Bacalao, "0!4 1" would be the same as "0 0 0 0 1"...
~p = [deg: "0!4 1"].pb.asStream;
~p.next(())
// ...whereas "0*4 1" is "[0 0 0 0] 1", which is how you are using '!'
// (I'm using '*' for that, as TidalCycles does)
~p = [deg: "0*4 1"].pb.asStream;
~p.next(())
// sequence with shuffled values each cycle
// ~p = Pbind(\degree, Pdv.parse("[0 1 2 3]$"))
// (I don't have an equivalent in Bacalao pattern notation, but you can do this)
~p = [deg: "0 1 2 3"].pb.scramble.asStream;
~p.next(())
// sequence with randomly selected value
// (I don't have an equivalent to your '#' in pattern notation, but one way would be to
// choose from a environment variable holding an array)
~myNotes = [0, 1, 2, 3]; ~p = [deg: "myNotes:r 7"].pb.asStream;
// You can also just use regular SC code inside the pattern -- as
// long as there are no spaces in it!
~p = [deg: "Pwhite(0,3) 7"].pb.asStream;
~p.next(())
// sequence with alternating values - similar to ppatlace
~p = [deg: "0 <1 2>"].pb.asStream;
~p.next(())
// sequence with alternating values with grouping
// ~p = Pbind(\degree, Pdv.parse("0 <1 [2 3]>")).asStream;
// (Hmm...my pattern parser isn't able to handle grouping
// within alternation right now ;-)
// use with midinote
~p = [midinote: "60 <62 63 64> <67 69>"].pb.asStream; // can also use 'mn' as short key
~p.next(())
If you use my Array.pb
method, you create a Pbind
: when there are multiple patterns (with durations), the rightmost one will override the durations for every other key. However, you can use my PtimeChain
operator (Array.tc
shortcut) to make the leftmost argument set the durations, similar to chaining in TidalCycles:
~p = [deg: "0 <1 2 3> 4 <7 8>", oct: "4@3 5", pan: "[-1 1]!2"].tc.asStream;
~p.next(())
Whereas <>
is the normal chaining operator in SC, you can also use my time-chaining operator <<
(here Iām also using the interpreter pre-processor so it looks more like TidalCycles):
Bacalao.start // start pre-processor that interprets deg"0 1" notation as [deg: "0 1"].pb
~p = (deg"0 <1 2 3> 4 <7 8>" << oct"4@3 5" << pan"[-1 1]!2").asStream;
~p.next(())
Note that my āDSLā also supports ācharacter patternsā, where each character is an event (like a step sequencer), and spaces are rests. It allows things like this:
~p = "01 2__3 ".cparse(\degree) // '_' extends/holds the previous note
// or, if the pre-processor is being used (note this uses single quotes)
~p = deg'01 2__3 '
Which are both equivalent to:
Pbind('degree', Pseq([ 0, 1, Rest(1), 2, 3, Rest(1) ]), 'dur', Pseq([ 0.125, 0.125, 0.125, 0.375, 0.125, 0.125 ]))
You can use any letters/numbers, and there is a way to tell it what Dictionary environment variable to use to lookup values from those characters. Also, for some things like frequency or amplitude, there is a āreasonableā default mapping from characters āa-zA-Z0-9ā to parameter values (for example, \amp
ā9ā is loud, ā0ā is quiet), that can be overridden.
Anyhow, I just thought Iād share a (not-so) different approach to writing patterns with durations.