Proper use of variables in ScriptDAT???

Howdy folks, I’m realizing that I’ve spent far too much time trying to get this to work, so I’m asking for help.

What I need to happen: is simply to hit a button, start recording a table, creating a new row every frame that goes by, that includes the frame index since hitting the record button (ie starting at 1) and the numerical value of various midi knobs. So the only way I’ve known how to use variables is that ridiculous setup where you have a table with names and initial values, another table you can refer to in other dats to change the values, and an evaluate DAT to use when you want to refer to a value. So I tried just creating a table and had it change length depending on a frame value in the evaluate DAT, and tried to have each new row become the values I desired, but TD just pooped out and couldn’t handle it at all.

But then I discovered the scriptDAT, I added an LFO to one of the examples in the snippets and found that I could update the values and the number of rows in it with no problems. I’ve been trying to follow Matthew Ragan’s script example matthewragan.com/2017/03/19/scr … hdesigner/ However, I have yet to figure out exactly what it’s trying to run with the script I have. I need to setup the variable f when I hit record, set it to 0, clear the table and create a first row with just the headers for each column. Each frame, f+=1, and it needs to add another row with the value of f and the respective midi values.

Here’s what I have right now:
when I hit record:
if op(‘vars’)[8,1] == 0:
attr = {
“c”:{
“f” : 0,
“justifyv” : 1
}
}
parent().store( ‘set_up_attr’, attr )
op(‘table2’)[8,1] = 1
else:
op(‘table2’)[8,1] = 0
#the vars and table2 [8,1] is my record variable, if it’s on it turns off, if it’s off it turns on, which works properly
(I created an empty container named c just because in Matthew’s example he attributes his variables to containers for some reason.)

And then in the script callbacks associated with my final table:

pars = parent().fetch( ‘set_up_attr’ )

the record button triggers setup parameters

def setupParameters(scriptOp):
#scriptOp.appendParFloat(‘ValueA’, page=‘Custom’)
#scriptOp.appendParFloat(‘ValueB’, page=‘Custom’)
scriptOp.clear()
scriptOp.insertRow( [‘frame’, ‘B1’, ‘B2’, ‘B3’, ‘B4’, ‘B5’, ‘K1’] )
return

called whenever custom pulse parameter is pushed

def onPulse(par):
return

def cook(scriptOp):
if op(‘vars’)[8,1] == 1:
pars[ ‘c’ ][ ‘f’ ] +=1
scriptOp.insertRow( [ pars[ ‘c’ ][ ‘f’ ], op(‘switch2’)[‘B1’], op(‘switch2’)[‘B2’], op(‘switch2’)[‘B3’], op(‘switch2’)[‘B4’], op(‘switch2’)[‘B5’],op(‘switch2’)[‘K1’] ],pars[ ‘c’ ][ ‘f’ ] )

return

def cook(scriptOp):
if op(‘vars’)[8,1] == 1:
pars[ ‘c’ ][ ‘f’ ] +=1
scriptOp.appendRow( [ pars[ ‘c’ ][ ‘f’ ], op(‘switch2’)[‘B1’], op(‘switch2’)[‘B2’], op(‘switch2’)[‘B3’], op(‘switch2’)[‘B4’], op(‘switch2’)[‘B5’],op(‘switch2’)[‘K1’] ],f )

return


What it actually does:

Once record is on, when I hit the midi buttons that it should be recording, I think it’s somehow triggering the ‘def cook’ portion (but only when I hit midi buttons) because it adds another row with a frame number that’s one higher than the last (despite the actual number of frames that may have passed by) with the appropriate midi info. The weird part is that when it adds a new row, it turns all the previous rows blank except for the header. Even weirder, when I initially hit record, it’s like it’s triggering the cook either 2 or 3 times, so what I’m left with after I hit record is the header, one or two blank rows, and then what would be the appropriate row for that frame number (either 2 or 3). When I hit record again, it deletes all the rows except for the header. What I’ll need it to do is copy the final table into another table and save it in a folder with a time stamp.

