You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, durationSec = 5, minFreqHz = 100, maxFreqHz = 8000, verticalBands = 50) {
const duration = parseFloat(durationSec) || 5;
const minFreq = parseFloat(minFreqHz) || 100;
const maxFreq = parseFloat(maxFreqHz) || 8000;
const H = parseInt(verticalBands) || 50;
const sampleRate = 44100;
const totalSamples = Math.floor(sampleRate * duration);
// 1. Process Image to extract Brightness Map (Inverse Spectrogram Data)
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const W = Math.max(1, Math.round((originalImg.width / originalImg.height) * H));
canvas.width = W;
canvas.height = H;
ctx.drawImage(originalImg, 0, 0, W, H);
// Read brightness for each pixel (Log intensity)
const imgData = ctx.getImageData(0, 0, W, H).data;
const brightness = Array.from({ length: W }, () => new Float32Array(H));
for (let y = 0; y < H; y++) {
for (let x = 0; x < W; x++) {
const idx = (y * W + x) * 4;
const r = imgData[idx];
const g = imgData[idx + 1];
const b = imgData[idx + 2];
// Compute luminance (0 to 1)
brightness[x][y] = (0.299 * r + 0.587 * g + 0.114 * b) / 255.0;
}
}
// 2. Synthesize Audio
const samples = new Float32Array(totalSamples);
const twoPi = 2 * Math.PI;
// Frequencies (Logarithmic scale: Top of image = high freq, Bottom = low freq)
const freqs = new Float32Array(H);
for (let y = 0; y < H; y++) {
const percent = (H - 1 - y) / (H - 1 || 1);
freqs[y] = minFreq * Math.pow(maxFreq / minFreq, percent);
}
// Generate sine waves per row and accumulate into the sample buffer
for (let y = 0; y < H; y++) {
const f = freqs[y];
let phase = 0;
const phaseInc = (twoPi * f) / sampleRate;
for (let x = 0; x < W; x++) {
const b1 = brightness[x][y];
const b2 = x < W - 1 ? brightness[x + 1][y] : 0;
const start = Math.floor((x / W) * totalSamples);
const end = Math.min(totalSamples, Math.floor(((x + 1) / W) * totalSamples));
const length = end - start;
for (let i = 0; i < length; i++) {
const pct = i / length;
const amp = b1 + (b2 - b1) * pct; // Linear interpolate brightness to avoid clicks
samples[start + i] += amp * Math.sin(phase);
phase += phaseInc;
}
}
}
// 3. Normalize audio and convert to Int16
let maxVal = 0;
for (let i = 0; i < totalSamples; i++) {
const absVal = Math.abs(samples[i]);
if (absVal > maxVal) maxVal = absVal;
}
const intSamples = new Int16Array(totalSamples);
const volumeMultiplier = maxVal > 0 ? (32767 * 0.9) / maxVal : 0;
for (let i = 0; i < totalSamples; i++) {
intSamples[i] = Math.max(-32768, Math.min(32767, samples[i] * volumeMultiplier));
}
// 4. Encode to MP3 (or gracefully fallback to WAV)
const loadLameJS = () => new Promise((resolve, reject) => {
if (window.lamejs) return resolve(window.lamejs);
const script = document.createElement('script');
script.src = "https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.1/lame.min.js";
script.onload = () => resolve(window.lamejs);
script.onerror = reject;
document.head.appendChild(script);
});
let audioBlob;
let extension = "mp3";
try {
const lamejs = await loadLameJS();
const mp3encoder = new lamejs.Mp3Encoder(1, sampleRate, 128); // 1 channel, 44.1kHz, 128kbps
const mp3Data = [];
const sampleBlockSize = 1152; // Needs to be a multiple of 576
for (let i = 0; i < totalSamples; i += sampleBlockSize) {
const chunk = intSamples.subarray(i, i + sampleBlockSize);
const mp3buf = mp3encoder.encodeBuffer(chunk);
if (mp3buf.length > 0) mp3Data.push(mp3buf);
}
const mp3buf = mp3encoder.flush();
if (mp3buf.length > 0) mp3Data.push(mp3buf);
audioBlob = new Blob(mp3Data, { type: 'audio/mp3' });
} catch (e) {
console.warn("Failed to load LameJS MP3 Encoder, falling back to Native WAV.", e);
extension = "wav";
// Generate WAV Header & Payload manually
const buffer = new ArrayBuffer(44 + intSamples.length * 2);
const view = new DataView(buffer);
const writeString = (v, offset, string) => {
for (let i = 0; i < string.length; i++) {
v.setUint8(offset + i, string.charCodeAt(i));
}
};
writeString(view, 0, 'RIFF');
view.setUint32(4, 36 + intSamples.length * 2, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true);
view.setUint16(22, 1, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * 2, true);
view.setUint16(32, 2, true);
view.setUint16(34, 16, true);
writeString(view, 36, 'data');
view.setUint32(40, intSamples.length * 2, true);
let offset = 44;
for (let i = 0; i < intSamples.length; i++, offset += 2) {
view.setInt16(offset, intSamples[i], true);
}
audioBlob = new Blob([view], { type: 'audio/wav' });
}
// 5. Build and return User Interface Element
const container = document.createElement('div');
container.style.fontFamily = 'system-ui, -apple-system, sans-serif';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.alignItems = 'center';
container.style.gap = '20px';
container.style.padding = '25px';
container.style.background = '#f9fafb';
container.style.border = '1px solid #e5e7eb';
container.style.borderRadius = '12px';
container.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1)';
container.style.maxWidth = '400px';
container.style.margin = '0 auto';
const titleInfo = document.createElement('h3');
titleInfo.textContent = "Image to Sound Effects Generator";
titleInfo.style.margin = '0';
titleInfo.style.color = '#1f2937';
container.appendChild(titleInfo);
// Provide scaled display representation
const displayImg = new Image();
displayImg.src = originalImg.src;
displayImg.style.maxWidth = '100%';
displayImg.style.maxHeight = '200px';
displayImg.style.objectFit = 'contain';
displayImg.style.borderRadius = '8px';
displayImg.style.border = '1px solid #d1d5db';
displayImg.style.background = '#000';
container.appendChild(displayImg);
const audioUrl = URL.createObjectURL(audioBlob);
// Audio Player
const audioEl = document.createElement('audio');
audioEl.controls = true;
audioEl.src = audioUrl;
audioEl.style.width = '100%';
container.appendChild(audioEl);
// Download Button
const downloadLink = document.createElement('a');
downloadLink.href = audioUrl;
downloadLink.download = `sonified_image_audio.${extension}`;
downloadLink.textContent = `Download ${extension.toUpperCase()} Track`;
downloadLink.style.padding = '12px 24px';
downloadLink.style.background = '#4F46E5';
downloadLink.style.color = '#ffffff';
downloadLink.style.textDecoration = 'none';
downloadLink.style.borderRadius = '8px';
downloadLink.style.fontWeight = '600';
downloadLink.style.transition = 'background 0.2s';
downloadLink.style.cursor = 'pointer';
downloadLink.onmouseover = () => downloadLink.style.background = '#4338CA';
downloadLink.onmouseout = () => downloadLink.style.background = '#4F46E5';
container.appendChild(downloadLink);
return container;
}
Apply Changes