Question regarding music and sounds

Hi guys! Today I need help to understand something.  

Managing audio and music has been a difficult task to manage and understand. I have managed to do it but I think my method is very long and there must be some way to simplify things. I would like some opinions and help.

In my external dada module I code this:

local M M.soundTable = {    menuMusic = audio.loadSound("sounds/menu\_music.wav"),    gameMusic = audio.loadSound("sounds/game\_music.wav"),    fx1 = audio.loadSound("sounds/bounce.wav"),    fx2 = audio.loadSound("sounds/bounce\_2.wav"),    fx3 = audio.loadSound("sounds/bounce\_3.wav"),    fx4 = audio.loadSound("sounds/win.wav"),    fx5 = audio.loadSound("sounds/lose.wav"),    fx6 = audio.loadSound("sounds/return.wav"), } --volume up M.volumeUp = function()   audio.setVolume( 0.4, {channel=3} );audio.setVolume( 0.4, {channel=4} )   audio.setVolume( 0.4, {channel=5} );audio.setVolume( 0.4, {channel=6} )   audio.setVolume( 0.4, {channel=7} );audio.setVolume( 0.4, {channel=8} )   audio.setVolume( 0.4, {channel=9} );audio.setVolume( 0.4, {channel=10} )   audio.setVolume( 0.4, {channel=11} );audio.setVolume( 0.4, {channel=12} )   audio.setVolume( 0.4, {channel=13} );audio.setVolume( 0.4, {channel=14} )   audio.setVolume( 0.4, {channel=15} );audio.setVolume( 0.4, {channel=16} )   audio.setVolume( 0.4, {channel=17} );audio.setVolume( 0.4, {channel=18} )   audio.setVolume( 0.4, {channel=19} );audio.setVolume( 0.4, {channel=20} )   audio.setVolume( 0.4, {channel=21} );audio.setVolume( 0.4, {channel=22} )   audio.setVolume( 0.4, {channel=23} );audio.setVolume( 0.4, {channel=24} )   audio.setVolume( 0.4, {channel=25} );audio.setVolume( 0.4, {channel=26} )   audio.setVolume( 0.4, {channel=27} );audio.setVolume( 0.4, {channel=28} )   audio.setVolume( 0.4, {channel=29} );audio.setVolume( 0.4, {channel=30} )   audio.setVolume( 0.4, {channel=31} );audio.setVolume( 0.4, {channel=32} ) end --volume down M.volumeDown = function()   audio.setVolume( 0, {channel=3} );audio.setVolume( 0, {channel=4} )   audio.setVolume( 0, {channel=5} );audio.setVolume( 0, {channel=6} )   audio.setVolume( 0, {channel=7} );audio.setVolume( 0, {channel=8} )   audio.setVolume( 0, {channel=9} );audio.setVolume( 0, {channel=10} )   audio.setVolume( 0, {channel=11} );audio.setVolume( 0, {channel=12} )   audio.setVolume( 0, {channel=13} );audio.setVolume( 0, {channel=14} )   audio.setVolume( 0, {channel=15} );audio.setVolume( 0, {channel=16} )   audio.setVolume( 0, {channel=17} );audio.setVolume( 0, {channel=18} )   audio.setVolume( 0, {channel=19} );audio.setVolume( 0, {channel=20} )   audio.setVolume( 0, {channel=21} );audio.setVolume( 0, {channel=22} )   audio.setVolume( 0, {channel=23} );audio.setVolume( 0, {channel=24} )   audio.setVolume( 0, {channel=25} );audio.setVolume( 0, {channel=26} )   audio.setVolume( 0, {channel=27} );audio.setVolume( 0, {channel=28} )   audio.setVolume( 0, {channel=29} );audio.setVolume( 0, {channel=30} )   audio.setVolume( 0, {channel=31} );audio.setVolume( 0, {channel=32} ) end return M

I reserve { channel=1} and {channel=2} in main.lua for menu music and game music respectively, and set channels volume to 0.4.

Because my game is a test and I realized that I have a lot to understand before make money programming games, I decided to use it to learn.

I do not want to use a scene for the settings because I’m just going to mute the music and the sounds. so I decide to make the sound function in menu scene.

