Unity: The script could not be instantiated! FMOD doesn't work on iOS

I’m trying to build our game on iOS for the first time since adding FMOD and am getting an error that says:

The script ‘Audio.FMODSound’ could not be instantiated!

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using FMOD.Studio;
using FMODUnity;
using PixelCrushers.DialogueSystem;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;

namespace Audio
{
    public class FMODSound : Sound
    {
        // Constants
        private const float GarbageCollectorIntervalSecs = 1f;
        private static readonly WaitForSeconds GarbageCollectorIntervalWait = new WaitForSeconds(GarbageCollectorIntervalSecs);
        private static readonly FMOD.Sound NullSoundStruct; // zeroed out
        private static readonly FMOD.Studio.SOUND_INFO NullSoundInfoStruct; // zeroed out
        private static readonly FMOD.Studio.EventInstance NullEventInstance; // zeroed out
        private static readonly SoundInstance NullSoundInstance = new FMODSoundInstanceContainer().GetReference();

        // References
        // [FMODUnity.BankRef]
        // public List<string> GlobalBanks;
        // [FMODUnity.BankRef]
        // public List<string> LocalizedGlobalBanks;
        // [FMODUnity.EventRef]
        public const string DialogueEvent = "event:/Voice/Dialogue";

        // Properties
        private List<FMODSoundInstanceContainer> ActiveSoundInstances;
        private Dictionary<string, string> LoadedBanksToOwner;
        private SoundInstance CurrentMusic;
        private Coroutine GarbageCollectorCoroutine;

        // #if UNITY_EDITOR
        //         void OnValidate()
        //         {
        //             for (int i = 0; i < LocalizedGlobalBanks.Count; i++)
        //             {
        //                 if (LocalizedGlobalBanks[i].Contains("_")) LocalizedGlobalBanks[i] = LocalizedGlobalBanks[i].Split('_')[0];
        //             }
        //         }
        // #endif

        // protected void Start() => StartCoroutine(EnableMeteringRoutine());
        // private IEnumerator EnableMeteringRoutine()
        // {
        //     // Wait for Initialization
        //     yield return new WaitUntil(() => FMODUnity.RuntimeManager.IsInitialized);
        //     FMOD.Studio.Bus bus = FMODUnity.RuntimeManager.GetBus("bus:/Voice");
        //     FMOD.RESULT result = bus.lockChannelGroup(); // Forces bus to be created
        //     if (result != FMOD.RESULT.OK)
        //     {
        //         Debug.LogError($"Failed to create bus to enable metering {result}");
        //         yield break;
        //     }
        //     // Channel group won't be created when FMOD is in async mode until  
        //     // the command has been executed in the async thread.  So we just keep
        //     // trying to fetch the group until it exists.  We only try 1000 times though.
        //     int safetyValve = 1000;
        //     FMOD.ChannelGroup channelGroup;
        //     channelGroup.handle = IntPtr.Zero;
        //     yield return new WaitUntil(() =>
        //     {
        //         safetyValve--;
        //         if (--safetyValve <= 0 || (result = bus.getChannelGroup(out channelGroup)) == FMOD.RESULT.OK) return true;
        //         return false;
        //     });
        //     if (result != FMOD.RESULT.OK)
        //     {
        //         Debug.LogError($"Failed to fetch channel group to enable metering for voice {result}");
        //         bus.unlockChannelGroup();
        //         yield break;
        //     }
        //     FMOD.DSP dsp;
        //     if (channelGroup.getDSP(0, out dsp) != FMOD.RESULT.OK)
        //     {
        //         Debug.LogError($"Failed to fetch dsp to enable metering for voice {result}");
        //         bus.unlockChannelGroup();
        //         yield break;
        //     }
        //     dsp.setMeteringEnabled(false, true);
        //     bus.unlockChannelGroup();
        // }

