Skip to content

Commit

Permalink
Restored resize; updated velocity input; made undo a no-op for now
Browse files Browse the repository at this point in the history
  • Loading branch information
leegee committed Oct 16, 2024
1 parent fcd5967 commit d288330
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 79 deletions.
4 changes: 2 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import DeviceSelector from './components/DeviceSelector';
import PlayPauseButton from './components/PlayMIDI';
import Grids from './components/Grids';
import SaveMIDI from './components/SaveMIDI';
import UndoButton from './components/UndoButton';
// import UndoButton from './components/UndoButton';

const App: React.FC = () => {
const { error, selectedInput, selectedOutput } = useMIDI();
Expand Down Expand Up @@ -49,7 +49,7 @@ const App: React.FC = () => {
<h1>MIDI Phase Editor</h1>
<PlayPauseButton />
<BPMInput />
<UndoButton />
{/* <UndoButton /> */}
<SaveMIDI />
<DeviceSelector />
</section>
Expand Down
88 changes: 13 additions & 75 deletions src/components/GridInput.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useRef } from 'react';

import './GridInput.css';
import useMusicStore, { type GridNote } from '../store';
Expand All @@ -11,110 +11,48 @@ interface GridInputProps {
gridIndex: number;
}

const debounce = (func: Function, delay: number) => {
let timeoutId: NodeJS.Timeout | null = null;

return (...args: any) => {
if (timeoutId) {
clearTimeout(timeoutId);
}

timeoutId = setTimeout(() => {
func(...args);
}, delay);
};
};

const GridInput: React.FC<GridInputProps> = ({ gridIndex }) => {
const { grids, setGrid, updateNoteVelocity, setOrUpdateNoteInGrid } = useMusicStore();
const { grids, setGrid, updateNoteVelocity, setOrUpdateNoteInGrid, deleteNoteFromGrid } = useMusicStore();
const grid = grids[gridIndex];
const gridRef = useRef<HTMLDivElement | null>(null);
const cellRefs = useRef<(HTMLDivElement | null)[][]>(Array.from({ length: GRID_PITCH_RANGE }, () => Array(grid ? grid.numColumns : 0).fill(null)));

const [isCtrlPressed, setIsCtrlPressed] = useState(false);
const [draggingNote, setDraggingNote] = useState<GridNote | null>(null);

const toggleNote = useCallback((pitch: number, beatIndex: number, velocity: number = 100) => {
const toggleNote = useCallback((pitch: number, beatIndex: number, velocity?: number | undefined) => {
const grid = grids[gridIndex];

const notes = grid.beats[beatIndex]?.notes || {};
const existingNote = notes[pitch];

if (existingNote) {
if (isCtrlPressed) {
// Adjust velocity if CTRL is pressed
if (velocity) {
existingNote.velocity = velocity;
updateNoteVelocity(gridIndex, beatIndex, pitch, velocity);
// dispatchPlayNoteNowEvent(pitch, velocity);
} else {
// Remove the note if it exists and CTRL is not pressed
delete notes[pitch];
setOrUpdateNoteInGrid(gridIndex, beatIndex, { pitch, velocity: 0 }); // Use a default velocity if needed
deleteNoteFromGrid(gridIndex, beatIndex, pitch);
}
} else {
// Add a new note if it does not exist
const newNote: GridNote = { pitch, velocity };
const newNote: GridNote = { pitch, velocity: velocity || 75 };
setOrUpdateNoteInGrid(gridIndex, beatIndex, newNote);
dispatchPlayNoteNowEvent(pitch, velocity);
dispatchPlayNoteNowEvent(pitch, velocity || 75);
}
}, [isCtrlPressed, gridIndex, setOrUpdateNoteInGrid, updateNoteVelocity, grids]);

const debouncedToggleNoteRef = useRef(debounce(toggleNote, 25));
// Update the ref whenever toggleNote changes
useEffect(() => {
debouncedToggleNoteRef.current = debounce(toggleNote, 25);
}, [toggleNote]);
}, [gridIndex, setOrUpdateNoteInGrid, deleteNoteFromGrid, updateNoteVelocity, grids]);

const handleMouseDown = (pitch: number, e: React.MouseEvent<HTMLDivElement>) => {
const beatIndex = Number(e.currentTarget.dataset.beat);
if (e.ctrlKey) {
setIsCtrlPressed(true);

// Access the current grid from the store
const grid = grids[gridIndex];

// Check if the note exists in the specified beat
const note = grid.beats[beatIndex]?.notes[pitch];

// Set dragging note to the existing note or create a new one
setDraggingNote(note || { pitch, velocity: 75 });
const velocity = Number(prompt('Enter new velocity (1-127)', '75'));
if (velocity) {
toggleNote(pitch, beatIndex, velocity);
}
} else {
toggleNote(pitch, beatIndex); // Call toggleNote with the updated signature
toggleNote(pitch, beatIndex);
}
};

const handleMouseMove = useCallback((e: MouseEvent) => {
if (draggingNote && isCtrlPressed) {
if (!gridRef.current?.clientWidth) return;

// Use the dragging note's pitch and calculate the velocity based on the mouse Y position
const velocity = Math.max(0, Math.min(127, 127 - e.clientY / 4));

const beatIndex = Math.floor(e.clientX / (gridRef.current?.clientWidth / grid.numColumns)); // Adjust based on your layout
// toggleNote(draggingNote.pitch, beatIndex, velocity); // Use stored pitch from dragging note
debouncedToggleNoteRef.current(draggingNote.pitch, beatIndex, velocity); // Use debounced function
}
}, [draggingNote, grid.numColumns, isCtrlPressed]);


const handleMouseUp = () => {
setIsCtrlPressed(false);
setDraggingNote(null);
};

// Set up event listeners for drag
useEffect(() => {
if (isCtrlPressed && draggingNote) {
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
}

return () => {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
};
}, [isCtrlPressed, draggingNote, handleMouseMove]);

const calculateOpacity = (velocity: number) => {
return velocity / 127; // Normalize velocity to opacity range [0, 1]
};
Expand Down
23 changes: 21 additions & 2 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export interface MusicState {
setNumColumns: (gridIndex: number, numColumns: number) => void;
updateNoteVelocity: (gridIndex: number, beatIndex: number, pitch: number, velocity: number) => void;
setOrUpdateNoteInGrid: (gridIndex: number, beatIndex: number, note: GridNote) => void;
deleteNoteFromGrid: (gridIndex: number, beatIndex: number, pitch: number) => void;

mergedBeats: MergedBeat[];
mergeGrids: () => void;

Expand All @@ -45,15 +47,14 @@ export interface MusicState {

selectedInput: WebMidi.MIDIInput | null;
setSelectedInput: (input: WebMidi.MIDIInput | null) => void;

selectedOutput: WebMidi.MIDIOutput | null;
setSelectedOutput: (output: WebMidi.MIDIOutput | null) => void;

inputs: WebMidi.MIDIInput[];
setInputs: (inputs: WebMidi.MIDIInput[]) => void;

outputs: WebMidi.MIDIOutput[];
setOutputs: (outputs: WebMidi.MIDIOutput[]) => void;

error: string | null;
setError: (error: string | null) => void;

Expand Down Expand Up @@ -163,6 +164,22 @@ const useMusicStore = create<MusicState>((set, get) => ({
return { grids: newGrids };
}),

deleteNoteFromGrid: (gridIndex: number, beatIndex: number, pitch: number) =>
set((state) => {
pushToUndoStack();
const newGrids = [...state.grids];
const grid = newGrids[gridIndex];
if (grid) {
const notes = grid.beats[beatIndex]?.notes || {};
if (notes[pitch]) {
delete notes[pitch]; // Remove the note from the notes object
grid.beats[beatIndex].notes = { ...notes }; // Update the notes of the specific beat
}
}

return { grids: newGrids };
}),

mergedBeats: [],
mergeGrids: () => set((state) => {
pushToUndoStack();
Expand Down Expand Up @@ -202,6 +219,7 @@ const useMusicStore = create<MusicState>((set, get) => ({

undoStack: [],
undo: () => {
return; // noop
const { undoStack } = get();

if (undoStack.length === 0) {
Expand Down Expand Up @@ -234,6 +252,7 @@ const useMusicStore = create<MusicState>((set, get) => ({


function pushToUndoStack() {
return; // noop
const currentState = useMusicStore.getState();

const stateToStore = Object.keys(currentState).reduce((acc, key) => {
Expand Down

0 comments on commit d288330

Please sign in to comment.