and in menu scene I use this functions

--manage music local function manageMusic( event )   if ( event.phase == "ended" ) then     if audio.getVolume( { channel=1 } ) == 0 and       audio.getVolume( { channel=2 } ) == 0 then        audio.setVolume( 0.4, { channel=1 } )        audio.setVolume( 0.4, { channel=2 } )        musicBtn.fill = loads.musicOn        --save music state to JSON       loads.musicStatus = "on"       loads.updateGameProgress()       print("Music is == ", loads.musicStatus )     else       audio.setVolume( 0, { channel=1 } )       audio.setVolume( 0, { channel=2 } )       musicBtn.fill = loads.musicOff       --save music state to JSON       loads.musicStatus = "off"       loads.updateGameProgress()       print("Music is == ", loads.musicStatus )    end  end  return true end --manage sound local function manageSound( event )   if ( event.phase == "ended" ) then    if audio.getVolume( { channel=3 } ) == 0 and       audio.getVolume( { channel=4 } ) == 0 and      audio.getVolume( { channel=5 } ) == 0 and      audio.getVolume( { channel=6 } ) == 0 and      audio.getVolume( { channel=7 } ) == 0 and      audio.getVolume( { channel=8 } ) == 0 and      audio.getVolume( { channel=9 } ) == 0 and      audio.getVolume( { channel=10 } ) == 0 and      audio.getVolume( { channel=11 } ) == 0 and      audio.getVolume( { channel=12 } ) == 0 and      audio.getVolume( { channel=13 } ) == 0 and      audio.getVolume( { channel=14 } ) == 0 and      audio.getVolume( { channel=15 } ) == 0 and --------TOO MUCH AND!!!      audio.getVolume( { channel=16 } ) == 0 and      audio.getVolume( { channel=17 } ) == 0 and      audio.getVolume( { channel=18 } ) == 0 and      audio.getVolume( { channel=19 } ) == 0 and      audio.getVolume( { channel=20 } ) == 0 and      audio.getVolume( { channel=21 } ) == 0 and      audio.getVolume( { channel=22 } ) == 0 and      audio.getVolume( { channel=23 } ) == 0 and      audio.getVolume( { channel=24 } ) == 0 and      audio.getVolume( { channel=25 } ) == 0 and      audio.getVolume( { channel=26 } ) == 0 and      audio.getVolume( { channel=27 } ) == 0 and      audio.getVolume( { channel=28 } ) == 0 and      audio.getVolume( { channel=29 } ) == 0 and      audio.getVolume( { channel=30 } ) == 0 and      audio.getVolume( { channel=31 } ) == 0 and      audio.getVolume( { channel=32 } ) == 0 then       loads.volumeUp()       soundBtn.fill = loads.soundOn      --save music state to JSON       loads.soundStatus = "on"       loads.updateGameProgress()       print("Music is == ", loads.soundStatus )    else        loads.volumeDown()        soundBtn.fill = loads.soundOff      --save music state to JSON      loads.soundStatus = "off"      loads.updateGameProgress()      print("Music is == ", loads.soundStatus )    end   end   return true end

It’s working but there is a way to use a sound channel table instead of call them manually?

Opinion

You are in fact working too hard.  However, this is a natural first starting point.  So, you’re doing what we all did originally when learning about sound support and similar resource management problems.

Instead of making a bunch of static tables and choices, use a modular function-based approach instead.

For most games, this approach is easiest and works fine:

  • Create a module (soundMgr) with these functions:
    • loadEffect( name, path ) - Loads sound files for use as sound effects.  There are loaded with audio.load().
    • loadMusic( name, path ) - Loads sound files for use as music and soundtracks. There are loaded with audio.loadStream().
    • playSound( name, options ) - Looks up a previously loaded sound, and plays it.  Applies options if provided.
    • stopSound( name ) - Stops a playing sound.
  • Have initialization code in one place that uses the module to load all sounds in advance.
  • Require the module as needed in your game an interface modules to make sounds.

You can further modify this module then to add features for:

  • Reserving channel ranges for sound categories (music or effect).
  • Set volume for an entire category.
  • Set volume for a single sound (I’d pass this in options during the play() call).

