Friday 4 February 2011

PGMidi updated

Attention PGMidi users! I've updated my MIDI Monitor example project again, this time making PGMidi less of an example, and a lot more useful for general use.

Check it out on the GitHub project page (historical note: it was hosted on this gitorious project page, but that is now deprecated in favour of GitHub).

What's changed:
  • The PGMidi class now allows you to determine what MIDI sources and MIDI connections are currently available.
  • It allows you to determine whether the source/destination is a networked session, or a physically attached device.
  • The "events" PGMidi sends through the delegate interface are far more explicit.
  • I've added some useful tools to find a matching source/destination pair if you want to speak to one specific MIDI device.
The MIDIMonitor example application continues to show how to use the PGMidi API.

Thanks for all the comments and feedback I've received about this code. I hope you find these updates useful.

25 comments:

Anonymous said...

Hello Pete
Great sample that helped me much to get started with my first MIDI app.
But I found a problem: It works only with devices connected while the app is already running, because the delegate for calling midiReceived is set in the sourceAdded method.
And because wireless MIDI was already connected before launching the app, wireless MIDI didn't work at all.
I could solve the problem by adding the line
[midi init];
as last line in the method setMidi in MidiMonitorViewController. That loops through the existing connections and sends a sourceAdded far all existing connections.
Is that the correct solution?
Best regards,
Matthias Bauer

Pete Goodliffe said...

You shouldn't ever re-init an object that has been initialised. That way bugs lie...

Basically, when you attach your delegate during app startup, you should also iterate over all sources already in the PGMidi object. Simple as that.

Take a look at the PGMidiAllSources object for a simple example of this.

Andy Capon said...

Hi Pete,

Thanks for the example, very useful.

I am just wondering how you go about debugging?

The simulator doesn't seem to support midi as far as I can see and while using the camera connection kit I cannot have the iPad connected to the computer for debugging.

Cheers

Andy

Pete Goodliffe said...

Good old fashioned debugging techniques. Logging, mostly.

Or use a wireless network MIDI session.

Andy Capon said...

Hi Pete,

Thanks for the info, I have it working over the network connection now.

Midi receive doesn't seem to work though midiReceived() is not called when midi is sent over the network to the iPad.

Should this work?

Thanks

Andy

Pete Goodliffe said...

I know other people have this working with network MIDI.

Do you have network MIDI working in other iOS MIDI applications?

Brett said...

Hi Pete,

First off, thanks for PGMidi code, saved me many many hours of time.

I have noticed that interfaces are listed after pressing list interfaces even when the device is not connected to anything (Named "Session 1" and is network). At the top, Midi devices shows sources=0 destinations=0. The funny thing is that I can then connect to my computer over wifi, send data successfully and nothing updates in the interface.

Any ideas to why old devices or something are lingering, and not reshowing up?

Thanks,
Brett

Unknown said...

Thanks so much for this. I'm embarking on my first iOS app (which will use MIDI) and your skeleton looks like a great starting point.

I looked at the source tree and couldn't find a LICENSE file. Have you thought about including one?

Anonymous said...

Hello Pete,

Thanks for your awesome code.

I have a question though. When I hook up a MIDI device to the iPad over the camera connection kit, all will work well. This is unless I start to press a lot of buttons or change sliders very fast, then the app will hang for a few seconds. The entire app becomes unresponsive during this time. After it recovers, it prints out the received MIDI bytes?

Any ideas? I've read that you should never do IO/printf/NSLog, etc. or even ObjC when receiving messages. (From the folks who made MoDrum and BassLine). What other options do we have?

Pete Goodliffe said...

Never do any serious work (especially Obj-C) on the MIDI callback thread. Always forward to the main thread to do "slow" tasks.

Note: I do do this.

The UI thread performance sucks because I update the UITextView in a very clumsy way. it's not at all optimal and performs terrible string manipulations which will go VERY slowly when loads of data comes in.

Treat that bit as example code, not production code!

Anonymous said...

Thanks Pete! Wow, fast reply!

I also noticed one thing. When I connect via WiFi, the app doesn't display any MIDI signals. I know I'm connected because other WiFi Midi apps work.

I traced it to the PGMIDIReadProc, specifically to:

PGMidiSource *self = (PGMidiSource*)srcConnRefCon;

For some reason the WiFi connection doesn't get registered here and thus, the method call to midiRead goes nowhere.

What I wound up doing is changing it so that it went to:

PGMidi *self=(PGMidi*)readProcRefCon;

And added the midiRead method to PGMidi. Seems to work.

Anonymous said...

