Also search on name when setting midi port

This commit is contained in:
2024-07-13 16:44:33 +02:00
parent f2269c8865
commit 8df6a4fff6
45 changed files with 2387 additions and 934 deletions

View File

@@ -2,7 +2,7 @@
<artifact type="jar" name="audio-worklet-js-1.0.0-SNAPSHOT"> <artifact type="jar" name="audio-worklet-js-1.0.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/audio-worklet/build/libs</output-path> <output-path>$PROJECT_DIR$/audio-worklet/build/libs</output-path>
<root id="archive" name="audio-worklet-js-1.0.0-SNAPSHOT.jar"> <root id="archive" name="audio-worklet-js-1.0.0-SNAPSHOT.jar">
<element id="module-output" name="vst-chip.audio-worklet.jsMain" /> <element id="module-output" name="vst-string.audio-worklet.jsMain" />
</root> </root>
</artifact> </artifact>
</component> </component>

View File

@@ -2,7 +2,7 @@
<artifact type="jar" name="audio-worklet-jvm-1.0.0-SNAPSHOT"> <artifact type="jar" name="audio-worklet-jvm-1.0.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/audio-worklet/build/libs</output-path> <output-path>$PROJECT_DIR$/audio-worklet/build/libs</output-path>
<root id="archive" name="audio-worklet-jvm-1.0.0-SNAPSHOT.jar"> <root id="archive" name="audio-worklet-jvm-1.0.0-SNAPSHOT.jar">
<element id="module-output" name="vst-chip.audio-worklet.jvmMain" /> <element id="module-output" name="vst-string.audio-worklet.jvmMain" />
</root> </root>
</artifact> </artifact>
</component> </component>

View File

@@ -2,7 +2,7 @@
<artifact type="jar" name="common-js-1.0.0-SNAPSHOT"> <artifact type="jar" name="common-js-1.0.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/common/build/libs</output-path> <output-path>$PROJECT_DIR$/common/build/libs</output-path>
<root id="archive" name="common-js-1.0.0-SNAPSHOT.jar"> <root id="archive" name="common-js-1.0.0-SNAPSHOT.jar">
<element id="module-output" name="vst-chip.common.jsMain" /> <element id="module-output" name="vst-string.common.jsMain" />
</root> </root>
</artifact> </artifact>
</component> </component>

View File

@@ -2,7 +2,7 @@
<artifact type="jar" name="common-jvm-1.0.0-SNAPSHOT"> <artifact type="jar" name="common-jvm-1.0.0-SNAPSHOT">
<output-path>$PROJECT_DIR$/common/build/libs</output-path> <output-path>$PROJECT_DIR$/common/build/libs</output-path>
<root id="archive" name="common-jvm-1.0.0-SNAPSHOT.jar"> <root id="archive" name="common-jvm-1.0.0-SNAPSHOT.jar">
<element id="module-output" name="vst-chip.common.jvmMain" /> <element id="module-output" name="vst-string.common.jvmMain" />
</root> </root>
</artifact> </artifact>
</component> </component>

View File

@@ -4,7 +4,7 @@
<option name="executionName"/> <option name="executionName"/>
<option name="externalProjectPath" value="$PROJECT_DIR$"/> <option name="externalProjectPath" value="$PROJECT_DIR$"/>
<option name="externalSystemIdString" value="GRADLE"/> <option name="externalSystemIdString" value="GRADLE"/>
<option name="scriptParameters" value="-DmainClass=nl.astraeus.vst.chip.MainKt --quiet"/> <option name="scriptParameters" value="-DmainClass=nl.astraeus.vst.string.MainKt --quiet"/>
<option name="taskDescriptions"> <option name="taskDescriptions">
<list/> <list/>
</option> </option>

View File

