audio-mixer-engine 1.3.7 → 1.3.8
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/dist/audio-mixer-engine.cjs.js +1 -1
- package/dist/audio-mixer-engine.es.js +329 -308
- package/package.json +1 -1
- package/src/lib/musicxml-converter.js +56 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audio-mixer-engine",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
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",
|
|
@@ -147,30 +147,51 @@ class MusicXmlConverter {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
}
|
|
150
|
-
//
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
150
|
+
// Walk children in order to collect position-aware tempo/swing changes.
|
|
151
|
+
// Tracking divCursor lets us record each tempo change's beat-level offset
|
|
152
|
+
// within the measure, so mid-measure rit/accel steps land at the right tick.
|
|
153
|
+
let divCursor = 0;
|
|
154
|
+
cache.tempos = []; // [{ divisionOffset, tempo }]
|
|
155
|
+
for (const child of m.children) {
|
|
156
|
+
if (child.tagName === 'note') {
|
|
157
|
+
if (child.querySelector('chord') === null) {
|
|
158
|
+
const durEl = child.querySelector('duration');
|
|
159
|
+
divCursor += durEl ? parseInt(durEl.textContent, 10) || 0 : 0;
|
|
160
|
+
}
|
|
161
|
+
} else if (child.tagName === 'forward') {
|
|
162
|
+
const durEl = child.querySelector('duration');
|
|
163
|
+
divCursor += durEl ? parseInt(durEl.textContent, 10) || 0 : 0;
|
|
164
|
+
} else if (child.tagName === 'backup') {
|
|
165
|
+
const durEl = child.querySelector('duration');
|
|
166
|
+
divCursor -= durEl ? parseInt(durEl.textContent, 10) || 0 : 0;
|
|
167
|
+
if (divCursor < 0) divCursor = 0;
|
|
168
|
+
} else if (child.tagName === 'direction') {
|
|
169
|
+
const sound = getChild(child, 'sound');
|
|
170
|
+
if (sound) {
|
|
171
|
+
const tempo = parseFloat(sound.getAttribute('tempo'));
|
|
172
|
+
if (!isNaN(tempo) && tempo > 0) {
|
|
173
|
+
cache.tempos.push({ divisionOffset: divCursor, tempo });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Detect <swing> element
|
|
177
|
+
const swingEl = getChild(sound, 'swing');
|
|
178
|
+
if (swingEl) {
|
|
179
|
+
if (getChild(swingEl, 'straight')) {
|
|
180
|
+
cache.swing = { straight: true };
|
|
181
|
+
} else {
|
|
182
|
+
const first = getChildNumber(swingEl, 'first');
|
|
183
|
+
const second = getChildNumber(swingEl, 'second');
|
|
184
|
+
const swingType = getChildText(swingEl, 'swing-type') || 'eighth';
|
|
185
|
+
if (first !== null && second !== null && first > 0 && second > 0) {
|
|
186
|
+
cache.swing = { first, second, swingType };
|
|
187
|
+
}
|
|
169
188
|
}
|
|
170
189
|
}
|
|
171
190
|
}
|
|
172
191
|
}
|
|
173
192
|
}
|
|
193
|
+
// Keep cache.tempo for structure-display purposes (first tempo seen in measure)
|
|
194
|
+
if (cache.tempos.length > 0) cache.tempo = cache.tempos[0].tempo;
|
|
174
195
|
measureTimingCache.set(mi, cache);
|
|
175
196
|
}
|
|
176
197
|
|
|
@@ -187,6 +208,10 @@ class MusicXmlConverter {
|
|
|
187
208
|
const mi = entry.measureIndex;
|
|
188
209
|
const cachedTiming = measureTimingCache.get(mi) || {};
|
|
189
210
|
|
|
211
|
+
// Read measure start before mutating the timing cursor for tempo placement
|
|
212
|
+
const measureStartTick = timingEngine.currentTick;
|
|
213
|
+
const actualBeats = entry.beatsInMeasure;
|
|
214
|
+
|
|
190
215
|
// Apply timing state for this measure
|
|
191
216
|
if (cachedTiming.divisions) {
|
|
192
217
|
timingEngine.currentDivisions = cachedTiming.divisions;
|
|
@@ -194,17 +219,22 @@ class MusicXmlConverter {
|
|
|
194
219
|
if (cachedTiming.timeSig) {
|
|
195
220
|
timingEngine.currentTimeSig = { ...cachedTiming.timeSig };
|
|
196
221
|
}
|
|
197
|
-
|
|
198
|
-
|
|
222
|
+
|
|
223
|
+
// Apply each tempo change at its exact tick offset within the measure so that
|
|
224
|
+
// mid-measure rit/accel steps (e.g. one change per beat) are preserved.
|
|
225
|
+
if (cachedTiming.tempos && cachedTiming.tempos.length > 0) {
|
|
226
|
+
const divisions = cachedTiming.divisions || timingEngine.currentDivisions;
|
|
227
|
+
for (const { divisionOffset, tempo } of cachedTiming.tempos) {
|
|
228
|
+
const tickOffset = Math.round((divisionOffset / divisions) * timingEngine.ticksPerBeat);
|
|
229
|
+
timingEngine.currentTick = measureStartTick + tickOffset;
|
|
230
|
+
timingEngine.processTempo(tempo);
|
|
231
|
+
}
|
|
232
|
+
timingEngine.currentTick = measureStartTick; // Restore cursor for bar-structure generation
|
|
199
233
|
}
|
|
234
|
+
|
|
200
235
|
if (cachedTiming.swing) {
|
|
201
236
|
timingEngine.processSwing(cachedTiming.swing);
|
|
202
237
|
}
|
|
203
|
-
|
|
204
|
-
// Generate barStructure entry at current tick position
|
|
205
|
-
// Use actual beats from unrolled entry (handles partial bars)
|
|
206
|
-
const measureStartTick = timingEngine.currentTick;
|
|
207
|
-
const actualBeats = entry.beatsInMeasure;
|
|
208
238
|
const barEntry = timingEngine.generateBarStructureEntry(measureStartTick, actualBeats);
|
|
209
239
|
barStructure.push(barEntry);
|
|
210
240
|
|