Щелчок при объединении / объединении двух или более файлов WAV

Я работаю над музыкальным проектом, где мне нужно объединить несколько файлов WAV. Мой код работает нормально, но вы четко слышите щелчок между двумя присоединенными файлами WAV. Это огромная проблема.

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

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

Я предоставляю свой код C# ниже, как я объединяю файлы WAV. Это работает для файлов WAV, которые находятся в том же "формате". Я нашел этот кусок кода на ( Как программно объединить 2 или более файлов.WAV вместе?). Далее я нашел эту возможность FadeIn/FadeOut, но я не знаю, как применить это к коду. Кроме того, я не знаю, предотвратит ли это щелчок.

Спасибо за совет и решение. Надеюсь, Марк Хит читает это:).

С наилучшими пожеланиями, Алекс

Формат волнового файла:

AverageBytesPerSecond: 264600 | BitsPerSample: 24 | BlockAlign: 6 | Каналы: 2 | Кодировка: PCM | Дополнительный размер: 0 | SampleRate: 44100 |

    public static void Concatenate(string outputFile, IEnumerable<string> sourceFiles)
{
    byte[] buffer = new byte[6]; //1024 was the original. but my wave file format has the blockAlign 6. So 1024 was not working for me. 6 does.
    WaveFileWriter waveFileWriter = null;

    try
    {
        foreach (string sourceFile in sourceFiles)
        {
            using (WaveFileReader reader = new WaveFileReader(sourceFile))
            {
                if (waveFileWriter == null)
                {
                    // first time in create new Writer
                    waveFileWriter = new WaveFileWriter(outputFile, reader.WaveFormat);
                }
                else
                {
                    if (!reader.WaveFormat.Equals(waveFileWriter.WaveFormat))
                    {
                        throw new InvalidOperationException("Can't concatenate WAV Files that don't share the same format");
                    }
                }

                int read;
                while ((read = reader.Read(buffer, 0, buffer.Length)) > 0)
                {
                    waveFileWriter.WriteData(buffer, 0, read);
                }
            }
        }
    }
    finally
    {
        if (waveFileWriter != null)
        {
            waveFileWriter.Dispose();
        }
    }
}

1 ответ

Это звучало как весело:)

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

Я использовал ReadNextSampleFrame методы в WaveFileReader для чтения данных в виде выборок с плавающей точкой IEEE (по одному с плавающей точкой на канал). Это значительно упрощает одностороннюю регулировку громкости, не беспокоясь о реальном представлении PCM. На выходе он использует WriteSamples на писателя написать настроенные образцы.

Моя первая попытка использовать NAudio FadeInFadeOutSampleProvider, Но я обнаружил странную ошибку, когда у вас было более одного аудиоканала.

Таким образом, код вручную применяет объем к каждому прочитанному образцу, увеличивая объем с 0,0 до 1,0 в начале каждого файла (кроме первого). Затем он копирует "середину" файла напрямую. Затем примерно за 1 секунду до конца файла (на самом деле, (WaveFormat.SampleRate * WaveFormat.Channels) сэмплов до конца файла), он понижает громкость с 1.0f до 0.0f.

Я протестировал его с помощью sox для генерации 5-секундного файла синусоидальной волны 440 Гц, частота дискретизации = 96 К, стерео, следующим образом:

sox -n -c 2 -r 96000 -b 24 sine.wav synth 5 sine 440

Тест назывался следующим образом:

FadeWeaver.FadeWeave("weaved.wav", "sine.wav", "sine.wav", "sine.wav");

И вот код:

