/* 
 * pvfadpcm.c
 *
 * Conversion pvf <--> ADPCM.
 * Based on the ZyXEL vcnvt program.
 *
 */

char pvfadpcm_c[] = "$Id: pvfadpcm.c,v 1.17 1995/04/11 19:18:32 marc Exp $";

#include "voice.h"
#include "pvflib.h"

typedef struct {
        int word;
        int nleft;
} state_t;

static state_t state_init = { 0x0000, 0 };

static int Mx[3][8] = {
    { 0x3800, 0x5600, 0,0,0,0,0,0 },
    { 0x399a, 0x3a9f, 0x4d14, 0x6607, 0,0,0,0 },
    { 0x3556, 0x3556, 0x399A, 0x3A9F, 0x4200, 0x4D14, 0x6607, 0x6607 },
};

static int bitmask[9] = { 0, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff }; 

static int
get_bits _P3((nbits, s, in), int nbits, state_t *s, FILE *in)
{
    while( s->nleft < nbits) {
	int d=getc(in);
	if (d==EOF) return EOF;
	s->word = (s->word<<8) | d;
	s->nleft+=8;
    }
    s->nleft -= nbits;
    return (s->word >> s->nleft) & bitmask[nbits];
}

static void
put_bits _P4((data, nbits, s, out),
	     int data, int nbits, state_t *s, FILE *out)
{
    s->word = (s->word<<nbits) | (data & bitmask[nbits]);
    s->nleft += nbits;
    while(s->nleft>=8) {
	int d = (s->word >> (s->nleft-8));
	putc(d&255, out);
	s->nleft-=8;
    }
}

static void
adpcmtopvf _P4((nbits, rom, in, out),
	       int nbits, int rom, FILE *in, FILE *out)
{
    state_t s;
    int a=0, d=5;
    char buf[16];
    
    fread(buf, 16, 1, in);
    nbits=buf[10]+1;
    if (strncmp(buf, "ZyXEL", 5) != 0 || nbits<2 || nbits>4)
	ERRORRETURN("unsupported voice file format");
    
    s=state_init;

    while (1) {
	int sign;
	int e=get_bits(nbits, &s, in);
	if(e==EOF) break;

	if(nbits==4 && e==0) d=4;
	
	sign = ( e>>(nbits-1) ) ? -1 : 1 ;
	e = e&bitmask[nbits-1];
	
	if(rom >= 610 && rom < 612) {
	    /* modified conversion algorithm for ROM >= 6.10 */
	    a = (a * 3973 + 2048) >>12;
	} else if(rom>=612) {
	    /* modified conversion algorithm for ROM >= 6.12 */
	    a = (a * 4093 + 2048) >>12;
	}
	
	a += sign * ((e<<1)+1) * d >>1;
	if ( (d&1) ) a++;
	zput(a<<2, out);
	
	d = (d*Mx[nbits-2][ e ] + 0x2000) >>14;
	if ( d < 5 ) d=5;	    
    }
}

static void
pvftoadpcm _P4((nbits, rom, in, out),
	       int nbits, int rom, FILE *in, FILE *out)
{
    int a=0, d=5;
    state_t s;
    static char buf[16]={'Z','y','X','E','L',2,0,0,0,0,0,0,0,0,0,0 };

    buf[10]=nbits-1;
    fwrite(buf, 16, 1, out);

    s=state_init;

    while(1) {
	int e=0, nmax = 1 << (nbits-1);
	int sign, delta;
	
	delta = (zget(in) >> 2) - a;
	if(feof(in))
	    break;
	
	if(delta<0) {
	    e = nmax;
	    delta = -delta;
	}
	while( --nmax && delta > d ) {
	    delta -= d;
	    e++;
	}
	
	if(nbits==4 && ((e&0x0f) == 0)) e=0x08;

	put_bits( e, nbits, &s, out);
	
	if(rom >= 610 && rom < 612) {
	    /* modified conversion algorithm for ROM >= 6.10 */
	    a = (a * 3973 + 2048) >>12;
	} else if(rom>=612) {
	    /* modified conversion algorithm for ROM >= 6.12 */
	    a = (a * 4093 + 2048) >>12;
	}
	
	sign = ( e>>(nbits-1) ) ? -1 : 1 ;
	e = e&bitmask[nbits-1];
	
	a += sign * ((e<<1)+1) * d >>1;
	if ( (d&1) ) a++;
	
	d = (d*Mx[nbits-2][ e ] + 0x2000) >>14;
	if ( d < 5 ) d=5;
    }
    if(s.nleft) put_bits(0, 8-s.nleft, &s, out);
}

int
pvfadpcm _P2((argc, argv), int argc, char **argv )
{
    FILE *in=stdin, *out=stdout;
    int nbits;
    int rom = ZYXEL_ROM;

    if(argc>=2) {
	if(strcmp(argv[1], "-r601")==0) rom = 601;
	else if(strcmp(argv[1], "-r610")==0) rom = 610;
	else if(strcmp(argv[1], "-r612")==0) rom = 612;
	else USAGE("[-r612|-r610|-r601]");
    }

    nbits= argv[0][strlen(argv[0])-1] -'0';
    if(nbits>=2 && nbits <=4) {
	pvftoadpcm(nbits, rom, in, out);
    } else {
	adpcmtopvf(nbits, rom, in, out);
    }
    return 0;
}
