shithub: neindaw

ref: 29415dfac1fb18f32d86433c00b4b4942c4c24d6
dir: /ay/ay.c/

View raw version
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>
#define CHIPS_IMPL
#define CHIPS_ASSERT assert
#include "ay38910.h"
#include "common.h"
#include "uiglue.h"
#include "aux.h"

#define MIN(a,b) ((a)<=(b)?(a):(b))
#define MAX(a,b) ((a)>=(b)?(a):(b))

enum {
	Levelenv = 1<<4,

	Hold = 1<<0,
	Alternate = 1<<1,
	Attack = 1<<2,
	Continue = 1<<3,
};

struct Auxdsp {
	ay38910_t ay;
	struct {
		float freq;
		float amp;
		float envelope;
		float noise;
		float enable;
	}chan[3];
	float hold, alternate, attack, cont;
	float noise;
	float envelope;
};

static Aux rootaux[] = {
	[Xctl] = {.type = Xctl},
	[Xmetadata] = {.type = Xmetadata},
	[Xclone] = {.type = Xclone},
};

static int tickhz = 1773400;

extern Srv fs;

static int rate = 44100;
static Aux *objs[32];
static char *meta =
	"name\tAY-3-8910\n"
	"group\tSynthesis\n";

static void
regw(ay38910_t *ay, int reg, int v)
{
	u64int p;

	/* latch address */
	p = AY38910_BDIR | AY38910_BC1;
	AY38910_SET_DATA(p, reg);
	ay38910_iorq(ay, p);

	/* write to psg */
	p = AY38910_BDIR;
	AY38910_SET_DATA(p, v);
	ay38910_iorq(ay, p);

	/* inactive */
	ay38910_iorq(ay, 0);
}

static int
regr(ay38910_t *ay, int reg)
{
	u64int p;
	int v;

	/* latch address */
	p = AY38910_BDIR | AY38910_BC1;
	AY38910_SET_DATA(p, reg);
	ay38910_iorq(ay, p);

	/* read from psg */
	v = AY38910_GET_DATA(ay38910_iorq(ay, AY38910_BC1));

	/* inactive */
	ay38910_iorq(ay, 0);

	return v;
}

static float
tone(ay38910_t *ay, int chan, float hz)
{
	int tp;

	tp = MAX(1, MIN(4095, tickhz / (MAX(1, hz) * 16)));
	regw(ay, AY38910_REG_PERIOD_A_FINE + 2*chan, tp & 0xff);
	regw(ay, AY38910_REG_PERIOD_A_COARSE + 2*chan, (tp>>8) & 0x0f);

	return tickhz/tp/16;
}

static int
writeay(UI *ui, int auxtype, char *s)
{
	Auxdsp *dsp;
	ay38910_t *ay;
	int r, i, level;
	vlong pd;

	if ((r = ui_writestr(ui, auxtype, s)) < 0)
		return r;

	dsp = ui->userdata;
	ay = &dsp->ay;
	for (i = 0; i < nelem(dsp->chan); i++) {
		if (ui->zone == &dsp->chan[i].freq) {
			*ui->zone = tone(ay, i, *ui->zone);
			return 0;
		}
		if (ui->zone == &dsp->chan[i].enable) {
			r = regr(ay, AY38910_REG_ENABLE) | 1<<i;
			if (*ui->zone)
				r &= ~(1<<i);
			regw(ay, AY38910_REG_ENABLE, r);
			return 0;
		}
		if (ui->zone == &dsp->chan[i].noise) {
			r = regr(ay, AY38910_REG_ENABLE) | 1<<(3+i);
			if (*ui->zone)
				r &= ~(1<<(3+i));
			regw(ay, AY38910_REG_ENABLE, r);
			return 0;
		}
		if (ui->zone == &dsp->chan[i].envelope) {
			r = regr(ay, AY38910_REG_AMP_A+i) & ~(1<<4);
			if (*ui->zone)
				r |= 1<<4;
			regw(ay, AY38910_REG_AMP_A+i, r);
			return 0;
		}
		if (ui->zone == &dsp->chan[i].amp) {
			level = MAX(0, MIN(15, *ui->zone * 15));
			level |= regr(ay, AY38910_REG_AMP_A+i) & (1<<4);
			regw(ay, AY38910_REG_AMP_A+i, level);
			*ui->zone = (float)(level&0xf) / 15.0f;
			return 0;
		}
	}

	if (ui->zone == &dsp->envelope) {
		pd = MAX(1, MIN(65535, (tickhz / 1000) * (*ui->zone) / 256));
		regw(ay, AY38910_REG_ENV_PERIOD_FINE, pd&0xff);
		regw(ay, AY38910_REG_ENV_PERIOD_COARSE, pd>>8);
		*ui->zone = MAX(1, pd*256000LL/tickhz);
		return 0;
	}

	if (ui->zone == &dsp->noise) {
		r = MAX(1, MIN(31, tickhz/(16 * (*ui->zone))));
		regw(ay, AY38910_REG_PERIOD_NOISE, r);
		*ui->zone = tickhz/16/r;
		return 0;
	}

	r = 0;
	if (dsp->hold)
		r |= 1<<0;
	if (dsp->alternate)
		r |= 1<<1;
	if (dsp->attack)
		r |= 1<<2;
	if (dsp->cont)
		r |= 1<<3;
	regw(ay, AY38910_REG_ENV_SHAPE_CYCLE, r);

	return 0;
}

