DEV Community

Cover image for Building Ada's Rainbow Piano
edensongjbs
edensongjbs

Posted on • Updated on

Building Ada's Rainbow Piano

Some Background

Learning Javascript has been both gratifying and frustrating in equal measure. There's an immediacy to it that I've found a lot of fun: seeing my code in action and easily manipulated in the browser! And then there's a whole bunch of stuff that left me scratching my head when my code breaks down (async, promises, scoping, callbacks, argh!!)

Starting out, I wanted to mark my progress with a fun little project for my 3 year old daughter Ada (which I later went on to build into a more full featured web app Ada's Ear Training. We've been working together for a while on some basic music theory and ear training. Her main introduction to playing music has been through these color coded pat bells that she received as a Christmas gift from her great grandma:

Pat bells

I thought it would be useful to introduce the concept of the piano keyboard using the same color coding (mapping each note in the C major scale to a specific color). Thus, my inspiration for Ada's rainbow piano.

If you're curious, below is a step by step tutorial on how I built it. And if you'd like to see and/or try out the code yourself, here's a link to the repository on GitHub

Tone.js

At the sonic heart of this project is the pleasantly intuitive Tone.js interactive music framework. It's a really powerful tool with various tone generators, oscillators, effects, and built in sampler, but for this project I really just scratched the surface of its capabilities. I ended up just downloading the source code and including it right inside my project directory, opting to avoid the ES6 import statements that were suggested in the README.

It's very easy to set up a basic synthesizer instance and patch it to the computer's audio output. I initially played around with the basic monophonic option for my initial tests, but went with a 4 voice polyphonic version for the finished code, so we can play full chords etc.

var synth = new Tone.PolySynth(4, Tone.Synth, {
oscillator : {
type : "square"
}
}).toMaster();

I've subsequently spent some time experimenting with sounds and linking it up to some real piano samples, but I've always had a fondness for a good ol' square wave, so this seems like a good place to start.

The UI Layout

After I was comfortable playing sounds and triggering attacks and releases for specific notes, it was time to build the user interface. Honestly, the most challenging bit about this little project wasn't the JavaScript, but the CSS styling! I hadn't ever written a ton of CSS, and positioning elements had always seemed a mysterious art form. This project proved to be excellent practice! (Note: For later projects using the keyboard, I decided to work with Flexbox and CSS Grid instead)

I started with one large <div> for the full octave keybed, and created an inner <div> for each separate key. I created separate classes for 'white-keys' and 'black-keys' and each key got its own id corresponding to its note letter and octave (C3, C#3, D3...B3, C4). This enabled me to make very specific CSS selections for intended coloring scheme, and provided an easy mechanism to send the corresponding note name of my mousedown events in order to play the intended note. It was important to specify that my key <div>'s were given CSS styling values of display: inline-block; so that they would display within the keybed <div> and not jump to the next line. The 'black-keys' class required need some additional styling: position: absolute; so they could overlap the 'white-keys' and be placed explicitly (make sure to specify a position attribute value for the enclosing keybed <div> as well or the black keys won't move with the rest of the keyboard!). It was a bit painstaking, but I specified a top and left value for each separate black key <div>.

Wiring it all up

Now that I knew enough of my way around Tone.js and had a fun and colorful UI I was basically pleased with, it was time to write my JS functions to get this thing working! I wanted to give the "user" (my 3yo) a couple options for trigger pitches: clicking on the trackpad and pressing keys on the keyboard. I wanted each to track both the mousedown/keydown events as well as the mouseup/keyup events for specifying the note attack and release.

I started by adding the event listeners for the "click" to each separate key <div>:

for (const note of allNotes) {
note.addEventListener('mousedown', () => {
playNote(event.target.id)
})
note.addEventListener('mouseup', () => {
releaseNote(event.target.id)
})
}

As mentioned above, each event sends the id of the clicked <div> element as a string parameter to the play/release functions.

I had to approach the keyup/keydown events slightly differently. I attached the event listener to the document DOM element and sent parameters to the play/release functions by way of a 'keyBindings' object that I created in order to avoid an undoubtedly ugly if/else/switch/case statement. The keys of the 'keyBindings' object are the characters of keys pressed and the values are the note names (matching the id of the 'key' <div>'s).

const keyBindings = {
"a": "C3",
"s": "D3",
"d": "E3",
"f": "F3",
"g": "G3",
"h": "A3",
"j": "B3",
"k": "C4",
"w": "C#3",
"e": "D#3",
"t": "F#3",
"y": "G#3",
"u": "A#3"
}

Final Touches

To provide a little visual interactivity to the rainbow piano, I decided to add some additional CSS styling to indicate when a note was currently playing. In this case, it was just a matter of adding an additional ("playing) class to the key <div> when the note attack is triggered and removing it when the release was triggered. Here are the updated playNote and releaseNote functions:

playNote = (note) => {
synth.triggerAttack(note)
const noteDiv = document.querySelector(
#${note.replace("#", '\#')}`)
noteDiv.classList.add('playing')
}

releaseNote = (note) => {
synth.triggerRelease(note);
const noteDiv = document.querySelector(#${note.replace("#", '\\#')})
noteDiv.classList.remove('playing')
}`

The additional styling subtly reduces the note's opacity to provide a highlighting effect when the note is played:

div.playing {
opacity: 0.7;
}

And that's basically it!

James and Ada playing with the piano

In Closing

The rainbow piano was definitely a fun first mini JavaScript project! Ada was pretty enthusiastic as well. At I mentioned earlier, I did go on to build this into a a more fully featured music theory and ear training application. I'm well aware that this is not the only "Build a JS Piano" tutorial out there, so I'll probably go back and read some of the others and see how the approaches differ. I'm somebody who enjoys the process of figuring things out for myself and struggling through the mistakes along the way, so I was hesitant to read any of those beforehand. Anyway, happy piano building!

The Full Ear Training App
Video Demo For Ear Training App

Top comments (1)

Collapse
 
veselinastaneva profile image
Vesi Staneva

That is a really cool way to learn JavaScript and spend time with your daughter! Keep going! 👍🙂
This reminded me of a story of a customer of ours that built a "Pokemon-Go-like" app with his 13-year-old son just to spend more quality time with him. Building apps With kids is so much more precious than simply building apps For them. :)