Closed Clockmender closed 6 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 :)
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.....
Forget the last bit, I solved the issue, but realised I have to add all the events at one time, now working on that!
I've inserted some fcurve-removal-code: https://gist.github.com/JacquesLucke/5ba7ca942fc7f6c7664e76f0fdec3847
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.
UPDATE: I am getting there!!!! :-)
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!
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!
please show me all the code, otherwise I can only guess.
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.
My test MIDI file is here, if you need it:
https://github.com/Clockmender/animation_nodes/blob/master/entertainer-new.csv
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]
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)
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!!
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.
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?
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.
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.
"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!
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.
You might like to know I have some progress, whilst I think about, and work on, your previous post:
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
@Clockmender I tried the code, but running it does not work. Could not find AnimationNodes module.
^^ 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.
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....
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:
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.
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!
Work is now progressing well with your script from a previous thread:
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.