diff --git a/cmake/wrap_auv2.cmake b/cmake/wrap_auv2.cmake index 6a5405b5..e02bc447 100644 --- a/cmake/wrap_auv2.cmake +++ b/cmake/wrap_auv2.cmake @@ -13,6 +13,17 @@ function(target_add_auv2_wrapper) CLAP_TARGET_FOR_CONFIG + # AUV2 uses a CFDictionary to store state so + # we need to choose what keys to populate it with. + # The wrapper by default uses the key choices from + # the AUSDK example, but for compatability with people + # moving from other frameworks we do support other schema. + # + # SUpported schema today are + # - WRAPPER (the default) + # - JUCE (use the JUCE AUV2 default) + DICTIONARY_STREAM_FORMAT + MACOS_EMBEDDED_CLAP_LOCATION ) cmake_parse_arguments(AUV2 "" "${oneValueArgs}" "" ${ARGN}) @@ -149,6 +160,14 @@ function(target_add_auv2_wrapper) ${bhtgoutdir}/generated_entrypoints.hxx) target_compile_options(${AUV2_TARGET} PRIVATE -fno-char8_t) + if (DEFINED AUV2_DICTIONARY_STREAM_FORMAT) + # valud options check + set (sf ${AUV2_DICTIONARY_STREAM_FORMAT}) + if (NOT ((${sf} STREQUAL "JUCE") OR (${sf} STREQUAL "WRAPPER"))) + message(FATAL_ERROR "Unrecognized DICTIONARY_STREAM_FORMAT in AUV2: ${sf}") + endif() + target_compile_definitions(${AUV2_TARGET} PRIVATE DICTIONARY_STREAM_FORMAT_${AUV2_DICTIONARY_STREAM_FORMAT}=1) + endif() if (NOT TARGET ${AUV2_TARGET}-clap-wrapper-auv2-lib) # For now make this an interface diff --git a/src/wrapasauv2.cpp b/src/wrapasauv2.cpp index 362df0fc..730960ab 100644 --- a/src/wrapasauv2.cpp +++ b/src/wrapasauv2.cpp @@ -1114,6 +1114,17 @@ OSStatus WrapAsAUV2::SaveState(CFPropertyListRef* ptPList) Clap::StateMemento chunk; _plugin->_ext._state->save(_plugin->_plugin, chunk); +#if DICTIONARY_STREAM_FORMAT_JUCE + auto err = ausdk::AUBase::SaveState(ptPList); + if (err != noErr) return err; + + CFDataRef tData = CFDataCreate(0, (UInt8*)chunk.data(), chunk.size()); + CFMutableDictionaryRef dict = (CFMutableDictionaryRef)*ptPList; + + CFDictionarySetValue(dict, CFSTR("jucePluginState"), tData); + CFRelease(tData); + chunk.clear(); +#else CFDataRef tData = CFDataCreate(0, (UInt8*)chunk.data(), chunk.size()); const AudioComponentDescription desc = GetComponentDescription(); @@ -1148,10 +1159,12 @@ OSStatus WrapAsAUV2::SaveState(CFPropertyListRef* ptPList) CFDictionarySetValue(*dict, CFSTR(kAUPresetNameKey), _current_program_name); *ptPList = static_cast(dict.release()); // transfer ownership +#endif } return noErr; } + OSStatus WrapAsAUV2::RestoreState(CFPropertyListRef plist) { if (!plist) return kAudioUnitErr_InvalidParameter; @@ -1162,7 +1175,39 @@ OSStatus WrapAsAUV2::RestoreState(CFPropertyListRef plist) // Find 'data' key const void* pData = CFDictionaryGetValue(tDict, CFSTR(kAUPresetDataKey)); - if (CFGetTypeID(CFTypeRef(pData)) != CFDataGetTypeID()) return -1; + if (!pData || CFGetTypeID(CFTypeRef(pData)) != CFDataGetTypeID()) return -1; + + /* + * In the read side I fall through to default, whereas in the write + * side I use an 'else' on the set of stream formats. This means + * you at least try in case saved with an older wrapper version + */ +#if DICTIONARY_STREAM_FORMAT_JUCE + /* + * In the case when migrating from a JUCE AUv2 to a + * clap-wrapper one, if you want to preserve state + * you want to read the juce key from the dictionary. + */ + CFDataRef juceData{nullptr}; + CFStringRef juceKey( + CFStringCreateWithCString(kCFAllocatorDefault, "jucePluginState", kCFStringEncodingUTF8)); + bool valuePresent = CFDictionaryGetValueIfPresent(tDict, juceKey, (const void**)&juceData); + CFRelease(juceKey); + if (valuePresent && juceData) + { + LOGINFO("[clap-wrapper] Restoring from JUCE block"); + const int numBytes = (int)CFDataGetLength(juceData); + if (numBytes > 0) + { + Clap::StateMemento chunk; + UInt8* streamData = (UInt8*)(CFDataGetBytePtr(juceData)); + + chunk.setData(streamData, numBytes); + _plugin->_ext._state->load(_plugin->_plugin, chunk); + } + return noErr; + } +#endif const void* pName = CFDictionaryGetValue(tDict, CFSTR(kAUPresetNameKey)); if (pName) @@ -1355,7 +1400,6 @@ UInt32 WrapAsAUV2::GetAudioChannelLayout(AudioUnitScope scope, AudioUnitElement AudioChannelLayout* outLayoutPtr, bool& outWritable) { // TODO: This is never called so the layout is never found - LOGINFO("[clap-wrapper] GetAudioChannelLayout"); return Base::GetAudioChannelLayout(scope, element, outLayoutPtr, outWritable); }