I am trying to modify an animation someone else made, but I'm having trouble animating the speed of this expression.

It is an expression driving a shape key, and it is supposed to be cycling between 0 and 1. It works fine until I animate it. When I try to animate it, the value starts jumping around between the two keyframes. Before and after it works fine.

So I'm no math genius but I'm guessing it's because the wave should be scaled (which is what the Speed variable does) from a different "pivot point" that is always the current frame I am on. I have no idea how that could be implemented.

So what i would like to achieve is a smooth transition between two wavelengths.

The second part with the "walking" is just an on-off switch I think, so only the first part needs to be considered.

So rephrasing my question, I would like to create an animation that is a sine wave with an unchanging amplitude, and I'd like to animate only the phase of it. I tried it with the animation curve modifier "built-in function" but it does not let me animate the phase multiple.

The script:

enter image description here

  • 1
    $\begingroup$ I am posting an incomplete answer to clarify your need. Could you please have a look ? Meanwhile, I will try to derive the math for the Speed variation minimizing jump between frames. $\endgroup$ Dec 9, 2023 at 18:09
  • 1
    $\begingroup$ Heroic effort by Stef to calculate it mathematically, but I think a very simple solution is to maintain state. In the past it was suggested to create a custom driver function that stores previous values, but nowadays I think Geometry Nodes' Simulation Zone is the answer: blender.stackexchange.com/a/301538/60486 (see the 2nd last GIF for changing a sine wave during animation) and by using a vertex hook you can transfer that data to a driver if you really need. $\endgroup$ Dec 10, 2023 at 23:42
  • $\begingroup$ @MarkusvonBroady Thanks for the reply. I managed to make a setup that can be used to make the animation I wanted, but I'm stuck on the vertex hook part. Could you elaborate on that, or point me to the right direction on where to look it up? $\endgroup$ Dec 11, 2023 at 19:26
  • 1
    $\begingroup$ Here's an example: blender.stackexchange.com/a/294231/60486 $\endgroup$ Dec 11, 2023 at 19:31

2 Answers 2


(Incomplete answer, mathematical retro-engineering to clarify the need)

Initial edition:
Stripped of extra parenthesis, the recast expression looks like:
$\frac{1}{2} \left\{ \left[ \cos{\left( \frac{1}{2} \times \textrm{frame} \times \textrm{Speed} + \textrm{Offset} \right)} +1 \right]\times \textrm{Walking} + \left(1-\textrm{Walking}\right) \right\}$

Assuming $\textrm{Offset}=0$ and $\textrm{Walking}=1$, it is reduced to:
$\frac{1}{2} \left\{ \cos{\left( \frac{1}{2} \times \textrm{frame} \times \textrm{Speed} \right)} +1 \right\}$

As is written in the question, $\frac{1}{2} \left\{ \cos{(x)} + 1 \right\}$ is cycling between 0 and 1. Furthermore, it appears that $\textrm{Speed}$ is homogenous to radian per frame. Assuming $\textrm{Speed}=0.901$ yields a period of about 14 frames (13.95 exactly, if frame count were not integer), because $\frac{1}{2} \times 14 \times 0.901 \simeq 2\pi$.

Changing/animating the $\textrm{Speed}$ will yield a discontinuity if the variation between $(\textrm{frame})$ and $(\textrm{frame}+1)$ at constant $\textrm{Speed}$ is very different from the variation between $(\textrm{frame})$ and $(\textrm{frame}+1)$ with the $\textrm{Speed}$ incrementation.

First edition:
Introducing $\omega = \frac{1}{2} \textrm{Speed}$ and $t = \textrm{frame}$, function $f_t(t, \omega)$ is defined as:
$f_t(t, \omega) = \frac{1}{2} \left\{ \cos{\left( \omega t \right)} +1 \right\}$

Its differential is:
$df_t(t, \omega) = -\frac{1}{2} \sin{\left( \omega t \right)} \times d(\omega t)$

So its relative variation is:
$\frac{df_t}{f_t} = -\frac{\sin{\left( \omega t \right)}}{\cos{\left( \omega t \right)} +1 } \times d(\omega t)$

If $\textbf{Speed}$ is constant, $\omega$ also and $d(\omega t) = \omega dt$ is constant.
This is illustrated in the next figure, where the switch between $\omega_0$ yielding 25 frames per period and $\omega_1$ yielding 50 frames per period (green curve) is triggered at frame $t_0 = t_1 = 56$. Blue filled circles are for $f_t$ while blue empty circles are for $d(\omega t)$. Heaviside It is to notice the discontinuity of $f_t$ at $t=t_0$, induced by the jump of $\omega$ from $\omega_0$ to $\omega_1$. This flaw can by corrected by adjusting the phase.
To do so, introducing $\theta = t - t_0$ and $\varphi = \omega_0 t_0$, function $f_\theta(\theta, \omega)$ is defined as:
$f_\theta(\theta, \omega) = \frac{1}{2} \left\{ \cos{\left( \omega \theta + \varphi \right)} +1 \right\}$

Rewriting $f_\theta$ as a function of $t$ instead of $\theta$ leads to:
$f_\theta(t, \omega) = \frac{1}{2} \left\{ \cos{\left( \omega t + \left[ \omega_0 - \omega \right] t_0 \right)} +1 \right\}$

For $t < t_0$, $\omega = \omega_0$ and $f_\theta$ is identical to $f_t$.
For $t > t_0$, $\omega = \omega_1$ and the difference between $f_\theta$ and $f_t$ is the constant phase shift $\left[ \omega_0 - \omega_1 \right] t_0$.

This correction is illustrated in the previous figure with red symbols. Cross signs are for $f_\theta$ while plus sign are for $d(\omega \theta)$. Compared to blue filled circles, red crosses show a continuous variation.
It is to notice that $d\theta = dt$, so $d(\omega \theta) = d(\omega t)$ when $\omega$ is constant.

As a conclusion, in the original expression, the parameter $\textrm{Offset}$ can by triggered at the same time as $\textrm{Speed}$ if a Heaviside function is used to control $\textrm{Speed}$:

  • if $t < t_0$ : $\textrm{Speed} = 2 \omega_0$, $\textrm{Offset} = 0$.
  • if $t > t_0$ : $\textrm{Speed} = 2 \omega_1$, $\textrm{Offset} = \left[ \omega_0 - \omega_1 \right] t_0$.

If $\textbf{Speed}$ is a function of $\mathbf{t}$, $\omega$ also and $d(\omega t) = \omega dt + t d\omega$ is not constant.
Assuming $\omega$ is a linear function of $t \in [t_0, t_1]$ yields:
$\omega(t) = \omega_0 + \alpha (t-t_0)$ with $\alpha = \frac{\omega_1 - \omega_0}{t_1-t_0}$.
$d(\omega t) = \left\{ (\omega_0 - \alpha t_0) + 2 \alpha t \right\} dt$
is a linear function of $t$ also.

This is illustrated in the next figure, where $\omega$ varies linearly from $\omega_0$ at $t_0=56$ to $\omega_1$ at $t_1=206$. It shows that $d(\omega t)$ is not continuous and that it might exceed $\omega_0 dt$ or $\omega_1 dt$. Linear t0=56

(to be continued)

  • $\begingroup$ Yes, thank you, that is that is a good phrasing of the problem. Speed being 0.901 is just a random choice. So I have added an edit to my question too, but to simplify the problem, I'm looking for a way to create a wave with a constant amplitude, and animated wavelength. $\endgroup$ Dec 9, 2023 at 18:15
  • $\begingroup$ I wrote the condition for the Offset if a Heaviside function is used to jump between two Speeds. In this case, the expression is continuous, but not its derivative. For that, a smooth transition is required. I am still looking for something to work properly... I will document my tests. $\endgroup$ Dec 10, 2023 at 23:22
  • $\begingroup$ Thanks you for this detailed explanation, this is way over my math understanding but I'll try to wrap my head around it! $\endgroup$ Dec 11, 2023 at 18:07

Under work...
(Using Blender 3.6.5 and Geometry Nodes)


Requirements To achieve

1 - Animate a sine wave:
- of constant amplitude,
- with a smooth transition between two wavelengths.
2 - Use it as a Driver.
3 - Bonus: Use the Graph Editor to control the transition.



Preliminary remarks

Stripped of extra parenthesis and assuming $\textrm{Walking}=1$, the recast expression reads:
$$f(\textrm{frame}) = \frac{1}{2} \left\{ \cos{\left( \frac{1}{2} \times \textrm{frame} \times \textrm{Speed} + \textrm{Offset} \right)} + 1 \right\} \label{eq_f_Frame} \tag{1}$$ So $\textrm{Speed}$ is homogenous to radian per frame.
Defining the time $t=\textrm{frame}$ in frames, the angular frequency $\omega=\frac{1}{2} \textrm{Speed}$ in radian per frame and the phase shift $\varphi=\textrm{Offset}$ in radian, Equation (\ref{eq_f_Frame}) is written: $$f(t) = \frac{1}{2} \left\{ \cos{\left(\omega t+ \varphi \right)} + 1 \right\} \label{eq_f_t} \tag{2}$$ Defining the phase $\theta=\omega t+ \varphi$ in radian, Equation (\ref{eq_f_t}) is written: $$f(\theta) = \frac{1}{2} \left\{ \cos{\theta} + 1 \right\} \label{eq_f_theta} \tag{3}$$ So for an infinitesimal variation of $\theta$, the variation of $f$ is: $$df = -\frac{1}{2} \sin{\theta} \ d\theta \label{eq_df} \tag{4}$$

