phoned/lib/libpvf/rockwell.c

767 lines
25 KiB
C
Raw Normal View History

2005-06-13 22:11:33 -04:00
/*
*
* rockwell.c
*
* Conversion subroutines for:
*
* signed linear 16 bit audio words <--> Rockwell 2, 3, or 4 bit ADPCM
*
* -----------------------------------------------------------------------
*
* Floating point version by
* Torsten Duwe <duwe@informatik.uni-erlangen.de>
*
* very remotely derived from Rockwell's d.asm.
* Converted to C and simplified by Torsten Duwe 1995
*
* -----------------------------------------------------------------------
*
* Floating point version was removed on Fri Jan 17 13:23:42 1997
*
* -----------------------------------------------------------------------
*
* Fixed point version
* by Peter Jaeckel <atmpj@ermine.ox.ac.uk> [1996-1997].
*
*
* Marc Eberhard dubbed this file the "Rocky Horror Picture Show".
* And he is right.
* PJ, Tue Jan 14 17:04:13 1997
*
*
* The fixed point version is a complete rewrite, I reused only some
* tiny fragments of the floating point code.
*
* The fixed point version actually manages to get output that is
* identical to that of the DOS executables distributed by Rockwell. It
* is noticeably better than that of the floating point version.
* Rockwell uses all sorts of dirty tricks in their fixed point code
* which I don't necessarily understand as I am not an expert on
* compression formats. Read the comments throughout the code for
* further explanation.
*
* Technical Note : The compressor does not do any silence deletion
* which is exactly what the Rockwell sources do as well. The
* decompressor detects silence codewords and inserts the required
* silence period if RV_HONOUR_SILENCE_CODEWORDS is defined. Otherwise,
* the silence codeword and the following integer word determining the
* length of the silence are just gobbled up as compressed data and may
* lead to some temporarily increased noise level after that.
*
* The Rockwell sources were taken from http://www.nb.rockwell.com/ref/adpcm.html .
*
* Peter Jaeckel, Fri Jan 17 14:40:32 1997
*
* -----------------------------------------------------------------------
*
* $Id: rockwell.c,v 1.6 1999/03/16 09:59:20 marcs Exp $
*
*/
#define RV_HONOUR_SILENCE_CODEWORDS
#include "../include/voice.h"
/*
PJ:
A first attempt to implement basically all that the original
Rockwell D.ASM code does in C. I never had to deal with assembler
before, so be lenient when you judge me, please...
NB: The guts of this code are not very pretty to look at.
RV_ stands for Rockwell Voice.
*/
#define RV_PNT98 32113 /* 0.98 */
#define RV_PNT012 393 /* 0.012 */
#define RV_PNT006 197 /* 0.006 */
#define RV_QDLMN 0x1F /* QDLMN = IQDLMN = 2.87 mV. */
#define RV_DEMPCF 0x3333 /* 0.4 */
#define RV_PDEMPCF 0x3333 /* 0.4 */
#define RV_QORDER 8 /* Total delay line length for the pole and zero delay lines */
/*
Design Notes: Relation of QDataIndex to position in QData Buffer.
the rotation is done by the QDataIndex. This variable always
points to the data to be multiplied by the coefficient a1. The
coefficients, a1..a2 and b1..b6, stay in the same relative
position in the coefficient array. Updates to these values are
done in place. Illustration belows shows the value of QDataIndex
and the Delay Line data in relation to the coefficient array.
Position
in Qdata Start 2nd 3rd 4th
-----------------------------------------
0 a1 Y(n-1) Y(n-2) Q(n-1) Q(n-2)
1 a2 Y(n-2) Q(n-1) Q(n-2) Q(n-3)
2 b1 Q(n-1) Q(n-2) Q(n-3) Q(n-4)
3 b2 Q(n-2) Q(n-3) Q(n-4) Q(n-5)
4 b3 Q(n-3) Q(n-4) Q(n-5) Q(n-6)
5 b4 Q(n-4) Q(n-5) Q(n-6) Y(n-1)
6 b5 Q(n-5) Q(n-6) Y(n-1) Y(n-2)
7 b6 Q(n-6) Y(n-1) Y(n-2) Q(n-1)
-----------------------------------------
QDataIndex 0 7 6 5
-----------------------------------------
*/
static vgetty_s_int16 RV_pzTable[8]; /* Coefficient Table for the pole and zero linear predictor. */
/* a1 a2 b1 b2 b3 b4 b5 b6 */
static vgetty_s_int16 RV_QdataIndex = 0; /* Delay line pointer to the coefficient a1. */
static vgetty_s_int16 RV_Qdata[RV_QORDER]; /* Delay line. */
#ifdef POSTFILTER /* DON'T USE THIS */
/*
The POSTFILTER code is in Rockwell's original D.ASM, too.
They too, don't use it in their distributed executables
though. I have no idea under what circumstances it might be
useful, I just left the code in here as I went through the
effort of writing it before I realised that it appears to be
useless here.
*/
static vgetty_s_int16 RV_QPdata[RV_QORDER];
static vgetty_s_int16 RV_QPPdata[RV_QORDER];
#endif
static vgetty_s_int16 RV_LastNu = 0; /* Last Nu value. */
static vgetty_s_int16 RV_Dempz = 0; /* De-emphasis filter delay line (one element). */
static vgetty_s_int16 RV_NewQdata = 0; /* Adaptive quantizer output. */
static vgetty_s_int16 RV_NewAppData = 0; /* Temporay data storage */
/* ML2bps, ML3bps, and ML4bps are combined in mul[][], just like Torsten suggested */
static vgetty_s_int16 RV_mul[3][16] =
{ /* Multiplier table to calculate new Nu for 2/3/4 BPS. */
{0x3333, 0x199A, 0x199A, 0x3333},
{0x3800, 0x2800, 0x1CCD, 0x1CCD, 0x1CCD, 0x1CCD, 0x2800, 0x3800},
{0x4CCD, 0x4000, 0x3333, 0x2666, 0x1CCD, 0x1CCD, 0x1CCD, 0x1CCD, 0x1CCD, 0x1CCD, 0x1CCD, 0x1CCD, 0x2666, 0x3333, 0x4000, 0x4CCD}
};
/* Zeta2bps, Zeta3bps, and Zeta4bps are combined in Zeta[][],
just like Torsten suggested */
static vgetty_u_int16 RV_Zeta[3][16] =
{ /* Multiplier table for 2/3/4 BPS to calculate inverse */
/* quantizer output. This number, index by the code */
/* word, times Nu is the inverse quantizer output. */
{0xCFAE, 0xF183, 0x0E7D, 0x3052},
{0xBB23, 0xD4FE, 0xE7CF, 0xF828, 0x07D8, 0x1831, 0x2B02, 0x44DD},
{0xA88B, 0xBDCB, 0xCC29, 0xD7CF, 0xE1D8, 0xEAFB, 0xF395, 0xFBE4, 0x041C, 0x0C6B, 0x1505, 0x1E28, 0x2831, 0x33C7, 0x4235, 0x5775}
};
static vgetty_s_int16 *RV_mul_p;
static vgetty_s_int16 *RV_Zeta_p;
static vgetty_u_int16 RV_silence_words[3] = {0x13ec, 0x23de, 0xc11c};
static vgetty_u_int16 RV_silence_word;
/* Maximum limit for Nu. Changes based on 2, 3, or 4 BPS selected.
Initialization routine updates this value. */
static vgetty_s_int16 RV_QDelayMX = 0;
/* Array index by BPS-2 for updating QDelayMX */
static vgetty_s_int16 RV_QDelayTable[3] = {0x54C4,0x3B7A,0x2ED5}; /* 2.01V, 1.41V, 1.11V */
/*
Macro definitions used in the decompression, interpolation, and
compression functions.
*/
static vgetty_s_int32 RV_max_local_int16,RV_min_local_int16;
static vgetty_s_int64 RV_max_local_int32,RV_min_local_int32;
#define RV_clip_16(a) ((a)<RV_min_local_int16?RV_min_local_int16:(a)>RV_max_local_int16?RV_max_local_int16:(a))
#define RV_clip_32(a) ((a)<RV_min_local_int32?RV_min_local_int32:(a)>RV_max_local_int32?RV_max_local_int32:(a))
#define HIWORD(x) (((vgetty_u_int32)x) >> 16)
#define LOWORD(x) ((vgetty_u_int32)(((unsigned int)x) & 0xffff))
#define LOBYTE(x) ((unsigned int)(((unsigned int)x) & 0xff))
#define RV_round_32_into_16(x) (vgetty_s_int16)((((vgetty_u_int32)x)>>16)+((((vgetty_u_int32)x)>>15)&0x0001))
/* In order to stay as close as possible to the original assembler
(a kludge, I know), we simulate the system's register(s) below */
static vgetty_s_int16 RV_di;
/* Utilities.
Routines that both the decompressor and the compressor use. */
/*
pzPred
Linear predictor coefficient update routine. Local to this module.
Inputs:
CX = Q(n), i.e. WORD PTR NewQData
Output: DI points to (QDataIndex+7)%8.
*/
static void RV_pzPred(vgetty_s_int16 cx){
/*
A little explanation is required here. Rockwell uses 16bit
integers to represent numbers in [-1,1). They take 0x8000 to be -1
and 0x7fff to be 0.999969482, i.e. the ints -32768 to 32767 are
normalised over 32768 into the required interval. The product of
two such numbers is supposed to be, again, in the range [-1,1).
I know that this is mathematically incorrect, but that's how they
do it, just read on.
The "adjustment" that is mentioned in D.ASM can be understood by
the following example: Assume you want to multiply -1 with -0.5. In
integers, that's imul 0x8000, 0xc000 (I know, it does actulally
require a register), i.e. -32768*-16384. The result is 0x20000000.
They only want to keep a 16 bit result, thus they need to round.
First, however, an adjustment for the moved decimal point is
required. The upper 16 bit of 0x20000000 are 0x2000 which
corresponds only to 8192/32768=0.25 ! Thus, all the bits need to be
left shifted by one place and the result will be 0x4000 which
correctly corresponds to 0.5 now. This confusion is due to the fact
that the original two numbers in total have two bits representing
two different signs and the result, which is again represented by a
total of 32 bits, needs only one sign bit. Thus, the result in 32
bits has effectively one more data bit available than the total of
the two multiplicands. The nature of integer arithmetics feeds that
bit in from the left behind the sign bit. A consequence of this is
that the two leftmost bits of the resulting 32 bit integer are
always equal, apart from one case, namely 0x8000*0x8000, or
-32768*-32768, which results in 0x40000000. Arithmetically, we
would expect this to be decimal-point-adjusted to the 16 bit
representation 0x7fff. The Rockwell assembler code, however, just
maps this to 0x8000, i.e. -1, by ignoring the special case of
0x8000*0x8000. This explains the cryptic warnings like
; Determine sign of Q(n) * Y(n-1)
;
; Do not change the sign determination method!
in D.ASM. Personally, this is the first time ever I have seen
anyone using arithmetics like -1 * -1 = -1 whilst -1 * -0.99 = 0.99 ;-).
So, after this type of decimal-point-adjustment they then round off
the lower 16 bit and just take the upper 16 to be the result.
*/
static vgetty_s_int32 x,y; /* local accumulator(s) */
static vgetty_s_int16 di;
static int i;
di = RV_QdataIndex;
/* Calculate coefficients a1 a2 b1 b2 b3 b4 b5 b6 . */
for (i = 0; i < 8; i++)
{
x = RV_pzTable[i] * ((vgetty_s_int32) RV_PNT98);
x <<= 1; /*
Rockwell-adjust for decimal point shift, then round off
lower 16 bits to obtain a 16 bit representation of the
result.
*/
x = RV_round_32_into_16 (x);
/* cx contains the NewQdata=Q(n) */
y = ((vgetty_s_int32) cx) * ((vgetty_s_int32) RV_Qdata[di]);
y <<= 1; /* Rockwell-adjust for decimal point shift. */
y = RV_round_32_into_16 (y);
x += (y < 0 ? -1 : 1) * (i < 2 ? RV_PNT012 : RV_PNT006);
/* i<2 ? The a's get RV_PNT012. All b's get RV_PNT006. */
/*
The result of a multiplication needs adjusting & rounding.
The result of an addition/subtraction needs clipping.
*/
RV_pzTable[i] = RV_clip_16 (x);
di++;
di %= 8;
}
}
/*
Taken from D.ASM:
Design Notes: Sum of Multiplications.
Multiplications are 16-bit signed numbers producing a signed 32-bit
result. The two operands are usually numbers less than one; this
requires a 32-bit shift by the macro "adjust" to bring the decimal
point in line. The 32-bit addition is two 16-bit additions with
carry. The "clip" macro checks for overflow and limits the result of
the addition to 0x7fffffff or 0x80000000 (for 32-bit results), or
0x7fff or 0x8000 (for 16-bit results). Note that the "clip" macro
depends on the flags being set because of an addition; the 80x86
processor does not update these flags because of a move operation.
*/
static vgetty_s_int32 RV_XpzCalc(vgetty_s_int16 cx){
/*
Linear pole and zero predictor calculate. CX,BX register pair is the
32 bit accumulator. Local to this module.
Input: CX = Initial Value. BX set to zero.
Output: CX,BX contains the result of the sum of products.
Also, DI points to (QDataIndex+7)%8.
*/
static vgetty_s_int32 x; /* local accumulator */
static vgetty_s_int64 sum;
int i;
RV_di = RV_QdataIndex;
sum = ((vgetty_s_int32) cx) << 16;
for (i = 0; i < 8; i++)
{
x = ((vgetty_s_int32) RV_pzTable[i]) * ((vgetty_s_int32) RV_Qdata[RV_di]);
x <<= 1; /* Rockwell-adjust for decimal point shift. */
sum += x;
sum = RV_clip_32 (sum);
RV_di++;
RV_di %= 8;
}
RV_di = (RV_QdataIndex + 7) % 8;
return (vgetty_s_int32) sum;
}
static void RV_Reset(int bps){
int i;
vgetty_u_int16 tmp_int16 = 0;
vgetty_u_int32 tmp_int32 = 0;
tmp_int16 = ~tmp_int16;
tmp_int16 >>= 1;
RV_max_local_int16 = tmp_int16;
RV_min_local_int16 = tmp_int16;
RV_min_local_int16 = -RV_min_local_int16;
RV_min_local_int16--;
tmp_int32 = ~tmp_int32;
tmp_int32 >>= 1;
RV_max_local_int32 = tmp_int32;
RV_min_local_int32 = tmp_int32;
RV_min_local_int32 = -RV_min_local_int32;
RV_min_local_int32--;
RV_QdataIndex = 0;
for (i = 0; i < RV_QORDER; i++)
{
RV_Qdata[i] = 0;
#ifdef POSTFILTER
RV_QPdata[i] = 0;
RV_QPPdata[i] = 0;
#endif
}
RV_Dempz = 0;
RV_NewQdata = 0;
RV_NewAppData = 0;
RV_LastNu = RV_QDLMN;
RV_QDelayMX = RV_QDelayTable[bps-2];
RV_silence_word = RV_silence_words[bps-2];
RV_mul_p = RV_mul[bps-2];
RV_Zeta_p = (vgetty_s_int16 *)RV_Zeta[bps-2];
}
#ifdef POSTFILTER
static vgetty_s_int16 RV_P8Table[6] = /* Adaptive post filter number 1 coefficients */
{0x6666, 0x51eb, 0x4189, 0x346d, 0x29f1, 0x218e};
static vgetty_s_int16 RV_PM5Table[6] = /* Adaptive post filter number 2 coefficients */
{0xc000, 0xe000, 0xf000, 0xf800, 0xfc00, 0xfe00};
static vgetty_s_int32 RV_App1Calc(vgetty_s_int16 cx){
/* Adaptive post filter number 1 */
/*
Load pointers to the predictor table and the pointer
to the coefficient a1.
*/
static vgetty_s_int32 x; /* local accumulator */
static vgetty_s_int64 sum;
static vgetty_s_int16 di;
int i;
di = RV_QdataIndex;
RV_NewAppData = cx;
sum = 0;
for (i = 0; i < 8; i++)
{
x = ((vgetty_s_int32) RV_pzTable[i]) * ((vgetty_s_int32) RV_P8Table[i]);
x <<= 1; /*
Rockwell-adjust for decimal point shift, then round off
lower 16 bits to obtain a 16 bit representation of the
result.
*/
x = ((vgetty_s_int32)
(RV_round_32_into_16 (x))) * ((vgetty_s_int32) RV_QPdata[di]);
x <<= 1; /* Rockwell-adjust for decimal point shift. */
sum += x;
sum = RV_clip_32 (sum);
di++;
di %= 8;
}
cx = HIWORD(sum);
di = (RV_QdataIndex + 7) % 8;
RV_QPdata[di] = cx; /* drop b6, now a1 Qdata */
di += 2;
di %= 8;
RV_QPdata[di] = RV_NewAppData; /* drop a2, now b1 Qdata */
return (vgetty_s_int32) sum;
}
static vgetty_s_int32 RV_App2Calc(vgetty_s_int16 cx){
/* Adaptive post filter number 2 */
/*
Load pointers to the predictor table and the pointer
to the coefficient a1.
*/
static vgetty_s_int32 x; /* local accumulator */
static vgetty_s_int64 sum;
static vgetty_s_int16 di;
int i;
di = RV_QdataIndex;
RV_NewAppData = cx;
sum = 0;
for (i = 0; i < 8; i++)
{
x = ((vgetty_s_int32) RV_pzTable[i]) * ((vgetty_s_int32) RV_PM5Table[i]);
x <<= 1; /*
Rockwell-adjust for decimal point shift, then round off
lower 16 bits to obtain a 16 bit representation of the
result.
*/
x = ((vgetty_s_int32) RV_round_32_into_16 (x)) * ((vgetty_s_int32) RV_QPPdata[di]);
x <<= 1; /* Rockwell-adjust for decimal point shift. */
sum += x;
sum = RV_clip_32 (sum);
di++;
di %= 8;
}
cx = HIWORD(sum);
di = (RV_QdataIndex + 7) % 8;
RV_QPPdata[di] = cx; /* drop b6, now a1 Qdata */
di += 2;
di %= 8;
RV_QPPdata[di] = RV_NewAppData; /* drop a2, now b1 Qdata */
return (vgetty_s_int32) sum;
}
#endif
static vgetty_s_int16 RV_DecomOne(vgetty_s_int16 ax, vgetty_s_int16 bx){
/*
RVDecomOne
Decode a code word. Local to this module.
Inputs:
AX = ML, adaptive multiplier for Nu.
BX = Zeta, base inverse quantizer value, modified by Nu.
Also, updates QdataIndex to (QdataIndex+7)%8 .
*/
static vgetty_s_int16 si;
static vgetty_s_int32 LastNu_bak;
static vgetty_s_int32 x; /* local accumulator */
static vgetty_s_int64 sum;
LastNu_bak = RV_LastNu;
x = ((vgetty_s_int32) ax) * ((vgetty_s_int32) RV_LastNu);
x <<= 1; /* Rockwell-adjust for decimal point shift. */
/* Round and Multiply by 4 */
x = (RV_round_32_into_16 (x) * ((vgetty_s_int32)4));
RV_LastNu = RV_clip_16 (x);
if (RV_LastNu < RV_QDLMN)
RV_LastNu = RV_QDLMN;
else if (RV_LastNu > RV_QDelayMX)
RV_LastNu = RV_QDelayMX;
x = bx * LastNu_bak; /* Zeta * LastNu */
x <<= 1; /* Rockwell-adjust for decimal point shift. */
x = (RV_round_32_into_16 (x) * ((vgetty_s_int32)4));
RV_NewQdata = RV_clip_16 (x);
sum = RV_XpzCalc (RV_NewQdata); /* Compute (Xp+z)(n) + Q(n) */
si = HIWORD(sum); /* Y(n) done, save in SI for later */
#ifdef POSTFILTER
sum = RV_App1Calc ((vgetty_s_int16)(HIWORD(sum)));
sum = RV_App2Calc ((vgetty_s_int16)(HIWORD(sum)));
#endif
/* Use a de-emphasis filter on Y(n) to remove the effects */
/* of the emphasis filter used during compression. */
x = RV_DEMPCF * RV_Dempz;
x <<= 1; /* Rockwell-adjust for decimal point shift. */
sum += x;
sum = RV_clip_32 (sum);
RV_Dempz = HIWORD(sum);
/* Update predictor filter coefficients. */
RV_pzPred (RV_NewQdata);
RV_Qdata[RV_di] = si; /* drop b6, now a1 Qdata */
/* Update delay line at the a1(n) table entry position. */
RV_QdataIndex = RV_di;
RV_di += 2;
RV_di %= 8;
RV_Qdata[RV_di] = RV_NewQdata; /* drop a2, now b1 Qdata */
return RV_Dempz;
}
/* Compression!! */
/* cd2x, cd3x, and cd4x are combined in cd[][], just like Torsten suggested */
static vgetty_s_int16 RV_cdx[3][16] =
{
{0x1F69},
{0x1005, 0x219A, 0x37F0},
{0x0843, 0x10B8, 0x1996, 0x232B, 0x2DFC, 0x3B02, 0x4CD6}
};
static vgetty_s_int16 RV_cdx_length[3] = { 1,3,7 };
static vgetty_s_int16 *RV_cd;
static vgetty_s_int16 RV_cd_length;
static vgetty_s_int32 RV_ComOne(vgetty_s_int16 ax)
{
/*
RVComOne
Code a word into a bps bit codeword. Local to this module.
Inputs:
AX = X(n)
*/
int i;
static vgetty_s_int16 adc16z1 = 0; /* X(n-1) */
static vgetty_s_int16 NewXpz = 0; /* Xp+z(n) */
static vgetty_s_int16 bx,cx,dx;
static vgetty_s_int32 x, y; /* local accumulator */
static vgetty_s_int64 sum;
sum = RV_XpzCalc ((vgetty_s_int16)0); /* Compute Xp+z(n) */
NewXpz = HIWORD(sum);
x = ((vgetty_s_int32) adc16z1) * RV_PDEMPCF;
x <<= 1; /* Rockwell-adjust for decimal point shift. */
sum += x;
sum = RV_clip_32 (sum);
cx = HIWORD(sum);
/* Optimise any of this at your own peril ! */
x = cx;
x -= ax;
cx = RV_clip_16 (x);
bx = cx;
if (bx<0) bx = -bx;
cx = -cx;
adc16z1 = ax;
y = RV_LastNu; /*
Optimise any of this at your own peril !
Ideally, the important variables
here should all be declared volatile.
If you change any of this, your
optimiser might break it !
*/
for (i=0;i<RV_cd_length;i++){
x = ((vgetty_s_int32)RV_cd[i]) * y;
x <<= 1; /* Rockwell-adjust for decimal point shift. */
/* Round and Multiply by 4 */
x = (RV_round_32_into_16 (x) * ((vgetty_s_int32)4));
dx = RV_clip_16 (x);
if (bx<dx) break;
}
i++;
if (cx<0){
i -= RV_cd_length;
i--;
i = -i;
} else i+= RV_cd_length;
ax = i;
x = ((vgetty_s_int32)RV_mul_p[i]) * y;
x <<= 1; /* Rockwell-adjust for decimal point shift. */
/* Round and Multiply by 4 */
x = (RV_round_32_into_16 (x) * ((vgetty_s_int32)4));
RV_LastNu = RV_clip_16 (x);
if (RV_LastNu < RV_QDLMN)
RV_LastNu = RV_QDLMN;
else if (RV_LastNu > RV_QDelayMX)
RV_LastNu = RV_QDelayMX;
/* Make a new inverse quantizer value. */
x = ((vgetty_s_int32)RV_Zeta_p[i]) * y;
x <<= 1; /* Rockwell-adjust for decimal point shift. */
/* Round and Multiply by 4 */
x = (RV_round_32_into_16 (x) * ((vgetty_s_int32)4));
cx = RV_clip_16 (x);
/* Update predictor filter coefficients. */
RV_pzPred (cx);
/* Update delay line at the a1(n) table entry position. */
RV_QdataIndex += 7;
RV_QdataIndex %= 8;
x = NewXpz;
x += cx;
RV_Qdata[RV_QdataIndex] = RV_clip_16 (x); /* drop b6, now a1 Qdata. */
RV_Qdata[(RV_QdataIndex+2)%8] = cx; /* drop a2, now b1 Qdata. */
return (vgetty_s_int32) ax;
}
#ifdef RV_HONOUR_SILENCE_CODEWORDS /* Honour silence codewords and insert the requested silence */
static void put_silence(vgetty_s_int32 num_samples, FILE * out)
{
/* Write num_samples 16 bit ints of value 0 */
num_samples *= 2;
while (num_samples && (putc (0, out) != EOF))
num_samples--;
if (num_samples)
{
perror ("write silence");
exit (1);
}
}
static int getcodeword(FILE * in, vgetty_s_int32 *codeword){
/*
Rockwell modems always pass on 16bit ints in little-endian format.
Therefore, we have to read the data the same way if we don't want
to miss a silence codeword.
*/
static int c;
if ((c = getc (in)) == EOF) return 0;
*codeword = c;
if ((c = getc (in)) == EOF) return 8;
*codeword |= (c<<8);
return 16;
}
int rockwelltopvf (FILE *fd_in, FILE *fd_out, int nbits, pvf_header *header_out)
{
vgetty_s_int32 w; /* packed compressed codewords */
int c; /* single compressed codeword */
vgetty_s_int32 mask=(1<<nbits)-1; /* bitmask for the decompression */
vgetty_s_int32 a = 0; /* local accumulator */
int valbits = 0; /* number of bits valid in accumulator */
/* The pvf header should have been written by now, start with the decompression */
/* Reset the coefficient table for the pole and zero linear predictor. */
for (w = 0; w < 8; w++) RV_pzTable[w] = 0;
RV_Reset (nbits);
/*
The algorithm below (copied from Torsten Duwe's code)
takes care of bit concatenation.
*/
while ((c=getcodeword(fd_in,&w)))
/*
Not using the pvf library generic read_bits interface because
Rockwell modems always pass on 16bit ints in little-endian format.
Therefore, we have to read the data the same way if we don't want
to miss a silence codeword.
*/
{
if (w == RV_silence_word)
{
getcodeword(fd_in,&w);
put_silence (w, fd_out);
RV_Reset (nbits);
valbits = 0;
continue;
}
a |= w<<valbits;
valbits += c;
while (valbits >= nbits)
{
c = a & mask;
w = RV_DecomOne(RV_mul_p[c],RV_Zeta_p[c]);
w <<= 8; /* The pvf routines expect a 24bit int */
header_out->write_pvf_data(fd_out, w);
a >>= nbits;
valbits -= nbits;
}
}
return(OK);
}
#else /* Silence codewords are just gobbled up as data */
int rockwelltopvf (FILE *fd_in, FILE *fd_out, int nbits, pvf_header *header_out)
{
state_t s = init_state;
int c; /* single compressed codeword or EOF */
/* The pvf header should have been written by now, start with the decompression */
/* Reset the coefficient table for the pole and zero linear predictor. */
for (c = 0; c < 8; c++) RV_pzTable[c] = 0;
RV_Reset (nbits);
while ((c = read_bits_reverse(fd_in,&s,nbits)) != EOF)
header_out->write_pvf_data(fd_out, /* The pvf routines expect a 24bit int */
((vgetty_s_int32)RV_DecomOne(RV_mul_p[c],RV_Zeta_p[c]))<<8 );
return(OK);
}
#endif
int pvftorockwell (FILE *fd_in, FILE *fd_out, int nbits, pvf_header *header_in)
{
state_t s = init_state;
vgetty_s_int32 w; /* uncompressed audio word */
/* Reset the coefficient table for the pole and zero linear predictor. */
for (w = 0; w < 8; w++) RV_pzTable[w] = 0;
RV_Reset (nbits);
RV_cd = RV_cdx[nbits-2];
RV_cd_length = RV_cdx_length[nbits-2];
/* The rmd header should have been written by now. Do the compression. */
while (1)
{
w = header_in->read_pvf_data(fd_in);
if (feof(fd_in))
break;
/* The pvf routines work on 24bit ints */
write_bits_reverse(fd_out, &s, nbits, (int)RV_ComOne((vgetty_s_int16)(w>>8)) );
}
if (s.nleft > 0) write_bits_reverse(fd_out, &s, 8 - s.nleft, 0x00);
return(OK);
}
/*
* I borrowed this code from wav.c - it assumes the pvf is a certain size and
* I'm not sure this is a good assumption -- Bill Nugent <whn@topelo.lopi.com>
*/
int rockwellpcmtopvf (FILE *fd_in, FILE *fd_out, int nbits, pvf_header *header_out)
{
int d;
/* 8 bit PCM */
while ((d = getc(fd_in)) != EOF)
{
d -= 0x80;
d <<= 16;
header_out->write_pvf_data(fd_out, d);
}
return(OK);
}
int pvftorockwellpcm (FILE *fd_in, FILE *fd_out, int nbits, pvf_header *header_in)
{
int data;
/* 8 bit PCM */
while((data = header_in->read_pvf_data(fd_in)) != EOF)
{
data >>=16;
if (data > 0x7f)
data = 0x7f;
if (data < -0x80)
data = -0x80;
putc(data+0x80,fd_out);
};
return(OK);
}