@karaplay/file-coder 1.4.7 → 1.4.9
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/RELEASE_v1.4.8.md +115 -0
- package/RELEASE_v1.4.9.md +144 -0
- package/dist/emk/client-decoder.d.ts +1 -0
- package/dist/emk/client-decoder.js +1 -0
- package/dist/emk/server-decode.d.ts +1 -0
- package/dist/emk/server-decode.js +1 -0
- package/dist/emk-to-kar.browser.js +7 -1
- package/dist/emk-to-kar.js +7 -1
- package/dist/ncntokar.browser.d.ts +4 -2
- package/dist/ncntokar.browser.js +13 -6
- package/dist/ncntokar.d.ts +4 -2
- package/dist/ncntokar.js +13 -7
- package/package.json +2 -1
- package/songs/fix/001_kar_convert_needtofix_tempo_fast.kar +0 -0
- package/songs/fix/001_original_emk.emk +0 -0
- 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/fix-tempo.js +0 -155
- package/kar-diagnostic.js +0 -158
- package/test-final.js +0 -175
package/kar-diagnostic.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* KAR File Diagnostic Tool
|
|
5
|
-
*
|
|
6
|
-
* This tool checks if a KAR file is valid and provides detailed diagnostics
|
|
7
|
-
* Usage: node kar-diagnostic.js <path-to-kar-file>
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
|
|
13
|
-
function checkKarFile(filePath) {
|
|
14
|
-
console.log('='.repeat(60));
|
|
15
|
-
console.log('KAR File Diagnostic Tool');
|
|
16
|
-
console.log('='.repeat(60));
|
|
17
|
-
console.log('File:', filePath);
|
|
18
|
-
console.log('');
|
|
19
|
-
|
|
20
|
-
// Check if file exists
|
|
21
|
-
if (!fs.existsSync(filePath)) {
|
|
22
|
-
console.error('❌ File not found:', filePath);
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Read file
|
|
27
|
-
const data = fs.readFileSync(filePath);
|
|
28
|
-
console.log('✅ File exists');
|
|
29
|
-
console.log(' Size:', data.length, 'bytes');
|
|
30
|
-
console.log('');
|
|
31
|
-
|
|
32
|
-
// Check MThd header
|
|
33
|
-
console.log('--- MIDI Header Check ---');
|
|
34
|
-
const expectedHeader = Buffer.from([0x4D, 0x54, 0x68, 0x64]); // MThd
|
|
35
|
-
const actualHeader = data.subarray(0, 4);
|
|
36
|
-
|
|
37
|
-
console.log('Expected: 4D 54 68 64 (MThd)');
|
|
38
|
-
console.log('Actual: ', actualHeader.toString('hex').match(/.{2}/g).join(' '));
|
|
39
|
-
|
|
40
|
-
if (Buffer.compare(expectedHeader, actualHeader) === 0) {
|
|
41
|
-
console.log('✅ Valid MThd header');
|
|
42
|
-
} else {
|
|
43
|
-
console.log('❌ Invalid header!');
|
|
44
|
-
console.log(' First 20 bytes:', data.subarray(0, 20).toString('hex'));
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
console.log('');
|
|
48
|
-
|
|
49
|
-
// Check MIDI structure
|
|
50
|
-
console.log('--- MIDI Structure ---');
|
|
51
|
-
const headerLength = data.readUInt32BE(4);
|
|
52
|
-
const format = data.readUInt16BE(8);
|
|
53
|
-
const numTracks = data.readUInt16BE(10);
|
|
54
|
-
const division = data.readUInt16BE(12);
|
|
55
|
-
|
|
56
|
-
console.log('Header length:', headerLength, '(expected: 6)');
|
|
57
|
-
console.log('Format:', format, '(expected: 0 or 1)');
|
|
58
|
-
console.log('Number of tracks:', numTracks);
|
|
59
|
-
console.log('Division:', division);
|
|
60
|
-
|
|
61
|
-
if (headerLength !== 6) {
|
|
62
|
-
console.log('⚠️ Unusual header length');
|
|
63
|
-
}
|
|
64
|
-
if (format > 2) {
|
|
65
|
-
console.log('⚠️ Invalid format');
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
if (numTracks === 0) {
|
|
69
|
-
console.log('❌ No tracks found');
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
console.log('✅ Valid structure');
|
|
73
|
-
console.log('');
|
|
74
|
-
|
|
75
|
-
// Check first track
|
|
76
|
-
console.log('--- First Track Check ---');
|
|
77
|
-
const firstTrackPos = 14;
|
|
78
|
-
const firstTrackHeader = data.subarray(firstTrackPos, firstTrackPos + 4).toString('ascii');
|
|
79
|
-
const firstTrackLength = data.readUInt32BE(firstTrackPos + 4);
|
|
80
|
-
|
|
81
|
-
console.log('Track header:', firstTrackHeader, '(expected: MTrk)');
|
|
82
|
-
console.log('Track length:', firstTrackLength);
|
|
83
|
-
|
|
84
|
-
if (firstTrackHeader !== 'MTrk') {
|
|
85
|
-
console.log('❌ Invalid track header');
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
console.log('✅ Valid first track');
|
|
89
|
-
console.log('');
|
|
90
|
-
|
|
91
|
-
// Try parsing with midi-file
|
|
92
|
-
console.log('--- Library Compatibility Test ---');
|
|
93
|
-
try {
|
|
94
|
-
const { parseMidi } = require('midi-file');
|
|
95
|
-
const parsed = parseMidi(data);
|
|
96
|
-
console.log('✅ midi-file library can parse');
|
|
97
|
-
console.log(' Parsed tracks:', parsed.tracks.length);
|
|
98
|
-
console.log(' Format:', parsed.header.format);
|
|
99
|
-
console.log(' Division:', parsed.header.ticksPerBeat || parsed.header.framesPerSecond);
|
|
100
|
-
} catch (e) {
|
|
101
|
-
console.log('❌ midi-file library error:', e.message);
|
|
102
|
-
return false;
|
|
103
|
-
}
|
|
104
|
-
console.log('');
|
|
105
|
-
|
|
106
|
-
// Check for karaoke content
|
|
107
|
-
console.log('--- Karaoke Content Check ---');
|
|
108
|
-
const fileContent = data.toString('binary');
|
|
109
|
-
const hasKaraoke = fileContent.includes('@T');
|
|
110
|
-
const hasWords = fileContent.includes('Words');
|
|
111
|
-
|
|
112
|
-
console.log('Contains @T markers:', hasKaraoke ? '✅' : '❌');
|
|
113
|
-
console.log('Contains "Words" track:', hasWords ? '✅' : '❌');
|
|
114
|
-
console.log('');
|
|
115
|
-
|
|
116
|
-
// Summary
|
|
117
|
-
console.log('='.repeat(60));
|
|
118
|
-
console.log('SUMMARY: File is VALID ✅');
|
|
119
|
-
console.log('='.repeat(60));
|
|
120
|
-
console.log('');
|
|
121
|
-
console.log('If you still get errors when using this file:');
|
|
122
|
-
console.log('1. Check file transfer method (use binary mode)');
|
|
123
|
-
console.log('2. Verify file size matches:', data.length, 'bytes');
|
|
124
|
-
console.log('3. Check first 4 bytes: should be 4D 54 68 64');
|
|
125
|
-
console.log('4. Try a different MIDI player/library');
|
|
126
|
-
console.log('5. Check if server is re-encoding the file');
|
|
127
|
-
console.log('');
|
|
128
|
-
|
|
129
|
-
return true;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Main
|
|
133
|
-
const args = process.argv.slice(2);
|
|
134
|
-
if (args.length === 0) {
|
|
135
|
-
console.error('Usage: node kar-diagnostic.js <path-to-kar-file>');
|
|
136
|
-
console.error('');
|
|
137
|
-
console.error('Example:');
|
|
138
|
-
console.error(' node kar-diagnostic.js output.kar');
|
|
139
|
-
console.error(' node kar-diagnostic.js /path/to/song.kar');
|
|
140
|
-
process.exit(1);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const filePath = args[0];
|
|
144
|
-
try {
|
|
145
|
-
checkKarFile(filePath);
|
|
146
|
-
} catch (error) {
|
|
147
|
-
console.error('');
|
|
148
|
-
console.error('❌ Error:', error.message);
|
|
149
|
-
console.error('');
|
|
150
|
-
console.error('Stack trace:');
|
|
151
|
-
console.error(error.stack);
|
|
152
|
-
process.exit(1);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
package/test-final.js
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
const fs = require('fs');
|
|
2
|
-
const { parseMidi } = require('midi-file');
|
|
3
|
-
const { convertEmkToKar } = require('./dist/index.js');
|
|
4
|
-
|
|
5
|
-
console.log('='.repeat(80));
|
|
6
|
-
console.log('FINAL TEMPO FIX VERIFICATION');
|
|
7
|
-
console.log('='.repeat(80));
|
|
8
|
-
console.log('');
|
|
9
|
-
|
|
10
|
-
// Convert EMK to KAR with automatic tempo fix
|
|
11
|
-
const outputKar = './songs/fix/final-converted.kar';
|
|
12
|
-
console.log('Step 1: Converting EMK to KAR with auto tempo fix...\n');
|
|
13
|
-
|
|
14
|
-
const result = convertEmkToKar({
|
|
15
|
-
inputEmk: './songs/fix/song.emk',
|
|
16
|
-
outputKar: outputKar,
|
|
17
|
-
keepIntermediateFiles: false
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
console.log(`\nConversion: ${result.success ? '✅ SUCCESS' : '❌ FAILED'}`);
|
|
21
|
-
console.log(`Warnings: ${result.warnings.length}`);
|
|
22
|
-
result.warnings.forEach(w => console.log(` - ${w}`));
|
|
23
|
-
|
|
24
|
-
// Compare timing and tempo
|
|
25
|
-
console.log('\n' + '='.repeat(80));
|
|
26
|
-
console.log('COMPARISON');
|
|
27
|
-
console.log('='.repeat(80));
|
|
28
|
-
|
|
29
|
-
const originalMidi = parseMidi(fs.readFileSync('./songs/fix/song.kar'));
|
|
30
|
-
const convertedMidi = parseMidi(fs.readFileSync(outputKar));
|
|
31
|
-
|
|
32
|
-
// Check tempo
|
|
33
|
-
console.log('\n1. Tempo:');
|
|
34
|
-
let originalTempo = null;
|
|
35
|
-
let convertedTempo = null;
|
|
36
|
-
|
|
37
|
-
originalMidi.tracks.forEach(track => {
|
|
38
|
-
track.forEach(event => {
|
|
39
|
-
if (event.type === 'setTempo') {
|
|
40
|
-
originalTempo = (60000000 / event.microsecondsPerBeat).toFixed(2);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
convertedMidi.tracks.forEach(track => {
|
|
46
|
-
track.forEach(event => {
|
|
47
|
-
if (event.type === 'setTempo') {
|
|
48
|
-
convertedTempo = (60000000 / event.microsecondsPerBeat).toFixed(2);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
console.log(` Original: ${originalTempo} BPM`);
|
|
54
|
-
console.log(` Converted: ${convertedTempo} BPM`);
|
|
55
|
-
console.log(` Difference: ${((convertedTempo / originalTempo - 1) * 100).toFixed(2)}%`);
|
|
56
|
-
|
|
57
|
-
// Check timing (first 10 lyric events)
|
|
58
|
-
console.log('\n2. Timing (first 10 karaoke events):');
|
|
59
|
-
|
|
60
|
-
function getKaraokeTrack(midi) {
|
|
61
|
-
let maxTextEvents = 0;
|
|
62
|
-
let karaokeTrack = null;
|
|
63
|
-
|
|
64
|
-
midi.tracks.forEach(track => {
|
|
65
|
-
const textEvents = track.filter(e => e.type === 'text');
|
|
66
|
-
if (textEvents.length > maxTextEvents) {
|
|
67
|
-
maxTextEvents = textEvents.length;
|
|
68
|
-
karaokeTrack = track;
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return karaokeTrack;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const originalKaraokeTrack = getKaraokeTrack(originalMidi);
|
|
76
|
-
const convertedKaraokeTrack = getKaraokeTrack(convertedMidi);
|
|
77
|
-
|
|
78
|
-
let originalAbsolute = 0;
|
|
79
|
-
let convertedAbsolute = 0;
|
|
80
|
-
let originalTimes = [];
|
|
81
|
-
let convertedTimes = [];
|
|
82
|
-
|
|
83
|
-
originalKaraokeTrack.forEach(event => {
|
|
84
|
-
originalAbsolute += event.deltaTime;
|
|
85
|
-
if (event.type === 'text') {
|
|
86
|
-
originalTimes.push(originalAbsolute);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
convertedKaraokeTrack.forEach(event => {
|
|
91
|
-
convertedAbsolute += event.deltaTime;
|
|
92
|
-
if (event.type === 'text') {
|
|
93
|
-
convertedTimes.push(convertedAbsolute);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
console.log(' Idx | Original | Converted | Match');
|
|
98
|
-
console.log(' ' + '-'.repeat(40));
|
|
99
|
-
|
|
100
|
-
for (let i = 0; i < Math.min(10, originalTimes.length, convertedTimes.length); i++) {
|
|
101
|
-
const match = originalTimes[i] === convertedTimes[i] ? '✓' : '✗';
|
|
102
|
-
console.log(` ${(i+1).toString().padStart(3)} | ${originalTimes[i].toString().padStart(8)} | ${convertedTimes[i].toString().padStart(9)} | ${match}`);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Calculate playback duration
|
|
106
|
-
console.log('\n3. Playback Duration Estimate:');
|
|
107
|
-
|
|
108
|
-
function calculateDuration(midi) {
|
|
109
|
-
const ticksPerBeat = midi.header.ticksPerBeat;
|
|
110
|
-
let microsecondsPerBeat = 500000; // default 120 BPM
|
|
111
|
-
|
|
112
|
-
// Get tempo
|
|
113
|
-
midi.tracks.forEach(track => {
|
|
114
|
-
track.forEach(event => {
|
|
115
|
-
if (event.type === 'setTempo') {
|
|
116
|
-
microsecondsPerBeat = event.microsecondsPerBeat;
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
// Get last event time
|
|
122
|
-
let maxTicks = 0;
|
|
123
|
-
midi.tracks.forEach(track => {
|
|
124
|
-
let currentTicks = 0;
|
|
125
|
-
track.forEach(event => {
|
|
126
|
-
currentTicks += event.deltaTime;
|
|
127
|
-
if (currentTicks > maxTicks) {
|
|
128
|
-
maxTicks = currentTicks;
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
// Calculate duration
|
|
134
|
-
const microsecondsPerTick = microsecondsPerBeat / ticksPerBeat;
|
|
135
|
-
const durationMicroseconds = maxTicks * microsecondsPerTick;
|
|
136
|
-
const durationSeconds = durationMicroseconds / 1000000;
|
|
137
|
-
|
|
138
|
-
return {
|
|
139
|
-
ticks: maxTicks,
|
|
140
|
-
seconds: durationSeconds,
|
|
141
|
-
minutes: Math.floor(durationSeconds / 60),
|
|
142
|
-
remainingSeconds: Math.floor(durationSeconds % 60)
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const originalDuration = calculateDuration(originalMidi);
|
|
147
|
-
const convertedDuration = calculateDuration(convertedMidi);
|
|
148
|
-
|
|
149
|
-
console.log(` Original: ${originalDuration.minutes}:${originalDuration.remainingSeconds.toString().padStart(2, '0')} (${originalDuration.ticks} ticks)`);
|
|
150
|
-
console.log(` Converted: ${convertedDuration.minutes}:${convertedDuration.remainingSeconds.toString().padStart(2, '0')} (${convertedDuration.ticks} ticks)`);
|
|
151
|
-
|
|
152
|
-
const durationDiff = Math.abs(originalDuration.seconds - convertedDuration.seconds);
|
|
153
|
-
console.log(` Time difference: ${durationDiff.toFixed(1)} seconds`);
|
|
154
|
-
|
|
155
|
-
// Summary
|
|
156
|
-
console.log('\n' + '='.repeat(80));
|
|
157
|
-
console.log('SUMMARY');
|
|
158
|
-
console.log('='.repeat(80));
|
|
159
|
-
|
|
160
|
-
const tempoOk = Math.abs(convertedTempo / originalTempo - 1) < 0.05; // within 5%
|
|
161
|
-
const timingOk = originalTimes[5] === convertedTimes[5]; // check 6th event
|
|
162
|
-
const durationOk = durationDiff < 10; // within 10 seconds
|
|
163
|
-
|
|
164
|
-
console.log(`\n✓ Tempo fix implemented: ${tempoOk ? '✅ PASS' : '❌ FAIL'} (within 5%)`);
|
|
165
|
-
console.log(`✓ Timing accuracy: ${timingOk ? '✅ PASS' : '❌ FAIL'} (exact match)`);
|
|
166
|
-
console.log(`✓ Duration accuracy: ${durationOk ? '✅ PASS' : '❌ FAIL'} (within 10s)`);
|
|
167
|
-
|
|
168
|
-
if (tempoOk && timingOk && durationOk) {
|
|
169
|
-
console.log('\n🎉 ALL TESTS PASSED! Tempo fix is working correctly.');
|
|
170
|
-
} else {
|
|
171
|
-
console.log('\n⚠️ Some tests failed. Review the results above.');
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
console.log(`\n✓ Converted file saved: ${outputKar}`);
|
|
175
|
-
console.log('');
|