Codementor Events

Let's write a piano chord visualization application - a vanilla JS beginner-friendly tutorial—part I.

Published May 23, 2022Last updated Apr 15, 2024
Let's write a piano chord visualization application - a vanilla JS beginner-friendly tutorial—part I.

In my debut post, I'd like to walk you through the creation of a simple chord visualization application.

We aim to create an extensible application that doesn't use any popular JavaScript framework. Instead, we want to learn (focus) more on the native JavaScript API. Of course, it's excellent to use React or Angular, but writing something in vanilla JS wouldn't hurt. Moreover, it will help us understand JavaScript functions more.

Contents (click to move to the specific section):

  1. Minimum music theory explanation for better topic understanding
  2. Setup
  3. [HTML/CSS] Model of a piano keyboard
    3.1 Complete code
  4. [HTML/CSS] Chord root and type selection
    4.1 Complete code
  5. [JS] Chord generation
    5.1 Complete code
  6. [HTML/JS] Putting all together

Minimum music theory explanation for better topic understanding

Before we move to the practical part, I'd like to briefly present the basic theory of chord construction in music.

In music theory, we have twelve semitones (and 17 notes naming them). The notes are C, C# (Db), D, D# (Eb), E, F, F# (Gb), G, G# (Ab), A, A# (Bb), B. In parentheses, you can see the alternative name of the semitone. In our project, we use only notation with "sharps" (#). You will find more on the topics in the links at the end of the section.

notes_in_octave.png
In two octaves, each octave repeats the same twelve notes.

Notes repeat in the same pattern, which means that after the "B" note, there is again "C," "C#," "D," and so on.

What is a chord?
A chord is a set of a minimum of three notes played concurrently or in arpeggio (played one after another) that creates harmony.

A chord's name is created by taking its first note and the type. The chord's type defines the number of notes and the interval between each note.

Music interval is the number of semitones between two notes.

Example.:
Let's take the C major chord. The chord's base note is C, and the type is major.

"Major" type chord contains three notes. The interval between the first and second notes is four semitones. The interval between the second and third note is three semitones.

[first note] <-- 4 semitones --> [second note] <-- 3 semitones --> [third note]

