Simulation 2

Anknüpfend an die erste Simulation werde ich eine zweite, ausgefeiltere Erstellen. Mithilfe des Programmes SoundParticles werden wieder 50 3D-Objekte im vorgefertigten Raumverteilt und durch sorgfältiges einstellen der Parameter eine Schwarmdynamik erzeugt. Das Produkt daraus wird nun in 4th-order Ambisonic gerendert und ausgegeben. Das so entstandene File importiere ich in Reaper um mit der IEM – Plugin Suite (Scene Rotator, Binaural Decoder) eine authentische Kopfbewegung zu realisieren. Das dadurch entstandene Produkt soll als Anhaltspunkt für die folgende, interaktive Installation gelten.

Es die Sounds können beliebig gewählt werden von natürlich bis abstrakt. Für diese Aufnahme habe ich in “Serum” wieder 7 leicht unterschiedliche Soundfiles erstellt, die zusammen den Flug von mehreren Maschinen mit Rotoren oder großen Insekten nachahmen sollten.

Des Weiteren wurde, zur besseren Veranschaulichung, eine binaurale (also bitte mit Kopfhörern wiedergeben) Bildschirmaufnahme des Programmfensters erstellt.

Spatial Audio in Unity (Einführung)

Spatial Audio, wird vor allem auch in der Unterhaltungsindustrie immer wichtiger und ist mittlerweile auch kaum mehr wegzudenken. Vorallem in Games, welche zunehmend mit Kopfhörern gespielt werden, hat sich “3D-Audio” etabliert.

Unity kann ohne externe Hilfsmittel (z.B. Middleware wie Fmod, Wwise oder die folgenden VR Kits) kein Spatial Audio wiedergeben (Stand 2018). Dafür gibt es jedoch viele Lösungen von verschiedenen Herstellern, welche sich je nach Einsatzgebiet besser oder schlechter eignen. Als fundamentales Entscheidungskriterium sollte zu Beginn entschieden werden ob man Channel- oder Objektbasiert spatialisieren möchte.

Channel:

In kanalbasierten (Audio-)Formaten wird je Channel fix ein Lautsprecher zugeordnet. Mit Phantomschallquellen können virtuelle Lautsprecher durch Summenlokalisation erzeugt werden (zb.: Phantommitte bei der Stereoaufstellung), diese müssen sich jedoch immer auf der Ebene der vorhandenen Lautsprecher befinden. Kanalbasierte Formate werden hauptsächlich zur linearen Speicherung und Wiedergabe genutzt.3

Object:

„In der Objektorientierung entsteht ein komplexes System aus mit- und zueinander kooperierender
Objekte. Eigenschaften und Funktionen von Objekten werden dabei zusammengefasst, um diese
einfacher auf reale Beispiele übertragen.“4

Für die objektorientierte Audioumgebung werden vielfältige Eigenschaften
gebraucht, um die Abhängigkeiten, aber auch Unabhängigkeiten der Objekte im komplexen System zu
definieren. Zunächst wird in der Produktion unabhängig vom Endmedium produziert (z.B.
Positionierung einer Schallquelle im Raum), da moderne Wiedergabemedien nicht immer kanaldefiniert
sind. Auf der Wiedergabeseite wandelt ein Dekoder dann die Audioszene auf das jeweilige Medium um
(Lautsprecheranlage, Handy, Kopfhörer, usw.) Dadurch wird auch die Wiedergabe in einem virtuellen
Raum unabhängig zum realen Raum möglich (z.B.
HRTF)5

Für dieses Projekt werde ich mich natürlich für ein Objektbasiertes Format entscheiden.

Nach weiterer Recherche hat sich herausgestellt, dass für dieses Project die Kombination der Unity-Engine mit der Fmod Middleware aufgrund bester Performance und freier Verfügbarkeit für nicht kommerzielle Projekte die optimale Lösung ist. Fmod kommt im Paket mit dem sogenannten “Resonance Audio Spatializer”, welcher eine einfache Ausgabe in ein binaurales Audioformat ermöglicht. Aufgrund der Tatsache, dass Kopfhörer verwendet werden, ist das natürlich ein klarer Gewinn.

Anschließend noch die Produktbeschreibungen der Herstellerseite von den Programmen:

Fmod Object Mixer

To get more discrete spatialization of an audio signal you can use the FMOD object spatializer, so named because the audio signal is packaged with the spatialization information (position, orientation, etc) and sent to an object mixer. Often used to highlight important sounds with strong localization to add interest to a scene, usually used in-conjunction with the channel based approach, be that 7.1.4 or even simply 5.1 / 7.1.

There is no limit to how many FMOD_DSP_TYPE_OBJECTPAN DSPs you create, however there is a flexible limit on a per-platform basis for how many can be processed. When there are more object spatializers in use than there is available resources FMOD will virtualize the least significant sounds by processing with a tradition channel based mix.

An important consideration when using object spatializers is signal flow, unlike most DSPs, after the signal enters the DSP it is then sent out to the object mixer. The object mixer could be a software library or a physical piece of hardware, in all cases though you no longer have access to that signal. Any processing you would like to perform must be done before that point. However (to assist mixing) the object spatializer will automatically apply any “downstream” ChannelGroup volume settings so it behaves similarly to the standard FMOD spatializer.

Resonance Audio Spatializer

Once such third party is the Resonance Audio cross-platform suite of plugins that comes bundled with FMOD. Resonance Audio offers a “Source” plugin which behaves much like the FMOD object spatializer in that audio is sent out to an object mixer, however the final signal returns as binaural output at the “Listener” plugin. Resonance Audio also offers a “Soundfield” plugin for playing back first order Ambisonic sound fields. For more details about the usage of Resonance Audio please check out the user guide.

https://resonance-audio.github.io/resonance-audio/develop/fmod/getting-started

Connecting Fmod to Unity

https://fmod.com/resources/documentation-unity?version=2.01&page=user-guide.html

1 Daniel Korgler – Virtual Reality-Spiele entwickeln mit Unity®: Grundlagen, Beispielprojekte …, 2018 https://books.google.at/books?id=ondGDwAAQBAJ&pg=PT211&lpg=PT211&dq=spatial+audio+m%C3%B6glichkeiten+in+unity&source=bl&ots=9EJBO0tgbu&sig=ACfU3U1ZLOiLCgG6qsF-LOyU040ee1-CUg&hl=de&sa=X&ved=2ahUKEwj7icKror70AhVQhP0HHQIkClkQ6AF6BAgXEAM#v=onepage&q=spatial%20audio%20m%C3%B6glichkeiten%20in%20unity&f=false

2 https://documentation.help/FMOD-Studio-API/spatialaudio.html

3 Weinzierl, S. (2008). Handbuch der Audiotechnik.

4 Stahl, S. (2013). Objektorientierte Audioumgebung für Surround-Sound (Master’s thesis,
Hochschule der Medien, Stuttgart, Deutschland).
https://filestore.hdm-stuttgart.de/s/WXtrh5ADxD2IGfl

5 Max Utke 2017, https://curdt.home.hdm-stuttgart.de/PDF/Objekt-_u_kanalbasierte_Audioformate.pdf

Unity Flocking

Auf der Unity Hompage findet sich praktischerweise ein simples Tutorial mit Videoanleitung, welches Flocking-Algorithmen verständlich machen soll. Da meine C# Kenntnisse noch recht bescheiden sind, hat mich das Ganze doch etwas Zeit und nerven gekostet, aber nun, endlich, habe ich es vollbracht.

Hier der Code des FlockingManagers und des Flock Files mit Erklärung:

Code Flock manager:

using UnityEngine;
public class FlockManager : MonoBehaviour {

//Erstellt den Flock Manager

    //Zugang zum Fish Game Object

    public GameObject fishPrefab;

    //Variierbare Startnummer an Fischen

    public int numFish = 20;

    //Array für die Erstellung der Fische

