websound extension update

As promised, I’ve made some progress on the websound extension for Web UI. Right now it supports most of the attributes of the HTML TADS sound tags: src, layer, repeat, volume, sequence, interrupt, and cancel.

Layers are only supported to a limited extent: there are four of them and they all work the same way. This should be mostly right, except for ambient; I need to ask a few clarifying questions of the experts to understand what that one is supposed to do.

Fades are not supported at all. This may be sheer ineptitude on my part but I can’t seem to get an mp3 hosted online to start playing instantly, much less perform incremental volume changes on the audio with sub-second precision. Local Web UI play is a different matter and probably could support fades.

The most significant restriction at the moment is that large mp3s don’t work well in the browser, when served out of the .t3 file. The session becomes unresponsive until the sound is loaded. This looks like an interpreter or server bottleneck rather than a problem with the sound code. The workaround is to put the mp3s online as separate files, and link to those files directly rather than to internal game resources.

For example:

#ifdef TADS_INCLUDE_NET
"<sound src=\"http://example.com/mp3/test.mp3\" layer=\"foreground\"/>";
#else
"<sound src=\"res/ogg/test.ogg\" layer=\"foreground\"/>";
#endif

Given all that, if it’s still of interest, you can get the latest websound snapshot. Unpack the files into your project folder and add the websound library to your Makefile at the end of the sources list.

This new module follows the webui conventions and is much less of a hacky abomination than the last version.

Basically it’s for deciding what not to play on implementations that can’t play all layers at once. In case you were not aware of this piece of documentation:

tads.org/t3doc/doc/htmltads/sound.htm

(Scroll down to the “Sound Architecture” section.)

From the random attribute description on that page:

The “from time to time” part is what I find ambiguous. Does the ambient layer try to find something to play every second? Every minute?

Put another way, if a sound has a random attribute of 1, that should mean it has a 1% chance to play in some interval of time. What interval do you use in QTads?

The interval is handled by the HTML subsystem (QTads only provides the timer interrupt API implementation so that the subsystem can use them.) Reading htmlsnd.cpp, CHtmlSoundQueue::on_timer(), suggests an interval of one second. CHtmlSoundQueue::maybe_start_sound() contains the code that makes the decision when to play the next sound.

So a 1% probability would play the sound every 100 seconds on average, and a 100% probably would play it every second. Note the “on average”. It doesn’t count how many seconds have passed. It calculates the probability every time and uses that to decide whether to play it or not. It has no memory of previous random probability outcomes.

Perfect, that’s what I needed to know. Thanks!

Btw, the code suggests (well, tells actually) that every sound in the sequence gets its own probability if the previous one didn’t score a high enough outcome. So that means that if the sequence contains 100 sounds, and the requested probability of each one is 1%, one of the sounds will almost always play. Each sound in the sequence can have a different probability though. So you might get one sound with a RANDOM value of 10 and another one with 30. If the first sound fails the 10% outcome, then a 30% outcome is tried for the next sound. And so on until a sound passes the check, or all sounds have been tried.

The actual code:

/*
 *   Check this sound for randomness.  If it has a random start
 *   probability, randomly decide whether to start it; if it fails,
 *   move on to the next sound.
 */
orig_nxt = nxt;
for (;;)
{
    /* if this one isn't random, start it immediately */
    if (nxt->get_random_start() == 0)
        break;

    /*.
     *   choose a random number from 0 to 100 - if it's less than the
     *   start probability, start the sound, otherwise skip it for now.
     */
    if ((rand() % 100) <= nxt->get_random_start())
        break;

    /*.
     *   move on to the next sound, wrapping to the start of the queue
     *   if we reach the end.
     */
    nxt = nxt->next_;
    if (nxt == 0)
        nxt = first_entry_;
        
    /* if we've looped, we don't want to start anything now */
    if (nxt == orig_nxt)
        return;
}

Great, that was helpful. I’ve added support for the random attribute and the ambient layer works properly now.

Now I just have to figure out what’s going on with the bundled MP3s.