Hey
For those interested in audio generation/programmation I finally found that the best solution was to stream audio data through an AudioClip “buffer”. I can then easily access the parameters of the audioSource and add the unity Pro features I need for sound (Filters, effects …).
cheers
Edit : full script
//------------------------------------------------------------------------------
// <auto-generated>
// Ce code a été généré par un outil.
// Version du runtime :4.0.30319.1022
//
// Les modifications apportées à ce fichier peuvent provoquer un comportement incorrect et seront perdues si
// le code est régénéré.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
namespace GrainerLib
{
/**
Creation : 06 / 2014
Author : Maxime GILLET
**/
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(AudioSource))]
/**
Class description:
This is the Grainer Class. It Randomly generates grains through a clip regarding a
SourceClip, given an offset (a position in the clip source), a Delta (a randomisation
tolerance) an overlap ratio between grains and any other parameters/Filters you can
add with Unity Pro(Low Pass, Reverb, Distortion...) or find on the audioSource (pitch
pan, volume...).
*/
public class Grainer : MonoBehaviour {
/** The Audio CLip source from which we pick the grains **/
public AudioClip SourceClip = null;
/** Number of channels in the sourceClip */
public int channels;
/** Position where to pick the grains **/
public int Offset;
/** Delta randomisation rate from Offset **/
public int Delta;
/** Number of Samples in one Grain **/
public int SamplesInOneGrain = 2048;
/** Overlap between grains**/
public float Covering = 0.1f;
/** IDN of the envelope function chosen in inspector**/
public int FadeFunction = 0;
/**Toggle for automatic incrementing Offset**/
public bool toggleOffsetInc = false;
/** Incrementation speed of the offset**/
public int OffsetIncSpeed = 1;
/** AudioSource assigned to the grainer **/
public AudioSource source = null;
/** Maximum Samples in One Grain */
public int MaxSamplesInOneGrain = 100000;
/** Minimum Samples in One Grain (default is 256)*/
public int MinSamplesInOneGrain = 256;
/** Maximum overlap ratio between grains*/
public float MaxCovering = 0.49f;
/** Minimum overlap ratio between grains*/
public float MinCovering = 0.01f;
/** Source Clip frequency (44100, 48000 ...)*/
public int sampleRate = 44100;
/** boolean for 3D grainer mode (false means grainer is in 2D mode) */
public bool Grainer3D = false;
/** Reference to the AudioListener */
public AudioListener Listener;
/** Default name of the grainer**/
public string Name = "Grainer";
/** Display the internBuffer view**/
public bool toggleView = false;
/** Display the Grainer controls in the inspector */
public bool showInspector = true;
/** Tell if the grainer is playing and currently extracting the grains */
public bool IsPlaying = false;
/** Private Variables **/
float lastRatio; //Remember last ratio parameter (used to update envelopes on change)
float lastSamples; //Remember last samples parameter (used to update envelopes on change)
int lastFadeFunction; //Remember last FadeFunction parameter (used to update envelopes on change)
float[] GrainData; //Temporary stored data of the grain
float[] internBuffer; //Circular Buffer Data (pre-DSP)
int QueueLength = 2; // Length of the intern buffer queue (pre-DSP)
int internBufferSize; //The size in samples of the intern Circular Buffer (pre-DSP) = MaxSamplesInOneGrain*QueueLength
int ptReadBuffer = 0; //Read pointer in the Circular Buffer
int ptWriteBuffer = 0; //Write pointer in the Circular Buffer
int ptReadGrain = 0; //Read pointer in the Grain
AudioClip internBufferClip; //Output Clip used to send Data to DSP
float[] FadeIN; //FadeIN curve data
float[] FadeOUT; //FadeOUT curve data
float[] SourceClipData; //Storage of all samples of the sourceClip (optional)
bool initIsDone = false; //To know if Initiation has already been done
int ClipSamples; //number of samples in the sourceClip
/** Texture representing the internbufferview */
public Texture2D internBufferView;
/** Gain to apply to scale the internbufferview */
public float gain = 1f;
public float fac = 1f;
/** Setter of the offset */
public void SetOffset(int offset){
if(offset < this.Delta/2)
this.Offset = this.Delta/2;
else if(offset > SourceClip.samples - this.Delta/2)
this.Offset = SourceClip.samples - this.Delta/2;
else
this.Offset = offset;
}
/** Setter of the Delta */
public void SetDelta(int delta){
if(delta > SourceClip.samples)
this.Delta = SourceClip.samples;
else if(delta < 1)
this.Delta = 1;
else
this.Delta = delta;
}
/** Setter of the covering between grains */
public void SetCovering(float cover){
if(cover > MaxCovering)
this.Covering = MaxCovering;
else if(cover < MinCovering)
this.Covering = MinCovering;
else
this.Covering = cover;
}
/** Setter of the number of samples. It may have to be re-adjusted to be a multiple of the number of channels (or we lose stereo cohesion and continuity).*/
public void SetSamplesInOneGrain(int samples){
//set samples to a multiple of the number of channels
samples = samples - samples%SourceClip.channels;
if(samples > MaxSamplesInOneGrain)
SamplesInOneGrain = MaxSamplesInOneGrain;
else if(samples < MinSamplesInOneGrain)
SamplesInOneGrain = MinSamplesInOneGrain;
else
SamplesInOneGrain = samples;
}
/** return the intern buffer size */
public int GetInternBufferSize(){
return internBufferSize;
}
/** return the intern buffer as a float[]*/
public float[] GetInternBuffer(){
return internBuffer;
}
/** Play the grainer */
public void Play(){
source.Play ();
IsPlaying = true;
}
/** Stop the grainer */
public void Stop(){
source.Stop ();
}
/** OnEnable Initiation */
/*
void OnEnable(){
if(SourceClip == null)
Debug.Log ("Please Assign a clip to all the grainers before running");
else
{
InitGrainer();
}
}*/
/** On Awake Initiation */
void Awake(){
if(SourceClip == null && !Application.isPlaying)
Debug.Log ("Please Assign a clip to all the grainers before running");
else
{
InitGrainer();
}
}
/** Called on creation , used for the initiation*/
void Start() {
showInspector = false;
if (SourceClip == null)
Debug.Log ("Please Assign a clip to all the grainers before running");
else
{
InitGrainer();
}
}
/** Set Default parameters */
public void InitGrainer(){
if(!initIsDone)
{
//Debug.Log("SourceClip.frequency : "+SourceClip.frequency);
//Debug.Log("SourceClip.channels : "+SourceClip.channels);
//Debug.Log("SourceClip.samples : "+SourceClip.samples);
sampleRate = SourceClip.frequency;
channels = SourceClip.channels;
ClipSamples = SourceClip.samples;
internBufferSize = MaxSamplesInOneGrain*QueueLength;
//Allocation
internBuffer = new float[internBufferSize];
GrainData = new float[MaxSamplesInOneGrain];
//Creation of the output clip
internBufferClip = AudioClip.Create("internBuffer", 4096, channels, sampleRate, Grainer3D, true, OnAudioRead);
//Check/add audioSource
if(source == null)
{
if(audio)
source = gameObject.GetComponent("AudioSource") as AudioSource;
else
source = gameObject.AddComponent("AudioSource") as AudioSource;
}
//Assign output clip to the audioSource
source.clip = internBufferClip;
//Play Clip on the audio Source
source.Play();
IsPlaying = true;
source.loop = true;
ResetEnvelopes(Covering,SamplesInOneGrain, FadeFunction);
//PreLoad all clip Data into memory
loadClipToMemory();
initIsDone = true;
}
}
/** Load the entire clip samples into memory (SourceClipData : Array of samples)*/
void loadClipToMemory(){
//Create the clip data array (size is samples*channels)
SourceClipData = new float[ClipSamples];
// Extract all Data from source clip to Memory
SourceClip.GetData(SourceClipData,0);
}
/** Checking parameters' values and critical variables Boundaries and limits */
public void CheckAllBoundaries(){
SetDelta(Delta);
SetOffset(Offset);
SetSamplesInOneGrain(SamplesInOneGrain);
SetCovering(Covering);
}
/** Called once per frame */
void Update(){
if (source.isPlaying)
IsPlaying = true;
//If there is no clip assigned to the grainer we do nothing
if (SourceClip == null && IsPlaying ) { }
else
{
//Offset incrementation option
if (toggleOffsetInc && Delta < ClipSamples) {
Offset = (Offset + (int)(Time.deltaTime * 100 * OffsetIncSpeed)) % (ClipSamples - Delta);
}
//Checking all Boundaries and limits for all critical variables.
CheckAllBoundaries ();
//Resetting envelopes Data if needed
ResetEnvelopes (Covering, SamplesInOneGrain, FadeFunction);
//Fill Intern Buffer with Grains
FillInternBufferFromMem (SamplesInOneGrain);
//FillInternBuffer(SamplesInOneGrain);
//Entered if we want to display a live view of the internBuffer (chose in inspector)
if (toggleView && Time.frameCount%10 ==0)
{
Destroy (internBufferView);
internBufferView = new Texture2D (500, 100);
for (int i = 0; i < internBufferSize; i++) {
internBufferView.SetPixel ((int)(internBufferView.width * i / internBufferSize), (int)(50 *( gain*internBuffer *+ 1f)), new Color (255, 165, 0));*
_ internBufferView.SetPixel (internBufferView.width * ptReadBuffer / internBufferSize, i, Color.blue);_
_ internBufferView.SetPixel (internBufferView.width * ptWriteBuffer / internBufferSize, i, Color.red);_
_ /** Fill the intern (pre-DSP) Circular Buffer with grains using preloaded Samples */_
-
void FillInternBufferFromMem(int samples){*
-
//Loop Filling Grain data until we reach the read pointer limit.*
-
while( ptWriteBuffer+ptReadGrain < ptReadBuffer)*
-
{*
-
//Loop Writting grain data until we need to extract a new grain*
-
while(ptReadGrain < samples)*
-
{*
-
if(ptWriteBuffer+ptReadGrain >= ptReadBuffer)*
-
break;*
-
else{*
-
//Add Grain Data to intern Buffer*
-
internBuffer[ (ptWriteBuffer+ptReadGrain)%internBufferSize] += GrainData[ptReadGrain];*
-
ptReadGrain ++;*
-
}*
-
}*
-
//Entered when we need to load a new grain*
-
if(ptReadGrain >= samples)*
-
{*
-
ptReadGrain = 0;*
-
//Random position from which we pick the Grain*
-
int position = Offset + (int) Mathf.Floor(Random.Range(-Delta/2,+Delta/2));*
-
//Checking Data block coherence regarding the number of channels*
-
position = position - position%channels;*
-
//Extract the Grain from the samples in memory*
-
GetGrainFromMem(samples , position);*
-
//Apply Envelope to Grain*
-
ApplyEnvelope(samples);*
-
//Update Write pointer*
_ ptWriteBuffer += samples -(int)(fac* FadeOUT.Length);_
-
}*
-
// Update write pointer and read pointer values when they are both greater than the intern buffer size*
-
if( ptWriteBuffer >= internBufferSize && ptReadBuffer >= internBufferSize){*
-
ptWriteBuffer = ptWriteBuffer%internBufferSize;*
-
ptReadBuffer = ptReadBuffer%internBufferSize;*
-
}*
-
} *
-
}*
_ /** Get Grain data from the samples load into memory */_
-
void GetGrainFromMem(int samples, int pos){*
-
//Check if the position is not out of bounds*
-
if (pos > SourceClipData.Length - Mathf.Max (Delta / 2, samples))*
-
pos = SourceClipData.Length - Mathf.Max (Delta / 2, samples);*
-
//Extract Grain from the samples in Memory*
-
for(int i = 0; i < samples; i++){*
_ GrainData = SourceClipData[pos+i];_
* }*
* }*
_ /** Fill the intern (pre-DSP) Circular Buffer with grains using GetData /_
_ void FillInternBuffer(int samples){*_
* while( ptWriteBuffer+ptReadGrain < ptReadBuffer)*
* {*
* while(ptReadGrain < samples)*
* {*
* if(ptWriteBuffer+ptReadGrain >= ptReadBuffer)*
* break;*
* else{*
* internBuffer[ (ptWriteBuffer+ptReadGrain)%internBufferSize] += GrainData[ptReadGrain];*
* ptReadGrain ++;*
* }*
* }*
* if(ptReadGrain >= samples)*
* {*
* ptReadGrain = 0;*
* int position = Offset + (int) Mathf.Floor(Random.Range(-Delta/2,+Delta/2));*
* position = position - position%channels;*
* SourceClip.GetData(GrainData, position);*
* ApplyEnvelope(samples);*
* ptWriteBuffer += samples - FadeOUT.Length;*
* }*
* if( ptWriteBuffer >= internBufferSize && ptReadBuffer >= internBufferSize){*
* ptWriteBuffer = ptWriteBuffer%internBufferSize;*
* ptReadBuffer = ptReadBuffer%internBufferSize;*
* }*
* } *
* }*
_ /** Apply envelope regarding the number of channels in the script, related to how the data is stored with GetData() for multiple channels. /_
_ void ApplyEnvelope(int samples ){*_
* //partie multichannel pas encore opérationnelle*
_ /_
_ for(int step = 0; step < channels; step++){*_
_ for(int i = step; i < channels*FadeIN.Length; i+=channels){_
GrainData *= FadeIN[(i-step)/channels];
* }*
_ for(int i = step; i < channels*FadeOUT.Length; i+=channels){_
_ GrainData[samples - channelsFadeOUT.Length + i] =FadeOUT[(i-step)/channels];
}
}*/_
* //Mono “Temporaire”*
* for(int i = 0; i < FadeIN.Length; i++){*
GrainData = FadeIN;*
* }*
* for(int i = 0; i < FadeOUT.Length; i++){*
_ GrainData[samples - FadeOUT.Length + i] = FadeOUT;*_
* }*
* }*
_ /** Called every time a chunk of data is read in the output clip*/
* void OnAudioRead(float data) {*_
* int count = 0;*
* while (count < data.Length) {*
* //Data streamed through the audio clip*
* data[count] = internBuffer[ (ptReadBuffer+count)%internBufferSize];*
* //Clean dirty samples*
* internBuffer[ (ptReadBuffer+count)%internBufferSize] = 0;*
* count++;*
* }*
* ptReadBuffer = ptReadBuffer + data.Length;*
* }*
_ /** Reset the Size of the envelopes if the ratio, the samples in one Grain or the envelope function changed /
void ResetEnvelopes(float ratio, int samples , int function){*_
* if( lastRatio != ratio || lastSamples != samples || lastFadeFunction != function)*
* {*
* //set size to a multiple of the number of channels*
_ int size = (int)Mathf.Floor(samples * ratio) - (int)Mathf.Floor(samples * ratio)%channels;
* //Allocation*
* FadeIN = new float;
FadeOUT = new float;*_
* //envelope generation modes (FadeIN)*
* for (int j =0; j< FadeIN.Length;j++){*
* switch (function){*
* case 0:*
* //Log*
_ FadeIN[j] = 2Mathf.Log( 1.455f+ 0.93fj/size) - 0.75f;
* break;
case 1:
//Gaussian*
FadeIN[j] = Mathf.Exp( -Mathf.Pow((1.2fj/size -1.5f),4) );
break;
case 2:
//Hyperbolic tan*
FadeIN[j] =((float)System.Math.Tanh(5fj/size-2.4f)+1f)/2f;
break;
case 3:
//sintanh_
_ FadeIN[j] =((float)System.Math.Tanh((float)j/size))Mathf.Sin(1.8fj/size)1.34f;
break;
case 4:
//Arctan*
FadeIN[j]= Mathf.Atan(20fj/size-10f)/3f+0.5f;
break;
case 5:
//cos²*
FadeIN[j] = Mathf.Cos(1.57fj/size-1.57f)Mathf.Cos(1.57fj/size-1.57f);
break;
case 6:
//cos12*
FadeIN[j] = Mathf.Pow (Mathf.Cos(1.57f/2j/size-1.57f/2),12);
break;
}
}
//envelope generation modes (FadeOUT)
for (int j =0; j< FadeOUT.Length;j++){
switch (FadeFunction){
case 0:
//Log*
FadeOUT[size-j-1] = 2Mathf.Log( 1.455f+ 0.93fj/size) - 0.75f;
* break;
case 1:
//Gaussian*
FadeOUT[size-j-1] = Mathf.Exp( -Mathf.Pow((1.2fj/size -1.5f),4) );
break;
case 2:
//Hyperbolic tan*
FadeOUT[size-j-1] = (float)(System.Math.Tanh(5fj/size-2.4f)+1f)/2f;
break;
case 3:
//sintanh_
_ FadeOUT[size-j-1] =((float)System.Math.Tanh((float)j/size))Mathf.Sin(1.8fj/size)1.34f;
break;
case 4:
//Arctan*
FadeOUT[size-j-1] = Mathf.Atan(20fj/size-10f)/3f+0.5f;
break;
case 5:
//cos²*
FadeOUT[size-j-1] = Mathf.Cos(1.57fj/size-1.57f)Mathf.Cos(1.57fj/size-1.57f);
break;
case 6:
//cos12*
FadeOUT[size-j-1] = Mathf.Pow (Mathf.Cos(1.57f/2j/size-1.57f/2),12);
break;
}
}
//Update the last Values*
* lastRatio = ratio;
lastSamples = samples;
lastFadeFunction = function;
}
}*_
_ /** Display GUI elements /
/
* void OnGUI(){
if(toggleView){
if(GUILayout.Button(“Play”))audio.Play();
if(GUILayout.Button(“Stop”))audio.Stop();
//Dynamic view of the data stored in the circular buffer*
* if(internBufferView)GUILayout.Label(internBufferView);
}*_
_ }/
/
* void OnGUI(){*_
* if(GUILayout.Button(“Debug”))Debug.Log ("SamplesInOneGrain : “+SamplesInOneGrain +”
Fade length : "+FadeOUT.Length);*
_ }*/_
_ /** Safe exit*/
* void OnApplicationQuit() {
if(source)
source.Stop();
}*_
* }*
}