feat: implemented piano roll hybrid mode to allow user edit MIDI notes while observing spectrogram as reference
This commit is contained in:
@@ -44,6 +44,8 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
pianoRollMode,
|
pianoRollMode,
|
||||||
openMidiPianoRoll,
|
openMidiPianoRoll,
|
||||||
openSpectrogramViewer,
|
openSpectrogramViewer,
|
||||||
|
openHybridMode,
|
||||||
|
hybridAudioRegionId,
|
||||||
addTrack,
|
addTrack,
|
||||||
addAudioTrack,
|
addAudioTrack,
|
||||||
projectName,
|
projectName,
|
||||||
@@ -552,6 +554,19 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
openSpectrogramViewer(regionId);
|
openSpectrogramViewer(regionId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle hybrid mode open (+ button clicked on opposite-type region)
|
||||||
|
const handleOpenHybrid = (regionId: string) => {
|
||||||
|
if (pianoRollMode === 'midi-edit' && activeRegionId) {
|
||||||
|
openHybridMode(activeRegionId, regionId);
|
||||||
|
} else if (pianoRollMode === 'spectrogram' && activeRegionId) {
|
||||||
|
openHybridMode(regionId, activeRegionId);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// + button is visible only when piano roll is open and mode is not hybrid
|
||||||
|
const showHybridButtonForAudio = showPianoRoll && pianoRollMode === 'midi-edit';
|
||||||
|
const showHybridButtonForMidi = showPianoRoll && pianoRollMode === 'spectrogram';
|
||||||
|
|
||||||
// Handle piano roll close
|
// Handle piano roll close
|
||||||
const handlePianoRollClose = () => {
|
const handlePianoRollClose = () => {
|
||||||
setShowPianoRoll(false);
|
setShowPianoRoll(false);
|
||||||
@@ -844,6 +859,9 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
onRegionClick={handleRegionClick}
|
onRegionClick={handleRegionClick}
|
||||||
onOpenPianoRoll={handleOpenPianoRoll}
|
onOpenPianoRoll={handleOpenPianoRoll}
|
||||||
onOpenSpectrogram={handleOpenSpectrogram}
|
onOpenSpectrogram={handleOpenSpectrogram}
|
||||||
|
showHybridButtonForAudio={showHybridButtonForAudio}
|
||||||
|
showHybridButtonForMidi={showHybridButtonForMidi}
|
||||||
|
onOpenHybrid={handleOpenHybrid}
|
||||||
onExternalDropComplete={handleExternalDropComplete}
|
onExternalDropComplete={handleExternalDropComplete}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -855,26 +873,33 @@ const MainContent: React.FC<MainContentProps> = ({
|
|||||||
onClose={handlePianoRollClose}
|
onClose={handlePianoRollClose}
|
||||||
regionId={activeRegionId}
|
regionId={activeRegionId}
|
||||||
mode={pianoRollMode}
|
mode={pianoRollMode}
|
||||||
audioRegion={pianoRollMode === 'spectrogram' && activeRegionId
|
audioRegion={(() => {
|
||||||
? (() => {
|
// spectrogram mode: audio region IS the activeRegionId
|
||||||
for (const track of tracks) {
|
// hybrid mode: audio region is hybridAudioRegionId
|
||||||
const region = track.getRegions().find(r => r.getId() === activeRegionId);
|
const audioId = pianoRollMode === 'spectrogram' ? activeRegionId
|
||||||
if (region && region.getCurrentType() === 'KGAudioRegion') {
|
: pianoRollMode === 'hybrid' ? hybridAudioRegionId
|
||||||
return region as unknown as KGAudioRegion;
|
: null;
|
||||||
}
|
if (!audioId) return undefined;
|
||||||
}
|
for (const track of tracks) {
|
||||||
return undefined;
|
const region = track.getRegions().find(r => r.getId() === audioId);
|
||||||
})()
|
if (region && region.getCurrentType() === 'KGAudioRegion') {
|
||||||
: undefined}
|
return region as unknown as KGAudioRegion;
|
||||||
trackId={pianoRollMode === 'spectrogram' && activeRegionId
|
}
|
||||||
? (() => {
|
}
|
||||||
for (const track of tracks) {
|
return undefined;
|
||||||
const region = track.getRegions().find(r => r.getId() === activeRegionId);
|
})()}
|
||||||
if (region) return track.getId().toString();
|
trackId={(() => {
|
||||||
}
|
const audioId = pianoRollMode === 'spectrogram' ? activeRegionId
|
||||||
return undefined;
|
: pianoRollMode === 'hybrid' ? hybridAudioRegionId
|
||||||
})()
|
: null;
|
||||||
: undefined}
|
if (!audioId) return undefined;
|
||||||
|
for (const track of tracks) {
|
||||||
|
if (track.getRegions().some(r => r.getId() === audioId)) {
|
||||||
|
return track.getId().toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
})()}
|
||||||
projectName={savedProjectName}
|
projectName={savedProjectName}
|
||||||
/>,
|
/>,
|
||||||
document.body
|
document.body
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ interface PianoGridProps {
|
|||||||
bpm?: number;
|
bpm?: number;
|
||||||
spectrogramThresholdDb?: number;
|
spectrogramThresholdDb?: number;
|
||||||
spectrogramPower?: number;
|
spectrogramPower?: number;
|
||||||
|
mode?: 'midi-edit' | 'spectrogram' | 'hybrid';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CursorPosition {
|
interface CursorPosition {
|
||||||
|
|||||||
@@ -77,10 +77,17 @@
|
|||||||
/* Ensure toolbar and its dropdowns appear above piano roll content */
|
/* Ensure toolbar and its dropdowns appear above piano roll content */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.piano-roll-toolbar .tool-button {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Override pointer-events for piano roll toolbar sections */
|
/* Override pointer-events for piano roll toolbar sections */
|
||||||
.piano-roll-toolbar .toolbar-left,
|
.piano-roll-toolbar .toolbar-left,
|
||||||
.piano-roll-toolbar .toolbar-right {
|
.piano-roll-toolbar .toolbar-right {
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
/* max-width: 70%; */
|
||||||
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.piano-roll-toolbar .quant-button {
|
.piano-roll-toolbar .quant-button {
|
||||||
@@ -101,6 +108,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
margin-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spectrogram-control-label {
|
.spectrogram-control-label {
|
||||||
@@ -121,7 +129,7 @@
|
|||||||
.spectrogram-threshold-value {
|
.spectrogram-threshold-value {
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
min-width: 44px;
|
min-width: 30px;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -131,7 +139,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
text-transform: uppercase;
|
/* text-transform: uppercase; */
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ interface PianoRollProps {
|
|||||||
regionId: string | null;
|
regionId: string | null;
|
||||||
initialPosition?: { x: number; y: number };
|
initialPosition?: { x: number; y: number };
|
||||||
initialSize?: { width: number; height: number };
|
initialSize?: { width: number; height: number };
|
||||||
mode?: 'midi-edit' | 'spectrogram';
|
mode?: 'midi-edit' | 'spectrogram' | 'hybrid';
|
||||||
audioRegion?: KGAudioRegion;
|
audioRegion?: KGAudioRegion;
|
||||||
trackId?: string;
|
trackId?: string;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
@@ -40,6 +40,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
|||||||
projectName,
|
projectName,
|
||||||
}) => {
|
}) => {
|
||||||
const isSpectrogram = mode === 'spectrogram';
|
const isSpectrogram = mode === 'spectrogram';
|
||||||
|
const isHybrid = mode === 'hybrid';
|
||||||
const { maxBars, tracks, updateTrack, timeSignature, showChatBox, showInstrumentSelection, keySignature, selectedMode, setSelectedMode, playheadPosition, isPlaying, autoScrollEnabled, bpm } = useProjectStore();
|
const { maxBars, tracks, updateTrack, timeSignature, showChatBox, showInstrumentSelection, keySignature, selectedMode, setSelectedMode, playheadPosition, isPlaying, autoScrollEnabled, bpm } = useProjectStore();
|
||||||
|
|
||||||
// Tool state for piano roll
|
// Tool state for piano roll
|
||||||
@@ -870,6 +871,11 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
|||||||
// Get the title for the piano roll based on the active region
|
// Get the title for the piano roll based on the active region
|
||||||
const getPianoRollTitle = () => {
|
const getPianoRollTitle = () => {
|
||||||
if (isSpectrogram) return audioRegion ? `SPECTROGRAM — ${audioRegion.getName()}` : 'SPECTROGRAM';
|
if (isSpectrogram) return audioRegion ? `SPECTROGRAM — ${audioRegion.getName()}` : 'SPECTROGRAM';
|
||||||
|
if (isHybrid) {
|
||||||
|
const midiName = activeRegion?.getName() ?? 'MIDI';
|
||||||
|
const audioName = audioRegion?.getName() ?? 'Audio';
|
||||||
|
return `${midiName} + ${audioName}`;
|
||||||
|
}
|
||||||
if (!activeRegion) return "EDIT NOTE CLIP";
|
if (!activeRegion) return "EDIT NOTE CLIP";
|
||||||
|
|
||||||
// Calculate the bar and beat position of the region
|
// Calculate the bar and beat position of the region
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ interface PianoRollContentProps {
|
|||||||
selectedMode: string;
|
selectedMode: string;
|
||||||
keySignature: KeySignature;
|
keySignature: KeySignature;
|
||||||
chordGuide: string;
|
chordGuide: string;
|
||||||
mode?: 'midi-edit' | 'spectrogram';
|
mode?: 'midi-edit' | 'spectrogram' | 'hybrid';
|
||||||
audioRegion?: KGAudioRegion;
|
audioRegion?: KGAudioRegion;
|
||||||
trackId?: string;
|
trackId?: string;
|
||||||
projectName?: string;
|
projectName?: string;
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ interface PianoRollToolbarProps {
|
|||||||
chordGuide: string;
|
chordGuide: string;
|
||||||
onChordGuideChange: (value: string) => void;
|
onChordGuideChange: (value: string) => void;
|
||||||
blinkButton?: string | null;
|
blinkButton?: string | null;
|
||||||
mode?: 'midi-edit' | 'spectrogram';
|
mode?: 'midi-edit' | 'spectrogram' | 'hybrid';
|
||||||
thresholdDb?: number;
|
thresholdDb?: number;
|
||||||
onThresholdChange?: (db: number) => void;
|
onThresholdChange?: (db: number) => void;
|
||||||
power?: number;
|
power?: number;
|
||||||
@@ -51,11 +51,27 @@ const PianoRollToolbar: React.FC<PianoRollToolbarProps> = ({
|
|||||||
onPowerChange,
|
onPowerChange,
|
||||||
}) => {
|
}) => {
|
||||||
const isSpectrogram = mode === 'spectrogram';
|
const isSpectrogram = mode === 'spectrogram';
|
||||||
|
const showMidiControls = mode !== 'spectrogram'; // midi-edit and hybrid
|
||||||
|
const showSpecControls = mode === 'spectrogram' || mode === 'hybrid';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="piano-roll-toolbar">
|
<div className="piano-roll-toolbar">
|
||||||
{!isSpectrogram && (
|
{showMidiControls && (
|
||||||
<div className="toolbar-left">
|
<div className="toolbar-left">
|
||||||
|
<button
|
||||||
|
className={`tool-button ${activeTool === 'pointer' ? 'active' : ''}`}
|
||||||
|
onClick={() => onToolSelect('pointer')}
|
||||||
|
title="Pointer Tool"
|
||||||
|
>
|
||||||
|
<FaMousePointer />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`tool-button ${activeTool === 'pencil' ? 'active' : ''}`}
|
||||||
|
onClick={() => onToolSelect('pencil')}
|
||||||
|
title="Pencil Tool"
|
||||||
|
>
|
||||||
|
<FaPencilAlt />
|
||||||
|
</button>
|
||||||
<KGDropdown
|
<KGDropdown
|
||||||
options={Object.entries(KGCore.FUNCTIONAL_CHORDS_DATA).map(([id, data]) => ({ label: data.name, value: id }))}
|
options={Object.entries(KGCore.FUNCTIONAL_CHORDS_DATA).map(([id, data]) => ({ label: data.name, value: id }))}
|
||||||
value={selectedMode}
|
value={selectedMode}
|
||||||
@@ -80,27 +96,8 @@ const PianoRollToolbar: React.FC<PianoRollToolbarProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isSpectrogram && (
|
|
||||||
<div className="toolbar-center">
|
|
||||||
<button
|
|
||||||
className={`tool-button ${activeTool === 'pointer' ? 'active' : ''}`}
|
|
||||||
onClick={() => onToolSelect('pointer')}
|
|
||||||
title="Pointer Tool"
|
|
||||||
>
|
|
||||||
<FaMousePointer />
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
className={`tool-button ${activeTool === 'pencil' ? 'active' : ''}`}
|
|
||||||
onClick={() => onToolSelect('pencil')}
|
|
||||||
title="Pencil Tool"
|
|
||||||
>
|
|
||||||
<FaPencilAlt />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="toolbar-right">
|
<div className="toolbar-right">
|
||||||
{!isSpectrogram && (
|
{showMidiControls && (
|
||||||
<>
|
<>
|
||||||
<KGDropdown
|
<KGDropdown
|
||||||
options={KGPianoRollState.SNAP_OPTIONS}
|
options={KGPianoRollState.SNAP_OPTIONS}
|
||||||
@@ -127,7 +124,7 @@ const PianoRollToolbar: React.FC<PianoRollToolbarProps> = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isSpectrogram && (
|
{showSpecControls && (
|
||||||
<div className="spectrogram-toolbar-controls">
|
<div className="spectrogram-toolbar-controls">
|
||||||
<span className="spectrogram-control-label">Floor</span>
|
<span className="spectrogram-control-label">Floor</span>
|
||||||
<input
|
<input
|
||||||
|
|||||||
@@ -140,6 +140,27 @@
|
|||||||
background: rgba(0, 0, 0, 0.35);
|
background: rgba(0, 0, 0, 0.35);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.region-hybrid-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
left: 24px;
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
color: #fff;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 2px;
|
||||||
|
margin: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-hybrid-btn:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
/* Instrument dropdown specific styles */
|
/* Instrument dropdown specific styles */
|
||||||
.instrument-dropdown .quant-dropdown {
|
.instrument-dropdown .quant-dropdown {
|
||||||
min-width: 80px;
|
min-width: 80px;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import './Region.css';
|
import './Region.css';
|
||||||
import { FaPencilAlt } from 'react-icons/fa';
|
import { FaPencilAlt, FaPlus } from 'react-icons/fa';
|
||||||
import { MdGraphicEq } from 'react-icons/md';
|
import { MdGraphicEq } from 'react-icons/md';
|
||||||
import type { ResizeAction } from '../interfaces';
|
import type { ResizeAction } from '../interfaces';
|
||||||
import { REGION_CONSTANTS, DEBUG_MODE } from '../../constants';
|
import { REGION_CONSTANTS, DEBUG_MODE } from '../../constants';
|
||||||
@@ -30,6 +30,9 @@ interface RegionItemProps {
|
|||||||
onOpenPianoRoll?: (regionId: string) => void;
|
onOpenPianoRoll?: (regionId: string) => void;
|
||||||
// Open spectrogram viewer for audio regions
|
// Open spectrogram viewer for audio regions
|
||||||
onOpenSpectrogram?: (regionId: string) => void;
|
onOpenSpectrogram?: (regionId: string) => void;
|
||||||
|
// Enter hybrid mode (show + when piano roll is open with the opposite region type selected)
|
||||||
|
showHybridButton?: boolean;
|
||||||
|
onOpenHybrid?: (regionId: string) => void;
|
||||||
// MIDI region data for rendering notes
|
// MIDI region data for rendering notes
|
||||||
midiRegion?: KGMidiRegion;
|
midiRegion?: KGMidiRegion;
|
||||||
// Audio region data for rendering waveform
|
// Audio region data for rendering waveform
|
||||||
@@ -53,6 +56,8 @@ const RegionItem: React.FC<RegionItemProps> = ({
|
|||||||
onClick,
|
onClick,
|
||||||
onOpenPianoRoll,
|
onOpenPianoRoll,
|
||||||
onOpenSpectrogram,
|
onOpenSpectrogram,
|
||||||
|
showHybridButton,
|
||||||
|
onOpenHybrid,
|
||||||
midiRegion,
|
midiRegion,
|
||||||
audioRegion,
|
audioRegion,
|
||||||
audioBuffer
|
audioBuffer
|
||||||
@@ -64,7 +69,7 @@ const RegionItem: React.FC<RegionItemProps> = ({
|
|||||||
const [resizeEdge, setResizeEdge] = useState<ResizeAction>('none');
|
const [resizeEdge, setResizeEdge] = useState<ResizeAction>('none');
|
||||||
const [isResizing, setIsResizing] = useState(false);
|
const [isResizing, setIsResizing] = useState(false);
|
||||||
const [isDragging, setIsDragging] = useState(false);
|
const [isDragging, setIsDragging] = useState(false);
|
||||||
const initialMousePosRef = useRef<{x: number, y: number}>({x: 0, y: 0});
|
const initialMousePosRef = useRef<{ x: number, y: number }>({ x: 0, y: 0 });
|
||||||
// Use refs to track states for immediate access
|
// Use refs to track states for immediate access
|
||||||
const isResizingRef = useRef<boolean>(false);
|
const isResizingRef = useRef<boolean>(false);
|
||||||
const isDraggingRef = useRef<boolean>(false);
|
const isDraggingRef = useRef<boolean>(false);
|
||||||
@@ -600,6 +605,26 @@ const RegionItem: React.FC<RegionItemProps> = ({
|
|||||||
<MdGraphicEq size={10} />
|
<MdGraphicEq size={10} />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{showHybridButton && (
|
||||||
|
<button
|
||||||
|
className="region-hybrid-btn"
|
||||||
|
title="Open in hybrid mode"
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
if (onOpenHybrid) {
|
||||||
|
onOpenHybrid(id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
aria-label="Open hybrid mode"
|
||||||
|
>
|
||||||
|
<FaPlus size={10} />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
<canvas ref={canvasRef} />
|
<canvas ref={canvasRef} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ interface TrackGridItemProps {
|
|||||||
onRegionClick?: (regionId: string) => void;
|
onRegionClick?: (regionId: string) => void;
|
||||||
onOpenPianoRoll?: (regionId: string) => void;
|
onOpenPianoRoll?: (regionId: string) => void;
|
||||||
onOpenSpectrogram?: (regionId: string) => void;
|
onOpenSpectrogram?: (regionId: string) => void;
|
||||||
|
showHybridButtonForAudio?: boolean;
|
||||||
|
showHybridButtonForMidi?: boolean;
|
||||||
|
onOpenHybrid?: (regionId: string) => void;
|
||||||
allTracks?: KGTrack[]; // Added to access all tracks for drag operations
|
allTracks?: KGTrack[]; // Added to access all tracks for drag operations
|
||||||
onKGOneClipDrop?: (e: React.DragEvent<HTMLDivElement>, trackIndex: number) => void;
|
onKGOneClipDrop?: (e: React.DragEvent<HTMLDivElement>, trackIndex: number) => void;
|
||||||
}
|
}
|
||||||
@@ -49,6 +52,9 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
|
|||||||
onRegionClick,
|
onRegionClick,
|
||||||
onOpenPianoRoll,
|
onOpenPianoRoll,
|
||||||
onOpenSpectrogram,
|
onOpenSpectrogram,
|
||||||
|
showHybridButtonForAudio,
|
||||||
|
showHybridButtonForMidi,
|
||||||
|
onOpenHybrid,
|
||||||
allTracks,
|
allTracks,
|
||||||
onKGOneClipDrop,
|
onKGOneClipDrop,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -571,6 +577,8 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
|
|||||||
onOpenSpectrogram={audioRegion ? (regionId) => {
|
onOpenSpectrogram={audioRegion ? (regionId) => {
|
||||||
onOpenSpectrogram?.(regionId);
|
onOpenSpectrogram?.(regionId);
|
||||||
} : undefined}
|
} : undefined}
|
||||||
|
showHybridButton={audioRegion ? showHybridButtonForAudio : showHybridButtonForMidi}
|
||||||
|
onOpenHybrid={onOpenHybrid}
|
||||||
midiRegion={midiRegion}
|
midiRegion={midiRegion}
|
||||||
audioRegion={audioRegion}
|
audioRegion={audioRegion}
|
||||||
audioBuffer={audioBuffer}
|
audioBuffer={audioBuffer}
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ interface TrackGridPanelProps {
|
|||||||
onRegionClick?: (regionId: string) => void;
|
onRegionClick?: (regionId: string) => void;
|
||||||
onOpenPianoRoll?: (regionId: string) => void;
|
onOpenPianoRoll?: (regionId: string) => void;
|
||||||
onOpenSpectrogram?: (regionId: string) => void;
|
onOpenSpectrogram?: (regionId: string) => void;
|
||||||
|
showHybridButtonForAudio?: boolean;
|
||||||
|
showHybridButtonForMidi?: boolean;
|
||||||
|
onOpenHybrid?: (regionId: string) => void;
|
||||||
onExternalDropComplete?: (trackIndex: number, regionUI: RegionUI) => void;
|
onExternalDropComplete?: (trackIndex: number, regionUI: RegionUI) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +51,9 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
|
|||||||
onRegionClick,
|
onRegionClick,
|
||||||
onOpenPianoRoll,
|
onOpenPianoRoll,
|
||||||
onOpenSpectrogram,
|
onOpenSpectrogram,
|
||||||
|
showHybridButtonForAudio,
|
||||||
|
showHybridButtonForMidi,
|
||||||
|
onOpenHybrid,
|
||||||
onExternalDropComplete,
|
onExternalDropComplete,
|
||||||
}) => {
|
}) => {
|
||||||
const gridContainerRef = useRef<HTMLDivElement>(null);
|
const gridContainerRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -654,6 +660,9 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
|
|||||||
onRegionClick={handleRegionClick}
|
onRegionClick={handleRegionClick}
|
||||||
onOpenPianoRoll={onOpenPianoRoll}
|
onOpenPianoRoll={onOpenPianoRoll}
|
||||||
onOpenSpectrogram={onOpenSpectrogram}
|
onOpenSpectrogram={onOpenSpectrogram}
|
||||||
|
showHybridButtonForAudio={showHybridButtonForAudio}
|
||||||
|
showHybridButtonForMidi={showHybridButtonForMidi}
|
||||||
|
onOpenHybrid={onOpenHybrid}
|
||||||
allTracks={tracks}
|
allTracks={tracks}
|
||||||
onKGOneClipDrop={handleExternalDrop}
|
onKGOneClipDrop={handleExternalDrop}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ interface ProjectState {
|
|||||||
// Piano roll state
|
// Piano roll state
|
||||||
showPianoRoll: boolean;
|
showPianoRoll: boolean;
|
||||||
activeRegionId: string | null;
|
activeRegionId: string | null;
|
||||||
pianoRollMode: 'midi-edit' | 'spectrogram';
|
pianoRollMode: 'midi-edit' | 'spectrogram' | 'hybrid';
|
||||||
|
hybridAudioRegionId: string | null;
|
||||||
|
|
||||||
// ChatBox state
|
// ChatBox state
|
||||||
showChatBox: boolean;
|
showChatBox: boolean;
|
||||||
@@ -149,6 +150,7 @@ interface ProjectState {
|
|||||||
setActiveRegionId: (regionId: string | null) => void;
|
setActiveRegionId: (regionId: string | null) => void;
|
||||||
openMidiPianoRoll: (regionId: string) => void;
|
openMidiPianoRoll: (regionId: string) => void;
|
||||||
openSpectrogramViewer: (regionId: string) => void;
|
openSpectrogramViewer: (regionId: string) => void;
|
||||||
|
openHybridMode: (midiRegionId: string, audioRegionId: string) => void;
|
||||||
|
|
||||||
// Project state cleanup
|
// Project state cleanup
|
||||||
cleanupProjectState: () => void;
|
cleanupProjectState: () => void;
|
||||||
@@ -308,6 +310,7 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
|||||||
showPianoRoll: false,
|
showPianoRoll: false,
|
||||||
activeRegionId: null,
|
activeRegionId: null,
|
||||||
pianoRollMode: 'midi-edit' as const,
|
pianoRollMode: 'midi-edit' as const,
|
||||||
|
hybridAudioRegionId: null,
|
||||||
|
|
||||||
// Initial ChatBox state
|
// Initial ChatBox state
|
||||||
showChatBox: initialChatBoxState,
|
showChatBox: initialChatBoxState,
|
||||||
@@ -1038,20 +1041,24 @@ export const useProjectStore = create<ProjectState>((set, get) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
openMidiPianoRoll: (regionId: string) => {
|
openMidiPianoRoll: (regionId: string) => {
|
||||||
set({ showPianoRoll: true, activeRegionId: regionId, pianoRollMode: 'midi-edit' });
|
set({ showPianoRoll: true, activeRegionId: regionId, pianoRollMode: 'midi-edit', hybridAudioRegionId: null });
|
||||||
},
|
},
|
||||||
|
|
||||||
openSpectrogramViewer: (regionId: string) => {
|
openSpectrogramViewer: (regionId: string) => {
|
||||||
set({ showPianoRoll: true, activeRegionId: regionId, pianoRollMode: 'spectrogram' });
|
set({ showPianoRoll: true, activeRegionId: regionId, pianoRollMode: 'spectrogram', hybridAudioRegionId: null });
|
||||||
|
},
|
||||||
|
|
||||||
|
openHybridMode: (midiRegionId: string, audioRegionId: string) => {
|
||||||
|
set({ showPianoRoll: true, activeRegionId: midiRegionId, hybridAudioRegionId: audioRegionId, pianoRollMode: 'hybrid' });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Project state cleanup - used when starting new/loading projects
|
// Project state cleanup - used when starting new/loading projects
|
||||||
cleanupProjectState: () => {
|
cleanupProjectState: () => {
|
||||||
// Close piano roll if it's visible
|
// Close piano roll if it's visible
|
||||||
set({ showPianoRoll: false });
|
set({ showPianoRoll: false });
|
||||||
|
|
||||||
// Clear active region
|
// Clear active region and hybrid state
|
||||||
set({ activeRegionId: null });
|
set({ activeRegionId: null, hybridAudioRegionId: null, pianoRollMode: 'midi-edit' });
|
||||||
|
|
||||||
// Clear any selected items
|
// Clear any selected items
|
||||||
KGCore.instance().clearSelectedItems();
|
KGCore.instance().clearSelectedItems();
|
||||||
|
|||||||
Reference in New Issue
Block a user