Sounds playing scenarios

Hello The Crucible, Happy New Year everyone,

For my jetpack prop, I have a few sound-types/sound playing scenarios, that I am not sure if they will be playing correctly:

  1. Music from the track player, I would like it to play without interruption, the other prop sounds play in addition to the track. I suppose that is how StartOrStopTrack() works ?

  2. I have transition sounds (startidlemode, startflightmode, stopflightmode & stopidlemode), they play once and then a looping sound (idlemode or flightmode) starts.

    void JetpackHelper(Effect& effect3, EffectType effect4) {
        Effect::FileID file_id(&effect3, 0, 0);
        jetpack_wav_player_.PlayOnce(file_id);
        SaberBase::DoEffect(effect4, 0);
    }

    // Initial start, transition to Idle Mode (from 0 to idle)
    void StartIdleMode() {
        if (jetpack_wav_player_.isPlaying()) {
            return;
        }
        JetpackHelper(SFX_startidlemode,EFFECT_STARTIDLEMODE); // start transition to idle mode
        flight_ = false;
        idle_   = true;
        timer_  = millis(); // Reset idle timer
        FastOn();
        PVLOG_STATUS << "Jetpack Idling\n";
    }

    // Transition to Flying Mode (from idle to flying)
    void StartFlightMode() {
        if (jetpack_wav_player_.isPlaying()) {
            return;
        }
        JetpackHelper(SFX_startflightmode,EFFECT_STARTFLIGHTMODE); // start transition to flight mode
        flight_ = true;
        idle_   = false;
        PVLOG_STATUS << "Jetpack Flying\n";
    }

    // Transition from Flying Mode (from flying to idle)
    void StopFlightMode() {
        if (jetpack_wav_player_.isPlaying()) {
            return;
        }
        JetpackHelper(SFX_stopflightmode,EFFECT_STOPFLIGHTMODE); // start transition from flight mode
        flight_ = false;
        idle_   = true;
    }

    // Stop Idle Mode (Jetpack completely off - idle to off)
    void StopIdleMode() {
        if (jetpack_wav_player_.isPlaying()) {
            return;
        }
        JetpackHelper(SFX_stopidlemode,EFFECT_STOPIDLEMODE); // start transition from idle mode
        flight_ = false;
        idle_   = false;
        Off(OFF_FAST); // Fully stop the jetpack
    }

    void Loop() override {
        unsigned long now = millis();

        // Handle missile sound playback and log completion
        missile_sound_queue_.PollSoundQueue(missile_wav_player_); // Handle missile sound playback
        if (!missile_sound_queue_.busy()) {
            PVLOG_NORMAL << "Missile Launch Sequence Completed!\n";
        }

        // Stop idle mode after timeout
        if (idle_ && (now - timer_ > JETPACK_IDLE_TIME)) {
            StopIdleMode(); // Stop jetpack if idle for more than defined time
            return; // Exit early to avoid further processing
        }

        // Start idle loop after transition sound finishes
        if  (idle_)  IdlePlaying();

        // Start flight loop after transition sound finishes
        if (flight_) FlightPlaying();
    }
  1. I have looping sounds (idlemode or flightmode)
    void IdlePlaying() {
        if (idle_ && !jetpack_wav_player_.isPlaying()) {
            jetpack_wav_player_.PlayLoop(&SFX_idlemode); // Start idle loop
            SaberBase::DoEffect(EFFECT_IDLEMODE,0); // is there a way to have SaberBase::DoEffect(EFFECT_IDLEMODE,0) repeat itself at the same rate as jetpack_wav_player_.PlayLoop(&SFX_idlemode) ?
        }
    }
