feat: merge mode_list.json into functional_chords.json

This commit is contained in:
Xiaohan-Tian
2025-12-17 22:10:37 -08:00
parent 1650bd05b7
commit 610b81e4cc
7 changed files with 41 additions and 85 deletions
@@ -1,5 +1,7 @@
{ {
"ionian": { "ionian": {
"name": "Ionian",
"steps": [2, 2, 1, 2, 2, 2, 1],
"T": ["I", "vi", "iii", "I⁶"], "T": ["I", "vi", "iii", "I⁶"],
"S": ["IV", "ii", "vi", "IV⁶"], "S": ["IV", "ii", "vi", "IV⁶"],
"D": ["V", "V7", "vii°", "♭II"], "D": ["V", "V7", "vii°", "♭II"],
@@ -18,6 +20,8 @@
} }
}, },
"aeolian": { "aeolian": {
"name": "Aeolian",
"steps": [2, 1, 2, 2, 1, 2, 2],
"T": ["i", "VI", "III", "i⁶"], "T": ["i", "VI", "III", "i⁶"],
"S": ["iv", "ii°", "VI", "iv⁶"], "S": ["iv", "ii°", "VI", "iv⁶"],
"D": ["v", "♭VII"], "D": ["v", "♭VII"],
@@ -34,6 +38,8 @@
} }
}, },
"harmonic_minor": { "harmonic_minor": {
"name": "Harmonic Minor",
"steps": [2, 1, 2, 2, 1, 3, 1],
"T": ["i", "VI", "III+", "i⁶"], "T": ["i", "VI", "III+", "i⁶"],
"S": ["iv", "ii°", "VI", "iv⁶"], "S": ["iv", "ii°", "VI", "iv⁶"],
"D": ["V", "V7", "vii°", "♭II"], "D": ["V", "V7", "vii°", "♭II"],
@@ -52,6 +58,8 @@
} }
}, },
"melodic_minor": { "melodic_minor": {
"name": "Melodic Minor",
"steps": [2, 1, 2, 2, 2, 2, 1],
"T": ["i", "III+", "vi", "i⁶"], "T": ["i", "III+", "vi", "i⁶"],
"S": ["IV", "ii"], "S": ["IV", "ii"],
"D": ["V", "vii°"], "D": ["V", "vii°"],
@@ -67,6 +75,8 @@
} }
}, },
"dorian": { "dorian": {
"name": "Dorian",
"steps": [2, 1, 2, 2, 2, 1, 2],
"T": ["i", "III", "i⁶"], "T": ["i", "III", "i⁶"],
"S": ["IV", "ii"], "S": ["IV", "ii"],
"D": ["v", "♭VII"], "D": ["v", "♭VII"],
@@ -81,6 +91,8 @@
} }
}, },
"phrygian": { "phrygian": {
"name": "Phrygian",
"steps": [1, 2, 2, 2, 1, 2, 2],
"T": ["i", "i⁶"], "T": ["i", "i⁶"],
"S": ["♭II", "iv"], "S": ["♭II", "iv"],
"D": [], "D": [],
@@ -92,6 +104,8 @@
} }
}, },
"lydian": { "lydian": {
"name": "Lydian",
"steps": [2, 2, 2, 1, 2, 2, 1],
"T": ["I", "I⁶"], "T": ["I", "I⁶"],
"S": ["II"], "S": ["II"],
"D": ["V"], "D": ["V"],
@@ -103,6 +117,8 @@
} }
}, },
"mixolydian": { "mixolydian": {
"name": "Mixolydian",
"steps": [2, 2, 1, 2, 2, 1, 2],
"T": ["I", "vi"], "T": ["I", "vi"],
"S": ["IV", "ii"], "S": ["IV", "ii"],
"D": ["v", "♭VII"], "D": ["v", "♭VII"],
@@ -116,6 +132,8 @@
} }
}, },
"locrian": { "locrian": {
"name": "Locrian",
"steps": [1, 2, 2, 1, 2, 2, 2],
"T": ["i°"], "T": ["i°"],
"S": ["♭II", "iv"], "S": ["♭II", "iv"],
"D": [], "D": [],
@@ -126,6 +144,8 @@
} }
}, },
"phrygian_dominant": { "phrygian_dominant": {
"name": "Phrygian Dominant",
"steps": [1, 3, 1, 2, 1, 2, 2],
"T": ["I", "I⁶"], "T": ["I", "I⁶"],
"S": ["♭II", "iv"], "S": ["♭II", "iv"],
"D": ["v°", "vii"], "D": ["v°", "vii"],
@@ -139,6 +159,8 @@
} }
}, },
"harmonic_major": { "harmonic_major": {
"name": "Harmonic Major",
"steps": [2, 2, 1, 2, 1, 3, 1],
"T": ["I", "VI", "iii"], "T": ["I", "VI", "iii"],
"S": ["IV", "ii"], "S": ["IV", "ii"],
"D": ["V", "V7", "vii°", "♭II"], "D": ["V", "V7", "vii°", "♭II"],
-59
View File
@@ -1,59 +0,0 @@
{
"modes": [
{
"id": "ionian",
"name": "Ionian",
"steps": [2, 2, 1, 2, 2, 2, 1]
},
{
"id": "aeolian",
"name": "Aeolian",
"steps": [2, 1, 2, 2, 1, 2, 2]
},
{
"id": "harmonic_minor",
"name": "Harmonic Minor",
"steps": [2, 1, 2, 2, 1, 3, 1]
},
{
"id": "melodic_minor",
"name": "Melodic Minor",
"steps": [2, 1, 2, 2, 2, 2, 1]
},
{
"id": "dorian",
"name": "Dorian",
"steps": [2, 1, 2, 2, 2, 1, 2]
},
{
"id": "phrygian",
"name": "Phrygian",
"steps": [1, 2, 2, 2, 1, 2, 2]
},
{
"id": "lydian",
"name": "Lydian",
"steps": [2, 2, 2, 1, 2, 2, 1]
},
{
"id": "mixolydian",
"name": "Mixolydian",
"steps": [2, 2, 1, 2, 2, 1, 2]
},
{
"id": "locrian",
"name": "Locrian",
"steps": [1, 2, 2, 1, 2, 2, 2]
},
{
"id": "phrygian_dominant",
"name": "Phrygian Dominant",
"steps": [1, 3, 1, 2, 1, 2, 2]
},
{
"id": "harmonic_major",
"name": "Harmonic Major",
"steps": [2, 2, 1, 2, 1, 3, 1]
}
]
}
+13 -11
View File
@@ -45,28 +45,30 @@ function App() {
// Load all mode and chord data files in parallel // Load all mode and chord data files in parallel
try { try {
const [modeListResponse, functionalChordsResponse] = await Promise.all([ const [functionalChordsResponse] = await Promise.all([
fetch(`${import.meta.env.BASE_URL}resources/modes/mode_list.json`),
fetch(`${import.meta.env.BASE_URL}resources/modes/functional_chords.json`) fetch(`${import.meta.env.BASE_URL}resources/modes/functional_chords.json`)
]); ]);
const [modeListData, functionalChordsData] = await Promise.all([ const [functionalChordsData] = await Promise.all([
modeListResponse.json(),
functionalChordsResponse.json() functionalChordsResponse.json()
]); ]);
// Store mode data with id, name, and steps // Store functional chords data (includes name, steps, T/S/D groups, and chord notes for each mode)
KGCore.MODE_DATA = modeListData.modes;
console.log(`Loaded ${modeListData.modes.length} modes:`, modeListData.modes.map((m: { name: string }) => m.name));
// Store functional chords data (T/S/D by mode, including mode-specific chord notes)
KGCore.FUNCTIONAL_CHORDS_DATA = functionalChordsData; KGCore.FUNCTIONAL_CHORDS_DATA = functionalChordsData;
console.log(`Loaded functional chords for ${Object.keys(functionalChordsData).length} modes`); console.log(`Loaded functional chords for ${Object.keys(functionalChordsData).length} modes`);
} catch (error) { } catch (error) {
console.error('Failed to load mode/chord data:', error); console.error('Failed to load mode/chord data:', error);
// Fallback to defaults // Fallback to defaults
KGCore.MODE_DATA = [{ id: 'ionian', name: 'Ionian', steps: [2, 2, 1, 2, 2, 2, 1] }]; KGCore.FUNCTIONAL_CHORDS_DATA = {
KGCore.FUNCTIONAL_CHORDS_DATA = {}; ionian: {
name: 'Ionian',
steps: [2, 2, 1, 2, 2, 2, 1],
T: [],
S: [],
D: [],
chords: {}
}
};
} }
// Log maxBars after initialization completes // Log maxBars after initialization completes
@@ -38,7 +38,7 @@ const PianoRollToolbar: React.FC<PianoRollToolbarProps> = ({
<div className="toolbar-left"> <div className="toolbar-left">
{/* Left section with mode and chord guide dropdowns */} {/* Left section with mode and chord guide dropdowns */}
<KGDropdown <KGDropdown
options={KGCore.MODE_DATA.map(m => ({ label: m.name, value: m.id }))} options={Object.entries(KGCore.FUNCTIONAL_CHORDS_DATA).map(([id, data]) => ({ label: data.name, value: id }))}
value={selectedMode} value={selectedMode}
onChange={(value) => onModeChange(value)} onChange={(value) => onModeChange(value)}
label="Mode" label="Mode"
+1 -2
View File
@@ -18,8 +18,7 @@ export class KGCore {
private static _instance: KGCore | null = null; private static _instance: KGCore | null = null;
// Global music data resources // Global music data resources
public static MODE_DATA: Array<{ id: string; name: string; steps: number[] }> = []; // Modes with id, display name, and interval steps public static FUNCTIONAL_CHORDS_DATA: Record<string, { name: string; steps: number[]; T: string[]; S: string[]; D: string[]; chords: Record<string, string[]> }> = {}; // Functional chords by mode (T/S/D) with mode-specific chord notes, and mode metadata
public static FUNCTIONAL_CHORDS_DATA: Record<string, { T: string[]; S: string[]; D: string[]; chords: Record<string, string[]> }> = {}; // Functional chords by mode (T/S/D) with mode-specific chord notes
private currentProject: KGProject = new KGProject(); private currentProject: KGProject = new KGProject();
+1 -9
View File
@@ -16,15 +16,7 @@ import functionalChordsData from '../../public/resources/modes/functional_chords
describe('scaleUtil', () => { describe('scaleUtil', () => {
// Setup: Mock KGCore with real chord data // Setup: Mock KGCore with real chord data
beforeEach(() => { beforeEach(() => {
// Mock MODE_DATA with ionian for basic tests // Use real functional chords data from JSON file (includes name, steps, and chord data)
KGCore.MODE_DATA = [
{ id: 'ionian', name: 'Ionian', steps: [2, 2, 1, 2, 2, 2, 1] },
{ id: 'dorian', name: 'Dorian', steps: [2, 1, 2, 2, 2, 1, 2] },
{ id: 'aeolian', name: 'Aeolian', steps: [2, 1, 2, 2, 1, 2, 2] },
{ id: 'mixolydian', name: 'Mixolydian', steps: [2, 2, 1, 2, 2, 1, 2] }
]
// Use real functional chords data from JSON file
KGCore.FUNCTIONAL_CHORDS_DATA = functionalChordsData KGCore.FUNCTIONAL_CHORDS_DATA = functionalChordsData
}) })
+3 -3
View File
@@ -66,12 +66,12 @@ export const getScalePitchClasses = (rootNote: string, modeSteps: number[]): num
* @returns Array of interval steps, or default ionian if not found * @returns Array of interval steps, or default ionian if not found
*/ */
export const getModeSteps = (modeId: string): number[] => { export const getModeSteps = (modeId: string): number[] => {
const modeData = KGCore.MODE_DATA.find(m => m.id === modeId); const functionalChords = KGCore.FUNCTIONAL_CHORDS_DATA[modeId];
if (!modeData) { if (!functionalChords || !functionalChords.steps) {
console.warn(`Mode not found: ${modeId}, defaulting to ionian`); console.warn(`Mode not found: ${modeId}, defaulting to ionian`);
return [2, 2, 1, 2, 2, 2, 1]; // Default to ionian (major scale) return [2, 2, 1, 2, 2, 2, 1]; // Default to ionian (major scale)
} }
return modeData.steps; return functionalChords.steps;
}; };
/** /**