Yesun Joung LogoYesun Joung

Senior Software Engineer

Building an Interactive Piano Component with React

This React component creates an interactive virtual piano with the following features:

Core Functionality

  • Renders a keyboard with notes spanning 3 octaves (3-5)
  • Generates piano-like sounds using Web Audio API
  • Allows both mouse/touch and keyboard interaction
  • Includes pre-programmed songs that can be played automatically

Technical Implementation

First, we set up the basic piano structure with our required imports and constants:

import { useState, useCallback } from "react"
import { motion } from "framer-motion"
import PianoKey from "./pianoKey"
import { happinessAnthem, happyBirthday } from "@/lib/songs"

const notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
const octaves = [3, 4, 5]
const audioContext =
  typeof window !== "undefined" ? new (window.AudioContext || (window as any).webkitAudioContext)() : null
  • Uses framer-motion for animations and visual feedback
  • Generates audio with oscillators, gain nodes and dynamic compression

For accurate sound generation, we calculate note frequencies using equal temperament tuning:

// Piano note frequencies (A4 = 440Hz as reference)
const getFrequency = (note: string) => {
  const [noteName, octaveStr] = [note.slice(0, -1), note.slice(-1)]
  const octave = Number.parseInt(octaveStr)

  if (isNaN(octave)) return 440 // Return A4 as fallback

  const noteIndex = notes.indexOf(noteName)
  if (noteIndex === -1) return 440 // Return A4 as fallback

  // Calculate semitones from A4 (A4 = 440Hz)
  const A4Index = notes.indexOf("A")
  const semitonesDiff = noteIndex - A4Index + (octave - 4) * 12

  // Calculate frequency using equal temperament formula
  return 440 * Math.pow(2, semitonesDiff / 12)
}
  • Calculates note frequencies mathematically (equal temperament tuning)
  • Shows visual feedback when keys are pressed

We implement a song playback system that can play any array of notes with customizable speed:

const playSong = useCallback(
  (song: string[], speed = 200) => {
    setIsPlaying(true)
    setCurrentSong(song)
    
    song.forEach((note, index) => {
      setTimeout(() => {
        if (note !== " ") playNote(note)
        if (index === song.length - 1) {
          setIsPlaying(false)
          setCurrentSong([])
        }
      }, index * speed)
    })
  },
  [playNote],
)

Finally, we render the piano keyboard with the appropriate UI elements:

return (
  <div className="w-full flex flex-col items-center space-y-4">
    <div className="w-full flex flex-wrap justify-center">
      {octaves.map((octave) =>
        notes.map((note) => (
          <PianoKey
            key={`${note}${octave}`}
            note={`${note}${octave}`}
            isActive={activeKeys.includes(`${note}${octave}`)}
            onClick={() => playNote(`${note}${octave}`)}
            isSharp={note.includes("#")}
          />
        )),
      )}
    </div>
    
    {/* Play buttons and status indicators */}
  </div>
))

User Experience

  • Keys light up when played (green animation)
  • Multiple songs available via button controls
  • Shows "Now Playing" status during song playback
  • Responsive layout that works across device sizes

The component combines audio synthesis, visual feedback, and interactivity to create an engaging musical experience.