Please bookmark this page to avoid losing your image tool!

Image Audio Vocoder Tool

(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, duration = 5, maxFrequency = 8000, gain = 0.5, logarithmicScale = 1) {

    /**
     * Encodes an AudioBuffer into a WAV file blob.
     * This is a self-contained helper function that takes an AudioBuffer and returns a Blob.
     * @param {AudioBuffer} abuffer The audio buffer to encode.
     * @returns {Blob} A blob containing the WAV file data.
     */
    const bufferToWave = (abuffer) => {
        const numOfChan = abuffer.numberOfChannels;
        const length = abuffer.length * numOfChan * 2 + 44;
        const buffer = new ArrayBuffer(length);
        const view = new DataView(buffer);
        const channels = [];
        let offset = 0;
        let pos = 0;

        const setUint16 = (data) => {
            view.setUint16(pos, data, true);
            pos += 2;
        };

        const setUint32 = (data) => {
            view.setUint32(pos, data, true);
            pos += 4;
        };

        // Write WAVE header
        setUint32(0x46464952); // "RIFF"
        setUint32(length - 8); // file length - 8
        setUint32(0x45564157); // "WAVE"

        setUint32(0x20746d66); // "fmt " chunk
        setUint32(16); // length = 16
        setUint16(1); // PCM (uncompressed)
        setUint16(numOfChan);
        setUint32(abuffer.sampleRate);
        setUint32(abuffer.sampleRate * 2 * numOfChan); // avg. bytes/sec
        setUint16(numOfChan * 2); // block-align
        setUint16(16); // 16-bit

        setUint32(0x61746164); // "data" chunk
        setUint32(length - pos - 4); // chunk length

        for (let i = 0; i < abuffer.numberOfChannels; i++) {
            channels.push(abuffer.getChannelData(i));
        }

        // Write interleaved PCM data from Float32
        while (offset < abuffer.length) {
            for (let i = 0; i < numOfChan; i++) {
                // Clamp, scale to 16-bit signed int, and write to buffer
                let sample = Math.max(-1, Math.min(1, channels[i][offset]));
                sample = sample < 0 ? sample * 32768 : sample * 32767;
                view.setInt16(pos, sample, true);
                pos += 2;
            }
            offset++;
        }

        return new Blob([buffer], {
            type: "audio/wav"
        });
    };

    // 1. Get pixel data from the image using a temporary canvas
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d', {
        willReadFrequently: true
    });
    const W = originalImg.naturalWidth;
    const H = originalImg.naturalHeight;
    canvas.width = W;
    canvas.height = H;

    // Handle potential CORS issues when drawing the image
    let imageData;
    try {
        ctx.drawImage(originalImg, 0, 0);
        imageData = ctx.getImageData(0, 0, W, H).data;
    } catch (e) {
        console.error("Could not get image data. If the image is from another domain, it may need CORS headers.", e);
        const errorEl = document.createElement('p');
        errorEl.textContent = 'Error: Could not process the image, possibly due to cross-origin restrictions. Try a different image or host.';
        errorEl.style.color = 'red';
        return errorEl;
    }

    // 2. Set up an OfflineAudioContext to render the sound without playing it live
    const sampleRate = 44100;
    const offlineCtx = new OfflineAudioContext(1, duration * sampleRate, sampleRate);

    // 3. Create a master gain node for overall volume control
    const masterGain = offlineCtx.createGain();
    masterGain.gain.setValueAtTime(Math.max(0, Math.min(1, parseFloat(gain) || 0.5)), 0);
    masterGain.connect(offlineCtx.destination);

    const minFreq = 20; // Human hearing floor

    // 4. Create an oscillator for each pixel row (representing a frequency band)
    for (let y = 0; y < H; y++) {
        const osc = offlineCtx.createOscillator();
        const amp = offlineCtx.createGain();
        amp.gain.setValueAtTime(0, 0); // Start with 0 gain
        osc.connect(amp);
        amp.connect(masterGain);

        // Map the pixel row 'y' to a frequency value.
        // normalizeY goes from 0 (bottom row) to 1 (top row).
        const normalizedY = H > 1 ? (H - 1 - y) / (H - 1) : 0.5;
        const maxFreq = Math.max(minFreq, parseFloat(maxFrequency) || 8000);
        
        let freq;
        if (parseInt(logarithmicScale) === 1) {
            // Logarithmic scale (more musical and perceptually uniform)
            freq = minFreq * Math.pow(maxFreq / minFreq, normalizedY);
        } else {
            // Linear scale
            freq = minFreq + normalizedY * (maxFreq - minFreq);
        }
        
        osc.frequency.setValueAtTime(freq, 0);

        // Create an array of brightness values from the current pixel row.
        // This array will control the oscillator's amplitude over time.
        const gainCurve = new Float32Array(W);
        for (let x = 0; x < W; x++) {
            const i = (y * W + x) * 4;
            const r = imageData[i];
            const g = imageData[i + 1];
            const b = imageData[i + 2];
            // Average brightness, normalized to 0.0 - 1.0, serves as gain
            gainCurve[x] = (r + g + b) / 3 / 255;
        }

        // Schedule the gain changes using the brightness curve
        amp.gain.setValueCurveAtTime(gainCurve, offlineCtx.currentTime, duration);
        osc.start(0);
        osc.stop(offlineCtx.currentTime + duration);
    }
    
    // 5. Start rendering the audio graph. This returns a Promise that resolves with the final AudioBuffer.
    const renderedBuffer = await offlineCtx.startRendering();

    // 6. Convert the rendered AudioBuffer to a WAV blob and create a playable <audio> element
    const wavBlob = bufferToWave(renderedBuffer);
    const audioUrl = URL.createObjectURL(wavBlob);
    
    const audioEl = document.createElement('audio');
    audioEl.controls = true;
    audioEl.src = audioUrl;
    audioEl.style.width = '100%';

    // Clean up the Blob URL when it's no longer needed to free up memory
    audioEl.addEventListener('play', () => {
        audioEl.onended = () => URL.revokeObjectURL(audioUrl);
    }, { once: true });

    // 7. Return the playable audio element
    return audioEl;
}

Free Image Tool Creator

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

Description

The Image Audio Vocoder Tool allows users to convert images into sound by mapping the pixel data from the image to audio frequencies and amplitudes. This tool processes an image to generate a unique audio waveform based on the brightness of pixels across the image. Users can customize parameters such as the duration of the sound, maximum frequency, gain, and whether to use a logarithmic scale for frequency mapping. This tool can be utilized for creative projects in music, sound design, and art, enabling artists and musicians to explore the intersection of visual and auditory media.

Leave a Reply

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