skimi3d Grid Log in Visit the grid
All scripts
Weather and atmosphere

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.

Goes inA small prim near what should be heard
Setup6 to set
Code186 lines
Open toOpen to everyone

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

1 Rez a small prim where the sound should come from and drop this script into it.
2 Drop a sound into the same prim's Contents. It uses the first one it finds, so one sound per prim is the simple arrangement.
3 Set VOLUME. Ambience wants to be quieter than you think: 0.3 is present, 0.8 is a nuisance three rooms away.
4 If it carries too far, set RADIUS to the number of metres it should reach.
5 For crickets, set WHEN to 1. For birds, set it to 2.

Settings you can change

6

Edit these at the top of the script. Everything works on the defaults · change them only when you need to.

SOUND 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. (empty)
VOLUME 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. 0.3
RADIUS 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. 0.0
WHEN 0 always, 1 only at night, 2 only by day. Judged from the region's own sun rather than a clock, so it agrees with the Night Lamp and anything else watching the light. 0
TOUCH_TO_TOGGLE Touch to stop and start it. FALSE
WHO_MAY_TOUCH 0 for everyone, 1 for the owner alone, 2 for anyone wearing the same group tag. 1
The code LSL · 186 lines · updated 8 Sep 2026 Download .lsl
// =====================================================================
//  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.