Open loopier opened 3 years ago
Another use for MIDI is to play one animation per note, somewhat like the Melotron. This might require that we can access actors with an index. Noting it here for things to do with MIDI.
Another option (instead or as well as what you propose) would be to map a specific note number to a set of commands. So maybe something more like:
/midi/noteon/36 velocity
/play omi
/fade omi $velocity 0.5
/midi/noteoff/36
/stop omi
/fade omi 0 1
Something like this would be for when you want to map a specific note to affect a specific actor (e.g. start playing an animation on noteon
, stop on noteoff
), whereas your example is setting the frame/fading of all selected actors based on whatever MIDI note is played. This would not require indexing actors. (If we did index them, how would we do it, because the "child index" is the obvious choice, but it can changes as you reorder actors or create new ones?)
Or else we need some kind of argument pattern matching, where you could do different things based on specific OSC argument values. Maybe something like this (notation to be defined -- something to indicate that it only matches when the first argument is 36):
/midi/noteon (note:36) velocity
/play omi
/fade omi $velocity 0.5
There was a problem getting MIDI input from software, which Godot ignored. In Linux, a virtual midi port has to be created with:
sudo modprobe snd_virmidi
Godot should automatically listen to these ports, which can be connected with aconnect [othersoftoutnumber:port] [virtualmidiinnumber:port]
(e.g. aconnect 129:7 24:0
) or any jack connection GUI.
Great, so this resolves your issues with MIDI on Linux?
yep!
This has been implemented in https://github.com/loopier/animatron/commit/f9d22eeca9535331d8bea39b7e683f2fa15727af.
There are different syntaxes for noteon/noteoff
events and cc
.
/midi target noteon min max
/midi target noteoff min max
/midi target cc ccnum min max
The reason behind this is that, normaly, I would map note
numbers to /frame
. That's the case I came up with first, and seemed reasonable to follow this to make it intuitive.
But that leaves velocity
unused for noteon/noteoff
. A more coherent way might be to implement noteon/noteoff
with
/midi target noteon notenum min max
/midi target noteoff notenum min max
using *
wildcard to map notenum
to min max
.
Then a /def
could be created for a shorter version without having to specify the *
wildcard if what needs to be mapped is the notenum
.
I don't understand yet how this works, do you have an example file showing the OSC messages you actually send for MIDI control, and what they would do? This is a bit different from mapping OSC messages, no? Because those don't have a target
, they just have OSC messages arguments that are available to access in the definition (with the $
prefix).
It works like the /sound
implementation.
For noteon/noteoff
events, the same command is sent for all notes.
The argument passed to these commands is the note number
, discarding the note velocity
.
The syntax for noteon/noteoff
events is:
/midi actor:s midimsg:s cmd:s rangemin:f rangemax:f
For example:
m = MIDIOut(0);
x.("/midi", "anactor", "noteon", "/scale", 0.3, 1.5);
m.noteOn(0, 60, 48);
this sends /scale anactor 0.87
. The /scale
value depends on the note num
received:
60
(the note) is mapped to 0.87
-- 60.linlin(0,127, 0.3, 1.5)
. 48
(the velocity) is unused.For cc
messages we need an extra argument to specify the cc num
, using the cc value
as the command parameter. So cc
events with different numbers send different commands.
The syntax is:
/midi actor:s midimsg:s ccnum:i cmd:s rangemin:f rangemax:f
For example:
m = MIDIOut(0);
x.("/midi", "anactor", "cc", 64, "/scale", 0.3, 1.5);
m.control(0, 60, 48);
In this case the command sent by the MIDI event would be /scale anactor 0.75
. The /scale
command is sent only on events where the cc num
is 60
.
60
(the CC number) sends a /scale
command. Different numbers can send different commands48
(the CC value) is mapped to 0.75
The difference between noteon/noteoff
events and cc
events is:
noteon/noteoff
events with differnt nums send the same command but with different values.cc
events with different nums send different commands.I don't like the inconsistency of different syntax for different MIDI events. I was suggesting to use the extended cc
syntax also in noteon/noteoff
events:
/midi actor:s midimsg:s midinum:i cmd:s rangemin:f rangemax:f
This would alter the behaviour of ALL-notes -> ONE-cmd, which I think is very useful (for example on mapping note num
to frame num
number). A solution to this case would be to use a *
whildcard:
x.("/midi", "anactor", "noteon", "*", "/frame", 200, 327);
This would map each note number to the specified range leaving velocity
unused again, like it currently works.
The def
was just to create a shorter version without the *
:
/def /midi/noteon actor min max
/midi $actor noteon * $min $max
we can forget about this.
Not sure if any of this makes sense. Maybe we could just forget about the noteon/noteoff
peculiarities using the long cc
version for everything and see how it goes.
There's an upgrade of the MIDI implementation in https://github.com/loopier/animatron/commit/5614ba28baf71863bb264e15560cc394bf73798c.
I changed noteon/noteoff
mapping command to match cc
's, making it more consistent. So now, for MIDI notes, the velocity is what is mapped. Different notes can send different commands. For example:
/midi om noteon 60 /scale 0.1 4.0
/midi om noteon 72 /rotation 0 360
would map om
's scale
to the velocity of midi note 60
', and rotation
to the velocity
of note 72
.
To map note numbers, as we had before, now it can be done with the *
wildcard. For example:
/midi actor noteon * /frame 100 227
would map note numbers to frame discarding velocity
.
A def
could be used for a shorter and more intuitive syntax, for example:
/def /midi/notes/on actor min max
/midi $actor noteon * $min $max
I added a universal velocity
that works just like the universal note number, but reversed: it discards num
instead of velocity
, so the velocity of any key would send the same command. This is useful, for example, in the case where you have all notes mapped to frame
number, and you want the velocity to set the size
.
/midi actor noteon * /frame 100 227
/midi actor velocity * /size 0.1 4.0
The only problem with this is that the num
parameter is not used with the velocity
MIDI message, making it counter-intuitive. This could be solved with a def
that's used to map midi velocity. For example:
/def /midi/velocity actor min max
/midi $actor velocity * $min $max
Cool. But why is the actor an argument of the /midi
command? Do you define a MIDI map of these defs per actor? Otherwise, why not have it more generic, like we'd said earlier, where the MIDI command is like a def with arguments, then you can send whatever existing OSC commands your want, like /scale actor sx sy
? It's because you want the remapping functionality? Probably I just need to play with this at some point to understand more, I'm probably missing something.
I am indeed defining MIDI map per actor, following the /sound
implementation. Your suggestion makes a lot of sense. I need to think about it.
Actually, in your solution the actor
is also passed as an argument, just in a different order, isn't it?
/midi noteon * /frame actor x
. Is this how you'd write it?/midi actor noteon * /frame x
Yours seems to make more sense and to be more consistent with the rest of the syntax.
Yes, because the /frame actor n
command already exists in that form. (At least, that was my reasoning)
I changed the syntax to /midi msg num cmd actor min max
in https://github.com/loopier/animatron/commit/26cdf1c62f936683310579c060f25b5291aff0c7.
But I just realized that with the former syntax, we could use /midi!
for all selected actors. Now I'm not sure we can, as the actor
parameter moved away. I can't tell from the code.
But can't you use cmd!
to do that? For example:
/midi noteon 60 scale! 0.1 2
Ah, could be. We'll have to try it out. I actually have never used it yet.
I'm having second thoughts on the MIDI implementation. The current one feels wrong.
As for now, the interface is:
/midi [noteon|noteoff|cc|velocity] <channel> <number> <cmdAddr> <cmdActor> <min> <max>
.
which maps:
for /midi "noteon" ch num /cmd actor min max
:
MIDI.noteOn(ch, num, val)
to /cmd actor val.linlin(0,127,min,max)
for /midi "noteon" ch "*" /cmd actor min max
:
adds one of these for every note number: MIDI.noteOn(ch, num, val)
to /cmd actor val.linlin(0,127,min,max)
for /midi "noteoff" ch num /cmd actor min max
:
MIDI.noteOff(ch, num, val)
to /cmd actor val.linlin(0,127,min,max)
for /midi "velocity" ch num /cmd actor min max
:
MIDI.noteOn(ch, num, val)
to /cmd actor num.linlin(0,127,min,max)
(not sure if this is what it does, but I think it is)
for /midi "cc" ch num /cmd actor min max
:
MIDI.control(ch, cc, val)
to /cmd actor val.linlin(0,127,min,max)
I'm thinking that a better implementation would be:
/midi [noteon|noteoff|cc|velocity] <channel> <cmdAddr> <cmdActor> <min> <max>
which maps:
for /midi "noteon" /cmd actor min max
:
MIDI.noteOn(ch, num, val)
to /cmd actor num.linlin(0,127,min,max)
for /midi "noteoff" /cmd actor min max
:
MIDI.noteOff(ch, num, val)
to /cmd actor num.linlin(0,127,min,max)
for /midi "velocity" /cmd actor min max
:
MIDI.noteOn(ch, num, val)
to /cmd actor val.linlin(0,127,min,max)
for /midi "cc" /cmd actor min max
:
MIDI.control(ch, cc, val)
to /cmd actor val.linlin(0,127,min,max)
with this implementation we would drop the *
version, which simplifies things, but I'm not sure if it makes sense or not.
A disadvantage is that we wouldn't be able to map different commands to different notes.
What do you think?
One comment, with the new form you lose the ability to get one piece of information, e.g. for "noteon" you now map the note number to the Actor command param but lose the velocity information. A simple solution would be to use different commands for the other (missing) case. Like maybe using "noteon" vs "noteon:60" (or some completely different string like "noteon_single" that uses the old arguments). But this makes the logic and parsing (and documentation) more complex... If the new form you propose is what you need at the moment (and if you don't really see a need for the old per-note way), then maybe just go ahead and change it to what you need now(?).
Yes, I was aware of that, thanks, that's what I meant by the "disadvantage". But you're right. Maybe we should just try and see how it goes.
I agree that alternative messages mean more complex parsing, which is what I was trying to avoid
I finally left it as it was. I fixed the implementation in https://github.com/loopier/animatron/commit/fc217bd4c2f8cd9f22be4ea9ce6b20f66ca3e4ff .
For events assigned to any MIDI number (note or cc) with *
, it iterates from 0..127
, adding entries for each note|cc using the MIDI number also as MIDI value. So:
/midi noteon 0 * /frame target min max
will iterate through a range from 0..127
, with something like this:
for i in range(128):
var value = noteNum.linlin(0,127, min, max)
midiCmds[noteOn/ch0/i] = {addr:/frame, actor:target, min:value, max:value}
(just pseudo-code to get the idea)
This way we get to keep the velocity per-note mapping.
My idea with MIDI input is to make it mappable just like OSC. Ideally with a similar syntax. Should it be something like
?