📈 Purpose 🔝
This was a project required to complete the Front End Development Libraries certification on freeCodeCamp. I found it to be an enjoyable and challenging project to work on. It taught me a lot about controlling state and passing props between components in React.
I decided on Chakra UI as the styling framework. It saved me some time that would have been spent worrying about the design, and I found it quite easy to work with.
🔎 Project Setup 🔝
I started out by creating my folders and component files, and linking them all together.
📦src
┣ 📂components
┃ ┣ 📜ButtonList.js
┃ ┣ 📜Display.js
┃ ┣ 📜DrumPad.js
┃ ┗ 📜Pad.js
┣ 📜App.js
┗ 📜index.js
I then installed my dependencies:
npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion
npm install react-icons
The icon package was needed for the "GraphicEQ" icon on the volume slider.
🌈 Setting Up a Theme 🔝
Following the documentation found on Chakra UI's Customize Theme page, I installed my needed fonts and created the colors for my theme.
npm install @fontsource/open-sans @fontsource/raleway
import * as React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ChakraProvider, extendTheme } from '@chakra-ui/react';
const theme = extendTheme({
fonts: {
heading: `'Open Sans', sans-serif`,
body: `'Raleway', sans-serif`
},
colors: {
light: {
100: 'rgba(247, 247, 248, 1)',
200: 'rgba(228, 227, 238, 1)'
},
pink: {
100: 'rgba(245, 188, 219, 1)',
200: 'rgba(253, 181, 161, 1)'
},
purple: {
100: 'rgba(180, 175, 220, 1)',
200: 'rgba(116, 9, 66, 1)',
300: 'rgba(98, 44, 111, 1)',
400: 'rgb(38, 0, 39)'
},
blue: {
100: 'rgba(175, 220, 217, 1)',
200: 'rgba(1, 143, 204, 1)',
300: 'rgba(36, 61, 135, 1)',
400: 'rgba(18, 27, 59, 1)'
}
}
});
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ChakraProvider theme={theme}>
<App />
</ChakraProvider>
);
Now I was ready to start working on the components.
📐 Building the Components 🔝
I usually start big and work smaller as I go along, so my first step was adding the wrapper to my App.js file. Using Chakra's documentation, I imported the elements I needed and got my container in the center of the screen. Then to finish up, I added a heading:
import { Center, Heading, Box } from '@chakra-ui/react';
function App() {
return (
<Center bg="purple.400" h="100vh" id="drum-machine">
<Box bg="purple.200" p={{ base: '5', md: '10' }} w={{ base: '95vw', md: '80vw' }} borderRadius="1rem">
<Heading>DRUM MACHINE</Heading>
</Box>
</Center>
);
}
export default App;
Now the easy part is done! |
I set up the grid for the buttons using Chakra's grid system.
import { Grid } from '@chakra-ui/react';
const DrumPad = () => {
return (
<Grid templateColumns="repeat(6, 1fr)" templateRows="repeat(3, 1fr)" gap={{ base: '3', md: '6' }}>
<Pad code="Q" />
<Pad code="W" />
<Pad code="E" />
<Display />
<Pad code="A" />
<Pad code="S" />
<Pad code="D" />
<Pad code="Z" />
<Pad code="X" />
<Pad code="C" />
</Grid>
);
};
export default DrumPad;
I can now add the DrumPad component to my App.js file. Of course, I now need to make and import the Pad and Display components. Once I've created those components and added some basic styling, the UI looks like this:
All of those buttons on the left were made using just one component. The Pad component is passed a prop called "code" and given a value from its parent, DrumPad. It can then display that value.
🎧 Creating the Audio Files 🔝
Since I'm a musician, I knew of several options for being able to sample drum sounds. In the end, I decided to go with Soundtrap. I settled on the 80s Drum Machine preset, and went to work creating the sounds I would need:
For each sound, I got an mp3 by clicking Save, then File -> Export -> Export project to mp3 file. The mixing process takes a while, so I had multiple tabs open to speed things up.
Once I was done, I trimmed up the audio using Audacity and saved everything in my public folder under a subfolder called sfx.
💭 Logic 🔝
I first needed a place to store all of the data related to each key. I did that in an object called ButtonList:
export const ButtonList = [
{
key: 'Q',
sound: `${process.env.PUBLIC_URL}/sfx/tom.mp3`,
desc: 'Tom'
},
{
key: 'W',
sound: `${process.env.PUBLIC_URL}/sfx/hh-closed.mp3`,
desc: 'Closed HH'
},
{
key: 'E',
sound: `${process.env.PUBLIC_URL}/sfx/hh-open.mp3`,
desc: 'Open HH'
},
{
key: 'A',
sound: `${process.env.PUBLIC_URL}/sfx/snare.mp3`,
desc: 'Snare'
},
{
key: 'S',
sound: `${process.env.PUBLIC_URL}/sfx/rim.mp3`,
desc: 'Rimshot'
},
{
key: 'D',
sound: `${process.env.PUBLIC_URL}/sfx/ride.mp3`,
desc: 'Ride'
},
{
key: 'Z',
sound: `${process.env.PUBLIC_URL}/sfx/kick.mp3`,
desc: 'Kick'
},
{
key: 'X',
sound: `${process.env.PUBLIC_URL}/sfx/hh-pedal.mp3`,
desc: 'HH Pedal'
},
{
key: 'C',
sound: `${process.env.PUBLIC_URL}/sfx/crash.mp3`,
desc: 'Crash'
}
];
Now I had access to each key, it's audio source location, and the description which would show on the display when a button or key was pressed.
My next challenge is deciding how to share data to the components who need that data. I mapped out my thought process:
Seeing it like this makes what I have to do look a lot more simple. The parent component will be the owner of all state variables, passing them down to the children when needed.
Now, I could write out the hooks needed:
Pad.js
- playSound():
- sets the volume of the audio file to the current volume, and resets the current time to 0
- plays the audio file
- changes the color of the key temporarily
- sets the key as the current "active" key
- handleKeyDown():
- calls playSound() if the key pressed was a match
- useEffect():
- adds an event listener to the page for any key presses and calls the handleKeyDown function
These are all wrapped in the useCallBack hook and passed the appropriate dependencies.
Display.js
- handleVolumeChange():
- waits for a change to the input slider
- updates the text to display the value selected
- updates the volume using setVolume
Once all the logic was implemented, all that was left to do was test it out and make sure all the tests pass. 🎊
If you made it this far, thanks for reading! ❤️ If you've started working on this project yourself, good luck! And if this helped you at all, please consider ⭐ starring the github repository!