Skip to content

SynthCookbookCompileDemo.kt

src/jvmMain/kotlin/afield/audio/synth/SynthCookbookCompileDemo.kt · 543 lines

@file:Suppress("unused")

package afield.audio.synth

Runnable cookbook coverage for afield.audio.synth.

The old monolithic compile-only object has been split into explicit snippet blocks so the FIELD_TEST catalog can discover and execute them through tools/field_backdoor.py.

These snippets aim at stable, runtime-safe coverage of the cookbook surface. They intentionally skip examples that need machine-specific external state, such as an actual Dirt sample folder or explicit audio-engine registration.

FIELD_TEST id: synth.cookbook.oscillators-and-patches runner: backdoor-box tags: [synth, cookbook, oscillators, audio] timeoutSec: 45 expect: outputContains: - "synth oscillators ok" eventKinds: - output

source — 44 lines, from line 29
private val SYNTH_COOKBOOK_OSCILLATORS_AND_PATCHES = """
val basicOsc = sine
val s1 = SineOsc(220.p)
val s2 = SawOsc(55.p)
val s3 = PulseOsc(freq = 110.p, width = 0.2.p)
val layeredA = saw + saw
val layeredB = sine * 0.1f + square * 0.1f

val directSignal = saw * 0.05f
val monoPatch = audio { saw * 0.05f }
val stereoPatchA = audio { (saw * 0.1f).pan(-0.35f) }
val stereoPatchB = audio { Pan(sine * 0.08f, 0.4.p) }
val launchBundle = audio { saw * 0.05f } +
    pat("bd [hh hh] sd").respond(cps = 0.5) { event ->
        println("bundle " + event.value)
    }
val launchList = listOf(
    audio { tri * 0.04f },
    pat("bd [hh hh] sd").respond(cps = 0.5) { event ->
        println("list " + event.value)
    }
)

val p440 = 440.p
val p015 = 0.15.p
val p8 = 8.p
val cutoff = Param { 400f + 300f * kotlin.math.sin(time * 0.3f) }
val cutoffFiltered = saw / MoogLadder(cutoff = cutoff, resonance = 0.2.p)
val steppedParam = Param(sequenceOf(220f, 330f, 440f, 550f))
val steppedOsc = SineOsc(steppedParam)
val yieldedParam = Param {
    var t = 0
    while (true) {
        t++
        if (t > 10) t = 0
        yield(10f - t + 0.15f * kotlin.math.sin(time * 0.5f))
    }
}
val yieldedSuperSaw = SuperSaw() * yieldedParam
val modFreq = 220.p.modulate(sine * 20f)
val modulatedOsc = SineOsc(modFreq)

println("synth oscillators ok")
""".trimIndent()

FIELD_TEST id: synth.cookbook.effects-envelopes-and-input runner: backdoor-box tags: [synth, cookbook, effects, envelopes, input] timeoutSec: 45 expect: outputContains: - "synth effects ok" eventKinds: - output

source — 47 lines, from line 86
private val SYNTH_COOKBOOK_EFFECTS_ENVELOPES_AND_INPUT = """
val delayed = saw / Delay()
val reverbed = saw / FreeVerb()
val springy = saw / SpringReverb()
val convolutionIr = reverbImpulse(lengthSeconds = 0.18, decay = 0.5, diffusion = 0.7, seed = 4)
val convolved = saw / convolutionReverb(convolutionIr, mix = 0.25, maxIrSamples = 2048)
val bandPassed = white / SVF(mode = SVFMode.BP, cutoff = 1200.p, resonance = 0.6.p)
val chain = saw / MoogLadder(cutoff = 800.p) / Chorus() / springReverb()
val tunedDelay = saw / FractionalDelay(delayTime = 0.0125.p, feedback = 0.7.p, mix = 0.6.p)
val signalDivision = saw / SineOsc(2.p)

val percussiveGate = gate.also { it.open() }
val percussive = SawOsc(80.p) * AR(gate = percussiveGate, attack = 0.001.p, release = 0.15.p)

val sustainedGate = gate.also { it.open() }
val sustained = SineOsc(220.p) * ADSR(
    gate = sustainedGate,
    attack = 0.01.p,
    decay = 0.08.p,
    sustain = 0.6.p,
    release = 0.3.p
)

var heldGate = gate
heldGate.open()
val heldVoice = SineOsc(110.p) * ADSR(gate = heldGate, release = 0.5.p)
heldGate.close()

val mix = Mix(
    SawOsc(110.p),
    SawOsc(110.5.p),
    SineOsc(55.p) * 0.3f
) * 0.08f

val stereoManual = StereoSignal(
    left = saw * 0.08f,
    right = tri * 0.08f
)
val panned = Pan(white / FreeVerb(), Param(-0.2f))

val monitor = audioIn * 0.8f
val processedInput = audioIn / Delay(delayTime = 0.2.p, feedback = 0.35.p, mix = 0.4.p)
val stereoInputPatch = audio(gain = 0.9f) { audioInStereo }
val filteredInput = audio(gain = 0.7f) { audioIn / MoogLadder(cutoff = 1800.p) / FreeVerb() }

println("synth effects ok")
""".trimIndent()

