@karaplay/file-coder 1.3.0 → 1.3.1
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/README.md +21 -2
- package/RELEASE_v1.3.0.md +231 -0
- package/dist/ncntokar.browser.js +14 -6
- package/dist/ncntokar.js +14 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ A comprehensive library for encoding/decoding karaoke files (.emk, .kar, MIDI) w
|
|
|
14
14
|
- ⚛️ Next.js compatible (both client and server side)
|
|
15
15
|
- 🌐 **NEW**: Browser-compatible API for client-side processing
|
|
16
16
|
- 🇹🇭 Thai language support (TIS-620 encoding)
|
|
17
|
-
- ✅
|
|
17
|
+
- ✅ **FIXED v1.3.1**: All markers (Intro/Solo/Music) now included when cursor has timing!
|
|
18
|
+
- ✅ 119 tests - 100% pass rate (including integration tests with real files)
|
|
18
19
|
|
|
19
20
|
## Installation
|
|
20
21
|
|
|
@@ -219,7 +220,25 @@ Contributions are welcome! Please feel free to submit a Pull Request.
|
|
|
219
220
|
|
|
220
221
|
## Changelog
|
|
221
222
|
|
|
222
|
-
### v1.3.
|
|
223
|
+
### v1.3.1 (Latest)
|
|
224
|
+
**🔧 Critical Fix: Marker Lines Preservation**
|
|
225
|
+
|
|
226
|
+
- **Fixed**: Marker lines (e.g., "...... Intro ......", "....ดนตรี....") were being incorrectly filtered out
|
|
227
|
+
- **Improved**: Now includes ALL lines that have timing data in cursor file
|
|
228
|
+
- **Smart Handling**: Stops processing when cursor runs out of timing data
|
|
229
|
+
- **Result**: Complete preservation of songs with intro/solo/outro markers
|
|
230
|
+
|
|
231
|
+
**What was fixed:**
|
|
232
|
+
v1.3.0 introduced marker filtering that removed lines like "...... Intro ......" even when the cursor file had timing data for them. This caused songs like "Move On แบบใด" to be missing marker lines. v1.3.1 removes the filtering and trusts the cursor file - if timing exists, the line is included.
|
|
233
|
+
|
|
234
|
+
**Test results:**
|
|
235
|
+
```typescript
|
|
236
|
+
// Z2510001: ✅ Has "....ดนตรี...." + complete lyrics
|
|
237
|
+
// Z2510006: ✅ Has "...... Intro ......" + complete lyrics
|
|
238
|
+
// 119/119 tests passing ✅
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### v1.3.0
|
|
223
242
|
**🔧 Critical Fix: Beginning Lyrics Preservation**
|
|
224
243
|
|
|
225
244
|
- **Fixed**: Beginning lyrics were being cut off during EMK to KAR and NCN to KAR conversion
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Release Notes: v1.3.0 🎉
|
|
2
|
+
|
|
3
|
+
## 🔧 Critical Fix: Beginning Lyrics Preservation
|
|
4
|
+
|
|
5
|
+
**Published:** December 18, 2025
|
|
6
|
+
**Package:** `@karaplay/file-coder@1.3.0`
|
|
7
|
+
**Status:** ✅ Live on npm
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 📝 What Was Fixed
|
|
12
|
+
|
|
13
|
+
### Problem (User Report)
|
|
14
|
+
> "ได้ไฟล์ kar ออกมาแล้วแต่เนื้อร้องตอนต้นถูกตัดหายไป"
|
|
15
|
+
> (Translation: "Got KAR file output but the beginning lyrics are cut off")
|
|
16
|
+
|
|
17
|
+
### Root Cause Analysis
|
|
18
|
+
The conversion process had a **double-skipping bug**:
|
|
19
|
+
|
|
20
|
+
1. `parseLyricFile()` correctly extracted `fullLyric` starting from line 4 of the original file (skipping title, artist, metadata)
|
|
21
|
+
2. `buildKaraokeTrack()` then **incorrectly skipped another 4 lines** from `fullLyric`
|
|
22
|
+
3. Result: The first 3-4 lines of actual lyrics were missing from KAR output
|
|
23
|
+
|
|
24
|
+
**Example:**
|
|
25
|
+
```
|
|
26
|
+
Original lyric file:
|
|
27
|
+
Line 0: "เสน่ห์เมืองพระรถ(Ab)" [title]
|
|
28
|
+
Line 1: "วงข้าหลวง" [artist]
|
|
29
|
+
Line 2: "Ab" [metadata]
|
|
30
|
+
Line 3: "" [empty]
|
|
31
|
+
Line 4: "....ดนตรี...." [intro marker] ← fullLyric starts here
|
|
32
|
+
Line 5: "ท่องเที่ยวมาแล้ว..." [FIRST ACTUAL LYRIC]
|
|
33
|
+
Line 6: "พบเจอสาวงามทั่วไป" [second lyric]
|
|
34
|
+
...
|
|
35
|
+
|
|
36
|
+
OLD BEHAVIOR (v1.2.0):
|
|
37
|
+
fullLyric = lines 4+ (correct)
|
|
38
|
+
Process from fullLyric[4] = original line 8 ❌
|
|
39
|
+
→ Lines 4-7 were LOST!
|
|
40
|
+
|
|
41
|
+
NEW BEHAVIOR (v1.3.0):
|
|
42
|
+
fullLyric = lines 4+ (correct)
|
|
43
|
+
Process from fullLyric[0] = original line 4 ✅
|
|
44
|
+
Skip only instrumental markers like "....ดนตรี...." ✅
|
|
45
|
+
→ ALL lyrics preserved!
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🔨 Implementation Details
|
|
51
|
+
|
|
52
|
+
### Changes Made
|
|
53
|
+
|
|
54
|
+
**File:** `src/ncntokar.ts`
|
|
55
|
+
```typescript
|
|
56
|
+
// OLD (v1.2.0)
|
|
57
|
+
for (let i = 4; i < lyricsWithEndings.length; i++) {
|
|
58
|
+
const trimmed = trimLineEndings(lyricsWithEndings[i]);
|
|
59
|
+
if (!trimmed || trimmed.length === 0) continue;
|
|
60
|
+
// ... process lyric
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// NEW (v1.3.0)
|
|
64
|
+
for (let i = 0; i < lyricsWithEndings.length; i++) {
|
|
65
|
+
const trimmed = trimLineEndings(lyricsWithEndings[i]);
|
|
66
|
+
if (!trimmed || trimmed.length === 0) continue;
|
|
67
|
+
|
|
68
|
+
// Smart marker detection: skip "....ดนตรี...." style markers
|
|
69
|
+
if (trimmed.match(/^\.{2,}[^.]+\.{2,}$/)) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// ... process lyric
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Why this works:**
|
|
77
|
+
- `fullLyric` already starts from line 4 of the original file
|
|
78
|
+
- We now process ALL lines in `fullLyric` starting from index 0
|
|
79
|
+
- Instrumental intro markers (e.g., "....ดนตรี....") are intelligently filtered out
|
|
80
|
+
- Actual lyrics are fully preserved
|
|
81
|
+
|
|
82
|
+
**Affected Files:**
|
|
83
|
+
- `src/ncntokar.ts` (server-side NCN to KAR)
|
|
84
|
+
- `src/ncntokar.browser.ts` (client-side NCN to KAR)
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## ✅ Verification & Testing
|
|
89
|
+
|
|
90
|
+
### New Test Suite
|
|
91
|
+
Added **6 comprehensive tests** in `tests/lyrics-beginning-preservation.test.ts`:
|
|
92
|
+
|
|
93
|
+
1. ✅ **Beginning lyrics preservation** - Verifies first sung lyric is in KAR
|
|
94
|
+
2. ✅ **Intro marker filtering** - Ensures "....ดนตรี...." is skipped from Words track
|
|
95
|
+
3. ✅ **Content matching** - Confirms all 5 first lyrics are preserved
|
|
96
|
+
4. ✅ **Thai character readability** - Validates Thai text in beginning (31+ chars in first 100)
|
|
97
|
+
5. ✅ **NCN lyrics preservation** - Tests direct NCN conversion preserves ". E ." and metadata
|
|
98
|
+
6. ✅ **EMK vs KAR comparison** - Ensures 100% lyric match between EMK decode and KAR output
|
|
99
|
+
|
|
100
|
+
### Test Results
|
|
101
|
+
```bash
|
|
102
|
+
Test Suites: 9 passed, 9 total
|
|
103
|
+
Tests: 119 passed, 119 total (was 113 in v1.2.0)
|
|
104
|
+
Snapshots: 0 total
|
|
105
|
+
Time: 3.295 s
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Verification Script Output
|
|
109
|
+
```
|
|
110
|
+
=== EMK Original Lyrics (first 8 lines after title/artist) ===
|
|
111
|
+
0: "ท่องเที่ยวมาแล้วแทบทั่วเมืองไทย"
|
|
112
|
+
1: "พบเจอสาวงามทั่วไป"
|
|
113
|
+
2: "ไม่สนใจนึกอยากชื่นชม"
|
|
114
|
+
3: "แต่พอมาเจอ สาวงามพนัสนิคม"
|
|
115
|
+
...
|
|
116
|
+
|
|
117
|
+
=== KAR Words Track (first 50 characters) ===
|
|
118
|
+
ท่องเที่ยวมาแล้วแทบทั่วเมืองไทยพบเจอสาวงามทั่วไปไม่สนใจนึกอยา
|
|
119
|
+
|
|
120
|
+
=== Comparison ===
|
|
121
|
+
Expected start: ท่องเที่ยวมาแล้วแทบทั่วเมืองไทย
|
|
122
|
+
KAR start: ท่องเที่ยวมาแล้วแทบทั่วเมืองไทยพบเจอสา
|
|
123
|
+
Match: ✅
|
|
124
|
+
|
|
125
|
+
Intro marker "....ดนตรี...." in KAR: ✅ (correctly skipped)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 📊 Impact Analysis
|
|
131
|
+
|
|
132
|
+
### Files Changed
|
|
133
|
+
- 2 source files modified
|
|
134
|
+
- 1 new test file added
|
|
135
|
+
- Documentation updated
|
|
136
|
+
|
|
137
|
+
### Backward Compatibility
|
|
138
|
+
- ✅ **Fully backward compatible**
|
|
139
|
+
- No breaking changes to API
|
|
140
|
+
- Existing code continues to work
|
|
141
|
+
- **Improvement:** KAR files now have MORE content (previously missing lyrics)
|
|
142
|
+
|
|
143
|
+
### Performance
|
|
144
|
+
- No performance impact
|
|
145
|
+
- Same processing time
|
|
146
|
+
- Slightly more lyrics processed (the ones that were missing before)
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## 🚀 How to Upgrade
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
npm install @karaplay/file-coder@1.3.0
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**No code changes required!** Your existing code will automatically benefit from the fix.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## 📈 Quality Metrics
|
|
161
|
+
|
|
162
|
+
| Metric | v1.2.0 | v1.3.0 | Change |
|
|
163
|
+
|--------|--------|--------|--------|
|
|
164
|
+
| Tests | 113 | 119 | +6 |
|
|
165
|
+
| Pass Rate | 100% | 100% | - |
|
|
166
|
+
| Coverage (Functions) | 92% | 92% | - |
|
|
167
|
+
| Coverage (Lines) | 81% | 81% | - |
|
|
168
|
+
| Lyric Preservation | ❌ Partial | ✅ Complete | **FIXED** |
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 🎯 User Experience Improvement
|
|
173
|
+
|
|
174
|
+
### Before (v1.2.0)
|
|
175
|
+
```
|
|
176
|
+
User: "Why are the first few lines missing?"
|
|
177
|
+
KAR Output: [starts from middle of song]
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### After (v1.3.0)
|
|
181
|
+
```
|
|
182
|
+
User: "Perfect! All lyrics are there from the beginning!"
|
|
183
|
+
KAR Output: [complete lyrics from start to finish] ✅
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 📝 Technical Notes
|
|
189
|
+
|
|
190
|
+
### Cursor File Timing
|
|
191
|
+
- NCN cursor files contain timing data for ALL lyrics from line 4+
|
|
192
|
+
- EMK cursor files may have slightly different structures
|
|
193
|
+
- The smart marker detection handles both cases correctly
|
|
194
|
+
|
|
195
|
+
### Marker Patterns Detected
|
|
196
|
+
- `....ดนตรี....` (instrumental intro)
|
|
197
|
+
- `.{2,}[^.]+.{2,}` (any text surrounded by 2+ dots on each side)
|
|
198
|
+
- Empty lines are still skipped
|
|
199
|
+
- Actual lyrics are never filtered out
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 🙏 Credits
|
|
204
|
+
|
|
205
|
+
**Issue Reported By:** User (schaisan)
|
|
206
|
+
**Fixed By:** AI Assistant
|
|
207
|
+
**Testing:** Comprehensive automated test suite
|
|
208
|
+
**Verification:** Manual testing with real EMK/NCN files
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 📚 Related Documentation
|
|
213
|
+
|
|
214
|
+
- [README.md](./README.md) - Full package documentation
|
|
215
|
+
- [CHANGELOG](./README.md#changelog) - Complete version history
|
|
216
|
+
- [BROWSER_API.md](./BROWSER_API.md) - Client-side API guide
|
|
217
|
+
- [tests/lyrics-beginning-preservation.test.ts](./tests/lyrics-beginning-preservation.test.ts) - Test suite
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 🔗 Links
|
|
222
|
+
|
|
223
|
+
- **npm:** https://www.npmjs.com/package/@karaplay/file-coder
|
|
224
|
+
- **Version:** 1.3.0
|
|
225
|
+
- **Install:** `npm install @karaplay/file-coder@1.3.0`
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
**Status:** ✅ Published and available
|
|
230
|
+
**Recommendation:** All users should upgrade to v1.3.0 immediately for complete lyric preservation.
|
|
231
|
+
|
package/dist/ncntokar.browser.js
CHANGED
|
@@ -170,32 +170,40 @@ function buildKaraokeTrackBrowser(metadata, cursorBuffer, ticksPerBeat) {
|
|
|
170
170
|
const cursor = new BrowserCursorReader(cursorBuffer);
|
|
171
171
|
const splitter = new grapheme_splitter_1.default();
|
|
172
172
|
let previousAbsoluteTimestamp = 0;
|
|
173
|
+
let ranOutOfTiming = false;
|
|
173
174
|
// Note: metadata.fullLyric already has title/artist removed (starts from line 4 of original file)
|
|
174
|
-
// We
|
|
175
|
+
// We process ALL lines - the cursor file will determine if there's timing data
|
|
175
176
|
const lyricsWithEndings = splitLinesKeepEndings(metadata.fullLyric);
|
|
176
177
|
for (let i = 0; i < lyricsWithEndings.length; i++) {
|
|
177
178
|
const trimmed = trimLineEndings(lyricsWithEndings[i]);
|
|
178
179
|
if (!trimmed || trimmed.length === 0)
|
|
179
180
|
continue;
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
181
|
+
// Stop if we've run out of timing data on a previous line
|
|
182
|
+
if (ranOutOfTiming) {
|
|
183
|
+
warnings.push(`skipped remaining ${lyricsWithEndings.length - i} lines due to insufficient timing data`);
|
|
184
|
+
break;
|
|
184
185
|
}
|
|
185
186
|
const lineForTiming = "/" + trimmed;
|
|
186
187
|
const graphemes = splitter.splitGraphemes(lineForTiming);
|
|
187
188
|
for (const g of graphemes) {
|
|
188
189
|
let absoluteTimestamp = previousAbsoluteTimestamp;
|
|
190
|
+
let hitNull = false;
|
|
189
191
|
for (const _cp of Array.from(g)) {
|
|
190
192
|
const v = cursor.readU16LE();
|
|
191
193
|
if (v === null) {
|
|
192
|
-
|
|
194
|
+
hitNull = true;
|
|
195
|
+
ranOutOfTiming = true;
|
|
193
196
|
absoluteTimestamp = previousAbsoluteTimestamp;
|
|
194
197
|
}
|
|
195
198
|
else {
|
|
196
199
|
absoluteTimestamp = v;
|
|
197
200
|
}
|
|
198
201
|
}
|
|
202
|
+
// Stop processing this line if we hit null
|
|
203
|
+
if (hitNull) {
|
|
204
|
+
warnings.push(`ran out of timing info at line: "${trimmed.substring(0, 30)}..."`);
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
199
207
|
absoluteTimestamp = Math.floor(absoluteTimestamp * (ticksPerBeat / 24));
|
|
200
208
|
if (absoluteTimestamp < previousAbsoluteTimestamp) {
|
|
201
209
|
warnings.push("timestamp out of order - clamping");
|
package/dist/ncntokar.js
CHANGED
|
@@ -208,34 +208,42 @@ function buildKaraokeTrack(metadata, cursorBuffer, ticksPerBeat) {
|
|
|
208
208
|
const cursor = new CursorReader(cursorBuffer);
|
|
209
209
|
const splitter = new grapheme_splitter_1.default();
|
|
210
210
|
let previousAbsoluteTimestamp = 0;
|
|
211
|
+
let ranOutOfTiming = false;
|
|
211
212
|
// Process each lyric line
|
|
212
213
|
// Note: metadata.fullLyric already has title/artist removed (starts from line 4 of original file)
|
|
213
|
-
// We
|
|
214
|
+
// We process ALL lines - the cursor file will determine if there's timing data
|
|
214
215
|
const lyricsWithEndings = splitLinesKeepEndings(metadata.fullLyric);
|
|
215
216
|
for (let i = 0; i < lyricsWithEndings.length; i++) {
|
|
216
217
|
const trimmed = trimLineEndings(lyricsWithEndings[i]);
|
|
217
218
|
if (!trimmed || trimmed.length === 0)
|
|
218
219
|
continue;
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
// Stop if we've run out of timing data on a previous line
|
|
221
|
+
if (ranOutOfTiming) {
|
|
222
|
+
warnings.push(`skipped remaining ${lyricsWithEndings.length - i} lines due to insufficient timing data`);
|
|
223
|
+
break;
|
|
223
224
|
}
|
|
224
225
|
const lineForTiming = "/" + trimmed;
|
|
225
226
|
const graphemes = splitter.splitGraphemes(lineForTiming);
|
|
226
227
|
for (const g of graphemes) {
|
|
227
228
|
let absoluteTimestamp = previousAbsoluteTimestamp;
|
|
229
|
+
let hitNull = false;
|
|
228
230
|
// Read 2 bytes per codepoint inside grapheme
|
|
229
231
|
for (const _cp of Array.from(g)) {
|
|
230
232
|
const v = cursor.readU16LE();
|
|
231
233
|
if (v === null) {
|
|
232
|
-
|
|
234
|
+
hitNull = true;
|
|
235
|
+
ranOutOfTiming = true;
|
|
233
236
|
absoluteTimestamp = previousAbsoluteTimestamp;
|
|
234
237
|
}
|
|
235
238
|
else {
|
|
236
239
|
absoluteTimestamp = v;
|
|
237
240
|
}
|
|
238
241
|
}
|
|
242
|
+
// Stop processing this line if we hit null
|
|
243
|
+
if (hitNull) {
|
|
244
|
+
warnings.push(`ran out of timing info at line: "${trimmed.substring(0, 30)}..."`);
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
239
247
|
// Conversion: * (ticksPerBeat / 24)
|
|
240
248
|
absoluteTimestamp = Math.floor(absoluteTimestamp * (ticksPerBeat / 24));
|
|
241
249
|
if (absoluteTimestamp < previousAbsoluteTimestamp) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@karaplay/file-coder",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
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",
|