kar-to-ass
Version:
Converts midi/kar karaoke files to ass format
128 lines • 4.9 kB
JavaScript
"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