/* sndwrite.c -- write sounds to files */

#include "sys/types.h"	
#include <stdio.h>
#include "sndheader.h"
#include "xlisp.h"
#include "sound.h"
#include "falloc.h"
#include "sndwrite.h"

#define D if (0) 

int sndwrite_trace = 0;	/* debugging */

void sound_save_sound(LVAL s_as_lval, long n, int fout, long format,
	long mode, long bits, char *buf, double *sr, double *duration);

void sound_save_array(LVAL sa, long n, int fout, long format, long mode, long bits, char *buf, double *sr, double *duration);

unsigned char st_linear_to_ulaw(int sample);


typedef struct {
    sound_type sound;
    long cnt;
    sample_block_values_type ptr;
    double scale;
    int terminated;
} sound_state_node, *sound_state_type;


void sound_save(
  LVAL snd_expr,
  long n,
  unsigned char *filename,
  long format,
  long mode,
  long bits,
  double *sr,
  long *nchans,
  double *duration)
{
    int fout;
    sample_block_type sampblock;
    LVAL result;
    char *buf;
    char error[80];

    /*
     * what about sample max, ie what about (lazy) normalizing?
     */

    if ((fout = creat(filename, PERMS)) < 0) {
	sprintf(error, "snd_save: cannot create file %s", filename);
	xlabort(error);
    }

    if ((buf = (char *) malloc(max_sample_block_len * MAX_SND_CHANNELS *
                                  sizeof(float))) == NULL) {
	xlabort("snd_save: couldn't allocate memory");
    }

    result = xleval(snd_expr);
    /* BE CAREFUL - DO NOT ALLOW GC TO RUN WHILE RESULT IS UNPROTECTED */
    if (vectorp(result)) {
        /* make sure all elements are of type a_sound */
        long i = getsize(result);
        *nchans = i;
        while (i > 0) {
            i--;
            if (!exttypep(getelement(result, i), a_sound)) {
                xlerror("sound_save: array has non-sound element",
                         result);
            }
        }
        sound_save_array(result, n, fout, format, mode, bits, 
			 buf, sr, duration);
    } else if (exttypep(result, a_sound)) {
        *nchans = 1;
        sound_save_sound(result, n, fout, format, mode, bits,
			 buf, sr, duration);
    } else {
        xlerror("sound_save: expression did not return a sound",
                 result);
    }
    free(buf);
    close(fout);
}


