CoreMIDI using objc_util questions
With the beta version of Pythonista it looks like it might now be possible to get CoreMIDI access going.
I know that there are several issues surrounding this like needing to have the audio key in their UIBackgroundModes in order to use CoreMIDI’s MIDISourceCreate and MIDIDestinationCreate functions. These functions return kMIDINotPermitted (-10844) if the key is not set according to Apple docs.
Pythonista seems to have the ability to play background audio now - but does that mean it has the audio key set?
In looking at CoreMIDI I see that it is more of a "C" API. It does not have many Objects - only Functions that can be called. Can you show an example of how to access a framework like this? I have just tried:
CoreMIDI = NSBundle.bundleWithPath_('/System/Library/Frameworks/CoreMIDI.framework') CoreMIDI.load()
Which seems to work and does not throw any errors. I think I would like to do something like this C code next:
MIDIClientCreate(CFSTR("simple core midi client"), NULL, NULL, &(_midiClient));
... but since this is straight C code there is no obvious NSObject to create with methods to call to do this using objc_util.
Please feel free to waive me off, if this is going to be a dead end for any reason.
most of the require headers can be found at the apple open source page, though it is not well reference by search engines. there are several github repos as well that contain framework headers.
here is a link to coremidi headers
i thought that cffi and the like require a c compiler in order to extract type info from precompiled or compiled code, rather than trying to parse C code directly (anyone who has tried to write their own simple c type parser soon realizes how painful this is, since you have to deal with basically two languages, c preprocessor language as well as c, and before you know it you basically have to write a complete c preprocessor to handle anything other than the simplest of headers.
I think your best bet is to pre create a complete set of python bindings, perhaps with one of the tools you mentioned above, as a class, then reuse those.
@wradcliffe To get the iOS header files you only need to install Xcode from the Mac App Store for free and poke around inside the app package a little. The license on the header files is Apple's open-source code license that allows (as far as I can tell) modification and redistribution.
@JonB When set up properly,
cffican run on Pythonista with
ctypes. By default it tries to import a compiled C module and do its ffi-ing with that, but you can make it use
import cffi import cffi.backend_ctypes ffi = cffi.FFI(backend=cffi.backend_ctypes.CTypesBackend()) # Done.
The C parsing is done entirely in Python using
pycparser, which is a full C parser based on
yacc). Though it does expect preprocessed C code - this means no comments, no includes, no defines, no if macros.
And I am still trying, and repeatedly failing, to write up some kind of basic C preprocessor in Python. Right now it can do
#undefand expansion of macro constants, and if you're lucky it can even handle macro functions. That is, nested macro function calls will fail almost all the time.
@dgelessus - it is new info to me that cffi must have preprocessed headers. I have not located any examples showing how they process header files and was expecting to find a utility of some kind that did the preprocessing somewhere. So you are telling me that this does not exist?
PyCParser actually has the preprocessor but I don't know how good or general it is. It is obviously good enough to handle SDL.h and is designed specifically to handle header files that include other header files, macros, ifdefs, etc. I have started looking at the code and playing with it and it looks very impressive. I keep wondering why the author - Albert Zeyer - stopped working on this 3 years ago. Might be because of cffi. The amount of work that went into this in order to solve this use case is jaw dropping.
EDIT: Also - I don't own a Mac desktop of any kind so downloading and installing XCode is not an option for me right now. @JonB thanks much for the pointer to the headers.
EDIT: Albert implemented the CWrapper (ctypes wrapper) functionality only 3 years ago so the work is still fairly fresh and he is accepting and merging fixes to the codebase from contributors.
pycparserbundled, it doesn't have its own C parser.
pycparser's level of understanding unpreprocessed C goes as far as "this line starts with a
#, I'm not touching that" and "what is this
/*doing here, this isn't valid syntax". On a normal computer you'd tell
pycparserto run the code through
cpp(the C preprocessor) before parsing it. That's not an option under Pythonista, for obvious reasons.
@dgelessus - I found this reference on stackoverflow:
Basically it says that there is no preprocessor and even using gcc to generate the cleaned up code is not recommended.
So this effectively takes cffi off the plate for me unless someone like @omz would commit to running some kind of useful preprocessing of the framework headers and supplying these for either download or as part of Pythonista. The good news is that this might effectively remove the copyright problem since these files would not be original Apple source code. May be a gray area but I think we could get away with it.
@JonB - the pointer to the headers you sent is pretty sketchy. File dates are 2006. I am hoping to find something more official and up to date. The Apple Open Source sites I looked at do not seem to provide usable access. Lots of tar balls to download but no way to see what is in them. Still searching for a stable reliable site that provides these headers.
gcchas a lot of non-standard syntax (like
pycparsercan't handle. I'm not sure about
clang, but the Darwin header files look like they don't include any other special syntax when using
clang. (I'm not sure, but wouldn't we have to (pretend to) use
clang, because that's what @omz compiles Python(ista) with?)
In a normal use case (providing Python bindings for a cross-platform C library) including system headers would be a problem, because they would only work on the exact system where they come from. It's also a load of boilerplate code that isn't really important in most cases. In the case of Pythonista portability is less of an issue - we're all on ARM running Darwin with the same version of Pythonista. The only important differences are the bitness (
__LP64__) and the iOS version.
Just because you don't have real header files doesn't mean you can't use
cffi. It just means that instead of this
libc = ctypes.CDLL(None) libc.strlen.argtypes = [ctypes.c_char_p] libc.strlen.restype = ctypes.c_int
you'd write this
ffi = cffi.FFI(...) libc = ffi.dlopen(None) ffi.cdef(""" int strlen(char *); """)
@dgelessus - you make many good points about cffi. I hope I did not imply that it was useless in general. Only for my particular use case.
I am not the only one with this case as you may have noticed @Cethric has bitten off doing OpenGLES bindings, which makes CoreMIDI look simple in comparison. He wrote a header file processor to generate a lot of the boilerplate: parseHeaders.py
I also want to clarify for anyone else reading through this thread that I WAS able to get CoreMIDI working using a limited subset of the API and it was not all that terrible to hand code it. I just wanted to go to the next step by implementing a full set of bindings, writing test cases, etc. and went down this rabbit hole. I also can't help noticing that there are a lot of forum support requests being caused by simple coding errors for these bindings with users asking @omz to debug or help write the code. I really wish to avoid using the forum to help find my typos or teach me Python syntax.
@wradcliffe , sorry I didn't quite get the last paragraph. Is omz making errors implementing the bindings for MIDI because of too many silly questions?
@Phuket2 - I guess that last paragraph sounds like I am angry or pissed off but that is not the case. I was just noting that a lot of the forum postings asking for help with it by other users has to do with mistakes in coding. Some of it is getting the API wrong, some is lack of understanding of Python, some is an inability to debug the mistakes. I feel like this is a waste of his time more then anything and I personally don't want to contribute to that. I could have it all wrong and he may enjoy the interaction.
Also - CoreMIDI support has been a requested feature of Pythonista for awhile by a few users but has not been implemented for a variety of reasons. You can search for MIDI or CoreMIDI to see the history of the topic. As a relatively new user you should read through those threads as well as the discussions on getting Python3 implemented to see the kinds of constraints omz is under in offering this product.
@wradcliffe , you read me correctly :) we all get frustrated at times. But we all need distractions sometime. I think if some seemingly stupid questions are been answered by over qualified people, it could be just what they need to take a break. You never know. unfortunately, I ask my fair share of stupid questions! But I am getting better :)
Anyway, I am useless to your question here. However, funny enough , I use to work for Roland Australia (many years ago, D50 was released when I was working for them). Also, As far as I know, I had one of the first ever external Midi interfaces for a Macintosh. I got a prototype from a company in Brookvale, Sydney. It was a serial interface, I had a Mac Plus back then.
It didn't matter a great deal. Atari was so ahead of the game when it come to MIDI back then. They even had pressure response.
Oh, also while I was a Roland I used to eat lunch most days with a guy that was on the original fairlight team. Fun years!
I hope you get a resolution to your problem
This is MIDI as in my MIDI keyboard that I can use with GarageBand for iOS? I'd love to see this in pythonista. Or am I confused about what MIDI is...
Musical Instrument Digital Interface
@Webmaster40 - yep - CoreMIDI is the framework used to interact with MIDI devices and applications. See: How To Use CoreMIDI
@dgelessus - I think some readers of this thread may wonder why PyObjC could not be used. This package has the longest history and goes back to 1996. It was designed to use a c compiler to generate the metadata we are discussing. It has struggled with methods of storing the metadata (preprocessed headers and massaged headers) and used XML files and more recently python source code and JSON files for this purpose. The fact that the metadata was always getting out of date and the use of a compiler as part of the process seems to have steered people away from it. Here is a pointer to the preprocessing part of the project:
The project README says: "This project is hopelessly incomplete at the moment".
All in all, it looks like there are good reasons why everyone is still looking for a pure python solution to my use case and why cffi offers the current best set of tradeoffs.
Great! I own a 25-key MIDI keyboard, it'd fun to play with this and pythonista. I think the introduction of CoreMIDI support would have to come with modules for audio processing. I've been playing with the
wavebendermodule to make some sounds, but I find it very confusing as I'm not an audiophile and don't understand how that stuff works.
By the way - I've worked on my C preprocessor thing a little. It seems to handle nested macro functions well now. I've also added basic conditional blocks using
#endif. Once I've got
#includeworking I'll probably put it on GitHub so you can all admire this "beautiful" code.
If anyone has any ideas on how the C expressions in
#elifcould be parsed, let me know. If all else fails I might do some dumb string replacing and then use Python's
astmodule for parsing. The syntax is similar enough.
Looking through the
cffisource code, it seems that it does remove comments from source code, and even understands very basic
#defines. Quoting one of the error messages:
only supports one of the following syntax: #define %s ... (literally dot-dot-dot) #define %s NUMBER (with NUMBER an integer constant, decimal/hex/octal)
This is a feature of
pycparser. Originally I only looked at the
pycparsersource, which is why I thought that
cffirequired fully preprocessed source code.
CFFI is created by the Pypy core team. They hint at an unreleased CFFI v1.3 here.
@wradcliffe Apple provides downloads for Xcode outside the App Store as well, at https://developer.apple.com/downloads/, though you do need to sign in with your Apple ID. (The first time you do so you'll also need to accept the usual agreement that you're over 13, etc.) The Xcode download is in the usual OS X installation package format, which is not very straightforward to open. If you have some time and don't mind a bit of exploring, read on.
To get to the actual Xcode data, grab yourself a copy of our lord and savior 7-Zip. You'll need it.
The installation image's file structure looks roughly like this:
dmgdisk image, which is a format that 7-Zip supports. It contains...
- The main
pkginstallation package. Technically it is a
xararchive, which 7-Zip supports. It contains...
- A number of folders with a
pkgextension. Which one contains what you want might be visible based on the name. Each one contains...
- A few data files.
Payloadis the main data, compressed using
gzip. How convenient, 7-Zip supports that as well. It contains...
cpioarchive. You guessed it, 7-Zip can read that as well. It contains...
- The good stuff. This is where you'll find the actual Xcode files.
The location of the iOS include directory (inside the Xcode.app bundle) is
Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include. I'm not sure what exactly that translates to in the installation image, since I got Xcode from the App Store, and don't have the Xcode image lying around atm. (It would be so much nicer if the header files were available online somewhere...)
@dgelessus - pycparser uses ply as its Lex Yacc tool and ply contains a preprocessor. Have a look:
I am not sure why cffi does not take better advantage of this. The author Armin Rigo talks about it in this stackoverflow thread:
"While it is possible to use the gcc -E approach and manually "trim" the result, it is not the recommended way to use CFFI. Instead, the cdef() code is usually made either incrementally (adding functions as needed) or in bulk from an edited copy of the .h file. The first approach works best when copying from man pages; the second approach is for the case where we want complete access to a single 3rd-party library.
In all cases, it is very likely that you need to edit the .h file anyway: the recommended approach is to use ffi.set_source(), and remove from the cdef() any declarations that are superfluous, replacing them with .... For example, the actual .h file may contain the declaration #define FOOBAR 42, but the value 42 should not be relied upon (e.g. it could change in the future), so the cdef() should rather receive #define FOOBAR ...."
@ccc - Armin Rigo is working on PyPy now and cffi and a lot of what is being done with cffi is about making it play better in PyPy. Lots of work being done to make cffi bindings run very fast in PyPy.
@omz ... has anyone asked about the possibility of a PyPythonista App?