diff --git a/index.html b/index.html index 3fb6355..1ee4b41 100644 --- a/index.html +++ b/index.html @@ -478,6 +478,33 @@

Available Tools

Utilities + + + + +
+ + + +
+ +
+
+

Audio Waveform Visualizer

+

Visualize audio files as static waveforms and in real-time.

+
+ Utility + Audio + Waveform +
+
+
+ +
+
+ + +
@@ -2345,6 +2372,7 @@

CSS Gradient Generator

+ diff --git a/tools/audio-waveform-visualizer/README.md b/tools/audio-waveform-visualizer/README.md new file mode 100644 index 0000000..1625293 --- /dev/null +++ b/tools/audio-waveform-visualizer/README.md @@ -0,0 +1,23 @@ +# Audio Waveform Visualization Tool + +A web-based tool for analyzing & visualizing audio files by rendering their waveforms to the HTML canvas. + +## Instructions +- Upload an audio file and the static waveform will be shown. +- Press play and the dynamic waveform will animate. +- Press pause to pause the audio file. +- Press mute to mute the audio without ending playback. + +## Screenshots + +### Desktop Interface + +![Desktop View](screenshots/desktop-main.png) + +### Mobile Responsive Design + +![Mobile View](screenshots/mobile-portrait.png) + +### Tool Demonstration + +![Feature Highlight](screenshots/feature-highlight.png) \ No newline at end of file diff --git a/tools/audio-waveform-visualizer/index.html b/tools/audio-waveform-visualizer/index.html new file mode 100644 index 0000000..5c2547d --- /dev/null +++ b/tools/audio-waveform-visualizer/index.html @@ -0,0 +1,210 @@ + + + + + + Audio Waveform Visualizer - DevToolkit + + + + + + + +
+
+
+
+
+
+
+
+
+ + +
+ + Back to Tools + + +
+ +
+

Audio Waveform Visualizer

+

+ Upload an audio file and draw its waveform to an HTML Canvas using Web Audio API. +

