Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>InstrumentFromPhoto - AI-Powered Virtual Instruments</title> | |
| <!-- Material Icons --> | |
| <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| :root { | |
| --primary: #6750A4; | |
| --primary-variant: #4F378B; | |
| --secondary: #625B71; | |
| --surface: #FFFBFE; | |
| --surface-variant: #E7E0EC; | |
| --background: #FEF7FF; | |
| --error: #BA1A1A; | |
| --on-primary: #FFFFFF; | |
| --on-secondary: #FFFFFF; | |
| --on-surface: #1C1B1F; | |
| --on-surface-variant: #49454F; | |
| --outline: #79747E; | |
| --shadow: rgba(0, 0, 0, 0.12); | |
| --elevation-1: 0 1px 2px rgba(0,0,0,0.16), 0 1px 3px rgba(0,0,0,0.23); | |
| --elevation-2: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); | |
| --elevation-3: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background: var(--background); | |
| color: var(--on-surface); | |
| overflow-x: hidden; | |
| position: relative; | |
| min-height: 100vh; | |
| } | |
| .app-container { | |
| display: flex; | |
| flex-direction: column; | |
| min-height: 100vh; | |
| position: relative; | |
| } | |
| /* Header */ | |
| .header { | |
| background: var(--primary); | |
| color: var(--on-primary); | |
| padding: 16px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| box-shadow: var(--elevation-2); | |
| position: sticky; | |
| top: 0; | |
| z-index: 100; | |
| } | |
| .header-title { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| font-size: 20px; | |
| font-weight: 600; | |
| } | |
| .header-actions { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .icon-button { | |
| background: rgba(255, 255, 255, 0.12); | |
| border: none; | |
| border-radius: 20px; | |
| width: 40px; | |
| height: 40px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .icon-button:hover { | |
| background: rgba(255, 255, 255, 0.24); | |
| transform: scale(1.05); | |
| } | |
| /* Main Content */ | |
| .main-content { | |
| flex: 1; | |
| padding: 16px; | |
| padding-bottom: 80px; | |
| } | |
| /* Camera View */ | |
| .camera-section { | |
| display: none; | |
| animation: fadeIn 0.3s ease-out; | |
| } | |
| .camera-section.active { | |
| display: block; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .camera-container { | |
| background: var(--surface); | |
| border-radius: 16px; | |
| overflow: hidden; | |
| box-shadow: var(--elevation-1); | |
| margin-bottom: 16px; | |
| } | |
| #camera-view { | |
| width: 100%; | |
| height: 300px; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .camera-placeholder { | |
| text-align: center; | |
| color: white; | |
| } | |
| .camera-placeholder .material-icons { | |
| font-size: 64px; | |
| margin-bottom: 16px; | |
| } | |
| .camera-controls { | |
| display: flex; | |
| gap: 12px; | |
| padding: 16px; | |
| background: var(--surface-variant); | |
| } | |
| .capture-button { | |
| flex: 1; | |
| background: var(--primary); | |
| color: var(--on-primary); | |
| border: none; | |
| padding: 16px; | |
| border-radius: 100px; | |
| font-size: 16px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| } | |
| .capture-button:hover { | |
| background: var(--primary-variant); | |
| transform: scale(1.02); | |
| } | |
| .capture-button:active { | |
| transform: scale(0.98); | |
| } | |
| /* AI Processing */ | |
| .ai-processing { | |
| display: none; | |
| background: var(--surface); | |
| border-radius: 16px; | |
| padding: 24px; | |
| text-align: center; | |
| box-shadow: var(--elevation-1); | |
| animation: pulse 2s infinite; | |
| } | |
| .ai-processing.active { | |
| display: block; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.7; } | |
| } | |
| .processing-icon { | |
| font-size: 48px; | |
| color: var(--primary); | |
| animation: spin 2s linear infinite; | |
| margin-bottom: 16px; | |
| } | |
| @keyframes spin { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| /* Instrument Selection */ | |
| .instrument-selection { | |
| display: none; | |
| animation: slideUp 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .instrument-selection.active { | |
| display: block; | |
| } | |
| @keyframes slideUp { | |
| from { opacity: 0; transform: translateY(20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .detection-result { | |
| background: var(--surface); | |
| border-radius: 16px; | |
| padding: 20px; | |
| margin-bottom: 16px; | |
| box-shadow: var(--elevation-1); | |
| } | |
| .detection-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| margin-bottom: 16px; | |
| } | |
| .detection-icon { | |
| width: 48px; | |
| height: 48px; | |
| background: var(--primary); | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: var(--on-primary); | |
| } | |
| .detection-info h3 { | |
| font-size: 20px; | |
| font-weight: 600; | |
| margin-bottom: 4px; | |
| } | |
| .confidence-badge { | |
| display: inline-block; | |
| background: var(--surface-variant); | |
| padding: 4px 12px; | |
| border-radius: 100px; | |
| font-size: 12px; | |
| font-weight: 500; | |
| color: var(--on-surface-variant); | |
| } | |
| .instrument-variants { | |
| display: flex; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| margin-top: 12px; | |
| } | |
| .variant-chip { | |
| background: var(--surface-variant); | |
| padding: 8px 16px; | |
| border-radius: 100px; | |
| font-size: 14px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .variant-chip:hover { | |
| background: var(--primary); | |
| color: var(--on-primary); | |
| } | |
| /* Playable Interfaces */ | |
| .instrument-playground { | |
| background: var(--surface); | |
| border-radius: 16px; | |
| padding: 20px; | |
| box-shadow: var(--elevation-1); | |
| min-height: 400px; | |
| } | |
| .playground-header { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| margin-bottom: 20px; | |
| } | |
| .playground-title { | |
| font-size: 18px; | |
| font-weight: 600; | |
| } | |
| .playground-controls { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .control-button { | |
| background: var(--surface-variant); | |
| border: none; | |
| padding: 8px 16px; | |
| border-radius: 100px; | |
| font-size: 14px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .control-button:hover { | |
| background: var(--primary); | |
| color: var(--on-primary); | |
| } | |
| /* Fretboard Renderer */ | |
| .fretboard-renderer { | |
| width: 100%; | |
| overflow-x: auto; | |
| -webkit-overflow-scrolling: touch; | |
| padding: 20px 0; | |
| } | |
| .fretboard { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| min-width: 600px; | |
| } | |
| .string { | |
| display: flex; | |
| align-items: center; | |
| height: 40px; | |
| position: relative; | |
| background: linear-gradient(90deg, var(--outline) 0%, transparent 1px); | |
| } | |
| .string::before { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background: var(--secondary); | |
| transform: translateY(-50%); | |
| } | |
| .fret { | |
| flex: 1; | |
| height: 40px; | |
| border-right: 2px solid var(--outline); | |
| position: relative; | |
| cursor: pointer; | |
| transition: all 0.1s; | |
| } | |
| .fret:hover { | |
| background: rgba(103, 80, 164, 0.1); | |
| } | |
| .fret.active { | |
| background: var(--primary); | |
| animation: noteGlow 0.3s ease-out; | |
| } | |
| @keyframes noteGlow { | |
| 0% { transform: scale(1); box-shadow: 0 0 0 0 rgba(103, 80, 164, 0.7); } | |
| 100% { transform: scale(1.1); box-shadow: 0 0 0 10px rgba(103, 80, 164, 0); } | |
| } | |
| .fret-marker { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: 8px; | |
| height: 8px; | |
| background: var(--on-surface-variant); | |
| border-radius: 50%; | |
| opacity: 0.3; | |
| } | |
| /* Pad Grid Renderer */ | |
| .pad-grid-renderer { | |
| display: grid; | |
| gap: 12px; | |
| padding: 20px 0; | |
| } | |
| .pad-grid-4x4 { | |
| grid-template-columns: repeat(4, 1fr); | |
| } | |
| .pad { | |
| aspect-ratio: 1; | |
| background: var(--surface-variant); | |
| border-radius: 12px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 14px; | |
| font-weight: 500; | |
| color: var(--on-surface-variant); | |
| cursor: pointer; | |
| transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .pad::before { | |
| content: ''; | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| width: 0; | |
| height: 0; | |
| background: var(--primary); | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| transition: width 0.3s, height 0.3s; | |
| } | |
| .pad:hover { | |
| transform: scale(0.95); | |
| } | |
| .pad.active { | |
| background: var(--primary); | |
| color: var(--on-primary); | |
| transform: scale(0.92); | |
| } | |
| .pad.active::before { | |
| width: 100px; | |
| height: 100px; | |
| } | |
| /* Keyboard Renderer */ | |
| .keyboard-renderer { | |
| padding: 20px 0; | |
| overflow-x: auto; | |
| -webkit-overflow-scrolling: touch; | |
| } | |
| .keyboard { | |
| display: flex; | |
| min-width: 400px; | |
| height: 120px; | |
| position: relative; | |
| background: var(--surface-variant); | |
| border-radius: 8px; | |
| padding: 8px; | |
| } | |
| .key { | |
| flex: 1; | |
| background: var(--surface); | |
| border: 1px solid var(--outline); | |
| border-radius: 0 0 4px 4px; | |
| cursor: pointer; | |
| transition: all 0.1s; | |
| position: relative; | |
| margin: 0 1px; | |
| } | |
| .key:hover { | |
| background: var(--surface-variant); | |
| } | |
| .key.active { | |
| background: var(--primary); | |
| transform: translateY(2px); | |
| } | |
| .key.black { | |
| position: absolute; | |
| width: 60%; | |
| height: 65%; | |
| background: var(--on-surface); | |
| z-index: 2; | |
| margin: 0 -30%; | |
| } | |
| .key.black.active { | |
| background: var(--primary-variant); | |
| } | |
| /* Continuous Pitch Renderer */ | |
| .continuous-pitch-renderer { | |
| height: 300px; | |
| background: var(--surface-variant); | |
| border-radius: 16px; | |
| position: relative; | |
| overflow: hidden; | |
| cursor: crosshair; | |
| touch-action: none; | |
| } | |
| .pitch-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient( | |
| to bottom, | |
| rgba(103, 80, 164, 0.1) 0%, | |
| transparent 50%, | |
| rgba(103, 80, 164, 0.1) 100% | |
| ); | |
| } | |
| .pitch-indicator { | |
| position: absolute; | |
| width: 20px; | |
| height: 20px; | |
| background: var(--primary); | |
| border-radius: 50%; | |
| transform: translate(-50%, -50%); | |
| pointer-events: none; | |
| box-shadow: 0 0 20px rgba(103, 80, 164, 0.5); | |
| display: none; | |
| } | |
| .pitch-indicator.active { | |
| display: block; | |
| animation: pulseIndicator 1s infinite; | |
| } | |
| @keyframes pulseIndicator { | |
| 0%, 100% { transform: translate(-50%, -50%) scale(1); } | |
| 50% { transform: translate(-50%, -50%) scale(1.2); } | |
| } | |
| .pitch-grid { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| display: grid; | |
| grid-template-columns: repeat(12, 1fr); | |
| grid-template-rows: repeat(8, 1fr); | |
| } | |
| .pitch-grid-line { | |
| border: 1px solid rgba(0, 0, 0, 0.05); | |
| } | |
| /* Biosensor Panel */ | |
| .biosensor-panel { | |
| background: var(--surface); | |
| border-radius: 16px; | |
| padding: 20px; | |
| margin-top: 16px; | |
| box-shadow: var(--elevation-1); | |
| } | |
| .biosensor-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| margin-bottom: 16px; | |
| } | |
| .biosensor-title { | |
| font-size: 16px; | |
| font-weight: 600; | |
| } | |
| .biosensor-controls { | |
| display: grid; | |
| grid-template-columns: repeat(2, 1fr); | |
| gap: 12px; | |
| } | |
| .biosensor-control { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .biosensor-label { | |
| font-size: 12px; | |
| color: var(--on-surface-variant); | |
| font-weight: 500; | |
| } | |
| .biosensor-slider { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| height: 4px; | |
| background: var(--surface-variant); | |
| border-radius: 2px; | |
| outline: none; | |
| } | |
| .biosensor-slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| background: var(--primary); | |
| border-radius: 50%; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .biosensor-slider::-webkit-slider-thumb:hover { | |
| transform: scale(1.2); | |
| } | |
| .biosensor-value { | |
| font-size: 14px; | |
| font-weight: 500; | |
| color: var(--primary); | |
| } | |
| /* Effects Panel */ | |
| .effects-panel { | |
| background: var(--surface); | |
| border-radius: 16px; | |
| padding: 20px; | |
| margin-top: 16px; | |
| box-shadow: var(--elevation-1); | |
| } | |
| .effects-grid { | |
| display: flex; | |
| gap: 8px; | |
| flex-wrap: wrap; | |
| margin-top: 12px; | |
| } | |
| .effect-chip { | |
| background: var(--surface-variant); | |
| padding: 8px 16px; | |
| border-radius: 100px; | |
| font-size: 14px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| border: 2px solid transparent; | |
| } | |
| .effect-chip:hover { | |
| border-color: var(--primary); | |
| } | |
| .effect-chip.active { | |
| background: var(--primary); | |
| color: var(--on-primary); | |
| } | |
| /* Bottom Navigation */ | |
| .bottom-nav { | |
| position: fixed; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| background: var(--surface); | |
| box-shadow: 0 -2px 8px var(--shadow); | |
| display: flex; | |
| justify-content: space-around; | |
| padding: 8px 0; | |
| z-index: 100; | |
| } | |
| .nav-item { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 4px; | |
| padding: 8px 16px; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| border-radius: 12px; | |
| } | |
| .nav-item:hover { | |
| background: var(--surface-variant); | |
| } | |
| .nav-item.active { | |
| color: var(--primary); | |
| } | |
| .nav-item .material-icons { | |
| font-size: 24px; | |
| } | |
| .nav-label { | |
| font-size: 12px; | |
| font-weight: 500; | |
| } | |
| /* Tutorial Overlay */ | |
| .tutorial-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: rgba(0, 0, 0, 0.8); | |
| display: none; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 1000; | |
| padding: 20px; | |
| } | |
| .tutorial-overlay.active { | |
| display: flex; | |
| } | |
| .tutorial-card { | |
| background: var(--surface); | |
| border-radius: 24px; | |
| padding: 32px; | |
| max-width: 400px; | |
| width: 100%; | |
| text-align: center; | |
| animation: tutorialSlide 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| @keyframes tutorialSlide { | |
| from { opacity: 0; transform: scale(0.9) translateY(20px); } | |
| to { opacity: 1; transform: scale(1) translateY(0); } | |
| } | |
| .tutorial-icon { | |
| font-size: 64px; | |
| color: var(--primary); | |
| margin-bottom: 16px; | |
| } | |
| .tutorial-title { | |
| font-size: 24px; | |
| font-weight: 600; | |
| margin-bottom: 12px; | |
| } | |
| .tutorial-text { | |
| color: var(--on-surface-variant); | |
| line-height: 1.5; | |
| margin-bottom: 24px; | |
| } | |
| .tutorial-button { | |
| background: var(--primary); | |
| color: var(--on-primary); | |
| border: none; | |
| padding: 16px 32px; | |
| border-radius: 100px; | |
| font-size: 16px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .tutorial-button:hover { | |
| background: var(--primary-variant); | |
| } | |
| /* Responsive Design */ | |
| @media (max-width: 480px) { | |
| .header-title { | |
| font-size: 18px; | |
| } | |
| .main-content { | |
| padding: 12px; | |
| } | |
| .detection-result { | |
| padding: 16px; | |
| } | |
| .biosensor-controls { | |
| grid-template-columns: 1fr; | |
| } | |
| .effects-grid { | |
| gap: 6px; | |
| } | |
| .effect-chip { | |
| padding: 6px 12px; | |
| font-size: 12px; | |
| } | |
| } | |
| /* Loading States */ | |
| .loading-skeleton { | |
| background: linear-gradient(90deg, var(--surface-variant) 25%, var(--surface) 50%, var(--surface-variant) 75%); | |
| background-size: 200% 100%; | |
| animation: loading 1.5s infinite; | |
| border-radius: 8px; | |
| } | |
| @keyframes loading { | |
| 0% { background-position: 200% 0; } | |
| 100% { background-position: -200% 0; } | |
| } | |
| /* Touch Feedback */ | |
| .touch-ripple { | |
| position: absolute; | |
| border-radius: 50%; | |
| background: rgba(103, 80, 164, 0.3); | |
| transform: scale(0); | |
| animation: ripple 0.6s ease-out; | |
| pointer-events: none; | |
| } | |
| @keyframes ripple { | |
| to { | |
| transform: scale(4); | |
| opacity: 0; | |
| } | |
| } | |
| /* Built with Badge */ | |
| .built-with { | |
| position: fixed; | |
| bottom: 80px; | |
| right: 16px; | |
| background: var(--primary); | |
| color: var(--on-primary); | |
| padding: 8px 12px; | |
| border-radius: 100px; | |
| font-size: 12px; | |
| text-decoration: none; | |
| box-shadow: var(--elevation-2); | |
| z-index: 99; | |
| transition: all 0.2s; | |
| } | |
| .built-with:hover { | |
| background: var(--primary-variant); | |
| transform: scale(1.05); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app-container"> | |
| <!-- Header --> | |
| <header class="header"> | |
| <div class="header-title"> | |
| <span class="material-icons">music_note</span> | |
| InstrumentFromPhoto | |
| </div> | |
| <div class="header-actions"> | |
| <button class="icon-button" onclick="toggleTutorial()"> | |
| <span class="material-icons">help_outline</span> | |
| </button> | |
| <button class="icon-button" onclick="toggleSettings()"> | |
| <span class="material-icons">settings</span> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="main-content"> | |
| <!-- Camera Section --> | |
| <section class="camera-section active" id="camera-section"> | |
| <div class="camera-container"> | |
| <div id="camera-view"> | |
| <div class="camera-placeholder"> | |
| <span class="material-icons">camera_alt</span> | |
| <h3>Point camera at an instrument</h3> | |
| <p>AI will identify and create a playable version</p> | |
| </div> | |
| </div> | |
| <div class="camera-controls"> | |
| <button class="capture-button" onclick="captureInstrument()"> | |
| <span class="material-icons">photo_camera</span> | |
| Capture Instrument | |
| </button> | |
| <button class="capture-button" onclick="demoInstrument()"> | |
| <span class="material-icons">auto_awesome</span> | |
| Demo Mode | |
| </button> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- AI Processing --> | |
| <section class="ai-processing" id="ai-processing"> | |
| <span class="material-icons processing-icon">psychology</span> | |
| <h3>Analyzing Instrument</h3> | |
| <p>AI is identifying and configuring your virtual instrument...</p> | |
| </section> | |
| <!-- Instrument Selection --> | |
| <section class="instrument-selection" id="instrument-selection"> | |
| <div class="detection-result"> | |
| <div class="detection-header"> | |
| <div class="detection-icon"> | |
| <span class="material-icons" id="instrument-icon">guitar</span> | |
| </div> | |
| <div class="detection-info"> | |
| <h3 id="instrument-name">Electric Guitar</h3> | |
| <span class="confidence-badge" id="confidence">91% confidence</span> | |
| </div> | |
| </div> | |
| <p id="instrument-description">6-string fretted instrument with magnetic pickups</p> | |
| <div class="instrument-variants" id="variants"> | |
| <span class="variant-chip" onclick="loadVariant('electric')">Electric</span> | |
| <span class="variant-chip" onclick="loadVariant('acoustic')">Acoustic</span> | |
| <span class="variant-chip" onclick="loadVariant('bass')">Bass</span> | |
| </div> | |
| </div> | |
| <!-- Instrument Playground --> | |
| <div class="instrument-playground"> | |
| <div class="playground-header"> | |
| <h3 class="playground-title">Virtual Instrument</h3> | |
| <div class="playground-controls"> | |
| <button class="control-button" onclick="toggleEffects()"> | |
| <span class="material-icons">tune</span> | |
| </button> | |
| <button class="control-button" onclick="toggleBiosensors()"> | |
| <span class="material-icons">favorite</span> | |
| </button> | |
| <button class="control-button" onclick="recordPerformance()"> | |
| <span class="material-icons">fiber_manual_record</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div id="instrument-renderer"></div> | |
| </div> | |
| <!-- Effects Panel --> | |
| <div class="effects-panel" id="effects-panel" style="display: none;"> | |
| <div class="effects-header"> | |
| <h4>Audio Effects</h4> | |
| </div> | |
| <div class="effects-grid"> | |
| <span class="effect-chip" onclick="toggleEffect('distortion')">Distortion</span> | |
| <span class="effect-chip" onclick="toggleEffect('delay')">Delay</span> | |
| <span class="effect-chip" onclick="toggleEffect('reverb')">Reverb</span> | |
| <span class="effect-chip" onclick="toggleEffect('chorus')">Chorus</span> | |
| <span class="effect-chip" onclick="toggleEffect('phaser')">Phaser</span> | |
| <span class="effect-chip" onclick="toggleEffect('flanger')">Flanger</span> | |
| </div> | |
| </div> | |
| <!-- Biosensor Panel --> | |
| <div class="biosensor-panel" id="biosensor-panel" style="display: none;"> | |
| <div class="biosensor-header"> | |
| <span class="material-icons">sensors</span> | |
| <h4 class="biosensor-title">Biosensor Controls</h4> | |
| </div> | |
| <div class="biosensor-controls"> | |
| <div class="biosensor-control"> | |
| <label class="biosensor-label">EEG Alpha (Filter)</label> | |
| <input type="range" class="biosensor-slider" id="eeg-alpha" min="0" max="127" value="64"> | |
| <span class="biosensor-value" id="eeg-alpha-value">64</span> | |
| </div> | |
| <div class="biosensor-control"> | |
| <label class="biosensor-label">Heart Rate (Tempo)</label> | |
| <input type="range" class="biosensor-slider" id="heart-rate" min="60" max="180" value="120"> | |
| <span class="biosensor-value" id="heart-rate-value">120 BPM</span> | |
| </div> | |
| <div class="biosensor-control"> | |
| <label class="biosensor-label">Breath (Volume)</label> | |
| <input type="range" class="biosensor-slider" id="breath" min="0" max="127" value="100"> | |
| <span class="biosensor-value" id="breath-value">100</span> | |
| </div> | |
| <div class="biosensor-control"> | |
| <label class="biosensor-label">Motion (Modulation)</label> | |
| <input type="range" class="biosensor-slider" id="motion" min="0" max="127" value="64"> | |
| <span class="biosensor-value" id="motion-value">64</span> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| </main> | |
| <!-- Bottom Navigation --> | |
| <nav class="bottom-nav"> | |
| <div class="nav-item active" onclick="showSection('camera')"> | |
| <span class="material-icons">camera_alt</span> | |
| <span class="nav-label">Capture</span> | |
| </div> | |
| <div class="nav-item" onclick="showSection('library')"> | |
| <span class="material-icons">library_music</span> | |
| <span class="nav-label">Library</span> | |
| </div> | |
| <div class="nav-item" onclick="showSection('create')"> | |
| <span class="material-icons">piano</span> | |
| <span class="nav-label">Create</span> | |
| </div> | |
| <div class="nav-item" onclick="showSection('profile')"> | |
| <span class="material-icons">person</span> | |
| <span class="nav-label">Profile</span> | |
| </div> | |
| </nav> | |
| <!-- Tutorial Overlay --> | |
| <div class="tutorial-overlay" id="tutorial-overlay"> | |
| <div class="tutorial-card"> | |
| <span class="material-icons tutorial-icon">music_note</span> | |
| <h2 class="tutorial-title">Welcome to InstrumentFromPhoto</h2> | |
| <p class="tutorial-text">Point your camera at any musical instrument and watch as AI creates a playable virtual version instantly!</p> | |
| <button class="tutorial-button" onclick="closeTutorial()">Get Started</button> | |
| </div> | |
| </div> | |
| <!-- Built with Badge --> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="built-with"> | |
| Built with anycoder | |
| </a> | |
| </div> | |
| <script> | |
| // Audio Context and Sound Engine | |
| let audioContext; | |
| let masterGainNode; | |
| let currentInstrument = null; | |
| let activeNotes = new Map(); | |
| let biosensorValues = { | |
| eegAlpha: 64, | |
| heartRate: 120, | |
| breath: 100, | |
| motion: 64 | |
| }; | |
| // Initialize Audio Context | |
| function initAudio() { | |
| if (!audioContext) { | |
| audioContext = new (window.AudioContext || window.webkitAudioContext)(); | |
| masterGainNode = audioContext.createGain(); | |
| masterGainNode.connect(audioContext.destination); | |
| masterGainNode.gain.value = 0.7; | |
| } | |
| } | |
| // Sound Generation Functions | |
| function playNote(frequency, type = 'sine', duration = 0.5) { | |
| initAudio(); | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.type = type; | |
| oscillator.frequency.value = frequency; | |
| // Apply biosensor modulation | |
| const breathModulation = biosensorValues.breath / 127; | |
| gainNode.gain.value = breathModulation * 0.3; | |
| // ADSR envelope | |
| const now = audioContext.currentTime; | |
| gainNode.gain.setValueAtTime(0, now); | |
| gainNode.gain.linearRampToValueAtTime(breathModulation * 0.3, now + 0.01); | |
| gainNode.gain.exponentialRampToValueAtTime(0.001, now + duration); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(masterGainNode); | |
| oscillator.start(now); | |
| oscillator.stop(now + duration); | |
| return { oscillator, gainNode }; | |
| } | |
| function playDrum(type = 'kick') { | |
| initAudio(); | |
| const now = audioContext.currentTime; | |
| if (type === 'kick') { | |
| const oscillator = audioContext.createOscillator(); | |
| const gainNode = audioContext.createGain(); | |
| oscillator.frequency.setValueAtTime(150, now); | |
| oscillator.frequency.exponentialRampToValueAtTime(0.01, now + 0.5); | |
| gainNode.gain.setValueAtTime(1, now); | |
| gainNode.gain.exponentialRampToValueAtTime(0.01, now + 0.5); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(masterGainNode); | |
| oscillator.start(now); | |
| oscillator.stop(now + 0.5); | |
| } else if (type === 'snare') { | |
| const noiseBuffer = audioContext.createBuffer(1, audioContext.sampleRate * 0.2, audioContext.sampleRate); | |
| const output = noiseBuffer.getChannelData(0); | |
| for (let i = 0; i < noiseBuffer.length; i++) { | |
| output[i] = Math.random() * 2 - 1; | |
| } | |
| const noiseSource = audioContext.createBufferSource(); | |
| noiseSource.buffer = noiseBuffer; | |
| const noiseFilter = audioContext.createBiquadFilter(); | |
| noiseFilter.type = 'highpass'; | |
| noiseFilter.frequency.value = 1000; | |
| const noiseGain = audioContext.createGain(); | |
| noiseGain.gain.setValueAtTime(1, now); | |
| noiseGain.gain.exponentialRampToValueAtTime(0.01, now + 0.2); | |
| noiseSource.connect(noiseFilter); | |
| noiseFilter.connect(noiseGain); | |
| noiseGain.connect(masterGainNode); | |
| noiseSource.start(now); | |
| } else if (type === 'hihat') { | |
| const noiseBuffer = audioContext.createBuffer(1, audioContext.sampleRate * 0.05, audioContext.sampleRate); | |
| const output = noiseBuffer.getChannelData(0); | |
| for (let i = 0; i < noiseBuffer.length; i++) { | |
| output[i] = Math.random() * 2 - 1; | |
| } | |
| const noiseSource = audioContext.createBufferSource(); | |
| noiseSource.buffer = noiseBuffer; | |
| const noiseFilter = audioContext.createBiquadFilter(); | |
| noiseFilter.type = 'highpass'; | |
| noiseFilter.frequency.value = 5000; | |
| const noiseGain = audioContext.createGain(); | |
| noiseGain.gain.setValueAtTime(0.3, now); |