You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, ringtoneTitle = "My Ringtone", durationSec = 30) {
durationSec = Number(durationSec);
// Create main container
const container = document.createElement('div');
container.style.fontFamily = 'system-ui, -apple-system, sans-serif';
container.style.padding = '24px';
container.style.maxWidth = '360px';
container.style.background = '#ffffff';
container.style.border = '1px solid #e0e0e0';
container.style.borderRadius = '16px';
container.style.boxShadow = '0 8px 24px rgba(0,0,0,0.1)';
container.style.textAlign = 'center';
container.style.margin = '0 auto';
container.style.color = '#333';
// Header
const header = document.createElement('h2');
header.textContent = 'MP3 to Phone Ringtone';
header.style.marginTop = '0';
header.style.marginBottom = '20px';
header.style.fontSize = '20px';
container.appendChild(header);
// Process originalImg as album art / cover
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const size = 160;
canvas.width = size;
canvas.height = size;
canvas.style.borderRadius = '12px';
canvas.style.objectFit = 'cover';
canvas.style.boxShadow = '0 6px 16px rgba(0,0,0,0.15)';
canvas.style.marginBottom = '20px';
// Draw cover (crop to center square)
const dim = Math.min(originalImg.width, originalImg.height);
const x = (originalImg.width - dim) / 2;
const y = (originalImg.height - dim) / 2;
if (dim > 0) {
ctx.drawImage(originalImg, x, y, dim, dim, 0, 0, size, size);
}
container.appendChild(canvas);
// Title label
const titleEl = document.createElement('h3');
titleEl.textContent = ringtoneTitle;
titleEl.style.marginTop = '0';
titleEl.style.marginBottom = '20px';
titleEl.style.fontSize = '18px';
titleEl.style.fontWeight = '500';
container.appendChild(titleEl);
// File Upload Area
const fileUploadLabel = document.createElement('label');
fileUploadLabel.style.display = 'inline-block';
fileUploadLabel.style.background = '#28a745';
fileUploadLabel.style.color = '#fff';
fileUploadLabel.style.padding = '10px 20px';
fileUploadLabel.style.borderRadius = '8px';
fileUploadLabel.style.cursor = 'pointer';
fileUploadLabel.style.fontWeight = 'bold';
fileUploadLabel.style.marginBottom = '16px';
fileUploadLabel.style.transition = 'background 0.2s';
fileUploadLabel.onmouseover = () => fileUploadLabel.style.background = '#218838';
fileUploadLabel.onmouseout = () => fileUploadLabel.style.background = '#28a745';
const fileUploadText = document.createElement('span');
fileUploadText.textContent = 'Select MP3 / Audio File';
fileUploadLabel.appendChild(fileUploadText);
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'audio/mpeg, audio/mp3, audio/wav, audio/ogg';
fileInput.style.display = 'none';
fileUploadLabel.appendChild(fileInput);
container.appendChild(fileUploadLabel);
// Trimming & Playback Controls Container
const controlsDiv = document.createElement('div');
controlsDiv.style.display = 'none';
controlsDiv.style.textAlign = 'left';
const durLabel = document.createElement('label');
durLabel.textContent = `Select part (max ${durationSec}s):`;
durLabel.style.display = 'block';
durLabel.style.marginBottom = '8px';
durLabel.style.fontSize = '14px';
durLabel.style.fontWeight = 'bold';
const waveCanvas = document.createElement('canvas');
waveCanvas.width = 312;
waveCanvas.height = 60;
waveCanvas.style.width = '100%';
waveCanvas.style.height = '60px';
waveCanvas.style.marginBottom = '10px';
waveCanvas.style.borderRadius = '6px';
waveCanvas.style.background = '#f8f9fa';
waveCanvas.style.border = '1px solid #dee2e6';
const startSlider = document.createElement('input');
startSlider.type = 'range';
startSlider.min = 0;
startSlider.value = 0;
startSlider.step = '0.1';
startSlider.style.width = '100%';
startSlider.style.marginBottom = '8px';
const timeDisplay = document.createElement('div');
timeDisplay.textContent = '0.0s - 0.0s';
timeDisplay.style.textAlign = 'center';
timeDisplay.style.marginBottom = '16px';
timeDisplay.style.fontSize = '14px';
timeDisplay.style.color = '#555';
controlsDiv.appendChild(durLabel);
controlsDiv.appendChild(waveCanvas);
controlsDiv.appendChild(startSlider);
controlsDiv.appendChild(timeDisplay);
const btnGroup = document.createElement('div');
btnGroup.style.display = 'flex';
btnGroup.style.justifyContent = 'space-between';
btnGroup.style.gap = '10px';
const previewBtn = document.createElement('button');
previewBtn.textContent = 'Preview';
const downloadBtn = document.createElement('button');
downloadBtn.textContent = 'Download Ringtone';
[previewBtn, downloadBtn].forEach(btn => {
btn.style.flex = '1';
btn.style.padding = '10px';
btn.style.border = 'none';
btn.style.borderRadius = '8px';
btn.style.background = '#007bff';
btn.style.color = '#fff';
btn.style.cursor = 'pointer';
btn.style.fontWeight = 'bold';
btn.style.transition = 'background 0.2s';
});
previewBtn.onmouseover = () => { if(previewBtn.textContent === 'Preview') previewBtn.style.background = '#0069d9'; };
previewBtn.onmouseout = () => { if(previewBtn.textContent === 'Preview') previewBtn.style.background = '#007bff'; };
downloadBtn.onmouseover = () => downloadBtn.style.background = '#0069d9';
downloadBtn.onmouseout = () => downloadBtn.style.background = '#007bff';
btnGroup.appendChild(previewBtn);
btnGroup.appendChild(downloadBtn);
controlsDiv.appendChild(btnGroup);
container.appendChild(controlsDiv);
// Audio & State Handling
let audioCtx;
let audioBuffer = null;
let previewSource = null;
let wavePeaks = [];
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
fileUploadText.textContent = 'Loading Audio...';
const reader = new FileReader();
reader.onload = async (ev) => {
const arrayBuffer = ev.target.result;
try {
if (!audioCtx) {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
}
if (audioCtx.state === 'suspended') {
await audioCtx.resume();
}
audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
fileUploadText.textContent = 'Change Audio File';
// Pre-compute waveform peaks for performance
wavePeaks = [];
const data = audioBuffer.getChannelData(0);
const step = Math.ceil(data.length / waveCanvas.width);
for (let i = 0; i < waveCanvas.width; i++) {
let min = 1.0;
let max = -1.0;
const limit = Math.min((i + 1) * step, data.length);
for (let j = i * step; j < limit; j++) {
const datum = data[j];
if (datum < min) min = datum;
if (datum > max) max = datum;
}
wavePeaks.push({ min, max });
}
controlsDiv.style.display = 'block';
const maxStart = Math.max(0, audioBuffer.duration - durationSec);
startSlider.max = maxStart;
startSlider.value = 0;
updateTimeDisplay();
} catch (err) {
fileUploadText.textContent = 'Select MP3 / Audio File';
alert("Error decoding audio file. Make sure it's a valid audio file format.");
console.error(err);
}
};
reader.readAsArrayBuffer(file);
});
startSlider.addEventListener('input', updateTimeDisplay);
function updateTimeDisplay() {
if (!audioBuffer) return;
const start = parseFloat(startSlider.value);
const end = Math.min(audioBuffer.duration, start + durationSec);
timeDisplay.textContent = `${start.toFixed(1)}s - ${end.toFixed(1)}s`;
// Draw Waveform and Playhead
const wctx = waveCanvas.getContext('2d');
const width = waveCanvas.width;
const height = waveCanvas.height;
wctx.clearRect(0, 0, width, height);
const amp = height / 2;
wctx.fillStyle = '#b3d4ff';
for (let i = 0; i < width; i++) {
if(!wavePeaks[i]) continue;
const { min, max } = wavePeaks[i];
wctx.fillRect(i, (1 + min) * amp, 1, Math.max(1, (max - min) * amp));
}
const startX = (start / audioBuffer.duration) * width;
const endX = ((start + Math.min(audioBuffer.duration, durationSec)) / audioBuffer.duration) * width;
wctx.fillStyle = 'rgba(0, 123, 255, 0.4)';
wctx.fillRect(startX, 0, endX - startX, height);
wctx.fillStyle = '#0056b3';
wctx.fillRect(startX, 0, 2, height);
wctx.fillRect(endX - 2, 0, 2, height);
}
previewBtn.addEventListener('click', async () => {
if (!audioBuffer) return;
if (audioCtx.state === 'suspended') {
await audioCtx.resume();
}
if (previewSource) {
previewSource.stop();
previewBtn.textContent = 'Preview';
previewBtn.style.background = '#007bff';
previewSource = null;
return;
}
previewSource = audioCtx.createBufferSource();
previewSource.buffer = audioBuffer;
previewSource.connect(audioCtx.destination);
const start = parseFloat(startSlider.value);
const playDur = Math.min(audioBuffer.duration - start, durationSec);
previewSource.start(0, start, playDur);
previewBtn.textContent = 'Stop';
previewBtn.style.background = '#dc3545';
previewSource.onended = () => {
previewBtn.textContent = 'Preview';
previewBtn.style.background = '#007bff';
previewSource = null;
};
});
downloadBtn.addEventListener('click', async () => {
if (!audioBuffer) return;
downloadBtn.textContent = 'Processing...';
downloadBtn.disabled = true;
setTimeout(async () => {
try {
const start = parseFloat(startSlider.value);
const dur = Math.min(audioBuffer.duration - start, durationSec);
// Use OfflineAudioContext for safe rendering length
const OfflineCtxClass = window.OfflineAudioContext || window.webkitOfflineAudioContext;
const lengthInSamples = Math.max(1, Math.ceil(audioBuffer.sampleRate * dur));
const offlineCtx = new OfflineCtxClass(
audioBuffer.numberOfChannels,
lengthInSamples,
audioBuffer.sampleRate
);
const source = offlineCtx.createBufferSource();
source.buffer = audioBuffer;
source.connect(offlineCtx.destination);
source.start(0, start, dur);
const renderedBuffer = await offlineCtx.startRendering();
const wavBlob = audioBufferToWav(renderedBuffer);
const url = URL.createObjectURL(wavBlob);
const a = document.createElement('a');
a.href = url;
// Keep localized title chars like cyrillic and latin
const fileName = ringtoneTitle.replace(/[^\wа-яА-ЯёЁ\s-]/g, '_').trim() || 'ringtone';
a.download = `${fileName}.wav`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (e) {
console.error(e);
alert("Error exporting ringtone.");
} finally {
downloadBtn.textContent = 'Download Ringtone';
downloadBtn.disabled = false;
}
}, 50);
});
// Helper Function: Convert AudioBuffer to WAV format Native handling
function audioBufferToWav(buffer) {
const numOfChan = buffer.numberOfChannels;
const length = buffer.length * numOfChan * 2 + 44;
const bufferArray = new ArrayBuffer(length);
const view = new DataView(bufferArray);
const channels = [];
let i, sample, offset = 0, pos = 0;
function setUint16(data) { view.setUint16(pos, data, true); pos += 2; }
function setUint32(data) { view.setUint32(pos, data, true); pos += 4; }
function setString(data) {
for (let j = 0; j < data.length; j++) {
view.setUint8(pos++, data.charCodeAt(j));
}
}
// WAV Header writer
setString('RIFF');
setUint32(length - 8);
setString('WAVE');
setString('fmt ');
setUint32(16);
setUint16(1); // PCM
setUint16(numOfChan);
setUint32(buffer.sampleRate);
setUint32(buffer.sampleRate * 2 * numOfChan); // byte rate
setUint16(numOfChan * 2); // block-align
setUint16(16); // 16-bit
setString('data');
setUint32(length - pos - 4);
for (i = 0; i < buffer.numberOfChannels; i++) {
channels.push(buffer.getChannelData(i));
}
// Interleave & Write PCM Data
while (pos < length) {
for (i = 0; i < numOfChan; i++) {
sample = Math.max(-1, Math.min(1, channels[i][offset])); // Hard clamp
sample = (0.5 + sample < 0 ? sample * 32768 : sample * 32767) | 0; // -> 16 bit
view.setInt16(pos, sample, true);
pos += 2;
}
offset++;
}
return new Blob([bufferArray], { type: "audio/wav" });
}
return container;
}
Apply Changes