grimmdude / MidiWriterJS

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

Two tracks with different instruments at the same time #97

Closed jessgusclark closed 1 year ago

jessgusclark commented 2 years ago

I am attempting to create a MIDI file with multiple tracks with different instruments. It seems like when I call

track.addEvent(new MidiWriter.ProgramChangeEvent({instrument: 1}));

it changes the instrument of all the tracks, not just the one attached to.

Here is a small POC as a demo.

const MidiWriter = require('midi-writer-js');
const fs = require('fs')

// Whole note track
const track = new MidiWriter.Track();
track.addEvent(new MidiWriter.ProgramChangeEvent({instrument: 1}));
track.addEvent(new MidiWriter.NoteEvent({pitch: ['E4'], duration: '1', velocity: 100}));

// Metronome track
const metronomeTrack = new MidiWriter.Track();
metronomeTrack.addEvent(new MidiWriter.ProgramChangeEvent({ instrument: 115 }))
const quarterNote = new MidiWriter.NoteEvent({pitch: ['B4'], duration: '4', velocity: 100, channel: 2})

metronomeTrack.addEvent([ quarterNote, quarterNote, quarterNote, quarterNote])

// Build the file and output it:
const write = new MidiWriter.Writer([track, metronomeTrack]);
const file = write.buildFile()

fs.writeFile('myMidi.midi', file, (err) => {
  console.log(err || 'success')
})

Expected: One whole note (piano) with four quarter notes (woodblock) in the background Result: One woodblock click followed by multiple piano quarter notes

jessgusclark commented 2 years ago

Here is a working repo of the issue if it helps: https://github.com/jessgusclark/midi-writer-bug

neoPix commented 1 year ago

Same here. Seems related to #79.

@jessgusclark did you figured it out since ?

jessgusclark commented 1 year ago

@neoPix It does look like it might be related. but I haven't found a solution to it.

It seems like when you call MidiWriter.ProgramChangeEvent() it changes it for the entire file which is incorrect. What should happen is the channel and instrument should be called together at the start.

A track should have a channel and instrument associated with it from the start that can't be changed. I would imagine something like this:

const track = new MidiWriter.Track({ channel: 1, instrument: 2 );

Then when converting it to the midi file it picks up the channel and instrument from the Track.

That being said, I haven't had a chance to dive into the conversion process to see.

marian-simonca commented 1 year ago

Any updates on this? We have the same problem and honestly it's incredible how problems keep pilling up without solutions..

grimmdude commented 1 year ago

Hi @marian-simonca, pull requests are always welcome 👍

marian-simonca commented 1 year ago

Hi @grimmdude ! I know they are welcome, and trust me, if I knew how to do it I would help. At the moment I'm just struggling to understand how/why things like this happen. Sorry if my previous comment was a bit acidic 🙏

grimmdude commented 1 year ago

I've added channel support for the ProgramChangeEvent. Setting different channels for the program change events should solve this, at least it did for me in GarageBand.

track.addEvent(new MidiWriter.ProgramChangeEvent({instrument: 1, channel: 2}));

I have a feeling the note events should technically be added using the same channel, but GarageBand doesn't seem to care. If that is necessary though, I think the ultimate fix will be to support setting the channel on the track level and have all events inherit from that.

jessgusclark commented 1 year ago

@grimmdude thank you very much. This seems like a good solution!