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,299 @@
1
+ <script lang="ts">
2
+ import type { AudioEngine } from ".."
3
+ import Light from "./els/Light.svelte"
4
+ import { playFx, playLoop, stopLoop } from "../fx/soundfx"
5
+ import btnNormalImg from '../assets/btn_normal.png'
6
+ import btnPressedImg from '../assets/btn_pressed.png'
7
+ import { btnRecSvg, btnPlaySvg, btnPauseSvg, btnRewSvg, btnFwdSvg, btnStopSvg } from '../assets/svg-icons.js'
8
+
9
+ const btnIcons: Record<string, string> = {
10
+ record: btnRecSvg,
11
+ play: btnPlaySvg,
12
+ pause: btnPauseSvg,
13
+ rew: btnRewSvg,
14
+ ffwd: btnFwdSvg,
15
+ stop: btnStopSvg,
16
+ }
17
+
18
+ let {
19
+ engine,
20
+ selectedTrack,
21
+ speed = $bindable(0),
22
+ recordEngaged = $bindable(false),
23
+ }: {
24
+ engine: AudioEngine
25
+ selectedTrack: number
26
+ speed: number
27
+ recordEngaged: boolean
28
+ } = $props()
29
+
30
+ let btns = $state({
31
+ record: { pressed: false },
32
+ play: { pressed: false },
33
+ rew: { pressed: false },
34
+ ffwd: { pressed: false },
35
+ stop: { pressed: false },
36
+ pause: { pressed: false },
37
+ })
38
+
39
+ let isPaused = $state(false)
40
+
41
+ $effect(() => {
42
+ let timer: ReturnType<typeof setInterval> | undefined
43
+
44
+ // Trigger stop when rewinding and reaching the end
45
+ if (speed < 0 && engine.position + speed / 50 <= 0) {
46
+ clicky("stop")
47
+ }
48
+ if (engine.position + speed / 50 > engine.duration) {
49
+ clicky("stop")
50
+ }
51
+
52
+ if (speed != 0) {
53
+ timer = setInterval(() => {
54
+ var newpos = Math.max(0, engine.position + speed / 50)
55
+ newpos = Math.min(newpos, engine.duration)
56
+ engine.seek(newpos)
57
+ }, 10)
58
+ }
59
+
60
+ return () => {
61
+ clearInterval(timer)
62
+ }
63
+ })
64
+
65
+ function reset() {
66
+ engine.stop()
67
+ engine.stopMonitoring()
68
+ speed = 0
69
+ recordEngaged = false
70
+
71
+ Object.entries(btns).forEach(([type, btn]) => {
72
+ if (type == "pause") return
73
+ btn.pressed = false
74
+ })
75
+ }
76
+
77
+ function clicky(btnType: string) {
78
+ switch (btnType) {
79
+ case "play":
80
+ if (btns.play.pressed) return
81
+ playFx("play")
82
+ if (btns.ffwd.pressed || btns.rew.pressed) {
83
+ stopLoop("ffwd")
84
+ playFx("stop")
85
+ }
86
+ reset()
87
+ btns.play.pressed = true
88
+ if (!isPaused) engine.play()
89
+ break
90
+ case "stop":
91
+ reset()
92
+ playFx("stop")
93
+ stopLoop("ffwd")
94
+
95
+ break
96
+ case "pause":
97
+ isPaused = !isPaused
98
+ btns.pause.pressed = isPaused
99
+ if (isPaused) {
100
+ playFx("pause")
101
+
102
+ engine.stop()
103
+ if (btns.record.pressed) {
104
+ engine.startMonitoring(selectedTrack)
105
+ }
106
+ } else {
107
+ playFx("pause")
108
+
109
+ if (btns.record.pressed) {
110
+ engine.record(selectedTrack)
111
+ } else if (btns.play.pressed) {
112
+ engine.play()
113
+ }
114
+ }
115
+ break
116
+ case "record":
117
+ if (btns.record.pressed) return
118
+ playFx("record")
119
+ if (btns.ffwd.pressed || btns.rew.pressed) {
120
+ stopLoop("ffwd")
121
+ playFx("stop")
122
+ }
123
+ reset()
124
+ btns.record.pressed = true
125
+ btns.play.pressed = true
126
+ recordEngaged = true
127
+ if (isPaused) {
128
+ engine.startMonitoring(selectedTrack)
129
+ } else {
130
+ engine.record(selectedTrack)
131
+ }
132
+ break
133
+ case "rew":
134
+ if (btns.play.pressed || btns.ffwd.pressed) {
135
+ playFx("stop")
136
+ }
137
+ reset()
138
+ if (engine.position != 0) {
139
+ playLoop("ffwd")
140
+ btns.rew.pressed = true
141
+ speed = -8
142
+ }
143
+ break
144
+ case "ffwd":
145
+ if (btns.play.pressed || btns.rew.pressed) {
146
+ playFx("stop")
147
+ }
148
+ reset()
149
+ playLoop("ffwd")
150
+ btns.ffwd.pressed = true
151
+ speed = 8
152
+ break
153
+ }
154
+ }
155
+ </script>
156
+
157
+ <div class="ctrlButtons" style:--btn-normal="url({btnNormalImg})" style:--btn-pressed="url({btnPressedImg})">
158
+ <div class="rec-light">
159
+ <Light
160
+ color="red"
161
+ active={btns.record.pressed}
162
+ pulsing={btns.record.pressed && isPaused ? "slow" : false}
163
+ />
164
+ </div>
165
+ <div class="btnLabels">
166
+ {#each Object.entries(btns) as [type, btn]}
167
+ <div class="btnLabel ui-label {type}">{type}</div>
168
+ {/each}
169
+ </div>
170
+ <div class="controlBtns">
171
+ <div class="imgBtns">
172
+ {#each Object.entries(btns) as [type, btn]}
173
+ <button
174
+ type="button"
175
+ class="btn {type}"
176
+ class:active={btn.pressed}
177
+ onmousedown={() => clicky(type)}
178
+ style:--btn-icon="url('{btnIcons[type]}')"
179
+ >
180
+ </button>
181
+ {/each}
182
+ </div>
183
+ <div class="after">&nbsp;</div>
184
+ </div>
185
+ </div>
186
+
187
+ <style>
188
+ .rec-light {
189
+ position: absolute;
190
+ height: 3cqw;
191
+ width: 3cqw;
192
+ left: -0.5cqw;
193
+ &.active {
194
+ opacity: 1;
195
+ }
196
+ /* &:before {
197
+ display: block;
198
+ content: " ";
199
+ border-top: 2px solid white;
200
+ width: 10px;
201
+ } */
202
+ }
203
+
204
+ .ctrlButtons {
205
+ display: flex;
206
+ flex-direction: column;
207
+ align-items: center;
208
+ /* container-type: size; */
209
+ flex: 1; /* if parent container is flex */
210
+ padding: 0 0 0 2cqw;
211
+ position: relative;
212
+ }
213
+ .controlBtns {
214
+ background: linear-gradient(to bottom right, #3d3c43, #646468);
215
+ width: 100%;
216
+ border-radius: 0.5cqw;
217
+ box-shadow:
218
+ inset 0.45cqw 0.45cqw 1.4cqw rgba(0, 0, 0, 0.6),
219
+ inset -0.01cqw -0.1cqw 0.1cqw rgba(255, 255, 255, 0.5);
220
+ display: flex;
221
+ flex-direction: column;
222
+ padding-top: 0.23cqw;
223
+ padding-left: 0.15cqw;
224
+ padding-right: 0.15cqw;
225
+ perspective: 182cqw;
226
+ }
227
+ .imgBtns {
228
+ padding: 0.1cqw;
229
+ border-radius: 0.45cqw;
230
+ background-color: #212121;
231
+ display: flex;
232
+ }
233
+ .btnLabels {
234
+ display: flex;
235
+ width: 99%;
236
+ div {
237
+ flex: 1;
238
+ text-align: center;
239
+ padding-bottom: 0.5cqw;
240
+ }
241
+ .record {
242
+ color: rgb(200, 60, 35);
243
+ text-shadow: 0px 0px 2px #0000007d;
244
+ transform: translateX(-0.8cqw);
245
+ &:before {
246
+ content: "─ ";
247
+ color: white;
248
+ opacity: 0.4;
249
+ }
250
+ }
251
+ }
252
+ .btn {
253
+ appearance: none;
254
+ border: none;
255
+ padding: 0;
256
+ cursor: pointer;
257
+ background-color: transparent;
258
+ background-image: var(--btn-normal);
259
+ background-size: cover;
260
+ background-position: center;
261
+ flex: 1;
262
+ aspect-ratio: 70 / 87;
263
+ margin-right: 0.2cqw;
264
+ box-shadow: 3.4cqw 3.4cqw 5cqw rgba(0, 0, 0, 0.6);
265
+ position: relative;
266
+
267
+ &:before {
268
+ display: block;
269
+ content: " ";
270
+ position: absolute;
271
+ top: 18%;
272
+ width: 100%;
273
+ height: 17%;
274
+ background-image: var(--btn-icon);
275
+ background-size: contain;
276
+ background-position: center;
277
+ mix-blend-mode: overlay;
278
+ background-repeat: no-repeat;
279
+ }
280
+ }
281
+ .btn.active,
282
+ .btn:active {
283
+ background-image: var(--btn-pressed);
284
+ box-shadow:
285
+ inset 1.1cqw 0 3.4cqw rgba(0, 0, 0, 0.4),
286
+ 2.3cqw 2.3cqw 4.5cqw rgba(0, 0, 0, 0.6);
287
+
288
+ &:before {
289
+ top: 31%;
290
+ transform: rotateX(32deg);
291
+ }
292
+ }
293
+ .btn.active + .btn.active {
294
+ box-shadow: 2.3cqw 2.3cqw 4.5cqw rgba(0, 0, 0, 0.6);
295
+ }
296
+ .after {
297
+ height: 5cqh;
298
+ }
299
+ </style>
@@ -0,0 +1,10 @@
1
+ import type { AudioEngine } from "..";
2
+ type $$ComponentProps = {
3
+ engine: AudioEngine;
4
+ selectedTrack: number;
5
+ speed: number;
6
+ recordEngaged: boolean;
7
+ };
8
+ declare const TransportButtons: import("svelte").Component<$$ComponentProps, {}, "speed" | "recordEngaged">;
9
+ type TransportButtons = ReturnType<typeof TransportButtons>;
10
+ export default TransportButtons;
@@ -0,0 +1,82 @@
1
+ <script lang="ts">
2
+ let { digit = 0 } = $props()
3
+
4
+ let roller: HTMLDivElement
5
+ let prev = -1
6
+ let wrapping = false
7
+ const step = 100 / 12
8
+ const pos = (d: number) => `translateY(${-step * (1 + d)}%)`
9
+
10
+ function jumpTo(transform: string) {
11
+ roller.style.transition = "none"
12
+ roller.style.transform = transform
13
+ roller.offsetHeight
14
+ roller.style.transition = ""
15
+ }
16
+
17
+ $effect(() => {
18
+ const d = +digit
19
+ if (!roller) return
20
+
21
+ if (prev === -1) {
22
+ jumpTo(pos(d))
23
+ } else if (prev === 9 && d === 0) {
24
+ wrapping = true
25
+ roller.style.transform = `translateY(${-step * 11}%)`
26
+ } else if (prev === 0 && d === 9) {
27
+ wrapping = true
28
+ roller.style.transform = `translateY(0%)`
29
+ } else {
30
+ wrapping = false
31
+ roller.style.transform = pos(d)
32
+ }
33
+
34
+ prev = d
35
+ })
36
+
37
+ function onTransitionEnd() {
38
+ if (!wrapping) return
39
+ wrapping = false
40
+ jumpTo(pos(+digit))
41
+ }
42
+ </script>
43
+
44
+ <div class="digits">
45
+ <div class="roller" bind:this={roller} ontransitionend={onTransitionEnd}>
46
+ {#each [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0] as n}
47
+ <div class="digit"><span>{n}</span></div>
48
+ {/each}
49
+ </div>
50
+ </div>
51
+
52
+ <style>
53
+ .digits {
54
+ overflow: hidden;
55
+ height: 20cqw;
56
+ width: 17cqw;
57
+ text-align: center;
58
+ color: #cfcdd3;
59
+ background: linear-gradient(to bottom, #474748, #000000, #545454);
60
+ border-right: 2px solid rgb(33, 33, 33);
61
+ font-family: sans-serif;
62
+ &:nth-child(1) {
63
+ transform: translateY(-1.5cqw);
64
+ }
65
+ &:nth-child(2) {
66
+ transform: translateY(-0.5cqw);
67
+ }
68
+ &:nth-child(3) {
69
+ transform: translateY(-2cqw);
70
+ }
71
+ }
72
+ .roller {
73
+ transition: 0.4s ease transform;
74
+ }
75
+ .digit {
76
+ font-size: 35cqh;
77
+ /* letter-spacing: 7cqw; */
78
+ }
79
+ .span {
80
+ padding-left: 1cqw;
81
+ }
82
+ </style>
@@ -0,0 +1,5 @@
1
+ declare const DigitRoller: import("svelte").Component<{
2
+ digit?: number;
3
+ }, {}, "">;
4
+ type DigitRoller = ReturnType<typeof DigitRoller>;
5
+ export default DigitRoller;
@@ -0,0 +1,267 @@
1
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
2
+ <script lang="ts">
3
+ let rotating = $state(false)
4
+ let _dragStart = $state(0)
5
+ let _dragDelta = $state(0)
6
+ let containerEl = $state()
7
+ let yStart = $state()
8
+
9
+ let internalValue = $state(0) // always between 0-1, transalte later
10
+
11
+ let {
12
+ min,
13
+ max,
14
+ value = $bindable(),
15
+ onchange,
16
+ label,
17
+ labelLeft,
18
+ labelRight,
19
+ color,
20
+ } = $props()
21
+
22
+ const startRotate = (e) => {
23
+ // triggered once
24
+ rotating = true
25
+ _dragStart = normalizeValue(value) // is this copying the value or becoming a reference?
26
+ _dragDelta = 0
27
+ yStart = e.clientY
28
+
29
+ // needed to keep tracking pointermove, even if it is outside
30
+ e.target.setPointerCapture(e.pointerId)
31
+ }
32
+
33
+ const rotate = (e) => {
34
+ // trigggered continously
35
+ if (!rotating) return // not sure why/if its needed?
36
+
37
+ const rect = containerEl.getBoundingClientRect()
38
+ const yMove = yStart - e.clientY // it should be the difference from where it initially started.
39
+
40
+ // max drag area is 4 times the height
41
+ _dragDelta = yMove / rect.height / 4
42
+ internalValue = Math.max(0, Math.min(_dragStart + _dragDelta, 1))
43
+
44
+ // Trigger Callback
45
+ onchange?.(denormalizeValue(internalValue))
46
+ }
47
+
48
+ const stopRotate = (e) => {
49
+ rotating = false
50
+ e.target.releasePointerCapture?.(e.pointerId)
51
+ }
52
+
53
+ // Take the value and put it on a 0-1 scale
54
+ function denormalizeValue(internalValue: number) {
55
+ var spread = max - min
56
+ return min + internalValue * spread
57
+ }
58
+
59
+ function normalizeValue(val: number) {
60
+ var spread = max - min
61
+ return (val - min) / spread
62
+ }
63
+
64
+ // Ensures that the button goes from 8-4o'clock
65
+ // Deliberately a bit extended at start and end.
66
+ function mapToKnob(val: number) {
67
+ return val * 0.73 - 0.37
68
+ }
69
+ </script>
70
+
71
+ <div class="knob-frame {color}">
72
+ <div class="knob-label">{label}</div>
73
+ <div class="knobx">
74
+ <div
75
+ class="knobcontainer"
76
+ bind:this={containerEl}
77
+ onpointermove={rotate}
78
+ onpointerup={stopRotate}
79
+ onpointerleave={stopRotate}
80
+ >
81
+ <div class="knob" onpointerdown={startRotate} class:dragging={rotating}>
82
+ <div class="layer1">
83
+ <div class="layer2">
84
+ <div class="layer3">
85
+ <div
86
+ class="layer4"
87
+ style="rotate: {mapToKnob(normalizeValue(value))}turn"
88
+ >
89
+ <div class="layer5">
90
+ <div class="line1"></div>
91
+ <div class="line2"></div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ <div class="labels">
100
+ <span class="ui-label">&nbsp;{labelLeft}</span>
101
+ <span class="ui-label">&nbsp;{labelRight}</span>
102
+ </div>
103
+ </div>
104
+ </div>
105
+
106
+ <style>
107
+ .knobx {
108
+ display: flex;
109
+ justify-content: center;
110
+ align-items: center;
111
+ flex-direction: column;
112
+ user-select: none;
113
+ container-type: inline-size;
114
+ width: 12cqh;
115
+ }
116
+ .knob-label {
117
+ position: absolute;
118
+ margin-top: -2.5cqh;
119
+ margin-left: -2.8cqw;
120
+ text-transform: uppercase;
121
+ color: rgba(255, 255, 255, 0.6);
122
+ font-size: 1.8cqh;
123
+ letter-spacing: 0.1cqh;
124
+ text-align: right;
125
+ font-weight: bold;
126
+ }
127
+ .knobcontainer {
128
+ width: 90cqw;
129
+ aspect-ratio: 1 / 1;
130
+ display: flex;
131
+ user-select: none;
132
+ flex-direction: column;
133
+ align-items: center;
134
+ justify-content: center;
135
+ position: relative;
136
+ margin-bottom: -5cqw;
137
+
138
+ &:before {
139
+ content: " ";
140
+ position: absolute;
141
+ display: block;
142
+ width: 100%;
143
+ height: 100%;
144
+ border-radius: 50%;
145
+ background: conic-gradient(
146
+ from 0deg at 50% 50%,
147
+ /* 12 o'clock (0°) */ rgba(255, 255, 255, 0.5) 0deg 1deg,
148
+ transparent 1deg 29deg,
149
+ /* 13:00 (30°) */ rgba(255, 255, 255, 0.5) 29deg 31deg,
150
+ transparent 31deg 59deg,
151
+ /* 14:00 (60°) */ rgba(255, 255, 255, 0.5) 59deg 61deg,
152
+ transparent 61deg 89deg,
153
+ /* 15:00 (90°) */ rgba(255, 255, 255, 0.5) 89deg 91deg,
154
+ transparent 91deg 119deg,
155
+ /* 16:00 (120°) */ rgba(255, 255, 255, 0.5) 119deg 121deg,
156
+ transparent 121deg 239deg,
157
+ /* 8:00 (240°) */ rgba(255, 255, 255, 0.5) 239deg 241deg,
158
+ transparent 241deg 269deg,
159
+ /* 9:00 (270°) */ rgba(255, 255, 255, 0.5) 269deg 271deg,
160
+ transparent 271deg 299deg,
161
+ /* 10:00 (300°) */ rgba(255, 255, 255, 0.5) 299deg 301deg,
162
+ transparent 301deg 329deg,
163
+ /* 11:00 (330°) */ rgba(255, 255, 255, 0.5) 329deg 331deg,
164
+ transparent 331deg 359deg,
165
+ /* 12 o'clock wrap */ rgba(255, 255, 255, 0.5) 359deg 360deg
166
+ );
167
+ }
168
+ }
169
+ .knob {
170
+ width: 66cqw;
171
+ aspect-ratio: 1 / 1;
172
+ border-radius: 100%;
173
+ background-color: rgb(32, 32, 32);
174
+ cursor: grab;
175
+ display: flex;
176
+ justify-content: center;
177
+ align-items: center;
178
+ position: absolute;
179
+
180
+ .layer1 {
181
+ position: absolute;
182
+ display: block;
183
+ width: 90%;
184
+ height: 90%;
185
+ border-radius: 100%;
186
+ background: linear-gradient(to right, #a9a9a9, #5c5c5c);
187
+ display: flex;
188
+ justify-content: center;
189
+ align-items: center;
190
+ }
191
+
192
+ .layer2 {
193
+ display: block;
194
+ width: 85%;
195
+ height: 85%;
196
+ border-radius: 100%;
197
+ background: linear-gradient(to bottom right, #8e8e8e 35%, #4a4a4a 65%);
198
+ display: flex;
199
+ justify-content: center;
200
+ align-items: center;
201
+ box-shadow: 30cqw 23cqw 25cqw rgba(0, 0, 0, 0.4);
202
+ }
203
+
204
+ .layer3 {
205
+ border-radius: 100%;
206
+ display: block;
207
+ width: 80%;
208
+ height: 80%;
209
+ background-color: #979797;
210
+ box-shadow:
211
+ inset 3cqw 3cqw 3cqw rgba(255, 255, 255, 0.5),
212
+ inset -1.5cqw -1.5cqw 3cqw rgba(50, 50, 50, 0.7);
213
+ }
214
+ .layer4 {
215
+ position: relative;
216
+ width: 100%;
217
+ height: 100%;
218
+ max-width: 70vw;
219
+ mix-blend-mode: overlay;
220
+
221
+ .line1 {
222
+ position: absolute;
223
+ background-color: #ededed;
224
+ width: 6cqw;
225
+ height: 15cqw;
226
+ left: calc(50% - 3cqw);
227
+ border-radius: 5cqw;
228
+ }
229
+ .line2 {
230
+ position: absolute;
231
+ background-color: #ededed;
232
+ width: 6cqw;
233
+ height: 6cqw;
234
+ left: calc(50% - 3cqw);
235
+ top: -12cqw;
236
+ border-radius: 1.5cqw;
237
+ }
238
+ }
239
+ }
240
+ .knob.dragging {
241
+ cursor: grabbing;
242
+ }
243
+
244
+ .labels {
245
+ display: flex;
246
+ width: 100%;
247
+ span {
248
+ flex: 1;
249
+ text-align: center;
250
+ }
251
+ }
252
+
253
+ .green .line1,
254
+ .green .line2 {
255
+ background-color: #60ac8f !important;
256
+ }
257
+
258
+ .pink .line1,
259
+ .pink .line2 {
260
+ background-color: #f5c68b !important;
261
+ }
262
+
263
+ .red .line1,
264
+ .red .line2 {
265
+ background-color: #b04a4a !important;
266
+ }
267
+ </style>
@@ -0,0 +1,12 @@
1
+ declare const Knob: import("svelte").Component<{
2
+ min: any;
3
+ max: any;
4
+ value?: any;
5
+ onchange: any;
6
+ label: any;
7
+ labelLeft: any;
8
+ labelRight: any;
9
+ color: any;
10
+ }, {}, "value">;
11
+ type Knob = ReturnType<typeof Knob>;
12
+ export default Knob;