Please bookmark this page to avoid losing your image tool!

Image Audio Effects Voice Changer

(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, audioUrl = 'https://cdn.freesound.org/previews/612/612089_11861999-lq.mp3', effectType = 'imageModulation', effectParam1 = 'gain', effectParam2 = 1.0) {
    /**
     * Applies audio effects to a sound file, with some effects controlled by an input image.
     * The function processes the audio offline and returns an HTML element with an audio player and a download link.
     *
     * @param {Image} originalImg - The input image object to use for modulation effects.
     * @param {string} audioUrl - The URL of the audio file to process. Must be CORS-accessible.
     * @param {string} effectType - The type of effect to apply. Options: 'imageModulation', 'pitchShift', 'distortion', 'reverb'.
     * @param {string|number} effectParam1 - The first parameter for the chosen effect.
     * @param {string|number} effectParam2 - The second parameter for the chosen effect.
     *
     * === Effect-specific parameters ===
     * 'imageModulation': Modulates an audio property based on image brightness.
     *   - effectParam1 (string): Property to modulate. 'gain', 'filter', or 'pitch'. Default: 'gain'.
     *   - effectParam2 (number): Scan speed multiplier across the image. 1.0 means the image is scanned once over the audio's duration. Default: 1.0.
     * 'pitchShift': Changes the audio pitch without changing its speed.
     *   - effectParam1 (number): Semitones to shift the pitch. Can be a negative value. Default: 0.
     * 'distortion': Adds a classic distortion/overdrive effect.
     *   - effectParam1 (number): Amount of distortion. Recommended range 0-1000. Default: 50.
     * 'reverb': Adds reverberation to the sound.
     *   - effectParam1 (number): Reverb decay time in seconds. Default: 3.
     *   - effectParam2 (number): Set to 1 for a reversed reverb effect. Default: 0.
     */

    // Helper function to convert an AudioBuffer into a WAV audio format Blob.
    const bufferToWave = (abuffer) => {
        const numOfChan = abuffer.numberOfChannels;
        const length = abuffer.length * numOfChan * 2 + 44;
        const buffer = new ArrayBuffer(length);
        const view = new DataView(buffer);
        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);
        setUint32(0x45564157); // "WAVE"
        setUint32(0x20746d66); // "fmt " chunk
        setUint32(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);

        // Get channel data and write interleaved samples
        const channels = [];
        for (let i = 0; i < abuffer.numberOfChannels; i++) {
            channels.push(abuffer.getChannelData(i));
        }

        for (let offset = 0; offset < abuffer.length; offset++) {
            for (let i = 0; i < numOfChan; i++) {
                const sample = Math.max(-1, Math.min(1, channels[i][offset] || 0));
                view.setInt16(pos, sample < 0 ? sample * 32768 : sample * 32767, true);
                pos += 2;
            }
        }
        return new Blob([view], { type: 'audio/wav' });
    };

    const resultContainer = document.createElement('div');
    resultContainer.style.fontFamily = 'sans-serif';
    resultContainer.innerHTML = 'Processing audio...';

    try {
        const response = await fetch(audioUrl);
        if (!response.ok) throw new Error(`Audio fetch failed: ${response.statusText}`);
        const arrayBuffer = await response.arrayBuffer();

        const tempContext = new(window.AudioContext || window.webkitAudioContext)();
        const decodedBuffer = await tempContext.decodeAudioData(arrayBuffer);
        await tempContext.close();

        const offlineContext = new OfflineAudioContext(
            decodedBuffer.numberOfChannels,
            decodedBuffer.length,
            decodedBuffer.sampleRate
        );

        const source = offlineContext.createBufferSource();
        source.buffer = decodedBuffer;
        let lastNode = source;

        // Apply selected audio effect
        switch (effectType) {
            case 'imageModulation': {
                const modulates = String(effectParam1) || 'gain';
                const scanSpeed = Number(effectParam2) || 1.0;

                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d', { willReadFrequently: true });
                canvas.width = originalImg.width;
                canvas.height = originalImg.height;
                ctx.drawImage(originalImg, 0, 0);

                let modulatorNode, automationParam, valueMapper;

                if (modulates === 'filter') {
                    modulatorNode = offlineContext.createBiquadFilter();
                    modulatorNode.type = 'lowpass';
                    modulatorNode.Q.value = 5;
                    automationParam = modulatorNode.frequency;
                    const minFreq = 40, maxFreq = offlineContext.sampleRate / 2;
                    const logMin = Math.log(minFreq), logMax = Math.log(maxFreq);
                    valueMapper = (b) => Math.exp(logMin + (logMax - logMin) * (b / 255));
                } else if (modulates === 'pitch') {
                    automationParam = source.detune; // Modulate source directly
                    const maxDetune = 2400; // Cents, +/- 2 octaves
                    valueMapper = (b) => (b / 255 * 2 - 1) * maxDetune;
                } else { // default to 'gain'
                    modulatorNode = offlineContext.createGain();
                    automationParam = modulatorNode.gain;
                    valueMapper = (b) => b / 255;
                }

                // Schedule parameter changes based on image brightness
                const updatesPerSecond = 60;
                const numSteps = Math.floor(decodedBuffer.duration * updatesPerSecond);
                for (let i = 0; i <= numSteps; i++) {
                    const time = i / updatesPerSecond;
                    if (time > decodedBuffer.duration) break;
                    
                    const progress = time / decodedBuffer.duration;
                    const imageX = Math.floor((progress * canvas.width * scanSpeed) % canvas.width);
                    const sliceData = ctx.getImageData(imageX, 0, 1, canvas.height).data;
                    
                    let sum = 0;
                    for (let j = 0; j < sliceData.length; j += 4) {
                        sum += (sliceData[j] + sliceData[j + 1] + sliceData[j + 2]) / 3;
                    }
                    const avgBrightness = sum / (sliceData.length / 4);
                    const value = valueMapper(avgBrightness);

                    if (i === 0) automationParam.setValueAtTime(value, 0);
                    else automationParam.linearRampToValueAtTime(value, time);
                }
                
                if (modulatorNode) {
                    lastNode.connect(modulatorNode);
                    lastNode = modulatorNode;
                }
                break;
            }
            case 'pitchShift': {
                const pitch = Number(effectParam1) || 0;
                source.detune.value = pitch * 100; // detune is in cents
                break;
            }
            case 'distortion': {
                const amount = Math.max(0, Number(effectParam1) || 50);
                const curve = new Float32Array(44100);
                for (let i = 0; i < curve.length; ++i) {
                    const x = i * 2 / curve.length - 1;
                    curve[i] = (Math.PI + amount) * x / (Math.PI + amount * Math.abs(x));
                }
                const distortion = offlineContext.createWaveShaper();
                distortion.curve = curve;
                distortion.oversample = '4x';
                lastNode.connect(distortion);
                lastNode = distortion;
                break;
            }
            case 'reverb': {
                const decay = Math.max(0.1, Number(effectParam1) || 3);
                const reverse = Number(effectParam2) || 0;
                const len = offlineContext.sampleRate * decay;
                const impulse = offlineContext.createBuffer(2, len, offlineContext.sampleRate);
                for (let c = 0; c < 2; c++) {
                    const data = impulse.getChannelData(c);
                    for (let i = 0; i < len; i++) {
                        const n = reverse ? len - i : i;
                        data[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / len, 2.5);
                    }
                }
                const convolver = offlineContext.createConvolver();
                convolver.buffer = impulse;
                lastNode.connect(convolver);
                lastNode = convolver;
                break;
            }
        }
        
        lastNode.connect(offlineContext.destination);
        source.start(0);

        const renderedBuffer = await offlineContext.startRendering();
        const wavBlob = bufferToWave(renderedBuffer);
        const audioObjectUrl = URL.createObjectURL(wavBlob);

        resultContainer.innerHTML = '';
        const title = document.createElement('h3');
        title.textContent = `Result for Effect: ${effectType}`;
        resultContainer.appendChild(title);

        const audioElement = document.createElement('audio');
        audioElement.controls = true;
        audioElement.src = audioObjectUrl;
        audioElement.style.width = '100%';
        resultContainer.appendChild(audioElement);

        const downloadLink = document.createElement('a');
        downloadLink.href = audioObjectUrl;
        downloadLink.download = `processed_audio_${effectType}.wav`;
        downloadLink.textContent = 'Download as .WAV';
        downloadLink.style.display = 'block';
        downloadLink.style.marginTop = '10px';
        resultContainer.appendChild(downloadLink);

    } catch (error) {
        console.error('ImageAudioEffects error:', error);
        resultContainer.innerHTML = `An error occurred: ${error.message}. <br>Check if the audio URL is valid and allows cross-origin requests (CORS).`;
        resultContainer.style.color = 'red';
    }

    return resultContainer;
}

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 Effects Voice Changer tool allows users to apply various audio effects to sound files using an input image for modulation. Users can manipulate audio properties such as gain, pitch, distortion, and reverb based on the brightness of the image. This tool can be beneficial for sound designers, musicians, and content creators looking to enhance their audio projects by integrating visual elements, or simply for those interested in experimenting with unique audio transformations. The processed audio can be easily downloaded in WAV format.

Leave a Reply

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