shithub: riscv

ref: eddec3a69fc3e7d4f629e6737f1f5e5c1a088b78
dir: /sys/src/cmd/audio/wavdec/wavdec.c/

View raw version
#include <u.h>
#include <libc.h>

int debug = 0;
int rate = 44100;

typedef struct Wave Wave;
typedef struct Chan Chan;

struct Chan
{
	ulong	phase;
	int	last;
};

struct Wave
{
	int	rate;
	int	channels;
	int	framesz;
	int	bits;
	int	fmt;
};

ulong
get2(void)
{
	uchar buf[2];

	if(readn(0, buf, 2) != 2)
		sysfatal("read: %r");
	return buf[0] | buf[1]<<8;
}

ulong
get4(void)
{
	uchar buf[4];

	if(readn(0, buf, 4) != 4)
		sysfatal("read: %r");
	return buf[0] | buf[1]<<8 | buf[2]<<16 | buf[3]<<24;
}

uchar*
getcc(uchar tag[4])
{
	if(readn(0, tag, 4) != 4)
		sysfatal("read: %r");
	return tag;
}

uchar*
resample(Chan *c, int *src, uchar *dst, int mono, ulong delta, ulong count)
{
	int last, val, out;
	ulong phase, pos;
	vlong v;

	last = c->last;
	phase = c->phase;
	pos = phase >> 16;
	while(pos < count){
		val = src[pos];
		if(pos)
			last = src[pos-1];

		/* interpolate */
		v = val;
		v -= last;
		v *= (phase & 0xFFFF);
		out = (last + (v >> 16)) >> (sizeof(int)*8 - 16);

		*dst++ = out;
		*dst++ = out >> 8;
		if(mono){
			*dst++ = out;
			*dst++ = out >> 8;
		} else
			dst += 2;
		phase += delta;
		pos = phase >> 16;
	}
	c->last = val;
	if(delta < 0x10000)
		c->phase = phase & 0xFFFF;
	else
		c->phase = phase - (count << 16);

	return dst;
}

void
conv(int *dst, uchar *src, int bits, int skip, int n)
{
	int i, v;

	while(n--){
		if(bits == 8)
			v = (int)src[0] - 127;
		else {
			v = 0;
			switch(i = bits/8){
			case 4:
				v = src[--i];
			case 3:
				v = (v<<8) | src[--i];
			case 2:
				v = (v<<8) | src[--i];
			case 1:
				v = (v<<8) | src[--i];
			}
		}
		v <<= sizeof(int)*8-bits;
		*dst++ = v;
		src += skip;
	}
}

void
usage(void)
{
	fprint(2, "usage: %s [ -d ]\n", argv0);
	exits("usage");
}

void
main(int argc, char *argv[])
{
	uchar buf[8*1024], *out, *p;
	int *samples;
	Chan c0, c1;
	Wave wav;
	ulong delta, len;
	int n, z;

	ARGBEGIN {
	case 'd':
		debug++;
		break;
	default:
		usage();
	} ARGEND;

	if(memcmp(getcc(buf), "RIFF", 4) != 0)
		sysfatal("no riff format");
	get4();
	if(memcmp(getcc(buf), "WAVE", 4) != 0)
		sysfatal("not a wave file");

	for(;;){
		getcc(buf);
		len = get4();
		if(memcmp(buf, "data", 4) == 0)
			break;
		if(memcmp(buf, "fmt ", 4) == 0){
			if(len < 2+2+4+4+2+2)
				sysfatal("format chunk too small");
			wav.fmt = get2();
			wav.channels = get2();
			wav.rate = get4();
			get4();
			wav.framesz = get2();
			wav.bits = get2();
			len -= 2+2+4+4+2+2;
		}
		while(len > 0){
			if(len < sizeof(buf))
				n = len;
			else
				n = sizeof(buf);
			if(readn(0, buf, n) != n)
				sysfatal("read: %r");
			len -= n;
		}
	}

	if(wav.fmt != 1)
		sysfatal("compressed format (0x%x) not supported", wav.fmt);
	if(wav.framesz <= 0 || wav.bits <= 0 || wav.framesz != wav.channels*wav.bits/8)
		sysfatal("bad format");
	if(debug)
		fprint(2, "wave: PCM %d Hz, %d ch, %d bits\n",
			wav.rate, wav.channels, wav.bits);

	delta = (wav.rate << 16) / rate;
	n = sizeof(buf)/wav.framesz;
	samples = malloc(sizeof(int) * n);
	out = malloc(4 * ((wav.rate + n*rate)/wav.rate));
	if(samples == nil || out == nil)
		sysfatal("out of memory");

	while(len % wav.framesz)
		--len;
	while(len){
		if(len < sizeof(buf))
			n = len;
		else
			n = sizeof(buf);
		while(n % wav.framesz)
			--n;
		if(readn(0, buf, n) != n)
			sysfatal("read: %r");
		len -= n;
		n /= wav.framesz;
		if(wav.channels == 2){
			conv(samples, buf + wav.bits/8, wav.bits, wav.framesz, n);
			resample(&c1, samples, out+2, 0, delta, n);
		}
		conv(samples, buf, wav.bits, wav.framesz, n);
		p = resample(&c0, samples, out, wav.channels == 1, delta, n);
		write(1, out, p-out);
	}
	exits(0);
}