Music Overlaying Sound and Sound Control for Player

Started by Ghostlady, Sat 25/05/2024 04:35:48

Previous topic - Next topic

Ghostlady

How can I lower the music for a couple seconds to a allow a sound to play and when the sound has ended, bring the music back to the same level?

Is there a way to have a control for the player so they can adjust the music level or turn if off if they want to?
My Games:

Hauntings Of Mystery Manor
Intrigue At Oakhaven Plantation
Haunting at Cliffhouse

Rik_Vargard

I have created a global variable for my music that is an audio channel called BGM.
Then, whenever I want to change the volume I write something like:

Code: ags
BGM = aMySong.Play();
BGM.Volume = 100;
//code
BGM.Volume = 50;
// code
BGM.Volume = 100;

and so on...

eri0o

If you have more than one channel set to music nothing guarantees that such variable will be the same, since you are always overwriting it, so this single global variable only works if you have at most one music playing at the same time, which may be correct by game design. Otherwise you may need an array to keep track of these or use the audio channel array directly.

But yeah, the Audio clip returns a pointer to the audio channel it was assigned.

If you want, you can also vary the volume by audio type.

https://adventuregamestudio.github.io/ags-manual/Game.html#gamesetaudiotypevolume

Code: ags
 Game.SetAudioTypeVolume(eAudioTypeMusic, 20, eVolExistingAndFuture);
Wait(10);
Game.SetAudioTypeVolume(eAudioTypeMusic, 100, eVolExistingAndFuture);

Crimson Wizard

#3
Using AudioChannel's pointer for this purpose is unreliable for multiple reasons. One of them is that AudioChannel.Volume affects only a volume of currently playing sound (or music). If player changes the slider while there's nothing playing on this channel, then nothing gets changed, and next music won't be affected. With this you would have to assign Volume each time you call Play.

Using Game.SetAudioTypeVolume is a correct way to set volume for any currently playing and future music.

For the player you may add a Slider control, and call Game.SetAudioTypeVolume when this slider changes, for example. There are of course other GUI methods, like a label with up/down or left/right arrow buttons, etc; but this is all a question of a GUI design.





In regards to lowering music's volume while a sound is playing...

If that's meant only for particular sounds, then you may write a custom function where you
a) lower music's volume
b) start the sound and save its channel in a global AudioChannel* variable
Then in "repeatedly_execute_always" check if that channel is set, and if it is no longer playing then restore music's volume to a normal value and reset the channel variable to "null".
A script example:
Code: ags
function PlaySoundAndLowerMusic(AudioClip *sound)
{
    SpecialSoundChannel = sound.Play();
    if (SpecialSoundChannel == null)
        return; // if failed to play for any reason, then stop here

    // make sure to also check the player's setting of a music's volume, otherwise we may end up
    // "lowering" it to a higher level instead
    int low_volume = 20;
    if (MusicVolumeSlider.Value < 20) // assuming you have a volume slider called "MusicVolumeSlider"
        low_volume = MusicVolumeSlider.Value;

    Game.SetAudioTypeVolume(eAudioTypeMusic, low_volume, eVolExistingAndFuture);
}

function repeatedly_execute_always()
{
    if (SpecialSoundChannel != null &&
        SpecialSoundChannel.IsPlaying)
    {
        // Restore the music's volume and reset the sound channel pointer
        Game.SetAudioTypeVolume(eAudioTypeMusic, MusicVolumeSlider.Value, eVolExistingAndFuture);
        SpecialSoundChannel = null;
    }
}


On another hand, if you want this to happen whenever any sound is playing, then, instead of having a "PlaySoundAndLowerMusic" function, you might check if any clip of type "Sound" is playing in "repeatedly_execute_always". Then it will look something like:
Code: ags
bool WasMusicLowered;

bool IsAnySoundPlaying()
{
    // we begin checking with channel 1, because channel 0 is reserved for the speech
    for (int i = 1; i < System.AudioChannelCount; i++)
    {
        AudioClip *clip = System.AudioChannel[i].PlayingClip;
        if (clip != null && clip.Type == eAudioTypeSound)
        {
            return true;
        }
    }
    return false;
}

function repeatedly_execute_always()
{
    bool any_sound_playing = IsAnySoundPlaying();
    if (!WasMusicLowered && any_sound_playing)
    {
        // make sure to also check the player's setting of a music's volume, otherwise we may end up
        // "lowering" it to a higher level instead
        int low_volume = 20;
        if (MusicVolumeSlider.Value < 20) // assuming you have a volume slider called "MusicVolumeSlider"
            low_volume = MusicVolumeSlider.Value;

        Game.SetAudioTypeVolume(eAudioTypeMusic, low_volume, eVolExistingAndFuture);
        WasMusicLowered = true;
    }
    else if (WasMusicLowered && !any_sound_playing)
    {
        // Restore the music's volume
        Game.SetAudioTypeVolume(eAudioTypeMusic, MusicVolumeSlider.Value, eVolExistingAndFuture);
        WasMusicLowered = false;
    }
}

