// ----------------------------------------------------------------------------
//
//  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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "calcthr.h"


Calcthr::Calcthr (void) :
    A_thread ("calc")
{
}


Calcthr::~Calcthr (void)
{
}


void Calcthr::stop (void)
{
    put_event (EV_EXIT, 1);
}


void Calcthr::thr_main (void)
{
    int  E;

    while (1)
    {
	E = get_event ();
	switch (E)
	{
	case TO_CALC:
	    handle_messsage (get_message ());
	    break;

	case EV_EXIT:
	    return;
	}
    }
}


void Calcthr::handle_messsage (ITC_mesg *M)
{
    switch (M->type ())
    {
    case MT_CONV_ONE:
	conv_one ((M_calc_conv *) M);
	send_event (TO_CTRL, M);
	break;

    case MT_CONV_ALL:
	conv_all ((M_calc_conv *) M);
	send_event (TO_CTRL, M);
	break;

    case MT_EDIT_ONE:
	edit_one ((M_calc_edit *) M);
	send_event (TO_CTRL, M);
	break;

    case MT_EDIT_ALL:
	edit_all ((M_calc_edit *) M);
	send_event (TO_CTRL, M);
	break;

    case MT_EXPORT:
	exp_impdata ((M_export *) M);
	send_event (TO_CTRL, M);
	break;

    default:
	M->recover ();
    }
}



void Calcthr::conv_one (M_calc_conv *M)
{
    int       c, i, j, k, n1, n2, r, w;
    float     *p, *q;

    M->_conv->reset ();
    k = M->_conv->part ();
    for (r = 0; r < M->_k1; r += k)
    {
        n1 = M->_data1->n_fram () - r;
        if (n1 < 0) n1 = 0;
        if (n1 > k) n1 = k;
        n2 = k - n1;
        c = M->_data1->n_chan ();
        for (j = 0; j < c; j++)
	{
            p = M->_data1->data (j) + c * r;
            q = M->_conv->wr_ptr (j);
            for (i = 0; i < n1; i++)
     	    {
		*q++ = *p;
                p += c;
	    }
            memset (q, 0, n2 * sizeof (float));                         
        }
        M->_conv->process ();
	send_event (EV_PROG, 1);
        w = r - M->_k0;
        n1 = 0;
        n2 = M->_data2->n_fram () - w;
        if (n2 > k) n2 = k;
        if (w < 0)
	{
            n1 -= w;
            n2 += w; 
            w = 0;
	}         
        if (n2 > 0)
	{
            c = M->_data2->n_chan ();
    	    for (j = 0; j < c; j++)
	    {
                p = M->_conv->rd_ptr (j) + n1;
                q = M->_data2->data (j) + c * w;
                for (i = 0; i < n2; i++)
     	        {
		    *q = *p++;
                    q += c;
		}
	    }
	}
    }
    if (M->_sect >= 0) M->_data2->write_sect (M->_sect);
}


void Calcthr::conv_all (M_calc_conv *M)
{
    int i, n;

    n = M->_data1->n_sect ();
    for (i = 0; i < n; i++)
    {
	M->_data1->read_sect (i);
	M->_sect = i;
        conv_one (M);
    }
}



