Programmable Sounds in Unity

Hi,
I’m having an issue getting programmable sounds to work in Unity with FMOD. I’m basing my test off of the example script in your documentation. I’ve managed to trigger the example event ok with the programmer sound when its created by passing in a key from an audio table. For my project though I will require the ability to create a programmer sound from a Unity AudioClip and pass that to the event with the programmer sound. I’ve managed to create the FMOD sound ok from an Audio Clip using code found on these forums but I can’t get the event to play it. The event will play with the sound that’s part of it but the programmer sound part of it will not play. I’ve hit a bit of a brick wall with it and am unsure what I need to do to get this to work. I have provided the full source code for my test below.

Thanks,
Tom

Demo Source Code:

using System;

using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;

class ScriptUsageProgrammerSounds : MonoBehaviour
{
FMOD.Studio.EVENT_CALLBACK dialogueCallback;

static AudioClip Clip;
static FMOD.Sound Sound;
static bool _unityAudio;

void Start()
{
    // Explicitly create the delegate object and assign it to a member so it doesn't get freed
    // by the garbage collected while it's being used
    dialogueCallback = new FMOD.Studio.EVENT_CALLBACK(DialogueEventCallback);

    Clip = Resources.Load<AudioClip>("banana");
    Sound = CreateSoundFromAudioClip(Clip);
    //FMOD.Channel channel;
    //FMODUnity.RuntimeManager.LowlevelSystem.playSound(Sound, null, false, out channel);
}

static FMOD.Sound CreateSoundFromAudioClip(AudioClip audioClip)
{
    var samplesSize = audioClip.samples * audioClip.channels;
    var samples = new float[samplesSize];
    audioClip.GetData(samples, 0);

    var bytesLength = (uint)(samplesSize * sizeof(float));

    var soundInfo = new FMOD.CREATESOUNDEXINFO();
    soundInfo.cbsize = Marshal.SizeOf(typeof(FMOD.CREATESOUNDEXINFO));
    soundInfo.length = bytesLength;
    soundInfo.format = FMOD.SOUND_FORMAT.PCMFLOAT;
    soundInfo.defaultfrequency = audioClip.frequency;
    soundInfo.numchannels = audioClip.channels;

    FMOD.Sound sound;
    var result = FMODUnity.RuntimeManager.LowlevelSystem.createSound("", FMOD.MODE.OPENUSER, ref soundInfo, out sound);
    if (result == FMOD.RESULT.OK)
    {
        IntPtr ptr1, ptr2;
        uint len1, len2;
        result = sound.@lock(0, bytesLength, out ptr1, out ptr2, out len1, out len2);
        if (result == FMOD.RESULT.OK)
        {
            var samplesLength = (int)(len1 / sizeof(float));
            Marshal.Copy(samples, 0, ptr1, samplesLength);
            if (len2 > 0)
            {
                Marshal.Copy(samples, samplesLength, ptr2, (int)(len2 / sizeof(float)));
            }

            result = sound.unlock(ptr1, ptr2, len1, len2);
            if (result == FMOD.RESULT.OK)
            {
                result = sound.setMode(FMOD.MODE.LOOP_NORMAL);
                if (result == FMOD.RESULT.OK)
                {
                    Debug.Log("[FMOD] Successfully loaded programmer sound!");
                    return sound;
                }
                else
                {
                    Debug.LogError("[FMOD] Failed to set mode to loop for programmer sound with error: " + result);
                }
            }
            else
            {
                Debug.LogError("[FMOD] Failed to unlock programmer sound with error: " + result);
            }
        }
        else
        {
            Debug.LogError("[FMOD] Failed to lock programmer sound with error: " + result);
        }
    }
    else
    {
        Debug.LogError("[FMOD] Failed to load programmer sound with error: " + result);
    }
    return null;
}

void PlayDialogue(string key)
{
    var dialogueInstance = FMODUnity.RuntimeManager.CreateInstance("event:/Character/Radio/Command");

    // Pin the key string in memory and pass a pointer through the user data
    GCHandle stringHandle = GCHandle.Alloc(key, GCHandleType.Pinned);
    dialogueInstance.setUserData(GCHandle.ToIntPtr(stringHandle));

    dialogueInstance.setCallback(dialogueCallback);
    dialogueInstance.start();
    dialogueInstance.release();
}

static FMOD.RESULT DialogueEventCallback(FMOD.Studio.EVENT_CALLBACK_TYPE type, IntPtr instancePtr, IntPtr parameterPtr)
{
    // Recreate the event instance C# object
    FMOD.Studio.EventInstance instance = new FMOD.Studio.EventInstance(instancePtr);

    // Retrieve the user data
    IntPtr stringPtr;
    instance.getUserData(out stringPtr);

    // Get the string object
    GCHandle stringHandle = GCHandle.FromIntPtr(stringPtr);
    String key = stringHandle.Target as String;

    Debug.Log("Event = " + type);

    switch (type)
    {
        case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
            {
                var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

                parameter.sound = Sound.getRaw();

                //FMOD.Studio.SOUND_INFO dialogueSoundInfo;
                //var keyResult = FMODUnity.RuntimeManager.StudioSystem.getSoundInfo(key, out dialogueSoundInfo);
                //if (keyResult != FMOD.RESULT.OK)
                //{
                //    Debug.Log("Result = " + keyResult);
                //    break;
                //}
                //FMOD.Sound dialogueSound;
                //var soundResult = FMODUnity.RuntimeManager.LowlevelSystem.createSound(dialogueSoundInfo.name_or_data, dialogueSoundInfo.mode, ref dialogueSoundInfo.exinfo, out dialogueSound);
                //if (soundResult == FMOD.RESULT.OK)
                //{
                //    parameter.sound = dialogueSound.getRaw();
                //    parameter.subsoundIndex = dialogueSoundInfo.subsoundindex;
                //    Marshal.StructureToPtr(parameter, parameterPtr, false);
                //}
            }
            break;
        case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROY_PROGRAMMER_SOUND:
            {
                var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));
                var sound = new FMOD.Sound(parameter.sound);
                sound.release();
            }
            break;
        case FMOD.Studio.EVENT_CALLBACK_TYPE.DESTROYED:
            // Now the event has been destroyed, unpin the string memory so it can be garbage collected
            stringHandle.Free();
            break;
    }
    return FMOD.RESULT.OK;
}

