Example #1
0
        //////////////////////////////////////// SPECTRUM ///////////////////////////////////////////////////////////

        public SpectrogramData GenerateSpectrogram(int delayInMilliseconds, string spectrogramDirectory)
        {
            const int fftSize    = 256; // image height = fft size / 2
            const int imageWidth = 1024;

            int delaySampleCount = (int)(_header.SampleRate * (delayInMilliseconds / TimeCode.BaseUnit));

            // ignore negative delays for now (pretty sure it can't happen in mkv and some places pass in -1 by mistake)
            delaySampleCount = Math.Max(delaySampleCount, 0);

            var    images = new List <Bitmap>();
            var    drawer = new SpectrogramDrawer(fftSize);
            var    readSampleDataValue   = GetSampleDataReader();
            Task   saveImageTask         = null;
            double sampleAndChannelScale = GetSampleAndChannelScale();
            long   fileSampleCount       = _header.LengthInSamples;
            long   fileSampleOffset      = -delaySampleCount;
            int    chunkSampleCount      = fftSize * imageWidth;
            int    chunkCount            = (int)Math.Ceiling((double)(fileSampleCount + delaySampleCount) / chunkSampleCount);

            byte[]   data         = new byte[chunkSampleCount * _header.BlockAlign];
            double[] chunkSamples = new double[chunkSampleCount];

            Directory.CreateDirectory(spectrogramDirectory);

            _stream.Seek(_header.DataStartPosition, SeekOrigin.Begin);

            // for negative delays, skip samples at the beginning
            if (fileSampleOffset > 0)
            {
                _stream.Seek(fileSampleOffset * _header.BlockAlign, SeekOrigin.Current);
            }

            for (int iChunk = 0; iChunk < chunkCount; iChunk++)
            {
                // calculate padding at the beginning (for positive delays)
                int startPaddingSampleCount = 0;
                if (fileSampleOffset < 0)
                {
                    startPaddingSampleCount = (int)Math.Min(-fileSampleOffset, chunkSampleCount);
                    fileSampleOffset       += startPaddingSampleCount;
                }

                // calculate how many samples to read from the file
                long fileSamplesRemaining = fileSampleCount - Math.Max(fileSampleOffset, 0);
                int  fileReadSampleCount  = (int)Math.Min(fileSamplesRemaining, chunkSampleCount - startPaddingSampleCount);

                // calculate padding at the end (when the data isn't an even multiple of our chunk size)
                int endPaddingSampleCount = chunkSampleCount - startPaddingSampleCount - fileReadSampleCount;

                int chunkSampleOffset = 0;

                // add padding at the beginning
                if (startPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, startPaddingSampleCount);
                    chunkSampleOffset += startPaddingSampleCount;
                }

                // read samples from the file
                if (fileReadSampleCount > 0)
                {
                    int fileReadByteCount = fileReadSampleCount * _header.BlockAlign;
                    _stream.Read(data, 0, fileReadByteCount);
                    fileSampleOffset += fileReadSampleCount;

                    int dataByteOffset = 0;
                    while (dataByteOffset < fileReadByteCount)
                    {
                        double value = 0D;
                        for (int iChannel = 0; iChannel < _header.NumberOfChannels; iChannel++)
                        {
                            value += readSampleDataValue(data, ref dataByteOffset);
                        }
                        chunkSamples[chunkSampleOffset] = value * sampleAndChannelScale;
                        chunkSampleOffset += 1;
                    }
                }

                // add padding at the end
                if (endPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, endPaddingSampleCount);
                }

                // generate spectrogram for this chunk
                Bitmap bmp = drawer.Draw(chunkSamples);
                images.Add(bmp);

                // wait for previous image to finish saving
                saveImageTask?.Wait();

                // save image
                string imagePath = Path.Combine(spectrogramDirectory, iChunk + ".gif");
                saveImageTask = Task.Factory.StartNew(() =>
                {
                    bmp.Save(imagePath, System.Drawing.Imaging.ImageFormat.Gif);
                });
            }

            // wait for last image to finish saving
            saveImageTask?.Wait();

            var    doc            = new XmlDocument();
            var    culture        = CultureInfo.InvariantCulture;
            double sampleDuration = (double)fftSize / _header.SampleRate;

            doc.LoadXml("<SpectrogramInfo><SampleDuration/><NFFT/><ImageWidth/><SecondsPerImage/></SpectrogramInfo>");
            doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText  = sampleDuration.ToString(culture);
            doc.DocumentElement.SelectSingleNode("NFFT").InnerText            = fftSize.ToString(culture);
            doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText      = imageWidth.ToString(culture);
            doc.DocumentElement.SelectSingleNode("SecondsPerImage").InnerText = ((double)chunkSampleCount / _header.SampleRate).ToString(culture); // currently unused; for backwards compatibility
            doc.Save(Path.Combine(spectrogramDirectory, "Info.xml"));

            return(new SpectrogramData(fftSize, imageWidth, sampleDuration, images));
        }
