Flutter MIDI控制插件flutter_midi_command的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter MIDI控制插件flutter_midi_command的使用

flutter_midi_command 是一个用于在Flutter应用中发送和接收MIDI消息的插件,它支持与物理和虚拟MIDI设备进行通信。以下是关于该插件的详细介绍,包括安装、配置以及如何编写简单的示例代码。

支持平台

Transports iOS macOS Android Linux Windows
USB
BLE
Virtual
Network Session

安装

  1. 确保项目使用Kotlin和Swift:创建Flutter项目时,请确保选择了Kotlin(Android)和Swift(iOS)作为原生语言。

  2. 添加依赖:在pubspec.yaml文件中添加以下行:

    dependencies:
      flutter_midi_command: ^0.5.1
    
  3. 配置iOS

    • ios/Podfile中设置最低版本为11.0:
      platform :ios, '11.0'
      
    • info.plist文件中添加蓝牙和网络权限说明:
      <key>NSBluetoothAlwaysUsageDescription</key>
      <string>We need bluetooth permissions to connect to MIDI devices.</string>
      <key>NSLocalNetworkUsageDescription</key>
      <string>We need local network access for MIDI over network.</string>
      
  4. Linux环境:确保已安装ALSA库。

使用指南

导入库

首先需要导入flutter_midi_command库:

import 'package:flutter_midi_command/flutter_midi_command.dart';

基本操作

  • 获取所有MIDI设备列表

    final MidiCommand midiCommand = MidiCommand();
    List<MidiDevice> devices = await midiCommand.devices;
    
  • 启动蓝牙中心(仅限BLE传输):

    await midiCommand.startBluetoothCentral();
    
  • 监听蓝牙状态变化

    StreamSubscription<BluetoothState>? _bluetoothStateSubscription =
        midiCommand.onBluetoothStateChanged.listen((state) {
      print('Bluetooth state changed to $state');
    });
    
  • 扫描并连接BLE MIDI设备

    // 开始扫描
    await midiCommand.startScanningForBluetoothDevices();
    
    // 连接到指定设备
    await midiCommand.connectToDevice(selectedDevice);
    
    // 停止扫描
    await midiCommand.stopScanningForBluetoothDevices();
    
  • 监听MIDI设置更改

    StreamSubscription<String>? _setupSubscription =
        midiCommand.onMidiSetupChanged?.listen((data) {
      print("MIDI setup changed: $data");
    });
    
  • 接收MIDI数据

    StreamSubscription<List<int>>? _midiDataSubscription =
        midiCommand.onMidiDataReceived.listen((data) {
      print("Received MIDI data: ${data.toList()}");
    });
    
  • 发送MIDI消息

    // 发送Note On消息
    List<int> noteOnMessage = [0x90, 60, 100]; // Channel 0, Note C4, Velocity 100
    await midiCommand.sendData(noteOnMessage);
    
  • 创建虚拟MIDI设备

    await midiCommand.addVirtualDevice(name: "My Virtual MIDI Device");
    

示例代码

