Also search on name when setting midi port
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
package nl.astraeus.vst.chip
|
||||
|
||||
import kotlin.js.JsName
|
||||
|
||||
data class PatchDTO(
|
||||
@JsName("waveform")
|
||||
val waveform: Int = 0,
|
||||
@JsName("midiId")
|
||||
val midiId: String = "",
|
||||
@JsName("midiName")
|
||||
val midiName: String = "",
|
||||
@JsName("midiChannel")
|
||||
var midiChannel: Int = 0,
|
||||
@JsName("volume")
|
||||
var volume: Double = 0.75,
|
||||
@JsName("dutyCycle")
|
||||
var dutyCycle: Double = 0.5,
|
||||
@JsName("fmModFreq")
|
||||
var fmModFreq: Double = 0.0,
|
||||
@JsName("fmModAmp")
|
||||
var fmModAmp: Double = 0.0,
|
||||
@JsName("amModFreq")
|
||||
var amModFreq: Double = 0.0,
|
||||
@JsName("amModAmp")
|
||||
var amModAmp: Double = 0.0,
|
||||
@JsName("attack")
|
||||
var attack: Double = 0.1,
|
||||
@JsName("decay")
|
||||
var decay: Double = 0.2,
|
||||
@JsName("sustain")
|
||||
var sustain: Double = 0.5,
|
||||
@JsName("release")
|
||||
var release: Double = 0.2,
|
||||
)
|
||||
24
src/commonMain/kotlin/nl/astraeus/vst/string/PatchDTO.kt
Normal file
24
src/commonMain/kotlin/nl/astraeus/vst/string/PatchDTO.kt
Normal file
@@ -0,0 +1,24 @@
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
import kotlin.js.ExperimentalJsExport
|
||||
import kotlin.js.JsExport
|
||||
import kotlin.js.JsName
|
||||
|
||||
@ExperimentalJsExport
|
||||
@JsExport
|
||||
data class PatchDTO(
|
||||
@JsName("midiId")
|
||||
val midiId: String = "",
|
||||
@JsName("midiName")
|
||||
val midiName: String = "",
|
||||
@JsName("midiChannel")
|
||||
var midiChannel: Int = 0,
|
||||
@JsName("volume")
|
||||
var volume: Double = 0.75,
|
||||
@JsName("damping")
|
||||
var damping: Double = 0.5,
|
||||
@JsName("delay")
|
||||
var delay: Double = 0.0,
|
||||
@JsName("delayDepth")
|
||||
var delayDepth: Double = 0.0,
|
||||
)
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip.logger
|
||||
package nl.astraeus.vst.string.logger
|
||||
|
||||
val log = Logger
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
package nl.astraeus.vst.chip.audio
|
||||
|
||||
import nl.astraeus.vst.chip.AudioContext
|
||||
|
||||
object AudioContextHandler {
|
||||
val audioContext: dynamic = AudioContext()
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
package nl.astraeus.vst.chip.audio
|
||||
|
||||
import nl.astraeus.vst.chip.PatchDTO
|
||||
import nl.astraeus.vst.chip.view.MainView
|
||||
import nl.astraeus.vst.chip.view.WaveformView
|
||||
import nl.astraeus.vst.ui.util.uInt8ArrayOf
|
||||
import org.khronos.webgl.Float32Array
|
||||
import org.khronos.webgl.Uint8Array
|
||||
import org.khronos.webgl.get
|
||||
import org.w3c.dom.MessageEvent
|
||||
import kotlin.experimental.and
|
||||
|
||||
object VstChipWorklet : AudioNode(
|
||||
"/vst-chip-worklet.js",
|
||||
"vst-chip-processor"
|
||||
) {
|
||||
var waveform: Int = 0
|
||||
set(value) {
|
||||
field = value
|
||||
postMessage("waveform\n$value")
|
||||
}
|
||||
var midiChannel = 0
|
||||
set(value) {
|
||||
check(value in 0..15) {
|
||||
"Midi channel must be between 0 and 15."
|
||||
}
|
||||
field = value
|
||||
postMessage("set_channel\n${midiChannel}")
|
||||
}
|
||||
var volume = 0.75
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 7, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var dutyCycle = 0.5
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x47, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var fmModFreq = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x4a, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var fmModAmp = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x4b, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var amModFreq = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x4c, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var amModAmp = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x4d, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
|
||||
var attack = 0.1
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x49, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var decay = 0.2
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x4b, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var sustain = 0.5
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x46, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var release = 0.2
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x48, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
|
||||
var recording: Float32Array? = null
|
||||
|
||||
override fun onMessage(message: MessageEvent) {
|
||||
//console.log("Message from worklet: ", message)
|
||||
|
||||
val data = message.data
|
||||
if (data is Float32Array) {
|
||||
this.recording = data
|
||||
WaveformView.requestUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
fun postDirectlyToWorklet(msg: Any) {
|
||||
super.postMessage(msg)
|
||||
}
|
||||
|
||||
override fun postMessage(msg: Any) {
|
||||
if (msg is Uint8Array) {
|
||||
if (
|
||||
msg.length == 3
|
||||
&& (msg[0] and 0xf == midiChannel.toByte())
|
||||
&& (msg[0] and 0xf0.toByte() == 0xb0.toByte())
|
||||
) {
|
||||
val knob = msg[1]
|
||||
val value = msg[2]
|
||||
|
||||
handleIncomingMidi(knob, value)
|
||||
} else {
|
||||
super.postMessage(msg)
|
||||
}
|
||||
} else {
|
||||
super.postMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleIncomingMidi(knob: Byte, value: Byte) {
|
||||
when (knob) {
|
||||
0x46.toByte() -> {
|
||||
volume = value / 127.0
|
||||
MainView.requestUpdate()
|
||||
}
|
||||
|
||||
0x4a.toByte() -> {
|
||||
dutyCycle = value / 127.0
|
||||
MainView.requestUpdate()
|
||||
}
|
||||
|
||||
0x4b.toByte() -> {
|
||||
fmModFreq = value / 127.0
|
||||
MainView.requestUpdate()
|
||||
}
|
||||
|
||||
0x4c.toByte() -> {
|
||||
fmModAmp = value / 127.0
|
||||
MainView.requestUpdate()
|
||||
}
|
||||
|
||||
0x47.toByte() -> {
|
||||
amModFreq = value / 127.0
|
||||
MainView.requestUpdate()
|
||||
}
|
||||
|
||||
0x48.toByte() -> {
|
||||
amModAmp = value / 127.0
|
||||
MainView.requestUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun load(patch: PatchDTO) {
|
||||
waveform = patch.waveform
|
||||
midiChannel = patch.midiChannel
|
||||
volume = patch.volume
|
||||
dutyCycle = patch.dutyCycle
|
||||
fmModFreq = patch.fmModFreq
|
||||
fmModAmp = patch.fmModAmp
|
||||
amModFreq = patch.amModFreq
|
||||
amModAmp = patch.amModAmp
|
||||
attack = patch.attack
|
||||
decay = patch.decay
|
||||
sustain = patch.sustain
|
||||
release = patch.release
|
||||
}
|
||||
|
||||
fun save(): PatchDTO {
|
||||
return PatchDTO(
|
||||
waveform = waveform,
|
||||
midiChannel = midiChannel,
|
||||
volume = volume,
|
||||
dutyCycle = dutyCycle,
|
||||
fmModFreq = fmModFreq,
|
||||
fmModAmp = fmModAmp,
|
||||
amModFreq = amModFreq,
|
||||
amModAmp = amModAmp,
|
||||
attack = attack,
|
||||
decay = decay,
|
||||
sustain = sustain,
|
||||
release = release
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
external class AudioContext {
|
||||
var sampleRate: Int
|
||||
@@ -1,12 +1,12 @@
|
||||
package nl.astraeus.vst.chip
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
import kotlinx.browser.document
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.komp.UnsafeMode
|
||||
import nl.astraeus.vst.chip.logger.log
|
||||
import nl.astraeus.vst.chip.midi.Midi
|
||||
import nl.astraeus.vst.chip.view.MainView
|
||||
import nl.astraeus.vst.chip.ws.WebsocketClient
|
||||
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
|
||||
|
||||
fun main() {
|
||||
@@ -0,0 +1,10 @@
|
||||
package nl.astraeus.vst.string.audio
|
||||
|
||||
import nl.astraeus.vst.string.AudioContext
|
||||
|
||||
object AudioContextHandler {
|
||||
val audioContext: dynamic = AudioContext()
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package nl.astraeus.vst.chip.audio
|
||||
package nl.astraeus.vst.string.audio
|
||||
|
||||
import nl.astraeus.vst.chip.AudioWorkletNode
|
||||
import nl.astraeus.vst.chip.AudioWorkletNodeParameters
|
||||
import nl.astraeus.vst.chip.audio.AudioContextHandler.audioContext
|
||||
import nl.astraeus.vst.string.AudioWorkletNode
|
||||
import nl.astraeus.vst.string.AudioWorkletNodeParameters
|
||||
import nl.astraeus.vst.string.audio.AudioContextHandler.audioContext
|
||||
import org.w3c.dom.MessageEvent
|
||||
import org.w3c.dom.MessagePort
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
@file:OptIn(ExperimentalJsExport::class)
|
||||
|
||||
package nl.astraeus.vst.string.audio
|
||||
|
||||
import nl.astraeus.vst.string.PatchDTO
|
||||
import nl.astraeus.vst.string.view.MainView
|
||||
import nl.astraeus.vst.string.view.WaveformView
|
||||
import nl.astraeus.vst.ui.util.uInt8ArrayOf
|
||||
import org.khronos.webgl.Float32Array
|
||||
import org.khronos.webgl.Uint8Array
|
||||
import org.khronos.webgl.get
|
||||
import org.w3c.dom.MessageEvent
|
||||
import kotlin.experimental.and
|
||||
|
||||
object VstStringWorklet : AudioNode(
|
||||
"/vst-string-worklet.js",
|
||||
"vst-string-processor"
|
||||
) {
|
||||
var midiChannel = 0
|
||||
set(value) {
|
||||
check(value in 0..15) {
|
||||
"Midi channel must be between 0 and 15."
|
||||
}
|
||||
field = value
|
||||
postMessage("set_channel\n${midiChannel}")
|
||||
}
|
||||
var volume = 0.75
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 7, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var damping = 0.996
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x47, ((value - 0.8) * 127).toInt())
|
||||
)
|
||||
}
|
||||
var delay = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x4e, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
var delayDepth = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 0x4f, (value * 127).toInt())
|
||||
)
|
||||
}
|
||||
|
||||
var recording: Float32Array? = null
|
||||
|
||||
override fun onMessage(message: MessageEvent) {
|
||||
//console.log("Message from worklet: ", message)
|
||||
|
||||
val data = message.data
|
||||
if (data is Float32Array) {
|
||||
this.recording = data
|
||||
WaveformView.requestUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
fun postDirectlyToWorklet(msg: Any) {
|
||||
super.postMessage(msg)
|
||||
}
|
||||
|
||||
override fun postMessage(msg: Any) {
|
||||
if (msg is Uint8Array) {
|
||||
if (
|
||||
msg.length == 3
|
||||
&& (msg[0] and 0xf == midiChannel.toByte())
|
||||
&& (msg[0] and 0xf0.toByte() == 0xb0.toByte())
|
||||
) {
|
||||
val knob = msg[1]
|
||||
val value = msg[2]
|
||||
|
||||
handleIncomingMidi(knob, value)
|
||||
} else {
|
||||
super.postMessage(msg)
|
||||
}
|
||||
} else {
|
||||
super.postMessage(msg)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleIncomingMidi(knob: Byte, value: Byte) {
|
||||
when (knob) {
|
||||
0x46.toByte() -> {
|
||||
volume = value / 127.0
|
||||
MainView.requestUpdate()
|
||||
}
|
||||
|
||||
0x4a.toByte() -> {
|
||||
damping = value / 127.0
|
||||
MainView.requestUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun load(patch: PatchDTO) {
|
||||
midiChannel = patch.midiChannel
|
||||
volume = patch.volume
|
||||
damping = patch.damping
|
||||
delay = patch.delay
|
||||
delayDepth = patch.delayDepth
|
||||
}
|
||||
|
||||
fun save(): PatchDTO {
|
||||
return PatchDTO(
|
||||
midiChannel = midiChannel,
|
||||
volume = volume,
|
||||
damping = damping,
|
||||
delay = delay,
|
||||
delayDepth = delayDepth,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@file:OptIn(ExperimentalJsExport::class)
|
||||
|
||||
package nl.astraeus.vst.chip.midi
|
||||
package nl.astraeus.vst.string.midi
|
||||
|
||||
import kotlinx.browser.window
|
||||
import org.khronos.webgl.Uint8Array
|
||||
@@ -1,8 +1,8 @@
|
||||
package nl.astraeus.vst.chip.midi
|
||||
package nl.astraeus.vst.string.midi
|
||||
|
||||
import kotlinx.browser.window
|
||||
import nl.astraeus.vst.chip.audio.VstChipWorklet
|
||||
import nl.astraeus.vst.chip.view.MainView
|
||||
import nl.astraeus.vst.string.audio.VstStringWorklet
|
||||
import nl.astraeus.vst.string.view.MainView
|
||||
import org.khronos.webgl.Uint8Array
|
||||
import org.khronos.webgl.get
|
||||
|
||||
@@ -125,7 +125,7 @@ object Midi {
|
||||
hex.append(" ")
|
||||
}
|
||||
console.log("Midi message:", hex)
|
||||
VstChipWorklet.postMessage(
|
||||
VstStringWorklet.postMessage(
|
||||
message.data
|
||||
)
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
package nl.astraeus.vst.chip.view
|
||||
@file:OptIn(ExperimentalJsExport::class)
|
||||
|
||||
package nl.astraeus.vst.string.view
|
||||
|
||||
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
|
||||
@@ -32,72 +31,30 @@ import nl.astraeus.css.style.Style
|
||||
import nl.astraeus.css.style.cls
|
||||
import nl.astraeus.komp.HtmlBuilder
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.komp.currentElement
|
||||
import nl.astraeus.vst.chip.audio.VstChipWorklet
|
||||
import nl.astraeus.vst.chip.audio.VstChipWorklet.midiChannel
|
||||
import nl.astraeus.vst.chip.midi.Midi
|
||||
import nl.astraeus.vst.chip.ws.WebsocketClient
|
||||
import nl.astraeus.vst.ui.components.KnobComponent
|
||||
import nl.astraeus.vst.string.PhysicalString
|
||||
import nl.astraeus.vst.string.audio.VstStringWorklet
|
||||
import nl.astraeus.vst.string.audio.VstStringWorklet.midiChannel
|
||||
import nl.astraeus.vst.string.midi.Midi
|
||||
import nl.astraeus.vst.string.ws.WebsocketClient
|
||||
import nl.astraeus.vst.ui.components.ExpKnobComponent
|
||||
import nl.astraeus.vst.ui.css.Css
|
||||
import nl.astraeus.vst.ui.css.Css.defineCss
|
||||
import nl.astraeus.vst.ui.css.Css.noTextSelect
|
||||
import nl.astraeus.vst.ui.css.CssName
|
||||
import nl.astraeus.vst.ui.css.hover
|
||||
import nl.astraeus.vst.ui.util.uInt8ArrayOf
|
||||
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
|
||||
|
||||
object WaveformView: Komponent() {
|
||||
|
||||
init {
|
||||
window.requestAnimationFrame(::onAnimationFrame)
|
||||
}
|
||||
|
||||
fun onAnimationFrame(time: Double) {
|
||||
if (MainView.started) {
|
||||
VstChipWorklet.postMessage("start_recording")
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(::onAnimationFrame)
|
||||
}
|
||||
|
||||
override fun HtmlBuilder.render() {
|
||||
div {
|
||||
if (VstChipWorklet.recording != null) {
|
||||
canvas {
|
||||
width = "1000"
|
||||
height = "400"
|
||||
val ctx = (currentElement() as? HTMLCanvasElement)?.getContext("2d") as? CanvasRenderingContext2D
|
||||
val data = VstChipWorklet.recording
|
||||
if (ctx != null && data != null) {
|
||||
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 = 1000.0 / data.length
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = "rgba(0, 255, 255, 0.5)"
|
||||
ctx.moveTo(0.0, halfHeight)
|
||||
for (i in 0 until data.length) {
|
||||
ctx.lineTo(i * step, halfHeight - data[i] * halfHeight)
|
||||
}
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object MainView : Komponent(), CssName {
|
||||
private var messages: MutableList<String> = ArrayList()
|
||||
var started = false
|
||||
val playString = PhysicalStringView(
|
||||
PhysicalString(
|
||||
sampleRate = 48000,
|
||||
damping = 0.996,
|
||||
)
|
||||
)
|
||||
|
||||
init {
|
||||
css()
|
||||
@@ -119,7 +76,7 @@ object MainView : Komponent(), CssName {
|
||||
div(StartButtonCss.name) {
|
||||
+"START"
|
||||
onClickFunction = {
|
||||
VstChipWorklet.create {
|
||||
VstStringWorklet.create {
|
||||
started = true
|
||||
requestUpdate()
|
||||
WebsocketClient.send("LOAD\n")
|
||||
@@ -130,7 +87,7 @@ object MainView : Komponent(), CssName {
|
||||
}
|
||||
}
|
||||
h1 {
|
||||
+"VST Chip"
|
||||
+"VST Guitar"
|
||||
}
|
||||
div {
|
||||
span {
|
||||
@@ -162,11 +119,11 @@ object MainView : Komponent(), CssName {
|
||||
+"channel:"
|
||||
input {
|
||||
type = InputType.number
|
||||
value = VstChipWorklet.midiChannel.toString()
|
||||
value = midiChannel.toString()
|
||||
onInputFunction = { event ->
|
||||
val target = event.target as HTMLInputElement
|
||||
println("onInput channel: $target")
|
||||
VstChipWorklet.midiChannel = target.value.toInt()
|
||||
VstStringWorklet.midiChannel = target.value.toInt()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +132,7 @@ object MainView : Komponent(), CssName {
|
||||
span(ButtonBarCss.name) {
|
||||
+"SAVE"
|
||||
onClickFunction = {
|
||||
val patch = VstChipWorklet.save().copy(
|
||||
val patch = VstStringWorklet.save().copy(
|
||||
midiId = Midi.currentInput?.id ?: "",
|
||||
midiName = Midi.currentInput?.name ?: ""
|
||||
)
|
||||
@@ -186,189 +143,29 @@ object MainView : Komponent(), CssName {
|
||||
span(ButtonBarCss.name) {
|
||||
+"STOP"
|
||||
onClickFunction = {
|
||||
VstChipWorklet.postDirectlyToWorklet(
|
||||
VstStringWorklet.postDirectlyToWorklet(
|
||||
uInt8ArrayOf(0xb0 + midiChannel, 123, 0)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
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(
|
||||
KnobComponent(
|
||||
value = VstChipWorklet.volume,
|
||||
ExpKnobComponent(
|
||||
value = VstStringWorklet.volume,
|
||||
label = "Volume",
|
||||
minValue = 0.0,
|
||||
minValue = 0.005,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.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(
|
||||
KnobComponent(
|
||||
value = VstChipWorklet.fmModFreq,
|
||||
label = "FM Freq",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
VstChipWorklet.fmModFreq = value
|
||||
}
|
||||
)
|
||||
include(
|
||||
KnobComponent(
|
||||
value = VstChipWorklet.fmModAmp,
|
||||
label = "FM Ampl",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
VstChipWorklet.fmModAmp = value
|
||||
}
|
||||
)
|
||||
include(
|
||||
KnobComponent(
|
||||
value = VstChipWorklet.amModFreq,
|
||||
label = "AM Freq",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
VstChipWorklet.amModFreq = value
|
||||
}
|
||||
)
|
||||
include(
|
||||
KnobComponent(
|
||||
value = VstChipWorklet.amModAmp,
|
||||
label = "AM Ampl",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
VstChipWorklet.amModAmp = value
|
||||
}
|
||||
)
|
||||
}
|
||||
div(ControlsCss.name) {
|
||||
include(
|
||||
KnobComponent(
|
||||
value = VstChipWorklet.attack,
|
||||
label = "Attack",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
VstChipWorklet.attack = value
|
||||
}
|
||||
)
|
||||
include(
|
||||
KnobComponent(
|
||||
value = VstChipWorklet.decay,
|
||||
label = "Decay",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
VstChipWorklet.decay = value
|
||||
}
|
||||
)
|
||||
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(
|
||||
KnobComponent(
|
||||
value = VstChipWorklet.release,
|
||||
label = "Release",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
VstChipWorklet.release = value
|
||||
VstStringWorklet.volume = value
|
||||
}
|
||||
)
|
||||
}
|
||||
include(WaveformView)
|
||||
include(playString)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
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) {
|
||||
if (MainView.started) {
|
||||
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) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package nl.astraeus.vst.string.view
|
||||
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.html.canvas
|
||||
import kotlinx.html.div
|
||||
import nl.astraeus.komp.HtmlBuilder
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.komp.currentElement
|
||||
import nl.astraeus.vst.string.audio.VstStringWorklet
|
||||
import org.khronos.webgl.get
|
||||
import org.w3c.dom.CanvasRenderingContext2D
|
||||
import org.w3c.dom.HTMLCanvasElement
|
||||
|
||||
object WaveformView : Komponent() {
|
||||
|
||||
init {
|
||||
window.requestAnimationFrame(::onAnimationFrame)
|
||||
}
|
||||
|
||||
fun onAnimationFrame(time: Double) {
|
||||
if (MainView.started) {
|
||||
VstStringWorklet.postMessage("start_recording")
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(::onAnimationFrame)
|
||||
}
|
||||
|
||||
override fun HtmlBuilder.render() {
|
||||
div {
|
||||
if (VstStringWorklet.recording != null) {
|
||||
canvas {
|
||||
width = "1000"
|
||||
height = "400"
|
||||
val ctx = (currentElement() as? HTMLCanvasElement)?.getContext("2d") as? CanvasRenderingContext2D
|
||||
val data = VstStringWorklet.recording
|
||||
if (ctx != null && data != null) {
|
||||
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 = 1000.0 / data.length
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = "rgba(0, 255, 255, 0.5)"
|
||||
ctx.moveTo(0.0, halfHeight)
|
||||
for (i in 0 until data.length) {
|
||||
ctx.lineTo(i * step, halfHeight - data[i] * halfHeight)
|
||||
}
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
package nl.astraeus.vst.chip.ws
|
||||
package nl.astraeus.vst.string.ws
|
||||
|
||||
import kotlinx.browser.window
|
||||
import nl.astraeus.vst.chip.PatchDTO
|
||||
import nl.astraeus.vst.chip.audio.VstChipWorklet
|
||||
import nl.astraeus.vst.chip.midi.Midi
|
||||
import nl.astraeus.vst.chip.view.MainView
|
||||
import nl.astraeus.vst.string.PatchDTO
|
||||
import nl.astraeus.vst.string.audio.VstStringWorklet
|
||||
import nl.astraeus.vst.string.midi.Midi
|
||||
import nl.astraeus.vst.string.view.MainView
|
||||
import org.w3c.dom.MessageEvent
|
||||
import org.w3c.dom.WebSocket
|
||||
import org.w3c.dom.events.Event
|
||||
@@ -17,7 +17,7 @@ object WebsocketClient {
|
||||
close()
|
||||
|
||||
websocket = if (window.location.hostname.contains("localhost") || window.location.hostname.contains("192.168")) {
|
||||
WebSocket("ws://${window.location.hostname}:9000/ws")
|
||||
WebSocket("ws://${window.location.hostname}:${window.location.port}/ws")
|
||||
} else {
|
||||
WebSocket("wss://${window.location.hostname}/ws")
|
||||
}
|
||||
@@ -87,7 +87,7 @@ object WebsocketClient {
|
||||
val patch = JSON.parse<PatchDTO>(patchJson)
|
||||
|
||||
Midi.setInput(patch.midiId, patch.midiName)
|
||||
VstChipWorklet.load(patch)
|
||||
VstStringWorklet.load(patch)
|
||||
MainView.requestUpdate()
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<script type="application/javascript" src="vst-chip-worklet-ui.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
1622
src/jvmMain/java/BarWavesCanvas.java
Normal file
1622
src/jvmMain/java/BarWavesCanvas.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
import java.security.SecureRandom
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import io.undertow.Undertow
|
||||
@@ -6,10 +6,10 @@ 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.chip.db.Database
|
||||
import nl.astraeus.vst.chip.logger.LogLevel
|
||||
import nl.astraeus.vst.chip.logger.Logger
|
||||
import nl.astraeus.vst.chip.web.RequestHandler
|
||||
import nl.astraeus.vst.string.db.Database
|
||||
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
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip
|
||||
package nl.astraeus.vst.string
|
||||
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
@@ -6,7 +6,7 @@ import java.util.*
|
||||
|
||||
object Settings {
|
||||
var runningAsRoot: Boolean = false
|
||||
var port = 9000
|
||||
var port = 9004
|
||||
var sslPort = 8443
|
||||
var connectionTimeout = 30000
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package nl.astraeus.vst.chip.db
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import kotlinx.datetime.Instant
|
||||
import nl.astraeus.vst.chip.logger.log
|
||||
import nl.astraeus.vst.string.logger.log
|
||||
import java.sql.PreparedStatement
|
||||
import java.sql.ResultSet
|
||||
import java.sql.Timestamp
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip.db
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip.db
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
interface Entity {
|
||||
fun getPK(): Array<Any>
|
||||
@@ -1,6 +1,6 @@
|
||||
package nl.astraeus.vst.chip.db
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import nl.astraeus.vst.chip.logger.log
|
||||
import nl.astraeus.vst.string.logger.log
|
||||
import java.sql.Connection
|
||||
import java.sql.SQLException
|
||||
import java.sql.Timestamp
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip.db
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
object PatchDao : BaseDao<PatchEntity>() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip.db
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip.db
|
||||
package nl.astraeus.vst.string.db
|
||||
|
||||
import java.sql.ResultSet
|
||||
import java.sql.Types
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip.web
|
||||
package nl.astraeus.vst.string.web
|
||||
|
||||
import kotlinx.html.body
|
||||
import kotlinx.html.head
|
||||
@@ -14,12 +14,12 @@ fun generateIndex(patch: String?): String {
|
||||
if (patch == null) {
|
||||
result.appendHTML(true).html {
|
||||
head {
|
||||
title { +"VST Chip" }
|
||||
title { +"VST String" }
|
||||
}
|
||||
body {
|
||||
script {
|
||||
type = "application/javascript"
|
||||
src = "/vst-chip-worklet-ui.js"
|
||||
src = "/vst-string-worklet-ui.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip.web
|
||||
package nl.astraeus.vst.string.web
|
||||
|
||||
import io.undertow.Handlers.websocket
|
||||
import io.undertow.server.HttpHandler
|
||||
@@ -16,10 +16,10 @@ 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.chip.db.PatchDao
|
||||
import nl.astraeus.vst.chip.db.PatchEntity
|
||||
import nl.astraeus.vst.chip.db.transaction
|
||||
import nl.astraeus.vst.chip.generateId
|
||||
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(
|
||||
@@ -1,4 +1,4 @@
|
||||
package nl.astraeus.vst.chip.web
|
||||
package nl.astraeus.vst.string.web
|
||||
|
||||
class VstSession(
|
||||
val patchId: String
|
||||
Reference in New Issue
Block a user