WebAudio / web-midi-api

The Web MIDI API, developed by the W3C Audio WG
http://webaudio.github.io/web-midi-api/
Other
321 stars 55 forks source link

Refactor getInput, getOutput -> getPort(), inputs[], outputs[] ? #2

Closed jussi-kalliokoski closed 10 years ago

jussi-kalliokoski commented 11 years ago

Originally reported on W3C Bugzilla ISSUE-20505 Mon, 24 Dec 2012 08:15:56 GMT Reported by Marcos Caceres Assigned to This bug has no owner yet - up for the taking

The methods getInput() and getOutput() effectively do the same thing (evident also by the amount of duplicate text in the spec); as such, they should be merged. Furthermore, it's not clear (in the spec) as why there are 3 different ways to get a MIDIPort. I would like to suggest that these two methods be merged into a single method and the way to be one way to get a MIDIPort: by its ID… hence getPort(id) or getPortById(id);

Consider:

  1. getInput/Output(MIDIPort)… doesn't make much sense, as why is a developer requesting a MIDIPort with a MIDIPort they already have? I'm sure there is something I'm missing here.
  2. getInput/Output(short): first, short here doesn't make much sense, given that a WebIDL short is defined as: "The short type is a signed integer type that has values in the range [−32768, 32767]". So requesting negative indexes don't make any sense here. In addition, this is basically the same as doing:

var port = midiAccess.getInputs()[x];

Additionally, the ordering of the ports may or may not be consistent after each session, so saying getInput(number) is inherently unreliable. Also, it's kinda unhelpful is you want to, for instance, get the last available port. This is because midiAccess has no length property. The only way to get the length is to first call getInputs(), and get the length from there… but then you already have the list of inputs, in so you don't then need to call getInput( number ), because it's already the same as inputs[number].

  1. getInput/Output( id ): this is probably the only one that makes sense. It's a cheap way of checking if a previously used exists:

var favInstrument = midiAccess.getInput(localStorage.fav) if(favInstrument){ … } Also, if target is is not found, just return null. Please don't throw an exception. Throwing exceptions should be done in "exceptional" circumstances, not on simple lookups.

jussi-kalliokoski commented 11 years ago

Original comment by Chris Wilson on W3C Bugzilla. Tue, 25 Dec 2012 23:20:35 GMT

1) The separation between these is because they are, in fact, two different lists (inputs and outputs), and they're fundamentally different objects; one can only read, one can only write. Merging these entirely will, I think, tend to be confusing, as output ports will have an onmessage handler, and input ports will have a send() method, even though they are orphans that don't do anything. I'm not very comfortable with that, as I think the guard rails (guide rails?) of having the methods only where appropriate is good.

On why there are three methods to get an input/output - personally, I'd ditch the MIDIPort version, but I do want to keep the index version, as (if you look at most of my demos), it's fairly common to show a dropdown to select ports, and index is an easy way to do that. No, it won't persist over sessions, but it doesn't need to. In fact, if you were persisting over sessions, I'd expect an app to pop "pick your port" dialog that is index-based, and then persist the ID.

jussi-kalliokoski commented 11 years ago

Original comment by Jussi Kalliokoski on W3C Bugzilla. Tue, 25 Dec 2012 23:48:44 GMT

(In reply to comment #1)

1) The separation between these is because they are, in fact, two different lists (inputs and outputs), and they're fundamentally different objects; one can only read, one can only write. Merging these entirely will, I think, tend to be confusing, as output ports will have an onmessage handler, and input ports will have a send() method, even though they are orphans that don't do anything. I'm not very comfortable with that, as I think the guard rails (guide rails?) of having the methods only where appropriate is good.

On why there are three methods to get an input/output - personally, I'd ditch the MIDIPort version, but I do want to keep the index version, as (if you look at most of my demos), it's fairly common to show a dropdown to select ports, and index is an easy way to do that. No, it won't persist over sessions, but it doesn't need to. In fact, if you were persisting over sessions, I'd expect an app to pop "pick your port" dialog that is index-based, and then persist the ID.

