4track 0.1.9 → 0.1.10
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/dist/audio/constants.d.ts +1 -1
- package/dist/audio/constants.js +1 -1
- package/dist/audio/engine.svelte.d.ts +2 -0
- package/dist/audio/engine.svelte.js +11 -7
- package/dist/components/TransportButtons.svelte +2 -1
- package/dist/components/els/DigitRoller.svelte +17 -31
- package/dist/components/els/DigitRoller.svelte.d.ts +2 -1
- package/dist/components/els/DigitRollerFirst.svelte +82 -0
- package/dist/components/els/DigitRollerFirst.svelte.d.ts +5 -0
- package/dist/components/els/Timestamp.svelte +12 -21
- package/package.json +1 -1
package/dist/audio/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Default configuration and constraints for the audio engine.
|
|
2
2
|
import workletUrl from "../assets/recorder-worklet.js?url";
|
|
3
|
-
export const PLAYBACK_TICK_MS =
|
|
3
|
+
export const PLAYBACK_TICK_MS = 32; // 30 fps
|
|
4
4
|
export const DEFAULT_CONFIG = {
|
|
5
5
|
sampleRate: 32000, // 44100 | 48000 | 96000
|
|
6
6
|
bitDepth: 16, // 8 = lo-fi, 16 = CD quality, 32 = float (uncompressed)
|
|
@@ -9,6 +9,8 @@ export declare class AudioEngine {
|
|
|
9
9
|
trimValue: number;
|
|
10
10
|
recordingVolume: number;
|
|
11
11
|
tracks: Track[];
|
|
12
|
+
/** Rounds a time value to 2 decimal places for position display. */
|
|
13
|
+
private roundPosition;
|
|
12
14
|
private config;
|
|
13
15
|
private audioContext;
|
|
14
16
|
private masterGainNode;
|
|
@@ -17,6 +17,10 @@ export class AudioEngine {
|
|
|
17
17
|
trimValue = $state(-1);
|
|
18
18
|
recordingVolume = $state(0.75);
|
|
19
19
|
tracks;
|
|
20
|
+
/** Rounds a time value to 2 decimal places for position display. */
|
|
21
|
+
roundPosition(seconds) {
|
|
22
|
+
return Math.round(seconds * 100) / 100;
|
|
23
|
+
}
|
|
20
24
|
// ─── Private state ──────────────────────────────────────────────────
|
|
21
25
|
config;
|
|
22
26
|
// Web Audio graph
|
|
@@ -172,7 +176,7 @@ export class AudioEngine {
|
|
|
172
176
|
const max = this.getMaxDuration();
|
|
173
177
|
const clamped = Math.max(0, Math.min(seconds, max));
|
|
174
178
|
this.playbackOffset = clamped;
|
|
175
|
-
this.position =
|
|
179
|
+
this.position = this.roundPosition(clamped);
|
|
176
180
|
if (this.playState === "playing") {
|
|
177
181
|
this.stopSources(this.activePlaybackSources);
|
|
178
182
|
this.clearPlaybackTick();
|
|
@@ -219,14 +223,14 @@ export class AudioEngine {
|
|
|
219
223
|
}
|
|
220
224
|
this.playbackStartTime = startTime;
|
|
221
225
|
this.playbackOffset = offsetSeconds;
|
|
222
|
-
this.position =
|
|
226
|
+
this.position = this.roundPosition(offsetSeconds);
|
|
223
227
|
this.playState = "playing";
|
|
224
228
|
this.playbackTickId = window.setInterval(() => {
|
|
225
229
|
const elapsed = ctx.currentTime - this.playbackStartTime;
|
|
226
|
-
this.position =
|
|
230
|
+
this.position = this.roundPosition(this.playbackOffset + elapsed);
|
|
227
231
|
if (elapsed >= effectiveDuration) {
|
|
228
232
|
this.playbackOffset = maxDuration;
|
|
229
|
-
this.position =
|
|
233
|
+
this.position = this.roundPosition(maxDuration);
|
|
230
234
|
this.clearPlaybackTick();
|
|
231
235
|
this.playState = "stopped";
|
|
232
236
|
}
|
|
@@ -242,7 +246,7 @@ export class AudioEngine {
|
|
|
242
246
|
this.stopSources(this.activePlaybackSources);
|
|
243
247
|
}
|
|
244
248
|
this.clearPlaybackTick();
|
|
245
|
-
this.position =
|
|
249
|
+
this.position = this.roundPosition(this.playbackOffset);
|
|
246
250
|
this.playState = "paused";
|
|
247
251
|
}
|
|
248
252
|
/** Stops playback or recording. If recording, finalizes and merges the recorded audio. */
|
|
@@ -258,7 +262,7 @@ export class AudioEngine {
|
|
|
258
262
|
this.stopSources(this.activePlaybackSources);
|
|
259
263
|
}
|
|
260
264
|
this.clearPlaybackTick();
|
|
261
|
-
this.position =
|
|
265
|
+
this.position = this.roundPosition(this.playbackOffset);
|
|
262
266
|
}
|
|
263
267
|
this.playState = "stopped";
|
|
264
268
|
}
|
|
@@ -457,7 +461,7 @@ export class AudioEngine {
|
|
|
457
461
|
this.recordingTrackIndex = trackIndex;
|
|
458
462
|
this.recordingLatencySeconds = recordLatencySeconds;
|
|
459
463
|
this.punchInOffset = this.playbackOffset;
|
|
460
|
-
this.position =
|
|
464
|
+
this.position = this.roundPosition(this.punchInOffset);
|
|
461
465
|
this.playState = "recording";
|
|
462
466
|
// Play other tracks for overdub monitoring, start meters and position timer
|
|
463
467
|
this.playOtherTracksForMonitoring(trackIndex, this.punchInOffset);
|
|
@@ -43,8 +43,9 @@
|
|
|
43
43
|
$effect(() => {
|
|
44
44
|
let timer: ReturnType<typeof setInterval> | undefined
|
|
45
45
|
|
|
46
|
-
// Trigger stop when rewinding and reaching the
|
|
46
|
+
// Trigger stop when rewinding and reaching the beginning
|
|
47
47
|
if (speed < 0 && engine.position + speed / 50 <= 0) {
|
|
48
|
+
engine.seek(0)
|
|
48
49
|
clicky("stop")
|
|
49
50
|
}
|
|
50
51
|
if (engine.position + speed / 50 > engine.duration) {
|
|
@@ -1,48 +1,34 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
let {
|
|
2
|
+
let { timestamp = 0, index = 0 } = $props()
|
|
3
3
|
|
|
4
4
|
let roller: HTMLDivElement
|
|
5
|
-
let prev = -1
|
|
6
|
-
let wrapping = false
|
|
7
5
|
const step = 100 / 12
|
|
6
|
+
|
|
8
7
|
const pos = (d: number) => `translateY(${-step * (1 + d)}%)`
|
|
9
8
|
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
roller.offsetHeight
|
|
14
|
-
roller.style.transition = ""
|
|
9
|
+
function sigmoidEase(x: number, k: number, midpoint: number) {
|
|
10
|
+
const f = (v: number) => 1 / (1 + Math.exp(-k * (v - midpoint)))
|
|
11
|
+
return (f(x) - f(0)) / (f(1) - f(0))
|
|
15
12
|
}
|
|
16
13
|
|
|
17
14
|
$effect(() => {
|
|
18
|
-
const d = +digit
|
|
19
15
|
if (!roller) return
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
}
|
|
17
|
+
// Get the value at this digit position (index 0=hundreds, 1=tens, 2=ones)
|
|
18
|
+
const divisor = Math.pow(10, 2 - index) //1, 10, 100
|
|
19
|
+
const value = (timestamp / divisor) % 10
|
|
33
20
|
|
|
34
|
-
|
|
35
|
-
|
|
21
|
+
// Split into whole digit and fractional part for smooth rolling
|
|
22
|
+
const digit = Math.floor(value)
|
|
23
|
+
const fraction = value - digit
|
|
36
24
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
jumpTo(pos(+digit))
|
|
41
|
-
}
|
|
25
|
+
const eased = divisor == 1 ? fraction : sigmoidEase(fraction, divisor == 10 ? 12 : 50, divisor == 10 ? 0.98 : 0.9995)
|
|
26
|
+
roller.style.transform = pos(digit + eased)
|
|
27
|
+
})
|
|
42
28
|
</script>
|
|
43
29
|
|
|
44
30
|
<div class="digits">
|
|
45
|
-
<div class="roller" bind:this={roller}
|
|
31
|
+
<div class="roller" bind:this={roller}>
|
|
46
32
|
{#each [9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0] as n}
|
|
47
33
|
<div class="digit"><span>{n}</span></div>
|
|
48
34
|
{/each}
|
|
@@ -69,9 +55,9 @@
|
|
|
69
55
|
transform: translateY(-2cqw);
|
|
70
56
|
}
|
|
71
57
|
}
|
|
72
|
-
.roller {
|
|
58
|
+
/* .roller {
|
|
73
59
|
transition: 0.4s ease transform;
|
|
74
|
-
}
|
|
60
|
+
} */
|
|
75
61
|
.digit {
|
|
76
62
|
font-size: 35cqh;
|
|
77
63
|
/* letter-spacing: 7cqw; */
|
|
@@ -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>
|
|
@@ -4,30 +4,21 @@
|
|
|
4
4
|
<script lang="ts">
|
|
5
5
|
let { timestamp } = $props()
|
|
6
6
|
import { playFx } from "../../fx/soundfx"
|
|
7
|
+
import { Tween } from "svelte/motion"
|
|
7
8
|
import DigitRoller from "./DigitRoller.svelte"
|
|
8
|
-
import counterBgImg from
|
|
9
|
+
import counterBgImg from "../../assets/counter_bg.png"
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
function count_to_str(nr: number) {
|
|
12
|
-
var cor_nr = Math.floor(nr - correction)
|
|
13
|
-
if (cor_nr < 0) cor_nr += 1000
|
|
11
|
+
const correction = new Tween(0)
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
return "00" + cor_nr.toString()
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function get_digit(nr, i) {
|
|
25
|
-
return count_to_str(nr).charAt(i)
|
|
26
|
-
}
|
|
13
|
+
let corrected = $derived.by(() => {
|
|
14
|
+
let v = timestamp - correction.current
|
|
15
|
+
if (v < 0) v += 1000
|
|
16
|
+
return v % 1000
|
|
17
|
+
})
|
|
27
18
|
|
|
28
19
|
function reset() {
|
|
29
20
|
playFx("counter")
|
|
30
|
-
correction
|
|
21
|
+
correction.set(timestamp, { duration: 400 })
|
|
31
22
|
}
|
|
32
23
|
</script>
|
|
33
24
|
|
|
@@ -37,9 +28,9 @@
|
|
|
37
28
|
<div class="counter">
|
|
38
29
|
<a onmousedown={() => reset()}> </a>
|
|
39
30
|
<div class="number-ticker">
|
|
40
|
-
<DigitRoller
|
|
41
|
-
<DigitRoller
|
|
42
|
-
<DigitRoller
|
|
31
|
+
<DigitRoller timestamp={corrected} index={0} />
|
|
32
|
+
<DigitRoller timestamp={corrected} index={1} />
|
|
33
|
+
<DigitRoller timestamp={corrected} index={2} />
|
|
43
34
|
</div>
|
|
44
35
|
</div>
|
|
45
36
|
</div>
|