Add chorus effect implementation to ChipProcessor
Introduced chorus effect parameters (`rate`, `depth`, `mix`) and integrated real-time processing. Updated `PatchDTO` and `MainView` to support chorus controls and visualization. Enhanced MIDI handling for dynamic chorus adjustments.
This commit is contained in:
1
.idea/misc.xml
generated
1
.idea/misc.xml
generated
@@ -1,4 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="FrameworkDetectionExcludesConfiguration">
|
<component name="FrameworkDetectionExcludesConfiguration">
|
||||||
|
|||||||
@@ -5,8 +5,12 @@ package nl.astraeus.vst.chip
|
|||||||
import nl.astraeus.midi.message.SortedTimedMidiMessageList
|
import nl.astraeus.midi.message.SortedTimedMidiMessageList
|
||||||
import nl.astraeus.midi.message.TimedMidiMessage
|
import nl.astraeus.midi.message.TimedMidiMessage
|
||||||
import nl.astraeus.tba.SlicedByteArray
|
import nl.astraeus.tba.SlicedByteArray
|
||||||
import nl.astraeus.vst.*
|
import nl.astraeus.vst.ADSR
|
||||||
|
import nl.astraeus.vst.AudioWorkletProcessor
|
||||||
|
import nl.astraeus.vst.currentTime
|
||||||
import nl.astraeus.vst.midi.MidiMessageHandler
|
import nl.astraeus.vst.midi.MidiMessageHandler
|
||||||
|
import nl.astraeus.vst.registerProcessor
|
||||||
|
import nl.astraeus.vst.sampleRate
|
||||||
import org.khronos.webgl.Float32Array
|
import org.khronos.webgl.Float32Array
|
||||||
import org.khronos.webgl.get
|
import org.khronos.webgl.get
|
||||||
import org.khronos.webgl.set
|
import org.khronos.webgl.set
|
||||||
@@ -17,7 +21,13 @@ import kotlin.math.sin
|
|||||||
|
|
||||||
val POLYPHONICS = 20
|
val POLYPHONICS = 20
|
||||||
val PI2 = PI * 2
|
val PI2 = PI * 2
|
||||||
|
var chorusRate = 0.25
|
||||||
|
var chorusDepth = 0.0
|
||||||
|
var chorusMix = 0.0
|
||||||
|
var chorusPhase = 0.0
|
||||||
|
val leftChorusBuffer = Float32Array(sampleRate)
|
||||||
|
val rightChorusBuffer = Float32Array(sampleRate)
|
||||||
|
var chorusIndex = 0
|
||||||
@ExperimentalJsExport
|
@ExperimentalJsExport
|
||||||
@JsExport
|
@JsExport
|
||||||
class PlayingNote(
|
class PlayingNote(
|
||||||
@@ -74,6 +84,14 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
|||||||
var amFreq = 0.0
|
var amFreq = 0.0
|
||||||
var amAmp = 0.0
|
var amAmp = 0.0
|
||||||
|
|
||||||
|
var chorusRate = 0.25
|
||||||
|
var chorusDepth = 0.0
|
||||||
|
var chorusMix = 0.0
|
||||||
|
var chorusPhase = 0.0
|
||||||
|
val leftChorusBuffer = Float32Array(sampleRate)
|
||||||
|
val rightChorusBuffer = Float32Array(sampleRate)
|
||||||
|
var chorusIndex = 0
|
||||||
|
|
||||||
var attack = 0.1
|
var attack = 0.1
|
||||||
var decay = 0.2
|
var decay = 0.2
|
||||||
var sustain = 0.5
|
var sustain = 0.5
|
||||||
@@ -159,6 +177,20 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
|||||||
addHandler(0xb0, 0x50) { b1, b2, b3 ->
|
addHandler(0xb0, 0x50) { b1, b2, b3 ->
|
||||||
feedback = b3 / 127.0
|
feedback = b3 / 127.0
|
||||||
}
|
}
|
||||||
|
addHandler(0xb0, 0x51) { b1, b2, b3 ->
|
||||||
|
chorusRate = 0.01 + (b3 / 127.0) * 99.99
|
||||||
|
}
|
||||||
|
addHandler(0xb0, 0x52) { b1, b2, b3 ->
|
||||||
|
chorusDepth = b3 / 127.0
|
||||||
|
}
|
||||||
|
addHandler(0xb0, 0x53) { b1, b2, b3 ->
|
||||||
|
chorusMix = b3 / 127.0
|
||||||
|
}
|
||||||
|
addHandler(0xb0, 123) { b1, b2, b3 ->
|
||||||
|
for (note in notes) {
|
||||||
|
note?.noteRelease = currentTime
|
||||||
|
}
|
||||||
|
}
|
||||||
addHandler(0xb0, 123) { b1, b2, b3 ->
|
addHandler(0xb0, 123) { b1, b2, b3 ->
|
||||||
for (note in notes) {
|
for (note in notes) {
|
||||||
note?.noteRelease = currentTime
|
note?.noteRelease = currentTime
|
||||||
@@ -418,6 +450,52 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
if (chorusMix > 0.0 && chorusDepth > 0.0) {
|
||||||
|
val baseDelaySamples = (sampleRate * 0.018).toInt()
|
||||||
|
val maxModulationSamples = sampleRate * 0.012 * chorusDepth
|
||||||
|
|
||||||
|
for (i in 0 until samples) {
|
||||||
|
val dryLeft = left[i]
|
||||||
|
val dryRight = right[i]
|
||||||
|
|
||||||
|
val leftLfo = sin(chorusPhase)
|
||||||
|
val rightLfo = sin(chorusPhase + PI)
|
||||||
|
|
||||||
|
val leftDelaySamples = baseDelaySamples + (leftLfo * maxModulationSamples).toInt()
|
||||||
|
val rightDelaySamples = baseDelaySamples + (rightLfo * maxModulationSamples).toInt()
|
||||||
|
|
||||||
|
val leftReadIndex =
|
||||||
|
(chorusIndex + leftChorusBuffer.length - leftDelaySamples) % leftChorusBuffer.length
|
||||||
|
val rightReadIndex =
|
||||||
|
(chorusIndex + rightChorusBuffer.length - rightDelaySamples) % rightChorusBuffer.length
|
||||||
|
|
||||||
|
val wetLeft = leftChorusBuffer[leftReadIndex]
|
||||||
|
val wetRight = rightChorusBuffer[rightReadIndex]
|
||||||
|
|
||||||
|
left[i] = dryLeft * (1f - chorusMix.toFloat()) + wetLeft * chorusMix.toFloat()
|
||||||
|
right[i] = dryRight * (1f - chorusMix.toFloat()) + wetRight * chorusMix.toFloat()
|
||||||
|
|
||||||
|
leftChorusBuffer[chorusIndex] = dryLeft
|
||||||
|
rightChorusBuffer[chorusIndex] = dryRight
|
||||||
|
|
||||||
|
chorusIndex++
|
||||||
|
chorusIndex %= leftChorusBuffer.length
|
||||||
|
|
||||||
|
chorusPhase += PI2 * chorusRate / sampleRate
|
||||||
|
if (chorusPhase > PI2) {
|
||||||
|
chorusPhase -= PI2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i in 0 until samples) {
|
||||||
|
leftChorusBuffer[chorusIndex] = left[i]
|
||||||
|
rightChorusBuffer[chorusIndex] = right[i]
|
||||||
|
|
||||||
|
chorusIndex++
|
||||||
|
chorusIndex %= leftChorusBuffer.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val delaySamples = (delay * leftDelayBuffer.length).toInt()
|
val delaySamples = (delay * leftDelayBuffer.length).toInt()
|
||||||
for (i in 0 until samples) {
|
for (i in 0 until samples) {
|
||||||
if (delaySamples > 0) {
|
if (delaySamples > 0) {
|
||||||
|
|||||||
@@ -41,4 +41,10 @@ data class PatchDTO(
|
|||||||
var delayDepth: Double = 0.0,
|
var delayDepth: Double = 0.0,
|
||||||
@JsName("feedback")
|
@JsName("feedback")
|
||||||
var feedback: Double = 0.0,
|
var feedback: Double = 0.0,
|
||||||
|
@JsName("chorusRate")
|
||||||
|
var chorusRate: Double = 0.0,
|
||||||
|
@JsName("chorusDepth")
|
||||||
|
var chorusDepth: Double = 0.0,
|
||||||
|
@JsName("chorusMix")
|
||||||
|
var chorusMix: Double = 0.0,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -119,6 +119,30 @@ object VstChipWorklet : AudioNode(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var chorusRate = 0.0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
super.postMessage(
|
||||||
|
0xb0 + midiChannel, 0x51, (value * 127).toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var chorusDepth = 0.0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
super.postMessage(
|
||||||
|
0xb0 + midiChannel, 0x52, (value * 127).toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
var chorusMix = 0.0
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
super.postMessage(
|
||||||
|
0xb0 + midiChannel, 0x53, (value * 127).toInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var recording: Float32Array? = null
|
var recording: Float32Array? = null
|
||||||
|
|
||||||
override fun onMessage(message: MessageEvent) {
|
override fun onMessage(message: MessageEvent) {
|
||||||
@@ -214,6 +238,9 @@ object VstChipWorklet : AudioNode(
|
|||||||
delay = patch.delay
|
delay = patch.delay
|
||||||
delayDepth = patch.delayDepth
|
delayDepth = patch.delayDepth
|
||||||
feedback = patch.feedback
|
feedback = patch.feedback
|
||||||
|
chorusRate = patch.chorusRate
|
||||||
|
chorusDepth = patch.chorusDepth
|
||||||
|
chorusMix = patch.chorusMix
|
||||||
}
|
}
|
||||||
|
|
||||||
fun save(): PatchDTO {
|
fun save(): PatchDTO {
|
||||||
@@ -232,8 +259,12 @@ object VstChipWorklet : AudioNode(
|
|||||||
release = release,
|
release = release,
|
||||||
delay = delay,
|
delay = delay,
|
||||||
delayDepth = delayDepth,
|
delayDepth = delayDepth,
|
||||||
feedback = feedback
|
feedback = feedback,
|
||||||
|
chorusRate = chorusRate,
|
||||||
|
chorusDepth = chorusDepth,
|
||||||
|
chorusMix = chorusMix,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,34 @@ package nl.astraeus.vst.chip.view
|
|||||||
|
|
||||||
import kotlinx.browser.document
|
import kotlinx.browser.document
|
||||||
import kotlinx.browser.window
|
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.id
|
||||||
|
import kotlinx.html.input
|
||||||
import kotlinx.html.js.onChangeFunction
|
import kotlinx.html.js.onChangeFunction
|
||||||
import kotlinx.html.js.onClickFunction
|
import kotlinx.html.js.onClickFunction
|
||||||
import kotlinx.html.js.onInputFunction
|
import kotlinx.html.js.onInputFunction
|
||||||
import nl.astraeus.css.properties.*
|
import kotlinx.html.option
|
||||||
|
import kotlinx.html.select
|
||||||
|
import kotlinx.html.span
|
||||||
|
import kotlinx.html.style
|
||||||
|
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.Overflow
|
||||||
|
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.style.Style
|
import nl.astraeus.css.style.Style
|
||||||
import nl.astraeus.css.style.cls
|
import nl.astraeus.css.style.cls
|
||||||
import nl.astraeus.komp.HtmlBuilder
|
import nl.astraeus.komp.HtmlBuilder
|
||||||
@@ -29,7 +52,11 @@ import nl.astraeus.vst.ui.css.Css.noTextSelect
|
|||||||
import nl.astraeus.vst.ui.css.CssName
|
import nl.astraeus.vst.ui.css.CssName
|
||||||
import nl.astraeus.vst.ui.css.hover
|
import nl.astraeus.vst.ui.css.hover
|
||||||
import org.khronos.webgl.get
|
import org.khronos.webgl.get
|
||||||
import org.w3c.dom.*
|
import org.w3c.dom.CanvasRenderingContext2D
|
||||||
|
import org.w3c.dom.Element
|
||||||
|
import org.w3c.dom.HTMLCanvasElement
|
||||||
|
import org.w3c.dom.HTMLInputElement
|
||||||
|
import org.w3c.dom.HTMLSelectElement
|
||||||
|
|
||||||
object WaveformView : Komponent() {
|
object WaveformView : Komponent() {
|
||||||
|
|
||||||
@@ -347,6 +374,47 @@ class MainView : Komponent() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
div(ControlsCss.name) {
|
||||||
|
include(
|
||||||
|
KnobComponent(
|
||||||
|
value = VstChipWorklet.chorusRate,
|
||||||
|
label = "Rate",
|
||||||
|
minValue = 0.0,
|
||||||
|
maxValue = 1.0,
|
||||||
|
step = 2.0 / 127.0,
|
||||||
|
width = 100,
|
||||||
|
height = 120,
|
||||||
|
) { value ->
|
||||||
|
VstChipWorklet.chorusRate = value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
include(
|
||||||
|
KnobComponent(
|
||||||
|
value = VstChipWorklet.chorusDepth,
|
||||||
|
label = "Depth",
|
||||||
|
minValue = 0.0,
|
||||||
|
maxValue = 1.0,
|
||||||
|
step = 2.0 / 127.0,
|
||||||
|
width = 100,
|
||||||
|
height = 120,
|
||||||
|
) { value ->
|
||||||
|
VstChipWorklet.chorusDepth = value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
include(
|
||||||
|
KnobComponent(
|
||||||
|
value = VstChipWorklet.chorusMix,
|
||||||
|
label = "Mix",
|
||||||
|
minValue = 0.0,
|
||||||
|
maxValue = 1.0,
|
||||||
|
step = 2.0 / 127.0,
|
||||||
|
width = 100,
|
||||||
|
height = 120,
|
||||||
|
) { value ->
|
||||||
|
VstChipWorklet.chorusMix = value
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
div(ControlsCss.name) {
|
div(ControlsCss.name) {
|
||||||
include(
|
include(
|
||||||
ExpKnobComponent(
|
ExpKnobComponent(
|
||||||
|
|||||||
Reference in New Issue
Block a user