    public GameObject[] allFish; 

    //Räumliche Limits

    public Vector3 swimLimits = new Vector3(5.0f, 5.0f, 5.0f);

    //Gemeinsame Zielposition

    public Vector3 goalPos;

    //Variabel verstellbare Parameter in Unity

    [Header("Fish Settings")]

    [Range(0.0f, 5.0f)]

    public float minSpeed;          // Minimum speed range

    [Range(0.0f, 5.0f)]

    public float maxSpeed;          // Maximum speed range

    [Range(1.0f, 10.0f)]

    public float neighbourDistance; //Distanz zum Nachbarn

    [Range(0.0f, 5.0f)]

    public float rotationSpeed;   //Rotationsgeschwindigkeit der FishPrefabs

 void Start() {

       // Erstellung der Fische

        allFish = new GameObject[numFish];

       //looped und instantiiert (Fisch)

        for (int i = 0; i < numFish; ++i) {

//Erstellt Position des Fisches → Flockmanager+randomVektor

            Vector3 pos = this.transform.position + new Vector3(Random.Range(-swimLimits.x, swimLimits.x),

                                                                Random.Range(-swimLimits.x, swimLimits.x),

                                                                Random.Range(-swimLimits.x, swimLimits.x));

            allFish[i] = (GameObject)Instantiate(fishPrefab, pos, Quaternion.identity);

            allFish[i].GetComponent<Flock>().myManager = this;

        }

        //Ziel zu dem sich die Fische hinbewegen

        goalPos = this.transform.position;

    }

   //Wird pro Frame upgedatet

    void Update() {

      //dem Ziel wird jedes mal ein neuer randomisierter Faktor hinzugefügt

        if (Random.Range(0.0f, 100.0f) < 10.0f) {

            goalPos = this.transform.position + new Vector3(Random.Range(-swimLimits.x, swimLimits.x),

                                                            Random.Range(-swimLimits.x, swimLimits.x),

                                                            Random.Range(-swimLimits.x, swimLimits.x));

        }

    }

}

Code Flocking:

using UnityEngine;

public class Flock : MonoBehaviour {

  //Zugriff auf den Flock Manager

    public FlockManager myManager;

    //Initial Speed vom fisch

    float speed;

    // Bool um die Raumlimitation zu checken

    bool turning = false;

    void Start() {

        // Random speed für jeden einzelnen Fisch

        speed = Random.Range(myManager.minSpeed, myManager.maxSpeed);

    }

    //Wird pro Frame upgedated

    void Update() {

        // Bbox des Manager Cubes

        Bounds b = new Bounds(myManager.transform.position, myManager.swimLimits * 2.0f);

        // wenn der Fisch auf Widerstand trifft oder an die Grenzen des Cubes kommt, muss er umdrehen

              RaycastHit hit = new RaycastHit();

        Vector3 direction = Vector3.zero;

        if (!b.Contains(transform.position)) {

            turning = true;

            direction = myManager.transform.position - transform.position;

        } else if (Physics.Raycast(transform.position, this.transform.forward * 50.0f, out hit)) {

            turning = true;

            // Debug.DrawRay(this.transform.position, this.transform.forward * 50.0f, Color.red);

            direction = Vector3.Reflect(this.transform.forward, hit.normal);

        } else {

            turning = false;

        }

        // Test ob gedreht wird

        if (turning) {

            // Drehen zum Cube Center

            transform.rotation = Quaternion.Slerp(transform.rotation,

                                                  Quaternion.LookRotation(direction),

                                                  myManager.rotationSpeed * Time.deltaTime);

        } else {

            // 10% chance of altering prefab speed

            if (Random.Range(0.0f, 100.0f) < 10.0f) {

                speed = Random.Range(myManager.minSpeed, myManager.maxSpeed);

            }

            // 20& chance of applying the flocking rules

            if (Random.Range(0.0f, 100.0f) < 20.0f) {

                ApplyRules();

            }

        }

        transform.Translate(0.0f, 0.0f, Time.deltaTime * speed);

    }

    void ApplyRules() {

        GameObject[] gos;

        gos = myManager.allFish;

        Vector3 vcentre = Vector3.zero;

        Vector3 vavoid = Vector3.zero;

        float gSpeed = 0.01f;

        float nDistance;

        int groupSize = 0;

        foreach (GameObject go in gos) {

            if (go != this.gameObject) {

                nDistance = Vector3.Distance(go.transform.position, this.transform.position);

                if (nDistance <= myManager.neighbourDistance) {

                    vcentre += go.transform.position;

                    groupSize++;

                    if (nDistance < 1.0f) {

                        vavoid = vavoid + (this.transform.position - go.transform.position);

                    }

                    Flock anotherFlock = go.GetComponent<Flock>();

                    gSpeed = gSpeed + anotherFlock.speed;

                }

            }

        }

        if (groupSize > 0) {

            // Find the average centre of the group then add a vector to the target (goalPos)

            vcentre = vcentre / groupSize + (myManager.goalPos - this.transform.position);

            speed = gSpeed / groupSize;

            Vector3 direction = (vcentre + vavoid) - transform.position;

            if (direction != Vector3.zero) {

                transform.rotation = Quaternion.Slerp(transform.rotation,

                                                      Quaternion.LookRotation(direction),

                                                      myManager.rotationSpeed * Time.deltaTime);

            }

        }

    }

}

Flocking Tutorial:

https://learn.unity.com/tutorial/flocking#

Schwarmsimulation mit SoundParticles

Um einen kleinen Eindruck zu bekommen, wie sich das Produkt möglicherweise anhören könnte, habe ich mit der Software SoundParticles (welche für Studenten und nicht-kommerzielle Zwecke frei zur Vefügung steht) eine Simulation eines Schwarms kreiirt, der sich in einem virtuellen 3D Raum über den Beobachter hinweg bewegt. Praktischerweise kann man in SoundParticles mit wenigen einfachen Klicks die verschiedensten Outputmöglichkeiten auswählen. Ich habe das Projekt nun in Stereo gerendert, aber ebenfalls ein File für die Lautsprecheraufstellung im CUBE des IEM erstellt. So kann ich bei Gelegenheit (falls dieser Lockdown jemals endet) auch die Wirkung in verschiedenen Ambisonic-Klassen testen.

Für die “Partikel” habe ich fünf 30-Sekunden Soundfiles mit Xfer Serum erstellt. Speziell wurde darauf geachtet, dass das Signal möglichst trocken bleibt und sich die verschiedenen Spuren nur leicht im Pitch und Frequenzanteil unterschieden, um eine möglichst homogene, aber doch leicht variierte Masse zu erreichen. Für den Effekt eines schlagenden Flügels verwende ich einen Sawtooth-Layer mit einem relativ schnellen LFO der auf die Volume geroutet wurde (Tremolo) und einem wenige ms kurzen Transienten um mehr Charakter hineinzubringen.

Danach füge ich die Soundfiles in SoundParticles ein und erstelle eine Particle-Group. Dies scheint anfangs etwas kompliziert, da ich erst die Navigation für die Automation lernen muss (welche meiner Meinung auch noch ausbaufähig ist) aber mit etwas tüftelei fliegt mein Schwarm bald durch den 3D Raum. Hier und da noch ein paar randomisierte Parameter hinzugefügt wirkt das Ganze auch schon etwas lebendig und das Ergebnis kann sich auch schon hören lassen.

WICHTIG! Das Soundfile wurde Stereo für Lautsprecher exportiert, darum wird das Erlebnis auf Kopfhörern beeinträchtigt sein.

Es ist möglicherweise noch wichtig anzumerken, dass es sich hier nicht um eine Schwarmsimulation handelt, sondern nur um eine Darstellung mit festen Parametern um sich das auditive Abbild vor Augen (oder eher Ohren) führen zu können.

https://xferrecords.com/products/serum

https://soundparticles.com/