Mathematical analysis

Regularity of $f$ and $df$

From Equation ($\ref{eq_f_theta}$), $f$ is continuous if $\theta$ is continuous. From equation ($\ref{eq_df}$), the variation of $f$ is smooth if $d \theta$ is continuous.
It is to notice that: $$ d\theta = \omega dt + t d\omega + d\varphi \label{eq_dtheta_phi} \tag{5}$$ Assuming that $\varphi(t)$ is a constant piecewise function yields $d\varphi=0$ and: $$d\theta = \omega dt + t d\omega \label{eq_dtheta_dt} \tag{6}$$ So $d\theta$ is continuous even if $\omega(t)$ is discontinuous as long as the right-hand side of Equation ($\ref{eq_dtheta_dt}$) is continuous, meaning that $d\omega$ is discontinuous too.

Relation between time and phase

Let introduce $T(t)$ such that: $$ \frac{T}{dt} = \frac{2\pi}{d\theta} \label{eq_T} \tag{7}$$ If $T$ were constant and $dt$ were assimilated to a constant time step, $d\theta$ would be constant too. The left-hand side would be the number of frames required to span one cycle, while the right-hand side would be the number of angular samples required to span one cycle.
So $T$ is the apparent number of frames per cycle at time $t$.
Rewriting Equation ($\ref{eq_T}$) as: $$ \frac{d\theta}{2\pi} = \frac{dt}{T} \label{eq_dtheta} \tag{8}$$ states that between two adjacent frames, the fraction of a round in phase is equal to the fraction of a period in time.

General formulation of $\theta(t)$

As a conclusion, $T(t)$ is the wavelength in frames, same as the period in time, that is assumed to vary continuously.
By integration of $d\theta$ from Equation ($\ref{eq_dtheta}$), defining $\theta_0$ as the phase at $t=0$, the general expression of $\theta(t)$ is: $$\theta(t) = \theta_0 + 2 \pi \int_{0}^{t} \frac{d\tau}{T(\tau)} \label{eq_theta_integral} \tag{9}$$ Equation ($\ref{eq_theta_integral}$) combined with the continuity of $T(t)$ guaranties that $\theta$ and $d\theta$ are continuous.

Analytical solutions for $\omega(t)$ and $\varphi(t)$

