/* 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);