Project Link in the Codecraft App

LESSON SUMMARY

DURATION: 45-60 mins

GETTING STARTED: (2-5 MINS)

  • Introduce the activity
  • Direct student to the activity

ACTIVITY: (20-40 MINS)

Facilitate and support students to complete the tutorial

WRAP-UP: (5-10 MINS)

Debrief and close

ASSESSMENT/EXTENDED LEARNING: (2-5 MINS)

Optional


What are we building?

We’re going to build a piano on the internet that you can really use to play songs.

You’ll learn about

  1. WebAudio using a library called Tone.js
  2. Some math behind how sounds works
  3. Javascript eventListeners
  4. Switch statements

Part 1

Learn about ToneJs, notes and sound /waves

What is Tone.js?

Tone.js is a framework for creating interactive music in the browser built on top of the Web Audio API. This means that you can create tones and sounds using some knowledge of HTML and Javascript and some knowledge of music.

What is a tone?

A musical or vocal sound with reference to its pitch, quality, and strength. Sound travels in waves and the frequency of those waves in terms of sound is called pitch. The Oscillation of those waves over time will determine the sound we hear. Here is a video that explains it in more detail. Knowing how sound works will give you a better understanding of Tone.js and how our piano will work.

In this tutorial, we’ll use something called Attack / Release. Now that you know how sound works, all you need to know about attack/ release is that attack is when the amplitude is rising and release is when it is decreasing. Okay, you’re an expert! Let’s make a piano.

Part 2 (HTML)

First, let’s start with the HTML.

Make sure you add the Tone.js package from CDN (Content download network). This allows the application to use the library without needing to add the files manually. It imports them straight from the URL provided. Neat!

We need 7 white piano keys, where 5 of them have sharps. This means we’re going to create 12 piano keys. We’ll represent these with list items and style them with CSS to look like piano keys.

Create a ul with 7 li items

<script src="<https://cdnjs.cloudflare.com/ajax/libs/tone/14.4.9/Tone.js>"></script>

<div class="title">
	<h1 >Internet Piano 🎹</h1>
  
    </div>
<ul id="piano">
  <li data-note="C4" class="key">
    <div data-note="C#4" class="black-key">R</div>
 		D
  </li>
  <li data-note="D4" class="key">
    <div data-note="D#4" class="black-key">T</div>
    F
  </li>
  <li data-note="E4" class="key">
    
  </li>
  <li data-note="F4" class="key">
    <div data-note="F#4" class="black-key">U</div>
    H
  </li>
  <li data-note="G4" class="key">
    <div data-note="G#4" class="black-key">R</div>
    H
  </li>
  <li data-note="A4" class="key">
    <div data-note="A#4" class="black-key">R</div>
    J
  </li>
  <li data-note="B4" class="key">
  </li>
 </ul>

Part 3 (CSS)

We’ll use align-items: flex-end; to add the letters to the bottom. The .key identifier will style our white (major) keys and while the .black-key identifier will handle styling our black (sharp and flat) keys.

.title {
 display: flex;
 justify-content: center;
 align-items: center; 
}

ul {
 display: flex; 
 list-style: none;
 justify-content: center;
}

ul .key {
  position: relative;
  width: 60px;
  height: 180px;
  border: 1px solid black;
  cursor: pointer;
  display: flex;
  background: #fffff0;
  justify-content: center;
  align-items: flex-end;
  border-radius: 5px;
}

ul .black-key {
 	position: absolute; 
 	width: 45px;
  left: 37.5px; 
  height: 120px;
  display: flex;
  background: black;
  color: white;
  justify-content: center;
  border-radius: 5px;
  align-items: flex-end;
}

Part 4 (JS)

Now to the good stuff! We’re going to reference the Tone.js api found here:https://tonejs.github.io/

// Create Synth
const synth = new Tone.Synth();

// Set wave type
synth.oscillator.type = "sine";

// Connect to master output
synth.toMaster();

// Attatch piano variable to html element
const piano = document.getElementById("piano");

// Event Listener for clicking "on" piano
piano.addEventListener("mousedown", e => {
  // Grabs note name from 'data-note' 
	synth.triggerAttack(e.target.dataset.note); 
});

// Event Listener for clicking "off" piano 
piano.addEventListener("mouseup", e => {
 synth.triggerRelease(); 
});

We’re going to create a variable called synth and assign it to a new Tone.Synth()

This is a Tone object with a member function called Synth() .

hint – We can tell it is a function noted by the pair of parentheses.

Our synth now has the attributes of the Tone.JS Synth. We’re going to assign the oscillator type to “sine” like a sine wave.

synth.oscillator.type = "sine"

Then we output the synth to our speaker output with:

synth.toMaster()

Cool! Our synth is set up to output audio to our speakers!! Now we just need to set up our synth to play notes when we click the keys and then assign keys to keyboard buttons.

f

Set a piano variable equal to document.getElementById("piano") . This selects the DOM element piano we made in the HTML section and assigns it to our new JS variable.

Now we add event listeners for mouseup and mousedown. We will use an arrow function => to assign the variable e to synth.triggerAttack(e.target.dataset.note) . We are able to grab the note name from the data-note attribute we made earlier. This is because we are using the dataset property on the HTML which allows us to read/write data attributes in the format data-*. By using data-note we can use the note attribute to grab our note names.

We’re going to add another event listener for mouseup to handle synth.triggerRelease in the same way we handled mouseup but we don’t need to handle any note names.

// Add Keyboard Commands
document.addEventListener("keydown", e => {
  switch(e.key) {
     case "d":
      return synth.triggerAttack("C4");
    case "r":
      return synth.triggerAttack("C#4");
    case "f":
      return synth.triggerAttack("D4");
    case "t":
      return synth.triggerAttack("D#4");
    case "g":
      return synth.triggerAttack("E4");
    case "h":
      return synth.triggerAttack("F4");
    case "u":
      return synth.triggerAttack("F#4");
    case "j":
      return synth.triggerAttack("G4");
    case "i":
      return synth.triggerAttack("G#4");
    case "k":
      return synth.triggerAttack("A4");
    case "o":
      return synth.triggerAttack("A#4");
    case "l":
      return synth.triggerAttack("B4");
    default:
      return;   
  }
});

document.addEventListener("keyup", e => {
  switch(e.key) {
    case "d":
    case "r":
    case "f":
    case "t":
    case "g":
    case "h":
    case "u":
    case "j":
    case "i":
    case "k":
    case "o":
    case "l":
       synth.triggerRelease(); 
  }
});

Almost done!!

We’re going to use switch statements to handle our keyboard keys for both pressing and releasing. We’ll add a keydown eventListener and make a switch statement for each key where we have a case for each keyboard key we want to use. In each case we return the note we want using synth.triggerAttack("<insertNote>");. Now we must handle turning the tone off when we release a keyboard key or else the done will play indefinitely and we don’t want that. We will make another eventListener with a switch statement to handle each keyboard key with a case for each key. Each case should handle synth.triggerRelease() and we can add this to the end of our switch statement as the default case.

That’s it 🥳!! You should be able to play your new Internet Piano using your keyboard! Show it off to your friends and family or try some of the challenges below to explore more music theory and the Tone.js library!

Challenges

  1. The piano does not allow you to play a full “octave”. You can hear this by playing every note in order. Towards the end is sounds like something is missing. Finish the piano 🎹
  2. Change the oscillator type to create different sounds or add more filters to the synth
  3. Add animation to the keys

Notes

https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/dataset