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