/*
 * Copyright (c) 1983-2023 Trevor Wishart and Composers Desktop Project Ltd
 * http://www.trevorwishart.co.uk
 * http://www.composersdesktop.com
 *
 This file is part of the CDP System.

 The CDP System is free software; you can redistribute it
 and/or modify it under the terms of the GNU Lesser General Public
 License as published by the Free Software Foundation; either
 version 2.1 of the License, or (at your option) any later version.

 The CDP System 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 Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public
 License along with the CDP System; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 02111-1307 USA
 *
 */



/* floatsam version*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <structures.h>
#include <tkglobals.h>
#include <cdpmain.h>
#include <modify.h>
#include <modeno.h>
#include <globcon.h>
#include <modify.h>

#include <sfsys.h>

/* RWD added do_clip, peak (later, sort out nChans PEAK stuff... */
static void gain(int samples,double multiplier,double maxnoclip,int *numclipped,
                 int do_clip,double *peak,dataptr dz);
static int find_max(double *maxfound,dataptr dz);
static int  find_maximum(int infile,int *maxloc,int *maxrep,double *maxsamp,dataptr dz);
static void     get_max(double *,int,int,int *,int *,dataptr dz);
static int  balance(double mratio,dataptr dz);
//TW replaced by peakchunk
//static int    write_maxsamp_properties(double,long,long,dataptr dz);
static int  do_balance(dataptr dz);
static int  gain_process(dataptr dz);
static int  get_normalisation_gain(dataptr dz);
static void do_normalise(dataptr dz);
static void do_phase_invert(dataptr dz);
static void find_maxsamp(double *maxfound,dataptr dz);
static int  do_multifile_loudness(dataptr dz);


#define MAX_DBGAIN       (90)

//TW UPDATE: further updated for floatsams and new-style clipping detection
int gainenv(int samples,int *last_total,double maxnoclip,int *numclipped,int do_clip,double *peak,dataptr dz);

/************************** LOUDNESS_PROCESS *******************************/

int loudness_process(dataptr dz)
{
    int n, m;
    double maxtime,intime, ratio;  /*RWD April 2004 needed to add these  */
    switch(dz->mode) {
    case(LOUD_PROPOR):
    case(LOUD_DB_PROPOR):
        maxtime = dz->brk[LOUD_GAIN][(dz->brksize[LOUD_GAIN] - 1) * 2];
        intime = (double)(dz->insams[0]/dz->infile->channels)/(double)dz->infile->srate;
        ratio = intime/maxtime;
        for(n=0,m = 0;n < dz->brksize[LOUD_GAIN];n++,m+=2)
            dz->brk[LOUD_GAIN][m] = dz->brk[LOUD_GAIN][m] * ratio;
        dz->mode = LOUDNESS_GAIN;
        break;
    case(LOUDNESS_DBGAIN):
        if(dz->brksize[LOUD_GAIN]) {
            for(n=0,m=1;n < dz->brksize[LOUD_GAIN];n++,m+=2)
                dz->brk[LOUD_GAIN][m] = dbtogain(dz->brk[LOUD_GAIN][m]);
        } else {
            dz->param[LOUD_GAIN] = dbtogain(dz->param[LOUD_GAIN]);
        }
        break;
    }
    switch(dz->mode) {
    case(LOUDNESS_LOUDEST):
    case(LOUDNESS_EQUALISE):    return do_multifile_loudness(dz);
    case(LOUDNESS_BALANCE):     return do_balance(dz);
    default:                                    return gain_process(dz);
    }
    return(FINISHED);   /* NOTREACHED */
}

/************************** GAIN_PROCESS *******************************/

int gain_process(dataptr dz)
{
    int exit_status;
    double maxnoclip = F_MAXSAMP, peakval = 0.0;
    //TW UPDATE
    int numclipped = 0, last_total = 0;
    int clipsamps = 1;
    float *buffer = dz->sampbuf[0];

    if(dz->true_outfile_stype == SAMP_FLOAT)
        if(dz->clip_floatsams == 0)
            clipsamps = 0;

    switch(dz->mode) {
    case(LOUDNESS_NORM):
    case(LOUDNESS_SET):
        if((exit_status = get_normalisation_gain(dz))!=CONTINUE)
            return(exit_status);
        break;
    }
    display_virtual_time(0,dz);
    while(dz->samps_left > 0) {
        if((exit_status = read_samps(buffer,dz))<0) {
            sprintf(errstr,"Cannot read data from sndfile.\n");
            return(PROGRAM_ERROR);
        }
        switch(dz->mode) {
        case(LOUDNESS_GAIN):
        case(LOUDNESS_DBGAIN):
            //TW UPDATE >>
            if(dz->brksize[LOUD_GAIN])
                gainenv(dz->ssampsread,&last_total,maxnoclip,&numclipped,clipsamps,&peakval,dz);
            else if(dz->param[LOUD_GAIN] <= FLTERR) {
                sprintf(errstr,"With gain of %lf the soundfile will be reduced to SILENCE!\n",dz->param[LOUD_GAIN]);
                return(DATA_ERROR);
            } else
                //<< TW UPDATE
                gain(dz->ssampsread,dz->param[LOUD_GAIN],maxnoclip,&numclipped,clipsamps,&peakval,dz);
            break;
        case(LOUDNESS_NORM):
        case(LOUDNESS_SET):
            do_normalise(dz);
            break;
        case(LOUDNESS_PHASE):
            do_phase_invert(dz);
            break;
        }
        if(dz->ssampsread > 0) {
            if((exit_status = write_exact_samps(buffer,dz->ssampsread,dz))<0)
                return(exit_status);
        }
    }
    if(clipsamps && (numclipped > 0))
        sprintf(errstr, "WARNING: %ld samples were clipped.\n", numclipped);
    dz->peak_fval = peakval;
    /*will get written to header automatically*/
    return(FINISHED);
}

/************************** GAIN *******************************/

void gain(int samples,double multiplier,double maxnoclip,int *numclipped,
          int do_clip,double *peak,dataptr dz)
{
    int i;
    double s, buf_peak = *peak;
    float *buffer = dz->sampbuf[0];
    if(multiplier < 0.0)
        multiplier = -multiplier;
    for(i = 0; i < samples; i++) {
        s = buffer[i] * multiplier;
        if(s >= 0) {
            if(do_clip && (s > maxnoclip)) {
                (*numclipped)++;
                s = F_MAXSAMP;
            }
            buf_peak = max(buf_peak,s);
        } else {
            if(do_clip && (-s > maxnoclip)) {
                (*numclipped)++;
                s = -F_MAXSAMP;
            }
            buf_peak = max(buf_peak,-s);
        }
        buffer[i] = (float) s;
        *peak = buf_peak;
    }

}

/**************************** DO_NORMALISE ***************************/
/* RWD NB my old floatsam version added a peak_val arg */
void do_normalise(dataptr dz)
{
    register int i;

    float *buffer = dz->sampbuf[0];
    float dgain = (float) dz->param[LOUD_GAIN];
    for(i = 0; i < dz->ssampsread; i++) {
        //TW    float sampl;
        //TW    sampl  = buffer[i] * dgain;
        //TW    buffer[i] =  sampl;
        //TW must cast to float anyway, as multiply gives double
        buffer[i] =  (float)(buffer[i] * dgain);
    }
}

/**************************** DO_PHASE_INVERT ***************************/

void do_phase_invert(dataptr dz)
{   register int i;
    double samplong;
    float *buffer = dz->sampbuf[0];
    for(i = 0; i < dz->ssampsread; i++) {
        samplong  = -buffer[i];
        samplong = min(samplong,(double)F_MAXSAMP);
        buffer[i] = (float)(samplong);
    }
}

/****************************** FIND_MAX *************************/

int find_max(double *maxfound,dataptr dz)
{
    int exit_status;
    float *buffer = dz->sampbuf[0];
    display_virtual_time(0L,dz);
    while(dz->samps_left != 0) {
        if((exit_status = read_samps(buffer,dz))<0) {
            sprintf(errstr,"Cannot read data from sndfile.\n");
            return(PROGRAM_ERROR);
        }
        find_maxsamp(maxfound,dz);
        display_virtual_time(dz->total_samps_read,dz);
    }
    /*RWD NB this is a weird step, specifically for this program - balances to
     * MAXSAMP if sfile is actually higher than that.
     * must delete next line for a generic version!
     */
    *maxfound = min(*maxfound,(double)F_MAXSAMP); /* e.g. found -32768: substitute 32767 */
    return(FINISHED);
}

/**************************** MAXSAMP ***************************/

void find_maxsamp(double *maxfound,dataptr dz)
{   register int i;
    double k;
    float *buffer = dz->sampbuf[0];
    for(i = 0; i < dz->ssampsread; i++) {
        if((k = fabs(buffer[i])) > *maxfound)
            *maxfound = k;
    }
    return;
}

/**************************** GET_NORMALISATION_GAIN ***************************/

int get_normalisation_gain(dataptr dz)
{
    int exit_status;
    double maxfound = 0.0;
    double loud_level = dz->param[LOUD_LEVEL] * (double)F_MAXSAMP;

    print_outmessage_flush("Finding maximum amplitude.\n");
    if((exit_status = find_max(&maxfound,dz))<0)
        return(exit_status);
    if(flteq(maxfound,loud_level)) {
        sprintf(errstr,"File is already at the specified level.\n");
        return(GOAL_FAILED);
    }
    if((maxfound > loud_level) && dz->mode == LOUDNESS_NORM) {
        sprintf(errstr,"File is already above the specified level.\n");
        return(GOAL_FAILED);
    }

    dz->param[LOUD_GAIN]  = loud_level/maxfound;

    display_virtual_time(0L,dz);
    sprintf(errstr,"Normalising with Gain Factor = %lf\n", dz->param[LOUD_GAIN]);
    print_outmessage_flush(errstr);
    if(sndseekEx(dz->ifd[0],0L,0)<0) {
        sprintf(errstr,"sndseek() failed.\n");
        return(SYSTEM_ERROR);
    }
    reset_filedata_counters(dz);
    return(CONTINUE);
}

/***************************** DO_BALANCE ************************/

int do_balance(dataptr dz)
{
    int exit_status;
    int maxloc;
    int maxrep;
    double max1, max2;
    double mratio;
    display_virtual_time(0,dz);
    print_outmessage_flush("Getting max level from file 1.\n");
    if((exit_status = find_maximum(dz->ifd[0],&maxloc,&maxrep,&max1,dz))<0)
        return(exit_status);
    if(max1 <= 0) {
        sprintf(errstr,"First file has zero amplitude: can't proceed.\n");
        return(GOAL_FAILED);
    }
    //maxloc1 = maxloc;
    //maxrep1 = maxrep;
    reset_filedata_counters(dz);
    dz->samps_left = dz->insams[1];
    display_virtual_time(0,dz);
    print_outmessage_flush("Getting max level from file 2.\n");
    if((exit_status = find_maximum(dz->ifd[1],&maxloc,&maxrep,&max2,dz))<0)
        return(exit_status);
    mratio = max2/max1;
    if(sndseekEx(dz->ifd[0],0,0)<0) {
        sprintf(errstr,"sndseek failed.\n");
        return(SYSTEM_ERROR);
    }
    reset_filedata_counters(dz);
    display_virtual_time(0,dz);
    sprintf(errstr,"Adjusting gain of 1st infile by %lf\n",mratio);
    print_outmessage_flush(errstr);
    if((exit_status = balance(mratio,dz))< 0)
        return(exit_status);
    //TW No longer required: takes place in PEAK procedure
    //  if((exit_status = write_maxsamp_properties(max2,maxloc1,maxrep1,dz))<0)
    //          return(exit_status);
    return(FINISHED);
}

/***************************** BALANCE ************************/

int balance(double mratio,dataptr dz)
{
    int exit_status;
    int /*samps_read,*/ ssampsread, n;
    /*double maxsamp = 0;*/
    float *buffer = dz->sampbuf[0];
    while((ssampsread = fgetfbufEx(buffer, dz->buflen,dz->ifd[0],0)) > 0) {
        for(n=0;n<ssampsread;n++)
            buffer[n] = (float) /*round*/((double)buffer[n] * mratio);
        if((exit_status = write_exact_samps(buffer,ssampsread,dz))<0)
            return(exit_status);
    }
    if(ssampsread<0) {
        sprintf(errstr,"Sound read error.\n");
        return(SYSTEM_ERROR);
    }
    return(FINISHED);
}

/***************************** FIND_MAXIMUM ************************/

int find_maximum(int infile,int *maxloc,int *maxrep,double *maxsamp,dataptr dz)
{
    int  ssampsread, last_total_ssampsread = 0, total_samps_read = 0;
    float *buffer = dz->sampbuf[0];
    *maxsamp = 0;
    while((ssampsread = fgetfbufEx(buffer, dz->buflen,infile,0)) > 0) {
        total_samps_read += ssampsread;
        get_max(maxsamp,ssampsread,last_total_ssampsread,maxloc,maxrep,dz);
        last_total_ssampsread += ssampsread;
        display_virtual_time(total_samps_read,dz);
    }
    if(ssampsread<0) {
        sprintf(errstr,"Sound read error.\n");
        return(SYSTEM_ERROR);
    }
    return(FINISHED);
}

