Please bookmark this page to avoid losing your image tool!

Image Music Song Visualization 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 = 8, minFreq = 80, maxFreq = 16000) {
    /**
     * This function interprets an image as a spectrogram and generates audio.
     * - X-axis of the image is mapped to time.
     * - Y-axis is mapped to frequency (logarithmically).
     * - Pixel brightness is mapped to amplitude.
     *
     * It uses an AudioWorklet for efficient, real-time audio synthesis.
     * The function returns an HTML element with play/stop controls.
     */

    // --- 1. Process Image Data ---
    // For performance, we'll cap the width of the processed image.
    const MAX_WIDTH = 1024;
    const scale = originalImg.width > MAX_WIDTH ? MAX_WIDTH / originalImg.width : 1;
    const width = Math.round(originalImg.width * scale);
    const height = Math.round(originalImg.height * scale);

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

    // Convert pixel data into a more usable spectrogram format:
    // An array of columns, where each column is an array of { freq, amp } objects.
    const spectrogramData = [];
    for (let x = 0; x < width; x++) {
        const column = [];
        for (let y = 0; y < height; y++) {
            const i = (y * width + x) * 4;
            // Calculate luminance for a more accurate brightness value.
            const brightness = (0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]) / 255;

            // Only process pixels above a certain brightness threshold.
            if (brightness > 0.05) {
                // Map y-coordinate to a logarithmic frequency scale.
                // (height - 1 - y) inverts the axis so top is high freq.
                const freq = minFreq * Math.pow(maxFreq / minFreq, (height - 1 - y) / (height - 1));
                column.push({ freq: freq, amp: brightness });
            }
        }
        spectrogramData.push(column);
    }

    // --- 2. Define AudioWorklet Processor ---
    // This processor runs in a separate thread to generate audio without blocking the UI.
    const processorString = `
      class ImageSonificationProcessor extends AudioWorkletProcessor {
        constructor() {
          super();
          this.spectrogram = [];
          this.playbackPosition = 0; // in samples
          this.phase = {}; // Stores phase for each frequency to ensure smooth sine waves

          this.port.onmessage = (event) => {
            this.spectrogram = event.data.spectrogram;
            this.duration = event.data.duration;
          };
        }

        process(inputs, outputs, parameters) {
          const output = outputs[0];
          const channel = output[0]; // Mono output
          const numSamples = channel.length;

          if (this.spectrogram.length === 0) return true; // Keep running if no data yet

          const imageWidth = this.spectrogram.length;
          const totalSamples = this.duration * sampleRate;

          for (let i = 0; i < numSamples; i++) {
            const currentTime = this.playbackPosition / sampleRate;
            
            // Stop when duration is exceeded
            if (currentTime > this.duration) {
                channel[i] = 0;
                this.playbackPosition++;
                continue;
            }

            // Determine which column of the image corresponds to the current time
            const x = Math.min(imageWidth - 1, Math.floor((currentTime / this.duration) * imageWidth));
            const column = this.spectrogram[x];
            
            let sampleValue = 0;
            if (column && column.length > 0) {
              // Additive synthesis: sum sine waves for all active frequencies in this time slice
              for (const note of column) {
                if (!this.phase[note.freq]) this.phase[note.freq] = 0;
                
                const increment = 2 * Math.PI * note.freq / sampleRate;
                sampleValue += Math.sin(this.phase[note.freq]) * note.amp;
                this.phase[note.freq] += increment;
                if (this.phase[note.freq] > 2 * Math.PI) this.phase[note.freq] -= 2 * Math.PI;
              }
              // Normalize the audio to prevent clipping when many notes play at once.
              sampleValue /= Math.sqrt(column.length);
            }
            
            channel[i] = sampleValue;
            this.playbackPosition++;
          }
          
          return this.playbackPosition < totalSamples; // Signal to keep processor alive
        }
      }
      registerProcessor('image-sonification-processor', ImageSonificationProcessor);
    `;

    // --- 3. Create Player UI ---
    const container = document.createElement('div');
    container.style.cssText = 'padding: 15px; border: 1px solid #ccc; border-radius: 8px; display: inline-flex; flex-direction: column; align-items: center; gap: 10px; background-color: #f9f9f9;';
    
    const controls = document.createElement('div');
    controls.style.cssText = 'display: flex; gap: 8px;';

    const playButton = document.createElement('button');
    playButton.textContent = '▶️ Play Sound';
    playButton.style.cssText = 'padding: 8px 12px; font-size: 16px; border-radius: 5px; border: 1px solid #999; cursor: pointer; background-color: #e0e0e0;';
    
    const stopButton = document.createElement('button');
    stopButton.textContent = '⏹️ Stop';
    stopButton.style.cssText = playButton.style.cssText;
    stopButton.disabled = true;

    controls.appendChild(playButton);
    controls.appendChild(stopButton);
    container.appendChild(controls);

    const progressContainer = document.createElement('div');
    progressContainer.style.cssText = 'width: 200px; height: 10px; border: 1px solid #bbb; background-color: #e0e0e0; border-radius: 5px; overflow: hidden;';
    const progressBar = document.createElement('div');
    progressBar.style.cssText = 'width: 0%; height: 100%; background-color: #4CAF50; transition: width 0.05s linear;';
    progressContainer.appendChild(progressBar);
    container.appendChild(progressContainer);
    
    const info = document.createElement('div');
    info.textContent = `Duration: ${duration}s | Freq: ${minFreq}-${maxFreq}Hz`;
    info.style.cssText = 'font-size: 12px; color: #555; font-family: sans-serif;';
    container.appendChild(info);

    let audioContext = null;
    let progressInterval = null;

    const cleanup = () => {
        clearInterval(progressInterval);
        progressBar.style.width = '0%';
        playButton.disabled = false;
        stopButton.disabled = true;
        audioContext = null;
    };

    playButton.onclick = async () => {
        if (audioContext && audioContext.state !== 'closed') return;

        playButton.disabled = true;
        stopButton.disabled = false;

        audioContext = new (window.AudioContext || window.webkitAudioContext)();
        await audioContext.resume();

        const blob = new Blob([processorString], { type: 'application/javascript' });
        const url = URL.createObjectURL(blob);
        await audioContext.audioWorklet.addModule(url);
        URL.revokeObjectURL(url);

        const sonificationNode = new AudioWorkletNode(audioContext, 'image-sonification-processor');
        sonificationNode.port.postMessage({ spectrogram: spectrogramData, duration: duration });

        const masterGain = audioContext.createGain();
        masterGain.gain.setValueAtTime(0.7, audioContext.currentTime); // Overall volume
        
        sonificationNode.connect(masterGain).connect(audioContext.destination);

        const startTime = Date.now();
        progressInterval = setInterval(() => {
            const elapsed = (Date.now() - startTime) / 1000;
            const progress = Math.min(100, (elapsed / duration) * 100);
            progressBar.style.width = `${progress}%`;
        }, 50);

        // Schedule cleanup when audio is expected to end
        setTimeout(() => {
            if (audioContext && audioContext.state !== 'closed') {
                audioContext.close().then(cleanup);
            }
        }, duration * 1000 + 200);
    };

    stopButton.onclick = () => {
        if (audioContext && audioContext.state !== 'closed') {
            audioContext.close().then(cleanup);
        }
    };

    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

The Image Music Song Visualization Tool allows users to convert images into audio representations by interpreting the visual data as a spectrogram. In this process, the brightness of each pixel in the image is mapped to amplitude, while the y-axis corresponds to frequency, and the x-axis represents time. This tool is useful for artists, sound designers, and musicians seeking to create unique audio experiences from visual stimuli, as well as educators and researchers exploring the intersection of sound and imagery. Users can play and stop the generated sound, adjusting parameters such as duration and frequency range to customize their auditory output.

Leave a Reply

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