Node.js - Parsing a MIDI file

Zápisník experimentátora

Hierarchy: Node.js

In this article, we will show you how to parse a MIDI file using javascript. As a MIDI file we will use the scale that we will generate in the program MuseScore. On the parsed content we will show what is hidden in the file itself.

The generated file has 2 bars, contains several notes from the scale in C major, which gradually change the volume and in parallel with them plays the chord C (notes C, E, G), which is one octave lower. That's enough to try MIDI.

Program

The program uses the midi-parser-js library. You can see that MIDI files contain a header, several tracks, and each track has different events that are separated by the time they are to be played. That time is a bit of a strange value that can depend on various factors. For simplicity, we only need to know that events that are delta 0 away should be played simultaneously. Those who have a delta non-zero must wait the appropriate time and then play back.

Event type 9 (note on) and event type 8 (note off) are essential for playing individual notes. However, it is often replaced by a type 9 event with the volume set to 0, which also means the same thing.

let midiParser  = require('midi-parser-js');
let fs = require('fs');

let data = fs.readFileSync('./stupnica.mid');
let midiArray = midiParser.parse(data);

console.log(`Tracks: ${midiArray.tracks}, TimeDivision: ${midiArray.timeDivision}`);
for(let i in midiArray.track) {
  console.log(`Track ${i}`);
  for(let j in midiArray.track[i].event) {
    let event = midiArray.track[i].event[j];
    switch(event.type) {
      case 8: // note off
      case 9: // note on
      case 11: // controller
      case 12: // program change
        console.log(` ${j} t:${event.type} ch:${event.channel} delta:${event.deltaTime} - ${event.data}`);
        break;
      case 255: // meta event
        console.log(` ${j} t:${event.type} m:${event.metaType} delta:${event.deltaTime} - ${event.data}`);
        break;
      default:
        console.log(` ${j} t:${event.type} delta:${event.deltaTime} - ${event.data}`);
        break;
      }
  }
}

Output from the program

The program must first be installed with the npm install command and then run with the node index command. In the report, you can see that the notes are placed in two tracks. At the beginning of each track are system events that we may not be interested in, and at the end of each track is an event that means the end of the track. I generated this MIDI file for BPB 120. The value is a bit hidden in the events, but for the purposes of our article, we only need to know that one quarter note in this case takes 480 ticks of some imaginary timer. When you take a good look at event 9, you see that each note has two events. And when you count, the deltas for notes are 455 + 25 = 480. This only means that the note is not played until the end and after each note there is a short pause. If I marked the articulation legato in the notation (in the language of musicians it means playing continuously, when each note follows the previous one without an audible pause), the duration of each note would be a bit longer.

d:\arduino\arduinoslovakia\midi\file\nodejs\midi-parser>npm install
npm WARN npm npm does not support Node.js v12.16.1
npm WARN npm You should probably upgrade to a newer version of node as we
npm WARN npm can't make any promises that npm will work with this version.
npm WARN npm Supported releases of Node.js are the latest release of 6, 8, 9, 10, 11.
npm WARN npm You can find the latest version at https://nodejs.org/
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN midi-parser@1.0.0 No description
npm WARN midi-parser@1.0.0 No repository field.

added 1 package from 1 contributor in 2.223s
[+] no known vulnerabilities found [1 packages audited]

d:\arduino\arduinoslovakia\midi\file\nodejs\midi-parser>node index
Tracks: 2, TimeDivision: 480
Track 0
 0 t:255 m:88 delta:0 - 4,2,24,8
 1 t:255 m:89 delta:0 - 0
 2 t:255 m:81 delta:0 - 500000
 3 t:11 ch:1 delta:0 - 121,0
 4 t:12 ch:1 delta:0 - 0
 5 t:11 ch:1 delta:0 - 7,100
 6 t:11 ch:1 delta:0 - 10,64
 7 t:11 ch:1 delta:0 - 91,0
 8 t:11 ch:1 delta:0 - 93,0
 9 t:255 m:33 delta:0 - 0
 10 t:9 ch:1 delta:0 - 60,49
 11 t:9 ch:1 delta:455 - 60,0
 12 t:9 ch:1 delta:25 - 62,53
 13 t:9 ch:1 delta:455 - 62,0
 14 t:9 ch:1 delta:25 - 64,57
 15 t:9 ch:1 delta:455 - 64,0
 16 t:9 ch:1 delta:25 - 65,62
 17 t:9 ch:1 delta:455 - 65,0
 18 t:9 ch:1 delta:25 - 67,66
 19 t:9 ch:1 delta:455 - 67,0
 20 t:9 ch:1 delta:25 - 69,71
 21 t:9 ch:1 delta:455 - 69,0
 22 t:9 ch:1 delta:25 - 71,75
 23 t:9 ch:1 delta:455 - 71,0
 24 t:9 ch:1 delta:25 - 72,80
 25 t:9 ch:1 delta:455 - 72,0
 26 t:255 m:47 delta:1 - undefined
Track 1
 0 t:255 m:89 delta:0 - 0
 1 t:255 m:33 delta:0 - 0
 2 t:9 ch:1 delta:0 - 48,49
 3 t:9 ch:1 delta:0 - 52,49
 4 t:9 ch:1 delta:0 - 55,49
 5 t:9 ch:1 delta:3647 - 48,0
 6 t:9 ch:1 delta:0 - 52,0
 7 t:9 ch:1 delta:0 - 55,0
 8 t:255 m:47 delta:1 - undefined

Source code

The source code is located on the GitHub server.


20.07.2020


Menu