-
Notifications
You must be signed in to change notification settings - Fork 0
/
PolyFraudic.ino
474 lines (454 loc) · 15.1 KB
/
PolyFraudic.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
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
#include "pitches.h"
#define AUDIO_OUT 19
#define TONES_IN_RANGE 13
#define NOTE_START_PIN 2
#define NOTE_END_PIN 14
#define MIN_RANGE 1
#define MAX_RANGE 7
#define PIN_RANGE_UP 15
#define PIN_RANGE_DOWN 18
#define START_RANGE 4 // C4 is "middle C"
#define SPECIAL_BASE_1 3
#define SPECIAL_BASE_2 5
#define SPECIAL_TRIGGER_1 NOTE_END_PIN
#define SPECIAL_TRIGGER_2 NOTE_END_PIN - 1
#define FRAUD_GAP_MS 12 // Two-thirds of this should be a whole number.
#define USE_LOW_POWER true
// Variables used in interrupts should be "volatile" so they
// don't get optimized out by the compiler.
boolean volatile glISRFree = true;
boolean volatile glReplay = false;
boolean volatile glSpecial1 = false;
boolean volatile glSpecial2 = false;
byte volatile gnRange = 0;
unsigned int gaNotes[TONES_IN_RANGE];
unsigned long volatile gnLoopsElapsed = 0;
// It takes ~38 seconds for 1,000,000 loops to run, a frequency of
// 26.315 kHz. That means one second of time passes for the loop() here to
// run 26,315 times. E.g. let's say we want an idle time of ~30 seconds to trigger
// low power mode. That would mean 30 x 26315 = 789,450 loops. (We could use 800,000.)
// (NOTE: even when loops to idle is zero, things work pretty darn well.
//#define LOOPS_TO_IDLE 800000 // About 30 seconds.
#define LOOPS_TO_IDLE 130000 // About 5 Seconds.
void setup() {
// All note buttons and interrupt triggers should be pulled up so that taking them
// LOW (or using FALLING, for IRQs) is cleaner. So, LOW state is what plays notes,
// and IRQs are triggered via FALLING (and checking for LOW in ISR as a quick debounce).
for (byte i = NOTE_START_PIN; i <= NOTE_END_PIN; i++) {
pinMode(i, INPUT_PULLUP);
}
// Range buttons are also pulled up.
pinMode(PIN_RANGE_UP, INPUT_PULLUP);
pinMode(PIN_RANGE_DOWN, INPUT_PULLUP);
// Attach any persistent interrupts. Normal notes will have interrupts attached and
// detached depending on power mode.
attachInterrupt(SPECIAL_TRIGGER_1, ISR_Special1, FALLING);
attachInterrupt(SPECIAL_TRIGGER_2, ISR_Special2, FALLING);
attachInterrupt(PIN_RANGE_UP, ISR_ChangeRangeUp, FALLING);
attachInterrupt(PIN_RANGE_DOWN, ISR_ChangeRangeDown, FALLING);
// Quick start-up tones. Sound slike the "line disconnected" phone trio.
tone(AUDIO_OUT, 950);
delay(500);
tone(AUDIO_OUT, 1400);
delay(500);
tone(AUDIO_OUT, 1875);
delay(1000);
noTone(AUDIO_OUT);
SetRange(START_RANGE);
return;
}
void loop() {
// ncrement global loop counter (used for LPM entry after set idle time at end of loop).
gnLoopsElapsed++;
// Before we check the keyboard, see if there are any special actions we should take.
// Special actions should be mutually-exclusive, so no need to "else" anything.
if (glSpecial1) {
PlayTestTones();
glSpecial1 = false;
return;
}
if (glSpecial2) {
PlayQuadrophenia();
glSpecial2 = false;
return;
}
// Because of the way we have set up the note pins, we can simply go incrementally from
// first to last and play whatever first note we find as LOW (pressed). The right PlayNote()
// subroutine loops until key is released. Testing shows it doesn't really matter if we force an
// exit or not when we know we have maximum polyphonia...we can scan all keys if we want
// and it doesn't affect performance.
byte lnButton1 = 0;
byte lnButton2 = 0;
byte lnButton3 = 0;
for (byte i = NOTE_START_PIN; i <= NOTE_END_PIN; i++) {
if (digitalRead(i) == LOW) {
if (lnButton1 == 0) {
lnButton1 = i;
} else if (lnButton2 == 0) {
lnButton2 = i;
} else if (lnButton3 == 0) {
lnButton3 = i;
}
}
}
if (lnButton3 > 0) {
Play3Notes(lnButton1, lnButton2, lnButton3);
return;
}
if (lnButton2 > 0) {
Play2Notes(lnButton1, lnButton2);
return;
}
if (lnButton1 > 0) {
PlayNote(lnButton1);
return;
}
// We have made it here because no buttons are pressed or the last-pressed button was
// just released. We can now check to see if we want to drop into low power mode.
if (USE_LOW_POWER && (gnLoopsElapsed > LOOPS_TO_IDLE)) {
// Switch to LPM4 with interrupts enabled. ISRs will wake things up, then another idle period
// will put things back to sleep. Attach/Detach interrupts to prevent any stuttering in played
// notes. When in active mode, it will be as if LPM was never initiated. But in LPM, interrupts
// will be in place to wake things up.
// 1. Get ready for sleep. Make sure all interrupts are attached so that playing a note
// immediately wakes up the MCU. We also have to attach the "special key", as those keys
// are also just notes. If we don't re-attach them after cycling in and out of LPM, the
// special key combinations will not work. (Apparently attachInterrupt() is smart enough
// to replace the interrups as first established by the "for" loop).
for (byte i = NOTE_START_PIN; i <= NOTE_END_PIN; i++) {
attachInterrupt(i, ISR_ButtonPressed, FALLING);
}
attachInterrupt(SPECIAL_TRIGGER_1, ISR_Special1, FALLING);
attachInterrupt(SPECIAL_TRIGGER_2, ISR_Special2, FALLING);
// 2. Sleep, baby. Hush now.
_BIS_SR(LPM4_bits | GIE);
// 3. We're back and awake. We don't need interrupts on normal, playable pins (notes) any more,
// except for the "special" keys, as that will just add stuttering/warbling to notes played in sequence.
for (byte i = NOTE_START_PIN; i <= NOTE_END_PIN; i++) {
if (i != SPECIAL_TRIGGER_1 && i != SPECIAL_TRIGGER_2) {
detachInterrupt(i);
}
}
gnLoopsElapsed = 0;
}
return;
}
// 04/05/2013 - Tried using noInterrupts() and interrupts() instead of a Lock/Unlock scheme,
// but it didn't seem to work. Any button attached to an interrupt would cause the MCU to
// reset. I have no idea why and don't care -- this works, and only adds a few dozen bytes
// to the flashed code size (and is still well within the 8K limit of the 2452 chip).
void LockInt() {
glISRFree = false;
delayMicroseconds(100);
return;
}
void UnlockInt() {
glISRFree = true;
delayMicroseconds(100);
return;
}
void ISR_ButtonPressed() {
// Just here to wake up from low power mode (if even attached).
// It does not appear that any live code needs to occur.
return;
}
void ISR_Special1() {
if (glISRFree) {
LockInt();
if (SpecialState()) {
glSpecial1 = true;
}
UnlockInt();
}
return;
}
void ISR_Special2() {
if (glISRFree) {
LockInt();
if (SpecialState()) {
glSpecial2 = true;
}
UnlockInt();
}
return;
}
void ISR_ChangeRangeUp() {
if (glISRFree) {
LockInt();
delayMicroseconds(1000);
if (digitalRead(PIN_RANGE_UP) != LOW) {
UnlockInt();
return;
}
SetRange(gnRange + 1);
glReplay = true;
UnlockInt();
}
return;
}
void ISR_ChangeRangeDown() {
if (glISRFree) {
LockInt();
delayMicroseconds(1000);
if (digitalRead(PIN_RANGE_DOWN) != LOW) {
UnlockInt();
return;
}
SetRange(gnRange - 1);
glReplay = true;
UnlockInt();
}
return;
}
void Play3Notes(byte pnButton1, byte pnButton2, byte pnButton3) {
if (digitalRead(pnButton1) == LOW && digitalRead(pnButton2) == LOW && digitalRead(pnButton3) == LOW) {
byte lnNoteIndex1 = pnButton1 - 2; // Buttons start at 2, note array is zero-indexed.
byte lnNoteIndex2 = pnButton2 - 2; // Buttons start at 2, note array is zero-indexed.
byte lnNoteIndex3 = pnButton3 - 3; // Buttons start at 2, note array is zero-indexed.
while (true) {
// Move tones inside the infinite loop (until done), alternating them
// to create a fraudulent polyphonia.
tone(AUDIO_OUT, gaNotes[lnNoteIndex1]);
delay(FRAUD_GAP_MS * 2 / 3);
tone(AUDIO_OUT, gaNotes[lnNoteIndex2]);
delay(FRAUD_GAP_MS * 2 / 3);
tone(AUDIO_OUT, gaNotes[lnNoteIndex3]);
delay(FRAUD_GAP_MS * 2 / 3);
// If a different button (from the ones being played here) is being pressed,
// we should stop the tone and let the loop() decide how to proceed.
boolean llPressChange = false;
for (byte i = NOTE_START_PIN; i <= NOTE_END_PIN; i++) {
if (i != pnButton1 && i != pnButton2 && i != pnButton3 && digitalRead(i) == LOW) {
llPressChange = true;
i == NOTE_END_PIN + 1;
}
}
if (llPressChange || digitalRead(pnButton1) == HIGH || digitalRead(pnButton2) == HIGH || digitalRead(pnButton3) == HIGH) {
// Our notes are no longer all being pressed, so let loop() take over again.
noTone(AUDIO_OUT);
return;
}
}
}
glReplay = false;
return;
}
void Play2Notes(byte pnButton1, byte pnButton2) {
if (digitalRead(pnButton1) == LOW && digitalRead(pnButton2) == LOW) {
byte lnNoteIndex1 = pnButton1 - 2; // Buttons start at 2, note array is zero-indexed.
byte lnNoteIndex2 = pnButton2 - 2; // Buttons start at 2, note array is zero-indexed.
while (true) {
// Move tones inside the infinite loop (until done), alternating them
// to create a fraudulent polyphonia.
tone(AUDIO_OUT, gaNotes[lnNoteIndex1]);
delay(FRAUD_GAP_MS);
tone(AUDIO_OUT, gaNotes[lnNoteIndex2]);
delay(FRAUD_GAP_MS);
// If a different button (from the ones being played here) is being pressed,
// we should stop the tone and let the loop() decide how to proceed.
boolean llPressChange = false;
for (byte i = NOTE_START_PIN; i <= NOTE_END_PIN; i++) {
if (i != pnButton1 && i != pnButton2 && digitalRead(i) == LOW) {
llPressChange = true;
i == NOTE_END_PIN + 1;
}
}
if (llPressChange || digitalRead(pnButton1) == HIGH || digitalRead(pnButton2) == HIGH) {
// Our notes are no longer both being pressed, so let loop() take over again.
noTone(AUDIO_OUT);
return;
}
}
}
glReplay = false;
return;
}
void PlayNote(byte pnButton) {
if (digitalRead(pnButton) == LOW) {
byte lnNoteIndex = pnButton - 2; // Buttons start at 2, note array is zero-indexed.
// Generate tone once, outside of the loop, otherwise it will have stutters in it as
// the loop cycles through its frequency.
tone(AUDIO_OUT, gaNotes[lnNoteIndex]);
while (true) {
// If the note being played is no longer pressed OR a different button is being pressed,
// we should stop the tone and let the loop() decide how to proceed.
boolean llPressChange = false;
for (byte i = NOTE_START_PIN; i <= NOTE_END_PIN; i++) {
if (i != pnButton && digitalRead(i) == LOW) {
llPressChange = true;
i == NOTE_END_PIN + 1;
}
}
if (llPressChange || digitalRead(pnButton) == HIGH) {
noTone(AUDIO_OUT);
return;
} else {
// In the single note scenario, we ned to trigger a replay, otherwise changing the
// octave range does not dynamically change the note being played.
if (glReplay) {
glReplay = false;
tone(AUDIO_OUT, gaNotes[lnNoteIndex]);
}
}
}
}
glReplay = false;
return;
}
boolean SpecialState() {
return (digitalRead(SPECIAL_BASE_1) == LOW & digitalRead(SPECIAL_BASE_2) == LOW);
}
void SetRange(byte pnRange) {
gnRange = pnRange;
// Make sure we are not going out of bounds in terms of range.
if (gnRange < MIN_RANGE) {gnRange = MIN_RANGE;}
if (gnRange > MAX_RANGE) {gnRange = MAX_RANGE;}
switch (gnRange) {
case 1:
gaNotes[0] = NOTE_C1;
gaNotes[1] = NOTE_CS1;
gaNotes[2] = NOTE_D1;
gaNotes[3] = NOTE_DS1;
gaNotes[4] = NOTE_E1;
gaNotes[5] = NOTE_F1;
gaNotes[6] = NOTE_FS1;
gaNotes[7] = NOTE_G1;
gaNotes[8] = NOTE_GS1;
gaNotes[9] = NOTE_A1;
gaNotes[10] = NOTE_AS1;
gaNotes[11] = NOTE_B1;
gaNotes[12] = NOTE_C2;
break;
case 2:
gaNotes[0] = NOTE_C2;
gaNotes[1] = NOTE_CS2;
gaNotes[2] = NOTE_D2;
gaNotes[3] = NOTE_DS2;
gaNotes[4] = NOTE_E2;
gaNotes[5] = NOTE_F2;
gaNotes[6] = NOTE_FS2;
gaNotes[7] = NOTE_G2;
gaNotes[8] = NOTE_GS2;
gaNotes[9] = NOTE_A2;
gaNotes[10] = NOTE_AS2;
gaNotes[11] = NOTE_B2;
gaNotes[12] = NOTE_C3;
break;
case 3:
gaNotes[0] = NOTE_C3;
gaNotes[1] = NOTE_CS3;
gaNotes[2] = NOTE_D3;
gaNotes[3] = NOTE_DS3;
gaNotes[4] = NOTE_E3;
gaNotes[5] = NOTE_F3;
gaNotes[6] = NOTE_FS3;
gaNotes[7] = NOTE_G3;
gaNotes[8] = NOTE_GS3;
gaNotes[9] = NOTE_A3;
gaNotes[10] = NOTE_AS3;
gaNotes[11] = NOTE_B3;
gaNotes[12] = NOTE_C4;
break;
case 4:
gaNotes[0] = NOTE_C4;
gaNotes[1] = NOTE_CS4;
gaNotes[2] = NOTE_D4;
gaNotes[3] = NOTE_DS4;
gaNotes[4] = NOTE_E4;
gaNotes[5] = NOTE_F4;
gaNotes[6] = NOTE_FS4;
gaNotes[7] = NOTE_G4;
gaNotes[8] = NOTE_GS4;
gaNotes[9] = NOTE_A4;
gaNotes[10] = NOTE_AS4;
gaNotes[11] = NOTE_B4;
gaNotes[12] = NOTE_C5;
break;
case 5:
gaNotes[0] = NOTE_C5;
gaNotes[1] = NOTE_CS5;
gaNotes[2] = NOTE_D5;
gaNotes[3] = NOTE_DS5;
gaNotes[4] = NOTE_E5;
gaNotes[5] = NOTE_F5;
gaNotes[6] = NOTE_FS5;
gaNotes[7] = NOTE_G5;
gaNotes[8] = NOTE_GS5;
gaNotes[9] = NOTE_A5;
gaNotes[10] = NOTE_AS5;
gaNotes[11] = NOTE_B5;
gaNotes[12] = NOTE_C6;
break;
case 6:
gaNotes[0] = NOTE_C6;
gaNotes[1] = NOTE_CS6;
gaNotes[2] = NOTE_D6;
gaNotes[3] = NOTE_DS6;
gaNotes[4] = NOTE_E6;
gaNotes[5] = NOTE_F6;
gaNotes[6] = NOTE_FS6;
gaNotes[7] = NOTE_G6;
gaNotes[8] = NOTE_GS6;
gaNotes[9] = NOTE_A6;
gaNotes[10] = NOTE_AS6;
gaNotes[11] = NOTE_B6;
gaNotes[12] = NOTE_C7;
break;
case 7:
gaNotes[0] = NOTE_C7;
gaNotes[1] = NOTE_CS7;
gaNotes[2] = NOTE_D7;
gaNotes[3] = NOTE_DS7;
gaNotes[4] = NOTE_E7;
gaNotes[5] = NOTE_F7;
gaNotes[6] = NOTE_FS7;
gaNotes[7] = NOTE_G7;
gaNotes[8] = NOTE_GS7;
gaNotes[9] = NOTE_A7;
gaNotes[10] = NOTE_AS7;
gaNotes[11] = NOTE_B7;
gaNotes[12] = NOTE_C8;
break;
}
return;
}
void PlayTestTones() {
// Test the full ranges.
byte lnCurrRange = gnRange;
for (byte i = MIN_RANGE; i <= MAX_RANGE; i++) {
SetRange(i);
for (byte j = 0; j <= TONES_IN_RANGE - 1; j++) {
tone(AUDIO_OUT, gaNotes[j], 1000);
delay(100);
}
}
// One last tone for next highest note (a C)! (Range will still be set to highest).
tone(AUDIO_OUT, gaNotes[TONES_IN_RANGE - 1], 2000);
delay(1000);
SetRange(lnCurrRange);
return;
}
void PlayQuadrophenia() {
int laQuadNotes[] = {NOTE_E4,
NOTE_D4,
NOTE_E4,
NOTE_F4,
NOTE_G4,
NOTE_F4,
NOTE_G4,
NOTE_C4,
NOTE_GS4,
NOTE_G4,
NOTE_GS4,
NOTE_G4,
NOTE_F4,
NOTE_E4,
NOTE_F4,
NOTE_D4};
for (byte i = 0; i <= 15; i++) {
tone(AUDIO_OUT, laQuadNotes[i]);
delay(200);
}
noTone(AUDIO_OUT);
return;
}