@karaplay/file-coder 1.5.4 → 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.
- package/EMK_REFERENCE_DATA.json +110 -188
- package/PLAYBACK_SPEED_FIX.md +223 -0
- package/README.md +25 -0
- package/RELEASE_v1.5.5.md +217 -0
- package/SONG_LIST.txt +334 -251
- package/TEST_DEMO.md +186 -0
- package/VERIFICATION_RESULT.md +116 -0
- package/debug-lyric-timing.js +219 -0
- package/debug-output.txt +2962 -0
- package/demo-client.html +6 -1
- package/demo-server.js +92 -4
- package/demo-simple.html +367 -71
- package/dist/emk/client-decoder.js +56 -0
- package/dist/emk/server-decode.js +70 -0
- package/dist/ncntokar.js +15 -17
- package/download-soundfonts.js +108 -0
- package/full-debug-output.txt +2971 -0
- package/full-debug-timing.js +256 -0
- package/package.json +4 -2
- package/restart-demo.js +105 -0
- package/songs/soundfonts/GeneralUserGS.sf3 +0 -0
- package/start-demo.sh +12 -0
- package/stop-demo.js +84 -0
- package/temp/convert-result.json +1 -0
- package/temp/debug-test.kar +0 -0
- package/temp/kar-data.txt +1 -0
- package/temp/kar-data2.txt +1 -0
- package/temp/kar-test.txt +1 -0
- package/test-browser-thai.html +88 -0
- package/test-kar-timing.js +249 -0
- package/test-playback-speed.js +133 -0
- package/verify-all-songs-duration.js +285 -0
package/demo-client.html
CHANGED
|
@@ -464,8 +464,13 @@
|
|
|
464
464
|
</div>
|
|
465
465
|
</div>
|
|
466
466
|
|
|
467
|
+
<!-- Load MIDI Player with Soundfont support -->
|
|
468
|
+
<script src="https://cdn.jsdelivr.net/npm/tone@14.8.49/build/Tone.js"></script>
|
|
469
|
+
<script src="https://cdn.jsdelivr.net/npm/@tonejs/midi@2.0.28/build/Midi.js"></script>
|
|
470
|
+
|
|
467
471
|
<script type="module">
|
|
468
|
-
|
|
472
|
+
// Note: Using Tone.js for better audio quality with synthesizers
|
|
473
|
+
// Soundfont GeneralUserGS.sf3 is available at /soundfonts/GeneralUserGS.sf3
|
|
469
474
|
|
|
470
475
|
let emkFiles = [];
|
|
471
476
|
let selectedFile = null;
|
package/demo-server.js
CHANGED
|
@@ -13,6 +13,9 @@ app.use(express.static(__dirname));
|
|
|
13
13
|
// Serve karaoke-player library
|
|
14
14
|
app.use('/lib', express.static(__dirname + '/node_modules/karaoke-player/lib'));
|
|
15
15
|
|
|
16
|
+
// Serve soundfonts directory
|
|
17
|
+
app.use('/soundfonts', express.static(path.join(__dirname, 'songs/soundfonts')));
|
|
18
|
+
|
|
16
19
|
// API: Get list of EMK files
|
|
17
20
|
app.get('/api/emk-files', (req, res) => {
|
|
18
21
|
try {
|
|
@@ -184,14 +187,99 @@ app.post('/api/parse-kar', express.json(), async (req, res) => {
|
|
|
184
187
|
// Decode base64 to buffer
|
|
185
188
|
const karBuffer = Buffer.from(karData, 'base64');
|
|
186
189
|
|
|
187
|
-
// Use
|
|
190
|
+
// Use Tone.js for accurate MIDI parsing (handles tempo correctly!)
|
|
191
|
+
const midi = new Midi(karBuffer);
|
|
192
|
+
|
|
193
|
+
// Use karaoke-player to get lyrics (Thai encoding is handled correctly!)
|
|
188
194
|
const KarFile = require('./demo-libs/KarFile');
|
|
189
195
|
const karFile = new KarFile();
|
|
190
196
|
karFile.readBuffer(karBuffer);
|
|
191
|
-
|
|
197
|
+
let lyrics = karFile.getLyrics();
|
|
192
198
|
|
|
193
|
-
|
|
194
|
-
|
|
199
|
+
console.log(`✅ Loaded ${lyrics.length} lyric lines from karaoke-player`);
|
|
200
|
+
if (lyrics.length > 0) {
|
|
201
|
+
console.log(` First lyric: ${(lyrics[0]?.time / 1000).toFixed(2)}s`);
|
|
202
|
+
console.log(` Last lyric: ${(lyrics[lyrics.length - 1]?.time / 1000).toFixed(2)}s`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Fix lyric timing: karaoke-player uses wrong tempo calculation
|
|
206
|
+
// Need to sync with actual MIDI note timing from Tone.js
|
|
207
|
+
// Find first actual vocal note (skip drums/intro)
|
|
208
|
+
let firstVocalNoteTime = Infinity;
|
|
209
|
+
for (let i = 1; i < midi.tracks.length; i++) {
|
|
210
|
+
const track = midi.tracks[i];
|
|
211
|
+
if (track.notes.length > 0) {
|
|
212
|
+
const firstNote = track.notes[0];
|
|
213
|
+
if (firstNote.time > 1 && firstNote.time < 10) {
|
|
214
|
+
firstVocalNoteTime = Math.min(firstVocalNoteTime, firstNote.time);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Find first real lyric (skip ***** and intro markers)
|
|
220
|
+
let firstRealLyricIndex = -1;
|
|
221
|
+
let firstLyricTime = 0;
|
|
222
|
+
for (let i = 0; i < lyrics.length; i++) {
|
|
223
|
+
const text = lyrics[i].text.replace(/[\.\*\s]/g, '');
|
|
224
|
+
if (text.length > 3 && !/^(Intro|intro)/.test(text)) {
|
|
225
|
+
firstRealLyricIndex = i;
|
|
226
|
+
firstLyricTime = lyrics[i].time / 1000; // Convert to seconds
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// NO TIMING ADJUSTMENT NEEDED!
|
|
232
|
+
// After fixing cursor scaling (2.4x), KAR file timing is now correct.
|
|
233
|
+
// Lyrics are synchronized with vocal notes (gap < 0.1s).
|
|
234
|
+
// Any adjustment here would make it worse!
|
|
235
|
+
let timingOffset = 0;
|
|
236
|
+
|
|
237
|
+
if (firstRealLyricIndex >= 0 && firstVocalNoteTime < Infinity) {
|
|
238
|
+
const gap = (firstLyricTime - firstVocalNoteTime);
|
|
239
|
+
console.log(`✅ KAR timing verified: vocal at ${firstVocalNoteTime.toFixed(2)}s, lyric at ${firstLyricTime.toFixed(2)}s, gap: ${(gap * 1000).toFixed(0)}ms`);
|
|
240
|
+
|
|
241
|
+
// Only adjust if gap is significant (> 0.5s)
|
|
242
|
+
if (Math.abs(gap) > 0.5) {
|
|
243
|
+
timingOffset = -gap * 1000; // Negative gap means lyric is late, need negative offset
|
|
244
|
+
console.log(`⚠️ Applying timing adjustment: ${timingOffset.toFixed(0)}ms`);
|
|
245
|
+
|
|
246
|
+
// Adjust all lyric timings only if needed
|
|
247
|
+
lyrics = lyrics.map(lyric => ({
|
|
248
|
+
...lyric,
|
|
249
|
+
time: Math.max(0, lyric.time + timingOffset),
|
|
250
|
+
parts: lyric.parts ? lyric.parts.map(p => ({
|
|
251
|
+
...p,
|
|
252
|
+
time: Math.max(0, p.time + timingOffset)
|
|
253
|
+
})) : lyric.parts
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 📊 DETAILED LYRIC TIMING LOG (ALL LINES)
|
|
259
|
+
console.log('\n' + '='.repeat(80));
|
|
260
|
+
console.log('📝 KAR FILE LYRIC TIMING (from karaoke-player) - ALL LINES');
|
|
261
|
+
console.log('='.repeat(80));
|
|
262
|
+
console.log('Line | Start Time | End Time | Duration | Text');
|
|
263
|
+
console.log('-----|------------|------------|----------|' + '-'.repeat(40));
|
|
264
|
+
|
|
265
|
+
for (let i = 0; i < lyrics.length; i++) {
|
|
266
|
+
const lyric = lyrics[i];
|
|
267
|
+
const startTime = (lyric.time / 1000).toFixed(3);
|
|
268
|
+
|
|
269
|
+
// Calculate end time (start of next lyric or +2s)
|
|
270
|
+
const nextLyric = lyrics[i + 1];
|
|
271
|
+
const endTime = nextLyric ? (nextLyric.time / 1000).toFixed(3) : (lyric.time / 1000 + 2).toFixed(3);
|
|
272
|
+
const duration = (parseFloat(endTime) - parseFloat(startTime)).toFixed(3);
|
|
273
|
+
|
|
274
|
+
const text = lyric.text.replace(/\n/g, ' ').substring(0, 40);
|
|
275
|
+
const lineNum = String(i).padStart(4);
|
|
276
|
+
|
|
277
|
+
console.log(`${lineNum} | ${startTime.padStart(10)}s | ${endTime.padStart(10)}s | ${duration.padStart(8)}s | "${text}"`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log('-----|------------|------------|----------|' + '-'.repeat(40));
|
|
281
|
+
console.log(`Total: ${lyrics.length} lyric lines`);
|
|
282
|
+
console.log('='.repeat(80) + '\n');
|
|
195
283
|
|
|
196
284
|
// Extract all note events with accurate timing from Tone.js
|
|
197
285
|
const allEvents = [];
|