You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
518 lines
14 KiB
518 lines
14 KiB
/* ***** BEGIN LICENSE BLOCK *****
|
|
*
|
|
* This software is released under the provisions of the GPL version 2.
|
|
* see file "COPYING". If that file is not available, the full statement
|
|
* of the license can be found at
|
|
*
|
|
* http://www.fsf.org/licensing/licenses/gpl.txt
|
|
*
|
|
* Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
|
|
* Copyright (c) 2005 Paul Cifarelli All Rights Reserved.
|
|
*
|
|
*
|
|
* This file is part of the Helix DNA Technology. RealNetworks is the
|
|
* developer of the Original Code and owns the copyrights in the
|
|
* portions it created.
|
|
*
|
|
* This file, and the files included with this file, is distributed
|
|
* and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
|
|
* ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TQUIET
|
|
* ENJOYMENT OR NON-INFRINGEMENT.
|
|
*
|
|
* Technology Compatibility Kit Test Suite(s) Location:
|
|
* http://www.helixcommunity.org/content/tck
|
|
*
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
#include <stdlib.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include <iostream>
|
|
#include "hxassert.h"
|
|
|
|
#include "gain.h"
|
|
|
|
using namespace std;
|
|
|
|
#define INT8_CEILING 255
|
|
#define INT16_CEILING 32767
|
|
#define INT32_CEILING 65535
|
|
|
|
struct GAIN_STATE
|
|
{
|
|
int sampleRate;
|
|
int nChannels;
|
|
int bytesPerSample;
|
|
bool isMute;
|
|
float instGain; /* gain applied right now */
|
|
float tgtGain; /* in a smooth gain change, the gain we are aiming for */
|
|
float decay;
|
|
};
|
|
|
|
GAIN_STATE* gainInit(int sampleRate, int nChannels, int bytesPerSample)
|
|
{
|
|
GAIN_STATE* g = (GAIN_STATE*) calloc(1,sizeof(GAIN_STATE)) ;
|
|
if (g)
|
|
{
|
|
g->sampleRate = sampleRate;
|
|
g->nChannels = nChannels;
|
|
g->bytesPerSample = bytesPerSample;
|
|
gainSetTimeConstant(0.1f, g);
|
|
}
|
|
|
|
return g ;
|
|
}
|
|
|
|
void gainFree(GAIN_STATE* g)
|
|
{
|
|
if (g) free(g) ;
|
|
}
|
|
|
|
float gainSetSmoothdB(float dB, GAIN_STATE* g)
|
|
{
|
|
float gain = pow(10.0, 0.05*dB) ;
|
|
|
|
if (g)
|
|
{
|
|
g->isMute = false;
|
|
g->tgtGain = gain ;
|
|
}
|
|
|
|
return dB ;
|
|
}
|
|
|
|
float gainSetImmediatedB(float dB, GAIN_STATE* g)
|
|
{
|
|
dB = gainSetSmoothdB(dB, g) ;
|
|
|
|
if (g)
|
|
g->instGain = g->tgtGain ; // make it instantaneous
|
|
|
|
return dB ;
|
|
}
|
|
|
|
float gainSetSmooth(float percent, GAIN_STATE* g)
|
|
{
|
|
float gaintop = pow(10.0, 0.05*GAIN_MAX_dB) ;
|
|
float gainbottom = pow(10.0, 0.05*GAIN_MIN_dB) ;
|
|
float gain = percent * (gaintop - gainbottom) + gainbottom;
|
|
|
|
if (g)
|
|
{
|
|
g->isMute = false;
|
|
g->tgtGain = gain ;
|
|
}
|
|
|
|
return gain;
|
|
}
|
|
|
|
float gainSetImmediate(float percent, GAIN_STATE* g)
|
|
{
|
|
float gain = gainSetSmooth(percent, g) ;
|
|
|
|
if (g)
|
|
g->instGain = g->tgtGain ; // make it instantaneous
|
|
|
|
return gain;
|
|
}
|
|
|
|
void gainSetMute(GAIN_STATE* g)
|
|
{
|
|
if (g)
|
|
{
|
|
g->isMute = true;
|
|
g->instGain = g->tgtGain = 0.0; // mute is immediate
|
|
}
|
|
}
|
|
|
|
int gainSetTimeConstant(float millis, GAIN_STATE* g)
|
|
{
|
|
if (!g)
|
|
return 0;
|
|
|
|
// we define the time constant millis so that the signal has decayed to 1/2 (-6dB) after
|
|
// millis milliseconds have elapsed.
|
|
// Let T[sec] = millis/1000 = time constant in units of seconds
|
|
//
|
|
// => (1-2^-s)^(T[sec]*sr) = 1/2
|
|
// => 1-2^-s = (1/2)^(1/(T[sec]*sr))
|
|
// => 2^-s = 1 - (1/2)^(1/(T[sec]*sr))
|
|
// => s = -log2(1 - (1/2)^(1 / (T[sec]*sr)))
|
|
|
|
// first 0.5 is rounding constant
|
|
int shift;
|
|
shift = (int)(0.5 - 1.0/log(2.0)*log(1.0 - pow(0.5, 1000.0/(millis * g->sampleRate)))) ;
|
|
if (shift < 1)
|
|
shift = 1 ;
|
|
if (shift > 31)
|
|
shift = 31 ;
|
|
|
|
g->decay = ::pow(2.0, (float) shift);
|
|
|
|
return 1 ; // OK
|
|
}
|
|
|
|
static void gainFeedMono(unsigned char* signal, unsigned char *outsignal, int len, GAIN_STATE *g)
|
|
{
|
|
if (!g)
|
|
return;
|
|
|
|
float tgtGain = g->tgtGain ;
|
|
float gain = g->instGain ;
|
|
unsigned char *bufferEnd = signal + len;
|
|
|
|
if (gain == tgtGain)
|
|
{ // steady state
|
|
while (signal < bufferEnd)
|
|
{
|
|
switch (g->bytesPerSample)
|
|
{
|
|
case 1:
|
|
{
|
|
short int res;
|
|
char *s = (char *) signal;
|
|
char *o = (char *) outsignal;
|
|
res = (short int) (*s * gain);
|
|
*o = (char) (res > INT8_CEILING ? INT8_CEILING : res);
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
long res;
|
|
short int *s = (short int *) signal;
|
|
short int *o = (short int *) outsignal;
|
|
res = (long) (*s * gain);
|
|
*o = (short int) (res > INT16_CEILING ? INT16_CEILING : res);
|
|
}
|
|
break;
|
|
case 4:
|
|
{
|
|
long long res;
|
|
long *s = (long *) signal;
|
|
long *o = (long *) outsignal;
|
|
res = (long long) (*s * gain);
|
|
*o = (long) (res > INT32_CEILING ? INT32_CEILING : res);
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
signal += g->bytesPerSample;
|
|
outsignal += g->bytesPerSample;
|
|
}
|
|
}
|
|
else
|
|
{ // while we are still ramping the gain
|
|
while (signal < bufferEnd)
|
|
{
|
|
switch (g->bytesPerSample)
|
|
{
|
|
case 1:
|
|
{
|
|
short int res;
|
|
char *s = (char *) signal;
|
|
char *o = (char *) outsignal;
|
|
res = (short int) (*s * gain);
|
|
*o = (char) (res > INT8_CEILING ? INT8_CEILING : res);
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
long res;
|
|
short int *s = (short int *) signal;
|
|
short int *o = (short int *) outsignal;
|
|
res = (long) (*s * gain);
|
|
*o = (short int) (res > INT16_CEILING ? INT16_CEILING : res);
|
|
}
|
|
break;
|
|
case 4:
|
|
{
|
|
long long res;
|
|
long *s = (long *) signal;
|
|
long *o = (long *) outsignal;
|
|
res = (long long) (*s * gain);
|
|
*o = (long) (res > INT32_CEILING ? INT32_CEILING : res);
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
signal += g->bytesPerSample;
|
|
outsignal += g->bytesPerSample;
|
|
gain += ((tgtGain-gain) / g->decay);
|
|
}
|
|
g->instGain = gain ;
|
|
}
|
|
}
|
|
|
|
static void gainFeedStereo(unsigned char* signal, unsigned char *outsignal, int len, GAIN_STATE *g)
|
|
{
|
|
if (!g)
|
|
return;
|
|
|
|
float tgtGain = g->tgtGain ;
|
|
float gain = g->instGain ;
|
|
unsigned char *bufferEnd = signal + len;
|
|
|
|
if (gain == tgtGain)
|
|
{ // steady state
|
|
while (signal < bufferEnd)
|
|
{
|
|
switch (g->bytesPerSample)
|
|
{
|
|
case 1:
|
|
{
|
|
short int res;
|
|
char *s = (char *) signal;
|
|
char *o = (char *) outsignal;
|
|
res = (short int) (*s * gain);
|
|
*o = (char) (res > INT8_CEILING ? INT8_CEILING : res);
|
|
s++;
|
|
o++;
|
|
res = (short int) (*s * gain);
|
|
*o = (char) (res > INT8_CEILING ? INT8_CEILING : res);
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
long res;
|
|
short int *s = (short int *) signal;
|
|
short int *o = (short int *) outsignal;
|
|
res = (long) (*s * gain);
|
|
*o = (short int) (res > INT16_CEILING ? INT16_CEILING : res);
|
|
s++;
|
|
o++;
|
|
res = (long) (*s * gain);
|
|
*o = (short int) (res > INT16_CEILING ? INT16_CEILING : res);
|
|
}
|
|
break;
|
|
case 4:
|
|
{
|
|
long long res;
|
|
long *s = (long *) signal;
|
|
long *o = (long *) outsignal;
|
|
res = (long long) (*s * gain);
|
|
*o = (long) (res > INT32_CEILING ? INT32_CEILING : res);
|
|
s++;
|
|
o++;
|
|
res = (long long) (*s * gain);
|
|
*o = (long) (res > INT32_CEILING ? INT32_CEILING : res);
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
signal += 2 * g->bytesPerSample;
|
|
outsignal += 2 * g->bytesPerSample;
|
|
}
|
|
}
|
|
else
|
|
{ // while we are still ramping the gain
|
|
while (signal < bufferEnd)
|
|
{
|
|
switch (g->bytesPerSample)
|
|
{
|
|
case 1:
|
|
{
|
|
short int res;
|
|
char *s = (char *) signal;
|
|
char *o = (char *) outsignal;
|
|
res = (short int) (*s * gain);
|
|
*o = (char) (res > INT8_CEILING ? INT8_CEILING : res);
|
|
s++;
|
|
o++;
|
|
res = (short int) (*s * gain);
|
|
*o = (char) (res > INT8_CEILING ? INT8_CEILING : res);
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
long res;
|
|
short int *s = (short int *) signal;
|
|
short int *o = (short int *) outsignal;
|
|
res = (long) (*s * gain);
|
|
*o = (short int) (res > INT16_CEILING ? INT16_CEILING : res);
|
|
s++;
|
|
o++;
|
|
res = (long) (*s * gain);
|
|
*o = (short int) (res > INT16_CEILING ? INT16_CEILING : res);
|
|
}
|
|
break;
|
|
case 4:
|
|
{
|
|
long long res;
|
|
long *s = (long *) signal;
|
|
long *o = (long *) outsignal;
|
|
res = (long long) (*s * gain);
|
|
*o = (long) (res > INT32_CEILING ? INT32_CEILING : res);
|
|
s++;
|
|
o++;
|
|
res = (long long) (*s * gain);
|
|
*o = (long) (res > INT32_CEILING ? INT32_CEILING : res);
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
signal += 2 * g->bytesPerSample;
|
|
outsignal += 2 * g->bytesPerSample;
|
|
gain += ((tgtGain-gain) / g->decay);
|
|
}
|
|
g->instGain = gain ;
|
|
}
|
|
}
|
|
|
|
static void gainFeedMulti(unsigned char* signal, unsigned char *outsignal, int len, GAIN_STATE *g)
|
|
{
|
|
if (!g)
|
|
return;
|
|
|
|
float tgtGain = g->tgtGain ;
|
|
float gain = g->instGain ;
|
|
unsigned char *bufferEnd = signal + len;
|
|
|
|
if (gain == tgtGain)
|
|
{ // steady state
|
|
while (signal < bufferEnd)
|
|
{
|
|
switch (g->bytesPerSample)
|
|
{
|
|
case 1:
|
|
{
|
|
short int res;
|
|
int i ;
|
|
char *s = (char *) signal;
|
|
char *o = (char *) outsignal;
|
|
for (i = 0 ; i < g->nChannels ; i++)
|
|
{
|
|
res = (short int) (*s * gain);
|
|
*o = (char) (res > INT8_CEILING ? INT8_CEILING : res);
|
|
s++;
|
|
o++;
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
long res;
|
|
int i ;
|
|
short int *s = (short int *) signal;
|
|
short int *o = (short int *) outsignal;
|
|
for (i = 0 ; i < g->nChannels ; i++)
|
|
{
|
|
res = (long) (*s * gain);
|
|
*o = (short int) (res > INT16_CEILING ? INT16_CEILING : res);
|
|
s++;
|
|
o++;
|
|
}
|
|
}
|
|
break;
|
|
case 4:
|
|
{
|
|
long long res;
|
|
int i ;
|
|
long *s = (long *) signal;
|
|
long *o = (long *) outsignal;
|
|
for (i = 0 ; i < g->nChannels ; i++)
|
|
{
|
|
res = (long long) (*s * gain);
|
|
*o = (long) (res > INT32_CEILING ? INT32_CEILING : res);
|
|
s++;
|
|
o++;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
signal += g->nChannels * g->bytesPerSample;
|
|
outsignal += g->nChannels * g->bytesPerSample;
|
|
}
|
|
}
|
|
else
|
|
{ // while we are still ramping the gain
|
|
while (signal < bufferEnd)
|
|
{
|
|
int i ;
|
|
|
|
switch (g->bytesPerSample)
|
|
{
|
|
case 1:
|
|
{
|
|
short int res;
|
|
char *s = (char *) signal;
|
|
char *o = (char *) outsignal;
|
|
for (i = 0 ; i < g->nChannels ; i++)
|
|
{
|
|
res = (short int) (*s * gain);
|
|
*o = (char) (res > INT8_CEILING ? INT8_CEILING : res);
|
|
s++;
|
|
o++;
|
|
}
|
|
}
|
|
break;
|
|
case 2:
|
|
{
|
|
long res;
|
|
short int *s = (short int *) signal;
|
|
short int *o = (short int *) outsignal;
|
|
for (i = 0 ; i < g->nChannels ; i++)
|
|
{
|
|
res = (long) (*s * gain);
|
|
*o = (short int) (res > INT16_CEILING ? INT16_CEILING : res);
|
|
s++;
|
|
o++;
|
|
}
|
|
}
|
|
case 4:
|
|
{
|
|
long long res;
|
|
long *s = (long *) signal;
|
|
long *o = (long *) outsignal;
|
|
for (i = 0 ; i < g->nChannels ; i++)
|
|
{
|
|
res = (long long) (*s * gain);
|
|
*o = (long) (res > INT32_CEILING ? INT32_CEILING : res);
|
|
s++;
|
|
o++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
signal += g->nChannels * g->bytesPerSample;
|
|
outsignal += g->nChannels * g->bytesPerSample;
|
|
gain += ((tgtGain-gain) / g->decay);
|
|
}
|
|
g->instGain = gain ;
|
|
}
|
|
}
|
|
|
|
void gainFeed(unsigned char* signal, unsigned char *outsignal, int len, GAIN_STATE* g)
|
|
{
|
|
if (!g)
|
|
return;
|
|
|
|
/* if the gain is 0dB, and we are not currently ramping, shortcut. */
|
|
if (g->instGain == 1.0 && g->instGain == g->tgtGain)
|
|
{
|
|
if (signal != outsignal)
|
|
memcpy(outsignal, signal, len);
|
|
|
|
return ;
|
|
}
|
|
switch (g->nChannels)
|
|
{
|
|
case 1:
|
|
gainFeedMono(signal, outsignal, len, g) ;
|
|
break ;
|
|
case 2:
|
|
gainFeedStereo(signal, outsignal, len, g) ;
|
|
break ;
|
|
default:
|
|
gainFeedMulti(signal, outsignal, len, g) ;
|
|
break ;
|
|
}
|
|
}
|