|
@@ -14,11 +14,6 @@
|
14
|
14
|
//
|
15
|
15
|
|
16
|
16
|
#ifdef DEBUG
|
17
|
|
-#define DEBUG_PLOT_FFT
|
18
|
|
-//#define DEBUG_PLOT_FFT_RAW
|
19
|
|
-#endif
|
20
|
|
-
|
21
|
|
-#ifdef DEBUG_PLOT_FFT
|
22
|
17
|
#define DEBUG_LOG_BEATS
|
23
|
18
|
#endif
|
24
|
19
|
|
|
@@ -26,10 +21,7 @@
|
26
|
21
|
#import "AppDelegate.h"
|
27
|
22
|
|
28
|
23
|
#import "EZAudioFFT.h"
|
29
|
|
-
|
30
|
|
-#ifdef DEBUG_PLOT_FFT
|
31
|
24
|
#import "EZAudioPlot.h"
|
32
|
|
-#endif
|
33
|
25
|
|
34
|
26
|
// Parameters for fine-tuning beat detection
|
35
|
27
|
#define FFT_BUCKET_COUNT 64
|
|
@@ -55,11 +47,27 @@ static AppDelegate *appDelegate = nil;
|
55
|
47
|
static EZAudioFFT *fft = nil;
|
56
|
48
|
static int maxBufferSize = 0;
|
57
|
49
|
static float sensitivity = 1.0f;
|
|
50
|
+static float history[FFT_BUCKET_COUNT][FFT_BUCKET_HISTORY];
|
|
51
|
+static int nextHistory = 0;
|
|
52
|
+static int samplesPerBucket = 0;
|
|
53
|
+static unsigned char lastRed = 0, lastGreen = 0, lastBlue = 0;
|
|
54
|
+
|
|
55
|
+static BOOL shouldShowWindow = NO;
|
|
56
|
+static NSWindow *window = nil;
|
|
57
|
+static EZAudioPlot *plot = nil;
|
|
58
|
+static NSTextField *label = nil;
|
58
|
59
|
|
59
|
60
|
@implementation AudioVisualizer
|
60
|
61
|
|
61
|
62
|
+ (void)setDelegate:(AppDelegate *)delegate {
|
62
|
63
|
appDelegate = delegate;
|
|
64
|
+
|
|
65
|
+ // Initialize static history variables
|
|
66
|
+ for (int i = 0; i < FFT_BUCKET_COUNT; i++) {
|
|
67
|
+ for (int j = 0; j < FFT_BUCKET_HISTORY; j++) {
|
|
68
|
+ history[i][j] = 0.5f;
|
|
69
|
+ }
|
|
70
|
+ }
|
63
|
71
|
}
|
64
|
72
|
|
65
|
73
|
+ (void)setSensitivity:(float)sens {
|
|
@@ -70,6 +78,7 @@ static float sensitivity = 1.0f;
|
70
|
78
|
// Create Fast Fourier Transformation object
|
71
|
79
|
if (fft == nil) {
|
72
|
80
|
maxBufferSize = bufferSize;
|
|
81
|
+ samplesPerBucket = bufferSize / FFT_BUCKET_COUNT;
|
73
|
82
|
fft = [EZAudioFFT fftWithMaximumBufferSize:maxBufferSize sampleRate:appDelegate.microphone.audioStreamBasicDescription.mSampleRate];
|
74
|
83
|
|
75
|
84
|
#ifdef DEBUG
|
|
@@ -81,6 +90,7 @@ static float sensitivity = 1.0f;
|
81
|
90
|
if (bufferSize > maxBufferSize) {
|
82
|
91
|
NSLog(@"Buffer Size changed?! %d != %d\n", maxBufferSize, bufferSize);
|
83
|
92
|
maxBufferSize = bufferSize;
|
|
93
|
+ samplesPerBucket = bufferSize / FFT_BUCKET_COUNT;
|
84
|
94
|
fft = [EZAudioFFT fftWithMaximumBufferSize:maxBufferSize sampleRate:appDelegate.microphone.audioStreamBasicDescription.mSampleRate];
|
85
|
95
|
}
|
86
|
96
|
|
|
@@ -94,20 +104,6 @@ static float sensitivity = 1.0f;
|
94
|
104
|
// Perform fast fourier transformation
|
95
|
105
|
[fft computeFFTWithBuffer:buffer withBufferSize:bufferSize];
|
96
|
106
|
|
97
|
|
- static float history[FFT_BUCKET_COUNT][FFT_BUCKET_HISTORY];
|
98
|
|
- static int nextHistory = 0;
|
99
|
|
- static int samplesPerBucket = 0;
|
100
|
|
-
|
101
|
|
- // Initialize static variables
|
102
|
|
- if (samplesPerBucket == 0) {
|
103
|
|
- samplesPerBucket = bufferSize / FFT_BUCKET_COUNT;
|
104
|
|
- for (int i = 0; i < FFT_BUCKET_COUNT; i++) {
|
105
|
|
- for (int j = 0; j < FFT_BUCKET_HISTORY; j++) {
|
106
|
|
- history[i][j] = 0.5f;
|
107
|
|
- }
|
108
|
|
- }
|
109
|
|
- }
|
110
|
|
-
|
111
|
107
|
// Split FFT output into a small number of 'buckets' or 'bins' and add to circular history buffer
|
112
|
108
|
for (int i = 0; i < FFT_BUCKET_COUNT; i++) {
|
113
|
109
|
float sum = 0.0f;
|
|
@@ -117,17 +113,13 @@ static float sensitivity = 1.0f;
|
117
|
113
|
history[i][nextHistory] = sum / samplesPerBucket;
|
118
|
114
|
}
|
119
|
115
|
|
120
|
|
-#ifdef DEBUG_PLOT_FFT
|
121
|
|
- int beatCount = 0;
|
122
|
|
-#endif
|
123
|
|
-
|
124
|
116
|
// Slowly fade old colors to black
|
125
|
|
- static unsigned char lastRed = 0, lastGreen = 0, lastBlue = 0;
|
126
|
117
|
lastRed = lastRed * FFT_COLOR_DECAY;
|
127
|
118
|
lastGreen = lastGreen * FFT_COLOR_DECAY;
|
128
|
119
|
lastBlue = lastBlue * FFT_COLOR_DECAY;
|
129
|
120
|
|
130
|
121
|
// Check for any beats
|
|
122
|
+ int beatCount = 0;
|
131
|
123
|
for (int i = 0; i < FFT_BUCKET_COUNT; i++) {
|
132
|
124
|
// Skip frequency bands, if required
|
133
|
125
|
#ifdef FFT_BUCKET_SKIP_CONDITION
|
|
@@ -172,9 +164,7 @@ static float sensitivity = 1.0f;
|
172
|
164
|
NSLog(@"Beat in %d with c: %f v: %f", i, (history[i][nextHistory] / average), v);
|
173
|
165
|
#endif
|
174
|
166
|
|
175
|
|
-#ifdef DEBUG_PLOT_FFT
|
176
|
167
|
beatCount++;
|
177
|
|
-#endif
|
178
|
168
|
}
|
179
|
169
|
}
|
180
|
170
|
|
|
@@ -187,67 +177,23 @@ static float sensitivity = 1.0f;
|
187
|
177
|
lastSentBlue = lastBlue;
|
188
|
178
|
}
|
189
|
179
|
|
190
|
|
- // Display debug FFT plot, if required
|
191
|
|
-#ifdef DEBUG_PLOT_FFT
|
192
|
|
- static NSWindow *window = nil;
|
193
|
|
- static EZAudioPlot *plot = nil;
|
194
|
|
- static NSTextField *label = nil;
|
195
|
|
- if ((window == nil) || (plot == nil) || (label == nil)) {
|
196
|
|
- // Create window
|
197
|
|
- NSRect frame = NSMakeRect(450, 300, 600, 400);
|
198
|
|
- window = [[NSWindow alloc] initWithContentRect:frame
|
199
|
|
- styleMask:NSClosableWindowMask | NSTitledWindowMask | NSBorderlessWindowMask
|
200
|
|
- backing:NSBackingStoreBuffered
|
201
|
|
- defer:NO];
|
202
|
|
- [window setTitle:@"Debug FFT"];
|
203
|
|
-
|
204
|
|
- // Create FFT Plot and add to window
|
205
|
|
- plot = [[EZAudioPlot alloc] initWithFrame:window.contentView.frame];
|
206
|
|
- plot.color = [NSColor whiteColor];
|
207
|
|
- plot.shouldOptimizeForRealtimePlot = NO; // Not working with 'YES' here?!
|
208
|
|
- plot.shouldFill = YES;
|
209
|
|
- plot.shouldCenterYAxis = NO;
|
210
|
|
- plot.shouldMirror = NO;
|
211
|
|
- plot.plotType = EZPlotTypeBuffer;
|
212
|
|
- [window.contentView addSubview:plot];
|
213
|
|
-
|
214
|
|
- // Create beat count label
|
215
|
|
- label = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 380, 600, 20)];
|
216
|
|
- [label setTextColor:[NSColor whiteColor]];
|
217
|
|
- [label setEditable:NO];
|
218
|
|
- [label setBezeled:NO];
|
219
|
|
- [label setDrawsBackground:NO];
|
220
|
|
- [label setSelectable:NO];
|
221
|
|
- [label setStringValue:@"-"];
|
222
|
|
- [window.contentView addSubview:label];
|
|
180
|
+ // Update debug FFT plot, if required
|
|
181
|
+ if (shouldShowWindow && (window != nil) && (plot != nil) && (label != nil)) {
|
|
182
|
+ for (UInt32 i = 0; i < FFT_BUCKET_COUNT; i++) {
|
|
183
|
+ // Copy output to input buffer (a bit ugly, but is always big enough)
|
|
184
|
+ buffer[i] = history[i][nextHistory];
|
|
185
|
+
|
|
186
|
+ // Scale so user can see something
|
|
187
|
+ buffer[i] *= FFT_DEBUG_FACTOR;
|
|
188
|
+ if (buffer[i] > 1.0f) buffer[i] = 1.0f;
|
|
189
|
+ if (buffer[i] < -1.0f) buffer[i] = -1.0f;
|
|
190
|
+ }
|
|
191
|
+ [plot updateBuffer:buffer withBufferSize:bufferSize];
|
223
|
192
|
|
224
|
|
- // Make window visible
|
225
|
|
- [window makeKeyAndOrderFront:appDelegate.application];
|
226
|
|
- NSLog(@"Created debugging FFT Plot window...\n");
|
227
|
|
- }
|
228
|
|
-
|
229
|
|
- // Copy output to input buffer (a bit ugly, but is always big enough)
|
230
|
|
- // Scale so user can see something
|
231
|
|
-# ifdef DEBUG_PLOT_FFT_RAW
|
232
|
|
- memcpy(buffer, fft.fftData, bufferSize * sizeof(float));
|
233
|
|
- for (UInt32 i = 0; i < bufferSize; i++) {
|
234
|
|
- buffer[i] *= FFT_DEBUG_RAW_FACTOR;
|
235
|
|
-# else
|
236
|
|
- for (int i = 0; i < FFT_BUCKET_COUNT; i++) {
|
237
|
|
- buffer[i] = history[i][nextHistory];
|
|
193
|
+ // Change background color to match color output and show beat counter
|
|
194
|
+ [window setBackgroundColor:[NSColor colorWithCalibratedRed:lastRed / 255.0 green:lastGreen / 255.0 blue:lastBlue / 255.0 alpha:1.0]];
|
|
195
|
+ [label setStringValue:[NSString stringWithFormat:@"Beats: %d", beatCount]];
|
238
|
196
|
}
|
239
|
|
- for (UInt32 i = 0; i < FFT_BUCKET_COUNT; i++) {
|
240
|
|
- buffer[i] *= FFT_DEBUG_FACTOR;
|
241
|
|
-# endif
|
242
|
|
- if (buffer[i] > 1.0f) buffer[i] = 1.0f;
|
243
|
|
- if (buffer[i] < -1.0f) buffer[i] = -1.0f;
|
244
|
|
- }
|
245
|
|
- [plot updateBuffer:buffer withBufferSize:bufferSize];
|
246
|
|
-
|
247
|
|
- // Change background color to match color output and show beat counter
|
248
|
|
- [window setBackgroundColor:[NSColor colorWithCalibratedRed:lastRed / 255.0 green:lastGreen / 255.0 blue:lastBlue / 255.0 alpha:1.0]];
|
249
|
|
- [label setStringValue:[NSString stringWithFormat:@"Beats: %d", beatCount]];
|
250
|
|
-#endif
|
251
|
197
|
|
252
|
198
|
// Point to next history buffer
|
253
|
199
|
nextHistory++;
|
|
@@ -256,4 +202,65 @@ static float sensitivity = 1.0f;
|
256
|
202
|
}
|
257
|
203
|
}
|
258
|
204
|
|
|
205
|
++ (void)setShowWindow:(BOOL)showWindow {
|
|
206
|
+ shouldShowWindow = showWindow;
|
|
207
|
+
|
|
208
|
+ // Close window if it was visible and should no longer be
|
|
209
|
+ if (showWindow == YES) {
|
|
210
|
+ if ((window == nil) || (plot == nil) || (label == nil)) {
|
|
211
|
+ // Create window
|
|
212
|
+ NSRect frame = NSMakeRect(450, 300, 600, 400);
|
|
213
|
+ window = [[NSWindow alloc] initWithContentRect:frame
|
|
214
|
+ styleMask:NSClosableWindowMask | NSTitledWindowMask | NSBorderlessWindowMask
|
|
215
|
+ backing:NSBackingStoreBuffered
|
|
216
|
+ defer:NO];
|
|
217
|
+ [window setTitle:@"CaseLights FFT"];
|
|
218
|
+ [window setReleasedWhenClosed:NO];
|
|
219
|
+
|
|
220
|
+ // Create FFT Plot and add to window
|
|
221
|
+ plot = [[EZAudioPlot alloc] initWithFrame:window.contentView.frame];
|
|
222
|
+ plot.color = [NSColor whiteColor];
|
|
223
|
+ plot.shouldOptimizeForRealtimePlot = NO; // Not working with 'YES' here?!
|
|
224
|
+ plot.shouldFill = YES;
|
|
225
|
+ plot.shouldCenterYAxis = NO;
|
|
226
|
+ plot.shouldMirror = NO;
|
|
227
|
+ plot.plotType = EZPlotTypeBuffer;
|
|
228
|
+ [window.contentView addSubview:plot];
|
|
229
|
+
|
|
230
|
+ // Create beat count label
|
|
231
|
+ label = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 380, 600, 20)];
|
|
232
|
+ [label setTextColor:[NSColor whiteColor]];
|
|
233
|
+ [label setEditable:NO];
|
|
234
|
+ [label setBezeled:NO];
|
|
235
|
+ [label setDrawsBackground:NO];
|
|
236
|
+ [label setSelectable:NO];
|
|
237
|
+ [label setStringValue:@"-"];
|
|
238
|
+ [window.contentView addSubview:label];
|
|
239
|
+
|
|
240
|
+#ifdef DEBUG
|
|
241
|
+ NSLog(@"Created debugging FFT Plot window...\n");
|
|
242
|
+#endif
|
|
243
|
+ }
|
|
244
|
+
|
|
245
|
+ if ([window isVisible] == NO) {
|
|
246
|
+ // Make window visible
|
|
247
|
+ [window makeKeyAndOrderFront:appDelegate.application];
|
|
248
|
+
|
|
249
|
+#ifdef DEBUG
|
|
250
|
+ NSLog(@"Made debugging FFT Plot window visible...\n");
|
|
251
|
+#endif
|
|
252
|
+ }
|
|
253
|
+ } else {
|
|
254
|
+ if (window != nil) {
|
|
255
|
+ if ([window isVisible] == YES) {
|
|
256
|
+ [window close];
|
|
257
|
+
|
|
258
|
+#ifdef DEBUG
|
|
259
|
+ NSLog(@"Closed debugging FFT Plot window...\n");
|
|
260
|
+#endif
|
|
261
|
+ }
|
|
262
|
+ }
|
|
263
|
+ }
|
|
264
|
+}
|
|
265
|
+
|
259
|
266
|
@end
|