Files
vst-string/src/jsMain/kotlin/nl/astraeus/vst/string/view/PhysicalStringView.kt
2024-08-09 19:55:15 +02:00

108 lines
2.8 KiB
Kotlin

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()
}
}
}