/*
 * sound_pulseaudio.c is part of Quisk, and is Copyright the following
 * authors:
 * 
 * Philip G. Lee <rocketman768@gmail.com>, 2014
 * Jim Ahlstrom, N2ADR, October, 2014
 * Eric Thornton, KM4DSJ, September, 2015
 * 
 * This code replaces the pulseaudio-simple version by Philip G. Lee.  It
 * uses the asynchronous pulseaudio API.  It was written by Eric Thornton, 2015.
 *
 * Quisk is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * Quisk is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */


/* This pulseaudio interface was rewritten to utilize the features in the 
 * asynchronous API and threaded mainloop.
 * Eric Thornton, KM4DSJ 2015
*/
/*
 * 2017 Nov by N2ADR Remove most exit(1) statements.
 * 2017 Nov by N2ADR Add code to enable PulseAudio to continue with mis-spelled device names.
*/

#include <Python.h>
#include <stdio.h>
#include <string.h>
#include <complex.h>
#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include "quisk.h"
#include <pulse/pulseaudio.h>

// From pulsecore/macro.h
#define pa_memzero(x,l) (memset((x), 0, (l)))
#define pa_zero(x) (pa_memzero(&(x), sizeof(x)))

// Current sound status
extern struct sound_conf quisk_sound_state;

//pointers for aychronous threaded loop
static pa_threaded_mainloop *pa_ml;
static pa_mainloop_api *pa_mlapi;
static pa_context *pa_ctx; 		//local context
static pa_context *pa_IQ_ctx; 		//remote context for IQ audio
volatile int streams_ready = 0;		//This is ++/-- by the mainloop thread
volatile int streams_to_start;

// remember all open devices for easy cleanup on exit
static pa_stream *OpenPulseDevices[PA_LIST_SIZE * 2] = {NULL};

static int have_QuiskDigitalInput;	// Do we need to create this null sink?
static int have_QuiskDigitalOutput;	// Do we need to create this null sink?

/* This callback happens any time a stream changes state. Here, it's primary used to 
 * tell the quisk thread when streams are ready. 
 */

