You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, defaultDurationSec = 30, themeColor = "#4CAF50") {
// Parse duration properly to ensure it's a number
defaultDurationSec = parseInt(defaultDurationSec, 10) || 30;
// Main container designed like a modern phone screen for the MP3 player
const phone = document.createElement('div');
phone.style.cssText = `
width: 340px;
min-height: 580px;
background: linear-gradient(145deg, #1e1e1e, #292929);
border-radius: 40px;
padding: 30px 20px;
box-sizing: border-box;
border: 10px solid #111;
box-shadow: 0 15px 35px rgba(0,0,0,0.4), inset 0 0 15px rgba(0,0,0,0.5);
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
overflow: hidden;
margin: 20px auto;
`;
// Screen Top / Title
const header = document.createElement('div');
header.style.cssText = `
text-align: center;
margin-bottom: 25px;
width: 100%;
`;
const title = document.createElement('h2');
title.innerText = "Ringtone Maker";
title.style.cssText = `
margin: 0;
font-size: 22px;
font-weight: 600;
color: ${themeColor};
letter-spacing: 1px;
`;
header.appendChild(title);
phone.appendChild(header);
// Album art visualization using the original image parameter
const albumContainer = document.createElement('div');
albumContainer.style.cssText = `
width: 200px;
height: 200px;
border-radius: 50%;
overflow: hidden;
margin-bottom: 30px;
box-shadow: 0 10px 25px rgba(0,0,0,0.6);
border: 4px solid #333;
display: flex;
justify-content: center;
align-items: center;
background: #000;
position: relative;
`;
// Inner vinyl look
const recordHole = document.createElement('div');
recordHole.style.cssText = `
position: absolute;
width: 30px;
height: 30px;
background: #1e1e1e;
border-radius: 50%;
z-index: 2;
border: 2px solid #333;
`;
albumContainer.appendChild(recordHole);
const imgCanvas = document.createElement('canvas');
imgCanvas.width = 200;
imgCanvas.height = 200;
imgCanvas.style.transition = "transform 0.1s linear";
const ctx = imgCanvas.getContext('2d');
// Draw and center crop the original image as album art
const size = Math.min(originalImg.width, originalImg.height);
const sx = (originalImg.width - size) / 2;
const sy = (originalImg.height - size) / 2;
ctx.drawImage(originalImg, sx, sy, size, size, 0, 0, 200, 200);
albumContainer.appendChild(imgCanvas);
phone.appendChild(albumContainer);
// Audio upload zone
const fileLabel = document.createElement('label');
fileLabel.style.cssText = `
background: #333;
color: #fff;
padding: 12px 25px;
border-radius: 30px;
cursor: pointer;
font-size: 14px;
margin-bottom: 5px;
font-weight: 500;
text-align: center;
transition: background 0.3s;
border: 1px solid #444;
`;
fileLabel.onmouseover = () => fileLabel.style.background = '#444';
fileLabel.onmouseout = () => fileLabel.style.background = '#333';
fileLabel.innerText = "📁 Choose MP3 / Audio";
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'audio/*';
fileInput.style.display = 'none';
fileLabel.appendChild(fileInput);
phone.appendChild(fileLabel);
const statusText = document.createElement('div');
statusText.innerText = "No audio loaded.";
statusText.style.cssText = `
font-size: 12px;
color: #888;
margin-bottom: 20px;
text-align: center;
white-space: pre-wrap;
`;
phone.appendChild(statusText);
// Controls container
const controls = document.createElement('div');
controls.style.cssText = `
width: 100%;
display: flex;
flex-direction: column;
gap: 15px;
opacity: 0.4;
pointer-events: none;
transition: opacity 0.3s;
`;
// Sliders Common Styles
const sliderStyle = `
width: 100%;
cursor: pointer;
margin: 5px 0;
accent-color: ${themeColor};
`;
// Start time slider
const startGroup = document.createElement('div');
const startHeader = document.createElement('div');
startHeader.style.cssText = "display: flex; justify-content: space-between; font-size: 13px; color: #ddd;";
const startLabel = document.createElement('span');
startLabel.innerText = "Start Time";
const startValueLabel = document.createElement('span');
startValueLabel.innerText = "0.0s";
startValueLabel.style.color = themeColor;
startHeader.appendChild(startLabel);
startHeader.appendChild(startValueLabel);
const startSlider = document.createElement('input');
startSlider.type = 'range';
startSlider.min = 0;
startSlider.max = 100;
startSlider.step = 0.1;
startSlider.value = 0;
startSlider.style.cssText = sliderStyle;
startGroup.appendChild(startHeader);
startGroup.appendChild(startSlider);
controls.appendChild(startGroup);
// Duration slider
const durGroup = document.createElement('div');
const durHeader = document.createElement('div');
durHeader.style.cssText = "display: flex; justify-content: space-between; font-size: 13px; color: #ddd;";
const durLabel = document.createElement('span');
durLabel.innerText = "Ringtone Length";
const durValueLabel = document.createElement('span');
durValueLabel.innerText = `${defaultDurationSec}.0s`;
durValueLabel.style.color = themeColor;
durHeader.appendChild(durLabel);
durHeader.appendChild(durValueLabel);
const durSlider = document.createElement('input');
durSlider.type = 'range';
durSlider.min = 1;
durSlider.max = 60;
durSlider.step = 1;
durSlider.value = defaultDurationSec;
durSlider.style.cssText = sliderStyle;
durGroup.appendChild(durHeader);
durGroup.appendChild(durSlider);
controls.appendChild(durGroup);
// Action buttons
const actions = document.createElement('div');
actions.style.cssText = `
display: flex;
justify-content: space-between;
width: 100%;
margin-top: 15px;
gap: 10px;
`;
const btnStyle = `
border: none;
padding: 12px 10px;
border-radius: 12px;
cursor: pointer;
font-size: 13px;
font-weight: 600;
flex: 1;
transition: transform 0.1s, opacity 0.2s;
`;
const playBtn = document.createElement('button');
playBtn.innerText = "▶ Play";
playBtn.style.cssText = btnStyle + `background: #444; color: #fff;`;
const stopBtn = document.createElement('button');
stopBtn.innerText = "■ Stop";
stopBtn.style.cssText = btnStyle + `background: #333; color: #aaa;`;
const saveBtn = document.createElement('button');
saveBtn.innerText = "💾 Make Ringtone";
saveBtn.style.cssText = btnStyle + `background: ${themeColor}; color: #000; font-weight: bold;`;
[playBtn, stopBtn, saveBtn].forEach(btn => {
btn.onmousedown = () => btn.style.transform = "scale(0.95)";
btn.onmouseup = () => btn.style.transform = "scale(1)";
btn.onmouseleave = () => btn.style.transform = "scale(1)";
});
actions.appendChild(playBtn);
actions.appendChild(stopBtn);
actions.appendChild(saveBtn);
controls.appendChild(actions);
phone.appendChild(controls);
// Internal State
let audioContext = null;
let audioBuffer = null;
let currentSource = null;
let startTime = 0;
let duration = defaultDurationSec;
let isPlaying = false;
let rotationAngle = 0;
let animationFrame = null;
// View Updates
const updateUI = () => {
if (!audioBuffer) return;
startTime = parseFloat(startSlider.value);
duration = parseFloat(durSlider.value);
startSlider.max = Math.max(0, audioBuffer.duration - duration);
if (startTime + duration > audioBuffer.duration) {
startTime = Math.max(0, audioBuffer.duration - duration);
startSlider.value = startTime;
}
startValueLabel.innerText = `${startTime.toFixed(1)}s`;
durValueLabel.innerText = `${duration.toFixed(1)}s`;
};
const animateAlbum = () => {
if (isPlaying) {
rotationAngle = (rotationAngle + 1) % 360;
imgCanvas.style.transform = `rotate(${rotationAngle}deg)`;
animationFrame = requestAnimationFrame(animateAlbum);
}
};
// Event Listeners
startSlider.addEventListener('input', updateUI);
durSlider.addEventListener('input', updateUI);
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
statusText.innerText = "Decoding audio... Please wait.";
try {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioContext.state === 'suspended') {
await audioContext.resume();
}
const buffer = await file.arrayBuffer();
audioBuffer = await audioContext.decodeAudioData(buffer);
statusText.innerText = `Track loaded: ${file.name}\nTotal length: ${audioBuffer.duration.toFixed(1)}s`;
statusText.style.color = '#aaa';
controls.style.opacity = '1';
controls.style.pointerEvents = 'auto';
startSlider.max = audioBuffer.duration;
startSlider.value = 0;
durSlider.value = Math.min(defaultDurationSec, audioBuffer.duration);
updateUI();
} catch (err) {
statusText.innerText = "Error decoding audio file.\nMake sure it's a valid audio format.";
statusText.style.color = '#ff6b6b';
console.error(err);
}
});
const stopAudio = () => {
if (currentSource) {
try { currentSource.stop(); } catch(e) {}
currentSource.disconnect();
currentSource = null;
}
isPlaying = false;
if (animationFrame) cancelAnimationFrame(animationFrame);
};
playBtn.addEventListener('click', () => {
if (!audioBuffer || !audioContext) return;
stopAudio();
currentSource = audioContext.createBufferSource();
currentSource.buffer = audioBuffer;
currentSource.connect(audioContext.destination);
currentSource.start(0, startTime, duration);
isPlaying = true;
animateAlbum();
currentSource.onended = () => {
isPlaying = false;
};
});
stopBtn.addEventListener('click', stopAudio);
saveBtn.addEventListener('click', async () => {
if (!audioBuffer) return;
stopAudio();
statusText.innerText = "Processing ringtone...";
saveBtn.disabled = true;
saveBtn.style.opacity = '0.5';
// Brief timeout allows UI to update before heavy synchronous WAV processing block
setTimeout(async () => {
try {
const sampleRate = audioBuffer.sampleRate;
const channels = audioBuffer.numberOfChannels;
const frameCount = Math.ceil(sampleRate * duration);
const OCtx = window.OfflineAudioContext || window.webkitOfflineAudioContext;
const offlineCtx = new OCtx(channels, frameCount, sampleRate);
const source = offlineCtx.createBufferSource();
source.buffer = audioBuffer;
source.connect(offlineCtx.destination);
source.start(0, startTime, duration);
const renderedBuffer = await offlineCtx.startRendering();
const wavBlob = audioBufferToWav(renderedBuffer);
const url = URL.createObjectURL(wavBlob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = 'My_Ringtone.wav';
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
statusText.innerText = `Success! Ringtone downloaded.`;
statusText.style.color = themeColor;
saveBtn.disabled = false;
saveBtn.style.opacity = '1';
}, 100);
} catch (err) {
statusText.innerText = "Error creating ringtone.";
statusText.style.color = '#ff6b6b';
console.error(err);
saveBtn.disabled = false;
saveBtn.style.opacity = '1';
}
}, 50);
});
// Helper: Convert AudioBuffer to downloadable Standard WAV (PCM 16-bit) format
function audioBufferToWav(buffer) {
const numChannels = buffer.numberOfChannels;
const sampleRate = buffer.sampleRate;
const format = 1; // PCM
const bitDepth = 16;
const channels = [];
for (let i = 0; i < numChannels; i++) {
channels.push(buffer.getChannelData(i));
}
const interleaved = new Float32Array(buffer.length * numChannels);
let offset = 0;
for (let i = 0; i < buffer.length; i++) {
for (let ch = 0; ch < numChannels; ch++) {
interleaved[offset++] = channels[ch][i];
}
}
const bufferArray = new ArrayBuffer(44 + interleaved.length * 2);
const view = new DataView(bufferArray);
const writeString = (view, offset, string) => {
for (let i = 0; i < string.length; i++) {
view.setUint8(offset + i, string.charCodeAt(i));
}
};
writeString(view, 0, 'RIFF');
view.setUint32(4, 36 + interleaved.length * 2, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, format, true);
view.setUint16(22, numChannels, true);
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * numChannels * 2, true);
view.setUint16(32, numChannels * 2, true);
view.setUint16(34, bitDepth, true);
writeString(view, 36, 'data');
view.setUint32(40, interleaved.length * 2, true);
let pcmOffset = 44;
for (let i = 0; i < interleaved.length; i++) {
let s = Math.max(-1, Math.min(1, interleaved[i]));
// 16-bit PCM conversion
s = s < 0 ? s * 0x8000 : s * 0x7FFF;
view.setInt16(pcmOffset, s, true);
pcmOffset += 2;
}
return new Blob([view], { type: 'audio/wav' });
}
return phone;
}
Apply Changes