@medyll/idae-cadenzia 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @medyll/idae-cadenzia
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - - feat(ajouter): des constantes musicales et améliorer la gestion des accords avec des options d'armure ([0f48cd9](https://github.com/medyll/idae/commit/0f48cd947ceabadf13a7488110c8bbdc6b32e638)) - 2024-11-16 by @medyll
8
+ - feat(ajouter): le composant App et les composants associés pour la visualisation des accords ([e7c9517](https://github.com/medyll/idae/commit/e7c95178e328e56f514678bed4c96015e7ef7529)) - 2024-11-16 by @medyll
9
+
3
10
  ## 0.1.0
4
11
 
5
12
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medyll/idae-cadenzia",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "vite dev",
@@ -1,18 +1,23 @@
1
1
  <!-- components/ChordTable.svelte -->
2
2
  <script lang="ts">
3
3
  import {
4
- chordEntries,
5
- updateCadences,
6
4
  qualities,
5
+ armorOptions,
6
+ modes,
7
7
  rootNotes,
8
- modifiers,
9
- toggleModifier
10
- } from '../functions/functions.svelte.js';
8
+ modifiers
9
+ } from '$lib/constants/constants.js';
10
+ import { getScaleNotes, isChordInScale } from '$lib/functions/rules.js';
11
+
12
+ import type { ChordEntry } from '$lib/types/types.js';
13
+ import { chordEntries, updateCadences, toggleModifier } from '../functions/functions.svelte.js';
11
14
 
12
15
  function addChordEntry() {
13
16
  chordEntries.push({
14
17
  chord: { root: 'C', quality: qualities.mode[0], modifier: undefined, duration: '1' },
15
- timeSignature: chordEntries.length === 0 ? { numerator: 4, denominator: 4 } : undefined
18
+ timeSignature: chordEntries.length === 0 ? { numerator: 4, denominator: 4 } : undefined,
19
+ armor: '',
20
+ mode: undefined
16
21
  });
17
22
  updateCadences();
18
23
  }
@@ -33,6 +38,15 @@
33
38
  chordEntries[index].timeSignature = { numerator: 4, denominator: 4 };
34
39
  }
35
40
  }
41
+
42
+ function isNoteInScale(note: string, entry: ChordEntry): boolean {
43
+ const scaleNotes = getScaleNotes(entry.armor);
44
+ return scaleNotes.includes(note);
45
+ }
46
+
47
+ function isChordValid(entry: ChordEntry): boolean {
48
+ return isChordInScale(entry);
49
+ }
36
50
  </script>
37
51
 
38
52
  <table>
@@ -40,6 +54,7 @@
40
54
  <tr>
41
55
  <th>Measure</th>
42
56
  <th>Time Signature</th>
57
+ <th>Armor</th>
43
58
  <th>Root</th>
44
59
  <th>Quality</th>
45
60
  <th>Modifier</th>
