@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.
Files changed (43) hide show
  1. package/DEMO_ENHANCED.md +168 -0
  2. package/DEMO_FIXED.md +57 -0
  3. package/DEMO_GUIDE.md +204 -0
  4. package/DEMO_README.md +193 -0
  5. package/DEMO_WORKING.md +157 -0
  6. package/RELEASE_v1.4.9.md +144 -0
  7. package/RELEASE_v1.5.0.md +91 -0
  8. package/WHY_DURATION_DECREASES.md +176 -0
  9. package/analyze-cursor-pattern.js +131 -0
  10. package/analyze-emk-cursor.js +169 -0
  11. package/analyze-emk-simple.js +124 -0
  12. package/analyze-tempo-duration.js +243 -0
  13. package/calculate-correct-ratio.js +97 -0
  14. package/check-real-duration.js +69 -0
  15. package/compare-kar-lyrics-timing.js +110 -0
  16. package/demo-client.html +722 -0
  17. package/demo-server.js +184 -0
  18. package/demo-simple.html +712 -0
  19. package/dist/emk-to-kar.browser.js +7 -4
  20. package/dist/emk-to-kar.js +7 -6
  21. package/dist/ncntokar.browser.d.ts +2 -1
  22. package/dist/ncntokar.browser.js +5 -3
  23. package/dist/ncntokar.d.ts +2 -1
  24. package/dist/ncntokar.js +6 -4
  25. package/find-zxio-tempo-ratio.js +118 -0
  26. package/match-cursor-to-lyrics.js +118 -0
  27. package/package.json +6 -2
  28. package/songs/emk/001_original_emk.emk +0 -0
  29. package/songs/fix/001_kar_convert_needtofix_tempo_fast.kar +0 -0
  30. package/songs/fix/001_original_emk.emk +0 -0
  31. package/temp/test_output.kar +0 -0
  32. package/test-all-emk-durations.js +109 -0
  33. package/test-all-emk-final.js +97 -0
  34. package/test-convert-001.js +130 -0
  35. package/analyze-failed-emk.js +0 -156
  36. package/check-gr.js +0 -19
  37. package/debug-emk-blocks.js +0 -123
  38. package/debug-midi-source.js +0 -108
  39. package/debug-tempo.js +0 -135
  40. package/debug-timing.js +0 -159
  41. package/find-midi-in-block.js +0 -96
  42. package/fix-tempo.js +0 -155
  43. 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
+
@@ -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
- });
@@ -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
- }
@@ -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
- }