audio-mixer-engine 1.3.3 → 1.3.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "audio-mixer-engine",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "Audio engine library for audio mixer applications with MIDI parsing, playback, and synthesis",
5
5
  "main": "dist/audio-mixer-engine.cjs.js",
6
6
  "module": "dist/audio-mixer-engine.es.js",
@@ -131,6 +131,7 @@ export class NoteExtractor {
131
131
  const isGrace = getChild(child, 'grace') !== null;
132
132
  const durationXml = getChildNumber(child, 'duration', 0);
133
133
  const durationTicks = this.timing.xmlDurationToTicks(durationXml);
134
+ const isTuplet = getChild(child, 'time-modification') !== null;
134
135
 
135
136
  // Grace notes: skip (no duration effect)
136
137
  if (isGrace) continue;
@@ -175,9 +176,11 @@ export class NoteExtractor {
175
176
  if (tieStart) {
176
177
  // Middle of a tie chain: extend the end tick
177
178
  pending.endTick = noteStart + durationTicks;
179
+ pending.isTuplet = pending.isTuplet || isTuplet;
178
180
  } else {
179
181
  // End of tie: emit the completed note
180
182
  pending.endTick = noteStart + durationTicks;
183
+ pending.isTuplet = pending.isTuplet || isTuplet;
181
184
  notes.push(this._buildNote(pending, partChannel, sourceTrackIndex));
182
185
  this.pendingTies.delete(midiPitch);
183
186
  }
@@ -187,7 +190,8 @@ export class NoteExtractor {
187
190
  pitch: midiPitch,
188
191
  startTick: noteStart,
189
192
  endTick: noteStart + durationTicks,
190
- velocity: this.currentVelocity
193
+ velocity: this.currentVelocity,
194
+ isTuplet
191
195
  });
192
196
  } else {
193
197
  // Regular note (not tied)
@@ -195,7 +199,8 @@ export class NoteExtractor {
195
199
  pitch: midiPitch,
196
200
  startTick: noteStart,
197
201
  endTick: noteStart + durationTicks,
198
- velocity: this.currentVelocity
202
+ velocity: this.currentVelocity,
203
+ isTuplet
199
204
  }, partChannel, sourceTrackIndex));
200
205
  }
201
206
 
@@ -209,7 +214,8 @@ export class NoteExtractor {
209
214
  text,
210
215
  syllabic: syllabic || 'single',
211
216
  tick: noteStart,
212
- time: this.timing.tickToTime(noteStart)
217
+ time: this.timing.tickToTime(noteStart),
218
+ isTuplet
213
219
  });
214
220
  }
215
221
  }
@@ -257,7 +263,8 @@ export class NoteExtractor {
257
263
  endTime: this.timing.tickToTime(data.endTick),
258
264
  velocity: data.velocity,
259
265
  channel,
260
- sourceTrackIndex
266
+ sourceTrackIndex,
267
+ isTuplet: data.isTuplet || false
261
268
  };
262
269
  }
263
270
 
@@ -250,9 +250,13 @@ class MusicXmlConverter {
250
250
 
251
251
  // Apply swing timing if any swing directives were found.
252
252
  // Transforms note and lyric tick positions; does not affect barStructure.
253
- if (timingEngine.swingMap.length > 0) {
254
- for (const part of Object.values(parts)) {
255
- for (const note of part.notes) {
253
+ // Tuplet notes (isTuplet=true) are excluded: their timing already embodies the
254
+ // subdivision that swing represents, so applying swing would double-count it.
255
+ // The isTuplet flag is an internal marker and is stripped here before output.
256
+ const hasSwing = timingEngine.swingMap.length > 0;
257
+ for (const part of Object.values(parts)) {
258
+ for (const note of part.notes) {
259
+ if (hasSwing && !note.isTuplet) {
256
260
  const s = timingEngine.applySwingToTick(note.startTick);
257
261
  const e = timingEngine.applySwingToTick(note.endTick);
258
262
  note.startTick = s;
@@ -261,11 +265,15 @@ class MusicXmlConverter {
261
265
  note.startTime = timingEngine.tickToTime(s);
262
266
  note.endTime = timingEngine.tickToTime(e);
263
267
  }
264
- for (const lyric of part.lyrics) {
268
+ delete note.isTuplet;
269
+ }
270
+ for (const lyric of part.lyrics) {
271
+ if (hasSwing && !lyric.isTuplet) {
265
272
  const t = timingEngine.applySwingToTick(lyric.tick);
266
273
  lyric.tick = t;
267
274
  lyric.time = timingEngine.tickToTime(t);
268
275
  }
276
+ delete lyric.isTuplet;
269
277
  }
270
278
  }
271
279