musicxml-io 0.7.0 → 0.7.1

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
@@ -179,6 +179,43 @@ import { exportMidi } from 'musicxml-io';
179
179
  const midi = exportMidi(score, { tempo: 120 });
180
180
  ```
181
181
 
182
+ #### Playback timeline (for audio alignment)
183
+
184
+ `generatePlaybackTimeline` returns a **timing sidecar** mapping playback time
185
+ (seconds) to conceptual musical positions (`measure` + `beat`), with
186
+ repeats/voltas/jumps expanded. It is a read-only analysis (under `query`) — the
187
+ sibling of `generatePlaybackSequence`, which gives the play *order*; this gives
188
+ the same expansion *with absolute times*.
189
+
190
+ ```typescript
191
+ import { generatePlaybackTimeline } from 'musicxml-io';
192
+
193
+ const sidecar = generatePlaybackTimeline(score);
194
+ // sidecar.breakpoints: [{ midiSec, quarterPos, measureNumber, beatInMeasure, repeatIteration }]
195
+ ```
196
+
197
+ The timeline is the one thing that cannot be recomputed from the MusicXML alone,
198
+ because the seconds depend on the tempo and repeat expansion. Its seconds equal
199
+ the playback time of `exportMidi`'s output (they share the same computation).
200
+ Pair it with an audio aligner that returns `audioSec ↔ midiSec` to follow a
201
+ recording on the score:
202
+
203
+ ```
204
+ audioSec ─[aligner]→ midiSec ─[timeline: interpolate quarterPos]→ (measure, beat)
205
+ ```
206
+
207
+ Breakpoints are emitted at every played-measure start and every tempo change,
208
+ sorted and monotone by `midiSec`. Between two consecutive breakpoints
209
+ `midiSec ↔ quarterPos` is linear (tempo is piecewise-constant), so any
210
+ intermediate time interpolates exactly. A repeated measure appears multiple
211
+ times with a rising `repeatIteration`. The conceptual position is
212
+ renderer-independent — resolving `(measure, beat)` to a rendered element is the
213
+ caller's responsibility.
214
+
215
+ When you need both the MIDI and its timeline, `exportMidiWithTimingMap(score)`
216
+ returns `{ midi, sidecar }` in one call (just `exportMidi` +
217
+ `generatePlaybackTimeline`, guaranteed consistent).
218
+
182
219
  ### Validation
183
220
 
184
221
  ```typescript
@@ -203,6 +240,7 @@ const { valid, errors } = validate(score);
203
240
  | `serializeCompressed(score)` | To .mxl |
204
241
  | `serializeAbc(score, options?)` | To ABC notation string |
205
242
  | `exportMidi(score)` | To MIDI |
243
+ | `exportMidiWithTimingMap(score)` | To MIDI + a MIDI↔(measure, beat) timing sidecar for audio alignment |
206
244
 
207
245
  ### Operations
208
246
 
@@ -260,6 +298,8 @@ See [OPERATIONS.md](OPERATIONS.md) for the complete list.
260
298
  | `getHarmonies(score)` | All chord symbols |
261
299
  | `getDynamics(score)` | All dynamics markings |
262
300
  | `getTempoMarkings(score)` | All tempo markings |
301
+ | `generatePlaybackSequence(score)` | Play order of measures (repeats/voltas/jumps expanded) |
302
+ | `generatePlaybackTimeline(score)` | Playback time (sec) ↔ (measure, beat) map for audio alignment |
263
303
 
264
304
  ### Accessors
265
305
 
@@ -8,7 +8,7 @@ import {
8
8
  getMeasureEndPosition,
9
9
  pitchToSemitone,
10
10
  semitoneToKeyAwarePitch
11
- } from "./chunk-CCSOG7HU.mjs";
11
+ } from "./chunk-MA2TPIIG.mjs";
12
12
 
13
13
  // src/id.ts
14
14
  import { customAlphabet } from "nanoid";