diff --git a/.idea/artifacts/audio_worklet_js_1_0_0_SNAPSHOT.xml b/.idea/artifacts/audio_worklet_js_1_0_0_SNAPSHOT.xml
index 48401c3..f0c6900 100644
--- a/.idea/artifacts/audio_worklet_js_1_0_0_SNAPSHOT.xml
+++ b/.idea/artifacts/audio_worklet_js_1_0_0_SNAPSHOT.xml
@@ -2,7 +2,7 @@
$PROJECT_DIR$/audio-worklet/build/libs
-
+
\ No newline at end of file
diff --git a/.idea/artifacts/audio_worklet_jvm_1_0_0_SNAPSHOT.xml b/.idea/artifacts/audio_worklet_jvm_1_0_0_SNAPSHOT.xml
index 06b6fe8..d7beaf8 100644
--- a/.idea/artifacts/audio_worklet_jvm_1_0_0_SNAPSHOT.xml
+++ b/.idea/artifacts/audio_worklet_jvm_1_0_0_SNAPSHOT.xml
@@ -2,7 +2,7 @@
$PROJECT_DIR$/audio-worklet/build/libs
-
+
\ No newline at end of file
diff --git a/.idea/artifacts/common_js_1_0_0_SNAPSHOT.xml b/.idea/artifacts/common_js_1_0_0_SNAPSHOT.xml
index d3d29c0..468ef31 100644
--- a/.idea/artifacts/common_js_1_0_0_SNAPSHOT.xml
+++ b/.idea/artifacts/common_js_1_0_0_SNAPSHOT.xml
@@ -2,7 +2,7 @@
$PROJECT_DIR$/common/build/libs
-
+
\ No newline at end of file
diff --git a/.idea/artifacts/common_jvm_1_0_0_SNAPSHOT.xml b/.idea/artifacts/common_jvm_1_0_0_SNAPSHOT.xml
index 1d6f5d0..d4560ce 100644
--- a/.idea/artifacts/common_jvm_1_0_0_SNAPSHOT.xml
+++ b/.idea/artifacts/common_jvm_1_0_0_SNAPSHOT.xml
@@ -2,7 +2,7 @@
$PROJECT_DIR$/common/build/libs
-
+
\ No newline at end of file
diff --git a/.run/Main [jvm].run.xml b/.run/Main [jvm].run.xml
index 4378084..5d42efd 100644
--- a/.run/Main [jvm].run.xml
+++ b/.run/Main [jvm].run.xml
@@ -4,7 +4,7 @@
-
+
diff --git a/audio-worklet/build.gradle.kts b/audio-worklet/build.gradle.kts
index 6eb7ea2..8b6fdf4 100644
--- a/audio-worklet/build.gradle.kts
+++ b/audio-worklet/build.gradle.kts
@@ -21,13 +21,13 @@ kotlin {
browser {
commonWebpackConfig {
- outputFileName = "vst-chip-worklet.js"
+ outputFileName = "vst-string-worklet.js"
sourceMaps = true
}
webpackTask {
output.libraryTarget = KotlinWebpackOutput.Target.VAR
- output.library = "vstChipWorklet"
+ output.library = "vstStringWorklet"
}
distribution {
diff --git a/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/chip/ChipProcessor.kt b/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/chip/ChipProcessor.kt
deleted file mode 100644
index 0c7ec2b..0000000
--- a/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/chip/ChipProcessor.kt
+++ /dev/null
@@ -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(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>,
- outputs: Array>,
- 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!")
-}
diff --git a/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/string/StringProcessor.kt b/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/string/StringProcessor.kt
new file mode 100644
index 0000000..a7aeaea
--- /dev/null
+++ b/audio-worklet/src/jsMain/kotlin/nl/astraeus/vst/string/StringProcessor.kt
@@ -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>,
+ outputs: Array>,
+ 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!")
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index f2d74f9..e172502 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -19,7 +19,7 @@ kotlin {
binaries.executable()
browser {
commonWebpackConfig {
- outputFileName = "vst-chip-worklet-ui.js"
+ outputFileName = "vst-string-worklet-ui.js"
sourceMaps = true
}
diff --git a/common/src/commonMain/kotlin/nl/astraeus/vst/Note.kt b/common/src/commonMain/kotlin/nl/astraeus/vst/Note.kt
index ca08cde..41970fa 100644
--- a/common/src/commonMain/kotlin/nl/astraeus/vst/Note.kt
+++ b/common/src/commonMain/kotlin/nl/astraeus/vst/Note.kt
@@ -1,5 +1,7 @@
package nl.astraeus.vst
+import kotlin.js.ExperimentalJsExport
+import kotlin.js.JsExport
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
@@ -11,6 +13,8 @@ import kotlin.math.round
* Time: 11:50
*/
+@ExperimentalJsExport
+@JsExport
enum class Note(
val sharp: String,
val flat: String
diff --git a/common/src/commonMain/kotlin/nl/astraeus/vst/string/PhysicalString.kt b/common/src/commonMain/kotlin/nl/astraeus/vst/string/PhysicalString.kt
new file mode 100644
index 0000000..1ba91f7
--- /dev/null
+++ b/common/src/commonMain/kotlin/nl/astraeus/vst/string/PhysicalString.kt
@@ -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.. 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]
+ }
+}
diff --git a/common/src/jsMain/kotlin/nl/astraeus/vst/string/PhysicalString.js.kt b/common/src/jsMain/kotlin/nl/astraeus/vst/string/PhysicalString.js.kt
new file mode 100644
index 0000000..8736db1
--- /dev/null
+++ b/common/src/jsMain/kotlin/nl/astraeus/vst/string/PhysicalString.js.kt
@@ -0,0 +1,5 @@
+package nl.astraeus.vst.string
+
+import kotlin.random.Random
+
+actual fun randomDouble(): Double = Random.nextDouble()
diff --git a/common/src/jvmMain/kotlin/nl/astraeus/vst/string/PhysicalString.jvm.kt b/common/src/jvmMain/kotlin/nl/astraeus/vst/string/PhysicalString.jvm.kt
new file mode 100644
index 0000000..c1ec7e2
--- /dev/null
+++ b/common/src/jvmMain/kotlin/nl/astraeus/vst/string/PhysicalString.jvm.kt
@@ -0,0 +1,5 @@
+package nl.astraeus.vst.string
+
+actual fun randomDouble(): Double {
+ TODO("Not yet implemented")
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index ab45cf9..69893ea 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,6 +1,6 @@
apply(from = "settings.common.gradle.kts")
-rootProject.name = "vst-chip"
+rootProject.name = "vst-string"
include(":common")
include(":audio-worklet")
diff --git a/src/commonMain/kotlin/nl/astraeus/vst/chip/PatchDTO.kt b/src/commonMain/kotlin/nl/astraeus/vst/chip/PatchDTO.kt
deleted file mode 100644
index 408fc1e..0000000
--- a/src/commonMain/kotlin/nl/astraeus/vst/chip/PatchDTO.kt
+++ /dev/null
@@ -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,
-)
diff --git a/src/commonMain/kotlin/nl/astraeus/vst/string/PatchDTO.kt b/src/commonMain/kotlin/nl/astraeus/vst/string/PatchDTO.kt
new file mode 100644
index 0000000..e9633af
--- /dev/null
+++ b/src/commonMain/kotlin/nl/astraeus/vst/string/PatchDTO.kt
@@ -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,
+)
diff --git a/src/commonMain/kotlin/nl/astraeus/vst/chip/logger/Logger.kt b/src/commonMain/kotlin/nl/astraeus/vst/string/logger/Logger.kt
similarity index 96%
rename from src/commonMain/kotlin/nl/astraeus/vst/chip/logger/Logger.kt
rename to src/commonMain/kotlin/nl/astraeus/vst/string/logger/Logger.kt
index cd71970..e60eadd 100644
--- a/src/commonMain/kotlin/nl/astraeus/vst/chip/logger/Logger.kt
+++ b/src/commonMain/kotlin/nl/astraeus/vst/string/logger/Logger.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.vst.chip.logger
+package nl.astraeus.vst.string.logger
val log = Logger
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/audio/AudioContextHandler.kt b/src/jsMain/kotlin/nl/astraeus/vst/chip/audio/AudioContextHandler.kt
deleted file mode 100644
index 3db72b1..0000000
--- a/src/jsMain/kotlin/nl/astraeus/vst/chip/audio/AudioContextHandler.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package nl.astraeus.vst.chip.audio
-
-import nl.astraeus.vst.chip.AudioContext
-
-object AudioContextHandler {
- val audioContext: dynamic = AudioContext()
-
-
-
-}
\ No newline at end of file
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/audio/VstChipWorklet.kt b/src/jsMain/kotlin/nl/astraeus/vst/chip/audio/VstChipWorklet.kt
deleted file mode 100644
index 807ccc8..0000000
--- a/src/jsMain/kotlin/nl/astraeus/vst/chip/audio/VstChipWorklet.kt
+++ /dev/null
@@ -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
- )
- }
-
-}
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/Externals.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/Externals.kt
similarity index 90%
rename from src/jsMain/kotlin/nl/astraeus/vst/chip/Externals.kt
rename to src/jsMain/kotlin/nl/astraeus/vst/string/Externals.kt
index c476f6b..84b07b8 100644
--- a/src/jsMain/kotlin/nl/astraeus/vst/chip/Externals.kt
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/Externals.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.vst.chip
+package nl.astraeus.vst.string
external class AudioContext {
var sampleRate: Int
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/Main.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/Main.kt
similarity index 66%
rename from src/jsMain/kotlin/nl/astraeus/vst/chip/Main.kt
rename to src/jsMain/kotlin/nl/astraeus/vst/string/Main.kt
index d15f86e..7c08d4f 100644
--- a/src/jsMain/kotlin/nl/astraeus/vst/chip/Main.kt
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/Main.kt
@@ -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() {
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/string/audio/AudioContextHandler.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/audio/AudioContextHandler.kt
new file mode 100644
index 0000000..fcfa5ee
--- /dev/null
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/audio/AudioContextHandler.kt
@@ -0,0 +1,10 @@
+package nl.astraeus.vst.string.audio
+
+import nl.astraeus.vst.string.AudioContext
+
+object AudioContextHandler {
+ val audioContext: dynamic = AudioContext()
+
+
+
+}
\ No newline at end of file
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/audio/AudioModule.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/audio/AudioModule.kt
similarity index 89%
rename from src/jsMain/kotlin/nl/astraeus/vst/chip/audio/AudioModule.kt
rename to src/jsMain/kotlin/nl/astraeus/vst/string/audio/AudioModule.kt
index f5e835a..248489c 100644
--- a/src/jsMain/kotlin/nl/astraeus/vst/chip/audio/AudioModule.kt
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/audio/AudioModule.kt
@@ -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
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/string/audio/VstStringWorklet.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/audio/VstStringWorklet.kt
new file mode 100644
index 0000000..eba59bf
--- /dev/null
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/audio/VstStringWorklet.kt
@@ -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,
+ )
+ }
+
+}
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/midi/Broadcaster.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/midi/Broadcaster.kt
similarity index 98%
rename from src/jsMain/kotlin/nl/astraeus/vst/chip/midi/Broadcaster.kt
rename to src/jsMain/kotlin/nl/astraeus/vst/string/midi/Broadcaster.kt
index ddcf9e3..5f59439 100644
--- a/src/jsMain/kotlin/nl/astraeus/vst/chip/midi/Broadcaster.kt
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/midi/Broadcaster.kt
@@ -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
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/midi/Midi.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/midi/Midi.kt
similarity index 94%
rename from src/jsMain/kotlin/nl/astraeus/vst/chip/midi/Midi.kt
rename to src/jsMain/kotlin/nl/astraeus/vst/string/midi/Midi.kt
index 1d969ef..f2665e8 100644
--- a/src/jsMain/kotlin/nl/astraeus/vst/chip/midi/Midi.kt
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/midi/Midi.kt
@@ -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
)
}
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/view/MainView.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/view/MainView.kt
similarity index 54%
rename from src/jsMain/kotlin/nl/astraeus/vst/chip/view/MainView.kt
rename to src/jsMain/kotlin/nl/astraeus/vst/string/view/MainView.kt
index e358a8c..5657840 100644
--- a/src/jsMain/kotlin/nl/astraeus/vst/chip/view/MainView.kt
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/view/MainView.kt
@@ -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 = 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)
}
}
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/string/view/PhysicalStringView.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/view/PhysicalStringView.kt
new file mode 100644
index 0000000..26480f0
--- /dev/null
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/view/PhysicalStringView.kt
@@ -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()
+ }
+ }
+}
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/string/view/WaveformView.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/view/WaveformView.kt
new file mode 100644
index 0000000..a642340
--- /dev/null
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/view/WaveformView.kt
@@ -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()
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/jsMain/kotlin/nl/astraeus/vst/chip/ws/WebsocketClient.kt b/src/jsMain/kotlin/nl/astraeus/vst/string/ws/WebsocketClient.kt
similarity index 87%
rename from src/jsMain/kotlin/nl/astraeus/vst/chip/ws/WebsocketClient.kt
rename to src/jsMain/kotlin/nl/astraeus/vst/string/ws/WebsocketClient.kt
index b58b43b..12285e6 100644
--- a/src/jsMain/kotlin/nl/astraeus/vst/chip/ws/WebsocketClient.kt
+++ b/src/jsMain/kotlin/nl/astraeus/vst/string/ws/WebsocketClient.kt
@@ -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(patchJson)
Midi.setInput(patch.midiId, patch.midiName)
- VstChipWorklet.load(patch)
+ VstStringWorklet.load(patch)
MainView.requestUpdate()
}
}
diff --git a/src/jsMain/resources/index.html b/src/jsMain/resources/index.html
deleted file mode 100644
index 22e3c6e..0000000
--- a/src/jsMain/resources/index.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/src/jvmMain/java/BarWavesCanvas.java b/src/jvmMain/java/BarWavesCanvas.java
new file mode 100644
index 0000000..cb12599
--- /dev/null
+++ b/src/jvmMain/java/BarWavesCanvas.java
@@ -0,0 +1,1622 @@
+// BarWaves.java (C) 2001 by Paul Falstad, www.falstad.com
+
+import javax.sound.sampled.AudioFormat;
+import javax.sound.sampled.AudioSystem;
+import javax.sound.sampled.DataLine;
+import javax.sound.sampled.SourceDataLine;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.AdjustmentEvent;
+import java.awt.event.AdjustmentListener;
+import java.awt.event.ComponentEvent;
+import java.awt.event.ComponentListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.util.Random;
+import java.util.Vector;
+
+class BarWavesCanvas extends Canvas {
+ BarWavesFrame pg;
+
+ BarWavesCanvas(BarWavesFrame p) {
+ pg = p;
+ }
+
+ public Dimension getPreferredSize() {
+ return new Dimension(300, 400);
+ }
+
+ public void update(Graphics g) {
+ pg.updateBarWaves(g);
+ }
+
+ public void paint(Graphics g) {
+ pg.updateBarWaves(g);
+ }
+};
+
+class BarWavesLayout implements LayoutManager {
+ public BarWavesLayout() {
+ }
+
+ public void addLayoutComponent(String name, Component c) {
+ }
+
+ public void removeLayoutComponent(Component c) {
+ }
+
+ public Dimension preferredLayoutSize(Container target) {
+ return new Dimension(500, 500);
+ }
+
+ public Dimension minimumLayoutSize(Container target) {
+ return new Dimension(100, 100);
+ }
+
+ public void layoutContainer(Container target) {
+ int barwidth = 0;
+ int i;
+ for (i = 1; i < target.getComponentCount(); i++) {
+ Component m = target.getComponent(i);
+ if (m.isVisible()) {
+ Dimension d = m.getPreferredSize();
+ if (d.width > barwidth) {
+ barwidth = d.width;
+ }
+ }
+ }
+ Insets insets = target.insets();
+ int targetw = target.size().width - insets.left - insets.right;
+ int cw = targetw - barwidth;
+ int targeth = target.size().height - (insets.top + insets.bottom);
+ target.getComponent(0).move(insets.left, insets.top);
+ target.getComponent(0).resize(cw, targeth);
+ cw += insets.left;
+ int h = insets.top;
+ for (i = 1; i < target.getComponentCount(); i++) {
+ Component m = target.getComponent(i);
+ if (m.isVisible()) {
+ Dimension d = m.getPreferredSize();
+ if (m instanceof Scrollbar) {
+ d.width = barwidth;
+ }
+ if (m instanceof Label) {
+ h += d.height / 5;
+ d.width = barwidth;
+ }
+ m.move(cw, h);
+ m.resize(d.width, d.height);
+ h += d.height;
+ }
+ }
+ }
+};
+
+class BarWavesFrame extends Frame
+ implements ComponentListener, ActionListener, AdjustmentListener,
+ MouseMotionListener, MouseListener, ItemListener {
+
+ Thread engine = null;
+
+ Dimension winSize;
+ Image dbimage;
+
+ Random random;
+ int maxTerms = 50;
+ int modeCount;
+ int maxMaxTerms = 90;
+ int sampleCount;
+ double modeTable[][];
+ double modeNorms[];
+ public static final double epsilon = .0000001;
+ public static final double epsilon2 = .003;
+
+ public String getAppletInfo() {
+ return "BarWaves by Paul Falstad";
+ }
+
+ Button sineButton;
+ Button blankButton;
+ Checkbox stoppedCheck;
+ Checkbox soundCheck;
+ Choice modeChooser;
+ Choice setupChooser;
+ Vector setupList;
+ Setup setup;
+ Choice displayChooser;
+ Scrollbar dampingBar;
+ Scrollbar speedBar;
+ Scrollbar loadBar;
+ Scrollbar baseFreqBar;
+ Scrollbar stiffnessBar;
+ double magcoef[];
+ double dampcoef[];
+ double phasecoef[];
+ double phasecoefcos[];
+ double phasecoefadj[];
+ double omega[];
+ static final double pi = 3.14159265358979323846;
+ double step;
+ double func[];
+ double funci[];
+ int thickness[];
+ int xpoints[], ypoints[];
+ int selectedCoef;
+ int magnitudesY;
+ static final int SEL_NONE = 0;
+ static final int SEL_FUNC = 1;
+ static final int SEL_MAG = 2;
+ static final int MODE_SHAPE = 0;
+ static final int MODE_FORCE = 1;
+ static final int MODE_THICKNESS = 2;
+ static final int DISP_PHASE = 0;
+ static final int DISP_PHASECOS = 1;
+ static final int DISP_MODES = 2;
+ static final int BOUND_HINGED = 0;
+ static final int BOUND_FREE = 1;
+ static final int BOUND_CLAMPED = 2;
+ int selection;
+ int dragX, dragY;
+ boolean dragging;
+ boolean java2present;
+ double t;
+ int pause;
+ Color gray1 = new Color(76, 76, 76);
+ Color gray2 = new Color(127, 127, 127);
+
+ int getrand(int x) {
+ int q = random.nextInt();
+ if (q < 0) {
+ q = -q;
+ }
+ return q % x;
+ }
+
+ BarWavesCanvas cv;
+
+ BarWavesFrame() {
+ super("Bar Waves Applet");
+ }
+
+ public void init() {
+ java2present = true;
+ if (System.getProperty("java.version").indexOf("1.1") == 0) {
+ java2present = false;
+ }
+ setupList = new Vector();
+ Setup s = new FreeBarSetup();
+ while (s != null) {
+ setupList.addElement(s);
+ s = s.createNext();
+ }
+ selectedCoef = -1;
+ setLayout(new BarWavesLayout());
+ cv = new BarWavesCanvas(this);
+ cv.addComponentListener(this);
+ cv.addMouseMotionListener(this);
+ cv.addMouseListener(this);
+ add(cv);
+
+ setupChooser = new Choice();
+ int i;
+ for (i = 0; i != setupList.size(); i++) {
+ setupChooser.add("Setup: " +
+ ((Setup) setupList.elementAt(i)).getName());
+ }
+ setup = (Setup) setupList.elementAt(0);
+ setupChooser.addItemListener(this);
+ add(setupChooser);
+
+ add(sineButton = new Button("Fundamental"));
+ sineButton.addActionListener(this);
+ add(blankButton = new Button("Clear"));
+ blankButton.addActionListener(this);
+ stoppedCheck = new Checkbox("Stopped");
+ stoppedCheck.addItemListener(this);
+ add(stoppedCheck);
+ soundCheck = new Checkbox("Sound", false);
+ soundCheck.addItemListener(this);
+ add(soundCheck);
+
+ modeChooser = new Choice();
+ modeChooser.add("Mouse = Shape bar");
+ modeChooser.add("Mouse = Apply static force");
+ modeChooser.addItemListener(this);
+ add(modeChooser);
+
+ displayChooser = new Choice();
+ displayChooser.add("Display Phases");
+ displayChooser.add("Display Phase Cosines");
+ displayChooser.add("Display Modes");
+ displayChooser.addItemListener(this);
+ add(displayChooser);
+
+ add(new Label("Simulation Speed", Label.CENTER));
+ add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 166, 1, 24, 300));
+ speedBar.addAdjustmentListener(this);
+
+ add(new Label("Damping", Label.CENTER));
+ add(dampingBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 0, 400));
+ dampingBar.addAdjustmentListener(this);
+
+ add(new Label("Resolution", Label.CENTER));
+ add(loadBar = new Scrollbar(Scrollbar.HORIZONTAL,
+ maxTerms, 1, 40, maxMaxTerms
+ ));
+ loadBar.addAdjustmentListener(this);
+ setLoadCount();
+
+ add(new Label("Base Frequency", Label.CENTER));
+ add(baseFreqBar = new Scrollbar(Scrollbar.HORIZONTAL,
+ 84, 12, 30, 168
+ ));
+ baseFreqBar.addAdjustmentListener(this);
+ baseFreqBar.disable();
+
+ add(new Label("String Stiffness", Label.CENTER));
+ add(stiffnessBar = new Scrollbar(Scrollbar.HORIZONTAL, 10, 1, 0, 100));
+ stiffnessBar.addAdjustmentListener(this);
+ stiffnessBar.disable();
+
+ try {
+ String param = null; //applet.getParameter("PAUSE");
+ if (param != null) {
+ pause = Integer.parseInt(param);
+ }
+ } catch (Exception e) {
+ }
+
+ magcoef = new double[maxMaxTerms];
+ phasecoef = new double[maxMaxTerms];
+ phasecoefcos = new double[maxMaxTerms];
+ phasecoefadj = new double[maxMaxTerms];
+ func = new double[maxMaxTerms + 1];
+ funci = new double[maxMaxTerms + 1];
+ xpoints = new int[4];
+ ypoints = new int[4];
+
+ random = new Random();
+ setDamping();
+ reinit();
+ cv.setBackground(Color.black);
+ cv.setForeground(Color.lightGray);
+ resize(500, 500);
+ handleResize();
+ show();
+ }
+
+ void reinit() {
+ doFundamental();
+ }
+
+ void handleResize() {
+ Dimension d = winSize = cv.getSize();
+ if (winSize.width == 0) {
+ return;
+ }
+ dbimage = createImage(d.width, d.height);
+ }
+
+ void doFundamental() {
+ doBlank();
+ magcoef[0] = 1;
+ if (soundCheck.getState()) {
+ doPlay();
+ }
+ }
+
+ void doBlank() {
+ int x;
+ for (x = 0; x <= sampleCount; x++) {
+ func[x] = 0;
+ }
+ transform(true);
+ }
+
+ void transform(boolean novel) {
+ int x, y;
+ t = 0;
+ for (y = 0; y != modeCount; y++) {
+ double a = 0;
+ double b = 0;
+ for (x = 1; x != sampleCount; x++) {
+ a += modeTable[x][y] * func[x];
+ b -= modeTable[x][y] * funci[x];
+ }
+ a /= modeNorms[y];
+ b /= omega[y] * modeNorms[y];
+ if (a < epsilon && a > -epsilon) {
+ a = 0;
+ }
+ if (b < epsilon && b > -epsilon) {
+ b = 0;
+ }
+ if (novel) {
+ b = 0;
+ }
+ double r = java.lang.Math.sqrt(a * a + b * b);
+ magcoef[y] = r;
+ double ph2 = java.lang.Math.atan2(b, a);
+ phasecoefadj[y] = ph2;
+ phasecoef[y] = ph2;
+ }
+ }
+
+ int getPanelHeight() {
+ return winSize.height / 3;
+ }
+
+ void centerString(Graphics g, String s, int y) {
+ FontMetrics fm = g.getFontMetrics();
+ g.drawString(s, (winSize.width - fm.stringWidth(s)) / 2, y);
+ }
+
+ public void paint(Graphics g) {
+ cv.repaint();
+ }
+
+ public void updateBarWaves(Graphics realg) {
+ if (!java2present) {
+ centerString(realg, "Need java2 for this applet.", 100);
+ return;
+ }
+ if (winSize == null || winSize.width == 0) {
+ return;
+ }
+ Graphics g = dbimage.getGraphics();
+ boolean allQuiet = true;
+ if (!stoppedCheck.getState()) {
+ int val = speedBar.getValue() - 100;
+ //if (val > 40)
+ //val += getrand(10);
+ double tadd = java.lang.Math.exp(val / 20.) * (.1 / 50);
+ // add random crap into the time to avoid aliasing
+ tadd *= 1 + getrand(300) * (.00191171);
+ t += tadd;
+ }
+ g.setColor(cv.getBackground());
+ g.fillRect(0, 0, winSize.width, winSize.height);
+ g.setColor(cv.getForeground());
+ int i;
+ int panelHeight = getPanelHeight();
+ int midy = panelHeight / 2;
+ int halfPanel = panelHeight / 2;
+ double ymult = .75 * halfPanel;
+ for (i = -1; i <= 1; i++) {
+ g.setColor((i == 0) ? gray2 : gray1);
+ g.drawLine(0, midy + (i * (int) ymult),
+ winSize.width, midy + (i * (int) ymult)
+ );
+ }
+ g.setColor(gray2);
+ g.drawLine(winSize.width / 2, midy - (int) ymult,
+ winSize.width / 2, midy + (int) ymult
+ );
+ int sampStart = (setup.leftBoundary() == BOUND_FREE) ? 1 : 0;
+ int sampEnd = sampleCount -
+ ((setup.rightBoundary() == BOUND_FREE) ? 1 : 0);
+ if (dragging && selection == SEL_FUNC) {
+ g.setColor(Color.cyan);
+ allQuiet = true;
+ for (i = sampStart; i <= sampEnd; i++) {
+ int x = winSize.width * i / sampleCount;
+ int y = midy - (int) (ymult * func[i]);
+ drawBarPiece(g, x, y, i, sampStart);
+ }
+ }
+ if (!stoppedCheck.getState() && !dragging) {
+ for (i = 0; i != modeCount; i++) {
+ magcoef[i] *= dampcoef[i];
+ }
+ }
+
+ double magcoefdisp[] = magcoef;
+ double phasecoefdisp[] = phasecoef;
+ double phasecoefcosdisp[] = phasecoefcos;
+
+ if (!(dragging && selection == SEL_FUNC)) {
+ g.setColor(Color.white);
+ int j;
+ for (j = 0; j != modeCount; j++) {
+ if (magcoef[j] < epsilon && magcoef[j] > -epsilon) {
+ magcoef[j] = phasecoef[j] = phasecoefadj[j] = 0;
+ continue;
+ }
+ allQuiet = false;
+ phasecoef[j] = (omega[j] * t + phasecoefadj[j]) % (2 * pi);
+ if (phasecoef[j] > pi) {
+ phasecoef[j] -= 2 * pi;
+ } else if (phasecoef[j] < -pi) {
+ phasecoef[j] += 2 * pi;
+ }
+ phasecoefcos[j] = java.lang.Math.cos(phasecoef[j]);
+ }
+
+ for (i = sampStart; i <= sampEnd; i++) {
+ int x = winSize.width * i / sampleCount;
+ double dy = 0;
+ for (j = 0; j != modeCount; j++) {
+ dy += magcoefdisp[j] *
+ modeTable[i][j] * phasecoefcosdisp[j];
+ }
+ func[i] = dy;
+ int y = midy - (int) (ymult * dy);
+ drawBarPiece(g, x, y, i, sampStart);
+ }
+ if (setup.getThickness() == 0) {
+ if (setup.leftBoundary() == BOUND_FREE) {
+ drawPin(g, 1, midy, ymult);
+ }
+ if (setup.rightBoundary() == BOUND_FREE) {
+ drawPin(g, sampleCount - 1, midy, ymult);
+ }
+ }
+ }
+ if (selectedCoef != -1 && !dragging &&
+ (
+ magcoefdisp[selectedCoef] > .04 ||
+ magcoefdisp[selectedCoef] < -.04
+ )) {
+ g.setColor(Color.yellow);
+ ymult *= magcoefdisp[selectedCoef];
+ for (i = sampStart; i <= sampEnd; i++) {
+ int x = winSize.width * i / sampleCount;
+ double dy = modeTable[i][selectedCoef] *
+ phasecoefcosdisp[selectedCoef];
+ int y = midy - (int) (ymult * dy);
+ drawBarPiece(g, x, y, i, sampStart);
+ }
+ }
+ if (selectedCoef != -1) {
+ int f = getFreq(selectedCoef);
+ g.setColor(Color.yellow);
+ centerString(g, f + " Hz", panelHeight);
+ } else if (soundCheck.getState()) {
+ int f = getFreq(0);
+ g.setColor(Color.white);
+ centerString(g, "Fundamental = " + f + " Hz", panelHeight);
+ }
+ int termWidth = getTermWidth();
+ ymult = .6 * halfPanel;
+ g.setColor(Color.white);
+ if (displayChooser.getSelectedIndex() == DISP_PHASE ||
+ displayChooser.getSelectedIndex() == DISP_PHASECOS) {
+ magnitudesY = panelHeight;
+ } else {
+ magnitudesY = panelHeight * 2;
+ }
+ midy = magnitudesY + (panelHeight / 2) + (int) ymult / 2;
+ g.setColor(gray2);
+ g.drawLine(0, midy, winSize.width, midy);
+ g.setColor(gray1);
+ g.drawLine(0, midy - (int) ymult, winSize.width, midy - (int) ymult);
+ g.drawLine(0, midy + (int) ymult, winSize.width, midy + (int) ymult);
+ g.drawLine(0, midy - (int) ymult / 4, winSize.width, midy - (int) ymult / 4);
+ g.drawLine(0, midy + (int) ymult / 4, winSize.width, midy + (int) ymult / 4);
+ int dotSize = termWidth - 3;
+ if (dotSize < 3) {
+ dotSize = 3;
+ }
+ for (i = 0; i != modeCount; i++) {
+ int t = termWidth * i + termWidth / 2;
+ int y = midy - (int) (logcoef(magcoefdisp[i]) * ymult);
+ g.setColor(i == selectedCoef ? Color.yellow : Color.white);
+ g.drawLine(t, midy, t, y);
+ g.fillOval(t - dotSize / 2, y - dotSize / 2, dotSize, dotSize);
+ }
+
+ if (displayChooser.getSelectedIndex() == DISP_PHASE ||
+ displayChooser.getSelectedIndex() == DISP_PHASECOS) {
+ g.setColor(Color.white);
+ boolean cosines =
+ displayChooser.getSelectedIndex() == DISP_PHASECOS;
+ ymult = .75 * halfPanel;
+ midy = ((panelHeight * 5) / 2);
+ for (i = -2; i <= 2; i++) {
+ if (cosines && (i == 1 || i == -1)) {
+ continue;
+ }
+ g.setColor((i == 0) ? gray2 : gray1);
+ g.drawLine(0, midy + (i * (int) ymult) / 2,
+ winSize.width, midy + (i * (int) ymult) / 2
+ );
+ }
+ if (!cosines) {
+ ymult /= pi;
+ }
+ for (i = 0; i != modeCount; i++) {
+ int t = termWidth * i + termWidth / 2;
+ double ph = (cosines) ? phasecoefcosdisp[i] : phasecoefdisp[i];
+ if (magcoef[i] > -epsilon2 / 4 && magcoefdisp[i] < epsilon2 / 4) {
+ ph = 0;
+ }
+ int y = midy - (int) (ph * ymult);
+ g.setColor(i == selectedCoef ? Color.yellow : Color.white);
+ g.drawLine(t, midy, t, y);
+ g.fillOval(t - dotSize / 2, y - dotSize / 2, dotSize, dotSize);
+ }
+ } else if (displayChooser.getSelectedIndex() == DISP_MODES) {
+ int sqw = (winSize.width - 25) / 3;
+ int sqh = (int) (sqw / pi);
+ int topY = panelHeight;
+ int leftX = 0;
+ int ox, oy = -1;
+ for (i = 0; i != modeCount; i++) {
+ if (!(
+ magcoefdisp[i] > .06 ||
+ magcoefdisp[i] < -.06
+ )) {
+ continue;
+ }
+ g.setColor(gray2);
+ int centerX = leftX + sqw / 2;
+ int centerY = topY + sqh / 2;
+ g.drawLine(leftX, centerY, leftX + sqw, centerY);
+ g.drawLine(centerX, topY, centerX, topY + sqh);
+ g.setColor(i == selectedCoef ? Color.yellow : Color.white);
+ g.drawRect(leftX, topY, sqw, sqh);
+ ox = -1;
+ ymult = sqh * .5 * magcoefdisp[i];
+ int j;
+ for (j = sampStart; j <= sampEnd; j++) {
+ int x = leftX + sqw * j / sampleCount;
+ double dy = modeTable[j][i] * phasecoefcosdisp[i];
+ int y = centerY - (int) (ymult * dy);
+ if (ox != -1) {
+ g.drawLine(ox, oy, x, y);
+ }
+ ox = x;
+ oy = y;
+ }
+ leftX += sqw + 10;
+ if (leftX + sqw > winSize.width) {
+ leftX = 0;
+ topY += sqh + 10;
+ if (topY + sqh > panelHeight * 2) {
+ break;
+ }
+ }
+ }
+ }
+ realg.drawImage(dbimage, 0, 0, this);
+ if (!stoppedCheck.getState() && !allQuiet) {
+ cv.repaint(pause);
+ }
+ }
+
+ void drawPin(Graphics g, int pos, int midy, double ymult) {
+ int x = winSize.width * pos / sampleCount;
+ g.setColor(gray2);
+ g.drawLine(x, (int) (midy - ymult),
+ x, (int) (midy + ymult)
+ );
+ g.setColor(Color.white);
+ g.fillOval(x - 2, midy - (int) (func[pos] * ymult) - 2, 5, 5);
+ }
+
+ int getTermWidth() {
+ int termWidth = winSize.width / modeCount;
+ int maxTermWidth = winSize.width / 30;
+ if (termWidth > maxTermWidth) {
+ termWidth = maxTermWidth;
+ }
+ termWidth &= ~1;
+ return termWidth;
+ }
+
+ void getVelocities() {
+ int k, j;
+ for (j = 0; j != sampleCount; j++) {
+ double dy = 0;
+ for (k = 0; k != modeCount; k++) {
+ dy += magcoef[k] * modeTable[j][k] *
+ java.lang.Math.sin(phasecoef[k]) * omega[k];
+ }
+ funci[j] = -dy;
+ }
+ }
+
+ void drawBarPiece(Graphics g, int x, int y, int i, int sampStart) {
+ int thick = setup.getThickness();
+ xpoints[0] = xpoints[3];
+ ypoints[0] = ypoints[3];
+ xpoints[1] = xpoints[2];
+ ypoints[1] = ypoints[2];
+ xpoints[2] = x;
+ ypoints[2] = y - thick;
+ xpoints[3] = x;
+ ypoints[3] = y + thick;
+ if (i != sampStart) {
+ if (thick == 0) {
+ g.drawLine(xpoints[0], ypoints[0], xpoints[2], ypoints[2]);
+ } else {
+ g.fillPolygon(xpoints, ypoints, 4);
+ }
+ }
+ }
+
+ void edit(MouseEvent e) {
+ if (selection == SEL_NONE) {
+ return;
+ }
+ int x = e.getX();
+ int y = e.getY();
+ switch (selection) {
+ case SEL_MAG:
+ editMag(x, y);
+ break;
+ case SEL_FUNC:
+ editFunc(x, y);
+ break;
+ }
+ }
+
+ void editMag(int x, int y) {
+ if (selectedCoef == -1) {
+ return;
+ }
+ int panelHeight = getPanelHeight();
+ double ymult = .6 * panelHeight / 2;
+ double midy = magnitudesY + (panelHeight / 2) + (int) ymult / 2;
+ double coef = -(y - midy) / ymult;
+ coef = unlogcoef(coef);
+ if (coef < -1) {
+ coef = -1;
+ }
+ if (coef > 1) {
+ coef = 1;
+ }
+ if (magcoef[selectedCoef] == coef) {
+ return;
+ }
+ magcoef[selectedCoef] = coef;
+ cv.repaint(pause);
+ }
+
+ void editFunc(int x, int y) {
+ if (modeChooser.getSelectedIndex() == MODE_FORCE) {
+ editFuncForce(x, y);
+ return;
+ }
+ if (dragX == x) {
+ editFuncPoint(x, y);
+ dragY = y;
+ } else {
+ // need to draw a line from old x,y to new x,y and
+ // call editFuncPoint for each point on that line. yuck.
+ int x1 = (x < dragX) ? x : dragX;
+ int y1 = (x < dragX) ? y : dragY;
+ int x2 = (x > dragX) ? x : dragX;
+ int y2 = (x > dragX) ? y : dragY;
+ dragX = x;
+ dragY = y;
+ for (x = x1; x <= x2; x++) {
+ y = y1 + (y2 - y1) * (x - x1) / (x2 - x1);
+ editFuncPoint(x, y);
+ }
+ }
+ }
+
+ double logep2 = 0;
+
+ double logcoef(double x) {
+ if (x >= .25 || x <= -.25) {
+ return x;
+ }
+ x *= 4;
+ double ep2 = epsilon2;
+ int sign = (x < 0) ? -1 : 1;
+ x *= sign;
+ if (x < ep2) {
+ return 0;
+ }
+ if (logep2 == 0) {
+ logep2 = -java.lang.Math.log(2 * ep2);
+ }
+ return .25 * sign * (java.lang.Math.log(x + ep2) + logep2) / logep2;
+ }
+
+ double unlogcoef(double x) {
+ if (x >= .25 || x <= -.25) {
+ return x;
+ }
+ double ep2 = epsilon2;
+ int sign = (x < 0) ? -1 : 1;
+ return .25 * sign * (java.lang.Math.exp(4 * x * sign * logep2 - logep2) - ep2);
+ }
+
+ void editFuncPoint(int x, int y) {
+ int panelHeight = getPanelHeight();
+ int midy = panelHeight / 2;
+ int halfPanel = panelHeight / 2;
+ int periodWidth = winSize.width;
+ double ymult = .75 * halfPanel;
+ int lox = x * sampleCount / periodWidth;
+ int hix = ((x + 1) * sampleCount - 1) / periodWidth;
+ double val = (midy - y) / ymult;
+ if (val > 1) {
+ val = 1;
+ }
+ if (val < -1) {
+ val = -1;
+ }
+ if (lox < 1) {
+ lox = 1;
+ }
+ if (hix >= sampleCount) {
+ hix = sampleCount - 1;
+ }
+ for (; lox <= hix; lox++) {
+ if (modeChooser.getSelectedIndex() == MODE_THICKNESS) {
+ thickness[lox] = (midy < y) ? (y - midy) * 2 : (midy - y) * 2;
+ if (thickness[lox] == 0) {
+ thickness[lox] = 1;
+ }
+ } else {
+ func[lox] = val;
+ funci[lox] = 0;
+ }
+ }
+ func[sampleCount] = func[0];
+ cv.repaint(pause);
+ if (soundCheck.getState() == false) {
+ transform(false);
+ }
+ }
+
+ void editFuncForce(int x, int y) {
+ int panelHeight = getPanelHeight();
+ int midy = panelHeight / 2;
+ int halfPanel = panelHeight / 2;
+ int periodWidth = winSize.width;
+ double ymult = .75 * halfPanel;
+ int ax = x * sampleCount / periodWidth;
+ double val = (midy - y) / ymult;
+ if (val > 1) {
+ val = 1;
+ }
+ if (val < -1) {
+ val = -1;
+ }
+ if (ax < 1 || ax >= sampleCount) {
+ return;
+ }
+
+ // solve equation A x = b, using diagonalized A,
+ // A = M Lambda M^-1. x = M Lambda^-1 M^-1 b. b is
+ // all zeros except at ax where it is 1.
+ double q[] = new double[modeCount];
+ int i, j;
+
+ // multiply b by Lambda^-1 M^-1. M^-1 has eigenvectors as rows
+ // (with the norm of each eigenvector divided out). Lambda^-1
+ // is a diagonal matrix with 1/lambdas as elements (lambda=omega^2)
+ for (i = 0; i != modeCount; i++) {
+ q[i] = modeTable[ax][i] / (omega[i] * omega[i] * modeNorms[i]);
+ }
+
+ // multiply q by M to get result. M has eigenvectors as columns.
+ for (i = 0; i != sampleCount; i++) {
+ double dy = 0;
+ for (j = 0; j != modeCount; j++) {
+ dy += q[j] * modeTable[i][j];
+ }
+ func[i] = dy;
+ }
+
+ // ok now we just scale the whole thing so we get the answer
+ // we want at ax.
+ double mult = val / func[ax];
+ for (i = 0; i <= sampleCount; i++) {
+ func[i] *= mult;
+ funci[i] = 0;
+ }
+
+ cv.repaint(pause);
+ if (soundCheck.getState() == false) {
+ transform(true);
+ }
+ }
+
+ public void componentHidden(ComponentEvent e) {
+ }
+
+ public void componentMoved(ComponentEvent e) {
+ }
+
+ public void componentShown(ComponentEvent e) {
+ cv.repaint(pause);
+ }
+
+ public void componentResized(ComponentEvent e) {
+ handleResize();
+ cv.repaint(pause);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ if (e.getSource() == sineButton) {
+ doFundamental();
+ cv.repaint();
+ }
+ if (e.getSource() == blankButton) {
+ doBlank();
+ cv.repaint();
+ }
+ }
+
+ public void adjustmentValueChanged(AdjustmentEvent e) {
+ System.out.print(((Scrollbar) e.getSource()).getValue() + "\n");
+ if (e.getSource() == dampingBar || e.getSource() == speedBar) {
+ setDamping();
+ }
+ if (e.getSource() == loadBar) {
+ setLoadCount();
+ }
+ if (e.getSource() == stiffnessBar) {
+ genModes();
+ }
+ cv.repaint(pause);
+ }
+
+ public boolean handleEvent(Event ev) {
+ if (ev.id == Event.WINDOW_DESTROY) {
+ //if (applet == null) dispose();
+ //else applet.destroyFrame();
+ return true;
+ }
+ return super.handleEvent(ev);
+ }
+
+ void setLoadCount() {
+ setup = (Setup)
+ setupList.elementAt(setupChooser.getSelectedIndex());
+ sampleCount = maxTerms = loadBar.getValue();
+ step = pi / sampleCount;
+ int x, y;
+ thickness = new int[sampleCount + 1];
+ int i;
+ for (i = 0; i <= sampleCount; i++) {
+ thickness[i] = 5;
+ }
+ genModes();
+ setDamping();
+ }
+
+ void setDamping() {
+ int i;
+ dampcoef = new double[modeCount];
+ double tadd = java.lang.Math.exp((speedBar.getValue() - 100) / 20.) * (.1 / 50);
+ for (i = 0; i != modeCount; i++) {
+ double damper = java.lang.Math.exp(dampingBar.getValue() / 40 - 3) * 30;
+ if (dampingBar.getValue() <= 2) {
+ damper = 0;
+ }
+ double damp2 = omega[i] * java.lang.Math.sqrt(
+ java.lang.Math.sqrt(1 + damper * damper / (omega[i] * omega[i])) - 1);
+ dampcoef[i] = java.lang.Math.exp(-damp2 * tadd * .004);
+ }
+ }
+
+ public void mouseDragged(MouseEvent e) {
+ dragging = true;
+ edit(e);
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
+ return;
+ }
+ int x = e.getX();
+ int y = e.getY();
+ dragX = x;
+ dragY = y;
+ int panelHeight = getPanelHeight();
+ int oldCoef = selectedCoef;
+ selectedCoef = -1;
+ selection = 0;
+ if (y < panelHeight) {
+ selection = SEL_FUNC;
+ }
+ if (y >= magnitudesY && y < magnitudesY + panelHeight) {
+ int termWidth = getTermWidth();
+ selectedCoef = x / termWidth;
+ if (selectedCoef >= modeCount) {
+ selectedCoef = -1;
+ }
+ if (selectedCoef != -1) {
+ selection = SEL_MAG;
+ }
+ }
+ if (selectedCoef != oldCoef) {
+ cv.repaint(pause);
+ }
+ }
+
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2 && selectedCoef != -1) {
+ int i;
+ for (i = 0; i != modeCount; i++) {
+ if (selectedCoef != i) {
+ magcoef[i] = 0;
+ }
+ }
+ magcoef[selectedCoef] = 1;
+ cv.repaint(pause);
+ }
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ }
+
+ public void mouseExited(MouseEvent e) {
+ if (!dragging && selectedCoef != -1) {
+ selectedCoef = -1;
+ cv.repaint(pause);
+ }
+ }
+
+ public void mousePressed(MouseEvent e) {
+ if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) {
+ return;
+ }
+ if (selection == SEL_FUNC) {
+ getVelocities();
+ }
+ dragging = true;
+ edit(e);
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) == 0) {
+ return;
+ }
+ if (dragging && selection == SEL_FUNC) {
+ if (modeChooser.getSelectedIndex() == MODE_THICKNESS) {
+ genModes();
+ } else {
+ transform(false);
+ if (soundCheck.getState()) {
+ doPlay();
+ }
+ }
+ }
+ if (dragging && selection == SEL_MAG && soundCheck.getState()) {
+ doPlay();
+ }
+ dragging = false;
+ cv.repaint(pause);
+ }
+
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getItemSelectable() == stoppedCheck) {
+ cv.repaint(pause);
+ return;
+ }
+ if (e.getItemSelectable() == soundCheck) {
+ if (soundCheck.getState()) {
+ speedBar.setValue(250);
+ dampingBar.setValue(170);
+ baseFreqBar.enable();
+ setDamping();
+ doPlay();
+ } else {
+ baseFreqBar.disable();
+ }
+ }
+ if (e.getItemSelectable() == displayChooser) {
+ cv.repaint(pause);
+ }
+ if (e.getItemSelectable() == setupChooser) {
+ setLoadCount();
+ if (setup instanceof StiffStringSetup) {
+ stiffnessBar.enable();
+ } else {
+ stiffnessBar.disable();
+ }
+ }
+ }
+
+ void dodiff(double matrix[][], int r, int i, int n, double mult) {
+ if (i < 1 && setup.leftBoundary() == BOUND_HINGED) {
+ return;
+ }
+ if (i > sampleCount - 1 && setup.rightBoundary() == BOUND_HINGED) {
+ return;
+ }
+
+ if (n == 2 && !(setup instanceof StringSetup)) {
+ if (i <= 1 && setup.leftBoundary() == BOUND_FREE) {
+ return;
+ }
+ if (i >= sampleCount - 1 && setup.rightBoundary() == BOUND_FREE) {
+ return;
+ }
+ }
+
+ if (n > 0) {
+ dodiff(matrix, r, i - 1, n - 2, -mult);
+ dodiff(matrix, r, i + 1, n - 2, -mult);
+ dodiff(matrix, r, i, n - 2, mult * 2);
+ return;
+ }
+ if (i >= 1 && i <= sampleCount - 1) {
+ matrix[r][i] += mult;
+ }
+ }
+
+ void genModes() {
+ int n = sampleCount - 1;
+ double matrix[][] = new double[n + 1][n + 1];
+ double d[] = new double[n + 1];
+ double e[] = new double[n + 1];
+ int i, j;
+ for (i = 1; i <= n; i++) {
+ setup.doMatrixStep(matrix, i, n);
+ }
+
+ if (setup instanceof StringSetup) {
+ if (setup.leftBoundary() == BOUND_FREE) {
+ matrix[1][1]--;
+ }
+ if (setup.rightBoundary() == BOUND_FREE) {
+ matrix[n][n]--;
+ }
+ }
+
+ for (j = 1; j <= n; j++) {
+ for (i = 1; i <= n; i++) {
+ System.out.print(matrix[i][j] + " ");
+ }
+ System.out.print("\n");
+ }
+
+ tred2(matrix, n, d, e);
+ tqli(d, e, n, matrix);
+
+ modeCount = sampleCount - 1;
+ omega = new double[modeCount];
+
+ // now get the eigenvalues and sort them
+ int omegamap[] = new int[sampleCount];
+ for (i = j = 0; i != n; i++) {
+ if (d[i + 1] < 1e-8) {
+ modeCount--;
+ continue;
+ }
+ omega[j] = java.lang.Math.sqrt(d[i + 1]);
+ omegamap[j] = i;
+ j++;
+ }
+
+ int si, sj;
+ // sort the omegas
+ for (si = 1; si < modeCount; si++) {
+ double v = omega[si];
+ int vm = omegamap[si];
+ sj = si;
+ while (omega[sj - 1] > v) {
+ omega[sj] = omega[sj - 1];
+ omegamap[sj] = omegamap[sj - 1];
+ sj--;
+ if (sj <= 0) {
+ break;
+ }
+ }
+ omega[sj] = v;
+ omegamap[sj] = vm;
+ }
+
+ modeTable = new double[sampleCount + 1][modeCount];
+ modeNorms = new double[modeCount];
+ for (i = 0; i != modeCount; i++) {
+ int om = omegamap[i] + 1;
+ double maxf = 0;
+ for (j = 0; j != sampleCount; j++) {
+ modeTable[j][i] = matrix[j][om];
+ if (modeTable[j][i] > maxf) {
+ maxf = modeTable[j][i];
+ }
+ if (-modeTable[j][i] > maxf) {
+ maxf = -modeTable[j][i];
+ }
+ }
+ modeNorms[i] = 1 / (maxf * maxf);
+ for (j = 0; j != sampleCount; j++) {
+ modeTable[j][i] /= maxf;
+ }
+ }
+
+ double mult = 1 / omega[0];
+ for (i = 0; i != modeCount; i++) {
+ omega[i] *= mult;
+ }
+ }
+
+
+ void tred2(double a[][], int n, double d[], double e[]) {
+ int l, k, j, i;
+ double scale, hh, h, g, f;
+
+ // this loop gets faster as i decreases
+ for (i = n; i >= 2; i--) {
+ l = i - 1;
+ h = scale = 0.0;
+ if (l > 1) {
+ for (k = 1; k <= l; k++) {
+ scale += java.lang.Math.abs(a[i][k]);
+ }
+ if (scale == 0.0) {
+ e[i] = a[i][l];
+ } else {
+ for (k = 1; k <= l; k++) {
+ a[i][k] /= scale;
+ h += a[i][k] * a[i][k];
+ }
+ f = a[i][l];
+ g = (f >= 0.0 ? -java.lang.Math.sqrt(h) : java.lang.Math.sqrt(h));
+ e[i] = scale * g;
+ h -= f * g;
+ a[i][l] = f - g;
+ f = 0.0;
+ for (j = 1; j <= l; j++) {
+ a[j][i] = a[i][j] / h;
+ g = 0.0;
+ for (k = 1; k <= j; k++) {
+ g += a[j][k] * a[i][k];
+ }
+ for (k = j + 1; k <= l; k++) {
+ g += a[k][j] * a[i][k];
+ }
+ e[j] = g / h;
+ f += e[j] * a[i][j];
+ }
+ hh = f / (h + h);
+ for (j = 1; j <= l; j++) {
+ f = a[i][j];
+ e[j] = g = e[j] - hh * f;
+ for (k = 1; k <= j; k++) {
+ a[j][k] -= (f * e[k] + g * a[i][k]);
+ }
+ }
+ }
+ } else {
+ e[i] = a[i][l];
+ }
+ d[i] = h;
+ }
+ d[1] = 0.0;
+ e[1] = 0.0;
+ /* Contents of this loop can be omitted if eigenvectors not
+ wanted except for statement d[i]=a[i][i]; */
+ // speed decreases as i increases
+ for (i = 1; i <= n; i++) {
+ l = i - 1;
+ if (d[i] != 0) {
+ for (j = 1; j <= l; j++) {
+ g = 0.0;
+ for (k = 1; k <= l; k++) {
+ g += a[i][k] * a[k][j];
+ }
+ for (k = 1; k <= l; k++) {
+ a[k][j] -= g * a[k][i];
+ }
+ }
+ }
+ d[i] = a[i][i];
+ a[i][i] = 1.0;
+ for (j = 1; j <= l; j++) {
+ a[j][i] = a[i][j] = 0.0;
+ }
+ }
+ }
+
+ // this is from Numerical Recipes in C. It finds the eigenvalues
+ // and eigenvectors of an nxn tridiagonal symmetric matrix specified
+ // by d[] and e[].
+ void tqli(double d[], double e[], int n, double z[][]) {
+ int m, l, iter, i, k;
+ double s, r, p, g, f, dd, c, b;
+
+ for (i = 2; i <= n; i++) {
+ e[i - 1] = e[i];
+ }
+ e[n] = 0.0;
+ // faster as l increases
+ for (l = 1; l <= n; l++) {
+ iter = 0;
+ do {
+ for (m = l; m <= n - 1; m++) {
+ dd = java.lang.Math.abs(d[m]) + java.lang.Math.abs(d[m + 1]);
+ if ((double) (java.lang.Math.abs(e[m]) + dd) == dd) {
+ break;
+ }
+ }
+ if (m != l) {
+ if (iter++ == 30) {
+ System.out.print("Too many iterations in tqli\n");
+ }
+ g = (d[l + 1] - d[l]) / (2.0 * e[l]);
+ r = pythag(g, 1.0);
+ g = d[m] - d[l] + e[l] / (g + SIGN(r, g));
+ s = c = 1.0;
+ p = 0.0;
+ for (i = m - 1; i >= l; i--) {
+ f = s * e[i];
+ b = c * e[i];
+ e[i + 1] = (r = pythag(f, g));
+ if (r == 0.0) {
+ d[i + 1] -= p;
+ e[m] = 0.0;
+ break;
+ }
+ s = f / r;
+ c = g / r;
+ g = d[i + 1] - p;
+ r = (d[i] - g) * s + 2.0 * c * b;
+ d[i + 1] = g + (p = s * r);
+ g = c * r - b;
+ for (k = 1; k <= n; k++) {
+ f = z[k][i + 1];
+ z[k][i + 1] = s * z[k][i] + c * f;
+ z[k][i] = c * z[k][i] - s * f;
+ }
+ }
+ if (r == 0.0 && i >= l) {
+ continue;
+ }
+ d[l] -= p;
+ e[l] = g;
+ e[m] = 0.0;
+ }
+ } while (m != l);
+ }
+ }
+
+ double SIGN(double a, double b) {
+ return b >= 0 ? java.lang.Math.abs(a) : -java.lang.Math.abs(a);
+ }
+
+ double SQR(double a) {
+ return a * a;
+ }
+
+ double pythag(double a, double b) {
+ double absa, absb;
+ absa = java.lang.Math.abs(a);
+ absb = java.lang.Math.abs(b);
+ if (absa > absb) {
+ return absa * java.lang.Math.sqrt(1.0 + SQR(absb / absa));
+ } else {
+ return (absb == 0.0 ? 0.0 : absb * java.lang.Math.sqrt(1.0 + SQR(absa / absb)));
+ }
+ }
+
+ abstract class Setup {
+ abstract String getName();
+
+ abstract Setup createNext();
+
+ abstract int leftBoundary();
+
+ abstract int rightBoundary();
+
+ int getThickness() {
+ return 3;
+ }
+
+ void doMatrixStep(double matrix[][], int i, int n) {
+ dodiff(matrix, i, i, 4, 1);
+ }
+ }
+
+ ;
+
+ class FreeBarSetup extends Setup {
+ String getName() {
+ return "bar, free";
+ }
+
+ Setup createNext() {
+ return new HingedBarSetup();
+ }
+
+ int leftBoundary() {
+ return BOUND_FREE;
+ }
+
+ int rightBoundary() {
+ return BOUND_FREE;
+ }
+ }
+
+ ;
+
+ class HingedBarSetup extends Setup {
+ String getName() {
+ return "bar, hinged";
+ }
+
+ Setup createNext() {
+ return new ClampedBarSetup();
+ }
+
+ int leftBoundary() {
+ return BOUND_HINGED;
+ }
+
+ int rightBoundary() {
+ return BOUND_HINGED;
+ }
+ }
+
+ ;
+
+ class ClampedBarSetup extends Setup {
+ String getName() {
+ return "bar, clamped";
+ }
+
+ Setup createNext() {
+ return new ClampedFreeBarSetup();
+ }
+
+ int leftBoundary() {
+ return BOUND_CLAMPED;
+ }
+
+ int rightBoundary() {
+ return BOUND_CLAMPED;
+ }
+ }
+
+ ;
+
+ class ClampedFreeBarSetup extends Setup {
+ String getName() {
+ return "bar, clamped/free";
+ }
+
+ Setup createNext() {
+ return new HingedClampedBarSetup();
+ }
+
+ int leftBoundary() {
+ return BOUND_CLAMPED;
+ }
+
+ int rightBoundary() {
+ return BOUND_FREE;
+ }
+ }
+
+ ;
+
+ class HingedClampedBarSetup extends Setup {
+ String getName() {
+ return "bar, hinged/clamped";
+ }
+
+ Setup createNext() {
+ return new StringSetup();
+ }
+
+ int leftBoundary() {
+ return BOUND_HINGED;
+ }
+
+ int rightBoundary() {
+ return BOUND_CLAMPED;
+ }
+ }
+
+ ;
+
+ class StringSetup extends Setup {
+ String getName() {
+ return "string, pinned";
+ }
+
+ void doMatrixStep(double matrix[][], int i, int n) {
+ dodiff(matrix, i, i, 2, 1);
+ }
+
+ Setup createNext() {
+ return new String1FreeSetup();
+ }
+
+ int leftBoundary() {
+ return BOUND_HINGED;
+ }
+
+ int rightBoundary() {
+ return BOUND_HINGED;
+ }
+
+ int getThickness() {
+ return 0;
+ }
+ }
+
+ ;
+
+ class String1FreeSetup extends StringSetup {
+ String getName() {
+ return "string, pinned/free";
+ }
+
+ Setup createNext() {
+ return new String2FreeSetup();
+ }
+
+ int rightBoundary() {
+ return BOUND_FREE;
+ }
+ }
+
+ ;
+
+ class String2FreeSetup extends String1FreeSetup {
+ String getName() {
+ return "string, free/free";
+ }
+
+ Setup createNext() {
+ return new StiffStringSetup();
+ }
+
+ int leftBoundary() {
+ return BOUND_FREE;
+ }
+ }
+
+ ;
+
+ class StiffStringSetup extends StringSetup {
+ String getName() {
+ return "stiff string, pinned";
+ }
+
+ void doMatrixStep(double matrix[][], int i, int n) {
+ dodiff(matrix, i, i, 2, 1);
+ double stiff = stiffnessBar.getValue() * .1;
+ dodiff(matrix, i, i, 4, stiff);
+ }
+
+ Setup createNext() {
+ return new StiffStringClampedSetup();
+ }
+ }
+
+ ;
+
+ class StiffStringClampedSetup extends StiffStringSetup {
+ String getName() {
+ return "stiff string, clamped";
+ }
+
+ Setup createNext() {
+ return null;
+ }
+
+ int leftBoundary() {
+ return BOUND_CLAMPED;
+ }
+
+ int rightBoundary() {
+ return BOUND_CLAMPED;
+ }
+ }
+
+ ;
+
+ double sndmin, sndmax;
+
+ int getFreq(int n) {
+ double stepsize = java.lang.Math.log(2) / 12;
+ double freq = java.lang.Math.exp(baseFreqBar.getValue() * stepsize);
+ return (int) (freq * omega[n]);
+ }
+
+ void doPlay() {
+ final int rate = 22000;
+ final int sampcount = rate;
+
+ byte b[] = new byte[sampcount];
+
+ double stepsize = java.lang.Math.log(2) / 12;
+ double freq = java.lang.Math.exp(baseFreqBar.getValue() * stepsize);
+ double n = 2 * pi * freq / rate;
+ n /= omega[0];
+ // filter out frequencies above Nyquist freq
+ double maxomega = pi / n;
+ int m = modeCount;
+ while (m > 0 && omega[m - 1] > maxomega) {
+ m--;
+ }
+ if (m == 0) {
+ return;
+ }
+ // filter out frequencies less than 20 Hz (we do that so that
+ // they do not throw off the bounds checking of the waveform)
+ int m0 = 0;
+ // freq = rate*omega*n/(2*pi)
+ double minomega = 20 * 2 * pi / (rate * n);
+ while (m0 < m && omega[m0] < minomega) {
+ m0++;
+ }
+ if (m0 == m) {
+ return;
+ }
+ boolean failed;
+ int i;
+ int sampWindow = rate / 40;
+ int offset = 0;
+ double lastscale = 1000;
+ double mag[] = new double[modeCount];
+ for (i = 0; i != modeCount; i++) {
+ mag[i] = magcoef[i];
+ }
+ do {
+ failed = false;
+ double mn = (-sndmin > sndmax) ? -sndmin : sndmax;
+ if (mn < .02) {
+ mn = .02;
+ }
+ double scale = 126 / mn;
+ if (scale > lastscale) {
+ scale = lastscale;
+ }
+ sndmin = sndmax = 0;
+ for (i = 0; i != sampWindow; i++) {
+ double dy = 0;
+ int j;
+ int ii = i + offset;
+ for (j = m0; j != m; j++) {
+ dy += mag[j] * java.lang.Math.sin(ii * n * omega[j]) * scale;
+ }
+ if (dy < sndmin) {
+ sndmin = dy;
+ }
+ if (dy > sndmax) {
+ sndmax = dy;
+ }
+ b[ii] = (byte) dy;
+ if (dy < -127 || dy > 127) {
+ failed = true;
+ }
+ }
+ sndmin /= scale;
+ sndmax /= scale;
+ if (failed) {
+ continue;
+ }
+ offset += sampWindow;
+ for (i = 0; i != modeCount; i++) {
+ mag[i] *= dampcoef[i];
+ }
+ if (offset >= sampcount) {
+ break;
+ }
+ } while (true);
+
+ AudioFormat format = new AudioFormat(rate, 8, 1, true, true);
+ DataLine.Info info = new DataLine.Info(
+ SourceDataLine.class,
+ format
+ );
+ SourceDataLine line = null;
+ try {
+ line = (SourceDataLine) AudioSystem.getLine(info);
+ line.open(format, sampcount);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ line.start();
+ line.write(b, 0, sampcount);
+ cv.repaint();
+ }
+};
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/GenerateId.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/GenerateId.kt
similarity index 90%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/GenerateId.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/GenerateId.kt
index d1e72a8..428caa2 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/GenerateId.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/GenerateId.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.vst.chip
+package nl.astraeus.vst.string
import java.security.SecureRandom
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/Main.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/Main.kt
similarity index 84%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/Main.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/Main.kt
index 7b42e14..dd2b8f6 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/Main.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/Main.kt
@@ -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
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/Settings.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/Settings.kt
similarity index 97%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/Settings.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/Settings.kt
index 9a76c92..7680d99 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/Settings.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/Settings.kt
@@ -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
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/BaseDao.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/BaseDao.kt
similarity index 98%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/db/BaseDao.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/db/BaseDao.kt
index 4ea410f..725fe9a 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/BaseDao.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/BaseDao.kt
@@ -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
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/Database.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/Database.kt
similarity index 98%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/db/Database.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/db/Database.kt
index 2276275..8f62db2 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/Database.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/Database.kt
@@ -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
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/Entity.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/Entity.kt
similarity index 87%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/db/Entity.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/db/Entity.kt
index 6c82712..9e32f19 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/Entity.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/Entity.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.vst.chip.db
+package nl.astraeus.vst.string.db
interface Entity {
fun getPK(): Array
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/Migrations.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/Migrations.kt
similarity index 96%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/db/Migrations.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/db/Migrations.kt
index 4207f92..754ad3b 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/Migrations.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/Migrations.kt
@@ -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
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/PatchDao.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/PatchDao.kt
similarity index 94%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/db/PatchDao.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/db/PatchDao.kt
index e24d5ba..c74b208 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/PatchDao.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/PatchDao.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.vst.chip.db
+package nl.astraeus.vst.string.db
object PatchDao : BaseDao() {
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/PatchEntity.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/PatchEntity.kt
similarity index 84%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/db/PatchEntity.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/db/PatchEntity.kt
index 80825d3..1276e7d 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/PatchEntity.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/PatchEntity.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.vst.chip.db
+package nl.astraeus.vst.string.db
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/PatchEntityQueryProvider.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/PatchEntityQueryProvider.kt
similarity index 97%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/db/PatchEntityQueryProvider.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/db/PatchEntityQueryProvider.kt
index cc3650c..3bea56e 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/db/PatchEntityQueryProvider.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/db/PatchEntityQueryProvider.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.vst.chip.db
+package nl.astraeus.vst.string.db
import java.sql.ResultSet
import java.sql.Types
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/web/Index.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/web/Index.kt
similarity index 87%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/web/Index.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/web/Index.kt
index 25cda93..d7c91b9 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/web/Index.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/web/Index.kt
@@ -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"
}
}
}
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/web/RequestHandler.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/web/RequestHandler.kt
similarity index 95%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/web/RequestHandler.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/web/RequestHandler.kt
index c36c8e1..4b6aaca 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/web/RequestHandler.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/web/RequestHandler.kt
@@ -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(
diff --git a/src/jvmMain/kotlin/nl/astraeus/vst/chip/web/Session.kt b/src/jvmMain/kotlin/nl/astraeus/vst/string/web/Session.kt
similarity index 53%
rename from src/jvmMain/kotlin/nl/astraeus/vst/chip/web/Session.kt
rename to src/jvmMain/kotlin/nl/astraeus/vst/string/web/Session.kt
index eb0c91b..068e1c9 100644
--- a/src/jvmMain/kotlin/nl/astraeus/vst/chip/web/Session.kt
+++ b/src/jvmMain/kotlin/nl/astraeus/vst/string/web/Session.kt
@@ -1,4 +1,4 @@
-package nl.astraeus.vst.chip.web
+package nl.astraeus.vst.string.web
class VstSession(
val patchId: String