void sound_save_sound(LVAL s_as_lval, long n, int fout, long format, 
	long mode, long bits, char *buf, double *sr, double *duration)
{
    int ntotal = 0;
    register double scale_factor;
    long blocklen;
    sample_block_values_type sbufp;
    signed char *byte_bufp;
    short *int16_bufp;
    long *int32_bufp;
    float *float_bufp;
    sound_type s;
    long debug_unit;    /* print messages at intervals of this many samples */
    long debug_count;   /* next point at which to print a message */

    /* if snd_expr was simply a symbol, then s now points to
        a shared sound_node.  If we read samples from it, then
        the sound bound to the symbol will be destroyed, so
        copy it first.  If snd_expr was a real expression that
        computed a new value, then the next garbage collection
        will reclaim the sound_node.  We need to make the new
	sound reachable by the garbage collector to that any
	lisp data reachable from the sound do not get collected.
	To make the sound reachable, we need to allocate a node,
	and the GC might run, so we need to protect the OLD s
	but then make it unreachable.
	We will let the GC collect the sound in the end.
    */
    xlprot1(s_as_lval);
    s = sound_copy(getsound(s_as_lval));
    s_as_lval = cvsound(s);	/* destroys only ref. to original */
    *sr = s->sr;

    /* for debugging */
/*    printing_this_sound = s;*/
    scale_factor = SCALE_FACTOR_TO_SHORT * s->scale;

    write_sndheader_start(fout, format, mode, bits, (long) 1 /* channel */, 
			  *sr);
    debug_unit = debug_count = max(*sr, 10000);

    while (n > 0) {
        long togo, j;
	sample_block_type sampblock = sound_get_next(s, &blocklen);
#ifdef SNAPSHOTS
        printf(".");
        if (sound_created_flag) {
            printf("SNAPSHOT: ");
            sound_print_tree(printing_this_sound);
            sound_created_flag = false;
        }
        fflush(stdout);
#endif
	if (sampblock == zero_block || blocklen == 0) {
	    break;
	}
        togo = min(blocklen, n);
        sbufp = sampblock->samples;
	switch (bits >> 3) {
	  case 1:
	    byte_bufp = (signed char *) buf;
	    if (mode == MODE_ULAW) {
		for (j = 0; j < togo; j++) {
	            double s = *sbufp++ * scale_factor;
		    int i;
	            /* round down, e.g. -4.5 => -5: */
	            if (s > 0) s += 0.5; else s -= 0.5;
		    i = s;
		    *byte_bufp++ = st_linear_to_ulaw(i);
		}
	    } else if (mode == MODE_UPCM) {
		for (j = 0; j < togo; j++) {
	            double s = *sbufp++ * scale_factor;
		    int i;
	            /* round down, e.g. -4.5 => -5: */
	            if (s > 0) s += 0.5; else s -= 0.5;
		    i = s;
		    *byte_bufp++ = (st_linear_to_ulaw(i) ^ 128);
		}
	    } else {
		scale_factor = SCALE_FACTOR_TO_BYTE * s->scale;
		for (j = 0; j < togo; j++) {
	            double s = *sbufp++ * scale_factor;
	            /* round down, e.g. -4.5 => -5: */
	            if (s > 0) s += 0.5; else s -= 0.5;
                    if (s >= 128 || s <= -128) {
                        printf("overflow of 8 bit sample: %g\n", s);
                    }
		    *byte_bufp++ = s;
		}
	    }		
	    break;
	  case 2:
	    int16_bufp = (short *) buf;
	    for (j = 0; j < togo; j++) {
		double s = *sbufp++ * scale_factor;
		int i;
	        /* round down, e.g. -4.5 => -5: */
	        if (s > 0) s += 0.5; else s -= 0.5;
		*int16_bufp++ = s;
	    }
	    break;
	  case 4:
	    if (mode == MODE_FLOAT) {
		float_bufp = (float *) buf;
		scale_factor = s->scale;
		for (j = 0; j < togo; j++) {
	            *float_bufp++ = *sbufp++ * scale_factor;
		}
	   }  else {
		int32_bufp = (long *) buf;
		scale_factor = SCALE_FACTOR_TO_LONG * s->scale;
		for (j = 0; j < togo; j++) {
	            double s = *sbufp++ * scale_factor;
	            /* round down, e.g. -4.5 => -5: */
	            if (s > 0) s += 0.5; else s -= 0.5;
		    *int32_bufp++ = s;
		}
	    }		    
	}
        write(fout, buf, togo * (bits >> 3));
        n -= togo;
	ntotal += togo;
        if (ntotal > debug_count) {
            printf(" %d ", ntotal);
	    fflush(stdout);
            debug_count += debug_unit;
        }
    }
    printf("\ntotal samples: %d\n", ntotal);
    *duration = ntotal / *sr;
    xlpop();
    
    write_sndheader_finish(fout, format, mode, bits, (long ) 1 /* channel */,
			   ntotal);
}