void stream_state_callback(pa_stream *s, void *userdata) {
    struct sound_dev *dev = userdata;
    assert(s);
    assert(dev);
    
    dev->pulse_stream_state = pa_stream_get_state(s);
    switch (dev->pulse_stream_state) {
        case PA_STREAM_CREATING:
	    if (quisk_sound_state.verbose_pulse)
		printf("\n**Stream %s state Creating\n", dev->name);
            break;
            
        case PA_STREAM_TERMINATED:
	    if (quisk_sound_state.verbose_pulse)
		printf("\n**Stream %s state Terminated\n", dev->name);
            streams_ready--;
            break;
            
        case PA_STREAM_READY:
	    if (quisk_sound_state.verbose_pulse)
		printf("\n**Stream %s state Ready\n", dev->name);
            streams_ready++; //increment counter to tell other thread that this stream is ready
	    streams_to_start++;
            if (quisk_sound_state.verbose_pulse) {
                const pa_buffer_attr *a;
                printf("Connected to device %s (%u, %ssuspended). ",
                       pa_stream_get_device_name(s), pa_stream_get_device_index(s),
                       pa_stream_is_suspended(s) ? "" : "not ");

                if (!(a = pa_stream_get_buffer_attr(s)))
                    printf("pa_stream_get_buffer_attr() failed: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
                else if (!(a->prebuf)) {
                    printf("Buffer metrics %s: maxlength=%u, fragsize=%u\n", dev->name, a->maxlength, a->fragsize);
                }
                else {
                    printf("Buffer metrics %s: maxlength=%u, prebuf=%u, tlength=%u  minreq=%u\n",
                           dev->name, a->maxlength, a->prebuf, a->tlength, a->minreq);
                }
            }
            break;
        
        case PA_STREAM_FAILED:
        default:
	    snprintf(quisk_sound_state.err_msg, QUISK_SC_SIZE,
		"Stream error: %.40s - %.40s", dev->name, pa_strerror(pa_context_errno(pa_stream_get_context(s))));
	    if (quisk_sound_state.verbose_pulse)
		printf("\n**Stream %s state Failed\n", dev->name);
            printf("%s\n", quisk_sound_state.err_msg);
	    streams_to_start++;
            break;
    }
}


//Indicates underflow on passed stream (playback)
static void stream_underflow_callback(pa_stream *s, void *userdata) {
    struct sound_dev *dev = userdata;
    assert(s);
    assert(dev);
    
    if (quisk_sound_state.verbose_pulse)
        printf("Stream underrun %s\n", dev->name);
    dev->dev_error++;
}

//Indicates overflow on passed stream (record)
static void stream_overflow_callback(pa_stream *s, void *userdata) {
    struct sound_dev *dev = userdata;
    assert(s);
    
    if (quisk_sound_state.verbose_pulse)
        printf("Stream overrun %s\n", dev->name);
    dev->dev_error++;
}

//Indicates stream has started
static void stream_started_callback(pa_stream *s, void *userdata) {
    struct sound_dev *dev = userdata;
    assert(s);
    assert(dev);
    
    if (quisk_sound_state.verbose_pulse)
        printf("Stream started %s\n", dev->name);
}

//Called when cork/uncork has succeeded on passed stream. Signals mainloop when complete.
static void stream_corked_callback(pa_stream *s, int success, void *userdata) {
    assert(s);
    struct sound_dev *dev = userdata;
    
    if (s) {
        if (quisk_sound_state.verbose_pulse)
            printf("Stream cork/uncork %s success\n", dev->name);
        pa_threaded_mainloop_signal(pa_ml, 0);
    }
    else {
        if (quisk_sound_state.verbose_pulse)
            printf("Stream cork/uncork %s Failure!\n", dev->name);
        exit(11);
    }
}

// Called when stream flush has completed.
static void stream_flushed_callback(pa_stream *s, int success, void *userdata) {
    assert(s);
    struct sound_dev *dev = userdata;
    
    if (s) {
        printf("Stream flush %s success\n", dev->name);
        pa_threaded_mainloop_signal(pa_ml, 0);
    }
    else {
        printf("Stream flush %s Failure!\n", dev->name);
        exit(12);
    }
}

// This is called by the play function when the timing structure is updated.
static void stream_timing_callback(pa_stream *s, int success, void *userdata) {
    struct sound_dev *dev = userdata;
    pa_usec_t l;
    int negative = 0;
    assert(s);

    if (!success || pa_stream_get_latency(s, &l, &negative) < 0) {
        printf("pa_stream_get_latency() failed: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        return;
    }

    dev->dev_latency = (int)l;
    if (negative)
        dev->dev_latency *= -1;
    pa_threaded_mainloop_signal(pa_ml, 0);
}


/* This callback allows us to read in the server side stream information so that we
 * can match stream formats and sizes to what is configured in pulseaudio.
*/

static void server_info_cb(pa_context *c, const pa_server_info *info, void *userdata) {
    struct sound_dev **pDevices = userdata;
    pa_buffer_attr rec_ba;
    pa_buffer_attr play_ba;
    pa_sample_spec ss;
    pa_sample_spec default_ss;
    pa_stream_flags_t pb_flags = PA_STREAM_NOFLAGS;
    pa_stream_flags_t rec_flags = PA_STREAM_ADJUST_LATENCY;
    default_ss = info->sample_spec;

    printf("Connected to %s \n", info->host_name);

    while(*pDevices) {
        struct sound_dev *dev = *pDevices++;
        const char *dev_name;
        pa_stream *s;
        
        pa_zero(rec_ba);
        pa_zero(play_ba);

        if (dev->name[5] == ':')
            dev_name = dev->name + 6;		// the device name is given; "pulse:alsa_input.pci-0000_00_1b.0.analog-stereo"
        else
            dev_name = NULL;			// the device name is "pulse" for the default device

        if (quisk_sound_state.verbose_pulse)
            printf("Opening Device %s\n", dev_name);

        //Construct sample specification. Use S16LE if availiable. Default to Float32 for others.
        //If the source/sink is not Float32, Pulseaudio will convert it (uses CPU)
        //dev->sample_bytes = (int)pa_frame_size(&ss) / ss.channels;
        if (default_ss.format == PA_SAMPLE_S16LE) {
            dev->sample_bytes = 2;
            ss.format = default_ss.format;
        }
        else {
            dev->sample_bytes = 4;
            ss.format = PA_SAMPLE_FLOAT32LE;
        }
        
        ss.rate = dev->sample_rate;
        ss.channels = dev->num_channels;

        rec_ba.maxlength = (uint32_t) -1;
        rec_ba.fragsize = (uint32_t) SAMP_BUFFER_SIZE / 16;  //higher numbers eat cpu on reading monitor streams.

        play_ba.maxlength = (uint32_t) -1;
        play_ba.prebuf = (uint32_t) (dev->sample_bytes * ss.channels * dev->latency_frames);
        //play_ba.tlength = (uint32_t) -1;
        play_ba.tlength = play_ba.prebuf;

        if (dev->latency_frames == 0)
            play_ba.minreq = (uint32_t) 0; //verify this is sane
        else
            play_ba.minreq = (uint32_t) -1;

        if (dev->stream_dir_record) {

            if (!(s = pa_stream_new(c, dev->stream_description, &ss, NULL))) {
                printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
                continue;
            }
            if (pa_stream_connect_record(s, dev_name, &rec_ba, rec_flags) < 0) {
                printf("pa_stream_connect_record() failed: %s", pa_strerror(pa_context_errno(c)));
                continue;
            }
            pa_stream_set_overflow_callback(s, stream_overflow_callback, dev);
        }

        else {
            pa_cvolume cv;
            pa_volume_t volume = PA_VOLUME_NORM;

            if (!(s = pa_stream_new(c, dev->stream_description, &ss, NULL))) {
                printf("pa_stream_new() failed: %s", pa_strerror(pa_context_errno(c)));
                continue;
            }
            if (pa_stream_connect_playback(s, dev_name, &play_ba, pb_flags, pa_cvolume_set(&cv, ss.channels, volume), NULL) < 0) {
                printf("pa_stream_connect_playback() failed: %s", pa_strerror(pa_context_errno(c)));
                continue;
            }
            pa_stream_set_underflow_callback(s, stream_underflow_callback, dev);
        }
        
        
        pa_stream_set_state_callback(s, stream_state_callback, dev);
        pa_stream_set_started_callback(s, stream_started_callback, dev);

        dev->handle = (void*)s; //save memory address for stream in handle

        int i;
        for(i=0;i < PA_LIST_SIZE;i++) {  //save address for stream for easy exit
            if (!(OpenPulseDevices[i])) {
                OpenPulseDevices[i] = dev->handle;
                break;
            }
        }
    }
}


//Context state callback. Basically here to pass initialization to server_info_cb
void state_cb(pa_context *c, void *userdata) {
    pa_context_state_t state;
    state = pa_context_get_state(c);
    switch  (state) {
        // There are just here for reference
        case PA_CONTEXT_UNCONNECTED:
        case PA_CONTEXT_CONNECTING:
        case PA_CONTEXT_AUTHORIZING:
        case PA_CONTEXT_SETTING_NAME:
        default:
            break;
        case PA_CONTEXT_FAILED:
        case PA_CONTEXT_TERMINATED:
            printf("Context Terminated\n");
            break;
        case PA_CONTEXT_READY: {
            pa_operation *o;
            if (!(o = pa_context_get_server_info(c, server_info_cb, userdata)))
                printf("pa_context_get_server_info() failed: %s", pa_strerror(pa_context_errno(c)));
            else
                pa_operation_unref(o);
        }
    }
}


#if 0
/* Stream draining complete */
static void stream_drain_complete(pa_stream*s, int success, void *userdata) {
    struct sound_dev *dev = userdata;

    if (!success) {
        printf("Failed to drain stream: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        quit(1);
    }

    if (quisk_sound_state.verbose_pulse)
        printf("Playback stream %s drained.\n", dev->name);
}


/*drain stream function*/
void quisk_drain_cork_stream(struct sound_dev *dev) {
    pa_stream *s = dev->handle;
    pa_operation *o;
   
    if (!(o = pa_stream_drain(s, stream_drain_complete, NULL))) {
        printf("pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        exit(1);
    }
    
    pa_operation_unref(o);
}

#endif

//Cork function. Holds mainloop lock until operation is completed.
void quisk_cork_pulseaudio(struct sound_dev *dev, int b) {
    pa_stream *s = dev->handle;
    pa_operation *o;
    
    pa_threaded_mainloop_lock(pa_ml);
    
    if (!(o = pa_stream_cork(s, b, stream_corked_callback, dev))) {
        printf("pa_stream_cork(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        exit(13);
    }
    else {
        while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
            pa_threaded_mainloop_wait(pa_ml);
        
        pa_operation_unref(o);
    }
    pa_threaded_mainloop_unlock(pa_ml);

    if (b)
        dev->cork_status = 1;
    else
        dev->cork_status = 0;
}

//Flush function. Holds mainloop lock until operation is completed.
void quisk_flush_pulseaudio(struct sound_dev *dev) {
    pa_stream *s = dev->handle;
    pa_operation *o;
    
    pa_threaded_mainloop_lock(pa_ml);
    
    if (!(o = pa_stream_flush(s, stream_flushed_callback, dev))) {
        printf("pa_stream_flush(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        exit(14);
    }
    else {
        while(pa_operation_get_state(o) == PA_OPERATION_RUNNING)
            pa_threaded_mainloop_wait(pa_ml);
        pa_operation_unref(o);
    }
    pa_threaded_mainloop_unlock(pa_ml);
}


static void WaitForPoll(void) {		// Implement a blocking read
    static double time0 = 0;		// start time in seconds
    double timer;			// time remaining from last poll usec

    timer = quisk_sound_state.data_poll_usec - (QuiskTimeSec() - time0) * 1e6;

    if (timer > 1000.0)     // see if enough time has elapsed
        QuiskSleepMicrosec((int)timer);		// wait for the remainder of the poll interval

    time0 = QuiskTimeSec();	// reset starting time value
}


/* Read samples from the PulseAudio device.
 * Samples are converted to complex form based upon format, counted
 * and returned via cSamples pointer.
 * Returns the number of samples placed into cSamples
 */
int quisk_read_pulseaudio(struct sound_dev *dev, complex double *cSamples)
{ // cSamples can be NULL to discard samples
    int i, nSamples;
    int read_frames;		// A frame is a sample from each channel
    const void * fbuffer;
    pa_stream *s = dev->handle;
    size_t read_bytes;
    
    if (!dev || dev->pulse_stream_state != PA_STREAM_READY)
        return 0;

    if (dev->cork_status) {
        if (dev->read_frames != 0) {
            WaitForPoll();
        }
        return 0;
    }

   // Note: Access to PulseAudio data from our sound thread requires locking the threaded mainloop.
    if (dev->read_frames == 0) {		// non-blocking: read available frames
        pa_threaded_mainloop_lock(pa_ml);
        read_frames = pa_stream_readable_size(s) / dev->num_channels / dev->sample_bytes;
        
        if (read_frames == 0) {
            pa_threaded_mainloop_unlock(pa_ml);
            return 0;
        }
        dev->dev_latency = read_frames * dev->num_channels * 1000 / (dev->sample_rate / 1000);
        
    }
    
    else {		// Blocking read for dev->read_frames total frames
        WaitForPoll();
        pa_threaded_mainloop_lock(pa_ml);
        read_frames = pa_stream_readable_size(s) / dev->num_channels / dev->sample_bytes;
        
        if (read_frames == 0) {
            pa_threaded_mainloop_unlock(pa_ml);
            return 0;
        }
        dev->dev_latency = read_frames * dev->num_channels * 1000 / (dev->sample_rate / 1000);
    }
    
    
    nSamples = 0;
    
   while (nSamples < read_frames) {		// read samples in chunks until we have enough samples
       if (pa_stream_peek (s, &fbuffer, &read_bytes) < 0) {
           printf("Failure of pa_stream_peek in quisk_read_pulseaudio\n");
           pa_threaded_mainloop_unlock(pa_ml);
           return nSamples;
       }
       
       if (fbuffer == NULL && read_bytes == 0) {		// buffer is empty
           break;
       }
       
       if (fbuffer == NULL) {		// there is a hole in the buffer
           pa_stream_drop(s);
           continue;
       }
       
       if (nSamples * dev->num_channels * dev->sample_bytes + read_bytes >= SAMP_BUFFER_SIZE * 8 / 10) {
           if (quisk_sound_state.verbose_pulse)
               printf("buffer full on %s\n", dev->name);
           pa_stream_drop(s);		// limit read request to buffer size
           break;
       }
       
       // Convert sampled data to complex data. dev->num_channels must be 2.
       if (dev->sample_bytes == 4) {  //float32
           float fi, fq;
           
           for( i = 0; i < read_bytes; i += (dev->num_channels * 4)) {
               memcpy(&fi, fbuffer + i + (dev->channel_I * 4), 4);
               memcpy(&fq, fbuffer + i + (dev->channel_Q * 4), 4);
               if (fi >=  1.0 || fi <= -1.0)
                   dev->overrange++;
               if (fq >=  1.0 || fq <= -1.0)
                   dev->overrange++;
               if (cSamples)
                   cSamples[nSamples] = (fi + I * fq) * CLIP32;
               nSamples++;
           }
       }
       
       else if (dev->sample_bytes == 2) { //16bit integer little-endian
           int16_t si, sq;
           for( i = 0; i < read_bytes; i += (dev->num_channels * 2)) {
               memcpy(&si, fbuffer + i + (dev->channel_I * 2), 2);
               memcpy(&sq, fbuffer + i + (dev->channel_Q * 2), 2);
               if (si >= CLIP16  || si <= -CLIP16)
                   dev->overrange++;
               if (sq >= CLIP16 || sq <= -CLIP16)
                   dev->overrange++;
               int ii = si << 16;
               int qq = sq << 16;
               if (cSamples)
                   cSamples[nSamples] = ii + I * qq;
               nSamples++;
           }
       }
       
       else {
           printf("Unknown sample size for %s", dev->name);
       }
       pa_stream_drop(s);
    }
    pa_threaded_mainloop_unlock(pa_ml);
    return nSamples;
}

/*!
 * \Write outgoing samples directly to pulseaudio server.
 * \param playdev Input. Device to which to play the samples.
 * \param nSamples Input. Number of samples to play.
 * \param cSamples Input. Sample buffer to play from.
 * \param report_latency Input. 1 to update \c quisk_sound_state.latencyPlay, 0 otherwise.
 * \param volume Input. Ratio in [0,1] by which to scale the played samples.
 */
void quisk_play_pulseaudio(struct sound_dev *dev, int nSamples, complex double *cSamples, 
    int report_latency, double volume) {
    pa_stream *s = dev->handle;
    int i=0, n=0;
    void *fbuffer;
    int fbuffer_bytes = 0;
    
    if( !dev || nSamples <= 0 || dev->pulse_stream_state != PA_STREAM_READY)
        return;
    
    if (dev->cork_status)
        return;
    
    if (report_latency) {     // Report the latency, if requested.
        pa_operation *o;
        
        pa_threaded_mainloop_lock(pa_ml);
        
        if (!(o = pa_stream_update_timing_info(s, stream_timing_callback, dev))) {
            printf("pa_stream_update_timing(): %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
        }
        else {
            while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
                pa_threaded_mainloop_wait(pa_ml);
            pa_operation_unref(o);
        }
        
        pa_threaded_mainloop_unlock(pa_ml);
        
    }
    

    fbuffer = pa_xmalloc(nSamples * dev->num_channels * dev->sample_bytes);
   
    // Convert from complex data to framebuffer

    if (dev->sample_bytes == 4) {
        float fi=0.f, fq=0.f;
        for(i = 0, n = 0; n < nSamples; i += (dev->num_channels * 4), ++n) {
            fi = (volume * creal(cSamples[n])) / CLIP32;
            fq = (volume * cimag(cSamples[n])) / CLIP32;
            memcpy(fbuffer + i + (dev->channel_I * 4), &fi, 4);
            memcpy(fbuffer + i + (dev->channel_Q * 4), &fq, 4);
        }
    }

    else if (dev->sample_bytes == 2) {
        int ii, qq;
        for(i = 0, n = 0; n < nSamples; i += (dev->num_channels * 2), ++n) {
            ii = (int)(volume * creal(cSamples[n]) / 65536);
            qq = (int)(volume * cimag(cSamples[n]) / 65536);
            memcpy(fbuffer + i + (dev->channel_I * 2), &ii, 2);
            memcpy(fbuffer + i + (dev->channel_Q * 2), &qq, 2);
        }
    }

    else {
       printf("Unknown sample size for %s", dev->name);
       exit(15);
    }



    fbuffer_bytes = nSamples * dev->num_channels * dev->sample_bytes;
    pa_threaded_mainloop_lock(pa_ml);
    size_t writable = pa_stream_writable_size(s);


    if (writable > 0) {
        if ( writable > 1024*1000 ) //sanity check to prevent pa_xmalloc from crashing on monitor streams
            writable = 1024*1000;
        if (fbuffer_bytes > writable) {
            if (quisk_sound_state.verbose_pulse)
                printf("Truncating write by %u bytes\n", fbuffer_bytes - (int)writable);
            fbuffer_bytes = writable;
        }
        pa_stream_write(dev->handle, fbuffer, (size_t)fbuffer_bytes, NULL, 0, PA_SEEK_RELATIVE);
        //printf("wrote %d to %s\n", writable, dev->name);
    }
    else {
        if (quisk_sound_state.verbose_pulse)
            printf("Can't write to stream %s. Dropping %d bytes\n", dev->name, fbuffer_bytes);
    }
    
    pa_threaded_mainloop_unlock(pa_ml);
    pa_xfree(fbuffer);
    fbuffer=NULL;
}


//This is a function to sort the device list into local and remote lists.
void sort_devices(struct sound_dev **plist, struct sound_dev **pLocal, struct sound_dev **pRemote) {

    while(*plist) {
        struct sound_dev *dev = *plist++;

	dev->pulse_stream_state = PA_STREAM_UNCONNECTED;
        // Not a PulseAudio device
        if( dev->driver != DEV_DRIVER_PULSEAUDIO )
            continue;

        // Device without name: sad.
        if( !dev->name[0] )
            continue;

        // This is a remote device
        if(dev->server[0]) {
            int i;
            for(i=0;i < PA_LIST_SIZE;i++) {
                if (!(*(pRemote+i))) {
                    *(pRemote+i) = dev;
                    break;
                }
            }
        }

        // This is a local device
        else {
            int i;
            for(i=0;i < PA_LIST_SIZE; i++) {
                if (!(*(pLocal+i))) {
                    *(pLocal+i) = dev;
                    break;
                }
            }
        }
    }
}


/*!
 * \brief Search for and open PulseAudio devices.
 * 
 * \param pCapture Input/Output. Array of capture devices to search through.
 *        If a device has its \c sound_dev.driver field set to
 *        \c DEV_DRIVER_PULSEAUDIO, it will be opened for recording.
 * \param pPlayback Input/Output. Array of playback devices to search through.
 *        If a device has its \c sound_dev.driver field set to
 *        \c DEV_DRIVER_PULSEAUDIO, it will be opened for recording.
 */

//sound_dev ** pointer(address) for list of addresses
//sound_dev * fpointer(address) for list of addresses

void quisk_start_sound_pulseaudio(struct sound_dev **pCapture, struct sound_dev **pPlayback) {
    int num_pa_devices = 0;
    int i;
    //sorted lists of local and remote devices
    struct sound_dev *LocalPulseDevices[PA_LIST_SIZE] = {NULL};
    struct sound_dev *RemotePulseDevices[PA_LIST_SIZE] = {NULL};

    sort_devices(pCapture, LocalPulseDevices, RemotePulseDevices);
    sort_devices(pPlayback, LocalPulseDevices, RemotePulseDevices);
    pa_IQ_ctx = NULL;
    pa_ctx = NULL;
    pa_ml = NULL;
    pa_mlapi = NULL;
    streams_to_start = 0;
    //quisk_sound_state.verbose_pulse = 1;
    
    if (!RemotePulseDevices[0] && !LocalPulseDevices[0]) {
        if (quisk_sound_state.verbose_pulse)
            printf("No pulseaudio devices to open!\n");
        return; //nothing to open. No need to start the mainloop.
    }

    // Create a mainloop API
    pa_ml = pa_threaded_mainloop_new();
    pa_mlapi = pa_threaded_mainloop_get_api(pa_ml);

    assert(pa_signal_init(pa_mlapi) == 0);
  
    if (pa_threaded_mainloop_start(pa_ml) < 0) {
        printf("pa_mainloop_run() failed.");
	return;
    }
    else
        printf("Pulseaudio threaded mainloop started\n");
  
    pa_threaded_mainloop_lock(pa_ml);

    if (RemotePulseDevices[0]) {	//we've got at least 1 remote device
        pa_IQ_ctx = pa_context_new(pa_mlapi, "Quisk-remote");
        if (pa_context_connect(pa_IQ_ctx, quisk_sound_state.IQ_server, 0, NULL) < 0)
            printf("Failed to connect to remote Pulseaudio server\n");
        pa_context_set_state_callback(pa_IQ_ctx, state_cb, RemotePulseDevices); //send a list of remote devices to open
    }

    if (LocalPulseDevices[0]) {	//we've got at least 1 local device
        pa_ctx = pa_context_new(pa_mlapi, "Quisk-local");
        if (pa_context_connect(pa_ctx, NULL, 0, NULL) < 0)
            printf("Failed to connect to local Pulseaudio server\n");
        pa_context_set_state_callback(pa_ctx, state_cb, LocalPulseDevices);
    }


    pa_threaded_mainloop_unlock(pa_ml);

    for(i=0; LocalPulseDevices[i]; i++) {
            num_pa_devices++;
    }

    for(i=0; RemotePulseDevices[i]; i++) {
            num_pa_devices++;
    }

    if (quisk_sound_state.verbose_pulse)
        printf("Waiting for %d streams to start\n", num_pa_devices);
    
    while (streams_to_start < num_pa_devices); // wait for all the devices to open

    if (quisk_sound_state.verbose_pulse)
        printf("All streams started\n");
    

}


// Close all streams/contexts/loops and return
void quisk_close_sound_pulseaudio() {
    int i = 0;
    
    if (quisk_sound_state.verbose_pulse)
        printf("Closing Pulseaudio interfaces \n ");

    while (OpenPulseDevices[i]) {
        pa_stream_disconnect(OpenPulseDevices[i]);
        pa_stream_unref(OpenPulseDevices[i]);
        OpenPulseDevices[i] = '\0';
        i++;
    }
    

    if (quisk_sound_state.verbose_pulse)
        printf("Waiting for %d streams to disconnect\n", streams_ready);

    while(streams_ready > 0);

    if (pa_IQ_ctx) {
        pa_context_disconnect(pa_IQ_ctx);
        pa_context_unref(pa_IQ_ctx);
	pa_IQ_ctx = NULL;
    }

    if (pa_ctx) {
        pa_context_disconnect(pa_ctx);
        pa_context_unref(pa_ctx);
	pa_ctx = NULL;
    }

    if (pa_ml) {
        pa_threaded_mainloop_stop(pa_ml);
        pa_threaded_mainloop_free(pa_ml);
	pa_ml = NULL;
    }
}



// Additional bugs added by N2ADR below this point.
// Code for quisk_pa_sound_devices is based on code by Igor Brezac and Eric Connell, and Jan Newmarch.
// This should only be called when Quisk first starts, but names changed to protect the other mainloop.

// This callback gets called when our context changes state.  We really only
// care about when it's ready or if it has failed.
static void pa_names_state_cb(pa_context *c, void *userdata) {
	pa_context_state_t ctx_state;
	int *main_state = userdata;

	ctx_state = pa_context_get_state(c);
	switch  (ctx_state) {
		// There are just here for reference
		case PA_CONTEXT_UNCONNECTED:
		case PA_CONTEXT_CONNECTING:
		case PA_CONTEXT_AUTHORIZING:
		case PA_CONTEXT_SETTING_NAME:
		default:
			break;
		case PA_CONTEXT_FAILED:
		case PA_CONTEXT_TERMINATED:
			*main_state = 9;
			break;
		case PA_CONTEXT_READY:
			*main_state = 1;
			break;
	}
}

static void source_sink(const char * name, const char * descr, pa_proplist * props, PyObject * pylist) {
	const char * value;
	char buf300[300];
	PyObject * pytup;

	pytup = PyTuple_New(3);
	PyList_Append(pylist, pytup);
	PyTuple_SET_ITEM(pytup, 0, PyString_FromString(name));
	PyTuple_SET_ITEM(pytup, 1, PyString_FromString(descr));
	value = pa_proplist_gets(props, "device.api");
    
	if (value && ! strcmp(value, "alsa")) {
		snprintf(buf300, 300, "%s %s (hw:%s,%s)", pa_proplist_gets(props, "alsa.card_name"), pa_proplist_gets(props, "alsa.name"),
			 pa_proplist_gets(props, "alsa.card"), pa_proplist_gets(props, "alsa.device"));

		PyTuple_SET_ITEM(pytup, 2, PyString_FromString(buf300));
	}
	else {
		PyTuple_SET_ITEM(pytup, 2, PyString_FromString(""));
	}
}




// pa_mainloop will call this function when it's ready to tell us about a sink.
static void pa_sinklist_cb(pa_context *c, const pa_sink_info *l, int eol, void *userdata) {
    if (eol > 0)	// If eol is set to a positive number, you're at the end of the list
		return;
    source_sink(l->name, l->description, l->proplist, (PyObject *)userdata);
    if ( ! strcmp(l->name, "QuiskDigitalInput"))
        have_QuiskDigitalInput = 1;
    if ( ! strcmp(l->name, "QuiskDigitalOutput"))
        have_QuiskDigitalOutput = 1;
}

static void pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) {
    if (eol > 0)
		return;
    source_sink(l->name, l->description, l->proplist, (PyObject *)userdata);
}

static void index_callback(pa_context *c, uint32_t idx, void *userdata) {
    //printf("%u\n", idx);
}

PyObject * quisk_pa_sound_devices(PyObject * self, PyObject * args)
{	// Return a list of PulseAudio device names [pycapt, pyplay]
	PyObject * pylist, * pycapt, * pyplay;
	pa_mainloop *pa_names_ml;
	pa_mainloop_api *pa_names_mlapi;
	pa_operation *pa_op=NULL;
	pa_context *pa_names_ctx;
	int state = 0;

	if (!PyArg_ParseTuple (args, ""))
		return NULL;
	// Each pycapt and pyplay is (dev name, description, alsa name)
	pylist = PyList_New(0);		// list [pycapt, pyplay]
	pycapt = PyList_New(0);		// list of capture devices
	pyplay = PyList_New(0);		// list of play devices
	PyList_Append(pylist, pycapt);
	PyList_Append(pylist, pyplay);
    
    //printf("Starting name loop\n");

	// Create a mainloop API and connection to the default server
	pa_names_ml = pa_mainloop_new();
	pa_names_mlapi = pa_mainloop_get_api(pa_names_ml);
	pa_names_ctx = pa_context_new(pa_names_mlapi, "DeviceNames");
 
	// This function connects to the pulse server
	if (pa_context_connect(pa_names_ctx, NULL, 0, NULL) < 0) {
	   if (quisk_sound_state.verbose_pulse)
	      printf("No local daemon to connect to for show_pulse_audio_devices option\n");
	   return pylist;
	}

	// This function defines a callback so the server will tell us it's state.
	pa_context_set_state_callback(pa_names_ctx, pa_names_state_cb, &state);

	// Now we'll enter into an infinite loop until we get the data we receive or if there's an error
	while (state < 10) {
		switch (state) {
		case 0:	// We can't do anything until PA is ready
			pa_mainloop_iterate(pa_names_ml, 1, NULL);
			break;
		case 1:
			// This sends an operation to the server.  pa_sinklist_info is
			// our callback function and a pointer to our devicelist will
			// be passed to the callback.
			pa_op = pa_context_get_sink_info_list(pa_names_ctx, pa_sinklist_cb, pyplay);
			// Update state for next iteration through the loop
			state++;
			pa_mainloop_iterate(pa_names_ml, 1, NULL);
			break;
		case 2:
			// Now we wait for our operation to complete.  When it's
			// complete our pa_output_devicelist is filled out, and we move
			// along to the next state
			if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
				pa_operation_unref(pa_op);
				// Now we perform another operation to get the source
				// (input device) list just like before.
				pa_op = pa_context_get_source_info_list(pa_names_ctx, pa_sourcelist_cb, pycapt);
				// Update the state so we know what to do next
				state++;
			}
			pa_mainloop_iterate(pa_names_ml, 1, NULL);
			break;
		case 3:
			if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
				pa_operation_unref(pa_op);
				state = 4;
			}
			else
				pa_mainloop_iterate(pa_names_ml, 1, NULL);
			break;
		// The following loads modules for digital input and output if they are not present.
		case 4:
			if ( ! have_QuiskDigitalInput) {
				pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink",
					"sink_name=QuiskDigitalInput sink_properties=device.description=QuiskDigitalInput",
					index_callback, NULL);
				state = 5;
				pa_mainloop_iterate(pa_names_ml, 1, NULL);
			}
			else
				state = 6;
			break;
		case 5:
			if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
				pa_operation_unref(pa_op);
				state = 6;
			}
			else
				pa_mainloop_iterate(pa_names_ml, 1, NULL);
			break;
		case 6:
			if ( ! have_QuiskDigitalOutput) {
				pa_op = pa_context_load_module(pa_names_ctx, "module-null-sink",
					"sink_name=QuiskDigitalOutput sink_properties=device.description=QuiskDigitalOutput",
					index_callback, NULL);
				state = 7;
				pa_mainloop_iterate(pa_names_ml, 1, NULL);
			}
			else
				state = 9;
			break;
		case 7:
			if (pa_operation_get_state(pa_op) == PA_OPERATION_DONE) {
				pa_operation_unref(pa_op);
				state = 9;
			}
			else
				pa_mainloop_iterate(pa_names_ml, 1, NULL);
			break;
		case 9:				// Now we're done, clean up and disconnect and return
			pa_context_disconnect(pa_names_ctx);
			pa_context_unref(pa_names_ctx);
			pa_mainloop_free(pa_names_ml);
			state = 99;
			break;
		}
	}
    //printf("Finished with name loop\n");
	return pylist;
}