/***************************** GET_MAX ************************/

void get_max(double *maxsamp,int ssampsread,int last_total_ssampsread, int *maxloc, int *maxrep,dataptr dz)
{
    register int n;
    float thisamp;
    float *buffer = dz->sampbuf[0];
    for(n=0;n<ssampsread;n++) {
        if((thisamp = (float)fabs(buffer[n])) > *maxsamp) {
            *maxsamp = thisamp;
            *maxloc  = last_total_ssampsread + n;
            *maxrep = 1;
        } else if(flteq(thisamp,*maxsamp)) {
            (*maxrep)++;
        }
    }
    return;
}

/********************** WRITE_MAXSAMP_PROPERTIES ***************************/
//
//int write_maxsamp_properties(double max1,long maxloc,long maxrep,dataptr dz)
//{
//
//      float maxpfamp = (float)max1;
//
//      if(maxloc<0 || maxrep<0) {
//              sprintf(errstr,"Failed to establish location and/or repetition-count of max-sample\n");
//              return(GOAL_FAILED);
//      }
/* TODO: scrap all this for the PEAK chunk instead! */
//* don't store in 'maxamp' as this is (long) in old files:
//      if(sndputprop(dz->ofd, "maxpfamp", (char *)&maxpfamp, sizeof(float)) < 0) {
//              sprintf(errstr,"Can't write new max-sample to sndfile.\n");
//              return(PROGRAM_ERROR);
//      }
//      if(sndputprop(dz->ofd, "maxloc", (char *)&maxloc, sizeof(long)) < 0) {
//              sprintf(errstr,"Can't write location of max-sample to sndfile.\n");
//              return(PROGRAM_ERROR);
//      }
//      if(sndputprop(dz->ofd, "maxrep", (char *)&maxrep, sizeof(long)) < 0) {
//              sprintf(errstr,"Can't write repeat-count of max-sample to sndfile.\n");
//              return(PROGRAM_ERROR);
//      }
//      return(FINISHED);
//}

/********************** DO_MULTIFILE_LOUDNESS ***************************/

