📜 ⬆️ ⬇️

Writing Beethoven in Javascript or a bit of MIDI.js

How to play notes in the browser? How to reduce any long piece to 107 individual notes (which can also be cached) and kilobyte-other text? A little music theory, js-library and MIDI under the cut.

Preface. I do not pretend to have a deep knowledge of musical notation. But this should not prevent the reader from understanding the note-writing letter and making something, at least, similar to the scale written in the notes by the composer. Many terms and concepts are purposely simplified. Pointing to grammar, syntax and other errors in the text are welcome in personal messages.

For the impatient, you can immediately get acquainted with the result (7 cycles of the moon sonata). Works in the latest Chrome and Firefox on Ubuntu 14.04. On mobile devices, most likely, will not work.

How to play a musical notation in the browser? The first thing that comes to mind is to find a solution that implements the basic functions. Search on github gives out midi.js. The solution is convenient. MIT license. Examples - work. We take!
')
$ git clone https://github.com/mudcube/MIDI.js.git 

Got a copy on the local environment. In the examples directory, we see Basic.html. We will copy there as Betchoven.html and we will change the content. Strings of interest to us:

  var delay = 0; // play one note every quarter second var note = 50; // the MIDI note var velocity = 127; // how hard the note hits // play the note MIDI.setVolume(0, 127); MIDI.noteOn(0, note, velocity, delay); MIDI.noteOff(0, note, delay + 0.75); 

You need to write something relatively simple and slow. For example, the first part of Beethoven’s Moonlight Sonata (Sonata for Piano No. 14 in C sharp minor, op. 27, No. 2, as suggested by the wiki). Find the notes.

Let them be like this:

Sign for the minimum necessary to understand this musical notation.

A small excursion into the theory


Sheet music on the piano. The first picture shows the piano layout.


The octave markup is not quite correct - the first two notes on the left are the octave 0 in simplified notation. From the first note Do (marked as C) begins the octave 1.

Pressing the piano key with this layout will play the corresponding sound:
from kontrakttavnyh A - A-sharp (he same B-flat) - C
up to the fifth octave. Where the whole notes (Do, Re, ..) are white keys, halftones (with sharps and flatons) are black.
In foreign literature, a different, simplified notation is often used, which we will eventually use:
notes are denoted by the Latin letters C (Do), D (D), E (Mi), F (F), G (Salt), A (La), B (Si). The octaves are simply numbered from 0 to 8. Accordingly, on the piano you will see the symbols of the notes.
from A0 - A0♯ (B0 ♭) - B0 - C1
to C8. Black keys may not be indicated as in the picture above. On black there are intermediate sounds (half tones) - notes with sharp and flat.
For each note and each half tone between them, depicted on this layout, there is a corresponding note number in MIDI (from 21 to 108). The ratio will be seen later.
Above the piano you can see two music camps (two times five lines). Upper - violin, denotes higher octaves, Lower - bass, lower. Squiggles at the beginning of the lines are the signs of the treble and bass keys, respectively. Note that the first line of the treble clef indicates the note M (E) of the first (4) octave, and the first bass - the note Sol (G) large (2).

Key

Following the key on the musical notation we see 4 sharp. This is indicated by tonality. In this case, it is called C-sharp minor.

To play correctly in this key, instead of notes, on the lines of which sharps are drawn, you need to play sharps (the next key to the right) of these notes. The sharps are drawn on notes C, D, F, G. I will not paint the principles of the construction of tonalities - now they are not so important and there is a lot of information in the network. Those wishing yes zagglyat.

Therefore, if we see the notes C1, D1, .. G7, mentally change them to the nearest C1♯, D1♯, ... G7♯ and afterwards look for the corresponding number in the MIDI numbering.

Alteration Signs (♯, ♮, ♭)

If these signs are not at the beginning of the line, but somewhere in a “random” place, then they temporarily, until the end of the current measure (bars are separated by a vertical line) change the note as follows:
- The alteration of this note in this octave, given by the pitch, is canceled. For example, the note C3 â™® and all following C3 until the end of the measure (the nearest vertical line) will be played as C3;
-♯ raises the note by a half tone. A3♯ and all of the following A3 until the end of the bar are played as A3♯;
- ♭ lowers a note by a half tone. D3 ♭ and all of the following D3 will be played as C3♯ (aka D3 ♭).

The thoughtful reader has already noticed that in some cases “temporary sharp and flat” does not make sense. For example, in the tonality of a lunar sonata with notes C, D, F, G, you can set and not put sharp. Nothing will change. Yes, for such cases there are double sharps, but they are beyond the scope of this article.

Duration

A note in a musical notation can have a hollow or filled circle (head), have a vertical stick (calm) and a flag. This determines the relative durability of the note. In our case, a single note takes the entire beat, half a half-touch, and so on. I bring the picture for clarity.



Another nuance - points. A dot immediately after a note means that this note is one and a half times the specified duration. For example, 1/8 with a point sounds for 1/8 + 1/16 = 3/16 of the beat. Immediately make a reservation for the experts, I did not translate Adagio to beats per minute, and if I translated, then what to do with this 60-80 is not very clear. Therefore, the duration of the tact chosen by ear.

Using this knowledge, we calculate the notes and translate them into MIDI using the following picture:



C2 -> 37 (one and a half tones higher, because about the tonality - C sharp minor)
C3 -> 49
G3 -> 56 and so on.

