Server/Spectrum/SpectrumBase.cs
author StephaneLenclud
Fri, 06 Jan 2017 18:27:19 +0100
changeset 276 7cd495550d5f
parent 274 920fea7a6427
child 278 2481c46d1f93
permissions -rw-r--r--
Published v1.4.3
Major hack into our spectrum math to make it prettier.
StephaneLenclud@273
     1
using System;
StephaneLenclud@273
     2
using System.Collections.Generic;
StephaneLenclud@273
     3
using System.ComponentModel;
StephaneLenclud@273
     4
using System.Diagnostics;
StephaneLenclud@273
     5
using CSCore;
StephaneLenclud@273
     6
using CSCore.DSP;
StephaneLenclud@273
     7
StephaneLenclud@273
     8
namespace Visualization
StephaneLenclud@273
     9
{
StephaneLenclud@273
    10
    public class SpectrumBase : INotifyPropertyChanged
StephaneLenclud@273
    11
    {
StephaneLenclud@273
    12
        private const int ScaleFactorLinear = 9;
StephaneLenclud@273
    13
        protected const int ScaleFactorSqr = 2;
StephaneLenclud@273
    14
        protected const double MinDbValue = -90;
StephaneLenclud@273
    15
        protected const double MaxDbValue = 0;
StephaneLenclud@273
    16
        protected const double DbScale = (MaxDbValue - MinDbValue);
StephaneLenclud@273
    17
StephaneLenclud@274
    18
StephaneLenclud@274
    19
        protected float[] iFftBuffer;
StephaneLenclud@274
    20
        private int _fftSize;        
StephaneLenclud@273
    21
        private bool _isXLogScale;
StephaneLenclud@273
    22
        private int _maxFftIndex;
StephaneLenclud@273
    23
        private int _maximumFrequency = 20000;
StephaneLenclud@273
    24
        private int _maximumFrequencyIndex;
StephaneLenclud@273
    25
        private int _minimumFrequency = 20; //Default spectrum from 20Hz to 20kHz
StephaneLenclud@273
    26
        private int _minimumFrequencyIndex;
StephaneLenclud@273
    27
        private ScalingStrategy _scalingStrategy;
StephaneLenclud@273
    28
        private int[] _spectrumIndexMax;
StephaneLenclud@273
    29
        private int[] _spectrumLogScaleIndexMax;
StephaneLenclud@273
    30
        private ISpectrumProvider _spectrumProvider;
StephaneLenclud@273
    31
StephaneLenclud@273
    32
        protected int SpectrumResolution;
StephaneLenclud@273
    33
        private bool _useAverage;
StephaneLenclud@273
    34
StephaneLenclud@273
    35
        public int MaximumFrequency
StephaneLenclud@273
    36
        {
StephaneLenclud@273
    37
            get { return _maximumFrequency; }
StephaneLenclud@273
    38
            set
StephaneLenclud@273
    39
            {
StephaneLenclud@273
    40
                if (value <= MinimumFrequency)
StephaneLenclud@273
    41
                {
StephaneLenclud@273
    42
                    throw new ArgumentOutOfRangeException("value",
StephaneLenclud@273
    43
                        "Value must not be less or equal the MinimumFrequency.");
StephaneLenclud@273
    44
                }
StephaneLenclud@273
    45
                _maximumFrequency = value;
StephaneLenclud@273
    46
                UpdateFrequencyMapping();
StephaneLenclud@273
    47
StephaneLenclud@273
    48
                RaisePropertyChanged("MaximumFrequency");
StephaneLenclud@273
    49
            }
StephaneLenclud@273
    50
        }
StephaneLenclud@273
    51
StephaneLenclud@273
    52
        public int MinimumFrequency
StephaneLenclud@273
    53
        {
StephaneLenclud@273
    54
            get { return _minimumFrequency; }
StephaneLenclud@273
    55
            set
StephaneLenclud@273
    56
            {
StephaneLenclud@273
    57
                if (value < 0)
StephaneLenclud@273
    58
                    throw new ArgumentOutOfRangeException("value");
StephaneLenclud@273
    59
                _minimumFrequency = value;
StephaneLenclud@273
    60
                UpdateFrequencyMapping();
StephaneLenclud@273
    61
StephaneLenclud@273
    62
                RaisePropertyChanged("MinimumFrequency");
StephaneLenclud@273
    63
            }
StephaneLenclud@273
    64
        }
StephaneLenclud@273
    65
StephaneLenclud@273
    66
        [BrowsableAttribute(false)]
StephaneLenclud@273
    67
        public ISpectrumProvider SpectrumProvider
StephaneLenclud@273
    68
        {
StephaneLenclud@273
    69
            get { return _spectrumProvider; }
StephaneLenclud@273
    70
            set
StephaneLenclud@273
    71
            {
StephaneLenclud@273
    72
                if (value == null)
StephaneLenclud@273
    73
                    throw new ArgumentNullException("value");
StephaneLenclud@273
    74
                _spectrumProvider = value;
StephaneLenclud@273
    75
StephaneLenclud@273
    76
                RaisePropertyChanged("SpectrumProvider");
StephaneLenclud@273
    77
            }
StephaneLenclud@273
    78
        }
StephaneLenclud@273
    79
StephaneLenclud@273
    80
        public bool IsXLogScale
StephaneLenclud@273
    81
        {
StephaneLenclud@273
    82
            get { return _isXLogScale; }
StephaneLenclud@273
    83
            set
StephaneLenclud@273
    84
            {
StephaneLenclud@273
    85
                _isXLogScale = value;
StephaneLenclud@273
    86
                UpdateFrequencyMapping();
StephaneLenclud@273
    87
                RaisePropertyChanged("IsXLogScale");
StephaneLenclud@273
    88
            }
StephaneLenclud@273
    89
        }
StephaneLenclud@273
    90
StephaneLenclud@273
    91
        public ScalingStrategy ScalingStrategy
StephaneLenclud@273
    92
        {
StephaneLenclud@273
    93
            get { return _scalingStrategy; }
StephaneLenclud@273
    94
            set
StephaneLenclud@273
    95
            {
StephaneLenclud@273
    96
                _scalingStrategy = value;
StephaneLenclud@273
    97
                RaisePropertyChanged("ScalingStrategy");
StephaneLenclud@273
    98
            }
StephaneLenclud@273
    99
        }
StephaneLenclud@273
   100
StephaneLenclud@273
   101
        public bool UseAverage
StephaneLenclud@273
   102
        {
StephaneLenclud@273
   103
            get { return _useAverage; }
StephaneLenclud@273
   104
            set
StephaneLenclud@273
   105
            {
StephaneLenclud@273
   106
                _useAverage = value;
StephaneLenclud@273
   107
                RaisePropertyChanged("UseAverage");
StephaneLenclud@273
   108
            }
StephaneLenclud@273
   109
        }
StephaneLenclud@273
   110
StephaneLenclud@273
   111
        [BrowsableAttribute(false)]
StephaneLenclud@273
   112
        public FftSize FftSize
StephaneLenclud@273
   113
        {
StephaneLenclud@273
   114
            get { return (FftSize) _fftSize; }
StephaneLenclud@273
   115
            protected set
StephaneLenclud@273
   116
            {
StephaneLenclud@273
   117
                if ((int) Math.Log((int) value, 2) % 1 != 0)
StephaneLenclud@273
   118
                    throw new ArgumentOutOfRangeException("value");
StephaneLenclud@273
   119
StephaneLenclud@273
   120
                _fftSize = (int) value;
StephaneLenclud@273
   121
                _maxFftIndex = _fftSize / 2 - 1;
StephaneLenclud@274
   122
                iFftBuffer = new float[_fftSize];
StephaneLenclud@273
   123
StephaneLenclud@273
   124
                RaisePropertyChanged("FFTSize");
StephaneLenclud@273
   125
            }
StephaneLenclud@273
   126
        }
StephaneLenclud@273
   127
StephaneLenclud@273
   128
        public event PropertyChangedEventHandler PropertyChanged;
StephaneLenclud@273
   129
StephaneLenclud@273
   130
        protected virtual void UpdateFrequencyMapping()
StephaneLenclud@273
   131
        {
StephaneLenclud@273
   132
            _maximumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MaximumFrequency) + 1, _maxFftIndex);
StephaneLenclud@273
   133
            _minimumFrequencyIndex = Math.Min(_spectrumProvider.GetFftBandIndex(MinimumFrequency), _maxFftIndex);
StephaneLenclud@273
   134
StephaneLenclud@273
   135
            int actualResolution = SpectrumResolution;
StephaneLenclud@273
   136
StephaneLenclud@273
   137
            int indexCount = _maximumFrequencyIndex - _minimumFrequencyIndex;
StephaneLenclud@273
   138
            double linearIndexBucketSize = Math.Round(indexCount / (double) actualResolution, 3);
StephaneLenclud@273
   139
StephaneLenclud@273
   140
            _spectrumIndexMax = _spectrumIndexMax.CheckBuffer(actualResolution, true);
StephaneLenclud@273
   141
            _spectrumLogScaleIndexMax = _spectrumLogScaleIndexMax.CheckBuffer(actualResolution, true);
StephaneLenclud@273
   142
StephaneLenclud@273
   143
            double maxLog = Math.Log(actualResolution, actualResolution);
StephaneLenclud@273
   144
            for (int i = 1; i < actualResolution; i++)
StephaneLenclud@273
   145
            {
StephaneLenclud@273
   146
                int logIndex =
StephaneLenclud@273
   147
                    (int) ((maxLog - Math.Log((actualResolution + 1) - i, (actualResolution + 1))) * indexCount) +
StephaneLenclud@273
   148
                    _minimumFrequencyIndex;
StephaneLenclud@273
   149
StephaneLenclud@273
   150
                _spectrumIndexMax[i - 1] = _minimumFrequencyIndex + (int) (i * linearIndexBucketSize);
StephaneLenclud@273
   151
                _spectrumLogScaleIndexMax[i - 1] = logIndex;
StephaneLenclud@273
   152
            }
StephaneLenclud@273
   153
StephaneLenclud@273
   154
            if (actualResolution > 0)
StephaneLenclud@273
   155
            {
StephaneLenclud@273
   156
                _spectrumIndexMax[_spectrumIndexMax.Length - 1] =
StephaneLenclud@273
   157
                    _spectrumLogScaleIndexMax[_spectrumLogScaleIndexMax.Length - 1] = _maximumFrequencyIndex;
StephaneLenclud@273
   158
            }
StephaneLenclud@273
   159
        }
StephaneLenclud@273
   160
StephaneLenclud@273
   161
        protected virtual SpectrumPointData[] CalculateSpectrumPoints(double maxValue, float[] fftBuffer)
StephaneLenclud@273
   162
        {
StephaneLenclud@273
   163
            var dataPoints = new List<SpectrumPointData>();
StephaneLenclud@273
   164
StephaneLenclud@273
   165
            double value0 = 0, value = 0;
StephaneLenclud@273
   166
            double lastValue = 0;
StephaneLenclud@273
   167
            double actualMaxValue = maxValue;
StephaneLenclud@273
   168
            int spectrumPointIndex = 0;
StephaneLenclud@273
   169
StephaneLenclud@276
   170
            int b0 = _minimumFrequencyIndex;
StephaneLenclud@276
   171
            int x, y;
StephaneLenclud@276
   172
            for (x = 0; x < SpectrumResolution; x++)
StephaneLenclud@276
   173
            {
StephaneLenclud@276
   174
                float peak = 0;
StephaneLenclud@276
   175
                //SL: We should have to compute that only once for a given resolution.
StephaneLenclud@276
   176
                // Try using the existing pre-computed array we have and refactor that whole mess.
StephaneLenclud@276
   177
                int b1 = (int)Math.Pow(2, x * 10.0 / (SpectrumResolution - 1)); 
StephaneLenclud@276
   178
                //int b1 = (IsXLogScale ? _spectrumLogScaleIndexMax[x] : _spectrumIndexMax[x]);
StephaneLenclud@276
   179
                if (b1 > _maximumFrequencyIndex) b1 = _maximumFrequencyIndex;
StephaneLenclud@276
   180
                if (b1 <= b0) b1 = b0 + 1;
StephaneLenclud@276
   181
                for (; b0 < b1; b0++)
StephaneLenclud@276
   182
                {
StephaneLenclud@276
   183
                    if (peak < fftBuffer[1 + b0]) peak = fftBuffer[1 + b0];
StephaneLenclud@276
   184
                }
StephaneLenclud@276
   185
                
StephaneLenclud@276
   186
                const int KHeightScaleFactor = 5; // SL: You can play with that scale factor to tune the height of the bars
StephaneLenclud@276
   187
                y = (int)(Math.Sqrt(peak) * KHeightScaleFactor * maxValue); 
StephaneLenclud@276
   188
                if (y > maxValue) y = (int)maxValue;
StephaneLenclud@276
   189
                if (y < 0) y = 0;
StephaneLenclud@276
   190
StephaneLenclud@276
   191
                dataPoints.Add(new SpectrumPointData { SpectrumPointIndex = x, Value = y });
StephaneLenclud@276
   192
                //_spectrumdata.Add((byte)y);
StephaneLenclud@276
   193
                //Console.Write("{0, 3} ", y);
StephaneLenclud@276
   194
            }
StephaneLenclud@276
   195
StephaneLenclud@276
   196
            /*
StephaneLenclud@273
   197
            for (int i = _minimumFrequencyIndex; i <= _maximumFrequencyIndex; i++)
StephaneLenclud@273
   198
            {
StephaneLenclud@273
   199
                switch (ScalingStrategy)
StephaneLenclud@273
   200
                {
StephaneLenclud@273
   201
                    case ScalingStrategy.Decibel:
StephaneLenclud@273
   202
                        value0 = (((20 * Math.Log10(fftBuffer[i])) - MinDbValue) / DbScale) * actualMaxValue;
StephaneLenclud@273
   203
                        break;
StephaneLenclud@273
   204
                    case ScalingStrategy.Linear:
StephaneLenclud@273
   205
                        value0 = (fftBuffer[i] * ScaleFactorLinear) * actualMaxValue;
StephaneLenclud@273
   206
                        break;
StephaneLenclud@273
   207
                    case ScalingStrategy.Sqrt:
StephaneLenclud@273
   208
                        value0 = ((Math.Sqrt(fftBuffer[i])) * ScaleFactorSqr) * actualMaxValue;
StephaneLenclud@273
   209
                        break;
StephaneLenclud@273
   210
                }
StephaneLenclud@273
   211
StephaneLenclud@273
   212
                bool recalc = true;
StephaneLenclud@273
   213
StephaneLenclud@273
   214
                value = Math.Max(0, Math.Max(value0, value));
StephaneLenclud@273
   215
StephaneLenclud@273
   216
                while (spectrumPointIndex <= _spectrumIndexMax.Length - 1 &&
StephaneLenclud@273
   217
                       i ==
StephaneLenclud@273
   218
                       (IsXLogScale
StephaneLenclud@273
   219
                           ? _spectrumLogScaleIndexMax[spectrumPointIndex]
StephaneLenclud@273
   220
                           : _spectrumIndexMax[spectrumPointIndex]))
StephaneLenclud@273
   221
                {
StephaneLenclud@273
   222
                    if (!recalc)
StephaneLenclud@273
   223
                        value = lastValue;
StephaneLenclud@273
   224
StephaneLenclud@273
   225
                    if (value > maxValue)
StephaneLenclud@273
   226
                        value = maxValue;
StephaneLenclud@273
   227
StephaneLenclud@273
   228
                    if (_useAverage && spectrumPointIndex > 0)
StephaneLenclud@273
   229
                        value = (lastValue + value) / 2.0;
StephaneLenclud@273
   230
StephaneLenclud@273
   231
                    dataPoints.Add(new SpectrumPointData {SpectrumPointIndex = spectrumPointIndex, Value = value});
StephaneLenclud@273
   232
StephaneLenclud@273
   233
                    lastValue = value;
StephaneLenclud@273
   234
                    value = 0.0;
StephaneLenclud@273
   235
                    spectrumPointIndex++;
StephaneLenclud@273
   236
                    recalc = false;
StephaneLenclud@273
   237
                }
StephaneLenclud@273
   238
StephaneLenclud@273
   239
                //value = 0;
StephaneLenclud@273
   240
            }
StephaneLenclud@276
   241
            */
StephaneLenclud@276
   242
StephaneLenclud@273
   243
StephaneLenclud@273
   244
            return dataPoints.ToArray();
StephaneLenclud@273
   245
        }
StephaneLenclud@273
   246
StephaneLenclud@273
   247
        protected void RaisePropertyChanged(string propertyName)
StephaneLenclud@273
   248
        {
StephaneLenclud@273
   249
            if (PropertyChanged != null && !String.IsNullOrEmpty(propertyName))
StephaneLenclud@273
   250
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
StephaneLenclud@273
   251
        }
StephaneLenclud@273
   252
StephaneLenclud@273
   253
        [DebuggerDisplay("{Value}")]
StephaneLenclud@273
   254
        protected struct SpectrumPointData
StephaneLenclud@273
   255
        {
StephaneLenclud@273
   256
            public int SpectrumPointIndex;
StephaneLenclud@273
   257
            public double Value;
StephaneLenclud@273
   258
        }
StephaneLenclud@273
   259
    }
StephaneLenclud@273
   260
}