Modulation, waveforms
This commit is contained in:
@@ -6,7 +6,6 @@ import nl.astraeus.vst.AudioWorkletProcessor
|
||||
import nl.astraeus.vst.Note
|
||||
import nl.astraeus.vst.registerProcessor
|
||||
import nl.astraeus.vst.sampleRate
|
||||
import org.khronos.webgl.ArrayBuffer
|
||||
import org.khronos.webgl.Float32Array
|
||||
import org.khronos.webgl.Int32Array
|
||||
import org.khronos.webgl.Uint8Array
|
||||
@@ -14,6 +13,7 @@ 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
|
||||
@@ -56,6 +56,14 @@ enum class Waveform {
|
||||
SAWTOOTH
|
||||
}
|
||||
|
||||
@ExperimentalJsExport
|
||||
@JsExport
|
||||
enum class RecordingState {
|
||||
STOPPED,
|
||||
WAITING_TO_START,
|
||||
RECORDING
|
||||
}
|
||||
|
||||
@ExperimentalJsExport
|
||||
@JsExport
|
||||
class VstChipProcessor : AudioWorkletProcessor() {
|
||||
@@ -66,6 +74,7 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
)
|
||||
}
|
||||
var waveform = Waveform.SINE.ordinal
|
||||
var volume = 0.75f
|
||||
var dutyCycle = 0.5
|
||||
var fmFreq = 0.0
|
||||
var fmAmp = 0.0
|
||||
@@ -73,25 +82,46 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
var amAmp = 0.0
|
||||
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)
|
||||
//console.log("VstChipProcessor: Received message:", message.data)
|
||||
|
||||
val data = message.data
|
||||
|
||||
try {
|
||||
when (data) {
|
||||
is String -> {
|
||||
if (data.startsWith("set_channel")) {
|
||||
val parts = data.split('\n')
|
||||
if (parts.size == 2) {
|
||||
midiChannel = parts[1].toInt()
|
||||
println("Setting channel: $midiChannel")
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +198,9 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
val value = bytes[2]
|
||||
|
||||
when (knob) {
|
||||
0x46 -> {
|
||||
volume = value / 127f
|
||||
}
|
||||
0x4a -> {
|
||||
dutyCycle = value / 127.0
|
||||
}
|
||||
@@ -244,6 +277,18 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
val left = outputs[0][0]
|
||||
val right = outputs[0][1]
|
||||
|
||||
var lowestNote = 200
|
||||
for (note in notes) {
|
||||
if (note.state != NoteState.OFF) {
|
||||
lowestNote = min(lowestNote, note.note)
|
||||
}
|
||||
}
|
||||
if (lowestNote == 200 && recordingState == RecordingState.WAITING_TO_START) {
|
||||
recordingState = RecordingState.RECORDING
|
||||
recordingSample = 0
|
||||
recordingStart = 0
|
||||
}
|
||||
|
||||
for (note in notes) {
|
||||
if (note.state != NoteState.OFF) {
|
||||
val sampleDelta = Note.fromMidi(note.note).sampleDelta
|
||||
@@ -264,16 +309,21 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
}
|
||||
|
||||
var cycleOffset = note.cycleOffset
|
||||
val fmModulation = sin(sampleLength * fmFreq * 10f * PI2 * note.sample).toFloat() * fmAmp * 5f
|
||||
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 += fmModulation
|
||||
|
||||
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 < dutyCycle) { 1f } else { -1f }
|
||||
if (cycleOffset < 0.5) { 1f } else { -1f }
|
||||
}
|
||||
2 -> when {
|
||||
cycleOffset < 0.25 -> 4 * cycleOffset
|
||||
@@ -288,18 +338,35 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
}
|
||||
}
|
||||
|
||||
left[i] = left[i] + waveValue * note.actualVolume * 0.3f * amModulation
|
||||
right[i] = right[i] + waveValue * note.actualVolume * 0.3f * amModulation
|
||||
left[i] = left[i] + waveValue * note.actualVolume * volume * amModulation
|
||||
right[i] = right[i] + waveValue * note.actualVolume * volume * amModulation
|
||||
|
||||
note.cycleOffset += sampleDelta
|
||||
if (cycleOffset > 1f) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,134 @@
|
||||
package nl.astraeus.vst.chip.audio
|
||||
|
||||
import nl.astraeus.vst.chip.view.MainView
|
||||
import nl.astraeus.vst.chip.view.WaveformView
|
||||
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
|
||||
var volume = 0.75
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
Uint8Array(arrayOf(0xb0.toByte(), 0x46.toByte(), (value * 127).toInt().toByte()))
|
||||
)
|
||||
}
|
||||
var dutyCycle = 0.5
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
Uint8Array(arrayOf(0xb0.toByte(), 0x4a.toByte(), (value * 127).toInt().toByte()))
|
||||
)
|
||||
}
|
||||
var fmModFreq = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
postMessage(
|
||||
super.postMessage(
|
||||
Uint8Array(arrayOf(0xb0.toByte(), 0x4b.toByte(), (value * 127).toInt().toByte()))
|
||||
)
|
||||
}
|
||||
var fmModAmp = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
postMessage(
|
||||
super.postMessage(
|
||||
Uint8Array(arrayOf(0xb0.toByte(), 0x4c.toByte(), (value * 127).toInt().toByte()))
|
||||
)
|
||||
}
|
||||
var amModFreq = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
Uint8Array(arrayOf(0xb0.toByte(), 0x47.toByte(), (value * 127).toInt().toByte()))
|
||||
)
|
||||
}
|
||||
var amModAmp = 0.0
|
||||
set(value) {
|
||||
field = value
|
||||
super.postMessage(
|
||||
Uint8Array(arrayOf(0xb0.toByte(), 0x48.toByte(), (value * 127).toInt().toByte()))
|
||||
)
|
||||
}
|
||||
var recording: Float32Array? = null
|
||||
|
||||
override fun onMessage(message: MessageEvent) {
|
||||
console.log("Message from worklet: ", message)
|
||||
//console.log("Message from worklet: ", message)
|
||||
|
||||
val data = message.data
|
||||
if (data is Float32Array) {
|
||||
this.recording = data
|
||||
WaveformView.requestUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
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 setChannel(channel: Int) {
|
||||
midiChannel = channel
|
||||
|
||||
postMessage("set_channel\n${midiChannel}")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ external class MIDIOutput {
|
||||
}
|
||||
|
||||
object Midi {
|
||||
var inputChannel: Int = -1
|
||||
var outputChannel: Int = -1
|
||||
|
||||
var inputs = mutableListOf<MIDIInput>()
|
||||
@@ -110,7 +109,7 @@ object Midi {
|
||||
currentOutput?.open()
|
||||
}
|
||||
|
||||
fun send(data: Uint8Array, timestamp: dynamic? = null) {
|
||||
fun send(data: Uint8Array, timestamp: dynamic = null) {
|
||||
currentOutput?.send(data, timestamp)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
package nl.astraeus.vst.chip.view
|
||||
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.html.*
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.canvas
|
||||
import kotlinx.html.classes
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.h1
|
||||
import kotlinx.html.input
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import kotlinx.html.js.onInputFunction
|
||||
import nl.astraeus.css.properties.*
|
||||
import kotlinx.html.option
|
||||
import kotlinx.html.select
|
||||
import kotlinx.html.span
|
||||
import nl.astraeus.css.properties.AlignItems
|
||||
import nl.astraeus.css.properties.BoxSizing
|
||||
import nl.astraeus.css.properties.Display
|
||||
import nl.astraeus.css.properties.FlexDirection
|
||||
import nl.astraeus.css.properties.FontWeight
|
||||
import nl.astraeus.css.properties.JustifyContent
|
||||
import nl.astraeus.css.properties.Position
|
||||
import nl.astraeus.css.properties.Transform
|
||||
import nl.astraeus.css.properties.em
|
||||
import nl.astraeus.css.properties.hsla
|
||||
import nl.astraeus.css.properties.prc
|
||||
import nl.astraeus.css.properties.px
|
||||
import nl.astraeus.css.properties.rem
|
||||
import nl.astraeus.css.properties.vh
|
||||
import nl.astraeus.css.properties.vw
|
||||
import nl.astraeus.css.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.midi.Midi
|
||||
import nl.astraeus.vst.ui.components.KnobComponent
|
||||
@@ -17,14 +40,60 @@ 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.util.formatDouble
|
||||
import org.khronos.webgl.Uint8Array
|
||||
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.clearRect(0.0, 0.0, width, height)
|
||||
val step = 1000.0 / data.length
|
||||
ctx.beginPath()
|
||||
ctx.strokeStyle = "rgba(255, 255, 255, 0.5)"
|
||||
ctx.moveTo(0.0, halfHeight)
|
||||
for (i in 0 until data.length) {
|
||||
ctx.lineTo(i * step, halfHeight - data[i] * halfHeight)
|
||||
}
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object MainView : Komponent(), CssName {
|
||||
private var messages: MutableList<String> = ArrayList()
|
||||
private var started = false
|
||||
var started = false
|
||||
|
||||
init {
|
||||
css()
|
||||
@@ -46,8 +115,8 @@ object MainView : Komponent(), CssName {
|
||||
div(StartButtonCss.name) {
|
||||
+"START"
|
||||
onClickFunction = {
|
||||
started = true
|
||||
VstChipWorklet.create {
|
||||
started = true
|
||||
requestUpdate()
|
||||
}
|
||||
}
|
||||
@@ -92,12 +161,11 @@ object MainView : Komponent(), CssName {
|
||||
+"channel:"
|
||||
input {
|
||||
type = InputType.number
|
||||
value = Midi.inputChannel.toString()
|
||||
value = VstChipWorklet.midiChannel.toString()
|
||||
onInputFunction = { event ->
|
||||
val target = event.target as HTMLInputElement
|
||||
Midi.inputChannel = target.value.toInt()
|
||||
println("onInput channel: ${Midi.inputChannel}")
|
||||
VstChipWorklet.postMessage("set_channel\n${Midi.inputChannel}")
|
||||
println("onInput channel: $target")
|
||||
VstChipWorklet.setChannel(target.value.toInt())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,31 +210,75 @@ object MainView : Komponent(), CssName {
|
||||
}
|
||||
}
|
||||
}
|
||||
div(ButtonCss.name) {
|
||||
+"Send note on to output"
|
||||
onClickFunction = {
|
||||
val data = Uint8Array(
|
||||
arrayOf(
|
||||
0x90.toByte(),
|
||||
0x3c.toByte(),
|
||||
0x70.toByte()
|
||||
div {
|
||||
span(ButtonCss.name) {
|
||||
+"Send note on to output"
|
||||
onClickFunction = {
|
||||
val data = Uint8Array(
|
||||
arrayOf(
|
||||
0x90.toByte(),
|
||||
0x3c.toByte(),
|
||||
0x70.toByte()
|
||||
)
|
||||
)
|
||||
)
|
||||
Midi.send(data, window.performance.now() + 1000)
|
||||
Midi.send(data, window.performance.now() + 2000)
|
||||
Midi.send(data, window.performance.now() + 1000)
|
||||
Midi.send(data, window.performance.now() + 2000)
|
||||
}
|
||||
}
|
||||
span(ButtonCss.name) {
|
||||
+"Send note off to output"
|
||||
onClickFunction = {
|
||||
val data = Uint8Array(
|
||||
arrayOf(
|
||||
0x90.toByte(),
|
||||
0x3c.toByte(),
|
||||
0x0.toByte(),
|
||||
)
|
||||
)
|
||||
Midi.send(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
div(ButtonCss.name) {
|
||||
+"Send note off to output"
|
||||
onClickFunction = {
|
||||
val data = Uint8Array(
|
||||
arrayOf(
|
||||
0x90.toByte(),
|
||||
0x3c.toByte(),
|
||||
0x0.toByte(),
|
||||
)
|
||||
)
|
||||
Midi.send(data)
|
||||
div {
|
||||
span(ButtonCss.name) {
|
||||
+"Sine"
|
||||
if (VstChipWorklet.waveform == 0) {
|
||||
classes += SelectedCss.name
|
||||
}
|
||||
onClickFunction = {
|
||||
VstChipWorklet.waveform = 0
|
||||
requestUpdate()
|
||||
}
|
||||
}
|
||||
span(ButtonCss.name) {
|
||||
+"Square"
|
||||
if (VstChipWorklet.waveform == 1) {
|
||||
classes += SelectedCss.name
|
||||
}
|
||||
onClickFunction = {
|
||||
VstChipWorklet.waveform = 1
|
||||
requestUpdate()
|
||||
}
|
||||
}
|
||||
span(ButtonCss.name) {
|
||||
+"Triangle"
|
||||
if (VstChipWorklet.waveform == 2) {
|
||||
classes += SelectedCss.name
|
||||
}
|
||||
onClickFunction = {
|
||||
VstChipWorklet.waveform = 2
|
||||
requestUpdate()
|
||||
}
|
||||
}
|
||||
span(ButtonCss.name) {
|
||||
+"Sawtooth"
|
||||
if (VstChipWorklet.waveform == 3) {
|
||||
classes += SelectedCss.name
|
||||
}
|
||||
onClickFunction = {
|
||||
VstChipWorklet.waveform = 3
|
||||
requestUpdate()
|
||||
}
|
||||
}
|
||||
}
|
||||
div(ControlsCss.name) {
|
||||
@@ -176,19 +288,35 @@ object MainView : Komponent(), CssName {
|
||||
label = "Volume",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.0
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
println("Value changed: ${formatDouble(value, 2)}")
|
||||
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
|
||||
step = 2.0 / 127.0,
|
||||
width = 100,
|
||||
height = 120,
|
||||
) { value ->
|
||||
VstChipWorklet.fmModFreq = value
|
||||
}
|
||||
@@ -199,18 +327,48 @@ object MainView : Komponent(), CssName {
|
||||
label = "FM Ampl",
|
||||
minValue = 0.0,
|
||||
maxValue = 1.0,
|
||||
step = 2.0 / 127.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
|
||||
}
|
||||
)
|
||||
}
|
||||
include(WaveformView)
|
||||
}
|
||||
}
|
||||
|
||||
object MainDivCss : CssName
|
||||
object ActiveCss : CssName
|
||||
object ButtonCss : CssName
|
||||
object SelectedCss : CssName
|
||||
object NoteBarCss : CssName
|
||||
object StartSplashCss : CssName
|
||||
object StartBoxCss : CssName
|
||||
@@ -242,7 +400,12 @@ object MainView : Komponent(), CssName {
|
||||
//transition()
|
||||
noTextSelect()
|
||||
}
|
||||
select("select", "input", "textarea") {
|
||||
backgroundColor(Css.currentStyle.mainBackgroundColor)
|
||||
color(Css.currentStyle.mainFontColor)
|
||||
}
|
||||
select(cls(ButtonCss)) {
|
||||
display(Display.inlineBlock)
|
||||
margin(1.rem)
|
||||
padding(1.rem)
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor)
|
||||
@@ -253,6 +416,9 @@ object MainView : Komponent(), CssName {
|
||||
hover {
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor.hover())
|
||||
}
|
||||
and(SelectedCss.cls()) {
|
||||
backgroundColor(Css.currentStyle.buttonBackgroundColor.hover().hover().hover())
|
||||
}
|
||||
}
|
||||
select(cls(ActiveCss)) {
|
||||
//backgroundColor(Css.currentStyle.selectedBackgroundColor)
|
||||
@@ -305,8 +471,8 @@ object MainView : Komponent(), CssName {
|
||||
}
|
||||
select(ControlsCss.cls()) {
|
||||
display(Display.flex)
|
||||
flexDirection(FlexDirection.column)
|
||||
justifyContent(JustifyContent.center)
|
||||
flexDirection(FlexDirection.row)
|
||||
justifyContent(JustifyContent.flexStart)
|
||||
alignItems(AlignItems.center)
|
||||
margin(1.rem)
|
||||
padding(1.rem)
|
||||
|
||||
Reference in New Issue
Block a user