entangled-worlds-orbiters-shared 2.7.0 → 2.8.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.
|
@@ -14,6 +14,16 @@ export interface SyncSettingsLike {
|
|
|
14
14
|
sourceTimeSec?: unknown;
|
|
15
15
|
} | null | undefined> | null;
|
|
16
16
|
gridStartTimeSec?: unknown;
|
|
17
|
+
/** Whether this track time-stretches onto the grid. A BOOLEAN, so `false` is an answer rather than
|
|
18
|
+
* an absence — see `resolveWarpEnabledFromTrackData`. */
|
|
19
|
+
warpEnabled?: unknown;
|
|
20
|
+
/** Whether this track plays looped. Same boolean rule as `warpEnabled`, and the same three states:
|
|
21
|
+
* on, off, and nobody-said — see `resolveLoopEnabledFromTrackData`. */
|
|
22
|
+
loopEnabled?: unknown;
|
|
23
|
+
/** The loop region in SECONDS (storage's time unit; the audio engine works in ms and converts at
|
|
24
|
+
* its edge). Only ever meaningful as a pair — see `resolveLoopRangeSecFromTrackData`. */
|
|
25
|
+
loopStartSec?: unknown;
|
|
26
|
+
loopEndSec?: unknown;
|
|
17
27
|
}
|
|
18
28
|
export interface TrackMetadataLike {
|
|
19
29
|
trackBpm?: unknown;
|
|
@@ -71,3 +81,33 @@ export declare function resolveTrackBpmFromTrackData(trackData?: TrackDataLike |
|
|
|
71
81
|
* Returns null when nothing resolves (consumers default to 0).
|
|
72
82
|
*/
|
|
73
83
|
export declare function resolvePrimaryGridMarkerTimeSecFromTrackData(trackData?: TrackDataLike | null): number | null;
|
|
84
|
+
/**
|
|
85
|
+
* Resolve whether this track should warp (time-stretch onto the grid) for the viewer.
|
|
86
|
+
*
|
|
87
|
+
* Returns null when nothing anywhere expressed a preference. Null means "no opinion", NOT "off" —
|
|
88
|
+
* the caller supplies its own default, because only the caller knows its runtime constraints (in
|
|
89
|
+
* orbiters, a deck with no tempo cannot warp at all, whatever was saved).
|
|
90
|
+
*/
|
|
91
|
+
export declare function resolveWarpEnabledFromTrackData(trackData?: TrackDataLike | null): boolean | null;
|
|
92
|
+
/**
|
|
93
|
+
* Resolve whether this track should play looped for the viewer.
|
|
94
|
+
*
|
|
95
|
+
* Same three states as warp — on, off, nobody-said — but no smart default anywhere behind it: the
|
|
96
|
+
* server stops at the owner's setting, so null here is genuinely common and simply means the caller
|
|
97
|
+
* keeps whatever looping default it already had.
|
|
98
|
+
*/
|
|
99
|
+
export declare function resolveLoopEnabledFromTrackData(trackData?: TrackDataLike | null): boolean | null;
|
|
100
|
+
/**
|
|
101
|
+
* Resolve the saved loop region, in seconds, or null when no layer holds a complete one.
|
|
102
|
+
*
|
|
103
|
+
* Resolved as a PAIR, never edge by edge: a start borrowed from one layer and an end from another
|
|
104
|
+
* describes a loop nobody set, and can invert. `start` of 0 is a real value, so the guard is a
|
|
105
|
+
* finiteness test rather than a truthiness one.
|
|
106
|
+
*
|
|
107
|
+
* The caller must still clamp this against the audio's real duration before using it — a region
|
|
108
|
+
* saved against an earlier version of the track can outlive the audio it was drawn on.
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveLoopRangeSecFromTrackData(trackData?: TrackDataLike | null): {
|
|
111
|
+
startSec: number;
|
|
112
|
+
endSec: number;
|
|
113
|
+
} | null;
|
|
@@ -20,6 +20,22 @@ function firstResolvedValue(candidates, parser) {
|
|
|
20
20
|
}
|
|
21
21
|
return null;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* The first candidate that is an actual boolean, or null when none of them is.
|
|
25
|
+
*
|
|
26
|
+
* Deliberately separate from `firstResolvedValue`: that one folds "absent" and "invalid" together
|
|
27
|
+
* and returns a number, which is exactly the wrong shape here. A warp preference has THREE states —
|
|
28
|
+
* on, off, and nobody-said — and `false` is the middle one, not the last. Any `||`, any truthiness
|
|
29
|
+
* test, any `?? ` against a default collapses off into nobody-said and loses the user's choice.
|
|
30
|
+
*/
|
|
31
|
+
function firstBoolean(candidates) {
|
|
32
|
+
for (const candidate of candidates) {
|
|
33
|
+
if (typeof candidate === 'boolean') {
|
|
34
|
+
return candidate;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
23
39
|
/**
|
|
24
40
|
* Resolve the effective track BPM. Precedence (first finite, > 0 wins):
|
|
25
41
|
* owner `trackBpm` → owner `effectiveSettings.sync.trackBpm` → per-user `trackUserSettings.sync.trackBpm`
|
|
@@ -64,3 +80,71 @@ export function resolvePrimaryGridMarkerTimeSecFromTrackData(trackData = null) {
|
|
|
64
80
|
trackData?.metadata?.wrap?.gridStartTimeSec,
|
|
65
81
|
], toNonNegativeNumber);
|
|
66
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* The shared walk behind the boolean preferences below.
|
|
85
|
+
*
|
|
86
|
+
* The server already applied the real precedence — the viewer's own saved setting, then the owner's,
|
|
87
|
+
* then (for warp) a smart default — and publishes the answer at `effectiveSettings.sync`. So that is
|
|
88
|
+
* read FIRST: it is the resolved value, not a competing opinion. The per-user path below it covers
|
|
89
|
+
* the shapes where the server has not resolved anything (a raw `trackUserSettings` fetch that has not
|
|
90
|
+
* been merged into a track payload yet).
|
|
91
|
+
*
|
|
92
|
+
* One walk, not one per field: warp and loop differ in what they MEAN, never in where they are found,
|
|
93
|
+
* and two hand-maintained copies of this list would drift the moment a payload shape changed.
|
|
94
|
+
*/
|
|
95
|
+
function resolveSyncFlagFromTrackData(trackData, field) {
|
|
96
|
+
return firstBoolean([
|
|
97
|
+
trackData?.track?.metadata?.effectiveSettings?.sync?.[field],
|
|
98
|
+
trackData?.release?.metadata?.effectiveSettings?.sync?.[field],
|
|
99
|
+
trackData?.metadata?.effectiveSettings?.sync?.[field],
|
|
100
|
+
trackData?.effectiveSettings?.sync?.[field],
|
|
101
|
+
trackData?.trackUserSettings?.sync?.[field],
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve whether this track should warp (time-stretch onto the grid) for the viewer.
|
|
106
|
+
*
|
|
107
|
+
* Returns null when nothing anywhere expressed a preference. Null means "no opinion", NOT "off" —
|
|
108
|
+
* the caller supplies its own default, because only the caller knows its runtime constraints (in
|
|
109
|
+
* orbiters, a deck with no tempo cannot warp at all, whatever was saved).
|
|
110
|
+
*/
|
|
111
|
+
export function resolveWarpEnabledFromTrackData(trackData = null) {
|
|
112
|
+
return resolveSyncFlagFromTrackData(trackData, 'warpEnabled');
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Resolve whether this track should play looped for the viewer.
|
|
116
|
+
*
|
|
117
|
+
* Same three states as warp — on, off, nobody-said — but no smart default anywhere behind it: the
|
|
118
|
+
* server stops at the owner's setting, so null here is genuinely common and simply means the caller
|
|
119
|
+
* keeps whatever looping default it already had.
|
|
120
|
+
*/
|
|
121
|
+
export function resolveLoopEnabledFromTrackData(trackData = null) {
|
|
122
|
+
return resolveSyncFlagFromTrackData(trackData, 'loopEnabled');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Resolve the saved loop region, in seconds, or null when no layer holds a complete one.
|
|
126
|
+
*
|
|
127
|
+
* Resolved as a PAIR, never edge by edge: a start borrowed from one layer and an end from another
|
|
128
|
+
* describes a loop nobody set, and can invert. `start` of 0 is a real value, so the guard is a
|
|
129
|
+
* finiteness test rather than a truthiness one.
|
|
130
|
+
*
|
|
131
|
+
* The caller must still clamp this against the audio's real duration before using it — a region
|
|
132
|
+
* saved against an earlier version of the track can outlive the audio it was drawn on.
|
|
133
|
+
*/
|
|
134
|
+
export function resolveLoopRangeSecFromTrackData(trackData = null) {
|
|
135
|
+
const candidates = [
|
|
136
|
+
trackData?.track?.metadata?.effectiveSettings?.sync,
|
|
137
|
+
trackData?.release?.metadata?.effectiveSettings?.sync,
|
|
138
|
+
trackData?.metadata?.effectiveSettings?.sync,
|
|
139
|
+
trackData?.effectiveSettings?.sync,
|
|
140
|
+
trackData?.trackUserSettings?.sync,
|
|
141
|
+
];
|
|
142
|
+
for (const candidate of candidates) {
|
|
143
|
+
const startSec = toNonNegativeNumber(candidate?.loopStartSec);
|
|
144
|
+
const endSec = toNonNegativeNumber(candidate?.loopEndSec);
|
|
145
|
+
if (startSec !== null && endSec !== null && endSec > startSec) {
|
|
146
|
+
return { startSec, endSec };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
package/package.json
CHANGED
|
@@ -18,6 +18,16 @@ export interface SyncSettingsLike {
|
|
|
18
18
|
primaryGridMarkerTimeSec?: unknown;
|
|
19
19
|
gridMarkers?: ReadonlyArray<{ sourceTimeSec?: unknown } | null | undefined> | null;
|
|
20
20
|
gridStartTimeSec?: unknown;
|
|
21
|
+
/** Whether this track time-stretches onto the grid. A BOOLEAN, so `false` is an answer rather than
|
|
22
|
+
* an absence — see `resolveWarpEnabledFromTrackData`. */
|
|
23
|
+
warpEnabled?: unknown;
|
|
24
|
+
/** Whether this track plays looped. Same boolean rule as `warpEnabled`, and the same three states:
|
|
25
|
+
* on, off, and nobody-said — see `resolveLoopEnabledFromTrackData`. */
|
|
26
|
+
loopEnabled?: unknown;
|
|
27
|
+
/** The loop region in SECONDS (storage's time unit; the audio engine works in ms and converts at
|
|
28
|
+
* its edge). Only ever meaningful as a pair — see `resolveLoopRangeSecFromTrackData`. */
|
|
29
|
+
loopStartSec?: unknown;
|
|
30
|
+
loopEndSec?: unknown;
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
export interface TrackMetadataLike {
|
|
@@ -72,6 +82,23 @@ function firstResolvedValue(
|
|
|
72
82
|
return null;
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
/**
|
|
86
|
+
* The first candidate that is an actual boolean, or null when none of them is.
|
|
87
|
+
*
|
|
88
|
+
* Deliberately separate from `firstResolvedValue`: that one folds "absent" and "invalid" together
|
|
89
|
+
* and returns a number, which is exactly the wrong shape here. A warp preference has THREE states —
|
|
90
|
+
* on, off, and nobody-said — and `false` is the middle one, not the last. Any `||`, any truthiness
|
|
91
|
+
* test, any `?? ` against a default collapses off into nobody-said and loses the user's choice.
|
|
92
|
+
*/
|
|
93
|
+
function firstBoolean(candidates: ReadonlyArray<unknown>): boolean | null {
|
|
94
|
+
for (const candidate of candidates) {
|
|
95
|
+
if (typeof candidate === 'boolean') {
|
|
96
|
+
return candidate;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
75
102
|
/**
|
|
76
103
|
* Resolve the effective track BPM. Precedence (first finite, > 0 wins):
|
|
77
104
|
* owner `trackBpm` → owner `effectiveSettings.sync.trackBpm` → per-user `trackUserSettings.sync.trackBpm`
|
|
@@ -125,3 +152,85 @@ export function resolvePrimaryGridMarkerTimeSecFromTrackData(
|
|
|
125
152
|
toNonNegativeNumber,
|
|
126
153
|
);
|
|
127
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The shared walk behind the boolean preferences below.
|
|
158
|
+
*
|
|
159
|
+
* The server already applied the real precedence — the viewer's own saved setting, then the owner's,
|
|
160
|
+
* then (for warp) a smart default — and publishes the answer at `effectiveSettings.sync`. So that is
|
|
161
|
+
* read FIRST: it is the resolved value, not a competing opinion. The per-user path below it covers
|
|
162
|
+
* the shapes where the server has not resolved anything (a raw `trackUserSettings` fetch that has not
|
|
163
|
+
* been merged into a track payload yet).
|
|
164
|
+
*
|
|
165
|
+
* One walk, not one per field: warp and loop differ in what they MEAN, never in where they are found,
|
|
166
|
+
* and two hand-maintained copies of this list would drift the moment a payload shape changed.
|
|
167
|
+
*/
|
|
168
|
+
function resolveSyncFlagFromTrackData(
|
|
169
|
+
trackData: TrackDataLike | null,
|
|
170
|
+
field: 'warpEnabled' | 'loopEnabled',
|
|
171
|
+
): boolean | null {
|
|
172
|
+
return firstBoolean([
|
|
173
|
+
trackData?.track?.metadata?.effectiveSettings?.sync?.[field],
|
|
174
|
+
trackData?.release?.metadata?.effectiveSettings?.sync?.[field],
|
|
175
|
+
trackData?.metadata?.effectiveSettings?.sync?.[field],
|
|
176
|
+
trackData?.effectiveSettings?.sync?.[field],
|
|
177
|
+
trackData?.trackUserSettings?.sync?.[field],
|
|
178
|
+
]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Resolve whether this track should warp (time-stretch onto the grid) for the viewer.
|
|
183
|
+
*
|
|
184
|
+
* Returns null when nothing anywhere expressed a preference. Null means "no opinion", NOT "off" —
|
|
185
|
+
* the caller supplies its own default, because only the caller knows its runtime constraints (in
|
|
186
|
+
* orbiters, a deck with no tempo cannot warp at all, whatever was saved).
|
|
187
|
+
*/
|
|
188
|
+
export function resolveWarpEnabledFromTrackData(
|
|
189
|
+
trackData: TrackDataLike | null = null,
|
|
190
|
+
): boolean | null {
|
|
191
|
+
return resolveSyncFlagFromTrackData(trackData, 'warpEnabled');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Resolve whether this track should play looped for the viewer.
|
|
196
|
+
*
|
|
197
|
+
* Same three states as warp — on, off, nobody-said — but no smart default anywhere behind it: the
|
|
198
|
+
* server stops at the owner's setting, so null here is genuinely common and simply means the caller
|
|
199
|
+
* keeps whatever looping default it already had.
|
|
200
|
+
*/
|
|
201
|
+
export function resolveLoopEnabledFromTrackData(
|
|
202
|
+
trackData: TrackDataLike | null = null,
|
|
203
|
+
): boolean | null {
|
|
204
|
+
return resolveSyncFlagFromTrackData(trackData, 'loopEnabled');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Resolve the saved loop region, in seconds, or null when no layer holds a complete one.
|
|
209
|
+
*
|
|
210
|
+
* Resolved as a PAIR, never edge by edge: a start borrowed from one layer and an end from another
|
|
211
|
+
* describes a loop nobody set, and can invert. `start` of 0 is a real value, so the guard is a
|
|
212
|
+
* finiteness test rather than a truthiness one.
|
|
213
|
+
*
|
|
214
|
+
* The caller must still clamp this against the audio's real duration before using it — a region
|
|
215
|
+
* saved against an earlier version of the track can outlive the audio it was drawn on.
|
|
216
|
+
*/
|
|
217
|
+
export function resolveLoopRangeSecFromTrackData(
|
|
218
|
+
trackData: TrackDataLike | null = null,
|
|
219
|
+
): { startSec: number; endSec: number } | null {
|
|
220
|
+
const candidates: Array<SyncSettingsLike | null | undefined> = [
|
|
221
|
+
trackData?.track?.metadata?.effectiveSettings?.sync,
|
|
222
|
+
trackData?.release?.metadata?.effectiveSettings?.sync,
|
|
223
|
+
trackData?.metadata?.effectiveSettings?.sync,
|
|
224
|
+
trackData?.effectiveSettings?.sync,
|
|
225
|
+
trackData?.trackUserSettings?.sync,
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
for (const candidate of candidates) {
|
|
229
|
+
const startSec = toNonNegativeNumber(candidate?.loopStartSec);
|
|
230
|
+
const endSec = toNonNegativeNumber(candidate?.loopEndSec);
|
|
231
|
+
if (startSec !== null && endSec !== null && endSec > startSec) {
|
|
232
|
+
return { startSec, endSec };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|