FIELD_TEST id: synth.cookbook.sampling-fm-and-wavetables runner: backdoor-box tags: [synth, cookbook, sampling, fm, spectral] timeoutSec: 45 expect: outputContains: - "synth sampling ok" eventKinds: - output

source — 125 lines, from line 146
private val SYNTH_COOKBOOK_SAMPLING_FM_AND_WAVETABLES = """
val kick = AudioSample.generate(0.25f) { t ->
    kotlin.math.sin((t * 55f * 2f * kotlin.math.PI.toFloat()).toDouble()).toFloat()
}
val kickPlayback = Sampler(kick) * 0.5f

val voice = AudioSample.generate(1.0f) { t ->
    kotlin.math.sin((t * 220f * 2f * kotlin.math.PI.toFloat()).toDouble()).toFloat()
}
val cloud = GrainCloud(
    voice,
    density = 18.p,
    position = 0.35.p,
    grainSize = 0.08.p,
    pitchJitterCents = 35.p,
    grainSizeJitter = 0.025.p,
    densityJitter = 0.45.p,
    gainJitter = 0.25.p,
    direction = GrainDirection.BIDIRECTIONAL,
    envelope = GrainEnvelope.GAUSSIAN
)

val liveCloud = LiveGrainCloud(
    input = Sampler(voice, loop = true),
    bufferSeconds = 2f,
    density = 20.p,
    position = 0.4.p,
    grainSize = 0.06.p,
    grainSizeJitter = 0.02.p,
    densityJitter = 0.5.p,
    gainJitter = 0.3.p,
    direction = GrainDirection.BIDIRECTIONAL
)

val fieldRecording = AudioSample.generate(1.5f) { t ->
    kotlin.math.sin((t * 110f * 2f * kotlin.math.PI.toFloat()).toDouble()).toFloat()
}
val stereoCloud = StereoGrainCloud(
    fieldRecording,
    density = 12.p,
    spread = 0.7.p,
    pitchJitterCents = 20.p,
    stereoWidth = 0.006.p,
    stereoWidthRandom = 0.003.p,
    stereoScatter = 0.002.p,
    grainSizeJitter = 0.02.p,
    densityJitter = 0.35.p,
    gainJitter = 0.2.p,
    direction = GrainDirection.REVERSE,
    envelope = GrainEnvelope.TUKEY
)

val fmOp = FMOperator(freq = 220.p, feedbackAmount = 0.2f)
val dexedish = FMPatch.twoOp(carrierFreq = 110f, index = 3.p) * 0.08f

val ksGate = gate.also { it.open() }
val burst = white * AR(gate = ksGate, attack = 0.0005.p, release = 0.008.p)
val stringPatch = audio { burst / KarplusStrong(freq = 110.p, decay = 0.992.p, brightness = 0.6.p) }

val hammerGate = gate.also { it.open() }
val hammer = impulse * AR(gate = hammerGate, attack = 0.0001.p, release = 0.002.p) * 0.8f
val hammeredString = audio {
    hammer / KarplusStrong(freq = 220.p, pickPosition = 0.08.p, brightness = 0.75.p)
}

val basicSuperSaw = supersaw
val tunedSuperSaw = SuperSaw(freq = 55.p, detuneCents = 18.p, blend = 0.9.p)
val filteredSuperSaw = audio {
    SuperSaw(freq = 110.p, detuneCents = 24.p, blend = 0.85.p) / MoogLadder(cutoff = 1400.p)
}
val basicSuperSquare = supersquare
val movingSuperSquare = audio {
    SuperSquare(freq = 110.p, voice = 0.42.p, lfo = 0.8.p, resonance = 0.25.p) * 0.08f
}
val pwmVoice = audio {
    SuperPWM(freq = 82.p, voice = 0.18.p, secondarySweep = 0.72.p) * 0.08f
}
val comparatorVoice = audio {
    SuperComparator(freq = 55.p, voice = 0.7.p, resonance = 0.3.p) * 0.06f
}
val chipVoice = audio {
    SuperChip(freq = 110.p, pitch2 = 2.p, pitch3 = 3.01.p, voice = 0.35.p) * 0.08f
}
val noiseVoice = audio {
    SuperNoise(freq = 90.p, voice = 0.4.p, resonance = 0.2.p) * 0.12f
}
val forkVoice = audio {
    SuperFork(freq = 440.p) * 0.08f
}

val rectified = audio { saw / Rectifier(mode = RectifierMode.FULL_WAVE, mix = 0.35.p) * 0.08f }
val clipped = audio {
    sine * 3f / Rectifier(RectifierMode.HALF_WAVE) /
        SVF(mode = SVFMode.LP, cutoff = 900.p) * 0.05f
}
val folded = audio {
    sine * 4f /
        StagedWaveFolder(stages = 4, symmetry = 0.18.p, stageGain = 1.5.p, makeup = 0.4.p) *
        0.08f
}

val wt = wavetable
wt.setFrame(0, fn = { phase: Float ->
    kotlin.math.sin((phase * kotlin.math.PI.toFloat() * 2f).toDouble()).toFloat()
})
wt.setFrame(1, fn = { phase: Float -> if (phase < 0.5f) 1f else -1f })
val wtOsc = audio {
    SpectralWavetableOsc(
        wt,
        freq = 110.p,
        position = Param { 0.5f + 0.45f * kotlin.math.sin(time * 0.18f) }
    ) * 0.08f
}
val wtRecorder = audio { audioIn / SpectralWavetableRecorder(wt, position = 0.2.p, captureRate = 6.p) }
val wtPlayback = audio {
    SpectralWavetableOsc(
        wt,
        freq = 55.p,
        position = Param { 0.2f + 0.6f * kotlin.math.sin(time * 0.09f) }
    ) / FreeVerb() * 0.08f
}
val explicitWt = SpectralWavetableOsc(wt, freq = 220.p, position = 0.5.p)

println("synth sampling ok")
""".trimIndent()

