/* Copyright (c) 2001 Miller Puckette and others.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/* this file calls Ross Bencina's and Phil Burk's Portaudio package.  It's
    the main way in for Mac OS and, with Michael Casey's help, also into
    ASIO in Windows. */


#include "m_pd.h"
#include "s_stuff.h"
#include <stdio.h>
#include <stdlib.h>
#include <portaudio.h>
#include "s_audio_pablio.h"
#ifdef MSW
#include <malloc.h>
#else
#include <alloca.h>
#endif

    /* LATER try to figure out how to handle default devices in portaudio;
    the way s_audio.c handles them isn't going to work here. */

    /* public interface declared in m_imp.h */

    /* implementation */
static PABLIO_Stream  *pa_stream;
static int pa_inchans, pa_outchans;
static float *pa_soundin, *pa_soundout;
static t_audiocallback pa_callback;

int pa_foo;

#ifndef MSW
#include <unistd.h>
#endif
static void pa_init(void)
{
    static int initialized;
    if (!initialized)
    {
#ifndef MSW
        /* Initialize PortAudio  */
        /* for some reason Pa_Initialize(0 closes file descriptor 1.
        As a workaround, dup it to another number and dup2 it back
        afterward. */
        int newfd = dup(1);
        int err = Pa_Initialize();
        if (newfd >= 0)
        {
            dup2(newfd, 1);
            close(newfd);
        }
#else
        int err = Pa_Initialize();
#endif
        if ( err != paNoError ) 
        {
            fprintf( stderr,
                "Error number %d occured initializing portaudio\n",
                err); 
            fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
            return;
        }
        initialized = 1;
    }
}

static int pa_lowlevel_callback(const void *inputBuffer,
    void *outputBuffer, unsigned long framesPerBuffer,
    const PaStreamCallbackTimeInfo *outTime, PaStreamCallbackFlags myflags, 
    void *userData)
{
    int i; 
    unsigned int j;
    float *fbuf, *fp2, *fp3, *soundiop;
    if (pa_foo)
       fprintf(stderr, "pa_lowlevel_callback\n");
    if (framesPerBuffer != DEFDACBLKSIZE)
    {
        fprintf(stderr, "ignoring buffer size %d\n", (int)framesPerBuffer);
        return 0;
    }
        if (inputBuffer != NULL)
    {
        fbuf = (float *)inputBuffer;
        soundiop = pa_soundin;
        for (i = 0, fp2 = fbuf; i < pa_inchans; i++, fp2++)
            for (j = 0, fp3 = fp2; j < framesPerBuffer; j++, fp3 += pa_inchans)
                *soundiop++ = *fp3;
    }
    else memset((void *)pa_soundin, 0,
        framesPerBuffer * pa_inchans * sizeof(float));
    memset((void *)pa_soundout, 0,
        framesPerBuffer * pa_outchans * sizeof(float));
    (*pa_callback)();
    if (outputBuffer != NULL)
    {
        fbuf = (float *)outputBuffer;
        soundiop = pa_soundout;
        for (i = 0, fp2 = fbuf; i < pa_outchans; i++, fp2++)
            for (j = 0, fp3 = fp2; j < framesPerBuffer; j++, fp3 += pa_outchans)
                *fp3 = *soundiop++;
    }
    if (pa_foo)
        fprintf(stderr, "done pa_lowlevel_callback\n"); 
    return 0;
}