static void
buildui(Auxdsp *dsp, UIGlue *ui)
{
	float min, step, max;
	char s[32];
	int i;

	min = ceil(tickhz/65520);
	max = floor(tickhz/16);
	step = MAX(1, ceil(tickhz/65504 - tickhz/65520));

	ui->userdata = dsp;
	ui->writestr = writeay;
	ui->openVerticalBox(ui->f, "AY-3-8910");
	ui->addHorizontalSlider(ui->f, "Volume", &dsp->ay.mag, 1.0f, 0.0f, 1.0f, 0.001f);

	for (i = 0; i < nelem(dsp->chan); i++) {
		sprint(s, "%c", 'A'+i);
		ui->openVerticalBox(ui->f, s);

		ui->openHorizontalBox(ui->f, "Tone");
		ui->declare(ui->f, &dsp->chan[i].freq, "0", "");
		ui->declare(ui->f, &dsp->chan[i].freq, "unit", "Hz");
		ui->addHorizontalSlider(ui->f, "Frequency", &dsp->chan[i].freq, min, min, max, step);
		ui->declare(ui->f, &dsp->chan[i].enable, "1", "");
		ui->addCheckButton(ui->f, "Enable", &dsp->chan[i].enable);
		ui->closeBox(ui->f);

		ui->declare(ui->f, &dsp->chan[i].amp, "0", "");
		ui->addHorizontalSlider(ui->f, "Volume", &dsp->chan[i].amp, 1.0f, 0.0f, 1.0f, 1.0f/15.0f);

		ui->declare(ui->f, &dsp->chan[i].envelope, "1", "");
		ui->addCheckButton(ui->f, "Envelope", &dsp->chan[i].envelope);

		ui->declare(ui->f, &dsp->chan[i].noise, "2", "");
		ui->addCheckButton(ui->f, "Noise", &dsp->chan[i].noise);

		ui->closeBox(ui->f);
	}

	min = ceil(tickhz/496);
	max = floor(tickhz/16);
	step = MAX(1, tickhz/480 - tickhz/496);
	ui->declare(ui->f, &dsp->noise, "unit", "Hz");
	ui->addHorizontalSlider(ui->f, "Noise", &dsp->noise, min, min, max, step);

	ui->openVerticalBox(ui->f, "Envelope");
	min = MAX(1, 256000/tickhz);
	max = floor(16776960000LL/tickhz);
	ui->declare(ui->f, &dsp->envelope, "0", "");
	ui->declare(ui->f, &dsp->envelope, "unit", "ms");
	ui->addHorizontalSlider(ui->f, "Period", &dsp->envelope, 500, min, max, 1);
	ui->declare(ui->f, &dsp->hold, "1", "");
	ui->addCheckButton(ui->f, "Hold", &dsp->hold);
	ui->declare(ui->f, &dsp->alternate, "2", "");
	ui->addCheckButton(ui->f, "Alternate", &dsp->alternate);
	ui->declare(ui->f, &dsp->attack, "3", "");
	ui->addCheckButton(ui->f, "Attack", &dsp->attack);
	ui->declare(ui->f, &dsp->cont, "4", "");
	ui->addCheckButton(ui->f, "Continue", &dsp->cont);
	ui->closeBox(ui->f);

	ui->closeBox(ui->f);
}

static Aux *
newobj(char *name)
{
	File *f;
	Aux *o;
	Auxdsp *dsp;
	int i;
	ay38910_desc_t d = {
		.type = AY38910_TYPE_8910,
		.tick_hz = tickhz,
		.sound_hz = rate,
		.magnitude = 1.0,
	};

	for (i = 0, o = nil; o == nil && i < nelem(objs); i++) {
		if (objs[i] == nil){
			o = objs[i] = calloc(1, sizeof(*o) + sizeof(Auxdsp));
			break;
		}
	}
	if (o == nil)
		return nil;

	o->id = i;
	o->type = Xdsp;
	o->ctl = Xdspctl;
	o->data = Xdspdata;
	o->dsp = dsp = (Auxdsp*)(o+1);
	ay38910_init(&dsp->ay, &d);
	regw(&dsp->ay, AY38910_REG_ENABLE, 0xff); /* disable everything */

	sprint(name, "%d", o->id);
	if ((f = createfile(fs.tree->root, name, nil, DMDIR|0775, o)) == nil)
		return nil;
	closefile(createfile(f, "ctl", nil, 0664, &o->ctl));
	closefile(createfile(f, "data", nil, 0664, &o->data));
	closefile(f);

	uiglue.f = f;
	buildui(dsp, &uiglue);

	return o;
}

