2
votes

I'm trying to reverse a .wav file in C#. I am basically reading a forwards .wav file, and copying in the format/meta data bytes directly into a new byte array. The rest of the "audio data" bytes I am copying into the new byte array in reverse. I am then creating a new stream with this reversed byte array and writing it to a file.

However, when I play this reversed .wav file, although I can hear the words in reverse, the volume is much higher and there is significant background noise.

How can I reverse the .wav file exactly so that the volume is the same and there is no background noise?

class Program
{
    static void Main(string[] args)
    {
        string localFolder = @"C:\Users\name\folder1";
        string forwardsWavFilePath = Path.Combine(localFolder, "forwardsFile.wav");
        FileStream forwardsWavFileStream = new FileStream(forwardsWavFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
        byte[] forwardsWavFileStreamByteArray = new byte[forwardsWavFileStream.Length];
        int numberOfBytesReadIntoForwardsWavFileStreamByteArray = forwardsWavFileStream.Read(forwardsWavFileStreamByteArray, 0, (int)forwardsWavFileStream.Length);

        int startIndexOfDataChunk = getStartIndexOfDataChunk(forwardsWavFileStreamByteArray);

        byte[] reversedWavFileStreamByteArray = new byte[forwardsWavFileStreamByteArray.Length];

        populateHeaderAndFormatChunk(forwardsWavFileStreamByteArray, startIndexOfDataChunk, reversedWavFileStreamByteArray);

        populateDataChunkInReverse(forwardsWavFileStreamByteArray, startIndexOfDataChunk, reversedWavFileStreamByteArray);

        string reversedWavFilePath = Path.Combine(localFolder, "reversedFile.wav");
        FileStream reversedFileStream = new FileStream(reversedWavFilePath, FileMode.Create, FileAccess.Write, FileShare.Write);
        reversedFileStream.Write(reversedWavFileStreamByteArray, 0, reversedWavFileStreamByteArray.Length);
    }

    private static void populateDataChunkInReverse(byte[] forwardsWavFileStreamByteArray, int startIndexOfAudioData, byte[] reversedWavFileStreamByteArray)
    {
        for (int i = forwardsWavFileStreamByteArray.Length - 1; i >= startIndexOfAudioData; i--)
        {
            reversedWavFileStreamByteArray[forwardsWavFileStreamByteArray.Length - 1 + startIndexOfAudioData - i] = forwardsWavFileStreamByteArray[i];
        }
    }

    private static void populateHeaderAndFormatChunk(byte[] forwardsWavFileStreamByteArray, int startIndexOfAudioData, byte[] reversedWavFileStreamByteArray)
    {
        for (int i = 0; i < startIndexOfAudioData; i++)
        {
            reversedWavFileStreamByteArray[i] = forwardsWavFileStreamByteArray[i];
        }
    }

    private static int getStartIndexOfDataChunk(byte[] forwardsWavFileStreamByteArray)
    {
        int startIndexOfAudioData = 12;
        int charDAsciiDecimalCode = 100; //'d'
        int charAAsciiDecimalCode = 97;  //'a'
        int charTAsciiDecimalCode = 116; //'t'

        //find "data" in the byte array
        while (!(forwardsWavFileStreamByteArray[startIndexOfAudioData] == charDAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 1] == charAAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 2] == charTAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 3] == charAAsciiDecimalCode))
        {
            startIndexOfAudioData += 4;
            int chunkSize = forwardsWavFileStreamByteArray[startIndexOfAudioData] + forwardsWavFileStreamByteArray[startIndexOfAudioData + 1] * 256 + forwardsWavFileStreamByteArray[startIndexOfAudioData + 2] * 65536 + forwardsWavFileStreamByteArray[startIndexOfAudioData + 3] * 16777216;
            startIndexOfAudioData += 4 + chunkSize;
        }
        startIndexOfAudioData += 8;
        return startIndexOfAudioData;
    }
}
2

2 Answers

3
votes

You currently are just reversing the byte stream - but what you should do is reverse the sample stream. Each sample is probably more than one byte, so this leads to the distortion. You can find out the sample size and the number of channels (e.g. stereo) from the header.

For a description of the header format see here - you need the bits per sample and the number of channels.

1
votes

Update: I've published a NuGet package for this: https://www.nuget.org/packages/WaveFileManipulator/