        protected override void OnEnable()
        {
            // Base
            base.OnEnable();

            // Create Loaded Banks Map
            LoadedBanksToOwner = new Dictionary<string, string>();

            // Create Sound Instance List
            ActiveSoundInstances = new List<FMODSoundInstanceContainer>();

            // Load Global Banks 
            // foreach (string bank in GlobalBanks) LoadBank(bank);
            LoadBank("Master");
            LoadBank("Master.strings");
            LoadBank("Global Events");

            // Load Localized Global Banks 
            // foreach (string bank in LocalizedGlobalBanks) LoadBankLocalized(bank);
            LoadBankLocalized("Common - Dialogue");
            LoadBankLocalized("Inventory - Dialogue");

            // Start Garbage Collector
            GarbageCollectorCoroutine = StartCoroutine(GarbageCollectorRoutine());
        }

        protected override void OnDisable()
        {
            // Stop Garbage Collector
            StopCoroutine(GarbageCollectorCoroutine);

            // Force Garbage Collect All
            RunGarbageCollectorSweep(true);

            // Unload All Banks
            UnloadAllBanks();
        }

        /**
         * Bank Loading
         */
        protected override void HandleLoadBankLocalized(string bank) => HandleLoadBank(bank + "_" + Localization.Language);
        protected override void HandleLoadBank(string bank)
        {
            if (!LoadedBanksToOwner.ContainsKey(bank))
            {
                try
                {
                    FMODUnity.RuntimeManager.LoadBank(bank);
                }
                catch (BankLoadException e)
                {
                    Debug.LogError("Failed to load bank: " + e);
                }
            }
            if (SceneManager.GetActiveScene() == null)
            {
                UnityAction<Scene, Scene> handler = null;
                SceneManager.activeSceneChanged += handler = (Scene from, Scene to) =>
                {
                    SceneManager.activeSceneChanged -= handler;
                    LoadedBanksToOwner[bank] = SceneManager.GetActiveScene().name;
                };
            }
            else LoadedBanksToOwner[bank] = SceneManager.GetActiveScene().name;
        }

        protected override void HandleUnloadBank(string bank)
        {
            if (string.IsNullOrEmpty(bank) || !LoadedBanksToOwner.ContainsKey(bank)) return;
            LoadedBanksToOwner.Remove(bank);
            FMODUnity.RuntimeManager.UnloadBank(bank);
        }

        private void UnloadAllBanks()
        {
            string[] banks = new string[LoadedBanksToOwner.Count];
            LoadedBanksToOwner.Keys.CopyTo(banks, 0);
            foreach (string bank in banks) UnloadBank(bank);
        }

        /**
         * Sound Playing
         */
        protected override SoundInstance HandleCreateSound(string soundEventKey) => CreateSoundInstance(soundEventKey).GetReference();

        protected override SoundInstance HandleCreateMusic(string musicEventKey)
        {
            // Stop Old Music
            if (CurrentMusic != null) CurrentMusic.Stop();

            // Return Sound Instance
            CurrentMusic = CreateSoundInstance(musicEventKey).GetReference();
            CurrentMusic.Start();
            return CurrentMusic;
        }

        protected override void HandlePlayOneShot(string soundEventKey) => FMODUnity.RuntimeManager.PlayOneShot(soundEventKey, Vector3.zero);

        protected override void HandleCreateDialogueRoutineAsync(string dialogueTableKey, Action<SoundInstance> soundReadyCallback)
        {
            if (soundReadyCallback == null) return;
            StartCoroutine(HandlePlayDialogueRoutineAsyncRoutine(dialogueTableKey, soundReadyCallback));
        }
        private IEnumerator HandlePlayDialogueRoutineAsyncRoutine(string dialogueTableKey, Action<SoundInstance> soundReadyCallback)
        {
            // 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(NullSoundInstance);
                yield break;
            }

