@octoseq/mir 0.1.0-main.4ecb074 → 0.1.0-main.5fdb072

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.
Files changed (47) hide show
  1. package/dist/chunk-HF3QHCRK.js +3234 -0
  2. package/dist/chunk-HF3QHCRK.js.map +1 -0
  3. package/dist/index.d.ts +2122 -47
  4. package/dist/index.js +3524 -39
  5. package/dist/index.js.map +1 -1
  6. package/dist/runMir-CnJQbBr8.d.ts +187 -0
  7. package/dist/runner/runMir.d.ts +2 -2
  8. package/dist/runner/runMir.js +1 -1
  9. package/dist/runner/workerProtocol.d.ts +8 -1
  10. package/dist/runner/workerProtocol.js.map +1 -1
  11. package/dist/types-DqH4umN8.d.ts +761 -0
  12. package/package.json +2 -2
  13. package/src/dsp/activity.ts +544 -0
  14. package/src/dsp/bandCqt.ts +662 -0
  15. package/src/dsp/bandEvents.ts +351 -0
  16. package/src/dsp/bandMask.ts +225 -0
  17. package/src/dsp/bandMir.ts +524 -0
  18. package/src/dsp/bandProposal.ts +552 -0
  19. package/src/dsp/beatCandidates.ts +299 -0
  20. package/src/dsp/cqt.ts +386 -0
  21. package/src/dsp/cqtSignals.ts +462 -0
  22. package/src/dsp/customSignalReduction.ts +841 -0
  23. package/src/dsp/eventToSignal.ts +531 -0
  24. package/src/dsp/frequencyBand.ts +956 -0
  25. package/src/dsp/mel.ts +56 -3
  26. package/src/dsp/musicalTime.ts +240 -0
  27. package/src/dsp/onset.ts +296 -30
  28. package/src/dsp/peakPicking.ts +519 -0
  29. package/src/dsp/phaseAlignment.ts +153 -0
  30. package/src/dsp/pitch.ts +289 -0
  31. package/src/dsp/resample.ts +44 -0
  32. package/src/dsp/signalTransforms.ts +660 -0
  33. package/src/dsp/silenceGating.ts +511 -0
  34. package/src/dsp/spectral.ts +124 -4
  35. package/src/dsp/tempoHypotheses.ts +395 -0
  36. package/src/gpu/bufferPool.ts +266 -0
  37. package/src/gpu/context.ts +30 -6
  38. package/src/gpu/helpers.ts +85 -0
  39. package/src/gpu/melProject.ts +83 -0
  40. package/src/index.ts +366 -5
  41. package/src/runner/runMir.ts +234 -2
  42. package/src/runner/workerProtocol.ts +9 -1
  43. package/src/types.ts +768 -3
  44. package/dist/chunk-DUWYCAVG.js +0 -1525
  45. package/dist/chunk-DUWYCAVG.js.map +0 -1
  46. package/dist/runMir-CSIBwNZ3.d.ts +0 -84
  47. package/dist/types-BE3py4fZ.d.ts +0 -83
package/src/dsp/mel.ts CHANGED
@@ -24,15 +24,68 @@ function assertPositiveInt(name: string, value: number): void {
24
24
  }
25
25
  }
26
26
 
