Home Technical Talk

MaxScriptin'

So I've decided to put down the brush for a few weeks and wrap my head around MaxScript. Knowing a few other polycounters have taken this trip I'm wondering if there isn't some good advice that might help make the trip less bumpy. MoP suggested the DVD's from CG Academy since they came as part of the Dom War prize pack, and he has given them a brief look. They are a little pricey but I might be able to talk my AD into picking them up for the work library.

I've ordered the MaxScript Essentials book and of course the Reference that comes with Max is fairly helpful most of the time, even if the syntax is a bit confusing and its light on examples.

I'm at a stuck point right now in a script I'm writing, I'm trying to control the Batch Render Interface but the ref/help files are very sketching and I'm having trouble finding info on how to create/name batch render views as well as set start/end frame ranges.

Heres the script I have so far, it relays pretty heavily on some custom elements of SoundTrax. The script does work with the demo which lets you load up 4 tracks.
<font class="small">Code:</font><hr /><pre>if (soundtrax.isactive() == false) then soundtrax.init(true) soundtrax.open() soundtrax.close()
rollout SoundErrorRollout "Missing Sound Files" width:160 height:104
(
label lbl1 "Please Launch SoundTrax and Load Sound Files" pos:[16,8] width:128 height:32
Button btn1 "SoundTrax" pos:[32,64] width:96 height:24
on btn1 pressed do
(
DestroyDialog SoundErrorRollout
soundtrax.open()
)
)

