Skip to content

Commit

Permalink
partial MIDI-CI implementation upgrades to MIDI-CI 1.2.
Browse files Browse the repository at this point in the history
  • Loading branch information
atsushieno committed Nov 29, 2023
1 parent 47a4cd3 commit 9ded1ee
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -728,7 +728,7 @@ object UmpFactory {
fun deviceIdentityNotification(device: DeviceDetails) =
Ump(0xF002_0000L.toInt(),
device.manufacturer,
((device.family.toUnsigned() shl 16) + device.familyModelNumber.toUnsigned()),
((device.family.toUInt() shl 16) + device.familyModelNumber).toInt(),
device.softwareRevisionLevel)

fun endpointNameNotification(name: String) = endpointNameNotification(name.toByteArray())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,8 @@ val Ump.endpointInfoSupportsTxJR

val Ump.deviceIdentity
get() = DeviceDetails(manufacturer = int2 and 0xFFFFFF,
family = ((int3 shr 16) and 0xFFFF).toShort(),
familyModelNumber = (int3 and 0xFFFF).toShort(),
family = ((int3 shr 16) and 0xFFFF).toUShort(),
familyModelNumber = (int3 and 0xFFFF).toUShort(),
softwareRevisionLevel = int4)

val Ump.streamConfigProtocol
Expand Down
90 changes: 67 additions & 23 deletions ktmidi/src/commonMain/kotlin/dev/atsushieno/ktmidi/ci/CIFactory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ object CIFactory {
const val SUB_ID_2_SET_PROFILE_OFF: Byte = 0x23
const val SUB_ID_2_PROFILE_ENABLED_REPORT: Byte = 0x24
const val SUB_ID_2_PROFILE_DISABLED_REPORT: Byte = 0x25
const val SUB_ID_2_PROFILE_ADDED_REPORT: Byte = 0x26
const val SUB_ID_2_PROFILE_REMOVED_REPORT: Byte = 0x27
const val SUB_ID_2_PROFILE_DETAILS_INQUIRY: Byte = 0x28
const val SUB_ID_2_PROFILE_DETAILS_INQUIRY_REPLY: Byte = 0x29
const val SUB_ID_2_PROFILE_SPECIFIC_DATA: Byte = 0x2F
const val SUB_ID_2_PROPERTY_CAPABILITIES_INQUIRY: Byte = 0x30
const val SUB_ID_2_PROPERTY_CAPABILITIES_REPLY: Byte = 0x31
Expand All @@ -50,8 +54,8 @@ object CIFactory {
const val PROPERTY_EXCHANGE_SUPPORTED = 8

// Assumes the input value is already 7-bit encoded if required.
fun midiCiDirectUint16At(dst: MutableList<Byte>, offset: Int, v: Short) {
dst[offset] = (v and 0xFF).toByte()
fun midiCiDirectUint16At(dst: MutableList<Byte>, offset: Int, v: UShort) {
dst[offset] = (v and 0xFFu).toByte()
dst[offset + 1] = (v.toInt() shr 8 and 0xFF).toByte()
}

Expand Down Expand Up @@ -83,10 +87,10 @@ object CIFactory {

fun midiCIMessageCommon(
dst: MutableList<Byte>,
destination: Byte, sysexSubId2: Byte, versionAndFormat: Byte, sourceMUID: Int, destinationMUID: Int
deviceId: Byte, sysexSubId2: Byte, versionAndFormat: Byte, sourceMUID: Int, destinationMUID: Int
) {
dst[0] = 0x7E
dst[1] = destination
dst[1] = deviceId
dst[2] = 0xD
dst[3] = sysexSubId2
dst[4] = versionAndFormat
Expand All @@ -100,8 +104,9 @@ object CIFactory {
fun midiCIDiscoveryCommon(
dst: MutableList<Byte>, sysexSubId2: Byte,
versionAndFormat: Byte, sourceMUID: Int, destinationMUID: Int,
deviceManufacturer3Bytes: Int, deviceFamily: Short, deviceFamilyModelNumber: Short,
softwareRevisionLevel: Int, ciCategorySupported: Byte, receivableMaxSysExSize: Int
deviceManufacturer3Bytes: Int, deviceFamily: UShort, deviceFamilyModelNumber: UShort,
softwareRevisionLevel: Int, ciCategorySupported: Byte, receivableMaxSysExSize: Int,
initiatorOutputPathId: Byte
) {
midiCIMessageCommon(dst, 0x7F, sysexSubId2, versionAndFormat, sourceMUID, destinationMUID)
midiCiDirectUint32At(
Expand All @@ -114,49 +119,74 @@ object CIFactory {
midiCiDirectUint32At(dst, 20, softwareRevisionLevel.toInt())
dst[24] = ciCategorySupported
midiCiDirectUint32At(dst, 25, receivableMaxSysExSize.toInt())
dst[29] = initiatorOutputPathId
}

fun midiCIDiscovery(
dst: MutableList<Byte>,
versionAndFormat: Byte, sourceMUID: Int,
deviceManufacturer: Int, deviceFamily: Short, deviceFamilyModelNumber: Short,
softwareRevisionLevel: Int, ciCategorySupported: Byte, receivableMaxSysExSize: Int
) {
deviceManufacturer: Int, deviceFamily: UShort, deviceFamilyModelNumber: UShort,
softwareRevisionLevel: Int, ciCategorySupported: Byte, receivableMaxSysExSize: Int,
initiatorOutputPathId: Byte
) : List<Byte> {
midiCIDiscoveryCommon(
dst, SUB_ID_2_DISCOVERY_INQUIRY,
versionAndFormat, sourceMUID, 0x7F7F7F7F,
deviceManufacturer, deviceFamily, deviceFamilyModelNumber,
softwareRevisionLevel, ciCategorySupported, receivableMaxSysExSize
softwareRevisionLevel, ciCategorySupported, receivableMaxSysExSize,
initiatorOutputPathId
)
return dst.take(30)
}

fun midiCIDiscoveryReply(
dst: MutableList<Byte>,
versionAndFormat: Byte, sourceMUID: Int, destinationMUID: Int,
deviceManufacturer: Int, deviceFamily: Short, deviceFamilyModelNumber: Short,
softwareRevisionLevel: Int, ciCategorySupported: Byte, receivableMaxSysExSize: Int
) {
deviceManufacturer: Int, deviceFamily: UShort, deviceFamilyModelNumber: UShort,
softwareRevisionLevel: Int, ciCategorySupported: Byte, receivableMaxSysExSize: Int,
initiatorOutputPathId: Byte, functionBlock: Byte
) : List<Byte> {
midiCIDiscoveryCommon(
dst, SUB_ID_2_DISCOVERY_REPLY,
versionAndFormat, sourceMUID, destinationMUID,
deviceManufacturer, deviceFamily, deviceFamilyModelNumber,
softwareRevisionLevel, ciCategorySupported, receivableMaxSysExSize
softwareRevisionLevel, ciCategorySupported, receivableMaxSysExSize,
initiatorOutputPathId
)
dst[30] = functionBlock
return dst.take(31)
}

fun midiCIEndpointMessage(dst: MutableList<Byte>, versionAndFormat: Byte, sourceMUID: Int, destinationMUID: Int, status: Byte
) : List<Byte> {
midiCIMessageCommon(dst, 0x7F, 0x7E, versionAndFormat, sourceMUID, destinationMUID)
dst[13] = status
return dst.take(14)
}

fun midiCIEndpointMessageReply(dst: MutableList<Byte>, versionAndFormat: Byte, sourceMUID: Int, destinationMUID: Int, status: Byte, informationData: List<Byte>
) : List<Byte> {
midiCIMessageCommon(dst, 0x7F, 0x7E, versionAndFormat, sourceMUID, destinationMUID)
dst[13] = status
midiCiDirectUint16At(dst, 14, informationData.size.toUShort())
return dst.take(14)
}

fun midiCIDiscoveryInvalidateMuid(
dst: MutableList<Byte>,
versionAndFormat: Byte, sourceMUID: Int, targetMUID: Int
) {
) : List<Byte> {
midiCIMessageCommon(dst, 0x7F, 0x7E, versionAndFormat, sourceMUID, 0x7F7F7F7F)
midiCiDirectUint32At(dst, 13, targetMUID)
return dst.take(17)
}

fun midiCIDiscoveryNak(
dst: MutableList<Byte>, deviceId: Byte,
versionAndFormat: Byte, sourceMUID: Int, destinationMUID: Int
) {
) : List<Byte> {
midiCIMessageCommon(dst, deviceId, 0x7F, versionAndFormat, sourceMUID, destinationMUID)
return dst.take(13)
}


Expand Down Expand Up @@ -265,7 +295,7 @@ object CIFactory {
sourceMUID: Int, destinationMUID: Int,
enabledProfiles: List<MidiCIProfileId>,
disabledProfiles: List<MidiCIProfileId>
) {
) : List<Byte> {
midiCIMessageCommon(
dst, source,
SUB_ID_2_PROFILE_INQUIRY_REPLY,
Expand All @@ -282,6 +312,8 @@ object CIFactory {
disabledProfiles.forEachIndexed { i, p ->
midiCIProfile(dst, pos + i * 5, p)
}
pos += disabledProfiles.size * 5
return dst.take(pos)
}

fun midiCIProfileSet(
Expand All @@ -296,6 +328,18 @@ object CIFactory {
midiCIProfile(dst, 13, profile)
}

fun midiCIProfileAddedRemoved(
dst: MutableList<Byte>, destination: Byte, isRemoved: Boolean,
sourceMUID: Int, profile: MidiCIProfileId
) {
midiCIMessageCommon(
dst, destination,
if (isRemoved) SUB_ID_2_PROFILE_REMOVED_REPORT else SUB_ID_2_PROFILE_ADDED_REPORT,
1, sourceMUID, 0x7F7F7F7F
)
midiCIProfile(dst, 13, profile)
}

fun midiCIProfileReport(
dst: MutableList<Byte>, source: Byte, isEnabledReport: Boolean,
sourceMUID: Int, profile: MidiCIProfileId
Expand Down Expand Up @@ -340,17 +384,17 @@ object CIFactory {
fun midiCIPropertyCommon(
dst: MutableList<Byte>, destination: Byte, messageTypeSubId2: Byte,
sourceMUID: Int, destinationMUID: Int,
requestId: Byte, headerSize: Short, header: List<Byte>,
numChunks: Short, chunkIndex: Short, dataSize: Short, data: List<Byte>
requestId: Byte, headerSize: UShort, header: List<Byte>,
numChunks: UShort, chunkIndex: UShort, dataSize: UShort, data: List<Byte>
) {
midiCIMessageCommon(dst, destination, messageTypeSubId2, 1, sourceMUID, destinationMUID)
dst[13] = requestId
midiCiDirectUint16At(dst, 14, headerSize)
memcpy(dst, 16, header, headerSize.toInt())
midiCiDirectUint16At(dst, 16 + headerSize, numChunks)
midiCiDirectUint16At(dst, 18 + headerSize, chunkIndex)
midiCiDirectUint16At(dst, 20 + headerSize, dataSize)
memcpy(dst, 22 + headerSize, data, dataSize.toInt())
midiCiDirectUint16At(dst, 16 + headerSize.toInt(), numChunks)
midiCiDirectUint16At(dst, 18 + headerSize.toInt(), chunkIndex)
midiCiDirectUint16At(dst, 20 + headerSize.toInt(), dataSize)
memcpy(dst, 22 + headerSize.toInt(), data, dataSize.toInt())
}

private fun memcpy(dst: MutableList<Byte>, dstOffset: Int, src: List<Byte>, size: Int) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ object CIRetrieval {
*/
fun midiCIGetDeviceDetails(sysex: List<Byte>) = DeviceDetails(
sysex[13] + (sysex[14] shl 8) + (sysex[15] shl 16),
(sysex[16] + (sysex[17] shl 8)).toShort(),
(sysex[18] + (sysex[19] shl 8)).toShort(),
(sysex[16] + (sysex[17] shl 8)).toUShort(),
(sysex[18] + (sysex[19] shl 8)).toUShort(),
sysex[20] + (sysex[21] shl 8) + (sysex[22] shl 16) + (sysex[23] shl 24))

/** retrieves source MUID from a MIDI-CI sysex7 chunk.
Expand Down Expand Up @@ -61,11 +61,11 @@ object CIRetrieval {
* The argument sysex bytestream is NOT specific to MIDI 1.0 bytestream and thus does NOT contain F0 and F7 (i.e. starts with 0xFE, xx, 0x0D...)
*/
fun midiCIGetProfileSet(sysex: List<Byte>): List<Pair<MidiCIProfileId, Boolean>> = mutableListOf<Pair<MidiCIProfileId, Boolean>>().apply {
val numEnabled = sysex[7] + (sysex[8] shl 7)
val numEnabled = sysex[13] + (sysex[14] shl 7)
(0 until numEnabled).forEach { i ->
this.add(Pair(midiCIGetProfileIdEntry(sysex, 9 + i * 5), true))
}
val pos = 9 + numEnabled * 5
val pos = 15 + numEnabled * 5
val numDisabled = sysex[pos] + (sysex[pos + 1] shl 7)
(0 until numDisabled).forEach { i ->
this.add(Pair(midiCIGetProfileIdEntry(sysex, pos + 2 + i * 5), false))
Expand All @@ -77,6 +77,8 @@ object CIRetrieval {
*/
fun midiCIGetProfileId(sysex: List<Byte>) = midiCIGetProfileIdEntry(sysex, 13)

fun midiCIGetProfileEnabledChannels(sysex: List<Byte>) = sysex[18] + (sysex[19] shl 7)

private fun midiCIGetProfileIdEntry(sysex: List<Byte>, offset: Int) =
MidiCIProfileId(sysex[offset], sysex[offset + 1], sysex[offset + 2], sysex[offset + 3], sysex[offset + 4])

Expand Down
Loading

0 comments on commit 9ded1ee

Please sign in to comment.