2013-10-01 8 views
6

ich zur Zeit versucht, Pitch Shifting einer Wave-Datei zu tun, um diesen AlgorithmusC# Pitch Verschiebung der Wellen Dateien

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

Hier ist mein Code, der die obige Implementierung verwenden, aber ohne Glück. Die ausgegebene Wave-Datei scheint beschädigt oder ungültig zu sein.

Der Code ist ganz einfach, mit Ausnahme des Pitch-Shift-Algorithmus :)

  1. Es eine Wave-Datei laden, liest sie die Datei Datenwelle und steckt es in einem byte [] Array.
  2. Dann "normalisieren" Bytes Daten in -1.0f bis 1.0f Format (wie vom Ersteller des Pitch-Shift-Algorithmus angefordert).
  3. Es wendet den Tonhöhenverschiebungsalgorithmus an und konvertiert dann die normalisierten Daten in ein bytes [] -Array zurück.
  4. Schließlich speichert eine Wave-Datei mit dem gleichen Header der ursprünglichen Welle Datei und die Tonhöhe verschobenen Daten.

Fehle ich etwas?

 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

Sind Sie sicher, dass die Kopfzeile für Ihre Audiodatei 44 Bytes ist? Laut dieser Seite http://www.sonicspot.com/guide/wavefiles.html hängt es von vielen Dingen ab und muss richtig analysiert werden. – Neil

+0

Sie haben Recht! Ich werde meine Frage automatisch beantworten, um die korrekte Verwendung zu veröffentlichen. – John

Antwort

4

Der Algorithmus hier funktioniert

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

Mein Fehler auf, war, wie ich den Wave-Header las und Daten winken. Ich poste hier den voll funktionsfähigen Code

ACHTUNG: Dieser Code funktioniert nur für PCM 16 Bit (Stereo/Mono) Wellen. Kann problemlos an PCM 8 Bit angepasst werden.

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

Ich frage mich, ob es möglich ist, den Tonhöhenverschiebungsalgorithmus zu beschleunigen. Ich benutze es auf einem iPhone (dank xamarin.ios) und es ist wirklich langsam. Ich denke, es hängt von der Tatsache ab, dass es float und double vars verwendet (was wiederum den fpu stresst?) – John

0

leid, das wieder zu beleben, aber ich versuchte, dass Pitchshifter Klasse und, während es funktioniert, ich Knistern im Audio erhalten, während (0,5f) Pitching nach unten. Sie erarbeiten sich einen Weg herum?