Modulation, waveforms

This commit is contained in:
2024-06-28 17:07:58 +02:00
parent b02c7733b0
commit ccc7e9a4e9
4 changed files with 392 additions and 56 deletions

View File

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