Please bookmark this page to avoid losing your image tool!

MP3 Audio To Phone Ringtone Converter

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
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;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

This tool allows you to create custom phone ringtones from your MP3 or other audio files. Users can upload an audio track, select a specific starting point using a slider, and adjust the total duration of the clip (up to 60 seconds). The tool provides a preview function to listen to the selected segment before exporting it. It is ideal for personalizing mobile devices with favorite song snippets, sound effects, or specific musical highlights.

Leave a Reply

Your email address will not be published. Required fields are marked *