-
Notifications
You must be signed in to change notification settings - Fork 70
Expand file tree
/
Copy pathlfo.js
More file actions
73 lines (64 loc) · 2.74 KB
/
Copy pathlfo.js
File metadata and controls
73 lines (64 loc) · 2.74 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// LFO tremolo: sine carrier modulated by a low-frequency oscillator.
// Run: node examples/lfo.js
// Run: node examples/lfo.js rate=5 depth=0.5 -d 10s
// Keys: ←/→ ±0.5 Hz rate · ↑/↓ ±0.05 depth · w cycle LFO wave · q quit
import { AudioContext } from 'web-audio-api'
import { args, sec, keys, status, clearLine, pausedTag, help } from './_util.js'
help({
description: 'modulate a 440 Hz carrier with an LFO tremolo',
usage: ['', 'rate=5 depth=0.5 wave=square dur=10s'],
options: [
['rate=<hz>', 'LFO frequency (default: 5)'],
['depth=<0..1>', 'tremolo depth (default: 0.5)'],
['wave=<type>', 'sine, square (default), triangle, or sawtooth'],
['-d, --duration <time>', 'run time with optional s/m/h suffix (default: 30s)'],
],
controls: [
['Space', 'pause/resume'], ['← / →', 'change rate by 0.5 Hz'], ['↑ / ↓', 'change depth by 0.05'],
['W', 'cycle LFO waveform'], ['Q / Esc', 'quit'],
],
})
let { $ } = args()
let dur = sec($('dur', '30'))
let rate = +$('rate', 5)
let depth = +$('depth', 0.5)
let waves = ['sine', 'square', 'triangle', 'sawtooth']
let wIdx = waves.indexOf($('wave', 'square')); if (wIdx < 0) wIdx = 1
let ctx = new AudioContext()
await ctx.resume()
let carrier = ctx.createOscillator()
carrier.frequency.value = 440
let lfo = ctx.createOscillator()
lfo.type = waves[wIdx]
lfo.frequency.value = rate
let lfoGain = ctx.createGain()
lfoGain.gain.value = depth
let offset = ctx.createConstantSource()
offset.offset.value = 1 - depth
let mixer = ctx.createGain()
mixer.gain.value = 0
let master = ctx.createGain()
master.gain.value = 0.3
carrier.connect(mixer).connect(master).connect(ctx.destination)
lfo.connect(lfoGain).connect(mixer.gain)
offset.connect(mixer.gain)
carrier.start(); lfo.start(); offset.start()
let apply = () => {
let t = ctx.currentTime
lfo.frequency.setTargetAtTime(rate, t, 0.02)
lfoGain.gain.setTargetAtTime(depth, t, 0.02)
offset.offset.setTargetAtTime(1 - depth, t, 0.02)
}
let render = status()
let ui = setInterval(() => render(`carrier 440Hz · LFO ${waves[wIdx].padEnd(9)} ${rate.toFixed(2)}Hz · depth ${depth.toFixed(2)} · space pause · ←→ rate · ↑↓ depth · w wave · q quit${pausedTag(ctx)}`), 80)
keys({
left: () => { rate = Math.max(0.1, rate - 0.5); apply() },
right: () => { rate += 0.5; apply() },
up: () => { depth = Math.min(1, depth + 0.05); apply() },
down: () => { depth = Math.max(0, depth - 0.05); apply() },
w: () => { wIdx = (wIdx + 1) % waves.length; lfo.type = waves[wIdx] },
}, () => { clearInterval(ui); clearLine(); ctx.close() }, ctx)
let t = ctx.currentTime + dur
master.gain.setValueAtTime(0.3, t - 0.05)
master.gain.linearRampToValueAtTime(0, t)
setTimeout(() => { clearInterval(ui); clearLine(); ctx.close(); process.exit(0) }, dur * 1000)