Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
717c508
Add files via upload
NoSloppy Aug 20, 2026
2bcb271
Update detonator_BC_buttons.h with new features
NoSloppy Aug 20, 2026
265a332
Enhance detonator_BC_buttons.h with new features
NoSloppy Aug 20, 2026
2c7dac1
Update detonator_BC_buttons.h with new features
NoSloppy Aug 21, 2026
5578996
Update detonator_BC_buttons.h with new comments
NoSloppy Aug 22, 2026
1da3a75
Merge pull request #5 from NoSloppy/add-stab-as-clash
NoSloppy Aug 22, 2026
d0667cc
Add files via upload
NoSloppy Aug 23, 2026
8b7f0a6
Add files via upload
NoSloppy Aug 23, 2026
d613512
Add files via upload
NoSloppy Aug 23, 2026
b79de48
Merge pull request #6 from NoSloppy/semi-final-BC-Detonator-buttons
NoSloppy Aug 23, 2026
6de8241
pdated comments
NoSloppy Aug 26, 2026
7273f64
Add files via upload
NoSloppy Sep 7, 2026
d9536d9
remove fix attempt
NoSloppy Sep 7, 2026
1a1f96a
indention
NoSloppy Sep 7, 2026
6c8d73f
fix boom
NoSloppy Sep 7, 2026
984f64d
Merge pull request #7 from NoSloppy/update-detonatorBC
NoSloppy Sep 7, 2026
cf00b10
better alt switching
NoSloppy Sep 8, 2026
a9082b2
Merge pull request #9 from profezzorn/master
NoSloppy Sep 8, 2026
9533bf4
Delete config/KR_TD_configBC.h
NoSloppy Sep 8, 2026
d8e629f
Delete config/KR_TD_configBC_saberTest.h
NoSloppy Sep 8, 2026
93aac6a
Delete props/detonator_BC_buttons.h
NoSloppy Sep 8, 2026
82bfe10
Add files via upload
NoSloppy Sep 8, 2026
b5af3ac
Add files via upload
NoSloppy Sep 8, 2026
cc4e87f
Add files via upload
NoSloppy Sep 8, 2026
41b9ed7
Add files via upload
NoSloppy Sep 8, 2026
3b072e0
Add files via upload
NoSloppy Sep 8, 2026
d0f6330
Add files via upload
NoSloppy Sep 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions common/delay_timer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#ifndef DELAY_TIMER_H
#define DELAY_TIMER_H

class DelayTimer {
public:
void Start(uint32_t duration_ms) {
uint32_t now = millis();
deadline_ms_ = now + duration_ms;
PVLOG_DEBUG << "*** TIMER Start: " << duration_ms
<< " ms, now=" << now
<< " deadline=" << deadline_ms_ << "\n";
}

void Append(uint32_t duration_ms) {
uint32_t now = millis();
uint32_t old_deadline = deadline_ms_;
if (deadline_ms_ < now) deadline_ms_ = now;
deadline_ms_ += duration_ms;
PVLOG_DEBUG << "*** TIMER Append: " << duration_ms
<< " ms, now=" << now
<< " old=" << old_deadline
<< " new=" << deadline_ms_ << "\n";
}

void ExtendTo(uint32_t deadline_ms) {
if (deadline_ms > deadline_ms_) {
PVLOG_DEBUG << "*** TIMER ExtendTo: old=" << deadline_ms_
<< " new=" << deadline_ms << "\n";
deadline_ms_ = deadline_ms;
}
}

bool Active() const {
return millis() < deadline_ms_;
}

// Milliseconds left until the deadline, 0 if it has passed.
uint32_t remaining() const {
uint32_t now = millis();
return (now < deadline_ms_) ? (deadline_ms_ - now) : 0;
}

bool Expired() {
uint32_t now = millis();
if (deadline_ms_ == 0 || now < deadline_ms_) return false;
PVLOG_DEBUG << "*** TIMER EXPIRED: now=" << now
<< " deadline=" << deadline_ms_ << "\n";
deadline_ms_ = 0;
return true;
}

void Stop() {
deadline_ms_ = 0;
}

uint32_t deadline() const {
return deadline_ms_;
}

private:
uint32_t deadline_ms_ = 0;
};

inline DelayTimer& delay_timer() {
static DelayTimer timer;
return timer;
}

#endif // DELAY_TIMER_H
135 changes: 81 additions & 54 deletions common/errors.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@
#ifndef COMMON_ERROR_H_DECLARED
#define COMMON_ERROR_H_DECLARED

#include "delay_timer.h"

// Ducks all wav players for a while, so that Talkie/beeper error messages
// remain intelligible when a prop has other sounds playing.
#ifdef ENABLE_AUDIO
void DodgeSound(uint32_t millis);
#else
#define DodgeSound(X) X
#endif

