StephaneLenclud@273: using System; StephaneLenclud@273: using System.Collections.Generic; StephaneLenclud@273: using System.ComponentModel; StephaneLenclud@273: using System.Diagnostics; StephaneLenclud@273: using CSCore; StephaneLenclud@273: using CSCore.DSP; StephaneLenclud@273: StephaneLenclud@273: namespace Visualization StephaneLenclud@273: { StephaneLenclud@273: public class SpectrumBase : INotifyPropertyChanged StephaneLenclud@273: { StephaneLenclud@273: private const int ScaleFactorLinear = 9; StephaneLenclud@273: protected const int ScaleFactorSqr = 2; StephaneLenclud@273: protected const double MinDbValue = -90; StephaneLenclud@273: protected const double MaxDbValue = 0; StephaneLenclud@273: protected const double DbScale = (MaxDbValue - MinDbValue); StephaneLenclud@273: StephaneLenclud@274: StephaneLenclud@274: protected float[] iFftBuffer; StephaneLenclud@274: private int _fftSize; StephaneLenclud@273: private bool _isXLogScale; StephaneLenclud@273: private int _maxFftIndex; StephaneLenclud@273: private int _maximumFrequency = 20000; StephaneLenclud@273: private int _maximumFrequencyIndex; StephaneLenclud@273: private int _minimumFrequency = 20; //Default spectrum from 20Hz to 20kHz StephaneLenclud@273: private int _minimumFrequencyIndex; StephaneLenclud@273: private ScalingStrategy _scalingStrategy; StephaneLenclud@273: private int[] _spectrumIndexMax; StephaneLenclud@273: private int[] _spectrumLogScaleIndexMax; StephaneLenclud@273: private ISpectrumProvider _spectrumProvider; StephaneLenclud@273: StephaneLenclud@273: protected int SpectrumResolution; StephaneLenclud@273: private bool _useAverage; StephaneLenclud@273: StephaneLenclud@273: public int MaximumFrequency StephaneLenclud@273: { StephaneLenclud@273: get { return _maximumFrequency; } StephaneLenclud@273: set StephaneLenclud@273: { StephaneLenclud@273: if (value <= MinimumFrequency) StephaneLenclud@273: { StephaneLenclud@273: throw new ArgumentOutOfRangeException("value", StephaneLenclud@273: "Value must not be less or equal the MinimumFrequency."); StephaneLenclud@273: } StephaneLenclud@273: _maximumFrequency = value; StephaneLenclud@273: UpdateFrequencyMapping(); StephaneLenclud@273: StephaneLenclud@273: RaisePropertyChanged("MaximumFrequency"); StephaneLenclud@273: } StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: public int MinimumFrequency StephaneLenclud@273: { StephaneLenclud@273: get { return _minimumFrequency; } StephaneLenclud@273: set StephaneLenclud@273: { StephaneLenclud@273: if (value < 0) StephaneLenclud@273: throw new ArgumentOutOfRangeException("value"); StephaneLenclud@273: _minimumFrequency = value; StephaneLenclud@273: UpdateFrequencyMapping(); StephaneLenclud@273: StephaneLenclud@273: RaisePropertyChanged("MinimumFrequency"); StephaneLenclud@273: } StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: [BrowsableAttribute(false)] StephaneLenclud@273: public ISpectrumProvider SpectrumProvider StephaneLenclud@273: { StephaneLenclud@273: get { return _spectrumProvider; } StephaneLenclud@273: set StephaneLenclud@273: { StephaneLenclud@273: if (value == null) StephaneLenclud@273: throw new ArgumentNullException("value"); StephaneLenclud@273: _spectrumProvider = value; StephaneLenclud@273: StephaneLenclud@273: RaisePropertyChanged("SpectrumProvider"); StephaneLenclud@273: } StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: public bool IsXLogScale StephaneLenclud@273: { StephaneLenclud@273: get { return _isXLogScale; } StephaneLenclud@273: set StephaneLenclud@273: { StephaneLenclud@273: _isXLogScale = value; StephaneLenclud@273: UpdateFrequencyMapping(); StephaneLenclud@273: RaisePropertyChanged("IsXLogScale"); StephaneLenclud@273: } StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: public ScalingStrategy ScalingStrategy StephaneLenclud@273: { StephaneLenclud@273: get { return _scalingStrategy; } StephaneLenclud@273: set StephaneLenclud@273: { StephaneLenclud@273: _scalingStrategy = value; StephaneLenclud@273: RaisePropertyChanged("ScalingStrategy"); StephaneLenclud@273: } StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: public bool UseAverage StephaneLenclud@273: { StephaneLenclud@273: get { return _useAverage; } StephaneLenclud@273: set StephaneLenclud@273: { StephaneLenclud@273: _useAverage = value; StephaneLenclud@273: RaisePropertyChanged("UseAverage"); StephaneLenclud@273: } StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: [BrowsableAttribute(false)] StephaneLenclud@273: public FftSize FftSize StephaneLenclud@273: { StephaneLenclud@273: get { return (FftSize) _fftSize; } StephaneLenclud@273: protected set StephaneLenclud@273: { StephaneLenclud@273: if ((int) Math.Log((int) value, 2) % 1 != 0) StephaneLenclud@273: throw new ArgumentOutOfRangeException("value"); StephaneLenclud@273: StephaneLenclud@273: _fftSize = (int) value; StephaneLenclud@273: _maxFftIndex = _fftSize / 2 - 1; StephaneLenclud@274: iFftBuffer = new float[_fftSize]; StephaneLenclud@273: StephaneLenclud@273: RaisePropertyChanged("FFTSize"); StephaneLenclud@273: } StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: public event PropertyChangedEventHandler PropertyChanged; StephaneLenclud@273: StephaneLenclud@273: protected virtual void UpdateFrequencyMapping() StephaneLenclud@273: { StephaneLenclud@273: _maximumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MaximumFrequency) + 1, _maxFftIndex); StephaneLenclud@273: _minimumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MinimumFrequency), _maxFftIndex); StephaneLenclud@273: StephaneLenclud@273: int actualResolution = SpectrumResolution; StephaneLenclud@273: StephaneLenclud@273: int indexCount = _maximumFrequencyIndex - _minimumFrequencyIndex; StephaneLenclud@273: double linearIndexBucketSize = Math.Round(indexCount / (double) actualResolution, 3); StephaneLenclud@273: StephaneLenclud@273: _spectrumIndexMax = _spectrumIndexMax.CheckBuffer(actualResolution, true); StephaneLenclud@273: _spectrumLogScaleIndexMax = _spectrumLogScaleIndexMax.CheckBuffer(actualResolution, true); StephaneLenclud@273: StephaneLenclud@273: double maxLog = Math.Log(actualResolution, actualResolution); StephaneLenclud@273: for (int i = 1; i < actualResolution; i++) StephaneLenclud@273: { StephaneLenclud@273: int logIndex = StephaneLenclud@273: (int) ((maxLog - Math.Log((actualResolution + 1) - i, (actualResolution + 1))) * indexCount) + StephaneLenclud@273: _minimumFrequencyIndex; StephaneLenclud@273: StephaneLenclud@273: _spectrumIndexMax[i - 1] = _minimumFrequencyIndex + (int) (i * linearIndexBucketSize); StephaneLenclud@273: _spectrumLogScaleIndexMax[i - 1] = logIndex; StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: if (actualResolution > 0) StephaneLenclud@273: { StephaneLenclud@273: _spectrumIndexMax[_spectrumIndexMax.Length - 1] = StephaneLenclud@273: _spectrumLogScaleIndexMax[_spectrumLogScaleIndexMax.Length - 1] = _maximumFrequencyIndex; StephaneLenclud@273: } StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: protected virtual SpectrumPointData[] CalculateSpectrumPoints(double maxValue, float[] fftBuffer) StephaneLenclud@273: { StephaneLenclud@273: var dataPoints = new List(); StephaneLenclud@273: StephaneLenclud@278: //double value0 = 0, value = 0; StephaneLenclud@278: //double lastValue = 0; StephaneLenclud@278: //double actualMaxValue = maxValue; StephaneLenclud@278: //int spectrumPointIndex = 0; StephaneLenclud@273: StephaneLenclud@276: int b0 = _minimumFrequencyIndex; StephaneLenclud@276: int x, y; StephaneLenclud@276: for (x = 0; x < SpectrumResolution; x++) StephaneLenclud@276: { StephaneLenclud@276: float peak = 0; StephaneLenclud@276: //SL: We should have to compute that only once for a given resolution. StephaneLenclud@276: // Try using the existing pre-computed array we have and refactor that whole mess. StephaneLenclud@276: int b1 = (int)Math.Pow(2, x * 10.0 / (SpectrumResolution - 1)); StephaneLenclud@276: //int b1 = (IsXLogScale ? _spectrumLogScaleIndexMax[x] : _spectrumIndexMax[x]); StephaneLenclud@276: if (b1 > _maximumFrequencyIndex) b1 = _maximumFrequencyIndex; StephaneLenclud@276: if (b1 <= b0) b1 = b0 + 1; StephaneLenclud@276: for (; b0 < b1; b0++) StephaneLenclud@276: { StephaneLenclud@276: if (peak < fftBuffer[1 + b0]) peak = fftBuffer[1 + b0]; StephaneLenclud@276: } StephaneLenclud@276: StephaneLenclud@276: const int KHeightScaleFactor = 5; // SL: You can play with that scale factor to tune the height of the bars StephaneLenclud@276: y = (int)(Math.Sqrt(peak) * KHeightScaleFactor * maxValue); StephaneLenclud@276: if (y > maxValue) y = (int)maxValue; StephaneLenclud@276: if (y < 0) y = 0; StephaneLenclud@276: StephaneLenclud@276: dataPoints.Add(new SpectrumPointData { SpectrumPointIndex = x, Value = y }); StephaneLenclud@276: //_spectrumdata.Add((byte)y); StephaneLenclud@276: //Console.Write("{0, 3} ", y); StephaneLenclud@276: } StephaneLenclud@276: StephaneLenclud@276: /* StephaneLenclud@273: for (int i = _minimumFrequencyIndex; i <= _maximumFrequencyIndex; i++) StephaneLenclud@273: { StephaneLenclud@273: switch (ScalingStrategy) StephaneLenclud@273: { StephaneLenclud@273: case ScalingStrategy.Decibel: StephaneLenclud@273: value0 = (((20 * Math.Log10(fftBuffer[i])) - MinDbValue) / DbScale) * actualMaxValue; StephaneLenclud@273: break; StephaneLenclud@273: case ScalingStrategy.Linear: StephaneLenclud@273: value0 = (fftBuffer[i] * ScaleFactorLinear) * actualMaxValue; StephaneLenclud@273: break; StephaneLenclud@273: case ScalingStrategy.Sqrt: StephaneLenclud@273: value0 = ((Math.Sqrt(fftBuffer[i])) * ScaleFactorSqr) * actualMaxValue; StephaneLenclud@273: break; StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: bool recalc = true; StephaneLenclud@273: StephaneLenclud@273: value = Math.Max(0, Math.Max(value0, value)); StephaneLenclud@273: StephaneLenclud@273: while (spectrumPointIndex <= _spectrumIndexMax.Length - 1 && StephaneLenclud@273: i == StephaneLenclud@273: (IsXLogScale StephaneLenclud@273: ? _spectrumLogScaleIndexMax[spectrumPointIndex] StephaneLenclud@273: : _spectrumIndexMax[spectrumPointIndex])) StephaneLenclud@273: { StephaneLenclud@273: if (!recalc) StephaneLenclud@273: value = lastValue; StephaneLenclud@273: StephaneLenclud@273: if (value > maxValue) StephaneLenclud@273: value = maxValue; StephaneLenclud@273: StephaneLenclud@273: if (_useAverage && spectrumPointIndex > 0) StephaneLenclud@273: value = (lastValue + value) / 2.0; StephaneLenclud@273: StephaneLenclud@273: dataPoints.Add(new SpectrumPointData {SpectrumPointIndex = spectrumPointIndex, Value = value}); StephaneLenclud@273: StephaneLenclud@273: lastValue = value; StephaneLenclud@273: value = 0.0; StephaneLenclud@273: spectrumPointIndex++; StephaneLenclud@273: recalc = false; StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: //value = 0; StephaneLenclud@273: } StephaneLenclud@276: */ StephaneLenclud@276: StephaneLenclud@273: StephaneLenclud@273: return dataPoints.ToArray(); StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: protected void RaisePropertyChanged(string propertyName) StephaneLenclud@273: { StephaneLenclud@273: if (PropertyChanged != null && !String.IsNullOrEmpty(propertyName)) StephaneLenclud@273: PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); StephaneLenclud@273: } StephaneLenclud@273: StephaneLenclud@273: [DebuggerDisplay("{Value}")] StephaneLenclud@273: protected struct SpectrumPointData StephaneLenclud@273: { StephaneLenclud@273: public int SpectrumPointIndex; StephaneLenclud@273: public double Value; StephaneLenclud@273: } StephaneLenclud@273: } StephaneLenclud@273: }