Custom Unity methods bindings?

Hi, I want to optimize some code but it requires one type to be changed in Unity method binding. For ex AudioClip.GetData is:

  [StaticAccessor("AudioClipBindings", StaticAccessorType.DoubleColon)]
  [NativeHeader("Modules/Audio/Public/ScriptBindings/Audio.bindings.h")]
  public sealed class AudioClip : Object
  {
    [MethodImpl(MethodImplOptions.InternalCall)]
    private static extern bool GetData(
      [NotNull("NullExceptionObject")] AudioClip clip,
      [Out] float[] data,
      int numSamples,
      int samplesOffset);

Instead of float[ ] I’d like to use NativeArray or Span (basically float*). Is it possible to define custom binding?

I’ve read that arrays are mapped to MonoArray* not pointers like in DllImport so I guess even with custom binding I won’t be able to use other type?

Unfortunately it is not possible to define custom bindings. However, since 2022.1, we have internally the ability to make Spans cross the native/managed boundary without buffer copy. I’ll file a ticket about this API, don’t hesitate to propose others :slight_smile:

3 Likes

Thank you! Another APIs commonly used by rhythm games are AudioSource.GetOutputData and AudioSource.GetSpectrumData (also AudioListener.GetOutputData, AudioListener.GetSpectrumData).

I’m mostly using burst and NativeArrays, hopefully new Span<byte>(nativeArray.GetUnsafeReadOnlyPtr(), nativeArray.Length); will work fine.

In 2023.1 (and in an uncoming 2022.2), NativeArray will be implicitly convertible to Span (and will also provide .AsSpan / .AsReadOnlySpan methods).

5 Likes

I have a library that allows you to do this, though unfortunately as of Unity 2023 the internal AudioClip::SetData/AudioClip::GetData methods are only available in the editor and in development builds. In release builds, SoundHandleAPI::SetData/SoundHandleAPI::GetData is used instead, which requires you to know the location of a field inside AudioClip (this isn’t too hard to figure out, but it depends on your Unity version.)

Using the library, you could write:

public static unsafe bool GetData(AudioClip clip, Span<float> data, uint offsetSamples)
{
#if !UNITY_2023_1_OR_NEWER || UNITY_EDITOR || DEVELOPMENT_BUILD
    if (clip.channels <= 0)
        return false;

    fixed (float* pData = data)
    {
        var native = NativeObjectUtility.GetNativeObject(clip);
        var result = GetData(native, pData, (uint)data.Length / (uint)clip.channels, offsetSamples);
        GC.KeepAlive(clip);
        return result;
    }

#if UNITY_64
    [EngineImport("?GetData@AudioClip@@QEAA_NPEAMII@Z")]
#else
    [EngineImport("?GetData@AudioClip@@QAE_NPAMII@Z", CallingConvention = CallingConvention.ThisCall)]
#endif
    [return: MarshalAs(UnmanagedType.U1)]
    static extern bool GetData(NativeObject* @this, float* data, uint lengthSamples, uint offsetSamples);
#else
    throw new NotSupportedException();
#endif
}

Note that obviously Unity likely won’t give you any support for using this technique, as it’s rather low-level and dangerous if not done correctly. It’s also worth noting that the library only supports Windows currently (both editor and builds), because it uses DIA to process the PDBs that ship with Unity, and DIA is a Windows-only API.

Speaking of GetData(), I ended up moving to FMOD (unity also uses fmod but old version). They simply give IntPtr buffer I can use with both array and NativeArray, way more flexible.

Working with TerrainData is sorely lacking these! For example, this relatively new method makes an array every time you call it.