Files
SonicForgeStudio/md/51_MEDIA_EXPLORER.md

17 KiB

DETAILED SPECIFICATION FOR DAW MEDIA EXPLORER PANEL INTERFACE & HTML5 SOURCE CODE

This document details the design structure, features, and executable HTML5 source code for a general-purpose Media Explorer Panel interface styled after the REAPER DAW.


I. DETAILED INTERFACE REGIONS

The interface is divided vertically into 3 primary functional regions, arranged from top to bottom:

1. Top Navigation Toolbar

  • Left Navigation Button Group: (Back), (Forward), (Up to Parent Directory), (Refresh).
  • Directory Address Bar: Displays the current folder path with a drop-down menu for quick-access path selection.
  • Filter / Search Box: Input field supporting quick file filtering and keyword searching.
  • View Mode Button: Toggle button for switching file display layouts (Details / List view).

2. Middle Split Panel (Directory Tree + File List)

  • Left Directory Tree View:

  • Hierarchical tree structure displaying project directories, system shortcuts, drives, and audio sample library folders.

  • Supports expand/collapse toggle buttons and highlighted background states indicating the actively selected folder.

  • Right File List Table:

  • Displays audio and MIDI files with standard category icons.

  • Highlights the currently selected file with a prominent blue row background.

3. Bottom Preview & Transport Panel

  • Transport Control Bar & Playback Parameters:

  • Transport Buttons: (Stop), (Play), ❚❚ (Pause), (Loop/Repeat), Auto-Play (Automatically previews files upon selection).

  • Parameter Controls: Pitch adjustment (Pitch matching), Rate (Playback speed factor), Volume Slider (Gain adjustment in dB).

  • Media Type Badge: Label displaying file format classification (MIDI / Audio).

  • Visualizer Canvas Display & Metadata:

  • Visualizer Canvas (Left): Dark display area rendering an overview Piano Roll note grid (for MIDI files) or waveform display (for Audio files), integrated with a timeline/bar ruler and white Playhead cursor.

  • Metadata Text Box (Right): Information pane displaying detailed file parameters (MIDI event count, duration, sample rate / resolution).

  • Footer Status Bar:

  • Displays current playback timestamp vs. total file length.

  • Name of the currently selected/playing file.

  • Tempo metadata (BPM) and playback rate scale factor.


II. EXECUTABLE HTML5 & TAILWIND CSS SOURCE CODE

