Listen to midi
This commit is contained in:
@@ -9,6 +9,7 @@ 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
|
||||
import org.khronos.webgl.get
|
||||
import org.khronos.webgl.set
|
||||
import org.w3c.dom.MessageEvent
|
||||
@@ -79,6 +80,13 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
}
|
||||
is ArrayBuffer -> {
|
||||
}
|
||||
is Uint8Array -> {
|
||||
val data32 = Int32Array(data.length)
|
||||
for (i in 0 until data.length) {
|
||||
data32[i] = (data[i].toInt() and 0xff)
|
||||
}
|
||||
playMidi(data32)
|
||||
}
|
||||
is Int32Array -> {
|
||||
playMidi(data)
|
||||
}
|
||||
@@ -102,8 +110,12 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
}
|
||||
}
|
||||
}
|
||||
0x90 -> {
|
||||
0x80 -> {
|
||||
if (bytes.length >= 2) {
|
||||
val note = bytes[1]
|
||||
|
||||
noteOff(note)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -113,7 +125,6 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
for (i in 0 until POLYPHONICS) {
|
||||
if (notes[i].note == note) {
|
||||
notes[i].retrigger(velocity)
|
||||
//console.log("Note retriggered", notes[i])
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -124,7 +135,9 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
velocity
|
||||
)
|
||||
notes[i].state = NoteState.ON
|
||||
console.log("Playing note", notes[i])
|
||||
|
||||
val n = Note.fromMidi(note)
|
||||
console.log("Playing note: ${n.sharp} (${n.freq})")
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -134,7 +147,6 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
for (i in 0 until POLYPHONICS) {
|
||||
if (notes[i].note == note && notes[i].state == NoteState.ON) {
|
||||
notes[i].state = NoteState.RELEASED
|
||||
//console.log("Released note", notes[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -163,14 +175,17 @@ class VstChipProcessor : AudioWorkletProcessor() {
|
||||
note.releaseSamples--
|
||||
targetVolume *= (note.releaseSamples / 10000f)
|
||||
}
|
||||
note.actualVolume += (targetVolume - note.actualVolume) * 0.01f
|
||||
note.actualVolume += (targetVolume - note.actualVolume) * 0.005f
|
||||
|
||||
if (note.state == NoteState.RELEASED && note.actualVolume <= 0) {
|
||||
note.state = NoteState.OFF
|
||||
}
|
||||
|
||||
left[i] = left[i] + sin(note.cycleOffset * PI2).toFloat() * note.actualVolume
|
||||
right[i] = right[i] + sin(note.cycleOffset * PI2).toFloat() * note.actualVolume
|
||||
left[i] = left[i] + sin(note.cycleOffset * PI2).toFloat() * note.actualVolume * 0.3f
|
||||
right[i] = right[i] + sin(note.cycleOffset * PI2).toFloat() * note.actualVolume * 0.3f
|
||||
|
||||
//left[i] = left[i] + if (note.cycleOffset < 0.5) { 0.3f } else { -0.3f } * note.actualVolume //sin(note.cycleOffset * PI2).toFloat() * note.actualVolume * 0.3f
|
||||
//right[i] = right[i] + if (note.cycleOffset < 0.5) { 0.3f } else { -0.3f } * note.actualVolume //sin(note.cycleOffset * PI2).toFloat() * note.actualVolume * 0.3f
|
||||
|
||||
note.cycleOffset += sampleDelta
|
||||
if (note.cycleOffset > 1f) {
|
||||
|
||||
@@ -23,7 +23,6 @@ kotlin {
|
||||
browser {
|
||||
commonWebpackConfig {
|
||||
outputFileName = "vst-chip-worklet-ui.js"
|
||||
//cssSupport.enabled = true
|
||||
sourceMaps = true
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package nl.astraeus.vst
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.round
|
||||
|
||||
/**
|
||||
* User: rnentjes
|
||||
@@ -15,17 +16,17 @@ enum class Note(
|
||||
val flat: String
|
||||
) {
|
||||
NONE("---", "---"),
|
||||
NO02("",""),
|
||||
NO03("",""),
|
||||
NO04("",""),
|
||||
NO05("",""),
|
||||
NO06("",""),
|
||||
NO07("",""),
|
||||
NO08("",""),
|
||||
NO09("",""),
|
||||
NO10("",""),
|
||||
NO11("",""),
|
||||
NO12("",""),
|
||||
No02("C--","C--"),
|
||||
NO03("C#-","Db-"),
|
||||
NO04("D--","D--"),
|
||||
NO05("D#-","Eb-"),
|
||||
NO06("E--","E--"),
|
||||
NO07("F--","F--"),
|
||||
NO08("F#-","Gb-"),
|
||||
NO09("G--","G--"),
|
||||
NO10("G#-","Ab-"),
|
||||
NO11("A#-","Bb-"),
|
||||
NO12("B--","B--"),
|
||||
C0("C-0","C-0"),
|
||||
C0s("C#0","Db0"),
|
||||
D0("D-0","D-0"),
|
||||
@@ -152,7 +153,7 @@ enum class Note(
|
||||
;
|
||||
|
||||
// 69 = A4.ordinal
|
||||
val freq: Double = 440.0 * 2.0.pow((ordinal - 69)/12.0)
|
||||
val freq: Double = round(440.0 * 2.0.pow((ordinal - 69)/12.0) * 100.0) / 100.0
|
||||
val cycleLength: Double = 1.0 / freq
|
||||
var sampleDelta: Double = 0.0
|
||||
|
||||
@@ -162,14 +163,12 @@ enum class Note(
|
||||
result = min(result, G9.ordinal)
|
||||
result = max(result, C0.ordinal)
|
||||
|
||||
entries.firstOrNull { it.ordinal == result } ?: this
|
||||
fromMidi(result)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
|
||||
companion object {
|
||||
var sampleRate: Int = 44100
|
||||
|
||||
fun fromMidi(midi: Int): Note {
|
||||
// todo: add check
|
||||
return entries[midi]
|
||||
@@ -177,10 +176,16 @@ enum class Note(
|
||||
|
||||
fun updateSampleRate(rate: Int) {
|
||||
println("Setting sample rate to $rate")
|
||||
sampleRate = rate
|
||||
for (note in Note.entries) {
|
||||
note.sampleDelta = (1.0 / sampleRate.toDouble()) / note.cycleLength
|
||||
note.sampleDelta = (1.0 / rate.toDouble()) / note.cycleLength
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// freq = 10Hz
|
||||
// cycleLength = 0.1
|
||||
// sampleRate = 48000
|
||||
// sampleDelta = 4800
|
||||
|
||||
// (1.0 / freq) * sampleRate
|
||||
|
||||
@@ -3,10 +3,13 @@ package nl.astraeus.vst.chip
|
||||
import kotlinx.browser.document
|
||||
import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.vst.chip.channel.Broadcaster
|
||||
import nl.astraeus.vst.chip.midi.Midi
|
||||
import nl.astraeus.vst.chip.view.MainView
|
||||
|
||||
fun main() {
|
||||
Komponent.create(document.body!!, MainView)
|
||||
|
||||
Broadcaster.start()
|
||||
|
||||
Midi.start()
|
||||
}
|
||||
|
||||
89
src/jsMain/kotlin/nl/astraeus/vst/chip/midi/Midi.kt
Normal file
89
src/jsMain/kotlin/nl/astraeus/vst/chip/midi/Midi.kt
Normal file
@@ -0,0 +1,89 @@
|
||||
package nl.astraeus.vst.chip.midi
|
||||
|
||||
import kotlinx.browser.window
|
||||
import nl.astraeus.vst.chip.audio.VstChipWorklet
|
||||
import nl.astraeus.vst.chip.view.MainView
|
||||
|
||||
external class MIDIInput {
|
||||
val connection: String
|
||||
val id: String
|
||||
val manufacturer: String
|
||||
val name: String
|
||||
val state: String
|
||||
val type: String
|
||||
val version: String
|
||||
var onmidimessage: (dynamic) -> Unit
|
||||
var onstatechange: (dynamic) -> Unit
|
||||
|
||||
fun open()
|
||||
fun close()
|
||||
}
|
||||
|
||||
external class MIDIOutput {
|
||||
val connection: String
|
||||
val id: String
|
||||
val manufacturer: String
|
||||
val name: String
|
||||
val state: String
|
||||
val type: String
|
||||
val version: String
|
||||
|
||||
fun send(message: dynamic)
|
||||
}
|
||||
|
||||
object Midi {
|
||||
var inputs = mutableListOf<MIDIInput>()
|
||||
var outputs = mutableListOf<MIDIInput>()
|
||||
var currentInput: MIDIInput? = null
|
||||
|
||||
fun start() {
|
||||
val navigator = window.navigator.asDynamic()
|
||||
|
||||
navigator.requestMIDIAccess().then(
|
||||
{ midiAccess ->
|
||||
val inp = midiAccess.inputs
|
||||
val outp = midiAccess.outputs
|
||||
|
||||
console.log("Midi inputs:", inputs)
|
||||
console.log("Midi outputs:", outputs)
|
||||
|
||||
inp.forEach() { input ->
|
||||
console.log("Midi input:", input)
|
||||
inputs.add(input)
|
||||
console.log("Name: ${(input as? MIDIInput)?.name}")
|
||||
}
|
||||
|
||||
outp.forEach() { output ->
|
||||
console.log("Midi output:", output)
|
||||
outputs.add(output)
|
||||
}
|
||||
|
||||
MainView.requestUpdate()
|
||||
},
|
||||
{ e ->
|
||||
println("Failed to get MIDI access - $e")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun setInput(input: MIDIInput) {
|
||||
console.log("Setting input", input)
|
||||
currentInput?.close()
|
||||
|
||||
currentInput = input
|
||||
|
||||
currentInput?.onstatechange = { message ->
|
||||
console.log("State change:", message)
|
||||
}
|
||||
|
||||
currentInput?.onmidimessage = { message ->
|
||||
console.log("Midi message:", message)
|
||||
VstChipWorklet.postMessage(
|
||||
message.data
|
||||
)
|
||||
}
|
||||
|
||||
currentInput?.open()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,14 +7,19 @@ import daw.style.CssId
|
||||
import daw.style.CssName
|
||||
import daw.style.hover
|
||||
import kotlinx.html.FlowContent
|
||||
import kotlinx.html.P
|
||||
import kotlinx.html.a
|
||||
import kotlinx.html.br
|
||||
import kotlinx.html.classes
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.h1
|
||||
import kotlinx.html.hr
|
||||
import kotlinx.html.js.onChangeFunction
|
||||
import kotlinx.html.js.onClickFunction
|
||||
import kotlinx.html.js.onMouseDownFunction
|
||||
import kotlinx.html.js.onMouseUpFunction
|
||||
import kotlinx.html.option
|
||||
import kotlinx.html.select
|
||||
import kotlinx.html.span
|
||||
import nl.astraeus.css.properties.BoxSizing
|
||||
import nl.astraeus.css.properties.FontWeight
|
||||
@@ -27,7 +32,9 @@ import nl.astraeus.komp.Komponent
|
||||
import nl.astraeus.vst.Note
|
||||
import nl.astraeus.vst.chip.audio.VstChipWorklet
|
||||
import nl.astraeus.vst.chip.channel.Broadcaster
|
||||
import nl.astraeus.vst.chip.midi.Midi
|
||||
import org.khronos.webgl.Int32Array
|
||||
import org.w3c.dom.HTMLSelectElement
|
||||
|
||||
object MainView : Komponent() {
|
||||
private var messages: MutableList<String> = ArrayList()
|
||||
@@ -68,31 +75,27 @@ object MainView : Komponent() {
|
||||
}
|
||||
}
|
||||
div {
|
||||
a {
|
||||
href = "#"
|
||||
+"Send broadcast test message"
|
||||
onClickFunction = {
|
||||
Broadcaster.send("Test message")
|
||||
}
|
||||
}
|
||||
}
|
||||
div {
|
||||
a {
|
||||
href = "#"
|
||||
+"Note on"
|
||||
onClickFunction = {
|
||||
VstChipWorklet.postMessage("test_on")
|
||||
}
|
||||
}
|
||||
a {
|
||||
href = "#"
|
||||
+"Note off"
|
||||
onClickFunction = {
|
||||
VstChipWorklet.postMessage("test_off")
|
||||
+ "Midi input: "
|
||||
select {
|
||||
for (mi in Midi.inputs) {
|
||||
option {
|
||||
+mi.name
|
||||
value = mi.id
|
||||
}
|
||||
}
|
||||
|
||||
onChangeFunction = { event ->
|
||||
val target = event.target as HTMLSelectElement
|
||||
val selected = Midi.inputs.find { it.id == target.value }
|
||||
if (selected != null) {
|
||||
Midi.setInput(selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
br {}
|
||||
|
||||
hr {}
|
||||
|
||||
repeat(9) {
|
||||
|
||||
Reference in New Issue
Block a user