Thanks to BrokenGlass for pointing me in the right direction. I was indeed reversing the bytes where I was only meant to reverse the samples. I worked out the number of bytes per sample, and then reversed the samples instead:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace TestWavPlayer
{
    class Program
    {
        const int _bitsPerByte = 8;
        static int _bytesPerSample;

        static void Main(string[] args)
        {
            string localFolder = @"C:\Users\username\AppData\LocalState";

            string forwardsWavFilePath = Path.Combine(localFolder, "forwardsFile.wav");
            byte[] forwardsWavFileStreamByteArray = populateForwardsWavFileByteArray(forwardsWavFilePath);

            getWavMetadata(forwardsWavFileStreamByteArray);

            int startIndexOfDataChunk = getStartIndexOfDataChunk(forwardsWavFileStreamByteArray);

            byte[] reversedWavFileStreamByteArray = populateReversedWavFileByteArray(forwardsWavFileStreamByteArray, startIndexOfDataChunk, _bytesPerSample);

            string reversedWavFilePath = Path.Combine(localFolder, "reversedFile.wav");
            writeReversedWavFileByteArrayToFile(reversedWavFileStreamByteArray, reversedWavFilePath);
        }

        private static void getWavMetadata(byte[] forwardsWavFileStreamByteArray)
        {
            MetadataGatherer.GetRiffText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetFileSize(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetWaveText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetFmtText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetLengthOfFormatData(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetTypeOfFormat(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetNumOfChannels(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetSampleRate(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetBytesPerSecond(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetBlockAlign(forwardsWavFileStreamByteArray);
            _bytesPerSample = MetadataGatherer.GetBitsPerSample(forwardsWavFileStreamByteArray) / _bitsPerByte;
            MetadataGatherer.GetListText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetDataText(forwardsWavFileStreamByteArray);
            MetadataGatherer.GetDataSize(forwardsWavFileStreamByteArray);
        }

        private static void writeReversedWavFileByteArrayToFile(byte[] reversedWavFileStreamByteArray, string reversedWavFilePath)
        {
            FileStream reversedFileStream = new FileStream(reversedWavFilePath, FileMode.Create, FileAccess.Write, FileShare.Write);
            reversedFileStream.Write(reversedWavFileStreamByteArray, 0, reversedWavFileStreamByteArray.Length);
        }

        private static byte[] populateReversedWavFileByteArray(byte[] forwardsWavFileStreamByteArray, int startIndexOfDataChunk, int bytesPerSample)
        {
            byte[] forwardsArrayWithOnlyHeaders = createForwardsArrayWithOnlyHeaders(forwardsWavFileStreamByteArray, startIndexOfDataChunk);

            byte[] forwardsArrayWithOnlyAudioData = createForwardsArrayWithOnlyAudioData(forwardsWavFileStreamByteArray, startIndexOfDataChunk);

            byte[] reversedArrayWithOnlyAudioData = reverseTheForwardsArrayWithOnlyAudioData(bytesPerSample, forwardsArrayWithOnlyAudioData);

            byte[] reversedWavFileStreamByteArray = combineArrays(forwardsArrayWithOnlyHeaders, reversedArrayWithOnlyAudioData);

            return reversedWavFileStreamByteArray;
        }

        private static byte[] combineArrays(byte[] forwardsArrayWithOnlyHeaders, byte[] reversedArrayWithOnlyAudioData)
        {
            byte[] reversedWavFileStreamByteArray = new byte[forwardsArrayWithOnlyHeaders.Length + reversedArrayWithOnlyAudioData.Length];
            Array.Copy(forwardsArrayWithOnlyHeaders, reversedWavFileStreamByteArray, forwardsArrayWithOnlyHeaders.Length);
            Array.Copy(reversedArrayWithOnlyAudioData, 0, reversedWavFileStreamByteArray, forwardsArrayWithOnlyHeaders.Length, reversedArrayWithOnlyAudioData.Length);
            return reversedWavFileStreamByteArray;
        }

        private static byte[] reverseTheForwardsArrayWithOnlyAudioData(int bytesPerSample, byte[] forwardsArrayWithOnlyAudioData)
        {
            int length = forwardsArrayWithOnlyAudioData.Length;
            byte[] reversedArrayWithOnlyAudioData = new byte[length];

            int sampleIdentifier = 0;

            for (int i = 0; i < length; i++)
            {
                if (i != 0 && i % bytesPerSample == 0)
                {
                    sampleIdentifier += 2 * bytesPerSample;
                }
                int index = length - bytesPerSample - sampleIdentifier + i;
                reversedArrayWithOnlyAudioData[i] = forwardsArrayWithOnlyAudioData[index];
            }
            return reversedArrayWithOnlyAudioData;
        }

        private static byte[] createForwardsArrayWithOnlyAudioData(byte[] forwardsWavFileStreamByteArray, int startIndexOfDataChunk)
        {
            byte[] forwardsArrayWithOnlyAudioData = new byte[forwardsWavFileStreamByteArray.Length - startIndexOfDataChunk];
            Array.Copy(forwardsWavFileStreamByteArray, startIndexOfDataChunk, forwardsArrayWithOnlyAudioData, 0, forwardsWavFileStreamByteArray.Length - startIndexOfDataChunk);
            return forwardsArrayWithOnlyAudioData;
        }

        private static byte[] createForwardsArrayWithOnlyHeaders(byte[] forwardsWavFileStreamByteArray, int startIndexOfDataChunk)
        {
            byte[] forwardsArrayWithOnlyHeaders = new byte[startIndexOfDataChunk];
            Array.Copy(forwardsWavFileStreamByteArray, 0, forwardsArrayWithOnlyHeaders, 0, startIndexOfDataChunk);
            return forwardsArrayWithOnlyHeaders;
        }

        private static byte[] populateForwardsWavFileByteArray(string forwardsWavFilePath)
        {
            FileStream forwardsWavFileStream = new FileStream(forwardsWavFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
            byte[] forwardsWavFileStreamByteArray = new byte[forwardsWavFileStream.Length];
            forwardsWavFileStream.Read(forwardsWavFileStreamByteArray, 0, (int)forwardsWavFileStream.Length);
            return forwardsWavFileStreamByteArray;
        }

        private static int getStartIndexOfDataChunk(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndexOfAudioData = 12;
            int charDAsciiDecimalCode = 100; //'d' //data is located at index 70 in my .wav file
            int charAAsciiDecimalCode = 97;  //'a'
            int charTAsciiDecimalCode = 116; //'t'

            int chunkSize;

            //find "data" in the byte array
            while (!(forwardsWavFileStreamByteArray[startIndexOfAudioData] == charDAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 1] == charAAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 2] == charTAsciiDecimalCode && forwardsWavFileStreamByteArray[startIndexOfAudioData + 3] == charAAsciiDecimalCode))
            {
                startIndexOfAudioData += 4;
                chunkSize = forwardsWavFileStreamByteArray[startIndexOfAudioData] + forwardsWavFileStreamByteArray[startIndexOfAudioData + 1] * 256 + forwardsWavFileStreamByteArray[startIndexOfAudioData + 2] * 65536 + forwardsWavFileStreamByteArray[startIndexOfAudioData + 3] * 16777216;
                startIndexOfAudioData += 4 + chunkSize;
            }
            startIndexOfAudioData += 8;
            return startIndexOfAudioData;
        }
    }
}

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestWavPlayer
{
    static class MetadataGatherer
    {
        internal static ushort GetTypeOfFormat(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 20;
            int endIndex = 21;
            byte[] typeOfFormatByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, startIndex, endIndex);
            ushort typeOfFormat = BitConverter.ToUInt16(typeOfFormatByteArray, 0);
            Console.WriteLine("Type of format (1 is PCM) = {0}", typeOfFormat);
            return typeOfFormat;
        }

        internal static void GetFmtText(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 12;
            int endIndex = 15;
            GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static string GetWaveText(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 8;
            int endIndex = 11;
            return GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static string GetRiffText(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 0;
            int endIndex = 3;
            return GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static uint GetLengthOfFormatData(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 16;
            int endIndex = 19;
            byte[] lengthOfFormatDataByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, startIndex, endIndex);
            uint lengthOfFormatData = BitConverter.ToUInt32(lengthOfFormatDataByteArray, 0);
            Console.WriteLine("Length of format data = {0}", lengthOfFormatData);
            return lengthOfFormatData;
        }

        internal static byte[] GetRelevantBytesIntoNewArray(byte[] forwardsWavFileStreamByteArray, int startIndex, int endIndex)
        {
            int length = endIndex - startIndex + 1;
            byte[] relevantBytesArray = new byte[length];
            Array.Copy(forwardsWavFileStreamByteArray, startIndex, relevantBytesArray, 0, length);
            return relevantBytesArray;
        }

        internal static uint GetFileSize(byte[] forwardsWavFileStreamByteArray)
        {
            int fileSizeStartIndex = 4;
            int fileSizeEndIndex = 7;
            byte[] fileSizeByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, fileSizeStartIndex, fileSizeEndIndex);
            uint fileSize = BitConverter.ToUInt32(fileSizeByteArray, 0) + 8; //need to add the size of the 
            Console.WriteLine("File size = {0}", fileSize);
            return fileSize;
        }

        internal static string GetAsciiText(byte[] forwardsWavFileStreamByteArray, int startIndex, int endIndex)
        {
            string asciiText = "";
            for (int i = startIndex; i <= endIndex; i++)
            {
                asciiText += Convert.ToChar(forwardsWavFileStreamByteArray[i]);
            }
            Console.WriteLine(asciiText);
            return asciiText;
        }

        internal static ushort GetNumOfChannels(byte[] forwardsWavFileStreamByteArray)
        {
            int numOfChannelsStartIndex = 22;
            int numOfChannelsEndIndex = 23;
            byte[] numOfChannelsByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, numOfChannelsStartIndex, numOfChannelsEndIndex);
            ushort numOfChannels = BitConverter.ToUInt16(numOfChannelsByteArray, 0); //need to add the size of the 
            Console.WriteLine("Number Of Channels = {0}", numOfChannels);
            return numOfChannels;
        }

        internal static uint GetSampleRate(byte[] forwardsWavFileStreamByteArray)
        {
            int sampleRateStartIndex = 24;
            int sampleRateEndIndex = 27;
            byte[] sampleRateByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, sampleRateStartIndex, sampleRateEndIndex);
            uint sampleRate = BitConverter.ToUInt32(sampleRateByteArray, 0); //need to add the size of the 
            Console.WriteLine("Sample Rate = {0}", sampleRate);
            return sampleRate;
        }

        internal static uint GetBytesPerSecond(byte[] forwardsWavFileStreamByteArray)
        {
            int bytesPerSecondStartIndex = 28;
            int bytesPerSecondEndIndex = 31;
            byte[] bytesPerSecondByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, bytesPerSecondStartIndex, bytesPerSecondEndIndex);
            uint bytesPerSecond = BitConverter.ToUInt32(bytesPerSecondByteArray, 0); //need to add the size of the 
            Console.WriteLine("Bytes Per Second = {0}", bytesPerSecond);
            return bytesPerSecond;
        }

        internal static ushort GetBlockAlign(byte[] forwardsWavFileStreamByteArray)
        {
            int blockAlignStartIndex = 32;
            int blockAlignEndIndex = 33;
            byte[] blockAlignByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, blockAlignStartIndex, blockAlignEndIndex);
            ushort blockAlign = BitConverter.ToUInt16(blockAlignByteArray, 0); //need to add the size of the 
            Console.WriteLine("Block Align = {0}", blockAlign);
            return blockAlign;
        }

        internal static ushort GetBitsPerSample(byte[] forwardsWavFileStreamByteArray)
        {
            int bitsPerSampleStartIndex = 34;
            int bitsPerSampleEndIndex = 35;
            byte[] bitsPerSampleByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, bitsPerSampleStartIndex, bitsPerSampleEndIndex);
            ushort bitsPerSample = BitConverter.ToUInt16(bitsPerSampleByteArray, 0); //need to add the size of the 
            Console.WriteLine("Bits Per Sample = {0}", bitsPerSample);
            return bitsPerSample;
        }

        internal static void GetDataText(byte[] forwardsWavFileStreamByteArray)
        {
            //should be these values according to http://www.topherlee.com/software/pcm-tut-wavformat.html
            //int startIndex = 36; //this is the index of "LIST" not "data" :S
            //int endIndex = 39;

            //data is located at index 70 in my .wav file
            int startIndex = 70;
            int endIndex = 73;
            GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static void GetListText(byte[] forwardsWavFileStreamByteArray)
        {
            int startIndex = 36; //this is the index of "LIST"
            int endIndex = 39;
            GetAsciiText(forwardsWavFileStreamByteArray, startIndex, endIndex);
        }

        internal static uint GetDataSize(byte[] forwardsWavFileStreamByteArray)
        {
            int dataSizeStartIndex = 70;
            int dataSizeEndIndex = 73;
            byte[] dataSizeByteArray = GetRelevantBytesIntoNewArray(forwardsWavFileStreamByteArray, dataSizeStartIndex, dataSizeEndIndex);
            uint dataSize = BitConverter.ToUInt16(dataSizeByteArray, 0); //need to add the size of the 
            Console.WriteLine("Data Size = {0}", dataSize);
            return dataSize;
        }
    }
}

Here's a tutorial describing the whole process.