refactor: chord guide system

This commit is contained in:
Xiaohan-Tian
2026-05-28 22:52:58 -07:00
parent ad17263b82
commit 72297cb731
16 changed files with 783 additions and 48 deletions
+3 -2
View File
@@ -3,7 +3,7 @@ import type { MutableRefObject } from 'react';
import { Playhead } from '../common';
import SelectionBox from './SelectionBox';
import { isModifierKeyPressed } from '../../util/osUtil';
import { generatePianoGridBackground, getMatchingChordsForPitch } from '../../util/scaleUtil';
import { generatePianoGridBackground } from '../../util/scaleUtil';
import type { KeySignature } from '../../core/KGProject';
import { KGPianoRollState } from '../../core/state/KGPianoRollState';
import SpectrogramCanvas from './SpectrogramCanvas';
@@ -11,6 +11,7 @@ import AudioWaveformCanvas from './AudioWaveformCanvas';
import type { KGAudioRegion } from '../../core/region/KGAudioRegion';
import type { SpectrogramHeightResolution } from '../../util/spectrogramUtil';
import { getNextChordCandidateIndex } from './chordGuideUtil';
import { getMatchingChordGuideChordsForPitch } from '../../util/chordGuideDataUtil';
interface PianoGridProps {
gridRef: MutableRefObject<HTMLDivElement | null>;
@@ -167,7 +168,7 @@ const PianoGrid: React.FC<PianoGridProps> = ({
// Use the utility function to get matching chords
const functionType = chordGuide as 'T' | 'S' | 'D';
return getMatchingChordsForPitch(cursorPosition.pitch, chordGuideKeySignature, chordGuideMode, functionType);
return getMatchingChordGuideChordsForPitch(cursorPosition.pitch, chordGuideKeySignature, chordGuideMode, functionType);
}, [cursorPosition, chordGuide, chordGuideKeySignature, chordGuideMode]);
// Calculate chord highlights based on selected chord index
+6 -24
View File
@@ -19,9 +19,9 @@ import { beatsToBar } from '../../util/midiUtil';
import { ReplaceChordRegionsInRangeCommand, UpdateRegionCommand } from '../../core/commands';
import { KGAudioInterface } from '../../core/audio-interface/KGAudioInterface';
import { KGAudioFileStorage } from '../../core/io/KGAudioFileStorage';
import { getSuitableChords, noteNameToPitchClass } from '../../util/scaleUtil';
import { showAlert, showChordDetectionOptions, showMidiChordDetectionOptions, showTempoApply, showTempoDetectionOptions } from '../../util/dialogUtil';
import { matchesKeyboardShortcut } from '../../util/osUtil';
import { resolveChordGuideItems } from '../../util/chordGuideDataUtil';
import {
normalizeSpectrogramHeightResolution,
type SpectrogramHeightResolution,
@@ -739,7 +739,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
if (chordGuide === 'N') {
// Disabled - clear chord data
pianoRollState.setCurrentSuitableChords({});
pianoRollState.setCurrentSuitableChords([]);
pianoRollState.setCurrentSuitableChordsPitchClasses({});
if (DEBUG_MODE.PIANO_ROLL) {
@@ -748,28 +748,10 @@ const PianoRoll: React.FC<PianoRollProps> = ({
} else {
// Get suitable chords for the selected function (T/S/D)
const functionType = chordGuide as 'T' | 'S' | 'D';
const suitableChords = getSuitableChords(effectiveChordGuideKeySignature, chordGuideMode, functionType);
// Convert note names to pitch classes (ensuring ascending order)
const chordsPitchClasses: Record<string, number[]> = {};
for (const [chordSymbol, noteNames] of Object.entries(suitableChords)) {
const pitchClasses: number[] = [];
let previousPitch = -1;
for (const noteName of noteNames) {
let pitchClass = noteNameToPitchClass(noteName);
// If this pitch is lower than the previous one, add an octave
if (previousPitch >= 0 && pitchClass <= previousPitch) {
pitchClass += 12;
}
pitchClasses.push(pitchClass);
previousPitch = pitchClass;
}
chordsPitchClasses[chordSymbol] = pitchClasses;
}
const suitableChords = resolveChordGuideItems(effectiveChordGuideKeySignature, chordGuideMode, functionType);
const chordsPitchClasses = Object.fromEntries(
suitableChords.map((item) => [item.name, item.pitchClasses])
);
// Update piano roll state
pianoRollState.setCurrentSuitableChords(suitableChords);
@@ -118,6 +118,17 @@ vi.mock('./PianoRollToolbar', () => ({
},
}));
vi.mock('./chordGuideUtil', async () => {
const actual = await vi.importActual<typeof import('./chordGuideUtil')>('./chordGuideUtil');
return {
...actual,
resolveChordGuideContext: vi.fn(() => ({
keySignature: 'C major',
mode: 'ionian',
})),
};
});
describe('PianoRoll zoom persistence', () => {
beforeEach(() => {
latestToolbarProps = null;