MuffleFX - Video & Audio Low-Pass Filter Studio
About this app
Apply muffled audio and low-pass filter effects to specific sections of your videos in real-time right in your browser.
MuffleFX - Video & Audio Low-Pass Filter Studio MuffleFX Studio Video Audio Low-Pass Filter Editor Load Demo Muffled Section Active Timeline Track 00:00.0 / 00:00.0 Real-time Spectrum Analyser Select Video / Audio File Add Muffle Section Muffled Audio Sections Render & Export Video Renders full video with live muffled audio filtering applied. Export Processed Video MuffleFX • Single-File Web Audio & Video FX Engine
Related apps
Put this on your site
Source
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
<title>MuffleFX - Video & Audio Low-Pass Filter Studio</title>
<meta name="description" content="Apply muffled audio and low-pass filter effects to specific sections of your videos in real-time right in your browser.">
<meta name="keywords" content="muffled audio, video filter, low pass filter, audio section editor, underwater audio effect, web audio api">
<!-- Libraries -->
<!-- jQuery (3.6.0) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js" crossorigin="anonymous"></script>
<!-- Bootstrap CSS (5.3.3) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" crossorigin="anonymous">
<!-- Bootstrap JS (5.3.3) -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.min.js" crossorigin="anonymous"></script>
<!-- Font Awesome (6.6.0) -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.6.0/css/all.min.css" crossorigin="anonymous">
<style>
/* Mobile-first base — keep and extend */
*, *::before, *::after { box-sizing: border-box; }
:root {
--bg-dark: #0f172a;
--card-bg: #1e293b;
--accent-purple: #a855f7;
--accent-cyan: #06b6d4;
--muffle-red: #f43f5e;
--text-light: #f8fafc;
}
html, body {
margin: 0;
padding: 0;
width: 100%;
max-width: 100%;
background-color: var(--bg-dark);
color: var(--text-light);
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
overflow-x: hidden;
-webkit-text-size-adjust: 100%;
}
img, video, svg {
max-width: 100%;
height: auto;
}
canvas {
max-width: 100%;
display: block;
}
button, input, select, textarea {
font-size: 16px;
max-width: 100%;
}
#main-container {
width: 100%;
max-width: 1000px;
margin: 0 auto;
min-height: 100dvh;
padding: 0.75rem;
padding-bottom: calc(1.5rem + env(safe-area-inset-bottom, 0px));
}
/* Custom UI Styling */
.app-card {
background: rgba(30, 41, 59, 0.85);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
padding: 1rem;
margin-bottom: 1rem;
box-shadow: 0 8px 20px rgba(0,0,0,0.3);
}
.video-container {
position: relative;
width: 100%;
background: #000;
border-radius: 8px;
overflow: hidden;
aspect-ratio: 16 / 9;
display: flex;
align-items: center;
justify-content: center;
}
video {
width: 100%;
height: 100%;
object-fit: contain;
}
.muffle-badge {
position: absolute;
top: 12px;
right: 12px;
background: rgba(244, 63, 94, 0.9);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-weight: bold;
font-size: 0.85rem;
letter-spacing: 0.5px;
display: none;
align-items: center;
gap: 6px;
box-shadow: 0 0 12px rgba(244, 63, 94, 0.6);
animation: pulse 1.5s infinite;
z-index: 10;
}
@keyframes pulse {
0% { opacity: 0.7; transform: scale(0.98); }
50% { opacity: 1; transform: scale(1.02); }
100% { opacity: 0.7; transform: scale(0.98); }
}
/* Visualizer Canvas */
#viz-canvas {
width: 100%;
height: 60px;
background: rgba(15, 23, 42, 0.6);
border-radius: 6px;
border: 1px solid rgba(255,255,255,0.05);
}
/* Custom Timeline Track */
.timeline-wrapper {
position: relative;
width: 100%;
height: 36px;
background: #0f172a;
border-radius: 6px;
margin: 10px 0;
cursor: pointer;
overflow: hidden;
border: 1px solid rgba(255,255,255,0.15);
}
.timeline-track {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
}
.muffle-range-bar {
position: absolute;
top: 0;
height: 100%;
background: rgba(244, 63, 94, 0.45);
border-left: 2px solid var(--muffle-red);
border-right: 2px solid var(--muffle-red);
pointer-events: none;
}
.playhead {
position: absolute;
top: 0;
width: 3px;
height: 100%;
background: #38bdf8;
box-shadow: 0 0 8px #38bdf8;
pointer-events: none;
z-index: 5;
}
.section-item {
background: rgba(15, 23, 42, 0.6);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 8px;
padding: 10px;
margin-bottom: 8px;
transition: all 0.2s ease;
}
.section-item.active {
border-color: var(--muffle-red);
background: rgba(244, 63, 94, 0.1);
}
.btn-accent {
background: linear-gradient(135deg, var(--accent-purple), #7c3aed);
color: white;
border: none;
font-weight: 600;
}
.btn-accent:hover, .btn-accent:focus {
background: linear-gradient(135deg, #7c3aed, #6d28d9);
color: white;
}
.btn-muffle {
background: linear-gradient(135deg, var(--muffle-red), #e11d48);
color: white;
border: none;
font-weight: 600;
}
.btn-muffle:hover, .btn-muffle:focus {
background: linear-gradient(135deg, #e11d48, #be123c);
color: white;
}
.form-control, .form-select {
background-color: #0f172a;
border-color: rgba(255,255,255,0.2);
color: #fff;
}
.form-control:focus, .form-select:focus {
background-color: #0f172a;
color: #fff;
border-color: var(--accent-purple);
box-shadow: 0 0 0 0.25rem rgba(168, 85, 247, 0.25);
}
.form-range::-webkit-slider-thumb {
background: var(--accent-purple);
}
/* Responsive Touch Controls */
.touch-btn {
min-height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
}
</style>
<script type="text/javascript">
try {
document.addEventListener("DOMContentLoaded", function() {
// State variables
let video = document.getElementById('video-player');
let audioCtx = null;
let sourceNode = null;
let filterNode = null;
let gainNode = null;
let analyserNode = null;
let isAudioSetup = false;
let duration = 0;
let sections = []; // Array of { id, start, end, preset, freq, q }
let nextId = 1;
let isRecording = false;
let mediaRecorder = null;
let recordedChunks = [];
// UI elements
const vizCanvas = document.getElementById('viz-canvas');
const vizCtx = vizCanvas.getContext('2d');
const timelineTrack = document.getElementById('timeline-track');
const playhead = document.getElementById('playhead');
const muffleIndicator = document.getElementById('muffle-indicator');
const timeDisplay = document.getElementById('time-display');
// Audio Presets
const presets = {
underwater: { freq: 350, q: 3.5, name: "Underwater Submerged" },
behind_wall: { freq: 500, q: 1.2, name: "Behind the Wall / Next Room" },
pillow: { freq: 750, q: 0.8, name: "Pillow Muffled" },
heavy_bass: { freq: 280, q: 5.0, name: "Car Trunk Bass" },
custom: { freq: 600, q: 1.5, name: "Custom Filter" }
};
// Auto-init Web Audio Context on user action
function initAudioEngine() {
if (isAudioSetup) return;
try {
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
sourceNode = audioCtx.createMediaElementSource(video);
filterNode = audioCtx.createBiquadFilter();
gainNode = audioCtx.createGain();
analyserNode = audioCtx.createAnalyser();
filterNode.type = 'lowpass';
filterNode.frequency.setValueAtTime(20000, audioCtx.currentTime); // bypass initial
analyserNode.fftSize = 128;
// Wire up: Video -> LowPass -> Gain -> Analyser -> Output
sourceNode.connect(filterNode);
filterNode.connect(gainNode);
gainNode.connect(analyserNode);
analyserNode.connect(audioCtx.destination);
isAudioSetup = true;
drawVisualizer();
} catch (err) {
console.error("Audio Context Init Error: ", err);
}
}
// Load Demo Video (Synthetic Canvas + Audio Tone Video Generator)
function loadDemo() {
const canvas = document.createElement('canvas');
canvas.width = 640;
canvas.height = 360;
const ctx = canvas.getContext('2d');
const stream = canvas.captureStream(30);
// Create synth audio stream
const synthCtx = new (window.AudioContext || window.webkitAudioContext)();
const osc = synthCtx.createOscillator();
const synthGain = synthCtx.createGain();
const dest = synthCtx.createMediaStreamDestination();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(220, synthCtx.currentTime);
// LFO for rhythmic synth sound
const lfo = synthCtx.createOscillator();
lfo.frequency.value = 4; // 4Hz pulse
const lfoGain = synthCtx.createGain();
lfoGain.gain.value = 100;
lfo.connect(lfoGain);
lfoGain.connect(osc.frequency);
osc.connect(synthGain);
synthGain.gain.value = 0.2;
synthGain.connect(dest);
osc.start();
lfo.start();
// Combine streams
const combinedStream = new MediaStream([
...stream.getVideoTracks(),
...dest.getAudioTracks()
]);
// Render dynamic demo video frames
let frameCount = 0;
function renderDemoFrame() {
frameCount++;
ctx.fillStyle = '#0f172a';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Animated neon synth circles
for (let i = 0; i < 5; i++) {
const radius = ((frameCount * 2 + i * 40) % 200);
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, radius, 0, Math.PI * 2);
ctx.strokeStyle = `hsl(${(frameCount + i * 50) % 360}, 80%, 60%)`;
ctx.lineWidth = 4;
ctx.stroke();
}
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 24px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('MuffleFX Studio Demo Video', canvas.width / 2, 60);
ctx.font = '16px sans-serif';
ctx.fillStyle = '#38bdf8';
ctx.fillText('Add dynamic muffled zones below & hit play!', canvas.width / 2, canvas.height - 40);
if (!video.srcObject && !video.src) {
requestAnimationFrame(renderDemoFrame);
}
}
renderDemoFrame();
// Record 10 seconds of synthetic video to create a Blob video
const rec = new MediaRecorder(combinedStream);
let chunks = [];
rec.ondataavailable = e => chunks.push(e.data);
rec.onstop = () => {
synthCtx.close();
const blob = new Blob(chunks, { type: 'video/webm' });
video.srcObject = null;
video.src = URL.createObjectURL(blob);
addDefaultSection();
};
rec.start();
setTimeout(() => rec.stop(), 10000);
}
function addDefaultSection() {
sections = [];
sections.push({
id: nextId++,
start: 2,
end: 6,
preset: 'underwater',
freq: presets.underwater.freq,
q: presets.underwater.q
});
renderSectionsUI();
renderTimelineRanges();
}
// File Upload Handler
$('#video-file-input').on('change', function(e) {
const file = e.target.files[0];
if (file) {
const url = URL.createObjectURL(file);
video.srcObject = null;
video.src = url;
sections = [];
nextId = 1;
renderSectionsUI();
renderTimelineRanges();
}
});
$('#btn-load-demo').on('click', function() {
loadDemo();
});
// Handle Video Metadata & Time updates
video.addEventListener('loadedmetadata', function() {
duration = video.duration || 10;
updateTimeDisplay();
renderTimelineRanges();
});
video.addEventListener('play', function() {
initAudioEngine();
if (audioCtx && audioCtx.state === 'suspended') {
audioCtx.resume();
}
});
video.addEventListener('timeupdate', function() {
updateTimeDisplay();
checkAndApplyMuffleEffect();
updatePlayhead();
});
function formatTime(seconds) {
if (isNaN(seconds)) return "00:00.0";
const m = Math.floor(seconds / 60);
const s = Math.floor(seconds % 60);
const ms = Math.floor((seconds % 1) * 10);
return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}.${ms}`;
}
function updateTimeDisplay() {
timeDisplay.innerText = `${formatTime(video.currentTime)} / ${formatTime(duration)}`;
}
function updatePlayhead() {
if (duration > 0) {
const pct = (video.currentTime / duration) * 100;
playhead.style.left = `${pct}%`;
}
}
// Check if currentTime is within any muffled section
function checkAndApplyMuffleEffect() {
const cur = video.currentTime;
let activeSec = sections.find(s => cur >= s.start && cur <= s.end);
if (activeSec && filterNode) {
// Apply Low-pass filter
filterNode.frequency.setTargetAtTime(activeSec.freq, audioCtx.currentTime, 0.04);
filterNode.Q.setTargetAtTime(activeSec.q, audioCtx.currentTime, 0.04);
muffleIndicator.style.display = 'flex';
muffleIndicator.innerHTML = `<i class="fa-solid fa-water"></i> Muffled: ${presets[activeSec.preset] ? presets[activeSec.preset].name : 'Active'}`;
} else if (filterNode) {
// Bypass filter (full frequency spectrum)
filterNode.frequency.setTargetAtTime(20000, audioCtx.currentTime, 0.04);
filterNode.Q.setTargetAtTime(1, audioCtx.currentTime, 0.04);
muffleIndicator.style.display = 'none';
}
}
// Timeline Clicking / Seeking
$('.timeline-wrapper').on('click', function(e) {
const rect = this.getBoundingClientRect();
const clickX = e.clientX - rect.left;
const pct = clickX / rect.width;
if (duration > 0) {
video.currentTime = pct * duration;
}
});
// Add New Muffled Section
$('#btn-add-section').on('click', function() {
const start = Math.max(0, parseFloat((video.currentTime).toFixed(1)));
const end = Math.min(duration || 10, parseFloat((start + 3).toFixed(1)));
sections.push({
id: nextId++,
start: start,
end: end,
preset: 'behind_wall',
freq: presets.behind_wall.freq,
q: presets.behind_wall.q
});
renderSectionsUI();
renderTimelineRanges();
});
// Render Section List UI
function renderSectionsUI() {
const list = $('#sections-list');
list.empty();
if (sections.length === 0) {
list.html('<div class="text-center text-muted py-3 small"><i class="fa-solid fa-sliders me-1"></i> No muffled sections added yet. Click "+ Add Muffle Section" to start!</div>');
return;
}
sections.forEach((sec) => {
const item = $(`
<div class="section-item" data-id="${sec.id}">
<div class="row g-2 align-items-center">
<div class="col-12 col-sm-4 d-flex align-items-center justify-content-between">
<span class="fw-bold text-danger"><i class="fa-solid fa-volume-xmark me-1"></i> Section #${sec.id}</span>
<button class="btn btn-sm btn-outline-danger btn-delete-sec touch-btn"><i class="fa-solid fa-trash"></i></button>
</div>
<div class="col-6 col-sm-4">
<label class="form-label mb-0 small text-muted">Start (s)</label>
<input type="number" step="0.1" min="0" max="${duration}" class="form-control form-control-sm inp-start" value="${sec.start}">
</div>
<div class="col-6 col-sm-4">
<label class="form-label mb-0 small text-muted">End (s)</label>
<input type="number" step="0.1" min="0" max="${duration}" class="form-control form-control-sm inp-end" value="${sec.end}">
</div>
<div class="col-12 col-sm-6">
<label class="form-label mb-0 small text-muted">Preset Effect</label>
<select class="form-select form-select-sm sel-preset">
<option value="underwater" ${sec.preset==='underwater'?'selected':''}>Underwater Submerged</option>
<option value="behind_wall" ${sec.preset==='behind_wall'?'selected':''}>Behind the Wall</option>
<option value="pillow" ${sec.preset==='pillow'?'selected':''}>Pillow Soft</option>
<option value="heavy_bass" ${sec.preset==='heavy_bass'?'selected':''}>Car Trunk Bass</option>
<option value="custom" ${sec.preset==='custom'?'selected':''}>Custom Cutoff Frequency</option>
</select>
</div>
<div class="col-12 col-sm-6 custom-freq-wrapper ${sec.preset === 'custom' ? '' : 'd-none'}">
<label class="form-label mb-0 small text-muted">Cutoff Freq: <span class="freq-val">${sec.freq}</span> Hz</label>
<input type="range" min="100" max="2500" step="50" class="form-range inp-freq" value="${sec.freq}">
</div>
</div>
</div>
`);
// Event bindings for inputs
item.find('.inp-start').on('change', function() {
sec.start = Math.max(0, parseFloat($(this).val()) || 0);
renderTimelineRanges();
});
item.find('.inp-end').on('change', function() {
sec.end = Math.min(duration || 100, parseFloat($(this).val()) || 0);
renderTimelineRanges();
});
item.find('.sel-preset').on('change', function() {
const val = $(this).val();
sec.preset = val;
if (val !== 'custom') {
sec.freq = presets[val].freq;
sec.q = presets[val].q;
item.find('.custom-freq-wrapper').addClass('d-none');
} else {
item.find('.custom-freq-wrapper').removeClass('d-none');
}
});
item.find('.inp-freq').on('input', function() {
sec.freq = parseInt($(this).val());
item.find('.freq-val').text(sec.freq);
});
item.find('.btn-delete-sec').on('click', function() {
sections = sections.filter(s => s.id !== sec.id);
renderSectionsUI();
renderTimelineRanges();
});
list.append(item);
});
}
// Render red muffled zone overlays on timeline track
function renderTimelineRanges() {
$(timelineTrack).empty();
if (!duration || duration <= 0) return;
sections.forEach(sec => {
const leftPct = (sec.start / duration) * 100;
const widthPct = ((sec.end - sec.start) / duration) * 100;
if (widthPct > 0) {
const bar = document.createElement('div');
bar.className = 'muffle-range-bar';
bar.style.left = `${leftPct}%`;
bar.style.width = `${widthPct}%`;
timelineTrack.appendChild(bar);
}
});
}
// Audio Frequency Visualizer Canvas
function drawVisualizer() {
requestAnimationFrame(drawVisualizer);
if (!analyserNode) return;
const bufferLength = analyserNode.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
analyserNode.getByteFrequencyData(dataArray);
vizCtx.clearRect(0, 0, vizCanvas.width, vizCanvas.height);
const barWidth = (vizCanvas.width / bufferLength) * 2.5;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const barHeight = (dataArray[i] / 255) * vizCanvas.height;
// Visual indication of high-frequency attenuation
const hue = 200 + (i * 2);
vizCtx.fillStyle = `hsl(${hue}, 80%, ${Math.min(70, barHeight + 20)}%)`;
vizCtx.fillRect(x, vizCanvas.height - barHeight, barWidth, barHeight);
x += barWidth + 1;
}
}
// Record & Export Video with Muffled Audio
$('#btn-export').on('click', function() {
if (!video.src && !video.srcObject) {
alert("Please load or select a video first!");
return;
}
initAudioEngine();
video.currentTime = 0;
const dest = audioCtx.createMediaStreamDestination();
gainNode.connect(dest);
// Capture video track from player or canvas
let videoTrack;
if (video.captureStream) {
videoTrack = video.captureStream().getVideoTracks()[0];
} else if (video.mozCaptureStream) {
videoTrack = video.mozCaptureStream().getVideoTracks()[0];
}
const combinedStream = new MediaStream([
...(videoTrack ? [videoTrack] : []),
...dest.stream.getAudioTracks()
]);
recordedChunks = [];
mediaRecorder = new MediaRecorder(combinedStream, { mimeType: 'video/webm' });
mediaRecorder.ondataavailable = e => {
if (e.data.size > 0) recordedChunks.push(e.data);
};
mediaRecorder.onstop = () => {
const blob = new Blob(recordedChunks, { type: 'video/webm' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'muffled_video_export.webm';
a.click();
$('#export-status').html('<span class="text-success"><i class="fa-solid fa-check-circle me-1"></i> Export Complete! Download started.</span>');
$('#btn-export').prop('disabled', false).html('<i class="fa-solid fa-file-export me-1"></i> Export Processed Video');
};
mediaRecorder.start();
video.play();
$('#btn-export').prop('disabled', true).html('<i class="fa-solid fa-spinner fa-spin me-1"></i> Recording Realtime Export...');
$('#export-status').html('<span class="text-warning"><i class="fa-solid fa-circle-notch fa-spin me-1"></i> Processing video playback export. Please wait...</span>');
video.onended = function() {
if (mediaRecorder && mediaRecorder.state === 'recording') {
mediaRecorder.stop();
}
};
});
// Initial sample video generation
loadDemo();
});
} catch (error) {
console.error("App Error: ", error);
}
</script>
</head>
<body>
<div id="main-container">
<!-- Header -->
<header class="d-flex align-items-center justify-content-between mb-3 border-bottom border-secondary pb-2">
<div class="d-flex align-items-center gap-2">
<i class="fa-solid fa-sliders text-danger fs-3"></i>
<div>
<h1 class="h4 mb-0 fw-bold">MuffleFX Studio</h1>
<p class="text-muted small mb-0">Video Audio Low-Pass Filter Editor</p>
</div>
</div>
<button id="btn-load-demo" class="btn btn-sm btn-outline-info touch-btn">
<i class="fa-solid fa-wand-magic-sparkles me-1"></i> Load Demo
</button>
</header>
<!-- Video Preview Player Section -->
<div class="app-card">
<div class="video-container">
<video id="video-player" playsinline crossorigin="anonymous"></video>
<div id="muffle-indicator" class="muffle-badge">
<i class="fa-solid fa-water"></i> Muffled Section Active
</div>
</div>
<!-- Custom Interactive Timeline -->
<div class="mt-3">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="small text-muted"><i class="fa-solid fa-clock me-1"></i> Timeline Track</span>
<span id="time-display" class="badge bg-dark font-monospace text-cyan">00:00.0 / 00:00.0</span>
</div>
<div class="timeline-wrapper">
<div id="timeline-track" class="timeline-track"></div>
<div id="playhead" class="playhead"></div>
</div>
</div>
<!-- Audio Visualizer Spectrum -->
<div class="mt-2">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="small text-muted"><i class="fa-solid fa-wave-square me-1"></i> Real-time Spectrum Analyser</span>
</div>
<canvas id="viz-canvas" height="60"></canvas>
</div>
</div>
<!-- Toolbar & File Input -->
<div class="app-card">
<div class="row g-2 align-items-center">
<div class="col-12 col-md-6">
<label class="form-label small text-muted mb-1"><i class="fa-solid fa-file-video me-1"></i> Select Video / Audio File</label>
<input class="form-control form-control-sm" type="file" id="video-file-input" accept="video/*,audio/*">
</div>
<div class="col-12 col-md-6 d-flex gap-2 align-items-end mt-2 mt-md-0">
<button id="btn-add-section" class="btn btn-muffle w-100 touch-btn">
<i class="fa-solid fa-plus-circle me-1"></i> Add Muffle Section
</button>
</div>
</div>
</div>
<!-- Sections Manager Panel -->
<div class="app-card">
<div class="d-flex align-items-center justify-content-between mb-2">
<h2 class="h6 mb-0 fw-bold text-light"><i class="fa-solid fa-list-check text-purple me-2"></i>Muffled Audio Sections</h2>
</div>
<div id="sections-list">
<!-- Dynamic Section Items inserted here -->
</div>
</div>
<!-- Export Panel -->
<div class="app-card text-center">
<div class="d-flex flex-column flex-sm-row justify-content-between align-items-center gap-2">
<div class="text-start">
<h3 class="h6 mb-0 fw-bold"><i class="fa-solid fa-download me-1 text-cyan"></i> Render & Export Video</h3>
<span id="export-status" class="small text-muted">Renders full video with live muffled audio filtering applied.</span>
</div>
<button id="btn-export" class="btn btn-accent touch-btn px-4">
<i class="fa-solid fa-file-export me-1"></i> Export Processed Video
</button>
</div>
</div>
<!-- Footer -->
<footer class="text-center text-muted small mt-3">
<p class="mb-0">MuffleFX • Single-File Web Audio & Video FX Engine</p>
</footer>
</div>
</body>
</html>
NEW APPS
These are apps made by the community!