繁体   English   中英

音频 API 中心频率可视化

Audio API center visualisation on frequency

提示:本站收集StackOverFlow近2千万问答,支持中英文搜索,鼠标放在语句上弹窗显示对应的参考中文或英文, 本站还提供   中文繁体   英文版本   中英对照 版本,有任何建议请联系yoyou2525@163.com。

我正在开发用于网络的音频可视化器,它还可以让用户将原始音频信号可视化器“调整”到某个频率。 这是许多硬件示波器的一个特性。 基本上,当用户以 440Hz 为中心并且我有一个 440Hz 的正弦波时,波应该保持在画布上不动,而不是向左或向右移动。 我的计划是根据频率将图形向左移动(440Hz = 每秒向左移动 1/440 秒,因为波应该每 1/440 秒重复一次),但这似乎不起作用。

我找不到音频分析器节点的时域数据使用的单位。 我想它以毫秒为单位,但我不确定。

 "use strict"; // Oscillator instead of mic for debugging const USE_OSCILLATOR = true; // Compatibility if (!window.AudioContext) window.AudioContext = window.webkitAudioContext; if (!navigator.getUserMedia) navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia; // Main class App { constructor(visualizerElement, optionsElement) { this.visualizerElement = visualizerElement; this.optionsElement = optionsElement; // HTML elements this.canvas = document.createElement("canvas"); // Context this.context = new AudioContext({ // Low latency latencyHint: "interactive", }); this.canvasCtx = this.canvas.getContext("2d", { // Low latency desynchronized: true, alpha: false, }); // Audio nodes this.audioAnalyser = this.context.createAnalyser(); this.audioBuffer = new Uint8Array(this.audioAnalyser.frequencyBinCount); this.audioInputStream = null; this.audioInputNode = null; if (this.canvasCtx === null) throw new Error("2D rendering Context not supported by browser."); this.updateCanvasSize(); window.addEventListener("resize", () => this.updateCanvasSize()); this.drawVisualizer(); this.visualizerElement.appendChild(this.canvas); if (USE_OSCILLATOR) { let oscillator = this.context.createOscillator(); oscillator.type = "sine"; oscillator.frequency.setValueAtTime(440, this.context.currentTime); oscillator.connect(this.audioAnalyser); oscillator.start(); } else { navigator.getUserMedia({ audio: true }, (stream) => { this.audioInputStream = stream; this.audioInputNode = this.context.createMediaStreamSource(stream); this.audioInputNode.channelCountMode = "explicit"; this.audioInputNode.channelCount = 1; this.audioBuffer = new Uint8Array(this.audioAnalyser.frequencyBinCount); this.audioInputNode.connect(this.audioAnalyser); }, (err) => console.error(err)); } } updateCanvasSize() { var _a; this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; (_a = this.canvasCtx) === null || _a === void 0 ? void 0 : _a.setTransform(1, 0, 0, -1, 0, this.canvas.height * 0.5); } drawVisualizer() { if (this.canvasCtx === null) return; const ctx = this.canvasCtx; ctx.fillStyle = "black"; ctx.fillRect(0, -0.5 * this.canvas.height, this.canvas.width, this.canvas.height); // Draw FFT this.audioAnalyser.getByteFrequencyData(this.audioBuffer); const step = this.canvas.width / this.audioBuffer.length; const scale = this.canvas.height / (2 * 255); ctx.beginPath(); ctx.moveTo(-step, this.audioBuffer[0] * scale); this.audioBuffer.forEach((sample, index) => { ctx.lineTo(index * step, scale * sample); }); ctx.strokeStyle = "white"; ctx.stroke(); // Get the highest dominant frequency let highestFreqHalfHz = 0; { /** * Highest frequency in 0.5Hz */ let highestFreq = NaN; let highestFreqAmp = NaN; let remSteps = NaN; for (let i = this.audioBuffer.length - 1; i >= 0; i--) { const sample = this.audioBuffer[i]; if (sample > 20 && (isNaN(highestFreqAmp) || sample > highestFreqAmp)) { highestFreq = i; highestFreqAmp = sample; if (isNaN(remSteps)) remSteps = 500; } if (!isNaN(remSteps)) { if (remSteps-- < 0) break; } } if (!isNaN(highestFreq)) { ctx.beginPath(); ctx.moveTo(highestFreq * step, 0); ctx.lineTo(highestFreq * step, scale * 255); ctx.strokeStyle = "green"; ctx.stroke(); highestFreqHalfHz = highestFreq; } } // Draw Audio this.audioAnalyser.getByteTimeDomainData(this.audioBuffer); { const bufferSize = this.audioBuffer.length; const offsetY = -this.canvas.height * 0.5; // I don't know what I am doing here: const offsetX = highestFreqHalfHz == 0 ? 0 : bufferSize - Math.round(((this.context.currentTime * 1000) % (1 / 440)) % bufferSize); // Draw the audio graph with the given offset ctx.beginPath(); ctx.moveTo(-step, this.audioBuffer[0] * scale + offsetY); for (let i = 0; i < bufferSize; i++) { const index = (offsetX + i) % bufferSize; const sample = this.audioBuffer[index]; ctx.lineTo(i * step, scale * sample + offsetY); } ctx.strokeStyle = "white"; ctx.stroke(); } } } window.addEventListener("load", () => { const app = new App(document.getElementById("visualizer"), document.getElementById("options")); requestAnimationFrame(draw); function draw() { requestAnimationFrame(draw); app.drawVisualizer(); } });
 html { background: black; } body { width: 100vw; height: 100vh; margin: 0; overflow: hidden; } #visualizer { position: fixed; inset: 0; }
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Equalizer</title> </head> <body> <div id="visualizer"></div> <div id="options"></div> </body> </html>

