You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, bpm = 120, mood = "Happy") {
// Create container
const container = document.createElement('div');
container.style.position = 'relative';
container.style.display = 'inline-block';
container.style.fontFamily = 'Arial, sans-serif';
container.style.boxShadow = '0 10px 30px rgba(0,0,0,0.5)';
container.style.borderRadius = '8px';
container.style.overflow = 'hidden';
// Main visualizer canvas
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0);
container.appendChild(canvas);
// Overlay controls
const btn = document.createElement('button');
btn.innerText = '▶ Generate & Play AI Music';
btn.style.position = 'absolute';
btn.style.top = '50%';
btn.style.left = '50%';
btn.style.transform = 'translate(-50%, -50%)';
btn.style.padding = '15px 30px';
btn.style.fontSize = '18px';
btn.style.fontWeight = 'bold';
btn.style.cursor = 'pointer';
btn.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
btn.style.color = '#333';
btn.style.border = 'none';
btn.style.borderRadius = '50px';
btn.style.boxShadow = '0 4px 15px rgba(0,0,0,0.3)';
btn.style.transition = 'all 0.3s ease';
btn.style.zIndex = '10';
btn.onmouseover = () => {
btn.style.transform = 'translate(-50%, -50%) scale(1.05)';
btn.style.backgroundColor = '#fff';
};
btn.onmouseout = () => {
btn.style.transform = 'translate(-50%, -50%) scale(1)';
btn.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
};
container.appendChild(btn);
// Music generation variables
let audioCtx = null;
let isPlaying = false;
let intervalId = null;
const gridSize = 16;
// Helper to generate scale frequencies based on mood
function getScaleFrequencies(selectedMood) {
const baseFreq = 130.81; // C3
const f = (steps) => baseFreq * Math.pow(2, steps / 12);
let intervals;
if (selectedMood.toLowerCase() === 'sad') {
intervals = [0, 3, 5, 7, 10]; // C Minor Pentatonic
} else if (selectedMood.toLowerCase() === 'mysterious') {
intervals = [0, 2, 4, 6, 8, 10]; // Whole tone
} else {
intervals = [0, 2, 4, 7, 9]; // C Major Pentatonic (Happy)
}
const freqs = [];
for (let octave = 0; octave < 4; octave++) {
for (let i = 0; i < intervals.length; i++) {
freqs.push(f(intervals[i] + octave * 12));
if (freqs.length === gridSize) break;
}
if (freqs.length === gridSize) break;
}
return freqs.reverse(); // high frequencies at the top (low Y index)
}
btn.addEventListener('click', () => {
if (isPlaying) {
// Stop logic
clearInterval(intervalId);
if (audioCtx) {
audioCtx.close();
audioCtx = null;
}
isPlaying = false;
btn.innerText = '▶ Play AI Music';
ctx.drawImage(originalImg, 0, 0); // reset visualizer
return;
}
// Start logic
isPlaying = true;
btn.innerText = '■ Stop Music';
// Initialize Web Audio API
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
// 1. Image Analysis (The "AI" processing)
const tempCanvas = document.createElement('canvas');
tempCanvas.width = gridSize;
tempCanvas.height = gridSize;
const tCtx = tempCanvas.getContext('2d');
tCtx.drawImage(originalImg, 0, 0, gridSize, gridSize);
const imgData = tCtx.getImageData(0, 0, gridSize, gridSize).data;
const scale = getScaleFrequencies(mood);
const melody = [];
// Map columns to time, rows to pitch
for (let x = 0; x < gridSize; x++) {
let notes = [];
for (let y = 0; y < gridSize; y++) {
const i = (y * gridSize + x) * 4;
const r = imgData[i];
const g = imgData[i+1];
const b = imgData[i+2];
const brightness = (r + g + b) / 3;
// Threshold to trigger notes, preserving polyphony
if (brightness > 100) {
// Select instrument based on dominant hue
let type = 'sine';
if (r > g + 20 && r > b + 20) type = 'sawtooth';
else if (g > r + 20 && g > b + 20) type = 'triangle';
else if (b > r + 20 && b > g + 20) type = 'square';
// Volume tied to brightness pixel value
const volume = (brightness / 255) * 0.15;
notes.push({ freq: scale[y], type, vol: volume, y });
}
}
// Fallback if the column is entirely dark (pick the brightest pixel)
if (notes.length === 0) {
let maxB = 0;
let bestY = 0;
for (let y = 0; y < gridSize; y++) {
const i = (y * gridSize + x) * 4;
const b = (imgData[i] + imgData[i+1] + imgData[i+2]) / 3;
if (b > maxB) { maxB = b; bestY = y; }
}
const vol = (maxB / 255) * 0.15;
if(vol > 0.02) {
notes.push({ freq: scale[bestY], type: 'sine', vol, y: bestY });
}
}
melody.push(notes);
}
// 2. Playback / Sequencer Loop
const parsedBpm = Number(bpm) || 120;
const stepTimeMs = 60000 / parsedBpm / 4; // 16th notes
let currentStep = 0;
ctx.lineWidth = 2;
intervalId = setInterval(() => {
const notes = melody[currentStep];
// Render visualizer
ctx.drawImage(originalImg, 0, 0);
// Draw scanning playhead
const colWidth = canvas.width / gridSize;
ctx.fillStyle = 'rgba(255, 255, 255, 0.2)';
ctx.fillRect(currentStep * colWidth, 0, colWidth, canvas.height);
ctx.strokeStyle = '#fff';
ctx.beginPath();
ctx.moveTo(currentStep * colWidth + colWidth/2, 0);
ctx.lineTo(currentStep * colWidth + colWidth/2, canvas.height);
ctx.stroke();
// Play notes and draw circles playing
notes.forEach(note => {
const osc = audioCtx.createOscillator();
const gainNode = audioCtx.createGain();
osc.type = note.type;
osc.frequency.setValueAtTime(note.freq, audioCtx.currentTime);
// Envelope
gainNode.gain.setValueAtTime(0.001, audioCtx.currentTime);
gainNode.gain.exponentialRampToValueAtTime(note.vol, audioCtx.currentTime + 0.05);
gainNode.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + (stepTimeMs/1000) * 0.9);
osc.connect(gainNode);
gainNode.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + (stepTimeMs/1000));
// Visual indicator for note hit
ctx.beginPath();
ctx.arc(
currentStep * colWidth + colWidth/2,
note.y * (canvas.height / gridSize) + (canvas.height / gridSize)/2,
Math.max(4, note.vol * 100),
0, 2 * Math.PI
);
ctx.fillStyle = 'rgba(255, 255, 255, 0.8)';
ctx.fill();
});
currentStep = (currentStep + 1) % gridSize;
}, stepTimeMs);
});
return container;
}
Apply Changes