//idlemode & flightmode are two sounds that I am not confident if an OLED animation can be synced while the sound is playing!
    void FlightPlaying() {
        if (flight_ && !jetpack_wav_player_.isPlaying()) {
            jetpack_wav_player_.PlayLoop(&SFX_flightmode); // Start flight loop
            SaberBase::DoEffect(EFFECT_FLIGHTMODE,0);
        }
    }
  1. The last ones are a series of sounds that I would like to play once, after one another, without gaps or interruption between each other, and they play in addition of what is already playing (sounds 1 to 3). Also, they need to trigger their SaberBase::DoEffect equivalent so the various OLED annimations representing these sounds can play synchronised as well.
    void MissileHelper(Effect* effect1,EffectType effect2) {
        PVLOG_DEBUG << "Queuing sound: \n";
        missile_sound_queue_.Play(SoundToPlay(effect1));
        SaberBase::DoEffect(effect2,0);
    }

    // I want the effects to play one after the other without overlapping or interrupting one another and without gaps.
    // Sequential sound queue for missile launch sequence
    void PerformMissileLaunchSequence() {
        PVLOG_NORMAL << "Starting Missile Launch Sequence\n";
        // create animation for OLED of viewfinder coming down
        MissileHelper(&SFX_aiming,         EFFECT_AIMING);
        // create animation for OLED of targetting
        MissileHelper(&SFX_targetting,     EFFECT_TARGETTING);
        // create animation for OLED of jetpack launching missile
        MissileHelper(&SFX_missilelaunch,  EFFECT_MISSILELAUNCH);
        // create animation for OLED of explosion
        MissileHelper(&SFX_missilegoesboom,EFFECT_MISSILEGOESBOOM);
        if (!flight_) { // Perform "Mando Talk" if not in flight mode!
        // create animation for OLED of mando & boba talking
            MissileHelper(&SFX_mandotalk,  EFFECT_MANDOTALK);
            PVLOG_NORMAL << "Mando: Nice shot!\nBoba: I was aiming for the other one!\n";
        }
        // create animation for OLED of viewfinder going back up
        MissileHelper(&SFX_disarm,         EFFECT_DISARM);
    }

And just in case you are curious, here is my latest jetpack_prop.h version 48:
jetpack_prop.h (25.6 KB)

I would love to know if you think my code could be (add General Grievous voice)“worthy of addition to your collection”? I believe that I reduced repetition as best as I can. If you find more repetition that I missed, please let me know. Are my sounds playing scenarios coded properly?

Thank you for reading so far,
Thank you in advance for any help/comments/review you can provide.

MTFBWYA.

  1. yes the only thing that interrupts tracks is changing presets
  2. You might consider using alt modes for this.
  3. There are ways to link the end of one effect to the beginning of another (or itself) to create looping sounds, however, there is no builtin system for triggering effects to match.
  4. Your MissileHelper uses SoundQueue to handle the sounds. It will handle playing the sounds after each other for you, but you’re calling SaberBase::DoEffect immediately, which means that OLED or blades will not be able to synchronize. I should also point out that sounds played with SoundQueue are not gapless. They will play with ~1ms gap or so, so each sounds needs a beginning and an end.

There a couple of ways to handle this that will work better, and you can choose based on needs, wants and taste, so let’s go over them:

  1. Don’t use a sequence of sounds. Just use one long sound instead. We can then use linking go make sure that sound1 pairs up with animation1, and they have the same length and “steps” inside them.
  2. Use the effect to trigger the sound. This means not using a sound queue. Instead we just call SaberBase::DoEffect(EFFECT_AIMING). This will invoke SB_Effect(), and we’ll start the sound there. Doing it this way means that Wavlen<> will work properly, and we can also get the length of the sound from SaberBase. Once we know how long the sound is, we just need to wait that long before calling the next effect. (Note that we cannot use delay() to wait, we must use a polling method.)
  3. Use the “next sound” linking method to trigger the next sound, then have the code checks when there is a transition from one sound to another and trigger the DoEffect calls at those points. This is a little bit more complicated, but it’s gapless. hybrid_font does this for preon->out transitions for instance.
  4. Make a copy of SoundQueue and have it take an optional effect with each call, then trigger the effects when new sounds are played.

Cool, so that is one down. :grin:

I read about alt modes, I don’t remember anything about it except that I didn’t understand it. Nope that’s not it, I am not remembering/understanding alt sounds (is that a thing?) ¯_(ツ)_/¯

Am I doing it right?

I was afraid of that.

I was afraid of that too.

I think I can live with even a 10ms gap.

like a fade-in/fade-out ?

