# Project export: PLAI

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: UC Berkeley AI Hackathon 2025
- Tagline: An Audio to Sheet Music Converter that takes any audio file and outputs sheet music, playable on the piano.
- Devpost: https://devpost.com/software/plai-kzglqm
- GitHub: https://github.com/litongdeng/plAI-by-ear
- Demo: https://plai-by-ear.vercel.app/
- Team: 1 GitHub contributor(s) — litongdeng (11 commits)

## Devpost submission (written by the team)

### Inspiration

There are many songs out there that do not have dedicated sheet music, and we wanted to create a tool that could let us play any song on the Internet on the piano.

### What it does

We are using AI to transcribe music. It converts audio files in mp3, wav, ogg, flac, or m4a format into sheet music by using Google's Gemini 2.5 Pro AI model.

### How we built it

PLAI was built using with TypeScript, but we also integrated Google's Gemini 2.5 Pro AI model for audio analysis. We implemented Web Audio API for file processing, and used VexFlow to convert AI-detected notes into sheet music notation. The development involved establishing the foundation, implementing audio processing with drag-and-drop functionality, integrating AI capabilities with error handling, building sheet music generation, and polishing the user experience with custom branding.

### Challenges we ran into

It was difficult getting the notes to look like the sheet music we saw online, and it was even more difficult getting the notes to be actually accurate to the ones being played on the mp3. We had a hard time getting the Gemini API model that we wanted.

### Accomplishments we're proud of

We managed to figure out which API works the best for generating sheet music that is easy to read and is also accurate to the song that was put in.

### What we learned

We learned that there are a lot of challenges when it comes to analyzing audio files, as not many API's are trained on how to do more complex analyzing tasks yet. We learned how to transform complex audio analysis into an intuitive music transcription tool.

### What's next

We are planning on expanding what instruments PLAI covers and the length of the sheet music. We would ideally like to expand from snippets of music to covering the entire song, and we would like to improve the complexity of songs that it can analyze. We want PLAI to be able to do a variety of different time signatures, lengths of notes, and clefs. We also want to implement Essentia, which is an open-source C++ library for audio analysis, to make our audio processing even more accurate.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 68 recognized source files, 243 KB.
- CSS (language) — detected in the code
- Google Gemini (technology) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (73 of 73)

```
.gitignore
app/api/analyze-audio-gemini/route.ts
app/api/test-gemini/route.ts
app/globals.css
app/layout.tsx
app/page.tsx
components.json
components/audio-analyzer.tsx
components/audio-uploader.tsx
components/sheet-music-display.tsx
components/theme-provider.tsx
components/theme-toggle.tsx
components/ui/accordion.tsx
components/ui/alert-dialog.tsx
components/ui/alert.tsx
components/ui/aspect-ratio.tsx
components/ui/avatar.tsx
components/ui/badge.tsx
components/ui/breadcrumb.tsx
components/ui/button.tsx
components/ui/calendar.tsx
components/ui/card.tsx
components/ui/carousel.tsx
components/ui/chart.tsx
components/ui/checkbox.tsx
components/ui/collapsible.tsx
components/ui/command.tsx
components/ui/context-menu.tsx
components/ui/dialog.tsx
components/ui/drawer.tsx
components/ui/dropdown-menu.tsx
components/ui/form.tsx
components/ui/hover-card.tsx
components/ui/input-otp.tsx
components/ui/input.tsx
components/ui/label.tsx
components/ui/menubar.tsx
components/ui/navigation-menu.tsx
components/ui/pagination.tsx
components/ui/popover.tsx
components/ui/progress.tsx
components/ui/radio-group.tsx
components/ui/resizable.tsx
components/ui/scroll-area.tsx
components/ui/select.tsx
components/ui/separator.tsx
components/ui/sheet.tsx
components/ui/sidebar.tsx
components/ui/skeleton.tsx
components/ui/slider.tsx
components/ui/sonner.tsx
components/ui/switch.tsx
components/ui/table.tsx
components/ui/tabs.tsx
components/ui/textarea.tsx
components/ui/toast.tsx
components/ui/toaster.tsx
components/ui/toggle-group.tsx
components/ui/toggle.tsx
components/ui/tooltip.tsx
components/ui/use-mobile.tsx
components/ui/use-toast.ts
hooks/use-mobile.tsx
hooks/use-toast.ts
lib/actions.ts
lib/store.ts
lib/utils.ts
next.config.mjs
package.json
postcss.config.mjs
styles/globals.css
tailwind.config.ts
tsconfig.json
```

