MLPV2 Sound Effects

MLP Sound Effects by Chaz Longstaff
This MLP sound tool will trigger sounds when poses of your choosing are played. You control if the sounds loop or not, how long they play, etc. The script also allows users to turn all sounds off or back on via a button.
You need two things, both provided on this page: A script and a notecard. Make the script (call it "~sounds" or really anything you want) and the notecard (call it .SOUNDS or .SOUNDS.whatever) and drop into your MLPV2 product. Full usage instructions are within the notecard.
PIECE 1: SCRIPT
//MLP Sounds
//v. 1.1 by Chaz Longstaff 2014-03-7
//This script also requires a notecard named .SOUNDS or .SOUNDS.whatever
//lines inside card should read like: posename,sound, delaybeforeinitialstart, howoftentorepeat, volume, #repeatsbeforeanotherdelay, delayduringplay. See sample notecard provided with full usage instructions
list DelayDuringPlay = [""];
list DelayStarts = [""];
list MaxPlays = [""];
list PoseNames = [""];
list Sounds = [""];
list SoundTimes = [""];
list SoundVols = [""];
integer ListLength;
string mysound;
list mysoundlist;
float mystartdelay;
float myplaydelay;
integer mymax;
float mytime;
float myvol;
string playstatus = "On";
integer soundtotal;
integer soundcount;
integer TimesPlayed;
integer x;
float Interval;
integer Debug;
integer PoseCfgCount;
string Pose;
config_init()
{
 PoseCfgCount = 0;
 DelayStarts = [];
 DelayDuringPlay = [];
 MaxPlays = [];
 PoseNames = [];
 Sounds = [];
 SoundTimes = [];
 SoundVols = [];
 Debug = 0;
 Interval = 15.;
}
config_parse(string str, string cardName, integer lineNum)
{
 str = llStringTrim(str, STRING_TRIM_HEAD); // delete leading spaces
 // lines beginning with slash are comments -- ignore them
 if ( (llGetSubString(str,0,0) == "/") || (str == "") ) {
 return;
 }
 list ldata = llParseStringKeepNulls(str, [","], [""]);
 PoseCfgCount += 1;
 string pose = llStringTrim(llList2String(ldata,0), STRING_TRIM);
 string mysoundstr = llStringTrim(llList2String(ldata,1), STRING_TRIM);
 string mystartdelaystr = llStringTrim(llList2String(ldata,2), STRING_TRIM);
 string mytimestr = llStringTrim(llList2String(ldata,3), STRING_TRIM);
 string myvolstr = llStringTrim(llList2String(ldata,4), STRING_TRIM);
 string mymaxstr = llStringTrim(llList2String(ldata,5), STRING_TRIM);
 string myplaydelaystr = llStringTrim(llList2String(ldata,6), STRING_TRIM);
 
 // posename, sound, delay, howoftentorepeat, volume, #repeatsbeforeanotherdelay
 
 addConfig(pose, mysoundstr, mystartdelaystr, mytimestr, myvolstr, mymaxstr, myplaydelaystr);
}
addConfig(string ref, string sound, string delayref, string time, string vol, string maxref, string playdelayref) {
 PoseNames += (list)ref;
 Sounds += (list)sound;
 DelayStarts += (list)delayref;
 SoundTimes += (list)time;
 SoundVols += (list)vol;
 MaxPlays += (list)maxref;
 DelayDuringPlay += (list)playdelayref;
 
}
// Post-process any config, if necessary
config_done() {
 announce();
}
announce() {
 //integer acount = llGetListLength(AnimNames);
 
 llWhisper(0,
 // + plural("anim", "anims", acount)
 //+ " and "
 (string) PoseCfgCount
 + " pose(s) configured specially for sound ("
 + llGetScriptName()
 + ": "
 + (string)llGetFreeMemory()
 + " bytes free).");
}
togglesound() {
 if (playstatus == "On") {
 playstatus = "Off";
 llSetTimerEvent(0);
 }
 else {
 playstatus = "On";
 llSetTimerEvent( mytime );
 }
}
// ==== Utilities ====
integer isKey(string str) { //Identifies whether a certain string is a UUID or not. TRUE if it is 
 return ( (llStringLength(str) == 36) && //UUIDs are always 36 characters 
 (llGetSubString(str, 8, 8) == "-") && //9th character is always a dash
 (llGetSubString(str, 13, 13) == "-") && //14th character is always a dash
 (llGetSubString(str, 18, 18) == "-") && //19th character is always a dash
 (llGetSubString(str, 23, 23) == "-") ); //24th character is always a dash
}
// Static parameters for reading card config
string ConfigNotecardPrefix = ".SOUNDS"; // string identifying config notecards
float ConfigTimeout = 180.0; // seconds per card to wait for slow server
// Globals for reading card config
integer ConfigLineIndex;
key ConfigRequestID;
list ConfigCards; // list of names of config cards
string ConfigCardName; // name of card being read
integer ConfigCardIndex; // index of next card to read
string ConfigCardKeys; // for detecting config change
// return TRUE if there is a config card
integer next_card()
{
 if (ConfigCardIndex >= llGetListLength(ConfigCards)) {
 return (FALSE);
 }
 
 ConfigLineIndex = 0;
 ConfigCardName = llList2String(ConfigCards, ConfigCardIndex);
 ConfigCardIndex++;
 ConfigRequestID = llGetNotecardLine(ConfigCardName, ConfigLineIndex);
 llWhisper(0,"Reading " + ConfigCardName);
 return (TRUE);
}
// Get list of config cards, and return list of their keys for use
// in detecting a config change.
string get_cards() {
 string item;
 string keys;
 
 ConfigCards = [];
 integer n = llGetInventoryNumber(INVENTORY_NOTECARD);
 while (n-- > 0) {
 item = llGetInventoryName(INVENTORY_NOTECARD, n);
 if (llSubStringIndex(item, ConfigNotecardPrefix) != -1) {
 ConfigCards += [item];
 keys += (string) llGetInventoryKey(item);
 }
 }
 return keys;
}
default
{
 state_entry() {
 state s_config;
 }
}
// This state is only used to get into s_config, because going from
// s_config to s_config won't redo it's state_entry. But we might
// not want to redo anything we might have put in default state entry.
state s_reconfig
{
 state_entry() {
 state s_config;
 }
}
// Read card config
// Multiple notecard version - read all cards with the given extension
state s_config
{
 state_entry() {
 config_init();
 ConfigCardKeys = get_cards();
 ConfigCardIndex = 0;
 if (next_card()) {
 llSetTimerEvent(ConfigTimeout);
 } else {
 state s_unconfigured;
 }
 }
 dataserver(key query_id, string data) {
 if (query_id == ConfigRequestID) {
 if (data == EOF) {
 if (next_card()) {
 llSetTimerEvent(ConfigTimeout);
 } else {
 config_done();
 state s_active;
 }
 } else {
 config_parse(data, ConfigCardName, ConfigLineIndex);
 ConfigRequestID = llGetNotecardLine(ConfigCardName, ++ConfigLineIndex);
 }
 }
 }
 timer() {
 llWhisper(0,"Dataserver time out");
 state s_unconfigured;
 }
 on_rez(integer num) { state s_reconfig; }
 changed(integer change) {
 if (change & CHANGED_OWNER) { llResetScript(); }
 if (change & CHANGED_INVENTORY) { state s_reconfig; }
 }
 state_exit() {
 llSetTimerEvent(0);
 }
}
state s_unconfigured
{
 changed(integer change) {
 if (change & CHANGED_INVENTORY) {
 if (get_cards() != ConfigCardKeys) {
 state s_config;
 }
 }
 }
}
state s_active {
 
 on_rez(integer start) {
 llResetScript();
 
 }
 
 state_entry(){
 ListLength = llGetListLength(PoseNames);
 }
 link_message(integer sender_num, integer num, string str, key id) {
 
 // num = 0 and msg = "POSEB": new pose
 // id = pose name
 if ( (num == 987790) && (str == "togglesound") ) {
 togglesound();
 }
 
 
 
 if (num == 0 && str == "STOP") {
 llStopSound();
 llSetTimerEvent(0);
 } else if (num == 989999) {
 if (str == "stopsound") {
 llStopSound();
 llSetTimerEvent(0);
 }
 }
 else if (num == 0 && str == "POSEB") {
 mystartdelay = 0;
 mymax = 0;
 soundcount = 0;
 soundtotal = 0;
 TimesPlayed = 0;
 mysoundlist = [];
 if (playstatus == "Off") {
 llSetTimerEvent(0);
 return;
 }
 Pose = llStringTrim( ((string)id) , STRING_TRIM);
 // llSay(0, "Pose : " + Pose);
 integer ix = llListFindList(PoseNames, (list)Pose);
 if ( ix != -1) {
 string mysounds = llList2String(Sounds,ix);
 integer index = llSubStringIndex(mysounds,"***");
 if(index == -1) {
 mysound = mysounds;
 }
 else {
 mysoundlist = llParseStringKeepNulls(mysounds, ["***"], [""]);
 soundtotal = llGetListLength(mysoundlist);
 mysound = llList2String(mysoundlist,soundcount);
 }
 mystartdelay = (float)llList2String(DelayStarts,ix);
 mymax = (integer)llList2String(MaxPlays,ix);
 mytime = (float)llList2String(SoundTimes,ix);
 myvol = (float)llList2String(SoundVols,ix);
 myplaydelay = (float)llList2String(DelayDuringPlay,ix);
// llSay(0, 
// "mysound : " + mysound
// + "/ delay : " + (string) mystartdelay
// + " / repeatseconds : " + (string) mytime
// + " / myvol : " + (string) myvol
// + " / maxrepeats : " + (string) mymax
// + " / delayduringplay : " + (string) myplaydelay 
// );
 if ( mystartdelay > 0 ) llSleep(mystartdelay);
 llPlaySound(mysound,myvol);
 TimesPlayed = TimesPlayed + 1;
 llSetTimerEvent( mytime );
 }
 else {
 // llSay(0, "got a shut off msg");
 llSetTimerEvent( 0 );
 }
 } 
 } //end linkmsg section
 timer() {
 //llSay(0, "in timer");
 if (mymax == 0) {
 //llSay(0, "no max so go ahead and play");
 }
 else if ( (TimesPlayed + 1) > mymax ) {
 TimesPlayed = 0;
 llSleep(myplaydelay);
 }
 
 
 if (soundtotal > 1) {
 soundcount = soundcount + 1;
 if ( (soundcount + 1) > soundtotal ) {
 soundcount = 0; 
 } 
 
 mysound = llList2String(mysoundlist,soundcount);
 }
 if (isKey(mysound) == TRUE) {
 //it's a UUID so just play it
 llPlaySound(mysound, myvol);
 TimesPlayed = TimesPlayed + 1;
 }
 else {
 //it's a sound file, so test to make sure it's present to avoid script errors
 if ( llGetInventoryType(mysound) != INVENTORY_NONE ) {
 llPlaySound(mysound, myvol);
 TimesPlayed = TimesPlayed + 1;
 } 
 }
 
 }
 
 changed(integer change) {
 if (change & CHANGED_INVENTORY) {
 if (get_cards() != ConfigCardKeys) {
 state s_config;
 }
 }
 }
 
 
}
PIECE 2: NOTECARD
//name card .SOUNDS
//or .SOUNDS.whatever
//lines inside card should read like: posename,sound, delaybeforeinitialstart, howoftentorepeat, volume, #repeatsbeforeanotherdelay, delayduringplay
//posename: case (and space sensitive inside pose names), make sure you get it right! You can leave spaces in between the commas or not, your choice.
//sound. You have two choices.
//Sound Choice (1) You can put in sound name, but if you do, you must include the sound file inside the prim. You need to have copy / transfer rights to the sound file to include it in products you want to sell to others. You don't need mod rights to the file.
//Sound Choice (2) You can instead supply the UUID of the sound file. You need full perm rights (mod / copy / transfer) to the sound file in order to be able to get the UUID -- right-click over a full-perm sound in your invy. Choose copy UUID, and use that as the sound name. When you list a sound UUID, you don't need to include the sound file inside the MLP product -- makes for tidier contents and less to proof for perms, etc.
//delay: how many seconds to pause initially before starting any sound play. Set to 0 if no delay is wanted. Applies only to initial start.
//howoftentorepeat: how often in seconds the sound should repeat play itself. Enter as a number. Decimal numbers such as 5.5 are fine. 0 equals play only once.
//volume: Enter a number ranging from .1 (lowest) to 1.0 (that being the loudest possible in SL). Instead of 1.0, you can enter just 1
// #repeatsbeforeanotherdelay: no of times to play the sound before once again doing the delay. Example: delay of 10 seconds, 15 repeats: script waits 10 seconds before starting playing. Plays sound 15 times (at the x seconds intervals you specified.) 
//delayduringplay: sound plays x number of times, then pauses this long in seconds, then plays x number of times again, then pauses again this long in seconds, etc. Put 0 if no delay.
//MENU BUTTON
//Creating a menu button like this will allow users to toggle the playing of sounds off and on
//LINKMSG buttonname | 0,-4,987790, togglesound
//change the buttonname part to be anything you like (e.g. SND ON/OFF or TOGGLE SOUND). Don't change anything else in this line.
LINKMSG SOUNDON/OFF | 0,-4,987790, togglesound
//SOUND LINE EXAMPLE
//Spanking, LoudSpank, 0, 10, 1.0, 0
//when they are on the Spanking pose, play the sound LoudSpank. No delay in starting. Play every 10 seconds at 1.0 volume (max volume). No limit to repeats.
//Flogging, 98gyt10f-82e4-788c-2a07-1c32d409yhmv, 10, 3, 1, 5
//when they are on the Flogging pose, play the sound represented by the UUID. Wait 10 seconds before starting, then play every 3 seconds at max volume which is 1. When the sound has played 5 times, then pause 10 seconds again, they start play all over again.
BInspect, d3b61629-50a3-9716-7683-8972f2bb631c, 14, 1, 1, 4, 26
BDick, beab335a-c030-0307-0948-c5cc77b3c3ac, 0, 20, 1, 0, 0
BTowel, 59fbf10f-82e4-788c-2a07-1c32d409fafe, 0, 3, 1, 0, 0
BSpank, d71bd292-6d72-a830-68a1-8b4322fb091d, 0, 3, 1, 0, 0