60 lines
2.2 KiB
JavaScript
60 lines
2.2 KiB
JavaScript
/* Compact note sequences and practice ranges, shared by browser and server. */
|
|
(function (root) {
|
|
'use strict';
|
|
const MAX_NOTES = 30000;
|
|
function normalizeNotes(input) {
|
|
if (!Array.isArray(input) || input.length > MAX_NOTES)
|
|
throw new Error('Invalid or oversized note sequence');
|
|
return input
|
|
.map((note) => {
|
|
const pitch = Number(note.pitch),
|
|
start = Number(note.start),
|
|
end = Number(note.end),
|
|
velocity = Number(note.velocity);
|
|
if (
|
|
!Number.isInteger(pitch) ||
|
|
pitch < 21 ||
|
|
pitch > 108 ||
|
|
!Number.isFinite(start) ||
|
|
start < 0 ||
|
|
!Number.isFinite(end) ||
|
|
end <= start ||
|
|
end > 3600 ||
|
|
!Number.isFinite(velocity)
|
|
)
|
|
throw new Error('Invalid piano note');
|
|
return {
|
|
pitch,
|
|
start: Math.round(start * 1000) / 1000,
|
|
end: Math.max(Math.round(end * 1000) / 1000, Math.round(start * 1000) / 1000 + 0.001),
|
|
velocity: Math.max(1, Math.min(127, Math.round(velocity))),
|
|
hand: ['left', 'right'].includes(note.hand) ? note.hand : pitch < 60 ? 'left' : 'right',
|
|
};
|
|
})
|
|
.sort((a, b) => a.start - b.start || a.pitch - b.pitch);
|
|
}
|
|
function fromBasicPitch(notes) {
|
|
return normalizeNotes(
|
|
notes
|
|
.filter((n) => n.pitchMidi >= 21 && n.pitchMidi <= 108)
|
|
.map((n) => ({
|
|
pitch: n.pitchMidi,
|
|
start: n.startTimeSeconds,
|
|
end: n.startTimeSeconds + n.durationSeconds,
|
|
velocity: n.amplitude * 127,
|
|
})),
|
|
);
|
|
}
|
|
const activeNotes = (notes, time) => notes.filter((n) => n.start <= time && n.end > time);
|
|
function practiceRange(time, duration) {
|
|
const start = Math.max(0, Number(time) || 0),
|
|
end = Math.min(start + 8, Number(duration) || start + 8);
|
|
return end > start
|
|
? { start: Math.round(start * 1000) / 1000, end: Math.round(end * 1000) / 1000 }
|
|
: null;
|
|
}
|
|
const api = { normalizeNotes, fromBasicPitch, activeNotes, practiceRange, MAX_NOTES };
|
|
if (typeof module !== 'undefined') module.exports = api;
|
|
else root.PianoCore = api;
|
|
})(typeof window !== 'undefined' ? window : globalThis);
|