Skip to content

Commit

Permalink
refactor: update naming
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The word "note" in relation of piano key with the word "key"
  • Loading branch information
tigranpetrossian committed Apr 28, 2024
1 parent d71b0c7 commit 473509b
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 130 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<div align="center">
<h1>Klavier</h1>
<h3>A lightweight, customizable, interactive piano library built with React.</h3>
<h3>A lightweight, customizable, interactive piano keyboard library built with React.</h3>
</div>

## Features
Expand Down Expand Up @@ -33,12 +33,12 @@ const App = () => {

| Prop | Default value | Description |
|-----------------------|------------------|-------------------------------------------------------------------------------------------------------------------------|
| `noteRange` | `[21, 108]` | The lowest and the highest notes of the piano in MIDI numbers (0-127). |
| `defaultActiveNotes` | `[]` | Notes that are pressed by default. Subsequent updates are ignored. Cleared when the user begins playing. |
| `activeNotes` | | Currently pressed notes. Puts component into controlled mode; active notes must be managed externally via callbacks. |
| `onPlayNote` | | Fired when a note is played. |
| `onStopNote` | | Fired when a note is stopped. |
| `onChange` | | Fired when active notes are changed via user input. |
| `keyRange` | `[21, 108]` | The lowest and the highest notes of the piano in MIDI numbers (0-127). |
| `defaultActiveKeys` | `[]` | Keys that are pressed by default. Subsequent updates are ignored. Cleared when the user begins playing. |
| `activeKeys` | | Currently pressed keys. Puts component into controlled mode; keys notes must be managed externally via callbacks. |
| `onKeyPress` | | Fired when a key is pressed. |
| `onKeyRelease` | | Fired when a key is released. |
| `onChange` | | Fired when active keys are changed via user input. |
| `interactive` | `true` | Enable interaction with the piano via keyboard, mouse, or touch. |
| `keymap` | `DEFAULT_KEYMAP` | Mapping of computer keys to MIDI note numbers, e.g. `[{ key: 'q', midiNumber: 60 }, ..., { key: 'i', midiNumber: 72 }]` |
| `width` | `"auto"` | Width of the piano. Accepts any valid CSS value. When unspecified, the piano fills it's container and is responsive. |
Expand All @@ -51,7 +51,7 @@ const App = () => {
## Styling

### Layout
By default, Klavier is responsive takes up the full width of its parent container. This can be changed by using a combination of `width`, `height`, `whiteKeyAspectRatio` and `blackKeyHeight` props.
By default, Klavier is responsive takes up the full width of its parent container. This can be changed by using a combination of `width`, `height`, and `blackKeyHeight` props.

### Appearance
The visual appearance of the keyboard can be customized by specifying custom components for the black and white keys. This enables styling the keyboard with any approach.
Expand Down
60 changes: 30 additions & 30 deletions src/components/Klavier.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,32 @@ interface KlavierProps {
* The lowest and the highest notes of the piano in MIDI numbers (0-127).
* @defaultValue [21, 108]
*/
noteRange?: [number, number];
keyRange?: [number, number];

/**
* Notes that are pressed by default. Subsequent updates are ignored. Cleared when the user begins playing.
* Keys that are pressed by default. Subsequent updates are ignored. Cleared when the user begins playing.
*/
defaultActiveNotes?: Array<number>;
defaultActiveKeys?: Array<number>;

/**
* Currently pressed notes. Puts component into controlled mode; active notes must be managed externally via callbacks.
* Currently pressed keys. Puts component into controlled mode; active keys must be managed externally via callbacks.
*/
activeNotes?: Array<number>;
activeKeys?: Array<number>;

/**
* Fired when a note is played.
* Fired when a key is played.
*/
onNotePlay?: (midiNumber: number) => void;
onKeyPress?: (midiNumber: number) => void;

/**
* Fired when a note is stopped.
* Fired when a key is released.
*/
onNoteStop?: (midiNumber: number) => void;
onKeyRelease?: (midiNumber: number) => void;

/**
* Fired when active notes are changed via user input.
* Fired when active keys are changed via user input.
*/
onChange?: (activeNotes: Array<number>) => void;
onChange?: (activeKeys: Array<number>) => void;

/**
* Enable interaction with the piano via keyboard, mouse, or touch.
Expand Down Expand Up @@ -92,44 +92,44 @@ interface KlavierProps {
const Klavier = (props: KlavierProps) => {
const klavierRootRef = useRef<HTMLDivElement>(null);
const {
defaultActiveNotes,
activeNotes,
onNotePlay,
onNoteStop,
defaultActiveKeys,
activeKeys,
onKeyPress,
onKeyRelease,
onChange,
noteRange = DEFAULT_NOTE_RANGE,
keyRange = DEFAULT_NOTE_RANGE,
interactive = true,
keyMap = DEFAULT_KEYMAP,
width,
height,
blackKeyHeight,
components,
} = props;
const [first, last] = noteRange;
const [first, last] = keyRange;
const {
state,
actions: { playNote, stopNote },
actions: { pressKey, releaseKey },
} = useKlavier({
defaultActiveNotes,
activeNotes,
onNotePlay,
onNoteStop,
defaultActiveKeys,
activeKeys,
onKeyPress,
onKeyRelease,
onChange,
});
const rootStyles = useMemo(() => getRootStyles(width, height), [width, height]);
const interactivitySettings = determineInteractivitySettings(interactive);
validateRange(noteRange);
validateRange(keyRange);

const handleMouseEvents = useMouse({ enabled: interactivitySettings.mouse, playNote, stopNote });
const handleMouseEvents = useMouse({ enabled: interactivitySettings.mouse, pressKey, releaseKey });
useKeyboard({
enabled: interactivitySettings.keyboard,
activeNotes: state.activeNotes,
activeKeys: state.activeKeys,
keyMap,
noteRange,
playNote,
stopNote,
keyRange,
pressKey,
releaseKey,
});
useTouch({ enabled: interactivitySettings.touch, klavierRootRef, playNote, stopNote });
useTouch({ enabled: interactivitySettings.touch, klavierRootRef, pressKey, releaseKey });

return (
<div style={rootStyles} ref={klavierRootRef}>
Expand All @@ -138,7 +138,7 @@ const Klavier = (props: KlavierProps) => {
key={midiNumber}
midiNumber={midiNumber}
firstNoteMidiNumber={first}
active={state.activeNotes.includes(midiNumber)}
active={state.activeKeys.includes(midiNumber)}
onMouseDown={handleMouseEvents}
onMouseUp={handleMouseEvents}
onMouseLeave={handleMouseEvents}
Expand Down
18 changes: 9 additions & 9 deletions src/interactivity/useKeyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,28 @@ import { noop } from 'lib/noop';

type UseKeyboardProps = {
enabled: boolean;
activeNotes: Array<number>;
playNote: (midiNumber: number) => void;
stopNote: (midiNumber: number) => void;
noteRange: [number, number];
activeKeys: Array<number>;
pressKey: (midiNumber: number) => void;
releaseKey: (midiNumber: number) => void;
keyRange: [number, number];
keyMap: Keymap;
};

function useKeyboard(props: UseKeyboardProps) {
const { enabled, activeNotes, playNote, stopNote, keyMap, noteRange } = props;
const { enabled, activeKeys, pressKey, releaseKey, keyMap, keyRange } = props;

const handleKeyboardEvents = useCallback(
(event: KeyboardEvent) => {
if (!isValidEvent(event)) return;
const midiNumber = getMidiNumberForKey(event.key, keyMap);
if (!isValidMidiNumber(midiNumber, noteRange)) return;
if (!isValidMidiNumber(midiNumber, keyRange)) return;
const actionMap = {
keydown: !activeNotes.includes(midiNumber) ? playNote : noop,
keyup: stopNote,
keydown: !activeKeys.includes(midiNumber) ? pressKey : noop,
keyup: releaseKey,
};
actionMap[event.type](midiNumber);
},
[noteRange, keyMap, activeNotes, playNote, stopNote]
[keyRange, keyMap, activeKeys, pressKey, releaseKey]
);

useEffect(() => {
Expand Down
14 changes: 7 additions & 7 deletions src/interactivity/useMouse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { useCallback, useRef } from 'react';

type UseMouseProps = {
enabled: boolean;
playNote: (midiNumber: number) => void;
stopNote: (midiNumber: number) => void;
pressKey: (midiNumber: number) => void;
releaseKey: (midiNumber: number) => void;
};

export function useMouse(props: UseMouseProps) {
const { enabled, playNote, stopNote } = props;
const { enabled, pressKey, releaseKey } = props;
// keep handleMouseEvents stable to prevent re-rendering of up to 88 keys
// on each mouse interaction by using ref instead of state
const isMouseDown = useRef(false);
Expand Down Expand Up @@ -36,22 +36,22 @@ export function useMouse(props: UseMouseProps) {
case 'mousedown':
window.addEventListener('mouseup', handleGlobalMouseUp);
setMouseDown(true);
playNote(midiNumber);
pressKey(midiNumber);
break;
case 'mouseup':
case 'mouseleave':
if (isMouseDown.current) {
stopNote(midiNumber);
releaseKey(midiNumber);
}
break;
case 'mouseenter':
if (isMouseDown.current) {
playNote(midiNumber);
pressKey(midiNumber);
}
break;
}
},
[enabled, handleGlobalMouseUp, playNote, stopNote, setMouseDown]
[enabled, handleGlobalMouseUp, pressKey, releaseKey, setMouseDown]
);

return handleMouseEvents;
Expand Down
24 changes: 12 additions & 12 deletions src/interactivity/useTouch/useTouch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { useActiveTouchPoints } from './useActiveTouchPoints';
type UseTouchProps = {
enabled: boolean;
klavierRootRef: React.RefObject<HTMLDivElement>;
playNote: (midiNumber: number) => void;
stopNote: (midiNumber: number) => void;
pressKey: (midiNumber: number) => void;
releaseKey: (midiNumber: number) => void;
};

function useTouch(props: UseTouchProps) {
const { klavierRootRef, enabled, playNote, stopNote } = props;
const { klavierRootRef, enabled, pressKey, releaseKey } = props;
const { activeTouchPoints, upsertTouchPoint, removeTouchPoint } = useActiveTouchPoints();

const handleTouchStart = useCallback(
Expand All @@ -19,10 +19,10 @@ function useTouch(props: UseTouchProps) {
const targetEvaluation = evaluateTarget(addedTouch.clientX, addedTouch.clientY);
if (!targetEvaluation.isValidTarget) return;
upsertTouchPoint(addedTouch.identifier, targetEvaluation.midiNumber);
playNote(targetEvaluation.midiNumber);
pressKey(targetEvaluation.midiNumber);
});
},
[playNote, upsertTouchPoint]
[pressKey, upsertTouchPoint]
);

const handleTouchEnd = useCallback(
Expand All @@ -33,11 +33,11 @@ function useTouch(props: UseTouchProps) {

removeTouchPoint(removedTouch.identifier);
if (wasLastTouchOnKey(targetEvaluation.midiNumber, removedTouch.identifier, event.touches)) {
stopNote(targetEvaluation.midiNumber);
releaseKey(targetEvaluation.midiNumber);
}
});
},
[stopNote, removeTouchPoint]
[releaseKey, removeTouchPoint]
);

const handleTouchMove = useCallback(
Expand All @@ -52,29 +52,29 @@ function useTouch(props: UseTouchProps) {

if (outcome.type === 'NEW_KEY') {
upsertTouchPoint(movedTouch.identifier, outcome.midiNumber);
playNote(outcome.midiNumber);
pressKey(outcome.midiNumber);
}

if (outcome.type === 'OUTSIDE_KEY') {
removeTouchPoint(movedTouch.identifier);
}

if (wasLastTouchOnKey(previousMidiNumber, movedTouch.identifier, event.touches)) {
stopNote(previousMidiNumber);
releaseKey(previousMidiNumber);
}
});
},
[activeTouchPoints, upsertTouchPoint, removeTouchPoint, playNote, stopNote]
[activeTouchPoints, upsertTouchPoint, removeTouchPoint, pressKey, releaseKey]
);

const handleTouchCancel = useCallback(
(event: TouchEvent) => {
Array.from(event.changedTouches).forEach((cancelledTouch) => {
stopNote(activeTouchPoints[cancelledTouch.identifier]);
releaseKey(activeTouchPoints[cancelledTouch.identifier]);
removeTouchPoint(cancelledTouch.identifier);
});
},
[activeTouchPoints, removeTouchPoint, stopNote]
[activeTouchPoints, removeTouchPoint, releaseKey]
);

useEffect(() => {
Expand Down
Loading

0 comments on commit 473509b

Please sign in to comment.