void Update()
{
    if (Input.GetKeyDown(KeyCode.Q))
    {
        PlayDialogue("640166main_MECO");
    }
}

}

Because the code you referenced is made for using audio tables, we can make a few changes to use external files in a similar way:

case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
{
    var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

    FMOD.Sound dialogueSound;
    var soundResult = FMODUnity.RuntimeManager.LowlevelSystem.createSound(key, FMOD.MODE.DEFAULT, out dialogueSound);

    if (soundResult == FMOD.RESULT.OK)
    {
        parameter.sound = dialogueSound.getRaw();
        Marshal.StructureToPtr(parameter, parameterPtr, false);
    }
}

The key that you pass into PlayDialogue will need to be the full path (eg. To get the StreamingAssets folder: Application.dataPath + “/StreamingAssets” + filename + file extension).

But if you are creating your own sound, as you have in CreateSoundFromAudioClip, you can then remove the createSound and pass the handle over to the programmer sound properties struct:

case FMOD.Studio.EVENT_CALLBACK_TYPE.CREATE_PROGRAMMER_SOUND:
{
    var parameter = (FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES)Marshal.PtrToStructure(parameterPtr, typeof(FMOD.Studio.PROGRAMMER_SOUND_PROPERTIES));

    parameter.sound = Sound.getRaw();
    Marshal.StructureToPtr(parameter, parameterPtr, false);
}

Ok that’s perfect thank you. I’ve gone with the solution in your second code snippet - essentially I was just missing the marshalling.
Thanks for the help Cameron!