@karaplay/file-coder 1.5.5 → 1.5.6

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.
@@ -0,0 +1,256 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { decodeEmkServer, convertEmkToKar } = require('./dist/index.js');
6
+ const { Midi } = require('@tonejs/midi');
7
+ const iconv = require('iconv-lite');
8
+
9
+ const emkFile = process.argv[2] || 'songs/emk/Z2510006.emk';
10
+
11
+ console.log('='.repeat(80));
12
+ console.log('šŸ” FULL DEBUG: EMK → KAR → Play Timeline');
13
+ console.log('='.repeat(80));
14
+ console.log('File:', emkFile);
15
+ console.log('');
16
+
17
+ // ============================================================================
18
+ // STEP 1: Decode EMK
19
+ // ============================================================================
20
+ console.log('šŸ“¦ STEP 1: Decoding EMK file...');
21
+ const emkBuffer = fs.readFileSync(emkFile);
22
+ const decoded = decodeEmkServer(emkBuffer);
23
+
24
+ console.log(' āœ… Decoded successfully');
25
+ console.log(' Format:', decoded.isZxioFormat ? 'ZXIO' : 'MThd');
26
+ console.log('');
27
+
28
+ // ============================================================================
29
+ // STEP 2: Analyze Original MIDI Timing
30
+ // ============================================================================
31
+ console.log('šŸŽµ STEP 2: Analyzing ORIGINAL EMK MIDI timing...');
32
+ const originalMidi = new Midi(decoded.midi);
33
+ const originalTempo = originalMidi.header.tempos[0]?.bpm || 120;
34
+ const ppq = originalMidi.header.ppq;
35
+
36
+ console.log(' Tempo:', originalTempo.toFixed(2), 'BPM');
37
+ console.log(' PPQ:', ppq);
38
+ console.log(' Duration:', originalMidi.duration.toFixed(2), 's');
39
+
40
+ // Find first notes
41
+ const firstNotes = [];
42
+ originalMidi.tracks.forEach((track, idx) => {
43
+ if (track.notes.length > 0 && idx > 0) {
44
+ const first = track.notes[0];
45
+ if (first.time < 10) {
46
+ firstNotes.push({ track: idx, time: first.time, midi: first.midi });
47
+ }
48
+ }
49
+ });
50
+ firstNotes.sort((a, b) => a.time - b.time);
51
+
52
+ console.log(' First 5 notes:');
53
+ for (let i = 0; i < Math.min(5, firstNotes.length); i++) {
54
+ console.log(` Track ${firstNotes[i].track}: ${firstNotes[i].time.toFixed(3)}s (note ${firstNotes[i].midi})`);
55
+ }
56
+
57
+ // Find first vocal note (skip intro drums)
58
+ let firstVocalTime = Infinity;
59
+ for (let i = 0; i < firstNotes.length; i++) {
60
+ if (firstNotes[i].time > 1 && firstNotes[i].time < 10) {
61
+ firstVocalTime = firstNotes[i].time;
62
+ break;
63
+ }
64
+ }
65
+ console.log(` → First vocal note: ${firstVocalTime.toFixed(3)}s`);
66
+ console.log('');
67
+
68
+ // ============================================================================
69
+ // STEP 3: Analyze Cursor Raw Data
70
+ // ============================================================================
71
+ console.log('ā±ļø STEP 3: Analyzing CURSOR raw data...');
72
+ const cursorBuffer = decoded.cursor;
73
+
74
+ // Read first 50 cursor values (U16LE)
75
+ const cursorValues = [];
76
+ for (let i = 0; i < Math.min(100, cursorBuffer.length - 1); i += 2) {
77
+ const value = cursorBuffer.readUInt16LE(i);
78
+ if (value !== 0xFFFF) {
79
+ cursorValues.push({ index: i / 2, rawValue: value });
80
+ }
81
+ }
82
+
83
+ console.log(` Total cursor entries: ${cursorValues.length}`);
84
+ console.log(' First 10 raw cursor values (U16LE):');
85
+ for (let i = 0; i < Math.min(10, cursorValues.length); i++) {
86
+ const entry = cursorValues[i];
87
+ // Calculate what this SHOULD be in seconds based on PPQ
88
+ const ticksWithMultiplier = entry.rawValue * (ppq / 24);
89
+ const secondsWithOriginalTempo = (ticksWithMultiplier / ppq) * (60 / originalTempo);
90
+ console.log(` [${entry.index}] raw=${entry.rawValue}, ticks=${ticksWithMultiplier.toFixed(0)}, seconds=${secondsWithOriginalTempo.toFixed(3)}`);
91
+ }
92
+ console.log('');
93
+
94
+ // ============================================================================
95
+ // STEP 4: Analyze Lyric Content
96
+ // ============================================================================
97
+ console.log('šŸ“ STEP 4: Analyzing LYRIC file...');
98
+ const lyricText = iconv.decode(decoded.lyric, 'tis-620');
99
+ const lyricLines = lyricText.split('\n');
100
+
101
+ console.log(` Total lines: ${lyricLines.length}`);
102
+ console.log(' Lines 0-9 (should be: title, artist, key, blank, then lyrics):');
103
+ for (let i = 0; i < Math.min(10, lyricLines.length); i++) {
104
+ const line = lyricLines[i].trim();
105
+ console.log(` [${i}] "${line.substring(0, 50)}${line.length > 50 ? '...' : ''}"`);
106
+ }
107
+ console.log('');
108
+
109
+ // ============================================================================
110
+ // STEP 5: Convert to KAR
111
+ // ============================================================================
112
+ console.log('šŸ”„ STEP 5: Converting EMK → KAR...');
113
+ const tempDir = path.join(__dirname, 'temp');
114
+ if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true });
115
+
116
+ const outputKar = path.join(tempDir, 'full-debug.kar');
117
+ const result = convertEmkToKar({
118
+ inputEmk: emkFile,
119
+ outputKar: outputKar,
120
+ keepIntermediateFiles: false
121
+ });
122
+
123
+ console.log(' āœ… Converted');
124
+ console.log(' Warnings:', result.warnings.length);
125
+ if (result.warnings.length > 0) {
126
+ console.log(' Warnings:');
127
+ result.warnings.forEach(w => console.log(` - ${w}`));
128
+ }
129
+ console.log('');
130
+
131
+ // ============================================================================
132
+ // STEP 6: Analyze KAR MIDI Timing
133
+ // ============================================================================
134
+ console.log('šŸŽ¼ STEP 6: Analyzing KAR MIDI timing...');
135
+ const karBuffer = fs.readFileSync(outputKar);
136
+ const karMidi = new Midi(karBuffer);
137
+ const karTempo = karMidi.header.tempos[0]?.bpm || 120;
138
+
139
+ console.log(' Tempo:', karTempo.toFixed(2), 'BPM');
140
+ console.log(' PPQ:', karMidi.header.ppq);
141
+ console.log(' Duration:', karMidi.duration.toFixed(2), 's');
142
+ console.log(' Tempo ratio:', (karTempo / originalTempo).toFixed(3), 'x');
143
+
144
+ // Find first notes in KAR
145
+ const karFirstNotes = [];
146
+ karMidi.tracks.forEach((track, idx) => {
147
+ if (track.notes.length > 0 && idx > 0) {
148
+ const first = track.notes[0];
149
+ if (first.time < 10) {
150
+ karFirstNotes.push({ track: idx, time: first.time, midi: first.midi });
151
+ }
152
+ }
153
+ });
154
+ karFirstNotes.sort((a, b) => a.time - b.time);
155
+
156
+ console.log(' First 5 notes in KAR:');
157
+ for (let i = 0; i < Math.min(5, karFirstNotes.length); i++) {
158
+ console.log(` Track ${karFirstNotes[i].track}: ${karFirstNotes[i].time.toFixed(3)}s (note ${karFirstNotes[i].midi})`);
159
+ }
160
+
161
+ let karFirstVocalTime = Infinity;
162
+ for (let i = 0; i < karFirstNotes.length; i++) {
163
+ if (karFirstNotes[i].time > 1 && karFirstNotes[i].time < 10) {
164
+ karFirstVocalTime = karFirstNotes[i].time;
165
+ break;
166
+ }
167
+ }
168
+ console.log(` → First vocal note in KAR: ${karFirstVocalTime.toFixed(3)}s`);
169
+ console.log('');
170
+
171
+ // ============================================================================
172
+ // STEP 7: Read KAR Lyrics with karaoke-player
173
+ // ============================================================================
174
+ console.log('šŸ“– STEP 7: Reading KAR lyrics with karaoke-player...');
175
+ const KarFile = require('./demo-libs/KarFile');
176
+ const karFile = new KarFile();
177
+ karFile.readBuffer(karBuffer);
178
+ const lyrics = karFile.getLyrics();
179
+
180
+ console.log(` Total lyrics: ${lyrics.length}`);
181
+ console.log(' First 10 lyrics:');
182
+ for (let i = 0; i < Math.min(10, lyrics.length); i++) {
183
+ const lyric = lyrics[i];
184
+ const text = lyric.text.replace(/\n/g, ' ').substring(0, 40);
185
+ console.log(` [${i}] ${(lyric.time / 1000).toFixed(3)}s - "${text}"`);
186
+ }
187
+
188
+ // Find first real lyric
189
+ let firstRealLyricIndex = -1;
190
+ let firstLyricTime = 0;
191
+ for (let i = 0; i < lyrics.length; i++) {
192
+ const text = lyrics[i].text.replace(/[\.\*\s]/g, '');
193
+ if (text.length > 3 && !/^(Intro|intro)/.test(text)) {
194
+ firstRealLyricIndex = i;
195
+ firstLyricTime = lyrics[i].time / 1000;
196
+ break;
197
+ }
198
+ }
199
+
200
+ if (firstRealLyricIndex >= 0) {
201
+ console.log(` → First real lyric: [${firstRealLyricIndex}] ${firstLyricTime.toFixed(3)}s`);
202
+ console.log(` Text: "${lyrics[firstRealLyricIndex].text.substring(0, 50)}"`);
203
+ }
204
+ console.log('');
205
+
206
+ // ============================================================================
207
+ // STEP 8: DIAGNOSIS
208
+ // ============================================================================
209
+ console.log('šŸ”¬ STEP 8: DIAGNOSIS');
210
+ console.log(' ' + '='.repeat(76));
211
+ console.log('');
212
+
213
+ console.log(' šŸ“Š Timeline Comparison:');
214
+ console.log(' ' + '-'.repeat(76));
215
+ console.log(' Event | EMK Original | KAR File | Difference');
216
+ console.log(' ' + '-'.repeat(76));
217
+ console.log(` First vocal note | ${firstVocalTime.toFixed(3)}s | ${karFirstVocalTime.toFixed(3)}s | ${(karFirstVocalTime - firstVocalTime).toFixed(3)}s`);
218
+ console.log(` First lyric (karaoke-player) | - | ${firstLyricTime.toFixed(3)}s | -`);
219
+ console.log(` Lyric vs Vocal gap | - | - | ${(firstLyricTime - karFirstVocalTime).toFixed(3)}s`);
220
+ console.log(' ' + '-'.repeat(76));
221
+ console.log('');
222
+
223
+ const lyricVocalGap = firstLyricTime - karFirstVocalTime;
224
+
225
+ if (Math.abs(lyricVocalGap) < 0.3) {
226
+ console.log(' āœ… TIMING IS GOOD: Lyrics and vocals are synchronized (< 0.3s gap)');
227
+ } else if (lyricVocalGap < 0) {
228
+ console.log(` āŒ PROBLEM: LYRICS TOO EARLY by ${Math.abs(lyricVocalGap).toFixed(3)}s`);
229
+ console.log('');
230
+ console.log(' šŸ” Root Cause Analysis:');
231
+ console.log(' Lyrics appear BEFORE the singing starts.');
232
+ console.log(' This means cursor timing is NOT being scaled correctly!');
233
+ console.log('');
234
+ console.log(' šŸ’” Expected behavior:');
235
+ console.log(` - Vocal note: ${karFirstVocalTime.toFixed(3)}s`);
236
+ console.log(` - Lyric should appear: ~${karFirstVocalTime.toFixed(3)}s`);
237
+ console.log(` - Actual lyric: ${firstLyricTime.toFixed(3)}s`);
238
+ console.log(` - Gap: ${Math.abs(lyricVocalGap).toFixed(3)}s too early`);
239
+ console.log('');
240
+ console.log(' šŸ”§ Solution:');
241
+ console.log(' The cursor timing values need to be multiplied by the TEMPO RATIO!');
242
+ console.log(` Tempo ratio: ${(karTempo / originalTempo).toFixed(3)}x`);
243
+ console.log(` Cursor should be scaled: rawCursor * ${(karTempo / originalTempo).toFixed(3)}`);
244
+ } else {
245
+ console.log(` āš ļø PROBLEM: LYRICS TOO LATE by ${lyricVocalGap.toFixed(3)}s`);
246
+ console.log(' Lyrics appear AFTER the singing starts.');
247
+ }
248
+
249
+ console.log('');
250
+ console.log(' ' + '='.repeat(76));
251
+ console.log('');
252
+
253
+ // Cleanup
254
+ fs.unlinkSync(outputKar);
255
+
256
+ console.log('āœ… Debug complete!');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@karaplay/file-coder",
3
- "version": "1.5.5",
3
+ "version": "1.5.6",
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",
@@ -30,7 +30,9 @@
30
30
  "test:emk:verbose": "node verify-emk-reference.js --verbose",
