midi subscribe/unsubscribe #476
Conversation
|
fixes #454 (once finished) |
|
|
||
| --- send midi event to all devices | ||
| function Midi.send_all(data) | ||
| for id,device in pairs(Midi.devices) do |
pq
Jul 25, 2018
Collaborator
maybe for _, device in ... (since id is ignored?)
maybe for _, device in ... (since id is ignored?)
pq
Jul 25, 2018
Collaborator
aside: i'm guessing arbitrary order is ok (and hence the use of pairs) but I wonder if the ordering of ipairs might ever help avoid surprises in debugging?
aside: i'm guessing arbitrary order is ok (and hence the use of pairs) but I wonder if the ordering of ipairs might ever help avoid surprises in debugging?
|
|
||
| --- unsubscribe | ||
| function Midi.unsubscribe(callback) | ||
| for i,v in pairs(Midi.broadcast) do |
pq
Jul 25, 2018
Collaborator
again on pairs ... will an arbitrary unsubscription order ever be surprising? (in general i'd expect unsubscription to happen in a deterministic order the reverse of subscription but maybe in practice this doesn't matter?)
again on pairs ... will an arbitrary unsubscription order ever be surprising? (in general i'd expect unsubscription to happen in a deterministic order the reverse of subscription but maybe in practice this doesn't matter?)
| @@ -78,6 +146,14 @@ norns.midi.event = function(id, data) | |||
| if d.event ~= nil then | |||
| d.event(data) | |||
| end | |||
| -- do any individual subscribed callbacks | |||
| for name,callback in pairs(Midi.devices[id].callbacks) do | |||
pq
Jul 25, 2018
Collaborator
again maybe: for _, callback in pairs ... ? (also note that name here shadows name in the function scope.)
again maybe: for _, callback in pairs ... ? (also note that name here shadows name in the function scope.)
| callback(data) | ||
| end | ||
| -- do broadcast callbacks | ||
| for n,callback in pairs(Midi.broadcast) do |
pq
Jul 25, 2018
Collaborator
one more chance for _ if it floats your boat.
one more chance for _ if it floats your boat.
|
Looks good, but the part with returning a To resolve this, we could either have explicit methods for sending data to midi, i.e. keep |
|
potential renames:
but also, the return of a send function is a convenience. furthermore, it's been confirmed that multiple similar devices enumerate with the same device string, so differentiation won't work for that use case. i can't decide if that tradeoff merits change. my matching device names we can reattach midi devices with already-defined callbacks for specific device strings. this seems like the more important feature IMO. luckily with custom controllers like the teensy you can rename the midi device string (right?) and that fixes the problem. so the use case left out is someone wanting to have two commercial midi controllers do different callbacks. but they can do this with the old method, attaching handlers to each device from but my hope was to have a unified solution. not a "low level" and "convenience" way of handling midi/grids/etc. side note: with grids we can attach handlers to the serial number, so this approach works perfectly there. thoughts? |
|
Just throwing this out there as an observer, apologies if it's off track! :) I feel like I would expect a local dev_id = midi.get_device_id("controller name")
-- working with ids allows us to solve for multiple controllers with the same name by
-- letting you also browse the complete list of midi.devices
-- a generic subscribe to get everything
-- midi.subscribe(handler, dev_id, [channel])
local dev_connected = midi.subscribe(all_midi_func, dev_id)
-- there could also be midi.subscribe_by_name() for convenience
-- convenience subscribes for message types
-- midi.subscribe_cc(handler, dev_id, [channel])
midi.subscribe_cc(my_cc_func, dev_id, 1)
-- for sending any data there's midi.send(dev_id, {data})
-- also convenience functions for sending various data types
-- midi.send_note_on(dev_id, channel, note, velocity)
midi.send_note_on(dev_id, 1, 40, 64) |
|
@markwheeler new proposed syntax below, as discussed with @pq and @artfwo local op1 = midi.connect("OP-1 Midi Device")
op1.event = process_op1
op1.send{144,80,100}
function process_op1(data) print(data[1]) end
local all = midi.connect()
all.event = process_all
all.send{144,80,100}
function process_all(data) print(data[1]) end |
|
cool! presumably i could also connect and then in future it'll be nice to add some util functions or maybe just consts for message types etc for handling the midi data in a more readable way. |
|
@markwheeler for two of the same-named device you'd need to redefine we're trying to come up with a simplified syntax that covers most use cases-- multi-same-named devices is the current downside of this approach, but it solves very many other issues (like re-plug working correctly). i'm up for proposals regarding handing midi messages. ie
design ideas welcomed... |
|
updated top post with newest syntax. do we like the name op1.handler = function(data) print(data[1]) endalso added the beginnings of some util.
see the current code for how it works, haven't added types beyond note and cc. also it seems it would be nice to define some helper functions ie also considering a "filter" model for redirecting traffic of incoming events. ie
where the todo:
|
|
is this approach assuming users are going to need to edit a script to reflect their midi devices? how about using a virtual /midi mapping approach? basically scripts would refer to virtual devices (mididevice1..N) this means musicians would not have to deal with code etc, and also scripts would be more 'generic' this approach would also work for multiple devices with the same device name (as they have difference id) e.g. two launchpad grids. |
|
This is looking great! My main feedback is that I still feel pretty strongly about the handlers needing to be lists you add and remove things from, otherwise it's going to be hard for people to combine multiple musical ideas from several scripts into a new script. (per discussion in #454) The local op1 = midi.connect("OP-1 Midi Device")
op1.add_handler(process_op1)
op1.send{144,80,100}
function process_op1(data) print(data[1]) endIf different parts of the script add separate handlers, they'll both be called. This pattern can extend fairly naturally to allowing the user to offload some of the filtering to the system, either with arguments: local op1 = midi.connect("OP-1 Midi Device")
op1.on(function (data) print(data[1]) end)
op1.on('cc', function (controller, value) ... end)
op1.on('cc', 17, function (value) ... end)
op1.send{144,80,100}or with method names like I agree with @TheTechnobear's comment above about virtual devices, but I think that could be deferred until later and implemented on top of The main counterargument I can think of is that the system MIDI code could end up doing a lot of branching and having to check several different lists on every MIDI event. I'm still pretty new to Lua but I think that can be made efficient, and it'd be nice to do it once rather than making every script author think about it. |
|
After thinking about it some more, I'm no longer convinced it's critical that specific-device midi callbacks be stackable. It might be nice to use a method rather than the assignment operator so callbacks could become stackable later without breaking the API, but I withdraw my concerns from the previous comment. |
actually they're already stackable:
this works with the current code. it's important because
agreed, i should've mentioned earlier. i'm thinking an extra SYSTEM menu list for virtual devices with a selector:
these strings can get stored in a table so it'd be natural for a user script to say:
and then by default all the virtual devices would point at ALL |
|
i could see adding the syntax you suggested:
where basically
i'm still feeling a little up in the air about filtering/directing messages by type. |
|
how do we want to handle note on vs. note off? ie 0x80 data messages vs. velocity 0? do we need to have separate |
|
note_off type, since that still allows for release velocity. |
|
@TheTechnobear so norns should also send |
|
I was thinking more when receiving... so that user can handle note_off consistently, for both newer and older devices, with one bit of code, rather than having to handle both use cases sending, hmm, i guess thats consistent. or sending, we leave it up to the script, if the script is not doing anything with release velocity, then they can choose either to send note_off, or note_on/v=0... |
|
many improvements. see top post (and code) for updates! |
I can probably do some testing this weekend if that's not too late |
|
tried this out now. found a bug. to recreate:
expected result: system > devices > grid menu is shown |
|
also one question - is midi subscribe / unsubscribe additions supposed to be backwards compatible (i think i read something about this). i tried my current |
|
also, a comment on the API. when is to clarify: for one-grid scenarios for multi-device scenarios, this would:
likewise for midi... rather than to have |
|
@antonhornquist thanks for the bug report. i'll fix it. i tried not to break any backwards compatibility with midi/grid regarding the "all" method is necessary as auto-population wouldn't really work given the current implementation. the virtual device list is a set of strings. it doesn't execute callbacks when these strings are changed... this would add a substantial layer of complication and likely require the whole thing to be rewritten (which could be a future project when someone is up for it.) i don't see dynamic virtual-device switching mid-script as a needed feature. scripts should just be re-initialized. agreed that "none" is a good idea for a default, but the first-time use case isn't very nice. ie. someone loads i definitely see an advantage to having "all" for midi, and "all" for grids is less useful certainly. (though mirrored grids is novel certainly... it does reveal some libmonome issue) |
|
@antonhornquist menu bug fixed |
|
anyone spent some time with this? i'll do more testing today ahead of a merge tonight. i'll make an accompanying update to dust fixing all of the scripts (in terms of grid interaction) |
|
considering naming the callbacks something nicer than "handler" midi: or perhaps just universally using |
|
@Dewb i'll have a go at updating |
|
big rework of the virtual port system for midi. much improved! see top post. all
todo
|
|
I get this when using this the first time after going to
It's double for some reason |
|
@simonvanderveldt thanks for the report. i'll test with clean system settings. it should look like this:
edit, oh you mean in the selector. aha, i'll investigate |
|
Just ran the two testscripts from maiden, all MIDI events are correctly logged. Also checked the grid with a minor change ( I guess this change means all scripts that use MIDI or a grid need to be updated? Will they use device 1 by default? Or how will that work exactly? |
|
@simonvanderveldt great, thanks. i believe any slowness in the cc's is just print lag. within the system, cc's mapped to filter, for example, is incredibly responsive. i tested with a touch controller, and it works for percussive playing. dust scripts are already updated: monome/dust#184 default port is 1, yes. |
|
this may the same issue as reported by @simonvanderveldt above. with a 128 plugged and changing device for
it's also possible to move cursor down until nothing is selected. |
|
also, after plugging out a midi device (OP-1 Midi Device) that midi device still shows up in the list of possible devices to choose even though there's a edit: this appears to be intermittent. anyhow a selected "OP-1 Midi Device" remains in the DEVICE > MIDI screen after it has been removed. and the possible devices to choose from seems a bit buggy (as @simonvanderveldt has pointed out). when i removed my OP-1 now it ended up in "all", "none", "all", "none", but there was only "OP-1 Midi Device", "all" and "none" prior to that. |
|
@antonhornquist i wasn't able to replicate the "all" "none" duplication bug. do you know a way to replicate it exactly? the unplug update is tricky. the list is updated when you enter the DEVICES menu, so if you add/remove devices while in this menu it won't be updated until you re-enter the root DEVICES menu. i can change this to update when you enter SELECT within DEVICES, but still the update won't be live on-screen if you're inside select... |
howto:
notes
grids!
follows same patterns as midi re: all/mirroring and named assignments