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> <Track Templates></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> <Project Directory></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>