shithub: neindaw

ref: 22f7e9038ca004a322d82d3ee543eb8ed42dbec9
dir: /ay/ay.c/

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

/*
FIXME just a note, remove later
	ay = aynew(1.0);
	amp(ay, 0, Levelenv | 4);
	envp(ay, ms2ep(500));
	envsc(ay, Continue|Attack);
	toneon(ay, 0);
	tone(ay, 0, 500);
*/

#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 volume;
	float noise;
};

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

static int tickhz = 1000000;

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 int
hz2tp(int hz)
{
	return MAX(1, MIN(4095, tickhz / (MAX(1, hz) * 16)));
}

static int
hz2ep(int hz)
{
	return MAX(1, MIN(65535, tickhz / (MAX(1, hz) * 256)));
}

static int
ms2ep(int ms)
{
	return MAX(1, MIN(65535, (tickhz / 1000) * ms / 256));
}

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

	tp = hz2tp(hz);
	regw(ay, chan*2+0, tp & 0xff); /* fine */
	regw(ay, chan*2+1, (tp>>8) & 0x0f); /* coarse */
}

static void
toneon(ay38910_t *ay, int chan)
{
	regw(ay, AY38910_REG_ENABLE, regr(ay, AY38910_REG_ENABLE) & ~(1<<chan));
}

static void
toneoff(ay38910_t *ay, int chan)
{
	regw(ay, AY38910_REG_ENABLE, regr(ay, AY38910_REG_ENABLE) | 1<<chan);
}

static void
envp(ay38910_t *ay, int p)
{
	regw(ay, AY38910_REG_ENV_PERIOD_FINE, p & 0xff);
	regw(ay, AY38910_REG_ENV_PERIOD_COARSE, p>>8);
}

static void
envsc(ay38910_t *ay, int v)
{
	regw(ay, AY38910_REG_ENV_SHAPE_CYCLE, v & 0xf);
}

static void
amp(ay38910_t *ay, int chan, int level)
{
	regw(ay, AY38910_REG_AMP_A+chan, level);
}

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
buildui(Auxdsp *dsp, UIGlue *ui)
{
	float min, step, max;
	char s[32];
	int i;

	min = tickhz/65520.0f;
	max = tickhz/16.0f;
	step = (max-min)/4094.0f;

	ui->openVerticalBox(ui->f, "AY-3-8910");

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

		ui->declare(ui->f, &dsp->chan[i].freq, "0", "");
		ui->declare(ui->f, &dsp->chan[i].freq, "unit", "Hz");
		ui->addHorizontalSlider(ui->f, "Tone", &dsp->chan[i].freq, min+(max-min)/2.0f, min, max, step);

		ui->declare(ui->f, &dsp->chan[i].amp, "1", "");
		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].enable, "2", "");
		ui->addCheckButton(ui->f, "Enable", &dsp->chan[i].enable);

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

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

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

	min = tickhz/496.0f;
	max = tickhz/16.0f;
	step = (max-min)/30.0f;

	ui->declare(ui->f, &dsp->noise, "noise", "");
	ui->addHorizontalSlider(ui->f, "Noise", &dsp->noise, 200, min, max, step);

	ui->openVerticalBox(ui->f, "Envelope");
	ui->declare(ui->f, &dsp->hold, "0", "");
	ui->addCheckButton(ui->f, "Hold", &dsp->hold);
	ui->declare(ui->f, &dsp->alternate, "1", "");
	ui->addCheckButton(ui->f, "Alternate", &dsp->alternate);
	ui->declare(ui->f, &dsp->attack, "2", "");
	ui->addCheckButton(ui->f, "Attack", &dsp->attack);
	ui->declare(ui->f, &dsp->cont, "3", "");
	ui->addCheckButton(ui->f, "Continue", &dsp->cont);
	ui->closeBox(ui->f);

	ui->addHorizontalSlider(ui->f, "Volume", &dsp->volume, 1.0f, 0.0f, 1.0f, 0.001f);

	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
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);
}