RSeries astromech firmware
AudioFrequencyBitmap.h
Go to the documentation of this file.
1 #include <complex>
2 #include <algorithm>
3 
4 #define CTFFT_MAXBITS 8
5 #include "CTFFT.h"
6 
7 #ifndef MEM_BARRIER
8 #define MEM_BARRIER()
9 #endif
10 
12 
14 {
15 public:
16  static constexpr size_t kFFTSize = 256;
17  static constexpr unsigned kInputBufferSize = kFFTSize * 2;
18  static constexpr double kDefaultSmoothingTimeConstant = 0.8;
19  static constexpr double kDefaultMinDecibels = -100;
20  static constexpr double kDefaultMaxDecibels = -30;
21 
23  fWriteIndex(0),
24  fSmoothingTimeConstant(kDefaultSmoothingTimeConstant),
25  fMinDecibels(kDefaultMinDecibels),
26  fMaxDecibels(kDefaultMaxDecibels)
27  {
28  memset(fSampleBuffer, '\0', sizeof(fSampleBuffer));
29  memset(fMagnitudeBuffer, '\0', sizeof(fMagnitudeBuffer));
30  }
31 
32  // sampleCount must be power of two
33  void writeInput(const int16_t* samples, size_t sampleCount)
34  {
35  // convert int16_t data to float
36  float* dst = &fSampleBuffer[fWriteIndex];
37  for (size_t i = 0; i < sampleCount; i++)
38  {
39  dst[i] = ((float)samples[i*2]) / 32768.0f;
40  }
41 
42  fWriteIndex += sampleCount;
43  if (fWriteIndex >= kInputBufferSize)
44  fWriteIndex = 0;
45  }
46 
47  bool ready()
48  {
49  return (fWriteIndex > 0 && (fWriteIndex % kFFTSize) == 0);
50  }
51 
52  unsigned getFrequencyBinCount() const
53  {
54  return kFFTSize / 2;
55  }
56 
57  void getByteFrequencyData(uint8_t* destArray, size_t destArraySize)
58  {
59  if (!destArray)
60  return;
61 
62  doFFTAnalysis();
63 
64  // Convert from linear magnitude to unsigned-byte decibels.
65  size_t len = std::min(magnitudeBufferLength(), destArraySize);
66  if (len > 0)
67  {
68  const double minDecibels = fMinDecibels;
69  const double rangeScaleFactor = (fMaxDecibels != minDecibels) ? 1 / (fMaxDecibels - minDecibels) : 1;
70 
71  const float* source = fMagnitudeBuffer;
72  for (unsigned i = 0; i < len; i++)
73  {
74  float linearValue = source[i];
75  double dbMag = (linearValue != 0) ? linearToDecibels(linearValue) : minDecibels;
76 
77  // The range m_minDecibels to m_maxDecibels will be scaled to byte values from 0 to UCHAR_MAX.
78  double scaledValue = UCHAR_MAX * (dbMag - minDecibels) * rangeScaleFactor;
79 
80  // Clip to valid range.
81  if (scaledValue < 0)
82  scaledValue = 0;
83  if (scaledValue > UCHAR_MAX)
84  scaledValue = UCHAR_MAX;
85 
86  destArray[i] = (uint8_t)scaledValue;
87  }
88  }
89  }
90 
91 private:
92  float fSampleBuffer[kInputBufferSize];
93  float fMagnitudeBuffer[kFFTSize];
94  unsigned fWriteIndex;
95  double fSmoothingTimeConstant;
96  double fMinDecibels;
97  double fMaxDecibels;
99 
100  size_t sampleBufferLength() const
101  {
102  return sizeof(fSampleBuffer) / sizeof(fSampleBuffer[0]);
103  }
104 
105  size_t magnitudeBufferLength() const
106  {
107  return sizeof(fMagnitudeBuffer) / sizeof(fMagnitudeBuffer[0]);
108  }
109 
110  // linearSample must be non-zero
111  static float linearToDecibels(float linearSample)
112  {
113  return 20 * log10f(linearSample);
114  }
115 
116  void applyWindow(float* p, size_t n)
117  {
118  static constexpr double twoPiDouble = M_PI * 2.0;
119 
120  // Blackman window
121  double alpha = 0.16;
122  double a0 = 0.5 * (1 - alpha);
123  double a1 = 0.5;
124  double a2 = 0.5 * alpha;
125 
126  for (unsigned i = 0; i < n; ++i)
127  {
128  double x = (double)i / (double)n;
129  double window = a0 - a1 * cos(twoPiDouble * x) + a2 * cos(twoPiDouble * 2.0 * x);
130  p[i] *= double(window);
131  }
132  }
133 
134  void doFFTAnalysis()
135  {
136  float tempBuffer[kFFTSize];
137  float* inputBuffer = fSampleBuffer;
138  float* tempP = tempBuffer;
139 
140  // Take the previous fftSize values from the input buffer and copy into the temporary buffer.
141  unsigned writeIndex = fWriteIndex;
142  if (writeIndex < kFFTSize)
143  {
144  memcpy(tempP, inputBuffer + writeIndex - kFFTSize + kInputBufferSize, sizeof(*tempP) * (kFFTSize - writeIndex));
145  memcpy(tempP + kFFTSize - writeIndex, inputBuffer, sizeof(*tempP) * writeIndex);
146  }
147  else
148  {
149  memcpy(tempP, inputBuffer + writeIndex - kFFTSize, sizeof(*tempP) * kFFTSize);
150  }
151 
152  // Window the input samples.
153  applyWindow(tempP, kFFTSize);
154 
155  // Do the analysis.
156  fAnalysisFrame.calculate(tempP);
157 
158  // Blow away the packed nyquist component.
159  tempP[1] = 0;
160 
161  // Normalize so that an input sine wave at 0dBfs registers as 0dBfs (undo FFT scaling factor).
162  const double magnitudeScale = 1.0 / kFFTSize;
163 
164  // A value of 0 does no averaging with the previous result. Larger values produce slower, but smoother changes.
165  double k = fSmoothingTimeConstant;
166  k = std::max(0.0, k);
167  k = std::min(1.0, k);
168 
169  // Convert the analysis data from complex to magnitude and average with the previous result.
170  float* destination = fMagnitudeBuffer;
171  size_t n = magnitudeBufferLength();
172  for (size_t i = 0; i < n; ++i)
173  {
174  std::complex<double> c(tempP[i*2], tempP[i*2+1]);
175  double scalarMagnitude = abs(c) * magnitudeScale;
176  destination[i] = float(k * destination[i] + (1 - k) * scalarMagnitude);
177  }
178  }
179 };
180 
182 
184 {
185 public:
187  {
188  if (pixels != nullptr)
189  memset(pixels, '\0', width * height);
190  }
191 
192  void processSamples(unsigned numBits, unsigned numChannels, const int16_t* samples, int sampleCount)
193  {
194  // static bool printed;
195  // if (!printed)
196  // {
197  // Serial.println("numBits: "+String(numBits));
198  // Serial.println("numChannels: "+String(numChannels));
199  // Serial.println("sampleCount: "+String(sampleCount));
200  // printed = true;
201  // }
202  // // Ignore frames that are not 640 samples
203  if (sampleCount == 1024)
204  {
205  while (sampleCount > 0)
206  {
207  analyser.writeInput(samples, 256);
208  samples += 256;
209  sampleCount -= 256;
210  }
211  }
212  else if (sampleCount == 256)
213  {
214  analyser.writeInput(samples, 256);
215  }
216  else if (sampleCount == 640)
217  {
218  // Break 640 samples into 128 samples and apply to analyser
219  while (sampleCount > 0)
220  {
221  analyser.writeInput(samples, 128);
222  samples += 128;
223  sampleCount -= 128;
224  }
225  }
226  else if (sampleCount == 1152)
227  {
228  // Break 640 samples into 128 samples and apply to analyser
229  while (sampleCount > 0)
230  {
231  analyser.writeInput(samples, 8);
232  samples += 8;
233  sampleCount -= 8;
234  }
235  }
236  else if (sampleCount == 0)
237  {
238  sampleCount = 1024;
239  while (sampleCount > 0)
240  {
241  analyser.writeInput(samples, 256);
242  samples += 256;
243  sampleCount -= 256;
244  }
245  }
246  else
247  {
248  Serial.println(sampleCount);
249  }
250  if (analyser.ready())
251  {
252  analyser.getByteFrequencyData(freq, width);
253  memmove(&pixels[width], pixels, width * (4*60-1));
254  memcpy(pixels, freq, width);
255  updated = true;
256  MEM_BARRIER();
257  }
258  }
259 
260  uint8_t* getPixels() { return pixels; }
261  unsigned getWidth() { return width; }
262  unsigned getHeight() { return height; }
263  uint8_t get(float x, float y)
264  {
265  return (x >= 0.0f && x <= 1.0f && y >= 0.0f && y <= 1.0f) ? pixels[unsigned((3*y) * width + (x*width))] : 0;
266  }
267  uint8_t get(unsigned x, unsigned y)
268  {
269  return (x < width && y < height) ? pixels[y * width + x] : 0;
270  }
271  bool isUpdated()
272  {
273  if (updated)
274  {
275  updated = false;
276  MEM_BARRIER();
277  return true;
278  }
279  return false;
280  }
281 
282 private:
283  RealtimeAnalyser analyser;
284  unsigned width = analyser.getFrequencyBinCount();
285  unsigned height = 4*60;
286  bool updated = false;
287  uint8_t* pixels = (uint8_t*)malloc(width * height);
288  uint8_t* freq = new uint8_t[width];
289 };
RealtimeAnalyser::kDefaultMinDecibels
static constexpr double kDefaultMinDecibels
Definition: AudioFrequencyBitmap.h:19
RealtimeAnalyser::kDefaultSmoothingTimeConstant
static constexpr double kDefaultSmoothingTimeConstant
Definition: AudioFrequencyBitmap.h:18
RealtimeAnalyser::getFrequencyBinCount
unsigned getFrequencyBinCount() const
Definition: AudioFrequencyBitmap.h:52
RealtimeAnalyser::RealtimeAnalyser
RealtimeAnalyser()
Definition: AudioFrequencyBitmap.h:22
RealtimeAnalyser::kDefaultMaxDecibels
static constexpr double kDefaultMaxDecibels
Definition: AudioFrequencyBitmap.h:20
RealtimeAnalyser::getByteFrequencyData
void getByteFrequencyData(uint8_t *destArray, size_t destArraySize)
Definition: AudioFrequencyBitmap.h:57
MEM_BARRIER
#define MEM_BARRIER()
Definition: AudioFrequencyBitmap.h:8
AudioFrequencyBitmap::processSamples
void processSamples(unsigned numBits, unsigned numChannels, const int16_t *samples, int sampleCount)
Definition: AudioFrequencyBitmap.h:192
RealtimeAnalyser
Definition: AudioFrequencyBitmap.h:13
AudioFrequencyBitmap::get
uint8_t get(float x, float y)
Definition: AudioFrequencyBitmap.h:263
AudioFrequencyBitmap::AudioFrequencyBitmap
AudioFrequencyBitmap()
Definition: AudioFrequencyBitmap.h:186
CTFFT::RealDiscrete< float, kFFTSize >
RealtimeAnalyser::ready
bool ready()
Definition: AudioFrequencyBitmap.h:47
RealtimeAnalyser::writeInput
void writeInput(const int16_t *samples, size_t sampleCount)
Definition: AudioFrequencyBitmap.h:33
AudioFrequencyBitmap::isUpdated
bool isUpdated()
Definition: AudioFrequencyBitmap.h:271
CTFFT.h
AudioFrequencyBitmap::getWidth
unsigned getWidth()
Definition: AudioFrequencyBitmap.h:261
CTFFT::RealDiscrete::calculate
void calculate(T *data)
Definition: CTFFT.h:542
AudioFrequencyBitmap::getHeight
unsigned getHeight()
Definition: AudioFrequencyBitmap.h:262
RealtimeAnalyser::kInputBufferSize
static constexpr unsigned kInputBufferSize
Definition: AudioFrequencyBitmap.h:17
AudioFrequencyBitmap::getPixels
uint8_t * getPixels()
Definition: AudioFrequencyBitmap.h:260
AudioFrequencyBitmap::get
uint8_t get(unsigned x, unsigned y)
Definition: AudioFrequencyBitmap.h:267
AudioFrequencyBitmap
Definition: AudioFrequencyBitmap.h:183
RealtimeAnalyser::kFFTSize
static constexpr size_t kFFTSize
Definition: AudioFrequencyBitmap.h:16