27
- function hzToMel(hz: number): number {
28
- // Slaney-style mel approximation (HTK-like).
27
+ /**
28
+ * Convert frequency in Hz to mel scale.
29
+ * Uses Slaney-style (HTK-like) approximation.
30
+ */
31
+ export function hzToMel(hz: number): number {
29
32
  return 2595 * Math.log10(1 + hz / 700);
30
33
  }
31
34
 
32
- function melToHz(mel: number): number {
35
+ /**
36
+ * Convert mel scale value to frequency in Hz.
37
+ * Inverse of hzToMel.
38
+ */
39
+ export function melToHz(mel: number): number {
33
40
  return 700 * (Math.pow(10, mel / 2595) - 1);
34
41
  }
35
42
 
43
+ /**
44
+ * Configuration for mel frequency conversions.
45
+ */
46
+ export type MelConversionConfig = {
47
+ nMels: number;
48
+ fMin: number;
49
+ fMax: number;
50
+ };
51
+
52
+ /**
53
+ * Convert a frequency in Hz to a feature index (0 to nMels-1).
54
+ * The mapping is linear in mel space.
55
+ *
56
+ * @param hz - Frequency in Hz
57
+ * @param config - Mel configuration with nMels, fMin, fMax
58
+ * @returns Feature index as a continuous value (may be fractional)
59
+ */
60
+ export function hzToFeatureIndex(hz: number, config: MelConversionConfig): number {
61
+ const melMin = hzToMel(config.fMin);
62
+ const melMax = hzToMel(config.fMax);
63
+ const melHz = hzToMel(hz);
64
+
65
+ // Map mel value to 0..1 range, then to 0..(nMels-1)
66
+ const normalized = (melHz - melMin) / (melMax - melMin);
67
+ return normalized * (config.nMels - 1);
68
+ }
69
+
70
+ /**
71
+ * Convert a feature index (0 to nMels-1) to frequency in Hz.
72
+ * Inverse of hzToFeatureIndex.
73
+ *
74
+ * @param index - Feature index (can be fractional)
75
+ * @param config - Mel configuration with nMels, fMin, fMax
76
+ * @returns Frequency in Hz
77
+ */
78
+ export function featureIndexToHz(index: number, config: MelConversionConfig): number {
79
+ const melMin = hzToMel(config.fMin);
80
+ const melMax = hzToMel(config.fMax);
81
+
82
+ // Map index from 0..(nMels-1) to mel space
83
+ const normalized = index / (config.nMels - 1);
84
+ const mel = melMin + normalized * (melMax - melMin);
85
+
86
+ return melToHz(mel);
87
+ }
88
+
36
89
  function buildMelFilterBank(
37
90
  sampleRate: number,
38
91
  fftSize: number,
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Musical Time utilities for B4.
3
+ *
4
+ * These functions compute derived values from musical time structures.
5
+ * All computations are deterministic and pure.
6
+ */
7
+
8
+ import type { BeatPosition, MusicalTimeSegment, MusicalTimeStructure, BeatGrid } from "../types";
9
+
10
+ /**
11
+ * Find the segment containing a given time.
12
+ * Segments are non-overlapping, so at most one segment contains any time.
13
+ *
14
+ * @param time - Time in seconds from track start
15
+ * @param segments - Ordered list of segments (by startTime ascending)
16
+ * @returns The containing segment, or null if time is outside all segments
17
+ */
18
+ export function findSegmentAtTime(
19
+ time: number,
20
+ segments: MusicalTimeSegment[]
21
+ ): MusicalTimeSegment | null {
22
+ // Binary search would be more efficient for many segments,
23
+ // but linear scan is fine for typical track segment counts (<10)
24
+ for (const segment of segments) {
25
+ if (time >= segment.startTime && time < segment.endTime) {
26
+ return segment;
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+
32
+ /**
33
+ * Compute the beat position at a given time.
34
+ *
35
+ * Beat position formula:
36
+ * period = 60 / bpm
37
+ * beatsFromStart = (time - phaseOffset) / period
38
+ * beatIndex = floor(beatsFromStart)
39
+ * beatPhase = beatsFromStart - beatIndex (fractional part, 0-1)
40
+ * beatPosition = beatsFromStart (continuous)
41
+ *
42
+ * @param time - Time in seconds from track start
43
+ * @param segments - Ordered list of segments (by startTime ascending)
44
+ * @returns BeatPosition if time is within a segment, null otherwise
45
+ */
46
+ export function computeBeatPosition(
47
+ time: number,
48
+ segments: MusicalTimeSegment[]
49
+ ): BeatPosition | null {
50
+ const segment = findSegmentAtTime(time, segments);
51
+ if (!segment) {
52
+ return null;
53
+ }
54
+
55
+ const period = 60 / segment.bpm;
56
+ const beatsFromStart = (time - segment.phaseOffset) / period;
57
+
58
+ // Handle edge case: time before first beat in segment
59
+ // beatIndex should never be negative within a properly defined segment
60
+ const beatIndex = Math.floor(beatsFromStart);
61
+ const beatPhase = beatsFromStart - beatIndex;
62
+
63
+ return {
64
+ segmentId: segment.id,
65
+ beatIndex,
66
+ beatPhase,
67
+ beatPosition: beatsFromStart,
68
+ bpm: segment.bpm,
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Compute beat position from a MusicalTimeStructure.
74
+ * Convenience wrapper for computeBeatPosition.
75
+ */
76
+ export function computeBeatPositionFromStructure(
77
+ time: number,
78
+ structure: MusicalTimeStructure | null
79
+ ): BeatPosition | null {
80
+ if (!structure || structure.segments.length === 0) {
81
+ return null;
82
+ }
83
+ return computeBeatPosition(time, structure.segments);
84
+ }
85
+
86
+ /**
87
+ * Generate a unique segment ID.
88
+ */
89
+ export function generateSegmentId(): string {
90
+ return `seg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
91
+ }
92
+
93
+ /**
94
+ * Create a new MusicalTimeSegment from a BeatGrid.
95
+ *
96
+ * @param grid - The beat grid to promote
97
+ * @param startTime - Segment start boundary in seconds
98
+ * @param endTime - Segment end boundary in seconds
99
+ * @returns A new MusicalTimeSegment
100
+ */
101
+ export function createSegmentFromGrid(
102
+ grid: BeatGrid,
103
+ startTime: number,
104
+ endTime: number
105
+ ): MusicalTimeSegment {
106
+ const effectivePhaseOffset = grid.phaseOffset + grid.userNudge;
107
+
108
+ return {
109
+ id: generateSegmentId(),
110
+ bpm: grid.bpm,
111
+ phaseOffset: effectivePhaseOffset,
112
+ startTime,
113
+ endTime,
114
+ confidence: grid.confidence,
115
+ provenance: {
116
+ source: "promoted_from_hypothesis",
117
+ sourceHypothesisId: grid.sourceHypothesisId,
118
+ promotedAt: new Date().toISOString(),
119
+ userNudge: grid.userNudge,
120
+ },
121
+ };
122
+ }
123
+
124
+ /**
125
+ * Create a new empty MusicalTimeStructure.
126
+ */
127
+ export function createMusicalTimeStructure(): MusicalTimeStructure {
128
+ const now = new Date().toISOString();
129
+ return {
130
+ version: 1,
131
+ segments: [],
132
+ createdAt: now,
133
+ modifiedAt: now,
134
+ };
135
+ }
136
+
137
+ /**
138
+ * Validate that segments are non-overlapping and ordered.
139
+ *
140
+ * @param segments - List of segments to validate
141
+ * @returns Array of validation errors (empty if valid)
142
+ */
143
+ export function validateSegments(segments: MusicalTimeSegment[]): string[] {
144
+ const errors: string[] = [];
145
+
146
+ for (let i = 0; i < segments.length; i++) {
147
+ const seg = segments[i];
148
+ if (!seg) continue;
149
+
150
+ // Check segment bounds
151
+ if (seg.startTime >= seg.endTime) {
152
+ errors.push(`Segment ${seg.id}: startTime (${seg.startTime}) >= endTime (${seg.endTime})`);
153
+ }
154
+
155
+ if (seg.bpm <= 0) {
156
+ errors.push(`Segment ${seg.id}: bpm (${seg.bpm}) must be positive`);
157
+ }
158
+
159
+ // Check ordering and non-overlap with next segment
160
+ if (i < segments.length - 1) {
161
+ const next = segments[i + 1];
162
+ if (!next) continue;
163
+ if (seg.startTime >= next.startTime) {
164
+ errors.push(`Segment ${seg.id} not ordered before ${next.id}`);
165
+ }
166
+ if (seg.endTime > next.startTime) {
167
+ errors.push(`Segment ${seg.id} overlaps with ${next.id}`);
168
+ }
169
+ }
170
+ }
171
+
172
+ return errors;
173
+ }
174
+
175
+ /**
176
+ * Sort segments by startTime ascending.
177
+ * Returns a new array (does not mutate input).
178
+ */
179
+ export function sortSegments(segments: MusicalTimeSegment[]): MusicalTimeSegment[] {
180
+ return [...segments].sort((a, b) => a.startTime - b.startTime);
181
+ }
182
+
183
+ /**
184
+ * Split a segment at a given time.
185
+ *
186
+ * @param segment - The segment to split
187
+ * @param splitTime - Time in seconds where to split
188
+ * @returns Tuple of [beforeSegment, afterSegment]
189
+ * @throws If splitTime is outside segment bounds
190
+ */
191
+ export function splitSegment(
192
+ segment: MusicalTimeSegment,
193
+ splitTime: number
194
+ ): [MusicalTimeSegment, MusicalTimeSegment] {
195
+ if (splitTime <= segment.startTime || splitTime >= segment.endTime) {
196
+ throw new Error(
197
+ `Split time ${splitTime} must be within segment bounds [${segment.startTime}, ${segment.endTime})`
198
+ );
199
+ }
200
+
201
+ const beforeSegment: MusicalTimeSegment = {
202
+ ...segment,
203
+ id: generateSegmentId(),
204
+ endTime: splitTime,
205
+ };
206
+
207
+ const afterSegment: MusicalTimeSegment = {
208
+ ...segment,
209
+ id: generateSegmentId(),
210
+ startTime: splitTime,
211
+ // phaseOffset stays the same - beat grid continues seamlessly
212
+ };
213
+
214
+ return [beforeSegment, afterSegment];
215
+ }
216
+
217
+ /**
218
+ * Generate beat times within a segment.
219
+ * Used for rendering beat markers.
220
+ *
221
+ * @param segment - The segment to generate beats for
222
+ * @returns Array of beat times in seconds
223
+ */
224
+ export function generateSegmentBeatTimes(segment: MusicalTimeSegment): number[] {
225
+ const period = 60 / segment.bpm;
226
+ const times: number[] = [];
227
+
228
+ // Find first beat at or after segment start
229
+ const beatsBeforeStart = Math.ceil((segment.startTime - segment.phaseOffset) / period);
230
+ let time = segment.phaseOffset + beatsBeforeStart * period;
231
+
232
+ while (time < segment.endTime) {
233
+ if (time >= segment.startTime) {
234
+ times.push(time);
235
+ }
236
+ time += period;
237
+ }
238
+
239
+ return times;
240
+ }