audio-edititng / index.html
MrEzzat's picture
Add 2 files
c921a6e verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Waveform - Professional Audio Editor</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">
<style>
/* Custom CSS for audio waveform */
.waveform-container {
height: 120px;
background: linear-gradient(90deg, rgba(30,58,138,0.2) 0%, rgba(79,70,229,0.2) 100%);
border-radius: 8px;
position: relative;
overflow: hidden;
}
.waveform {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.waveform-bar {
width: 3px;
background-color: #4f46e5;
border-radius: 3px;
transition: height 0.3s ease;
}
.playhead {
position: absolute;
top: 0;
left: 0;
width: 2px;
height: 100%;
background-color: #ffffff;
z-index: 10;
box-shadow: 0 0 5px rgba(255,255,255,0.8);
}
.effect-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
/* Selection area for trimming */
.selection-area {
position: absolute;
top: 0;
height: 100%;
background-color: rgba(79, 70, 229, 0.3);
border-left: 2px solid #4f46e5;
border-right: 2px solid #4f46e5;
z-index: 5;
cursor: move;
}
.selection-handle {
position: absolute;
top: 0;
width: 10px;
height: 100%;
background-color: rgba(255, 255, 255, 0.8);
cursor: ew-resize;
z-index: 6;
}
.selection-handle-left {
left: -5px;
}
.selection-handle-right {
right: -5px;
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Animation for recording */
@keyframes pulse {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(1.1);
opacity: 0.7;
}
100% {
transform: scale(1);
opacity: 1;
}
}
.recording-animation {
animation: pulse 1.5s infinite;
}
/* Tooltip styles */
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
/* Effect panel styles */
.effect-panel {
transition: all 0.3s ease;
max-height: 0;
overflow: hidden;
}
.effect-panel.open {
max-height: 500px;
padding: 1rem;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
margin-top: 0.5rem;
}
/* Track item styles */
.track-item {
transition: all 0.2s ease;
}
.track-item.active {
border-color: #4f46e5;
background-color: #f5f3ff;
}
/* Timeline ruler */
.timeline-ruler {
height: 30px;
background-color: #f3f4f6;
border-bottom: 1px solid #e5e7eb;
position: relative;
overflow: hidden;
}
.timeline-marker {
position: absolute;
bottom: 0;
height: 100%;
border-right: 1px solid #d1d5db;
}
.timeline-marker-label {
position: absolute;
bottom: 5px;
font-size: 0.75rem;
color: #6b7280;
}
</style>
</head>
<body class="bg-gray-50 text-gray-800 font-sans">
<!-- Navigation -->
<nav class="bg-indigo-900 text-white shadow-lg">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16 items-center">
<div class="flex items-center">
<i class="fas fa-wave-square text-2xl mr-2 text-indigo-300"></i>
<span class="text-xl font-bold">Waveform Pro</span>
</div>
<div class="hidden md:flex items-center space-x-8">
<a href="#" class="hover:text-indigo-200 transition">File</a>
<a href="#" class="hover:text-indigo-200 transition">Edit</a>
<a href="#" class="hover:text-indigo-200 transition">View</a>
<a href="#" class="hover:text-indigo-200 transition">Help</a>
</div>
<div class="flex items-center space-x-4">
<button class="px-4 py-2 rounded-md bg-indigo-700 hover:bg-indigo-600 transition">Save</button>
<button class="px-4 py-2 rounded-md bg-white text-indigo-900 hover:bg-gray-100 transition">Export</button>
</div>
</div>
</div>
</nav>
<!-- Main Editor Section -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div class="flex flex-col lg:flex-row gap-4">
<!-- Left Sidebar - Tracks -->
<div class="lg:w-1/4 bg-white rounded-xl shadow-md p-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-bold">Tracks</h2>
<button id="addTrackBtn" class="text-indigo-600 hover:text-indigo-800">
<i class="fas fa-plus"></i> Add Track
</button>
</div>
<div id="tracksContainer" class="space-y-3">
<!-- Tracks will be added here dynamically -->
</div>
<div class="mt-4 pt-4 border-t border-gray-200">
<h3 class="font-medium mb-2">Master Track</h3>
<div class="flex items-center space-x-3">
<div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center text-indigo-600">
<i class="fas fa-sliders-h"></i>
</div>
<div class="flex-1">
<input type="range" min="0" max="100" value="80" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
</div>
<span class="text-sm text-gray-500">-2.4dB</span>
</div>
</div>
</div>
<!-- Main Editor Area -->
<div class="lg:w-2/4 bg-white rounded-xl shadow-md p-4">
<!-- Timeline Ruler -->
<div class="timeline-ruler mb-2" id="timelineRuler">
<!-- Timeline markers will be added here -->
</div>
<!-- Waveform Display -->
<div class="waveform-container mb-4 relative" id="mainWaveformContainer">
<div class="waveform" id="mainWaveform"></div>
<div class="playhead" id="mainPlayhead"></div>
<div class="selection-area hidden" id="selectionArea">
<div class="selection-handle selection-handle-left"></div>
<div class="selection-handle selection-handle-right"></div>
</div>
</div>
<!-- Audio Clips -->
<div id="audioClipsContainer" class="bg-gray-50 rounded-lg p-3 mb-4 min-h-16">
<!-- Audio clips will be added here -->
</div>
<!-- Transport Controls -->
<div class="flex justify-between items-center">
<div class="flex space-x-3">
<button id="playBtn" class="w-12 h-12 rounded-full bg-indigo-600 text-white hover:bg-indigo-700 flex items-center justify-center tooltip">
<i class="fas fa-play"></i>
<span class="tooltiptext">Play (Space)</span>
</button>
<button id="pauseBtn" class="w-12 h-12 rounded-full bg-gray-200 hover:bg-gray-300 flex items-center justify-center tooltip">
<i class="fas fa-pause"></i>
<span class="tooltiptext">Pause (Space)</span>
</button>
<button id="stopBtn" class="w-12 h-12 rounded-full bg-gray-200 hover:bg-gray-300 flex items-center justify-center tooltip">
<i class="fas fa-stop"></i>
<span class="tooltiptext">Stop</span>
</button>
<button id="loopBtn" class="w-12 h-12 rounded-full bg-gray-200 hover:bg-gray-300 flex items-center justify-center tooltip">
<i class="fas fa-redo"></i>
<span class="tooltiptext">Loop</span>
</button>
</div>
<div class="flex items-center space-x-4">
<div class="flex items-center space-x-2">
<span class="text-sm text-gray-600">Speed:</span>
<input type="range" id="speedControl" min="50" max="200" value="100" class="w-24">
<span id="speedValue" class="text-sm font-medium w-10">1.0x</span>
</div>
<div id="timeDisplay" class="text-gray-600">00:00:00 / 00:03:45</div>
</div>
</div>
<!-- Zoom Controls -->
<div class="mt-4 flex justify-between items-center">
<div class="flex space-x-2">
<button id="zoomInBtn" class="px-3 py-1 rounded-md bg-gray-100 hover:bg-gray-200 text-sm">
<i class="fas fa-search-plus"></i> Zoom In
</button>
<button id="zoomOutBtn" class="px-3 py-1 rounded-md bg-gray-100 hover:bg-gray-200 text-sm">
<i class="fas fa-search-minus"></i> Zoom Out
</button>
<button id="zoomFitBtn" class="px-3 py-1 rounded-md bg-gray-100 hover:bg-gray-200 text-sm">
<i class="fas fa-expand"></i> Fit to View
</button>
</div>
<div class="text-sm text-gray-500">Zoom: <span id="zoomLevel">100%</span></div>
</div>
</div>
<!-- Right Sidebar - Tools & Effects -->
<div class="lg:w-1/4 bg-white rounded-xl shadow-md p-4">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-bold">Tools</h2>
</div>
<div class="grid grid-cols-4 gap-2 mb-6">
<button id="selectTool" class="p-2 rounded-md bg-indigo-100 text-indigo-700 hover:bg-indigo-200 flex flex-col items-center tooltip">
<i class="fas fa-mouse-pointer mb-1"></i>
<span class="text-xs">Select</span>
<span class="tooltiptext">Selection Tool (V)</span>
</button>
<button id="cutTool" class="p-2 rounded-md bg-gray-100 hover:bg-gray-200 flex flex-col items-center tooltip">
<i class="fas fa-cut mb-1"></i>
<span class="text-xs">Cut</span>
<span class="tooltiptext">Cut Tool (C)</span>
</button>
<button id="trimTool" class="p-2 rounded-md bg-gray-100 hover:bg-gray-200 flex flex-col items-center tooltip">
<i class="fas fa-arrows-alt-h mb-1"></i>
<span class="text-xs">Trim</span>
<span class="tooltiptext">Trim Tool (T)</span>
</button>
<button id="fadeTool" class="p-2 rounded-md bg-gray-100 hover:bg-gray-200 flex flex-col items-center tooltip">
<i class="fas fa-wave-square mb-1"></i>
<span class="text-xs">Fade</span>
<span class="tooltiptext">Fade Tool (F)</span>
</button>
</div>
<div class="mb-6">
<div class="flex justify-between items-center mb-3">
<h3 class="font-medium">Selected Region</h3>
<button id="clearSelectionBtn" class="text-xs text-indigo-600 hover:text-indigo-800">Clear</button>
</div>
<div id="selectionInfo" class="text-sm text-gray-500 p-3 bg-gray-50 rounded-md">
No selection made
</div>
<div class="mt-3 grid grid-cols-2 gap-2">
<button id="trimSelectionBtn" class="px-3 py-1 text-sm rounded-md bg-gray-100 hover:bg-gray-200">Trim</button>
<button id="deleteSelectionBtn" class="px-3 py-1 text-sm rounded-md bg-gray-100 hover:bg-gray-200">Delete</button>
<button id="fadeInBtn" class="px-3 py-1 text-sm rounded-md bg-gray-100 hover:bg-gray-200">Fade In</button>
<button id="fadeOutBtn" class="px-3 py-1 text-sm rounded-md bg-gray-100 hover:bg-gray-200">Fade Out</button>
</div>
</div>
<div>
<div class="flex justify-between items-center mb-3">
<h3 class="font-medium">Effects</h3>
<button id="addEffectBtn" class="text-xs text-indigo-600 hover:text-indigo-800">Add Effect</button>
</div>
<div id="effectsList" class="space-y-3">
<!-- Effects will be added here -->
</div>
</div>
</div>
</div>
</div>
<!-- Effect Panel Template (hidden) -->
<div id="effectPanelTemplate" class="effect-panel hidden">
<div class="flex justify-between items-center mb-2">
<h4 class="font-medium">Effect Settings</h4>
<button class="text-gray-500 hover:text-gray-700 close-effect-panel">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-3">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Parameter 1</label>
<input type="range" min="0" max="100" value="50" class="w-full">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Parameter 2</label>
<input type="range" min="0" max="100" value="50" class="w-full">
</div>
<button class="w-full py-1 text-sm rounded-md bg-red-100 text-red-700 hover:bg-red-200 remove-effect">
Remove Effect
</button>
</div>
</div>
<!-- Track Item Template (hidden) -->
<div id="trackItemTemplate" class="track-item border border-gray-200 rounded-lg p-3 hover:border-indigo-300 transition hidden">
<div class="flex justify-between items-center mb-2">
<span class="font-medium track-name">Track 1</span>
<div class="flex space-x-2">
<button class="text-gray-500 hover:text-indigo-600 toggle-mute">
<i class="fas fa-volume-up"></i>
</button>
<button class="text-gray-500 hover:text-indigo-600 toggle-solo">
<i class="fas fa-headphones"></i>
</button>
<button class="text-gray-500 hover:text-indigo-600 toggle-visibility">
<i class="fas fa-eye"></i>
</button>
<button class="text-gray-500 hover:text-indigo-600 delete-track">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="h-2 bg-gray-100 rounded-full overflow-hidden">
<div class="h-full bg-indigo-500 volume-level w-3/4"></div>
</div>
<input type="range" min="-30" max="6" value="0" step="0.1" class="w-full mt-2 volume-slider">
<div class="text-xs text-gray-500 text-right mt-1 volume-db">0.0 dB</div>
</div>
<!-- Audio Clip Template (hidden) -->
<div id="audioClipTemplate" class="flex-shrink-0 h-16 rounded-md border border-gray-300 bg-white hover:border-indigo-400 transition relative hidden">
<div class="absolute inset-0 flex items-center justify-start pl-2 overflow-hidden">
<span class="text-sm truncate audio-clip-name">Audio Clip</span>
</div>
<div class="absolute top-0 right-0 p-1">
<button class="text-gray-500 hover:text-indigo-600 text-xs delete-clip">
<i class="fas fa-times"></i>
</button>
</div>
<div class="absolute bottom-0 left-0 right-0 h-1 bg-gray-100">
<div class="h-full bg-indigo-400 audio-clip-waveform"></div>
</div>
</div>
<script>
// Global variables
let currentTool = 'select';
let isPlaying = false;
let currentTime = 0;
let totalDuration = 225; // 3:45 in seconds
let zoomLevel = 100;
let selectionStart = 0;
let selectionEnd = 0;
let hasSelection = false;
let trackCount = 0;
let clipCount = 0;
// DOM elements
const mainWaveformContainer = document.getElementById('mainWaveformContainer');
const selectionArea = document.getElementById('selectionArea');
const selectionInfo = document.getElementById('selectionInfo');
const timeDisplay = document.getElementById('timeDisplay');
const zoomLevelDisplay = document.getElementById('zoomLevel');
const speedControl = document.getElementById('speedControl');
const speedValue = document.getElementById('speedValue');
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
// Generate demo waveform
generateWaveform('mainWaveform', 500);
// Create timeline markers
createTimelineMarkers();
// Add event listeners
setupEventListeners();
// Add a couple of demo tracks
addTrack('Vocals');
addTrack('Guitar');
addTrack('Drums');
// Add some demo audio clips
addAudioClip('Vocals', 'Verse 1', 0, 30);
addAudioClip('Vocals', 'Chorus', 30, 45);
addAudioClip('Guitar', 'Rhythm', 0, 45);
addAudioClip('Drums', 'Drum Loop', 0, 45);
// Start the playhead animation
animatePlayhead();
// Update time display
updateTimeDisplay();
});
// Generate waveform visualization
function generateWaveform(elementId, barCount) {
const container = document.getElementById(elementId);
container.innerHTML = '';
for (let i = 0; i < barCount; i++) {
const bar = document.createElement('div');
bar.className = 'waveform-bar';
// Create a more interesting waveform pattern
const pos = i / barCount;
let height;
if (pos < 0.2) {
// Intro section - quieter
height = 20 + Math.sin(pos * 20) * 15 + Math.random() * 10;
} else if (pos > 0.8) {
// Outro section - quieter
height = 20 + Math.sin(pos * 25) * 10 + Math.random() * 8;
} else {
// Main section - louder with more variation
height = 40 + Math.sin(pos * 50) * 30 + Math.random() * 15;
// Add some "peaks"
if (Math.random() > 0.95) {
height += 30;
}
}
// Ensure height is within bounds
height = Math.max(5, Math.min(100, height));
bar.style.height = `${height}%`;
container.appendChild(bar);
}
}
// Create timeline markers
function createTimelineMarkers() {
const ruler = document.getElementById('timelineRuler');
ruler.innerHTML = '';
const totalWidth = ruler.offsetWidth;
const totalSeconds = totalDuration;
const pixelsPerSecond = totalWidth / (totalSeconds * (zoomLevel / 100));
// Add markers every 5 seconds
for (let seconds = 0; seconds <= totalSeconds; seconds += 5) {
const marker = document.createElement('div');
marker.className = 'timeline-marker';
marker.style.left = `${seconds * pixelsPerSecond}px`;
// Add label for every 15 seconds
if (seconds % 15 === 0) {
const label = document.createElement('div');
label.className = 'timeline-marker-label';
label.style.left = `${seconds * pixelsPerSecond + 2}px`;
label.textContent = formatTime(seconds);
ruler.appendChild(label);
}
ruler.appendChild(marker);
}
}
// Format time as MM:SS
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
// Format time as HH:MM:SS
function formatTimeLong(seconds) {
const hours = Math.floor(seconds / 3600);
const mins = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
return `${hours.toString().padStart(2, '0')}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
// Update the time display
function updateTimeDisplay() {
timeDisplay.textContent = `${formatTimeLong(currentTime)} / ${formatTimeLong(totalDuration)}`;
}
// Animate the playhead
function animatePlayhead() {
const playhead = document.getElementById('mainPlayhead');
const container = document.getElementById('mainWaveformContainer');
function updatePlayheadPosition() {
if (isPlaying) {
currentTime += 0.1 * (parseInt(speedControl.value) / 100);
if (currentTime > totalDuration) {
currentTime = 0;
if (!document.getElementById('loopBtn').classList.contains('bg-indigo-100')) {
isPlaying = false;
document.getElementById('playBtn').classList.remove('bg-indigo-600');
document.getElementById('playBtn').classList.add('bg-gray-200');
}
}
updateTimeDisplay();
}
const percentage = (currentTime / totalDuration) * 100;
playhead.style.left = `${percentage}%`;
requestAnimationFrame(updatePlayheadPosition);
}
updatePlayheadPosition();
}
// Setup event listeners
function setupEventListeners() {
// Tool buttons
document.getElementById('selectTool').addEventListener('click', () => setActiveTool('select'));
document.getElementById('cutTool').addEventListener('click', () => setActiveTool('cut'));
document.getElementById('trimTool').addEventListener('click', () => setActiveTool('trim'));
document.getElementById('fadeTool').addEventListener('click', () => setActiveTool('fade'));
// Transport controls
document.getElementById('playBtn').addEventListener('click', togglePlay);
document.getElementById('pauseBtn').addEventListener('click', togglePlay);
document.getElementById('stopBtn').addEventListener('click', stopPlayback);
document.getElementById('loopBtn').addEventListener('click', toggleLoop);
// Zoom controls
document.getElementById('zoomInBtn').addEventListener('click', () => adjustZoom(10));
document.getElementById('zoomOutBtn').addEventListener('click', () => adjustZoom(-10));
document.getElementById('zoomFitBtn').addEventListener('click', fitToView);
// Speed control
speedControl.addEventListener('input', updateSpeed);
// Selection controls
document.getElementById('clearSelectionBtn').addEventListener('click', clearSelection);
document.getElementById('trimSelectionBtn').addEventListener('click', trimSelection);
document.getElementById('deleteSelectionBtn').addEventListener('click', deleteSelection);
document.getElementById('fadeInBtn').addEventListener('click', () => applyFade('in'));
document.getElementById('fadeOutBtn').addEventListener('click', () => applyFade('out'));
// Track controls
document.getElementById('addTrackBtn').addEventListener('click', () => addTrack(`Track ${trackCount + 1}`));
// Effect controls
document.getElementById('addEffectBtn').addEventListener('click', addEffect);
// Waveform interaction
mainWaveformContainer.addEventListener('mousedown', handleWaveformMouseDown);
mainWaveformContainer.addEventListener('mousemove', handleWaveformMouseMove);
mainWaveformContainer.addEventListener('mouseup', handleWaveformMouseUp);
mainWaveformContainer.addEventListener('mouseleave', handleWaveformMouseUp);
// Keyboard shortcuts
document.addEventListener('keydown', handleKeyboardShortcuts);
}
// Set active tool
function setActiveTool(tool) {
currentTool = tool;
// Update UI
document.getElementById('selectTool').classList.remove('bg-indigo-100', 'text-indigo-700');
document.getElementById('cutTool').classList.remove('bg-indigo-100', 'text-indigo-700');
document.getElementById('trimTool').classList.remove('bg-indigo-100', 'text-indigo-700');
document.getElementById('fadeTool').classList.remove('bg-indigo-100', 'text-indigo-700');
document.getElementById(`${tool}Tool`).classList.add('bg-indigo-100', 'text-indigo-700');
// Change cursor based on tool
switch(tool) {
case 'select':
mainWaveformContainer.style.cursor = 'default';
break;
case 'cut':
mainWaveformContainer.style.cursor = 'crosshair';
break;
case 'trim':
mainWaveformContainer.style.cursor = 'col-resize';
break;
case 'fade':
mainWaveformContainer.style.cursor = 'pointer';
break;
}
}
// Toggle play/pause
function togglePlay() {
isPlaying = !isPlaying;
if (isPlaying) {
document.getElementById('playBtn').classList.remove('bg-gray-200');
document.getElementById('playBtn').classList.add('bg-indigo-600');
document.getElementById('pauseBtn').classList.remove('bg-indigo-600');
document.getElementById('pauseBtn').classList.add('bg-gray-200');
} else {
document.getElementById('playBtn').classList.remove('bg-indigo-600');
document.getElementById('playBtn').classList.add('bg-gray-200');
document.getElementById('pauseBtn').classList.remove('bg-gray-200');
document.getElementById('pauseBtn').classList.add('bg-indigo-600');
}
}
// Stop playback
function stopPlayback() {
isPlaying = false;
currentTime = 0;
updateTimeDisplay();
document.getElementById('playBtn').classList.remove('bg-indigo-600');
document.getElementById('playBtn').classList.add('bg-gray-200');
document.getElementById('pauseBtn').classList.remove('bg-indigo-600');
document.getElementById('pauseBtn').classList.add('bg-gray-200');
}
// Toggle loop
function toggleLoop() {
const loopBtn = document.getElementById('loopBtn');
loopBtn.classList.toggle('bg-indigo-100');
loopBtn.classList.toggle('text-indigo-700');
}
// Adjust zoom level
function adjustZoom(amount) {
zoomLevel = Math.max(50, Math.min(200, zoomLevel + amount));
zoomLevelDisplay.textContent = `${zoomLevel}%`;
// In a real app, this would adjust the waveform display
createTimelineMarkers();
}
// Fit to view
function fitToView() {
zoomLevel = 100;
zoomLevelDisplay.textContent = `${zoomLevel}%`;
// In a real app, this would adjust the waveform display
createTimelineMarkers();
}
// Update playback speed
function updateSpeed() {
const speed = parseInt(speedControl.value);
speedValue.textContent = `${(speed / 100).toFixed(1)}x`;
}
// Handle waveform mouse down
function handleWaveformMouseDown(e) {
if (currentTool === 'select' || currentTool === 'cut') {
const rect = mainWaveformContainer.getBoundingClientRect();
const x = e.clientX - rect.left;
const percentage = (x / rect.width) * 100;
selectionStart = percentage;
selectionEnd = percentage;
selectionArea.style.left = `${percentage}%`;
selectionArea.style.width = '0';
selectionArea.classList.remove('hidden');
hasSelection = true;
updateSelectionInfo();
}
}
// Handle waveform mouse move
function handleWaveformMouseMove(e) {
if (hasSelection && (currentTool === 'select' || currentTool === 'cut')) {
const rect = mainWaveformContainer.getBoundingClientRect();
const x = e.clientX - rect.left;
const percentage = (x / rect.width) * 100;
selectionEnd = percentage;
if (percentage > selectionStart) {
selectionArea.style.left = `${selectionStart}%`;
selectionArea.style.width = `${percentage - selectionStart}%`;
} else {
selectionArea.style.left = `${percentage}%`;
selectionArea.style.width = `${selectionStart - percentage}%`;
}
updateSelectionInfo();
}
}
// Handle waveform mouse up
function handleWaveformMouseUp() {
if (currentTool === 'cut' && hasSelection) {
// In a real app, this would cut the audio at the selection points
console.log(`Cut audio from ${selectionStart}% to ${selectionEnd}%`);
}
hasSelection = false;
}
// Update selection info display
function updateSelectionInfo() {
const startTime = (selectionStart / 100) * totalDuration;
const endTime = (selectionEnd / 100) * totalDuration;
const duration = Math.abs(endTime - startTime);
selectionInfo.innerHTML = `
Start: <strong>${formatTimeLong(startTime)}</strong><br>
End: <strong>${formatTimeLong(endTime)}</strong><br>
Duration: <strong>${formatTimeLong(duration)}</strong>
`;
}
// Clear selection
function clearSelection() {
selectionArea.classList.add('hidden');
selectionInfo.textContent = 'No selection made';
selectionStart = 0;
selectionEnd = 0;
}
// Trim selection
function trimSelection() {
if (selectionStart === 0 && selectionEnd === 0) return;
// In a real app, this would trim the audio to the selected region
console.log(`Trim audio to ${selectionStart}% - ${selectionEnd}%`);
alert(`Audio trimmed to selected region (${formatTimeLong((selectionStart / 100) * totalDuration)} - ${formatTimeLong((selectionEnd / 100) * totalDuration)})`);
clearSelection();
}
// Delete selection
function deleteSelection() {
if (selectionStart === 0 && selectionEnd === 0) return;
// In a real app, this would delete the selected region
console.log(`Delete audio from ${selectionStart}% to ${selectionEnd}%`);
alert(`Deleted selected audio region (${formatTimeLong((selectionStart / 100) * totalDuration)} - ${formatTimeLong((selectionEnd / 100) * totalDuration)})`);
clearSelection();
}
// Apply fade effect
function applyFade(type) {
if (selectionStart === 0 && selectionEnd === 0) return;
// In a real app, this would apply a fade to the selected region
console.log(`Apply ${type} fade to ${selectionStart}% - ${selectionEnd}%`);
alert(`Applied ${type} fade to selected region`);
clearSelection();
}
// Add a new track
function addTrack(name) {
trackCount++;
const template = document.getElementById('trackItemTemplate');
const clone = template.cloneNode(true);
clone.id = `track-${trackCount}`;
clone.classList.remove('hidden');
const trackName = clone.querySelector('.track-name');
trackName.textContent = name;
// Set up event listeners for the new track
clone.querySelector('.toggle-mute').addEventListener('click', function() {
this.classList.toggle('text-red-500');
console.log(`Toggle mute for ${name}`);
});
clone.querySelector('.toggle-solo').addEventListener('click', function() {
this.classList.toggle('text-indigo-600');
console.log(`Toggle solo for ${name}`);
});
clone.querySelector('.toggle-visibility').addEventListener('click', function() {
this.classList.toggle('text-gray-500');
this.classList.toggle('text-indigo-600');
console.log(`Toggle visibility for ${name}`);
});
clone.querySelector('.delete-track').addEventListener('click', function() {
if (confirm(`Delete track "${name}"?`)) {
clone.remove();
console.log(`Deleted track ${name}`);
}
});
const volumeSlider = clone.querySelector('.volume-slider');
const volumeLevel = clone.querySelector('.volume-level');
const volumeDb = clone.querySelector('.volume-db');
volumeSlider.addEventListener('input', function() {
const value = parseFloat(this.value);
const percentage = ((value + 30) / 36) * 100;
volumeLevel.style.width = `${percentage}%`;
volumeDb.textContent = `${value.toFixed(1)} dB`;
// In a real app, this would adjust the track volume
console.log(`Set volume for ${name} to ${value} dB`);
});
// Add to tracks container
document.getElementById('tracksContainer').appendChild(clone);
}
// Add an audio clip to a track
function addAudioClip(trackName, clipName, startTime, duration) {
clipCount++;
const template = document.getElementById('audioClipTemplate');
const clone = template.cloneNode(true);
clone.id = `clip-${clipCount}`;
clone.classList.remove('hidden');
const nameElement = clone.querySelector('.audio-clip-name');
nameElement.textContent = clipName;
// Position the clip based on start time and duration
const containerWidth = document.getElementById('audioClipsContainer').offsetWidth;
const totalSeconds = totalDuration;
const pixelsPerSecond = containerWidth / (totalSeconds * (zoomLevel / 100));
clone.style.width = `${duration * pixelsPerSecond}px`;
clone.style.left = `${startTime * pixelsPerSecond}px`;
// Generate a mini waveform for the clip
const waveformElement = clone.querySelector('.audio-clip-waveform');
generateMiniWaveform(waveformElement, duration * 10);
// Set up event listeners for the clip
clone.addEventListener('click', function() {
// Select this clip
document.querySelectorAll('#audioClipsContainer > div').forEach(c => {
c.classList.remove('border-indigo-500', 'bg-indigo-50');
});
this.classList.add('border-indigo-500', 'bg-indigo-50');
console.log(`Selected clip: ${clipName}`);
});
clone.querySelector('.delete-clip').addEventListener('click', function(e) {
e.stopPropagation();
if (confirm(`Delete clip "${clipName}"?`)) {
clone.remove();
console.log(`Deleted clip ${clipName}`);
}
});
// Add to audio clips container
document.getElementById('audioClipsContainer').appendChild(clone);
}
// Generate a mini waveform for a clip
function generateMiniWaveform(element, barCount) {
element.innerHTML = '';
for (let i = 0; i < barCount; i++) {
const bar = document.createElement('div');
bar.className = 'inline-block h-full w-px bg-indigo-500';
const height = 20 + Math.sin(i / 3) * 15 + Math.random() * 10;
bar.style.height = `${height}%`;
element.appendChild(bar);
}
}
// Add an effect
function addEffect() {
const effectName = prompt("Enter effect name:");
if (!effectName) return;
const effectsList = document.getElementById('effectsList');
const effectItem = document.createElement('div');
effectItem.className = 'effect-card bg-gray-50 rounded-lg p-3 border border-gray-200 transition cursor-pointer';
effectItem.innerHTML = `
<div class="flex justify-between items-center">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 rounded-full bg-purple-100 flex items-center justify-center text-purple-600">
<i class="fas fa-sliders-h"></i>
</div>
<div>
<h3 class="font-medium">${effectName}</h3>
<p class="text-xs text-gray-500">Click to edit settings</p>
</div>
</div>
<button class="text-gray-500 hover:text-red-500 delete-effect">
<i class="fas fa-times"></i>
</button>
</div>
<div class="effect-panel mt-2" id="effect-panel-${effectName.toLowerCase().replace(' ', '-')}">
<!-- Effect panel content will be added here -->
</div>
`;
// Set up event listeners
effectItem.querySelector('.delete-effect').addEventListener('click', function(e) {
e.stopPropagation();
if (confirm(`Remove effect "${effectName}"?`)) {
effectItem.remove();
console.log(`Removed effect ${effectName}`);
}
});
effectItem.addEventListener('click', function() {
const panel = this.querySelector('.effect-panel');
const template = document.getElementById('effectPanelTemplate');
if (!panel.innerHTML.trim()) {
const panelClone = template.cloneNode(true);
panelClone.classList.remove('hidden');
panelClone.classList.add('open');
panelClone.id = '';
// Update panel title
panelClone.querySelector('h4').textContent = `${effectName} Settings`;
// Set up close button
panelClone.querySelector('.close-effect-panel').addEventListener('click', function(e) {
e.stopPropagation();
panelClone.classList.remove('open');
});
// Set up remove button
panelClone.querySelector('.remove-effect').addEventListener('click', function(e) {
e.stopPropagation();
if (confirm(`Remove effect "${effectName}"?`)) {
effectItem.remove();
console.log(`Removed effect ${effectName}`);
}
});
panel.appendChild(panelClone);
} else {
panel.querySelector('.effect-panel').classList.toggle('open');
}
});
effectsList.appendChild(effectItem);
}
// Handle keyboard shortcuts
function handleKeyboardShortcuts(e) {
// Space to play/pause
if (e.code === 'Space') {
e.preventDefault();
togglePlay();
}
// Tool shortcuts
if (e.key === 'v') setActiveTool('select');
if (e.key === 'c') setActiveTool('cut');
if (e.key === 't') setActiveTool('trim');
if (e.key === 'f') setActiveTool('fade');
// Zoom shortcuts
if (e.ctrlKey && e.key === '+') adjustZoom(10);
if (e.ctrlKey && e.key === '-') adjustZoom(-10);
if (e.ctrlKey && e.key === '0') fitToView();
// Playback control
if (e.key === 'l') toggleLoop();
if (e.key === 's') stopPlayback();
}
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MrEzzat/audio-edititng" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>