add synth panel
s-ol
24 days ago
9 | 9 | <canvas id="canvas-bg"></canvas> |
10 | 10 | <canvas id="canvas-fg"></canvas> |
11 | 11 | </main> |
12 | <aside class="pattern controls"> | |
13 | <div class="control control--preset"> | |
14 | <label for="pattern-preset">pattern</label> | |
15 | <select id="pattern-preset"> | |
16 | <option value="custom">(custom)</option> | |
17 | <optgroup label="scales"> | |
18 | <option value="major-7" >major</option> | |
19 | <option value="minor-7" >minor</option> | |
20 | <option value="minor-harm-7">harmonic minor</option> | |
21 | <option value="minor-mel-7" >melodic minor</option> | |
22 | <option value="minor-hung-7">hungarian minor</option> | |
23 | <option value="penta" >pentatonic</option> | |
24 | </optgroup> | |
25 | <optgroup label="triads"> | |
26 | <option value="major-3" >major</option> | |
27 | <option value="major-3+">augmented</option> | |
28 | <option value="minor-3" >minor</option> | |
29 | <option value="minor-3-">diminished</option> | |
30 | </optgroup> | |
31 | </select> | |
32 | </div> | |
33 | <div class="control"> | |
34 | <label for="pattern-len">octave size</label> | |
35 | <input id="pattern-len" type="number" min="4" value="12" /> | |
36 | </div> | |
37 | <div class="steps"> | |
38 | <input type="checkbox" checked disabled /> | |
39 | <input type="checkbox" /> | |
40 | <input type="checkbox" /> | |
41 | <input type="checkbox" /> | |
42 | </div> | |
12 | <aside class="pattern panel"> | |
13 | <section id="pattern" class="controls"> | |
14 | <div class="control control--preset"> | |
15 | <label for="pattern-preset">pattern</label> | |
16 | <select id="pattern-preset"> | |
17 | <option value="custom">(custom)</option> | |
18 | <optgroup label="scales"> | |
19 | <option value="major-7" >major</option> | |
20 | <option value="minor-7" >minor</option> | |
21 | <option value="minor-harm-7">harmonic minor</option> | |
22 | <option value="minor-mel-7" >melodic minor</option> | |
23 | <option value="minor-hung-7">hungarian minor</option> | |
24 | <option value="penta" >pentatonic</option> | |
25 | </optgroup> | |
26 | <optgroup label="triads"> | |
27 | <option value="major-3" >major</option> | |
28 | <option value="major-3+">augmented</option> | |
29 | <option value="minor-3" >minor</option> | |
30 | <option value="minor-3-">diminished</option> | |
31 | </optgroup> | |
32 | </select> | |
33 | </div> | |
34 | <div class="control"> | |
35 | <label for="pattern-len">octave size</label> | |
36 | <input id="pattern-len" type="number" min="4" value="12" /> | |
37 | </div> | |
38 | <div class="steps"> | |
39 | <input type="checkbox" checked disabled /> | |
40 | <input type="checkbox" /> | |
41 | <input type="checkbox" /> | |
42 | <input type="checkbox" /> | |
43 | </div> | |
44 | </section> | |
45 | <section id="synth" class="controls"> | |
46 | <div class="control control--toggle"> | |
47 | <label for="synth-enabled">synth</label> | |
48 | <input id="synth-enabled" type="checkbox" checked /> | |
49 | </div> | |
50 | <div class="control"> | |
51 | <label for="synth-freq">center freq</label> | |
52 | <input id="synth-freq" type="number" min="0" value="110" /> | |
53 | </div> | |
54 | <div class="control"> | |
55 | <label for="synth-voices">voices</label> | |
56 | <input id="synth-voices" type="number" min="1" max="32" value="8" /> | |
57 | </div> | |
58 | <div class="control control--toggle"> | |
59 | <label><a href="synth.html" target="_blank">options</a></label> | |
60 | <button id="synth-opts-paste">paste</button> | |
61 | <button id="synth-opts-reset">reset</button> | |
62 | </div> | |
63 | </section> | |
43 | 64 | </aside> |
44 | 65 | <aside class="layout"> |
45 | 66 | <svg |
96 | 117 | id="turn-cw" class="turn" transform="rotate(1.5)" |
97 | 118 | d="M 63.875 -112.84961 L 59.068359 -104.08203 C 69.570355 -98.323686 70.491716 -97.891327 78.669922 -89.982422 L 74.099609 -85.28125 L 96.498047 -79.628906 L 95.910156 -81.712891 L 90.216797 -101.85938 L 85.628906 -97.140625 C 76.875667 -105.51066 74.458477 -107.04659 63.875 -112.84961 z " /> |
98 | 119 | </svg> |
99 | <div class="controls"> | |
120 | <section id="layout" class="panel controls"> | |
100 | 121 | <div class="control control--preset"> |
101 | 122 | <label for="layout-preset">layout</label> |
102 | 123 | <select id="layout-preset"> |
131 | 152 | invert |
132 | 153 | </label> |
133 | 154 | </div> |
134 | </div> | |
155 | </section> | |
135 | 156 | </aside> |
136 | 157 | <script type="module" src="main.js"></script> |
137 | 158 | </body> |
0 | const panel = document.querySelector('aside.layout'); | |
0 | const panel = document.getElementById('layout'); | |
1 | 1 | |
2 | 2 | const svg = document.getElementById('diagram'); |
3 | 3 | const arrows = Array.from(svg.querySelectorAll('.arrow')); |
4 | 4 | |
5 | const preset = panel.querySelector('.control--preset select'); | |
5 | const preset = document.getElementById('layout-preset'); | |
6 | 6 | const controls = Array.from(panel.querySelectorAll('.control--axis')); |
7 | 7 | const steps = Array.from(panel.querySelectorAll('.control--axis > input')); |
8 | 8 | const dirs = Array.from(panel.querySelectorAll('.control--axis .dir input')); |
0 | const panel = document.querySelector('aside.pattern'); | |
0 | const panel = document.getElementById('pattern'); | |
1 | 1 | |
2 | const preset = panel.querySelector('.control--preset select'); | |
2 | const preset = document.getElementById('pattern-preset'); | |
3 | 3 | const patternLength = document.getElementById('pattern-len'); |
4 | 4 | const steps = panel.querySelector('.steps'); |
5 | 5 |
53 | 53 | height: 100%; |
54 | 54 | } |
55 | 55 | |
56 | .panel { | |
57 | display: flex; | |
58 | flex-direction: column; | |
59 | gap: 0.75rem; | |
60 | padding: 1rem 1.5rem; | |
61 | background: #eeeeee; | |
62 | } | |
63 | ||
64 | .panel section + section { | |
65 | padding-top: 0.3rem; | |
66 | border-top: 1px solid #b9bdc1; | |
67 | } | |
68 | ||
56 | 69 | .controls { |
57 | 70 | display: flex; |
58 | 71 | flex-direction: column; |
72 | justify-content: flex-start; | |
73 | overflow: hidden; | |
74 | gap: 0.25rem; | |
75 | } | |
76 | ||
77 | .controls--minimized { | |
78 | max-height: 1.25em; | |
79 | } | |
80 | ||
81 | #layout.controls { | |
59 | 82 | justify-content: center; |
60 | padding: 1rem 1.5rem; | |
61 | gap: 0.25rem; | |
62 | background: #eeeeee; | |
63 | 83 | } |
64 | 84 | |
65 | 85 | /* controls */ |
76 | 96 | } |
77 | 97 | |
78 | 98 | .control--preset { |
79 | padding-bottom: 0.5rem; | |
80 | border-bottom: 1px solid #b9bdc1; | |
81 | 99 | margin-bottom: 0.25rem; |
82 | 100 | } |
83 | 101 | |
85 | 103 | flex: 1; |
86 | 104 | } |
87 | 105 | |
88 | .control--preset label:first-child { | |
106 | .controls .control:first-child label:first-child { | |
89 | 107 | font-weight: bold; |
90 | 108 | } |
91 | 109 | |
110 | .control--toggle label:first-child, | |
92 | 111 | .control--axis label:first-child { |
93 | 112 | flex: 1; |
94 | 113 | } |
0 | 0 | import SimpleJSSynth from './simple-js-synth.js'; |
1 | 1 | import * as pattern from './pattern.js'; |
2 | ||
3 | const panel = document.getElementById('synth'); | |
4 | const enabled = document.getElementById('synth-enabled'); | |
5 | const freq = document.getElementById('synth-freq'); | |
6 | const poly = document.getElementById('synth-voices'); | |
7 | const optsPaste = document.getElementById('synth-opts-paste'); | |
8 | const optsReset = document.getElementById('synth-opts-reset'); | |
2 | 9 | |
3 | 10 | const ctx = new window.AudioContext(); |
4 | 11 | |
18 | 25 | voices.push(SimpleJSSynth(ctx.destination, opts)); |
19 | 26 | }; |
20 | 27 | |
21 | setOptions({ | |
28 | enabled.onchange = () => { | |
29 | if (enabled.checked) ctx.resume(); | |
30 | else ctx.suspend(); | |
31 | ||
32 | panel.classList.toggle('controls--minimized', !enabled.checked); | |
33 | }; | |
34 | ||
35 | freq.onchange = () => { | |
36 | let frequency = +freq.value; | |
37 | if (!frequency || frequency < 1) frequency = 110; | |
38 | opts.frequency = frequency; | |
39 | }; | |
40 | ||
41 | poly.onchange = () => { | |
42 | let polyphony = +poly.value; | |
43 | if (!polyphony || polyphony < 1) polyphony = 1; | |
44 | setOptions({ polyphony }); | |
45 | }; | |
46 | ||
47 | optsPaste.onclick = () => { | |
48 | const json = window.prompt("Please paste the JSON from the right panel of the synth page:"); | |
49 | setOptions(JSON.parse(json)); | |
50 | }; | |
51 | ||
52 | optsReset.onclick = () => setOptions({ | |
22 | 53 | osc1type: "sine", osc1vol: 0.2, osc1tune: 0, |
23 | 54 | osc2type: "square", osc2vol: 0.14, osc2tune: 12, |
24 | 55 | osc3type: "sine", osc3vol: 0.05, osc3tune: -6.7, |
25 | 56 | attack: 0, decay: 0.3, sustain: 0.5, susdecay: 5, |
26 | 57 | cutoff: 36, |
27 | 58 | |
59 | frequency: 110, | |
28 | 60 | polyphony: 8, |
29 | 61 | }); |
30 | 62 | |
63 | optsReset.onclick(); | |
64 | ||
31 | 65 | export const on = (key, note, vol=1) => { |
32 | ctx.resume() | |
66 | if (!enabled.checked) return; | |
67 | ctx.resume(); | |
33 | 68 | |
34 | 69 | if (tones[key]?.note === note) return; |
35 | 70 | off(key); |
36 | 71 | |
37 | const freq = 110.0 * Math.pow(2, note / pattern.getLength()); | |
72 | const freq = opts.frequency * Math.pow(2, note / pattern.getLength()); | |
38 | 73 | |
39 | 74 | const voice = voices.find(s => s.isReady()); |
40 | 75 | if (!voice) return; |