### Dependencies

- package.json: @google/generative-ai@latest, @hookform/resolvers@^3.9.1, @radix-ui/react-accordion@1.2.2, @radix-ui/react-alert-dialog@1.1.4, @radix-ui/react-aspect-ratio@1.1.1, @radix-ui/react-avatar@1.1.2, @radix-ui/react-checkbox@latest, @radix-ui/react-collapsible@1.1.2, @radix-ui/react-context-menu@2.2.4, @radix-ui/react-dialog@1.1.4, @radix-ui/react-dropdown-menu@2.1.4, @radix-ui/react-hover-card@1.1.4, @radix-ui/react-label@2.1.1, @radix-ui/react-menubar@1.1.4, @radix-ui/react-navigation-menu@1.2.3, @radix-ui/react-popover@1.1.4, @radix-ui/react-progress@latest, @radix-ui/react-radio-group@1.2.2, @radix-ui/react-scroll-area@1.2.2, @radix-ui/react-select@2.1.4, @radix-ui/react-separator@1.1.1, @radix-ui/react-slider@latest, @radix-ui/react-slot@1.1.1, @radix-ui/react-switch@1.1.2, @radix-ui/react-tabs@1.1.2, @radix-ui/react-toast@latest, @radix-ui/react-toggle@1.1.1, @radix-ui/react-toggle-group@1.1.1, @radix-ui/react-tooltip@1.1.6, @types/node@^22, @types/react@^19, @types/react-dom@^19, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, cmdk@1.0.4, date-fns@^3.6.0, embla-carousel-react@8.5.1, immer@latest, input-otp@1.4.1, lucide-react@^0.454.0, next@15.2.4, next-themes@^0.4.4, postcss@^8.5, react@^19, react-day-picker@8.10.1, react-dom@^19, react-hook-form@^7.54.1, react-resizable-panels@^2.1.7, recharts@2.15.0, sonner@^1.7.1, tailwind-merge@^2.5.5, tailwindcss@^3.4.17, tailwindcss-animate@^1.0.7, typescript@^5, use-sync-external-store@latest, vaul@^0.9.6, zod@^3.24.1, zustand@latest

### Recent commits (newest first)

