Mar*_*ros 8 javascript audio frontend frequency node.js
我需要创建一种类似吉他调音器的东西......它可以识别声音频率并确定我实际演奏的女巫和弦。它类似于我在网上找到的吉他调音器: https: //musicjungle.com.br/afinador-online 但由于 webpack 文件,我无法弄清楚它是如何工作的..我想让这个工具应用程序无后端..有人知道如何仅在前端执行此操作吗?
我创建了一些不能一起工作的旧代码..我需要新的想法
fdc*_*cpp 37
这里有很多问题需要解决,其中一些需要更多有关应用程序的信息。希望随着这个答案的进展,这项任务的巨大规模将变得显而易见。
\n就目前情况而言,这里有两个问题:
\n\n\n需要创建一种类似吉他调音器的东西..
\n
1. 如何检测吉他音符的基本音高并将该信息反馈给浏览器中的用户?
\n和
\n\n\n这就是识别声音频率并确定我实际演奏的女巫和弦。
\n
2. 如何检测吉他正在演奏哪个和弦?
\n第二个问题绝对不是一个小问题,但我们将依次讨论它。这不是一个编程问题,而是一个DSP问题
\n如果您希望在浏览器中检测音符的音高,则需要分解几个子问题。从表面上看,我们有以下 JavaScript 浏览器问题:
\n这不是一个详尽的列表,但它应该构成整个问题的大部分
\n没有最小的、可重现的示例,因此不能假设上述情况。
\n基本实现包括使用 A. v. Knesebeck 和 U. Z\xc3\xb6lzer 论文 [1] 中概述的自相关方法对单个基频 (f0) 进行数值表示。
\n还有其他混合和匹配滤波和音高检测算法的方法,我认为这远远超出了合理答案的范围。
\n注意: Web Audio API仍然没有在所有浏览器上得到同等的实现。您应该检查每个主要浏览器并在您的程序中进行调整。以下内容是在 Google Chrome 中进行测试的,因此您的情况可能(并且可能会)在其他浏览器中有所不同。
\n我们的页面应该包括
\n更全面的界面可能会拆分以下操作
\n到单独的界面元素中,但为了简洁起见,它们将被包装到单个元素中。这给了我们一个基本的 HTML 页面
\n<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <title>Pitch Detection</title>\n</head>\n<body>\n<h1>Frequency (Hz)</h1>\n<h2 id="frequency">0.0</h2>\n<div>\n <button onclick="startPitchDetection()">\n Start Pitch Detection\n </button>\n</div>\n</body>\n</html>\nRun Code Online (Sandbox Code Playgroud)\n我们有点操之过急了<button onclick="startPitchDetection()">。我们将把操作包装在一个名为的函数中startPitchDetection
对于自相关音高检测方法,我们的变量列表需要包括:
\n给我们类似的东西
\nlet audioCtx = new (window.AudioContext || window.webkitAudioContext)();\nlet microphoneStream = null;\nlet analyserNode = audioCtx.createAnalyser()\nlet audioData = new Float32Array(analyserNode.fftSize);;\nlet corrolatedSignal = new Float32Array(analyserNode.fftSize);;\nlet localMaxima = new Array(10);\nconst frequencyDisplayElement = document.querySelector(\'#frequency\');\nRun Code Online (Sandbox Code Playgroud)\n留下一些值null,因为在激活麦克风流之前它们是不知道的。in有点随意10。let localMaxima = new Array(10);该数组将存储关联信号的连续最大值之间的样本距离。
我们的<button>元素有一个onclick函数startPitchDetection,因此这是必需的。我们还需要
然而,我们要做的第一件事是请求使用麦克风的许可。为了实现这一点,我们使用navigator.mediaDevices.getUserMedia,它将返回一个Promise。对 MDN 文档中概述的内容进行修饰后,我们得到的内容大致如下
navigator.mediaDevices.getUserMedia({audio: true})\n.then((stream) => {\n /* use the stream */\n})\n.catch((err) => {\n /* handle the error */\n});\nRun Code Online (Sandbox Code Playgroud)\n伟大的!现在我们可以开始将主要功能添加到该then函数中。
我们的事件顺序应该是
\n最重要的是,添加该方法的错误日志catch。
然后可以将它们全部包装到startPitchDetection函数中,给出如下内容:
function startPitchDetection()\n{\n navigator.mediaDevices.getUserMedia ({audio: true})\n .then((stream) =>\n {\n microphoneStream = audioCtx.createMediaStreamSource(stream);\n microphoneStream.connect(analyserNode);\n\n audioData = new Float32Array(analyserNode.fftSize);\n corrolatedSignal = new Float32Array(analyserNode.fftSize);\n\n setInterval(() => {\n analyserNode.getFloatTimeDomainData(audioData);\n\n let pitch = getAutocorrolatedPitch();\n\n frequencyDisplayElement.innerHTML = `${pitch}`;\n }, 300);\n })\n .catch((err) =>\n {\n console.log(err);\n });\n}\nRun Code Online (Sandbox Code Playgroud)\nsetIntervalof的更新间隔300是任意的。一些实验将决定哪种间隔最适合您。您甚至可能希望让用户对此进行控制,但这超出了本问题的范围。
下一步是实际定义什么是getAutocorrolatedPitch()自相关,所以让我们实际分解什么是自相关。
自相关是信号与其自身卷积的过程。任何时候结果从正变化率变为负变化率都被定义为局部最大值。相关信号开始到第一个最大值之间的样本数应为 的样本周期f0。我们可以继续寻找后续的最大值并取平均值,这应该会稍微提高准确性。某些频率没有完整样本的周期,例如440采样率为 Hz 的44100Hz 的周期为100.227。从技术上讲,我们永远无法440通过取单个最大值来准确检测 Hz 的频率,结果始终是441Hz ( 44100/100) 或436Hz ( 44100/101)。
对于我们的自相关函数,我们需要
\n我们的函数应该首先执行自相关,找到局部最大值的样本位置,然后计算这些最大值之间的平均距离。这给出了一个如下所示的函数:
\nfunction getAutocorrolatedPitch()\n{\n // First: autocorrolate the signal\n\n let maximaCount = 0;\n\n for (let l = 0; l < analyserNode.fftSize; l++) {\n corrolatedSignal[l] = 0;\n for (let i = 0; i < analyserNode.fftSize - l; i++) {\n corrolatedSignal[l] += audioData[i] * audioData[i + l];\n }\n if (l > 1) {\n if ((corrolatedSignal[l - 2] - corrolatedSignal[l - 1]) < 0\n && (corrolatedSignal[l - 1] - corrolatedSignal[l]) > 0) {\n localMaxima[maximaCount] = (l - 1);\n maximaCount++;\n if ((maximaCount >= localMaxima.length))\n break;\n }\n }\n }\n\n // Second: find the average distance in samples between maxima\n\n let maximaMean = localMaxima[0];\n\n for (let i = 1; i < maximaCount; i++)\n maximaMean += localMaxima[i] - localMaxima[i - 1];\n\n maximaMean /= maximaCount;\n\n return audioCtx.sampleRate / maximaMean;\n}\nRun Code Online (Sandbox Code Playgroud)\n一旦你实现了这一点,你可能会发现实际上存在一些问题。
\n不稳定的结果是由于自相关本身并不是一个完美的解决方案。您需要首先尝试过滤信号并聚合其他方法。您还可以尝试限制信号或仅在信号高于特定阈值时分析信号。您还可以提高执行检测的速率并对结果进行平均。
\n其次,展示方式受到限制。音乐家不会欣赏简单的数字结果,相反,某种图形反馈会更直观。同样,这超出了问题的范围。
\n<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <title>Pitch Detection</title>\n</head>\n<body>\n<h1>Frequency (Hz)</h1>\n<h2 id="frequency">0.0</h2>\n<div>\n <button onclick="startPitchDetection()">\n Start Pitch Detection\n </button>\n</div>\n<script>\n let audioCtx = new (window.AudioContext || window.webkitAudioContext)();\n let microphoneStream = null;\n let analyserNode = audioCtx.createAnalyser()\n let audioData = new Float32Array(analyserNode.fftSize);;\n let corrolatedSignal = new Float32Array(analyserNode.fftSize);;\n let localMaxima = new Array(10);\n const frequencyDisplayElement = document.querySelector(\'#frequency\');\n\n function startPitchDetection()\n {\n navigator.mediaDevices.getUserMedia ({audio: true})\n .then((stream) =>\n {\n microphoneStream = audioCtx.createMediaStreamSource(stream);\n microphoneStream.connect(analyserNode);\n\n audioData = new Float32Array(analyserNode.fftSize);\n corrolatedSignal = new Float32Array(analyserNode.fftSize);\n\n setInterval(() => {\n analyserNode.getFloatTimeDomainData(audioData);\n\n let pitch = getAutocorrolatedPitch();\n\n frequencyDisplayElement.innerHTML = `${pitch}`;\n }, 300);\n })\n .catch((err) =>\n {\n console.log(err);\n });\n }\n\n function getAutocorrolatedPitch()\n {\n // First: autocorrolate the signal\n\n let maximaCount = 0;\n\n for (let l = 0; l < analyserNode.fftSize; l++) {\n corrolatedSignal[l] = 0;\n for (let i = 0; i < analyserNode.fftSize - l; i++) {\n corrolatedSignal[l] += audioData[i] * audioData[i + l];\n }\n if (l > 1) {\n if ((corrolatedSignal[l - 2] - corrolatedSignal[l - 1]) < 0\n && (corrolatedSignal[l - 1] - corrolatedSignal[l]) > 0) {\n localMaxima[maximaCount] = (l - 1);\n maximaCount++;\n if ((maximaCount >= localMaxima.length))\n break;\n }\n }\n }\n\n // Second: find the average distance in samples between maxima\n\n let maximaMean = localMaxima[0];\n\n for (let i = 1; i < maximaCount; i++)\n maximaMean += localMaxima[i] - localMaxima[i - 1];\n\n maximaMean /= maximaCount;\n\n return audioCtx.sampleRate / maximaMean;\n }\n</script>\n</body>\n</html>\nRun Code Online (Sandbox Code Playgroud)\n在这一点上,我想我们都同意这个答案已经有点失控了。到目前为止,我们刚刚介绍了一种音高检测方法。有关多重检测算法的一些建议,请参阅参考文献 [2,3,4] f0。
本质上,这个问题归结为检测所有f0s 并根据和弦字典查找生成的音符。为此,您至少应该做一些工作。有关 DSP 的任何问题都应该指向https://dsp.stackexchange.com。关于音高检测算法的问题,您将被宠坏了