This site requires JavaScript, please enable it in your browser!
Greenfoot back
lehrerfreund
lehrerfreund wrote ...

2022/7/22

Stop all GreenfootSounds at once?

lehrerfreund lehrerfreund

2022/7/22

#
Hi, is it possible to stop all GreenfootSounds that are playing at once? I am calling them from different contexts (partly static contexts) and I don't know which sounds are playing. I also have no access to the according GreenfootSound-variables. I want to realize this in the World-method, like
1
2
3
public void stopped() {
  // stop all sounds that are playing right now
}
tmhscs tmhscs

2022/7/22

#
One good way I've found of doing this is to make one class called "Music" that does not extend Actor or World that contains all of the GreenfootSounds you are playing. Assign them in the "Music" class constructor. Then you can make a method like you wrote as shown below to stop them all. Then, in your StartScreen World you could have its constructor run Music music = new Music(); and then have it do music.stopAllTracks(); music.track1.playLoop(); Other world constructors could also do that same thing, but play a different track on loop.
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Music {
  private static GreenfootSound track1, track2, track3;
  public Music() {
    track1 = new GreenfootSound("track1.mp3");
    track2 = new GreenfootSound("track2.mp3");
    track3 = new GreenfootSound("track3.mp3");
  }
  public static void stopAllTracks() {
    track1.stop();
    track2.stop();
    track3.stop();
  }
}
Lastly, if you had a ton of tracks, you would probably want an array or list of GreenfootSounds.
danpost danpost

2022/7/22

#
Better would be to set a static variable to the currently playing music and only play what the variable holds.
lehrerfreund lehrerfreund

2022/7/23

#
@tmhscs - Thanks, this should work, also in my case, see below! @danpost - Also thanks :-) I added all available sounds to a static array. Now I can iterate over this array and stop all sounds if running. Is this the direction you meant?
lehrerfreund lehrerfreund

2022/7/23

#
Ok - I found a (though not slender) way to handle this. I show the whole class below. If there is something that could be done better or more elegant, I would appreciate feedback very much!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import greenfoot.*;
import java.util.*;
 
/**
 * Handles Sounds. Usage:
 * - fill the array soundNames with Strings (+ number them in comments)(
 * - enter a new line in the constructor with the according filename
 *
 * Call from other classes
 * SoundHelper.playSound("EXPLOSION")
 * and
 * SoundHelper.stopSound("EXPLOSION");
 * also
 * SoundHelper.stopAllSounds();
 */
public class SoundHelper extends UIStuff
{
    private static boolean soundIsOn = true;
    private static String[] soundNames = {
            "EXPLOSION"     // 0
        ,   "SAD"           // 1
        ,   "BGMUSIK"       // 2
        ,   "ACK"           // 3
        };
    static HashMap<String, GreenfootSound> soundMap = new HashMap<String, GreenfootSound>();
 
    public SoundHelper() {
        soundMap.put(soundNames[0], this.generateGFSound("explosion.wav"));
        soundMap.put(soundNames[1], this.generateGFSound("sad_sound.mp3"));
        soundMap.put(soundNames[2], this.generateGFSound("background_music.mp3"));
        soundMap.put(soundNames[3], this.generateGFSound("ack.mp3"));
    }
 
    private GreenfootSound generateGFSound (String soundfilename)
    {
        return new GreenfootSound(soundfilename);
    }
 
    static void playSound(String soundName) {
        if(SoundHelper.checkIfSoundExists(soundName)) { // array contains sound
            if(!SoundHelper.soundMap.get(soundName).isPlaying()) {
                SoundHelper.soundMap.get(soundName).play();
            }
        }
    }
 
    static void stopSound(String soundName) {
        if(SoundHelper.checkIfSoundExists(soundName)) { // array contains sound
            if(SoundHelper.soundMap.get(soundName).isPlaying()) {
                SoundHelper.soundMap.get(soundName).stop();
            }
        }
    }
 
    static void stopAllSounds() {
        for(String oneSoundName : SoundHelper.soundNames) {
            SoundHelper.stopSound(oneSoundName);
        }
    }
    static boolean checkIfSoundExists(String soundName) {
        for(String oneSoundName : SoundHelper.soundNames) {
            if(oneSoundName == soundName) {
                return true;
            }
        }
        System.out.println("ERROR - Sound " + soundName + " was called but doesn't exist. Check class SoundHelper.");
 
        return false;
 
    }
 
    static void toggleSound(boolean soundIsOn) {
        SoundHelper.soundIsOn = soundIsOn;
    }
 
}
Spock47 Spock47

2022/7/23

