ar1st0crat / NWaves

.NET DSP library with a lot of audio processing functions
MIT License
453 stars 71 forks source link

How to avoid clipping during Concatenate ? #52

Closed matteofabbri closed 2 years ago

matteofabbri commented 2 years ago

Hi, thanks for this wonderful code ! I'm trying to make some music with it, but when i concatenate two signal, for example two sins, i got a terrible clipping effect between every sound. What can i do to avoid that ?

ar1st0crat commented 2 years ago

Hi, thanks )

It's a common problem. Clicks (high-frequency noise) are caused by abrupt change of the amplitude of two adjacent samples. There are 2 simplest approachs to get rid of this effect: 1) simple fast fade in/out to zero at the edges of concatenated signals; 2) crossfading.

Code:


// s1 and s2 are discrete signals (e.g. sinusoids)

// approach 1:
s1.FadeInOut(0.002, 0.005);      // e.g., fade-in 2ms; fade-out 5ms
s2.FadeInOut(0.005, 0.002);      // e.g., fade-in 5ms; fade-out 2ms

var res1 = s1.Concatenate(s2)
             .Concatenate(s1);

// approach 2:
var res2 = s1.CrossFade(s2, 0.005)     // cross-fade 5ms
             .CrossFade(s1, 0.005);

Extension methods (I will refactor them and add them to next version of the lib, since it's quite useful functionality):

    public static class SignalFadeExtensions
    {
        /// <summary>
        /// Simple fade-in/out of the signal.
        /// </summary>
        /// <param name="signal"></param>
        /// <param name="fadeInDuration"></param>
        /// <param name="fadeOutDuration"></param>
        public static void FadeInOut(this DiscreteSignal signal, double fadeInDuration, double fadeOutDuration)
        {
            var fadeInSampleCount = Math.Min(signal.Length, (int)(signal.SamplingRate * fadeInDuration));

            for (var i = 0; i < fadeInSampleCount; i++)
            {
                signal[i] *= (float)i / fadeInSampleCount;
            }

            var fadeOutSampleCount = Math.Min(signal.Length, (int)(signal.SamplingRate * fadeOutDuration));

            for (int i = signal.Length - fadeOutSampleCount, idx = fadeOutSampleCount - 1; i < signal.Length; i++, idx--)
            {
                signal[i] *= (float)idx / fadeOutSampleCount;
            }
        }

        /// <summary>
        /// Crossfade between <paramref name="signal1"/> and <paramref name="signal2"/>.
        /// </summary>
        /// <param name="signal1"></param>
        /// <param name="signal2"></param>
        /// <param name="duration"></param>
        public static DiscreteSignal CrossFade(this DiscreteSignal signal1, DiscreteSignal signal2, double duration)
        {
            var crossfadeSampleCount = (int)(signal1.SamplingRate * duration); // Math.Min(crossfaded.Length, (int)(crossfaded.SamplingRate * duration));

            var crossfaded = new DiscreteSignal(signal1.SamplingRate, signal1.Length + signal2.Length - crossfadeSampleCount);

            Array.Copy(signal1.Samples, crossfaded.Samples, signal1.Length - crossfadeSampleCount);
            Array.Copy(signal2.Samples, crossfadeSampleCount, crossfaded.Samples, signal1.Length, signal2.Length - crossfadeSampleCount);

            var startPos = signal1.Length - crossfadeSampleCount;

            for (int i = 0, idx = startPos; i < crossfadeSampleCount; i++, idx++)
            {
                var frac = (float)i / crossfadeSampleCount;

                crossfaded[idx] = (1 - frac) * signal1[idx] + frac * signal2[i];
            }

            return crossfaded;
        }
    }
ar1st0crat commented 2 years ago

...I closed this issue accidentally ). Feel free to continue this thread if there are still questions.