-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathMode_Triggerboy.ino
571 lines (493 loc) · 21.2 KB
/
Mode_Triggerboy.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
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
/***************************************************************************
* Name: Eliot Lash *
* Email: eliot.lash@gmail.com *
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include <fix_fft.h>
//Speed improvements for AnalogRead
#define FASTADC 1
// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
// -------------------- Trigger config -------------------- //
const byte NUM_TRIGGERS = 3 + 1; //Set the number on the left to the # of triggers in use. The +1 is for the null trigger
const byte NULL_TRIGGER = 0; //DO NOT CHANGE!!! Triggers that are currently disabled can redirect their status changes to this trigger, so we don't have to disable the hooks for them throughout the code
//The in-use triggers should be continuous numbers from 1 through (NUM_TRIGGERS - 1.)
//Extras may be assigned to NULL_TRIGGER to disable them.
const byte TICK_TRIGGER = NULL_TRIGGER;
const byte TICK_TOGGLE_TRIGGER = NULL_TRIGGER;
const byte AMPLITUDE_TRIGGER = NULL_TRIGGER;
const byte AMPLITUDE_TOGGLE_TRIGGER = NULL_TRIGGER;
const byte TEST_CLOCK_TRIGGER = 3;
const byte TEST_INTERRUPT_TRIGGER = NULL_TRIGGER;
const byte LOW_BAND_TRIGGER = 1;
const byte MID_BAND_TRIGGER = NULL_TRIGGER;
const byte HIGH_BAND_TRIGGER = 2;
//Config for each trigger:
//TICK_TRIGGER, TICK_TOGGLE_TRIGGER
const byte tickTriggerTicksPerStep = 6;
const byte tickTriggerStepsPerBeat = 4;
const unsigned long msTickTriggerPulseDuration = 2;
//AMPLITUDE_TRIGGER
const int vAmplitudeThreshold = 514; //Voltage threshold for this trigger to turn on, this will be a value from analogRead() from 0 to 1023
//TEST_CLOCK_TRIGGER
const unsigned long msTestClockTickInterval = 1000; //How long to wait between test clock ticks (in milliseconds)
//LOW_BAND_TRIGGER, MID_BAND_TRIGGER, HIGH_BAND_TRIGGER
//FFT amplitude thresholds for trigger to turn on
const float fftaLowBandThreshold = 0.50; //Change this to a float and update the average calculation in fft_forward if decimal precision is needed
const float fftaMidBandThreshold = 4; //IFIXME aven't tested this one yet
const float fftaHighBandThreshold = 0.10;
// -------------------- Pin config -------------------- //
const byte AUDIO_IN_LEFT_PIN = 3; //Read left channel audio from Analog In Pin 3.
const byte GB_CLOCK_LINE_PIN = 2; //Digital pin for GB clock line external interrupt
byte GB_CLOCK_LINE_INTERRUPT_NUMBER = 0xF00L; //Which interrupt the GB clock line is on. Initialize to invalid # so we know if it was set properly in configurePinouts()
const byte MAX_PIN_OUT = 13; //Largest output pin #
//Some magic numbers for invalid pins, these should never get assigned as real outputs
const byte INVALID_PIN_MAGIC_NULL_TRIGGER = 255;
const byte INVALID_PIN_MAGIC_USED_PINS = 254;
// -------------------- Print debugging settings -------------------- //
//Uncomment a #define to enable printing to serial
//Print FFT every FFT frame:
//#define PRINT_FFT
//Print pinout state every time it changes:
//#define PRINT_TRIGGERS
//Print amplitude whenever the sample exceeds the threshold:
//#define PRINT_AMPLITUDE_THRESH
//Print low band FFT average:
//#define PRINT_FFT_BAND_AVGS
// -------------------- Data Structures, etc. -------------------- //
//Trigger data structures
byte triggerMap[NUM_TRIGGERS]; //Assignment of absolute triggers (index) to digital out port number (value)
boolean triggerStates[NUM_TRIGGERS]; //The current on/off state of each trigger
volatile boolean pendingTriggerStates[NUM_TRIGGERS]; //Pending changes to on/off state of each trigger on the next update
//FFT stuff
#define DATA_SIZE 128
char im[DATA_SIZE];
char data[DATA_SIZE];
void modeTriggerboySetup()
{
logLine("Beginning setup of Triggerboy main mode.");
#if FASTADC
// set prescale to 16
sbi(ADCSRA,ADPS2) ;
cbi(ADCSRA,ADPS1) ;
cbi(ADCSRA,ADPS0) ;
#endif
//Set up LSDJ Master Sync
digitalWrite(pinStatusLed,LOW);
DDRC = B00000000; //Set analog in pins as inputs
countSyncTime=0;
blinkMaxCount=1000;
//Set up mapping between triggers and pinouts.
//ex. "triggerMap[FOO_TRIGGER] = 13" means trigger foo is assigned to pin 13, etc.
//It's okay for disabled triggers with conflicting pinouts to be here since they will not fire.
triggerMap[TICK_TRIGGER] = 4; //Pulse in time with LSDJ Master Clock.
triggerMap[TICK_TOGGLE_TRIGGER] = 6; //Toggle on/off in time with LSDJ Master Clock.
triggerMap[AMPLITUDE_TRIGGER] = 6; //Trigger when audio amplitude is over a certain threshhold
triggerMap[AMPLITUDE_TOGGLE_TRIGGER] = 4; //Trigger when audio amplitude is over a certain threshhold
triggerMap[TEST_CLOCK_TRIGGER] = 13; //Trigger on an internal timer, for testing outputs independently of the connected inputs
triggerMap[TEST_INTERRUPT_TRIGGER] = 13; //For triggering when an interrupt handler is invoked, currently assigned to handleGbClockLineByte
triggerMap[LOW_BAND_TRIGGER] = 6; //Low-band FFT threshold trigger
triggerMap[MID_BAND_TRIGGER] = 6; //Mid-band FFT threshold trigger
triggerMap[HIGH_BAND_TRIGGER] = 4; //High-band FFT threshold trigger
triggerMap[NULL_TRIGGER] = INVALID_PIN_MAGIC_NULL_TRIGGER; //A place for currently disabled triggers to dump data. This shouldn't be a real pin and should never be actually written to. Pinout defined after the other ones to overwrite them in case any of them are currently pointing to the null trigger.
logTimestamp();
Serial.print("There are currently ");
Serial.print(NUM_TRIGGERS - 1);
Serial.println(" active triggers:");
//Validate & configure all mapped pins
configurePinouts();
printTriggers();
modeTriggerboy();
}
/**
* Validate the pinout configuration. Then, configure the selected pins as outputs.
*/
void configurePinouts() {
byte usedPinouts[NUM_TRIGGERS]; //Track which pins we've assigned already so we can catch dupes
//Fill usedPinouts array with invalid pin assignments so we can catch issues better
memset(usedPinouts, INVALID_PIN_MAGIC_USED_PINS, NUM_TRIGGERS);
for (int currentTrigger = 0; currentTrigger < NUM_TRIGGERS; currentTrigger++) {
byte currentPin = triggerMap[currentTrigger];
//Sanity range check
if (currentPin > MAX_PIN_OUT && currentTrigger != NULL_TRIGGER) {
char errorMessage [80];
sprintf(
errorMessage,
"Trigger %i is assigned to pin %i, which doesn't exist.",
currentTrigger,
currentPin
);
fatalError(errorMessage);
}
//Sanity check on reserved pins
if (
(usbMode && (0 == currentPin || 1 == currentPin))
|| GB_CLOCK_LINE_PIN == currentPin
|| pinButtonMode == currentPin
) {
char errorMessage [70];
sprintf(
errorMessage,
"Trigger %i is assigned to pin %i, which is reserved.",
currentTrigger,
currentPin
);
fatalError(errorMessage);
}
//Sanity check on pins that were already assigned to triggers
for (int i = 0; i < NUM_TRIGGERS; i++) {
if (currentTrigger == i) continue; //Only compare to other triggers
if (usedPinouts[i] == currentPin) {
char errorMessage [90];
sprintf(
errorMessage,
"Trigger %i is assigned to pin %i, which is already in use by trigger %i.",
currentTrigger,
currentPin,
i
);
fatalError(errorMessage);
}
}
pinMode(currentPin, OUTPUT);
usedPinouts[currentTrigger] = currentPin;
}
//Register external interrupt handler for GB clock line
if (GB_CLOCK_LINE_PIN > 3 || GB_CLOCK_LINE_PIN < 2) {
fatalError("GB_CLOCK_LINE_PIN must be set to either digital pin 2 or 3!");
}
GB_CLOCK_LINE_INTERRUPT_NUMBER = GB_CLOCK_LINE_PIN - 2;
tb_assert(0 == GB_CLOCK_LINE_INTERRUPT_NUMBER || 1 == GB_CLOCK_LINE_INTERRUPT_NUMBER, "GB_CLOCK_LINE_INTERRUPT_NUMBER is valid");
pinMode(GB_CLOCK_LINE_PIN, INPUT);
attachInterrupt(GB_CLOCK_LINE_INTERRUPT_NUMBER, handleGbClockLineByte, FALLING);
}
void modeTriggerboyCleanup() {
detachInterrupt(GB_CLOCK_LINE_INTERRUPT_NUMBER);
}
/**
* Interrupt handler for when GB_CLOCK_LINE_PIN turns on
*/
void handleGbClockLineByte() {
/*
readgbClockLine = PINC & 0x01; //Read gameboy's clock line
if(readgbClockLine) { //If Gb's Clock is On
while(readgbClockLine) { //Loop untill its off
readgbClockLine = PINC & 0x01; //Read the clock again
bit = (PINC & 0x04)>>2; //Read the serial input for song position
}
}
*/
tb_sendMidiClockSlaveFromLSDJ();
}
void modeTriggerboy()
{
while(1){
//Process LSDJ Master Sync
/*
if (Serial.available()) { //If serial data was send to midi input
incomingMidiByte = Serial.read(); //Read it
if(!checkForProgrammerSysex(incomingMidiByte) && !usbMode) Serial.write(incomingMidiByte); //Send it to the midi output
}
*/
//readGbSerialIn = readGbSerialIn << 1; //left shift the serial byte by one to append new bit from last loop
//readGbSerialIn = readGbSerialIn + bit; //and then add the bit that was read
tb_checkActions();
alwaysRunActions(); //Do stuff that should happen on every loop
setMode();
}
}
void fft_forward() {
//Process the FFT for this loop
int static i = 0;
static long tt;
int val;
if (millis() > tt) {
if (i < DATA_SIZE) {
val = analogRead(AUDIO_IN_LEFT_PIN);
data[i] = val / 4 - DATA_SIZE;
im[i] = 0;
i++;
} else {
//this could be done with the fix_fftr function without the im array.
//fix_fftr(data,7,0);
fix_fft(data, im, 7, 0);
// I am only interessted in the absolute value of the transformation
int lowBandSum = 0, midBandSum = 0, highBandSum = 0;
int lowBandAvgDenominator = 0, midBandAvgDenominator = 0, highBandAvgDenominator = 0;
for (i=0; i< 64;i++) {
data[i] = sqrt(data[i] * data[i] + im[i] * im[i]);
if (i >= 2 && i <= 10) {
//Considering this the "low band" for now. Too much noise in buckets 0 & 1 to include them.
lowBandSum += data[i];
lowBandAvgDenominator++;
} else if (i > 10 && i <= 20) {
//"mid band"
midBandSum += data[i];
midBandAvgDenominator++;
} else if (i > 20) {
//"high band"
highBandSum += data[i];
highBandAvgDenominator++;
}
}
//Compute mean average amplitude of each FFT band
/*FIXME I really want to be able to use fractional numbers here for better granularity on the threshold
and avoid remainder chopping, but there's probably a workaround to use integer math here and retain precision using
the modulus or something like that. */
//int lowBandAvg = lowBandSum / lowBandAvgDenominator; //But we don't need float precision on these bands, not yet anyway
//int midBandAvg = midBandSum / midBandAvgDenominator;
float lowBandAvg = (float)lowBandSum / (float)lowBandAvgDenominator;
float midBandAvg = (float)midBandSum / (float)midBandAvgDenominator;
float highBandAvg = (float)highBandSum / (float)highBandAvgDenominator;
//If this band is above our threshold, trigger on, otherwise trigger off
pendingTriggerStates[LOW_BAND_TRIGGER] = (lowBandAvg > fftaLowBandThreshold);
pendingTriggerStates[MID_BAND_TRIGGER] = (midBandAvg > fftaMidBandThreshold);
pendingTriggerStates[HIGH_BAND_TRIGGER] = (highBandAvg > fftaHighBandThreshold);
#ifdef PRINT_FFT_BAND_AVGS
logTimestamp();
Serial.print("FFT Band Averages: ");
Serial.print(lowBandAvg);
Serial.print(" ");
Serial.print(midBandAvg);
Serial.print(" ");
Serial.println(highBandAvg);
#endif
//do something with the data values 1..64 and ignore im
#ifdef PRINT_FFT
print_fft(data);
#endif
}
tt = millis();
}
}
#ifdef PRINT_FFT
void print_fft(char * data) {
//Print FFT to serial for debugging
//for (int i = 0; i < sizeof(data) / sizeof(char); i++) {
for (int i = 0; i < 64; i++) {
Serial.print(data[i], DEC);
Serial.print(" ");
//Serial.println(i);
}
Serial.println("");
}
#endif
void alwaysRunActions() {
//Run these every single loop
//If no FFT-based triggers are currently assigned, skip this entire thing to save clock cyles
if (!(LOW_BAND_TRIGGER == NULL_TRIGGER
&& MID_BAND_TRIGGER == NULL_TRIGGER
&& HIGH_BAND_TRIGGER == NULL_TRIGGER)) {
fft_forward(); //run the FFT
}
triggerShit();
}
void tb_checkActions()
{
tb_checkLSDJStopped(); //Check if LSDJ hit Stop
setMode();
updateStatusLight();
}
void triggerShit() {
//Trigger some outputs!
//Set trigger states
boolean stateChanged = false;
for (int currentTrigger = 0; currentTrigger < NUM_TRIGGERS; currentTrigger++) {
byte currentPin = triggerMap[currentTrigger];
if (NULL_TRIGGER == currentTrigger) {
//No-op
} else if (TEST_CLOCK_TRIGGER == currentTrigger) {
static unsigned long msLastTestClockTick;
unsigned long msCurrent = millis();
if ((msCurrent - msLastTestClockTick) >= msTestClockTickInterval) {
//Toggle trigger on every tick of this timer
if (didUpdate(currentTrigger, !triggerStates[currentTrigger])) stateChanged = true;
msLastTestClockTick = msCurrent;
}
} else if (TEST_INTERRUPT_TRIGGER == currentTrigger) {
//Process pending trigger state and then disable
if (didUpdate(currentTrigger, pendingTriggerStates[currentTrigger])) stateChanged = true;
pendingTriggerStates[currentTrigger] = false;
} else if (TICK_TRIGGER == currentTrigger) {
static unsigned long msTickTriggerPulseBegan;
if (pendingTriggerStates[currentTrigger]) {
pendingTriggerStates[currentTrigger] = false;
if (didUpdate(currentTrigger, true)) stateChanged = true; //Update the current trigger state if needed
//Start timer for pulse
msTickTriggerPulseBegan = millis();
} else if (triggerStates[currentTrigger] && ((millis() - msTickTriggerPulseBegan) > msTickTriggerPulseDuration)) {
//If trigger is on but the pulse should be over, turn the trigger off
if (didUpdate(currentTrigger, false)) stateChanged = true;
}
} else if (TICK_TOGGLE_TRIGGER == currentTrigger) {
if (pendingTriggerStates[currentTrigger]) {
pendingTriggerStates[currentTrigger] = false;
//Toggle the pin
if (didUpdate(currentTrigger, !triggerStates[currentTrigger])) stateChanged = true; //Update the current trigger state if needed
}
} else if (AMPLITUDE_TRIGGER == currentTrigger) {
static unsigned long msLastReadTime;
if (millis() > msLastReadTime) {
//Sample every millisecond
int amp = analogRead(AUDIO_IN_LEFT_PIN);
if (amp > vAmplitudeThreshold) {
#ifdef PRINT_AMPLITUDE_THRESH
logTimestamp();
Serial.print("AMPLITUDE = ");
Serial.println(amp);
#endif
if (didUpdate(currentTrigger, true)) stateChanged = true;
} else {
if (didUpdate(currentTrigger, false)) stateChanged = true;
}
msLastReadTime = millis();
}
} else if (AMPLITUDE_TOGGLE_TRIGGER == currentTrigger) {
static unsigned long msLastReadTime;
static bool toggleNext = true;
if (millis() > msLastReadTime) {
//Sample every millisecond
int amp = analogRead(AUDIO_IN_LEFT_PIN);
if (amp > vAmplitudeThreshold) {
if (toggleNext) {
if (didUpdate(currentTrigger, !triggerStates[currentTrigger])) stateChanged = true;
toggleNext = false;
}
} else {
toggleNext = true;
}
msLastReadTime = millis();
}
//DEFAULT TRIGGER BEHAVIOR:
} else {
//Just use the pending value without modifying it (in case it's continuous)
if (didUpdate(currentTrigger, pendingTriggerStates[currentTrigger])) stateChanged = true;
}
}
#ifdef PRINT_TRIGGERS
if (stateChanged) {
printTriggers();
}
#endif
}
/**
* Print information on trigger->pinout assignments and pin HIGH/LOW state for debugging purposes
*/
void printTriggers() {
logTimestamp();
for (int currentTrigger = 0; currentTrigger < NUM_TRIGGERS; currentTrigger++) {
byte currentPin = triggerMap[currentTrigger];
boolean currentState = triggerStates[currentTrigger];
if (currentTrigger != NULL_TRIGGER) {
Serial.print("T");
Serial.print(currentTrigger);
} else {
//Just to clarify that this isn't a normal trigger, we'll give it a special name
//Serial.print("TNULL");
//Actually, let's just not show the null trigger at all, since it's now guaranteed to never write to an actual pinout
continue;
}
Serial.print("->D");
Serial.print(currentPin);
Serial.print(currentState ? "[*] " : "[ ] ");
}
Serial.println(" END"); //I was seeing a bug with just using an empty string here where the serial monitor was appearing to get stuck in a loop, adding END seems to fix it for some reason.
}
boolean didUpdate(byte trigger, boolean state) {
//Does this trigger need an update? If so, update it and return true. Otherwise, return false.
if (trigger == NULL_TRIGGER) return false; //Skip the null trigger
if (state != triggerStates[trigger]) {
triggerStates[trigger] = state;
digitalWrite(triggerMap[trigger], state ? HIGH : LOW);
return true;
} else {
return false;
}
}
/*
tb_checkLSDJStopped counts how long the clock was on, if its been on too long we assume
LSDJ has stopped- Send a MIDI transport stop message and return true.
*/
boolean tb_checkLSDJStopped()
{
countClockPause++; //Increment the counter
if(countClockPause > 16000) { //if we've reached our waiting period
if(sequencerStarted) {
readgbClockLine=false;
countClockPause = 0; //reset our clock
lsdjTickCounter = 0; //Reset LSDJ tick count
if (usbMode) {
//Serial.println("LSDJ stopped");
} else {
Serial.write(0xFC); //send the transport stop message
}
sequencerStop(); //call the global sequencer stop function
}
return true;
}
return false;
}
/*
tb_sendMidiClockSlaveFromLSDJ waits for 8 clock bits from LSDJ,
sends the transport start command if sequencer hasnt started yet,
sends the midi clock tick, and sends a note value that corrisponds to
LSDJ's row number on start (LSDJ only sends this once when it starts)
*/
void tb_sendMidiClockSlaveFromLSDJ()
{
const byte lsdjTicksPerBeat = tickTriggerTicksPerStep * tickTriggerStepsPerBeat;
if(!countGbClockTicks) { //If we hit 8 bits
if(!sequencerStarted) { //If the sequencer hasnt started
sequencerStart(); //call the global sequencer start function
}
if (lsdjTickCounter == 0) {
//There have been enough ticks to make a beat
pendingTriggerStates[TICK_TRIGGER] = true;
pendingTriggerStates[TICK_TOGGLE_TRIGGER] = true;
}
lsdjTickCounter++;
if (lsdjTickCounter == lsdjTicksPerBeat) lsdjTickCounter = 0;
countGbClockTicks=0; //Reset the bit counter
//readGbSerialIn = 0x00; //Reset our serial read value
//updateVisualSync();
}
countGbClockTicks++; //Increment the bit counter
if(countGbClockTicks==8) countGbClockTicks=0;
countClockPause= 0; //Reset our wait timer for detecting a sequencer stop
}
void tb_assert(bool assertion, const char * description) {
//Holy hell. I'm jumping through some nice hoops to not use String... oh well!
const byte prefixSize = 22;
const byte descriptionMaxSize = 150;
const byte bufSize = prefixSize + descriptionMaxSize;
char descriptionPrefix[prefixSize] = "An assertion failed: ";
char descriptionFinal[bufSize];
char *descriptionFinalPtr = &descriptionFinal[0];
strncpy(descriptionFinalPtr, &descriptionPrefix[0], prefixSize);
strncpy(descriptionFinalPtr + (prefixSize - 1), description, descriptionMaxSize);
if (!assertion) fatalError(descriptionFinal);
}
void fatalError(const char * error) {
if (usbMode) {
Serial.print("FATAL ERROR: ");
Serial.println(error);
Serial.println("Please fix this issue and re-flash the Arduino.");
}
while(1) { //endless loop to stop main program from looping again
//Flash an SOS
morseBlink("sos ");
delay(3000);
}
}