//menu system and help from Yu Morimoto
//granulation synthesis on small real time input samples
//based on pseudocode from MusicDSP.org http://musicdsp.org/archive.php?classid=4#154
//it will take the first input on the default input device and output it to the
//default output devices first n channels, where n is defined by #define OUTPUT_CHANNELS
//This will delay up to one second, as it will create a delay buffer the size of the samplerate

#include <stdio.h>
#include <math.h>
#include "portaudio.h"
#include <stdlib.h>

#define SAMPLE_RATE   (44100)
#define OUTPUT_CHANNELS (2)

typedef struct
{
	float *Buffer;
	int outputIndex;
}
gran;

// Device Info subroutine shamelessly copied from PortAudio test suite from Yu Morimoto
void devInfo(void)
{
    int      i,j;
    int      numDevices;
    const    PaDeviceInfo *pdi;
    PaError  err;
    numDevices = Pa_CountDevices();
    if( numDevices < 0 )
    {
        printf("ERROR: Pa_CountDevices returned 0x%x\n", numDevices );
        err = numDevices;
        goto error;
    }
    printf("Number of devices = %d\n", numDevices );
    for( i=0; i<numDevices; i++ )
    {
        pdi = Pa_GetDeviceInfo( i );
        printf("---------------------------------------------- #%d", i );
        if( i == Pa_GetDefaultInputDeviceID() ) printf(" DefaultInput");
        if( i == Pa_GetDefaultOutputDeviceID() ) printf(" DefaultOutput");
        printf("\nName         = %s\n", pdi->name );
        printf("Max Inputs   = %d", pdi->maxInputChannels  );
        printf(", Max Outputs = %d\n", pdi->maxOutputChannels  );
        if( pdi->numSampleRates == -1 )
        {
            printf("Sample Rate Range = %f to %f\n", pdi->sampleRates[0], pdi->sampleRates[1] );
        }
        else
        {
            printf("Sample Rates =");
            for( j=0; j<pdi->numSampleRates; j++ )
            {
                printf(" %8.2f,", pdi->sampleRates[j] );
            }
            printf("\n");
        }
        printf("Native Sample Formats = ");
        if( pdi->nativeSampleFormats & paInt8 )        printf("paInt8, ");
        if( pdi->nativeSampleFormats & paUInt8 )       printf("paUInt8, ");
        if( pdi->nativeSampleFormats & paInt16 )       printf("paInt16, ");
        if( pdi->nativeSampleFormats & paInt32 )       printf("paInt32, ");
        if( pdi->nativeSampleFormats & paFloat32 )     printf("paFloat32, ");
        if( pdi->nativeSampleFormats & paInt24 )       printf("paInt24, ");
        if( pdi->nativeSampleFormats & paPackedInt24 ) printf("paPackedInt24, ");
        printf("\n");
    }

    printf("----------------------------------------------\n");
	return;
error:
    Pa_Terminate();
    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 ) );
}

static int granulate( void *inputBuffer, void *outputBuffer,
                           unsigned long framesPerBuffer,
                           PaTimestamp outTime, void *userData )
{
    // Defining data used for struct
    gran *data = (gran*)userData;
    float *out = (float*)outputBuffer;
    unsigned int i;
    (void) outTime; // Prevent unused variable warnings from Yu Morimoto
    float *input = (float*)inputBuffer;
	int aNum = rand()%(SAMPLE_RATE - framesPerBuffer);
	
	int j = 0;
    for( i=0; i<framesPerBuffer; i++ )
    {
		*out++ = data->Buffer[aNum];
		*out++ = data->Buffer[aNum];
		aNum++;
		data->Buffer[data->outputIndex++] = *input++;
		if (data->outputIndex >= SAMPLE_RATE)
			data->outputIndex = 0;
	}
    return 0;
}

static gran data;
int main(void);
int main(void)
{
    PortAudioStream *stream;
    PaError err;
	
	//Welcome Message from Yu Morimoto
    printf("PortAudio Test: RT Delay (with bonus stereo effect!).\n");
	printf("Type 'q' and hit return to quit\nType 'd' and hit return to get device info\n");
	
	data.Buffer = (float*) malloc(SAMPLE_RATE*sizeof(float));
	data.inputIndex = 0;
	data.outputIndex = 0;
	
    // Pa Initialization
    err = Pa_Initialize();
    if( err != paNoError ) goto error;
	
    
	// Setting up audio I/O stream
    err = Pa_OpenDefaultStream(
              &stream,
              1,              // number of input channels
              OUTPUT_CHANNELS,              // name of stereo output 
              paFloat32,      /* 32 bit floating point output */
              SAMPLE_RATE,
              1024,            // frames per buffer 
              0,              // zero buffers = default minimum */
              granulate,
              &data );
    if( err != paNoError ) goto error;
    err = Pa_StartStream( stream );
    if( err != paNoError ) goto error;
	
	//MENU SYSTEM from Yu Morimoto
	int keyInput = 0;
	do{
		keyInput = getchar();
		if (keyInput == 'd')
			devInfo();
	}
	while (keyInput != 'q');
	
    err = Pa_StopStream( stream );
    if( err != paNoError ) goto error;
    err = Pa_CloseStream( stream );
    if( err != paNoError ) goto error;
    Pa_Terminate();
    printf("Test finished.\n");
    return err;
error:
    Pa_Terminate();
    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 ) );
    return err;
}
