|
1 | 1 | <script setup>
|
2 |
| -import { |
3 |
| - onKeyDown, onKeyUp, useStorage, onKeyStroke |
4 |
| -} from '@vueuse/core'; |
5 |
| -import { useClamp } from '@vueuse/math'; |
6 |
| -import { context } from 'tone'; |
7 |
| -import { onMounted, reactive, computed, watch } from 'vue'; |
8 |
| -import AMYCodes from '#/db/amy-codes.yaml' |
9 |
| -import { midi } from '#/use/midi' |
| 2 | +import { useAMY } from './useAMY'; |
10 | 3 |
|
11 |
| -var amy_started = false; |
12 |
| -var amy_audio_buffer = null; |
13 |
| -var amy_play_message = null; |
14 |
| -var amy_start_web = null; |
15 |
| -var callback = null |
16 | 4 |
|
17 |
| -const amy = reactive({ |
18 |
| - started: false, |
19 |
| - codes: AMYCodes, |
20 |
| -
|
21 |
| - message: computed(() => { |
22 |
| - const arr = [] |
23 |
| - for (let p in amy.knobs) { |
24 |
| - arr.push(`${p}${amy.knobs[p]}`) |
25 |
| - } |
26 |
| - arr.push('A0,0.2,150,1,250,0T59') |
27 |
| -
|
28 |
| - return arr.join('') |
29 |
| - }), |
30 |
| - waveforms: ['SINE', 'PULSE', 'SAW_DOWN', 'SAW_UP', 'TRI', 'NOISE', 'KS', 'PCM', 'ALGO', 'PARTIAL', 'PARTIALS'], |
31 |
| -
|
32 |
| - knobs: { |
33 |
| - p: useClamp(useStorage('amy-patch', 248), 0, 1024), |
34 |
| - w: useClamp(useStorage('amy-waveform', 7), 0, 11), |
35 |
| - V: useClamp(useStorage('amy-volume', 1), 0, 10), |
36 |
| - d: useClamp(useStorage('amy-duty', 0.5), 0.001, 0.999), |
37 |
| - o: useClamp(useStorage('amy-algo', 1), 1, 32), |
38 |
| - b: useClamp(useStorage('amy-feedback', 0), 0, 1), |
39 |
| - }, |
40 |
| - note: useClamp(60, 10, 127), |
41 |
| -
|
42 |
| -
|
43 |
| - history: [] |
44 |
| -}) |
45 |
| -
|
46 |
| -function useAMY() { |
47 |
| -
|
48 |
| - watch(() => midi.note, n => { |
49 |
| - amy.play(n.number, n.velocity / 127) |
50 |
| - }) |
51 |
| -
|
52 |
| - watch(() => amy.message, m => { |
53 |
| - amy_play_message(m) |
54 |
| - amy.history.unshift(m) |
55 |
| - }) |
56 |
| -
|
57 |
| - onKeyDown('a', () => { amy.play(amy.note, 1) }) |
58 |
| - onKeyUp('a', () => { amy.play(amy.note, 0) }) |
59 |
| -
|
60 |
| - onKeyStroke('ArrowLeft', () => { amy.knobs.patch-- }) |
61 |
| - onKeyStroke('ArrowRight', () => { amy.knobs.patch++ }) |
62 |
| -
|
63 |
| - onKeyStroke('ArrowUp', (e) => { e.preventDefault(); amy.note++ }) |
64 |
| - onKeyStroke('ArrowDown', (e) => { e.preventDefault(); amy.note-- }) |
65 |
| -
|
66 |
| - onMounted(() => { |
67 |
| - import('./amyJS.js').then(amy => { |
68 |
| - const { Module } = amy |
69 |
| - Module.onRuntimeInitialized = function () { |
70 |
| - amy_audio_buffer = Module.cwrap( |
71 |
| - 'web_audio_buffer', 'number', ['number', 'number'] |
72 |
| - ); |
73 |
| - amy_play_message = Module.cwrap( |
74 |
| - 'amy_play_message', null, ['string'] |
75 |
| - ); |
76 |
| - amy_start_web = Module.cwrap( |
77 |
| - 'amy_start_web', null, ['number'] |
78 |
| - ); |
79 |
| - } |
80 |
| -
|
81 |
| - var dataHeap = null; |
82 |
| - var dataPtr = null; |
83 |
| - var data = new Float32Array(); |
84 |
| -
|
85 |
| - callback = function audioCallback(l) { |
86 |
| - if (dataHeap == null || dataHeap.length == 0) { |
87 |
| - data = new Float32Array(l.length); |
88 |
| - var nDataBytes = data.length * data.BYTES_PER_ELEMENT; |
89 |
| - dataPtr = Module._malloc(nDataBytes); |
90 |
| - dataHeap = new Uint8Array(Module.HEAPU8.buffer, dataPtr, nDataBytes); |
91 |
| - dataHeap.set(new Uint8Array(data.buffer)); |
92 |
| - } |
93 |
| -
|
94 |
| - amy_audio_buffer(dataHeap.byteOffset, l.length); |
95 |
| - var result = new Float32Array(dataHeap.buffer, dataHeap.byteOffset, data.length); |
96 |
| -
|
97 |
| - for (let i = 0; i < l.length; i++) { |
98 |
| - l[i] = result[i]; |
99 |
| - } |
100 |
| - } |
101 |
| - }) |
102 |
| - }) |
103 |
| -
|
104 |
| - var audioRunning = false; |
105 |
| - var scriptNode = null; |
106 |
| - var source = null; |
107 |
| - var audioCtx = null; |
108 |
| -
|
109 |
| - function setupAudio(fn) { |
110 |
| -
|
111 |
| - var AudioContext = window.AudioContext |
112 |
| - || window.webkitAudioContext |
113 |
| - || false; |
114 |
| -
|
115 |
| - if (!AudioContext) { |
116 |
| - console.error("No Audio") |
117 |
| - return |
118 |
| - } |
119 |
| - audioCtx = new AudioContext({ |
120 |
| - sampleRate: 48000 |
121 |
| - }); |
122 |
| - source = audioCtx.createBufferSource(); |
123 |
| - scriptNode = audioCtx.createScriptProcessor(256, 0, 1); |
124 |
| - scriptNode.onaudioprocess = function (audioProcessingEvent) { |
125 |
| - fn(audioProcessingEvent.outputBuffer.getChannelData(0)); |
126 |
| - }; |
127 |
| - } |
128 |
| -
|
129 |
| - function startAudio() { |
130 |
| -
|
131 |
| - if (audioRunning) return; |
132 |
| - amy_start_web?.(); |
133 |
| - setupAudio(callback); |
134 |
| - scriptNode.connect(audioCtx.destination); |
135 |
| - source.start(); |
136 |
| - audioRunning = true; |
137 |
| - // setTimeout(() => { |
138 |
| - // console.log('started') |
139 |
| - // amy_play_message("v54l1w8n70p30") |
140 |
| - // }, 50) |
141 |
| - amy_started = true; |
142 |
| - } |
143 |
| -
|
144 |
| - function stopAudio() { |
145 |
| - audioRunning = false; |
146 |
| - amy_started = false; |
147 |
| - audioCtx.suspend().then(function () { |
148 |
| - console.log('stopped') |
149 |
| - }); |
150 |
| - } |
151 |
| -
|
152 |
| - amy.play = (note, velocity = 0) => { |
153 |
| - if (!amy_started) startAudio() |
154 |
| - let osc = `v${note * 20}` |
155 |
| - if (velocity > 0) { |
156 |
| - let setup = osc + amy.message |
157 |
| - amy_play_message(setup) |
158 |
| - amy.history.unshift(setup) |
159 |
| - } |
160 |
| -
|
161 |
| - let msg = osc + `n${note}l${velocity.toFixed(2)}` |
162 |
| - amy.history.unshift(msg) |
163 |
| - amy_play_message(msg) |
164 |
| - } |
165 |
| -
|
166 |
| - amy.reset = (n = 100000) => { |
167 |
| - let msg = `S${n}` |
168 |
| - amy.history.unshift(msg) |
169 |
| - amy_play_message(msg) |
170 |
| - } |
171 |
| -
|
172 |
| - return amy |
173 |
| -} |
174 |
| -
|
175 |
| -
|
176 |
| -useAMY() |
| 5 | +const { knobs, waveforms, reset, note, play, message, history } = useAMY() |
177 | 6 |
|
178 | 7 | </script>
|
179 | 8 |
|
180 | 9 | <template lang='pug'>
|
181 | 10 | .p-2.rounded-xl.border-1.m-2.select-none.flex.flex-wrap.gap-2
|
182 | 11 | transition-group(name="fade")
|
183 | 12 | control-rotary(
|
| 13 | + key="Wave" |
184 | 14 | :min="0"
|
185 | 15 | :max="11"
|
186 | 16 | param="Wave"
|
187 |
| - v-model="amy.knobs.w" |
| 17 | + v-model="knobs.w" |
188 | 18 | :fixed="0"
|
189 |
| - :unit="amy.waveforms[amy.knobs.w]" |
| 19 | + :unit="waveforms[knobs.w]" |
190 | 20 | )
|
191 | 21 | control-rotary(
|
| 22 | + key="Patch" |
192 | 23 | :min="1"
|
193 | 24 | :max="1024"
|
194 | 25 | param="Patch"
|
195 |
| - v-model="amy.knobs.p" |
196 |
| - v-if="amy.knobs.w > 6" |
| 26 | + v-model="knobs.p" |
| 27 | + v-if="knobs.w > 6" |
197 | 28 | :fixed="0"
|
198 | 29 | )
|
199 | 30 | control-rotary(
|
| 31 | + key="Duty" |
200 | 32 | :min="0.001"
|
201 | 33 | :max=".999"
|
202 | 34 | param="Duty"
|
203 | 35 | :step="0.01"
|
204 |
| - v-if="amy.knobs.w == 1" |
205 |
| - v-model="amy.knobs.d" |
| 36 | + v-if="knobs.w == 1" |
| 37 | + v-model="knobs.d" |
206 | 38 | :fixed="2"
|
207 | 39 | )
|
208 | 40 | control-rotary(
|
| 41 | + key="Algo" |
209 | 42 | :min="1"
|
210 | 43 | :max="32"
|
211 | 44 | param="ALGO"
|
212 |
| - v-if="amy.knobs.w == 8" |
213 |
| - v-model="amy.knobs.o" |
| 45 | + v-if="knobs.w == 8" |
| 46 | + v-model="knobs.o" |
214 | 47 | :fixed="0"
|
215 | 48 | )
|
216 | 49 | control-rotary(
|
| 50 | + key="Feedback" |
217 | 51 | :min="0"
|
218 | 52 | :max="1"
|
219 | 53 | param="Feedback"
|
220 |
| - v-model="amy.knobs.b" |
221 |
| - v-if="amy.knobs.w>5" |
| 54 | + v-model="knobs.b" |
| 55 | + v-if="knobs.w > 5" |
222 | 56 | :fixed="1"
|
223 | 57 | :step="0.01"
|
224 | 58 | )
|
225 | 59 | control-rotary(
|
| 60 | + key="volume" |
226 | 61 | :min="0"
|
227 | 62 | :max="10"
|
228 | 63 | param="Volume"
|
229 |
| - v-model="amy.knobs.V" |
| 64 | + v-model="knobs.V" |
230 | 65 | :fixed="1"
|
231 | 66 | :step="0.01"
|
232 | 67 | )
|
233 |
| - .flex-auto |
234 |
| - button.text-button(@click="amy.reset()") RESET |
| 68 | + .flex-auto(key="flex") |
| 69 | + button.text-button(@click="reset()" key="reset") RESET |
235 | 70 | .p-2.rounded-xl.border-1.m-2.select-none.flex.flex-wrap.gap-4
|
236 | 71 | control-rotary(
|
237 | 72 | :min="1"
|
238 | 73 | :max="127"
|
239 | 74 | param="Note"
|
240 |
| - v-model="amy.note" |
| 75 | + v-model="note" |
241 | 76 | :fixed="0"
|
242 | 77 | unit="MIDI"
|
243 | 78 | )
|
244 | 79 | button.flex.items-center.gap-4.select-none.p-4.rounded-xl.bg-green-300.dark-bg-green-900.active-font-bold(
|
245 |
| - @mousedown.prevent.stop="amy.play(amy.note,1)" |
246 |
| - @touchstart.prevent.stop="amy.play(amy.note,1)" |
247 |
| - @mouseup.prevent.stop="amy.play(amy.note,0)" |
248 |
| - @touchend.prevent.stop="amy.play(amy.note,0)" |
| 80 | + @mousedown.prevent.stop="play(note, 1)" |
| 81 | + @touchstart.prevent.stop="play(note, 1)" |
| 82 | + @mouseup.prevent.stop="play(note, 0)" |
| 83 | + @touchend.prevent.stop="play(note, 0)" |
249 | 84 | )
|
250 | 85 | .i-la-play.text-4xl
|
251 | 86 |
|
252 |
| -.p-4.flex.items-center.font-mono {{ amy.message }} |
| 87 | +.p-4.flex.items-center.font-mono {{ message }} |
253 | 88 |
|
254 | 89 | .top-16.right-4.p-4.w-80.max-h-80vh.overflow-hidden.flex.flex-col.opacity-30.pointer-events-none.font-mono.text-sm.fixed
|
255 |
| - .font-bold.border-b-2 {{ amy.message }} |
| 90 | + .font-bold.border-b-2 {{ message }} |
256 | 91 | transition-group(name="fade")
|
257 |
| - .p-0(v-for="rec in amy.history" :key="rec") {{ rec }} |
| 92 | + .p-0(v-for="rec in history" :key="rec") {{ rec }} |
258 | 93 | //- .flex.flex-wrap.gap-4
|
259 | 94 | //- .text-2xl Floats
|
260 | 95 | //- .p-2.flex(v-for="(knob,key) in amy.knobs" :key="key")
|
|
0 commit comments