2013-10-01 37 views
6

Obecnie próbuję zrobić Pitch Shifting pliku fali za pomocą tego algorytmuC# Pitch przesunięcie fali plików

https://sites.google.com/site/mikescoderama/pitch-shifting

Oto mój kod, który wykorzystuje powyższe realizacji, ale bez powodzenia. Wydrukowany plik wave wydaje się być uszkodzony lub niepoprawny.

Kod jest dość prosty, z wyjątkiem algorytmu zmianę wysokości tonu :)

  1. ona załadowania pliku fali, odczytuje plik danych fali i umieścić ją w bajtów [] tablicy.
  2. Następnie "normalizuje" dane bajtów w formacie -1.0f do 1.0f (jako żądany przez twórcę algorytmu zmiany wysokości).
  3. Stosuje algorytm zmiany wysokości tonu, a następnie przekształca z powrotem znormalizowane dane na tablicę bytes [].
  4. Wreszcie zapisuje plik wave z tym samym nagłówkiem pliku oryginalnej fali i przesuniętymi tonami danych.

Czy czegoś brakuje?

 static void Main(string[] args) 
    { 
     // Read the wave file data bytes 

     byte[] waveheader = null; 
     byte[] wavedata = null; 
     using (BinaryReader reader = new BinaryReader(File.OpenRead("sound.wav"))) 
     { 
      // Read first 44 bytes (header); 
      waveheader= reader.ReadBytes(44); 

      // Read data 
      wavedata = reader.ReadBytes((int)reader.BaseStream.Length - 44); 
     } 

     short nChannels = BitConverter.ToInt16(waveheader, 22); 
     int sampleRate = BitConverter.ToInt32(waveheader, 24); 
     short bitRate = BitConverter.ToInt16(waveheader, 34); 

     // Normalized data store. Store values in the format -1.0 to 1.0 
     float[] in_data = new float[wavedata.Length/2]; 

     // Normalize wave data into -1.0 to 1.0 values 
     using(BinaryReader reader = new BinaryReader(new MemoryStream(wavedata))) 
     { 
      for (int i = 0; i < in_data.Length; i++) 
      { 
       if(bitRate == 16) 
        in_data[i] = reader.ReadInt16()/32768f; 

       if (bitRate == 8)     
        in_data[i] = (reader.ReadByte() - 128)/128f; 
      } 
     } 

     //PitchShifter.PitchShift(1f, in_data.Length, (long)1024, (long)32, sampleRate, in_data); 

     // Backup wave data 
     byte[] copydata = new byte[wavedata.Length]; 
     Array.Copy(wavedata, copydata, wavedata.Length); 

     // Revert data to byte format 
     Array.Clear(wavedata, 0, wavedata.Length); 
     using (BinaryWriter writer = new BinaryWriter(new MemoryStream(wavedata))) 
     { 
      for (int i = 0; i < in_data.Length; i++) 
      { 
       if(bitRate == 16) 
        writer.Write((short)(in_data[i] * 32768f)); 

       if (bitRate == 8) 
        writer.Write((byte)((in_data[i] * 128f) + 128)); 
      } 
     } 

     // Compare new wavedata with copydata 
     if (wavedata.SequenceEqual(copydata)) 
     { 
      Console.WriteLine("Data has no changes"); 
     } 
     else 
     { 
      Console.WriteLine("Data has changed!"); 
     } 

     // Save modified wavedata 

     string targetFilePath = "sound_low.wav"; 
     if (File.Exists(targetFilePath)) 
      File.Delete(targetFilePath); 

     using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(targetFilePath))) 
     { 
      writer.Write(waveheader); 
      writer.Write(wavedata); 
     } 

     Console.ReadLine(); 
    } 
+0

Czy na pewno nagłówek pliku audio ma rozmiar 44 bajtów? Według tej strony http://www.sonicspot.com/guide/wavefiles.html zależy to od wielu rzeczy i musi być poprawnie przeanalizowany. – Neil

+0

Masz rację! Zamierzam automatycznie odpowiedzieć na moje pytanie, aby opublikować prawidłowe użycie. – John

Odpowiedz

4

Algorytm tutaj działa dobrze

https://sites.google.com/site/mikescoderama/pitch-shifting

Mój błąd był na jak ja czytając nagłówek wave i wave danych. Zamieszczam tutaj w pełni działający kod:

OSTRZEŻENIE: ten kod działa tylko dla fal 16-bitowych PCM (stereo/mono). Można go łatwo dostosować do pracy z 8-bitowym PCM.

