All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.org.androidaudioplugin.composeaudiocontrols.midi.DiatonicLiveMidiKeyboard.kt Maven / Gradle / Ivy

package org.androidaudioplugin.composeaudiocontrols.midi

import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import dev.atsushieno.ktmidi.MidiChannelStatus
import dev.atsushieno.ktmidi.Ump
import dev.atsushieno.ktmidi.UmpFactory
import dev.atsushieno.ktmidi.toPlatformNativeBytes
import org.androidaudioplugin.composeaudiocontrols.DiatonicKeyboardNoteExpressionOrigin
import org.androidaudioplugin.composeaudiocontrols.DiatonicKeyboardWithControllers
import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.math.roundToLong

@Composable
fun MidiDeviceAccessScope.DiatonicLiveMidiKeyboard() {
    Column {
        val noteOnStates = remember { List(128) { 0L }.toMutableStateList() }
        var expressionX by remember { mutableStateOf(0f) }
        var expressionY by remember { mutableStateOf(0f) }
        var expressionP by remember { mutableStateOf(0f) }

        DiatonicKeyboardWithControllers(
            noteOnStates.toList(),
            showExpressionSensitivitySlider = false,
            onNoteOn = { note, _ ->
                if (isTransportUmp) {
                    val i64 = UmpFactory.midi2NoteOn(0, 0, note, 0, 0xF800, 0)
                    send(Ump(i64).toPlatformNativeBytes(), 0, 8, 0)
                } else {
                    val bytes = byteArrayOf(MidiChannelStatus.NOTE_ON.toByte(), note.toByte(), 120)
                    send(bytes, 0, bytes.size, 0)
                }
                noteOnStates[note] = 1
            },
            onNoteOff = { note, _ ->
                if (isTransportUmp) {
                    val i64 = UmpFactory.midi2NoteOff(0, 0, note, 0, 0xF800, 0)
                    send(Ump(i64).toPlatformNativeBytes(), 0, 8, 0)
                } else {
                    val bytes = byteArrayOf(MidiChannelStatus.NOTE_OFF.toByte(), note.toByte(), 120)
                    send(bytes, 0, bytes.size, 0)
                }
                noteOnStates[note] = 0
            },
            onExpression = { dir, note, data ->
                if (isTransportUmp) {
                    // MIDI 2.0 mode:
                    // Pitch Bend for horizontal moves
                    if (dir == DiatonicKeyboardNoteExpressionOrigin.HorizontalDragging) {
                        val v32 = min(0xFFFF_FFFFL, (((data + 1.0) / 2.0) * 0x1_0000_0000L).roundToLong())
                        val i64 = UmpFactory.midi2PitchBendDirect(0, 0, v32)
                        send(Ump(i64).toPlatformNativeBytes(), 0, 8, 0)
                    }
                    // Per-Note Pitch Bend for vertical moves
                    if (dir == DiatonicKeyboardNoteExpressionOrigin.VerticalDragging) {
                        val v32 = min(0xFFFF_FFFFL, (((data + 1.0) / 2.0) * 0x1_0000_0000L).roundToLong())
                        val i64 = UmpFactory.midi2PerNotePitchBendDirect(0, 0, note, v32)
                        send(Ump(i64).toPlatformNativeBytes(), 0, 8, 0)
                    }
                    // MIDI 2.0 PAf for pressure
                    if (dir == DiatonicKeyboardNoteExpressionOrigin.Pressure) {
                        val v32 = (((data / 2.0) + 0.5) * 0xFFFFFFFF).roundToLong()
                        val i64 = UmpFactory.midi2PAf(0, 0, note, v32)
                        send(Ump(i64).toPlatformNativeBytes(), 0, 8, 0)
                    }
                } else {
                    // MIDI 1.0 mode:
                    // Pitch Bend for horizontal moves
                    if (dir == DiatonicKeyboardNoteExpressionOrigin.HorizontalDragging) {
                        val dataIn14Bit = min(16383, ((data * 8192f).roundToInt() + 8192)).toShort()
                        val bytes =
                            byteArrayOf(MidiChannelStatus.PITCH_BEND.toByte(), (dataIn14Bit % 0x80).toByte(), (dataIn14Bit / 0x80).toByte())
                        send(bytes, 0, bytes.size, 0)
                    }
                    // MIDI 1.0 PAf for pressure
                    if (dir == DiatonicKeyboardNoteExpressionOrigin.Pressure) {
                        val dataIn7Bit = min(127, ((data * 64f).roundToInt() + 64)).toByte()
                        val bytes =
                            byteArrayOf(MidiChannelStatus.PAF.toByte(), note.toByte(), dataIn7Bit)
                        send(bytes, 0, bytes.size, 0)
                    }
                }
                when (dir) {
                    DiatonicKeyboardNoteExpressionOrigin.HorizontalDragging -> expressionX = data
                    DiatonicKeyboardNoteExpressionOrigin.VerticalDragging -> expressionY = data
                    DiatonicKeyboardNoteExpressionOrigin.Pressure -> expressionP = data
                    else -> {}
                }
            }
        )
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy