2016-04-26 8 views
1

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

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

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

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

Благодарим за советы и решение. Надеюсь, Марк Хит читает это :).

С наилучшими пожеланиями, Alex

Формат Wavefile:

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

Если предположить, что код заботится о заголовках (пропуск 2-ой и изменении 1-й друга задайте количество образцов для образцов1 + samples2 - crossfadeLenght), чтобы создать перекрестное затухание, которое вам нужно: принять решение о количестве образцов, которые нужно использовать, а затем, хорошо исчезать с 100% + 0% до 0% + 100% для каждого. . – TaW

+0

Благодарим за помощь. Я объясню это с вашей маленькой концепцией. Кроме того, я нашел это: http://www.codeproject.com/Articles/35725/C-WAV-file-class-audio-mixing-and-some-light-audio Возможно, это тоже помогает мне. – Alex

+0

Да, это звучит полезно и, вероятно, стоит изучать. В последний раз, когда я играл с волновыми файлами, я обнаружил, что очень просто ввести глупые артефакты, например. не сглаживая ошибки округления. Удачи! – TaW

ответ

0

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

Вот пример я написал, чтобы сделать это. Он принимает список шаблонов имен входных файлов (предполагает текущий каталог) и имя выходного файла. Он сшивает файлы вместе, исчезает ~ 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 секунд длиной 440Hz файл синусоидальной волны, частота дискретизации = 96K, стерео, следующим образом:

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){} 
     } 
    } 
} 
+0

Можете ли вы дать более подробную информацию об ошибке, найденной в 'FadeInFadeOutSampleProvider'? – Corey

+0

Прежде всего, спасибо за ваш код. На первый взгляд это выглядит как гениальная работа;). Я применим это к своему алгоритму и попробую его. Кроме того, если вы могли бы предоставить некоторую информацию об этой ошибке, которую вы нашли в FadeInFadeOutSampleProvider, как запросил Боб C, это было бы очень мило с вашей стороны. – Alex

+0

Ok Bob C, я применил ваш код, немного изменил его, чтобы он соответствовал моим потребностям, и он отлично работает. Единственное, что мне теперь нужно изменить, это длительность fadeIn и -out. Мне в основном нужен кроссфейд, который слышит не слышит. Очень небольшое количество «образцов». – Alex