# Generative

music the sketch works out for itself from four small pieces: each ring is a Rhythm, and its strikes spread over the steps as evenly as whole steps allow

Run it with `swift run --package-path Examples Example-Audio-Generative`.

```swift
import Ollin
import OllinAudio

/// Music the sketch works out for itself, from four small pieces.
///
/// Each ring is a `Rhythm`: a number of strikes spread over a number of steps
/// as evenly as whole steps allow, which is the shape drawn between them. The
/// bar sweeping round is one `StepCounter` turning the sketch clock into step
/// numbers, and all three rings read the same number.
///
/// What is played on a strike comes from the other three. The bass takes a
/// degree from a `MarkovChain` taught a four note motif, so it wanders but
/// keeps the motif's habits. The middle ring plays an `Arpeggio` over a chord
/// built out of the `Scale` itself, so the chord's color follows the key. The
/// outer ring is air rather than pitch.
///
/// Nothing here has a clock of its own. Every one of them answers a step
/// number, which is what lets the same pattern run off the sketch clock now and
/// off a drum machine later.
@main
final class Generative: Sketch {

    @Param(2 ... 12, icon: "circle.grid.cross", group: "Rhythm") var bassStrikes = 3
    @Param(2 ... 12, icon: "circle.grid.cross", group: "Rhythm") var chordStrikes = 5
    @Param(2 ... 15, icon: "circle.grid.cross", group: "Rhythm") var airStrikes = 11
    @Param(60 ... 160, icon: "metronome", group: "Rhythm") var tempo: Tempo = 104

    @Param(icon: "pianokeys", group: "Notes") var mode: Scale.Mode = .minorPentatonic
    @Param(icon: "music.quarternote.3", group: "Notes") var quality: Chord.Quality = .minorSeventh
    @Param(icon: "arrow.up.arrow.down", group: "Notes") var figure: Arpeggio.Pattern = .upDown

    /// One cycle, in steps. Sixteen is the usual bar of sixteenth notes.
    let steps = 16

    let bass = Synth(.bass, polyphony: 4)
    let chords = Synth(.pluck, polyphony: 12)
    let air = Synth(.breath, polyphony: 6)

    var counter = StepCounter(perBeat: 4)
    var melody = MarkovChain<Int>(seed: 4)

    /// Where each ring's playing lands on the picture, and when.
    var marks: [(ring: Int, step: Int, start: Double, tone: Double)] = []
    var lastStep = 0

    override func setup() {
        noStroke()
        bass.gain = 0.55
        chords.gain = 0.32
        air.gain = 0.16
        chords.reverb = Reverb(.hall, mix: 0.25)
        air.reverb = Reverb(.hall, mix: 0.4)

        // A four note motif, learned as a loop so what follows the last degree
        // is the first. The chain then has somewhere to go from anywhere in it.
        melody.learn([0, 2, 4, 2, 0, -3, 0, 4], loops: true)
        melody.start(at: 0)
    }

    override func draw() {
        background(Color(white: 0.05))

        let key = Scale(mode, root: "A2")
        let rings = [
            Rhythm(bassStrikes, in: steps),
            Rhythm(chordStrikes, in: steps),
            Rhythm(airStrikes, in: steps),
        ]

        // The clock the whole piece runs on. Beats, not seconds: everything
        // else here counts steps.
        let beats = tempo.beats(at: time)
        for step in counter.steps(upTo: beats) {
            lastStep = step
            play(step, rings: rings, key: key)
        }

        drawWheel(rings: rings, beats: beats)
        drawMarks()
        // Scoped, because the notation sets a font the notices would otherwise
        // inherit on the next frame.
        withState { drawNotation(rings: rings) }

        drawCaption("\(bassStrikes)/\(chordStrikes)/\(airStrikes) over \(steps) "
                    + "· \(mode.optionLabel) · \(quality.optionLabel) · \(tempo)",
                    edge: .top)
        drawCaption("Three Euclidean rhythms on one step count. The parameters change what is played, live.")
    }

    // MARK: Playing

    private func play(_ step: Int, rings: [Rhythm], key: Scale) {
        let inBar = ((step % steps) + steps) % steps

        if rings[0][step] {
            // A degree from the chain, made into a pitch by the scale, so a
            // wandering number never leaves the key.
            let degree = melody.next() ?? 0
            bass.play(key[degree], velocity: 0.9, for: 0.34)
            mark(ring: 0, step: inBar, tone: Double((degree + 6) % 8) / 8)
        }

        if rings[1][step] {
            // The chord is built out of the scale rather than named, so it
            // changes color with the key rather than fighting it. The figure
            // is read at the step, so each strike plays the note the figure
            // holds at that step.
            let chord = Chord(key[0].transposed(by: 12), quality)
            let arp = Arpeggio(chord, figure, octaves: 2)
            let pitch = key.snap(arp[step])
            chords.play(pitch, velocity: 0.55, for: 0.22)
            mark(ring: 1, step: inBar, tone: (pitch.midi - 45) / 40)
        }

        if rings[2][step] {
            air.play(Pitch(78 + Double(inBar % 3) * 5), velocity: 0.4, for: 0.1)
            mark(ring: 2, step: inBar, tone: 0.95)
        }
    }

    private func mark(ring: Int, step: Int, tone: Double) {
        marks.append((ring: ring, step: step, start: time, tone: clamp(0, tone, 1)))
    }

    // MARK: Drawing

    private var wheelCenter: Vector2 { Vector2(Double(width) / 2, Double(height) * 0.42) }
    private var outerRadius: Double { Double(shortSide) * 0.29 }

    private func radius(of ring: Int) -> Double {
        outerRadius - Double(ring) * outerRadius * 0.28
    }

    private func point(ring: Int, step: Double) -> Vector2 {
        let angle = step / Double(steps) * .tau - .pi / 2
        return wheelCenter + Vector2(angle: angle) * radius(of: ring)
    }

    private func drawWheel(rings: [Rhythm], beats: Double) {
        let colors = [Color(hex: 0xF2B134), Color(hex: 0x67C9D9), Color(hex: 0xE56B6F)]

        for (index, rhythm) in rings.enumerated() {
            let color = colors[index]

            // The shape between the strikes is the picture of the rhythm: an
            // even spread makes a regular polygon, an uneven one leans.
            let onsets = rhythm.onsets
            if onsets.count > 1 {
                noFill()
                stroke(color.withAlpha(0.34))
                strokeWeight(1.2 * scale)
                drawPolygon(onsets.map { point(ring: index, step: Double($0)) })
            }

            noStroke()
            for step in 0..<steps {
                let at = point(ring: index, step: Double(step))
                if rhythm[step] {
                    fill(color)
                    drawCircle(center: at, radius: 7 * scale)
                } else {
                    fill(Color(white: 0.2))
                    drawCircle(center: at, radius: 2 * scale)
                }
            }
        }

        // One bar for all three rings, because they share a step count.
        let position = beats * 4
        let angle = position / Double(steps) * .tau - .pi / 2
        let heading = Vector2(angle: angle)
        stroke(Color(white: 0.8))
        strokeWeight(1.5 * scale)
        drawLine(wheelCenter, wheelCenter + heading * (outerRadius + 26 * scale))
        noStroke()
        fill(Color(white: 0.9))
        drawCircle(center: wheelCenter + heading * (outerRadius + 26 * scale), radius: 4 * scale)
    }

    private func drawMarks() {
        marks.removeAll { time - $0.start > 0.9 }
        blendMode(.add)
        noFill()
        for mark in marks {
            let age = (time - mark.start) / 0.9
            let at = point(ring: mark.ring, step: Double(mark.step))
            stroke(Colormap.magma.color(at: 0.35 + mark.tone * 0.5).withAlpha((1 - age) * 0.85))
            strokeWeight((1 - age) * 3 * scale + 0.5)
            drawCircle(center: at, radius: (7 + age * 26) * scale)
        }
        noStroke()
        blendMode(.normal)
    }

    /// The same three rhythms written out, which is the notation the algorithm
    /// is usually discussed in.
    private func drawNotation(rings: [Rhythm]) {
        textFont(BitmapFont.builtIn)
        textSize(20 * scale)
        textAlign(.center)
        let names = ["bass", "chord", "air"]
        for (index, rhythm) in rings.enumerated() {
            let played = rhythm[lastStep]
            fill(Color(white: played ? 0.95 : 0.55))
            let line = Double(height) * 0.8 + Double(index) * 34 * scale
            drawText("\(names[index])  \(rhythm)", Double(width) / 2, line)
        }
    }
}
```