The first interval is four semitones. We start our count from note C as the chord name is C. Our second note is E. (C - [C#, D, D#] - E). Then from E, let's count three semitones. It gives us note G. (E - [F, F#] - G).

c_maj_build.png
Intervals between each note of the C major chord.

The C major chord contains of notes C-E-G.

Piano keybaord layout

When we look at the piano, we'll see 88 keys. That is because the piano keyboard contains 7 octaves (with additional four keys). So each one has seven white keys and five black keys. Each key takes one semitone, as presented in the image below.

two_octaves.png
Two octaves on piano keyboard

After getting familiar with basic theory, we can move forward with creating our application.

Section knowledge base
Here you will find links to helpful articles on the topic I don't cover thoroughly in this section.

Piano Notes and Keys – How to Label Piano Keys by piano-keyboard-guide.com - All about piano keyboard layout and key naming
Chord by wikipedia.org - Theory on chord naming and building


Setup

Our project will not use any tool requiring the usage of NodeJS, which means all you need is any available code editor and browser.

One of the best code editors available for free is Visual Studio Code.

Project structure
The structure will be pretty straightforward. Files you have to create are:

  • index.html
  • index.js
  • piano-keyboard.css

project_structure.png
Project structure

Content of index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf8" />
    <title>Piano chord visualizer</title>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.2/font/bootstrap-icons.css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor"
      crossorigin="anonymous"
    />
    <link rel="stylesheet" href="piano-keyboard.css" />
  </head>
  <body class="d-flex flex-column h-100 min-vh-100">
    <main class="bg-secondary text-white flex-grow-1">
      <div class="container">
        <div class="row mt-3">
          <div class="col-md-12">
            <h1>Piano chord visualizer</h1>
          </div>
        </div>
        <div class="row mt-5">
          <div class="col-md-6">
            <!-- chord root and type selection -->
          </div>
        </div>
        <div class="col-md-6 d-flex justify-content-center flex-wrap">
          <!-- piano keyboard layout -->
        </div>
      </div>
    </main>
  </body>
</html>

The code presents our starting index.html contents. You can notice that I used the Bootstrap CSS framework. Bootstrap allows us to create good-looking pages quickly.

You can check out Bootstrap framework and learn more here.

Most of the class names used in html tags will come from Bootstrap except the HTML code of piano keyboard layout.

Contents of index.js and piano-keyboard.css stay empty, as we'll progressively add code as we continue further in the article.


[HTML/CSS] Model of a piano keyboard

We're starting with creating an HTML and CSS representation of a piano keyboard. We will later use it to highlight keys belonging to a specified chord.

As we know from the previous section, the piano keyboard has 7 octaves. Each octave has seven white keys and five black keys.

In an octave: White keys are placed next to each other, and black keys are placed between white keys 1/2, 2/3, 4/5, 5/6, and 6/7.

keyboard_clean.png
Two octave piano keyboard

1. HTML structure

<div id="piano-keyboard">
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-sharp"></div>
  <div class="key key-natural"></div>
  <div class="key key-natural"></div>
</div>

The structure is pretty simple. Let's explain it a bit more:

  • <div id="piano-keyboard"> is the wrapper for all the divs representing keyboard keys,
  • <div class="key key-natural"></div> represents the white keys,
  • <div class="key key-sharp"></div> represents the black keys,

In index.html, replace the code <!-- piano keyboard layout --> with the piano keyboard structure code.

2. CSS styles
Now we will style our HTML structure. All the styles we'll be writing in the piano-keyboard.css.

Styles for the wrapper.

#piano-keyboard {
  position: relative;
  display: flex;
  justify-content: flex-start;
}

We use display: flex; and justify-content: flex-start; to align child elements in the div to the left side. position: relative is set to help us later position <div class="key key-sharp"></div> elements.

We have our keys wrapper ready. Time to style the natural keys of our piano keyboard.

:root {
  --natural-key-width: 48px;
  --natural-key-height: 200px;
}

.key-natural {
  position: relative;
  width: var(--natural-key-width);
  height: var(--natural-key-height);
  border: 1px solid black;
  box-sizing: border-box;
  background: #fffff0; /* ivory color */
}

Here you can see the use of CSS variable. It will come in handy when we get to the styling of the black key elements. The advantage of a variable is that we can resize our keyboard with a single change instead of having it changed everywhere where width or height changes.

We have set our keys to be of a width of 48 pixels and a height of 200 pixels. In addition, we set the border to 1px solid black to separate each key visually. I decided to go with an ivory color for the keys color.

Our piano keyboard should look like this.
keyboard_wip_01.png
Piano keyboard showing white keys only

Now we add black keys styling to our piano keyboard.

:root {
  --natural-key-width: 48px;
  --natural-key-height: 200px;
  --sharp-key-width: 30px; /* new variable for black key styling */
  --sharp-key-height: 100px; /* new variable for black key styling */
}

.key-sharp {
  position: relative;
  width: var(--sharp-key-width);
  height: var(--sharp-key-height);
  background: #36454f; /* charcoal color */
  box-sizing: border-box;
}

Now our piano keyboard presents like this.

keyboard_wip_02.png
Piano keyboard with unpositioned keys

We can say it's not what we'd like to have displayed. We'll fix it by adding positioning to the key elements.

keyboard_wip_03.png
Black keys placed between make a gap in white key layout

First we'll remove gap between white keys created by adding black key styling.

.key-sharp + .key-natural {
  margin-left: calc(-1 * var(--sharp-key-width)); /* calculating a negative margin equal to black key width */ 
}

NOTE: The adjacent sibling combinator (+) separates two selectors and matches the second element only if it immediately follows the first element, and both are children of the same parent element. Adjecent sibling comparator by developer.mozilla.org

CSS selector .key-sharp + .key-natural targets all .key-natural elements following .key-sharp. This way, we only change the position where the gap is present. However, we have covered (overlayed) the black keys after this change. We will fix it by setting the z-index attribute on .key-sharp.

.key-sharp {
  position: relative;
  width: var(--sharp-key-width);
  height: var(--sharp-key-height);
  background: #36454f; /* charcoal color */
  box-sizing: border-box;
  z-index: 1; /* increasing z-index value to put element in front, when it's stacked with other html elements */
}

Our piano keyboard looks almost perfect.

keyboard_wip_04.png
Piano keyboard with unaligned black keys

One of the last things to finish is moving the black keys to the left, so the middle of the key is placed on the white keys joint. We'll move .key-sharp to the left by 50% of its width.

.key-sharp {
  position: relative;
  width: var(--sharp-key-width);
  height: var(--sharp-key-height);
  background: #36454f; /* charcoal color */
  box-sizing: border-box;
  z-index: 1; /* increasing z-index value to put element in front, when it's stacked with other html elements */
  transform: translateX(-50%); /* move element to the left by 50% of its width */
}

Our piano keyboard is now ready.

keyboard_wip_05.png
Aligned piano keyboard layout

The last thing to do is add styles for the "active" class. "active" class will indicate the note of the chord.

.key-natural.active::after,
.key-sharp.active::after {
  content: ""; /* set content of pseudo-element to be empty */
  position: absolute; /* absolute positioning */
  bottom: 10px; /* set pseudo-element to be 10 pixels from the bottom of parent */
  left: 50%; /* move pseudo-element to the right by 50% of parents width */
  transform: translateX(-50%); /* move pseudo-element to the left by its width */
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background-color: red;
}

NOTE: In CSS, ::after creates a pseudo-element that is the last child of the selected element. It is often used to add cosmetic content to an element with the content property. It is inline by default. ::after (:after) by developer.mozilla.org

We use a pseudo-class selector ":after," absolute positioning and transform. We must set the content property to make the pseudo-element visible. It can be empty; however, it must be present to make our element visible. Then we set the attribute position to absolute. That means the element is relatively positioned to the closest ancestor with the position attribute present. In our case, our piano key div is the first positioned element to the pseudo-element. Next, we place the "active" indicator 10 pixels from the key edge and horizontally center.

horizontal_centering.png
Horizontal centering

We move the element to the left by 50% of its width. Next, we move the element to the right by 50% of its parent width. Then we give our active indicator a round shape by adding border-radius: 50% and making it red with background-color: red. The size of the indicator is 15 pixels by 15 pixels.

To make the piano key active, we add the class name active. Ex.:

<div class="key key-natural active"></div>

keyboard_wip_06.png
Piano key with active notes C, E and G

[HTML/CSS] Model of a piano keyboard - complete code

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf8" />
    <title>Piano chord visualizer</title>
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.2/font/bootstrap-icons.css"
    />
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor"
      crossorigin="anonymous"
    />
    <link rel="stylesheet" href="piano-keyboard.css" />
  </head>
  <body class="d-flex flex-column h-100 min-vh-100">
    <main class="bg-secondary text-white flex-grow-1">
      <div class="container">
        <div class="row mt-3">
          <div class="col-md-12">
            <h1>Piano chord visualizer</h1>
          </div>
        </div>
        <div class="row mt-5">
          <div class="col-md-6">
            <!-- chord root and type selection -->
          </div>

          <div class="col-md-6 d-flex justify-content-center flex-wrap">
            <div id="piano-keyboard">
              <div class="key key-natural active"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural active"></div>
              <div class="key key-natural"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural active"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural"></div>
              <div class="key key-natural"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural"></div>
              <div class="key key-natural"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural"></div>
              <div class="key key-sharp"></div>
              <div class="key key-natural"></div>
              <div class="key key-natural"></div>
            </div>
          </div>
        </div>
      </div>
    </main>
  </body>
</html>

piano-keyboard.css

:root {
  --natural-key-width: 48px;
  --natural-key-height: 200px;
  --sharp-key-width: 30px;
  --sharp-key-height: 100px;
}

#piano-keyboard {
  position: relative;
  display: flex;
  justify-content: flex-start;
}

