loopier / animatron-godot3

Yet another implementation of Animatron, but in Godot
GNU General Public License v3.0
18 stars 1 forks source link

MIDI input #18

Open loopier opened 3 years ago

loopier commented 3 years ago

My idea with MIDI input is to make it mappable just like OSC. Ideally with a similar syntax. Should it be something like

/midi/noteon note velocity
    /frame! $note
    /alpha! $velocity

?

loopier commented 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.

totalgee commented 3 years ago

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?)

totalgee commented 3 years ago

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
loopier commented 2 years ago

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.

totalgee commented 2 years ago

Great, so this resolves your issues with MIDI on Linux?

loopier commented 2 years ago

yep!

loopier commented 2 years ago

This has been implemented in https://github.com/loopier/animatron/commit/f9d22eeca9535331d8bea39b7e683f2fa15727af.

There are different syntaxes for noteon/noteoffevents 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.

totalgee commented 2 years ago

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).

loopier commented 2 years ago

It works like the /sound implementation.

noteon/noteoff

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:

cc

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.

Conclusion

The difference between noteon/noteoff events and cc events is:

Afterthought

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.

loopier commented 2 years ago

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
totalgee commented 2 years ago

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.

loopier commented 2 years ago

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.

loopier commented 2 years ago

Actually, in your solution the actor is also passed as an argument, just in a different order, isn't it?

Yours seems to make more sense and to be more consistent with the rest of the syntax.

totalgee commented 2 years ago

Yes, because the /frame actor n command already exists in that form. (At least, that was my reasoning)

loopier commented 2 years ago

I changed the syntax to /midi msg num cmd actor min maxin 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.

totalgee commented 2 years ago

But can't you use cmd! to do that? For example:

/midi noteon 60 scale! 0.1 2

loopier commented 2 years ago

Ah, could be. We'll have to try it out. I actually have never used it yet.

loopier commented 1 year ago

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:

I'm thinking that a better implementation would be:

/midi [noteon|noteoff|cc|velocity] <channel> <cmdAddr> <cmdActor> <min> <max>

which maps:

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?

totalgee commented 1 year ago

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(?).

loopier commented 1 year ago

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

loopier commented 1 year ago

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 maxwill 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.