// While saber is DEACTIVATED: // Start/Stop Track: Click Aux // Next Track: Hold Aux while track is playing // Next Preset: Hold Aux when track is not playing // Check Battery: Double-click Aux // Activate Saber: Press Power // // While saber is ACTIVATED: // Clash: Click Power (This generates a clash effect with no impact to the blade) // Lockup: Hold Power (This triggers a clash and then a lockup, with no impact to the blade) // Blaster Block: Click Aux // Lightning Block: Hold Aux // Lockup: Hold Aux during impact // Drag: Hold Aux during impact with saber pointed down. // Melt: Hold Aux and stab // Enter Volume Menu: Double-click and hold Aux (Be aware that the first click will trigger a blaster block) // Increase Volume: Rotate hilt right while in Volume Menu // Decrease Volume: Rotate hilt left while in Volume Menu // Save Changes and Exit Volume Menu: Click Power // Discard Changes and Exit Volume Menu: Click Aux // Enter Color Change Mode (Color Wheel): Triple-click and hold Aux (Be aware that the first two clicks will trigger blaster blocks) // Change Color: Rotate hilt // Save Changes and Exit Color Change Mode: Click Power // Reset Color and Exit Color Change mode: Click Aux // Deactivate Saber: Hold Aux and press Power #ifndef PROPS_CAIWYN_BUTTONS_H #define PROPS_CAIWYN_BUTTONS_H #include "prop_base.h" #include "../sound/hybrid_font.h" #include "../sound/sound_library.h" #undef PROP_TYPE #define PROP_TYPE CaiwynButtons #ifndef NUM_BUTTONS #define NUM_BUTTONS 2 #endif #if NUM_BUTTONS < 2 #error /props/saber_caiwyn_buttons.h requires 2 or 3 Buttons for operation #endif #ifndef BUTTON_DOUBLE_CLICK_TIMEOUT #define BUTTON_DOUBLE_CLICK_TIMEOUT 300 #endif #ifndef BUTTON_SHORT_CLICK_TIMEOUT #define BUTTON_SHORT_CLICK_TIMEOUT 300 #endif #ifndef BUTTON_HELD_TIMEOUT #define BUTTON_HELD_TIMEOUT 300 #endif #ifndef BUTTON_HELD_MEDIUM_TIMEOUT #define BUTTON_HELD_MEDIUM_TIMEOUT 1000 #endif #ifndef BUTTON_HELD_LONG_TIMEOUT #define BUTTON_HELD_LONG_TIMEOUT 2000 #endif EFFECT(vmbegin); // Volume Change Menu EFFECT(vmend); // Save Volume Change EFFECT(vmcancel); // Cancel Volume Change EFFECT(monce); // Play Single Track EFFECT(mloop); // Repeat Single Track EFFECT(mrotate); // Repeat All Tracks EFFECT(mrandom); // Play Random Tracks // The Saber class implements the basic states and actions for the saber. class CaiwynButtons : public PropBase { public: CaiwynButtons() : PropBase() {} const char* name() override { return "CaiwynButtons"; } void Loop() override { PropBase::Loop(); DetectTwist(); TrackPlayer(); } bool Parse(const char *cmd, const char* arg) override { if (PropBase::Parse(cmd, arg)) return true; if (!strcmp(cmd, "list_current_tracks")) { // Tracks must be in: font/tracks/*.wav LOCK_SD(true); for (const char* dir = current_directory; dir; dir = next_current_directory(dir)) { PathHelper path(dir, "tracks"); ListTracks(path); } LOCK_SD(false); return true; } return false; } void VolumeUp() { STDOUT.println("Volume up"); if (dynamic_mixer.get_volume() < VOLUME) { dynamic_mixer.set_volume(std::min(VOLUME,dynamic_mixer.get_volume() + VOLUME * 0.10)); STDOUT.print("Current Volume: "); STDOUT.println(dynamic_mixer.get_volume()); } } void VolumeDown() { STDOUT.println("Volume Down"); if (dynamic_mixer.get_volume() > 0) { dynamic_mixer.set_volume(std::max(0,dynamic_mixer.get_volume() - VOLUME * 0.10)); STDOUT.print("Current Volume: "); STDOUT.println(dynamic_mixer.get_volume()); } } // Fett263 Track Player, modified by Caiwyn enum TrackMode { PLAYBACK_ONCE, PLAYBACK_LOOP, PLAYBACK_ROTATE, PLAYBACK_RANDOM, }; void TrackPlayer() { if (track_mode_ != PLAYBACK_ONCE) { if (!track_player_) { if (track_num_ <= 0 && track_mode_ == PLAYBACK_LOOP) { StartOrStopTrack(); } else { switch (track_mode_) { case PLAYBACK_ROTATE: track_num_ += 1; if (track_num_ > num_tracks_) track_num_ = 1; break; case PLAYBACK_RANDOM: track_num_ = rand() % num_tracks_; if (track_num_ <= 0) track_num_ = num_tracks_; break; default: break; } PlayTrack(); } } } else { if (track_player_ && !track_player_->isPlaying()) { track_player_.Free(); } } } void PlayTrack() { char playtrack[128]; RunCommandAndGetSingleLine("list_current_tracks", nullptr, track_num_, playtrack, sizeof(playtrack)); MountSDCard(); EnableAmplifier(); track_player_ = GetFreeWavPlayer(); if (track_player_) { track_player_->Play(playtrack); } else { STDOUT.println("No available WAV players."); } } void StartTrackPlayer() { num_tracks_ = RunCommandAndGetSingleLine("list_current_tracks", nullptr, 0, 0, 0); if (num_tracks_ > 0) { PlayTrack(); } else { // Play default track if tracks not found track_num_ = 0; StartOrStopTrack(); } } void StopTrackPlayer() { if (track_player_) { track_player_->Stop(); track_player_.Free(); } else { StartOrStopTrack(); } } void NextTrack() { if (num_tracks_ > 0) { track_player_->Stop(); track_player_.Free(); track_num_ += 1; if (track_num_ > num_tracks_) track_num_ = 1; PlayTrack(); } else { StartOrStopTrack(); StartOrStopTrack(); } } #ifndef DISABLE_COLOR_CHANGE // Revert color change without saving (reset to Variation == 0) void ResetColorChangeMode() { if (!current_style()) return; STDOUT << "Reset Color Variation" << "\n"; SetVariation(0); STDOUT << "Color change mode done, variation = 0" << "\n"; SaberBase::SetColorChangeMode(SaberBase::COLOR_CHANGE_MODE_NONE); } #endif bool Event2(enum BUTTON button, EVENT event, uint32_t modifiers) override { switch (EVENTID(button, event, modifiers)) { case EVENTID(BUTTON_AUX, EVENT_FIRST_PRESSED, MODE_OFF): on_when_pressed_ = false; return true; case EVENTID(BUTTON_AUX, EVENT_FIRST_PRESSED, MODE_ON): on_when_pressed_ = true; return true; // Activate Saber case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_OFF): on_when_pressed_ = false; On(); return true; // Activate Saber and Play Track case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_OFF | BUTTON_AUX): #if NUM_BUTTONS > 2 case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_OFF | BUTTON_AUX2): #endif on_when_pressed_ = false; On(); if (!track_player_) { StartTrackPlayer(); } return true; // Deactivate Saber case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_ON | BUTTON_AUX): #if NUM_BUTTONS > 2 case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_ON | BUTTON_AUX2): #endif on_when_pressed_ = true; if (!mode_volume_ && (SaberBase::GetColorChangeMode() == SaberBase::COLOR_CHANGE_MODE_NONE)) { Off(); return true; } break; // Start or Stop Track case EVENTID(BUTTON_AUX, EVENT_FIRST_SAVED_CLICK_SHORT, MODE_OFF): if (!on_when_pressed_) { if (track_player_) { StopTrackPlayer(); } else { StartTrackPlayer(); } return true; } break; // Next Preset/Track case EVENTID(BUTTON_AUX, EVENT_FIRST_HELD_MEDIUM, MODE_OFF): if (!on_when_pressed_) { beeper.Beep(0.1, 2000); if (track_player_) { NextTrack(); } else { next_preset(); track_num_ = 1; } return true; } break; // Battery Level case EVENTID(BUTTON_AUX, EVENT_SECOND_CLICK_SHORT, MODE_OFF): if (!on_when_pressed_) { sound_library_.SayBatteryLevel(); sound_library_.SayNumber(battery_monitor.battery_percent(), SAY_WHOLE); sound_library_.SayPercent(); // talkie.SayDigit((int)floorf(battery_monitor.battery())); // talkie.Say(spPOINT); // talkie.SayDigit(((int)floorf(battery_monitor.battery() * 10)) % 10); // talkie.SayDigit(((int)floorf(battery_monitor.battery() * 100)) % 10); // talkie.Say(spVOLTS); return true; } break; // Track Playback Mode case EVENTID(BUTTON_AUX, EVENT_SECOND_HELD_MEDIUM, MODE_OFF): if (!on_when_pressed_) { beeper.Beep(0.1, 2000); switch (track_mode_) { case PLAYBACK_ONCE: track_mode_ = PLAYBACK_LOOP; if (SFX_mloop) { hybrid_font.PlayPolyphonic(&SFX_mloop); } break; case PLAYBACK_LOOP: track_mode_ = PLAYBACK_ROTATE; if (SFX_mrotate) { hybrid_font.PlayPolyphonic(&SFX_mrotate); } break; #ifndef DISABLE_RANDOM_TRACKS case PLAYBACK_ROTATE: track_mode_ = PLAYBACK_RANDOM; if (SFX_mrandom) { hybrid_font.PlayPolyphonic(&SFX_mrandom); } break; #endif default: track_mode_ = PLAYBACK_ONCE; if (SFX_monce) { hybrid_font.PlayPolyphonic(&SFX_monce); } break; } } // Clash case EVENTID(BUTTON_POWER, EVENT_PRESSED, MODE_ON): on_when_pressed_ = true; if (!mode_volume_ && (SaberBase::GetColorChangeMode() == SaberBase::COLOR_CHANGE_MODE_NONE)) { SaberBase::DoClash(); return true; } break; // Save Volume / Save Color case EVENTID(BUTTON_POWER, EVENT_CLICK_SHORT, MODE_ON): if (mode_volume_) { mode_volume_ = false; beeper.Beep(0.1, 2000); if (SFX_vmend) { hybrid_font.PlayPolyphonic(&SFX_vmend); } STDOUT.println("Exit Volume Menu"); return true; #ifndef DISABLE_COLOR_CHANGE } else if (SaberBase::GetColorChangeMode() != SaberBase::COLOR_CHANGE_MODE_NONE) { if (SFX_ccend) { SFX_ccend.Select(0); beeper.Beep(0.1, 2000); } ToggleColorChangeMode(); #endif } break; // Cancel Volume / Reset Color / Blaster Block case EVENTID(BUTTON_AUX, EVENT_CLICK_SHORT, MODE_ON): if (mode_volume_) { dynamic_mixer.set_volume(reset_volume_); mode_volume_ = false; beeper.Beep(0.1, 2000); if (SFX_vmcancel) { hybrid_font.PlayPolyphonic(&SFX_vmcancel); } STDOUT.println("Volume Change Cancelled"); STDOUT.print("Current Volume: "); STDOUT.println(dynamic_mixer.get_volume()); #ifndef DISABLE_COLOR_CHANGE } else if (SaberBase::GetColorChangeMode() != SaberBase::COLOR_CHANGE_MODE_NONE) { if (SFX_ccend) { SFX_ccend.Select(1); beeper.Beep(0.1, 2000); } ResetColorChangeMode(); #endif } else if (on_when_pressed_) { SaberBase::DoBlast(); return true; } break; // Lockup/Drag case EVENTID(BUTTON_NONE, EVENT_CLASH, MODE_ON | BUTTON_AUX): if (!SaberBase::Lockup() && !mode_volume_ && (SaberBase::GetColorChangeMode() == SaberBase::COLOR_CHANGE_MODE_NONE)) { if (accel_.x < -0.15) { SaberBase::SetLockup(SaberBase::LOCKUP_DRAG); } else { SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); } SaberBase::DoBeginLockup(); return true; } break; // Non-Clash Lockup case EVENTID(BUTTON_POWER, EVENT_HELD, MODE_ON): if (!SaberBase::Lockup() && !mode_volume_ && on_when_pressed_ && (SaberBase::GetColorChangeMode() == SaberBase::COLOR_CHANGE_MODE_NONE)) { SaberBase::SetLockup(SaberBase::LOCKUP_NORMAL); SaberBase::DoBeginLockup(); return true; } break; // Lightning Block case EVENTID(BUTTON_AUX, EVENT_FIRST_HELD, MODE_ON): if (!SaberBase::Lockup() && !mode_volume_ && on_when_pressed_ && (SaberBase::GetColorChangeMode() == SaberBase::COLOR_CHANGE_MODE_NONE)) { SaberBase::SetLockup(SaberBase::LOCKUP_LIGHTNING_BLOCK); SaberBase::DoBeginLockup(); return true; } break; // Melt case EVENTID(BUTTON_NONE, EVENT_STAB, MODE_ON | BUTTON_AUX): if (!SaberBase::Lockup() && !mode_volume_ && (SaberBase::GetColorChangeMode() == SaberBase::COLOR_CHANGE_MODE_NONE)) { SaberBase::SetLockup(SaberBase::LOCKUP_MELT); SaberBase::DoBeginLockup(); return true; } break; // Enter Volume Menu case EVENTID(BUTTON_AUX, EVENT_SECOND_HELD_MEDIUM, MODE_ON): if (!mode_volume_ && on_when_pressed_ && (SaberBase::GetColorChangeMode() == SaberBase::COLOR_CHANGE_MODE_NONE)) { reset_volume_ = dynamic_mixer.get_volume(); mode_volume_ = true; beeper.Beep(0.1, 2000); if (SFX_vmbegin) { hybrid_font.PlayPolyphonic(&SFX_vmbegin); } STDOUT.println("Enter Volume Menu"); return true; } break; // Volume Up case EVENTID(BUTTON_NONE, EVENT_TWIST_RIGHT, MODE_ON): if (mode_volume_) { VolumeUp(); return true; } break; // Volume Down case EVENTID(BUTTON_NONE, EVENT_TWIST_LEFT, MODE_ON): if (mode_volume_) { VolumeDown(); return true; } break; // Color Change #ifndef DISABLE_COLOR_CHANGE case EVENTID(BUTTON_AUX, EVENT_THIRD_HELD_MEDIUM, MODE_ON): if (!mode_volume_ && on_when_pressed_) { if (SFX_ccbegin) { beeper.Beep(0.1, 2000); } ToggleColorChangeMode(); return true; } break; #endif #ifdef BLADE_DETECT_PIN case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_ON, MODE_ANY_BUTTON | MODE_OFF): // Might need to do something cleaner, but let's try this for now. blade_detected_ = true; FindBladeAgain(); SaberBase::DoBladeDetect(true); return true; case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_BLADE_DETECT, EVENT_LATCH_OFF, MODE_ANY_BUTTON | MODE_OFF): // Might need to do something cleaner, but let's try this for now. blade_detected_ = false; FindBladeAgain(); SaberBase::DoBladeDetect(false); return true; #endif // Events that need to be handled regardless of what other buttons are pressed. case EVENTID(BUTTON_POWER, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_AUX, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): case EVENTID(BUTTON_AUX2, EVENT_RELEASED, MODE_ANY_BUTTON | MODE_ON): if (SaberBase::Lockup()) { SaberBase::DoEndLockup(); SaberBase::SetLockup(SaberBase::LOCKUP_NONE); return true; } } return false; } private: bool on_when_pressed_ = false; bool mode_volume_ = false; int32_t reset_volume_ = dynamic_mixer.get_volume(); TrackMode track_mode_ = PLAYBACK_ONCE; int track_num_ = 1; // Track Number for Track Player int num_tracks_; // Number of Tracks Found }; #endif