@medyll/idae-cadenzia 0.78.0 → 0.80.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/.prettierignore +4 -4
- package/.prettierrc +15 -15
- package/CHANGELOG.md +23 -0
- package/README.md +38 -38
- package/eslint.config.js +33 -33
- package/package.json +1 -1
- package/scripts/package-pre.js +3 -3
- package/src/app.d.ts +13 -13
- package/src/app.html +12 -12
- package/src/demo.spec.ts +7 -7
- package/src/lib/components/App.svelte +46 -46
- package/src/lib/components/CadencePanel.svelte +24 -24
- package/src/lib/components/ChordTable.svelte +252 -252
- package/src/lib/components/ChordVisualization.svelte +82 -82
- package/src/lib/constants/constants.ts +49 -49
- package/src/lib/functions/functions.svelte.ts +139 -139
- package/src/lib/functions/rules.ts +118 -118
- package/src/lib/index.ts +9 -9
- package/src/lib/types/types.ts +31 -31
- package/src/routes/+page.svelte +7 -7
- package/svelte.config.js +18 -18
- package/tsconfig.json +19 -19
- package/vite.config.ts +10 -10
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
/* constants/constants.ts */
|
|
2
|
-
import type { Cadence } from '$lib/types/types';
|
|
3
|
-
|
|
4
|
-
// Definition of common musical cadences
|
|
5
|
-
export const cadencePatterns: Cadence[] = [
|
|
6
|
-
{ name: 'Perfect', chords: ['V', 'I'] },
|
|
7
|
-
{ name: 'Plagal', chords: ['IV', 'I'] },
|
|
8
|
-
{ name: 'Deceptive', chords: ['V', 'VI'] },
|
|
9
|
-
{ name: 'Half', chords: ['I', 'V'] },
|
|
10
|
-
{ name: 'Italian', chords: ['IV', 'V', 'I'] },
|
|
11
|
-
{ name: 'German', chords: ['II', 'V', 'I'] },
|
|
12
|
-
{ name: 'Phrygian', chords: ['IV', 'V', 'III'] }
|
|
13
|
-
];
|
|
14
|
-
|
|
15
|
-
// Fundamental root notes in order
|
|
16
|
-
export const rootNotes = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
|
|
17
|
-
|
|
18
|
-
// Different chord qualities grouped by category
|
|
19
|
-
export const qualities = {
|
|
20
|
-
mode: ['maj', 'min'],
|
|
21
|
-
augDim: ['aug', 'dim'],
|
|
22
|
-
sus: ['sus4', 'sus2'],
|
|
23
|
-
sept: ['7', '5']
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
// Modifier symbols (sharp, flat, natural)
|
|
27
|
-
export const modifiers = ['♯', '♭', '♮'];
|
|
28
|
-
|
|
29
|
-
// Armor options with alteration value and key name
|
|
30
|
-
export const armorOptions = [
|
|
31
|
-
{ value: '', name: 'C' },
|
|
32
|
-
{ value: '1♯', name: 'G' },
|
|
33
|
-
{ value: '2♯', name: 'D' },
|
|
34
|
-
{ value: '3♯', name: 'A' },
|
|
35
|
-
{ value: '4♯', name: 'E' },
|
|
36
|
-
{ value: '5♯', name: 'B' },
|
|
37
|
-
{ value: '6♯', name: 'F♯' },
|
|
38
|
-
{ value: '7♯', name: 'C♯' },
|
|
39
|
-
{ value: '1♭', name: 'F' },
|
|
40
|
-
{ value: '2♭', name: 'B♭' },
|
|
41
|
-
{ value: '3♭', name: 'E♭' },
|
|
42
|
-
{ value: '4♭', name: 'A♭' },
|
|
43
|
-
{ value: '5♭', name: 'D♭' },
|
|
44
|
-
{ value: '6♭', name: 'G♭' },
|
|
45
|
-
{ value: '7♭', name: 'C♭' }
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
// Musical modes
|
|
49
|
-
export const modes = ['Ionian', 'Dorian', 'Phrygian', 'Lydian', 'Mixolydian', 'Aeolian', 'Locrian'];
|
|
1
|
+
/* constants/constants.ts */
|
|
2
|
+
import type { Cadence } from '$lib/types/types';
|
|
3
|
+
|
|
4
|
+
// Definition of common musical cadences
|
|
5
|
+
export const cadencePatterns: Cadence[] = [
|
|
6
|
+
{ name: 'Perfect', chords: ['V', 'I'] },
|
|
7
|
+
{ name: 'Plagal', chords: ['IV', 'I'] },
|
|
8
|
+
{ name: 'Deceptive', chords: ['V', 'VI'] },
|
|
9
|
+
{ name: 'Half', chords: ['I', 'V'] },
|
|
10
|
+
{ name: 'Italian', chords: ['IV', 'V', 'I'] },
|
|
11
|
+
{ name: 'German', chords: ['II', 'V', 'I'] },
|
|
12
|
+
{ name: 'Phrygian', chords: ['IV', 'V', 'III'] }
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
// Fundamental root notes in order
|
|
16
|
+
export const rootNotes = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
|
|
17
|
+
|
|
18
|
+
// Different chord qualities grouped by category
|
|
19
|
+
export const qualities = {
|
|
20
|
+
mode: ['maj', 'min'],
|
|
21
|
+
augDim: ['aug', 'dim'],
|
|
22
|
+
sus: ['sus4', 'sus2'],
|
|
23
|
+
sept: ['7', '5']
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Modifier symbols (sharp, flat, natural)
|
|
27
|
+
export const modifiers = ['♯', '♭', '♮'];
|
|
28
|
+
|
|
29
|
+
// Armor options with alteration value and key name
|
|
30
|
+
export const armorOptions = [
|
|
31
|
+
{ value: '', name: 'C' },
|
|
32
|
+
{ value: '1♯', name: 'G' },
|
|
33
|
+
{ value: '2♯', name: 'D' },
|
|
34
|
+
{ value: '3♯', name: 'A' },
|
|
35
|
+
{ value: '4♯', name: 'E' },
|
|
36
|
+
{ value: '5♯', name: 'B' },
|
|
37
|
+
{ value: '6♯', name: 'F♯' },
|
|
38
|
+
{ value: '7♯', name: 'C♯' },
|
|
39
|
+
{ value: '1♭', name: 'F' },
|
|
40
|
+
{ value: '2♭', name: 'B♭' },
|
|
41
|
+
{ value: '3♭', name: 'E♭' },
|
|
42
|
+
{ value: '4♭', name: 'A♭' },
|
|
43
|
+
{ value: '5♭', name: 'D♭' },
|
|
44
|
+
{ value: '6♭', name: 'G♭' },
|
|
45
|
+
{ value: '7♭', name: 'C♭' }
|
|
46
|
+
];
|
|
47
|
+
|
|
48
|
+
// Musical modes
|
|
49
|
+
export const modes = ['Ionian', 'Dorian', 'Phrygian', 'Lydian', 'Mixolydian', 'Aeolian', 'Locrian'];
|
|
@@ -1,139 +1,139 @@
|
|
|
1
|
-
// functions/functions.svelte.ts
|
|
2
|
-
import { rootNotes, cadencePatterns, armorOptions } from '$lib/constants/constants.js';
|
|
3
|
-
import type { Chord, Cadence, ChordEntry } from '$lib/types/types';
|
|
4
|
-
|
|
5
|
-
export const chords = $state<Chord[]>([]);
|
|
6
|
-
export const suggestedCadences = $state<Cadence[]>([]);
|
|
7
|
-
export const chordEntries = $state<ChordEntry[]>([]);
|
|
8
|
-
|
|
9
|
-
export function updateCadences() {
|
|
10
|
-
console.log('Current Chords:', $state.snapshot({ chords }));
|
|
11
|
-
|
|
12
|
-
if (chordEntries.length === 0) {
|
|
13
|
-
suggestedCadences.length = 0;
|
|
14
|
-
console.log('No chords, no suggestions');
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const lastEntry = chordEntries[chordEntries.length - 1];
|
|
19
|
-
const lastChordMode = lastEntry.mode;
|
|
20
|
-
|
|
21
|
-
const romanNumerals = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'];
|
|
22
|
-
const lastChord = chordEntries[chordEntries.length - 1].chord;
|
|
23
|
-
|
|
24
|
-
const rootIndex = rootNotes.indexOf(lastChord.root.toUpperCase());
|
|
25
|
-
let lastChordRoman = rootIndex !== -1 ? romanNumerals[rootIndex] : null;
|
|
26
|
-
|
|
27
|
-
if (lastChord.modifier === '♯') {
|
|
28
|
-
lastChordRoman += '#';
|
|
29
|
-
} else if (lastChord.modifier === '♭') {
|
|
30
|
-
lastChordRoman += 'b';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
console.log('Last Chord Roman:', lastChordRoman);
|
|
34
|
-
|
|
35
|
-
const suggestions = cadencePatterns.filter((cadence) => {
|
|
36
|
-
return cadence.chords[0] === lastChordRoman;
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
suggestedCadences.length = 0;
|
|
40
|
-
suggestedCadences.push(...suggestions);
|
|
41
|
-
|
|
42
|
-
console.log('Suggested Cadences:', $state.snapshot({ suggestedCadences }));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function toggleModifier(chordIndex: number, modifier: string) {
|
|
46
|
-
const chord = chordEntries[chordIndex].chord;
|
|
47
|
-
chord.modifier = chord.modifier === modifier ? undefined : modifier;
|
|
48
|
-
updateCadences();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function getArmorInfo(armorName: string) {
|
|
52
|
-
const armor = armorOptions.find((a) => a.name === armorName);
|
|
53
|
-
return armor ? `${armor.name}${armor.value ? ` (${armor.value})` : ''}` : '';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function getDurationValue(duration: string): number {
|
|
57
|
-
const [numerator, denominator] = duration.split('/').map(Number);
|
|
58
|
-
return denominator ? numerator / denominator : numerator;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function updateMeasureInfo() {
|
|
62
|
-
let currentMeasure = 1;
|
|
63
|
-
let currentBeat = 0;
|
|
64
|
-
let currentTimeSignature = { numerator: 4, denominator: 4 };
|
|
65
|
-
|
|
66
|
-
for (let i = 0; i < chordEntries.length; i++) {
|
|
67
|
-
const entry = chordEntries[i];
|
|
68
|
-
|
|
69
|
-
if (entry.timeSignature) {
|
|
70
|
-
currentTimeSignature = entry.timeSignature;
|
|
71
|
-
if (currentBeat > 0) {
|
|
72
|
-
currentMeasure++;
|
|
73
|
-
currentBeat = 0;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const chordDuration = getDurationValue(entry.chord.duration);
|
|
78
|
-
const beatsPerMeasure = currentTimeSignature.numerator;
|
|
79
|
-
|
|
80
|
-
const measureStart = currentMeasure;
|
|
81
|
-
const beatStart = currentBeat;
|
|
82
|
-
|
|
83
|
-
currentBeat += chordDuration * beatsPerMeasure;
|
|
84
|
-
while (currentBeat >= beatsPerMeasure) {
|
|
85
|
-
currentMeasure++;
|
|
86
|
-
currentBeat -= beatsPerMeasure;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
chordEntries[i] = {
|
|
90
|
-
...entry,
|
|
91
|
-
measureInfo: {
|
|
92
|
-
start: measureStart,
|
|
93
|
-
end: currentMeasure,
|
|
94
|
-
beatStart: beatStart
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function addChordEntry() {
|
|
101
|
-
const newEntry = {
|
|
102
|
-
chord: { root: 'C', quality: 'maj', modifier: undefined, duration: '1' },
|
|
103
|
-
timeSignature: chordEntries.length === 0 ? { numerator: 4, denominator: 4 } : undefined,
|
|
104
|
-
armor: '',
|
|
105
|
-
measureInfo: { start: 1, end: 1, beatStart: 0 }
|
|
106
|
-
};
|
|
107
|
-
chordEntries.push(newEntry);
|
|
108
|
-
updateCadences();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export function updateChordEntry(index: number, updatedEntry: Partial<ChordEntry>) {
|
|
112
|
-
if (index >= 0 && index < chordEntries.length) {
|
|
113
|
-
chordEntries[index] = { ...chordEntries[index], ...updatedEntry };
|
|
114
|
-
updateCadences();
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function handleChordChange(index: number, changes: Partial<ChordEntry>) {
|
|
119
|
-
if (index >= 0 && index < chordEntries.length) {
|
|
120
|
-
const currentEntry = chordEntries[index];
|
|
121
|
-
const updatedEntry: ChordEntry = { ...currentEntry };
|
|
122
|
-
|
|
123
|
-
if (changes.chord) {
|
|
124
|
-
updatedEntry.chord = { ...currentEntry.chord, ...changes.chord };
|
|
125
|
-
}
|
|
126
|
-
if (changes.timeSignature) {
|
|
127
|
-
updatedEntry.timeSignature = changes.timeSignature;
|
|
128
|
-
}
|
|
129
|
-
if (changes.armor !== undefined) {
|
|
130
|
-
updatedEntry.armor = changes.armor;
|
|
131
|
-
}
|
|
132
|
-
if (changes.mode !== undefined) {
|
|
133
|
-
updatedEntry.mode = changes.mode;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
chordEntries[index] = updatedEntry;
|
|
137
|
-
updateCadences();
|
|
138
|
-
}
|
|
139
|
-
}
|
|
1
|
+
// functions/functions.svelte.ts
|
|
2
|
+
import { rootNotes, cadencePatterns, armorOptions } from '$lib/constants/constants.js';
|
|
3
|
+
import type { Chord, Cadence, ChordEntry } from '$lib/types/types';
|
|
4
|
+
|
|
5
|
+
export const chords = $state<Chord[]>([]);
|
|
6
|
+
export const suggestedCadences = $state<Cadence[]>([]);
|
|
7
|
+
export const chordEntries = $state<ChordEntry[]>([]);
|
|
8
|
+
|
|
9
|
+
export function updateCadences() {
|
|
10
|
+
console.log('Current Chords:', $state.snapshot({ chords }));
|
|
11
|
+
|
|
12
|
+
if (chordEntries.length === 0) {
|
|
13
|
+
suggestedCadences.length = 0;
|
|
14
|
+
console.log('No chords, no suggestions');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const lastEntry = chordEntries[chordEntries.length - 1];
|
|
19
|
+
const lastChordMode = lastEntry.mode;
|
|
20
|
+
|
|
21
|
+
const romanNumerals = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'];
|
|
22
|
+
const lastChord = chordEntries[chordEntries.length - 1].chord;
|
|
23
|
+
|
|
24
|
+
const rootIndex = rootNotes.indexOf(lastChord.root.toUpperCase());
|
|
25
|
+
let lastChordRoman = rootIndex !== -1 ? romanNumerals[rootIndex] : null;
|
|
26
|
+
|
|
27
|
+
if (lastChord.modifier === '♯') {
|
|
28
|
+
lastChordRoman += '#';
|
|
29
|
+
} else if (lastChord.modifier === '♭') {
|
|
30
|
+
lastChordRoman += 'b';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log('Last Chord Roman:', lastChordRoman);
|
|
34
|
+
|
|
35
|
+
const suggestions = cadencePatterns.filter((cadence) => {
|
|
36
|
+
return cadence.chords[0] === lastChordRoman;
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
suggestedCadences.length = 0;
|
|
40
|
+
suggestedCadences.push(...suggestions);
|
|
41
|
+
|
|
42
|
+
console.log('Suggested Cadences:', $state.snapshot({ suggestedCadences }));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function toggleModifier(chordIndex: number, modifier: string) {
|
|
46
|
+
const chord = chordEntries[chordIndex].chord;
|
|
47
|
+
chord.modifier = chord.modifier === modifier ? undefined : modifier;
|
|
48
|
+
updateCadences();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getArmorInfo(armorName: string) {
|
|
52
|
+
const armor = armorOptions.find((a) => a.name === armorName);
|
|
53
|
+
return armor ? `${armor.name}${armor.value ? ` (${armor.value})` : ''}` : '';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getDurationValue(duration: string): number {
|
|
57
|
+
const [numerator, denominator] = duration.split('/').map(Number);
|
|
58
|
+
return denominator ? numerator / denominator : numerator;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function updateMeasureInfo() {
|
|
62
|
+
let currentMeasure = 1;
|
|
63
|
+
let currentBeat = 0;
|
|
64
|
+
let currentTimeSignature = { numerator: 4, denominator: 4 };
|
|
65
|
+
|
|
66
|
+
for (let i = 0; i < chordEntries.length; i++) {
|
|
67
|
+
const entry = chordEntries[i];
|
|
68
|
+
|
|
69
|
+
if (entry.timeSignature) {
|
|
70
|
+
currentTimeSignature = entry.timeSignature;
|
|
71
|
+
if (currentBeat > 0) {
|
|
72
|
+
currentMeasure++;
|
|
73
|
+
currentBeat = 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const chordDuration = getDurationValue(entry.chord.duration);
|
|
78
|
+
const beatsPerMeasure = currentTimeSignature.numerator;
|
|
79
|
+
|
|
80
|
+
const measureStart = currentMeasure;
|
|
81
|
+
const beatStart = currentBeat;
|
|
82
|
+
|
|
83
|
+
currentBeat += chordDuration * beatsPerMeasure;
|
|
84
|
+
while (currentBeat >= beatsPerMeasure) {
|
|
85
|
+
currentMeasure++;
|
|
86
|
+
currentBeat -= beatsPerMeasure;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
chordEntries[i] = {
|
|
90
|
+
...entry,
|
|
91
|
+
measureInfo: {
|
|
92
|
+
start: measureStart,
|
|
93
|
+
end: currentMeasure,
|
|
94
|
+
beatStart: beatStart
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function addChordEntry() {
|
|
101
|
+
const newEntry = {
|
|
102
|
+
chord: { root: 'C', quality: 'maj', modifier: undefined, duration: '1' },
|
|
103
|
+
timeSignature: chordEntries.length === 0 ? { numerator: 4, denominator: 4 } : undefined,
|
|
104
|
+
armor: '',
|
|
105
|
+
measureInfo: { start: 1, end: 1, beatStart: 0 }
|
|
106
|
+
};
|
|
107
|
+
chordEntries.push(newEntry);
|
|
108
|
+
updateCadences();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function updateChordEntry(index: number, updatedEntry: Partial<ChordEntry>) {
|
|
112
|
+
if (index >= 0 && index < chordEntries.length) {
|
|
113
|
+
chordEntries[index] = { ...chordEntries[index], ...updatedEntry };
|
|
114
|
+
updateCadences();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function handleChordChange(index: number, changes: Partial<ChordEntry>) {
|
|
119
|
+
if (index >= 0 && index < chordEntries.length) {
|
|
120
|
+
const currentEntry = chordEntries[index];
|
|
121
|
+
const updatedEntry: ChordEntry = { ...currentEntry };
|
|
122
|
+
|
|
123
|
+
if (changes.chord) {
|
|
124
|
+
updatedEntry.chord = { ...currentEntry.chord, ...changes.chord };
|
|
125
|
+
}
|
|
126
|
+
if (changes.timeSignature) {
|
|
127
|
+
updatedEntry.timeSignature = changes.timeSignature;
|
|
128
|
+
}
|
|
129
|
+
if (changes.armor !== undefined) {
|
|
130
|
+
updatedEntry.armor = changes.armor;
|
|
131
|
+
}
|
|
132
|
+
if (changes.mode !== undefined) {
|
|
133
|
+
updatedEntry.mode = changes.mode;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
chordEntries[index] = updatedEntry;
|
|
137
|
+
updateCadences();
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
// functions/rules.ts
|
|
2
|
-
import { rootNotes, armorOptions, modes } from '$lib/constants/constants.js';
|
|
3
|
-
import type { ChordEntry } from '../types/types';
|
|
4
|
-
|
|
5
|
-
// Function to get scale notes based on the armor
|
|
6
|
-
export function getScaleNotes(armor: string): string[] {
|
|
7
|
-
const baseScale = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
|
|
8
|
-
const armorInfo = armorOptions.find((a) => a.name === armor);
|
|
9
|
-
|
|
10
|
-
if (!armorInfo) return baseScale;
|
|
11
|
-
|
|
12
|
-
const sharpCount = armorInfo.value.includes('♯') ? parseInt(armorInfo.value) : 0;
|
|
13
|
-
const flatCount = armorInfo.value.includes('♭') ? parseInt(armorInfo.value) : 0;
|
|
14
|
-
|
|
15
|
-
if (sharpCount > 0) {
|
|
16
|
-
const sharps = ['F', 'C', 'G', 'D', 'A', 'E', 'B'].slice(0, sharpCount);
|
|
17
|
-
return baseScale.map((note) => (sharps.includes(note) ? note + '♯' : note));
|
|
18
|
-
} else if (flatCount > 0) {
|
|
19
|
-
const flats = ['B', 'E', 'A', 'D', 'G', 'C', 'F'].slice(0, flatCount);
|
|
20
|
-
return baseScale.map((note) => (flats.includes(note) ? note + '♭' : note));
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
return baseScale;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Function to check if a chord is in the scale
|
|
27
|
-
export function isChordInScale(chordEntry: ChordEntry): boolean {
|
|
28
|
-
const scaleNotes = getScaleNotes(chordEntry.armor);
|
|
29
|
-
const { root, quality, modifier } = chordEntry.chord;
|
|
30
|
-
console.log({ scaleNotes });
|
|
31
|
-
// Check if the root note is in the scale
|
|
32
|
-
// if (!scaleNotes.includes(root)) return false;
|
|
33
|
-
|
|
34
|
-
// Get chord notes considering the quality and the modifier
|
|
35
|
-
const chordNotes = getChordNotes(root, quality, modifier);
|
|
36
|
-
return chordNotes.every((note) => scaleNotes.includes(note));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Function to get the degrees of the scale
|
|
40
|
-
export function getScaleDegrees(armor: string, mode: string): string[] {
|
|
41
|
-
const scaleNotes = getScaleNotes(armor);
|
|
42
|
-
const modeIndex = modes.indexOf(mode);
|
|
43
|
-
if (modeIndex === -1) return [];
|
|
44
|
-
|
|
45
|
-
const rotatedScale = [...scaleNotes.slice(modeIndex), ...scaleNotes.slice(0, modeIndex)];
|
|
46
|
-
return rotatedScale.map((note, index) => {
|
|
47
|
-
const degree = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'][index];
|
|
48
|
-
return `${degree} (${note})`;
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Function to suggest valid chords based on armor and mode
|
|
53
|
-
export function suggestValidChords(armor: string, mode: string): string[] {
|
|
54
|
-
const scaleNotes = getScaleNotes(armor);
|
|
55
|
-
const degrees = getScaleDegrees(armor, mode);
|
|
56
|
-
// implement logic to suggest chords based on the scale notes and mode
|
|
57
|
-
return degrees;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Helper function to get chord notes based on root and quality
|
|
61
|
-
function getChordNotes(root: string, quality: string, modifier?: string): string[] {
|
|
62
|
-
const chromaticScale = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B'];
|
|
63
|
-
let rootIndex = chromaticScale.indexOf(root);
|
|
64
|
-
|
|
65
|
-
// Apply modifier to root if present
|
|
66
|
-
if (modifier) {
|
|
67
|
-
if (modifier === '♯') rootIndex = (rootIndex + 1) % 12;
|
|
68
|
-
if (modifier === '♭') rootIndex = (rootIndex - 1 + 12) % 12;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const modifiedRoot = chromaticScale[rootIndex];
|
|
72
|
-
|
|
73
|
-
let intervals: number[] = [0]; // Root note is always included
|
|
74
|
-
|
|
75
|
-
// Base triad
|
|
76
|
-
if (quality.includes('min')) {
|
|
77
|
-
intervals.push(3, 7); // Minor triad
|
|
78
|
-
} else if (quality.includes('aug')) {
|
|
79
|
-
intervals.push(4, 8); // Augmented triad
|
|
80
|
-
} else if (quality.includes('dim')) {
|
|
81
|
-
intervals.push(3, 6); // Diminished triad
|
|
82
|
-
} else {
|
|
83
|
-
intervals.push(4, 7); // Major triad (default)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Sevenths
|
|
87
|
-
if (quality.includes('7')) {
|
|
88
|
-
if (quality.includes('maj7')) {
|
|
89
|
-
intervals.push(11); // Major seventh
|
|
90
|
-
} else if (quality.includes('dim')) {
|
|
91
|
-
intervals.push(9); // Diminished seventh
|
|
92
|
-
} else {
|
|
93
|
-
intervals.push(10); // Minor seventh (dominant seventh)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Sixths
|
|
98
|
-
if (quality.includes('6')) {
|
|
99
|
-
intervals.push(9); // Major sixth
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Suspended chords
|
|
103
|
-
if (quality.includes('sus4')) {
|
|
104
|
-
intervals = intervals.filter((i) => i !== 4).concat(5);
|
|
105
|
-
} else if (quality.includes('sus2')) {
|
|
106
|
-
intervals = intervals.filter((i) => i !== 4).concat(2);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Add9, add11, add13
|
|
110
|
-
if (quality.includes('add9')) intervals.push(14);
|
|
111
|
-
if (quality.includes('add11')) intervals.push(17);
|
|
112
|
-
if (quality.includes('add13')) intervals.push(21);
|
|
113
|
-
|
|
114
|
-
// Remove duplicates and sort
|
|
115
|
-
intervals = [...new Set(intervals)].sort((a, b) => a - b);
|
|
116
|
-
|
|
117
|
-
return intervals.map((interval) => chromaticScale[(rootIndex + interval) % 12]);
|
|
118
|
-
}
|
|
1
|
+
// functions/rules.ts
|
|
2
|
+
import { rootNotes, armorOptions, modes } from '$lib/constants/constants.js';
|
|
3
|
+
import type { ChordEntry } from '../types/types';
|
|
4
|
+
|
|
5
|
+
// Function to get scale notes based on the armor
|
|
6
|
+
export function getScaleNotes(armor: string): string[] {
|
|
7
|
+
const baseScale = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
|
|
8
|
+
const armorInfo = armorOptions.find((a) => a.name === armor);
|
|
9
|
+
|
|
10
|
+
if (!armorInfo) return baseScale;
|
|
11
|
+
|
|
12
|
+
const sharpCount = armorInfo.value.includes('♯') ? parseInt(armorInfo.value) : 0;
|
|
13
|
+
const flatCount = armorInfo.value.includes('♭') ? parseInt(armorInfo.value) : 0;
|
|
14
|
+
|
|
15
|
+
if (sharpCount > 0) {
|
|
16
|
+
const sharps = ['F', 'C', 'G', 'D', 'A', 'E', 'B'].slice(0, sharpCount);
|
|
17
|
+
return baseScale.map((note) => (sharps.includes(note) ? note + '♯' : note));
|
|
18
|
+
} else if (flatCount > 0) {
|
|
19
|
+
const flats = ['B', 'E', 'A', 'D', 'G', 'C', 'F'].slice(0, flatCount);
|
|
20
|
+
return baseScale.map((note) => (flats.includes(note) ? note + '♭' : note));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return baseScale;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Function to check if a chord is in the scale
|
|
27
|
+
export function isChordInScale(chordEntry: ChordEntry): boolean {
|
|
28
|
+
const scaleNotes = getScaleNotes(chordEntry.armor);
|
|
29
|
+
const { root, quality, modifier } = chordEntry.chord;
|
|
30
|
+
console.log({ scaleNotes });
|
|
31
|
+
// Check if the root note is in the scale
|
|
32
|
+
// if (!scaleNotes.includes(root)) return false;
|
|
33
|
+
|
|
34
|
+
// Get chord notes considering the quality and the modifier
|
|
35
|
+
const chordNotes = getChordNotes(root, quality, modifier);
|
|
36
|
+
return chordNotes.every((note) => scaleNotes.includes(note));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Function to get the degrees of the scale
|
|
40
|
+
export function getScaleDegrees(armor: string, mode: string): string[] {
|
|
41
|
+
const scaleNotes = getScaleNotes(armor);
|
|
42
|
+
const modeIndex = modes.indexOf(mode);
|
|
43
|
+
if (modeIndex === -1) return [];
|
|
44
|
+
|
|
45
|
+
const rotatedScale = [...scaleNotes.slice(modeIndex), ...scaleNotes.slice(0, modeIndex)];
|
|
46
|
+
return rotatedScale.map((note, index) => {
|
|
47
|
+
const degree = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'][index];
|
|
48
|
+
return `${degree} (${note})`;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Function to suggest valid chords based on armor and mode
|
|
53
|
+
export function suggestValidChords(armor: string, mode: string): string[] {
|
|
54
|
+
const scaleNotes = getScaleNotes(armor);
|
|
55
|
+
const degrees = getScaleDegrees(armor, mode);
|
|
56
|
+
// implement logic to suggest chords based on the scale notes and mode
|
|
57
|
+
return degrees;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Helper function to get chord notes based on root and quality
|
|
61
|
+
function getChordNotes(root: string, quality: string, modifier?: string): string[] {
|
|
62
|
+
const chromaticScale = ['C', 'C♯', 'D', 'D♯', 'E', 'F', 'F♯', 'G', 'G♯', 'A', 'A♯', 'B'];
|
|
63
|
+
let rootIndex = chromaticScale.indexOf(root);
|
|
64
|
+
|
|
65
|
+
// Apply modifier to root if present
|
|
66
|
+
if (modifier) {
|
|
67
|
+
if (modifier === '♯') rootIndex = (rootIndex + 1) % 12;
|
|
68
|
+
if (modifier === '♭') rootIndex = (rootIndex - 1 + 12) % 12;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const modifiedRoot = chromaticScale[rootIndex];
|
|
72
|
+
|
|
73
|
+
let intervals: number[] = [0]; // Root note is always included
|
|
74
|
+
|
|
75
|
+
// Base triad
|
|
76
|
+
if (quality.includes('min')) {
|
|
77
|
+
intervals.push(3, 7); // Minor triad
|
|
78
|
+
} else if (quality.includes('aug')) {
|
|
79
|
+
intervals.push(4, 8); // Augmented triad
|
|
80
|
+
} else if (quality.includes('dim')) {
|
|
81
|
+
intervals.push(3, 6); // Diminished triad
|
|
82
|
+
} else {
|
|
83
|
+
intervals.push(4, 7); // Major triad (default)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Sevenths
|
|
87
|
+
if (quality.includes('7')) {
|
|
88
|
+
if (quality.includes('maj7')) {
|
|
89
|
+
intervals.push(11); // Major seventh
|
|
90
|
+
} else if (quality.includes('dim')) {
|
|
91
|
+
intervals.push(9); // Diminished seventh
|
|
92
|
+
} else {
|
|
93
|
+
intervals.push(10); // Minor seventh (dominant seventh)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Sixths
|
|
98
|
+
if (quality.includes('6')) {
|
|
99
|
+
intervals.push(9); // Major sixth
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Suspended chords
|
|
103
|
+
if (quality.includes('sus4')) {
|
|
104
|
+
intervals = intervals.filter((i) => i !== 4).concat(5);
|
|
105
|
+
} else if (quality.includes('sus2')) {
|
|
106
|
+
intervals = intervals.filter((i) => i !== 4).concat(2);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Add9, add11, add13
|
|
110
|
+
if (quality.includes('add9')) intervals.push(14);
|
|
111
|
+
if (quality.includes('add11')) intervals.push(17);
|
|
112
|
+
if (quality.includes('add13')) intervals.push(21);
|
|
113
|
+
|
|
114
|
+
// Remove duplicates and sort
|
|
115
|
+
intervals = [...new Set(intervals)].sort((a, b) => a - b);
|
|
116
|
+
|
|
117
|
+
return intervals.map((interval) => chromaticScale[(rootIndex + interval) % 12]);
|
|
118
|
+
}
|
package/src/lib/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
// auto exports of entry components
|
|
2
|
-
export * from '$lib/types/types.js';
|
|
3
|
-
export * from '$lib/functions/rules.js';
|
|
4
|
-
export * from '$lib/functions/functions.svelte.js';
|
|
5
|
-
export * from '$lib/constants/constants.js';
|
|
6
|
-
export { default as ChordVisualization } from '$lib/components/ChordVisualization.svelte';
|
|
7
|
-
export { default as ChordTable } from '$lib/components/ChordTable.svelte';
|
|
8
|
-
export { default as CadencePanel } from '$lib/components/CadencePanel.svelte';
|
|
9
|
-
export { default as App } from '$lib/components/App.svelte';
|
|
1
|
+
// auto exports of entry components
|
|
2
|
+
export * from '$lib/types/types.js';
|
|
3
|
+
export * from '$lib/functions/rules.js';
|
|
4
|
+
export * from '$lib/functions/functions.svelte.js';
|
|
5
|
+
export * from '$lib/constants/constants.js';
|
|
6
|
+
export { default as ChordVisualization } from '$lib/components/ChordVisualization.svelte';
|
|
7
|
+
export { default as ChordTable } from '$lib/components/ChordTable.svelte';
|
|
8
|
+
export { default as CadencePanel } from '$lib/components/CadencePanel.svelte';
|
|
9
|
+
export { default as App } from '$lib/components/App.svelte';
|