Help?

First, SSK already has a solution for this.  https://roaminggamer.github.io/RGDocs/pages/SSK2/libraries/soundMgr/

However, that may be more complicated than you are willing to learn, or maybe not suitable to your needs.

Second, This is a wonderful kind of thing to make a side-project where you can can code, re-code, … until you really have a solid handle on the idea.

Third, I’d be willing to code up a starter module demoing some of these concepts so you can expand from there.

Ping back your thoughts.

@roaminggamer

The most I want is to be able to understand well how to do this task. Actually I have confusion in the external functions, for example, I do not know what the (parameter, parameter) is used within each () of the function. I’m reading a lot but I have doubts. If you want to give me a “code hint” to have a base to start from, thank you in advance. 

I’ll make up something as an example for you and the community.

Random Question: “Do you use composer.*”?  for your game interface management?

Yes, everything I know about programming I have learned from forum users like you and the Corona docs. I use composer because I understand very well how it works and it helps me put my ideas into practice more quickly.

Here is a set of examples:

https://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2018/10/basicSoundManager.zip

All versions have the same sound manager module found in scripts/soundMgr.lua

1_simplest - Simple example showing how to use sound manager in a single file (main.lua).

2_better - Initialization done in scripts/init.lua.  Also uses ‘post()’ call to trigger sound effect/music.

3_composer - Full composer framework, OOP button instead of simple buttons from prior examples. 

Missing Stuff

  • No easy way to change volume of currently playing sounds.
  • No flood control.  Flooding is when an effect sound is played too frequently, as can happen in action games.
  • Sound stopping by sound name.
  • … 

https://www.youtube.com/watch?v=7r3RSQdKt4Y&feature=youtu.be

I leave it to you to dig into this and ask questions as needed.

Cheer,

Ed

To put things in perspective, with this manager your code would look more like this:

local soundMgr = require "scripts.soundMgr" soundMgr.addMusic( "menu", "sounds/menu\_music.wav") soundMgr.addMusic( "game", "sounds/game\_music.wav") soundMgr.addEffect( "bounce1", "sounds/bounce.wav") soundMgr.addEffect( "bounce2", "sounds/bounce2.wav") soundMgr.addEffect( "bounce3", "sounds/bounce3.wav") soundMgr.addEffect( "win", "sounds/win.wav") soundMgr.addEffect( "lose", "sounds/lose.wav") soundMgr.addEffect( "return", "sounds/return.wav") soundMgr.setVolume( "effect", 0.4 )

Remember, the sound manager I provided cannot change the volume of sounds that are currently playing.  This is not hard to add, but I didn’t feel like doing it because:

  1. It would make it a little harder to understand.

  2. It would take more time and I wanted to limit the effort for myself.

Tip: The trick to doing this is to:

  1. Track what sound are playing in a list/table.

  2. When the sounds stop playing / end take them out of the list (think… onComplete() )

  3. When the setVolume() call comes in, iterate over the list of playing sounds and change their volume accordingly.

“killer code machine” has spoken. Right now I’m going to download and study as if I were in college.  

thanks @roaminggamer…. :slight_smile:

I realise Ed has already helped with your main issue, but I noticed that in your examples you have lots of repeating pieces of code (you even have a comment saying “TOO MUCH AND”) and thought I would make a suggestion that should help yourself (and others) in future.

As a general rule, if you are writing the same line of code over and over you can usually make things a bit tidier by using a “for loop”.

Instead of writing  audio.setVolume( 0.4, {channel=3} ) for channel 3 and then channel 4, 5, 6… you can write a small loop that will start at one number and repeat until it reaches a final number:

for i = 3, 32 do audio.setVolume( 0.4, {channel=i} ) end

