如何使用JavaScript从麦克风获取音频频率并实现无后端前端吉他和弦调音器
嘿,我之前刚好做过类似的纯前端吉他调音器项目,结合Web Audio API完全能实现你的需求,给你拆解下完整的实现思路和代码片段:
实现纯前端吉他调音器:从麦克风取频到和弦识别的完整方案
一、用JavaScript从麦克风获取音频频率的核心逻辑
前端获取麦克风音频频率完全依赖Web Audio API,这是浏览器原生支持的音频处理标准,步骤清晰且无需后端:
1. 核心步骤拆解
- 请求麦克风权限并获取媒体流(注意:该API仅支持HTTPS或localhost环境)
- 搭建音频处理链路:
AudioContext→ 媒体源节点 →AnalyserNode(频谱分析) - 通过傅里叶变换将音频波形转为频率数据,提取能量最高的有效频率
2. 可运行的基础代码
// 兼容不同浏览器的AudioContext前缀 const AudioContext = window.AudioContext || window.webkitAudioContext; const audioCtx = new AudioContext(); let analyser; let frequencyDataArray; // 初始化麦克风监听 async function initMicrophone() { try { // 请求麦克风音频权限 const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // 创建媒体流源节点 const audioSource = audioCtx.createMediaStreamSource(stream); // 创建分析器节点:FFT大小决定频率精度(2048是平衡性能和精度的常用值) analyser = audioCtx.createAnalyser(); analyser.fftSize = 2048; // 连接音频处理链路 audioSource.connect(analyser); // 创建存储频率数据的数组 frequencyDataArray = new Uint8Array(analyser.frequencyBinCount); // 启动持续频率检测 startFrequencyDetection(); } catch (error) { console.error('麦克风权限获取失败,请检查浏览器设置:', error); } } // 持续检测并计算频率 function startFrequencyDetection() { // 获取当前频谱数据 analyser.getByteFrequencyData(frequencyDataArray); // 找到能量最高的频率索引 const maxEnergyIndex = frequencyDataArray.indexOf(Math.max(...frequencyDataArray)); // 计算实际频率:采样率 × 索引 ÷ FFT大小 const currentFrequency = (maxEnergyIndex * audioCtx.sampleRate) / analyser.fftSize; // 这里可以把频率传给后续的音高匹配/和弦识别逻辑 console.log('当前检测到的核心频率:', currentFrequency.toFixed(2)); // 和浏览器渲染同步,避免卡顿 requestAnimationFrame(startFrequencyDetection); } // 启动麦克风监听 initMicrophone();
二、吉他调音器+和弦识别的具体实现
1. 单弦音准识别:匹配标准吉他音高
首先定义标准吉他空弦的频率映射,然后通过计算频率偏差判断音准:
// 标准吉他空弦频率(从6弦到1弦) const standardGuitarTuning = { 'E2': 82.41, 'A2': 110.00, 'D3': 146.83, 'G3': 196.00, 'B3': 246.94, 'E4': 329.63 }; // 匹配检测到的频率对应的吉他音高,并计算偏差 function matchGuitarNote(frequency) { if (!frequency || frequency < 50) return null; // 过滤低噪音 let closestNote = null; let minDeviation = Infinity; const semitoneRatio = Math.pow(2, 1/12); // 音乐中半音的频率比例 for (const [noteName, standardFreq] of Object.entries(standardGuitarTuning)) { // 计算检测频率与标准音的半音偏差 const deviation = Math.log(frequency / standardFreq) / Math.log(semitoneRatio); const absDeviation = Math.abs(deviation); if (absDeviation < minDeviation) { minDeviation = absDeviation; closestNote = { note: noteName, deviation: deviation.toFixed(2), // 正数=偏高,负数=偏低 isInTune: absDeviation < 0.1 // 偏差小于0.1半音视为音准合格 }; } } return closestNote; }
你可以在startFrequencyDetection里调用这个函数,把结果实时渲染到UI上(比如用箭头显示偏高/偏低,用颜色标记音准状态)。
2. 和弦识别:匹配多音组合
和弦识别需要两步:收集有效频率对应的音名,再和预定义的和弦库匹配:
// 将频率转换为标准音名(如C3、E4) function frequencyToNoteName(frequency) { const A4_FREQUENCY = 440; // 国际标准音A4的频率 const semitonesFromA4 = Math.round(12 * Math.log2(frequency / A4_FREQUENCY)); const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; const noteIndex = (semitonesFromA4 + 9) % 12; // A4对应索引9 const octave = 4 + Math.floor((semitonesFromA4 + 9) / 12); return `${noteNames[noteIndex]}${octave}`; } // 预定义常见吉他和弦的音名集合(可根据需求扩展) const guitarChordLibrary = [ { name: 'C大调', notes: ['C3', 'E3', 'G3', 'C4', 'E4', 'G4'] }, { name: 'G大调', notes: ['G2', 'B2', 'D3', 'G3', 'B3', 'D4'] }, { name: 'D大调', notes: ['D2', 'F#2', 'A2', 'D3', 'F#3', 'A3'] }, { name: 'Am小调', notes: ['A2', 'C3', 'E3', 'A3', 'C4', 'E4'] } ]; // 检测当前弹奏的和弦 function detectGuitarChord(detectedNoteNames) { for (const chord of guitarChordLibrary) { // 至少匹配3个核心音视为有效和弦 const matchedNoteCount = chord.notes.filter(note => detectedNoteNames.includes(note)).length; if (matchedNoteCount >= 3) { return chord.name; } } return '未知和弦'; }
然后修改startFrequencyDetection,添加频率收集和和弦检测逻辑:
let detectedNotes = new Set(); let chordDetectionTimer; function startFrequencyDetection() { analyser.getByteFrequencyData(frequencyDataArray); const sampleRate = audioCtx.sampleRate; const frequencyBinCount = analyser.frequencyBinCount; // 收集能量较高的前3个频率(过滤噪音) const topFrequencies = []; for (let i = 0; i < frequencyBinCount; i++) { if (frequencyDataArray[i] > 100) { // 能量阈值可根据环境调整 const freq = (i * sampleRate) / analyser.fftSize; topFrequencies.push({ freq, energy: frequencyDataArray[i] }); } } // 转换为音名并去重 topFrequencies.sort((a, b) => b.energy - a.energy).slice(0,3).forEach(item => { const noteName = frequencyToNoteName(item.freq); detectedNotes.add(noteName); }); // 每隔1秒检测一次和弦(避免频繁触发) if (!chordDetectionTimer) { chordDetectionTimer = setTimeout(() => { const currentChord = detectGuitarChord([...detectedNotes]); console.log('当前和弦:', currentChord); detectedNotes.clear(); chordDetectionTimer = null; }, 1000); } requestAnimationFrame(startFrequencyDetection); }
三、纯前端实现的关键注意事项
- 兼容性:记得处理
AudioContext的浏览器前缀,且必须在HTTPS环境下部署(localhost可用于开发) - 性能优化:FFT大小不要设置过大(2048足够),用
requestAnimationFrame替代setInterval保证流畅性 - 噪音过滤:可通过能量阈值、低通滤波器(
BiquadFilterNode)进一步过滤环境噪音 - UI体验:实时渲染音高偏差(比如仪表盘、颜色提示)、和弦名称,让用户直观看到结果
内容的提问来源于stack exchange,提问作者Marvin Medeiros
相关产品推荐
相关产品推荐

