
[英]Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. - React JS
[英]Error: Too many re-renders. React limits the number of renders to prevent an infinite loop. GUITAR TUNER project using Web Audio API
我正在尝试使用 React 和 web 音频 API 创建吉他调音应用程序。这是我项目的 DOM 结构
index.js
\__App.js
\__Tuner.js
这是有关该应用程序如何工作的简要概述...
本质上。
在主屏幕上,您单击开始按钮,我们可以访问用户的麦克风,然后我们使用 window.AudioContext class 实例化一个 analyserNode,它提供麦克风拾取的音频频率的实时数据,接下来我们由于 AutoCorrelate.js 文件中编写的算法,数据被转换为单个数值,该值存储在钩子 state 中,最后 - 通过各种 state 的一系列呈现 - 浏览器呈现音高#值,音高字母和相对于球场的 state 值移动的仪表/状态栏。
从日志和代码的 l.60 中可以看出, updatePitch
函数需要每 1 毫秒调用一次,以便更新呈现在屏幕上的音高值。 嵌套在我们的updatePitch
函数中的是各种 state 钩子,每 1 毫秒被调用一次: setPitchNote
、 setPitchScale
、 setDetune
、 setNotification
。 人们会认为这会导致重新渲染问题,但实际上效果很好。
文件:Tuner.js
import React, { useState, useEffect } from 'react';
import AudioContext from '../contexts/AudioContext.js';
import autoCorrelate from "../libs/AutoCorrelate.js";
import {
noteFromPitch,
centsOffFromPitch,
getDetunePercent,
} from "../libs/Helpers.js";
const audioCtx = AudioContext.getAudioContext();
const analyserNode = AudioContext.getAnalyser();
const bufferlength = 2048;
let buf = new Float32Array(bufferlength);
const noteStrings = [
"C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
"A",
"A#",
"B",
];
const Tuner = () => {
/*////AUDIO STATE////*/
const [source, setSource] = useState(null);
const [started, setStart] = useState(false);
const [pitchNote, setPitchNote] = useState("C");
const [pitchScale, setPitchScale] = useState("4");
const [pitch, setPitch] = useState("0 Hz");
const [detune, setDetune] = useState("0");
const [notification, setNotification] = useState(false);
/*////UPDATES PITCH////*/
const updatePitch = (time) => {
analyserNode.getFloatTimeDomainData(buf);
var ac = autoCorrelate(buf, audioCtx.sampleRate);
if (ac > -1) {
let note = noteFromPitch(ac);
let sym = noteStrings[note % 12];
let scl = Math.floor(note / 12) - 1;
let dtune = centsOffFromPitch(ac, note);
setPitch(parseFloat(ac).toFixed(2) + " Hz");
setPitchNote(sym);
setPitchScale(scl);
setDetune(dtune);
setNotification(false);
console.log(note, sym, scl, dtune, ac);
}
};
setInterval(updatePitch, 1);
useEffect(() => {
if (source != null) {
source.connect(analyserNode);
}
}, [source]);
const start = async () => {
const input = await getMicInput();
if (audioCtx.state === "suspended") {
await audioCtx.resume();
}
setStart(true);
setNotification(true);
setTimeout(() => setNotification(false), 5000);
setSource(audioCtx.createMediaStreamSource(input));
};
const stop = () => {
source.disconnect(analyserNode);
setStart(false);
};
const getMicInput = () => {
return navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
autoGainControl: false,
noiseSuppression: false,
latency: 0,
},
});
};
return (
<div className='tuner'>
<div className='notification' style={{color: notification ? 'black' : 'white'}}>
Please, bring your instrument near to the microphone!
</div>
<div className ='container'>
<div className='screen'>
<div className='top-half'>
<span className='note-letter'>{pitchNote}</span>
<span className='note-number'>{pitchScale}</span>
</div>
<div className='bottom-half'>
<span className='meter-left' style={{
width: (detune < 0 ? getDetunePercent(detune) : "50") + "%",
}}></span>
<span className='dial'>|</span>
<span className='meter-right' style={{
width: (detune > 0 ? getDetunePercent(detune) : "50") + "%",
}}></span>
</div>
<div className='text'>
<span>{pitch}</span>
</div>
</div>
</div>
<div className='tuning-btn'>
{!started ?
(<button onClick={() => {start()}}>Start</button>)
:
(<button onClick={() => {stop()}}>Stop</button>)
}
</div>
</div>
)
}
export default Tuner;
现在,我想做一个合适的吉他调音器。 这意味着不是渲染返回到屏幕的每个音高值。 我想将当前音高值与另一个“目标”值进行比较,并让 UI 元素根据当前音高是否与目标音高匹配做出不同反应。
一把标准吉他有 6 根弦……因此有 6 个目标音高
const standard = {
E: 82.41,
A: 110,
D: 146.8,
G: 196,
B: 246.9,
e: 329.6
}
我暂时试图为低 E 弦编码出这背后的逻辑,这就是我想出的。
查看 l.61- 82 的低 E 弦音 function 和 l.138...对于更改,我对 JSX 元素进行了更改
文件:Tuner.js
import React, { useState, useEffect } from 'react';
import AudioContext from '../contexts/AudioContext.js';
import autoCorrelate from "../libs/AutoCorrelate.js";
import {
noteFromPitch,
centsOffFromPitch,
getDetunePercent,
} from "../libs/Helpers.js";
const audioCtx = AudioContext.getAudioContext();
const analyserNode = AudioContext.getAnalyser();
const bufferlength = 2048;
let buf = new Float32Array(bufferlength);
let log = console.log.bind(console);
const noteStrings = [
"C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
"A",
"A#",
"B",
];
const standardStrings = ['A', 'D', 'G', 'B', 'e'];
const standard = {
E: 82.41,
A: 110,
D: 146.8,
G: 196,
B: 246.9,
e: 329.6
}
const dropD = {
D: 73.42,
A: 110,
D: 146.8,
G: 196,
B: 246.9,
E: 329.6
}
const Tuner = () => {
/*////AUDIO STATE////*/
const [source, setSource] = useState(null);
const [started, setStart] = useState(false);
const [pitchNote, setPitchNote] = useState("C");
const [pitchScale, setPitchScale] = useState("4");
const [pitch, setPitch] = useState("0 Hz");
const [detune, setDetune] = useState("0");
const [notification, setNotification] = useState(false);
/*Low E String */
const [ENote, setENote] = useState("E");
const [Epitch, setEPitchScale] = useState("2");
const [findingE, startFindingE] = useState(false);
const [onKey, isOnKey] = useState('Play');
const isE = () => {
let ac = autoCorrelate(buf, audioCtx.sampleRate);
if (ac > -1) {
let pitchValue = parseFloat(ac).toFixed(2);
log('ac:', ac);
log('pitchValue:', pitchValue);
if (standard.E - .75 <= pitchValue && pitchValue <= standard.E + .75) {
isOnKey('GOOD');
} else if (pitchValue <= standard.E - .75) {
isOnKey('b');
} else if (pitchValue >= standard.E - .75) {
isOnKey('#');
}
}
}
if (findingE) {setInterval(isE, 100)};
/*////UPDATES PITCH////*/
const updatePitch = (time) => {
analyserNode.getFloatTimeDomainData(buf);
var ac = autoCorrelate(buf, audioCtx.sampleRate);
if (ac > -1) {
let note = noteFromPitch(ac);
let sym = noteStrings[note % 12];
let scl = Math.floor(note / 12) - 1;
let dtune = centsOffFromPitch(ac, note);
setPitch(parseFloat(ac).toFixed(2) + " Hz");
setPitchNote(sym);
setPitchScale(scl);
setDetune(dtune);
setNotification(false);
// console.log(note, sym, scl, dtune, ac);
}
};
setInterval(updatePitch, 1);
useEffect(() => {
if (source) {
source.connect(analyserNode);
}
}, [source]);
const start = async () => {
const input = await getMicInput();
if (audioCtx.state === "suspended") {
await audioCtx.resume();
}
setStart(true);
setNotification(true);
setTimeout(() => setNotification(false), 5000);
setSource(audioCtx.createMediaStreamSource(input));
};
const stop = () => {
source.disconnect(analyserNode);
setStart(false);
};
const getMicInput = () => {
return navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
autoGainControl: false,
noiseSuppression: false,
latency: 0,
},
});
};
return (
<div className='tuner'>
<div className='notification' style={ notification ? {color:'white', backgroundColor: 'lightgrey'} : {color: 'white'}}>
Please, bring your instrument near to the microphone!
</div>
<div className ='tuner-container'>
<div className='screen'>
<div className='top-half'>
<span className='note-letter'
style={ (findingE && onKey === 'b' || findingE && onKey === '#' ) ? {color: 'red'} : (findingE && onKey === 'GOOD' ? r: 'lightgreen'} : {color: 'black'} )}>
{!findingE ? (pitchNote) : (ENote)}
</span>
<span style={ (findingE && onKey === 'b' || findingE && onKey === '#' ) ? {color: 'red'} : (findingE && onKey === 'GOOD' lor: 'lightgreen'} : {color: 'black'} )}className='note-number'>{!findingE ? (pitchScale) : (Epitch)}</span>
</div>
<div className='bottom-half'>
<span className='meter-left' style={{
width: (detune < 0 ? getDetunePercent(detune) : "50") + "%",
}}></span>
<span style={ (findingE && onKey === 'b' || findingE && onKey === '#' ) ? {color: 'red'} : (findingE && onKey === 'GOOD' lor: 'lightgreen'} : {color: 'black'} )} className='dial'>|</span>
<span className='meter-right' style={{
width: (detune > 0 ? getDetunePercent(detune) : "50") + "%",
}}></span>
</div>
<div className='pitch-text'>
<span style={ (findingE && onKey === 'b' || findingE && onKey === '#' ) ? {color: 'red'} : (findingE && onKey === 'GOOD' lor: 'lightgreen'} : {color: 'black'} )}>{!findingE ? (pitch) : (onKey)}</span>
</div>
</div>
</div>
<div className='tuning-btn'>
{!started ?
(<button onClick={() => {start()}}>Start</button>)
:
(<button onClick={() => {stop()}}>Stop</button>)
}
</div>
<div>
<div className='string'>
{!findingE ?
(<button onClick={() => {startFindingE(true)}}>E</button>)
:
(<button onClick={() => {startFindingE(false)}}>E</button>)
}
</div>
</div>
</div>
)
}
export default Tuner;
逻辑与此应用程序的第一个版本非常相似,具有嵌套状态和所有内容。 它同样有效
现在的问题是将其应用于剩余的 6 个目标音高。 显然,我不想为每个字符串单独写一个isNote
function 。 我想编写一个 function 从每个“吉他弦”按钮获取 innerHTML 字母,并根据我单击的按钮在屏幕上呈现不同的字母。
这让我想到了这个
文件:Tuner.js
const standardStrings = ['E', 'A', 'D', 'G', 'B', 'e'];
const standard = {
E: 82.41,
A: 110,
D: 146.8,
G: 196,
B: 246.9,
e: 329.6
}
/*////STANDARD TUNING////*/
const [standardNote, setStandardNote] = useState('');
const [standardPitch, setStandardPitch] = useState('');
const [findingStandard, startFindingStandard] = useState({finding: false, note: 'note', pitch: null});
const [onKey, isOnKey] = useState('play');
const standardTuning = (note) => {
const standard = {
E: [82.41, 2],
A: [110, 2],
D: [146.8, 3],
G: [196, 3],
B: [246.9, 3],
e: [329.6, 4]
}
let ac = autoCorrelate(buf, audioCtx.sampleRate);
let pitchValue = parseFloat(ac).toFixed(2);
log('pitchValue:', pitchValue);
log('standard[note]:', standard[note]);
if (ac > -1) {
startFindingStandard({...findingStandard, pitch: standard[note][1]})
if (standard[note][0] - .75 <= ac && ac <= standard[note][0] + .75) {
isOnKey('GOOD');
} else if (ac <= standard[note][0] - .75) {
isOnKey('b');
} else if (ac >= standard[note][0] - .75) {
isOnKey('#');
}
}
}
return (
<div className='tuner'>
<div className='notification' style={ notification ? {color:'white', backgroundColor: 'lightgrey'} : {color: 'white'}}>
Please, bring your instrument near to the microphone!
</div>
<div className ='tuner-container'>
<div className='screen'>
<div className='top-half'>
<span className='note-letter'
style={ (findingStandard.finding && onKey === 'b' || findingStandard.finding && onKey === '#' ) ? {color: 'red'} : (ngStandard.finding && onKey === 'GOOD' ? {color: 'lightgreen'} : {color: 'black'} )}>
{!findingStandard.finding ? (pitchNote) : (findingStandard.note)}
</span>
<span style={ (findingStandard.finding && onKey === 'b' || findingStandard.finding && onKey === '#' ) ? {color: 'red'} : ingStandard.finding && onKey === 'GOOD' ? {color: 'lightgreen'} : {color: 'black'} ssName='note-number'>{!findingStandard.finding ? (pitchScale) : (findingStandard.pitch)}</span>
</div>
<div className='bottom-half'>
<span className='meter-left' style={{
width: (detune < 0 ? getDetunePercent(detune) : "50") + "%",
}}></span>
<span style={ (findingStandard.finding && onKey === 'b' || findingStandard.finding && onKey === '#' ) ? {color: 'red'} : ingStandard.finding && onKey === 'GOOD' ? {color: 'lightgreen'} : {color: 'black'} )} className='dial'>|</span>
<span className='meter-right' style={{
width: (detune > 0 ? getDetunePercent(detune) : "50") + "%",
}}></span>
</div>
<div className='pitch-text'>
<span style={ (findingStandard.finding && onKey === 'b' || findingStandard.finding && onKey === '#' ) ? {color: 'red'} : ingStandard.finding && onKey === 'GOOD' ? {color: 'lightgreen'} : {color: 'black'} )}>{!findingStandard.finding ? (pitch) : ()}</span>
</div>
</div>
</div>
<div className='tuning-btn'>
{!started ?
(<button onClick={() => {start()}}>Start</button>)
:
(<button onClick={() => {stop()}}>Stop</button>)
}
</div>
<div>
{standardStrings.map((string) => {
return (
<div className='string'>
{!findingStandard.finding ?
(<button onClick={(e) => {startFindingStandard({...findingStandard, finding: true, note: e.target.innerHTML, pitch: }>{string}</button>)
:
(<button onClick={() => {startFindingStandard({...findingStandard, finding: false, note: 'note', pitch: '' {string}</button>)
}
</div>
)
})}
</div>
</div>
)
现在,由于重新呈现的次数太多,应用程序正在崩溃。
我不太确定如何处理这个。 我知道这是一条错误消息,通常在您将钩子嵌套到 useEffect 钩子中并且未能为其提供导致无限循环的依赖项时发生...但我还没有找到无限循环发生的位置。 与我尝试将音高与低 E 弦匹配时相比,逻辑并没有真正不同。
有什么想法吗? 更重要的是,有没有人对以后如何使用此错误消息调试类似问题有任何建议?
如果您需要更多信息,请告诉我。
我还将包括用于将音频数据转换为单个音高值的代码
文件:AutoCorrelate.js
const autoCorrelate = (buf, sampleRate) => {
let [SIZE, rms] = [buf.length, 0];
for (let i = 0; i < SIZE; i++) {
let val = buf[i];
rms += val * val;
}
rms = Math.sqrt(rms / SIZE);
if (rms < 0.01) {
// not enough signal
return -1;
}
let [r1, r2, thres] = [0, SIZE - 1, 0.2];
for (let i = 0; i < SIZE / 2; i++)
if (Math.abs(buf[i]) < thres) {
r1 = i;
break;
}
for (let i = 1; i < SIZE / 2; i++)
if (Math.abs(buf[SIZE - i]) < thres) {
r2 = SIZE - i;
break;
}
buf = buf.slice(r1, r2);
SIZE = buf.length;
let c = new Array(SIZE).fill(0);
for (let i = 0; i < SIZE; i++) {
for (let j = 0; j < SIZE - i; j++) {
c[i] = c[i] + buf[j] * buf[j + i];
}
}
let d = 0;
while (c[d] > c[d + 1]) {
d++;
}
let [maxval, maxpos] = [-1, -1];
for (let i = d; i < SIZE; i++) {
if (c[i] > maxval) {
maxval = c[i];
maxpos = i;
}
}
let T0 = maxpos;
let [x1, x2, x3] = [c[T0 - 1], c[T0], c[T0 + 1]];
let [a, b] =[ (x1 + x3 - 2 * x2) / 2, (x3 - x1) / 2]
if (a) {
T0 = T0 - b / (2 * a)
};
return sampleRate / T0;
};
module.exports = autoCorrelate;
我设法为所有 6 个目标球场找到了一个可行的解决方案,但我不确定问题出在哪里。 我只是从头开始重做它。 如果有人有见识,请随时分享。 谢谢你。
/*////STATE & HOOKS////*/
const strings = [['E', 2], ['A', 2], ['D', 3], ['G', 3], ['B', 3], ['e', 4]];
const standard = {
E: 82.41,
A: 110,
D: 146.8,
G: 196,
B: 246.9,
e: 329.6
}
const [note, setNote] = useState("C");
const [Epitch, setEPitchScale] = useState("4");
const [findingPitch, startFindingPitch] = useState(false);
const [onKey, isOnKey] = useState('Play');
const isStandard = () => {
let ac = autoCorrelate(buf, audioCtx.sampleRate);
if (ac > -1) {
let pitchValue = Number(pitch.split('').slice(0, -3).join(''));
log('buf:', buf);
log('audioCtx.sampleRate', audioCtx.sampleRate);
log('ac:', ac);
log('pitchValue:', pitchValue);
if (standard[note] - .75 <= pitchValue && pitchValue <= standard[note] + .75) {
isOnKey('GOOD');
} else if (pitchValue <= standard[note] - .75) {
isOnKey('b');
} else if (pitchValue >= standard[note] - .75) {
isOnKey('#');
}
}
}
if (findingPitch) {setInterval(isStandard, 100)};
/*////JSX////*/
return (
<div className='tuner'>
<div className='notification' style={ notification ? {color:'white', backgroundColor: 'lightgrey'} : {color: 'white'}}>
Please, bring your instrument near to the microphone!
</div>
<div className ='tuner-container'>
<div className='screen'>
<div className='top-half'>
<span className='note-letter'
style={ (findingPitch && onKey === 'b' || findingPitch && onKey === '#' ) ? {color: 'red'} : (findingPitch && onKey === 'GOOD' ? {color: 'lightgreen'} : {color: 'black'} )}>
{!findingPitch ? (pitchNote) : (note)}
</span>
<span style={ (findingPitch && onKey === 'b' || findingPitch && onKey === '#' ) ? {color: 'red'} : (findingPitch && onKey === 'GOOD' ? {color: 'lightgreen'} : {color: 'black'} )}className='note-number'>{!findingPitch ? (pitchScale) : (Epitch)}</span>
</div>
<div className='bottom-half'>
<span className='meter-left' style={{
width: (detune < 0 ? getDetunePercent(detune) : "50") + "%",
}}></span>
<span style={ (findingPitch && onKey === 'b' || findingPitch && onKey === '#' ) ? {color: 'red'} : (findingPitch && onKey === 'GOOD' ? {color: 'lightgreen'} : {color: 'black'} )} className='dial'>|</span>
<span className='meter-right' style={{
width: (detune > 0 ? getDetunePercent(detune) : "50") + "%",
}}></span>
</div>
<div className='pitch-text'>
<span style={ (findingPitch && onKey === 'b' || findingPitch && onKey === '#' ) ? {color: 'red'} : (findingPitch && onKey === 'GOOD' ? {color: 'lightgreen'} : {color: 'black'} )}>{!findingPitch ? (pitch) : (onKey)}</span>
</div>
</div>
</div>
<div className='tuning-btn'>
{!started ?
(<button onClick={() => {start()}}>Start</button>)
:
(<button onClick={() => {stop()}}>Stop</button>)
}
</div>
<div>
{strings.map((string) => {
return (
<div className='string'>
{!findingPitch ?
(<button onClick={(e) => {startFindingPitch(true); setNote(string[0]); setPitchScale(string[1])}}> {string[0]} </button>)
:
(<button onClick={() => {startFindingPitch(false)}}>{string[0]}</button>)
}
</div>
)
})}
</div>
</div>
)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.