aud_midi 0.1.3 copy "aud_midi: ^0.1.3" to clipboard
aud_midi: ^0.1.3 copied to clipboard

Platformweb

MIDI for Dart and Flutter apps on iOS, macOS, Android, Windows, Linux and the web: USB, Bluetooth LE, virtual and network MIDI in a MIDI isolate

aud_midi #

MIDI for Dart and Flutter apps on iOS, macOS, Android, Windows, Linux and the web: USB, Bluetooth LE, virtual and network MIDI with MIDI 1.0 and MIDI 2.0 (UMP) messages. All MIDI I/O runs in its own isolate, so a busy app isolate never delays it. No platform channels: the backends talk to the operating system through FFI, JNI and JS interop.

Goals #

  • One API for six platforms; ports are the primary entity
  • USB, Bluetooth LE, virtual ports and network sessions (AppleMIDI, Network MIDI 2.0)
  • MIDI 1.0 and MIDI 2.0 messages with explicit translation
  • Exact timing: OS timestamps on input, OS scheduling on output
  • A MIDI isolate that the app's isolates cannot delay
  • Losses are visible as diagnostics, streams never error

State #

Platform Backend Verified in this ticket
macOS CoreMIDI (UMP), Bonjour, pure-Dart AppleMIDI real loopback, example app build
iOS CoreMIDI (UMP), MIDINetworkSession simulator loopback, example app build
Android android.media.midi + AMidi emulator loopback, example APK build
Windows WinRT MIDI, optional Windows MIDI Services Dart logic only, not built on Windows
Linux ALSA sequencer, BlueZ, Avahi Dart logic only, not run on Linux
Web Web MIDI Chrome, example web build

midi_bench on this Mac (Apple backend, two clean runs, 500 messages each):

Check Median p95 p99
Round trip through virtual endpoints to the app isolate 152–158 µs 228–249 µs 378–384 µs
CoreMIDI-scheduled send with a busy app isolate, arrival − due 62–63 µs 133–138 µs 164–726 µs
Software-scheduled send from an own virtual source, arrival − due 2.5–2.6 ms 3.2 ms 3.3 ms

Fragmented SysEx with clocks in between is reassembled, overflows are fully accounted for by diagnostics, hotplug is reported after about 290 ms. Coverage of the package is 100 %.

Installation #

dependencies:
  aud_midi:
    git:
      url: git@github.com:audmidi/aud_midi.git

The package depends on all backends; aud_midi_android needs the Flutter SDK (jni), so use the dart of a Flutter installation.

Platform setup:

  • iOS: NSBluetoothAlwaysUsageDescription, NSLocalNetworkUsageDescription, NSBonjourServices (_apple-midi._udp), UIBackgroundModes audio for virtual ports (and bluetooth-central for BLE in the background)
  • macOS: entitlements com.apple.security.device.bluetooth, com.apple.security.network.client, com.apple.security.network.server
  • Android: the permissions and services from the aud_midi_android README
  • Windows: MSIX capabilities bluetooth, internetClient, privateNetworkClientServer
  • Linux: libasound2, access to /dev/snd/seq; BlueZ and Avahi over D-Bus for BLE and network
  • Web: https or localhost, the browser's MIDI permission

The example app in example/ shows these settings.

Documentation #

Code Examples #

example/cli/bin/hello_midi.dart:

import 'package:aud_midi/aud_midi.dart';

Future<void> main() async {
  // Start the MIDI isolate with the backend of this platform
  final midi = await Midi.open();
  print('Backend: ${midi.backendName}');
  for (final input in midi.inputs) {
    print('Input:  ${input.info.name}');
  }
  for (final output in midi.outputs) {
    print('Output: ${output.info.name}');
  }

  // Create a virtual source that other apps can receive from
  final source = await midi.virtualPorts!.createSource('aud_midi hello');

  // Play C4 now and release it 500 ms later; the MIDI isolate schedules
  // the Note Off, so a busy app isolate cannot delay it
  await source.send(const MidiNoteOn(channel: 0, note: 60, velocity: 100));
  await source.send(
    const MidiNoteOff(channel: 0, note: 60),
    at: midi.now() + const Duration(milliseconds: 500),
  );
  await Future<void>.delayed(const Duration(seconds: 1));

  await midi.close();
}

How It Works #

Midi.open() spawns one MIDI isolate per process. It hosts a MidiEngine from aud_midi_core with the backend of the platform, composed with the network session and Bluetooth LE where needed. The Midi, MidiInput and MidiOutput objects in the app are proxies that talk to it through SendPorts; other isolates attach with Midi.attach(midi.connector). Scheduled sends reach the MIDI isolate at once and are handed to the operating system 100 ms before they are due, so they stay cancellable. On the web the engine runs on the main thread, where Web MIDI lives.

A native consumer, e.g. an audio engine, takes the raw data of an input without any isolate in between: where input.supportsNativeTap holds (CoreMIDI, android.media.midi), input.attachNativeTap(MidiNativeTap( function: ..., user: ...)) registers a C function that the backend calls on its receive thread with UMP words or MIDI 1.0 bytes and a host timestamp in nanoseconds, and with gone when the port disappears; the handle's detach() completes after the last call. The taps of a proxy leave with it.

On iOS, midi.bluetooth!.showSystemDevicePicker() shows the Bluetooth MIDI device picker of the operating system; the devices connected there appear as ports. Elsewhere it throws a MidiUnsupported; scan with midi.bluetooth!.scan() there instead.

Contributing #

See doc/guides/develop-guide.md.

0
likes
160
points
472
downloads

Documentation

API reference

Publisher

verified publisheraudanika.com

Weekly Downloads

MIDI for Dart and Flutter apps on iOS, macOS, Android, Windows, Linux and the web: USB, Bluetooth LE, virtual and network MIDI in a MIDI isolate

Repository (GitHub)
View/report issues

Topics

#midi #audio #music

License

MIT (license)

Dependencies

aud_midi_android, aud_midi_apple, aud_midi_core, aud_midi_linux, aud_midi_network, aud_midi_standard, aud_midi_web, aud_midi_windows

More

Packages that depend on aud_midi