You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, barGap = "6", cornerRadius = "8") {
const gap = parseInt(barGap, 10) || 6;
const radius = parseInt(cornerRadius, 10) || 8;
// We constrain the layout to maximum dimensions for consistent styling
const maxTotalWidth = 800;
const maxTotalHeight = 400;
// Calculate final drawing dimensions while preserving aspect ratio if possible
const scale = Math.min(maxTotalWidth / originalImg.width, maxTotalHeight / originalImg.height, 1) || 1;
const finalImgWidth = Math.max(originalImg.width * scale, 320); // enforce min width
const finalImgHeight = Math.max(originalImg.height * scale, 150); // enforce min height
const numNotes = 8;
const sliceWidth = originalImg.width / numNotes;
const finalSliceWidth = finalImgWidth / numNotes;
// Frequencies for a C-major scale starting at C4
const frequencies = [261.63, 293.66, 329.63, 349.23, 392.00, 440.00, 493.88, 523.25];
let audioCtx = null;
// Helper Web Audio API function to play a synthesized xylophone sound
function playNote(freq) {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
}
const t = audioCtx.currentTime;
// Primary tone
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(freq, t);
gain.gain.setValueAtTime(0, t);
gain.gain.linearRampToValueAtTime(1, t + 0.01);
gain.gain.exponentialRampToValueAtTime(0.001, t + 0.5);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start(t);
osc.stop(t + 0.6);
// Harmonic tone for wooden "strike" characteristic
const osc2 = audioCtx.createOscillator();
const gain2 = audioCtx.createGain();
osc2.type = 'triangle';
osc2.frequency.setValueAtTime(freq * 2.5, t);
gain2.gain.setValueAtTime(0, t);
gain2.gain.linearRampToValueAtTime(0.4, t + 0.01);
gain2.gain.exponentialRampToValueAtTime(0.001, t + 0.15);
osc2.connect(gain2);
gain2.connect(audioCtx.destination);
osc2.start(t);
osc2.stop(t + 0.2);
}
// Creating the main container
const container = document.createElement('div');
container.style.position = 'relative';
container.style.display = 'flex';
container.style.justifyContent = 'center';
container.style.alignItems = 'center';
container.style.padding = '40px 20px';
container.style.background = 'linear-gradient(135deg, #8B5A2B, #5C3A21)';
container.style.borderRadius = '16px';
container.style.boxShadow = 'inset 0 0 30px rgba(0,0,0,0.6), 0 10px 20px rgba(0,0,0,0.3)';
container.style.userSelect = 'none';
// Decorative background wooden rails
const topRail = document.createElement('div');
topRail.style.position = 'absolute';
topRail.style.top = '30%';
topRail.style.left = '4%';
topRail.style.right = '4%';
topRail.style.height = '14px';
topRail.style.background = '#3e2723';
topRail.style.boxShadow = '2px 2px 6px rgba(0,0,0,0.7)';
container.appendChild(topRail);
const bottomRail = document.createElement('div');
bottomRail.style.position = 'absolute';
bottomRail.style.bottom = '30%';
bottomRail.style.left = '4%';
bottomRail.style.right = '4%';
bottomRail.style.height = '14px';
bottomRail.style.background = '#3e2723';
bottomRail.style.boxShadow = '2px 2px 6px rgba(0,0,0,0.7)';
container.appendChild(bottomRail);
const notesContainer = document.createElement('div');
notesContainer.style.display = 'flex';
notesContainer.style.gap = `${gap}px`;
notesContainer.style.zIndex = '1';
notesContainer.style.alignItems = 'center';
// Build the xylophone bars
for (let i = 0; i < numNotes; i++) {
const barWrapper = document.createElement('div');
// Bars get progressively shorter corresponding to pitch
const heightMultiplier = 1 - (i * 0.045);
const currentHeight = finalImgHeight * heightMultiplier;
barWrapper.style.width = `${finalSliceWidth}px`;
barWrapper.style.height = `${currentHeight}px`;
barWrapper.style.borderRadius = `${radius}px`;
barWrapper.style.overflow = 'hidden';
barWrapper.style.cursor = 'pointer';
barWrapper.style.position = 'relative';
barWrapper.style.boxShadow = '3px 3px 8px rgba(0,0,0,0.7), inset -2px -2px 5px rgba(0,0,0,0.4), inset 2px 2px 5px rgba(255,255,255,0.4)';
barWrapper.style.transition = 'transform 0.05s, box-shadow 0.05s';
barWrapper.style.background = '#333';
const canvas = document.createElement('canvas');
canvas.width = finalSliceWidth;
canvas.height = currentHeight;
canvas.style.display = 'block';
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.pointerEvents = 'none';
const ctx = canvas.getContext('2d');
// Draw the respective image slice, adapting it to the current bar's height constraint
ctx.drawImage(
originalImg,
i * sliceWidth, 0, sliceWidth, originalImg.height,
0, 0, finalSliceWidth, currentHeight
);
// Draw structural metallic "screws" on the top and bottom of each bar
const screwRadius = Math.max(2, Math.min(4, finalSliceWidth * 0.1));
const screwYOffset = Math.min(20, Math.max(10, currentHeight * 0.1));
ctx.fillStyle = '#111';
// Top screw
ctx.beginPath();
ctx.arc(finalSliceWidth / 2, screwYOffset, screwRadius, 0, Math.PI * 2);
ctx.fill();
// Bottom screw
ctx.beginPath();
ctx.arc(finalSliceWidth / 2, currentHeight - screwYOffset, screwRadius, 0, Math.PI * 2);
ctx.fill();
// Adding interaction event listeners to simulate playing notes
const pressNote = (e) => {
e.preventDefault();
playNote(frequencies[i]);
barWrapper.style.transform = 'translateY(4px) scale(0.97)';
barWrapper.style.boxShadow = '1px 1px 3px rgba(0,0,0,0.7), inset -1px -1px 3px rgba(0,0,0,0.4), inset 1px 1px 3px rgba(255,255,255,0.4)';
};
const releaseNote = () => {
barWrapper.style.transform = 'translateY(0) scale(1)';
barWrapper.style.boxShadow = '3px 3px 8px rgba(0,0,0,0.7), inset -2px -2px 5px rgba(0,0,0,0.4), inset 2px 2px 5px rgba(255,255,255,0.4)';
};
barWrapper.addEventListener('mousedown', pressNote);
barWrapper.addEventListener('touchstart', pressNote, { passive: false });
barWrapper.addEventListener('mouseup', releaseNote);
barWrapper.addEventListener('mouseleave', releaseNote);
barWrapper.addEventListener('touchend', releaseNote);
barWrapper.appendChild(canvas);
notesContainer.appendChild(barWrapper);
}
container.appendChild(notesContainer);
return container;
}
Apply Changes