- fixed ui
- before light mode edits
- tried to change light mode no work?
- timestamp + scrubbing functional
- finished liquid glass
- liquid glass step 1
- ui changed
- before any ui changes
- added logo!!!
- Update app title and layout; enhance audio uploader with waveform visualization and playback controls; add theme toggle component; refine audio analysis API integration.
- first commit

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
  "name": "my-v0-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "build": "next build",
    "dev": "next dev",
    "lint": "next lint",
    "start": "next start"
  },
  "dependencies": {
    "@google/generative-ai": "latest",
    "@hookform/resolvers": "^3.9.1",
    "@radix-ui/react-accordion": "1.2.2",
    "@radix-ui/react-alert-dialog": "1.1.4",
    "@radix-ui/react-aspect-ratio": "1.1.1",
    "@radix-ui/react-avatar": "1.1.2",
    "@radix-ui/react-checkbox": "latest",
    "@radix-ui/react-collapsible": "1.1.2",
    "@radix-ui/react-context-menu": "2.2.4",
    "@radix-ui/react-dialog": "1.1.4",
    "@radix-ui/react-dropdown-menu": "2.1.4",
    "@radix-ui/react-hover-card": "1.1.4",
    "@radix-ui/react-label": "2.1.1",
    "@radix-ui/react-menubar": "1.1.4",
    "@radix-ui/react-navigation-menu": "1.2.3",
    "@radix-ui/react-popover": "1.1.4",
    "@radix-ui/react-progress": "latest",
    "@radix-ui/react-radio-group": "1.2.2",
    "@radix-ui/react-scroll-area": "1.2.2",
    "@radix-ui/react-select": "2.1.4",
    "@radix-ui/react-separator": "1.1.1",
    "@radix-ui/react-slider": "latest",
    "@radix-ui/react-slot": "1.1.1",
    "@radix-ui/react-switch": "1.1.2",
    "@radix-ui/react-tabs": "1.1.2",
    "@radix-ui/react-toast": "latest",
    "@radix-ui/react-toggle": "1.1.1",
    "@radix-ui/react-toggle-group": "1.1.1",
    "@radix-ui/react-tooltip": "1.1.6",
    "autoprefixer": "^10.4.20",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "1.0.4",
    "date-fns": "^3.6.0",
    "embla-carousel-react": "8.5.1",
    "immer": "latest",
    "input-otp": "1.4.1",
    "lucide-react": "^0.454.0",
    "next": "15.2.4",
    "next-themes": "^0.4.4",
    "react": "^19",
    "react-day-picker": "8.10.1",
    "react-dom": "^19",
    "react-hook-form": "^7.54.1",
    "react-resizable-panels": "^2.1.7",
    "recharts": "2.15.0",
    "sonner": "^1.7.1",
    "tailwind-merge": "^2.5.5",
    "tailwindcss-animate": "^1.0.7",
    "use-sync-external-store": "latest",
    "vaul": "^0.9.6",
    "zod": "^3.24.1",
    "zustand": "latest"
  },
  "devDependencies": {
    "@types/node": "^22",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "postcss": "^8.5",
    "tailwindcss": "^3.4.17",
    "typescript": "^5"
  }
}
```

### app/layout.tsx

```typescript
import type React from "react"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import "./globals.css"
import { ThemeProvider } from "@/components/theme-provider"
import { Toaster } from "@/components/ui/toaster"

const inter = Inter({ subsets: ["latin"] })

export const metadata: Metadata = {
  title: "PLAI: An Audio to Sheet Music Converter",
  description: "Convert audio files to piano sheet music",
    generator: 'v0.dev'
}

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={inter.className}>
        <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
          <main className="min-h-screen bg-background">{children}</main>
          <Toaster />
        </ThemeProvider>
      </body>
    </html>
  )
}

```

### app/page.tsx

```typescript
import type { Metadata } from "next"
import Image from "next/image"
import AudioUploader from "@/components/audio-uploader"
import { SheetMusicDisplay } from "@/components/sheet-music-display"
import { ThemeToggle } from "@/components/theme-toggle"

export const metadata: Metadata = {
  title: "PLAI: An Audio to Sheet Music Converter",
  description: "Convert audio files to piano sheet music",
}

export default function Home() {
  return (
    <div className="container mx-auto py-4 space-y-6">
      <div className="relative">
        <div className="text-center">
          <div className="flex justify-center mb-2">
            <Image
              src="/PlAI_logo.png"
              alt="PLAI Logo"
              width={200}
              height={80}
              className="h-auto dark:invert block"
              priority
            />
          </div>
          <h1 className="text-xl font-bold tracking-tight">PLAI: An Audio to Sheet Music Converter</h1>
          <p className="text-muted-foreground max-w-2xl mx-auto mt-2">
            Upload an audio file of music and we'll convert it to piano sheet music that you can read, play, and download.
          </p>
        </div>
        <div className="absolute top-0 right-0">
          <ThemeToggle />
        </div>
      </div>

      <div className="grid gap-8 md:grid-cols-1 lg:grid-cols-[1fr_2fr]">
        <div className="space-y-6">
          <div className="liquid-panel p-6">
            <h2 className="text-xl font-semibold mb-4 glass-text">Upload Audio</h2>
            <AudioUploader />
          </div>
        </div>

        <div className="liquid-panel p-6">
          <h2 className="text-xl font-semibold mb-4 glass-text">Sheet Music</h2>
          <SheetMusicDisplay />
        </div>
      </div>
    </div>
  )
}