.key-natural {
  position: relative;
  width: var(--natural-key-width);
  height: var(--natural-key-height);
  border: 1px solid black;
  box-sizing: border-box;
  background: #fffff0;
}

.key-sharp {
  position: relative;
  width: var(--sharp-key-width);
  height: var(--sharp-key-height);
  background: #36454f;
  box-sizing: border-box;
  z-index: 1;
  transform: translateX(-50%);
}

.key-sharp + .key-natural {
  margin-left: calc(-1 * var(--sharp-key-width));
}

.key-natural.active::after,
.key-sharp.active::after {
  content: "";
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background-color: red;
  position: absolute;
  bottom: 10px;
  left: 50%;
  transform: translateX(-50%);
}

Section knowledge base
Here you will find links to helpful articles on the topic I don't cover thoroughly in this section.

A Complete Guide to Flexbox by css-tricks.com - CSS flexbox positioning
A Complete Guide to Custom Properties by css-tricks.com - CSS variables
CSS z-index Property by w3schools.com - z-index property
Adjecent sibling comparator by developer.mozilla.org - CSS adjacent indicator
CSS position by developer.mozilla.org - CSS positioning guide


In part II we will:

  • add chord selection form
  • create functions to find our chord names
  • create functions to dynamically mark piano keys active to match the select chord notes - moved to part III, which will come soon
Discover and read more posts from Andrzej Gorgoń
get started
post commentsBe the first to share your opinion
jonathan Olukotun
2 years ago

Great! Thanks for sharing some of your knowledge

Anthony
2 years ago

Awesome read

Harry Che
2 years ago

Looking forward to the part 2

Andrzej Gorgoń
2 years ago

Glad you liked it :) Part 2 comes to live around sunday.

Andrzej Gorgoń
2 years ago

Part II published :) Feel free to leave comments, thoughts, and ideas. https://www.codementor.io/@andrzejgorgonag/let-s-write-a-piano-chord-visualization-application-a-vanilla-js-beginner-friendly-tutorial-part-ii-1trq96zr6o

However, writing a tutorial and providing all the details takes time, so that the final part will arrive somewhere in the middle of the week.

Show more replies