Created
June 14, 2025 07:36
-
-
Save qtopie/927b3960ee26112f7a706c7826ea9cf6 to your computer and use it in GitHub Desktop.
节拍器
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { Button } from '@fluentui/react-components'; | |
| import { useCallback, useEffect, useRef, useState } from 'react'; | |
| const Metronome = () => { | |
| const [bpm, setBpm] = useState(120); // 每分钟节拍数 | |
| const [isPlaying, setIsPlaying] = useState(false); // 是否正在播放 | |
| const [volume, setVolume] = useState(50); // 音量 | |
| const audioContextRef = useRef<AudioContext | null>(null); // 音频上下文 | |
| const timerRef = useRef<number | null>(null); // 定时器引用 | |
| // 初始化 AudioContext | |
| useEffect(() => { | |
| if (!audioContextRef.current) { | |
| audioContextRef.current = new AudioContext(); | |
| } | |
| return () => { | |
| if (audioContextRef.current) { | |
| audioContextRef.current.close(); | |
| } | |
| }; | |
| }, []); | |
| // 播放节拍音效 | |
| const playTick = useCallback(() => { | |
| if (!audioContextRef.current) return; | |
| const oscillator = audioContextRef.current.createOscillator(); | |
| const gainNode = audioContextRef.current.createGain(); | |
| oscillator.connect(gainNode); | |
| gainNode.connect(audioContextRef.current.destination); | |
| oscillator.type = 'sine'; // 正弦波 | |
| gainNode.gain.setValueAtTime(volume / 100, audioContextRef.current.currentTime); | |
| oscillator.start(audioContextRef.current.currentTime); | |
| oscillator.stop(audioContextRef.current.currentTime + 0.1); // 播放 0.1 秒 | |
| }, [volume]); | |
| // 启动节拍器 | |
| const startMetronome = useCallback(() => { | |
| if (isPlaying) return; | |
| setIsPlaying(true); | |
| timerRef.current = window.setInterval(() => { | |
| playTick(); | |
| }, (60 / bpm) * 1000); | |
| }, [bpm, isPlaying, playTick]); | |
| // 停止节拍器 | |
| const stopMetronome = useCallback(() => { | |
| setIsPlaying(false); | |
| if (timerRef.current) { | |
| clearInterval(timerRef.current); | |
| timerRef.current = null; | |
| } | |
| }, []); | |
| // 切换节拍器状态 | |
| const toggleMetronome = () => { | |
| isPlaying ? stopMetronome() : startMetronome(); | |
| }; | |
| // 清理定时器 | |
| useEffect(() => { | |
| return () => { | |
| if (timerRef.current) { | |
| clearInterval(timerRef.current); | |
| } | |
| }; | |
| }, []); | |
| return ( | |
| <div className="min-h-screen bg-gray-900 text-white flex flex-col items-center justify-center p-4"> | |
| <h1 className="text-4xl font-bold mb-6">React 节拍器</h1> | |
| <div className="bg-gray-800 p-6 rounded-lg shadow-lg text-center"> | |
| <Button | |
| onClick={toggleMetronome} | |
| className={`w-full py-3 rounded-lg text-lg font-semibold ${isPlaying ? 'bg-red-500 hover:bg-red-600' : 'bg-blue-500 hover:bg-blue-600' | |
| }`} | |
| > | |
| {isPlaying ? '停止' : '启动'} | |
| </Button> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| export default Metronome; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment