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 |
} |