I guess what I need to know is when exactly each of the sections in the script callbacks are run. (I’m also curious how you set up this so called custom pulse thing), how to get it to cook once every frame, and how to get it to add to the table each time without deleting the previous rows, thanks!

I don’t think you actually want a script DAT for this - at least in thinking about this challenge it seems more complicated than it’s worth to do it in the script DAT unless you really have to.

You can achieve I think what you’re after with CHOP Execute DATs. Here’s a simplified example where one DAT controls the recording process by appending a table while the value from a button is true.

Another CHOP execute resets the table and restores the header so you can start fresh.

Does this get you closer to what you’re looking for?
base_record_to_table_example.tox (1.62 KB)

on a sidenote, if you use the “code” button on this forum to markup your Python code in your post it becomes a lot more readable, including indents.

The man, the myth, the legend himself. Thanks man, that was a lot of help. I was able to get it to record to a table. Right now I’m using a whileOn in a chopexecuteDAT to write each new line in the table, which according to derivative.com, runs the script once every frame. Well that’s not exactly happening as it seems to be skipping over maybe 4 out of every 5 frames, so the table comes out way too short.

What I need to do is record midi responses in real time to the table, then play it back with realtime off in order to record a video, with the midi responses matching exactly with the music as I recorded them. I have the framework for this done but what it ends up recording is just a couple seconds of video, as it only counted one in every x amount of frames.

If someone could let me know what exactly is happening here that would be awesome. I imagine it has something to do with fps when it’s recording. (It’s at like 13 fps when it’s meant to be 60.) I imagine it would be recording only 13 frames in a second then? Could there be some way to extrapolate the table to fill the values it didn’t pick up? For example, if it was at 7 fps the table for that second would record like:

frame 1
–7 copies of frame 1
frame 2
–8 copies of frame 2
frame 3
–7 copies of frame 3
frame 4
–8 copies of frame 3
frame 5
–7 copies of frame 3
frame 6
–8 copies of frame 3
frame 7
–8 copies of frame 3

Absolutely no clue how to make that happen. Any ideas let me know!

AHHHHH. Hip, what you’re after makes a little more sense hearing the above.

You might consider using a Record CHOP or a Trail CHOP to capture the vals coming from your midi in CHOP. You could then use a look-up CHOP to playback the stored vals. The Lookup CHOP can be set to interpolate between samples to give you some nice smoothing between vals.

Another approach that might be more fruitful would be to run another instance as a pass through to your actual project. The helper app would capture and record your inputs and then you could pass the data over to your project with a shared memory CHOP (if you have a commercial or edu license), a touch out / in combination, or a Pipe out / in combination. That should ensure that you can record at a high frame rate in a separate instance while also getting your data over to your project file.

Does that help give you a push in the right direction?

What’s happening is that your code generates a load which is too heavy for your computer to keep up with at 60 FPS, so it slows down to the maximum tempo it can do, which seems to be 13 FPS. This means it is missing many frames it should be recording.

If you want to record multiple Midi channels in realtime, I have a feeling you would be better off using a Record CHOP.

edit: i was typing on mobile, did not see matthew already answered

Wow, thanks, that was an extremely quick response. Did some research about those chops and that was way easier.

The only thing I need to know is how to extract the length of the record chop (how many frames long the recording is) so I can give it to the timer that’s running the progress bar and the playback of the recording.

Use an Info CHOP to read out the properties of the recording in the Record CHOP.

Ok, thanks. I also found out you can snag it with op(‘CHOPname’).numSamples

So, for some reason the record chop seems to only record frames as they appear on the timeline. So if the timeline is a 10 second loop, it just dubs over previously recording changes as you cycle through the 10 seconds again. The timeline and I really have no business together and I’d prefer not to deal with it at all. How can I create a recording that starts at frame 0 and ends at frame x, which can be up to an infinite amount of frames past the start? I’m so close to finishing this endeavor but I think this is the last hiccup.

If there’s no way to escape the timeline, would I somehow have to restart the timeline as I hit the playback button, and then continuously change the loop length to a little ways ahead of wherever the current frame is that’s recording?