#
I am not a big fan of static states - e.g. since they are difficult for testing and since they somehow rely on the fact that initialization of the classes and execution of the programs are done in one run, which can be problematic e.g. with the Greenfoot framework/UI. However, taking a static state class as given, here are some observations about the current version and improvements for the proposed Sound helper: 1. The current version is reliant on having one instance of SoundHelper created to initialize the sound map. It also relies on the condition that no other instance of SoundHelper is created - because in that case the already created sounds would be shadowed and not be accessible anymore. Furthermore, these preconditions are not mentioned in the documentation. 2. The boolean soundIsOn is unused (it can be toggled, but it currently does not have any functionality: "dead switch"). 3. The sound names are already stored in the soundMap. Having a second attribute soundNames invites inconsistencies. It should be removed. 4. The method "generateGFSound" should be static since it does not involve the instance at all. (below renamed to "create") 5. The soundMap is an internal implementation detail. It should be private. 6. The current implementation requires any user that wants to add a sound to change the SoundHelper class. That is not good (see open-closed principle of SOLID). Instead you can offer a "register" method that will put a given sound into your sound library. This method can then easily be called from anywhere: Sound.register("EXPLOSION", "explosion.wav") or even just Sound.register("explosion.wav"), if you don't need the additional sound name and want to register it directly with its file name. 6a. If the user asks to play a sound that is not yet registered, for convenience the method could automatically register it and immediately play it. 7. The check for an existing sound uses "==" for string comparison, but should use ".equals". However, there is already an existing method called "contains" for collections that does what you want. 7a. Since you use the check method only in places where you actually need the sound afterwards, it would be even more practicable to return GreenfootSound (and null if it does not exist). 8. Why does the class extend UIStuff? It does not seem to use anything from the superclass! Also, since it is a "static state class", it wouldn't make sense to have a superclass anyway. So, let's just remove the extend statement. 9. The class purpose is "Sound", so e.g. when calling playSound, it is already clear that it is about sounds. To have a more concise notation for the user, one could remove the redundancies, e.g. instead of "SoundHelper.playSound(fileName)", one could just call "Sound.play(fileName)" 10. The method "toggleSound" implies that the value of soundIsOn is toggled, but instead it has a parameter which is used to set the value of isOn. The comon name for this functionality would be "setSoundIsOn". The common functionality for a method called "toggleSound" is shown below. Here is a cleaned-up version that handles these comments and adds the improvements:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import greenfoot.*;
import java.util.HashMap;
import java.util.Map;
  
/**
 * Handles Sounds. Usage:
 *
 * Call from other classes
 *     Sound.play("explosion.wav")
 * and
 *     Sound.stop("explosion.wav");
 * or
 *     Sound.register("ACK", "ack.mp3");
 *     Sound.play("ACK");
 *     Sound.stop("ACK");
 * also
 *     Sound.stopAll();
 * also
 *     Sound.initializeDefaultSounds();
 *     Sound.play("SAD")
 */
public enum Sound
{
    ; // entry-less enum to prevent creation of any instances of static state class.
 
    private static boolean isOn = true; // TODO: Add functionality!
    private static final Map<String, GreenfootSound> sounds = new HashMap<>();
 
    public static void initializeDefaultSounds() { // not necessary
        register("EXPLOSION", "explosion.wav");
        register("SAD", "sad_sound.mp3");
        register("BGMUSIK", "background_music.mp3");
        register("ACK", "ack.mp3");
    }
  
    public static void register(final String fileName) {
        register(fileName, fileName);
    }
  
    public static void register(final String soundName, final String fileName) {
        final GreenfootSound alreadyRegisteredSound = sounds.get(soundName);
        if (alreadyRegisteredSound != null) {
            alreadyRegisteredSound.stop(); // make sure that old sound is stopped before replacing it.
        }
        sounds.put(soundName, create(fileName));
    }
  
    private static GreenfootSound create(final String fileName) {
        return new GreenfootSound(fileName);
    }
  
    public static void play(final String soundName) {
        final GreenfootSound sound = sounds.get(soundName);
        if (sound == null) { // register, if not yet existing.
            register(soundName);
        }
        if (!sound.isPlaying()) {
            sound.play();
        }
    }
  
    public static void stop(final String soundName) {
        final GreenfootSound sound = get(soundName);
        if (sound != null) {
            sound.stop();
        }
    }
  
    public static void stopAll() {
        for(final GreenfootSound sound : sounds.values()) {
            sound.stop();
        }
    }
     
    private static GreenfootSound get(final String soundName) {
        final GreenfootSound result = sounds.get(soundName);
        if (result == null) {
            System.out.println("ERROR - Sound " + soundName + " was called but doesn't exist. Check class SoundHelper.");
        }
        return result;
    }
  
    public static void toggleIsOn() {
        isOn = !isOn;
    }
  
}
lehrerfreund lehrerfreund

2022/7/24

#
Wow, Spock47, thank you so much for your indepth analysis and the improved code - this is very helpful! Your implementation is much more professional than mine, I bow to my knees :-) There is one flaw: If a not registered sound is called like
1
Sound.play("SADD"); // instead of SAD
a FileNotFoundException is thrown by the create-method. I fixed this by implementing a try-catch-block in the play-method:
1
2
3
4
5
6
7
8
9
10
11
12
13
public static void play(final String soundName) {
    final GreenfootSound sound = sounds.get(soundName);
    try {
 
        if (sound == null) { // register, if not yet existing.
            register(soundName);
        }
        if (!sound.isPlaying()) {
            sound.play();
        }
    } catch(Exception e) {
        System.out.println("Not existing sound has been called - not executed");           }
}
Spock47 Spock47

2022/7/25

#
lehrerfreund wrote...
There is one flaw: If a not registered sound is called (...) a FileNotFoundException is thrown by the create-method.
Good catch! :)
You need to login to post a reply.