Your Max Standalone on Mac App Store


Many people enjoy sharing their Max creations with others in the form of a standalone application. To reach a broader audience, we would like to be able to submit standalone applications to Apple’s Mac App Store. Regular Cycling ’74 forum readers will likely recognize a long running thread called “Max apps in new Apple Mac App Store?” that was started in 2010 and still receives new postings. That thread documents our efforts, together with Cycling ‘74, to navigate Apple’s various guidelines and restrictions in order to get Max standalones into the Mac App Store.

Between that thread and a recent story that appeared on Create Digital Music, we’ve received lots of requests for more information on the process. This article will attempt to fill in the details so other Max creators can get in on the App Store action!

First off, you must use Max 6 to create a standalone that will satisfy Apple’s requirements, primarily because in Max 6, app-specific preference files are saved in a location permitted by Apple.

It’s also probably a good idea to make sure that your standalone has some of the basics down (custom icon, appropriate menus, etc…) before embarking on the steps below. Dan covered many of these topics in his 2011 Expo ‘74 presentation entitled “Making A Slick Max Standalone.” The slides for the presentation are available here.

After you are in the program, you will be required to follow Apple’s guidelines before your application will be accepted for inclusion. Here are some of the rules that are most likely to affect Max-based applications:

  • No use of Java (so no mxj use in your standalone)
  • No demo or limited functionality versions of your app
  • No file access (read or write) beyond “approved” locations, without specific end-user request for such (via file dialog box for example). These approved locations are:
    • ~/Library/Application Support/
    • ~/Library/
    • ~/Library/Caches/

Setting Up the Preferences File

To ensure that your application stores its preferences in one of these approved locations, do the following:

  1. Include a standalone object in your patch.
  2. Open the inspector for the standalone object, and change the “Preferences File Name” attribute to the same name as your application. For example, if your application will be called My Cool App, the Preferences File Name must also be My Cool App. This will create a folder the first time your app is launched at ~/Library/Application Support/My Cool App, and all Max’s will subsequently be stored within that folder. You can also use this folder if your app needs to store any other files (such as a custom preferences file).

Stripping Out PowerPC Code

If you are using any third party Max externals, they may contain PowerPC (PPC) code. which is not allowed in the App Store.

If you want to verify whether you have any third-party externals containing PowerPC code, you can use the following command in the Terminal that will list any problematic objects. Before executing the command, set the current directory to a folder containing your third-party externals.

find . -type f -exec lipo -info '{}' 2>/dev/null \; | grep ppc | grep MacOS | sed -e 's/.*MacOS\///g' -e 's/ are.*//g'

To get rid of the PowerPC code, you can use an application called TrimTheFat; just drop the .mxo external in question onto the TrimTheFat icon, and it will strip away the PowerPC part of the binaries. You’ll need to do this before building your application, because a Max standalone stores external object executable code in a proprietary format that can’t easily be modified. However, you need only remove PowerPC code once.

By the way, if you are using other people’s work, check to make sure the license terms are compatible with the App Store’s, and that you are allowed to use it in a closed source product (if that is your intention).

Modifying Your Standalone

The next few changes involve tweaks within your application’s bundle folder. You’ll need to make these changes after building your standalone.

To access the bundle, ctrl- or right-click on your application’s icon in the Finder, and choose Show Package Contents. This will open a new window that shows you the set of files inside the application bundle, which is actually a folder. All the items in an application bundle are inside a folder called Contents.

Apple apparently doesn’t like underscore (_) characters to be part of any bundle identifier. Within the support folder inside the Contents folder, open the ad folder where you’ll find the Core Audio driver object called ad_coreaudio.mxo. Ctrl- or right-click on the .mxo file and choose Show Package Contents. Double-click on the Info.plist file, at Contents/Info.plist and change the Bundle Identifier from com.cycling74.ad_coreaudio to com.cycling74.adcoreaudio.

You also have to add some specific entries within your app’s Info.plist file located in the Contents folder. These include a valid bundle ID, bundle version, copyright string, and application category (LSApplicationCategoryType). More information is available in the Mac App Store Developer document, in the “Requirements” section. You may wish to copy the Info.plist file to another location once you modify it, then you can replace the standard one Max puts into your standalone if you ever want to make an updated version of your application and submit it to Apple.

