GAWD! The AGS sound channel system is SO hard to understand!

Started by Mandle, Thu 20/10/2016 15:40:38

Previous topic - Next topic

Mandle

So...to start off: this is very embarrassing...

I've been bashing my head up against the simple problem of how to change the volume setting of two looping sound effects in a project...

No matter what I wiki, google, or whatever I still only get no results, or I get Null-pointer errors...

(An extra frustration point is that actually had it working, until I imported the second sound effect, and then everything went to hell, even if I reverted to the original code by taking out the new stuff with //comments)

I CANNOT understand how the sound channels work!

Could some kind person please give me a quick "DUMMIES" guide that I can understand?

I know I'm going to get bashed for not providing examples of code etc...

But I have been through countless posts of people who could not understand the AGS system of simply changing the volume of one sound effect (I've gotten around this in the past by just installing several of the same effect with different starting volumes... horrible, I know)

Can anyone explain how the whole shebang works?

I'm at a loss...And I have tried!

selmiak

Think of it as different CD Players or so. You can play a backgroundmusic track on the music channel (insert the musicCD into the music CD Player), insert the ambient noise into the ambient noise Player, and every now and then you press play on the SFX CD Player that plays the selected track (soundeffect) whenever you want it to. They all don't interfere with each other. And each CD Player (music channel) has it's own volume so you can play with that.
In case you know you might have 2 SFX playing at the same time or overlapping just use 2 different CD Players /2 different channels.
Mastervolume (or is it called Game.Volume?) silences all channels (CD Players) at once, like leaving the room with all the players or so.
Was this understandable?

Crimson Wizard

#2
Another variant of explanation :).

Audio Channel --> is a SLOT.
Audio Clip --> is a playable resource.

There is always a FIXED number of Audio Channels (SLOTS) in the game.
Audio Channels always exist, even if nothing is played on them.

Audio Clip always exists too and is immutable. When you ask AGS to play a clip, an instance, COPY, of a clip is being inserted into channel (SLOT) and played.

By doing aClip.Play() you ask AGS to put this clip's instance into available channel and play it. AGS returns you a pointer to audio channel (SLOT) if it succeds or null, if no channel is available.
If there are not enough channels, and new sound has equal or more priority than the one which is played on some slot, then that sound will be stopped, and new sound put in channel and played.


Since Audio Channels are fixed thing that always exists, the pointer to Audio Channel is the pointer to fixed, always present object. That pointer keeps working even after the clip stopped playing. So you may end up changing volume of another playback, by the way, if you keep using same pointer all the time.

Since COPIES of AudioClip are played on channels, changing their volumes during playback does not change original AudioClip default volume (this is done using different commands).


You can find out how many AudioChannels you have and access them using System.AudioChannels array and System.AudioChannelCount property.
You can find out how many AudioClips you have and access them using Game.AudioClips array and Game.AudioClipCount property.

You cannot, sadly, know INSTANCES of which clips are currently played, at least not directly. To know this, you must check every existing audio channel to see what is playing on them.


Example of checking which clips are playing right now:
Code: ags

int i = 0;
while (i < System.AudioChannelCount)
{
  AudioChannel *chan = System.AudioChannels[i];
  if (chan.PlayingClip != null)
  {
    // Hey, something is playing on this channel
    // AWWWW, no way to know the clip's friendly name! let's compare the pointers then
    AudioClip *clip = chan.PlayingClip;
    if (clip == aMyMusic)
      Display("My music is played on channel %d", chan.ID);
    else if (clip == aMySound)
      Display("My sound is played on channel %d", chan.ID);
  }
  i++;
}

selmiak


Mandle

Thanks so much guys! What amazingly clear explanations! They should be copy/pasted into the manual!

HandsFree

I confess I still don't really see how I'm supposed to use it either.
If I want to play the sound of wind and stop it later I just do
aWind.Play();
aWind.Stop();

But that's not using channels. If I want to use channels, I have to make a global variable 'wind' and go
wind = aWind.Play(); Is that correct?

But that suggests that I have to create a separate channel for every single sound in the game. That can't be true is it?
But then how am supposed to handle playing different sounds?
Should I make 5 channels: sound1, sound2 etc, and decide myself what sound will be played on what channel? But then I have to keep track of what is playing where and when, which is what AGS does for me if I don't use channels but just do aSound.Play();

Still confused. :-\

Jack

The number of channels you have for any given type of audio is constant and defined in the "Types" audio section. Sounds will play on the first available channel, and as I understand it, other sounds will be "overwritten" when there are no more free channels available. Meaning they will stop playing and the new one will start in its place.

You don't have to define a channel for every sound as they will be re-used. The pointer just lets you know which channel it's being played on, in case you want to modify that sound/channel while it's playing, like adjusting the volume to fade out, etc.

Crimson Wizard

One thing I got to mention
Quote from: HandsFree on Sat 22/10/2016 12:06:37
If I want to play the sound of wind and stop it later I just do
aWind.Play();
aWind.Stop();

There is a difference between AudioChannel.Stop and AudioClip.Stop.
AudioChannel.Stop will stop anything playing on the channel, - the instance of some clip - and only that. That could be useful if you may have different instances of same clip playing at the same time and you want to stop only one, OR if you may have any random clip playing on the channel, and you do not know which one (e.g. one of a set of wind sounds).
AudioClip.Stop will stop ALL instances of that clip playing right now. That could be useful if you are absolutely sure there is always only one instance of that clip playing, or maybe if you want to stop the sounds whatsoever.



As Jack said in the post above, you do not have to control which channels you play your sounds on, if you don't want to. Clip.Play() will make it play on any free channel available for its AudioType, or override existing playback if there are no free ones.

Now, one thing that may not be immediately obvious is that if you wanted to have more precise control over which clips play on which channels, there is actually a workaround to that. You can add custom Audio Types, and define a strict number of channels (e.g. 1) per each type. Then arrange your clips around those AudioTypes. This way you may have reserved channel(s) for certain sets of clips to make sure they will always play without problem.

Snarky

Quote from: HandsFree on Sat 22/10/2016 12:06:37
I confess I still don't really see how I'm supposed to use it either.
If I want to play the sound of wind and stop it later I just do
aWind.Play();
aWind.Stop();

But that's not using channels. If I want to use channels, I have to make a global variable 'wind' and go
wind = aWind.Play(); Is that correct?

Not quite. The system uses audio channels regardless (you are literally calling the same system function in both cases). The question is just whether you want to keep track of them. And also it doesn't need to be a global variable: any kind of variable will do (local, script-scope, struct member), depending on what you need it for.

Imagine that each audio channel is a separate music player with a separate speaker. Each player can play one thing at a time, and you have about 8 of them. When you ask the system to play an audio clip, AGS will look for a player that isn't currently playing anything, and tell that player (i.e. audio channel) to play the clip. As it does so, it lets you know which of the players it used; so if that's something you you are about (because you might want to adjust the volume or stop it from playing later on), you can store that value in a variable. If you don't need to, you don't have to; it will still play the same either way.

To stop a clip later on, there are two ways:

1. If you stored the player (audio channel) that the clip is playing on in a variable, you can call AudioChannel.Stop() on that variable. However, it might be that the clip has already stopped playing, and in that case AGS may now be using the player/channel to play something else. There are ways to check for this (see CW's sample), but you should probably only use this approach if the sound logic is written in a way so that it can't happen (e.g. with looping sounds that aren't in danger of being preempted).

2. You can just call AudioClip.Stop() for the audio clip, like in your example. This is fine, but it won't just stop just any particular instance of the clip being played, but all of them. So if it's the sound of a glass breaking, for example, and you're playing it separately for a lot of glasses shattering, it will stop all of them. This should only be a problem in rare circumstances, though.

Quote from: HandsFree on Sat 22/10/2016 12:06:37
Should I make 5 channels: sound1, sound2 etc, and decide myself what sound will be played on what channel?

No, absolutely not. And in fact I don't think you even can: there's no way AFAIK to tell AGS to play something on a specific channel. (Edit: Apart from the workaround CW described, but even that doesn't allow you to choose a channel freely, just predefine one that will always be used.)

Finally, I want to go back to this:

Quote from: HandsFree on Sat 22/10/2016 12:06:37
If I want to use channels...

It's not about wanting to use channels or not. The audio channels are how the AGS sound system works. (And having some understanding of it is helpful however you want to use it.) It's about you implementing certain effects in your game. Keeping track of the channels is necessary for some effects, but most often it is not.

Arlann

I also want to ask a question about an old problem that I encountered.

I tried to put footstep sounds to several characters (frame sounds) but I wanted these sounds to have a lower priority than other sound effects played from the script.
So I created an AudioType “Foley” with 4 max channels.
A folder with default type “Foley”, High priority by default, where i put my sound effects.
An other folder with my footstep audio files, with default type “Foley” and Low priority by default.
Is the ViewFrame.LinkedAudio uses the audio file default priority and there audiotype properties ?
Because in my method, all the footsteps, had priority over other sounds effect i played from the script.
I haven't found a solution and I finally decided to disable the frame sounds of secondary characters to not overload the sound effects channels.
But maybe my approach was bad...

(please excuse my english :-[)

Alan v.Drake

Quote from: Arlann on Sat 22/10/2016 15:21:12
I also want to ask a question about an old problem that I encountered.

I tried to put footstep sounds to several characters (frame sounds) but I wanted these sounds to have a lower priority than other sound effects played from the script.
So I created an AudioType “Foley” with 4 max channels.
A folder with default type “Foley”, High priority by default, where i put my sound effects.
An other folder with my footstep audio files, with default type “Foley” and Low priority by default.
Is the ViewFrame.LinkedAudio uses the audio file default priority and there audiotype properties ?
Because in my method, all the footsteps, had priority over other sounds effect i played from the script.
I haven't found a solution and I finally decided to disable the frame sounds of secondary characters to not overload the sound effects channels.
But maybe my approach was bad...

(please excuse my english :-[)

Unless it was changed in the last 3 years, "MaxChannels" actually means "RESERVE X CHANNELS ONLY FOR THIS".
So by setting it to 4, you're reducing by 4 the available channels for all the other sounds.

- Alan

Crimson Wizard

Actually there is a total max of 8 audio channels in AGS... :-X
I do not know if Editor automatically detects if users configure their audio incompatibly.

Arlann

QuoteSo by setting it to 4, you're reducing by 4 the available channels for all the other sounds.
I just wanted to limit this audio type to 4 channels to keep the other for music, atmospheres ...
But I could not give to footstep (frame.LinkedAudio) a lower priority than more important sounds for this channels.

Snarky

I just realized I'm confused on one point. You say:

Quote from: Crimson Wizard on Thu 20/10/2016 17:29:13
Since Audio Channels are fixed thing that always exists, the pointer to Audio Channel is the pointer to fixed, always present object. That pointer keeps working even after the clip stopped playing. So you may end up changing volume of another playback, by the way, if you keep using same pointer all the time.

But the manual says (under "Other Features | Music and Sound"):

Quotewhen you play the sound you set it like this:

longWindedSound = aExplosion.Play();

later on, elsewhere in the script, you can change the volume by doing:

if (longWindedSound != null)
{
  longWindedSound.Volume = 20;
}


Note the check for null here -- this makes sure that your game won't crash if the sound isn't playing (it might have finished, or not have been started yet).

This is a coding pattern I've always followed. But if the Play() command returns a pointer to an AudioChannel object that always exists, under what circumstances could it be null?

Crimson Wizard

Quote from: Snarky on Thu 24/11/2016 12:55:53
This is a coding pattern I've always followed. But if the Play() command returns a pointer to an AudioChannel object that always exists, under what circumstances could it be null?
Play returns null if no sound has started - therefore no audio channel was in use. The line "(it might have finished, or not have been started yet)" is clearly a mistake in the manual. Pointer variable is not self-managing, it cannot change itself when sound starts or stops; if script does not assign new value, it always keeps same value while variable exists.

Snarky

OK, thanks. That makes more sense. I thought maybe the properties internally referenced some other object that could be null, but it seemed like an odd way to do things.

I think that line in the manual is very misleading, then. It makes it sound like the AudioChannel object expires and the pointer somehow nulled out once the sound stops playing, which perhaps helps explain why so many people have a wrong mental model of how the system works.

SMF spam blocked by CleanTalk