// ===================================================================== // Ambient Sound Loop // skimi3d script library // https://grid.skimi3d.com/scripts/ambient-sound-loop // // Plays a sound from this prim's own Contents, over and over, at a // volume and a range you set. Waves on a shore, a stream, wind in a // wood, a generator humming, crickets after dark. // // Sound is the half of a place that nobody builds. A region with the // right three seconds of water in it feels finished in a way that no // amount of extra prims achieves. // // Plain LSL. // ===================================================================== // --------------------------------------------------------------------- // Settings // --------------------------------------------------------------------- // The sound to loop, by name. Left empty it uses the first sound it // finds in this prim's Contents, so you can drop one in and be done. string SOUND = ""; // How loud, from 0.0 to 1.0. Ambience wants to be quieter than you // think: 0.3 is present, 0.8 is a nuisance three rooms away. float VOLUME = 0.3; // How far it carries, in metres. 0.0 leaves it at the region's normal // range, which is generous. Set a small number for a fountain that // should not be heard from the road. float RADIUS = 0.0; // When it should play: // 0 = always // 1 = only at night // 2 = only by day integer WHEN = 0; // Touch to stop and start it. integer TOUCH_TO_TOGGLE = FALSE; // Who may touch it. 0 = everyone, 1 = only the owner, 2 = same group. integer WHO_MAY_TOUCH = 1; // --------------------------------------------------------------------- // Everything below here is the machinery // --------------------------------------------------------------------- // How often to look at the sky when WHEN is not "always". Half a minute // is far more often than dusk needs, and still nothing. float SKY_CHECK = 30.0; integer gPlaying = FALSE; integer gWanted = TRUE; // what the switch says, before the sky votes string gSound = ""; string findSound() { if (SOUND != "") return SOUND; if (llGetInventoryNumber(INVENTORY_SOUND) > 0) { return llGetInventoryName(INVENTORY_SOUND, 0); } return ""; } integer isNight() { // The sun below the horizon. The same test the Night Lamp uses, and // it is the honest one: the region's own sun, not a clock. return llGetSunDirection().z < 0.0; } // Whether it should be sounding right now, given the switch and the sky. integer shouldPlay() { if (!gWanted) return FALSE; if (WHEN == 1) return isNight(); if (WHEN == 2) return !isNight(); return TRUE; } settle() { integer want = shouldPlay(); // Only write when something actually changes. Calling llLoopSound // again on an already looping sound restarts it from the beginning, // which is a hiccup every half minute for no reason at all. if (want == gPlaying) return; if (want) { if (gSound == "") { llOwnerSay("I have no sound to play. Drop one into my Contents, or write its name into SOUND."); return; } if (RADIUS > 0.0) llSetSoundRadius(RADIUS); llLoopSound(gSound, VOLUME); } else { llStopSound(); } gPlaying = want; } integer mayTouch(key who) { if (WHO_MAY_TOUCH == 1) return who == llGetOwner(); if (WHO_MAY_TOUCH == 2) return llSameGroup(who); return TRUE; } default { state_entry() { gSound = findSound(); gPlaying = FALSE; gWanted = TRUE; llStopSound(); settle(); // The clock only runs when the sky has a say. A sound that // plays always needs no timer at all, and should not have one. if (WHEN == 0) llSetTimerEvent(0.0); if (WHEN != 0) llSetTimerEvent(SKY_CHECK); } timer() { settle(); } touch_start(integer total) { if (!TOUCH_TO_TOGGLE) return; key who = llDetectedKey(0); if (!mayTouch(who)) { llRegionSayTo(who, 0, "This one is not yours to switch."); return; } gWanted = !gWanted; settle(); } changed(integer what) { // A sound dropped in afterwards should just start working. if (what & CHANGED_INVENTORY) { string now = findSound(); if (now != gSound) { gSound = now; llStopSound(); gPlaying = FALSE; settle(); } } } on_rez(integer start) { llResetScript(); } }