Solved Multiple SoundFX in the same animation

Question that is answered or resolved.

Okamijoe

Member
Hello,

I'm a bit confused after reading the manual, especially when it comes to using multiple .wav files in the same animation.

sound {path}
  • {path} points to a sound effect. The sound will be played as soon as the next frame is reached.
  • You can declare more than one, in different frames that is. Beware though, the previous played sound will be immediately stopped when new one is played.
I gave it a try, but it doesn't work like in the description.
Previously played sounds never stop, they keep on playing to the end, no matter how many of them are declared in the same animation.
I tested this with a bunch of different animations and sound files (Build 6391).

Has there been a change to the engine and the manual was not updated?
Is there a way to stop a sound effect from playing when reaching a certain frame without using script?

Thank you for your help!
 
Hello,

I'm a bit confused after reading the manual, especially when it comes to using multiple .wav files in the same animation.

sound {path}
  • {path} points to a sound effect. The sound will be played as soon as the next frame is reached.
  • You can declare more than one, in different frames that is. Beware though, the previous played sound will be immediately stopped when new one is played.
I gave it a try, but it doesn't work like in the description.
Previously played sounds never stop, they keep on playing to the end, no matter how many of them are declared in the same animation.
I tested this with a bunch of different animations and sound files (Build 6391).

Has there been a change to the engine and the manual was not updated?
Is there a way to stop a sound effect from playing when reaching a certain frame without using script?

Thank you for your help!
@Okamijoe

Maybe the manual needs some update on this description, but indeed the engine can pause samples.

I think the simplest way is by using the function pausesamples()in all the frames you want, which will pause all samples at the same time. Depending on what you are trying to do, it can work fine.
Code:
anim attack1
@script if(frame == 1){pausesamples(1);} @end_script
    fastattack 1
    jugglecost 4
    forcedirection -1
    otg 1
    loop    0
    delay    4
    offset    62 126
    bbox    50 55 23 74
    @cmd hitfx "sor3_hit.wav"
    @cmd sound "sor3_attack.wav"
    #attack 62 61 9999 999 2000 1 1 0 5 999 #DEBUG TEST
    frame    data/chars/heroes/axel/a100.png
    attack 62 61 42 13 2 0 0 0 5 12
    frame    data/chars/heroes/axel/a101.png
    attack 0 0 0 0 0 0 0 0 0 0
    frame    data/chars/heroes/axel/a100.png

In case you need to pause a single sound, you will need to use some scripting to get the sound channel and then pausing it.

C:
pausesample(1, querychannel(loadsample("data/sounds/test.wav")));

Below some info from the manual:
1678611440175.png
 
Beware though, the previous played sound will be immediately stopped when new one is played.

I'm not sure who put that in the manual or why, but that was never how sounds worked at any point. It's specifically coded to do the exact opposite.

As @Kratus explained though, you can override the default behaviors to pause and cut sounds short if you want (plus and endless amount of other tricks).

I also suggest you read this. It's under construction but provides a more comprehensive and accurate explanation of how sounds work.


HTH,
DC
 
@Kratus
@DCurrent
Thank you for helping me out with these examples.
I'm not a programmer, so please bear with me.
I tried the script, but I'm getting some strange results, pretty sure I made a mistake somewhere...

