Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save EncodeTheCode/90774becda470278ffcc0894db253b1b to your computer and use it in GitHub Desktop.

Select an option

Save EncodeTheCode/90774becda470278ffcc0894db253b1b to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>24 Voice Polyphonic Chiptune Player</title>
<style>
body {
margin: 0;
background: #0d0d12;
color: #f5f5f5;
font-family: Arial, sans-serif;
overflow-x: hidden;
}
.container {
max-width: 1100px;
margin: auto;
padding: 24px;
}
.player {
background: #171722;
border-radius: 16px;
padding: 24px;
box-shadow: 0 0 30px rgba(0,0,0,0.4);
}
h1 {
margin-top: 0;
font-size: 32px;
}
.drop-zone {
border: 2px dashed #666;
border-radius: 16px;
padding: 40px;
text-align: center;
transition: 0.25s;
margin-bottom: 20px;
background: rgba(255,255,255,0.02);
}
.drop-zone.dragover {
border-color: #66d9ef;
background: rgba(102,217,239,0.08);
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-top: 18px;
}
button {
border: none;
background: #66d9ef;
color: black;
padding: 12px 18px;
border-radius: 10px;
font-weight: bold;
cursor: pointer;
transition: 0.2s;
}
button:hover {
transform: scale(1.05);
}
button:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.sliders {
margin-top: 24px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 20px;
}
.slider-group {
background: rgba(255,255,255,0.03);
padding: 16px;
border-radius: 12px;
}
label {
display: block;
margin-bottom: 8px;
font-size: 14px;
}
input[type="range"] {
width: 100%;
}
canvas {
width: 100%;
height: 120px;
background: black;
border-radius: 12px;
margin-top: 22px;
}
.status {
margin-top: 12px;
color: #a8a8a8;
}
.file-name {
margin-top: 12px;
font-weight: bold;
color: #66d9ef;
}
.footer {
margin-top: 20px;
opacity: 0.6;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<div class="player">
<h1>24 Voice Polyphonic Chiptune Converter</h1>
<div class="drop-zone" id="dropZone">
Drag & Drop MP3 / WAV Here<br />
<small>Realtime conversion into classic early 2000s Sony-style polyphonic ringtone tone synthesis</small>
</div>
<input type="file" id="fileInput" accept="audio/*" hidden />
<div class="controls">
<button id="browseBtn">Browse</button>
<button id="playBtn" disabled>Play</button>
<button id="pauseBtn" disabled>Pause</button>
<button id="stopBtn" disabled>Stop</button>
<button id="saveBtn" disabled>Save Polyphonic MP3</button>
</div>
<div class="sliders">
<div class="slider-group">
<label>Warmth</label>
<input type="range" id="warmthSlider" min="0" max="2" step="0.01" value="1.2" />
</div>
<div class="slider-group">
<label>Tone Body</label>
<input type="range" id="bodySlider" min="0" max="2" step="0.01" value="1.1" />
</div>
<div class="slider-group">
<label>Stereo Width</label>
<input type="range" id="stereoSlider" min="0" max="1" step="0.01" value="0.2" />
</div>
<div class="slider-group">
<label>Jitter Reduction</label>
<input type="range" id="jitterSlider" min="0" max="1" step="0.01" value="0.92" />
</div>
<div class="slider-group">
<label>Dither Removal</label>
<input type="range" id="ditherSlider" min="0" max="1" step="0.01" value="0.88" />
</div>
<div class="slider-group">
<label>Harmonic Smoothness</label>
<input type="range" id="smoothSlider" min="0" max="1" step="0.01" value="0.82" />
</div>
<div class="slider-group">
<label>Polyphonic Resonance</label>
<input type="range" id="resonanceSlider" min="0" max="2" step="0.01" value="1.0" />
</div>
<div class="slider-group">
<label>Speaker Emulation</label>
<input type="range" id="speakerSlider" min="0" max="1" step="0.01" value="0.38" />
</div>
<div class="slider-group">
<label><input type="checkbox" id="enableJitter" checked /> Enable Jitter Cleanup</label>
</div>
<div class="slider-group">
<label><input type="checkbox" id="enableDither" checked /> Enable Dither Cleanup</label>
</div>
<div class="slider-group">
<label><input type="checkbox" id="enableStereo" checked /> Enable Stereo Widening</label>
</div>
<div class="slider-group">
<label><input type="checkbox" id="enableSpeaker" checked /> Enable Sony Speaker Emulation</label>
</div>
<div class="slider-group">
<label>Bit Crush Depth</label>
<input type="range" id="bitDepth" min="2" max="12" value="5" />
</div>
<div class="slider-group">
<label>Sample Rate Reduction</label>
<input type="range" id="sampleRate" min="4000" max="22050" value="9000" />
</div>
<div class="slider-group">
<label>Polyphony Voices</label>
<input type="range" id="polyphony" min="4" max="24" value="24" />
</div>
<div class="slider-group">
<label>Delay / Resonance</label>
<input type="range" id="delayMix" min="0" max="1" step="0.01" value="0.24" />
</div>
</div>
<canvas id="visualizer"></canvas>
<div class="file-name" id="fileName">No file loaded</div>
<div class="status" id="status">Idle</div>
<div class="footer">
Engine: Web Audio API | 24 Voice Polyphonic Synth Emulation | Memory Optimized Streaming
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/lamejs@1.2.1/lame.min.js"></script>
<script>
const audioCtx = new (window.AudioContext || window.webkitAudioContext)({ latencyHint: 'interactive' });
const dropZone = document.getElementById('dropZone');
const fileInput = document.getElementById('fileInput');
const browseBtn = document.getElementById('browseBtn');
const playBtn = document.getElementById('playBtn');
const pauseBtn = document.getElementById('pauseBtn');
const stopBtn = document.getElementById('stopBtn');
const saveBtn = document.getElementById('saveBtn');
const fileName = document.getElementById('fileName');
const statusEl = document.getElementById('status');
const bitDepthSlider = document.getElementById('bitDepth');
const sampleRateSlider = document.getElementById('sampleRate');
const polyphonySlider = document.getElementById('polyphony');
const delayMixSlider = document.getElementById('delayMix');
const warmthSlider = document.getElementById('warmthSlider');
const bodySlider = document.getElementById('bodySlider');
const stereoSlider = document.getElementById('stereoSlider');
const jitterSlider = document.getElementById('jitterSlider');
const ditherSlider = document.getElementById('ditherSlider');
const smoothSlider = document.getElementById('smoothSlider');
const resonanceSlider = document.getElementById('resonanceSlider');
const speakerSlider = document.getElementById('speakerSlider');
const enableJitter = document.getElementById('enableJitter');
const enableDither = document.getElementById('enableDither');
const enableStereo = document.getElementById('enableStereo');
const enableSpeaker = document.getElementById('enableSpeaker');
const canvas = document.getElementById('visualizer');
const ctx = canvas.getContext('2d');
let sourceNode;
let audioBuffer;
let analyser;
let processor;
let delayNode;
let feedbackNode;
let lowpass;
let mediaDest;
let mediaRecorder;
let recordedChunks = [];
let isPaused = false;
let startTime = 0;
let pauseOffset = 0;
let animationId;
const VOICE_COUNT = 24;
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
function setStatus(msg) {
statusEl.textContent = msg;
}
browseBtn.onclick = () => fileInput.click();
fileInput.onchange = e => {
const file = e.target.files[0];
if (file) loadFile(file);
};
dropZone.addEventListener('dragover', e => {
e.preventDefault();
dropZone.classList.add('dragover');
});
dropZone.addEventListener('dragleave', () => {
dropZone.classList.remove('dragover');
});
dropZone.addEventListener('drop', e => {
e.preventDefault();
dropZone.classList.remove('dragover');
const file = e.dataTransfer.files[0];
if (file) loadFile(file);
});
async function loadFile(file) {
setStatus('Loading and preparing realtime polyphonic synthesis...');
const arrayBuffer = await file.arrayBuffer();
audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
fileName.textContent = file.name;
playBtn.disabled = false;
stopBtn.disabled = false;
saveBtn.disabled = false;
setStatus('Ready');
}
function createChiptuneProcessor() {
processor = audioCtx.createScriptProcessor(4096, 2, 2);
let sampleHold = 0;
let sampleCounter = 0;
const phaseMemory = [];
for (let i = 0; i < VOICE_COUNT; i++) {
phaseMemory.push({
fundamental: 0,
harmonicA: 0,
harmonicB: 0,
shimmer: 0
});
}
let smoothEnvelope = 0;
let toneBody = 0;
let warmth = 0;
let stereoWidth = 0;
processor.onaudioprocess = e => {
const inputL = e.inputBuffer.getChannelData(0);
const outputL = e.outputBuffer.getChannelData(0);
const outputR = e.outputBuffer.getChannelData(1);
const bits = parseInt(bitDepthSlider.value);
const norm = Math.pow(2, bits - 1);
const reduction = parseInt(sampleRateSlider.value);
const sampleStep = audioCtx.sampleRate / reduction;
const polyphony = parseInt(polyphonySlider.value);
for (let i = 0; i < inputL.length; i++) {
sampleCounter++;
if (sampleCounter >= sampleStep) {
sampleCounter = 0;
sampleHold = inputL[i];
}
let crushed = Math.round(sampleHold * norm) / norm;
const jitterReduction = parseFloat(jitterSlider.value);
const ditherReduction = parseFloat(ditherSlider.value);
const smoothness = parseFloat(smoothSlider.value);
if (enableJitter.checked) {
crushed = crushed * jitterReduction + sampleHold * (1 - jitterReduction);
}
if (enableDither.checked) {
crushed *= (1 - (0.015 * ditherReduction));
}
crushed = crushed * smoothness + sampleHold * (1 - smoothness);
const amplitude = Math.abs(crushed);
smoothEnvelope = smoothEnvelope * 0.992 + amplitude * 0.008;
toneBody = toneBody * 0.985 + amplitude * 0.015;
warmth = warmth * 0.978 + amplitude * 0.022;
stereoWidth = stereoWidth * 0.995 + amplitude * 0.005;
let polyphonicMix = 0;
for (let v = 0; v < polyphony; v++) {
const phases = phaseMemory[v];
const spread = 1 + (v * 0.0025);
const detune = Math.sin(v * 0.7) * 0.35;
const baseFreq = 110 * spread + detune;
const harmonicFreq = 220 * spread + detune;
const upperFreq = 440 * spread + detune;
const shimmerFreq = 1760 * spread;
phases.fundamental += (2 * Math.PI * baseFreq) / audioCtx.sampleRate;
phases.harmonicA += (2 * Math.PI * harmonicFreq) / audioCtx.sampleRate;
phases.harmonicB += (2 * Math.PI * upperFreq) / audioCtx.sampleRate;
phases.shimmer += (2 * Math.PI * shimmerFreq) / audioCtx.sampleRate;
const fundamentalTone = (
Math.sin(phases.fundamental) +
Math.sin(phases.fundamental * 0.5) * 0.22
) * smoothEnvelope;
const harmonicTone = (
Math.sin(phases.harmonicA) +
Math.sin(phases.harmonicA * 2) * 0.08
) * toneBody;
const upperTone = Math.sin(phases.harmonicB) * warmth;
const shimmerTone = Math.sin(phases.shimmer) * stereoWidth * 0.06;
const warmthAmount = parseFloat(warmthSlider.value);
const bodyAmount = parseFloat(bodySlider.value);
const resonanceAmount = parseFloat(resonanceSlider.value);
const sonyVoice = (
fundamentalTone * (0.58 * bodyAmount) +
harmonicTone * (0.27 * warmthAmount) +
upperTone * (0.11 * resonanceAmount) +
shimmerTone * 0.04
);
polyphonicMix += sonyVoice / Math.pow(v + 1, 0.42);
}
polyphonicMix /= (polyphony * 0.16);
const rounded = Math.tanh(polyphonicMix * 1.35);
let sonyColor = (
rounded * 0.88 +
Math.sin(rounded * 1.2) * 0.08 +
crushed * 0.04
);
if (enableSpeaker.checked) {
const speakerAmount = parseFloat(speakerSlider.value);
sonyColor = sonyColor * (1 - speakerAmount * 0.12) + Math.tanh(sonyColor * 1.5) * (speakerAmount * 0.12);
}
if (enableStereo.checked) {
const stereoAmount = parseFloat(stereoSlider.value);
outputL[i] = sonyColor * (1 + stereoAmount * 0.03);
outputR[i] = sonyColor * (1 - stereoAmount * 0.03);
} else {
}
outputL[i] = sonyColor;
outputR[i] = sonyColor;
}
};
return processor;
}
function setupAudioGraph() {
analyser = audioCtx.createAnalyser();
analyser.fftSize = 2048;
lowpass = audioCtx.createBiquadFilter();
lowpass.type = 'lowpass';
lowpass.frequency.value = 7200;
delayNode = audioCtx.createDelay();
delayNode.delayTime.value = 0.08;
feedbackNode = audioCtx.createGain();
feedbackNode.gain.value = delayMixSlider.value;
mediaDest = audioCtx.createMediaStreamDestination();
processor = createChiptuneProcessor();
processor.connect(lowpass);
lowpass.connect(delayNode);
delayNode.connect(feedbackNode);
feedbackNode.connect(delayNode);
delayNode.connect(analyser);
analyser.connect(audioCtx.destination);
analyser.connect(mediaDest);
}
function cleanup() {
if (sourceNode) {
try { sourceNode.stop(); } catch (e) {}
sourceNode.disconnect();
}
if (processor) processor.disconnect();
if (delayNode) delayNode.disconnect();
if (feedbackNode) feedbackNode.disconnect();
if (lowpass) lowpass.disconnect();
cancelAnimationFrame(animationId);
}
playBtn.onclick = async () => {
if (!audioBuffer) return;
cleanup();
setupAudioGraph();
await audioCtx.resume();
sourceNode = audioCtx.createBufferSource();
sourceNode.buffer = audioBuffer;
sourceNode.connect(processor);
setStatus('Realtime 24 voice polyphonic rendering...');
recordedChunks = [];
mediaRecorder = new MediaRecorder(mediaDest.stream);
mediaRecorder.ondataavailable = e => {
if (e.data.size > 0) recordedChunks.push(e.data);
};
mediaRecorder.start();
const offset = isPaused ? pauseOffset : 0;
startTime = audioCtx.currentTime - offset;
sourceNode.start(0, offset);
playBtn.disabled = true;
pauseBtn.disabled = false;
isPaused = false;
visualize();
sourceNode.onended = () => {
if (!isPaused) {
stopPlayback();
}
};
};
pauseBtn.onclick = () => {
if (!sourceNode) return;
pauseOffset = audioCtx.currentTime - startTime;
isPaused = true;
cleanup();
playBtn.disabled = false;
setStatus('Paused');
};
stopBtn.onclick = stopPlayback;
function stopPlayback() {
cleanup();
pauseOffset = 0;
isPaused = false;
playBtn.disabled = false;
pauseBtn.disabled = true;
setStatus('Stopped');
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
}
}
saveBtn.onclick = async () => {
if (!recordedChunks.length) {
alert('Play the track fully before exporting.');
return;
}
setStatus('Encoding MP3...');
const blob = new Blob(recordedChunks, { type: 'audio/webm' });
const audio = await blob.arrayBuffer();
const decoded = await audioCtx.decodeAudioData(audio);
const samples = decoded.getChannelData(0);
const mp3encoder = new lamejs.Mp3Encoder(1, decoded.sampleRate, 128);
const sampleBlockSize = 1152;
let mp3Data = [];
for (let i = 0; i < samples.length; i += sampleBlockSize) {
const sampleChunk = samples.subarray(i, i + sampleBlockSize);
const int16 = new Int16Array(sampleChunk.length);
for (let j = 0; j < sampleChunk.length; j++) {
int16[j] = sampleChunk[j] * 32767;
}
const mp3buf = mp3encoder.encodeBuffer(int16);
if (mp3buf.length > 0) {
mp3Data.push(mp3buf);
}
}
const end = mp3encoder.flush();
if (end.length > 0) {
mp3Data.push(end);
}
const mp3Blob = new Blob(mp3Data, { type: 'audio/mp3' });
const url = URL.createObjectURL(mp3Blob);
const a = document.createElement('a');
a.href = url;
a.download = 'polyphonic_ringtone.mp3';
a.click();
setStatus('MP3 exported');
};
function visualize() {
const bufferLength = analyser.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
function draw() {
animationId = requestAnimationFrame(draw);
analyser.getByteFrequencyData(dataArray);
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const barWidth = (canvas.width / bufferLength) * 2.5;
let x = 0;
for (let i = 0; i < bufferLength; i++) {
const barHeight = dataArray[i] / 2;
ctx.fillStyle = `rgb(${barHeight + 80},255,180)`;
ctx.fillRect(x, canvas.height - barHeight, barWidth, barHeight);
x += barWidth + 1;
}
}
draw();
}
delayMixSlider.oninput = () => {
if (feedbackNode) {
feedbackNode.gain.value = delayMixSlider.value;
}
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment