349 lines
17 KiB
Markdown
349 lines
17 KiB
Markdown
# 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:
|
|
|
|
```html
|
|
<!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>
|
|
|
|
``` |