if (soundtrax.numtracks() != 0) == true then
(
--Name
StxN = for i=1 to soundtrax.numtracks() collect
(
getfilenamefile (soundtrax.name(i))
)
--Length
StxL = for i=1 to soundtrax.numtracks() collect
(
soundtrax.filelength(i) as integer /320
)
--Start
StxS = for i=1 to soundtrax.numtracks() collect
(
soundtrax.start(i) as integer /320
)
--End
StxE = for i=1 to soundtrax.numtracks() collect
(
soundtrax.end(i) as integer /320
)

-- This will be our new multidimensional array
StxTrack = #()

for i = 1 to StxN.count do
(
-- Create a new array appending each value of StxN one at time
append StxTrack StxN

-- Create another array for each index of new_narr
-- This will make it a multi dimensional array holding all the relevant values for each element of narr
StxTrack = #()
append StxTrack StxL
append StxTrack StxS
append StxTrack StxE

--print the values using the format command
format "% = %\n" StxN StxTrack
)
)
else
(
CreateDialog SoundErrorRollout 160 104
)</pre><hr />Right now I have it printing using the format command (which is new to me) until I figure out how to use that data to build a Batch Render list. It prints an array for each track,(track name, start frame, end frame, total # of frames)

Specific Questions:
- How do I control the batch render interface? Or any kind of similar interface?
- Any suggestions on code layout (tabbing) and comments?
- Can anyone better explain functions?
- Is there an easy way to tell max to back one folder out of a path? How about backing out once and entering into another folder?

maybe if I explain the workflow it will help things make sense.
Using Soundtrax I import 20-100 sound files and using a script I already wrote it sequences and pads the files back to back. I then lip sync a character to the sound files. Using another script I gather into an array the name of the sound track, its start and end frame and its path. I want to create a batch render "view" for each sound file and render out to the path the frames it runs for. The issue is I can create new views in the batch render interface but I can't adjust any of the other values.

Replies

  • CrazyButcher
    Options
    Offline / Send Message
    CrazyButcher polycounter lvl 18
    <font class="small">Code:</font><hr /><pre>
    -- a function frequently used
    fn f_max a b = (if (a > b) then a else b)

    -- the method returns the "next" upper directory of a path
    -- so "C:\\gigatexture\\europe\\" will return "C:\\gigatexture\\"
    -- it will always return a minimum of "C:\\"
    -- at least should I havent tested the code at all ;)
    function f_getupperdir filepath = (
    local pathnames = filterString filepath "\\"
    -- rebuild newname, cept last one
    -- pick greater value for count
    -- we always want a minimum of 1 however (the file drive)

    local cnt = f_max 1 (pathnames.count-1)
    local newname = ""
    for i=1 to cnt do (
    newname += pathnames + "\\"
    )
    -- last value is always returned automatically in maxscript
    newname
    )

    -- the StxTrack array doesnt seem to be needed or ?
    for i = 1 to StxN.count do
    (
    local filenamefull = StxN
    -- some built-in max functions to get details from a full filename (as returned by getfilename)
    -- e.g "C:\\soundfiles\\überzeugenderspruch.wav"
    local filenameshort = getFilenameFile filenamefull -- e.g will be "überzeugenderspruch"
    local filepath = getFilenamePath filenamefull -- e.g will be "C:\\soundfiles\\"

    -- camnode must be spcified !
    -- dont know what "camera" you need there but you should pass it somehow
    local camnode

    -- functions in maxscript just get the arguments written after another
    -- if no argument is needed you must do funcname()
    -- thats utter crap imo but w2g whosever idea it was to not always use brackets
    -- like in almost every other language

    local newbatch = batchRenderMgr.CreateView camnode

    -- interfaces let you directly acces your variable with .interfaceproperty / func
    -- set batch properties
    newbatch.name = filenameshort
    newbatch.startFrame = StxS
    newbatch.endFrame = StxE

    -- build the outfilename "go up one directory"
    -- the brackets will tell that the first function's argument is only
    -- filepath
    newbatch.outputFilename = (f_getupperdir filepath) + "someotherdiryouwant\\" + filenameshort + ".avi"
    )
    </pre><hr />

    however, havent tested it at all, not even compiled for syntax erros, nevertheless in theory it should work or get you started to fix issues left (personally I have 0 experience with batch render stuff). use at your own risk, might atomize a country near you.
  • Mark Dygert
    Options
    Offline / Send Message
    WOW that opened so many doors and is teaching me so much, talk about a quality example, thanks. I'm still going over it and learning but for the most part it runs pretty much like you envisioned it with some minor errors setting up the output path. No doubt the errors are probably coming from something I butchered to make it fit =P

    I think the error is coming from what I have defined for "camnode" I'm using "getActiveCamera()" I might need to create a camera and use a constant name to get it working. either that or it doesn't like "local filenamefull = StxN(i)" I'll look into it deeper tomorrow. Thanks for for giving me such a great example to work off of.


    Thanks a bunch!
  • seforin
    Options
    Offline / Send Message
    seforin polycounter lvl 17
    haah vig you wanna get my reflection shader to go next?

    haha, hey im maybe a bit off as far as scripting goes for max... can you kinda explain some of the advantages it offers?
  • Mark Dygert
    Options
    Offline / Send Message
    The best way to explain what max script offers is to troll around www.scriptspot.com and check out all the stuff people have done over years with MaxScript. After seeing what other people have done with it and how handy their scripts have been I decided to dabble in it myself and see if I could automate some very long boring processes I have to go through. Most scripts are so specific they won't be useful to too many other people besides the author but every once in a while you'll find one that speeds things up and makes you wonder why Max didn't have it already.

    Some of my personal favorites I use all the time:
    JJTools: Specifically the Biped selector.
    Advanced Painter: This allows you to paint/scatter objects onto another. Such as rocks on a ground plane, or trees in a scene. I also use the spaghetti and toothpaste features to make groups of wires, or paint wires running from a light bulb to a switch.
    Camera Switcher: At work it isn't uncommon for our scenes to have over 50 cameras. This tool puts all the cameras in a list an lets you switch the active viewport to that camera by clicking on the camera in the list. I edited this one so I could bind to a shortcut, switching to the next/previous camera. So I can press page up/down and cycle through all the cameras.
    Chunk Generator: Great for making rubble, works great with advanced painter.
    E-Light Dome: Creates a light dome, and all of the lights are controlled by one panel. It works great as a quick and dirty AO light set up. You can also set up the dome to use an HDRI which also works great.

    So far I've made a a script that uses soundtrax to sequence audio files back to back but can also put a frame buffer between each track. Which is handy because for each character I have about 40-70 sound clips I have to do lip sync for.

    If you find yourself clicking 7 times to get to a tool there is a good chance with a simple script you can turn that into 1-2 clicks. After seeing what MaxScript has done for others you start looking around and thinking, "damn, I bet this or that could be scripted out" and eventually you start doing it, hahaha.

    As for your reflection shader I bet there is very little scripting involved to fix it, if you want to send me a basic scene with it applied to a sphere I'll take a look at it. There are a few different ways to do reflections in Max it depends on which you need to pull off as to what well have to do.

    Are you trying to get a shader that works in game? If so which game?
    Are you trying to get a shader that just renders nicely? If so what render'er are you using? Scanline, MentalRay ect...
  • seforin
    Options
    Offline / Send Message
    seforin polycounter lvl 17
    ah I see I see, WEll no the shader I was doing, was basically the one ben mathis gave out a while ago for his character. which was just a shader that rendered out / viewport only. I just couldnt get the damn thing to work correctly with my HDR photos, ran out of time so I kinda gave up working on it.

    As far some of those scripts your talking about, I think I might look into them, I never knew you could do so many different effects with max scripts!
  • Rick Stirling
    Options
    Offline / Send Message
    Rick Stirling polycounter lvl 18
    [ QUOTE ]

    haha, hey im maybe a bit off as far as scripting goes for max... can you kinda explain some of the advantages it offers?

    [/ QUOTE ]

    Are you doing the same task over and over? Script it.
  • cw
    Options
    Offline / Send Message
    cw polycounter lvl 17
    exactly what rick said - I've made scripts for many many small repetitive tasks at work such as:

    -batch applying controllers to nodes
    -automatically setting up materials' map slots
    -dumping info about max scenes into text files/xml files
    -automating certain parts of pipeline such as setting skinning parameters and adding bones
    -batch exporting a scene(s) in various ways to save much time doing it manually.
    -error checking engine incompatibe skinning (invalid weights etc.) with visual feedback before leaving max
    -rendering sets of assets with consistent light setup for various purposes

    bunch more stuff, usually small things, but really, if its repetitive its probably script-able.

    At the start i was a nubling and it was all quite tricky, but nowadays its kinda straightforward, unless I come up agains a gnarly thing (and there are a few things in maxscript which are gnarlier than most)

    anyway - OP's picks for training are good, and the best learning is done by trying to get something to work, so once you get the basics down, go for it! laugh.gif

    as an aside, you can write scripts in photoshop too, which can be neat for certain stuff which is awkward to do by actions.

    have fun! laugh.gif
  • Mark Dygert
    Options
    Offline / Send Message
    Hot Buttered Funk on a stick, it works!
    What this script does:
    For each sound track loaded it creates a batch render view and sets the start/end frames and output path for each view to that of the track. It is VERY specific to my work flow, but will help anyone having to lip sync and render many audio clips. This is going to shave about 3-4 hours per character of mind numbing data entry.
    <font class="small">Code:</font><hr /><pre>--*********************************************************************
    -- SoundTraxBatchRenderAutomate V 0.25
    -- Script written by Mark Dygert, with many thanks to Christoph Kubisch (CrazyButcher)
    -- Lead Character Artist,
    -- Her Interactive 8-27-07
    -- This Script will create a batch render file for each sound track, including frame ranges, file path/name
    -- This script requires SoundTrax from www.Boomerlabs.com
    --*********************************************************************

    StxN = for i=1 to soundtrax.numtracks() collect getfilenamefile (soundtrax.name(i)) -- e.g will be "soundname"
    StxS = for i=1 to soundtrax.numtracks() collect soundtrax.start(i) as integer /320 -- e.g will be "start frame number"
    StxE = for i=1 to soundtrax.numtracks() collect soundtrax.end(i) as integer /320 -- e.g will be "end frame number"

    fn f_max a b = (if (a > b) then a else b) -- some wierd voodoo going on here

    function f_getupperdir filepath = ( -- open function
    local pathnames = filterString filepath "\\"
    local cnt = f_max 1 (pathnames.count-1)
    local newname = ""
    for i=1 to cnt do ( -- open loop
    newname += pathnames + "\\"
    ) -- close loop
    newname
    ) -- close funtion

    for i = 1 to StxN.count do ( -- open loop
    makedir (getfilenamepath (soundtrax.name(i)) + "Head\\" + StxN) -- Make new folders for sounds inside the head folder
    ) -- close loop

    for i = 1 to StxN.count do ( -- open loop
    local filenamefull = StxN
    local filenameshort = getFilenameFile filenamefull -- e.g will be "soundname"
    local filepath = getFilenamePath (soundtrax.name(1)) -- Gathers the path of the first track. e.g will be "C:\\path\\SoundsFolder\\"
    local camnode = getActiveCamera() -- Active viewport should be set to a camera
    local newbatch = batchRenderMgr.CreateView camnode
    newbatch.name = filenameshort -- Names the new batch render view
    newbatch.overridePreset = true -- Turn on defult settings overide
    newbatch.startFrame = StxS -- Set start frames
    newbatch.endFrame = StxE -- Set end frames
    newbatch.outputFilename = (f_getupperdir filepath) + "Head\\" + "\\" + StxN + "\\" + filenameshort + "_0000" + ".tga" -- Set OutPut Path & file name
    ) -- close loop</pre><hr />
    Now all that is left is to have it do some checks and clean up so it doesn't keep creating views if views are already made. Then I'm off to draw up a master UI for all the scripts. Thanks again CrazyButcher, you taught me much.
  • CrazyButcher
    Options
    Offline / Send Message
    CrazyButcher polycounter lvl 18
    you're welcome,
    the weird voodoo is just returning the "greater/maximum" of the two values, its a function as well.
    maxscript allows "fn" or "function" as keyword.

    in this case its useful as "pathnames.count-1" might be 0 (if the input path already is "C:\\") so we would return an empty string which isnt good for the filename we use the output function for. Hence the f_max call makes sure that at least 1 e.g C:\ is returned.

    with batchRenderMgr.FindView and batchRenderMgr.GetView, you should be able to wrap the createview with a check if name is already in use

    like
    <font class="small">Code:</font><hr /><pre>
    local newbatch
    local idx = batchRenderMgr.FindView filenameshort
    -- "undefined" is a special value when a variable is uninitialized, or basically has no value at all.
    if (idx == undefined) then(
    newbatch = batchRenderMgr.CreateView camnode
    )
    else (
    newbatch = batchRenderMgr.GetView idx
    )
    </pre><hr />
  • Mark Dygert
    Options
    Offline / Send Message
    It's coming along nicely thanks to CB. I've created a very simple rollout UI, and it sets the active time segment to the track you click on, this will be very handy when it comes time to use it.

    But I hit a snag, I'm trying to sequence the sound files but I can't figure out how to get a loop going that will find the end frame of the last track and make that the start frame of the first track. I think I MIGHT be able to tackle it with functions but I'm still having trouble getting that to work so I tried getting it all to flow in one loop, and that isn't working. I'm almost done... so close... I'll try doing it with functions in the morning.

    Here is what I have so far, which works with the demo of SoundTrax.
    LipSyncHelperUI.jpg
    <font class="small">Code:</font><hr /><pre>--*********************************************************************
    -- SoundTraxBatchRenderAutomate V 0.25
    -- Script written by Mark Dygert, with many thanks to Christoph Kubisch (CrazyButcher)
    -- Lead Character Artist,
    -- Her Interactive 8-27-07
    -- This Script will create a batch render file for each sound track, including frame ranges, file path/name
    -- This script requires SoundTrax from www.Boomerlabs.com
    Global StxN = for i=1 to soundtrax.numtracks() collect getfilenamefile (soundtrax.name(i)) -- e.g will be "soundname"
    Global StxS = for i=1 to soundtrax.numtracks() collect soundtrax.start(i) as integer /320 -- e.g will be "start frame number"
    Global StxE = for i=1 to soundtrax.numtracks() collect soundtrax.end(i) as integer /320 -- e.g will be "end frame number"
    Global StxL = for i=1 to soundtrax.numtracks() collect soundtrax.filelength(i) as integer /320 -- e.g will be "total number of frames in track"

    rollout MainUI "LipSync Helper" width:160 height:432
    ( -- start MainUI Rollout
    GroupBox grp1 "Track Management" pos:[8,8] width:144 height:88
    button btn_ResetAll "Reset All" pos:[16,24] width:64 height:16 toolTip:"Resets all Sound Tracks to Start at 0"
    edittext e_frmbuff "" pos:[96,72] width:40 height:16
    button btn_SeqTracks "Seq Tracks" pos:[16,48] width:64 height:16 toolTip:"Sequences each track back to back"
    button btn_FBSeqTracks "Seq Tracks" pos:[16,72] width:64 height:16 toolTip:"Sequences each track and adds the specified frame buffer"
    label lbl10 "+" pos:[88,72] width:8 height:16
    GroupBox grp12 "Active Time Tool" pos:[8,104] width:144 height:320
    listbox listsel "Click to Set Active Time" pos:[16,120] width:128 height:16 items: StxN
    button btn20 "Set to All" pos:[16,360] width:56 height:24 toolTip:"Sets the active time segment to include all tracks"
    button btn_Batch "Batch" pos:[16,384] width:56 height:24 toolTip:"Auto completes the batch render window"
    label lbl7 "All Start 0" pos:[88,24] width:56 height:16
    label lbl8 "Piggy Back" pos:[88,48] width:56 height:16
    button btn_STXopen "SoundTrax" pos:[72,384] width:72 height:24 toolTip:"Auto completes the batch render window"
    button btn_VOMopen "VoiceOMatic" pos:[72,360] width:72 height:24 toolTip:"Auto completes the batch render window"

    on btn_Batch pressed do ( -- start of Batch
    fn f_max a b = (if (a > b) then a else b) -- some wierd voodoo going on here

    function f_getupperdir filepath = ( -- open function
    local pathnames = filterString filepath "\\"
    local cnt = f_max 1 (pathnames.count-1)
    local newname = ""
    for i=1 to cnt do ( -- open loop
    newname += pathnames + "\\"
    ) -- close loop
    newname
    ) -- close funtion

    for i = 1 to StxN.count do ( -- open loop
    makedir (getfilenamepath (soundtrax.name(i)) + "Head\\" + StxN) -- Make new folders for sounds inside the head folder
    ) -- close loop

    for i = 1 to StxN.count do ( -- open loop
    local filenamefull = StxN
    local filenameshort = getFilenameFile filenamefull -- e.g will be "soundname"
    local filepath = getFilenamePath (soundtrax.name(1)) -- Gathers the path of the first track. e.g will be "C:\\path\\SoundsFolder\\"
    local camnode = getActiveCamera() -- Active viewport should be set to a camera
    local newbatch
    local idx = batchRenderMgr.FindView filenameshort
    if (idx == undefined) then (
    -- newbatch = batchRenderMgr.GetView idx
    messagebox "Views already created"
    )
    else (
    newbatch = batchRenderMgr.CreateView camnode
    newbatch.name = filenameshort -- Names the new batch render view
    newbatch.overridePreset = true -- Turn on defult settings overide
    newbatch.startFrame = StxS -- Set start frames
    newbatch.endFrame = StxE -- Set end frames
    newbatch.outputFilename = (f_getupperdir filepath) + "Head\\" + "\\" + StxN + "\\" + filenameshort + "_0000" + ".tga" -- Set OutPut Path & file name
    )
    ) -- close loop
    ) -- end of batch

    on btn_ResetAll pressed do ( -- Start of reset tracks to 0
    for i = 1 to stxN.count do ( -- open loop
    soundtrax.setstart i 0
    soundtrax.setend i StxL
    ) -- close loop
    ) -- End of Reset tracks to 0

    on btn_SeqTracks pressed do ( -- Start of track sequence
    for i = 1 to StxN.count do ( -- open loop
    messagebox "This feature is broken as of right now"
    ) -- close loop
    ) -- End of track sequence

    on btn_FBSeqTracks pressed do ( -- Start of track sequence + frame buffer
    for i = 1 to StxN.count do ( -- open loop
    local frmbuff = e_frmbuff.text as integer
    local p_StxE = soundtrax.end(i) as integer /320 -- attempting to get previous track end frame
    soundtrax.setstart i 0
    soundtrax.setend i StxL
    soundtrax.setstart i (p_StxE + frmbuff)
    soundtrax.setend i (StxL + frmbuff)
    messagebox "This feature is broken as of right now"
    ) -- close loop
    )-- End of track sequence + frame buffer

    on listsel selected nameIndex do ( -- Start of set active time segment
    local arrnum = finditem StxN listsel.selected
    animationRange = interval StxS[arrnum] StxE[arrnum]
    ) -- End of set active time segment
    ) -- End MainUI rollout

    rollout SoundErrorRollout "Missing Sound Files" width:192 height:120
    ( -- start of SoundError rollout
    button btn1 "Launch SoundTrax" pos:[24,64] width:136 height:40 toolTip:"Launches SoundTrax"
    label lbl19 " Zero sound files loaded! Please launch SoundTrax and load sound files" pos:[32,8] width:128 height:40
    on btn1 pressed do (
    DestroyDialog SoundErrorRollout
    soundtrax.close()
    soundtrax.open()
    )
    ) -- end of SoundError rollout
    ( -- start of script
    if (soundtrax.isactive() == false) then soundtrax.init(true) SoundTrax.open() SoundTrax.close() -- initialize SoundTrax

    if (soundtrax.numtracks() != 0) == true then ( -- check to see if sound files are loaded
    CreateDialog MainUI 160 432
    )
    else (
    CreateDialog SoundErrorRollout 192 120
    )
    ) -- end of script</pre><hr />
    Clean up some comments, finish off that sequencer and output some data to an excel file and I'll be well on my way to a much more productive day. Baby steps though... baby steps heh =)
  • cw
    Options
    Offline / Send Message
    cw polycounter lvl 17
    congrats! sounds useful! laugh.gif
  • Mark Dygert
    Options
    Offline / Send Message
    The sequencer is beating me up, I can't get it to space out the tracks back to back with a 10 frame buffer. So far all I've managed to pull off is have it adjust all the tracks to the same start frame. Despite my best efforts to reset all the tracks to zero before sequencing when the script is run again, they all keep jumping up each time the script is run.
    <font class="small">Code:</font><hr /><pre>
    Global StxN = for i=1 to soundtrax.numtracks() collect getfilenamefile (soundtrax.name(i)) -- e.g will be "soundname"
    Global StxS = for i=1 to soundtrax.numtracks() collect soundtrax.start(i) as integer /320 -- e.g will be "start frame number"
    Global StxE = for i=1 to soundtrax.numtracks() collect soundtrax.end(i) as integer /320 -- e.g will be "end frame number"
    Global StxL = for i=1 to soundtrax.numtracks() collect soundtrax.filelength(i) as integer /320 -- e.g will be "total number of frames in track"
    Global frmbuff = 10
    (
    fn f_check a b = (if (i-1) > 1 then (i=i-1) else (i=1))

    for i=1 to StxN.count do (
    soundtrax.setend i StxL -- resets the end frame to match the length of the track
    soundtrax.setstart i 0 -- resets the track to start on frame 0, otherwise it keeps adding
    )
    for i=1 to StxN.count do (
    soundtrax.shift i (StxE[(f_check (i-1) i)] + frmbuff)-- this should shift the track by finding the end frame of the last track and adding 10frames (the frame buffer)
    )
    )</pre><hr />When I run the above code it returns:
    <font class="small">Code:</font><hr /><pre>#("HEN00a", "HEN00b", "HEN00c", "HEN00d") -- Sound Track Names
    #(0, 0, 0, 0) -- Sound Track Start Frames
    #(68, 40, 14, 14) -- Sound Track End Frames
    #(68, 40, 14, 14) -- Sound Track Length
    10 -- Frame Buffer
    OK
    OK
    and if I run it again...
    #("HEN00a", "HEN00b", "HEN00c", "HEN00d")-- Sound Track Names
    #(78, 78, 78, 78)-- Sound Track Start Frames
    #(146, 118, 92, 92) -- Sound Track End Frames
    #(68, 40, 14, 14) -- Sound Track Length
    10 -- Frame Buffer
    OK
    OK
    and if I run it again...
    #("HEN00a", "HEN00b", "HEN00c", "HEN00d") -- Sound Track Names
    #(156, 156, 156, 156) -- Sound Track Start Frames
    #(302, 274, 248, 170) -- Sound Track End Frames
    #(68, 40, 14, 14) -- Sound Track Length
    10 -- Frame Buffer
    OK
    OK
    and so on ...</pre><hr />

    I can sequence the tracks by tediously building a monster script that slowly process each of the 100 track but there has to be a better way to handle it than writing arrays for each track.
    This is the code for the first two tracks, each of the 100 tracks will have it's own block referencing the last. It also takes a really long time to run.
    <font class="small">Code:</font><hr /><pre>--StxTrack 1
    t1name = getfilenamefile (soundtrax.name(1)) --Gathers sound file name
    t1leg = soundtrax.filelength(1)as integer/320 --Gathers track length
    --Resets the track to frame 0
    t1f = soundtrax.filelength(1)as integer/320
    soundtrax.setstart 1 0
    soundtrax.setend 1 (t1f)
    t1startf = soundtrax.start(1)as integer/320 --Regathers the new start frame
    t1endf = soundtrax.end(1) as integer/320 --Regathers the new end frame
    StxTrack1 = #(t1name, t1leg, t1startf, t1endf) --Track array to be used later.

    --StxTrack 2
    t2name = getfilenamefile (soundtrax.name(2)) --Gathers sound file name
    t2leg = soundtrax.filelength(2)as integer/320 --Gathers track length
    --Resets the track to frame 0
    t2f = soundtrax.filelength(2)as integer/320
    soundtrax.setstart 2 0
    soundtrax.setend 2 (t2f)
    -- Shift track 5 frames after previous track
    soundtrax.shift 2 (StxTrack1[4] + frmbuff)
    t2startf = soundtrax.start(2)as integer/320 --Regathers the new start frame
    t2endf = soundtrax.end(2) as integer/320 --Regathers the new end frame
    StxTrack2 = #(t2name, t2leg, t2startf, t2endf) --Track array to be used later.</pre><hr />
    I'm totally lost and can't figure it out... I think I'm going to have to write blocks of code for each of the 100 tracks. At least I think I can write a check to see if a track is loaded and have it stop when it finds the first unloaded track.
  • CrazyButcher
    Options
    Offline / Send Message
    CrazyButcher polycounter lvl 18
    naah the whole point of coding is not doing copy&paste work and tedious manual text editing in masses!

    analyze what exactly you do with the shift, especially the f_check is a bit weird

    you want every start/end pair after another with a 10 frame break right ?

    your stxE is only written to once (on start) so it doesn accumulate the frames as you go along,
    also you dont need two loops, you can do stuff in the first. easiest would be you create a "runner" variable which you simply add lenght + 10 at the end of each loop iteration.

    so
    <font class="small">Code:</font><hr /><pre>
    local shiftval = 0
    for i=1 to cnt do(
    -- set start/end to 0 and length as you did
    -- and as you noticed you dont need to collect Start and End and as you overwrite them with 0 and Length anyway
    -- then call shift

    -- finally raise shift by length and buffer
    shiftval += length + frmbuff
    )
    </pre><hr />

    also the way you setup teh function f_check its not "typical" for how functions work

    <font class="small">Code:</font><hr /><pre>
    -- yours
    fn f_check a b = (if (i-1) > 1 then (i=i-1) else (i=1))

    -- more "sense"
    -- I split in multiple lines for easier read
    fn f_check a = (
    -- a is the input value that is handed over when the function is called
    if (a-1) > 1 then(
    a-1
    )
    else(
    1
    )

    -- the last values in this case "1" or "a-1" are automatically returned by the function
    -- you can also write "return 1" or "return a-1" but in simple cases not using return is faster I think
    )

    -- now when calling the function you hand over a value and
    -- the func returns the stuff like

    b = f_check 5
    -- b will be 4

    -- or you can access the array as you wanted, or whatever

    for i=1 to 5 do(
    -- a function value passed is not changed (unless passed by reference with the & symbol)
    -- so lets say i is 2 here
    test[f_check i]
    -- here i is still 2, because basically a copy of it was passed to the function and f_check returns a fresh copy as well.
    --This is "by value" passing/returning is only true for basic types (numbers, strings). For structs and arrays it can be a bit more complex

    -- so if you want i to change as well you do
    i = f_check i
    -- but this would actually cause an infinite loop
    -- because now i is 1, and in the for statement above it will be raised to 2 again, then here decreased to 1 and so on ;)
    )
    </pre><hr />

    also I noticed you made your arrays "global" which means they will stay in memory even after the script is run and they are actually not needed ? (unless you access them with other scripts, too).

    cause if you dont need the stuff declare it local, as maxscript by default makes stuff global.

    <font class="small">Code:</font><hr /><pre>

    (
    varA = #(adasd...)
    local varB = #(23,...)
    local somevalue = 10
    (
    -- = performs a copy here
    local varC = somevalue
    varC += 5 -- do something

    -- = basically "links/points" to varB's array
    local varD = varB
    varD[1] = 0
    )
    -- varC cannot be accessed here it is deleted at end
    -- of )
    -- varD was a "pointer" to the array handle, and is deleted
    -- but the array is still alive
    -- however varB[1] now is 0 and not 23 anymore

    -- basic types however are copied directly, so
    -- somevalue is still 10 here
    )
    -- varA now is still in memory (it is global) whilst varB was deleted as well
    </pre><hr />


    the key words are "by value" passing, and "scope of variables" and garbage collection (regarding local/global) if you want to searhc the maxscript help
  • Mark Dygert
    Options
    Offline / Send Message
    It's been a long weekend and I am just now sitting down and going over your post. Best examples yet, I learned and I learned some more.

    ahh garbage collection, that explains why I would have arrays and values floating around in memory defining things causing problems. So max only holds onto these things while the scope is open, gotcha, that makes life easier.

    Everything now works like it should and the script is finished! WOO HOO first script down many more to go! heh
    Thank you so much Christoph for you patients, clear explanations and excellent examples.
    LipSyncHelper.jpg
    Final Script is HERE
    Note: This works with the demo of SoundTrax which will allow you to have 4 sound tracks instead of the 1 max allows. Even with just 4 tracks this tool comes in very handy.

    Here is a break down of what the tool does:
    Reset all -- Sets all of the sound tracks to start at frame zero
    Seq Tracks -- sets each track to start where the other left off. If you want the tracks to be padded by a few frames put that number in the empty box and hit "Seq Tracks".
    Active Time Tool -- This lists the tracks by name, clicking on a track will set the active time segment to the length of the track. Handy when working on one track at a time and many tracks make it impossible to work effectively in track view.
    Set Active To All -- Sets the active time segment to show all tracks
    Create/Update Batch -- This takes info from the tracks such as track name, start/end frame and file path, then creates a batch render file. Note: The path is gathered from the sound file path and then changed to a folder called "head". This is specific to my work flow if you would like it to go somewhere else you can edit the script. The lines containing the path are easy to find if you search for "head".
    Launch SoundTrax -- launches SoundTrax
Sign In or Register to comment.