I agree with Chris, the port types (input, output) are very different, and having the MIDIPort interface as separate from those two helps with the fact that you don't have to capture the devices when you enumerate (e.g. winmm grants only one program exclusive, and exclusive only, access per MIDI device).

As for the getInput/getOutput merging, I'm quite fine with reducing the overloads to just the ID, the use case Chris mentioned is as simple and better handled with IDs anyway, to be more resilient to live hardware changes:

inputSelect = document.createElement('select') document.body.appendChild(inputSelect)

midiaccess.enumerateInputs().forEach((input) => { var elem = document.createElement('option') elem.value = input.id elem.innerHTML = input.name inputSelect.appendChild(elem) })

inputSelect.onchange = function () { var input = midiaccess.getInput(this.value) }

But I'm not sure it's a good idea to merge the two different methods, unless we want to add a mandatory constraint that the fingerprint/ID needs to be different for input devices and output devices. It would be especially annoying if faced with a UA that doesn't have enough data to give reliable fingerprints, an application would've stored a fingerprint and assumed that the method would return an output port, but was given an input port instead, resulting in an error if it tried to send anything to it.

jussi-kalliokoski commented 11 years ago

Original comment by Marcos Caceres on W3C Bugzilla. Wed, 26 Dec 2012 00:14:20 GMT

