|Version 4.28 of MUSHclient introduces somewhat better handling of playing sounds, in particular multiple simultaneous sounds.|
This leverages off the DirectSound technology which is part of DirectX. This uses a couple of DirectX features to work, so it may not work if you don't have DirectX installed (most recent PCs do).
However as these are new script calls, the old sounds should still work.
The basic features are:
- Up to 10 simultaneous sounds (channels), per world, can be played. Windows mixes them together to give an aggregate sound out of your speakers.
- Each channel can play at a different volume level. (eg. quiet background ambient sounds)
- Individual channels can loop if desired (eg. for background music, or ambient sounds).
- Each channel can be "panned" so that the sound comes out of the left speaker, the right speaker, or both. This might be useful if you see something like "there is a market off to your left", so you could play ambient "market" sounds out of the left speaker.
- Sounds can be reconfigured whilst playing, for example to make them softer or louder.
- Sounds can be cancelled at any time.
- You can query if a sound is still playing or not. You might set up a timer to check if the music channel has finished playing, and if so, play something else.
The fundamental feature you need to know about to use this, is the concept of channels or buffers. Each sound is played from its own buffer (which is loaded up from a .wav file). You have 10 buffers to work with, numbered 1 to 10.
If designing sounds for use with a MUD you would need to assign various channels for various purposes. For example:
- Temporary battle sounds (like swords clashing)
- Ambient sound loops (like forest sounds, or town sounds)
- Background music loops
By assigning a buffer or channel to each type of sound like this, you can change the music by playing a new sound in channel 3, if that was the one you designated for music.
PlaySound (1, "swordhit.wav")
PlaySound (2, "forest.wav", true, -6) -- loop forest sounds, -6 db volume
PlaySound (3, "calm_music.wav", true, -4) -- loop music, -4 db volume
Now if you got into a battle you could simply replace the music with battle music:
PlaySound (3, "battle_music.wav", true, -3) -- loop music, -3 db volume
The other 2 channels will continue playing what they were before.
To make life easier, you can use channel 0, which means "take the first available channel". This is intended to let MUSHclient choose a free channel (one that is not currently playing a sound), rather than having to micro-manage channels for short-term effects like swords hitting, spells casting, and so on. The first available channel is chosen from channels 1 to 5, keeping channels 6 to 10 free for longer-term effects like ambient sounds and music.
PlaySound (0, "swordhit.wav")
PlaySound (0, "shield_block.wav")
PlaySound (0, "fireball.wav")
The code above would let all 3 sounds play simultaneously. This would be useful where triggers might cause a sound to be played before another one had finished, and you didn't want the earlier sound to be cut off.
If MUSHclient can't find a free channel, the sound will replace whatever was in channel 1. Thus, you should keep channel 1 free for temporary sounds, and not use it to play music or ambient loops, otherwise they might get cut off during a busy battle.
In fact, you are advised to group your sounds as follows:
- Buffers 1 to 5 - short-term sounds (like a sword-hit, spell cast, cry of pain, etc.)
- Buffers 6 to 10 - longer-duration sounds or loops (like ambient sounds, music, crackling fire, etc.)
Full sound interface definition
result = PlaySound (Buffer, FileName, Loop, Volume, Pan)
Plays a sound, or adjusts the parameters of a sound that is currently playing. If a sound is currently playing in the selected buffer it is automatically stopped and its memory freed. Thus you do not need to stop sounds first before replacing them by something else.
- Buffer: Which buffer to use (1 to 10) or 0 to take the first free buffer when playing a new sound. If there are no free buffers, buffer 1 is chosen.
- FileName: The sound file to play. Can be the empty string in which case you are modifying the behaviour of a currently-playing sound in that buffer.
If the file name is a fully-qualified path name (ie. it starts with a slash, or a drive letter, colon, slash like "C:\") then it is used "as is".
Otherwise, MUSHclient prepends the MUSHclient execution directory + "sounds" subdirectory to the name
For example, the file name "swordhit.wav" would be looked for at:
You can use forward slashes in the file name (for convenience in typing strings in scripts), they are converted to backslashes.
The only file type supported is a .wav file, as follows:
- 22.05 KHz
- PCM (ie. uncompressed)
- Mono or Stereo
Note that the entire file is read into memory, uncompressed. We do not attempt to "stream" the file. Thus large sound files will take a lot of memory. The sound functions are intended for sound effects and short loops of music or ambient sounds. They are not intended to play Beethoven's Fifth Symphony while you play.
You can convert sound files which are in other formats (eg. MP3) into WAV files by using various programs. One very good free program is Audacity, which is available for Mac or Windows:
In Audacity, open your file, and then choose: File Menu -> Export As WAV...
- Loop: true or false (1 or 0). If true the sound loops, and thus repeats indefinitely until cancelled. A sound can be cancelled by calling the StopSound function for that buffer number, or simply playing a different sound in that buffer. Obviously it is preferable to not use buffer 0 if you are planning to loop, because you won't know which buffer actually got allocated.
If omitted in Lua scripting this defaults to false (not looping).
- Volume: -100 to 0. This is the number of db (decibels) to attenuate (reduce) the sound. Decibels are a logarithmic scale, every 3 db corresponds to half the perceived sound volume. Thus a volume of zero is "full volume". A volume of -3 is half-volume, -6 is quarter-volume, and so on. If you want to fade a sound away rather than just cut if off, you get quite a good effect by readjusting the sound every half second or so in a loop, reducing the volume by -2 each time, from 0 to -40. A sound at volume -40 is pretty quiet, after which you can just stop playing the sound. If you are going to write a "reduce the volume" loop you would need to use Lua coroutines, or some other timer mechanism, to turn the volume in that channel down periodically.
If omitted in Lua scripting this defaults to 0 (full volume).
If the volume is out of the range -100 to 0 then 0 (full volume) is used.
- Pan: -100 to +100. This is the number of db (decibels) to attenuate the sound in one speaker, to create a stereo affect (that is, more from one speaker than the other).
- Pan of -100 makes the sound come completely from the left speaker (the right speaker is cut off).
- Pan of 0 makes the sound come from both speakers, and thus it sounds centered.
- Pan of +100 makes the sound come completely from the right speaker (the left speaker is cut off).
If omitted in Lua scripting this defaults to 0 (both speakers, or centered).
If the pan value is out of the range -100 to +100 then 0 is used.
-- play sound once only, full volume, centered, in buffer 1
PlaySound (1, "boing.wav", false, 0, 0)
-- play sound once only, quarter volume, from the left speaker, in buffer 2
PlaySound (2, "ding.wav", false, -6, -100)
-- play sound looping, eighth volume, from the right speaker, in buffer 3
PlaySound (3, "forest.wav", true, -9, 100)
-- play sound once only, full volume, slightly to the left, in the first available buffer
PlaySound (0, "swordhit.wav", false, 0, -3)
To adjust an existing sound, like the forest sound above:
PlaySound (3, "", false, -9, 100) --> cancel looping for buffer 3, still play at -9 db
PlaySound (3, "", false, -12, 100) --> cancel looping for buffer 3 and make softer
Possible return codes
- eCannotPlaySound (30004):
* DirectSound not initialized
* An attempt made to change the volume, looping, or pan of a sound that is not playing
* Sound file is not a .wav file
* Cannot parse sound file
* Sound file is not a PCM format file (wrong sound file format)
* Cannot find sound data in sound file
* Cannot create internal sound buffer (maybe all in use)
* Cannot read sound data into memory
* DirectSound refuses to play the sound
- eBadParameter (30046):
* An attempt made to change the volume, looping, or pan of a sound that is not in use
* No sound file name given, and buffer 0 given
* Buffer number outside range 0 to 10
* File name > 127 characters long
- eFileNotFound (30051):
* Sound file not found
- eOK (0):
* Sound played OK (or volume/looping/pan adjusted OK)
result = StopSound (Buffer)
Stops one, or all, sound buffers from playing. It is not necessary to call this for non-looping sounds, which stop of their own accord anyway, however doing so would free up the memory used by the sound.
- Buffer: Which buffer to stop (1 to 10) or 0 to stop all sound buffers playing.
Possible return codes
- eBadParameter (30046):
* Buffer number outside range 0 to 10
- eOK (0):
* Requested buffer(s) have been stopped.
result = GetSoundStatus (Buffer)
Gets the status of one of the sound buffers.
Basically if the returned number is > 0 then the sound is playing, and if it is <= 0 then it is not playing.
You might test this in a timer to see if a sound had stopped playing (eg. some music), and if so, play something else.
- Buffer: Which buffer to query (1 to 10).
Possible return codes
- -3: Sound system is not initialized (ie. DirectX not installed or too early version)
- -2: That buffer is free (never played a sound, or the sound was stopped with StopSound)
- -1: Requested buffer number out of range 1 to 10
- 0: That buffer has reached the end and stopped playing
- 1: That buffer is playing a sound, but not looping
- 2: That buffer is playing a sound, which is a looping sound
A sensible test for a plugin might be to check if the sound system is available, eg.
if GetSoundStatus (1) == -3 then
EnablePlugin ("982581e59ab42844527eec80", false) -- can't play sounds, disable plugin
end -- if
The MUSHclient Display menu -> Stop Sound Playing will cancel any sounds you have started via the PlaySound function. You might use that if sounds get out of hand a bit during testing.
There may be limitations on how many sounds can play simultaneously on your particular system. I haven't yet hit that limit, but it probably varies from one PC to the next, depending on Operating System verson, DirectX versions, type of sound card, sound card driver, and available system memory.
Apparently sounds can stop playing if you Alt+Tab to another application (or simply switch windows), and then switch back. Your scripts might want to periodically check whether music or ambient sounds they have started earlier are still playing (use GetSoundStatus), and if not, start them again.
Under the current configuration, the sounds stop playing if you make another application active, and then resume when MUSHclient is the foreground application again (or at least, it does for me).
You can still use the original Sound script function - that will play sounds "on top of" the sounds from PlaySound. Thus trigger (and other) sounds will continue to work.
I found that a judicious selection of sounds helped make playing a MUD somewhat more immersive. Having a stirring battle tune start up as you start a fight, and hearing each sword blow land, followed by the mob's death cry, made it quite a lot of fun.
Some versions of Windows may simply not support any DirectSound sound playing. If you can't get anything to work, try:
/print (GetSoundStatus (1))
If that prints -3 then your version of Windows does not support DirectSound. You could try installing DirectX.
Otherwise, if you are having problems check the return code, for example:
check (PlaySound (0, "cast/fireball.wav")) --> Cannot open the specified file (misspelt maybe?)
- Nick Gammon
| top |