@karaplay/file-coder 1.4.8 โ 1.5.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/DEMO_ENHANCED.md +168 -0
- package/DEMO_FIXED.md +57 -0
- package/DEMO_GUIDE.md +204 -0
- package/DEMO_README.md +193 -0
- package/DEMO_WORKING.md +157 -0
- package/RELEASE_v1.4.9.md +144 -0
- package/RELEASE_v1.5.0.md +91 -0
- package/WHY_DURATION_DECREASES.md +176 -0
- package/analyze-cursor-pattern.js +131 -0
- package/analyze-emk-cursor.js +169 -0
- package/analyze-emk-simple.js +124 -0
- package/analyze-tempo-duration.js +243 -0
- package/calculate-correct-ratio.js +97 -0
- package/check-real-duration.js +69 -0
- package/compare-kar-lyrics-timing.js +110 -0
- package/demo-client.html +722 -0
- package/demo-server.js +184 -0
- package/demo-simple.html +712 -0
- package/dist/emk-to-kar.browser.js +7 -4
- package/dist/emk-to-kar.js +7 -6
- package/dist/ncntokar.browser.d.ts +2 -1
- package/dist/ncntokar.browser.js +5 -3
- package/dist/ncntokar.d.ts +2 -1
- package/dist/ncntokar.js +6 -4
- package/find-zxio-tempo-ratio.js +118 -0
- package/match-cursor-to-lyrics.js +118 -0
- package/package.json +6 -2
- package/songs/emk/001_original_emk.emk +0 -0
- package/songs/fix/001_kar_convert_needtofix_tempo_fast.kar +0 -0
- package/songs/fix/001_original_emk.emk +0 -0
- package/temp/test_output.kar +0 -0
- package/test-all-emk-durations.js +109 -0
- package/test-all-emk-final.js +97 -0
- package/test-convert-001.js +130 -0
- package/analyze-failed-emk.js +0 -156
- package/check-gr.js +0 -19
- package/debug-emk-blocks.js +0 -123
- package/debug-midi-source.js +0 -108
- package/debug-tempo.js +0 -135
- package/debug-timing.js +0 -159
- package/find-midi-in-block.js +0 -96
- package/fix-tempo.js +0 -155
- package/kar-diagnostic.js +0 -158
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const { Midi } = require('@tonejs/midi');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
console.log('='.repeat(80));
|
|
6
|
+
console.log('TEST: Converting 001_original_emk.emk to KAR');
|
|
7
|
+
console.log('='.repeat(80));
|
|
8
|
+
console.log('');
|
|
9
|
+
|
|
10
|
+
// Target duration
|
|
11
|
+
const targetMin = 4 * 60 + 42; // 4:42 = 282 seconds
|
|
12
|
+
const targetMax = 4 * 60 + 45; // 4:45 = 285 seconds
|
|
13
|
+
|
|
14
|
+
console.log('๐ฏ Target Duration: 4:42 - 4:45 (282-285 seconds)');
|
|
15
|
+
console.log('');
|
|
16
|
+
|
|
17
|
+
// Copy file to temp for processing
|
|
18
|
+
const inputPath = './songs/emk/001_original_emk.emk';
|
|
19
|
+
const tempInput = './temp/test_input.emk';
|
|
20
|
+
const tempOutput = './temp/test_output.kar';
|
|
21
|
+
|
|
22
|
+
// Ensure temp dir exists
|
|
23
|
+
if (!fs.existsSync('./temp')) {
|
|
24
|
+
fs.mkdirSync('./temp');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Copy input
|
|
28
|
+
fs.copyFileSync(inputPath, tempInput);
|
|
29
|
+
|
|
30
|
+
console.log('๐ Converting: songs/emk/001_original_emk.emk');
|
|
31
|
+
console.log('');
|
|
32
|
+
|
|
33
|
+
// Use the file-based API
|
|
34
|
+
const { convertEmkToKar } = require('./dist/index.js');
|
|
35
|
+
|
|
36
|
+
const result = convertEmkToKar({
|
|
37
|
+
inputEmk: tempInput,
|
|
38
|
+
outputKar: tempOutput,
|
|
39
|
+
keepIntermediateFiles: false
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!result.success) {
|
|
43
|
+
console.log('โ Conversion failed!');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log('โ
Conversion successful!');
|
|
48
|
+
console.log('');
|
|
49
|
+
|
|
50
|
+
// Analyze result
|
|
51
|
+
const karBuffer = fs.readFileSync(tempOutput);
|
|
52
|
+
const karMidi = new Midi(karBuffer);
|
|
53
|
+
|
|
54
|
+
console.log('๐ Converted KAR Info:');
|
|
55
|
+
console.log(' PPQ:', karMidi.header.ppq);
|
|
56
|
+
console.log(' Tempo:', karMidi.header.tempos[0].bpm.toFixed(2), 'BPM');
|
|
57
|
+
console.log(' Duration:', karMidi.duration.toFixed(2), 'seconds');
|
|
58
|
+
console.log(' Duration:', Math.floor(karMidi.duration / 60) + ':' + String(Math.floor(karMidi.duration % 60)).padStart(2, '0'));
|
|
59
|
+
console.log('');
|
|
60
|
+
|
|
61
|
+
// Check if in range
|
|
62
|
+
const duration = karMidi.duration;
|
|
63
|
+
const inRange = duration >= targetMin && duration <= targetMax;
|
|
64
|
+
|
|
65
|
+
console.log('๐ฏ Duration Check:');
|
|
66
|
+
console.log(' Target: 4:42 - 4:45 (282-285 seconds)');
|
|
67
|
+
console.log(' Got:', duration.toFixed(2), 'seconds');
|
|
68
|
+
console.log(' Status:', inRange ? 'โ
IN RANGE!' : 'โ OUT OF RANGE!');
|
|
69
|
+
console.log('');
|
|
70
|
+
|
|
71
|
+
if (!inRange) {
|
|
72
|
+
const diff = duration < targetMin ? targetMin - duration : duration - targetMax;
|
|
73
|
+
console.log(' Difference:', diff.toFixed(2), 'seconds');
|
|
74
|
+
console.log(' Too', duration < targetMin ? 'LONG' : 'SHORT', 'by', diff.toFixed(2), 'seconds');
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
// Calculate what ratio we need
|
|
78
|
+
const currentTempo = karMidi.header.tempos[0].bpm;
|
|
79
|
+
|
|
80
|
+
// Calculate target tempo for 283.5 seconds (middle of 4:42-4:45)
|
|
81
|
+
const targetDuration = 283.5;
|
|
82
|
+
const targetTempo = currentTempo * (duration / targetDuration);
|
|
83
|
+
|
|
84
|
+
console.log('๐ Correction Needed:');
|
|
85
|
+
console.log(' Current tempo:', currentTempo.toFixed(2), 'BPM');
|
|
86
|
+
console.log(' Target tempo:', targetTempo.toFixed(2), 'BPM');
|
|
87
|
+
console.log(' Tempo adjustment:', (targetTempo / currentTempo).toFixed(4) + 'x');
|
|
88
|
+
console.log('');
|
|
89
|
+
|
|
90
|
+
// Find current ratio being used
|
|
91
|
+
const { decodeEmkServer } = require('./dist/index.js');
|
|
92
|
+
const emkBuffer = fs.readFileSync(inputPath);
|
|
93
|
+
const decoded = decodeEmkServer(emkBuffer);
|
|
94
|
+
const emkMidi = new Midi(decoded.midi);
|
|
95
|
+
const emkTempo = emkMidi.header.tempos[0].bpm;
|
|
96
|
+
const currentRatio = currentTempo / emkTempo;
|
|
97
|
+
const targetRatio = targetTempo / emkTempo;
|
|
98
|
+
|
|
99
|
+
console.log('๐ Ratio Analysis:');
|
|
100
|
+
console.log(' EMK tempo:', emkTempo.toFixed(2), 'BPM');
|
|
101
|
+
console.log(' Current ratio:', currentRatio.toFixed(4) + 'x');
|
|
102
|
+
console.log(' Target ratio:', targetRatio.toFixed(4) + 'x');
|
|
103
|
+
console.log(' Format:', decoded.isZxioFormat ? 'ZXIO' : 'MThd');
|
|
104
|
+
console.log('');
|
|
105
|
+
|
|
106
|
+
console.log('โ๏ธ ACTION REQUIRED:');
|
|
107
|
+
if (decoded.isZxioFormat) {
|
|
108
|
+
console.log(' Update ZXIO ratio in libs from', currentRatio.toFixed(4) + 'x', 'to', targetRatio.toFixed(4) + 'x');
|
|
109
|
+
const ppq = karMidi.header.ppq;
|
|
110
|
+
const newDivisor = ppq / targetRatio;
|
|
111
|
+
console.log(' Change: ticksPerBeat /', currentRatio.toFixed(2), 'โ ticksPerBeat /', targetRatio.toFixed(2));
|
|
112
|
+
console.log(' Or: ticksPerBeat /', newDivisor.toFixed(2));
|
|
113
|
+
} else {
|
|
114
|
+
console.log(' Update MThd ratio in libs from', currentRatio.toFixed(4) + 'x', 'to', targetRatio.toFixed(4) + 'x');
|
|
115
|
+
}
|
|
116
|
+
console.log('');
|
|
117
|
+
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log('๐ SUCCESS! Duration is within target range!');
|
|
122
|
+
console.log('');
|
|
123
|
+
|
|
124
|
+
// Clean up temp input
|
|
125
|
+
fs.unlinkSync(tempInput);
|
|
126
|
+
|
|
127
|
+
// Keep output for testing
|
|
128
|
+
console.log('๐พ Saved to: temp/test_output.kar');
|
|
129
|
+
console.log('');
|
|
130
|
+
|
package/analyze-failed-emk.js
DELETED
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { inflateSync } = require('zlib');
|
|
3
|
-
|
|
4
|
-
const XOR_KEY = Buffer.from([0xAF, 0xF2, 0x4C, 0x9C, 0xE9, 0xEA, 0x99, 0x43]);
|
|
5
|
-
const MAGIC_SIGNATURE = '.SFDS';
|
|
6
|
-
const ZLIB_SECOND_BYTES = new Set([0x01, 0x5E, 0x9C, 0xDA, 0x7D, 0x20, 0xBB]);
|
|
7
|
-
|
|
8
|
-
function xorDecrypt(data) {
|
|
9
|
-
const decrypted = Buffer.alloc(data.length);
|
|
10
|
-
for (let i = 0; i < data.length; i++) {
|
|
11
|
-
decrypted[i] = data[i] ^ XOR_KEY[i % XOR_KEY.length];
|
|
12
|
-
}
|
|
13
|
-
return decrypted;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function looksLikeText(buf) {
|
|
17
|
-
const sample = buf.subarray(0, Math.min(64, buf.length));
|
|
18
|
-
for (let i = 0; i < sample.length; i++) {
|
|
19
|
-
const c = sample[i];
|
|
20
|
-
if (c === 0x0a || c === 0x0d || c === 0x09 || (c >= 0x20 && c <= 0x7e) || c >= 0x80) {
|
|
21
|
-
continue;
|
|
22
|
-
}
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
console.log('='.repeat(80));
|
|
29
|
-
console.log('ANALYZING failed01.emk STRUCTURE');
|
|
30
|
-
console.log('='.repeat(80));
|
|
31
|
-
console.log('');
|
|
32
|
-
|
|
33
|
-
const emkBuffer = fs.readFileSync('./songs/emk/failed01.emk');
|
|
34
|
-
console.log(`File size: ${emkBuffer.length} bytes`);
|
|
35
|
-
console.log('');
|
|
36
|
-
|
|
37
|
-
// Decrypt
|
|
38
|
-
console.log('Step 1: Decrypting...');
|
|
39
|
-
const decryptedBuffer = xorDecrypt(emkBuffer);
|
|
40
|
-
|
|
41
|
-
const magic = decryptedBuffer.subarray(0, MAGIC_SIGNATURE.length).toString('utf-8');
|
|
42
|
-
console.log(`Magic signature: "${magic}" ${magic === MAGIC_SIGNATURE ? 'โ' : 'โ'}`);
|
|
43
|
-
|
|
44
|
-
if (magic !== MAGIC_SIGNATURE) {
|
|
45
|
-
console.log('โ Invalid EMK file signature!');
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
console.log('');
|
|
50
|
-
console.log('Step 2: Finding zlib blocks...');
|
|
51
|
-
console.log('');
|
|
52
|
-
|
|
53
|
-
const inflatedParts = [];
|
|
54
|
-
let blockIndex = 0;
|
|
55
|
-
|
|
56
|
-
for (let i = 0; i < decryptedBuffer.length - 2; i++) {
|
|
57
|
-
const b0 = decryptedBuffer[i];
|
|
58
|
-
const b1 = decryptedBuffer[i + 1];
|
|
59
|
-
|
|
60
|
-
if (b0 !== 0x78 || !ZLIB_SECOND_BYTES.has(b1)) continue;
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
const inflated = inflateSync(decryptedBuffer.subarray(i));
|
|
64
|
-
blockIndex++;
|
|
65
|
-
|
|
66
|
-
console.log(`Block ${blockIndex} at offset ${i}:`);
|
|
67
|
-
console.log(` Compressed from: ${i}`);
|
|
68
|
-
console.log(` Inflated size: ${inflated.length} bytes`);
|
|
69
|
-
|
|
70
|
-
const asciiPrefix = inflated.subarray(0, Math.min(32, inflated.length)).toString('ascii', 0, 16);
|
|
71
|
-
const hexPrefix = inflated.subarray(0, Math.min(32, inflated.length)).toString('hex').match(/.{1,2}/g).join(' ');
|
|
72
|
-
|
|
73
|
-
console.log(` ASCII prefix: "${asciiPrefix}"`);
|
|
74
|
-
console.log(` HEX prefix: ${hexPrefix}`);
|
|
75
|
-
|
|
76
|
-
let blockType = 'Unknown';
|
|
77
|
-
let isMidi = false;
|
|
78
|
-
|
|
79
|
-
if (asciiPrefix.startsWith('SIGNATURE=')) {
|
|
80
|
-
blockType = 'Header';
|
|
81
|
-
} else if (asciiPrefix.startsWith('CODE=')) {
|
|
82
|
-
blockType = 'SongInfo';
|
|
83
|
-
} else if (inflated.subarray(0, 4).toString('ascii') === 'MThd') {
|
|
84
|
-
blockType = 'MIDI (MThd)';
|
|
85
|
-
isMidi = true;
|
|
86
|
-
} else if (inflated.subarray(0, 4).toString('ascii') === 'RIFF') {
|
|
87
|
-
blockType = 'RIFF (WAV/similar)';
|
|
88
|
-
} else if (looksLikeText(inflated)) {
|
|
89
|
-
blockType = 'Text (Lyric/Cursor)';
|
|
90
|
-
} else {
|
|
91
|
-
blockType = 'Binary (Cursor?)';
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
console.log(` Type: ${blockType} ${isMidi ? '***' : ''}`);
|
|
95
|
-
console.log('');
|
|
96
|
-
|
|
97
|
-
inflatedParts.push({
|
|
98
|
-
type: blockType,
|
|
99
|
-
offset: i,
|
|
100
|
-
size: inflated.length,
|
|
101
|
-
data: inflated,
|
|
102
|
-
index: blockIndex
|
|
103
|
-
});
|
|
104
|
-
} catch (err) {
|
|
105
|
-
// Not a valid zlib block, continue
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
console.log('='.repeat(80));
|
|
111
|
-
console.log('SUMMARY');
|
|
112
|
-
console.log('='.repeat(80));
|
|
113
|
-
console.log(`Total blocks found: ${inflatedParts.length}`);
|
|
114
|
-
|
|
115
|
-
inflatedParts.forEach((block, i) => {
|
|
116
|
-
console.log(`${i+1}. ${block.type.padEnd(25)} - ${block.size.toString().padStart(6)} bytes (offset ${block.offset})`);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Check for MIDI
|
|
120
|
-
const midiBlocks = inflatedParts.filter(p => p.type.includes('MIDI'));
|
|
121
|
-
console.log(`\nMIDI blocks: ${midiBlocks.length}`);
|
|
122
|
-
|
|
123
|
-
if (midiBlocks.length === 0) {
|
|
124
|
-
console.log('\nโ NO MIDI BLOCK FOUND!');
|
|
125
|
-
console.log('This is why the conversion fails.');
|
|
126
|
-
console.log('\nPossible reasons:');
|
|
127
|
-
console.log(' 1. The file is corrupted');
|
|
128
|
-
console.log(' 2. The MIDI data is not in standard MThd format');
|
|
129
|
-
console.log(' 3. The zlib compression is different');
|
|
130
|
-
console.log(' 4. The file format is different from standard EMK');
|
|
131
|
-
|
|
132
|
-
// Check if there's any block that might be MIDI but not detected
|
|
133
|
-
console.log('\nChecking for non-standard MIDI formats...');
|
|
134
|
-
inflatedParts.forEach((block, i) => {
|
|
135
|
-
if (block.type === 'Binary (Cursor?)' && block.size > 1000) {
|
|
136
|
-
console.log(`\nBlock ${i+1} (${block.size} bytes) might be MIDI:`);
|
|
137
|
-
const first16 = block.data.subarray(0, 16);
|
|
138
|
-
console.log(` First 16 bytes (hex): ${first16.toString('hex').match(/.{1,2}/g).join(' ')}`);
|
|
139
|
-
console.log(` First 16 bytes (ascii): ${first16.toString('ascii').replace(/[^\x20-\x7E]/g, '.')}`);
|
|
140
|
-
|
|
141
|
-
// Try to find MThd anywhere in the block
|
|
142
|
-
const mthdIndex = block.data.indexOf('MThd');
|
|
143
|
-
if (mthdIndex >= 0) {
|
|
144
|
-
console.log(` โ ๏ธ Found 'MThd' at offset ${mthdIndex} inside this block!`);
|
|
145
|
-
console.log(` This block might contain MIDI data starting at offset ${mthdIndex}`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
} else {
|
|
150
|
-
console.log('\nโ MIDI block found');
|
|
151
|
-
midiBlocks.forEach(m => {
|
|
152
|
-
console.log(` - Block ${m.index}: ${m.size} bytes at offset ${m.offset}`);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
console.log('');
|
package/check-gr.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { parseMidi } = require('midi-file');
|
|
3
|
-
const iconv = require('iconv-lite');
|
|
4
|
-
|
|
5
|
-
const karBuffer = fs.readFileSync('test-emk-output.kar');
|
|
6
|
-
const midi = parseMidi(karBuffer);
|
|
7
|
-
|
|
8
|
-
const wordsTrack = midi.tracks[1]; // Words track
|
|
9
|
-
console.log('=== Words Track Events (first 30) ===\n');
|
|
10
|
-
|
|
11
|
-
wordsTrack.slice(0, 30).forEach((event, i) => {
|
|
12
|
-
if (event.type === 'text') {
|
|
13
|
-
const text = event.text;
|
|
14
|
-
const bytes = Buffer.from(text, 'latin1');
|
|
15
|
-
const decoded = iconv.decode(bytes, 'tis-620');
|
|
16
|
-
|
|
17
|
-
console.log(`${i}. "${text}" -> bytes: [${Array.from(bytes).map(b => '0x'+b.toString(16)).join(', ')}] -> TIS-620: "${decoded}"`);
|
|
18
|
-
}
|
|
19
|
-
});
|
package/debug-emk-blocks.js
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { parseMidi } = require('midi-file');
|
|
3
|
-
const { inflateSync } = require('zlib');
|
|
4
|
-
|
|
5
|
-
const XOR_KEY = Buffer.from([0xAF, 0xF2, 0x4C, 0x9C, 0xE9, 0xEA, 0x99, 0x43]);
|
|
6
|
-
const MAGIC_SIGNATURE = '.SFDS';
|
|
7
|
-
const ZLIB_SECOND_BYTES = new Set([0x01, 0x5E, 0x9C, 0xDA, 0x7D, 0x20, 0xBB]);
|
|
8
|
-
|
|
9
|
-
function xorDecrypt(data) {
|
|
10
|
-
const decrypted = Buffer.alloc(data.length);
|
|
11
|
-
for (let i = 0; i < data.length; i++) {
|
|
12
|
-
decrypted[i] = data[i] ^ XOR_KEY[i % XOR_KEY.length];
|
|
13
|
-
}
|
|
14
|
-
return decrypted;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function looksLikeText(buf) {
|
|
18
|
-
const sample = buf.subarray(0, Math.min(64, buf.length));
|
|
19
|
-
for (let i = 0; i < sample.length; i++) {
|
|
20
|
-
const c = sample[i];
|
|
21
|
-
if (c === 0x0a || c === 0x0d || c === 0x09 || (c >= 0x20 && c <= 0x7e) || c >= 0x80) {
|
|
22
|
-
continue;
|
|
23
|
-
}
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
console.log('Analyzing EMK file structure...\n');
|
|
30
|
-
|
|
31
|
-
const emkBuffer = fs.readFileSync('./songs/fix/song.emk');
|
|
32
|
-
const decryptedBuffer = xorDecrypt(emkBuffer);
|
|
33
|
-
|
|
34
|
-
const magic = decryptedBuffer.subarray(0, MAGIC_SIGNATURE.length).toString('utf-8');
|
|
35
|
-
console.log('Magic signature:', magic);
|
|
36
|
-
|
|
37
|
-
if (magic !== MAGIC_SIGNATURE) {
|
|
38
|
-
console.error('Invalid EMK file!');
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
console.log('\nSearching for zlib blocks...\n');
|
|
43
|
-
|
|
44
|
-
const inflatedParts = [];
|
|
45
|
-
let blockIndex = 0;
|
|
46
|
-
|
|
47
|
-
for (let i = 0; i < decryptedBuffer.length - 2; i++) {
|
|
48
|
-
const b0 = decryptedBuffer[i];
|
|
49
|
-
const b1 = decryptedBuffer[i + 1];
|
|
50
|
-
|
|
51
|
-
if (b0 !== 0x78 || !ZLIB_SECOND_BYTES.has(b1)) continue;
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const inflated = inflateSync(decryptedBuffer.subarray(i));
|
|
55
|
-
blockIndex++;
|
|
56
|
-
|
|
57
|
-
console.log(`Block ${blockIndex} (offset ${i}):`);
|
|
58
|
-
console.log(` Size: ${inflated.length} bytes`);
|
|
59
|
-
|
|
60
|
-
const asciiPrefix = inflated.subarray(0, 16).toString('ascii');
|
|
61
|
-
let blockType = 'Unknown';
|
|
62
|
-
|
|
63
|
-
if (asciiPrefix.startsWith('SIGNATURE=')) {
|
|
64
|
-
blockType = 'Header';
|
|
65
|
-
} else if (asciiPrefix.startsWith('CODE=')) {
|
|
66
|
-
blockType = 'SongInfo';
|
|
67
|
-
} else if (inflated.subarray(0, 4).toString('ascii') === 'MThd') {
|
|
68
|
-
blockType = 'MIDI';
|
|
69
|
-
|
|
70
|
-
// Parse MIDI and check tempo
|
|
71
|
-
try {
|
|
72
|
-
const midi = parseMidi(inflated);
|
|
73
|
-
console.log(` Type: ${blockType} ***`);
|
|
74
|
-
console.log(` Format: ${midi.header.format}`);
|
|
75
|
-
console.log(` Tracks: ${midi.tracks.length}`);
|
|
76
|
-
console.log(` Ticks per beat: ${midi.header.ticksPerBeat}`);
|
|
77
|
-
|
|
78
|
-
// Find tempo events
|
|
79
|
-
midi.tracks.forEach((track, trackIdx) => {
|
|
80
|
-
let absoluteTime = 0;
|
|
81
|
-
track.forEach((event) => {
|
|
82
|
-
absoluteTime += event.deltaTime;
|
|
83
|
-
if (event.type === 'setTempo') {
|
|
84
|
-
const bpm = 60000000 / event.microsecondsPerBeat;
|
|
85
|
-
console.log(` Tempo in Track ${trackIdx}: ${bpm.toFixed(2)} BPM (${event.microsecondsPerBeat} ยตs/beat)`);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
} catch (e) {
|
|
90
|
-
console.log(` Type: ${blockType} (parse error: ${e.message})`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
inflatedParts.push({ type: blockType, data: inflated, index: blockIndex });
|
|
94
|
-
continue;
|
|
95
|
-
} else if (looksLikeText(inflated)) {
|
|
96
|
-
blockType = 'Text (Lyric/Cursor)';
|
|
97
|
-
} else {
|
|
98
|
-
blockType = 'Binary (Cursor?)';
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
console.log(` Type: ${blockType}`);
|
|
102
|
-
console.log(` First 32 bytes: ${inflated.subarray(0, 32).toString('hex')}`);
|
|
103
|
-
console.log('');
|
|
104
|
-
|
|
105
|
-
inflatedParts.push({ type: blockType, data: inflated, index: blockIndex });
|
|
106
|
-
} catch {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
console.log('='.repeat(80));
|
|
112
|
-
console.log(`Total blocks found: ${inflatedParts.length}`);
|
|
113
|
-
console.log('='.repeat(80));
|
|
114
|
-
|
|
115
|
-
// Check if there are multiple MIDI blocks
|
|
116
|
-
const midiBlocks = inflatedParts.filter(p => p.type === 'MIDI');
|
|
117
|
-
console.log(`\nMIDI blocks found: ${midiBlocks.length}`);
|
|
118
|
-
|
|
119
|
-
if (midiBlocks.length > 1) {
|
|
120
|
-
console.log('\nโ ๏ธ Multiple MIDI blocks found!');
|
|
121
|
-
console.log('The decoder currently picks the FIRST one.');
|
|
122
|
-
console.log('One of the other MIDI blocks might have the correct tempo.');
|
|
123
|
-
}
|
package/debug-midi-source.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { parseMidi } = require('midi-file');
|
|
3
|
-
const { decodeEmkServer } = require('./dist/index.js');
|
|
4
|
-
|
|
5
|
-
// Check MIDI tempo from EMK
|
|
6
|
-
console.log('Step 1: Decode EMK and check MIDI tempo...\n');
|
|
7
|
-
|
|
8
|
-
const emkBuffer = fs.readFileSync('./songs/fix/song.emk');
|
|
9
|
-
const decoded = decodeEmkServer(emkBuffer);
|
|
10
|
-
|
|
11
|
-
console.log('EMK decoded successfully');
|
|
12
|
-
console.log('Has MIDI:', !!decoded.midi);
|
|
13
|
-
console.log('Has Lyric:', !!decoded.lyric);
|
|
14
|
-
console.log('Has Cursor:', !!decoded.cursor);
|
|
15
|
-
|
|
16
|
-
if (decoded.midi) {
|
|
17
|
-
// Write the decoded MIDI to temp file
|
|
18
|
-
fs.writeFileSync('./songs/fix/decoded-midi-temp.mid', decoded.midi);
|
|
19
|
-
|
|
20
|
-
// Parse the decoded MIDI
|
|
21
|
-
const midi = parseMidi(decoded.midi);
|
|
22
|
-
|
|
23
|
-
console.log('\nDecoded MIDI file:');
|
|
24
|
-
console.log(' Format:', midi.header.format);
|
|
25
|
-
console.log(' Tracks:', midi.tracks.length);
|
|
26
|
-
console.log(' Ticks per beat:', midi.header.ticksPerBeat);
|
|
27
|
-
|
|
28
|
-
// Find tempo events
|
|
29
|
-
const tempoEvents = [];
|
|
30
|
-
|
|
31
|
-
midi.tracks.forEach((track, trackIdx) => {
|
|
32
|
-
let absoluteTime = 0;
|
|
33
|
-
|
|
34
|
-
track.forEach((event) => {
|
|
35
|
-
absoluteTime += event.deltaTime;
|
|
36
|
-
|
|
37
|
-
if (event.type === 'setTempo') {
|
|
38
|
-
const bpm = 60000000 / event.microsecondsPerBeat;
|
|
39
|
-
tempoEvents.push({
|
|
40
|
-
trackIdx: trackIdx,
|
|
41
|
-
absoluteTime: absoluteTime,
|
|
42
|
-
microsecondsPerBeat: event.microsecondsPerBeat,
|
|
43
|
-
bpm: bpm.toFixed(2)
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
if (tempoEvents.length === 0) {
|
|
50
|
-
console.log('\nโ ๏ธ NO TEMPO EVENTS in decoded MIDI!');
|
|
51
|
-
} else {
|
|
52
|
-
console.log(`\n Tempo events: ${tempoEvents.length}`);
|
|
53
|
-
tempoEvents.forEach((t, i) => {
|
|
54
|
-
console.log(` ${i+1}. Track ${t.trackIdx}, BPM: ${t.bpm}, ยตs/beat: ${t.microsecondsPerBeat}`);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Cleanup
|
|
59
|
-
fs.unlinkSync('./songs/fix/decoded-midi-temp.mid');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
console.log('\n' + '='.repeat(80));
|
|
63
|
-
console.log('Step 2: Check ncntokar conversion process...\n');
|
|
64
|
-
|
|
65
|
-
// Check if ncntokar.ts adds any tempo
|
|
66
|
-
const ncntokarCode = fs.readFileSync('./src/ncntokar.ts', 'utf8');
|
|
67
|
-
|
|
68
|
-
if (ncntokarCode.includes('setTempo')) {
|
|
69
|
-
console.log('โ ๏ธ ncntokar.ts contains "setTempo" - it might be modifying tempo!');
|
|
70
|
-
|
|
71
|
-
// Find the lines
|
|
72
|
-
const lines = ncntokarCode.split('\n');
|
|
73
|
-
lines.forEach((line, idx) => {
|
|
74
|
-
if (line.includes('setTempo')) {
|
|
75
|
-
console.log(` Line ${idx+1}: ${line.trim()}`);
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
} else {
|
|
79
|
-
console.log('โ ncntokar.ts does not modify tempo');
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
console.log('\n' + '='.repeat(80));
|
|
83
|
-
console.log('Step 3: Check original song.kar tempo location...\n');
|
|
84
|
-
|
|
85
|
-
const originalBuffer = fs.readFileSync('./songs/fix/song.kar');
|
|
86
|
-
const originalMidi = parseMidi(originalBuffer);
|
|
87
|
-
|
|
88
|
-
console.log('Original song.kar:');
|
|
89
|
-
console.log(' Tracks:', originalMidi.tracks.length);
|
|
90
|
-
|
|
91
|
-
// Check which tracks have tempo
|
|
92
|
-
originalMidi.tracks.forEach((track, trackIdx) => {
|
|
93
|
-
const tempoCount = track.filter(e => e.type === 'setTempo').length;
|
|
94
|
-
if (tempoCount > 0) {
|
|
95
|
-
console.log(` Track ${trackIdx} has ${tempoCount} tempo event(s)`);
|
|
96
|
-
|
|
97
|
-
track.forEach((event, eventIdx) => {
|
|
98
|
-
if (event.type === 'setTempo') {
|
|
99
|
-
const bpm = 60000000 / event.microsecondsPerBeat;
|
|
100
|
-
console.log(` Event ${eventIdx}: BPM ${bpm.toFixed(2)}, ยตs/beat: ${event.microsecondsPerBeat}, deltaTime: ${event.deltaTime}`);
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
console.log('\n' + '='.repeat(80));
|
|
107
|
-
console.log('ANALYSIS COMPLETE');
|
|
108
|
-
console.log('='.repeat(80));
|
package/debug-tempo.js
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const { parseMidi } = require('midi-file');
|
|
4
|
-
|
|
5
|
-
// Parse and compare tempo events
|
|
6
|
-
function analyzeTempo(filePath) {
|
|
7
|
-
console.log(`\n=== Analyzing: ${path.basename(filePath)} ===`);
|
|
8
|
-
|
|
9
|
-
const buffer = fs.readFileSync(filePath);
|
|
10
|
-
const midi = parseMidi(buffer);
|
|
11
|
-
|
|
12
|
-
console.log(`Format: ${midi.header.format}`);
|
|
13
|
-
console.log(`Tracks: ${midi.tracks.length}`);
|
|
14
|
-
console.log(`Ticks per beat: ${midi.header.ticksPerBeat}`);
|
|
15
|
-
|
|
16
|
-
// Find all tempo events in all tracks
|
|
17
|
-
const tempoEvents = [];
|
|
18
|
-
|
|
19
|
-
midi.tracks.forEach((track, trackIdx) => {
|
|
20
|
-
let absoluteTime = 0;
|
|
21
|
-
|
|
22
|
-
track.forEach((event, eventIdx) => {
|
|
23
|
-
absoluteTime += event.deltaTime;
|
|
24
|
-
|
|
25
|
-
if (event.type === 'setTempo') {
|
|
26
|
-
// microsecondsPerBeat = event.microsecondsPerBeat
|
|
27
|
-
// BPM = 60000000 / microsecondsPerBeat
|
|
28
|
-
const bpm = 60000000 / event.microsecondsPerBeat;
|
|
29
|
-
|
|
30
|
-
tempoEvents.push({
|
|
31
|
-
trackIdx: trackIdx,
|
|
32
|
-
eventIdx: eventIdx,
|
|
33
|
-
absoluteTime: absoluteTime,
|
|
34
|
-
deltaTime: event.deltaTime,
|
|
35
|
-
microsecondsPerBeat: event.microsecondsPerBeat,
|
|
36
|
-
bpm: bpm.toFixed(2)
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
if (tempoEvents.length === 0) {
|
|
43
|
-
console.log('\nโ ๏ธ NO TEMPO EVENTS FOUND - USING DEFAULT 120 BPM');
|
|
44
|
-
return {
|
|
45
|
-
hasTempoEvents: false,
|
|
46
|
-
defaultBpm: 120,
|
|
47
|
-
tempoEvents: []
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
console.log(`\nTempo events found: ${tempoEvents.length}`);
|
|
52
|
-
tempoEvents.forEach((t, i) => {
|
|
53
|
-
console.log(`${i+1}. Track ${t.trackIdx}, Delta: ${t.deltaTime}, Absolute: ${t.absoluteTime}, BPM: ${t.bpm}, ยตs/beat: ${t.microsecondsPerBeat}`);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return {
|
|
57
|
-
hasTempoEvents: true,
|
|
58
|
-
tempoEvents: tempoEvents
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Main
|
|
63
|
-
const originalKar = './songs/fix/song.kar';
|
|
64
|
-
const emkFile = './songs/fix/song.emk';
|
|
65
|
-
const tempKar = './songs/fix/converted-test.kar';
|
|
66
|
-
|
|
67
|
-
console.log('Converting EMK to KAR...');
|
|
68
|
-
const { convertEmkToKar } = require('./dist/index.js');
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const result = convertEmkToKar({
|
|
72
|
-
inputEmk: emkFile,
|
|
73
|
-
outputKar: tempKar,
|
|
74
|
-
keepIntermediateFiles: false
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
console.log(`Conversion ${result.success ? 'SUCCESS' : 'FAILED'}`);
|
|
78
|
-
|
|
79
|
-
console.log('\n' + '='.repeat(80));
|
|
80
|
-
console.log('TEMPO COMPARISON');
|
|
81
|
-
console.log('='.repeat(80));
|
|
82
|
-
|
|
83
|
-
const originalTempo = analyzeTempo(originalKar);
|
|
84
|
-
const convertedTempo = analyzeTempo(tempKar);
|
|
85
|
-
|
|
86
|
-
console.log('\n' + '='.repeat(80));
|
|
87
|
-
console.log('COMPARISON RESULTS');
|
|
88
|
-
console.log('='.repeat(80));
|
|
89
|
-
|
|
90
|
-
console.log(`\nOriginal has tempo events: ${originalTempo.hasTempoEvents}`);
|
|
91
|
-
console.log(`Converted has tempo events: ${convertedTempo.hasTempoEvents}`);
|
|
92
|
-
|
|
93
|
-
if (originalTempo.hasTempoEvents && !convertedTempo.hasTempoEvents) {
|
|
94
|
-
console.log('\n๐ด PROBLEM FOUND: Original has tempo events but converted file does NOT!');
|
|
95
|
-
console.log('This will cause the converted file to play at default 120 BPM instead of the intended tempo.');
|
|
96
|
-
console.log('\nOriginal tempo events:');
|
|
97
|
-
originalTempo.tempoEvents.forEach((t, i) => {
|
|
98
|
-
console.log(` ${i+1}. Track ${t.trackIdx}, BPM: ${t.bpm} (${t.microsecondsPerBeat} ยตs/beat)`);
|
|
99
|
-
});
|
|
100
|
-
} else if (!originalTempo.hasTempoEvents && !convertedTempo.hasTempoEvents) {
|
|
101
|
-
console.log('\nโ Both files use default tempo (120 BPM)');
|
|
102
|
-
} else if (originalTempo.hasTempoEvents && convertedTempo.hasTempoEvents) {
|
|
103
|
-
console.log('\nโ Both files have tempo events');
|
|
104
|
-
|
|
105
|
-
// Compare tempos
|
|
106
|
-
if (originalTempo.tempoEvents.length !== convertedTempo.tempoEvents.length) {
|
|
107
|
-
console.log(`\nโ ๏ธ Warning: Different number of tempo events (${originalTempo.tempoEvents.length} vs ${convertedTempo.tempoEvents.length})`);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Compare first tempo
|
|
111
|
-
const origFirst = originalTempo.tempoEvents[0];
|
|
112
|
-
const convFirst = convertedTempo.tempoEvents[0];
|
|
113
|
-
|
|
114
|
-
if (origFirst && convFirst) {
|
|
115
|
-
console.log(`\nFirst tempo comparison:`);
|
|
116
|
-
console.log(` Original: ${origFirst.bpm} BPM`);
|
|
117
|
-
console.log(` Converted: ${convFirst.bpm} BPM`);
|
|
118
|
-
|
|
119
|
-
if (origFirst.bpm !== convFirst.bpm) {
|
|
120
|
-
console.log(` ๐ด TEMPO MISMATCH!`);
|
|
121
|
-
} else {
|
|
122
|
-
console.log(` โ Tempo matches`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Cleanup
|
|
128
|
-
if (fs.existsSync(tempKar)) {
|
|
129
|
-
fs.unlinkSync(tempKar);
|
|
130
|
-
console.log('\nCleaned up temporary file.');
|
|
131
|
-
}
|
|
132
|
-
} catch (error) {
|
|
133
|
-
console.error('Error:', error.message);
|
|
134
|
-
console.error(error.stack);
|
|
135
|
-
}
|