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.
Related
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;
}
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
I have a application running on windows, which will send data over serial port.
Here is the code:
m_hCommPort= ::CreateFile(L"\\\\.\\COM3",
GENERIC_READ|GENERIC_WRITE, 0, 0, OPEN_EXISTING,0,0);
if(m_hCommPort == INVALID_HANDLE_VALUE)
{
printf("COM error: %d\n", GetLastError());
}
config.DCBlength = sizeof(config);
if((GetCommState(m_hCommPort, &config) == 0))
{
CloseHandle(m_hCommPort);
printf("Get configuration port has a problem.\n");
return FALSE;
}
config.BaudRate = 9600;
config.StopBits = ONESTOPBIT;
config.Parity = PARITY_NONE;
config.ByteSize = DATABITS_8;
config.fDtrControl = 0;
config.fRtsControl = 0;
if (!SetCommState(m_hCommPort, &config))
{
CloseHandle(m_hCommPort);
printf( "Failed to Set Comm State Reason: %d\n",GetLastError());
return E_FAIL;
}
Here is the code for Send only (Working) (continuously sending )
while(1)
{
Sleep(5000);
int isWritten = WriteFile(m_hCommPort, txData, 9/*(DWORD)sizeof(txData)*/, &dwBytesWritten, NULL);
printf("isWritten: %d, dwBytesWritten: %d \n", isWritten, dwBytesWritten);
}
After this I have added code for Receive data too, then send is NOT WORKING. I mean not able to send data over UART. WriteFile() seems not executed, its stuck.
Here I have added a thread to receive data, is thread causing the problem ? or do I need to do something else ?
void ReceiverThread(void *param)
{
DWORD dwRead=0;
BOOL fWaitingOnRead = FALSE;
OVERLAPPED osReader = {0};
osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (osReader.hEvent == NULL)
printf("Error creating overlapped event; abort.\n");
while(1)
{
if (!ReadFile(m_hCommPort, &Byte, 1, &dwRead, &osReader)) {
if (GetLastError() != ERROR_IO_PENDING) // read not delayed?
printf("Error in communications; report it.\n");
else
fWaitingOnRead = TRUE;
}
else {
rxData[rxHeadIndex++]= Byte;
rxHeadIndex = (rxHeadIndex) & QUEUE_MASK;
}
}
}
SetCommMask (m_hCommPort, EV_RXCHAR/ | EV_ERR); //receive character event
_beginthread(ReceiverThread,0,NULL);
while(1)
{
Sleep(5000);
int isWritten = WriteFile(m_hCommPort, txData, 9/*(DWORD)sizeof(txData)*/, &dwBytesWritten, NULL);
printf("isWritten: %d, dwBytesWritten: %d \n", isWritten, dwBytesWritten);
}
Thanks in advance.
Ashok
I've encountered a similar problem a while ago.
I found out that WriteFile(..) blocks if a ReadFile(..) is currently in progress for a serial port. So that's a problem if ReadFile(..) blocks if there is no data to be read.
I've solved the problem by checking if there is data available within the serial buffer to be read by using the function ClearCommError(..). This ensures that ReadFile(..) can read something immediately and does not unnecessarily block the device. Try changing your ReceiverThread into something like this:
void ReceiverThread(void *param)
{
DWORD dwRead=0;
COMSTAT comStat;
char buffer[1024];
while (m_hCommPort != INVALID_HANDLE_VALUE)
{
if ((ClearCommError(m_hCommPort, NULL, &comStat) != FALSE) && (comStat.cbInQue > 0))
{
/* todo: ensure buffer is big enough to contain comStat.cbInQue bytes! */
if (ReadFile(m_hCommPort, buffer, comStat.cbInQue, &dwRead, NULL) != FALSE)
{
/* do something with data in buffer */
}
}
/* avoid busy-wait */
if (comStat.cbInQue == 0)
{
SleepEx(1, FALSE);
}
}
}
This way ReadFile(..) is only called if data is available and meanwhile WriteFile(..) can send data without being blocked.
Unfortunately I've not been able to make ClearCommError(..) blocking so I used the SleepEx(1, FALSE); work-around to avoid a busy-wait and therefore prefenting the ReceiverThread to eat up the CPU.
config.fDtrControl = 0;
config.fRtsControl = 0;
These settings turn the DTR and RTS handshake lines off. Most serial devices pay attention to these signals. They won't send anything when your DTR signal is off, assuming that the machine is not powered up. And won't send anything when your RTS signal is off, assuming that the machine is not ready to receive any data.
So what you observed is entirely normal.
Since the device appears to be "normal" and does pay attention to the handshake lines, you'll want to configure the DCB to let the device driver automatically control these signals. Fix:
config.fDtrControl = DTR_CONTROL_ENABLE;
config.fRtsControl = RTS_CONTROL_HANDSHAKE;
Also the default for terminal emulators like Putty and HyperTerminal. Use such a program first to ensure that the wiring and device are functional. If you can't get any device data to show up in such a program then it won't work with your program either. If this all checks out then also set the fDsrSensitivity, fOutxCtsFlow and fOutxDsrFlow properties to TRUE so that you will, in turn, pay attention to the handshake signals of the device.
I wrote a windows socket server with I/O Completion port to handle heavy data transmission. It works smoothly when there is only one client connected. when more than one client connect, other threads just seem blocked while only one thread works fine.
I changed my design to use select() with each thread for each connection, the problem seems still the same. By the way, the same design works fine in OSX.
However, when I run multiple instances of my process, a process serves each connection, it works great.
Anyone kind enough to enlighten me? I still prefer multithread design. Core code as following:
other part of code basically comes from: http://www.codeproject.com/Articles/13382/A-simple-application-using-I-O-Completion-Ports-an
//Worker thread will service IOCP requests
DWORD WINAPI WorkerThread(LPVOID lpParam)
{
int nThreadNo = (int)lpParam;
void *lpContext = NULL;
OVERLAPPED *pOverlapped = NULL;
CClientContext *pClientContext = NULL;
DWORD dwBytesTransfered = 0;
int nBytesRecv = 0;
int nBytesSent = 0;
DWORD dwBytes = 0, dwFlags = 0;
//Worker thread will be around to process requests, until a Shutdown event is not Signaled.
while (WAIT_OBJECT_0 != WaitForSingleObject(g_hShutdownEvent, 0))
{
BOOL bReturn = GetQueuedCompletionStatus(
g_hIOCompletionPort,
&dwBytesTransfered,
(LPDWORD)&lpContext,
&pOverlapped,
INFINITE);
//WriteToConsole("\nThread %d: GetQueuedCompletionStatus.", nThreadNo);
if (NULL == lpContext)
{
//We are shutting down
break;
}
//Get the client context
pClientContext = (CClientContext *)lpContext;
if ((FALSE == bReturn) || ((TRUE == bReturn) && (0 == dwBytesTransfered)))
{
//Client connection gone, remove it.
RemoveFromClientListAndFreeMemory(pClientContext);
continue;
}
WSABUF *p_wbuf = pClientContext->GetWSABUFPtr();
OVERLAPPED *p_ol = pClientContext->GetOVERLAPPEDPtr();
switch (pClientContext->GetOpCode())
{
case OP_READ:
pClientContext->SetTransLen(dwBytesTransfered);
if(!pClientContext->IsComplete())
{
pClientContext->SetOpCode(OP_READ);
dwFlags = 0;
//Overlapped send
nBytesSent = WSASend(pClientContext->GetSocket(), p_wbuf, 1,
&dwBytes, dwFlags, p_ol, NULL);
WriteToConsole("\nThread %d: WSASend continue bytes = %d.", nThreadNo, dwBytes);
if ((SOCKET_ERROR == nBytesSent) && (WSA_IO_PENDING != WSAGetLastError()))
{
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
WriteToConsole("\nThread %d: WSASend failed.", nThreadNo);
}
}
else
{
WriteToConsole("\nsocket %d: WSASend complete.", pClientContext->GetSocket());
//clear cache
pClientContext->ResetWSABUF();
//for the next recv, must be triggered.
pClientContext->SetOpCode(OP_WRITE);
//Get the data.
nBytesRecv = WSARecv(pClientContext->GetSocket(), p_wbuf, 1,
&dwBytes, &dwFlags, p_ol, NULL);
if ((SOCKET_ERROR == nBytesRecv) && (WSA_IO_PENDING != WSAGetLastError()))
{
WriteToConsole("\nThread %d: Error occurred while executing WSARecv().", nThreadNo);
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
}
}
break;
case OP_WRITE:
pClientContext->SetTransLen(dwBytesTransfered);
if(pClientContext->IsComplete())
{
if(!pClientContext->ProcessCmd())
{
WriteToConsole("\nThread %d: ProcessCmd failed.", nThreadNo);
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
}
WriteToConsole("\nThread %d: receive completed.", nThreadNo);
//Send the message back to the client.
pClientContext->SetOpCode(OP_READ);
dwFlags = 0;
//Overlapped send
nBytesSent = WSASend(pClientContext->GetSocket(), p_wbuf, 1,
&dwBytes, dwFlags, p_ol, NULL);
WriteToConsole("\nThread %d: WSASend bytes = %d.", nThreadNo, dwBytes);
if ((SOCKET_ERROR == nBytesSent) && (WSA_IO_PENDING != WSAGetLastError()))
{
WriteToConsole("\nThread %d: Error occurred while executing WSASend().", nThreadNo);
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
}
}
else //continue receiving
{
pClientContext->SetOpCode(OP_WRITE);
//Get the data.
nBytesRecv = WSARecv(pClientContext->GetSocket(), p_wbuf, 1,
&dwBytes, &dwFlags, p_ol, NULL);
WriteToConsole("\nThread %d: WSARecv continue bytes = %d.", nThreadNo, dwBytes);
if ((SOCKET_ERROR == nBytesRecv) && (WSA_IO_PENDING != WSAGetLastError()))
{
WriteToConsole("\nThread %d: Error occurred while executing WSARecv().", nThreadNo);
//Let's not work with this client
RemoveFromClientListAndFreeMemory(pClientContext);
}
}
break;
default:
//We should never be reaching here, under normal circumstances.
break;
} // switch
} // while
return 0;
}
Updates: since my server app need to send large amount of data to multiple clients, it works fine maybe for some minutes, but somehow for no reason, some threads just block without responding. Does it have anything to do with the size of data?
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);
}