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