+
+ + +
+ + + + + + + +
+ + + + \ No newline at end of file diff --git a/tools/audio-waveform-visualizer/screenshots/desktop-main.png b/tools/audio-waveform-visualizer/screenshots/desktop-main.png new file mode 100644 index 0000000..72e5175 Binary files /dev/null and b/tools/audio-waveform-visualizer/screenshots/desktop-main.png differ diff --git a/tools/audio-waveform-visualizer/screenshots/feature-highlight.png b/tools/audio-waveform-visualizer/screenshots/feature-highlight.png new file mode 100644 index 0000000..c85819e Binary files /dev/null and b/tools/audio-waveform-visualizer/screenshots/feature-highlight.png differ diff --git a/tools/audio-waveform-visualizer/screenshots/mobile-portrait.png b/tools/audio-waveform-visualizer/screenshots/mobile-portrait.png new file mode 100644 index 0000000..f7ec661 Binary files /dev/null and b/tools/audio-waveform-visualizer/screenshots/mobile-portrait.png differ diff --git a/tools/audio-waveform-visualizer/script.js b/tools/audio-waveform-visualizer/script.js new file mode 100644 index 0000000..b3ef0cc --- /dev/null +++ b/tools/audio-waveform-visualizer/script.js @@ -0,0 +1,152 @@ +document.addEventListener('DOMContentLoaded', function() { + // Get DOM elements + const fileInput = document.getElementById('audioFileInput'); + const audio = document.getElementById('audio'); + const playButton = document.getElementById('playButton'); + const muteButton = document.getElementById('muteButton'); + + // Set up Web Audio API context and nodes + const audioCtx = new AudioContext(); + const gainNode = audioCtx.createGain(); + let track = audioCtx.createMediaElementSource(audio); + const analyser = new AnalyserNode(audioCtx, { + fftSize: 2048, + maxDecibels: -25, + minDecibels: -60, + smoothingTimeConstant: 0.5, + }); + + // Connect audio nodes + track.connect(analyser); + track.connect(gainNode); + gainNode.connect(audioCtx.destination); + + // Prepare analyser data array + const bufferLength = analyser.frequencyBinCount; + const dataArray = new Uint8Array(bufferLength); + let channelData = null; + + // Set up canvas for waveform visualization + const canvas = document.getElementById("waveformCanvas"); + const canvasCtx = canvas.getContext("2d"); + const WIDTH = canvas.width; + const HEIGHT = canvas.height; + let animationId = null; + + // Handle upload button click + document.getElementById('uploadButton').addEventListener('click', function() { + document.getElementById('audioFileInput').click(); + }); + + // Handle file input change + fileInput.addEventListener('change', async (event) => { + // Get the selected file + const fileList = event.target.files; + const file = fileList[0]; + + // If no file selected, return + if (!file) return; + // Resume audio context if suspended + if (audioCtx.state === "suspended") audioCtx.resume(); + // Cancel any ongoing animation + if (animationId) cancelAnimationFrame(animationId); + + // Create a URL for the file + const fileURL = URL.createObjectURL(file); + // Set the audio player's source to the file URL + audio.src = fileURL; + + // Read and decode audio data + const arrayBuffer = await file.arrayBuffer(); + const decoded = await audioCtx.decodeAudioData(arrayBuffer); + channelData = decoded.getChannelData(0); + + // Draw the full waveform once audio is loaded + drawFullWaveform(); + + // Capture data array from audio + analyser.getByteTimeDomainData(dataArray); + + // Enable play and mute buttons + playButton.disabled = false; + muteButton.disabled = false; + }); + + // Handle play button click + playButton.addEventListener('click', () => { + if (!audio.paused) { + audio.pause(); + playButton.textContent = "Play"; + if (animationId) cancelAnimationFrame(animationId); + drawFullWaveform(); + } else { + audio.play(); + playButton.textContent = "Pause"; + if (animationId) cancelAnimationFrame(animationId); + canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); + drawLiveWaveform(); + } + }); + + // Handle mute button click + muteButton.addEventListener('click', () => { + let muted = gainNode.gain.value === 0; + gainNode.gain.value = muted ? 1 : 0; + muteButton.classList.toggle('muteRed', !muted); + }); + + // Handle audio ended event + audio.addEventListener("ended", () => { + if (animationId) cancelAnimationFrame(animationId); + playButton.textContent = "Play"; + if (channelData) drawFullWaveform(); + audio.currentTime = 0; + }); + + // Function to draw live waveform + function drawLiveWaveform() { + animationId = requestAnimationFrame(drawLiveWaveform); + analyser.getByteTimeDomainData(dataArray); + + canvasCtx.fillStyle = "#1a1a2e"; + canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); + + canvasCtx.lineWidth = 2; + canvasCtx.strokeStyle = "#0081b4"; + canvasCtx.beginPath(); + + const sliceWidth = WIDTH / bufferLength; + let x = 0; + for (let i = 0; i < bufferLength; i++) { + const v = dataArray[i] / 128.0; + const y = v * (HEIGHT / 2); + i === 0 ? canvasCtx.moveTo(x, y) : canvasCtx.lineTo(x, y); + x += sliceWidth; + } + + canvasCtx.lineTo(WIDTH, HEIGHT / 2); + canvasCtx.stroke(); + } + + // Function to draw full waveform + function drawFullWaveform() { + canvasCtx.clearRect(0, 0, WIDTH, HEIGHT); + canvasCtx.fillStyle = "#1a1a2e"; + canvasCtx.fillRect(0, 0, WIDTH, HEIGHT); + canvasCtx.strokeStyle = "#0081b4"; + canvasCtx.lineWidth = 1; + canvasCtx.beginPath(); + + const step = Math.ceil(channelData.length / WIDTH); + const amp = HEIGHT / 2; + + for (let i = 0; i < WIDTH; i++) { + const segment = channelData.slice(i * step, (i + 1) * step); + const min = Math.min(...segment); + const max = Math.max(...segment); + canvasCtx.moveTo(i, (1 + min) * amp); + canvasCtx.lineTo(i, (1 + max) * amp); + } + canvasCtx.stroke(); + } +}); \ No newline at end of file