(In reply to comment #2)

I agree with Chris, the port types (input, output) are very different, and having the MIDIPort interface as separate from those two helps with the fact that you don't have to capture the devices when you enumerate (e.g. winmm grants only one program exclusive, and exclusive only, access per MIDI device).

Ok, my objection was purely based on having to add more objects to the global scope (i.e., all WebIDL interfaces end up on the Window object... but c'est la vie). From an authoring perspective, it makes little difference and Chris' is right in that it may avoid some confusion.

As for the getInput/getOutput merging, I'm quite fine with reducing the overloads to just the ID, the use case Chris mentioned is as simple and better handled with IDs anyway, to be more resilient to live hardware changes:

inputSelect = document.createElement('select') document.body.appendChild(inputSelect)

midiaccess.enumerateInputs().forEach((input) => { var elem = document.createElement('option') elem.value = input.id elem.innerHTML = input.name inputSelect.appendChild(elem) })

inputSelect.onchange = function () { var input = midiaccess.getInput(this.value) }

But I'm not sure it's a good idea to merge the two different methods, unless we want to add a mandatory constraint that the fingerprint/ID needs to be different for input devices and output devices.

I'm of the opinion that one should go for "the ideal" solution first, then backtrack if it fails. In this case, it would mean getting some implementation experience to see if we can get unique fingerprints.

It would be especially annoying if faced with a UA that doesn't have enough data to give reliable fingerprints, an application would've stored a fingerprint and assumed that the method would return an output port, but was given an input port instead, resulting in an error if it tried to send anything to it.

Agreed. But as I said above, at this stage in the standardisation game, it does not hurt to see if that problem can be solved somehow.

Having said that, we should add a big red note to the spec asking implementers for feedback exactly about this.

jussi-kalliokoski commented 11 years ago

Original comment by Marcos Caceres on W3C Bugzilla. Wed, 26 Dec 2012 15:27:24 GMT

(In reply to comment #3)

(In reply to comment #2)

It would be especially annoying if faced with a UA that doesn't have enough data to give reliable fingerprints, an application would've stored a fingerprint and assumed that the method would return an output port, but was given an input port instead, resulting in an error if it tried to send anything to it.

Agreed. But as I said above, at this stage in the standardisation game, it does not hurt to see if that problem can be solved somehow.

Having said that, we should add a big red note to the spec asking implementers for feedback exactly about this.

Just wanted to record a random thought here:

getPort(DOMString id, optional MIDIPortType type);

As in: var port = midi.getPort("12e23f3", "input");

jussi-kalliokoski commented 11 years ago

Original comment by Chris Wilson on W3C Bugzilla. Wed, 26 Dec 2012 17:00:30 GMT

(In reply to comment #4)

Just wanted to record a random thought here:

getPort(DOMString id, optional MIDIPortType type);

As in: var port = midi.getPort("12e23f3", "input");

But why would you do that? For any given id, the type is predetermined (and fixed). If you were going down this path (of collapsing Input and Output together), I would expect:

interface MIDIAccess { sequence listInputs (); sequence listOutputs (); MIDIPort getPort (MIDIPort or DOMString or short target); };

jussi-kalliokoski commented 11 years ago

Original comment by Marcos Caceres on W3C Bugzilla. Wed, 26 Dec 2012 17:10:32 GMT

(In reply to comment #5)

(In reply to comment #4)

Just wanted to record a random thought here:

getPort(DOMString id, optional MIDIPortType type);

As in: var port = midi.getPort("12e23f3", "input");

But why would you do that? For any given id, the type is predetermined (and fixed).

Jussi said that the fingerprint might not be reliable (i.e., an input and and output would have the same fingerprint): "It would be especially annoying if faced with a UA that doesn't have enough data to give reliable fingerprints, an application would've stored a fingerprint and assumed that the method would return an output port, but was given an input port instead, resulting in an error if it tried to send anything to it."

If you were going down this path (of collapsing Input and Output together), I would expect:

interface MIDIAccess { sequence listInputs (); sequence listOutputs (); MIDIPort getPort (MIDIPort or DOMString or short target); };

Sorry to again be a dumbass, but I really don't understand why you send a MIDIPort to get a MIDIPort? Can you please explain the logic there?

jussi-kalliokoski commented 11 years ago

Original comment by Chris Wilson on W3C Bugzilla. Wed, 26 Dec 2012 17:29:52 GMT

(In reply to comment #6)

(In reply to comment #5)

(In reply to comment #4)

Just wanted to record a random thought here:

getPort(DOMString id, optional MIDIPortType type);

As in: var port = midi.getPort("12e23f3", "input");

But why would you do that? For any given id, the type is predetermined (and fixed).

Jussi said that the fingerprint might not be reliable (i.e., an input and and output would have the same fingerprint): "It would be especially annoying if faced with a UA that doesn't have enough data to give reliable fingerprints, an application would've stored a fingerprint and assumed that the method would return an output port, but was given an input port instead, resulting in an error if it tried to send anything to it."

I don't believe the system would ever confuse an input and an output fingerprint. In my shim implementation, I'll simply prepend "i" or "o" to identify. The confusion potential with fingerprints is across sessions, when you have multiple ports with the same name in the system, when the indices change (e.g. when a device is added or removed from the system) - For example, I have two Novation Launchpads (not an uncommon thing). If I add a controller that ends up (according to the OS) showing up in the list of interfaces before those, suddenly one of those might be at index 5 instead of 4 - and the other one is at index 4, and looks just like the other one used to in terms of name, manufacturer, index... everything that persists.

MIDIPort          getPort (MIDIPort or DOMString or short target);

Sorry to again be a dumbass, but I really don't understand why you send a MIDIPort to get a MIDIPort? Can you please explain the logic there?

I would be happy to drop it. I was simply copying out of the current spec. I think index (short) and id (string) are sufficient.

The MIDIPort type as a param predates the index; Jussi's original proposal had MIDIPort, I believe.

jussi-kalliokoski commented 11 years ago

Original comment by Jussi Kalliokoski on W3C Bugzilla. Wed, 26 Dec 2012 18:12:03 GMT

(In reply to comment #7)

I don't believe the system would ever confuse an input and an output fingerprint. In my shim implementation, I'll simply prepend "i" or "o" to identify. The confusion potential with fingerprints is across sessions, when you have multiple ports with the same name in the system, when the indices change (e.g. when a device is added or removed from the system) - For example, I have two Novation Launchpads (not an uncommon thing). If I add a controller that ends up (according to the OS) showing up in the list of interfaces before those, suddenly one of those might be at index 5 instead of 4 - and the other one is at index 4, and looks just like the other one used to in terms of name, manufacturer, index... everything that persists.

You're probably right, it seems unlikely that the IDs would get confused. Sounds good, I'm fine with going ahead and merging the methods.

jussi-kalliokoski commented 11 years ago

Original comment by Chris Wilson on W3C Bugzilla. Wed, 26 Dec 2012 19:05:00 GMT

(In reply to comment #8)

You're probably right, it seems unlikely that the IDs would get confused. Sounds good, I'm fine with going ahead and merging the methods.

I don't think merging the methods is the right thing to do, as you can't use indices then. I just meant that if you DID have a getPort, it doesn't need a type if it's using ONLY fingerprint (or MIDIPort).

cwilso commented 11 years ago

Further feedback: Ryoya Kawai of Yamaha mentioned that they did not particularly favor the merging of input/output querying to getPorts, but that they found the getInput/getInputs (i.e. one letter difference between method names) confusing. They had liked enumerateInputs/Outputs, but I explained that it's not an enumeration as per Marcos' feedback; perhaps we should change to listInputs()/listOutputs().

cwilso commented 11 years ago

Bump: should we change to listInputs/listOutputs?

marcoscaceres commented 11 years ago

(sorry, it's been a while and I'm trying to get my brain back into this space)

FWIW, I would prefer just ".inputs" and ".outputs" (attributes, instead of methods). Rationale: The word "list" is redundant in that it implies "get" and also the return type (which is a list). Also, given the getting the lists is performed synchronously, and no argument is passed to either method, then they could just be attributes.

I also never got an answer to this question:

MIDIPort getPort (MIDIPort or DOMString or short target); Sorry to again be a dumbass, but I really don't understand why you send a MIDIPort to get a MIDIPort? Can you please explain the logic there?

@cwilso, above you said you would be happy to drop that, but I'm still not sure what the intent was

cwilso commented 11 years ago

MIDIPort getPort() is NOT a part of the spec - it's MIDIInput getInput() or MIDIOutput getOutput() (i.e. the result of the "get" call is not a MIDIPort, it's specifically an input or output object). The getInput/getOutput is what registers a port, and tells the underlying system it should be listening (in the case of an input) or preparing to send (in the case of an output). Otherwise, the underlying system has to listen to all devices, or specifically track the devices with an event handler on message, in order to know what to register for. Let me talk it over with the developer.

.inputs/.outputs would also somewhat imply that numeric indicies are the right way to index the LIVE list (note that the resulting sequence from getInputs/getOutputs is NOT live, so you can index through it).

I meant I would be happy to drop passing a MIDIPort into getInput/getOutput to receive a MIDIInput/MIDIOutput (rather than an ID or an index), since you could just pass in the ID from the MIDIPort anyway.

marcoscaceres commented 11 years ago

I'm happy with whatever you choose to do here.

jussi-kalliokoski commented 11 years ago

I'd be really happy to just get rid of all the overloads on these methods, I believe IDs will cater for all use cases well enough without adding to the mental load of the API.

toyoshim commented 11 years ago

At least, I feel we should remove short type overload against getInput and GetOutput. There are two reasons.

  1. WebIDL doesn't allow short and DOMString overloads (See, also another thread at crbug https://code.google.com/p/chromium/issues/detail?id=235884)
  2. getInput(short index) needs to hold a snapshot of MIDIInput sequence when a user call getInputs() What's happen when a user call getInputs() multiple times? Maybe only the last sequence is valid for short index access. But, it's confusing, redundant, and make implementation complicated.

Because of reason 1, I'll implement it in Chromium without short overload functions tentatively. Any thoughts?

cwilso commented 11 years ago

Takashi-san - I'd like to just change the signatures completely and go with inputs[] and outputs[]. Is that okay with you?

On Wed, May 1, 2013 at 8:01 AM, Takashi Toyoshima notifications@github.comwrote:

At least, I feel we should remove short type overload against getInput and GetOutput. There are two reasons.

1.

WebIDL doesn't allow short and DOMString overloads (See, also another thread at crbug https://code.google.com/p/chromium/issues/detail?id=235884) 2.

getInput(short index) needs to hold a snapshot of MIDIInput sequence when a user call getInputs() What's happen when a user call getInputs() multiple times? Maybe only the last sequence is valid for short index access. But, it's confusing, redundant, and make implementation complicated.

Because of reason 1, I'll implement it in Chromium without short overload functions tentatively. Any thoughts?

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17285662 .

jussi-kalliokoski commented 11 years ago

I'd like to just change the signatures completely and go with inputs[] and outputs[].

That doesn't sound like a good idea, with attributes I believe that you should have a really good justification for breaking the promise of object.attribute === object.attribute which we can't keep without unnecessarily complicating the implementation.

Like said earlier, I'd prefer we just used IDs.

cwilso commented 11 years ago

Can you explain the complexity?

I'm fine dropping Port; I don't like dropping index. inputs[] outputs[] takes care of that.

On Wed, May 1, 2013 at 10:14 AM, Jussi Kalliokoski <notifications@github.com

wrote:

I'd like to just change the signatures completely and go with inputs[] and outputs[].

That doesn't sound like a good idea, with attributes I believe that you should have a really good justification for breaking the promise of object.attribute === object.attribute which we can't keep without unnecessarily complicating the implementation.

Like said earlier, I'd prefer we just used IDs.

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17292759 .

jussi-kalliokoski commented 11 years ago

Can you explain the complexity?

In order to make sure that the attribute stays the same during the comparison operation, we'd have to mandate that inputs[] and outputs[] stay the same until the event loop is freed again, and that's complicated to implement. This is similar to the issue of Web Audio API AudioParam .value changing while it's being read, although we're looking at a much more stable situation.

What's the use case for getting by index that isn't covered by getting by ID?

marcoscaceres commented 11 years ago

Can't you just make it a sequence?

cwilso commented 11 years ago

sequence is actually what I meant, not array.

Jussi: I find it confusing in practice that you get a list of MIDIPorts, then you have to ask to "convert" one into a MIDIInput or MIDIOutput; it would be far far easier to just access them.

So yeah, I misspoke -

Sequence inputs; Sequence outputs;

On Wed, May 1, 2013 at 11:08 AM, Marcos Caceres notifications@github.comwrote:

Can't you just make it a sequence?

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17296478 .

marcoscaceres commented 11 years ago

It would mean going back to having a function ( inputs(), outputs() )

marcoscaceres commented 11 years ago

Just to clarify, those would go back to being methods if we use a sequence because WebIDL doesn't allow attributes to return sequences. I personally think that is ok.

marcoscaceres commented 11 years ago

Err. Could you please send that again without the HTML links? (see on Github)

marcoscaceres commented 11 years ago

Cleaned up for you.

interface MIDIAccess : EventTarget { 
  sequence<MIDIInput> inputs (); 
  sequence<MIDIOutput> outputs (); 
  attribute EventHandler onconnect; 
  attribute EventHandler ondisconnect; 
  MIDIMessageEvent createMessageEvent(optional Uint8Array data, optional double receivedTime); 
  MIDIConnectionEvent createConnectionEvent (MIDIPort port); 
};
marcoscaceres commented 11 years ago

Um, what are these two?

  MIDIMessageEvent createMessageEvent(optional Uint8Array data, optional double receivedTime); 
  MIDIConnectionEvent createConnectionEvent (MIDIPort port); 
cwilso commented 11 years ago

Those are the synthesizing of message and connection events you said I had to add?

On Wed, May 1, 2013 at 11:21 AM, Marcos Caceres notifications@github.comwrote:

Um, what are these two?

MIDIMessageEvent createMessageEvent(optional Uint8Array data, optional double receivedTime); MIDIConnectionEvent createConnectionEvent (MIDIPort port);

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17297153 .

marcoscaceres commented 11 years ago

Oh, no. Sorry for the misunderstanding! The MIDIMessageEvent themselves gain a constructor, we don't need helper methods on the MIDIAccess interface.

cwilso commented 11 years ago

Sorry, my mistake. Will fix.

On Wed, May 1, 2013 at 11:24 AM, Marcos Caceres notifications@github.comwrote:

Oh, no. Sorry for the misunderstanding! The MIDIMessageEvent themselves gain a constructor, we don't need helper methods on the MIDIAccess interface.

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17297318 .

marcoscaceres commented 11 years ago

So for MIDIMessageEvent:

[Constructor(DOMString type, optional MIDIMessageEventInit eventInitDict)]
interface MIDIMessageEvent{
  readonly attribute Uint8Array data;
  readonly attribute double receivedTime;
}
dictionary MIDIMessageEventInit{ 
  Uint8Array data;
  double receivedTime;
}

And for MIDIConnectionEvent:

[Constructor(DOMString type, optional MIDIConnectionEventInit eventInitDict)]
MIDIConnectionEvent{
   readonly attribute MIDIPort port;
}
dictionary MIDIConnectionEventInit{
  MIDIPort port;
}
jussi-kalliokoski commented 11 years ago

Jussi: I find it confusing in practice that you get a list of MIDIPorts, then you have to ask to "convert" one into a MIDIInput or MIDIOutput; it would be far far easier to just access them.

Hmm? It's not like sites should ever access them directly anyway. The sites have no way of knowing what the MIDI device at a given index is designed to do, so the site needs to ask the user which port to use anyway, at which point the site already needs more info than just the port indices. I'd really like to make it inconvenient to access ports by ID to avoid developers just going with getOutput(0) because they're too lazy to implement a user interface for that, then someone goes to a website that tries to play music but instead does weird things to the users lighting system.

cwilso commented 11 years ago

Marcos, why would this be

[Constructor(DOMString type, optional MIDIMessageEventInit eventInitDict)]

instead of

[Constructor(optional Uint8Array data, optional double receivedTime)]

?

Is this just to enable extensibility later?

On Wed, May 1, 2013 at 11:32 AM, Marcos Caceres notifications@github.comwrote:

So for MIDIMessageEvent:

[Constructor(DOMString type, optional MIDIMessageEventInit eventInitDict)] interface MIDIMessageEvent{ readonly attribute Uint8Array data; readonly attribute double receivedTime; } dictionary MIDIMessageEventInit{ Uint8Array data; double receivedTime; }

And for MIDIConnectionEvent:

[Constructor(DOMString type, optional MIDIConnectionEventInit eventInitDict)] MIDIConnectionEvent{ readonly attribute MIDIPort port; } dictionary MIDIConnectionEventInit{ MIDIPort port; }

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17297786 .

cwilso commented 11 years ago

I have an aversion to magic opaque handles, when the user (developer) scenario is a collection (sequence).

Yes, it's always going to be bad practice to be lazy and latch the first device. You will always be able to be lazy, though:

midi. getOutput( midi. getOutputs()[0] );

On Wed, May 1, 2013 at 11:37 AM, Jussi Kalliokoski <notifications@github.com

wrote:

Jussi: I find it confusing in practice that you get a list of MIDIPorts, then you have to ask to "convert" one into a MIDIInput or MIDIOutput; it would be far far easier to just access them.

Hmm? It's not like sites should ever access them directly anyway. The sites have no way of knowing what the MIDI device at a given index is designed to do, so the site needs to ask the user which port to use anyway, at which point the site already needs more info than just the port indices. I'd really like to make it inconvenient to access ports by ID to avoid developers just going with getOutput(0) because they're too lazy to implement a user interface for that, then someone goes to a website that tries to play music but instead does weird things to the users lighting system.

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17298090 .

jussi-kalliokoski commented 11 years ago

You will always be able to be lazy, though: midi. getOutput( midi. getOutputs()[0] );

Actually, what I want is

midi.getOutput( midi.getOutputs()[0].id );

Why add a convenience helper for a bad thing when the bad thing is already pretty easy to do?

The UAs are free to cater better options to lazy developers though, such as providing a UI for the user to select a preferred MIDI playback device and then always giving that device an ID "default", then the developer can just do:

try {
  outputPort = midi.getOutput( 'default' ) ;
} catch (e) {
  // Ask the user which device she wants to use for playback.
}
cwilso commented 11 years ago

Why not get rid of the MIDIPort instantiation (and the re-querying to call getOutput()) altogether, and just expose a sequence of MIDIOutputs?

You're trying to prevent people from being lazy, which is just boiling the ocean as far as I'm concerned. And please, let's NOT do the "UA for default MIDI device" - that never really worked in Windows with MIDIMapper, either. Just let the app authors solve it as they want to.

On Wed, May 1, 2013 at 12:16 PM, Jussi Kalliokoski <notifications@github.com

wrote:

You will always be able to be lazy, though: midi. getOutput( midi. getOutputs()[0] );

Actually, what I want is

midi.getOutput( midi.getOutputs()[0].id );

Why add a convenience helper for a bad thing when the bad thing is already pretty easy to do?

The UAs are free to cater better options to lazy developers though, such as providing a UI for the user to select a preferred MIDI playback device and then always giving that device an ID "default", then the developer can just do:

try { outputPort = midi.getOutput( 'default' ) ; } catch (e) { // Ask the user which device she wants to use for playback. }

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17300328 .

jussi-kalliokoski commented 11 years ago

Why not get rid of the MIDIPort instantiation (and the re-querying to call getOutput()) altogether, and just expose a sequence of MIDIOutputs?

I thought we were over this already? This is not an option unless the UA is supposed to take exclusive control over all MIDI devices on the computer.

You're trying to prevent people from being lazy, which is just boiling the ocean as far as I'm concerned.

I'm not. I just don't see the point of catering special things to their will to cut corners and provide a worse user experience.

And please, let's NOT do the "UA for default MIDI device" - that never really worked in Windows with MIDIMapper, either. Just let the app authors solve it as they want to.

Like said, UAs are free to do this currently, I'm not saying we should spec this.

cwilso commented 11 years ago

Why exclusive control? Just because one app has requested access to a port should not prevent other devices from accessing them.

on default device - I would be upset if a UA decided to make a special magic value of what was supposed to be an opaque identifier.

On Wed, May 1, 2013 at 12:38 PM, Jussi Kalliokoski <notifications@github.com

wrote:

Why not get rid of the MIDIPort instantiation (and the re-querying to call getOutput()) altogether, and just expose a sequence of MIDIOutputs?

I thought we were over this already? This is not an option unless the UA is supposed to take exclusive control over all MIDI devices on the computer.

You're trying to prevent people from being lazy, which is just boiling the ocean as far as I'm concerned.

I'm not. I just don't see the point of catering special things to their will to cut corners and provide a worse user experience.

And please, let's NOT do the "UA for default MIDI device" - that never really worked in Windows with MIDIMapper, either. Just let the app authors solve it as they want to.

Like said, UAs are free to do this currently, I'm not saying we should spec this.

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17301574 .

jussi-kalliokoski commented 11 years ago

Why exclusive control? Just because one app has requested access to a port should not prevent other devices from accessing them.

But with winmm it does exactly that.

on default device - I would be upset if a UA decided to make a special magic value of what was supposed to be an opaque identifier.

To be clear, I don't think that is a good idea, but if we want to serve lazy developers, I think that's the best way to do it.

cwilso commented 11 years ago

Well, then, a Windows implementation will have to release hooks when they're not in use (when no event listeners are registered for Inputs, when not actively sending for output). Nasty, but frankly it's killing a bunch of scenarios otherwise (multiple channels with different apps using IO). I do this in demos today - one app responding to drum pads, different app responding to keys on same input device.

On Wed, May 1, 2013 at 12:46 PM, Jussi Kalliokoski <notifications@github.com

wrote:

Why exclusive control? Just because one app has requested access to a port should not prevent other devices from accessing them.

But with winmm it does exactly that.

on default device - I would be upset if a UA decided to make a special magic value of what was supposed to be an opaque identifier.

To be clear, I don't think that is a good idea, but if we want to serve lazy developers, I think that's the best way to do it.

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-17302042 .

toyoshim commented 11 years ago

I'm back to the discussion. If I understand correctly, the latest proposal is this one?

interface MIDIAccess : EventTarget { 
  sequence<MIDIInput> inputs (); 
  sequence<MIDIOutput> outputs (); 
  attribute EventHandler onconnect; 
  attribute EventHandler ondisconnect; 
};
cwilso commented 11 years ago

Yes. Sound good?

On Thu, May 23, 2013 at 5:18 AM, Takashi Toyoshima <notifications@github.com

wrote:

I'm back to the discussion. If I understand correctly, the latest proposal is this one?

interface MIDIAccess : EventTarget { sequence inputs (); sequence outputs (); attribute EventHandler onconnect; attribute EventHandler ondisconnect; };

— Reply to this email directly or view it on GitHubhttps://github.com/WebAudio/web-midi-api/issues/2#issuecomment-18339515 .

toyoshim commented 11 years ago

yeah, sgtm.

cwilso commented 11 years ago

Update:

Option 1): Sequence cannot be used in an attribute. However, [](aka Array) can, so MIDIAccess can be handled this way. So, the definition could be:

interface MIDIAccess : EventTarget { 
    readonly attribute MIDIInput[] inputs; 
    readonly attribute MIDIOutput[] outputs; 
    attribute EventHandler onconnect; 
    attribute EventHandler ondisconnect; 
  };

However, it's questionable whether Windows can handle non-exclusive access to MIDI devices, and this approach sort of presumes that the implementation doesn't need to explicitly take control of a given interface it's interested in (since it has all of them "ready" in the array at all times once the MIDIAccess is granted). If this is a problem on Windows (we don't want the Web MIDI implementation to have to grab all MIDI devices exclusively, obviously), then we may have to stick with:

Option 2) (what's in the spec right now:)

interface MIDIAccess : EventTarget {
    sequence<MIDIPort> getInputs ();
    sequence<MIDIPort> getOutputs ();
    MIDIInput          getInput (MIDIPort or DOMString or short target);
    MIDIOutput         getOutput (MIDIPort or DOMString or short target);
    attribute EventHandler onconnect;
    attribute EventHandler ondisconnect;
};

Downside of this is the confusing getInput()/getInputs() dichotomy, and the overhead of having an instantiated MIDIPort that you can't just use (i.e. you call getInputs, but you can't set .onmidimessage of getInputs()[0].)

Thoughts? Can I get confirmation/denial of Windows' exclusivity?

marcoscaceres commented 11 years ago

I haven't had my morning cup of coffee yet, but what about just:

  sequence<MIDIInput> inputs (optional (DOMString[] or DOMString) id); 
  sequence<MIDIOutput> outputs (optional (DOMString[] or DOMString) id); 

When the value is missing, it returns everything. When the value is one or more matching ids, it returns a sequence with the matching objects. If a wrong id is supplied, then a NotFoundError is thrown.

The slightly sucky bit is having to unwrap an array with a single value, but it does provide some flexibility when performing look ups for multiple midi objects.... you could even combine the above into:

 emum MIDIIO{ "inputs", "outputs"}
 sequence<MIDIPort> get (MIDIIO type, optional (DOMString[] or DOMString) id);

Then you get:

var in = midi.get("inputs", id)[0];
var ins = midi.get("inputs")
var outs = midi.get("outputs",  ["xyz", "zya"]);
cwilso commented 11 years ago

That seems like even more goofiness. Or maybe I've had one too many cups of coffee.

marcoscaceres commented 11 years ago

:smile: ... at a minimum, we have agreement to only use a DOMString ID, right?

cwilso commented 11 years ago

I'm not sure what that means. Not use a MIDIPort reference, sure - but I still think indexing an array/sequence is going to be how most devs do it. I guess if it's always a sequence returned, that would be handled.

marcoscaceres commented 11 years ago

Sorry, what I mean is that "short" is gone from getInput(), getOutput() - and that there is no need to pass a MIDIPort to those functions.

cwilso commented 11 years ago

If we still have getInput(), then I would want an index to work there. If we have only getInputs(), that returns an array or a sequence, then it's not an issue.