// ----------------------------------------------------------------------------
//
//  Copyright (C) 2004-2018 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 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, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------


#include <math.h>
#include "audio.h"
#include "messages.h"


Audio::Audio (ITC_ctrl *cmain, const char *name) :
    A_thread ("Audio"),
    _jname (name),
    _cmain (cmain),
    _run_alsa (0),
    _run_jack (0),
    _active (false),
    _inputA (-1),
    _inputB (-1),
    _dataA (0),
    _dataB (0),
    _out1 (0),
    _out2 (0)
{
}


Audio::~Audio (void)
{
    if (_run_alsa) close_alsa ();
    if (_run_jack) close_jack ();
    delete[] _out1;
    delete[] _out2;
}


void Audio::init (void)
{
    _rngen.init (0);
    _b0 = _b1 = _b2 = _b3 = _b4 = _b5 = _b6 = 0.0f;
}


void Audio::init_alsa (const char *playdev, const char *captdev,
                       int fsamp, int fsize, int nfrags)
{
    _run_alsa = true;
    _alsa_handle = new Alsa_pcmi (playdev, captdev, 0, fsamp, fsize, nfrags);
    if (_alsa_handle->state () < 0)
    {
        fprintf (stderr, "Can't connect to ALSA\n");
        exit (1);
    } 
    _ncapt = _alsa_handle->ncapt ();
    _nplay = _alsa_handle->nplay ();
    _fsamp  = fsamp;
    _fsize = fsize;

    if (_nplay >= 2)
    {
	_out1 = new float [fsize];
	_out2 = new float [fsize];
    }

    init ();
   
    _cmain->put_event (EV_MESG, new M_audio (fsamp, _ncapt, _nplay, _jname));
    _alsa_handle->printinfo ();
    fprintf (stderr, "Connected to ALSA with %d inputs and %d outputs\n", _ncapt, _nplay); 

    if (thr_start (SCHED_FIFO, -10, 0x00010000))
    {
        fprintf (stderr, "Can't create ALSA thread with RT priority\n");
        if (thr_start (SCHED_OTHER, 0, 0x00010000))
        {
            fprintf (stderr, "Can't create ALSA thread\n");
            exit (1);
	}
    }
}


void Audio::close_alsa ()
{
    _run_alsa = false;
    get_event (1 << EV_EXIT);
    delete _alsa_handle;
}


void Audio::thr_main (void) 
{
    unsigned long k, m, n;

    _alsa_handle->pcm_start ();

    while (_run_alsa)
    {
	k = _alsa_handle->pcm_wait ();  
	if (_alsa_handle->state () == Alsa_pcmi::STATE_FAIL) break;
        while (k >= _fsize)
       	{
            if (_ncapt)
	    { 
		_alsa_handle->capt_init (_fsize);
		if (_dataA)
		{
		    m = _fsize;
		    n = _size - _dind;
		    if (m >= n)
		    {
			if (_inputA < 0) memset (_dataA + _dind, 0, n * sizeof (float));
                        else _alsa_handle->capt_chan (_inputA, _dataA + _dind, n);
			if (_inputB < 0) memset (_dataB + _dind, 0, n * sizeof (float));
                        else 
			{
			    if (_inputB == _inputA) memcpy (_dataB + _dind, _dataA + _dind, n * sizeof (float));
                            else _alsa_handle->capt_chan (_inputB, _dataB + _dind, n);
			}
			_dind = 0;
			m -= n;
		    }
		    if (m)
		    {
			if (_inputA < 0) memset (_dataA + _dind, 0, m * sizeof (float));
                        else _alsa_handle->capt_chan (_inputA, _dataA + _dind, m);
			if (_inputB < 0) memset (_dataB + _dind, 0, m * sizeof (float));
                        else
			{
			    if (_inputB == _inputA) memcpy (_dataB + _dind, _dataA + _dind, m * sizeof (float));
                            else _alsa_handle->capt_chan (_inputB, _dataB + _dind, m);
			}
			_dind += m;
		    }
		}

		_alsa_handle->capt_done (_fsize);
	    }

            if (_nplay)
	    {
		_alsa_handle->play_init (_fsize);

		if (_nplay >= 2)
		{
                    gen_noise (_fsize, _out1, _out2);
                    _alsa_handle->play_chan (0, _out1, _fsize);
                    _alsa_handle->play_chan (1, _out2, _fsize);
		}

		_alsa_handle->play_done (_fsize);
	    }

            k -= _fsize;
            _scnt += _fsize;
	}
        get_messages ();
    }

    _alsa_handle->pcm_stop ();
    put_event (EV_EXIT);
}


