You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 14:07:39