FMOD programmer sounds no longer work Unity 2021.2.*

We just upgraded to Unity 2021.2.* and realized our programmer sounds no longer function. We’re getting this stack trace:

NotSupportedException: IL2CPP does not support marshaling delegates that point to instance methods to native code. The method we're attempting to marshal is: FMOD.SOUND_PCMREAD_CALLBACK::Invoke
  at FMOD.System.FMOD5_System_CreateSound (System.IntPtr system, System.IntPtr name_or_data, FMOD.MODE mode, FMOD.CREATESOUNDEXINFO& exinfo, System.IntPtr& sound) [0x00000] in <00000000000000000000000000000000>:0 
  at Audio.FMODSound+<HandlePlayDialogueRoutineAsyncRoutine>d__33.MoveNext () [0x00000] in <00000000000000000000000000000000>:0 
  at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) [0x00000] in <00000000000000000000000000000000>:0 
  at PixelCrushers.DialogueSystem.SequencerCommands.SequencerCommandDialogueWait.Awake () [0x00000] in <00000000000000000000000000000000>:0 
  at PixelCrushers.DialogueSystem.Sequencer.ActivateCommand (System.String commandName, System.String endMessage, UnityEngine.Transform speaker, UnityEngine.Transform listener, System.String[] args) [0x00000] in <00000000000000000000000000000000>:0 
  at PixelCrushers.DialogueSystem.Sequencer.OnSequencerMessage (System.String message) [0x00000] in <00000000000000000000000000000000>:0 
  at LeanTween.update () [0x00000] in <00000000000000000000000000000000>:0 
PixelCrushers.DialogueSystem.SequencerCommands.SequencerCommandDialogueWait:Awake()
PixelCrushers.DialogueSystem.Sequencer:ActivateCommand(String, String, Transform, Transform, String[])
PixelCrushers.DialogueSystem.Sequencer:OnSequencerMessage(String)
LeanTween:update()

Tested on:
FMOD 2.01.14, 2.02.05
Platforms: iOS, macOS
Unity: 2.02.07

Here’s a repro. Just build it for MacOS or iOS and it should reproduce the issue.

Once the player loads to a blue screen, hit space bar.

Thank you for the repro, it looks like this is related to a known issue with FMOD when using IL2CPP and Unity 2021.2. The current workaround is to assign null to the pcmreadcallback, pcmsetposcallback and nonblockcallback in the SOUND_INFO.exinfo struct before calling createSound. Can you please try that and see if that gets around the exception and lets programmer sounds play correctly?

Thanks very much for taking a look! I tried out the suggested workaround:

        private IEnumerator HandlePlayDialogueRoutineAsyncRoutine(string dialogueTableKey, Action<EventInstance> soundReadyCallback)
        {
            // Return Value
            EventInstance eventInstance;
            eventInstance.handle = IntPtr.Zero;

            // Load Sound Path
            FMOD.Studio.SOUND_INFO dialogueSoundInfo;
            FMOD.RESULT keyResult = FMODUnity.RuntimeManager
                .StudioSystem
                .getSoundInfo(dialogueTableKey, out dialogueSoundInfo);
            if (keyResult != FMOD.RESULT.OK)
            {
                Debug.LogError("Couldn't find dialogue with key: " + dialogueTableKey);
                soundReadyCallback?.Invoke(eventInstance);
                yield break;
            }

            // TODO - Workaround - https://qa.fmod.com/t/fmod-programmer-sounds-no-longer-work-unity-2021-2/18137
            dialogueSoundInfo.exinfo.pcmreadcallback = null;
            dialogueSoundInfo.exinfo.pcmsetposcallback = null;
            dialogueSoundInfo.exinfo.nonblockcallback = null;
            // TODO

            // Load Sound
            FMOD.Sound dialogueSound;
            FMOD.MODE soundMode = FMOD.MODE.LOOP_NORMAL
                | FMOD.MODE.CREATECOMPRESSEDSAMPLE
                | FMOD.MODE.NONBLOCKING;

            FMOD.RESULT soundResult = FMODUnity.RuntimeManager.CoreSystem.createSound(
                dialogueSoundInfo.name_or_data, soundMode | dialogueSoundInfo.mode,
                ref dialogueSoundInfo.exinfo, out dialogueSound);
            if (soundResult != FMOD.RESULT.OK)
            {
                Debug.LogError("Couldn't load sound: " + dialogueTableKey);
                soundReadyCallback?.Invoke(eventInstance);
                yield break;
            }

            // Wait to Load
            int maxFrameWait = 120;
            FMOD.OPENSTATE openstate;
            uint percentbuffered;
            bool starving;
            bool diskbusy;
            dialogueSound.getOpenState(out openstate, out percentbuffered, out starving, out diskbusy);
            while (openstate != FMOD.OPENSTATE.READY)
            {
                yield return null;
                dialogueSound.getOpenState(out openstate, out percentbuffered, out starving, out diskbusy);
                if (--maxFrameWait <= 0)
                {
                    dialogueSound.release();
                    Debug.LogError("Failed to load dialogue sound " + dialogueTableKey);
                    soundReadyCallback?.Invoke(eventInstance);
                    yield break;
                }
            }

            // Create Instance 
            eventInstance = CreateInstance(DialogueEvent.eventPath);

            // Store Loaded Sound Data
            FMODUserData userData = eventInstance.GetUserData();
            userData.fmodSound = dialogueSound;
            userData.fmodSoundInfo = dialogueSoundInfo;

            // Return the instance
            soundReadyCallback(eventInstance);
        }

However, it didn’t seem to have any effect. I still get this exception:

NotSupportedException: IL2CPP does not support marshaling delegates that point to instance methods to native code. The method we're attempting to marshal is: FMOD.FILE_OPEN_CALLBACK::Invoke                                                                                                                                                                            │
  at FMOD.System.createSound (System.IntPtr name_or_data, FMOD.MODE mode, FMOD.CREATESOUNDEXINFO& exinfo, FMOD.Sound& sound) [0x00000] in <00000000000000000000000000000000>:0                                                                                                                                                                                           │
  at Audio.FMODSound+<HandlePlayDialogueRoutineAsyncRoutine>d__42.MoveNext () [0x00000] in <00000000000000000000000000000000>:0                                                                                                                                                                                                                                          │
  at UnityEngine.SetupCoroutine.InvokeMoveNext (System.Collections.IEnumerator enumerator, System.IntPtr returnValueAddress) [0x00000] in <00000000000000000000000000000000>:0                                                                                                                                                                                           │
  at PixelCrushers.DialogueSystem.SequencerCommands.SequencerCommandDialogueWait.Awake () [0x00000] in <00000000000000000000000000000000>:0                                                                                                                                                                                                                              │
  at PixelCrushers.DialogueSystem.Sequencer.ActivateCommand (System.String commandName, System.String endMessage, UnityEngine.Transform speaker, UnityEngine.Transform listener, System.String[] args) [0x00000] in <00000000000000000000000000000000>:0                                                                                                                 │
  at PixelCrushers.DialogueSystem.Sequencer.OnSequencerMessage (System.String message) [0x00000] in <00000000000000000000000000000000>:0                                                                                                                                                                                                                                 │
  at LeanTween.update () [0x00000] in <00000000000000000000000000000000>:0                                                                                                                                                                                                                                                                                               │
PixelCrushers.DialogueSystem.SequencerCommands.SequencerCommandDialogueWait:Awake()                                                                                                                                                                                                                                                                                      │
PixelCrushers.DialogueSystem.Sequencer:ActivateCommand(String, String, Transform, Transform, String[])                                                                                                                                                                                                                                                                   │
PixelCrushers.DialogueSystem.Sequencer:OnSequencerMessage(String)                                                                                                                                                                                                                                                                                                        │
LeanTween:update()

Did I understand correctly?

Apologies for the oversight there, looks like you’ll also need to null out the other exinfo callbacks:

dialogueSoundInfo.exinfo.pcmreadcallback = null;
dialogueSoundInfo.exinfo.pcmsetposcallback = null;
dialogueSoundInfo.exinfo.nonblockcallback = null;
dialogueSoundInfo.exinfo.fileuseropen = null;
dialogueSoundInfo.exinfo.fileuserclose = null;
dialogueSoundInfo.exinfo.fileuserread = null;
dialogueSoundInfo.exinfo.fileuserseek = null;
dialogueSoundInfo.exinfo.fileuserasyncread = null;
dialogueSoundInfo.exinfo.fileuserasynccancel = null;

That seems to have worked! Thanks for the workaround! Is this something that will ultimately be fixed by Unity or in a future release of FMOD? I just want to make sure we get the fix before launch.

Good to hear that worked! This is an issue with our C# wrapper that will be fixed in an upcoming release- it’s a simple solution and is affecting core functionality so I would expect it to be resolved soon.