Waveform not working

Hi, we are currently having an issue that’s happening from quite some time already and we don’t know what are we doing wrong.
Everytime a VO event is being played there is an audio waveform visualizer that shows the moving waveform of that event. This feature worked fine but somehow the waveform visualizer now stops working when the user/player sets the volume from master or dialogues to 0 zero from the options menu. This worked fine before, even if the player set master or dialogue volume to zero.

The bus routing is working with a bus named VO (which contains the audio VO events) and it’s parent its a bus called WaveformVO which is the one the waveform data gets extracted. Also we created a custom DSP like the documentation reccomends.

While testing with Live Update, we can see that the WaveformVO Bus receives audio information, even when the VO Bus does not. All this while we reproduce the scenario that the volume is set to 0 when dialogues are being played.

Also, when the volume of dialogues is set to 0 while a dialogue is playing we get this console warning:

Error initializing dialogue waveform, trying again : ERR_STUDIO_NOT_LOADED

I don’t know if it helps but this is the script for the waveform visualizer:

 using FMOD;
 using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.InteropServices;
 using UnityEngine;
 using UnityEngine.UI;
 using UnityEngine.UI.Extensions;
 
 public class DialogueWaveformView : MonoBehaviour
 {
     FMOD.Studio.EventInstance musicInstance;
     FMOD.DSP fft;
 
     //public UILineRenderer lineRenderer;
 
     public Image[] waveImages;
 
     private int divisionsCount = 10;
 
     const int WindowSize = 1024;
 
     [Range(0f,0.1f)]
     public float heightMult = 0.01f;
     [Range(0,1)]
     public float heightOffset = 0.1f;
 
     //public AnimationCurve decompressionMap = AnimationCurve.Linear(0f, -1, 1f, 1f);
     [Range(0f, 1f)] public float valueDamp = 0.1f;
     [Range(0f, 80f)] public float valueGateCutoff = 0.01f;
     [Range(0f, WindowSize)] public float spectrumStart = 150f;      //Fine tuning by voice range
     [Range(0f, WindowSize)] public float spectrumEnd = 800f;
 
     [Range(0f, 1f)]
     public float noiseIntensity = 0.1f;
 
     private float[] easedValues;
 
     [SerializeField, InspectorReadOnly]
     private bool initialized = false;
     [SerializeField, InspectorReadOnly]
     private bool updateWave = false;
 
     private Coroutine initializeRoutine = null;
 
     private IEnumerator Initialize()
     {
         divisionsCount = waveImages.Length;
         
         easedValues = new float[divisionsCount];
 
 
         FMODUnity.RuntimeManager.CoreSystem.createDSPByType(FMOD.DSP_TYPE.FFT, out fft);
         fft.setParameterInt((int)FMOD.DSP_FFT.WINDOWTYPE, (int)FMOD.DSP_FFT_WINDOW.HANNING);
         fft.setParameterInt((int)FMOD.DSP_FFT.WINDOWSIZE, WindowSize * 2);
 
         FMOD.RESULT result = FMOD.RESULT.OK;
         ChannelGroup channelGroup;
 
         AudioManager.instance.LockWaveformBus();
         
         do
         {
             yield return null;
             result = AudioManager.instance.GetWaveformBus(out channelGroup);
             if (result != FMOD.RESULT.OK)
                 DebugEx.Warning("Error initializing dialogue waveform, trying again : " + result.ToString());
         } while (result != FMOD.RESULT.OK);
         
         channelGroup.addDSP(FMOD.CHANNELCONTROL_DSP_INDEX.TAIL, fft);
         initialized = true;
         initializeRoutine = null;
     }
 
     public void OnEndDialogue(Person person, string line)
     {
         updateWave = false;
         //AudioManager.instance.UnlockVoiceBus();
     }
 
     public void OnStartDialogue(Person person, string line)
     {
         updateWave = true;
         //AudioManager.instance.LockVoiceBus();
         if (!initialized)
         {
             if(initializeRoutine == null)
                 initializeRoutine = StartCoroutine(Initialize());
         }
     }
 
     private void OnEnable()
     {
         ResetWaveform();
     }
 
     private void ResetWaveform()
     {
         float value;
         if(easedValues == null || easedValues.Length == 0)
             easedValues = new float[waveImages.Length];
 
         for (int i = 0; i < waveImages.Length; i++)
         {
             easedValues[i] = 0;
 
             value = easedValues[i] + heightOffset;
             waveImages[i].transform.localScale = new Vector3(1f, value, 1f);
         }
     }
 
     void Update()
     {
         if (!initialized) return;
 
         if (updateWave)
         {
             IntPtr unmanagedData;
             uint length;
             fft.getParameterData((int)FMOD.DSP_FFT.SPECTRUMDATA, out unmanagedData, out length);
             FMOD.DSP_PARAMETER_FFT fftData = (FMOD.DSP_PARAMETER_FFT)Marshal.PtrToStructure(unmanagedData, typeof(FMOD.DSP_PARAMETER_FFT));
             var spectrum = fftData.spectrum;
 
             if (fftData.numchannels > 0)
             {
                 for (int i = 0; i < divisionsCount; ++i)
                 {
 
                     int j = (int)(Mathf.Lerp(spectrumStart, spectrumEnd, (float)i / divisionsCount)) * 2;
                     var v = (lin2dB(spectrum[0][j]) + 80);
                     //var s = (spectrum[0][j] * normalize - .5f) * 2f;
                     //v = Mathf.Abs(decompressionMap.Evaluate(v));
 
                     v = Mathf.Abs(v);
                     if (v < valueGateCutoff)
                         v = 0f;
                     v *= heightMult;
                     
 
                     v = Mathf.Max(easedValues[i], v);
                     easedValues[i] = v;
                 }
             }
         }
 
         float value;
         var damp = Mathf.Pow(1f - valueDamp, Time.deltaTime * 60f);
         for (int i = 0; i < divisionsCount; i++)
         {
             easedValues[i] += UnityEngine.Random.value * noiseIntensity;
             easedValues[i] *= damp;
             easedValues[i] = Mathf.Clamp(easedValues[i], 0f, 1f);
             
             value = easedValues[i] + heightOffset;
             waveImages[i].transform.localScale = new Vector3(1f, value, 1f);
         }
     }
 
     float lin2dB(float linear)
     {
         return Mathf.Clamp(Mathf.Log10(linear) * 20.0f, 80.0f, 0.0f);
     }
 }

