Getting angular velocity to affect sound pitch?

I have a ship wheel that rotates on the Z axis using a hinge and has a rigid body. I would just like to have the angular velocity affect the pitch of an attached audio clip. If anyone could help me out it would be greatly appreciated.

I started writing some code but I’m lost as to where to go with it, I’m not that great of a great coder.

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

public class wheelSpinSound : MonoBehaviour
{
    public AudioClip wheelSpin;
    public Rigidbody rb;
    public float minPitch;
    public float maxPitch;

    AudioSource wheelAudioSource;
    private readonly float Z;

    //-------------------------------------------------
    void Awake()
    {
        wheelAudioSource = GetComponent<AudioSource>();
    }


    //-------------------------------------------------
    private void Update()
    {
        rb.velocity = rb.velocity.normalized;
        wheelAudioSource.pitch = ((maxPitch - minPitch) * Z) + minPitch;
        wheelAudioSource.PlayOneShot(wheelSpin);
    }
}

I recommend to edit your post and add code-tags, as this will improve the readability and people are more likely to look at it then.

Done!

Still trying to figure this out…

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Valve.VR;

public class wheelSpinSound : MonoBehaviour
{
    public AudioClip wheelSpin;
    public Rigidbody rb;
    public SteamVR_Controller.Device device;
    public SteamVR_TrackedObject controller = null;

    AudioSource wheelAudioSource;
   

   
    //-------------------------------------------------
    void Awake()
    {
        controller = GetComponent<SteamVR_TrackedObject>();
        wheelAudioSource = GetComponent<AudioSource>();
    }


    //-------------------------------------------------
   void Update()
    {
        device = SteamVR_Controller.Input((int)controller.index);

        if (controller.GetPressDown(SteamVR_Controller.ButtonMask.Grip))
        {
            print("pressed");
            rb.velocity = new Vector3(0, 0, 1);
            wheelAudioSource.pitch = Mathf.Lerp(0, 1, Time.time);
            rb.velocity = rb.velocity.normalized;
            wheelAudioSource.PlayOneShot(wheelSpin);
        }
       
    }
   

    }

I think you need this.

That seems a little intense. Isn’t there a way I can just normalize the pitch from 0 to 1 in a straightforward way? I feel like this shouldn’t be super complicated. I just need the sound to start when the motion controller clicks the wheel I created, and then depending on how fast I spin it, I need the pitch and/or volume to rise and fall with the velocity of the wheel.

Still trying to figure out how to set this up properly…

I’m also having a hard time just triggering audio with Vive controller interaction using the grips, would anyone be able to help with that at least?

You would like to adjust the audio pitch every cycle in accordance with the angular velocity right?
You will have to check that angular velocity and also set the pitch within the Update(). You don’t
want to call the “PlayOneShot” method every cycle or it could freeze your system. You should
only call it once to start it playing, and then adjust the pitch in accordance with the angular velocity.
It seems as though you may need to set up some boolean variables to control when the clip is being
played and when the pitch should be adjusting. Something like “adjustAudioPitch = false” and then
change the state of that boolean when the wheel is being spun. Within that conditional block, include
the check for the normalized angular velocity and also apply it to the audio pitch. When the wheel is
not being spun, change the boolean to false so that conditional block will not execute (saving cpu cycles).

Here are a couple of links that may help you:

I don’t want to spoil too much for you, but I hope this gets you on the right track to solving your problem.

Ok so I got it to actually play the sound and it seems to slow down, but it sounds super weird, very robotic. Anyone have any idea where I went wrong? (might have something to do with the “playOneShot” or the way I’m working with the pitch, not sure)

using UnityEngine;
using Valve.VR;
using ViveGrip;

public class WheelSpinSound : MonoBehaviour
{
    public Rigidbody rb;
    public AudioClip wheelSpin;
    AudioSource audioSource;


    void Start()
    {
        rb = GetComponent<Rigidbody>();
        audioSource = GetComponent<AudioSource>();
       
    }

    void Update()

    {
       
        if (rb.angularVelocity.magnitude > 1)
        {
            audioSource.PlayOneShot(wheelSpin);
            rb.velocity = rb.velocity.normalized;
            audioSource.pitch = Mathf.Lerp(0, 1, Time.time);
           
           
        }
       
    }
}

Got it pretty close, but I’m still having issues. The sound goes way too fast when I spin it, then it adjusts to a normal speed but it repeats (I don’t have loop enabled) and then every time I turn it, the loop just plays faster and faster. Really in the dark here.

using UnityEngine;


public class WheelSpinSound : MonoBehaviour
{
    public Rigidbody rb;
    public AudioClip wheelSpin;
    AudioSource audioSource;
    public static Vector3 Normalized;
    public float Speed = 1f;
   





    void Start()
    {
        rb = GetComponent<Rigidbody>();
        audioSource = GetComponent<AudioSource>();
       

    }

    void Update()

    {

        if (rb.angularVelocity.magnitude > 1)
        {
           
            audioSource.clip = wheelSpin;
            audioSource.Play();
            audioSource.pitch = Time.time * rb.angularVelocity.magnitude;
            Vector3 localangularvelocity = transform.InverseTransformDirection(rb.angularVelocity)/Speed;
        }
       
    }
}

If I could reverse engineer this somehow I would, but I have no clue how to C# code this logic (see attached image)

I feel like I’m getting closer but I’m still having the problem with the pitch.

Update() is called each frame. It will vary with framerate.

Since your audio is in relation to a rigidbody which is a physics object I expect using FixedUpdate() will settle things down.

Pitch is sensitive so small values make big changes. Your angular velocity on the other hand can produce a large value with little change. Dividing by a sensible value (such as a maximum velocity) then clamping to a sensible range https://docs.unity3d.com/ScriptReference/Mathf.Clamp.html will help here. Angular velocity also incorporates time in its equation so you don’t need Time.time there at all.

There’s also https://docs.unity3d.com/ScriptReference/AudioSource-isPlaying.html which could be used to determine and control the Play state of your AudioSource. Similar for Stop. Check the example at the bottom of the page.

I tried all this stuff but it’s not getting better. Also tried the Audio source is playing thing but it just started the audio automatically. This is how I have it set up now and it’s basically doing the same thing.

using UnityEngine;


public class WheelSpinSound : MonoBehaviour
{
    public Rigidbody rb;
    public AudioClip wheelSpin;
    AudioSource audioSource;
   
   
   
   





    void Start()
    {
        rb = GetComponent<Rigidbody>();
        audioSource = GetComponent<AudioSource>();
       
       



    }

    void FixedUpdate()

    {

        if (rb.angularVelocity.magnitude > 1)
        {
           
            audioSource.clip = wheelSpin;
            audioSource.PlayOneShot(wheelSpin);
            audioSource.pitch = Mathf.Clamp(rb.angularVelocity.magnitude/20f, 0f, 1.0f);

        }
    }
}

I know I’m doing something wrong I just have no idea what it is.

That’s much better! Just a few points now;

  • FixedUpdate is not the place for this. It can be called multiple times per frame (depending on your framerate) which you don’t want. Just stick with Update() here
  • PlayOneShot will play the AudioClip everytime it’s called. Just stick with Play(), Stop() and isPlaying
  • Unless you’re planning to change the AudioClip during play there’s no need to assign it every frame. Either assign it during Start() if you really must, or better still, remove the variable altogether and drop the clip into your AudioSource components’ AudioClip field found in the inspector
  • Looping. To my mind it doesn’t make alot of sense playing the clip once and not looping for the duration of any input. This will lead to silence once the clip has ended - even if your users are still moving the wheel

With that said, here’s a tweaked version of your code with the above suggestions implemented…

    // the following public fields must now be assigned in the inspector which'll save the overhead of having to find them during Start().
    public Rigidbody rb;
  
    // the AudioClip for this source will also need to be assigned in the inspector.
    public AudioSource audioSource;
  
    // canPlay is used to prevent the clip being looped. You can remove all traces of this variable if you ever decide looping is for you!
    private bool canPlay;

    void Start()
    {
        canPlay = true;
    }

    void Update()
    {
        // this is merely an optimization. "magnitude" is relatively expensive to compute. Let's store and re-use it instead.
        float velocity = rb.angularVelocity.magnitude;     
      
        if (velocity > 1f)
        {
            // if the clip hasn't already been played and it's not playing, play it.
            if (canPlay && !audioSource.isPlaying)
            {
                audioSource.Play();
                canPlay = false;
            }

            // excellent! although 20ms seems a little high. Lower if your pitch is too low.
            audioSource.pitch = Mathf.Clamp(velocity / 7f, 0f, 1f);
        }
        else
        {
            // here we stop the clip playing when our velocity is below our min threshold (1 in this case).
            if (audioSource.isPlaying)
            {
                audioSource.Stop();
            }

            canPlay = true;
        }
    }
1 Like

Yes! That worked perfectly. Thank you so much, total lifesaver.