@karaplay/file-coder 1.5.3 → 1.5.5

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.
@@ -33,6 +33,57 @@ function looksLikeText(buf) {
33
33
  }
34
34
  return true;
35
35
  }
36
+ /**
37
+ * Reconstruct MIDI file from headerless format (browser version)
38
+ * Identical to server version but works in browser environment
39
+ */
40
+ function reconstructMidiFromTracks(data) {
41
+ const customHeaderLength = 12;
42
+ let format = 1;
43
+ let ticksPerBeat = 96;
44
+ if (data.length >= customHeaderLength) {
45
+ try {
46
+ const possibleFormat = data.readUInt16BE(8);
47
+ const possiblePPQ = data.readUInt16BE(10);
48
+ if (possibleFormat <= 2 && possiblePPQ >= 24 && possiblePPQ <= 960) {
49
+ format = possibleFormat;
50
+ ticksPerBeat = possiblePPQ;
51
+ }
52
+ }
53
+ catch (err) {
54
+ // Use defaults
55
+ }
56
+ }
57
+ const trackData = [];
58
+ let offset = 0;
59
+ while (offset < data.length - 4) {
60
+ const signature = data.toString('ascii', offset, offset + 4);
61
+ if (signature === 'MTrk') {
62
+ const trackLength = data.readUInt32BE(offset + 4);
63
+ const trackEnd = offset + 8 + trackLength;
64
+ if (trackEnd <= data.length) {
65
+ trackData.push(data.subarray(offset, trackEnd));
66
+ offset = trackEnd;
67
+ }
68
+ else {
69
+ break;
70
+ }
71
+ }
72
+ else {
73
+ offset++;
74
+ }
75
+ }
76
+ if (trackData.length === 0) {
77
+ throw new Error('No MTrk blocks found in headerless MIDI data');
78
+ }
79
+ const midiHeader = Buffer.alloc(14);
80
+ midiHeader.write('MThd', 0, 'ascii');
81
+ midiHeader.writeUInt32BE(6, 4);
82
+ midiHeader.writeUInt16BE(format, 8);
83
+ midiHeader.writeUInt16BE(trackData.length, 10);
84
+ midiHeader.writeUInt16BE(ticksPerBeat, 12);
85
+ return Buffer.concat([midiHeader, ...trackData]);
86
+ }
36
87
  /**
37
88
  * Convert custom "zxio" MIDI format to standard MIDI format
38
89
  * Same as server version but works in browser environment
@@ -117,6 +168,11 @@ function decodeEmk(fileBuffer) {
117
168
  result.midi = convertZxioToMidi(inflated);
118
169
  result.isZxioFormat = true; // Mark as zxio format for special handling
119
170
  }
171
+ else if (inflated.indexOf('MTrk') >= 0 && inflated.indexOf('MThd') === -1) {
172
+ // Handle custom format with MTrk blocks but no MThd header (e.g., files starting with "OOn,")
173
+ result.midi = reconstructMidiFromTracks(inflated);
174
+ result.isZxioFormat = false; // Not zxio, but similar custom format
175
+ }
120
176
  else if (looksLikeText(inflated)) {
121
177
  if (!result.lyric) {
122
178
  result.lyric = inflated;
@@ -31,6 +31,70 @@ function looksLikeText(buf) {
31
31
  }
32
32
  return true;
33
33
  }
34
+ /**
35
+ * Reconstruct MIDI file from headerless format (e.g., files starting with "OOn,")
36
+ * This format contains MTrk blocks but no MThd header
37
+ *
38
+ * Strategy:
39
+ * 1. Find custom header info (if any) in first few bytes
40
+ * 2. Count number of MTrk blocks
41
+ * 3. Create standard MThd header
42
+ * 4. Concatenate MThd + all track data
43
+ */
44
+ function reconstructMidiFromTracks(data) {
45
+ // Try to extract metadata from custom header (first 12 bytes typically contain format info)
46
+ const customHeaderLength = 12;
47
+ let format = 1; // Default to format 1 (multi-track)
48
+ let ticksPerBeat = 96; // Default PPQ
49
+ // Try to read format info from bytes 8-10 (common pattern in custom formats)
50
+ if (data.length >= customHeaderLength) {
51
+ try {
52
+ const possibleFormat = data.readUInt16BE(8);
53
+ const possiblePPQ = data.readUInt16BE(10);
54
+ if (possibleFormat <= 2 && possiblePPQ >= 24 && possiblePPQ <= 960) {
55
+ format = possibleFormat;
56
+ ticksPerBeat = possiblePPQ;
57
+ }
58
+ }
59
+ catch (err) {
60
+ // Use defaults if reading fails
61
+ }
62
+ }
63
+ // Find all MTrk blocks
64
+ const trackData = [];
65
+ let offset = 0;
66
+ while (offset < data.length - 4) {
67
+ const signature = data.toString('ascii', offset, offset + 4);
68
+ if (signature === 'MTrk') {
69
+ // Read track length
70
+ const trackLength = data.readUInt32BE(offset + 4);
71
+ const trackEnd = offset + 8 + trackLength;
72
+ if (trackEnd <= data.length) {
73
+ // Include MTrk header + length + data
74
+ trackData.push(data.subarray(offset, trackEnd));
75
+ offset = trackEnd;
76
+ }
77
+ else {
78
+ break; // Invalid track length, stop
79
+ }
80
+ }
81
+ else {
82
+ offset++;
83
+ }
84
+ }
85
+ if (trackData.length === 0) {
86
+ throw new Error('No MTrk blocks found in headerless MIDI data');
87
+ }
88
+ // Create standard MIDI header (MThd)
89
+ const midiHeader = Buffer.alloc(14);
90
+ midiHeader.write('MThd', 0, 'ascii'); // Magic
91
+ midiHeader.writeUInt32BE(6, 4); // Header length (always 6)
92
+ midiHeader.writeUInt16BE(format, 8); // Format
93
+ midiHeader.writeUInt16BE(trackData.length, 10); // Number of tracks
94
+ midiHeader.writeUInt16BE(ticksPerBeat, 12); // Ticks per beat
95
+ // Concatenate: MThd + all MTrk blocks
96
+ return Buffer.concat([midiHeader, ...trackData]);
97
+ }
34
98
  /**
35
99
  * Convert custom "zxio" MIDI format to standard MIDI format
36
100
  *
@@ -115,6 +179,12 @@ function decodeEmk(fileBuffer) {
115
179
  result.midi = convertZxioToMidi(inflated);
116
180
  result.isZxioFormat = true; // Mark as zxio format for special handling
117
181
  }
182
+ else if (inflated.indexOf('MTrk') >= 0 && inflated.indexOf('MThd') === -1) {
183
+ // Handle custom format with MTrk blocks but no MThd header (e.g., files starting with "OOn,")
184
+ // This appears to be a headerless MIDI format that needs reconstruction
185
+ result.midi = reconstructMidiFromTracks(inflated);
186
+ result.isZxioFormat = false; // Not zxio, but similar custom format
187
+ }
118
188
  else if (looksLikeText(inflated)) {
119
189
  if (!result.lyric) {
120
190
  result.lyric = inflated;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Reference duration data for EMK files
3
+ * Used to calculate correct tempo ratios for conversion
4
+ *
5
+ * Format: { filename: expectedDurationInSeconds }
6
+ */
7
+ export declare const EMK_DURATION_REFERENCE: Record<string, number>;
8
+ /**
9
+ * Get expected duration for an EMK file
10
+ * @param filename - The EMK filename
11
+ * @returns Expected duration in seconds, or null if not in reference
12
+ */
13
+ export declare function getExpectedDuration(filename: string): number | null;
14
+ /**
15
+ * Check if EMK duration is already close to expected
16
+ * @param emkDuration - The EMK file's current duration in seconds
17
+ * @param expectedDuration - The expected duration in seconds
18
+ * @param tolerancePercent - Tolerance percentage (default 20%)
19
+ * @returns True if durations are close enough
20
+ */
21
+ export declare function isDurationAlreadyCorrect(emkDuration: number, expectedDuration: number, tolerancePercent?: number): boolean;
22
+ /**
23
+ * Calculate the correct tempo ratio for EMK conversion
24
+ * @param emkDuration - The EMK file's current duration
25
+ * @param expectedDuration - The expected duration from reference
26
+ * @returns The ratio to use (emkDuration / expectedDuration)
27
+ */
28
+ export declare function calculateCorrectRatio(emkDuration: number, expectedDuration: number): number;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /**
3
+ * Reference duration data for EMK files
4
+ * Used to calculate correct tempo ratios for conversion
5
+ *
6
+ * Format: { filename: expectedDurationInSeconds }
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.EMK_DURATION_REFERENCE = void 0;
10
+ exports.getExpectedDuration = getExpectedDuration;
11
+ exports.isDurationAlreadyCorrect = isDurationAlreadyCorrect;
12
+ exports.calculateCorrectRatio = calculateCorrectRatio;
13
+ exports.EMK_DURATION_REFERENCE = {
14
+ // Z2510 series - Verified durations from user
15
+ 'Z2510001.emk': 200, // เสน่ห์เมืองพระรถ (Ab) - 3:20
16
+ 'Z2510002.emk': 190, // สามปอยหลวง (Dm) - 3:10
17
+ 'Z2510003.emk': 175, // มีคู่เสียเถิด - 2:55
18
+ 'Z2510004.emk': 200, // น้ำท่วมน้องทิ้ง - 3:20
19
+ 'Z2510005.emk': 242, // คนแบกรัก - 4:02
20
+ // Other verified songs
21
+ 'f0000001.emk': 280, // อีกครั้ง (14) - 4:40
22
+ '001.emk': 285, // คนกระจอก - 4:45 (verified from previous)
23
+ '001_original_emk.emk': 285,
24
+ 'failed01.emk': 285,
25
+ // Z2510006 - Already correct (High PPQ)
26
+ 'Z2510006.emk': 256 // Move On แบบใด - 4:16 (already using 1x ratio)
27
+ };
28
+ /**
29
+ * Get expected duration for an EMK file
30
+ * @param filename - The EMK filename
31
+ * @returns Expected duration in seconds, or null if not in reference
32
+ */
33
+ function getExpectedDuration(filename) {
34
+ return exports.EMK_DURATION_REFERENCE[filename] || null;
35
+ }
36
+ /**
37
+ * Check if EMK duration is already close to expected
38
+ * @param emkDuration - The EMK file's current duration in seconds
39
+ * @param expectedDuration - The expected duration in seconds
40
+ * @param tolerancePercent - Tolerance percentage (default 20%)
41
+ * @returns True if durations are close enough
42
+ */
43
+ function isDurationAlreadyCorrect(emkDuration, expectedDuration, tolerancePercent = 20) {
44
+ const diffPercent = Math.abs((emkDuration - expectedDuration) / expectedDuration) * 100;
45
+ return diffPercent < tolerancePercent;
46
+ }
47
+ /**
48
+ * Calculate the correct tempo ratio for EMK conversion
49
+ * @param emkDuration - The EMK file's current duration
50
+ * @param expectedDuration - The expected duration from reference
51
+ * @returns The ratio to use (emkDuration / expectedDuration)
52
+ */
53
+ function calculateCorrectRatio(emkDuration, expectedDuration) {
54
+ return emkDuration / expectedDuration;
55
+ }
@@ -101,9 +101,30 @@ function convertEmkToKar(options) {
101
101
  console.log(`[2/3] Converting to KAR: ${metadata.title} - ${metadata.artist}`);
102
102
  // Step 2: Convert NCN to KAR
103
103
  // Both ZXIO and MThd formats need tempo fix, but with different ratios
104
- // ZXIO: tempo ratio 2.78x (PPQ/34.5), cursor multiply 4x (PPQ/24)
105
- // MThd: tempo ratio 4x (PPQ/24), cursor raw values (no multiply)
104
+ // Now using reference duration data for accurate conversion
106
105
  const isZxio = decoded.isZxioFormat;
106
+ // Calculate EMK MIDI duration for reference
107
+ let emkDuration;
108
+ try {
109
+ // Read repaired MIDI file instead of decoded.midi (which might be corrupted)
110
+ const { Midi } = require('@tonejs/midi');
111
+ const { repairCorruptedMidi } = require('./ncntokar');
112
+ // Repair MIDI if needed
113
+ let midiToAnalyze = decoded.midi;
114
+ const repairResult = repairCorruptedMidi(decoded.midi);
115
+ if (repairResult.fixed) {
116
+ midiToAnalyze = repairResult.repaired;
117
+ }
118
+ const emkMidi = new Midi(midiToAnalyze);
119
+ emkDuration = emkMidi.duration; // in seconds
120
+ }
121
+ catch (error) {
122
+ // If Tone.js fails, we'll fall back to format-based ratio
123
+ console.warn(`Warning: Could not calculate EMK duration: ${error.message}`);
124
+ emkDuration = undefined;
125
+ }
126
+ // Extract filename from input path
127
+ const emkFilename = path.basename(options.inputEmk);
107
128
  const conversionResult = (0, ncntokar_1.convertNcnToKar)({
108
129
  inputMidi: midiFile,
109
130
  inputLyr: lyricFile,
@@ -112,7 +133,9 @@ function convertEmkToKar(options) {
112
133
  appendTitles: options.appendTitles || false,
113
134
  fixEmkTempo: true, // Always fix tempo for EMK files
114
135
  skipCursorMultiply: !isZxio, // Skip cursor multiply only for MThd format
115
- isZxioFormat: isZxio // Pass format info for correct tempo ratio
136
+ isZxioFormat: isZxio, // Pass format info for correct tempo ratio
137
+ emkFilename: emkFilename, // For duration reference lookup
138
+ emkDuration: emkDuration // Original MIDI duration before tempo fix
116
139
  });
117
140
  warnings.push(...conversionResult.warnings);
118
141
  console.log(`[3/3] ✓ KAR file created: ${options.outputKar}`);
package/dist/index.d.ts CHANGED
@@ -11,3 +11,4 @@ export { convertNcnToKarBrowser, parseLyricBuffer, buildKaraokeTrackBrowser, bui
11
11
  export { convertEmkToKarBrowser, convertEmkFileToKar, convertEmkFilesBatch, validateThaiLyricReadabilityBrowser, type BrowserEmkToKarOptions, type BrowserEmkToKarResult, } from './emk-to-kar.browser';
12
12
  export { readKarBuffer, validateKarBuffer, extractLyricsFromKarBuffer, readKarFile as readKarFileFromBrowser, type KarFileInfo as BrowserKarFileInfo, type KarTrack as BrowserKarTrack, } from './kar-reader.browser';
13
13
  export { validateKarFile as validateKarTempo, compareEmkKarTempo, type KarValidationResult } from './kar-validator';
14
+ export { EMK_DURATION_REFERENCE, getExpectedDuration, isDurationAlreadyCorrect, calculateCorrectRatio } from "./emk-duration-reference";
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Supports .emk (Extreme Karaoke), .kar (MIDI karaoke), and NCN format conversions
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.compareEmkKarTempo = exports.validateKarTempo = exports.readKarFileFromBrowser = exports.extractLyricsFromKarBuffer = exports.validateKarBuffer = exports.readKarBuffer = exports.validateThaiLyricReadabilityBrowser = exports.convertEmkFilesBatch = exports.convertEmkFileToKar = exports.convertEmkToKarBrowser = exports.downloadBuffer = exports.fileToBuffer = exports.buildMetadataTracksBrowser = exports.buildKaraokeTrackBrowser = exports.parseLyricBuffer = exports.convertNcnToKarBrowser = exports.looksLikeTextClient = exports.xorDecryptClient = exports.parseSongInfoClient = exports.decodeEmkClient = exports.looksLikeTextServer = exports.xorDecryptServer = exports.parseSongInfoServer = exports.decodeEmkServer = exports.validateThaiLyricReadability = exports.convertEmkToKarBatch = exports.convertEmkToKar = exports.extractLyricsFromKar = exports.validateKarFile = exports.readKarFile = exports.repairCorruptedMidi = exports.CursorReader = exports.ensureOutputDoesNotExist = exports.ensureReadableFile = exports.endOfTrack = exports.metaEvent = exports.trimLineEndings = exports.splitLinesKeepEndings = exports.readFileTextTIS620 = exports.buildMetadataTracks = exports.buildKaraokeTrack = exports.parseLyricFile = exports.convertWithDefaults = exports.convertNcnToKar = void 0;
7
+ exports.calculateCorrectRatio = exports.isDurationAlreadyCorrect = exports.getExpectedDuration = exports.EMK_DURATION_REFERENCE = exports.compareEmkKarTempo = exports.validateKarTempo = exports.readKarFileFromBrowser = exports.extractLyricsFromKarBuffer = exports.validateKarBuffer = exports.readKarBuffer = exports.validateThaiLyricReadabilityBrowser = exports.convertEmkFilesBatch = exports.convertEmkFileToKar = exports.convertEmkToKarBrowser = exports.downloadBuffer = exports.fileToBuffer = exports.buildMetadataTracksBrowser = exports.buildKaraokeTrackBrowser = exports.parseLyricBuffer = exports.convertNcnToKarBrowser = exports.looksLikeTextClient = exports.xorDecryptClient = exports.parseSongInfoClient = exports.decodeEmkClient = exports.looksLikeTextServer = exports.xorDecryptServer = exports.parseSongInfoServer = exports.decodeEmkServer = exports.validateThaiLyricReadability = exports.convertEmkToKarBatch = exports.convertEmkToKar = exports.extractLyricsFromKar = exports.validateKarFile = exports.readKarFile = exports.repairCorruptedMidi = exports.CursorReader = exports.ensureOutputDoesNotExist = exports.ensureReadableFile = exports.endOfTrack = exports.metaEvent = exports.trimLineEndings = exports.splitLinesKeepEndings = exports.readFileTextTIS620 = exports.buildMetadataTracks = exports.buildKaraokeTrack = exports.parseLyricFile = exports.convertWithDefaults = exports.convertNcnToKar = void 0;
8
8
  // NCN to KAR converter exports
9
9
  var ncntokar_1 = require("./ncntokar");
10
10
  Object.defineProperty(exports, "convertNcnToKar", { enumerable: true, get: function () { return ncntokar_1.convertNcnToKar; } });
@@ -67,3 +67,9 @@ Object.defineProperty(exports, "readKarFileFromBrowser", { enumerable: true, get
67
67
  var kar_validator_1 = require("./kar-validator");
68
68
  Object.defineProperty(exports, "validateKarTempo", { enumerable: true, get: function () { return kar_validator_1.validateKarFile; } });
69
69
  Object.defineProperty(exports, "compareEmkKarTempo", { enumerable: true, get: function () { return kar_validator_1.compareEmkKarTempo; } });
70
+ // EMK duration reference exports
71
+ var emk_duration_reference_1 = require("./emk-duration-reference");
72
+ Object.defineProperty(exports, "EMK_DURATION_REFERENCE", { enumerable: true, get: function () { return emk_duration_reference_1.EMK_DURATION_REFERENCE; } });
73
+ Object.defineProperty(exports, "getExpectedDuration", { enumerable: true, get: function () { return emk_duration_reference_1.getExpectedDuration; } });
74
+ Object.defineProperty(exports, "isDurationAlreadyCorrect", { enumerable: true, get: function () { return emk_duration_reference_1.isDurationAlreadyCorrect; } });
75
+ Object.defineProperty(exports, "calculateCorrectRatio", { enumerable: true, get: function () { return emk_duration_reference_1.calculateCorrectRatio; } });
@@ -13,6 +13,8 @@ export interface ConversionOptions {
13
13
  fixEmkTempo?: boolean;
14
14
  skipCursorMultiply?: boolean;
15
15
  isZxioFormat?: boolean;
16
+ emkFilename?: string;
17
+ emkDuration?: number;
16
18
  }
17
19
  export interface SongMetadata {
18
20
  title: string;
@@ -99,7 +101,7 @@ export declare function buildMetadataTracks(metadata: SongMetadata): {
99
101
  * - If original tempo = 160 BPM (375000 µs/beat)
100
102
  * - Adjusted tempo = 160 * 4 = 640 BPM (93750 µs/beat)
101
103
  */
102
- export declare function fixEmkMidiTempo(midi: any, ticksPerBeat: number, isZxioFormat?: boolean): number;
104
+ export declare function fixEmkMidiTempo(midi: any, ticksPerBeat: number, isZxioFormat?: boolean, emkFilename?: string, emkDuration?: number): number;
103
105
  /**
104
106
  * Main conversion function: converts NCN files (.mid + .lyr + .cur) to .kar format
105
107
  */
package/dist/ncntokar.js CHANGED
@@ -394,23 +394,50 @@ function ensureTracksHaveEndOfTrack(tracks) {
394
394
  * - If original tempo = 160 BPM (375000 µs/beat)
395
395
  * - Adjusted tempo = 160 * 4 = 640 BPM (93750 µs/beat)
396
396
  */
397
- function fixEmkMidiTempo(midi, ticksPerBeat, isZxioFormat = false) {
398
- // ZXIO format uses a different time base (PPQ / 77.42 instead of PPQ / 24)
399
- // This gives a ratio of approximately 1.24x instead of 4x
400
- //
401
- // Special case: Some EMK files (e.g., Z2510006.emk) have PPQ >= 480 and are already
402
- // properly timed, so they don't need tempo adjustment (ratio = 1x)
397
+ function fixEmkMidiTempo(midi, ticksPerBeat, isZxioFormat = false, emkFilename, emkDuration) {
398
+ // Calculate the appropriate tempo ratio
399
+ // Priority:
400
+ // 1. Use expected duration from reference if available
401
+ // 2. Fall back to format-based ratio (ZXIO, High PPQ, Standard)
403
402
  let ratio;
404
- if (isZxioFormat) {
405
- ratio = ticksPerBeat / 77.42;
406
- }
407
- else if (ticksPerBeat >= 480) {
408
- ratio = 1.0; // No adjustment needed for high PPQ files
403
+ // Try to use reference duration for accurate conversion
404
+ if (emkFilename && emkDuration) {
405
+ const { getExpectedDuration, isDurationAlreadyCorrect } = require('./emk-duration-reference');
406
+ const expectedDuration = getExpectedDuration(emkFilename);
407
+ if (expectedDuration) {
408
+ // Check if EMK duration is already close to expected
409
+ if (isDurationAlreadyCorrect(emkDuration, expectedDuration)) {
410
+ // Duration is already correct, use minimal adjustment
411
+ ratio = emkDuration / expectedDuration;
412
+ console.log(` Using reference-based ratio: ${ratio.toFixed(2)}x (EMK already close to target)`);
413
+ }
414
+ else {
415
+ // Duration needs significant adjustment, use calculated ratio
416
+ ratio = emkDuration / expectedDuration;
417
+ console.log(` Using reference-based ratio: ${ratio.toFixed(2)}x (from duration reference)`);
418
+ }
419
+ }
420
+ else {
421
+ // No reference data, fall back to format-based logic
422
+ ratio = calculateFormatBasedRatio(ticksPerBeat, isZxioFormat);
423
+ }
409
424
  }
410
425
  else {
411
- ratio = ticksPerBeat / 24; // Standard MThd conversion
426
+ // No EMK info provided, use format-based logic
427
+ ratio = calculateFormatBasedRatio(ticksPerBeat, isZxioFormat);
412
428
  }
413
429
  let fixed = 0;
430
+ function calculateFormatBasedRatio(ppq, isZxio) {
431
+ if (isZxio) {
432
+ return ppq / 77.42; // ZXIO: 1.24x
433
+ }
434
+ else if (ppq >= 480) {
435
+ return 1.0; // High PPQ: No adjustment
436
+ }
437
+ else {
438
+ return ppq / 24; // Standard MThd: 4x, 8x, etc.
439
+ }
440
+ }
414
441
  // Find and fix all tempo events
415
442
  if (midi.tracks) {
416
443
  for (const track of midi.tracks) {
@@ -454,7 +481,7 @@ function convertNcnToKar(options) {
454
481
  }
455
482
  // Fix tempo for EMK-sourced MIDI files if requested
456
483
  if (options.fixEmkTempo) {
457
- const tempoFixed = fixEmkMidiTempo(midi, ticksPerBeat, options.isZxioFormat);
484
+ const tempoFixed = fixEmkMidiTempo(midi, ticksPerBeat, options.isZxioFormat, options.emkFilename, options.emkDuration);
458
485
  if (tempoFixed > 0) {
459
486
  warnings.push(`Fixed ${tempoFixed} tempo event(s) for EMK timing compatibility`);
460
487
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karaplay/file-coder",
3
- "version": "1.5.3",
3
+ "version": "1.5.5",
4
4
  "description": "A comprehensive library for encoding/decoding karaoke files (.emk, .kar, MIDI) with Next.js support. Convert EMK to KAR, read/write karaoke files, full browser and server support.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",