Categories

Sound Wrapper Control (SoundEntity)

When it’s time to play sound effect enhancing your game’s feeling, you need some flexible control over playback of the sound itself.

Then it come to SoundEntity with this post. Imagine when you start your game with the welcome mainmenu screen with only 2 UI Buttons available for users to click on which are “Play”, and “Quit”. Also you want some kind of special setup, when the mouse cursor is on the button, you want to play a “MouseOver” sound only one time. And also when users click the mouse, you want to play a “MouseClicked” sound as well. But apart from those two situations, when the mouse is not on those two buttons, you didn’t play any sound thus just stop any currently in playing state of those sounds.

See the concept?
Yep, you can do just like that with the simple code even not bother creating another class to do the job, but the code will be surely mess and dirty as you need to manage and handle all those dirty variables along the line, and they will be left in the game logic space unnecessary !

So by creating a sound wrapper control class we could do the trick. I named it “SoundEntity” (as we all have Entity, Entity2D, or Entity3D, why can’t be any SoundEntity right?

Let me point out first before we go down the line. The approach used here is not touch with XACT, just plain SoundEffect class. It’s more convenience as you don’t need much more special stuff, just load and play it with some optionals properties to set prior to playing -> volume, pitch, pan, and looping. With those setting, we are just enough in happy mood!!

The code below is from my Wekarusung Engine’s SoundEntity class. Let’s see the code below.

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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
//-----------------------------------------------------------------------------
// SoundEntity.cs
// Wekarusung Engine
//
// Description: Represents the single sound control, and playback.
// 
// Programmer: Haxpor (haxpor@gmail.com)
//-----------------------------------------------------------------------------
 
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;
using Wekarusung.Framework;
using Wekarusung.Framework.Interface;
 
namespace Wekarusung.Framework.Sound
{
    /// <summary>
    /// Represents the single sound control, and playback.
    /// </summary>
    public class SoundEntity
    {
        /// <summary>
        /// Sound effect.
        /// </summary>
        private SoundEffect sound;
        private SoundEffectInstance instance;
 
        private bool isLoop = false;
        private float volume = 1.0f;
        private float pitch = 0.0f;
        private float pan = 0.0f;
 
        // application-related variables
        private bool markPlayed = false;
 
        /// <summary>
        /// Get whether this sound entity is playing.
        /// </summary>
        public bool IsPlaying
        {
            get
            {
                if (instance != null)
                    return instance.State == SoundState.Playing ? true : false;
                else
                    return false;
            }
        }
 
        /// <summary>
        /// Get whether this sound entity is stopped.
        /// </summary>
        /// <remarks>If the sound is not played yet, this property also returns true.</remarks>
        public bool IsStopped
        {
            get
            {
                if (instance != null)
                    return instance.State == SoundState.Stopped ? true : false;
                else if (GetState() == null)
                    return true;
                else
                    return false;
            }
        }
 
        /// <summary>
        /// Get whether this sound entity is paused.
        /// </summary>
        public bool IsPaused
        {
            get
            {
                if (instance != null)
                    return instance.State == SoundState.Paused ? true : false;
                else
                    return false;
            }
        }
 
        /// <summary>
        /// Get or set the loop play for this sound effect.
        /// </summary>
        public bool IsLoop
        {
            get { return isLoop; }
            set { isLoop = value; }
        }
 
        /// <summary>
        /// Get or set volume of this sound entity.
        /// </summary>
        /// <remarks>The value can range from 0.0f (silence) to 1.0f (full volume)(</remarks>
        public float Volume
        {
            get { return volume; }
            set { volume = value; }
        }
 
        /// <summary>
        /// Get or set the picth of this sound entity.
        /// </summary>
        public float Pitch
        {
            get { return pitch; }
            set { pitch = value; }
        }
 
        /// <summary>
        /// Get or set pan of this sound entity.
        /// </summary>
        /// <remarks>The value can range from -1 (full left) to 1 (full right).</remarks>
        public float Pan
        {
            get { return pan; }
            set { pan = value; }
        }
 
        /// <summary>
        /// Gets whether this sound entity is marked as played.
        /// </summary>
        public bool IsMarkedAsPlayed
        {
            get { return markPlayed; }
        }
 
        /// <summary>
        /// Gets whether this sound entity is marked as stopped.
        /// </summary>
        public bool IsMarkedAsStopped
        {
            get { return !markPlayed; }
        }
 
        /// <summary>
        /// Create a sound entity.
        /// </summary>
        /// <param name="path">Path of the sound file</param>
        public SoundEntity(string path)
        {
            sound = Engine.Content.Load<SoundEffect>(path);
            instance = null;
        }
 
        /// <summary>
        /// Release the resource used by this sound entity.
        /// </summary>
        public void Dispose()
        {
            sound.Dispose();
            instance = null;
        }
 
        /// <summary>
        /// Play this sound entity from the specification of the setting.
        /// </summary>
        public void Play()
        {
            instance = sound.Play(volume, pitch, pan, isLoop);
        }
 
        /// <summary>
        /// Play this sound entity from the specification of the setting but in loop.
        /// </summary>
        public void PlayLoop()
        {
            instance = sound.Play(volume, pitch, pan, true);
        }
 
        /// <summary>
        /// Resume playing this sound entity.
        /// </summary>
        public void Resume()
        {
            if (instance != null && instance.State == SoundState.Paused)
            {
                instance.Resume();
            }
        }
 
        /// <summary>
        /// Stop this sound entity.
        /// </summary>
        public void Stop()
        {
            if (instance != null)
            {
                instance.Stop();
            }
        }
 
        /// <summary>
        /// Pause this sound entity.
        /// </summary>
        public void Pause()
        {
            if (instance != null && instance.State == SoundState.Playing)
            {
                instance.Pause();
            }
        }
 
        /// <summary>
        /// Get the current state of the sound.
        /// </summary>
        /// <returns>SoundState if its instace is not null, otherwise returns null.</returns>
        public SoundState? GetState()
        {
            if (instance != null)
                return instance.State;
            else
                return null;
        }
 
        /// <summary>
        /// Mark this sound as it's played.
        /// </summary>
        /// <remarks>Users must manually handle and call this method in order to apply playing this sound entity for one time in particular
        /// circumstance.</remarks>
        public void MarkAsPlayed()
        {
            markPlayed = true;
        }
 
        /// <summary>
        /// Mark this sound as it's stopped.
        /// </summary>
        /// <remarks>Users must manually handle and call this method in order to apply playing this sound entity for one time in particular
        /// circumstance.</remarks>
        public void MarkAsStopped()
        {
            markPlayed = false;
        }
    }
}

From the code, when we play sound with SoundEffect class, we would call Play() method. It will return the SoundEffectInstance, this instance you will be working with to track the sound’s current state and do playback. Please note that if you decide to play the sound again immediately after playing it for the first time, you are risk to mess the quality of sound, and mess with the tracking state of the sound itself. So from the game programming point of view, that bit of working must be ensured and done by programmers, they should know what they are doing. If doing just like that, keep in mind that the sound should be played as 1 round, so if we just play it immediately for the second time, the first play is not concerned anymore as we know it will be stopped in a predictable time.

Now let’s see how we can put this SoundEntity into real use.
We need to create an instance of the SoundEntity first by the following code.

SoundEntity explosive = new SoundEntity("my/sound/folder/explosive");

Above line of code will create an instance of SoundEntity with explosive .wav file indicated to use.
In addition, you can even preset its properties, see the example below.

explosive.IsLoop = true;
explosive.Volume = 0.80f;
explosive.Pan = -0.50f;

The above setting up tells us that explosive sound will played as loop, with 80% of volume, and play bias to the left of the speaker. Yep, we could do something special with this SoundEffect class wrapped by SoundEntity.

When it’s time to play, then just invoke.

explosive.Play();

Or you could see the definition from the class and call PlayLoop() to play it as loop, this could pose some real useful method here, as some sounds may be regarded as a single play, but in some case that we just want to play it as loop so we could use PlayLoop() method.

When you don’t want to use that sound anymore, then just call.

explosive.Dispose();

This will release the resource used by that sound.

Okay, just back to our initial purpose, the mainmenu case.
See the code below for doing that kind of thing.

            ....
            // play sound effect on button
            if (Engine.MouseManager.IsOnSight((Entity2D)play))
            {
                if (Engine.MouseManager.LeftClicked)
                {
                    if (SM.MouseClicked.IsStopped)
                    {
                        SM.MouseClicked.Play();
                    }
                }
                else
                {
                    if (SM.MouseOver.IsStopped && SM.MouseOver.IsMarkedAsStopped)
                    {
                        SM.MouseOver.Play();
                        SM.MouseOver.MarkAsPlayed();
                    }
                }
            }
            else if (Engine.MouseManager.IsOnSight((Entity2D)quit))
            {
                if (Engine.MouseManager.LeftClicked)
                {
                    if (SM.MouseClicked.IsStopped)
                    {
                        SM.MouseClicked.Play();
                    }
                }
                else
                {
                    if (SM.MouseOver.IsStopped && SM.MouseOver.IsMarkedAsStopped)
                    {
                        SM.MouseOver.Play();
                        SM.MouseOver.MarkAsPlayed();
                    }
                }
            }
            else
            {
                //clear state of both sound effects
                SM.MouseOver.Stop();
                SM.MouseOver.MarkAsStopped();
                SM.MouseClicked.Stop();
            }
            ...

The above code I extracted it from my demo game “Junk Master: The Journey To Junk Lord”. But it could be easily understand with the sense of pseudocode.
From the code, MarkAsPlayed(), and MarkAsStopped() method will come in handy as they’re used in the application layer. They are used to check and control playing the sound only once in certain circumstance. We only need those methods in the case when the state-changing is immediate, and not be well separated, I am sure by just using it for a few time, you will realize when or not when to use it.

And that’s it for the sound stuff for this post.

The sound’s playback won’t be mess anymore, thanks to SoundEntity. (be sure to change the namespace to match with your project first, and cut out some reference at the top of the code also :)

Download the SoundEntity class.
Til next time.

Haxpor

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">