-
Notifications
You must be signed in to change notification settings - Fork 445
/
Copy pathsound_reactive.ino
324 lines (263 loc) · 7.94 KB
/
sound_reactive.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
#include <FastLED.h>
/** BASIC CONFIGURATION **/
//The amount of LEDs in the setup
#define NUM_LEDS 150
//The pin that controls the LEDs
#define LED_PIN 6
//The pin that we read sensor values form
#define ANALOG_READ 0
//Confirmed microphone low value, and max value
#define MIC_LOW 0.0
#define MIC_HIGH 737.0
/** Other macros */
//How many previous sensor values effects the operating average?
#define AVGLEN 5
//How many previous sensor values decides if we are on a peak/HIGH (e.g. in a song)
#define LONG_SECTOR 20
//Mneumonics
#define HIGH 3
#define NORMAL 2
//How long do we keep the "current average" sound, before restarting the measuring
#define MSECS 30 * 1000
#define CYCLES MSECS / DELAY
/*Sometimes readings are wrong or strange. How much is a reading allowed
to deviate from the average to not be discarded? **/
#define DEV_THRESH 0.8
//Arduino loop delay
#define DELAY 1
float fscale( float originalMin, float originalMax, float newBegin, float newEnd, float inputValue, float curve);
void insert(int val, int *avgs, int len);
int compute_average(int *avgs, int len);
void visualize_music();
//How many LEDs to we display
int curshow = NUM_LEDS;
/*Not really used yet. Thought to be able to switch between sound reactive
mode, and general gradient pulsing/static color*/
int mode = 0;
//Showing different colors based on the mode.
int songmode = NORMAL;
//Average sound measurement the last CYCLES
unsigned long song_avg;
//The amount of iterations since the song_avg was reset
int iter = 0;
//The speed the LEDs fade to black if not relit
float fade_scale = 1.2;
//Led array
CRGB leds[NUM_LEDS];
/*Short sound avg used to "normalize" the input values.
We use the short average instead of using the sensor input directly */
int avgs[AVGLEN] = {-1};
//Longer sound avg
int long_avg[LONG_SECTOR] = {-1};
//Keeping track how often, and how long times we hit a certain mode
struct time_keeping {
unsigned long times_start;
short times;
};
//How much to increment or decrement each color every cycle
struct color {
int r;
int g;
int b;
};
struct time_keeping high;
struct color Color;
void setup() {
Serial.begin(9600);
//Set all lights to make sure all are working as expected
FastLED.addLeds<NEOPIXEL, LED_PIN>(leds, NUM_LEDS);
for (int i = 0; i < NUM_LEDS; i++)
leds[i] = CRGB(0, 0, 255);
FastLED.show();
delay(1000);
//bootstrap average with some low values
for (int i = 0; i < AVGLEN; i++) {
insert(250, avgs, AVGLEN);
}
//Initial values
high.times = 0;
high.times_start = millis();
Color.r = 0;
Color.g = 0;
Color.b = 1;
}
/*With this we can change the mode if we want to implement a general
lamp feature, with for instance general pulsing. Maybe if the
sound is low for a while? */
void loop() {
switch(mode) {
case 0:
visualize_music();
break;
default:
break;
}
delay(DELAY); // delay in between reads for stability
}
/**Funtion to check if the lamp should either enter a HIGH mode,
or revert to NORMAL if already in HIGH. If the sensors report values
that are higher than 1.1 times the average values, and this has happened
more than 30 times the last few milliseconds, it will enter HIGH mode.
TODO: Not very well written, remove hardcoded values, and make it more
reusable and configurable. */
void check_high(int avg) {
if (avg > (song_avg/iter * 1.1)) {
if (high.times != 0) {
if (millis() - high.times_start > 200.0) {
high.times = 0;
songmode = NORMAL;
} else {
high.times_start = millis();
high.times++;
}
} else {
high.times++;
high.times_start = millis();
}
}
if (high.times > 30 && millis() - high.times_start < 50.0)
songmode = HIGH;
else if (millis() - high.times_start > 200) {
high.times = 0;
songmode = NORMAL;
}
}
//Main function for visualizing the sounds in the lamp
void visualize_music() {
int sensor_value, mapped, avg, longavg;
//Actual sensor value
sensor_value = analogRead(ANALOG_READ);
//If 0, discard immediately. Probably not right and save CPU.
if (sensor_value == 0)
return;
//Discard readings that deviates too much from the past avg.
mapped = (float)fscale(MIC_LOW, MIC_HIGH, MIC_LOW, (float)MIC_HIGH, (float)sensor_value, 2.0);
avg = compute_average(avgs, AVGLEN);
if (((avg - mapped) > avg*DEV_THRESH)) //|| ((avg - mapped) < -avg*DEV_THRESH))
return;
//Insert new avg. values
insert(mapped, avgs, AVGLEN);
insert(avg, long_avg, LONG_SECTOR);
//Compute the "song average" sensor value
song_avg += avg;
iter++;
if (iter > CYCLES) {
song_avg = song_avg / iter;
iter = 1;
}
longavg = compute_average(long_avg, LONG_SECTOR);
//Check if we enter HIGH mode
check_high(longavg);
if (songmode == HIGH) {
fade_scale = 3;
Color.r = 5;
Color.g = 3;
Color.b = -1;
}
else if (songmode == NORMAL) {
fade_scale = 2;
Color.r = -1;
Color.b = 2;
Color.g = 1;
}
//Decides how many of the LEDs will be lit
curshow = fscale(MIC_LOW, MIC_HIGH, 0.0, (float)NUM_LEDS, (float)avg, -1);
/*Set the different leds. Control for too high and too low values.
Fun thing to try: Dont account for overflow in one direction,
some interesting light effects appear! */
for (int i = 0; i < NUM_LEDS; i++)
//The leds we want to show
if (i < curshow) {
if (leds[i].r + Color.r > 255)
leds[i].r = 255;
else if (leds[i].r + Color.r < 0)
leds[i].r = 0;
else
leds[i].r = leds[i].r + Color.r;
if (leds[i].g + Color.g > 255)
leds[i].g = 255;
else if (leds[i].g + Color.g < 0)
leds[i].g = 0;
else
leds[i].g = leds[i].g + Color.g;
if (leds[i].b + Color.b > 255)
leds[i].b = 255;
else if (leds[i].b + Color.b < 0)
leds[i].b = 0;
else
leds[i].b = leds[i].b + Color.b;
//All the other LEDs begin their fading journey to eventual total darkness
} else {
leds[i] = CRGB(leds[i].r/fade_scale, leds[i].g/fade_scale, leds[i].b/fade_scale);
}
FastLED.show();
}
//Compute average of a int array, given the starting pointer and the length
int compute_average(int *avgs, int len) {
int sum = 0;
for (int i = 0; i < len; i++)
sum += avgs[i];
return (int)(sum / len);
}
//Insert a value into an array, and shift it down removing
//the first value if array already full
void insert(int val, int *avgs, int len) {
for (int i = 0; i < len; i++) {
if (avgs[i] == -1) {
avgs[i] = val;
return;
}
}
for (int i = 1; i < len; i++) {
avgs[i - 1] = avgs[i];
}
avgs[len - 1] = val;
}
//Function imported from the arduino website.
//Basically map, but with a curve on the scale (can be non-uniform).
float fscale( float originalMin, float originalMax, float newBegin, float
newEnd, float inputValue, float curve){
float OriginalRange = 0;
float NewRange = 0;
float zeroRefCurVal = 0;
float normalizedCurVal = 0;
float rangedValue = 0;
boolean invFlag = 0;
// condition curve parameter
// limit range
if (curve > 10) curve = 10;
if (curve < -10) curve = -10;
curve = (curve * -.1) ; // - invert and scale - this seems more intuitive - postive numbers give more weight to high end on output
curve = pow(10, curve); // convert linear scale into lograthimic exponent for other pow function
// Check for out of range inputValues
if (inputValue < originalMin) {
inputValue = originalMin;
}
if (inputValue > originalMax) {
inputValue = originalMax;
}
// Zero Refference the values
OriginalRange = originalMax - originalMin;
if (newEnd > newBegin){
NewRange = newEnd - newBegin;
}
else
{
NewRange = newBegin - newEnd;
invFlag = 1;
}
zeroRefCurVal = inputValue - originalMin;
normalizedCurVal = zeroRefCurVal / OriginalRange; // normalize to 0 - 1 float
// Check for originalMin > originalMax - the math for all other cases i.e. negative numbers seems to work out fine
if (originalMin > originalMax ) {
return 0;
}
if (invFlag == 0){
rangedValue = (pow(normalizedCurVal, curve) * NewRange) + newBegin;
}
else // invert the ranges
{
rangedValue = newBegin - (pow(normalizedCurVal, curve) * NewRange);
}
return rangedValue;
}