Giter Site home page Giter Site logo

atsushieno / ktmidi Goto Github PK

View Code? Open in Web Editor NEW
68.0 9.0 6.0 13.96 MB

Kotlin multiplatform library for MIDI access abstraction and data processing for MIDI 1.0, MIDI 2.0, SMF, SMF2 (MIDI Clip File), and MIDI-CI.

License: MIT License

Kotlin 99.86% Shell 0.03% Swift 0.07% HTML 0.04%
midi kotlin music kotlin-multiplatform midi2 midi-ci

ktmidi's Introduction

ktmidi: Kotlin Multiplatform library for MIDI 1.0 and MIDI 2.0

maven repo

ktmidi is a Kotlin Multiplatform library for MIDI Access API and MIDI data processing that covers MIDI 1.0 and MIDI 2.0.

ktmidi-ci-tool screenshot

Features

It provides various MIDI features, including implementations for MIDI 1.0, Standard MIDI File Format, MIDI 2.0 UMP, MIDI-CI and related specifications.

In ktmidi module:

  • MIDI 1.0 bytestream messages and 2.0 UMPs everywhere.
  • MidiAccess : MIDI access abstraction API like what Web MIDI API 1.0 provides.
    • It also supports MIDI 2.0 UMP ports since v0.8.0, if the underlying API supports them. Currently Android 15 (preview API) is supported.
    • There are actual implementations for some platform specific MIDI API within this library, and you can implement your own backend if you need.
    • Unlike javax.sound.midi API, this API also covers creating virtual ports wherever possible.
      • ktmidi-jvm-desktop module contains ALSA backend (AlsaMidiAccess), as well as RtMidi backend (RtMidiAccess) via atsushieno/rtmidi-javacpp for Linux and MacOS
        • Windows needs JavaCPP build improvements and left unsupported (it does not matter, WinMM does not support virtual ports either way)
      • ktmidi-native-ext module contains RtMidi native backend (RtMidiNativeAccess) for Kotlin-Native. player-sample-native sample app uses it.
    • For Kotlin/Native and Apple OSes (macOS, iOS, etc.) there are UmpCoreMidiAccess (supports access to MIDI 2.0 and 1.0 devices, only on newer OSes) and TraditionalCoreMidiAccess (supports access to MIDI 1.0 devices).
    • For Kotlin/JS, JzzMidiAccess which wraps Jazz-Soft JZZ is included in ktmidi module. It should cover both node.js and web browsers.
    • For Kotlin/Wasm on browsers, WebMidiAccess in ktmidi module makes use of Web MIDI API directly. (Node/Deno via wasmWasi is not covered yet.)
  • MidiMusic and Midi2Music : represents Standard MIDI File format structure, with reader and writer. (MIDI 2.0 support only partially based on standard; Midi2Track follows MIDI Clip File specification but there is no multi-track comparable specification to SMF for MIDI 2.0 yet.)
    • No strongly-typed message types (something like NoteOnMessage, NoteOffMessage, and so on). There is no point of defining strongly-typed messages for each mere MIDI status byte - you wouldn't need message type abstraction.
      • No worries, there are constants of Int or Byte in MidiChannelStatus, MidiCC, MidiRpn, MidiMetaType etc. so that you don't have to remember the actual constant numbers.
    • MidiMusic.read() reads and MidiMusic.write() writes to SMF (standard MIDI format) files with MIDI messages, with Midi1TrackMerger, Midi2TrackMerger, Midi1TrackSplitter and Midi2TrackSplitter that help you implement sequential event processing for your own MIDI players, or per-track editors if needed.
    • UmpFactory and UmpRetriever provides various accessors to MIDI 2.0 Ump data class.
    • UmpTranslator covers the standard conversion between MIDI 1.0 and MIDI 2.0 protocols, with a set of extensive options.
    • Midi1Machine and Midi2Machine work as a potential MIDI device internal state machine, which is also to cover MIDI-CI Process Inquiry "MIDI Message Report" feature.
  • MidiPlayer and Midi2Player: provides MIDI player functionality: play/pause/stop and fast-forwarding.
    • Midi messages are sent to its "message listeners". If you don't pass a Midi Access instance or a Midi Output instance, it will do nothing but event dispatching.
    • It is based on customizible scheduler MidiPlayerTimer. You can pass a stub implementation that does not wait, for testing.
    • Not realtime strict (as on GC-ed language / VM), but would suffice for general usage.

