From 0defc6b5e92632933bb52d516117fa3e20bb265e Mon Sep 17 00:00:00 2001 From: "J.D. Purcell" Date: Sun, 13 Sep 2015 01:11:13 -0400 Subject: [PATCH 1/2] WaveToVisualizer: fix peak generation skipping the last fractional second of audio. Add waveform batch: generate spectrogram (if respective setting is enabled). Add waveform batch: fix status not showing the "calculating..." part. Small code cleanups. --- libse/WaveToVisualizer.cs | 41 +++++++++++++++++-------------- src/Forms/AddWaveForm.Designer.cs | 4 +-- src/Forms/AddWaveForm.cs | 30 ++++++++++------------ src/Forms/AddWaveformBatch.cs | 36 +++++++++++++-------------- src/Forms/Main.cs | 2 +- 5 files changed, 57 insertions(+), 56 deletions(-) diff --git a/libse/WaveToVisualizer.cs b/libse/WaveToVisualizer.cs index 8f2770155..125f6ebe1 100644 --- a/libse/WaveToVisualizer.cs +++ b/libse/WaveToVisualizer.cs @@ -249,9 +249,8 @@ namespace Nikse.SubtitleEdit.Core /// /// Generate peaks (samples with some interval) for an uncompressed wave file /// - /// Sampeles per second / sample rate /// Delay in milliseconds (normally zero) - public void GeneratePeakSamples(int peaksPerSecond, int delayInMilliseconds) + public void GeneratePeakSamples(int delayInMilliseconds) { if (Header.BytesPerSample == 4) { @@ -259,7 +258,11 @@ namespace Nikse.SubtitleEdit.Core throw new Exception("32-bit samples are unsupported."); } - PeaksPerSecond = peaksPerSecond; + PeaksPerSecond = Math.Min(Configuration.Settings.VideoControls.WaveformMinimumSampleRate, Header.SampleRate); + + // Ensure that peaks per second is a multiple of the sample rate + while (Header.SampleRate % PeaksPerSecond != 0) + PeaksPerSecond++; ReadSampleDataValueDelegate readSampleDataValue = GetSampleDataReader(); DataMinValue = int.MaxValue; @@ -268,7 +271,7 @@ namespace Nikse.SubtitleEdit.Core if (delayInMilliseconds > 0) { - for (int i = 0; i < peaksPerSecond * delayInMilliseconds / 1000; i++) + for (int i = 0; i < PeaksPerSecond * delayInMilliseconds / 1000; i++) PeakSamples.Add(0); } @@ -276,9 +279,9 @@ namespace Nikse.SubtitleEdit.Core _data = new byte[Header.BytesPerSecond]; _stream.Position = Header.DataStartPosition; int bytesRead = _stream.Read(_data, 0, _data.Length); - while (bytesRead == Header.BytesPerSecond) + while (bytesRead > 0) { - for (int i = 0; i < Header.BytesPerSecond; i += bytesInterval) + for (int i = 0; i < bytesRead; i += bytesInterval) { int index = i; int value = 0; @@ -286,7 +289,7 @@ namespace Nikse.SubtitleEdit.Core { value += readSampleDataValue.Invoke(ref index); } - value = value / Header.NumberOfChannels; + value /= Header.NumberOfChannels; if (value < DataMinValue) DataMinValue = value; if (value > DataMaxValue) @@ -318,14 +321,14 @@ namespace Nikse.SubtitleEdit.Core DataMaxValue = int.MinValue; AllSamples = new List(); int index = 0; - while (index + Header.NumberOfChannels < Header.DataChunkSize) + while (index < Header.DataChunkSize) { int value = 0; for (int channelNumber = 0; channelNumber < Header.NumberOfChannels; channelNumber++) { value += readSampleDataValue.Invoke(ref index); } - value = value / Header.NumberOfChannels; + value /= Header.NumberOfChannels; if (value < DataMinValue) DataMinValue = value; if (value > DataMaxValue) @@ -508,6 +511,8 @@ namespace Nikse.SubtitleEdit.Core 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); @@ -604,8 +609,8 @@ namespace Nikse.SubtitleEdit.Core private class SpectrogramDrawer { - private const double raisedCosineWindowScale = 0.5; - private const int magnitudeIndexRange = 256; + private const double RaisedCosineWindowScale = 0.5; + private const int MagnitudeIndexRange = 256; private readonly int _nfft; private readonly MagnitudeToIndexMapper _mapper; @@ -619,7 +624,7 @@ namespace Nikse.SubtitleEdit.Core public SpectrogramDrawer(int nfft) { _nfft = nfft; - _mapper = new MagnitudeToIndexMapper(100.0, magnitudeIndexRange - 1); + _mapper = new MagnitudeToIndexMapper(100.0, MagnitudeIndexRange - 1); _fft = new RealFFT(nfft); _palette = GeneratePalette(); _segment = new double[nfft]; @@ -627,7 +632,7 @@ namespace Nikse.SubtitleEdit.Core _magnitude1 = new double[nfft / 2]; _magnitude2 = new double[nfft / 2]; - double scaleCorrection = 1.0 / (raisedCosineWindowScale * _fft.ForwardScaleFactor); + double scaleCorrection = 1.0 / (RaisedCosineWindowScale * _fft.ForwardScaleFactor); for (int i = 0; i < _window.Length; i++) { _window[i] *= scaleCorrection; @@ -694,18 +699,18 @@ namespace Nikse.SubtitleEdit.Core private static FastBitmap.PixelData[] GeneratePalette() { - var palette = new FastBitmap.PixelData[magnitudeIndexRange]; + var palette = new FastBitmap.PixelData[MagnitudeIndexRange]; if (Configuration.Settings.VideoControls.SpectrogramAppearance == "Classic") { - for (int colorIndex = 0; colorIndex < magnitudeIndexRange; colorIndex++) - palette[colorIndex] = new FastBitmap.PixelData(PaletteValue(colorIndex, magnitudeIndexRange)); + for (int colorIndex = 0; colorIndex < MagnitudeIndexRange; colorIndex++) + palette[colorIndex] = new FastBitmap.PixelData(PaletteValue(colorIndex, MagnitudeIndexRange)); } else { var list = SmoothColors(0, 0, 0, Configuration.Settings.VideoControls.WaveformColor.R, Configuration.Settings.VideoControls.WaveformColor.G, - Configuration.Settings.VideoControls.WaveformColor.B, magnitudeIndexRange); - for (int i = 0; i < magnitudeIndexRange; i++) + Configuration.Settings.VideoControls.WaveformColor.B, MagnitudeIndexRange); + for (int i = 0; i < MagnitudeIndexRange; i++) palette[i] = new FastBitmap.PixelData(list[i]); } return palette; diff --git a/src/Forms/AddWaveForm.Designer.cs b/src/Forms/AddWaveForm.Designer.cs index cdd01efaf..3a323e37f 100644 --- a/src/Forms/AddWaveForm.Designer.cs +++ b/src/Forms/AddWaveForm.Designer.cs @@ -138,8 +138,8 @@ this.ShowInTaskbar = false; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Generate waveform data"; - this.Shown += new System.EventHandler(this.AddWareForm_Shown); - this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.AddWareForm_KeyDown); + this.Shown += new System.EventHandler(this.AddWaveform_Shown); + this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.AddWaveform_KeyDown); this.ResumeLayout(false); this.PerformLayout(); diff --git a/src/Forms/AddWaveForm.cs b/src/Forms/AddWaveForm.cs index 701334f80..e9e932775 100644 --- a/src/Forms/AddWaveForm.cs +++ b/src/Forms/AddWaveForm.cs @@ -234,28 +234,24 @@ namespace Nikse.SubtitleEdit.Forms labelProgress.Text = Configuration.Settings.Language.AddWaveform.GeneratingPeakFile; Refresh(); - var waveFile = new WavePeakGenerator(targetFile); - - int sampleRate = Configuration.Settings.VideoControls.WaveformMinimumSampleRate; // Normally 128 - while (waveFile.Header.SampleRate % sampleRate != 0 && sampleRate < 5000) - sampleRate++; // old sample-rate / new sample-rate must have rest = 0 - - waveFile.GeneratePeakSamples(sampleRate, delayInMilliseconds); // samples per second - SampleRate - - if (Configuration.Settings.VideoControls.GenerateSpectrogram) + using (var waveFile = new WavePeakGenerator(targetFile)) { - labelProgress.Text = Configuration.Settings.Language.AddWaveform.GeneratingSpectrogram; - Refresh(); - Directory.CreateDirectory(_spectrogramDirectory); - SpectrogramBitmaps = waveFile.GenerateFourierData(256, _spectrogramDirectory, delayInMilliseconds); // image height = nfft / 2 + waveFile.GeneratePeakSamples(delayInMilliseconds); + + if (Configuration.Settings.VideoControls.GenerateSpectrogram) + { + labelProgress.Text = Configuration.Settings.Language.AddWaveform.GeneratingSpectrogram; + Refresh(); + SpectrogramBitmaps = waveFile.GenerateFourierData(256, _spectrogramDirectory, delayInMilliseconds); // image height = nfft / 2 + } + + WavePeak = waveFile; } - WavePeak = waveFile; - waveFile.Close(); labelPleaseWait.Visible = false; } - private void AddWareForm_Shown(object sender, EventArgs e) + private void AddWaveform_Shown(object sender, EventArgs e) { Refresh(); var audioTrackNames = new List(); @@ -367,7 +363,7 @@ namespace Nikse.SubtitleEdit.Forms } } - private void AddWareForm_KeyDown(object sender, KeyEventArgs e) + private void AddWaveform_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Escape) DialogResult = DialogResult.Cancel; diff --git a/src/Forms/AddWaveformBatch.cs b/src/Forms/AddWaveformBatch.cs index 04459ed04..728ddb750 100644 --- a/src/Forms/AddWaveformBatch.cs +++ b/src/Forms/AddWaveformBatch.cs @@ -211,7 +211,12 @@ namespace Nikse.SubtitleEdit.Forms while (index < listViewInputFiles.Items.Count && _abort == false) { var item = listViewInputFiles.Items[index]; - item.SubItems[3].Text = Configuration.Settings.Language.AddWaveformBatch.ExtractingAudio; + Action updateStatus = status => + { + item.SubItems[3].Text = status; + Refresh(); + }; + updateStatus(Configuration.Settings.Language.AddWaveformBatch.ExtractingAudio); string fileName = item.Text; try { @@ -285,7 +290,7 @@ namespace Nikse.SubtitleEdit.Forms } } - item.SubItems[3].Text = Configuration.Settings.Language.AddWaveformBatch.Calculating; + updateStatus(Configuration.Settings.Language.AddWaveformBatch.Calculating); MakeWaveformAndSpectrogram(fileName, targetFile, _delayInMilliseconds); // cleanup @@ -300,13 +305,13 @@ namespace Nikse.SubtitleEdit.Forms IncrementAndShowProgress(); - item.SubItems[3].Text = Configuration.Settings.Language.AddWaveformBatch.Done; + updateStatus(Configuration.Settings.Language.AddWaveformBatch.Done); } catch { IncrementAndShowProgress(); - item.SubItems[3].Text = Configuration.Settings.Language.AddWaveformBatch.Error; + updateStatus(Configuration.Settings.Language.AddWaveformBatch.Error); } index++; } @@ -322,21 +327,16 @@ namespace Nikse.SubtitleEdit.Forms private void MakeWaveformAndSpectrogram(string videoFileName, string targetFile, int delayInMilliseconds) { - var waveFile = new WavePeakGenerator(targetFile); + using (var waveFile = new WavePeakGenerator(targetFile)) + { + waveFile.GeneratePeakSamples(delayInMilliseconds); + waveFile.WritePeakSamples(Main.GetPeakWaveFileName(videoFileName)); - int sampleRate = Configuration.Settings.VideoControls.WaveformMinimumSampleRate; // Normally 128 - while (waveFile.Header.SampleRate % sampleRate != 0 && sampleRate < 5000) - sampleRate++; // old sample-rate / new sample-rate must have rest = 0 - - waveFile.GeneratePeakSamples(sampleRate, delayInMilliseconds); // samples per second - SampleRate - - //if (Configuration.Settings.VideoControls.GenerateSpectrogram) - //{ - // //Directory.CreateDirectory(_spectrogramDirectory); - // //SpectrogramBitmaps = waveFile.GenerateFourierData(256, _spectrogramDirectory, delayInMilliseconds); // image height = nfft / 2 - //} - waveFile.WritePeakSamples(Main.GetPeakWaveFileName(videoFileName)); - waveFile.Close(); + if (Configuration.Settings.VideoControls.GenerateSpectrogram) + { + waveFile.GenerateFourierData(256, Main.GetSpectrogramFolder(videoFileName), delayInMilliseconds); // image height = nfft / 2 + } + } } private void IncrementAndShowProgress() diff --git a/src/Forms/Main.cs b/src/Forms/Main.cs index 3b987037c..86cd498e1 100644 --- a/src/Forms/Main.cs +++ b/src/Forms/Main.cs @@ -14803,7 +14803,7 @@ namespace Nikse.SubtitleEdit.Forms return wavePeakName; } - private static string GetSpectrogramFolder(string videoFileName) + public static string GetSpectrogramFolder(string videoFileName) { var dir = Configuration.SpectrogramsFolder.TrimEnd(Path.DirectorySeparatorChar); if (!Directory.Exists(dir)) From f6014d7dc8ba753c915ea9c8dbf8afa7cc1aa7c3 Mon Sep 17 00:00:00 2001 From: "J.D. Purcell" Date: Sun, 13 Sep 2015 02:22:15 -0400 Subject: [PATCH 2/2] Fix waveform height calculation being 2x too high (but set default vertical zoom to 2.0 to keep the same effective look). Now you can zoom out vertically to 1.0 to see the correct height. Increase maximum vertical zoom factor to compensate for the height correction. Allow vertical zoom to be changed with control + shift + scroll wheel. --- src/Controls/AudioVisualizer.cs | 30 +++++++++++++++++++++++++----- src/Forms/Main.Designer.cs | 2 +- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/Controls/AudioVisualizer.cs b/src/Controls/AudioVisualizer.cs index b2e9643d9..e58cee463 100644 --- a/src/Controls/AudioVisualizer.cs +++ b/src/Controls/AudioVisualizer.cs @@ -133,8 +133,8 @@ namespace Nikse.SubtitleEdit.Controls } public const double VerticalZoomMinimum = 1.0; - public const double VerticalZoomMaximum = 20.0; - private double _verticalZoomFactor = 1.0; // 1.0=no zoom + public const double VerticalZoomMaximum = 40.0; + private double _verticalZoomFactor = 2.0; // 1.0=no zoom public double VerticalZoomFactor { get @@ -404,7 +404,7 @@ namespace Nikse.SubtitleEdit.Controls private static int CalculateHeight(double value, int imageHeight, int maxHeight) { double percentage = value / maxHeight; - var result = (int)Math.Round((percentage * imageHeight) + (imageHeight / 2.0)); + var result = (int)Math.Round((percentage / 2.0 + 0.5) * imageHeight); return imageHeight - result; } @@ -1690,18 +1690,28 @@ namespace Nikse.SubtitleEdit.Controls public void ZoomIn() { - ZoomFactor = ZoomFactor + 0.1; + ZoomFactor += 0.1; if (OnZoomedChanged != null) OnZoomedChanged.Invoke(this, null); } public void ZoomOut() { - ZoomFactor = ZoomFactor - 0.1; + ZoomFactor -= 0.1; if (OnZoomedChanged != null) OnZoomedChanged.Invoke(this, null); } + private void VerticalZoomIn() + { + VerticalZoomFactor *= 1.1; + } + + private void VerticalZoomOut() + { + VerticalZoomFactor /= 1.1; + } + private void WaveformMouseWheel(object sender, MouseEventArgs e) { // The scroll wheel could work in theory without the waveform loaded (it would be @@ -1720,6 +1730,16 @@ namespace Nikse.SubtitleEdit.Controls return; } + if (ModifierKeys == (Keys.Control | Keys.Shift)) + { + if (e.Delta > 0) + VerticalZoomIn(); + else + VerticalZoomOut(); + + return; + } + int delta = e.Delta; if (!MouseWheelScrollUpIsForward) delta = delta * -1; diff --git a/src/Forms/Main.Designer.cs b/src/Forms/Main.Designer.cs index cf90cdc27..ad8743efc 100644 --- a/src/Forms/Main.Designer.cs +++ b/src/Forms/Main.Designer.cs @@ -4450,7 +4450,7 @@ this.audioVisualizer.TextBold = true; this.audioVisualizer.TextColor = System.Drawing.Color.Gray; this.audioVisualizer.TextSize = 9F; - this.audioVisualizer.VerticalZoomFactor = 1D; + this.audioVisualizer.VerticalZoomFactor = 2D; this.audioVisualizer.WaveformNotLoadedText = "Click to add waveform"; this.audioVisualizer.WavePeaks = null; this.audioVisualizer.ZoomFactor = 1D;