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 ADDED
@@ -0,0 +1,18 @@
1
+ AGENTS.md - Repository Agent Guidelines
2
+ Build: `npm install`; Types: `npm run build:types` (tsc JSDoc declarations only).
3
+ Test all: `npm test` (runs Node builtin test runner on `test/*.test.js`).
4
+ Watch tests: `npm run test:watch`.
5
+ Single test file: `node --test test/editableSVGuitar.test.js`.
6
+ Single test by name: `node --test test/editableSVGuitar.test.js --test-name-pattern 'EditableSVGuitarChord'`.
7
+ No linter configured; keep style consistent; propose ESLint only if requested (do not add unasked).
8
+ Modules: ESM (`"type":"module"`); always include `.js` extension in relative imports.
9
+ Exports: prefer named exports; update `types/` via build when adding API.
10
+ Formatting: 2-space indent, semicolons, trailing commas only where existing pattern; keep lines < 100 chars.
11
+ Naming: constants/enums UPPER_SNAKE_CASE; functions & variables camelCase; generators use verbNoun (e.g., generateInversions).
12
+ Types: use JSDoc `@typedef`, `@template`, `@param`, `@returns`; keep `//@ts-check` at top of new JS files.
13
+ Data structures: prefer immutable operations; clearly document and minimize in-place mutation.
14
+ Tests: use `describe` + `test`; assertions via `node:assert/strict`; avoid flaky/time-based tests.
15
+ Test additions: place in `test/` with `.test.js`; keep one concern per `describe`; avoid console logs (remove debug prints).
16
+ Performance: prefer simple loops over heavy abstractions in hot paths; avoid allocating inside tight generators unnecessarily.
17
+ Public API stability: avoid breaking existing exported names; add new exports rather than renaming.
18
+ Do not add new tooling/config (linters, formatters, CI) unless explicitly requested.
package/FORMAT.md ADDED
@@ -0,0 +1,277 @@
1
+ # How to represent a fingering using text
2
+
3
+ 2 formats are supported. A concise ascii one that is designed to be edited easily and a nicer one that uses unicode, looks better but is more difficult to edit.
4
+
5
+ ## General rules and semantic
6
+ The diagram represent a fragment of a guitar fretboard with low pitch string on the left and high pitch on the right. They are formed by 3 sections:
7
+ - title (optional)
8
+ - open strings (optional)
9
+ - fretboard (mandatory)
10
+
11
+ ### String numbering
12
+ Strings are numbered 1-6 from right to left in the diagram. In standard guitar tuning:
13
+ - String 6 (leftmost) is the lowest pitch (E2)
14
+ - String 1 (rightmost) is the highest pitch (E4)
15
+
16
+ ### Color values
17
+ The format supports two colors for fingering markers:
18
+ - `#000000` (black) for regular fingering positions
19
+ - `#e74c3c` (red/root marker color) for special positions (typically root notes)
20
+
21
+ Any color value other than `#000000` is treated as a root marker and rendered with the special marker symbol.
22
+
23
+ ## ascii format
24
+
25
+ ### title section
26
+ The title (optional) is max 15 characters all on the same line. If this section is included it is separated from the section underneath by a line of "#". The line is as long as the title above, with a minimum of 6 characters.
27
+
28
+ ### open string section
29
+ The open string section is optional and separated from the section underneath by "------" or "======". This separator is always included as it is the first line of the fretboard section.
30
+ - "------" is used when no fret position is set, or when the fret position is written on the first line of the fretboard section (including position 1)
31
+ - "======" is used as an alternate representation when the fret position is 1 (in this case, the position number is not written on the fretboard line)
32
+
33
+ Note: Position 1 can be represented in two ways:
34
+ 1. Using "------" with " 1" written on the first fretboard line
35
+ 2. Using "======" with no position number on the fretboard line
36
+
37
+ This section contains up to 6 elements (one for each string). Trailing spaces are trimmed from the line. Each position can be:
38
+ - " " (space) means that the string at this position is neither muted nor played
39
+ - "x" means that the string at this position is muted
40
+ - "o" means that the string at this position is played (open string)
41
+
42
+ ### fretboard section
43
+ This section is mandatory and at least 3 rows long (3 frets).
44
+ The first line can optionally have an indication of the starting fret on the left, right-justified in a 2-character field (e.g., " 5" for fret 5, "15" for fret 15).
45
+
46
+ These different characters are used:
47
+ - "|" indicates that there is no fingering in this position
48
+ - "o" indicates that this position is fingered with a regular dot (color #000000)
49
+ - "*" indicates that this position is fingered with a root marker (non-black color, typically #e74c3c)
50
+ - any other character is displayed as text on the fretboard at that position (commonly used for finger numbers or note names)
51
+
52
+ Examples:
53
+
54
+ ```
55
+ A min
56
+ ######
57
+ oo o
58
+ ------
59
+ ||||o|
60
+ ||o*||
61
+ ||||||
62
+
63
+ D
64
+ ######
65
+ xoo
66
+ ------
67
+ ||||||
68
+ |||o|o
69
+ ||||*|
70
+
71
+ G 7
72
+ ######
73
+ xx
74
+ ------
75
+ 5||*|||
76
+ ||||o|
77
+ |||o|o
78
+
79
+ E dom 7
80
+ #######
81
+ x x
82
+ ------
83
+ |||3||
84
+ |51|||
85
+ ||||7|
86
+
87
+ Dominant 7
88
+ ##########
89
+ xx
90
+ ======
91
+ ||*|||
92
+ ||||o|
93
+ |||o|o
94
+
95
+ ```
96
+
97
+ ## unicode format
98
+
99
+ ### title section
100
+ The title (optional) is max 15 characters all on the same line. If this section is included it is separated from the section underneath by a line of "‾". The line is as long as the title above and at least 11 characters
101
+
102
+ ### open string section
103
+ The open string section is optional and separated from the section underneath by "┌─┬─┬─┬─┬─┐" or "╒═╤═╤═╤═╕". This separator is always included as it is the first line of the fretboard section.
104
+ - "┌─┬─┬─┬─┬─┐" is used when no fret position is set, or when the fret position is written on the second line of the fretboard section (including position 1)
105
+ - "╒═╤═╤═╤═╕" is used as an alternate representation when the fret position is 1 (in this case, the position number is not written on the fretboard)
106
+
107
+ Note: Position 1 can be represented in two ways:
108
+ 1. Using "┌─┬─┬─┬─┬─┐" with " 1" written on the second line of the fretboard section
109
+ 2. Using "╒═╤═╤═╤═╕" with no position number on the fretboard
110
+
111
+ This section contains up to 6 elements (one for each string) separated by spaces. Trailing spaces are trimmed from the line. Each position can be:
112
+ - " " (space) means that the string at this position is neither muted nor played
113
+ - "×" means that the string at this position is muted
114
+ - "○" means that the string at this position is played (open string)
115
+
116
+ ### fretboard section
117
+ This section is mandatory and at least 3 frets long. It is built as a grid:
118
+ ```
119
+ ┌─┬─┬─┬─┬─┐
120
+ │ │ │ │ │ │
121
+ ├─┼─┼─┼─┼─┤
122
+ │ │ │ │ │ │
123
+ ├─┼─┼─┼─┼─┤
124
+ │ │ │ │ │ │
125
+ └─┴─┴─┴─┴─┘
126
+ ```
127
+ The second line can optionally have an indication of the starting fret on the left, right-justified in a 2-character field (e.g., " 5" for fret 5, "15" for fret 15).
128
+
129
+ The even rows contain characters representing the fingering:
130
+ - "│" indicates that there is no fingering in this position
131
+ - "○" indicates that this position is fingered with a regular dot (color #000000)
132
+ - "●" indicates that this position is fingered with a root marker (non-black color, typically #e74c3c)
133
+ - any other character is displayed as text on the fretboard at that position (commonly used for finger numbers or note names)
134
+
135
+ Example:
136
+ ```
137
+ A minor
138
+ ‾‾‾‾‾‾‾‾‾‾‾
139
+ ○ ○ ○
140
+ ╒═╤═╤═╤═╤═╕
141
+ │ │ │ │ ○ │
142
+ ├─┼─┼─┼─┼─┤
143
+ │ │ ○ ● │ │
144
+ ├─┼─┼─┼─┼─┤
145
+ │ │ │ │ │ │
146
+ └─┴─┴─┴─┴─┘
147
+
148
+ D
149
+ ‾‾‾‾‾‾‾‾‾‾‾
150
+ × ○ ○
151
+ ╒═╤═╤═╤═╤═╕
152
+ │ │ │ │ │ │
153
+ ├─┼─┼─┼─┼─┤
154
+ │ │ │ ○ │ ○
155
+ ├─┼─┼─┼─┼─┤
156
+ │ │ │ │ ● │
157
+ └─┴─┴─┴─┴─┘
158
+
159
+ G 7
160
+ ‾‾‾‾‾‾‾‾‾‾‾
161
+ × ×
162
+ ┌─┬─┬─┬─┬─┐
163
+ 5│ │ ● │ │ │
164
+ ├─┼─┼─┼─┼─┤
165
+ │ │ │ │ ○ │
166
+ ├─┼─┼─┼─┼─┤
167
+ │ │ │ ○ │ ○
168
+ └─┴─┴─┴─┴─┘
169
+
170
+ E dom 7
171
+ ‾‾‾‾‾‾‾‾‾‾‾
172
+ × ×
173
+ ┌─┬─┬─┬─┬─┐
174
+ │ │ │ 3 │ │
175
+ ├─┼─┼─┼─┼─┤
176
+ │ 5 1 │ │ │
177
+ ├─┼─┼─┼─┼─┤
178
+ │ │ │ │ 7 │
179
+ └─┴─┴─┴─┴─┘
180
+
181
+ Dominant 7
182
+ ‾‾‾‾‾‾‾‾‾‾‾
183
+ × ×
184
+ ╒═╤═╤═╤═╤═╕
185
+ │ │ ● │ │ │
186
+ ├─┼─┼─┼─┼─┤
187
+ │ │ │ │ ○ │
188
+ ├─┼─┼─┼─┼─┤
189
+ │ │ │ ○ │ ○
190
+ └─┴─┴─┴─┴─┘
191
+
192
+ ```
193
+
194
+ ## Edge cases and special behaviors
195
+
196
+ ### Missing sections
197
+ - If no title is provided, the title and separator lines are omitted
198
+ - If no open strings are defined (no string with fret 0 or "x"), the open string section is omitted
199
+ - The fretboard section is always present (minimum 3 frets)
200
+
201
+ ### Empty chords
202
+ An empty chord (no fingerings) renders as just the fretboard grid with no markers.
203
+
204
+ ASCII format:
205
+ ```
206
+ ------
207
+ ||||||
208
+ ||||||
209
+ ||||||
210
+ ```
211
+
212
+ Unicode format:
213
+ ```
214
+ ┌─┬─┬─┬─┬─┐
215
+ │ │ │ │ │ │
216
+ ├─┼─┼─┼─┼─┤
217
+ │ │ │ │ │ │
218
+ ├─┼─┼─┼─┼─┤
219
+ │ │ │ │ │ │
220
+ └─┴─┴─┴─┴─┘
221
+ ```
222
+
223
+ ### All open strings
224
+ All six strings can be marked as open.
225
+
226
+ ASCII format:
227
+ ```
228
+ E major
229
+ #######
230
+ oooooo
231
+ ------
232
+ ||||||
233
+ ||||||
234
+ ||||||
235
+ ```
236
+
237
+ Unicode format:
238
+ ```
239
+ E major
240
+ ‾‾‾‾‾‾‾‾‾‾‾
241
+ ○ ○ ○ ○ ○ ○
242
+ ╒═╤═╤═╤═╤═╕
243
+ │ │ │ │ │ │
244
+ ├─┼─┼─┼─┼─┤
245
+ │ │ │ │ │ │
246
+ ├─┼─┼─┼─┼─┤
247
+ │ │ │ │ │ │
248
+ └─┴─┴─┴─┴─┘
249
+ ```
250
+
251
+ ### All muted strings
252
+ All six strings can be marked as muted.
253
+
254
+ ASCII format:
255
+ ```
256
+ Muted
257
+ #####
258
+ xxxxxx
259
+ ------
260
+ ||||||
261
+ ||||||
262
+ ||||||
263
+ ```
264
+
265
+ Unicode format:
266
+ ```
267
+ Muted
268
+ ‾‾‾‾‾‾‾‾‾‾‾
269
+ × × × × × ×
270
+ ┌─┬─┬─┬─┬─┐
271
+ │ │ │ │ │ │
272
+ ├─┼─┼─┼─┼─┤
273
+ │ │ │ │ │ │
274
+ ├─┼─┼─┼─┼─┤
275
+ │ │ │ │ │ │
276
+ └─┴─┴─┴─┴─┘
277
+ ```
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 text-guitar-chart
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Text Guitar Chart
2
+
3
+ This library allows to write fretboard charts in a text format that is easy to read. It also includes utilities to convert the charts in svg using [svguitar](https://github.com/omnibrain/svguitar).
4
+
5
+ See the [format](FORMAT.md)
6
+
7
+ # How to use
8
+ Use [the editor](https://sithmel.github.io/text-guitar-chart/) to edit the fingering you like. Copy paste the fretboard chart wherever you like!
9
+
10
+ # Utilities
11
+ Here is a list of utilities it provides:
12
+ - EditableSVGuitarChord an editable chord chart utility
13
+ - stringToFingering transforms a text representation of a fingering into data that can be used to render that fingering using SVGuitar
14
+ - fingeringToString transforms SVGuitar data format into a text representation of a fingering
15
+
16
+ ## TypeScript Support
17
+
18
+ This library includes TypeScript declarations generated from JSDoc comments:
19
+
20
+ ```bash
21
+ npm run build:types
22
+ ```
23
+
24
+ ## Development
25
+
26
+ ### Running Tests
27
+
28
+ ```bash
29
+ npm test
30
+ npm run test:watch # Watch mode
31
+ ```
32
+
33
+ ### Generate Type Declarations
34
+
35
+ ```bash
36
+ npm run build:types
37
+ ```
38
+
39
+ ## License
40
+
41
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
package/docs/app.js ADDED
@@ -0,0 +1,172 @@
1
+ //@ts-check
2
+ import { EditableSVGuitarChord } from '../lib/editableSVGuitar.js';
3
+ import { SVGuitarChord } from 'svguitar';
4
+ import splitStringInRectangles from '../lib/splitStringInRectangles.js';
5
+ import stringToFingering from '../lib/stringToFingering.js';
6
+ import layoutChordStrings from '../lib/layoutChordStrings.js';
7
+
8
+ /** @type {HTMLElement} */
9
+ const editor = /** @type {HTMLElement} */(document.getElementById('editor'));
10
+ /** @type {HTMLElement} */
11
+ const output = /** @type {HTMLElement} */(document.getElementById('output'));
12
+ /** @type {HTMLElement} */
13
+ const outputAscii = /** @type {HTMLElement} */(document.getElementById('output-ascii'));
14
+ /** @type {HTMLElement} */
15
+ const outputUnicode = /** @type {HTMLElement} */(document.getElementById('output-unicode'));
16
+
17
+
18
+ if (!editor || !outputAscii || !outputUnicode) {
19
+ throw new Error('Required DOM elements not found');
20
+ }
21
+
22
+ const editable = new EditableSVGuitarChord(editor)
23
+ .chord({ fingers: [], barres: [] })
24
+ .configure({ frets: 5, noPosition: true })
25
+ .draw();
26
+
27
+ editable.onChange(() => {
28
+ updateJSON();
29
+ });
30
+
31
+ /** Update JSON panel */
32
+ function updateJSON() {
33
+ output.textContent = JSON.stringify(editable.getChord(), null, 2);
34
+ outputAscii.textContent = editable.toString({ useUnicode: false });
35
+ outputUnicode.textContent = editable.toString({ useUnicode: true });
36
+ }
37
+
38
+ document.getElementById('clear')?.addEventListener('click', () => {
39
+ editable.chord({ fingers: [], barres: [] }).redraw();
40
+ updateJSON();
41
+ });
42
+
43
+ document.getElementById('copy-json')?.addEventListener('click', () => {
44
+ navigator.clipboard.writeText(output.textContent || '').catch(err => {
45
+ console.error('Failed to copy JSON:', err);
46
+ });
47
+ });
48
+
49
+ document.getElementById('copy-ascii')?.addEventListener('click', () => {
50
+ navigator.clipboard.writeText(outputAscii.textContent || '').catch(err => {
51
+ console.error('Failed to copy ASCII:', err);
52
+ });
53
+ });
54
+
55
+ document.getElementById('copy-unicode')?.addEventListener('click', () => {
56
+ navigator.clipboard.writeText(outputUnicode.textContent || '').catch(err => {
57
+ console.error('Failed to copy Unicode:', err);
58
+ });
59
+ });
60
+
61
+ // Initial panel fill
62
+ updateJSON();
63
+
64
+ // Tab switching
65
+ document.getElementById('tab-editor')?.addEventListener('click', () => {
66
+ document.getElementById('tab-editor')?.classList.add('active');
67
+ document.getElementById('tab-batch')?.classList.remove('active');
68
+ document.getElementById('interactive-editor')?.classList.add('active');
69
+ document.getElementById('batch-converter')?.classList.remove('active');
70
+ });
71
+
72
+ document.getElementById('tab-batch')?.addEventListener('click', () => {
73
+ document.getElementById('tab-batch')?.classList.add('active');
74
+ document.getElementById('tab-editor')?.classList.remove('active');
75
+ document.getElementById('batch-converter')?.classList.add('active');
76
+ document.getElementById('interactive-editor')?.classList.remove('active');
77
+ });
78
+
79
+ // Batch conversion
80
+ document.getElementById('convert-btn')?.addEventListener('click', () => {
81
+ const textarea = /** @type {HTMLTextAreaElement} */(document.getElementById('batch-input'));
82
+ const chordGrid = document.getElementById('chord-grid');
83
+
84
+ if (!textarea || !chordGrid) {
85
+ console.error('Required elements not found');
86
+ return;
87
+ }
88
+
89
+ const input = textarea.value;
90
+ if (!input.trim()) {
91
+ console.warn('No input provided');
92
+ return;
93
+ }
94
+
95
+ try {
96
+ // Clear previous results
97
+ chordGrid.innerHTML = '';
98
+
99
+ // Split input into rectangles
100
+ const rectangles = splitStringInRectangles(input);
101
+
102
+ if (rectangles.length === 0) {
103
+ console.warn('No chord diagrams found in input');
104
+ return;
105
+ }
106
+
107
+ // Convert each rectangle to a chord and render
108
+ rectangles.forEach((rectangle, index) => {
109
+ try {
110
+ const chordConfig = stringToFingering(rectangle);
111
+ if (chordConfig == null) {
112
+ return;
113
+ }
114
+
115
+ // Create container for this chord
116
+ const container = document.createElement('div');
117
+ container.className = 'chord-container';
118
+ chordGrid.appendChild(container);
119
+
120
+ // Render the chord
121
+ const chart = new SVGuitarChord(container);
122
+ const frets = Math.max(...chordConfig.fingers.map(f => (typeof f[1] === 'number' ? f[1] : 0)), 3);
123
+ const noPosition = chordConfig.position === 0 || (chordConfig.position === undefined);
124
+ chart
125
+ .chord(chordConfig)
126
+ .configure({ frets, noPosition })
127
+ .draw();
128
+ } catch (err) {
129
+ console.error(`Error rendering chord ${index + 1}:`, err);
130
+ }
131
+ });
132
+ } catch (err) {
133
+ console.error('Error during batch conversion:', err);
134
+ }
135
+ });
136
+
137
+ /**
138
+ *
139
+ * @param {number} columnNumber
140
+ */
141
+ function layout (columnNumber){
142
+ const textarea = /** @type {HTMLTextAreaElement} */(document.getElementById('batch-input'));
143
+ if (!textarea) {
144
+ console.error('Required elements not found');
145
+ return;
146
+ }
147
+
148
+ const input = textarea.value;
149
+ if (!input.trim()) {
150
+ console.warn('No input provided');
151
+ return;
152
+ }
153
+ // Split input into rectangles
154
+ const rectangles = splitStringInRectangles(input);
155
+ const layout = layoutChordStrings(rectangles, columnNumber, 3);
156
+ textarea.value = layout;
157
+ }
158
+
159
+ // Batch conversion
160
+ document.getElementById('layout-2-btn')?.addEventListener('click', () => {
161
+ layout(2);
162
+ });
163
+
164
+ // Batch conversion
165
+ document.getElementById('layout-3-btn')?.addEventListener('click', () => {
166
+ layout(3);
167
+ });
168
+
169
+ // Batch conversion
170
+ document.getElementById('layout-4-btn')?.addEventListener('click', () => {
171
+ layout(4);
172
+ });