Modifying pitch/speed audio parameters by script (As in AudioSource)

Hello,

So I would like to know how to modify pitch/speed parameters without using the parameters on AudioSource and without modifying the OutputSampleRate in AudioSettings.

I am processing audio data using OnAudioFilterRead() I can match the unity’s DSP OutputSampleRate with my wanted sound frequency but the pitch/speed is actually too high.

anyone can help?

thanks

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));*
  •  			}*
    
  •  			for (int i = 0; i < 100; i++) {*
    

_ internBufferView.SetPixel (internBufferView.width * ptReadBuffer / internBufferSize, i, Color.blue);_

  •  			}*
    
  •  			internBufferView.Apply ();*
    
  •  			for (int i = 0; i < 100; i++) {*
    

_ internBufferView.SetPixel (internBufferView.width * ptWriteBuffer / internBufferSize, i, Color.red);_

  •  			}*
    
  •  			internBufferView.Apply ();	*
    
  •  		}*
    
  •  	}*
    
  •  }*
    

_ /** 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();
}*_

* }*
}