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:
2026-07-03 14:22:44 +02:00
parent 887a10ec06
commit 418ce6452e
5 changed files with 189 additions and 7 deletions

View File

@@ -5,8 +5,12 @@ package nl.astraeus.vst.chip
import nl.astraeus.midi.message.SortedTimedMidiMessageList
import nl.astraeus.midi.message.TimedMidiMessage
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.registerProcessor
import nl.astraeus.vst.sampleRate
import org.khronos.webgl.Float32Array
import org.khronos.webgl.get
import org.khronos.webgl.set
@@ -17,7 +21,13 @@ import kotlin.math.sin
val POLYPHONICS = 20
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
@JsExport
class PlayingNote(
@@ -74,6 +84,14 @@ class VstChipProcessor : AudioWorkletProcessor() {
var amFreq = 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 decay = 0.2
var sustain = 0.5
@@ -159,6 +177,20 @@ class VstChipProcessor : AudioWorkletProcessor() {
addHandler(0xb0, 0x50) { b1, b2, b3 ->
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 ->
for (note in notes) {
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()
for (i in 0 until samples) {
if (delaySamples > 0) {