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 projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const sampleRoot = path.join(projectRoot, "backend", "sample_midi"); function load(name) { return new Midi(fs.readFileSync(path.join(sampleRoot, name))); } function createVariant(source, options) { const result = new Midi(); const header = source.header.toJSON(); result.header.fromJSON({ ...header, name: options.name, tempos: [{ ticks: 0, bpm: options.tempo }], meta: [], }); const originalTempo = source.header.tempos[0]?.bpm ?? options.tempo; const timeScale = originalTempo / options.tempo; for (const trackIndex of options.trackIndexes) { const sourceTrack = source.tracks[trackIndex]; if (!sourceTrack) continue; const targetTrack = result.addTrack(); targetTrack.name = sourceTrack.name; targetTrack.channel = sourceTrack.channel; targetTrack.instrument.number = sourceTrack.instrument.number; let notes = sourceTrack.notes.filter((note) => note.time < options.endTime); if (options.leftHandRoots && trackIndex === 1) { const measureSeconds = (60 / originalTempo) * (header.timeSignatures[0]?.timeSignature[0] ?? 4); const roots = new Map(); for (const note of notes) { const measure = Math.floor(note.time / measureSeconds); const current = roots.get(measure); if (!current || note.midi < current.midi) roots.set(measure, note); } notes = [...roots.values()]; } for (const note of notes) { targetTrack.addNote({ midi: note.midi, time: note.time * timeScale, duration: options.leftHandRoots && trackIndex === 1 ? Math.max(note.duration * timeScale, 0.8) : note.duration * timeScale, velocity: note.velocity, }); } } return result; } function write(name, midi) { fs.writeFileSync(path.join(sampleRoot, name), Buffer.from(midi.toArray())); } const furElise = load("fur-elise.mid"); write( "fur-elise-beginner.mid", createVariant(furElise, { name: "엘리제를 위하여 입문 양손 연습", tempo: 60, endTime: 32, trackIndexes: [0, 1], leftHandRoots: true, }), ); write( "fur-elise-easy.mid", createVariant(furElise, { name: "엘리제를 위하여 쉬운 양손 연습", tempo: 66, endTime: 45, trackIndexes: [0, 1], leftHandRoots: true, }), ); const bachMinuet = load("minuet-bwv-anh-114.mid"); write( "minuet-bwv-anh-114-beginner.mid", createVariant(bachMinuet, { name: "미뉴에트 사장조 입문 양손 연습", tempo: 100, endTime: bachMinuet.duration + 1, trackIndexes: [0, 1], leftHandRoots: true, }), ); const beethovenMinuet = load("menuett-woo-82.mid"); write( "menuett-woo-82-easy.mid", createVariant(beethovenMinuet, { name: "미뉴에트 내림마장조 쉬운 양손 연습", tempo: 88, endTime: beethovenMinuet.duration + 1, trackIndexes: [0, 1], leftHandRoots: true, }), ); console.log("양손이 포함된 난이도별 연습 MIDI 4개를 만들었습니다.");