Jump to: Board index » General » Fusion

Expression for loop with speed control between two values?

Learn about 3D compositing, animation, broadcast design and VFX workflows.
  • Author
  • Message
Offline

banana

  • Posts: 35
  • Joined: Mon Dec 10, 2018 9:41 am
  • Real Name: Klaus Mayer

Expression for loop with speed control between two values?

PostThu Oct 07, 2021 6:51 pm

Hi there,
is there a way to create a loop by an expression (so without keyframes and the spline editor) and have controls for start/end-value and speed?
To give some context (maybe that is important).
I need a given number A to count up to a given number B, and after reaching number B dropping down to Number A again and repeating counting up. So this is no pingpong loop, I would call it a linear loop. Also I would like to have the start/end values (number A and B) customizable so I can link these values to other nodes, where these values are generated at the start of the animation or maybe let them be defined by user input (e.g. a custom tool).
To make everything even more complex, it would be nice to have a control for the speed of the animation, so that I can customize the duration of one loop-cycle as well.
I don't know, if everything I wish for needs to be in one expression, but maybe it is important for you, finding the right syntax.

I have tried messing around with "sin" and "cos" functions together with "floor" etc., but they all perform a pingpong output. But as I mentinoed, I need a drop to the start value after the first loop. I also tried to play around with a LERP-function (which I don't know much about) but this also included keyframes in a way, which I try to avoid, because of the lagging customizability.

Thanks,
Klaus
Davinci Resolve 18.5
Intel i7 6700
Radeon RX570 8gb
Offline

xunile

  • Posts: 3109
  • Joined: Mon Apr 23, 2018 5:21 am
  • Real Name: Eric Eisenmann

Re: Expression for loop with speed control between two value

PostThu Oct 07, 2021 7:19 pm

You can use the mod operator % to have it loop around, if you use "time%9" then it will start at 0 and go to 8 then back to 0. You can also use custom controls for the start and end values.
Attachments
2021-10-07 (4).png
2021-10-07 (4).png (673.9 KiB) Viewed 3198 times
2021-10-07 (5).png
2021-10-07 (5).png (695.27 KiB) Viewed 3198 times
2021-10-07 (6).png
2021-10-07 (6).png (694.88 KiB) Viewed 3198 times
Win 10 Home | Intel i7 - 10700f 64 GB 1 TB GB SSD 2 TB SSD
RTX-3060 12 GB | Resolve Studio 18.6.6| Fusion Studio 18.6.6

Win 10 Home | Intel Core I7-7700HQ 32 GB 1 TB NVME SSD 1 TB SATA SSD
GTX-1060-6GB | Resolve 17.4.6
Offline
User avatar

Bryan Ray

  • Posts: 2591
  • Joined: Mon Nov 28, 2016 5:32 am
  • Location: Los Angeles, CA, USA

Re: Expression for loop with speed control between two value

PostThu Oct 07, 2021 8:39 pm

The simplest solution is to get the Wave Modifier from Reactor. It's a Fuse, so you'll need to restart Fusion/Resolve after installing. Right-click on the control you want to modify, and choose Modify With > Wave.

You want the Saw mode. The Period is the duration of the loop, Offset is the Start value, and Offset + Strength will be the End value. You'll want to increase Limit to a value higher than the highest value you expect to output.

Capture.JPG
Capture.JPG (78.41 KiB) Viewed 3175 times


It is, of course, also possible to write an expression to do the same thing, but I don't have the time right this minute to get into it. If your use case isn't taken care of by either Xunile's nor my solutions, let us know, and I can dive into the math problem a bit later on.
Bryan Ray
http://www.bryanray.name
http://www.sidefx.com
Offline
User avatar

TheBloke

  • Posts: 1905
  • Joined: Sat Nov 02, 2019 11:49 pm
  • Location: UK
  • Real Name: Tom Jobbins

Re: Expression for loop with speed control between two value

PostFri Oct 08, 2021 9:10 am

There's also the Extended Wave modifier, also in Reactor, which adds a couple more options, like the ability to reverse the wave. Not needed in this instance though.

banana, I know you said you didn't want to use the Spline Editor, but just checking you were aware that you can achieve the loop part of this (not the custom control part) - with two keyframes and the Set Loop -> Loop option:
Image

This has the advantage that you could, if desired, set easing on the transition from lower to upper value and that would be automatically replicated in the loop. But yeah it doesn't easily work with custom controls for upper/lower/period, you'd need to edit the keyframe values and timing to change the loop.

Below is a Background node with its four colour controls (TopLeftRed, TopLeftGreen, TopLeftBlue, TopLeftAlpha) animated in four different ways. It has three custom controls - LoopPeriod, LoopLower, LoopUpper - to control this:
  • TopLeftRed = Wave modifier (requires installation from Reactor)
    • Period -> Connect To -> LoopPeriod.
    • Offset -> Connect To -> LoopLower
    • Strength -> SimpleExpression -> "LoopUpper - LoopLower" (could also have used a Calculation modifier)
  • TopLeftGreen = Keyframes with Loop option as described above. Looping from 0 to 1.0, not connected to the custom controls.
  • TopLeftBlue = Expression Modifier, implementing an expression to loop between LoopLower and LoopUpper in LoopPeriod.
    • LoopLower, LoopUpper and LoopPeriod are Connect To into n1, n2 and n3 of the modifier.
    • Number Out = an expression to loop between these values (probably not the most elegant expression, but seems to work)
  • TopLeftAlpha = SimpleExpression. Same expression as in the Modifier, but referencing the three custom controls directly
So pick your poison as to which method you like most. In this example the Expression Modifier doesn't do anything that the SimpleExpression doesn't also do, but the modifier is more work to set up. I think the only reason to consider an Expression Modifier is that it could then be further modified (eg if you wanted some randomness via a Perturb), and because it has the unique ability to keyframe the expression, allowing different expressions at different times.
Code: Select all
{
   Tools = ordered() {
      Background1 = Background {
         CtrlWZoom = false,
         Inputs = {
            GlobalOut = Input { Value = 100, },
            Width = Input { Value = 1920, },
            Height = Input { Value = 1080, },
            ["Gamut.SLogVersion"] = Input { Value = FuID { "SLog2" }, },
            TopLeftRed = Input {
               SourceOp = "WaveModifier1",
               Source = "Result",
            },
            TopLeftGreen = Input {
               SourceOp = "Background1TopLeftGreen",
               Source = "Value",
            },
            TopLeftBlue = Input {
               SourceOp = "Expression1",
               Source = "NumberResult",
            },
            TopLeftAlpha = Input {
               Value = 20,
               Expression = "(time % LoopPeriod / LoopPeriod * (LoopUpper - LoopLower)) + LoopLower",
            },
            LoopLower = Input {
               SourceOp = "Publish1",
               Source = "Value",
            },
            LoopUpper = Input {
               SourceOp = "Publish2",
               Source = "Value",
            },
            LoopPeriod = Input {
               SourceOp = "Publish3",
               Source = "Value",
            },
         },
         ViewInfo = OperatorInfo { Pos = { 948, 147.97 } },
         UserControls = ordered() {
            LoopLower = {
               LINKS_Name = "LoopLower",
               LINKID_DataType = "Number",
               INPID_InputControl = "SliderControl",
               INP_Integer = false,
               INP_MinScale = 0,
               INP_MaxScale = 1,
               ICS_ControlPage = "Color",
            },
            LoopUpper = {
               LINKS_Name = "LoopUpper",
               LINKID_DataType = "Number",
               INPID_InputControl = "SliderControl",
               INP_Integer = false,
               INP_MinScale = 0,
               INP_MaxScale = 1,
               ICS_ControlPage = "Color",
            },
            LoopPeriod = {
               LINKS_Name = "LoopPeriod",
               LINKID_DataType = "Number",
               INPID_InputControl = "SliderControl",
               INP_Integer = true,
               INP_MinScale = 1,
               INP_MaxScale = 1000,
               INP_MinAllowed = 1,
               INP_MaxAllowed = 1000000,
               ICS_ControlPage = "Color"
            }
         }
      },
      WaveModifier1 = Fuse.WaveModifier {
         CtrlWZoom = false,
         Inputs = {
            Wave = Input { Value = FuID { "Saw" }, },
            Period = Input {
               SourceOp = "Publish3",
               Source = "Value",
            },
            Strength = Input {
               Value = 10,
               Expression = "Background1.LoopUpper-Background1.LoopLower",
            },
            Offset = Input {
               SourceOp = "Publish1",
               Source = "Value",
            },
            Limit = Input { Value = 0, },
         },
         Version = 100
      },
      Publish3 = PublishNumber {
         CtrlWZoom = false,
         Inputs = {
            Value = Input { Value = 10, },
         },
      },
      Publish1 = PublishNumber {
         CtrlWZoom = false,
         Inputs = {
            Value = Input { Value = 20, },
         },
      },
      Background1TopLeftGreen = BezierSpline {
         SplineColor = { Red = 0, Green = 255, Blue = 0 },
         NameSet = true,
         KeyFrames = {
            [0] = { 0, RH = { 3.33333333333333, 0.333333333333333 }, Flags = { Linear = true, Loop = true } },
            [10] = { 1, LH = { 6.66666666666667, 0.666666666666667 }, Flags = { Linear = true, Loop = true } }
         }
      },
      Expression1 = Expression {
         CtrlWZoom = false,
         Inputs = {
            n1 = Input {
               SourceOp = "Publish1",
               Source = "Value",
            },
            n2 = Input {
               SourceOp = "Publish2",
               Source = "Value",
            },
            n3 = Input {
               SourceOp = "Publish3",
               Source = "Value",
            },
            NumberExpression = Input { Value = "(time % n3 / n3) * (n2 - n1) + n1", },
            NumberControls = Input { Value = 1, },
            NameforNumber1 = Input { Value = "Lower", },
            NameforNumber2 = Input { Value = "Upper", },
            NameforNumber3 = Input { Value = "Loop Period", },
            ShowNumber4 = Input { Value = 0, },
            ShowNumber5 = Input { Value = 0, },
            ShowNumber6 = Input { Value = 0, },
            ShowNumber7 = Input { Value = 0, },
            ShowNumber8 = Input { Value = 0, },
            ShowNumber9 = Input { Value = 0, },
            PointControls = Input { Value = 1, },
            ShowPoint1 = Input { Value = 0, },
            ShowPoint2 = Input { Value = 0, },
            ShowPoint3 = Input { Value = 0, },
            ShowPoint4 = Input { Value = 0, },
            ShowPoint5 = Input { Value = 0, },
            ShowPoint6 = Input { Value = 0, },
            ShowPoint7 = Input { Value = 0, },
            ShowPoint8 = Input { Value = 0, },
            ShowPoint9 = Input { Value = 0, },
         },
      },
      Publish2 = PublishNumber {
         CtrlWZoom = false,
         Inputs = {
            Value = Input { Value = 30, },
         },
      }
   },
   ActiveTool = "Background1"
}
Resolve Studio 17.4.3 and Fusion Studio 17.4.3 on macOS 11.6.1

Hackintosh:: X299, Intel i9-10980XE, 128GB DDR4, AMD 6900XT 16GB
Monitors: 1 x 3840x2160 & 3 x 1920x1200
Disk: 2TB NVMe + 4TB RAID0 NVMe; NAS: 36TB RAID6
BMD Speed Editor
Offline

banana

  • Posts: 35
  • Joined: Mon Dec 10, 2018 9:41 am
  • Real Name: Klaus Mayer

Re: Expression for loop with speed control between two value

PostSat Oct 09, 2021 5:24 pm

Thank you xunile,
thank you Bryan and
thank you TheBloke
:D
To make it short: The solutions, which works best for me is xunile's suggestion. Using the % operator for a loop.

@xunile: I was able to use the expression right from the start and I managed to put every variable I needed into it. Thanks for posting the example. I did not know the % thing before. Very handy.

@Bryan: I have read about the Reactor's Wave Modifier in an older post of yours before, when I was searching this forum for answers to my question. I did not know that it provides a saw mode as well. Nice!
I don't have Reactor installed yet and to keep things easy and futureproof I found xuniles solution more appealing. But nevertheles thank you for the suggestion. I don't know much about Reactor yet, except it's awesomeness ;) I guess I should give it a try, as soon as I have time for that.

@TheBloke: Yeah, I knew about the SplineEditors loop functionality before, thank you for suggesting. But, you're right, I don't want to use keyframes as I need as much customizability and responsiveness as possible. I'm working on complicated (at least for me) typo animations for creating custom titles with user input options (e.g. duration of loop) most of the time. That is why. I don't have Reactor installed yet, so I could not try out your posted comp, sorry. I may get into it, as soon as I will have the need for it again. At the moment there is no time for that unfortunatelly.

Thanks again
bye guys.
Davinci Resolve 18.5
Intel i7 6700
Radeon RX570 8gb
Offline
User avatar

Bryan Ray

  • Posts: 2591
  • Joined: Mon Nov 28, 2016 5:32 am
  • Location: Los Angeles, CA, USA

Re: Expression for loop with speed control between two value

PostSat Oct 09, 2021 5:33 pm

I just remembered something! I actually have a macro with the sawtooth expression in it! Copy-and-paste this into your flow view, and you'll be able to see expressions with several periodic functions:


Code: Select all
{
   Tools = ordered() {
      MT_Glitch_Waves = MacroOperator {
         Inputs = ordered() {
               Comments = Input {
               Value = "A collection of wave-generating expressions.\r\n\r\nMuse Tools, Glitch Tools Collection\r\nby Bryan Ray\r\nwww.musevfx.com",
            },

            Sin = InstanceInput {
               SourceOp = "Waves",
               Source = "SineWave",
               Expression = "(sin(2*pi*time/Period) + 1) / 2 * (Maximum-Minimum) + Minimum",
               Default = 0.9829629131445,
            },
            Triangle = InstanceInput {
               SourceOp = "Waves",
               Source = "TriangleWave",
               Expression = "(asin(sin(2*pi*time/Period))/pi+0.5) * (Maximum-Minimum) + Minimum",
               Default = 0.9166666666667,
            },
            Square = InstanceInput {
               SourceOp = "Waves",
               Source = "SquareWave",
               Expression = "floor(SineWave + 0.5)",
               Default = 1,
            },
            Sawtooth = InstanceInput {
               SourceOp = "Waves",
               Source = "SawtoothWave",
               Expression = "(time%Period)/Period *  (Maximum-Minimum) + Minimum",
               Default = 0.2916666666667,
            },
            Parabolic = InstanceInput {
               SourceOp = "Waves",
               Source = "ParabolicSawtoothWave",
               Expression = "sin(( (pi*time) / (2*Period) ) % (pi/2)) *  (Maximum-Minimum) + Minimum",
               Default = 0.442288690219,
            },
            ReverseParabolic = InstanceInput {
               SourceOp = "Waves",
               Source = "ReversedParabolicSawtoothWave",
               Expression = "cos(( (pi*time) / (2*Period) ) % (pi/2)) * (Maximum-Minimum) + Minimum",
               Default = 0.8968727415327,
            },
            Exponential = InstanceInput {
               SourceOp = "Waves",
               Source = "ExponentialSawtoothWave",
               Expression = "(exp((2*pi*(time % Period))/Period) - 1)/exp(2*pi)",
               Default = 0.0098042406716,
            },
            Bounce = InstanceInput {
               SourceOp = "Waves",
               Source = "BounceWave",
               Expression = "abs(sin(pi*time/Period)) *  (Maximum-Minimum) + Minimum",
               Default = 0.7933533402912,
            },
            Min = InstanceInput {
               SourceOp = "Waves",
               Source = "Minimum",
               Default = 0,
            },
            Max = InstanceInput {
               SourceOp = "Waves",
               Source = "Maximum",
               Default = 1,
            },
            Period = InstanceInput {
               SourceOp = "Waves",
               Source = "Period",
               Default = 24,
            },
         },
         ViewInfo = GroupInfo { Pos = { 0, 0, }, },
         Tools = ordered() {
            Waves = BrightnessContrast {
               CtrlWZoom = false,
               NameSet = true,
               Inputs = {
                  Blend = Input { Value = 0, },
                  ProcessRed = Input { Value = 0, },
                  ProcessGreen = Input { Value = 0, },
                  ProcessBlue = Input { Value = 0, },
                  ProcessAlpha = Input { Value = 0, },
                  Red = Input { Value = 1, },
                  Green = Input { Value = 1, },
                  Blue = Input { Value = 1, },
                  Gain = Input { Value = 1, },
                  Gamma = Input { Value = 1, },
                  Saturation = Input { Value = 1, },
                  High = Input { Value = 1, },
                  SineWave = Input {
                     Value = 0.982962913144534,
                     Expression = "(sin(2*pi*time/Period) + 1) / 2 * (Maximum-Minimum) + Minimum",
                  },
                  TriangleWave = Input {
                     Value = 0.916666666666667,
                     Expression = "(asin(sin(2*pi*time/Period))/pi+0.5) * (Maximum-Minimum) + Minimum",
                  },
                  SquareWave = Input {
                     Value = 1,
                     Expression = "floor(SineWave + 0.5)",
                  },
                  SawtoothWave = Input {
                     Value = 0.291666666666667,
                     Expression = "(time%Period)/Period *  (Maximum-Minimum) + Minimum",
                  },
                  ParabolicSawtoothWave = Input {
                     Value = 0.442288690219001,
                     Expression = "sin(( (pi*time) / (2*Period) ) % (pi/2)) *  (Maximum-Minimum) + Minimum",
                  },
                  ReversedParabolicSawtoothWave = Input {
                     Value = 0.896872741532688,
                     Expression = "cos(( (pi*time) / (2*Period) ) % (pi/2)) * (Maximum-Minimum) + Minimum",
                  },
                  ExponentialSawtoothWave = Input {
                     Value = 0.00980424067164143,
                     Expression = "(exp((2*pi*(time % Period))/Period) - 1)/exp(2*pi)",
                  },
                  BounceWave = Input {
                     Value = 0.793353340291235,
                     Expression = "abs(sin(pi*time/Period)) *  (Maximum-Minimum) + Minimum",
                  },
                  Blip = Input { Expression = "iif( (((time+Period) % (Period+BlipLength) / Period) * (Period/BlipLength) - (Period/BlipLength)) >= 0,Maximum,Minimum)", },
               },
               ViewInfo = OperatorInfo { Pos = { -1980, -280.5, }, },
               Colors = {
                  TileColor = { R = 0.498039215686275, G = 0.498039215686275, B = 1, },
                  TextColor = { R = 0, G = 0, B = 0, },
               },
               UserControls = ordered() {
                  Red = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Green = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Blue = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Alpha = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Gain = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Lift = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Gamma = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Contrast = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Brightness = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Saturation = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Low = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  High = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  Direction = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  ClipBlack = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  ClipWhite = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  PreDividePostMultiply = {
                     INPID_PreviewControl = "",
                     INPID_InputControl = "",
                     IC_ControlPage = 0,
                  },
                  SineWave = {
                     LINKID_DataType = "Number",
                     LINKS_Name = "Sine Wave",
                     INPID_InputControl = "SliderControl",
                     IC_ControlPage = 0,
                     INP_Default = 0,
                  },
                  TriangleWave = {
                     LINKID_DataType = "Number",
                     LINKS_Name = "Triangle Wave",
                     INPID_InputControl = "SliderControl",
                     IC_ControlPage = 0,
                     INP_Default = 0,
                  },
                  SquareWave = {
                     LINKID_DataType = "Number",
                     LINKS_Name = "Square Wave",
                     INPID_InputControl = "SliderControl",
                     IC_ControlPage = 0,
                     INP_Default = 0,
                  },
                  SawtoothWave = {
                     LINKID_DataType = "Number",
                     LINKS_Name = "Sawtooth Wave",
                     INPID_InputControl = "SliderControl",
                     IC_ControlPage = 0,
                     INP_Default = 0,
                  },
                  ParabolicSawtoothWave = {
                     LINKID_DataType = "Number",
                     LINKS_Name = "Parabolic Sawtooth Wave",
                     INPID_InputControl = "SliderControl",
                     IC_ControlPage = 0,
                     INP_Default = 0,
                  },
                  ReversedParabolicSawtoothWave = {
                     LINKID_DataType = "Number",
                     LINKS_Name = "Reversed Parabolic Sawtooth Wave",
                     INPID_InputControl = "SliderControl",
                     IC_ControlPage = 0,
                     INP_Default = 0,
                  },
                  ExponentialSawtoothWave = {
                     LINKID_DataType = "Number",
                     LINKS_Name = "Exponential Sawtooth Wave",
                     INPID_InputControl = "SliderControl",
                     IC_ControlPage = 0,
                     INP_Default = 0,
                  },
                  BounceWave = {
                     LINKID_DataType = "Number",
                     LINKS_Name = "Bounce Wave",
                     INPID_InputControl = "SliderControl",
                     IC_ControlPage = 0,
                     INP_Default = 0,
                  },
                  Blip = {
                     LINKID_DataType = "Number",
                     INPID_InputControl = "SliderControl",
                     INP_Default = 0,
                     IC_ControlPage = 0,
                  },
                  SineBlip = {
                     LINKID_DataType = "Number",
                     INP_Default = 0,
                     INPID_InputControl = "SliderControl",
                     LINKS_Name = "Sine Blip",
                     IC_ControlPage = 0,
                  },
                  Minimum = {
                     LINKID_DataType = "Number",
                     INPID_InputControl = "SliderControl",
                     INP_Default = 0,
                     IC_ControlPage = 0,
                  },
                  Maximum = {
                     LINKID_DataType = "Number",
                     INPID_InputControl = "SliderControl",
                     INP_Default = 1,
                     IC_ControlPage = 0,
                  },
                  Period = {
                     IC_ControlPage = 0,
                     INP_Default = 24,
                     INP_MinScale = 2,
                     INPID_InputControl = "SliderControl",
                     INP_MaxScale = 120,
                     LINKID_DataType = "Number",
                  },
                  BlipLength = {
                     IC_ControlPage = 0,
                     INP_Default = 2,
                     LINKS_Name = "Blip Length",
                     INPID_InputControl = "SliderControl",
                     INP_MaxScale = 60,
                     LINKID_DataType = "Number",
                  },
               },
            },
         },
      },
   },
   ActiveTool = "MT_Glitch_Waves",
}



I don't remember if that's in the actual Glitch Tools package in Reactor or not, but I find it useful when I want to distribute something but can't be sure whoever receives it will have the Waves Modifier fuse installed. As a macro, it has no dependencies.
Bryan Ray
http://www.bryanray.name
http://www.sidefx.com

Return to Fusion

Who is online

Users browsing this forum: No registered users and 25 guests