@karaplay/file-coder 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BROWSER_API.md +318 -0
- package/README.md +216 -0
- package/REFACTORING_SUMMARY.txt +250 -0
- package/WORKFLOW_SUMMARY.txt +78 -0
- package/bin/ncntokar-cli.js +39 -0
- package/dist/client.d.ts +26 -0
- package/dist/client.js +74 -0
- package/dist/emk/client-decoder.d.ts +22 -0
- package/dist/emk/client-decoder.js +133 -0
- package/dist/emk/server-decode.d.ts +21 -0
- package/dist/emk/server-decode.js +123 -0
- package/dist/emk-to-kar.browser.d.ts +51 -0
- package/dist/emk-to-kar.browser.js +139 -0
- package/dist/emk-to-kar.d.ts +53 -0
- package/dist/emk-to-kar.js +210 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +64 -0
- package/dist/kar-reader.browser.d.ts +46 -0
- package/dist/kar-reader.browser.js +209 -0
- package/dist/kar-reader.d.ts +42 -0
- package/dist/kar-reader.js +197 -0
- package/dist/ncntokar.browser.d.ts +99 -0
- package/dist/ncntokar.browser.js +296 -0
- package/dist/ncntokar.d.ts +88 -0
- package/dist/ncntokar.js +340 -0
- package/examples/NextJSComponent.tsx +175 -0
- package/libs/emk/client-decoder.ts +142 -0
- package/libs/emk/server-decode.ts +133 -0
- package/libs/ncntokar.js +256 -0
- package/package.json +79 -0
- package/songs/.gitkeep +3 -0
- package/songs/cur/BPL3457.cur +0 -0
- package/songs/cur/Z2510006.cur +0 -0
- package/songs/cur/Z2510008.cur +0 -0
- package/songs/cur/Z2510136.cur +0 -0
- package/songs/cur/Z2510137.cur +0 -0
- package/songs/cur/Z2510138.cur +0 -0
- package/songs/cur/Z2510139.cur +0 -0
- package/songs/cur/Z2510140.cur +0 -0
- package/songs/cur/Z2510141.cur +0 -0
- package/songs/cur/song.cur +0 -0
- package/songs/emk/Z2510001.emk +0 -0
- package/songs/emk/Z2510002.emk +0 -0
- package/songs/emk/Z2510003.emk +0 -0
- package/songs/emk/Z2510004.emk +0 -0
- package/songs/emk/Z2510005.emk +0 -0
- package/songs/emk/Z2510006.emk +0 -0
- package/songs/kar/bpl3457.kar +0 -0
- package/songs/kar/z2510006.kar +0 -0
- package/songs/kar/z2510008.kar +0 -0
- package/songs/kar/z2510136.kar +0 -0
- package/songs/kar/z2510137.kar +0 -0
- package/songs/kar/z2510138.kar +0 -0
- package/songs/kar/z2510139.kar +0 -0
- package/songs/kar/z2510140.kar +0 -0
- package/songs/kar/z2510141.kar +0 -0
- package/songs/lyr/BPL3457.lyr +57 -0
- package/songs/lyr/Z2510006.lyr +53 -0
- package/songs/lyr/Z2510008.lyr +57 -0
- package/songs/lyr/Z2510136.lyr +54 -0
- package/songs/lyr/Z2510137.lyr +66 -0
- package/songs/lyr/Z2510138.lyr +62 -0
- package/songs/lyr/Z2510139.lyr +60 -0
- package/songs/lyr/Z2510140.lyr +41 -0
- package/songs/lyr/Z2510141.lyr +48 -0
- package/songs/lyr/song.lyr +37 -0
- package/songs/midi/BPL3457.MID +0 -0
- package/songs/midi/Z2510006.mid +0 -0
- package/songs/midi/Z2510008.mid +0 -0
- package/songs/midi/Z2510136.mid +0 -0
- package/songs/midi/Z2510137.mid +0 -0
- package/songs/midi/Z2510138.mid +0 -0
- package/songs/midi/Z2510139.mid +0 -0
- package/songs/midi/Z2510140.mid +0 -0
- package/songs/midi/Z2510141.mid +0 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Browser-compatible NCN to KAR converter
|
|
4
|
+
* Works with File API and ArrayBuffers instead of fs
|
|
5
|
+
*/
|
|
6
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
+
if (k2 === undefined) k2 = k;
|
|
8
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
+
}
|
|
12
|
+
Object.defineProperty(o, k2, desc);
|
|
13
|
+
}) : (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
o[k2] = m[k];
|
|
16
|
+
}));
|
|
17
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
+
}) : function(o, v) {
|
|
20
|
+
o["default"] = v;
|
|
21
|
+
});
|
|
22
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
+
var ownKeys = function(o) {
|
|
24
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
+
var ar = [];
|
|
26
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
+
return ar;
|
|
28
|
+
};
|
|
29
|
+
return ownKeys(o);
|
|
30
|
+
};
|
|
31
|
+
return function (mod) {
|
|
32
|
+
if (mod && mod.__esModule) return mod;
|
|
33
|
+
var result = {};
|
|
34
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
+
__setModuleDefault(result, mod);
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
})();
|
|
39
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
+
};
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.BrowserCursorReader = void 0;
|
|
44
|
+
exports.readBufferTextTIS620 = readBufferTextTIS620;
|
|
45
|
+
exports.splitLinesKeepEndings = splitLinesKeepEndings;
|
|
46
|
+
exports.trimLineEndings = trimLineEndings;
|
|
47
|
+
exports.createMetaEvent = createMetaEvent;
|
|
48
|
+
exports.createEndOfTrack = createEndOfTrack;
|
|
49
|
+
exports.parseLyricBuffer = parseLyricBuffer;
|
|
50
|
+
exports.buildKaraokeTrackBrowser = buildKaraokeTrackBrowser;
|
|
51
|
+
exports.buildMetadataTracksBrowser = buildMetadataTracksBrowser;
|
|
52
|
+
exports.convertNcnToKarBrowser = convertNcnToKarBrowser;
|
|
53
|
+
exports.fileToBuffer = fileToBuffer;
|
|
54
|
+
exports.downloadBuffer = downloadBuffer;
|
|
55
|
+
const iconv = __importStar(require("iconv-lite"));
|
|
56
|
+
const grapheme_splitter_1 = __importDefault(require("grapheme-splitter"));
|
|
57
|
+
const midi_file_1 = require("midi-file");
|
|
58
|
+
/**
|
|
59
|
+
* Reads text from buffer as TIS-620 (Thai encoding)
|
|
60
|
+
*/
|
|
61
|
+
function readBufferTextTIS620(buffer) {
|
|
62
|
+
return iconv.decode(buffer, "tis-620");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Splits text into lines while keeping line endings
|
|
66
|
+
*/
|
|
67
|
+
function splitLinesKeepEndings(text) {
|
|
68
|
+
return text.match(/[^\n]*\n|[^\n]+$/g) || [];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Removes line endings from a string
|
|
72
|
+
*/
|
|
73
|
+
function trimLineEndings(s) {
|
|
74
|
+
return s.replace(/[\r\n]+$/g, "");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Creates a MIDI meta event with TIS-620 encoding for Thai text
|
|
78
|
+
*/
|
|
79
|
+
function createMetaEvent(subtype, deltaTime, text) {
|
|
80
|
+
const tis620Bytes = iconv.encode(text, 'tis-620');
|
|
81
|
+
const metaTypeMap = {
|
|
82
|
+
'text': 0x01,
|
|
83
|
+
'trackName': 0x03,
|
|
84
|
+
'copyrightNotice': 0x02,
|
|
85
|
+
'instrumentName': 0x04,
|
|
86
|
+
'lyrics': 0x05,
|
|
87
|
+
'marker': 0x06,
|
|
88
|
+
'cuePoint': 0x07,
|
|
89
|
+
};
|
|
90
|
+
const metatypeByte = metaTypeMap[subtype];
|
|
91
|
+
if (metatypeByte !== undefined) {
|
|
92
|
+
return {
|
|
93
|
+
deltaTime,
|
|
94
|
+
type: 'unknownMeta',
|
|
95
|
+
metatypeByte: metatypeByte,
|
|
96
|
+
data: Array.from(tis620Bytes)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
return { deltaTime, type: subtype, text };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Creates an end-of-track MIDI event
|
|
103
|
+
*/
|
|
104
|
+
function createEndOfTrack(deltaTime = 0) {
|
|
105
|
+
return { deltaTime, type: "endOfTrack" };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Simple cursor reader for browser
|
|
109
|
+
*/
|
|
110
|
+
class BrowserCursorReader {
|
|
111
|
+
constructor(buf) {
|
|
112
|
+
this.buf = buf;
|
|
113
|
+
this.off = 0;
|
|
114
|
+
}
|
|
115
|
+
readU16LE() {
|
|
116
|
+
if (this.off + 2 > this.buf.length)
|
|
117
|
+
return null;
|
|
118
|
+
const v = this.buf.readUInt16LE(this.off);
|
|
119
|
+
this.off += 2;
|
|
120
|
+
return v;
|
|
121
|
+
}
|
|
122
|
+
readU8() {
|
|
123
|
+
if (this.off + 1 > this.buf.length)
|
|
124
|
+
return null;
|
|
125
|
+
const v = this.buf[this.off];
|
|
126
|
+
this.off += 1;
|
|
127
|
+
return v;
|
|
128
|
+
}
|
|
129
|
+
remaining() {
|
|
130
|
+
return this.buf.length - this.off;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
exports.BrowserCursorReader = BrowserCursorReader;
|
|
134
|
+
/**
|
|
135
|
+
* Parses lyric buffer to extract metadata (title, artist, lyrics)
|
|
136
|
+
*/
|
|
137
|
+
function parseLyricBuffer(lyricBuffer) {
|
|
138
|
+
const lyricText = readBufferTextTIS620(lyricBuffer);
|
|
139
|
+
const linesWithEndings = splitLinesKeepEndings(lyricText);
|
|
140
|
+
if (linesWithEndings.length < 2) {
|
|
141
|
+
throw new Error("Lyric file has too few lines (need title and artist).");
|
|
142
|
+
}
|
|
143
|
+
const songTitle = trimLineEndings(linesWithEndings[0] ?? "");
|
|
144
|
+
const artistName = trimLineEndings(linesWithEndings[1] ?? "");
|
|
145
|
+
let fullLyric = "";
|
|
146
|
+
const lyricLines = [];
|
|
147
|
+
for (let i = 4; i < linesWithEndings.length; i++) {
|
|
148
|
+
fullLyric += linesWithEndings[i];
|
|
149
|
+
const trimmed = trimLineEndings(linesWithEndings[i]);
|
|
150
|
+
if (trimmed && trimmed.length > 0) {
|
|
151
|
+
lyricLines.push(trimmed);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
title: songTitle,
|
|
156
|
+
artist: artistName,
|
|
157
|
+
fullLyric,
|
|
158
|
+
lines: lyricLines
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Builds karaoke track with timing information (browser version)
|
|
163
|
+
*/
|
|
164
|
+
function buildKaraokeTrackBrowser(metadata, cursorBuffer, ticksPerBeat) {
|
|
165
|
+
const karaokeTrack = [];
|
|
166
|
+
const warnings = [];
|
|
167
|
+
karaokeTrack.push(createMetaEvent("trackName", 0, "Words"));
|
|
168
|
+
karaokeTrack.push(createMetaEvent("text", 0, "@T" + metadata.title));
|
|
169
|
+
karaokeTrack.push(createMetaEvent("text", 0, "@T" + metadata.artist));
|
|
170
|
+
const cursor = new BrowserCursorReader(cursorBuffer);
|
|
171
|
+
const splitter = new grapheme_splitter_1.default();
|
|
172
|
+
let previousAbsoluteTimestamp = 0;
|
|
173
|
+
const lyricsWithEndings = splitLinesKeepEndings(metadata.fullLyric);
|
|
174
|
+
for (let i = 4; i < lyricsWithEndings.length; i++) {
|
|
175
|
+
const trimmed = trimLineEndings(lyricsWithEndings[i]);
|
|
176
|
+
if (!trimmed || trimmed.length === 0)
|
|
177
|
+
continue;
|
|
178
|
+
const lineForTiming = "/" + trimmed;
|
|
179
|
+
const graphemes = splitter.splitGraphemes(lineForTiming);
|
|
180
|
+
for (const g of graphemes) {
|
|
181
|
+
let absoluteTimestamp = previousAbsoluteTimestamp;
|
|
182
|
+
for (const _cp of Array.from(g)) {
|
|
183
|
+
const v = cursor.readU16LE();
|
|
184
|
+
if (v === null) {
|
|
185
|
+
warnings.push("ran out of timing info; reusing previous timestamp");
|
|
186
|
+
absoluteTimestamp = previousAbsoluteTimestamp;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
absoluteTimestamp = v;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
absoluteTimestamp = Math.floor(absoluteTimestamp * (ticksPerBeat / 24));
|
|
193
|
+
if (absoluteTimestamp < previousAbsoluteTimestamp) {
|
|
194
|
+
warnings.push("timestamp out of order - clamping");
|
|
195
|
+
absoluteTimestamp = previousAbsoluteTimestamp;
|
|
196
|
+
}
|
|
197
|
+
const relativeTimestamp = absoluteTimestamp - previousAbsoluteTimestamp;
|
|
198
|
+
karaokeTrack.push(createMetaEvent("text", relativeTimestamp, g));
|
|
199
|
+
previousAbsoluteTimestamp = absoluteTimestamp;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const terminator = cursor.readU8();
|
|
203
|
+
if (terminator === null) {
|
|
204
|
+
warnings.push("EOF without terminator while reading timing info");
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const leftover = cursor.remaining();
|
|
208
|
+
if (terminator !== 0xff || leftover > 0) {
|
|
209
|
+
warnings.push(`${leftover} bytes (${leftover / 2} values) of unused timing info, or timing info ended without 0xFF terminator`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
karaokeTrack.push(createEndOfTrack(0));
|
|
213
|
+
return { track: karaokeTrack, warnings };
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Builds additional metadata tracks (Lyric, Artist, SongTitle)
|
|
217
|
+
*/
|
|
218
|
+
function buildMetadataTracksBrowser(metadata) {
|
|
219
|
+
const lyricTrack = [
|
|
220
|
+
createMetaEvent("trackName", 0, "Lyric"),
|
|
221
|
+
createMetaEvent("text", 0, metadata.fullLyric),
|
|
222
|
+
createEndOfTrack(0),
|
|
223
|
+
];
|
|
224
|
+
const artistTrack = [
|
|
225
|
+
createMetaEvent("trackName", 0, "Artist"),
|
|
226
|
+
createMetaEvent("text", 0, metadata.artist),
|
|
227
|
+
createEndOfTrack(0),
|
|
228
|
+
];
|
|
229
|
+
const titleTrack = [
|
|
230
|
+
createMetaEvent("trackName", 0, "SongTitle"),
|
|
231
|
+
createMetaEvent("text", 0, metadata.title),
|
|
232
|
+
createEndOfTrack(0),
|
|
233
|
+
];
|
|
234
|
+
return { lyricTrack, artistTrack, titleTrack };
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Browser-compatible NCN to KAR conversion
|
|
238
|
+
* Works with buffers instead of file paths
|
|
239
|
+
*/
|
|
240
|
+
function convertNcnToKarBrowser(options) {
|
|
241
|
+
const warnings = [];
|
|
242
|
+
try {
|
|
243
|
+
// Parse MIDI
|
|
244
|
+
const midi = (0, midi_file_1.parseMidi)(options.midiBuffer);
|
|
245
|
+
const ticksPerBeat = midi.header && midi.header.ticksPerBeat;
|
|
246
|
+
if (!ticksPerBeat) {
|
|
247
|
+
throw new Error("Only ticks-per-beat MIDI timing is supported (SMPTE timing not supported).");
|
|
248
|
+
}
|
|
249
|
+
// Parse lyric buffer
|
|
250
|
+
const metadata = parseLyricBuffer(options.lyricBuffer);
|
|
251
|
+
// Build karaoke track
|
|
252
|
+
const { track: karaokeTrack, warnings: karaokeWarnings } = buildKaraokeTrackBrowser(metadata, options.cursorBuffer, ticksPerBeat);
|
|
253
|
+
warnings.push(...karaokeWarnings);
|
|
254
|
+
// Build metadata tracks
|
|
255
|
+
const { lyricTrack, artistTrack, titleTrack } = buildMetadataTracksBrowser(metadata);
|
|
256
|
+
// Insert tracks as second track (index 1)
|
|
257
|
+
const originalTracks = midi.tracks || [];
|
|
258
|
+
const newTracks = originalTracks.slice();
|
|
259
|
+
newTracks.splice(1, 0, karaokeTrack, lyricTrack, artistTrack, titleTrack);
|
|
260
|
+
midi.tracks = newTracks;
|
|
261
|
+
// Write output
|
|
262
|
+
const outBytes = (0, midi_file_1.writeMidi)(midi);
|
|
263
|
+
const karBuffer = Buffer.from(outBytes);
|
|
264
|
+
return {
|
|
265
|
+
success: true,
|
|
266
|
+
karBuffer,
|
|
267
|
+
fileName: options.outputFileName || 'output.kar',
|
|
268
|
+
metadata,
|
|
269
|
+
warnings
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
throw new Error(`Browser NCN to KAR conversion failed: ${error.message}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Helper to convert File to Buffer for browser use
|
|
278
|
+
*/
|
|
279
|
+
async function fileToBuffer(file) {
|
|
280
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
281
|
+
return Buffer.from(arrayBuffer);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Helper to download buffer as file in browser
|
|
285
|
+
*/
|
|
286
|
+
function downloadBuffer(buffer, fileName, mimeType = 'audio/midi') {
|
|
287
|
+
const blob = new Blob([new Uint8Array(buffer)], { type: mimeType });
|
|
288
|
+
const url = URL.createObjectURL(blob);
|
|
289
|
+
const a = document.createElement('a');
|
|
290
|
+
a.href = url;
|
|
291
|
+
a.download = fileName;
|
|
292
|
+
document.body.appendChild(a);
|
|
293
|
+
a.click();
|
|
294
|
+
document.body.removeChild(a);
|
|
295
|
+
URL.revokeObjectURL(url);
|
|
296
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NCN (.mid + .lyr + .cur) -> .kar (MIDI with embedded karaoke/lyric/title/artist tracks)
|
|
3
|
+
* - .lyr decoded as TIS-620 (Thai encoding)
|
|
4
|
+
* - meta events use midi-file format: { deltaTime, type: <subtype>, text }
|
|
5
|
+
*/
|
|
6
|
+
export interface ConversionOptions {
|
|
7
|
+
inputMidi: string;
|
|
8
|
+
inputLyr: string;
|
|
9
|
+
inputCur: string;
|
|
10
|
+
outputKar: string;
|
|
11
|
+
appendTitles?: boolean;
|
|
12
|
+
titlesFile?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface SongMetadata {
|
|
15
|
+
title: string;
|
|
16
|
+
artist: string;
|
|
17
|
+
fullLyric: string;
|
|
18
|
+
lines: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare class CursorReader {
|
|
21
|
+
private buf;
|
|
22
|
+
private off;
|
|
23
|
+
constructor(buf: Buffer);
|
|
24
|
+
readU16LE(): number | null;
|
|
25
|
+
readU8(): number | null;
|
|
26
|
+
remaining(): number;
|
|
27
|
+
eof(): boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Reads a file and decodes it as TIS-620 (Thai encoding)
|
|
31
|
+
*/
|
|
32
|
+
export declare function readFileTextTIS620(filePath: string): string;
|
|
33
|
+
/**
|
|
34
|
+
* Splits text into lines while keeping line endings
|
|
35
|
+
*/
|
|
36
|
+
export declare function splitLinesKeepEndings(text: string): string[];
|
|
37
|
+
/**
|
|
38
|
+
* Removes line endings from a string
|
|
39
|
+
*/
|
|
40
|
+
export declare function trimLineEndings(s: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a MIDI meta event with TIS-620 encoding for Thai text
|
|
43
|
+
*/
|
|
44
|
+
export declare function metaEvent(subtype: string, deltaTime: number, text: string): any;
|
|
45
|
+
/**
|
|
46
|
+
* Creates an end-of-track MIDI event
|
|
47
|
+
*/
|
|
48
|
+
export declare function endOfTrack(deltaTime?: number): any;
|
|
49
|
+
/**
|
|
50
|
+
* Validates that a file exists and is readable
|
|
51
|
+
*/
|
|
52
|
+
export declare function ensureReadableFile(filePath: string): void;
|
|
53
|
+
/**
|
|
54
|
+
* Ensures output file doesn't exist and creates directory if needed
|
|
55
|
+
*/
|
|
56
|
+
export declare function ensureOutputDoesNotExist(filePath: string, createDir?: boolean): void;
|
|
57
|
+
/**
|
|
58
|
+
* Parses lyric file and extracts metadata (title, artist, lyrics)
|
|
59
|
+
*/
|
|
60
|
+
export declare function parseLyricFile(lyricFilePath: string): SongMetadata;
|
|
61
|
+
/**
|
|
62
|
+
* Builds karaoke track with timing information
|
|
63
|
+
*/
|
|
64
|
+
export declare function buildKaraokeTrack(metadata: SongMetadata, cursorBuffer: Buffer, ticksPerBeat: number): {
|
|
65
|
+
track: any[];
|
|
66
|
+
warnings: string[];
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Builds additional metadata tracks (Lyric, Artist, SongTitle)
|
|
70
|
+
*/
|
|
71
|
+
export declare function buildMetadataTracks(metadata: SongMetadata): {
|
|
72
|
+
lyricTrack: any[];
|
|
73
|
+
artistTrack: any[];
|
|
74
|
+
titleTrack: any[];
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Main conversion function: converts NCN files (.mid + .lyr + .cur) to .kar format
|
|
78
|
+
*/
|
|
79
|
+
export declare function convertNcnToKar(options: ConversionOptions): {
|
|
80
|
+
success: boolean;
|
|
81
|
+
outputFile: string;
|
|
82
|
+
warnings: string[];
|
|
83
|
+
metadata: SongMetadata;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* CLI-compatible function that uses hard-coded paths (for backward compatibility)
|
|
87
|
+
*/
|
|
88
|
+
export declare function convertWithDefaults(inputMidi: string, inputLyr: string, inputCur: string, outputKar: string): void;
|