// ----------------------------------------------------------------------------
//
//  Copyright (C) 2003-2013 Fons Adriaensen <fons@linuxaudio.org>
//    
//  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 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/>.
//
// ----------------------------------------------------------------------------


#include <math.h>
#include "audiothr.h"


Audiothr::Audiothr (Shdata *shdata) :
    _shdata (shdata),
    _run_alsa (0),
    _run_jack (0)
{
    for (int i = 0; i < N_IP; i++) _shdata->_meter [i] = 0.0f;
    if      (_shdata->_state == Shdata::A_JACK) init_jack ();   
    else if (_shdata->_state == Shdata::A_ALSA) init_alsa ();   
}


Audiothr::~Audiothr (void)
{
    if (_run_alsa) close_alsa ();
    if (_run_jack) close_jack ();
}


void Audiothr::init_alsa (void)
{
    _fsamp = _shdata->_fsamp;
    _fsize = _shdata->_fsize;
    _nplay = _shdata->_nplay;
    _ncapt = _shdata->_ncapt;

    _alsa_handle = new Alsa_pcmi (_shdata->_ident,
                                  _shdata->_ident,
                                  0,
				  _shdata->_fsamp,
				  _shdata->_fsize,
                                  _shdata->_nfrag,
                                  0);
    if (_alsa_handle->state () < 0)
    {
        fprintf (stderr, "Can't connect to ALSA\n");
        _shdata->_state |= Shdata::A_FAIL;
        _shdata->_cld2par.eput (Shdata::X_EXIT);
        return;
    } 

    _run_alsa = true;
    if (_alsa_handle->nplay () < _nplay) _shdata->_nplay = _nplay = _alsa_handle->nplay ();
    if (_alsa_handle->ncapt () < _ncapt) _shdata->_ncapt = _ncapt = _alsa_handle->ncapt ();

    for (int i = 0; i < _nplay; i++) _op [i] = new float [_fsize];
    for (int i = 0; i < _ncapt; i++) _ip [i] = new float [_fsize];

    if (_shdata->_class == SCHED_FIFO)
    {
        if (thr_start (SCHED_FIFO, _shdata->_prior, 0x00010000))
        {
            fprintf (stderr, "Can't create ALSA thread with RT priority\n");
            _shdata->_state |= Shdata::A_NORT;
            _shdata->_class = SCHED_OTHER;   
	}
    }
    if (_shdata->_class == SCHED_OTHER)
    {
        if (thr_start (SCHED_OTHER, 0, 0x00010000))
        {
            fprintf (stderr, "Can't create ALSA thread\n");
            _shdata->_state |= Shdata::A_FAIL;
            _shdata->_cld2par.eput (Shdata::X_EXIT);
            return; 
	}
    }

    init ();
}


void Audiothr::close_alsa ()
{
    _run_alsa = false;
    _alsa_sync.lock ();
    _alsa_sync.eget ();
    _alsa_sync.unlock ();

    for (int i = 0; i < _nplay; i++) delete[] _op [i];
    for (int i = 0; i < _ncapt; i++) delete[] _ip [i];
}


void Audiothr::thr_main (void) 
{
    unsigned long k;

    _alsa_handle->pcm_start ();

    while (_run_alsa)
    {
	k = _alsa_handle->pcm_wait ();  
        while (k >= _fsize)
       	{
            if (_ncapt)
	    { 
		_alsa_handle->capt_init (_fsize);
                for (int i = 0; i < _ncapt; i++) _alsa_handle->capt_chan (i, _ip [i], _fsize);
		_alsa_handle->capt_done (_fsize);
	    }
            process ();
            if (_nplay)
	    {
		_alsa_handle->play_init (_fsize);
                for (int i = 0; i < _nplay; i++) _alsa_handle->play_chan (i, _op [i], _fsize);
		_alsa_handle->play_done (_fsize);
	    }
            k -= _fsize;
	}
    }

    _alsa_handle->pcm_stop ();
    _alsa_sync.lock ();
    _alsa_sync.eput (0);
    _alsa_sync.unlock ();
}


