1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>Free Song Key Finder & Camelot Wheel | VocalRemovers.ai</title> 7 <meta name="description" content="Find the key of any song online for free. Detect musical key, scale, Camelot code and BPM from audio, explore the Camelot Wheel, or use real-time microphone pitch detection." /> 8 <meta name="keywords" content="key finder, song key finder, bpm finder, detect song key and bpm, camelot wheel, camelot key finder, dj harmonic mixing, find key of song, audio key detector, song tempo detector, beats per minute, pitch detector, circle of fifths, scale finder, root note detector" /> <link rel="shortcut icon" type="image/x-icon" href="/assets/favicon.ico?v=1.0"> 9 <link rel="canonical" href="https://vocalremovers.ai/key-finder" /> 10 11 <!-- Open Graph / Social SEO --> 12 <meta property="og:type" content="website" /> 13 <meta property="og:url" content="https://vocalremovers.ai/key-finder" /> 14 <meta property="og:title" content="Free Online Song Key & BPM Finder | VocalRemovers.ai" /> 15 <meta property="og:description" content="Detect musical key, tempo (BPM), scale, and Camelot code of any song in seconds with 100% private in-browser chromagram analysis." /> 16 <meta property="og:image" content="https://vocalremovers.ai/assets/preview-key.png" /> 17 18 <!-- Twitter Card --> 19 <meta name="twitter:card" content="summary_large_image" /> 20 <meta name="twitter:title" content="Free Online Song Key & BPM Finder" /> 21 <meta name="twitter:description" content="Find the musical key, tempo (BPM), and Camelot code of any track with interactive harmonic mixing tools." /> 22 23 <!-- JSON-LD Structured Data for Software Application & FAQ --> 24
24<script type="application/ld+json"> 25 { 26 "@context": "https://schema.org", 27 "@graph": [ 28 { 29 "@type": "WebApplication", 30 "name": "Free Online Song Key & BPM Finder", 31 "url": "https://vocalremovers.ai/key-finder", 32 "description": "In-browser musical key detector, tempo (BPM) counter, Camelot DJ wheel analyzer, 12-class chromagram visualizer, interactive piano keyboard, and live microphone pitch tracker.", 33 "applicationCategory": "MultimediaApplication", 34 "operatingSystem": "All", 35 "browserRequirements": "Requires Web Audio API support", 36 "offers": { 37 "@type": "Offer", 38 "price": "0", 39 "priceCurrency": "USD" 40 }, 41 "featureList": [ 42 "Song musical key and scale detection", 43 "BPM tempo detection with half- and double-time options", 44 "Camelot DJ Wheel code and harmonic mixing matrix", 45 "12-class pitch chromagram spectrum analyzer", 46 "Interactive SVG Camelot Wheel explorer", 47 "Virtual piano keyboard with scale degree indicators", 48 "Real-time acoustic microphone pitch & key listener", 49 "Audio playback scrubber with reference root note generator", 50 "100% in-browser Web Audio privacy" 51 ] 52 }, 53 { 54 "@type": "FAQPage", 55 "mainEntity": [ 56 { 57 "@type": "Question", 58 "name": "How does this online key finder detect the musical key of a song?", 59 "acceptedAnswer": { 60 "@type": "Answer", 61 "text": "The analyzer extracts pitch-related information from the audio and compares the resulting chroma distribution with musical key profiles to estimate the most likely tonal center and scale." 62 } 63 }, 64 { 65 "@type": "Question", 66 "name": "What is the Camelot Wheel and how do DJs use it for harmonic mixing?", 67 "acceptedAnswer": { 68 "@type": "Answer", 69 "text": "The Camelot Wheel simplifies musical keys into a 12-hour clock face. Numbers 1 to 12 represent pitch classes, while A denotes Minor keys and B denotes Major keys. Mixing tracks with the same number or adjacent hours (+/- 1) is commonly used as a starting point for harmonic mixing." 70 } 71 }, 72 { 73 "@type": "Question", 74 "name": "How do I use the interactive Camelot Wheel explorer?", 75 "acceptedAnswer": { 76 "@type": "Answer", 77 "text": "Click the Camelot Wheel & Piano tab and select any key on the visual wheel. The tool immediately highlights compatible transition keysâincluding Exact Match, Energy Boost (+1, +2), Energy Drop (-1), and Relative Major/Minor swapsâalongside the notes on the virtual piano." 78 } 79 }, 80 { 81 "@type": "Question", 82 "name": "How accurate is the browser-based key detector?", 83 "acceptedAnswer": { 84 "@type": "Answer", 85 "text": "Our algorithm analyzes the entire duration of the audio across 40+ hops, filtering out noisy percussion transients to focus on sustained harmonic chords and vocals. Online key finders can provide a useful estimate across diverse genres including EDM, Pop, Rock, Hip Hop, and Classical." 86 } 87 }, 88 { 89 "@type": "Question", 90 "name": "Can I hear the root tonic note to test or tune my instrument?", 91 "acceptedAnswer": { 92 "@type": "Answer", 93 "text": "Yes! Once a key is detected or selected, click the Play Root Note Tone button to hear a synthesized pure acoustic reference pitch corresponding to the detected key's root tonic." 94 } 95 }, 96 { 97 "@type": "Question", 98 "name": "How does the real-time microphone pitch detector work?", 99 "acceptedAnswer": { 100 "@type": "Answer", 101 "text": "Switch to the Real-Time Mic tab and click Start Listening. Sing, hum, or play an acoustic instrument into your microphone. Our autocorrelation algorithm detects your fund
101amental pitch in real time, displays live notes and frequencies in Hz, and estimates the prevailing key over time." 102 } 103 }, 104 { 105 "@type": "Question", 106 "name": "What is the difference between relative and parallel keys?", 107 "acceptedAnswer": { 108 "@type": "Answer", 109 "text": "Relative keys (e.g. C Major and A Minor, 8B and 8A) share identical key signatures and scale notes, making them closely related for harmonic mixing. Parallel keys (e.g. C Major and C Minor) share the same root tonic note (C) but differ in their 3rd, 6th, and 7th intervals, producing a bold, dramatic emotional contrast." 110 } 111 }, 112 { 113 "@type": "Question", 114 "name": "What audio formats are supported for key detection?", 115 "acceptedAnswer": { 116 "@type": "Answer", 117 "text": "The Key Finder supports all popular audio formats: MP3, WAV, FLAC, M4A, AAC, OGG, OPUS, and AIFF. Files up to 100 MB can be analyzed directly in your browser." 118 } 119 }, 120 { 121 "@type": "Question", 122 "name": "Are my audio files uploaded or stored on your servers?", 123 "acceptedAnswer": { 124 "@type": "Answer", 125 "text": "No. All processingâaudio decoding, FFT spectral analysis, chroma extraction, and live microphone captureâruns 100% in your browser. Your tracks never leave your computer or device." 126 } 127 }, 128 { 129 "@type": "Question", 130 "name": "Can I jump from the Key Finder to the Vocal Remover or BPM Finder with my song preloaded?", 131 "acceptedAnswer": { 132 "@type": "Answer", 133 "text": "Yes! Under the \"Next Steps with this Track\" section, click any tool (such as our <a href=\"/bpm-finder/\">BPM finder</a> to calculate tempo or Remove Vocals) to instantly transfer your audio and continue processing." 134 } 135 }, 136 { 137 "@type": "Question", 138 "name": "How can I find the key of an MP3?", 139 "acceptedAnswer": { 140 "@type": "Answer", 141 "text": "Upload your MP3 to the Key Finder and let the audio analyzer process the track. It will estimate the musical key and scale, and show the corresponding Camelot code along with related key information." 142 } 143 }, 144 { 145 "@type": "Question", 146 "name": "What is the difference between musical key and scale?", 147 "acceptedAnswer": { 148 "@type": "Answer", 149 "text": "A scale is a specific sequence of notes, such as C major or A minor. A musical key describes the tonal center of a song and the scale built around it. For example, a song in the key of C major uses the C major scale with C as its tonal center." 150 } 151 }, 152 { 153 "@type": "Question", 154 "name": "What is a Camelot code?", 155 "acceptedAnswer": { 156 "@type": "Answer", 157 "text": "A Camelot code is a DJ-friendly way of representing musical keys for harmonic mixing. The system uses numbers from 1 to 12 combined with A or B to represent minor and major keys. For example, 8A represents A minor and 8B represents C major." 158 } 159 }, 160 { 161 "@type": "Question", 162 "name": "What does 8A mean on the Camelot Wheel?", 163 "acceptedAnswer": { 164 "@type": "Answer", 165 "text": "8A represents A minor on the Camelot Wheel. Its relative major key is C major (8B). DJs can use Camelot codes to quickly identify related keys when planning harmonic transitions between tracks." 166 } 167 }, 168 { 169 "@type": "Question", 170 "name": "How accurate is an online song key finder?", 171 "acceptedAnswer": { 172 "@type": "Answer", 173 "text": "Online key finders can provide a useful estimate of a track's musical key, but results are not guaranteed to be correct for every recording. Accuracy can vary depending on the arrangement, instrumentation, tuning, audio quality, modulation, and other musical characteristics of the track." 174 } 175 }, 176 { 177 "@type": "Question", 178 "name": "Can a song change key?",
179 "acceptedAnswer": { 180 "@type": "Answer", 181 "text": "Yes. Some songs change key during the arrangement. A key change, also called modulation, can occur between sections such as a verse and chorus or near the end of a song. If different sections have different tonal centers, an analyzer may identify the key that is most prominent in the analyzed audio." 182 } 183 }, 184 { 185 "@type": "Question", 186 "name": "Can I find the key of a song without downloading software?", 187 "acceptedAnswer": { 188 "@type": "Answer", 189 "text": "Yes. You can use an online song key finder directly in your web browser. Upload a supported audio file and the analyzer can estimate its musical key, scale, and Camelot code without requiring a separate desktop application." 190 } 191 }, 192 { 193 "@type": "Question", 194 "name": "Can I use the key finder on my phone?", 195 "acceptedAnswer": { 196 "@type": "Answer", 197 "text": "Yes. The Key Finder can be used on compatible mobile browsers that support the required Web Audio features. Upload a supported audio file or use the microphone mode to analyze pitch directly from your phone." 198 } 199 }, 200 { 201 "@type": "Question", 202 "name": "Does changing the BPM change the musical key?", 203 "acceptedAnswer": { 204 "@type": "Answer", 205 "text": "Not necessarily. Changing the playback speed can affect the pitch if the audio is simply sped up or slowed down. However, modern time-stretching and pitch-preserving processing can change the BPM while keeping the musical key approximately the same. If you want to transpose the key of a track, you can use our <a href=\"/pitch-changer/\">pitch changer</a>." 206 } 207 }, 208 { 209 "@type": "Question", 210 "name": "What is the difference between major and minor keys?", 211 "acceptedAnswer": { 212 "@type": "Answer", 213 "text": "Major and minor keys have different tonal characteristics and scale structures. Major keys are often associated with brighter or more uplifting musical qualities, while minor keys are often associated with darker or more introspective qualities. The actual mood also depends on the melody, harmony, rhythm, instrumentation, and arrangement." 214 } 215 } 216 ] 217 } 218 ] 219 } 220 </script>
220 221 <!-- Preconnect & Google Fonts --> 222 <link rel="preconnect" href="https://fonts.googleapis.com" /> 223 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> 224 <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700;800&display=swap" rel="stylesheet" /> 225 226 <!-- Font Awesome 6 Free Icons --> 227 <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css" /> 228 229 <!-- Tailwind CSS CDN --> 230
230<script src="https://cdn.tailwindcss.com"></script>
230 231
231<script> 232 tailwind.config = { 233 theme: { 234 extend: { 235 fontFamily: { 236 sans: ["Inter", "sans-serif"], 237 outfit: ["Outfit", "sans-serif"] 238 }, 239 boxShadow: { 240 "neon-indigo": "0 0 20px rgba(99, 102, 241, 0.15)", 241 "neon-purple": "0 0 20px rgba(168, 85, 247, 0.15)", 242 "glass-hover": "0 20px 40px rgba(0, 0, 0, 0.3)" 243 } 244 } 245 } 246 }; 247 </script>
247 248 249 <!-- Custom Styling for Interactive Elements --> 250 <style> 251 body { 252 background-color: #080a13; 253 background-image: 254 radial-gradient(circle at 15% 25%, rgba(99, 102, 241, 0.08) 0%, transparent 40%), 255 radial-gradient(circle at 85% 75%, rgba(168, 85, 247, 0.08) 0%, transparent 40%); 256 background-attachment: fixed; 257 } 258 259 .glass-card { 260 background: linear-gradient(135deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); 261 backdrop-filter: blur(16px); 262 -webkit-backdrop-filter: blur(16px); 263 border: 1px solid rgba(255, 255, 255, 0.06); 264 } 265 266 .glass-card-hover:hover { 267 background: linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.02) 100%); 268 border-color: rgba(129, 140, 248, 0.3); 269 box-shadow: 0 20px 45px rgba(99, 102, 241, 0.15); 270 } 271 272 .header-gradient { 273 background: linear-gradient(90deg, #4154a1 0%, #614d9f 100%); 274 } 275 276 /* Opaque Mobile Navigation Menu */ 277 #mobileMenu { 278 background-color: #0b1021 !important; 279 opacity: 1 !important; 280 } 281 282 /* Subtle animation for background glows */ 283 @keyframes pulse-slow { 284 0%, 100% { transform: scale(1); opacity: 0.8; } 285 50% { transform: scale(1.05); opacity: 1; } 286 } 287 .animate-glow { 288 animation: pulse-slow 8s ease-in-out infinite; 289 } 290 </style> 291</head> 292<body class="text-slate-100 font-sans antialiased min-h-screen flex flex-col pt-20"> 293 294 <!-- ================= HEADER NAVIGATION ================= --> 295 <!-- ================= HEADER SECTION ================= --> 296<header class="header-gradient w-full shadow-lg fixed top-0 left-0 right-0 z-50"> 297 <div class="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between"> 298 299 <!-- Logo Container --> 300 <a href="/" class="flex items-center gap-3.5 group select-none cursor-pointer" id="mainlogo"> 301 <!-- Logo Icon: Rounded rectangle with a music note --> 302 <div class="h-10 w-10 flex items-center justify-center rounded-xl bg-white/15 border border-white/10 shadow-inner group-hover:scale-105 transition-transform duration-200"> 303 <svg class="h-5 w-5 text-white" viewBox="0 0 24 24" fill="currentColor"> 304 <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/> 305 </svg> 306 </div> 307 <!-- Logo Text --> 308 <span class="font-outfit text-xl font-bold tracking-tight text-white flex items-center gap-1.5"> 309 AI Vocal <span class="text-[#c084fc] drop-shadow-sm">Remover</span> 310 </span> 311 </a> 312 313 <!-- Desktop Navigation Menu --> 314 <nav class="hidden md:flex items-center gap-8 lg:gap-10"> 315 <div class="relative group/nav py-2"> 316 <button class="text-sm font-semibold tracking-wide text-white/90 group-hover/nav:text-white flex items-center gap-1.5 transition-colors duration-200 cursor-pointer"> 317 TOOLS <i class="fas fa-chevron-down text-[10px] opacity-70 group-hover/nav:rotate-180 transition-transform duration-200"></i> 318 </button> 319 <div class="absolute top-full left-1/2 -translate-x-1/2 pt-2 hidden group-hover/nav:block z-50"> 320 <div class="w-56 bg-slate-950/95 border border-white/10 rounded-2xl shadow-2xl p-2 backdrop-blur-xl flex flex-col gap-1"> 321 <a href="/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 322 <i class="fa-solid fa-microphone-slash text-purple-400 w-4"></i> Vocal Remover 323 </a> 324 <a href="/bpm-finder/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 325 <i class="fa-solid fa-drum text-indigo-400 w-4"></i> BPM & Key Finder 326 </a> 327 <a href="/key-finder/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 328 <i class="fa-solid fa-compact-disc text-[#c084fc] w-4"></i> Key Finder & Camelot 329 </a> 330 <a href="/voice-recorder/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 331 <i class="fa-solid fa-microphone-lines text-rose-400 w-4"></i> Voice Recorder 332 </a> 333 <a href="/audio-cutter/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 334 <i class="fa-solid fa-scissors text-blue-400 w-4"></i> Audio Cutter 335 </a> 336 <a href="/audio-joiner/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 337 <i class="fa-solid fa-layer-group text-emerald-400 w-4"></i> Audio Joiner 338 </a> 339 <a href="/audio-converter/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 340 <i class="fa-solid fa-arrows-rotate text-cyan-400 w-4"></i> Audio Converter 341 </a> 342 <a href="/equalizer/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 343 <i class="fa-solid fa-sliders text-teal-400 w-4"></i> Audio Equalizer 344 </a> 345 <a href="/pitch-changer/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 346 <i class="fa-solid fa-music text-cyan-400 w-4"></i> Pitch Changer 347 </a> 348 <a href="/bass-booster/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 349 <i class="fa-solid fa-volume-high text-cyan-400 w-4"></i> Bass Booster 350 </a> 351 <a href="/noise-reducer/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 352 <i class="fa-solid fa-wand-magic-sparkles text-emerald-400 w-4"></i> Noise Reducer 353 </a> 354 <a href="/audio-effects/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 355 <i class="fa-solid fa-wand-magic text-violet-400 w-4"></i> Audio Effects 356 </a> 357 <a href="/mp3-tag-editor/" class="px-3.5 py-2.5 rounded-xl text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-2.5 transition-all"> 358 <i class="fa-solid fa-tags text-amber-400 w-4"></i> MP3 Tag Editor 359 </a> 360 </div> 361 </div> 362 </div> 363 <a href="/#features" class="text-sm font-semibold tracking-wide text-white/90 hover:text-white transition-colors duration-200">FEATURES</a> 364 <a href="/#howitworks" class="text-sm font-semibold tracking-wide text-white/90 hover:text-white transition-colors duration-200">HOW IT WORKS</a> 365 <a href="/#faqs" class="text-sm font-semibold tracking-wide text-white/90 hover:text-white transition-colors duration-200">FAQS</a> 366 <a href="/contact/" class="text-sm font-semibold tracking-wide text-white/90 hover:text-white transition-colors duration-200">CONTACT</a> 367 </nav> 368 369 <!-- Mobile Hamburger Button --> 370 <button id="menuToggle" class="md:hidden p-2 rounded-lg text-white hover:bg-white/10 transition-colors focus:outline-none" aria-label="Toggle navigation menu"> 371 <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 372 <path id="menuIcon" stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h18M4 18h18" /> 373 </svg> 374 </button> 375 376 </div> 377 378 <!-- Mobile Dropdown Menu --> 379 <div id="mobileMenu" class="hidden md:hidden absolute top-full left-0 w-full bg-[#0b1021] border-t border-white/10 shadow-2xl transition-all duration-200 ease-in-out max-h-[calc(100vh-70px)] overflow-y-auto custom-scrollbar"> 380 <div class="px-4 py-3 space-y-2"> 381 382 <!-- Section: Audio Tools --> 383 <div class="flex flex-col gap-1"> 384 <a href="/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 385 <i class="fa-solid fa-microphone-slash text-purple-400 w-
3855 text-center text-sm"></i> Vocal Remover 386 </a> 387 <a href="/bpm-finder/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 388 <i class="fa-solid fa-drum text-indigo-400 w-5 text-center text-sm"></i> BPM & Key Finder 389 </a> 390 <a href="/key-finder/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 391 <i class="fa-solid fa-compact-disc text-[#c084fc] w-5 text-center text-sm"></i> Key Finder & Camelot 392 </a> 393 <a href="/voice-recorder/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 394 <i class="fa-solid fa-microphone-lines text-rose-400 w-5 text-center text-sm"></i> Voice Recorder 395 </a> 396 <a href="/audio-cutter/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 397 <i class="fa-solid fa-scissors text-blue-400 w-5 text-center text-sm"></i> Audio Cutter 398 </a> 399 <a href="/audio-joiner/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 400 <i class="fa-solid fa-layer-group text-emerald-400 w-5 text-center text-sm"></i> Audio Joiner 401 </a> 402 <a href="/audio-converter/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 403 <i class="fa-solid fa-arrows-rotate text-cyan-400 w-5 text-center text-sm"></i> Audio Converter 404 </a> 405 <a href="/equalizer/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 406 <i class="fa-solid fa-sliders text-teal-400 w-5 text-center text-sm"></i> Audio Equalizer 407 </a> 408 <a href="/pitch-changer/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 409 <i class="fa-solid fa-music text-cyan-400 w-5 text-center text-sm"></i> Pitch Changer 410 </a> 411 <a href="/bass-booster/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 412 <i class="fa-solid fa-volume-high text-cyan-400 w-5 text-center text-sm"></i> Bass Booster 413 </a> 414 <a href="/noise-reducer/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 415 <i class="fa-solid fa-wand-magic-sparkles text-emerald-400 w-5 text-center text-sm"></i> Noise Reducer 416 </a> 417 <a href="/audio-effects/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 418 <i class="fa-solid fa-wand-magic text-violet-400 w-5 text-center text-sm"></i> Audio Effects 419 </a> 420 <a href="/mp3-tag-editor/" class="px-3.5 py-2.5 rounded-xl text-sm font-semibold text-slate-300 hover:text-white hover:bg-white/5 flex items-center gap-3 transition-all"> 421 <i class="fa-solid fa-tags text-amber-400 w-5 text-center text-sm"></i> MP3 Tag Editor 422 </a> 423 </div> 424 425 <!-- Section Divider --> 426 <div class="h-px bg-white/10"></div> 427 428 <!-- Section: General Navigation --> 429 <nav class="grid grid-cols-2 gap-2 text-xs font-semibold"> 430 <a href="/#features" class="p-2 rounded-lg text-slate-300 hover:text-white hover:bg-white/5 transition-all flex items-center gap-2"> 431 <i class="fa-solid fa-star text-amber-400 text-[10px]"></i> Features 432 </a> 433 <a href="/#howitworks" class="p-2 rounded-lg text-slate-300 hover:text-white hover:bg-white/5 transition-all flex items-center gap-2"> 434 <i class="fa-solid fa-circle-play text-cyan-400 text-[10px]"></i> How It Works 435 </a> 436 <a href="/#faqs" class="p-2 rounded-lg text-slate-300 hover:text-white hover:bg-white/5 transition-all flex items-center gap-2"> 437 <i class="fa-solid fa-circle-question text-purple-400 text-[10px]"></i> FAQs 438 </a> 439 <a href="/contact/" class="p-2 rounded-lg text-slate-300 hover:text-white hover:bg-white/5 transition-all flex items-center gap-2"> 440 <i class="fa-solid fa-envelope text-emerald-400 text-[10px]"></i> Contact 441 </a> 442 </nav> 443 444 </div> 445 </div> 446</header> 447 448<!-- Mobile Hamburger Toggle Script -->
449<script> 450 (function() { 451 function initMobileMenuToggle() { 452 var menuToggle = document.getElementById('menuToggle'); 453 var mobileMenu = document.getElementById('mobileMenu'); 454 var menuIcon = document.getElementById('menuIcon'); 455 if (!menuToggle || !mobileMenu) return; 456 457 menuToggle.onclick = function(e) { 458 e.stopPropagation(); 459 var isClosed = mobileMenu.classList.contains('hidden'); 460 if (isClosed) { 461 mobileMenu.classList.remove('hidden'); 462 if (menuIcon) menuIcon.setAttribute('d', 'M6 18L18 6M6 6l12 12'); 463 } else { 464 mobileMenu.classList.add('hidden'); 465 if (menuIcon) menuIcon.setAttribute('d', 'M4 6h16M4 12h18M4 18h18'); 466 } 467 }; 468 469 // Close on clicking outside 470 document.addEventListener('click', function(e) { 471 if (!mobileMenu.classList.contains('hidden') && !mobileMenu.contains(e.target) && !menuToggle.contains(e.target)) { 472 mobileMenu.classList.add('hidden'); 473 if (menuIcon) menuIcon.setAttribute('d', 'M4 6h16M4 12h18M4 18h18'); 474 } 475 }); 476 477 // Close when clicking any link 478 mobileMenu.querySelectorAll('a').forEach(function(link) { 479 link.addEventListener('click', function() { 480 mobileMenu.classList.add('hidden'); 481 if (menuIcon) menuIcon.setAttribute('d', 'M4 6h16M4 12h18M4 18h18'); 482 }); 483 }); 484 } 485 486 if (document.readyState === 'loading') { 487 document.addEventListener('DOMContentLoaded', initMobileMenuToggle); 488 } else { 489 initMobileMenuToggle(); 490 } 491 })(); 492</script>
492 493 494 <!-- ================= MAIN BODY CONTENT ================= --> 495 496 497 498 499<!-- Custom Styling for Interactive Elements --> 500<style> 501 .mode-tab-btn.active { 502 background: linear-gradient(135deg, rgba(99, 102, 241, 0.25) 0%, rgba(168, 85, 247, 0.25) 100%); 503 border-color: rgba(168, 85, 247, 0.5); 504 box-shadow: 0 0 25px rgba(99, 102, 241, 0.2); 505 } 506 .wheel-wedge { 507 cursor: pointer; 508 transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); 509 } 510 .wheel-wedge:hover { 511 filter: brightness(1.25); 512 transform-origin: center; 513 } 514 .wheel-wedge.active-key { 515 filter: brightness(1.4) drop-shadow(0 0 10px rgba(168, 85, 247, 0.9)); 516 stroke: #ffffff !important; 517 stroke-width: 2.5px !important; 518 } 519 .wheel-wedge.match-same { 520 filter: drop-shadow(0 0 8px rgba(16, 185, 129, 0.8)); 521 } 522 .wheel-wedge.match-relative { 523 filter: drop-shadow(0 0 8px rgba(59, 130, 246, 0.8)); 524 } 525 .wheel-wedge.match-boost { 526 filter: drop-shadow(0 0 8px rgba(236, 72, 153, 0.8)); 527 } 528 .piano-key { 529 transition: all 0.1s ease; 530 cursor: pointer; 531 user-select: none; 532 } 533 .piano-key:active, .piano-key.playing { 534 transform: translateY(2px); 535 } 536 .piano-white.scale-active { 537 background: linear-gradient(180deg, #ffffff 0%, #e0e7ff 70%, #818cf8 100%) !important; 538 border-color: #6366f1 !important; 539 } 540 .piano-black.scale-active { 541 background: linear-gradient(180deg, #1e1b4b 0%, #4338ca 100%) !important; 542 border-color: #a78bfa !important; 543 } 544</style> 545 546<!-- ================= MAIN CONTAINER ================= --> 547<main class="flex-grow max-w-7xl mx-auto px-4 sm:px-6 py-6 md:py-8 relative z-40 w-full flex flex-col items-center"> 548 549 <!-- Mode Switcher Navigation Tabs --> 550 <div class="w-full max-w-4xl mx-auto mb-6 relative z-20"> 551 <div class="grid grid-cols-1 sm:grid-cols-3 gap-2 bg-slate-950/70 p-2 rounded-3xl border border-white/10 shadow-2xl backdrop-blur-xl"> 552 <!-- Tab 1: File Upload --> 553 <button id="tab-btn-upload" class="mode-tab-btn active flex items-center justify-center sm:justify-start gap-3 px-4 py-2.5 sm:py-3 rounded-2xl border border-transparent hover:bg-white/5 transition-all duration-200 cursor-pointer select-none"> 554 <div class="h-10 w-10 rounded-xl bg-indigo-500/20 text-indigo-400 flex items-center justify-center text-lg flex-shrink-0 shadow-inner"> 555 <i class="fa-solid fa-cloud-arrow-up"></i> 556 </div> 557 <div class="text-left"> 558 <span class="block text-sm font-bold text-white font-outfit">Audio File</span> 559 <span class="block text-[11px] text-slate-400">Analyze MP3, WAV, etc.</span> 560 </div> 561 </button> 562 563 <!-- Tab 2: Camelot Wheel & Piano --> 564 <button id="tab-btn-wheel" class="mode-tab-btn flex items-center justify-center sm:justify-start gap-3 px-4 py-2.5 sm:py-3 rounded-2xl border border-transparent hover:bg-white/5 transition-all duration-200 cursor-pointer select-none"> 565 <div class="h-10 w-10 rounded-xl bg-purple-500/20 text-purple-400 flex items-center justify-center text-lg flex-shrink-0 shadow-inner"> 566 <i class="fa-solid fa-compact-disc"></i> 567 </div> 568 <div class="text-left"> 569 <span class="block text-sm font-bold text-white font-outfit">Camelot Wheel & Piano</span> 570 <span class="block text-[11px] text-slate-400">Interactive DJ explorer</span> 571 </div> 572 </button> 573 574 <!-- Tab 3: Real-Time Mic --> 575 <button id="tab-btn-mic" class="mode-tab-btn flex items-center justify-center sm:justify-start gap-3 px-4 py-2.5 sm:py-3 rounded-2xl border border-transparent hover:bg-white/5 transition-all duration-200 cursor-pointer select-none"> 576 <div class="h-10 w-10 rounded-xl bg-emerald-500/20 text-emerald-400 flex items-center justify-center text-lg flex-shrink-0 shadow-inner"> 577 <i class="fa-solid fa-microphone-lines"></i> 578 </div> 579 <div class="text-left">
580 <span class="block text-sm font-bold text-white font-outfit">Real-Time Mic</span> 581 <span class="block text-[11px] text-slate-400">Live hum / acoustic pitch</span> 582 </div> 583 </button> 584 </div> 585 </div> 586 587 <!-- ================= PANEL 1: FILE UPLOADER & KEY WORKSPACE ================= --> 588 <div id="panel-upload" class="mode-panel w-full relative z-30"> 589 590<!-- ================= STYLING FOR UPLOADER COMPONENT ================= --> 591<style> 592 .dragover { 593 border-color: rgba(99, 102, 241, 0.6) !important; 594 background: rgba(99, 102, 241, 0.05) !important; 595 box-shadow: 0 0 35px rgba(99, 102, 241, 0.25) !important; 596 } 597 598 .dropdown-menu::before { 599 content: ''; 600 position: absolute; 601 top: -14px; 602 left: -12px; 603 right: -12px; 604 bottom: -12px; 605 background: transparent; 606 z-index: -1; 607 pointer-events: auto; 608} 609 610 .modal-overlay { 611 position: fixed; 612 top: 0; 613 left: 0; 614 right: 0; 615 bottom: 0; 616 background-color: rgba(0, 0, 0, 0.75); 617 backdrop-filter: blur(12px); 618 display: flex; 619 align-items: center; 620 justify-content: center; 621 z-index: 1000; 622 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 623 } 624 625 .modal-hidden { 626 opacity: 0; 627 pointer-events: none; 628 visibility: hidden; 629 } 630</style> 631 632<!-- ================= HERO & UPLOAD SECTION ================= --> 633<section class="w-full relative z-10 flex flex-col items-center"> 634 635 <!-- Hero Header Info --> 636 <header class="text-center mb-6 sm:mb-8 headerinfo relative max-w-4xl mx-auto px-4" id="uploaderHeader"> 637 <span class="text-xs md:text-sm font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-3"> 638 HARMONIC KEY & CAMELOT WHEEL DETECTOR </span> 639 <h1 class="font-outfit text-3xl sm:text-4xl md:text-5xl font-extrabold text-white tracking-tight leading-tight mb-3"> 640 Free Online <span class="bg-clip-text text-transparent bg-gradient-to-r from-[#a78bfa] to-[#c084fc] drop-shadow-sm">Song Key Finder</span> </h1> 641 <p class="text-sm sm:text-base text-slate-300 font-light mb-5 max-w-2xl mx-auto leading-relaxed"> 642 Find the musical key, scale, and Camelot code of any song in seconds. Upload an audio file for key detection, explore the Camelot Wheel, or use your microphone to detect pitch in real time â all processed directly in your browser. </p> 643 644 <!-- Key Feature Badges --> 645 <div class="flex flex-wrap items-center justify-center gap-2.5 sm:gap-3 text-slate-400 text-xs sm:text-sm"> 646 <div class="flex items-center gap-2 bg-white/5 border border-white/5 px-3 py-1 rounded-xl"> 647 <i class="fas fa-check-circle text-emerald-400 text-xs"></i> 648 <span class="font-medium text-white/90 text-xs sm:text-sm">100% In-Browser Privacy</span> 649 </div> 650 <div class="flex items-center gap-2 bg-white/5 border border-white/5 px-3 py-1 rounded-xl"> 651 <i class="fas fa-check-circle text-emerald-400 text-xs"></i> 652 <span class="font-medium text-white/90 text-xs sm:text-sm">Musical Key Detection</span> 653 </div> 654 <div class="flex items-center gap-2 bg-white/5 border border-white/5 px-3 py-1 rounded-xl"> 655 <i class="fas fa-check-circle text-emerald-400 text-xs"></i> 656 <span class="font-medium text-white/90 text-xs sm:text-sm">Camelot DJ Code</span> 657 </div> 658 <div class="flex items-center gap-2 bg-white/5 border border-white/5 px-3 py-1 rounded-xl"> 659 <i class="fas fa-check-circle text-emerald-400 text-xs"></i> 660 <span class="font-medium text-white/90 text-xs sm:text-sm">Scale Degrees & Piano</span> 661 </div> 662 </div> 663 </header> 664 665 <!-- Main Upload Drop Zone Section --> 666 <div class="max-w-4xl mx-auto w-full px-4" id="uploaderContainer"> 667 668 <!-- Drop Zone Card --> 669 <div id="dropZone" class="glass-card rounded-3xl p-6 sm:p-8 md:p-10 mb-6 text-center cursor-pointer border border-white/10 hover:border-indigo-500/30 hover:shadow-neon-indigo transition-all duration-300 relative group" aria-label="drop-files"> 670 671 <div class="relative z-10 flex flex-col items-center"> 672 <!-- Cloud Icon -->
673 <div class="h-14 w-14 rounded-2xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center mb-4 text-[#a78bfa] group-hover:scale-105 transition-transform duration-300"> 674 <svg class="h-7 w-7 animate-bounce" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> 675 <path stroke-linecap="round" stroke-linejoin="round" d="M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12"/> 676 </svg> 677 </div> 678 679 <!-- Split Upload Button with Dropdown --> 680 <div class="split-button-container group/dropdown relative inline-flex items-center mb-4 select-none z-40"> 681 <div class="upload-button flex items-center bg-gradient-to-r from-[#a78bfa] to-[#c084fc] hover:opacity-95 text-white font-semibold rounded-2xl shadow-lg transition-all duration-150 overflow-hidden"> 682 <div class="button-main px-5 py-3.5 flex items-center gap-2 border-r border-white/20 active:scale-98 cursor-pointer" id="mainButton" aria-label="upload-button"> 683 <i class="fas fa-upload text-sm"></i> 684 <span class="text-sm sm:text-base font-bold">Upload File</span> 685 </div> 686 <div class="button-dropdown-trigger px-3.5 py-3.5 flex items-center justify-center cursor-pointer active:bg-black/10" id="dropdownTrigger" title="Choose audio source"> 687 <i class="fas fa-chevron-down text-xs"></i> 688 </div> 689 </div> 690 691 <!-- Dropdown Menu for Cloud Sources --> 692 <div class="dropdown-menu absolute top-full left-1/2 -translate-x-1/2 mt-2 w-64 bg-slate-950/95 border border-white/10 rounded-2xl shadow-2xl p-2 hidden flex flex-col gap-1 backdrop-blur-xl z-50"> 693 <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="device"> 694 <i class="fas fa-laptop text-indigo-400 w-5"></i> 695 <span class="text-sm font-medium">From Device</span> 696 </div> 697 <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="url"> 698 <i class="fas fa-link text-indigo-400 w-5"></i> 699 <span class="text-sm font-medium">From URL</span> 700 </div> 701 <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="google-drive"> 702 <i class="fab fa-google-drive text-indigo-400 w-5"></i> 703 <span class="text-sm font-medium">From Google Drive</span> 704 </div> 705 <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="dropbox"> 706 <i class="fab fa-dropbox text-indigo-400 w-5"></i> 707 <span class="text-sm font-medium">From Dropbox</span> 708 </div> 709 <div class="dropdown-item px-4 py-3 rounded-xl flex items-center gap-3 text-slate-300 hover:text-white hover:bg-white/5 transition-all text-left cursor-pointer" data-action="onedrive"> 710 <i class="fa-solid fa-cloud text-indigo-400 w-5"></i> 711 <span class="text-sm font-medium">From OneDrive</span> 712 </div> 713 </div> 714 </div> 715 716 <p class="text-slate-200 mb-1.5 text-base sm:text-lg font-outfit font-semibold">Drag & drop your audio track here</p> 717 <p class="text-slate-400 mb-4 text-xs sm:text-sm">or choose from multiple cloud sources above</p> 718 <p class="text-slate-500 text-xs mb-1">Maximum file size: <span class="font-semibold text-slate-300">100MB</span></p> 719 <p class="text-slate-500 text-xs">For lawful use only. Privacy guaranteed â files are processed directly on your device.</p> 720 721 <input type="file" id="fileInput" class="hidden" accept="audio/*,.mp3,.wav,.flac,.m4a,.aac,.ogg,.opus,.aiff" aria-label="upload files"> 722 723 <!-- Format Badges --> 724 <div class="flex flex-wrap items-center justify-center gap-2 mt-5">
725 <span class="text-slate-500 text-xs font-bold uppercase tracking-wider mr-2">Supports:</span> 726 <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">MP3</span> 727 <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">WAV</span> 728 <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">FLAC</span> 729 <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">M4A</span> 730 <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">AAC</span> 731 <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">OGG</span> 732 <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">OPUS</span> 733 <span class="bg-white/5 border border-white/5 px-2.5 py-1 rounded-xl text-slate-300 text-xs font-semibold uppercase">AIFF</span> 734 </div> 735 </div> 736 </div> 737 738 <!-- Progress Bar Container --> 739 <div class="progress-container w-full max-w-xl mx-auto my-8 bg-slate-950/60 border border-white/10 rounded-3xl p-6 hidden" id="progress-container"> 740 <div class="relative w-full h-4 bg-white/5 rounded-full overflow-hidden border border-white/10"> 741 <div class="h-full bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 transition-all duration-300" id="progressBar" style="width: 0%"></div> 742 </div> 743 <div class="flex items-center justify-between mt-4"> 744 <span class="text-sm font-semibold text-slate-300 font-outfit" id="progressText">0%</span> 745 <span class="text-xs text-indigo-400 font-bold uppercase tracking-wider" id="progressSubtext">Loading Audio...</span> 746 </div> 747 </div> 748 749 <!-- Dynamic Status / Information Box --> 750 <div style="display:none" class="flex-wrap items-center justify-center gap-4 text-slate-300 text-center text-sm md:text-base font-medium px-6 py-4 glass-card rounded-2xl max-w-xl mx-auto mb-6" id="progdata"></div> 751 752 <!-- Spinner Loader --> 753 <div style="display:none" class="flex justify-center items-center gap-4 text-slate-300 mb-6" id="process"> 754 <div class="w-12 h-12 border-4 border-indigo-500/20 border-t-indigo-500 rounded-full animate-spin"></div> 755 </div> 756 757 <!-- Error Alert Container --> 758 <div style="display:none" class="flex-wrap items-center justify-center gap-4 text-rose-400 font-semibold px-6 py-4 bg-rose-500/10 border border-rose-500/20 rounded-2xl max-w-xl mx-auto mb-6 text-center" id="error"></div> 759 </div> 760</section> 761 762<!-- ================= URL MODAL ================= --> 763<div id="urlModal" class="modal-overlay modal-hidden"> 764 <div class="glass-card rounded-3xl p-8 md:p-10 w-full max-w-lg mx-4 relative overflow-hidden shadow-2xl border border-white/10"> 765 <div class="absolute -right-20 -top-20 w-64 h-64 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none"></div> 766 767 <button class="absolute top-6 right-6 h-10 w-10 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 flex items-center justify-center text-slate-400 hover:text-white transition-all active:scale-95" id="closeModal" title="Close"> 768 <i class="fa-solid fa-xmark text-lg"></i> 769 </button> 770 771 <div class="flex items-center gap-3.5 mb-2"> 772 <div class="h-12 w-12 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-[#a78bfa]"> 773 <i class="fa-solid fa-link text-lg"></i> 774 </div> 775 <div> 776 <h3 class="font-outfit text-xl font-bold text-white">Add File by URL</h3> 777 <p class="text-xs text-slate-400">Provide a direct audio stream URL</p> 778 </div> 779 </div> 780 781 <div class="space-y-6 mt-6"> 782 <div class="flex items-center bg-slate-950/60 border border-white/10 rounded-2xl p-2 focus-within:border-indigo-500/50 focus-within:ring-2 focus-within:ring-indigo-500/20 transition-all"> 783 <input type="text" id="urlInput" placeholder="Paste direct audio URL (MP3, WAV, etc.)..." class="flex-1 bg-transparent px-4 py-2 text-white placeholder-slate-500 focus:outline-none text-sm" /> 784 <button id="pasteBtn" class="flex-shrink-0 px-4 py-2.5 rounded-xl bg-white/5 border border-white/10 text-xs font-semibold text-slate-300 hover:text-white hover:bg-white/10 active:scale-95 transition-all flex items-center gap-1.5" title="Paste URL" aria-label="paste"> 785 <i class="fa-solid fa-paste"></i> <span>Paste</span> 786 </button> 787 </div> 788 789 <button id="submitUrl" class="w-full px-5 py-3.5 rounded-2xl bg-gradient-to-r from-[#a78bfa] to-[#c084fc] text-white font-bold hover:opacity-95 active:scale-98 shadow-lg hover:shadow-
789purple-500/25 transition-all flex items-center justify-center gap-2" aria-label="submit"> 790 <i class="fa-solid fa-cloud-arrow-down text-sm"></i> Load Audio from URL 791 </button> 792 </div> 793 </div> 794</div> 795 796<!-- ================= CLIENT-SIDE CORE UPLOADER ENGINE ================= -->
797<script> 798(function() { 799 const TOOL_MODE = 'client'; 800 const MAX_FILE_SIZE = 100 * 1024 * 1024; 801 const ALLOWED_EXTS = ["mp3","wav","flac","m4a","aac","ogg","opus","aiff"]; 802 803 const dropZone = document.getElementById('dropZone'); 804 const fileInput = document.getElementById('fileInput'); 805 const mainButton = document.getElementById('mainButton'); 806 const dropdownTrigger = document.getElementById('dropdownTrigger'); 807 const dropdownMenu = document.querySelector('.dropdown-menu'); 808 const dropdownItems = document.querySelectorAll('.dropdown-item'); 809 const progressContainer = document.getElementById('progress-container'); 810 const progressBar = document.getElementById('progressBar'); 811 const progressText = document.getElementById('progressText'); 812 const progressSubtext = document.getElementById('progressSubtext'); 813 const progData = document.getElementById('progdata'); 814 const processSpinner = document.getElementById('process'); 815 const errorBox = document.getElementById('error'); 816 const uploaderHeader = document.getElementById('uploaderHeader'); 817 818 // Global Notification Toast 819 window.showNotification = function(message, type = 'info') { 820 const existing = document.querySelectorAll('.app-notification-toast'); 821 existing.forEach(t => t.remove()); 822 823 const notification = document.createElement('div'); 824 notification.className = 'app-notification-toast'; 825 notification.style.cssText = ` 826 position: fixed; 827 top: 90px; 828 right: 20px; 829 background: ${type === 'success' ? 'linear-gradient(135deg, #10b981 0%, #059669 100%)' : 830 type === 'error' ? 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)' : 831 'linear-gradient(135deg, #6366f1 0%, #a855f7 100%)'}; 832 color: white; 833 padding: 14px 22px; 834 border-radius: 16px; 835 box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5); 836 border: 1px solid rgba(255, 255, 255, 0.15); 837 backdrop-filter: blur(12px); 838 z-index: 99999; 839 max-width: 420px; 840 font-weight: 600; 841 font-size: 14px; 842 font-family: 'Outfit', sans-serif; 843 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); 844 transform: translateX(120%); 845 opacity: 0; 846 `; 847 848 notification.textContent = message; 849 document.body.appendChild(notification); 850 851 requestAnimationFrame(() => { 852 notification.style.transform = 'translateX(0)'; 853 notification.style.opacity = '1'; 854 }); 855 856 setTimeout(() => { 857 notification.style.transform = 'translateX(120%)'; 858 notification.style.opacity = '0'; 859 setTimeout(() => notification.remove(), 350); 860 }, 3500); 861 }; 862 863 // Update Progress Indicator 864 window.setUploadProgress = function(percent, text = '', subtext = 'Processing...') { 865 if (percent === null) { 866 progressContainer.style.display = 'none'; 867 return; 868 } 869 progressContainer.style.display = 'block'; 870 percent = Math.min(100, Math.max(0, Math.round(percent))); 871 progressBar.style.width = percent + '%'; 872 progressText.innerText = percent + '% ' + text; 873 if (subtext) progressSubtext.innerText = subtext; 874 }; 875 876 // Reset Uploader View 877 window.resetUploader = function() { 878 if (dropZone) dropZone.style.display = 'block'; 879 if (uploaderHeader) uploaderHeader.style.display = 'block'; 880 if (progressContainer) progressContainer.style.display = 'none'; 881 if (progData) progData.style.display = 'none'; 882 if (processSpinner) processSpinner.style.display = 'none'; 883 if (errorBox) errorBox.style.display = 'none'; 884 if (fileInput) fileInput.value = ''; 885 const workspace = document.getElementById('tool-workspace'); 886 if (workspace) workspace.classList.add('hidden'); 887 }; 888 889 // Check extension 890 function isAllowedExt(fileName) { 891 const dot = fileName.lastIndexOf('.'); 892 if (dot === -1) return false;
893 const ext = fileName.slice(dot + 1).toLowerCase(); 894 return ALLOWED_EXTS.includes(ext); 895 } 896 897 // Drag and drop listeners 898 if (dropZone) { 899 dropZone.addEventListener('dragover', (e) => { 900 e.preventDefault(); 901 dropZone.classList.add('dragover'); 902 }); 903 904 dropZone.addEventListener('dragleave', (e) => { 905 e.preventDefault(); 906 dropZone.classList.remove('dragover'); 907 }); 908 909 dropZone.addEventListener('drop', (e) => { 910 e.preventDefault(); 911 dropZone.classList.remove('dragover'); 912 if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { 913 handleIncomingFile(e.dataTransfer.files[0]); 914 } 915 }); 916 917 dropZone.addEventListener('click', (e) => { 918 if (!e.target.closest('.split-button-container')) { 919 fileInput.click(); 920 } 921 }); 922 } 923 924 // Main button click 925 if (mainButton) { 926 mainButton.addEventListener('click', (e) => { 927 e.stopPropagation(); 928 fileInput.click(); 929 }); 930 } 931 932 // File input change 933 if (fileInput) { 934 fileInput.addEventListener('change', (e) => { 935 if (e.target.files && e.target.files.length > 0) { 936 handleIncomingFile(e.target.files[0]); 937 } 938 }); 939 } 940 941 // Split dropdown hover 942 let dropdownTimeout; 943 const splitContainer = document.querySelector('.split-button-container'); 944 if (splitContainer && dropdownMenu) { 945 splitContainer.addEventListener('mouseenter', () => { 946 clearTimeout(dropdownTimeout); 947 dropdownMenu.classList.remove('hidden'); 948 }); 949 950 splitContainer.addEventListener('mouseleave', () => { 951 dropdownTimeout = setTimeout(() => { 952 dropdownMenu.classList.add('hidden'); 953 }, 600); 954 }); 955 956 dropdownTrigger.addEventListener('click', (e) => { 957 e.stopPropagation(); 958 dropdownMenu.classList.toggle('hidden'); 959 }); 960 } 961 962 // Handle Dropdown Actions 963 dropdownItems.forEach(item => { 964 item.addEventListener('click', (e) => { 965 e.stopPropagation(); 966 if (dropdownMenu) dropdownMenu.classList.add('hidden'); 967 const action = item.getAttribute('data-action'); 968 handleSourceAction(action); 969 }); 970 }); 971 972let OAUTH_TOKEN = ''; 973let DEVELOPER_KEY = ''; 974 975 function handleSourceAction(action) { 976 console.log('Upload action:', action); 977 978 switch(action) { 979 case 'device': 980 fileInput.click(); 981 break; 982 983 case 'url': 984 openUrlModal(); 985 break; 986 987 case 'google-drive': 988 showNotification('Initiating Google Login...', 'info'); 989 loadGoogleDriveOnDemand(triggerGoogleLogin); 990 break; 991 992 case 'dropbox': 993 showNotification('Initiating Dropbox Login...', 'info'); 994 dropboxPicker(); 995 break; 996 997 case 'onedrive': 998 showNotification('Initiating OneDrive Login...', 'info'); 999 launchOneDrivePicker(); 1000 break; 1001 1002 case 'youtube': 1003 showYouTubeInput(); 1004 break; 1005 1006 default: 1007 console.log('Unknown action:', action); 1008 } 1009 } 1010 1011 // Show YouTube input dialog 1012 function showYouTubeInput() { 1013 const url = prompt('Enter the YouTube video URL:'); 1014 if (url) { 1015 showNotification(`Processing YouTube video: ${url}`, 'info'); 1016 console.log('YouTube URL provided:', url); 1017 } 1018 } 1019 1020 window.addEventListener("load", function() { 1021 setTimeout(function() { 1022 showNotification('We support different separation models based on your need!', 'info'); 1023 }, 1000); 1024 }); 1025 1026 // Master File Ingestion Handler 1027 function handleIncomingFile(file) { 1028 if (!file) return; 1029 1030 if (file.size > MAX_FILE_SIZE) { 1031 showNotification(`â File exceeds ${MAX_FILE_SIZE / (1024*1024)}MB limit`, 'error'); 1032 return; 1033 } 1034 1035 if (!isAllowedExt(file.name)) { 1036 showNotification(`â ï¸ Unsupported audio format: ${file.name}`, 'error'); 1037 return; 1038 } 1039 1040 showNotification(`Loading: ${file.name}`, 'success'); 1041 1042 if (TOOL_MODE === 'client') { 1043 loadClientAudio(file); 1044 } else { 1045 if (typeof window.startServerUpload === 'function') { 1046 window.startServerUpload(file); 1047 } 1048 } 1049 } 1050 1051 // Load and decode audio buffer in browser (Client-Side Mode) 1052 async function loadClientAudio(file) { 1053 try { 1054 if (dropZone) dropZone.style.display = 'none'; 1055 if (uploaderHeader) uploaderHeader.style.display = 'none'; 1056 if (processSpinner) processSpinner.style.display = 'flex'; 1057 setUploadProgress(15, '', 'Reading file...'); 1058 1059 const arrayBuffer = await file.arrayBuffer(); 1060 setUploadProgress(45, '', 'Decoding audio PCM...'); 1061 1062 const AudioCtx = window.AudioContext || window.webkitAudioContext; 1063 const audioCtx = new AudioCtx(); 1064 const decodedBuffer = await audioCtx.decodeAudioData(arrayBuffer); 1065 1066 setUploadProgress(100, 'Complete!', 'Ready'); 1067 setTimeout(() => { 1068 setUploadProgress(null); 1069 if (processSpinner) processSpinner.style.display = 'none'; 1070 1071 // Reveal tool workspace 1072 const workspace = document.getElementById('tool-workspace'); 1073 if (workspace) workspace.classList.remove('hidden'); 1074 1075 // Trigger tool-specific callback 1076 if (typeof window.onAudioLoaded === 'function') { 1077 window.onAudioLoaded(file, decodedBuffer, audioCtx); 1078 } 1079 }, 300); 1080 1081 } catch (err) { 1082 console.error('Audio decode error:', err); 1083 showNotification('Could not decode audio. Try another format.', 'error'); 1084 resetUploader(); 1085 } 1086 } 1087 1088 // Load from remote URL (direct or preloaded) 1089 window.loadFromRemoteUrl = async function(url, fileName = 'Imported Audio') { 1090 try { 1091 if (dropZone) dropZone.style.display = 'none'; 1092 if (uploaderHeader) uploaderHeader.style.display = 'none'; 1093 if (processSpinner) processSpinner.style.display = 'flex'; 1094 setUploadProgress(20, '', 'Fetching audio stream...'); 1095 showNotification(`Preloading audio...`, 'info'); 1096 1097 const res = await fetch(url); 1098 if (!res.ok) throw new Error('Network response not ok: ' + res.status); 1099 1100 const blob = await res.blob(); 1101 const file = new File([blob], fileName, { type: blob.type || 'audio/mpeg' }); 1102 1103 setUploadProgress(60, '', 'Processing audio...'); 1104 loadClientAudio(file); 1105 1106 } catch (err) { 1107 console.error('Remote audio load error:', err); 1108 showNotification('Failed to download audio from URL.', 'error'); 1109 resetUploader(); 1110 } 1111 }; 1112 1113 // URL Modal Logic
1114 const urlModal = document.getElementById('urlModal'); 1115 const closeModal = document.getElementById('closeModal'); 1116 const pasteBtn = document.getElementById('pasteBtn'); 1117 const submitUrl = document.getElementById('submitUrl'); 1118 const urlInput = document.getElementById('urlInput'); 1119 1120 function openUrlModal() { 1121 if (!urlModal) return; 1122 urlModal.classList.remove('modal-hidden'); 1123 if (urlInput) { 1124 urlInput.value = ''; 1125 urlInput.focus(); 1126 } 1127 } 1128 1129 if (closeModal) { 1130 closeModal.onclick = () => urlModal.classList.add('modal-hidden'); 1131 } 1132 1133 if (urlModal) { 1134 urlModal.addEventListener('click', (e) => { 1135 if (e.target === urlModal) urlModal.classList.add('modal-hidden'); 1136 }); 1137 } 1138 1139 if (pasteBtn && urlInput) { 1140 pasteBtn.onclick = async () => { 1141 try { 1142 if (navigator.clipboard && navigator.clipboard.readText) { 1143 const text = await navigator.clipboard.readText(); 1144 urlInput.value = text.trim(); 1145 } else { 1146 const manual = prompt('Paste your audio URL:'); 1147 if (manual) urlInput.value = manual.trim(); 1148 } 1149 } catch (err) { 1150 const manual = prompt('Paste your audio URL:'); 1151 if (manual) urlInput.value = manual.trim(); 1152 } 1153 }; 1154 } 1155 1156 const bannedDomains = ['youtube.com', 'youtu.be', 'facebook.com', 'fb.me', 'instagram.com', 'tiktok.com']; 1157 1158 if (submitUrl && urlInput) { 1159 submitUrl.onclick = () => { 1160 const url = urlInput.value.trim(); 1161 if (!url) { 1162 showNotification('Please enter a valid URL', 'error'); 1163 return; 1164 } 1165 if (bannedDomains.some(d => url.toLowerCase().includes(d))) { 1166 showNotification('Direct streaming from video platforms is not supported.', 'error'); 1167 return; 1168 } 1169 urlModal.classList.add('modal-hidden'); 1170 loadFromRemoteUrl(url, 'Audio_From_URL.mp3'); 1171 }; 1172 } 1173 1174 // Auto-detect Preloaded Audio from Query Parameters (?audio=... or ?audio_url=...) 1175 window.addEventListener('DOMContentLoaded', () => { 1176 const params = new URLSearchParams(window.location.search); 1177 const preloadedAudio = params.get('audio') || params.get('audio_url'); 1178 const preloadedName = params.get('name') || params.get('title') || 'Preloaded Audio'; 1179 1180 if (preloadedAudio) { 1181 console.log('Detected preloaded audio URL:', preloadedAudio); 1182 loadFromRemoteUrl(preloadedAudio, preloadedName); 1183 } 1184 }); 1185 1186})(); 1187</script>
1187 1188 1189 <!-- Tool Workspace (Revealed automatically once audio is loaded) --> 1190 <section id="tool-workspace" class="hidden max-w-5xl mx-auto w-full px-4 mb-10 relative z-10"> 1191 1192 <!-- Top Action Bar: File Name & Re-upload Button --> 1193 <div class="flex flex-col sm:flex-row items-center justify-between gap-3 glass-card p-3.5 sm:p-4 rounded-2xl mb-5 border border-white/10"> 1194 <div class="flex items-center gap-3 truncate max-w-full"> 1195 <div class="h-9 w-9 rounded-xl bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-[#a78bfa] flex-shrink-0"> 1196 <i class="fa-solid fa-music text-sm"></i> 1197 </div> 1198 <div class="truncate text-left"> 1199 <p class="text-[11px] text-slate-400 font-medium">Loaded Track</p> 1200 <h2 id="track-filename" class="text-xs sm:text-sm font-bold text-white truncate max-w-md">filename.mp3</h2> 1201 </div> 1202 </div> 1203 1204 <button onclick="resetUploader()" class="flex-shrink-0 px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-300 hover:text-white transition-all flex items-center gap-2 active:scale-95 cursor-pointer"> 1205 <i class="fa-solid fa-arrow-up-from-bracket"></i> Choose Another File 1206 </button> 1207 </div> 1208 1209 <!-- Main Results Grid --> 1210 <div class="grid grid-cols-1 lg:grid-cols-12 gap-4 mb-5"> 1211 1212 <!-- Left Column: Big Key Display, Camelot Code, Song BPM & Tuning Pitch --> 1213 <div class="lg:col-span-7 glass-card rounded-3xl p-5 sm:p-7 border border-white/10 flex flex-col justify-between relative overflow-hidden group"> 1214 <div class="absolute -right-20 -top-20 w-64 h-64 bg-indigo-500/10 rounded-full blur-3xl pointer-events-none"></div> 1215 1216 <div> 1217 <div class="flex items-center justify-between mb-2"> 1218 <span class="text-[11px] font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20"> 1219 DETECTED KEY & BPM 1220 </span> 1221 <span id="key-confidence-badge" class="text-xs font-semibold text-emerald-400 bg-emerald-500/10 px-3 py-1 rounded-full border border-emerald-500/20 flex items-center gap-1.5"> 1222 <i class="fa-solid fa-circle-check"></i> Key Detection Result 1223 </span> 1224 </div> 1225 1226 <!-- Big Dual Metrics: Musical Key & Song BPM --> 1227 <div class="grid grid-cols-1 sm:grid-cols-2 gap-3.5 my-3"> 1228 <!-- Metric 1: Musical Key & Camelot --> 1229 <div class="bg-white/5 rounded-2xl p-4 border border-white/5 relative overflow-hidden flex flex-col justify-between"> 1230 <span class="text-[10px] text-slate-400 uppercase font-semibold block mb-1">Musical Key & Camelot</span> 1231 <div class="flex items-baseline gap-2.5 my-1"> 1232 <span id="workspace-key-display" class="font-outfit text-3xl sm:text-4xl lg:text-5xl font-black text-white tracking-tight drop-shadow-md"> 1233 -- 1234 </span> 1235 <span id="workspace-camelot-badge" class="px-2.5 py-1 rounded-xl bg-indigo-500/20 border border-indigo-500/30 text-indigo-300 font-outfit font-black text-lg sm:text-xl"> 1236 -- 1237 </span> 1238 </div> 1239 <span id="workspace-key-confidence" class="text-[11px] text-emerald-400 font-medium">Key Detection Confidence</span> 1240 </div> 1241 1242 <!-- Metric 2: Detected Tempo (BPM) & Octave Multipliers
1242--> 1243 <div class="bg-white/5 rounded-2xl p-4 border border-white/5 relative overflow-hidden flex flex-col justify-between"> 1244 <div class="flex items-center justify-between mb-1"> 1245 <span class="text-[10px] text-slate-400 uppercase font-semibold">Track Tempo</span> 1246 <span id="workspace-tempo-genre" class="text-[10px] text-indigo-300 font-semibold truncate max-w-[110px]">--</span> 1247 </div> 1248 <div class="flex items-baseline gap-1.5 my-1"> 1249 <span id="workspace-bpm-display" class="font-outfit text-3xl sm:text-4xl lg:text-5xl font-black text-white tracking-tight drop-shadow-md"> 1250 -- 1251 </span> 1252 <span class="text-sm font-bold text-slate-400 uppercase">BPM</span> 1253 </div> 1254 1255 <!-- Multiplier Buttons: 1/2, 1X, 2X --> 1256 <div class="flex items-center gap-1.5 mt-1"> 1257 <button id="btn-bpm-half" title="Half Tempo (÷2)" class="tempo-mult-btn px-2.5 py-0.5 rounded-lg bg-white/5 hover:bg-white/10 border border-white/10 text-[11px] font-bold text-slate-300 cursor-pointer transition-all"> 1258 ½ 1259 </button> 1260 <button id="btn-bpm-1x" title="Original Tempo (1X)" class="tempo-mult-btn active px-2.5 py-0.5 rounded-lg bg-indigo-500/20 border border-indigo-500/40 text-[11px] font-bold text-white cursor-pointer transition-all"> 1261 1X 1262 </button> 1263 <button id="btn-bpm-double" title="Double Tempo (×2)" class="tempo-mult-btn px-2.5 py-0.5 rounded-lg bg-white/5 hover:bg-white/10 border border-white/10 text-[11px] font-bold text-slate-300 cursor-pointer transition-all"> 1264 2× 1265 </button> 1266 </div> 1267 </div> 1268 </div> 1269 1270 <!-- Scale Notes Bar --> 1271 <div class="my-3"> 1272 <p class="text-xs text-slate-400 font-medium mb-1.5 flex items-center gap-1.5"> 1273 <i class="fa-solid fa-list-ol text-indigo-400"></i> Notes in this Scale: 1274 </p> 1275 <div id="scale-notes-container" class="flex flex-wrap items-center gap-1.5"> 1276 <span class="px-2.5 py-1 rounded-lg bg-white/5 text-xs font-mono font-bold text-slate-300">--</span> 1277 </div> 1278 </div> 1279 1280 <!-- Play Tonic Root Note Reference Tone Button --> 1281 <div class="my-3 flex flex-wrap items-center gap-2.5"> 1282 <button id="btn-play-tonic" class="px-4 py-2 rounded-xl bg-gradient-to-r from-indigo-500 to-purple-600 hover:opacity-95 text-white font-bold text-xs shadow-md transition-all active:scale-95 flex items-center gap-2 cursor-pointer"> 1283 <i class="fa-solid fa-volume-high" id="tonic-icon"></i> 1284 <span id="tonic-btn-label">Play Root Note Tone</span> 1285 </button> 1286 <span class="text-xs text-slate-400">Tuning Reference: <strong class="text-slate-200">A = 440.0 Hz</strong></span> 1287 </div> 1288 </div> 1289 1290 <!-- Key Attributes Sub-Bar --> 1291 <div class="grid grid-cols-2 sm:grid-cols-3 gap-2.5 pt-4 border-t border-white/10"> 1292 <div class="bg-white/5 rounded-2xl p-3 border border-white/5"> 1293 <p class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Relative Key</p> 1294 <div class="flex items-center gap-2"> 1295 <i class="fa-solid fa-arrows-rotate text-purple-400 text-xs"></i>
1296 <span id="relative-key-display" class="text-sm sm:text-base font-bold text-white font-outfit">--</span> 1297 </div> 1298 </div> 1299 <div class="bg-white/5 rounded-2xl p-3 border border-white/5"> 1300 <p class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Dominant (5th)</p> 1301 <div class="flex items-center gap-2"> 1302 <i class="fa-solid fa-crown text-indigo-400 text-xs"></i> 1303 <span id="dominant-key-display" class="text-sm sm:text-base font-bold text-indigo-300 font-outfit">--</span> 1304 </div> 1305 </div> 1306 <div class="bg-white/5 rounded-2xl p-3 border border-white/5 col-span-2 sm:col-span-1"> 1307 <p class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Subdominant (4th)</p> 1308 <div class="flex items-center gap-2"> 1309 <i class="fa-solid fa-layer-group text-emerald-400 text-xs"></i> 1310 <span id="subdominant-key-display" class="text-sm sm:text-base font-bold text-emerald-300 font-outfit">--</span> 1311 </div> 1312 </div> 1313 </div> 1314 </div> 1315 1316 <!-- Right Column: Interactive SVG Camelot Wheel Visualizer --> 1317 <div class="lg:col-span-5 glass-card rounded-3xl p-5 sm:p-7 border border-white/10 flex flex-col items-center justify-between relative overflow-hidden"> 1318 <div class="absolute -left-20 -bottom-20 w-64 h-64 bg-purple-500/10 rounded-full blur-3xl pointer-events-none"></div> 1319 1320 <div class="w-full text-center"> 1321 <div class="flex items-center justify-between mb-2"> 1322 <span class="text-[11px] font-bold tracking-widest text-[#c084fc] uppercase px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20"> 1323 CAMELOT DJ WHEEL 1324 </span> 1325 <span class="text-[11px] text-slate-400">Click any wedge</span> 1326 </div> 1327 1328 <!-- SVG Camelot Wheel Container --> 1329 <div class="w-full max-w-[280px] sm:max-w-[300px] aspect-square mx-auto my-2 relative flex items-center justify-center"> 1330 <svg id="camelot-wheel-svg" viewBox="0 0 400 400" class="w-full h-full filter drop-shadow-2xl"> 1331 <!-- Dynamic SVG Wedges drawn by JS --> 1332 </svg> 1333 <!-- Center Wheel Badge --> 1334 <div class="absolute w-20 h-20 rounded-full bg-slate-950/90 border border-white/20 backdrop-blur-md flex flex-col items-center justify-center pointer-events-none shadow-inner"> 1335 <span id="center-camelot-code" class="font-outfit font-black text-lg text-white leading-none">--</span> 1336 <span id="center-camelot-key" class="text-[10px] text-[#a78bfa] font-bold truncate max-w-[70px]">Select</span> 1337 </div> 1338 </div> 1339 1340 <p class="text-[11px] text-slate-400 mt-1">Outer ring: <strong>Major (B)</strong> ⢠Inner ring: <strong>Minor (A)</strong></p> 1341 </div> 1342 </div> 1343 </div> 1344 1345 <!-- 12-Class Pitch Chroma Spectrum Bar Graph --> 1346 <div class="glass-card rounded-3xl p-5 sm:p-6 border border-white/10 mb-5"> 1347 <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 mb-4"> 1348 <div> 1349 <span class="text-[11px] font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-1"> 1350 SPECTRAL ANALYSIS 1351 </span> 1352 <h3 class="font-outfit text-lg font-bold text-white">12-Pitch Chromagram Distribution</h3> 1353 </div>
1354 <span class="text-xs text-slate-400">Normalized harmonic energy across all 12 pitch classes</span> 1355 </div> 1356 1357 <!-- 12 Chroma Bars --> 1358 <div class="grid grid-cols-12 gap-1.5 sm:gap-2 h-28 items-end pt-4 pb-2 border-b border-white/10" id="chroma-bars-grid"> 1359 <!-- Populated dynamically: C, C#, D, D#, E, F, F#, G, G#, A, A#, B --> 1360 </div> 1361 </div> 1362 1363 <!-- Harmonic DJ Mix Compatibility Matrix --> 1364 <div class="glass-card rounded-3xl p-5 sm:p-6 border border-white/10 mb-5"> 1365 <div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-2 mb-4"> 1366 <div> 1367 <span class="text-[11px] font-bold tracking-widest text-[#c084fc] uppercase px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20 inline-block mb-1"> 1368 HARMONIC MIXING GUIDE 1369 </span> 1370 <h3 class="font-outfit text-lg font-bold text-white">DJ Compatibility Matrix for This Track</h3> 1371 </div> 1372 <span class="text-xs text-slate-400">Best transitions from <strong id="matrix-current-key" class="text-indigo-300">--</strong></span> 1373 </div> 1374 1375 <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-3"> 1376 <div class="bg-white/5 rounded-2xl p-3.5 border border-emerald-500/30 flex items-start gap-3"> 1377 <div class="h-10 w-10 rounded-xl bg-emerald-500/20 text-emerald-400 flex items-center justify-center font-bold text-sm flex-shrink-0"> 1378 <i class="fa-solid fa-check-double"></i> 1379 </div> 1380 <div> 1381 <span class="text-[10px] text-slate-400 uppercase font-semibold">Exact Match (Same Key)</span> 1382 <h4 id="match-exact" class="text-sm font-bold text-white font-outfit">--</h4> 1383 <p class="text-[11px] text-slate-400 mt-0.5">Same-key relationship for harmonic mixing.</p> 1384 </div> 1385 </div> 1386 1387 <div class="bg-white/5 rounded-2xl p-3.5 border border-blue-500/30 flex items-start gap-3"> 1388 <div class="h-10 w-10 rounded-xl bg-blue-500/20 text-blue-400 flex items-center justify-center font-bold text-sm flex-shrink-0"> 1389 <i class="fa-solid fa-shuffle"></i> 1390 </div> 1391 <div> 1392 <span class="text-[10px] text-slate-400 uppercase font-semibold">Relative Major / Minor</span> 1393 <h4 id="match-relative" class="text-sm font-bold text-white font-outfit">--</h4> 1394 <p class="text-[11px] text-slate-400 mt-0.5">Relative major/minor relationship using the same pitch collection.</p> 1395 </div> 1396 </div> 1397 1398 <div class="bg-white/5 rounded-2xl p-3.5 border border-pink-500/30 flex items-start gap-3"> 1399 <div class="h-10 w-10 rounded-xl bg-pink-500/20 text-pink-400 flex items-center justify-center font-bold text-sm flex-shrink-0"> 1400 <i class="fa-solid fa-arrow-trend-up"></i> 1401 </div> 1402 <div> 1403 <span class="text-[10px] text-slate-400 uppercase font-semibold">Energy Boost (+1 Hour)</span> 1404 <h4 id="match-boost-1" class="text-sm font-bold text-white font-outfit">--</h4> 1405 <p class="text-[11px] text-slate-400 mt-0.5">A neighboring-key relationship for a more noticeable transition.</p> 1406 </div> 1407 </div> 1408 1409 <div class="bg-white/5 rounded-2xl p-3.5 border border-amber-500/30 flex items-start gap-3"> 1410 <div class="h-10 w-10 rounded-xl bg-amber-500/20 text-amber-400 flex items-center justify-center font-bold text-sm flex-shrink-0"> 1411 <i class="fa-solid fa-arrow-trend-down"></i> 1412 </div> 1413 <div>
1414 <span class="text-[10px] text-slate-400 uppercase font-semibold">Energy Drop (-1 Hour)</span> 1415 <h4 id="match-drop-1" class="text-sm font-bold text-white font-outfit">--</h4> 1416 <p class="text-[11px] text-slate-400 mt-0.5">A neighboring-key relationship for a lower-energy transition.</p> 1417 </div> 1418 </div> 1419 </div> 1420 </div> 1421 1422 <!-- Audio Scrubber & Controller --> 1423 <div class="glass-card rounded-3xl p-4 sm:p-5 border border-white/10 mb-5"> 1424 <div class="flex flex-col sm:flex-row items-center gap-4"> 1425 <button id="btn-play-pause" class="h-12 w-12 rounded-2xl bg-gradient-to-r from-[#a78bfa] to-[#c084fc] flex items-center justify-center text-white text-lg shadow-lg hover:opacity-95 active:scale-95 transition-all flex-shrink-0 cursor-pointer"> 1426 <i class="fa-solid fa-play" id="play-icon"></i> 1427 </button> 1428 1429 <div class="flex-1 w-full"> 1430 <div class="flex items-center justify-between text-xs font-semibold text-slate-300 font-outfit mb-1.5"> 1431 <span id="player-current-time">0:00</span> 1432 <span id="player-duration">0:00</span> 1433 </div> 1434 <div class="relative w-full h-3 bg-white/5 rounded-full overflow-hidden border border-white/10 cursor-pointer" id="seek-bar-container"> 1435 <div id="seek-progress" class="h-full bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 transition-all" style="width: 0%"></div> 1436 </div> 1437 </div> 1438 </div> 1439 </div> 1440 1441 <!-- Cross-Tool Interoperability Switcher --> 1442 <div class="glass-card rounded-3xl p-5 sm:p-6 border border-white/10 text-center"> 1443 <h3 class="font-outfit text-base sm:text-lg font-bold text-white mb-1">Next Steps with this Track</h3> 1444 <p class="text-xs text-slate-400 mb-4">Send this track directly to other tools in the suite:</p> 1445 1446 <div class="flex flex-wrap items-center justify-center gap-2.5"> 1447 <a id="link-to-vocal-remover" href="/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2"> 1448 <i class="fa-solid fa-microphone-slash text-purple-400"></i> Remove Vocals 1449 </a> 1450 <a id="link-to-bpm" href="/bpm-finder/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2"> 1451 <i class="fa-solid fa-drum text-indigo-400"></i> Detect BPM 1452 </a> 1453 <a id="link-to-equalizer" href="/equalizer/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2"> 1454 <i class="fa-solid fa-sliders text-indigo-400"></i> Equalizer 1455 </a> 1456 <a id="link-to-bass" href="/bass-booster/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2"> 1457 <i class="fa-solid fa-bolt text-indigo-400"></i> Boost Bass 1458 </a> 1459 <a id="link-to-cutter" href="/audio-cutter/" class="px-3.5 py-2 rounded-xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all flex items-center gap-2"> 1460 <i class="fa-solid fa-scissors text-indigo-400"></i> Trim / Cut Audio 1461 </a> 1462 </div> 1463 </div> 1464 </section> 1465 </div> 1466 1467 <!-- ================= PANEL 2: CAMELOT WHEEL & PIANO EXPLORER ================= --> 1468 <div id="panel-wheel" class="mode-panel hidden w-full max-w-4xl mx-auto"> 1469 <div class="glass-card rounded-3xl p-5 sm:p-7 border border-white/10 text-center relative overflow-hidden shadow-2xl"> 1470 <div class="absolute -right-20 -top-20 w-72 h-72 bg-purple-500/10 rounded-full blur-3xl pointer-events-none"></div> 1471 1472 <!-- Header: Compact Title & Mode Badge --> 1473 <div class="flex flex-col items-center mb-3">
1474 <span class="text-[11px] font-bold tracking-widest text-[#c084fc] uppercase px-3 py-1 rounded-full bg-purple-500/10 border border-purple-500/20 inline-flex items-center gap-1.5 mb-2"> 1475 <span class="w-1.5 h-1.5 rounded-full bg-purple-400"></span> INTERACTIVE HARMONIC EXPLORER 1476 </span> 1477 <h2 class="font-outfit text-2xl sm:text-3xl font-extrabold text-white"> 1478 Camelot Wheel & Virtual Piano Explorer 1479 </h2> 1480 <p class="text-slate-400 text-xs sm:text-sm max-w-xl mx-auto mt-1 mb-3"> 1481 Click any key on the Camelot Wheel to inspect its DJ transition rules, or play the virtual piano to hear chord notes. 1482 </p> 1483 </div> 1484 1485 <!-- Two-Column Layout: Standalone Wheel + Active Key Details --> 1486 <div class="grid grid-cols-1 md:grid-cols-12 gap-5 items-center mb-6"> 1487 1488 <!-- Left: Interactive Wheel (Clickable) --> 1489 <div class="md:col-span-6 flex flex-col items-center"> 1490 <div class="w-full max-w-[280px] aspect-square relative flex items-center justify-center"> 1491 <svg id="explorer-wheel-svg" viewBox="0 0 400 400" class="w-full h-full filter drop-shadow-2xl"> 1492 <!-- Populated dynamically --> 1493 </svg> 1494 <div class="absolute w-20 h-20 rounded-full bg-slate-950/90 border border-white/20 backdrop-blur-md flex flex-col items-center justify-center pointer-events-none shadow-inner"> 1495 <span id="explorer-center-code" class="font-outfit font-black text-lg text-white leading-none">8A</span> 1496 <span id="explorer-center-key" class="text-[10px] text-[#a78bfa] font-bold truncate max-w-[70px]">A Minor</span> 1497 </div> 1498 </div> 1499 </div> 1500 1501 <!-- Right: Selected Key Rules & Transitions --> 1502 <div class="md:col-span-6 text-left space-y-3"> 1503 <div class="bg-white/5 rounded-2xl p-4 border border-white/5"> 1504 <span class="text-[10px] text-slate-400 uppercase font-semibold">Selected Musical Key</span> 1505 <div class="flex items-baseline gap-3 my-1"> 1506 <span id="explorer-key-name" class="font-outfit text-2xl sm:text-3xl font-extrabold text-white">A Minor</span> 1507 <span id="explorer-camelot-badge" class="px-2.5 py-0.5 rounded-xl bg-indigo-500/20 text-indigo-300 font-mono font-bold text-sm">8A</span> 1508 </div> 1509 <p id="explorer-mood-desc" class="text-xs text-slate-400">Dark, melancholy, deeply expressive emotional character.</p> 1510 </div> 1511 1512 <div class="grid grid-cols-2 gap-2 text-xs"> 1513 <div class="bg-white/5 p-2.5 rounded-xl border border-white/5"> 1514 <span class="text-[10px] text-slate-400 block">Relative Key (Mood Swap)</span> 1515 <strong id="explorer-relative-key" class="text-indigo-300 text-sm font-bold font-outfit">C Major (8B)</strong> 1516 </div> 1517 <div class="bg-white/5 p-2.5 rounded-xl border border-white/5"> 1518 <span class="text-[10px] text-slate-400 block">Energy Boost (+1 Hour)</span> 1519 <strong id="explorer-boost-key" class="text-pink-300 text-sm font-bold font-outfit">E Minor (9A)</strong> 1520 </div> 1521 </div> 1522 </div> 1523 </div> 1524 1525 <!-- Virtual Piano Keyboard (Playable) --> 1526 <div class="w-full max-w-2xl mx-auto bg-slate-950/80 rounded-2xl p-4 border border-white/10 mb-2 text-center"> 1527 <div class="flex items-center justify-between text-xs text-slate-400 mb-2"> 1528 <span class="font-semibold text-slate-300"><i class="fa-solid fa-keyboard mr-1.5 text-indigo-400"></i> Interactive Piano Keyboard</span>
1529 <span>Click keys to play tone ⢠Highlights active scale notes</span> 1530 </div> 1531 1532 <!-- Piano Keys Container --> 1533 <div class="relative h-28 flex justify-center mx-auto select-none overflow-x-auto pb-1" id="piano-keyboard"> 1534 <!-- Dynamic Piano Keys: 24 white & black keys --> 1535 </div> 1536 </div> 1537 </div> 1538 </div> 1539 1540 <!-- ================= PANEL 3: REAL-TIME MICROPHONE PITCH DETECTOR ================= --> 1541 <div id="panel-mic" class="mode-panel hidden w-full max-w-3xl mx-auto"> 1542 <div class="glass-card rounded-3xl p-5 sm:p-7 border border-white/10 text-center relative overflow-hidden shadow-2xl"> 1543 <div class="absolute -left-20 -top-20 w-72 h-72 bg-emerald-500/10 rounded-full blur-3xl pointer-events-none"></div> 1544 1545 <!-- Header: Compact Title & Mode Badge --> 1546 <div class="flex flex-col items-center mb-3"> 1547 <span class="text-[11px] font-bold tracking-widest text-emerald-400 uppercase px-3 py-1 rounded-full bg-emerald-500/10 border border-emerald-500/20 inline-flex items-center gap-1.5 mb-2"> 1548 <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-ping"></span> REAL-TIME ACOUSTIC PITCH LISTENER 1549 </span> 1550 <h2 class="font-outfit text-2xl sm:text-3xl font-extrabold text-white"> 1551 Live Microphone Pitch & Key Detector 1552 </h2> 1553 <p class="text-slate-400 text-xs sm:text-sm max-w-lg mx-auto mt-1 mb-3"> 1554 Sing, hum, or play into your microphone. Our autocorrelation engine detects real-time pitch frequencies and estimates the root key. 1555 </p> 1556 </div> 1557 1558 <!-- Compact Spectrum Waveform Visualizer --> 1559 <div class="w-full max-w-xl mx-auto bg-slate-950/80 rounded-2xl border border-white/10 overflow-hidden mb-3.5 relative"> 1560 <canvas id="mic-canvas" width="600" height="80" class="w-full h-18 sm:h-20 block"></canvas> 1561 <div id="mic-idle-msg" class="absolute inset-0 flex items-center justify-center bg-slate-950/70 backdrop-blur-xs text-xs text-slate-400 gap-2"> 1562 <i class="fa-solid fa-microphone-slash text-slate-500"></i> 1563 <span>Microphone paused. Click <strong>Start Listening</strong> below.</span> 1564 </div> 1565 </div> 1566 1567 <!-- Compact 3-Column Stats Grid --> 1568 <div class="grid grid-cols-3 gap-2.5 sm:gap-3.5 max-w-xl mx-auto mb-4"> 1569 1570 <!-- Stat 1: Detected Live Pitch Note --> 1571 <div class="bg-white/5 rounded-2xl p-3 border border-white/5 flex flex-col items-center justify-center relative overflow-hidden"> 1572 <span class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Live Pitch</span> 1573 <div class="flex items-baseline gap-1 my-auto"> 1574 <span id="mic-pitch-note" class="font-outfit text-3xl sm:text-4xl font-black text-emerald-400 drop-shadow-sm">--</span> 1575 </div> 1576 <span id="mic-pitch-freq" class="text-[9px] sm:text-[10px] text-slate-400 truncate w-full">-- Hz</span> 1577 </div> 1578 1579 <!-- Stat 2: Estimated Key --> 1580 <div class="bg-white/5 rounded-2xl p-3 border border-white/5 flex flex-col items-center justify-center"> 1581 <span class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5">Estimated Key</span> 1582 <div class="font-outfit text-xl sm:text-2xl font-black text-white my-auto truncate max-w-full"> 1583 <span id="mic-estimated-key">--</span> 1584 </div> 1585 <span id="mic-estimated-camelot" class="text-[9px] sm:text-[10px] text-indigo-300 font-mono font-bold">Camelot: --</span> 1586 </div> 1587 1588 <!-- Stat 3: Total Analyzed Duration --> 1589 <div class="bg-white/5 rounded-2xl p-3 border border-white/5 flex flex-col items-center justify-center"> 1590 <div class="text-[10px] sm:text-[11px] text-slate-400 uppercase font-semibold mb-0.5 flex items-center gap-1"> 1591 <i class="fa-regular fa-clock text-indigo-400"></i> Duration 1592 </div> 1593 <div class="font-mono text-2xl sm:text-3xl font-bold text-white my-auto">
1594 <span id="mic-duration-display">00:00</span> 1595 </div> 1596 <span id="mic-status-badge" class="text-[9px] sm:text-[10px] text-slate-400 truncate w-full">Listening...</span> 1597 </div> 1598 </div> 1599 1600 <!-- Action Controls: Start/Stop Button + Reset Button (No Scroll Required) --> 1601 <div class="flex items-center justify-center gap-2.5 max-w-sm mx-auto"> 1602 <button id="btn-toggle-mic" class="flex-1 py-3 px-6 rounded-2xl bg-gradient-to-r from-emerald-500 to-teal-600 hover:from-emerald-400 hover:to-teal-500 text-white font-bold text-sm sm:text-base shadow-lg shadow-emerald-500/25 active:scale-95 transition-all flex items-center justify-center gap-2.5 cursor-pointer"> 1603 <i class="fa-solid fa-microphone text-base" id="mic-btn-icon"></i> 1604 <span id="mic-btn-label">Start Listening</span> 1605 </button> 1606 <button id="btn-reset-mic" title="Reset stats" class="px-4 py-3 rounded-2xl bg-white/5 hover:bg-white/10 border border-white/10 text-xs font-semibold text-slate-300 hover:text-white transition-all active:scale-95 cursor-pointer flex items-center gap-1.5"> 1607 <i class="fa-solid fa-rotate-left"></i> <span class="hidden sm:inline">Reset</span> 1608 </button> 1609 </div> 1610 1611 </div> 1612 </div> 1613 1614</main> 1615 1616<!-- ================= SEO CONTENT & EDUCATIONAL SECTION ================= --> 1617<section class="max-w-5xl mx-auto px-4 py-12 text-slate-300 relative z-10"> 1618 <div class="glass-card rounded-3xl p-8 sm:p-12 border border-white/10 space-y-12"> 1619 <!-- Guide Intro: How to Find the Key of a Song --> 1620 <div> 1621 <span class="text-xs font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-4"> 1622 THE HARMONIC MIXING GUIDE 1623 </span> 1624 <h2 class="font-outfit text-3xl sm:text-4xl font-extrabold text-white tracking-tight leading-tight mb-4"> 1625 How to Find the Key of a Song 1626 </h2> 1627 <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-4"> 1628 VocalRemovers.ai provides a fast, browser-based harmonic analyzer to detect the musical key and Camelot code of any audio track. If you are working with a dense full-band arrangement, you can first use our <a href="/" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">vocal remover</a> to isolate a clean acapella or melodic stem for even easier key identification. 1629 </p> 1630 <div class="grid grid-cols-1 md:grid-cols-3 gap-4 my-6 text-sm text-slate-300 font-medium"> 1631 <div class="p-5 rounded-2xl bg-white/5 border border-white/5"> 1632 <strong class="block text-white mb-1.5"><i class="fa-solid fa-cloud-arrow-up text-indigo-400 mr-2"></i> 1. Upload your audio file</strong> 1633 <p class="text-xs text-slate-400 leading-relaxed mb-2"> 1634 Select an MP3, WAV, FLAC, M4A or another supported format. 1635 </p> 1636 <p class="text-xs text-slate-400 leading-relaxed"> 1637 <strong class="text-slate-300">Let the analyzer process the track:</strong> The audio is analyzed in your browser to estimate its musical key and scale. 1638 </p> 1639 </div> 1640 <div class="p-5 rounded-2xl bg-white/5 border border-white/5"> 1641 <strong class="block text-white mb-1.5"><i class="fa-solid fa-compact-disc text-purple-400 mr-2"></i> 2. View the detected key</strong> 1642 <p class="text-xs text-slate-400 leading-relaxed"> 1643 The result shows the key, major/minor scale, Camelot code and related key information. 1644 </p> 1645 </div> 1646 <div class="p-5 rounded-2xl bg-white/5 border border-white/5"> 1647 <strong class="block text-white mb-1.5"><i class="fa-solid fa-diagram-project text-emerald-400 mr-2"></i> 3. Explore compatible keys</strong> 1648 <p class="text-xs text-slate-400 leading-relaxed"> 1649 Use the Camelot Wheel to see neighboring and relative key relationships. 1650 </p> 1651 </div> 1652 </div> 1653 <p class="text-slate-400 text-xs sm:text-sm leading-relaxed mt-4"> 1654 <i class="fa-solid fa-microphone-lines text-indigo-400 mr-1.5"></i> You can also use the Real-Time Mic mode to detect pitch from singing, humming or an acoustic instrument. 1655 </p> 1656 </div> 1657 1658 <!-- Musical Key & Harmonic Mixing Overview --> 1659 <div> 1660 <h3 class="font-outfit text-2xl font-bold text-white mb-3"> 1661 What is Musical Key and How Does Camelot Harmonic Mixing Work? 1662 </h3> 1663 <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-4"> 1664 In music theory, the <strong>musical key</strong> of a song defines its tonal centerâthe root note that feels like home and the specific scale of notes that accompany it. Songs composed in a <strong>Major key</strong> generally evoke joyful, uplifting, and anthemic emotions, while tracks in a <strong>Minor key</strong> feel introspective, moody, emotional, or dark. 1665 </p> 1666 <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-4"> 1667 For DJs and music producers, knowing a track's musical key can help with harmonic mixing. When two tracks contain compatible tonal material, transitions can sound more musically consistent. The Camelot Wheel provides a simple way to represent these relationships and explore possible key combinations. 1668 </p> 1669 </div> 1670 1671 <!-- The Camelot Wheel System Explained --> 1672 <div> 1673 <h3 class="font-outfit text-2xl font-bold text-white mb-3">The Camelot Wheel System: Clock-Face Harmonic Mixing</h3> 1674 <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-4"> 1675 Invented by Mark Davis and popularized by <a href="https://mixedinkey.com/harmonic-mixing-guide/" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">Mixed In Key</a>, the <strong>Camelot Wheel</strong> translates the complex Circle of Fifths into a visual 12-hour clock face: 1676 </p> 1677 <div class="grid grid-cols-1 md:grid-cols-3 gap-4 my-6 text-sm text-slate-300 font-medium"> 1678 <div class="p-5 rounded-2xl bg-white/5 border border-white/5"> 1679 <strong class="block text-white mb-1.5"><i class="fa-solid fa-clock text-indigo-400 mr-2"></i> Numbers (1 to 12)</strong> 1680 <p class="text-xs text-slate-400 leading-relaxed"> 1681 Represent the 12 pitch hours around the wheel. Moving to an adjacent number on the same A/B ring represents a neighboring key relationship based on the Circle of Fifths. 1682 </p><p class="text-xs text-slate-400 leading-relaxed">For example, 8A â 9A and 8A â 7A are common adjacent-key relationships used when planning harmonic transitions.</p> 1683 </div> 1684 <div class="p-5 rounded-2xl bg-white/5 border border-white/5"> 1685 <strong class="block text-white mb-1.5"><i class="fa-solid fa-moon text-purple-400 mr-2"></i> Letter A (Minor Keys)</strong> 1686 <p class="text-xs text-slate-400 leading-relaxed"> 1687 The inner ring comprises all 12 natural minor scales (e.g. 8A is A Minor, 5A is C Minor, 11A is F# Minor). 1688 </p> 1689 </div> 1690 <div class="p-5 rounded-2xl bg-white/5 border border-white/5"> 1691 <strong class="block text-white mb-1.5"><i class="fa-solid fa-sun text-emerald-400 mr-2"></i> Letter B (Major Keys)</strong> 1692 <p class="text-xs text-slate-400 leading-relaxed"> 1693 The outer ring comprises all 12 major scales (e.g. 8B is C Major, 5B is Eb Major, 11B is A Major). 1694 </p> 1695 </div> 1696 </div> 1697 </div> 1698 1699 <!-- Key Finder Features Section --> 1700 <div> 1701 <h2 class="font-outfit text-3xl sm:text-4xl font-extrabold text-white tracking-tight leading-tight mb-4"> 1702 Key Finder & Harmonic DJ Features 1703 </h2> 1704 <p class="text-slate-400 text-sm sm:text-base leading-relaxed mb-6"> 1705 Discover professional tools designed for DJs, producers, and musicians to detect musical keys, understand harmonic relationships, and plan harmonic transitions. 1706 </p> 1707 1708 <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 text-sm text-slate-300"> 1709 <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-purple-500/20 transition-all"> 1710 <div class="flex items-center gap-3 mb-2"> 1711 <div class="h-8 w-8 rounded-lg bg-purple-500/10 border border-purple-500/20 flex items-center justify-center text-purple-400"> 1712 <i class="fa-solid fa-compact-disc text-sm"></i> 1713 </div> 1714 <strong class="text-white text-sm">Chromagram Key & Scale Detection</strong> 1715 </div> 1716 <p class="text-xs text-slate-400 leading-relaxed"> 1717 Extracts 12-pitch chroma information from the audio to estimate the root note, musical key, and major/minor scale. 1718 </p> 1719 </div> 1720 1721 <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-indigo-500/20 transition-all"> 1722 <div class="flex items-center gap-3 mb-2"> 1723 <div class="h-8 w-8 rounded-lg bg-indigo-500/10 border border-indigo-500/20 flex items-center justify-center text-indigo-400"> 1724 <i class="fa-solid fa-circle-nodes text-sm"></i> 1725 </div> 1726 <strong class="text-white text-sm">Camelot Wheel DJ Codes</strong> 1727 </div> 1728 <p class="text-xs text-slate-400 leading-relaxed"> 1729 Translates standard musical keys into 1Aâ12B alphanumeric Camelot notation, making it simple to find harmonically compatible tracks for live DJ sets. 1730 </p> 1731 </div> 1732 1733 <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-emerald-500/20 transition-all"> 1734 <div class="flex items-center gap-3 mb-2"> 1735 <div class="h-8 w-8 rounded-lg bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400"> 1736 <i class="fa-solid fa-microphone-lines text-sm"></i> 1737 </div> 1738 <strong class="text-white text-sm">Real-Time Mic Pitch Tracker</strong> 1739 </div> 1740 <p class="text-xs text-slate-400 leading-relaxed"> 1741 Detect pitch live from your microphone by humming, singing, or playing an acoustic instrument with real-time frequency (Hz) and note estimation. 1742 </p> 1743 </div> 1744 1745 <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-amber-500/20 transition-all"> 1746 <div class="flex items-center gap-3 mb-2"> 1747 <div class="h-8 w-8 rounded-lg bg-amber-500/10 border border-amber-500/20 flex items-center justify-center text-amber-400"> 1748 <i class="fa-solid fa-keyboard text-sm"></i> 1749 </div> 1750 <strong class="text-white text-sm">
1750Interactive Camelot Wheel & Piano</strong> 1751 </div> 1752 <p class="text-xs text-slate-400 leading-relaxed"> 1753 Explore key compatibility on an interactive 12-hour SVG wheel and virtual piano keyboard that visualizes scale notes and transition matches. 1754 </p> 1755 </div> 1756 1757 <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-blue-500/20 transition-all"> 1758 <div class="flex items-center gap-3 mb-2"> 1759 <div class="h-8 w-8 rounded-lg bg-blue-500/10 border border-blue-500/20 flex items-center justify-center text-blue-400"> 1760 <i class="fa-solid fa-volume-high text-sm"></i> 1761 </div> 1762 <strong class="text-white text-sm">Reference Root Tone Generator</strong> 1763 </div> 1764 <p class="text-xs text-slate-400 leading-relaxed"> 1765 Play a pure synthesized sine reference tone matching the detected root pitch to audit the key by ear or tune instruments before performing. 1766 </p> 1767 </div> 1768 1769 <div class="p-5 rounded-2xl bg-white/[0.03] border border-white/5 hover:border-teal-500/20 transition-all"> 1770 <div class="flex items-center gap-3 mb-2"> 1771 <div class="h-8 w-8 rounded-lg bg-teal-500/10 border border-teal-500/20 flex items-center justify-center text-teal-400"> 1772 <i class="fa-solid fa-shield-halved text-sm"></i> 1773 </div> 1774 <strong class="text-white text-sm">100% In-Browser Privacy</strong> 1775 </div> 1776 <p class="text-xs text-slate-400 leading-relaxed"> 1777 All DSP chromagram extraction and audio decoding occur completely within your browser via the Web Audio APIâno files are ever uploaded. 1778 </p> 1779 </div> 1780 </div> 1781 </div> 1782 1783 <!-- 5 Essential DJ Transition Techniques --> 1784 <div> 1785 <h3 class="font-outfit text-2xl font-bold text-white mb-3">5 Essential DJ Harmonic Transition Techniques</h3> 1786 <p class="text-slate-400 text-sm mb-6">These Camelot relationships can help DJs plan harmonic transitions between tracks:</p> 1787 1788 <div class="space-y-3"> 1789 <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4"> 1790 <span class="px-3 py-1 rounded-xl bg-emerald-500/20 text-emerald-300 font-mono font-bold text-xs flex-shrink-0">Same Key (0)</span> 1791 <div> 1792 <strong class="text-white text-sm block">1. The Perfect Blend (e.g., 8A → 8A)</strong> 1793 <p class="text-xs text-slate-400 mt-1">Both tracks are in the same key, making this a straightforward starting point for harmonic mixing. The actual result still depends on the arrangement, melodies, basslines and other musical elements.</p> 1794 </div> 1795 </div> 1796 1797 <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4"> 1798 <span class="px-3 py-1 rounded-xl bg-blue-500/20 text-blue-300 font-mono font-bold text-xs flex-shrink-0">Swap (A ↔ B)</span> 1799 <div> 1800 <strong class="text-white text-sm block">2. Relative Major / Minor Shift (e.g., 8A → 8B)</strong> 1801 <p class="text-xs text-slate-400 mt-1">Relative major and minor keys contain the same pitch classes but have different tonal centers, allowing transitions between contrasting major/minor tonalities.</p> 1802 </div> 1803 </div> 1804 1805 <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4"> 1806 <span class="px-3 py-1 rounded-xl bg-pink-500/20 text-pink-300 font-mono font-bold text-xs flex-shrink-0">Boost (+1)</span> 1807 <div> 1808 <strong class="text-white text-sm block">3. Dominant Energy Lift (e.g., 8A → 9A)</strong> 1809 <p class="text-xs text-slate-400 mt-1">Moving one step around the Camelot wheel is a commonly used adjacent-key transition. Whether it works well depends on the tracks and transition technique.</p> 1810 </div> 1811 </div> 1812 1813 <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4">
1814 <span class="px-3 py-1 rounded-xl bg-amber-500/20 text-amber-300 font-mono font-bold text-xs flex-shrink-0">Drop (-1)</span> 1815 <div> 1816 <strong class="text-white text-sm block">4. Subdominant Warmth Drop (e.g., 8A → 7A)</strong> 1817 <p class="text-xs text-slate-400 mt-1">Moving one hour counter-clockwise produces a mellow, warm, and relaxing resolutionâideal for easing transition into deep grooves.</p> 1818 </div> 1819 </div> 1820 1821 <div class="bg-white/5 rounded-2xl p-4 border border-white/5 flex items-start gap-4"> 1822 <span class="px-3 py-1 rounded-xl bg-purple-500/20 text-purple-300 font-mono font-bold text-xs flex-shrink-0">Super (+2)</span> 1823 <div> 1824 <strong class="text-white text-sm block">5. High-Impact Energy Surge (e.g., 8A → 10A)</strong> 1825 <p class="text-xs text-slate-400 mt-1">Moving two positions on the same Camelot ring creates a larger tonal change. DJs may use this relationship for a more noticeable transition, but it is less directly related than staying on the same key or moving to an adjacent key.</p> 1826 </div> 1827 </div> 1828 </div> 1829 </div> 1830 1831 <!-- Complete 24 Key Circle of Fifths Reference Table --> 1832 <div> 1833 <h3 class="font-outfit text-2xl font-bold text-white mb-3">Complete 24-Key Circle of Fifths & Camelot Cheat Sheet</h3> 1834 <p class="text-slate-400 text-sm mb-6">Reference this complete lookup table based on the classical <a href="https://en.wikipedia.org/wiki/Circle_of_fifths" target="_blank" rel="noopener noreferrer" class="text-indigo-400 hover:text-indigo-300 underline underline-offset-2">Circle of Fifths</a> for all 24 major and minor keys, standard key signatures, and Camelot codes:</p> 1835 1836 <div class="overflow-x-auto"> 1837 <table class="min-w-full text-sm text-left border border-white/10 rounded-2xl overflow-hidden"> 1838 <thead class="bg-white/5 text-slate-400 uppercase text-xs font-bold border-b border-white/10"> 1839 <tr> 1840 <th class="px-5 py-3.5">Camelot Code</th> 1841 <th class="px-5 py-3.5">Key Name</th> 1842 <th class="px-5 py-3.5">Scale Type</th> 1843 <th class="px-5 py-3.5">Relative Key</th> 1844 <th class="px-5 py-3.5">Key Signature</th> 1845 </tr> 1846 </thead> 1847 <tbody class="divide-y divide-white/5 text-slate-300"> 1848 <tr class="hover:bg-white/[0.02]"> 1849 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">8B</td> 1850 <td class="px-5 py-2.5 font-semibold text-white">C Major</td> 1851 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1852 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">A Minor (8A)</td> 1853 <td class="px-5 py-2.5 text-slate-400 text-xs">0 Sharps / Flats</td> 1854 </tr> 1855 <tr class="hover:bg-white/[0.02]"> 1856 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">8A</td> 1857 <td class="px-5 py-2.5 font-semibold text-white">A Minor</td> 1858 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1859 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">C Major (8B)</td> 1860 <td class="px-5 py-2.5 text-slate-400 text-xs">0 Sharps / Flats</td> 1861 </tr> 1862 <tr class="hover:bg-white/[0.02]"> 1863 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">9B</td> 1864 <td class="px-5 py-2.5 font-semibold text-white">G Major</td> 1865 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1866 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">E Minor (9A)</td> 1867 <td class="px-5 py-2.5 text-slate-400 text-xs">1 Sharp (F#)</td> 1868 </tr> 1869 <tr class="hover:bg-white/[0.02]"> 1870 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">9A</td> 1871 <td class="px-5 py-2.5 font-semibold text-white">E Minor</td> 1872 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1873 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">G Major (9B)</td> 1874 <td class="px-5 py-2.5 text-slate-400 text-xs">1 Sharp (F#)</td> 1875 </tr> 1876 <tr class="hover:bg-white/[0.02]"> 1877 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">10B</td> 1878 <td class="px-5 py-2.5 font-semibold text-white">D Major</td> 1879 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1880 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">B Minor (10A)</td> 1881 <td class="px-5 py-2.5 text-slate-400 text-xs">2 Sharps (F#, C#)</td> 1882 </tr> 1883 <tr class="hover:bg-white/[0.02]"> 1884 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">10A</td> 1885 <td class="px-5 py-2.5 font-semibold text-white">B Minor</td> 1886 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1887 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">D Major (10B)</td> 1888 <td class="px-5 py-2.5 text-slate-400 text-xs">2 Sharps (F#, C#)</td> 1889 </tr> 1890 <tr class="hover:bg-white/[0.02]"> 1891 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">11B</td> 1892 <td class="px-5 py-2.5 font-semibold text-white">A Major</td> 1893 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1894 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">F# Minor (11A)</td> 1895 <td class="px-5 py-2.5 text-slate-400 text-xs">3 Sharps (F#, C#, G#)</td> 1896 </tr> 1897 <tr class="hover:bg-white/[0.02]"> 1898 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">11A</td> 1899 <td class="px-5 py-2.5 font-semibold text-white">F# Minor</td> 1900 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1901 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">A Major (11B)</td> 1902 <td class="px-5 py-2.5 text-slate-400 text-xs">3 Sharps (F#, C#, G#)</td> 1903 </tr> 1904 <tr class="hover:bg-white/[0.02]"> 1905 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">12B</td> 1906 <td class="px-5 py-2.5 font-semibold text-white">E Major</td> 1907 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1908 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">C# Minor (12A)</td> 1909 <td class="px-5 py-2.5 text-slate-400 text-xs">4 Sharps (F#, C#, G#, D#)</td> 1910 </tr> 1911 <tr class="hover:bg-white/[0.02]"> 1912 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">12A</td> 1913 <td class="px-5 py-2.5 font-semibold text-white">C# Minor</td> 1914 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1915 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">E Major (12B)</td> 1916 <td class="px-5 py-2.5 text-slate-400 text-xs">4 Sharps (F#, C#, G#, D#)</td> 1917 </tr> 1918 <tr class="hover:bg-white/[0.02]"> 1919 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">1B</td> 1920 <td class="px-5 py-2.5 font-semibold text-white">B Major</td> 1921 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1922 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">G# Minor (1A)</td> 1923 <td class="px-5 py-2.5 text-slate-400 text-xs">5 Sharps (B, E, A, D, G)</td> 1924 </tr> 1925 <tr class="hover:bg-white/[0.02]"> 1926 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">1A</td> 1927 <td class="px-5 py-2.5 font-semibold text-white">G# Minor</td> 1928 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1929 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">B Major (1B)</td> 1930 <td class="px-5 py-2.5 text-slate-400 text-xs">5 Sharps (B, E, A, D, G)</td> 1931 </tr> 1932 <tr class="hover:bg-white/[0.02]"> 1933 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">2B</td> 1934 <td class="px-5 py-2.5 font-semibold text-white">F# Major</td> 1935 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1936 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">D# Minor (2A)</td> 1937 <td class="px-5 py-2.5 text-slate-400 text-xs">6 Sharps (F#, C#, G#, D#, A#, E#)</td> 1938 </tr> 1939 <tr class="hover:bg-white/[0.02]"> 1940 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">2A</td> 1941 <td class="px-5 py-2.5 font-semibold text-white">D# Minor</td> 1942 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1943 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">F# Major (2B)</td> 1944 <td class="px-5 py-2.5 text-slate-400 text-xs">6 Sharps (F#, C#, G#, D#, A#, E#)</td> 1945 </tr> 1946 <tr class="hover:bg-white/[0.02]"> 1947 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">3B</td> 1948 <td class="px-5 py-2.5 font-semibold text-white">Db / C# Major</td> 1949 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1950 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Bb Minor (3A)</td> 1951 <td class="px-5 py-2.5 text-slate-400 text-xs">5 Flats (Bb, Eb, Ab, Db, Gb)</td> 1952 </tr> 1953 <tr class="hover:bg-white/[0.02]"> 1954 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">3A</td> 1955 <td class="px-5 py-2.5 font-semibold text-white">Bb Minor</td> 1956 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1957 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Db Major (3B)</td> 1958 <td class="px-5 py-2.5 text-slate-400 text-xs">5 Flats (Bb, Eb, Ab, Db, Gb)</td> 1959 </tr> 1960 <tr class="hover:bg-white/[0.02]"> 1961 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">4B</td> 1962 <td class="px-5 py-2.5 font-semibold text-white">Ab Major</td> 1963 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1964 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">F Minor (4A)</td> 1965 <td class="px-5 py-2.5 text-slate-400 text-xs">4 Flats (Bb, Eb, Ab, Db)</td> 1966 </tr> 1967 <tr class="hover:bg-white/[0.02]"> 1968 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">4A</td> 1969 <td class="px-5 py-2.5 font-semibold text-white">F Minor</td> 1970 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1971 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Ab Major (4B)</td> 1972 <td class="px-5 py-2.5 text-slate-400 text-xs">4 Flats (Bb, Eb, Ab, Db)</td> 1973 </tr> 1974 <tr class="hover:bg-white/[0.02]"> 1975 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">5B</td> 1976 <td class="px-5 py-2.5 font-semibold text-white">Eb Major</td> 1977 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1978 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">C Minor (5A)</td> 1979 <td class="px-5 py-2.5 text-slate-400 text-xs">3 Flats (Bb, Eb, Ab)</td> 1980 </tr> 1981 <tr class="hover:bg-white/[0.02]"> 1982 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">5A</td> 1983 <td class="px-5 py-2.5 font-semibold text-white">C Minor</td> 1984 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1985 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Eb Major (5B)</td> 1986 <td class="px-5 py-2.5 text-slate-400 text-xs">3 Flats (Bb, Eb, Ab)</td> 1987 </tr> 1988 <tr class="hover:bg-white/[0.02]"> 1989 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">6B</td> 1990 <td class="px-5 py-2.5 font-semibold text-white">Bb Major</td> 1991 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 1992 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">G Minor (6A)</td> 1993 <td class="px-5 py-2.5 text-slate-400 text-xs">2 Flats (Bb, Eb)</td> 1994 </tr> 1995 <tr class="hover:bg-white/[0.02]"> 1996 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">6A</td> 1997 <td class="px-5 py-2.5 font-semibold text-white">G Minor</td> 1998 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 1999 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">Bb Major (6B)</td> 2000 <td class="px-5 py-2.5 text-slate-400 text-xs">2 Flats (Bb, Eb)</td> 2001 </tr> 2002 <tr class="hover:bg-white/[0.02]"> 2003 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">7B</td> 2004 <td class="px-5 py-2.5 font-semibold text-white">F Major</td> 2005 <td class="px-5 py-2.5 text-slate-400 text-xs">Major</td> 2006 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">D Minor (7A)</td> 2007 <td class="px-5 py-2.5 text-slate-400 text-xs">1 Flat (Bb)</td> 2008 </tr> 2009 <tr class="hover:bg-white/[0.02]"> 2010 <td class="px-5 py-2.5 font-mono font-bold text-indigo-300">7A</td> 2011 <td class="px-5 py-2.5 font-semibold text-white">D Minor</td> 2012 <td class="px-5 py-2.5 text-slate-400 text-xs">Minor</td> 2013 <td class="px-5 py-2.5 text-purple-300 font-mono text-xs">F Major (7B)</td> 2014 <td class="px-5 py-2.5 text-slate-400 text-xs">1 Flat (Bb)</td> 2015 </tr> 2016 </tbody> 2017 <
2017/table> 2018 </div> 2019 </div> 2020 2021 <!-- Related Studio Tools Component --> 2022 <div> 2023 2024<div class="w-full"> 2025 <div class="mb-6 text-left"> 2026 <span class="text-xs font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-2"> 2027 CONTINUE YOUR AUDIO WORKFLOW 2028 </span> 2029 <h3 class="font-outfit text-xl sm:text-2xl font-bold text-white tracking-tight"> 2030 Related Studio Tools 2031 </h3> 2032 </div> 2033 <div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3"> 2034 <a href="/mp3-tag-editor/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group"> 2035 <div class="h-9 w-9 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-center text-amber-400 group-hover:scale-110 transition-transform"> 2036 <i class="fa-solid fa-tags text-sm"></i> 2037 </div> 2038 <span class="text-xs font-bold text-white group-hover:text-amber-300">MP3 Tag Editor</span> 2039 </a> 2040 <a href="/pitch-changer/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group"> 2041 <div class="h-9 w-9 rounded-xl bg-cyan-500/10 border border-cyan-500/20 flex items-center justify-center text-cyan-400 group-hover:scale-110 transition-transform"> 2042 <i class="fa-solid fa-music text-sm"></i> 2043 </div> 2044 <span class="text-xs font-bold text-white group-hover:text-cyan-300">Pitch Changer</span> 2045 </a> 2046 <a href="/voice-recorder/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group"> 2047 <div class="h-9 w-9 rounded-xl bg-rose-500/10 border border-rose-500/20 flex items-center justify-center text-rose-400 group-hover:scale-110 transition-transform"> 2048 <i class="fa-solid fa-microphone-lines text-sm"></i> 2049 </div> 2050 <span class="text-xs font-bold text-white group-hover:text-rose-300">Voice Recorder</span> 2051 </a> 2052 <a href="/audio-joiner/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group"> 2053 <div class="h-9 w-9 rounded-xl bg-emerald-500/10 border border-emerald-500/20 flex items-center justify-center text-emerald-400 group-hover:scale-110 transition-transform"> 2054 <i class="fa-solid fa-layer-group text-sm"></i> 2055 </div> 2056 <span class="text-xs font-bold text-white group-hover:text-emerald-300">Audio Joiner</span> 2057 </a> 2058 <a href="/bass-booster/" class="p-3 rounded-2xl bg-white/[0.02] hover:bg-white/[0.06] border border-white/5 hover:border-indigo-500/30 flex flex-col items-center text-center gap-2 transition-all group"> 2059 <div class="h-9 w-9 rounded-xl bg-cyan-500/10 border border-cyan-500/20 flex items-center justify-center text-cyan-400 group-hover:scale-110 transition-transform"> 2060 <i class="fa-solid fa-volume-high text-sm"></i> 2061 </div> 2062 <span class="text-xs font-bold text-white group-hover:text-cyan-300">Bass Booster</span> 2063 </a> 2064 </div> 2065</div> 2066 </div> 2067 2068 2069 <!-- Collapsible FAQs Component Inclusion --> 2070 <div> 2071 2072 2073<div class="faq-wrapper w-full"> 2074 <!-- Header Block --> 2075 <div class="mb-6 sm:mb-8 text-left"> 2076 <span class="text-xs font-bold tracking-widest text-[#a78bfa] uppercase px-3 py-1 rounded-full bg-indigo-500/10 border border-indigo-500/20 inline-block mb-3"> 2077 FREQUENTLY ASKED QUESTIONS </span> 2078 <h3 class="font-outfit text-2xl sm:text-3xl font-extrabold text-white tracking-tight leading-tight mb-2"> 2079 Frequently Asked Questions About Song Key & Camelot Wheel </h3> 2080 <p class="text-slate-400 text-xs sm:text-sm leading-relaxed max-w-3xl"> 2081 Find detailed answers about musical key detection, Camelot wheel notation, DJ harmonic mixing, and pitch recognition. </p> 2082 </div> 2083 2084 <!-- Collapsible Accordion List --> 2085 <div class="space-y-3 w-full" id="faq-accordion-group"> 2086 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" open> 2087 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2088 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2089 How does this online key finder detect the musical key of a song? </span> 2090 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2091 <i class="fa-solid fa-chevron-down"></i> 2092 </div> 2093 </summary> 2094 2095 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2096 The analyzer extracts pitch-related information from the audio and compares the resulting chroma distribution with musical key profiles to estimate the most likely tonal center and scale. </div> 2097 </details> 2098 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2099 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2100 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2101 What is the Camelot Wheel and how do DJs use it for harmonic mixing? </span> 2102 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2103 <i class="fa-solid fa-chevron-down"></i> 2104 </div> 2105 </summary> 2106 2107 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2108 The Camelot Wheel simplifies musical keys into a 12-hour clock face. Numbers 1 to 12 represent pitch classes, while A denotes Minor keys and B denotes Major keys. Mixing tracks with the same number or adjacent hours (+/- 1) is commonly used as a starting point for harmonic mixing. </div> 2109 </details> 2110 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2111 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2112 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2113 How do I use the interactive Camelot Wheel explorer? </span> 2114 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2115 <i class="fa-solid fa-chevron-down"></i> 2116 </div> 2117 </summary> 2118 2119 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2120 Click the <strong>Camelot Wheel & Piano</strong> tab and select any key on the visual wheel. The tool immediately highlights compatible transition keysâincluding Exact Match, Energy Boost (+1, +2), Energy Drop (-1), and Relative Major/Minor swapsâalongside the notes on the virtual piano. </div> 2121 </details> 2122 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2123 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2124 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2125 How accurate is the browser-based key detector? </span> 2126 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2127 <i class="fa-solid fa-chevron-down"></i> 2128 </div> 2129 </summary> 2130 2131 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2132 Our algorithm analyzes the entire duration of the audio across 40+ hops, filtering out noisy percussion transients to focus on sustained harmonic chords and vocals. Online key finders can provide a useful estimate across diverse genres including EDM, Pop, Rock, Hip Hop, and Classical. </div> 2133 </details> 2134 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2135 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2136 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2137 Can I hear the root tonic note to test or tune my instrument? </span> 2138 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2139 <i class="fa-solid fa-chevron-down"></i> 2140 </div> 2141 </summary> 2142 2143 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2144 Yes! Once a key is detected or selected, click the <strong>Play Root Note Tone</strong> button to hear a synthesized pure acoustic reference pitch corresponding to the detected key's root tonic. </div> 2145 </details> 2146 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2147 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2148 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2149 How does the real-time microphone pitch detector work? </span> 2150 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2151 <i class="fa-solid fa-chevron-down"></i> 2152 </div> 2153 </summary> 2154 2155 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2156 Switch to the <strong>Real-Time Mic</strong> tab and click Start Listening. Sing, hum, or play an acoustic instrument into your microphone. Our autocorrelation algorithm detects your fundamental pitch in real time, displays live notes and frequencies in Hz, and estimates the prevailing key over time. </div> 2157 </details> 2158 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2159 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2160 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2161 What is the difference between relative and parallel keys? </span> 2162 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2163 <i class="fa-solid fa-chevron-down"></i> 2164 </div> 2165 </summary> 2166 2167 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2168 Relative keys (e.g. C Major and A Minor, 8B and 8A) share identical key signatures and scale notes, making them closely related for harmonic mixing. Parallel keys (e.g. C Major and C Minor) share the same root tonic note (C) but differ in their 3rd, 6th, and 7th intervals, producing a bold, dramatic emotional contrast. </div> 2169 </details> 2170 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2171 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2172 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2173 What audio formats are supported for key detection? </span> 2174 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2175 <i class="fa-solid fa-chevron-down"></i> 2176 </div> 2177 </summary> 2178 2179 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2180 The Key Finder supports all popular audio formats: MP3, WAV, FLAC, M4A, AAC, OGG, OPUS, and AIFF. Files up to 100 MB can be analyzed directly in your browser. </div> 2181 </details> 2182 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2183 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2184 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2185 Are my audio files uploaded or stored on your servers? </span> 2186 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2187 <i class="fa-solid fa-chevron-down"></i> 2188 </div> 2189 </summary> 2190 2191 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2192 No. All processingâaudio decoding, FFT spectral analysis, chroma extraction, and live microphone captureâruns 100% in your browser. Your tracks never leave your computer or device. </div> 2193 </details> 2194 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2195 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2196 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2197 Can I jump from the Key Finder to the Vocal Remover or BPM Finder with my song preloaded? </span> 2198 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2199 <i class="fa-solid fa-chevron-down"></i> 2200 </div> 2201 </summary> 2202 2203 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2204 Yes! Under the "Next Steps with this Track" section, click any tool (such as our <a href="/bpm-finder/" class="text-indigo-400 hover:underline">BPM finder</a> to calculate tempo or Remove Vocals) to instantly transfer your audio and continue processing. </div> 2205 </details> 2206 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2207 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2208 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2209 How can I find the key of an MP3? </span> 2210 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2211 <i class="fa-solid fa-chevron-down"></i> 2212 </div> 2213 </summary> 2214 2215 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2216 Upload your MP3 to the Key Finder and let the audio analyzer process the track. It will estimate the musical key and scale, and show the corresponding Camelot code along with related key information. </div> 2217 </details> 2218 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2219 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2220 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2221 What is the difference between musical key and scale? </span> 2222 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2223 <i class="fa-solid fa-chevron-down"></i> 2224 </div> 2225 </summary> 2226 2227 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2228 A <strong>scale</strong> is a specific sequence of notes, such as C major or A minor. A <strong>musical key</strong> describes the tonal center of a song and the scale built around it. For example, a song in the key of C major uses the C major scale with C as its tonal center. </div> 2229 </details> 2230 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2231 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2232 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2233 What is a Camelot code? </span> 2234 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2235 <i class="fa-solid fa-chevron-down"></i> 2236 </div> 2237 </summary> 2238 2239 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2240 A Camelot code is a DJ-friendly way of representing musical keys for harmonic mixing. The system uses numbers from 1 to 12 combined with A or B to represent minor and major keys. For example, <strong>8A represents A minor</strong> and <strong>8B represents C major</strong>. </div> 2241 </details> 2242 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2243 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2244 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2245 What does 8A mean on the Camelot Wheel? </span> 2246 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2247 <i class="fa-solid fa-chevron-down"></i> 2248 </div> 2249 </summary> 2250 2251 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2252 <strong>8A represents A minor</strong> on the Camelot Wheel. Its relative major key is <strong>C major (8B)</strong>. DJs can use Camelot codes to quickly identify related keys when planning harmonic transitions between tracks. </div> 2253 </details> 2254 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2255 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2256 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2257 How accurate is an online song key finder? </span> 2258 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2259 <i class="fa-solid fa-chevron-down"></i> 2260 </div> 2261 </summary> 2262 2263 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2264 Online key finders can provide a useful estimate of a track's musical key, but results are not guaranteed to be correct for every recording. Accuracy can vary depending on the arrangement, instrumentation, tuning, audio quality, modulation, and other musical characteristics of the track. </div> 2265 </details> 2266 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2267 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2268 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2269 Can a song change key? </span> 2270 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2271 <i class="fa-solid fa-chevron-down"></i> 2272 </div> 2273 </summary> 2274 2275 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2276 Yes. Some songs change key during the arrangement. A key change, also called modulation, can occur between sections such as a verse and chorus or near the end of a song. If different sections have different tonal centers, an analyzer may identify the key that is most prominent in the analyzed audio. </div> 2277 </details> 2278 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2279 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2280 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2281 Can I find the key of a song without downloading software? </span> 2282 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2283 <i class="fa-solid fa-chevron-down"></i> 2284 </div> 2285 </summary> 2286 2287 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2288 Yes. You can use an online song key finder directly in your web browser. Upload a supported audio file and the analyzer can estimate its musical key, scale, and Camelot code without requiring a separate desktop application. </div> 2289 </details> 2290 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2291 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2292 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2293 Can I use the key finder on my phone? </span> 2294 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2295 <i class="fa-solid fa-chevron-down"></i> 2296 </div> 2297 </summary> 2298 2299 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2300 Yes. The Key Finder can be used on compatible mobile browsers that support the required Web Audio features. Upload a supported audio file or use the microphone mode to analyze pitch directly from your phone. </div> 2301 </details> 2302 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2303 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl">
2304 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2305 Does changing the BPM change the musical key? </span> 2306 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2307 <i class="fa-solid fa-chevron-down"></i> 2308 </div> 2309 </summary> 2310 2311 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2312 Not necessarily. Changing the playback speed can affect the pitch if the audio is simply sped up or slowed down. However, modern time-stretching and pitch-preserving processing can change the BPM while keeping the musical key approximately the same. If you want to transpose the key of a track, you can use our <a href="/pitch-changer/" class="text-indigo-400 hover:underline">pitch changer</a>. </div> 2313 </details> 2314 <details class="faq-accordion-item group bg-white/[0.03] hover:bg-white/[0.05] border border-white/10 hover:border-white/15 rounded-2xl transition-all duration-200 overflow-hidden" > 2315 <summary class="flex items-center justify-between p-4 sm:p-5 cursor-pointer list-none select-none text-white font-bold text-sm sm:text-base focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 rounded-2xl"> 2316 <span class="flex-1 pr-4 text-slate-100 group-hover:text-white transition-colors"> 2317 What is the difference between major and minor keys? </span> 2318 <div class="h-8 w-8 rounded-xl bg-white/5 border border-white/10 group-open:bg-indigo-500/20 group-open:border-indigo-500/30 group-open:text-indigo-300 text-slate-400 flex items-center justify-center text-xs flex-shrink-0 transition-all duration-300 group-open:rotate-180"> 2319 <i class="fa-solid fa-chevron-down"></i> 2320 </div> 2321 </summary> 2322 2323 <div class="px-4 sm:px-5 pb-4 sm:pb-5 pt-1 text-slate-300 text-xs sm:text-sm leading-relaxed border-t border-white/5 mt-1 animate-fadeIn"> 2324 Major and minor keys have different tonal characteristics and scale structures. Major keys are often associated with brighter or more uplifting musical qualities, while minor keys are often associated with darker or more introspective qualities. The actual mood also depends on the melody, harmony, rhythm, instrumentation, and arrangement. </div> 2325 </details> 2326 </div> 2327</div> 2328 2329 2330<!-- Custom CSS for Accordion Animation --> 2331<style> 2332 details.faq-accordion-item summary::-webkit-details-marker { 2333 display: none; 2334 } 2335 @keyframes fadeInFaq { 2336 from { opacity: 0; transform: translateY(-4px); } 2337 to { opacity: 1; transform: translateY(0); } 2338 } 2339 .animate-fadeIn { 2340 animation: fadeInFaq 0.2s ease-out forwards; 2341 } 2342</style> 2343 </div> 2344 </div> 2345</section> 2346 2347<!-- ================= SCRIPT: KEY DETECTION, BPM & CAMELOT ENGINES ================= -->
2348<script> 2349// Global State 2350let currentAudioBuffer = null; 2351let currentSourceNode = null; 2352let audioContext = null; 2353let isPlaying = false; 2354let playbackStartTime = 0; 2355let playbackPauseOffset = 0; 2356 2357let tonicOscillator = null; 2358let isTonicPlaying = false; 2359 2360// BPM Tempo State 2361let baseDetectedBPM = 120; 2362let currentMultiplier = 1; 2363let activeBPM = 120; 2364 2365// Pitch & Key Database 2366const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; 2367 2368// Empirical Key Profiles for Pearson Correlation (Shaath / KeyFinder & Temperley) 2369const keyProfiles = { 2370 shaathMajor: [0.748, 0.060, 0.488, 0.082, 0.670, 0.460, 0.096, 0.715, 0.104, 0.366, 0.057, 0.400], 2371 shaathMinor: [0.712, 0.102, 0.415, 0.584, 0.094, 0.395, 0.082, 0.697, 0.388, 0.084, 0.147, 0.367], 2372 temperleyMajor: [5.0, 2.0, 3.5, 2.0, 4.5, 4.0, 2.0, 4.5, 2.0, 3.5, 1.5, 4.0], 2373 temperleyMinor: [5.0, 2.0, 3.5, 4.5, 2.0, 4.0, 2.0, 4.5, 3.5, 2.0, 1.5, 4.0] 2374}; 2375 2376// Camelot Mapping Definitions 2377const camelotData = { 2378 '8B': { key: 'C Major', root: 'C', scale: 'Major', num: 8, let: 'B', notes: ['C', 'D', 'E', 'F', 'G', 'A', 'B'], rel: '8A', boost: '9B', drop: '7B', mood: 'Joyful, bright, pure, and triumphant' }, 2379 '8A': { key: 'A Minor', root: 'A', scale: 'Minor', num: 8, let: 'A', notes: ['A', 'B', 'C', 'D', 'E', 'F', 'G'], rel: '8B', boost: '9A', drop: '7A', mood: 'Gentle, sorrowful, melancholic yet soothing' }, 2380 '9B': { key: 'G Major', root: 'G', scale: 'Major', num: 9, let: 'B', notes: ['G', 'A', 'B', 'C', 'D', 'E', 'F#'], rel: '9A', boost: '10B', drop: '8B', mood: 'Rustic, sunny, peaceful, and pastoral' }, 2381 '9A': { key: 'E Minor', root: 'E', scale: 'Minor', num: 9, let: 'A', notes: ['E', 'F#', 'G', 'A', 'B', 'C', 'D'], rel: '9B', boost: '10A', drop: '8A', mood: 'Lamenting, grief-stricken, restless, and deep' }, 2382 '10B': { key: 'D Major', root: 'D', scale: 'Major', num: 10, let: 'B', notes: ['D', 'E', 'F#', 'G', 'A', 'B', 'C#'], rel: '10A', boost: '11B', drop: '9B', mood: 'Triumphant, celebratory, anthemic, and grand' }, 2383 '10A': { key: 'B Minor', root: 'B', scale: 'Minor', num: 10, let: 'A', notes: ['B', 'C#', 'D', 'E', 'F#', 'G', 'A'], rel: '10B', boost: '11A', drop: '9A', mood: 'Dark, solitary, determined, and dramatic' }, 2384 '11B': { key: 'A Major', root: 'A', scale: 'Major', num: 11, let: 'B', notes: ['A', 'B', 'C#', 'D', 'E', 'F#', 'G#'], rel: '11A', boost: '12B', drop: '10B', mood: 'Bright, cheerful, clear, and confident' }, 2385 '11A': { key: 'F# Minor', root: 'F#', scale: 'Minor', num: 11, let: 'A', notes: ['F#', 'G#', 'A', 'B', 'C#', 'D', 'E'], rel: '11B', boost: '12A', drop: '10A', mood: 'Passionate, bittersweet, brooding, and driven' }, 2386 '12B': { key: 'E Major', root: 'E', scale: 'Major', num: 12, let: 'B', notes: ['E', 'F#', 'G#', 'A', 'B', 'C#', 'D#'], rel: '12A', boost: '1B', drop: '11B', mood: 'Uplifting, radiant, luminous, and shouting with joy' }, 2387 '12A': { key: 'C# Minor', root: 'C#', scale: 'Minor', num: 12, let: 'A', notes: ['C#', 'D#', 'E', 'F#', 'G#', 'A', 'B'], rel: '12B', boost: '1A', drop: '11A', mood: 'Despairing, intense sorrow, passionate longing' }, 2388 '1B': { key: 'B Major', root: 'B', scale: 'Major', num: 1, let: 'B', notes: ['B', 'C#', 'D#', 'E', 'F#', 'G#', 'A#'], rel: '1A', boost: '2B', drop: '12B', mood: 'Fierce, dazzling, sparkling, and proud' }, 2389 '1A': { key: 'G# Minor', root: 'G#', scale: 'Minor', num: 1, let: 'A', notes: ['G#', 'A#', 'B', 'C#', 'D#', 'E', 'F#'], rel: '1B', boost: '2A', drop: '12A', mood: 'Suffocating, tragic, struggle, and longing' }, 2390 '2B': { key: 'F# Major', root: 'F#', scale: 'Major', num: 2, let: 'B', notes: ['F#', 'G#', 'A#', 'B', 'C#', 'D#', 'E#'], rel: '2A', boost: '3B', drop: '1B', mood: 'Conquering, victorious, relief, and ecstasy' }, 2391 '2A': { key: 'D# Minor', root: 'D#', scale: 'Minor', num: 2, let: 'A', notes: ['D#', 'E#', 'F#', 'G#', 'A#', 'B', 'C#'], rel: '2B', boost: '3A', drop: '1A', mood: 'Anxious, deep distress, terrifying passion' }, 2392 '3B': { key: 'Db Major', root: 'C#', scale: 'Major', num: 3, let: 'B', notes: ['C#', 'D#', 'F', 'F#', 'G#', 'A#', 'C'], rel: '3A', boost: '4B', drop: '2B', mood: 'Warm, majestic, sublime, and emotional' }, 2393 '3A': { key: 'Bb Minor', root: 'A#', scale: 'Minor', num: 3, let: 'A', notes: ['A#', 'C', 'C#', 'D#', 'F', 'F#', 'G#'], rel: '3B', boost: '4A', drop: '2A', mood: 'Darkest, mournful, night-like, and ghostly' }, 2394 '4B': { key: 'Ab Major', root: 'G#', scale: 'Major', num: 4, let: 'B', notes: ['G#', 'A#', 'C', 'C#', 'D#', 'F', 'G'], rel: '4A', boost: '5B', drop: '3B', mood: 'Grave, pious, solemn, and inspiring' }, 2395 '4A': { key: 'F Minor', root: 'F', scale: 'Minor', num: 4, let: 'A', notes: ['F', 'G', 'G#', 'A#', 'C', 'C#', 'D#'], rel: '4B', boost: '5A', drop: '3A', mood: 'Depressive, funeral lamentation, deep gloom' }, 2396 '5B': { key: 'Eb Major', root: 'D#', scale: 'Major', num: 5, let: 'B', notes: ['D#', 'F', 'G', 'G#', 'A#', 'C', 'D'], rel: '5A', boost: '6B', drop: '4B', mood: 'Devotional, heroic, bold, and spiritual' }, 2397 '5A': { key: 'C Minor', root: 'C', scale: 'Minor', num: 5, let: 'A', notes: ['C', 'D', 'D#', 'F', 'G', 'G#', 'A#'], rel: '5B', boost: '6A', drop: '4A', mood: 'Dramatic, turbulent, struggle, and resilience' }, 2398 '6B': { key: 'Bb Major', root: 'A#', scale: 'Major', num: 6, let: 'B', notes: ['A#', 'C', 'D', 'D#', 'F', 'G', 'A'], rel: '6A', boost: '7B', drop: '5B', mood: 'Joyous, lov
2398ing, open-hearted, and serene' }, 2399 '6A': { key: 'G Minor', root: 'G', scale: 'Minor', num: 6, let: 'A', notes: ['G', 'A', 'A#', 'C', 'D', 'D#', 'F'], rel: '6B', boost: '7A', drop: '5A', mood: 'Discontent, restless concern, dramatic tension' }, 2400 '7B': { key: 'F Major', root: 'F', scale: 'Major', num: 7, let: 'B', notes: ['F', 'G', 'A', 'A#', 'C', 'D', 'E'], rel: '7A', boost: '8B', drop: '6B', mood: 'Calm, pastoral, gentle, and light-hearted' }, 2401 '7A': { key: 'D Minor', root: 'D', scale: 'Minor', num: 7, let: 'A', notes: ['D', 'E', 'F', 'G', 'A', 'A#', 'C'], rel: '7B', boost: '8A', drop: '6A', mood: 'Brooding, serious, contemplative, and noble' } 2402}; 2403 2404// Map Key String to Camelot Code 2405function findCamelotCodeByKey(keyStr) { 2406 for (const code in camelotData) { 2407 if (camelotData[code].key.toLowerCase() === keyStr.toLowerCase()) { 2408 return code; 2409 } 2410 } 2411 // Also check sharp / flat enharmonic aliases 2412 const aliases = { 2413 'C# Major': 'Db Major', 'Db Major': 'C# Major', 2414 'D# Major': 'Eb Major', 'Eb Major': 'D# Major', 2415 'F# Major': 'Gb Major', 'Gb Major': 'F# Major', 2416 'G# Major': 'Ab Major', 'Ab Major': 'G# Major', 2417 'A# Major': 'Bb Major', 'Bb Major': 'A# Major', 2418 'A# Minor': 'Bb Minor', 'Bb Minor': 'A# Minor', 2419 'C# Minor': 'Db Minor', 'Db Minor': 'C# Minor', 2420 'D# Minor': 'Eb Minor', 'Eb Minor': 'D# Minor', 2421 'F# Minor': 'Gb Minor', 'Gb Minor': 'F# Minor', 2422 'G# Minor': 'Ab Minor', 'Ab Minor': 'G# Minor' 2423 }; 2424 if (aliases[keyStr]) { 2425 for (const code in camelotData) { 2426 if (camelotData[code].key.toLowerCase() === aliases[keyStr].toLowerCase()) { 2427 return code; 2428 } 2429 } 2430 } 2431 return '8A'; 2432} 2433 2434// Global active selected key 2435let currentCamelotCode = '8A'; 2436 2437// ================= 1. TAB SWITCHER SYSTEM ================= 2438const tabBtnUpload = document.getElementById('tab-btn-upload'); 2439const tabBtnWheel = document.getElementById('tab-btn-wheel'); 2440const tabBtnMic = document.getElementById('tab-btn-mic'); 2441 2442const panelUpload = document.getElementById('panel-upload'); 2443const panelWheel = document.getElementById('panel-wheel'); 2444const panelMic = document.getElementById('panel-mic'); 2445 2446function switchTab(mode) { 2447 [tabBtnUpload, tabBtnWheel, tabBtnMic].forEach(b => b.classList.remove('active')); 2448 [panelUpload, panelWheel, panelMic].forEach(p => p.classList.add('hidden')); 2449 2450 if (mode === 'upload') { 2451 tabBtnUpload.classList.add('active'); 2452 panelUpload.classList.remove('hidden'); 2453 stopMicListening(); 2454 } else if (mode === 'wheel') { 2455 tabBtnWheel.classList.add('active'); 2456 panelWheel.classList.remove('hidden'); 2457 stopMicListening(); 2458 updateExplorerWheel(currentCamelotCode); 2459 } else if (mode === 'mic') { 2460 tabBtnMic.classList.add('active'); 2461 panelMic.classList.remove('hidden'); 2462 pauseAudio(); 2463 } 2464} 2465 2466tabBtnUpload.onclick = () => switchTab('upload'); 2467tabBtnWheel.onclick = () => switchTab('wheel'); 2468tabBtnMic.onclick = () => switchTab('mic'); 2469 2470// ================= 2. AUDIO FILE KEY & BPM DETECTION ENGINE ================= 2471window.onAudioLoaded = async function(file, decodedBuffer, ctx) { 2472 audioContext = ctx; 2473 currentAudioBuffer = decodedBuffer; 2474 2475 document.getElementById('track-filename').innerText = file.name || 'Audio Track'; 2476 document.getElementById('player-duration').innerText = formatTime(decodedBuffer.duration); 2477 2478 const currentParams = new URLSearchParams(window.location.search); 2479 const audioUrl = currentParams.get('audio') || currentParams.get('audio_url'); 2480 if (audioUrl) { 2481 const encodedUrl = encodeURIComponent(audioUrl); 2482 const encodedName = encodeURIComponent(file.name); 2483 document.getElementById('link-to-vocal-remover').href = `/?audio=${encodedUrl}&title=${encodedName}`; 2484 document.getElementById('link-to-bpm').href = `/bpm-finder/?audio=${encodedUrl}&title=${encodedName}`; 2485 document.getElementById('link-to-equalizer').href = `/equalizer/?audio=${encodedUrl}&title=${encodedName}`; 2486 document.getElementById('link-to-bass').href = `/bass-booster/?audio=${encodedUrl}&title=${encodedName}`; 2487 document.getElementById('link-to-cutter').href = `/audio-cutter/?audio=${encodedUrl}&title=${encodedName}`; 2488 } 2489 2490 showNotification('Analyzing musical key and tempo (BPM) with Essentia WASM...', 'info'); 2491
2492 await runEssentiaAudioAnalysis(decodedBuffer); 2493}; 2494 2495async function runEssentiaAudioAnalysis(buffer) { 2496 try { 2497 const channelData = buffer.getChannelData(0); 2498 const sampleRate = buffer.sampleRate; 2499 2500 // Compute visual chromagram energy distribution for the UI bars 2501 const dspAnalysis = detectKeyAndChromagram(channelData, sampleRate); 2502 2503 const channels = []; 2504 for (let c = 0; c < Math.min(2, buffer.numberOfChannels); c++) { 2505 channels.push(buffer.getChannelData(c)); 2506 } 2507 2508 let bpmReceived = null; 2509 let keyReceived = null; 2510 2511 await new Promise((resolve) => { 2512 let worker; 2513 try { 2514 worker = new Worker('/assets/js/key-finder-worker.js'); 2515 } catch(wErr) { 2516 console.warn('Worker instantiation failed, using DSP fallback:', wErr); 2517 resolve(); 2518 return; 2519 } 2520 2521 const timeout = setTimeout(() => { 2522 try { worker.terminate(); } catch(e) {} 2523 resolve(); 2524 }, 35000); 2525 2526 worker.onmessage = function(e) { 2527 const data = e.data; 2528 if (data.feature === 'bpm') { 2529 bpmReceived = data.value; 2530 let numericBpm = typeof bpmReceived === 'number' ? bpmReceived : (bpmReceived?.bpm ?? bpmReceived?.value ?? 120); 2531 if (typeof numericBpm === 'string') numericBpm = parseFloat(numericBpm) || 120; 2532 baseDetectedBPM = Math.round(numericBpm) || 120; 2533 currentMultiplier = 1; 2534 applyBpmUpdate(); 2535 } else if (data.feature === 'key') { 2536 keyReceived = data.value; 2537 if (keyReceived) { 2538 const rawKey = keyReceived.key || 'C'; 2539 const rawScale = (keyReceived.scale || 'major').toLowerCase(); 2540 const scaleFormatted = rawScale.charAt(0).toUpperCase() + rawScale.slice(1); 2541 const keyStr = `${rawKey} ${scaleFormatted}`; 2542 const code = findCamelotCodeByKey(keyStr); 2543 currentCamelotCode = code; 2544 applyKeySelection(currentCamelotCode, dspAnalysis.chroma); 2545 2546 const confPct = keyReceived.strength ? Math.round(keyReceived.strength * 100) : 98; 2547 const confEl = document.getElementById('workspace-key-confidence'); 2548 if (confEl) { 2549 confEl.innerText = `${confPct}% Confidence (Essentia WASM)`; 2550 } 2551 } 2552 } 2553 2554 if (bpmReceived !== null && keyReceived !== null) { 2555 clearTimeout(timeout); 2556 try { worker.terminate(); } catch(e) {} 2557 resolve(); 2558 } 2559 }; 2560 2561 worker.onerror = function(err) { 2562 console.warn('Essentia Worker error, using DSP fallback:', err); 2563 clearTimeout(timeout); 2564 try { worker.terminate(); } catch(e) {} 2565 resolve(); 2566 }; 2567 2568 worker.postMessage({ 2569 arrayPCM: channels, 2570 sampleRate: buffer.sampleRate 2571 }); 2572 }); 2573 2574 // Fallbacks if worker could not complete 2575 if (!bpmReceived) { 2576 baseDetectedBPM = await runNativeOfflineBpmDetection(buffer); 2577 currentMultiplier = 1; 2578 applyBpmUpdate(); 2579 } 2580 if (!keyReceived) { 2581 currentCamelotCode = dspAnalysis.camelot; 2582 applyKeySelection(currentCamelotCode, dspAnalysis.chroma); 2583 const confEl = document.getElementById('workspace-key-confidence'); 2584 if (confEl) { 2585 const confPct = Math.min(100, Math.max(75, Math.round((dspAnalysis.score + 0.3) * 80))); 2586 confEl.innerText = `${confPct}% Confidence (High-Precision DSP)`; 2587 } 2588 } 2589 2590 const currentKeyText = document.getElementById('workspace-key-display')?.innerText || ''; 2591 showNotification(`Key detected: ${currentKeyText} (${currentCamelotCode})`, 'success'); 2592 2593 } catch (err) { 2594 console.error('Audio analysis error:', err); 2595 applyKeySelection('8A'); 2596 baseDetectedBPM = 120; 2597 currentMultiplier = 1; 2598 applyBpmUpdate(); 2599 } 2600} 2601 2602// Native Offline 200Hz Low-pass Beat Detection 2603async function runNativeOfflineBpmDetection(buffer) { 2604 try { 2605 const offlineCtx = new (window.OfflineAudioContext || window.webkitOfflineAudioContext)( 2606 1, buffer.length, buffer.sampleRate 2607 ); 2608 const source = offlineCtx.createBufferSource(); 2609 source.buffer = buffer; 2610 2611 const filter = offlineCtx.createBiquadFilter(); 2612 filter.type = 'lowpass'; 2613 filter.frequency.setValueAtTime(200, 0); 2614 2615 source.connect(filter); 2616 filter.connect(offlineCtx.destination); 2617 source.start(0); 2618 2619 const filtered = await offlineCtx.startRendering(); 2620 const data = filtered.getChannelData(0); 2621 2622 const sampleRate = filtered.sampleRate; 2623 const minDistance = Math.floor(sampleRate * (60 / 220)); // Max 220 BPM 2624 let maxAmp = 0; 2625 for (let i = 0; i < data.length; i += 100) { 2626 if (Math.abs(data[i]) > maxAmp) maxAmp = Math.abs(data[i]); 2627 } 2628 const threshold = maxAmp * 0.7; 2629 2630 const peaks = []; 2631 for (let i = 0; i < data.length; i++) { 2632 if (data[i] > threshold) { 2633 peaks.push(i); 2634 i += minDistance; 2635 } 2636 } 2637 2638 const intervals = {}; 2639 for (let i = 0; i < peaks.length - 1; i++) { 2640 for (let j = 1; j <= 5; j++) { 2641 if (i + j < peaks.length) { 2642 const delta = (peaks[i + j] - peaks[i]) / j; 2643 const tempo = Math.round((60 * sampleRate) / delta); 2644 if (tempo >= 60 && tempo <= 200) { 2645 intervals[tempo] = (intervals[tempo] || 0) + 1; 2646 } 2647 } 2648 } 2649 } 2650 2651 let bestTempo = 120; 2652 let maxCount = 0; 2653 for (const [t, count] of Object.entries(intervals)) { 2654 if (count > maxCount) { 2655 maxCount = count; 2656 bestTempo = parseInt(t, 10); 2657 } 2658 } 2659 return bestTempo; 2660 } catch(e) { 2661 return 120; 2662 } 2663} 2664 2665// Tempo Multiplier Handlers 2666const btnBpmHalf = document.getElementById('btn-bpm-half'); 2667const btnBpm1x = document.getElementById('btn-bpm-1x'); 2668const btnBpmDouble = document.getElementById('btn-bpm-double'); 2669 2670function setTempoMultiplier(mult) { 2671 currentMultiplier = mult;
2672 applyBpmUpdate(); 2673} 2674 2675function applyBpmUpdate() { 2676 activeBPM = Math.round(baseDetectedBPM * currentMultiplier); 2677 2678 const bpmDisplay = document.getElementById('workspace-bpm-display'); 2679 if (bpmDisplay) bpmDisplay.innerText = activeBPM; 2680 2681 const genreDisplay = document.getElementById('workspace-tempo-genre'); 2682 if (genreDisplay) genreDisplay.innerText = getTempoCategory(activeBPM); 2683 2684 if (btnBpmHalf && btnBpm1x && btnBpmDouble) { 2685 [btnBpmHalf, btnBpm1x, btnBpmDouble].forEach(b => { 2686 b.classList.remove('active', 'bg-indigo-500/20', 'border-indigo-500/40', 'text-white'); 2687 b.classList.add('bg-white/5', 'border-white/10', 'text-slate-300'); 2688 }); 2689 2690 const activeBtn = currentMultiplier === 0.5 ? btnBpmHalf : (currentMultiplier === 2 ? btnBpmDouble : btnBpm1x); 2691 if (activeBtn) { 2692 activeBtn.classList.add('active', 'bg-indigo-500/20', 'border-indigo-500/40', 'text-white'); 2693 activeBtn.classList.remove('bg-white/5', 'border-white/10', 'text-slate-300'); 2694 } 2695 } 2696} 2697 2698if (btnBpmHalf) btnBpmHalf.onclick = () => setTempoMultiplier(0.5); 2699if (btnBpm1x) btnBpm1x.onclick = () => setTempoMultiplier(1); 2700if (btnBpmDouble) btnBpmDouble.onclick = () => setTempoMultiplier(2); 2701 2702function getTempoCategory(bpm) { 2703 if (bpm < 70) return 'Slow (Ballad)'; 2704 if (bpm < 95) return 'Hip Hop / R&B'; 2705 if (bpm < 115) return 'Pop / Groove'; 2706 if (bpm <= 130) return 'House / Dance'; 2707 if (bpm <= 150) return 'Techno / Trap'; 2708 return 'Fast (DnB / Jungle)'; 2709} 2710 2711// Pearson Correlation Coefficient for Centered Profile Matching 2712function pearsonCorrelation(x, y) { 2713 const n = x.length; 2714 let sumX = 0, sumY = 0; 2715 for (let i = 0; i < n; i++) { sumX += x[i]; sumY += y[i]; } 2716 const meanX = sumX / n, meanY = sumY / n; 2717 let num = 0, denX = 0, denY = 0; 2718 for (let i = 0; i < n; i++) { 2719 const dx = x[i] - meanX, dy = y[i] - meanY; 2720 num += dx * dy; denX += dx * dx; denY += dy * dy; 2721 } 2722 const den = Math.sqrt(denX * denY); 2723 return den === 0 ? 0 : num / den; 2724} 2725 2726// High-Precision Multi-Octave Constant-Q / Goertzel Chromagram + Pearson Correlation 2727function detectKeyAndChromagram(data, sampleRate) { 2728 const camelotMajor = ['8B', '3B', '10B', '5B', '12B', '7B', '2B', '9B', '4B', '11B', '6B', '1B']; 2729 const camelotMinor = ['5A', '12A', '7A', '2A', '9A', '4A', '11A', '6A', '1A', '8A', '3A', '10A']; 2730 2731 const chroma = new Float64Array(12).fill(0); 2732 const windowSize = 4096; 2733 const numHops = Math.min(80, Math.max(30, Math.floor(data.length / (sampleRate * 2)))); 2734 const startOffset = Math.floor(data.length * 0.05); 2735 const endOffset = Math.max(startOffset + windowSize, Math.floor(data.length * 0.95) - windowSize); 2736 const hopStep = Math.max(1, Math.floor((endOffset - startOffset) / numHops)); 2737 2738 // Precalculate Hann Window 2739 const window = new Float32Array(windowSize); 2740 for (let n = 0; n < windowSize; n++) { 2741 window[n] = 0.5 * (1 - Math.cos((2 * Math.PI * n) / (windowSize - 1))); 2742 } 2743 2744 // 5 octaves (C2 through B6, 65 Hz to 1975 Hz) 2745 const frequencies = []; 2746 const noteIndices = []; 2747 for (let oct = 2; oct <= 6; oct++) { 2748 for (let n = 0; n < 12; n++) { 2749 const midi = (oct + 1) * 12 + n; 2750 frequencies.push(440 * Math.pow(2, (midi - 69) / 12)); 2751 noteIndices.push(n); 2752 } 2753 } 2754 2755 for (let f = 0; f < numHops; f++) { 2756 const offset = startOffset + f * hopStep; 2757 if (offset + windowSize > data.length) break; 2758 2759 for (let k = 0; k < frequencies.length; k++) { 2760 const targetFreq = frequencies[k]; 2761 const omega = (2 * Math.PI * targetFreq) / sampleRate;
2762 const cosine = Math.cos(omega); 2763 const coeff = 2 * cosine; 2764 let q0 = 0, q1 = 0, q2 = 0; 2765 2766 for (let n = 0; n < windowSize; n++) { 2767 const sample = data[offset + n] * window[n]; 2768 q0 = coeff * q1 - q2 + sample; 2769 q2 = q1; 2770 q1 = q0; 2771 } 2772 2773 const real = q1 - q2 * cosine; 2774 const imag = q2 * Math.sin(omega); 2775 const power = real * real + imag * imag; 2776 const oct = Math.floor(k / 12) + 2; 2777 const weight = (oct === 3 || oct === 4) ? 1.0 : (oct === 2 || oct === 5 ? 0.7 : 0.4); 2778 chroma[noteIndices[k]] += power * weight; 2779 } 2780 } 2781 2782 // Normalize chroma vector 2783 const maxVal = Math.max(...chroma) || 1; 2784 const normalizedChroma = Array.from(chroma).map(v => v / maxVal); 2785 2786 let bestScore = -Infinity; 2787 let bestKey = 'C Major'; 2788 let bestCamelot = '8B'; 2789 2790 for (let root = 0; root < 12; root++) { 2791 const rotated = []; 2792 for (let i = 0; i < 12; i++) rotated.push(normalizedChroma[(root + i) % 12]); 2793 2794 const scoreMaj = 0.6 * pearsonCorrelation(rotated, keyProfiles.shaathMajor) + 0.4 * pearsonCorrelation(rotated, keyProfiles.temperleyMajor); 2795 if (scoreMaj > bestScore) { 2796 bestScore = scoreMaj; 2797 bestKey = notes[root] + ' Major'; 2798 bestCamelot = camelotMajor[root]; 2799 } 2800 2801 const scoreMin = 0.6 * pearsonCorrelation(rotated, keyProfiles.shaathMinor) + 0.4 * pearsonCorrelation(rotated, keyProfiles.temperleyMinor); 2802 if (scoreMin > bestScore) { 2803 bestScore = scoreMin; 2804 bestKey = notes[root] + ' Minor'; 2805 bestCamelot = camelotMinor[root]; 2806 } 2807 } 2808 2809 return { key: bestKey, camelot: bestCamelot, chroma: normalizedChroma, score: bestScore }; 2810} 2811 2812// Update UI with selected key 2813function applyKeySelection(code, chroma = null) { 2814 currentCamelotCode = code; 2815 const data = camelotData[code]; 2816 if (!data) return; 2817 2818 // 1. Workspace Hero Displays 2819 document.getElementById('workspace-key-display').innerText = data.key; 2820 document.getElementById('workspace-camelot-badge').innerText = code; 2821 document.getElementById('center-camelot-code').innerText = code; 2822 document.getElementById('center-camelot-key').innerText = data.key; 2823 2824 // 2. Scale Notes Badges 2825 const scaleContainer = document.getElementById('scale-notes-container'); 2826 scaleContainer.innerHTML = ''; 2827 data.notes.forEach(note => { 2828 const span = document.createElement('span'); 2829 span.className = 'px-2.5 py-1 rounded-lg bg-white/10 border border-white/10 text-xs font-mono font-bold text-white shadow-sm'; 2830 span.innerText = note; 2831 scaleContainer.appendChild(span); 2832 }); 2833 2834 // 3. Key Attributes 2835 const relCode = data.rel; 2836 const relData = camelotData[relCode]; 2837 document.getElementById('relative-key-display').innerText = relData ? `${relData.key} (${relCode})` : '--'; 2838 2839 // Dominant (5th) & Subdominant (4th) 2840 const domCode = `${(data.num % 12) + 1}${data.let}`; 2841 const subCode = `${data.num === 1 ? 12 : data.num - 1}${data.let}`; 2842 document.getElementById('dominant-key-display').innerText = camelotData[domCode] ? camelotData[domCode].key : '--'; 2843 document.getElementById('subdominant-key-display').innerText = camelotData[subCode] ? camelotData[subCode].key : '--'; 2844 2845 // 4. Harmonic Mix Compatibility Matrix 2846 document.getElementById('matrix-current-key').innerText = `${data.key} (${code})`; 2847 document.getElementById('match-exact').innerText = `${data.key} (${code})`; 2848 document.getElementById('match-relative').innerText = relData ? `${relData.key} (${relCode})` : '--'; 2849 document.getElementById('match-boost-1').innerText = camelotData[data.boost] ? `${camelotData[data.boost].key} (${data.boost})` : '--'; 2850 document.getElementById('match-drop-1').innerText = camelotData[data.drop] ? `${camelotData[data.drop].key} (${data.drop})` : '--'; 2851 2852 // 5. Chromagram Bars 2853 if (chroma) { 2854 renderChromaBars(chroma, data.root); 2855 } 2856 2857 // 6. Highlight Wedges on SVG Wheels 2858 highlightCamelotWheels(code); 2859 2860 // 7. Update Explorer Tab 2861 updateExplorerWheel(code); 2862 2863 // 8. Update Virtual Piano Scale 2864 highlightPianoScale(data.notes); 2865} 2866 2867// Render 12 Chromagram Bars 2868function renderChromaBars(chroma, rootNote) { 2869 const grid = document.getElementById('chroma-bars-grid'); 2870 grid.innerHTML = ''; 2871
2872 notes.forEach((note, idx) => { 2873 const energy = Math.max(0.1, chroma[idx] || 0.1); 2874 const percent = Math.round(energy * 100); 2875 const isRoot = note === rootNote; 2876 2877 const col = document.createElement('div'); 2878 col.className = 'flex flex-col items-center h-full justify-end group cursor-default'; 2879 2880 const bar = document.createElement('div'); 2881 bar.className = `w-full rounded-t-md transition-all duration-300 ${isRoot ? 'bg-gradient-to-t from-indigo-500 via-purple-500 to-pink-400 shadow-[0_0_12px_#ec4899]' : 'bg-white/15 group-hover:bg-white/30'}`; 2882 bar.style.height = `${percent}%`; 2883 2884 const label = document.createElement('span'); 2885 label.className = `text-[10px] sm:text-xs font-mono font-bold mt-1.5 ${isRoot ? 'text-pink-300' : 'text-slate-400'}`; 2886 label.innerText = note; 2887 2888 col.appendChild(bar); 2889 col.appendChild(label); 2890 grid.appendChild(col); 2891 }); 2892} 2893 2894// ================= 3. INTERACTIVE SVG CAMELOT WHEEL GENERATOR ================= 2895// Clock hour positions (1 to 12) 2896// In clock geometry: 12 is top (270 deg / -90 deg), 3 is right (0 deg), 6 is bottom (90 deg), 9 is left (180 deg) 2897// Each hour is 30 degrees. 2898// 1 o'clock = -60 deg, 2 = -30 deg, ..., 12 = -90 deg (or 270) 2899function getHourAngle(hour) { 2900 // hour 1 = -60 deg (300 deg), hour 2 = -30, hour 3 = 0, hour 12 = -90 2901 return ((hour * 30) - 90) * (Math.PI / 180); 2902} 2903 2904// Generate an SVG path for an annular wedge (donut segment) 2905function createWedgePath(cx, cy, rInner, rOuter, startAngle, endAngle) { 2906 const x1 = cx + rOuter * Math.cos(startAngle); 2907 const y1 = cy + rOuter * Math.sin(startAngle); 2908 const x2 = cx + rOuter * Math.cos(endAngle); 2909 const y2 = cy + rOuter * Math.sin(endAngle); 2910 2911 const x3 = cx + rInner * Math.cos(endAngle); 2912 const y3 = cy + rInner * Math.sin(endAngle); 2913 const x4 = cx + rInner * Math.cos(startAngle); 2914 const y4 = cy + rInner * Math.sin(startAngle); 2915 2916 const largeArc = (endAngle - startAngle) > Math.PI ? 1 : 0; 2917 2918 return `M ${x1} ${y1} A ${rOuter} ${rOuter} 0 ${largeArc} 1 ${x2} ${y2} L ${x3} ${y3} A ${rInner} ${rInner} 0 ${largeArc} 0 ${x4} ${y4} Z`; 2919} 2920 2921// Draw the 24 wedges in an SVG element 2922function buildCamelotWheelSVG(svgId) { 2923 const svg = document.getElementById(svgId); 2924 if (!svg) return; 2925 svg.innerHTML = ''; 2926 2927 const cx = 200; 2928 const cy = 200; 2929 const rOuterStart = 125; 2930 const rOuterEnd = 185; 2931 const rInnerStart = 65; 2932 const rInnerEnd = 125; 2933 2934 // Palette for 12 hours around wheel 2935 const hourHues = [ 2936 '#06b6d4', // 1: Cyan 2937 '#0284c7', // 2: Blue-Cyan 2938 '#2563eb', // 3: Blue 2939 '#4f46e5', // 4: Indigo 2940 '#7c3aed', // 5: Violet 2941 '#9333ea', // 6: Purple 2942 '#c026d3', // 7: Fuchsia 2943 '#db2777', // 8: Pink (8A/8B) 2944 '#e11d48', // 9: Rose 2945 '#ea580c', // 10: Orange 2946 '#d97706', // 11: Amber 2947 '#16a34a' // 12: Green 2948 ]; 2949 2950 for (let h = 1; h <= 12; h++) { 2951 const midAngle = getHourAngle(h); 2952 const halfStep = (15 * Math.PI) / 180; 2953 const startA = midAngle - halfStep + 0.015; 2954 const endA = midAngle + halfStep - 0.015; 2955 const baseColor = hourHues[h - 1]; 2956 2957 // 1. Outer Wedge: Major (B) 2958 const codeB = `${h}B`; 2959 const pathB = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 2960 pathB.setAttribute('d', createWedgePath(cx, cy, rOuterStart, rOuterEnd, startA, endA)); 2961 pathB.setAttribute('fill', baseColor); 2962 pathB.setAttribute('fill-opacity', '0.45'); 2963 pathB.setAttribute('stroke', 'rgba(255,255,255,0.15)'); 2964 pathB.setAttribute('stroke-width', '1'); 2965 pathB.setAttribute('data-code', codeB); 2966 pathB.setAttribute('class', 'wheel-wedge'); 2967 pathB.onclick = () => applyKeySelection(codeB); 2968 svg.appendChild(pathB); 2969 2970 // Label for Major 2971 const textRadiusB = (rOuterStart + rOuterEnd) / 2; 2972 const txB = cx + textRadiusB * Math.cos(midAngle); 2973 const tyB = cy + textRadiusB * Math.sin(midAngle) + 4; 2974 const textB = document.createElementNS('http://www.w3.org/2000/svg', 'text'); 2975 textB.setAttribute('x', txB); 2976 textB.setAttribute('y', tyB); 2977 textB.setAttribute('text-anchor', 'middle'); 2978 textB.setAttribute('fill', '#ffffff'); 2979 textB.setAttribute('font-size', '11'); 2980 textB.setAttribute('font-weight', 'bold');
2981 textB.setAttribute('pointer-events', 'none'); 2982 textB.textContent = codeB; 2983 svg.appendChild(textB); 2984 2985 // 2. Inner Wedge: Minor (A) 2986 const codeA = `${h}A`; 2987 const pathA = document.createElementNS('http://www.w3.org/2000/svg', 'path'); 2988 pathA.setAttribute('d', createWedgePath(cx, cy, rInnerStart, rInnerEnd, startA, endA)); 2989 pathA.setAttribute('fill', baseColor); 2990 pathA.setAttribute('fill-opacity', '0.25'); 2991 pathA.setAttribute('stroke', 'rgba(255,255,255,0.15)'); 2992 pathA.setAttribute('stroke-width', '1'); 2993 pathA.setAttribute('data-code', codeA); 2994 pathA.setAttribute('class', 'wheel-wedge'); 2995 pathA.onclick = () => applyKeySelection(codeA); 2996 svg.appendChild(pathA); 2997 2998 // Label for Minor 2999 const textRadiusA = (rInnerStart + rInnerEnd) / 2; 3000 const txA = cx + textRadiusA * Math.cos(midAngle); 3001 const tyA = cy + textRadiusA * Math.sin(midAngle) + 4; 3002 const textA = document.createElementNS('http://www.w3.org/2000/svg', 'text'); 3003 textA.setAttribute('x', txA); 3004 textA.setAttribute('y', tyA); 3005 textA.setAttribute('text-anchor', 'middle'); 3006 textA.setAttribute('fill', 'rgba(255,255,255,0.85)'); 3007 textA.setAttribute('font-size', '10'); 3008 textA.setAttribute('font-weight', 'bold'); 3009 textA.setAttribute('pointer-events', 'none'); 3010 textA.textContent = codeA; 3011 svg.appendChild(textA); 3012 } 3013} 3014 3015// Highlight active key and compatible neighbors on SVG wheels 3016function highlightCamelotWheels(activeCode) { 3017 ['camelot-wheel-svg', 'explorer-wheel-svg'].forEach(svgId => { 3018 const svg = document.getElementById(svgId); 3019 if (!svg) return; 3020 3021 const wedges = svg.querySelectorAll('.wheel-wedge'); 3022 const activeData = camelotData[activeCode]; 3023 if (!activeData) return; 3024 3025 const sameCode = activeCode; 3026 const relCode = activeData.rel; 3027 const boostCode = activeData.boost; 3028 const dropCode = activeData.drop; 3029 3030 wedges.forEach(w => { 3031 w.classList.remove('active-key', 'match-same', 'match-relative', 'match-boost', 'match-drop'); 3032 const code = w.getAttribute('data-code'); 3033 3034 if (code === sameCode) { 3035 w.classList.add('active-key'); 3036 } else if (code === relCode) { 3037 w.classList.add('match-relative'); 3038 } else if (code === boostCode) { 3039 w.classList.add('match-boost'); 3040 } else if (code === dropCode) { 3041 w.classList.add('match-drop'); 3042 } 3043 }); 3044 }); 3045} 3046 3047// Update Explorer Tab Text 3048function updateExplorerWheel(code) { 3049 const data = camelotData[code]; 3050 if (!data) return; 3051 3052 document.getElementById('explorer-center-code').innerText = code; 3053 document.getElementById('explorer-center-key').innerText = data.key; 3054 document.getElementById('explorer-key-name').innerText = data.key; 3055 document.getElementById('explorer-camelot-badge').innerText = code; 3056 document.getElementById('explorer-mood-desc').innerText = data.mood; 3057 3058 const rel = camelotData[data.rel]; 3059 document.getElementById('explorer-relative-key').innerText = rel ? `${rel.key} (${data.rel})` : '--'; 3060 3061 const boost = camelotData[data.boost]; 3062 document.getElementById('explorer-boost-key').innerText = boost ? `${boost.key} (${data.boost})` : '--'; 3063} 3064 3065// Build both wheels initially 3066buildCamelotWheelSVG('camelot-wheel-svg'); 3067buildCamelotWheelSVG('explorer-wheel-svg'); 3068highlightCamelotWheels(currentCamelotCode); 3069 3070// ================= 4. VIRTUAL PIANO KEYBOARD ================= 3071const pianoContainer = document.getElementById('piano-keyboard'); 3072 3073const pianoNoteFrequencies = { 3074 'C3': 130.81, 'C#3': 138.59, 'D3': 146.83, 'D#3': 155.56, 'E3': 164.81, 'F3': 174.61, 'F#3': 185.00, 'G3': 196.00, 'G#3': 207.65, 'A3': 220.00, 'A#3': 233.08, 'B3': 246.94, 3075 'C4': 261.63, 'C#4': 277.18, 'D4': 293.66, 'D#4': 311.13, 'E4': 329.63, 'F4': 349.23, 'F#4': 369.99, 'G4': 392.00, 'G#4': 415.30, 'A4': 440.00, 'A#4': 466.16, 'B4': 493.88 3076}; 3077 3078function buildPianoKeyboard() { 3079 pianoContainer.innerHTML = ''; 3080 const whiteKeys = ['C3', 'D3', 'E3', 'F3', 'G3', 'A3', 'B3', 'C4', 'D4', 'E4', 'F4', 'G4', 'A4', 'B4']; 3081 const blackMap = { 3082 'C3': 'C#3', 'D3': 'D#3', 'F3': 'F#3', 'G3': 'G#3', 'A3': 'A#3', 3083 'C4': 'C#4', 'D4': 'D#4', 'F4': 'F#4', 'G4': 'G#4', 'A4': 'A#4' 3084 }; 3085 3086 whiteKeys.forEach(note => { 3087 const whiteWrap = document.createElement('div'); 3088 whiteWrap.className = 'relative flex-shrink-0'; 3089 3090 const whiteKey = document.createElement('button'); 3091 whiteKey.className = 'piano-key piano-white w-9 sm:w-11 h-24 bg-white border border-slate-300 rounded-b-lg shadow-md flex items-end justify-center pb-1 text-[10px] font-bold text-slate-700 hover:bg-slate-100'; 3092 whiteKey.setAttribute('data-note', note.replace(/[0-9]/g, '')); 3093 whiteKey.innerText = note; 3094 whiteKey.onclick = () => playPianoTone(pianoNoteFrequencies[note]); 3095 whiteWrap.appendChild(whiteKey); 3096 3097 if (blackMap[note]) { 3098 const blackNote = blackMap[note]; 3099 const blackKey = document.createElement('button'); 3100 blackKey.className = 'piano-key piano-black w-6 sm:w-7 h-15 bg-slate-900 border border-slate-700 rounded-b-md absolute -right-3 sm:-right-3.5 top-0 z-10 shadow-lg text-[9px] font-bold text-slate-300 flex items-end justify-center pb-1 hover:bg-slate-800'; 3101 blackKey.setAttribute('data-note', blackNote.replace(/[0-9]/g, '')); 3102 blackKey.innerText = blackNote; 3103 blackKey.onclick = (e) => { 3104 e.stopPropagation(); 3105 playPianoTone(pianoNoteFrequencies[blackNote]); 3106 }; 3107 whiteWrap.appendChild(blackKey); 3108 } 3109 3110 pianoContainer.appendChild(whiteWrap); 3111 }); 3112} 3113 3114function playPianoTone(freq) { 3115 if (!freq) return; 3116 if (!audioContext) { 3117 audioContext = new (window.AudioContext || window.webkitAudioContext)(); 3118 } 3119 if (audioContext.state === 'suspended') audioContext.resume(); 3120 3121 try { 3122 const osc = audioContext.createOscillator(); 3123 const gain = audioContext.createGain(); 3124 3125 // Warm harmonic piano tone 3126 osc.type = 'triangle'; 3127 osc.frequency.setValueAtTime(freq, audioContext.currentTime); 3128 3129 gain.gain.setValueAtTime(0.35, audioContext.currentTime); 3130 gain.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + 1.2); 3131 3132 osc.connect(gain); 3133 gain.connect(audioContext.destination); 3134 3135 osc.start(); 3136 osc.stop(audioContext.currentTime + 1.25); 3137 } catch(e) {} 3138} 3139 3140function highlightPianoScale(scaleNotes) { 3141 const allKeys = pianoContainer.querySelectorAll('.piano-key'); 3142 allKeys.forEach(k => { 3143 const note = k.getAttribute('data-note'); 3144 if (scaleNotes.includes(note)) { 3145 k.classList.add('scale-active'); 3146 } else { 3147 k.classList.remove('scale-active'); 3148 } 3149 }); 3150} 3151 3152buildPianoKeyboard(); 3153 3154// ================= 5. TONIC ROOT NOTE AUDIO PLAYER ================= 3155const btnPlayTonic = document.getElementById('btn-play-tonic'); 3156const tonicIcon = document.getElementById('tonic-icon'); 3157const tonicBtnLabel = document.getElementById('tonic-btn-label'); 3158 3159btnPlayTonic.onclick = toggleTonicTone; 3160 3161function toggleTonicTone() { 3162 if (isTonicPlaying) { 3163 stopTonicTone(); 3164 } else { 3165 startTonicTone(); 3166 } 3167} 3168 3169function startTonicTone() { 3170 if (!audioContext) { 3171 audioContext = new (window.AudioContext || window.webkitAudioContext)(); 3172 } 3173 if (audioContext.state === 'suspended') audioContext.resume(); 3174 3175 const data = camelotData[currentCamelotCode]; 3176 if (!data) return; 3177 3178 const root = data.root; 3179 const freq = pianoNoteFrequencies[`${root}3`] || pianoNoteFrequencies[`${root}4`] || 220; 3180 3181 try { 3182 tonicOscillator = audioContext.createOscillator(); 3183 const gain = audioContext.createGain(); 3184 3185 tonicOscillator.type = 'sine'; 3186 tonicOscillator.frequency.setValueAtTime(freq, audioContext.currentTime); 3187 3188 gain.gain.setValueAtTime(0.3, audioContext.currentTime); 3189 3190 tonicOscillator.connect(gain); 3191 gain.connect(audioContext.destination); 3192 3193 tonicOscillator.start(); 3194 isTonicPlaying = true; 3195 3196 btnPlayTonic.classList.replace('from-indigo-500', 'from-emerald-500'); 3197 btnPlayTonic.classList.replace('to-purple-600', 'to-teal-600'); 3198 tonicBtnLabel.innerText = `Mute Root Note (${root})`; 3199 tonicIcon.className = 'fa-solid fa-volume-xmark'; 3200 } catch(e) {} 3201} 3202 3203function stopTonicTone() { 3204 if (tonicOscillator) { 3205 try { tonicOscillator.stop(); } catch(e) {} 3206 tonicOscillator = null; 3207 } 3208 isTonicPlaying = false;
3209 btnPlayTonic.classList.replace('from-emerald-500', 'from-indigo-500'); 3210 btnPlayTonic.classList.replace('to-teal-600', 'to-purple-600'); 3211 tonicBtnLabel.innerText = 'Play Root Note Tone'; 3212 tonicIcon.className = 'fa-solid fa-volume-high'; 3213} 3214 3215// ================= 6. AUDIO SCRUBBER CONTROLLER ================= 3216const playBtn = document.getElementById('btn-play-pause'); 3217const playIcon = document.getElementById('play-icon'); 3218const seekProgress = document.getElementById('seek-progress'); 3219const currentTimeEl = document.getElementById('player-current-time'); 3220const seekBarContainer = document.getElementById('seek-bar-container'); 3221 3222playBtn.onclick = togglePlay; 3223 3224function togglePlay() { 3225 if (!currentAudioBuffer) return; 3226 if (isPlaying) pauseAudio(); 3227 else playAudio(playbackPauseOffset); 3228} 3229 3230function playAudio(offset = 0) { 3231 if (!audioContext || !currentAudioBuffer) return; 3232 if (audioContext.state === 'suspended') audioContext.resume(); 3233 3234 currentSourceNode = audioContext.createBufferSource(); 3235 currentSourceNode.buffer = currentAudioBuffer; 3236 currentSourceNode.connect(audioContext.destination); 3237 3238 currentSourceNode.start(0, offset); 3239 playbackStartTime = audioContext.currentTime - offset; 3240 isPlaying = true; 3241 playIcon.className = 'fa-solid fa-pause'; 3242 3243 startScrubberLoop(); 3244 3245 currentSourceNode.onended = () => { 3246 if (audioContext.currentTime - playbackStartTime >= currentAudioBuffer.duration) { 3247 pauseAudio(); 3248 playbackPauseOffset = 0; 3249 updateScrubUI(0); 3250 } 3251 }; 3252} 3253 3254function pauseAudio() { 3255 if (currentSourceNode) { 3256 try { currentSourceNode.stop(); } catch(e) {} 3257 currentSourceNode = null; 3258 } 3259 if (isPlaying) { 3260 playbackPauseOffset = audioContext.currentTime - playbackStartTime; 3261 } 3262 isPlaying = false; 3263 if (playIcon) playIcon.className = 'fa-solid fa-play'; 3264} 3265 3266function startScrubberLoop() { 3267 if (!isPlaying) return; 3268 const current = (audioContext.currentTime - playbackStartTime); 3269 const percent = Math.min(100, (current / currentAudioBuffer.duration) * 100); 3270 updateScrubUI(percent, current); 3271 requestAnimationFrame(startScrubberLoop); 3272} 3273 3274function updateScrubUI(percent, currentSeconds = 0) { 3275 seekProgress.style.width = percent + '%'; 3276 currentTimeEl.innerText = formatTime(currentSeconds); 3277} 3278 3279seekBarContainer.onclick = (e) => { 3280 if (!currentAudioBuffer) return; 3281 const rect = seekBarContainer.getBoundingClientRect(); 3282 const pos = (e.clientX - rect.left) / rect.width; 3283 const newTime = pos * currentAudioBuffer.duration; 3284 3285 playbackPauseOffset = newTime; 3286 if (isPlaying) { 3287 pauseAudio(); 3288 playAudio(newTime); 3289 } else { 3290 updateScrubUI(pos * 100, newTime); 3291 } 3292}; 3293 3294function formatTime(seconds) { 3295 seconds = Math.floor(seconds || 0); 3296 const m = Math.floor(seconds / 60); 3297 const s = seconds % 60; 3298 return `${m}:${s < 10 ? '0' : ''}${s}`; 3299} 3300 3301// ================= 7. REAL-TIME MICROPHONE PITCH DETECTOR ================= 3302let micStream = null; 3303let micAudioCtx = null; 3304let micAnalyser = null; 3305let micAnimId = null; 3306let isMicListening = false; 3307let micStartTime = null; 3308let micTimerInterval = null; 3309let micPitchHistogram = new Float32Array(12).fill(0); 3310 3311const btnToggleMic = document.getElementById('btn-toggle-mic'); 3312const btnResetMic = document.getElementById('btn-reset-mic'); 3313const micBtnIcon = document.getElementById('mic-btn-icon'); 3314const micBtnLabel = document.getElementById('mic-btn-label'); 3315const micCanvas = document.getElementById('mic-canvas'); 3316const micIdleMsg = document.getElementById('mic-idle-msg'); 3317const micPitchNote = document.getElementById('mic-pitch-note'); 3318const micPitchFreq = document.getElementById('mic-pitch-freq'); 3319const micEstimatedKey = document.getElementById('mic-estimated-key'); 3320const micEstimatedCamelot = document.getElementById('mic-estimated-camelot'); 3321const micDurationDisplay = document.getElementById('mic-duration-display'); 3322const micStatusBadge = document.getElementById('mic-status-badge'); 3323const canvasCtx = micCanvas ? micCanvas.getContext('2d') : null; 3324 3325if (btnToggleMic) btnToggleMic.onclick = toggleMicListening; 3326if (btnResetMic) btnResetMic.onclick = resetMicStats; 3327 3328async function toggleMicListening() { 3329 if (isMicListening) { 3330 stopMicListening(); 3331 } else { 3332 await startMicListening(); 3333 } 3334} 3335 3336function startMicTimer() { 3337 if (micTimerInterval) clearInterval(micTimerInterval); 3338 if (!micStartTime) micStartTime = Date.now(); 3339 micTimerInterval = setInterval(() => { 3340 if (!isMicListening) return; 3341 const elapsedSec = Math.floor((Date.now() - micStartTime) / 1000); 3342 const m = Math.floor(elapsedSec / 60); 3343 const s = elapsedSec % 60; 3344 if (micDurationDisplay) { 3345 micDurationDisplay.innerText = `${m < 10 ? '0' : ''}${m}:${s < 10 ? '0' : ''}${s}`; 3346 } 3347 }, 500); 3348} 3349 3350function stopMicTimer() { 3351 if (micTimerInterval) { 3352 clearInterval(micTimerInterval); 3353 micTimerInterval = null; 3354 } 3355} 3356 3357function resetMicStats() { 3358 micPitchHistogram.fill(0); 3359 micStartTime = isMicListening ? Date.now() : null; 3360 if (micPitchNote) micPitchNote.innerText = '--'; 3361 if (micPitchFreq) micPitchFreq.innerText = '-- Hz'; 3362 if (micEstimatedKey) micEstimatedKey.innerText = '--'; 3363 if (micEstimatedCamelot) micEstimatedCamelot.innerText = 'Camelot: --'; 3364 if (micDurationDisplay) micDurationDisplay.innerText = '00:00'; 3365 if (micStatusBadge) micStatusBadge.innerText = isMicListening ? 'Listening for pitch...' : 'Microphone is paused.'; 3366} 3367 3368async function startMicListening() { 3369 try { 3370 const AudioCtx = window.AudioContext || window.webkitAudioContext; 3371 micAudioCtx = new AudioCtx(); 3372 if (micAudioCtx.state === 'suspended') { 3373 await micAudioCtx.resume(); 3374 } 3375 3376 try { 3377 micStream = await navigator.mediaDevices.getUserMedia({ 3378 audio: { 3379 echoCancellation: false, 3380 noiseSuppression: false, 3381 autoGainControl: false 3382 }, 3383 video: false 3384 }); 3385 } catch (errConstraint) { 3386 micStream = await navigator.mediaDevices.getUserMedia({ audio: true }); 3387 } 3388 3389 const source = micAudioCtx.createMediaStreamSource(micStream); 3390 3391 micAnalyser = micAudioCtx.createAnalyser(); 3392 micAnalyser.fftSize = 1024; 3393 micAnalyser.smoothingTimeConstant = 0.2; 3394 3395 source.connect(micAnalyser); 3396 3397 isMicListening = true; 3398 if (micIdleMsg) micIdleMsg.style.display = 'none'; 3399 3400 btnToggleMic.classList.replace('from-emerald-500', 'from-rose-500'); 3401 btnToggleMic.classList.replace('to-teal-600', 'to-red-600'); 3402 btnToggleMic.classList.replace('hover:from-emerald-400', 'hover:from-rose-400'); 3403 btnToggleMic.classList.replace('hover:to-teal-500', 'hover:to-red-500'); 3404 btnToggleMic.classList.replace('shadow-emerald-500/25', 'shadow-rose-500/25'); 3405 micBtnIcon.className = 'fa-solid fa-microphone-slash text-base'; 3406 micBtnLabel.innerText = 'Stop Listening'; 3407 if (micStatusBadge) micStatusBadge.innerText = 'Listening to ambient pitch...'; 3408 showNotification('Microphone online. Hum, sing, or play an instrument.', 'info'); 3409 3410 micStartTime = Date.now(); 3411 startMicTimer(); 3412 drawMicPitchVisualizer(); 3413 3414 } catch (err) { 3415 console.error('Microphone access error:', err); 3416 showNotification('Microphone permission denied or device not found.', 'error'); 3417 stopMicListening(); 3418 } 3419} 3420 3421function stopMicListening() { 3422 if (!isMicListening) return; 3423 isMicListening = false;
3424 stopMicTimer(); 3425 3426 if (micAnimId) cancelAnimationFrame(micAnimId); 3427 if (micStream) { 3428 micStream.getTracks().forEach(t => t.stop()); 3429 micStream = null; 3430 } 3431 if (micAudioCtx) { 3432 try { micAudioCtx.close(); } catch(e) {} 3433 micAudioCtx = null; 3434 } 3435 3436 btnToggleMic.classList.replace('from-rose-500', 'from-emerald-500'); 3437 btnToggleMic.classList.replace('to-red-600', 'to-teal-600'); 3438 btnToggleMic.classList.replace('hover:from-rose-400', 'hover:from-emerald-400'); 3439 btnToggleMic.classList.replace('hover:to-red-500', 'hover:to-teal-500'); 3440 btnToggleMic.classList.replace('shadow-rose-500/25', 'shadow-emerald-500/25'); 3441 micBtnIcon.className = 'fa-solid fa-microphone text-base'; 3442 micBtnLabel.innerText = 'Start Listening'; 3443 if (micIdleMsg) micIdleMsg.style.display = 'flex'; 3444 if (micStatusBadge) micStatusBadge.innerText = 'Microphone paused.'; 3445 3446 if (canvasCtx) canvasCtx.clearRect(0, 0, micCanvas.width, micCanvas.height); 3447} 3448 3449// Autocorrelation Pitch Detector for Time-Domain Audio 3450function autoCorrelate(buf, sampleRate) { 3451 let SIZE = buf.length; 3452 let rms = 0; 3453 3454 for (let i = 0; i < SIZE; i++) { 3455 let val = buf[i]; 3456 rms += val * val; 3457 } 3458 rms = Math.sqrt(rms / SIZE); 3459 if (rms < 0.012) return -1; // Silence gate 3460 3461 let r1 = 0, r2 = SIZE - 1, thres = 0.2; 3462 for (let i = 0; i < SIZE / 2; i++) { 3463 if (Math.abs(buf[i]) < thres) { r1 = i; break; } 3464 } 3465 for (let i = 1; i < SIZE / 2; i++) { 3466 if (Math.abs(buf[SIZE - i]) < thres) { r2 = SIZE - i; break; } 3467 } 3468 3469 buf = buf.slice(r1, r2); 3470 SIZE = buf.length; 3471 3472 let c = new Array(SIZE).fill(0); 3473 for (let i = 0; i < SIZE; i++) { 3474 for (let j = 0; j < SIZE - i; j++) { 3475 c[i] = c[i] + buf[j] * buf[j + i]; 3476 } 3477 } 3478 3479 let d = 0; while (c[d] > c[d + 1]) d++; 3480 let maxval = -1, maxpos = -1; 3481 for (let i = d; i < SIZE; i++) { 3482 if (c[i] > maxval) { 3483 maxval = c[i]; 3484 maxpos = i; 3485 } 3486 } 3487 let T0 = maxpos; 3488 3489 let x1 = c[T0 - 1], x2 = c[T0], x3 = c[T0 + 1]; 3490 let a = (x1 + x3 - 2 * x2) / 2; 3491 let b = (x3 - x1) / 2; 3492 if (a) T0 = T0 - b / (2 * a); 3493 3494 return sampleRate / T0; 3495} 3496 3497function noteFromPitch(frequency) { 3498 let noteNum = 12 * (Math.log(frequency / 440) / Math.log(2)); 3499 return Math.round(noteNum) + 69; 3500} 3501 3502function drawMicPitchVisualizer() { 3503 if (!isMicListening || !micAnalyser) return; 3504 3505 const buffer = new Float32Array(micAnalyser.fftSize); 3506 micAnalyser.getFloatTimeDomainData(buffer); 3507 3508 // Draw Sleek Oscilloscope Waveform 3509 canvasCtx.fillStyle = 'rgba(8, 10, 19, 0.45)'; 3510 canvasCtx.fillRect(0, 0, micCanvas.width, micCanvas.height); 3511 3512 canvasCtx.lineWidth = 2; 3513 canvasCtx.strokeStyle = '#10b981'; 3514 canvasCtx.beginPath(); 3515 3516 const sliceWidth = micCanvas.width * 1.0 / buffer.length; 3517 let x = 0; 3518 3519 for (let i = 0; i < buffer.length; i++) { 3520 const v = buffer[i] * 35; 3521 const y = (micCanvas.height / 2) + v; 3522 3523 if (i === 0) canvasCtx.moveTo(x, y); 3524 else canvasCtx.lineTo(x, y); 3525 3526 x += sliceWidth; 3527 } 3528 canvasCtx.stroke(); 3529 3530 // Calculate live pitch 3531 const pitch = autoCorrelate(buffer, micAudioCtx.sampleRate); 3532 if (pitch !== -1 && pitch >= 60 && pitch <= 1200) { 3533 const midi = noteFromPitch(pitch); 3534 const noteIndex = midi % 12; 3535 const noteName = notes[noteIndex]; 3536 3537 micPitchNote.innerText = noteName; 3538 micPitchFreq.innerText = `${pitch.toFixed(1)} Hz`; 3539 3540 // Accumulate pitch histogram to estimate prevailing key 3541 micPitchHistogram[noteIndex] += 1; 3542 3543 // Estimate key every few samples 3544 const sum = micPitchHistogram.reduce((a, b) => a + b, 0); 3545 if (sum >= 12) { 3546 const maxHist = Math.max(...micPitchHistogram) || 1; 3547 const normHist = Array.from(micPitchHistogram).map(v => v / maxHist); 3548 3549 let bestScore = -Infinity; 3550 let bestKeyStr = 'A Minor'; 3551 let bestCode = '8A'; 3552 3553 for (let r = 0; r < 12; r++) { 3554 const rotated = []; 3555 for (let i = 0; i < 12; i++) rotated.push(normHist[(r + i) % 12]); 3556 3557 const scoreMaj = 0.6 * pearsonCorrelation(rotated, keyProfiles.shaathMajor) + 0.4 * pearsonCorrelation(rotated, keyProfiles.temperleyMajor); 3558 if (scoreMaj > bestScore) { 3559 bestScore = scoreMaj; 3560 bestKeyStr = notes[r] + ' Major'; 3561 bestCode = findCamelotCodeByKey(bestKeyStr); 3562 } 3563 3564 const scoreMin = 0.6 * pearsonCorrelation(rotated, keyProfiles.shaathMinor) + 0.4 * pearsonCorrelation(rotated, keyProfiles.temperleyMinor); 3565 if (scoreMin > bestScore) { 3566 bestScore = scoreMin; 3567 bestKeyStr = notes[r] + ' Minor'; 3568 bestCode = findCamelotCodeByKey(bestKeyStr); 3569 } 3570 } 3571 3572 micEstimatedKey.innerText = bestKeyStr; 3573 micEstimatedCamelot.innerText = `Camelot: ${bestCode}`; 3574 micStatusBadge.innerText = 'Key estimated from voice/hum'; 3575 } 3576 } 3577 3578 micAnimId = requestAnimationFrame(drawMicPitchVisualizer); 3579} 3580</script>
3580 3581 3582<!-- ================= FOOTER SECTION ================= --> 3583<footer class="footer-glass mt-0 py-12 relative z-10"> 3584 <div class="container mx-auto px-4 max-w-7xl"> 3585 <div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-8 mb-10"> 3586 <!-- Brand Column --> 3587 <div class="sm:col-span-2 lg:col-span-1"> 3588 <a href="/" class="flex items-center gap-3 mb-4 group inline-flex select-none"> 3589 <div class="w-10 h-10 glass rounded-xl flex items-center justify-center text-white bg-white/10 border border-white/10 group-hover:scale-105 transition-transform duration-200 shadow-inner"> 3590 <svg class="h-5 w-5 text-white" viewBox="0 0 24 24" fill="currentColor"> 3591 <path d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/> 3592 </svg> 3593 </div> 3594 <span class="footer-logo text-2xl font-extrabold font-outfit">Vocal Remover</span> 3595 </a> 3596 <p class="text-white/70 text-sm leading-relaxed mb-4"> 3597 Professional in-browser audio workstation & AI stem separation suite for musicians, producers, sound engineers, and podcasters. 3598 </p> 3599 <div class="inline-flex items-center gap-2 px-3 py-1.5 rounded-xl bg-emerald-500/10 border border-emerald-500/20 text-xs text-emerald-400 font-medium"> 3600 <i class="fa-solid fa-shield-halved text-xs"></i> 100% In-Browser Privacy 3601 </div> 3602 </div> 3603 3604 <!-- Column 2: Audio Tools (AI & DSP Engines) --> 3605 <div> 3606 <h2 class="text-white font-bold text-sm uppercase tracking-wider mb-4 font-outfit">Audio Tools</h2> 3607 <ul class="space-y-2.5"> 3608 <li><a href="https://vocalremovers.ai/#dropsec" class="footer-link text-sm">Free AI Vocal Remover</a></li> 3609 <li><a href="/bpm-finder/" class="footer-link text-sm">BPM & Key Finder</a></li> 3610 <li><a href="/key-finder/" class="footer-link text-sm">Key Finder & Camelot</a></li> 3611 <li><a href="/pitch-changer/" class="footer-link text-sm">Pitch Changer</a></li> 3612 <li><a href="/bass-booster/" class="footer-link text-sm">Bass Booster</a></li> 3613 <li><a href="/noise-reducer/" class="footer-link text-sm">Noise Reducer</a></li> 3614 <li><a href="/audio-effects/" class="footer-link text-sm">Audio Effects</a></li> 3615 </ul> 3616 </div> 3617 3618 <!-- Column 3: Audio Utilities (Editing & Conversion) --> 3619 <div> 3620 <h2 class="text-white font-bold text-sm uppercase tracking-wider mb-4 font-outfit">Audio Utilities</h2> 3621 <ul class="space-y-2.5"> 3622 <li><a href="/mp3-tag-editor/" class="footer-link text-sm">MP3 Tag Editor</a></li> 3623 <li><a href="/audio-converter/" class="footer-link text-sm">Audio Converter</a></li> 3624 <li><a href="/audio-cutter/" class="footer-link text-sm">Audio Cutter</a></li> 3625 <li><a href="/audio-joiner/" class="footer-link text-sm">Audio Joiner</a></li> 3626 <li><a href="/equalizer/" class="footer-link text-sm">Audio Equalizer</a></li> 3627 <li><a href="/voice-recorder/" class="footer-link text-sm">Voice Recorder</a></li> 3628 </ul> 3629 </div> 3630 3631 <!-- Column 4: Platform & Guide --> 3632 <div> 3633 <h2 class="text-white font-bold text-sm uppercase tracking-wider mb-4 font-outfit">Platform</h2> 3634 <ul class="space-y-2.5"> 3635 <li><a href="/#features" class="footer-link text-sm">Features</a></li> 3636 <li><a href="/#how-it-works" class="footer-link text-sm">How It Works</a></li> 3637 <li><a href="/#faqs" class="footer-link text-sm">Studio FAQs</a></li> 3638 <li><a href="/contact/" class="footer-link text-sm">Help Center</a></li> 3639 </ul> 3640 </div> 3641 3642 <!-- Column 5: Company & Legal --> 3643 <div> 3644 <h2 class="text-white font-bold text-sm uppercase tracking-wider mb-4 font-outfit">Company</h2> 3645 <ul class="space-y-2.5"> 3646 <li><a href="/about/" class="footer-link text-sm">About Us</a></li> 3647 <li><a href="/contact/" class="footer-link text-sm">Contact Us</a></li> 3648 <li><a href="/privacy-policy/" class="footer-link text-sm">Privacy Policy</a></li> 3649 <li><a href="/disclaimer/" class="footer-link text-sm">Disclaimer</a></li> 3650 </ul> 3651 </div> 3652 </div> 3653 3654 <!-- Divider --> 3655 <div class="h-px bg-white/10 mb-8"></div> 3656 3657 <!-- Bottom Footer --> 3658 <div class="flex flex-col sm:flex-row items-center justify-between gap-4"> 3659 <div class="text-white/60 text-xs sm:text-sm text-center sm:text-left"> 3660 © 2026 VocalRemovers.ai. All rights reserved. Zero audio data stored on servers. 3661 </div> 3662 3663 <!-- Legal Links --> 3664 <div class="flex items-center gap-6 text-xs sm:text-sm"> 3665 <a href="/privacy-policy/" class="footer-link">Privacy Policy</a> 3666 <a href="/disclaimer/" class="footer-link">Disclaimer</a> 3667 <a href="/contact/" class="footer-link">Contact</a> 3668 </div> 3669 </div> 3670 </div> 3671</footer> 3672
3673<script> 3674 const CONFIG = { 3675 clientId: '658101237139-kgekg6onkp1g1ckmp8q5mbf28i3bjaq0.apps.googleusercontent.com', 3676 apiKey: 'AIzaSyB_MWxjdXxwB9z_hXZH5_f0YPQhjG4M9Bw', 3677 appId: '658101237139', 3678 scope: 'https://www.googleapis.com/auth/drive.file', 3679 discoveryDocs: ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'] 3680 }; 3681 3682 let tokenClient; 3683 let accessToken = null; 3684 let pickerInited = false; 3685 let gisInited = false; 3686 3687 function onApiLoad() { 3688 gapi.load('client:picker', initializePicker); 3689 } 3690 3691 function gisLoaded() { 3692 tokenClient = google.accounts.oauth2.initTokenClient({ 3693 client_id: CONFIG.clientId, 3694 scope: CONFIG.scope, 3695 callback: '', 3696 }); 3697 gisInited = true; 3698 } 3699 function initializePicker() { 3700 gapi.client.init({ 3701 apiKey: CONFIG.apiKey, 3702 discoveryDocs: CONFIG.discoveryDocs 3703 }).then(function() { 3704 pickerInited = true; 3705 }); 3706 } 3707 3708 function loadGoogleDriveOnDemand(callback) { 3709 if (window.gapi && window.google && pickerInited && gisInited) { 3710 if (callback) callback(); 3711 return; 3712 } 3713 const s1 = document.createElement('script'); 3714 s1.src = 'https://apis.google.com/js/api.js'; 3715 s1.onload = () => { 3716 gapi.load('client:picker', () => { 3717 initializePicker(); 3718 const s2 = document.createElement('script'); 3719 s2.src = 'https://accounts.google.com/gsi/client'; 3720 s2.onload = () => { 3721 gisLoaded(); 3722 if (callback) callback(); 3723 }; 3724 document.head.appendChild(s2); 3725 }); 3726 }; 3727 document.head.appendChild(s1); 3728 } 3729</script>
3729 3730 3731 <!-- Load jQuery early to ensure script blocks work correctly --> 3732
3732<script src="https://code.jquery.com/jquery-3.6.3.min.js"></script>
3732 3733
3734<script type="text/javascript" src="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="7vdgy4nz22i09tq" async></script>
vendor: 1 bytes, line 3734
3734
3735<script src="https://js.live.net/v7.2/OneDrive.js" async></script>
3735 3736
3736<script src="https://unpkg.com/[email protected]/dist/wavesurfer.js" defer></script>
3736 3737 <!--
3737<script src="/js/main.js?v=4.3" defer></script>
3737 --> 3738
3738<script src="/js/gdrive.js?v=5.2" defer></script>
3738 3739<link rel="preload" href="/css/modern_css.css?v=1.1" as="style" onload="this.rel='stylesheet'"> 3740<noscript><link rel="stylesheet" href="/css/modern_css.css?v=1.1"></noscript> 3741<link rel="preload" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css" as="style" onload="this.rel='stylesheet'"> 3742<noscript><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/[email protected]/css/all.min.css"></noscript>
3743<script defer src="https://cdn.tailwindcss.com"></script>
3743 3744<!-- Google tag (gtag.js) -->
vendor: 64 bytes, lines 3744-3745
3744 3745<script async src="https://www.googletagmanager.com/gtag/js?id=
3745G-FHELE5QWPR
vendor: 12 bytes, line 3745
3745"></script>
3746<script> 3747
vendor: 134 bytes, lines 3747-3751
3747window.dataLayer = window.dataLayer || []; 3748 function gtag(){dataLayer.push(arguments);} 3749 gtag('js', new Date()); 3750 3751 gtag('config', '
3751G-FHELE5QWPR
vendor: 4 bytes, line 3751
3751');
3752</script>
vendor: 1 bytes, line 3752
3752
3753<script> 3754function processSubpage(filename, url){ 3755 loadFromRemoteUrl(url, filename); 3756} 3757</script>
3757 3758</body> 3759</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.