如何使用 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 浏览器问题:
- how to get microphone permission?
- how to tap microhone for sample data?
- how to start an audio context?
- how to display a value?
- how to update a value regularly?
- how to filter audio data?
- how to perform pitch detection?
- how to perform a fourier transform?
这不是一个详尽的列表,但它应该构成整个问题的大部分
没有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>
元素具有 startPitchDetection
的 onclick
功能,因此这是必需的。我们还需要
- 一个更新函数(用于更新显示)
- 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
参考资料
- 一个。 v. Knesebeck 和 U. Zölzer,“real-time 吉他效果的音高跟踪器比较”, 第 13 届国际数字音频效果会议 (DAFx-10) 会议记录,奥地利格拉茨,9 月6-10, 2010.
- 一个。 P. Klapuri,“A perceptually motivated multiple-F0 estimation method”,IEEE 音频和声学信号处理应用研讨会,2005 年,2005 年,第 291-294 页,doi: 10.1109/ASPAA.2005.1540227.
- 一个。 P. Klapuri,“基于谐波和频谱平滑度的多基频估计”,IEEE 语音和音频处理交易,卷。 11,没有。 6,第 804-816 页,2003 年 11 月,doi:10.1109/TSA.2003.815516.
- 一个。 P. Klapuri,“通过频谱平滑原理进行多音高估计和声音分离”,2001 年 IEEE 声学、语音和信号处理国际会议。会议记录(目录号 01CH37221),2001 年,第 3381-3384 页,第 5 卷,doi:10.1109/ICASSP.2001.940384。
我需要创建一种类似吉他调音器的东西。它可以识别声音频率并确定我实际演奏的 和弦。它类似于我在网上找到的这个吉他调音器: 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 浏览器问题:
- how to get microphone permission?
- how to tap microhone for sample data?
- how to start an audio context?
- how to display a value?
- how to update a value regularly?
- how to filter audio data?
- how to perform pitch detection?
- how to perform a fourier transform?
这不是一个详尽的列表,但它应该构成整个问题的大部分
没有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>
元素具有 startPitchDetection
的 onclick
功能,因此这是必需的。我们还需要
- 一个更新函数(用于更新显示)
- 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
参考资料
- 一个。 v. Knesebeck 和 U. Zölzer,“real-time 吉他效果的音高跟踪器比较”, 第 13 届国际数字音频效果会议 (DAFx-10) 会议记录,奥地利格拉茨,9 月6-10, 2010.
- 一个。 P. Klapuri,“A perceptually motivated multiple-F0 estimation method”,IEEE 音频和声学信号处理应用研讨会,2005 年,2005 年,第 291-294 页,doi: 10.1109/ASPAA.2005.1540227.
- 一个。 P. Klapuri,“基于谐波和频谱平滑度的多基频估计”,IEEE 语音和音频处理交易,卷。 11,没有。 6,第 804-816 页,2003 年 11 月,doi:10.1109/TSA.2003.815516.
- 一个。 P. Klapuri,“通过频谱平滑原理进行多音高估计和声音分离”,2001 年 IEEE 声学、语音和信号处理国际会议。会议记录(目录号 01CH37221),2001 年,第 3381-3384 页,第 5 卷,doi:10.1109/ICASSP.2001.940384。