void Audiothr::init_jack (const char *server)
{
    char                s [16];
    int                 opts;
    jack_status_t       stat;
    struct sched_param  spar;

    opts = JackNoStartServer;
    if (server) opts |= JackServerName;

    _jack_handle = jack_client_open (_shdata->_ident, (jack_options_t)opts, &stat, server);
    if (!_jack_handle)
    {
        fprintf (stderr, "Can't connect to JACK\n");
        _shdata->_state |= Shdata::A_FAIL;
        _shdata->_cld2par.eput (Shdata::X_EXIT);
        return;
    }

    jack_set_process_callback (_jack_handle, jack_static_callback, (void *)this);
    jack_on_shutdown (_jack_handle, jack_static_shutdown, (void *)this);

    _nplay = _shdata->_nplay;
    _ncapt = _shdata->_ncapt;
    for (int i = 0; i < _nplay; i++)
    {
        sprintf(s, "out_%d", i + 1);
        _jack_op [i] = jack_port_register (_jack_handle, s, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    }
    for (int i = 0; i < _ncapt; i++)
    {
        sprintf(s, "in_%d", i + 1);
        _jack_ip [i] = jack_port_register (_jack_handle, s, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
    }

    _shdata->_fsamp = _fsamp = jack_get_sample_rate (_jack_handle);
    _shdata->_fsize = _fsize = jack_get_buffer_size (_jack_handle);
    _run_jack = true;

    if (jack_activate (_jack_handle))
    {
        fprintf(stderr, "Can't activate JACK");
        _shdata->_state |= Shdata::A_FAIL;
        _shdata->_cld2par.eput (Shdata::X_EXIT);
        return;
    }

    pthread_getschedparam (jack_client_thread_id (_jack_handle), &_shdata->_class, &spar);
    _shdata->_prior = spar.sched_priority -  sched_get_priority_max (_shdata->_class);

    init ();
 }


void Audiothr::close_jack ()
{
    jack_deactivate (_jack_handle);
    for (int i = 0; i < _nplay; i++) jack_port_unregister(_jack_handle, _jack_op [i]);
    for (int i = 0; i < _ncapt; i++) jack_port_unregister(_jack_handle, _jack_ip [i]);
    jack_client_close (_jack_handle);
}


void Audiothr::jack_static_shutdown (void *arg)
{
    ((Audiothr *) arg)->jack_shutdown ();
}


void Audiothr::jack_shutdown (void)
{
    _shdata->_state |= Shdata::A_KILL;
    _shdata->_mode  = 0;
    _shdata->_cld2par.eput (Shdata::X_EXIT);
}


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



int Audiothr::jack_callback (jack_nframes_t nframes)
{
    if (nframes != _fsize)
    {
        _shdata->_state |= Shdata::A_SIZE;
        _shdata->_mode  = 0;
        _shdata->_cld2par.eput (Shdata::X_EXIT);
    }

    for (int i = 0; i < _ncapt; i++) _ip [i] = (float *)(jack_port_get_buffer (_jack_ip [i], nframes));
    for (int i = 0; i < _nplay; i++) _op [i] = (float *)(jack_port_get_buffer (_jack_op [i], nframes));
    process ();

    return 0;
}


void Audiothr::init (void)
{
    _decay = powf (10.0f, -0.05f * DECAY * _fsize / _fsamp);
    _shdata->_cld2par.eput (Shdata::X_INIT);
}


void Audiothr::process (void)
{
    unsigned int  i, j, k, n;
    float         *p, *q, t, m;
    Fltlfq        *Q;
    Ipdata        *D;

    if (_shdata->_mode & Shdata::M_MEAS)
    {
	for (j = 0; (int) j < _ncapt; j++)
        {
            p = _ip [j];
	    m = fabsf (*p++);
            for (i = 1; i < _fsize; i++)
	    {
		t = fabsf (*p++);
                if (t > m) m = t;
	    }
            t = _decay * _shdata->_meter [j] + 1e-10f;
            if (t > m)  m = t;
            _shdata->_meter [j] = m;   
	}    
    }
    else
    {
	for (j = 0; (int) j < _ncapt; j++) _shdata->_meter [j] = 0.0f;
    }

    if (_shdata->_mode & Shdata::M_CAPT)
    {
	for (k = 0; k < 8; k++)
	{
	    D = _shdata->_ipdat + k;
            Q = D->_queue;
            if (Q)
	    {
                n = D->_type & 255;
                for (j = 0; j < n; j++)
	        {
                    p = Q->wr_ptr () + j;
		    q = _ip [D->_chan0 + j];
                    for (i = 0; i < _fsize; i++) p [i * n] = q [i];
	        }
                Q->wr_commit (_fsize); 
	    }
	}
    }

    if (_shdata->_mode & Shdata::M_PLAY)
    {
        Q = _shdata->_opdat._queue;
        p = Q->rd_ptr ();
        k = _shdata->_opsel;
        for (j = 0; (int) j < _nplay; j++)
        {
            if (k & 1) memcpy (_op [j], p, _fsize * sizeof (float));
            else       memset (_op [j], 0, _fsize * sizeof (float));
            k >>= 1;
	}
        Q->rd_commit (_fsize); 
    }
    else
    {
       for (j = 0; (int) j < _nplay; j++) memset (_op [j], 0, _fsize * sizeof (float));
    }
}