Example #2
0
        //////////////////////////////////////// SPECTRUM ///////////////////////////////////////////////////////////
        public List<Bitmap> GenerateFourierData(int nfft, string spectrogramDirectory, int delayInMilliseconds)
        {
            if (Header.BytesPerSample == 4)
            {
                // Can't handle 32-bit samples due to the way the channel averaging is done
                throw new Exception("32-bit samples are unsupported.");
            }

            const int bitmapWidth = 1024;

            List<Bitmap> bitmaps = new List<Bitmap>();
            SpectrogramDrawer drawer = new SpectrogramDrawer(nfft);
            Task saveImageTask = null;
            ReadSampleDataValueDelegate readSampleDataValue = GetSampleDataReader();
            double sampleScale = 1.0 / (Math.Pow(2.0, Header.BytesPerSample * 8 - 1) * Header.NumberOfChannels);

            int delaySampleCount = (int)(Header.SampleRate * (delayInMilliseconds / TimeCode.BaseUnit));

            // other code (e.g. generating peaks) doesn't handle negative delays, so we'll do the same for now
            delaySampleCount = Math.Max(delaySampleCount, 0);

            long fileSampleCount = Header.LengthInSamples;
            long fileSampleOffset = -delaySampleCount;
            int chunkSampleCount = nfft * bitmapWidth;
            int chunkCount = (int)Math.Ceiling((double)(fileSampleCount + delaySampleCount) / chunkSampleCount);
            double[] chunkSamples = new double[chunkSampleCount];

            Directory.CreateDirectory(spectrogramDirectory);

            _data = new byte[chunkSampleCount * Header.BlockAlign];
            _stream.Seek(Header.DataStartPosition, SeekOrigin.Begin);

            // for negative delays, skip samples at the beginning
            if (fileSampleOffset > 0)
            {
                _stream.Seek(fileSampleOffset * Header.BlockAlign, SeekOrigin.Current);
            }

            for (int iChunk = 0; iChunk < chunkCount; iChunk++)
            {
                // calculate padding at the beginning (for positive delays)
                int startPaddingSampleCount = 0;
                if (fileSampleOffset < 0)
                {
                    startPaddingSampleCount = (int)Math.Min(-fileSampleOffset, chunkSampleCount);
                    fileSampleOffset += startPaddingSampleCount;
                }

                // calculate how many samples to read from the file
                long fileSamplesRemaining = fileSampleCount - Math.Max(fileSampleOffset, 0);
                int fileReadSampleCount = (int)Math.Min(fileSamplesRemaining, chunkSampleCount - startPaddingSampleCount);

                // calculate padding at the end (when the data isn't an even multiple of our chunk size)
                int endPaddingSampleCount = chunkSampleCount - startPaddingSampleCount - fileReadSampleCount;

                int chunkSampleOffset = 0;

                // add padding at the beginning
                if (startPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, startPaddingSampleCount);
                    chunkSampleOffset += startPaddingSampleCount;
                }

                // read samples from the file
                if (fileReadSampleCount > 0)
                {
                    int fileReadByteCount = fileReadSampleCount * Header.BlockAlign;
                    _stream.Read(_data, 0, fileReadByteCount);
                    fileSampleOffset += fileReadSampleCount;

                    int dataByteOffset = 0;
                    while (dataByteOffset < fileReadByteCount)
                    {
                        int value = 0;
                        for (int iChannel = 0; iChannel < Header.NumberOfChannels; iChannel++)
                        {
                            value += readSampleDataValue(ref dataByteOffset);
                        }
                        chunkSamples[chunkSampleOffset] = value * sampleScale;
                        chunkSampleOffset += 1;
                    }
                }

                // add padding at the end
                if (endPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, endPaddingSampleCount);
                    chunkSampleOffset += endPaddingSampleCount;
                }

                // generate spectrogram for this chunk
                Bitmap bmp = drawer.Draw(chunkSamples);
                bitmaps.Add(bmp);

                // wait for previous image to finish saving
                if (saveImageTask != null)
                    saveImageTask.Wait();

                // save image
                string imagePath = Path.Combine(spectrogramDirectory, iChunk + ".gif");
                saveImageTask = Task.Factory.StartNew(() =>
                {
                    bmp.Save(imagePath, System.Drawing.Imaging.ImageFormat.Gif);
                });
            }

            // wait for last image to finish saving
            if (saveImageTask != null)
                saveImageTask.Wait();

            var doc = new XmlDocument();
            var culture = CultureInfo.InvariantCulture;
            doc.LoadXml("<SpectrogramInfo><SampleDuration/><NFFT/><ImageWidth/><SecondsPerImage/></SpectrogramInfo>");
            doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText = ((double)nfft / Header.SampleRate).ToString(culture);
            doc.DocumentElement.SelectSingleNode("NFFT").InnerText = nfft.ToString(culture);
            doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText = bitmapWidth.ToString(culture);
            doc.DocumentElement.SelectSingleNode("SecondsPerImage").InnerText = ((double)chunkSampleCount / Header.SampleRate).ToString(culture); // currently unused; for backwards compatibility
            doc.Save(Path.Combine(spectrogramDirectory, "Info.xml"));

            return bitmaps;
        }
        //////////////////////////////////////// SPECTRUM ///////////////////////////////////////////////////////////
        public SpectrogramData GenerateSpectrogram(int delayInMilliseconds, string spectrogramDirectory)
        {
            const int fftSize = 256; // image height = fft size / 2
            const int imageWidth = 1024;

            int delaySampleCount = (int)(_header.SampleRate * (delayInMilliseconds / TimeCode.BaseUnit));

            // ignore negative delays for now (pretty sure it can't happen in mkv and some places pass in -1 by mistake)
            delaySampleCount = Math.Max(delaySampleCount, 0);

            var images = new List<Bitmap>();
            var drawer = new SpectrogramDrawer(fftSize);
            var readSampleDataValue = GetSampleDataReader();
            Task saveImageTask = null;
            double sampleAndChannelScale = GetSampleAndChannelScale();
            long fileSampleCount = _header.LengthInSamples;
            long fileSampleOffset = -delaySampleCount;
            int chunkSampleCount = fftSize * imageWidth;
            int chunkCount = (int)Math.Ceiling((double)(fileSampleCount + delaySampleCount) / chunkSampleCount);
            byte[] data = new byte[chunkSampleCount * _header.BlockAlign];
            double[] chunkSamples = new double[chunkSampleCount];

            Directory.CreateDirectory(spectrogramDirectory);

            _stream.Seek(_header.DataStartPosition, SeekOrigin.Begin);

            // for negative delays, skip samples at the beginning
            if (fileSampleOffset > 0)
            {
                _stream.Seek(fileSampleOffset * _header.BlockAlign, SeekOrigin.Current);
            }

            for (int iChunk = 0; iChunk < chunkCount; iChunk++)
            {
                // calculate padding at the beginning (for positive delays)
                int startPaddingSampleCount = 0;
                if (fileSampleOffset < 0)
                {
                    startPaddingSampleCount = (int)Math.Min(-fileSampleOffset, chunkSampleCount);
                    fileSampleOffset += startPaddingSampleCount;
                }

                // calculate how many samples to read from the file
                long fileSamplesRemaining = fileSampleCount - Math.Max(fileSampleOffset, 0);
                int fileReadSampleCount = (int)Math.Min(fileSamplesRemaining, chunkSampleCount - startPaddingSampleCount);

                // calculate padding at the end (when the data isn't an even multiple of our chunk size)
                int endPaddingSampleCount = chunkSampleCount - startPaddingSampleCount - fileReadSampleCount;

                int chunkSampleOffset = 0;

                // add padding at the beginning
                if (startPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, startPaddingSampleCount);
                    chunkSampleOffset += startPaddingSampleCount;
                }

                // read samples from the file
                if (fileReadSampleCount > 0)
                {
                    int fileReadByteCount = fileReadSampleCount * _header.BlockAlign;
                    _stream.Read(data, 0, fileReadByteCount);
                    fileSampleOffset += fileReadSampleCount;

                    int dataByteOffset = 0;
                    while (dataByteOffset < fileReadByteCount)
                    {
                        double value = 0D;
                        for (int iChannel = 0; iChannel < _header.NumberOfChannels; iChannel++)
                        {
                            value += readSampleDataValue(data, ref dataByteOffset);
                        }
                        chunkSamples[chunkSampleOffset] = value * sampleAndChannelScale;
                        chunkSampleOffset += 1;
                    }
                }

                // add padding at the end
                if (endPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, endPaddingSampleCount);
                    chunkSampleOffset += endPaddingSampleCount;
                }

                // generate spectrogram for this chunk
                Bitmap bmp = drawer.Draw(chunkSamples);
                images.Add(bmp);

                // wait for previous image to finish saving
                if (saveImageTask != null)
                    saveImageTask.Wait();

                // save image
                string imagePath = Path.Combine(spectrogramDirectory, iChunk + ".gif");
                saveImageTask = Task.Factory.StartNew(() =>
                {
                    bmp.Save(imagePath, System.Drawing.Imaging.ImageFormat.Gif);
                });
            }

            // wait for last image to finish saving
            if (saveImageTask != null)
                saveImageTask.Wait();

            var doc = new XmlDocument();
            var culture = CultureInfo.InvariantCulture;
            double sampleDuration = (double)fftSize / _header.SampleRate;
            doc.LoadXml("<SpectrogramInfo><SampleDuration/><NFFT/><ImageWidth/><SecondsPerImage/></SpectrogramInfo>");
            doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText = sampleDuration.ToString(culture);
            doc.DocumentElement.SelectSingleNode("NFFT").InnerText = fftSize.ToString(culture);
            doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText = imageWidth.ToString(culture);
            doc.DocumentElement.SelectSingleNode("SecondsPerImage").InnerText = ((double)chunkSampleCount / _header.SampleRate).ToString(culture); // currently unused; for backwards compatibility
            doc.Save(Path.Combine(spectrogramDirectory, "Info.xml"));

            return new SpectrogramData(fftSize, imageWidth, sampleDuration, images);
        }