That, I am sure I don’t want to do. I still need to create the various sounds and animations. I want to have the flexibility of just changing one part and the rest will “sync” with one change at at time.

This I understand, and also what I would like to do (I thought: I was doing it already? I guess not)

Thrust me, I know (I read the whole Crucible forum in the last few month - I know you hate it, and I understand why!). I don’t want to “wait”, I also hate to wait! :rofl:

Is there an example of polling method that I can look at ?

I like the sound of that! (no pun intended) This seem to be the most “ProffieOS” oriented solution. I don’t mind complicated or needing time to understand. I would love to read about some examples.

This seems like something you would call a “hack” (am I right). I would like to stay away from hacks, unless I need a quick temporary fix because I am getting too frustrated while failing with the “right way”.

Thank you for all that marvelous information. Have a great weekend.

alt sounds / alt modes, same thing…

You are not doing that at all, but as mention later, it is optional.

Something like that. Without gapless playback, each sound is like a word in a sentence, with some small, minor pause in between.

You don’t have an SB_Effect() function, so no…

Probably the best way to do this is to write it as one or more state machines.
There are lots of examples of state machines in ProffieOS.
One of the biggest and best examples is probably this one:

State machines use SLEEP() instead of delay(), and while they look suspiciously similar, they don’t work the same at all. SLEEP() will return from the function, let the code to other stuff, and keep doing that until the required number of milliseconds has passed.

You may consider this magic, your you could look up how it works here:

1 Like

I read it again, and still don’t see why/how alt sound would be applicable in my case?

For the missile sequence, it is probably preferred to have a small gap between most effects. I was more worried of having seconds between each, or all playing on top of each other.

Oh, yes I do:

    void SB_Effect(EffectType effect, EffectLocation location) override {
      switch (effect) {
        default: return;
        // Missile effects:
        case EFFECT_AIMING:          PVLOG_STATUS << "Aiming\n";                       return;//Must not be interrupted and must play in sequence with one another!
        case EFFECT_TARGETTING:      PVLOG_STATUS << "Targetting\n";                   return;//Must not be interrupted and must play in sequence with one another!
        case EFFECT_MISSILELAUNCH:   PVLOG_STATUS << "Missile Launch\n";               return;//Must not be interrupted and must play in sequence with one another!
        case EFFECT_MISSILEGOESBOOM: PVLOG_STATUS << "Missile Explodes\n";             return;//Must not be interrupted and must play in sequence with one another!
        case EFFECT_MANDOTALK:       PVLOG_STATUS << "Mando & Boba Talking\n";         return;//Must not be interrupted and must play in sequence with one another!
        case EFFECT_DISARM:          PVLOG_STATUS << "Disarm Targetting\n";            return;//Must not be interrupted and must play in sequence with one another!
        // Engine mishap effects:
        case EFFECT_FALSESTART:      PVLOG_STATUS << "Jetpack False Start\n";          return;
        case EFFECT_STUTTERING:      PVLOG_STATUS << "Jetpack Stuttering\n";           return;
        case EFFECT_SELFDESTRUCT:    PVLOG_STATUS << "Jetpack Self Destruct\n";        return;
        case EFFECT_MELTDOWN:        PVLOG_STATUS << "Jetpack Meltdown\n";             return;
        case EFFECT_DUD:             PVLOG_STATUS << "Oh Frack!\nIt's a Dud!\n";       return;
        // Engine normal effects:
        case EFFECT_STARTIDLEMODE:   PVLOG_STATUS << "Jetpack Starting to Idle\n";     return;//after startidlemode, play idle mode on loop
        case EFFECT_IDLEMODE:        PVLOG_STATUS << "Jetpack in idle mode.\n";        return;//plays on loop
        case EFFECT_STARTFLIGHTMODE: PVLOG_STATUS << "Jetpack Starting to Flight\n";   return;//after startflightmode, play flight mode on loop
        case EFFECT_FLIGHTMODE:      PVLOG_STATUS << "Jetpack in flight mode.\n";      return;//plays on loop
        case EFFECT_STOPFLIGHTMODE:  PVLOG_STATUS << "Jetpack Slowing Down to Idle\n"; return;//after stopflightmode, play idle on loop
        case EFFECT_STOPIDLEMODE:    PVLOG_STATUS << "Jetpack Completely Off\n";       return;
        // System effects:
        // On-Demand Battery Level
        case EFFECT_BATTERY_LEVEL:   if (!hybrid_font.PlayPolyphonic(&SFX_battery)) {
                                       beeper.Beep(1.0, 475);
                                       beeper.Beep(0.5, 693);
                                       beeper.Beep(0.16, 625);
                                       beeper.Beep(0.16, 595);
                                       beeper.Beep(0.16, 525);
                                       beeper.Beep(1.1, 950);
                                       beeper.Beep(0.5, 693);
                                       PVLOG_NORMAL << "May the Force be with you...always.\n";} return;
        }  // switch (effect)
    }  // SB_Effect