FIELD_TEST id: synth.cookbook.patterns-and-notes runner: backdoor-box tags: [synth, cookbook, patterns, notes, scheduling] timeoutSec: 45 expect: outputContains: - "synth patterns ok" eventKinds: - output

source — 126 lines, from line 284
private val SYNTH_COOKBOOK_PATTERNS_AND_NOTES = """
val arp = notes {
    listOf(48, 55, 60, 64).forEachIndexed { step, pitch ->
        note(
            pitch = pitch,
            duration = 0.18,
            velocity = 0.85,
            label = "n" + step
        )
        rest(0.25)
    }
}
val arpSvg = arp.at(0.8).pianoRollSvg()

val motif = notes {
    chord(60, 64, 67, durationSeconds = 0.35, velocity = 0.7)
    rest(0.5)
    note(pitch = 72, duration = 0.2, velocity = 1.0, label = "lead")
    rest(0.25)
    note(pitch = 67, duration = 0.45, start = 1.2)
}
val clip = listOf(
    note(60, duration = 0.25, start = 0.0),
    note(64, duration = 0.25, start = 0.25),
    note(67, duration = 0.5, start = 0.5)
)
val clipSvg = clip.pianoRollSvg(nowSeconds = 0.4, futureSeconds = 1.2)

val hats = pat("bd [hh hh] sd")
val postfixOperators = pat("bd(3,8) hh!2 cp*2 lead@3")
val randomChoice = pat("[bd*4|hh*12|cp*2]")
val parameterizedAtom = pat("bd:(1,2,3) hh")
val scheduledAtoms = hats.scheduled(cps = 2.0, startAt = 0.25).take(4).toList()
val postfixSchedule = postfixOperators.scheduled(cps = 1.0).take(8).toList()
val randomChoiceSchedule = randomChoice.scheduled(cps = 1.0).take(12).toList()
val parameterizedAtomSchedule = parameterizedAtom.scheduled(cps = 1.0).take(4).toList()

val reversed = pat("a b c d").rev()
val varied = pat("a b c d")
    .every(4) { it.rev() }
    .sometimes { it.fast(2) }
    .degradeBy(0.15)

val layered = stack(
    pat("bd ~ sd"),
    pat("[hh hh hh hh]").degradeBy(0.2)
)
val euclidKick = euclidFill(3, 8, on = pat("kick")).scheduled(cps = 1.5).take(8).toList()
val held = pat("lead@3 tail").scheduled(cps = 1.0).take(3).toList()
val replicated = pat("bd!3 cp").scheduled(cps = 1.0).take(4).toList()
val densified = pat("bd*3 cp").scheduled(cps = 1.0).take(8).toList()

data class Cue(val color: String, val gain: Float)
val cues = pat("calm bright danger") { atom ->
    when (atom) {
        "calm" -> Cue("#0af", 0.15f)
        "bright" -> Cue("#fff", 0.4f)
        else -> Cue("#f80", 0.8f)
    }
}
val cueSchedule = cues.scheduled(cps = 0.5).take(3).toList()
val gaussianField = patFn(
    "g:(0,0.08) g:(1,0.05,0.7) g:(-0.5,0.12,0.4)",
    cps = 2.0,
    range = 0.5
)
val gaussianFieldDirect = pat("g:(0,0.08) g:(1,0.05,0.7)").asFn(
    cps = 2.0,
    range = 0.5
)
val shapedField = pat(
    "tent:(0.4,0.12) box:(1,0.04,0.5) wendland:(-0.6,0.18) spline:(0.25,0.1,0.8)"
).asFn(cps = 1.5, range = 0.6)
val envelopeField = pat(
    "env:(0.7,0.03,0.1) adsr:(0.4,0.01,0.08,0.5,0.2)"
).asFn(cps = 1.0, range = 0.5)
val dynamicField = pat("a b c") { atom ->
    when (atom) {
        "a" -> gaussianAtom(sigma = 0.08) { x -> kotlin.math.sin(local(x) * 12.0) }
        "b" -> gaussianAtom(sigma = 0.12) { x -> bipolarRandom() * (1.0 - normalized(x)) }
        else -> adsrAtom(attack = 0.01, decay = 0.08, sustain = 0.4, release = 0.2) { x ->
            0.5 + 0.5 * noise(x, frequency = 18.0)
        }
    }
}.asFn(cps = 2.0, maxLookAhead = 0.5)
val gaussianFieldSamples = listOf(
    gaussianField(0.0),
    gaussianField(0.25),
    gaussianField(0.5),
    gaussianField(1.0)
)
val shapedFieldSamples = listOf(
    shapedField(0.0),
    shapedField(0.25),
    envelopeField(0.5),
    envelopeField(1.0),
    dynamicField(0.33),
    dynamicField(0.9)
)

val degrees = pat("0 3 5 7")
val bassFromPattern = degrees.asNotes(cps = 2.0) { event ->
    val degree = event.value.toIntOrNull() ?: return@asNotes emptyList()
    listOf(
        note(
            pitch = 36 + degree,
            duration = event.duration * 0.95,
            start = event.start,
            label = event.value
        )
    )
}
val bassSvg = bassFromPattern.at(1.0).pianoRollSvg()
val motifSvg = motif.at(1.0).pianoRollSvg()
println(
    "synth patterns ok " +
        scheduledAtoms.size + " " +
        postfixSchedule.size + " " +
        cueSchedule.size + " " +
        gaussianFieldSamples.size + " " +
        clipSvg.length + " " +
        arpSvg.length + " " +
        bassSvg.length + " " +
        motifSvg.length
)
""".trimIndent()