To solve the problem for the devives plugged before the app run (and don't re-init the object), simlply do this :
midi = [PGMidi alloc];
[midi enableNetwork:YES];
viewController.midi = midi;
[midi init];
in application:didFinishLaunchingWithOptions

Regards

Rainer said...

Hey Pete,

your PGMidi works fine, but how to get it to mainthread??? Where and what do I have to declare with performselectoronmainthread?

My main problem is:
MIDI output freezes while scrolling on scrollview.

giku said...

I started using your code, great job. However ... I experience a rare problem: sticking midi notes. It seems that sometimes on some controllers I don't get Note Off (or Note On with 0 velocity). Have anybody experienced this? How core midi deals with MIDI running status?

Pete Goodliffe said...

I've not specifically seen this happen.

It's not due to a loose connection to your MIDI device, is it? If the connection drops and re-establishes, you may miss some events.

Anonymous said...

hi Pete

many thanks for this "unique" example explaining us how to deal with CoreMidi on iOS. Your sample is of big help for any of us!

i was not able to get any midi input displayed on the textview, even if I saw midi in (in the MIDIReadProc, but the delegate was nil)
as you mentioned, in the viewcontroller, in setMidi, i added a check on the sources after the midi.delegate=self line:
for (PGMidiSource *source in midi.sources) source.delegate = self;

now, i see midi messages on the textview.
I still have messages on the debug console about a leak:
MidiMonitor[303:5d03] *** __NSAutoreleaseNoPool(): Object 0x347ca0 of class NSCFString autoreleased with no pool in place - just leaking

this comes each time a midi in message is coming.

could you just tell us what to do to avoid this?

thanks for your precious help.

Pa

Brian Ping-Yao Wang said...

Hello Pete,

I am the developer of PianoAngel app. Thanks for your great sample code. I integrated your PGMidi classes in PianoAngel in March and I added a sourceDelegate for assigning source delegate to it.
Recently I've added virtual MIDI port support by modifying for your classes and I think you might be interested in my modification. I cloned your project and push my modification at: https://gitorious.org/~wangpy/midimonitor/wangpys-midimonitor
FYI. :)

Best regards,
Brian

dhjdhj said...

Using PGMidi with our iPad product, very appreciate of your having made it available.

Just ran into an issue that's bothering me. It seems as if the MIDI input misses packets if they come too fast.

I have Event Chasing enabled in Digital Performer, meaning that when one starts from the middle of a song, Digital Performer will quickly send out the latest seen values for Program Change, Pitchbend, Controllers, and so forth.

The Snoize MIDI Monitor indicates that a ProgramChange and PitchBend message were sent at exactly the same moment but I only ever see one packet show up in the PacketList.

Is this a known issue?

Pete Goodliffe said...

Please note that I have updated this post as the location of the PGMidi repo has changed (by popular request) to GitHub: https://github.com/petegoodliffe/PGMidi.

Unknown said...
This comment has been removed by the author.
Thomas said...

Hey Pete,

thank you for your great MIDI classes. I am unfortunately running into some memory leak issues with ARC.

I run pgmidi sendBytes in a separate thread, and have surrounded the call with @autoreleasepool {}. However, I get the following error message on every (!) sendBytes call:

Object 0x1e0286f0 of class PGMidiSource autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug

I don't know how to further debug it, especially since there is no PGMidiSource related stuff in sendBytes. It would be great if you could give some help on this problem.

Best,
Thomas

drew meyer said...

i don't understand how to make this work. i've traced my way through your example, and i get this far:

midi = [[PGMidi alloc] init];
[midi enableNetwork:YES];
midi.delegate = self;
for (PGMidiSource *source in midi.sources) {
source.delegate = self;
}

i was also able to implement the events (midi received, etc).

i'm not sure where to go from here. how do i get it to connect to the bonjour midi server running on my mac? how do i send data? i just don't see it!

RikMaxSpeed said...

Hi Peter

Have you ever tried PGMidi with long SysEx messages?
I experienced some trouble with this causing the MidiServer on the device to repeatedly crash MIDIPacketListAdd.

MIDIPacketLists have a buffer size of 256 whilst I was sending a 1Kbyte message. However Apple's documentation states this is possible if the buffer is alloced dynamically - which is what the PGMidi code does.

My current solution has been to augment PGMIDI with the correct MIDISendSysex call. This is an asynchronous method (as the messages are potentially up to 65Kb in length).

Best regards,
Richard.

What was strange about this is that Apple's MIDIServer crashed - not my application - the symptom on the application side was that I stopped receiving any events.

Stack Trace from MIDIServer recovered from the device log:
Thread 6 Crashed:
0 CoreMIDI 0x33313db0 MIDIPacketListAdd + 12
1 AppleMIDIRTPDriver 0x012e3de8 0x12d8000 + 48616
2 AppleMIDIRTPDriver 0x012e3e76 0x12d8000 + 48758
3 AppleMIDIRTPDriver 0x012db06a 0x12d8000 + 12394
4 AppleMIDIRTPDriver 0x012dea9a 0x12d8000 + 27290
5 CoreMIDI 0x3332efa4 MIDIDestination::SendPacketsNow(void*, MIDIPacketList const*) + 392
6 CoreMIDI 0x3332edfa MIDIDestination::SendPacketList(void*, MIDIPacketList const*) + 294
7 CoreMIDI 0x3331b856 _MIDISend + 250
8 CoreMIDI 0x33330d92 ClientProcess::WriteDataAvailable() + 98
9 CoreMIDI 0x33320e98 MIDIIOThread::Run() + 180
10 CoreMIDI 0x33316910 XThread::RunHelper(void*) + 8
11 CoreMIDI 0x333162a2 CAPThread::Entry(CAPThread*) + 262
12 libsystem_c.dylib 0x3b10730e _pthread_start + 306
13 libsystem_c.dylib 0x3b1071d4 thread_start + 4

Anonymous said...

Hi,
could you help me please.
i've added PGmidi to my project, all works nice besides one thing: i can't make my application visible as a source or destination in other applications. What should i do for that? thanks in advance.

Boris Lau said...

I thought I had the problem of missing events as well. But my MIDI parsing code did not account for multiple messages being in one packet, e.g. Note Off + Note On in one 6 byte packet. Then I discovered that there's also the 'feature' of the running status.

Pete, do you know of a library that takes care of the Midi parsing and spits out the individual events - and works well with PGMidi?

Also, a big thank you for your work on PGMidi!

Best, Boris