You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1091 lines
37 KiB
1091 lines
37 KiB
10 years ago
|
using System;
|
||
|
using UnityEngine;
|
||
|
|
||
|
public class SfxrSynth {
|
||
|
|
||
|
/**
|
||
|
* SfxrSynth
|
||
|
*
|
||
|
* Copyright 2010 Thomas Vian
|
||
|
* Copyright 2013 Zeh Fernando
|
||
|
*
|
||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
* you may not use this file except in compliance with the License.
|
||
|
* You may obtain a copy of the License at
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* SfxrSynth
|
||
|
* Generates and plays all necessary audio
|
||
|
*
|
||
|
* @author Zeh Fernando
|
||
|
*/
|
||
|
|
||
|
|
||
|
// Constants
|
||
|
private const int LO_RES_NOISE_PERIOD = 8; // Should be < 32
|
||
|
private enum Endian {
|
||
|
BIG_ENDIAN,
|
||
|
LITTLE_ENDIAN
|
||
|
}
|
||
|
|
||
|
// Unity specific objects
|
||
|
private SfxrParams _params = new SfxrParams(); // Params instance
|
||
|
|
||
|
private GameObject _gameObject; // Game object that will contain the audio player script
|
||
|
private SfxrAudioPlayer _audioPlayer; // Audio player script that will be attached to a GameObject to play the sound
|
||
|
|
||
|
private Transform _parentTransform; // Parent that will contain the audio (for positional audio)
|
||
|
|
||
|
// Sound properties
|
||
|
private bool _mutation; // If the current sound playing or caching is a mutation
|
||
|
|
||
|
private float[] _cachedWave; // Cached wave data from a cacheSound() call
|
||
|
private uint _cachedWavePos; // Equivalent to _cachedWave.position in the old code
|
||
|
private bool _cachingNormal; // If the synth is caching a normal sound
|
||
|
|
||
|
private int _cachingMutation; // Current caching ID
|
||
|
private float[] _cachedMutation; // Current caching wave data for mutation
|
||
|
private uint _cachedMutationPos; // Equivalent to _cachedMutation.position in the old code
|
||
|
private float[][] _cachedMutations; // Cached mutated wave data
|
||
|
private uint _cachedMutationsNum; // Number of cached mutations
|
||
|
private float _cachedMutationAmount; // Amount to mutate during cache
|
||
|
|
||
|
private bool _cachingAsync; // If the synth is currently caching asynchronously
|
||
|
|
||
|
private float[] _waveData; // Full wave, read out in chuncks by the onSampleData method
|
||
|
private uint _waveDataPos; // Current position in the waveData
|
||
|
|
||
|
private SfxrParams _original; // Copied properties for mutation base
|
||
|
|
||
|
// Synth properies
|
||
|
private bool _finished; // If the sound has finished
|
||
|
|
||
|
private float _masterVolume; // masterVolume * masterVolume (for quick calculations)
|
||
|
|
||
|
private uint _waveType; // Shape of wave to generate (see enum WaveType)
|
||
|
|
||
|
private float _envelopeVolume; // Current volume of the envelope
|
||
|
private int _envelopeStage; // Current stage of the envelope (attack, sustain, decay, end)
|
||
|
private float _envelopeTime; // Current time through current enelope stage
|
||
|
private float _envelopeLength; // Length of the current envelope stage
|
||
|
private float _envelopeLength0; // Length of the attack stage
|
||
|
private float _envelopeLength1; // Length of the sustain stage
|
||
|
private float _envelopeLength2; // Length of the decay stage
|
||
|
private float _envelopeOverLength0; // 1 / _envelopeLength0 (for quick calculations)
|
||
|
private float _envelopeOverLength1; // 1 / _envelopeLength1 (for quick calculations)
|
||
|
private float _envelopeOverLength2; // 1 / _envelopeLength2 (for quick calculations)
|
||
|
private uint _envelopeFullLength; // Full length of the volume envelop (and therefore sound)
|
||
|
|
||
|
private float _sustainPunch; // The punch factor (louder at begining of sustain)
|
||
|
|
||
|
private int _phase; // Phase through the wave
|
||
|
private float _pos; // Phase expresed as a Number from 0-1, used for fast sin approx
|
||
|
private float _period; // Period of the wave
|
||
|
private float _periodTemp; // Period modified by vibrato
|
||
|
private int _periodTempInt; // Period modified by vibrato (as an Int)
|
||
|
private float _maxPeriod; // Maximum period before sound stops (from minFrequency)
|
||
|
|
||
|
private float _slide; // Note slide
|
||
|
private float _deltaSlide; // Change in slide
|
||
|
private float _minFrequency; // Minimum frequency before stopping
|
||
|
|
||
|
private float _vibratoPhase; // Phase through the vibrato sine wave
|
||
|
private float _vibratoSpeed; // Speed at which the vibrato phase moves
|
||
|
private float _vibratoAmplitude; // Amount to change the period of the wave by at the peak of the vibrato wave
|
||
|
|
||
|
private float _changeAmount; // Amount to change the note by
|
||
|
private int _changeTime; // Counter for the note change
|
||
|
private int _changeLimit; // Once the time reaches this limit, the note changes
|
||
|
|
||
|
private float _squareDuty; // Offset of center switching point in the square wave
|
||
|
private float _dutySweep; // Amount to change the duty by
|
||
|
|
||
|
private int _repeatTime; // Counter for the repeats
|
||
|
private int _repeatLimit; // Once the time reaches this limit, some of the variables are reset
|
||
|
|
||
|
private bool _phaser; // If the phaser is active
|
||
|
private float _phaserOffset; // Phase offset for phaser effect
|
||
|
private float _phaserDeltaOffset; // Change in phase offset
|
||
|
private int _phaserInt; // Integer phaser offset, for bit maths
|
||
|
private int _phaserPos; // Position through the phaser buffer
|
||
|
private float[] _phaserBuffer; // Buffer of wave values used to create the out of phase second wave
|
||
|
|
||
|
private bool _filters; // If the filters are active
|
||
|
private float _lpFilterPos; // Adjusted wave position after low-pass filter
|
||
|
private float _lpFilterOldPos; // Previous low-pass wave position
|
||
|
private float _lpFilterDeltaPos; // Change in low-pass wave position, as allowed by the cutoff and damping
|
||
|
private float _lpFilterCutoff; // Cutoff multiplier which adjusts the amount the wave position can move
|
||
|
private float _lpFilterDeltaCutoff; // Speed of the low-pass cutoff multiplier
|
||
|
private float _lpFilterDamping; // Damping muliplier which restricts how fast the wave position can move
|
||
|
private bool _lpFilterOn; // If the low pass filter is active
|
||
|
|
||
|
private float _hpFilterPos; // Adjusted wave position after high-pass filter
|
||
|
private float _hpFilterCutoff; // Cutoff multiplier which adjusts the amount the wave position can move
|
||
|
private float _hpFilterDeltaCutoff; // Speed of the high-pass cutoff multiplier
|
||
|
|
||
|
// From BFXR
|
||
|
private float _changePeriod;
|
||
|
private int _changePeriodTime;
|
||
|
|
||
|
private bool _changeReached;
|
||
|
|
||
|
private float _changeAmount2; // Amount to change the note by
|
||
|
private int _changeTime2; // Counter for the note change
|
||
|
private int _changeLimit2; // Once the time reaches this limit, the note changes
|
||
|
private bool _changeReached2;
|
||
|
|
||
|
private int _overtones; // Minimum frequency before stopping
|
||
|
private float _overtoneFalloff; // Minimum frequency before stopping
|
||
|
|
||
|
private float _bitcrushFreq; // Inversely proportional to the number of samples to skip
|
||
|
private float _bitcrushFreqSweep; // Change of the above
|
||
|
private float _bitcrushPhase; // Samples when this > 1
|
||
|
private float _bitcrushLast; // Last sample value
|
||
|
|
||
|
private float _compressionFactor;
|
||
|
|
||
|
// Pre-calculated data
|
||
|
private float[] _noiseBuffer; // Buffer of random values used to generate noise
|
||
|
private float[] _pinkNoiseBuffer; // Buffer of random values used to generate pink noise
|
||
|
private PinkNumber _pinkNumber; // Used to generate pink noise
|
||
|
private float[] _loResNoiseBuffer; // Buffer of random values used to generate Tan waveform
|
||
|
|
||
|
// Temp
|
||
|
private float _superSample; // Actual sample writen to the wave
|
||
|
private float _sample; // Sub-sample calculated 8 times per actual sample, averaged out to get the super sample
|
||
|
private float _sample2; // Used in other calculations
|
||
|
private float amp; // Used in other calculations
|
||
|
|
||
|
private System.Random randomGenerator = new System.Random(); // Used to generate random numbers safely
|
||
|
|
||
|
|
||
|
// ================================================================================================================
|
||
|
// ACCESSOR INTERFACE ---------------------------------------------------------------------------------------------
|
||
|
|
||
|
/** The sound parameters */
|
||
|
public SfxrParams parameters {
|
||
|
get { return _params; }
|
||
|
set { _params = value; _params.paramsDirty = true; }
|
||
|
}
|
||
|
|
||
|
|
||
|
// ================================================================================================================
|
||
|
// PUBLIC INTERFACE -----------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Plays the sound. If the parameters are dirty, synthesises sound as it plays, caching it for later.
|
||
|
* If they're not, plays from the cached sound.
|
||
|
* Won't play if caching asynchronously.
|
||
|
*/
|
||
|
public void Play() {
|
||
|
if (_cachingAsync) return;
|
||
|
|
||
|
Stop();
|
||
|
|
||
|
_mutation = false;
|
||
|
|
||
|
if (_params.paramsDirty || _cachingNormal || _cachedWave == null) {
|
||
|
// Needs to cache new data
|
||
|
_cachedWavePos = 0;
|
||
|
_cachingNormal = true;
|
||
|
_waveData = null;
|
||
|
Reset(true);
|
||
|
_cachedWave = new float[_envelopeFullLength];
|
||
|
} else {
|
||
|
// Play from cached data
|
||
|
_waveData = _cachedWave;
|
||
|
_waveDataPos = 0;
|
||
|
}
|
||
|
|
||
|
createGameObject();
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Plays a mutation of the sound. If the parameters are dirty, synthesises sound as it plays, caching it for later.
|
||
|
* If they're not, plays from the cached sound.
|
||
|
* Won't play if caching asynchronously.
|
||
|
* @param mutationAmount Amount of mutation
|
||
|
* @param mutationsNum The number of mutations to cache before picking from them
|
||
|
*/
|
||
|
public void PlayMutated(float __mutationAmount = 0.05f, uint __mutationsNum = 15) {
|
||
|
Stop();
|
||
|
|
||
|
if (_cachingAsync) return;
|
||
|
|
||
|
_mutation = true;
|
||
|
|
||
|
_cachedMutationsNum = __mutationsNum;
|
||
|
|
||
|
if (_params.paramsDirty || _cachedMutations == null) {
|
||
|
// New set of mutations
|
||
|
_cachedMutations = new float[_cachedMutationsNum][];
|
||
|
_cachingMutation = 0;
|
||
|
}
|
||
|
|
||
|
if (_cachingMutation != -1) {
|
||
|
// Continuing caching new mutations
|
||
|
Reset(true); // To get _envelopeFullLength
|
||
|
|
||
|
_cachedMutation = new float[_envelopeFullLength];
|
||
|
_cachedMutationPos = 0;
|
||
|
_cachedMutations[_cachingMutation] = _cachedMutation;
|
||
|
_waveData = null;
|
||
|
|
||
|
_original = _params.Clone();
|
||
|
_params.Mutate(__mutationAmount);
|
||
|
|
||
|
Reset(true);
|
||
|
} else {
|
||
|
// Play from random cached mutation
|
||
|
_waveData = _cachedMutations[(uint)(_cachedMutations.Length * getRandom())];
|
||
|
_waveDataPos = 0;
|
||
|
}
|
||
|
|
||
|
createGameObject();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Stops the currently playing sound
|
||
|
*/
|
||
|
public void Stop() {
|
||
|
if (_audioPlayer != null) {
|
||
|
_audioPlayer.Destroy();
|
||
|
_audioPlayer = null;
|
||
|
}
|
||
|
|
||
|
if (_original != null) {
|
||
|
_params.CopyFrom(_original);
|
||
|
_original = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private int WriteSamples(float[] __originSamples, int __originPos, float[] __targetSamples, int __targetChannels) {
|
||
|
// Writes raw samples to Unity's format and return number of samples actually written
|
||
|
int samplesToWrite = __targetSamples.Length / __targetChannels;
|
||
|
|
||
|
if (__originPos + samplesToWrite > __originSamples.Length) samplesToWrite = __originSamples.Length - __originPos;
|
||
|
|
||
|
if (samplesToWrite > 0) {
|
||
|
// Interlaced filling of sample datas (faster?)
|
||
|
int i, j;
|
||
|
for (i = 0; i < __targetChannels; i++) {
|
||
|
for (j = 0; j < samplesToWrite; j++) {
|
||
|
__targetSamples[(j * __targetChannels) + i] = __originSamples[j + __originPos];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return samplesToWrite;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* If there is a cached sound to play, reads out of the data.
|
||
|
* If there isn't, synthesises new chunch of data, caching it as it goes.
|
||
|
* @param data Float[] to write data to
|
||
|
* @param channels Number of channels used
|
||
|
* @return Whether it needs to continue (there are samples left) or not
|
||
|
*/
|
||
|
public bool GenerateAudioFilterData(float[] __data, int __channels) {
|
||
|
bool endOfSamples = false;
|
||
|
|
||
|
if (_waveData != null) {
|
||
|
int samplesWritten = WriteSamples(_waveData, (int)_waveDataPos, __data, __channels);
|
||
|
_waveDataPos += (uint)samplesWritten;
|
||
|
if (samplesWritten == 0) endOfSamples = true;
|
||
|
} else {
|
||
|
if (_mutation) {
|
||
|
if (_original != null) {
|
||
|
_waveDataPos = _cachedMutationPos;
|
||
|
|
||
|
int samplesNeeded = (int)Mathf.Min((__data.Length / __channels), _cachedMutation.Length - _cachedMutationPos);
|
||
|
|
||
|
if (SynthWave(_cachedMutation, (int)_cachedMutationPos, (uint)samplesNeeded) || samplesNeeded == 0) {
|
||
|
// Finished
|
||
|
_params.CopyFrom(_original);
|
||
|
_original = null;
|
||
|
|
||
|
_cachingMutation++;
|
||
|
|
||
|
endOfSamples = true;
|
||
|
|
||
|
if (_cachingMutation >= _cachedMutationsNum) _cachingMutation = -1;
|
||
|
} else {
|
||
|
_cachedMutationPos += (uint)samplesNeeded;
|
||
|
}
|
||
|
|
||
|
WriteSamples(_cachedMutation, (int)_waveDataPos, __data, __channels);
|
||
|
}
|
||
|
} else {
|
||
|
if (_cachingNormal) {
|
||
|
_waveDataPos = _cachedWavePos;
|
||
|
|
||
|
int samplesNeeded = (int)Mathf.Min((__data.Length / __channels), _cachedWave.Length - _cachedWavePos);
|
||
|
|
||
|
if (SynthWave(_cachedWave, (int)_cachedWavePos, (uint)samplesNeeded) || samplesNeeded == 0) {
|
||
|
_cachingNormal = false;
|
||
|
endOfSamples = true;
|
||
|
} else {
|
||
|
_cachedWavePos += (uint)samplesNeeded;
|
||
|
}
|
||
|
|
||
|
WriteSamples(_cachedWave, (int)_waveDataPos, __data, __channels);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return !endOfSamples;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Cache sound methods
|
||
|
|
||
|
/**
|
||
|
* Cache the sound for speedy playback.
|
||
|
* If a callback is passed in, the caching will be done asynchronously, taking maxTimePerFrame milliseconds
|
||
|
* per frame to cache, them calling the callback when it's done.
|
||
|
* If not, the whole sound is cached immediately - can freeze the player for a few seconds, especially in debug mode.
|
||
|
* @param callback Function to call when the caching is complete
|
||
|
* @param maxTimePerFrame Maximum time in milliseconds the caching will use per frame
|
||
|
*/
|
||
|
public void CacheSound(Action __callback = null, bool __isFromCoroutine = false) {
|
||
|
Stop();
|
||
|
|
||
|
if (_cachingAsync && !__isFromCoroutine) return;
|
||
|
|
||
|
if (__callback != null) {
|
||
|
_mutation = false;
|
||
|
_cachingNormal = true;
|
||
|
_cachingAsync = true;
|
||
|
|
||
|
GameObject _surrogateObj = new GameObject("SfxrGameObjectSurrogate-" + (Time.realtimeSinceStartup));
|
||
|
SfxrCacheSurrogate _surrogate = _surrogateObj.AddComponent<SfxrCacheSurrogate>();
|
||
|
_surrogate.CacheSound(this, __callback);
|
||
|
} else {
|
||
|
Reset(true);
|
||
|
|
||
|
_cachedWave = new float[_envelopeFullLength];
|
||
|
|
||
|
SynthWave(_cachedWave, 0, _envelopeFullLength);
|
||
|
|
||
|
_cachingNormal = false;
|
||
|
_cachingAsync = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Caches a series of mutations on the source sound.
|
||
|
* If a callback is passed in, the caching will be done asynchronously, taking maxTimePerFrame milliseconds
|
||
|
* per frame to cache, them calling the callback when it's done.
|
||
|
* If not, the whole sound is cached immediately - can freeze the player for a few seconds, especially in debug mode.
|
||
|
* @param mutationsNum Number of mutations to cache
|
||
|
* @param mutationAmount Amount of mutation
|
||
|
* @param callback Function to call when the caching is complete
|
||
|
* @param maxTimePerFrame Maximum time in milliseconds the caching will use per frame
|
||
|
*/
|
||
|
public void CacheMutations(uint __mutationsNum = 15, float __mutationAmount = 0.05f, Action __callback = null, bool __isFromCoroutine = false) {
|
||
|
Stop();
|
||
|
|
||
|
if (_cachingAsync && !__isFromCoroutine) return;
|
||
|
|
||
|
_cachedMutationsNum = __mutationsNum;
|
||
|
_cachedMutations = new float[_cachedMutationsNum][];
|
||
|
|
||
|
if (__callback != null) {
|
||
|
_mutation = true;
|
||
|
_cachingAsync = true;
|
||
|
|
||
|
GameObject _surrogateObj = new GameObject("SfxrGameObjectSurrogate-" + (Time.realtimeSinceStartup));
|
||
|
SfxrCacheSurrogate _surrogate = _surrogateObj.AddComponent<SfxrCacheSurrogate>();
|
||
|
_surrogate.CacheMutations(this, __mutationsNum, __mutationAmount, __callback);
|
||
|
} else {
|
||
|
Reset(true);
|
||
|
|
||
|
SfxrParams original = _params.Clone();
|
||
|
|
||
|
for (uint i = 0; i < _cachedMutationsNum; i++) {
|
||
|
_params.Mutate(__mutationAmount);
|
||
|
CacheSound();
|
||
|
_cachedMutations[i] = _cachedWave;
|
||
|
_params.CopyFrom(original);
|
||
|
}
|
||
|
|
||
|
_cachingAsync = false;
|
||
|
_cachingMutation = -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the parent transform of this audio, for positional audio
|
||
|
* @param __transform The transform object of the parent
|
||
|
*/
|
||
|
public void SetParentTransform(Transform __transform) {
|
||
|
_parentTransform = __transform;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a ByteArray of the wave in the form of a .wav file, ready to be saved out
|
||
|
* @param __sampleRate Sample rate to generate the .wav data at (44100 or 22050, default 44100)
|
||
|
* @param __bitDepth Bit depth to generate the .wav at (8 or 16, default 16)
|
||
|
* @return Wave data (in .wav format) as a byte array
|
||
|
*/
|
||
|
public byte[] GetWavFile(uint __sampleRate = 44100, uint __bitDepth = 16) {
|
||
|
Stop();
|
||
|
|
||
|
Reset(true);
|
||
|
|
||
|
if (__sampleRate != 44100) __sampleRate = 22050;
|
||
|
if (__bitDepth != 16) __bitDepth = 8;
|
||
|
|
||
|
uint soundLength = _envelopeFullLength;
|
||
|
if (__bitDepth == 16) soundLength *= 2;
|
||
|
if (__sampleRate == 22050) soundLength /= 2;
|
||
|
|
||
|
uint fileSize = 36 + soundLength;
|
||
|
uint blockAlign = __bitDepth / 8;
|
||
|
uint bytesPerSec = __sampleRate * blockAlign;
|
||
|
|
||
|
// The file size is actually 8 bytes more than the fileSize
|
||
|
byte[] wav = new byte[fileSize + 8];
|
||
|
|
||
|
int bytePos = 0;
|
||
|
|
||
|
// Header
|
||
|
|
||
|
// Chunk ID "RIFF"
|
||
|
writeUintToBytes(wav, ref bytePos, 0x52494646, Endian.BIG_ENDIAN);
|
||
|
|
||
|
// Chunck Data Size
|
||
|
writeUintToBytes(wav, ref bytePos, fileSize, Endian.LITTLE_ENDIAN);
|
||
|
|
||
|
// RIFF Type "WAVE"
|
||
|
writeUintToBytes(wav, ref bytePos, 0x57415645, Endian.BIG_ENDIAN);
|
||
|
|
||
|
// Format Chunk
|
||
|
|
||
|
// Chunk ID "fmt "
|
||
|
writeUintToBytes(wav, ref bytePos, 0x666D7420, Endian.BIG_ENDIAN);
|
||
|
|
||
|
// Chunk Data Size
|
||
|
writeUintToBytes(wav, ref bytePos, 16, Endian.LITTLE_ENDIAN);
|
||
|
|
||
|
// Compression Code PCM
|
||
|
writeShortToBytes(wav, ref bytePos, 1, Endian.LITTLE_ENDIAN);
|
||
|
// Number of channels
|
||
|
writeShortToBytes(wav, ref bytePos, 1, Endian.LITTLE_ENDIAN);
|
||
|
// Sample rate
|
||
|
writeUintToBytes(wav, ref bytePos, __sampleRate, Endian.LITTLE_ENDIAN);
|
||
|
// Average bytes per second
|
||
|
writeUintToBytes(wav, ref bytePos, bytesPerSec, Endian.LITTLE_ENDIAN);
|
||
|
// Block align
|
||
|
writeShortToBytes(wav, ref bytePos, (short)blockAlign, Endian.LITTLE_ENDIAN);
|
||
|
// Significant bits per sample
|
||
|
writeShortToBytes(wav, ref bytePos, (short)__bitDepth, Endian.LITTLE_ENDIAN);
|
||
|
|
||
|
// Data Chunk
|
||
|
|
||
|
// Chunk ID "data"
|
||
|
writeUintToBytes(wav, ref bytePos, 0x64617461, Endian.BIG_ENDIAN);
|
||
|
// Chunk Data Size
|
||
|
writeUintToBytes(wav, ref bytePos, soundLength, Endian.LITTLE_ENDIAN);
|
||
|
|
||
|
// Generate normal synth data
|
||
|
float[] audioData = new float[_envelopeFullLength];
|
||
|
SynthWave(audioData, 0, _envelopeFullLength);
|
||
|
|
||
|
// Write data as bytes
|
||
|
int sampleCount = 0;
|
||
|
float bufferSample = 0f;
|
||
|
for (int i = 0; i < audioData.Length; i++) {
|
||
|
bufferSample += audioData[i];
|
||
|
sampleCount++;
|
||
|
|
||
|
if (__sampleRate == 44100 || sampleCount == 2) {
|
||
|
bufferSample /= sampleCount;
|
||
|
sampleCount = 0;
|
||
|
|
||
|
if (__bitDepth == 16) {
|
||
|
writeShortToBytes(wav, ref bytePos, (short)Math.Round(32000f * bufferSample), Endian.LITTLE_ENDIAN);
|
||
|
} else {
|
||
|
writeBytes(wav, ref bytePos, new byte[]{ (byte)(Math.Round(bufferSample * 127f) + 128) }, Endian.LITTLE_ENDIAN);
|
||
|
}
|
||
|
|
||
|
bufferSample = 0f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return wav;
|
||
|
}
|
||
|
|
||
|
|
||
|
// ================================================================================================================
|
||
|
// INTERNAL INTERFACE ---------------------------------------------------------------------------------------------
|
||
|
|
||
|
/**
|
||
|
* Resets the runing variables from the params
|
||
|
* Used once at the start (total reset) and for the repeat effect (partial reset)
|
||
|
* @param totalReset If the reset is total
|
||
|
*/
|
||
|
private void Reset(bool __totalReset) {
|
||
|
// Shorter reference
|
||
|
SfxrParams p = _params;
|
||
|
|
||
|
_period = 100.0f / (p.startFrequency * p.startFrequency + 0.001f);
|
||
|
_maxPeriod = 100.0f / (p.minFrequency * p.minFrequency + 0.001f);
|
||
|
|
||
|
_slide = 1.0f - p.slide * p.slide * p.slide * 0.01f;
|
||
|
_deltaSlide = -p.deltaSlide * p.deltaSlide * p.deltaSlide * 0.000001f;
|
||
|
|
||
|
if (p.waveType == 0) {
|
||
|
_squareDuty = 0.5f - p.squareDuty * 0.5f;
|
||
|
_dutySweep = -p.dutySweep * 0.00005f;
|
||
|
}
|
||
|
|
||
|
_changePeriod = Mathf.Max(((1f - p.changeRepeat) + 0.1f) / 1.1f) * 20000f + 32f;
|
||
|
_changePeriodTime = 0;
|
||
|
|
||
|
if (p.changeAmount > 0.0) {
|
||
|
_changeAmount = 1.0f - p.changeAmount * p.changeAmount * 0.9f;
|
||
|
} else {
|
||
|
_changeAmount = 1.0f + p.changeAmount * p.changeAmount * 10.0f;
|
||
|
}
|
||
|
|
||
|
_changeTime = 0;
|
||
|
_changeReached = false;
|
||
|
|
||
|
if (p.changeSpeed == 1.0f) {
|
||
|
_changeLimit = 0;
|
||
|
} else {
|
||
|
_changeLimit = (int)((1f - p.changeSpeed) * (1f - p.changeSpeed) * 20000f + 32f);
|
||
|
}
|
||
|
|
||
|
if (p.changeAmount2 > 0f) {
|
||
|
_changeAmount2 = 1f - p.changeAmount2 * p.changeAmount2 * 0.9f;
|
||
|
} else {
|
||
|
_changeAmount2 = 1f + p.changeAmount2 * p.changeAmount2 * 10f;
|
||
|
}
|
||
|
|
||
|
_changeTime2 = 0;
|
||
|
_changeReached2 = false;
|
||
|
|
||
|
if (p.changeSpeed2 == 1.0f) {
|
||
|
_changeLimit2 = 0;
|
||
|
} else {
|
||
|
_changeLimit2 = (int)((1f - p.changeSpeed2) * (1f - p.changeSpeed2) * 20000f + 32f);
|
||
|
}
|
||
|
|
||
|
_changeLimit = (int)(_changeLimit * ((1f - p.changeRepeat + 0.1f) / 1.1f));
|
||
|
_changeLimit2 = (int)(_changeLimit2 * ((1f - p.changeRepeat + 0.1f) / 1.1f));
|
||
|
|
||
|
if (__totalReset) {
|
||
|
p.paramsDirty = false;
|
||
|
|
||
|
_masterVolume = p.masterVolume * p.masterVolume;
|
||
|
|
||
|
_waveType = p.waveType;
|
||
|
|
||
|
if (p.sustainTime < 0.01) p.sustainTime = 0.01f;
|
||
|
|
||
|
float totalTime = p.attackTime + p.sustainTime + p.decayTime;
|
||
|
if (totalTime < 0.18f) {
|
||
|
float multiplier = 0.18f / totalTime;
|
||
|
p.attackTime *= multiplier;
|
||
|
p.sustainTime *= multiplier;
|
||
|
p.decayTime *= multiplier;
|
||
|
}
|
||
|
|
||
|
_sustainPunch = p.sustainPunch;
|
||
|
|
||
|
_phase = 0;
|
||
|
|
||
|
_overtones = (int)(p.overtones * 10f);
|
||
|
_overtoneFalloff = p.overtoneFalloff;
|
||
|
|
||
|
_minFrequency = p.minFrequency;
|
||
|
|
||
|
_bitcrushFreq = 1f - Mathf.Pow(p.bitCrush, 1f / 3f);
|
||
|
_bitcrushFreqSweep = -p.bitCrushSweep * 0.000015f;
|
||
|
_bitcrushPhase = 0;
|
||
|
_bitcrushLast = 0;
|
||
|
|
||
|
_compressionFactor = 1f / (1f + 4f * p.compressionAmount);
|
||
|
|
||
|
_filters = p.lpFilterCutoff != 1.0 || p.hpFilterCutoff != 0.0;
|
||
|
|
||
|
_lpFilterPos = 0.0f;
|
||
|
_lpFilterDeltaPos = 0.0f;
|
||
|
_lpFilterCutoff = p.lpFilterCutoff * p.lpFilterCutoff * p.lpFilterCutoff * 0.1f;
|
||
|
_lpFilterDeltaCutoff = 1.0f + p.lpFilterCutoffSweep * 0.0001f;
|
||
|
_lpFilterDamping = 5.0f / (1.0f + p.lpFilterResonance * p.lpFilterResonance * 20.0f) * (0.01f + _lpFilterCutoff);
|
||
|
if (_lpFilterDamping > 0.8f) _lpFilterDamping = 0.8f;
|
||
|
_lpFilterDamping = 1.0f - _lpFilterDamping;
|
||
|
_lpFilterOn = p.lpFilterCutoff != 1.0f;
|
||
|
|
||
|
_hpFilterPos = 0.0f;
|
||
|
_hpFilterCutoff = p.hpFilterCutoff * p.hpFilterCutoff * 0.1f;
|
||
|
_hpFilterDeltaCutoff = 1.0f + p.hpFilterCutoffSweep * 0.0003f;
|
||
|
|
||
|
_vibratoPhase = 0.0f;
|
||
|
_vibratoSpeed = p.vibratoSpeed * p.vibratoSpeed * 0.01f;
|
||
|
_vibratoAmplitude = p.vibratoDepth * 0.5f;
|
||
|
|
||
|
_envelopeVolume = 0.0f;
|
||
|
_envelopeStage = 0;
|
||
|
_envelopeTime = 0;
|
||
|
_envelopeLength0 = p.attackTime * p.attackTime * 100000.0f;
|
||
|
_envelopeLength1 = p.sustainTime * p.sustainTime * 100000.0f;
|
||
|
_envelopeLength2 = p.decayTime * p.decayTime * 100000.0f + 10f;
|
||
|
_envelopeLength = _envelopeLength0;
|
||
|
_envelopeFullLength = (uint)(_envelopeLength0 + _envelopeLength1 + _envelopeLength2);
|
||
|
|
||
|
_envelopeOverLength0 = 1.0f / _envelopeLength0;
|
||
|
_envelopeOverLength1 = 1.0f / _envelopeLength1;
|
||
|
_envelopeOverLength2 = 1.0f / _envelopeLength2;
|
||
|
|
||
|
_phaser = p.phaserOffset != 0.0f || p.phaserSweep != 0.0f;
|
||
|
|
||
|
_phaserOffset = p.phaserOffset * p.phaserOffset * 1020.0f;
|
||
|
if (p.phaserOffset < 0.0f) _phaserOffset = -_phaserOffset;
|
||
|
_phaserDeltaOffset = p.phaserSweep * p.phaserSweep * p.phaserSweep * 0.2f;
|
||
|
_phaserPos = 0;
|
||
|
|
||
|
if (_phaserBuffer == null) _phaserBuffer = new float[1024];
|
||
|
if (_noiseBuffer == null) _noiseBuffer = new float[32];
|
||
|
if (_pinkNoiseBuffer == null) _pinkNoiseBuffer = new float[32];
|
||
|
if (_pinkNumber == null) _pinkNumber = new PinkNumber();
|
||
|
if (_loResNoiseBuffer == null) _loResNoiseBuffer = new float[32];
|
||
|
|
||
|
uint i;
|
||
|
for (i = 0; i < 1024; i++) _phaserBuffer[i] = 0.0f;
|
||
|
for (i = 0; i < 32; i++) _noiseBuffer[i] = getRandom() * 2.0f - 1.0f;
|
||
|
for (i = 0; i < 32; i++) _pinkNoiseBuffer[i] = _pinkNumber.getNextValue();
|
||
|
for (i = 0; i < 32; i++) _loResNoiseBuffer[i] = ((i % LO_RES_NOISE_PERIOD) == 0) ? getRandom() * 2.0f - 1.0f : _loResNoiseBuffer[i-1];
|
||
|
|
||
|
_repeatTime = 0;
|
||
|
|
||
|
if (p.repeatSpeed == 0.0) {
|
||
|
_repeatLimit = 0;
|
||
|
} else {
|
||
|
_repeatLimit = (int)((1.0-p.repeatSpeed) * (1.0-p.repeatSpeed) * 20000) + 32;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Writes the wave to the supplied buffer array of floats (it'll contain the mono audio)
|
||
|
* @param buffer A float[] to write the wave to
|
||
|
* @param waveData If the wave should be written for the waveData
|
||
|
* @return If the wave is finished
|
||
|
*/
|
||
|
private bool SynthWave(float[] __buffer, int __bufferPos, uint __length) {
|
||
|
_finished = false;
|
||
|
|
||
|
int i, j, n, k;
|
||
|
int l = (int)__length;
|
||
|
float overtoneStrength, tempPhase, sampleTotal;
|
||
|
|
||
|
for (i = 0; i < l; i++) {
|
||
|
if (_finished) return true;
|
||
|
|
||
|
// Repeats every _repeatLimit times, partially resetting the sound parameters
|
||
|
if (_repeatLimit != 0) {
|
||
|
if (++_repeatTime >= _repeatLimit) {
|
||
|
_repeatTime = 0;
|
||
|
Reset(false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_changePeriodTime++;
|
||
|
if (_changePeriodTime >= _changePeriod) {
|
||
|
_changeTime = 0;
|
||
|
_changeTime2 = 0;
|
||
|
_changePeriodTime = 0;
|
||
|
if (_changeReached) {
|
||
|
_period /= _changeAmount;
|
||
|
_changeReached = false;
|
||
|
}
|
||
|
if (_changeReached2) {
|
||
|
_period /= _changeAmount2;
|
||
|
_changeReached2 = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If _changeLimit is reached, shifts the pitch
|
||
|
if (!_changeReached) {
|
||
|
if (++_changeTime >= _changeLimit) {
|
||
|
_changeReached = true;
|
||
|
_period *= _changeAmount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// If _changeLimit is reached, shifts the pitch
|
||
|
if (!_changeReached2) {
|
||
|
if (++_changeTime2 >= _changeLimit2) {
|
||
|
_changeReached2 = true;
|
||
|
_period *= _changeAmount2;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Acccelerate and apply slide
|
||
|
_slide += _deltaSlide;
|
||
|
_period *= _slide;
|
||
|
|
||
|
// Checks for frequency getting too low, and stops the sound if a minFrequency was set
|
||
|
if (_period > _maxPeriod) {
|
||
|
_period = _maxPeriod;
|
||
|
if (_minFrequency > 0) _finished = true;
|
||
|
}
|
||
|
|
||
|
_periodTemp = _period;
|
||
|
|
||
|
// Applies the vibrato effect
|
||
|
if (_vibratoAmplitude > 0) {
|
||
|
_vibratoPhase += _vibratoSpeed;
|
||
|
_periodTemp = _period * (1.0f + Mathf.Sin(_vibratoPhase) * _vibratoAmplitude);
|
||
|
}
|
||
|
|
||
|
_periodTempInt = (int)_periodTemp;
|
||
|
if (_periodTemp < 8) _periodTemp = _periodTempInt = 8;
|
||
|
|
||
|
// Sweeps the square duty
|
||
|
if (_waveType == 0) {
|
||
|
_squareDuty += _dutySweep;
|
||
|
if (_squareDuty < 0.0) {
|
||
|
_squareDuty = 0.0f;
|
||
|
} else if (_squareDuty > 0.5) {
|
||
|
_squareDuty = 0.5f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Moves through the different stages of the volume envelope
|
||
|
if (++_envelopeTime > _envelopeLength) {
|
||
|
_envelopeTime = 0;
|
||
|
|
||
|
switch(++_envelopeStage) {
|
||
|
case 1: _envelopeLength = _envelopeLength1; break;
|
||
|
case 2: _envelopeLength = _envelopeLength2; break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Sets the volume based on the position in the envelope
|
||
|
switch(_envelopeStage) {
|
||
|
case 0: _envelopeVolume = _envelopeTime * _envelopeOverLength0; break;
|
||
|
case 1: _envelopeVolume = 1.0f + (1.0f - _envelopeTime * _envelopeOverLength1) * 2.0f * _sustainPunch; break;
|
||
|
case 2: _envelopeVolume = 1.0f - _envelopeTime * _envelopeOverLength2; break;
|
||
|
case 3: _envelopeVolume = 0.0f; _finished = true; break;
|
||
|
}
|
||
|
|
||
|
// Moves the phaser offset
|
||
|
if (_phaser) {
|
||
|
_phaserOffset += _phaserDeltaOffset;
|
||
|
_phaserInt = (int)_phaserOffset;
|
||
|
if (_phaserInt < 0) {
|
||
|
_phaserInt = -_phaserInt;
|
||
|
} else if (_phaserInt > 1023) {
|
||
|
_phaserInt = 1023;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Moves the high-pass filter cutoff
|
||
|
if (_filters && _hpFilterDeltaCutoff != 0) {
|
||
|
_hpFilterCutoff *= _hpFilterDeltaCutoff;
|
||
|
if (_hpFilterCutoff < 0.00001f) {
|
||
|
_hpFilterCutoff = 0.00001f;
|
||
|
} else if (_hpFilterCutoff > 0.1f) {
|
||
|
_hpFilterCutoff = 0.1f;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_superSample = 0;
|
||
|
for (j = 0; j < 8; j++) {
|
||
|
// Cycles through the period
|
||
|
_phase++;
|
||
|
if (_phase >= _periodTempInt) {
|
||
|
_phase = _phase % _periodTempInt;
|
||
|
|
||
|
// Generates new random noise for this period
|
||
|
if (_waveType == 3) {
|
||
|
// Noise
|
||
|
for (n = 0; n < 32; n++) _noiseBuffer[n] = getRandom() * 2.0f - 1.0f;
|
||
|
} else if (_waveType == 5) {
|
||
|
// Pink noise
|
||
|
for (n = 0; n < 32; n++) _pinkNoiseBuffer[n] = _pinkNumber.getNextValue();
|
||
|
} else if (_waveType == 6) {
|
||
|
// Tan
|
||
|
for (n = 0; n < 32; n++) _loResNoiseBuffer[n] = ((n % LO_RES_NOISE_PERIOD) == 0) ? getRandom() * 2.0f - 1.0f : _loResNoiseBuffer[n-1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_sample = 0;
|
||
|
sampleTotal = 0;
|
||
|
overtoneStrength = 1f;
|
||
|
|
||
|
for (k = 0; k <= _overtones; k++) {
|
||
|
tempPhase = (float)((_phase * (k + 1))) % _periodTemp;
|
||
|
|
||
|
// Gets the sample from the oscillator
|
||
|
switch (_waveType) {
|
||
|
case 0:
|
||
|
// Square
|
||
|
_sample = ((tempPhase / _periodTemp) < _squareDuty) ? 0.5f : -0.5f;
|
||
|
break;
|
||
|
case 1:
|
||
|
// Sawtooth
|
||
|
_sample = 1.0f - (tempPhase / _periodTemp) * 2.0f;
|
||
|
break;
|
||
|
case 2:
|
||
|
// Sine: fast and accurate approx
|
||
|
_pos = tempPhase / _periodTemp;
|
||
|
_pos = _pos > 0.5f ? (_pos - 1.0f) * 6.28318531f : _pos * 6.28318531f;
|
||
|
_sample = _pos < 0 ? 1.27323954f * _pos + 0.405284735f * _pos * _pos : 1.27323954f * _pos - 0.405284735f * _pos * _pos;
|
||
|
_sample = _sample < 0 ? 0.225f * (_sample * -_sample - _sample) + _sample : 0.225f * (_sample * _sample - _sample) + _sample;
|
||
|
break;
|
||
|
case 3:
|
||
|
// Noise
|
||
|
_sample = _noiseBuffer[(uint)(tempPhase * 32f / _periodTempInt) % 32];
|
||
|
break;
|
||
|
case 4:
|
||
|
// Triangle
|
||
|
_sample = Math.Abs(1f - (tempPhase / _periodTemp) * 2f) - 1f;
|
||
|
break;
|
||
|
case 5:
|
||
|
// Pink noise
|
||
|
_sample = _pinkNoiseBuffer[(uint)(tempPhase * 32f / _periodTempInt) % 32];
|
||
|
break;
|
||
|
case 6:
|
||
|
// Tan
|
||
|
_sample = (float)Math.Tan(Math.PI * tempPhase / _periodTemp);
|
||
|
break;
|
||
|
case 7:
|
||
|
// Whistle
|
||
|
// Sine wave code
|
||
|
_pos = tempPhase / _periodTemp;
|
||
|
_pos = _pos > 0.5f ? (_pos - 1.0f) * 6.28318531f : _pos * 6.28318531f;
|
||
|
_sample = _pos < 0 ? 1.27323954f * _pos + 0.405284735f * _pos * _pos : 1.27323954f * _pos - 0.405284735f * _pos * _pos;
|
||
|
_sample = 0.75f * (_sample < 0 ? 0.225f * (_sample * -_sample - _sample) + _sample : 0.225f * (_sample * _sample - _sample) + _sample);
|
||
|
// Then whistle (essentially an overtone with frequencyx20 and amplitude0.25
|
||
|
_pos = ((tempPhase * 20f) % _periodTemp) / _periodTemp;
|
||
|
_pos = _pos > 0.5f ? (_pos - 1.0f) * 6.28318531f : _pos * 6.28318531f;
|
||
|
_sample2 = _pos < 0 ? 1.27323954f * _pos + .405284735f * _pos * _pos : 1.27323954f * _pos - 0.405284735f * _pos * _pos;
|
||
|
_sample += 0.25f * (_sample2 < 0 ? .225f * (_sample2 * -_sample2 - _sample2) + _sample2 : .225f * (_sample2 * _sample2 - _sample2) + _sample2);
|
||
|
break;
|
||
|
case 8:
|
||
|
// Breaker
|
||
|
amp = tempPhase / _periodTemp;
|
||
|
_sample = Math.Abs(1f - amp * amp * 2f) - 1f;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
sampleTotal += overtoneStrength * _sample;
|
||
|
overtoneStrength *= (1f - _overtoneFalloff);
|
||
|
}
|
||
|
|
||
|
_sample = sampleTotal;
|
||
|
|
||
|
// Applies the low and high pass filters
|
||
|
if (_filters) {
|
||
|
_lpFilterOldPos = _lpFilterPos;
|
||
|
_lpFilterCutoff *= _lpFilterDeltaCutoff;
|
||
|
if (_lpFilterCutoff < 0.0) {
|
||
|
_lpFilterCutoff = 0.0f;
|
||
|
} else if (_lpFilterCutoff > 0.1) {
|
||
|
_lpFilterCutoff = 0.1f;
|
||
|
}
|
||
|
|
||
|
if (_lpFilterOn) {
|
||
|
_lpFilterDeltaPos += (_sample - _lpFilterPos) * _lpFilterCutoff;
|
||
|
_lpFilterDeltaPos *= _lpFilterDamping;
|
||
|
} else {
|
||
|
_lpFilterPos = _sample;
|
||
|
_lpFilterDeltaPos = 0.0f;
|
||
|
}
|
||
|
|
||
|
_lpFilterPos += _lpFilterDeltaPos;
|
||
|
|
||
|
_hpFilterPos += _lpFilterPos - _lpFilterOldPos;
|
||
|
_hpFilterPos *= 1.0f - _hpFilterCutoff;
|
||
|
_sample = _hpFilterPos;
|
||
|
}
|
||
|
|
||
|
// Applies the phaser effect
|
||
|
if (_phaser) {
|
||
|
_phaserBuffer[_phaserPos & 1023] = _sample;
|
||
|
_sample += _phaserBuffer[(_phaserPos - _phaserInt + 1024) & 1023];
|
||
|
_phaserPos = (_phaserPos + 1) & 1023;
|
||
|
}
|
||
|
|
||
|
_superSample += _sample;
|
||
|
}
|
||
|
|
||
|
// Averages out the super samples and applies volumes
|
||
|
_superSample = _masterVolume * _envelopeVolume * _superSample * 0.125f;
|
||
|
|
||
|
// Bit crush
|
||
|
_bitcrushPhase += _bitcrushFreq;
|
||
|
if (_bitcrushPhase > 1f) {
|
||
|
_bitcrushPhase = 0;
|
||
|
_bitcrushLast = _superSample;
|
||
|
}
|
||
|
_bitcrushFreq = Mathf.Max(Mathf.Min(_bitcrushFreq + _bitcrushFreqSweep, 1f), 0f);
|
||
|
|
||
|
_superSample = _bitcrushLast;
|
||
|
|
||
|
// Compressor
|
||
|
if (_superSample > 0f) {
|
||
|
_superSample = Mathf.Pow(_superSample, _compressionFactor);
|
||
|
} else {
|
||
|
_superSample = -Mathf.Pow(-_superSample, _compressionFactor);
|
||
|
}
|
||
|
|
||
|
// BFXR leftover:
|
||
|
//if (_muted) {
|
||
|
// _superSample = 0;
|
||
|
//}
|
||
|
|
||
|
// Clipping if too loud
|
||
|
if (_superSample < -1f) {
|
||
|
_superSample = -1f;
|
||
|
} else if (_superSample > 1f) {
|
||
|
_superSample = 1f;
|
||
|
}
|
||
|
|
||
|
// Writes value to list, ignoring left/right sound channels (this is applied when filtering the audio later)
|
||
|
__buffer[i + __bufferPos] = _superSample;
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
private void createGameObject() {
|
||
|
// Create a game object to handle playback
|
||
|
_gameObject = new GameObject("SfxrGameObject-" + (Time.realtimeSinceStartup));
|
||
|
fixGameObjectParent();
|
||
|
|
||
|
// Create actual audio player
|
||
|
_audioPlayer = _gameObject.AddComponent<SfxrAudioPlayer>();
|
||
|
_audioPlayer.SetSfxrSynth(this);
|
||
|
_audioPlayer.SetRunningInEditMode(Application.isEditor && !Application.isPlaying);
|
||
|
}
|
||
|
|
||
|
|
||
|
private void fixGameObjectParent() {
|
||
|
// Sets the parent of the game object to be the wanted object
|
||
|
Transform transformToUse = _parentTransform;
|
||
|
|
||
|
// If no parent assigned, use main camera by default
|
||
|
if (transformToUse == null) transformToUse = Camera.main.transform;
|
||
|
|
||
|
// If has any parent (assigned, or main camera exist) assigns it
|
||
|
if (transformToUse != null) _gameObject.transform.parent = transformToUse;
|
||
|
|
||
|
// Center in parent (or scene if no parent)
|
||
|
_gameObject.transform.localPosition = new Vector3(0, 0, 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a random value: 0 <= n < 1
|
||
|
* This function is needed so we can follow the original code more strictly; Unity's Random.value returns 0 <= n <= 1
|
||
|
*/
|
||
|
private float getRandom() {
|
||
|
// We can't use Unity's Random.value because it cannot be called from a separate thread
|
||
|
// (We get the error "get_value can only be called from the main thread" when this is called to generate the sound data)
|
||
|
return (float)(randomGenerator.NextDouble() % 1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Writes a short (Int16) to a byte array.
|
||
|
* This is an aux function used when creating the WAV data.
|
||
|
*/
|
||
|
private void writeShortToBytes(byte[] __bytes, ref int __position, short __newShort, Endian __endian) {
|
||
|
writeBytes(__bytes, ref __position, new byte[2] { (byte)((__newShort >> 8) & 0xff), (byte)(__newShort & 0xff) }, __endian);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Writes a uint (UInt32) to a byte array.
|
||
|
* This is an aux function used when creating the WAV data.
|
||
|
*/
|
||
|
private void writeUintToBytes(byte[] __bytes, ref int __position, uint __newUint, Endian __endian) {
|
||
|
writeBytes(__bytes, ref __position, new byte[4] { (byte)((__newUint >> 24) & 0xff), (byte)((__newUint >> 16) & 0xff), (byte)((__newUint >> 8) & 0xff), (byte)(__newUint & 0xff) }, __endian);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Writes any number of bytes into a byte array, at a given position.
|
||
|
* This is an aux function used when creating the WAV data.
|
||
|
*/
|
||
|
private void writeBytes(byte[] __bytes, ref int __position, byte[] __newBytes, Endian __endian) {
|
||
|
// Writes __newBytes to __bytes at position __position, increasing the position depending on the length of __newBytes
|
||
|
for (int i = 0; i < __newBytes.Length; i++) {
|
||
|
__bytes[__position] = __newBytes[__endian == Endian.BIG_ENDIAN ? i : __newBytes.Length - i - 1];
|
||
|
__position++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// ================================================================================================================
|
||
|
// AUX CLASSES ----------------------------------------------------------------------------------------------------
|
||
|
|
||
|
/*
|
||
|
Pink Number
|
||
|
-----------
|
||
|
From BFXR
|
||
|
Class taken from http://www.firstpr.com.au/dsp/pink-noise/#Filtering
|
||
|
*/
|
||
|
|
||
|
public class PinkNumber {
|
||
|
// Properties
|
||
|
private int max_key;
|
||
|
private int key;
|
||
|
private uint[] white_values;
|
||
|
private uint range;
|
||
|
private System.Random randomGenerator;
|
||
|
|
||
|
// Temp
|
||
|
private float rangeBy5;
|
||
|
private int last_key;
|
||
|
private uint sum;
|
||
|
private int diff;
|
||
|
private int i;
|
||
|
|
||
|
public PinkNumber() {
|
||
|
max_key = 0x1f; // Five bits set
|
||
|
range = 128;
|
||
|
rangeBy5 = (float)range / 5f;
|
||
|
key = 0;
|
||
|
white_values = new uint[5];
|
||
|
randomGenerator = new System.Random();
|
||
|
for (i = 0; i < 5; i++) white_values[i] = (uint)((randomGenerator.NextDouble() % 1) * rangeBy5);
|
||
|
}
|
||
|
|
||
|
public float getNextValue() {
|
||
|
// Returns a number between -1 and 1
|
||
|
last_key = key;
|
||
|
sum = 0;
|
||
|
|
||
|
key++;
|
||
|
if (key > max_key) key = 0;
|
||
|
|
||
|
// Exclusive-Or previous value with current value. This gives
|
||
|
// a list of bits that have changed.
|
||
|
diff = last_key ^ key;
|
||
|
sum = 0;
|
||
|
for (i = 0; i < 5; i++) {
|
||
|
// If bit changed get new random number for corresponding
|
||
|
// white_value
|
||
|
if ((diff & (1 << i)) > 0) white_values[i] = (uint)((randomGenerator.NextDouble() % 1) * rangeBy5);;
|
||
|
sum += white_values[i];
|
||
|
}
|
||
|
return (float)sum / 64f - 1f;
|
||
|
}
|
||
|
};
|