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>MIDI Melody Generator</title> 7 <!-- Reminder: cdn.tailwindcss.com should not be used in production --> 8
8<script src="https://cdn.tailwindcss.com"></script>
8 9 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> 10 <style> 11 /* Import fonts */ 12 @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); 13 14 body { 15 font-family: 'Poppins', sans-serif; 16 background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); 17 min-height: 100vh; 18 } 19 20 .gradient-bg { 21 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 22 } 23 24 .file-upload { 25 border: 2px dashed #cbd5e0; 26 transition: all 0.3s ease; 27 } 28 29 .file-upload:hover { 30 border-color: #667eea; 31 transform: translateY(-2px); 32 } 33 34 .file-upload.dragover { 35 border-color: #667eea; 36 background-color: rgba(102, 126, 234, 0.1); 37 } 38 39 .parameter-slider::-webkit-slider-thumb { 40 -webkit-appearance: none; 41 appearance: none; 42 width: 20px; 43 height: 20px; 44 border-radius: 50%; 45 background: #667eea; 46 cursor: pointer; 47 } 48 49 .result-card { 50 box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 51 0 4px 6px -2px rgba(0, 0, 0, 0.05); 52 transition: all 0.3s ease; 53 } 54 55 .result-card:hover { 56 transform: translateY(-5px); 57 box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 58 0 10px 10px -5px rgba(0, 0, 0, 0.04); 59 } 60 61 .loading-spinner { 62 animation: spin 1s linear infinite; 63 } 64 65 @keyframes spin { 66 0% { transform: rotate(0deg); } 67 100% { transform: rotate(360deg); } 68 } 69 70 /* Audio visualizer canvas styling */ 71 #audioVisualizer { 72 background-color: #f5f7fa; 73 border-radius: 0.5rem; 74 box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); 75 } 76 </style> 77</head> 78<body class="text-gray-800"> 79 <div class="container mx-auto px-4 py-8 max-w-6xl"> 80 <!-- Header --> 81 <header class="gradient-bg text-white rounded-xl p-6 mb-8 shadow-lg"> 82 <div class="flex flex-col md:flex-row items-center justify-between"> 83 <div class="mb-4 md:mb-0"> 84 <h1 class="text-3xl font-bold"> 85 <i class="fas fa-music mr-2"></i> MIDI Melody Generator 86 </h1> 87 <p class="mt-2 opacity-90">Transform your MIDI files with expressive melodies</p> 88 </div> 89 <div class="flex items-center space-x-2"> 90 <span class="px-3 py-1 bg-white bg-opacity-20 rounded-full text-sm">Hugging Face</span> 91 <span class="px-3 py-1 bg-white bg-opacity-20 rounded-full text-sm">AI Music</span> 92 </div> 93 </div> 94 </header> 95 96 <!-- Main Content --> 97 <main class="space-y-8"> 98 <!-- Input Section --> 99 <section class="bg-white rounded-xl p-6 shadow-md"> 100 <h2 class="text-xl font-semibold mb-4 flex items-center"> 101 <i class="fas fa-upload mr-2 text-indigo-500"></i> Upload MIDI File 102 </h2> 103 104 <div id="fileDropArea" class="file-upload rounded-lg p-8 text-center cursor-pointer mb-6"> 105 <div class="flex flex-col items-center justify-center"> 106 <i class="fas fa-cloud-upload-alt text-4xl text-indigo-400 mb-3"></i> 107 <p class="font-medium">Drag & drop your MIDI file here</p> 108 <p class="text-gray-500 text-sm mt-1">or click to browse files</p> 109 <input type="file" id="fileInput" class="hidden" accept=".mid,.midi"> 110 </div> 111 </div> 112 113 <div id="fileNameDisplay" class="hidden bg-gray-50 rounded-lg p-4 mb-6"> 114 <div class="flex items-center justify-between"> 115 <div class="flex items-center"> 116 <i class="fas fa-file-audio text-indigo-500 mr-3 text-xl"></i> 117 <div> 118 <p class="font-medium" id="fileName">sample.mid</p> 119 <p class="text-gray-500 text-sm" id="fileSize">2.4 MB</p> 120 </div> 121 </div> 122 <button id="removeFileBtn" class="text-red-500 hover:text-red-700"> 123 <i class="fas fa-times"></i> 124 </button> 125 </div> 126 </div> 127 128 <!-- Melody Parameters --> 129 <h2 class="text-xl font-semibold mb-4 mt-8 flex items-center"> 130 <i class="fas fa-sliders-h mr-2 text-indigo-500"></i> Melody Parameters 131 </h2> 132 133 <div class="space-y-6"> 134 <div> 135 <label class="block text-sm font-medium text-gray-700 mb-1">Melody Type</label> 136 <div class="grid grid-cols-2 gap-2"> 137 <button id="expressiveBtn" class="melody-type-btn bg-indigo-100 text-indigo-700 border-indigo-300"> 138 Expressive 139 </button> 140 <button id="simpleBtn" class="melody-type-btn bg-white text-gray-700 border-gray-300"> 141 Simple 142 </button> 143 </div> 144 </div> 145 146 <div> 147 <label for="channelInput" class="block text-sm font-medium text-gray-700 mb-1">Channel</label> 148 <input type="range" id="channelInput" min="0" max="15" value="
1480" class="w-full parameter-slider"> 149 <div class="flex justify-between text-xs text-gray-500 mt-1"> 150 <span>0</span> 151 <span>15</span> 152 </div> 153 </div> 154 155 <div> 156 <label for="patchInput" class="block text-sm font-medium text-gray-700 mb-1">Patch (Instrument)</label> 157 <input type="range" id="patchInput" min="0" max="127" value="0" class="w-full parameter-slider"> 158 <div class="flex justify-between text-xs text-gray-500 mt-1"> 159 <span>0 (Piano)</span> 160 <span>127 (Gunshot)</span> 161 </div> 162 </div> 163 164 <div> 165 <label for="startChordInput" class="block text-sm font-medium text-gray-700 mb-1">Start Chord</label> 166 <input type="range" id="startChordInput" min="0" max="100" value="0" class="w-full parameter-slider"> 167 <div class="flex justify-between text-xs text-gray-500 mt-1"> 168 <span>0%</span> 169 <span>100%</span> 170 </div> 171 </div> 172 173 <div class="flex items-center"> 174 <input type="checkbox" id="sustainInput" checked class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded"> 175 <label for="sustainInput" class="ml-2 block text-sm text-gray-700">Apply sustains</label> 176 </div> 177 </div> 178 179 <button id="generateBtn" class="gradient-bg text-white font-medium py-3 px-6 rounded-lg w-full mt-8 hover:opacity-90 transition flex items-center justify-center"> 180 <i class="fas fa-magic mr-2"></i> Generate Melody 181 </button> 182 </section> 183 184 <!-- Results Section (Always at the bottom) --> 185 <section class="bg-white rounded-xl p-6 shadow-md"> 186 <h2 class="text-xl font-semibold mb-4 flex items-center"> 187 <i class="fas fa-poll mr-2 text-indigo-500"></i> Results 188 </h2> 189 190 <div id="emptyState" class="text-center py-12"> 191 <i class="fas fa-music text-4xl text-gray-300 mb-4"></i> 192 <p class="text-gray-500">Your generated melody will appear here</p> 193 </div> 194 195 <div id="loadingState" class="hidden text-center py-12"> 196 <div class="loading-spinner inline-block border-4 border-indigo-500 border-t-transparent rounded-full w-12 h-12 mb-4"></div> 197 <p class="text-gray-700">Generating your melody...</p> 198 </div> 199 200 <div id="resultContainer" class="hidden"> 201 <div class="result-card bg-indigo-50 rounded-lg p-4 mb-4"> 202 <div class="flex items-center mb-3"> 203 <i class="fas fa-file-audio text-indigo-500 mr-2"></i> 204 <h3 class="font-medium" id="resultTitle">Generated Melody</h3> 205 </div> 206 <audio id="audioPlayer" crossOrigin="anonymous" controls class="w-full"></audio> 207 <!-- Audio Visualizer Canvas --> 208 <canvas id="audioVisualizer" class="w-full h-48 mt-4"></canvas> 209 <div class="mt-4 flex space-x-2"> 210 <button id="downloadBtn" class="flex-1 bg-white text-indigo-600 border border-indigo-300 py-2 px-4 rounded hover:bg-indigo-50 transition flex items-center justify-center"> 211 <i class="fas fa-download mr-2"></i> Download 212 </button> 213 <button id="copyLinkBtn" class="flex-1 bg-white text-indigo-600 border border-indigo-300 py-2 px-4 rounded hover:bg-indigo-50 transition flex items-center justify-center"> 214 <i class="fas fa-link mr-2"></i> Copy Link 215 </button> 216 </div> 217 </div> 218 219 <div class="result-card bg-gray-50 rounded-lg p-4 mt-6"> 220 <h3 class="font-medium mb-3 flex items-center"> 221 <i class="fas fa-info-circle text-indigo-500 mr-2"></i> Generation Details 222 </h3> 223 <div class="space-y-2 text-sm"> 224 <div class="flex justify-between"> 225 <span class="text-gray-600">Melody Type:</span>
226 <span id="detailType" class="font-medium">Expressive</span> 227 </div> 228 <div class="flex justify-between"> 229 <span class="text-gray-600">Channel:</span> 230 <span id="detailChannel" class="font-medium">0</span> 231 </div> 232 <div class="flex justify-between"> 233 <span class="text-gray-600">Instrument:</span> 234 <span id="detailPatch" class="font-medium">Piano (0)</span> 235 </div> 236 <div class="flex justify-between"> 237 <span class="text-gray-600">Start Chord:</span> 238 <span id="detailStart" class="font-medium">0%</span> 239 </div> 240 <div class="flex justify-between"> 241 <span class="text-gray-600">Sustains:</span> 242 <span id="detailSustain" class="font-medium">Enabled</span> 243 </div> 244 </div> 245 <div id="outputSummary" class="mt-4 text-sm text-gray-700"></div> 246 </div> 247 </div> 248 </section> 249 </main> 250 251 <!-- Footer --> 252 <footer class="mt-12 text-center text-gray-500 text-sm"> 253 <p>Powered by Hugging Face Spaces ⢠MIDI Melody Generator</p> 254 <p class="mt-1">Upload your MIDI file to generate beautiful melodies with AI</p> 255 </footer> 256 </div> 257 258 <!-- Module Script --> 259
259<script type="module"> 260 import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; 261 262 document.addEventListener('DOMContentLoaded', async function() { 263 // DOM Elements 264 const fileDropArea = document.getElementById('fileDropArea'); 265 const fileInput = document.getElementById('fileInput'); 266 const fileNameDisplay = document.getElementById('fileNameDisplay'); 267 const fileName = document.getElementById('fileName'); 268 const fileSize = document.getElementById('fileSize'); 269 const removeFileBtn = document.getElementById('removeFileBtn'); 270 const generateBtn = document.getElementById('generateBtn'); 271 const emptyState = document.getElementById('emptyState'); 272 const loadingState = document.getElementById('loadingState'); 273 const resultContainer = document.getElementById('resultContainer'); 274 const audioPlayer = document.getElementById('audioPlayer'); 275 const downloadBtn = document.getElementById('downloadBtn'); 276 const copyLinkBtn = document.getElementById('copyLinkBtn'); 277 const expressiveBtn = document.getElementById('expressiveBtn'); 278 const simpleBtn = document.getElementById('simpleBtn'); 279 const channelInput = document.getElementById('channelInput'); 280 const patchInput = document.getElementById('patchInput'); 281 const startChordInput = document.getElementById('startChordInput'); 282 const sustainInput = document.getElementById('sustainInput'); 283 const detailType = document.getElementById('detailType'); 284 const detailChannel = document.getElementById('detailChannel'); 285 const detailPatch = document.getElementById('detailPatch'); 286 const detailStart = document.getElementById('detailStart'); 287 const detailSustain = document.getElementById('detailSustain'); 288 const resultTitle = document.getElementById('resultTitle'); 289 const outputSummary = document.getElementById('outputSummary'); 290 const audioVisualizer = document.getElementById('audioVisualizer'); 291 292 let selectedFile = null; 293 let melodyType = 'Expressive'; 294 let generatedMidiFileUrl = null; 295 296 // Connect to the Gradio backend 297 const gradioClient = await Client.connect("asigalov61/MIDI-Melody"); 298 299 // Event Listeners 300 fileDropArea.addEventListener('click', () => fileInput.click()); 301 fileInput.addEventListener('change', handleFileSelect); 302 removeFileBtn.addEventListener('click', removeFile); 303 generateBtn.addEventListener('click', generateMelody); 304 expressiveBtn.addEventListener('click', () => setMelodyType('Expressive')); 305 simpleBtn.addEventListener('click', () => setMelodyType('Simple')); 306 downloadBtn.addEventListener('click', downloadResult); 307 copyLinkBtn.addEventListener('click', copyResultLink); 308 309 // Drag and drop events 310 ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { 311 fileDropArea.addEventListener(eventName, preventDefaults, false); 312 }); 313 314 ['dragenter', 'dragover'].forEach(eventName => { 315 fileDropArea.addEventListener(eventName, highlight, false); 316 }); 317 318 ['dragleave', 'drop'].forEach(eventName => { 319 fileDropArea.addEventListener(eventName, unhighlight, false); 320 }); 321 322 fileDropArea.addEventListener('drop', handleDrop, false); 323 324 // Parameter change events 325 channelInput.addEventListener('input', updateDetails); 326 patchInput.addEventListener('input', updateDetails); 327 startChordInput.addEventListener('input', updateDetails); 328 sustainInput.addEventListener('change', updateDetails); 329 330 // Audio visualizer variables 331 let audioCtx, analyser, source; 332 const canvasCtx = audioVisualizer.getContext("2d"); 333 334 function preventDefaults(e) { 335 e.preventDefault(); 336 e.stopPropagation(); 337 } 338 339 function highlight() { 340 fileDropArea.classList.add('dragover'); 341 } 342 343 function unhighlight() { 344 fileDropArea.classList.remove('dragover'); 345 } 346 347 function handleDrop(e) { 348 const dt = e.dataTransfer; 349 const files = dt.files; 350 if (files.length) { 351 handleFile(files[0]); 352 } 353 } 354 355 function handleFileSelect(e) { 356 const files = e.target.files; 357 if (files.length) { 358 handleFile(files[0]); 359 } 360 } 361 362 function handleFile(file) { 363 if (!file.name.match(/\.(mid|midi)$/i)) { 364 alert('Please upload a MIDI file (.mid or .midi)'); 365 return; 366 } 367 selectedFile = file; 368 fileName.textContent = file.name; 369 fileSize.textContent = formatFileSize(file.size); 370 fileNameDisplay.classList.remove('hidden'); 371 372 generateBtn.classList.remove('opacity-50'); 373 generateBtn.disabled = false; 374 } 375 376 function removeFile() { 377 selectedFile = null; 378 fileInput.value = ''; 379 fileNameDisplay.classList.add('hidden'); 380 generateBtn.classList.add('opacity-50'); 381 generateBtn.disabled = true; 382 } 383 384 function formatFileSize(bytes) { 385 if (bytes === 0) return '0 Bytes';
386 const k = 1024; 387 const sizes = ['Bytes', 'KB', 'MB', 'GB']; 388 const i = Math.floor(Math.log(bytes) / Math.log(k)); 389 return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; 390 } 391 392 function setMelodyType(type) { 393 melodyType = type; 394 if (type === 'Expressive') { 395 expressiveBtn.classList.add('bg-indigo-100', 'text-indigo-700', 'border-indigo-300'); 396 expressiveBtn.classList.remove('bg-white', 'text-gray-700', 'border-gray-300'); 397 simpleBtn.classList.add('bg-white', 'text-gray-700', 'border-gray-300'); 398 simpleBtn.classList.remove('bg-indigo-100', 'text-indigo-700', 'border-indigo-300'); 399 } else { 400 simpleBtn.classList.add('bg-indigo-100', 'text-indigo-700', 'border-indigo-300'); 401 simpleBtn.classList.remove('bg-white', 'text-gray-700', 'border-gray-300'); 402 expressiveBtn.classList.add('bg-white', 'text-gray-700', 'border-gray-300'); 403 expressiveBtn.classList.remove('bg-indigo-100', 'text-indigo-700', 'border-indigo-300'); 404 } 405 updateDetails(); 406 } 407 408 async function generateMelody() { 409 if (!selectedFile) return; 410 411 emptyState.classList.add('hidden'); 412 loadingState.classList.remove('hidden'); 413 resultContainer.classList.add('hidden'); 414 415 try { 416 const normalizedName = selectedFile.name; 417 const correctedFile = new File([selectedFile], normalizedName, { 418 type: "audio/midi", 419 lastModified: selectedFile.lastModified 420 }); 421 422 const tempBlob = handle_file(correctedFile); 423 const file_ref = new File( 424 [tempBlob], 425 correctedFile.name, 426 { type: "audio/midi", lastModified: correctedFile.lastModified } 427 ); 428 429 const result = await gradioClient.predict("/AddMelody", { 430 input_midi: file_ref, 431 input_mel_type: melodyType, 432 input_channel: parseInt(channelInput.value), 433 input_patch: parseInt(patchInput.value), 434 input_start_chord: parseInt(startChordInput.value), 435 input_apply_sustains: sustainInput.checked, 436 }); 437 438 const [outputTitle, outputSummaryText, outputMidiFile, outputMidiAudio, outputMidiScorePlot] = result.data; 439 loadingState.classList.add('hidden'); 440 resultContainer.classList.remove('hidden'); 441 442 resultTitle.textContent = outputTitle || "Generated Melody"; 443 outputSummary.textContent = outputSummaryText || ""; 444 445 if (outputMidiAudio) { 446 let audioUrl; 447 if (typeof outputMidiAudio === "object" && outputMidiAudio.url) { 448 audioUrl = outputMidiAudio.url; 449 } else if (outputMidiAudio instanceof Blob) { 450 audioUrl = URL.createObjectURL(outputMidiAudio); 451 } else if (typeof outputMidiAudio === "string") { 452 audioUrl = outputMidiAudio; 453 } else { 454 console.error("Unexpected type for outputMidiAudio:", outputMidiAudio); 455 } 456 audioPlayer.src = audioUrl; 457 } 458 459 if (outputMidiFile) { 460 let midiUrl; 461 if (typeof outputMidiFile === "object" && outputMidiFile.url) { 462 midiUrl = outputMidiFile.url; 463 } else if (outputMidiFile instanceof Blob) { 464 midiUrl = URL.createObjectURL(outputMidiFile); 465 } else if (typeof outputMidiFile === "string") { 466 midiUrl = outputMidiFile; 467 } else { 468 console.error("Unexpected type for outputMidiFile:", outputMidiFile); 469 } 470 generatedMidiFileUrl = midiUrl; 471 } 472 473 let plotImg = document.getElementById("midiScorePlot"); 474 if (!plotImg) { 475 plotImg = document.createElement("img"); 476 plotImg.id = "midiScorePlot"; 477 plotImg.className = "w-full mt-4"; 478 resultContainer.appendChild(plotImg); 479 } 480 if (outputMidiScorePlot) { 481 let plotUrl; 482 if (typeof outputMidiScorePlot === "object" && outputMidiScorePlot.plot) { 483 plotUrl = outputMidiScorePlot.plot; 484 } else if (outputMidiScorePlot instanceof Blob) { 485 plotUrl = URL.createObjectURL(outputMidiScorePlot); 486 } else if (typeof outputMidiScorePlot === "string") { 487 plotUrl = outputMidiScorePlot; 488 } else { 489 console.error("Unexpected type for outputMidiScorePlot:", outputMidiScorePlot); 490 return; 491 } 492 plotImg.src = plotUrl; 493 } 494 updateDetails(); 495 } catch (error) { 496 console.error("Error generating melody:", error); 497 loadingState.classList.add('hidden'); 498 emptyState.classList.remove('hidden'); 499 alert("Error generating melody. Please try again."); 500 } 501 } 502 503 function updateDetails() { 504 detailType.textContent = melodyType; 505 detailChannel.textContent = channelInput.value; 506 507 const patchNumber = parseInt(patchInput.value); 508 let instrumentName = "Piano"; 509 if (patchNumber >= 0 && patchNumber <= 7) instrumentName = "Piano"; 510 else if (patchNumber >= 8 && patchNumber <= 15) instrumentName = "Chromatic Percussion"; 511 else if (patchNumber >= 16 && patchNumber <= 23) instrumentName = "Organ"; 512 else if (patchNumber >= 24 && patchNumber <= 31) instrumentName = "Guitar"; 513 else if (patchNumber >= 32 && patchNumber <= 39) instrumentName = "Bass"; 514 else if (patchNumber >= 40 && patchNumber <= 47) instrumentName = "Strings"; 515 else if (patchNumber >= 48 && patchNumber <= 55) instrumentName = "Ensemble"; 516 else if (patchNumber >= 56 && patchNumber <= 63) instrumentName = "Brass"; 517 else if (patchNumber >= 64 && patchNumber <= 71) instrumentName = "Reed"; 518 else if (patchNumber >= 72 && patchNumber <= 79) instrumentName = "Pipe"; 519 else if (patchNumber >= 80 && patchNumber <= 87) instrumentName = "Synth Lead"; 520 else if (patchNumber >= 88 && patchNumber <= 95) instrumentName = "Synth Pad"; 521 else if (patchNumber >= 96 && patchNumber <= 103) instrumentName = "Synth Effects"; 522 else if (patchNumber >= 104 && patchNumber <= 111) instrumentName = "Ethnic"; 523 else if (patchNumber >= 112 && patchNumber <= 119) instrumentName = "Percussive"; 524 else if (patchNumber >= 120 && patchNumber <= 127) instrumentName = "Sound Effects"; 525 526 detailPatch.textContent = `${instrumentName} (${patchNumber})`; 527 detailStart.textContent = `${startChordInput.value}%`; 528 detailSustain.textContent = sustainInput.checked ? "Enabled" : "Disabled"; 529 } 530 531 function downloadResult() { 532 if (generatedMidiFileUrl) { 533 const a = document.createElement("a"); 534 a.href = generatedMidiFileUrl; 535 a.download = resultTitle.textContent || "melody.mid"; 536 document.body.appendChild(a); 537 a.click(); 538 document.body.removeChild(a); 539 } else { 540 alert("No generated file available for download."); 541 } 542 } 543 544 function copyResultLink() { 545 if (generatedMidiFileUrl) { 546 navigator.clipboard.writeText(generatedMidiFileUrl) 547 .then(() => alert("Download link copied to clipboard!")) 548 .catch(() => alert("Failed to copy the link.")); 549 } else { 550 alert("No generated link available to copy."); 551 } 552 } 553 554 // Setup the audio visualizer when playback starts 555 audioPlayer.addEventListener('play', () => { 556 if (!audioCtx) { 557 audioCtx = new (window.AudioContext || window.webkitAudioContext)(); 558 analyser = audioCtx.createAnalyser(); 559 analyser.fftSize = 256; 560 source = audioCtx.createMediaElementSource(audioPlayer); 561 source.connect(analyser); 562 analyser.connect(audioCtx.destination); 563 visualize(); 564 } 565 }); 566 567 function visualize() { 568 const bufferLength = analyser.frequencyBinCount; 569 const dataArray = new Uint8Array(bufferLength); 570 571 function draw() { 572 requestAnimationFrame(draw); 573 analyser.getByteFrequencyData(dataArray);
574 canvasCtx.fillStyle = "#f5f7fa"; 575 canvasCtx.fillRect(0, 0, audioVisualizer.width, audioVisualizer.height); 576 577 const barWidth = (audioVisualizer.width / bufferLength) * 2.5; 578 let barHeight; 579 let x = 0; 580 581 for(let i = 0; i < bufferLength; i++) { 582 barHeight = dataArray[i]; 583 const red = barHeight + 25; 584 const green = 250 - barHeight; 585 const blue = 50; 586 canvasCtx.fillStyle = `rgb(${red}, ${green}, ${blue})`; 587 canvasCtx.fillRect(x, audioVisualizer.height - barHeight/2, barWidth, barHeight/2); 588 x += barWidth + 1; 589 } 590 } 591 draw(); 592 } 593 594 // Initialize details on page load 595 updateDetails(); 596 }); 597 </script>
597 598</body> 599</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.