static void Main(string[] args) 
    { 
     // Read header, data and channels as separated data 

     // Normalized data stores. Store values in the format -1.0 to 1.0 
     byte[] waveheader = null; 
     byte[] wavedata = null; 

     int sampleRate = 0; 

     float[] in_data_l = null; 
     float[] in_data_r = null; 

     GetWaveData("sound.wav", out waveheader, out wavedata, out sampleRate, out in_data_l, out in_data_r); 

     // 
     // Apply Pitch Shifting 
     // 

     if(in_data_l != null) 
      PitchShifter.PitchShift(2f, in_data_l.Length, (long)1024, (long)10, sampleRate, in_data_l); 

     if(in_data_r != null) 
      PitchShifter.PitchShift(2f, in_data_r.Length, (long)1024, (long)10, sampleRate, in_data_r); 

     // 
     // Time to save the processed data 
     // 

     // Backup wave data 
     byte[] copydata = new byte[wavedata.Length]; 
     Array.Copy(wavedata, copydata, wavedata.Length); 

     GetWaveData(in_data_l, in_data_r, ref wavedata); 

     // 
     // Check if data actually changed 
     // 

     bool noChanges = true; 
     for (int i = 0; i < wavedata.Length; i++) 
     { 
      if (wavedata[i] != copydata[i]) 
      { 
       noChanges = false; 
       Console.WriteLine("Data has changed!"); 
       break; 
      } 
     } 

     if(noChanges) 
      Console.WriteLine("Data has no changes"); 

     // Save modified wavedata 

     string targetFilePath = "sound_low.wav"; 
     if (File.Exists(targetFilePath)) 
      File.Delete(targetFilePath); 

     using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(targetFilePath))) 
     { 
      writer.Write(waveheader); 
      writer.Write(wavedata); 
     } 

     Console.ReadLine(); 
    } 

    // Returns left and right float arrays. 'right' will be null if sound is mono. 
    public static void GetWaveData(string filename, out byte[] header, out byte[] data, out int sampleRate, out float[] left, out float[] right) 
    { 
     byte[] wav = File.ReadAllBytes(filename); 

     // Determine if mono or stereo 
     int channels = wav[22];  // Forget byte 23 as 99.999% of WAVs are 1 or 2 channels 

     // Get sample rate 
     sampleRate = BitConverter.ToInt32(wav, 24); 

     int pos = 12; 

     // Keep iterating until we find the data chunk (i.e. 64 61 74 61 ...... (i.e. 100 97 116 97 in decimal)) 
     while(!(wav[pos]==100 && wav[pos+1]==97 && wav[pos+2]==116 && wav[pos+3]==97)) { 
      pos += 4; 
      int chunkSize = wav[pos] + wav[pos + 1] * 256 + wav[pos + 2] * 65536 + wav[pos + 3] * 16777216; 
      pos += 4 + chunkSize; 
     } 

     pos += 4; 

     int subchunk2Size = BitConverter.ToInt32(wav, pos); 
     pos += 4; 

     // Pos is now positioned to start of actual sound data. 
     int samples = subchunk2Size/2;  // 2 bytes per sample (16 bit sound mono) 
     if (channels == 2) 
      samples /= 2;  // 4 bytes per sample (16 bit stereo) 

     // Allocate memory (right will be null if only mono sound) 
     left = new float[samples]; 

     if (channels == 2) 
      right = new float[samples]; 
     else 
      right = null; 

     header = new byte[pos]; 
     Array.Copy(wav, header, pos); 

     data = new byte[subchunk2Size]; 
     Array.Copy(wav, pos, data, 0, subchunk2Size); 

     // Write to float array/s: 
     int i=0;    
     while (pos < subchunk2Size) 
     { 

      left[i] = BytesToNormalized_16(wav[pos], wav[pos + 1]); 
      pos += 2; 
      if (channels == 2) 
      { 
       right[i] = BytesToNormalized_16(wav[pos], wav[pos + 1]); 
       pos += 2; 
      } 
      i++; 
     } 
    } 

    // Return byte data from left and right float data. Ignore right when sound is mono 
    public static void GetWaveData(float[] left, float[] right, ref byte[] data) 
    { 
     // Calculate k 
     // This value will be used to convert float to Int16 
     // We are not using Int16.Max to avoid peaks due to overflow conversions    
     float k = (float)Int16.MaxValue/left.Select(x => Math.Abs(x)).Max();   

     // Revert data to byte format 
     Array.Clear(data, 0, data.Length); 
     int dataLenght = left.Length; 
     int byteId = -1; 
     using (BinaryWriter writer = new BinaryWriter(new MemoryStream(data))) 
     { 
      for (int i = 0; i < dataLenght; i++) 
      { 
       byte byte1 = 0; 
       byte byte2 = 0; 

       byteId++; 
       NormalizedToBytes_16(left[i], k, out byte1, out byte2); 
       writer.Write(byte1); 
       writer.Write(byte2); 

       if (right != null) 
       { 
        byteId++; 
        NormalizedToBytes_16(right[i], k, out byte1, out byte2); 
        writer.Write(byte1); 
        writer.Write(byte2);       
       } 
      } 
     }   
    } 

    // Convert two bytes to one double in the range -1 to 1 
    static float BytesToNormalized_16(byte firstByte, byte secondByte) 
    { 
     // convert two bytes to one short (little endian) 
     short s = (short)((secondByte << 8) | firstByte); 
     // convert to range from -1 to (just below) 1 
     return s/32678f; 
    } 

    // Convert a float value into two bytes (use k as conversion value and not Int16.MaxValue to avoid peaks) 
    static void NormalizedToBytes_16(float value, float k, out byte firstByte, out byte secondByte) 
    { 
     short s = (short)(value * k); 
     firstByte = (byte)(s & 0x00FF); 
     secondByte = (byte)(s >> 8); 
    } 
+0

Zastanawiam się, czy możliwe jest przyspieszenie alogorith przesunięcia pitch. Używam go na iPhonie (dzięki xamarin.ios) i jest naprawdę powolny. Chyba zależy to od tego, czy używa on float i double vars (co z kolei stresuje fpu?) – John

0

przykro ożywić to, ale próbowałem tej klasy Pitchshifter i mimo to działa, mam trzaski w dźwięku podczas zjazdu w dół (0.5f). Przezwyciężyłeś to?