/***************************************************************************
 *   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=new short[buffer_len*2];
        bufferPointerL=0;
        bufferPointerR=0;
        devdsp_audio_device=0;
        leftLoop=0;
        rightLoop=0;
        output=OUTPUT_NONE;
        playFromFileV=FALSE;
        previewFileJack=0;
        savedLeftLoop=-1;
        savedRightLoop=-1;
        closeV=FALSE;
}


SoundPlayer::~SoundPlayer()
{
    zaparr(bufferLR);
    if(output==OUTPUT_DEVDSP) ::close (devdsp_audio_device) ;
    else if(output==OUTPUT_JACK) closeJack();
}

void SoundPlayer::playDevDsp(){
        if(output==OUTPUT_DEVDSP){
            while(position<rightLoop && playing){
                    if(position>=0 && position<soundManager->getFrames()){
                        if(soundManager->isPlayingBandOnly()){
                                soundManager->exportMultiplexedShortChannelBand(bufferLR,position,position+buffer_len);
                        	position+=buffer_len;
			}else{
				if(position+buffer_len<rightLoop){
                                    soundManager->exportMultiplexedShortBuffer(bufferLR, position,position+buffer_len);
                                       if (NOT_NATIVE_ENDIAN) swap_endian(bufferLR, buffer_len*2);
					write (devdsp_audio_device,bufferLR,buffer_len*2* sizeof (short)) ;
					position+=buffer_len;
                                }else if (position<rightLoop){
                                    soundManager->exportMultiplexedShortBuffer(bufferLR, position,
                                            rightLoop);
                                        if (NOT_NATIVE_ENDIAN) swap_endian(bufferLR, (rightLoop-position)*2);
					write (devdsp_audio_device,bufferLR,(rightLoop-position)*2* sizeof (short));
					position=rightLoop;
                                }else position=rightLoop;
			}
                        positionChanged(position);
                    }
            }
            if(playingOnce){
                this->stopPlay();
                playingOnce=FALSE;
                resetLoop();
            }
        }
}

void SoundPlayer::stopPlay(){

        if(playing){
            playing=false;
            positionChanged(leftLoop);
        }
}





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


void SoundPlayer::positionChanged(long _pos)
{
    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);
    }

}

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 */


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

    if(output==OUTPUT_DEVDSP){
        while(!closeV){
            if(playing){
                playDevDsp();
                if(playing) position=leftLoop;
            }else if(playFromFileV){
                if(playing) stopPlay();
                playFromFileThreaded();
            }else{
                msleep(100);
            }
        }
    }
}


/*!
    \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;
}


/*!
    \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;
    if(!playing) position=leftLoop;
}

/*!
    \fn SoundPlayer::setLoop(long,long)
 */
void SoundPlayer::setOneTimeLoop(long l,long r)
{
    savedLeftLoop=leftLoop;
    savedRightLoop=rightLoop;
    leftLoop=l;
    rightLoop=r;
    if(!playing) position=leftLoop;
}


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

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";
    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;
}