FIELD_TEST id: synth.cookbook.visualizers runner: backdoor-box tags: [synth, cookbook, visualizers, html] timeoutSec: 45 expect: outputContains: - "synth visuals ok" eventKinds: - set-output

private val SYNTH_COOKBOOK_VISUALIZERS = """
val patch = audio(gain = 0.1f) { saw / FreeVerb() }
val monoScope = (saw / MoogLadder(cutoff = 900.p)).scope()
val stereoScope = audioInStereo.scope()
val env = ADSR(gate = gate.also { it.open() }, release = 0.4.p)
val comp = Compressor(threshold = (-18).p, ratio = 6.p)

display("status", "<div style=\"padding:18px;background:#101820;color:#f2aa4c;font-family:monospace\">synth visuals ok</div>")
display("audio", patch.summaryHtml())
display("scope", monoScope.summaryHtml())
display("stereo", stereoScope.summaryHtml())
display("env", env.stateSvg())
display("comp", comp.stateSvg())
""".trimIndent()

FIELD_TEST id: synth.cookbook.terse-api runner: backdoor-box tags: [synth, cookbook, terse, api] timeoutSec: 45 expect: outputContains: - "terse api ok" eventKinds: - output

source — 41 lines, from line 450
private val SYNTH_COOKBOOK_TERSE_API = """
// ── Oscillator factories ──────────────────────────────
val bass = saw(110)
val lead = sine(440)
val pad  = square(55)
val tri1 = tri(330)
val pwm  = pulse(220, width = 0.3)

// ── Envelope factories — compose with * ───────────────
val pluck     = saw(110) * ar()
val sustained = sine(220) * adsr(a = 0.01, d = 0.1, s = 0.7, r = 0.3)
val hit       = saw(80)  * perc(0.08)

// ── Filter/effect factories — compose with / ──────────
val darkPad    = saw(55) / lpf(800, 0.3) / reverb(0.7, 0.5, 0.3)
val brightLead = saw(440) / hpf(1200) / delayEffect(0.25, feedback = 0.4)
val filtered   = square(110) / bpf(600) / chorus()
val dynCutoff  = saw(110) / lpf(Param { 400f + 200f * kotlin.math.sin(time * 0.3f) })

// ── FM ────────────────────────────────────────────────
val fmBass = fm(110, index = 3)
val fmBell = fm(880, ratio = 1.4, index = 6) / reverb(0.9) * perc(0.3)

// ── Double operators on Signal ────────────────────────
val mixed = saw(110) * 0.1 + sine(220) * 0.05
val doubl = 2 * saw(55)

// ── Patterns ──────────────────────────────────────────
val drums  = pat("bd ~ sd").stack(pat("[hh hh hh hh]"))
val melody = pat("60 64 67 72").notes(cps = 2.0)
val scale  = pat("0 2 4 5 7").degrees(root = 48, cps = 1.5)
val field  = pat("g:(0,0.08) tent:(1,0.05)").fn(cps = 2.0, range = 0.5)
val prev   = pat("bd [hh hh] sd").peek(4)

// ── Full terse patch ──────────────────────────────────
val fullPatch = audio {
    fm(110, index = 3) / lpf(600) / reverb() * ar() * 0.08
}

println("terse api ok " + prev.size + " " + field(0.5))
""".trimIndent()

