grimmdude / MidiWriterJS

♬ A JavaScript library which provides an API for programmatically generating and creating expressive multi-track MIDI files and JSON.
MIT License
557 stars 60 forks source link

NoteOn / NoteOffEvents constructor error #61

Closed wustep closed 2 years ago

wustep commented 5 years ago

Hi! thankful for this project -- ran into issues trying to use NoteOn / NoteOff!

Error: TypeError: _midiWriterJs.default.NoteOnEvent is not a constructor or NoteOffEvent.

Code:

          const noteEvent = {
              pitch: KEY_TO_NOTE[message.data[1]],
              velocity: message.data[2],
              startTick:
                "158"
            };
            console.log(noteEvent);
            events.push(new MidiWriter.NoteOnEvent(noteEvent));

I think this is a quick fix -- exporting the events in main.js?

grimmdude commented 5 years ago

Hi @wustep,

I think it's just a matter of using NoteEvent wrapper instead of NoteOnEvent. NoteOnEventisn't meant to be used like that. This should work:

const noteEvent = {
  pitch: KEY_TO_NOTE[message.data[1]],
  velocity: message.data[2],
  startTick:
    "158"
};
console.log(noteEvent);
events.push(new MidiWriter.NoteEvent(noteEvent));

-Garrett

wustep commented 5 years ago

hi Garrett!

Thanks for the quick reply -- was a bit confused cause docs mention NoteOn and NoteOff. I tried editing main.js and using NoteOn/Off but am having timing issues with that (but maybe I just need to figure out how ticks work).

So -- my use-case is recording MIDI input and creating MIDI output, so I have a bunch of events like:

messages = 
[{ data: [ 144, 64, 80 ], timestamp: 2020737.209999992 }
{ data: [ 144, 60, 80 ], timestamp: 2020777.28499996 }
...
{ data: [ 128, 67, 0 ], timestamp: 2021860.4849999538 }
{ data: [ 144, 67, 80 ], timestamp: 2021981.9349999889 }
{ data: [ 128, 71, 0 ], timestamp: 2021983.889999974 }
{ data: [ 128, 67, 0 ], timestamp: 2022004.9349999754 }
{ data: [ 128, 69, 0 ], timestamp: 2022005.2499999874 }];

where timestamp is the time in milliseconds.

NoteEvent doesn't work for me, as I'm not specifying duration (I don't know duration without pre-processing), so I get:

      const noteEvent = {
        pitch: NOTE_TO_KEY[message.data[1]],
        velocity: message.data[2],
        startTick: Math.floor(message.timestamp - recordingTimestamp)
      };
      console.log(noteEvent);
      track.addEvent(new MidiWriter.NoteEvent(noteEvent));
TypeError: Cannot read property 'toString' of undefined
    at Function.getTickDuration (.../node_modules/midi-writer-js/build/index.js:287:27)
    at new NoteEvent (.../node_modules/midi-writer-js/build/index.js:536:33)

This is my full (dirty) testing code to help you understand my use-case. I don't think I'm doing tempo correctly (goal is 100 bpm later)!

export const writeMidi = (messages, recordingTimestamp) => {
  const track = new MidiWriter.Track();
  const events = [];
  for (let message of messages) {
    if (message.data[0] === MIDI_EVENTS.NOTE_ON) {
      events.push(new MidiWriter.NoteOnEvent({
        pitch: NOTE_TO_KEY[message.data[1]],
        velocity: message.data[2],
        startTick: Math.floor(message.timestamp - recordingTimestamp)
      };));
    } else if (message.data[0] === MIDI_EVENTS.NOTE_OFF) {
      events.push(
        new MidiWriter.NoteOffEvent({
          pitch: NOTE_TO_KEY[message.data[1]],
          startTick: Math.floor(message.timestamp - recordingTimestamp),
          duration: 0 /* need this here or noteOff will error */
        })
      );
    }
  }
  track.setTempo(1000);
  track.addEvent(events);
  const write = new MidiWriter.Writer(track);
  console.log(write.dataUri());
  const randomFileName = Math.random()
    .toString(36)
    .substring(7);
  write.saveMIDI("./records/" + randomFileName);
  console.log(`Recorded to ${randomFileName}.midi`);
};

This compiles, but the output is not as expected (all notes are played at once for a short period of time at some time) -- let me know what you think!

thanks! -stephen

grimmdude commented 5 years ago

Hi Stephen,

Ah I gotcha. Interesting, yea I suppose if NoteOnEvent and NoteOffEvent were exported from main.js you could use them this way. I haven't tested interfacing with them publicly like that, but it certainly seems like what you need. I'm sure others would find it useful too.

I can look into that a little later, or if like feel free to submit a PR.

-Garrett

wustep commented 5 years ago

Got it! Thanks -- might try to tackle this in a few days.

In the meantime -- do you know what I should do for tempo? If I setTempo(60), what should startTicks be if I have timestamp in milliseconds?

e.g. C4 is played @ 1 second in, then let go at 2 seconds. Is startTick=1 for the NoteOn and 2 for the NoteOff? Or is it 128 for the first and 256 for the second?

thebne commented 4 years ago

@grimmdude , I was about to use your library in order to serialize WebMIDI to files (let you download what you just played) - but I can't do that because the library kinda forces me to use the duration mechanism (I forked the code and started changing it, but NoteOff isn't written for such a scenario). Any tips on how to write MIDI files from plain NoteOn/Off events without changing much of your code?

grimmdude commented 4 years ago

Hi @wustep and @thebne, I've exported the NoteOnEvent and NoteOffEvent classes to main so they are accessible directly now. I haven't done much testing with it, but hopefully this should give you the flexibility you need. I'll write some tests for them when I have more time, but let me know if this helps out. Released version 1.7.3 with this change.

-Garrett

thebne commented 4 years ago

@grimmdude thanks, I ended up using midi-file for now. I think right now it's a bit tricky to use the NoteOn/Off functionality because I'd have to apply math on the ticks to make it work.

grimmdude commented 3 years ago

Hey @thebne,

@grimmdude , I was about to use your library in order to serialize WebMIDI to files (let you download what you just played) - but I can't do that because the library kinda forces me to use the duration mechanism (I forked the code and started changing it, but NoteOff isn't written for such a scenario).

If you have a file, array buffer, or data uri you can use https://github.com/grimmdude/MidiPlayerJS as a parser/serializer as well if you like. To get MIDI events serialized to JSON you can do something like this:

const MidiPlayer = require('midi-player-js');
const Player = new MidiPlayer.Player;
Player.loadDataUri(/*dataUri*/);

Player.tracks.map(function(track) {
    // MIDI event json can be found here for each track.
    console.log(track.events);
});

Example json for an event:

{
  "byteIndex": 104,
  "channel": 1,
  "delta": 1,
  "name": "Note on",
  "noteName": "E5",
  "noteNumber": 76,
  "running": true,
  "tick": 120,
  "track": 1
}