/***************************************************************************
 *   Copyright (C) 2004 by Predrag Viceic                                  *
 *   viceic@net2000.ch                                             *
 *                                                                         *
 *   This program 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 2 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

 /*
    JACK layer taken from AlsaModularSynth project by Matthias Nagorni
    http://alsamodular.sourceforge.net/

    /dev/dsp layer taken from libsndfile examples by Erik de Castro Lopo
    http://www.mega-nerd.com/libsndfile/
*/

#include "soundplayer.h"
#include <sndfile.h>
#include <qconfig.h>
#include <qcanvas.h>
#include "drawsoundwidget.h"
#include <math.h>
#include <stdlib.h>
#ifndef max
#define max(a,b)            (((a) > (b)) ? (a) : (b))
#endif

#ifndef min
#define min(a,b)            (((a) < (b)) ? (a) : (b))
#endif

#ifdef Q_BYTE_ORDER
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
#define NOT_NATIVE_ENDIAN 0
#else
#define NOT_NATIVE_ENDIAN 1
#endif
#endif

/* borrowed from glib2 */
#define SHORT_SWAP_LE_BE(val)	((short) ( \
    (short) ((short) (val) >> 8) |	\
    (short) ((short) (val) << 8)))

static void swap_endian(short *data, int length)
{
	int i;
	for (i = 0; i < length; i += 1, data++)
    		*data = SHORT_SWAP_LE_BE(*data);
}


SoundPlayer::SoundPlayer()
 : QThread()
{
	playing=false;
	playingOnce=FALSE;
        position=0;
        soundManager=0;
        buffer_len=1024;
        bufferLR_S=new short[buffer_len*2];
        bufferLR_F=new float[buffer_len*2];
        bufferPointerL=0;
        bufferPointerR=0;
        bufferPointersLR=new float* [2];
        #ifdef HAS_OSS
            devdsp_audio_device=0;
        #endif
        leftLoop=0;
        rightLoop=0;
        output=OUTPUT_NONE;
        playFromFileV=FALSE;
        previewFile=0;
        savedLeftLoop=ULONG_MAX;
        savedRightLoop=ULONG_MAX;
        closeV=FALSE;
        #ifdef HAS_PORTAUDIO
            portaudio_stream=0;
        #endif
        #ifdef HAS_ALSA
            alsa_rate = 44100;
            alsa_periods = 2;
            alsa_periodsize = 4096;
            alsa_pcm_name = strdup("plughw:0,0");
        #endif
        processedPositions=0;
}


SoundPlayer::~SoundPlayer()
{
    zaparr(bufferLR_S);
    zaparr(bufferLR_F);
    #ifdef HAS_OSS
        if(output==OUTPUT_DEVDSP) ::close (devdsp_audio_device) ;
    #endif
    #ifdef HAS_JACK
        if(output==OUTPUT_JACK) closeJack();
    #endif
    #ifdef HAS_PORTAUDIO
        if(output==OUTPUT_PORTAUDIO) closePortaudio();
    #endif
    #ifdef HAS_ALSA
        if(output==OUTPUT_ALSA && alsa_pcm_handle!=0) snd_pcm_close(alsa_pcm_handle);
    #endif
}
#ifdef HAS_OSS
void SoundPlayer::playDevDsp(){
        while(position<rightLoop && playing){
            if(position+buffer_len<rightLoop){
                soundManager->exportMultiplexedShortBuffer(bufferLR_S, position,position+buffer_len);
                    if (NOT_NATIVE_ENDIAN) swap_endian(bufferLR_S, buffer_len*2);
                    write (devdsp_audio_device,bufferLR_S,buffer_len*2* sizeof (short)) ;
                    position+=buffer_len;
            }else if (position<rightLoop){
                soundManager->exportMultiplexedShortBuffer(bufferLR_S, position,
                        rightLoop);
                    if (NOT_NATIVE_ENDIAN) swap_endian(bufferLR_S, (rightLoop-position)*2);
                    write (devdsp_audio_device,bufferLR_S,(rightLoop-position)*2* sizeof (short));
                    position=rightLoop;
            }else position=rightLoop;
            positionChanged(position);
        }
        if(playingOnce){
            this->stopPlay();
            playingOnce=FALSE;
            resetLoop();
        }
}
#endif
void SoundPlayer::stopPlay(){

        if(playing){
            playing=false;
            positionChanged(leftLoop);
            #ifdef HAS_PORTAUDIO
                if(output==OUTPUT_PORTAUDIO) stopPAStream();
            #endif
            #ifdef HAS_ALSA
                if(output==OUTPUT_ALSA) snd_pcm_drop(alsa_pcm_handle);
            #endif
        }
}





/*!
    \fn SoundPlayer::setSoundmanager(SoundManager *sm)
 */
void SoundPlayer::setSoundmanager(SoundManager *sm)
{
    soundManager=sm;
    initLoop();
}


void SoundPlayer::positionChanged(unsigned long _pos)
{
    /*
    if(processedPositions>=1000){
        processedPositions=0;
        mainApplication->processEvents(1);
    }
    */
    processedPositions++;
    position=_pos;
    QObject* soundMonitor;
    for ( soundMonitor = soundMonitors.first(); soundMonitor; soundMonitor = soundMonitors.next() ){
        PlayPositionChanged* myevent=new PlayPositionChanged();
        myevent->setPosition(position);
        if(playFromFileV)
            myevent->setPlaying(TRUE);
        else myevent->setPlaying(playing);
        QApplication::postEvent(soundMonitor,myevent);
    }

}
#ifdef HAS_OSS
/*!
    \fn SoundPlayer::connect_devdsp()
 */
int SoundPlayer::connect_devdsp()
{
        fprintf (stderr, "Querying /dev/dsp.\n");
        //devdsp_audio_device = linux_open_dsp_device (soundManager->getChannels(),
        //                                                                soundManager->getRate()) ;
        devdsp_audio_device = linux_open_dsp_device (2,44100) ;
        if(devdsp_audio_device){
            output=OUTPUT_DEVDSP;
            fprintf (stderr, "Connected to /dev/dsp.\n");
            return 0;
            bufferLR_S=new short[buffer_len*2];
        }else return 1;
}


int SoundPlayer::linux_open_dsp_device (int channels, int srate)
{	int fd, stereo, temp, error ;

        if ((fd = open ("/dev/dsp", O_WRONLY, 0)) <0){
            perror("linux_open_dsp_device : open ") ;
            return 0;
        }

        stereo = 1 ;
        if (ioctl (fd, SNDCTL_DSP_STEREO, &stereo) <0){
                perror("linux_open_dsp_device : stereo ") ;
                return 0;
        }else std::cout<<"stereo: "<<stereo<<"\n";

        if (ioctl (fd, SNDCTL_DSP_RESET, 0)){
                perror ("linux_open_dsp_device : reset ") ;
                return 0;
        }
        temp = 16 ;
        if ((error = ioctl (fd, SOUND_PCM_WRITE_BITS, &temp)) != 0){
            perror ("linux_open_dsp_device : bitwidth ") ;
            return 0;
        }else std::cout<<"bitwidth: "<<temp<<"\n";

        if ((error = ioctl (fd, SOUND_PCM_WRITE_CHANNELS, &channels)) != 0){
            perror ("linux_open_dsp_device : channels ") ;
            return 0;
        }else std::cout<<"channels: "<<channels<<"\n";

        if ((error = ioctl (fd, SOUND_PCM_WRITE_RATE, &srate)) != 0){
            perror ("linux_open_dsp_device : sample rate ") ;
            return 0;
        }else std::cout<<"sample rate: "<<srate<<"\n";

        if ((error = ioctl (fd, SNDCTL_DSP_SYNC, 0)) != 0){
            perror ("linux_open_dsp_device : sync ") ;
            return 0;
        }

        return 	fd ;
} /* linux_open_dsp_device */
#endif

/*!
    \fn SoundPlayer::run
 */
void SoundPlayer::run()
{
    #ifdef HAS_OSS
    if(output==OUTPUT_DEVDSP){
        while(!closeV){
            if(playing){
                playDevDsp();
                if(playing) position=leftLoop;
            }else if(playFromFileV){
                if(playing) stopPlay();
                playFromFileThreaded();
            }else{
                msleep(100);
            }
        }
    }
    #endif
    #ifdef HAS_ALSA
    if(output==OUTPUT_ALSA){
        while(!closeV){
            if(playing){
                playALSA();
                if(playing) position=leftLoop;
                snd_pcm_prepare(alsa_pcm_handle);
            }else if(playFromFileV){
                if(playing) stopPlay();
                playFromFileThreaded();
            }else{
                msleep(100);
            }
        }
    }
    #endif
}


/*!
    \fn SoundPlayer::exit(int)
 */
void SoundPlayer::exit(int)
{
    close();
    while(!finished());
}




/*!
    \fn SoundPlayer::isPlaying()
 */
bool SoundPlayer::isPlaying()
{
    return playing;
}


/*!
    \fn SoundPlayer::setPlaying()
 */
void SoundPlayer::setPlaying(bool _pl)
{
    playing=_pl;
    #ifdef HAS_PORTAUDIO
        if(output==OUTPUT_PORTAUDIO && playing) startPAStream();
        else if(output==OUTPUT_PORTAUDIO && !playing) stopPAStream();
    #endif
}


/*!
    \fn SoundPlayer::setLoop(long,long)
 */
void SoundPlayer::setLoop(long l,long r)
{
    leftLoop=l<0?0:l;
    rightLoop=r>=soundManager->getFrames()?soundManager->getFrames()-1:r;
    position=leftLoop;
    //cout<<"position set in setLoop("<<l<<","<<r<<") at "<<position<<endl;
}

/*!
    \fn SoundPlayer::setLoop(long,long)
 */
void SoundPlayer::setOneTimeLoop(long l,long r)
{
    savedLeftLoop=leftLoop;
    savedRightLoop=rightLoop;
    leftLoop=l;
    rightLoop=r;
    position=leftLoop;
    //cout<<"position set in setOneTimeLoop("<<l<<","<<r<<") at "<<position<<endl;
}


/*!
    \fn SoundPlayer::setPlayingOnce(bool)
 */