First of all, here's what I'm trying to do:
My character charges up for an energy blast (charge sound start playing at frame #4), and when it reaches frame #15, a blast sound is playing and should cancel the charging sound before it.
anim freespecial1
offset 82 177
delay 6
custknife blast
throwframe 14
bbox 67 81 33 96
@script if(frame == 14){pausesamples(1);} @end_script
frame data/chars/cam/blast01.png
frame data/chars/cam/blast02.png
frame data/chars/cam/blast03.png
sound data/sounds/charge.wav
frame data/chars/cam/blast04.png
frame data/chars/cam/blast05.png
frame data/chars/cam/blast06.png
frame data/chars/cam/blast07.png
frame data/chars/cam/blast08.png
frame data/chars/cam/blast09.png
frame data/chars/cam/blast10.png
frame data/chars/cam/blast11.png
frame data/chars/cam/blast12.png
frame data/chars/cam/blast13.png
frame data/chars/cam/blast14.png
sound data/sounds/blast.wav
frame data/chars/cam/blast15.png
frame data/chars/cam/blast16.png
frame data/chars/cam/blast17.png
frame data/chars/cam/blast18.png
frame data/chars/cam/blast19.png
frame data/chars/cam/blast20.png
With the pause all samples script it seems to do the trick, but I noticed that if you pause the game and unpause it again, the paused samples from that animation will resume playing all at once. I guess pausing the sample is not really canceling it, and it keeps being stored in memory, which I think could later cause problems if you run the same animation a few times in a row?

anim freespecial1
offset 82 177
delay 6
custknife blast
throwframe 14
bbox 67 81 33 96
@script if(frame == 14){pausesample(1, querychannel(loadsample("data/sounds/charge.wav")));} @end_script
frame data/chars/cam/blast01.png
frame data/chars/cam/blast02.png
I tried the other method, to pause a single specific sample, but this makes the engine crash as soon as the animation is reaching frame #14...
The log says this:
Script function 'querychannel' returned an exception, check the manual for details.
parameters: 10,
So I'm kind of lost what to do next. Any suggestions?

Thank you!
 
With the pause all samples script it seems to do the trick, but I noticed that if you pause the game and unpause it again, the paused samples from that animation will resume playing all at once. I guess pausing the sample is not really canceling it, and it keeps being stored in memory, which I think could later cause problems if you run the same animation a few times in a row?

It doesn't affect memory like you think. I'll need to explain at length and maybe it will help you overall.

First, each sound file is stored into RAM in an indexed list. The engine uses a smart loading routine that ensures any given file is only stored one time. It looks something like this, and has no practical limit other than your platform RAM:

Sound Files Loaded

Sound IndexFile
0my_cool_sound_a.wav
1my_cool_sound_b.wav
2my_cool_sound_c.wav

Next, there is a second list for playing sounds. Each entry houses an array of properties needed to play a sound. You can think of these sort of like old school hardware sound channels. The playback list has 64 entries (channels), meaning you can play up to 64 sounds simultaneously.

Each time a sound request occurs, the engine finds the first available open channel. It then populates that channel with the stuff it needs to play a sound. If there are no channels open it finds the one with lowest priority property, and compares it to the incoming sound request priority. If the new sound priority is lower, the request is ignored. Otherwise OpenBOR replaces the channel's data with the new sound request. Note that sounds all have a default priority of 0.

Here's what an entry in the playback list looks like. Notice how there's no sound file, just a reference to an index in the sound file list, in this case to my_cool_sound_c.wav.

Playback List (Channels)

Channel IndexSound IndexVolume LeftVolume RightSpeedLoopPriority
0210010010000

Elsewhere in the code, a playback routine reads all the channels and does the work of actually playing each sound. Once a respective channel's sound is finished playing, the channel clears and it's ready for use by another sound request. Also note the engine doesn't give two spits what the sound is. You could play a lengthy music track if you wanted.

In short, there is by design exactly one copy of sound data in memory, no more or less. Play a sound 500 times, pause, fill up every single sound channel at once, it will never need more. This is one of the many things OpenBOR does to make itself optimal as possible.

HTH,
DC
 
Last edited:
It doesn't affect memory like you think. I'll need to explain at length and maybe it will help you overall.

First, each sound file is stored into RAM in an indexed list. The engine uses a smart loading routine that ensures any given file is only stored one time. It looks something like this, and has no practical limit other than your platform RAM:

Sound Files Loaded

Sound IndexFile
0my_cool_sound_a.wav
1my_cool_sound_b.wav
2my_cool_sound_c.wav

Next, there is a second list for playing sounds. Each entry houses an array of properties needed to play a sound. You can think of these sort of like old school hardware sound channels. The playback list has 64 entries (channels), meaning you can play up to 64 sounds simultaneously.

Each time a sound request occurs, the engine finds the first available open channel. It then populates that channel with the stuff it needs to play a sound. If there are no channels open it finds the one with lowest priority property, and compares it to the incoming sound request priority. If the new sound priority is lower, the request is ignored. Otherwise OpenBOR replaces the channel's data with the new sound request. Note that sounds all have a default priority of 0.

Here's what an entry in the playback list looks like. Notice how there's no sound file, just a reference to an index in the sound file list, in this case to my_cool_sound_c.wav.

Playback List (Channels)

Channel IndexSound IndexVolume LeftVolume RightSpeedLoopPriority
0210010010000

Elsewhere in the code, a playback routine reads all the channels and does the work of actually playing each sound. Once a respective channel's sound is finished playing, the channel clears and it's ready for use by another sound request. Also note the engine doesn't give two spits what the sound is. You could play a lengthy music track if you wanted.

In short, there is by design exactly one copy of sound data in memory, no more or less. Play a sound 500 times, pause, fill up every single sound channel at once, it will never need more. This is one of the many things OpenBOR does to make itself optimal as possible.

HTH,
DC
@DCurrent
After reading your explanation I understand that stopping and clearing the channels is the best way to stop samples, instead of pausing and causing it to play again after some events (like pausing/unpausing the game).
Thanks to it I searched the engine for a scripted way to stop channels and luckily I found a undocumented script function that does exactly the job. I didn't find it in the manual or in the new Wiki, but we could update adding this one stopchannel(channel).

@Okamijoe
Thanks to DC explanation I was able to develop a better code to stop samples instead of pausing it. Please, replace the pausesamples method with this new one and let me know if it works for you.
Code:
anim attack1
@script
if(frame == 1)
{
    int maxchannels = openborvariant("maxsoundchannels");
    int channel;

    //STOP ALL SAMPLE CHANNELS
    for(channel = 0; channel < maxchannels; channel++){
        stopchannel(channel);
    }
}
@end_script
    fastattack 1
    jugglecost 4
    forcedirection -1
    otg 1
    loop    0
    delay    4
    offset    62 126
    bbox    50 55 23 74
    @cmd hitfx "sor3_hit.wav"
    @cmd sound "sor3_attack.wav"
    #attack 62 61 9999 999 2000 1 1 0 5 999 #DEBUG TEST
    frame    data/chars/heroes/axel/a100.png
    attack 62 61 42 13 2 0 0 0 5 12
    frame    data/chars/heroes/axel/a101.png
    attack 0 0 0 0 0 0 0 0 0 0
    frame    data/chars/heroes/axel/a100.png

@Kratus
pausesamples something like: when the second sound plays the first sound will stop? Is it like this?
@machok
Yeah, you can adapt the code to work like this too.
 
Thanks to it I searched the engine for a scripted way to stop channels and luckily I found a undocumented script function that does exactly the job. I didn't find it in the manual or in the new Wiki, but we could update adding this one stopchannel(channel).

Thought I put stopchannel() in there. Sorry about that. I'll get it updated.

DC
 
Thought I put stopchannel() in there. Sorry about that. I'll get it updated.
No problem buddy, after I posted the last message I went back to both Wiki/Manual and I found a reference used as an example, we only need to list it as an available function like the others.

1678660938816.png
 
@Kratus

@script
if(frame == 1)
{
int maxchannels = openborvariant("maxsoundchannels");
int channel;

//STOP ALL SAMPLE CHANNELS
for(channel = 0; channel < maxchannels; channel++){
stopchannel(channel);
}
}
@end_script
First of all, thank you for your script examples!
I tried this new code, but unfortunately it crashes the engine as soon as it reaches the set frame again.
The log says this:
Script function 'stopchannel' returned an exception, check the manual for details.
parameters: 0,
Any idea what could cause this?
 
@Kratus


First of all, thank you for your script examples!
I tried this new code, but unfortunately it crashes the engine as soon as it reaches the set frame again.
The log says this:

Any idea what could cause this?
@Okamijoe
Hmm maybe the stopchannel is not available in all engine builds or could be a bug in previous versions, tested again in the build I'm using and works fine.
I will check it.
 
Yes, it works with this one (Build 7123)! Should I use this version of the engine, or is it a Dev-Build that's just for testing and not stable yet?
Like I suspected, someone fixed this function in the source after the 6391 launch.
Yes, you can use this build in case you don't find any issue with your project. This build is the same I'm using for the latest SOR2X version, for my game it works fine.
Even if this one was not launched officially, it was compiled using the official source code.
 
Like I suspected, someone fixed this function in the source after the 6391 launch.
Yes, you can use this build in case you don't find any issue with your project. This build is the same I'm using for the latest SOR2X version, for my game it works fine.
Even if this one was not launched officially, it was compiled using the official source code.
Thank you so much!
I have one more request:
In some instances I really need to specify only one sound effect I want to stop, can you give me the code which lets me define the sample path (like in the pausesample method)?
If it's not too much trouble, of course.
 
Thank you so much!
I have one more request:
In some instances I really need to specify only one sound effect I want to stop, can you give me the code which lets me define the sample path (like in the pausesample method)?
If it's not too much trouble, of course.
No problem, I only need to take some tests first but I'm away from my PC now. As soon as I get the code working, I will post it here.
 
@Okamijoe
I made some tests and the easiest way to stop a single sound is by detecting the last sample played, you can use the frame number as a reference to stop the correct sound.

Code:
anim attack1
@script
if(frame == 1)
{
    stopchannel(querychannel(openborvariant("sample_play_id")));
}
@end_script
    loop    0
    delay    5
    offset    45 119
    bbox    21 2 45 119
    sound data/sounds/hit1.wav
    frame    data/chars/maxima/punch000.gif
    frame    data/chars/maxima/punch001.gif

1678741440429.png
 
@Okamijoe
I made some tests and the easiest way to stop a single sound is by detecting the last sample played, you can use the frame number as a reference to stop the correct sound.

Code:
anim attack1
@script
if(frame == 1)
{
    stopchannel(querychannel(openborvariant("sample_play_id")));
}
@end_script
    loop    0
    delay    5
    offset    45 119
    bbox    21 2 45 119
    sound data/sounds/hit1.wav
    frame    data/chars/maxima/punch000.gif
    frame    data/chars/maxima/punch001.gif

View attachment 3398
This works perfectly! Thank you very much for your help! 🤩
@Kratus
Edit: I encountered a small problem: If the animation gets interrupted by an enemy attack before it reaches the specified frame, it keeps on playing.
Can the code be modified somehow so that last active sample will stop if the animation gets interrupted as well?
 
Last edited:
This works perfectly! Thank you very much for your help! 🤩
@Kratus
Edit: I encountered a small problem: If the animation gets interrupted by an enemy attack before it reaches the specified frame, it keeps on playing.
Can the code be modified somehow so that last active sample will stop if the animation gets interrupted as well?
Glad to know the code worked :)

