Exemple #1
0
        /// <summary>
        /// Renders and plays the supplied SfxParams
        /// </summary>
        /// <param name="param">The sound effect parameters to use</param>
        /// <param name="asPreview">If set, the effect will always play on the first channel (this stops any previous preview that is still playing)</param>
        public static void Play(SfxrParams param, bool asPreview = false)
        {
            PurgeCache();
            UpdateInstance();

            var entry = CacheGet(param);

            if (!entry.firstPlay && !asPreview && entry.timeSinceLastTrigger < instance.minRetriggerTime)
            {
                return;
            }

            entry.UpdateTime();
            PlayClip(entry.clip, asPreview);
        }
Exemple #2
0
        float amp;                                                              // Used in other calculations


        /// <summary>
        /// Synchronously generates a Unity AudioClip
        /// </summary>
        public AudioClip GenerateClip(SfxrParams param)
        {
            // var s = new Stopwatch();
            // s.Start();
            this.param = param;
            Reset(true);

            var clip = AudioClip.Create("usfxr", (int)envelopeFullLength, 1, 44100, false);

            var sampleData = new float[envelopeFullLength];

            SynthWave(sampleData, 0, envelopeFullLength);
            clip.SetData(sampleData, 0);

            // Debug.Log($"Generated sfx in {s.Elapsed.TotalMilliseconds:F1} ms.");
            return(clip);
        }
Exemple #3
0
        /// <summary>
        /// Iterates over all the fields in a SfxrParams instance and applies these to a serialized property
        /// This is needed to make the code that directly modifies SfxrParams play nice with the Unity undo system
        /// </summary>
        static void SetParam(SerializedProperty property, SfxrParams param)
        {
            // iterate over all the fields
            foreach (var field in paramFields)
            {
                // find the corresponding property
                var prop = property.FindPropertyRelative(field.Name);
                if (paramData[field.Name].locked)
                {
                    continue;
                }

                // apply the value from the SfxrParams struct to the SerializedProperty
                // only enums and floats for now, add more as needed
                if (prop.type == "Enum")
                {
                    prop.enumValueIndex = Convert.ToInt32(field.GetValue(param));
                }
                else if (prop.type == "float")
                {
                    prop.floatValue = (float)field.GetValue(param);
                }
            }
        }
Exemple #4
0
        /// <summary>
        /// Drops the oldest N sfx from the cache
        /// </summary>
        static void PurgeCache()
        {
            if (cache.Count < MaxCacheSize)
            {
                return;
            }

            var now    = GetTimestamp();
            var maxAge = float.MinValue;
            var oldest = new SfxrParams();

            foreach (var entry in cache)
            {
                var age = now - entry.Value.triggerTime;
                if (age < maxAge)
                {
                    continue;
                }
                maxAge = age;
                oldest = entry.Key;
            }

            cache.Remove(oldest);
        }
Exemple #5
0
        /// <summary>
        /// Retrieves an AudioClip along with some other data if it's cached, otherwise it is generated
        /// </summary>
        static ClipTimeTuple CacheGet(SfxrParams param)
        {
            // make sure we have a renderer
            if (sfxrRenderer == null)
            {
                sfxrRenderer = new SfxrRenderer();
            }

            if (cache.TryGetValue(param, out var entry))
            {
                // sometimes it seems the audio clip will get lost despite the cache having a reference to it, so we may need to regenerate it
                if (entry.clip == null)
                {
                    entry.clip = sfxrRenderer.GenerateClip(param);
                }
                entry.firstPlay = false;
                return(entry);
            }

            entry = new ClipTimeTuple(sfxrRenderer.GenerateClip(param));
            cache.Add(param, entry);

            return(entry);
        }
Exemple #6
0
        /// <summary>
        /// Writes the wave to the supplied buffer array of floats (it'll contain the mono audio)
        /// </summary>
        bool SynthWave(float[] buffer, int bufferPos, uint length)
        {
            var finished = false;

            for (var i = 0; i < (int)length; 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;
                    }
                }

                // Accelerate 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;
                int j;
                for (j = 0; j < 8; j++)
                {
                    // Cycles through the period
                    phase++;
                    if (phase >= periodTempInt)
                    {
                        phase = phase % periodTempInt;

                        // Generates new random noise for this period
                        int n;
                        if (waveType == WaveType.Noise)
                        {
                            // Noise
                            for (n = 0; n < 32; n++)
                            {
                                noiseBuffer[n] = SfxrParams.GetRandom() * 2.0f - 1.0f;
                            }
                        }
                        else if (waveType == WaveType.PinkNoise)
                        {
                            // Pink noise
                            for (n = 0; n < 32; n++)
                            {
                                pinkNoiseBuffer[n] = pinkNumber.getNextValue();
                            }
                        }
                        else if (waveType == WaveType.Tan)
                        {
                            // Tan
                            for (n = 0; n < 32; n++)
                            {
                                loResNoiseBuffer[n] = ((n % LoResNoisePeriod) == 0)
                                                                        ? SfxrParams.GetRandom() * 2.0f - 1.0f
                                                                        : loResNoiseBuffer[n - 1];
                            }
                        }
                    }

                    sample = 0;
                    float sampleTotal      = 0;
                    var   overtoneStrength = 1f;

                    int k;
                    for (k = 0; k <= overtones; k++)
                    {
                        var tempPhase = phase * (k + 1) % periodTemp;

                        // Gets the sample from the oscillator
                        switch (waveType)
                        {
                        case WaveType.Square:
                            sample = ((tempPhase / periodTemp) < squareDuty) ? 0.5f : -0.5f;
                            break;

                        case WaveType.Sawtooth:
                            sample = 1.0f - (tempPhase / periodTemp) * 2.0f;
                            break;

                        case WaveType.Sine:
                            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 WaveType.Noise:
                            // Noise
                            sample = noiseBuffer[(uint)(tempPhase * 32f / periodTempInt) % 32];
                            break;

                        case WaveType.Triangle:
                            sample = Math.Abs(1f - (tempPhase / periodTemp) * 2f) - 1f;
                            break;

                        case WaveType.PinkNoise:
                            sample = pinkNoiseBuffer[(uint)(tempPhase * 32f / periodTempInt) % 32];
                            break;

                        case WaveType.Tan:
                            // Tan
                            sample = (float)Math.Tan(Math.PI * tempPhase / periodTemp);
                            break;

                        case WaveType.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 WaveType.Breaker:
                            // 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);
                }

                // 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);
        }
Exemple #7
0
        /// <summary>
        /// Resets the running 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
        /// </summary>
        void Reset(bool totalReset)
        {
            // Shorter reference
            var p = param;

            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)
            {
                return;
            }

            masterVolume = p.masterVolume * p.masterVolume;

            waveType = p.waveType;

            if (p.sustainTime < 0.01)
            {
                p.sustainTime = 0.01f;
            }

            var totalTime = p.attackTime + p.sustainTime + p.decayTime;

            if (totalTime < 0.18f)
            {
                var 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] = SfxrParams.GetRandom() * 2.0f - 1.0f;
            }
            for (i = 0; i < 32; i++)
            {
                pinkNoiseBuffer[i] = pinkNumber.getNextValue();
            }
            for (i = 0; i < 32; i++)
            {
                loResNoiseBuffer[i] = i % LoResNoisePeriod == 0 ? SfxrParams.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;
            }
        }
Exemple #8
0
 public static AudioClip GetClip(SfxrParams param)
 {
     return(CacheGet(param).clip);
 }