Once you’ve got your app completely ready to go, you need to “sign” and package it using certificates that Apple issues to you. More information on how to get those certificates on your computer is available on the Apple Developer site.

Once you’ve downloaded the certificates to your computer, you’re ready to sign your app. Within your standalone, you’ll need to sign the .app itself as well as any Max frameworks, audio drivers, midi drivers and/or extensions included within your application bundle. Here are examples of the code signing commands (to be run within the Terminal) for an app that includes the Core Audio audio driver, Core MIDI driver, and the setplugpath extension:

codesign -f -s "3rd Party Mac Developer Application: DEVELOPER_NAME" PATH_TO_YOUR_APP

codesign -f -s "3rd Party Mac Developer Application: DEVELOPER_NAME" PATH_TO_YOUR_APP/Contents/Frameworks/MaxAPI.framework/Versions/A

codesign -f -s "3rd Party Mac Developer Application: DEVELOPER_NAME" PATH_TO_YOUR_APP/Contents/Frameworks/MaxAudioAPI.framework/Versions/A

codesign -f -s "3rd Party Mac Developer Application: DEVELOPER_NAME" PATH_TO_YOUR_APP/Contents/support/ad/ad_coreaudio.mxo

codesign -f -s "3rd Party Mac Developer Application: DEVELOPER_NAME" PATH_TO_YOUR_APP/Contents/support/mididrivers/coremidi.mxo

codesign -f -s "3rd Party Mac Developer Application: DEVELOPER_NAME" PATH_TO_YOUR_APP/Contents/support/extensions/setplugpath.mxo

Once the signing is done, you need to package your application using the productbuild command, again using a Terminal command and with an Apple-provided certificate. Here is example that’s worked for us. This command will place the final package file at the root of your home directory:

productbuild \
--component PATH_TO_YOUR_APP /Applications \
--sign "3rd Party Mac Developer Installer: DEVELOPER_NAME" \
--product "PATH_TO_YOUR_APP/Contents/Info.plist" APP_NAME.pkg

Now all that’s left is application screenshots and other details which you’ll need to provide on Apple’s iTunesConnect site, and uploading the .pkg file you created in the last step using the Application Loader utility, which should be provided within Apple’s Developer tools on your hard drive.

We hope this covers most of the unique aspects of getting a Max application distributed via Apple’s Mac App Store – we look forward to seeing more Max creations available there!

Oli Larkin: pMix – A sound design, composition and performance tool that allows you to morph between VST plugin presets using an intuitive graphical interface.


View the full-sized screen shot.

Dan Nigrin: Audio Plugin Player – A lightweight VST and AU instrument plugin host, that allows you to play these instruments using either your mouse, computer keyboard, or MIDI device, with support for VST and AU effects too.

Dan Nigrin: General MIDI Player – An easy way to turn your Mac into an instrument, using its built-in General MIDI support, with options for mouse, computer keyboard, or MIDI input.

James Howard Young: TapNTempo – A fully featured metronome that offers customizable sounds, real-time tempo tapping, and beat and division capabilities.



Anonymous
April 19, 2012 | 9:14 pm

Commendable work guys, highly informative.



Yozo
April 20, 2012 | 2:14 am

Thanks for the info, it’s a great help!
Will this also work for max5 or is this max6 only?


April 20, 2012 | 8:12 am

You *must* use Max 6. I don’t think you will ever have success in getting approved with an app compiled with an earlier version of Max, as any standalone created by those earlier versions automatically place preference files in a location that Apple does not allow.


April 21, 2012 | 4:22 pm

Wow, so you can make like..ipad-apps?


April 23, 2012 | 1:46 pm

No – this is for the Apple *Mac* App Store I’m afraid, not the iOS store…


April 26, 2012 | 4:34 pm

Just made my entrance to the App Store myself! It’s a small utility called MATRIK Check it out. Great to have all these info compiled in one article.


April 26, 2012 | 11:18 pm

Question: is using Max For Live UI objects ok for Mac App Store standalones?


April 27, 2012 | 8:23 am

Yup, they are fine. For example, look at my Audio Plugin Player app – the volume knob is a live.dial object.



Darnell
April 29, 2012 | 2:58 pm

Dan nilgrin I need some assistance on this is there a way I can privately message you


April 30, 2012 | 8:04 am

dan@defectiverecords.com



Giorgio
May 11, 2012 | 1:47 am

I hope that C74 will consider also a IOS export for standalones
that would be groundbreaking