Example #4
0
        //////////////////////////////////////// SPECTRUM ///////////////////////////////////////////////////////////
        public List<Bitmap> GenerateFourierData(int nfft, string spectrogramDirectory, int delayInMilliseconds)
        {
            const int bitmapWidth = 1024;

            List<Bitmap> bitmaps = new List<Bitmap>();
            SpectrogramDrawer drawer = new SpectrogramDrawer(nfft);
            ReadSampleDataValueDelegate readSampleDataValue = GetSampleDataReader();
            double sampleScale = 1.0 / (Math.Pow(2.0, Header.BitsPerSample - 1) * Header.NumberOfChannels);

            int delaySampleCount = (int)(Header.SampleRate * (delayInMilliseconds / TimeCode.BaseUnit));

            // other code (e.g. generating peaks) doesn't handle negative delays, so we'll do the same for now
            delaySampleCount = Math.Max(delaySampleCount, 0);

            long fileSampleCount = Header.LengthInSamples;
            long fileSampleOffset = -delaySampleCount;
            int chunkSampleCount = nfft * bitmapWidth;
            int chunkCount = (int)Math.Ceiling((double)(fileSampleCount + delaySampleCount) / chunkSampleCount);
            double[] chunkSamples = new double[chunkSampleCount];

            _data = new byte[chunkSampleCount * Header.BlockAlign];
            _stream.Seek(Header.DataStartPosition, SeekOrigin.Begin);

            // for negative delays, skip samples at the beginning
            if (fileSampleOffset > 0)
            {
                _stream.Seek(fileSampleOffset * Header.BlockAlign, SeekOrigin.Current);
            }

            for (int iChunk = 0; iChunk < chunkCount; iChunk++)
            {
                // calculate padding at the beginning (for positive delays)
                int startPaddingSampleCount = 0;
                if (fileSampleOffset < 0)
                {
                    startPaddingSampleCount = (int)Math.Min(-fileSampleOffset, chunkSampleCount);
                    fileSampleOffset += startPaddingSampleCount;
                }

                // calculate how many samples to read from the file
                long fileSamplesRemaining = fileSampleCount - Math.Max(fileSampleOffset, 0);
                int fileReadSampleCount = (int)Math.Min(fileSamplesRemaining, chunkSampleCount - startPaddingSampleCount);

                // calculate padding at the end (when the data isn't an even multiple of our chunk size)
                int endPaddingSampleCount = chunkSampleCount - startPaddingSampleCount - fileReadSampleCount;

                int chunkSampleOffset = 0;

                // add padding at the beginning
                if (startPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, startPaddingSampleCount);
                    chunkSampleOffset += startPaddingSampleCount;
                }

                // read samples from the file
                if (fileReadSampleCount > 0)
                {
                    int fileReadByteCount = fileReadSampleCount * Header.BlockAlign;
                    _stream.Read(_data, 0, fileReadByteCount);
                    fileSampleOffset += fileReadSampleCount;

                    int dataByteOffset = 0;
                    while (dataByteOffset < fileReadByteCount)
                    {
                        int value = 0;
                        for (int iChannel = 0; iChannel < Header.NumberOfChannels; iChannel++)
                        {
                            value += readSampleDataValue(ref dataByteOffset);
                        }
                        chunkSamples[chunkSampleOffset] = value * sampleScale;
                        chunkSampleOffset += 1;
                    }
                }

                // add padding at the end
                if (endPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, endPaddingSampleCount);
                    chunkSampleOffset += endPaddingSampleCount;
                }

                // generate spectrogram for this chunk
                Bitmap bmp = drawer.Draw(chunkSamples);
                bmp.Save(Path.Combine(spectrogramDirectory, iChunk + ".gif"), System.Drawing.Imaging.ImageFormat.Gif);
                bitmaps.Add(bmp);
            }

            var doc = new XmlDocument();
            doc.LoadXml("<SpectrogramInfo><SampleDuration/><TotalDuration/><AudioFormat /><AudioFormat /><ChunkId /><SecondsPerImage /><ImageWidth /><NFFT /></SpectrogramInfo>");
            doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText = ((double)nfft / Header.SampleRate).ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("TotalDuration").InnerText = Header.LengthInSeconds.ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("AudioFormat").InnerText = Header.AudioFormat.ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("ChunkId").InnerText = Header.ChunkId.ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("SecondsPerImage").InnerText = ((double)chunkSampleCount / Header.SampleRate).ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText = bitmapWidth.ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("NFFT").InnerText = nfft.ToString(CultureInfo.InvariantCulture);
            doc.Save(Path.Combine(spectrogramDirectory, "Info.xml"));

            return bitmaps;
        }
