WaveshapeWorkedExample.kt
src/jvmMain/kotlin/afield/examples/functions/WaveshapeWorkedExample.kt · 162 lines
package afield.examples.functions
import afield.math.functions.Functions
import afield.math.functions.graphSignature
FIELD_TEST id: functions.waveshape.worked-example runner: jvm-main entrypoint: afield.examples.functions.WaveshapeWorkedExample tags: [smoke, examples, functions, waveshape, cpu] timeoutSec: 30 expect: exitCode: 0 stdoutContains: - "waveshape worked example passed"
object WaveshapeWorkedExample
A CPU-only reproducibility check for the waveshaping / distortion transform family
(fold / rectify / quantize / crush / chebyshev / clip / softClip /
clamp / wrapY).
There is no GPU, no Vulkan, no Field window and — deliberately — no file output.
It sweeps a rising test ramp -2..2 through each transform so every result reads as
that transform's transfer curve (the shape it imposes on its input), samples each
curve across the domain, and returns the sampled cells so main and the JUnit test
can assert on the actual shaping geometry.
Everything is a deterministic pure function of the source signal, so runExample
reproduces bit-for-bit across runs — the property the doc walkthrough claims. The
committed docs figure is a live capture="box" FLine plot generated by the
docs-capture pipeline, not an asset written here (we do not write document assets
from code that isn't in the document system).
object WaveshapeWorkedExample {
const val SAMPLES: Int = 240
data class Cell
One transfer-curve cell: the transform's label, the graph fn, and the sampled
(xs, ys) of that fn across domain.
data class Cell(
val label: String,
val fn: Functions.Fn,
val domain: Functions.Domain,
val xs: DoubleArray,
val ys: DoubleArray
)
data class Result(val cells: List<Cell>)
fun runExample(samples: Int = SAMPLES): Result
Build the sweep, push it through every shaper, and sample each transfer curve. Pure and deterministic — no I/O — so two calls return identical numbers.
fun runExample(samples: Int = SAMPLES): Result = with(Functions) {
val domain = Functions.Domain.of(0.0, 1.0)
// A ramp sweeping -2..2 across the domain: plotting a transform of it against x
// draws its transfer curve directly.
val sweep = (ramp(-2.0, 2.0) over domain).withName("sweep -2..2")
// Keep this list in lockstep with the `diffstatic_runner` snippet in
// functions_examples.md so the CPU check and the live docs figure stay aligned.
val specs: List<Pair<String, Functions.Fn>> = listOf(
"input sweep -2..2" to sweep,
"fold(0.7)" to (sweep / fold(0.7)),
"rectify(FULL)" to (sweep / rectify(Functions.RectifyMode.FULL)),
"rectify(HALF)" to (sweep / rectify(Functions.RectifyMode.HALF)),
"quantize(6)" to (sweep / quantize(6)),
"crush(3)" to (sweep / crush(3)),
"chebyshev(3)" to (sweep / chebyshev(3)),
"clip(0.8)" to sweep.clip(0.8),
"softClip(3.0)" to sweep.softClip(3.0),
"clamp(-0.8, 0.8)" to sweep.clamp(-0.8, 0.8),
"wrapY(-0.8, 0.8)" to sweep.wrapY(-0.8, 0.8)
)
val n = samples.coerceAtLeast(2)
val cells = specs.map { (label, fn) ->
val xs = DoubleArray(n) { domain.min + domain.length * it / (n - 1).toDouble() }
val ys = DoubleArray(n) { i -> fn.evalSafe(xs[i], outside = Double.NaN) }
Cell(label, fn, domain, xs, ys)
}
Result(cells)
}
private fun Result.cell(label: String): Cell =
cells.firstOrNull { it.label == label } ?: error("no cell '$label'")
fun main(args: Array<String>)
body — 72 lines, from line 90
@JvmStatic
fun main(args: Array<String>) {
val result = runExample()
val eps = 1e-9
// Diagnostic samples so the shaping is auditable on the console too.
val sweep = result.cell("input sweep -2..2")
val fold = result.cell("fold(0.7)")
val rectFull = result.cell("rectify(FULL)")
val quant = result.cell("quantize(6)")
println("waveshape worked example: ${result.cells.size} transfer curves, ${SAMPLES} samples each")
println("fold(0.7) / rectify(FULL) / quantize(6) over sweep -2..2:")
for (i in 0..10) {
val idx = (i * (SAMPLES - 1)) / 10
println(" x=${"%.2f".format(sweep.xs[idx])} in=${"%.3f".format(sweep.ys[idx])} " +
"fold=${"%.3f".format(fold.ys[idx])} " +
"rectFull=${"%.3f".format(rectFull.ys[idx])} " +
"quant6=${"%.3f".format(quant.ys[idx])}")
}
// Correctness of the shaping geometry — each curve must actually do what it claims.
fun bounded(label: String, lo: Double, hi: Double) {
val ys = result.cell(label).ys.filter { it.isFinite() }
check(ys.isNotEmpty()) { "$label produced no finite samples" }
check(ys.all { it in (lo - eps)..(hi + eps) }) {
"$label escaped [$lo, $hi]: min=${ys.min()} max=${ys.max()}"
}
}
// The sweep really spans -2..2.
check(sweep.ys.first() <= -2.0 + 1e-6 && sweep.ys.last() >= 2.0 - 1e-6) {
"sweep endpoints ${sweep.ys.first()}..${sweep.ys.last()} are not -2..2"
}
bounded("fold(0.7)", -0.7, 0.7)
bounded("clip(0.8)", -0.8, 0.8)
bounded("clamp(-0.8, 0.8)", -0.8, 0.8)
bounded("wrapY(-0.8, 0.8)", -0.8, 0.8)
bounded("chebyshev(3)", -1.0, 1.0) // clamps its drive to [-1, 1], so Tₙ stays in [-1, 1].
// Rectifiers are non-negative; FULL is |input|.
val rectFullYs = rectFull.ys
check(rectFullYs.all { !it.isFinite() || it >= -eps }) { "rectify(FULL) went negative" }
for (i in sweep.xs.indices) {
val expected = kotlin.math.abs(sweep.ys[i])
check(kotlin.math.abs(rectFullYs[i] - expected) < 1e-6) {
"rectify(FULL) != |input| at i=$i: ${rectFullYs[i]} vs $expected"
}
}
check(result.cell("rectify(HALF)").ys.all { !it.isFinite() || it >= -eps }) {
"rectify(HALF) went negative"
}
// Quantize/crush stay within the input's own range (staircases of the input).
val inLo = sweep.ys.min(); val inHi = sweep.ys.max()
bounded("quantize(6)", inLo, inHi)
bounded("crush(3)", inLo, inHi)
// Every cell must have finite samples (nothing degenerated to all-NaN).
for (c in result.cells) {
check(c.ys.any { it.isFinite() }) { "cell '${c.label}' has no finite samples" }
}
// Determinism: a second run reproduces every sample and every graph signature.
val again = runExample()
check(again.cells.size == result.cells.size) { "cell count changed across runs" }
for ((a, b) in result.cells.zip(again.cells)) {
check(a.label == b.label) { "cell order changed: ${a.label} vs ${b.label}" }
check(a.ys.contentEquals(b.ys)) { "cell '${a.label}' samples not reproducible" }
check(a.fn.graphSignature() == b.fn.graphSignature()) {
"cell '${a.label}' graph signature not reproducible"
}
}
println("waveshape worked example passed")
}
}