void Calcthr::edit_one (M_calc_edit *M)
{
    int    c, d, i, j, k, n;
    float  *p, *q, g, w;

    c = M->_data1->n_chan (); 
    d = M->_trm01 - M->_trm00;
    if (d) 
    {
        p = M->_data1->data (0);
        k = M->_ind0 * c;
        while (k--) *p++ = 0;
        w = M_PI / d;
        for (i = M->_ind0; (i < M->_trm01) && (i <= M->_ind1); i++)
	{
	    g = 0.5f * (1.0f - cosf (w * (i - M->_trm00)));
            for (j = 0; j < c; j++) *p++ *= g;
	}
    }
    d = M->_trm11 - M->_trm10;
    if (d)
    {
	n = M->_data1->n_fram ();
        p = M->_data1->data (0) + n * c;
        n -= 1; 
        k = (n - M->_ind1) * c;
        while (k--) *--p = 0;
        w = M_PI / d;  
        for (i = M->_ind1; (i > M->_trm10) && (i >= M->_ind0); i--)
	{
	    g = 0.5f * (1.0f - cosf (w * (i - M->_trm11)));
            for (j = 0; j < c; j++) *--p *= g;
	}
    }
    if (M->_slope < 0)
    {
        p = M->_data1->data (0) + M->_ind0 * c;
	w = M->_slope / (10.0f * M->_data1->drate ());
	for (i = M->_ind0; i <= M->_ind1; i++)
	{
	    g = 1.0f / sqrtf (1.0f + powf (10.0f, (M->_tfade - i) * w));
            for (j = 0; j < c; j++) *p++ *= g;
	}
    }
    if (M->_sect >= 0)
    {
	p = M->_data1->data (0) + c * M->_ind0;
        q = M->_data2->data (0);
        for (i = 0; i < c * (int)(M->_data2->n_fram ()); i++) *q++ = M->_gain * *p++; 
        M->_data2->write_sect (M->_sect);
        send_event (EV_PROG, 1);
    }
}


void Calcthr::edit_all (M_calc_edit *M)
{
    int i, n;

    n = M->_data1->n_sect ();
    for (i = 0; i < n; i++)
    {
	M->_data1->read_sect (i);
	M->_sect = i;
        edit_one (M);
    }
}



void Calcthr::exp_impdata (M_export *M)
{
    SNDFILE  *S;
    U32      i, c, m, n, s, s0, s1;
    float    *d;

    i = M->_impd->i_sect ();
    d = 0;
    for (n = 0, m = M->_mask; m; m >>= 1) n += m & 1;
    if (M->_sect == EXP_SECT_CURR) { s0 = i; s1 = i + 1;  }
    else                           { s0 = 0; s1 = M->_impd->n_sect (); }

    if (M->_type == EXP_TYPE_WAVEX || M->_type == EXP_TYPE_AMBIS)
    {
	if (M->_impd->n_chan () > n) d = new float [n * M->_impd->n_fram ()];
        if (M->_sect == EXP_SECT_COMB) 
        {
            if (!(S = exp_open_multi (M->_impd, M->_name, M->_type, M->_form, -1, n)))
	    {
		M->_impd->read_sect (i);
		M->_rval = 1;
		delete[] d;
	    }
  	    for (s = s0; s < s1; s++)
	    {
		M->_impd->read_sect (s);
                exp_writesect (S, M->_impd, m, n, d);
	    }
	    sf_close (S);
	}		
	else
	{
	    for (s = s0; s < s1; s++)
	    {
                if (!(S = exp_open_multi (M->_impd, M->_name, M->_type, M->_form, s, n)))
		{
		    M->_impd->read_sect (i);
		    M->_rval = 1;
  		    delete[] d;
		}
		M->_impd->read_sect (s);
                exp_writesect (S, M->_impd, m, n, d);
		sf_close (S);
	    }
	}
    }
    else
    {
	if (M->_impd->n_chan () > 1) d = new float [M->_impd->n_fram ()];
	for (c = 0, m = 1; c < M->_impd->n_chan (); c++, m <<= 1)
	{
	    if (m & M->_mask)
	    {
		if (M->_sect == EXP_SECT_COMB) 
		{
		    if (!(S = exp_open_single (M->_impd, M->_name, M->_type, M->_form, -1, c)))
   		    {
			M->_impd->read_sect (i);
			M->_rval = 1;
  		        delete[] d;
		    }
		    for (s = s0; s < s1; s++)
		    {
			M->_impd->read_sect (s);
                        exp_writesect (S, M->_impd, m, 1, d);
		    }
  		    sf_close (S);
		}
		else
		{
		    for (s = s0; s < s1; s++)
 	            {
			if (!(S = exp_open_single (M->_impd, M->_name, M->_type, M->_form, s, c)))
			{
			    M->_impd->read_sect (i);
			    M->_rval = 1;
   		            delete[] d;
			}
			M->_impd->read_sect (s);
                        exp_writesect (S, M->_impd, m, 1, d);
			sf_close (S);
		    }
		}
	    }
	}
    }

    M->_impd->read_sect (i);
    M->_rval = 0;
    delete[] d;
}


