Build a Guitar with Reactjs (4): The Mute Buttons

Subscribe to my newsletter and never miss my upcoming articles

Hi and welcome to part 4 of "How to build a Guitar with React" 🎸

In part 3, we've added the fretboard component, and the background SVG as its first child component. In this part, we'll add the buttons to mute certain strings, so they'll be silent when playing on the guitar body.

I'll start with a reminder again what the final fretboard will look like:

fretboard.jpg

The <MuteBtns /> component consists of 6 checkboxes to toggle between muted/unmuted, which means I'll have to store the information somewhere, and also make it accessible to the <GuitarBody />. I'm already passing down the currFrets array (the one we created in the first part of this series and that has since collected dust), so I'll use that now to determine the current state of each checkbox.

The values in that array can be one of the following:

  • a positive number for a fretted string (to be implemented in the next part)
  • a negative number for a muted string
  • 0 or -0 for an open string

I had considered to just set the value of a muted string to -1, but then the string would forget where it was fretted before it got muted, so instead I'll toggle between positive and negative numbers.

Getting the boilerplate out of the way:


🎸 Setup

First, adding another file MuteBtns.js to the Components folder. It'll need a function to update the currFrets array, so I'm already including that:

MuteBtns.js

import React from 'react';

function MuteBtns({ currFrets, updateCurrFrets }){

    return (
        <div className="mute-btns">

        </div>
    )
}

export default MuteBtns

Importing and rendering it in the <Fretboard /> component:

Fretboard.js

import React from 'react';
import MuteBtns from './MuteBtns.js';
import FretboardBg from './FretboardBg.js';

function Fretboard({ currFrets, updateCurrFrets }){

    return (
        <div className="fretboard-container">
            <MuteBtns
                currFrets={currFrets}
                updateCurrFrets={updateCurrFrets}
            />
            <FretboardBg />
        </div>
    )
}

export default Fretboard;

And finally, passing down currFrets and updateCurrFrets from the <Guitar /> component. We'll build the function along the way, for now I'll just include an empty function to avoid having an error thrown at me:

Guitar.js

import React, { useState } from 'react';
import GuitarBody from './GuitarBody.js';
import Fretboard from './Fretboard.js';

function Guitar(){

    const [currFrets, setCurrFrets] = useState([0,0,0,0,0,0]);

    function updateCurrFrets(){

    }

    return (
        <div className="guitar">
            <GuitarBody
                currFrets={currFrets}
            />
            <Fretboard
                currFrets={currFrets}
                updateCurrFrets={updateCurrFrets}
            />
        </div>
    )
}

export default Guitar;

Everything's set up, so let's npm start 🚀 with the mute buttons.


🎸 The <MuteBtns /> component

The mute buttons will be checkbox inputs, and their values (checked/unchecked) will be controlled by the currFrets array.

  • if the fret has a value of 0 or >0, the fret is unmuted and the checkbox is unchecked
  • if the fret has a value of -0 or <0, the fret is muted and the checkbox is checked

To toggle between those two, each checkbox will have an onChange handler, which will update the currFrets array by multiplying the corresponding fret value with -1.

The JSX

Let's first build the JSX by mapping over the currFrets array.

I'll include a Boolean isChecked for the respective checkbox attribute, but I can't use the identity operator === to check if an open string is muted, because it doesn't differentiate between 0 and -0:

0 === -0 // true

So I'll use the lesser known comparison method Object.is(). That method returns the same as the identity operator in almost all cases - with two exceptions:

0 === -0 // true
Object.is(0, -0) // false

NaN === NaN // false
Object.is(NaN, NaN) // true

It makes the assignment of isChecked a little weird to read, but that can't be helped:

function MuteBtns({ currFrets, updateCurrFrets }){
    return (
        <div className="mute-btns">
            {currFrets.map((fret, i) => {

                const isChecked = fret < 0 || Object.is(fret, -0);

                return (
                    <div key={i} className="mute-btn">
                        <input
                            id={`mute-${i}`}
                            type="checkbox"
                            checked={isChecked}
                            onChange={() => updateCurrFrets(i)}
                        />
                        <label htmlFor={`mute-${i}`}></label>
                    </div>
                )
            })}
        </div>
    )
}

The onChange event handler

Right now, we're only using the updateCurrFrets function to toggle between muted/unmuted. The parameter passed into it in <MuteBtns /> is the index i from the .map function, which corresponds to the string it's supposed to mute/unmute.

A first version of the function would now look like this:

Guitar.js

function updateCurrFrets(string){
    let newFrets = [...currFrets];
    newFrets[string] = newFrets[string] * -1;
    setCurrFrets(newFrets);
}

The CSS

The functionality is there now, the currFrets get updated, but our layout is messed up, the order of the checkboxes is reversed, and it's also weird to see a checked box for a muted string, but we can handle all that with CSS. Instead of showing the actual checkbox, I'll make use of the well-known "checkbox-hack", and inject a ::before and ::after pseudo element to the <label> to show a red X when a box is checked. I'll also flex-reverse their order:

.mute-btns {
    display:flex;
    flex-direction:column-reverse;
}
.mute-btn input {
    width:0;
    height:0;
    position:absolute;
    opacity:0;
}
.mute-btn label {
    width:16px;
    height:16px;
    display:block;
    background:rgba(204, 187, 170, 0.5);
    border:1px solid #fff;
    border-radius:2px;
    position:relative;
    cursor:pointer;
}
.mute-btn input:checked + label::before,
.mute-btn input:checked + label::after {
    content:'';
    width:2px;
    height:13px;
    position:absolute;
    top:0;
    left:6px;
    background:#a00;
}
.mute-btn input:checked + label::before {
    transform:rotate(45deg);
}
.mute-btn input:checked + label::after {
    transform:rotate(-45deg);
}

Now everything looks good again as it should. It just doesn't work yet, which we can test by muting some strings and then playing on the guitar body - so let's head over to the sounds.js file.


🎸 Updating sounds.js

Right now, the playGuitarBody function only checks the currStrings array. To also check if a string is muted, I'll write a mini helper function isMuted for the sake of readability, and add a second short-circuit condition:

sounds.js

function isMuted(fret){
    return fret < 0 || Object.is(fret, -0);
}

function playGuitarBody(currFrets, currStrings){
    currFrets.forEach((f,s) => currStrings.includes(s) && !isMuted(f) && playNote(s,f))
}

So there it is, we can strum up and down the guitar body, we can mute strings, but annoyingly, we can still only play six notes for six open strings, so it's definitely time to change that.


🎸 Outlook for the final part

In part 5, we'll...

  • add the final component <FretMarkers /> to the fretboard
  • include the functions to show where each string is fretted/marked
  • include the possibility to play single notes on the fretboard

(Code for this part here 👉 on GitHub)


🎸 Thank you for reading!

I hope you enjoyed coding along so far. If you found any mistakes, if something isn't working as expected, if you see room for improvement anywhere, or if you just happen to like this post, I'd appreciate your feedback in the comment section below. You can also @ me on Twitter. If you'd like to read more like this, I invite you to subsribe to my newsletter. Until next time 👋

No Comments Yet