entangled-worlds-orbiters-shared 1.4.0 → 1.5.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,50 @@
1
+ import type { TrackDataLike } from './track-metadata.js';
2
+ export interface MeterPreset {
3
+ id: string;
4
+ numerator: number;
5
+ denominator: number;
6
+ }
7
+ /** Quick-pick reference meters. `id` is the persisted/display value, not a closed enum. */
8
+ export declare const METER_PRESETS: MeterPreset[];
9
+ export declare const DEFAULT_METER_ID = "4/4";
10
+ export declare const MIN_METER_NUMERATOR = 1;
11
+ export declare const MAX_METER_NUMERATOR = 64;
12
+ export declare const VALID_METER_DENOMINATORS: readonly number[];
13
+ interface MeterFraction {
14
+ numerator: number;
15
+ denominator: number;
16
+ }
17
+ export interface ParsedMeter {
18
+ id: string;
19
+ numerator: number;
20
+ denominator: number;
21
+ beatsPerBar: number;
22
+ beatUnit: number;
23
+ sharedBeatsPerBar: number;
24
+ quarterBeatsPerBar: number;
25
+ clickIntervalQuarterBeats: number;
26
+ clicksPerBar: number;
27
+ }
28
+ export declare function formatMeterId(value?: Partial<MeterFraction>): string;
29
+ /** True when `value` is a valid arbitrary meter string or `{ numerator, denominator }`. */
30
+ export declare function isValidMeterId(value: unknown): boolean;
31
+ /** Normalize an arbitrary value to a persisted meter id (`n/d`), falling back to the default. */
32
+ export declare function normalizeMeterId(value: unknown): string;
33
+ /**
34
+ * Resolve a meter id to its numbers, including `sharedBeatsPerBar` — the count of shared (quarter-note)
35
+ * beats in one bar, which is what the engine uses for the downbeat accent and the launch bar.
36
+ */
37
+ export declare function parseMeter(value: unknown): ParsedMeter;
38
+ /** Shared (quarter-note) beats per bar for a meter id — the engine's bar length. */
39
+ export declare function sharedBeatsPerBar(id: unknown): number;
40
+ /**
41
+ * Resolve the per-track meter from assembled track data — the LOAD side, walking the SAME precedence
42
+ * as the shared tempo resolver (`resolveTrackBpmFromTrackData`): owner CANONICAL first, then the
43
+ * per-user override, then auto-detected analysis, then the default. First valid wins:
44
+ * 1) owner canonical — `metadata.meter` → `metadata.effectiveSettings.sync.meter`
45
+ * 2) per-user — `trackUserSettings.sync.meter`
46
+ * 3) auto-detected — `metadata.audioAnalysis.analysis` time signature
47
+ * 4) default (4/4)
48
+ */
49
+ export declare function resolveTrackMeterFromTrackData(trackData?: TrackDataLike | null): string;
50
+ export {};
@@ -0,0 +1,145 @@
1
+ // ./clock/meter — time signature / meter model. A per-track musical property (how many beats make a
2
+ // bar), resolved with the SAME owner → per-user → auto-detected → default precedence as the tempo
3
+ // resolver in `./clock/track-metadata`. Promoted out of the orbiters app so EW reuses it verbatim.
4
+ // Framework-free: no three, no react.
5
+ //
6
+ // Units: `sharedBeatsPerBar` is quarter-note beats per bar = numerator * 4 / denominator — the same
7
+ // beat unit `MusicalProjection` (./clock/musical) counts, so a resolved meter feeds its `signatureMap`
8
+ // directly: `{ startBar: 0, numerator, denominator }`.
9
+ /** Quick-pick reference meters. `id` is the persisted/display value, not a closed enum. */
10
+ export const METER_PRESETS = [
11
+ { id: '4/4', numerator: 4, denominator: 4 },
12
+ { id: '3/4', numerator: 3, denominator: 4 },
13
+ { id: '6/8', numerator: 6, denominator: 8 },
14
+ ];
15
+ export const DEFAULT_METER_ID = '4/4';
16
+ export const MIN_METER_NUMERATOR = 1;
17
+ export const MAX_METER_NUMERATOR = 64;
18
+ export const VALID_METER_DENOMINATORS = [1, 2, 4, 8, 16, 32];
19
+ const BY_ID = new Map(METER_PRESETS.map((m) => [m.id, m]));
20
+ const DENOMINATORS = new Set(VALID_METER_DENOMINATORS);
21
+ function isValidNumerator(n) {
22
+ return Number.isInteger(n) && n >= MIN_METER_NUMERATOR;
23
+ }
24
+ function isValidDenominator(n) {
25
+ return Number.isInteger(n) && DENOMINATORS.has(n);
26
+ }
27
+ function parseMeterString(value) {
28
+ if (typeof value !== 'string')
29
+ return null;
30
+ const match = value.trim().match(/^(\d+)\s*\/\s*(\d+)$/);
31
+ if (!match)
32
+ return null;
33
+ const numerator = Number(match[1]);
34
+ const denominator = Number(match[2]);
35
+ if (!isValidNumerator(numerator) || !isValidDenominator(denominator))
36
+ return null;
37
+ return { numerator, denominator };
38
+ }
39
+ function parseMeterObject(value) {
40
+ if (!value || typeof value !== 'object')
41
+ return null;
42
+ const numerator = Number(value.numerator);
43
+ const denominator = Number(value.denominator);
44
+ if (!isValidNumerator(numerator) || !isValidDenominator(denominator))
45
+ return null;
46
+ return { numerator, denominator };
47
+ }
48
+ export function formatMeterId(value = {}) {
49
+ const n = Number(value.numerator);
50
+ const d = Number(value.denominator);
51
+ return isValidNumerator(n) && isValidDenominator(d) ? `${n}/${d}` : DEFAULT_METER_ID;
52
+ }
53
+ /** True when `value` is a valid arbitrary meter string or `{ numerator, denominator }`. */
54
+ export function isValidMeterId(value) {
55
+ return parseMeterString(value) !== null || parseMeterObject(value) !== null;
56
+ }
57
+ /** Normalize an arbitrary value to a persisted meter id (`n/d`), falling back to the default. */
58
+ export function normalizeMeterId(value) {
59
+ const parsed = parseMeterString(value) ?? parseMeterObject(value);
60
+ return parsed ? formatMeterId(parsed) : DEFAULT_METER_ID;
61
+ }
62
+ /**
63
+ * Resolve a meter id to its numbers, including `sharedBeatsPerBar` — the count of shared (quarter-note)
64
+ * beats in one bar, which is what the engine uses for the downbeat accent and the launch bar.
65
+ */
66
+ export function parseMeter(value) {
67
+ const id = normalizeMeterId(value);
68
+ const preset = BY_ID.get(id);
69
+ const parsed = preset ?? parseMeterString(id) ?? parseMeterString(DEFAULT_METER_ID);
70
+ const numerator = parsed.numerator;
71
+ const denominator = parsed.denominator;
72
+ const sharedBeatsPerBar = (numerator * 4) / denominator;
73
+ const clickIntervalQuarterBeats = 4 / denominator;
74
+ return {
75
+ id,
76
+ numerator,
77
+ denominator,
78
+ beatsPerBar: numerator,
79
+ beatUnit: denominator,
80
+ sharedBeatsPerBar,
81
+ quarterBeatsPerBar: sharedBeatsPerBar,
82
+ clickIntervalQuarterBeats,
83
+ clicksPerBar: numerator,
84
+ };
85
+ }
86
+ function resolveAnalysisMeter(analysis) {
87
+ if (!analysis || typeof analysis !== 'object')
88
+ return null;
89
+ const a = analysis;
90
+ const candidates = [
91
+ a.time_signature,
92
+ a.timeSignature,
93
+ a.meter,
94
+ a.time_signature?.meter,
95
+ a.timeSignature?.meter,
96
+ a.time_signature?.value,
97
+ a.timeSignature?.value,
98
+ ];
99
+ for (const candidate of candidates) {
100
+ if (isValidMeterId(candidate)) {
101
+ return normalizeMeterId(candidate);
102
+ }
103
+ }
104
+ return null;
105
+ }
106
+ /** Shared (quarter-note) beats per bar for a meter id — the engine's bar length. */
107
+ export function sharedBeatsPerBar(id) {
108
+ return parseMeter(id).sharedBeatsPerBar;
109
+ }
110
+ /**
111
+ * Resolve the per-track meter from assembled track data — the LOAD side, walking the SAME precedence
112
+ * as the shared tempo resolver (`resolveTrackBpmFromTrackData`): owner CANONICAL first, then the
113
+ * per-user override, then auto-detected analysis, then the default. First valid wins:
114
+ * 1) owner canonical — `metadata.meter` → `metadata.effectiveSettings.sync.meter`
115
+ * 2) per-user — `trackUserSettings.sync.meter`
116
+ * 3) auto-detected — `metadata.audioAnalysis.analysis` time signature
117
+ * 4) default (4/4)
118
+ */
119
+ export function resolveTrackMeterFromTrackData(trackData = null) {
120
+ const td = trackData;
121
+ const candidates = [
122
+ // 1) owner canonical — top-level metadata (mirrors tempo's `metadata.trackBpm`)
123
+ td?.track?.metadata?.meter,
124
+ td?.release?.metadata?.meter,
125
+ td?.metadata?.meter,
126
+ // 1) owner canonical — effectiveSettings (mirrors tempo's `effectiveSettings.sync.trackBpm`)
127
+ td?.track?.metadata?.effectiveSettings?.sync?.meter,
128
+ td?.release?.metadata?.effectiveSettings?.sync?.meter,
129
+ td?.metadata?.effectiveSettings?.sync?.meter,
130
+ td?.track?.effectiveSettings?.sync?.meter,
131
+ td?.release?.effectiveSettings?.sync?.meter,
132
+ td?.effectiveSettings?.sync?.meter,
133
+ // 2) per-user override (mirrors tempo's `trackUserSettings.sync.trackBpm`)
134
+ td?.trackUserSettings?.sync?.meter,
135
+ // 3) auto-detected analysis (mirrors tempo's `audioAnalysis.analysis.tempo_bpm`)
136
+ resolveAnalysisMeter(td?.track?.metadata?.audioAnalysis?.analysis),
137
+ resolveAnalysisMeter(td?.release?.metadata?.audioAnalysis?.analysis),
138
+ resolveAnalysisMeter(td?.metadata?.audioAnalysis?.analysis),
139
+ ];
140
+ for (const c of candidates) {
141
+ if (isValidMeterId(c))
142
+ return normalizeMeterId(c);
143
+ }
144
+ return DEFAULT_METER_ID;
145
+ }
@@ -1,11 +1,14 @@
1
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.
2
+ * The slice of a track's data these resolvers read (tempo here; meter in `./clock/meter`). Every field
3
+ * is optional and untyped at the leaf because the runtime objects come from different app layers
4
+ * (release metadata, effective owner settings, per-user overrides, audio analysis). Resolvers coerce
5
+ * leaves with Number(...)/String(...) and ignore anything invalid, so a permissive shape here is
6
+ * intentional — not every consumer fills every path.
6
7
  */