Below is the complete HTML5 source code integrating Web Audio API synthesis, a Canvas visualizer, and real-time interactive file selection and audio preview playback:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>DAW Media Explorer Panel Component</title>
  <script src="https://cdn.tailwindcss.com"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
  <style>
    body { font-family: 'Inter', sans-serif; user-select: none; }
    .font-mono { font-family: 'JetBrains Mono', monospace; }

    /* REAPER Classic Panel Style */
    .reaper-panel {
      background: #c0c0c0;
      border: 2px solid #ffffff;
      border-right-color: #808080;
      border-bottom-color: #808080;
    }
    .reaper-inset {
      background: #ffffff;
      border: 1px solid #808080;
      box-shadow: inset 1px 1px 2px rgba(0,0,0,0.3);
    }
    .reaper-dark-inset {
      background: #181818;
      border: 1px solid #3a3a3a;
      box-shadow: inset 1px 1px 3px rgba(0,0,0,0.8);
    }
    .file-row-selected {
      background-color: #3399ff !important;
      color: #ffffff !important;
    }
    .file-row-selected .file-icon { color: #ffffff !important; }

    /* Scrollbar */
    ::-webkit-scrollbar { width: 12px; height: 12px; }
    ::-webkit-scrollbar-track { background: #e0e0e0; border-left: 1px solid #a0a0a0; }
    ::-webkit-scrollbar-thumb { background: #b0b0b0; border: 1px solid #ffffff; border-right-color: #707070; border-bottom-color: #707070; }
    ::-webkit-scrollbar-thumb:hover { background: #909090; }

    /* Volume Slider */
    input[type=range].fader-slider {
      appearance: none; -webkit-appearance: none;
      background: #111; height: 6px; border-radius: 3px; border: 1px solid #555;
    }
    input[type=range].fader-slider::-webkit-slider-thumb {
      -webkit-appearance: none; height: 16px; width: 12px;
      background: linear-gradient(180deg, #e2e8f0 0%, #64748b 50%, #1e293b 100%);
      border: 1px solid #000; border-radius: 2px; box-shadow: 0 2px 4px rgba(0,0,0,0.5); cursor: pointer;
    }
  </style>
</head>
<body class="bg-slate-950 text-slate-900 h-screen flex items-center justify-center p-2 overflow-hidden">

  <!-- MEDIA EXPLORER PANEL CONTAINER -->
  <div class="w-full max-w-4xl h-[420px] flex flex-col reaper-panel text-slate-900 overflow-hidden shadow-2xl relative">
    
    <!-- 1. TOP NAVIGATION TOOLBAR -->
    <div class="h-8 bg-[#d4d0c8] border-b border-[#808080] px-2 flex items-center justify-between text-xs shrink-0">
      <div class="flex items-center gap-1 flex-1 mr-2">
        <button class="w-5 h-5 bg-[#e0e0e0] hover:bg-[#ffffff] border border-[#707070] rounded-sm flex items-center justify-center text-[10px]" title="Back">
          <i class="fa-solid fa-arrow-left"></i>
        </button>
        <button class="w-5 h-5 bg-[#e0e0e0] hover:bg-[#ffffff] border border-[#707070] rounded-sm flex items-center justify-center text-[10px]" title="Forward">
          <i class="fa-solid fa-arrow-right"></i>
        </button>
        <button class="w-5 h-5 bg-[#e0e0e0] hover:bg-[#ffffff] border border-[#707070] rounded-sm flex items-center justify-center text-[10px]" title="Up Directory">
          <i class="fa-solid fa-arrow-up"></i>
        </button>
        <button class="w-5 h-5 bg-[#e0e0e0] hover:bg-[#ffffff] border border-[#707070] rounded-sm flex items-center justify-center text-[10px]" title="Refresh">
          <i class="fa-solid fa-rotate-right"></i>
        </button>

        <div class="flex-1 flex items-center reaper-inset h-5 px-1 bg-white">
          <i class="fa-solid fa-folder-open text-[#d9a752] text-[11px] mr-1.5"></i>
          <input type="text" id="addressBarInput" value="Root:\Media Library" class="w-full text-xs outline-none bg-transparent font-sans text-slate-800" readonly>
          <i class="fa-solid fa-caret-down text-slate-600 text-[10px] ml-1"></i>
        </div>
      </div>

      <div class="flex items-center gap-2">
        <div class="flex items-center reaper-inset h-5 px-1 bg-white w-40">
          <input type="text" placeholder="Filter/Search..." class="w-full text-xs outline-none bg-transparent font-sans text-slate-800">
          <i class="fa-solid fa-caret-down text-slate-600 text-[10px] ml-1"></i>
        </div>
        <button class="px-2 py-0.5 bg-[#e0e0e0] hover:bg-[#ffffff] border border-[#707070] rounded-sm text-[11px] flex items-center gap-1 font-semibold">
          <span>Details</span> <i class="fa-solid fa-caret-down text-[9px]"></i>
        </button>
      </div>
    </div>

    <!-- 2. MIDDLE SPLIT VIEW (DIRECTORY TREE + FILE LIST) -->
    <div class="flex-1 flex overflow-hidden">
      
      <!-- DIRECTORY TREE (LEFT) -->
      <div class="w-60 reaper-inset m-1 mr-0 overflow-y-auto p-1 text-xs select-none bg-white">
        <div class="space-y-0.5 font-sans">
          <div class="flex items-center gap-1 px-1 py-0.5 hover:bg-slate-200 text-slate-700"><span class="w-3"></span> &lt;Track Templates&gt;</div>
          <div class="flex items-center gap-1 px-1 py-0.5 hover:bg-slate-200 text-slate-700"><span class="w-3"></span> &lt;Project Directory&gt;</div>
          <div class="flex items-center gap-1 px-1 py-0.5 hover:bg-slate-200 text-slate-700"><i class="fa-solid fa-plus text-[9px] text-slate-500"></i> My Computer</div>

          <!-- Selected Folder -->
          <div id="folderMidi" class="flex items-center gap-1 px-1 py-0.5 hover:bg-blue-100 cursor-pointer font-semibold text-slate-900 bg-slate-300 rounded-sm">
            <i class="fa-solid fa-minus text-[9px] text-slate-600"></i>
            <i class="fa-solid fa-folder-open text-[#d9a752]"></i> Media Library
          </div>

          <div class="pl-3 space-y-0.5">
            <div class="flex items-center gap-1 px-1 py-0.5 hover:bg-slate-200 cursor-pointer text-slate-800">
              <i class="fa-solid fa-plus text-[9px] text-slate-500"></i> <i class="fa-solid fa-folder text-[#d9a752]"></i> Sound Effects
            </div>
            <div id="folderDigitalJuice" class="flex items-center gap-1 px-1 py-0.5 hover:bg-blue-100 cursor-pointer text-slate-800 pl-4">
              <i class="fa-solid fa-folder text-[#d9a752]"></i> MIDI Collections
            </div>
          </div>

          <div class="flex items-center gap-1 px-1 py-0.5 hover:bg-slate-200 text-slate-700"><i class="fa-solid fa-plus text-[9px] text-slate-500"></i> Desktop</div>
          <div class="flex items-center gap-1 px-1 py-0.5 hover:bg-slate-200 text-slate-700"><i class="fa-solid fa-plus text-[9px] text-slate-500"></i> My Documents</div>
        </div>
      </div>

      <!-- FILE LIST TABLE (RIGHT) -->
      <div class="flex-1 reaper-inset m-1 overflow-y-auto relative bg-white">
        <table class="w-full text-xs text-left border-collapse">
          <thead class="sticky top-0 bg-[#e0e0e0] border-b border-[#a0a0a0] text-slate-800 font-semibold select-none shadow-sm z-10">
            <tr><th class="py-1 px-2 border-r border-[#b0b0b0]">File</th></tr>
          </thead>
          <tbody id="fileListTbody" class="font-sans text-slate-800">
            <!-- Dynamic File Rows -->
          </tbody>
        </table>
      </div>

    </div>

    <!-- 3. BOTTOM PREVIEW CONTROL BAR & VISUALIZER CANVAS -->
    <div class="h-32 bg-[#d4d0c8] border-t border-[#808080] p-1.5 flex flex-col justify-between text-xs shrink-0 select-none">
      
      <!-- CONTROLS ROW -->
      <div class="flex items-center justify-between gap-2">
        <div class="flex items-center gap-1">
          <button id="btnStop" class="w-6 h-6 bg-[#e0e0e0] hover:bg-[#ffffff] border border-[#707070] rounded-sm flex items-center justify-center text-slate-800"><i class="fa-solid fa-square text-[10px]"></i></button>
          <button id="btnPlay" class="w-6 h-6 bg-[#e0e0e0] hover:bg-[#ffffff] border border-[#707070] rounded-sm flex items-center justify-center text-emerald-700 font-bold"><i class="fa-solid fa-play text-xs"></i></button>
          <button id="btnPause" class="w-6 h-6 bg-[#e0e0e0] hover:bg-[#ffffff] border border-[#707070] rounded-sm flex items-center justify-center text-amber-700"><i class="fa-solid fa-pause text-xs"></i></button>
          <button id="btnLoop" class="w-6 h-6 bg-[#e0e0e0] hover:bg-[#ffffff] border border-[#707070] rounded-sm flex items-center justify-center text-slate-700"><i class="fa-solid fa-rotate-right text-xs"></i></button>
          <button id="btnAutoPlay" class="h-6 px-2 bg-gradient-to-r from-cyan-600 to-emerald-600 text-white border border-slate-700 rounded-sm font-bold text-[10px] flex items-center gap-1 shadow-sm">
            <i class="fa-solid fa-bolt text-[9px]"></i> <span>Auto-Play</span>
          </button>
        </div>

        <div class="flex items-center gap-3 font-mono text-[11px]">
          <div class="flex items-center gap-1">
            <span>Pitch:</span>
            <div class="reaper-inset px-1 bg-white h-5 flex items-center w-14">
              <input type="number" value="0.00" step="0.5" class="w-full text-xs text-right outline-none bg-transparent">
            </div>
          </div>
          <div class="flex items-center gap-1">
            <span>Rate:</span>
            <div class="reaper-inset px-1 bg-white h-5 flex items-center w-12">
              <input type="number" value="1.0" step="0.1" class="w-full text-xs text-right outline-none bg-transparent">
            </div>
          </div>
        </div>

        <div class="flex items-center gap-2">
          <span class="font-sans text-slate-700">Volume:</span>
          <input id="volSlider" type="range" min="-60" max="12" step="0.5" value="0" class="fader-slider w-28">
          <div class="reaper-inset px-1 bg-white h-5 flex items-center justify-center w-14 font-mono text-[11px]">
            <span id="volDbText">0.00 dB</span>
          </div>
        </div>

        <div id="badgeType" class="px-2 py-0.5 bg-purple-950 text-purple-300 border border-purple-800 font-mono font-bold text-[10px] rounded-sm">
          MIDI
        </div>
      </div>

      <!-- CANVAS & METADATA ROW -->
      <div class="flex items-stretch gap-2 my-1 h-16">
        <div class="flex-1 reaper-dark-inset relative overflow-hidden">
          <canvas id="previewCanvas" class="w-full h-full block cursor-pointer"></canvas>
        </div>

        <div class="w-56 reaper-dark-inset p-1.5 font-mono text-[10px] text-slate-300 leading-tight overflow-y-auto">
          <div id="metadataContent">
            76 MIDI events<br>
            Length: 16 quarter notes<br>
            Length: 0:08.000 (est)<br>
            Ticks per quarter note: 480
          </div>
        </div>
      </div>

      <!-- BOTTOM STATUS BAR -->
      <div class="h-5 bg-[#c0c0c0] border-t border-[#ffffff] flex items-center justify-between text-[11px] font-mono px-1">
        <div class="flex items-center gap-3">
          <div id="timeRange" class="reaper-inset px-1.5 bg-white text-slate-900 font-bold">0.0000 / 16.0000</div>
          <div class="reaper-inset px-1.5 bg-white text-slate-900">0.0000</div>
          <div class="reaper-inset px-1.5 bg-white text-slate-900">16.0000</div>
        </div>

        <div id="statusFileName" class="text-slate-800 font-bold truncate max-w-xs">File_Selected.mid</div>
        <div id="statusBpm" class="text-slate-700">130 bpm x0.923</div>
      </div>

    </div>

  </div>

  <!-- SCRIPT LOGIC -->
  <script>
    const sampleDb = {
      midi: [
        { name: "MIDI_Loop_01.mid", events: 95, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130 },
        { name: "MIDI_Loop_02_Bass.mid", events: 48, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130 },
        { name: "MIDI_Loop_03_Lead.mid", events: 110, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130 },
        { name: "MIDI_Loop_04.mid", events: 76, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130 },
        { name: "MIDI_Loop_05_Bass.mid", events: 52, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130 },
        { name: "MIDI_Loop_06.mid", events: 88, lengthQn: 16, time: "0:08.000", tpqn: 480, bpm: 130 }
      ]
    };

    let selectedFile = sampleDb.midi[3];
    const fileListTbody = document.getElementById('fileListTbody');
    const previewCanvas = document.getElementById('previewCanvas');
    const canvasCtx = previewCanvas.getContext('2d');

    function renderFiles() {
      fileListTbody.innerHTML = '';
      sampleDb.midi.forEach((f) => {
        const tr = document.createElement('tr');
        tr.className = `cursor-pointer hover:bg-blue-100 ${f.name === selectedFile.name ? 'file-row-selected' : ''}`;
        tr.innerHTML = `<td class="py-1 px-2 flex items-center gap-2"><i class="fa-solid fa-music text-purple-600 file-icon"></i><span>${f.name}</span></td>`;
        tr.onclick = () => {
          selectedFile = f;
          renderFiles();
          updatePreview();
        };
        fileListTbody.appendChild(tr);
      });
    }

    function updatePreview() {
      document.getElementById('statusFileName').innerText = selectedFile.name;
      document.getElementById('metadataContent').innerHTML = `${selectedFile.events} MIDI events<br>Length: ${selectedFile.lengthQn} quarter notes<br>Length: ${selectedFile.time} (est)<br>Ticks per quarter note: ${selectedFile.tpqn}`;
      renderCanvas();
    }

    function renderCanvas() {
      previewCanvas.width = previewCanvas.clientWidth;
      previewCanvas.height = previewCanvas.clientHeight;
      const w = previewCanvas.width, h = previewCanvas.height;
      canvasCtx.clearRect(0, 0, w, h);

      // Grid
      canvasCtx.strokeStyle = '#222';
      for (let y = 0; y < h - 14; y += 8) {
        canvasCtx.beginPath(); canvasCtx.moveTo(0, y); canvasCtx.lineTo(w, y); canvasCtx.stroke();
      }
      canvasCtx.strokeStyle = '#333';
      for (let b = 0; b <= 16; b += 4) {
        const x = (b / 16) * w;
        canvasCtx.beginPath(); canvasCtx.moveTo(x, 0); canvasCtx.lineTo(x, h - 14); canvasCtx.stroke();
      }

      // Notes
      canvasCtx.fillStyle = '#9ca3af';
      for (let i = 0; i < selectedFile.events; i++) {
        const noteX = ((i * 17 + 76) % 95) / 100 * w;
        const noteY = ((i * 13 + 76) % (h - 24)) + 4;
        canvasCtx.fillRect(noteX, noteY, Math.max(8, (i % 5 + 1) * 12), 3);
      }

      // Bar Ruler
      canvasCtx.fillStyle = '#111';
      canvasCtx.fillRect(0, h - 14, w, 14);
      canvasCtx.fillStyle = '#888';
      canvasCtx.font = '9px JetBrains Mono';
      for (let b = 0; b <= 12; b += 4) {
        canvasCtx.fillText(b.toString(), (b / 16) * w + 2, h - 3);
      }
    }

    window.onload = () => { renderFiles(); updatePreview(); };
  </script>
</body>
</html>