How to write a Semaphore checking for two different states

I'm using a Text to Speech service(TTS) to read results from the Yelp API. I am running into a timing issue:

  1. If I call the TTS function too quickly, the speech will interrupt itself.

My idea was to write a semaphore that checks if the audio is playing and only reds the next command once its done. Unfortunately:

  1. If I wait until the audio isn't playing (audio.paused == true), the program hangs/wont break out of the while loop.

Does anyone have any ideas on how to solve the first problem without running into the second?

//check if command isn't already queued, and then add command to the queue
function voiceSynth (string, name) { 
if(voiceQueue.indexOf(string)== -1){
    voiceQueue.push(string) 
    voiceQueue.push(name) //used to keep track of the current item being read
}       
//Iterate over items in the queue
while (voiceQueue.length > 1){
    if (voiceBusy == false && audio.paused == true) {   
        voiceCall.call(undefined, voiceQueue.shift(),voiceQueue.shift())
    }
    }
}
//ajax call to the TTS service
function voiceCall (string, name) {
    voiceBusy = true
    console.log('synth called on ' + string)
    $.ajax('read/?string=' + string + '&speed=' + speed, {
        type: 'GET',
        success: function(src) {
            audio.setAttribute('src', src)  
            audio.play()
            voiceBusy = false
            voiceCursor = name  
        },
        error: function(xhr, ajaxOptions, thrownError) {
            console.log(xhr)
            console.log(ajaxOptions)
            console.log(thrownError)
            }
    })
    }

A semaphore is used in a multi-threading/IPC environment. With a javascript engine, you don't have this. You're trying to poll in Javascript, which won't work in its single threaded environment, unless you use setTimeout or setInteval.

In your setup, it looks like you have three types of events: a new voice task presents itself (and should be enqueued), a Yelp AJAX call returns, and audio playback ends. You seem to have handled the first two events and you're trying to figure out how to handle the audio ending event.

I'd move all the queue servicing code into its own function and then just call it whenever an interesting event happens. Let the queue servicing code figure out what to do. The rest of the code just has to handle the events and keep track of state. Something like this might work:

    var ajaxOutstanding = false;
    var audioOutstanding = false;

    function voiceSynth(string, name) {
       if(voiceQueue.indexOf(string)== -1){
          voiceQueue.push(string) 
          voiceQueue.push(name) //used to keep track of the current item being read
       }
       serviceQueue();
    }

    //ajax call to the TTS service
    function voiceCall (string, name) {
        console.log('synth called on ' + string)
        $.ajax('read/?string=' + string + '&speed=' + speed, {
            type: 'GET',
            success: function(src) {
                ajaxOutstanding = false;
                audio.setAttribute('src', src);
                audioOutstanding = true;
                audio.play();
                voiceCursor = name;
                serviceQueue();
            },
            error: function(xhr, ajaxOptions, thrownError) {
                ajaxOutstanding = false;
                console.log(xhr);
                console.log(ajaxOptions);
                console.log(thrownError);
                serviceQueue();
            }
        });
    }

    function handleAudioEnded() {
        audioOutstanding = false;
        serviceQueue();
    }

    audio.addEventListener('ended', handleAudioEnded);

    function serviceQueue() {
        if (voiceQueue.length > 1 && !ajaxOustanding && !audioOutstanding) {
        voiceCall.call(undefined, voiceQueue.shift(),voiceQueue.shift());
        }
    }

By the way,

    voiceCall.call(undefined, voiceQueue.shift(),voiceQueue.shift());

is the same as

    voiceCall(voiceQueue.shift(), voiceQueue.shift());

and is clearer.