7
8
  export interface SyncSettingsLike {
8
9
  trackBpm?: unknown;
10
+ /** Per-track time signature (e.g. `'4/4'` or `{ numerator, denominator }`) — see `./clock/meter`. */
11
+ meter?: unknown;
9
12
  primaryGridMarkerTimeSec?: unknown;
10
13
  gridMarkers?: ReadonlyArray<{
11
14
  sourceTimeSec?: unknown;
@@ -14,6 +17,8 @@ export interface SyncSettingsLike {
14
17
  }
15
18
  export interface TrackMetadataLike {
16
19
  trackBpm?: unknown;
20
+ /** Per-track time signature (owner canonical) — see `./clock/meter`. */
21
+ meter?: unknown;
17
22
  primaryGridMarkerTimeSec?: unknown;
18
23
  effectiveSettings?: {
19
24
  sync?: SyncSettingsLike | null;
@@ -21,6 +26,8 @@ export interface TrackMetadataLike {
21
26
  audioAnalysis?: {
22
27
  analysis?: {
23
28
  tempo_bpm?: unknown;
29
+ time_signature?: unknown;
30
+ timeSignature?: unknown;
24
31
  } | null;
25
32
  } | null;
26
33
  wrap?: {
@@ -33,11 +40,20 @@ export interface TrackMetadataLike {
33
40
  export interface TrackDataLike {
34
41
  track?: {
35
42
  metadata?: TrackMetadataLike | null;
43
+ effectiveSettings?: {
44
+ sync?: SyncSettingsLike | null;
45
+ } | null;
36
46
  } | null;
37
47
  release?: {
38
48
  metadata?: TrackMetadataLike | null;
49
+ effectiveSettings?: {
50
+ sync?: SyncSettingsLike | null;
51
+ } | null;
39
52
  } | null;
40
53
  metadata?: TrackMetadataLike | null;
54
+ effectiveSettings?: {
55
+ sync?: SyncSettingsLike | null;
56
+ } | null;
41
57
  trackUserSettings?: {
42
58
  sync?: SyncSettingsLike | null;
43
59
  } | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "entangled-worlds-orbiters-shared",
3
- "version": "1.4.0",
3
+ "version": "1.5.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",
@@ -25,6 +25,10 @@
25
25
  "types": "./dist/clock/track-metadata.d.ts",
26
26
  "import": "./dist/clock/track-metadata.js"
27
27
  },
28
+ "./clock/meter": {
29
+ "types": "./dist/clock/meter.d.ts",
30
+ "import": "./dist/clock/meter.js"
31
+ },
28
32
  "./clock/sync": {
29
33
  "types": "./dist/clock/sync.d.ts",
30
34
  "import": "./dist/clock/sync.js"
@@ -0,0 +1,177 @@
1
+ // ./clock/meter — time signature / meter model. A per-track musical property (how many beats make a
2
+ // bar), resolved with the SAME owner → per-user → auto-detected → default precedence as the tempo
3
+ // resolver in `./clock/track-metadata`. Promoted out of the orbiters app so EW reuses it verbatim.
4
+ // Framework-free: no three, no react.
5
+ //
6
+ // Units: `sharedBeatsPerBar` is quarter-note beats per bar = numerator * 4 / denominator — the same
7
+ // beat unit `MusicalProjection` (./clock/musical) counts, so a resolved meter feeds its `signatureMap`
8
+ // directly: `{ startBar: 0, numerator, denominator }`.
9
+
10
+ import type { TrackDataLike } from './track-metadata.js';
11
+
12
+ export interface MeterPreset {
13
+ id: string;
14
+ numerator: number;
15
+ denominator: number;
16
+ }
17
+
18
+ /** Quick-pick reference meters. `id` is the persisted/display value, not a closed enum. */
19
+ export const METER_PRESETS: MeterPreset[] = [
20
+ { id: '4/4', numerator: 4, denominator: 4 },
21
+ { id: '3/4', numerator: 3, denominator: 4 },
22
+ { id: '6/8', numerator: 6, denominator: 8 },
23
+ ];
24
+
25
+ export const DEFAULT_METER_ID = '4/4';
26
+ export const MIN_METER_NUMERATOR = 1;
27
+ export const MAX_METER_NUMERATOR = 64;
28
+ export const VALID_METER_DENOMINATORS: readonly number[] = [1, 2, 4, 8, 16, 32];
29
+
30
+ const BY_ID = new Map(METER_PRESETS.map((m) => [m.id, m]));
31
+ const DENOMINATORS = new Set(VALID_METER_DENOMINATORS);
32
+
33
+ interface MeterFraction {
34
+ numerator: number;
35
+ denominator: number;
36
+ }
37
+
38
+ export interface ParsedMeter {
39
+ id: string;
40
+ numerator: number;
41
+ denominator: number;
42
+ beatsPerBar: number;
43
+ beatUnit: number;
44
+ sharedBeatsPerBar: number;
45
+ quarterBeatsPerBar: number;
46
+ clickIntervalQuarterBeats: number;
47
+ clicksPerBar: number;
48
+ }
49
+
50
+ function isValidNumerator(n: number): boolean {
51
+ return Number.isInteger(n) && n >= MIN_METER_NUMERATOR;
52
+ }
53
+
54
+ function isValidDenominator(n: number): boolean {
55
+ return Number.isInteger(n) && DENOMINATORS.has(n);
56
+ }
57
+
58
+ function parseMeterString(value: unknown): MeterFraction | null {
59
+ if (typeof value !== 'string') return null;
60
+ const match = value.trim().match(/^(\d+)\s*\/\s*(\d+)$/);
61
+ if (!match) return null;
62
+ const numerator = Number(match[1]);
63
+ const denominator = Number(match[2]);
64
+ if (!isValidNumerator(numerator) || !isValidDenominator(denominator)) return null;
65
+ return { numerator, denominator };
66
+ }
67
+
68
+ function parseMeterObject(value: unknown): MeterFraction | null {
69
+ if (!value || typeof value !== 'object') return null;
70
+ const numerator = Number((value as Record<string, unknown>).numerator);
71
+ const denominator = Number((value as Record<string, unknown>).denominator);
72
+ if (!isValidNumerator(numerator) || !isValidDenominator(denominator)) return null;
73
+ return { numerator, denominator };
74
+ }
75
+
76
+ export function formatMeterId(value: Partial<MeterFraction> = {}): string {
77
+ const n = Number(value.numerator);
78
+ const d = Number(value.denominator);
79
+ return isValidNumerator(n) && isValidDenominator(d) ? `${n}/${d}` : DEFAULT_METER_ID;
80
+ }
81
+
82
+ /** True when `value` is a valid arbitrary meter string or `{ numerator, denominator }`. */
83
+ export function isValidMeterId(value: unknown): boolean {
84
+ return parseMeterString(value) !== null || parseMeterObject(value) !== null;
85
+ }
86
+
87
+ /** Normalize an arbitrary value to a persisted meter id (`n/d`), falling back to the default. */
88
+ export function normalizeMeterId(value: unknown): string {
89
+ const parsed = parseMeterString(value) ?? parseMeterObject(value);
90
+ return parsed ? formatMeterId(parsed) : DEFAULT_METER_ID;
91
+ }
92
+
93
+ /**
94
+ * Resolve a meter id to its numbers, including `sharedBeatsPerBar` — the count of shared (quarter-note)
95
+ * beats in one bar, which is what the engine uses for the downbeat accent and the launch bar.
96
+ */
97
+ export function parseMeter(value: unknown): ParsedMeter {
98
+ const id = normalizeMeterId(value);
99
+ const preset = BY_ID.get(id);
100
+ const parsed = preset ?? parseMeterString(id) ?? (parseMeterString(DEFAULT_METER_ID) as MeterFraction);
101
+ const numerator = parsed.numerator;
102
+ const denominator = parsed.denominator;
103
+ const sharedBeatsPerBar = (numerator * 4) / denominator;
104
+ const clickIntervalQuarterBeats = 4 / denominator;
105
+ return {
106
+ id,
107
+ numerator,
108
+ denominator,
109
+ beatsPerBar: numerator,
110
+ beatUnit: denominator,
111
+ sharedBeatsPerBar,
112
+ quarterBeatsPerBar: sharedBeatsPerBar,
113
+ clickIntervalQuarterBeats,
114
+ clicksPerBar: numerator,
115
+ };
116
+ }
117
+
118
+ function resolveAnalysisMeter(analysis: unknown): string | null {
119
+ if (!analysis || typeof analysis !== 'object') return null;
120
+ const a = analysis as Record<string, any>;
121
+ const candidates = [
122
+ a.time_signature,
123
+ a.timeSignature,
124
+ a.meter,
125
+ a.time_signature?.meter,
126
+ a.timeSignature?.meter,
127
+ a.time_signature?.value,
128
+ a.timeSignature?.value,
129
+ ];
130
+ for (const candidate of candidates) {
131
+ if (isValidMeterId(candidate)) {
132
+ return normalizeMeterId(candidate);
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+
138
+ /** Shared (quarter-note) beats per bar for a meter id — the engine's bar length. */
139
+ export function sharedBeatsPerBar(id: unknown): number {
140
+ return parseMeter(id).sharedBeatsPerBar;
141
+ }
142
+
143
+ /**
144
+ * Resolve the per-track meter from assembled track data — the LOAD side, walking the SAME precedence
145
+ * as the shared tempo resolver (`resolveTrackBpmFromTrackData`): owner CANONICAL first, then the
146
+ * per-user override, then auto-detected analysis, then the default. First valid wins:
147
+ * 1) owner canonical — `metadata.meter` → `metadata.effectiveSettings.sync.meter`
148
+ * 2) per-user — `trackUserSettings.sync.meter`
149
+ * 3) auto-detected — `metadata.audioAnalysis.analysis` time signature
150
+ * 4) default (4/4)
151
+ */
152
+ export function resolveTrackMeterFromTrackData(trackData: TrackDataLike | null = null): string {
153
+ const td = trackData;
154
+ const candidates = [
155
+ // 1) owner canonical — top-level metadata (mirrors tempo's `metadata.trackBpm`)
156
+ td?.track?.metadata?.meter,
157
+ td?.release?.metadata?.meter,
158
+ td?.metadata?.meter,
159
+ // 1) owner canonical — effectiveSettings (mirrors tempo's `effectiveSettings.sync.trackBpm`)
160
+ td?.track?.metadata?.effectiveSettings?.sync?.meter,
161
+ td?.release?.metadata?.effectiveSettings?.sync?.meter,
162
+ td?.metadata?.effectiveSettings?.sync?.meter,
163
+ td?.track?.effectiveSettings?.sync?.meter,
164
+ td?.release?.effectiveSettings?.sync?.meter,
165
+ td?.effectiveSettings?.sync?.meter,
166
+ // 2) per-user override (mirrors tempo's `trackUserSettings.sync.trackBpm`)
167
+ td?.trackUserSettings?.sync?.meter,
168
+ // 3) auto-detected analysis (mirrors tempo's `audioAnalysis.analysis.tempo_bpm`)
169
+ resolveAnalysisMeter(td?.track?.metadata?.audioAnalysis?.analysis),
170
+ resolveAnalysisMeter(td?.release?.metadata?.audioAnalysis?.analysis),
171
+ resolveAnalysisMeter(td?.metadata?.audioAnalysis?.analysis),
172
+ ];
173
+ for (const c of candidates) {
174
+ if (isValidMeterId(c)) return normalizeMeterId(c);
175
+ }
176
+ return DEFAULT_METER_ID;
177
+ }
@@ -5,13 +5,16 @@
5
5
  // startSec: gridSec, bpm }] })`. Promoted out of the orbiters app (ORB-137) so EW reuses it verbatim.
6
6
 
7
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.
8
+ * The slice of a track's data these resolvers read (tempo here; meter in `./clock/meter`). Every field
9
+ * is optional and untyped at the leaf because the runtime objects come from different app layers
10
+ * (release metadata, effective owner settings, per-user overrides, audio analysis). Resolvers coerce
11
+ * leaves with Number(...)/String(...) and ignore anything invalid, so a permissive shape here is
12
+ * intentional — not every consumer fills every path.
12
13
  */
13
14
  export interface SyncSettingsLike {
14
15
  trackBpm?: unknown;
16
+ /** Per-track time signature (e.g. `'4/4'` or `{ numerator, denominator }`) — see `./clock/meter`. */
17
+ meter?: unknown;
15
18
  primaryGridMarkerTimeSec?: unknown;
16
19
  gridMarkers?: ReadonlyArray<{ sourceTimeSec?: unknown } | null | undefined> | null;
17
20
  gridStartTimeSec?: unknown;
@@ -19,9 +22,13 @@ export interface SyncSettingsLike {
19
22
 
20
23
  export interface TrackMetadataLike {
21
24
  trackBpm?: unknown;
25
+ /** Per-track time signature (owner canonical) — see `./clock/meter`. */
26
+ meter?: unknown;
22
27
  primaryGridMarkerTimeSec?: unknown;
23
28
  effectiveSettings?: { sync?: SyncSettingsLike | null } | null;
24
- audioAnalysis?: { analysis?: { tempo_bpm?: unknown } | null } | null;
29
+ audioAnalysis?: {
30
+ analysis?: { tempo_bpm?: unknown; time_signature?: unknown; timeSignature?: unknown } | null;
31
+ } | null;
25
32
  wrap?: {
26
33
  gridMarkers?: ReadonlyArray<{ sourceTimeSec?: unknown } | null | undefined> | null;
27
34
  gridStartTimeSec?: unknown;
@@ -29,9 +36,16 @@ export interface TrackMetadataLike {
29
36
  }
30
37
 
31
38
  export interface TrackDataLike {
32
- track?: { metadata?: TrackMetadataLike | null } | null;
33
- release?: { metadata?: TrackMetadataLike | null } | null;
39
+ track?: {
40
+ metadata?: TrackMetadataLike | null;
41
+ effectiveSettings?: { sync?: SyncSettingsLike | null } | null;
42
+ } | null;
43
+ release?: {
44
+ metadata?: TrackMetadataLike | null;
45
+ effectiveSettings?: { sync?: SyncSettingsLike | null } | null;
46
+ } | null;
34
47
  metadata?: TrackMetadataLike | null;
48
+ effectiveSettings?: { sync?: SyncSettingsLike | null } | null;
35
49
  trackUserSettings?: { sync?: SyncSettingsLike | null } | null;
36
50
  }
37
51