/************************************************************************

	IMPORTANT NOTE : this file contains two clearly delimited sections :
	the ARCHITECTURE section (in two parts) and the USER section. Each section
	is governed by its own copyright and license. Please check individually
	each section for license and copyright information.
*************************************************************************/

/*******************BEGIN ARCHITECTURE SECTION (part 1/2)****************/

/************************************************************************
    FAUST Architecture File
	Copyright (C) 2003-2011 GRAME, Centre National de Creation Musicale
    ---------------------------------------------------------------------
    This Architecture section 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.

    This program 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/>.

	EXCEPTION : As a special exception, you may create a larger work
	that contains this FAUST architecture section and distribute
	that work under terms of your choice, so long as this FAUST
	architecture section is not modified.


 ************************************************************************
 ************************************************************************/


#ifndef __portaudio_dsp__
#define __portaudio_dsp__

#include <stdio.h>
#include <assert.h>
#include <portaudio.h>

#include "audio.h"
#include "dsp.h"

#define max(x,y) (((x)>(y)) ? (x) : (y))

static int audioCallback(const void *ibuf, void *obuf, unsigned long frames, const PaStreamCallbackTimeInfo*,  PaStreamCallbackFlags, void * drv);
//----------------------------------------------------------------------------
static void pa_error(int err)
{
	if (err != paNoError) {
		printf(  "PortAudio error: %s\n", Pa_GetErrorText( err ) );
	}
}

/******************************************************************************
*******************************************************************************

							PORT AUDIO INTERFACE

*******************************************************************************
*******************************************************************************/
class portaudio : public audio {

        dsp*			fDsp;
        PaStream*		fAudioStream;
        long fSampleRate, fBufferSize;
        //----------------------------------------------------------------------------
        // 	number of physical input and output channels of the PA device
        //----------------------------------------------------------------------------
        int		fDevNumInChans;
        int		fDevNumOutChans;

        //----------------------------------------------------------------------------
        // tables of noninterleaved input and output channels for FAUST
        //----------------------------------------------------------------------------
        float* 	fInChannel[256];
        float* 	fOutChannel[256];

        //----------------------------------------------------------------------------
        // allocated the noninterleaved input and output channels for FAUST
        //----------------------------------------------------------------------------
        void allocChannels (int size, int numInChan, int numOutChan)
        {
            assert (numInChan < 256);
            assert (numOutChan < 256);

            for (int i = 0; i < numInChan; i++) {
                fInChannel[i] = (float*) calloc (size, sizeof(float));
                for (int j = 0; j < size; j++)
                    fInChannel[i][j] = 0.0;
            }

            for (int i = 0; i < numOutChan; i++) {
                fOutChannel[i] = (float*) calloc (size, sizeof(float));
                for (int j = 0; j < size; j++)
                    fOutChannel[i][j] = 0.0;
            }
        }

	public:
			 portaudio(long srate, long bsize) : fDsp(0), fAudioStream(0),
				fSampleRate(srate), fBufferSize(bsize), fDevNumInChans(0), fDevNumOutChans(0) {}
	virtual ~portaudio() { stop(); }

	virtual bool init(const char* name, dsp* DSP){
		fDsp = DSP;
		pa_error(Pa_Initialize());

		const PaDeviceInfo*	idev = Pa_GetDeviceInfo(Pa_GetDefaultInputDevice());
		const PaDeviceInfo*	odev = Pa_GetDeviceInfo(Pa_GetDefaultOutputDevice());

		fDevNumInChans = (fDsp->getNumInputs() > 0) ? idev->maxInputChannels : 0 ;
		fDevNumOutChans = (fDsp->getNumOutputs() > 0) ? odev->maxOutputChannels : 0;

		PaStreamParameters inputParameters;
		PaStreamParameters outputParameters;

		inputParameters.device = Pa_GetDefaultInputDevice();
		inputParameters.sampleFormat = paFloat32;
		inputParameters.channelCount = fDevNumInChans;
		inputParameters.hostApiSpecificStreamInfo = 0;

		outputParameters.device = Pa_GetDefaultOutputDevice();
		outputParameters.sampleFormat = paFloat32;
		outputParameters.channelCount = fDevNumOutChans;
		outputParameters.hostApiSpecificStreamInfo = 0;

		PaError err;
		if ((err = Pa_IsFormatSupported(
			((fDevNumInChans > 0) ? &inputParameters : 0),
			((fDevNumOutChans > 0) ? &outputParameters : 0), fSampleRate)) != 0) {
			printf("stream format is not supported err = %d\n", err);
			return false;
		}

		allocChannels(fBufferSize, max(fDevNumInChans, fDsp->getNumInputs()), max(fDevNumOutChans, fDsp->getNumOutputs()));
		fDsp->init(fSampleRate);
		return true;
	}

	virtual bool start() {
		pa_error(Pa_OpenDefaultStream(&fAudioStream, fDevNumInChans, fDevNumOutChans, paFloat32, fSampleRate, fBufferSize, audioCallback, this));
		Pa_StartStream(fAudioStream);
		return true;
	}

	virtual void stop() {
		if (fAudioStream) {
			Pa_StopStream (fAudioStream);
			Pa_CloseStream(fAudioStream);
			fAudioStream = 0;
		}
	}

	int processAudio(const float *ibuf, float *obuf, unsigned long frames) {
		const float* fInputBuffer = ibuf;
		float* fOutputBuffer = obuf;

		// split input samples
		for (unsigned long s = 0; s < frames; s++) {
			for (int c = 0; c < fDevNumInChans; c++) {
				fInChannel[c][s] = fInputBuffer[c + s*fDevNumInChans];
			}
		}

		// process samples
		fDsp->compute(frames, fInChannel, fOutChannel);

		// merge output samples
		for (unsigned long s = 0; s < frames; s++) {
			for (int c = 0; c < fDevNumOutChans; c++) {
				fOutputBuffer[c + s*fDevNumOutChans] = fOutChannel[c][s];
			}
		}
		return 0;
	}
};

//----------------------------------------------------------------------------
// Port Audio Callback
//----------------------------------------------------------------------------
static int audioCallback(const void *ibuf, void *obuf, unsigned long frames, const PaStreamCallbackTimeInfo*,  PaStreamCallbackFlags, void * drv)
{
	portaudio * pa = (portaudio*) drv;
	return pa->processAudio ((const float*)ibuf, (float*)obuf, frames);
}

#endif
