Make two different audio files play sequentially instead of at the same time?

Very simple - two lines to play two sound effects, something like this:

case EVENTID(BUTTON_POWER, EVENT etc:
      PlaySound("mysound1.wav");
      PlaySound("mysound2.wav");

but obviously written like this they both play at the same time, and I want the system to wait until the first has finished before playing the second. I know I can use delay(3000); or some similar value in between them, but I really want it to base the second play off the first file finishing as there are likely to be different file lengths of the first sound.

Iā€™ve tried various things, some of which even compile, but none seems to work correctly.

Iā€™m sure Iā€™ve seen a simple line somewhere to tell the system to wait until the first audio file finishes, but I canā€™t seem to find it anywhere.

You need to use SoundQueue.

1 Like

Many thanks Fernando!
Just what I needed! :pray: :smiley:

No, you cannot. That would prevent ProffieOS from working for 3 seconds.
More information here:

Hmmmmā€¦ turns out this is isnā€™t quite as simple as I thought it would beā€¦

I actually want this to play a second sound file after a first sound file has run, but the first soundfile playing is kind of embedded in the array select process. If Iā€™ve understood it right, to use SoundQueue, I need to get the system to unpick which soundfile is being played by the first routine in order to add that file to the SoundQueue so that it (Soundqueue) can monitor it to know when itā€™s finished playing.

But it gets worse! Not only do I need to unpick which soundfile name is being played, I also need to unpick which edition of that name is being played, because the first file in question is the arrayX.wav which has a unique number which is determined by the first routine.

And Prof, yes, Iā€™ve been reading up about polling, and that does indeed seem to be the way to make this work, but as I understand it, it still means I need to extract those sound file name details in order to tell the system which audio player event to poll. Donā€™t I?

I think on balance itā€™s going to be too many hoops to jump through, compared to simply adding a fixed delay long enough to accommodate the longest arrayx.wav file thatā€™s likely to be used. I know thatā€™s not especially slick from an end user point of view, but itā€™s a lot more streamlined in terms of coding.

That said, Prof, I donā€™t quite understand when you say:

Iā€™ve tried exactly this and it does work, though Iā€™m guessing from what you say that the only reason it does is because between switching to an array (which includes playing an array ident) and then playing a font file, ProffieOS isnā€™t being asked to do anything else - the blade is off and the switching happens in a fraction of a second, but playing the array ident takes perhaps 1.5 to 2 seconds, and once it starts playing, ProffieOS can put its feet up until the next thing which is playing the font wav? Or have I got this all back to front?

Like I said before, Iā€™m learning tons, even if it doesnā€™t always result in getting a feature to work. :slight_smile:

My 2 cents. Use existing systems.
Add your sound to sound_library.h.
Call it in your prop like:

sound_library_.SayMySound1();
sound_library_.SayMySound2();

The 2nd sound will play as soon as the 1st is finished.

Youā€™ll need to add #include "../sound/sound_library.h" up top where you include prop_base.h.

Thanks Brian.
But I was aiming to make everything work without having to make changes to anything outside the prop file, though I recognise that as things get more ambitious that probably becomes less viable.

The more you want to add, the more you will need to change (in or out your prop)

1 Like

OK, Iā€™m revisiting this, and trying to not only get my head around polling and what it actually is, but also how to access it and what syntax to use.

At the risk of being burned for heresy, Iā€™ve been using ChatGPT to try and get it to explain these principles and help with coding mods, but of course it keeps directing me to lines of code that donā€™t exist in ProffieOS.

So if I have this code for a fixed delay between playing the sound file and doing the reboot:

        if (SFX_reset) {  // Optional sound file to confirm deletions.
          hybrid_font.PlayCommon(&SFX_reset); // Plays file before reboot.
          delay(2700);  //  To allow sound file to finish playing.
          STM32.reset();  //  Reboots system. 
        } else {
          STM32.reset();  //  Reboots system immediately if no reset.wav available. 
        }
        }

What do I need to insert instead of delay(2700); to make the system know when the actual wav file has stopped playing?

This is what ChatGPT said, and of course it looks lovely and simple:

    if (SFX_reset) {  // Optional sound file to confirm deletions.
          hybrid_font.PlayCommon(&SFX_reset); // Plays file before reboot.
          while (IsSoundPlaying()) {  //  To allow sound file to finish playing.
          STM32.reset();  //  Reboots system. 
        } else {
          STM32.reset();  //  Reboots system immediately if no reset.wav available. 
        }
        }

but obviously it doesnā€™t work because IsSoundPlaying - while being wonderfully descriptive ā€“ isnā€™t part of ProffieOS. :confused:

