Hi fellow devs,
I have found a solution that satisfies at least my specific needs regarding the matter. Perhaps this could be helpful to someone else as well, so I should post my solution here. It seems that the most reliable way to play audio in a tight rhythm sequence is to use the scheduling functionalities of the Unity audio system. Thus, the help page for AudioSource.PlayScheduled(double time) should already give ideas: Unity - Scripting API: AudioSource.PlayScheduled
However, as to my specific machine gun audio solution, here is my first barebones prototype, where I got a satisfactory functionality working:
Class __Tests.cs:
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(AudioClipController))]
public class __Tests : MonoBehaviour {
//General
AudioClipController myAudioClipController;
//Test: Exact sound repetition rhythm attempt using scheduled play
public AudioClip clip;
public double soundRepeatTime = 0.2;
double nextSoundPlaytime;
double schedulingMargin = 0.1; //Give the system some time to arrange the scheduling. This is important to get right. A smaller figure such as 0.1 seems to produce best results. In this implementation, when this gets too large, the sound will keep repeating after the firing key is released. With a too small value, there appears arrhythmia.
double lastSoundPlaytime = 0;
double firingPauseMinimumTime = 0.05; //This is how fast the weapon can be be fired by rapid fire key pressing
void Start () {
//General
myAudioClipController = GetComponent<AudioClipController>();
}
void Update () {
//Test: Exact sound repetition rhythm attempt using scheduled play
if (Input.GetKey(KeyCode.Return))
{
double time = AudioSettings.dspTime;
if (Input.GetKeyDown(KeyCode.Return)) //Did we start firing at this frame?
{
if (time > lastSoundPlaytime + firingPauseMinimumTime) //It can be useful to limit the maximum rate of fire with separate presses. In my opinion, an already rapid weapon (repeat time <= 0.1) will sound messy when the key is stroked rapidly, if the key press firing rate is not limited.
{
nextSoundPlaytime = time; //Set next sound to be played immediately.
}
}
if (time > nextSoundPlaytime - schedulingMargin)
{
myAudioClipController.PlayClipScheduled(clip, nextSoundPlaytime, 8);
lastSoundPlaytime = nextSoundPlaytime;
nextSoundPlaytime += soundRepeatTime;
}
}
}
}
Class AudioClipController.cs – This is a custom class that I use to rotate audio sources, in order to play the same sound without the next play clipping the previous, utilizing different audio sources for this. The class adds a parameterized number of audio sources for the clip that you wish to play. You will most likely need to rotate at least two different audio sources to avoid the worst clipping artifacts when playing sounds repetitively in at least a moderately fast succession, and obviously in such pace where successive plays begin before a previous one has ended. Even though more audio sources might produce better sounding results, you should consider the memory usage etc. when adding lots of components to the scene objects. You can of course use whatever other audio source solution for your own specific implementation.
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class AudioClipChannel
{
public List<AudioSource> audioSources;
private int _currentSourceIndex = -1;
public int currentSourceIndex
{ get {
_currentSourceIndex++;
if (_currentSourceIndex == audioSources.Count)
_currentSourceIndex = 0;
return _currentSourceIndex; }
set { _currentSourceIndex = value; } }
public AudioSource CurrentAudioSource
{ get { return audioSources[currentSourceIndex]; } }
}
public class AudioClipController : MonoBehaviour {
Dictionary<AudioClip, AudioClipChannel> audioClipChannels = new Dictionary<AudioClip, AudioClipChannel>();
public void PlayClip(AudioClip clip, int nSources)
{
AudioClipChannel acc = GetOrCreateAudioClipChannel(clip, nSources);
acc.CurrentAudioSource.Play();
}
public void PlayClipDelayed(AudioClip clip, float delay, int nSources)
{
AudioClipChannel acc = GetOrCreateAudioClipChannel(clip, nSources);
acc.CurrentAudioSource.PlayDelayed(delay);
}
public void PlayClipScheduled(AudioClip clip, double playTime, int nSources)
{
AudioClipChannel acc = GetOrCreateAudioClipChannel(clip, nSources);
acc.CurrentAudioSource.PlayScheduled(playTime);
}
protected AudioClipChannel GetOrCreateAudioClipChannel(AudioClip clip, int nSources)
{
AudioClipChannel acc = null;
if (!audioClipChannels.ContainsKey(clip))
{
acc = audioClipChannels[clip] = new AudioClipChannel();
acc.audioSources = GetNewAudioClipSources(clip, nSources);
}
else
acc = audioClipChannels[clip];
//Add sources, if current nSources is greater than existing number
int nSourceDifference = nSources - acc.audioSources.Count;
if (nSourceDifference > 0)
acc.audioSources.AddRange(GetNewAudioClipSources(clip, nSourceDifference));
return audioClipChannels[clip];
}
protected List<AudioSource> GetNewAudioClipSources(AudioClip clip, int nSources)
{
List<AudioSource> sources = new List<AudioSource>();
for (int i = 0; i < nSources; i++)
{
AudioSource asrc = gameObject.AddComponent<AudioSource>();
asrc.clip = clip;
sources.Add(asrc);
}
return sources;
}
}