diff --git a/src/js/musicxml-grooves.js b/src/js/musicxml-grooves.js
index fcdaec01..43e28c3a 100755
--- a/src/js/musicxml-grooves.js
+++ b/src/js/musicxml-grooves.js
@@ -19,7 +19,6 @@ const DIVISIONS_256th = DIVISIONS/64
const DIVISIONS_512th = DIVISIONS/128
const DIVISIONS_1024th = DIVISIONS/256
const QUANTIZATION_DEFAULT_GRID = [4, 3]
-const QUANTIZATION_FINEST_GRID = [32]
import fs from 'fs'
import xmlFormat from 'xml-formatter'
@@ -210,8 +209,8 @@ function createPartList(groove) {
if (partCandidates[b].usage === partCandidates[a].usage) {
if (a in parts) return -1
if (b in parts) return 1
- return SaxonJS.XPath.evaluate(`count(//instrument[@id="${a}"]/drum)`, instruments) -
- SaxonJS.XPath.evaluate(`count(//instrument[@id="${b}"]/drum)`, instruments)
+ return SaxonJS.XPath.evaluate(`count(//instrument[@id="${a}"]/drum)`, instruments)
+ - SaxonJS.XPath.evaluate(`count(//instrument[@id="${b}"]/drum)`, instruments)
}
else {
return partCandidates[b].usage - partCandidates[a].usage
@@ -325,8 +324,8 @@ function createPartMeasures(groove, partId, part) {
8: 'eighth',
16: '16th'
}
- return part[0].sequence.map((_, i) => {
- const direction = (i > 0 || partId > 1) ? '' : `
+ return part[0].sequence.map((_, measure) => {
+ const direction = (measure > 0 || partId > 1) ? '' : `
@@ -337,7 +336,7 @@ function createPartMeasures(groove, partId, part) {
`.trim()
- const attributes = i > 0 ? '' : `
+ const attributes = measure > 0 ? '' : `
${DIVISIONS}
`.trim()
return `
-
+
${attributes}
${direction}
- ${createMeasureNotes(groove, part, i)}
+ ${createMeasureNotes(groove, part, measure)}
`.trim()
}).join('')
@@ -375,15 +374,14 @@ function createPartMeasures(groove, partId, part) {
* - Generate note timings, including quantization, extra ties and rests
* - Generate the MusicXML note representation
*/
-function createMeasureNotes(groove, part, i) {
+function createMeasureNotes(groove, part, measure) {
const instrumentId = part[0].candidateInstrumentIds[0]
const beats = parseInt(groove.timeSignature.split('/')[0])
- const beatType = parseInt(groove.timeSignature.split('/')[1])
// Gather all notes and parse them.
return part.reduce((notes, track) => {
const voice = SaxonJS.XPath.evaluate(`//instrument[@id="${instrumentId}"]/drum[@midi="${track.midi[0]}"]/voice/text()`, instruments) ?? '1'
- return notes.concat(track.sequence[i].split(';').map(note => {
+ return notes.concat(track.sequence[measure].split(';').map(note => {
const parts = note.split(/\s+/).filter(part => !!part)
return parts[0] === 'z' ? undefined : {
midi: track.midi[0],
@@ -393,7 +391,7 @@ function createMeasureNotes(groove, part, i) {
partId: track.partId,
track: track.track,
voice: voice.textContent,
- measure: i
+ measure
}
}).filter(note => !!note))
}, [])
@@ -428,9 +426,9 @@ function createMeasureNotes(groove, part, i) {
// At the first note of each voice, if the onset is > 1, we insert a rest to start the measure.
// At the last note of each voice, the duration is the remaining time until the measure end.
.reduce((notes, note, index, input) => {
- const isFirstNote = notes.length === 0 || notes[notes.length - 1].voice !== note.voice
- const isLastNote = index === input.length - 1 || input[index + 1].voice !== note.voice
- const previousOnset = isFirstNote ? 1 : notes[notes.length - 1].onset
+ const isFirstNote = notes.length === 0 || notes[notes.length-1].voice !== note.voice
+ const isLastNote = index === input.length - 1 || input[index+1].voice !== note.voice
+ const previousOnset = isFirstNote ? 1 : notes[notes.length-1].onset
const boundary = Math.floor(previousOnset) + 1 - previousOnset
const duration = Math.min(note.onset - previousOnset, boundary)
if (duration > 0) {
@@ -457,7 +455,21 @@ function createMeasureNotes(groove, part, i) {
notes.push(note)
}
else {
- // TODO Move note to next measure.
+ // Move note to next measure.
+ // TODO Handle the case where there's already a note at onset 1.
+ if (measure < part[0].sequence.length - 1) {
+ const track = part.find(t => t.track === note.track)
+ const next = track.sequence[measure+1]
+ if (next.trim() === 'z') {
+ track.sequence[measure+1] = `1 1t ${note.velocity}`
+ }
+ else {
+ track.sequence[measure+1] = `1 1t ${note.velocity} ; ${next}`
+ }
+ }
+ else {
+ console.warn(`[${note.track}:${note.measure+1}] Quantized note at ${note.onset} cannot be moved beyond last measure. Dropping.`)
+ }
}
return notes
}, [])
@@ -554,8 +566,7 @@ function createMeasureNotes(groove, part, i) {
* Quantize a single note onset.
*/
function quantizeNoteOnset(note, index, notes, beats, grid) {
- const isFirstNote = index === 0 || notes[index - 1].voice !== note.voice
- const isLastNote = index === notes.length - 1 || notes[index + 1].voice !== note.voice
+ const isFirstNote = index === 0 || notes[index-1].voice !== note.voice
const scoreDuration = Math.round(note.duration * DIVISIONS)
const scoreOnset = Math.round((note.onset - 1) * DIVISIONS)
const onset = grid.map(unit => {
@@ -567,7 +578,7 @@ function quantizeNoteOnset(note, index, notes, beats, grid) {
return onset
}
- if (isFirstNote || notes[index - 1].quantized.onset < candidate.multiple) {
+ if (isFirstNote || notes[index-1].quantized.onset < candidate.multiple) {
return candidate
}
}, undefined)
@@ -576,8 +587,8 @@ function quantizeNoteOnset(note, index, notes, beats, grid) {
if (onset === undefined) {
console.warn(`[${note.track}:${note.measure+1}] Failed to quantize note onset at ${note.onset} to avoid collision with previous note. Moving it manually.`)
onset = {
- multiple: notes[index - 1].quantized.onset + DIVISIONS_1024th,
- error_sgn: scoreOnset - (notes[index - 1].quantized.onset + DIVISIONS_1024th)
+ multiple: notes[index-1].quantized.onset + DIVISIONS_1024th,
+ error_sgn: scoreOnset - (notes[index-1].quantized.onset + DIVISIONS_1024th)
}
}
if (onset.multiple >= beats * DIVISIONS) {
@@ -596,11 +607,11 @@ function quantizeNoteOnset(note, index, notes, beats, grid) {
* Don't let duration remain at 0.
*/
function quantizeNoteDuration(note, index, notes, beats, grid) {
- const isFirstNote = index === 0 || notes[index - 1].voice !== note.voice
- const isLastNote = index === notes.length - 1 || notes[index + 1].voice !== note.voice
+ const isFirstNote = index === 0 || notes[index-1].voice !== note.voice
+ const isLastNote = index === notes.length - 1 || notes[index+1].voice !== note.voice
const scoreOffset = Math.min(
note.quantized.onset + note.quantized.duration,
- isLastNote ? beats * DIVISIONS : (notes[index + 1].quantized.onset + notes[index + 1].quantized.duration)
+ isLastNote ? beats * DIVISIONS : (notes[index+1].quantized.onset + notes[index+1].quantized.duration)
)
let offset = grid.map(unit => {
return nearestMultiple(scoreOffset, DIVISIONS/unit)
@@ -631,7 +642,7 @@ function quantizeNoteDuration(note, index, notes, beats, grid) {
note.duration = note.quantized.duration / DIVISIONS
// Add rests before and after note if needed.
- const previousOffset = isFirstNote ? 0 : notes[index - 1].quantized.onset + notes[index - 1].quantized.duration
+ const previousOffset = isFirstNote ? 0 : notes[index-1].quantized.onset + notes[index-1].quantized.duration
return [
fillWithRests(note, previousOffset, note.quantized.onset),
isLastNote ? fillWithRests(note, note.quantized.onset + note.quantized.duration, beats * DIVISIONS) : []
@@ -660,7 +671,7 @@ function fillWithRests(note, gapStart, gapEnd) {
onset: gapStart / DIVISIONS + 1,
duration: duration / DIVISIONS,
})
- gap -= rests[rests.length - 1].quantized.duration
+ gap -= rests[rests.length-1].quantized.duration
}
while (gap > Number.EPSILON) {
const duration = Math.min(gap, DIVISIONS_QUARTER)
@@ -675,7 +686,7 @@ function fillWithRests(note, gapStart, gapEnd) {
onset: (gapEnd - gap) / DIVISIONS + 1,
duration: duration / DIVISIONS
})
- gap -= rests[rests.length - 1].quantized.duration
+ gap -= rests[rests.length-1].quantized.duration
}
}
return rests
@@ -802,9 +813,9 @@ function createNoteTiming(note, index, notes) {
let onset = note.quantized.onset
for (const [entry, type] of types) {
if (remainingDuration >= entry) {
- if (extra.length > 0 && extra[extra.length - 1].duration === entry * 2) {
- extra[extra.length - 1].dots += 1
- extra[extra.length - 1].duration += entry
+ if (extra.length > 0 && extra[extra.length-1].duration === entry * 2) {
+ extra[extra.length-1].dots += 1
+ extra[extra.length-1].duration += entry
}
else {
extra.push({
@@ -821,7 +832,7 @@ function createNoteTiming(note, index, notes) {
}
// Close up the last tie.
- extra[extra.length - 1].tie.start = false
+ extra[extra.length-1].tie.start = false
// Transfer first extra note to current note.
note.musicXml = extra.shift()