Ghostlady

If I use this gui, which is the overall functional gui for both characters that drops down when rolling your mouse at the top of the screen, and add something like a Up and Down arrow or a music note and if they click on it do I need another gui with a slider?



This would be for all sounds and music so if I use the bottom script, would I put that code in each room where I want to check for sound/music?
My Games:

Hauntings Of Mystery Manor
Intrigue At Oakhaven Plantation
Haunting at Cliffhouse

Crimson Wizard

#5
Quote from: Ghostlady on Sat 25/05/2024 18:06:38If I use this gui, which is the overall functional gui for both characters that drops down when rolling your mouse at the top of the screen, and add something like a Up and Down arrow or a music note and if they click on it do I need another gui with a slider?

That is a purely a question of game and gui design; for this I don't have a direct answer. You may do either, or both, as you see fit. From the script's perspective, you would need to make sure that you can read current value. If you make 2 controls, then you'll have to synchronize them somehow, like, having a global variable with this volume setting.


Quote from: Ghostlady on Sat 25/05/2024 18:06:38This would be for all sounds and music so if I use the bottom script, would I put that code in each room where I want to check for sound/music?

You only need to put this code once in the GlobalScript, and it will work for the whole game.

If, for any reason, you would like to disable this behavior for particular rooms, that may also be achieved by adding another global variable acting as a "behavior switch", and checking it prior to rest of this code in repeatedly_execute_always.

Ghostlady

I am not understanding this part of the script:

 int low_volume = 20;
        if (MusicVolumeSlider.Value < 20) // assuming you have a volume slider called "MusicVolumeSlider"
            low_volume = MusicVolumeSlider.Value;

Where is the volume slider?  Is this a global int?
My Games:

Hauntings Of Mystery Manor
Intrigue At Oakhaven Plantation
Haunting at Cliffhouse

Crimson Wizard

Quote from: Ghostlady on Sat 25/05/2024 19:03:11I am not understanding this part of the script:

 int low_volume = 20;
        if (MusicVolumeSlider.Value < 20) // assuming you have a volume slider called "MusicVolumeSlider"
            low_volume = MusicVolumeSlider.Value;

Where is the volume slider?  Is this a global int?


This script assumes that you have created a volume slider in one of your menus, and called it "MusicVolumeSlider".
You may call it differently in your game of course.

If you do not want to create such slider, then replace this with something else, like a variable that you set when changing a volume in your menu.

Ghostlady

Ok I see what you are saying now.  I didn't realize that the slider was created within a gui.

I added the second script to the global script where you stated and I get an error message:

Error: Specified argument was out of the range of valid values.
Version: AGS 3.6.1.24

System.ArgumentOutOfRangeException: Specified argument was out of the range of valid values.
   at WeifenLuo.WinFormsUI.Docking.DockContentCollection.GetVisibleContent(Int32 index)
   at WeifenLuo.WinFormsUI.Docking.DockContentCollection.get_Item(Int32 index)
   at WeifenLuo.WinFormsUI.Docking.DockPaneStripBase.TabCollection.get_Item(Int32 index)
   at WeifenLuo.WinFormsUI.Docking.VS2005DockPaneStrip.EnsureDocumentTabVisible(IDockContent content, Boolean repaint)
   at WeifenLuo.WinFormsUI.Docking.VS2005DockPaneStrip.OnPaint(PaintEventArgs e)
   at System.Windows.Forms.Control.PaintWithErrorHandling(PaintEventArgs e, Int16 layer)
   at System.Windows.Forms.Control.WmPaint(Message& m)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at WeifenLuo.WinFormsUI.Docking.DockPaneStripBase.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

It is also telling me that a serious error occurred, and data may be corrupted.  My main game is on another computer.  I installed the latest version on a different computer so that I can play around with it and not break anything.
My Games:

Hauntings Of Mystery Manor
Intrigue At Oakhaven Plantation
Haunting at Cliffhouse

Crimson Wizard

Quote from: Ghostlady on Mon 27/05/2024 03:47:26I added the second script to the global script where you stated and I get an error message:

This is some error in the Editor, but it's likely not related to the script at all. Could be a random glitch related to the open panels.

Could you try again?

Ghostlady

It's getting an error on this line in red and the error reads:  Nested functions not supported (you may have forgotten a closing brace)

bool WasMusicLowered;

