4track 0.1.6 → 0.1.7
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/README.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
A browser-based 4-track audio recorder built with the Web Audio API and SvelteKit. Designed for low-latency recording with overdub support, latency compensation, and sample-accurate multi-track playback — all running entirely client-side with no server required.
|
|
4
4
|
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Give it a try
|
|
8
|
+
|
|
9
|
+
You can try out the 4 track recorder on [4track.cc](https://www.4track.cc)
|
|
10
|
+
|
|
5
11
|
## Installation
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -12,7 +18,7 @@ npm install 4-track-recorder
|
|
|
12
18
|
|
|
13
19
|
```svelte
|
|
14
20
|
<script>
|
|
15
|
-
import { FourTrack } from
|
|
21
|
+
import { FourTrack } from "4-track-recorder"
|
|
16
22
|
</script>
|
|
17
23
|
|
|
18
24
|
<FourTrack />
|
|
@@ -28,7 +34,7 @@ Use `bind:save` and `bind:load` to get functions you can call from your own UI:
|
|
|
28
34
|
|
|
29
35
|
```svelte
|
|
30
36
|
<script>
|
|
31
|
-
import { FourTrack } from
|
|
37
|
+
import { FourTrack } from "4-track-recorder"
|
|
32
38
|
|
|
33
39
|
let save
|
|
34
40
|
let load
|
|
@@ -37,9 +43,9 @@ Use `bind:save` and `bind:load` to get functions you can call from your own UI:
|
|
|
37
43
|
const blob = save()
|
|
38
44
|
// Download as file
|
|
39
45
|
const url = URL.createObjectURL(blob)
|
|
40
|
-
const a = document.createElement(
|
|
46
|
+
const a = document.createElement("a")
|
|
41
47
|
a.href = url
|
|
42
|
-
a.download =
|
|
48
|
+
a.download = "my-song.4trk"
|
|
43
49
|
a.click()
|
|
44
50
|
URL.revokeObjectURL(url)
|
|
45
51
|
}
|
|
@@ -51,7 +57,7 @@ Use `bind:save` and `bind:load` to get functions you can call from your own UI:
|
|
|
51
57
|
|
|
52
58
|
// Or load directly from a URL
|
|
53
59
|
function loadFromUrl() {
|
|
54
|
-
load(
|
|
60
|
+
load("https://example.com/songs/demo.4trk")
|
|
55
61
|
}
|
|
56
62
|
</script>
|
|
57
63
|
|
|
@@ -71,10 +77,10 @@ Pass a URL or `File` to `initialProject` to auto-load a project when the compone
|
|
|
71
77
|
|
|
72
78
|
## Props
|
|
73
79
|
|
|
74
|
-
| Prop
|
|
75
|
-
|
|
76
|
-
| `hiddenTracks`
|
|
77
|
-
| `onready`
|
|
78
|
-
| `save`
|
|
79
|
-
| `load`
|
|
80
|
-
| `initialProject` | `string \| File`
|
|
80
|
+
| Prop | Type | Description |
|
|
81
|
+
| ---------------- | ------------------------------------------------------ | ----------------------------------------------------------------- |
|
|
82
|
+
| `hiddenTracks` | `HiddenTrackConfig[]` | Background audio tracks (e.g. cassette hiss) |
|
|
83
|
+
| `onready` | `(detail: { engine: AudioEngine }) => void` | Callback when the engine is initialized |
|
|
84
|
+
| `save` | `() => Blob` (bindable) | Bind to get a function that exports the project as a `.4trk` blob |
|
|
85
|
+
| `load` | `(source: File \| string) => Promise<void>` (bindable) | Bind to get a function that imports a `.4trk` file or URL |
|
|
86
|
+
| `initialProject` | `string \| File` | URL or File to auto-load on mount |
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
4
4
|
<script>
|
|
5
5
|
import { playFx } from "../../fx/soundfx"
|
|
6
|
-
import slideSelectIndicatorImg from
|
|
7
|
-
import slideSelectThumbImg from
|
|
6
|
+
import slideSelectIndicatorImg from "../../assets/slideselect-indicator.svg?url"
|
|
7
|
+
import slideSelectThumbImg from "../../assets/slideselect-thumb.png"
|
|
8
8
|
let dragging = $state(false)
|
|
9
9
|
let trackEl = $state()
|
|
10
10
|
let selected_i = $state(4)
|
|
@@ -68,7 +68,11 @@
|
|
|
68
68
|
})
|
|
69
69
|
</script>
|
|
70
70
|
|
|
71
|
-
<div
|
|
71
|
+
<div
|
|
72
|
+
class="slider-holder"
|
|
73
|
+
style:--bg-slideselect-indicator="url({slideSelectIndicatorImg})"
|
|
74
|
+
style:--bg-slideselect-thumb="url({slideSelectThumbImg})"
|
|
75
|
+
>
|
|
72
76
|
<div class="slideselect-indicator"></div>
|
|
73
77
|
<div
|
|
74
78
|
bind:this={trackEl}
|
|
@@ -117,6 +121,7 @@
|
|
|
117
121
|
position: relative;
|
|
118
122
|
border-radius: 4cqw;
|
|
119
123
|
background: rgb(100, 100, 100);
|
|
124
|
+
touch-action: none;
|
|
120
125
|
box-shadow:
|
|
121
126
|
inset 8cqw 1cqh 12cqw rgba(31, 31, 31, 0.75),
|
|
122
127
|
inset 2cqw 0.2cqh 2cqw rgba(31, 31, 31, 0.45),
|
|
@@ -142,6 +147,7 @@
|
|
|
142
147
|
background-size: 100% 100%;
|
|
143
148
|
position: absolute;
|
|
144
149
|
border-radius: 1cqh;
|
|
150
|
+
touch-action: none;
|
|
145
151
|
top: 0%;
|
|
146
152
|
left: 0;
|
|
147
153
|
cursor: grab;
|
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
<!-- svelte-ignore a11y_interactive_supports_focus -->
|
|
3
3
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
4
4
|
<script>
|
|
5
|
-
import sliderIndicatorImg from
|
|
6
|
-
import sliderImg from
|
|
5
|
+
import sliderIndicatorImg from "../../assets/slider-indicator.svg?url"
|
|
6
|
+
import sliderImg from "../../assets/slider.png"
|
|
7
7
|
|
|
8
|
-
let {
|
|
8
|
+
let {
|
|
9
|
+
value = $bindable(0),
|
|
10
|
+
min = 0,
|
|
11
|
+
max = 1,
|
|
12
|
+
onchange,
|
|
13
|
+
btnHeight = 0.35,
|
|
14
|
+
padding = 1,
|
|
15
|
+
} = $props()
|
|
9
16
|
let dragging = $state(false)
|
|
10
17
|
let trackEl = $state()
|
|
11
18
|
let startY = $state(0)
|
|
@@ -20,7 +27,8 @@
|
|
|
20
27
|
|
|
21
28
|
// Inverted: top = max, bottom = min (like a real fader)
|
|
22
29
|
let topPercent = $derived(
|
|
23
|
-
padding +
|
|
30
|
+
padding +
|
|
31
|
+
(1 - normalizeValue(value)) * (1 - btnHeight - (2 * padding) / 100) * 100,
|
|
24
32
|
)
|
|
25
33
|
|
|
26
34
|
const start = (event) => {
|
|
@@ -48,7 +56,11 @@
|
|
|
48
56
|
}
|
|
49
57
|
</script>
|
|
50
58
|
|
|
51
|
-
<div
|
|
59
|
+
<div
|
|
60
|
+
class="slider-holder"
|
|
61
|
+
style:--bg-slider-indicator="url({sliderIndicatorImg})"
|
|
62
|
+
style:--bg-slider="url({sliderImg})"
|
|
63
|
+
>
|
|
52
64
|
<div
|
|
53
65
|
class="slider-indicator"
|
|
54
66
|
style="height: {(1 - btnHeight) * 100}%; top: {(btnHeight / 2) * 100}%"
|
|
@@ -99,6 +111,7 @@
|
|
|
99
111
|
height: 100%;
|
|
100
112
|
position: relative;
|
|
101
113
|
border-radius: 8cqw;
|
|
114
|
+
touch-action: none;
|
|
102
115
|
box-shadow:
|
|
103
116
|
inset 9cqw 2cqh 15cqw rgba(31, 31, 31, 0.75),
|
|
104
117
|
inset 1.5cqw 0.2cqh 1.5cqw rgba(31, 31, 31, 0.45),
|
|
@@ -129,6 +142,7 @@
|
|
|
129
142
|
cursor: grab;
|
|
130
143
|
z-index: 1;
|
|
131
144
|
border-radius: 10cqw;
|
|
145
|
+
touch-action: none
|
|
132
146
|
box-shadow:
|
|
133
147
|
15cqw 1cqh 8cqw rgba(0, 0, 0, 0.4),
|
|
134
148
|
inset 1.5cqw 0.5cqh 1cqw rgba(255, 252, 252, 0.35);
|