In ktmidi-ci module (the overall API is unstable and subject to change):

  • MidiCIDevice class implements MIDI-CI agent models that conforms to MIDI-CI, Common Rules for MIDI-CI Profile Inquiry, and Common Rules for MIDI-CI Property Exchange specifications.
  • Message implements the sturecures for each MIDI-CI message type.
  • primitive MIDI-CI SysEx byte stream processor in CIFactory and CIRetrieval classes.

See MIDI-CI design doc for more details.

There are handful of sample project modules:

  • player-sample is an example console MIDI player for Kotlin/JVM desktop.
  • player-sample-native is almost the same, but for Kotlin/Native desktop.
  • input-sample is an example console MIDI input receiver that dumps the MIDI messages it received, for Kotlin/JVM desktop.
  • ktmidi-ci-tool is a comprehensive MIDI-CI functionality demo that connects to another MIDI-CI device (through a pair of MIDI connections so far).

Using ktmidi

Here is an example code excerpt to set up platform MIDI device access, load an SMF from some file, and play it:

// for some complicated reason we don't have simple "default" MidiAccess API instance
val access = if(File("/dev/snd/seq").exists()) AlsaMidiAccess() else JvmMidiAccess()
val bytes = Files.readAllBytes(Path.of(fileName)).toList()
val music = MidiMusic()
music.read(bytes)
val player = MidiPlayer(music, access)
player.play()

To use ktmidi, add the following lines in the dependencies section in build.gradle(.kts):

dependencies {
    implementation "dev.atsushieno:ktmidi:+" // replace + with the actual version
}

The actual artifact might be platform dependent like dev.atsushieno:ktmidi-android:+ or dev.atsushieno:ktmidi-js:+, depending on the project targets.

If you want to bring better user experience on desktop (which @atsushieno recommends as javax.sound.midi on Linux is quite featureless), add ktmidi-jvm-desktop too,

plugins { // skip this if you are rather building a library (not an app)
    id("org.bytedeco.gradle-javacpp-platform") version "1.5.10"
}

dependencies {
    implementation "dev.atsushieno:ktmidi-jvm-desktop:+" // replace + with the actual version
}

... and use AlsaMidiAccess on Linux, or RtMidiAccess elsewhere. I use if (File.exists("/dev/snd/seq")) AlsaMidiAccess() else RtMidiAccess() (or JvmMidiAccess instead of RtMidiAccess) to create best MidiAccess instance.

NOTE: if you are building a desktop MIDI library using ktmidi-jvm-desktop, your application needs to add javacpp-platform Gradle plugin:

plugins {
    id("org.bytedeco.gradle-javacpp-platform") version "1.5.10"
}

This Gradle plugin replaces the reference to javacpp library with the platform-specific ones. So if you do this in your library build, it will result in that your library is useful only on the same platform as your building environment(!)

ktmidi is released at sonatype and hence available at Maven Central.

Platform Access API

