New math function 'wave()'

For next version G’MIC 3.5.4, I’ve introduced a new math function wave(x,type,period,amplitude) which eases the creation of waveforms.

These two simple examples shows what it does: it defines a function that model a periodic 1D function:

$ gmic 1024,1,1,4,"wave(x,c,128)" s c dg 500,300 append_tiles ,

The type argument allows to choose the wave shape : 0: Square , 1: Triangle , 2:Sawtooth and 3: Sine .
The two other arguments set the period and amplitude of the desired signal.

This function will be useful in a number of situations, for instance to distort an image:

foo :
  sp colorful,256
  100%,100%,1,2,"[ wave(y,3,64,20),0 ]"
  warp.. .,1
  to.. "Sine"

At the end, it should be more simple to use than custom sin() or worse, custom-made triangular functions. If you have already tried to define your own triangle waveform, you’ll be happy ! :slight_smile:

New pre-release binaries including this features have been released this morning.
Let me know if you have ideas to improve this.

3 Likes

Another example:

foo :
  repeat 4 { 200,200,10,1,"r = hypot(x - w/2,y - h/2); wave(r - 3*z,$>,30,255)" }
  a x map 16
  s z o anim_waves.gif,15

generates this animation, where you can see the different types of waveforms:

anim_waves-ezgif.com-optimize

Reminds me this, but with four eyes :sweat_smile:

hypno-snake

This would simplify modular_formula which is a custom command I did. Just look at the complex logic there.

Also, shouldn’t the second and third mode be swapped to fit the neumann, periodic, continuous convention?

They probably used G’Mic too back in the day.

Any chance of introducing irregularity?

With function composition?

$ gmic   400,1,1,1,"wave(wave(x,1,130,130) + 5*u(-1,1),2,200)" plot

1 Like

Phase (as in shifting the periodic 27° or -34.756124°) ?
Or shall we -shift periodic the waveform image slightly?

You should have launched a challenge thread before releasing this!

EDIT:
Phase?..
Ah… through the first argument, which can be a full-bore math expression, wave(x+<phase shift expression>,…) no?

Yes, exactly. Phase can be added in the first argument.
Actually, the period and amplitude arguments could be also discarded, as they can appear as amplitude*wave(x/period). But I thought it was still quite nice to have them as arguments.
Which means, phase could be an argument too. But I don’t know why I haven’t thought of this one.

OK, so that’s a real question.
What arguments should I keep ?

Maybe it’s just better to have a minimal version of wave with two arguments only wave(x,type) and the others can be added explicitly as in amplitude*wave(x/period,type) .

Just the first few arguments would be enough to express with basic math. You could have angle adjustment as an argument for third or as second when denoting with degree. Anything about changing type order to fit convention?

Discarding amplitude/using it as a multiplier flatlines here. git-synced with develop branch about 14:15 UTC

gosgood@lydia ~ $ gmic run '128,1,1,1,"[100*wave(x)]"'
[gmic]./ Start G'MIC interpreter (v.3.5.4).
[gmic]./run/__run/ Input image at position 0, with values '[100*wave(x)]' (1 image 128x1x1x1).
[gmic]./ Display image [0] = '[[100*wave(x)]]'.
[0] = '[[100*wave(x)]]':
  size = (128,1,1,1) [512 b of float32].
  data = (0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0).

Using the amplitude argument seems necessary with this build:

$ gmic run '128,1,1,1,"wave(x,0,w,255)"'
[gmic]./ Start G'MIC interpreter (v.3.5.4).
[gmic]./run/__run/ Input image at position 0, with values 'wave(x,0,w,255)' (1 image 128x1x1x1).
[gmic]./ Display image [0] = '[wave(x,0,w,255)]'.
[0] = '[wave(x,0,w,255)]':
  size = (128,1,1,1) [512 b of float32].
  data = (255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255,-255).
  min = -255, max = 255, mean = 0, std = 256.002, coords_min = (64,0,0,0), coords_max = (0,0,0,0).

Argument 3 (being called ‘period’, a measure in time) seems to work more like ‘wavelength’ measured in distance (Welcome to ‘English’ We invented the language to aggravate the French).
See my second example above. To get a one period square wave (the relative time it takes to travel 128 pixels), amplitude 255, I needed an argument three value of ‘w (equal to 128 pixels, a distance)’.
Example: American amateur radio has an “80 meter” wavelength band. Exactly one period (cycle) of that radiation travels 80 meters of distance.

This is how I actually get a period 4 signal:

$ gmic run '128,1,1,1,"wave(x,3,w/4,100)"'
[gmic]./ Start G'MIC interpreter (v.3.5.4).
[gmic]./run/__run/ Input image at position 0, with values 'wave(x,3,w/4,100)' (1 image 128x1x1x1).
[gmic]./ Display image [0] = '[wave(x,3,w/4,100)]'.
[0] = '[wave(x,3,w/4,100)]':
  size = (128,1,1,1) [512 b of float32].
  data = (0,19.509,38.2683,55.557,70.7107,83.147,92.388,98.0785,100,98.0785,92.388,83.147,70.7107,55.557,38.2683,19.509,1.22465e-14,-19.509,-38.2683,-55.557,-70.7107,-83.147,-92.388,-98.0785,-100,-98.0785,-92.388,-83.147,-70.7107,-55.557,-38.2683,-19.509,0,19.509,38.2683,55.557,70.7107,83.147,92.388,98.0785,100,98.0785,92.388,83.147,70.7107,55.557,38.2683,19.509,1.22465e-14,-19.509,-38.2683,-55.557,-70.7107,-83.147,-92.388,-98.0785,-100,-98.0785,-92.388,-83.147,-70.7107,-55.557,-38.2683,-19.509,0,19.509,38.2683,55.557,70.7107,83.147,92.388,98.0785,100,98.0785,92.388,83.147,70.7107,55.557,38.2683,19.509,1.22465e-14,-19.509,-38.2683,-55.557,-70.7107,-83.147,-92.388,-98.0785,-100,-98.0785,-92.388,-83.147,-70.7107,-55.557,-38.2683,-19.509,0,19.509,38.2683,55.557,70.7107,83.147,92.388,98.0785,100,98.0785,92.388,83.147,70.7107,55.557,38.2683,19.509,1.22465e-14,-19.509,-38.2683,-55.557,-70.7107,-83.147,-92.388,-98.0785,-100,-98.0785,-92.388,-83.147,-70.7107,-55.557,-38.2683,-19.509).
  min = -100, max = 100, mean = 0, std = 70.9885, coords_min = (24,0,0,0), coords_max = (8,0,0,0).
[gmic]./ End G'MIC interpreter.

Cycle starts at the zero crossing. As @David_Tschumperle confirmed, to phase shift, add to (or subtract from) the first argument math expression.

One of my favourite childhood movies. Memorable songs. Did not realize the colours were offset by one ring. Did you watch it en français ?

What happens if wave() is called outside of the context of a reference image (thinking of eval here)

Also one of my favourite. Incidentally, I watched the “modern” version shot in 2016 a couple of days ago, and thought it was pretty good too.

Nothing special. It is a math function as any other. But you are right, it’s probably useful only when called for multiple input values :slight_smile: (contrary to a function like abs() for instance).

I’ve just pushed the modification that makes wave() having two arguments only.
Pre-release packages are being built.

2 Likes

The fact is, if you use write wave(x), you’ll get a periodic signal with a period of 1, which is quite useless, in the case of discrete images where the minimal distance between pixels is 1 !
(the length of a pixel is the length of the wavelength).

I think I noticed that.
My rough notes on a cli build as of commit fb1f5f7abd25, gmic develop branch, yours: Apr 23 15:44:26 2025 UTC

The first argument to wave(u,c) is some math function written in terms of u=x/w , and template curve c \in \{ \text{0:square, }\text{1:triangle, } \text{2:sawtooth, } \text{3:sine } \} . It is used in fill expression contexts:

  1. The fifth, formula, argument to -input
  2. The formula argument to -fill
  3. The non-empty selection form of eval[<selection>] <formula>

wave() is syntactically correct in single evaluation math contexts, or contexts where the reference image is undefined, and samples the curve at the relative phase value p (see following for p).

Math Expresson Notes

a*wave(f*(x|y/(w|h)+p, c)
  1. i, defined on the interval 1, the relative width|height of the current reference image.
  2. Use of placeholder variables [x,w] in the math expression argument defines the interval on the width of the image; placeholder variables [y,h] defines the interval on the height of the image.
  3. a sets amplitude of the wave form. if omitted, defaults to 1.
  4. f sets the repetition frequency of the waveform in the interval i. If omitted, defaults to 1 cycle per interval.
  5. p sets the ± phase offset. A unitless value in the range [-1 ,…,1] that is defined relative to the waveform period: λ=i/f and offsets the waveform toward –∞ for +p, and +∞ for –p by a fraction of the period, keyed from the rising zero point. Could be written as a fraction: p=d°/360 for d°, a phase angle in degrees. wave((x/w)+45/360,3) is an extraordinarily roundabout way to write \frac{1}{\sqrt{2}} .
  6. c curve type. See above.

Maybe some examples later. Time to walk Vinney.

1 Like

With that notation I get a flatline. For a frequency 1 curve (one cycle per interval) I use something like:

 $ gmic run '100,1,1,1,10*wave(x/w) plot.

period 1, frequency 1 cycle per interval, amplitude 10.

$ gmic run '100,1,1,1,10*wave(4*x/w+45/360) plot.'


As above, but a frequency of four cycles per interval with a phase offset of 45°.

Placeholder variables x|y / w|h for current row|column pixel in relation to width|height seem necessary for writing the formula argument.

2 Likes

With two variables, yes.

Spotted a bug this morning, with wrong results of frac(x) when x is negative.
This has effect on wave(x) too, as wave() uses frac() internally.
Fixed pre-release packages should be available in a few minutes.

1 Like

Here in the wee hours of Brooklyn, Noo Yawk, 0bf8b8d50f00 on the develop branch, your instance: Apr 24 10:18:03 2025 UTC builds fine here.

For folks helicoptering into the discussion, further remark that the amplitude modifier may also be a function in x/w ( or y/h ). Example:

$  gmic run '100,1,1,1,5*(1+x/w)*wave(20*x/w+45/360) plot.'

Haven’t similarly tried dynamic evaluation of frequency or phase; perhaps later in the day. Time for the early morning constitutional with Vinney.

NB: I think resolution advisories are in order: the curve is pretty ragged here. To be expected with f=20 and an interval of just 100 pixels. That’s just asking for trouble…

1 Like