This video is available to students only

A Bit of Music Theory to Build MIDI Notes in React

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

Previous LessonBootstrap a React App With Footer, Header, and CSS VariablesNext LessonBuild a Keyboard App With a React Hook and the Web Audio API

Lesson Transcript

  • [00:00 - 01:07] A bit of music theory. Before we continue, let's dive into music theory. First of all, how do we represent the musical notes in our application? Nowadays, it is considered standard to use median notes numbers for that. A median note number is a number that represents a note in range of minus first to the ninth octave. An octave is a set of 12 semitones that are different from each other by half of a tone, hence semitone. Notes in octave start from C and go up to B, like this. C, C#, D, D#, E, F, F#, G#, A, A#, B. A#, which tells us that given note is sharp, a sharp note is a half step higher than its natural note, and you can see it in the keyboard. The sharp keys are kind of in between the naturals. Coding music rules. Now let's try to express all that in TypeScript. Create a new file, as I see, here we create a new folder, the main, and inside of it, note.ts. Here we expert, new type, note.

  • [01:08 - 02:17] Type, it can be natural, it can be flat, or it can be sharp. There is also note pitch, expert type, note , pitch, equals A, B, C, D, E, F, and there are octave indices. Expert type, octave, index. It's going to be numbers, 1, 2, 3, 4, 5, 6, 7, or 8. So we created a bunch of union types here. For example, note type contains all the note types that we'll use. Union types are useful when we want to create a set of entities to select from. So in our case, note type is a set of possible note types, like natural, sharp, or flat. Even though we will only use sharps, and as you remember, we didn't even discuss flats when we looked at the keyboard, it is still a good idea to keep them there in this note file, just because they exist in music theory. Note pitch is a union type that contains all the possible note pitches from A to G. Since the order of items in union is not important, we can order our pitches in alphabetical order to make it easier to work with them later.

  • [02:18 - 02:27] And octave index is a union that contains all the actives that can exist on a piano keyboard. We also want to create some type aliases to make the signatures of our future functions clearer.

  • [02:28 - 03:19] Expert type, MIDI value equals number, expert type, pitch, index, number. Here we define a MIDI value type, which is basically a number from the active notation above, and a pitch index, which is also a number representing the index of a giving pitch in an octave from 0 to 11. Pitch index is useful when we want to compare notes with each other to figure out which one is higher. Now let's create a note type. Expert type, note, it will contain all the fields relevant for us when we are talking about the note. So first of all, it's going to contain the MIDI value, MIDI of type, MIDI value is going to have type, note, type, as you remember, it 's natural, flat, or sharp, it's going to contain pitch, note, pitch, index, pitch, index, and octave, octave, index.

  • [03:20 - 04:40] Now let's outline in what range do we want our keyboard to contain notes. First of all, let's consider the lowest note possible to play, which is C in the first octave . It has MIDI value of 24, which we will save in a constant C1 MIDI number, const C1 MIDI number equals 24. Then let's define the constraints for our keyboard. It's going to start from C4 MIDI number, const C4 MIDI number, and B5 MIDI number, which is 83. We're also going to need to count the number of half steps in an octave, which we'll save in the semitones in octave. But first let's define the areas for the C4 MIDI number as a lower note, expert, const lower note equals C4 MIDI number, expert, const higher note, B5 MIDI number, and the semitones in octave, expert, const semitones in octave, it's going to be 12. Now we can create some kind of map to connect literal and numerical representations of pitches of our notes. Expert, const, natural, pitch indices. It's going to be an array of pitch index, pitch index array starting from 0, then 24, 5, 7, 9, and 11. And here we just skip the sharps.

  • [04:41 - 06:16] Now let's define the pitches registry. That is going to be an object with pitch index as key and note pitch as value. So we'll connect the note names with their numeric values. Expert, const, pitches, registry. As it is an object, it's convenient to use the record type where you can specify the type of the key and the type of the value. So here is going to be a record with the keys of type pitch index and values of type note pitch. Now let's define them. The record here is a generic utility type with two type variables. If you look at this type definition, you'll see that it accepts two type variables, k that extends string number or symbol. It means that the type that we pass as the first type variable should conform or should match string number or symbol types and t that can be anything. It's not constrained. And then it returns an object type with properties from type variable k and values of type t. And here we just said that k is going to be pitch index and t is going to be note pitch. Now we want to define a function that will produce a note object from a given media value. Expert function from MIDI, we accept the MIDI argument of type MIDI value and we return a note. First let's define the piano range, const, piano, range equals MIDI minus C1 MIDI number. Then we get the octave, const, octave equals math floor.

  • [06:17 - 07:36] We divide the piano range by the amount of semitones in octave. We add plus one because octave indexes start from one. So it cannot be zero. And then we cast it to octave index. Next we get the note index, const index equals piano, range, modulo, semitones in octave. So now we know the index inside of the octave. Then we get the pitch equals pitches registry. And here we just get the value by the index. We also want to know if it's sharp, const is sharp, and it's going to be not included in natural pitch indices. So if it's not there, then it's sharp. Natural pitch indices includes index. Now we can calculate the type. If it's sharp, then the type is going to be sharp. Otherwise, it's going to be natural. And finally we return an old object, return octave, pitch, index, type, and the MIDI. I'd like to know that it's quite convenient to add the return type annotations, because it reduces the chance of you making a typo and not really returning a proper type from your function. So it's usually good to add the return type.

  • [07:37 - 08:46] Okay, now we have the from MIDI function. Now let's generate an initial set of tones, or define a function that will do it for us. Let's define a type, notes generator settings from note is going to be MIDI value to note. Also an optional property is going to be a MIDI value as well. Expert function generate notes. Here we destructure the first argument that is going to be an object to use the named properties. So from note is going to have default value of lower note and to note by default is going to be higher note. And the type of this object is going to be notes generator settings with default value of empty object. We want this function to return an array of notes. So here we return an array. We can specify the length of the generated array, passing the argument to it to note minus from note last one. Then we fill it with zeros, fill zero, and then map where for each element, we don't care about the value, we only care about the index index number.

  • [08:47 - 09:01] We call from MIDI and generate the note from note plus index. And now we export the generated notes. Expert const, notes equals generate notes.

This lesson preview is part of the Fullstack React with TypeScript Masterclass course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

Unlock This Course

Get unlimited access to Fullstack React with TypeScript Masterclass, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Fullstack React with TypeScript Masterclass