// Set by PlayErrorMessage() when a WAV has been queued for the error that is
// currently being reported, which means we should not also say it with Talkie.
inline bool& error_wav_queued() {
static bool queued = false;
return queued;
}

class ProffieOSErrors {
public:
static void sd_card_not_found();
Expand Down Expand Up @@ -42,125 +53,141 @@ void ProffieOSErrors::sd_card_not_found() {
}

void ProffieOSErrors::font_directory_not_found() {
error_wav_queued() = false;
SaberBase::DoEffect(EFFECT_FONT_DIRECTORY_NOT_FOUND, 0);
PVLOG_ERROR << "** ERROR - Font directory not found.\n"
"** See https://pod.hubbe.net/troubleshooting/what-is-it-saying.html#font-directory-not-found\n";
#ifdef ENABLE_AUDIO
if (SaberBase::sound_length > 0) return;
if (error_wav_queued()) return;
#ifndef DISABLE_TALKIE
talkie.Say(talkie_font_directory_15, 15);
talkie.Say(talkie_not_found_15, 15);
DodgeSound(2000);
delay_timer().Append(2000);
// Duck any other sounds for as long as we will be talking.
DodgeSound(delay_timer().remaining());
#else
beeper.Beep(0.5, 261.63 * 2); // C5 - Font
beeper.Beep(0.5/3, 246.94 * 2); // B4 - di
beeper.Beep(0.5/3, 220.00 * 2); // A4 - rec
beeper.Beep(0.5/3, 196.00 * 2); // G4 - tor
beeper.Beep(0.5, 174.61 * 2); // F4 - y
beeper.Beep(0.5, 146.83 * 2); // D4 - not
beeper.Beep(0.5, 130.81 * 2); // C4 - found
DodgeSound(2530);
beeper.Beep(0.5, 261.63 * 2); // C5 - Font
beeper.Beep(0.5/3, 246.94 * 2); // B4 - di
beeper.Beep(0.5/3, 220.00 * 2); // A4 - rec
beeper.Beep(0.5/3, 196.00 * 2); // G4 - tor
beeper.Beep(0.5, 174.61 * 2); // F4 - y
beeper.Beep(0.5, 146.83 * 2); // D4 - not
beeper.Beep(0.5, 130.81 * 2); // C4 - found
delay_timer().Append(2530);
DodgeSound(delay_timer().remaining());
#endif
#endif
}

void ProffieOSErrors::voice_pack_not_found() {
error_wav_queued() = false;
SaberBase::DoEffect(EFFECT_VOICE_PACK_NOT_FOUND, 0);
PVLOG_ERROR << "** ERROR - Voice pack not found.\n"
"** See https://pod.hubbe.net/troubleshooting/what-is-it-saying.html#voice-pack-not-found\n";
#ifdef ENABLE_AUDIO
if (SaberBase::sound_length > 0) return;
if (error_wav_queued()) return;
#ifndef DISABLE_TALKIE
talkie.Say(talkie_voice_pack_15, 25);
talkie.Say(talkie_not_found_15, 15);
DodgeSound(2000);
delay_timer().Append(2000);
DodgeSound(delay_timer().remaining());
#else
beeper.Beep(1.0, 220.00 * 2); // A4 - Voice
beeper.Beep(0.5, 130.81 * 2); // C4 - pack
beeper.Beep(0.5, 146.83 * 2); // D4 - not
beeper.Beep(1.0, 130.81 * 2); // C4 - found
DodgeSound(3000);
beeper.Beep(1.0, 220.00 * 2); // A4 - Voice
beeper.Beep(0.5, 130.81 * 2); // C4 - pack
beeper.Beep(0.5, 146.83 * 2); // D4 - not
beeper.Beep(1.0, 130.81 * 2); // C4 - found
delay_timer().Append(3000);
DodgeSound(delay_timer().remaining());
#endif
#endif
}

void ProffieOSErrors::error_in_blade_array() {
error_wav_queued() = false;
SaberBase::DoEffect(EFFECT_ERROR_IN_BLADE_ARRAY, 0);
PVLOG_ERROR << "** ERROR - Error in blade array\n"
"** See https://pod.hubbe.net/troubleshooting/what-is-it-saying.html#error-in-blade-array\n";
#ifdef ENABLE_AUDIO
if (SaberBase::sound_length > 0) return;
if (error_wav_queued()) return;
#ifndef DISABLE_TALKIE
talkie.Say(talkie_error_in_15, 15);
talkie.Say(talkie_blade_array_15, 15);
DodgeSound(2000);
delay_timer().Append(2000);
DodgeSound(delay_timer().remaining());
#else
beeper.Beep(0.25, 174.61 * 2); // F4 - Err
beeper.Beep(0.25, 196.00 * 2); // G4 - or
beeper.Beep(0.25, 174.61 * 2); // F4 - in
beeper.Beep(0.25, 164.81 * 2); // E4 - the
beeper.Beep(0.3, 146.83 * 2); // D4 - blade
beeper.Beep(0.2, 0); // Silence
beeper.Beep(0.5, 146.83 * 2); // D4 - ar
beeper.Beep(1.0, 130.81 * 2); // C4 - ray
DodgeSound(3000);
beeper.Beep(0.25, 174.61 * 2); // F4 - Err
beeper.Beep(0.25, 196.00 * 2); // G4 - or
beeper.Beep(0.25, 174.61 * 2); // F4 - in
beeper.Beep(0.25, 164.81 * 2); // E4 - the
beeper.Beep(0.3, 146.83 * 2); // D4 - blade
beeper.Beep(0.2, 0); // Silence
beeper.Beep(0.5, 146.83 * 2); // D4 - ar
beeper.Beep(1.0, 130.81 * 2); // C4 - ray
delay_timer().Append(3000);
DodgeSound(delay_timer().remaining());
#endif
#endif
}

void ProffieOSErrors::error_in_font_directory() {
error_wav_queued() = false;
SaberBase::DoEffect(EFFECT_ERROR_IN_FONT_DIRECTORY, 0);
PVLOG_ERROR << "** ERROR - Error in font directory.\n"
"** See https://pod.hubbe.net/troubleshooting/what-is-it-saying.html#error-in-font-directory\n";
#ifdef ENABLE_AUDIO
if (SaberBase::sound_length > 0) return;
if (error_wav_queued()) return;
#ifndef DISABLE_TALKIE
talkie.Say(talkie_error_in_15, 15);
talkie.Say(talkie_font_directory_15, 15);
DodgeSound(1300);
delay_timer().Append(1300);
DodgeSound(delay_timer().remaining());
#else
beeper.Beep(0.25, 174.61 * 2); // F4 - Err
beeper.Beep(0.25, 196.00 * 2); // G4 - or
beeper.Beep(0.25, 174.61 * 2); // F4 - in
beeper.Beep(0.25, 164.81 * 2); // E4 - the
beeper.Beep(0.5, 146.83 * 2); // D4 - font
beeper.Beep(0.5, 164.81 * 2); // E4 - di
beeper.Beep(0.5, 196.00 * 2); // G4 - rec
beeper.Beep(0.5, 246.94 * 2); // B4 - tor
beeper.Beep(0.5, 261.63 * 2); // C5 - y
DodgeSound(3500);
beeper.Beep(0.25, 174.61 * 2); // F4 - Err
beeper.Beep(0.25, 196.00 * 2); // G4 - or
beeper.Beep(0.25, 174.61 * 2); // F4 - in
beeper.Beep(0.25, 164.81 * 2); // E4 - the
beeper.Beep(0.5, 146.83 * 2); // D4 - font
beeper.Beep(0.5, 164.81 * 2); // E4 - di
beeper.Beep(0.5, 196.00 * 2); // G4 - rec
beeper.Beep(0.5, 246.94 * 2); // B4 - tor
beeper.Beep(0.5, 261.63 * 2); // C5 - y
delay_timer().Append(3500);
DodgeSound(delay_timer().remaining());
#endif
#endif
}

void ProffieOSErrors::error_in_voice_pack_version() {
error_wav_queued() = false;
SaberBase::DoEffect(EFFECT_ERROR_IN_VOICE_PACK_VERSION, 0);
PVLOG_ERROR << "** ERROR - Error in voice pack version.\n"
"** See https://pod.hubbe.net/troubleshooting/what-is-it-saying.html#error-in-voice-pack-version\n";
#ifdef ENABLE_AUDIO
if (SaberBase::sound_length > 0) return;
if (error_wav_queued()) return;
#ifndef DISABLE_TALKIE
talkie.Say(talkie_error_in_15, 15);
talkie.Say(talkie_voice_pack_15, 15);
talkie.Say(talkie_version_15, 15);
DodgeSound(1500);
delay_timer().Append(1800);
DodgeSound(delay_timer().remaining());
#else
beeper.Beep(0.25, 174.61 * 2); // F4 - Err
beeper.Beep(0.25, 196.00 * 2); // G4 - or
beeper.Beep(0.25, 174.61 * 2); // F4 - in
beeper.Beep(0.25, 164.81 * 2); // E4 - the
beeper.Beep(0.5, 220.00 * 2); // A4 - voice
beeper.Beep(0.5, 146.83 * 2); // D4 - pack
beeper.Beep(1.0, 196.00 * 2); // G4 - ver
beeper.Beep(0.5, 130.81 * 2); // C4 - sion
DodgeSound(3500);
beeper.Beep(0.25, 174.61 * 2); // F4 - Err
beeper.Beep(0.25, 196.00 * 2); // G4 - or
beeper.Beep(0.25, 174.61 * 2); // F4 - in
beeper.Beep(0.25, 164.81 * 2); // E4 - the
beeper.Beep(0.5, 220.00 * 2); // A4 - voice
beeper.Beep(0.5, 146.83 * 2); // D4 - pack
beeper.Beep(1.0, 196.00 * 2); // G4 - ver
beeper.Beep(0.5, 130.81 * 2); // C4 - sion
delay_timer().Append(3500);
DodgeSound(delay_timer().remaining());
#endif
#endif
}

void ProffieOSErrors::low_battery() {
#ifdef ENABLE_AUDIO
// play the fonts low battery sound if it exists
// play the font's low battery sound if it exists
if (SFX_lowbatt) {
hybrid_font.PlayCommon(&SFX_lowbatt);
return;
Expand All @@ -169,8 +196,8 @@ void ProffieOSErrors::low_battery() {
#ifndef DISABLE_TALKIE
talkie.Say(talkie_low_battery_15, 15);
#else
beeper.Beep(1.0, 261.63 * 2); // C4
beeper.Beep(1.0, 130.81 * 2); // C3
beeper.Beep(1.0, 261.63 * 2); // C4
beeper.Beep(1.0, 130.81 * 2); // C3
#endif
#endif
}
Expand Down
56 changes: 56 additions & 0 deletions common/saber_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "linked_list.h"
#include "vec3.h"
#include "onceperblade.h"
#include "delay_timer.h"

// SaberBase is our main class for distributing saber-related events, such
// as on/off/clash/etc. to where they need to go. Each SABERFUN below
Expand Down Expand Up @@ -255,6 +256,37 @@ class EffectLocation {
BladeSet blades_;
};

// Events that have been held back while error messages are being announced.
// We defer the whole event rather than just the sound so that sound, display
// and blade styles stay in sync, and so that SaberBase::sound_length is valid
// by the time anything looks at it.
class DeferredEffects {
public:
static const size_t MAX_DEFERRED_EFFECTS = 4;

bool push(EffectType effect, EffectLocation location) {
if (num_ >= MAX_DEFERRED_EFFECTS) return false;
effects_[num_] = effect;
locations_[num_] = location;
num_++;
return true;
}
size_t size() const { return num_; }
EffectType effect(size_t n) const { return effects_[n]; }
EffectLocation location(size_t n) const { return locations_[n]; }
void clear() { num_ = 0; }

private:
EffectType effects_[MAX_DEFERRED_EFFECTS];
EffectLocation locations_[MAX_DEFERRED_EFFECTS];
size_t num_ = 0;
};

inline DeferredEffects& deferred_effects() {
static DeferredEffects effects;
return effects;
}

struct BladeEffect {
EffectType type;

Expand Down Expand Up @@ -445,10 +477,34 @@ public: \

public:
static void DoEffect(EffectType effect, EffectLocation location) {
if (DeferEffect(effect, location)) return;
ClearSoundInfo();
DoEffectInternal2(effect, location);
}

// Boot and font announcements are held back while error messages are
// playing, so that the user gets to hear what is wrong first, and so that
// display and audio still arrive together when it finally does play.
// Returns true if the event was deferred.
static bool DeferEffect(EffectType effect, EffectLocation location) {
if (effect != EFFECT_BOOT && effect != EFFECT_NEWFONT) return false;
if (!delay_timer().Active()) return false;
return deferred_effects().push(effect, location);
}

// Called from a Looper, see sound.h
static void PollDeferredEffects() {
DeferredEffects& deferred = deferred_effects();
if (!deferred.size() || delay_timer().Active()) return;
// Copy before dispatching, as the handlers may defer new events.
DeferredEffects pending = deferred;
deferred.clear();
for (size_t i = 0; i < pending.size(); i++) {
ClearSoundInfo();
DoEffectInternal2(pending.effect(i), pending.location(i));
}
}

static void DoOn(EffectLocation location) {
ClearSoundInfo();
DoOnInternal(location);
Expand Down
6 changes: 6 additions & 0 deletions props/prop_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,12 @@ class PropBase : CommandParser, protected Looper, protected SaberBase, public Mo
for (size_t i = 0; i < NELEM(wav_players); i++) {
wav_players[i].Stop();
}
// The pending error announcements are all about the font we're leaving,
// so drop them. ScanCurrentDirectory() below will queue up whatever is
// wrong with the new font.
SOUNDQ->clear_pending();
deferred_effects().clear();
delay_timer().Stop();
#endif

char *b = current_directory;
Expand Down
Loading