May 20, 2012 | 5:54 pm

Hi, just wondering how far have you gotten with sandboxing your apps. I’m about to start some testings on my app and would really like to hear about your experience (I’ll post my results on the forums when done).


May 21, 2012 | 7:39 am

I too have to dig into this more now. I tried to build my App Store apps with sandboxing in mind, such that I think they should be compliant (e.g. not reading/writing for disallowed places), but I’ve done no formal testing yet…


May 21, 2012 | 9:28 am

I’m a bit concerned about the "microphone" entitlement, and about the actual methods that Max implements to access audio devices, would they be "allowed" methods? The same goes for read/write methods… we’ll see



Leigh Marble
May 30, 2012 | 11:16 am

I haven’t made the jump to Max 6 yet, so sorry if this is obvious… but how would you save your own custom preferences file to the ~/Library/Application Support/My Cool App folder?

This article states "You can also use this folder if your app needs to store any other files (such as a custom preferences file)," but I don’t see other info about how to do that. File path management has always been a bit sticky in Max, so I’m wondering if this has been cleaned up with the new restrictions on file locations in Lion OS X.


May 30, 2012 | 12:27 pm

Hi Leigh – this is one of the few places where I use a 3rd party external – in this case, Jasch’s wonderful getpaths (Mac) and getenv (Windows) externals. http://www.jasch.ch/dl/ Hope this helps a bit.



Leigh Marble
May 31, 2012 | 5:59 pm

Thanks, Dan! I’ve tried to roll my own version of something like that before just for Mac, glad to see someone else has gotten it working cross-platform.


August 8, 2012 | 12:03 pm

just to say, Max-built apps are not currently viable on the app store due to the sand-boxing stuff. C74 are aware of it.


August 20, 2012 | 4:38 am

what is it about sand-box?

Do we work on conforming to it?


August 20, 2012 | 4:51 am

It is in Cycling’s hands. The problem is that upon app initialization (*any* Max app), calls get made to "inappropriate" places, that trigger sandbox violations.



Digitom
September 11, 2012 | 8:46 am

Does anyone know if the 6.0.7 update fixes the problems now?


September 11, 2012 | 10:11 am

Unfortunately, it does not….


September 26, 2012 | 6:41 am

Hi Dan,
I would be interested in developing an app. What is the current state of the affairs? Can you ask Cycling to look into what is needed? What is needed, actually?

I want to ask you about your app General Midi player. Would it replace a keyboard which is needed to work with Cubase? I do ot know, if you are familiar with cubase, but in order to get the system working one has to connect a midi keyboard.


September 26, 2012 | 7:38 am

Hi Hans – to my knowledge, standalones built with Max will not pass Apple’s new sandbox restrictions, for placement in the Mac App Store. The problem is that standalones currently place temp files in places that are not "allowed" by Apple, and there is no way to keep it from doing so at the moment. Cycling is aware of the problem, but I can’t comment on if/when they plan to address it.

Re: General MIDI Player, I don’t think it will do what you want. It can *receive* MIDI signals, but is not meant to transmit those signals to another app. If you have more questions – feel free to ask on my web forum (this is probably not the right place!). http://defectiverecords.com/forum .



Tom M
November 13, 2012 | 12:35 pm

Could it be there’s something more than just the place where the temp files are stored? It seems that it has not been fixed in the 6.0.8 either. Perspnally, I think it’s a great opportunity for the community to be able to share their creations in the app store and it’s hard for me to see why it has such a low priority in the c74 fix list.


November 14, 2012 | 9:28 am

