OPENRNDR & Processing - Additive wave example

Additive wave example

2021-01-29-124047_640x360_scrot

Following previous posts in which I compared OPENRNDR and Processing code, this time I “reinterpreted” the next Processing example:

Processing / Java

You can click “show original” above to compare it with the following code:

OPENRNDR / Kotlin

imports
import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.extra.noise.Random
import org.openrndr.shape.Circle
import kotlin.math.PI
import kotlin.math.sin
private data class SineWave(val freq: Double, val shift: Double, val amp: Double) {
    fun value(t: Double, x: Double) = amp * sin(t * freq + shift * x)
}
fun main() = application {
    program {
        val numPoints = 80
        val sineWaves = List(5) { SineWave(-5 rnd 5, 0 rnd PI * 2, 10 rnd 30) }
        extend {
            drawer.clear(ColorRGBa.BLACK)
            drawer.fill = ColorRGBa.WHITE.opacify(0.3)
            drawer.stroke = null
            drawer.circles(List(numPoints) { num ->
                val x = width * num / (numPoints - 1.0)
                val y = height * 0.5 + sineWaves.sumOf {
                    it.value(seconds, x * 0.01)
                }
                Circle(x, y, 10.0)
            })
        }
    }
}
private infix fun Number.rnd(max: Number) =
    Random.double(this.toDouble(), max.toDouble())

The resulting animated-additive-sine wave looks similar but it’s not a literal translation, as I wanted to write something simpler and making use of Kotlin’s features.

Differences

Observe how the original code uses 5 for loops and the Kotlin code uses none.

Purpose Processing OPENRNDR
initialize variables for loop replaced by List(5){...}
set variables to zero and accumulate wave height 3 for loops replaced by one call to List(numPoints) and .sumOf used to accumulate the wave height at each point
render all the circles for loop replaced by OPENRNDR’s drawer.circles() method which takes a list of Circle instances

The original code toggles between sine and cosine, but I found that unnecessary since sine is basically a shifted cosine :slight_smile: Instead, I created a data class called SineWave which has three properties: frequency, shift and amplitude. I included a value() method in that class which takes time and horizontal-position-in-the-waveform as arguments and returns the vertical shift.

Note the rnd infix function at the bottom. It allows me to write

SineWave(-5 rnd 5, 0 rnd PI * 2, 10 rnd 30)

instead of the longer version

SineWave(
  Random.double(-5.0, 5.0), 
  Random.double0(PI * 2), 
  Random.double(10.0, 30.0)
)

Finally, see how the original code uses 7 “global” variables", a few of them used to express one idea: a collection of sine waves. Kotlin makes it very easy to create data classes, basically in one line of code, grouping related properties together under one object. I used it to try to make it more obvious that we are working with a List of SineWaves.

Closing thoughts

As it happens most of the time, I find the Kotlin code beautiful and easier to understand. The program is called AdditiveWave and it has one line that reads sineWaves.sumOf perfectly matching the program name. The Java code feels lower level and needs comments to clarify intentions. It is possible that the lower level code is better for learning or teaching computation, but at least in my daily work I find Kotlin more understandable and easier to work with. I can express myself with less effort and when I look at my previously written OPENRNDR programs I spend less time trying to understand what they do.

ps. Modern Java supported in the coming Processing 4 may allow simplifying the original program too. Feel free to share how that might look like.
ps 2. Don’t use this code to run self driving vehicles :upside_down_face: I’m only learning and sharing what I learn.

:point_down: Share your questions and comments below | :mag_right: Find other OPENRNDR & Processing posts

5 Likes