JacquesLucke / animation_nodes

Node based visual scripting system designed for motion graphics in Blender.
Other
2.29k stars 342 forks source link

Slight issue with Code MIDI #811

Closed Clockmender closed 6 years ago

Clockmender commented 7 years ago

Work is now progressing well with your script from a previous thread:

screen shot 2017-10-23 at 14 45 20

But...... If I change the values in the "addKeyFrame" sections at the bottom and re-run the "Bake Midi" button the old values still seem to persist and the new one s are added. This line of code in your template:

self.notes.clear()

Don't seem to work, could you elaborate on what this should do please?

If I delete the node and then add it back it runs properly.

JacquesLucke commented 7 years ago

good point, this is because the keyframes are stored separately from the actual value in Blender. Will try to fix it later today :)

Clockmender commented 7 years ago

Thank you - Also I am struggling to fix this part:

def createNote(name):
            dataPath = "nodes[\"{}\"].notes[{}].value".format(self.name, len(self.notes))
            item = self.notes.add()
            item.noteName = name

            def insertKeyframe(value, frame):
                item.value = value
                self.id_data.keyframe_insert(dataPath, frame = frame)

            return insertKeyframe

So it doesn't add a new item if it already exists. The reason being that the events in the MIDI file are not sorted by note, only time. So I need to create the record item = self.notes.add() if it doesn't exist and add values to it if it does. I am reading frantically, but no solution yet!

I know I need to test if it exists, then assign item to it - but my known methods don't work.....

Clockmender commented 7 years ago

Forget the last bit, I solved the issue, but realised I have to add all the events at one time, now working on that!

JacquesLucke commented 7 years ago

I've inserted some fcurve-removal-code: https://gist.github.com/JacquesLucke/5ba7ca942fc7f6c7664e76f0fdec3847

Clockmender commented 7 years ago

Thanks :) I have built it into my node and it works well. I am nearly at a stage to test on a single channel MIDI file.

Clockmender commented 7 years ago

UPDATE: I am getting there!!!! :-)

screen shot 2017-10-23 at 21 34 25

57 Notes in the File, 57 F-Curves stored, and they are cleared when you re-run the script - Yippee!

The F-Curves are not the right shape yet, I need to add my "Easing" setting that I used before.

Thanks for the help!

Clockmender commented 7 years ago

Good Morning!

I have a little problem:

In my code I generate an Integer List:

group = bpy.data.groups.get(self.keys_grp)
        assert group is not None
        keys_objs = group.objects
        control_list.pop(0)
        ke_names = []
        index = []
        for obj in keys_objs:
            note = obj.name.split("_")[0]
            ke_names.append(note)

        for note in control_list:
            indx = ke_names.index(note)
            index.append(indx)

This index is good and works. I need to add this as an output socket on the Node.

So I tried: self.newOutput("Integer List", "Index", "index") - this broke the node (Red Box) I also tried adding return LongList.fromValues(index) at the end of the code above - same thing - broke the node ("LongList not defined" error message) I also tried this in the Execute function return notes, DoubleList.fromValues(values), index - broke the node.

I tried other things from other nodes - always broke the node. I looked at what you did to get the output from the notes variable, I am not able to re-create this.

Can you give me a little help please? Just an idea of what is wrong.

This code works in a Script Sub-programme, with an integer List output Socket:

Objects = list(getattr(Controls, "objects", []))
keysL = list(getattr(Keys, "objects", []))
keys_out = Keys
f_curves = []
index = []
chanl = Objects[0].name.split('_')[0] + "_"
ke_names = [chanl + obj.name.split("_")[0] for obj in keysL]

for ob in Objects:
    ob_name = ob.name
    if ob_name in ke_names:
        indx = ke_names.index(ob_name)
        index.append(indx)
    f_curves.append(list(ob.animation_data.action.fcurves)[0])

index = LongList.fromValues(index)

I am confused!

JacquesLucke commented 7 years ago

please show me all the code, otherwise I can only guess.

Clockmender commented 7 years ago

Sorry for that, it's here https://github.com/Clockmender/animation_nodes/blob/master/midi_input.py

I just need to understand how to get the index list onto a socket, this will be useful to me for other projects also.

Clockmender commented 7 years ago

My test MIDI file is here, if you need it:

https://github.com/Clockmender/animation_nodes/blob/master/entertainer-new.csv

Clockmender commented 7 years ago

There was an error in the code (so many tries and sometimes I res up other things....

here is the revised bit:

# Get Channel Name and process events for each note
        channelName = control_list[0]
        # Loop through Notes
        for rec in range(1, numb_1):  # len(control_list)
            f_n = control_list[rec]
JacquesLucke commented 7 years ago

Why do you need this index as output in the first place? Only values that are returned in the execute function will be used as node output. You could store the index in the MidiNoteData PropertyGroup, however I think this should not be necessary. The node already has all the outputs that are needed to deal with midi data. A separate node should do the note-to-object mapping. That other node just gets the note names and values as input (+an object list maybe)

Clockmender commented 7 years ago

Very good point and I have a Script Sub-programme that maps the controls in my first system to the note meshes, using object groups and it generates an index. This nodes executes at every frame, I haven't found a way to stop that yet and am still working on it. From a programming/systems analysis point of view this is not good practice, since the index never changes in the animation, yet I have to recalculate it every frame.

I could take the output from the Note socket, and then rename all my meshes so they match this - a huge job every time I change midi channels, so that is why I need the index. The keys in my project, of which there are 88 and they are not in any order, deliberately so I could cater for users who don't put their keys in note order, they are named, for example a3_key - my first system simply used the control name - t2_a3 (track 2 note a3) for example and matched them up - simple and it does not need the keys to be in order, or for there to be the exact number of keys to match the MIDI file, just the same or more of them. If I duplicated the keys, the next set would be a3_key.001 and would be in a new group - the system still worked because it only looked at the first part - a3_key so this could be a3_key_Piano and the system still works.

So it is down to me not wanting to run the index calculation every time the frame changes, just once at the start - the Node timings drop from something like 12ms to less than 1 if I don't regenerate the index.

I can easily write a script to take the output from the new Node and map that to the keys meshes, but again it would have to run every frame, I cannot see a way around this. If I put a step in so it only runs on frame 1, the index disappears on frame 2 onwards, I have tried every trick I can think of. :-)

