/*
	$Id: bufutil.c,v 1.6 2001/09/03 18:50:50 rogue Exp $

	Copyright (c) 1999 - 2001 Xforge project
*/

#include <Xm/Xm.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "xforge.h"
#include "glob.h"

struct buffer *root = NULL;			/* list of buffers */
int nbuf = 0;					/* number of buffers */

/* buffer management routines */

/*
	findbyname(name)

	returns the buffer which has buf->name matching name or NULL if no
	such buffer exists
*/

static struct buffer *findbyname(const char *name) {
	struct buffer *p;

	for (p = root; p; p = p->succ)
		if (!(strcmp(p->name, name)))
			return p;

	return NULL;
}

/*
	setbufname(buf, name)

	attempts to rename buffer buf to name. If such name already exists,
	it attempts to find a name of the form "name <n>" with n increasing
	from 2.

	setbufname() also truncates the name to maximum of 119 characters
	plus the added "<n>". It gives up searching for free name after
	9998 tries, which should be plenty for now.
*/

void setbufname(struct buffer *buf, const char *name) {
	char newname[128];
	int namelen, i = 2;

	if (buf->name)
		free(buf->name);
	namelen = strlen(name);
	if (namelen > 127)
		namelen = 127;
	strncpy(newname, name, namelen);
	newname[namelen] = 0;		/* force termination of string */
	while (findbyname(newname) && i < 10000)
		sprintf(newname, "%.119s <%d>", name, i++);
	buf->name = strdup(newname);
}

/*
	add_buf()

	allocates and inserts new buffer structure to list of buffers
*/

struct buffer *add_buf(void) {
	struct buffer *p;

	if (!(p = malloc(sizeof(struct buffer)))) {
		syserr(EMEM_MSG);
		return NULL;
	}
	nbuf++;

	/* initialize buffer */
	p->data = NULL;
	p->undo_data = NULL;
	p->len = 0;
	p->undo_len = 0;
	p->mod = 0;
	p->channels = 1;
	p->rate = 44100;
	p->bits = 16;
	p->path = NULL;
	p->name = NULL;
	setbufname(p, "Xforge");
	p->xname = XmStringCreateLocalized(p->name);
	p->selmask = 0;
	p->selstart = 0;
	p->selend = 0;
	p->dispstart = 0;
	p->dispend = 0;
	p->filef = FILEF_RAW;
	p->p_loop = 0;

	/* add this node to second place of list */
	if (!root) {
		root = p;
		p->succ = NULL;
		return p;
	}
	p->succ = root->succ;
	root->succ = p;
	return p;
}

/*
	remove_buf(buf)

	removes and frees buffer structure buf from list of buffers
*/

void remove_buf(struct buffer *buf) {
	struct buffer *p, *q;

	/* free buffer data */
	if (buf->undo_data)
		free(buf->undo_data);
	if (buf->data)
		free(buf->data);
	if (buf->path)
		free(buf->path);
	if (buf->name)
		free(buf->name);
	XmStringFree(buf->xname);

	/* start from root and scan the list until buffer is found */
	if (!root) {				/* no root? too bad... */
		xferr("Root buffer does not exist - please report this bug");
		return;
	}
	for (p = root, q = NULL; p; q = p, p = p->succ)
		if (p == buf)
			break;
	if (p) {
		if (!q) {	/* removing root */
			root = p->succ;
		} else {
			q->succ = p->succ;
		}
		free(p);
	} else {
		xferr("Buffer does not exist - please report this bug");
		return;
	}
	nbuf--;
}

int undo_create(struct buffer *buf) {
	float *p = NULL;

	if (buf->data) {
		if (!(p = malloc(sizeof(float) * buf->len * buf->channels))) {
			syserr(EMEM_MSG);
			return -1;
		}
	}

	if (buf->undo_data)
		free(buf->undo_data);

	buf->undo_data = p;
	buf->undo_len = buf->len;
	buf->undo_channels = buf->channels;
	buf->undo_rate = buf->rate;
	memcpy(p, buf->data, buf->len * buf->channels * sizeof(float));

	return 0;
}

void undo(struct buffer *buf) {
	float *p;
	int l, c, r;

	p = buf->undo_data;
	buf->undo_data = buf->data;
	buf->data = p;
	l = buf->undo_len;
	buf->undo_len = buf->len;
	buf->len = l;
	c = buf->undo_channels;
	buf->undo_channels = buf->channels;
	buf->channels = c;
	r = buf->undo_rate;
	buf->undo_rate = buf->rate;
	buf->rate = r;
}

void buf_remdc(struct buffer *buf) {
	float *p, *q, sum = 0.0f;
	int i, j, clen, mask = 0x1;

	if (undo_create(buf))
		return;

	clen = buf->selend - buf->selstart + 1;

	for (i = 0; i < buf->channels; i++) {
		p = q = buf->data + buf->len * i;
		if (mask & buf->selmask) {
			for (j = 0; j < clen; j++)
				sum += *p++;
			sum /= clen;
			for (j = 0; j < clen; j++)
				*q++ -= sum;
		}
		mask = mask << 1;
	}

	clamp(buf);
	buf->mod = 1;
}

int buf_addws(struct buffer *buf, int len) {
	float *p, *q, *r;
	int i, j, size;

	size = (buf->len + len) * buf->channels;

	if (!(buf->data))
		size = len;

	if (!(p = r = malloc(size * sizeof(float)))) {
		syserr(EMEM_MSG);
		return -1;
	}

	if (undo_create(buf)) {
		free(p);
		return -1;
	}

	if (!(buf->data)) {
		buf->data = p;
		buf->rate = 44100;
		buf->filef = FILEF_RAW;
		buf->bits = 16;
		buf->channels = 1;
		buf->len = 0;
	}

	q = buf->data;
	for (i = 0; i < buf->channels; i++) {
		memcpy(p, q, buf->len * sizeof(float));
		p += buf->len;
		q += buf->len;
		for (j = 0; j < len; j++)
			*p++ = 0;
	}

	/* if there was old buffer, free() it */
	if (r != buf->data)
		free(buf->data);
	buf->data = r;
	buf->len += len;
	buf->mod = 1;
	return 0;
}

