Как зациклить аудиофайл с одинарной высотой звука без перерыва или щелчка

У меня есть несколько (моно) записей флейты разных высот, чтобы сделать синтезатор флейты. Каждая из записей имеет длительность 1 секунда, в то время как синтезатор должен воспроизводить высоту звука любой заданной длины. Так что в основном мне нужно зациклить запись, чтобы воспроизвести высоту звука в течение более длительного периода. Я пытался реализовать это с помощью NAudio, но при перемотке всегда слышен щелчок.

Я всегда сохраняю образцы в файл WAV, поэтому это не должно быть проблемой задержки. Я также обрезал аудио, чтобы зацикленные сэмплы не имели щелчка. При выполнении обрезки я также старался, чтобы итоговое число сэмплов было кратным числу сэмплов за цикл (рассчитано с использованием стандартной частоты шага). Казалось, немного помогло, но щелчок все еще существует.

Я также пробовал исчезать. Когда я просто потушил последние сэмплы, а затем постепенно начал сэмплировать, звук щелчка исчез, однако во время перехода вы могли услышать разрыв (короткое молчание). Затем я позволил перекрытию постепенного исчезновения и постепенного появления, и щелчок вернулся снова.

Я искал это некоторое время, и в основном люди говорят о кроссфейдинге. Есть ли что-то не так с моим подходом перекрытия постепенного исчезновения и постепенного появления?

Вот код:

internal class NeverEndingPitchSampleProvider : ISampleProvider
{
    // The amount of samples to discard at beginning and end
    private const double TrimSamples = 0.1;

    // The amount of samples to fade out and fade in
    private const double FadeSamples = 0.05;

    private List<float> samples;

    private int currentPosition;

    public WaveFormat WaveFormat { get; }

    public NeverEndingPitchSampleProvider(ISampleProvider sourceProvider, double frequency)
    {
        this.WaveFormat = sourceProvider.WaveFormat;
        this.samples = SampleProviderHelper.GetAllSamples(sourceProvider);
        this.Trim(frequency);
        this.CrossFade();
        this.currentPosition = 0;
    }

    public int Read(float[] buffer, int offset, int count)
    {
        int samplesToRead = count;
        while (samplesToRead > 0)
        {
            int newPosition = this.currentPosition + samplesToRead;
            if (newPosition > this.samples.Count)
            {
                newPosition = this.samples.Count;
            }

            for (int i = this.currentPosition; i < newPosition; i++)
            {
                buffer[offset++] = this.samples[i];
            }

            samplesToRead -= newPosition - this.currentPosition;
            this.currentPosition = newPosition >= this.samples.Count ? 0 : newPosition;
        }

        return count;
    }

    private void Trim(double frequency)
    {
        // Basic trimming: discard some of the starting and ending samples
        int samplesToTrim = (int)(NeverEndingPitchSampleProvider.TrimSamples * this.samples.Count);
        int samplesToKeep = this.samples.Count - samplesToTrim - samplesToTrim;

        // Align with the pitch frequency
        int samplesPerCycle = (int)(this.WaveFormat.SampleRate / frequency);
        samplesToKeep -= samplesToKeep % samplesPerCycle;

        this.samples = this.samples.GetRange(samplesToTrim, samplesToKeep);
    }

    private void CrossFade()
    {
        int samplesToFade = (int)(this.samples.Count * NeverEndingPitchSampleProvider.FadeSamples);
        int fadeOutStart = this.samples.Count - samplesToFade;

        // Fade-in fade-out separately
        this.FadeIn(0, samplesToFade);
        this.FadeOut(fadeOutStart, samplesToFade);

        // Overlap at the end
        for (int i = 0; i < samplesToFade; i++)
        {
            this.samples[fadeOutStart + i] += this.samples[i];
        }

        // Remove the beginning samples
        this.samples = this.samples.GetRange(samplesToFade, fadeOutStart);
    }

    private void FadeIn(int begin, int count)
    {
        for (int i = 0; i < count; i++)
        {
            this.samples[begin + i] *= (float)(i + 1) / count;
        }
    }

    private void FadeOut(int begin, int count)
    {
        for (int i = 0; i < count; i++)
        {
            this.samples[begin + i] *= (float)(count - i) / count;
        }
    }
}

0 ответов

Другие вопросы по тегам