Copy link to clipboard
Copied
Hi, I started a thread here about the project I'm working on: https://forums.adobe.com/thread/2398912
As Dan Ebberts suggested, I should create a script out of what I was trying to achieve. My project structure looks like this.
Main Comp (which is the active comp):
MIDI Control comp:
Here's the expression I was using on each individual checkbox control for activeLeftHand and activeRightHand layers if it's of any use. Also modified from something Dan Ebberts helped me out with a while back:
midiNote = comp('Main comp').layer( 'midi' ).effect( 'ch_0_pitch' )( 'Slider' );
midiNum = thisProperty.propertyGroup(1).name;
n = 0;
for (i = 1; i <= midiNote.numKeys; i++){
if (midiNote.key(i).time > time) break;
if (midiNote.key(i).value == midiNum) n++;
}
if (n%2) 1 else 0
I've got this far but need to complete the section for lines 26-27:
var proj = app.project; // The Application Object is at the top level followed by the Project Object.
var comp = proj.activeItem; // Set the comp variable to the active composition.
var midiLayer = comp.layer("midi"); // Set the 'midi' layer of the active composition to a variable.
var leftHandKeys = midiLayer.effect("ch_0_pitch")("Slider"); // Get channel_0_pitch values - used for left hand notes.
var rightHandKeys = midiLayer.effect("ch_1_pitch")("Slider"); // Get channel_1_pitch values - used for right hand notes.
var midiControlComp = comp.layer("MIDI Control").source; // Set the 'MIDI Control' composition to a variable.
var leftHandController = midiControlComp.layer("activeLeftHand"); // Get the checkbox control layer for the left hand.
var rightHandController = midiControlComp.layer("activeRightHand"); // Get the checkbox control layer for the right hand.
// TODO: At the end use the following to run the function.
// midiDataToControlComp( leftHandKeys, leftHandController );
// midiDataToControlComp( rightHandKeys, rightHandController );
function midiDataToControlComp(channelPitchSlider, checkBoxControlLayerName) {
// If there are keyframes available, continue.
if (channelPitchSlider.numKeys > 0) {
for (k = 1; k <= channelPitchSlider.numKeys; k++) {
// Break loop if the keyframe's time more than the current time.
if (channelPitchSlider.key(i).time > app.project.activeItem.time) break;
// TODO: Get keyframe(s) value and assign it to the corresponding checkbox(es) on the checkBoxControlLayerName layer.
// IMPORTANT NOTE: There may be MIDI chord data at a given time which means there will be multiple keyframes stacked. Get all values out.
}
// If there are no keyframes availble, display an error alert.
} else {
alert("There are no keyframes in " + channelPitchSlider + " within the midi layer.");
} // End if statement.
} // End midiDataToControlComp() function.
Not sure if line 24 is also doing its job too.
If anyone can help me out here that would be brilliant.
Thanks!
Might have figured out but not in a clean way.
...app.beginUndoGroup("MIDI Keyframes to Control Layers"); // Create undo group for ease undoing should something go wrong.
// Swap 'Left' and 'Right' below if colours come out opposite for both hands.
midiDataToControlComp( "1", "Left" );
midiDataToControlComp( "0", "Right" );
app.endUndoGroup(); // End undo group.
function midiDataToControlComp( channelNumber, checkBoxControlLayerHand ) { // Usage example: midiDataToControlComp( "[0/1]", "[
Copy link to clipboard
Copied
There are a number of ways to approach it, but one way that should only require the pitch channel would be to set up an array to count keyframes for each key (so you can use the odd/even test to see if your checkbox should be on or off). Initialize the array to all zeros, and then as you loop through the pitch keyframes, for each keyframe, increment the appropriate array value for the key, and create a checkbox keyframe at the same time, setting the value based on whether the note count is currently odd or even for that key.
I'm not sure what your line 24 is about, but I think you'd want to process all the pitch keyframes.
Dan
Copy link to clipboard
Copied
I don't really know how to start the looping process especially when it involves arrays and creating objects.
I can see how it worked within the expression but when it comes to writing this all in the script, I feel lost.
Would you be able to help me out here? I'd really appreciate your time.
So far I've tried this but don't know if I'm doing this right.
var keyframesArray, totalKeys, prop, curKeyIndex, curKeyValue, inin, outin;
keyframesArray = new Array, // Create an empty array to store the keyframe data.
totalKeys = channelPitchSlider.numKeys, // Total number of keyframes in channel pitch.
if ( totalKeys > 0 ) { // If there are keyframes available, continue.
for ( k = 1; k <= channelPitchSlider.numKeys; k++ ) { // Start looping through the keyframes
curKeyIndex = k;
curKeyTime = channelPitchSlider.keyTime(i);
curKeyValue = channelPitchSlider.keyValue(i);
inin = channelPitchSlider.keyInInterpolationType(curKeyIndex);
outin = channelPitchSlider.keyOutInterpolation(curKeyIndex);
keyframesArray[keyframesArray.length] = {
'curKeyTime':curKeyTime,
'curKeyIndex':curKeyIndex,
'curKeyValue':curKeyValue
};
} // End looping through the keyframes
}
Copy link to clipboard
Copied
Not too sure whether i fully understood, but the follwing might work :
var initCheckboxValue = 0;
var checkboxControls = {};
var counts = {};
var midiNum;
for (midiNum=1; midiNum<=88; midiNum++){
checkboxControls[midiNum] = // reference to the appropriate checkbox control
checkboxControls[midiNum].property(1).setValueAtTime(0, initCheckboxValue);
counts[midiNum] = 0;
};
for (i = 1; i <= midiNoteSlider.numKeys; i++){
midiNum = midiNoteSlider.keyValue(i);
++counts[midinum];
checkboxControls[midiNum].property(1).setValueAtTime(midiNoteSlider.keyTime(i), counts[midiNum]%2);
};
Xavier
Edit: Before the line ++counts[midiNum]; there should be a check that midiNum is indeed an integer in the range 1-88, else this will create an error
Copy link to clipboard
Copied
This is pretty rough (and not tested at all), and I'm making some assumptions based on screen caps, but it might be helpful:
function getComp(theName) {
for (var i = 1; i <= app.project.numItems; i++) {
if ((app.project.item(i).name == theName) && (app.project.item(i) instanceof CompItem)) {
return app.project.item(i);
}
}
return null;
}
var midiControlComp = getComp("MIDI Control");
var leftHandLayer = midiControlComp.layer("activeLeftHand");
var totalNotes = 88;
var startNote = 21;
var notes = [];
for (var i = 0; i < totalNotes; i ++) notes = 0; // initialize array
var curKeyTime, curKeyValue, curNoteIdx, curCB;
for (var i = 1; i <= channelPitchSlider.numKeys; i++){
curKeyTime = channelPitchSlider.keyTime(i);
curKeyValue = channelPitchSlider.keyValue(i);
curNoteIdx = curKeyValue - startNote;
if (curNoteIdx >= 0 && curNoteIdx < totalNotes){
notes[curNoteIdx] += 1;
curCB = leftHandLayer.property("Effects").property(curKeyValue.toString()).property("Checkbox");
curCB.setValueAtTime(curKeyTime,(notes[curNoteIdx]%2);
}
}
Dan
Copy link to clipboard
Copied
Amazing as always, Dan! Your code worked brilliantly for setting up the keyframes as I needed them.
However, it is setting the start of every checkbox to 'On' / value of 1 (at 0 time), the rest of the keyframing works perfectly.
I've added line 5 to your code which sets a starting keyframe to 0:
if (curNoteIdx >= 0 && curNoteIdx < totalNotes){
notes[curNoteIdx] += 1;
curCB = controlLayer.property("Effects").property(curKeyValue.toString()).property("Checkbox");
curCB.setValueAtTime(curKeyTime,(notes[curNoteIdx]%2));
curCB.setValueAtTime(0,(0));
}
I'm guessing it is creating multiple keyframes on top of each other with a value of 0 if the midi key is being pressed since it is inside the for loop.
Also, one more thing. How can I mass delete all existing keyframes on the control layers before running the function that adds the keyframes?
Copy link to clipboard
Copied
>How can I mass delete all existing keyframes on the control layers?
Something like this should work:
curCB = controlLayer.property("Effects").property(curKeyValue.toString()).property("Checkbox");
while (curCB.numKeys > 0){
curCB.removeKey(curCB.numKeys);
}
Dan
Copy link to clipboard
Copied
Doesn't seem to work, the next time I run the same script, the checkboxes aren't set to On anymore but I can see the keyframes set.
Do I place the while loop after line 25 of your code (i.e. the curCB variable)?
Copy link to clipboard
Copied
If you put it there, you need to make sure it only runs when i == 1. It might be cleaner though, to put it in a separate loop before you get to the loop that sets the keyframes.
Dan
Copy link to clipboard
Copied
Thanks for your help Dan, much appreciated!
Copy link to clipboard
Copied
How easy would it be to set a keyframe with 'Off' state, and set it back by 2 frames, if the same midi key is being played twice or more in a row?
For example in Synthesia it does something like this (if you look at 7 seconds in):
Dancing Line - The Sailor's Tale // Custom Arrangement (Synthesia Tutorial + MIDI + PDF) - YouTube
Want to be able to show that the piano key has been lifted and pressed again.
Currently, the keyframe structure doesn't show lifts if the key is being pressed in quick succession.
Copy link to clipboard
Copied
You could do that, but it seems like it might be better to process the duration channel. I'm not familiar with the script you're using to capture the channel data, but I'm wondering if the duration channel has the info you need for the timing of the checkbox "off" keyframes.
Dan
Copy link to clipboard
Copied
There is data available within the duration channel. I'll try tackling it myself but I've just sent you a PM with the project file link if it's easier for you to modify the script you've written. Hope you don't mind!
Copy link to clipboard
Copied
Here are the keyframes for channel 1's pitch, velocity and duration if it's easier to understand.
You can see that the small fraction of frame differences are the lifts and presses.
ch_1_pitch (values are from 21-108😞
Adobe After Effects 8.0 Keyframe Data
Units Per Second 29.97
Source Width 100
Source Height 100
Source Pixel Aspect Ratio 1
Comp Pixel Aspect Ratio 1
Effects Slider Control #4 Slider #2
Frame
0.015 38
0.03 50
8.99125 38
9.00625 50
35.9638 45
35.9788 52
53.9463 45
53.9613 52
53.9763 38
53.9913 50
62.9375 38
62.9525 50
89.91 48
89.925 52
89.94 57
107.893 48
107.907 52
107.923 57
107.938 46
107.953 50
107.968 53
116.883 46
116.898 50
116.913 53
143.856 48
143.871 52
143.886 55
…
etc
…
End of Keyframe Data
ch_1_vel (values are from 0-127😞
Adobe After Effects 8.0 Keyframe Data
Units Per Second 29.97
Source Width 100
Source Height 100
Source Pixel Aspect Ratio 1
Comp Pixel Aspect Ratio 1
Effects Slider Control #5 Slider #2
Frame
0 0
0.015 89
0.03 89
8.99125 0
9.00625 0
35.9638 87
35.9788 87
53.9463 0
53.9613 0
53.9763 95
53.9913 95
62.9375 0
62.9525 0
89.91 87
89.925 87
89.94 87
107.893 0
107.907 0
107.923 0
107.938 92
107.953 92
107.968 92
116.883 0
116.898 0
116.913 0
143.856 87
143.871 87
143.886 87
...
etc
...
End of Keyframe Data
ch_0_dur (values are based on how long a note is held):
Adobe After Effects 8.0 Keyframe Data
Units Per Second 29.97
Source Width 100
Source Height 100
Source Pixel Aspect Ratio 1
Comp Pixel Aspect Ratio 1
Effects Slider Control #6 Slider #2
Frame
0.015 0.3
0.03 0.3
35.9638 0.6
35.9788 0.6
53.9763 0.3
53.9913 0.3
89.91 0.6
89.925 0.6
89.94 0.6
107.938 0.3
107.953 0.3
107.968 0.3
143.856 0.6
143.871 0.6
143.886 0.6
…
etc
…
End of Keyframe Data
Going to give this a crack but feel free to assist if you guys can help out!
Thanks.
Copy link to clipboard
Copied
Tidied up the script a fair bit and added a bunch of comments to it.
I've also passed through the ch_X_vel and ch_X_dur sliders so trying to work out setting the keyframes for the hold / release of the same key.
Here's the script for future reference.
app.beginUndoGroup("MIDI Keyframes to Control Layers");
// Swap 'Left' and 'Right' below if colours come out opposite for both hands.
midiDataToControlComp( 1, "Left" );
midiDataToControlComp( 0, "Right" );
app.endUndoGroup();
function midiDataToControlComp( channelNumber, checkBoxControlLayerHand ) {
var proj = app.project, // The Application Object is at the top level followed by the Project Object.
comp = proj.activeItem, // Set the comp variable to the active composition.
midiLayer = comp.layer( "midi" ), // Set the 'midi' layer of the active composition to a variable.
midiPitch = midiLayer.effect( "ch_"+channelNumber+"_pitch" )( "Slider" ), // Get channel pitch.
midiVelocity = midiLayer.effect( "ch_"+channelNumber+"_vel" )( "Slider" ), // Get channel velocity.
midiDuration = midiLayer.effect( "ch_"+channelNumber+"_dur" )( "Slider" ), // Get channel duration.
midiControlComp = comp.layer( "MIDI Control" ).source, // Set the 'MIDI Control' composition to a variable.
handController = midiControlComp.layer( "active"+checkBoxControlLayerHand+"Hand" ), // Get the checkbox control layer for the hand.
totalNotes = 88, // Total number of keys on the piano.
startNote = 21, // Starting MIDI note.
notes = [];
for (var i = 0; i < totalNotes; i ++) notes = 0; // Initialize the notes array.
var curKeyTime, curKeyValue, curNoteIdx, curCB; // Set up couple of variable to use for 'Main Comp' > 'midi' keyframes and 'Midi Control' > 'active[Left/Right]Hand' checkboxes.
for (var i = 1; i <= midiPitch.numKeys; i++){ // Loop through the keyframes.
curKeyTime = midiPitch.keyTime(i); // Get current keyframe's time.
curKeyValue = midiPitch.keyValue(i); // Get current keyframe's value.
curNoteIdx = curKeyValue - startNote; // Get current keyframe's note index.
if (curNoteIdx >= 0 && curNoteIdx < totalNotes){
notes[curNoteIdx] += 1;
curCB = handController.property("Effects").property(curKeyValue.toString()).property("Checkbox"); // Get current key value's corresponding checkbox in 'Midi Control' > 'active[Left/Right]Hand'.
curCB.setValueAtTime(curKeyTime,(notes[curNoteIdx]%2)); // Set a keyframe at current time to 0 or 1 (checking for odd/even here).
curCB.setValueAtTime(0,(0)); // Set a keyframe at 0 time for the checkbox to 0 so the key starts at an 'off' state.
}
} // End looping through the keyframes.
} // End midiDataToControlComp().
Copy link to clipboard
Copied
Still struggling with utilising all the data that's been provided in the midi layer.
Currently, the custom script is doing a great job, but I'm just not getting the key releases when the same key is being pressed again (such as the same chord being played 2 times in a row).
Here's the screenshot from the project:
I've copied keyframe data for the highlighted sliders above, pasted it into a spreadsheet and aligned each data row with the corresponding frame to visualise the data better.
I can see that is has an 'on' state and 'off' state for each midi note, looking at the pitch and velocity values.
For example:
- Cell B5 is midi number 38. Since the velocity is not 0 (in this case it's 89), this is the on / pressed state.
- Cell B7 is midi number 38 again. This time it is showing velocity of 0. It would mean the note is in the off / released state.
I don't understand how the duration channel is working with the value given, but the values only appear where there is an 'on' state for a midi number.
I want to somehow make use of all this data and make the keyframes a bit more accurate.
Anyone able to help here?
Thanks.
Copy link to clipboard
Copied
Might have figured out but not in a clean way.
app.beginUndoGroup("MIDI Keyframes to Control Layers"); // Create undo group for ease undoing should something go wrong.
// Swap 'Left' and 'Right' below if colours come out opposite for both hands.
midiDataToControlComp( "1", "Left" );
midiDataToControlComp( "0", "Right" );
app.endUndoGroup(); // End undo group.
function midiDataToControlComp( channelNumber, checkBoxControlLayerHand ) { // Usage example: midiDataToControlComp( "[0/1]", "[Right / Left]" );
var proj = app.project, // The Application Object is at the top level followed by the Project Object.
comp = proj.activeItem, // Set the comp variable to the active composition.
midiLayer = comp.layer( "midi" ), // Set the 'midi' layer of the active composition to a variable.
midiPitch = midiLayer.effect( "ch_"+channelNumber+"_pitch" )( "Slider" ), // Get channel pitch.
midiVelocity = midiLayer.effect( "ch_"+channelNumber+"_vel" )( "Slider" ), // Get channel velocity. Not making use of yet.
midiDuration = midiLayer.effect( "ch_"+channelNumber+"_dur" )( "Slider" ), // Get channel duration. Not making use of yet.
midiControlComp = comp.layer( "MIDI Control" ).source, // Set the 'MIDI Control' composition to a variable.
handController = midiControlComp.layer( "active"+checkBoxControlLayerHand+"Hand" ), // Get the checkbox control layer for the hand.
totalNotes = 88, // Total number of keys on the piano.
startNote = 21, // Starting MIDI note.
notes = []; // Create the notes array.
for (var i = 0; i < totalNotes; i ++) notes = 0; // Initialize the notes array.
var curKeyTime, curKeyValue, curNoteIdx, curCB, checkCB, iFirstKey; // Set up couple of variable to use for 'Main Comp' > 'midi' keyframes and 'Midi Control' > 'active[Left/Right]Hand' checkboxes.
for (var i = 1; i <= midiPitch.numKeys; i++){ // Loop through the keyframes.
curKeyTime = midiPitch.keyTime(i); // Get current keyframe's time.
curKeyValue = midiPitch.keyValue(i); // Get current keyframe's value.
curNoteIdx = curKeyValue - startNote; // Get current keyframe's note index.
if (curNoteIdx >= 0 && curNoteIdx < totalNotes){ //
notes[curNoteIdx] += 1; //
iFirstKey = 0; // Set up variable for later use on checking if there are keyframes.
checkCB = ( notes[curNoteIdx]%2 ) ? "On" : "Off"; // If current note index notes is odd, say it is 'on' - if it's even, say it is 'off'.
curCB = handController.property("Effects").property(curKeyValue.toString()).property("Checkbox"); // Get current key value's corresponding checkbox in 'Midi Control' > 'active[Left/Right]Hand'.
if ( checkCB == "On" ) { // If checkbox is 'On'...
curCB.setValueAtTime(curKeyTime,(1)); // Set a keyframe at current time to 1.
curCB.setValueAtTime((curKeyTime-0.05),(0)); // Set a keyframe at current time minus 0.05 to 0. Useful for when the same midi key is being pressed twice.
} else { //... Otherwise if checkbox is 'Off'
curCB.setValueAtTime(curKeyTime,(0)); // Set a keyframe at current time to 0.
} // End if statement for checkCB.
while ( iFirstKey < 1 ) { // If there is a first keyframe and while iFirstKey is less than 1...
curCB.setValueAtTime(0,(0)); // ...set a keyframe at 0 time for the checkbox to 0 so the key starts at an 'off' state.
iFirstKey++; // Increment iFirstKey by 1.
} // End while loop for iFirstKey.
} // End if statement for curNoteIdx.
} // End looping through the keyframes.
} // End midiDataToControlComp().
Seems to be doing a good job with the effect I'm looking to achieve.
Thanks for everyone's help in the replies, especially Dan Ebberts for getting me almost there!
Learnt a fair bit from this as it was my first ever script
Copy link to clipboard
Copied
Here's where I got to with it, pleased with the outcome! Moving on to creating particles