I've just discovered CABTMIDILocalPeripheralViewController
for iOS which
handles user settings for enabling Bluetooth MIDI discoverability. This is
fine and good but in order to integrate bluetooth into the rest of my app's
Network MIDI connectivity it would be good to be able to handle the
enabling directly from my app's code rather than rely on this opaque VC.
Does anyone know whether that is possible?
There's no public API to manage this functionality. Upon investigation with Instruments, it appears that the switch causes an instantiation of CBPeripheralManager
. I presume it sets up the device as a Bluetooth peripheral and manually channels the data to and from a MIDIEndpointRef
which is has also created.
In other words, there's no one-liner solution. If I go down this route further I'll post the code unless someone else wants to have a go...
UPDATE
The magic code...
- (instancetype)init
{
self = [super init];
if (self) {
_peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil];
}
return self;
}
//---------------------------------------------------------------------
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
{
if (peripheral.state != CBPeripheralManagerStatePoweredOn) {
return;
}
info(@"_peripheralManager powered on.");
// CBMutableCharacteristic *tx = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:RBL_TX_UUID] properties:CBCharacteristicPropertyWriteWithoutResponse value:nil permissions:CBAttributePermissionsWriteable];
//
rx = [[CBMutableCharacteristic alloc] initWithType:[CBUUID UUIDWithString:@"7772E5DB-3868-4112-A1A9-F2669D106BF3"] properties:CBCharacteristicPropertyRead|CBCharacteristicPropertyWriteWithoutResponse|CBCharacteristicPropertyNotify value:nil permissions:CBAttributePermissionsReadable|CBAttributePermissionsWriteable];
CBMutableService *s = [[CBMutableService alloc] initWithType:[CBUUID UUIDWithString:@"03B80E5A-EDE8-4B33-A751-6CE34EC4C700"] primary:YES];
s.characteristics = @[rx];
[_peripheralManager addService:s];
NSDictionary *advertisingData = @{CBAdvertisementDataLocalNameKey : BLE_NAME, CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:@"03B80E5A-EDE8-4B33-A751-6CE34EC4C700"]]};
[_peripheralManager startAdvertising:advertisingData];
}
It's those IDs that define a MIDI peripheral. More info:
Apple used to have a doc at
...which is no longer. I'd love to find an old copy as it turns out the receiver code (emulating CABTMIDICentralViewController
) is even harder to crack...
This is swift 5 version. this works fine.
import SpriteKit
import CoreBluetooth
class GameScene: SKScene {
var manager: CBPeripheralManager!
var service: CBMutableService!
// MIDI Service UUID
let serviceID = CBUUID(string: "03B80E5A-EDE8-4B33-A751-6CE34EC4C700")
// MIDI I/O Characteristic UUID
let characteristicID = CBUUID(string: "7772E5DB-3868-4112-A1A9-F2669D106BF3")
override func didMove(to view: SKView) {
super.didMove(to: view)
self.manager = CBPeripheralManager(delegate : self, queue : nil, options: nil)
}
}
extension GameScene: CBPeripheralManagerDelegate {
// Notify the peripheral state
func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
guard peripheral.state == .poweredOn else {
print("error: \(peripheral.state)")
return
}
print("bluetooth pwoer on")
// Add service and characteristic
self.service = CBMutableService(type: self.serviceID,
primary: true)
let properties: CBCharacteristicProperties = [.notify, .read, .writeWithoutResponse]
let permissions: CBAttributePermissions = [.readable, .writeable]
let characteristic = CBMutableCharacteristic(type: self.characteristicID,
properties: properties,
value: nil, permissions: permissions)
self.service.characteristics = [characteristic]
self.manager.add(self.service)
}
// Notify add the service
func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) {
guard (error == nil) else {
print("Add service failed")
return
}
print("Add service succeeded")
// Start advertising
let advertisementData = [CBAdvertisementDataLocalNameKey: "Penguin Drums",
CBAdvertisementDataServiceUUIDsKey: [self.serviceID]] as [String : Any]
manager.startAdvertising(advertisementData)
print("Service starts advertising!")
}
}
So I pieced together a pretty hacky solution for discovering which MIDI device the user clicked on once inside the CABTMIDICentralViewController. I'm not sure if this is a good idea—if Apple changes the internals of the controller it won't work anymore. Also I'm not sure if it's "legal" concerning App Store guidelines. Anyone know more info on this?
DPBleMidiDeviceManager.h:
#import <CoreAudioKit/CoreAudioKit.h>
@protocol MidiDeviceConnectedDelegate <NSObject>
-(void) onMidiDeviceConnected: (NSString*) deviceName;
@end
@interface DPBleMidiDeviceManager : CABTMIDICentralViewController
@property (weak, nonatomic) id<MidiDeviceConnectedDelegate> midiDeviceDelegate;
@end
DPBleMidiDeviceManager.m:
#import "DPBleMidiDeviceManager.h"
@implementation DPBleMidiDeviceManager
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"midi device selected %@", indexPath);
[super tableView:tableView didSelectRowAtIndexPath:indexPath];
// TODO: this is very bad. apple may change their internal API and this will break.
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell respondsToSelector:@selector(deviceNameLabel)]) {
UILabel* deviceLabel = [cell performSelector:@selector(deviceNameLabel)];
NSLog(@"midi device named %@", deviceLabel.text);
// must wait a couple seconds for it to actually connect.
[self performSelector:@selector(sendMidiDeviceConnected:) withObject:deviceLabel.text afterDelay: 3];
}
}
- (void) sendMidiDeviceConnected: (NSString*) deviceName
{
[self.midiDeviceDelegate onMidiDeviceConnected:deviceName];
}
@end
Then, in your parent view controller, you can get the result from the delegate and look for a new MIDI device matching that name:
...
DPBleMidiDeviceManager *controller = [DPBleMidiDeviceManager new];
controller.midiDeviceDelegate = self;
// now present the VC as usual
...
-(void) onMidiDeviceConnected: (NSString*) deviceName
{
[self connectMidiDevice: deviceName];
}
/**
Connects to a MIDI source with the given name,
and interprets all notes from that source as notes;
*/
- (void) connectMidiDevice: (NSString*) deviceName
{
NSLog(@"Connecting to MIDI device: %@", deviceName);
PGMidi* midi = [[PGMidi alloc] init];
if (midi != NULL) {
NSArray* sources = midi.sources;
for (PGMidiSource* src in sources) {
NSLog(@"Found midi source: %@", src.name);
if ([src.name containsString: deviceName]) {
NSLog(@"Connecting to midi source: %@", src.name);
[src addDelegate:self];
}
}
}
}
The only other alternative I can think of is to scan for MIDI devices before showing the controller, save the list of devices, then open the controller. When it closes, scan MIDI devices again, and diff that new list with the old one. Any new MIDI devices that show up would be the ones the user selected. Not sure why Apple didn't make this easier for us...
I think you could be looking for this : CABTMIDICentralViewController more info on this page : https://developer.apple.com/library/ios/qa/qa1831/_index.html basicaly this helps you scan and connect to devices through your app. I'm not sure if you only want to be discovered or also to be the one that scans. I hope this helps
CABTMIDILocalPeripheralViewController
, which is definitely the one to use in my case. Either way, this page doesn't describe how to accomplish the task without using these VCs which is what I want to know. –
Warnock CABT...
VCs –
Warnock © 2022 - 2024 — McMap. All rights reserved.
CABTMIDI...
api as far as I can see – Warnock