Tai Phan Mem Pitch Shifter - Html5 ›
body background: linear-gradient(145deg, #101418 0%, #0b0e14 100%); font-family: 'Segoe UI', 'Inter', system-ui, -apple-system, 'Roboto', sans-serif; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; padding: 24px;
<div class="pitch-area"> <div class="knob-label"> <span>🔽 PITCH SHIFT</span> <span class="pitch-value" id="pitchDisplay">0.00 semitones</span> </div> <input type="range" id="pitchSlider" min="-12" max="12" step="0.1" value="0"> <div class="semitone-buttons"> <button class="st-btn" data-shift="-2">-2 sem</button> <button class="st-btn" data-shift="-1">-1 sem</button> <button class="st-btn reset" data-shift="0">⟳ Reset</button> <button class="st-btn" data-shift="1">+1 sem</button> <button class="st-btn" data-shift="2">+2 sem</button> <button class="st-btn" data-shift="5">+5 sem</button> <button class="st-btn" data-shift="-5">-5 sem</button> </div> <div style="font-size: 0.7rem; text-align: center; margin-top: 12px; color:#6b7280"> ⚡ Pitch factor: <span id="pitchFactorSpan">1.000</span> </div> </div>
// Replace function globally createAndStartSource = patchedCreateAndStartSource.bind(this); tai phan mem pitch shifter - html5
// Helper: convert semitones to playback rate function semitonesToRate(semitones) // pitch shift formula: rate = 2^(semitones/12) return Math.pow(2, semitones / 12);
input[type="range"]::-webkit-slider-thumb -webkit-appearance: none; width: 20px; height: 20px; background: #3b82f6; border-radius: 50%; cursor: pointer; box-shadow: 0 0 8px #3b82f6; border: 2px solid white; body background: linear-gradient(145deg
// Update UI from current semitone value function updatePitchUI(semitones) currentPitchSemitones = semitones; pitchSlider.value = semitones; const sign = semitones >= 0 ? '+' : ''; pitchDisplay.innerText = `$sign$semitones.toFixed(1) semitones`; const rate = semitonesToRate(semitones); pitchFactorSpan.innerText = rate.toFixed(4); // If a source is active and playing, we need to update playbackRate dynamically if (sourceNode && isPlaying && audioContext && audioContext.state === 'running') try sourceNode.playbackRate.value = rate; catch(e) /* ignore if source disconnected */
// Create a new source from current audioBuffer, applying current pitch rate, and start at 'when' (relative to ctx currentTime) // offsetSec: where to start in buffer (seconds) function createAndStartSource(offsetSec) { if (!audioContext || !audioBuffer) return null; // ensure context is running (resume if suspended) if (audioContext.state === 'suspended') audioContext.resume().then(() => createAndStartSource(offsetSec); ).catch(e => console.warn("AudioContext resume failed", e)); return null; // Stop existing source if any if (sourceNode) { try sourceNode.stop(); catch(e) {} sourceNode.disconnect(); sourceNode = null; } font-family: 'Segoe UI'
footer font-size: 0.7rem; text-align: center; margin-top: 2rem; color: #4b556b;