Playing unique blade array audio idents on live array switching

It is. just a rollover pops up a window.

Screenshot 2024-12-04 at 10.28.21 PM

2 Likes

would kate work? I think it does the something.

git is a version control software that’s by-far the most widely used, though there are others (mercurial, subversion, etc.)

GitHub is an online hosting service for maintaining remote copies of git repositories. There are other services like this as well, such as GitLab (which you can self host also), and of course there’s other ways to manually setup a self-hosted git server also.

1 Like

That’s what that “pop-up” is. I need to go over it slower. So far it was a nuisance that was “getting in my way”. Cool to know. Thank you NoSloppy.

Thank you Ryan

For anyone interested, this is a modified version of the above to allow manual blade array selection while also having Blade ID Constant Monitoring with BLADE_ID_SCAN_MILLIS active.

Additional sound things ironed out:

  • bladein/bladeout.wav files are always played if a blade is inserted/removed (if they exist), otherwise font.wav.

  • It now also checks if you are using array ident sounds (array1.wav, array2.wav, etc…) and if not, it plays the font.wav of the newly selected preset array instead.

  • Before, if SAVE_PRESET was used and a manual switch was done, no sound would play. Remedied.

  • If you switched to the same array as the one previously active (either by a manual switch or by native Blade ID detection), nothing would play. Fixed.

Unsanctioned, but tested and I think it’s mostly legit.

The core of how I got it to work was to spoof an array choice and essentially “fake out” the constant scanning so it didn’t override the manual choice due to measurements.

Then, when a blade is physically inserted or removed, it exits manual spoof mode and allows the native choice through.

Now, this is intended for ONE NO_BLADE state and any number of blades. If you want chassisin/out or crystalin/out sounds, then additional code will need to be written somewhere down the line to accommodate that. Looking back on @sabersense’s thread a while back (when profezzorn implemented NO_BLADE_RANGE) there’s NO_BLADE*2 things and stuff I haven’t dabbled with yet, so this here isn’t up to date with any of that yet.

To have this work, you’ll need to make a couple of functions in prop_base.h virtual:
FindBestConfig()
PollScanId()
Let me know if it’s horrible :crazy_face:


  // Manual blade array selection
  // even with Real TIme Blade ID active 
  bool manual_blade_array_active = false;
  size_t last_real_best_config = NELEM(blades);
  size_t real_best_config = NELEM(blades);
  size_t fake_best_config = NELEM(blades);
  size_t prev_real_best_config = NELEM(blades);

  size_t FindBestConfig(bool announce = false) override {
    // Get the OS determined best_config
    real_best_config = PropBase::FindBestConfig(announce);

    // Handle manual blade array switching mode
    if (manual_blade_array_active) {
      if (real_best_config != last_real_best_config) {
        PVLOG_NORMAL << "** REAL BLADE CHANGE DETECTED. Exiting manual mode\n";
        manual_blade_array_active = false;
        prev_real_best_config = last_real_best_config;
        last_real_best_config = real_best_config;

        find_blade_again_pending_ = true;
        PollScanId();

        return real_best_config;
      }
    // Spoof best_config during manual mode
    fake_best_config = current_config - blades;  // the incremented array we manually set in FakeFindBladeAgain()
    return fake_best_config;
  }

    // Normal mode: Update stored real best_config
    if (real_best_config != last_real_best_config) {
      last_real_best_config = real_best_config;
    }
    return real_best_config;
  }

  void TriggerBladeID() {
    FindBladeAgain();
    if (SFX_array) {
      SFX_array.Select(current_config - blades);
      hybrid_font.PlayPolyphonic(&SFX_array);
    } else {
      SaberBase::DoNewFont();
    }
  }

  void NextBladeArray() {
    manual_blade_array_active = true;
    PVLOG_NORMAL << "** Manually Switching to Blade Array: " << (current_config - blades + 1) << "\n";
    FakeFindBladeAgain();
  }

  // Manual Blade Array Selection version of FindBladeAgain()
  void FakeFindBladeAgain() {
    // Reverse everything FindBladeAgain does, except for recalculating best_config
    ONCEPERBLADE(UNSET_BLADE_STYLE)

#undef DEACTIVATE
#define DEACTIVATE(N) do {                      \
    if (current_config->blade##N)               \
      current_config->blade##N->Deactivate();   \
  } while(0);

    ONCEPERBLADE(DEACTIVATE);
    SaveVolumeIfNeeded();
    // Cycle to the next blade array
    current_config = blades + (current_config - blades + 1) % NELEM(blades);
    int chosen_array = current_config->ohm;

#undef ACTIVATE
#define ACTIVATE(N) do {                        \
    if (!current_config->blade##N) {            \
      goto bad_blade;                           \
    }                                           \
    current_config->blade##N->Activate(N);      \
  } while(0);

    ONCEPERBLADE(ACTIVATE);
    RestoreGlobalState();

#ifdef SAVE_PRESET
    savestate_.ReadINIFromSaveDir("curstate");
    if (SFX_array) {
      SetPreset(savestate_.preset, false);
      SFX_array.Select(current_config - blades);
      hybrid_font.PlayPolyphonic(&SFX_array);
    } else {
      SetPreset(savestate_.preset, true);
    }
#else

    if (SFX_array) {
      SetPreset(0, false);
      SFX_array.Select(current_config - blades);
      hybrid_font.PlayPolyphonic(&SFX_array);
    } else {
      SetPreset(0, true);
    }
#endif // SAVE_PRESET
    return;

#if NUM_BLADES != 0
  bad_blade:
    ProffieOSErrors::error_in_blade_array();
#endif
  }