            // 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(NullSoundInstance);
                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(NullSoundInstance);
                    yield break;
                }
            }

            // Create Instance 
            FMODSoundInstanceContainer soundInstanceContainer = CreateSoundInstance(DialogueEvent, dialogueSound, dialogueSoundInfo);

            // Enable Metering
            // Debug.Log("WHAT THE FUCK FMOD: https://qa.fmod.com/t/err-studio-not-loaded-cant-enable-metering/17358");
            // yield return null;
            // yield return null;
            // yield return null;
            // yield return null;
            // yield return null;
            // yield return null;
            // FMOD.ChannelGroup channelGroup;
            // FMOD.RESULT result = soundInstanceContainer.EventInstance.getChannelGroup(out channelGroup);
            // if (result != FMOD.RESULT.OK)
            // {
            //     Debug.LogError($"Failed to fetch channel group to enable metering for voice {result}");
            // }
            // FMOD.DSP dsp;
            // if (channelGroup.getDSP(0, out dsp) != FMOD.RESULT.OK)
            // {
            //     Debug.LogError($"Failed to fetch dsp to enable metering for voice {result}");
            // }
            // dsp.setMeteringEnabled(false, true);

            // Return the instance
            soundReadyCallback(soundInstanceContainer.GetReference());
        }

        protected override SoundInstance HandleGetCurrentMusic() => CurrentMusic;

        /**
         * Timescale Pause Toggle
         */
        protected override void HandleTimescalePauseToggle(bool pause)
        {
            foreach (FMODSoundInstanceContainer instanceContainer in ActiveSoundInstances)
            {
                instanceContainer.TimeScalePause(pause);
            }
        }

        private FMODSoundInstanceContainer CreateSoundInstance(string eventName) => CreateSoundInstance(eventName, NullSoundStruct, NullSoundInfoStruct);
        private FMODSoundInstanceContainer CreateSoundInstance(string eventName, FMOD.Sound sound, FMOD.Studio.SOUND_INFO soundInfo)
        {
            FMOD.Studio.EventInstance instance = FMODUnity.RuntimeManager.CreateInstance(eventName);
            FMODSoundInstanceContainer instanceContainer = new FMODSoundInstanceContainer(instance, eventName, sound, soundInfo);
            ActiveSoundInstances.Add(instanceContainer);
            return instanceContainer;
        }

        private IEnumerator GarbageCollectorRoutine()
        {
            while (true)
            {
                yield return GarbageCollectorIntervalWait;
                RunGarbageCollectorSweep();
            }
        }

        private void RunGarbageCollectorSweep(bool destroyAll = false)
        {
            List<FMODSoundInstanceContainer> retainList = new List<FMODSoundInstanceContainer>();
            FMOD.Studio.PLAYBACK_STATE state;
            foreach (FMODSoundInstanceContainer container in ActiveSoundInstances)
            {
                // Destroy Everything
                if (destroyAll)
                {
                    container.DestroyInstance();
                    continue;
                }

                // Conditional Destruction 
                // Must be Stopped/Paused and Dead
                if (!container.IsAlive())
                {
                    bool isPaused;
                    container.EventInstance.getPaused(out isPaused);
                    container.EventInstance.getPlaybackState(out state);
                    if (state == FMOD.Studio.PLAYBACK_STATE.STOPPED || isPaused)
                    {
                        container.DestroyInstance();
                    }
                }
                else retainList.Add(container);
            }
            ActiveSoundInstances = retainList;
        }

        /**
         * Sound Instance Classes
         */
        private class FMODSoundInstanceContainer
        {
            // Parent Class Public
            public FMOD.Studio.EventInstance EventInstance;
            public string EventKey;
            public List<CallbackAndMask> Callbacks;
            // Properties
            private FMOD.Studio.EVENT_CALLBACK FMODCallback;
            private WeakReference<FMODSoundInstance> Reference;
            private FMODCallbackWrapper CallbackWrapper;
            private GCHandle CallbackWrapperHandle;
            private bool timeScalePaused;

            public FMODSoundInstanceContainer() : this(NullEventInstance, string.Empty) { } // Used for failure to load
            public FMODSoundInstanceContainer(FMOD.Studio.EventInstance eventInstance, string eventKey) : this(eventInstance, eventKey, NullSoundStruct, NullSoundInfoStruct) { }
            public FMODSoundInstanceContainer(FMOD.Studio.EventInstance eventInstance, string eventKey, FMOD.Sound sound, FMOD.Studio.SOUND_INFO soundInfo)
            {
                this.EventInstance = eventInstance;
                this.EventKey = eventKey;
                this.Reference = new WeakReference<FMODSoundInstance>(new FMODSoundInstance(this));
                this.Callbacks = new List<CallbackAndMask>(128);
                // Setup Callback Handler
                this.FMODCallback = new FMOD.Studio.EVENT_CALLBACK(FMODUnmanagedCallback);
                this.CallbackWrapper = new FMODCallbackWrapper(FMODCallbackHandler, sound, soundInfo);
                this.CallbackWrapperHandle = GCHandle.Alloc(CallbackWrapper, GCHandleType.Pinned);
                this.EventInstance.setUserData(GCHandle.ToIntPtr(CallbackWrapperHandle));
                this.EventInstance.setCallback(this.FMODCallback,
                FMOD.Studio.EVENT_CALLBACK_TYPE.STOPPED  // Stop is the only user supported event 
                | FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND
                | FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND
                | FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROYED
                | FMOD.Studio.EVENT_CALLBACK_TYPE.STOPPED
                );
            }

            public SoundInstance GetReference()
            {
                FMODSoundInstance instance;
                Reference.TryGetTarget(out instance);
                return instance;
            }

            public bool IsAlive()
            {
                FMODSoundInstance instance;
                return Reference.TryGetTarget(out instance);
            }

            public void DestroyInstance()
            {
                if (IsAlive()) Debug.LogWarning("Destroying SoundInstance that still has an owner");
                EventInstance.release();
                EventInstance.stop(FMOD.Studio.STOP_MODE.IMMEDIATE);
            }

            public void TimeScalePause(bool pause)
            {
                if (pause)
                {
                    if (timeScalePaused) return;
                    PLAYBACK_STATE playbackState;
                    EventInstance.getPlaybackState(out playbackState);
                    if (playbackState == PLAYBACK_STATE.PLAYING)
                    {
                        timeScalePaused = true;
                        EventInstance.setPaused(true);
                    }
                }
                else if (timeScalePaused)
                {
                    timeScalePaused = false;
                    EventInstance.setPaused(false);
                }
            }

            private void FMODCallbackHandler(FMOD.Studio.EVENT_CALLBACK_TYPE eventType)
            {
                foreach (CallbackAndMask callback in this.Callbacks)
                {
                    if (callback.IsCallbackTypeListener(eventType))
                    {
                        callback.Callback?.Invoke(callback.TranslateType(eventType));
                    }
                }
            }

            [AOT.MonoPInvokeCallback(typeof(FMOD.Studio.EVENT_CALLBACK))]
            private static FMOD.RESULT FMODUnmanagedCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
            {
                // Get Instance
                FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);
                // Retrieve the user data
                IntPtr callbackWrapperPtr;
                FMOD.RESULT result = instance.getUserData(out callbackWrapperPtr);
                if (result != FMOD.RESULT.OK)
                {
                    Debug.LogError("Failed to fetch user data for audio callback: " + result);
                }
                else if (callbackWrapperPtr != IntPtr.Zero)
                {
                    // Grab Parameters
                    GCHandle callbackWrapperHandle = GCHandle.FromIntPtr(callbackWrapperPtr);
                    FMODCallbackWrapper callbackWrapper = (FMODCallbackWrapper)callbackWrapperHandle.Target;

                    // Handle Default Actions
                    switch (type)
                    {
                        case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
                            {
                                FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES programmerSoundProperties =
                                    (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(
                                        parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                                programmerSoundProperties.sound = callbackWrapper.Sound.handle;
                                programmerSoundProperties.subsoundIndex = callbackWrapper.SoundInfo.subsoundindex;
                                Marshal.StructureToPtr(programmerSoundProperties, parameterPtr, false);
                                break;
                            }
                        case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
                            {
                                FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES parameter =
                                    (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(
                                        parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                                FMOD.Sound sound = new FMOD.Sound(parameter.sound);
                                sound.release();
                                break;
                            }
                        case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROYED:
                            {
                                callbackWrapperHandle.Free();
                                break;
                            }
                    }

                    // Call User Callbacks
                    callbackWrapper.UserCallbackHandler?.Invoke(type);
                }
                return FMOD.RESULT.OK;
            }

            private class FMODSoundInstance : SoundInstance
            {
                private FMODSoundInstanceContainer Container;
                private bool meteringEnabled;
                public FMODSoundInstance(FMODSoundInstanceContainer container) => this.Container = container;

                public override string GetEventName() => Container.EventKey;

                public override void OnStateChange(Action<SoundInstanceState> callback, SoundInstanceState stateMask)
                {
                    // Sanity Checks
                    if (callback == null) return;
                    if (!Container.EventInstance.isValid())
                    {
                        Debug.LogError("Can't set callback on destroyed sound instance");
                        callback(SoundInstanceState.Error);
                        return;
                    }
                    if (stateMask != SoundInstanceState.Stopped)
                    {
                        Debug.LogError("Only Stopped and Error state are supported for callback");
                        return;
                    }

                    // Store Callback
                    Container.Callbacks.Add(new CallbackAndMask(callback, stateMask));
                }

                public override void SetParameter(string parameter, float value, bool skipSeek)
                {
                    // Sanity
                    if (!Container.EventInstance.isValid())
                    {
                        Debug.LogError("Can't set parameter on destroyed sound instance");
                        return;
                    }
                    FMOD.RESULT result = Container.EventInstance.setParameterByName(parameter, value, skipSeek);
                    if (result != FMOD.RESULT.OK)
                    {
                        Debug.LogError("Failed to set variable " + parameter + " to " + value + " with result " + result);
                    }
                }

                public override void Start()
                {
                    // Sanity
                    if (!Container.EventInstance.isValid())
                    {
                        Debug.LogError("Can't start a destroyed sound instance");
                        return;
                    }
                    Container.EventInstance.start();
                }

                public override void Stop(bool immediate = false)
                {
                    if (!Container.EventInstance.isValid())
                    {
                        Debug.LogError("Can't attach a destroyed sound instance");
                        return;
                    }
                    Container.EventInstance.stop(immediate ? FMOD.Studio.STOP_MODE.IMMEDIATE : FMOD.Studio.STOP_MODE.ALLOWFADEOUT);
                }

                public override void Pause(bool pause)
                {
                    // Sanity
                    if (!Container.EventInstance.isValid())
                    {
                        Debug.LogError("Can't pause a destroyed sound instance");
                        return;
                    }
                    Container.EventInstance.setPaused(pause);
                }

                public override void AttachToGameObject(GameObject gameObject)
                {
                    // Sanity
                    if (!Container.EventInstance.isValid())
                    {
                        Debug.LogError("Can't attach a destroyed sound instance");
                        return;
                    }
                    FMODUnity.RuntimeManager
                        .AttachInstanceToGameObject(
                            Container.EventInstance, gameObject.transform, gameObject.GetComponent<Rigidbody2D>());
                }

                public override void DetachFromGameObject()
                {
                    // Sanity
                    if (!Container.EventInstance.isValid())
                    {
                        Debug.LogError("Can't detach a destroyed sound instance");
                        return;
                    }
                    FMODUnity.RuntimeManager.DetachInstanceFromGameObject(Container.EventInstance);
                }

                public override float GetBusLevelDBs()
                {
                    if (!Container.EventInstance.isValid())
                    {
                        Debug.LogError("Can't get levels for a destroyed sound instance");
                        return MinVolumeDBs;
                    }

                    // Fetch channel group
                    FMOD.ChannelGroup channelGroup;
                    FMOD.RESULT result = Container.EventInstance.getChannelGroup(out channelGroup);
                    if (result == FMOD.RESULT.ERR_STUDIO_NOT_LOADED) return MinVolumeDBs;
                    if (result != FMOD.RESULT.OK)
                    {
                        Debug.LogError($"Failed to fetch channel group to check metering {result}");
                        return MinVolumeDBs;
                    }

                    // Fetch DSP
                    FMOD.DSP dsp;
                    result = channelGroup.getDSP(0, out dsp);
                    if (result != FMOD.RESULT.OK)
                    {
                        Debug.LogError($"Failed to fetch dsp to check metering {result}");
                        return MinVolumeDBs;
                    }

                    // Enable metering if necessary
                    if (!meteringEnabled)
                    {
                        dsp.setMeteringEnabled(false, true);
                        meteringEnabled = true;
                    }

                    // Fetch metering info
                    FMOD.DSP_METERING_INFO meteringInfo;
                    dsp.getMeteringInfo(IntPtr.Zero, out meteringInfo);
                    float rms = 0f;
                    for (int i = 0; i < meteringInfo.numchannels; i++)
                    {
                        rms += meteringInfo.rmslevel[i] * meteringInfo.rmslevel[i];
                    }
                    rms = Mathf.Sqrt(rms / (float)meteringInfo.numchannels);
                    float db = rms > 0 ? 20.0f * Mathf.Log10(rms * Mathf.Sqrt(2.0f)) : MinVolumeDBs;
                    db = Mathf.Clamp(db, MinVolumeDBs, MaxVolumeDBs);
                    return db;
                }
            }

            public class CallbackAndMask
            {
                public SoundInstance.SoundInstanceState Mask;
                public Action<SoundInstance.SoundInstanceState> Callback;

                public CallbackAndMask(Action<SoundInstance.SoundInstanceState> callback, SoundInstance.SoundInstanceState mask)
                {
                    this.Mask = mask;
                    this.Callback = callback;
                }

                public bool IsCallbackTypeListener(FMOD.Studio.EVENT_CALLBACK_TYPE eventType)
                {
                    return (TranslateType(eventType) & Mask) != 0;
                }

                public SoundInstance.SoundInstanceState TranslateType(FMOD.Studio.EVENT_CALLBACK_TYPE eventType)
                {
                    switch (eventType)
                    {
                        case FMOD.Studio.EVENT_CALLBACK_TYPE.STOPPED:
                            return SoundInstance.SoundInstanceState.Stopped;
                    }
                    return 0;
                }
            }
        }
    }

    [StructLayout(LayoutKind.Sequential)]
    class FMODCallbackWrapper
    {
        // User Callback Handler 
        public Action<FMOD.Studio.EVENT_CALLBACK_TYPE> UserCallbackHandler;
        // Dialogue Parameters
        public FMOD.Sound Sound;
        public FMOD.Studio.SOUND_INFO SoundInfo;

        public FMODCallbackWrapper(Action<FMOD.Studio.EVENT_CALLBACK_TYPE> userCallbackHandler)
        {
            this.UserCallbackHandler = userCallbackHandler;
        }

        public FMODCallbackWrapper(
            Action<FMOD.Studio.EVENT_CALLBACK_TYPE> userCallbackHandler,
            FMOD.Sound sound,
            FMOD.Studio.SOUND_INFO soundInfo) : this(userCallbackHandler)
        {
            this.Sound = sound;
            this.SoundInfo = soundInfo;
        }
    }
}

I’m not sure what’s going on, but audio does on on the MacOS build.

The script 'Audio.FMODSound' could not be instantiated! 
(Filename: ./Runtime/Scripting/ManagedReference/SerializableManagedRef.cpp Line: 227)

I’ve got a reproduction uploaded call Reproduction 2. I built that for another project, but it’s possible to reproduce this error with the same project.

Is this another Unity bug, or is this a problem with FMOD?

I asked Unity about this and they said it’s a bug. Here’s the bug report for anyone else who’s trying to use programmer sounds on iOS. They’re not supported for now.

We have verified Programmer Sounds are behaving as expected on iOS when following the Programmer Sounds Scripting Example, and haven’t been able to reproduce the error outside of the example you have provided.
Thanks for sharing the bug report, we’ll monitor to see if any more information comes up.

OK so I got a message back from Unity. The Programmer Sounds Scripting example as the GC.Alloc() call with the “Pinned” setting. This actually isn’t required unless you’re grabbing the address to the object (which we aren’t in the example given).

If you used pinned, then every object within the object you’re pinning needs to be blittable, and I had an Action object in there.

I just didn’t know enough about the managed/unmanaged stuff to know that “Pinned” wasn’t required even though it’s in the example. Might be worth removing it unless it IS required. If it is required, then I’ve still got a problem I guess.

Thanks for looking into this, you are correct it looks like there is no need to use the Pinned setting in the Programmer Sounds Scripting example, as well as in a few other examples we have.
I’ve created a task to update our examples, thank you again for sharing your investigation!