Ambient Sound Loop
Loops a sound from the prim's own Contents at a volume and a range you set, and can hold its tongue until nightfall.
What it does
It finds a sound in the prim's Contents, loops it, and keeps quiet about it. Volume and audible range are both yours, so a fountain can be heard at the fountain and not from the road. WHEN lets it play only after dark or only by day, judged from the region's own sun rather than from a clock, so it agrees with everything else in the region that watches the light. It writes only when something actually changes, because calling for a loop that is already looping restarts it from the beginning and puts a hiccup in the middle of your waves.
What you can build with it
Waves on a shore, a stream, wind in a wood, rain on a roof, a generator humming in a shed, crickets after dark, birds by day, the hum of a shop's cooling. Sound is the half of a place that nobody builds, and a region with the right three seconds of water in it feels finished in a way that no amount of extra prims achieves.
How to use it
Settings you can change
6Edit these at the top of the script. Everything works on the defaults · change them only when you need to.
// =====================================================================
// 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();
}
}
Copy it, make a new script inworld, paste over what is there and save. Nothing here phones home.