monome / crow

Crow speaks and listens and remembers bits of text. A scriptable USB-CV-II machine
GNU General Public License v3.0
162 stars 34 forks source link

MIDI input #12

Closed trentgill closed 5 years ago

trentgill commented 5 years ago

Requires the user to call a global function activating midi mode. this could / should deactivate the ADC on channel one, and definitley deactivate any callback set for that channel. Might be able to auto-detect midistate by leaving the UART activated, and asking the user to 'press any key' on their device, then detect this as uart data.

UART should be on it's own channel to be independent of the uart-debugger.

probably just want to have a lua callback for any midi message, and likely just copy the midi-input library directly from norns (or include it directly).

expose some helper functions to the user that simplify getting notes vs. ccs vs. sync.

trentgill commented 5 years ago

the low-level driver for this is working but needs a layer to make it more usable. currently it just gives a stream of bytes (the right ones!) with no effort made to decide where the packets start/stop.

can likely lift a good chunk of the solution from the norns midi implementation.

tehn commented 5 years ago

yep this is totally doable, great to hear the serial is coming in!

trentgill commented 5 years ago

LL driver currently receives 0 to 9 bytes of MIDI data then the interrupt is triggered but the callback never occurs (and the driver is frozen). does not affect the rest of the system

tehn commented 5 years ago
trentgill commented 5 years ago

low-level processing: df7a2697f92f5a3e24a71ad3afbf2ecc6e91c5c8

trentgill commented 5 years ago

i’m imagining user writes something like this:

m.event = function(data)
  local d = midi.to_msg(data)
  if d.type == ‘note_on’ then
    — handle note on event
  elseif d.type == ‘cc’ then
    — handle cc event
  end
end

i’m sure you’ve considered an alternative, more hand-holdy approach where the m.event has a default definition that has a bunch of other callbacks:

— in the library
m.event = function(data
  local d = midi.to_msg(data)
  if d.type == ‘note_on’ then
    m.note(data)
  elseif d.type == ‘cc’ then
    m.cc(data)
  end
end

— user land
m.note = function(data)
  — handle notes
end

m.cc = function(data)
  — handle ccs
end

seems like not that much of an improvement, and adds a potentially unneccessary layer. the only reason i think of it is it would allow me to move the to_msg function to C which is 90% implemented in the driver layer by necessity (we have to parse on midi type to know when a full midi command is received to create the callback). that is, i could just make a separate callback for each midi type, rather than funneling them through a single channel to lua, then matching again.

//

perhaps it becomes a more interesting in the norns-remote context. the input & timer libraries have default functionality that means adc stream/change events and base timer ticks send a a ^^ command over the usb port unless the user defines their own event. this is aimed at making crow a minimal-effort expansion to the functionality of norns / laptop.

thinking of this goal as it applies to midi, crow can act as a midi-to-usb bridge, but super-powered by the pre-parsed sent-as-lua-functions style.

re: norns this seems absolutely unnecessary. the user can just define the crow midi callback like:

— crow
m.event = function(data) — the default callback
  _c.tell(‘midi’,data) — send a ^^ command over usb
end

— norns
crow.midi = function(data)
  local d = midi.to_msg(data)
  — just like normal norns midi!
end

nb: midi functions are using lua tables as the primary way of passing arguments, but there’s no obvious way to send a key-value table as a function argument as plain text. we’d have to implement something that serialize them for transmission (which might end up being too lengthy).

whereas to get similar functionality in max / readline application, the user would need to import the ‘to_msg’ helper function somehow. it could of course be built into the readline application, but as we’ve mentioned, it’s really nice to have crow just send executable code.

i guess my proposal would be along the lines of having an individual event per message. then we leave sysex and such unimplemented and wait for community engagement with that for what is useful. perhaps:

— crow
m.event = function(data)
  local d = midi.to_msg(data)
  if d.type == ‘note_on’ then _c.tell(‘m.note_on’,d.note,d.vel,d.ch)
  elseif d.type == ‘note_off’ then _c.tell(‘m.note_off’,d.note,d.vel,d.ch)
  elseif d.type == ‘cc’ then _c.tell(‘m.cc’,d.cc,d.val,d.ch)
  end
end

— norns/remote (could be provided as stubs, or available via request from crow)
crow.m.note_on = function( note, velocity, channel )
  — handle a note_on
end
crow.m.note_off = function( note, velocity, channel )
  — handle a note_off
end
crow.m.cc = function( cc, value, channel )
  — handle a cc
end
tehn commented 5 years ago

i have opinions about this but it has to wait until tomorrow morning (sorry!)

tehn commented 5 years ago

i like all of these ideas, they're very cool.

one thing about crow midi is that we only are thinking in terms of midi receiving whereas the norns midi deals not only with both in/out, but multiple ports.

so i take back what i mentioned last night about m vs. midi. it actually maybe makes sense to keep it m as a table, or even long names for callbacks like midi_event_note_on() without a table at all... since on norns you have to instantiate a midi device ie m = midi.connect() (which connects to vport 1 blah blah blah) so it would be confusing to have a midi table/lib on crow that doesn't follow the midi.connect() logic.

another thing this might solve is send vs. receive naming collisions. see https://github.com/monome/norns/blob/dev/lua/core/midi.lua#L121

(on norns) basically m.note_on() etc all all send helper functions, whereas on crow the above proposals make them receive callbacks.

re: unmatched midi bytes, would you basically have something like midi_event_other() ?

agreed re: no requiring to_msg for things like max/msp. your last code block above does a good job, though obviously it's not leveraging C (which is ok).

maybe there's a good way to do catch-all event and event_note_on in parallel.

this is the sort of discussion that would be good to run by the crew on slack. i think some auto-parsing of incoming midi would be an improvement for norns also.

trentgill commented 5 years ago

elevating to 1.0 as it's central to a number of use-cases we want to support out of the box (specifically in the standalone context). specifically speaks to needs in the WR line of modules.