Build a Guitar with Reactjs (2): Guitar Sounds

Build a Guitar with Reactjs (2): Guitar Sounds

·

10 min read

Hallo and welcome to part 2 of "How to build a Guitar with React" 🎸

In part 1, we've created the setup and already built the <GuitarBody /> component. It's still silent though, so in this part, we'll add the most important feature:

🔊 Sound 🔊

I once built a JavaScript piano, and while I was searching for a good resource for sound files, I came across audiosynth and was really impressed with the quality of the acoustic guitar sound. AudioSynth creates .wav files on-the-fly, very similar to a synthesizer. An "A" note on a piano is just like an "A" note on a guitar - a superposition of oscillators with frequency 440Hz and multiples of that. The difference lies in which overtones are audible, i.e. their amplitudes (this is over-simplified, but this is not an article about the theory of sound). Each instrument has its own characteristic sound profile, a set of rules how to superpose the waveforms of the base note and its overtones.

Creating the files dynamically has some advantages over pre-recorded sound files, the most obvious being that there's no extra files needed. Also, you can still modify the lengths and volumes of each tone.


🎸 Content of this part

  • getting AudioSynth and adding a few modifications to make it work in our app
  • implementing the functionality to play open strings on the body

🎸 Setting up the files

First of all, I'll add a utils folder to my src folder, to keep all the audio functions in one place. There'll be two files in it for now - one to generate the .wavs (audiosynth.js), and one to actually play them (sounds.js).

audiosynth.js

AudioSynth comes with some handy methods to create an instrument and play a note. All we have to do is add this to the bottom of the audiosynth.js file:

const guitar = Synth.createInstrument(2);
export { guitar }

This will give us an object with a .play method, which we'll later import in our sounds.js file.

The argument (2) defines the instrument to create - in our case, a guitar of course. You can make your guitar sound differently by changing that value - there's four sound profiles available: 0 = piano, 1 = organ, 2 = guitar, 3 = edm.

The original audiosynth.js file from the repo I've linked above uses pre-ES6 syntax, and I ran into some issues with undeclared variables when using it in React, so I've made some minor adjustments. You can find the updated file here on GitHub and just copy/paste the content, or give it a shot and make it work yourself. Feel free to dive deeper into the code, particularly if you're interested in how to create a sound file with JavaScript.

sounds.js

This file will import the guitar object from audiosynth.js and export a customised play function for the <GuitarBody /> component. I won't care for now which notes are played, I'll start with a first "proof of concept" to see if I can make my guitar play some sound at all.

First step - importing the guitar object into sounds.js:

import { guitar } from './audiosynth.js';

As mentioned above, this object comes with a .play method, and it takes three parameters:

  • the name of the note (a string in capital letters, like "C" or "C#")
  • the octave of the note (a number; a guitar starts on octave 2)
  • the duration of the note (a number; if omitted, the default is 2 seconds)

Adding a function that plays a hard-coded E-4 and exporting it:

sounds.js

import { guitar } from './audiosynth.js';

function playGuitarBody(){
    guitar.play('E', 4, 1.5)
}

export { playGuitarBody }

Importing that function into the <GuitarBody /> component and adding a click event listener to the guitar body:

GuitarBody.js

import { playGuitarBody } from '../utils/sounds.js';

...

return (
    <div className="guitar-body-container">
        <div
            className="guitar-body"
            onMouseMove={showStringsMarker}
            onMouseLeave={hideStringsMarker}

            onClick={playGuitarBody}
        >

...

A click on the strings - and our guitar makes its first sound 👶

🎸 A little helper function

The playGuitarBody function needs a note and an octave as parameter, but the <GuitarBody /> component doesn't know any note names, it only knows the currFrets array (passed down from <Guitar />) and the currStrings array (from its own state).

So the first thing I'm going to write is a function that can translate a pair of (string,fret) values to a pair of (note,octave) values. A full octave has the notes:

['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']

The lowest note on a guitar is an E-2 (that's an E on the 2nd octave), which would be the index 4 in the array. The second-lowest string is an A-2 (index 9). The highest string is an E-4, which is two octaves (24 half tones) above the lowest.

An array for all six open strings would be:

[4, 9, 14, 19, 23, 28]

For any given (string, fret) pair, all I have to do is start at the index corresponding to the string, and add the frets.

Putting all this together:

sounds.js

function getNote(string, fret){
    const notes = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
    const open = [4, 9, 14, 19, 23, 28];

    const index = open[string] + fret;

    const note = notes[index % 12];
    const octave = Math.floor(index / 12) + 2;

    return [note, octave]
}

This can be used now to write the actual play functions that will be imported by the <GuitarBody />.

🎸 The play functions

Playing on the guitar body means playing multiple strings/notes at once, depending on the current frets and currently active/selected strings. I'll start with a function that can play a single note:

sounds.js

function playNote(string, fret){
    const [note, octave] = getNote(string, fret);
    const duration = 1.5;
    guitar.play(note, octave, duration)
}

Instead of playing a hard-coded note, the playGuitarBody function can now iterate over the currFrets array, and play the respective note - if the corresponding string is currently active:

function playGuitarBody(strings, frets){
    frets.forEach((f, s) => strings.includes(s) && playNote(s, f))
}

Almost there! 🤟


One thing before we move on: I already mentioned in the previous article that the current strum direction only affects which strings get played, but it makes no audible difference. If you'd like to change that, and are up for a little extra challenge, you could pass strumDir as a third parameter to the playGuitarBody function, and play the notes with a little delay (around 100 ms) for each.


🎸 The final <GuitarBody /> component

As the playGuitarBody function now takes the strings and frets as parameters, we need a little modification in the click handler:

GuitarBody.js

<div className="guitar-body-container">
    <div
        className="guitar-body"
        onMouseMove={showStringsMarker}
        onMouseLeave={hideStringsMarker}

        onClick={() => playGuitarBody(currStrings, currFrets)}
    >

And that's it - we can play open strings on the guitar body now. Happy strumming!


🎸 Outlook for the next part

In part 3, we'll create the background image for the fretboard: a stateless component <FretboardBg /> that renders an inline SVG with dimensions and fret sizes just like on a real 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 👋