253 lines
7.6 KiB
JavaScript
253 lines
7.6 KiB
JavaScript
import { faCode } from "@fortawesome/free-solid-svg-icons";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import React from "react";
|
|
import { Form } from "react-bootstrap";
|
|
import colorSets from "./variables.module.scss";
|
|
import { drumPadKeys } from "./drumPadKeys.js";
|
|
import "./App.scss";
|
|
|
|
class App extends React.Component {
|
|
constructor() {
|
|
super({});
|
|
const processedColorSets = {};
|
|
let noOfColorSets = 0;
|
|
for (const key in colorSets) {
|
|
if (/rgba/.test(colorSets[key])) {
|
|
processedColorSets[key] = colorSets[key].split(/(?<=\))\s/g);
|
|
} else if (/,\s/.test(colorSets[key])) {
|
|
processedColorSets[key] = colorSets[key].split(/,\s/g);
|
|
} else if (/\s/.test(colorSets[key])) {
|
|
processedColorSets[key] = colorSets[key].split(/\s/g);
|
|
} else if (/,[?=#]/.test(colorSets[key])) {
|
|
processedColorSets[key] = colorSets[key].split(/,/g);
|
|
} else {
|
|
console.log("Error: split pattern not implemented");
|
|
}
|
|
noOfColorSets = processedColorSets[key].length;
|
|
}
|
|
this.state = {
|
|
colorSets: processedColorSets,
|
|
noOfColorSets: noOfColorSets,
|
|
drumPadKeys: drumPadKeys,
|
|
power: true,
|
|
bank: false,
|
|
volume: 100,
|
|
displayPanelText: "Drum Machine",
|
|
latestKeyPressed: undefined,
|
|
};
|
|
this.updateColors = this.updateColors.bind(this);
|
|
this.togglePower = this.togglePower.bind(this);
|
|
this.togglePowerColor = this.togglePowerColor.bind(this);
|
|
this.handleVolumeChange = this.handleVolumeChange.bind(this);
|
|
this.toggleBank = this.toggleBank.bind(this);
|
|
this.displayDrumPadPress = this.displayDrumPadPress.bind(this);
|
|
this.handleKeyPresses = this.handleKeyPresses.bind(this);
|
|
this.updateColors();
|
|
}
|
|
|
|
handleKeyPresses(event) {
|
|
this.setState({
|
|
latestKeyPressed: String(event.key).toUpperCase(),
|
|
});
|
|
}
|
|
|
|
componentDidMount() {
|
|
document.addEventListener("keydown", this.handleKeyPresses, false);
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
document.removeEventListener("keydown", this.handleKeyPresses, false);
|
|
}
|
|
|
|
togglePower() {
|
|
this.setState((state) => ({
|
|
power: !state.power,
|
|
displayPanelText: `Power: ${!state.power ? "ON" : "OFF"}`,
|
|
}));
|
|
this.togglePowerColor();
|
|
}
|
|
|
|
displayDrumPadPress(note) {
|
|
this.setState({
|
|
displayPanelText: note,
|
|
latestKeyPressed: undefined,
|
|
});
|
|
}
|
|
|
|
togglePowerColor() {
|
|
if (!this.state.power) {
|
|
this.updateColors();
|
|
} else {
|
|
const root = document.documentElement;
|
|
root.style.setProperty("--custom-color", "#666");
|
|
root.style.setProperty("--custom-darker-color", "#555");
|
|
root.style.setProperty("--custom-super-light-color", "#eee");
|
|
}
|
|
}
|
|
|
|
toggleBank() {
|
|
this.setState((state) => ({
|
|
bank: !state.bank,
|
|
displayPanelText: !state.bank ? "Smooth Piano Kit" : "Heater Kit",
|
|
}));
|
|
if (this.state.power) {
|
|
this.updateColors();
|
|
}
|
|
}
|
|
|
|
handleVolumeChange(event) {
|
|
this.setState({
|
|
volume: event.target.value,
|
|
displayPanelText: `Volume: ${event.target.value}`,
|
|
});
|
|
}
|
|
|
|
updateColors() {
|
|
const randomColorIndex = Math.floor(
|
|
Math.random() * this.state.noOfColorSets
|
|
);
|
|
const root = document.documentElement;
|
|
root.style.setProperty("--bs-body-font-weight", "bold");
|
|
root.style.setProperty(
|
|
"--custom-color",
|
|
this.state.colorSets.colors[randomColorIndex]
|
|
);
|
|
root.style.setProperty(
|
|
"--custom-darker-color",
|
|
this.state.colorSets.darkerColors[randomColorIndex]
|
|
);
|
|
root.style.setProperty(
|
|
"--custom-super-light-color",
|
|
this.state.colorSets.superLightColors[randomColorIndex]
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const drumPadRows = this.state.drumPadKeys.map((row, rowIndex) => {
|
|
return (
|
|
<div
|
|
className="row justify-content-center"
|
|
id={`drum-pads-row-${rowIndex}`}
|
|
>
|
|
{row.map((note) => {
|
|
return (
|
|
<DrumPad
|
|
drumPadKey={note.key}
|
|
isKeyPressed={this.state.latestKeyPressed === note.key}
|
|
noteName={this.state.bank ? note.altName : note.name}
|
|
audioSrc={this.state.bank ? note.altAudioSrc : note.audioSrc}
|
|
power={this.state.power}
|
|
volume={this.state.volume}
|
|
colSize={Math.trunc(12 / row.length || 1)}
|
|
displayDrumPadPress={this.displayDrumPadPress}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
});
|
|
return (
|
|
<div
|
|
className="text-center d-flex align-items-center min-vh-100 custom-bg-color"
|
|
id="drum-machine"
|
|
>
|
|
<div className="container">
|
|
<div className="card">
|
|
<div className="card-body text-black">
|
|
<div
|
|
className="row justify-content-center justify-content-md-left"
|
|
id="drum-pads-and-controls"
|
|
>
|
|
<div className="col-md-7 col-12">{drumPadRows}</div>
|
|
<div className="col-md-3 col-sm-6 col-8">
|
|
<div className="row h-100">
|
|
<div className="col-12 mt-2 mb-0 my-md-0">
|
|
<p className="m-0">Power</p>
|
|
<Form.Switch
|
|
id="Power"
|
|
checked={this.state.power}
|
|
onChange={this.togglePower}
|
|
></Form.Switch>
|
|
</div>
|
|
<p
|
|
className="col-12 my-2 my-md-auto panel-bg-color"
|
|
id="display"
|
|
>
|
|
{this.state.displayPanelText}
|
|
</p>
|
|
<div className="col-12 my-2 my-md-auto">
|
|
<p className="m-0">Volume</p>
|
|
<Form.Range
|
|
onChange={this.handleVolumeChange}
|
|
value={this.state.volume}
|
|
/>
|
|
</div>
|
|
<div className="col-12 my-0">
|
|
<p className="m-0">Bank</p>
|
|
<Form.Switch
|
|
id="bank"
|
|
checked={this.state.bank}
|
|
onChange={this.toggleBank}
|
|
></Form.Switch>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<footer className="mt-2">
|
|
<p>
|
|
<a
|
|
className="link-light"
|
|
href="https://radii.dev/freeCodeCamp.org-Front-End-Dev-Libraries/Build-a-Drum-Machine"
|
|
>
|
|
<FontAwesomeIcon icon={faCode} /> {`Source Code & License`}
|
|
</a>
|
|
</p>
|
|
</footer>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
class DrumPad extends React.Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {};
|
|
this.handleClick = this.handleClick.bind(this);
|
|
}
|
|
|
|
componentDidUpdate() {
|
|
if (this.props.isKeyPressed) {
|
|
this.handleClick();
|
|
}
|
|
}
|
|
|
|
// TO-DO: refactor by handling props change here
|
|
handleClick() {
|
|
this.props.displayDrumPadPress(this.props.noteName);
|
|
this.audio = new Audio(this.props.audioSrc);
|
|
this.audio.currentTime = 0;
|
|
this.audio.volume = this.props.volume / 100;
|
|
if (this.props.power) {
|
|
this.audio.play();
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return (
|
|
<button
|
|
id={this.props.drumPadKey}
|
|
className={`drum-pad btn col-${this.props.colSize} m-1 custom-el-color`}
|
|
style={{ height: 80, width: 100, fontWeight: "bold" }}
|
|
onClick={this.handleClick}
|
|
>
|
|
{this.props.drumPadKey}
|
|
</button>
|
|
);
|
|
}
|
|
}
|
|
|
|
export default App;
|