You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, baseVolume = "0.5") {
// Parse volume and clamp it between 0.0 and 1.0
const volume = Math.max(0, Math.min(parseFloat(baseVolume) || 0.5, 1));
// Create the main container
const container = document.createElement('div');
container.style.position = 'relative';
container.style.display = 'inline-block';
container.style.userSelect = 'none';
container.style.overflow = 'hidden';
container.style.lineHeight = '0'; // Prevent bottom spacing issues
container.style.borderRadius = '8px';
container.style.boxShadow = '0 4px 12px rgba(0,0,0,0.2)';
// Create the interactive canvas
const canvas = document.createElement('canvas');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
canvas.style.cursor = 'crosshair';
canvas.style.maxWidth = '100%';
canvas.style.display = 'block';
canvas.style.touchAction = 'none'; // Prevent scrolling when tapping
container.appendChild(canvas);
const ctx = canvas.getContext('2d');
// Audio Context and state
let audioCtx = null;
let interacted = false;
let particles = [];
// Toy Sound Synthesis Generators
const soundGenerators = {
squeak: () => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(600, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(1400, audioCtx.currentTime + 0.1);
osc.frequency.exponentialRampToValueAtTime(600, audioCtx.currentTime + 0.2);
gain.gain.setValueAtTime(volume, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.2);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(); osc.stop(audioCtx.currentTime + 0.2);
},
boing: () => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(150, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(800, audioCtx.currentTime + 0.05);
osc.frequency.exponentialRampToValueAtTime(100, audioCtx.currentTime + 0.4);
gain.gain.setValueAtTime(volume * 1.2, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(); osc.stop(audioCtx.currentTime + 0.4);
},
honk: () => {
const osc = audioCtx.createOscillator();
const filter = audioCtx.createBiquadFilter();
const gain = audioCtx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(250 + Math.random() * 50, audioCtx.currentTime);
filter.type = 'lowpass';
filter.frequency.setValueAtTime(2000, audioCtx.currentTime);
filter.frequency.exponentialRampToValueAtTime(200, audioCtx.currentTime + 0.25);
gain.gain.setValueAtTime(0, audioCtx.currentTime);
gain.gain.linearRampToValueAtTime(volume * 0.8, audioCtx.currentTime + 0.05);
gain.gain.linearRampToValueAtTime(0.01, audioCtx.currentTime + 0.3);
osc.connect(filter);
filter.connect(gain);
gain.connect(audioCtx.destination);
osc.start(); osc.stop(audioCtx.currentTime + 0.3);
},
beep: () => {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'square';
osc.frequency.setValueAtTime(700 + Math.random() * 600, audioCtx.currentTime);
gain.gain.setValueAtTime(volume * 0.4, audioCtx.currentTime);
gain.gain.setValueAtTime(0, audioCtx.currentTime + 0.15);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(); osc.stop(audioCtx.currentTime + 0.15);
},
rattle: () => {
const buffSize = audioCtx.sampleRate * 0.4;
const buffer = audioCtx.createBuffer(1, buffSize, audioCtx.sampleRate);
const data = buffer.getChannelData(0);
for (let i = 0; i < buffSize; i++) data[i] = Math.random() * 2 - 1;
const noise = audioCtx.createBufferSource();
noise.buffer = buffer;
const amGain = audioCtx.createGain();
const lfo = audioCtx.createOscillator();
lfo.type = 'square';
lfo.frequency.value = 15;
lfo.connect(amGain.gain);
const masterGain = audioCtx.createGain();
masterGain.gain.setValueAtTime(volume * 0.7, audioCtx.currentTime);
masterGain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + 0.4);
noise.connect(amGain);
amGain.connect(masterGain);
masterGain.connect(audioCtx.destination);
noise.start(); lfo.start();
noise.stop(audioCtx.currentTime + 0.4); lfo.stop(audioCtx.currentTime + 0.4);
}
};
const toys = [
{ id: 'squeak', text: "SQUEAK!", color: "#FFE800", shape: "circle" },
{ id: 'boing', text: "BOING!", color: "#00FF66", shape: "star" },
{ id: 'honk', text: "HONK!", color: "#FF3366", shape: "cloud" },
{ id: 'beep', text: "BEEP!", color: "#33CCFF", shape: "square" },
{ id: 'rattle', text: "RATTLE!", color: "#FF9900", shape: "star" }
];
// Interaction Handler
function handleInteraction(e) {
e.preventDefault();
// Initialize Audio context on first interaction
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
interacted = true;
// Get coordinates
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
let clientX = e.clientX;
let clientY = e.clientY;
if (e.touches && e.touches.length > 0) {
clientX = e.touches[0].clientX;
clientY = e.touches[0].clientY;
}
const x = (clientX - rect.left) * scaleX;
const y = (clientY - rect.top) * scaleY;
// Pick a random toy noise
const toy = toys[Math.floor(Math.random() * toys.length)];
// Play the sound
soundGenerators[toy.id]();
// Add visual particle
particles.push({
x: x,
y: y,
life: 1.0,
text: toy.text,
color: toy.color,
shape: toy.shape,
rotation: (Math.random() - 0.5) * 0.6,
scale: 0.8 + Math.random() * 0.4
});
}
// Helper to draw geometric shape
function drawShape(ctx, shape) {
ctx.beginPath();
if (shape === 'star') {
const spikes = 8;
const outerRadius = 45;
const innerRadius = 30;
let rot = Math.PI / 2 * 3;
let step = Math.PI / spikes;
ctx.moveTo(0, -outerRadius);
for (let i = 0; i < spikes; i++) {
ctx.lineTo(Math.cos(rot) * outerRadius, Math.sin(rot) * outerRadius);
rot += step;
ctx.lineTo(Math.cos(rot) * innerRadius, Math.sin(rot) * innerRadius);
rot += step;
}
ctx.lineTo(0, -outerRadius);
} else if (shape === 'circle') {
ctx.arc(0, 0, 40, 0, Math.PI * 2);
} else if (shape === 'cloud') {
ctx.ellipse(0, 0, 50, 35, 0, 0, Math.PI * 2);
} else if (shape === 'square') {
ctx.rect(-40, -40, 80, 80);
}
ctx.closePath();
ctx.fill();
ctx.lineWidth = 4;
ctx.strokeStyle = 'black';
ctx.stroke();
}
// Render loop
function render() {
// Only constantly redraw if active particles exist or prior to first interaction
if (interacted && particles.length === 0) {
requestAnimationFrame(render);
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalImg, 0, 0);
if (!interacted) {
// Draw instructional overlay
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = 'white';
const fontSize = Math.max(16, canvas.width * 0.05);
ctx.font = `bold ${fontSize}px "Comic Sans MS", "Chalkboard SE", sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Text shadow for readability
ctx.shadowColor = 'black';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
ctx.fillText('Click to play Toy Noises!', canvas.width / 2, canvas.height / 2);
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
// Draw and update active particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(p.rotation);
// Pop out and float up
const s = p.scale * (1.2 - p.life * 0.2);
ctx.scale(s, s);
ctx.globalAlpha = p.life;
p.y -= 1.5; // Float upwards slightly
// Draw Toy Visual Marker
ctx.fillStyle = p.color;
drawShape(ctx, p.shape);
// Draw Toy Text Inside
ctx.fillStyle = 'black';
ctx.font = '900 18px "Comic Sans MS", "Chalkboard SE", sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(p.text, 0, 0);
ctx.restore();
p.life -= 0.015; // Fade out speed
if (p.life <= 0) {
particles.splice(i, 1);
}
}
// Keep redrawing empty frame to clean up once completed
if (interacted && particles.length === 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(originalImg, 0, 0);
}
requestAnimationFrame(render);
}
// Attach listeners
canvas.addEventListener('mousedown', handleInteraction);
canvas.addEventListener('touchstart', handleInteraction, {passive: false});
// Start render loop
requestAnimationFrame(render);
return container;
}
Apply Changes