public class FadeWeaver
{
    static
    public
    void
    FadeWeave( string _outfilename,
               params string [] _inpatterns )
    {
        WaveFileWriter output = null;
        WaveFormat waveformat = null;
        float [] sample = null;

        float volume = 1.0f;
        float volumemod = 0.0f;

        // Add .wav extension to the output if not specified.
        string extension = Path.GetExtension(_outfilename);
        if( string.Compare(extension, ".wav", true) != 0 ) _outfilename += ".wav";

        // Assume we're using the current directory.  Let's get the
        // list of filenames.
        List<string> filenames = new List<string>();
        foreach( string pattern in _inpatterns )
        {
            filenames.AddRange(Directory.GetFiles(Directory.GetCurrentDirectory(), pattern));
        }

        try
        {
            // Alrighty.  Let's march over them.  We'll index them (rather than
            // foreach'ing) so that we can monitor first/last file.
            for( int index = 0; index < filenames.Count; ++index )
            {
                // Grab the file and use an 'audiofilereader' to load it.
                string filename = filenames[index];
                using( WaveFileReader reader = new WaveFileReader(filename) )
                {
                    // Get our first/last flags.
                    bool firstfile = (index == 0 );
                    bool lastfile = (index == filenames.Count - 1);

                    // If it's the first...
                    if( firstfile )
                    {
                        // Initialize the writer.
                        waveformat = reader.WaveFormat;
                        output = new WaveFileWriter(_outfilename, waveformat);
                    }
                    else
                    {
                        // All files must have a matching format.
                        if( !reader.WaveFormat.Equals(waveformat) )
                        {
                            throw new InvalidOperationException("Different formats");
                        }
                    }


                    long fadeinsamples = 0;
                    if( !firstfile )
                    {
                        // Assume 1 second of fade in, but set it to total size
                        // if the file is less than one second.
                        fadeinsamples = waveformat.SampleRate;
                        if( fadeinsamples > reader.SampleCount ) fadeinsamples = reader.SampleCount;

                    }

                    // Initialize volume and read from the start of the file to
                    // the 'fadeinsamples' count (which may be 0, if it's the first
                    // file).
                    volume = 0.0f;
                    volumemod = 1.0f / (float)fadeinsamples;
                    int sampleix = 0;
                    while( sampleix < (long)fadeinsamples )
                    {
                        sample = reader.ReadNextSampleFrame();
                        for( int floatix = 0; floatix < waveformat.Channels; ++floatix )
                        {
                            sample[floatix] = sample[floatix] * volume;
                        }

                        // Add modifier to volume.  We'll make sure it isn't over
                        // 1.0!
                        if( (volume = (volume + volumemod)) > 1.0f ) volume = 1.0f;

                        // Write them to the output and bump the index.
                        output.WriteSamples(sample, 0, sample.Length);
                        ++sampleix;
                    }

                    // Now for the time between fade-in and fade-out.
                    // Determine when to start.
                    long fadeoutstartsample = reader.SampleCount;
                    //if( !lastfile )
                    {
                        // We fade out every file except the last.  Move the
                        // sample counter back by one second.
                        fadeoutstartsample -= waveformat.SampleRate;
                        if( fadeoutstartsample < sampleix ) 
                        {
                            // We've actually crossed over into our fade-in
                            // timeframe.  We'll have to adjust the actual
                            // fade-out time accordingly.
                            fadeoutstartsample = reader.SampleCount - sampleix;
                        }
                    }

                    // Ok, now copy everything between fade-in and fade-out.
                    // We don't mess with the volume here.
                    while( sampleix < (int)fadeoutstartsample )
                    {
                        sample = reader.ReadNextSampleFrame();
                        output.WriteSamples(sample, 0, sample.Length);
                        ++sampleix;
                    }

                    // Fade out is next.  Initialize the volume.  Note that
                    // we use a bit-shorter of a time frame just to make sure
                    // we hit 0.0f as our ending volume.
                    long samplesleft = reader.SampleCount - fadeoutstartsample;
                    volume = 1.0f;
                    volumemod = 1.0f / ((float)samplesleft * 0.95f);

                    // And loop over the reamaining samples
                    while( sampleix < (int)reader.SampleCount )
                    {
                        // Grab a sample (one float per channel) and adjust by
                        // volume.
                        sample = reader.ReadNextSampleFrame();
                        for( int floatix = 0; floatix < waveformat.Channels; ++floatix )
                        {
                            sample[floatix] = sample[floatix] * volume;
                        }

                        // Subtract modifier from volume.  We'll make sure it doesn't
                        // accidentally go below 0.
                        if( (volume = (volume - volumemod)) < 0.0f ) volume = 0.0f;

                        // Write them to the output and bump the index.
                        output.WriteSamples(sample, 0, sample.Length);
                        ++sampleix;
                    }
                }
            }
        }
        catch( Exception _ex )
        {
            Console.WriteLine("Exception: {0}", _ex.Message);
        }
        finally
        {
            if( output != null ) try{ output.Dispose(); } catch(Exception){}
        }
    }
}
Другие вопросы по тегам