MacOS app crashes when sending large SysEx messages
intonarumori opened this issue · 5 comments
I've been working on a MIDI editor application and noticed crashes when sending larger SysEx messages.
I traced down the issue to this line of code for iOS and macOS, see screenshot.
I looked at some other repositories for reference and here's what I found:
- The Snoize apps are an excellent implementation of MIDI handling on macOS:
https://github.com/krevis/MIDIApps/blob/94587d3fb02d7851fccd2f85aca40712305de54e/Frameworks/SnoizeMIDI/SMMIDIUtilities.m#L10-L14
https://github.com/krevis/MIDIApps/blob/94587d3fb02d7851fccd2f85aca40712305de54e/Frameworks/SnoizeMIDI/OutputStream.swift#L35-L57 - Some hints from the documentation:
https://developer.apple.com/documentation/coremidi/1495128-midipacketlistadd?language=objc - Another reference implementation breaking up large messages:
https://github.com/thestk/rtmidi/blob/e8c8791823ca99956adb4faf2b28bb843c7c1f6e/RtMidi.cpp#L1667-L1730 - Another reference implementation meticulously calculating the list size:
https://github.com/mixedinkey-opensource/MIKMIDI/blob/9373633014db4bce94a5375c32df19e6c74311a9/Source/MIKMIDICommand.m#L337-L352
Takeaways so far
- Looks like we should check the returned
packet
value fromMIDIPacketListAdd
and handle errors accordingly - MIDIApps wraps
MIDIPacketListAdd
in Obj-C and makes the return typenullable
- We should probably break up large messages to smaller chunks, even though based on the documentation up until 65535 bytes this shouldn't be an issue
- We use 1024 bytes as the list size in
MIDIPacketListAdd
- I wonder if this is correct
I've attached an example application, pressing "Sending 512 bytes" couple of times crashes the app for me, let me know if you can reproduce.
I will be looking into the issue to see if I can fix it as I have some existing Obj-C implementation that worked fine in the past.
Flutter Example for sending large SysEx messages
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_midi_command/flutter_midi_command.dart';
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(body: HomeView()),
);
}
}
class HomeView extends StatefulWidget {
const HomeView({super.key});
@override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
final midi = MidiCommand();
@override
void initState() {
super.initState();
midi.onMidiSetupChanged?.listen((_) => _updateSetup());
_updateSetup();
}
void _updateSetup() async {
final devices = await midi.devices ?? [];
for (final device in devices) {
if (!device.connected) {
try {
await midi.connectToDevice(device);
} catch (_) {
debugPrint('Could not connect to device: $device');
}
}
}
}
void _sendEmptySysex(int length) {
final data = Uint8List(length);
data[0] = 0xF0;
data[length - 1] = 0xF7;
_sendData(data);
}
void _sendData(Uint8List data) async {
final devices = await midi.devices ?? [];
for (final device in devices) {
midi.sendData(data, deviceId: device.id);
}
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [256, 512, 768, 1024]
.map(
(e) => Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: () => _sendEmptySysex(e),
child: Text('Send $e bytes'),
),
),
)
.toList(),
),
);
}
}
Quick update:
I was able to fix the crashes by using these two parts of the MIDIApps code:
- Being able to catch
nil
returned fromMIDIPacketListAdd
- Chunking MIDI messages and using a different way of working with pointers in swift
I will see if I can simplify the code a little bit, it looks like we have to introduce some Obj-C helpers.
Thank you for diving into this issue.
Splitting large arrays of data into smaller ones should not be a big challenge.
However, I am a little confused as to why sending multiple 512 bytes SysEx chunks causes the system to crash.
Mostly because every send
call creates its own MIDIPacketList
and puts data into that. (I can see some other potential issues here though). This must mean that there is further "queueing' deeper down the system ?
Alternatively, we should consider migrating to the never MIDIEventList
type on Apple's platforms, but that is probably a bigger undertaking.
@intonarumori please try out this branch which does packet splitting:
https://github.com/InvisibleWrench/FlutterMidiCommand/tree/bugfix/coremidi_dealloc
@mortenboye I ran my example app and this looks to be working great.
Fix included in v. 0.4.16.