@@ -21,13 +21,13 @@ kotlin {
browser { browser {
commonWebpackConfig { commonWebpackConfig {
outputFileName = "vst-chip-worklet.js" outputFileName = "vst-string-worklet.js"
sourceMaps = true sourceMaps = true
} }
webpackTask { webpackTask {
output.libraryTarget = KotlinWebpackOutput.Target.VAR output.libraryTarget = KotlinWebpackOutput.Target.VAR
output.library = "vstChipWorklet" output.library = "vstStringWorklet"
} }
distribution { distribution {

View File

@@ -1,393 +0,0 @@
@file:OptIn(ExperimentalJsExport::class)
package nl.astraeus.vst.chip
import nl.astraeus.vst.ADSR
import nl.astraeus.vst.AudioWorkletProcessor
import nl.astraeus.vst.Note
import nl.astraeus.vst.currentTime
import nl.astraeus.vst.registerProcessor
import nl.astraeus.vst.sampleRate
import org.khronos.webgl.Float32Array
import org.khronos.webgl.Int32Array
import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get
import org.khronos.webgl.set
import org.w3c.dom.MessageEvent
import kotlin.math.PI
import kotlin.math.min
import kotlin.math.sin
val POLYPHONICS = 10
val PI2 = PI * 2
@ExperimentalJsExport
@JsExport
class PlayingNote(
val note: Int,
var velocity: Int = 0
) {
fun retrigger(velocity: Int) {
this.velocity = velocity
sample = 0
noteStart = currentTime
noteRelease = null
}
var noteStart = currentTime
var noteRelease: Double? = null
var cycleOffset = 0.0
var sample = 0
var actualVolume = 0f
}
enum class Waveform {
SINE,
SQUARE,
TRIANGLE,
SAWTOOTH
}
@ExperimentalJsExport
@JsExport
enum class RecordingState {
STOPPED,
WAITING_TO_START,
RECORDING
}
@ExperimentalJsExport
@JsExport
class VstChipProcessor : AudioWorkletProcessor() {
var midiChannel = 0
val notes = Array<PlayingNote?>(POLYPHONICS) { null }
var waveform = Waveform.SINE.ordinal
var volume = 0.75f
var dutyCycle = 0.5
var fmFreq = 0.0
var fmAmp = 0.0
var amFreq = 0.0
var amAmp = 0.0
var attack = 0.1
var decay = 0.2
var sustain = 0.5
var release = 0.2
val sampleLength = 1 / sampleRate.toDouble()
val recordingBuffer = Float32Array(sampleRate / 60)
var recordingState = RecordingState.STOPPED
var recordingSample = 0
var recordingStart = 0
init {
this.port.onmessage = ::handleMessage
Note.updateSampleRate(sampleRate)
}
private fun handleMessage(message: MessageEvent) {
//console.log("VstChipProcessor: Received message:", message.data)
val data = message.data
try {
when (data) {
is String -> {
when(data) {
"start_recording" -> {
port.postMessage(recordingBuffer)
if (recordingState == RecordingState.STOPPED) {
recordingState = RecordingState.WAITING_TO_START
recordingSample = 0
}
}
else ->
if (data.startsWith("set_channel")) {
val parts = data.split('\n')
if (parts.size == 2) {
midiChannel = parts[1].toInt()
println("Setting channel: $midiChannel")
}
} else if (data.startsWith("waveform")) {
val parts = data.split('\n')
if (parts.size == 2) {
waveform =parts[1].toInt()
println("Setting waveform: $waveform")
}
}
}
}
is Uint8Array -> {
val data32 = Int32Array(data.length)
for (i in 0 until data.length) {
data32[i] = (data[i].toInt() and 0xff)
}
playMidi(data32)
}
is Int32Array -> {
playMidi(data)
}
else ->
console.error("Don't kow how to handle message", message)
}
} catch(e: Exception) {
console.log(e.message, e)
}
}
private fun playMidi(bytes: Int32Array) {
console.log("playMidi", bytes)
if (bytes.length > 0) {
var cmdByte = bytes[0]
val channelCmd = ((cmdByte shr 4) and 0xf) != 0xf0
val channel = cmdByte and 0xf
println("Channel cmd: $channelCmd")
if (channelCmd && channel != midiChannel) {
console.log("Wrong channel", midiChannel, bytes)
return
}
cmdByte = cmdByte and 0xf0
//console.log("Received", bytes)
when(cmdByte) {
0x90 -> {
if (bytes.length == 3) {
val note = bytes[1]
val velocity = bytes[2]
if (velocity > 0) {
noteOn(note, velocity)
} else {
noteOff(note)
}
}
}
0x80 -> {
if (bytes.length >= 2) {
val note = bytes[1]
noteOff(note)
}
}
0xc9 -> {
if (bytes.length >= 1) {
val waveform = bytes[1]
if (waveform < 4) {
this.waveform = waveform
}
}
}
0xb0 -> {
if (bytes.length == 3) {
val knob = bytes[1]
val value = bytes[2]
when (knob) {
7 -> {
volume = value / 127f
}
0x47 -> {
dutyCycle = value / 127.0
}
0x4a -> {
fmFreq = value / 127.0
}
0x4b -> {
fmAmp = value / 127.0
}
0x4c -> {
amFreq = value / 127.0
}
0x4d -> {
amAmp = value / 127.0
}
0x49 -> {
attack = value / 127.0
}
0x4b -> {
decay = value / 127.0
}
0x46 -> {
sustain = value / 127.0
}
0x48 -> {
release = value / 127.0
}
123 -> {
for (note in notes) {
note?.noteRelease = currentTime
}
}
}
}
}
0xe0 -> {
if (bytes.length == 3) {
val lsb = bytes[1]
val msb = bytes[2]
amFreq = (((msb - 0x40) + (lsb / 127.0)) / 0x40) * 10.0
}
}
}
}
}
private fun noteOn(note: Int, velocity: Int) {
for (i in 0 until POLYPHONICS) {
if (notes[i]?.note == note) {
notes[i]?.retrigger(velocity)
return
}
}
for (i in 0 until POLYPHONICS) {
if (notes[i] == null) {
notes[i] = PlayingNote(
note,
velocity
)
break
}
}
}
private fun noteOff(note: Int) {
for (i in 0 until POLYPHONICS) {
if (notes[i]?.note == note) {
notes[i]?.noteRelease = currentTime
break
}
}
}
override fun process (
inputs: Array<Array<Float32Array>>,
outputs: Array<Array<Float32Array>>,
parameters: dynamic
) : Boolean {
val samples = outputs[0][0].length
val left = outputs[0][0]
val right = outputs[0][1]
var lowestNote = 200
for (note in notes) {
if (note != null) {
lowestNote = min(lowestNote, note.note)
}
}
if (lowestNote == 200 && recordingState == RecordingState.WAITING_TO_START) {
recordingState = RecordingState.RECORDING
recordingSample = 0
recordingStart = 0
}
for ((index, note) in notes.withIndex()) {
if (note != null) {
val sampleDelta = Note.fromMidi(note.note).sampleDelta
for (i in 0 until samples) {
var targetVolume = note.velocity / 127f
targetVolume *= ADSR.calculate(
attack,
decay,
sustain,
release,
note.noteStart,
currentTime,
note.noteRelease
).toFloat()
note.actualVolume += (targetVolume - note.actualVolume) * 0.01f
if (note.noteRelease != null && note.actualVolume <= 0.01) {
notes[index] = null
}
var cycleOffset = note.cycleOffset
val fmModulation = sampleDelta * sin( fmFreq * 20f * PI2 * (note.sample / sampleRate.toDouble())).toFloat() * fmAmp
val amModulation = 1f + (sin(sampleLength * amFreq * 10f * PI2 * note.sample) * amAmp).toFloat()
cycleOffset = if (cycleOffset < dutyCycle) {
cycleOffset / dutyCycle / 2.0
} else {
0.5 + ((cycleOffset -dutyCycle) / (1.0 - dutyCycle) / 2.0)
}
val waveValue: Float = when (waveform) {
0 -> {
sin(cycleOffset * PI2).toFloat()
}
1 -> {
if (cycleOffset < 0.5) { 1f } else { -1f }
}
2 -> when {
cycleOffset < 0.25 -> 4 * cycleOffset
cycleOffset < 0.75 -> 2 - 4 * cycleOffset
else -> 4 * cycleOffset - 4
}.toFloat()
3 -> {
((cycleOffset * 2f) - 1f).toFloat()
}
else -> {
if (cycleOffset < 0.5) { 1f } else { -1f }
}
}
left[i] = left[i] + waveValue * note.actualVolume * volume * amModulation
right[i] = right[i] + waveValue * note.actualVolume * volume * amModulation
note.cycleOffset += sampleDelta + fmModulation
if (note.cycleOffset > 1f) {
note.cycleOffset -= 1f
if (note.note == lowestNote && recordingState == RecordingState.WAITING_TO_START) {
recordingState = RecordingState.RECORDING
recordingSample = 0
recordingStart = i
}
}
note.sample++
}
}
}
if (recordingState == RecordingState.RECORDING) {
for (i in recordingStart until samples) {
recordingBuffer[recordingSample] = (left[i] + right[i]) / 2f
if (recordingSample < recordingBuffer.length - 1) {
recordingSample++
} else {
recordingState = RecordingState.STOPPED
}
}
recordingStart = 0
}
return true
}
}
fun main() {
registerProcessor("vst-chip-processor", VstChipProcessor::class.js)
println("VstChipProcessor registered!")
}

View File

@@ -0,0 +1,264 @@
@file:OptIn(ExperimentalJsExport::class)
package nl.astraeus.vst.string
import nl.astraeus.vst.AudioWorkletProcessor
import nl.astraeus.vst.Note
import nl.astraeus.vst.currentTime
import nl.astraeus.vst.registerProcessor
import nl.astraeus.vst.sampleRate
import org.khronos.webgl.Float32Array
import org.khronos.webgl.Int32Array
import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get
import org.khronos.webgl.set
import org.w3c.dom.MessageEvent
import kotlin.math.PI
import kotlin.math.min
val POLYPHONICS = 10
val PI2 = PI * 2
@ExperimentalJsExport
@JsExport
class PlayingNote(
val note: Int,
var velocity: Int = 0
) {
fun retrigger(velocity: Int) {
this.velocity = velocity
sample = 0
noteStart = currentTime
noteRelease = null
}
var noteStart = currentTime
var noteRelease: Double? = null
var cycleOffset = 0.0
var sample = 0
var actualVolume = 0f
}
@ExperimentalJsExport
@JsExport
enum class RecordingState {
STOPPED,
WAITING_TO_START,
RECORDING
}
@ExperimentalJsExport
@JsExport
class VstStringProcessor : AudioWorkletProcessor() {
var midiChannel = 0
var volume = 0.75f
var damping = 0.996
val recordingBuffer = Float32Array(sampleRate / 60)
var recordingState = RecordingState.STOPPED
var recordingSample = 0
var recordingStart = 0
val strings = Array(POLYPHONICS) {
PhysicalString(sampleRate, damping)
}
init {
this.port.onmessage = ::handleMessage
Note.updateSampleRate(sampleRate)
}
private fun handleMessage(message: MessageEvent) {
//console.log("VstChipProcessor: Received message:", message.data)
val data = message.data
try {
when (data) {
is String -> {
when (data) {
"start_recording" -> {
port.postMessage(recordingBuffer)
if (recordingState == RecordingState.STOPPED) {
recordingState = RecordingState.WAITING_TO_START
recordingSample = 0
}
}
else ->
if (data.startsWith("set_channel")) {
val parts = data.split('\n')
if (parts.size == 2) {
midiChannel = parts[1].toInt()
println("Setting channel: $midiChannel")
}
}
}
}
is Uint8Array -> {
val data32 = Int32Array(data.length)
for (i in 0 until data.length) {
data32[i] = (data[i].toInt() and 0xff)
}
playMidi(data32)
}
is Int32Array -> {
playMidi(data)
}
else ->
console.error("Don't kow how to handle message", message)
}
} catch (e: Exception) {
console.log(e.message, e)
}
}
private fun playMidi(bytes: Int32Array) {
console.log("playMidi", bytes)
if (bytes.length > 0) {
var cmdByte = bytes[0]
val channelCmd = ((cmdByte shr 4) and 0xf) != 0xf0
val channel = cmdByte and 0xf
println("Channel cmd: $channelCmd")
if (channelCmd && channel != midiChannel) {
console.log("Wrong channel", midiChannel, bytes)
return
}
cmdByte = cmdByte and 0xf0
//console.log("Received", bytes)
when (cmdByte) {
0x90 -> {
if (bytes.length == 3) {
val note = bytes[1]
val velocity = bytes[2]
if (velocity > 0) {
noteOn(note, velocity)
} else {
noteOff(note)
}
}
}
0x80 -> {
if (bytes.length >= 2) {
val note = bytes[1]
noteOff(note)
}
}
0xb0 -> {
if (bytes.length == 3) {
val knob = bytes[1]
val value = bytes[2]
when (knob) {
7 -> {
volume = value / 127f
}
0x47 -> {
damping = 0.8 + value / 127.0
}
}
}
}
}
}
}
private fun noteOn(midiNote: Int, velocity: Int) {
val note = Note.fromMidi(midiNote)
for (i in 0 until POLYPHONICS) {
if (strings[i].currentNote == note) {
strings[i].pluck(note, velocity / 127.0)
strings[i].damping = damping
return
}
}
for (i in 0 until POLYPHONICS) {
if (strings[i].available) {
strings[i].pluck(note, velocity / 127.0)
strings[i].damping = damping
break
}
}
}
private fun noteOff(midiNote: Int) {
val note = Note.fromMidi(midiNote)
for (i in 0 until POLYPHONICS) {
if (strings[i].currentNote.ordinal == note.ordinal) {
strings[i].available = true
strings[i].damping = damping * 0.9
}
}
}
override fun process(
inputs: Array<Array<Float32Array>>,
outputs: Array<Array<Float32Array>>,
parameters: dynamic
): Boolean {
val samples = outputs[0][0].length
val left = outputs[0][0]
val right = outputs[0][1]
var lowestNote = 200
for (string in strings) {
if (string.available) {
lowestNote = min(lowestNote, string.currentNote.ordinal)
}
}
if (lowestNote == 200 && recordingState == RecordingState.WAITING_TO_START) {
recordingState = RecordingState.RECORDING
recordingSample = 0
recordingStart = 0
}
for (string in strings) {
for (i in 0 until samples) {
val waveValue: Float = string.tick().toFloat()
left[i] = left[i] + waveValue * volume
right[i] = right[i] + waveValue * volume
if (lowestNote == string.currentNote.ordinal && recordingState == RecordingState.WAITING_TO_START && string.index == 0) {
recordingState = RecordingState.RECORDING
recordingSample = 0
recordingStart = 0
}
}
}
if (recordingState == RecordingState.RECORDING) {
for (i in recordingStart until samples) {
recordingBuffer[recordingSample] = (left[i] + right[i]) / 2f
if (recordingSample < recordingBuffer.length - 1) {
recordingSample++
} else {
recordingState = RecordingState.STOPPED
}
}
recordingStart = 0
}
return true
}
}
fun main() {
registerProcessor("vst-string-processor", VstStringProcessor::class.js)
println("VstStringProcessor registered!")
}

View File

@@ -19,7 +19,7 @@ kotlin {
binaries.executable() binaries.executable()
browser { browser {
commonWebpackConfig { commonWebpackConfig {
outputFileName = "vst-chip-worklet-ui.js" outputFileName = "vst-string-worklet-ui.js"
sourceMaps = true sourceMaps = true
} }

View File

@@ -1,5 +1,7 @@
package nl.astraeus.vst package nl.astraeus.vst
import kotlin.js.ExperimentalJsExport
import kotlin.js.JsExport
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
@@ -11,6 +13,8 @@ import kotlin.math.round
* Time: 11:50 * Time: 11:50
*/ */
@ExperimentalJsExport
@JsExport
enum class Note( enum class Note(
val sharp: String, val sharp: String,
val flat: String val flat: String

View File

@@ -0,0 +1,80 @@
package nl.astraeus.vst.string
import nl.astraeus.vst.Note
import kotlin.js.ExperimentalJsExport
import kotlin.js.JsExport
import kotlin.math.round
expect fun randomDouble(): Double
@ExperimentalJsExport
@JsExport
class PhysicalString(
val sampleRate: Int,
var damping: Double,
) {
val sampleLength = 1.0 / sampleRate.toDouble()
val maxLength = sampleRate / Note.G9.freq
var length = 1
val buffer = Array(maxLength.toInt() + 1) { 0.0 }
var sample = 0
var index = 0
var remaining = 0.0
var currentNote = Note.C4
var available = true
fun pluck(note: Note, velocity: Double, smoothing: Int = 0) {
available = false
currentNote = note
length = round(sampleRate / note.freq).toInt()
sample = 0
index = 0
for (i in 0..<length) {
if (i < length / 2) {
buffer[i] = randomDouble() * velocity
} else {
buffer[i] = -randomDouble() * velocity
}
//buffer[i] = (randomDouble() - 0.5) * 2.0 * velocity
//buffer[i] = sin(PI * 2 * i/length)
//buffer[i] = (i/length.toDouble() * 2.0) - 1.0 //if (i < length / 2) { 1.0 } else { -1.0 }
}
repeat(smoothing) {
for (i in 0..<length) {
tick()
}
}
}
fun update(time: Double) {
remaining += (time / 1000.0)
while (remaining > sampleLength) {
remaining -= sampleLength
tick()
}
}
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
buffer[index] = newValue
index = (index + 1) % length
return result
}
private fun getValueFromBuffer(index: Int): Double {
return buffer[(index + length) % length]
}
}

View File

@@ -0,0 +1,5 @@
package nl.astraeus.vst.string
import kotlin.random.Random
actual fun randomDouble(): Double = Random.nextDouble()

View File

@@ -0,0 +1,5 @@
package nl.astraeus.vst.string
actual fun randomDouble(): Double {
TODO("Not yet implemented")
}

View File

@@ -1,6 +1,6 @@
apply(from = "settings.common.gradle.kts") apply(from = "settings.common.gradle.kts")
rootProject.name = "vst-chip" rootProject.name = "vst-string"
include(":common") include(":common")
include(":audio-worklet") include(":audio-worklet")

View File

@@ -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,
)

View 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,
)

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip.logger package nl.astraeus.vst.string.logger
val log = Logger val log = Logger

View File

@@ -1,10 +0,0 @@
package nl.astraeus.vst.chip.audio
import nl.astraeus.vst.chip.AudioContext
object AudioContextHandler {
val audioContext: dynamic = AudioContext()
}

View File

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

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip package nl.astraeus.vst.string
external class AudioContext { external class AudioContext {
var sampleRate: Int var sampleRate: Int

View File

@@ -1,12 +1,12 @@
package nl.astraeus.vst.chip package nl.astraeus.vst.string
import kotlinx.browser.document import kotlinx.browser.document
import nl.astraeus.komp.Komponent import nl.astraeus.komp.Komponent
import nl.astraeus.komp.UnsafeMode import nl.astraeus.komp.UnsafeMode
import nl.astraeus.vst.chip.logger.log import nl.astraeus.vst.string.logger.log
import nl.astraeus.vst.chip.midi.Midi import nl.astraeus.vst.string.midi.Midi
import nl.astraeus.vst.chip.view.MainView import nl.astraeus.vst.string.view.MainView
import nl.astraeus.vst.chip.ws.WebsocketClient import nl.astraeus.vst.string.ws.WebsocketClient
import nl.astraeus.vst.ui.css.CssSettings import nl.astraeus.vst.ui.css.CssSettings
fun main() { fun main() {

View File

@@ -0,0 +1,10 @@
package nl.astraeus.vst.string.audio
import nl.astraeus.vst.string.AudioContext
object AudioContextHandler {
val audioContext: dynamic = AudioContext()
}

View File

@@ -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.string.AudioWorkletNode
import nl.astraeus.vst.chip.AudioWorkletNodeParameters import nl.astraeus.vst.string.AudioWorkletNodeParameters
import nl.astraeus.vst.chip.audio.AudioContextHandler.audioContext import nl.astraeus.vst.string.audio.AudioContextHandler.audioContext
import org.w3c.dom.MessageEvent import org.w3c.dom.MessageEvent
import org.w3c.dom.MessagePort import org.w3c.dom.MessagePort

View File

@@ -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,
)
}
}

View File

@@ -1,6 +1,6 @@
@file:OptIn(ExperimentalJsExport::class) @file:OptIn(ExperimentalJsExport::class)
package nl.astraeus.vst.chip.midi package nl.astraeus.vst.string.midi
import kotlinx.browser.window import kotlinx.browser.window
import org.khronos.webgl.Uint8Array import org.khronos.webgl.Uint8Array

View File

@@ -1,8 +1,8 @@
package nl.astraeus.vst.chip.midi package nl.astraeus.vst.string.midi
import kotlinx.browser.window import kotlinx.browser.window
import nl.astraeus.vst.chip.audio.VstChipWorklet import nl.astraeus.vst.string.audio.VstStringWorklet
import nl.astraeus.vst.chip.view.MainView import nl.astraeus.vst.string.view.MainView
import org.khronos.webgl.Uint8Array import org.khronos.webgl.Uint8Array
import org.khronos.webgl.get import org.khronos.webgl.get
@@ -125,7 +125,7 @@ object Midi {
hex.append(" ") hex.append(" ")
} }
console.log("Midi message:", hex) console.log("Midi message:", hex)
VstChipWorklet.postMessage( VstStringWorklet.postMessage(
message.data message.data
) )
} }

View File

@@ -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.InputType
import kotlinx.html.canvas
import kotlinx.html.classes
import kotlinx.html.div import kotlinx.html.div
import kotlinx.html.h1 import kotlinx.html.h1
import kotlinx.html.input import kotlinx.html.input
@@ -32,72 +31,30 @@ import nl.astraeus.css.style.Style
import nl.astraeus.css.style.cls import nl.astraeus.css.style.cls
import nl.astraeus.komp.HtmlBuilder import nl.astraeus.komp.HtmlBuilder
import nl.astraeus.komp.Komponent import nl.astraeus.komp.Komponent
import nl.astraeus.komp.currentElement import nl.astraeus.vst.string.PhysicalString
import nl.astraeus.vst.chip.audio.VstChipWorklet import nl.astraeus.vst.string.audio.VstStringWorklet
import nl.astraeus.vst.chip.audio.VstChipWorklet.midiChannel import nl.astraeus.vst.string.audio.VstStringWorklet.midiChannel
import nl.astraeus.vst.chip.midi.Midi import nl.astraeus.vst.string.midi.Midi
import nl.astraeus.vst.chip.ws.WebsocketClient import nl.astraeus.vst.string.ws.WebsocketClient
import nl.astraeus.vst.ui.components.KnobComponent import nl.astraeus.vst.ui.components.ExpKnobComponent
import nl.astraeus.vst.ui.css.Css import nl.astraeus.vst.ui.css.Css
import nl.astraeus.vst.ui.css.Css.defineCss import nl.astraeus.vst.ui.css.Css.defineCss
import nl.astraeus.vst.ui.css.Css.noTextSelect import nl.astraeus.vst.ui.css.Css.noTextSelect
import nl.astraeus.vst.ui.css.CssName import nl.astraeus.vst.ui.css.CssName
import nl.astraeus.vst.ui.css.hover import nl.astraeus.vst.ui.css.hover
import nl.astraeus.vst.ui.util.uInt8ArrayOf 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.HTMLInputElement
import org.w3c.dom.HTMLSelectElement 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 { object MainView : Komponent(), CssName {
private var messages: MutableList<String> = ArrayList() private var messages: MutableList<String> = ArrayList()
var started = false var started = false
val playString = PhysicalStringView(
PhysicalString(
sampleRate = 48000,
damping = 0.996,
)
)
init { init {
css() css()
@@ -119,7 +76,7 @@ object MainView : Komponent(), CssName {
div(StartButtonCss.name) { div(StartButtonCss.name) {
+"START" +"START"
onClickFunction = { onClickFunction = {
VstChipWorklet.create { VstStringWorklet.create {
started = true started = true
requestUpdate() requestUpdate()
WebsocketClient.send("LOAD\n") WebsocketClient.send("LOAD\n")
@@ -130,7 +87,7 @@ object MainView : Komponent(), CssName {
} }
} }
h1 { h1 {
+"VST Chip" +"VST Guitar"
} }
div { div {
span { span {
@@ -162,11 +119,11 @@ object MainView : Komponent(), CssName {
+"channel:" +"channel:"
input { input {
type = InputType.number type = InputType.number
value = VstChipWorklet.midiChannel.toString() value = midiChannel.toString()
onInputFunction = { event -> onInputFunction = { event ->
val target = event.target as HTMLInputElement val target = event.target as HTMLInputElement
println("onInput channel: $target") 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) { span(ButtonBarCss.name) {
+"SAVE" +"SAVE"
onClickFunction = { onClickFunction = {
val patch = VstChipWorklet.save().copy( val patch = VstStringWorklet.save().copy(
midiId = Midi.currentInput?.id ?: "", midiId = Midi.currentInput?.id ?: "",
midiName = Midi.currentInput?.name ?: "" midiName = Midi.currentInput?.name ?: ""
) )
@@ -186,189 +143,29 @@ object MainView : Komponent(), CssName {
span(ButtonBarCss.name) { span(ButtonBarCss.name) {
+"STOP" +"STOP"
onClickFunction = { onClickFunction = {
VstChipWorklet.postDirectlyToWorklet( VstStringWorklet.postDirectlyToWorklet(
uInt8ArrayOf(0xb0 + midiChannel, 123, 0) 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) { div(ControlsCss.name) {
include( include(
KnobComponent( ExpKnobComponent(
value = VstChipWorklet.volume, value = VstStringWorklet.volume,
label = "Volume", label = "Volume",
minValue = 0.0, minValue = 0.005,
maxValue = 1.0, maxValue = 1.0,
step = 2.0 / 127.0, step = 5.0 / 127.0,
width = 100, width = 100,
height = 120, height = 120,
) { value -> ) { value ->
VstChipWorklet.volume = value VstStringWorklet.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
} }
) )
} }
include(WaveformView) include(WaveformView)
include(playString)
} }
} }

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
package nl.astraeus.vst.chip.ws package nl.astraeus.vst.string.ws
import kotlinx.browser.window import kotlinx.browser.window
import nl.astraeus.vst.chip.PatchDTO import nl.astraeus.vst.string.PatchDTO
import nl.astraeus.vst.chip.audio.VstChipWorklet import nl.astraeus.vst.string.audio.VstStringWorklet
import nl.astraeus.vst.chip.midi.Midi import nl.astraeus.vst.string.midi.Midi
import nl.astraeus.vst.chip.view.MainView import nl.astraeus.vst.string.view.MainView
import org.w3c.dom.MessageEvent import org.w3c.dom.MessageEvent
import org.w3c.dom.WebSocket import org.w3c.dom.WebSocket
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
@@ -17,7 +17,7 @@ object WebsocketClient {
close() close()
websocket = if (window.location.hostname.contains("localhost") || window.location.hostname.contains("192.168")) { 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 { } else {
WebSocket("wss://${window.location.hostname}/ws") WebSocket("wss://${window.location.hostname}/ws")
} }
@@ -87,7 +87,7 @@ object WebsocketClient {
val patch = JSON.parse<PatchDTO>(patchJson) val patch = JSON.parse<PatchDTO>(patchJson)
Midi.setInput(patch.midiId, patch.midiName) Midi.setInput(patch.midiId, patch.midiName)
VstChipWorklet.load(patch) VstStringWorklet.load(patch)
MainView.requestUpdate() MainView.requestUpdate()
} }
} }

View File

@@ -1,6 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<body>
<script type="application/javascript" src="vst-chip-worklet-ui.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip package nl.astraeus.vst.string
import java.security.SecureRandom import java.security.SecureRandom

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip package nl.astraeus.vst.string
import com.zaxxer.hikari.HikariConfig import com.zaxxer.hikari.HikariConfig
import io.undertow.Undertow import io.undertow.Undertow
@@ -6,10 +6,10 @@ import io.undertow.UndertowOptions
import io.undertow.server.session.InMemorySessionManager import io.undertow.server.session.InMemorySessionManager
import io.undertow.server.session.SessionAttachmentHandler import io.undertow.server.session.SessionAttachmentHandler
import io.undertow.server.session.SessionCookieConfig import io.undertow.server.session.SessionCookieConfig
import nl.astraeus.vst.chip.db.Database import nl.astraeus.vst.string.db.Database
import nl.astraeus.vst.chip.logger.LogLevel import nl.astraeus.vst.string.logger.LogLevel
import nl.astraeus.vst.chip.logger.Logger import nl.astraeus.vst.string.logger.Logger
import nl.astraeus.vst.chip.web.RequestHandler import nl.astraeus.vst.string.web.RequestHandler
fun main() { fun main() {
Logger.level = LogLevel.DEBUG Logger.level = LogLevel.DEBUG

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip package nl.astraeus.vst.string
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
@@ -6,7 +6,7 @@ import java.util.*
object Settings { object Settings {
var runningAsRoot: Boolean = false var runningAsRoot: Boolean = false
var port = 9000 var port = 9004
var sslPort = 8443 var sslPort = 8443
var connectionTimeout = 30000 var connectionTimeout = 30000

View File

@@ -1,7 +1,7 @@
package nl.astraeus.vst.chip.db package nl.astraeus.vst.string.db
import kotlinx.datetime.Instant 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.PreparedStatement
import java.sql.ResultSet import java.sql.ResultSet
import java.sql.Timestamp import java.sql.Timestamp

View File

@@ -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.HikariConfig
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip.db package nl.astraeus.vst.string.db
interface Entity { interface Entity {
fun getPK(): Array<Any> fun getPK(): Array<Any>

View File

@@ -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.Connection
import java.sql.SQLException import java.sql.SQLException
import java.sql.Timestamp import java.sql.Timestamp

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip.db package nl.astraeus.vst.string.db
object PatchDao : BaseDao<PatchEntity>() { object PatchDao : BaseDao<PatchEntity>() {

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip.db package nl.astraeus.vst.string.db
import kotlinx.datetime.Clock import kotlinx.datetime.Clock
import kotlinx.datetime.Instant import kotlinx.datetime.Instant

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip.db package nl.astraeus.vst.string.db
import java.sql.ResultSet import java.sql.ResultSet
import java.sql.Types import java.sql.Types

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip.web package nl.astraeus.vst.string.web
import kotlinx.html.body import kotlinx.html.body
import kotlinx.html.head import kotlinx.html.head
@@ -14,12 +14,12 @@ fun generateIndex(patch: String?): String {
if (patch == null) { if (patch == null) {
result.appendHTML(true).html { result.appendHTML(true).html {
head { head {
title { +"VST Chip" } title { +"VST String" }
} }
body { body {
script { script {
type = "application/javascript" type = "application/javascript"
src = "/vst-chip-worklet-ui.js" src = "/vst-string-worklet-ui.js"
} }
} }
} }

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip.web package nl.astraeus.vst.string.web
import io.undertow.Handlers.websocket import io.undertow.Handlers.websocket
import io.undertow.server.HttpHandler 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.WebSocketChannel
import io.undertow.websockets.core.WebSockets import io.undertow.websockets.core.WebSockets
import io.undertow.websockets.spi.WebSocketHttpExchange import io.undertow.websockets.spi.WebSocketHttpExchange
import nl.astraeus.vst.chip.db.PatchDao import nl.astraeus.vst.string.db.PatchDao
import nl.astraeus.vst.chip.db.PatchEntity import nl.astraeus.vst.string.db.PatchEntity
import nl.astraeus.vst.chip.db.transaction import nl.astraeus.vst.string.db.transaction
import nl.astraeus.vst.chip.generateId import nl.astraeus.vst.string.generateId
import java.nio.file.Paths import java.nio.file.Paths
class WebsocketHandler( class WebsocketHandler(

View File

@@ -1,4 +1,4 @@
package nl.astraeus.vst.chip.web package nl.astraeus.vst.string.web
class VstSession( class VstSession(
val patchId: String val patchId: String