About the animation interruption it can be a bit more complex, but it's doable with a few more scripts. Maybe saving the sample channel into a variable at the moment it's played can help to stop the proper channel when necessary in other animation, once the openborvariant "sample_play_id" gets only the latest and other samples can be played during this time.

EDIT: I coded an example to check if it works, and yes, tested it works exactly as intended. Don't forget to apply the script to stop the sample in all pain/fall animations.

Code:
anim attack1
@script
if(frame == 0)
{
    //PLAY SAMPLE AND SAVE THE USED CHANNEL IMMEDIATELY
    playsample(loadsample("data/sounds/hit.wav"), 0, 120, 120, 100, 1);
    setentityvar(getlocalvar("self"), "usedChannel", querychannel(openborvariant("sample_play_id")));
}
@end_script
    loop    0
    delay    5
    offset    45 119
    bbox    21 2 45 119
    frame    data/chars/maxima/punch000.gif
    frame    data/chars/maxima/punch001.gif

Code:
anim pain
@script
if(getentityvar(getlocalvar("self"), "usedChannel") != NULL())
{
    //GET THE USED CHANNEL PREVIOUSLY SAVED, STOP THE SAMPLE AND CLEAR THE VARIABLE
    stopchannel(getentityvar(getlocalvar("self"), "usedChannel"));
    setentityvar(getlocalvar("self"), "usedChannel", NULL());
}
@end_script
    loop    0
    offset    67 117
    bbox    48 12 37 105
    delay    8
    frame    data/chars/maxima/pain02.gif
    delay    36
    frame    data/chars/maxima/pain01.gif

Code:
anim fall
@script
if(getentityvar(getlocalvar("self"), "usedChannel") != NULL())
{
    //GET THE USED CHANNEL PREVIOUSLY SAVED, STOP THE SAMPLE AND CLEAR THE VARIABLE
    stopchannel(getentityvar(getlocalvar("self"), "usedChannel"));
    setentityvar(getlocalvar("self"), "usedChannel", NULL());
}
@end_script
    loop    0
    offset    67 67
    delay    20
    attack    38 16 44 49 17 1
    frame    data/chars/maxima/fall00.gif
    attack    13 48 107 26 17 1
    frame    data/chars/maxima/fall01.gif
 
Last edited:
Back
Top Bottom