@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/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
- import { Player } from 'https://cdn.jsdelivr.net/npm/karaoke-player@latest/dist/index.min.js';
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 karaoke-player to get lyrics
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
- const lyrics = karFile.getLyrics();
197
+ let lyrics = karFile.getLyrics();
192
198
 
193
- // Use Tone.js for accurate MIDI parsing (handles tempo correctly!)
194
- const midi = new Midi(karBuffer);
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 = [];