feat: added mode list dropdown

This commit is contained in:
Xiaohan-Tian
2025-12-08 22:59:09 -08:00
parent 02a7a61b1d
commit 98ed8a60ce
6 changed files with 125 additions and 7 deletions
+59
View File
@@ -0,0 +1,59 @@
{
"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]
}
]
}
+10
View File
@@ -597,6 +597,7 @@ body {
padding: 0 10px; padding: 0 10px;
user-select: none; user-select: none;
position: relative; position: relative;
z-index: 100; /* Ensure toolbar and its dropdowns appear above piano roll content */
} }
/* Override pointer-events for piano roll toolbar sections */ /* Override pointer-events for piano roll toolbar sections */
@@ -609,6 +610,15 @@ body {
font-size: 10px; font-size: 10px;
} }
.piano-roll-toolbar .toolbar-left .quant-button {
margin-left: 0px;
margin-right: 5px;
}
.piano-roll-toolbar .toolbar-left .quant-dropdown {
left: 0;
}
/* Generic button blink effect */ /* Generic button blink effect */
.quant-button.button-blink { .quant-button.button-blink {
animation: buttonBlink 0.2s ease-in-out; animation: buttonBlink 0.2s ease-in-out;
+14
View File
@@ -12,6 +12,7 @@ import { SettingsPanel } from './components/settings';
import LoadingOverlay from './components/common/LoadingOverlay'; import LoadingOverlay from './components/common/LoadingOverlay';
import { useEffect as useEffectReact, useState, useRef } from 'react'; import { useEffect as useEffectReact, useState, useRef } from 'react';
import { KGToneBuffersPool } from './core/audio-interface/KGToneBuffersPool'; import { KGToneBuffersPool } from './core/audio-interface/KGToneBuffersPool';
import { KGPianoRollState } from './core/state/KGPianoRollState';
function App() { function App() {
// Enable global keyboard handler for copy/paste and undo/redo // Enable global keyboard handler for copy/paste and undo/redo
@@ -33,6 +34,19 @@ function App() {
// Initialize store from config after ConfigManager is ready // Initialize store from config after ConfigManager is ready
await initializeFromConfig(); await initializeFromConfig();
// Load mode list from JSON
try {
const response = await fetch(`${import.meta.env.BASE_URL}resources/modes/mode_list.json`);
const data = await response.json();
const modeNames = data.modes.map((mode: { name: string; steps: number[] }) => mode.name);
KGPianoRollState.MODE_OPTIONS = modeNames;
console.log(`Loaded ${modeNames.length} modes:`, modeNames);
} catch (error) {
console.error('Failed to load mode list:', error);
// Fallback to default mode
KGPianoRollState.MODE_OPTIONS = ['ionian'];
}
// Log maxBars to console // Log maxBars to console
console.log(`Project max bars: ${maxBars}`); console.log(`Project max bars: ${maxBars}`);
}; };
+14
View File
@@ -39,6 +39,9 @@ const PianoRoll: React.FC<PianoRollProps> = ({
// Snapping state // Snapping state
const [snapping, setSnapping] = useState<string>('NO SNAP'); const [snapping, setSnapping] = useState<string>('NO SNAP');
// Mode state
const [selectedMode, setSelectedMode] = useState<string>('ionian');
// Piano roll state with temporary initial values // Piano roll state with temporary initial values
const [position, setPosition] = useState(initialPosition || { x: 0, y: 0 }); const [position, setPosition] = useState(initialPosition || { x: 0, y: 0 });
@@ -296,6 +299,15 @@ const PianoRoll: React.FC<PianoRollProps> = ({
} }
}, []); }, []);
// Handle mode selection
const handleModeSelect = useCallback((value: string) => {
setSelectedMode(value);
KGPianoRollState.instance().setCurrentMode(value);
if (DEBUG_MODE.PIANO_ROLL) {
console.log(`Selected mode: ${value}`);
}
}, []);
// Handler for receiving the setNoteUpdateCounter function from PianoRollContent // Handler for receiving the setNoteUpdateCounter function from PianoRollContent
const handleSetNoteUpdateTrigger = (setNoteFn: React.Dispatch<React.SetStateAction<number>>) => { const handleSetNoteUpdateTrigger = (setNoteFn: React.Dispatch<React.SetStateAction<number>>) => {
triggerNoteUpdateRef.current = setNoteFn; triggerNoteUpdateRef.current = setNoteFn;
@@ -765,6 +777,8 @@ const PianoRoll: React.FC<PianoRollProps> = ({
onQuantSelect={handleQuantSelect} onQuantSelect={handleQuantSelect}
snapping={snapping} snapping={snapping}
onSnappingSelect={handleSnappingSelect} onSnappingSelect={handleSnappingSelect}
selectedMode={selectedMode}
onModeChange={handleModeSelect}
blinkButton={blinkButton} blinkButton={blinkButton}
/> />
+12 -1
View File
@@ -11,6 +11,8 @@ interface PianoRollToolbarProps {
onQuantSelect: (type: 'position' | 'length', value: string) => void; onQuantSelect: (type: 'position' | 'length', value: string) => void;
snapping: string; snapping: string;
onSnappingSelect: (value: string) => void; onSnappingSelect: (value: string) => void;
selectedMode: string;
onModeChange: (value: string) => void;
blinkButton?: string | null; blinkButton?: string | null;
} }
@@ -22,12 +24,21 @@ const PianoRollToolbar: React.FC<PianoRollToolbarProps> = ({
onQuantSelect, onQuantSelect,
snapping, snapping,
onSnappingSelect, onSnappingSelect,
selectedMode,
onModeChange,
blinkButton = null blinkButton = null
}) => { }) => {
return ( return (
<div className="piano-roll-toolbar"> <div className="piano-roll-toolbar">
<div className="toolbar-left"> <div className="toolbar-left">
{/* Left section - can add more tools later */} {/* Left section with mode dropdown */}
<KGDropdown
options={KGPianoRollState.MODE_OPTIONS}
value={selectedMode}
onChange={(value) => onModeChange(value)}
label="Mode"
buttonClassName="mode-dropdown"
/>
</div> </div>
<div className="toolbar-center"> <div className="toolbar-center">
+10
View File
@@ -8,10 +8,12 @@ export class KGPianoRollState {
public static SNAP_OPTIONS: string[] = ['NO SNAP', '1/3', '1/4', '1/6', '1/8', '1/12', '1/16', '1/24', '1/32']; public static SNAP_OPTIONS: string[] = ['NO SNAP', '1/3', '1/4', '1/6', '1/8', '1/12', '1/16', '1/24', '1/32'];
public static QUANT_POS_OPTIONS: string[] = ['1/3', '1/4', '1/6', '1/8', '1/12', '1/16', '1/24', '1/32']; public static QUANT_POS_OPTIONS: string[] = ['1/3', '1/4', '1/6', '1/8', '1/12', '1/16', '1/24', '1/32'];
public static QUANT_LEN_OPTIONS: string[] = ['1/1', '1/2', '1/3', '1/4', '1/6', '1/8', '1/12', '1/16', '1/24', '1/32']; public static QUANT_LEN_OPTIONS: string[] = ['1/1', '1/2', '1/3', '1/4', '1/6', '1/8', '1/12', '1/16', '1/24', '1/32'];
public static MODE_OPTIONS: string[] = []; // Will be populated from mode_list.json
private activeTool: string = "pointer"; private activeTool: string = "pointer";
private currentSnap: string = "NO SNAP"; private currentSnap: string = "NO SNAP";
private lastEditedNoteLength: number = 1; // Default to 1 beat private lastEditedNoteLength: number = 1; // Default to 1 beat
private currentMode: string = "ionian"; // Default mode
private constructor() { private constructor() {
console.log("KGPianoRollState initialized"); console.log("KGPianoRollState initialized");
@@ -48,4 +50,12 @@ export class KGPianoRollState {
public setLastEditedNoteLength(length: number): void { public setLastEditedNoteLength(length: number): void {
this.lastEditedNoteLength = length; this.lastEditedNoteLength = length;
} }
public getCurrentMode(): string {
return this.currentMode;
}
public setCurrentMode(mode: string): void {
this.currentMode = mode;
}
} }