Jump to content

[TOPIC: topicViewTemplate]
[GLOBAL: userSmallPhoto]
Photo

Question regarding music and sounds
Started by dodi_games Oct 07 2018 10:33 AM

9 replies to this topic

Best Answer Alan PlantPot , 09 October 2018 - 02:58 AM

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  :)

[TOPIC CONTROLS]
[/TOPIC CONTROLS]
[modOptionsDropdown]
[/modOptionsDropdown]
[reputationFilter]
[TOPIC: post.html]
#1

dodi_games

[GLOBAL: userInfoPane.html]
dodi_games
  • Contributor

  • 289 posts
  • Corona SDK

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?

 

 

 



[TOPIC: post.html]
#2

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,093 posts
  • Corona SDK

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.


Edited by roaminggamer, 07 October 2018 - 10:58 AM.

  • ldurniat and dodi_games like this

[TOPIC: post.html]
#3

dodi_games

[GLOBAL: userInfoPane.html]
dodi_games
  • Contributor

  • 289 posts
  • Corona SDK

@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. 



[TOPIC: post.html]
#4

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,093 posts
  • Corona SDK

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

 

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



[TOPIC: post.html]
#5

dodi_games

[GLOBAL: userInfoPane.html]
dodi_games
  • Contributor

  • 289 posts
  • Corona SDK

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.



[TOPIC: post.html]
#6

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,093 posts
  • Corona SDK

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.
  • ... 

 

 

 

 

 

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

 

Cheer,

Ed


Edited by roaminggamer, 07 October 2018 - 04:44 PM.

  • dodi_games likes this

[TOPIC: post.html]
#7

roaminggamer

[GLOBAL: userInfoPane.html]
roaminggamer
  • Corona Geek

  • 7,093 posts
  • Corona SDK

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.


  • dodi_games likes this

[TOPIC: post.html]
#8

dodi_games

[GLOBAL: userInfoPane.html]
dodi_games
  • Contributor

  • 289 posts
  • Corona SDK

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

thanks @roaminggamer…. :)



[TOPIC: post.html]
#9

Alan PlantPot

[GLOBAL: userInfoPane.html]
Alan PlantPot
  • Contributor

  • 874 posts
  • Corona SDK

  Best Answer

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  :)


  • SGS and dodi_games like this

[TOPIC: post.html]
#10

dodi_games

[GLOBAL: userInfoPane.html]
dodi_games
  • Contributor

  • 289 posts
  • Corona SDK

Thank you @Alan PlantPot




[topic_controls]
[/topic_controls]