How to Play AudioClip from Editor from a Start Sample/Time

Partly inspired by Ryan Hipple’s recent presentation at Unity 2014, The Hack Spectrum, I’ve been working on a script that will allow me to play an AudioClip from a custom editor without any nonsense (like instantiating and managing an AudioSource in the current scene).

Using reflection I’ve found the AudioUtil class, which seems to include the PlayClip() method I’ve been looking for. Right now I’ve got something resembling the following:

public static void PlayClip(AudioClip clip, float startTime, bool loop)
{
    int startSample = (int)(startTime * clip.frequency);
 
    Assembly assembly = typeof(AudioImporter).Assembly;
    Type audioUtilType = assembly.GetType("UnityEditor.AudioUtil");
 
    Type[] typeParams = { typeof(AudioClip), typeof(int), typeof(bool) };
    object[] objParams = { clip, startSample, loop };
 
    MethodInfo method = audioUtilType.GetMethod("PlayClip", typeParams);
    method.Invoke(null, BindingFlags.Static | BindingFlags.Public, null, objParams, null);
}

However, the startTime/startSample functionality of the code does not seem to work. No matter what values I give it, PlayClip always plays the AudioClip from the very beginning.

Any suggestions? Am I misunderstanding something, or does the method just have a bug? (If that’s the case, I suppose I can’t expect any help from Unity since the class and method aren’t public…)

sampleStart = (int)Math.Ceiling (audioClip.samples * ((__sequence.timeCurrent - node.startTime) / audioClip.length));//do your calculation

								AudioUtilW.PlayClip (audioClip, 0, node.loop);//startSample doesn't work in this function????
															AudioUtilW.SetClipSamplePosition (audioClip, sampleStart);

AudioUtilW is reflected Class from AudioUtil. So trick is you first call PlayClip and then SetClipSamplePosition

Problem with AudioUtil is that Stop and StopAll doesn’t work as expected when you Play more clips at once.

You first need to PlayClip() and then execute the SetClipSamplePosition(). Both of these methods need to be called using the Reflection as posted by @winxalex.

The easiest way is to create an Editor script that emulates the AudioUtil class, so you can call those functions more easily. @Rtyper wrote a version in this thread:
https://forum.unity.com/threads/reflected-audioutil-class-for-making-audio-based-editor-extensions.308133/

Read the whole thread carefully. It also covers some updates to PlayClip() code for Unity 2019 as well as how to get clip samples GetSampleCount() in order to accurately get the position you want to start the clip from.

This is how your code would look like using the AudioUtility.cs script from the thread above:

AudioUtility.PlayClip(selectedAudioClip);

//Get desired position as percentage
var desiredClipPercentagePos = desiredTime / selectedAudioClip.length;

//Get the number of samples:
var samples = AudioUtility.GetSampleCount(selectedAudioClip);

//Then, simply multiply your percentage against that:
int playFrom = Mathf.FloorToInt(samples * desiredClipPercentagePos);

AudioUtility.SetClipSamplePosition(selectedAudioClip, playFrom);

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AudioManager : MonoBehaviour
{
    private static AudioManager instance;
    private bool firstMusicSourceIsPlaying;
    public AudioClip[] pianoTones;
    private AudioSource pianoSource;
    public float bgMusicVolume;
    int sfxCounter;
    public static AudioManager Instance
    {
        get
        {
            if(instance == null)
            instance = FindObjectOfType<AudioManager>();
            if(instance == null)
            {
                instance = new GameObject("Spawn AudioManager", typeof(AudioManager)).GetComponent<AudioManager>();

            }

        return instance;

        }
        private set
        {
            instance = value;
        }
    }

    //fields  
    private AudioSource musicSource;
    private AudioSource musicSource2;
    private AudioSource sfxSource;
  private void Awake() 
  {
     DontDestroyOnLoad(this.gameObject);
     musicSource = this.gameObject.AddComponent<AudioSource>(); 
     musicSource2 = this.gameObject.AddComponent<AudioSource>();  
     sfxSource = this.gameObject.AddComponent<AudioSource>();  
     pianoSource = this.gameObject.AddComponent<AudioSource>(); 
    
     //Looping music
     musicSource.loop = true;
     musicSource2.loop =true;
  }

  public void PlayMusic(AudioClip musicClip)
  {
      //determine which source is active
      AudioSource activeSource = (firstMusicSourceIsPlaying) ? musicSource : musicSource2;

      activeSource.clip = musicClip;
      activeSource.volume = bgMusicVolume;
      activeSource.Play();
  }



 
  public void PlayRandomPiano()
  {
      //sfxCounter++;
      int randomTone = Random.Range(0,5);
      pianoSource.clip = pianoTones[randomTone];
      pianoSource.PlayOneShot(pianoSource.clip);
  }
  public void PlayPianoSequentially(int x)
  {
      pianoSource.clip = pianoTones[x];
      pianoSource.PlayOneShot(pianoSource.clip);
  }
  public void PlaySFX(AudioClip clip, float volume)
  {
      sfxSource.PlayOneShot(clip, volume);
  }
  
  public void SetMusicVolume(float volume)
  {
      musicSource.volume = volume;
      musicSource2.volume = volume;
  }

  public void SetSFXVolume(float volume)
  {
      sfxSource.volume = volume;
  }
  
}

You’ll need an AudioSource with your clip set to its clip property. Unity - Scripting API: AudioSource

The time property can be set to a playback position Unity - Scripting API: AudioSource.time