UNPKG

kar-to-ass

Version:

Converts midi/kar karaoke files to ass format

128 lines 4.9 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseKar = void 0; const midi_file_1 = require("midi-file"); const utils_1 = require("./utils"); const debug_1 = __importDefault(require("debug")); const debugTimebase = debug_1.default('timebase'); const debugTracks = debug_1.default('tracks'); const debugLyrics = debug_1.default('lyrics'); const isEventLyrics = event => (['text', 'lyrics'].includes(event.type) && !event.text.startsWith('@')); const DEFAULT_EOL = 200; function parseKar(fileContent) { const midi = midi_file_1.parseMidi(fileContent); const timebase = getTimebase(midi); const rawTracks = getTracks(midi); rawTracks.forEach(track => applyTimebase(track, timebase)); return { timebase: timebase, tracks: rawTracks, }; } exports.parseKar = parseKar; function applyTimebase(track, timebase) { const tmpTimebase = utils_1.clone(timebase); let time = tmpTimebase.shift(); let baseUs = 0; for (const event of track) { while (event.ticks >= time.tickEnd) { baseUs += time.microSecondsPerTick * (time.tickEnd - time.tickStart); time = tmpTimebase.shift(); } event.timeUs = baseUs + time.microSecondsPerTick * (event.ticks - time.tickStart); } debugTracks('Applied timebase to ', track); } function getTimebase(midi) { const tempoTrack = midi.tracks.filter(track => track.some(event => event.type == 'setTempo'))[0]; debugTimebase('Tempo track is ', tempoTrack); const timebase = []; let curr = 0; const tempoEvents = tempoTrack.filter(e => e.type === 'setTempo'); for (let i = 0; i < tempoEvents.length; i++) { const tempoEvent = tempoEvents[i]; const duration = (i + 1 < tempoEvents.length) ? tempoEvents[i + 1].deltaTime : Number.MAX_SAFE_INTEGER; timebase.push({ tickStart: curr, tickEnd: curr + duration, microSecondsPerTick: tempoEvent.microsecondsPerBeat / midi.header.ticksPerBeat, }); curr += duration; debugTimebase('Added timebase event', timebase[timebase.length - 1]); } return timebase; } function getTracks(midi, defaultEOL = DEFAULT_EOL) { const response = []; debugTracks('All tracks from midi', midi.tracks); const tracksWithLyrics = midi.tracks .filter(events => events.some(isEventLyrics)); debugTracks('Filtered tracks with lyric events', tracksWithLyrics); for (const rawTrack of tracksWithLyrics) { let timeCursor = 0; let track = []; for (const rawEvent of rawTrack) { if (isEventLyrics(rawEvent)) { const cleanText = rawEvent.text.replace(/[\/\\]/g, ''); debugLyrics('Lyrics line: ', cleanText, rawEvent); if (['/', '\r', '\n', '\r\n'].includes(rawEvent.text)) { timeCursor += rawEvent.deltaTime; track.push({ ticks: timeCursor, type: 'EndOfLine', }); } else if (rawEvent.text.match(/^[\/\\]/)) { track.push({ ticks: timeCursor + defaultEOL, type: 'EndOfLine', }); timeCursor += rawEvent.deltaTime; track.push({ ticks: timeCursor, type: 'Lyrics', text: cleanText, }); } else if (rawEvent.text.match(/[\/\\]$/)) { timeCursor += rawEvent.deltaTime; track.push({ ticks: timeCursor, type: 'Lyrics', text: cleanText, }); track.push({ ticks: timeCursor + defaultEOL, type: 'EndOfLine', }); } else { timeCursor += rawEvent.deltaTime; track.push({ ticks: timeCursor, type: 'Lyrics', text: cleanText, }); } } else { timeCursor += rawEvent.deltaTime; } } if (track[track.length - 1].type === 'Lyrics') { track.push({ ticks: timeCursor + defaultEOL, type: 'EndOfLine', }); } debugTracks('Finalized track: ', track); response.push(track); } return response; } //# sourceMappingURL=kar.js.map