31
31
  "analyze:emk": "node analyze-all-emk.js",
32
32
  "prepare": "npm run build",
33
- "demo": "node demo-server.js"
33
+ "demo": "node demo-server.js",
34
+ "stop": "node stop-demo.js",
35
+ "restart": "node restart-demo.js"
34
36
  },
35
37
  "keywords": [
36
38
  "karaoke",
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync, spawn } = require('child_process');
4
+ const os = require('os');
5
+
6
+ const PORT = 3000;
7
+ const platform = os.platform();
8
+
9
+ console.log('šŸ”„ Restarting demo server...\n');
10
+
11
+ // Function to kill process on port
12
+ function killProcessOnPort(port) {
13
+ try {
14
+ if (platform === 'win32') {
15
+ // Windows
16
+ console.log(`šŸ” Finding process on port ${port} (Windows)...`);
17
+ const output = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf8' });
18
+ const lines = output.split('\n').filter(line => line.includes('LISTENING'));
19
+
20
+ if (lines.length > 0) {
21
+ const pid = lines[0].trim().split(/\s+/).pop();
22
+ console.log(`āŒ Killing process ${pid}...`);
23
+ execSync(`taskkill /F /PID ${pid}`);
24
+ console.log('āœ… Old process killed\n');
25
+ } else {
26
+ console.log('āœ… No process found on port (clean start)\n');
27
+ }
28
+ } else {
29
+ // macOS/Linux
30
+ console.log(`šŸ” Finding process on port ${port} (macOS/Linux)...`);
31
+
32
+ // Try method 1: lsof + kill (works in most cases)
33
+ try {
34
+ const output = execSync(`lsof -ti:${port}`, { encoding: 'utf8' });
35
+ const pids = output.trim().split('\n').filter(pid => pid);
36
+
37
+ if (pids.length > 0) {
38
+ pids.forEach(pid => {
39
+ try {
40
+ console.log(`āŒ Killing process ${pid}...`);
41
+ execSync(`kill -9 ${pid}`, { stdio: 'ignore' });
42
+ console.log('āœ… Process killed\n');
43
+ } catch (killErr) {
44
+ // Try pkill as fallback
45
+ try {
46
+ console.log(`āš ļø kill failed, trying pkill...`);
47
+ execSync(`pkill -9 -f "node.*demo-server"`, { stdio: 'ignore' });
48
+ console.log('āœ… Process killed via pkill\n');
49
+ } catch (pkillErr) {
50
+ console.log(`āš ļø Could not kill process (will continue anyway)\n`);
51
+ }
52
+ }
53
+ });
54
+
55
+ // Wait a bit for port to be released
56
+ console.log('ā³ Waiting for port to be released...');
57
+ execSync('sleep 1');
58
+ } else {
59
+ console.log('āœ… No process found on port\n');
60
+ }
61
+ } catch (err) {
62
+ // lsof returns error if no process found - try pkill as last resort
63
+ try {
64
+ execSync(`pkill -9 -f "node.*demo-server"`, { stdio: 'ignore' });
65
+ console.log('āœ… Killed via pkill\n');
66
+ execSync('sleep 1');
67
+ } catch {
68
+ console.log('āœ… No process found on port (clean start)\n');
69
+ }
70
+ }
71
+ }
72
+ } catch (error) {
73
+ console.error('āš ļø Error killing process:', error.message);
74
+ console.log('Continuing anyway...\n');
75
+ }
76
+ }
77
+
78
+ // Kill old process
79
+ killProcessOnPort(PORT);
80
+
81
+ // Start new server
82
+ console.log('šŸš€ Starting demo server...\n');
83
+ console.log('='.repeat(80));
84
+
85
+ const server = spawn('node', ['demo-server.js'], {
86
+ stdio: 'inherit'
87
+ });
88
+
89
+ server.on('error', (err) => {
90
+ console.error('āŒ Failed to start server:', err);
91
+ process.exit(1);
92
+ });
93
+
94
+ // Handle Ctrl+C
95
+ process.on('SIGINT', () => {
96
+ console.log('\n\nšŸ›‘ Stopping server...');
97
+ server.kill('SIGINT');
98
+ process.exit(0);
99
+ });
100
+
101
+ process.on('SIGTERM', () => {
102
+ console.log('\n\nšŸ›‘ Stopping server...');
103
+ server.kill('SIGTERM');
104
+ process.exit(0);
105
+ });
package/start-demo.sh ADDED
@@ -0,0 +1,12 @@
1
+ #!/bin/bash
2
+ echo "======================================"
3
+ echo "šŸŽ¤ Starting EMK to KAR Demo Server"
4
+ echo "======================================"
5
+ echo ""
6
+ echo "Server will start at: http://localhost:3000"
7
+ echo "Demo page: http://localhost:3000/demo-simple.html"
8
+ echo ""
9
+ echo "Press Ctrl+C to stop"
10
+ echo ""
11
+
12
+ node demo-server.js
package/stop-demo.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require('child_process');
4
+ const os = require('os');
5
+
6
+ const PORT = 3000;
7
+ const platform = os.platform();
8
+
9
+ console.log('šŸ›‘ Stopping demo server...\n');
10
+
11
+ // Function to kill process on port
12
+ function killProcessOnPort(port) {
13
+ let killed = false;
14
+
15
+ try {
16
+ if (platform === 'win32') {
17
+ // Windows
18
+ console.log(`šŸ” Finding process on port ${port} (Windows)...`);
19
+ const output = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf8' });
20
+ const lines = output.split('\n').filter(line => line.includes('LISTENING'));
21
+
22
+ if (lines.length > 0) {
23
+ const pid = lines[0].trim().split(/\s+/).pop();
24
+ console.log(`āŒ Killing process ${pid}...`);
25
+ execSync(`taskkill /F /PID ${pid}`);
26
+ console.log('āœ… Server stopped!\n');
27
+ killed = true;
28
+ }
29
+ } else {
30
+ // macOS/Linux
31
+ console.log(`šŸ” Finding process on port ${port} (macOS/Linux)...`);
32
+
33
+ // Try method 1: pkill (most reliable for node processes)
34
+ try {
35
+ console.log(`šŸ” Trying pkill for demo-server...`);
36
+ execSync(`pkill -9 -f "node.*demo-server"`, { encoding: 'utf8' });
37
+ console.log('āœ… Server stopped via pkill!');
38
+ killed = true;
39
+ execSync('sleep 1'); // Wait for port to be released
40
+ } catch (pkillErr) {
41
+ // pkill didn't find process or failed
42
+ }
43
+
44
+ // Try method 2: lsof + kill (if pkill didn't work)
45
+ if (!killed) {
46
+ try {
47
+ const output = execSync(`lsof -ti:${port}`, { encoding: 'utf8' });
48
+ const pids = output.trim().split('\n').filter(pid => pid);
49
+
50
+ if (pids.length > 0) {
51
+ pids.forEach(pid => {
52
+ try {
53
+ console.log(`šŸ” Found process ${pid} on port ${port}`);
54
+ console.log(`āŒ Killing process ${pid}...`);
55
+ execSync(`kill -9 ${pid}`, { encoding: 'utf8' });
56
+ console.log('āœ… Process killed');
57
+ killed = true;
58
+ } catch (killErr) {
59
+ console.log(`āš ļø Could not kill ${pid}: ${killErr.message}`);
60
+ }
61
+ });
62
+ if (killed) {
63
+ execSync('sleep 1'); // Wait for port to be released
64
+ }
65
+ }
66
+ } catch (err) {
67
+ // lsof failed, no process found
68
+ }
69
+ }
70
+ }
71
+
72
+ if (killed) {
73
+ console.log('\nāœ… Demo server stopped successfully!\n');
74
+ } else {
75
+ console.log('\nāš ļø No demo server found running on port', port, '\n');
76
+ }
77
+
78
+ } catch (error) {
79
+ console.log('\nāš ļø No demo server found running\n');
80
+ }
81
+ }
82
+
83
+ // Kill process
84
+ killProcessOnPort(PORT);