shithub: neindaw

ref: 8747033fb429a67053e229b30c323ce1c5a25a54
dir: /piper/piper.c/

View raw version
#include <u.h>
#include <libc.h>
#include <thread.h>
#include <bio.h>
#include "common.h"
#include "piper.h"
#include "util.h"

typedef struct Inst Inst;
typedef struct Group Group;

struct Inst {
	void *aux;
	int data;
	int id;
	int numout;
};

struct Group {
	char *path;
	Synth *synth;
	Inst *inst;
	int numinst;
	int clone;
};

static int bpm = 120;
static float bar = 2.0;
static float vol = 1.0;
static QLock grouplock;
static Group *groups;
static int numgroups;
static int audio;
static int record = -1;
static int rate = RATE;

static Synth *synths[] = {
	&ay_3_8910,
	&kick_drum,
	&piano,
};

static void
usage(void)
{
	print("usage: %s [-m] [-r rate] [-t FILE] DIR...\n", argv0);
	threadexitsall("usage");
}

static Inst *
getinst(Group *g, int id)
{
	Inst *inst;
	void *aux;
	char *path, *s, tmp[8];
	Biobuf *b;
	int i, n, f;

	for (i = 0; i < g->numinst; i++) {
		if (id == g->inst[i].id)
			return &g->inst[i];
	}

	for (i = g->numinst; i <= id;) {
		/* first see if it already exists */
		if ((path = smprint("%s/%d", g->path, i)) == nil)
			sysfatal("memory");
		if ((f = open(path, OREAD)) >= 0)
			close(f);
		free(path);

		if (f < 0) {
			/* doesn't exist, clone */
			seek(g->clone, 0, 0);
			if ((n = read(g->clone, tmp, sizeof(tmp))) < 1)
				sysfatal("clone failed");
			tmp[n] = 0;
			i = atoi(tmp);
		}

		if ((path = smprint("%s/%d/%s", g->path, i, g->synth->name)) == nil)
			sysfatal("memory");
		if ((aux = g->synth->alloc(path)) == nil)
			sysfatal("couldn't alloc instance: %r");
		free(path);

		if ((path = smprint("%s/%d/data", g->path, i)) == nil)
			sysfatal("memory");
		if ((f = open(path, OREAD)) < 0)
			sysfatal("couldn't open data: %r");
		free(path);

		if ((g->inst = realloc(g->inst, sizeof(Inst)*(g->numinst+1))) == nil)
			sysfatal("memory");
		inst = &g->inst[g->numinst];
		memset(inst, 0, sizeof(*inst));
		inst->data = f;
		inst->id = i;
		inst->aux = aux;
		g->numinst++;

		path = smprint("%s/%d/ctl", g->path, i);
		if ((b = Bopen(path, OREAD)) == nil)
			sysfatal("%r");
		free(path);
		while ((s = Brdline(b, '\n')) != nil) {
			if (strncmp(s, "numout\t", 7) == 0) {
				s[Blinelen(b)-1] = 0;
				inst->numout = atoi(s+7);
				break;
			}
		}
		Bterm(b);

		if (inst->numout != 1 && inst->numout != 2)
			sysfatal("%s/%d: %d channels", g->path, i, inst->numout);

		if (i == id)
			return inst;

		if (f >= 0)
			i++;
	}

	return nil;
}

static char *
parse(char *s, Group *g)
{
	Cmd c;
	char *e;
	Inst *inst;
	int i, n;
	float f;

	for (i = 1; s[i] != 0 && s[i] != '\n' && s[i] != ';'; i++);
	e = (s[i] == 0 || s[i] == '\n') ? nil : s + i + 1;
	s[i] = 0;

	if (g != nil) {
		s++;
		if ((inst = getinst(g, i36(*s++))) != nil) {
			memset(&c, 0, sizeof(c));
			c.type = -1;

			n = 0;
nextnote:
			f = 1.0;
			if (*s == '!') {
				f = -1.0;
				s++;
			}

			if ((*s >= 'a' && *s <= 'g') || (*s >= 'A' && *s <= 'G')) {
				if (s[1] != 0) {
					f *= note2freq(s[0], s[1]);
					s += 2;
					c.type = CmdNote;
					c.note[n].freq = f;
					c.note[n].vel = 1.0;
					c.note[n].dur = bar / 16.0;
					if (*s != 0) {
						c.note[n].vel = (float)i36(*s) / 16.0;
						s++;
						if (*s != 0) {
							f = i36(*s);
							c.note[n].dur = bar / (f != 0 ? f : 1);
							s++;
						}
					}
					n++;
					if (*s == ':') {
						s++;
						/* for the lack of better ideas, overwrite */
						if (n >= nelem(c.note))
							n--;
						goto nextnote;
					}
				}
				c.numnotes = n;
			} else if (strncmp(s, "vol", 3) == 0) {
				c.type = CmdVol;
				c.vol = atof(s+3) / 100.0;
			} else if (*s != 0) {
				c.type = CmdRaw;
				c.raw = s;
			}

			if (c.type >= 0)
				g->synth->cmd(inst->aux, &c);
		}
	} else if (strncmp(s, "bpm", 3) == 0) {
		if ((i = atoi(s+3)) > 0) {
			bpm = i;
			bar = 240.0 / (float)bpm;
		}
	} else if (strncmp(s, "vol", 3) == 0) {
		vol = atof(s+3) / 100.0;
	}

	return e;
}

