grimmdude / MidiWriterJS

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

NoteEvent limitation (?) #29

Closed PedroAlvesV closed 6 years ago

PedroAlvesV commented 7 years ago

Is there any way of, in a single track, playing two notes, with different durations, simultaneously?
I mean, imagine an A4 and an E4; One whole note, the other quarter note; Both starting together. Is there any way of producing it with this API?

The same for arpeggios: is it possible to start a note before stopping the previous one?

grimmdude commented 7 years ago

Hi @PedroAlvesV ,

Currently it is not possible to have overlapping durations within a single track in this API; it would have to be on separate tracks. This would be a nice feature, though I'm not sure how the API would look for that. Do you have any ideas?

-Garrett

PedroAlvesV commented 7 years ago

Yep. I was thinking about giving the user more control over it. I mean, the NoteEvent is an awesome abstraction, but, as I pointed, there's this one limitation. Maybe if the user could, by wish, set the On and Off signals, it would solve the problem. So, it would keep the NoteEvent, but also offer the usage of NoteOn and NoteOff.

Another thing that I noticed is that these classes (NoteOn and NoteOff) do exactly the same, and what they do is not even enough to justify a single whole class, from the way I see it. Also, they're used exclusively in NoteEvent:buildData() and in Track:polyModeOn().


What I would do (and probably will with LuaMidi haha):

grimmdude commented 7 years ago

Hi Pedro,

Good ideas. I think I used separate classes for NoteOn and NoteOff with the thought that it would be convenient to add more functionality to them later. You're right though, currently they're just containers for properties.

I'm thinking something like this may work to provide the ability to have two simultaneous notes with different durations, where the duration index would line up with the pitch index:

new MidiWriter.NoteEvent({pitch: ['E4','D4'], duration: ['4', '2']})

The problem, though, is how to know where the next NoteEvent will begin.

As you suggested, we could provide functions which allow you to explicitly turn on/off notes, but the user/developer would need to keep track of timing themselves though so it's just not very user friendly. Do you have a particular need for this, or is it just something you had in mind?

-Garrett

PedroAlvesV commented 7 years ago

The user would have to be aware of the delta time, that's true, but most people working with MIDI frameworks do. Besides, the NoteEvent would always be available. I agree that it's very dangerous to use an array of durations. I think NoteEvent is just fine as it currently is.

I have been wondering about it because I wrote a function that receives a MIDI file and creates LuaMidi objects to represent it. It works pretty fine, actually. The only thing I couldn't manage it to get, yet, is the timing, but that's because I haven't code anything this week haha

However, this overlapping functionality is somehow a basic need. Take as example Stairway to Heaven's intro. It sounds kind of chiptune or something like it, because the arpeggio sounds as if there's a hardware limitation of one note at a time.

grimmdude commented 7 years ago

I think in the case of Stairway to Heaven, and other similarly played sustained arpeggios, changing the note durations isn't the best approach because it becomes a bit unreadable as musical notation. I understand that you can dial in the exact note length that way though. The notes would still be abruptly cut off instead of trailed off.

For this case I think there should be support in this API for Controller Change events. Then you can apply a sustain pedal as needed for the notes that should be sustained.

Looks like the status code for Control Change is Bn. Here's a list of messages:

https://www.midi.org/specifications/item/table-3-control-change-messages-data-bytes-2

I'll take a closer look at this when I have some time and try to incorporate into MidiWriterJS.

-Garrett

grimmdude commented 6 years ago

Hi @PedroAlvesV,

I've added a ControllerChangeEvent class in 1.5.2 which can be used to add controller events.

track.addEvent([
    new MidiWriter.NoteEvent({pitch: ['A4'], duration: '4'}),
    new MidiWriter.ControllerChangeEvent({controllerNumber: 1, controllerValue: 127}),
    new MidiWriter.NoteEvent({pitch: ['B4'], duration: '4'}),
]);

I think whether or not the control changes are respected will depend on the player you use. But you should now be able to add sustain pedal controls to achieve what we were discussing above. Let me know if you have any issues.

-Garrett

jpw commented 6 years ago

I knew there was an issue somewhere I meant to comment on!

Starting one note on a channel before another ends (legato?), where the second note "chokes" the first seems often to be how slides/glissando are sequenced on 303-style hardware synths (MB-33 & clones, TT-303, x0xb0x...), probably because they tend not to have MIDI implementations sophisticated enough for controller events. Some more sophisticated synths (e.g. Behringer DeepMind 12) also use this same mechanism, the speed of the slide (or none at all) being dependant on a portamento setting.

IIRC MIDI notes on a single channel cannot overlap, so no gap is a primitive way of specifying slide.

However I have not tested this with MidiWriterJS (I am using it to algo-generate MIDI files and play them with hardware) but will give it a go and find out. Thanks!