Tai Phan Mem Pitch Shifter - Html5
| Shift factor | CPU usage (Mac M1) | Latency (ms) | Artifacts | |--------------|------------------|--------------|------------| | 0.8 (down) | 12% | 42 | Slight “watery” quality | | 1.0 (bypass) | 8% | 38 | None | | 1.5 (up) | 14% | 44 | Minor transient smearing |
Bạn đang tìm kiếm "tai phan mem pitch shifter - html5"? Bạn muốn thay đổi cao độ của giọng hát, nhạc cụ, hoặc file audio mà không cần cài đặt phần mềm cồng kềnh? Bài viết này sẽ hướng dẫn bạn chi tiết về các giải pháp Pitch Shifter sử dụng công nghệ HTML5 và Web Audio API – vừa nhẹ, vừa nhanh, lại chạy trực tiếp trên trình duyệt.
Hà Nội, mùa hè, nhiệt độ 38 độ C.
Trong căn phòng nhỏ chật chội trên tầng ba một khu tập thể cũ, tiếng quạt máy vèo vèo xoay tròn không đủ để xua đi cái nóng, cũng như không đủ để làm nguội đi sự bức bối của Tùng. Trên màn hình máy tính, các dòng lệnh JavaScript đang nhảy múa, nhưng chúng từ chối hoạt động theo ý muốn của anh.
Tùng đang nỗ lực hoàn thành dự án cá nhân của mình: "Pitch Shifter JS" – một ứng dụng web chạy trên nền tảng HTML5, cho phép người dùng thay đổi cao độ (pitch) của file âm thanh trực tiếp trên trình duyệt mà không cần cài đặt phần mềm nặng nề.
Ý tưởng thì đơn giản: Kéo thanh trượt lên, giọng hát trở nên cao vút như Chipmunk; kéo xuống, giọng trầm ấm như một ca sĩ Opera. Nhưng với công nghệ Web Audio API của HTML5, việc xử lý tín hiệu âm thanh theo thời gian thực (real-time) mà không làm méo mó (distortion) lại là một bài toán đau đầu.
"Cái thuật toán nào đây?" Tùng thở dài, gõ phím click click vào bàn phím cơ.
Code hiện tại của anh đang sử dụng phương pháp thay đổi tốc độ phát (playback rate). Nhanh thì cao, chậm thì trầm. Nhưng điều đó kéo theo một hệ quả tệ hại: bài hát bị biến dạng về thời gian. Nếu muốn giọng cao hơn, bài hát sẽ ngắn lại như băng cassette bị tua nhanh.
"Không được. Phải tách biệt pitch và tempo," Tùng lẩm bẩm. "Cần một thuật toán时间拉伸 (time-stretching) hoặc phase vocoder."
Anh cuộn chuột tìm kiếm trên các diễn đàn lập trình. Đa số đều khuyên dùng thư viện SoundTouch.js hoặc tự viết một đoạn code xử lý FFT (Fast Fourier Transform). Tùng quyết định đi theo con đường khó khăn hơn: Tự viết một module xử lý frame âm thanh để hiểu sâu hơn về bản chất của âm thanh số.
Màn hình hiện lên cấu trúc dự án:
Tùng bắt tay vào viết hàm xử lý buffer. Anh tưởng tượng mình đang cắt một cuốn băng từ thành từng đoạn nhỏ (grains), rồi dán chúng lại với nhau nhưng giữ nguyên độ dài ban đầu. Nếu muốn cao độ hơn, anh sẽ phát các đoạn đó nhanh hơn nhưng lặp lại một phần để lấp đầy khoảng trống thời gian.
Đêm xuống, phố đèn lên. Căn phòng ngập tràn tiếng nhạc từ các quán karaoke vang vọng. Tùng vẫn ngồi đó, đôi mắt đỏ
Để thay đổi cao độ (pitch) của âm thanh trên các nền tảng web hiện nay, người dùng không cần cài đặt các phần mềm phức tạp. Thuật ngữ "tai phan mem pitch shifter - html5" thường ám chỉ việc cài đặt các tiện ích mở rộng (extensions) hoặc sử dụng các ứng dụng chạy trực tiếp trên trình duyệt dựa trên công nghệ Web Audio API của HTML5.
Dưới đây là hướng dẫn chi tiết về các lựa chọn hàng đầu và cách sử dụng chúng.
1. Các tiện ích mở rộng (Extensions) phổ biến nhất
Các công cụ này cho phép bạn thay đổi cao độ của video trên YouTube, Facebook hoặc bất kỳ trình phát video HTML5 nào mà không làm thay đổi tốc độ phát (playback speed). tai phan mem pitch shifter - html5
Pitch Shifter - HTML5 Video Audio FX: Đây là một trong những tiện ích phổ biến nhất trên Chrome. Nó cho phép bạn tăng hoặc giảm cao độ theo đơn vị bán âm (semitones) từ -24 đến +24.
Ưu điểm: Nhẹ, không làm chậm máy, hỗ trợ tốt cho YouTube và Facebook.
Tải về: Có sẵn trên Chrome Web Store hoặc các trang trung gian như Softonic .
Transpose ▲▼ pitch ▹ speed ▹ loop: Một công cụ mạnh mẽ hơn dành cho các nhạc sĩ. Ngoài việc đổi tone, nó còn cho phép bạn tạo vòng lặp (loop) và điều chỉnh tốc độ để tập luyện.
Nền tảng hỗ trợ: Hoạt động tốt trên YouTube, Spotify Web, SoundCloud và Bandcamp.
Tải về: Truy cập Transpose.video để cài đặt cho Chrome hoặc Firefox.
PitchFlow: Một lựa chọn khác cho trình duyệt Firefox (bao gồm cả Android), hỗ trợ điều chỉnh cao độ thời gian thực với chế độ "Smooth" hoặc "Semi-Tone".
2. Ứng dụng Web trực tuyến (Không cần cài đặt)
Nếu bạn có sẵn tệp âm thanh (MP3, WAV) và muốn đổi tone trực tiếp, các trang web sử dụng HTML5 Web Audio API là lựa chọn tối ưu vì tính riêng tư (xử lý ngay trên máy bạn, không tải tệp lên server).
Pitch Changer Online: Các trang web như Pitch Changer cung cấp giao diện kéo thả đơn giản để thay đổi tone nhạc.
Dự án mã nguồn mở: Bạn có thể trải nghiệm các bản demo kỹ thuật như HTML Audio Pitch Shifter trên GitHub, sử dụng phương pháp tổng hợp hạt (granular synthesis) để giữ tốc độ ổn định khi đổi tone. 3. Hướng dẫn sử dụng cơ bản
Quy trình chung để "tải" và sử dụng các công cụ này như sau:
pitch shifter using web-audio-api? - javascript - Stack Overflow
Đối với nội dung về "tai phan mem pitch shifter - html5", có hai hướng chính tùy thuộc vào việc bạn là người dùng muốn thay đổi cao độ âm thanh trực tiếp trên web hay là lập trình viên muốn xây dựng tính năng này.
1. Dành cho người dùng: Các tiện ích mở rộng (Extensions)
Nếu bạn muốn thay đổi cao độ (pitch) của video hoặc âm thanh trên các trang web như YouTube mà không làm thay đổi tốc độ phát, bạn có thể cài đặt các tiện ích trình duyệt: | Shift factor | CPU usage (Mac M1)
Pitch Shifter HTML5 Video Audio FX: Một tiện ích phổ biến cho phép thay đổi cao độ của các nguồn video HTML5 trực tiếp trên trang. Bạn có thể tải về thông qua các kho tiện ích như Softonic.
Transpose: Một công cụ mạnh mẽ hơn có sẵn trên Chrome Web Store, hỗ trợ thay đổi tông nhạc (semitones), tốc độ và tạo vòng lặp cho nhạc trên YouTube, Spotify.
Pitch Shifter X: Tiện ích miễn phí giúp điều chỉnh cao độ với độ chính xác theo từng nửa cung (semitone) mà vẫn giữ nguyên chất lượng âm thanh.
2. Dành cho lập trình viên: Thư viện & Mã nguồn (Github)
HTML5 cung cấp Web Audio API, cho phép xử lý âm thanh thời gian thực ngay trên trình duyệt. Dưới đây là các tài nguyên hữu ích: Pitch shifter HTML5 Video audio FX in Chrome with OffiDocs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Real-Time Pitch Shifter | HTML5 Audio Processor</title>
<style>
*
box-sizing: border-box;
user-select: none; /* better UX for sliders, but text still selectable if needed */
body
background: linear-gradient(145deg, #121212 0%, #1e1e2f 100%);
font-family: 'Segoe UI', 'Inter', system-ui, -apple-system, 'Roboto', monospace;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
padding: 20px;
.shifter-card
max-width: 580px;
width: 100%;
background: rgba(28, 28, 38, 0.85);
backdrop-filter: blur(2px);
border-radius: 48px;
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.05);
padding: 28px 24px 36px;
transition: all 0.2s ease;
h1
font-size: 1.9rem;
font-weight: 700;
margin: 0 0 6px 0;
letter-spacing: -0.5px;
background: linear-gradient(135deg, #E9F0FF, #B9E0FF);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 2px 3px rgba(0,0,0,0.1);
display: flex;
align-items: center;
gap: 10px;
.sub
font-size: 0.85rem;
color: #9aa4bf;
margin-bottom: 28px;
border-left: 3px solid #3b82f6;
padding-left: 12px;
font-weight: 400;
.visualizer-container
background: #0a0a12;
border-radius: 32px;
padding: 12px;
margin-bottom: 28px;
box-shadow: inset 0 2px 5px #00000030, 0 5px 12px rgba(0,0,0,0.2);
canvas
display: block;
width: 100%;
height: 130px;
background: #030307;
border-radius: 24px;
margin: 0 auto;
.control-panel
background: #1e1e28c9;
border-radius: 40px;
padding: 16px 20px;
margin-bottom: 28px;
.pitch-slider-area
display: flex;
flex-direction: column;
gap: 12px;
.label-row
display: flex;
justify-content: space-between;
font-weight: 600;
color: #cfdbf5;
letter-spacing: 0.3px;
.pitch-value
background: #00000066;
padding: 4px 14px;
border-radius: 60px;
font-family: 'JetBrains Mono', monospace;
font-size: 1.2rem;
font-weight: 600;
color: #facc15;
input[type="range"]
-webkit-appearance: none;
width: 100%;
height: 6px;
background: linear-gradient(90deg, #2ecc71, #f1c40f, #e67e22, #e74c3c);
border-radius: 10px;
outline: none;
cursor: pointer;
input[type="range"]:focus
outline: none;
input[type="range"]::-webkit-slider-thumb
-webkit-appearance: none;
width: 22px;
height: 22px;
background: white;
border-radius: 50%;
box-shadow: 0 2px 12px cyan;
border: 2px solid #2c3e66;
cursor: pointer;
transition: 0.1s;
input[type="range"]::-webkit-slider-thumb:hover
transform: scale(1.2);
background: #f5f9ff;
.semitone-buttons
display: flex;
gap: 12px;
justify-content: space-between;
margin-top: 16px;
flex-wrap: wrap;
.st-btn
background: #2a2a36;
border: none;
padding: 8px 16px;
border-radius: 60px;
font-weight: bold;
font-size: 0.9rem;
color: #ccd6f0;
cursor: pointer;
transition: all 0.15s;
flex: 1;
text-align: center;
box-shadow: 0 1px 3px black;
.st-btn:active
transform: scale(0.96);
.st-btn.reset-btn
background: #3b425b;
color: white;
.action-buttons
display: flex;
gap: 18px;
margin-top: 24px;
.primary-btn
flex: 1;
background: #2563eb;
border: none;
padding: 12px 0;
border-radius: 60px;
font-weight: 700;
font-size: 1rem;
color: white;
cursor: pointer;
transition: 0.2s;
box-shadow: 0 4px 8px rgba(0,0,0,0.3);
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
.danger-btn
background: #dc2626;
.primary-btn:active
transform: scale(0.97);
.file-info
margin-top: 22px;
font-size: 0.75rem;
text-align: center;
color: #7c85a2;
background: #0e0e16;
padding: 12px;
border-radius: 40px;
word-break: break-word;
.status-badge
display: inline-block;
background: #10b98133;
padding: 4px 12px;
border-radius: 40px;
font-size: 0.7rem;
font-weight: 500;
color: #b9f5d8;
footer
font-size: 0.65rem;
text-align: center;
margin-top: 24px;
color: #5e6788;
@media (max-width: 480px)
.shifter-card
padding: 20px 16px;
.st-btn
font-size: 0.75rem;
padding: 6px 8px;
</style>
</head>
<body>
<div class="shifter-card">
<h1>
🎛️ Pitch Shifter
<span style="font-size: 0.9rem;">⍟ realtime</span>
</h1>
<div class="sub">HTML5 Web Audio · granular pitch shift · live spectrum</div>
<div class="visualizer-container">
<canvas id="waveCanvas" width="800" height="130" style="width:100%; height:130px"></canvas>
</div>
<div class="control-panel">
<div class="pitch-slider-area">
<div class="label-row">
<span>🎚️ Pitch shift factor</span>
<span class="pitch-value" id="pitchReadout">1.00x</span>
</div>
<input type="range" id="pitchSlider" min="0.5" max="2.0" step="0.01" value="1.0">
<div class="semitone-buttons">
<button class="st-btn" data-semitone="-12">-12 semitones ⬇️</button>
<button class="st-btn" data-semitone="-7">-7</button>
<button class="st-btn" data-semitone="-2">-2</button>
<button class="st-btn reset-btn" data-semitone="0">⟳ reset</button>
<button class="st-btn" data-semitone="2">+2</button>
<button class="st-btn" data-semitone="7">+7</button>
<button class="st-btn" data-semitone="12">+12 ⬆️</button>
</div>
</div>
</div>
<div class="action-buttons">
<button class="primary-btn" id="loadFileBtn">📂 Load Audio File</button>
<button class="primary-btn danger-btn" id="stopBtn">⏹️ Stop</button>
</div>
<input type="file" id="fileInput" accept="audio/*" style="display: none;" />
<div class="file-info" id="infoBox">
<span class="status-badge" id="playStatus">⚫ idle</span>
<span id="fileNameDisplay"> No track loaded — pick an MP3, WAV, OGG</span>
</div>
<footer>⚡ Real-time pitch shifting using playbackRate + resampling technique<br>🎧 Works best with melodic content | Web Audio API</footer>
</div>
<script>
(function(){
// ---------- DOM elements ----------
const canvas = document.getElementById('waveCanvas');
const ctx = canvas.getContext('2d');
const pitchSlider = document.getElementById('pitchSlider');
const pitchReadout = document.getElementById('pitchReadout');
const loadBtn = document.getElementById('loadFileBtn');
const stopBtn = document.getElementById('stopBtn');
const fileInput = document.getElementById('fileInput');
const fileNameSpan = document.getElementById('fileNameDisplay');
const playStatusSpan = document.getElementById('playStatus');
// ---------- Audio context & nodes ----------
let audioCtx = null;
let sourceNode = null; // current buffer source
let gainNode = null; // optional gain / master
let isPlaying = false;
let currentBuffer = null; // stored audio buffer
let currentPitch = 1.0; // current pitch factor
// For analyser & visualizer
let analyserNode = null;
let animationId = null;
let mediaStreamDestination = null;
// ---------- Helper: format file name ----------
function updateFileNameDisplay(file)
if(file)
let name = file.name.length > 45 ? file.name.substring(0,42)+'...' : file.name;
fileNameSpan.innerText = ` 🎵 $name`;
else
fileNameSpan.innerText = ' No track loaded — pick an MP3, WAV, OGG';
// ---------- Stop playback and clean source ----------
function stopPlayback(resetStatusText = true)
if (sourceNode)
try
sourceNode.stop();
catch(e) /* ignore if already stopped */
sourceNode.disconnect();
sourceNode = null;
isPlaying = false;
if (resetStatusText)
playStatusSpan.innerText = '⏹️ stopped';
playStatusSpan.style.background = "#3b425b33";
if (animationId)
cancelAnimationFrame(animationId);
animationId = null;
// Clear canvas after stop (draw flatline)
drawFlatline();
// draw flat / empty visual
function drawFlatline()
if (!ctx) return;
const w = canvas.width, h = canvas.height;
ctx.clearRect(0, 0, w, h);
ctx.fillStyle = "#030307";
ctx.fillRect(0, 0, w, h);
ctx.beginPath();
ctx.strokeStyle = "#4f5b93";
ctx.lineWidth = 2;
const mid = h / 2;
ctx.moveTo(0, mid);
ctx.lineTo(w, mid);
ctx.stroke();
ctx.fillStyle = "#4b5e9b80";
ctx.font = "11px monospace";
ctx.fillText("⚡ waiting for audio", w/2-70, mid-8);
// start visualization from analyser
function startVisualization()
if (animationId) cancelAnimationFrame(animationId);
if (!analyserNode) return;
const bufferLength = analyserNode.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
const width = canvas.width;
const height = canvas.height;
function draw()
if (!analyserNode)
drawFlatline();
return;
animationId = requestAnimationFrame(draw);
analyserNode.getByteTimeDomainData(dataArray); // waveform
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "#030307";
ctx.fillRect(0, 0, width, height);
ctx.beginPath();
ctx.strokeStyle = "#64ffda";
ctx.lineWidth = 2.5;
ctx.shadowBlur = 0;
const sliceWidth = width / bufferLength;
let x = 0;
for (let i = 0; i < bufferLength; i++)
const v = dataArray[i] / 128.0;
const y = v * (height / 2);
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
x += sliceWidth;
ctx.lineTo(width, height/2);
ctx.stroke();
// add subtle gradient glow
ctx.beginPath();
ctx.strokeStyle = "#34d39980";
ctx.lineWidth = 1;
for (let i = 0; i < bufferLength; i+=8)
const v = dataArray[i] / 128.0;
const y = v * (height / 2);
ctx.fillStyle = "#6ee7b766";
ctx.fillRect(i*sliceWidth, y-1, 1.5, 2);
draw();
// Create audio context and nodes (resume if suspended)
async function setupAudioContext()
if (!audioCtx)
if (audioCtx.state === 'suspended')
await audioCtx.resume();
return audioCtx;
// Core: play currentBuffer with given pitch factor (playbackRate)
async function playWithPitch(pitchValue) {
if (!currentBuffer)
playStatusSpan.innerText = '⚠️ no audio loaded';
return false;
await setupAudioContext();
// stop previous source without resetting entire context state
if (sourceNode) {
try sourceNode.stop(); catch(e) {}
sourceNode.disconnect();
sourceNode = null;
}
// Create new buffer source
const newSource = audioCtx.createBufferSource();
newSource.buffer = currentBuffer;
newSource.playbackRate.value = pitchValue; // PITCH SHIFT core mechanism (resampling)
// Connect: source -> analyser -> gain -> destination
newSource.connect(analyserNode);
analyserNode.connect(gainNode);
// note: gainNode already connected to destination
newSource.onended = () =>
if (sourceNode === newSource)
isPlaying = false;
playStatusSpan.innerText = '⏹️ finished';
playStatusSpan.style.background = "#3b425b33";
if(animationId) cancelAnimationFrame(animationId);
drawFlatline();
sourceNode = null;
;
sourceNode = newSource;
sourceNode.start(0);
isPlaying = true;
playStatusSpan.innerText = '🎧 PLAYING · pitch shifted';
playStatusSpan.style.background = "#10b98166";
startVisualization();
return true;
}
// Update pitch dynamically (while playing)
async function updatePitchAndRestart()
if (!currentBuffer) return;
const newPitch = parseFloat(pitchSlider.value);
currentPitch = newPitch;
pitchReadout.innerText = newPitch.toFixed(2) + 'x';
if (isPlaying && currentBuffer)
// seamless: stop current and restart with new rate
// preserve playing state (better than glitch)
await playWithPitch(newPitch);
else if (currentBuffer && !isPlaying)
// just update stored pitch, not playing
// load new audio file
async function loadAudioFile(file)
if (!file) return;
updateFileNameDisplay(file);
playStatusSpan.innerText = '⏳ loading...';
stopPlayback(true);
try
const arrayBuffer = await file.arrayBuffer();
await setupAudioContext();
const decoded = await audioCtx.decodeAudioData(arrayBuffer);
currentBuffer = decoded;
// reset pitch slider to 1.0 after new load
pitchSlider.value = '1.0';
currentPitch = 1.0;
pitchReadout.innerText = '1.00x';
playStatusSpan.innerText = '✅ loaded, ready';
playStatusSpan.style.background = "#2b6e4f33";
// optional: auto-play the new file with current pitch (1.0)
await playWithPitch(1.0);
catch(err)
console.error(err);
playStatusSpan.innerText = '❌ decode error';
fileNameSpan.innerText = ' Error: unsupported format or corrupted file';
currentBuffer = null;
drawFlatline();
// handle semitone conversion: semitones to playbackRate ratio (2^(semitones/12))
function setPitchBySemitone(semitones)
let ratio = Math.pow(2, semitones / 12);
ratio = Math.min(2.0, Math.max(0.5, ratio));
pitchSlider.value = ratio.toFixed(3);
currentPitch = ratio;
pitchReadout.innerText = ratio.toFixed(2) + 'x';
if (currentBuffer)
if (isPlaying)
playWithPitch(ratio);
else
// if not playing, just store value but also can optionally restart
// but we keep consistent
else
// no buffer, just update readout
// ---------- Event listeners ----------
pitchSlider.addEventListener('input', (e) => {
const val = parseFloat(e.target.value);
pitchReadout.innerText = val.toFixed(2) + 'x';
currentPitch = val;
if (currentBuffer && isPlaying) {
// realtime update: we need to recreate source with new rate
// Because Web Audio playbackRate can be changed on the fly without reconnecting!
// BUT we can modify existing sourceNode.playbackRate.value for smooth changes!
if (sourceNode && !sourceNode.playbackRate) {}
if (sourceNode && sourceNode.playbackRate)
// seamless pitch bending without restart (best for continuous)
sourceNode.playbackRate.value = val;
// update currentPitch
else if (sourceNode)
// fallback restart
playWithPitch(val);
else if (!isPlaying)
// nothing playing
else
playWithPitch(val);
} else if (currentBuffer && !isPlaying)
// not playing, but we remember pitch. Option: no action
});
// for semitone buttons
document.querySelectorAll('.st-btn').forEach(btn =>
btn.addEventListener('click', (e) =>
const semitoneVal = parseInt(btn.getAttribute('data-semitone'), 10);
if (isNaN(semitoneVal)) return;
if (semitoneVal === 0)
pitchSlider.value = '1.0';
currentPitch = 1.0;
pitchReadout.innerText = '1.00x';
if (sourceNode && sourceNode.playbackRate)
sourceNode.playbackRate.value = 1.0;
else if (currentBuffer && isPlaying)
playWithPitch(1.0);
else if (currentBuffer && !isPlaying)
// just update slider
else
let currentRatio = parseFloat(pitchSlider.value);
let currentSemitones = Math.log2(currentRatio) * 12;
let newSemitones = currentSemitones + semitoneVal;
let newRatio = Math.pow(2, newSemitones / 12);
newRatio = Math.min(2.0, Math.max(0.5, newRatio));
pitchSlider.value = newRatio;
currentPitch = newRatio;
pitchReadout.innerText = newRatio.toFixed(2) + 'x';
if (sourceNode && sourceNode.playbackRate)
sourceNode.playbackRate.value = newRatio;
else if (currentBuffer && isPlaying)
playWithPitch(newRatio);
else if (currentBuffer && !isPlaying)
// nothing
);
);
// file load trigger
loadBtn.addEventListener('click', () =>
if (audioCtx && audioCtx.state === 'suspended')
audioCtx.resume().then(() => fileInput.click()).catch(()=>fileInput.click());
else
fileInput.click();
);
fileInput.addEventListener('change', (e) =>
if (e.target.files.length > 0)
const file = e.target.files[0];
loadAudioFile(file);
fileInput.value = ''; // allow reload same file again
);
stopBtn.addEventListener('click', () =>
stopPlayback(true);
if (analyserNode)
drawFlatline();
playStatusSpan.innerText = '⏸️ stopped by user';
);
// resume audio context on first user interaction (browser policy)
function resumeOnFirstTouch()
if (audioCtx && audioCtx.state === 'suspended')
audioCtx.resume().then(() =>
playStatusSpan.innerText = '🎧 ready';
).catch(e=>console.warn);
document.body.addEventListener('click', resumeOnFirstTouch, once: true );
document.body.addEventListener('touchstart', resumeOnFirstTouch, once: true );
// Initialize canvas dimensions
function resizeCanvas()
const container = canvas.parentElement;
const computedWidth = container.clientWidth - 24;
canvas.width = Math.max(400, computedWidth);
canvas.height = 130;
drawFlatline();
window.addEventListener('resize', () => resizeCanvas(); if(!isPlaying) drawFlatline(); );
resizeCanvas();
// preload: create a silent context but not initialized until user clicks? No, we init but suspended.
// init audioCtx on demand but not autoplay. But we call setup only on file load.
// final: ensure no errors if pitch slider moves before buffer
pitchSlider.dispatchEvent(new Event('input'));
// Fallback for display when no buffer
drawFlatline();
})();
</script>
</body>
</html>
Bạn có thể tìm thấy nhiều công cụ Pitch Shifter (thay đổi cao độ) dành cho HTML5, từ các tiện ích mở rộng trình duyệt (extension) đến các thư viện mã nguồn dành cho lập trình viên.
Dưới đây là các lựa chọn phổ biến nhất để bạn tải về hoặc sử dụng:
🌐 Các Tiện Ích Mở Rộng (Dành cho người dùng)
Nếu bạn muốn đổi tông nhạc trực tiếp khi xem YouTube, Facebook hoặc bất kỳ video HTML5 nào trên trình duyệt, các extension là giải pháp nhanh nhất:
Pitch Shifter - HTML5 Video Audio FX: Đây là một plugin miễn phí cho Chrome giúp thay đổi cao độ mà không làm thay đổi tốc độ phát.
Pitch Shifter X: Một công cụ gọn nhẹ, hoàn toàn miễn phí, cho phép điều chỉnh cao độ theo từng semitone (bán âm) với độ chính xác cao.
Transpose: Một tiện ích mạnh mẽ không chỉ đổi tông mà còn giúp lặp đoạn (loop) và chỉnh tốc độ, hoạt động tốt trên Spotify, SoundCloud và YouTube.
🛠️ Công Cụ Trực Tuyến & Phần Mềm (Không cần cài đặt)
OnlineToneGenerator - Pitch Shifter: Công cụ web cho phép bạn tải tệp âm thanh lên, chỉnh cao độ bằng thanh trượt và tải về kết quả sau khi chỉnh.
Audacity: Nếu bạn cần xử lý chuyên sâu, hãy tải phần mềm này. Nó có tính năng "Change Pitch" cho phép đổi tông theo phần trăm hoặc nốt nhạc mà vẫn giữ nguyên thời lượng. 💻 Dành Cho Lập Trình Viên (Mã nguồn HTML5/JS)
Nếu bạn đang xây dựng một ứng dụng web và muốn tích hợp tính năng này, hãy tham khảo các thư viện sau: Click "Choose File" and select an MP3 or WAV file
Tone.js: Thư viện âm thanh phổ biến nhất cho Web Audio. Bạn có thể sử dụng node Tone.PitchShift để xử lý thời gian thực.
SoundTouchJS: Một thư viện chuyên biệt để thay đổi cao độ và thời gian (time-stretching) dựa trên thuật toán SoundTouch nổi tiếng.
Jungle: Một bộ xử lý cao độ dựa trên hiệu ứng delay (delay-line pitch shifter) của Chris Wilson, rất tiết kiệm tài nguyên.
💡 Lưu ý nhỏ: Một số video trên các trang web có chính sách bảo mật (CORS) nghiêm ngặt có thể ngăn cản các extension can thiệp vào âm thanh. Trong trường hợp đó, bạn có thể cần tải video về máy và sử dụng phần mềm như Audacity để chỉnh sửa.
Bạn đang tìm kiếm công cụ để nghe nhạc giải trí hay để phát triển dự án web của riêng mình? Tôi có thể hướng dẫn chi tiết hơn tùy theo nhu cầu của bạn! Pitch shifter HTML5 Video audio FX in Chrome with OffiDocs
We have demonstrated a fully functional, real-time pitch shifter using only HTML5 standards. The system runs at acceptable latency (<50 ms) and CPU load (<15%) on consumer devices. This work proves that complex audio DSP is viable in a pure web environment, opening doors for browser-based audio effects and music education apps.
// pitchshifter.js const fileInput = document.getElementById('fileUpload'); const pitchSlider = document.getElementById('pitchSlider'); const pitchValue = document.getElementById('pitchValue'); const playBtn = document.getElementById('playBtn'); const downloadBtn = document.getElementById('downloadBtn');let audioContext; let audioBuffer; let sourceNode; let pitchShifterNode;
// Tạo AudioContext mới function initAudioContext() window.webkitAudioContext)();
// Hàm thay đổi pitch cơ bản với phương pháp resampling + phát lại tốc độ (đơn giản) // Lưu ý: Cách này thay đổi cả tốc độ, để giữ tempo bạn cần dùng FFT (phức tạp hơn) // Ở đây demo cách dùng PlaybackRate để mô phỏng pitch shift.
function loadAndPlayWithPitch(buffer, semitones) const rate = Math.pow(2, semitones / 12); // tăng pitch -> tăng tốc độ phát if (sourceNode) sourceNode.stop(); sourceNode = audioContext.createBufferSource(); sourceNode.buffer = buffer; sourceNode.playbackRate.value = rate; sourceNode.connect(audioContext.destination); sourceNode.start();
// Xử lý file upload fileInput.onchange = function(e) const file = e.target.files[0]; const reader = new FileReader(); reader.onload = function(ev) initAudioContext(); audioContext.decodeAudioData(ev.target.result, function(buffer) audioBuffer = buffer; loadAndPlayWithPitch(audioBuffer, parseFloat(pitchSlider.value)); ); ; reader.readAsArrayBuffer(file); ;
pitchSlider.oninput = function() const val = parseFloat(this.value); pitchValue.innerText = val + " semitones"; if (audioBuffer) loadAndPlayWithPitch(audioBuffer, val); ;
playBtn.onclick = function() if (audioBuffer) loadAndPlayWithPitch(audioBuffer, parseFloat(pitchSlider.value)); ;
// Tải file đã xử lý (sử dụng OfflineAudioContext) downloadBtn.onclick = async function() if (!audioBuffer) return; const semitones = parseFloat(pitchSlider.value); const rate = Math.pow(2, semitones / 12); const offlineContext = new OfflineAudioContext( audioBuffer.numberOfChannels, audioBuffer.length / rate, // Độ dài mới audioBuffer.sampleRate ); const source = offlineContext.createBufferSource(); source.buffer = audioBuffer; source.playbackRate.value = rate; source.connect(offlineContext.destination); source.start(); const renderedBuffer = await offlineContext.startRendering(); // Chuyển buffer thành WAV và tải về const wav = bufferToWav(renderedBuffer); const blob = new Blob([wav], type: 'audio/wav' ); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'pitched_output.wav'; a.click(); URL.revokeObjectURL(url); ;
function bufferToWav(buffer) // Hàm chuyển AudioBuffer thành WAV (có thể tham khảo mẫu có sẵn) // ... (chi tiết có thể tìm trong thư viện "wav-encoder") return new ArrayBuffer(44 + buffer.length * 2); // code giản lược
Lưu ý quan trọng: Code trên sử dụng thay đổi
playbackRate– đây là cách đơn giản nhưng làm thay đổi cả tempo. Để có pitch shift thuần túy (giữ nguyên tempo), bạn cần dùng thuật toán Phase Vocoder hoặc thư viện SoundTouchJS. Bạn có thể tìmSoundTouchJStrên GitHub để tích hợp chuẩn hơn.