aboutsummaryrefslogtreecommitdiffstats
path: root/synth.js
blob: fdf2f87cedf89f9b621a67424fbe372a14cf655c (plain)
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import SimpleJSSynth from './simple-js-synth.js';
import * as pattern from './pattern.js';

const panel = document.getElementById('synth');
const enabled = document.getElementById('synth-enabled');
const freq = document.getElementById('synth-freq');
const poly = document.getElementById('synth-voices');
const optsPaste = document.getElementById('synth-opts-paste');
const optsReset = document.getElementById('synth-opts-reset');

const ctx = new window.AudioContext();

const opts = {};
const voices = [];
const tones = [];

export const getOptions = () => opts;

export const setOptions = (o) => {
  Object.assign(opts, o);

  while (voices.length)
    voices.pop().destroy();

  for (let i = 0; i < opts.polyphony; i++)
    voices.push(SimpleJSSynth(ctx.destination, opts));
};

enabled.onchange = () => {
  if (enabled.checked) ctx.resume();
  else ctx.suspend();

  panel.classList.toggle('controls--minimized', !enabled.checked);
};

freq.onchange = () => {
  let frequency = +freq.value;
  if (!frequency || frequency < 1) frequency = 110;
  opts.frequency = frequency;
};

poly.onchange = () => {
  let polyphony = +poly.value;
  if (!polyphony || polyphony < 1) polyphony = 1;
  setOptions({ polyphony });
};

optsPaste.onclick = () => {
  const json = window.prompt("Please paste the JSON from the right panel of the synth page:");
  setOptions(JSON.parse(json));
};

optsReset.onclick = () => setOptions({
  osc1type: "sine", osc1vol: 0.2, osc1tune: 0,
  osc2type: "square", osc2vol: 0.14, osc2tune: 12,
  osc3type: "sine", osc3vol: 0.05, osc3tune: -6.7,
  attack: 0, decay: 0.3, sustain: 0.5, susdecay: 5,
  cutoff: 36,

  frequency: 110,
  polyphony: 8,
});

optsReset.onclick();

export const on = (key, note, vol=1) => {
  if (!enabled.checked) return;
  ctx.resume();

  if (tones[key]?.note === note) return;
  off(key);

  const freq = opts.frequency * Math.pow(2, note / pattern.getLength());

  const voice = voices.find(s => s.isReady());
  if (!voice) return;

  voice.noteOn(freq, vol);
  tones[key] = off && { note, voice };
};

export const off = (key) => {
  tones[key]?.voice.noteOff();
  delete tones[key];
};

export const isOn = (note) => Object.values(tones).some(t => t.note === note);