4track 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +80 -0
  3. package/dist/assets/btn_fwd.svg +30 -0
  4. package/dist/assets/btn_normal.png +0 -0
  5. package/dist/assets/btn_pause.svg +30 -0
  6. package/dist/assets/btn_play.svg +25 -0
  7. package/dist/assets/btn_pressed.png +0 -0
  8. package/dist/assets/btn_rec.svg +25 -0
  9. package/dist/assets/btn_rew.svg +30 -0
  10. package/dist/assets/btn_stop.svg +25 -0
  11. package/dist/assets/casette_hiss.mp3 +0 -0
  12. package/dist/assets/casette_hiss_compressed.mp3 +0 -0
  13. package/dist/assets/cassette.jpg +0 -0
  14. package/dist/assets/counter_bg.png +0 -0
  15. package/dist/assets/fx/counter.wav +0 -0
  16. package/dist/assets/fx/ffwd.wav +0 -0
  17. package/dist/assets/fx/pause.wav +0 -0
  18. package/dist/assets/fx/play.wav +0 -0
  19. package/dist/assets/fx/record.wav +0 -0
  20. package/dist/assets/fx/stop.wav +0 -0
  21. package/dist/assets/fx/track.wav +0 -0
  22. package/dist/assets/logo.svg +51 -0
  23. package/dist/assets/noise_50.jpg +0 -0
  24. package/dist/assets/openstudio.svg +38 -0
  25. package/dist/assets/recorder-worklet.d.ts +8 -0
  26. package/dist/assets/recorder-worklet.js +30 -0
  27. package/dist/assets/rotator.png +0 -0
  28. package/dist/assets/slider-indicator.svg +139 -0
  29. package/dist/assets/slider.png +0 -0
  30. package/dist/assets/slideselect-indicator.svg +64 -0
  31. package/dist/assets/slideselect-thumb.png +0 -0
  32. package/dist/assets/svg-icons.d.ts +6 -0
  33. package/dist/assets/svg-icons.js +8 -0
  34. package/dist/assets.d.ts +34 -0
  35. package/dist/audio/constants.d.ts +4 -0
  36. package/dist/audio/constants.js +27 -0
  37. package/dist/audio/engine.svelte.d.ts +90 -0
  38. package/dist/audio/engine.svelte.js +604 -0
  39. package/dist/audio/input-fx.d.ts +8 -0
  40. package/dist/audio/input-fx.js +44 -0
  41. package/dist/audio/metering.d.ts +3 -0
  42. package/dist/audio/metering.js +20 -0
  43. package/dist/audio/pcm.d.ts +2 -0
  44. package/dist/audio/pcm.js +43 -0
  45. package/dist/audio/project-io.d.ts +6 -0
  46. package/dist/audio/project-io.js +85 -0
  47. package/dist/audio/recording.d.ts +2 -0
  48. package/dist/audio/recording.js +80 -0
  49. package/dist/audio/track.svelte.d.ts +13 -0
  50. package/dist/audio/track.svelte.js +17 -0
  51. package/dist/components/Cassette.svelte +179 -0
  52. package/dist/components/Cassette.svelte.d.ts +9 -0
  53. package/dist/components/FourTrack.svelte +443 -0
  54. package/dist/components/FourTrack.svelte.d.ts +16 -0
  55. package/dist/components/Mixer.svelte +105 -0
  56. package/dist/components/Mixer.svelte.d.ts +7 -0
  57. package/dist/components/TransportButtons.svelte +299 -0
  58. package/dist/components/TransportButtons.svelte.d.ts +10 -0
  59. package/dist/components/els/DigitRoller.svelte +82 -0
  60. package/dist/components/els/DigitRoller.svelte.d.ts +5 -0
  61. package/dist/components/els/Knob.svelte +267 -0
  62. package/dist/components/els/Knob.svelte.d.ts +12 -0
  63. package/dist/components/els/Light.svelte +104 -0
  64. package/dist/components/els/Light.svelte.d.ts +8 -0
  65. package/dist/components/els/Lights.svelte +101 -0
  66. package/dist/components/els/Lights.svelte.d.ts +11 -0
  67. package/dist/components/els/SlideSelect.svelte +159 -0
  68. package/dist/components/els/SlideSelect.svelte.d.ts +15 -0
  69. package/dist/components/els/Slider.svelte +139 -0
  70. package/dist/components/els/Slider.svelte.d.ts +21 -0
  71. package/dist/components/els/Timestamp.svelte +92 -0
  72. package/dist/components/els/Timestamp.svelte.d.ts +5 -0
  73. package/dist/fx/soundfx.d.ts +14 -0
  74. package/dist/fx/soundfx.js +65 -0
  75. package/dist/index.d.ts +4 -0
  76. package/dist/index.js +3 -0
  77. package/dist/types.d.ts +40 -0
  78. package/dist/types.js +1 -0
  79. package/package.json +48 -0
