我试图显示正在播放的音符的名称。如果我在超时时间内使用console.log(noteName),它将完美地显示每个音符的名称。但是,如果我尝试使用setState,它将呈现太多次。我错过了什么?
这个想法是,每一个音符在琶音显示,因为它正在发挥
const modes = {
'ionian': [0, 2, 4, 5, 7, 9, 11, 12],
'dorian': [0, 2, 3, 5, 7, 9, 10, 12],
'phrygian': [0, 1, 3, 5, 7, 8, 10, 12],
'lydian': [0, 2, 4, 6, 7, 9, 11, 12],
'mixolydian': [0, 2, 4, 5, 7, 9, 10, 12],
'aeolian': [0, 2, 3, 5, 7, 8, 10, 12],
'locrian': [0, 1, 3, 5, 6, 8, 10, 12],
};
const noteNames = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
const ArpeggioPlayer = () => {
const [mode, setMode] = useState('ionian');
const [isPlaying, setIsPlaying] = useState(false);
const [context, setContext] = useState(null);
const [noteName, setNoteName] = useState('');
useEffect(() => {
const AudioContext = window.AudioContext || window.webkitAudioContext;
setContext(new AudioContext());
}, []);
const startContext = async () => {
if (context.state === 'suspended') {
await context.resume();
}
};
useEffect(() => {
if (!context || !isPlaying) return;
startContext();
const rootNote = 60; // middle C
const scale = modes[mode];
let arpeggio = scale.map(i => rootNote + i);
arpeggio.forEach((note, i) => {
let newNoteName = noteNames[note % 12];
setTimeout(() => {
setNoteName(newNoteName);
const oscillator = context.createOscillator();
oscillator.frequency.value = 440 * Math.pow(2, (note - 69) / 12);
oscillator.connect(context.destination);
oscillator.start();
oscillator.stop(context.currentTime + 0.5);
}, i * 500);
});
}, [context, isPlaying, mode, startContext]);
return (
<div>
<select value={mode} onChange={e => setMode(e.target.value)}>
{Object.keys(modes).map(m => (
<option key={m} value={m}>{m}</option>
))}
</select>
<button onClick={() => setIsPlaying(!isPlaying)}>
{isPlaying ? 'Stop' : 'Start'}
</button>
<h1>{noteName}</h1>
</div>
);
};
export default ArpeggioPlayer;
1条答案
按热度按时间yx2lnoni1#
无论何时设置状态(例如调用
setNoteName
),组件重新呈现。每次重新呈现时,都会创建startContext
的新示例。由于第二个useEffect
块依赖于startContext
,因此它会触发forEach
循环,从而创建多个超时。但是,之前的超时未清除,因此会触发新超时和之前的超时。并且它们改变状态,这导致设置更多的超时,等等。您可以将
startContext
Package 到useCallback
中,也可以将其内联到useEffect
中,因为它在其他地方不使用。您的代码的主要问题是无法在播放过程中停止或切换。我将通过生成音频上下文并在同一个
useEffect
块中播放来重构您的代码。每当isPlaying
或mode
发生变化时,前一个context
就会关闭,并生成一个新的,从而停止当前播放的上下文。此外,如果有下一个音符,请使用
OscillatorNode
ended event来播放,而不是使用超时。一个二个一个一个