```

### app/api/test-gemini/route.ts

```typescript
import { GoogleGenerativeAI } from "@google/generative-ai"
import { NextResponse } from "next/server"

export async function GET() {
  try {
    console.log("=== Gemini API Test ===")

    // Check environment variables
    const apiKey = process.env.GOOGLE_AI_API_KEY
    const allEnvVars = Object.keys(process.env).filter((key) => key.includes("GOOGLE"))

    console.log("Available Google env vars:", allEnvVars)
    console.log("API key exists:", !!apiKey)
    console.log("API key length:", apiKey ? apiKey.length : 0)
    console.log("Node environment:", process.env.NODE_ENV)

    if (!apiKey) {
      return NextResponse.json({
        success: false,
        error: "GOOGLE_AI_API_KEY not found",
        debug: {
          availableGoogleEnvVars: allEnvVars,
          nodeEnv: process.env.NODE_ENV,
          vercelEnv: process.env.VERCEL_ENV || "not set",
        },
        instructions: [
          "1. Create .env.local file in project root",
          "2. Add: GOOGLE_AI_API_KEY=your_api_key",
          "3. Restart development server",
          "4. Try again",
        ],
      })
    }

    // Test multiple model variants starting with Gemini 2.5 Pro Preview
    const genAI = new GoogleGenerativeAI(apiKey)
    const modelsToTest = ["gemini-2.5-pro-preview-06-05", "gemini-1.5-pro", "gemini-1.5-flash", "gemini-1.5-pro-latest"]

    let workingModel = null
    const testResults = []

    for (const modelName of modelsToTest) {
      try {
        console.log(`Testing model: ${modelName}`)
        const model = genAI.getGenerativeModel({ model: modelName })

        const result = await model.generateContent(
          'Respond with this exact JSON: {"status": "working", "message": "API key works!", "model": "' +
            modelName +
            '"}',
        )
        const response = await result.response
        const text = response.text()

        testResults.push({
          model: modelName,
          success: true,
          response: text,
        })

        if (!workingModel) {
          workingModel = modelName
        }

        console.log(`✅ ${modelName} test successful!`)
      } catch (error) {
        console.log(`❌ ${modelName} test failed:`, error instanceof Error ? error.message : "Unknown error")
        testResults.push({
          model: modelName,
          success: false,
          error: error instanceof Error ? error.message : "Unknown error",
        })
      }
    }

    if (workingModel) {
      return NextResponse.json({
        success: true,
        message: `Google AI API key is working correctly!`,
        workingModel: workingModel,
        allResults: testResults,
        debug: {
          apiKeyLength: apiKey.length,
          responseLength: testResults.find((r) => r.success)?.response?.length || 0,
        },
      })
    } else {
      return NextResponse.json(
        {
          success: false,
          error: "No Gemini models are working",
          allResults: testResults,
          debug: {
            apiKeyExists: !!apiKey,
            apiKeyLength: apiKey.length,
            nodeEnv: process.env.NODE_ENV,
          },
        },
        { status: 500 },
      )
    }
  } catch (error) {
    console.error("❌ API test failed:", error)

    let errorType = "unknown"
    let suggestions = []

    if (error instanceof Error) {
      if (error.message.includes("API_KEY") || error.message.includes("key")) {
        errorType = "invalid_key"
        suggestions = [
          "Check that your API key is correct",
          "Ensure there are no extra spaces or characters",
          "Verify the key is enabled at https://aistudio.google.com/app/apikey",
        ]
      } else if (error.message.includes("quota")) {
        errorType = "quota_exceeded"
        suggestions = [
          "You've exceeded your API quota",
          "Wait for quota reset or upgrade your plan",
          "Check usage at https://aistudio.google.com/app/apikey",
        ]
      } else if (error.message.includes("network")) {
        errorType = "network_error"
        suggestions = [
          "Check your internet connection",
          "Try again in a few moments",
          "Verify Google AI services are accessible",
        ]
      } else if (error.message.includes("pattern") || error.message.includes("model")) {
        errorType = "model_not_available"
        suggestions = [
          "Some Gemini models may not be available in your region",
          "Check if you have access to the latest models",
          "Try again later as models may be temporarily unavailable",
        ]
      }
    }

    return NextResponse.json(
      {
        success: false,
        error: error instanceof Error ? error.message : "Unknown error",
        errorType,
        suggestions,
        debug: {
          apiKeyExists: !!process.env.GOOGLE_AI_API_KEY,
          apiKeyLength: process.env.GOOGLE_AI_API_KEY?.length || 0,
          nodeEnv: process.env.NODE_ENV,
        },
      },
      { status: 500 },
    )
  }
}

