From 255ac33cbd1032cf81d5f4bebe317cc5e5b58d3d Mon Sep 17 00:00:00 2001 From: Oliver Bryan Date: Sun, 25 Jan 2026 13:54:55 +0000 Subject: [PATCH] keyboard is bound to piano --- src/Index.tsx | 107 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/src/Index.tsx b/src/Index.tsx index 652211d..20606e7 100644 --- a/src/Index.tsx +++ b/src/Index.tsx @@ -22,6 +22,62 @@ import * as Tone from "tone"; import Layout from "./Layout"; import { cn } from "./lib/utils"; +const KEY_NOTE_BINDINGS = [ + { key: "z", note: "C", octaveOffset: -1 }, + { key: "x", note: "C#", octaveOffset: -1 }, + { key: "c", note: "D", octaveOffset: -1 }, + { key: "v", note: "D#", octaveOffset: -1 }, + { key: "b", note: "E", octaveOffset: -1 }, + { key: "n", note: "F", octaveOffset: -1 }, + { key: "m", note: "F#", octaveOffset: -1 }, + { key: ",", note: "G", octaveOffset: -1 }, + { key: ".", note: "G#", octaveOffset: -1 }, + { key: "a", note: "A", octaveOffset: -1 }, + { key: "s", note: "A#", octaveOffset: -1 }, + { key: "d", note: "B", octaveOffset: -1 }, + { key: "f", note: "C", octaveOffset: 0 }, + { key: "g", note: "C#", octaveOffset: 0 }, + { key: "h", note: "D", octaveOffset: 0 }, + { key: "j", note: "D#", octaveOffset: 0 }, + { key: "k", note: "E", octaveOffset: 0 }, + { key: "l", note: "F", octaveOffset: 0 }, + { key: ";", note: "F#", octaveOffset: 0 }, + { key: "'", note: "G", octaveOffset: 0 }, + { key: "q", note: "G#", octaveOffset: 0 }, + { key: "w", note: "A", octaveOffset: 0 }, + { key: "e", note: "A#", octaveOffset: 0 }, + { key: "r", note: "B", octaveOffset: 0 }, + { key: "t", note: "C", octaveOffset: 1 }, + { key: "y", note: "C#", octaveOffset: 1 }, + { key: "u", note: "D", octaveOffset: 1 }, + { key: "i", note: "D#", octaveOffset: 1 }, + { key: "o", note: "E", octaveOffset: 1 }, + { key: "p", note: "F", octaveOffset: 1 }, + { key: "[", note: "F#", octaveOffset: 1 }, + { key: "]", note: "G", octaveOffset: 1 }, + { key: "1", note: "G#", octaveOffset: 1 }, + { key: "2", note: "A", octaveOffset: 1 }, + { key: "3", note: "A#", octaveOffset: 1 }, + { key: "4", note: "B", octaveOffset: 1 }, + { key: "5", note: "C", octaveOffset: 2 }, + { key: "6", note: "C#", octaveOffset: 2 }, + { key: "7", note: "D", octaveOffset: 2 }, + { key: "8", note: "D#", octaveOffset: 2 }, + { key: "9", note: "E", octaveOffset: 2 }, + { key: "0", note: "F", octaveOffset: 2 }, + { key: "-", note: "F#", octaveOffset: 2 }, + { key: "=", note: "G", octaveOffset: 2 }, +]; + +const KEY_NOTE_MAP = new Map(KEY_NOTE_BINDINGS.map((binding) => [binding.key, binding])); +const COLOR_BY_NOTE = new Map(colorScale.map((entry) => [entry.note, entry.color])); +const MIN_OCTAVE = 1; +const MAX_OCTAVE = 8; + +function clampOctave(value: number) { + return Math.min(MAX_OCTAVE, Math.max(MIN_OCTAVE, value)); +} + function Index() { const [dimensions, setDimensions] = useState({ width: window.innerWidth - 320, @@ -82,6 +138,57 @@ function Index() { pitchTime, ]); + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.repeat || event.metaKey || event.ctrlKey || event.altKey) { + return; + } + + const target = event.target; + if (target instanceof HTMLElement) { + const tagName = target.tagName.toLowerCase(); + if ( + target.isContentEditable || + tagName === "input" || + tagName === "textarea" || + tagName === "select" + ) { + return; + } + } + + const normalizedKey = event.key.length === 1 ? event.key.toLowerCase() : event.key; + const binding = KEY_NOTE_MAP.get(normalizedKey); + if (!binding) { + return; + } + + setState((prev) => { + const targetOctave = clampOctave(prev.octave + binding.octaveOffset); + console.log(`${binding.note + targetOctave} ${prev.octave} + ${binding.octaveOffset}`); + const color = COLOR_BY_NOTE.get(binding.note) ?? prev.color; + + void playPreviewSample({ + preset: prev.preset, + roundness: prev.roundness, + size: prev.size, + grain: prev.grain, + note: binding.note, + octave: targetOctave, + synthNodes: synthRef.current, + }); + + return { + ...prev, + color, + }; + }); + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [setState, synthRef]); + const sidebarContent = (