-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathsequencer.js
More file actions
45 lines (39 loc) · 1.46 KB
/
sequencer.js
File metadata and controls
45 lines (39 loc) · 1.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Step sequencer: schedule notes precisely using currentTime.
// Run: node examples/sequencer.js
// Run: node examples/sequencer.js bpm=140 -d 10s
// Keys: q quit
import { AudioContext } from 'web-audio-api'
import { args, sec, keys, clearLine } from './_util.js'
let { $ } = args()
let bpm = +$('bpm', 140)
const steps = 16
let stepDuration = 60 / bpm / 4
let dur = sec($('dur', steps * stepDuration))
const ctx = new AudioContext()
await ctx.resume()
const notes = [
440, 0, 523, 0, 587, 0, 659, 0,
587, 523, 440, 0, 330, 0, 440, 0,
]
let t0 = ctx.currentTime
let nLoops = Math.ceil(dur / (steps * stepDuration))
for (let loop = 0; loop < nLoops; loop++) {
for (let step = 0; step < steps; step++) {
let freq = notes[step]
if (!freq) continue
let when = t0 + (loop * steps + step) * stepDuration
if (when > t0 + dur) break
let osc = ctx.createOscillator()
osc.type = 'square'
osc.frequency.value = freq
let env = ctx.createGain()
env.gain.setValueAtTime(0, when)
env.gain.linearRampToValueAtTime(0.3, when + 0.005)
env.gain.exponentialRampToValueAtTime(0.01, when + stepDuration * 0.9)
osc.connect(env).connect(ctx.destination)
osc.start(when); osc.stop(when + stepDuration)
}
}
keys({}, () => { clearLine(); ctx.close() }, ctx)
console.log(`Sequencer: ${bpm} BPM, ${steps}-step pattern (${dur}s) space pause · q quit`)
setTimeout(() => { clearLine(); ctx.close(); process.exit(0) }, dur * 1000 + 200)