grimmdude / MidiPlayerJS

♬ MIDI parser & player engine for browser or Node. As a parser converts MIDI events into JSON. Works well with single or multitrack MIDI files.
https://grimmdude.com/MidiPlayerJS/
MIT License
357 stars 52 forks source link

Last few events do not seem to fire during playback... #98

Open rchrdnsh opened 1 year ago

rchrdnsh commented 1 year ago

Got a midi file with a series of events, logged using the getEvents method:

{track: 1, delta: 0, tick: 0, byteIndex: 0, name: 'Sequence/Track Name', …}
{track: 1, delta: 0, tick: 0, byteIndex: 11, name: 'Time Signature', …}
{track: 1, delta: 0, tick: 0, byteIndex: 19, name: 'Time Signature', …}
{track: 1, delta: 0, tick: 0, byteIndex: 27, name: 'Program Change', …}
{track: 1, delta: 0, tick: 0, byteIndex: 30, name: 'Note on', …}
{track: 1, delta: 0, tick: 0, byteIndex: 34, name: 'Note on', …}
{track: 1, delta: 0, tick: 0, byteIndex: 38, name: 'Note on', …}
{track: 1, delta: 0, tick: 0, byteIndex: 42, name: 'Note on', …}
{track: 1, delta: 192, tick: 192, byteIndex: 46, name: 'Note off', …}
{track: 1, delta: 0, tick: 192, byteIndex: 51, name: 'Note off', …}
{track: 1, delta: 0, tick: 192, byteIndex: 55, name: 'Note off', …}
{track: 1, delta: 0, tick: 192, byteIndex: 59, name: 'Note off', …}
{track: 1, delta: 192, tick: 384, byteIndex: 63, name: 'Note on', …}
{track: 1, delta: 0, tick: 384, byteIndex: 68, name: 'Note on', …}
{track: 1, delta: 0, tick: 384, byteIndex: 72, name: 'Note on', …}
{track: 1, delta: 0, tick: 384, byteIndex: 76, name: 'Note on', …}
{track: 1, delta: 192, tick: 576, byteIndex: 80, name: 'Note off', …}
{track: 1, delta: 0, tick: 576, byteIndex: 85, name: 'Note off', …}
{track: 1, delta: 0, tick: 576, byteIndex: 89, name: 'Note off', …}
{track: 1, delta: 0, tick: 576, byteIndex: 93, name: 'Note off', …}
{track: 1, delta: 192, tick: 768, byteIndex: 97, name: 'Note on', …}
{track: 1, delta: 0, tick: 768, byteIndex: 102, name: 'Note on', …}
{track: 1, delta: 0, tick: 768, byteIndex: 106, name: 'Note on', …}
{track: 1, delta: 0, tick: 768, byteIndex: 110, name: 'Note on', …}
{track: 1, delta: 0, tick: 768, byteIndex: 114, name: 'Note on', …}
{track: 1, delta: 192, tick: 960, byteIndex: 118, name: 'Note off', …}
{track: 1, delta: 0, tick: 960, byteIndex: 123, name: 'Note off', …}
{track: 1, delta: 0, tick: 960, byteIndex: 127, name: 'Note off', …}
{track: 1, delta: 0, tick: 960, byteIndex: 131, name: 'Note off', …}
{track: 1, delta: 0, tick: 960, byteIndex: 135, name: 'Note off', …}
{track: 1, delta: 192, tick: 1152, byteIndex: 139, name: 'Note on', …}
{track: 1, delta: 0, tick: 1152, byteIndex: 144, name: 'Note on', …}
{track: 1, delta: 0, tick: 1152, byteIndex: 148, name: 'Note on', …}
{track: 1, delta: 0, tick: 1152, byteIndex: 152, name: 'Note on', …}
{track: 1, delta: 0, tick: 1152, byteIndex: 156, name: 'Note on', …}
{track: 1, delta: 192, tick: 1344, byteIndex: 160, name: 'Note off', …}
{track: 1, delta: 0, tick: 1344, byteIndex: 165, name: 'Note off', …}
{track: 1, delta: 0, tick: 1344, byteIndex: 169, name: 'Note off', …}
{track: 1, delta: 0, tick: 1344, byteIndex: 173, name: 'Note off', …}
{track: 1, delta: 0, tick: 1344, byteIndex: 177, name: 'Note off', …}
{track: 1, delta: 0, tick: 1344, byteIndex: 181, name: 'End of Track', ...}

but during playback the last series of note off events and the end of track event do not fire, which leaves the midi events unfinished...

Player.on('endOfFile') does seem to fire, so I cannot figure out why the last few events are not firing...

I'm running this lib in a svelte component, but everything in the code seems to be working ok up until the last series of events...

It seems to happen with every midi file i play, even though they are show that there are note off and end of track events in them.

Here is some of the code being used to run the midi file:

const handleFileUpload = (event) => {
    const file = event.target.files[0];
    const reader = new FileReader();
    reader.readAsArrayBuffer(file);

    reader.onload = () => {
      midiData = new Uint8Array(reader.result);
      player = new MidiPlayer.Player(function() {});

      player.on('fileLoaded', () => {
        console.log(`A MIDI file has been uploaded`);
        allEvents = player.getEvents();
        console.log('All MIDI events:', allEvents);
      });

      player.on('midiEvent', (event) => {
        console.log(`MIDI Event Fired`, event.name);
        handleMidiEvent(event, piano);
      });

      player.on('endOfFile', () => {
        console.log(`You have reached the end of the file.`)
      });

      player.loadArrayBuffer(midiData);
    };
  };

and:

function handleMidiEvent(event) {
    console.log('midi event:', event);

    if (endOfTrackReached) {
      return; // ignore all events after end of track
    }

    if (event.name === 'Note on') {
      console.log('Note on event:', event);
      const noteNumber = event.noteNumber;
      const velocity = event.velocity;
      console.log('Velocity:', velocity);

      const source = playSample(piano, 60, noteNumber, velocity);
      noteSources[noteNumber] = source;

      activeNotes.push(noteNumber);
      console.log('activeNotes:', activeNotes);
    } else if (event.name === 'Note off') {
      console.log('Note off event:', event);
      const noteNumber = event.noteNumber;
      const index = activeNotes.indexOf(noteNumber);
      if (index !== -1) {
        activeNotes.splice(index, 1);
      }
      console.log('activeNotes:', activeNotes);

      const source = noteSources[noteNumber];
      if (source) {
        source.stop();
        delete noteSources[noteNumber];
      }
    } else if (event.name === 'End of Track') {
      console.log(`That is the end of the track.`)
      endOfTrackReached = true;
    }
  }

...not getting errors in the console, just does not play all the events to the end of the file...

Andrew-J-Larson commented 1 year ago

Not sure if related, but also found that some MIDI's don't signal that it has stopped:

e.g. https://www.mediafire.com/file/ecfn1w70yy9iz8c/%255BSC-55%255D_Jack_Black_-_Peaches.mid/file

teletype1 commented 1 year ago

I'm experiencing the same thing - it seems to not want to fire the last Note Off message, and the End of Track message (if you can call that a message). You can see it happening like this:

var MidiPlayer = require('midi-player-js');

var Player = new MidiPlayer.Player(function(event) { console.log(event); console.log(Player.getTotalEvents(), Player.eventsPlayed()); });

// Load MIDI file Player.loadFile('./1.mid'); Player.play();

It never reaches the total number of events in the file. Perhaps an order of operations problem?