Most people never use the timeline in TD. In the lower left corner you can set the length of the timeline to any number of frames you desire, so set it to something like 600000 and be done with it.

For playback of the recording, connect the output of the Record CHOP to a Lookup CHOP and control playback with another CHOP giving a 0-1 signal to the Lookup CHOP.

Ok, so I think it’s playing back and recording video just fine, however, when I hit record I’m watching the record chop, it adds like 5 to 10 seconds of constant, straight lines for each channel before it starts recording anything. Even weirder, so the button I’m hitting to record is part of the channel running into record. In that ghost period that it adds the record is at 1, then changes back to 0 as it starts actually recording. I really have no explanation for any of this. I took off the record button from the input with a select chop in case that had anything to do with it. What I got to work was to restart the timeline and then start the recording, and it gave me no problems. For the life of me though, I can’t find a way to reset the timeline in python, or any other way besides hitting that button yourself.

If anyone knows of a way to control the timeline or has any idea what could possibly be happening here, let me know, thanks!

this is the moment it starts to get handier if you just post your .toe file instead of describing all the nodes you put down, makes it easier for people to see what you did and /or post a new version.

I mean the piece that’s acting up is a record chop, with a midi input using linear interpolation, input as current frame. Timeline set to 60000 as suggested, done. If I included the .toe you wouldn’t have my same midi controller anyway to be able to test it.

Anyways, actually recording from zero just now it gave me an extra 35 ghost seconds tacked onto the end of what I just recorded. I really think at this point one of the Touchdesigner developers took my instance of the software off autonomous mode and is just doing this to mess with me. I really have no explanation for why the record chop should behave like that.

Re: the record CHOP and oddities in your data. You’ll likely need to fire a script to reset the play head of the timeline before you start recording. Something like:

op('/local/time').frame = 1 op('your_record_chop_here').par.reset.pulse() op('/local/time').play = 1

The above assumes you are starting from a state where your project is paused. First you reset your play head position to the first frame, then dump the previous recorded data, finally you then you start the timeline. Something like this should give you a clean start.


Rather than the record CHOP you might use a trail CHOP.

Turn on the Grow Length par (this will let your trail CHOP grow continuously).

BE ADVISED YOU ARE NOW IN DANGEROUS WATERS!

I’ve seen other folks fall into the trap of forgetting to stop their trail CHOP from caching data - this isn’t a problem at small scales, but you can easily end up with toe files that become huge and unstable, and even in some cases will fail to launch due to the memory burden they impose on your system at start.

Awesome, thanks I’ll try it! If I’m using the trail chop do I even need to worry about the timeline?

The Trail CHOP doesn’t care about the timeline if you turn on Grow Length - though you’ll need to remember pulse reset it so you don’t have any left over data when you start a new session.

Alright, thanks for all the help; all set, except for one thing. After recording the transitions to the music, I need to play back what I just did so I can see if it’s ready to export or not. I already have this coded in, except I’ve never been able to get audio to play in Touch. It seems the only way to do that is using the audio play chop, but no matter what I do to it it always just says “object noneType non-subscriptable” which I’ve seen before as a general error and realize that it’s just as nonsensical as it sounds. It’s not in the operator snippets either.

I have an audio file in chop with the song I’m working with, setting it to play from the start when I start recording midi changes, when I start playing it back to view what I recorded, and when I start recording the movie out with the midi changes. For those first two cases I need to hear it through the speakers, so if I could just connect it to some sort of audio out node rather than having to reload the song again somewhere else that would be ideal. Any ideas would be helpful! This whole endeavor has been filled with little hiccups, hopefully I can just figure this out and be down with it.

You have to use the Audio Device Out CHOP to listen to audio which is coming from your CHOP nodes, instead of the Audio Play CHOP.

This is mentioned in the help page of the the Audio Play CHOP (click the question mark in the top-left of the parameter window of any node to read its help page how to use this node): With the Audio Play CHOP, audio samples do not enter or pass through TouchDesigner, so you will not see them in CHOPs, and you cannot process them in CHOPs. The Audio Play CHOP starts an external process that opens the file and sends it directly to the the audio outputs of your computer.