@@ -51,7 +66,7 @@
51
66
  {#each chordEntries as entry, i}
52
67
  <tr>
53
68
  <td>{i + 1}</td>
54
- <td>
69
+ <td class="timeSignature">
55
70
  {#if entry.timeSignature}
56
71
  <input type="number" bind:value={entry.timeSignature.numerator} min="1" max="32" />
57
72
  /
@@ -60,10 +75,34 @@
60
75
  <button onclick={() => toggleTimeSignature(i)}>Add Time Signature</button>
61
76
  {/if}
62
77
  </td>
78
+ <td>
79
+ <div>
80
+ <label for="armor-{i}">Armor:</label>
81
+ <select id="armor-{i}" bind:value={entry.armor} onchange={handleChordChange}>
82
+ {#each armorOptions as armor}
83
+ <option value={armor.name}>
84
+ {armor.name}
85
+ {armor.value ? `(${armor.value})` : ''}
86
+ </option>
87
+ {/each}
88
+ </select>
89
+ </div>
90
+ <div>
91
+ <label for="mode-{i}">Mode:</label>
92
+ <select id="mode-{i}" bind:value={entry.mode} onchange={handleChordChange}>
93
+ <option value={undefined}>Select mode</option>
94
+ {#each modes as mode}
95
+ <option value={mode}>{mode}</option>
96
+ {/each}
97
+ </select>
98
+ </div>
99
+ </td>
63
100
  <td>
64
101
  <select bind:value={entry.chord.root} onchange={handleChordChange}>
65
102
  {#each rootNotes as note}
66
- <option value={note}>{note}</option>
103
+ <option value={note} class:not-in-scale={!isChordValid({...entry, chord: {...entry.chord, root: note}})}>
104
+ {note}
105
+ </option>
67
106
  {/each}
68
107
  </select>
69
108
  </td>
@@ -71,7 +110,7 @@
71
110
  {#each Object.entries(qualities) as [group, qualityOptions]}
72
111
  <div>
73
112
  {#each qualityOptions as quality}
74
- <label>
113
+ <label class:not-in-scale={!isChordValid({...entry, chord: {...entry.chord, quality}})}>
75
114
  <input
76
115
  type="radio"
77
116
  name={`quality-${group}-${i}`}
@@ -92,7 +131,7 @@
92
131
  </td>
93
132
  <td>
94
133
  {#each modifiers as modifier}
95
- <label>
134
+ <label class:not-in-scale={!isChordValid({...entry, chord: {...entry.chord, modifier}})}>
96
135
  <input
97
136
  type="radio"
98
137
  name={`modifier-${i}`}
@@ -104,7 +143,7 @@
104
143
  </label>
105
144
  {/each}
106
145
  </td>
107
- <td>
146
+ <td class="duration">
108
147
  <input
109
148
  type="text"
110
149
  bind:value={entry.chord.duration}
@@ -132,7 +171,27 @@
132
171
  td {
133
172
  border: 1px solid #ddd;
134
173
  padding: 8px;
135
- text-align: left;
174
+ text-align: center;
175
+ }
176
+ td.timeSignature {
177
+ & input[type='number'] {
178
+ width: 40px;
179
+ text-align: center;
180
+ }
181
+
182
+ & button {
183
+ padding: 5px 10px;
184
+ background-color: #f0f0f0;
185
+ border: 1px solid #ccc;
186
+ border-radius: 3px;
187
+ }
188
+ }
189
+
190
+ td.duration {
191
+ & input[type='text'] {
192
+ width: 60px;
193
+ text-align: center;
194
+ }
136
195
  }
137
196
  th {
138
197
  background-color: #f2f2f2;
@@ -142,4 +201,12 @@
142
201
  input[type='number'] {
143
202
  width: 100%;
144
203
  }
204
+
205
+ option.not-in-scale {
206
+ color: red;
207
+ }
208
+
209
+ label.not-in-scale {
210
+ color: red;
211
+ }
145
212
  </style>
@@ -1,17 +1,13 @@
1
1
  <!-- components/ChordVisualization.svelte -->
2
2
  <script lang="ts">
3
- import { chordEntries } from '$lib/functions/functions.svelte';
3
+ import { chordEntries, getArmorInfo } from '$lib/functions/functions.svelte';
4
4
  import type { ChordEntry } from '$lib/types/types';
5
5
 
6
6
  function getChordName(entry: ChordEntry) {
7
7
  const { chord } = entry;
8
8
  let name = chord.root;
9
9
 
10
- if (chord.modifier) {
11
- name += chord.modifier;
12
- }
13
-
14
- name += chord.mode ?? '';
10
+ name += chord.modifier ?? '';
15
11
  name += chord.augDim ?? '';
16
12
  name += chord.sus ?? '';
17
13
  name += chord.sept ?? '';
@@ -35,15 +31,20 @@
35
31
  {:else}
36
32
  <div class="chord-sequence">
37
33
  {#each chordEntries as entry, i}
38
- {#if entry.timeSignature}
34
+ {#if entry.timeSignature || entry.armor || entry.mode}
39
35
  <div class="signature">
40
- - Signature: {entry.timeSignature.numerator}/{entry.timeSignature.denominator}
36
+ {#if entry.timeSignature}
37
+ - Signature: {entry.timeSignature.numerator}/{entry.timeSignature.denominator}
38
+ {/if}
39
+ {#if entry.armor}
40
+ - Armure: {getArmorInfo(entry.armor)}
41
+ {/if}
42
+ {#if entry.mode}
43
+ - Mode: {entry.mode}
44
+ {/if}
41
45
  </div>
42
46
  {/if}
43
- <div
44
- class="chord"
45
- style="--baseTime:{getDurationValue(entry.chord.duration)}"
46
- >
47
+ <div class="chord" style="--baseTime:{getDurationValue(entry.chord.duration)}">
47
48
  <span>{getChordName(entry)}</span>
48
49
  <span>{entry.chord.duration}</span>
49
50
  </div>
@@ -72,10 +73,10 @@
72
73
  background-color: #fff;
73
74
  border: 1px solid #ccc;
74
75
  border-radius: 5px;
75
- box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
76
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
76
77
  text-align: center;
77
78
  width: calc(200px * var(--baseTime, 1));
78
79
  min-width: 30px; /* Ensure a minimum width for very short durations */
79
80
  max-width: 100%; /* Prevent oversized chords */
80
81
  }
81
- </style>
82
+ </style>
@@ -0,0 +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,31 +1,11 @@
1
1
  // functions/functions.svelte.ts
2
- import type { Chord, Cadence, ChordEntry } from '../types/types';
2
+ import { rootNotes, cadencePatterns, armorOptions } from '$lib/constants/constants.js';
3
+ import type { Chord, Cadence, ChordEntry } from '$lib/types/types';
3
4
 
4
5
  export const chords = $state<Chord[]>([]);
5
6
  export const suggestedCadences = $state<Cadence[]>([]);
6
7
  export const chordEntries = $state<ChordEntry[]>([]);
7
8
 
8
- export const cadencePatterns: Cadence[] = [
9
- { name: 'Perfect', chords: ['V', 'I'] },
10
- { name: 'Plagal', chords: ['IV', 'I'] },
11
- { name: 'Deceptive', chords: ['V', 'VI'] },
12
- { name: 'Half', chords: ['I', 'V'] },
13
- { name: 'Italian', chords: ['IV', 'V', 'I'] },
14
- { name: 'German', chords: ['II', 'V', 'I'] },
15
- { name: 'Phrygian', chords: ['IV', 'V', 'III'] }
16
- ];
17
-
18
- export const rootNotes = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
19
-
20
- export const qualities = {
21
- mode: ['maj', 'min'],
22
- augDim: ['aug', 'dim'],
23
- sus: ['sus4', 'sus2'],
24
- sept: ['7', '5']
25
- };
26
-
27
- export const modifiers = ['♯', '♭', '♮'];
28
-
29
9
  export function updateCadences() {
30
10
  console.log('Current Chords:', $state.snapshot({ chords }));
31
11
 
@@ -35,6 +15,9 @@ export function updateCadences() {
35
15
  return;
36
16
  }
37
17
 
18
+ const lastEntry = chordEntries[chordEntries.length - 1];
19
+ const lastChordMode = lastEntry.mode;
20
+
38
21
  const romanNumerals = ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'];
39
22
  const lastChord = chordEntries[chordEntries.length - 1].chord;
40
23
 
@@ -64,3 +47,8 @@ export function toggleModifier(chordIndex: number, modifier: string) {
64
47
  chord.modifier = chord.modifier === modifier ? undefined : modifier;
65
48
  updateCadences();
66
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
+ }
@@ -0,0 +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
+ }
package/src/lib/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  // auto exports of entry components
2
2
  export * from '$lib/types/types.js';
3
+ export * from '$lib/functions/rules.js';
3
4
  export * from '$lib/functions/functions.svelte.js';
5
+ export * from '$lib/constants/constants.js';
4
6
  export { default as ChordVisualization } from '$lib/components/ChordVisualization.svelte';
5
7
  export { default as ChordTable } from '$lib/components/ChordTable.svelte';
6
8
  export { default as CadencePanel } from '$lib/components/CadencePanel.svelte';
@@ -1,7 +1,6 @@
1
1
  export type Chord = {
2
2
  root: string;
3
3
  quality: string;
4
- mode?: string;
5
4
  augDim?: string;
6
5
  sus?: string;
7
6
  sept?: string;
@@ -16,7 +15,9 @@ export type TimeSignature = {
16
15
 
17
16
  export type ChordEntry = {
18
17
  chord: Chord;
19
- timeSignature?: TimeSignature;
18
+ timeSignature?: { numerator: number; denominator: number };
19
+ armor: string;
20
+ mode?: string;
20
21
  };
21
22
 
22
23
  export type Cadence = {