UNPKG

museaikit

Version:

A powerful music-focused AI toolkit

119 lines 5.17 kB
export function mergeSamePitchNotes(notes, mergeThreshold) { const groups = {}; notes.forEach(note => { if (!groups[note.pitch]) { groups[note.pitch] = []; } groups[note.pitch].push(note); }); const mergedNotes = []; Object.keys(groups).forEach(key => { const group = groups[+key].sort((a, b) => a.startTime - b.startTime); const mergedGroup = []; let current = group[0]; for (let i = 1; i < group.length; i++) { const note = group[i]; if (note.startTime - current.endTime < mergeThreshold || note.startTime <= current.endTime) { current.endTime = Math.max(current.endTime, note.endTime); } else { mergedGroup.push(current); current = note; } } mergedGroup.push(current); mergedNotes.push(...mergedGroup); }); return mergedNotes; } export function mergeDifferentPitchNotes(notes, mergeThreshold) { const mergedNotes = notes.slice().sort((a, b) => a.startTime - b.startTime); let merged = true; while (merged) { merged = false; for (let i = 0; i < mergedNotes.length; i++) { for (let j = i + 1; j < mergedNotes.length; j++) { if (mergedNotes[i].pitch !== mergedNotes[j].pitch) { const overlap = (mergedNotes[i].startTime < mergedNotes[j].endTime) && (mergedNotes[j].startTime < mergedNotes[i].endTime); const gap = mergedNotes[j].startTime - mergedNotes[i].endTime; const nearlyConsecutive = gap < mergeThreshold && gap >= 0; if (overlap || nearlyConsecutive) { const durationI = mergedNotes[i].endTime - mergedNotes[i].startTime; const durationJ = mergedNotes[j].endTime - mergedNotes[j].startTime; if (durationI >= durationJ) { mergedNotes[i].startTime = Math.min(mergedNotes[i].startTime, mergedNotes[j].startTime); mergedNotes[i].endTime = Math.max(mergedNotes[i].endTime, mergedNotes[j].endTime); mergedNotes.splice(j, 1); } else { mergedNotes[j].startTime = Math.min(mergedNotes[i].startTime, mergedNotes[j].startTime); mergedNotes[j].endTime = Math.max(mergedNotes[i].endTime, mergedNotes[j].endTime); mergedNotes.splice(i, 1); merged = true; break; } merged = true; break; } } } if (merged) { break; } } } return mergedNotes; } export function quantizeNoteTimes(notes, resolution) { if (notes.length === 0) return notes; const firstOnset = Math.min(...notes.map(note => note.startTime)); const shiftedNotes = notes.map(note => ({ ...note, startTime: note.startTime - firstOnset, endTime: note.endTime - firstOnset, })); const sortedNotes = shiftedNotes.slice().sort((a, b) => a.startTime - b.startTime); const quantizedNotes = []; let cumulativeShift = 0; for (let i = 0; i < sortedNotes.length; i++) { let qStart = Math.round(sortedNotes[i].startTime / resolution) * resolution; let qEnd = Math.round(sortedNotes[i].endTime / resolution) * resolution; if (qEnd <= qStart) { qEnd = qStart + resolution; } qStart = qStart - cumulativeShift; qEnd = qEnd - cumulativeShift; if (i > 0) { const originalGap = sortedNotes[i].startTime - sortedNotes[i - 1].endTime; if (originalGap < resolution) { const desiredStart = quantizedNotes[i - 1].endTime; const shift = qStart - desiredStart; if (shift > 0) { qStart = qStart - shift; qEnd = qEnd - shift; cumulativeShift += shift; } } } quantizedNotes.push({ ...sortedNotes[i], startTime: qStart, endTime: qEnd, }); } return quantizedNotes; } export function cleanNoteSequence(ns, minNoteDuration, mergeThreshold, quantizeResolution) { const clonedNotes = ns.notes.map(note => ({ ...note })); const samePitchMerged = mergeSamePitchNotes(clonedNotes, mergeThreshold); const allMerged = mergeDifferentPitchNotes(samePitchMerged, mergeThreshold); const cleanedNotes = allMerged.filter(note => (note.endTime - note.startTime) >= minNoteDuration); const quantized = quantizeNoteTimes(cleanedNotes, quantizeResolution); return { ...ns, notes: quantized }; } //# sourceMappingURL=note_sequence_utils.js.map