/*!
    \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=new short[buffer_len*2];
        }else return 1;
}

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(!soundManager) return (setJackNotPlaying(p1,nframes) || setJackNotPlaying(p2,nframes));
    if(nframes>buffer_len) setJackBuffersLen(nframes);

    if(playFromFileV){
        if (!previewFileJack) return (setJackNotPlaying(p1,nframes) || setJackNotPlaying(p2,nframes));
        float* buffL=new float[nframes];
        float* buffR=new float[nframes];
        float* buff=new float[nframes*2];
        long length=sfinfoForJackPreview.frames;
        if(position>=0 && position+buffer_len<=length){
            sf_readf_float(previewFileJack, buff,(sf_count_t)(nframes));
            int count=0;
            for (int 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(isPlaying()){
                if(position>=rightLoop || position>=soundManager->getFrames()){
                    position=leftLoop;
                    if(playingOnce){
                        this->stopPlay();
                        playingOnce=FALSE;
                        return (setJackNotPlaying(p1,nframes) || setJackNotPlaying(p2,nframes));
                    }
                }
                if(soundManager->isPlayingBandOnly()){
                    float* outputLeft=new float[nframes];
                    float* outputRight=new float[nframes];
                    soundManager->getLeftChannelBand(outputLeft,position,position+nframes);
                    soundManager->getRightChannelBand(outputRight,position,position+nframes);

                    if (outputLeft) memcpy (p1, outputLeft, sizeof(jack_default_audio_sample_t) * nframes);
                    else   setJackNotPlaying(p1,nframes);

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

                    zaparr(outputLeft);
                    zaparr(outputRight);
                }else{
                    bufferPointerL=
                            &soundManager->getLeftChannel()[position];
                    bufferPointerR=
                            &soundManager->getRightChannel()[position];

                    if(position+nframes<rightLoop){
                        if (bufferPointerL) memcpy (p1, bufferPointerL, sizeof(jack_default_audio_sample_t) * nframes);
                        else   setJackNotPlaying(p1,nframes);
                        if (bufferPointerR) memcpy (p2, bufferPointerR, sizeof(jack_default_audio_sample_t) * nframes);
                        else   setJackNotPlaying(p2,nframes);
                        position+=nframes;
                    }else{
                         if (bufferPointerL){
                             memcpy (p1, bufferPointerL, sizeof(jack_default_audio_sample_t) * (rightLoop-position));
                             memcpy (&p1[rightLoop-position],
                                 &soundManager->getLeftChannel()[leftLoop],
                                 sizeof(jack_default_audio_sample_t) * (nframes-(rightLoop-position)));
                         }else   setJackNotPlaying(p1,nframes);
                        if (bufferPointerR){
                            memcpy (p2, bufferPointerL, sizeof(jack_default_audio_sample_t) * (rightLoop-position));
                             memcpy (&p2[rightLoop-position],
                                 &soundManager->getRightChannel()[leftLoop],
                                 sizeof(jack_default_audio_sample_t) * (nframes-(rightLoop-position)));
                        }else   setJackNotPlaying(p2,nframes);
                        position=leftLoop+(nframes-(rightLoop-position));
                    }
                }
                positionChanged(position);
        }else{
            setJackNotPlaying(p1,nframes);
            setJackNotPlaying(p2,nframes);
        }
    }
    return 0;
}


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

    if(connect_jack() && connect_devdsp()){
        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(long start,long end){
    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::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;
}


/*!
    \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;
    //position=0;
    if(output==OUTPUT_JACK)  previewFileJack= sf_open(path, SFM_READ, &sfinfoForJackPreview);
}

/*!
    \fn SoundPlayer::playFromFile(QString path)
 */
void SoundPlayer::playFromFileThreaded()
{
    long length=0;
    short* buff=new short[buffer_len*2];
    SF_INFO sfinfo;
    SNDFILE* tmp_sndfile= sf_open(playFromFilePath, SFM_READ, &sfinfo);
    if (!tmp_sndfile) return;
    sf_seek (tmp_sndfile, (sf_count_t)position, SEEK_SET);
    length=sfinfo.frames;
    if(output==OUTPUT_DEVDSP){
            while(position<length && playFromFileV){
                if(position>0 && position+buffer_len<=length){
                        sf_readf_short(tmp_sndfile, 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);
            sf_close(tmp_sndfile);
            if(playFromFileV) stopPlayFromFile();
        }
}


/*!
    \fn SoundPlayer::stopPlayFromFile()
 */
void SoundPlayer::stopPlayFromFile()
{
    playFromFileV=FALSE;
    playing=FALSE;
    position=0;
    positionChanged(position);
    if(output==OUTPUT_JACK) sf_close(previewFileJack);
}

/*!
    \fn SoundPlayer::pausePlayFromFile()
 */
void SoundPlayer::pausePlayFromFile()
{
    playFromFileV=FALSE;
    //if(output==OUTPUT_JACK) sf_close(previewFileJack);
}



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


/*!
    \fn SoundPlayer::setPosition(long pos)
 */
void SoundPlayer::setPosition(long pos)
{
    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);
    }
}