上面的代码片段是从 TypeScript 生成的。 您可以在此处找到来源。 如果它按预期工作,振荡图(底部)将不会移动。

1 个回复

感谢Raymond Toy 的评论和我的数学老师(谢谢 Klein 先生),我能够解决这个问题。 解决方案是Math.round((this.context.currentTime % iv) * sampleRate)其中iv是频率的间隔( 1/Hz )。 波不是完全居中的。 但是,FFT 近似值不是很准确。 在下面的示例中,我强制检测到的频率为指定的频率。

 "use strict"; // Oscillator instead of mic for debugging const USE_OSCILLATOR = true; const OSCILLATOR_HZ = 1000; // Compatibility if (!window.AudioContext) window.AudioContext = window.webkitAudioContext; if (!navigator.getUserMedia) navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia; // Main class App { constructor(visualizerElement, optionsElement) { this.visualizerElement = visualizerElement; this.optionsElement = optionsElement; // HTML elements this.canvas = document.createElement("canvas"); // Context this.context = new AudioContext({ // Low latency latencyHint: "interactive", }); this.canvasCtx = this.canvas.getContext("2d", { // Low latency desynchronized: true, alpha: false, }); // Audio nodes this.audioAnalyser = this.context.createAnalyser(); this.audioBuffer = new Uint8Array(0); this.audioInputStream = null; this.audioInputNode = null; if (this.canvasCtx === null) throw new Error("2D rendering Context not supported by browser."); this.updateCanvasSize(); window.addEventListener("resize", () => this.updateCanvasSize()); this.drawVisualizer(); this.visualizerElement.appendChild(this.canvas); this.audioAnalyser.fftSize = 2048; this.audioAnalyser.maxDecibels = -10; this.audioBuffer = new Uint8Array(this.audioAnalyser.frequencyBinCount * 2); this.audioFilter = this.context.createBiquadFilter(); this.audioFilter.type = "bandpass"; this.audioFilter.frequency.value = 900; this.audioFilter.Q.value = 20; this.audioAmplifier = this.context.createGain(); this.audioAmplifier.gain.value = 5; this.audioFilter.connect(this.audioAmplifier); this.audioAmplifier.connect(this.audioAnalyser); if (USE_OSCILLATOR) { let oscillator = this.context.createOscillator(); oscillator.type = "sine"; oscillator.frequency.setValueAtTime(OSCILLATOR_HZ, this.context.currentTime); oscillator.connect(this.audioFilter); oscillator.start(); } else { navigator.getUserMedia({ audio: true }, (stream) => { this.audioInputStream = stream; this.audioInputNode = this.context.createMediaStreamSource(stream); this.audioInputNode.channelCountMode = "explicit"; this.audioInputNode.channelCount = 1; this.audioBuffer = new Uint8Array(this.audioAnalyser.frequencyBinCount); this.audioInputNode.connect(this.audioFilter); }, (err) => console.error(err)); } } updateCanvasSize() { var _a; this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; (_a = this.canvasCtx) === null || _a === void 0 ? void 0 : _a.setTransform(1, 0, 0, -1, 0, this.canvas.height * 0.5); } drawVisualizer() { if (this.canvasCtx === null) return; const ctx = this.canvasCtx; ctx.globalAlpha = 0.5; ctx.fillStyle = "black"; ctx.fillRect(0, -0.5 * this.canvas.height, this.canvas.width, this.canvas.height); ctx.globalAlpha = 1; // Draw FFT this.audioAnalyser.getByteFrequencyData(this.audioBuffer); const scale = this.canvas.height / (2 * 255); const { frequencyBinCount } = this.audioAnalyser; const { sampleRate } = this.context; { const step = this.canvas.width / frequencyBinCount; ctx.beginPath(); ctx.moveTo(-step, this.audioBuffer[0] * scale); for (let index = 0; index < frequencyBinCount; index++) { ctx.lineTo(index * step, scale * this.audioBuffer[index]); } ctx.strokeStyle = "white"; ctx.stroke(); } // Get the highest dominant frequency const step = this.canvas.width / frequencyBinCount; let highestFreqHz = 0; { /** * Highest frequency index in the buffer */ let highestFreqIndex = NaN; let highestFreqAmp = NaN; let remSteps = NaN; for (let i = frequencyBinCount - 1; i >= 0; i--) { const sample = this.audioBuffer[i]; if (sample > 30) { if (isNaN(highestFreqAmp)) { highestFreqIndex = i; highestFreqAmp = sample; } else { if (sample > highestFreqAmp) { highestFreqIndex = i; highestFreqAmp = sample; } } //if (isNaN(remSteps)) remSteps = 100; } if (!isNaN(remSteps)) { if (remSteps-- < 0) break; } } if (!isNaN(highestFreqIndex)) { // Force exact value: (not necessary) highestFreqIndex = (OSCILLATOR_HZ * (2 * frequencyBinCount)) / sampleRate; ctx.beginPath(); ctx.moveTo(highestFreqIndex * step, 0); ctx.lineTo(highestFreqIndex * step, scale * 255); ctx.strokeStyle = "green"; ctx.stroke(); highestFreqHz = (highestFreqIndex * sampleRate) / (2 * frequencyBinCount); window.HZ = highestFreqHz; } } // Draw Audio this.audioAnalyser.getByteTimeDomainData(this.audioBuffer); { const iv = highestFreqHz == 0 ? 0 : 1 / highestFreqHz; const bufferSize = this.audioBuffer.length; const offsetY = -this.canvas.height / 2.4; const startIndex = Math.round(iv * sampleRate); const step = this.canvas.width / (this.audioBuffer.length - startIndex); const scale = this.canvas.height / (3 * 255); const offsetX = highestFreqHz == 0 ? 0 : Math.round((this.context.currentTime % iv) * sampleRate) % bufferSize; // Draw the audio graph with the given offset ctx.beginPath(); ctx.moveTo(-step, this.audioBuffer[startIndex - offsetX] * scale + offsetY); for (let i = startIndex; i < bufferSize; i += 4) { const index = (i - offsetX) % bufferSize; const sample = this.audioBuffer[index]; ctx.lineTo((i - startIndex) * step, scale * sample + offsetY); } ctx.strokeStyle = "white"; ctx.stroke(); } } } window.addEventListener("load", () => { const app = new App(document.getElementById("visualizer"), document.getElementById("options")); requestAnimationFrame(draw); function draw() { requestAnimationFrame(draw); app.drawVisualizer(); } });
 html { background: black; } body { width: 100vw; height: 100vh; margin: 0; overflow: hidden; } #visualizer { position: fixed; inset: 0; }
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Equalizer</title> </head> <body> <div id="visualizer"></div> <div id="options"></div> </body> </html>

