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.
- package/LICENSE +674 -0
- package/README.md +80 -0
- package/dist/assets/btn_fwd.svg +30 -0
- package/dist/assets/btn_normal.png +0 -0
- package/dist/assets/btn_pause.svg +30 -0
- package/dist/assets/btn_play.svg +25 -0
- package/dist/assets/btn_pressed.png +0 -0
- package/dist/assets/btn_rec.svg +25 -0
- package/dist/assets/btn_rew.svg +30 -0
- package/dist/assets/btn_stop.svg +25 -0
- package/dist/assets/casette_hiss.mp3 +0 -0
- package/dist/assets/casette_hiss_compressed.mp3 +0 -0
- package/dist/assets/cassette.jpg +0 -0
- package/dist/assets/counter_bg.png +0 -0
- package/dist/assets/fx/counter.wav +0 -0
- package/dist/assets/fx/ffwd.wav +0 -0
- package/dist/assets/fx/pause.wav +0 -0
- package/dist/assets/fx/play.wav +0 -0
- package/dist/assets/fx/record.wav +0 -0
- package/dist/assets/fx/stop.wav +0 -0
- package/dist/assets/fx/track.wav +0 -0
- package/dist/assets/logo.svg +51 -0
- package/dist/assets/noise_50.jpg +0 -0
- package/dist/assets/openstudio.svg +38 -0
- package/dist/assets/recorder-worklet.d.ts +8 -0
- package/dist/assets/recorder-worklet.js +30 -0
- package/dist/assets/rotator.png +0 -0
- package/dist/assets/slider-indicator.svg +139 -0
- package/dist/assets/slider.png +0 -0
- package/dist/assets/slideselect-indicator.svg +64 -0
- package/dist/assets/slideselect-thumb.png +0 -0
- package/dist/assets/svg-icons.d.ts +6 -0
- package/dist/assets/svg-icons.js +8 -0
- package/dist/assets.d.ts +34 -0
- package/dist/audio/constants.d.ts +4 -0
- package/dist/audio/constants.js +27 -0
- package/dist/audio/engine.svelte.d.ts +90 -0
- package/dist/audio/engine.svelte.js +604 -0
- package/dist/audio/input-fx.d.ts +8 -0
- package/dist/audio/input-fx.js +44 -0
- package/dist/audio/metering.d.ts +3 -0
- package/dist/audio/metering.js +20 -0
- package/dist/audio/pcm.d.ts +2 -0
- package/dist/audio/pcm.js +43 -0
- package/dist/audio/project-io.d.ts +6 -0
- package/dist/audio/project-io.js +85 -0
- package/dist/audio/recording.d.ts +2 -0
- package/dist/audio/recording.js +80 -0
- package/dist/audio/track.svelte.d.ts +13 -0
- package/dist/audio/track.svelte.js +17 -0
- package/dist/components/Cassette.svelte +179 -0
- package/dist/components/Cassette.svelte.d.ts +9 -0
- package/dist/components/FourTrack.svelte +443 -0
- package/dist/components/FourTrack.svelte.d.ts +16 -0
- package/dist/components/Mixer.svelte +105 -0
- package/dist/components/Mixer.svelte.d.ts +7 -0
- package/dist/components/TransportButtons.svelte +299 -0
- package/dist/components/TransportButtons.svelte.d.ts +10 -0
- package/dist/components/els/DigitRoller.svelte +82 -0
- package/dist/components/els/DigitRoller.svelte.d.ts +5 -0
- package/dist/components/els/Knob.svelte +267 -0
- package/dist/components/els/Knob.svelte.d.ts +12 -0
- package/dist/components/els/Light.svelte +104 -0
- package/dist/components/els/Light.svelte.d.ts +8 -0
- package/dist/components/els/Lights.svelte +101 -0
- package/dist/components/els/Lights.svelte.d.ts +11 -0
- package/dist/components/els/SlideSelect.svelte +159 -0
- package/dist/components/els/SlideSelect.svelte.d.ts +15 -0
- package/dist/components/els/Slider.svelte +139 -0
- package/dist/components/els/Slider.svelte.d.ts +21 -0
- package/dist/components/els/Timestamp.svelte +92 -0
- package/dist/components/els/Timestamp.svelte.d.ts +5 -0
- package/dist/fx/soundfx.d.ts +14 -0
- package/dist/fx/soundfx.js +65 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +40 -0
- package/dist/types.js +1 -0
- package/package.json +48 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
let {
|
|
3
|
+
color = "green",
|
|
4
|
+
active = false,
|
|
5
|
+
pulsing = false,
|
|
6
|
+
}: {
|
|
7
|
+
color?: "red" | "green"
|
|
8
|
+
active?: boolean
|
|
9
|
+
pulsing?: false | "slow" | "fast"
|
|
10
|
+
} = $props()
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div class="light-bevel">
|
|
14
|
+
<div
|
|
15
|
+
class="light {color}"
|
|
16
|
+
class:active
|
|
17
|
+
class:pulsing-slow={pulsing === "slow" || pulsing === true}
|
|
18
|
+
class:pulsing-fast={pulsing === "fast"}
|
|
19
|
+
>
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<style>
|
|
25
|
+
.light-bevel {
|
|
26
|
+
background: linear-gradient(to bottom right, #101010, #a2a1a1);
|
|
27
|
+
height: 1.5cqw;
|
|
28
|
+
width: 1.5cqw;
|
|
29
|
+
border-radius: 50%;
|
|
30
|
+
container-type: size;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.light {
|
|
34
|
+
border-radius: 89cqw;
|
|
35
|
+
width: 66cqw;
|
|
36
|
+
height: 66cqh;
|
|
37
|
+
margin-top: 22cqh;
|
|
38
|
+
margin-left: 20cqw;
|
|
39
|
+
background: #645855;
|
|
40
|
+
box-shadow: inset 6cqw -6cqh 22cqw 17cqw rgba(62, 2, 2, 0.7);
|
|
41
|
+
|
|
42
|
+
/* &.red {
|
|
43
|
+
background: #8f3333;
|
|
44
|
+
box-shadow: inset 6cqw -6cqh 22cqw 17cqw rgba(62, 2, 2, 0.7);
|
|
45
|
+
} */
|
|
46
|
+
&.red.active {
|
|
47
|
+
background: #ff0000;
|
|
48
|
+
box-shadow:
|
|
49
|
+
inset 0 0 33cqw rgba(32, 1, 1, 0.9),
|
|
50
|
+
0 0 89cqw 89cqw rgba(209, 24, 24, 0.1);
|
|
51
|
+
}
|
|
52
|
+
&.red.pulsing-slow {
|
|
53
|
+
animation: pulse-red 2s ease-in-out infinite;
|
|
54
|
+
}
|
|
55
|
+
&.red.pulsing-fast {
|
|
56
|
+
animation: pulse-red 0.5s ease-in-out infinite;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&.green {
|
|
60
|
+
background: #628615;
|
|
61
|
+
box-shadow: inset 6cqw -6cqh 22cqw 17cqw rgba(32, 40, 0, 0.8);
|
|
62
|
+
}
|
|
63
|
+
&.green.active {
|
|
64
|
+
background: #bbff00;
|
|
65
|
+
box-shadow:
|
|
66
|
+
inset 0 0 33cqw rgba(45, 53, 2, 0.9),
|
|
67
|
+
0 0 89cqw 89cqw rgba(176, 240, 2, 0.1);
|
|
68
|
+
}
|
|
69
|
+
&.green.pulsing-slow {
|
|
70
|
+
animation: pulse-green 2s ease-in-out infinite;
|
|
71
|
+
}
|
|
72
|
+
&.green.pulsing-fast {
|
|
73
|
+
animation: pulse-green 0.5s ease-in-out infinite;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@keyframes pulse-red {
|
|
78
|
+
0%,
|
|
79
|
+
100% {
|
|
80
|
+
background: #645855;
|
|
81
|
+
box-shadow: inset 6cqw -6cqh 22cqw 17cqw rgba(62, 2, 2, 0.7);
|
|
82
|
+
}
|
|
83
|
+
50% {
|
|
84
|
+
background: #ff0000;
|
|
85
|
+
box-shadow:
|
|
86
|
+
inset 0 0 33cqw rgba(32, 1, 1, 0.9),
|
|
87
|
+
0 0 89cqw 89cqw rgba(209, 24, 24, 0.1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@keyframes pulse-green {
|
|
92
|
+
0%,
|
|
93
|
+
100% {
|
|
94
|
+
background: #645855;
|
|
95
|
+
box-shadow: inset 6cqw -6cqh 22cqw 17cqw rgba(32, 40, 0, 0.8);
|
|
96
|
+
}
|
|
97
|
+
50% {
|
|
98
|
+
background: #bbff00;
|
|
99
|
+
box-shadow:
|
|
100
|
+
inset 0 0 33cqw rgba(45, 53, 2, 0.9),
|
|
101
|
+
0 0 89cqw 89cqw rgba(176, 240, 2, 0.1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
</style>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
let { level } = $props()
|
|
3
|
+
</script>
|
|
4
|
+
|
|
5
|
+
<div class="indicators">
|
|
6
|
+
<div class="labels">
|
|
7
|
+
<div class="left">+6</div>
|
|
8
|
+
<div class="right">0</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="lights">
|
|
11
|
+
<div class="inner">
|
|
12
|
+
<div class="light-bevel">
|
|
13
|
+
<div class="light high active" class:active={level > 3}> </div>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="light-bevel">
|
|
16
|
+
<div class="light low active" class:active={level > 1}> </div>
|
|
17
|
+
</div>
|
|
18
|
+
<!-- <div class="light low" class:active={level > 0.5}> </div> -->
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<style>
|
|
24
|
+
.indicators {
|
|
25
|
+
container-type: size;
|
|
26
|
+
width: 100%;
|
|
27
|
+
height: 100%;
|
|
28
|
+
}
|
|
29
|
+
.labels {
|
|
30
|
+
color: white;
|
|
31
|
+
display: flex;
|
|
32
|
+
font-size: 20cqw;
|
|
33
|
+
opacity: 0.8;
|
|
34
|
+
.left {
|
|
35
|
+
flex: 1;
|
|
36
|
+
}
|
|
37
|
+
.right {
|
|
38
|
+
flex: 1;
|
|
39
|
+
text-align: right;
|
|
40
|
+
transform: translate(-4cqw, 20cqh);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
.lights {
|
|
44
|
+
height: 130cqw;
|
|
45
|
+
width: 50cqw;
|
|
46
|
+
background: #46474a;
|
|
47
|
+
box-shadow:
|
|
48
|
+
inset 4cqw -2cqw 12cqw rgba(0, 0, 0, 0.6),
|
|
49
|
+
inset 4cqw -4cqw 4cqw rgba(255, 255, 255, 0.5);
|
|
50
|
+
position: relative;
|
|
51
|
+
border-radius: 40cqw;
|
|
52
|
+
transform: rotate(-60deg) translate(10cqw, 10cqh);
|
|
53
|
+
}
|
|
54
|
+
.inner {
|
|
55
|
+
position: relative;
|
|
56
|
+
top: 3.5cqw;
|
|
57
|
+
left: 4cqw;
|
|
58
|
+
height: 120cqw;
|
|
59
|
+
width: 44cqw;
|
|
60
|
+
background: #46474a;
|
|
61
|
+
border-radius: 40cqw;
|
|
62
|
+
}
|
|
63
|
+
.light-bevel {
|
|
64
|
+
background: linear-gradient(to bottom, #101010, #6b6b6b);
|
|
65
|
+
top: 2.5cqh;
|
|
66
|
+
left: 4cqw;
|
|
67
|
+
height: 36cqw;
|
|
68
|
+
width: 36cqw;
|
|
69
|
+
margin-bottom: 15cqh;
|
|
70
|
+
border-radius: 50%;
|
|
71
|
+
position: relative;
|
|
72
|
+
}
|
|
73
|
+
.light {
|
|
74
|
+
position: absolute;
|
|
75
|
+
width: 31cqw;
|
|
76
|
+
height: 31cqw;
|
|
77
|
+
top: 3cqw;
|
|
78
|
+
left: 3cqw;
|
|
79
|
+
border-radius: 50%;
|
|
80
|
+
&.low {
|
|
81
|
+
background: #628615;
|
|
82
|
+
box-shadow: inset 2cqw -2cqw 8cqw 6cqw rgba(32, 40, 0, 0.8);
|
|
83
|
+
}
|
|
84
|
+
&.low.active {
|
|
85
|
+
background: #bbff00;
|
|
86
|
+
box-shadow:
|
|
87
|
+
inset 0 0 12cqw rgba(45, 53, 2, 0.9),
|
|
88
|
+
0 0 32cqw 32cqw rgba(176, 240, 2, 0.1);
|
|
89
|
+
}
|
|
90
|
+
&.high {
|
|
91
|
+
background: #8f3333;
|
|
92
|
+
box-shadow: inset 2cqw -2cqw 8cqw 6cqw rgba(62, 2, 2, 0.7);
|
|
93
|
+
}
|
|
94
|
+
&.high.active {
|
|
95
|
+
background: #ff0000;
|
|
96
|
+
box-shadow:
|
|
97
|
+
inset 0 0 12cqw rgba(32, 1, 1, 0.9),
|
|
98
|
+
0 0 32cqw 32cqw rgba(209, 24, 24, 0.1);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
</style>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default Lights;
|
|
2
|
+
type Lights = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const Lights: import("svelte").Component<{
|
|
7
|
+
level: any;
|
|
8
|
+
}, {}, "">;
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
level: any;
|
|
11
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
<!-- svelte-ignore a11y_role_has_required_aria_props -->
|
|
2
|
+
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
3
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
4
|
+
<script>
|
|
5
|
+
import { playFx } from "../../fx/soundfx"
|
|
6
|
+
import slideSelectIndicatorImg from '../../assets/slideselect-indicator.svg?url'
|
|
7
|
+
import slideSelectThumbImg from '../../assets/slideselect-thumb.png'
|
|
8
|
+
let dragging = $state(false)
|
|
9
|
+
let trackEl = $state()
|
|
10
|
+
let selected_i = $state(4)
|
|
11
|
+
let startY = $state(0)
|
|
12
|
+
let startIndex = $state(0)
|
|
13
|
+
let initialized = false // to disable sound fx on loading of the component
|
|
14
|
+
let selections = [
|
|
15
|
+
{ lbl: "TRK 1", val: 0 },
|
|
16
|
+
{ lbl: "2", val: 1 },
|
|
17
|
+
{ lbl: "3", val: 2 },
|
|
18
|
+
{ lbl: "4", val: 3 },
|
|
19
|
+
{ lbl: "SAFE", val: -1 },
|
|
20
|
+
]
|
|
21
|
+
let btnHeight = 0.55
|
|
22
|
+
|
|
23
|
+
let { value = $bindable(), padding = 2, disabled = false } = $props() // padding: % inset top/bottom
|
|
24
|
+
|
|
25
|
+
const steps = selections.length - 1
|
|
26
|
+
const adjusted_scrollarea = 1 - btnHeight - (2 * padding) / 100
|
|
27
|
+
let xpos_percentage = $derived(
|
|
28
|
+
padding + Math.round((selected_i / steps) * adjusted_scrollarea * 100),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const start = (event) => {
|
|
32
|
+
if (disabled) return
|
|
33
|
+
dragging = true
|
|
34
|
+
startY = event.clientY
|
|
35
|
+
startIndex = selected_i
|
|
36
|
+
event.target.setPointerCapture(event.pointerId)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const move = (event) => {
|
|
40
|
+
if (!dragging) return
|
|
41
|
+
const rect = trackEl.getBoundingClientRect()
|
|
42
|
+
const steps = selections.length - 1
|
|
43
|
+
const adjusted_scrollarea = 1 - btnHeight - (2 * padding) / 100
|
|
44
|
+
const scrollHeight = rect.height * adjusted_scrollarea
|
|
45
|
+
const stepHeight = scrollHeight / steps
|
|
46
|
+
const deltaSteps = Math.round((event.clientY - startY) / stepHeight)
|
|
47
|
+
|
|
48
|
+
selected_i = Math.max(0, Math.min(startIndex + deltaSteps, steps))
|
|
49
|
+
|
|
50
|
+
// This is the bindable value
|
|
51
|
+
value = selections[selected_i]?.val
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const stop = (event) => {
|
|
55
|
+
dragging = false
|
|
56
|
+
event.target.releasePointerCapture?.(event.pointerId)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
$effect(() => {
|
|
60
|
+
const trigger = value // We are referencing value so that Svelte triggers the effect smartly
|
|
61
|
+
|
|
62
|
+
if (!initialized) {
|
|
63
|
+
initialized = true
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
playFx("track")
|
|
68
|
+
})
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<div class="slider-holder" style:--bg-slideselect-indicator="url({slideSelectIndicatorImg})" style:--bg-slideselect-thumb="url({slideSelectThumbImg})">
|
|
72
|
+
<div class="slideselect-indicator"></div>
|
|
73
|
+
<div
|
|
74
|
+
bind:this={trackEl}
|
|
75
|
+
class="track"
|
|
76
|
+
onpointermove={move}
|
|
77
|
+
onpointerup={stop}
|
|
78
|
+
onpointerleave={stop}
|
|
79
|
+
role="slider"
|
|
80
|
+
>
|
|
81
|
+
<div
|
|
82
|
+
class="thumb"
|
|
83
|
+
class:dragging
|
|
84
|
+
class:disabled
|
|
85
|
+
onpointerdown={start}
|
|
86
|
+
style="top: {xpos_percentage}%; height: {btnHeight * 100}%"
|
|
87
|
+
></div>
|
|
88
|
+
<!-- <div
|
|
89
|
+
class="slot"
|
|
90
|
+
style="height: {(1 - btnHeight) * 100}%; top: {(btnHeight / 2) * 100}%"
|
|
91
|
+
></div> -->
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<style>
|
|
96
|
+
.slider-holder {
|
|
97
|
+
height: 100%;
|
|
98
|
+
width: 100%;
|
|
99
|
+
display: flex;
|
|
100
|
+
container-type: size;
|
|
101
|
+
padding-top: 0cqh;
|
|
102
|
+
}
|
|
103
|
+
.slideselect-indicator {
|
|
104
|
+
position: relative;
|
|
105
|
+
background: var(--bg-slideselect-indicator);
|
|
106
|
+
background-repeat: no-repeat;
|
|
107
|
+
background-size: contain;
|
|
108
|
+
width: 100%;
|
|
109
|
+
height: 43cqh;
|
|
110
|
+
background-position: top right;
|
|
111
|
+
opacity: 0.7;
|
|
112
|
+
top: 10%;
|
|
113
|
+
}
|
|
114
|
+
.track {
|
|
115
|
+
width: 32cqw;
|
|
116
|
+
height: 100%;
|
|
117
|
+
position: relative;
|
|
118
|
+
border-radius: 4cqw;
|
|
119
|
+
background: rgb(100, 100, 100);
|
|
120
|
+
box-shadow:
|
|
121
|
+
inset 8cqw 1cqh 12cqw rgba(31, 31, 31, 0.75),
|
|
122
|
+
inset 2cqw 0.2cqh 2cqw rgba(31, 31, 31, 0.45),
|
|
123
|
+
inset -2cqw -0.2cqh 2cqw rgba(255, 252, 252, 0.35);
|
|
124
|
+
|
|
125
|
+
.slot {
|
|
126
|
+
display: block;
|
|
127
|
+
content: " ";
|
|
128
|
+
width: 50%;
|
|
129
|
+
background-color: rgb(28, 28, 29);
|
|
130
|
+
position: absolute;
|
|
131
|
+
margin: 0 auto;
|
|
132
|
+
left: 50%;
|
|
133
|
+
top: 50%;
|
|
134
|
+
transform: translate(-50%);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.thumb {
|
|
139
|
+
width: 75%;
|
|
140
|
+
margin: 0 12%;
|
|
141
|
+
background: var(--bg-slideselect-thumb);
|
|
142
|
+
background-size: 100% 100%;
|
|
143
|
+
position: absolute;
|
|
144
|
+
border-radius: 1cqh;
|
|
145
|
+
top: 0%;
|
|
146
|
+
left: 0;
|
|
147
|
+
cursor: grab;
|
|
148
|
+
z-index: 1;
|
|
149
|
+
box-shadow:
|
|
150
|
+
10cqw 0.5cqh 10cqw rgba(0, 0, 0, 0.4),
|
|
151
|
+
inset 1.5cqw 0.5cqh 1cqw rgba(255, 252, 252, 0.35);
|
|
152
|
+
}
|
|
153
|
+
.thumb.dragging {
|
|
154
|
+
cursor: grabbing;
|
|
155
|
+
}
|
|
156
|
+
.thumb.disabled {
|
|
157
|
+
cursor: not-allowed;
|
|
158
|
+
}
|
|
159
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default SlideSelect;
|
|
2
|
+
type SlideSelect = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const SlideSelect: import("svelte").Component<{
|
|
7
|
+
value?: any;
|
|
8
|
+
padding?: number;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}, {}, "value">;
|
|
11
|
+
type $$ComponentProps = {
|
|
12
|
+
value?: any;
|
|
13
|
+
padding?: number;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
<!-- svelte-ignore a11y_role_has_required_aria_props -->
|
|
2
|
+
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
3
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
4
|
+
<script>
|
|
5
|
+
import sliderIndicatorImg from '../../assets/slider-indicator.svg?url'
|
|
6
|
+
import sliderImg from '../../assets/slider.png'
|
|
7
|
+
|
|
8
|
+
let { value = $bindable(0), min = 0, max = 1, onchange, btnHeight = 0.35, padding = 1 } = $props()
|
|
9
|
+
let dragging = $state(false)
|
|
10
|
+
let trackEl = $state()
|
|
11
|
+
let startY = $state(0)
|
|
12
|
+
let startNorm = $state(0)
|
|
13
|
+
|
|
14
|
+
function normalizeValue(val) {
|
|
15
|
+
return (val - min) / (max - min)
|
|
16
|
+
}
|
|
17
|
+
function denormalizeValue(norm) {
|
|
18
|
+
return min + norm * (max - min)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Inverted: top = max, bottom = min (like a real fader)
|
|
22
|
+
let topPercent = $derived(
|
|
23
|
+
padding + ((1 - normalizeValue(value)) * (1 - btnHeight - (2 * padding) / 100) * 100),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const start = (event) => {
|
|
27
|
+
dragging = true
|
|
28
|
+
startY = event.clientY
|
|
29
|
+
startNorm = normalizeValue(value)
|
|
30
|
+
trackEl.setPointerCapture(event.pointerId)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const move = (event) => {
|
|
34
|
+
if (!dragging) return
|
|
35
|
+
const rect = trackEl.getBoundingClientRect()
|
|
36
|
+
const deltaY = event.clientY - startY
|
|
37
|
+
const scrollHeight = rect.height * (1 - btnHeight)
|
|
38
|
+
// Inverted: dragging down decreases value
|
|
39
|
+
const deltaNorm = -(deltaY / scrollHeight)
|
|
40
|
+
const newNorm = Math.max(0, Math.min(startNorm + deltaNorm, 1))
|
|
41
|
+
value = denormalizeValue(newNorm)
|
|
42
|
+
onchange?.(value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const stop = (event) => {
|
|
46
|
+
dragging = false
|
|
47
|
+
trackEl.releasePointerCapture?.(event.pointerId)
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<div class="slider-holder" style:--bg-slider-indicator="url({sliderIndicatorImg})" style:--bg-slider="url({sliderImg})">
|
|
52
|
+
<div
|
|
53
|
+
class="slider-indicator"
|
|
54
|
+
style="height: {(1 - btnHeight) * 100}%; top: {(btnHeight / 2) * 100}%"
|
|
55
|
+
></div>
|
|
56
|
+
<div
|
|
57
|
+
bind:this={trackEl}
|
|
58
|
+
class="track"
|
|
59
|
+
onpointermove={move}
|
|
60
|
+
onpointerup={stop}
|
|
61
|
+
onpointerleave={stop}
|
|
62
|
+
role="slider"
|
|
63
|
+
>
|
|
64
|
+
<div
|
|
65
|
+
class="thumb"
|
|
66
|
+
class:dragging
|
|
67
|
+
onpointerdown={start}
|
|
68
|
+
style="top: {topPercent}%; height: {btnHeight * 100}%"
|
|
69
|
+
></div>
|
|
70
|
+
<div
|
|
71
|
+
class="slot"
|
|
72
|
+
style="height: {(1 - btnHeight) * 100}%; top: {(btnHeight / 2) * 100}%"
|
|
73
|
+
></div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<style>
|
|
78
|
+
.slider-holder {
|
|
79
|
+
height: 100%;
|
|
80
|
+
width: 5cqw;
|
|
81
|
+
display: flex;
|
|
82
|
+
container-type: size;
|
|
83
|
+
padding-top: 2cqh;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.slider-indicator {
|
|
87
|
+
position: relative;
|
|
88
|
+
background: var(--bg-slider-indicator);
|
|
89
|
+
background-repeat: no-repeat;
|
|
90
|
+
background-size: contain;
|
|
91
|
+
width: 55cqw;
|
|
92
|
+
height: 100%;
|
|
93
|
+
background-position: center;
|
|
94
|
+
opacity: 0.7;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.track {
|
|
98
|
+
width: 45cqw;
|
|
99
|
+
height: 100%;
|
|
100
|
+
position: relative;
|
|
101
|
+
border-radius: 8cqw;
|
|
102
|
+
box-shadow:
|
|
103
|
+
inset 9cqw 2cqh 15cqw rgba(31, 31, 31, 0.75),
|
|
104
|
+
inset 1.5cqw 0.2cqh 1.5cqw rgba(31, 31, 31, 0.45),
|
|
105
|
+
inset -1.5cqw -0.2cqh 1.5cqw rgba(255, 252, 252, 0.35);
|
|
106
|
+
|
|
107
|
+
.slot {
|
|
108
|
+
display: block;
|
|
109
|
+
content: " ";
|
|
110
|
+
width: 50%;
|
|
111
|
+
background-color: rgb(28, 28, 29);
|
|
112
|
+
border-radius: 3cqw;
|
|
113
|
+
position: absolute;
|
|
114
|
+
margin: 0 auto;
|
|
115
|
+
left: 50%;
|
|
116
|
+
top: 50%;
|
|
117
|
+
transform: translate(-50%);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.thumb {
|
|
122
|
+
width: 80%;
|
|
123
|
+
margin: 0 10%;
|
|
124
|
+
background: var(--bg-slider);
|
|
125
|
+
background-size: 100% 100%;
|
|
126
|
+
position: absolute;
|
|
127
|
+
top: 0%;
|
|
128
|
+
left: 0;
|
|
129
|
+
cursor: grab;
|
|
130
|
+
z-index: 1;
|
|
131
|
+
border-radius: 10cqw;
|
|
132
|
+
box-shadow:
|
|
133
|
+
15cqw 1cqh 8cqw rgba(0, 0, 0, 0.4),
|
|
134
|
+
inset 1.5cqw 0.5cqh 1cqw rgba(255, 252, 252, 0.35);
|
|
135
|
+
}
|
|
136
|
+
.thumb.dragging {
|
|
137
|
+
cursor: grabbing;
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export default Slider;
|
|
2
|
+
type Slider = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const Slider: import("svelte").Component<{
|
|
7
|
+
value?: number;
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
onchange: any;
|
|
11
|
+
btnHeight?: number;
|
|
12
|
+
padding?: number;
|
|
13
|
+
}, {}, "value">;
|
|
14
|
+
type $$ComponentProps = {
|
|
15
|
+
value?: number;
|
|
16
|
+
min?: number;
|
|
17
|
+
max?: number;
|
|
18
|
+
onchange: any;
|
|
19
|
+
btnHeight?: number;
|
|
20
|
+
padding?: number;
|
|
21
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
2
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
3
|
+
<!-- svelte-ignore a11y_missing_attribute -->
|
|
4
|
+
<script lang="ts">
|
|
5
|
+
let { timestamp } = $props()
|
|
6
|
+
import { playFx } from "../../fx/soundfx"
|
|
7
|
+
import DigitRoller from "./DigitRoller.svelte"
|
|
8
|
+
import counterBgImg from '../../assets/counter_bg.png'
|
|
9
|
+
|
|
10
|
+
let correction = $state(0)
|
|
11
|
+
function count_to_str(nr: number) {
|
|
12
|
+
var cor_nr = Math.floor(nr - correction)
|
|
13
|
+
if (cor_nr < 0) cor_nr += 1000
|
|
14
|
+
|
|
15
|
+
if (cor_nr > 99) {
|
|
16
|
+
return cor_nr.toString()
|
|
17
|
+
} else if (cor_nr > 9) {
|
|
18
|
+
return "0" + cor_nr.toString()
|
|
19
|
+
} else {
|
|
20
|
+
return "00" + cor_nr.toString()
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function get_digit(nr, i) {
|
|
25
|
+
return count_to_str(nr).charAt(i)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function reset() {
|
|
29
|
+
playFx("counter")
|
|
30
|
+
correction = timestamp
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<!-- <div>{count_to_str(timestamp)}</div> -->
|
|
35
|
+
|
|
36
|
+
<div class="wrapper" style:--bg-counter="url({counterBgImg})">
|
|
37
|
+
<div class="counter">
|
|
38
|
+
<a onmousedown={() => reset()}> </a>
|
|
39
|
+
<div class="number-ticker">
|
|
40
|
+
<DigitRoller digit={get_digit(timestamp, 0)} />
|
|
41
|
+
<DigitRoller digit={get_digit(timestamp, 1)} />
|
|
42
|
+
<DigitRoller digit={get_digit(timestamp, 2)} />
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
.number-ticker {
|
|
49
|
+
display: flex;
|
|
50
|
+
height: 17cqw;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.wrapper {
|
|
55
|
+
container-type: size;
|
|
56
|
+
height: 8cqh;
|
|
57
|
+
aspect-ratio: 180 / 80;
|
|
58
|
+
}
|
|
59
|
+
.counter {
|
|
60
|
+
padding: 15.2cqw 17cqh 13.8cqw;
|
|
61
|
+
color: rgb(216, 216, 216);
|
|
62
|
+
background-image: var(--bg-counter);
|
|
63
|
+
background-size: 100% 100%;
|
|
64
|
+
background-repeat: no-repeat;
|
|
65
|
+
width: 100cqw;
|
|
66
|
+
height: 100cqh;
|
|
67
|
+
position: relative;
|
|
68
|
+
user-select: none;
|
|
69
|
+
|
|
70
|
+
box-sizing: border-box;
|
|
71
|
+
}
|
|
72
|
+
a {
|
|
73
|
+
width: 11cqw;
|
|
74
|
+
height: 25cqh;
|
|
75
|
+
background-color: rgb(34, 34, 34);
|
|
76
|
+
border-radius: 50%;
|
|
77
|
+
position: absolute;
|
|
78
|
+
right: 16.7cqw;
|
|
79
|
+
top: 50%;
|
|
80
|
+
transform: translateY(-15cqh);
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
box-shadow:
|
|
83
|
+
6cqw 15cqh 5.5cqw rgba(0, 0, 0, 0.8),
|
|
84
|
+
inset 1cqw 2.5cqh 1.7cqw rgba(255, 255, 255, 0.4);
|
|
85
|
+
|
|
86
|
+
&:active {
|
|
87
|
+
box-shadow:
|
|
88
|
+
5.5cqw 13.75cqh 6cqw rgba(0, 0, 0, 0.8),
|
|
89
|
+
inset 1cqw 2.5cqh 1.7cqw rgba(255, 255, 255, 0.4);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const soundPaths: {
|
|
2
|
+
stop: string;
|
|
3
|
+
ffwd: string;
|
|
4
|
+
pause: string;
|
|
5
|
+
play: string;
|
|
6
|
+
track: string;
|
|
7
|
+
counter: string;
|
|
8
|
+
record: string;
|
|
9
|
+
};
|
|
10
|
+
type SoundKey = keyof typeof soundPaths;
|
|
11
|
+
export declare function playFx(key: SoundKey, volume?: number): Promise<void>;
|
|
12
|
+
export declare function playLoop(key: SoundKey, volume?: number): Promise<void>;
|
|
13
|
+
export declare function stopLoop(key: SoundKey): void;
|
|
14
|
+
export {};
|