Please bookmark this page to avoid losing your image tool!

Image To Music Converter

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
async function processImage(originalImg, durationSec = "5", minFreqHz = "100", maxFreqHz = "8000", timeResolution = "100", freqResolution = "64") {
    const duration = parseFloat(durationSec) || 5;
    const minFreq = parseFloat(minFreqHz) || 100;
    const maxFreq = parseFloat(maxFreqHz) || 8000;
    const specWidth = parseInt(timeResolution) || 100;
    const specHeight = parseInt(freqResolution) || 64;

    // Create main container
    const container = document.createElement("div");
    container.style.display = "flex";
    container.style.flexDirection = "column";
    container.style.alignItems = "center";
    container.style.gap = "15px";
    container.style.fontFamily = "system-ui, -apple-system, sans-serif";
    container.style.background = "#1e1e2e";
    container.style.padding = "20px";
    container.style.borderRadius = "12px";
    container.style.boxShadow = "0 4px 15px rgba(0,0,0,0.3)";
    container.style.color = "#cdd6f4";
    container.style.maxWidth = "100%";
    container.style.width = "max-content";
    container.style.margin = "0 auto";

    // Header label
    const header = document.createElement("h3");
    header.textContent = "Image to Music (Spectrogram)";
    header.style.margin = "0";
    header.style.color = "#f9e2af";
    container.appendChild(header);

    // Create canvas to display the image and play head
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d", { willReadFrequently: true });
    
    // Scale canvas nicely for display
    const maxDisplaySize = 600;
    const scale = Math.min(maxDisplaySize / originalImg.width, maxDisplaySize / originalImg.height, 1);
    canvas.width = originalImg.width * scale;
    canvas.height = originalImg.height * scale;
    canvas.style.borderRadius = "8px";
    canvas.style.boxShadow = "0 4px 10px rgba(0,0,0,0.5)";
    canvas.style.maxWidth = "100%";
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    container.appendChild(canvas);

    // Play/Pause button
    const btn = document.createElement("button");
    btn.textContent = "🔊 Generate & Play";
    btn.style.padding = "12px 24px";
    btn.style.fontSize = "16px";
    btn.style.fontWeight = "bold";
    btn.style.cursor = "pointer";
    btn.style.border = "none";
    btn.style.borderRadius = "8px";
    btn.style.backgroundColor = "#a6e3a1";
    btn.style.color = "#11111b";
    btn.style.transition = "transform 0.1s, background-color 0.2s";
    
    // Hover effects for button
    btn.onmouseover = () => { if (!btn.disabled) btn.style.transform = "scale(1.05)"; };
    btn.onmouseout = () => { if (!btn.disabled) btn.style.transform = "scale(1)"; };

    container.appendChild(btn);

    // Audio tracking variables
    let audioCtx;
    let audioBuffer;
    let sourceNode;
    let isPlaying = false;
    let startTime = 0;
    let animFrameId;

    const generateAudio = async () => {
        // Step 1: Reduce image to spectrogram resolution (timeResolution x freqResolution)
        const tempCanvas = document.createElement("canvas");
        tempCanvas.width = specWidth;
        tempCanvas.height = specHeight;
        const tempCtx = tempCanvas.getContext("2d", { willReadFrequently: true });
        tempCtx.drawImage(originalImg, 0, 0, specWidth, specHeight);
        const imgData = tempCtx.getImageData(0, 0, specWidth, specHeight).data;

        // Extract amplitudes matching Luma values
        const amps = new Float32Array(specWidth * specHeight);
        for (let y = 0; y < specHeight; y++) {
            for (let x = 0; x < specWidth; x++) {
                const idx = (y * specWidth + x) * 4;
                const r = imgData[idx];
                const g = imgData[idx + 1];
                const b = imgData[idx + 2];
                // Non-linear luma extraction to reduce noise and emphasize bold features
                const luma = Math.pow((0.299 * r + 0.587 * g + 0.114 * b) / 255.0, 1.5);
                amps[y * specWidth + x] = luma > 0.05 ? luma : 0;
            }
        }

        // Step 2: Set up AudioBuffer
        const sampleRate = audioCtx.sampleRate;
        const totalSamples = Math.floor(duration * sampleRate);
        audioBuffer = audioCtx.createBuffer(1, totalSamples, sampleRate);
        const bufferData = audioBuffer.getChannelData(0);

        // Step 3: Additive Synthesis per Y-row (Frequency)
        for (let y = 0; y < specHeight; y++) {
            // Logarithmic scale for frequencies sounds more natural visually applied
            const freq = minFreq * Math.pow(maxFreq / minFreq, (specHeight - 1 - y) / (specHeight - 1));

            // Traverse X-columns (Time) 
            for (let x = 0; x < specWidth - 1; x++) {
                const amp1 = amps[y * specWidth + x];
                const amp2 = amps[y * specWidth + x + 1];

                // Skip processing if this time segment is silent for this frequency
                if (amp1 === 0 && amp2 === 0) continue;

                const startSample = Math.floor((x * totalSamples) / (specWidth - 1));
                const endSample = Math.floor(((x + 1) * totalSamples) / (specWidth - 1));

                for (let i = startSample; i < endSample; i++) {
                    const t = i / sampleRate;
                    // Linearly interpolate amplitude to prevent audio clicks/flutter between columns
                    const mix = (i - startSample) / (endSample - startSample);
                    const amplitude = amp1 + (amp2 - amp1) * mix;
                    
                    bufferData[i] += amplitude * Math.sin(2 * Math.PI * freq * t);
                }
            }
        }

        // Step 4: Normalize audio to prevent clipping
        let maxVal = 0.001;
        for (let i = 0; i < totalSamples; i++) {
            if (Math.abs(bufferData[i]) > maxVal) {
                maxVal = Math.abs(bufferData[i]);
            }
        }
        for (let i = 0; i < totalSamples; i++) {
            bufferData[i] /= maxVal;
            // Drop volume slightly lower than absolute peak to be easy on ears
            bufferData[i] *= 0.9;
        }
    };

    const drawLoop = () => {
        if (!isPlaying || !audioCtx) return;

        const timeElapsed = audioCtx.currentTime - startTime;
        if (timeElapsed >= duration) {
            stopAudio();
            return;
        }

        // Redraw image
        ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);

        // Progress playhead
        const progress = timeElapsed / duration;
        const x = progress * canvas.width;

        // Draw shadow/overlay for played area
        ctx.fillStyle = "rgba(0, 0, 0, 0.4)";
        ctx.fillRect(0, 0, x, canvas.height);

        // Draw sweeping laser line
        ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, canvas.height);
        ctx.strokeStyle = "#f38ba8";
        ctx.lineWidth = 3;
        ctx.shadowColor = "#f38ba8";
        ctx.shadowBlur = 10;
        ctx.stroke();
        
        ctx.shadowBlur = 0; // reset
        animFrameId = requestAnimationFrame(drawLoop);
    };

    const playAudio = () => {
        sourceNode = audioCtx.createBufferSource();
        sourceNode.buffer = audioBuffer;
        sourceNode.connect(audioCtx.destination);
        sourceNode.start();
        isPlaying = true;
        startTime = audioCtx.currentTime;
        
        btn.textContent = "⏹ Stop Music";
        btn.style.backgroundColor = "#f38ba8"; 
        
        drawLoop();
    };

    const stopAudio = () => {
        if (sourceNode) {
            sourceNode.stop();
            sourceNode.disconnect();
            sourceNode = null;
        }
        isPlaying = false;
        btn.textContent = "🔊 Play Music";
        btn.style.backgroundColor = "#a6e3a1";
        cancelAnimationFrame(animFrameId);
        
        // Reset visual state
        ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    };

    btn.addEventListener("click", async () => {
        if (isPlaying) {
            stopAudio();
            return;
        }

        // Initialize AudioContext synchronously on user interaction (Safari requirement)
        if (!audioCtx) {
            audioCtx = new (window.AudioContext || window.webkitAudioContext)();
        }
        if (audioCtx.state === "suspended") {
            await audioCtx.resume();
        }

        if (!audioBuffer) {
            btn.textContent = "⏳ Synthesizing Audio...";
            btn.style.backgroundColor = "#89b4fa";
            btn.style.color = "#11111b";
            btn.disabled = true;
            btn.style.cursor = "wait";
            
            // Allow DOM to repaint before heavy synchronous task
            await new Promise(resolve => setTimeout(resolve, 50));
            
            await generateAudio();
            
            btn.disabled = false;
            btn.style.cursor = "pointer";
        }
        
        playAudio();
    });

    return container;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

This tool converts images into audio by treating the image as a visual spectrogram. It maps the brightness and structure of an image to sound frequencies and amplitudes, effectively translating visual data into a unique musical experience. Users can adjust parameters such as duration, frequency range, and resolution to fine-tune the synthesized sound. This tool is useful for digital artists exploring sonification, experimental musicians creating unique textures, or anyone interested in experiencing visual art through sound.

Leave a Reply

Your email address will not be published. Required fields are marked *