FIELD_TEST id: synth.cookbook.dough-ports runner: backdoor-box tags: [synth, cookbook, dough, dsp, audio] timeoutSec: 45 expect: outputContains: - "dough ports ok" eventKinds: - output

private val SYNTH_COOKBOOK_DOUGH_PORTS = """
// ── Naive (deliberately aliasing) oscillators ─────────
val grittyA = zaw
val grittyB = pulze
val gritty  = zaw(110)
val duty    = pulze(220, duty = 0.25)

// ── Dattorro plate reverb — mono in, stereo out ───────
val plated     = (saw(110) / lpf(1200)).plateReverb(decay = 0.85, mix = 0.4)
val platedAlias = sine(220).plate(decay = 0.6)
val platedFull = PlateReverb(
    source = zaw(55) * ar(),
    decay = 0.8.p,
    damping = 0.9.p,
    mix = 0.35.p
)
val platedRaw  = PlateReverb(source = white * 0.05f, scaleToSampleRate = false)

// ── Phaser and the superdough lowpass ─────────────────
val swirled  = supersaw / phaser(rate = 0.4, depth = 0.7, center = 900)
val snarling = zaw(110) / chamberlinLpf(700, resonance = 0.85)
val crunched = saw(110) / softDistort(2.5) / chamberlinLpf(1400)

// ── Curved ADSR ───────────────────────────────────────
val shaped = sine(220) * curvedAdsr(a = 0.02, d = 0.06, s = 0.5, r = 0.2)
val eased  = saw(110) * curvedAdsr(attackCurve = 4.0, decayCurve = 1.0)

// ── The fast math, usable directly ────────────────────
val approxPow = DoughMath.pow(2f, 10f)
val approxSin = DoughMath.parSin(1.5f)

// ── A full patch through the ported chain ─────────────
val doughPatch = audio(gain = 0.08f) {
    (zaw(110) / chamberlinLpf(900, 0.6) / softDistort(1.5) * curvedAdsr(r = 0.4))
        .plateReverb(decay = 0.8, mix = 0.3)
}

println("dough ports ok " + approxPow.toInt() + " " + (approxSin > 0.9f))
""".trimIndent()