| import React, { useRef, useEffect, useState } from 'react'; |
| import './App.css'; |
|
|
| function App() { |
| const audioContextRef = useRef(null); |
| const analyserRef = useRef(null); |
| const gainNodeRef = useRef(null); |
| const bassFilterRef = useRef(null); |
| const trebleFilterRef = useRef(null); |
| const sourceRef = useRef(null); |
| const canvasRef = useRef(null); |
| const animationFrameIdRef = useRef(null); |
|
|
| const [isPlaying, setIsPlaying] = useState(false); |
| const [volume, setVolume] = useState(0.7); |
| const [bass, setBass] = useState(0); |
| const [treble, setTreble] = useState(0); |
|
|
| useEffect(() => { |
| |
| audioContextRef.current = new (window.AudioContext || window.webkitAudioContext)(); |
| analyserRef.current = audioContextRef.current.createAnalyser(); |
| gainNodeRef.current = audioContextRef.current.createGain(); |
| bassFilterRef.current = audioContextRef.current.createBiquadFilter(); |
| trebleFilterRef.current = audioContextRef.current.createBiquadFilter(); |
|
|
| |
| bassFilterRef.current.type = 'lowshelf'; |
| bassFilterRef.current.frequency.value = 250; |
| trebleFilterRef.current.type = 'highshelf'; |
| trebleFilterRef.current.frequency.value = 4000; |
|
|
| |
| |
| bassFilterRef.current.connect(trebleFilterRef.current); |
| trebleFilterRef.current.connect(gainNodeRef.current); |
| gainNodeRef.current.connect(analyserRef.current); |
| analyserRef.current.connect(audioContextRef.current.destination); |
|
|
| |
| gainNodeRef.current.gain.value = volume; |
|
|
| |
| return () => { |
| if (animationFrameIdRef.current) { |
| cancelAnimationFrame(animationFrameIdRef.current); |
| } |
| if (audioContextRef.current) { |
| audioContextRef.current.close(); |
| } |
| }; |
| }, []); |
|
|
| useEffect(() => { |
| if (gainNodeRef.current) { |
| gainNodeRef.current.gain.value = volume; |
| } |
| }, [volume]); |
|
|
| useEffect(() => { |
| if (bassFilterRef.current) { |
| bassFilterRef.current.gain.value = bass; |
| } |
| }, [bass]); |
|
|
| useEffect(() => { |
| if (trebleFilterRef.current) { |
| trebleFilterRef.current.gain.value = treble; |
| } |
| }, [treble]); |
|
|
| const loadAndPlayAudio = async () => { |
| if (sourceRef.current) { |
| sourceRef.current.stop(); |
| sourceRef.current.disconnect(); |
| } |
|
|
| |
| |
| const response = await fetch('https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'); |
| const arrayBuffer = await response.arrayBuffer(); |
| const audioBuffer = await audioContextRef.current.decodeAudioData(arrayBuffer); |
|
|
| sourceRef.current = audioContextRef.current.createBufferSource(); |
| sourceRef.current.buffer = audioBuffer; |
| sourceRef.current.loop = true; |
| sourceRef.current.connect(bassFilterRef.current); |
| sourceRef.current.start(0); |
| setIsPlaying(true); |
| drawVisualizer(); |
| }; |
|
|
| const togglePlayPause = () => { |
| if (audioContextRef.current.state === 'suspended') { |
| audioContextRef.current.resume(); |
| } else if (audioContextRef.current.state === 'running' && isPlaying) { |
| audioContextRef.current.suspend(); |
| } else if (audioContextRef.current.state === 'suspended' && !isPlaying) { |
| audioContextRef.current.resume(); |
| } |
|
|
| if (!isPlaying && !sourceRef.current) { |
| loadAndPlayAudio(); |
| } else { |
| setIsPlaying(!isPlaying); |
| } |
| }; |
|
|
| const drawVisualizer = () => { |
| const canvas = canvasRef.current; |
| if (!canvas) return; |
| const ctx = canvas.getContext('2d'); |
| const WIDTH = canvas.width; |
| const HEIGHT = canvas.height; |
|
|
| analyserRef.current.fftSize = 256; |
| const bufferLength = analyserRef.current.frequencyBinCount; |
| const dataArray = new Uint8Array(bufferLength); |
|
|
| const renderFrame = () => { |
| animationFrameIdRef.current = requestAnimationFrame(renderFrame); |
|
|
| analyserRef.current.getByteFrequencyData(dataArray); |
|
|
| ctx.clearRect(0, 0, WIDTH, HEIGHT); |
| ctx.fillStyle = 'rgb(0, 0, 0)'; |
| ctx.fillRect(0, 0, WIDTH, HEIGHT); |
|
|
| const barWidth = (WIDTH / bufferLength) * 2.5; |
| let x = 0; |
|
|
| for (let i = 0; i < bufferLength; i++) { |
| const barHeight = dataArray[i] / 2; |
|
|
| ctx.fillStyle = 'rgb(' + (barHeight + 100) + ',50,50)'; |
| ctx.fillRect(x, HEIGHT - barHeight / 2, barWidth, barHeight); |
|
|
| x += barWidth + 1; |
| } |
| }; |
|
|
| renderFrame(); |
| }; |
|
|
| |
| useEffect(() => { |
| if (window.Telegram && window.Telegram.WebApp) { |
| window.Telegram.WebApp.ready(); |
| window.Telegram.WebApp.expand(); |
| console.log('Telegram Mini App initialized'); |
| |
| |
| |
| } |
| }, []); |
|
|
| return ( |
| <div className="App"> |
| <header className="App-header"> |
| <h1>HCY Audio System Clone</h1> |
| <canvas ref={canvasRef} width="300" height="100"></canvas> |
| <div className="controls"> |
| <button onClick={togglePlayPause}> |
| {isPlaying ? 'Pause' : 'Play'} |
| </button> |
| <div> |
| <label>Volume:</label> |
| <input |
| type="range" |
| min="0" |
| max="1" |
| step="0.01" |
| value={volume} |
| onChange={(e) => setVolume(parseFloat(e.target.value))} |
| /> |
| </div> |
| <div> |
| <label>Bass:</label> |
| <input |
| type="range" |
| min="-20" |
| max="20" |
| step="1" |
| value={bass} |
| onChange={(e) => setBass(parseFloat(e.target.value))} |
| /> |
| </div> |
| <div> |
| <label>Treble:</label> |
| <input |
| type="range" |
| min="-20" |
| max="20" |
| step="1" |
| value={treble} |
| onChange={(e) => setTreble(parseFloat(e.target.value))} |
| /> |
| </div> |
| </div> |
| </header> |
| </div> |
| ); |
| } |
|
|
| export default App; |
|
|