Things in WebMidi.js 2.52 that make me go huh?

I have been using the WebMidi.js library to:

Control parameters in p5.js sketches

Switch scenes and controls audio volume in OBS through Websockets.js

Change patch banks on a Roland D2


While writing the code I have come across several design decisions that made me go huh.


Parameter channel is the second or later argument in many methods.

I am used to thinking of MIDI functions as being action like Note On or Control Change followed by the channel then any values.

I see you buried the channel argument inside options in 3.0


parameter channel default is all

I can't see sending a Note On to all 16 MIDI channels.


PlayNote and StopNote have a default Velocity Range as a float from 0.0 to 1.0

I have to add a rawVelocity=true inside options to have it interpreted as 0-127

Why is the default a float?

value for a Control Change isn't a float.


The default value for Velocity is 0.5

Why 0.5? why not 127


You mention that Webmidi.js will append an RPN call to clear the parameters after an NRPN call in this forum post:

https://webmidijs.org/forum/discussion/22/why-does-nrpn-also-send-rpn/p1

This is not documented publicly anywhere.

I look at NRPN as setting the Parameter Number once and then just sending the Value I want the NRPN to set to.


I could see doing the reset in an onunload event, but not for every single NRPN call.


I tested this with a Korg Electribe EA-1 which requires NRPN calls to change parameters even though no value ever goes above 127.

The setNonRegisteredParameter method and sending Control Changes 99, 98 and 6

Both work.


sendControlChange Control Code numbers are restricted to 0-119.

That's great if you are working with a hardware device like a synth, but not when I am working with a device that I can freely assign CC numbers like a USB MIDI controller

