museaikit
Version:
A powerful music-focused AI toolkit
119 lines • 5.17 kB
JavaScript
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