bool IsAnySoundPlaying()
{
    // we begin checking with channel 1, because channel 0 is reserved for the speech
    for (int i = 1; i < System.AudioChannelCount; i++)
    {
        AudioClip *clip = System.AudioChannel.PlayingClip;
        if (clip != null && clip.Type == eAudioTypeSound)
        {
            return true;
        }
    }
    return false;
My Games:

Hauntings Of Mystery Manor
Intrigue At Oakhaven Plantation
Haunting at Cliffhouse

Crimson Wizard

Quote from: Ghostlady on Mon 27/05/2024 05:16:21Nested functions not supported (you may have forgotten a closing brace)

This means that you placed this whole code inside another function. Each function must be separate.

Ghostlady

Ok I fixed that part.  Now an error saying AudioChannel is not a public member of 'System".  Are you sure you spelt it correctly (remember, capital letters are important)?

// we begin checking with channel 1, because channel 0 is reserved for the speech
    for (int i = 1; i < System.AudioChannelCount; i++)
    {
        AudioClip *clip = System.AudioChannel.PlayingClip;
        if (clip != null && clip.Type == eAudioTypeSound)
My Games:

Hauntings Of Mystery Manor
Intrigue At Oakhaven Plantation
Haunting at Cliffhouse


Ghostlady

Where does this part of the code go? I have it in the Repeatedly Execute function but still getting an error about nested functions.

bool WasMusicLowered;

bool IsAnySoundPlaying()
{
    // we begin checking with channel 1, because channel 0 is reserved for the speech
    for (int i = 1; i < System.AudioChannelCount; i++)
    {
        AudioClip *clip = System.AudioChannel.PlayingClip;
        if (clip != null && clip.Type == eAudioTypeSound)
        {
            return true;
        }
    }
    return false;
}
My Games:

Hauntings Of Mystery Manor
Intrigue At Oakhaven Plantation
Haunting at Cliffhouse

Crimson Wizard

Quote from: Ghostlady on Mon 27/05/2024 06:25:29Where does this part of the code go? I have it in the Repeatedly Execute function but still getting an error about nested functions.

Functions must be outside of other functions. You don't need to put anything inside "Repeatedly Execute". Just place the code into the Global Script, anywhere outside of other functions.

My code example has 2 functions: "IsAnySoundPlaying()" and "repeatedly_execute_always()". The latter is a standard function and may be already present in your Global Script. If you do already have it, then append my code from "repeatedly_execute_always" to the end of yours.

Ghostlady

Ok makes sense.  I moved that part to the top of the script.  I am getting a parse error on the System.AudioChannels playing clip line.

Here is the top part of my script:
Code: ags
// Automatically converted interaction variables
int IntVar_Global_1 = 0;
export IntVar_Global_1;
int IntVar_barhaveitems = 0;
export IntVar_barhaveitems;
// main global script file
string herscore;
string hisscore;
int x; 
int y; 
bool WasMusicLowered;
bool IsAnySoundPlaying()
{
    // we begin checking with channel 1, because channel 0 is reserved for the speech
    for (int i = 1; i < System.AudioChannelCount; i++)
    {
        System.AudioChannels[i].PlayingClip;
        if (clip != null && clip.Type == eAudioTypeSound)
        {
            return true;
        }
    }
        return false;
        }

#sectionstart game_start  // DO NOT EDIT OR REMOVE THIS LINE
My Games:

Hauntings Of Mystery Manor
Intrigue At Oakhaven Plantation
Haunting at Cliffhouse

Crimson Wizard

It should be :
Code: ags
AudioClip *clip = System.AudioChannels[i].PlayingClip;

My typo was only in "AudioChannels" word, so I posted only that fixed part last time. But the full assignment of "clip" variable is still necessary.

Ghostlady

It is working perfectly now. :)  Thank you for your time and patience, it's been a while and I am rusty. I do have a question about the music slider.  I have it set up on the gui that I posted above.  If they click on the question mark, which I will change to a music symbol, it opens the slider gui up.  They can then adjust the music. But then the slider gui does not go away unless you click on it.  Is there a better way to do this?
My Games:

Hauntings Of Mystery Manor
Intrigue At Oakhaven Plantation
Haunting at Cliffhouse

Snarky

It just depends on how you want it to act.

If you want it to go away when the player clicks somewhere else, you should probably add a case to on_mouse_click.

If you want it to go away if the player moves the mouse far enough away from the slider, you probably need to check that in repeatedly_execute or repeatedly_execute_always.

If you want it to go away if the player doesn't move the mouse or do anything else for a while, the way the UI works in many video players such as YouTube, you also need to keep track of that in repeatedly_execute.

SMF spam blocked by CleanTalk