@parent-tobias/chord-component 1.0.0
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/LICENSE +21 -0
- package/README.md +359 -0
- package/dist/chord-data-service.js +190 -0
- package/dist/chord-diagram.js +181 -0
- package/dist/chord-editor.js +691 -0
- package/dist/chord-list.js +119 -0
- package/dist/default-chords.js +322 -0
- package/dist/index.js +26 -0
- package/dist/indexed-db-service.js +228 -0
- package/dist/music-utils.js +128 -0
- package/dist/node_modules/@lit/reactive-element/css-tag.js +42 -0
- package/dist/node_modules/@lit/reactive-element/decorators/base.js +9 -0
- package/dist/node_modules/@lit/reactive-element/decorators/custom-element.js +13 -0
- package/dist/node_modules/@lit/reactive-element/decorators/property.js +37 -0
- package/dist/node_modules/@lit/reactive-element/decorators/query.js +20 -0
- package/dist/node_modules/@lit/reactive-element/decorators/state.js +12 -0
- package/dist/node_modules/@lit/reactive-element/reactive-element.js +251 -0
- package/package.json +83 -0
- package/src/chord-data-service.ts +275 -0
- package/src/chord-diagram.ts +255 -0
- package/src/chord-editor.ts +919 -0
- package/src/chord-list.ts +145 -0
- package/src/default-chords.ts +333 -0
- package/src/index.ts +7 -0
- package/src/indexed-db-service.ts +356 -0
- package/src/music-utils.ts +216 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chord Data Service
|
|
3
|
+
*
|
|
4
|
+
* This service provides a unified interface for accessing chord data.
|
|
5
|
+
* It implements a caching strategy using IndexedDB:
|
|
6
|
+
* 1. First checks IndexedDB for cached data
|
|
7
|
+
* 2. Falls back to local default chord data if not found
|
|
8
|
+
* 3. Caches the data in IndexedDB for future use
|
|
9
|
+
* 4. Can be easily extended to fetch from a remote API
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { indexedDBService } from './indexed-db-service.js';
|
|
13
|
+
import { systemDefaultChords, type InstrumentDefault } from './default-chords.js';
|
|
14
|
+
|
|
15
|
+
export interface ChordDataSource {
|
|
16
|
+
type: 'indexeddb' | 'local' | 'api';
|
|
17
|
+
timestamp?: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface ChordDataResult {
|
|
21
|
+
data: Record<string, InstrumentDefault>;
|
|
22
|
+
source: ChordDataSource;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class ChordDataService {
|
|
26
|
+
private useRemoteAPI: boolean = false;
|
|
27
|
+
private apiEndpoint: string = '';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Configure the service to use a remote API endpoint
|
|
31
|
+
*/
|
|
32
|
+
configureAPI(endpoint: string) {
|
|
33
|
+
this.apiEndpoint = endpoint;
|
|
34
|
+
this.useRemoteAPI = true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Disable remote API and use local data
|
|
39
|
+
*/
|
|
40
|
+
disableAPI() {
|
|
41
|
+
this.useRemoteAPI = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get chord data for a specific instrument
|
|
46
|
+
* This method implements the fallback chain: IndexedDB -> API -> Local
|
|
47
|
+
*/
|
|
48
|
+
async getChordData(instrument: string): Promise<ChordDataResult> {
|
|
49
|
+
// Step 1: Try to get from IndexedDB cache
|
|
50
|
+
try {
|
|
51
|
+
const cachedData = await indexedDBService.getChordData(instrument);
|
|
52
|
+
|
|
53
|
+
if (cachedData && cachedData.chords) {
|
|
54
|
+
console.log(`[ChordDataService] Loaded ${instrument} from IndexedDB cache`);
|
|
55
|
+
return {
|
|
56
|
+
data: cachedData.chords,
|
|
57
|
+
source: { type: 'indexeddb', timestamp: cachedData.timestamp }
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.warn('[ChordDataService] IndexedDB error, falling back:', error);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Step 2: Try to fetch from remote API if configured
|
|
65
|
+
if (this.useRemoteAPI && this.apiEndpoint) {
|
|
66
|
+
try {
|
|
67
|
+
const apiData = await this.fetchFromAPI(instrument);
|
|
68
|
+
|
|
69
|
+
if (apiData) {
|
|
70
|
+
console.log(`[ChordDataService] Loaded ${instrument} from remote API`);
|
|
71
|
+
|
|
72
|
+
// Cache the API data in IndexedDB for future use
|
|
73
|
+
await this.cacheChordData(instrument, apiData);
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
data: apiData,
|
|
77
|
+
source: { type: 'api', timestamp: Date.now() }
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
console.warn('[ChordDataService] API fetch error, falling back to local:', error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Step 3: Fall back to local default data
|
|
86
|
+
console.log(`[ChordDataService] Loaded ${instrument} from local defaults`);
|
|
87
|
+
const localData = systemDefaultChords[instrument] || {};
|
|
88
|
+
|
|
89
|
+
// Cache the local data in IndexedDB for future use
|
|
90
|
+
await this.cacheChordData(instrument, localData);
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
data: localData,
|
|
94
|
+
source: { type: 'local', timestamp: Date.now() }
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Fetch chord data from remote API
|
|
100
|
+
* This is a placeholder that can be implemented when API is ready
|
|
101
|
+
*/
|
|
102
|
+
private async fetchFromAPI(instrument: string): Promise<Record<string, InstrumentDefault> | null> {
|
|
103
|
+
if (!this.apiEndpoint) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const url = `${this.apiEndpoint}?instrument=${encodeURIComponent(instrument)}`;
|
|
109
|
+
const response = await fetch(url);
|
|
110
|
+
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const data = await response.json();
|
|
116
|
+
return data.chords || data; // Flexible response format
|
|
117
|
+
} catch (error) {
|
|
118
|
+
console.error('[ChordDataService] API fetch error:', error);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Cache chord data in IndexedDB
|
|
125
|
+
*/
|
|
126
|
+
private async cacheChordData(instrument: string, chords: Record<string, InstrumentDefault>): Promise<void> {
|
|
127
|
+
try {
|
|
128
|
+
await indexedDBService.setChordData(instrument, chords);
|
|
129
|
+
console.log(`[ChordDataService] Cached ${instrument} in IndexedDB`);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.warn('[ChordDataService] Failed to cache in IndexedDB:', error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get all available instruments from local defaults
|
|
137
|
+
* In the future, this could also query the API
|
|
138
|
+
*/
|
|
139
|
+
getAvailableInstruments(): string[] {
|
|
140
|
+
return Object.keys(systemDefaultChords);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Clear all cached data from IndexedDB
|
|
145
|
+
*/
|
|
146
|
+
async clearCache(): Promise<void> {
|
|
147
|
+
await indexedDBService.clearAll();
|
|
148
|
+
console.log('[ChordDataService] Cache cleared');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Force refresh data from source (API or local) and update cache
|
|
153
|
+
*/
|
|
154
|
+
async refreshData(instrument: string): Promise<ChordDataResult> {
|
|
155
|
+
// If using API, fetch fresh data
|
|
156
|
+
if (this.useRemoteAPI && this.apiEndpoint) {
|
|
157
|
+
try {
|
|
158
|
+
const apiData = await this.fetchFromAPI(instrument);
|
|
159
|
+
|
|
160
|
+
if (apiData) {
|
|
161
|
+
await this.cacheChordData(instrument, apiData);
|
|
162
|
+
return {
|
|
163
|
+
data: apiData,
|
|
164
|
+
source: { type: 'api', timestamp: Date.now() }
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
} catch (error) {
|
|
168
|
+
console.warn('[ChordDataService] Refresh from API failed:', error);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Fall back to local data
|
|
173
|
+
const localData = systemDefaultChords[instrument] || {};
|
|
174
|
+
await this.cacheChordData(instrument, localData);
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
data: localData,
|
|
178
|
+
source: { type: 'local', timestamp: Date.now() }
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if a specific chord exists for an instrument
|
|
184
|
+
*/
|
|
185
|
+
async hasChord(instrument: string, chordName: string): Promise<boolean> {
|
|
186
|
+
const result = await this.getChordData(instrument);
|
|
187
|
+
return chordName in result.data;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get data for a specific chord
|
|
192
|
+
* @param instrument - The instrument name
|
|
193
|
+
* @param chordName - The chord name
|
|
194
|
+
* @param preferUser - If true, returns user override if it exists; if false, returns system default only
|
|
195
|
+
*/
|
|
196
|
+
async getChord(instrument: string, chordName: string, preferUser: boolean = true): Promise<InstrumentDefault | null> {
|
|
197
|
+
// Check for user override first if preferUser is true
|
|
198
|
+
if (preferUser) {
|
|
199
|
+
try {
|
|
200
|
+
const userChord = await indexedDBService.getUserChord(instrument, chordName);
|
|
201
|
+
if (userChord) {
|
|
202
|
+
return {
|
|
203
|
+
fingers: userChord.fingers,
|
|
204
|
+
barres: userChord.barres,
|
|
205
|
+
position: userChord.position
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.warn('[ChordDataService] Failed to get user chord:', error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Fall back to system default
|
|
214
|
+
const result = await this.getChordData(instrument);
|
|
215
|
+
return result.data[chordName] || null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Save a user-defined chord override
|
|
220
|
+
*/
|
|
221
|
+
async saveUserChord(instrument: string, chordName: string, chordData: InstrumentDefault): Promise<void> {
|
|
222
|
+
await indexedDBService.saveUserChord(instrument, chordName, chordData);
|
|
223
|
+
console.log(`[ChordDataService] Saved user chord: ${instrument} - ${chordName}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Delete a user-defined chord override (revert to system default)
|
|
228
|
+
*/
|
|
229
|
+
async deleteUserChord(instrument: string, chordName: string): Promise<void> {
|
|
230
|
+
await indexedDBService.deleteUserChord(instrument, chordName);
|
|
231
|
+
console.log(`[ChordDataService] Deleted user chord: ${instrument} - ${chordName}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get all user-defined chords for an instrument
|
|
236
|
+
*/
|
|
237
|
+
async getUserChordsByInstrument(instrument: string): Promise<Array<{ chordName: string, data: InstrumentDefault }>> {
|
|
238
|
+
const userChords = await indexedDBService.getUserChordsByInstrument(instrument);
|
|
239
|
+
return userChords.map(chord => ({
|
|
240
|
+
chordName: chord.chordName,
|
|
241
|
+
data: {
|
|
242
|
+
fingers: chord.fingers,
|
|
243
|
+
barres: chord.barres,
|
|
244
|
+
position: chord.position
|
|
245
|
+
}
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Get all user-defined chords across all instruments
|
|
251
|
+
*/
|
|
252
|
+
async getAllUserChords(): Promise<Array<{ instrument: string, chordName: string, data: InstrumentDefault }>> {
|
|
253
|
+
const userChords = await indexedDBService.getAllUserChords();
|
|
254
|
+
return userChords.map(chord => ({
|
|
255
|
+
instrument: chord.instrument,
|
|
256
|
+
chordName: chord.chordName,
|
|
257
|
+
data: {
|
|
258
|
+
fingers: chord.fingers,
|
|
259
|
+
barres: chord.barres,
|
|
260
|
+
position: chord.position
|
|
261
|
+
}
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Clear all user-defined chord overrides
|
|
267
|
+
*/
|
|
268
|
+
async clearUserChords(): Promise<void> {
|
|
269
|
+
await indexedDBService.clearUserChords();
|
|
270
|
+
console.log('[ChordDataService] Cleared all user chords');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Export a singleton instance
|
|
275
|
+
export const chordDataService = new ChordDataService();
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { LitElement, css, html } from 'lit';
|
|
2
|
+
import { customElement, property, query, state } from 'lit/decorators.js';
|
|
3
|
+
import { SVGuitarChord } from 'svguitar';
|
|
4
|
+
|
|
5
|
+
import { instruments, chordOnInstrument, chordToNotes } from './music-utils.js';
|
|
6
|
+
import { chordDataService } from './chord-data-service.js';
|
|
7
|
+
import type { InstrumentDefault } from './default-chords.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A web component that displays a chord diagram for various instruments.
|
|
11
|
+
*
|
|
12
|
+
* @element chord-diagram
|
|
13
|
+
*
|
|
14
|
+
* @attr {string} instrument - The instrument to display the chord for (default: 'Standard Ukulele')
|
|
15
|
+
* @attr {string} chord - The chord name to display (e.g., 'C', 'Am7', 'F#dim')
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```html
|
|
19
|
+
* <chord-diagram chord="C" instrument="Standard Ukulele"></chord-diagram>
|
|
20
|
+
* <chord-diagram chord="Am7" instrument="Standard Guitar"></chord-diagram>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
@customElement('chord-diagram')
|
|
24
|
+
export class ChordDiagram extends LitElement {
|
|
25
|
+
|
|
26
|
+
static styles = css`
|
|
27
|
+
:host {
|
|
28
|
+
display: block;
|
|
29
|
+
width: 100%;
|
|
30
|
+
min-width: 100px;
|
|
31
|
+
max-width: 150px;
|
|
32
|
+
border: 1px solid #4a5568;
|
|
33
|
+
border-radius: 4px;
|
|
34
|
+
background: #2d3748;
|
|
35
|
+
padding: 0.5rem;
|
|
36
|
+
box-sizing: border-box;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.chord {
|
|
40
|
+
display: flex;
|
|
41
|
+
flex-direction: column;
|
|
42
|
+
align-items: center;
|
|
43
|
+
width: 100%;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.chord span {
|
|
47
|
+
color: #f8f8f8;
|
|
48
|
+
font-size: 0.9rem;
|
|
49
|
+
font-weight: 500;
|
|
50
|
+
margin-bottom: 0.25rem;
|
|
51
|
+
text-align: center;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.diagram {
|
|
55
|
+
width: 100%;
|
|
56
|
+
display: flex;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.diagram :global(svg) {
|
|
61
|
+
max-width: 100%;
|
|
62
|
+
height: auto;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.error {
|
|
66
|
+
color: #fc8181;
|
|
67
|
+
font-size: 0.8rem;
|
|
68
|
+
text-align: center;
|
|
69
|
+
padding: 0.5rem;
|
|
70
|
+
}
|
|
71
|
+
`
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The instrument to display the chord for
|
|
75
|
+
*/
|
|
76
|
+
@property({
|
|
77
|
+
type: String
|
|
78
|
+
})
|
|
79
|
+
instrument = 'Standard Ukulele';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The chord name to display
|
|
83
|
+
*/
|
|
84
|
+
@property({
|
|
85
|
+
type: String
|
|
86
|
+
})
|
|
87
|
+
chord = '';
|
|
88
|
+
|
|
89
|
+
@query('.diagram')
|
|
90
|
+
container?: HTMLElement;
|
|
91
|
+
|
|
92
|
+
@state()
|
|
93
|
+
private chordData: Record<string, InstrumentDefault> = {};
|
|
94
|
+
|
|
95
|
+
@state()
|
|
96
|
+
private isLoading = false;
|
|
97
|
+
|
|
98
|
+
@state()
|
|
99
|
+
private loadError: string | null = null;
|
|
100
|
+
|
|
101
|
+
async connectedCallback() {
|
|
102
|
+
super.connectedCallback();
|
|
103
|
+
await this.loadChordData();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async updated(changedProperties: Map<string, any>) {
|
|
107
|
+
super.updated(changedProperties);
|
|
108
|
+
|
|
109
|
+
// Reload chord data if instrument changes
|
|
110
|
+
if (changedProperties.has('instrument')) {
|
|
111
|
+
await this.loadChordData();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async loadChordData() {
|
|
116
|
+
this.isLoading = true;
|
|
117
|
+
this.loadError = null;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const result = await chordDataService.getChordData(this.instrument);
|
|
121
|
+
this.chordData = result.data;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error('Failed to load chord data:', error);
|
|
124
|
+
this.loadError = 'Failed to load chord data';
|
|
125
|
+
this.chordData = {};
|
|
126
|
+
} finally {
|
|
127
|
+
this.isLoading = false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
render() {
|
|
132
|
+
if (this.isLoading) {
|
|
133
|
+
return html`
|
|
134
|
+
<div class='chord'>
|
|
135
|
+
<div style="color: #90cdf4; font-size: 0.8rem; text-align: center; padding: 0.5rem;">
|
|
136
|
+
Loading...
|
|
137
|
+
</div>
|
|
138
|
+
</div>
|
|
139
|
+
`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (this.loadError) {
|
|
143
|
+
return html`
|
|
144
|
+
<div class='chord'>
|
|
145
|
+
<div class='error'>${this.loadError}</div>
|
|
146
|
+
</div>
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!this.chord) {
|
|
151
|
+
return html`
|
|
152
|
+
<div class='chord'>
|
|
153
|
+
<div class='error'>No chord specified</div>
|
|
154
|
+
</div>
|
|
155
|
+
`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const instrumentObject = instruments.find(({name}) => name === this.instrument);
|
|
159
|
+
|
|
160
|
+
if (!instrumentObject) {
|
|
161
|
+
return html`
|
|
162
|
+
<div class='chord'>
|
|
163
|
+
<span>${this.chord.replace(/(maj)$/, '')}</span>
|
|
164
|
+
<div class='error'>Unknown instrument: ${this.instrument}</div>
|
|
165
|
+
</div>
|
|
166
|
+
`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const chordFinder = chordOnInstrument(instrumentObject);
|
|
170
|
+
|
|
171
|
+
// Given the chord name (G7, Bbmin), we need the notes in the chord
|
|
172
|
+
const chordObject = chordToNotes(this.chord);
|
|
173
|
+
|
|
174
|
+
if (!chordObject || !chordObject.notes || chordObject.notes.length === 0) {
|
|
175
|
+
return html`
|
|
176
|
+
<div class='chord'>
|
|
177
|
+
<span>${this.chord.replace(/(maj)$/, '')}</span>
|
|
178
|
+
<div class='error'>Unknown chord: ${this.chord}</div>
|
|
179
|
+
</div>
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check if we have a default for this chord/instrument combination
|
|
184
|
+
const chartSettings = this.chordData[this.chord] ?
|
|
185
|
+
this.chordData[this.chord] :
|
|
186
|
+
{
|
|
187
|
+
barres: [],
|
|
188
|
+
fingers: chordFinder(chordObject) || []
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Auto-calculate position based on chord data (not stored with chord)
|
|
192
|
+
const arrayOfFrets: number[] = chartSettings.fingers.map(([, fret]): number =>
|
|
193
|
+
typeof fret === 'number' ? fret : Infinity
|
|
194
|
+
);
|
|
195
|
+
const barreFrets = chartSettings.barres.map((b: any) => typeof b.fret === 'number' ? b.fret : 0);
|
|
196
|
+
const allChordFrets = [...arrayOfFrets, ...barreFrets];
|
|
197
|
+
|
|
198
|
+
const minChordFret = allChordFrets.length > 0 ? Math.min(...allChordFrets.filter(f => f > 0)) : 1;
|
|
199
|
+
const maxChordFret = allChordFrets.length > 0 ? Math.max(...allChordFrets, 0) : 4;
|
|
200
|
+
|
|
201
|
+
let position = 1;
|
|
202
|
+
if (maxChordFret > 4) {
|
|
203
|
+
// For high chords, start from the lowest fret
|
|
204
|
+
position = Math.max(1, minChordFret);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Determine fret range to display
|
|
208
|
+
let fretCount: number;
|
|
209
|
+
let displayPosition: number;
|
|
210
|
+
|
|
211
|
+
if (position > 1 || maxChordFret > 4) {
|
|
212
|
+
// High position chord - show from position
|
|
213
|
+
fretCount = Math.max(maxChordFret - position + 1, 4);
|
|
214
|
+
displayPosition = position;
|
|
215
|
+
} else {
|
|
216
|
+
// Low position chord - show from fret 1
|
|
217
|
+
fretCount = Math.max(maxChordFret, 4);
|
|
218
|
+
displayPosition = 1;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Create a container div for SVGuitar
|
|
222
|
+
const divEl = document.createElement("div");
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const chart = new SVGuitarChord(divEl);
|
|
226
|
+
chart
|
|
227
|
+
.configure({
|
|
228
|
+
strings: instrumentObject.strings.length,
|
|
229
|
+
frets: fretCount,
|
|
230
|
+
position: displayPosition,
|
|
231
|
+
tuning: [...instrumentObject.strings]
|
|
232
|
+
})
|
|
233
|
+
.chord({
|
|
234
|
+
fingers: chartSettings.fingers,
|
|
235
|
+
barres: chartSettings.barres
|
|
236
|
+
})
|
|
237
|
+
.draw();
|
|
238
|
+
|
|
239
|
+
return html`
|
|
240
|
+
<div class='chord'>
|
|
241
|
+
<span>${this.chord.replace(/(maj)$/, '')}</span>
|
|
242
|
+
<div class='diagram'>${divEl.firstChild}</div>
|
|
243
|
+
</div>
|
|
244
|
+
`;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Error generating chord diagram:', error);
|
|
247
|
+
return html`
|
|
248
|
+
<div class='chord'>
|
|
249
|
+
<span>${this.chord.replace(/(maj)$/, '')}</span>
|
|
250
|
+
<div class='error'>Error generating diagram</div>
|
|
251
|
+
</div>
|
|
252
|
+
`;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|