Skip to content

Instantly share code, notes, and snippets.

@dmorgan-github
Last active March 2, 2023 04:00

Revisions

  1. dmorgan-github revised this gist Mar 2, 2023. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,6 @@
    /*
    NOTE: There is a Quark for this - https://github.com/dmorgan-github/Pdv
    Copy to Extensions folder and re-compile
    Ryhthmically sequences numeric values which can then be used with any event key, e.g. degree or midinote.
  2. dmorgan-github revised this gist Nov 6, 2022. 1 changed file with 74 additions and 18 deletions.
    92 changes: 74 additions & 18 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -4,14 +4,16 @@ Copy to Extensions folder and re-compile
    Ryhthmically sequences numeric values which can then be used with any event key, e.g. degree or midinote.
    operators:
    " " - empty space separates beats/values
    ~ - rest
    [] - beat sub division
    <> - alternating values
    ^ - stretch duration
    ! - repeat value
    $ - shuffle group of values
    # - choose from group of values
    " " - empty space separates beats/values
    ~ - rest
    [] - beat sub division
    <> - alternating values
    ^n - stretch duration - where n is a float
    !n - repeat value - where n is an integer
    $ - shuffle group of values
    #(nn) - choose from group of values - optionally provide weights as integers 0-9
    %n - chance value is chosen vs rest - where n is an integer 0-9 - can be used on value or group
    note: a key of \g1 is added to the stream at beginning of each cycle which can then be used with Pgate
    @@ -84,6 +86,30 @@ examples:
    // sequence with randomly selected value
    ~p = Pbind(\degree, Pdv.parse("[0 1 2 3]#"))
    equivalent: Pbind(\degree, Prand([0, 1, 2, 3], inf), \dur, 1)
    // optionally provide weights to values
    ~p = Pbind(\degree, Pdv.parse("[0 1 2 3]#(4321)"))
    equivalent: Pbind(\degree, Pwrand([0, 1, 2, 3], [4, 3, 2, 1].normalizeSum, inf), \dur, 1)
    // sequence with value or rest chosen by % chance
    ~p = Pbind(\degree, Pdv.parse("0%4 1%3 2%9 3%0")).asStream;
    ~p.next(Event.default)
    ( 'degree': rest, 'dur': Rest(1.0), 'g1': true )
    ( 'degree': rest, 'dur': Rest(1.0) )
    ( 'degree': 2.0, 'dur': 1.0 )
    ( 'degree': rest, 'dur': Rest(1.0) )
    ( 'degree': rest, 'dur': Rest(1.0), 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': 2.0, 'dur': 1.0 )
    ( 'degree': rest, 'dur': Rest(1.0) )
    // sequence with value or rest chosen by % chance at group level
    ~p = Pbind(\degree, Pdv.parse("[0 1 2 3]%4")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 0.25, 'g1': true )
    ( 'degree': 1.0, 'dur': 0.25 )
    ( 'degree': 2.0, 'dur': 0.25 )
    ( 'degree': rest, 'dur': Rest(0.25) )
    // sequence with alternating values - similar to ppatlace
    ~p = Pbind(\degree, Pdv.parse("0 <1 2>")).asStream;
    @@ -155,24 +181,41 @@ Pdv {

    var parse;

    parse = {|obj, div=1, stretch=1|
    parse = {|obj, div=1, stretch=1, chance=9|

    var val, rep, shuf;
    var val, rep, shuf, choose, weights;
    var result = ();

    val = obj['val'].value;
    stretch = (obj['stretch'] ?? stretch).value.asFloat;
    chance = (obj['chance'] ?? chance).value.asInteger;
    rep = (obj['rep'] ?? 1).value.asInteger;
    choose = obj['choose'];
    weights = obj['weights'];

    if (choose == true) {
    if (val.isSequenceableCollection) {
    if (weights.notNil) {
    val = val.wchoose(weights.normalizeSum);
    }{
    val = val.choose;
    }
    }
    };

    if (val.isSequenceableCollection) {
    var size = val.size;
    val.do({|item|
    parse.(item, div * size.reciprocal, stretch);
    parse.(item, div * size.reciprocal, stretch, chance);
    });
    }{

    if (obj['type'] == \value) {

    if ( (chance/9).coin.not ) {
    val = \rest;
    };

    if (val.isRest) {
    div = Rest(div);
    };
    @@ -183,7 +226,7 @@ Pdv {
    gate = nil
    })
    } {
    parse.(val, div, stretch)
    parse.(val, div, stretch, chance)
    }
    };
    };
    @@ -211,22 +254,22 @@ Pdv {
    var mylist = List.new;
    mylist = parse.(val, mylist);
    result.add(
    // is it necessary to do this here?
    // or would it be better to move to rout function
    item['val'] = if (item['shuf'] == true) {
    { mylist.asArray.scramble }
    } {
    if (item['choose'] == true) {
    { mylist.asArray.choose }
    } {
    mylist
    }
    mylist
    };
    );
    } {
    if (item['type'] == \alt) {
    var mylist = List.new;
    mylist = parse.(val, mylist);
    result.add(

    // we need to create the routine here
    // otherwise we end up creating it each cycle
    // and it would sequnce properly
    item['val'] = Routine({
    var cnt = 0;
    inf.do({|i|
    @@ -265,6 +308,8 @@ Pdv {
    'rest', "^\~",
    'shuf', "^\\$",
    'choose', "^\#",
    'chance', "^\%",
    'weights', "^\\([0-9]+\\)",
    '[', "^\\[",
    ']', "^\\]",
    '<', "^\<",
    @@ -296,6 +341,7 @@ Pdv {
    spec.pairsDo({|k, v|
    if (result.isNil) {
    var val = match.(v, str[cursor..]);
    //[k, v, val].debug("match");
    if (val.notNil) {
    if (k.isNil) {
    getNext.()
    @@ -324,6 +370,7 @@ Pdv {
    var exit = false;
    while ({ hasMoreTokens.() and: { exit.not } }, {
    var token = getNextToken.();
    //token.debug("token");
    switch(token['type'],
    // entities
    'number', {
    @@ -339,12 +386,21 @@ Pdv {
    'rep', {
    list.last['rep'] = getNextToken.()['val'].asInteger;
    },
    'chance', {
    list.last['chance'] = getNextToken.()['val'].asInteger;
    },
    'shuf', {
    list.last['shuf'] = true;
    },
    'choose', {
    list.last['choose'] = true;
    },
    'weights', {
    var weights = token['val'];
    weights = weights.findRegexp("\\d+")[0][1];
    weights = weights.asString.as(Array).collect(_.digit);
    list.last['weights'] = weights;//.debug("weights");
    },
    // grouping delimiters
    '[', {
    var result;
  3. dmorgan-github revised this gist Nov 2, 2022. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,7 @@
    /*
    Copy to Extensions folder and re-compile
    Ryhthmically sequences numeric values which can then be used with any event key, e.g. degree or midinote.
    operators:
    " " - empty space separates beats/values
  4. dmorgan-github revised this gist Nov 2, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -130,7 +130,7 @@ examples:
    ( 'dur': 1.0, 'midinote': 60.0, 'g1': true )
    ( 'dur': 1.0, 'midinote': 67.0 )
    // you can further modulate values and durations with event keys
    // you can further modulate values and durations with regular event keys
    // e.g. modulate durations with \stretch
    ~p = Pbind(\degree, Pdv.parse("0 1 [2 3]"), \stretch, 0.5).asStream;
    ~p.next(Event.default)
  5. dmorgan-github revised this gist Nov 2, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -3,9 +3,9 @@ Copy to Extensions folder and re-compile
    operators:
    " " - empty space separates beats
    " " - empty space separates beats/values
    ~ - rest
    [] - sub division
    [] - beat sub division
    <> - alternating values
    ^ - stretch duration
    ! - repeat value
  6. dmorgan-github revised this gist Nov 2, 2022. No changes.
  7. dmorgan-github revised this gist Nov 2, 2022. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@ Copy to Extensions folder and re-compile
    operators:
    " " - empty space separates beats
    ~ - rest
    [] - group
    [] - sub division
    <> - alternating values
    ^ - stretch duration
    ! - repeat value
  8. dmorgan-github revised this gist Nov 2, 2022. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -6,8 +6,8 @@ operators:
    " " - empty space separates beats
    ~ - rest
    [] - group
    <> - alternating group
    ^ - stretch durations
    <> - alternating values
    ^ - stretch duration
    ! - repeat value
    $ - shuffle group of values
    # - choose from group of values
  9. dmorgan-github revised this gist Nov 2, 2022. 1 changed file with 11 additions and 2 deletions.
    13 changes: 11 additions & 2 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -4,6 +4,7 @@ Copy to Extensions folder and re-compile
    operators:
    " " - empty space separates beats
    ~ - rest
    [] - group
    <> - alternating group
    ^ - stretch durations
    @@ -16,13 +17,21 @@ note: a key of \g1 is added to the stream at beginning of each cycle which can t
    examples:
    // basic sequence
    ~p = Pbind(\degree, Pdv.parse("0 1 2 3")).asStream;
    ~p = Pbind(\degree, Pdv.parse("0 1 2 3")).asStream; // commas are ignored
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': 2.0, 'dur': 1.0 )
    ( 'degree': 3.0, 'dur': 1.0 )
    // sequence with rest
    ~p = ~p = Pbind(\degree, Pdv.parse("0 1 ~ 2")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': rest, 'dur': Rest(1.0) )
    ( 'degree': 2.0, 'dur': 1.0 )
    // sequence with sub division
    ~p = Pbind(\degree, Pdv.parse("0 1 [2 3]")).asStream;
    ~p.next(Event.default)
  10. dmorgan-github revised this gist Nov 2, 2022. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,6 @@
    /*
    Copy to Extensions folder and re-compile
    operators:
    " " - empty space separates beats
  11. dmorgan-github revised this gist Nov 2, 2022. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,5 @@
    /*
    operators:
    " " - empty space separates beats
    [] - group
  12. dmorgan-github revised this gist Nov 2, 2022. 1 changed file with 121 additions and 21 deletions.
    142 changes: 121 additions & 21 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -1,31 +1,131 @@
    /*
    Pbind(\degree, Pdv.parse("0 1 2 3"))
    -> Pbind(\degree, Pseq([0 1 2 3], inf), \dur, 1)
    Pbind(\degree, Pdv.parse("0 1 [2 3]"))
    -> Pbind(\degree, Pseq([0 1 2 3], inf), \dur, Pseq([1, 1, 0.5, 0.5], inf))
    Pbind(\degree, Pdv.parse("0 1 [2 [3, 4]]"))
    -> Pbind(\degree, Pseq([0 1 2 3, 4], inf), \dur, Pseq([1, 1, 0.5, 0.25, 0.25], inf))
    operators:
    " " - empty space separates beats
    [] - group
    <> - alternating group
    ^ - stretch durations
    ! - repeat value
    $ - shuffle group of values
    # - choose from group of values
    note: a key of \g1 is added to the stream at beginning of each cycle which can then be used with Pgate
    examples:
    // basic sequence
    ~p = Pbind(\degree, Pdv.parse("0 1 2 3")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': 2.0, 'dur': 1.0 )
    ( 'degree': 3.0, 'dur': 1.0 )
    Pbind(\degree, Pdv.parse("0 1 [2 3, 4]"))
    -> Pbind(\degree, Pseq([0 1 2 3, 4], inf), \dur, Pseq([1, 1, 1/3, 1/3, 1/3], inf))
    // sequence with sub division
    ~p = Pbind(\degree, Pdv.parse("0 1 [2 3]")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': 2.0, 'dur': 0.5 )
    ( 'degree': 3.0, 'dur': 0.5 )
    // sequence with nested sub divisions
    ~p = Pbind(\degree, Pdv.parse("0 1 [2 [3 4]]")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': 2.0, 'dur': 0.5 )
    ( 'degree': 3.0, 'dur': 0.25 )
    ( 'degree': 4.0, 'dur': 0.25 )
    Pbind(\degree, Pdv.parse("0 1 [2 3, 4]^2"))
    -> Pbind(\degree, Pseq([0 1 2 3, 4], inf), \dur, Pseq([1, 1, 1/1.5, 1/1.5, 1/1.5], inf))
    Pbind(\degree, Pdv.parse("0!4 1"))
    -> Pbind(\degree, Pseq([0, 0, 0, 0, 1], inf), \dur, Pseq([0.25, 0.25, 0.25, 0.25, 1], inf))
    // sequence with irregular sub division
    ~p = Pbind(\degree, Pdv.parse("0 1 [2 3 4]")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': 2.0, 'dur': 0.33333333333333 )
    ( 'degree': 3.0, 'dur': 0.33333333333333 )
    ( 'degree': 4.0, 'dur': 0.33333333333333 )
    // sequence with sub division stretched
    ~p = Pbind(\degree, Pdv.parse("0 1 [2 3 4]^2")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': 2.0, 'dur': 0.66666666666667 )
    ( 'degree': 3.0, 'dur': 0.66666666666667 )
    ( 'degree': 4.0, 'dur': 0.66666666666667 )
    // sequence with repeated value
    ~p = Pbind(\degree, Pdv.parse("0!4 1")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 0.25, 'g1': true )
    ( 'degree': 0.0, 'dur': 0.25 )
    ( 'degree': 0.0, 'dur': 0.25 )
    ( 'degree': 0.0, 'dur': 0.25 )
    ( 'degree': 1.0, 'dur': 1.0 )
    // sequence with shuffled values each cycle
    ~p = Pbind(\degree, Pdv.parse("[0 1 2 3]$"))
    equivalent: Pbind(\degree, Pshuf([ 1, 3, 2, 0 ], inf), \dur, 0.25)
    // sequence with randomly selected value
    ~p = Pbind(\degree, Pdv.parse("[0 1 2 3]#"))
    equivalent: Pbind(\degree, Prand([0, 1, 2, 3], inf), \dur, 1)
    // shuffle
    Pbind(\degree, Pdv.parse("[0 1 2 3]$"))
    -> Pbind(\degree, Pshuf([ 1, 3, 2, 0 ], inf), \dur, 0.25)
    // sequence with alternating values - similar to ppatlace
    ~p = Pbind(\degree, Pdv.parse("0 <1 2>")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 2.0, 'dur': 1.0 )
    // choose
    Pbind(\degree, Pdv.parse("[0 1 2 3]#"))
    -> Pbind(\degree, Prand([0, 1, 2, 3], inf), \dur, 1)
    // sequence with alternating values with grouping
    ~p = Pbind(\degree, Pdv.parse("0 <1 [2 3]>")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0 )
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 2.0, 'dur': 0.5 )
    ( 'degree': 3.0, 'dur': 0.5 )
    // alternating values with stretched durs
    ~p = Pbind(\degree, Pdv.parse("0 <1 [2 3]>^2")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 1.0, 'dur': 2.0 )
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 2.0, 'dur': 1.0 )
    ( 'degree': 3.0, 'dur': 1.0 )
    // combining operators
    ~p = Pbind(\degree, Pdv.parse("0 <[2 3 4]$ [7 9 10]#>")).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 3.0, 'dur': 0.33333333333333 )
    ( 'degree': 4.0, 'dur': 0.33333333333333 )
    ( 'degree': 2.0, 'dur': 0.33333333333333 )
    ( 'degree': 0.0, 'dur': 1.0, 'g1': true )
    ( 'degree': 7.0, 'dur': 1.0 )
    // use with midinote
    ~p = Pbind(\midinote, Pdv.parse("60 <[62 63 64]$ [67 69 70]#>")).asStream
    ~p.next(Event.default)
    ( 'dur': 1.0, 'midinote': 60.0, 'g1': true )
    ( 'dur': 0.33333333333333, 'midinote': 62.0 )
    ( 'dur': 0.33333333333333, 'midinote': 63.0 )
    ( 'dur': 0.33333333333333, 'midinote': 64.0 )
    ( 'dur': 1.0, 'midinote': 60.0, 'g1': true )
    ( 'dur': 1.0, 'midinote': 67.0 )
    // you can further modulate values and durations with event keys
    // e.g. modulate durations with \stretch
    ~p = Pbind(\degree, Pdv.parse("0 1 [2 3]"), \stretch, 0.5).asStream;
    ~p.next(Event.default)
    ( 'degree': 0.0, 'dur': 1.0, 'stretch': 0.5, 'g1': true )
    ( 'degree': 1.0, 'dur': 1.0, 'stretch': 0.5 )
    ( 'degree': 2.0, 'dur': 0.5, 'stretch': 0.5 )
    ( 'degree': 3.0, 'dur': 0.5, 'stretch': 0.5 )
    */

  13. dmorgan-github created this gist Nov 2, 2022.
    271 changes: 271 additions & 0 deletions Pdv.sc
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,271 @@
    /*
    Pbind(\degree, Pdv.parse("0 1 2 3"))
    -> Pbind(\degree, Pseq([0 1 2 3], inf), \dur, 1)
    Pbind(\degree, Pdv.parse("0 1 [2 3]"))
    -> Pbind(\degree, Pseq([0 1 2 3], inf), \dur, Pseq([1, 1, 0.5, 0.5], inf))
    Pbind(\degree, Pdv.parse("0 1 [2 [3, 4]]"))
    -> Pbind(\degree, Pseq([0 1 2 3, 4], inf), \dur, Pseq([1, 1, 0.5, 0.25, 0.25], inf))
    Pbind(\degree, Pdv.parse("0 1 [2 3, 4]"))
    -> Pbind(\degree, Pseq([0 1 2 3, 4], inf), \dur, Pseq([1, 1, 1/3, 1/3, 1/3], inf))
    Pbind(\degree, Pdv.parse("0 1 [2 3, 4]^2"))
    -> Pbind(\degree, Pseq([0 1 2 3, 4], inf), \dur, Pseq([1, 1, 1/1.5, 1/1.5, 1/1.5], inf))
    Pbind(\degree, Pdv.parse("0!4 1"))
    -> Pbind(\degree, Pseq([0, 0, 0, 0, 1], inf), \dur, Pseq([0.25, 0.25, 0.25, 0.25, 1], inf))
    // shuffle
    Pbind(\degree, Pdv.parse("[0 1 2 3]$"))
    -> Pbind(\degree, Pshuf([ 1, 3, 2, 0 ], inf), \dur, 0.25)
    // choose
    Pbind(\degree, Pdv.parse("[0 1 2 3]#"))
    -> Pbind(\degree, Prand([0, 1, 2, 3], inf), \dur, 1)
    */

    Pdv {

    *new {
    }

    *rout {|list|

    var gate = nil;

    ^Prout({|inval|

    var parse;

    parse = {|obj, div=1, stretch=1|

    var val, rep, shuf;
    var result = ();

    val = obj['val'].value;
    stretch = (obj['stretch'] ?? stretch).value.asFloat;
    rep = (obj['rep'] ?? 1).value.asInteger;

    if (val.isSequenceableCollection) {
    var size = val.size;
    val.do({|item|
    parse.(item, div * size.reciprocal, stretch);
    });
    }{

    if (obj['type'] == \value) {

    if (val.isRest) {
    div = Rest(div);
    };
    rep.do({|i|
    inval[\g1] = gate;
    inval['dur'] = div * rep.reciprocal * stretch;
    inval = val.embedInStream(inval);
    gate = nil
    })
    } {
    parse.(val, div, stretch)
    }
    };
    };

    inf.do({
    gate = true;
    list.asArray.do({|val|
    parse.(val);
    });
    });

    inval;
    });
    }

    *sequence {|list|

    var parse;

    parse = {|list, result|

    list.do({|item|
    var val = item['val'];
    if (item['type'] == \group) {
    var mylist = List.new;
    mylist = parse.(val, mylist);
    result.add(
    item['val'] = if (item['shuf'] == true) {
    { mylist.asArray.scramble }
    } {
    if (item['choose'] == true) {
    { mylist.asArray.choose }
    } {
    mylist
    }
    };
    );
    } {
    if (item['type'] == \alt) {
    var mylist = List.new;
    mylist = parse.(val, mylist);
    result.add(

    item['val'] = Routine({
    var cnt = 0;
    inf.do({|i|
    if (item['shuf'] == true) {
    mylist = mylist.asArray.scramble;
    };
    mylist.wrapAt(i).yield;
    cnt = cnt + 1;
    });
    })
    );
    }{
    result.add( item )
    }
    }
    });
    result;
    };

    ^parse.(list, List.new);
    }

    *tokenize {|str|

    var exec, match;
    var getNextToken;
    var hasMoreTokens;
    var spec;
    var cursor = 0;

    // as pairs
    spec = [
    'number', "^[+-]?([0-9]*[.])?[0-9]+",
    'stretch', "^\\^",
    'rep', "^\!",
    'rest', "^\~",
    'shuf', "^\\$",
    'choose', "^\#",
    '[', "^\\[",
    ']', "^\\]",
    '<', "^\<",
    '>', "^\>",
    nil, "^\\s+",
    nil, "^\,",

    ];

    hasMoreTokens = {
    cursor < str.size;
    };

    match = {|regex, str|
    var val = nil;
    var m = str.findRegexp(regex);
    if (m.size > 0) {
    val = m[0][1];
    cursor = cursor + val.size;
    };
    val;
    };

    getNextToken = {
    var getNext;
    var result = nil;
    getNext = {
    if (hasMoreTokens.()) {
    spec.pairsDo({|k, v|
    if (result.isNil) {
    var val = match.(v, str[cursor..]);
    if (val.notNil) {
    if (k.isNil) {
    getNext.()
    }{
    result = (
    type: k,
    val: val
    );
    }
    }
    }
    });
    };
    };

    getNext.();

    if (result.isNil) {
    "unexpected token %".format(str[cursor]).throw
    };
    result;
    };

    exec = {|list|

    var exit = false;
    while ({ hasMoreTokens.() and: { exit.not } }, {
    var token = getNextToken.();
    switch(token['type'],
    // entities
    'number', {
    list.add( (val:token['val'].asFloat, type:\value) )
    },
    'rest', {
    list.add( (val:\rest, type:\value) )
    },
    // modifiers
    'stretch', {
    list.last['stretch'] = getNextToken.()['val'].asFloat
    },
    'rep', {
    list.last['rep'] = getNextToken.()['val'].asInteger;
    },
    'shuf', {
    list.last['shuf'] = true;
    },
    'choose', {
    list.last['choose'] = true;
    },
    // grouping delimiters
    '[', {
    var result;
    list.add( () );
    result = exec.(List.new);
    list.last['val'] = result;
    list.last['type'] = 'group'
    },
    ']', {
    exit = true
    },
    '<', {
    var result;
    list.add( () );
    result = exec.(List.new);
    list.last['val'] = result;
    list.last['type'] = 'alt'
    },
    '>', {
    exit = true
    }
    );
    });

    list;
    };

    ^exec.(List.new);
    }

    *parse {|str|
    var list, seq;
    list = Pdv.tokenize(str);
    seq = Pdv.sequence(list)
    ^Pdv.rout(seq);
    }
    }