int do_multifile_loudness(dataptr dz)
{
    int exit_status;
    double *greater, greatest = 0.0, maxfound;
    /*RWD*/
    double dbamp;
    int clipsamps = 1;
    double peak_val = 0.0;

    int orig_ifd = dz->ifd[0];
    //TW REVISED Dec 2002
    //  int n, do_gain, namelen, maxno = 0;
    int n, do_gain, maxno = 0;
    //TW REVISED Dec 2002
    //  char outfilename[256], *outfilenumber;     /* RWD fname was only [64] */
    char outfilename[256];
    int numclipped = 0;
    double this_gain = 1.0, maxnoclip = 1.0;

    if((greater = (double *)malloc(dz->infilecnt * sizeof(double)))==NULL) {
        sprintf(errstr,"Insufficient memory to store file maxima\n");
        return(MEMORY_ERROR);
    }

    if(dz->true_outfile_stype == SAMP_FLOAT)
        if(dz->clip_floatsams == 0)
            clipsamps = 0;

    fprintf(stdout,"INFO: Finding loudest file\n");
    fflush(stdout);
    for(n=0;n<dz->infilecnt;n++) {
        maxfound = 0.0;
        dz->ifd[0] = dz->ifd[n];
        dz->total_samps_written = 0;
        dz->samps_left = dz->insams[n];
        dz->total_samps_read = 0;
        if((exit_status = find_max(&maxfound,dz))<0) {
            dz->ifd[0] = orig_ifd;
            return(exit_status);
        }
        if(maxfound > greatest) {
            greatest = maxfound;
            maxno = n;
        }
        greater[n] = maxfound/F_MAXSAMP;
    }
    greatest /= F_MAXSAMP;
    dz->ifd[0] = orig_ifd;
    dbamp = 20 * log10(greatest);  /*RWD*/
    if(dz->mode==LOUDNESS_LOUDEST) {
        fprintf(stdout,"INFO: loudest file is %s with level %lf (%.3f dB)\n",dz->wordstor[maxno],greatest,dbamp);
        fflush(stdout);
        return(FINISHED);
    }
    fprintf(stdout,"INFO: Equalising levels\n");
    fflush(stdout);
    for(n=0;n<dz->infilecnt;n++) {
        dz->ssampsread = 0;
        dz->total_samps_read = 0;
        dz->ifd[0] = dz->ifd[n];
        if(sndseekEx(dz->ifd[0],0L,0)<0) {
            sprintf(errstr,"sndseekEx() failed.\n");
            return(SYSTEM_ERROR);
        }
        dz->total_samps_written = 0;
        dz->samps_left = dz->insams[n];
        dz->total_samps_read = 0;
        if(!flteq(greatest,greater[n])) {
            this_gain = greatest/greater[n];
            maxnoclip  = fabs((double)F_MAXSAMP/this_gain);
            do_gain = 1;
        } else {
            do_gain = 0;
        }
        if(n>0) {
            strcpy(outfilename,dz->wordstor[dz->infilecnt]);
            /* RWD 9:2001 the -1 is unsafe if outname is only one char! */
            //TW This is a sloom requirement,as standard sloom temp outfile name is 'cdptest0'
            // and temporary-file housekeeping requires files to be named cdptestN in numeric order of N
            // However, we can do it differently for cmdline case....
            if(sloom)
                insert_new_number_at_filename_end(outfilename,n,1);
            else
                insert_new_number_at_filename_end(outfilename,n,0);
            if((exit_status = create_sized_outfile(outfilename,dz))<0) {
                sprintf(errstr,"Cannot open output file %s\n", outfilename);
                dz->ifd[0] = orig_ifd;
                return(DATA_ERROR);
            }
        }
        while(dz->samps_left > 0) {
            if((exit_status = read_samps(dz->sampbuf[0],dz))<0) {
                sprintf(errstr,"Cannot read data from sndfile %d\n",n+1);
                dz->ifd[0] = orig_ifd;
                return(PROGRAM_ERROR);
            }
            if(do_gain)
                gain(dz->ssampsread,this_gain,maxnoclip,&numclipped,clipsamps,&peak_val,dz);
            if(dz->ssampsread > 0) {
                if((exit_status = write_samps(dz->sampbuf[0],dz->ssampsread,dz))<0) {
                    dz->ifd[0] = orig_ifd;
                    return(exit_status);
                }
            }
        }
        if(n < dz->infilecnt - 1) {
            if((exit_status = headwrite(dz->ofd,dz))<0) {
                dz->ifd[0] = orig_ifd;
                return(exit_status);
            }
            if((exit_status = reset_peak_finder(dz))<0)
                return(exit_status);
            if(sndcloseEx(dz->ofd) < 0) {
                fprintf(stdout,"WARNING: Can't close output soundfile %s\n",outfilename);
                fflush(stdout);
            }
            dz->ofd = -1;
        }
    }
    dz->ifd[0] = orig_ifd;
    /*RWD*/
    if(clipsamps && (numclipped > 0))
        sprintf(errstr, "WARNING: %d samples were clipped.\n", numclipped);
    dz->peak_fval = peak_val;
    /*will get written to header automatically*/
    return(FINISHED);
}


//TW NEW FUNCTION, updated for floatsams and new-style clipping detection
/********************** GAINENV ***************************/

int gainenv(int samples,int *last_total,double maxnoclip,int *numclipped,int do_clip,double *peak,dataptr dz)
{
    int exit_status;
    register int i;
    double s, buf_peak = *peak;
    double timeincr = 1.0/(double)dz->infile->srate;
    float *buffer = dz->sampbuf[0];
    double time = (double)(*last_total/dz->infile->channels)/(double)dz->infile->srate;

    for(i = 0; i < samples; i++) {
        if(i % dz->infile->channels == 0) {
            if((exit_status = read_value_from_brktable(time,LOUD_GAIN,dz))<0)
                return(exit_status);
            time += timeincr;
        }
        s = buffer[i];
        s *= dz->param[LOUD_GAIN];
        if(s >= 0) {
            if(do_clip && (s > maxnoclip)) {
                (*numclipped)++;
                s = 1.0;
            }
            buf_peak = max(buf_peak,s);
        } else {
            if(do_clip && (s < -maxnoclip)) {
                (*numclipped)++;
                s = -1.0;
            }
            buf_peak = max(buf_peak,-s);
        }
        buffer[i] = (float)s;
    }
    *peak = buf_peak;
    *last_total = dz->total_samps_read;
    return(FINISHED);
}
