Skip to content
Miika Alonen edited this page Dec 15, 2022 · 13 revisions

Rules

Ziffers rule mechanism is a string replacement system inspired by L-System, Markov Chains and Regular expressions. Rules can be used to generate fractal melodies, automatas or anything imaginable in terms of Ziffers syntax.

Define transformation rules as hash object and use gen parameter to define amount of generations the rules are run against. Rules can also be used in a loop where current cycle is considered as generation.

Matched values are defined as hash keys and replacements as hash value. Generative notation is processed for each generation by default. For example:

zplay "q 1", rules: {"1"=>"1 3","3"=>"6 (2,7) 3 1"}, gen: 3

Rules replaces everything as a text, so you can use it with any Ziffers syntax like chords. Roman numerals are part of generative notation (roman numeral generates chord in a given key), which requires stable: true to be used. For stable generations the generative notation is processed only for the result. Use debug to see the generated generations. For example:

Ziffers.debug

zplay "( i iv )@(e 0 2 1 3 q 123)",
rules: {
  " i "=>" i^m7 iv ",
  " iv "=>" vii iii i ",
  " vii "=>" iv ii "
}, gen: 6, scale: :blues_major, stable: true

Stable parameter can be used to generate generative syntax, for example the most inefficient way to create random numbers between random numbers:

Ziffers.debug

z1 "(1,7)",
rules: {
  "1"=>"(1,7)",
  "8"=>"(1,7)"
}, gen: 6, stable: true

Rule keys can be strings or regular expressions that represents the string to be matched and replaced. You can feed back the matched string with $ or ${1-9} if using regexp groups. Use evaluate syntax {...} to evaluate mathematical expressions. Some rules may take some time to evaluate and it is more likely to take some time with more generations. To adjust some time off for the string rewrite to parse use rewrite_time to set numbers of beats to sleep before trying to play the results.

zplay "1", rules: {/e (3) ([0-7])/=>"q {$1+1} $2 {$1+(0,3)}",/([qe] )*[1-7]/=>"e 3 1 3"}, gen: 6, rewrite_time: 3 # Using regexgroups $1 and $2
sleep 2
zplay "1 2 3", rules: {/[1-9]/=>"({$*2} [e,q] {$*3})%7"}, gen: 4 # Using single regex and doing mod 7 to the result pitches

Rules can also be combined with loops, for example:

z1 "q 0 1 e 2 3 q 1", rules: {"0"=>"1", "1"=>"3", "3"=>"(1,6)", /[0-9]/=>"3" }
z2 "q0", synth: :dull_bell, scale: :gong, rules: {
  /e([0-6]) e([0-6])/ => "q{$1+2} q{$2+1}",
  /q([0-5]) q([0-5]) q([0-5])/ => "q(0,5)",
  /q[0-5]/ => "e0 e(2,4)"
}

Stochastic rules

Stochastic rules can be written using the conditional assigment with random number:

zplay "q 1 2 3 4", rules: {"1"=>"{%>0.2?(1..9):6}"}, gen: 9
zplay "q 1 2 3 4", rules: {/[1-7]/=>"{%>0.3?(0,3):(4,7)}"}, gen: 3
zplay "q 1 2 3 4", rules: {"1"=>->(){rand<0.2 ? "2 1 3" : "1"}}, gen: 9

Context dependent rules

Use regular expression lookbehind and lookahead syntax. For example this would match 3 only if it is between 1 and 2:

/(?<=1)3(?=2)/

Generation specific rules

Use Array or Ring to indicate which generation the rules will affect, for example:

Rules for first and third generations only:

zplay "q 1 2 3 4", rules: {"3"=>["7 3",nil,"9 4"]}, gen: 12

Rules for every third generation:

zplay "q 1 2 3 4", rules: {"3"=>(ring nil, nil,"1 4 2 3")}, gen: 12

Lambdas

Matched patterns can also be prosessed using lambda fuctions. Support for 0 to 2 parameters where first parameter is generation number and second param is array of matched items.

Simple test:

z1 "0 1 2", rules: { /([0-9])/=> ->(i,g){ print g } }

This example adds following degrees together:

z1 "9 8 7 6 5 4", rules: {/([0-9]) ([0-9])/=>->(i,g){ g[1].to_i+g[2].to_i}, /[0-9]/=>"$ 1 2"  }
z1 "q 0 1 e 2 3 q 1", rules: {"0"=>"1", "1"=>"3", "3"=>->(){rrand_i(1,5)}, /[0-9]/=>"3" }

Replace

Replace is a shorthand for singular rules replacement. It can be used to achieve many things, for example dividing melody to separate parts:

melody = "[:q 0 1 2 0:][:q 2 3 h4:]"
z1 melody, synth: :dsaw, replace: {"1"=>"r","4"=>"r"}
z2 melody, synth: :chiplead, replace: {"0"=>"r","2"=>"r","4"=>"r"}

Markov chains and automata rules

Markov chains can also be created using the rules that will produce next set of notes in a way that the end result does not grow. This type of rule somewhat similar to cellular automata.

This type of rules can be played in a loop without worrying about the ever enlarging expansions of the sequences, for example:

z1 "q 1", seed: 1, rules: {
  "1"=>"2",
  "2"=>"3",
  "3"=>"[1,2]"
}

Alternatively you can define generation using gen, but the loop then plays only that given generation. seed parameter can also be used to change the randomization seed for the generative syntax in the substitutions.

Notice that editing the rules and running the code again wont start automatically from the first generation. Use reset to if you want to start from the beginning when running the code.

Example with reset:

use_synth :chipbass

z1 "q 0 1 e 2 3 4 5", reset: true, seed: 3, rules: {
  "0"=>"[1,3]",
  "1"=>"[0,4]",
  "2"=>"4",
  "3"=>"2",
  "4"=>"[0,2]"
}

Manual string rewrite

String rewrite system can also be called manually using either string_rewrite or string_generations.

print string_rewrite "0 1 2 3", {"0"=>"4","3"=>"2","2"=>"0","4"=>"3"}, 5 # axiom, rules, generations, separator
print string_generations "0 1 2 3", {"0"=>"4","3"=>"2","2"=>"0","4"=>"3"}, 5, " " # axiom, rules, generations, separator
Clone this wiki locally