void Audio::init_jack (const char *server)
{
    char            s [16];
    int             opts;
    jack_status_t   stat;

    opts = JackNoStartServer;
    if (server) opts |= JackServerName;
    _jack_handle = jack_client_open (_jname, (jack_options_t)opts, &stat, server);
    if (!_jack_handle)
    {
        fprintf (stderr, "Can't connect to JACK\n");
        exit (1);
    }

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

    if (jack_activate (_jack_handle))
    {
        fprintf(stderr, "Can't activate JACK");
        exit (1);
    }
    _run_jack = true;

    _ncapt = 4;
    for (int i = 0; i < _ncapt; i++)
    {
        sprintf(s, "in_%d", i + 1);
        _jack_in [i] = jack_port_register (_jack_handle, s, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
    }

    _nplay = 2;
    _jack_out [0] = jack_port_register (_jack_handle, "white", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
    _jack_out [1] = jack_port_register (_jack_handle, "pink",  JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);

    init ();
    
    _fsamp = jack_get_sample_rate (_jack_handle);
    _fsize = jack_get_buffer_size (_jack_handle);
    _jname = jack_get_client_name (_jack_handle);
    _cmain->put_event (EV_MESG, new M_audio (_fsamp, _ncapt, _nplay, _jname));
    _active = true;
}


void Audio::close_jack ()
{
    jack_deactivate (_jack_handle);
    jack_client_close (_jack_handle);
}


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


void Audio::jack_shutdown (void)
{
    _cmain->put_event (EV_JACK);
}


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


int Audio::jack_callback (jack_nframes_t nframes)
{
    unsigned long  m, n;
    float  *pA, *pB;

    if (!_active) return 0;
    if (_dataA)
    {
        pA = (_inputA >= 0) ? (float *)(jack_port_get_buffer (_jack_in [_inputA], nframes)) : 0;
        pB = (_inputB >= 0) ? (float *)(jack_port_get_buffer (_jack_in [_inputB], nframes)) : 0;
	m = nframes;
        n = _size - _dind;
        while (m >= n)
	{
            if (pA)
	    {
                memcpy (_dataA + _dind, pA, sizeof(float) * n);
		pA += n;
	    }
	    else  memset (_dataA + _dind, 0,  sizeof(float) * n);
            if (pB)
	    {
                memcpy (_dataB + _dind, pB, sizeof(float) * n);
		pB += n;
	    }
	    else memset (_dataB + _dind, 0,  sizeof(float) * n);
            m -= n;
	    n = _size;
            _dind = 0;
        }
        if (m)
	{
            if (pA) memcpy (_dataA + _dind, pA, sizeof(float) * m);
	    else    memset (_dataA + _dind, 0,  sizeof(float) * m);
            if (pB) memcpy (_dataB + _dind, pB, sizeof(float) * m);
	    else    memset (_dataB + _dind, 0,  sizeof(float) * m);
            _dind += m;
	}
        _scnt += nframes;
    }

    gen_noise (nframes, 
               (float *)(jack_port_get_buffer (_jack_out [0], nframes)),
               (float *)(jack_port_get_buffer (_jack_out [1], nframes)));

    get_messages ();

    return 0;
}


void Audio::gen_noise (int n, float *op1, float *op2 ) 
{
    float x;

    while (n--)
    {
	x = _rngen.grandf ();
	*op1++ = 0.07071f * x;       	   
        x *= 0.023f;
        _b0 = 0.99886f * _b0 + 0.0555179f * x;
        _b1 = 0.99332f * _b1 + 0.0750759f * x;
        _b2 = 0.96900f * _b2 + 0.1538520f * x;
        _b3 = 0.86650f * _b3 + 0.3104856f * x;
        _b4 = 0.55000f * _b4 + 0.5329522f * x;
        _b5 = -0.7616f * _b5 - 0.0168980f * x;
        *op2++ = _b0 + _b1 + _b2 + _b3 + _b4 + _b5 + _b6 + x * 0.5362f;
        _b6 = x * 0.115926f;
    }
}


void Audio::get_messages (void) 
{
    int       k;
    ITC_mesg *M;

    if (_dataA)
    {
        k = _scnt / _step;
        if (k && _cmain->put_event_try (EV_TRIG, k) == ITC_ctrl::NO_ERROR) _scnt -= k * _step;
    }
   
    if (get_event_nowait (1 << EV_MESG) == EV_MESG)
    {
	M = get_message ();
	if (M->type () == MT_BUFFP)
	{
	    M_buffp *Z = (M_buffp *) M; 
	    _dataA = Z->_dataA;
	    _dataB = Z->_dataB;
	    _size  = Z->_size;
	    _step  = Z->_step; 
	    _dind  = 0;
	    _scnt  = 0;
	}
	else if (M->type () == MT_INPUT)
	{
	    M_input *Z = (M_input *) M; 
	    _inputA = Z->_inputA;
	    _inputB = Z->_inputB;
            if (_inputA >= _ncapt) _inputA = -1; 
            if (_inputB >= _ncapt) _inputB = -1; 
	}
	M->recover ();
    }
}


