CoreMIDI: midiReadProc only receives the first packet of 3 packets with the same timestamp - macos

I am working on a small program to monitor midi output from NI Maschine 2. (Idea is to add program change messages to some Parts in NI Machine and use them to trigger VJ-efects and some other stuff)
The following test case gives the problem. the test Part in NI Machine has one note and two program change messages on the first beat and a second note a 16th later.
When i hit start and capture the output with a MIDI Monitor tool i see:
This is correct. Exactly what Maschine is sending.
- notice the two Control Changes and the next Note On packets have the same timestamp.
When i do the same with my simple virtual client (see code below) i get this:
as you can see the second Control Change and the Note On packets are missing!
notice also the second an third line (Song Position and Continue) also have the same timestamp and are in both cases received.
If you took the trouble to read until this point you will understand the problem.
I know the big difference between my simpel virtual client and the MIDI Monitor program is the use of a CoreMidi Service plugin for 'spying' the midi output.
Is this just a limitation of CoreMidi or am i missing something?
Below the code for the virtual client:
It is stripped down to the basic needs to receive something from NI Mashine.
The init sets up the virtual client, destination and sets the UniqueID.
The readproc is producing the NSLOG messages with the missing packets as shown above.
Any suggestions are very appreciated
//
// VirtualClient.m
// testMidiReadProc
//
// Created by Rob Keeris on 02/06/15.
// Copyright (c) 2015 Connector. All rights reserved.
//
#import "VirtualClient.h"
#implementation VirtualClient
SInt32 virtualinUniqueId = 1234567893;
MIDIClientRef client;
MIDIEndpointRef virtualIn;
NSString * midiTypeName(Byte midiType){
switch (midiType) {
case 0x80: return #"Note Off";
case 0x90: return #"Note On";
case 0xB0: return #"ControlChange";
case 0xC0: return #"ProgramChange";
case 0xF2: return #"SongPosition";
case 0xF8: return #"Clock";
case 0xFA: return #"Start";
case 0xFB: return #"Continue";
case 0xFC: return #"Stop";
case 0x00: return #"InvalidType";
default: return [NSString stringWithFormat:#"Unlisted midiType 0x%02x",midiType];
}
}
void midiReadProc (const MIDIPacketList *list, void *procRef, void *srcRef) {
const MIDIPacket *packet = &list->packet[0]; // ?defined as const to avoid compiler warnings?
for (int i = 0; i < list->numPackets; i++) {
if (packet->data[0] != 0xF8){ // filter out Clock messages
NSLog(#"%llu packet(%i of %i) %#(0x%02x) 0x%02x 0x%02x",
packet->timeStamp,i+1,list->numPackets,midiTypeName(packet->data[0]),packet->data[0],packet->data[1],packet->data[2]);
}
packet = MIDIPacketNext (packet);
}
}
- (id)init{
OSStatus result;
self = [super init];
if (self) {
// Create the client
result = MIDIClientCreate(CFSTR("myVirtualClient"), NULL, NULL, &client);
if (result !=0) NSLog(#"MIDIClientCreate error %i",result);
// create the destination
result = MIDIDestinationCreate(client, CFSTR("myVirtualDestination"), midiReadProc,(__bridge void *)(self),&virtualIn);
if (result !=0) NSLog(#"MIDIClientCreate error %i",result);
// set the UniqueId so i dont have to toggele the output in NI Maschine
result = MIDIObjectSetIntegerProperty(virtualIn, kMIDIPropertyUniqueID, virtualinUniqueId);
if (result !=0) NSLog(#"MIDIClientCreate error %i",result);
}
return self;
}
#end
Extra info
Put in this extra test in response to the hints of Gene to see what happens when
removing the 0xF8 filter and using a printf instead of NSLog();
Output from MIDI Monitor:
Output form my code:
No solution but now a clock is received with the same timestamp.
Tempo in this test was set to 50 BPM (so one clock every 50 ms)
I checked to see if there are missing clock pulses but this isn't the case. All clocks are received with approx the expected timestamp.

Minor comment here.
Since you're doing this:
const MIDIPacket *packet = &list->packet[0]; // ?defined as const to avoid compiler warnings?
This
for (int i = 0; i < list->numPackets; i++) {
should be
for (int i = 0; i < list->numPackets; ++i) {
But that doesn't fix your problem.
What happens when you remove the filtering "if" (besides getting lots of F8s)?
Shot in the dark: try printf to see if NSLog is up to no good.

I think i found out wat the problem is.
The packets are not missing but put together in the data[0] of the first package.
Another post (proper-use-of-midipacketlistadd-coremidi) put me on the right track.
for this test i put a chord of 3 notes and 2 control change messages on the first bar and a single not on the sixteenth and a singe control change .
Well, its clear the control change holds the 2 control change's and the 3 NoteOn messages. The NoteOf message holds the 3 NoteOf's.
Is this a bug in Coremidi or bad behavieur of the programmer on the other side?
EDIT
This is solved.
As Kurt pointed out its just normal and you have to be prepared for multiple messages in one packet. So you have to do something like below.
Thanks for all input.
#import "VirtualClient.h"
#implementation VirtualClient
SInt32 virtualinUniqueId = 1234567893;
MIDIClientRef client;
MIDIEndpointRef virtualIn;
MIDITimeStamp previousTimeStamp;
struct MIDIMessage
{
char* description;
int messageLength;
};
typedef struct MIDIMessage MIDIMessage;
// incomplete list of possible messages but suffucient for this test
const MIDIMessage x00 = {"Error! or Unlisted",0};
const MIDIMessage x80 = {"Note Off",3};
const MIDIMessage x90 = {"Note On",3};
const MIDIMessage xB0 = {"ControlChange",3};
const MIDIMessage xC0 = {"ProgramChange",2};
const MIDIMessage xF2 = {"SongPosition",3};
const MIDIMessage xF8 = {"Clock",1};
const MIDIMessage xFA = {"Start",1};
const MIDIMessage xFB = {"Continue",1};
const MIDIMessage xFC = {"Stop",1};
MIDIMessage midiType(Byte midiType){
switch (midiType) {
case 0x80: return x80;
case 0x90: return x90;
case 0xB0: return xB0;
case 0xC0: return xC0;
case 0xF2: return xF2;
case 0xF8: return xF8;
case 0xFA: return xFA;
case 0xFB: return xFB;
case 0xFC: return xFC;
default: return x00;
}
}
void midiReadProc (const MIDIPacketList *list, void *procRef, void *srcRef) {
int index;
int messageLength;
const MIDIPacket *packet = &list->packet[0];
// handle packets
for (int i =0;i< list->numPackets;++i){
// handle messages in each packet
index = 0;
while (index < packet->length){
messageLength = midiType(packet->data[index]).messageLength;
if (messageLength){
printf("%llu packet(%i of %i) %s", packet->timeStamp,i+1,list->numPackets,midiType(packet->data[index]).description);
for (int x =1; x< messageLength;x++)
printf(" data[%i]:0x%02x",x,packet->data[index+x]);
printf("\n");
index+=messageLength;
}
else{
printf("Unlisted comand! printing rest of bytes");
for (int i =index; i < packet->length;i++)
printf("0x%02x ",packet->data[i]);
printf("\n");
break;
}
}
packet = MIDIPacketNext (packet);
}
}
- (id)init{
OSStatus result;
self = [super init];
if (self) {
// Create the client
result = MIDIClientCreate(CFSTR("myVirtualClient"), NULL, NULL, &client);
if (result !=0) NSLog(#"MIDIClientCreate error %i",result);
// create the destination
result = MIDIDestinationCreate(client, CFSTR("myVirtualDestination"), midiReadProc,(__bridge void *)(self),&virtualIn);
if (result !=0) NSLog(#"MIDIClientCreate error %i",result);
// set the UniqueId so i dont have to toggele the output in NI Maschine
result = MIDIObjectSetIntegerProperty(virtualIn, kMIDIPropertyUniqueID, virtualinUniqueId);
if (result !=0) NSLog(#"MIDIClientCreate error %i",result);
}
return self;
}
#end

Related

Sending a string to UART gives garbage with printf

I'm trying to format data sent over a USB UART with printf and it's giving me garbage. I can send a simple string and that works but anything I try to format gives junk. Looking through the code I think it has to do with my string not being in program space but I'm not sure.
Here is my main:
void main(void) {
CPU_PRESCALE(CPU_16MHz);
init_uart();
int degree = 0;
char buffer[50];
while(1) {
degree = (degree + 1) % 360;
send_str(PSTR("\n\nHello!!!\n\n"));
memset(buffer, 0, 50);
sprintf_P(buffer, PSTR("%d degrees\n"), degree);
send_str(buffer);
_delay_ms(20);
}
}
The output looks like this:
Hello!!!
����/�������(/����#Q��������
Hello!!!
����/�������(/����#Q��������
The USB UART code I found in a tutorial. The relevant parts look like this:
void send_str(const char *s)
{
char c;
while (1) {
c = pgm_read_byte(s++);
if (!c) break;
usb_serial_putchar(c);
}
}
int8_t usb_serial_putchar(uint8_t c)
{
uint8_t timeout, intr_state;
// if we're not online (enumerated and configured), error
if (!usb_configuration) return -1;
// interrupts are disabled so these functions can be
// used from the main program or interrupt context,
// even both in the same program!
intr_state = SREG;
cli();
UENUM = CDC_TX_ENDPOINT;
// if we gave up due to timeout before, don't wait again
if (transmit_previous_timeout) {
if (!(UEINTX & (1<<RWAL))) {
SREG = intr_state;
return -1;
}
transmit_previous_timeout = 0;
}
// wait for the FIFO to be ready to accept data
timeout = UDFNUML + TRANSMIT_TIMEOUT;
while (1) {
// are we ready to transmit?
if (UEINTX & (1<<RWAL)) break;
SREG = intr_state;
// have we waited too long? This happens if the user
// is not running an application that is listening
if (UDFNUML == timeout) {
transmit_previous_timeout = 1;
return -1;
}
// has the USB gone offline?
if (!usb_configuration) return -1;
// get ready to try checking again
intr_state = SREG;
cli();
UENUM = CDC_TX_ENDPOINT;
}
// actually write the byte into the FIFO
UEDATX = c;
// if this completed a packet, transmit it now!
if (!(UEINTX & (1<<RWAL))) UEINTX = 0x3A;
transmit_flush_timer = TRANSMIT_FLUSH_TIMEOUT;
SREG = intr_state;
return 0;
}

libtorrent - storage_interface readv explanation

I have implemented a custom storage interface in libtorrent as described in the help section here.
The storage_interface is working fine, although I can't figure out why readv is only called randomly while downloading a torrent. From my view the overriden virtual function readv should get called each time I call handle->read_piece in piece_finished_alert. It should read the piece for read_piece_alert?
The buffer is provided in read_piece_alert without getting notified in readv.
So the question is why it is called only randomly and why it's not called on a read_piece() call? Is my storage_interface maybe wrong?
The code looks like this:
struct temp_storage : storage_interface
{
virtual int readv(file::iovec_t const* bufs, int num_bufs
, int piece, int offset, int flags, storage_error& ec)
{
// Only called on random pieces while downloading a larger torrent
std::map<int, std::vector<char> >::const_iterator i = m_file_data.find(piece);
if (i == m_file_data.end()) return 0;
int available = i->second.size() - offset;
if (available <= 0) return 0;
if (available > num_bufs) available = num_bufs;
memcpy(&bufs, &i->second[offset], available);
return available;
}
virtual int writev(file::iovec_t const* bufs, int num_bufs
, int piece, int offset, int flags, storage_error& ec)
{
std::vector<char>& data = m_file_data[piece];
if (data.size() < offset + num_bufs) data.resize(offset + num_bufs);
std::memcpy(&data[offset], bufs, num_bufs);
return num_bufs;
}
virtual bool has_any_file(storage_error& ec) { return false; }
virtual ...
virtual ...
}
Intialized with
storage_interface* temp_storage_constructor(storage_params const& params)
{
printf("NEW INTERFACE\n");
return new temp_storage(*params.files);
}
p.storage = &temp_storage_constructor;
The function below sets up alerts and invokes read_piece on each completed piece.
while(true) {
std::vector<alert*> alerts;
s.pop_alerts(&alerts);
for (alert* i : alerts)
{
switch (i->type()) {
case read_piece_alert::alert_type:
{
read_piece_alert* p = (read_piece_alert*)i;
if (p->ec) {
// read_piece failed
break;
}
// piece buffer, size is provided without readv
// notification after invoking read_piece in piece_finished_alert
break;
}
case piece_finished_alert::alert_type: {
piece_finished_alert* p = (piece_finished_alert*)i;
p->handle.read_piece(p->piece_index);
// Once the piece is finished, we read it to obtain the buffer in read_piece_alert.
break;
}
default:
break;
}
}
Sleep(100);
}
I will answer my own question. As Arvid said in the comments: readv was not invoked because of caching. Setting settings_pack::use_read_cache to false will invoke readv always.

libwebsockets write to all active connections after receive

I am toying around with a libwebsockets tutorial trying to make it such that, after it receives a message from a connection over a given protocol, it sends a response to all active connections implementing that protocol. I have used the function libwebsocket_callback_all_protocol but it is not doing what I think it should do from its name (I'm not quite sure what it does from the documentation).
The goal is to have two webpages open and, when info is sent from one, the result will be relayed to both. Below is my code - you'll see that libwebsocket_callback_all_protocol is called in main (which currently does nothing, I think....) :
#include <stdio.h>
#include <stdlib.h>
#include <libwebsockets.h>
#include <string.h>
static int callback_http(struct libwebsocket_context * this,
struct libwebsocket *wsi,
enum libwebsocket_callback_reasons reason, void *user,
void *in, size_t len)
{
return 0;
}
static int callback_dumb_increment(struct libwebsocket_context * this,
struct libwebsocket *wsi,
enum libwebsocket_callback_reasons reason,
void *user, void *in, size_t len)
{
switch (reason) {
case LWS_CALLBACK_ESTABLISHED: // just log message that someone is connecting
printf("connection established\n");
break;
case LWS_CALLBACK_RECEIVE: { // the funny part
// create a buffer to hold our response
// it has to have some pre and post padding. You don't need to care
// what comes there, libwebsockets will do everything for you. For more info see
// http://git.warmcat.com/cgi-bin/cgit/libwebsockets/tree/lib/libwebsockets.h#n597
unsigned char *buf = (unsigned char*) malloc(LWS_SEND_BUFFER_PRE_PADDING + len +
LWS_SEND_BUFFER_POST_PADDING);
int i;
// pointer to `void *in` holds the incomming request
// we're just going to put it in reverse order and put it in `buf` with
// correct offset. `len` holds length of the request.
for (i=0; i < len; i++) {
buf[LWS_SEND_BUFFER_PRE_PADDING + (len - 1) - i ] = ((char *) in)[i];
}
// log what we recieved and what we're going to send as a response.
// that disco syntax `%.*s` is used to print just a part of our buffer
// http://stackoverflow.com/questions/5189071/print-part-of-char-array
printf("received data: %s, replying: %.*s\n", (char *) in, (int) len,
buf + LWS_SEND_BUFFER_PRE_PADDING);
// send response
// just notice that we have to tell where exactly our response starts. That's
// why there's `buf[LWS_SEND_BUFFER_PRE_PADDING]` and how long it is.
// we know that our response has the same length as request because
// it's the same message in reverse order.
libwebsocket_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], len, LWS_WRITE_TEXT);
// release memory back into the wild
free(buf);
break;
}
default:
break;
}
return 0;
}
static struct libwebsocket_protocols protocols[] = {
/* first protocol must always be HTTP handler */
{
"http-only", // name
callback_http, // callback
0, // per_session_data_size
0
},
{
"dumb-increment-protocol", // protocol name - very important!
callback_dumb_increment, // callback
0, // we don't use any per session data
0
},
{
NULL, NULL, 0, 0 /* End of list */
}
};
int main(void) {
// server url will be http://localhost:9000
int port = 9000;
const char *interface = NULL;
struct libwebsocket_context *context;
// we're not using ssl
const char *cert_path = NULL;
const char *key_path = NULL;
// no special options
int opts = 0;
// create libwebsocket context representing this server
struct lws_context_creation_info info;
memset(&info, 0, sizeof info);
info.port = port;
info.iface = interface;
info.protocols = protocols;
info.extensions = libwebsocket_get_internal_extensions();
info.ssl_cert_filepath = cert_path;
info.ssl_private_key_filepath = key_path;
info.gid = -1;
info.uid = -1;
info.options = opts;
info.user = NULL;
info.ka_time = 0;
info.ka_probes = 0;
info.ka_interval = 0;
/*context = libwebsocket_create_context(port, interface, protocols,
libwebsocket_get_internal_extensions,
cert_path, key_path, -1, -1, opts);
*/
context = libwebsocket_create_context(&info);
if (context == NULL) {
fprintf(stderr, "libwebsocket init failed\n");
return -1;
}
libwebsocket_callback_all_protocol(&protocols[1], LWS_CALLBACK_RECEIVE);
printf("starting server...\n");
// infinite loop, to end this server send SIGTERM. (CTRL+C)
while (1) {
libwebsocket_service(context, 50);
// libwebsocket_service will process all waiting events with their
// callback functions and then wait 50 ms.
// (this is a single threaded webserver and this will keep our server
// from generating load while there are not requests to process)
}
libwebsocket_context_destroy(context);
return 0;
}
I had the same problem, the libwebsocket_write on LWS_CALLBACK_ESTABLISHED generate some random segfault so using the mail list the libwebsockets developer Andy Green instructed me the correct way is to use libwebsocket_callback_on_writable_all_protocol, the file test-server/test-server.c in library source code shows sample of use.
libwebsocket_callback_on_writable_all_protocol(libwebsockets_get_protocol(wsi))
It worked very well to notify all instances, but it only call the write method in all connected instances, it do not define the data to send. You need to manage the data yourself. The sample source file test-server.c show a sample ring buffer to do it.
http://ml.libwebsockets.org/pipermail/libwebsockets/2015-January/001580.html
Hope it helps.
From what I can quickly grab from the documentation, in order to send a message to all clients, what you should do is store somewhere (in a vector, a hashmap, an array, whatever) the struct libwebsocket * wsi that you have access when your clients connect.
Then when you receive a message and want to broadcast it, simply call libwebsocket_write on all wsi * instances.
That's what I'd do, anyway.

CoreMIDI MidiReceived Issue

I have a thread in my OSX app where I process midi messages stored in a packetList and send them using MIDIReceived(deviceEndpoint, packetList) to a virtual instrument;
As part of my program, I send 16 note-off messages at one time to tell the virtual instrument to stop playing all 16 notes.
There's something strange - if I enable the debug message NSLog(#"Num Packets %d", packetList->numPackets); the code works fine.
However, if I take NSLog(#"Num Packets %d", packetList->numPackets); away, some notes do not get turned off some times. It's almost as if some messages were skipped over because the thread runs too fast or something.
Could someone explain to me why this is happening? Thanks a lot.
- (void) processMIDIThread:(NSObject*)param {
#autoreleasepool {
MAIN:
for (;;) {
if([[NSThread currentThread] isCancelled]) {
NSLog(#"Thread has been cancelled");
goto END;
}
int pktToTrigger = 0;
if (midiMode==kKeyboardMode)
pktToTrigger = 1; // in case note off message is lost
else
pktToTrigger = 3;
// Queue processing starts here
if (packetList->numPackets >= pktToTrigger) {
NSLog(#"Num Packets %d", packetList->numPackets);
OSStatus res = MIDIReceived(deviceEndpoint, packetList);
if (res)
NSLog(#"Problem sending midi packets to virtual device");
// NSLog(#"Sent %d %d %d" ,(int)packetList->packet->data[0],(int)packetList->packet->data[1],(int)packetList->packet->data[2]);
[self clearPacketList];
}
}
}
END: [NSThread exit];
NSLog(#"Exited current thread.");
;
}
The add packet function
- (void) addPacketToPacketList:(Byte*) data ofLength:(int) len {
if (_midiDeviceMode==kMidiOut) {
//NSLog(#"length %d", len);
//NSLog(#"%lld", mach_absolute_time());
//NSLog(#"Insert %d %d %d" ,(int)data[0],(int)data[1],(int)data[2]);
currentPacket = MIDIPacketListAdd(packetList, PACKETLIST_SIZE, currentPacket, mach_absolute_time(), len, data);
//packetReady = YES;
if (!currentPacket) exit(1);
}
}
The calling function from another class
-(void) sendAllNoteOffs {
NSMutableArray* noteZoneArray = [_keyboardView noteZoneArray];
for (int i=0; i < noteZoneArray.count; i++) {
NoteZone* nz = [noteZoneArray objectAtIndex:i];
if ([nz lastNotePlayed]) {
NotePacket* note = [NotePacket initWithLeapValue:127 forNote:[nz noteValue] noteOn:NO forChan:_keyboardChannel];
[_virtualMIDIDevice addPacketToPacketList:note->data ofLength:note->length];
// [_virtualMIDIDevice addPacketToPacketList:note->data ofLength:note->length];
[nz setLastNotePlayed:NO];
}
[nz setHighlightedOn:NO];
}
[_virtualMIDIDevice setAllNotesOffSent:YES];
}
Update :
Replacing the debug statement with the following seems to work as well.
[NSThread sleepForTimeInterval:0.0000001];
But I still don't have a good explanation for it. While not noticeable this means there's a delay in between notes.

Why doesn't this simple CoreMIDI program produce MIDI output?

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);
}

Resources