Search Unity

Steady rapidly repeating sound

Discussion in 'Audio & Video' started by Archie888, Jul 2, 2015.

  1. Archie888

    Archie888

    Joined:
    May 17, 2014
    Posts:
    41
    Hi,

    I am trying to code a machine gun sound, repeating one audio clip in rapid succession. However, it seems that I cannot get the repetition interval to be as exact as possible; there is a certain amount of unsteadiness in the interval of repetition, making the repetition to not to have the steady rhythm that you would be expecting.

    I have tried to do this both in the update and fixedupdate methods, with time checks, but both yield similar unsteady results. I also tried the approaches in this tutorial with the same result:

    http://www.41post.com/2831/programming/unity3d-programming-a-machine-gun-part-1

    I have found two ways to produce a steadily repeating sound. One is to play the audio source in a loop. However, with this approach, you need to always modify the audio data itself to change the repeat rate, by changing the duration of the clip. Another is by hacking together a long repetition sequence using a number of AudioSource.PlayDelayed(float) commands, with steadily increasing delay time parameter. For example, using 10 different AudioSources, giving them all the delayed commands with increasing delays, and looping this repetition in a co-routine, with a wait command in the end for the total duration (there will be a slight rhythm glitch at the loop repeat point, but that is still better than the time-dependent arhythmic result using update, for example). However, with this approach, you need to build an intricate system to correctly stop the delayed playings upon release of the firing button for example.

    The question is, how to produce a rapid, steadily repeating sound?

    Cheers,
    -A888
     
    Last edited: Jul 2, 2015
    DharmaPunk likes this.
  2. Archie888

    Archie888

    Joined:
    May 17, 2014
    Posts:
    41
    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: http://docs.unity3d.com/ScriptReference/AudioSource.PlayScheduled.html

    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:
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4.  
    5. [RequireComponent(typeof(AudioClipController))]
    6. public class __Tests : MonoBehaviour {
    7.  
    8.     //General
    9.     AudioClipController myAudioClipController;
    10.  
    11.     //Test: Exact sound repetition rhythm attempt using scheduled play
    12.     public AudioClip clip;
    13.     public double soundRepeatTime = 0.2;
    14.     double nextSoundPlaytime;
    15.     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.
    16.     double lastSoundPlaytime = 0;
    17.     double firingPauseMinimumTime = 0.05; //This is how fast the weapon can be be fired by rapid fire key pressing
    18.  
    19.     void Start () {
    20.         //General
    21.         myAudioClipController = GetComponent<AudioClipController>();
    22.     }
    23.  
    24.     void Update () {
    25.  
    26.         //Test: Exact sound repetition rhythm attempt using scheduled play
    27.         if (Input.GetKey(KeyCode.Return))
    28.         {
    29.             double time = AudioSettings.dspTime;
    30.  
    31.             if (Input.GetKeyDown(KeyCode.Return)) //Did we start firing at this frame?
    32.             {
    33.                 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.
    34.                 {
    35.                     nextSoundPlaytime = time; //Set next sound to be played immediately.
    36.                 }
    37.             }
    38.  
    39.             if (time > nextSoundPlaytime - schedulingMargin)
    40.             {
    41.                 myAudioClipController.PlayClipScheduled(clip, nextSoundPlaytime, 8);
    42.                 lastSoundPlaytime = nextSoundPlaytime;
    43.                 nextSoundPlaytime += soundRepeatTime;
    44.             }
    45.         }
    46.     }
    47. }
    48.  

    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.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4.  
    5. public class AudioClipChannel
    6. {
    7.     public List<AudioSource> audioSources;
    8.     private int _currentSourceIndex = -1;
    9.     public int currentSourceIndex
    10.     {    get {
    11.             _currentSourceIndex++;
    12.             if (_currentSourceIndex == audioSources.Count)
    13.                 _currentSourceIndex = 0;
    14.             return _currentSourceIndex; }
    15.         set { _currentSourceIndex = value; } }
    16.     public AudioSource CurrentAudioSource
    17.     {    get { return audioSources[currentSourceIndex]; } }
    18. }
    19.  
    20. public class AudioClipController : MonoBehaviour {
    21.  
    22.     Dictionary<AudioClip, AudioClipChannel> audioClipChannels = new Dictionary<AudioClip, AudioClipChannel>();
    23.  
    24.     public void PlayClip(AudioClip clip, int nSources)
    25.     {
    26.         AudioClipChannel acc = GetOrCreateAudioClipChannel(clip, nSources);
    27.         acc.CurrentAudioSource.Play();
    28.     }
    29.  
    30.     public void PlayClipDelayed(AudioClip clip, float delay, int nSources)
    31.     {
    32.         AudioClipChannel acc = GetOrCreateAudioClipChannel(clip, nSources);
    33.         acc.CurrentAudioSource.PlayDelayed(delay);
    34.     }
    35.  
    36.     public void PlayClipScheduled(AudioClip clip, double playTime, int nSources)
    37.     {
    38.         AudioClipChannel acc = GetOrCreateAudioClipChannel(clip, nSources);
    39.         acc.CurrentAudioSource.PlayScheduled(playTime);
    40.     }
    41.  
    42.     protected AudioClipChannel GetOrCreateAudioClipChannel(AudioClip clip, int nSources)
    43.     {
    44.         AudioClipChannel acc = null;
    45.         if (!audioClipChannels.ContainsKey(clip))
    46.         {
    47.             acc = audioClipChannels[clip] = new AudioClipChannel();
    48.             acc.audioSources = GetNewAudioClipSources(clip, nSources);
    49.         }
    50.         else
    51.             acc = audioClipChannels[clip];
    52.         //Add sources, if current nSources is greater than existing number
    53.         int nSourceDifference = nSources - acc.audioSources.Count;
    54.         if (nSourceDifference > 0)
    55.             acc.audioSources.AddRange(GetNewAudioClipSources(clip, nSourceDifference));
    56.         return audioClipChannels[clip];
    57.     }
    58.  
    59.     protected List<AudioSource> GetNewAudioClipSources(AudioClip clip, int nSources)
    60.     {
    61.         List<AudioSource> sources = new List<AudioSource>();
    62.         for (int i = 0; i < nSources; i++)
    63.         {
    64.             AudioSource asrc = gameObject.AddComponent<AudioSource>();
    65.             asrc.clip = clip;
    66.             sources.Add(asrc);
    67.         }
    68.         return sources;
    69.     }
    70. }
    71.  
     
    Last edited: Jul 4, 2015
    DharmaPunk and FlightOfOne like this.