如何使用 javascript 从我的麦克风获取音频?

How do i get the audio frequency from my mic using javascript?

我需要创建一种类似吉他调音器的东西。它可以识别声音频率并确定我实际演奏的 和弦。它类似于我在网上找到的这个吉他调音器: https://musicjungle.com.br/afinador-online 但是由于 webpack 文件,我无法弄清楚它是如何工作的。我想让这个工具应用程序无休止地运行。有人知道如何只在前端执行此操作吗?

我创建了一些不能协同工作的旧代码片段。我需要新的想法

我想这取决于您构建应用程序的方式。如果没有关于规格的详细信息,很难提供帮助。不过,这里有几个选项供您选择。

有几个流选项,例如;

或者如果您使用的是 React;

或者,如果您想使用一些普通的 JS 进行真正的基础;

这里有很多问题需要解决,其中一些问题需要更多关于应用程序的信息。希望这个任务的庞大规模会随着这个答案的进展而变得明显。

按照目前的情况,这里有两个问题:

need to create a sort of like guitar tuner..

1。您如何检测吉他音符的基本音高并将该信息反馈给浏览器中的用户?

thats recognize the sound frequencies and determines in witch chord i am actually playing.

2。你如何检测吉他弹奏的和弦?

这第二个问题绝对不是一个小问题,但我们将依次讨论。其实这不是编程题,而是DSP题

问题 1:浏览器中的音调检测

细分

如果你想在浏览器中检测音符的音高,有一对 sub-problems 应该分开。从臀部拍摄我们有以下 JavaScript 浏览器问题:

这不是一个详尽的列表,但它应该构成整个问题的大部分

没有Minimal, Reproducible Example,所以可以假设上面的none。

实施

基本实现包括使用 A. v. Knesebeck 和 U. Zölzer 论文 [1] 中概述的自相关方法对单个基频 (f0) 进行数字表示。

还有其他混合和匹配过滤和音调检测算法的方法,我认为这远远超出了合理答案的范围。

注意: Web Audio API is still not equally implemented across all browser。您应该检查每个主要浏览器并在您的程序中进行调整。以下内容在 Google Chrome 中进行了测试,因此您的里程可能(并且很可能会)在其他浏览器中有所不同。

HTML

我们的页面应该包括

  • 显示频率的元素
  • 启动音调检测的元素

更圆润的界面可能会拆分

的操作
  • 正在请求麦克风许可
  • 开始麦克风流
  • 正在处理麦克风流

到单独的界面元素中,但为简洁起见,它们将被包装到单个元素中。这为我们提供了

的基本 HTML 页
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Pitch Detection</title>
</head>
<body>
<h1>Frequency (Hz)</h1>
<h2 id="frequency">0.0</h2>
<div>
    <button onclick="startPitchDetection()">
        Start Pitch Detection
    </button>
</div>
</body>
</html>

我们对 <button onclick="startPitchDetection()"> 的看法略有仓促。我们将把操作封装在一个名为 startPitchDetection

的函数中
变量列表

对于自相关音调检测方法,我们的变量面板需要包括:

  • 音频上下文
  • 麦克风流
  • 一个分析器节点
  • 音频数据数组
  • 相关信号的数组
  • 相关信号最大值数组
  • a DOM 参考频率

给我们类似的东西

let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
let microphoneStream = null;
let analyserNode = audioCtx.createAnalyser()
let audioData = new Float32Array(analyserNode.fftSize);;
let corrolatedSignal = new Float32Array(analyserNode.fftSize);;
let localMaxima = new Array(10);
const frequencyDisplayElement = document.querySelector('#frequency');

还剩下一些值 null,因为只有在激活麦克风流后才能知道这些值。 let localMaxima = new Array(10); 中的 10 有点武断。该数组将存储相关信号的连续最大值之间的样本距离。

主脚本

我们的 <button> 元素具有 startPitchDetectiononclick 功能,因此这是必需的。我们还需要

  • 一个更新函数(用于更新显示)
  • returns一个音高
  • 的自相关函数

但是,我们要做的第一件事是请求使用麦克风的权限。 To achieve this we use navigator.mediaDevices.getUserMedia, which will returm a Promise。美化 MDN 文档中概述的内容,这使我们大致看起来像

navigator.mediaDevices.getUserMedia({audio: true})
.then((stream) => {
  /* use the stream */
})
.catch((err) => {
  /* handle the error */
});

太棒了!现在我们可以开始将我们的主要功能添加到 then 函数。

我们的活动顺序应该是

  • 开始麦克风流
  • 将麦克风流连接到分析器节点
  • 设置定时回调
    • 从Analyzer节点获取最新的时域音频数据
    • 获取自相关推导的音调估计
    • 用值
    • 更新html元素

最重要的是,添加来自 catch 方法的错误日志。

然后可以将其全部包装到 startPitchDetection 函数中,给出如下内容:

function startPitchDetection()
{
    navigator.mediaDevices.getUserMedia ({audio: true})
        .then((stream) =>
        {
            microphoneStream = audioCtx.createMediaStreamSource(stream);
            microphoneStream.connect(analyserNode);

            audioData = new Float32Array(analyserNode.fftSize);
            corrolatedSignal = new Float32Array(analyserNode.fftSize);

            setInterval(() => {
                analyserNode.getFloatTimeDomainData(audioData);

                let pitch = getAutocorrolatedPitch();

                frequencyDisplayElement.innerHTML = `${pitch}`;
            }, 300);
        })
        .catch((err) =>
        {
            console.log(err);
        });
}

setInterval of 300 的更新间隔是任意的。稍作试验即可确定最适合您的时间间隔。您甚至可能希望让用户对此进行控制,但这超出了这个问题的范围。

下一步是实际定义 getAutocorrolatedPitch() 的作用,所以让我们实际分解什么是自相关。

自相关是convolving 信号本身。任何时候结果从正变化率变为负变化率都被定义为 局部最大值 。相关信号开始到第一个最大值之间的样本数应该是 f0 的样本周期。我们可以继续寻找后续的最大值并取平均值,这应该会稍微提高准确性。某些频率没有整个样本的周期,例如 440 Hz 在 44100 Hz 的采样率下具有 100.227 的周期。从技术上讲,我们永远无法通过取一个最大值来准确检测到 440 赫兹的频率,结果总是 441 赫兹(44100/100)或 436 赫兹(44100/101).

对于我们的自相关函数,我们需要

  • 跟踪检测到的最大值
  • 最大值之间的平均距离

我们的函数应该首先执行自相关,找到局部最大值的样本位置,然后计算这些最大值之间的平均距离。这给出了一个看起来像这样的函数:

function getAutocorrolatedPitch()
{
    // First: autocorrolate the signal

    let maximaCount = 0;

    for (let l = 0; l < analyserNode.fftSize; l++) {
        corrolatedSignal[l] = 0;
        for (let i = 0; i < analyserNode.fftSize - l; i++) {
            corrolatedSignal[l] += audioData[i] * audioData[i + l];
        }
        if (l > 1) {
            if ((corrolatedSignal[l - 2] - corrolatedSignal[l - 1]) < 0
                && (corrolatedSignal[l - 1] - corrolatedSignal[l]) > 0) {
                localMaxima[maximaCount] = (l - 1);
                maximaCount++;
                if ((maximaCount >= localMaxima.length))
                    break;
            }
        }
    }

    // Second: find the average distance in samples between maxima

    let maximaMean = localMaxima[0];

    for (let i = 1; i < maximaCount; i++)
        maximaMean += localMaxima[i] - localMaxima[i - 1];

    maximaMean /= maximaCount;

    return audioCtx.sampleRate / maximaMean;
}
问题

一旦你实现了这个,你可能会发现实际上有几个问题。

  • 频率结果有点不稳定
  • 为了调整目的,显示方法不直观

结果不稳定是因为自相关本身并不是一个完美的解决方案。您将需要尝试先过滤信号并聚合其他方法。您也可以尝试限制信号或仅在信号高于特定阈值时才分析信号。您还可以提高执行检测的速度并对结果进行平均。

其次,展示方式有限。音乐家不会欣赏简单的数字结果。相反,某种图形反馈会更直观。同样,这超出了问题的范围。

整页和脚本
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Pitch Detection</title>
</head>
<body>
<h1>Frequency (Hz)</h1>
<h2 id="frequency">0.0</h2>
<div>
    <button onclick="startPitchDetection()">
        Start Pitch Detection
    </button>
</div>
<script>
    let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    let microphoneStream = null;
    let analyserNode = audioCtx.createAnalyser()
    let audioData = new Float32Array(analyserNode.fftSize);;
    let corrolatedSignal = new Float32Array(analyserNode.fftSize);;
    let localMaxima = new Array(10);
    const frequencyDisplayElement = document.querySelector('#frequency');

    function startPitchDetection()
    {
        navigator.mediaDevices.getUserMedia ({audio: true})
            .then((stream) =>
            {
                microphoneStream = audioCtx.createMediaStreamSource(stream);
                microphoneStream.connect(analyserNode);

                audioData = new Float32Array(analyserNode.fftSize);
                corrolatedSignal = new Float32Array(analyserNode.fftSize);

                setInterval(() => {
                    analyserNode.getFloatTimeDomainData(audioData);

                    let pitch = getAutocorrolatedPitch();

                    frequencyDisplayElement.innerHTML = `${pitch}`;
                }, 300);
            })
            .catch((err) =>
            {
                console.log(err);
            });
    }

    function getAutocorrolatedPitch()
    {
        // First: autocorrolate the signal

        let maximaCount = 0;

        for (let l = 0; l < analyserNode.fftSize; l++) {
            corrolatedSignal[l] = 0;
            for (let i = 0; i < analyserNode.fftSize - l; i++) {
                corrolatedSignal[l] += audioData[i] * audioData[i + l];
            }
            if (l > 1) {
                if ((corrolatedSignal[l - 2] - corrolatedSignal[l - 1]) < 0
                    && (corrolatedSignal[l - 1] - corrolatedSignal[l]) > 0) {
                    localMaxima[maximaCount] = (l - 1);
                    maximaCount++;
                    if ((maximaCount >= localMaxima.length))
                        break;
                }
            }
        }

        // Second: find the average distance in samples between maxima

        let maximaMean = localMaxima[0];

        for (let i = 1; i < maximaCount; i++)
            maximaMean += localMaxima[i] - localMaxima[i - 1];

        maximaMean /= maximaCount;

        return audioCtx.sampleRate / maximaMean;
    }
</script>
</body>
</html>

问题 2:检测多个音符

在这一点上,我想我们都同意这个答案有点失控了。到目前为止,我们只介绍了一种音调检测方法。有关多重 f0 检测算法的一些建议,请参阅参考文献 [2、3、4]。

本质上,这个问题将归结为检测所有 f0 并根据和弦字典查找生成的音符。为此,您至少应该做一些工作。关于 DSP 的任何问题都应该指向 https://dsp.stackexchange.com. You will be spoiled for choice on questions regarding pitch detection algorithms

参考资料

  1. 一个。 v. Knesebeck 和 U. Zölzer,“real-time 吉他效果的音高跟踪器比较”, 第 13 届国际数字音频效果会议 (DAFx-10) 会议记录,奥地利格拉茨,9 月6-10, 2010.
  2. 一个。 P. Klapuri,“A perceptually motivated multiple-F0 estimation method”,IEEE 音频和声学信号处理应用研讨会,2005 年,2005 年,第 291-294 页,doi: 10.1109/ASPAA.2005.1540227.
  3. 一个。 P. Klapuri,“基于谐波和频谱平滑度的多基频估计”,IEEE 语音和音频处理交易,卷。 11,没有。 6,第 804-816 页,2003 年 11 月,doi:10.1109/TSA.2003.815516.
  4. 一个。 P. Klapuri,“通过频谱平滑原理进行多音高估计和声音分离”,2001 年 IEEE 声学、语音和信号处理国际会议。会议记录(目录号 01CH37221),2001 年,第 3381-3384 页,第 5 卷,doi:10.1109/ICASSP.2001.940384。