Please bookmark this page to avoid losing your image tool!

Audio URL Vocoder For Image Processing

(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, carrierAudioUrl = '', modulatorAudioUrl = '') {

    /**
     * Creates an AudioBuffer containing white noise.
     * @param {AudioContext} context - The audio context.
     * @param {number} duration - The duration of the noise in seconds.
     * @returns {AudioBuffer} The generated audio buffer.
     */
    const createWhiteNoise = (context, duration) => {
        const bufferSize = context.sampleRate * duration;
        const buffer = context.createBuffer(1, bufferSize, context.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i = 0; i < bufferSize; i++) {
            data[i] = Math.random() * 2 - 1;
        }
        return buffer;
    };

    /**
     * Creates an AudioBuffer containing a sine wave.
     * @param {AudioContext} context - The audio context.
     * @param {number} duration - The duration of the wave in seconds.
     * @param {number} freq - The frequency of the sine wave.
     * @returns {AudioBuffer} The generated audio buffer.
     */
    const createSineWave = (context, duration, freq) => {
        const bufferSize = context.sampleRate * duration;
        const buffer = context.createBuffer(1, bufferSize, context.sampleRate);
        const data = buffer.getChannelData(0);
        for (let i = 0; i < bufferSize; i++) {
            data[i] = Math.sin(2 * Math.PI * freq * i / context.sampleRate);
        }
        return buffer;
    };

    /**
     * Processes an audio source to generate raw spectrogram data. It uses an OfflineAudioContext
     * to process the entire audio file non-real-time.
     * @param {string} audioUrl - URL of the audio file.
     * @param {Function} defaultGenerator - Function to generate a default AudioBuffer if the URL is not provided.
     * @returns {Promise<number[][]>} A promise that resolves to the raw spectrogram data (2D array).
     */
    const getRawSpectrogram = async (audioUrl, defaultGenerator) => {
        // A transient AudioContext is needed to create/decode audio buffers.
        const tempAudioCtx = new(window.AudioContext || window.webkitAudioContext)();
        let audioBuffer;

        try {
            if (audioUrl) {
                const response = await fetch(audioUrl);
                if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
                const arrayBuffer = await response.arrayBuffer();
                audioBuffer = await tempAudioCtx.decodeAudioData(arrayBuffer);
            } else {
                audioBuffer = defaultGenerator(tempAudioCtx, 3); // Default 3 second audio
            }
        } catch (e) {
            console.error("Error loading or decoding audio. Using fallback.", e);
            audioBuffer = defaultGenerator(tempAudioCtx, 3);
        }
        await tempAudioCtx.close();
        
        const offlineCtx = new OfflineAudioContext(audioBuffer.numberOfChannels, audioBuffer.length, audioBuffer.sampleRate);
        const source = offlineCtx.createBufferSource();
        source.buffer = audioBuffer;

        const analyser = offlineCtx.createAnalyser();
        analyser.fftSize = 2048;
        const frequencyBinCount = analyser.frequencyBinCount;

        // Using the deprecated ScriptProcessorNode for simplicity. AudioWorklet is the modern replacement.
        const scriptProcessor = offlineCtx.createScriptProcessor(analyser.fftSize, 1, 1);
        
        const spectrogram = [];
        scriptProcessor.onaudioprocess = () => {
            const frequencyData = new Uint8Array(frequencyBinCount);
            analyser.getByteFrequencyData(frequencyData);
            spectrogram.push(Array.from(frequencyData));
        };

        source.connect(analyser);
        analyser.connect(scriptProcessor);
        scriptProcessor.connect(offlineCtx.destination);
        source.start(0);

        await offlineCtx.startRendering();
        return spectrogram;
    };

    /**
     * Resamples a 2D array (spectrogram) to new dimensions using bilinear interpolation.
     * @param {number[][]} data - The input 2D array [width][height].
     * @param {number} newWidth - The target width.
     * @param {number} newHeight - The target height.
     * @returns {number[][]} The resampled 2D array.
     */
    const resampleSpectrogram = (data, newWidth, newHeight) => {
        if (!data || !data.length || !data[0].length) return [];
        const oldWidth = data.length;
        const oldHeight = data[0].length;
        const newData = Array(newWidth).fill(0).map(() => Array(newHeight).fill(0));

        for (let x = 0; x < newWidth; x++) {
            for (let y = 0; y < newHeight; y++) {
                const srcX = (newWidth === 1) ? 0 : x * (oldWidth - 1) / (newWidth - 1);
                const srcY = (newHeight === 1) ? 0 : y * (oldHeight - 1) / (newHeight - 1);

                const x1 = Math.floor(srcX);
                const y1 = Math.floor(srcY);
                const x2 = Math.min(x1 + 1, oldWidth - 1);
                const y2 = Math.min(y1 + 1, oldHeight - 1);

                const val11 = data[x1][y1];
                const val21 = data[x2][y1];
                const val12 = data[x1][y2];
                const val22 = data[x2][y2];

                const xDiff = srcX - x1;
                const yDiff = srcY - y1;

                const interpolated = val11 * (1 - xDiff) * (1 - yDiff) +
                                     val21 * xDiff * (1 - yDiff) +
                                     val12 * (1 - xDiff) * yDiff +
                                     val22 * xDiff * yDiff;
                
                newData[x][y] = interpolated;
            }
        }
        return newData;
    };

    // --- Main Function Logic ---
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    const { width, height } = originalImg;
    canvas.width = width;
    canvas.height = height;

    try {
        ctx.drawImage(originalImg, 0, 0, width, height);
        const imageData = ctx.getImageData(0, 0, width, height);
        const pixels = imageData.data;

        // Process both audio sources in parallel to get their spectrograms
        const [carrierRawSpectrogram, modulatorRawSpectrogram] = await Promise.all([
            getRawSpectrogram(carrierAudioUrl, (context, duration) => createSineWave(context, duration, 440)),
            getRawSpectrogram(modulatorAudioUrl, (context, duration) => createWhiteNoise(context, duration))
        ]);
        
        // Resample spectrograms to match the image dimensions
        const carrierSpectrogram = resampleSpectrogram(carrierRawSpectrogram, width, height);
        const modulatorSpectrogram = resampleSpectrogram(modulatorRawSpectrogram, width, height);

        if (!carrierSpectrogram.length || !modulatorSpectrogram.length) {
            throw new Error("Failed to generate valid spectrograms.");
        }

        // Apply the visual vocoder effect pixel by pixel
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                const i = (y * width + x) * 4;
                const r = pixels[i];
                const g = pixels[i + 1];
                const b = pixels[i + 2];

                const carrierVal = carrierSpectrogram[x][y];
                const modulatorVal = modulatorSpectrogram[x][y];

                // Create 3 "frequency band" gains from the modulator value (0-255).
                // These control the mix between the original image and the carrier signal visualization.
                const lowGain = Math.max(0, 1.0 - modulatorVal / 128.0);
                const highGain = Math.max(0, (modulatorVal - 128.0) / 128.0);
                const midGain = 1.0 - Math.abs(modulatorVal - 128.0) / 128.0;

                // Mix original pixel color with the carrier value based on the modulator gains for each channel
                pixels[i]     = r * highGain + carrierVal * (1 - highGain); // Red channel influenced by "high frequencies"
                pixels[i + 1] = g * midGain  + carrierVal * (1 - midGain);  // Green channel influenced by "mid frequencies"
                pixels[i + 2] = b * lowGain  + carrierVal * (1 - lowGain);  // Blue channel influenced by "low frequencies"
            }
        }

        ctx.putImageData(imageData, 0, 0);
    } catch (error) {
        console.error("An error occurred during image processing:", error);
        // In case of error, draw the original image on the canvas as a fallback.
        ctx.clearRect(0, 0, width, height);
        ctx.drawImage(originalImg, 0, 0, width, height);
        const p = document.createElement('p');
        p.textContent = `Error: ${error.message}. Please check audio URLs and browser console.`;
        p.style.color = 'red';
        p.style.position = 'absolute';
        p.style.top = '10px';
        p.style.left = '10px';
        p.style.backgroundColor = 'rgba(255,255,255,0.7)';
        return p;
    }

    return canvas;
}

Free Image Tool Creator

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

Description

Audio URL Vocoder for Image Processing is a creative tool that allows you to transform images by processing them with audio signals. Users can input an image alongside two audio URLs—one functioning as a carrier signal and the other as a modulator. The tool analyzes the audio to generate spectrograms, which are then applied to the image, modifying its colors based on the audio’s properties. This tool can be used by artists, designers, and musicians looking to create visually striking effects that fuse sound and imagery, suitable for digital art projects, music album covers, or visual installations.

Leave a Reply

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