Component
Interactive MIDI Keys in Framer
About the resource
The keys that make up the keyboard are SVG layers created in Figma and pasted back over to Framer. Usually, when you are creating detailed SVG illustrations with shadows and custom forms, it's better to use Figma and copy the SVGs over to Framer.
About the resource
The keys that make up the keyboard are SVG layers created in Figma and pasted back over to Framer. Usually, when you are creating detailed SVG illustrations with shadows and custom forms, it's better to use Figma and copy the SVGs over to Framer.
About the resource
The keys that make up the keyboard are SVG layers created in Figma and pasted back over to Framer. Usually, when you are creating detailed SVG illustrations with shadows and custom forms, it's better to use Figma and copy the SVGs over to Framer.
As you can see, each key has a "Default" and an "Active" design. In Framer, I created a component for each key with two variants: "Default" and "Active." Then, all I had to do was create an override that I can apply to each key so when they're clicked, they change to the "Active" variant and also play a note. You can find that override code below.
MIDI keyboard code override
Feel free to copy the code and create the override for yourself in your project by going to the assets panel, scrolling down to code, clicking the plus button, and creating a new code override file. Then, replace the default code with the code below.
As you can see, each key has a "Default" and an "Active" design. In Framer, I created a component for each key with two variants: "Default" and "Active." Then, all I had to do was create an override that I can apply to each key so when they're clicked, they change to the "Active" variant and also play a note. You can find that override code below.
MIDI keyboard code override
Feel free to copy the code and create the override for yourself in your project by going to the assets panel, scrolling down to code, clicking the plus button, and creating a new code override file. Then, replace the default code with the code below.
As you can see, each key has a "Default" and an "Active" design. In Framer, I created a component for each key with two variants: "Default" and "Active." Then, all I had to do was create an override that I can apply to each key so when they're clicked, they change to the "Active" variant and also play a note. You can find that override code below.
MIDI keyboard code override
Feel free to copy the code and create the override for yourself in your project by going to the assets panel, scrolling down to code, clicking the plus button, and creating a new code override file. Then, replace the default code with the code below.
import type { ComponentType } from "react" import { useEffect, useRef, useCallback, useState } from "react" const noteFrequencies = { C: 261.63 / 4, "C#": 277.18 / 4, D: 293.66 / 4, "D#": 311.13 / 4, E: 329.63 / 4, F: 349.23 / 4, "F#": 369.99 / 4, G: 392.0 / 4, "G#": 415.3 / 4, A: 440.0 / 4, "A#": 466.16 / 4, B: 493.88 / 4, } const noteKeys = { 1: "d", 2: "r", 3: "f", 4: "t", 5: "g", 6: "h", 7: "u", 8: "j", 9: "i", 10: "k", 11: "o", 12: "l", } function useWebAudio(noteFrequency: number) { const audioContextRef = useRef<AudioContext | null>(null) const oscillatorRef = useRef<OscillatorNode | null>(null) const gainNodeRef = useRef<GainNode | null>(null) useEffect(() => { if (!audioContextRef.current) { audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)() } }, []) const playNote = useCallback(() => { if (!audioContextRef.current) return const audioCtx = audioContextRef.current // Stop the previous note if it's still playing if (oscillatorRef.current) { oscillatorRef.current.stop() gainNodeRef.current!.gain.exponentialRampToValueAtTime( 0.001, audioCtx.currentTime + 0.03 ) } const oscillator = audioCtx.createOscillator() const gainNode = audioCtx.createGain() oscillator.type = "sawtooth" // Changed to 'sawtooth' for a synth-like sound oscillator.frequency.setValueAtTime(noteFrequency, audioCtx.currentTime) gainNode.gain.setValueAtTime(0.5, audioCtx.currentTime) oscillator.connect(gainNode) gainNode.connect(audioCtx.destination) oscillator.start() gainNode.gain.exponentialRampToValueAtTime( 0.001, audioCtx.currentTime + 0.75 ) oscillator.stop(audioCtx.currentTime + 0.75) oscillatorRef.current = oscillator gainNodeRef.current = gainNode }, [noteFrequency]) return playNote } function isTouchDevice() { if (typeof window === "undefined") return false return ( "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 ) } const createNoteOverride = (noteIndex: number) => { const noteKey = noteKeys[noteIndex] const noteFrequency = Object.values(noteFrequencies)[noteIndex - 1] return function (Component: ComponentType): ComponentType { return (props) => { const playNote = useWebAudio(noteFrequency) const [variant, setVariant] = useState("Default") const isPlaying = useRef(false) useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === noteKey && !isPlaying.current) { playNote() setVariant("Active") isPlaying.current = true } } const handleKeyUp = (event: KeyboardEvent) => { if (event.key === noteKey) { setVariant("Default") isPlaying.current = false } } window.addEventListener("keydown", handleKeyDown) window.addEventListener("keyup", handleKeyUp) return () => { window.removeEventListener("keydown", handleKeyDown) window.removeEventListener("keyup", handleKeyUp) } }, [noteKey, playNote]) const handleMouseDown = () => { if (!isPlaying.current) { playNote() setVariant("Active") isPlaying.current = true } window.addEventListener("mouseup", handleMouseUp) } const handleMouseUp = () => { setVariant("Default") isPlaying.current = false window.removeEventListener("mouseup", handleMouseUp) } const handleTouchStart = () => { if (!isPlaying.current) { playNote() setVariant("Active") isPlaying.current = true } } const handleTouchEnd = () => { setVariant("Default") isPlaying.current = false } const events = isTouchDevice() ? { onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd } : { onMouseDown: handleMouseDown, onMouseUp: handleMouseUp } return ( <Component {...props} variant={variant} {...events} tabIndex="-1" style={{ WebkitTapHighlightColor: "transparent", outline: "none", }} /> ) } } } // Generate note HOCs export const withNote1 = (Component: ComponentType): ComponentType => createNoteOverride(1)(Component) export const withNote2 = (Component: ComponentType): ComponentType => createNoteOverride(2)(Component) export const withNote3 = (Component: ComponentType): ComponentType => createNoteOverride(3)(Component) export const withNote4 = (Component: ComponentType): ComponentType => createNoteOverride(4)(Component) export const withNote5 = (Component: ComponentType): ComponentType => createNoteOverride(5)(Component) export const withNote6 = (Component: ComponentType): ComponentType => createNoteOverride(6)(Component) export const withNote7 = (Component: ComponentType): ComponentType => createNoteOverride(7)(Component) export const withNote8 = (Component: ComponentType): ComponentType => createNoteOverride(8)(Component) export const withNote9 = (Component: ComponentType): ComponentType => createNoteOverride(9)(Component) export const withNote10 = (Component: ComponentType): ComponentType => createNoteOverride(10)(Component) export const withNote11 = (Component: ComponentType): ComponentType => createNoteOverride(11)(Component) export const withNote12 = (Component: ComponentType): ComponentType => createNoteOverride(12)(Component)
import type { ComponentType } from "react" import { useEffect, useRef, useCallback, useState } from "react" const noteFrequencies = { C: 261.63 / 4, "C#": 277.18 / 4, D: 293.66 / 4, "D#": 311.13 / 4, E: 329.63 / 4, F: 349.23 / 4, "F#": 369.99 / 4, G: 392.0 / 4, "G#": 415.3 / 4, A: 440.0 / 4, "A#": 466.16 / 4, B: 493.88 / 4, } const noteKeys = { 1: "d", 2: "r", 3: "f", 4: "t", 5: "g", 6: "h", 7: "u", 8: "j", 9: "i", 10: "k", 11: "o", 12: "l", } function useWebAudio(noteFrequency: number) { const audioContextRef = useRef<AudioContext | null>(null) const oscillatorRef = useRef<OscillatorNode | null>(null) const gainNodeRef = useRef<GainNode | null>(null) useEffect(() => { if (!audioContextRef.current) { audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)() } }, []) const playNote = useCallback(() => { if (!audioContextRef.current) return const audioCtx = audioContextRef.current // Stop the previous note if it's still playing if (oscillatorRef.current) { oscillatorRef.current.stop() gainNodeRef.current!.gain.exponentialRampToValueAtTime( 0.001, audioCtx.currentTime + 0.03 ) } const oscillator = audioCtx.createOscillator() const gainNode = audioCtx.createGain() oscillator.type = "sawtooth" // Changed to 'sawtooth' for a synth-like sound oscillator.frequency.setValueAtTime(noteFrequency, audioCtx.currentTime) gainNode.gain.setValueAtTime(0.5, audioCtx.currentTime) oscillator.connect(gainNode) gainNode.connect(audioCtx.destination) oscillator.start() gainNode.gain.exponentialRampToValueAtTime( 0.001, audioCtx.currentTime + 0.75 ) oscillator.stop(audioCtx.currentTime + 0.75) oscillatorRef.current = oscillator gainNodeRef.current = gainNode }, [noteFrequency]) return playNote } function isTouchDevice() { if (typeof window === "undefined") return false return ( "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 ) } const createNoteOverride = (noteIndex: number) => { const noteKey = noteKeys[noteIndex] const noteFrequency = Object.values(noteFrequencies)[noteIndex - 1] return function (Component: ComponentType): ComponentType { return (props) => { const playNote = useWebAudio(noteFrequency) const [variant, setVariant] = useState("Default") const isPlaying = useRef(false) useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === noteKey && !isPlaying.current) { playNote() setVariant("Active") isPlaying.current = true } } const handleKeyUp = (event: KeyboardEvent) => { if (event.key === noteKey) { setVariant("Default") isPlaying.current = false } } window.addEventListener("keydown", handleKeyDown) window.addEventListener("keyup", handleKeyUp) return () => { window.removeEventListener("keydown", handleKeyDown) window.removeEventListener("keyup", handleKeyUp) } }, [noteKey, playNote]) const handleMouseDown = () => { if (!isPlaying.current) { playNote() setVariant("Active") isPlaying.current = true } window.addEventListener("mouseup", handleMouseUp) } const handleMouseUp = () => { setVariant("Default") isPlaying.current = false window.removeEventListener("mouseup", handleMouseUp) } const handleTouchStart = () => { if (!isPlaying.current) { playNote() setVariant("Active") isPlaying.current = true } } const handleTouchEnd = () => { setVariant("Default") isPlaying.current = false } const events = isTouchDevice() ? { onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd } : { onMouseDown: handleMouseDown, onMouseUp: handleMouseUp } return ( <Component {...props} variant={variant} {...events} tabIndex="-1" style={{ WebkitTapHighlightColor: "transparent", outline: "none", }} /> ) } } } // Generate note HOCs export const withNote1 = (Component: ComponentType): ComponentType => createNoteOverride(1)(Component) export const withNote2 = (Component: ComponentType): ComponentType => createNoteOverride(2)(Component) export const withNote3 = (Component: ComponentType): ComponentType => createNoteOverride(3)(Component) export const withNote4 = (Component: ComponentType): ComponentType => createNoteOverride(4)(Component) export const withNote5 = (Component: ComponentType): ComponentType => createNoteOverride(5)(Component) export const withNote6 = (Component: ComponentType): ComponentType => createNoteOverride(6)(Component) export const withNote7 = (Component: ComponentType): ComponentType => createNoteOverride(7)(Component) export const withNote8 = (Component: ComponentType): ComponentType => createNoteOverride(8)(Component) export const withNote9 = (Component: ComponentType): ComponentType => createNoteOverride(9)(Component) export const withNote10 = (Component: ComponentType): ComponentType => createNoteOverride(10)(Component) export const withNote11 = (Component: ComponentType): ComponentType => createNoteOverride(11)(Component) export const withNote12 = (Component: ComponentType): ComponentType => createNoteOverride(12)(Component)
import type { ComponentType } from "react" import { useEffect, useRef, useCallback, useState } from "react" const noteFrequencies = { C: 261.63 / 4, "C#": 277.18 / 4, D: 293.66 / 4, "D#": 311.13 / 4, E: 329.63 / 4, F: 349.23 / 4, "F#": 369.99 / 4, G: 392.0 / 4, "G#": 415.3 / 4, A: 440.0 / 4, "A#": 466.16 / 4, B: 493.88 / 4, } const noteKeys = { 1: "d", 2: "r", 3: "f", 4: "t", 5: "g", 6: "h", 7: "u", 8: "j", 9: "i", 10: "k", 11: "o", 12: "l", } function useWebAudio(noteFrequency: number) { const audioContextRef = useRef<AudioContext | null>(null) const oscillatorRef = useRef<OscillatorNode | null>(null) const gainNodeRef = useRef<GainNode | null>(null) useEffect(() => { if (!audioContextRef.current) { audioContextRef.current = new (window.AudioContext || (window as any).webkitAudioContext)() } }, []) const playNote = useCallback(() => { if (!audioContextRef.current) return const audioCtx = audioContextRef.current // Stop the previous note if it's still playing if (oscillatorRef.current) { oscillatorRef.current.stop() gainNodeRef.current!.gain.exponentialRampToValueAtTime( 0.001, audioCtx.currentTime + 0.03 ) } const oscillator = audioCtx.createOscillator() const gainNode = audioCtx.createGain() oscillator.type = "sawtooth" // Changed to 'sawtooth' for a synth-like sound oscillator.frequency.setValueAtTime(noteFrequency, audioCtx.currentTime) gainNode.gain.setValueAtTime(0.5, audioCtx.currentTime) oscillator.connect(gainNode) gainNode.connect(audioCtx.destination) oscillator.start() gainNode.gain.exponentialRampToValueAtTime( 0.001, audioCtx.currentTime + 0.75 ) oscillator.stop(audioCtx.currentTime + 0.75) oscillatorRef.current = oscillator gainNodeRef.current = gainNode }, [noteFrequency]) return playNote } function isTouchDevice() { if (typeof window === "undefined") return false return ( "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0 ) } const createNoteOverride = (noteIndex: number) => { const noteKey = noteKeys[noteIndex] const noteFrequency = Object.values(noteFrequencies)[noteIndex - 1] return function (Component: ComponentType): ComponentType { return (props) => { const playNote = useWebAudio(noteFrequency) const [variant, setVariant] = useState("Default") const isPlaying = useRef(false) useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === noteKey && !isPlaying.current) { playNote() setVariant("Active") isPlaying.current = true } } const handleKeyUp = (event: KeyboardEvent) => { if (event.key === noteKey) { setVariant("Default") isPlaying.current = false } } window.addEventListener("keydown", handleKeyDown) window.addEventListener("keyup", handleKeyUp) return () => { window.removeEventListener("keydown", handleKeyDown) window.removeEventListener("keyup", handleKeyUp) } }, [noteKey, playNote]) const handleMouseDown = () => { if (!isPlaying.current) { playNote() setVariant("Active") isPlaying.current = true } window.addEventListener("mouseup", handleMouseUp) } const handleMouseUp = () => { setVariant("Default") isPlaying.current = false window.removeEventListener("mouseup", handleMouseUp) } const handleTouchStart = () => { if (!isPlaying.current) { playNote() setVariant("Active") isPlaying.current = true } } const handleTouchEnd = () => { setVariant("Default") isPlaying.current = false } const events = isTouchDevice() ? { onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd } : { onMouseDown: handleMouseDown, onMouseUp: handleMouseUp } return ( <Component {...props} variant={variant} {...events} tabIndex="-1" style={{ WebkitTapHighlightColor: "transparent", outline: "none", }} /> ) } } } // Generate note HOCs export const withNote1 = (Component: ComponentType): ComponentType => createNoteOverride(1)(Component) export const withNote2 = (Component: ComponentType): ComponentType => createNoteOverride(2)(Component) export const withNote3 = (Component: ComponentType): ComponentType => createNoteOverride(3)(Component) export const withNote4 = (Component: ComponentType): ComponentType => createNoteOverride(4)(Component) export const withNote5 = (Component: ComponentType): ComponentType => createNoteOverride(5)(Component) export const withNote6 = (Component: ComponentType): ComponentType => createNoteOverride(6)(Component) export const withNote7 = (Component: ComponentType): ComponentType => createNoteOverride(7)(Component) export const withNote8 = (Component: ComponentType): ComponentType => createNoteOverride(8)(Component) export const withNote9 = (Component: ComponentType): ComponentType => createNoteOverride(9)(Component) export const withNote10 = (Component: ComponentType): ComponentType => createNoteOverride(10)(Component) export const withNote11 = (Component: ComponentType): ComponentType => createNoteOverride(11)(Component) export const withNote12 = (Component: ComponentType): ComponentType => createNoteOverride(12)(Component)