entangled-worlds-orbiters-shared 1.3.0 → 1.4.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.
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The slice of a track's data these resolvers read. Every field is optional and untyped at the leaf
|
|
3
|
+
* because the runtime objects come from different app layers (release metadata, effective owner
|
|
4
|
+
* settings, per-user overrides, audio analysis). Resolvers coerce leaves with Number(...) and ignore
|
|
5
|
+
* anything non-finite, so a permissive shape here is intentional — not every consumer fills every path.
|
|
6
|
+
*/
|
|
7
|
+
export interface SyncSettingsLike {
|
|
8
|
+
trackBpm?: unknown;
|
|
9
|
+
primaryGridMarkerTimeSec?: unknown;
|
|
10
|
+
gridMarkers?: ReadonlyArray<{
|
|
11
|
+
sourceTimeSec?: unknown;
|
|
12
|
+
} | null | undefined> | null;
|
|
13
|
+
gridStartTimeSec?: unknown;
|
|
14
|
+
}
|
|
15
|
+
export interface TrackMetadataLike {
|
|
16
|
+
trackBpm?: unknown;
|
|
17
|
+
primaryGridMarkerTimeSec?: unknown;
|
|
18
|
+
effectiveSettings?: {
|
|
19
|
+
sync?: SyncSettingsLike | null;
|
|
20
|
+
} | null;
|
|
21
|
+
audioAnalysis?: {
|
|
22
|
+
analysis?: {
|
|
23
|
+
tempo_bpm?: unknown;
|
|
24
|
+
} | null;
|
|
25
|
+
} | null;
|
|
26
|
+
wrap?: {
|
|
27
|
+
gridMarkers?: ReadonlyArray<{
|
|
28
|
+
sourceTimeSec?: unknown;
|
|
29
|
+
} | null | undefined> | null;
|
|
30
|
+
gridStartTimeSec?: unknown;
|
|
31
|
+
} | null;
|
|
32
|
+
}
|
|
33
|
+
export interface TrackDataLike {
|
|
34
|
+
track?: {
|
|
35
|
+
metadata?: TrackMetadataLike | null;
|
|
36
|
+
} | null;
|
|
37
|
+
release?: {
|
|
38
|
+
metadata?: TrackMetadataLike | null;
|
|
39
|
+
} | null;
|
|
40
|
+
metadata?: TrackMetadataLike | null;
|
|
41
|
+
trackUserSettings?: {
|
|
42
|
+
sync?: SyncSettingsLike | null;
|
|
43
|
+
} | null;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve the effective track BPM. Precedence (first finite, > 0 wins):
|
|
47
|
+
* owner `trackBpm` → owner `effectiveSettings.sync.trackBpm` → per-user `trackUserSettings.sync.trackBpm`
|
|
48
|
+
* → auto-detected `audioAnalysis.analysis.tempo_bpm`. Returns null when nothing resolves.
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveTrackBpmFromTrackData(trackData?: TrackDataLike | null): number | null;
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the primary grid-marker (downbeat) time in seconds — the grid offset that becomes
|
|
53
|
+
* `tempoMap[0].startSec`. Precedence (first finite, >= 0 wins) walks the explicit
|
|
54
|
+
* `primaryGridMarkerTimeSec`, then per-user grid markers / start time, then the wrap fallback.
|
|
55
|
+
* Returns null when nothing resolves (consumers default to 0).
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolvePrimaryGridMarkerTimeSecFromTrackData(trackData?: TrackDataLike | null): number | null;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// ./clock/track-metadata — pure resolution of a track's tempo (BPM) and its primary grid-marker
|
|
2
|
+
// (downbeat) time from the loosely-typed metadata both apps already carry. Framework-free, no three,
|
|
3
|
+
// no react: it walks a fixed precedence (owner setting → per-user setting → auto-detected analysis)
|
|
4
|
+
// and returns the first finite value, so a consumer can feed `new MusicalProjection({ tempoMap: [{
|
|
5
|
+
// startSec: gridSec, bpm }] })`. Promoted out of the orbiters app (ORB-137) so EW reuses it verbatim.
|
|
6
|
+
function toPositiveNumber(value) {
|
|
7
|
+
const numeric = Number(value);
|
|
8
|
+
return Number.isFinite(numeric) && numeric > 0 ? numeric : null;
|
|
9
|
+
}
|
|
10
|
+
function toNonNegativeNumber(value) {
|
|
11
|
+
const numeric = Number(value);
|
|
12
|
+
return Number.isFinite(numeric) && numeric >= 0 ? numeric : null;
|
|
13
|
+
}
|
|
14
|
+
function firstResolvedValue(candidates, parser) {
|
|
15
|
+
for (const candidate of candidates) {
|
|
16
|
+
const resolved = parser(candidate);
|
|
17
|
+
if (resolved !== null) {
|
|
18
|
+
return resolved;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the effective track BPM. Precedence (first finite, > 0 wins):
|
|
25
|
+
* owner `trackBpm` → owner `effectiveSettings.sync.trackBpm` → per-user `trackUserSettings.sync.trackBpm`
|
|
26
|
+
* → auto-detected `audioAnalysis.analysis.tempo_bpm`. Returns null when nothing resolves.
|
|
27
|
+
*/
|
|
28
|
+
export function resolveTrackBpmFromTrackData(trackData = null) {
|
|
29
|
+
return firstResolvedValue([
|
|
30
|
+
trackData?.track?.metadata?.trackBpm,
|
|
31
|
+
trackData?.release?.metadata?.trackBpm,
|
|
32
|
+
trackData?.metadata?.trackBpm,
|
|
33
|
+
trackData?.track?.metadata?.effectiveSettings?.sync?.trackBpm,
|
|
34
|
+
trackData?.release?.metadata?.effectiveSettings?.sync?.trackBpm,
|
|
35
|
+
trackData?.metadata?.effectiveSettings?.sync?.trackBpm,
|
|
36
|
+
trackData?.trackUserSettings?.sync?.trackBpm,
|
|
37
|
+
trackData?.track?.metadata?.audioAnalysis?.analysis?.tempo_bpm,
|
|
38
|
+
trackData?.release?.metadata?.audioAnalysis?.analysis?.tempo_bpm,
|
|
39
|
+
trackData?.metadata?.audioAnalysis?.analysis?.tempo_bpm,
|
|
40
|
+
], toPositiveNumber);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the primary grid-marker (downbeat) time in seconds — the grid offset that becomes
|
|
44
|
+
* `tempoMap[0].startSec`. Precedence (first finite, >= 0 wins) walks the explicit
|
|
45
|
+
* `primaryGridMarkerTimeSec`, then per-user grid markers / start time, then the wrap fallback.
|
|
46
|
+
* Returns null when nothing resolves (consumers default to 0).
|
|
47
|
+
*/
|
|
48
|
+
export function resolvePrimaryGridMarkerTimeSecFromTrackData(trackData = null) {
|
|
49
|
+
return firstResolvedValue([
|
|
50
|
+
trackData?.track?.metadata?.primaryGridMarkerTimeSec,
|
|
51
|
+
trackData?.release?.metadata?.primaryGridMarkerTimeSec,
|
|
52
|
+
trackData?.metadata?.primaryGridMarkerTimeSec,
|
|
53
|
+
trackData?.track?.metadata?.effectiveSettings?.sync?.primaryGridMarkerTimeSec,
|
|
54
|
+
trackData?.release?.metadata?.effectiveSettings?.sync?.primaryGridMarkerTimeSec,
|
|
55
|
+
trackData?.metadata?.effectiveSettings?.sync?.primaryGridMarkerTimeSec,
|
|
56
|
+
trackData?.trackUserSettings?.sync?.primaryGridMarkerTimeSec,
|
|
57
|
+
trackData?.trackUserSettings?.sync?.gridMarkers?.[0]?.sourceTimeSec,
|
|
58
|
+
trackData?.trackUserSettings?.sync?.gridStartTimeSec,
|
|
59
|
+
trackData?.track?.metadata?.wrap?.gridMarkers?.[0]?.sourceTimeSec,
|
|
60
|
+
trackData?.release?.metadata?.wrap?.gridMarkers?.[0]?.sourceTimeSec,
|
|
61
|
+
trackData?.metadata?.wrap?.gridMarkers?.[0]?.sourceTimeSec,
|
|
62
|
+
trackData?.track?.metadata?.wrap?.gridStartTimeSec,
|
|
63
|
+
trackData?.release?.metadata?.wrap?.gridStartTimeSec,
|
|
64
|
+
trackData?.metadata?.wrap?.gridStartTimeSec,
|
|
65
|
+
], toNonNegativeNumber);
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "entangled-worlds-orbiters-shared",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Shared library for entangled worlds orbiters - contains core world rendering, moons system, and UI components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"types": "./dist/clock/musical.d.ts",
|
|
22
22
|
"import": "./dist/clock/musical.js"
|
|
23
23
|
},
|
|
24
|
+
"./clock/track-metadata": {
|
|
25
|
+
"types": "./dist/clock/track-metadata.d.ts",
|
|
26
|
+
"import": "./dist/clock/track-metadata.js"
|
|
27
|
+
},
|
|
24
28
|
"./clock/sync": {
|
|
25
29
|
"types": "./dist/clock/sync.d.ts",
|
|
26
30
|
"import": "./dist/clock/sync.js"
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// ./clock/track-metadata — pure resolution of a track's tempo (BPM) and its primary grid-marker
|
|
2
|
+
// (downbeat) time from the loosely-typed metadata both apps already carry. Framework-free, no three,
|
|
3
|
+
// no react: it walks a fixed precedence (owner setting → per-user setting → auto-detected analysis)
|
|
4
|
+
// and returns the first finite value, so a consumer can feed `new MusicalProjection({ tempoMap: [{
|
|
5
|
+
// startSec: gridSec, bpm }] })`. Promoted out of the orbiters app (ORB-137) so EW reuses it verbatim.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The slice of a track's data these resolvers read. Every field is optional and untyped at the leaf
|
|
9
|
+
* because the runtime objects come from different app layers (release metadata, effective owner
|
|
10
|
+
* settings, per-user overrides, audio analysis). Resolvers coerce leaves with Number(...) and ignore
|
|
11
|
+
* anything non-finite, so a permissive shape here is intentional — not every consumer fills every path.
|
|
12
|
+
*/
|
|
13
|
+
export interface SyncSettingsLike {
|
|
14
|
+
trackBpm?: unknown;
|
|
15
|
+
primaryGridMarkerTimeSec?: unknown;
|
|
16
|
+
gridMarkers?: ReadonlyArray<{ sourceTimeSec?: unknown } | null | undefined> | null;
|
|
17
|
+
gridStartTimeSec?: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface TrackMetadataLike {
|
|
21
|
+
trackBpm?: unknown;
|
|
22
|
+
primaryGridMarkerTimeSec?: unknown;
|
|
23
|
+
effectiveSettings?: { sync?: SyncSettingsLike | null } | null;
|
|
24
|
+
audioAnalysis?: { analysis?: { tempo_bpm?: unknown } | null } | null;
|
|
25
|
+
wrap?: {
|
|
26
|
+
gridMarkers?: ReadonlyArray<{ sourceTimeSec?: unknown } | null | undefined> | null;
|
|
27
|
+
gridStartTimeSec?: unknown;
|
|
28
|
+
} | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface TrackDataLike {
|
|
32
|
+
track?: { metadata?: TrackMetadataLike | null } | null;
|
|
33
|
+
release?: { metadata?: TrackMetadataLike | null } | null;
|
|
34
|
+
metadata?: TrackMetadataLike | null;
|
|
35
|
+
trackUserSettings?: { sync?: SyncSettingsLike | null } | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function toPositiveNumber(value: unknown): number | null {
|
|
39
|
+
const numeric = Number(value);
|
|
40
|
+
return Number.isFinite(numeric) && numeric > 0 ? numeric : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function toNonNegativeNumber(value: unknown): number | null {
|
|
44
|
+
const numeric = Number(value);
|
|
45
|
+
return Number.isFinite(numeric) && numeric >= 0 ? numeric : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function firstResolvedValue(
|
|
49
|
+
candidates: ReadonlyArray<unknown>,
|
|
50
|
+
parser: (value: unknown) => number | null,
|
|
51
|
+
): number | null {
|
|
52
|
+
for (const candidate of candidates) {
|
|
53
|
+
const resolved = parser(candidate);
|
|
54
|
+
if (resolved !== null) {
|
|
55
|
+
return resolved;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Resolve the effective track BPM. Precedence (first finite, > 0 wins):
|
|
63
|
+
* owner `trackBpm` → owner `effectiveSettings.sync.trackBpm` → per-user `trackUserSettings.sync.trackBpm`
|
|
64
|
+
* → auto-detected `audioAnalysis.analysis.tempo_bpm`. Returns null when nothing resolves.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveTrackBpmFromTrackData(trackData: TrackDataLike | null = null): number | null {
|
|
67
|
+
return firstResolvedValue(
|
|
68
|
+
[
|
|
69
|
+
trackData?.track?.metadata?.trackBpm,
|
|
70
|
+
trackData?.release?.metadata?.trackBpm,
|
|
71
|
+
trackData?.metadata?.trackBpm,
|
|
72
|
+
trackData?.track?.metadata?.effectiveSettings?.sync?.trackBpm,
|
|
73
|
+
trackData?.release?.metadata?.effectiveSettings?.sync?.trackBpm,
|
|
74
|
+
trackData?.metadata?.effectiveSettings?.sync?.trackBpm,
|
|
75
|
+
trackData?.trackUserSettings?.sync?.trackBpm,
|
|
76
|
+
trackData?.track?.metadata?.audioAnalysis?.analysis?.tempo_bpm,
|
|
77
|
+
trackData?.release?.metadata?.audioAnalysis?.analysis?.tempo_bpm,
|
|
78
|
+
trackData?.metadata?.audioAnalysis?.analysis?.tempo_bpm,
|
|
79
|
+
],
|
|
80
|
+
toPositiveNumber,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Resolve the primary grid-marker (downbeat) time in seconds — the grid offset that becomes
|
|
86
|
+
* `tempoMap[0].startSec`. Precedence (first finite, >= 0 wins) walks the explicit
|
|
87
|
+
* `primaryGridMarkerTimeSec`, then per-user grid markers / start time, then the wrap fallback.
|
|
88
|
+
* Returns null when nothing resolves (consumers default to 0).
|
|
89
|
+
*/
|
|
90
|
+
export function resolvePrimaryGridMarkerTimeSecFromTrackData(
|
|
91
|
+
trackData: TrackDataLike | null = null,
|
|
92
|
+
): number | null {
|
|
93
|
+
return firstResolvedValue(
|
|
94
|
+
[
|
|
95
|
+
trackData?.track?.metadata?.primaryGridMarkerTimeSec,
|
|
96
|
+
trackData?.release?.metadata?.primaryGridMarkerTimeSec,
|
|
97
|
+
trackData?.metadata?.primaryGridMarkerTimeSec,
|
|
98
|
+
trackData?.track?.metadata?.effectiveSettings?.sync?.primaryGridMarkerTimeSec,
|
|
99
|
+
trackData?.release?.metadata?.effectiveSettings?.sync?.primaryGridMarkerTimeSec,
|
|
100
|
+
trackData?.metadata?.effectiveSettings?.sync?.primaryGridMarkerTimeSec,
|
|
101
|
+
trackData?.trackUserSettings?.sync?.primaryGridMarkerTimeSec,
|
|
102
|
+
trackData?.trackUserSettings?.sync?.gridMarkers?.[0]?.sourceTimeSec,
|
|
103
|
+
trackData?.trackUserSettings?.sync?.gridStartTimeSec,
|
|
104
|
+
trackData?.track?.metadata?.wrap?.gridMarkers?.[0]?.sourceTimeSec,
|
|
105
|
+
trackData?.release?.metadata?.wrap?.gridMarkers?.[0]?.sourceTimeSec,
|
|
106
|
+
trackData?.metadata?.wrap?.gridMarkers?.[0]?.sourceTimeSec,
|
|
107
|
+
trackData?.track?.metadata?.wrap?.gridStartTimeSec,
|
|
108
|
+
trackData?.release?.metadata?.wrap?.gridStartTimeSec,
|
|
109
|
+
trackData?.metadata?.wrap?.gridStartTimeSec,
|
|
110
|
+
],
|
|
111
|
+
toNonNegativeNumber,
|
|
112
|
+
);
|
|
113
|
+
}
|