shithub: sox

ref: 3b89cf7fa3d1548b5b91133a11be2d4d88521bb1
dir: /src/silence.c/

View raw version
/*	Silence effect for SoX
 *	by Heikki Leinonen (heilei@iki.fi) 25.03.2001
 *
 *	This effect deletes samples from the start of the sound
 *	file until a sample exceeds a given threshold (either
 *	left or right channel in stereo files). This can be used
 *	to filter out unwanted silence or low noise in the beginning
 *	of a sound file. The threshold can be given either as a
 *	percentage or in decibels.
 */


#include <string.h>
#include <math.h>
#include "st.h"

#ifndef TRUE
#define TRUE 1
#endif

#ifndef FALSE
#define FALSE 0
#endif

#ifndef min
#define min(s1,s2) ((s1)<(s2)?(s1):(s2))
#endif

/* Private data for silence effect. */

#define SILENCE_START  0
#define SILENCE_TRIM   1 
#define SILENCE_COPY   2
#define SILENCE_FLUSH  3
#define SILENCE_STOP   4

typedef struct silencestuff
{
    char	trim;
    double	trim_threshold;
    char	trim_unit; /* "d" for decibels or "%" for percent. */
    char	stop;
    double	stop_threshold;
    ULONG	stop_duration;
    char	stop_unit;
    LONG	*holdoff;
    ULONG	holdoff_count;
    ULONG	holdoff_offset;
    char	mode;
    char	crossings;
} *silence_t;


int st_silence_getopts(eff_t effp, int n, char **argv)
{
	silence_t	silence = (silence_t) effp->priv;

	switch (n)
	{
		case 0:	/* No arguments, use defaults given above. */
		break;

		case 1:
			sscanf(argv[0], "%lf", &silence->trim_threshold);
		break;

		default:
			sscanf(argv[0], "%lf", &silence->trim_threshold);
			sscanf(argv[1], "%c", &silence->trim_unit);
		break;
	}
	/* silence threshold type duration count [-notrim ] [ threshold type duration count ] */
	if ((silence->trim_unit != '%') && (silence->trim_unit != 'd'))
		st_fail("Usage: silence [threshold [d | %%]]");
	if ((silence->trim_unit == '%') && ((silence->trim_threshold < 0.0) || 
		                             (silence->trim_threshold > 100.0)))
		st_fail("silence threshold should be between 0.0 and 100.0 %%");
	if ((silence->trim_unit == 'd') && (silence->trim_threshold >= 0.0))
		st_fail("silence threshold should be less than 0.0 dB");
	return(ST_SUCCESS);
}

int st_silence_start(eff_t effp)
{
	silence_t	silence = (silence_t) effp->priv;

	silence->trim = TRUE;
	silence->trim_threshold = 1.0;
	silence->trim_unit = '%';

	silence->stop = TRUE;
	silence->stop_threshold = 1.0;
	silence->stop_duration = 1.8 * effp->ininfo.rate;
	silence->stop_unit = '%';
	silence->holdoff = malloc(sizeof(LONG)*silence->stop_duration);
	silence->holdoff_count = 0;
	silence->holdoff_offset = 0;

	silence->mode = SILENCE_START;

	silence->crossings = 0;

	if ((effp->outinfo.channels != 1) && (effp->outinfo.channels != 2))
	{
		st_fail("Silence effect can only be run on mono or stereo data");
		return (ST_EOF);
	}
	return(ST_SUCCESS);
}

int aboveThreshold(LONG value, double threshold, char unit)
{
	double	ratio;

	ratio = (double)labs(value) / (double)MAXLONG;
	return((unit == '%') ? ((ratio * 100.0) >= threshold) : ((log10(ratio) * 20.0) >= threshold));
}