PaError pa_open_callback(double sampleRate, int inchannels, int outchannels,
    int framesperbuf, int nbuffers, int indeviceno, int outdeviceno)
{
    long   bytesPerSample;
    PaError err;
    PABLIO_Stream *pastream;
    long   numFrames;
    PaStreamParameters instreamparams, outstreamparams;

    if (indeviceno < 0) 
    {
        indeviceno = Pa_GetDefaultInputDevice();
        fprintf(stderr, "using default input device number: %d\n", indeviceno);
    }
    if (outdeviceno < 0)
    {
        outdeviceno = Pa_GetDefaultOutputDevice();
        fprintf(stderr, "using default output device number: %d\n", outdeviceno);
    }
    /* fprintf(stderr, "nchan %d, flags %d, bufs %d, framesperbuf %d\n",
            nchannels, flags, nbuffers, framesperbuf); */

    /* Allocate PABLIO_Stream structure for caller. */
    pastream = (PABLIO_Stream *)malloc( sizeof(PABLIO_Stream));
    if (pastream == NULL)
        return (1);
    memset(pastream, 0, sizeof(PABLIO_Stream));

    /* Determine size of a sample. */
    bytesPerSample = Pa_GetSampleSize(paFloat32);
    if (bytesPerSample < 0)
    {
        err = (PaError) bytesPerSample;
        goto error;
    }
    pastream->insamplesPerFrame = inchannels;
    pastream->inbytesPerFrame = bytesPerSample * pastream->insamplesPerFrame;
    pastream->outsamplesPerFrame = outchannels;
    pastream->outbytesPerFrame = bytesPerSample * pastream->outsamplesPerFrame;

    numFrames = nbuffers * framesperbuf;

    instreamparams.device = indeviceno;
    instreamparams.channelCount = inchannels;
    instreamparams.sampleFormat = paFloat32;
    instreamparams.suggestedLatency = nbuffers*framesperbuf/sampleRate;
    instreamparams.hostApiSpecificStreamInfo = 0;
    
    outstreamparams.device = outdeviceno;
    outstreamparams.channelCount = outchannels;
    outstreamparams.sampleFormat = paFloat32;
    outstreamparams.suggestedLatency = nbuffers*framesperbuf/sampleRate;
    outstreamparams.hostApiSpecificStreamInfo = 0;

    err = Pa_OpenStream(
              &pastream->stream,
              (inchannels ? &instreamparams : 0),
              (outchannels ? &outstreamparams : 0),
              sampleRate,
              DEFDACBLKSIZE,
              paNoFlag,      /* portaudio will clip for us */
              pa_lowlevel_callback,
              pastream);
    if (err != paNoError)
        goto error;

    err = Pa_StartStream(pastream->stream);
    if (err != paNoError)
    {
        fprintf(stderr, "Pa_StartStream failed; closing audio stream...\n");
        CloseAudioStream( pastream );
        goto error;
    }
    pa_stream = pastream;
    return paNoError;
error:
    pa_stream = NULL;
    return err;
}

int pa_open_audio(int inchans, int outchans, int rate, t_sample *soundin,
    t_sample *soundout, int framesperbuf, int nbuffers,
    int indeviceno, int outdeviceno, t_audiocallback callbackfn)
{
    PaError err;
    int j, devno, pa_indev = 0, pa_outdev = 0;
    
    pa_callback = callbackfn;
    /* fprintf(stderr, "open callback %d\n", (callbackfn != 0)); */
    pa_init();
    /* post("in %d out %d rate %d device %d", inchans, outchans, rate, deviceno); */
    
    if (inchans > 0)
    {
        for (j = 0, devno = 0; j < Pa_GetDeviceCount(); j++)
        {
            const PaDeviceInfo *info = Pa_GetDeviceInfo(j);
            if (info->maxInputChannels > 0)
            {
                if (devno == indeviceno)
                {
                    pa_indev = j;
                    break;
                }
                devno++;
            }
        }
    }   
    
    if (outchans > 0)
    {
        for (j = 0, devno = 0; j < Pa_GetDeviceCount(); j++)
        {
            const PaDeviceInfo *info = Pa_GetDeviceInfo(j);
            if (info->maxOutputChannels > 0)
            {
                if (devno == outdeviceno)
                {
                    pa_outdev = j;
                    break;
                }
                devno++;
            }
        }
    }   

    if (sys_verbose)
    {
        post("input device %d, channels %d", pa_indev, inchans);
        post("output device %d, channels %d", pa_outdev, outchans);
        post("framesperbuf %d, nbufs %d", framesperbuf, nbuffers);
    }
    pa_inchans = inchans;
    pa_outchans = outchans;
    pa_soundin = soundin;
    pa_soundout = soundout;
    if (! inchans && !outchans)
        return(0);
    if (callbackfn)
    {
        pa_callback = callbackfn;
        err = pa_open_callback(rate, inchans, outchans,
            framesperbuf, nbuffers, pa_indev, pa_outdev);
    }
    else
    {
        err = OpenAudioStream( &pa_stream, rate, paFloat32,
            inchans, outchans, framesperbuf, nbuffers,
                pa_indev, pa_outdev);
    }
    if ( err != paNoError ) 
    {
        fprintf(stderr, "Error number %d opening portaudio stream\n",
            err); 
        fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
        Pa_Terminate();
        return (1);
    }
    else if (sys_verbose)
        post("... opened OK.");
    return (0);
}

void pa_close_audio( void)
{
    /* fprintf(stderr, "close\n"); */
    if (pa_inchans || pa_outchans)
        CloseAudioStream( pa_stream );
    pa_inchans = pa_outchans = 0;
}

