package nl.astraeus.vst.string.view import kotlinx.browser.window import kotlinx.html.canvas import kotlinx.html.div import kotlinx.html.js.onClickFunction import kotlinx.html.span import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.Komponent import nl.astraeus.komp.currentElement import nl.astraeus.vst.Note import nl.astraeus.vst.string.PhysicalString import nl.astraeus.vst.string.audio.VstStringWorklet import nl.astraeus.vst.string.view.MainView.ControlsCss import nl.astraeus.vst.ui.components.KnobComponent import nl.astraeus.vst.util.formatDouble import org.w3c.dom.CanvasRenderingContext2D import org.w3c.dom.HTMLCanvasElement class PhysicalStringView( val string: PhysicalString ) : Komponent() { var context: CanvasRenderingContext2D? = null var interval: Int = -1 var lastUpdateTime: Double = window.performance.now() init { window.requestAnimationFrame(::onAnimationFrame) interval = window.setInterval({ if (context?.canvas?.isConnected == true) { val now: Double = window.performance.now() val time = now - lastUpdateTime lastUpdateTime = now string.update(time) } else { window.clearInterval(interval) } }, 1) } private fun onAnimationFrame(time: Double) { draw() window.requestAnimationFrame(::onAnimationFrame) } override fun HtmlBuilder.render() { div { div(ControlsCss.name) { include( KnobComponent( value = VstStringWorklet.damping, label = "Damping", minValue = 0.8, maxValue = 1.0, step = 0.2 / 127.0, width = 100, height = 120, renderer = { formatDouble(it, 3) } ) { value -> VstStringWorklet.damping = value } ) } div { span { +"Play C3" onClickFunction = { string.pluck(Note.C3, 1.0) } } span { +"Play C4" onClickFunction = { string.pluck(Note.C4, 1.0) } } } canvas { width = "1000" height = "400" context = (currentElement() as? HTMLCanvasElement)?.getContext("2d") as? CanvasRenderingContext2D } } } private fun draw() { val ctx = context if (ctx != null && ctx.canvas.isConnected) { val width = ctx.canvas.width.toDouble() val height = ctx.canvas.height.toDouble() val halfHeight = height / 2.0 ctx.lineWidth = 2.0 ctx.clearRect(0.0, 0.0, width, height) val step = width / string.length ctx.beginPath() ctx.strokeStyle = "rgba(0, 255, 255, 0.5)" for (i in 0 until string.length) { ctx.moveTo(i * step, halfHeight) ctx.lineTo(i * step, halfHeight + string.buffer[i] * halfHeight) } ctx.stroke() } } }