import { isArray, isString, last } from "../utils"
import { ERR_EXPECT_PATTERN, ERR_EXPECT_STRING } from "../vm"
import { isArray, isString, last } from "../utils"
import { ERR_EXPECT_PATTERN, ERR_EXPECT_STRING } from "../vm"
The standard library include basic (arithmetic, logic, etc.) commands
wrap A modulo operation that handles negative n more appropriately e.g. wrap(-1, 3) returns 2 see http://en.wikipedia.org/wiki/Modulo_operation see also http://jsperf.com/modulo-for-negative-numbers
const wrap = (a, b) => (a % b + b) % b
op1 A generic stack operation that pops one value and pushes on result
const op1 = fn => ({ stack }) => {
stack.push(fn(stack.pop()))
}
op2 A generic stack operation that pops two values and pushes one result
const op2 = fn => ({ stack }) => {
stack.push(fn(stack.pop(), stack.pop()))
}
export default {
@+, @add: Add two values
[1, 2, "@+"]
"@+": op2((b, a) => a + b),
"@add": "@+",
@-, @sub: Subtract two values
[2, 1, "@-"]
"@-": op2((b, a) => a - b),
"@sub": "@-",
@*, @mul: Multiply two values
[2, 4, "@*"]
"@*": op2((b, a) => a * b),
"@mul": "@*",
@/, @div: Divide two values
[4, 2, "@/"]
"@/": op2((b, a) => b === 0 ? 0 : a / b),
"@div": "@/",
@%, @wrap: Modulo for positive and negative numbers
[4, -2, "@%"]
"@%": op2((b, a) => b === 0 ? 0 : wrap(a, b)),
"@wrap": "@%",
@mod: Standard modulo operation
[4, 2, "@mod"]
"@mod": op2((b, a) => b === 0 ? 0 : a % b),
@neg: The negative of a value
[4, "@neg"]
"@neg": op1(a => -a),
@cond: Conditional execution
[true, "@cond", [<success pattern>], [<fail pattern>]]
"@cond": ({ stack, operations }) => {
const test = stack.pop()
this is the pattern to execute if the test passes
const success = operations.pop()
the next pattern is the “else” part
if (test) {
remove the “else” part
operations.pop()
operations.push(success)
}
},
@>: Greater than
"@>": op2((b, a) => a > b),
@>=: Greater or equal than
"@>=": op2((b, a) => a >= b),
@<: Less than
"@<": op2((b, a) => a < b),
@<=: Less or equal than
"@<=": op2((b, a) => a <= b),
@==: Is equal
"@==": op2((b, a) => a === b),
@!=: Is not equal
"@!=": op2((b, a) => a !== b),
"@!": op1(a => !a),
@!: Logic not
"@not": "@!",
@&&, @and: Logic and
"@&&": op2((b, a) => a && b),
"@and": "@&&",
@||, @or: Logic or
"@||": op2((b, a) => a || b),
"@or": "@||",
Operations related to interact with the current process
@let: Assign a value to the local context
440,"freq","@let"
|
"@let": ({ stack, context }) => context.let(stack.pop(), stack.pop()),
@set: Assign a value to the global context
"@set": ({ stack, context }) => context.set(stack.pop(), stack.pop()),
@get: Push the value of a variable into the stack
"@get": ({ stack, context }) => stack.push(context.get(stack.pop())),
@wait: Wait an amount of time (in beats)
1,"@wait"
"@wait": proc => proc.wait(Math.abs(Number(proc.stack.pop()))),
@sync: Wait until next beat
"@sync": proc => proc.wait(Math.floor(proc.time) + 1 - proc.time),
@scale-rate: Change the current rate by a factor
1.5, "@scale-rate"
"@scale-rate": proc => {
const factor = parseFloat(proc.stack.pop(), 10)
if (factor > 0) proc.rate *= factor
},
"@with-rate": ({ stack, operations, error }) => {
const factor = parseFloat(stack.pop(), 10)
const pattern = operations.pop()
if (!isArray(pattern)) error("@with-rate", ERR_EXPECT_PATTERN, pattern)
operations.push([
factor,
"@scale-rate",
pattern,
1 / factor,
"@scale-rate"
])
},
@dup: Duplicate item (so you can use it twice)
10,"@dup"
"@dup": ({ stack }) => stack.push(last(stack)),
@execute: Execute an instruction
10,"dup","@execute"
"@execute": ({ operations, error }) => {
const instr = operations.pop()
if (isString(instr)) operations.push("@instr")
else error("@execute", ERR_EXPECT_STRING, instr)
},
@: Alias of @execute
10,"dup","@"
"@": "@execute",
@repeat: Repeat
4, "@repeat", ["@kick", 0.5, "@wait"]
"@repeat": ({ stack, operations, error }) => {
const repetitions = stack.pop()
const pattern = last(operations)
if (!isArray(pattern)) error("@repeat", ERR_EXPECT_PATTERN, pattern)
else {
for (let i = 1; i < repetitions; i++) {
operations.push(pattern)
}
}
},
@forever: Repeat forever
"@forever", ["@kick", 0.5, "@wait"]
"@forever": ({ operations, error }) => {
const pattern = last(operations)
if (isArray(pattern) && pattern.length) {
operations.push("@forever")
operations.push(pattern)
} else error("@forever", ERR_EXPECT_PATTERN, pattern)
},
@iter: Iterate a pattern
[["@iter", [0.3, 1]], "amp", "@set"]
"@iter": ({ operations, error }) => {
const pattern = operations.pop()
if (!isArray(pattern) || !pattern.length) {
error("@iter", ERR_EXPECT_PATTERN, pattern)
} else {
Rotates the pattern and plays the first item only each time remove “1st” item, schedule, then push to back:
const first = pattern.splice(0, 1)
operations.push(first)
pattern.push(first)
}
},
@rotate: Rotate a pattern
"@rotate": ({ stack, operations, error }) => {
const pattern = operations.pop()
let rot = stack.pop()
if (isArray(pattern) && pattern.length > 0) {
ensure rot is valid between -args.length to +args.length
rot = rot % pattern.length
var copy = pattern.splice(0)
rotate in-place
pattern.push.apply(pattern, copy.slice(rot))
pattern.push.apply(pattern, copy.slice(0, rot))
schedule a shallow copy:
operations.push(copy)
} else {
error("@rotate", ERR_EXPECT_PATTERN, pattern)
}
},
@mtof: midi to frequency [60, ‘@mtof’]
"@mtof": ({ stack }) => {
const midi = stack.pop()
const freq = 440 * Math.pow(2, (+midi - 69) / 12)
stack.push(freq)
},
@linear: convert a value between two linear scales [value, fromLow, fromHi, toLow, toHi, “@linear”]
"@linear": ({ stack }) => {
const ohi = stack.pop()
const olo = stack.pop()
const ihi = stack.pop()
const ilo = stack.pop()
const v = stack.pop()
if (ihi === ilo) {
stack.push(olo)
} else {
stack.push(olo + (ohi - olo) * ((v - ilo) / (ihi - ilo)))
}
}
}