text-guitar-chart 0.0.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/AGENTS.md +18 -0
- package/FORMAT.md +277 -0
- package/LICENSE +21 -0
- package/README.md +41 -0
- package/docs/app.js +172 -0
- package/docs/bundle.js +8712 -0
- package/docs/bundle.js.map +7 -0
- package/docs/index.html +93 -0
- package/index.js +7 -0
- package/lib/editableSVGuitar.js +1050 -0
- package/lib/fingeringToString.js +261 -0
- package/lib/layoutChordStrings.js +92 -0
- package/lib/splitStringInRectangles.js +132 -0
- package/lib/stringToFingering.js +435 -0
- package/package.json +43 -0
- package/scripts/build.js +30 -0
- package/test/editableSVGuitar.test.js +444 -0
- package/test/fingeringToString.test.js +705 -0
- package/test/layoutChordStrings.test.js +193 -0
- package/test/splitStringInRectangles.test.js +98 -0
- package/test/stringToFingering.test.js +1086 -0
- package/tsconfig.json +25 -0
- package/types/editableSVGuitar.d.ts +209 -0
- package/types/fingeringToString.d.ts +10 -0
- package/types/layoutChordStrings.d.ts +10 -0
- package/types/splitStringInRectangles.d.ts +18 -0
- package/types/stringToFingering.d.ts +10 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
//@ts-check
|
|
2
|
+
|
|
3
|
+
// ASCII format characters
|
|
4
|
+
const ASCII_VERTICAL = "|";
|
|
5
|
+
const ASCII_DASH = "-";
|
|
6
|
+
const ASCII_EQUALS = "=";
|
|
7
|
+
const ASCII_OPEN = "o";
|
|
8
|
+
const ASCII_MUTED = "x";
|
|
9
|
+
const ASCII_ROOT = "*";
|
|
10
|
+
|
|
11
|
+
// Unicode format characters
|
|
12
|
+
const UNICODE_VERTICAL = "│";
|
|
13
|
+
const UNICODE_OPEN = "○";
|
|
14
|
+
const UNICODE_MUTED = "×";
|
|
15
|
+
const UNICODE_ROOT = "●";
|
|
16
|
+
|
|
17
|
+
// Unicode box drawing characters for grid detection
|
|
18
|
+
// Includes both double-line (╒═╤╕) and light (┌─┬┐) box-drawing sets
|
|
19
|
+
const UNICODE_BOX_CHARS = "╒═╤╕├─┼┤└┴┘┌┬┐";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Detect if the string uses Unicode format
|
|
23
|
+
* @param {string} str
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
function isUnicodeFormat(str) {
|
|
27
|
+
return (
|
|
28
|
+
str.includes(UNICODE_VERTICAL) ||
|
|
29
|
+
str.includes(UNICODE_OPEN) ||
|
|
30
|
+
str.includes(UNICODE_ROOT) ||
|
|
31
|
+
str.includes(UNICODE_MUTED) ||
|
|
32
|
+
[...UNICODE_BOX_CHARS].some(c => str.includes(c))
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Find grid boundaries for Unicode format
|
|
38
|
+
* Unicode grids use box-drawing characters with consistent spacing.
|
|
39
|
+
* The grid line looks like: ╒═╤═╤═╤═╤═╕ or │ │ │ │ │ │
|
|
40
|
+
* Separators are at even positions, cells (where notes go) are at odd positions.
|
|
41
|
+
* @param {string[]} lines
|
|
42
|
+
* @param {number} firstGridRowIdx
|
|
43
|
+
* @returns {{ startCol: number, endCol: number, numStrings: number }}
|
|
44
|
+
*/
|
|
45
|
+
function findUnicodeGridBoundaries(lines, firstGridRowIdx) {
|
|
46
|
+
let minPos = Infinity;
|
|
47
|
+
let maxPos = -1;
|
|
48
|
+
|
|
49
|
+
// Scan all grid lines to find the full extent
|
|
50
|
+
for (let i = firstGridRowIdx; i < lines.length; i++) {
|
|
51
|
+
const line = lines[i];
|
|
52
|
+
for (let j = 0; j < line.length; j++) {
|
|
53
|
+
const char = line[j];
|
|
54
|
+
if (char === UNICODE_VERTICAL || "╒╤╕├┼┤└┴┘═─┌┬┐".includes(char)) {
|
|
55
|
+
if (j < minPos) minPos = j;
|
|
56
|
+
if (j > maxPos) maxPos = j;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (minPos === Infinity || maxPos === -1) {
|
|
62
|
+
return { startCol: 0, endCol: 0, numStrings: 0 };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// In Unicode format, grid cells are spaced every 2 characters
|
|
66
|
+
// For a grid spanning positions 2-12 (width 11), we have 6 positions: 2,4,6,8,10,12
|
|
67
|
+
// Which maps to 6 strings
|
|
68
|
+
const numStrings = Math.floor((maxPos - minPos) / 2) + 1;
|
|
69
|
+
return { startCol: minPos, endCol: maxPos, numStrings };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Map a character position to a string number for Unicode format (1-6, right to left)
|
|
74
|
+
* @param {number} charPos - Character position in the line
|
|
75
|
+
* @param {number} startCol - Start column of the grid
|
|
76
|
+
* @param {number} numStrings - Number of strings
|
|
77
|
+
* @returns {number} String number (1-6) or -1 if out of bounds
|
|
78
|
+
*/
|
|
79
|
+
function unicodeCharPosToStringNum(charPos, startCol, numStrings) {
|
|
80
|
+
// In Unicode format, positions are at startCol, startCol+2, startCol+4, etc.
|
|
81
|
+
const offset = charPos - startCol;
|
|
82
|
+
if (offset < 0 || offset % 2 !== 0) return -1;
|
|
83
|
+
const idx = offset / 2;
|
|
84
|
+
if (idx >= numStrings) return -1;
|
|
85
|
+
// String 6 is at idx 0, string 1 is at idx (numStrings - 1)
|
|
86
|
+
return numStrings - idx;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Find grid boundaries for ASCII format
|
|
91
|
+
* In the new format, the grid looks like: ------
|
|
92
|
+
* And fret rows look like: |||||| or |||o||
|
|
93
|
+
* The first dash/equals line or pipe sequence determines the grid extent.
|
|
94
|
+
* @param {string[]} lines
|
|
95
|
+
* @param {number} firstGridRowIdx
|
|
96
|
+
* @returns {{ startCol: number, endCol: number, numStrings: number }}
|
|
97
|
+
*/
|
|
98
|
+
function findAsciiGridBoundaries(lines, firstGridRowIdx) {
|
|
99
|
+
let minPos = Infinity;
|
|
100
|
+
let maxPos = -1;
|
|
101
|
+
|
|
102
|
+
// Scan all grid lines to find the full extent of the grid
|
|
103
|
+
for (let i = firstGridRowIdx; i < lines.length; i++) {
|
|
104
|
+
const line = lines[i];
|
|
105
|
+
|
|
106
|
+
// Look for continuous sequences of pipes or dashes/equals
|
|
107
|
+
let inSequence = false;
|
|
108
|
+
let seqStart = -1;
|
|
109
|
+
|
|
110
|
+
for (let j = 0; j < line.length; j++) {
|
|
111
|
+
const char = line[j];
|
|
112
|
+
const isGridChar = char === ASCII_VERTICAL || char === ASCII_DASH || char === ASCII_EQUALS;
|
|
113
|
+
|
|
114
|
+
if (isGridChar && !inSequence) {
|
|
115
|
+
// Start of sequence
|
|
116
|
+
inSequence = true;
|
|
117
|
+
seqStart = j;
|
|
118
|
+
} else if (!isGridChar && inSequence) {
|
|
119
|
+
// End of sequence
|
|
120
|
+
inSequence = false;
|
|
121
|
+
const seqEnd = j - 1;
|
|
122
|
+
if (seqStart < minPos) minPos = seqStart;
|
|
123
|
+
if (seqEnd > maxPos) maxPos = seqEnd;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Handle sequence that extends to end of line
|
|
128
|
+
if (inSequence) {
|
|
129
|
+
const seqEnd = line.length - 1;
|
|
130
|
+
if (seqStart < minPos) minPos = seqStart;
|
|
131
|
+
if (seqEnd > maxPos) maxPos = seqEnd;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (minPos === Infinity || maxPos === -1) {
|
|
136
|
+
return { startCol: 0, endCol: 0, numStrings: 0 };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Number of strings is the span from minPos to maxPos inclusive
|
|
140
|
+
const numStrings = maxPos - minPos + 1;
|
|
141
|
+
return { startCol: minPos, endCol: maxPos, numStrings };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Map a character position to a string number for ASCII format (1-6, right to left)
|
|
146
|
+
* @param {number} charPos - Character position in the line
|
|
147
|
+
* @param {number} startCol - Start column of the grid
|
|
148
|
+
* @param {number} numStrings - Number of strings (typically 6)
|
|
149
|
+
* @returns {number} String number (1-6) or -1 if out of bounds
|
|
150
|
+
*/
|
|
151
|
+
function asciiCharPosToStringNumber(charPos, startCol, numStrings) {
|
|
152
|
+
const offset = charPos - startCol;
|
|
153
|
+
if (offset < 0 || offset >= numStrings) return -1;
|
|
154
|
+
// String 6 is at offset 0, string 1 is at offset (numStrings - 1)
|
|
155
|
+
return numStrings - offset;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Check if a line is a grid row (contains structural characters like pipes, dashes, box drawing)
|
|
160
|
+
* In new ASCII format: ------ or ====== for separator, |||||| for content
|
|
161
|
+
* @param {string} line
|
|
162
|
+
* @param {boolean} isUnicode
|
|
163
|
+
* @returns {boolean}
|
|
164
|
+
*/
|
|
165
|
+
function isGridRow(line, isUnicode) {
|
|
166
|
+
if (isUnicode) {
|
|
167
|
+
// For Unicode, look for box drawing characters or vertical bars
|
|
168
|
+
let count = 0;
|
|
169
|
+
for (const char of line) {
|
|
170
|
+
if (char === UNICODE_VERTICAL || "╒╤╕├┼┤└┴┘┌┬┐".includes(char)) count++;
|
|
171
|
+
}
|
|
172
|
+
return count >= 2;
|
|
173
|
+
} else {
|
|
174
|
+
// For ASCII, check for:
|
|
175
|
+
// 1. Separator lines: 4+ consecutive dashes or equals
|
|
176
|
+
// 2. Content lines: multiple pipes (even if interrupted by notes/digits)
|
|
177
|
+
const hasDashes = /-{4,}/.test(line);
|
|
178
|
+
const hasEquals = /={4,}/.test(line);
|
|
179
|
+
if (hasDashes || hasEquals) return true;
|
|
180
|
+
|
|
181
|
+
// Count pipes for content rows (allow notes/digits between pipes)
|
|
182
|
+
const pipeCount = (line.match(/\|/g) || []).length;
|
|
183
|
+
return pipeCount >= 4;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check if a Unicode line is a fret content row (has │) vs separator row (has ├, ─, etc.)
|
|
189
|
+
* @param {string} line
|
|
190
|
+
* @returns {boolean}
|
|
191
|
+
*/
|
|
192
|
+
function isUnicodeFretRow(line) {
|
|
193
|
+
// Fret rows contain │ (vertical bar with content), not just ├┼┤ (horizontal connectors)
|
|
194
|
+
if (line.includes(UNICODE_VERTICAL)) return true;
|
|
195
|
+
|
|
196
|
+
// Also check for note characters (for rows with notes but no vertical bars)
|
|
197
|
+
const noteChars = [UNICODE_OPEN, UNICODE_ROOT, UNICODE_MUTED];
|
|
198
|
+
for (const char of line) {
|
|
199
|
+
if (noteChars.includes(char) || /[0-9]/.test(char)) return true;
|
|
200
|
+
}
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Check if an ASCII line is a fret content row (has |) vs separator row (has - or =)
|
|
206
|
+
* @param {string} line
|
|
207
|
+
* @returns {boolean}
|
|
208
|
+
*/
|
|
209
|
+
function isAsciiFretRow(line) {
|
|
210
|
+
// Separator rows contain dashes or equals
|
|
211
|
+
if (/-{4,}/.test(line) || /={4,}/.test(line)) return false;
|
|
212
|
+
|
|
213
|
+
// Fret rows contain pipes
|
|
214
|
+
if (line.includes(ASCII_VERTICAL)) return true;
|
|
215
|
+
|
|
216
|
+
// Also check for note characters (for rows with notes but no pipes)
|
|
217
|
+
const noteChars = [ASCII_OPEN, ASCII_ROOT, ASCII_MUTED];
|
|
218
|
+
for (const char of line) {
|
|
219
|
+
if (noteChars.includes(char) || /[0-9]/.test(char)) return true;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Parse a string representation of a guitar fingering into internal format
|
|
226
|
+
* @param {string} fingeringStr - The string representation of the fingering
|
|
227
|
+
* @param {{ redColor?: string, blackColor?: string }} [options]
|
|
228
|
+
* @returns {import("svguitar").Chord | null} The parsed fingering object
|
|
229
|
+
*/
|
|
230
|
+
export default function stringToFingering(fingeringStr, options = {}) {
|
|
231
|
+
const { redColor = "#e74c3c", blackColor = "#000000" } = options;
|
|
232
|
+
|
|
233
|
+
if (!fingeringStr || fingeringStr.trim() === "") {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const lines = fingeringStr.split("\n");
|
|
238
|
+
const isUnicode = isUnicodeFormat(fingeringStr);
|
|
239
|
+
|
|
240
|
+
const openChar = isUnicode ? UNICODE_OPEN : ASCII_OPEN;
|
|
241
|
+
const mutedChar = isUnicode ? UNICODE_MUTED : ASCII_MUTED;
|
|
242
|
+
const rootChar = isUnicode ? UNICODE_ROOT : ASCII_ROOT;
|
|
243
|
+
|
|
244
|
+
/** @type {import("svguitar").Finger[]} */
|
|
245
|
+
const fingers = [];
|
|
246
|
+
/** @type {string | undefined} */
|
|
247
|
+
let title;
|
|
248
|
+
/** @type {number | undefined} */
|
|
249
|
+
let position;
|
|
250
|
+
|
|
251
|
+
// Find the first grid row
|
|
252
|
+
let firstGridRowIdx = -1;
|
|
253
|
+
for (let i = 0; i < lines.length; i++) {
|
|
254
|
+
if (isGridRow(lines[i], isUnicode)) {
|
|
255
|
+
firstGridRowIdx = i;
|
|
256
|
+
break;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (firstGridRowIdx === -1) {
|
|
261
|
+
// No valid grid found
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Get column mapping based on format
|
|
266
|
+
let startCol = 0;
|
|
267
|
+
let numStrings = 6;
|
|
268
|
+
|
|
269
|
+
if (isUnicode) {
|
|
270
|
+
const bounds = findUnicodeGridBoundaries(lines, firstGridRowIdx);
|
|
271
|
+
startCol = bounds.startCol;
|
|
272
|
+
numStrings = bounds.numStrings;
|
|
273
|
+
if (numStrings === 0) {
|
|
274
|
+
return { fingers: [], barres: [] };
|
|
275
|
+
}
|
|
276
|
+
// Check if first grid row uses ╒═╤ pattern (indicates position = 1)
|
|
277
|
+
const firstGridLine = lines[firstGridRowIdx];
|
|
278
|
+
if (firstGridLine.includes("╒") && firstGridLine.includes("═")) {
|
|
279
|
+
position = 1;
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
const bounds = findAsciiGridBoundaries(lines, firstGridRowIdx);
|
|
283
|
+
startCol = bounds.startCol;
|
|
284
|
+
numStrings = bounds.numStrings;
|
|
285
|
+
if (numStrings === 0) {
|
|
286
|
+
return { fingers: [], barres: [] };
|
|
287
|
+
}
|
|
288
|
+
// Check if first grid row uses ====== pattern (indicates position = 1)
|
|
289
|
+
const firstGridLine = lines[firstGridRowIdx];
|
|
290
|
+
if (/={4,}/.test(firstGridLine)) {
|
|
291
|
+
position = 1;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Get string number for a character position
|
|
297
|
+
* @param {number} charPos
|
|
298
|
+
* @returns {number}
|
|
299
|
+
*/
|
|
300
|
+
const getStringNumber = (charPos) => {
|
|
301
|
+
if (isUnicode) {
|
|
302
|
+
return unicodeCharPosToStringNum(charPos, startCol, numStrings);
|
|
303
|
+
} else {
|
|
304
|
+
return asciiCharPosToStringNumber(charPos, startCol, numStrings);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Parse title from lines before the grid
|
|
309
|
+
// Title must be followed by a separator line (all # or all ‾)
|
|
310
|
+
for (let i = 0; i < firstGridRowIdx - 1; i++) {
|
|
311
|
+
const line = lines[i];
|
|
312
|
+
const nextLine = lines[i + 1];
|
|
313
|
+
const trimmed = line.trim();
|
|
314
|
+
|
|
315
|
+
if (trimmed === "") continue;
|
|
316
|
+
|
|
317
|
+
// Check if next line is a separator line
|
|
318
|
+
const asciiSeparatorPattern = /^[\s#]+$/;
|
|
319
|
+
const unicodeSeparatorPattern = /^[\s‾]+$/;
|
|
320
|
+
const nextTrimmed = nextLine.trim();
|
|
321
|
+
|
|
322
|
+
if (asciiSeparatorPattern.test(nextTrimmed) || unicodeSeparatorPattern.test(nextTrimmed)) {
|
|
323
|
+
// This line is the title
|
|
324
|
+
title = trimmed;
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// If no title found, set to empty string
|
|
330
|
+
if (title === undefined) {
|
|
331
|
+
title = "";
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Parse open/muted string indicators (line just before first grid row)
|
|
335
|
+
const indicatorLineIdx = firstGridRowIdx - 1;
|
|
336
|
+
if (indicatorLineIdx >= 0) {
|
|
337
|
+
const indicatorLine = lines[indicatorLineIdx];
|
|
338
|
+
for (let i = 0; i < indicatorLine.length; i++) {
|
|
339
|
+
const char = indicatorLine[i];
|
|
340
|
+
const stringNum = getStringNumber(i);
|
|
341
|
+
if (stringNum <= 0) continue;
|
|
342
|
+
|
|
343
|
+
if (char === openChar || (!isUnicode && char === ASCII_OPEN)) {
|
|
344
|
+
fingers.push([stringNum, 0, { text: "", color: blackColor }]);
|
|
345
|
+
} else if (char === mutedChar || (!isUnicode && char === ASCII_MUTED)) {
|
|
346
|
+
fingers.push([stringNum, "x", { text: "", color: blackColor }]);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Parse fret rows
|
|
352
|
+
let fretNumber = 1;
|
|
353
|
+
let isFirstFretRow = true;
|
|
354
|
+
|
|
355
|
+
for (let lineIdx = firstGridRowIdx; lineIdx < lines.length; lineIdx++) {
|
|
356
|
+
const line = lines[lineIdx];
|
|
357
|
+
|
|
358
|
+
// Check if this is a grid row or a fret content row
|
|
359
|
+
const isStructuralGridRow = isGridRow(line, isUnicode);
|
|
360
|
+
|
|
361
|
+
// If not a structural grid row, check if it's a note-only fret row
|
|
362
|
+
let isFretContentRow = false;
|
|
363
|
+
if (!isStructuralGridRow) {
|
|
364
|
+
// Check for note characters (only after first grid row is found)
|
|
365
|
+
if (isUnicode) {
|
|
366
|
+
isFretContentRow = isUnicodeFretRow(line);
|
|
367
|
+
} else {
|
|
368
|
+
isFretContentRow = isAsciiFretRow(line);
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
// It's a structural grid row, check if it's content vs separator
|
|
372
|
+
if (isUnicode) {
|
|
373
|
+
isFretContentRow = isUnicodeFretRow(line);
|
|
374
|
+
} else {
|
|
375
|
+
isFretContentRow = isAsciiFretRow(line);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Skip if not a fret content row
|
|
380
|
+
if (!isFretContentRow) continue;
|
|
381
|
+
|
|
382
|
+
// Check for position number at start of first fret row (1-2 digits)
|
|
383
|
+
if (isFirstFretRow) {
|
|
384
|
+
const posMatch = line.match(/^\s*(\d{1,2})[\s│|]/);
|
|
385
|
+
if (posMatch) {
|
|
386
|
+
position = parseInt(posMatch[1], 10);
|
|
387
|
+
}
|
|
388
|
+
isFirstFretRow = false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Scan for fretted notes and finger numbers in this row
|
|
392
|
+
for (let i = 0; i < line.length; i++) {
|
|
393
|
+
const char = line[i];
|
|
394
|
+
|
|
395
|
+
const stringNum = getStringNumber(i);
|
|
396
|
+
if (stringNum <= 0) continue;
|
|
397
|
+
|
|
398
|
+
// Skip empty positions and structural characters
|
|
399
|
+
if (isUnicode) {
|
|
400
|
+
if (char === UNICODE_VERTICAL || "╒═╤╕├─┼┤└┴┘┌┬┐".includes(char) || char === " ") continue;
|
|
401
|
+
} else {
|
|
402
|
+
// In ASCII format, skip pipes and spaces, but process notes/digits
|
|
403
|
+
if (char === ASCII_VERTICAL || char === " ") continue;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Check for root marker
|
|
407
|
+
if (char === rootChar) {
|
|
408
|
+
fingers.push([stringNum, fretNumber, { text: "", color: redColor }]);
|
|
409
|
+
}
|
|
410
|
+
// Check for regular note (○ in Unicode, o in ASCII within grid)
|
|
411
|
+
else if (char === UNICODE_OPEN || char === ASCII_OPEN) {
|
|
412
|
+
fingers.push([stringNum, fretNumber, { text: "", color: blackColor }]);
|
|
413
|
+
}
|
|
414
|
+
// Check for finger number (digit)
|
|
415
|
+
else if (/[0-9]/.test(char)) {
|
|
416
|
+
fingers.push([stringNum, fretNumber, { text: char, color: blackColor }]);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
fretNumber++;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/** @type {import("svguitar").Chord} */
|
|
424
|
+
const result = {
|
|
425
|
+
fingers,
|
|
426
|
+
barres: [],
|
|
427
|
+
title,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
if (position !== undefined) {
|
|
431
|
+
result.position = position;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return result;
|
|
435
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "text-guitar-chart",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A JavaScript library to write text based guitar chord charts and convert them to SVG.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"types": "types/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"test": "node --test test/*.test.js",
|
|
10
|
+
"test:watch": "node --test --watch test/*.test.js",
|
|
11
|
+
"build:types": "tsc",
|
|
12
|
+
"build": "node ./scripts/build.js",
|
|
13
|
+
"serve:demo": "npx http-server -p 8080 docs"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"guitar",
|
|
17
|
+
"chords",
|
|
18
|
+
"music",
|
|
19
|
+
"voicing",
|
|
20
|
+
"intervals",
|
|
21
|
+
"inversions"
|
|
22
|
+
],
|
|
23
|
+
"author": "Maurizio Lupo <maurizio.lupo@gmail.com>",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18.0.0"
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/sithmel/text-guitar-chart.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/sithmel/text-guitar-chart/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/sithmel/text-guitar-chart#readme",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"svguitar": "^2.5.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"esbuild": "^0.27.2",
|
|
41
|
+
"typescript": "^5.9.2"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/scripts/build.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//@ts-check
|
|
3
|
+
import { build } from 'esbuild';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { dirname, resolve } from 'node:path';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
|
|
11
|
+
const root = resolve(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
async function run() {
|
|
14
|
+
// Build demo bundle
|
|
15
|
+
await build({
|
|
16
|
+
entryPoints: [resolve(root, 'docs', 'app.js')],
|
|
17
|
+
bundle: true,
|
|
18
|
+
format: 'esm',
|
|
19
|
+
platform: 'browser',
|
|
20
|
+
target: ['es2018'],
|
|
21
|
+
sourcemap: true,
|
|
22
|
+
outfile: resolve(root, 'docs', 'bundle.js'),
|
|
23
|
+
logLevel: 'info',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const demoSize = readFileSync(resolve(root, 'docs', 'bundle.js')).length;
|
|
27
|
+
console.log(`EditableSVGuitar demo bundle written: ${demoSize} bytes`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
run().catch((err) => { console.error(err); process.exit(1); });
|