Here's how I set myself up for a happy MFX workflow using Xcode 6.2, I'm going to write it out as a full tutorial for the benefit of others (and future me, for when I've forgotten how I got things working)
1. Work your way through Jules' instructions on patching Xcode for AU builds
2. Start a new Introjucer project, choose "audio plugin"
3. In Introjucer's project config:
• leave "Build AudioUnit" checked
• leave "Build VST" checked (so you can test your plugin in Jules' PluginHost.app)
• check "Is a Synth"
• check "Plugin wants midi input"
• check "Plugin produces midi output"
• set "Plugin AU Main Type" to 'aumi' (including the single quotes)
• leave channel configuration as "{1, 1}, {2, 2}
• set the various PluginName and CompanyName fields
4. In Introjucer's Xcode config:
• add the following "post-build shell script" to automagically copy plugins to the correct places when you build
# This script takes the build product and copies it to the AU, VST, VST3, RTAS and AAX folders, depending on
# which plugin types you've built
original=$CONFIGURATION_BUILD_DIR/$FULL_PRODUCT_NAME
# this looks inside the binary to detect which platforms are needed..
copyAU=`nm -g "$CONFIGURATION_BUILD_DIR/$EXECUTABLE_PATH" | grep -i 'AudioUnit' | wc -l`
copyVST=`nm -g "$CONFIGURATION_BUILD_DIR/$EXECUTABLE_PATH" | grep -i 'VSTPlugin' | wc -l`
copyVST3=`nm -g "$CONFIGURATION_BUILD_DIR/$EXECUTABLE_PATH" | grep -i 'GetPluginFactory' | wc -l`
copyRTAS=`nm -g "$CONFIGURATION_BUILD_DIR/$EXECUTABLE_PATH" | grep -i 'CProcess' | wc -l`
copyAAX=`nm -g "$CONFIGURATION_BUILD_DIR/$EXECUTABLE_PATH" | grep -i 'ACFStartup' | wc -l`
if [ $copyAU -gt 0 ]; then
echo "Copying to AudioUnit folder..."
AU=~/Library/Audio/Plug-Ins/Components/$PRODUCT_NAME.component
if [ -d "$AU" ]; then
rm -r "$AU"
fi
cp -r "$original" "$AU"
sed -i "" -e 's/TDMwPTul/BNDLPTul/g' "$AU/Contents/PkgInfo"
sed -i "" -e 's/TDMw/BNDL/g' "$AU/Contents/$INFOPLIST_FILE"
# Fix info.plist for AUs built with Xcode 3
if [ -f "$DEVELOPER_DIR/Library/Developer/CoreAudio/AudioUnits/AUPublic/AUBase/AUPlugInDispatch.cpp" ]; then
echo
else
echo "Removing AudioComponents entry from Info.plist because this is not a new-format AU"
/usr/libexec/PlistBuddy -c "Delete AudioComponents" "$AU/Contents/Info.plist"
fi
fi
if [ $copyVST -gt 0 ]; then
echo "Copying to VST folder..."
VST=~/Library/Audio/Plug-Ins/VST/$PRODUCT_NAME.vst
if [ -d "$VST" ]; then
rm -r "$VST"
fi
cp -r "$original" "$VST"
sed -i "" -e 's/TDMwPTul/BNDLPTul/g' "$VST/Contents/PkgInfo"
sed -i "" -e 's/TDMw/BNDL/g' "$VST/Contents/$INFOPLIST_FILE"
fi
if [ $copyVST3 -gt 0 ]; then
echo "Copying to VST3 folder..."
VST3=~/Library/Audio/Plug-Ins/VST3/$PRODUCT_NAME.vst3
if [ -d "$VST3" ]; then
rm -r "$VST3"
fi
cp -r "$original" "$VST3"
sed -i "" -e 's/TDMwPTul/BNDLPTul/g' "$VST3/Contents/PkgInfo"
sed -i "" -e 's/TDMw/BNDL/g' "$VST3/Contents/$INFOPLIST_FILE"
fi
if [ $copyRTAS -gt 0 ]; then
echo "Copying to RTAS folder..."
RTAS=/Library/Application\ Support/Digidesign/Plug-Ins/$PRODUCT_NAME.dpm
if [ -d "$RTAS" ]; then
rm -r "$RTAS"
fi
cp -r "$original" "$RTAS"
fi
if [ $copyAAX -gt 0 ]; then
echo "Copying to AAX folder..."
if [ -d "/Applications/ProTools_3PDev/Plug-Ins" ]; then
AAX1="/Applications/ProTools_3PDev/Plug-Ins/$PRODUCT_NAME.aaxplugin"
if [ -d "$AAX1" ]; then
rm -r "$AAX1"
fi
cp -R -H "$original" "$AAX1"
fi
if [ -d "/Library/Application Support/Avid/Audio/Plug-Ins" ]; then
AAX2="/Library/Application Support/Avid/Audio/Plug-Ins/$PRODUCT_NAME.aaxplugin"
if [ -d "$AAX2" ]; then
rm -r "$AAX2"
fi
cp -R -H "$original" "$AAX2"
fi
fi
• make sure "VST Folder" is pointing to Steinberg's VST SDK
• for both "Debug" and "Release", set your "Compatability Version" to your minimum target OS (I'm targetting 10.9+)
• leave "Base SDK Version" on "Use Default"
5. Make a build of Jules' PluginHost.app. I modified the source code slightly so the app always opens the last saved preset (which massively speeds up development for me). I added the following code to the end of MainHostWindow's constructor in MainHostWindow.cpp
//====================================================================================
// Hack to automatically load most recent filtergraph when opening the app
// Added by My Name 2015/XX/XX
GraphDocumentComponent* const graphEditor = getGraphEditor();
RecentlyOpenedFilesList recentFiles;
recentFiles.restoreFromString (getAppProperties().getUserSettings()
->getValue ("recentFilterGraphFiles"));
if (graphEditor != nullptr && graphEditor->graph.saveIfNeededAndUserAgrees() == FileBasedDocument::savedOk)
graphEditor->graph.loadFrom (recentFiles.getFile (0), true);
//====================================================================================
6. Back in your Introjucer project, hit "Save Project and Open in Xcode..."
7. Set up debugging in Xcode using the PluginHost.app you built in step 5...
• Go Product > Scheme > Edit Scheme
• Select "Run" in the left hand pane
• From the "Exectuable" drop down, choose other, point it to the PluginHost.app you built in step 5
• Make sure "Debug executable" is checked
• Click "Close"
• Build and Run. The Plugin Host app should open with an empty window
• In Plugin Host, go Options > Edit the list of available plug-ins...
• Click Options... > Scan for new or updated VST plug-ins
• Ensure that "/Users/myusername/Library/Audio/Plug-Ins/VST" is on the list and click "Scan"
• Right click on the background of the Plugin Host and your plugin should be in the list :)
• Add an instance of your plugin (we have to use the VST version as Plugin Host doesn't support MFX plugins)
• Wire up the MIDI In, and the MIDI Out to a VI with a MIDI Monitor of some sort (I use NI Kontakt)
• Save this preset in the Plugin Host (it should get automagically opened on the next build as per step 5)
• Quit the Plugin Host
8. At this point the VST build should already function as a MIDI Thru, but the AU MFX build (if you try inserting it in LPX) will not pass any MIDI, time to patch AuBase.cpp as per Oscar1771's fix.
• Back in Xcode, open the source tree to "Juce Library Code / Juce AU Wrapper" and open AuBase.cpp
• Search for the following (you should get one match around line 1357):
if (output->GetStreamFormat().NumberChannelStreams() != ioData.mNumberBuffers)
• On the line before the matching if statement, add:
//==================================================================================== // Hack to make MFX plugin call AudioProcessor::ProcessBlock() // Added by My Name on 2015/XX/XX ioData.mNumberBuffers = 1; //====================================================================================
Bear in mind that AuBase.cpp resides at /Applications/Xcode.app/Contents/Developer/Extras/CoreAudio/AudioUnits/AUPublic/AUBase and will likely need to be re-patched when updating Xcode
9. Now for a little MIDI Processor implementation.
• Open Xcode's source tree to "My Plugin / Source" and open PluginProcessor.cpp
• Find AudioProcessor::processBlock and replace it with:
void <MyPluginNameHere>AudioProcessor::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { buffer.clear(); // ignore all audio input, output only silence MidiBuffer processedMidi; // our processed MIDI output int time; // the timestamp of the current MIDI message in the for loop MidiMessage m; // the current MIDI message in the for loop const uint8 myFixedVelocity = 50; // a fixed velocity to apply to note messages // iterate through the input buffer, process our MIDI, add it to the output buffer for (MidiBuffer::Iterator i (midiMessages); i.getNextEvent (m, time);) { if (m.isNoteOn()) { m = MidiMessage::noteOn(m.getChannel(), m.getNoteNumber(), myFixedVelocity); } processedMidi.addEvent (m, time); } // replace the outgoing MIDI buffer with our processed MIDI buffer midiMessages.swapWith (processedMidi); }
• Build and Run. In Plugin Host, test the VST version of your plugin for the desired behaviour (you should only see notes coming out with velocity 50).
• Open LPX and insert the MFX version of your plugin (it should have the same behaviour as the VST version you tested in Plugin Host).