void buf_invert(struct buffer *buf) {
	int i, j, mask = 0x1, sellen;
	float *p;

	if (undo_create(buf))
		return;

	sellen = buf->selend - buf->selstart + 1;

	for (i = 0; i < buf->channels; i++) {
		p = buf->data + buf->selstart + i * buf->len;
		if (mask & buf->selmask) {
			for (j = 0; j < sellen; j++) {
				*p = -(*p);
				p++;
			}
			buf->mod = 1;
		}
		mask = mask << 1;
	}
}

void buf_reverse(struct buffer *buf) {
	int i, j, mask = 0x1, sellen;
	float *p, *q, t;

	if (undo_create(buf))
		return;

	sellen = buf->selend - buf->selstart + 1;

	for (i = 0; i < buf->channels; i++) {
		p = buf->data + buf->selstart + i * buf->len;
		q = p + sellen - 1;
		if (mask & buf->selmask) {
			for (j = 0; j < sellen / 2; j++) {
				t = *p;
				*p++ = *q;
				*q-- = t;
			}
			buf->mod = 1;
		}
		mask = mask << 1;
	}
}

void buf_tomono(struct buffer *buf, float balance) {
	float l, r, *p, *q;
	int i;

	l = 0.5f * (1.0f - balance);
	r = 0.5f * (1.0f + balance);

	if (!(p = malloc(buf->len * sizeof(float)))) {
		syserr(EMEM_MSG);
		return;
	}

	if (undo_create(buf)) {
		free(p);
		return;
	}

	q = buf->data;
	for (i = 0; i < buf->len; i++)
		p[i] = l * q[i] + r * q[i + buf->len];

	free(buf->data);
	buf->data = p;

	buf->channels = 1;
	buf->mod = 1;
}

void buf_tostereo(struct buffer *buf) {
	float *p;

	if (!(p = malloc(buf->len * sizeof(float) * 2))) {
		syserr(EMEM_MSG);
		return;
	}

	if (undo_create(buf)) {
		free(p);
		return;
	}

	memcpy(p, buf->data, buf->len * sizeof(float));
	memcpy(p + buf->len, buf->data, buf->len * sizeof(float));

	free(buf->data);
	buf->data = p;

	buf->channels = 2;
	buf->mod = 1;
}

int buf_mtosshuffle(struct buffer *buf) {
	int i;
	float *p, *q;

	if (!(buf->data))
		return 1;

	if (!(q = malloc(buf->len / 2 * sizeof(float))))
		return 1;

	p = buf->data;
	/* copy right channel to temporary buffer */
	for (i = 0; i < buf->len / 2; i++)
		q[i] = p[2 * i + 1];
	/* fix left channel */
	for (i = 0; i < buf->len / 2; i++)
		p[i] = p[2 * i];
	/* fix right channel */
	for (i = 0; i < buf->len / 2; i++)
		p[i + buf->len / 2] = q[i];
	free(q);
	return 0;
}

int buf_stomshuffle(struct buffer *buf) {
	int i;
	float *p, *q;

	if (!(buf->data))
		return 1;

	if (!(q = malloc(buf->len * sizeof(float))))
		return 1;

	p = buf->data;
	/* copy right channel to temporary buffer */
	for (i = 0; i < buf->len; i++)
		q[i] = p[i + buf->len];
	/* fix left channel */
	for (i = buf->len; i; i--)
		p[2 * i] = p[i];
	/* fix right channel */
	for (i = 0; i < buf->len; i++)
		p[2 * i + 1] = q[i];
	free(q);
	return 0;
}

int buf_mix(struct buffer *dst, struct buffer *src, float sratio, int
	autogrow) {
	float *p, *q;
	int i, len;

	if (undo_create(dst))
		return 1;

	if (!(dst->data)) {
		if (autogrow) {
			if (buf_addws(dst, src->len)) {
				return 1;
			}
			dst->dispstart = 0;
			dst->dispend = dst->len - 1;
		} else {
			return 1;
		}
	}

	if (dst->len >= src->len)
		len = src->len;
	else
		len = dst->len;

	if (src->len > dst->len) {
		if (autogrow) {
			len = src->len;
			if (buf_addws(dst, src->len - dst->len))
				return 1;
		} else {
			if (undo_create(dst)) {
				return 1;
			}
		}
	} else {
		if (undo_create(dst)) {
			return 1;
		}
	}


	if (dst->channels == 1) {
		/* mono wave, mix only the leftmost channel of source */
		p = dst->data;
		q = src->data;
		for (i = 0; i < len; i++)
			*p++ += sratio * *q++;
	} else {
		p = dst->data;
		q = src->data;
		/* source is mono, mix it to both left and right */
		if (src->channels == 1) {
			for (i = 0; i < len; i++)
				*p++ += sratio * *q++;
			p = dst->data + dst->len;
			q = src->data;
			for (i = 0; i < len; i++)
				*p++ += sratio * *q++;
		/* source is stereo, just mix everything */
		} else {
			for (i = 0; i < len; i++)
				*p++ += sratio * *q++;
			p = dst->data + dst->len;
			q = src->data + src->len;
			for (i = 0; i < len; i++)
				*p++ += sratio * *q++;
		}
	}
	clamp(dst);
	dst->mod = 1;
	return 0;
}