SNDFILE *Calcthr::exp_open_multi (Impdata *D, const char *name, U32 type, U32 form, S32 sect, U32 chan)
{
    SF_INFO   I;
    SNDFILE  *S;
    const char *fext;
    char        path [1024];

    fext = (type == EXP_TYPE_AMBIS) ? "amb" : "wav";
    if (sect < 0) snprintf (path, 1024, "%s.%s", name, fext);
    else          snprintf (path, 1024, "%s-%03d.%s", name, sect + 1, fext);

    I.samplerate = (int)(D->drate ());
    I.channels = chan;
    I.sections = 1;
    I.format = SF_FORMAT_WAVEX;
    switch (form)
    {
    case EXP_FORM_16B:
        I.format |= SF_FORMAT_PCM_16;
	break;
    case EXP_FORM_24B:
        I.format |= SF_FORMAT_PCM_24;
	break;
    default:
        I.format |= SF_FORMAT_FLOAT;
    }

    S = sf_open (path, SFM_WRITE, &I);
    if (!S)
    {
        fprintf (stderr, "Export: can't create '%s'.\n", path);
	return 0;
    }
    if (type == EXP_TYPE_AMBIS)
    {
	// set the Ambisonics flag;
    }

    return S;
}


SNDFILE *Calcthr::exp_open_single (Impdata *D, const char *name, U32 type, U32 form, S32 sect, U32 chan)
{
    SF_INFO   I;
    SNDFILE  *S;
    const char *fext, *suff;
    char        path [1024];

    suff = Impdata::channame (D->type (), chan);
    fext = (type == EXP_TYPE_RAW) ? "pcm" : "wav";
    if (suff && *suff)
    {
        if (sect < 0) snprintf (path, 1024, "%s-%s.%s", name, suff, fext);
        else          snprintf (path, 1024, "%s-%03d-%s.%s", name, sect + 1, suff, fext);
    }
    else
    {
        if (sect < 0) snprintf (path, 1024, "%s.%s", name, fext);
        else          snprintf (path, 1024, "%s-%03d.%s", name, sect + 1, fext);
    }

    I.samplerate = (int)(D->drate ());
    I.channels = 1;
    I.sections = 1;
    if (type == EXP_TYPE_RAW)
    {
	I.format = SF_FORMAT_RAW;
	fext = ".pcm";
    }
    else
    {
	I.format = SF_FORMAT_WAV;
	fext = ".wav";
    }
    switch (form)
    {
    case EXP_FORM_16B:
        I.format |= SF_FORMAT_PCM_16;
	break;
    case EXP_FORM_24B:
        I.format |= SF_FORMAT_PCM_24;
	break;
    default:
        I.format |= SF_FORMAT_FLOAT;
    }

    S = sf_open (path, SFM_WRITE, &I);
    if (!S)
    {
        fprintf (stderr, "Export: can't create '%s'.\n", path);
	return 0;
    }

    return S;
}


int Calcthr::exp_writesect (SNDFILE *S, Impdata *D, U32 mask, U32 chan, float *data)
{
    U32       i, j, k, n;
    float     *p, *q;

    if (data)
    {
	for (j = k = 0; mask; j++, mask >>= 1)
	{
	    if (mask & 1)
	    {
		p = D->data (j);
		q = data + k++;
		for (i = 0; i < D->n_fram (); i++)
		{
		    *q = *p;
		    p += D->n_chan ();
		    q += chan;
		}
	    }
	}
        n = sf_writef_float (S, data, D->n_fram ());
    }
    else
    {
        n = sf_writef_float (S, D->data (0), D->n_fram ());
    }

    send_event (EV_PROG, 1);
    return (n == D->n_fram ()) ? 0 : 1;
}


