import ddf.minim.ugens.Oscil
import ddf.minim.ugens.Pan
import org.openrndr.MouseTracker
import org.openrndr.application
import org.openrndr.color.rgb
import org.openrndr.extra.minim.minim
import org.openrndr.math.Polar
import kotlin.math.pow
import kotlin.random.Random

/**
 * Random drone generator and visualizer with 20 stereo voices.
 * Hold the mouse button to randomize the frequencies.
 * Press keys 'a' or 'b' for less random frequencies.
 */
fun main() {
    application {
        program {
            val minim = minim()
            val out = minim.lineOut

            if (out == null) {
                application.exit()
            }

            // generates a random frequency value biased down
            fun randomFreq() = 20f + Random.nextFloat().pow(3) * 1000

            // If one didn't want to visualize or control the synths we
            // wouldn't need a data structure to store them. Here we store
            // Pairs, so we have access both to the frequency of the wave
            // and the current amplitude defined by the lfo (low frequency
            // oscillator).
            val synths = List(20) {
                // By default, Oscil creates sine waves, but it can be changed.
                val lfo = Oscil(
                    Random.nextFloat() * 0.1f + 0.005f,
                    0.05f
                ).apply {
                    // Here we set the center of the lfo to 0.05f.
                    // Since the amplitude is also 0.05f, it moves between
                    // 0.00f and 0.10f.
                    offset.lastValue = 0.05f

                    // Have the sine waves to not start in sync.
                    //phase.lastValue = Random.nextFloat() * 6.28f
                }
                val wave = Oscil(randomFreq(), 0f)
                // The `lfo` Oscil controls the `wave` Oscil's amplitude.
                lfo.patch(wave.amplitude)
                // Random pan to avoid a mono sound.
                val pan = Pan(Random.nextFloat() * 2 - 1)
                wave.patch(pan)
                pan.patch(out)
                // Store a [Pair] in `synths`.
                Pair(wave, lfo)
            }
            val bgColor = rgb(0.094, 0.188, 0.349)
            val lineColor = rgb(0.992, 0.918, 0.671)
            val mouseTracker = MouseTracker(mouse)

            extend {
                drawer.clear(bgColor)
                drawer.translate(drawer.bounds.center)
                drawer.rotate(seconds)
                // A CircleBatchBuilder for faster drawing of circles.
                drawer.circles {
                    // For each synth draw a circle.
                    synths.forEachIndexed { i, (wave, lfo) ->
                        stroke = lineColor.opacify(Random.nextDouble(0.4) + 0.6)
                        fill = lineColor.opacify(Random.nextDouble() * 0.04)
                        // A Polar arrangement centered on the screen.
                        // Higher pitch circles are farther away from the center.
                        val pos = Polar(
                            360.0 * i / synths.size,
                            50.0 + wave.frequency.lastValue * 0.2
                        ).cartesian
                        // The size of the circle depends on the current volume
                        // set by the lfo.
                        circle(pos, 500 * lfo.lastValues.last().toDouble())
                    }
                }
                if (mouseTracker.pressedButtons.isNotEmpty()) {
                    synths.random().first.setFrequency(randomFreq())
                }
            }
            keyboard.keyDown.listen { key ->
                when (key.name) {
                    "a" -> {
                        // make all frequencies close to a base frequency
                        // (circular arrangement)
                        val baseFreq = 20 + Random.nextFloat() * 200
                        synths.forEach {
                            it.first.setFrequency(baseFreq + Random.nextFloat() * 20)
                        }
                    }

                    "b" -> {
                        // make all frequencies follow an exponential series
                        // (spiral arrangement)
                        val inc = Random.nextFloat() * 0.1f
                        synths.forEachIndexed { i, (wave, _) ->
                            wave.setFrequency(25f.pow(1f + i * inc))
                        }
                    }
                }
            }
        }
    }
}