Endless Cave Ambience

Endless Cave Music (Random Music System)

In Articles by John

Recently, I created an ambient, dungeon style track, made for a cave scenario, that was essentially a mix of ambient, ethereal pads and strings, with timed melodic and percussive cues. More like sound design than a fixed musical progression, I wanted the track to feel random and unstructured. Although there was a musical time, most of the parts within the track were placed in a deliberately unordered way, and simply separated out to keep it even. Once I finished the track, I realised that I had been trying to emulate randomly placed parts. Wouldn’t it be much better to procedurally trigger them randomly as the track progressed?

I went back into the project and isolated the core elements of the music. A looping bed containing the pads and strings, melodic elements using pitched instruments (I used piano and brass mostly), percussive cues and finally some sweeping ethereal FX. I exported all of these individual sounds into banks of cues.In total, as well as my looping bed, I had seven melodic cues, five percussive and two long effects.

Using Unity, I set up four different audio sources to act as different output channels for each type of cue. Then I wrote a script that would randomly switch each audio source’s clip at defined musical intervals, based on the music’s bpm and time signature. Finally I added a condition to prevent cues from cutting each other off, so that a cue can still be longer than the time between triggers (this was important for the FX cues especially, which each last for several bars).

The end result, after a morning of dabbling, was a basic random music system that can play ambient dungeon music endlessly with enough variation that it does not repeat. I’ve shared the package for free on the Unity Asset Store and the (since improved) script I wrote is below. You can also try a HTML5 version, right here.

using UnityEngine;
using System.Collections;

public class MusicScript : MonoBehaviour {

	public AudioSource melody;
	public AudioSource perc;
	public AudioSource fx;

	public AudioClip[] melodyArray;
	public AudioClip[] percArray;
	public AudioClip[] fxArray;

	public int bpm=120;
	public int beatsPerBar=4;
	public int barsPerTrigger=2;

	public int melodyChance=50;
	public int percChance=40;
	public int fxChance=20;

	float timer;
	float triggerTime;

	// Use this for initialization
	void Start () {
		triggerTime = 60f / bpm * beatsPerBar * barsPerTrigger;
		Debug.Log(triggerTime);
	}
	
	// Update is called once per frame
	void Update () {
		timer += Time.deltaTime;
		if (timer > triggerTime)
		{
			PlayClips();
		}
	}

	void PlayClips()
	{
		timer=0f;
		int melodyRoll=Random.Range(0,100);
		int percRoll=Random.Range(0,100);
		int fxRoll=Random.Range(0,100);

		if (melodyChance > melodyRoll && !melody.isPlaying)
		{
			int melodyIndex=Random.Range(0,melodyArray.Length);
			melody.clip = melodyArray[melodyIndex];
			melody.Play();
			Debug.Log("Melody Played");
		}

		if (percChance > percRoll && !perc.isPlaying )
		{
			int percIndex=Random.Range(0,percArray.Length);
			perc.clip = percArray[percIndex];
			perc.Play();
			Debug.Log("Perc Played");
		}

		if (fxChance > fxRoll && !fx.isPlaying )
		{
			int fxIndex=Random.Range(0,fxArray.Length);
			fx.clip = fxArray[fxIndex];
			fx.Play();
			Debug.Log("FX Played");
		}
	}
}