/* Process signed long samples from ibuf to obuf. */
/* Return number of samples processed in isamp and osamp. */
int st_silence_flow(eff_t effp, LONG *ibuf, LONG *obuf, LONG *isamp, LONG *osamp)
{
    silence_t silence = (silence_t) effp->priv;
    int	threshold, i, j;
    LONG nrOfTicks, nrOfInSamplesRead, nrOfOutSamplesWritten;

    nrOfInSamplesRead = 0;
    nrOfOutSamplesWritten = 0;

    switch (silence->mode)
    {
	case SILENCE_START:
	    /* Fall through until start is written */
	    silence->mode = SILENCE_TRIM;

        /* Reads and discards all input data until it detects a
         * sample that is above the specified threshold.  Turns on
	 * copy mode when detected.
	 */
	case SILENCE_TRIM:
	    nrOfTicks = min((*isamp), (*osamp)) / effp->ininfo.channels;
	    for(i = 0; i < nrOfTicks; i++)
	    {
		threshold = 1;
		for (j = 0; j < effp->ininfo.channels; j++)
		{
		    threshold &= aboveThreshold(ibuf[j], silence->trim_threshold, 
			                        silence->trim_unit);
		}
		if (threshold)
		{
		    silence->mode = SILENCE_COPY;
		    goto silence_copy;
		}
		ibuf += effp->ininfo.channels;
		nrOfInSamplesRead += effp->ininfo.channels;
	    }
	    break;
	    /* Attempts to copy samples into output buffer.  If not
	     * looking for silence to terminate copy then blindly
	     * copy data into output buffer.
	     *
	     * If looking for silence, then see if input sample is above
	     * threshold.  If found then flush out hold off buffer
	     * and copy over to output buffer.  Tell user about
	     * input and output processing.
	     *
	     * If not above threshold then store in hold off buffer
	     * and do not write to output buffer.  Tell user input
	     * was processed.
	     *
	     * If hold off buffer is full then stop copying data and
	     * discard data in hold off buffer.
	     */
	case SILENCE_COPY:
silence_copy:
	    nrOfTicks = min((*isamp-nrOfInSamplesRead), 
	                    (*osamp-nrOfOutSamplesWritten)) / effp->ininfo.channels;
	    if (silence->stop)
	    {
	        for(i = 0; i < nrOfTicks; i++)
	        {
		    threshold = 0;
		    for (j = 0; j < effp->ininfo.channels; j++)
		    {
		        threshold |= aboveThreshold(ibuf[j], 
				                    silence->trim_threshold, 
			                            silence->trim_unit);
		    }
		    if (threshold && silence->holdoff_count)
		    {
			silence->mode = SILENCE_FLUSH;
			goto silence_flush;
		    }
		    else if (threshold)
		    {
			/* Not holding off so copy into output buffer */
			memcpy(obuf,ibuf,sizeof(LONG)*effp->ininfo.channels);
			nrOfInSamplesRead += effp->ininfo.channels;
			nrOfOutSamplesWritten += effp->ininfo.channels;
		    }
		    else if (!threshold)
		    {
			/* Add to holdoff buffer */
		        for (j = 0; j < effp->ininfo.channels; j++)
		        {
			    silence->holdoff[silence->holdoff_count++] = 
				*ibuf++;
			    nrOfInSamplesRead++;
		        }
			/* Check if holdoff buffer is greater than duration, 
			 * if so then stop processing.
			 */
			if (silence->holdoff_count >= 
				silence->stop_duration)
			{
			    silence->mode = SILENCE_STOP;
			    silence->holdoff_count = 0;
			    break;
			}
		    }
	        }
	    }
	    else
	    {
	        memcpy(obuf, ibuf, sizeof(LONG)*nrOfTicks);
	        nrOfInSamplesRead += nrOfTicks;
	        nrOfOutSamplesWritten += nrOfTicks;
	    }
	    break;

	case SILENCE_FLUSH:
silence_flush:
	    nrOfTicks = min((silence->holdoff_count - silence->holdoff_offset), 
	                    (*osamp-nrOfOutSamplesWritten)) / effp->ininfo.channels;
	    for(i = 0; i < nrOfTicks; i++)
	    {
		*obuf++ = silence->holdoff[silence->holdoff_offset++];
		nrOfOutSamplesWritten++;
	    }

	    if (silence->holdoff_offset == silence->holdoff_count)
	    {
		silence->holdoff_offset = 0;
		silence->holdoff_count = 0;
		silence->mode = SILENCE_COPY;
		/* Return to copy mode incase there are is more room in output buffer
		 * to copy some more data from input buffer.
		 */
		goto silence_copy;
	    }
	    break;
	case SILENCE_STOP:
	    break;
	}

	*isamp = nrOfInSamplesRead;
	*osamp = nrOfOutSamplesWritten;

	return (ST_SUCCESS);
}

int st_silence_drain(eff_t effp, LONG *obuf, LONG *osamp)
{
    silence_t silence = (silence_t) effp->priv;
    int i;
    LONG nrOfTicks, nrOfOutSamplesWritten = 0;

    /* Only if in flush mode will there be possible samples to write
     * out during drain() call.
     */
    if (silence->mode == SILENCE_FLUSH)
    {
        nrOfTicks = min((silence->holdoff_count - silence->holdoff_offset), 
	                *osamp) / effp->ininfo.channels;
	for(i = 0; i < nrOfTicks; i++)
	{
	    *obuf++ = silence->holdoff[silence->holdoff_offset++];
	    nrOfOutSamplesWritten++;
        }

	if (silence->holdoff_offset == silence->holdoff_count)
	{
	    silence->holdoff_offset = 0;
	    silence->holdoff_count = 0;
	    silence->mode = SILENCE_STOP;
	}
    }

    *osamp = nrOfOutSamplesWritten;
    return(ST_SUCCESS);
}

int st_silence_stop(eff_t effp)
{
    silence_t silence = (silence_t) effp->priv;

    free(silence->holdoff);
    return(ST_SUCCESS);
}