djipco / webmidi

Tame the Web MIDI API. Send and receive MIDI messages with ease. Control instruments with user-friendly functions (playNote, sendPitchBend, etc.). React to MIDI input with simple event listeners (noteon, pitchbend, controlchange, etc.).
Apache License 2.0
1.53k stars 115 forks source link

Can't get the MIDI port ID for a `ControlChangeMessageEvent` #220

Closed jjeff closed 2 years ago

jjeff commented 2 years ago

Describe the bug and how to reproduce it The ControlChangeMessageEvent Typescript interface defines the target as only being an InputChannel - which is a type that doesn't contain an .id property. I'm porting from WebMidi 2 to 3 and using the port IDs as unique identifiers for MIDI devices.

So the following Typescript generates errors:

function receiveCC(midiEvent: ControlChangeMessageEvent): void {
  const target = midiEvent.target as Input // <-- error: Can't convert 'InputChannel' to 'Input'
  const id = target.id
  const channel = midiEvent.message.channel
  const hash = 'cc:' + midiEvent.controller.number
  emitMidiEvent(hash, id, channel)
}

compared to the note handler... which seems to be fine:

function receiveNoteOn(midi: NoteMessageEvent): void {
  const note = midi.note.number
  const channel = midi.message.channel
  const id = midi.target.input.id
  const hash = 'noteon:' + note
  emitMidiEvent(hash, id, channel)
}

So I'm not quite sure if this is a question or a bug report. Maybe it's both.

The bug: Typescript can't gleen the port ID for a ControlChangeMessageEvent.

The question: How do I get the port ID for a CC event?

jjeff commented 2 years ago

Ugh. Nevermind. I've realized that the exact same method from NoteMessageEvent works with ControlChangeMessageEvent.

djipco commented 2 years ago

Yeah... this is not obvious and is one of the changes between v2 and v3. Here's the gist of it: when you add a listener for a channel event (e.g. noteon) to an Input object, it's actually the InputChannel that dispatches the event. In this case, the Input object acts as a middle man and actually adds the listener to the InputChannel objects for you. This allows you to listen for noteon on many channels at once.

If you only want to listen to noteon on a single channel, you should add the listener directly to that specific channel.

This means that the target property now is an InputChannel instead of an Input. Unfortunately, this is one of the things I could not make backward compatible. However, as you have found, all InputChannel objects have an input property that points back at their parent Input. You can fetch the id from there.