下面是一个完整的Dart程序示例,展示了如何使用flutter_midi_command插件来列出可用的MIDI设备,并允许用户通过界面与这些设备交互。

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_midi_command/flutter_midi_command.dart';

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp> {
  StreamSubscription<String>? _setupSubscription;
  StreamSubscription<BluetoothState>? _bluetoothStateSubscription;
  final MidiCommand _midiCommand = MidiCommand();

  bool _virtualDeviceActivated = false;
  bool _iOSNetworkSessionEnabled = false;

  bool _didAskForBluetoothPermissions = false;

  @override
  void initState() {
    super.initState();

    _setupSubscription = _midiCommand.onMidiSetupChanged?.listen((data) async {
      if (kDebugMode) {
        print("setup changed $data");
      }
      setState(() {});
    });

    _bluetoothStateSubscription =
        _midiCommand.onBluetoothStateChanged.listen((data) {
      if (kDebugMode) {
        print("bluetooth state change $data");
      }
      setState(() {});
    });

    _updateNetworkSessionState();
  }

  @override
  void dispose() {
    _setupSubscription?.cancel();
    _bluetoothStateSubscription?.cancel();
    super.dispose();
  }

  Future<void> _updateNetworkSessionState() async {
    var nse = await _midiCommand.isNetworkSessionEnabled;
    if (nse != null) {
      setState(() {
        _iOSNetworkSessionEnabled = nse;
      });
    }
  }

  IconData _deviceIconForType(String type) {
    switch (type) {
      case "native":
        return Icons.devices;
      case "network":
        return Icons.language;
      case "BLE":
        return Icons.bluetooth;
      default:
        return Icons.device_unknown;
    }
  }

  Future<void> _informUserAboutBluetoothPermissions(
      BuildContext context) async {
    if (_didAskForBluetoothPermissions) {
      return;
    }

    await showDialog<void>(
      context: context,
      barrierDismissible: false,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text(
              'Please Grant Bluetooth Permissions to discover BLE MIDI Devices.'),
          content: const Text(
              'In the next dialog we might ask you for bluetooth permissions.\n'
              'Please grant permissions to make bluetooth MIDI possible.'),
          actions: <Widget>[
            TextButton(
              child: const Text('Ok. I got it!'),
              onPressed: () {
                Navigator.of(context).pop();
              },
            ),
          ],
        );
      },
    );

    _didAskForBluetoothPermissions = true;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('FlutterMidiCommand Example'),
          actions: <Widget>[
            Switch(
                value: _iOSNetworkSessionEnabled,
                onChanged: (newValue) {
                  _midiCommand.setNetworkSessionEnabled(newValue);
                  setState(() {
                    _iOSNetworkSessionEnabled = newValue;
                  });
                }),
            Switch(
                value: _virtualDeviceActivated,
                onChanged: (newValue) {
                  setState(() {
                    _virtualDeviceActivated = newValue;
                  });
                  if (newValue) {
                    _midiCommand.addVirtualDevice(name: "Flutter MIDI Command");
                  } else {
                    _midiCommand.removeVirtualDevice(
                        name: "Flutter MIDI Command");
                  }
                }),
            Builder(builder: (context) {
              return IconButton(
                  onPressed: () async {
                    // Ask for bluetooth permissions
                    await _informUserAboutBluetoothPermissions(context);

                    // Start bluetooth
                    if (kDebugMode) {
                      print("start ble central");
                    }
                    await _midiCommand
                        .startBluetoothCentral()
                        .catchError((err) {
                      ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                        content: Text(err),
                      ));
                    });

                    if (kDebugMode) {
                      print("wait for init");
                    }
                    await _midiCommand
                        .waitUntilBluetoothIsInitialized()
                        .timeout(const Duration(seconds: 5), onTimeout: () {
                      if (kDebugMode) {
                        print("Failed to initialize Bluetooth");
                      }
                    });

                    // If bluetooth is powered on, start scanning
                    if (_midiCommand.bluetoothState ==
                        BluetoothState.poweredOn) {
                      _midiCommand
                          .startScanningForBluetoothDevices()
                          .catchError((err) {
                        if (kDebugMode) {
                          print("Error $err");
                        }
                      });
                      if (context.mounted) {
                        ScaffoldMessenger.of(context)
                            .showSnackBar(const SnackBar(
                          content: Text('Scanning for bluetooth devices ...'),
                        ));
                      }
                    } else {
                      final messages = {
                        BluetoothState.unsupported:
                            'Bluetooth is not supported on this device.',
                        BluetoothState.poweredOff:
                            'Please switch on bluetooth and try again.',
                        BluetoothState.poweredOn: 'Everything is fine.',
                        BluetoothState.resetting:
                            'Currently resetting. Try again later.',
                        BluetoothState.unauthorized:
                            'This app needs bluetooth permissions. Please open settings, find your app and assign bluetooth access rights and start your app again.',
                        BluetoothState.unknown:
                            'Bluetooth is not ready yet. Try again later.',
                        BluetoothState.other:
                            'This should never happen. Please inform the developer of your app.',
                      };
                      if (context.mounted) {
                        ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                          backgroundColor: Colors.red,
                          content: Text(messages[_midiCommand.bluetoothState] ??
                              'Unknown bluetooth state: ${_midiCommand.bluetoothState}'),
                        ));
                      }
                    }

                    if (kDebugMode) {
                      print("done");
                    }
                    // If not show a message telling users what to do
                    setState(() {});
                  },
                  icon: const Icon(Icons.refresh));
            }),
          ],
        ),
        bottomNavigationBar: Container(
          padding: const EdgeInsets.all(24.0),
          child: const Text(
            "Tap to connnect/disconnect, long press to control.",
            textAlign: TextAlign.center,
          ),
        ),
        body: Center(
          child: FutureBuilder(
            future: _midiCommand.devices,
            builder: (BuildContext context, AsyncSnapshot snapshot) {
              if (snapshot.hasData && snapshot.data != null) {
                var devices = snapshot.data as List<MidiDevice>;
                return ListView.builder(
                  itemCount: devices.length,
                  itemBuilder: (context, index) {
                    MidiDevice device = devices[index];

                    return ListTile(
                      title: Text(
                        device.name,
                        style: Theme.of(context).textTheme.headlineSmall,
                      ),
                      subtitle: Text(
                          "ins:${device.inputPorts.length} outs:${device.outputPorts.length}, ${device.id}, ${device.type}"),
                      leading: Icon(device.connected
                          ? Icons.radio_button_on
                          : Icons.radio_button_off),
                      trailing: Icon(_deviceIconForType(device.type)),
                      onLongPress: () {
                        _midiCommand.stopScanningForBluetoothDevices();
                        // Navigate to controller page or perform other actions
                      },
                      onTap: () {
                        if (device.connected) {
                          if (kDebugMode) {
                            print("disconnect");
                          }
                          _midiCommand.disconnectDevice(device);
                        } else {
                          if (kDebugMode) {
                            print("connect");
                          }
                          _midiCommand.connectToDevice(device).then((_) {
                            if (kDebugMode) {
                              print("device connected async");
                            }
                          }).catchError((err) {
                            ScaffoldMessenger.of(context).showSnackBar(SnackBar(
                                content: Text(
                                    "Error: ${(err as PlatformException?)?.message}")));
                          });
                        }
                      },
                    );
                  },
                );
              } else {
                return const CircularProgressIndicator();
              }
            },
          ),
        ),
      ),
    );
  }
}