We get a somewhat clumsy, but working implementation of the first measure:

 window.onload = function () { MIDI.loadPlugin({ soundfontUrl: "./soundfont/", instrument: "acoustic_grand_piano", onprogress: function(state, progress) { console.log(state, progress); }, onsuccess: function() { play(); } }); }; function play() { var delay = 0; // play one note every quarter second var velocity = 127; // how hard the note hits var gap = 0.6; var duration = 0.4; MIDI.setVolume(0, 80); //   delay += gap; MIDI.noteOn(0, 49, velocity, delay); MIDI.noteOff(0, 49, delay + 4 * gap); MIDI.noteOn(0, 37, velocity, delay); MIDI.noteOff(0, 37, delay + 4 * gap); MIDI.noteOn(0, 56, velocity, delay); MIDI.noteOff(0, 56, delay + duration); delay += gap; MIDI.noteOn(0, 61, velocity, delay); MIDI.noteOff(0, 61, delay + duration); delay += gap; MIDI.noteOn(0, 64, velocity, delay); MIDI.noteOff(0, 64, delay + duration); delay += gap; MIDI.noteOn(0, 56, velocity, delay); MIDI.noteOff(0, 56, delay + duration); delay += gap; MIDI.noteOn(0, 61, velocity, delay); MIDI.noteOff(0, 61, delay + duration); delay += gap; MIDI.noteOn(0, 64, velocity, delay); MIDI.noteOff(0, 64, delay + duration); delay += gap; MIDI.noteOn(0, 56, velocity, delay); MIDI.noteOff(0, 56, delay + duration); delay += gap; MIDI.noteOn(0, 61, velocity, delay); MIDI.noteOff(0, 61, delay + duration); delay += gap; MIDI.noteOn(0, 64, velocity, delay); MIDI.noteOff(0, 64, delay + duration); delay += gap; MIDI.noteOn(0, 56, velocity, delay); MIDI.noteOff(0, 56, delay + duration); delay += gap; MIDI.noteOn(0, 61, velocity, delay); MIDI.noteOff(0, 61, delay + duration); delay += gap; MIDI.noteOn(0, 64, velocity, delay); MIDI.noteOff(0, 64, delay + duration); } 

This is only the beginning, but an untrained person may already be tired of counting tonalities and temporary alterations. Yes, and sheets of the code are obtained long. And this is just the first beat. Of course, an eye aimed at OOP will immediately find targets for refactoring. However, the task of calculating the sound can be safely shifted to javascript.

We describe the tonality. Tonality - the term vague. But in our particular example, the tonality is simply the necessary alteration of notes in this tonality. As you remember, in the key in C-sharp minor, we see notes C1, D1, .. G7, and substitute C1♯, D1♯, ... G7♯ in their place. I simply marked the shift for each of these notes (+1 or just 1). 4 - the number of sharps. Bemoli would be designated as -4. Connoisseurs will understand that in this narrow case the difference between parallel keys in C-sharp minor and E-major for our task is not. Same sharps with the same notes.

The code for the article should not be cluttered, so there is no JSDoc, there were no JSHint checks and other things, but there are comments translated into Russian.

 var keys = { 4 : { C : 1, D : 1, F : 1, G : 1 } }; 

Now create an object to play:

 var player = { //   barDuration : 8, //   timeline : 0, //     velocity : 127, //   key : keys[4], //      tempAlts : {}, //  -   , ,      play : function(noteString, duration, moveTime) { //     var noteInt = this.calcNote(noteString); MIDI.noteOn(0, noteInt, this.velocity, this.timeline); //       *     MIDI.noteOff(0, noteInt, this.velocity, this.timeline + this.barDuration * duration); if (typeof moveTime !== 'undefined' && moveTime === true) { this.move(duration); } }, move : function(duration) { this.timeline += this.barDuration * duration; //      ,    . if (this.timeline % this.barDuration === 0) { this.tempAlts = {};} }, }; 

Now release the note counting and further improve the code:

 var player = { barDuration : 8, timeline : 0, velocity : 127, key : keys[4], tempAlts : {}, play : function(noteString, duration, moveTime) { var noteInt = this.calcNote(noteString); MIDI.noteOn(0, noteInt, this.velocity, this.timeline); MIDI.noteOff(0, noteInt, this.velocity, this.timeline + this.barDuration * duration); if (typeof moveTime !== 'undefined' && moveTime === true) { this.move(duration); } }, move : function(duration) { this.timeline += this.barDuration * duration; if (this.isEndOfBar()) { this.tempAlts = {};} }, calcNote : function(noteString) { var note = noteString[0]; var noteWithOctave = noteString.substring(0,2); //        var altering = this.getAltering(noteString); //   , ,  if (altering) { this.setTempAltering(noteWithOctave, altering); } //     -     MIDI +    if (this.tempAlts[noteWithOctave] !== undefined) { return MIDI.keyToNote[noteWithOctave] + this.tempAlts[noteWithOctave]; } //     -     MIDI +     //     return MIDI.keyToNote[noteWithOctave] + (this.key[note] !== undefined ? this.key[note] : 0); }, isEndOfBar : function() { return !!(this.timeline % this.barDuration === 0) }, //       false getAltering : function(noteString) { var altering = noteString[2]; return altering !== undefined ? altering : false; }, setTempAltering : function(noteWithOctave, altering) { switch (altering) { //         1     case 'b': this.tempAlts[noteWithOctave] = -1; break; //    "%" case '%': this.tempAlts[noteWithOctave] = 0; break; case '#': this.tempAlts[noteWithOctave] = 1; break; } } } 

Well, the score itself:

  player.play('C2', 1); player.play('C1', 1); player.play('G3', 1/12, true); player.play('C4', 1/12, true); player.play('E4', 1/12, true); player.play('G3', 1/12, true); player.play('C4', 1/12, true); player.play('E4', 1/12, true); player.play('G3', 1/12, true); player.play('C4', 1/12, true); player.play('E4', 1/12, true); player.play('G3', 1/12, true); player.play('C4', 1/12, true); player.play('E4', 1/12, true); ... 

The resulting result can be heard here and see here . Made for example 7 cycles of 19.

UPD Corrected about the octaves, notation and duration according to the comment lair

Source: https://habr.com/ru/post/257813/


All Articles