static void *
auxtype2obj(int *type)
{
	switch (*type) {
	case Xdspctl:
	case Xuictl:
		return (uchar*)type - offsetof(Aux, ctl);
	case Xdspdata:
		return (uchar*)type - offsetof(Aux, data);
	case Xuimeta:
		return (uchar*)type - offsetof(Aux, metadata);
	default:
		sysfatal("trying to get aux out of type %d", *type);
	}

	return nil;
}

static void
fsopen(Req *r)
{
	respond(r, nil);
}

static void
fsread(Req *r)
{
	Aux *a, *o;
	Auxdsp *dsp;
	char b[256];
	float *p;
	int n;

	a = r->fid->file->aux;
	switch (a->type) {
	case Xctl:
		respond(r, nil);
		break;
	case Xmetadata:
		readstr(r, meta);
		respond(r, nil);
		break;
	case Xclone:
		if (r->ifcall.offset == 0) {
			if (newobj(b) != nil) {
				readstr(r, b);
			} else {
				snprint(b, sizeof(b), "no free objects: %r");
				respond(r, b);
				break;
			}
		}
		respond(r, nil);
		break;
	case Xuictl:
	case Xuimeta:
		o = auxtype2obj(&a->type);
		if (o->ui->readstr != nil)
			readstr(r, o->ui->readstr(o->ui, a->type, b, sizeof(b)));
		respond(r, nil);
		break;
	case Xdspdata:
		o = auxtype2obj(&a->type);
		dsp = o->dsp;
		n = r->ifcall.count;
		for (p = (float*)r->ofcall.data; n >= sizeof(float); p++) {
			while (!ay38910_tick(&dsp->ay));
			*p = dsp->ay.sample;
			n -= sizeof(float);
		}
		r->ofcall.count = r->ifcall.count - n;
		respond(r, nil);
		break;
	default:
		respond(r, "not implemented");
		break;
	}
}

static void
fswrite(Req *r)
{
	Aux *a, *o;
	char b[256];

	if (r->ifcall.count >= sizeof(b)) {
		respond(r, "can't fit into buffer");
		return;
	}

	memmove(b, r->ifcall.data, r->ifcall.count);
	b[r->ifcall.count] = '\0';
	r->ofcall.count = r->ifcall.count;

	a = r->fid->file->aux;
	switch (a->type) {
	case Xuictl:
		o = auxtype2obj(&a->type);
		if (o->ui->writestr == nil)
			respond(r, "not implemented");
		else if (o->ui->writestr(o->ui, a->type, b) >= 0)
			respond(r, nil);
		else
			responderror(r);
		break;
	case Xdspctl: /* FIXME changing sampling rate */
		o = auxtype2obj(&a->type);
		if (strncmp(b, "reset", 5) == 0)
			ay38910_reset(&o->dsp->ay);
		respond(r, nil);
		break;
	case Xmetadata: /* FIXME should be possible to add new key/value */
	default:
		respond(r, "not implemented");
		break;
	}
}

Srv fs = {
	.open = fsopen,
	.read = fsread,
	.write = fswrite,
};

static void
freeobj(Aux *o)
{
	if (o == nil)
		return;

	if (o->type == Xdsp)
		objs[o->id] = nil;

	free(o);
}

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

static void
fsdestroyfile(File *f)
{
	Aux *a;

	if ((a = f->aux) == nil)
		return;
	switch (a->type) {
	case Xdsp:
	case Xui:
		freeobj(a);
		f->aux = nil;
		break;
	}
}

void
threadmain(int argc, char **argv)
{
	char *srv, *mtpt;

	srv = nil;
	mtpt = nil;
	ARGBEGIN{
	case 'D':
		chatty9p++;
		break;
	case 's':
		srv = EARGF(usage());
		break;
	case 'm':
		mtpt = EARGF(usage());
		break;
	case 'r':
		rate = atoi(EARGF(usage()));
		break;
	case 't':
		tickhz = atoi(EARGF(usage()));
		break;
	default:
		usage();
	}ARGEND

	if (rate < 1 || tickhz < 1)
		usage();

	if (srv == nil && mtpt == nil)
		sysfatal("must specify -s or -m option");

	fs.tree = alloctree(nil, nil, DMDIR|0775, fsdestroyfile);
	closefile(createfile(fs.tree->root, "ctl", nil, 0666, &rootaux[Xctl]));
	closefile(createfile(fs.tree->root, "metadata", nil, 0444, &rootaux[Xmetadata]));
	closefile(createfile(fs.tree->root, "clone", nil, 0444, &rootaux[Xclone]));
	threadpostmountsrv(&fs, srv, mtpt, MREPL);
	threadexits(nil);
}