|
16 | 16 | const pointers = new Map(); |
17 | 17 | const pi = Math.PI; |
18 | 18 | let currentBass = 220.0; |
| 19 | + let frozenBass = 220.0; |
19 | 20 | let sampleBuf = undefined; |
20 | 21 |
|
21 | 22 | fetch("./guitar.wav").then(async r => { |
|
30 | 31 | function chordFreq(semitones) { |
31 | 32 | let k = currentBass * 2 ** (semitones / 12); |
32 | 33 | console.log(k); |
33 | | - if (k < 300) k *= 2; |
34 | | - if (k > 600) k /= 2; |
| 34 | + if (k < 250) k *= 2; |
| 35 | + if (k > 650) k /= 2; |
35 | 36 | return k; |
36 | 37 | } |
37 | 38 |
|
38 | 39 | function noteNameToFrequency(note) { |
39 | | - const i = " D EF G A BC".indexOf(note.charAt(0)) - 8; |
| 40 | + const base = Number(document.querySelector("#base").value); |
| 41 | + const i = ("A BC D EF G ".indexOf(note.charAt(0)) + 1200 - base) % 12 + base; |
40 | 42 | const j = i + !!note.match(/♯/) - !!note.match(/♭/); |
41 | 43 | return 220 * 2 ** (j / 12); |
42 | 44 | } |
43 | 45 |
|
44 | | - function makeOsc(freq, gainValue) { |
45 | | - if (true) { |
46 | | - const osc = ctx.createBufferSource(); |
47 | | - const gain = ctx.createGain(); |
48 | | - gain.gain.value = gainValue; |
49 | | - osc.buffer = sampleBuf; |
50 | | - osc.connect(gain); |
51 | | - osc.gainNode = gain; |
52 | | - gain.connect(mix); |
53 | | - // osc.frequency.value = freq; |
54 | | - osc.playbackRate.value = freq / 110; |
55 | | - osc.start(ctx.currentTime + Math.random()*0.04); |
56 | | - return osc; |
57 | | - |
58 | | - } |
59 | | - const osc = ctx.createOscillator(); |
| 46 | + function makeOsc(freq, gainValue, strumFactor) { |
| 47 | + const osc = ctx.createBufferSource(); |
60 | 48 | const gain = ctx.createGain(); |
61 | 49 | gain.gain.value = gainValue; |
62 | | - osc.type = "triangle"; |
| 50 | + osc.buffer = sampleBuf; |
63 | 51 | osc.connect(gain); |
64 | 52 | osc.gainNode = gain; |
65 | 53 | gain.connect(mix); |
66 | | - osc.frequency.value = freq; |
67 | | - osc.start(); |
| 54 | + // osc.frequency.value = freq; |
| 55 | + osc.playbackRate.value = freq / 110; |
| 56 | + osc.start(ctx.currentTime + Math.random() * strumFactor); |
68 | 57 | return osc; |
69 | 58 | } |
70 | 59 |
|
|
85 | 74 | const centerX = rect.left + rect.width / 2; |
86 | 75 | const centerY = rect.top + rect.height / 2; |
87 | 76 |
|
| 77 | + let isFrozen = document.getElementById("freeze").checked; |
88 | 78 | let freq = noteNameToFrequency(e.target.innerText); |
| 79 | + if (!isFrozen) frozenBass = freq; |
89 | 80 | currentBass = freq; |
| 81 | + if (isFrozen) freq = frozenBass; |
| 82 | + |
90 | 83 | const fourthLower = e.clientY > rect.top + rect.height * 0.65; |
91 | 84 | e.target.style.background = fourthLower |
92 | 85 | ? "linear-gradient(to bottom, #a99 65%, #f80 65%)" |
|
97 | 90 | centerY: centerY, |
98 | 91 | note: e.target.innerText, |
99 | 92 | target: e.target, |
100 | | - oscs: [makeOsc(freq / 2, 0.3)], |
| 93 | + oscs: [makeOsc(freq / 2, 0.3, 0)], |
101 | 94 | }); |
102 | 95 |
|
103 | 96 | for (const v of pointers.values()) { |
|
117 | 110 | const p = pointers.get(pointerId); |
118 | 111 | if (!p) return; |
119 | 112 | for (const osc of p.oscs) { |
120 | | - osc.gainNode.gain.setTargetAtTime(0, ctx.currentTime, 0.005) |
| 113 | + osc.gainNode.gain.setTargetAtTime(0, ctx.currentTime, 0.01) |
121 | 114 | osc.stop(ctx.currentTime + 0.2); |
122 | 115 | } |
123 | 116 | p.target.style.background = ""; |
|
142 | 135 | note: e.target.innerText, |
143 | 136 | target: e.target, |
144 | 137 | voicing: voicing, |
145 | | - oscs: voicing.map(i => makeOsc(chordFreq(i), 0.06)), |
| 138 | + oscs: voicing.map(i => makeOsc(chordFreq(i), 0.06, 0.04)), |
146 | 139 | }); |
147 | 140 | }); |
148 | 141 | b.addEventListener("pointermove", (e) => { |
149 | 142 | const p = pointers.get(e.pointerId); |
150 | 143 | if (!p) return; |
151 | 144 | const detune = e.clientY - p.centerY; |
152 | 145 | for (const osc of p.oscs) { |
153 | | - osc.detune.value = detune * 0.5; |
| 146 | + osc.detune.value = detune * -0.5; |
154 | 147 | } |
155 | 148 | }) |
156 | 149 | b.addEventListener("pointerup", (e) => stop(e.pointerId)); |
157 | 150 | b.addEventListener( |
158 | 151 | "pointerleave", |
159 | 152 | (e) => stop(e.pointerId) |
160 | 153 | ); |
161 | | - |
162 | 154 | } |
163 | 155 | }); |
164 | 156 | </script> |
|
211 | 203 | } |
212 | 204 |
|
213 | 205 | .chord-column { |
| 206 | + display: flex; |
| 207 | + flex-direction: column; |
| 208 | + justify-content: end; |
| 209 | + } |
| 210 | + |
| 211 | + .middle { |
214 | 212 | display: flex; |
215 | 213 | flex-direction: column; |
216 | 214 | justify-content: center; |
| 215 | + align-items: center; |
217 | 216 | } |
218 | 217 |
|
219 | 218 | .bass { |
| 219 | + user-select: none; |
220 | 220 | touch-action: none; |
221 | 221 | background: rgba(0, 0, 0, 0.1); |
222 | 222 | padding: 20px 40px; |
|
267 | 267 | } |
268 | 268 |
|
269 | 269 | .refresh-link { |
| 270 | + user-select: none; |
270 | 271 | position: absolute; |
271 | 272 | top: 4px; |
272 | 273 | right: 4px; |
273 | 274 | } |
| 275 | + |
| 276 | + input[type=range][orient=vertical] |
| 277 | + { |
| 278 | + writing-mode: bt-lr; /* IE */ |
| 279 | + -webkit-appearance: slider-vertical; /* WebKit */ |
| 280 | + width: 12px; |
| 281 | + height: 80%; |
| 282 | + padding: 0 5px; |
| 283 | + } |
| 284 | + |
| 285 | + input[type=checkbox] |
| 286 | + { |
| 287 | + width: 20px; |
| 288 | + height: 20px; |
| 289 | + } |
274 | 290 | </style> |
275 | 291 | </head> |
276 | 292 |
|
|
300 | 316 | <div class="button bass-button">F♯</div> |
301 | 317 | </div> |
302 | 318 | </div> |
| 319 | + <div class="middle"> |
| 320 | + <input id="base" type="range" orient="vertical" min="-12" max="12" value="-8"> |
| 321 | + <!-- <input id="freeze" type="checkbox"> --> |
| 322 | + </div> |
303 | 323 | <div class="chords"> |
304 | 324 | <div class="chord-column"> |
305 | 325 | <div class="button chord-button" data-chord="7 11 14 16">Δ9</div> |
306 | 326 | <div class="button chord-button" data-chord="7 10 14 15">m9</div> |
307 | 327 | <div class="button chord-button" data-chord="7 10 12 17">7s</div> |
308 | 328 | <div class="button chord-button" data-chord="6 10 12 16">♭5</div> |
309 | | - |
310 | 329 | </div> |
311 | 330 | <div class="chord-column"> |
312 | 331 | <div class="button chord-button" data-chord="7 11 12 16">Δ</div> |
|
0 commit comments