static void
mixer(void *)
{
	int i, j, n, bufframes;
	s16int *pcm;
	float *out, *x, f;
	Inst *inst;
	Group *g;

	bufframes = rate/100;
	pcm = malloc(2*bufframes*sizeof(*pcm)); /* stereo */
	out = malloc(2*bufframes*sizeof(*out));
	x = malloc(2*bufframes*sizeof(*x));

	for (;;) {
		memset(out, 0, 2*bufframes*sizeof(*out));
		qlock(&grouplock);
		for (i = 0; i < numgroups; i++) {
			g = &groups[i];
			for (j = 0; j < g->numinst; j++) {
				inst = &g->inst[j];
				if (readn(inst->data, x, inst->numout*bufframes*sizeof(*x)) < 1)
					break;
				if (inst->numout == 1) {
					for (n = 0; n < bufframes; n++) {
						out[n*2+0] += x[n];
						out[n*2+1] += x[n];
					}
				} else {
					for (n = 0; n < 2*bufframes; n++)
						out[n] += x[n];
				}
			}
		}
		qunlock(&grouplock);
		for (n = 0; n < 2*bufframes; n++) {
			f = out[n] * 8192.0 * vol;
			if (f > 32767.0)
				f = 32767.0;
			else if (f < -32767.0)
				f = -32767.0;
			pcm[n] = f;
		}
		if (write(audio, pcm, 2*bufframes*sizeof(*pcm)) < 0) {
			fprint(2, "audio: %r\n");
			break;
		}
		if (record >= 0 && write(record, pcm, 2*bufframes*sizeof(*pcm)) < 0) {
			fprint(2, "record: %r\n");
			break;
		}
	}

	threadexits(nil);
}

void
threadmain(int argc, char **argv)
{
	char *s, t[256], *to;
	Synth *synth;
	Biobuf *b;
	int i, j, n, nomixer;

	nomixer = 0;
	to = nil;
	ARGBEGIN{
	case 'm':
		nomixer = 1;
		break;
	case 'r':
		rate = atoi(EARGF(usage()));
		if (rate < 1)
			sysfatal("invalid rate %d", rate);
		break;
	case 't':
		to = EARGF(usage());
		break;
	default:
		usage();
	}ARGEND;

	if (argc < 1)
		usage();

	if (!nomixer && to != nil) {
		fprint(2, "can't record with the mixer disabled\n");
		threadexitsall("fail");
	}

	if (!nomixer && (audio = open("/dev/audio", OWRITE)) < 0)
		sysfatal("%r");
	if (to != nil && (record = create(to, OWRITE|OTRUNC, 0644)) < 0)
		sysfatal("%r");

	quotefmtinstall();
	setfcr(FPPDBL|FPRNR);

	/* go through all groups */
	for (i = 0; i < argc; i++) {
		/* search for specific synth handler by its name */
		s = smprint("%s/metadata", argv[i]);
		if ((b = Bopen(s, OREAD)) == nil)
			sysfatal("%r");
		free(s);
		synth = nil;
		while ((s = Brdline(b, '\n')) != nil) {
			if (strncmp(s, "name\t", 5) == 0) {
				s[Blinelen(b)-1] = 0;
				for (j = 0; j < nelem(synths) && strcmp(synths[j]->name, s+5) != 0; j++);
				if (j >= nelem(synths))
					sysfatal("unknown synth %q\n", s+5);
				if ((groups = realloc(groups, sizeof(Group)*(numgroups+1))) == nil)
					sysfatal("memory");
				synth = synths[j];
				memset(&groups[numgroups], 0, sizeof(Group));
				if ((s = smprint("%s/clone", argv[i])) == nil)
					sysfatal("memory");
				if ((groups[numgroups].clone = open(s, OREAD)) < 0)
					sysfatal("%r");
				free(s);
				groups[numgroups].path = argv[i];
				groups[numgroups++].synth = synth;
				break;
			}
		}
		Bterm(b);
		if (synth == nil)
			sysfatal("no name set in %s/metadata", argv[i]);
	}

	if (!nomixer)
		proccreate(mixer, nil, 4096);

	for (;;) {
		if ((n = read(0, t, sizeof(t)-1)) < 1)
			break;
		t[n] = 0;
		for (s = t; s != nil && *s;) {
			i = i36(s[0]);
			qlock(&grouplock);
			s = parse(s, i < numgroups ? &groups[i] : nil);
			qunlock(&grouplock);
		}
	}

	threadexitsall(nil);
}