(I’ll explain further but if you don’t use loops regularly, I’d recommend looking up the syntax: https://www.lua.org/pil/4.3.4.html )
 

Using loops has a number of advantages. Most obviously, it’s much less code to write in the first place - I just used three lines where the original code used thirty. Secondly, it’s much easier to make a change to a single line than it is to 30 lines. If you decide that you actually want the default volume to be 0.45 instead of 0.4 you only need to change it in one place. That cuts down on time, and on the potential for mistakes to creep in.

For your second example where you have lots of “and” :
 

if audio.getVolume( { channel=3 } ) == 0 and audio.getVolume( { channel=4 } ) == 0 and ...etc

it’s maybe not so obvious to a beginner how to get the same result with a loop. I find it easy to think of the problem in reverse. 
Rather than “are all of these things true” I work out “is any single one of these things false”. If one is false, it does not matter how many of the others are true or false, because your code wants them all to be true when deciding if you trigger volumeUp(). 
 

One way to check this is in a loop is to create a holding variable which is set to true:

local allVolumesAreZero = true

Note that this variable being true at the start is just an assumption until we actually perform the check.

Then we loop through the channel numbers checking the current volume - if any of them are more than 0 then we set our boolean variable to false:

for i = 3, 32 do if audio.getVolume( { channel=i } ) \> 0 then allVolumesAreZero = false break end end

Notice that I have also added the keyword “break”. This stops the loop from continuing. I only want to know if any volume is more than zero, and since I found one I don’t need to waste time checking the rest.

After the loop I just check the state of allVolumesAreZero, and then call whichever functions are needed. Here is the whole process:
 

local allVolumesAreZero = true for i = 3, 32 do if audio.getVolume( { channel=i } ) \> 0 then allVolumesAreZero = false break end end if allVolumesAreZero then loads.volumeUp() else loads.volumeDown() end

Hopefully that is useful for yourself or newcomers, if you already knew this then just consider it a refresher  :slight_smile:

Thank you @Alan PlantPot

Opinion

You are in fact working too hard.  However, this is a natural first starting point.  So, you’re doing what we all did originally when learning about sound support and similar resource management problems.

Instead of making a bunch of static tables and choices, use a modular function-based approach instead.

For most games, this approach is easiest and works fine:

  • Create a module (soundMgr) with these functions:
    • loadEffect( name, path ) - Loads sound files for use as sound effects.  There are loaded with audio.load().
    • loadMusic( name, path ) - Loads sound files for use as music and soundtracks. There are loaded with audio.loadStream().
    • playSound( name, options ) - Looks up a previously loaded sound, and plays it.  Applies options if provided.
    • stopSound( name ) - Stops a playing sound.
  • Have initialization code in one place that uses the module to load all sounds in advance.
  • Require the module as needed in your game an interface modules to make sounds.

You can further modify this module then to add features for:

  • Reserving channel ranges for sound categories (music or effect).
  • Set volume for an entire category.
  • Set volume for a single sound (I’d pass this in options during the play() call).

Help?

First, SSK already has a solution for this.  https://roaminggamer.github.io/RGDocs/pages/SSK2/libraries/soundMgr/

However, that may be more complicated than you are willing to learn, or maybe not suitable to your needs.

Second, This is a wonderful kind of thing to make a side-project where you can can code, re-code, … until you really have a solid handle on the idea.

Third, I’d be willing to code up a starter module demoing some of these concepts so you can expand from there.

Ping back your thoughts.

@roaminggamer

The most I want is to be able to understand well how to do this task. Actually I have confusion in the external functions, for example, I do not know what the (parameter, parameter) is used within each () of the function. I’m reading a lot but I have doubts. If you want to give me a “code hint” to have a base to start from, thank you in advance. 

I’ll make up something as an example for you and the community.

Random Question: “Do you use composer.*”?  for your game interface management?

Yes, everything I know about programming I have learned from forum users like you and the Corona docs. I use composer because I understand very well how it works and it helps me put my ideas into practice more quickly.

Here is a set of examples:

https://github.com/roaminggamer/RG_FreeStuff/raw/master/AskEd/2018/10/basicSoundManager.zip

All versions have the same sound manager module found in scripts/soundMgr.lua

1_simplest - Simple example showing how to use sound manager in a single file (main.lua).

2_better - Initialization done in scripts/init.lua.  Also uses ‘post()’ call to trigger sound effect/music.

3_composer - Full composer framework, OOP button instead of simple buttons from prior examples. 

Missing Stuff

  • No easy way to change volume of currently playing sounds.
  • No flood control.  Flooding is when an effect sound is played too frequently, as can happen in action games.
  • Sound stopping by sound name.
  • … 

https://www.youtube.com/watch?v=7r3RSQdKt4Y&feature=youtu.be

I leave it to you to dig into this and ask questions as needed.

Cheer,

Ed

To put things in perspective, with this manager your code would look more like this:

local soundMgr = require "scripts.soundMgr" soundMgr.addMusic( "menu", "sounds/menu\_music.wav") soundMgr.addMusic( "game", "sounds/game\_music.wav") soundMgr.addEffect( "bounce1", "sounds/bounce.wav") soundMgr.addEffect( "bounce2", "sounds/bounce2.wav") soundMgr.addEffect( "bounce3", "sounds/bounce3.wav") soundMgr.addEffect( "win", "sounds/win.wav") soundMgr.addEffect( "lose", "sounds/lose.wav") soundMgr.addEffect( "return", "sounds/return.wav") soundMgr.setVolume( "effect", 0.4 )

Remember, the sound manager I provided cannot change the volume of sounds that are currently playing.  This is not hard to add, but I didn’t feel like doing it because:

  1. It would make it a little harder to understand.

  2. It would take more time and I wanted to limit the effort for myself.

Tip: The trick to doing this is to:

  1. Track what sound are playing in a list/table.

  2. When the sounds stop playing / end take them out of the list (think… onComplete() )

  3. When the setVolume() call comes in, iterate over the list of playing sounds and change their volume accordingly.

“killer code machine” has spoken. Right now I’m going to download and study as if I were in college.  

thanks @roaminggamer…. :slight_smile:

I realise Ed has already helped with your main issue, but I noticed that in your examples you have lots of repeating pieces of code (you even have a comment saying “TOO MUCH AND”) and thought I would make a suggestion that should help yourself (and others) in future.

As a general rule, if you are writing the same line of code over and over you can usually make things a bit tidier by using a “for loop”.

Instead of writing  audio.setVolume( 0.4, {channel=3} ) for channel 3 and then channel 4, 5, 6… you can write a small loop that will start at one number and repeat until it reaches a final number:

for i = 3, 32 do audio.setVolume( 0.4, {channel=i} ) end

(I’ll explain further but if you don’t use loops regularly, I’d recommend looking up the syntax: https://www.lua.org/pil/4.3.4.html )
 

Using loops has a number of advantages. Most obviously, it’s much less code to write in the first place - I just used three lines where the original code used thirty. Secondly, it’s much easier to make a change to a single line than it is to 30 lines. If you decide that you actually want the default volume to be 0.45 instead of 0.4 you only need to change it in one place. That cuts down on time, and on the potential for mistakes to creep in.

For your second example where you have lots of “and” :
 

if audio.getVolume( { channel=3 } ) == 0 and audio.getVolume( { channel=4 } ) == 0 and ...etc

it’s maybe not so obvious to a beginner how to get the same result with a loop. I find it easy to think of the problem in reverse. 
Rather than “are all of these things true” I work out “is any single one of these things false”. If one is false, it does not matter how many of the others are true or false, because your code wants them all to be true when deciding if you trigger volumeUp(). 
 

One way to check this is in a loop is to create a holding variable which is set to true:

local allVolumesAreZero = true

Note that this variable being true at the start is just an assumption until we actually perform the check.

Then we loop through the channel numbers checking the current volume - if any of them are more than 0 then we set our boolean variable to false:

for i = 3, 32 do if audio.getVolume( { channel=i } ) \> 0 then allVolumesAreZero = false break end end

Notice that I have also added the keyword “break”. This stops the loop from continuing. I only want to know if any volume is more than zero, and since I found one I don’t need to waste time checking the rest.

After the loop I just check the state of allVolumesAreZero, and then call whichever functions are needed. Here is the whole process:
 

local allVolumesAreZero = true for i = 3, 32 do if audio.getVolume( { channel=i } ) \> 0 then allVolumesAreZero = false break end end if allVolumesAreZero then loads.volumeUp() else loads.volumeDown() end

Hopefully that is useful for yourself or newcomers, if you already knew this then just consider it a refresher  :slight_smile:

Thank you @Alan PlantPot