fix: few minor UI adjustments and fixes
This commit is contained in:
@@ -106,8 +106,13 @@
|
|||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
border: 1px solid #444;
|
border: 1px solid #444;
|
||||||
border-radius: 3px;
|
border-right: 0;
|
||||||
background-color: #3a3a3a;
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
border-bottom-left-radius: 3px;
|
||||||
|
background-color: #2f2f2f;
|
||||||
|
box-sizing: border-box;
|
||||||
color: #e0e0e0;
|
color: #e0e0e0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -120,8 +125,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.list-event-add-button:hover {
|
.list-event-add-button:hover {
|
||||||
background-color: #464646;
|
background-color: #3b3b3b;
|
||||||
border-color: #5a5a5a;
|
border-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-event-dropdown-button,
|
.list-event-dropdown-button,
|
||||||
@@ -142,6 +147,8 @@
|
|||||||
.list-event-type-button {
|
.list-event-type-button {
|
||||||
min-width: 88px;
|
min-width: 88px;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-event-delete-button {
|
.list-event-delete-button {
|
||||||
|
|||||||
@@ -219,6 +219,21 @@
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.piano-roll-toolbar .automation-toggle-button.active {
|
||||||
|
border-color: #e0e0e0;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piano-roll-toolbar .automation-toggle-button.active:hover {
|
||||||
|
border-color: #f0f0f0;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piano-roll-toolbar .automation-toggle-button.active:active {
|
||||||
|
border-color: #d6d6d6;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.piano-roll-toolbar .automation-type-dropdown {
|
.piano-roll-toolbar .automation-type-dropdown {
|
||||||
min-width: 92px;
|
min-width: 92px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
@@ -318,6 +333,26 @@
|
|||||||
overflow-x: visible;
|
overflow-x: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.piano-roll-body-shell {
|
||||||
|
display: flex;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piano-roll-keys-viewport {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 15;
|
||||||
|
width: var(--region-piano-key-width);
|
||||||
|
flex: 0 0 var(--region-piano-key-width);
|
||||||
|
overflow: hidden;
|
||||||
|
border-right: 1px solid #3a3a3a;
|
||||||
|
background-color: #252525;
|
||||||
|
}
|
||||||
|
|
||||||
|
.piano-roll-keys-offset {
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
.piano-roll-automation-section {
|
.piano-roll-automation-section {
|
||||||
flex: 0 0 50%;
|
flex: 0 0 50%;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -374,24 +409,19 @@
|
|||||||
.piano-roll-body {
|
.piano-roll-body {
|
||||||
display: flex;
|
display: flex;
|
||||||
min-height: calc(8 * 12 * var(--region-piano-key-height));
|
min-height: calc(8 * 12 * var(--region-piano-key-height));
|
||||||
/* 8 octaves * 12 notes * piano key height */
|
width: calc(var(--max-number-of-bars) * var(--region-grid-bar-width));
|
||||||
width: calc(var(--max-number-of-bars) * var(--region-grid-bar-width) + var(--region-piano-key-width));
|
|
||||||
/* 32 bars * 160px width + piano keys width */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.piano-keys-container {
|
.piano-keys-container {
|
||||||
width: var(--region-piano-key-width);
|
width: var(--region-piano-key-width);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-right: 1px solid #3a3a3a;
|
|
||||||
background-color: #252525;
|
background-color: #252525;
|
||||||
position: sticky;
|
|
||||||
left: 0;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.piano-grid-container {
|
.piano-grid-container {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
min-width: calc(var(--max-number-of-bars) * var(--region-grid-bar-width));
|
min-width: calc(var(--max-number-of-bars) * var(--region-grid-bar-width));
|
||||||
min-height: calc(8 * 12 * var(--region-piano-key-height));
|
min-height: calc(8 * 12 * var(--region-piano-key-height));
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
|||||||
@@ -205,6 +205,8 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
|||||||
// Sync tool state
|
// Sync tool state
|
||||||
const currentTool = pianoRollState.getActiveTool() as 'pointer' | 'pencil';
|
const currentTool = pianoRollState.getActiveTool() as 'pointer' | 'pencil';
|
||||||
setActiveTool(currentTool);
|
setActiveTool(currentTool);
|
||||||
|
setAutomationEnabled(pianoRollState.getAutomationViewEnabled());
|
||||||
|
setAutomationType(pianoRollState.getCurrentAutomationType() as PianoRollAutomationType);
|
||||||
|
|
||||||
if (DEBUG_MODE.PIANO_ROLL) {
|
if (DEBUG_MODE.PIANO_ROLL) {
|
||||||
console.log(`Synced piano roll state on mount - snap: ${currentSnap}, tool: ${currentTool}`);
|
console.log(`Synced piano roll state on mount - snap: ${currentSnap}, tool: ${currentTool}`);
|
||||||
@@ -691,11 +693,16 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
|||||||
}, [pianoRollZoom]);
|
}, [pianoRollZoom]);
|
||||||
|
|
||||||
const handleAutomationToggle = useCallback(() => {
|
const handleAutomationToggle = useCallback(() => {
|
||||||
setAutomationEnabled(current => !current);
|
setAutomationEnabled(current => {
|
||||||
|
const next = !current;
|
||||||
|
KGPianoRollState.instance().setAutomationViewEnabled(next);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleAutomationTypeChange = useCallback((value: PianoRollAutomationType) => {
|
const handleAutomationTypeChange = useCallback((value: PianoRollAutomationType) => {
|
||||||
setAutomationType(value);
|
setAutomationType(value);
|
||||||
|
KGPianoRollState.instance().setCurrentAutomationType(value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Calculate C4 position and scroll to it when piano roll opens
|
// Calculate C4 position and scroll to it when piano roll opens
|
||||||
@@ -745,7 +752,9 @@ const PianoRoll: React.FC<PianoRollProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
container.addEventListener('scroll', handleScroll);
|
container.addEventListener('scroll', handleScroll);
|
||||||
return () => container.removeEventListener('scroll', handleScroll);
|
return () => {
|
||||||
|
container.removeEventListener('scroll', handleScroll);
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Auto-scroll to keep playhead centered during playback
|
// Auto-scroll to keep playhead centered during playback
|
||||||
|
|||||||
@@ -74,4 +74,30 @@ describe('PianoRollAutomationLane', () => {
|
|||||||
|
|
||||||
expect(screen.getByText('No CC64 events in this region')).toBeInTheDocument();
|
expect(screen.getByText('No CC64 events in this region')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders step-style hold segments for non-interpolatable automation', () => {
|
||||||
|
const controllerEventsByType: KGMidiControllerEvent[][] = Array.from({ length: 128 }, () => []);
|
||||||
|
controllerEventsByType[64] = [
|
||||||
|
createMockMidiControllerEvent({ id: 'cc64-1', beat: 0.5, value: 127 }),
|
||||||
|
createMockMidiControllerEvent({ id: 'cc64-2', beat: 2, value: 0 }),
|
||||||
|
];
|
||||||
|
|
||||||
|
const region = createMockMidiRegion({
|
||||||
|
startFromBeat: 0,
|
||||||
|
controllerEventsByType,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { container } = render(
|
||||||
|
<PianoRollAutomationLane
|
||||||
|
activeRegion={region}
|
||||||
|
automationType="cc-64"
|
||||||
|
maxBars={8}
|
||||||
|
timeSignature={{ numerator: 4, denominator: 4 }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.querySelector('.piano-roll-automation-line')).not.toBeNull();
|
||||||
|
expect(container.querySelector('polyline.piano-roll-automation-line')).toBeNull();
|
||||||
|
expect(container.querySelectorAll('line.piano-roll-automation-line')).toHaveLength(2);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
|
import { KGMidiRegion } from '../../core/region/KGMidiRegion';
|
||||||
import {
|
import {
|
||||||
MIDI_PITCH_BEND_MAX,
|
MIDI_PITCH_BEND_MAX,
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
midiPitchBendToSignedValue,
|
midiPitchBendToSignedValue,
|
||||||
} from '../../util/midiUtil';
|
} from '../../util/midiUtil';
|
||||||
import {
|
import {
|
||||||
|
getAutomationInterpolationMode,
|
||||||
getControllerNumberForAutomationType,
|
getControllerNumberForAutomationType,
|
||||||
PIANO_ROLL_AUTOMATION_OPTIONS,
|
PIANO_ROLL_AUTOMATION_OPTIONS,
|
||||||
type PianoRollAutomationType,
|
type PianoRollAutomationType,
|
||||||
@@ -28,8 +29,8 @@ interface PianoRollAutomationLaneProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const AUTOMATION_COLOR = '#87CEFA';
|
const AUTOMATION_COLOR = '#87CEFA';
|
||||||
const LANE_HEIGHT = 160;
|
|
||||||
const LANE_PADDING_Y = 16;
|
const LANE_PADDING_Y = 16;
|
||||||
|
const MIN_LANE_HEIGHT = 160;
|
||||||
|
|
||||||
const PianoRollAutomationLane: React.FC<PianoRollAutomationLaneProps> = ({
|
const PianoRollAutomationLane: React.FC<PianoRollAutomationLaneProps> = ({
|
||||||
activeRegion,
|
activeRegion,
|
||||||
@@ -38,8 +39,33 @@ const PianoRollAutomationLane: React.FC<PianoRollAutomationLaneProps> = ({
|
|||||||
timeSignature,
|
timeSignature,
|
||||||
redrawVersion = 0,
|
redrawVersion = 0,
|
||||||
}) => {
|
}) => {
|
||||||
|
const laneRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [laneHeight, setLaneHeight] = useState(MIN_LANE_HEIGHT);
|
||||||
const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
|
const beatWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-grid-beat-width')) || 40;
|
||||||
const keyWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-piano-key-width')) || 60;
|
const keyWidth = parseInt(getComputedStyle(document.documentElement).getPropertyValue('--region-piano-key-width')) || 60;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const element = laneRef.current;
|
||||||
|
if (!element) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateHeight = () => {
|
||||||
|
setLaneHeight(Math.max(Math.round(element.clientHeight), MIN_LANE_HEIGHT));
|
||||||
|
};
|
||||||
|
|
||||||
|
updateHeight();
|
||||||
|
|
||||||
|
const observer = new ResizeObserver(() => {
|
||||||
|
updateHeight();
|
||||||
|
});
|
||||||
|
observer.observe(element);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
const points = useMemo<AutomationPoint[]>(() => {
|
const points = useMemo<AutomationPoint[]>(() => {
|
||||||
if (!activeRegion) {
|
if (!activeRegion) {
|
||||||
return [];
|
return [];
|
||||||
@@ -70,15 +96,16 @@ const PianoRollAutomationLane: React.FC<PianoRollAutomationLaneProps> = ({
|
|||||||
|
|
||||||
const selectedOption = PIANO_ROLL_AUTOMATION_OPTIONS.find(option => option.value === automationType);
|
const selectedOption = PIANO_ROLL_AUTOMATION_OPTIONS.find(option => option.value === automationType);
|
||||||
const laneLabel = selectedOption?.label ?? automationType;
|
const laneLabel = selectedOption?.label ?? automationType;
|
||||||
|
const interpolationMode = getAutomationInterpolationMode(automationType);
|
||||||
const totalBeats = maxBars * timeSignature.numerator;
|
const totalBeats = maxBars * timeSignature.numerator;
|
||||||
const totalWidth = 'calc(var(--max-number-of-bars) * var(--region-grid-bar-width) + var(--region-piano-key-width))';
|
const totalWidth = 'calc(var(--max-number-of-bars) * var(--region-grid-bar-width) + var(--region-piano-key-width))';
|
||||||
|
|
||||||
const toY = (value: number): number => {
|
const toY = (value: number): number => {
|
||||||
const minValue = automationType === 'pitch-bend' ? MIDI_PITCH_BEND_MIN : 0;
|
const minValue = automationType === 'pitch-bend' ? MIDI_PITCH_BEND_MIN : 0;
|
||||||
const maxValue = automationType === 'pitch-bend' ? MIDI_PITCH_BEND_MAX : 127;
|
const maxValue = automationType === 'pitch-bend' ? MIDI_PITCH_BEND_MAX : 127;
|
||||||
const usableHeight = LANE_HEIGHT - LANE_PADDING_Y * 2;
|
const usableHeight = laneHeight - LANE_PADDING_Y * 2;
|
||||||
const normalized = (value - minValue) / (maxValue - minValue);
|
const normalized = (value - minValue) / (maxValue - minValue);
|
||||||
return LANE_HEIGHT - LANE_PADDING_Y - normalized * usableHeight;
|
return laneHeight - LANE_PADDING_Y - normalized * usableHeight;
|
||||||
};
|
};
|
||||||
|
|
||||||
const svgPoints = points.map(point => {
|
const svgPoints = points.map(point => {
|
||||||
@@ -90,6 +117,10 @@ const PianoRollAutomationLane: React.FC<PianoRollAutomationLaneProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const polylinePoints = (() => {
|
const polylinePoints = (() => {
|
||||||
|
if (interpolationMode === 'step') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
if (svgPoints.length === 0) {
|
if (svgPoints.length === 0) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -107,11 +138,25 @@ const PianoRollAutomationLane: React.FC<PianoRollAutomationLaneProps> = ({
|
|||||||
.join(' ');
|
.join(' ');
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const stepSegments = (() => {
|
||||||
|
if (interpolationMode !== 'step' || svgPoints.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return svgPoints.map((point, index) => ({
|
||||||
|
id: `${point.id}-step`,
|
||||||
|
x1: point.x,
|
||||||
|
y1: point.y,
|
||||||
|
x2: index < svgPoints.length - 1 ? svgPoints[index + 1].x : beatWidth * totalBeats + keyWidth,
|
||||||
|
}));
|
||||||
|
})();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="piano-roll-automation-lane"
|
className="piano-roll-automation-lane"
|
||||||
data-testid="piano-roll-automation-lane"
|
data-testid="piano-roll-automation-lane"
|
||||||
aria-label={`${laneLabel} automation lane`}
|
aria-label={`${laneLabel} automation lane`}
|
||||||
|
ref={laneRef}
|
||||||
>
|
>
|
||||||
<div className="piano-roll-automation-track" style={{ width: totalWidth }}>
|
<div className="piano-roll-automation-track" style={{ width: totalWidth }}>
|
||||||
<div className="piano-roll-automation-gutter" />
|
<div className="piano-roll-automation-gutter" />
|
||||||
@@ -120,11 +165,11 @@ const PianoRollAutomationLane: React.FC<PianoRollAutomationLaneProps> = ({
|
|||||||
<svg
|
<svg
|
||||||
className="piano-roll-automation-svg"
|
className="piano-roll-automation-svg"
|
||||||
width="100%"
|
width="100%"
|
||||||
height={LANE_HEIGHT}
|
height={laneHeight}
|
||||||
viewBox={`0 0 ${beatWidth * totalBeats + keyWidth} ${LANE_HEIGHT}`}
|
viewBox={`0 0 ${beatWidth * totalBeats + keyWidth} ${laneHeight}`}
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
>
|
>
|
||||||
{points.length > 0 && (
|
{interpolationMode === 'linear' && points.length > 0 && (
|
||||||
<polyline
|
<polyline
|
||||||
className="piano-roll-automation-line"
|
className="piano-roll-automation-line"
|
||||||
fill="none"
|
fill="none"
|
||||||
@@ -133,8 +178,20 @@ const PianoRollAutomationLane: React.FC<PianoRollAutomationLaneProps> = ({
|
|||||||
points={polylinePoints}
|
points={polylinePoints}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{interpolationMode === 'step' && stepSegments.map(segment => (
|
||||||
|
<line
|
||||||
|
key={segment.id}
|
||||||
|
className="piano-roll-automation-line"
|
||||||
|
x1={segment.x1}
|
||||||
|
y1={segment.y1}
|
||||||
|
x2={segment.x2}
|
||||||
|
y2={segment.y1}
|
||||||
|
stroke={AUTOMATION_COLOR}
|
||||||
|
strokeWidth="2"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
{svgPoints.map(point => {
|
{svgPoints.map(point => {
|
||||||
const labelY = Math.max(14, Math.min(LANE_HEIGHT - 6, point.y - 10));
|
const labelY = Math.max(14, Math.min(laneHeight - 6, point.y - 10));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<g key={point.id}>
|
<g key={point.id}>
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
|
|||||||
const isSpectrogram = mode === 'spectrogram';
|
const isSpectrogram = mode === 'spectrogram';
|
||||||
const showAutomationLane = automationEnabled && !isSpectrogram;
|
const showAutomationLane = automationEnabled && !isSpectrogram;
|
||||||
const [spectrogramLoading, setSpectrogramLoading] = useState(false);
|
const [spectrogramLoading, setSpectrogramLoading] = useState(false);
|
||||||
|
const [noteScrollTop, setNoteScrollTop] = useState(0);
|
||||||
const handleSpectrogramLoadingChange = useCallback((loading: boolean) => {
|
const handleSpectrogramLoadingChange = useCallback((loading: boolean) => {
|
||||||
setSpectrogramLoading(loading);
|
setSpectrogramLoading(loading);
|
||||||
}, []);
|
}, []);
|
||||||
@@ -291,10 +292,21 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
|
|||||||
<div className={`piano-roll-main-section ${showAutomationLane ? 'with-automation' : ''}`}>
|
<div className={`piano-roll-main-section ${showAutomationLane ? 'with-automation' : ''}`}>
|
||||||
<PianoGridHeader maxBars={maxBars} timeSignature={timeSignature} />
|
<PianoGridHeader maxBars={maxBars} timeSignature={timeSignature} />
|
||||||
|
|
||||||
<div className="piano-roll-note-scroll" ref={noteScrollRef}>
|
<div className="piano-roll-body-shell">
|
||||||
<div className="piano-roll-body">
|
<div className="piano-roll-keys-viewport">
|
||||||
<PianoKeys activeRegion={activeRegion} />
|
<div
|
||||||
|
className="piano-roll-keys-offset"
|
||||||
|
style={{ transform: `translateY(-${noteScrollTop}px)` }}
|
||||||
|
>
|
||||||
|
<PianoKeys activeRegion={activeRegion} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="piano-roll-note-scroll"
|
||||||
|
ref={noteScrollRef}
|
||||||
|
onScroll={(event) => setNoteScrollTop(event.currentTarget.scrollTop)}
|
||||||
|
>
|
||||||
|
<div className="piano-roll-body">
|
||||||
<PianoGrid
|
<PianoGrid
|
||||||
gridRef={pianoGridRef}
|
gridRef={pianoGridRef}
|
||||||
onDoubleClick={isSpectrogram ? () => {} : handleGridDoubleClick}
|
onDoubleClick={isSpectrogram ? () => {} : handleGridDoubleClick}
|
||||||
@@ -319,6 +331,7 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
|
|||||||
{memoizedNotes}
|
{memoizedNotes}
|
||||||
{!isSpectrogram && recordingNoteOverlays}
|
{!isSpectrogram && recordingNoteOverlays}
|
||||||
</PianoGrid>
|
</PianoGrid>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,15 +9,16 @@ export type PianoRollAutomationType =
|
|||||||
export interface PianoRollAutomationOption {
|
export interface PianoRollAutomationOption {
|
||||||
label: string;
|
label: string;
|
||||||
value: PianoRollAutomationType;
|
value: PianoRollAutomationType;
|
||||||
|
interpolationMode: 'linear' | 'step';
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PIANO_ROLL_AUTOMATION_OPTIONS: PianoRollAutomationOption[] = [
|
export const PIANO_ROLL_AUTOMATION_OPTIONS: PianoRollAutomationOption[] = [
|
||||||
{ label: 'Pitch Bend', value: 'pitch-bend' },
|
{ label: 'Pitch Bend', value: 'pitch-bend', interpolationMode: 'linear' },
|
||||||
{ label: 'CC1', value: 'cc-1' },
|
{ label: 'CC1', value: 'cc-1', interpolationMode: 'linear' },
|
||||||
{ label: 'CC2', value: 'cc-2' },
|
{ label: 'CC2', value: 'cc-2', interpolationMode: 'linear' },
|
||||||
{ label: 'CC7', value: 'cc-7' },
|
{ label: 'CC7', value: 'cc-7', interpolationMode: 'linear' },
|
||||||
{ label: 'CC11', value: 'cc-11' },
|
{ label: 'CC11', value: 'cc-11', interpolationMode: 'linear' },
|
||||||
{ label: 'CC64', value: 'cc-64' },
|
{ label: 'CC64', value: 'cc-64', interpolationMode: 'step' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getControllerNumberForAutomationType(type: PianoRollAutomationType): number | null {
|
export function getControllerNumberForAutomationType(type: PianoRollAutomationType): number | null {
|
||||||
@@ -37,3 +38,7 @@ export function getControllerNumberForAutomationType(type: PianoRollAutomationTy
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAutomationInterpolationMode(type: PianoRollAutomationType): 'linear' | 'step' {
|
||||||
|
return PIANO_ROLL_AUTOMATION_OPTIONS.find(option => option.value === type)?.interpolationMode ?? 'linear';
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export class KGPianoRollState {
|
|||||||
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 currentMode: string = "ionian"; // Default mode
|
||||||
|
private automationViewEnabled: boolean = false;
|
||||||
|
private currentAutomationType: string = "pitch-bend";
|
||||||
|
|
||||||
// Chord guide state
|
// Chord guide state
|
||||||
private currentSuitableChords: Record<string, string[]> = {}; // Map of chord symbols to note names (e.g., {"I": ["C", "E", "G"]})
|
private currentSuitableChords: Record<string, string[]> = {}; // Map of chord symbols to note names (e.g., {"I": ["C", "E", "G"]})
|
||||||
@@ -65,6 +67,22 @@ export class KGPianoRollState {
|
|||||||
this.currentMode = mode;
|
this.currentMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getAutomationViewEnabled(): boolean {
|
||||||
|
return this.automationViewEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setAutomationViewEnabled(enabled: boolean): void {
|
||||||
|
this.automationViewEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentAutomationType(): string {
|
||||||
|
return this.currentAutomationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setCurrentAutomationType(type: string): void {
|
||||||
|
this.currentAutomationType = type;
|
||||||
|
}
|
||||||
|
|
||||||
public getCurrentSuitableChords(): Record<string, string[]> {
|
public getCurrentSuitableChords(): Record<string, string[]> {
|
||||||
return this.currentSuitableChords;
|
return this.currentSuitableChords;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user