That seems complicated.

But I like the sound of SLEEP() (no pun intended).

Where can I find the code that plays hum on loop ? I think that is what I would like to use for my idle and flight loops ?

Where can I find the code that gets the length of a sound ?
Edit: I know you said saberbase.h but I can’t see it. Or could you point to somewhere where it is used ?

Does the jetpack needs these line:

        // Events that needs 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()) {                                // Does jetpack need this ??? No Lockup in jetpack
            SaberBase::DoEndLockup();                               // Does jetpack need this ??? No Lockup in jetpack
            SaberBase::SetLockup(SaberBase::LOCKUP_NONE);           // Does jetpack need this ??? No Lockup in jetpack
        return true;

I am talking about the 3 lockup lines stuff. The EVENT_RELEASED should probably be there ?

So it would be something like this:

SaberBase::DoEffect(EFFECT_AIMING)
"GetSoundLenght" of &SFX_aiming = wait_next_effect_
SLEEP(wait_next_effect_)
SaberBase::DoEffect(EFFECT_...next in line)
repeat ?

And SaberBase::DoEffect(EFFECT_AIMING) would trigger:
hybrid_font.PlayPolyphonic(&SFX_aiming);
PVLOG_someting…;

Which would also trigger EFFECT_AIMING from my jetpackdisplaycontroller and display the relevant info/animation on OLED for the duration of the sound ?

Your prop has different “modes”.
If you change the alt number when you switch mode, then every sound that is used can be different depending on the mode. So now you don’t need a different looping background sound for each mode, because the “hum” is all your looping background sounds, you just need a different hum sound for each alt(ernative).

Oh, right. You just don’t play your sounds from there…

hum is not played from loop(), it uses EFFECT2() to set the “next effect” link which causes it to loop gaplessly automatically.

Immediately after doing an effect, it will be in SaberBase::sound_length. (Or it will be -1 if no sound was started.)

I don’t know. Does your jetpack have “lockup” ?
If you remove the lockup stuff, then the case lines can go away too.

it looks a little funky, but yes, basically that.

1 Like

Oh, I see now, so Alt1 would be idle and Alt2 would be fight (at full/take-off power), and I could add Alt3 for “medium power”, Alt4 for landing power, … and it would all be “controlled” from a blade style to switch between modes. The “AltHumms” would the be the various looping sounds, “altins” and “altouts” would be the transitions. In the “root” font folder I would have the common stuff like mishaps & missile sequence. That opens up a lot more possibilities.

I just don’t play my sounds from there …, YET ! :grin:

I didn’t say hum was played from loop(), but it is “looped” to itself somehow/somewhere (and I know it is not “magic”, which I don’t believe in anyways because I know that what we call magic is just something for which we don’t yet have a “scientific” explanation for). So where is that “somewhere” where hum is taken care off by EFFECT2()?

So what is the proper call to get sound_length ? SaberBase::Get.sound_lenght ?

Well, at the moment:no, it does’t have “lockup”.
Is there a scenario where “lockup” could be “useful”(as a function) on a jetpack ? Personally, I can’t think of anything.

Sorry for the “on the fly” “pseudo” code. How about:

SaberBase::DoEffect(EFFECT_AIMING);
SLEEP(SaberBase::sound_length);
SaberBase::DoEffect(EFFECT_TARGETTING);
SLEEP(SaberBase::sound_length);
... until the end of the missile sequence ?

You could control it from a style, but you don’t have to. The prop can directly switch the alt value.

No, just SaberBase::sound_length
It’s not a function, just a floating-point value. (in seconds).

YOu’ll need to multiply by 1000, since SLEEP takes milliseconds and SaberBase::sound_length is in seconds, but other than that, it is correct.

1 Like

I was initially not a fan of the Alt way but I think I am warming up to it.
So how would a prop switch from Alt1 to Alt2,3,4 ? Would it be switching randomly or sequentially? I am hoping for sequentially.

That’s it? Only two lines! There must be more than just that, or no? What is the difference between hum & humm?
Where can I find the code for EFFECT2 and/or how do I use it?

So it is a specific type of variable (floating-point value), correct ? Where is the code that assigns its value? Not that I will use it, but out of curiosity, since I was looking for it but failed to find it.

But I guess not just “whole” number of seconds, also with decimals (like 1.2345 sec) ? At which decimal does it “round-off/up/down” or “cut-off” ?

Thank you, that feels quite encouraging.

It says on the pod page:

  SaberBase::DoEffect(EFFECT_ALT_SOUND, 0.0, N);

N is the alt value, it can be anything you want.

It’s only one line. “humm” is a plecter thing and you can ignore that.

I’m not a search engine. use github search.

What actually happens is that EFFECT2 ends up setting the “next effect” part of the effect, and the wav player automatically follows that when a sound runs out. When “next effect” point to itself, it becomes a loop.

How did you look? “SaberBase::sound_length =” occurs 3 times in the code, it’s not hard to figure out which one is doing what you’re looking for.

And what is “0.0” ?

I am sorry, I didn’t mean to offend you.

gives 8 results back, and I don’t think any one of them shows me what is happening.

I searched sound_length, result: 10 files and 20 instances (+/- 1), none seems to give me an answer:

And when I search with SaberBase::sound_length, I get 7 files and 17 instances (I counted the instances manually so it’s +/- 1)
Then I searched for length: 12 files, more or less 32 instances
Then I searched for void length(: 14 files, most of them don’t have void & length on the same line. That’s when I decided to ask you. I am pretty sure I am using the wrong search way. I can’t seem to find better.

I apologize if my questions are bothering you (at least, that is how it seems to me). If they do bother you, please feel free to ignore them. I know I can be “too much” when I become passionate.

If you look up SaberBase::DoEffect, you see that it’s the location, which is not really used much.

Questions do not bother me. But the goal for me is always to teach you how to find your own answers. I get annoyed when I feel there should be more progress on that front.

Try this query: Code search results ¡ GitHub

I guess my counting is also +/-1, because there are four hits.
The one in buffered_wav_player.h is the one that actually does anything interesting.

ProffieOS.ino hit is initialization
The one in hybrid_font.h is a special case.
The one in sound/tests.cpp is a test, and not part of normal ProffieOS.

1 Like

I am very sorry if I annoy you, please accept my apology for being a slow learner.

I didn’t know I was supposed to use “quotation marks” around my search on Github.

Most search engines use quotation marks to mean search for literally this.
A search like foo bar generally means find something with foo and bar in it.
A search like "foo bar" means search for foo, immediately followed by bar.
Different search engines might do slightly different things, but generally speaking, this is usually how it works.

1 Like

So I tried the SLEEP() route for the missile launcher function:

    void PerformMissileLaunchSequence() {
        PVLOG_NORMAL << "Starting Missile Launch Sequence\n";
        SaberBase::DoEffect(EFFECT_AIMING,0);
        SLEEP(SaberBase::sound_length * 1000 + 10);
        SaberBase::DoEffect(EFFECT_TARGETTING,0);
        SLEEP(SaberBase::sound_length * 1000 + 10);
        SaberBase::DoEffect(EFFECT_MISSILELAUNCH,0);
        SLEEP(SaberBase::sound_length * 1000);
        SaberBase::DoEffect(EFFECT_MISSILEGOESBOOM,0);
        SLEEP(SaberBase::sound_length * 1000 + 10);
        if (!flight_) { // Perform "Mando Talk" if not in flight mode!
            SaberBase::DoEffect(EFFECT_MANDOTALK,0);
            SLEEP(SaberBase::sound_length * 1000 + 10);
            PVLOG_NORMAL << "Mando: Nice shot!\nBoba: I was aiming for the other one!\n";
        }
        SaberBase::DoEffect(EFFECT_DISARM,0);
        SLEEP(SaberBase::sound_length * 1000);
        PVLOG_NORMAL << "Missile Launch Sequence Completed!\n";
    }

But I am getting these errors:

In file included from C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\ProffieOS_for_Oli-experiments.ino:293:
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h: In member function 'void Jetpack::PerformMissileLaunchSequence()':
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:43: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                           ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:766:9: note: in expansion of macro 'SLEEP'
  766 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:112: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:766:9: note: in expansion of macro 'SLEEP'
  766 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:37: error: 'class StateMachine' has no member named 'next_state_'
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                     ^~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:766:9: note: in expansion of macro 'SLEEP'
  766 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:69: error: case label '766' not within a switch statement
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                                                     ^~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:766:9: note: in expansion of macro 'SLEEP'
  766 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:43: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                           ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:769:9: note: in expansion of macro 'SLEEP'
  769 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:112: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:769:9: note: in expansion of macro 'SLEEP'
  769 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:37: error: 'class StateMachine' has no member named 'next_state_'
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                     ^~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:769:9: note: in expansion of macro 'SLEEP'
  769 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:69: error: case label '769' not within a switch statement
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                                                     ^~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:769:9: note: in expansion of macro 'SLEEP'
  769 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:43: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                           ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:772:9: note: in expansion of macro 'SLEEP'
  772 |         SLEEP(SaberBase::sound_length * 1000);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:112: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:772:9: note: in expansion of macro 'SLEEP'
  772 |         SLEEP(SaberBase::sound_length * 1000);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:37: error: 'class StateMachine' has no member named 'next_state_'
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                     ^~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:772:9: note: in expansion of macro 'SLEEP'
  772 |         SLEEP(SaberBase::sound_length * 1000);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:69: error: case label '772' not within a switch statement
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                                                     ^~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:772:9: note: in expansion of macro 'SLEEP'
  772 |         SLEEP(SaberBase::sound_length * 1000);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:43: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                           ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:775:9: note: in expansion of macro 'SLEEP'
  775 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:112: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:775:9: note: in expansion of macro 'SLEEP'
  775 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:37: error: 'class StateMachine' has no member named 'next_state_'
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                     ^~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:775:9: note: in expansion of macro 'SLEEP'
  775 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:69: error: case label '775' not within a switch statement
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                                                     ^~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:775:9: note: in expansion of macro 'SLEEP'
  775 |         SLEEP(SaberBase::sound_length * 1000 + 10);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:43: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                           ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:779:13: note: in expansion of macro 'SLEEP'
  779 |             SLEEP(SaberBase::sound_length * 1000 + 10);
      |             ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:112: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:779:13: note: in expansion of macro 'SLEEP'
  779 |             SLEEP(SaberBase::sound_length * 1000 + 10);
      |             ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:37: error: 'class StateMachine' has no member named 'next_state_'
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                     ^~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:779:13: note: in expansion of macro 'SLEEP'
  779 |             SLEEP(SaberBase::sound_length * 1000 + 10);
      |             ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:69: error: case label '779' not within a switch statement
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                                                     ^~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:779:13: note: in expansion of macro 'SLEEP'
  779 |             SLEEP(SaberBase::sound_length * 1000 + 10);
      |             ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:43: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                           ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:784:9: note: in expansion of macro 'SLEEP'
  784 |         SLEEP(SaberBase::sound_length * 1000);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:112: error: 'class StateMachine' has no member named 'sleep_until_'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                ^~~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:784:9: note: in expansion of macro 'SLEEP'
  784 |         SLEEP(SaberBase::sound_length * 1000);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:37: error: 'class StateMachine' has no member named 'next_state_'
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                     ^~~~~~~~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:784:9: note: in expansion of macro 'SLEEP'
  784 |         SLEEP(SaberBase::sound_length * 1000);
      |         ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:12:69: error: case label '784' not within a switch statement
   12 | #define YIELD() do { state_machine_.next_state_ = __LINE__; return; case __LINE__: break; } while(0)
      |                                                                     ^~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\common\state_machine.h:13:126: note: in expansion of macro 'YIELD'
   13 | #define SLEEP(MILLIS) do { state_machine_.sleep_until_ = millis() + (MILLIS); while (millis() < state_machine_.sleep_until_) YIELD(); } while(0)
      |                                                                                                                              ^~~~~
C:\Users\Olivier\Desktop\LightSabers\ProffieOS_for_Oli-experiments\props\jetpack_prop.h:784:9: note: in expansion of macro 'SLEEP'
  784 |         SLEEP(SaberBase::sound_length * 1000);
      |         ^~~~~

exit status 1

Compilation error: 'class StateMachine' has no member named 'sleep_until_'

It seems to me from the error message that the two variables sleep_until_ & next_state_ are not properly declared in common/state_machine.h, even though they are declared in struct StateMachineState ? Or do I need to declare them in jetpack_prop.h ?

Thanks for any explanation.

You can’t put SLEEP() in any old normal function.
It must be between BEGIN_STATE_MACHINE() and END_STATE_MACHINE().
Also state machines have to be in a function that you call over and over again, like loop() (or a function called from loop()) otherwise it will exit once and not continue.

Finally, for a state machine to work, it needs a StateMachineState to store the state in between function calls. Normally all you need to do is to add:

   StateMachineState state_machine_;

to your class.
In some places, you may see classes that inherit from StateMachine, which basically does the same thing. (This is what the V4TestScript class does, which I linked earlier in this thread.)

Because of how the state_machine_ variable thing, you generally cannot have two state machines in the same class. (Or, at least you can’t have two state machines running at the same time in a single class.)

1 Like

This was problem number1: I had StateMachine state_machine_;, I was missing a State! :crazy_face:

Problem number2: I didn’t have BEGIN_STATE_MACHINE() and END_STATE_MACHINE(). But then it occurred to me that my missile function would start & keep on playing regardless of any button press, so I added a bool missilelaunch_ to make sure it only starts after the correct button press. Is this correct ? Or, do I totally miss-understand StateMachineState ?

    void Loop() override {
        unsigned long now = millis();

        // Perform the missile launch sequence
        if (missilelaunch_) PerformMissileLaunchSequence();

        // Stop idle mode after timeout
        if (idle_ && (now - timer_ > JETPACK_IDLE_TIME)) {
            StopIdleMode(); // Stop jetpack if idle for more than defined time
            return; // Exit early to avoid further processing
        }

        // Start idle loop after transition sound finishes
        if  (idle_)  IdlePlaying();

        // Start flight loop after transition sound finishes
        if (flight_) FlightPlaying();
    }

    void PerformMissileLaunchSequence() {
        STATE_MACHINE_BEGIN();
        PVLOG_NORMAL << "Starting Missile Launch Sequence\n";
        SaberBase::DoEffect(EFFECT_AIMING,0);
        SLEEP(SaberBase::sound_length * 1000 + 10);
        SaberBase::DoEffect(EFFECT_TARGETTING,0);
        SLEEP(SaberBase::sound_length * 1000);
        SaberBase::DoEffect(EFFECT_MISSILELAUNCH,0);
        SLEEP(SaberBase::sound_length * 1000);
        SaberBase::DoEffect(EFFECT_MISSILEGOESBOOM,0);
        SLEEP(SaberBase::sound_length * 1000 + 10);
        if (!flight_) { // Perform "Mando Talk" if not in flight mode!
            SaberBase::DoEffect(EFFECT_MANDOTALK,0);
            SLEEP(SaberBase::sound_length * 1000 + 10);
            PVLOG_NORMAL << "Mando: Nice shot!\nBoba: I was aiming for the other one!\n";
        }
        SaberBase::DoEffect(EFFECT_DISARM,0);
        SLEEP(SaberBase::sound_length * 1000);
        PVLOG_NORMAL << "Missile Launch Sequence Completed!\n";
        missilelaunch_ = false;
        STATE_MACHINE_END();
    }

Here is my latest update:
jetpack_prop.h (25.5 KB)