int pa_send_dacs(void)
{
    unsigned int framesize = (sizeof(float) * DEFDACBLKSIZE) *
        (pa_inchans > pa_outchans ? pa_inchans:pa_outchans);
    float *samples, *fp1, *fp2;
    int i, j;
    double timebefore;
    
    
    samples = alloca(framesize);
    
    timebefore = sys_getrealtime();
    if ((pa_inchans && GetAudioStreamReadable(pa_stream) < DEFDACBLKSIZE) ||
        (pa_outchans && GetAudioStreamWriteable(pa_stream) < DEFDACBLKSIZE))
    {
        if (pa_inchans && pa_outchans)
        {
            int synced = 0;
            while (GetAudioStreamWriteable(pa_stream) > 2*DEFDACBLKSIZE)
            {
                for (j = 0; j < pa_outchans; j++)
                    for (i = 0, fp2 = samples + j; i < DEFDACBLKSIZE; i++,
                        fp2 += pa_outchans)
                {
                    *fp2 = 0;
                }
                synced = 1;
                WriteAudioStream(pa_stream, samples, DEFDACBLKSIZE);
            }
            while (GetAudioStreamReadable(pa_stream) > 2*DEFDACBLKSIZE)
            {
                synced = 1;
                ReadAudioStream(pa_stream, samples, DEFDACBLKSIZE);
            }
            /* if (synced)
                post("sync"); */
        }
        return (SENDDACS_NO);
    }
    if (pa_inchans)
    {
        ReadAudioStream(pa_stream, samples, DEFDACBLKSIZE);
        for (j = 0, fp1 = pa_soundin; j < pa_inchans; j++, fp1 += DEFDACBLKSIZE)
            for (i = 0, fp2 = samples + j; i < DEFDACBLKSIZE; i++,
                fp2 += pa_inchans)
        {
            fp1[i] = *fp2;
        }
    }
#if 0 
        {
                static int nread;
                if (nread == 0)
                {
                        post("it's %f %f %f %f",
                        pa_soundin[0], pa_soundin[1], pa_soundin[2],                                    pa_soundin[3]);
                        nread = 1000;
                }
                nread--;
        }
#endif
    if (pa_outchans)
    {
        for (j = 0, fp1 = pa_soundout; j < pa_outchans; j++,
            fp1 += DEFDACBLKSIZE)
                for (i = 0, fp2 = samples + j; i < DEFDACBLKSIZE; i++,
                    fp2 += pa_outchans)
        {
            *fp2 = fp1[i];
            fp1[i] = 0;
        }
        WriteAudioStream(pa_stream, samples, DEFDACBLKSIZE);
    }

    if (sys_getrealtime() > timebefore + 0.002)
    {
        /* post("slept"); */
        return (SENDDACS_SLEPT);
    }
    else return (SENDDACS_YES);
}


void pa_listdevs(void)     /* lifted from pa_devs.c in portaudio */
{
    int      i,j;
    int      numDevices;
    const    PaDeviceInfo *pdi;
    PaError  err;
    pa_init();
    numDevices = Pa_GetDeviceCount();
    if( numDevices < 0 )
    {
        fprintf(stderr, "ERROR: Pa_GetDeviceCount returned %d\n", numDevices );
        err = numDevices;
        goto error;
    }
    fprintf(stderr, "Audio Devices:\n");
    for( i=0; i<numDevices; i++ )
    {
        pdi = Pa_GetDeviceInfo( i );
        fprintf(stderr, "device %d:", i+1 );
        fprintf(stderr, " %s;", pdi->name );
        fprintf(stderr, "%d inputs, ", pdi->maxInputChannels  );
        fprintf(stderr, "%d outputs", pdi->maxOutputChannels  );
        if ( i == Pa_GetDefaultInputDevice() )
            fprintf(stderr, " (Default Input)");
        if ( i == Pa_GetDefaultOutputDevice() )
            fprintf(stderr, " (Default Output)");
        fprintf(stderr, "\n");
    }

    fprintf(stderr, "\n");
    return;

error:
    fprintf( stderr, "An error occured while using the portaudio stream\n" ); 
    fprintf( stderr, "Error number: %d\n", err );
    fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );

}

    /* scanning for devices */
void pa_getdevs(char *indevlist, int *nindevs,
    char *outdevlist, int *noutdevs, int *canmulti, 
        int maxndev, int devdescsize)
{
    int i, nin = 0, nout = 0, ndev;
    *canmulti = 1;  /* one dev each for input and output */

    pa_init();
    ndev = Pa_GetDeviceCount();
    for (i = 0; i < ndev; i++)
    {
        const PaDeviceInfo *pdi = Pa_GetDeviceInfo(i);
        if (pdi->maxInputChannels > 0 && nin < maxndev)
        {
            sprintf(indevlist + nin * devdescsize, "(%d)%s",
                pdi->hostApi,pdi->name);
            /* strcpy(indevlist + nin * devdescsize, pdi->name); */
            nin++;
        }
        if (pdi->maxOutputChannels > 0 && nout < maxndev)
        {
            sprintf(outdevlist + nout * devdescsize, "(%d)%s",
                pdi->hostApi,pdi->name);
            /* strcpy(outdevlist + nout * devdescsize, pdi->name); */
            nout++;
        }
    }
    *nindevs = nin;
    *noutdevs = nout;
}