feat: implemented the logic to automatically create a chord when a suitable guide chord is highlighted.
This commit is contained in:
@@ -167,6 +167,18 @@ const PianoGrid: React.FC<PianoGridProps> = ({
|
||||
return highlights;
|
||||
}, [cursorPosition, matchingChords, selectedChordIndex]);
|
||||
|
||||
const cursorPitch = cursorPosition?.pitch ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
const pianoRollState = KGPianoRollState.instance();
|
||||
pianoRollState.setCurrentMatchingChords(matchingChords);
|
||||
pianoRollState.setCurrentChordCursorPitch(cursorPitch);
|
||||
}, [matchingChords, cursorPitch]);
|
||||
|
||||
useEffect(() => {
|
||||
KGPianoRollState.instance().setCurrentSelectedChordIndex(selectedChordIndex);
|
||||
}, [selectedChordIndex]);
|
||||
|
||||
const lastEditedNoteLength = KGPianoRollState.instance().getLastEditedNoteLength();
|
||||
|
||||
// Reset selected chord index when cursor moves to a different pitch or beat
|
||||
|
||||
@@ -17,6 +17,9 @@ export class KGPianoRollState {
|
||||
// Chord guide state
|
||||
private currentSuitableChords: Record<string, string[]> = {}; // Map of chord symbols to note names (e.g., {"I": ["C", "E", "G"]})
|
||||
private currentSuitableChordsPitchClasses: Record<string, number[]> = {}; // Map of chord symbols to pitch classes (e.g., {"I": [0, 4, 7]})
|
||||
private currentMatchingChords: number[][] = [];
|
||||
private currentSelectedChordIndex: number = 0;
|
||||
private currentChordCursorPitch: number | null = null;
|
||||
|
||||
private constructor() {
|
||||
console.log("KGPianoRollState initialized");
|
||||
@@ -77,4 +80,28 @@ export class KGPianoRollState {
|
||||
public setCurrentSuitableChordsPitchClasses(chordsPitchClasses: Record<string, number[]>): void {
|
||||
this.currentSuitableChordsPitchClasses = chordsPitchClasses;
|
||||
}
|
||||
|
||||
public getCurrentMatchingChords(): number[][] {
|
||||
return this.currentMatchingChords;
|
||||
}
|
||||
|
||||
public setCurrentMatchingChords(chords: number[][]): void {
|
||||
this.currentMatchingChords = chords;
|
||||
}
|
||||
|
||||
public getCurrentSelectedChordIndex(): number {
|
||||
return this.currentSelectedChordIndex;
|
||||
}
|
||||
|
||||
public setCurrentSelectedChordIndex(index: number): void {
|
||||
this.currentSelectedChordIndex = index;
|
||||
}
|
||||
|
||||
public getCurrentChordCursorPitch(): number | null {
|
||||
return this.currentChordCursorPitch;
|
||||
}
|
||||
|
||||
public setCurrentChordCursorPitch(pitch: number | null): void {
|
||||
this.currentChordCursorPitch = pitch;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import { KGCore } from '../core/KGCore';
|
||||
import { KGPianoRollState } from '../core/state/KGPianoRollState';
|
||||
import { KGAudioInterface } from '../core/audio-interface/KGAudioInterface';
|
||||
import { CreateNoteCommand, DeleteNotesCommand, ResizeNotesCommand, MoveNotesCommand } from '../core/commands';
|
||||
import { CreateNotesCommand } from '../core/commands/note/CreateNotesCommand';
|
||||
|
||||
interface UseNoteOperationsProps {
|
||||
activeRegion: KGMidiRegion | null;
|
||||
@@ -210,20 +211,64 @@ export const useNoteOperations = ({
|
||||
const lastEditedLength = KGPianoRollState.instance().getLastEditedNoteLength();
|
||||
const noteEndBeat = noteStartBeat + lastEditedLength; // Use last edited note length
|
||||
const velocity = 127; // Maximum velocity
|
||||
const pianoRollState = KGPianoRollState.instance();
|
||||
const matchingChordPitches = pianoRollState.getCurrentMatchingChords();
|
||||
const selectedChordIndex = pianoRollState.getCurrentSelectedChordIndex();
|
||||
const cursorChordPitch = pianoRollState.getCurrentChordCursorPitch();
|
||||
const chordIndex = matchingChordPitches.length > 0
|
||||
? ((selectedChordIndex % matchingChordPitches.length) + matchingChordPitches.length) % matchingChordPitches.length
|
||||
: 0;
|
||||
|
||||
// Create and execute the note creation command
|
||||
const command = new CreateNoteCommand(
|
||||
activeRegion.getId(),
|
||||
noteStartBeat,
|
||||
noteEndBeat,
|
||||
pitch,
|
||||
velocity
|
||||
);
|
||||
let chordNotePitches: number[] = [];
|
||||
|
||||
core.executeCommand(command);
|
||||
if (
|
||||
matchingChordPitches.length > 0 &&
|
||||
cursorChordPitch !== null &&
|
||||
Math.abs(cursorChordPitch - pitch) <= 1
|
||||
) {
|
||||
const cursorOctave = Math.floor(cursorChordPitch / 12);
|
||||
const pitchClasses = matchingChordPitches[chordIndex] || [];
|
||||
chordNotePitches = pitchClasses
|
||||
.map(actualPitchClass => {
|
||||
const actualPitch = cursorOctave * 12 + actualPitchClass;
|
||||
return actualPitch >= 0 && actualPitch <= 127 ? actualPitch : null;
|
||||
})
|
||||
.filter((value): value is number => value !== null);
|
||||
|
||||
// Get the created note for audio preview
|
||||
const createdNote = command.getCreatedNote();
|
||||
chordNotePitches = Array.from(new Set(chordNotePitches));
|
||||
}
|
||||
|
||||
const notePitchesToCreate = chordNotePitches.length > 0 ? chordNotePitches : [pitch];
|
||||
const isChordCreation = notePitchesToCreate.length > 1;
|
||||
|
||||
let createdNotes: KGMidiNote[] = [];
|
||||
|
||||
if (isChordCreation) {
|
||||
const chordCommand = new CreateNotesCommand(
|
||||
notePitchesToCreate.map(notePitch => ({
|
||||
regionId: activeRegion.getId(),
|
||||
startBeat: noteStartBeat,
|
||||
endBeat: noteEndBeat,
|
||||
pitch: notePitch,
|
||||
velocity
|
||||
}))
|
||||
);
|
||||
core.executeCommand(chordCommand);
|
||||
createdNotes = chordCommand.getCreatedNotes().map(({ note }) => note);
|
||||
} else {
|
||||
const singleNoteCommand = new CreateNoteCommand(
|
||||
activeRegion.getId(),
|
||||
noteStartBeat,
|
||||
noteEndBeat,
|
||||
notePitchesToCreate[0],
|
||||
velocity
|
||||
);
|
||||
core.executeCommand(singleNoteCommand);
|
||||
const createdNote = singleNoteCommand.getCreatedNote();
|
||||
if (createdNote) {
|
||||
createdNotes = [createdNote];
|
||||
}
|
||||
}
|
||||
|
||||
// Increment the note update counter to trigger a re-render
|
||||
setNoteUpdateCounter((prev: number) => prev + 1);
|
||||
@@ -238,19 +283,19 @@ export const useNoteOperations = ({
|
||||
updateTrack(track);
|
||||
|
||||
// Play note preview if audio interface is ready and note was created
|
||||
if (createdNote) {
|
||||
if (createdNotes.length > 0) {
|
||||
const audioInterface = KGAudioInterface.instance();
|
||||
if (audioInterface.getIsInitialized()) {
|
||||
// Try to start audio context if not started yet (user interaction will allow this)
|
||||
if (!audioInterface.getIsAudioContextStarted()) {
|
||||
audioInterface.startAudioContext().catch(() => {
|
||||
// Silently fail if still not allowed - browser policy
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger note if audio context is now started
|
||||
|
||||
if (audioInterface.getIsAudioContextStarted()) {
|
||||
audioInterface.triggerNote(track.getId().toString(), createdNote);
|
||||
createdNotes.forEach(note => {
|
||||
audioInterface.triggerNote(track.getId().toString(), note);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user