-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathmenu_handler.cpp
More file actions
751 lines (660 loc) · 26.2 KB
/
menu_handler.cpp
File metadata and controls
751 lines (660 loc) · 26.2 KB
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
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
#include "menu_handler.h"
#include "morse_audio.h"
#include "settings_eeprom.h"
#include "config.h"
#include "equal_temperament.h"
#include <MIDIUSB.h>
#define SETTING_MODE_TIMEOUT 30000 // 30 seconds
// Valid keyer types for cycling (1-9, skipping 0=Passthrough)
#define KEYER_MIN 1
#define KEYER_MAX 9
// Module-level state
static MenuHandlerState menuState = {
MODE_NORMAL, // currentMode
12, // tempSpeedWPM (default 12 WPM = 100ms dit)
69, // tempToneNote (default A4 = 440Hz)
8, // tempKeyerType (default Iambic B)
0 // lastActivityTime
};
// Module-level references
static VailAdapter* adapter = nullptr;
static CWMemory* memorySlots = nullptr;
static RecordingState* recordingState = nullptr;
static PlaybackState* playbackState = nullptr;
static FlushBounceCallback flushBounceCallback = nullptr;
// ============================================================================
// Initialization
// ============================================================================
void initMenuHandler(VailAdapter* adapterRef,
CWMemory* memoryRef,
RecordingState* recordingRef,
PlaybackState* playbackRef,
FlushBounceCallback flushCallback) {
adapter = adapterRef;
memorySlots = memoryRef;
recordingState = recordingRef;
playbackState = playbackRef;
flushBounceCallback = flushCallback;
}
MenuHandlerState& getMenuState() {
return menuState;
}
// ============================================================================
// Conversion Utilities
// ============================================================================
int ditDurationToWPM(uint16_t ditDuration) {
// WPM = 1200 / dit_duration_ms
if (ditDuration == 0) return 12; // Safety
return 1200 / ditDuration;
}
uint16_t wpmToDitDuration(int wpm) {
// dit_duration_ms = 1200 / WPM
if (wpm <= 0) return 100; // Safety
return 1200 / wpm;
}
// ============================================================================
// Apply Temporary Settings (for testing before committing)
// ============================================================================
void applyTemporarySpeed(int wpm) {
if (!adapter) return;
// Apply speed to adapter without saving to EEPROM
// This allows testing the speed before committing
uint16_t newDitDuration = wpmToDitDuration(wpm);
midiEventPacket_t event;
event.header = 0x0B;
event.byte1 = 0xB0;
event.byte2 = 1;
event.byte3 = newDitDuration / (2 * MILLISECOND);
adapter->HandleMIDI(event);
}
void applyTemporaryTone(uint8_t noteNumber) {
if (!adapter) return;
// Apply tone to adapter without saving to EEPROM
// This allows testing the tone before committing
midiEventPacket_t event;
event.header = 0x0B;
event.byte1 = 0xB0;
event.byte2 = 2;
event.byte3 = noteNumber;
adapter->HandleMIDI(event);
}
void applyTemporaryKeyerType(uint8_t keyerType) {
if (!adapter) return;
// Apply keyer type to adapter without saving to EEPROM
// This allows testing the keyer type before committing
midiEventPacket_t event;
event.header = 0x0C;
event.byte1 = 0xC0;
event.byte2 = keyerType;
event.byte3 = 0;
adapter->HandleMIDI(event);
// Flush bounce state after mode change to prevent stuck keys
if (flushBounceCallback) {
flushBounceCallback();
}
}
// ============================================================================
// Button State to String Conversion
// ============================================================================
const char* buttonStateToString(ButtonState state) {
switch (state) {
case BTN_NONE: return "NONE";
case BTN_1: return "B1";
case BTN_2: return "B2";
case BTN_3: return "B3";
case BTN_1_2: return "B1+B2";
case BTN_1_3: return "B1+B3";
case BTN_2_3: return "B2+B3";
default: return "UNKNOWN";
}
}
// ============================================================================
// Quick Press Handlers (by mode)
// ============================================================================
static void handleQuickPressSpeedMode(ButtonState gestureDetected) {
if (gestureDetected == BTN_1) {
// Increase speed
menuState.tempSpeedWPM++;
if (menuState.tempSpeedWPM > 40) {
menuState.tempSpeedWPM = 40; // Clamp at max
playErrorTone();
Serial.println(" -> At maximum speed (40 WPM)");
} else {
applyTemporarySpeed(menuState.tempSpeedWPM); // Apply so user can test
playAdjustmentBeep(true); // Higher tone for increase
Serial.print(" -> Speed increased to ");
Serial.print(menuState.tempSpeedWPM);
Serial.println(" WPM");
}
} else if (gestureDetected == BTN_3) {
// Decrease speed
menuState.tempSpeedWPM--;
if (menuState.tempSpeedWPM < 5) {
menuState.tempSpeedWPM = 5; // Clamp at min
playErrorTone();
Serial.println(" -> At minimum speed (5 WPM)");
} else {
applyTemporarySpeed(menuState.tempSpeedWPM); // Apply so user can test
playAdjustmentBeep(false); // Lower tone for decrease
Serial.print(" -> Speed decreased to ");
Serial.print(menuState.tempSpeedWPM);
Serial.println(" WPM");
}
}
}
static void handleQuickPressToneMode(ButtonState gestureDetected) {
if (gestureDetected == BTN_1) {
// Increase tone (higher pitch)
menuState.tempToneNote++;
if (menuState.tempToneNote > 85) {
menuState.tempToneNote = 85; // Clamp at middle third max (MIDI 85)
playErrorTone();
Serial.println(" -> At maximum tone (MIDI 85)");
} else {
applyTemporaryTone(menuState.tempToneNote); // Apply so user can test
// Play a quick beep at the new tone
tone(PIEZO_PIN, equalTemperamentNote[menuState.tempToneNote]);
delay(100);
noTone(PIEZO_PIN);
Serial.print(" -> Tone increased to MIDI note ");
Serial.print(menuState.tempToneNote);
Serial.print(" (");
Serial.print(equalTemperamentNote[menuState.tempToneNote]);
Serial.println(" Hz)");
}
} else if (gestureDetected == BTN_3) {
// Decrease tone (lower pitch)
menuState.tempToneNote--;
if (menuState.tempToneNote < 43) {
menuState.tempToneNote = 43; // Clamp at middle third min (MIDI 43)
playErrorTone();
Serial.println(" -> At minimum tone (MIDI 43)");
} else {
applyTemporaryTone(menuState.tempToneNote); // Apply so user can test
// Play a quick beep at the new tone
tone(PIEZO_PIN, equalTemperamentNote[menuState.tempToneNote]);
delay(100);
noTone(PIEZO_PIN);
Serial.print(" -> Tone decreased to MIDI note ");
Serial.print(menuState.tempToneNote);
Serial.print(" (");
Serial.print(equalTemperamentNote[menuState.tempToneNote]);
Serial.println(" Hz)");
}
}
}
static void handleQuickPressKeyMode(ButtonState gestureDetected) {
if (gestureDetected == BTN_1) {
// Cycle to next keyer type (forward)
menuState.tempKeyerType++;
if (menuState.tempKeyerType > KEYER_MAX) {
menuState.tempKeyerType = KEYER_MIN; // Wrap around to beginning
}
applyTemporaryKeyerType(menuState.tempKeyerType); // Apply so user can test
playKeyerTypeCode(menuState.tempKeyerType); // Play Morse code identifier
Serial.print(" -> Keyer type changed to ");
Serial.println(getKeyerTypeName(menuState.tempKeyerType));
} else if (gestureDetected == BTN_3) {
// Cycle to previous keyer type (backward)
menuState.tempKeyerType--;
if (menuState.tempKeyerType < KEYER_MIN) {
menuState.tempKeyerType = KEYER_MAX; // Wrap around to end
}
applyTemporaryKeyerType(menuState.tempKeyerType); // Apply so user can test
playKeyerTypeCode(menuState.tempKeyerType); // Play Morse code identifier
Serial.print(" -> Keyer type changed to ");
Serial.println(getKeyerTypeName(menuState.tempKeyerType));
}
}
static void handleQuickPressNormalMode(ButtonState gestureDetected) {
// In normal mode: quick press plays memory via current output mode
uint8_t slotNumber = 0;
if (gestureDetected == BTN_1) slotNumber = 0;
else if (gestureDetected == BTN_2) slotNumber = 1;
else if (gestureDetected == BTN_3) slotNumber = 2;
else return; // Not a single button press
if (!memorySlots[slotNumber].isEmpty()) {
Serial.print(" -> Playing memory slot ");
Serial.print(slotNumber + 1);
Serial.println(" via current output mode");
startPlayback(*playbackState, slotNumber, memorySlots[slotNumber]);
menuState.currentMode = MODE_PLAYING_MEMORY;
} else {
Serial.print(" -> Memory slot ");
Serial.print(slotNumber + 1);
Serial.println(" is empty");
}
}
static void handleQuickPressRecordingMode(ButtonState gestureDetected) {
// In recording mode: single-click stops and saves recording
uint8_t activeSlot = (menuState.currentMode == MODE_RECORDING_MEMORY_1) ? 0 :
(menuState.currentMode == MODE_RECORDING_MEMORY_2) ? 1 : 2;
// Check if user clicked the same button that started recording
uint8_t clickedSlot = 0;
if (gestureDetected == BTN_1) clickedSlot = 0;
else if (gestureDetected == BTN_2) clickedSlot = 1;
else if (gestureDetected == BTN_3) clickedSlot = 2;
else return; // Not a single button press
if (clickedSlot == activeSlot) {
Serial.println(" -> Stopping recording (user-triggered)");
stopRecording(*recordingState, memorySlots[activeSlot]);
saveMemoryToEEPROM(activeSlot, memorySlots[activeSlot]);
// Play confirmation tone
playAdjustmentBeep(true);
delay(100);
playAdjustmentBeep(true);
menuState.currentMode = MODE_MEMORY_MANAGEMENT;
Serial.println(" -> Returned to memory management mode");
}
}
static void handleQuickPressMemoryManagementMode(ButtonState gestureDetected) {
// In memory management mode: quick press plays memory via piezo only
uint8_t slotNumber = 0;
if (gestureDetected == BTN_1) slotNumber = 0;
else if (gestureDetected == BTN_2) slotNumber = 1;
else if (gestureDetected == BTN_3) slotNumber = 2;
else return; // Not a single button press
Serial.print(" -> Attempting playback of slot ");
Serial.print(slotNumber + 1);
Serial.print(" - transitions: ");
Serial.print(memorySlots[slotNumber].transitionCount);
Serial.print(", duration: ");
Serial.print(memorySlots[slotNumber].getDurationMs());
Serial.println("ms");
if (!memorySlots[slotNumber].isEmpty()) {
Serial.println(" -> Starting playback (piezo only)");
startPlayback(*playbackState, slotNumber, memorySlots[slotNumber]);
// Note: Playback happens in the background via updatePlayback() in loop()
} else {
Serial.println(" -> ERROR: Memory slot is empty!");
}
}
// ============================================================================
// Long Press Handlers (by mode)
// ============================================================================
static void handleLongPressNormalMode(ButtonState currentState, unsigned long currentTime) {
// In normal mode: long press enters setting modes
switch(currentState) {
case BTN_1:
Serial.println(" - Entering SPEED mode");
playMorseWord("SPEED");
menuState.currentMode = MODE_SPEED_SETTING;
if (adapter) {
menuState.tempSpeedWPM = ditDurationToWPM(adapter->getDitDuration());
}
applyTemporarySpeed(menuState.tempSpeedWPM); // Apply current speed so user can test
menuState.lastActivityTime = currentTime; // Reset timeout timer
Serial.print("Current speed: ");
Serial.print(menuState.tempSpeedWPM);
Serial.println(" WPM");
break;
case BTN_2:
Serial.println(" - Entering TONE mode");
playMorseWord("TONE");
menuState.currentMode = MODE_TONE_SETTING;
if (adapter) {
menuState.tempToneNote = adapter->getTxNote();
}
// Clamp to middle third range (MIDI 43-85)
if (menuState.tempToneNote < 43) menuState.tempToneNote = 43;
if (menuState.tempToneNote > 85) menuState.tempToneNote = 85;
applyTemporaryTone(menuState.tempToneNote); // Apply current tone so user can test
menuState.lastActivityTime = currentTime; // Reset timeout timer
Serial.print("Current tone: MIDI note ");
Serial.print(menuState.tempToneNote);
Serial.print(" (");
Serial.print(equalTemperamentNote[menuState.tempToneNote]);
Serial.println(" Hz)");
break;
case BTN_3:
Serial.println(" - Entering KEY TYPE mode");
playMorseWord("KEY");
menuState.currentMode = MODE_KEY_SETTING;
if (adapter) {
menuState.tempKeyerType = adapter->getCurrentKeyerType();
}
// Ensure we're using a valid keyer type (1-9)
if (menuState.tempKeyerType < KEYER_MIN || menuState.tempKeyerType > KEYER_MAX) {
menuState.tempKeyerType = 8; // Default to Iambic B
}
applyTemporaryKeyerType(menuState.tempKeyerType); // Apply current keyer so user can test
menuState.lastActivityTime = currentTime; // Reset timeout timer
Serial.print("Current keyer type: ");
Serial.println(getKeyerTypeName(menuState.tempKeyerType));
break;
default:
Serial.println();
break;
}
}
static void handleLongPressSpeedMode(ButtonState currentState) {
// In speed mode: B2 long press saves and exits
if (currentState == BTN_2 && adapter) {
Serial.println(" - Saving and exiting SPEED mode");
// Convert WPM to dit duration and apply to adapter
uint16_t newDitDuration = wpmToDitDuration(menuState.tempSpeedWPM);
// Update adapter settings via MIDI commands (same as loadSettingsFromEEPROM does)
midiEventPacket_t event;
event.header = 0x0B;
event.byte1 = 0xB0;
event.byte2 = 1;
event.byte3 = newDitDuration / (2 * MILLISECOND);
adapter->HandleMIDI(event);
// Save to EEPROM
saveSettingsToEEPROM(adapter->getCurrentKeyerType(), newDitDuration, adapter->getTxNote());
Serial.print("Saved speed: ");
Serial.print(menuState.tempSpeedWPM);
Serial.print(" WPM (");
Serial.print(newDitDuration);
Serial.println("ms dit duration)");
// Play confirmation and return to normal mode
playMorseWord("RR");
menuState.currentMode = MODE_NORMAL;
}
}
static void handleLongPressToneMode(ButtonState currentState) {
// In tone mode: B2 long press saves and exits
if (currentState == BTN_2 && adapter) {
Serial.println(" - Saving and exiting TONE mode");
// Update adapter settings via MIDI commands
midiEventPacket_t event;
event.header = 0x0B;
event.byte1 = 0xB0;
event.byte2 = 2;
event.byte3 = menuState.tempToneNote;
adapter->HandleMIDI(event);
// Save to EEPROM
saveSettingsToEEPROM(adapter->getCurrentKeyerType(), adapter->getDitDuration(), menuState.tempToneNote);
Serial.print("Saved tone: MIDI note ");
Serial.print(menuState.tempToneNote);
Serial.print(" (");
Serial.print(equalTemperamentNote[menuState.tempToneNote]);
Serial.println(" Hz)");
// Play confirmation and return to normal mode
playMorseWord("RR");
menuState.currentMode = MODE_NORMAL;
}
}
static void handleLongPressKeyMode(ButtonState currentState) {
// In key type mode: B2 long press saves and exits
if (currentState == BTN_2 && adapter) {
Serial.println(" - Saving and exiting KEY TYPE mode");
// Update adapter settings via MIDI commands
midiEventPacket_t event;
event.header = 0x0C;
event.byte1 = 0xC0;
event.byte2 = menuState.tempKeyerType;
event.byte3 = 0;
adapter->HandleMIDI(event);
// Flush bounce state after final mode switch to prevent stuck keys
if (flushBounceCallback) {
flushBounceCallback();
}
// Save to EEPROM
saveSettingsToEEPROM(menuState.tempKeyerType, adapter->getDitDuration(), adapter->getTxNote());
Serial.print("Saved keyer type: ");
Serial.println(getKeyerTypeName(menuState.tempKeyerType));
// Play confirmation and return to normal mode
playMorseWord("RR");
menuState.currentMode = MODE_NORMAL;
}
}
static void handleLongPressMemoryManagementMode(ButtonState currentState) {
// In memory management mode: long press clears the memory slot
uint8_t slotNumber = 0;
if (currentState == BTN_1) slotNumber = 0;
else if (currentState == BTN_2) slotNumber = 1;
else if (currentState == BTN_3) slotNumber = 2;
else {
Serial.println();
return;
}
Serial.print(" - Clearing memory slot ");
Serial.println(slotNumber + 1);
memorySlots[slotNumber].clear();
clearMemoryInEEPROM(slotNumber);
playMemoryClearedAnnouncement(slotNumber);
}
// ============================================================================
// Timeout Handlers (by mode)
// ============================================================================
static void handleTimeoutSpeedMode(unsigned long currentTime) {
if ((currentTime - menuState.lastActivityTime) >= SETTING_MODE_TIMEOUT && adapter) {
Serial.println(">>> TIMEOUT - Auto-saving and exiting SPEED mode");
// Save current settings
uint16_t newDitDuration = wpmToDitDuration(menuState.tempSpeedWPM);
midiEventPacket_t event;
event.header = 0x0B;
event.byte1 = 0xB0;
event.byte2 = 1;
event.byte3 = newDitDuration / (2 * MILLISECOND);
adapter->HandleMIDI(event);
saveSettingsToEEPROM(adapter->getCurrentKeyerType(), newDitDuration, adapter->getTxNote());
Serial.print("Auto-saved speed: ");
Serial.print(menuState.tempSpeedWPM);
Serial.println(" WPM");
// Play descending tones and return to normal mode
playDescendingTones();
menuState.currentMode = MODE_NORMAL;
}
}
static void handleTimeoutToneMode(unsigned long currentTime) {
if ((currentTime - menuState.lastActivityTime) >= SETTING_MODE_TIMEOUT && adapter) {
Serial.println(">>> TIMEOUT - Auto-saving and exiting TONE mode");
// Save current settings
midiEventPacket_t event;
event.header = 0x0B;
event.byte1 = 0xB0;
event.byte2 = 2;
event.byte3 = menuState.tempToneNote;
adapter->HandleMIDI(event);
saveSettingsToEEPROM(adapter->getCurrentKeyerType(), adapter->getDitDuration(), menuState.tempToneNote);
Serial.print("Auto-saved tone: MIDI note ");
Serial.print(menuState.tempToneNote);
Serial.print(" (");
Serial.print(equalTemperamentNote[menuState.tempToneNote]);
Serial.println(" Hz)");
// Play descending tones and return to normal mode
playDescendingTones();
menuState.currentMode = MODE_NORMAL;
}
}
static void handleTimeoutKeyMode(unsigned long currentTime) {
if ((currentTime - menuState.lastActivityTime) >= SETTING_MODE_TIMEOUT && adapter) {
Serial.println(">>> TIMEOUT - Auto-saving and exiting KEY TYPE mode");
// Save current settings
midiEventPacket_t event;
event.header = 0x0C;
event.byte1 = 0xC0;
event.byte2 = menuState.tempKeyerType;
event.byte3 = 0;
adapter->HandleMIDI(event);
saveSettingsToEEPROM(menuState.tempKeyerType, adapter->getDitDuration(), adapter->getTxNote());
Serial.print("Auto-saved keyer type: ");
Serial.println(getKeyerTypeName(menuState.tempKeyerType));
// Play descending tones and return to normal mode
playDescendingTones();
menuState.currentMode = MODE_NORMAL;
}
}
// ============================================================================
// Main Menu Update Function
// ============================================================================
void updateMenuHandler(unsigned long currentTime, ButtonDebouncer& buttonDebouncer) {
if (!adapter || !memorySlots || !recordingState || !playbackState) return;
// Read button state with debouncing
int analogVal = readButtonAnalog();
ButtonState currentButtonState = getButtonState(analogVal);
// Reset activity timer on any button press in setting modes
if (currentButtonState != BTN_NONE && menuState.currentMode != MODE_NORMAL) {
menuState.lastActivityTime = currentTime;
}
// Update debouncer - returns true when complete gesture detected (press & release)
if (buttonDebouncer.update(currentButtonState, currentTime)) {
ButtonState gestureDetected = buttonDebouncer.getMaxState();
unsigned long duration = buttonDebouncer.getLastPressDuration();
Serial.print("Button Gesture: ");
Serial.print(buttonStateToString(gestureDetected));
// Check for double-click FIRST to trigger recording (before handling quick press)
bool isDoubleClick = buttonDebouncer.isDoubleClick();
if (isDoubleClick && menuState.currentMode == MODE_MEMORY_MANAGEMENT) {
// Only allow double-click recording in memory management mode
uint8_t slotNumber = 0;
if (gestureDetected == BTN_1) slotNumber = 0;
else if (gestureDetected == BTN_2) slotNumber = 1;
else if (gestureDetected == BTN_3) slotNumber = 2;
else return; // Not a single button double-click
Serial.print(" [DOUBLE-CLICK]");
Serial.print(">>> DOUBLE-CLICK DETECTED on Button ");
Serial.print(slotNumber + 1);
Serial.println(" - Starting recording...");
// Stop any ongoing playback before starting recording
if (playbackState->isPlaying) {
Serial.println("Stopping playback before starting recording");
playbackState->stopPlayback();
}
// Play countdown: "doot, doot, dah" (3 beeps with the last one longer)
playRecordingCountdown();
// Start recording
startRecording(*recordingState, slotNumber);
// Switch to recording mode
if (slotNumber == 0) menuState.currentMode = MODE_RECORDING_MEMORY_1;
else if (slotNumber == 1) menuState.currentMode = MODE_RECORDING_MEMORY_2;
else if (slotNumber == 2) menuState.currentMode = MODE_RECORDING_MEMORY_3;
Serial.print("Entered recording mode for memory slot ");
Serial.println(slotNumber + 1);
return; // Exit early - don't process as quick press
}
if (duration >= 2000) {
Serial.print(" [LONG PRESS - ");
Serial.print(duration);
Serial.println("ms]");
} else {
Serial.print(" [quick press - ");
Serial.print(duration);
Serial.println("ms]");
// Handle quick presses based on current mode (only if NOT a double-click)
switch (menuState.currentMode) {
case MODE_SPEED_SETTING:
handleQuickPressSpeedMode(gestureDetected);
break;
case MODE_TONE_SETTING:
handleQuickPressToneMode(gestureDetected);
break;
case MODE_KEY_SETTING:
handleQuickPressKeyMode(gestureDetected);
break;
case MODE_NORMAL:
handleQuickPressNormalMode(gestureDetected);
break;
case MODE_RECORDING_MEMORY_1:
case MODE_RECORDING_MEMORY_2:
case MODE_RECORDING_MEMORY_3:
handleQuickPressRecordingMode(gestureDetected);
break;
case MODE_MEMORY_MANAGEMENT:
handleQuickPressMemoryManagementMode(gestureDetected);
break;
default:
break;
}
}
}
// Check for long press threshold (2 seconds) - fires once while button still held
if (buttonDebouncer.isLongPress(currentTime)) {
ButtonState currentState = buttonDebouncer.getMaxState();
Serial.print(">>> LONG PRESS DETECTED: ");
Serial.print(buttonStateToString(currentState));
// Handle based on current mode
switch (menuState.currentMode) {
case MODE_NORMAL:
handleLongPressNormalMode(currentState, currentTime);
break;
case MODE_SPEED_SETTING:
handleLongPressSpeedMode(currentState);
break;
case MODE_TONE_SETTING:
handleLongPressToneMode(currentState);
break;
case MODE_KEY_SETTING:
handleLongPressKeyMode(currentState);
break;
case MODE_MEMORY_MANAGEMENT:
handleLongPressMemoryManagementMode(currentState);
break;
default:
break;
}
}
// Check for combo press threshold (0.5 seconds) - fires once while buttons still held
if (buttonDebouncer.isComboPress(currentTime)) {
ButtonState currentState = buttonDebouncer.getMaxState();
Serial.print(">>> COMBO PRESS DETECTED: ");
Serial.print(buttonStateToString(currentState));
// B1+B3 combo toggles memory management mode
if (currentState == BTN_1_3) {
if (menuState.currentMode == MODE_NORMAL) {
// Don't allow entering memory management mode in radio mode
if (adapter->isRadioModeActive()) {
Serial.println(" - Cannot enter MEMORY MANAGEMENT mode while in radio mode");
playErrorTone();
} else {
Serial.println(" - Entering MEMORY MANAGEMENT mode");
playMorseWord("MEM");
menuState.currentMode = MODE_MEMORY_MANAGEMENT;
}
} else if (menuState.currentMode == MODE_MEMORY_MANAGEMENT) {
Serial.println(" - Exiting MEMORY MANAGEMENT mode");
playDescendingTones();
menuState.currentMode = MODE_NORMAL;
}
} else {
Serial.println();
}
}
// Check for MIDI switch press threshold (3 seconds) - fires once while B1+B2 still held
// ONLY active in MODE_NORMAL to avoid conflicts with other modes
if (buttonDebouncer.isMidiSwitchPress(currentTime) && menuState.currentMode == MODE_NORMAL) {
Serial.print(">>> MIDI SWITCH PRESS DETECTED (3s B1+B2): ");
if (adapter) {
bool currentMode = adapter->KeyboardMode();
if (currentMode) {
// Currently in keyboard mode, switch to MIDI mode
Serial.println("Switching from Keyboard to MIDI mode");
midiEventPacket_t event;
event.header = 0x0B;
event.byte1 = 0xB0;
event.byte2 = 0;
event.byte3 = 0x00; // 0x00 = MIDI mode (< 0x3f)
adapter->HandleMIDI(event);
playMorseChar('M'); // M
playMorseChar('M'); // M -> "MM" = MIDI Mode
} else {
// Currently in MIDI mode, switch to keyboard mode
Serial.println("Switching from MIDI to Keyboard mode");
midiEventPacket_t event;
event.header = 0x0B;
event.byte1 = 0xB0;
event.byte2 = 0;
event.byte3 = 0x7F; // 0x7F = Keyboard mode (> 0x3f)
adapter->HandleMIDI(event);
playMorseChar('K'); // K
playMorseChar('M'); // M -> "KM" = Keyboard Mode
}
}
}
// Check for timeout in setting modes
switch (menuState.currentMode) {
case MODE_SPEED_SETTING:
handleTimeoutSpeedMode(currentTime);
break;
case MODE_TONE_SETTING:
handleTimeoutToneMode(currentTime);
break;
case MODE_KEY_SETTING:
handleTimeoutKeyMode(currentTime);
break;
default:
break;
}
}