import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import midiPackage from '../frontend/node_modules/@tonejs/midi/build/Midi.js'; const { Midi } = midiPackage; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'backend', 'sample_midi'); const collection = JSON.parse(fs.readFileSync(path.join(root, 'practice_collection.json'), 'utf8')); if (collection.length !== 30 || new Set(collection.map((item) => item.title)).size !== 30) { throw new Error('서로 다른 제목의 연습곡 30개가 필요합니다'); } const motifs = new Set(); for (const item of collection) { const midi = new Midi(fs.readFileSync(path.join(root, item.storage_name))); const [right, left] = midi.tracks; if (midi.tracks.length !== 2 || right.notes.length < 12 || left.notes.length < 6) { throw new Error(`${item.storage_name}: 양손 음표가 부족합니다`); } if (midi.duration < 60 || midi.duration > 180) { throw new Error(`${item.storage_name}: 곡 길이가 기준을 벗어납니다`); } if (right.notes[0].time < 0.4 || right.notes[4].time < 5 || left.notes[0].time !== 0) { throw new Error(`${item.storage_name}: 도입부를 확인해야 합니다`); } if (midi.duration - right.notes.at(-1).time < 2.5 || midi.duration - left.notes.at(-1).time < 4) { throw new Error(`${item.storage_name}: 마무리 지속 시간이 부족합니다`); } const motif = right.notes.slice(4, 20).map((note) => note.midi).join('-'); if (motifs.has(motif)) throw new Error(`${item.storage_name}: 다른 곡과 선율이 같습니다`); motifs.add(motif); if (![...right.notes, ...left.notes].every((note) => note.midi >= 21 && note.midi <= 108)) { throw new Error(`${item.storage_name}: 피아노 음역을 벗어났습니다`); } console.log(`${item.storage_name}: ${midi.duration.toFixed(1)}초, 오른손 ${right.notes.length}음, 왼손 ${left.notes.length}음`); }