以上就是flutter_midi_command插件的基本用法及示例代码。希望这能帮助你更好地理解和使用这个强大的MIDI控制工具!如果有任何问题或需要进一步的帮助,请随时提问。


更多关于Flutter MIDI控制插件flutter_midi_command的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter MIDI控制插件flutter_midi_command的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何使用Flutter MIDI控制插件 flutter_midi_command 的代码示例。这个示例将展示如何连接到MIDI设备、发送MIDI消息以及监听MIDI输入。

首先,你需要在你的 pubspec.yaml 文件中添加 flutter_midi_command 依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_midi_command: ^最新版本号  # 请替换为实际的最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来是示例代码:

import 'package:flutter/material.dart';
import 'package:flutter_midi_command/flutter_midi_command.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  MidiDevice? connectedDevice;
  String midiInput = "";

  @override
  void initState() {
    super.initState();
    // 请求MIDI权限
    MidiCommand.requestMidiAccess().then((access) {
      setState(() {
        access.inputs.forEach((input) {
          print("Available MIDI input: ${input.name}");
          // 这里可以添加选择设备的逻辑,例如通过UI让用户选择
          // connectedDevice = input.port;
        });

        access.outputs.forEach((output) {
          print("Available MIDI output: ${output.name}");
          // 自动连接第一个输出设备(仅示例,实际使用可能需要用户选择)
          if (access.outputs.isNotEmpty) {
            connectedDevice = access.outputs.first.port;
          }
        });
      });

      // 监听MIDI输入
      access.inputs.forEach((input) {
        input.onmidimessage = (message) {
          setState(() {
            midiInput = "Received MIDI message: ${message.data.join(', ')}";
          });
        };
      });
    });
  }

  void sendMidiMessage() {
    if (connectedDevice != null) {
      // 示例:发送一个简单的音符消息(C4,即中央C,MIDI音符60)
      List<int> noteOnMessage = [0x90, 60, 0x7F]; // Note On with velocity 127
      List<int> noteOffMessage = [0x80, 60, 0x00]; // Note Off with velocity 0

      MidiCommand.sendMidiMessage(connectedDevice!, noteOnMessage);
      Future.delayed(Duration(seconds: 1), () {
        MidiCommand.sendMidiMessage(connectedDevice!, noteOffMessage);
      });
    } else {
      print("No MIDI device connected.");
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter MIDI Control Example'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(midiInput),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: sendMidiMessage,
                child: Text('Send MIDI Note'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

解释

  1. 依赖添加:在 pubspec.yaml 中添加 flutter_midi_command 依赖。

  2. 权限请求:在 initState 方法中,使用 MidiCommand.requestMidiAccess() 请求MIDI访问权限。

  3. 设备选择:遍历可用的MIDI输入和输出设备,并打印它们的名称。实际应用中,你可能需要通过UI让用户选择设备。

  4. 监听MIDI输入:为每个MIDI输入设备设置 onmidimessage 回调,以监听MIDI消息。

  5. 发送MIDI消息:定义一个 sendMidiMessage 方法,用于向已连接的MIDI设备发送MIDI消息(例如,发送一个音符消息)。

  6. UI:使用Flutter的UI组件(如 TextElevatedButton)来显示接收到的MIDI消息并提供发送MIDI消息的按钮。

这个示例展示了基本的MIDI设备连接、消息发送和接收功能。实际应用中,你可能需要根据具体需求进行扩展和调整。

回到顶部