So is there a similarly simple solution, and if not, how do I go about actually polling the loop (is that a thing?) and what is the syntax and/or setup to make this work?

Polling is a generic programming turn which means that you somehow have to keep checking if something is done or not. The opposite of polling is waiting, which usually means that you call a function that doesnā€™t return until something is done.

waiting is generally discouraged in ProffieOS, because it doesnā€™t have threads, so if you call a function that doesnā€™t return for a while, that means that nothing else will happen while we wait. delay() is a function that waits, and so it is discouraged. While while (IsSoundPlaying()); is technically polling (since it calls IsSoundPlaying() over and over until it returns false.) it is also waiting, because it doesnā€™t allow the program to continue.

To do polling properly, you have to do something like this:

hybrid_font.PlayCommon(&SFX_reset);
reset_after_sound_has_played_ = true;

Then, in the loop function, which gets called over and over again:

  if (reset_after_sound_has_played_ && !IsResetSoundPlaying()) {
    STM32.reset();
  }

Of course, we also need an IsResetSoundPlaying() function, which could look like this:

  bool IsResetSoundPlaying() {
     return !!GetWavPlayerPlaying(&SFX_reset);
  }

Normally, this is what you want to do to make ProffieOS able to continue to operate like normal while youā€™re waiting for a sound to stop playing so you can do something else. Itā€™s in fact exactly what the sound queue class does, which can help with these sort of things.

However, in this particular case, none of this makes sense, because if you change presets, or press the wrong button, ProffieOS could create new save files, which is not actually what you want. So doing the easy thing and just call delay() or while(IsResetSoundPlaying()); is probably better, because that will absolutely prevent ProffieOS from creating new save files. (Or doing anything other than just wait and then reset.)

2 Likes

I should probably also point out that ā€œpollingā€ is a bad programming practice in most languages/contexts. In a real OS which has multiple threads and multiple processes, polling uses up CPU power that could otherwise be used for something else. ProffieOS is different.

1 Like

Prof, you have worked your magic again! :smiley:

Iā€™ve been reading up, consulting with @NoSloppy and circling the issue like a hawk circling a mouse, but Iā€™ve been missing the target by a whisker over and overā€¦ until now!

Your last post served to pull together all the elements Iā€™d been wrestling with, and put all Brianā€™s info into focus, and itā€™s now working exactly as I want it to! :ok_hand: :+1:

Two nights running and Iā€™m a happy coder! :slight_smile: Iā€™m also quietly confident that I now have the kit of parts needed to add the final polish to my setup!

Sincere thanks again to you all - @profezzorn @NoSloppy @Fett263 - Iā€™m in all of your debt as always. :pray:

oooo. Is this double bang saying ā€œnot-notā€, and the double negative guarantees a boolean is returned? That is neat! That would return a pointer otherwise, yes?
Wait, no. it would return the number of the wav player, which is an int.
Doing !! make an integer become a FALSE, then inverts to a TRUE.
or
the inverse case that if it was zero, (somewhat falsy), it becomes TRUE, then FALSE.
Cool.
So logical haha.

So what is IsPlaying() then? I see itā€™s in playwav.h as a helper function, but I get lost as it does run_.get().

1 Like

Yes

No, you were right the first time, you can read the code here:

isPlaying is a method in the the PlayWav class. ProffieOS has seven instances of the PlayWav class in memory, so you have to figure out which one you want before you can call isPlaying on it. If you want to know if any sound is playing, you could iterate over all seven instances and call isPlaying on them. If any of them return true, then it is currently playing a sound.

Finding out if a sound is playing or not isnā€™t as useful as finding out if a specific sound is playing though. There could be another PlayWav instance playing a track, and if we waited for all sounds to stop playing, we could be waiting a whileā€¦

2 Likes

Thank you for the explanations.

OK, Iā€™ve been working a little more on doing this ā€˜rightā€™, following some extremely generous help from @NoSloppy (thanks again for that Brian :pray:). But Iā€™m sharing this question here as it might help others as well as myself.

Iā€™ve ignored the reset thing, as we agreed that locking the system while that ran was actually beneficial. So Iā€™m working now on two things - playing a bladeidX.wav when using on-demand bladeid scanning, and playing an arrayX.wav when using manual array switching - both of which I want to be followed by playing the font ident (subject to a define).

Iā€™ll focus just on array switching as, for the benefits of this, they both work the same.

So Iā€™ve re-arranged things so that this is now in my loop section:

  //  For enabling sequential sounds effects and processes on array handling.
#ifdef SABERSENSE_ARRAY_SELECTOR
        if (donewfont_after_sound_has_played_ && !IsArraySoundPlaying()) {
        SaberBase::DoNewFont();
        }
#endif

This is the main code:

#ifdef SABERSENSE_ARRAY_SELECTOR
    bool IsArraySoundPlaying() {
     return !!GetWavPlayerPlaying(&SFX_array);
    } 
#ifdef SABERSENSE_ENABLE_ARRAY_FONT_IDENT  //  Plays 'array' sound AND 'font' sound.
    void NextBladeArray() {
      FakeFindBladeAgain();
        SFX_array.Select(current_config - blades);
        hybrid_font.PlayCommon(&SFX_array);
          donewfont_after_sound_has_played_ = true;
            return false;
//          if (!IsArraySoundPlaying());  //  Wait for 'array' sound file to finish...
//        SaberBase::DoNewFont();  //  ...then play font ident.
        }
#else
    //  Plays 'array' sound only, or 'font' sound if no 'array' sound available.
    void NextBladeArray() {
      FakeFindBladeAgain();
        if (SFX_array) {
          SFX_array.Select(current_config - blades);
          hybrid_font.PlayCommon(&SFX_array);  //  Play 'array' sound file if available.
        } else {
        SaberBase::DoNewFont();  //  Play font ident if 'array' sound file missing.
        }
    }
#endif
#endif

And this line has been added to my private bool section at the bottom of the prop:
bool donewfont_after_sound_has_played_ = false;

So if Iā€™ve understood it right, what should happen is this:

Loop keeps on asking has the array wav finished and is it still playing. If it gets a false to either of those - which it does to at least one of them by default because of the bottom bool line, it returns false and moves on. But once both of those returns true, it knows it has to run SaberBase::DoNewFont();
once the previous effect has finished.

This, I think, follows the advice given above, except for the fact that in my version,
if (!IsBladeidSoundPlaying());
and
SaberBase::DoNewFont();
are shown twice - once in the loop and once in the main code. Iā€™m pretty sure this is wrong, but I canā€™t quite figure out what it should be instead.

What seems to be happening is that when
donewfont_after_sound_has_played_ = true;
which is in the main code calls
if (donewfont_after_sound_has_played_ && !IsArraySoundPlaying()) {
from loop,
SaberBase::DoNewFont();
gets stuck in a loop playing the first few milliseconds of the font ident over and over again until I hit the kill switch. But if I leave out the
donewfont_after_sound_has_played_ = true;,
weā€™re back to arrayX.wav and font ident playing at the same time.

Conversely, if I leave out the two lines from the main code
if (!IsBladeidSoundPlaying());
and
SaberBase::DoNewFont();,
I again get the weird looping effect.

Iā€™ve tried leaving out
SaberBase::DoNewFont();
from both the main code and the loop, plus as many other combinations as I can think of, but thereā€™s clearly some little detail wrong. It almost feels like I need a return false; somewhere, but when I try that, it wonā€™t compile.

What have I missed?

Update:
I think Iā€™ve figured this out, and I have actually got it working, but I wouldnā€™t mind an expert just taking a look in case Iā€™ve inadvertently left some kind of landmine in my system, or otherwise deviated from accepted practiceā€¦

So the problem seems to have been that the main code was calling the loop function and flipping the return of donewfont_after_sound_has_played_ from false to true, thereby making it qualify for running the next line of loop, i.e. running SaberBase::DoNewFont(); The problem was that there was nothing to return the donewfont_after_sound_has_played_ status back to false afterwards , so the system just kept trying to run SaberBase::DoNewFont(); indefinitely.

So Iā€™ve changed the main code to look like this:

void NextBladeArray() {
      FakeFindBladeAgain();
        SFX_array.Select(current_config - blades);
        hybrid_font.PlayCommon(&SFX_array);
              donewfont_after_sound_has_played_ = !donewfont_after_sound_has_played_;

This means that once SFX_array starts playing, it flips the return status, thus calling loop to wait until the effect finishes. Then in loop, Iā€™ve changed it to this:

        if (donewfont_after_sound_has_played_ && !IsArraySoundPlaying()) {
        SaberBase::DoNewFont();
        donewfont_after_sound_has_played_ = !donewfont_after_sound_has_played_;
        }

Thus the main code flips the return, loop performs SaberBase::DoNewFont();, then it (loop) flips the status back again to false straight away, thus ensuring that the system doesnā€™t keep trying to run SaberBase::DoNewFont(); over and over again.

So as mentioned, the question is, is this a legit solution?
House points or detention?
Thoughts welcome.
Thanks in advance.
:slight_smile:

2 Likes

Yes it is.
It may need some minor cleanup, but overall the function is basically what itā€™s supposed to be.

1 Like

Thanks Prof!
Music to my ears! :smiley: