/*
 *  sample.c - Functions for handling samples.
 *
 *  (C) 1994 Mikael Nordqvist (d91mn@efd.lth.se, mech@df.lth.se)
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>
#include <sys/ultrasound.h>
#include <limits.h>

#include "mod.h"

/* External global variables */

SEQ_DECLAREBUF();
extern int seqfd, gus_dev;

extern struct mod_info M;
extern struct options opt;

int read_and_upload_sample(int fd, int i)
{
    int t, delta, base, skip, span, origlen;
    struct patch_info *p;
    struct sample_info *s;
    char buf[128];

    skip=0; /* Not necessary, but removes a compiler warning */
    
    s=&M.sample[i];

    /* +2 for click-removal */
    p=malloc(sizeof(struct patch_info)+s->length+2);

    p->key=GUS_PATCH;
    p->device_no=gus_dev;
    p->instr_no=i;
    p->len=origlen=s->length;

    /* PAL = 8287 Hz ; NTSC = 8363 Hz */
    if(s->c2freq == -1)
	p->base_freq=(opt.ntsc_samples ? 8363 : 8287);
    else
	p->base_freq=s->c2freq;

    p->base_note=261632;            /* C-2 */
    p->high_note=INT_MAX;
    p->low_note=0;
    p->panning=0;
    p->detuning=0;
    p->volume=64;      /* Not used in kernel when we have LINEAR volumes */
    p->mode=0;         /* Default to signed 8 bit nonlooped samples      */

    /* Read the first 4 bytes and try to determine if we should skip the first
     * 0, 2 or 4 bytes (amiga modules have a couple of 0-bytes at the start
     * of samples to handle looping; 4 on older modules and 2 on newer ones).
     * We never skip zeroes that are included in loops though (this is a
     * special protection for short samples that often are looped and
     * sound very strange if the looptime is shortened).
     *
     * The above scheme will cause samples to be a few bytes short (and thus
     * played "a bit too early") but they will click less when triggered.
     * If the sample is 16bit it can't be an amiga-sample and nothing is done.
     */

    if(!s->bits_16) {
	if(read(fd, p->data, 4) == 4) {
	    skip=0;
	    if(!p->data[0] && !p->data[1]) {
		skip=2;
		if(!p->data[2] && !p->data[3])
		    skip=4;
	    }
	    
	    if(s->looped)
		skip=MIN(skip, s->repeat_start);
	    
	    lseek(fd, skip-4, SEEK_CUR);
	    p->len-=skip;
	    s->length=p->len;
	    s->repeat_start=MAX(0, s->repeat_start)-skip;
	    s->repeat_end=MAX(0, s->repeat_end-skip);
	}
	else {
	    free(p);
	    return 0;  /* Sample too short */
	}
#if 0
	/* Show the first bytes of the sample */
	if(opt.verbose >= 4) {
	    printf("%-22s: ", s->name);
	    for(t=0; t < skip; ++t)
		printf("%4d ", (int)p->data[t]);
	}
#endif
    }
    
    if(s->bits_16)
	p->mode|=WAVE_16_BITS;

    if(s->looped) {
	p->loop_start=s->repeat_start;
	p->loop_end=s->repeat_end;
	p->mode|=WAVE_LOOPING;
    }

    if(s->unsigned_data)
	p->mode|=WAVE_UNSIGNED;

    if(s->looped == LOOP_BIDIR)
	p->mode|=WAVE_BIDIR_LOOP;

    /* Some modules seems to be 4 or 8 bytes short. Probably a
     * moduleripper that didn't do it's job too well.
     * Quick'n dirty solution: allow the last sample to be a bit short.
     */
    if((t=read(fd, p->data, p->len)) != p->len) {
	if(p->len-t <= 8 || opt.tolerant) {
	    info("(Sample %d was %d bytes short)\n", i, (int)p->len-t);
	    p->len=t;
	    s->length=t;
	    if(s->repeat_end >=t) {
		if(!s->bits_16) {
		    s->repeat_end=t-1;
		    p->loop_end=t-1;
		}
		else {
		    s->repeat_end=t-2;
		    p->loop_end=t-2;
		}
	    }
	}
	else {
	    free(p);
	    return 0; /* Sample more than 8 bytes short, abort */
	}
    }

#if 0
    /* Show the rest of the 10 first bytes of the sample */
    if(!s->bits_16 && opt.verbose >= 4) {
	for(t=0; t < 10-skip; ++t)
	    printf("%4d ", (int)p->data[t]);
	printf("(%d)\n", skip);
    }
#endif
    
#if 0
    /* Modify the sample after loop_end so the GF1 on the GUS interpolates
     * correctly.
     * This really doesn't work work if the last sample is included in the
     * loop (has to be done by the kernel then).
     */
    
    if(s->looped) {
	if(!s->bits_16)
	    ((char *)p->data)[p->loop_end+1]=
		((char *)p->data)[p->loop_start];
	else
	    ((short *)p->data)[p->loop_end/2+1]=
		((short *)p->data)[p->loop_start/2];
    }
#endif

    /* Attempt to minimize clicking/popping on looped samples by
     * connecting samples at the loopingpoint with a straight line.
     * Samples outside the loop will never be modified.
     */
    if(s->looped && opt.click_removal > 0 && origlen > opt.click_removal) {
	span=2+1; /* Change 2 last bytes */
	if(!s->bits_16) {
	    if(p->loop_end+1-span < p->loop_start)
		span=p->loop_end-p->loop_start+1;
	    base=p->data[p->loop_end+1-span];
	    delta=p->data[p->loop_end+1]-base;
	    for(t=1; t<span; ++t)
		p->data[p->loop_end+1-span+t]=(char)(base+(delta*t)/span);
	}
	else {
	    if(p->loop_end+(1-span)*2 < p->loop_start)
		span=(p->loop_end-p->loop_start)/2+1;
	    base=((short *)p->data)[p->loop_end/2+1-span];
	    delta=((short *)p->data)[p->loop_end/2+1]-base;
	    for(t=1; t<span; ++t)
		((short *)p->data)[p->loop_end/2+1-span+t]=
		    (short)(base+(delta*t)/span);
	}
    }

    t=gus_dev;
    ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &t);
    if(p->len <= t)
	SEQ_WRPATCH(p, sizeof(struct patch_info)+p->len);
    
    if(p->len > t) {
	s->valid=0;
	sprintf(buf, "Skipping sample %d", i);
	print_status(buf);
	info("Sample %d doesn't fit in GUS-memory - skipped.\n", i);
	sleep(1);
    }
    else
	s->valid=1;
    
    free(p);
    
    return 1;
}


void print_sample_info_header(void)
{
    if(!opt.verbose)
	return;

	printf(
"\n   #  Samplename               Size Finetune Loopstart Loopend Volume  #\n"
"--------------------------------------------------------------------------\n");
}


void print_sample_info(int i)
{
    struct sample_info *s;

    if(!opt.verbose)
	return;

    s=&M.sample[i];
    if((opt.verbose && s->length) || (opt.verbose >= 2)) {
	printf("%c %2d  %-22s %6d%c   %2d      %5d%c   %5d   %3d   %2d %c\n",
	       (s->length ? ' ' : '*'),
	       i,
	       (M.format == MODFORMAT_ULT ? s->dosname :
		fill_string(s->name, 22)),
	       s->length,
	       (s->bits_16 ? '*' : ' '),
	       s->finetune,
	       s->repeat_start,
	       (s->looped == LOOP_BIDIR ? 'B' : ' '),
	       s->repeat_end,
	       s->volume,
	       i,
	       (s->length ? ' ' : '*'));
    }
}