EDIT: Some more info that could help: The visualizer works again when having some volume, even if little. This issue only happens when the volume is set to zero either for dialogues or master slider from options menu in game.

I hope that someone can shed some light into this issue, thanks!

Do you need more information regarding the issue? Please, if someone can give a hand with this, it would be very helpful

How are you letting users set the volume, are they directly manipulating any of your busses? I believe that for optimisation reasons if there is a volume of totally zero anywhere in a signal chain, FMOD doesn’t actually run the events routed to that chain, which might be where your STUDIO_NOT_LOADED error is coming from.

If you give players access to a VCA instead of an actual mixer bus, that should allow the audio output to be silent without affecting the rest of your signal chain and preventing events from getting played.

1 Like

Hey @zanhulme ! Thanks for your response. We tried arming corresponding VCAs for the VO bus, and modyfied the script corresponding to volume slider controllers so the users can manipulate the VCO for turning the volume down of the dialogues.
image
Unfortunately the issue is still happening. The message STUDIO_NOT_LOADED error is still there when turning the volume control of VCA called VO to zero and the waveform doesnt move. Only if there is even the minimum of volume the waveform starts to move again.

This is the script made for using the VCAs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using FMOD;
using FMOD.Studio;
using UnityEngine;
using EventInstance = FMOD.Studio.EventInstance;

public class AudioManager : Singleton<AudioManager>, IEventListener
{
	public static float DISTANCE_FACTOR = 0.008f;

	public AudioMusic music;
	public AudioGameplay gameplay;
	public AudioUI ui;
	public AudioMainMenu mainMenu;

	PARAMETER_ID paramTimescale;
	PARAMETER_ID paramCameraZoom;

	Bus musicBus, sfxBus, voBus, masterBus, waveformBus;

	VCA musicVCA, sfxVCA, voVCA, masterVCA;

	[NonSerialized] public OrbitingCamera camera;

	List<(string, EventInstance)> _registered = new List<(string, EventInstance)>();

	internal PARAMETER_ID GetGlobalParameter(string parameterName)
	{
		FMODUnity.RuntimeManager.StudioSystem.getParameterDescriptionByName(parameterName, out var description);
		return description.id;
	}

	public AudioManager()
	{
		music = new AudioMusic(this);
		gameplay = new AudioGameplay(this);
		ui = new AudioUI(this);
		mainMenu = new AudioMainMenu(this);

		paramTimescale = GetGlobalParameter("timescale");
		paramCameraZoom = GetGlobalParameter("camera_zoom");

		musicBus = FMODUnity.RuntimeManager.GetBus("bus:/Soundtrack");
		sfxBus = FMODUnity.RuntimeManager.GetBus("bus:/SFX");
		voBus = FMODUnity.RuntimeManager.GetBus("bus:/VO");
        waveformBus = FMODUnity.RuntimeManager.GetBus("bus:/VO/WaveformVO");

		musicVCA = FMODUnity.RuntimeManager.GetVCA("vca:/Soundtrack_VCA");
		sfxVCA = FMODUnity.RuntimeManager.GetVCA("vca:/SFX_VCA");
		voVCA = FMODUnity.RuntimeManager.GetVCA("vca:/VO_VCA");

		masterBus = FMODUnity.RuntimeManager.GetBus("bus:/");
		masterVCA = FMODUnity.RuntimeManager.GetVCA("vca:/Master_VCA");
	}

	[Conditional("DEBUG")]
	internal void RegisterInstance(string id, EventInstance instance)
	{
		_registered.Add((id, instance));
	}

	public override void OnDestroySingleton()
	{
		throw new System.NotImplementedException();
	}

	public void TimeMeterChange(float x)
	{
		music.TimeMeterChange(x);
		ui.TimeMeterChange(x);
		gameplay.TimeMeterChange(x);
		var result = FMODUnity.RuntimeManager.StudioSystem.setParameterByID(paramTimescale, x);
	}

	public void OnFrame()
	{
		if(camera != null)
		{
			var zoom = Mathf.Clamp01((camera.distance - camera.minDistance) / (camera.maxDistance - camera.minDistance));
			var result = FMODUnity.RuntimeManager.StudioSystem.setParameterByID(paramCameraZoom, zoom);
		}
		music.OnFrame();
		gameplay.OnFrame();
	}

	public void Dispose()
	{
		gameplay?.Dispose();
        UnlockWaveformBus(); //We release the voice bus that was locked by the waveform widget
	}

	public void SubscribeEventHandlers()
	{
		gameplay?.SubscribeEventHandlers();

		Start();
	}

	void Start()
	{
		camera = GameObject.FindObjectOfType<OrbitingCamera>();
		music?.Start();
	}

	public void UnsubscribeEventHandlers()
	{
		gameplay?.UnsubscribeEventHandlers();
		music?.Stop();
	}

    //This enables to always have the bus available, which is used to add a FFT DSP for the dialogue waveform widget
    public RESULT LockWaveformBus()
    {
        return waveformBus.lockChannelGroup();
    }

    public RESULT UnlockWaveformBus()
    {
        return waveformBus.unlockChannelGroup();
    }

    public RESULT GetWaveformBus(out ChannelGroup channelGroup)
    {
        return waveformBus.getChannelGroup(out channelGroup);
    }
	
	public void SetMasterVolume(float vol)
	{
        masterVCA.setVolume(vol);
    }

	public void SetMusicVolume(float vol)
	{
        musicVCA.setVolume(vol);
    }

	public void SetSFXVolume(float vol)
	{
        sfxVCA.setVolume(vol);
    }

	public void SetVoiceVolume(float vol)
	{
        voVCA.setVolume(vol);
	}

	[ConsoleCommand("Audio_Diagnostic")]
	static void Audio_Diagnostic(object[] args)
	{
		foreach(var t in AudioManager.instance._registered)
		{
			DebugEx.ConsoleLog("INSTANCE " + t.Item1);
			try
			{
				var e = t.Item2;

				var result = e.get3DAttributes(out var attr3D);
				DebugEx.ConsoleLog($"\t({result})position = {attr3D.position.x}, {attr3D.position.y}, {attr3D.position.z}");
				result = e.getDescription(out var desc);
				desc.getPath(out var path);
				desc.getLength(out var length);
				DebugEx.ConsoleLog($"\t({result})description = {(desc.isValid() ? "VALID" : "INVALID")} path={path} length={length}");
				desc.getParameterDescriptionCount(out var paramCount);
				for(int i = 0; i < paramCount; i++)
				{
					var result1 = desc.getParameterDescriptionByIndex(i, out var param);
					var result2 = e.getParameterByID(param.id, out var value);
					DebugEx.ConsoleLog($"\t({result1} {result2}) {(string)param.name} {param.type.ToString()} [{param.minimum}..{param.maximum}] = {value}");
				}
			}
			catch
			{
				DebugEx.ConsoleLog($"\tERROR");
			}

		}
	}

	[ConsoleCommand("Set Music Volume")]
	static void SetMusicVol(object[] args)
	{
		if(args.Length < 1 || !(args[0] is float))
		{
			DebugEx.ConsoleLog("Usage: SetMusicVol [volume:float 0-1]"); return;
		}

		float vol = Mathf.Clamp01((float)args[0]);
		AudioManager.instance.SetMusicVolume(vol);
		DebugEx.ConsoleLog("Music volume set to " + vol.ToString("f1"));
	}

	[ConsoleCommand("Set SFX Volume")]
	static void SetSFXVol(object[] args)
	{
		if (args.Length < 1 || !(args[0] is float))
		{
			DebugEx.ConsoleLog("Usage: SetSFXVol [volume:float 0-1]"); return;
		}

		float vol = Mathf.Clamp01((float)args[0]);
		AudioManager.instance.SetSFXVolume(vol);
		DebugEx.ConsoleLog("SFX volume set to " + vol.ToString("f1"));
	}

	[ConsoleCommand("Set Voice Volume")]
	static void SetVoiceVol(object[] args)
	{
		if (args.Length < 1 || !(args[0] is float))
		{
			DebugEx.ConsoleLog("Usage: SetVoiceVol [volume:float 0-1]"); return;
		}

		float vol = Mathf.Clamp01((float)args[0]);
		AudioManager.instance.SetVoiceVolume(vol);
		DebugEx.ConsoleLog("Voice volume set to " + vol.ToString("f1"));
	}
}

Adding some more info regarding all this issue:
While checking with live update the WaveformVO bus stops recieving any signal when the user sets the master or voices sliders to 0. Before checking while playing live I recorded a profiling session and checked afterwards the recorded session and played it as “simulate”. That’s why I saw the WaveformVO bus getting signal as in my original post screenshot shows.

I reproduced this scenario with sliders controlled by user by buses (original scenario) and with the sliders controlled by user by VCAs (the suggested fix).

Also the warning Error initializing dialogue waveform, trying again : ERR_STUDIO_NOT_LOADED happens everytime the dialogues triggers or appears in game, not when the volume is set to 0 under options like I said first.

The issue in both cases is still the same

Hi,
@zanhulme, that you for the suggestion!

@k4rru, unfortunately, I was not able to reproduce the issue. Is it possible to get a copy of the Unity and FMOD projects uploaded to your profile? If not, is it possible to create a stripped-out version of the projects that display this behavior?

1 Like

Hi @Connor_FMOD thanks for your response. We are working under NDA and can’t sent you a copy or a stripped-out copy.
We will make a trick that makes the waveform move when there is 0 volume under Dialogues to solve this issue. Since the user wont hear the dialogues we can make the waveform move in any other way by code

I understand, apologies I could not help further.

1 Like