Comments

  • Thank you so much for your input. This is great feedback as I'm working on v3. There is a lot to unpack in your message, so I'll break it down in separate messages.

    Parameter channel is the second or later argument in many methods. I am used to thinking of MIDI functions as being action like Note On or Control Change followed by the channel then any values. I see you buried the channel argument inside options in 3.0

    The logic behind v3 is that you can work with either Output or OutputChannel objects. If you need to send commands to a single channel, you would use an OutputChannel objet:

    WebMidi.output[0].channels[1].playNote("C3");
    

    If you want to send commands to all channels of a specific device, you would use the broader Output object:

    WebMidi.output[0].playNote("C3");
    

    If you want to send commands to specific channels (for example: 2, 4 and 6) of an output device, you would do:

    WebMidi.output[0].playNote("C3", {channels: [2, 4, 6});
    

    To me, this feels logical but perhaps I'm missing something and I would gladly hear your opinion on this approach. As suggested by another user, I might event allow you to do it directly on the WebMidi object so we can listen for or send messages accross all devices found on the system.

  • parameter channel default is all. I can't see sending a Note On to all 16 MIDI channels.

    It used to be like that in v2 and I figured I'd leave it for backwards-compatibility. For beginners, it makes it very easy to get started. However, in retrospect, it was probably a bad decision. However, v3 mitigates this. If you intend on sending messages to a specific channel on a specific device, ou can just use an OutputChannel object and this problem disappears:

    const myChannel = WebMidi.output[0].channels[1];
    myChannel.playNote("C3");
    

    In the above scenario, there is no need to worry about the channels parameter at all.

  • PlayNote and StopNote have a default Velocity Range as a float from 0.0 to 1.0. I have to add a rawVelocity=true inside options to have it interpreted as 0-127. Why is the default a float? value for a Control Change isn't a float.

    This is a very good point and I actually struggled with this one. For flexibility, I feel using values between 0 and 1 to be a lot more logical. If you project interfaces with other systems, you will most likely use a 0-1 value. However, the MIDI-way would be to pass a 7bit value (0-127).

    Control change is not like that because sometimes a control change message represents a discrete choice (e.g. bank select) while at other times it represents a a graduated choice (e.g. mod wheel).

    I'm trying to preserve backwards-compatibility for v3 but maybe that shouldn't be my first criteria. What do you think? I'd love to hear others chime in on this as well. If I'm going to change this, v3 would be the time to do it.

  • The default value for Velocity is 0.5. Why 0.5? why not 127

    As far as I know the MIDI spec does not say anything about what the default velocity should be. A quick search reveals that different manufacturers opted for different values (Cubase, Ardour, Ableton, Steinberg, Logic).

    A value of 0.5 (64) felt like an appropriate default but I'd love to hear why you think it's a bad choice.

  • You mention that Webmidi.js will append an RPN call to clear the parameters after an NRPN call in this forum post: https://webmidijs.org/forum/discussion/22/why-does-nrpn-also-send-rpn/p1 This is not documented publicly anywhere. I look at NRPN as setting the Parameter Number once and then just sending the Value I want the NRPN to set to.

    I could see doing the reset in an onunload event, but not for every single NRPN call. I tested this with a Korg Electribe EA-1 which requires NRPN calls to change parameters even though no value ever goes above 127. The setNonRegisteredParameter method and sending Control Changes 99, 98 and 6. Both work.

    NRPNs are the only part of WebMidi.js I did not create myself. It was submitted by a kind soul but I never really got around to thoroughly test them. I will be redoing all that work in v3 to make sure it's solid.

  • sendControlChange Control Code numbers are restricted to 0-119. That's great if you are working with a hardware device like a synth, but not when I am working with a device that I can freely assign CC numbers like a USB MIDI controller

    Per the specification, MIDI controller numbers 120-127 are reserved for Channel Mode messages. That's why I left them out. Instead, I created specific methods for these (e.g. seLocalControl() , setOmniMode() , etc. ). I believe other manufacturers opted for the same approach (Ableton, for example).

    Having said that, if it could be useful to allow them, that could easily be done without breaking backwards-compatibility. I simply do not want to break broader MIDI compatibility. Thoughts?

  • Channel Argument

    I am used to thinking of MIDI as Command and Channel combined into 1 byte, then parameter values following after.

    So I would expect:

    Note On, Channel, Note, Velocity

    92 32 7F

    Control Change, Channel, Value

    B2 7F

    I'll definitely take advantage of the OutputChannel class.

  • Note Velocity being a float threw me off when I was trying to control the LED's on an Akai APC Mini which takes values from 0 to 6 to select the LED colour and LED flashing.

    Sending velocity 3 which should have lit up a pad in red didn't do anything.

    I wasn't expecting to have to send 0.0234375 to get an LED to light red.

    Rereading the docs more carefully was when i went huh.

    I think it should use 0-127 with a float 0.0-1.0 being the option.

    You could make the int to float conversion a utility method or just an array.


    As for the default value for velocity, aside from 0 which is treated as Note Off, and 64 which the MIDI spec is suggested for non-velocity sensitive synths, I have seen 100 and 127.

    I think it should be 127, or a developer settable default.

  • I am used to thinking of MIDI as Command and Channel combined into 1 byte, then parameter values following after. So I would expect: Note On, Channel, Note, Velocity

    I totally understand where you are coming from. You probably have a good knowledge of how MIDI works (which is not necessarily the case for everybody). The trick for me is to strike the right level of abstraction.

    With the OutputChannel in v3, I think I have the right balance. Instead of always thinking about channel, you fetch the device once and then only worry about the note (and velocity):

    const device = WebMidi.output[0].channels[1];
    device.playNote("C3");
    
  • Note Velocity being a float threw me off when I was trying to control the LED's on an Akai APC Mini which takes values from 0 to 6 to select the LED colour and LED flashing. (...) I think it should use 0-127 with a float 0.0-1.0 being the option.

    Your example illustrates the problem very well. I mostly agree with you but am concerned about backwards-compatibility. I'd love to get opinions from others about this.

    As for the default value for velocity, aside from 0 which is treated as Note Off, and 64 which the MIDI spec is suggested for non-velocity sensitive synths, I have seen 100 and 127. I think it should be 127, or a developer settable default.

    I think your suggestion to use a settable default makes a lot of sense. I have added it to my todo list. By the way, can you tell me where you saw the suggested value for non-velocity sensitive synths?

  • edited May 6

    MIDI Spec recommends a value of 64 for non-velocity sensitive devices:

    Complete MIDI 1.0 Detailed Specification Document (1996) - complete_midi_96-1-3

    page 42 of the pdf. Page 10 of the section MIDI 1.0 Detailed Specifications

  • Got it!

    Thanks again for your very valuable feedback Tak. If you feel like it, I would like to invite you to fill in the survey I set up about version 3:

    There will be a big push in the fall to get v3 out and feedback is always welcome.

    Cheers!