Hope this makes sense, I do tend to over explain things sometimes.....

I now have the system working, except the wrong keys are played!!

screen shot 2017-10-24 at 16 32 18

Note the 0.5ms time for the node tree.

If I could make a node that did the mapping only once, I would be happy, but all the data I need is already in the midi_input node.

Clockmender commented 7 years ago

I had an idea but do NOT want to risk trying this without asking you first.

Could I alter the MidiNoteData to have three values Note, Index, Value and write the index into the index field, or 0 if the Keys group is not set - then I could simply take this value to link the meshes. So I would add a new socket with this as an output and add bits to the execute function to feed the socket.

Or is this a bad idea?

JacquesLucke commented 7 years ago

Read the last message I sent here, I suggested exactly that.

However, I have another idea now, which I think is better (because it gives you more possibilities).

You could change the behavior of the Midi Input node so that it does not output all notes and values but only the ones you need.

midi test

This can be archieved in a relatively efficient way by changing the execute function to:

def execute(self, notes):
    valueByNote = {item.name : item.value for item in self.notes}
    values = [valueByNote.get(note, -1) for note in notes]
    return DoubleList.fromValues(values)

The expression node you see here just gives you all the names from the object list. If you don't understand the syntax here is something you can google: python list comprehension and python dict comprehension. The [:-len("_key")] just removes the last for letters of the name. You could also write [:-4] instead, or make it a input socket.

The output of the Midi Input node has the correct order now.

Clockmender commented 7 years ago

"Read the last message I sent here, I suggested exactly that." oops - sorry, I wanted to know if it is safe to alter the class, or whether it should be renamed if altered. I will study list comprehension and see if I can work that elsewhere.

Thanks!

Clockmender commented 7 years ago

So as I have to sleep tonight..... I have amended my code to output the index value in the MIdiNoteData class and the animation now plays the correct notes. I actually don't need the noteNames bit, other than to check the correct notes are used.

I will study the new info you kindly provided and change the Node to use this when I have some more free time - next few days as I need to understand comprehensions first.

But there is a problem - the animation is not as smooth or precise as with my previous method, I have refined the system and got the Node Tree time down to around 2ms with still calculating the Index every frame using a Script Sub-programme on the Mk1 version.

I guess this lack of precise movement has to do with how the data is being stored/accessed and the match to exact keyframes - I just don't know what the difference is. The Mk2 version works very well for slower pieces, but throw something fast at it and the meshes behave oddly, but do not on the Mk1 version. I must investigate further.

Thanks again for your help - I like the method to re-sort the list BTW.

Clockmender commented 7 years ago

You might like to know I have some progress, whilst I think about, and work on, your previous post:

screen shot 2017-10-25 at 14 06 24

It would be nice if I could get the node to populate the top text boxes when you select the groups at the bottom, is this possible? Seems to break the node if I try to set it in the Execute function.

Code is here: https://github.com/Clockmender/animation_nodes/blob/master/midi_indices.py

enzyme69 commented 6 years ago

@Clockmender I tried the code, but running it does not work. Could not find AnimationNodes module.

Clockmender commented 6 years ago

^^ Which code? I have revised all my nodes and will up-load them later today. I found some issues in the animation for this one that are now resolved. Also I have added a new variable so you can get "Square" curves - note is either on or off and "Smooth" curves where the note varies between the on and off via smooth transition. The midiindices node will not work on its own - its and Evaluation Node for the "Controls" Node. I will add a couple of blend files and sounds (maybe a size issue?) to show the setup. Did you load the Animation Nodes Add-on - silly question I know but...... The nodes must reside in a separate folder in the AN "node" directory and that must also contain a blank _init\.py file.

Cheers, Clock.

enzyme69 commented 6 years ago

This code 👍 Code is here: https://github.com/Clockmender/animation_nodes/blob/master/midi_indices.py

Still waiting for your example via email. My Python is far under your Python level....

Clockmender commented 6 years ago

Testing is going well!

Jacques, I am having issues with the code to sort the lists, some reasons:

Musicians don't sort notes alphabetically - they use Doh, Ray, Me, etc where Doh is the first note of the Key, so could be C or whatever. C major key is C, D, E, F, G, A, B - but this does not apply to E Flat for example.

Often I have more keys than notes played and they are in any random order in the Blend File - so the index sort I used, from your code doesn't work. So rather than try to sort out the list with lots of code, I am just using the Index system I have built into the node - just a few lines of easily maintained code.

Latest project:

first-time-3

Shape keys, Scaling, Rotating, Material changes, Armature and IK bone chains all feature in here. All driven by the MIDI "Bake" node built on your framework, thanks again for that! I will put the video on YouTube once I have all the channels complete.

Cheers, Clock.

Clockmender commented 6 years ago

Latest test video using the "Bake" Node:

https://www.youtube.com/watch?v=QM0b1PHrigc

Closing this now as it is resolved. Latest code on my GitHub under "Version 1.1"

Cheers, Clock.

PS. Thanks for all the help Jacques!