void SoundPlayer::setPlayingOnce(bool po)
{
    playingOnce=po;
}





#ifdef HAS_JACK
int SoundPlayer::closeJack ()
{
    jack_deactivate (jack_handle);
    for (int i = 0; i < PLAY_PORTS; i++) jack_port_unregister(jack_handle, jack_out[i]);
    jack_client_close (jack_handle);
    return 0;
}


/*!
    \fn SoundPlayer::connect_jack()
 */
int SoundPlayer::connect_jack()
{


    std::cout<<"Querying Jack...\n";
    QString jackName="Freecycle "+QString::number(QDateTime::currentDateTime().toTime_t());
    if ((jack_handle = jack_client_new (jackName.latin1())) == 0){
        fprintf (stderr, "Can't connect to JACK\n");
        return 1;
    }

    jack_set_process_callback (jack_handle, jack_static_callback, (void *)this);

    //rate = jack_get_sample_rate (jack_handle);
    //periodsize = MAXIMUM_PERIODSIZE;


    jack_out [0] = jack_port_register (jack_handle, "out L", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    jack_out [1] = jack_port_register (jack_handle, "out R", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);


    if (jack_activate (jack_handle))
    {
        fprintf(stderr, "Can't activate JACK");
        return 1;
    }

    const char** ports=jack_get_ports (jack_handle,NULL, NULL, JackPortIsInput);
    if(ports[0])
        jack_connect(jack_handle, jack_port_name(jack_out[0]), ports[0]);
    if(ports[1])
        jack_connect(jack_handle, jack_port_name(jack_out[1]), ports[1]);

    fprintf (stderr, "Connected to JACK.\n");
    output=OUTPUT_JACK;
    return 0;
}



int SoundPlayer::jack_static_callback (jack_nframes_t nframes, void *arg)
{
    return ((SoundPlayer *) arg)->jack_callback (nframes);
}

int SoundPlayer::jack_callback (jack_nframes_t nframes)
{
    jack_default_audio_sample_t * p1 =
                 (jack_default_audio_sample_t *)(jack_port_get_buffer (jack_out [0], nframes));
    jack_default_audio_sample_t * p2 =
                 (jack_default_audio_sample_t *)(jack_port_get_buffer (jack_out [1], nframes));
    if(!(p1 && p2)){
        cout<<"Jack buffers unavailable!!\n";
        resetLoop();
        playing=FALSE;
        playingOnce=FALSE;
        return 0;
    }
    if(nframes>buffer_len) setJackBuffersLen(nframes);
    if(playFromFileV){
        if (!previewFile) return (setJackNotPlaying(p1,nframes) || setJackNotPlaying(p2,nframes));
        float* buffL=new float[nframes];
        float* buffR=new float[nframes];
        float* buff=new float[nframes*2];
        unsigned long length=sfinfoForPreview.frames;
        if(position+buffer_len<length){
            sf_readf_float(previewFile, buff,(sf_count_t)(nframes));
            int count=0;
            for (uint i=0;i<nframes;i++){
                buffL[i]=buff[count++];
                buffR[i]=buff[count++];
            }
            if (buffL) memcpy (p1, buffL, sizeof(jack_default_audio_sample_t) * nframes);
            else   setJackNotPlaying(p1,nframes);

            if (buffR) memcpy (p2, buffR, sizeof(jack_default_audio_sample_t) * nframes);
            else   setJackNotPlaying(p2,nframes);

            position+=nframes;
            positionChanged(position);
        }else stopPlayFromFile();
        zaparr(buffL);
        zaparr(buffR);
        zaparr(buff);
    }else{
        if(!soundManager || !soundManager->hasSound()) 
            return (setJackNotPlaying(p1,nframes) || setJackNotPlaying(p2,nframes));
        if(isPlaying()){
            Q_ASSERT(position<soundManager->getFrames());
            bufferPointerL=&soundManager->getLeftChannel(SoundHolder::FOR_OUTPUT)[position];
            bufferPointerR=&soundManager->getRightChannel(SoundHolder::FOR_OUTPUT)[position];
            if(position+nframes<getRightLoop()){
                memcpy (p1, bufferPointerL, sizeof(jack_default_audio_sample_t) * nframes);
                memcpy (p2, bufferPointerR, sizeof(jack_default_audio_sample_t) * nframes);
                position+=nframes;
            }else{
                    memcpy (p1, bufferPointerL,
                        sizeof(jack_default_audio_sample_t) * (getRightLoop()-position));
                    memcpy (p2, bufferPointerR,
                        sizeof(jack_default_audio_sample_t) * (getRightLoop()-position));
                    if(playingOnce){
                        setJackNotPlaying(&(p1[getRightLoop()-position]),nframes-(getRightLoop()-position));
                        setJackNotPlaying(&(p2[getRightLoop()-position]),nframes-(getRightLoop()-position));
                        playing=FALSE;//this->stopPlay();
                        playingOnce=FALSE;
                        position=leftLoop;
                        positionChanged(position);
                        resetLoop();
                        return 0;
                    }else{
                        memcpy (&p1[getRightLoop()-position],
                            &soundManager->getLeftChannel()[getLeftLoop()],
                            sizeof(jack_default_audio_sample_t) * (nframes-(getRightLoop()-position)));
                        memcpy (&p2[getRightLoop()-position],
                            &soundManager->getRightChannel()[getLeftLoop()],
                            sizeof(jack_default_audio_sample_t) * (nframes-(getRightLoop()-position)));
                        position=getLeftLoop()+(nframes-(getRightLoop()-position));
                    }
            }
            positionChanged(position);
        }else{
            setJackNotPlaying(p1,nframes);
            setJackNotPlaying(p2,nframes);
        }
    }
    return 0;
}


/*!
    \fn SoundPlayer::setJackNotPlaying()
 */
int SoundPlayer::setJackNotPlaying(jack_default_audio_sample_t * p, jack_nframes_t nframes)
{
    memset (p, 0, sizeof(jack_default_audio_sample_t) * nframes);
    return 0;
}

#endif

/*!
    \fn SoundPlayer::init()
 */
void SoundPlayer::init()
{

    if(
       #ifdef HAS_JACK
        connect_jack() &&
       #endif
       #ifdef HAS_ALSA 
        connect_alsa() &&
       #endif
       #ifdef HAS_PORTAUDIO 
        connect_portaudio() &&
       #endif
       #ifdef HAS_OSS
        connect_devdsp() &&
       #endif
        true){
        output=OUTPUT_NONE;
        std::cout<<"Oh, anyways we need no sound..Aren't we?\n";
    }
}


/*!
    \fn SoundPlayer::setJackBuffersLen(int)
 */
void SoundPlayer::setJackBuffersLen(int bl)
{
    buffer_len=bl;
}

int SoundPlayer::getOutputType(){
    return output;
}

void SoundPlayer::cropped(unsigned long start, unsigned long end){
    unsigned long temppposition=0;
    if(position<start) temppposition=0;
    else if(position>end) temppposition=soundManager->getFrames()-1;
    else temppposition=position-start;
    positionChanged(temppposition);
    if((leftLoop<start && rightLoop<start) ||
        (leftLoop>end && rightLoop>end)){
        leftLoop=0;
        rightLoop=soundManager->getFrames()-1;
    }else{
        if(leftLoop<start) leftLoop=0;
        else leftLoop=leftLoop-start;

        if(rightLoop>end) rightLoop=soundManager->getFrames()-1;
        else rightLoop=rightLoop-start;
    }
}




/*!
    \fn SoundPlayer::addSoundMonitor(SoundMonitor*)
 */
void SoundPlayer::addSoundMonitor(QObject* sm)
{
    if (!soundMonitors.containsRef(sm))
        soundMonitors.append(sm);
}


/*!
    \fn SoundPlayer::updateLoop()
 */
void SoundPlayer::initLoop()
{
    setLoop(0,soundManager->getFrames()-1);
}

/*!
    \fn SoundPlayer::playFromFile(QString path)
 */
void SoundPlayer::playFromFile(QString path)
{
    playFromFileV=TRUE;
    playFromFilePath=path;
        previewFile= sf_open(path, SFM_READ, &sfinfoForPreview);
    #ifdef HAS_ALSA
        if(output==OUTPUT_ALSA) snd_pcm_prepare(alsa_pcm_handle);
    #endif
    #ifdef HAS_PORTAUDIO
        if(output==OUTPUT_PORTAUDIO) startPAStream();
    #endif
}

/*!
    \fn SoundPlayer::playFromFile(QString path)
 */
void SoundPlayer::playFromFileThreaded()
{
    unsigned long length=sfinfoForPreview.frames;
    #ifdef HAS_OSS
    if(output==OUTPUT_DEVDSP){
            short* buff=new short[buffer_len*2];
            while(position<length && playFromFileV){
                if(position+buffer_len<=length){
                        sf_readf_short(previewFile, buff,(sf_count_t)(buffer_len));
                        if (NOT_NATIVE_ENDIAN) swap_endian(buff, buffer_len*2);
                        write (devdsp_audio_device,buff,buffer_len*2* sizeof (short)) ;
                    }
                    position+=buffer_len;
                    if(playFromFileV) positionChanged(position);
            }
            zaparr(buff);
           if(playFromFileV) stopPlayFromFile();
            else playFromFileV=FALSE;
        }
    #endif
    #ifdef HAS_ALSA
    if(output==OUTPUT_ALSA){
            bool pcm_return=0;
            float* buff=new float[alsa_periodsize];
            float* buffL=new float[alsa_frames];
            float* buffR=new float[alsa_frames];
            float** buffLR=new float*[2];
            while(position<length && playFromFileV){
                if(position+alsa_frames<=length){
                        sf_readf_float(previewFile, buff,(sf_count_t)(alsa_frames));
                        int count=0;
                        for (int i=0;i<alsa_frames;i++){
                            buffL[i]=buff[count++];
                            buffR[i]=buff[count++];
                        }
                        
                        buffLR[0]=buffL;
                        buffLR[1]=buffR;
                        pcm_return=snd_pcm_writen(alsa_pcm_handle,(void**) buffLR, alsa_frames);
                        if(pcm_return<0){
                            snd_pcm_prepare(alsa_pcm_handle);
                            fprintf(stderr, "xrun !\n");
                        }
                    }
                    position+=alsa_frames;
                    if(playFromFileV) positionChanged(position);
            }
            snd_pcm_drop(alsa_pcm_handle);
            zaparr(buff);
            zaparr(buffL);
            zaparr(buffR);
            zaparr(buffLR);
            if(playFromFileV) stopPlayFromFile();
            else playFromFileV=FALSE;
        }
    #endif
}


/*!
    \fn SoundPlayer::stopPlayFromFile()
 */
void SoundPlayer::stopPlayFromFile()
{
    playFromFileV=FALSE;
    playing=FALSE;
    position=0;
    positionChanged(position);
    #ifdef HAS_PORTAUDIO
        if(output==OUTPUT_PORTAUDIO) stopPAStream();
    #endif
    #ifdef HAS_ALSA
        if(output==OUTPUT_ALSA) snd_pcm_drop(alsa_pcm_handle);
    #endif
    sf_close(previewFile);
}

/*!
    \fn SoundPlayer::pausePlayFromFile()
 */
void SoundPlayer::pausePlayFromFile()
{
    playFromFileV=FALSE;
    #ifdef HAS_ALSA
        if(output==OUTPUT_ALSA) snd_pcm_drop(alsa_pcm_handle);
    #endif
    #ifdef HAS_PORTAUDIO
        if(output==OUTPUT_PORTAUDIO) stopPAStream(); 
    #endif
    //if(output==OUTPUT_JACK) sf_close(previewFile);
}



/*!
    \fn SoundPlayer::resetLoop()
 */
void SoundPlayer::resetLoop()
{
    if(savedLeftLoop!=ULONG_MAX || savedRightLoop!=ULONG_MAX){
        leftLoop=savedLeftLoop;
        rightLoop=savedRightLoop;
        savedLeftLoop=ULONG_MAX;
        savedRightLoop=ULONG_MAX;
    }
}


/*!
    \fn SoundPlayer::setPosition(long pos)
 */
void SoundPlayer::setPosition(long pos)
{
    if (playFromFileV){
        sf_seek (previewFile, (sf_count_t)pos, SEEK_SET);
    }
    positionChanged(pos);
}


/*!
    \fn SoundPlayer::close()
 */
void SoundPlayer::close()
{
    stopPlay();
    stopPlayFromFile();
    closeV=TRUE;
}


/*!
    \fn SoundPlayer::removeSoundMonitor(QObject* sm)
 */
void SoundPlayer::removeSoundMonitor(QObject* sm)
{
    if (soundMonitors.containsRef(sm)){
        soundMonitors.remove(sm);
    }
}

#ifdef HAS_PORTAUDIO
/*!
    \fn SoundPlayer::portaudio_callback( const void *inputBuffer, void *outputBuffer,
                     unsigned long framesPerBuffer,
                     const PaStreamCallbackTimeInfo* timeInfo,
                     PaStreamCallbackFlags statusFlags)
 */
int SoundPlayer::portaudio_callback( const void */*inputbuffer*/, void *outputBuffer,
                     unsigned long nframes,
                     const PaStreamCallbackTimeInfo* /*timeInfo*/,
                     PaStreamCallbackFlags /*statusFlags*/)
{
    if(!soundManager) return paComplete;
    if(playFromFileV){
        if (!previewFile) return paComplete;
        unsigned long length=sfinfoForPreview.frames;
        //cout<<"position: "<<position<<endl;
        if(position+nframes<length){
            //cout<<"position: "<<position<<endl;
            sf_readf_float(previewFile, (float*)outputBuffer,(sf_count_t)(nframes));
            position+=nframes;
            positionChanged(position);
            return paContinue;
        }else{
            sf_readf_float(previewFile, (float*)outputBuffer,(sf_count_t)(length-position));
            memset ((&((float*)outputBuffer)[length-position]), 0,
                                 sizeof(float) * (nframes-(length-position))*2);
            playFromFileV=FALSE;//stopPlayFromFile();
            return paAbort;
        }
    }else{
        if(isPlaying()){
                if(position+nframes<getRightLoop()){
                
                    soundManager->exportMultiplexedBuffer((float*)outputBuffer,
                                            position,
                                            position+long(nframes));
                    position+=nframes;
                }else{
                    soundManager->exportMultiplexedBuffer((float*)outputBuffer,
                                            position,
                                            getRightLoop());
                    if(playingOnce){
                        memset ((&((float*)outputBuffer)[getRightLoop()-position]), 0,
                                 sizeof(float) * (nframes-(getRightLoop()-position))*2);
                        playing=FALSE; //stopPlay(); 
                        playingOnce=FALSE;
                        position=leftLoop;
                        positionChanged(position);
                        resetLoop(); 
                        return paAbort;
                    }else{
                        soundManager->exportMultiplexedBuffer(
                                                (&((float*)outputBuffer)[getRightLoop()-position]),
                                                getLeftLoop(),
                                                getLeftLoop()+(nframes-(getRightLoop()-position)));
                        position=getLeftLoop()+(nframes-(getRightLoop()-position));
                    }
                }
                positionChanged(position);
                return paContinue;
        }else{
            return paComplete;
        }
    }
    return paComplete;
    
}

/*!
    \fn SoundPlayer::portaudio_static_callback( const void *inputBuffer, void *outputBuffer,
                     unsigned long framesPerBuffer,
                     const PaStreamCallbackTimeInfo* timeInfo,
                     PaStreamCallbackFlags statusFlags, void *userData )
 */
int SoundPlayer::portaudio_static_callback( const void *inputBuffer, void *outputBuffer,
                     unsigned long framesPerBuffer,
                     const PaStreamCallbackTimeInfo* timeInfo,
                     PaStreamCallbackFlags statusFlags, void *userData )
{
    return ((SoundPlayer *) userData)->portaudio_callback (inputBuffer,
                                                           outputBuffer,
                                                           framesPerBuffer,
                                                           timeInfo,
                                                           statusFlags);
}


/*!
    \fn SoundPlayer::connect_portaudio()
 */
int SoundPlayer::connect_portaudio()
{
    std::cout<<"Querying Portaudio...\n";
    PaError err=Pa_Initialize();
    if( err != paNoError ){
        printf(  "PortAudio error: %s\n", Pa_GetErrorText( err ) );
        return 1;
    }
    
    
    
    err = Pa_OpenDefaultStream(
                    &portaudio_stream,        /* passes back stream pointer */
                    0,              /* no input channels */
                    2,              /* stereo output */
                    paFloat32,      /* 32 bit floating point output */
                    44100,          /* sample rate */
                    256,            /* frames per buffer */
                    portaudio_static_callback, /* specify our custom callback */
                    (void *)this );        /* pass our data through to callback */
    
    if( err != paNoError ){
        printf(  "PortAudio error: %s\n", Pa_GetErrorText( err ) );
        return 1;
    }
    output=OUTPUT_PORTAUDIO;
    fprintf (stderr, "Connected to Portaudio.\n");
    return 0;
}


/*!
    \fn SoundPlayer::closePortaudio()
 */
void SoundPlayer::closePortaudio()
{
    Pa_CloseStream(portaudio_stream);
    Pa_Terminate();
}


/*!
    \fn SoundPlayer::startPAStream()
 */
int SoundPlayer::startPAStream()
{
    if(Pa_IsStreamActive(portaudio_stream)){
        Pa_StopStream(portaudio_stream);
    }
    if(Pa_IsStreamStopped( portaudio_stream )==1){
        PaError err=Pa_StartStream( portaudio_stream );
        if( err != paNoError ){
            printf(  "(in start) PortAudio error: %s\n", Pa_GetErrorText( err ) );
        }else{
            //cout<<"Latency: "<<Pa_GetStreamInfo( portaudio_stream )->outputLatency<<"sec"<<endl;
        }
        return err;
    } return 0;
}


/*!
    \fn SoundPlayer::stopPAStream()
 */
int SoundPlayer::stopPAStream()
{
    if(Pa_IsStreamStopped( portaudio_stream )!=1){
        PaError err=Pa_AbortStream( portaudio_stream );
        if( err != paNoError ){
            printf(  "(in stop) PortAudio error: %s\n", Pa_GetErrorText( err ) );
        }else{
            //while(portaudio_stream){}
        }
        return err;
    }
    return 0;
}
#endif

#ifdef HAS_ALSA
QString SoundPlayer::getAlsaPcmName(){
    return QString(alsa_pcm_name);
}

int SoundPlayer::setAlsaParameters(QString name, int frames, int periods, int rate){
    alsa_rate = rate;
    alsa_periods = periods;
    alsa_periodsize = frames<<2;
    QString nameQS=QStringList::split(" ",name).first();
    alsa_pcm_name = (char*)nameQS.latin1();
    if(alsa_pcm_handle!=0) snd_pcm_close(alsa_pcm_handle);
    cout<<"device: "<<alsa_pcm_name<<" rate: "<<alsa_rate<<" periods: "<<
            alsa_periods<<" period size: "<<alsa_periodsize<<" frames: "<<frames<<"\n";
    int ret=0;
    if((ret=connect_alsa())){
        output=OUTPUT_NONE;
    }
    return ret;
}


QStringList SoundPlayer::getAlsaInterfaces(){
                QStringList outputList;
    // Enumerate the ALSA cards and PCM harfware devices...
		snd_ctl_t *handle;
		snd_ctl_card_info_t *info;
		snd_pcm_info_t *pcminfo;
		snd_ctl_card_info_alloca(&info);
		snd_pcm_info_alloca(&pcminfo);
		int iCard = -1;
		int iCards = 0;
		QString sName, sText;
		while (snd_card_next(&iCard) >= 0 && iCard >= 0) {
			sName = "hw:" + QString::number(iCard);
			if (snd_ctl_open(&handle, sName.latin1(), 0) >= 0
				&& snd_ctl_card_info(handle, info) >= 0) {

				if (iCards > 0){
					outputList.append("----------------------");
			        }
			         
				sText  = sName + " [";
				sText += QString(snd_ctl_card_info_get_name(info))+"]";
				outputList.append("plug"+sText);
				int iDevice = -1;
				while (snd_ctl_pcm_next_device(handle, &iDevice) >= 0
					&& iDevice >= 0) {
					snd_pcm_info_set_device(pcminfo, iDevice);
					snd_pcm_info_set_subdevice(pcminfo, 0);
					snd_pcm_info_set_stream(pcminfo,SND_PCM_STREAM_PLAYBACK);
					if (snd_ctl_pcm_info(handle, pcminfo) >= 0) {
						sText  = sName + ',' + QString::number(iDevice) + " [";
						sText += QString(snd_pcm_info_get_name(pcminfo))+"]";
						outputList.append("plug"+sText);
					}
				}
				snd_ctl_close(handle);
				++iCards;
			}
		}
		qHeapSort(outputList);
		return outputList;
}

/*!
    \fn SoundPlayer::connect_alsa()
 */
int SoundPlayer::connect_alsa()
{
    int err=0;
    std::cout<<"Querying Alsa...\n";
    snd_pcm_stream_t alsa_stream = SND_PCM_STREAM_PLAYBACK;
    /* Init pcm_name. Of course, later you */
    /* will make this configurable ;-)     */
  
    /* Allocate the snd_pcm_hw_params_t structure on the stack. */
    snd_pcm_hw_params_alloca(&alsa_hwparams);
    
     /* Allocate the snd_pcm_sw_params_t structure on the stack. */
    snd_pcm_sw_params_alloca(&alsa_swparams);
    
    /* Open PCM. The last parameter of this function is the mode. */
    /* If this is set to 0, the standard mode is used. Possible   */
    /* other values are SND_PCM_NONBLOCK and SND_PCM_ASYNC.       */ 
    /* If SND_PCM_NONBLOCK is used, read / write access to the    */
    /* PCM device will return immediately. If SND_PCM_ASYNC is    */
    /* specified, SIGIO will be emitted whenever a period has     */
    /* been completely processed by the soundcard.                */
    if (snd_pcm_open(&alsa_pcm_handle, alsa_pcm_name, alsa_stream, 0) < 0) {
      fprintf(stderr, "Error opening PCM device %s\n", alsa_pcm_name);
      return(-1);
    }
    /* Init hwparams with full configuration space */
    if (snd_pcm_hw_params_any(alsa_pcm_handle, alsa_hwparams) < 0) {
      fprintf(stderr, "Can not configure this PCM device.\n");
      return(-2);
    }
    
     //snd_pcm_sw_params_set_start_threshold (alsa_pcm_handle, alsa_swparams, 0);
     
     if (snd_pcm_hw_params_set_access(alsa_pcm_handle, alsa_hwparams, SND_PCM_ACCESS_RW_NONINTERLEAVED) < 0) {
      fprintf(stderr, "Error setting access.\n");
      return(-3);
    }
    
    /* Set sample format */
    
    if ((err=snd_pcm_hw_params_set_format(alsa_pcm_handle, alsa_hwparams, SND_PCM_FORMAT_FLOAT)) < 0) {
        fprintf(stderr, "Error setting Float sample format  (%s)\n",snd_strerror (err));
        return(-4);
    }
    
    
    /* Set sample rate. If the exact rate is not supported */
    /* by the hardware, use nearest possible rate.         */ 
    alsa_exact_rate = alsa_rate;
    if (snd_pcm_hw_params_set_rate_near(alsa_pcm_handle, alsa_hwparams, &alsa_exact_rate, 0) < 0) {
      fprintf(stderr, "Error setting rate.\n");
      return(-5);
    }
    if (alsa_rate != alsa_exact_rate) {
      fprintf(stderr, "The rate %d Hz is not supported by your hardware.\n==> Using %d Hz instead.\n", alsa_rate, alsa_exact_rate);
    }
    
    /* Set number of channels */
    if (snd_pcm_hw_params_set_channels(alsa_pcm_handle, alsa_hwparams, 2) < 0) {
      fprintf(stderr, "Error setting channels.\n");
      return(-6);
    }

    /* Set number of periods. Periods used to be called fragments. */ 
    if (snd_pcm_hw_params_set_periods(alsa_pcm_handle, alsa_hwparams, alsa_periods, 0) < 0) {
      fprintf(stderr, "Error setting periods.\n");
      return(-7);
    }
    
    /* Set buffer size (in frames). The resulting latency is given by */
    /* latency = periodsize * periods / (rate * bytes_per_frame)     */
    if (snd_pcm_hw_params_set_buffer_size(alsa_pcm_handle, alsa_hwparams, (alsa_periodsize * alsa_periods)>>2) < 0) {
      fprintf(stderr, "Error setting buffersize.\n");
      return(-8);
    }
    
    /* Apply HW parameter settings to */
    /* PCM device and prepare device  */
    if (snd_pcm_hw_params(alsa_pcm_handle, alsa_hwparams) < 0) {
      fprintf(stderr, "Error setting HW params.\n");
      return(-9);
    }
    
    /* Apply SW parameter settings to */
    /* PCM device and prepare device  */
    /*if (snd_pcm_sw_params(alsa_pcm_handle, alsa_swparams) < 0) {
      fprintf(stderr, "Error setting HW params.\n");
      return(-10);
    }*/
    
    alsa_buffer_size=((alsa_periodsize * alsa_periods)>>2);
    alsa_frames=alsa_periodsize>>2;
    output=OUTPUT_ALSA;
    bufferLR_F=new float[alsa_periodsize];
    float latency = alsa_periodsize * float(alsa_periods) / float(((alsa_rate/1000) * 8 /*bytes_per_frame*/));
    cout<<"Initialised ALSA - "<<" device: "<<alsa_pcm_name<<" rate: "<<alsa_exact_rate<<" channels: 2 periods: "<<alsa_periods<<" buffer size: "<<
        alsa_buffer_size<<" frames: "<<alsa_frames<<" period size: "<<alsa_periodsize<<" latency: "<<latency<<" ms\n";
    return 0;
}

void  SoundPlayer::playALSA(){
    while(position<rightLoop && playing){
            int pcm_return=0;
            if(position+alsa_frames<rightLoop){
                bufferPointersLR[0]=&soundManager->getLeftChannel(SoundHolder::FOR_OUTPUT)[position];
                bufferPointersLR[1]=&soundManager->getRightChannel(SoundHolder::FOR_OUTPUT)[position];
                pcm_return=snd_pcm_writen(alsa_pcm_handle,(void**)bufferPointersLR, alsa_frames);
                if(pcm_return>0) position+=pcm_return;
                
                /*
                soundManager->exportMultiplexedBuffer(bufferLR_F, position,position+alsa_frames);
                pcm_return=snd_pcm_writei(alsa_pcm_handle, bufferLR_F, alsa_frames);
                if(pcm_return>0) position+=pcm_return;
                */
            }else if (position<rightLoop){
                /*
                soundManager->exportMultiplexedBuffer(bufferLR_F, position,
                        rightLoop);
                */
                 if(!playingOnce){
                    bufferPointersLR[0]=&soundManager->getLeftChannel(SoundHolder::FOR_OUTPUT)[position];
                    bufferPointersLR[1]=&soundManager->getRightChannel(SoundHolder::FOR_OUTPUT)[position];
                    pcm_return=snd_pcm_writen(alsa_pcm_handle,(void**)bufferPointersLR, rightLoop-position);
                    bufferPointersLR[0]=&soundManager->getLeftChannel(SoundHolder::FOR_OUTPUT)[leftLoop];
                    bufferPointersLR[1]=&soundManager->getRightChannel(SoundHolder::FOR_OUTPUT)[leftLoop];
                    pcm_return=snd_pcm_writen(alsa_pcm_handle,(void**)bufferPointersLR,(alsa_frames-(rightLoop-position)));
                    
                    /*
                    soundManager->exportMultiplexedBuffer(&bufferLR_F[rightLoop-position], leftLoop,
                            leftLoop+(alsa_frames-(rightLoop-position)));
                    */
                    //cout<<"alsa_frames: "<<alsa_frames<<" (alsa_frames-(rightLoop-position)): "<<(alsa_frames-(rightLoop-position));
                    //cout<< " rightLoop-position : "<<rightLoop-position<<"\n";
                    //pcm_return=snd_pcm_writen(alsa_pcm_handle, bufferLR_F, alsa_frames);
                    if(pcm_return>0) position=leftLoop+(pcm_return-(rightLoop-position));
                 }else{
                    bufferPointersLR[0]=&soundManager->getLeftChannel(SoundHolder::FOR_OUTPUT)[position];
                    bufferPointersLR[1]=&soundManager->getRightChannel(SoundHolder::FOR_OUTPUT)[position];
                    pcm_return=snd_pcm_writen(alsa_pcm_handle,(void**)bufferPointersLR, rightLoop-position);
                    snd_pcm_drain(alsa_pcm_handle);
                    //memset(&bufferLR_F[rightLoop-position],0,(alsa_frames-(rightLoop-position)));
                    //pcm_return=snd_pcm_writei(alsa_pcm_handle, bufferLR_F, alsa_frames);
                    position=rightLoop;
                 }
            }else position=rightLoop;
            if(isPlaying()) positionChanged(position);
            if(pcm_return<0){
                snd_pcm_prepare(alsa_pcm_handle);
                //cout<<"-EBADFD: "<<-EBADFD<<" -EPIPE: "<<-EPIPE<<" -ESTRPIPE: "<<-ESTRPIPE<<" -EAGAIN "<<-EAGAIN;
                //cout<<" -EINVAL: "<<-EINVAL<<" -EIO: "<<-EIO<<"\n";
                cout<<"xrun !   ("<<pcm_return<<")\n";
            }
        }
        if(playingOnce){
            this->stopPlay();
            playingOnce=FALSE;
            resetLoop();
        }
}
#endif

unsigned long SoundPlayer::getRightLoop() const {
    if (position<rightLoop) return rightLoop;
    else return soundManager->getFrames();
}


void SoundPlayer::setRightLoop(const unsigned long& theValue) {
    rightLoop = theValue;
}


unsigned long SoundPlayer::getLeftLoop() const {
    if (position<rightLoop) return leftLoop;
    else return 0;
}


void SoundPlayer::setLeftLoop(const unsigned long& theValue) {
    leftLoop = theValue;
}


void SoundPlayer::setMainApplication(QApplication* theValue) {
    mainApplication = theValue;
}