Given the analytical expression of $T(t)$, Equation ($\ref{eq_theta_integral}$) yields $\theta(t)$, then $\omega(t)$ and $\varphi(t)$.
Let $t_L$ and $t_R$ be two times such that $t_L < t_R$. Let $T_L$ (resp. $T_R$) be the period for $t \leq t_L$ (resp. for $t \geq t_R)$. So: $$ \left\{ {\begin{array}{llll} \forall t, t \leq t_L, & \omega(t)=\omega_L=\frac{2 \pi}{T_L}, & d\omega = 0, & d\theta = \omega_L dt \\ \forall t, t \geq t_R, & \omega(t)=\omega_R=\frac{2 \pi}{T_R}, & d\omega = 0, & d\theta = \omega_R dt \end{array}} \right. \label{eq_T_L_T_R} \tag{10}$$ In what follows, upper-script $[]^{-}$ means the limit by lower value of $t$, while upper-script $[]^{+}$ means the limit by higher value of $t$.

Expression of $\varphi(t)$

Assuming that the phase shift $\varphi_L^{-}$ is know and constant for $t \leq t_L$, the continuity of $\theta$ for $t=t_L$ leads to: $$ {\begin{array}{lrcl} \mbox{} & \theta(t_L^{-}) & = & \theta(t_L^{+}) \\ \Leftrightarrow & \omega(t_L^{-}) \ t_L + \varphi_L^{-} & = & \omega(t_L^{+}) \ t_L + \varphi_L^{+} \\ \Leftrightarrow & \varphi_L^{+} & = & \varphi_L^{-} + \left[ \omega_L - \omega(t_L^{+}) \right] \ t_L \end{array}}\tag{11}$$ While the continuity of $\theta$ for $t=t_R$ leads to: $$ \varphi_R^{+} = \varphi_R^{-} + \left[ \omega(t_R^{-}) - \omega_R \right] \ t_R \label{eq_phi_R} \tag{12}$$ It is to notice that if $\omega$ is not continuous at $t=t_L$ or $t=t_R$, $\varphi$ is discontinuous too.
So whatever the expression of $\omega(t)$ for $t_L \lt t \lt t_R$ is, the phase shift $\varphi(t)$ is defined by: $$\left\{ {\begin{array}{llll} \forall t, t \leq t_L, & \varphi(t)= \varphi_L^{-} \\ \forall t, t_L \lt t \lt t_R, & \varphi(t)= \varphi_L^{+} = \varphi_R^{-} \\ \forall t, t \geq t_R, & \varphi(t)= \varphi_R^{+} \\ \end{array}} \right. \label{eq_phi_t} \tag{13}$$

Case: Step variation of $T(t)$

Assuming that $t_L=t_R=t_{LR}$ leads to the Heaviside step function for $\omega(t)$. $\theta(t)$ is continuous at $t=t_{LR}$, but $d\theta$ is not because $d\theta^{-}=\omega_L dt$ while $d\theta^{+}=\omega_R dt$.
$\omega(t)$ and $\varphi(t)$ are defined by: $$ \left\{ {\begin{array}{lll} \forall t, t \lt t_{LR}, & \omega(t)=\omega_L, & \varphi(t) = \theta_0 \\ \forall t, t \gt t_{LR}, & \omega(t)=\omega_R, & \varphi(t) = \theta_0 + \left[ \omega_L - \omega_R \right] \ t_{LR} \end{array}} \right. \label{eq_Heaviside} \tag{14}$$

This is illustrated in the next figure with $\theta_0=0$, where the switch between $T_L$ yielding 25 frames per period and $T_R$ yielding 50 frames per period is triggered between frames $t_L=59$ and $t_R=60$ (indicated with purple vertical lines). In the top sub-figure, $f(t)$ is drawn with blue plus signs and $T(t)$ with red solid line. $f(t)$ is continuous, but as expected, the period is not. In the bottom sub-figure, $d\theta$ normalised by $\omega_L dt$ is plotted with red dotted line, to compare to $\omega(t)$ in blue solid line. As expected, it is equal to $\omega_L dt$ for $t \lt t_L$, and to $\omega_R dt$ for $t \gt t_R$, with a discontinuity between. $\varphi(t)$ in green solid line is also discontinuous.

Step variation of T(t)

Case: Linear variation of $T(t)$

Assuming that for $t_L \lt t \lt t_R$, $T(t)=a_0+a_1 \ t$, and that for $t \leq t_L$, $\varphi(t) = \theta_0$, Equation ($\ref{eq_theta_integral}$) reads: $$\theta(t) = \theta_0 + 2 \pi \int_{0}^{t_L} \frac{d\tau}{T_L} + 2 \pi \int_{t_L}^{t} \frac{d\tau}{a_0+a_1 \tau} \label{eq_theta_integral_Linear} \tag{15}$$ It leads to: $$\theta(t) = \theta_0 + \omega_L \ t_L + \frac{2 \pi}{a_1} \ln{ \left[ \frac{a_1}{T_L} (t-t_L) + 1 \right]} \label{eq_theta_Linear} \tag{16}$$ So for $t_L \lt t \lt t_R$, $\omega(t)$ and $\varphi(t)$ are defined by: $$ \left\{ {\begin{array}{lcl} \omega(t) & = & \frac{1}{t} \frac{2 \pi}{a_1} \ln{ \left[ \frac{a_1}{T_L} (t-t_L) + 1 \right]} \\ \varphi(t) & = & \theta_0 + \omega_L \ t_L \end{array}} \right. \label{eq_omega_phi_linear} \tag{17}$$ It is to notice that $\omega(t_L^{+})=0$, so $\omega(t)$ is discontinuous.
$a_1$ is defined as: $$ a_1 = \frac{t_L - t_R}{T_L - T_R} \label{eq_a1} \tag{18}$$

This is illustrated in the next figure, where the linear transition between $T_L$ yielding 25 frames per period and $T_R$ yielding 50 frames per period is occurring between frames $t_L=59$ and $t_R=250$. In the top sub-figure, $f(t)$ in blue and $T(t)$ in red are continuous. In the bottom sub-figure, $d\theta$ in red is continuous too, while $\omega(t)$ in blue and $\varphi(t)$ in green are not. It is to notice the log-shape of $\omega(t)$ for $t \in [t_L, t_R]$, and the discontinuity of its derivative at $t=t_L$ and $t=t_R$.

Linear variation of T(t)


Tool box

Computation of $f(\theta)$

f(theta) computation

This graph computes $f(\theta)$ following Equation ($\ref{eq_f_theta}$): $$f(\theta) = \frac{1}{2} \left\{ \cos{\theta} + 1 \right\}$$

Computation of $\theta(t)$ from $T(t)$ controlled by the Graph Editor

Compute theta from the Graph Editor

This graph computes $\theta(t)$ by discretization of Equation ($\ref{eq_theta_integral}$): $$\theta(t) = \theta_0 + 2 \pi \int_{0}^{t} \frac{d\tau}{T(\tau)}$$ as the accumulation inside a Simulation Zone of: $$ \theta^{n+1} = \theta^{n} + d\theta^{n} \ \mathrm{with} \ d\theta^{n}=\frac{2 \pi}{T^{n}}$$ assuming that the animation of the Graph Editor is providing $T^n$ at frame $n$ and that the increment $d\tau$ between two adjacent frames is equal to 1.

1. The Simulation Input node provides $\theta^n$ and $d\theta^n$.
2. $\theta^{n+1}$ is computed from $\theta^{n}$ and $d\theta^{n}$.
3. $d\theta^{n+1}$ is computed from $T^{n+1}$.
4. $\theta^{n+1}$ and $d\theta^{n+1}$ are stored in the Simulation Output node.
5. At initialization, $\theta^{-1}=\theta_0$ and $d\theta^{-1}=0$ are input such that $\theta^0=\theta_0$.
6. To limit truncation errors, $\theta$ is kept in the interval $[0, 2 \pi]$.

Computation of $\theta(t)$ for the step variation of $T(t)$

Compute theta from Heaviside function This graph computes $\theta(t)$ from $\omega(t)$ and $\varphi(t)$ at frame $n$.

1. $\omega_L$ and $\omega_R$ are computed from Equation ($\ref{eq_T_L_T_R}$).
2. $\omega^n$ and $\varphi^n$ are computed from Equation ($\ref{eq_Heaviside}$).
3. $\theta^n$ is computed as $\theta^n = \omega^n t^n + \varphi^n$.

Computation of $\theta(t)$ for the linear variation of $T(t)$

This part is divided into 3 groups.

Compute omega with log-expression This graph computes $\omega(t)$ following Equation ($\ref{eq_omega_phi_linear}$): $$ \omega(t) = \frac{2 \pi}{a_1 t} \ln{ \left[ \frac{a_1}{T_L} (t-t_L) + 1 \right]} $$

Compute coefficients This graph computes various constant coefficients.

1. $a_1$ is computed from Equation ($\ref{eq_a1}$).
1. $\varphi(t_L^+)=\varphi(t_R^-)$ is computed from Equation ($\ref{eq_omega_phi_linear}$).
1. $\varphi(t_R^+)$ is computed from Equation ($\ref{eq_phi_R}$).

Compute theta for a linear variation of T This graph computes $\theta(t)$ from $\omega(t)$ and $\varphi(t)$ at frame $n$.

1. $\omega_L$ and $\omega_R$ are computed from Equation ($\ref{eq_T_L_T_R}$), and $\omega(t)$ from Equation ($\ref{eq_omega_phi_linear}$), to determine $\omega^n$.
2. $\varphi^n$ is computed from Equation ($\ref{eq_phi_t}$).
3. $\theta^n$ is computed as $\theta^n = \omega^n t^n + \varphi^n$.

Driver based on Graph

Driver based on Graph This picture shows the Geometry Nodes graph coding $f(t)$ as the position of a vertex, and how it is controlled through the Graph Editor.

0. In Object Mode, a mesh made of a single vertex is added, using the Add Mesh Extra Objects Add-ons. Then it is equipped with a GeometryNodes modifier.
1. Keyframes are added to control the $T(t)$ input socket. The shape of the curve $T^n$ as a function of the current frame number $n$ is adjusted in the Graph Editor.
2. $\theta_0$ and $T^n$ are transferred through the Group Input node.
3. $\theta^n$ and $f^n=f(\theta^n)$ are computed using the tool box.
4. The vertex is displaced to the position $(f^n, 0, 0)$. So $f^n$ is exposed outside of Geometry Nodes as the vertex $X$ coordinate.

Driver based on algebra

How to setup as a Driver

How to debug


Blender file



You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .