This document is about: FUSION 2
SWITCH TO

Metaverse Music


Available in the Industries Circle
Circle

Overview

The Music scene allows the player to test his DJ skills with pads allowing him to trigger sounds and music, as well as to control light shows.
This demonstrates how to synchronize an audio track or a light through the network.
The scene includes several DJ pads. Some controls music and another one controls the lights.

Note that in desktop mode, the zoom icon at the bottom of each pad allows to display it in fullscreen in order to better control the UI with the mouse.

Fusion Metaverse Music

Music pads

Each music pad is composed of one or more buttons. To each button corresponds an AudioSource and a sound. The sound can be set as a loop.
A music pad contains a slider to change the volume.

Fusion Metaverse Music

A DJ pad behavior is managed by 3 classes:

  • DJPadVolumeSlider : manages the volume of the pad
  • DJPadTouch : manages the sound buttons
  • DJPadManager : manages the overall function of the pad

DJPadVolumeSlider

When the player changes the volume by touching the slider, the DJPadVolumeSlider calls the DJPadManager ChangeVolume methods.

C#

void RequestVolumeChange(float volume)
{
    padManager.ChangeVolume(volume);
}

public async void ChangeVolume(float volume)
{
    // We use an attribute, so if another volume is requested while taking the authority, the last volume request is the one executed
    lastVolumeRequest = volume;
    if (!Object.HasStateAuthority)
    {
        await Object.WaitForStateAuthority();
    }
    MasterVolume = lastVolumeRequest;
}

The networked variable MasterVolume in DJPadManager is then synchronized over the network.
A ChangeDetector is used to detect MasterVolume variable modification in Render() loop.

C#

[Networked]
public float MasterVolume { get; set; } = 1;

ChangeDetector changeDetector;

public override void Render()
{
    base.Render();
    foreach (var changedVar in changeDetector.DetectChanges(this))
    {
        if (changedVar == nameof(MasterVolume))
        {
            OnMasterVolumeChanged();
        }
    }
}

void OnMasterVolumeChanged()
{
    if(volumeManager != null) volumeManager.OnVolumeChanged(this, MasterVolume);
}

So that everyone can update the local volume.

C#

public void OnVolumeChanged(DJPadManager bPMClipsPlayer, float volume)
{
    ChangeSliderValue(volume);
}

DJPadTouch

Each button references an index, so that the DJPadManager knows which button controls each audio source, and knows which buttons should be notified when an audio source state changed.
So when the user touchs a button, it calls the UpdatePadStatus() method of DJPadTouch.
Then, UpdatePadStatus() notifies the DJPadManager of the new status.

C#

public void UpdatePadStatus()
{
    if (audioSource.clip)
    { 
        isPlaying = !isPlaying;
        padManager.ChangeAudioSourceState(this, isPlaying);
    }
}

Also, when a button has been touched, the DJPadManager calls the OnAudioSourceStatusChanged method of the DJPadTouch in order to ask it to update the button colors according to the new status.

DJPadManager

The status of each button is synchronized thanks to a network dictionnary called PadsStatus.

C#

[Networked]
Capacity(50)]
public NetworkDictionary<int, NetworkBool> PadsStatus { get; }

At start, the DJPadManager saves all buttons in a audioSourceManagers dictionnary (all DJPadTouch implements the IAudioSourceManager interface).

C#

foreach (var manager in GetComponentsInChildren<IAudioSourceManager>(true))
{
    audioSourceManagers[manager.AudioSourceIndex] = manager;
}

When the DJPadManager receives a new button status with ChangeAudioSourceState(), it requests for the StateAuthority if it doesn't have it yet and then update the networked dictionnary so that all remote users will received the update.

C#

public async void ChangeAudioSourceState(IAudioSourceManager audioSourceManager, bool isPlaying)
{
    if (!Object.HasStateAuthority)
    {
        await Object.WaitForStateAuthority();
    }
    PadsStatus.Set(audioSourceManager.AudioSourceIndex, isPlaying);
}

The RefreshPads(), SyncAudioSource() & OnAudioStatusChanged() methods are in charge to :

  • update the audiosource of each button (DJPadTouch) according to the volume slider value
  • update the pad networked dictionnary if a button status has changed (an audio clip has finished for example)
  • synchronized the audioSource (play/stop) of each button according to the networked dictionnary
  • inform a button that its status changed in order to update the button colors

Instead of doing these updates as soon as the networked dictionary is changed, they are performed regulary based on the predefined BPM parameter
(method AudioLoop()).

Light pad

The ligh pad controls 4 lights.
For each light, the pad allows the user to :

  • switch on/off the light
  • switch on/off the movement of the light
  • change light intensity

The light pad architecture is very similar to the music pad.
The lighting is managed by the following classes:

  • LightPadManager : manages the overall function of the pad
  • LightPadTouch : manages the light buttons
  • LightSystem : manages the light state
  • LightIntensitySlider : manages the light intensity
  • LightDirectionControler : manages the light rotator

LightIntensitySlider

The LightIntensitySlider is very similar to the DJPadVolumeSlider.
When the player changes the intensity by touching the slider, the RequestIntensityChange calls the LightPadManager ChangeIntensity() method.

In the other way, the OnLightStatusChanged method is called by the LightPadManager in order to update the slider postion if a remote player changed the intensity.

LightPadTouch

LightPadTouch manages light buttons.
As defined in the LightPadManager, there are 3 kinds of light buttons :

C#

public enum LightManagerType
    {
        OnOff,              // switch on/off the light
        Movement,           // switch on/off the movement
        Intensity           // slider to change the intensity
    }

Each button references a light index, so that the LightPadManager knows which button controls each light, and knows which buttons should be notified when a light state changed.

UpdatePadStatus() is called when the player touch a button. It informes the PadManager that the state must be changed.
OnLightStatusChanged() is called by the PadManager when the status changed so the button UI must be updated.

LightSystem

The LightSystem class inherits from the abstract class EffectSystem.
LightSystem is in charge to change the light state according to the parameters received in ChangeState method :

LightDirectionControler

LightDirectionControler class controls the rotator script which rotates the light game object.
LightDirectionControler is enabled/disabled by the LightSystem.

LightPadManager

LightPadManager references all light objects (LightInfo).

Each LightInfo has an index and an effect system that can modify the light parameters.

C#

public struct LightInfo 
{
    public int lightIndex;
    public EffectSystem effectSystem;
}

At start, LightPadManager registers all light buttons into a dictionnary.

Also, there are 3 networked dictionnaries to save the various parameters of the lights (light on/off, movement on/off, light intensity)

C#

[Networked]
[Capacity(MAX_LIGHTS)]
public NetworkDictionary<int, NetworkBool> LightStatus { get; }

[Networked]
[Capacity(MAX_LIGHTS)]
public NetworkDictionary<int, NetworkBool> LightMovementStatus { get; }

[Networked]
[Capacity(MAX_LIGHTS)]
public NetworkDictionary<int, float> LightIntensities { get; }

ChangeDetector changeDetector;

When the local player push a button, the LightPadManager is informed by the associated method (ChangeLightState()/ChangeMovementState()/ChangeIntensity()) .
Then the associated networked dictionnary is updated.

For example, when a light is switch on/off, LightPadManager requests for the StateAuthority if it doesn't have it yet and then update the networked dictionnary so that all remote users will received the update.

C#

public async void ChangeLightState(LightPadTouch lightPadTouch, bool isLightOn)
{
    if (!Object.HasStateAuthority)
    {
        await Object.WaitForStateAuthority();
    }

    // update network status
    LightStatus.Set(lightPadTouch.LightIndex, isLightOn);

    // movement must be stopped if the light is off
    if (!isLightOn)
        LightMovementStatus.Set(lightPadTouch.LightIndex, false);
}

As soon as a dictionnary is updated, the Refresh() methods is called on local & remote players thanks to the ChangeDetector.

C#

public override void Render()
{
    base.Render();
    foreach (var changedVar in changeDetector.DetectChanges(this))
    {
        if (changedVar == nameof(LightStatus))
        {
            Refresh();
        }

        if (changedVar == nameof(LightMovementStatus))
        {
            Refresh();
        }

        if (changedVar == nameof(LightIntensities))
        {
            Refresh();
        }
    }
}

Refresh() is in charge to update all lights state and associated buttons using the UpdateLightsAndButtons() method

When a player joins, its lights are updated with the networked dictionnaries.

Back to top