#ifdef BLADE_ID_SCAN_MILLIS
  // Must be called from loop()
  void PollScanId() {
    if (find_blade_again_pending_) {
      find_blade_again_pending_ = false;
      int noblade_level_before = current_config->ohm / NO_BLADE;
      FindBladeAgain();
      int noblade_level_after = current_config->ohm / NO_BLADE;

      if (noblade_level_before < noblade_level_after) {
        if (!SaberBase::IsOn()) {
        SaberBase::DoBladeDetect(false);
        } else {
          SaberBase::DoNewFont();
        }
      } else if(noblade_level_before > noblade_level_after) {
        if (!SaberBase::IsOn()) {
          SaberBase::DoBladeDetect(true);
        } else {
          SaberBase::DoNewFont();
        }
      } else {
        if (prev_real_best_config != last_real_best_config) {
        // A real blade change has occurred, play bladein/out.
        // If bladein/out doesn't exist, hybrid_font handles playing font.wav instead.
          PVLOG_NORMAL << "** REAL BLADE CHANGE DETECTED. Playing bladein/out\n";
          hybrid_font.SB_BladeDetect(!(id() >= NO_BLADE));
          prev_real_best_config = last_real_best_config;
          last_real_best_config = real_best_config;
        } else {
          PVLOG_NORMAL << "**** No Blade Change Detected, something else changed Blade ID somehow. Playing font.wav\n";
          SaberBase::DoNewFont();
        }
      }
    }
  }
  #endif

Still called from Event2 (your button event may vary):

// Manually cycle to Next Preset Array
  case EVENTID(BUTTON_POWER, EVENT_FOURTH_HELD_MEDIUM, MODE_OFF):
    NextBladeArray();
    return true;

// Manually trigger Blade ID scan
  case EVENTID(BUTTON_POWER, EVENT_FOURTH_CLICK_LONG, MODE_OFF):
    TriggerBladeID();
    return true;
1 Like

@NoSloppy this is amazing work! :clap: :clap: :clap:

So let me check I’ve understood it right…

…the system continuously scans for new BladeIDs, and if you fit a blade with a suitable resistance value it switches to that blade’s associated array. But you can then override the auto-selection to go to a different array manually, in which case, although the system might still be running blade ID scans, it will ignore the results until such time as those results change, i.e. you fit a different blade with a different value, or remove the blade completely (which also returns a different value), at which point it will automatically switch to the correct array for the new state?

Does that accurately describe it or have I got it confused?

If I have got it right, this opens up even more options. For example, you could have your hilt setup for various different length blades, plus of course a no-blade state in which you could have fancy emitter animations, and each blade state could be further divided into light side, dark side and diagnostic presets (diagnostic being things like battery levels, blade plug charging mode, length finder etc.). :smiley:

I dare say other implementations of this will present themselves as time goes on and people start using it. So I for one think this is an amazing addition to ProffieOS - I just wish I’d thought of it! Oh, hang on… :wink: Jus’ kidding - what I actually wish is I’d had the skills to make it work myself as elegantly as you’ve done! Much respect again! :clap: :clap: :clap: Great work! :smiley:

Not sure if I’m reading this correctly, but it doesn’t present as subsets of a given state.
It’s still using your single set of blade arrays in your blade config, just the selection is either manual (next, next,next) or auto (scanning), both available at the same time .
You can still manually trigger a scan as well.

Yes, removing the blade is a physical change.

No, sorry, what I meant was that you could have the hilt set up for, say, three blade settings - no blade, LGT 32 inch and KR 36 inch, and then have more than one array for each, like this:

  • Array 1 - No blade, Light Side
  • Array 2 - No blade, Dark Side
  • Array 3 - LGT 32 inch blade, Light Side
  • Array 4 - LGT 32 inch blade, Dark Side
  • Array 5 - KR 36 inch blade, Light Side
  • Array 6 - KR 36 inch blade Dark Side
  • Array 7 - Diagnostics

But what I was driving at was that the continuous scanning must be ignored after a manual switch. i.e., in the above setup, if I have my LGT blade fitted and then manually switch to array 5, the continuous scanning will switch it straight back to array 3 or 4 unless continuous scanning is ignored after the manual switch and only heeded once it registers a new physical state such as removing the LGT blade or fitting the KR blade.

Ah I think I understand. That would mean that 1 of the multiple arrays setup for a specific blade would be the actual measured value which the scanning would take you to, then the other would have a different value (one that would not ever be chosen by native Blade ID), but had the same pixel count and stuff, but called a different CONFIGARRAY().
So you would “land” on the first one with the scan, but could then move manually to the alternate.
Does that sound right?
If yes, then yes, it sounds like a viable use.

As soon as there’s a physical change, it’s going to exit manual switched mode. So the removal of a blade in this example. The logic dictates that “fitting the KR blade” couldn’t be the exit catalyst if it was preceeded by a blade removal. Just clarifying.

Ahh, sorry, yes - I can see now that I haven’t quite thought that example through by having light side and dark side arrays for the same blade, but you’re right of course in asking the question, “which one will the scan take you to?”.

But ignoring that for a moment, and assuming one array per blade type, the question I was asking is the one you’ve addressed, i.e. if I have the LGT 32 inch blade fitted and I switch manually to 36 inch LGT blade mode, the continuous scanning will now return values different to the array it’s now sat on, hence the system needs to effectively enter an ‘ignore scan mode’ until such time as it detects a physical blade change from the one it’s on - which of course means the only change it can register if a blade is already fitted is the blade being removed which will return a no blade value.

Sorry, yes, I get it now. Again great job! :ok_hand: :clap: