feat: added mode list dropdown
This commit is contained in:
@@ -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
@@ -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
@@ -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}`);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user