class in Clatter.Core
An AudioGenerator can generate audio within a dynamic physics simulation.
AudioGenerator is not required for audio generation but is usually your best option, for two reasons. First, AudioGenerator automatically converts CollisionEvent
data into audio data, meaning that it's suitable for physics simulation. Second, AudioGenerator is multi-threaded, meaning that concurrent audio generation is very fast.
AudioGenerator is structured like a UnityEngine MonoBehaviour object but it isn't a subclass of MonoBehaviour and won't update like one; Update() needs to be called manually.
Regarding randomness: AudioGenerator has an rng parameter that is a System.Random object. In Clatter, audio is generated from both fixed values and random values; see the constructor for Modes
and Modes
.AdjustPowers(). In most cases, you'll want the audio to be truly random. If you want to replicate the exact same audio every time you run your program, set a random seed: Random rng = new Random(0).
Name | Type | Description | Default Value |
---|---|---|---|
maxNumAudioEvents | int | The maximum number of impacts, scrapes, and rolls that can occur on one frame. After this many events, any new audio events are ignored. | 200 |
Name | Type | Description | Default Value |
---|---|---|---|
onImpact | AudioGenerationAction | Invoked when impact audio is generated. | |
onScrapeStart | AudioGenerationAction | Invoked when audio is generated by a new scrape event. | |
onScrapeOngoing | AudioGenerationAction | Invoked when audio is generated by an ongoing scrape event. | |
onScrapeEnd | Action< int > | Invoked when a scrape ends. |
public delegate void AudioGenerationAction(CollisionEvent collisionEvent, Samples samples, Vector3d position, int audioSourceId)
Delegate for actions that are invoked during audio generation.
Name | Type | Description |
---|---|---|
collisionEvent | CollisionEvent |
The collision event that generated the audio. |
samples | Samples |
The audio samples. |
position | Vector3d |
The position of the audio source. |
audioSourceId | int | The audio source ID. |
public AudioGenerator(IEnumerable< ClatterObjectData > clatterObjects, int? seed=null)
Name | Type | Description |
---|---|---|
clatterObjects | ClatterObjectData |
The objects that will generate audio. |
seed | int? | The random seed. If null, the seed is random. |
public void Update()
Call this once per frame to process collision events and generate audio.
public void AddCollision(CollisionEvent collisionEvent)
Register a new collision audio event.
Name | Type | Description |
---|---|---|
collisionEvent | CollisionEvent |
The collision event. |
public void End()
Call this to announce to kill lingering threads.
This is a minimal example of how to process multiple concurrent collisions with an AudioGenerator and, using WavWriter
, write .wav files. Note that we're making a few implausible assumptions:
All of the objects are randomly generated. In a real simulation, you'll probably want more control over the objects' audio values.
All of the collisions have a centroid of (0, 0, 0). In a real simulation, the collisions should probably be spatialized.
All of the collision events are impacts. In a real simulation, we could add a
using System;
using System.IO;
using Clatter.Core;
public static class AudioGeneratorImpact
{
private static AudioGenerator generator;
private static Queue audioData = new Queue();
public static void Main(string[] args)
{
Random rng = new Random();
// Add some objects.
ClatterObjectData[] objects = new ClatterObjectData[64];
uint[] objectIds = new uint[64];
for (uint i = 0; i < objects.Length; i++)
{
// Generate a random object.
objects[i] = new ClatterObjectData(i, ImpactMaterial.glass_1, rng.NextDouble(), rng.NextDouble(), rng.NextDouble() * 5);
objectIds[i] = i;
}
// Create the audio generator.
generator = new AudioGenerator(objects);
// Listen for impact events.
generator.onImpact += OnImpact;
// Get the output directory.
string outputDirectory = Path.GetFullPath("output");
// Iterate for 15 frames.
for (int i = 0; i < 15; i++)
{
// Get a random number of collisions.
int numCollisions = rng.Next(15, 30);
// Randomize the object IDs.
objectIds = objectIds.OrderBy(x => rng.NextDouble()).ToArray();
int objectIndex = 0;
// Generate collisions.
for (int j = 0; j < numCollisions; j++)
{
// Get random primary and secondary objects.
ClatterObjectData primary = objects[objectIds[objectIndex]];
ClatterObjectData secondary = objects[objectIds[objectIndex + 1]];
// Generate a collision.
CollisionEvent collisionEvent = new CollisionEvent(primary, secondary, AudioEventType.impact, rng.NextDouble() * 1.75, Vector3d.Zero);
// Add the collision.
generator.AddCollision(collisionEvent);
// Increment the object index for the next collision.
objectIndex += 2;
if (objectIndex >= objects.Length)
{
objectIndex = 0;
}
}
// Update.
generator.Update();
// Write the audio wav data.
int audioIndex = 0;
while (audioData.Count > 0)
{
WavWriter writer = new WavWriter(Path.Combine(outputDirectory, audioIndex + ".wav"));
writer.Write(audioData.Dequeue());
writer.End();
audioIndex++;
}
}
}
private static void OnImpact(CollisionEvent collisionEvent, Samples samples, Vector3d centroid, int audioSourceId)
{
audioData.Enqueue(samples.ToInt16Bytes());
}
}