You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, aspectRatioStr = "1.85", tempo = 240, numNotes = 16) {
// 1. Calculate dimensions for a 1.85:1 aspect ratio center crop.
const targetRatio = aspectRatioStr.includes(':')
? parseFloat(aspectRatioStr.split(':')[0]) / parseFloat(aspectRatioStr.split(':')[1])
: parseFloat(aspectRatioStr);
let sWidth = originalImg.width;
let sHeight = originalImg.height;
let sX = 0;
let sY = 0;
if (sWidth / sHeight > targetRatio) {
// Image is wider than target ratio
const newWidth = sHeight * targetRatio;
sX = (sWidth - newWidth) / 2;
sWidth = newWidth;
} else {
// Image is taller than target ratio
const newHeight = sWidth / targetRatio;
sY = (sHeight - newHeight) / 2;
sHeight = newHeight;
}
// 2. Render the cropped image for the ringtone MP3 Cover Art
const canvas = document.createElement('canvas');
canvas.width = 1200; // Fixed high-resolution output width
canvas.height = Math.round(canvas.width / targetRatio);
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(originalImg, sX, sY, sWidth, sHeight, 0, 0, canvas.width, canvas.height);
// 3. Sonify the image (Convert visual data to melodies for the Ringtone)
// Map brightness of horizontal image segment slices to a pleasing Pentatonic scale
const pentatonicScale = [
261.63, // C4
293.66, // D4
329.63, // E4
392.00, // G4
440.00, // A4
523.25, // C5
587.33, // D5
659.25, // E5
783.99, // G5
880.00 // A5
];
// Extract notes
const freqs = [];
const sliceWidth = Math.floor(canvas.width / numNotes);
for (let i = 0; i < numNotes; i++) {
const sliceData = ctx.getImageData(i * sliceWidth, 0, sliceWidth, canvas.height).data;
let rSum = 0, gSum = 0, bSum = 0, count = 0;
// Sampling every 16th pixel vertically and horizontally for performance
for (let j = 0; j < sliceData.length; j += 16 * 4) {
rSum += sliceData[j];
gSum += sliceData[j+1];
bSum += sliceData[j+2];
count++;
}
const brightness = (0.299 * (rSum / count)) + (0.587 * (gSum / count)) + (0.114 * (bSum / count));
const noteIndex = Math.min(pentatonicScale.length - 1, Math.floor((brightness / 255) * pentatonicScale.length));
freqs.push(pentatonicScale[noteIndex]);
}
// 4. Generate Raw Audio PCM data
const sampleRate = 44100;
const durationPerNote = 60 / tempo; // e.g., 240 bpm = 0.25 seconds per note
const samplesPerNote = Math.floor(sampleRate * durationPerNote);
const numLoops = 4; // Loop the ringtone a few times so it's longer
const totalSamples = samplesPerNote * numNotes * numLoops;
const pcmData = new Int16Array(totalSamples);
for (let l = 0; l < numLoops; l++) {
let loopStartOffset = l * (samplesPerNote * numNotes);
for (let i = 0; i < numNotes; i++) {
const freq = freqs[i];
const noteStartOffset = loopStartOffset + (i * samplesPerNote);
for (let j = 0; j < samplesPerNote; j++) {
const t = j / sampleRate;
// Attack phase preventing clicks (first 5ms)
const attackSamples = Math.min(220, samplesPerNote * 0.1);
const attack = j < attackSamples ? j / attackSamples : 1;
// Plucky logarithmic decay envelope typical of synth ringtones
const decay = Math.pow(1 - (j / samplesPerNote), 2.5);
// Construct telephone-bell like synth waves
let val = Math.sin(2 * Math.PI * freq * t) * 0.5; // fundamental
val += Math.sin(2 * Math.PI * freq * 2 * t) * 0.25; // 1st overtone
val += Math.sin(2 * Math.PI * freq * 3.5 * t) * 0.15; // inharmonic bell overtone
val += Math.sin(2 * Math.PI * freq * 4 * t) * 0.1; // 2nd overtone
// Final envelope application & conversion to 16-bit PCM integer
pcmData[noteStartOffset + j] = Math.max(-1, Math.min(1, val * attack * decay)) * 32767;
}
}
}
// 5. Load LameJS dynamically to encode to MP3 formatted Ringtone
let lamejsAvailable = false;
try {
await new Promise((resolve, reject) => {
if (window.lamejs) return resolve();
if (document.getElementById('lamejs-script')) {
const timer = setInterval(() => {
if (window.lamejs) { clearInterval(timer); resolve(); }
}, 100);
return;
}
const script = document.createElement('script');
script.id = 'lamejs-script';
script.src = 'https://cdnjs.cloudflare.com/ajax/libs/lamejs/1.2.1/lame.min.js';
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
lamejsAvailable = true;
} catch (e) {
console.warn("lamejs could not be loaded, falling back to WAV");
}
let audioUrl = "";
let fileExtension = "mp3";
if (lamejsAvailable) {
const mp3encoder = new window.lamejs.Mp3Encoder(1, sampleRate, 128);
const mp3Data = [];
const sampleBlockSize = 1152; // Needs to be multiples of 1152
for (let i = 0; i < pcmData.length; i += sampleBlockSize) {
const sampleChunk = pcmData.subarray(i, i + sampleBlockSize);
const mp3buf = mp3encoder.encodeBuffer(sampleChunk);
if (mp3buf.length > 0) mp3Data.push(mp3buf);
}
const lastMp3Buf = mp3encoder.flush();
if (lastMp3Buf.length > 0) mp3Data.push(lastMp3Buf);
const blob = new Blob(mp3Data, { type: 'audio/mp3' });
audioUrl = URL.createObjectURL(blob);
} else {
// Fallback: Generate WAV File from buffer
fileExtension = "wav";
const buffer = new ArrayBuffer(44 + pcmData.length * 2);
const view = new DataView(buffer);
const writeString = (v, offset, str) => {
for (let i = 0; i < str.length; i++) v.setUint8(offset + i, str.charCodeAt(i));
};
writeString(view, 0, 'RIFF');
view.setUint32(4, 36 + pcmData.length * 2, true);
writeString(view, 8, 'WAVE');
writeString(view, 12, 'fmt ');
view.setUint32(16, 16, true);
view.setUint16(20, 1, true); // PCM
view.setUint16(22, 1, true); // Mono
view.setUint32(24, sampleRate, true);
view.setUint32(28, sampleRate * 2, true);
view.setUint16(32, 2, true);
view.setUint16(34, 16, true);
writeString(view, 36, 'data');
view.setUint32(40, pcmData.length * 2, true);
for (let i = 0; i < pcmData.length; i++) {
view.setInt16(44 + (i * 2), pcmData[i], true);
}
const blob = new Blob([view], { type: 'audio/wav' });
audioUrl = URL.createObjectURL(blob);
}
// 6. Construct the UI
const container = document.createElement('div');
container.style.cssText = `
display: flex; flex-direction: column; align-items: center; justify-content: center;
background: #1e1e1e; color: #fff; padding: 32px; border-radius: 16px;
box-shadow: 0 8px 30px rgba(0,0,0,0.6); font-family: 'Segoe UI', system-ui, sans-serif;
max-width: 650px; margin: 20px auto;
`;
const title = document.createElement('h2');
title.innerText = 'Ringtone Mp3 & MP3 Player Cover (1.85:1)';
title.style.cssText = 'margin: 0 0 20px 0; text-align: center; font-weight: 600; color: #e0e0e0;';
const canvasWrapper = document.createElement('div');
canvasWrapper.style.cssText = `
width: 100%; border-radius: 12px; overflow: hidden;
box-shadow: 0 4px 15px rgba(0,0,0,0.5); position: relative; background: #000;
`;
canvas.style.cssText = 'width: 100%; height: auto; display: block; object-fit: contain;';
canvasWrapper.appendChild(canvas);
const playerContainer = document.createElement('div');
playerContainer.style.cssText = 'width: 100%; margin-top: 28px; display: flex; flex-direction: column; gap: 16px;';
const audioPlayer = document.createElement('audio');
audioPlayer.controls = true;
audioPlayer.src = audioUrl;
audioPlayer.style.cssText = 'width: 100%; outline: none; border-radius: 30px; height: 50px;';
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'display: flex; gap: 12px; width: 100%; flex-wrap: wrap;';
const dlButtonCss = `
flex: 1; min-width: 200px; text-align: center; text-decoration: none; padding: 14px 20px;
border-radius: 28px; font-weight: bold; font-size: 15px; cursor: pointer; border: none;
transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s;
`;
const downloadRingtoneBtn = document.createElement('a');
downloadRingtoneBtn.href = audioUrl;
downloadRingtoneBtn.download = `Photo_Ringtone_Audio.${fileExtension}`;
downloadRingtoneBtn.innerText = `↓ Download MP3 Ringtone Music`;
downloadRingtoneBtn.style.cssText = dlButtonCss + 'background: #1ed760; color: #000;';
downloadRingtoneBtn.onmouseover = () => downloadRingtoneBtn.style.opacity = '0.85';
downloadRingtoneBtn.onmouseout = () => downloadRingtoneBtn.style.opacity = '1';
const downloadCoverBtn = document.createElement('a');
downloadCoverBtn.innerText = '↓ Download Cover Art (Ratio 1.85:1)';
downloadCoverBtn.style.cssText = dlButtonCss + 'background: #3a3a3a; color: #fff;';
downloadCoverBtn.onclick = () => {
downloadCoverBtn.href = canvas.toDataURL('image/jpeg', 0.95);
downloadCoverBtn.download = 'Ringtone_Cover_1_85_1.jpg';
};
downloadCoverBtn.onmouseover = () => downloadCoverBtn.style.opacity = '0.85';
downloadCoverBtn.onmouseout = () => downloadCoverBtn.style.opacity = '1';
buttonContainer.appendChild(downloadRingtoneBtn);
buttonContainer.appendChild(downloadCoverBtn);
playerContainer.appendChild(audioPlayer);
playerContainer.appendChild(buttonContainer);
container.appendChild(title);
container.appendChild(canvasWrapper);
container.appendChild(playerContainer);
return container;
}
Apply Changes