void sound_save_array(LVAL sa, long n, int fout, long format, long mode, long bits, char *buf, double *sr, double *duration)
{
    int ntotal = 0;
    long i, len;
    sound_state_type state;
    double scale_factor = SCALE_FACTOR_TO_SHORT;
    double start_time = HUGE_VAL;
    signed char *byte_bufp;
    short *int16_bufp;
    long *int32_bufp;
    float *float_bufp;
    LVAL sa_copy;
    long debug_unit;    /* print messages at intervals of this many samples */
    long debug_count;   /* next point at which to print a message */

    switch (bits >> 3) {
      case 1:
        if (mode != MODE_ULAW) scale_factor = SCALE_FACTOR_TO_BYTE;
        break;
      case 2:
        break;
      case 4:
        if (mode != MODE_FLOAT) scale_factor = SCALE_FACTOR_TO_LONG;
        break;
      default:
        xlerror("bad value for bits/sample", cvfixnum((FIXTYPE) bits));
        break;
    }

    /* if snd_expr was simply a symbol, then sa now points to
        a shared sound_node.  If we read samples from it, then
        the sounds bound to the symbol will be destroyed, so
        copy it first.  If snd_expr was a real expression that
        computed a new value, then the next garbage collection
        will reclaim the sound array.  See also sound_save_sound()
    */
    len = getsize(sa);
    if (len > MAX_SND_CHANNELS) {
        xlerror("sound_save: too many channels", sa);
    }
    xlprot1(sa);
    sa_copy = newvector(len);
    xlprot1(sa_copy);

    /* Why do we copy the array into an xlisp array instead of just
     * the state[i] array? Because some of these sounds may reference
     * the lisp heap. We must put the sounds in an xlisp array so that
     * the gc will find and mark them. xlprot1(sa_copy) makes the array
     * visible to gc.
     */
    for (i = 0; i < len; i++) {
	sound_type s = getsound(getelement(sa, i));
	setelement(sa_copy, i, cvsound(sound_copy(s)));
    }
    sa = sa_copy;	/* destroy original reference to allow GC */

    state = (sound_state_type)
      malloc(sizeof(sound_state_node) * len);
    for (i = 0; i < len; i++) {
        state[i].sound = getsound(getelement(sa, i));
        state[i].scale = scale_factor * state[i].sound->scale;
D       printf("save scale factor %d = %g\n", i, state[i].scale);
        state[i].terminated = false;
        state[i].cnt = 0;   /* force a fetch */
	start_time = min(start_time, state[i].sound->t0);
    }
    *sr = state[0].sound->sr;  /* assume all are the same */

    for (i = 0; i < len; i++) {
	if (state[i].sound->t0 > start_time) sound_prepend_zeros(state[i].sound, start_time);
    }

    /* for debugging */
/*    printing_this_sound = s;*/

    write_sndheader_start(fout, format, mode, bits, len, *sr);
    debug_unit = debug_count = max(*sr, 10000);

    while (n > 0) {
        /* keep the following information for each sound:
            has it terminated?
            pointer to samples
            number of samples remaining in block
           scan to find the minimum remaining samples and
           output that many in an inner loop.  Stop outer
           loop if all sounds have terminated
         */
        int terminated = true;
        int togo = n;
        int j;

        for (i = 0; i < len; i++) {
            if (state[i].cnt == 0) {
		if (sndwrite_trace) {
		    printf("CALLING SOUND_GET_NEXT ON CHANNEL %d (%x)\n",
			   i, state[i].sound);
		    sound_print_tree(state[i].sound);
		}
		state[i].ptr = sound_get_next(state[i].sound,
                                   &(state[i].cnt))->samples;
		if (sndwrite_trace) {
		    printf("RETURNED FROM CALL TO SOUND_GET_NEXT ON CHANNEL %d\n", i);
		}
                if (state[i].ptr == zero_block->samples) {
                    state[i].terminated = true;
                }
            }
            if (!state[i].terminated) terminated = false;
            togo = min(togo, state[i].cnt);
        }

        if (terminated) break;

	switch (bits >> 3) {
	  case 1:
	    byte_bufp = (signed char *) buf;
	    if (mode == MODE_ULAW) {
                for (j = 0; j < togo; j++) {
                    for (i = 0; i < len; i++) {
                        double s = *(state[i].ptr++) * state[i].scale;
                        int is;
                        /* round down, e.g. -4.5 => -5: */
                        if (s > 0) s += 0.5; else s -= 0.5;
                        is = s;
                        *byte_bufp++ = st_linear_to_ulaw(is);
                    }
                }
	    } else if (mode == MODE_UPCM) {
                for (j = 0; j < togo; j++) {
                    for (i = 0; i < len; i++) {
                        double s = *(state[i].ptr++) * state[i].scale;
                        int is;
                        /* round down, e.g. -4.5 => -5: */
                        if (s > 0) s += 0.5; else s -= 0.5;
                        is = s;
                        *byte_bufp++ = (st_linear_to_ulaw(is) ^ 128);
                    }
                }
	    } else {
                for (j = 0; j < togo; j++) {
                    for (i = 0; i < len; i++) {
                        double s = *(state[i].ptr++) * state[i].scale;
                         /* round down, e.g. -4.5 => -5: */
                        if (s > 0) s += 0.5; else s -= 0.5;
                        *byte_bufp++ = s;
                    }
                }
	    }		
	    break;
	  case 2:
	    int16_bufp = (short *) buf;
            for (j = 0; j < togo; j++) {
                for (i = 0; i < len; i++) {
                    double s = *(state[i].ptr++) * state[i].scale;
                    /* round down, e.g. -4.5 => -5: */
                    if (s > 0) s += 0.5; else s -= 0.5;
                    *int16_bufp++ = s;
                }
            }
	    break;
	  case 4:
	    if (mode == MODE_FLOAT) {
		float_bufp = (float *) buf;
                for (j = 0; j < togo; j++) {
                    for (i = 0; i < len; i++) {
                        *float_bufp++ = *(state[i].ptr++) * state[i].scale;
                    }
                }
	   }  else {
		int32_bufp = (long *) buf;
                for (j = 0; j < togo; j++) {
                    for (i = 0; i < len; i++) {
                        double s = *(state[i].ptr++) * state[i].scale;
                         /* round down, e.g. -4.5 => -5: */
                        if (s > 0) s += 0.5; else s -= 0.5;
                        *int32_bufp++ = s;
                    }
                }
	    }		    
	}
        write(fout, buf, togo * (bits >> 3) * len);
        n -= togo;
        for (i = 0; i < len; i++) {
            state[i].cnt -= togo;
        }
	ntotal += togo;
        if (ntotal > debug_count) {
            printf(" %d ", ntotal);
	    fflush(stdout);
            debug_count += debug_unit;
        }
    }
    printf("total samples: %d x %d channels\n", ntotal, len);
    *duration = ntotal / *sr;
    /* references to sounds are shared by sa_copy and state[].
     * here, we dispose of state[], allowing GC to do the
     * sound_unref call that frees the sounds. (Freeing them now
     * would be a bug.)
     */
    free(state);
    xlpop();
    write_sndheader_finish(fout, format, mode, bits, len, ntotal);
}


#define BIAS 0x84   /* define the add-in bias for 16 bit samples */
#define CLIP 32635

unsigned char
st_linear_to_ulaw( sample )
int sample;
    {
    static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,
                               4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
                               5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
                               5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
                               6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
                               6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
                               6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
                               6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
                               7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                               7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                               7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                               7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                               7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                               7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                               7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
                               7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7};
    int sign, exponent, mantissa;
    unsigned char ulawbyte;

    /* Get the sample into sign-magnitude. */
    sign = (sample >> 8) & 0x80;		/* set aside the sign */
    if ( sign != 0 ) sample = -sample;		/* get magnitude */
    if ( sample > CLIP ) sample = CLIP;		/* clip the magnitude */

    /* Convert from 16 bit linear to ulaw. */
    sample = sample + BIAS;
    exponent = exp_lut[( sample >> 7 ) & 0xFF];
    mantissa = ( sample >> ( exponent + 3 ) ) & 0x0F;
    ulawbyte = ~ ( sign | ( exponent << 4 ) | mantissa );
#ifdef ZEROTRAP
    if ( ulawbyte == 0 ) ulawbyte = 0x02;	/* optional CCITT trap */
#endif

    xlpopn(2);
    return ulawbyte;
    }
