I'm writing a Foundation tool for Mac and trying to detect when Apple devices are connected and disconnected via USB. I found some help in this post along with the USBPrivateDataSample -- but it seems to only be working if I provide both a vendor ID and a product ID. I'd like to eliminate the product ID and find detect all USB events on Apple devices (vendor ID 1452). Any help here?
Here's my code that doesn't seem to detect any devices:
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOMessage.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#define kMyVendorID 1452
int list_devices(void);
int list_devices(void){
CFMutableDictionaryRef matchingDict;
io_iterator_t iter;
kern_return_t kr;
io_service_t device;
CFNumberRef numberRef;
long usbVendor = kMyVendorID;
/* set up a matching dictionary for the class */
matchingDict = IOServiceMatching(kIOUSBDeviceClassName); // Interested in instances of class
// IOUSBDevice and its subclasses
if (matchingDict == NULL) {
fprintf(stderr, "IOServiceMatching returned NULL.\n");
return -1;
}
// We are interested in all USB devices (as opposed to USB interfaces). The Common Class Specification
// tells us that we need to specify the idVendor, idProduct, and bcdDevice fields, or, if we're not interested
// in particular bcdDevices, just the idVendor and idProduct. Note that if we were trying to match an
// IOUSBInterface, we would need to set more values in the matching dictionary (e.g. idVendor, idProduct,
// bInterfaceNumber and bConfigurationValue.
// Create a CFNumber for the idVendor and set the value in the dictionary
numberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &usbVendor);
CFDictionarySetValue(matchingDict,
CFSTR(kUSBVendorID),
numberRef);
CFRelease(numberRef);
numberRef = NULL;
/* Now we have a dictionary, get an iterator.*/
kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter);
if (kr != KERN_SUCCESS)
return -1;
/* iterate */
while ((device = IOIteratorNext(iter)))
{
io_name_t deviceName;
CFStringRef deviceNameAsCFString;
/* do something with device, eg. check properties */
/* ... */
/* And free the reference taken before continuing to the next item */
// Get the USB device's name.
kr = IORegistryEntryGetName(device, deviceName);
if (KERN_SUCCESS != kr) {
deviceName[0] = '\0';
}
deviceNameAsCFString = CFStringCreateWithCString(kCFAllocatorDefault, deviceName,
kCFStringEncodingASCII);
// Dump our data to stderr just to see what it looks like.
fprintf(stderr, "deviceName: ");
CFShow(deviceNameAsCFString);
IOObjectRelease(device);
}
/* Done, release the iterator */
IOObjectRelease(iter);
return 1;
}
int main(int argc, char* argv[])
{
while(1){
list_devices();
sleep(1);
}
return 0;
}
Of note: If I add a product ID to the matchingDict and plug in such a device it will find the device no problem (without changing the vendor ID). But I can't find it with the vendor ID alone.
To get list of all products that belongs to particular vendor you can use wildcards in product ID field .A sample matching condition is as below:
long vid = 1452; //Apple vendor ID
CFNumberRef refVendorId = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &vid);
CFDictionarySetValue (matchingDict, CFSTR ("idVendor"), refVendorId);
CFRelease(refVendorId);
//all product by same vendor
CFDictionarySetValue (matchingDict, CFSTR ("idProduct"), CFSTR("*"));
Creating a dictionary filter with only a VID entry should match all PIDs for that vendor. I'd recommend registering for device insertion callbacks instead of polling in your own code. Let the OS handle detection and notify your app asynchronously.
This code works for me:
#import "IOKit/hid/IOHIDManager.h"
#include <IOKit/usb/IOUSBLib.h>
#implementation MyApp
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
IOHIDManagerRef HIDManager = IOHIDManagerCreate(kCFAllocatorDefault,
kIOHIDOptionsTypeNone);
CFMutableDictionaryRef matchDict = CFDictionaryCreateMutable(
kCFAllocatorDefault,
2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
int vid = 0x1234; // ToDo: Place your VID here
CFNumberRef vid_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &vid);
CFDictionarySetValue(matchDict, CFSTR(kUSBVendorID), vid_num);
CFRelease(vid_num);
IOHIDManagerSetDeviceMatching(HIDManager, matchDict);
// Here we use the same callback for insertion & removal.
// Use separate handlers if desired.
IOHIDManagerRegisterDeviceMatchingCallback(HIDManager, Handle_UsbDetectionCallback, (__bridge void*)self);
IOHIDManagerRegisterDeviceRemovalCallback(HIDManager, Handle_UsbDetectionCallback, (__bridge void*)self);
IOHIDManagerScheduleWithRunLoop(HIDManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
IOHIDManagerOpen(HIDManager, kIOHIDOptionsTypeNone);
}
// New USB device specified in the matching dictionary has been added (callback function)
static void Handle_UsbDetectionCallback(void *inContext, IOReturn inResult, void *inSender, IOHIDDeviceRef inIOHIDDeviceRef) {
//ToDo: Code for dealing with the USB device
}
#end
Related
If I go to Settings on a Windows 10 (1803) computer, I have access to a page ("App Volume and Device Preferences") that lets me set the default input and output device for a running application.
How can I set these options programmatically?
Related:
Set audio endpoint devices application specific (programmatically)Seems to refer to a more specific problem, and is unanswered.
Controlling “App volume and device preferences” menu, from settings, with python 3.6, or any language(Windows). Automatic App audio device switchingToo broad, wrong site.
IAudioSessionControl2 and related familyAllows reading the device and setting volume and mute, but doesn't seem to allow changing the device
GitHub issue page for SoundSwitch also looking for APIImplies API is undocumented by design (default device being controlled by user)
Here you can enumerate all the playback devices
#include <windows.h>
#include <mmsystem.h>
#include <iostream>
using namespace std;
#pragma comment(lib, "Winmm.lib")
int main()
{
int nSoundCardCount = waveOutGetNumDevs();
for (int i = 0; i < nSoundCardCount; i++)
{
WAVEOUTCAPS woc;
waveOutGetDevCaps(i, &woc, sizeof(woc));
cout << woc.szPname << endl;
}
system("pause");
return 0;
}
Here you need to use PolicyConfig.h and SetDefaultAudioPlaybackDevice to add .h files and interfaces. Refer to this project
1.Add the header file PolicyConfig.h
2.Add the head file and interface.
#include "Mmdeviceapi.h"
#include "PolicyConfig.h"
#include "Propidl.h"
#include "Functiondiscoverykeys_devpkey.h"
HRESULT SetDefaultAudioPlaybackDevice( LPCWSTR devID )
{
IPolicyConfigVista *pPolicyConfig;
ERole reserved = eConsole;
HRESULT hr = CoCreateInstance(__uuidof(CPolicyConfigVistaClient),
NULL, CLSCTX_ALL, __uuidof(IPolicyConfigVista), (LPVOID *)&pPolicyConfig);
if (SUCCEEDED(hr))
{
hr = pPolicyConfig->SetDefaultEndpoint(devID, reserved);
pPolicyConfig->Release();
}
return hr;
}
3.Use the above interface to write a function to set the default output device.
It's MFC Project. Maybe you need to change.
Which output device needs to be set, you can modify the content of the macro yourself.
I get the name of output device using waveOutGetDevCaps()
//Set the default audio playback device
#define DEF_AUDIO_NAME _T("Speakers (2- Logitech USB Heads") //modify it, my device is Speakers (2- Logitech USB Heads
void InitDefaultAudioDevice()
{
HRESULT hr = CoInitialize(NULL);
if (SUCCEEDED(hr))
{
IMMDeviceEnumerator *pEnum = NULL;
// Create a multimedia device enumerator.
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnum);
if (SUCCEEDED(hr))
{
//Determine if it is the default audio device
bool bExit = false;
IMMDevice *pDefDevice = NULL;
hr = pEnum->GetDefaultAudioEndpoint(eRender, eMultimedia,&pDefDevice);
if (SUCCEEDED(hr))
{
IPropertyStore *pStore;
hr = pDefDevice->OpenPropertyStore(STGM_READ, &pStore);
if (SUCCEEDED(hr))
{
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
hr = pStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
if (SUCCEEDED(hr))
{
CString strTmp = friendlyName.pwszVal;
if (strTmp.Find(DEF_AUDIO_NAME) != -1)
{
bExit = true;
}
PropVariantClear(&friendlyName);
}
pStore->Release();
}
pDefDevice->Release();
}
if (bExit)
{
pEnum->Release();
return;
}
IMMDeviceCollection *pDevices;
// Enumerate the output devices.
hr = pEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pDevices);
if (SUCCEEDED(hr))
{
UINT count;
pDevices->GetCount(&count);
if (SUCCEEDED(hr))
{
for (int i = 0; i < count; i++)
{
bool bFind = false;
IMMDevice *pDevice;
hr = pDevices->Item(i, &pDevice);
if (SUCCEEDED(hr))
{
LPWSTR wstrID = NULL;
hr = pDevice->GetId(&wstrID);
if (SUCCEEDED(hr))
{
IPropertyStore *pStore;
hr = pDevice->OpenPropertyStore(STGM_READ, &pStore);
if (SUCCEEDED(hr))
{
PROPVARIANT friendlyName;
PropVariantInit(&friendlyName);
hr = pStore->GetValue(PKEY_Device_FriendlyName, &friendlyName);
if (SUCCEEDED(hr))
{
// if no options, print the device
// otherwise, find the selected device and set it to be default
CString strTmp = friendlyName.pwszVal;
if (strTmp.Find(DEF_AUDIO_NAME) != -1)
{
SetDefaultAudioPlaybackDevice(wstrID);
bFind = true;
}
PropVariantClear(&friendlyName);
}
pStore->Release();
}
}
pDevice->Release();
}
if (bFind)
{
break;
}
}
}
pDevices->Release();
}
pEnum->Release();
}
}
CoUninitialize();
}
This sample can only change the output of Master volume. I don't know whether it can meet your requirements? If you need to change other apps, you have to explore for a while.
So I have been using SoundVolumeView for a while that let me mute and unmute my mic for meeting with a command line and I have discovered recently (because of OBS and monitoring audio) that it can also change device for an app or global default device
http://www.nirsoft.net/utils/sound_volume_view.html
And using /SetDefault and /SetAppDefault as shown in the doc example to the bottom of the page
I have put that in a batch script and bind a macro to my keyboard and it's doing a good job so far :)
I am trying to use the SystemConfiguration on mac OS to get a notification when a new network interface appears on the mac and a new IP address is assigned for it.
I set it up to watch for the system configuration key State:/Network/Interface and it works that I get a notification whenever a new network interface appears or disappears.
However I would like to get a notification whenever the IPv4 address is assigned on the new network interface (e.g. by DHCP). I know that the key State:/Network/Interface/en0/IPv4 is holding the IPv4 address for the en0 interface. But using regular expressions as depicted in the man page for all IPv4 addresses State:/Network/Interface/.*/IPv4 does not work for the new interface.
I have put together a small minimal code example on github, however one can also use the scutil command line tool.
Link to demo repository
main.c
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
/* Callback used if a configuration change on monitored keys was detected.
*/
void dynamicStoreCallback(SCDynamicStoreRef store, CFArrayRef changedKeys, void* __nullable info) {
CFIndex count = CFArrayGetCount(changedKeys);
for (CFIndex i=0; i<count; i++) {
NSLog(#"Key \"%#\" was changed", CFArrayGetValueAtIndex(changedKeys, i));
}
}
int main(int argc, const char * argv[]) {
NSArray *SCMonitoringInterfaceKeys = #[#"State:/Network/Interface.*"];
#autoreleasepool {
SCDynamicStoreRef dsr = SCDynamicStoreCreate(NULL, CFSTR("network_interface_detector"), &dynamicStoreCallback, NULL);
SCDynamicStoreSetNotificationKeys(dsr, CFBridgingRetain(SCMonitoringInterfaceKeys), NULL);
CFRunLoopAddSource(CFRunLoopGetCurrent(), SCDynamicStoreCreateRunLoopSource(NULL, dsr, 0), kCFRunLoopDefaultMode);
NSLog(#"Starting RunLoop...");
while([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
}
return 0;
}
With the help of some developer colleagues I found out what went wrong. The signature for the SCDynamicStoreSetNotificationKeys function is as follows:
Boolean SCDynamicStoreSetNotificationKeys (SCDynamicStoreRef store,
CFArrayRef __nullable keys,
CFArrayRef __nullable patterns
)
Meaning that I have to set the pattern separately from the keys which act as the root of the tree under which the pattern matching will occur. Here is the modified version of my main.m:
int main(int argc, const char * argv[]) {
NSArray *SCMonitoringInterfaceKeys = #[#"State:/Network/Interface"];
NSArray *patterns = #[#"en\\d*/IPv4"];
#autoreleasepool {
SCDynamicStoreRef dsr = SCDynamicStoreCreate(NULL, CFSTR("network_interface_detector"), &dynamicStoreCallback, NULL);
SCDynamicStoreSetNotificationKeys(dsr, CFBridgingRetain(SCMonitoringInterfaceKeys), CFBridgingRetain(patterns));
CFRunLoopAddSource(CFRunLoopGetCurrent(), SCDynamicStoreCreateRunLoopSource(NULL, dsr, 0), kCFRunLoopDefaultMode);
NSLog(#"Starting RunLoop...");
while([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
}
return 0;
}
I have included the solution into the solved branch of the repo.
I am building a list of the OSX audio devices.
I am using CoreAudio for getting the list
and I would like also to get the vendor-id and product-id for each one of them.
Any idea how to do it?
You can use selector = kAudioDevicePropertyModelUID
This will contain both vid and pid and you will be able to extract it from there
AudioObjectPropertyAddress address = {
kAudioDevicePropertyModelUID,
kAudioObjectPropertyScopeGlobal,
kAudioObjectPropertyElementMaster
};
And query for the property data using: AudioObjectGetPropertyData
You’ll need to first look up the audio device in the I/O registry and get the VID/PID from there, as it was outlined a while ago on the CoreAudio mailing list:
#include <IOKit/IOKitLib.h>
#include <IOKit/audio/IOAudioDefines.h>
#include <IOKit/usb/USB.h>
AudioDeviceID deviceID = ...
// get device UID for AudioDevice ID
AudioObjectPropertyAddress address = {kAudioDevicePropertyDeviceUID, kAudioObjectPropertyScopeOutput, 0};
UInt32 size = sizeof(CFStringRef);
CFStringRef uid = NULL;
OSStatus err = AudioObjectGetPropertyData(deviceID, &address, 0, NULL, &size, &uid);
NSNumber *vid, *pid;
// find matching IOAudioEngine object
io_iterator_t it;
if (IOServiceGetMatchingServices(kIOMasterPortDefault, IOServiceMatching(kIOAudioEngineClassName), &it) == KERN_SUCCESS) {
io_service_t service;
while ((service = IOIteratorNext(it)) != 0) {
CFStringRef uniqueID = IORegistryEntryCreateCFProperty(service, CFSTR(kIOAudioEngineGlobalUniqueIDKey), kCFAllocatorDefault, 0);
if (uniqueID && CFEqual(uniqueID, uid)) {
vid = CFBridgingRelease(IORegistryEntryCreateCFProperty(service, CFSTR(kUSBVendorID), kCFAllocatorDefault, 0));
pid = CFBridgingRelease(IORegistryEntryCreateCFProperty(service, CFSTR(kUSBProductID), kCFAllocatorDefault, 0));
}
CFRelease(uniqueID);
if (vid || pid) break;
}
IOObjectRelease(it);
}
The same technique can also be used to get other USB properties of the audio device like the USB location ID (kUSBDevicePropertyLocationID), e.g. to correlate audio with CoreMIDI devices.
I've been following the examples on Audio Units from Learning Core Audio book by Adamson and Avila. I'm getting the above error for some reason. I #include <CoreAudio/CoreAudio.h> even just to make sure I am importing the possible libraries for Audio, and made sure to configure the "Link Binary with Libraries" under the "Build Phases" part of target. I even changed the Base SDK to OSX 10.7 (as opposed to the default 10.8) to see what happens, but no cigar. And according to the documentation, the Speech Synthesis API is not fully deprecated in anyway -- some functions are, however. My MacBook is running 10.7.5. XCode is Version 4.6 (4H127.
Below I put a comment on where I got the error in CAPS. Any ideas?
//
// main.c
// CAsamplerSynthesisGraph
//
// Created by Edderic Ugaddan on 6/25/13.
// Copyright (c) 2013 Edderic Ugaddan. All rights reserved.
//
//#include <CoreFoundation/CoreFoundation.h>
#include <AudioUnit/AudioUnit.h>
#include <AudioToolbox/AudioToolbox.h>
#include <CoreAudio/CoreAudio.h>
// #define PART_II
#pragma mark user-data struct
// Insert Listing 7.19 here
typedef struct MyAUGraphPlayer {
AUGraph graph;
AudioUnit samplerAU;
} MyAUGraphPlayer;
#pragma mark utility functions
// Insert Listing 4.2 here
static void CheckError(OSStatus error, const char *operation) {
if (error == noErr) return;
char errorString[20];
// See if it appears to be a 4-char-code.
*(UInt32 *)*(errorString + 1) = CFSwapInt32HostToBig(error);
if (isprint(errorString[1]) && isprint(errorString[2]) &&
isprint(errorString[3]) && isprint(errorString[4])) {
errorString[0] = errorString[5] = '\'';
errorString[6] = '\0';
}
else {
// No, format it as an integer.
sprintf(errorString, "%d", (int) error);
fprintf(stderr, "Error: %s (%s)\n", operation, errorString);
exit(1);
}
}
void CreateMyAUGraph(MyAUGraphPlayer *player) {
// Insert Listing 7.21 here
// Create a new graph
CheckError(NewAUGraph(&player->graph),
"NewAUGraph failed");
// Generates a description that matches our output device (speakers)
AudioComponentDescription outputcd = {0};
outputcd.componentType = kAudioUnitType_Output;
outputcd.componentSubType = kAudioUnitSubType_DefaultOutput;
outputcd.componentManufacturer = kAudioUnitManufacturer_Apple;
// Adds a node with above description to the graph
AUNode outputNode;
CheckError(AUGraphAddNode(player->graph,
&outputcd,
&outputNode),
"AUGraphAddNode[kAudioUnitSubType_DefaultOutput] failed");
// Generates a description that will match a generator AU
// of type: sampler synthesizer
AudioComponentDescription samplercd = {0};
samplercd.componentType = kAudioUnitType_Generator;
samplercd.componentSubType = kAudioUnitSubType_SpeechSynthesis; // I GET ERROR HERE
samplercd.componentManufacturer = kAudioUnitManufacturer_Apple;
// Adds a node with above description to the graph
AUNode samplerNode;
CheckError(AUGraphAddNode(player->graph,
&samplercd,
&samplerNode),
"AUGraphAddNode[kAudioUnitSubType_samplerSynthesis] failed");
// Opening the graph opens all contained audio units, but
// does not allocate any resources yet
CheckError(AUGraphOpen(player->graph),
"AUGraphOpen failed");
// Gets the reference to the AudioUnit object for the
// sampler graph node
CheckError(AUGraphNodeInfo(player->graph,
samplerNode,
NULL,
&player->samplerAU),
"AUGraphNodeInfo failed");
#ifdef PART_II
// Insert Listing 7.24 - 7.26 here
#else
// Insert Listing 7.22 here
// Connect the output source of the sampler synthesis AU
// to the input source of the output node
CheckError(AUGraphConnectNodeInput(player->graph,
samplerNode,
0,
outputNode,
0),
"AUGraphConnectNodeInput failed");
#endif
}
// Replace with listing 7.23
void PrepareSamplerAU(MyAUGraphPlayer *player) {
// Sampler
}
#pragma mark main function
// Replace with listing 7.20
int main(int argc, const char * argv[])
{
MyAUGraphPlayer player = {0};
// Build a basic sampler->speakers graph
CreateMyAUGraph(&player);
// Configure the sampler synthesizer
PrepareSamplerAU(&player);
// Start playing
CheckError(AUGraphStart(player.graph),
"AUGraphStart failed");
// Sleep a while so the sampler can play out
usleep ((int)(10 * 1000. * 1000.));
// Cleanup
AUGraphStop(player.graph);
AUGraphUninitialize(player.graph);
AUGraphClose(player.graph);
return 0;
}
kAudioUnitSubType_SpeechSynthesis is declared in SpeechSynthesis.framework, which lives within the ApplicationServices.framework umbrella framework, so you should #import < ApplicationServices.framework>.
Here is an extremely simple CoreMIDI OS X application that sends MIDI data. The problem is that it doesn't work. It compiles fine, and runs. It reports no errors, and does not crash. The Source created becomes visible in MIDI Monitor. However, no MIDI data comes out.
Could somebody let me know what I'm doing wrong here?
#include <CoreMIDI/CoreMIDI.h>
int main(int argc, char *args[])
{
MIDIClientRef theMidiClient;
MIDIEndpointRef midiOut;
MIDIPortRef outPort;
char pktBuffer[1024];
MIDIPacketList* pktList = (MIDIPacketList*) pktBuffer;
MIDIPacket *pkt;
Byte midiDataToSend[] = {0x91, 0x3c, 0x40};
int i;
MIDIClientCreate(CFSTR("Magical MIDI"), NULL, NULL,
&theMidiClient);
MIDISourceCreate(theMidiClient, CFSTR("Magical MIDI Source"),
&midiOut);
MIDIOutputPortCreate(theMidiClient, CFSTR("Magical MIDI Out Port"),
&outPort);
pkt = MIDIPacketListInit(pktList);
pkt = MIDIPacketListAdd(pktList, 1024, pkt, 0, 3, midiDataToSend);
for (i = 0; i < 100; i++) {
if (pkt == NULL || MIDISend(outPort, midiOut, pktList)) {
printf("failed to send the midi.\n");
} else {
printf("sent!\n");
}
sleep(1);
}
return 0;
}
You're calling MIDISourceCreate to create a virtual MIDI source.
This means that your source will appear in other apps' MIDI setup UI, and that those apps can choose whether or not to listen to your source. Your MIDI will not get sent to any physical MIDI ports, unless some other app happens to channel it there. It also means that your app has no choice as to where the MIDI it's sending goes. I'm assuming that's what you want.
The documentation for MIDISourceCreate says:
After creating a virtual source, use MIDIReceived to transmit MIDI messages from your virtual source to any clients connected to the virtual source.
So, do two things:
Remove the code that creates the output port. You don't need it.
change MIDISend(outPort, midiOut, pktList) to: MIDIReceived(midiOut, pktlist).
That should solve your problem.
So what are output ports good for? If you wanted to direct your MIDI data to a specific destination -- maybe a physical MIDI port -- you would NOT create a virtual MIDI source. Instead:
Call MIDIOutputPortCreate() to make an output port
Use MIDIGetNumberOfDestinations() and MIDIGetDestination() to get the list of destinations and find the one you're interested in.
To send MIDI to one destination, call MIDISend(outputPort, destination, packetList).
I'm just leaving this here for my own reference. It's a full example based 100% on yours, but including the other side (receiving), my bad C code and the accepted answer's corrections (of course).
#import "AppDelegate.h"
#implementation AppDelegate
#synthesize window = _window;
#define NSLogError(c,str) do{if (c) NSLog(#"Error (%#): %u:%#", str, (unsigned int)c,[NSError errorWithDomain:NSMachErrorDomain code:c userInfo:nil]); }while(false)
static void spit(Byte* values, int length, BOOL useHex) {
NSMutableString *thing = [#"" mutableCopy];
for (int i=0; i<length; i++) {
if (useHex)
[thing appendFormat:#"0x%X ", values[i]];
else
[thing appendFormat:#"%d ", values[i]];
}
NSLog(#"Length=%d %#", length, thing);
}
- (void) startSending {
MIDIEndpointRef midiOut;
char pktBuffer[1024];
MIDIPacketList* pktList = (MIDIPacketList*) pktBuffer;
MIDIPacket *pkt;
Byte midiDataToSend[] = {0x91, 0x3c, 0x40};
int i;
MIDISourceCreate(theMidiClient, CFSTR("Magical MIDI Source"),
&midiOut);
pkt = MIDIPacketListInit(pktList);
pkt = MIDIPacketListAdd(pktList, 1024, pkt, 0, 3, midiDataToSend);
for (i = 0; i < 100; i++) {
if (pkt == NULL || MIDIReceived(midiOut, pktList)) {
printf("failed to send the midi.\n");
} else {
printf("sent!\n");
}
sleep(1);
}
}
void ReadProc(const MIDIPacketList *packetList, void *readProcRefCon, void *srcConnRefCon)
{
const MIDIPacket *packet = &packetList->packet[0];
for (int i = 0; i < packetList->numPackets; i++)
{
NSData *data = [NSData dataWithBytes:packet->data length:packet->length];
spit((Byte*)data.bytes, data.length, YES);
packet = MIDIPacketNext(packet);
}
}
- (void) setupReceiver {
OSStatus s;
MIDIEndpointRef virtualInTemp;
NSString *inName = [NSString stringWithFormat:#"Magical MIDI Destination"];
s = MIDIDestinationCreate(theMidiClient, (__bridge CFStringRef)inName, ReadProc, (__bridge void *)self, &virtualInTemp);
NSLogError(s, #"Create virtual MIDI in");
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
MIDIClientCreate(CFSTR("Magical MIDI"), NULL, NULL,
&theMidiClient);
[self setupReceiver];
[self startSending];
}
#end
A little detail that others are skipping: the time parameter of MIDIPacketListAdd is important for some musical apps.
Here is an example of how you can retrieve it:
#import <mach/mach_time.h>
MIDITimeStamp midiTime = mach_absolute_time();
Source: Apple Documentation
And then, applied to the other examples here:
pktBuffer[1024];
MIDIPacketList *pktList = (MIDIPacketList*)pktBuffer;
MIDIPacket *pktPtr = MIDIPacketListInit(pktList);
MIDITimeStamp midiTime = mach_absolute_time();
Byte midiDataToSend[] = {0x91, 0x3c, 0x40};
pktPtr = MIDIPacketListAdd(pktList, sizeof(pktList), pktPtr, midiTime, sizeof(midiDataToSend), midiDataToSend);
Consider your own midi client creating application may crash or the host sending midi can crash also. You can handle this easier with checking if an client/destination exists already then doing this by handling singleton allocations. When your Midi client is existing but not working then this is because you need to tell CoreMidi what your costume made client is capable of processing and what latency it will have specially when the host sending client is using timestamps a lot (aka ableton and other).
in your .h file
#import <CoreMIDI/CoreMIDI.h>
#import <CoreAudio/HostTime.h>
#interface YourVirtualMidiHandlerObject : NSObject
#property (assign, nonatomic) MIDIClientRef midi_client;
#property (nonatomic) MIDIEndpointRef outSrc;
#property (nonatomic) MIDIEndpointRef inSrc;
- (id)initWithVirtualSourceName:(NSString *)clientName;
#end
in your .m file
#interface YourVirtualMidiHandlerObject () {
MIDITimeStamp midiTime;
MIDIPacketList pktList;
}
#end
You would prepare initiation of your virtual client in the following way
also in your .m file
#implementation YourVirtualMidiHandlerObject
// this you can call in dealloc or manually
// else where when you stop working with your virtual client
-(void)teardown {
MIDIEndpointDispose(_inSrc);
MIDIEndpointDispose(_outSrc);
MIDIClientDispose(_midi_client);
}
- (id)initWithVirtualSourceName:(NSString *)clientName {
if (self = [super init]) {
OSStatus status = MIDIClientCreate((__bridge CFStringRef)clientName, (MIDINotifyProc)MidiNotifyProc, (__bridge void *)(self), &_midi_client);
BOOL isSourceLoaded = NO;
BOOL isDestinationLoaded = NO;
ItemCount sourceCount = MIDIGetNumberOfSources();
for (ItemCount i = 0; i < sourceCount; ++i) {
_outSrc = MIDIGetSource(i);
if ( _outSrc != 0 ) {
if ([[self getMidiDisplayName:_outSrc] isEqualToString:clientName] && !isSourceLoaded) {
isSourceLoaded = YES;
break; //stop looping thru sources if it is existing
}
}
}
ItemCount destinationCount = MIDIGetNumberOfDestinations();
for (ItemCount i = 0; i < destinationCount; ++i) {
_inSrc = MIDIGetDestination(i);
if (_inSrc != 0) {
if ([[self getMidiDisplayName:_inSrc] isEqualToString:clientName] && !isDestinationLoaded) {
isDestinationLoaded = YES;
break; //stop looping thru destinations if it is existing
}
}
}
if(!isSourceLoaded) {
//your costume source needs to tell CoreMidi what it is handling
MIDISourceCreate(_midi_client, (__bridge CFStringRef)clientName, &_outSrc);
MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyMaxTransmitChannels, 16);
MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsProgramChanges, 1);
MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsNotes, 1);
// MIDIObjectSetIntegerProperty(_outSrc, kMIDIPropertyTransmitsClock, 1);
isSourceLoaded = YES;
}
if(!isDestinationLoaded) {
//your costume destination needs to tell CoreMidi what it is handling
MIDIDestinationCreate(_midi_client, (__bridge CFStringRef)clientName, midiRead, (__bridge void *)(self), &_inSrc);
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyAdvanceScheduleTimeMuSec, 1); // consider more 14ms in some cases
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesClock, 1);
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesNotes, 1);
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesProgramChanges, 1);
MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyMaxReceiveChannels, 16);
// MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesMTC, 1);
// MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesBankSelectMSB, 1);
// MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertyReceivesBankSelectLSB, 1);
// MIDIObjectSetIntegerProperty(_inSrc, kMIDIPropertySupportsMMC, 1);
isDestinationLoaded = YES;
}
if (!isDestinationLoaded || !isSourceLoaded) {
if (status != noErr ) {
NSLog(#"Failed creation of virtual Midi client \"%#\", so disposing the client!",clientName);
MIDIClientDispose(_midi_client);
}
}
}
return self;
}
// Returns the display name of a given MIDIObjectRef as an NSString
-(NSString *)getMidiDisplayName:(MIDIObjectRef)obj {
CFStringRef name = nil;
if (noErr != MIDIObjectGetStringProperty(obj, kMIDIPropertyDisplayName, &name)) return nil;
return (__bridge NSString *)name;
}
For those of you trying to read tempo (midi transport) and set the propertys for the virtual destination in your creation process...
Don't forget timestamps are send with the packets but a packet can contain several commands of same type, even several clock commands. When constructing a clock counter to find bpm tempo you will have to consider counting at least 12 of them before calculating. When you go only with 3 of them you are actually measuring your own buffer read processing latency instead of the real timestamps.
Your reading procedure (callback) will handle timestamps if the midi sender fails to set those properly with...
void midiRead(const MIDIPacketList * pktlist, void * readProcRefCon, void * srcConnRefCon) {
const MIDIPacket *pkt = pktlist->packet;
for ( int index = 0; index < pktlist->numPackets; index++, pkt = MIDIPacketNext(pkt) ) {
MIDITimeStamp timestamp = pkt->timeStamp;
if ( !timestamp ) timestamp = mach_absolute_time();
if ( pkt->length == 0 ) continue;
const Byte *p = &pkt->data[0];
Byte functionalDataGroup = *p & 0xF0;
// Analyse the buffered bytes in functional groups is faster
// like 0xF will tell it is clock/transport midi stuff
// go in detail after this will shorten the processing
// and it is easier to read in code
switch (functionalDataGroup) {
case 0xF : {
// in here read the exact Clock command
// 0xF8 = clock
}
break;
case ... : {
// do other nice grouped stuff here, like reading notes
}
break;
default : break;
}
}
}
dont forget the client needs a callback where internal notifications are handled.
void MidiNotifyProc(const MIDINotification* message, void* refCon) {
// when creation of virtual client fails we despose the whole client
// meaning unless you need it you can ignore added/removed notifications
if (message->messageID != kMIDIMsgObjectAdded &&
message->messageID != kMIDIMsgObjectRemoved) return;
// reactions to other midi notications you gonna trigger here..
}
then you can send midi with...
-(void)sendMIDICC:(uint8_t)cc Value:(uint8_t)v ChZeroToFifteen:(uint8_t)ch {
MIDIPacket *packet = MIDIPacketListInit(&pktList);
midiTime = packet->timeStamp;
unsigned char ctrl[3] = { 0xB0 + ch, cc, v };
while (1) {
packet = MIDIPacketListAdd(&pktList, sizeof(pktList), packet, midiTime, sizeof(ctrl), ctrl);
if (packet != NULL) break;
// create an extra packet to fill when it failed before
packet = MIDIPacketListInit(&pktList);
}
// OSStatus check = // you dont need it if you don't check failing
MIDIReceived(_outSrc, &pktList);
}