@@ -0,0 +1,443 @@
1
+ <script lang="ts">
2
+ import { AudioEngine } from "../audio/engine.svelte.js"
3
+ import type { HiddenTrackConfig, LoadStatus } from "../types.js"
4
+ import casetteHissUrl from "../assets/casette_hiss.mp3"
5
+ import noiseImg from "../assets/noise_50.jpg"
6
+ import logoImg from "../assets/logo.svg?url"
7
+ import openstudioImg from "../assets/openstudio.svg?url"
8
+ import { onMount } from "svelte"
9
+ import Knob from "./els/Knob.svelte"
10
+ import Light from "./els/Light.svelte"
11
+ import Lights from "./els/Lights.svelte"
12
+ import Slider from "./els/Slider.svelte"
13
+ import SlideSelect from "./els/SlideSelect.svelte"
14
+ import Timestamp from "./els/Timestamp.svelte"
15
+ import Cassette from "./Cassette.svelte"
16
+ import TransportButtons from "./TransportButtons.svelte"
17
+
18
+ let {
19
+ hiddenTracks = [{ url: casetteHissUrl, volume: 0.08 }],
20
+ onready,
21
+ save = $bindable(),
22
+ load = $bindable(),
23
+ initialProject,
24
+ status = $bindable<LoadStatus>("idle"),
25
+ loadProgress = $bindable(0),
26
+ }: {
27
+ hiddenTracks?: HiddenTrackConfig[]
28
+ onready?: (detail: { engine: AudioEngine }) => void
29
+ save?: () => Blob
30
+ load?: (source: File | string) => Promise<void>
31
+ initialProject?: string | File
32
+ status?: LoadStatus
33
+ loadProgress?: number
34
+ } = $props()
35
+
36
+ let engine: AudioEngine | null = $state(null)
37
+ let selectedTrack = $state(0)
38
+ let speed = $state(0)
39
+ let recordEngaged = $state(false)
40
+
41
+ async function fetchWithProgress(url: string): Promise<File> {
42
+ const response = await fetch(url)
43
+ const contentLength = response.headers.get("Content-Length")
44
+
45
+ if (!contentLength || !response.body) {
46
+ const buffer = await response.arrayBuffer()
47
+ loadProgress = 1
48
+ return new File([buffer], "project.4trk")
49
+ }
50
+
51
+ const total = parseInt(contentLength, 10)
52
+ const reader = response.body.getReader()
53
+ const chunks: Uint8Array[] = []
54
+ let received = 0
55
+
56
+ while (true) {
57
+ const { done, value } = await reader.read()
58
+ if (done) break
59
+ chunks.push(value)
60
+ received += value.length
61
+ loadProgress = received / total
62
+ }
63
+
64
+ const buffer = new Uint8Array(received)
65
+ let offset = 0
66
+ for (const chunk of chunks) {
67
+ buffer.set(chunk, offset)
68
+ offset += chunk.length
69
+ }
70
+
71
+ return new File([buffer], "project.4trk")
72
+ }
73
+
74
+ onMount(async () => {
75
+ engine = new AudioEngine({ hiddenTracks })
76
+ engine.initAudioContext()
77
+
78
+ save = () => engine!.exportProject()
79
+ load = async (source: File | string) => {
80
+ status = "loading"
81
+ loadProgress = 0
82
+ try {
83
+ const file =
84
+ typeof source === "string" ? await fetchWithProgress(source) : source
85
+ await engine!.importProject(file)
86
+ status = "ready"
87
+ } catch (e) {
88
+ status = "error"
89
+ throw e
90
+ }
91
+ }
92
+
93
+ if (initialProject) {
94
+ await load(initialProject)
95
+ } else {
96
+ status = "ready"
97
+ }
98
+
99
+ onready?.({ engine })
100
+ return () => engine?.dispose()
101
+ })
102
+ </script>
103
+
104
+ {#if engine}
105
+ {@const tracks = engine.tracks}
106
+
107
+ {#snippet channelStrip(track, i)}
108
+ <div
109
+ class="channel-lights cell-center"
110
+ style="grid-area: {i + 3} / 2 / {i + 4} / 3"
111
+ >
112
+ <Lights level={track.level} />
113
+ </div>
114
+
115
+ <div class="cell-center" style="grid-area: {i + 3} / 3 / {i + 4} / 4">
116
+ <Knob
117
+ min={0}
118
+ max={1.5}
119
+ bind:value={track.volume}
120
+ onchange={(vol) => engine.setTrackVolume(i, vol)}
121
+ />
122
+ </div>
123
+
124
+ <div class="cell-center" style="grid-area: {i + 3} / 4 / {i + 4} / 5">
125
+ <Knob
126
+ min={-1}
127
+ max={1}
128
+ bind:value={track.pan}
129
+ onchange={(pan) => engine.setTrackPan(i, pan)}
130
+ label={"TRK " + (i + 1)}
131
+ labelLeft="L"
132
+ labelRight="R"
133
+ color="pink"
134
+ />
135
+ </div>
136
+ {/snippet}
137
+
138
+ <div
139
+ class="fourtrack"
140
+ style:--bg-noise="url({noiseImg})"
141
+ style:--bg-logo="url({logoImg})"
142
+ style:--bg-openstudio="url({openstudioImg})"
143
+ >
144
+ <div class="frame">
145
+ <div class="app">
146
+ <div class="parent">
147
+ <div class="cell-center" style="grid-area: 2 / 2 / 3 / 3">
148
+ <span class="ui-label">Phones</span>
149
+ </div>
150
+
151
+ <div class="master cell-center" style="grid-area: 2 / 3 / 3 / 4">
152
+ <div class="phonos-button">
153
+ <Knob
154
+ min={0}
155
+ max={1.5}
156
+ value={engine.masterVolume}
157
+ onchange={(vol) => engine.setMasterVolume(vol)}
158
+ color="green"
159
+ />
160
+ </div>
161
+ </div>
162
+
163
+ <!-- Mixer: Channel strips -->
164
+ {#each tracks as track, i}
165
+ {#if !track.hidden}
166
+ {@render channelStrip(track, i)}
167
+ {/if}
168
+ {/each}
169
+
170
+ <div class="ui-label cell-center" style="grid-area: 7 / 3 / 8 / 4">
171
+ Level
172
+ </div>
173
+ <div class="ui-label cell-center" style="grid-area: 7 / 4 / 8 / 5">
174
+ Pan
175
+ </div>
176
+ <div class="cell-center" style="grid-area: 8 / 3 / 9 / 5">
177
+ <span class="output ui-label">└── Output ──┘</span>
178
+ </div>
179
+
180
+ <div class="cell-center" style="grid-area: 1 / 5 / 9 / 6">
181
+ <div class="separator"></div>
182
+ </div>
183
+
184
+ <div class="cell-center" style="grid-area: 5 / 6 / 7 / 7">
185
+ <SlideSelect bind:value={selectedTrack} disabled={recordEngaged} />
186
+ </div>
187
+
188
+ <div
189
+ class="ui-label cell-right"
190
+ style="grid-area: 7 / 6 / 8 / 7; text-align: right"
191
+ >
192
+ Track
193
+ </div>
194
+
195
+ <div class="cell-center" style="grid-area: 2 / 6 / 3 / 8">
196
+ <div class="mic-status">
197
+ <div class="ui-label">mic status</div>
198
+ <div
199
+ class="mic-status-light"
200
+ title="Microphone status: {engine.micStatus}"
201
+ >
202
+ <Light
203
+ color={engine.micStatus === "prompt" ||
204
+ engine.micStatus === "active"
205
+ ? "green"
206
+ : "red"}
207
+ active={engine.micStatus === "active"}
208
+ pulsing={engine.micStatus === "no-device"
209
+ ? "fast"
210
+ : engine.micStatus === "inactive" ||
211
+ engine.micStatus === "active"
212
+ ? false
213
+ : "slow"}
214
+ />
215
+ </div>
216
+ </div>
217
+ </div>
218
+
219
+ <!-- Input Controls -->
220
+ <div class="cell-center" style="grid-area: 3 / 6 / 4 / 8">
221
+ <Knob
222
+ min={-1}
223
+ max={1}
224
+ bind:value={engine.trimValue}
225
+ onchange={(trim) => engine.setTrim(trim)}
226
+ label="TRIM"
227
+ labelLeft="LINE"
228
+ labelRight="MIC"
229
+ color="red"
230
+ />
231
+ </div>
232
+
233
+ <div class="cell-center" style="grid-area: 4 / 7 / 7 / 8">
234
+ <Slider
235
+ min={0}
236
+ max={1.5}
237
+ bind:value={engine.recordingVolume}
238
+ onchange={(vol) => engine.setRecordingVolume(vol)}
239
+ />
240
+ </div>
241
+
242
+ <div class="ui-label cell-center" style="grid-area: 7 / 7 / 8 / 8">
243
+ Volume
244
+ </div>
245
+
246
+ <div class="ui-label cell-center" style="grid-area: 8 / 6 / 9 / 8">
247
+ └─ Input ─┘
248
+ </div>
249
+
250
+ <div class="cell-center" style="grid-area: 1 / 8 / 9 / 9">
251
+ <div class="separator"></div>
252
+ </div>
253
+
254
+ <div class="cell-center" style="grid-area: 2 / 9 / 3 / 10">
255
+ <div class="mic-status">
256
+ <div class="ui-label">power</div>
257
+ <div class="mic-status-light" title="Cassette status: xx">
258
+ <Light color="green" active={true} pulsing={false} />
259
+ </div>
260
+ </div>
261
+ </div>
262
+
263
+ <div class="cell-timestamp" style="grid-area: 2 / 10 / 3 / 11">
264
+ <Timestamp timestamp={engine.position} />
265
+ </div>
266
+
267
+ <div class="logos" style="grid-area: 2 / 11 / 3 / 12">
268
+ <div class="logo"></div>
269
+ <div class="logo-tag"></div>
270
+ </div>
271
+
272
+ <div style="grid-area: 3 / 9 / 6 / 13">
273
+ <Cassette
274
+ {speed}
275
+ time={engine.position}
276
+ max={engine.duration || 180}
277
+ onchange={(ts) => engine.seek(ts)}
278
+ isRecording={engine.playState === "recording"}
279
+ />
280
+ </div>
281
+
282
+ <div class="cell-bottom" style="grid-area: 6 / 9 / 9 / 12">
283
+ <TransportButtons
284
+ {engine}
285
+ {selectedTrack}
286
+ bind:speed
287
+ bind:recordEngaged
288
+ />
289
+ </div>
290
+ </div>
291
+ </div>
292
+ </div>
293
+ </div>
294
+ {/if}
295
+
296
+ <style>
297
+ /* Scoped utility classes */
298
+ .fourtrack :global(.cell-center) {
299
+ display: flex;
300
+ align-items: center;
301
+ justify-content: center;
302
+ }
303
+
304
+ .fourtrack :global(.cell-bottom) {
305
+ display: flex;
306
+ align-items: end;
307
+ justify-content: center;
308
+ }
309
+
310
+ .fourtrack :global(.cell-right) {
311
+ display: flex;
312
+ align-items: center;
313
+ justify-content: right;
314
+ }
315
+
316
+ .fourtrack :global(.ui-label) {
317
+ color: rgba(255, 255, 255, 0.6);
318
+ text-transform: uppercase;
319
+ font-size: 1.8cqh;
320
+ font-family: sans-serif;
321
+ }
322
+
323
+ .fourtrack {
324
+ container-type: size;
325
+ aspect-ratio: 1 / 0.6;
326
+ width: 100%;
327
+ /* max-height: 75dvh; */
328
+ /* max-width: min(90vw, calc(75dvh / 0.6)); */
329
+ user-select: none;
330
+
331
+ @media (max-width: 1024px) {
332
+ max-height: 90dvh;
333
+ /* max-width: min(95vw, calc(90dvh / 0.6)); */
334
+ }
335
+ }
336
+
337
+ .separator {
338
+ height: 100%;
339
+ width: 0.45cqw;
340
+ border-radius: 0 0 0.15cqw 0.15cqw;
341
+ box-shadow:
342
+ inset 2px 2px 1px rgb(19 18 18 / 75%),
343
+ inset 1px 1px 1px rgba(31, 31, 31, 0.45),
344
+ inset -1px -1px 1px rgba(255, 252, 252, 0.35);
345
+ }
346
+
347
+ .parent {
348
+ display: grid;
349
+ grid-template-columns: 4cqw 4cqw 10cqw 10cqw 4cqw 4cqw 8cqw 5cqw 10cqw 1fr 1fr 4cqw;
350
+ grid-template-rows: 3cqw 9cqw 1fr 1fr 1fr 1fr 2.4cqw 2.4cqw 4.8cqw;
351
+ grid-column-gap: 0px;
352
+ grid-row-gap: 0px;
353
+ height: 100%;
354
+ }
355
+
356
+ .frame {
357
+ background: linear-gradient(to bottom, #616161, #3b3b3b);
358
+ padding: 1px;
359
+ border-radius: 1cqw 1cqw 2.5cqw 2.5cqw;
360
+ height: 100%;
361
+
362
+ box-shadow: 30px 20px 30px 0px rgb(33 34 36 / 31%);
363
+ position: relative;
364
+ &:before {
365
+ position: absolute;
366
+ content: " ";
367
+ width: 96%;
368
+ height: 0.5cqw;
369
+ margin: 0 2%;
370
+ background: linear-gradient(to right, #6f7074, #505252);
371
+ top: -0.5cqw;
372
+ border-radius: 10cqw 10cqw 0 0;
373
+ box-shadow: inset 0cqw 0.3cqh 0.2cqw rgb(225 225 225 / 40%);
374
+ border: 1px solid #686868;
375
+ }
376
+ }
377
+
378
+ .app {
379
+ background: radial-gradient(ellipse at top left, #686b71, #383840);
380
+ border-radius: 1cqw 1cqw 3cqw 3cqw;
381
+ height: 100cqh;
382
+ box-shadow:
383
+ inset 0.2cqw 0.5cqh 0.4cqw rgb(225 225 225 / 50%),
384
+ inset -0.2cqw -1.5cqh 0.2cqw rgb(0 0 0 / 26.6%);
385
+
386
+ position: relative;
387
+ }
388
+
389
+ .app:before {
390
+ content: " ";
391
+ width: 100%;
392
+ height: 100%;
393
+ display: block;
394
+ background: var(--bg-noise);
395
+ background-size: 50px;
396
+ mix-blend-mode: multiply;
397
+ position: absolute;
398
+ opacity: 0.9;
399
+ border-radius: 10px 10px 36px 36px;
400
+ }
401
+
402
+ .logos {
403
+ display: flex;
404
+ flex-direction: column;
405
+ gap: 5cqh;
406
+ }
407
+ .logo {
408
+ background: var(--bg-logo);
409
+ background-repeat: no-repeat;
410
+ background-size: contain;
411
+ background-position: top right;
412
+ width: 100%;
413
+ height: 2.75cqh;
414
+ }
415
+
416
+ .logo-tag {
417
+ background: var(--bg-openstudio);
418
+ background-repeat: no-repeat;
419
+ background-size: contain;
420
+ background-position: top right;
421
+ width: 100%;
422
+ height: 2.75cqh;
423
+ opacity: 0.6;
424
+ }
425
+
426
+ .mic-status {
427
+ text-align: center;
428
+ cursor: help;
429
+ transform: translateY(-2cqh);
430
+ .ui-label {
431
+ margin-bottom: 2cqh;
432
+ }
433
+ .mic-status-light {
434
+ display: flex;
435
+ justify-content: center;
436
+ }
437
+ }
438
+
439
+ .cell-timestamp {
440
+ padding-top: 3cqh;
441
+ padding-left: 2cqw;
442
+ }
443
+ </style>
@@ -0,0 +1,16 @@
1
+ import { AudioEngine } from "../audio/engine.svelte.js";
2
+ import type { HiddenTrackConfig, LoadStatus } from "../types.js";
3
+ type $$ComponentProps = {
4
+ hiddenTracks?: HiddenTrackConfig[];
5
+ onready?: (detail: {
6
+ engine: AudioEngine;
7
+ }) => void;
8
+ save?: () => Blob;
9
+ load?: (source: File | string) => Promise<void>;
10
+ initialProject?: string | File;
11
+ status?: LoadStatus;
12
+ loadProgress?: number;
13
+ };
14
+ declare const FourTrack: import("svelte").Component<$$ComponentProps, {}, "save" | "load" | "status" | "loadProgress">;
15
+ type FourTrack = ReturnType<typeof FourTrack>;
16
+ export default FourTrack;
@@ -0,0 +1,105 @@
1
+ <script lang="ts">
2
+ import type { AudioEngine } from ".."
3
+ import Knob from "./els/Knob.svelte"
4
+ import Lights from "./els/Lights.svelte"
5
+ let { engine }: { engine: AudioEngine } = $props()
6
+
7
+ let mVolume = $state(0)
8
+ </script>
9
+
10
+ <div class="row master">
11
+ <!-- <span class="col1 ui-label">Phones</span> -->
12
+ <div class="col2 phonos-button">
13
+ <Knob
14
+ min={0}
15
+ max={1.5}
16
+ value={engine.masterVolume}
17
+ onchange={(vol) => engine.setMasterVolume(vol)}
18
+ color="green"
19
+ />
20
+ </div>
21
+ </div>
22
+
23
+ {#snippet channelStrip(track, i)}
24
+ <div class="col1 channel-lights" style="grid-area: {i + 2} / 2 / {i + 3} / 3">
25
+ <Lights level={track.level} />
26
+ </div>
27
+
28
+ <div class="col2 channel-knob" style="grid-area: {i + 2} / 3 / {i + 3} / 4">
29
+ <Knob
30
+ min={0}
31
+ max={1.5}
32
+ bind:value={track.volume}
33
+ onchange={(vol) => engine.setTrackVolume(i, vol)}
34
+ />
35
+ </div>
36
+
37
+ <div class="col3 channel-knob" style="grid-area: {i + 2} / 4 / {i + 3} / 5">
38
+ <Knob
39
+ min={-1}
40
+ max={1}
41
+ bind:value={track.pan}
42
+ onchange={(pan) => engine.setTrackPan(i, pan)}
43
+ labelLeft="L"
44
+ labelRight="R"
45
+ color="pink"
46
+ />
47
+ </div>
48
+ {/snippet}
49
+
50
+ <!-- Not sure if sunippets are most useful here, but wanted to ttry them -->
51
+ {#each engine.tracks as track, i}
52
+ {#if !track.hidden}
53
+ {@render channelStrip(track, i)}
54
+ {/if}
55
+ {/each}
56
+
57
+ <div class="ui-label" style="grid-area: 6 / 3 / 7 / 6">Level</div>
58
+ <div class="ui-label" style="grid-area: 6 / 4 / 7 / 7">Pan</div>
59
+
60
+ <style>
61
+ .master {
62
+ height: 14cqw;
63
+ padding-top: 5cqw;
64
+ aspect-ratio: 1 / 1;
65
+ grid-area: 1 / 3 / 2 / 4;
66
+ }
67
+ .channel-strip {
68
+ /* align-items: center; */
69
+ height: 10cqw;
70
+ }
71
+
72
+ .channel-knob {
73
+ /* width: 7cqw; */
74
+ align-items: center;
75
+ justify-content: center;
76
+ }
77
+
78
+ .bg {
79
+ background: rgba(46, 46, 46, 0.5);
80
+ width: 20px;
81
+ height: 40px;
82
+ transform: rotate(-50deg);
83
+ border-radius: 20px;
84
+ outline: 1px solid rgb(36, 36, 36);
85
+ padding: 2px;
86
+ }
87
+
88
+ .light {
89
+ border-radius: 50%;
90
+ /* margin: 40%; */
91
+ width: 1cqw;
92
+ height: 1cqw;
93
+ aspect-ratio: 1 / 1;
94
+ opacity: 0.4;
95
+ &.low {
96
+ background-color: rgb(166, 255, 0);
97
+ }
98
+ &.high {
99
+ background-color: red;
100
+ }
101
+ &.active {
102
+ opacity: 1;
103
+ }
104
+ }
105
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { AudioEngine } from "..";
2
+ type $$ComponentProps = {
3
+ engine: AudioEngine;
4
+ };
5
+ declare const Mixer: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Mixer = ReturnType<typeof Mixer>;
7
+ export default Mixer;