For platform MIDI access API, we cover the following APIs:

  • AndroidMidiAccess: Android MIDI API (in Kotlin)
  • AlsaMidiAccess: ALSA sequencer
  • RtMidiAccess: RtMidi (which covers Windows, Mac, Linux, and iOS, but iOS is in general excluded in JVM solution. Note that rtmidi-javacpp contains prebuilt binaries only for those x86_64 desktop targets. For other platforms, you are supposed to set up rtmidi 5.0.x locally..
  • RtMidiNativeAccess: RtMidi access for Kotln/Native implementation. Note tha there is a static linking issue to be resolved.
  • UmpCoreMidiAccess: Apple CoreMIDI API on Kotlin/Native. MIDI 2.0 or 1.0, but only on newer OSes.
  • TraditionalCoreMidiAccess: Apple CoreMIDI API on Kotlin/Native. MIDI 1.0 only.
  • JvmMidiAccess: javax.sound.midi API (with limited feature set).
  • WebMidiAccess : Web MIDI API for Kotlin/Wasm target (browser only).
  • JzzMidiAccess : Web MIDI API for Kotlin/JS target (browser and nodejs, experimental, untested).

For dependency resolution reason, ALSA implementation and RtMidi implementation are split from ktmidi-jvm and formed as ktmidi-jvm-desktop.

ktmidi builds for Kotlin/JVM, Kotlin/JS and Kotlin/Native (though I only develop with Kotlin/JVM and Kotlin/JS so far).

The entire API is still subject to change, and it had been actually radically changing when development was most active.

MIDI 2.0 support

Potential field of usages

ktmidi supports MIDI 2.0 UMPs, and MIDI-CI if you count it as part of MIDI 2.0.

UMPs It can be sent and received in our own manner (i.e. it presumes MIDI 2.0 protocol is already established elsewhere). There was a now-deprecated way to promote MIDI protocols using Protocol Negotiation and some apps like atsushieno/kmmk made use of it (Protocol Negotiation is gone in MIDI-CI version 1.2 specification). Now that it is gone, you are supposed to establish UMP-enabled transports by your own somehow.

ktmidi assumes there are various other use-cases without those message exchanges e.g. use of UMPs in MIDI 2.0-only messaging in apps or audio plugins (for example, Audio Plugins For Android along with resident-midi-keyboard).

Since you can derive from MidiAccess abstract API, you can create your own MIDI access implementation and don't have to wait for platform native API to support MIDI 2.0.

It would be useful for general MIDI 2.0 software tools such as MIDI 2.0 UMP player.

Implemented features

Here is a list of MIDI 2.0 extensibility in this API:

  • MidiInput and MidiOutput now has midiProtocol property which can be get and/or set. When MidiCIProtocolType.MIDI2 is specified, then the I/O object is supposed to process UMPs (Universal MIDI Packets).
  • Midi2Music is a feature parity with MidiMusic, but all the messages are stored as UMPs. Since ktmidi 0.5.0 we support the Delta Clockstamps as well as DCTPQ, as well as Flex Data messages that correspond to SMF meta events (though not fully, as it is technically impossible). See docs/MIDI2_FORMATS.md for details.
  • Midi2Player is a feature parity with MidiPlayer.
  • UmpFactory class contains a bunch of utility functions that are used to construct UMP integer values.
  • dev.atsushieno.ktmidi.ci package contains a bunch of utility functions that are used to construct MIDI-CI system exclusive packets.

atsushieno/kmmk supports "MIDI 2.0 mode" which sends MIDI messages in MIDI 2.0 UMPs. There is also an ongoing experimental project to process MIDI 2.0 UMPs in audio plugins on Android.

SMF alternative format

MIDI 2.0 June 2023 updates comes with a brand-new MIDI Clip File specification, which calls itself "SMF2". Though it is not a multi-track music file format like SMF. Therefore, we still have our own format. See docs/MIDI2_FORMATS.md for details.

It is implemented as Midi2Music (read() and write()), and mugene-ng makes use of it to generate music files that are based on this format.

Historical background

It started as the Kotlin port of C# managed-midi library. Also it started with partial copy of fluidsynth-midi-service-j project.

However everything in this project went far beyond them and now we are making it usable for MIDI 2.0.

Some of the MIDI 2.0 related bits are ported from cmidi2 library.

Historically ktmidi-jvm-desktop used to reside in its own repository to avoid complicated dependency resolution, so there would be some outdated information that tells it was isolated from this project/repository.

Resources

We use GitHub issues for bug reports etc., and GitHub Discussions boards open to everyone.

For hacking and/or contributing to ktmidi, please have a look at docs/HACKING.md.

There is Application/library showcase discussions thread.

API documentation is published at: https://atsushieno.github.io/ktmidi/

(The documentation can be built using ./gradlew dokkaHtml and it will be generated locally at build/dokka/html.)

There are couple of API/implementation design docs:

You can also find some real-world usage examples of these API components:

License

ktmidi is distributed under the MIT license.

jazz-soft/JZZ is included in ktmidi-js package and is distributed under the MIT license.

rtmidi is included in ktmidi-native-ext package and is distributed under the MIT license. (It is also indirectly referenced via rtmidi-jna, which may be a different version.)

midi-ci-tool uses Wavesonics/compose-multiplatform-file-picker which is distributed under the MIT license.

ktmidi's People

Contributors

alecjy avatar atsushieno avatar burtan avatar sed0 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ktmidi's Issues

All device IDs are 0 in TraditionalCoreMidiAccess

ISSUE: The ids of all midi devices (both, inputs and outputs) are 0 (TraditionalCoreMidiAccess), so we cannot distinguish them by id, even though those fields should be unique according to apple docs.

This is the definition of the field in CoreMidiPortDetails

    @OptIn(ExperimentalForeignApi::class)
    override val id: String
        get() = getPropertyInt(endpoint, kMIDIPropertyUniqueID).toString()

And here's the Apple docs for the field:
https://developer.apple.com/documentation/coremidi/kmidipropertyuniqueid

UPDATE: Tested and reproduced by connecting 3 USB MIDI devices (via a powered hub) and one MIDI network session to a non-M1 iPad Pro with iPadOS 17.5.1. Same result (id 0) is also present when connecting one USB device directly to the iPad (plus the network session, also id 0).

Originally posted by @JBramEu in #85 (comment)

add support for SMPTE offsets in MidiMusic, MidiPlayer, and DeltaTimeComputer<T>

Currently MidiMusic does not support SMPTE offsets (only because they were so irrelevant to me) but to deal with realtime inputs SMPTE offsets are definitely useful.

There are however not a few lines of change other than the lines to replace TODO("Not Implemented") with the actual implementation. There are couple of lines that had premise on "delta times are positive" parts, especially in MidiPlayer (for both 1 and 2) and DeltaTimeComputer<T> (for both SMF and UMP). They all need to be supported altogether.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Repository problems

These problems occurred while renovating this repository. View logs.

  • WARN: Package lookup failures

Rate-Limited

These updates are currently rate-limited. Click on a checkbox below to force their creation now.

  • Update dependency androidx.activity:activity-compose to v1.9.0
  • Update dependency androidx.core:core-ktx to v1.13.0
  • Update dependency gradle to v8.7
  • Update junit5 monorepo to v5.10.2 (org.junit.jupiter:junit-jupiter-engine, org.junit.jupiter:junit-jupiter-api)
  • ๐Ÿ” Create all rate-limited PRs at once ๐Ÿ”

Warning

Renovate failed to look up the following dependencies: Failed to look up maven package org.jetbrains.kotlinx:kotlinx-coroutines-core-wasmjs.

Files affected: gradle/libs.versions.toml


Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/actions.yml
  • actions/checkout v4
  • actions/setup-java v4
  • actions/upload-artifact v4
  • actions/upload-artifact v4
  • JamesIves/github-pages-deploy-action 4.1.2
  • ubuntu 22.04
gradle
gradle.properties
publish-root.gradle
settings.gradle
build.gradle
gradle/libs.versions.toml
  • org.jetbrains.kotlin:kotlin-test 1.9.22
  • org.jetbrains.kotlin:kotlin-test-junit 1.9.22
  • junit:junit 4.13.2
  • androidx.core:core-ktx 1.12.0
  • androidx.test.ext:junit 1.1.5
  • androidx.test.espresso:espresso-core 3.5.1
  • androidx.appcompat:appcompat 1.6.1
  • androidx.activity:activity-compose 1.8.2
  • androidx.compose.ui:ui 1.6.5
  • androidx.compose.ui:ui-tooling 1.6.5
  • androidx.compose.ui:ui-tooling-preview 1.6.5
  • dev.atsushieno:alsakt 0.3.5
  • org.junit.jupiter:junit-jupiter-api 5.9.1
  • org.junit.jupiter:junit-jupiter-engine 5.9.1
  • org.jetbrains.kotlinx:kotlinx-coroutines-core 1.8.0
  • org.jetbrains.kotlinx:kotlinx-coroutines-swing 1.8.0
  • org.jetbrains.kotlinx:kotlinx-coroutines-android 1.8.0
  • org.jetbrains.kotlinx:kotlinx-coroutines-core-js 1.8.0
  • org.jetbrains.kotlinx:kotlinx-coroutines-core-wasmjs 1.8.0
  • org.jetbrains.kotlinx:kotlinx-coroutines-test 1.8.0
  • org.jetbrains.kotlinx:kotlinx-datetime 0.5.0
  • org.jetbrains.kotlinx:kotlinx-serialization-core 1.6.2
  • org.jetbrains.kotlinx:kotlinx-serialization-json 1.6.2
  • io.ktor:ktor-io 2.3.7
  • io.ktor:ktor-utils 2.3.7
  • com.darkrockstudios:mpfilepicker 3.1.0
  • dev.atsushieno:rtmidi-javacpp 0.1.5
  • dev.atsushieno:rtmidi-javacpp-platform 0.1.5
  • com.android.application 8.2.2
  • com.android.library 8.2.2
  • org.jetbrains.compose 1.6.0
  • org.jetbrains.kotlin.multiplatform 1.9.22
  • org.jetbrains.kotlin.jvm 1.9.22
  • org.jetbrains.dokka 1.9.10
  • org.bytedeco.gradle-javacpp-build 1.5.10
  • org.bytedeco.gradle-javacpp-platform 1.5.10
  • org.jetbrains.kotlin.plugin.serialization 1.9.22
  • org.jetbrains.kotlinx.binary-compatibility-validator 0.14.0
  • org.jetbrains.kotlin.android 1.9.22
input-sample/build.gradle.kts
ktmidi/build.gradle.kts
ktmidi-ci/build.gradle.kts
ktmidi-ci-tool/build.gradle.kts
ktmidi-jvm-desktop/build.gradle.kts
ktmidi-native-ext/build.gradle.kts
player-sample/build.gradle.kts
gradle-wrapper
gradle/wrapper/gradle-wrapper.properties
  • gradle 8.6

  • Check this box to trigger a request for Renovate to run again on this repository

No luck sending/receiving MIDI on iOS

Test setup:

  • ktmidi 0.9.0
  • TraditionalCoreMidiAccess
  • iPad Pro (non-M1)
  • iPadOS 17.5.1
  • USB Midi connection to a Sequential Prophet 6 Synthesizer
  • Same application works fine on JVM and Android targets with their respective MidiAccess implementation

What is working:

  • device detection (querying list of IOs, Prophet 6 shows up)

What's not working:

  • Sending midi (nothing received on the synth)
  • Receiving midi (listener not triggered)

Alternate approaches tried:

  • UmpCoreMidiAccess (crashes the app)

Further investigation results:
Both input and output report CLOSED as their connectionState immediately after MidiAccess.openIn/Output

citool: save and restore along with app lifecycle

Currently there is no lifecycle management. I thought Essenty is useful, but it does not provide actual platform implementations to manage lifecycles (only stub implementations are there). We will have to come up with something.

(taken from #56)

ALSA sequencer support

Currently desktop jvmMain implementation provides JvmMidiAccess which is based on javax.sound.midi. Sadly this Oracle API is useless, because they thought ALSA rawmidi is useful enough, which is simply not true. What developers really want is ALSA sequencer, and it is not supported.

How can we fix this? At managed-midi we created alsa-sharp project which provides P/Invoke access to ALSA sequencer API. To achieve that, we would like to make use of some existing native binding tools. Candidates:

  • JavaCPP - it looks good, provides automated API generation. But we have to hack around some dependencies (e.g. pollfd() in libc) which is going to be mess. Also adds runtime dependency on javacpp-runtime (not really problematic a lot, but something)
  • JNA - works, better if we can get JNAerator working to automate binding generation. But it is not maintained by the original developer anymore. Maybe some forks still work. Also adds runtime dependency on JNA (not really problematic a lot, but something).
  • hand-bound JNI - messy, but we can simply limit scope of the bound API, which eases the development. We don't really need a lot of API bindings to implement MidiAccess API.

Still wondering which to go.

[BREAKING CHANGE] fix Midi2Music serialization format for the number of UMPs in a track

Currently the file formats for Midi2Music is defined as:

// Data Format:
//   identifier: 0xAAAAAAAAAAAAAAAA (16 bytes)
//   i32 deltaTimeSpec
//   i32 numTracks
//   tracks - each track contains:
//       identifier: 0xEEEEEEEEEEEEEEEE (16 bytes)
//       i32 numUMPs
//       umps (i32, i64 or i128) in BIG endian

It is however not a well-thought design because we cannot retrieve a track without parsing the entire binary content of previous tracks, because the size of a UMP varies per message type and thus the number of UMPs does not really indicate the precise binary size. It should be simply number of bytes.

It is however a breaking change to existing serialized stream. Personally I am optimistic to assume that there were no users of this feature yet, but we would bump the version to 0.4 to imply the incompatible changes with bold claim.

MidiPlayer or alsakt causing CPU hog

I still have no idea how to reproduce this in minimum repro, but kmmk runs into 100% CPU usages when I play a compiled MML on the app (that uses MidiPlayer internally).

MIDI-CI: zlib+Mcoded7 on all platforms

Currently only jvmMain and androidMain has working implementation (based on java.util.zip).

We could implement for most of the rest, but I'm rather waiting for ktor-io 3.0.0 which has DeflateEncoder. It's not ready for all the required platforms yet (namely wasmJs).

(taken from #56)

Linux arm64 and Darwin arm64 for RtMidiAccess

Currently we depend on atsushieno/rtmidi-jna to implement RtMidiAccess (which is the primary API for native desktop MidiAccess for ktmidi), but it cannot support Darwin arm64 (M1/M2) because the underlying jnaerator does not support it (and the development had stopped).

We should probably find some alternative to rtmidi-jna. So far JRtMidi does not support darwin-arm64 either.

meta: remaining MIDI-CI tasks

Here is the list of missing features in dev.atsushieno.ktmidi.ci package:

  • property message chunking. It would result in separation of Message and the actual message packets in SysEx message units
  • support for zlib+Mcoded7 (we need zlib implementation for KMP) -> works on desktop now
  • apply property body binary conversion based on mutualEncoding
  • initiator: ChannelList app developer should provide it
  • initiator: Schema app developer should provide it
  • ASCII validation of strings (product instance ID)
  • initiator: Profile Details Inquiry
  • initiator: Property subscription and subscription management
  • move logging facility from midi-ci-tool (app) to ktmidi (library) module
  • responder: overall UI
  • responder: partial property update by RFC6901 (needs to parse the expressions and apply patching updates)
  • initiator: partial property update by RFC6901 for subscription updates
  • overall message buffer size verification
  • Process Inquiry
  • MIDI message report implementation (entrypoints are already implemented in ktmidi-ci)
  • probably define Muid type that only contains an Int value but ensures that its data content is 28 bit and supports appropriate conversion -> so far it is visible as 28bit on UI
  • save/load local configuration
  • initiator UI: there should be device info configuration (currently it is only for responder)
  • overall MUID validation
  • take out muid from SavedSettings (it must be always uniquely created)
  • end subscription
  • support "group" in Message and everywhere applicable
  • timeout manager (adds some threading complication)
  • app startup/terminate lifecycle support (we can kinda skip that; the specification anticipates InvalidateMUID may not be sent)
  • property pagination

set up API for configuring UMP endpoints

In UMP v1.1 specification there are UMP stream configuration messages and it involves bidirectional messaging. There are also new concepts of function blocks which had better modeled as some kind of classes, especially when they have dedicated groups and function block info definitions. It is too complicated for us to manually implement them. It is kind of common task for every UMP endpoint managers.

sanitize MidiPlayer internals

Currently MidiPlayer internals are based on legacy code from managed-midi, which is kind of awkward. It would be probably better rewritten in structured concurrency manner. No idea in practice though.

MIDI-CI: request/retry timeout manager

(taken from #56)

These items are subject to timeout, and we do not have any timeout manager so far:

  • requestID: recycled within 0-127, need to keep which is active
  • subscriptions: they could stay forever
  • discovery (after 3 seconds we could drop any DiscoveryReply. We do not have to though.)

We would also need request pending queue, or immediately send retry. There should be some policy on how to deal with them. Depending on it, we will have to send ACK (which is currently not in use).

JzzMidiInput erroring when MidiInput messageRecieved to listener

Trying out the library for the browser and I ran into an issue. When using JzzMidiInput and a registered OnMidiReceivedEventListener is going to be triggered the library calls

private val outFunc : dynamic = { midi : dynamic ->
        val arr = midi.data
        this.listener?.onEventReceived(arr, 0, arr.length, 0)
    }

For some reason midi.data is undefined in both Firefox and Chrome on my Mac. I was able to locally just change the midi.data to midi and it solved the issue. I'm happy to put up the minor PR, but I just wanted to check if I was missing something before I do it

make it ready for Android

I have no idea how, but MPP project structure offered by JetBrains (IntelliJ IDEA) has a significant problem that there is no way to differentiate Android-specific library and desktop-specific library (Oracle, GNU Classpath, etc.). Desktop implementation has JvmMidiAccess which is based on javax.sound.midi API (javax.sound.midi is incompete, but at least implementable) and it is under jvmMain, but since there is no androidMain unlike Jetpack Compose MPP template (which makes use of different JetBrains Kotlin project plugin), there is no way to add Android specific sources to the project.

Jetpack Compose MPP offers a project structure where multiple modules are built for even one platform (commonMain + androidMain for common module, and Android specific module aside) but multiplatform plugin does not offer that.

It seems that Kotlin MPP is still no-go for us yet.

Use flows instead of interface listeners

Hi,

I think it would be more idiomatic for kotlin to use flows for midi inputs instead of interface listeners. At least lambdas should be provided. What do you think?

Virtual port creator API

At managed-midi we had MidiPortCreatorExtension API which provides a way to create virtual ports. It is doable on Linux (ALSA) and Mac (CoreMIDI) and there is no reason to limit platform MIDI features behind lame OSes that don't support such a feature (WiinMM / UWP).

So far they don't exist in the MidiAccess API yet. We would first provide platform implemantation to ensure that this MidiAccess API in general works and then extend it, this time without annoying "extension" model.

SYSEX troubles on nearly every platform

As stated in the title, I'm experiencing lots of troubles with SYSEX messages. Here's a summary:

Windows JVM
Works by default

MacOS JVM
MacOS Bug, their Java Midi implementation ignores SYSEX messages.
I could work around this by adding CoreMidi4J as a dependency and cloning JvmMidiAccess to use the library components. May I suggest adding this workaround to the library?

Android
The Android platform splits up midi data in quite small chunks - and since the data I'm handling is a SysEx bulk of more than 1k Bytes, I need to check for the Sysex start byte - and if there's no Sysex End, keep and combine the received data bytes until I finally receive a sysex end byte. This is quite annoying. Not sure if this could happen on all platforms and android is the only one chunking the data stream in such small pieces or if the other platforms make sure they don't split SysEx messages in half.

iOS
Both, TraditionalMidiAccess and UmpMidiAccess just crash instantly upon trying to send a message. No solution/workaround yet, need to do some more debugging.

I haven't tried Linux or any of the native targets yet.

support resId in the way that makes sense

Currently property values are stored without paying any attention to resId. But having one single value for a property seems fundamental mistake. When resId is involved, the property values are stored like a map. juce_midi_ci has the same problem. Common Rules for PE specification itself is indeed problematic by stating almost nothing about resId. There is no access to what kind of resId is available, especially to clients.

minimal API documentation

It has long been "ktmidi API is basically based on Web MIDI API, along with basic music structure and player, so use of API would be fairly straightforward."

As we have a lot more features, it does not apply anymore. We'd need some API documentation, not just API reference structure that gives the basics.

revamp the device change detection events

Regarding #38, we need a working mechanism for device change detection. I mean, it is not working now. Here is the current API definition:

abstract class MidiAccess {
    open val canDetectStateChanges = false
    var stateChanged : (MidiPortDetails) -> Unit = {}

This API structure is taken from Web MIDI API. However, their MidiPort and our MidiPortDetails are quite different. Namely, our MidiPortDetails is not "live". We do not have their state property which can be "disconnected" or "connected".

Passing only MidiPortDetails means that we cannot really report "device added" and "device removed". Or it can be even "device changed" (of some properties), but I have no right idea what could change, on each platform.

In either way, we will have to deprecate this stateChanged and introduce another property.

CI builds

ktmidi depends on alsakt, and android-audio-plugin-framework will depend on ktmidi. Thus it is critical if alsakt does not build on CI. And it actually doesn't build, due to tight dependency on ALSA.

Therefore, alsakt has to be outsted from ktmidi in terms of build dependency. Since Kotlin MPP is based on quite fragile premises on "same Kotlin/JVM module works everywhere" fantasy, it will not work for our standard demand. The repository structure will have to become like:

  • ktmidi : MPP, foundation for all the supported platforms (JVM/JS/Native)
  • ktmidi-alsa : JVM, AlsaMidiAccess and co.
  • alsakt : JVM, JavaCPP ALSA binding.
  • ktmidi-jvm : other boring desktop binding based on javax.sound.midi (Features limited)
  • ktmidi-android : Android MIDI API implementation.

These are nonexistent but possible solutions

  • ktmidi-libremidi : if we want to support virtual ports in CoreMIDI, it is the closest solution
  • libremidi-kotlin-jvm : we need similar libremidi binding to alsakt (but it was not simply doable yet, this API causes a lot more problems with complex C++ constructs like std::function)

org.bytedeco:javacpp:1.5.10 not found with 0.8.2

When bumping my application from 0.8.0-pre to 0.8.2 I get the following error output:

Could not create task ':startScripts'.
Could not create task ':jar'.
Could not resolve all files for configuration ':runtimeClasspath'.
Could not find javacpp-1.5.10-android-arm.jar (org.bytedeco:javacpp:1.5.10).
Searched in the following locations:
    https://repo.maven.apache.org/maven2/org/bytedeco/javacpp/1.5.10/javacpp-1.5.10-android-arm.jar

With version 0.8.1 this error is not thrown.

consistent MidiPlayer implementation

Right now the unit tests don't consistently complete, involves a handful of delay() hacks to probably workaround blocking issues, and time calculation is not consistent while it should be. We need a solid implementation based on coroutines.

Basic example for KotlinJvm and midi device handling

Thank you very much for creating this library. I tried to use it in my current project but it seems that just from the documentation and test scripts, I am not able to get it up and running.

I would like to control some parameters in my software with a midi controller. But I am not able to receive a midi message from my controller:

private val midi = JvmMidiAccess()
private var midiInput: MidiInput? = null

fun main(args: Array) {
    checkForInput()
}

fun checkForInput() {
    for (input in midi.inputs) {
        if (input.name == deviceName) {
            midiInput?.close()
            setupInput(input)
            break
        }
    }
}

fun setupInput(portDetails: MidiPortDetails) {
    midiInput = runBlocking { midi.openInputAsync(portDetails.id) }
    midiInput?.setMessageReceivedListener(this)
}

override fun onEventReceived(data: ByteArray, start: Int, length: Int, timestampInNanoseconds: Long) {
    println("message received")
}

Instead of JvmMidiAccess I also tried RtMidiAccess. A device is found and I can open the port (openInputAsync), but I never receive an event. Could you please give me a hint how to receive basic midi data (and maybe even parse it)?

random runtime crashes on RtMidiAccess

It is probably use of the API, but RtMidiAccess seems to cause random-looking runtime crashes. It is so far reproducible on kmmk. Use of API in general means pointer access to non-retained Java Objects.

rename -Async() functions?

Re: MidiAccess.openInputAsync() and MidiAccess.openOutputAsync().

Naming suspend functions as -Async() is not likely cool, but not sure if this is a good practice in Kotlin/JVM world.

rtmidi on ios is not working

Can't build for iOS, says dependencies can't be resolved for iosArm64 and iosSimulatorArm64. My guess is iosX64 target is not enough and you should also include iosArm64 and iosSimulatorArm64

bring in AlsaMidiAccess to this repo?

ktmidi-jvm-desktop was split from here to avoid complicated package dependencies. Now that jitpack is not able to build this repository anymore (due to lack of chromeheadless test runner environment), we will have to resort to MavenCentral or whatever usable anyways. Once we choose a working repository that does not need any authentication (GitHub Packages is no-go in the end), we can bring back AlsaMidiAccess into this repository, adding alsakt as a dependency.

(RtMidi) The specified module could not be found

Hello,

I was testing the library to see if I could use it on my system. For some reason, when I try to get MIDI access, java cannot load the rtmidi module. I'm using gradle version 7.2; kotlin version 1.6.10; java compile target 1.8; and the jvm is temurin 17.0.1.

This is the stack trace:

Exception in thread "main" java.lang.UnsatisfiedLinkError: The specified module could not be found.
	at com.sun.jna.Native.open(Native Method)
	at com.sun.jna.NativeLibrary.loadLibrary(NativeLibrary.java:277)
	at com.sun.jna.NativeLibrary.getInstance(NativeLibrary.java:461)
	at com.sun.jna.NativeLibrary.getInstance(NativeLibrary.java:403)
	at dev.atsushieno.rtmidijna.RtmidiLibrary.<clinit>(RtmidiLibrary.java:20)
	at dev.atsushieno.ktmidi.RtMidiAccess.<clinit>(RtMidiAccess.kt:12)
	at MainKt.main(Main.kt:6)

This is the code:

import dev.atsushieno.ktmidi.AlsaMidiAccess
import dev.atsushieno.ktmidi.RtMidiAccess
import java.io.File

fun main(args: Array<String>) {
    val access = if(File("/dev/snd/seq").exists()) AlsaMidiAccess() else RtMidiAccess()
    access.inputs.forEach {
        println("${it.id} ${it.name} [ ${it.manufacturer} ] - ver. ${it.version}")
    }
}

This is the gradle implementation block:

val ktmidi_version = "0.3.16"
dependencies {
    implementation("dev.atsushieno:ktmidi:$ktmidi_version")
    implementation("dev.atsushieno:ktmidi-jvm:$ktmidi_version")
    implementation("dev.atsushieno:ktmidi-jvm-desktop:$ktmidi_version")
}

Checking the external libraries, I can see that gradle did in fact pull 5 dependencies with your package name. Here is a screenshot (I expanded the rtmidi-jna block to verify that the dll files were included):
External Libraries

I'm unsure as to why this is happening as this is a bit beyond my knowledge at this point.

Thanks in advance!

JvmMidiAccess does not recognize input ports

Hey,
thanks for creating this library.

However, JvmMidiAccess does not find any of my midi input ports. A quick look into the sources showed that it maps all receivers, but they are all empty in my case. Would you accept pull requests on a fix for this?

make it MPP ready

One of the purpose of this library is using it in android-audio-plugin-framework which partly considers desktop as the target. Therefore building ktmidi for desktop will be mandatory at some point.

Similarly, when we want to build and run those apps for web using kotlin JS/wasm, we will have to eliminate all Java dependencies. i.e. any use of java.* API will have to be eliminated.

local rtmidi setup instruction

I tried using ktmidi for jvm-desktop and got UnsatisfiedLinkError. Tried to debug and putting a renamed rtmidi.dll to C:\Users%user%\AppData\Local\Temp\jna-72501012\ (found this directory while debugging, it tries to open a .dll with randomly generated name and fails, I thought replacing it might help) but I still got the exception. Not sure what I have to do and what I've been doing wrong, an instruction would help

(Breaking changes) rename MidiPlayer to Midi1Player, as well as MidiPlayerCommon

Currently MidiPlayer is only for MIDI 1.0. MIDI 2.0 player is Midi2Player, and there is a common base class MidiPlayerCommon<T>. Since this <T> practically blocks specifying this class by users, it does not make a lot of sense.

There should be probably an interface that provides the operations over those classes, but it cannot be named as MidiPlayer.

Instead of taking long steps to rename these classes to keep API compatibility, I would take current "there is almost no user" status as an opportunity and break API compatibility immediately.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.