Skip to content

Instantly share code, notes, and snippets.

@aqandrew
Last active December 20, 2023 19:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aqandrew/99590331a431c9252f4db0b0cbd5325a to your computer and use it in GitHub Desktop.
Save aqandrew/99590331a431c9252f4db0b0cbd5325a to your computer and use it in GitHub Desktop.
React music visualizer
// my first React project, from February 2020
#main

React music visualizer

My first personal project using React! I'm totally fascinated by music visualizers, and this turned out to be a great introduction. I learned when to use stateless components, how to use refs, and the useState hook.

This was my first time working with the Web Audio API too. Huge thanks to Sarah Drasner, who clearly explained how to set up FFT analysis in one of her pens (see JS).

A Pen by Andrew Aquino on CodePen.

License.

// Shoutout to Sarah Drasner for heavily commenting her visualizer setup!
// https://codepen.io/sdras/pen/PVjPKa
const AMPLITUDE_MAX = 255;
function App() {
const [userClicked, setUserClicked] = React.useState(false);
// Chrome requires a user click/gesture before AudioContext can be created
// https://goo.gl/7K7WLu
return (
<div id="app">
{!userClicked ? (
<img onClick={() => setUserClicked(true)} src="https://t3.rbxcdn.com/e6f7d14da3ca488e03d069a68c2960a2" alt="start" />
) : (
// TODO Find a version without dog barking
<Visualizer frequencyCount={128} audioSource={'https://archive.org/download/whatisthemusictitle_201811/Darude%20-%20Sandstorm.mp3'} />
)}
</div>
)
}
class Visualizer extends React.Component {
constructor(props) {
super(props);
this.state = {
frequencyAmplitudes: Array.from(props.frequencyCount)
};
}
// Ensure this.audio has been set by ref callback
componentDidMount() {
// Wire up our audio
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
this.source = this.audioContext.createMediaElementSource(this.audioRef);
this.volumeControl = this.audioContext.createGain();
this.source.connect(this.audioContext.destination);
this.source.connect(this.volumeControl);
// Create our analyZer 🇺🇸
this.analyzer = this.audioContext.createAnalyser();
this.analyzer.fftSize = this.props.frequencyCount * 2;
this.volumeControl.connect(this.analyzer);
this.analyzer.connect(this.audioContext.destination);
// Analyzer will know user's set volume
this.volumeControl.gain.value = this.audioRef.volume;
// Kick off our animation
this.animate();
}
// Constantly get frequency data from audio so that amplitude can be updated
animate() {
let frequencyData = new Uint8Array(this.props.frequencyCount);
this.analyzer.getByteFrequencyData(frequencyData);
// Without the "bind(this)" silliness, we'll lose the component's context
requestAnimationFrame(this.animate.bind(this)); // lol
// Trigger React render with updated frequencyData
this.setState({
frequencyAmplitudes: Array.from(frequencyData).map(amplitude => amplitude * 100 / AMPLITUDE_MAX)
});
}
render() {
const bars = this.state.frequencyAmplitudes.map(val => {
return <Bar amplitude={val} />
});
return (
<div id="visualizer">
<div id="bars">
{bars}
</div>
<div id="controls">
<audio ref={element => this.audioRef = element} src={this.props.audioSource} controls crossorigin="anonymous"></audio>
</div>
</div>
)
}
}
function Bar({ amplitude }) {
return <div className="bar" style={{ height: amplitude + '%' }}></div>
}
ReactDOM.render(<App />, document.getElementById('main'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
html, body
height: 100%
body
margin: 0
#main,
#app,
#visualizer
height: 100%
#app
display: flex
justify-content: center
align-items: center
background: url("https://i.imgur.com/Zk6TR5k.jpg") center center / cover no-repeat
img
cursor: pointer
height: 80px
#visualizer
display: flex
flex-direction: column
width: 100%
padding: 4px
box-sizing: border-box
background: radial-gradient(white 30%, blue)
#controls
flex: 1
display: flex
justify-content: center
align-items: center
background: linear-gradient(dimgray, ghostwhite)
#bars
flex: 4
display: flex
justify-content: center
align-items: flex-end
background: black
padding-top: 4vh
padding-bottom: 4vh
.bar
width: 4px
margin-right: 1px
background: chartreuse
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment