代码中的 JavaScript
变量、函数、循环和 register,以及让共享代码保持安全的沙箱、确定性和限制。
程序就是 JavaScript。大多数程序只是串联模式方法,但你也可以用变量、函数、数组、循环和条件来构建它们。
变量与函数
const 给模式起个名字,方便重复使用。函数可以根据参数构建模式。
const groove = s("bd [~ bd] sd ~, hh*8")
const riff = (root) => n("0 2 4 2").scale(`${root}:minor`)
drums: groove.bank("tr909")
lead: cat(riff("C"), riff("F"), riff("G"), riff("C"))双引号与单引号
双引号(以及反引号)是迷你记谱法:"c3 e3" 是一个两个音符的模式。单引号是普通字符串,
不会被当作迷你记谱法读取,适合包含空格的名字。
n("0 1 2 3").scale('C minor')循环与数组
普通的 JavaScript 就能生成模式。这里用循环构建一个级数数组,再拼接成迷你记谱法:
const steps = []
for (let i = 0; i < 8; i++) steps.push(i % 3 === 0 ? i : "~")
n(steps.join(" ")).scale("A:minor").s("triangle")你自己的方法:register
register(name, fn) 添加一个可以像内置方法一样串联的方法。在函数的参数里,模式排在最后。
register('dub', (amount, pat) => pat.delay(amount).delayfeedback(0.6).room(0.3))
note("c3 ~ ~ eb3 ~ ~ g3 ~").s("square").dub(0.5)沙箱
代码会出现在共享项目和链接里,所以 TuttiDAW 从不把它当作普通的页面脚本运行。简单的程序由 TuttiDAW 自己的解释器读取;其余的在一个封闭的 JavaScript 沙箱(WebAssembly 中的 QuickJS) 里运行,它唯一的能力就是构建模式:
- 没有网络、没有页面、没有存储、没有定时器,也没有
eval; - 不能用
import(...)和fetch;外部音频用samples(...)声明,只有在你按下 Play 时 才由播放器加载; - 每个程序都有工作量预算和时间限制,所以死循环会以报错结束,而不会卡死页面。
确定性
同样的代码总是演奏同样的音符:在你的电脑上、合作者的电脑上、导出时都一样:
Math.random()以及模式函数rand、?、|、sometimes等都有种子;- 时钟是停住的:
Date.now()总是返回同一个时刻; - 每次渲染都从头开始,所以存在变量里的状态不会从这一次播放带到下一次。
const pick = Math.floor(Math.random() * 4)
n(`<${pick} 2 4 6>`).scale("E:minor")这个程序每次运行,挑出的“随机”音都一样。
限制
- 一个程序最长 100,000 个字符(/code 链接最长 20,000);
- 一个声部的一个周期最多 2,048 个事件。
超出限制的程序会在引起问题的代码处报错;其他声部继续演奏。