Example #5
0
        //////////////////////////////////////// SPECTRUM ///////////////////////////////////////////////////////////

        public List <Bitmap> GenerateFourierData(int nfft, string spectrogramDirectory, int delayInMilliseconds)
        {
            const int bitmapWidth = 1024;

            List <Bitmap>               bitmaps             = new List <Bitmap>();
            SpectrogramDrawer           drawer              = new SpectrogramDrawer(nfft);
            ReadSampleDataValueDelegate readSampleDataValue = GetSampleDataReader();
            double sampleScale = 1.0 / (Math.Pow(2.0, Header.BitsPerSample - 1) * Header.NumberOfChannels);

            int delaySampleCount = (int)(Header.SampleRate * (delayInMilliseconds / TimeCode.BaseUnit));

            // other code (e.g. generating peaks) doesn't handle negative delays, so we'll do the same for now
            delaySampleCount = Math.Max(delaySampleCount, 0);

            long fileSampleCount  = Header.LengthInSamples;
            long fileSampleOffset = -delaySampleCount;
            int  chunkSampleCount = nfft * bitmapWidth;
            int  chunkCount       = (int)Math.Ceiling((double)(fileSampleCount + delaySampleCount) / chunkSampleCount);

            double[] chunkSamples = new double[chunkSampleCount];

            _data = new byte[chunkSampleCount * Header.BlockAlign];
            _stream.Seek(Header.DataStartPosition, SeekOrigin.Begin);

            // for negative delays, skip samples at the beginning
            if (fileSampleOffset > 0)
            {
                _stream.Seek(fileSampleOffset * Header.BlockAlign, SeekOrigin.Current);
            }

            for (int iChunk = 0; iChunk < chunkCount; iChunk++)
            {
                // calculate padding at the beginning (for positive delays)
                int startPaddingSampleCount = 0;
                if (fileSampleOffset < 0)
                {
                    startPaddingSampleCount = (int)Math.Min(-fileSampleOffset, chunkSampleCount);
                    fileSampleOffset       += startPaddingSampleCount;
                }

                // calculate how many samples to read from the file
                long fileSamplesRemaining = fileSampleCount - Math.Max(fileSampleOffset, 0);
                int  fileReadSampleCount  = (int)Math.Min(fileSamplesRemaining, chunkSampleCount - startPaddingSampleCount);

                // calculate padding at the end (when the data isn't an even multiple of our chunk size)
                int endPaddingSampleCount = chunkSampleCount - startPaddingSampleCount - fileReadSampleCount;

                int chunkSampleOffset = 0;

                // add padding at the beginning
                if (startPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, startPaddingSampleCount);
                    chunkSampleOffset += startPaddingSampleCount;
                }

                // read samples from the file
                if (fileReadSampleCount > 0)
                {
                    int fileReadByteCount = fileReadSampleCount * Header.BlockAlign;
                    _stream.Read(_data, 0, fileReadByteCount);
                    fileSampleOffset += fileReadSampleCount;

                    int dataByteOffset = 0;
                    while (dataByteOffset < fileReadByteCount)
                    {
                        int value = 0;
                        for (int iChannel = 0; iChannel < Header.NumberOfChannels; iChannel++)
                        {
                            value += readSampleDataValue(ref dataByteOffset);
                        }
                        chunkSamples[chunkSampleOffset] = value * sampleScale;
                        chunkSampleOffset += 1;
                    }
                }

                // add padding at the end
                if (endPaddingSampleCount > 0)
                {
                    Array.Clear(chunkSamples, chunkSampleOffset, endPaddingSampleCount);
                    chunkSampleOffset += endPaddingSampleCount;
                }

                // generate spectrogram for this chunk
                Bitmap bmp = drawer.Draw(chunkSamples);
                bmp.Save(Path.Combine(spectrogramDirectory, iChunk + ".gif"), System.Drawing.Imaging.ImageFormat.Gif);
                bitmaps.Add(bmp);
            }

            var doc = new XmlDocument();

            doc.LoadXml("<SpectrogramInfo><SampleDuration/><TotalDuration/><AudioFormat /><AudioFormat /><ChunkId /><SecondsPerImage /><ImageWidth /><NFFT /></SpectrogramInfo>");
            doc.DocumentElement.SelectSingleNode("SampleDuration").InnerText  = ((double)nfft / Header.SampleRate).ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("TotalDuration").InnerText   = Header.LengthInSeconds.ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("AudioFormat").InnerText     = Header.AudioFormat.ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("ChunkId").InnerText         = Header.ChunkId.ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("SecondsPerImage").InnerText = ((double)chunkSampleCount / Header.SampleRate).ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("ImageWidth").InnerText      = bitmapWidth.ToString(CultureInfo.InvariantCulture);
            doc.DocumentElement.SelectSingleNode("NFFT").InnerText            = nfft.ToString(CultureInfo.InvariantCulture);
            doc.Save(Path.Combine(spectrogramDirectory, "Info.xml"));

            return(bitmaps);
        }