I completely agree with you, but Cycling must have their reasons I guess. That said, there was an interesting development recently, a Max-built app was newly released on the Mac App Store (Batcrack: https://itunes.apple.com/us/app/batcrack-radio-delay/id570527951?mt=12 ), and apparently it still triggers the sandboxd errors we have been seeing, but nonetheless it got through the store fine. All this time I was reluctant to even submit anything to the store for approval fearing rejection because of those errors, but now I might just give it a shot and see what happens…



rarasmus
January 31, 2013 | 6:52 am

After over a month of codesigning and endless trial and error, numerous rejections, I can now confirm that max built apps can currently go through.

Some up to date hints:
*you have to provide @2x icons for retina as well. (use the iconutil command in terminal, more info http://developer.apple.com/library/mac/#documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html)

* if you codesign with entitlements include some audio related stuff(like the mic i/p) in as well, even if your not using it, otherwise the app will crash. (more details here http://cycling74.com/forums/topic.php?id=44476 )

* if you’re recording to disk (sfrecord), then in the .plist file leave a third document type for .aiff as well. otherwise, if you delete it, your app will record a unreadable file.

* if you get something like "object file format unrecognized, invalid, or unsuitable)" when you try to codesign use this before you start: export CODESIGN_ALLOCATE="/Applications/Xcode.app/Contents/Developer/usr/bin/codesign_allocate"

* use the most recent application loader. I got some weird errors that I could not fix in a old version of application loader (1.4.1)

*there’s lot of trial and error, and it seems there’s a human element involved in the review process… so, just resubmit until you get approved, I guess.

The apps (targeting the so called ‘regular user’) are here: a simple keyboard piano https://itunes.apple.com/ee/app/shibo-the-keyboard-piano/id586990285?mt=12
and a sound effects app.

https://itunes.apple.com/ee/app/effectogram/id595632594?mt=12


January 31, 2013 | 1:59 pm

Fantastic! Really appreciate the details – now just have to get off my butt and submit some updates to my apps that I have had waiting…


March 27, 2013 | 8:13 am

Wanted to update everyone that I too have gotten a significant update (more than just bug fixes) through the Mac App Store for one of my apps (Audio Plugin Player). Rarasmus details above were all correct. I can also add some info on what needs to be included in the .entitlements file for both MIDI support, as well as plugin hosting:
com.apple.security.temporary-exception.mach-lookup.global-name

com.apple.midiserver
com.apple.midiserver.io

com.apple.security.temporary-exception.audio-unit-host

Of note, my app also still triggers some sandboxd violations, seen in the Console. I believe these occur at the time that standalone starts up, and Max does some stuff to initialize audio and MIDI. See snipped Console output below, where all the /private/var/temp/ files are created. But, for now at least, Apple seems to ignore these (or perhaps we all just got lucky)….

3/27/13 10:57:50.469 AM Audio Plugin Player: startup call: startup_load(extensions)
3/27/13 10:57:51.067 AM _spotlight: audit warning: allsoft
3/27/13 10:57:51.068 AM _spotlight: audit warning: soft /var/audit
3/27/13 10:57:51.070 AM _spotlight: audit warning: closefile /var/audit/20130327145322.20130327145751
3/27/13 10:57:51.245 AM com.apple.SecurityServer: Session 100883 created
3/27/13 10:57:58.283 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:00.873 AM Audio Plugin Player: startup call: startup_load(init)
3/27/13 10:58:00.905 AM Audio Plugin Player: startup call: sched_midiinit
3/27/13 10:58:01.253 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:01.335 AM MIDIServer: MIDIServer [17064] starting; arch=x86_64
3/27/13 10:58:01.693 AM MIDIServer: PlugIn MIDI%20Monitor.plugin -- file://localhost/Users/dnigrin//Library/Audio/MIDI%20Drivers/ does not contain a supported architecture.
3/27/13 10:58:01.693 AM MIDIServer: MIDIServer relaunching because a 32-bit driver was found
3/27/13 10:58:01.707 AM MIDIServer: MIDIServer [17064] starting; arch=i386
3/27/13 10:58:02.819 AM Audio Plugin Player: startup call: ad_load
3/27/13 10:58:02.869 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:02.979 AM Audio Plugin Player: jaudiowrapper_createdevice Built-in Line Input Built-in Line Output
3/27/13 10:58:03.010 AM Audio Plugin Player: jaudiowrapper_createdevice Built-in Line Input Built-in Line Output
3/27/13 10:58:03.028 AM Audio Plugin Player: startup call: path_buildcache
3/27/13 10:58:03.509 AM Audio Plugin Player: startup call: defaults_startup
3/27/13 10:58:03.511 AM Audio Plugin Player: startup call: sched_start
3/27/13 10:58:03.533 AM Audio Plugin Player: startup call: sysmenu_init
3/27/13 10:58:03.534 AM Audio Plugin Player: startup call: interface_init
3/27/13 10:58:03.569 AM Audio Plugin Player: startup call: prototype_init5
3/27/13 10:58:03.571 AM Audio Plugin Player: startup call: post_init
3/27/13 10:58:03.571 AM Audio Plugin Player: startup call: bitwrap_init
3/27/13 10:58:03.578 AM Audio Plugin Player: startup call: internals_initclass
3/27/13 10:58:03.579 AM Audio Plugin Player: startup call: globalsymbol_initclass
3/27/13 10:58:03.580 AM Audio Plugin Player: startup call: single_open
3/27/13 10:58:03.589 AM Audio Plugin Player: startup call: path_setdefault
3/27/13 10:58:03.590 AM Audio Plugin Player: startup call: kernel_init
3/27/13 10:58:03.591 AM Audio Plugin Player: startup call: pathcache_new
3/27/13 10:58:03.591 AM Audio Plugin Player: startup call: translation_getpreferredlanguage
3/27/13 10:58:03.592 AM Audio Plugin Player: code: en, name English
3/27/13 10:58:03.592 AM Audio Plugin Player: startup call: eventreporter_init
3/27/13 10:58:03.593 AM Audio Plugin Player: startup call: eventcontext_end
3/27/13 10:58:03.732 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/tmp/com.cycling74.501/mfl2
3/27/13 10:58:03.771 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:03.924 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:03.941 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:03.983 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.018 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.037 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.056 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.076 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.094 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.113 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.132 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.152 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.169 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.195 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.215 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.247 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.262 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:04.277 AM sandboxd: ([17046]) Audio Plugin Pla(17046) deny file-write-create /private/var/tmp/tmp.0.Awi6c7
3/27/13 10:58:05.898 AM Audio Plugin Player: jaudiowrapper_createdevice Built-in Line Input Built-in Line Output
3/27/13 10:58:05.930 AM Audio Plugin Player: jaudiowrapper_createdevice Built-in Line Input Built-in Line Output


March 27, 2013 | 8:16 am

OK, well the formatting of that post didn’t quite work out, and don’t see a way to edit it. But anyway, just ask me if you have questions… If I get some time, I may update this entire article with some of this newer information, to make it easier for people…


June 15, 2013 | 4:25 am

About to try and get something on the Mac store, an updated article would be fantastic to take into account all of the changes detailed. Whats the story getting things into the Windows app store?


June 15, 2013 | 9:59 am

No experience on the Windows side – that said, I’m not even sure I know what the Windows app store is? Any pointers?

Re: the updated article, sorry, just haven’t had time to do it yet. Primarily because I’m still finding new nuances to the process (which seems to be constantly evolving on the Apple side). If you have specific questions, please feel free to ask them and I’ll do my best to answer, based on my experiences…


June 17, 2013 | 10:33 am

Just a quick note here to point out that Cycling was kind enough to fix some errors that slipped in when this article was originally posted – there were extra set of quote marks in the various codesign statements in the Modifying Your Standalone section. Thanks also to Thomas Sandberg for spotting the errors!


June 25, 2013 | 11:44 am

Hello guys, I see this comment of Rarasmus:
if you get something like "object file format unrecognized, invalid, or unsuitable)" when you try to codesign use this before you start: export CODESIGN_ALLOCATE="/Applications/Xcode.app/Contents/Developer/usr/bin/codesign_allocate".
I have the same problem, but i can’t solve it. :(


June 25, 2013 | 12:55 pm

So when you type:

export CODESIGN_ALLOCATE="/Applications/Xcode.app/Contents/Developer/usr/bin/codesign_allocate

into the Terminal, press return, and then try the codesign command again, it doesn’t work? What error do you get? For me, that command fixes the problem as it did for Rarasmus…


June 25, 2013 | 5:01 pm

Yes Dan, I don’t know why but now it works perfectly this terminal command ;) Now I have only a problem with Sandbox, I read that a common error. But Application Loader works fine now, and that is very important for me xD


June 25, 2013 | 7:10 pm

OK cool, keep us posted on how things go at the App Store!


September 23, 2013 | 4:53 am

I started a thread to keep track of all the Max apps for sale on the Mac App Store – if you get your app on there, please add to the list!


December 16, 2013 | 10:10 am

I encountered a problem on submit to the Mac App Store.

Deprecated API Usage – Apple no longer accepts submissions of apps that use QuickTime APIs.

My App uses jit.qt.movie. What can I do for this problem ?


April 30, 2014 | 4:50 am

There’s discussion of this problem towards the bottom of this thread.


Viewing 40 posts - 1 through 40 (of 40 total)