1 播放的音频文件的可视化频率似乎错误

我的应用程序具有两项功能,用于记录和播放记录的或其他PCM文件。 两者都可以在记录和播放文件时可视化频率。 但是在录制和播放时的可视化频率似乎有所不同。 我认为正确的是在录制时。 现在有人出什么事了吗? 这是我的playAudio代码 ...

3 可视化频率数据

有没有人知道一种很好的方法来显示表缺少值的不同表的数据频率。 举个例子 我怎样才能最好地可视化这些数据,以便可以看到表格之间的比较。 直方图并没有因为垃圾箱而对我不利。 ...

4 音频/语音可视化

嘿你Objective-C bods。 有谁知道我将如何根据iPhone上麦克风的输入更改(转换)图像? 即,当用户对着麦克风说话时,图像会发出脉冲或歪斜。 [编辑]任何人有任何想法,我有(基本上是)一个录音应用程序。 我只想在提供语音输入时改变一些东西。 我在一个示例项目 ...

5 可视化收到的音频

我想创建一个类似于Siri的效果(使用此库https://github.com/caffeinalab/siriwavejs )进行VOIP调用。 我正在使用https://webrtc.org/开发一个Web应用程序,以生成VOIP呼叫会话。 我基本上是在按照本教程https:// ...

6 android音频可视化

我正在尝试开发将具有可视化器的音频输入应用程序。 我为此使用android.media.audiofx.Visualizer类。 但是无法初始化Visualizer对象。 参考: https : //github.com/felixpalmer/android-visualiz ...

2012-09-03 11:55:36 1 4542   android
7 音频处理和可视化

我本质上想做的是: 我有一个音频文件,我想设置一个dB阈值,用于确定响度(我不知道这里的正确词是什么)超过某个限制时要切换布尔值或类似值的位置。 最后,我想记录这些布尔更改,然后将其转换为与记录一样长的视频。 我不知道该怎么做。 视频编辑软件能够做到这一点还是我应该使用Python(如果是,那 ...

8 如何可视化组和子组频率?

我必须使用组变量As和子组变量ADs绘制频率数据。 可视化频率的最佳方法是什么,即饼图或马赛克? ggplot2 中是否有任何可用的功能? 一些想法如下: 但是,有没有办法在单个图中区分组和子组? 在提议的解决方案中,下面两个图表看起来很有希望。 饼图和平铺图 我使用了 M-- 建 ...

9 Google Data Studio 按值的频率可视化数据

我有一个非常简单的测验,如应用程序,它基本上是一个问答。 我想了解用户的常见错误,即用户大部分时间在哪个问题上犯错。 为此,我有自定义事件,每次用户回答错误时都会记录一个事件。 因此,如果有 5 个问题并且用户通常会错误地回答问题 3,那么该用户的常见错误是问题 3。 但是在数据工作室中,我找不到 ...

10 可视化双向加权频率表

如果我们具有上述数据帧并创建双向加权频率表: 看起来像这样: 可视化的最佳方法是什么? 像这样可视化加权数据时,是否应该使用其他方法? Quick-R确实提到: 使用vcd包可视化分类数据之间的关系(例如,镶嵌图和关联图)。 ...

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2022 STACKOOM.COM