c#波形文件的俯仰移位

时间:2013-10-01 08:53:16

标签: c# audio wav pitch pitch-shifting

我目前正在尝试使用此算法对波形文件进行音高变换

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

这里我的代码使用了上面的实现,但没有运气。输出的wave文件似乎已损坏或无效。

代码非常简单,除了音高变换算法:)

  1. 它加载一个wave文件,它读取波形文件数据并将其放入 byte [] array。
  2. 然后它将字节数据“规范化”为-1.0f到1.0f格式(如 由音高变换算法的创建者提出请求。
  3. 它应用音高移位算法然后转换回来 将数据标准化为bytes []数组。
  4. 最后保存一个波形文件,其中包含原始波形的相同标题 文件和音调移位数据。
  5. 我错过了什么吗?

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

2 个答案:

答案 0 :(得分:4)

这里的算法运行良好

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

我的错误在于我如何阅读波形标题和波形数据。我在这里发布了完整的代码

警告:此代码仅适用于PCM 16位(立体声/单声道)波。可以很容易地适应PCM 8位。

    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);
    }

答案 1 :(得分:0)

很抱歉重振了这个,但是我尝试了这个变速器课程,虽然它有效,但我在音调时发出噼啪声(0.5f)。你找到了解决方法吗?