// ----------------------------------------------------------------------------
//
//  Copyright (C) 2021 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 <string.h>
#include "fader2.h"


Fader2chan::Fader2chan (void) :
    _mute (false),
    _state (MININF),
    _g0 (1.0f),
    _g1 (0.0f),
    _g (0),
    _d (0)
{
}


void Fader2chan::prepare (int fragm, float gain, bool mute)
{
    float gt;

    // Prepare for the next 10 ms fragment. If either the
    // current or target gain is zero, a linear fade in
    // or out is used. Otherwise the gain will change by
    // at most 6 dB.
    
    _g = _g1; // Removes round-off errors.
    gt = (_mute || mute) ? 0 : _g0 * gain;
    if (_g1 == gt)
    {
	if (_g1 == 0) _state = MININF;
	else          _state = STATIC;
	return;
    }			    
    _state = FADING;
    if (gt > 2 * _g1)
    {
	if (_g1 == 0) _g1 = gt;
	else          _g1 *= 2;
    }
    else if (_g1 > 2 * gt)
    {
	if (gt == 0) _g1 = 0;
	else         _g1 /= 2;
    }
    else _g1 = gt;
    _d = (_g1 - _g) / fragm;
}


void Fader2chan::process (int n, float *inp, float *out)
{
    int   j;
    float g;

    g = _g;
    switch (_state)
    {
    case STATIC:
        for (j = 0; j < n; j++)
	{
            out [j] = g * inp [j];
	}
  	break;
    case FADING:
        for (j = 0; j < n; j++)
        {
	    g += _d;
            out [j] = g * inp [j];
        }
        _g = g;
        break;
    default:		
	memset (out, 0, n * sizeof (float));
    }
}


Fader2::Fader2 (int nchan, int fsamp) :
    _nchan (nchan),
    _fragm (fsamp / 100),
    _count (0),
    _mute (false),
    _gain (0.0f)
{
    _procs = new Fader2chan [nchan];
}


Fader2::~Fader2 (void)
{
    delete[] _procs;
}


void Fader2::setgain (int chan, float gdb)
{
    float g;
    
    if (gdb < -120.0f) g = 0.0f; 
    else g = powf (10.0f, 0.05f * gdb);
    if (chan < 0) _gain = g;
    else if (chan < _nchan) _procs [chan]._g0 = g;
}


void Fader2::setmute (int chan, bool mute)
{
    if (chan < 0) _mute = mute;
    else if (chan < _nchan) _procs [chan]._mute = mute;
}


void Fader2::process (int nframes, float *inp[], float *out[])
{
    int i, k, n;

    k = 0;
    while (nframes)
    {
	if (_count == 0)
	{
            for (i = 0; i < _nchan; i++)
            {
                _procs [i].prepare (_fragm, _gain, _mute);
            }
	    _count = _fragm;
	}
	n = (_count < nframes) ? _count : nframes;
        for (i = 0; i < _nchan; i++)
        {
            _procs [i].process (n, inp [i] + k, out [i] + k);
	}
	k += n;
	_count -= n;
	nframes -= n;
    }
}
