// Some terminology: // a sequence = a serie of elements placed between quotes // a stack = a serie of vertically aligned slices sharing the same overall length // a slice = a serie of horizontally aligned elements { var PatternStub = function(source, alignment) { this.type_ = "pattern"; this.arguments_ = { alignment : alignment}; this.source_ = source; } var OperatorStub = function(name, args, source) { this.type_ = name; this.arguments_ = args; this.source_ = source; } var ElementStub = function(source, options) { this.type_ = "element"; this.source_ = source; this.options_ = options; this.location_ = location(); } var CommandStub = function(name, options) { this.type_ = "command"; this.name_ = name; this.options_ = options; } } start = statement // ----- Numbers ----- number "number" = minus? int frac? exp? { return parseFloat(text()); } decimal_point = "." digit1_9 = [1-9] e = [eE] exp = e (minus / plus)? DIGIT+ frac = decimal_point DIGIT+ int = zero / (digit1_9 DIGIT*) minus = "-" plus = "+" zero = "0" DIGIT = [0-9] // ------------------ delimiters --------------------------- ws "whitespace" = [ \n\r\t]* comma = ws "," ws; quote = '"' / "'" // ------------------ steps and cycles --------------------------- // single step definition (e.g bd) step_char = [0-9a-zA-Z~] / "-" / "#" / "." / "^" / "_" step = ws chars:step_char+ ws { return chars.join("") } // define a sub cycle e.g. [1 2, 3 [4]] sub_cycle = ws "[" ws s:stack ws "]" ws { return s} // define a timeline e.g <1 3 [3 5]>. We simply defer to a stack and change the alignement timeline = ws "<" ws sc:single_cycle ws ">" ws { sc.arguments_.alignment = "t"; return sc;} // a slice is either a single step or a sub cycle slice = step / sub_cycle / timeline // slice modifier affects the timing/size of a slice (e.g. [a b c]@3) // at this point, we assume we can represent them as regular sequence operators slice_modifier = slice_weight / slice_bjorklund / slice_slow / slice_fast / slice_fixed_step / slice_replicate slice_weight = "@" a:number { return { weight: a} } slice_replicate = "!"a:number { return { replicate: a } } slice_bjorklund = "(" ws p:number ws comma ws s:number ws")" { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s } } } } slice_slow = "/"a:number { return { operator : { type_: "stretch", arguments_ :{ amount:a } } } } slice_fast = "*"a:number { return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+a } } } } slice_fixed_step = "%"a:number { return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } } // a slice with an modifier applied i.e [bd@4 sd@3]@2 hh] slice_with_modifier = s:slice o:slice_modifier? { return new ElementStub(s, o);} // a single cycle is a combination of one or more successive slices (as an array). If we // have only one element, we skip the array and return the element itself single_cycle = s:(slice_with_modifier)+ { return new PatternStub(s,"h"); } // a stack is a serie of vertically aligned single cycles, separated by a comma // if the stack contains only one element, we don't create a stack but return the // underlying element stack = c:single_cycle cs:(comma v:single_cycle { return v})* { if (cs.length == 0 && c instanceof Object) { return c;} else { cs.unshift(c); return new PatternStub(cs,"v");} } // a sequence is a quoted stack sequence = ws quote s:stack quote { return s; } // ------------------ operators --------------------------- operator = scale / slow / fast / target / bjorklund / struct / rotR / rotL struct = "struct" ws s:sequence_or_operator { return { name: "struct", args: { sequence:s }}} target = "target" ws quote s:step quote { return { name: "target", args : { name:s}}} bjorklund = "euclid" ws p:int ws s:int { return { name: "bjorklund", args :{ pulse: parseInt(p), step:parseInt(s) }}} slow = "slow" ws a:number { return { name: "stretch", args :{ amount: a}}} rotL = "rotL" ws a:number { return { name: "shift", args :{ amount: "-"+a}}} rotR = "rotR" ws a:number { return { name: "shift", args :{ amount: a}}} fast = "fast" ws a:number { return { name: "stretch", args :{ amount: "1/"+a}}} scale = "scale" ws quote s:(step_char)+ quote { return { name: "scale", args :{ scale: s.join("")}}} comment = '//' p:([^\n]*) // ---------------- grouping -------------------------------- group_operator = cat // cat is another form of timeline cat = "cat" ws "[" ws s:sequence_or_operator ss:(comma v:sequence_or_operator { return v})* ws "]" { ss.unshift(s); return new PatternStub(ss,"t"); } // ------------------ high level sequence --------------------------- sequence_or_group = group_operator / sequence sequence_or_operator = sg:sequence_or_group ws (comment)* {return sg} / o:operator ws "$" ws soc:sequence_or_operator { return new OperatorStub(o.name,o.args,soc)} sequ_or_operator_or_comment = sc: sequence_or_operator { return sc } / comment sequence_definition = s:sequ_or_operator_or_comment // ---------------------- statements ---------------------------- command = ws c:(setcps / setbpm / hush) ws { return c } setcps = "setcps" ws v:number { return new CommandStub("setcps", { value: v})} setbpm = "setbpm" ws v:number { return new CommandStub("setcps", { value: (v/120/2)})} hush = "hush" { return new CommandStub("hush")} // ---------------------- statements ---------------------------- statement = sequence_definition / command