You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, title = "Ringtone.mp3", themeColor = "#4CAF50") {
// 1. Create a data URL from the original image for the album art and background
const canvas = document.createElement('canvas');
canvas.width = originalImg.width || 300;
canvas.height = originalImg.height || 300;
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
const imgDataUrl = canvas.toDataURL('image/png');
// 2. Generate a synthetic Android-style Ringtone (WAV format encoded in JS)
// 6 seconds duration: double chime, pause, double chime, pause
const sampleRate = 22050;
const duration = 6;
const numSamples = sampleRate * duration;
const buffer = new ArrayBuffer(44 + numSamples * 2);
const view = new DataView(buffer);
// Helper to write WAV strings
const writeString = (view, offset, string) => {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
};
// Write WAV header
writeString(view, 0, 'RIFF');
view.setUint32(4, 36 + numSamples * 2, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true); // PCM Format
view.setUint16(22, 1, true); // Mono
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * 2, true); // Byte rate
view.setUint16(32, 2, true); // Block align
view.setUint16(34, 16, true); // Bits per sample
writeString(view, 36, 'data');
view.setUint32(40, numSamples * 2, true);
// Synthesize the ringtone audio data
let offset = 44;
for (let i = 0; i < numSamples; i++) {
const t = i / sampleRate;
let sample = 0;
const cycle = t % 3.0; // 3 seconds loop cycle
// A pleasant double-chirp ringing pattern
if (cycle < 1.0) {
if ((cycle > 0.0 && cycle < 0.3) || (cycle > 0.4 && cycle < 0.7)) {
// Electronic chime frequencies
sample = (Math.sin(t * 2 * Math.PI * 800) + Math.sin(t * 2 * Math.PI * 1040)) * 0.5;
// Add an envelope mapping to avoid audio clicks
let localT = cycle > 0.4 ? cycle - 0.4 : cycle;
let env = 1;
if (localT < 0.05) env = localT / 0.05; // Attack
else if (localT > 0.25) env = (0.3 - localT) / 0.05; // Release
sample *= env;
}
}
// Apply master volume and write 16-bit PCM integer
sample = sample * 0.4;
let v = sample * 32767;
view.setInt16(offset, v, true);
offset += 2;
}
// Create an audio blob and object URL
const audioBlob = new Blob([view], { type: 'audio/wav' });
const audioUrl = URL.createObjectURL(audioBlob);
const audioNode = new Audio(audioUrl);
audioNode.loop = true;
// 3. Build the MP3 Player UI
const container = document.createElement('div');
container.style.position = 'relative';
container.style.width = '320px';
container.style.height = '480px';
container.style.borderRadius = '24px';
container.style.overflow = 'hidden';
container.style.boxShadow = '0 12px 36px rgba(0,0,0,0.4)';
container.style.fontFamily = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
container.style.color = '#fff';
container.style.margin = '20px auto';
// Blurred Background
const bg = document.createElement('div');
bg.style.position = 'absolute';
bg.style.top = '-20px';
bg.style.left = '-20px';
bg.style.right = '-20px';
bg.style.bottom = '-20px';
bg.style.backgroundImage = `url(${imgDataUrl})`;
bg.style.backgroundSize = 'cover';
bg.style.backgroundPosition = 'center';
bg.style.filter = 'blur(18px) brightness(0.3)';
bg.style.zIndex = '1';
container.appendChild(bg);
// Frontend UI Layer
const ui = document.createElement('div');
ui.style.position = 'absolute';
ui.style.top = '0';
ui.style.left = '0';
ui.style.width = '100%';
ui.style.height = '100%';
ui.style.zIndex = '2';
ui.style.display = 'flex';
ui.style.flexDirection = 'column';
ui.style.alignItems = 'center';
ui.style.padding = '40px 24px';
ui.style.boxSizing = 'border-box';
container.appendChild(ui);
// Album Art
const albumArt = document.createElement('img');
albumArt.src = imgDataUrl;
albumArt.style.width = '200px';
albumArt.style.height = '200px';
albumArt.style.borderRadius = '20px';
albumArt.style.objectFit = 'cover';
albumArt.style.boxShadow = '0 10px 20px rgba(0,0,0,0.5)';
albumArt.style.marginBottom = '25px';
ui.appendChild(albumArt);
// Song Title & Artist
const titleEl = document.createElement('h2');
titleEl.innerText = title;
titleEl.style.margin = '0 0 5px 0';
titleEl.style.fontSize = '20px';
titleEl.style.fontWeight = '600';
titleEl.style.textAlign = 'center';
titleEl.style.whiteSpace = 'nowrap';
titleEl.style.overflow = 'hidden';
titleEl.style.textOverflow = 'ellipsis';
titleEl.style.width = '100%';
ui.appendChild(titleEl);
const subEl = document.createElement('p');
subEl.innerText = "Android Audio Player";
subEl.style.margin = '0 0 35px 0';
subEl.style.fontSize = '13px';
subEl.style.color = 'rgba(255, 255, 255, 0.7)';
subEl.style.textAlign = 'center';
ui.appendChild(subEl);
// Progress Bar Wrapper
const progWrapper = document.createElement('div');
progWrapper.style.width = '100%';
progWrapper.style.marginBottom = '25px';
ui.appendChild(progWrapper);
// Seek Slider
const progress = document.createElement('input');
progress.type = 'range';
progress.min = 0;
progress.max = 100;
progress.value = 0;
progress.style.width = '100%';
progress.style.cursor = 'pointer';
progress.style.margin = '0';
progWrapper.appendChild(progress);
// Timestamps
const timeInfo = document.createElement('div');
timeInfo.style.display = 'flex';
timeInfo.style.justifyContent = 'space-between';
timeInfo.style.fontSize = '11px';
timeInfo.style.fontWeight = '500';
timeInfo.style.color = 'rgba(255, 255, 255, 0.6)';
timeInfo.style.marginTop = '8px';
const currTimeEl = document.createElement('span');
currTimeEl.innerText = '0:00';
const totalTimeEl = document.createElement('span');
totalTimeEl.innerText = '0:06';
timeInfo.appendChild(currTimeEl);
timeInfo.appendChild(totalTimeEl);
progWrapper.appendChild(timeInfo);
// Controller Buttons
const controls = document.createElement('div');
controls.style.display = 'flex';
controls.style.alignItems = 'center';
controls.style.justifyContent = 'center';
controls.style.gap = '25px';
ui.appendChild(controls);
const createBtn = (icon, size, fontSize) => {
const btn = document.createElement('button');
btn.innerText = icon;
btn.style.width = size;
btn.style.height = size;
btn.style.borderRadius = '50%';
btn.style.border = 'none';
btn.style.backgroundColor = 'rgba(255, 255, 255, 0.15)';
btn.style.color = '#fff';
btn.style.fontSize = fontSize;
btn.style.cursor = 'pointer';
btn.style.display = 'flex';
btn.style.justifyContent = 'center';
btn.style.alignItems = 'center';
btn.style.transition = 'all 0.2s ease-in-out';
btn.addEventListener('mouseover', () => btn.style.backgroundColor = 'rgba(255, 255, 255, 0.25)');
btn.addEventListener('mouseout', () => btn.style.backgroundColor = 'rgba(255, 255, 255, 0.15)');
return btn;
};
const prevBtn = createBtn('⏪', '44px', '16px');
const playBtn = createBtn('▶', '64px', '26px');
const nextBtn = createBtn('⏩', '44px', '16px');
// Customize main Play button layout
playBtn.style.backgroundColor = themeColor;
playBtn.style.boxShadow = '0 6px 16px rgba(0,0,0,0.3)';
playBtn.addEventListener('mouseover', () => playBtn.style.transform = 'scale(1.05)');
playBtn.addEventListener('mouseout', () => playBtn.style.transform = 'scale(1.0)');
controls.appendChild(prevBtn);
controls.appendChild(playBtn);
controls.appendChild(nextBtn);
// 4. Implement Audio Player Logic
let isPlaying = false;
let isDragging = false;
playBtn.addEventListener('click', () => {
if (isPlaying) {
audioNode.pause();
playBtn.innerText = '▶';
} else {
audioNode.play();
playBtn.innerText = '⏸';
}
isPlaying = !isPlaying;
});
prevBtn.addEventListener('click', () => {
audioNode.currentTime = Math.max(0, audioNode.currentTime - 1);
});
nextBtn.addEventListener('click', () => {
audioNode.currentTime = Math.min(audioNode.duration || duration, audioNode.currentTime + 1);
});
const formatTime = (secs) => {
const m = Math.floor(secs / 60);
const s = Math.floor(secs % 60);
return `${m}:${s < 10 ? '0' : ''}${s}`;
};
audioNode.addEventListener('timeupdate', () => {
if (!isDragging) {
const percent = (audioNode.currentTime / audioNode.duration) * 100;
progress.value = percent || 0;
currTimeEl.innerText = formatTime(audioNode.currentTime);
}
});
// Cleanup drag issues and seek properly
progress.addEventListener('input', () => {
isDragging = true;
currTimeEl.innerText = formatTime((progress.value / 100) * (audioNode.duration || duration));
});
progress.addEventListener('change', () => {
audioNode.currentTime = (progress.value / 100) * (audioNode.duration || duration);
isDragging = false;
});
return container;
}
Apply Changes