Save patch
This commit is contained in:
@@ -2,8 +2,10 @@
|
||||
|
||||
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
|
||||
@@ -19,14 +21,6 @@ import kotlin.math.sin
|
||||
val POLYPHONICS = 10
|
||||
val PI2 = PI * 2
|
||||
|
||||
@ExperimentalJsExport
|
||||
@JsExport
|
||||
enum class NoteState {
|
||||
ON,
|
||||
RELEASED,
|
||||
OFF
|
||||
}
|
||||
|
||||
@ExperimentalJsExport
|
||||
@JsExport
|
||||
class PlayingNote(
|
||||
@@ -35,17 +29,15 @@ class PlayingNote(
|
||||
) {
|
||||
fun retrigger(velocity: Int) {
|
||||
this.velocity = velocity
|
||||
state = NoteState.ON
|
||||
sample = 0
|
||||
attackSamples = 2500
|
||||
releaseSamples = 10000
|
||||
noteStart = currentTime
|
||||
noteRelease = null
|
||||
}
|
||||
|
||||
var state = NoteState.OFF
|
||||
var noteStart = currentTime
|
||||
var noteRelease: Double? = null
|
||||
var cycleOffset = 0.0
|
||||
var sample = 0
|
||||
var attackSamples = 2500
|
||||
var releaseSamples = 10000
|
||||
var actualVolume = 0f
|
||||
}
|
||||
|
||||
@@ -68,11 +60,8 @@ enum class RecordingState {
|
||||
@JsExport
|
||||
class VstChipProcessor : AudioWorkletProcessor() {
|
||||
var midiChannel = 0
|
||||
val notes = Array(POLYPHONICS) {
|
||||
PlayingNote(
|
||||
0
|
||||
)
|
||||
}
|
||||
val notes = Array<PlayingNote?>(POLYPHONICS) { null }
|
||||
|
||||
var waveform = Waveform.SINE.ordinal
|
||||
var volume = 0.75f
|
||||
var dutyCycle = 0.5
|
||||
@@ -80,6 +69,12 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
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)
|
||||
@@ -198,27 +193,49 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
val value = bytes[2]
|
||||
|
||||
when (knob) {
|
||||
0x46 -> {
|
||||
7 -> {
|
||||
volume = value / 127f
|
||||
}
|
||||
0x4a -> {
|
||||
dutyCycle = value / 127.0
|
||||
}
|
||||
|
||||
0x4b -> {
|
||||
fmFreq = value / 127.0
|
||||
}
|
||||
|
||||
0x4c -> {
|
||||
fmAmp = value / 127.0
|
||||
}
|
||||
|
||||
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 -> {
|
||||
amAmp = value / 127.0
|
||||
release = value / 127.0
|
||||
}
|
||||
|
||||
123 -> {
|
||||
for (note in notes) {
|
||||
note?.noteRelease = currentTime
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,21 +255,17 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
|
||||
private fun noteOn(note: Int, velocity: Int) {
|
||||
for (i in 0 until POLYPHONICS) {
|
||||
if (notes[i].note == note) {
|
||||
notes[i].retrigger(velocity)
|
||||
if (notes[i]?.note == note) {
|
||||
notes[i]?.retrigger(velocity)
|
||||
return
|
||||
}
|
||||
}
|
||||
for (i in 0 until POLYPHONICS) {
|
||||
if (notes[i].state == NoteState.OFF) {
|
||||
if (notes[i] == null) {
|
||||
notes[i] = PlayingNote(
|
||||
note,
|
||||
velocity
|
||||
)
|
||||
notes[i].state = NoteState.ON
|
||||
|
||||
val n = Note.fromMidi(note)
|
||||
//console.log("Playing note: ${n.sharp} (${n.freq})")
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -260,8 +273,8 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
|
||||
private fun noteOff(note: Int) {
|
||||
for (i in 0 until POLYPHONICS) {
|
||||
if (notes[i].note == note && notes[i].state == NoteState.ON) {
|
||||
notes[i].state = NoteState.RELEASED
|
||||
if (notes[i]?.note == note) {
|
||||
notes[i]?.noteRelease = currentTime
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -279,7 +292,7 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
|
||||
var lowestNote = 200
|
||||
for (note in notes) {
|
||||
if (note.state != NoteState.OFF) {
|
||||
if (note != null) {
|
||||
lowestNote = min(lowestNote, note.note)
|
||||
}
|
||||
}
|
||||
@@ -289,23 +302,25 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
recordingStart = 0
|
||||
}
|
||||
|
||||
for (note in notes) {
|
||||
if (note.state != NoteState.OFF) {
|
||||
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
|
||||
if (note.state == NoteState.ON && note.sample < note.attackSamples) {
|
||||
note.attackSamples--
|
||||
targetVolume *= ( 1f - (note.attackSamples / 2500f))
|
||||
} else if (note.state == NoteState.RELEASED) {
|
||||
note.releaseSamples--
|
||||
targetVolume *= (note.releaseSamples / 10000f)
|
||||
}
|
||||
targetVolume *= ADSR.calculate(
|
||||
attack,
|
||||
decay,
|
||||
sustain,
|
||||
release,
|
||||
note.noteStart,
|
||||
currentTime,
|
||||
note.noteRelease
|
||||
).toFloat()
|
||||
note.actualVolume += (targetVolume - note.actualVolume) * 0.0001f
|
||||
|
||||
if (note.state == NoteState.RELEASED && note.actualVolume <= 0) {
|
||||
note.state = NoteState.OFF
|
||||
if (note.noteRelease != null && note.actualVolume <= 0.01) {
|
||||
notes[index] = null
|
||||
}
|
||||
|
||||
var cycleOffset = note.cycleOffset
|
||||
|
||||
Reference in New Issue
Block a user