```

### app/api/analyze-audio-gemini/route.ts

```typescript
import { GoogleGenerativeAI } from "@google/generative-ai"
import { type NextRequest, NextResponse } from "next/server"

export async function POST(request: NextRequest) {
  try {
    const formData = await request.formData()
    const audioFile = formData.get("audio") as File

    if (!audioFile) {
      return NextResponse.json({ error: "No audio file provided" }, { status: 400 })
    }

    // Enhanced API key debugging
    const apiKey = process.env.GOOGLE_AI_API_KEY

    console.log("=== API Key Debug Info ===")
    console.log(
      "Environment variables available:",
      Object.keys(process.env).filter((key) => key.includes("GOOGLE")),
    )
    console.log("GOOGLE_AI_API_KEY exists:", !!apiKey)
    console.log("GOOGLE_AI_API_KEY length:", apiKey ? apiKey.length : 0)
    console.log("GOOGLE_AI_API_KEY starts with:", apiKey ? apiKey.substring(0, 10) + "..." : "undefined")

    if (!apiKey) {
      console.error("❌ Google AI API key not found in environment variables")
      return NextResponse.json(
        {
          error: "Google AI API key not configured",
          details:
            "The GOOGLE_AI_API_KEY environment variable is not set. Please add it to your .env.local file and restart your development server.",
          debugInfo: {
            availableEnvVars: Object.keys(process.env).filter((key) => key.includes("GOOGLE")),
            nodeEnv: process.env.NODE_ENV,
            vercelEnv: process.env.VERCEL_ENV,
          },
          instructions: [
            "1. Create a .env.local file in your project root",
            "2. Add: GOOGLE_AI_API_KEY=your_actual_api_key_here",
            "3. Restart your development server completely",
            "4. Get your API key from: https://aistudio.google.com/app/apikey",
          ],
        },
        { status: 500 },
      )
    }

    console.log(`Processing ${audioFile.name} (${audioFile.type}, ${audioFile.size} bytes)`)

    const genAI = new GoogleGenerativeAI(apiKey)

    // Try multiple model variants starting with Gemini 2.5 Pro Preview
    let model
    let modelUsed = ""

    try {
      // First try Gemini 2.5 Pro Preview (latest and most advanced)
      model = genAI.getGenerativeModel({ model: "gemini-2.5-pro-preview-06-05" })
      modelUsed = "gemini-2.5-pro-preview-06-05"
      console.log("Using Gemini 2.5 Pro Preview model")
    } catch (error) {
      try {
        // Fallback to Gemini 1.5 Pro (stable and widely available)
        model = genAI.getGenerativeModel({ model: "gemini-1.5-pro" })
        modelUsed = "gemini-1.5-pro"
        console.log("Falling back to Gemini 1.5 Pro model")
      } catch (error2) {
        try {
          // Fallback to Gemini 1.5 Flash
          model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" })
          modelUsed = "gemini-1.5-flash"
          console.log("Falling back to Gemini 1.5 Flash model")
        } catch (error3) {
          throw new Error("Unable to initialize any Gemini model. Please check your API key and model availability.")
        }
      }
    }

    // Convert file to base64
    const arrayBuffer = await audioFile.arrayBuffer()
    const base64Audio = Buffer.from(arrayBuffer).toString("base64")
    const mimeType = audioFile.type || "audio/mpeg"

    // Enhanced prompt optimized for Gemini 2.5 Pro capabilities
    const prompt = `You are a professional music transcription AI using Gemini 2.5 Pro's advanced audio analysis capabilities. Analyze this audio file and extract musical notes for sheet music transcription with the highest possible accuracy. Assume you have perfect pitch and are able to hear the notes.

CRITICAL: Return ONLY valid JSON in this exact format (no markdown, no explanations):
{
  "title": "Audio Analysis",
  "timeSignature": "4/4",
  "keySignature": "C",
  "tempo": 120,
  "duration": 30,
  "notes": [
    {"pitch": "C4", "duration": "q", "time": 0.0, "confidence": 0.95, "velocity": 80},
    {"pitch": "E4", "duration": "q", "time": 0.5, "confidence": 0.92, "velocity": 75}
  ],
  "analysis": {
    "instrument": "piano",
    "complexity": "moderate",
    "quality": "excellent",
    "keyDetected": "C major",
    "tempoDetected": 120,
    "timeSignatureDetected": "4/4",
    "musicalStyle": "classical",
    "dynamicRange": "moderate",
    "recommendations": "Clear polyphonic texture with well-defined melody line"
  }
}

ADVANCED ANALYSIS GUIDELINES FOR GEMINI 2.5 PRO:

1. **Pitch Detection (Use Gemini 2.5 Pro's enhanced audio understanding)**:
   - Standard notation: C3, C#3, D3, D#3, E3, F3, F#3, G3, G#3, A3, A#3, B3, C4, D4, E4, F4, G4, A4, B4, C5, D5, E5, F5, G5, A5, B5, C6
   - Focus on the most prominent and clear pitches
   - Detect microtonal variations and pitch bends
   - Identify overtones and harmonics for richer analysis

2. **Duration Analysis (Leverage advanced temporal understanding)**:
   - "w" (whole), "h" (half), "q" (quarter), "e" (eighth), "s" (sixteenth)
   - Estimate durations based on note spacing and tempo
   - Detect triplets, dotted notes, and complex rhythmic patterns
   - Analyze swing timing and rubato

3. **Musical Intelligence (Use Gemini 2.5 Pro's deep musical knowledge)**:
   - Identify the primary instrument being played
   - Detect the musical key and time signature
   - Estimate tempo from note timing patterns
   - Assess the complexity and quality of the recording
   - Identify chord progressions and harmonic relationships
   - Detect musical phrases and structural elements
   - Recognize musical styles and genres

4. **Quality Standards for Gemini 2.5 Pro**:
   - **Confidence Scoring**: 0.0 to 1.0 (include notes with confidence > 0.7)
   - **Velocity Analysis**: MIDI velocity 1-127 based on amplitude and attack
   - **Musical Logic**: Ensure notes make musical sense
   - **Limit**: Maximum 150 most musically significant notes
   - **Accuracy**: Prioritize precision over quantity
   - **Harmonic Content**: Understand the role of each note in the musical structure

5. **Advanced Features*
[truncated — 7818 more characters]
```

### tailwind.config.ts

```typescript
import type { Config } from "tailwindcss";

const config: Config = {
    darkMode: ["class"],
    content: [
    "./pages/**/*.{js,ts,jsx,tsx,mdx}",
    "./components/**/*.{js,ts,jsx,tsx,mdx}",
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "*.{js,ts,jsx,tsx,mdx}"
  ],
  theme: {
  	extend: {
  		colors: {
  			background: 'hsl(var(--background))',
  			foreground: 'hsl(var(--foreground))',
  			card: {
  				DEFAULT: 'hsl(var(--card))',
  				foreground: 'hsl(var(--card-foreground))'
  			},
  			popover: {
  				DEFAULT: 'hsl(var(--popover))',
  				foreground: 'hsl(var(--popover-foreground))'
  			},
  			primary: {
  				DEFAULT: 'hsl(var(--primary))',
  				foreground: 'hsl(var(--primary-foreground))'
  			},
  			secondary: {
  				DEFAULT: 'hsl(var(--secondary))',
  				foreground: 'hsl(var(--secondary-foreground))'
  			},
  			muted: {
  				DEFAULT: 'hsl(var(--muted))',
  				foreground: 'hsl(var(--muted-foreground))'
  			},
  			accent: {
  				DEFAULT: 'hsl(var(--accent))',
  				foreground: 'hsl(var(--accent-foreground))'
  			},
  			destructive: {
  				DEFAULT: 'hsl(var(--destructive))',
  				foreground: 'hsl(var(--destructive-foreground))'
  			},
  			border: 'hsl(var(--border))',
  			input: 'hsl(var(--input))',
  			ring: 'hsl(var(--ring))',
  			chart: {
  				'1': 'hsl(var(--chart-1))',
  				'2': 'hsl(var(--chart-2))',
  				'3': 'hsl(var(--chart-3))',
  				'4': 'hsl(var(--chart-4))',
  				'5': 'hsl(var(--chart-5))'
  			},
  			sidebar: {
  				DEFAULT: 'hsl(var(--sidebar-background))',
  				foreground: 'hsl(var(--sidebar-foreground))',
  				primary: 'hsl(var(--sidebar-primary))',
  				'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
  				accent: 'hsl(var(--sidebar-accent))',
  				'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
  				border: 'hsl(var(--sidebar-border))',
  				ring: 'hsl(var(--sidebar-ring))'
  			}
  		},
  		borderRadius: {
  			lg: 'var(--radius)',
  			md: 'calc(var(--radius) - 2px)',
  			sm: 'calc(var(--radius) - 4px)'
  		},
  		keyframes: {
  			'accordion-down': {
  				from: {
  					height: '0'
  				},
  				to: {
  					height: 'var(--radix-accordion-content-height)'
  				}
  			},
  			'accordion-up': {
  				from: {
  					height: 'var(--radix-accordion-content-height)'
  				},
  				to: {
  					height: '0'
  				}
  			}
  		},
  		animation: {
  			'accordion-down': 'accordion-down 0.2s ease-out',
  			'accordion-up': 'accordion-up 0.2s ease-out'
  		}
  	}
  },
  plugins: [require("tailwindcss-animate")],
};
export default config;

```

### lib/utils.ts

```typescript
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

```

### components/theme-provider.tsx

```typescript
'use client'

import * as React from 'react'
import {
  ThemeProvider as NextThemesProvider,
  type ThemeProviderProps,
} from 'next-themes'

export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
  return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

```

### hooks/use-mobile.tsx

```typescript
import * as React from "react"

const MOBILE_BREAKPOINT = 768

export function useIsMobile() {
  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)

  React.useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
    const onChange = () => {
      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
    }
    mql.addEventListener("change", onChange)
    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
    return () => mql.removeEventListener("change", onChange)
  }, [])

  return !!isMobile
}

```

### lib/store.ts

```typescript
"use client"

import { create } from "zustand"

// Define the store types
interface SheetMusicData {
  title: string
  timeSignature: string
  keySignature: string
  tempo: number
  notes: Array<{
    pitch: string
    duration: string
    time: number
  }>
}

interface SheetMusicStore {
  sheetMusic: SheetMusicData | null
  isLoading: boolean
  setSheetMusic: (data: SheetMusicData) => void
  setLoading: (loading: boolean) => void
  clearSheetMusic: () => void
}

// Create the store
export const useSheetMusicStore = create<SheetMusicStore>((set) => ({
  sheetMusic: null,
  isLoading: false,
  setSheetMusic: (data) => set({ sheetMusic: data, isLoading: false }),
  setLoading: (loading) => set({ isLoading: loading }),
  clearSheetMusic: () => set({ sheetMusic: null, isLoading: false }),
}))

```

[58 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]