From a4eb5b62ef59d7d894dc0da3b40959bfc390433a Mon Sep 17 00:00:00 2001 From: rnentjes Date: Wed, 21 Jan 2026 17:09:11 +0100 Subject: [PATCH] Upgrade Kotlin to 2.3.0, refine `MainView`, and update dependencies Updated Kotlin Multiplatform to 2.3.0 for enhanced compatibility and improvements. Streamlined `MainView` with cleaner structure, added responsive scaling, and introduced a `HiddenCss` class. Upgraded various dependencies, including `vst-ui-base` to 2.2.3 and `midi-arrays` to 0.3.6, ensuring better functionality and consistency. Added changes to build task configurations for improved build pipelines. --- .run/JS.run.xml | 9 + .run/Main [jvm].run.xml | 8 +- audio-worklet/build.gradle.kts | 2 +- .../nl/astraeus/vst/chip/ChipProcessor.kt | 10 +- build.gradle.kts | 15 +- common.gradle.kts | 2 +- settings.common.gradle.kts | 2 +- .../nl/astraeus/vst/chip/view/MainView.kt | 656 +++++++++--------- 8 files changed, 370 insertions(+), 334 deletions(-) create mode 100644 .run/JS.run.xml diff --git a/.run/JS.run.xml b/.run/JS.run.xml new file mode 100644 index 0000000..6f18372 --- /dev/null +++ b/.run/JS.run.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/.run/Main [jvm].run.xml b/.run/Main [jvm].run.xml index 4378084..c81f6df 100644 --- a/.run/Main [jvm].run.xml +++ b/.run/Main [jvm].run.xml @@ -17,11 +17,11 @@ false true + false false false - - + false + false + \ No newline at end of file diff --git a/audio-worklet/build.gradle.kts b/audio-worklet/build.gradle.kts index f1b1ebe..122a4e4 100644 --- a/audio-worklet/build.gradle.kts +++ b/audio-worklet/build.gradle.kts @@ -40,7 +40,7 @@ kotlin { val commonMain by getting { dependencies { implementation("nl.astraeus:vst-worklet-base:1.0.1") - implementation("nl.astraeus:midi-arrays:0.3.4") + implementation("nl.astraeus:midi-arrays:0.3.6") } } val jsMain by getting diff --git a/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/chip/ChipProcessor.kt b/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/chip/ChipProcessor.kt index 039c719..427e28d 100644 --- a/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/chip/ChipProcessor.kt +++ b/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/chip/ChipProcessor.kt @@ -5,12 +5,8 @@ package nl.astraeus.vst.chip import nl.astraeus.midi.message.SortedTimedMidiMessageList import nl.astraeus.midi.message.TimedMidiMessage import nl.astraeus.tba.SlicedByteArray -import nl.astraeus.vst.ADSR -import nl.astraeus.vst.AudioWorkletProcessor -import nl.astraeus.vst.currentTime +import nl.astraeus.vst.* import nl.astraeus.vst.midi.MidiMessageHandler -import nl.astraeus.vst.registerProcessor -import nl.astraeus.vst.sampleRate import org.khronos.webgl.Float32Array import org.khronos.webgl.get import org.khronos.webgl.set @@ -392,8 +388,8 @@ class VstChipProcessor : AudioWorkletProcessor() { val delaySampleIndex = (note.sample + note.combDelayBuffer.length) % note.combDelayBuffer.length - left[i] = left[i] + (note.combDelayBuffer[delaySampleIndex] * feedback.toFloat()) - right[i] = right[i] + (note.combDelayBuffer[delaySampleIndex] * feedback.toFloat()) + //left[i] = left[i] + (note.combDelayBuffer[delaySampleIndex] * feedback.toFloat()) + //right[i] = right[i] + (note.combDelayBuffer[delaySampleIndex] * feedback.toFloat()) note.combDelayBuffer[delaySampleIndex] = (left[i] + right[i]) / 2f // end - comb filter delay diff --git a/build.gradle.kts b/build.gradle.kts index 9b25fab..0ea0ab7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -56,8 +56,8 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - api("nl.astraeus:vst-ui-base:2.0.0") - implementation("nl.astraeus:midi-arrays:0.3.4") + api("nl.astraeus:vst-ui-base:2.2.3") + implementation("nl.astraeus:midi-arrays:0.3.6") } } val jsMain by getting @@ -85,6 +85,17 @@ tasks.register("buildJS") { from(layout.projectDirectory.dir("web2")) into(layout.projectDirectory.dir("web")) } +tasks.register("buildJSProd") { + duplicatesStrategy = DuplicatesStrategy.INCLUDE + dependsOn("audio-worklet:jsBrowserDistribution") + dependsOn("jsBrowserDistribution") + + from(layout.projectDirectory.dir("web1")) + into(layout.projectDirectory.dir("web")) + + from(layout.projectDirectory.dir("web2")) + into(layout.projectDirectory.dir("web")) +} /* Hardcoded deploy configuration */ diff --git a/common.gradle.kts b/common.gradle.kts index fc75351..98eada6 100644 --- a/common.gradle.kts +++ b/common.gradle.kts @@ -1,5 +1,5 @@ group = "nl.astraeus" -version = "0.1.0" +version = "0.2.0" allprojects { repositories { diff --git a/settings.common.gradle.kts b/settings.common.gradle.kts index e67c5a0..33f8ffe 100644 --- a/settings.common.gradle.kts +++ b/settings.common.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { plugins { - kotlin("multiplatform") version "2.1.20" + kotlin("multiplatform") version "2.3.0" } repositories { gradlePluginPortal() diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/view/MainView.kt b/src/jsMain/kotlin/nl/astraeus/vst/chip/view/MainView.kt index b774793..04541c5 100644 --- a/src/jsMain/kotlin/nl/astraeus/vst/chip/view/MainView.kt +++ b/src/jsMain/kotlin/nl/astraeus/vst/chip/view/MainView.kt @@ -2,34 +2,13 @@ package nl.astraeus.vst.chip.view +import kotlinx.browser.document import kotlinx.browser.window -import kotlinx.html.InputType -import kotlinx.html.canvas -import kotlinx.html.classes -import kotlinx.html.div -import kotlinx.html.h1 -import kotlinx.html.input +import kotlinx.html.* import kotlinx.html.js.onChangeFunction import kotlinx.html.js.onClickFunction import kotlinx.html.js.onInputFunction -import kotlinx.html.option -import kotlinx.html.select -import kotlinx.html.span -import nl.astraeus.css.properties.AlignItems -import nl.astraeus.css.properties.BoxSizing -import nl.astraeus.css.properties.Display -import nl.astraeus.css.properties.FlexDirection -import nl.astraeus.css.properties.FontWeight -import nl.astraeus.css.properties.JustifyContent -import nl.astraeus.css.properties.Position -import nl.astraeus.css.properties.Transform -import nl.astraeus.css.properties.em -import nl.astraeus.css.properties.hsla -import nl.astraeus.css.properties.prc -import nl.astraeus.css.properties.px -import nl.astraeus.css.properties.rem -import nl.astraeus.css.properties.vh -import nl.astraeus.css.properties.vw +import nl.astraeus.css.properties.* import nl.astraeus.css.style.Style import nl.astraeus.css.style.cls import nl.astraeus.komp.HtmlBuilder @@ -50,12 +29,9 @@ import nl.astraeus.vst.ui.css.Css.noTextSelect import nl.astraeus.vst.ui.css.CssName import nl.astraeus.vst.ui.css.hover import org.khronos.webgl.get -import org.w3c.dom.CanvasRenderingContext2D -import org.w3c.dom.HTMLCanvasElement -import org.w3c.dom.HTMLInputElement -import org.w3c.dom.HTMLSelectElement +import org.w3c.dom.* -object WaveformView: Komponent() { +object WaveformView : Komponent() { init { window.requestAnimationFrame(::onAnimationFrame) @@ -102,9 +78,11 @@ object WaveformView: Komponent() { class MainView : Komponent() { private var messages: MutableList = ArrayList() var started = false + var firstRender = true init { css() + window.addEventListener("resize", { requestUpdate() }, Any()) } fun addMessage(message: String) { @@ -122,307 +100,333 @@ class MainView : Komponent() { override fun HtmlBuilder.render() { div(MainDivCss.name) { - if (!started) { - div(StartSplashCss.name) { - div(StartBoxCss.name) { - div(StartButtonCss.name) { - +"START" + id = "fixed-container" + style = scaleContainer(currentElement(), 1920, 1080) + + div(InnerDivCss.name) { + if (!started) { + div(StartSplashCss.name) { + div(StartBoxCss.name) { + div(StartButtonCss.name) { + id = "start-button" + +"START" + } } - } - onClickFunction = { - VstChipWorklet.create { - started = true - requestUpdate() - WebsocketClient.send("LOAD\n") + onClickFunction = { + document.getElementById("start-button")?.classList?.add(HiddenCss.name) + VstChipWorklet.create { + started = true + requestUpdate() + WebsocketClient.send("LOAD\n") + } } } } - } - h1 { - +"VST Chip" - } - div { - span { - +"Midi input: " - select { - option { - +"None" - value = "none" - } - for (mi in Midi.inputs) { + h1 { + +"VST Chip" + } + div { + span { + +"Midi input: " + select { option { - +mi.name - value = mi.id - selected = mi.id == Midi.currentInput?.id + +"None" + value = "none" + } + for (mi in Midi.inputs) { + option { + +mi.name + value = mi.id + selected = mi.id == Midi.currentInput?.id + } + } + + onChangeFunction = { event -> + val target = event.target as HTMLSelectElement + if (target.value == "none") { + Midi.setInput(null) + } else { + Midi.setInput(target.value) + } } } - - onChangeFunction = { event -> - val target = event.target as HTMLSelectElement - if (target.value == "none") { - Midi.setInput(null) - } else { - Midi.setInput(target.value) + } + span { + +"channel:" + input { + type = InputType.number + value = VstChipWorklet.midiChannel.toString() + onInputFunction = { event -> + val target = event.target as HTMLInputElement + println("onInput channel: $target") + VstChipWorklet.midiChannel = target.value.toInt() } } } } - span { - +"channel:" - input { - type = InputType.number - value = VstChipWorklet.midiChannel.toString() - onInputFunction = { event -> - val target = event.target as HTMLInputElement - println("onInput channel: $target") - VstChipWorklet.midiChannel = target.value.toInt() + div { + span(ButtonBarCss.name) { + +"SAVE" + onClickFunction = { + val patch = VstChipWorklet.save().copy( + midiId = Midi.currentInput?.id ?: "", + midiName = Midi.currentInput?.name ?: "" + ) + + WebsocketClient.send("SAVE\n${JSON.stringify(patch)}") + } + } + span(ButtonBarCss.name) { + +"STOP" + onClickFunction = { + VstChipWorklet.postDirectlyToWorklet( + TimedMidiMessage(getCurrentTime(), (0xb0 + midiChannel).toByte(), 123, 0) + .data.buffer.data + ) } } } + div { + span(ButtonBarCss.name) { + +"Sine" + if (VstChipWorklet.waveform == 0) { + classes += SelectedCss.name + } + onClickFunction = { + VstChipWorklet.waveform = 0 + requestUpdate() + } + } + span(ButtonBarCss.name) { + +"Square" + if (VstChipWorklet.waveform == 1) { + classes += SelectedCss.name + } + onClickFunction = { + VstChipWorklet.waveform = 1 + requestUpdate() + } + } + span(ButtonBarCss.name) { + +"Triangle" + if (VstChipWorklet.waveform == 2) { + classes += SelectedCss.name + } + onClickFunction = { + VstChipWorklet.waveform = 2 + requestUpdate() + } + } + span(ButtonBarCss.name) { + +"Sawtooth" + if (VstChipWorklet.waveform == 3) { + classes += SelectedCss.name + } + onClickFunction = { + VstChipWorklet.waveform = 3 + requestUpdate() + } + } + } + div(ControlsCss.name) { + include( + ExpKnobComponent( + value = VstChipWorklet.volume, + label = "Volume", + minValue = 0.0, + maxValue = 1.0, + step = 5.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.volume = value + } + ) + include( + KnobComponent( + value = VstChipWorklet.dutyCycle, + label = "Duty cycle", + minValue = 0.0, + maxValue = 1.0, + step = 2.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.dutyCycle = value + } + ) + include( + ExpKnobComponent( + value = VstChipWorklet.fmModFreq, + label = "FM Freq", + minValue = 0.0, + maxValue = 2.0, + step = 5.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.fmModFreq = value + } + ) + include( + ExpKnobComponent( + value = VstChipWorklet.fmModAmp, + label = "FM Ampl", + minValue = 0.0, + maxValue = 1.0, + step = 5.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.fmModAmp = value + } + ) + include( + ExpKnobComponent( + value = VstChipWorklet.amModFreq, + label = "AM Freq", + minValue = 0.0, + maxValue = 1.0, + step = 5.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.amModFreq = value + } + ) + include( + ExpKnobComponent( + value = VstChipWorklet.amModAmp, + label = "AM Ampl", + minValue = 0.0, + maxValue = 1.0, + step = 5.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.amModAmp = value + } + ) + include( + ExpKnobComponent( + value = VstChipWorklet.feedback, + label = "Feedback", + minValue = 0.0, + maxValue = 1.0, + step = 5.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.feedback = value + } + ) + include( + ExpKnobComponent( + value = VstChipWorklet.delay, + label = "Delay", + minValue = 0.0, + maxValue = 1.0, + step = 5.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.delay = value + } + ) + include( + ExpKnobComponent( + value = VstChipWorklet.delayDepth, + label = "Delay depth", + minValue = 0.0, + maxValue = 1.0, + step = 5.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.delayDepth = value + } + ) + } + div(ControlsCss.name) { + include( + ExpKnobComponent( + value = VstChipWorklet.attack, + label = "Attack", + minValue = 0.0, + maxValue = 5.0, + step = 25.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.attack = value / 5.0 + } + ) + include( + ExpKnobComponent( + value = VstChipWorklet.decay, + label = "Decay", + minValue = 0.0, + maxValue = 5.0, + step = 25.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.decay = value / 5.0 + } + ) + include( + KnobComponent( + value = VstChipWorklet.sustain, + label = "Sustain", + minValue = 0.0, + maxValue = 1.0, + step = 2.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.sustain = value + } + ) + include( + ExpKnobComponent( + value = VstChipWorklet.release, + label = "Release", + minValue = 0.0, + maxValue = 5.0, + step = 25.0 / 127.0, + width = 100, + height = 120, + ) { value -> + VstChipWorklet.release = value / 5.0 + } + ) + } + include(WaveformView) } - div { - span(ButtonBarCss.name) { - +"SAVE" - onClickFunction = { - val patch = VstChipWorklet.save().copy( - midiId = Midi.currentInput?.id ?: "", - midiName = Midi.currentInput?.name ?: "" - ) - - WebsocketClient.send("SAVE\n${JSON.stringify(patch)}") - } - } - span(ButtonBarCss.name) { - +"STOP" - onClickFunction = { - VstChipWorklet.postDirectlyToWorklet( - TimedMidiMessage(getCurrentTime(), (0xb0 + midiChannel).toByte(), 123, 0) - .data.buffer.data - ) - } - } - } - div { - span(ButtonBarCss.name) { - +"Sine" - if (VstChipWorklet.waveform == 0) { - classes += SelectedCss.name - } - onClickFunction = { - VstChipWorklet.waveform = 0 - requestUpdate() - } - } - span(ButtonBarCss.name) { - +"Square" - if (VstChipWorklet.waveform == 1) { - classes += SelectedCss.name - } - onClickFunction = { - VstChipWorklet.waveform = 1 - requestUpdate() - } - } - span(ButtonBarCss.name) { - +"Triangle" - if (VstChipWorklet.waveform == 2) { - classes += SelectedCss.name - } - onClickFunction = { - VstChipWorklet.waveform = 2 - requestUpdate() - } - } - span(ButtonBarCss.name) { - +"Sawtooth" - if (VstChipWorklet.waveform == 3) { - classes += SelectedCss.name - } - onClickFunction = { - VstChipWorklet.waveform = 3 - requestUpdate() - } - } - } - div(ControlsCss.name) { - include( - ExpKnobComponent( - value = VstChipWorklet.volume, - label = "Volume", - minValue = 0.0, - maxValue = 1.0, - step = 5.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.volume = value - } - ) - include( - KnobComponent( - value = VstChipWorklet.dutyCycle, - label = "Duty cycle", - minValue = 0.0, - maxValue = 1.0, - step = 2.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.dutyCycle = value - } - ) - include( - ExpKnobComponent( - value = VstChipWorklet.fmModFreq, - label = "FM Freq", - minValue = 0.0, - maxValue = 2.0, - step = 5.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.fmModFreq = value - } - ) - include( - ExpKnobComponent( - value = VstChipWorklet.fmModAmp, - label = "FM Ampl", - minValue = 0.0, - maxValue = 1.0, - step = 5.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.fmModAmp = value - } - ) - include( - ExpKnobComponent( - value = VstChipWorklet.amModFreq, - label = "AM Freq", - minValue = 0.0, - maxValue = 1.0, - step = 5.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.amModFreq = value - } - ) - include( - ExpKnobComponent( - value = VstChipWorklet.amModAmp, - label = "AM Ampl", - minValue = 0.0, - maxValue = 1.0, - step = 5.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.amModAmp = value - } - ) - include( - ExpKnobComponent( - value = VstChipWorklet.feedback, - label = "Feedback", - minValue = 0.0, - maxValue = 1.0, - step = 5.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.feedback = value - } - ) - include( - ExpKnobComponent( - value = VstChipWorklet.delay, - label = "Delay", - minValue = 0.0, - maxValue = 1.0, - step = 5.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.delay = value - } - ) - include( - ExpKnobComponent( - value = VstChipWorklet.delayDepth, - label = "Delay depth", - minValue = 0.0, - maxValue = 1.0, - step = 5.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.delayDepth = value - } - ) - } - div(ControlsCss.name) { - include( - ExpKnobComponent( - value = VstChipWorklet.attack, - label = "Attack", - minValue = 0.0, - maxValue = 5.0, - step = 25.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.attack = value / 5.0 - } - ) - include( - ExpKnobComponent( - value = VstChipWorklet.decay, - label = "Decay", - minValue = 0.0, - maxValue = 5.0, - step = 25.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.decay = value / 5.0 - } - ) - include( - KnobComponent( - value = VstChipWorklet.sustain, - label = "Sustain", - minValue = 0.0, - maxValue = 1.0, - step = 2.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.sustain = value - } - ) - include( - ExpKnobComponent( - value = VstChipWorklet.release, - label = "Release", - minValue = 0.0, - maxValue = 5.0, - step = 25.0 / 127.0, - width = 100, - height = 120, - ) { value -> - VstChipWorklet.release = value / 5.0 - } - ) - } - include(WaveformView) } } + private fun scaleContainer( + container: Element, + containerWidth: Int, + containerHeight: Int, + ): String { + val vpWidth = window.innerWidth + val vpHeight = window.innerHeight + + val scaleX: Double = vpWidth / containerWidth.toDouble() + val scaleY: Double = vpHeight / containerHeight.toDouble() + + val scale = if (scaleX < scaleY) scaleX else scaleY + val left = (vpWidth - containerWidth * scale) / 2 + val top = (vpHeight - containerHeight * scale) / 2 + + return "transform: translate(${left}px, ${top}px) scale($scale);" + } + companion object MainViewCss : CssName() { object MainDivCss : CssName() + object InnerDivCss : CssName() object ActiveCss : CssName() object ButtonCss : CssName() object ButtonBarCss : CssName() @@ -431,6 +435,7 @@ class MainView : Komponent() { object StartSplashCss : CssName() object StartBoxCss : CssName() object StartButtonCss : CssName() + object HiddenCss : CssName() object ControlsCss : CssName() private fun css() { @@ -445,10 +450,12 @@ class MainView : Komponent() { select("html", "body") { margin(0.px) padding(0.px) + width(100.prc) height(100.prc) + overflow(Overflow.hidden) } select("html", "body") { - backgroundColor(Css.currentStyle.mainBackgroundColor) + backgroundColor(Css.currentStyle.mainBackgroundColor.darken(10)) color(Css.currentStyle.mainFontColor) fontFamily("JetbrainsMono, monospace") @@ -478,7 +485,17 @@ class MainView : Komponent() { minHeight(4.rem) } select(cls(MainDivCss)) { - margin(1.rem) + backgroundColor(Css.currentStyle.mainBackgroundColor) + width(1920.px) + height(1080.px) + position(Position.absolute) + top(0.px) + left(0.px) + transformOrigin("0 0") + + select(cls(InnerDivCss)) { + padding(1.rem) + } } select("select") { plain("appearance", "none") @@ -496,17 +513,17 @@ class MainView : Komponent() { position(Position.fixed) left(0.px) top(0.px) - width(100.vw) - height(100.vh) + width(1920.px) + height(1080.px) zIndex(100) backgroundColor(hsla(32, 0, 5, 0.65)) select(cls(StartBoxCss)) { position(Position.relative) - left(25.vw) - top(25.vh) - width(50.vw) - height(50.vh) + left(100.px) + top(100.px) + width(1720.px) + height(880.px) backgroundColor(hsla(239, 50, 10, 1.0)) borderColor(Css.currentStyle.mainFontColor) borderWidth(2.px) @@ -531,6 +548,9 @@ class MainView : Komponent() { padding(1.rem) backgroundColor(Css.currentStyle.mainBackgroundColor) } + select(HiddenCss.cls()) { + display(Display.none) + } } }