Compare commits
6 Commits
8df6a4fff6
...
import-pro
| Author | SHA1 | Date | |
|---|---|---|---|
| 8accd60b46 | |||
| e977b2c88a | |||
| 573fc921bb | |||
| 37a42dd88c | |||
| 76866eb392 | |||
| 1eed613b2a |
@@ -1,8 +1,6 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="audio-worklet-js-1.0.0-SNAPSHOT">
|
||||
<output-path>$PROJECT_DIR$/audio-worklet/build/libs</output-path>
|
||||
<root id="archive" name="audio-worklet-js-1.0.0-SNAPSHOT.jar">
|
||||
<element id="module-output" name="vst-string.audio-worklet.jsMain" />
|
||||
</root>
|
||||
<root id="archive" name="audio-worklet-js-1.0.0-SNAPSHOT.jar" />
|
||||
</artifact>
|
||||
</component>
|
||||
@@ -1,8 +1,6 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="audio-worklet-jvm-1.0.0-SNAPSHOT">
|
||||
<output-path>$PROJECT_DIR$/audio-worklet/build/libs</output-path>
|
||||
<root id="archive" name="audio-worklet-jvm-1.0.0-SNAPSHOT.jar">
|
||||
<element id="module-output" name="vst-string.audio-worklet.jvmMain" />
|
||||
</root>
|
||||
<root id="archive" name="audio-worklet-jvm-1.0.0-SNAPSHOT.jar" />
|
||||
</artifact>
|
||||
</component>
|
||||
4
.idea/artifacts/common_js_1_0_0_SNAPSHOT.xml
generated
4
.idea/artifacts/common_js_1_0_0_SNAPSHOT.xml
generated
@@ -1,8 +1,6 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="common-js-1.0.0-SNAPSHOT">
|
||||
<output-path>$PROJECT_DIR$/common/build/libs</output-path>
|
||||
<root id="archive" name="common-js-1.0.0-SNAPSHOT.jar">
|
||||
<element id="module-output" name="vst-string.common.jsMain" />
|
||||
</root>
|
||||
<root id="archive" name="common-js-1.0.0-SNAPSHOT.jar" />
|
||||
</artifact>
|
||||
</component>
|
||||
4
.idea/artifacts/common_jvm_1_0_0_SNAPSHOT.xml
generated
4
.idea/artifacts/common_jvm_1_0_0_SNAPSHOT.xml
generated
@@ -1,8 +1,6 @@
|
||||
<component name="ArtifactManager">
|
||||
<artifact type="jar" name="common-jvm-1.0.0-SNAPSHOT">
|
||||
<output-path>$PROJECT_DIR$/common/build/libs</output-path>
|
||||
<root id="archive" name="common-jvm-1.0.0-SNAPSHOT.jar">
|
||||
<element id="module-output" name="vst-string.common.jvmMain" />
|
||||
</root>
|
||||
<root id="archive" name="common-jvm-1.0.0-SNAPSHOT.jar" />
|
||||
</artifact>
|
||||
</component>
|
||||
7
.idea/jsLibraryMappings.xml
generated
7
.idea/jsLibraryMappings.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<excludedPredefinedLibrary name="vst-chip/build/js/node_modules" />
|
||||
<excludedPredefinedLibrary name="vst-chip/build/js/packages/vst-base-test/node_modules" />
|
||||
</component>
|
||||
</project>
|
||||
11
LICENSE.txt
Normal file
11
LICENSE.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
MIT NonCommercial License
|
||||
|
||||
Copyright (c) 2025 H.Nentjes
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to use, copy, modify, merge, publish, and distribute the Software, subject to the following conditions:
|
||||
|
||||
NonCommercial Use Only. The Software may not be used, in whole or in part, for any commercial purpose. Commercial purpose means using the Software in a way that is intended for or directed toward commercial advantage or monetary compensation.
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,6 +1,5 @@
|
||||
@file:OptIn(ExperimentalKotlinGradlePluginApi::class, ExperimentalDistributionDsl::class)
|
||||
@file:OptIn(ExperimentalDistributionDsl::class)
|
||||
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalDistributionDsl
|
||||
import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackOutput
|
||||
|
||||
@@ -42,7 +41,7 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
|
||||
implementation("nl.astraeus:vst-worklet-base:1.0.0-SNAPSHOT")
|
||||
implementation("nl.astraeus:vst-worklet-base:1.0.1")
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Paths
|
||||
|
||||
buildscript {
|
||||
apply(from = "common.gradle.kts")
|
||||
apply(from = "version.gradle.kts")
|
||||
}
|
||||
|
||||
plugins {
|
||||
@@ -28,7 +32,7 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
jvm{
|
||||
jvm {
|
||||
withJava()
|
||||
}
|
||||
|
||||
@@ -37,15 +41,15 @@ kotlin {
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
//base
|
||||
api("nl.astraeus:kotlin-css-generator:1.0.7")
|
||||
implementation("nl.astraeus:kotlin-css-generator:1.0.10")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")
|
||||
implementation("nl.astraeus:vst-ui-base:1.1.2")
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
//base
|
||||
implementation("nl.astraeus:kotlin-komponent-js:1.2.2")
|
||||
implementation("nl.astraeus:vst-ui-base:1.0.0-SNAPSHOT")
|
||||
implementation("nl.astraeus:kotlin-komponent-js:1.2.4")
|
||||
}
|
||||
}
|
||||
val jsTest by getting {
|
||||
@@ -70,3 +74,88 @@ kotlin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
application {
|
||||
mainClass.set("nl.astraeus.vst.string.MainKt")
|
||||
}
|
||||
|
||||
/* Hardcoded deploy configuration */
|
||||
|
||||
val deployDirectory = "vst-string.midi-vst.com"
|
||||
|
||||
tasks.register<Copy>("unzipDistribution") {
|
||||
mustRunAfter("removeSymbolicLink")
|
||||
val zipDir = layout.projectDirectory.dir("build/distributions")
|
||||
val zipFile = zipDir.file("${project.name}-${project.version}.zip")
|
||||
|
||||
val outputDir = file("/home/rnentjes/www/${deployDirectory}")
|
||||
|
||||
from(zipTree(zipFile))
|
||||
into(outputDir)
|
||||
}
|
||||
|
||||
tasks.register("createSymbolicLink") {
|
||||
mustRunAfter("unzipDistribution")
|
||||
doLast {
|
||||
val targetDir =
|
||||
Paths.get("/home/rnentjes/www/${deployDirectory}/${project.name}-${project.version}") // Directory to link to
|
||||
val symlink =
|
||||
Paths.get("/home/rnentjes/www/${deployDirectory}/${project.name}") // Path for the symbolic link
|
||||
|
||||
if (!Files.exists(targetDir)) {
|
||||
throw IllegalArgumentException("Target directory does not exist: $targetDir")
|
||||
}
|
||||
|
||||
if (Files.exists(symlink)) {
|
||||
println("Symbolic link already exists: $symlink")
|
||||
} else {
|
||||
Files.createSymbolicLink(symlink, targetDir)
|
||||
println("Symbolic link created: $symlink -> $targetDir")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register<Copy>("copyWeb") {
|
||||
val webDir = layout.projectDirectory.dir("web")
|
||||
val outputDir = file("/home/rnentjes/www/${deployDirectory}/web")
|
||||
|
||||
from(webDir)
|
||||
into(outputDir)
|
||||
}
|
||||
|
||||
tasks.named<Task>("build") {
|
||||
dependsOn("generateVersionProperties")
|
||||
}
|
||||
|
||||
tasks.named("kotlinUpgradeYarnLock") {
|
||||
mustRunAfter("clean")
|
||||
}
|
||||
|
||||
tasks.named("build") {
|
||||
mustRunAfter("kotlinUpgradeYarnLock")
|
||||
}
|
||||
|
||||
tasks.named("build") {
|
||||
mustRunAfter("kotlinUpgradeYarnLock")
|
||||
}
|
||||
|
||||
tasks.named("copyWeb") {
|
||||
mustRunAfter("build")
|
||||
}
|
||||
|
||||
tasks.register("removeSymbolicLink") {
|
||||
mustRunAfter("build")
|
||||
doLast {
|
||||
delete(layout.projectDirectory.file("/home/rnentjes/www/${deployDirectory}/${project.name}"))
|
||||
}
|
||||
}
|
||||
|
||||
tasks.register("deploy") {
|
||||
dependsOn("clean")
|
||||
dependsOn("kotlinUpgradeYarnLock")
|
||||
dependsOn("build")
|
||||
dependsOn("copyWeb")
|
||||
dependsOn("removeSymbolicLink")
|
||||
dependsOn("unzipDistribution")
|
||||
dependsOn("createSymbolicLink")
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
group = "nl.astraeus"
|
||||
version = "1.0.0-SNAPSHOT"
|
||||
version = "0.1.0"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
maven("https://reposilite.astraeus.nl/releases")
|
||||
maven {
|
||||
url = uri("https://nexus.astraeus.nl/nexus/content/groups/public")
|
||||
url = uri("https://gitea.astraeus.nl:8443/api/packages/rnentjes/maven")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
@file:OptIn(ExperimentalKotlinGradlePluginApi::class)
|
||||
|
||||
import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
|
||||
|
||||
buildscript {
|
||||
apply(from = "../common.gradle.kts")
|
||||
}
|
||||
@@ -20,13 +16,8 @@ kotlin {
|
||||
jvm()
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
}
|
||||
}
|
||||
val commonMain by getting
|
||||
val jsMain by getting
|
||||
val jvmMain by getting
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@file:OptIn(ExperimentalJsExport::class)
|
||||
|
||||
package nl.astraeus.vst
|
||||
|
||||
import kotlin.js.ExperimentalJsExport
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
@file:OptIn(ExperimentalJsExport::class)
|
||||
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
import nl.astraeus.vst.Note
|
||||
@@ -7,6 +9,8 @@ import kotlin.math.round
|
||||
|
||||
expect fun randomDouble(): Double
|
||||
|
||||
const val BUFFER_MULTIPLY = 4
|
||||
|
||||
@ExperimentalJsExport
|
||||
@JsExport
|
||||
class PhysicalString(
|
||||
@@ -14,9 +18,9 @@ class PhysicalString(
|
||||
var damping: Double,
|
||||
) {
|
||||
val sampleLength = 1.0 / sampleRate.toDouble()
|
||||
val maxLength = sampleRate / Note.G9.freq
|
||||
val maxLength = sampleRate / Note.NO01.freq
|
||||
var length = 1
|
||||
val buffer = Array(maxLength.toInt() + 1) { 0.0 }
|
||||
val buffer = Array((maxLength * BUFFER_MULTIPLY).toInt() + 1) { 0.0 }
|
||||
var sample = 0
|
||||
var index = 0
|
||||
var remaining = 0.0
|
||||
@@ -26,7 +30,7 @@ class PhysicalString(
|
||||
fun pluck(note: Note, velocity: Double, smoothing: Int = 0) {
|
||||
available = false
|
||||
currentNote = note
|
||||
length = round(sampleRate / note.freq).toInt()
|
||||
length = round(BUFFER_MULTIPLY * sampleRate / note.freq).toInt()
|
||||
sample = 0
|
||||
index = 0
|
||||
|
||||
@@ -37,7 +41,7 @@ class PhysicalString(
|
||||
buffer[i] = -randomDouble() * velocity
|
||||
}
|
||||
//buffer[i] = (randomDouble() - 0.5) * 2.0 * velocity
|
||||
//buffer[i] = sin(PI * 2 * i/length)
|
||||
//buffer[i] = sin(PI * 2 * i/length) * randomDouble() * velocity
|
||||
//buffer[i] = (i/length.toDouble() * 2.0) - 1.0 //if (i < length / 2) { 1.0 } else { -1.0 }
|
||||
}
|
||||
repeat(smoothing) {
|
||||
@@ -58,18 +62,20 @@ class PhysicalString(
|
||||
fun tick(): Double {
|
||||
val result = buffer[index]
|
||||
|
||||
var newValue = 0.0
|
||||
newValue += getValueFromBuffer(index + 1) * 0.2
|
||||
newValue += getValueFromBuffer(index + 2) * 0.3
|
||||
newValue += getValueFromBuffer(index + 3) * 0.3
|
||||
newValue += getValueFromBuffer(index + 4) * 0.2
|
||||
// newValue += getValueFromBuffer(index + 5) * 0.2
|
||||
// newValue += getValueFromBuffer(index + 6) * 0.3
|
||||
newValue *= damping
|
||||
repeat(BUFFER_MULTIPLY) {
|
||||
var newValue = 0.0
|
||||
newValue += getValueFromBuffer(index + 1) * 0.1
|
||||
newValue += getValueFromBuffer(index + 2) * 0.15
|
||||
newValue += getValueFromBuffer(index + 3) * 0.25
|
||||
newValue += getValueFromBuffer(index + 4) * 0.25
|
||||
newValue += getValueFromBuffer(index + 5) * 0.15
|
||||
newValue += getValueFromBuffer(index + 6) * 0.1
|
||||
newValue *= damping
|
||||
|
||||
buffer[index] = newValue
|
||||
buffer[index] = newValue
|
||||
|
||||
index = (index + 1) % length
|
||||
index = (index + 1) % length
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
pluginManagement {
|
||||
plugins {
|
||||
kotlin("multiplatform") version "2.0.20-Beta1"
|
||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0"
|
||||
kotlin("multiplatform") version "2.1.0"
|
||||
}
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
package nl.astraeus.vst.string.logger
|
||||
|
||||
val log = Logger
|
||||
|
||||
enum class LogLevel {
|
||||
TRACE,
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR,
|
||||
FATAL
|
||||
}
|
||||
|
||||
object Logger {
|
||||
var level: LogLevel = LogLevel.INFO
|
||||
|
||||
fun trace(message: () -> String?) {
|
||||
if (level.ordinal <= LogLevel.TRACE.ordinal) {
|
||||
println("TRACE: ${message()}")
|
||||
}
|
||||
}
|
||||
|
||||
fun debug(message: () -> String?) {
|
||||
if (level.ordinal <= LogLevel.DEBUG.ordinal) {
|
||||
println("DEBUG: ${message()}")
|
||||
}
|
||||
}
|
||||
|
||||
fun info(message: () -> String?) {
|
||||
if (level.ordinal <= LogLevel.INFO.ordinal) {
|
||||
println("INFO: ${message()}")
|
||||
}
|
||||
}
|
||||
|
||||
fun warn(e: Throwable? = null, message: () -> String?) {
|
||||
if (level.ordinal <= LogLevel.WARN.ordinal) {
|
||||
println("WARN: ${message()}")
|
||||
e?.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun error(e: Throwable? = null, message: () -> String?) {
|
||||
if (level.ordinal <= LogLevel.ERROR.ordinal) {
|
||||
println("ERROR: ${message()}")
|
||||
e?.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun fatal(e: Throwable, message: () -> String?) {
|
||||
println("FATAL: ${message()}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,24 @@ package nl.astraeus.vst.string
|
||||
import kotlinx.browser.document
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.komp.UnsafeMode
|
||||
import nl.astraeus.vst.string.audio.VstStringWorklet
|
||||
import nl.astraeus.vst.string.logger.log
|
||||
import nl.astraeus.vst.string.midi.Midi
|
||||
import nl.astraeus.vst.string.view.MainView
|
||||
import nl.astraeus.vst.string.ws.WebsocketClient
|
||||
import nl.astraeus.vst.ui.css.CssSettings
|
||||
import nl.astraeus.vst.ui.view.BaseVstView
|
||||
|
||||
fun main() {
|
||||
CssSettings.shortId = false
|
||||
CssSettings.preFix = "vst-chip"
|
||||
CssSettings.preFix = "vst-string"
|
||||
|
||||
Komponent.unsafeMode = UnsafeMode.UNSAFE_SVG_ONLY
|
||||
Komponent.create(document.body!!, MainView)
|
||||
Komponent.create(document.body!!, BaseVstView("VST Guiter", MainView) {
|
||||
VstStringWorklet.create {
|
||||
WebsocketClient.send("LOAD\n")
|
||||
}
|
||||
})
|
||||
|
||||
Midi.start()
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@ object AudioContextHandler {
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,10 @@ 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.style.Style
|
||||
import nl.astraeus.css.style.cls
|
||||
import nl.astraeus.komp.HtmlBuilder
|
||||
@@ -48,7 +43,6 @@ import org.w3c.dom.HTMLSelectElement
|
||||
|
||||
object MainView : Komponent(), CssName {
|
||||
private var messages: MutableList<String> = ArrayList()
|
||||
var started = false
|
||||
val playString = PhysicalStringView(
|
||||
PhysicalString(
|
||||
sampleRate = 48000,
|
||||
@@ -70,22 +64,6 @@ object MainView : Komponent(), CssName {
|
||||
|
||||
override fun HtmlBuilder.render() {
|
||||
div(MainDivCss.name) {
|
||||
if (!started) {
|
||||
div(StartSplashCss.name) {
|
||||
div(StartBoxCss.name) {
|
||||
div(StartButtonCss.name) {
|
||||
+"START"
|
||||
onClickFunction = {
|
||||
VstStringWorklet.create {
|
||||
started = true
|
||||
requestUpdate()
|
||||
WebsocketClient.send("LOAD\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
+"VST Guitar"
|
||||
}
|
||||
@@ -175,9 +153,6 @@ object MainView : Komponent(), CssName {
|
||||
object ButtonBarCss : CssName
|
||||
object SelectedCss : CssName
|
||||
object NoteBarCss : CssName
|
||||
object StartSplashCss : CssName
|
||||
object StartBoxCss : CssName
|
||||
object StartButtonCss : CssName
|
||||
object ControlsCss : CssName
|
||||
|
||||
private fun css() {
|
||||
@@ -239,36 +214,6 @@ object MainView : Komponent(), CssName {
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
borderRadius(0.25.em)
|
||||
}
|
||||
select(cls(StartSplashCss)) {
|
||||
position(Position.fixed)
|
||||
left(0.px)
|
||||
top(0.px)
|
||||
width(100.vw)
|
||||
height(100.vh)
|
||||
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)
|
||||
backgroundColor(hsla(239, 50, 10, 1.0))
|
||||
borderColor(Css.currentStyle.mainFontColor)
|
||||
borderWidth(2.px)
|
||||
|
||||
select(cls(StartButtonCss)) {
|
||||
position(Position.absolute)
|
||||
left(50.prc)
|
||||
top(50.prc)
|
||||
transform(Transform("translate(-50%, -50%)"))
|
||||
padding(1.rem)
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor)
|
||||
cursor("pointer")
|
||||
}
|
||||
}
|
||||
}
|
||||
select(ControlsCss.cls()) {
|
||||
display(Display.flex)
|
||||
flexDirection(FlexDirection.row)
|
||||
|
||||
@@ -40,9 +40,7 @@ class PhysicalStringView(
|
||||
}
|
||||
|
||||
private fun onAnimationFrame(time: Double) {
|
||||
if (MainView.started) {
|
||||
draw()
|
||||
}
|
||||
draw()
|
||||
|
||||
window.requestAnimationFrame(::onAnimationFrame)
|
||||
}
|
||||
@@ -89,7 +87,7 @@ class PhysicalStringView(
|
||||
|
||||
private fun draw() {
|
||||
val ctx = context
|
||||
if (ctx != null) {
|
||||
if (ctx != null && ctx.canvas.isConnected) {
|
||||
val width = ctx.canvas.width.toDouble()
|
||||
val height = ctx.canvas.height.toDouble()
|
||||
val halfHeight = height / 2.0
|
||||
|
||||
@@ -18,7 +18,7 @@ object WaveformView : Komponent() {
|
||||
}
|
||||
|
||||
fun onAnimationFrame(time: Double) {
|
||||
if (MainView.started) {
|
||||
if (VstStringWorklet.recording == null) {
|
||||
VstStringWorklet.postMessage("start_recording")
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ object WaveformView : Komponent() {
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
VstStringWorklet.recording = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
import java.security.SecureRandom
|
||||
|
||||
val idChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
val random = SecureRandom()
|
||||
|
||||
fun generateId(): String {
|
||||
val id = StringBuilder()
|
||||
|
||||
for (i in 0 until 8) {
|
||||
id.append(idChars[random.nextInt(idChars.length)])
|
||||
}
|
||||
|
||||
return id.toString()
|
||||
}
|
||||
@@ -1,15 +1,10 @@
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import io.undertow.Undertow
|
||||
import io.undertow.UndertowOptions
|
||||
import io.undertow.server.session.InMemorySessionManager
|
||||
import io.undertow.server.session.SessionAttachmentHandler
|
||||
import io.undertow.server.session.SessionCookieConfig
|
||||
import nl.astraeus.vst.string.db.Database
|
||||
import nl.astraeus.vst.base.Settings
|
||||
import nl.astraeus.vst.base.db.Database
|
||||
import nl.astraeus.vst.base.web.UndertowServer
|
||||
import nl.astraeus.vst.string.logger.LogLevel
|
||||
import nl.astraeus.vst.string.logger.Logger
|
||||
import nl.astraeus.vst.string.web.RequestHandler
|
||||
|
||||
fun main() {
|
||||
Logger.level = LogLevel.DEBUG
|
||||
@@ -18,32 +13,13 @@ fun main() {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
Class.forName("nl.astraeus.jdbc.Driver")
|
||||
Settings.port = 9004
|
||||
Settings.jdbcStatsPort = 6004
|
||||
|
||||
Database.initialize(HikariConfig().apply {
|
||||
driverClassName = "nl.astraeus.jdbc.Driver"
|
||||
jdbcUrl = "jdbc:stat:webServerPort=6002:jdbc:sqlite:data/chip.db"
|
||||
username = "sa"
|
||||
password = ""
|
||||
maximumPoolSize = 25
|
||||
isAutoCommit = false
|
||||
Database.start()
|
||||
|
||||
validate()
|
||||
})
|
||||
|
||||
val sessionHandler = SessionAttachmentHandler(
|
||||
InMemorySessionManager("vst-session-manager"),
|
||||
SessionCookieConfig()
|
||||
UndertowServer.start(
|
||||
"Vst String",
|
||||
"/vst-string-worklet-ui.js"
|
||||
)
|
||||
sessionHandler.setNext(RequestHandler)
|
||||
|
||||
val server = Undertow.builder()
|
||||
.addHttpListener(Settings.port, "localhost")
|
||||
.setIoThreads(4)
|
||||
.setHandler(sessionHandler)
|
||||
.setServerOption(UndertowOptions.SHUTDOWN_TIMEOUT, 1000)
|
||||
.build()
|
||||
|
||||
println("Starting server at port ${Settings.port}...")
|
||||
server?.start()
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.*
|
||||
|
||||
object Settings {
|
||||
var runningAsRoot: Boolean = false
|
||||
var port = 9004
|
||||
var sslPort = 8443
|
||||
var connectionTimeout = 30000
|
||||
|
||||
var jdbcDriver = "nl.astraeus.jdbc.Driver"
|
||||
var jdbcConnectionUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/srp.db"
|
||||
var jdbcUser = "sa"
|
||||
var jdbcPassword = ""
|
||||
|
||||
var adminUser = "rnentjes"
|
||||
var adminPassword = "9/SG_Bd}9gWz~?j\\A.U]n9]OO"
|
||||
|
||||
fun getPropertiesFromFile(filename: String): Properties? {
|
||||
val propertiesFile = File(filename)
|
||||
return if (propertiesFile.exists()) {
|
||||
val properties = Properties()
|
||||
FileInputStream(propertiesFile).use {
|
||||
properties.load(it)
|
||||
}
|
||||
properties
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun readProperties(args: Array<String>) {
|
||||
val filename = if (args.isNotEmpty()) args[0] else "srp.properties"
|
||||
val properties = getPropertiesFromFile(filename) ?: return // return if properties couldn't be loaded
|
||||
|
||||
runningAsRoot = properties.getProperty("runningAsRoot", runningAsRoot.toString()).toBoolean()
|
||||
port = properties.getProperty("port", port.toString()).toInt()
|
||||
sslPort = properties.getProperty("sslPort", sslPort.toString()).toInt()
|
||||
connectionTimeout = properties.getProperty("connectionTimeout", connectionTimeout.toString()).toInt()
|
||||
jdbcDriver = properties.getProperty("jdbcDriver", jdbcDriver)
|
||||
jdbcConnectionUrl = properties.getProperty("jdbcConnectionUrl", jdbcConnectionUrl)
|
||||
jdbcUser = properties.getProperty("jdbcUser", jdbcUser)
|
||||
jdbcPassword = properties.getProperty("jdbcPassword", jdbcPassword)
|
||||
|
||||
adminUser = properties.getProperty("adminUser", adminUser)
|
||||
adminPassword = properties.getProperty("adminPassword", adminPassword)
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import nl.astraeus.vst.string.logger.log
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.ResultSet
|
||||
import java.sql.Timestamp
|
||||
|
||||
fun Instant.toSqlTimestamp() = Timestamp(this.toEpochMilliseconds())
|
||||
fun Timestamp.toDateTimeInstant() = Instant.fromEpochMilliseconds(this.time)
|
||||
|
||||
data class SqlStatement<T : Entity>(
|
||||
val sql: String,
|
||||
val prepareParameters: T.(PreparedStatement) -> Unit
|
||||
)
|
||||
|
||||
data class SqlQuery<T : Entity>(
|
||||
val sql: String,
|
||||
val resultMapper: (ResultSet) -> T
|
||||
)
|
||||
|
||||
abstract class QueryProvider<T : Entity> {
|
||||
abstract val tableName: String
|
||||
open val idQuery: String
|
||||
get() = "SELECT * FROM $tableName WHERE ID = ?"
|
||||
abstract val resultSetMapper: (ResultSet) -> T
|
||||
open val find: SqlQuery<T>
|
||||
get() = SqlQuery(
|
||||
idQuery,
|
||||
resultSetMapper
|
||||
)
|
||||
abstract val insert: SqlStatement<T>
|
||||
abstract val update: SqlStatement<T>
|
||||
open val delete: SqlStatement<T>
|
||||
get() = SqlStatement(
|
||||
"DELETE FROM $tableName WHERE ID = ?"
|
||||
) { ps ->
|
||||
ps.setLong(1, getPK()[0] as Long)
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BaseDao<T : Entity> {
|
||||
abstract val queryProvider: QueryProvider<T>
|
||||
open val autogeneratedPrimaryKey: Boolean = true
|
||||
|
||||
open fun insert(entity: T) {
|
||||
executeInsert(entity, "insert", queryProvider.insert)
|
||||
}
|
||||
|
||||
open fun update(entity: T): Int = executeUpdate(
|
||||
entity,
|
||||
"update",
|
||||
queryProvider.update,
|
||||
true
|
||||
)
|
||||
|
||||
open fun upsert(entity: T) {
|
||||
if ((entity.getPK()[0] as Long) == 0L) {
|
||||
insert(entity)
|
||||
} else {
|
||||
update(entity)
|
||||
}
|
||||
}
|
||||
|
||||
open fun delete(entity: T) {
|
||||
executeUpdate(entity, "delete", queryProvider.delete, true)
|
||||
}
|
||||
|
||||
open fun find(
|
||||
id: Long
|
||||
): T? {
|
||||
return executeQuery(
|
||||
"find",
|
||||
queryProvider.find
|
||||
) { ps ->
|
||||
ps.setLong(1, id)
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
protected fun executeSQLUpdate(
|
||||
sql: String,
|
||||
parameterSetter: (PreparedStatement) -> Unit
|
||||
): Int {
|
||||
return transaction { con ->
|
||||
con.prepareStatement(sql).use { ps ->
|
||||
parameterSetter(ps)
|
||||
|
||||
ps.executeUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun executeQuery(
|
||||
label: String,
|
||||
statement: SqlQuery<T>,
|
||||
prepareParameters: (PreparedStatement) -> Unit,
|
||||
): List<T> {
|
||||
return transaction { con ->
|
||||
log.debug { "Executing query [$label] - [${statement.sql}]" }
|
||||
val result = mutableListOf<T>()
|
||||
|
||||
con.prepareStatement(statement.sql)?.use { ps ->
|
||||
prepareParameters(ps)
|
||||
|
||||
val rs = ps.executeQuery()
|
||||
|
||||
while (rs.next()) {
|
||||
result.add(statement.resultMapper(rs))
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
protected fun executeInsert(
|
||||
entity: T,
|
||||
label: String,
|
||||
statement: SqlStatement<T>,
|
||||
checkSingleRow: Boolean = false
|
||||
) {
|
||||
transaction { con ->
|
||||
log.debug { "Executing insert [$label] - [${statement.sql}] - [$entity]" }
|
||||
con.prepareStatement(statement.sql)?.use { ps ->
|
||||
statement.prepareParameters(entity, ps)
|
||||
|
||||
val rows = if (checkSingleRow) {
|
||||
ps.execute()
|
||||
1
|
||||
} else {
|
||||
ps.executeUpdate()
|
||||
}
|
||||
|
||||
if (autogeneratedPrimaryKey) {
|
||||
val keyResult = ps.generatedKeys
|
||||
if (keyResult.next()) {
|
||||
entity.setPK(arrayOf(keyResult.getLong(1)))
|
||||
}
|
||||
}
|
||||
|
||||
check(rows == 1) {
|
||||
"Statement [$label] affected more than 1 row! [${statement.sql}]"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun executeUpdate(
|
||||
entity: T,
|
||||
label: String,
|
||||
statement: SqlStatement<T>,
|
||||
checkSingleRow: Boolean = false
|
||||
): Int = transaction { con ->
|
||||
var rows = 1
|
||||
|
||||
log.debug { "Executing update [$label] - [${statement.sql}] - [$entity]" }
|
||||
con.prepareStatement(statement.sql)?.use { ps ->
|
||||
statement.prepareParameters(entity, ps)
|
||||
|
||||
rows = ps.executeUpdate()
|
||||
|
||||
check(checkSingleRow || rows == 1) {
|
||||
"Statement [$label] affected more than 1 row! [${statement.sql}]"
|
||||
}
|
||||
}
|
||||
|
||||
rows
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import java.sql.Connection
|
||||
import java.util.*
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
enum class TxScope {
|
||||
REQUIRED,
|
||||
/* if needed we need to switch db, sqlite only allows one writer/connection */
|
||||
//REQUIRES_NEW
|
||||
}
|
||||
|
||||
private val currentConnection = ThreadLocal<Connection>()
|
||||
|
||||
fun <T> transaction(
|
||||
scope: TxScope = TxScope.REQUIRED,
|
||||
block: (Connection) -> T
|
||||
): T {
|
||||
val hasConnection = currentConnection.get() != null
|
||||
var oldConnection: Connection? = null
|
||||
|
||||
if (!hasConnection) {
|
||||
currentConnection.set(Database.getConnection())
|
||||
/*
|
||||
} else if (scope == TxScope.REQUIRES_NEW) {
|
||||
oldConnection = currentConnection.get()
|
||||
|
||||
currentConnection.set(Database.getConnection())
|
||||
*/
|
||||
}
|
||||
|
||||
val connection = currentConnection.get()
|
||||
|
||||
try {
|
||||
val result = block(connection)
|
||||
|
||||
connection.commit()
|
||||
|
||||
return result
|
||||
} finally {
|
||||
if (!hasConnection) {
|
||||
currentConnection.set(oldConnection)
|
||||
connection.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Database {
|
||||
|
||||
var ds: HikariDataSource? = null
|
||||
|
||||
fun initialize(config: HikariConfig) {
|
||||
val properties = Properties()
|
||||
properties["journal_mode"] = "WAL"
|
||||
|
||||
|
||||
config.dataSourceProperties = properties
|
||||
config.addDataSourceProperty("cachePrepStmts", "true")
|
||||
config.addDataSourceProperty("prepStmtCacheSize", "250")
|
||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
||||
|
||||
ds = HikariDataSource(config)
|
||||
Migrations.databaseVersionTableCreated = AtomicBoolean(false)
|
||||
Migrations.updateDatabaseIfNeeded()
|
||||
}
|
||||
|
||||
fun getConnection() = ds?.connection ?: error("Database has not been initialized!")
|
||||
|
||||
/*
|
||||
val ds: HikariDataSource
|
||||
|
||||
init {
|
||||
val properties = Properties()
|
||||
properties["journal_mode"] = "WAL"
|
||||
|
||||
val config = HikariConfig().apply {
|
||||
driverClassName = "nl.astraeus.jdbc.Driver"
|
||||
jdbcUrl = "jdbc:stat:webServerPort=6001:jdbc:sqlite:data/daw3.db"
|
||||
username = "sa"
|
||||
password = ""
|
||||
maximumPoolSize = 25
|
||||
isAutoCommit = false
|
||||
dataSourceProperties = properties
|
||||
validate()
|
||||
}
|
||||
|
||||
config.addDataSourceProperty("cachePrepStmts", "true")
|
||||
config.addDataSourceProperty("prepStmtCacheSize", "250")
|
||||
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048")
|
||||
|
||||
ds = HikariDataSource(config)
|
||||
}
|
||||
|
||||
fun getConnection() = ds.connection
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
interface Entity {
|
||||
fun getPK(): Array<Any>
|
||||
fun setPK(pks: Array<Any>)
|
||||
}
|
||||
|
||||
interface EntityId : Entity {
|
||||
var id: Long
|
||||
|
||||
override fun getPK(): Array<Any> = arrayOf(id)
|
||||
|
||||
override fun setPK(pks: Array<Any>) {
|
||||
id = pks[0] as Long
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import nl.astraeus.vst.string.logger.log
|
||||
import java.sql.Connection
|
||||
import java.sql.SQLException
|
||||
import java.sql.Timestamp
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
sealed class Migration {
|
||||
class Query(
|
||||
val query: String
|
||||
) : Migration() {
|
||||
override fun toString(): String {
|
||||
return query
|
||||
}
|
||||
}
|
||||
|
||||
class Code(
|
||||
val code: (Connection) -> Unit
|
||||
) : Migration() {
|
||||
override fun toString(): String {
|
||||
return code.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val DATABASE_MIGRATIONS = arrayOf<Migration>(
|
||||
Migration.Query(
|
||||
"""
|
||||
CREATE TABLE DATABASE_VERSION (
|
||||
ID INTEGER PRIMARY KEY,
|
||||
QUERY TEXT,
|
||||
EXECUTED TIMESTAMP
|
||||
)
|
||||
""".trimIndent()
|
||||
),
|
||||
Migration.Query(PATCH_CREATE_QUERY),
|
||||
)
|
||||
|
||||
object Migrations {
|
||||
var databaseVersionTableCreated = AtomicBoolean(false)
|
||||
|
||||
fun updateDatabaseIfNeeded() {
|
||||
try {
|
||||
transaction { con ->
|
||||
con.prepareStatement(
|
||||
"""
|
||||
SELECT MAX(ID) FROM DATABASE_VERSION
|
||||
""".trimIndent()
|
||||
).use { ps ->
|
||||
ps.executeQuery().use { rs ->
|
||||
databaseVersionTableCreated.compareAndSet(false, true)
|
||||
|
||||
if (rs.next()) {
|
||||
val maxId = rs.getInt(1)
|
||||
|
||||
for (index in maxId + 1..<DATABASE_MIGRATIONS.size) {
|
||||
executeMigration(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: SQLException) {
|
||||
if (databaseVersionTableCreated.compareAndSet(false, true)) {
|
||||
executeMigration(0)
|
||||
updateDatabaseIfNeeded()
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun executeMigration(index: Int) {
|
||||
transaction { con ->
|
||||
log.debug {
|
||||
"Executing migration $index - [${DATABASE_MIGRATIONS[index]}]"
|
||||
}
|
||||
val description = when (
|
||||
val migration = DATABASE_MIGRATIONS[index]
|
||||
) {
|
||||
is Migration.Query -> {
|
||||
con.prepareStatement(migration.query).use { ps ->
|
||||
ps.execute()
|
||||
}
|
||||
|
||||
migration.query
|
||||
}
|
||||
|
||||
is Migration.Code -> {
|
||||
migration.code(con)
|
||||
|
||||
migration.code.toString()
|
||||
}
|
||||
}
|
||||
con.prepareStatement("INSERT INTO DATABASE_VERSION VALUES (?, ?, ?)").use { ps ->
|
||||
ps.setInt(1, index)
|
||||
ps.setString(2, description)
|
||||
ps.setTimestamp(3, Timestamp(System.currentTimeMillis()))
|
||||
|
||||
ps.execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
object PatchDao : BaseDao<PatchEntity>() {
|
||||
|
||||
override val queryProvider: QueryProvider<PatchEntity>
|
||||
get() = PatchEntityQueryProvider
|
||||
|
||||
fun create(
|
||||
patchId: String,
|
||||
patch: String
|
||||
): PatchEntity {
|
||||
val result = PatchEntity(
|
||||
0,
|
||||
patchId,
|
||||
patch
|
||||
)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun findById(patchId: String): PatchEntity? = executeQuery(
|
||||
"findById",
|
||||
SqlQuery(
|
||||
"SELECT * FROM ${queryProvider.tableName} WHERE PATCH_ID = ?",
|
||||
queryProvider.resultSetMapper
|
||||
)
|
||||
) { ps ->
|
||||
ps.setString(1, patchId)
|
||||
}.firstOrNull()
|
||||
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
data class PatchEntity(
|
||||
override var id: Long,
|
||||
var patchId: String,
|
||||
var patch: String,
|
||||
var created: Instant = Clock.System.now(),
|
||||
var updated: Instant = Clock.System.now(),
|
||||
) : EntityId
|
||||
@@ -1,64 +0,0 @@
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import java.sql.ResultSet
|
||||
import java.sql.Types
|
||||
|
||||
val PATCH_CREATE_QUERY = """
|
||||
CREATE TABLE INSTRUMENTS (
|
||||
ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
PATCH_ID TEXT,
|
||||
PATCH TEXT,
|
||||
CREATED TIMESTAMP,
|
||||
UPDATED TIMESTAMP
|
||||
)
|
||||
""".trimIndent()
|
||||
|
||||
object PatchEntityQueryProvider : QueryProvider<PatchEntity>() {
|
||||
override val tableName: String
|
||||
get() = "INSTRUMENTS"
|
||||
override val resultSetMapper: (ResultSet) -> PatchEntity
|
||||
get() = { rs ->
|
||||
PatchEntity(
|
||||
rs.getLong(1),
|
||||
rs.getString(2),
|
||||
rs.getString(3),
|
||||
rs.getTimestamp(4).toDateTimeInstant(),
|
||||
rs.getTimestamp(5).toDateTimeInstant()
|
||||
)
|
||||
}
|
||||
override val insert: SqlStatement<PatchEntity>
|
||||
get() = SqlStatement(
|
||||
"""
|
||||
INSERT INTO $tableName (
|
||||
ID,
|
||||
PATCH_ID,
|
||||
PATCH,
|
||||
CREATED,
|
||||
UPDATED
|
||||
) VALUES (
|
||||
?,?,?,?,?
|
||||
)
|
||||
""".trimIndent()
|
||||
) { ps ->
|
||||
ps.setNull(1, Types.BIGINT)
|
||||
ps.setString(2, patchId)
|
||||
ps.setString(3, patch)
|
||||
ps.setTimestamp(4, created.toSqlTimestamp())
|
||||
ps.setTimestamp(5, updated.toSqlTimestamp())
|
||||
}
|
||||
override val update: SqlStatement<PatchEntity>
|
||||
get() = SqlStatement(
|
||||
"""
|
||||
UPDATE $tableName
|
||||
SET PATCH_ID = ?,
|
||||
PATCH = ?,
|
||||
UPDATED = ?
|
||||
WHERE ID = ?
|
||||
""".trimIndent()
|
||||
) { ps ->
|
||||
ps.setString(1, patchId)
|
||||
ps.setString(2, patch)
|
||||
ps.setTimestamp(3, updated.toSqlTimestamp())
|
||||
ps.setLong(4, id)
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package nl.astraeus.vst.string.web
|
||||
|
||||
import kotlinx.html.body
|
||||
import kotlinx.html.head
|
||||
import kotlinx.html.html
|
||||
import kotlinx.html.meta
|
||||
import kotlinx.html.script
|
||||
import kotlinx.html.stream.appendHTML
|
||||
import kotlinx.html.title
|
||||
|
||||
fun generateIndex(patch: String?): String {
|
||||
val result = StringBuilder();
|
||||
|
||||
if (patch == null) {
|
||||
result.appendHTML(true).html {
|
||||
head {
|
||||
title { +"VST String" }
|
||||
}
|
||||
body {
|
||||
script {
|
||||
type = "application/javascript"
|
||||
src = "/vst-string-worklet-ui.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.appendHTML(true).html {
|
||||
head {
|
||||
title { +"VST Chip" }
|
||||
meta {
|
||||
httpEquiv = "refresh"
|
||||
content = "0; url=/patch/$patch"
|
||||
}
|
||||
}
|
||||
body {
|
||||
+"Redirecting to patch $patch..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
package nl.astraeus.vst.string.web
|
||||
|
||||
import io.undertow.Handlers.websocket
|
||||
import io.undertow.server.HttpHandler
|
||||
import io.undertow.server.HttpServerExchange
|
||||
import io.undertow.server.handlers.PathHandler
|
||||
import io.undertow.server.handlers.resource.PathResourceManager
|
||||
import io.undertow.server.handlers.resource.ResourceHandler
|
||||
import io.undertow.server.session.Session
|
||||
import io.undertow.server.session.SessionConfig
|
||||
import io.undertow.server.session.SessionManager
|
||||
import io.undertow.websockets.WebSocketConnectionCallback
|
||||
import io.undertow.websockets.core.AbstractReceiveListener
|
||||
import io.undertow.websockets.core.BufferedBinaryMessage
|
||||
import io.undertow.websockets.core.BufferedTextMessage
|
||||
import io.undertow.websockets.core.WebSocketChannel
|
||||
import io.undertow.websockets.core.WebSockets
|
||||
import io.undertow.websockets.spi.WebSocketHttpExchange
|
||||
import nl.astraeus.vst.string.db.PatchDao
|
||||
import nl.astraeus.vst.string.db.PatchEntity
|
||||
import nl.astraeus.vst.string.db.transaction
|
||||
import nl.astraeus.vst.string.generateId
|
||||
import java.nio.file.Paths
|
||||
|
||||
class WebsocketHandler(
|
||||
val session: Session?
|
||||
) : AbstractReceiveListener(), WebSocketConnectionCallback {
|
||||
|
||||
override fun onConnect(exchange: WebSocketHttpExchange, channel: WebSocketChannel) {
|
||||
channel.receiveSetter.set(this)
|
||||
channel.resumeReceives()
|
||||
}
|
||||
|
||||
override fun onFullTextMessage(channel: WebSocketChannel, message: BufferedTextMessage) {
|
||||
val vstSession = session?.getAttribute("html-session") as? VstSession
|
||||
|
||||
val data = message.data
|
||||
val commandLength = data.indexOf('\n')
|
||||
if (commandLength > 0) {
|
||||
val command = data.substring(0, commandLength)
|
||||
val value = data.substring(commandLength + 1)
|
||||
|
||||
when (command) {
|
||||
"SAVE" -> {
|
||||
val patchId = vstSession?.patchId
|
||||
if (patchId != null) {
|
||||
transaction {
|
||||
val patchEntity = PatchDao.findById(patchId)
|
||||
|
||||
if (patchEntity != null) {
|
||||
PatchDao.update(patchEntity.copy(patch = value))
|
||||
} else {
|
||||
PatchDao.insert(PatchEntity(0, patchId, value))
|
||||
}
|
||||
}
|
||||
WebSockets.sendText("SAVED\n$patchId", channel, null)
|
||||
}
|
||||
}
|
||||
|
||||
"LOAD" -> {
|
||||
val patchId = vstSession?.patchId
|
||||
if (patchId != null) {
|
||||
transaction {
|
||||
val patchEntity = PatchDao.findById(patchId)
|
||||
|
||||
if (patchEntity != null) {
|
||||
WebSockets.sendText("LOAD\n${patchEntity.patch}", channel, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFullBinaryMessage(channel: WebSocketChannel?, message: BufferedBinaryMessage?) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
object WebsocketConnectHandler : HttpHandler {
|
||||
override fun handleRequest(exchange: HttpServerExchange) {
|
||||
val sessionManager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY)
|
||||
val sessionConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY)
|
||||
|
||||
val httpSession: Session? = sessionManager.getSession(exchange, sessionConfig)
|
||||
|
||||
websocket(WebsocketHandler(httpSession)).handleRequest(exchange)
|
||||
}
|
||||
}
|
||||
|
||||
object PatchHandler : HttpHandler {
|
||||
override fun handleRequest(exchange: HttpServerExchange) {
|
||||
if (exchange.requestPath.startsWith("/patch/")) {
|
||||
val patchId = exchange.requestPath.substring(7)
|
||||
val sessionManager = exchange.getAttachment(SessionManager.ATTACHMENT_KEY)
|
||||
val sessionConfig = exchange.getAttachment(SessionConfig.ATTACHMENT_KEY)
|
||||
var httpSession: Session? = sessionManager.getSession(exchange, sessionConfig)
|
||||
|
||||
if (httpSession == null) {
|
||||
httpSession = sessionManager.createSession(exchange, sessionConfig)
|
||||
}
|
||||
httpSession?.setAttribute("html-session", VstSession(patchId))
|
||||
|
||||
exchange.responseSender.send(generateIndex(null))
|
||||
} else {
|
||||
val patchId = generateId()
|
||||
|
||||
exchange.responseSender.send(generateIndex(patchId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object RequestHandler : HttpHandler {
|
||||
val resourceHandler = ResourceHandler(PathResourceManager(Paths.get("web")))
|
||||
val pathHandler = PathHandler(resourceHandler)
|
||||
|
||||
init {
|
||||
pathHandler.addExactPath("/", PatchHandler)
|
||||
pathHandler.addExactPath("/index.html", PatchHandler)
|
||||
pathHandler.addPrefixPath("/patch", PatchHandler)
|
||||
pathHandler.addExactPath("/ws", WebsocketConnectHandler)
|
||||
}
|
||||
|
||||
override fun handleRequest(exchange: HttpServerExchange) {
|
||||
pathHandler.handleRequest(exchange)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package nl.astraeus.vst.string.web
|
||||
|
||||
class VstSession(
|
||||
val patchId: String
|
||||
)
|
||||
21
version.gradle.kts
Normal file
21
version.gradle.kts
Normal file
@@ -0,0 +1,21 @@
|
||||
import java.util.Date
|
||||
import java.util.Properties
|
||||
|
||||
tasks.register("generateVersionProperties") {
|
||||
doLast {
|
||||
val versionDir = layout.buildDirectory.dir("processedResources/jvm/main")
|
||||
val versionFile = versionDir.get().file("version.properties").asFile
|
||||
versionDir.get().asFile.mkdirs()
|
||||
|
||||
val properties = Properties().apply {
|
||||
setProperty("group", project.group.toString())
|
||||
setProperty("name", project.name.toString())
|
||||
setProperty("version", project.version.toString())
|
||||
setProperty("buildTime", Date().toString())
|
||||
}
|
||||
|
||||
versionFile.writer().use { writer ->
|
||||
properties.store(writer, "Version information")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user