shithub: neindaw

ref: 5cf2a66d5a71fad0128b2f9d0b088cea743c0d60
dir: neindaw/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 "ui.h"
#include "fs.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 noisefreq;
	float envperiod;
	float hit;
	int envmode;
};

static int tickhz = 1773400;
static int rate = 44100;

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
writectl(UI *ui, int auxtype, char *s)
{
	struct 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->envperiod) {
		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->noisefreq) {
		r = MAX(1, MIN(31, tickhz/(16 * (*ui->zone))));
		regw(ay, AY38910_REG_PERIOD_NOISE, r);
		*ui->zone = tickhz/16/r;
		return 0;
	}

	if (ui->zone == &dsp->hit) {
		if (*ui->zone)
			regw(ay, AY38910_REG_ENV_SHAPE_CYCLE, dsp->envmode);
		return 0;
	}

	dsp->envmode = 0;
	if (dsp->hold)
		dsp->envmode |= 1<<0;
	if (dsp->alternate)
		dsp->envmode |= 1<<1;
	if (dsp->attack)
		dsp->envmode |= 1<<2;
	if (dsp->cont)
		dsp->envmode |= 1<<3;

	return 0;
}

#define BIND(x) do { ui = x; ui->userdata = dsp; ui->writestr = writectl; writectl(ui, Xuictl, "reset"); } while(0)

extern File *uif;

static Auxdsp *
dspnew(void)
{
	struct Auxdsp *dsp;
	UI *ui;
	char s[32];
	float min, step, max;
	ay38910_desc_t desc = {
		.type = AY38910_TYPE_8910,
		.tick_hz = tickhz,
		.sound_hz = rate,
		.magnitude = 1.0,
	};
	int i;

	if ((dsp = malloc(sizeof(*dsp))) == nil)
		return nil;
	ay38910_init(&dsp->ay, &desc);
	regw(&dsp->ay, AY38910_REG_ENABLE, 0xff); /* disable everything */

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

	ui_vgroup("AY-3-8910");

	BIND(ui_hslider("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_tgroup(s);
			ui_hgroup("Tone");
				ui_declare(&dsp->chan[i].freq, "0", "");
				ui_declare(&dsp->chan[i].freq, "unit", "Hz");
				BIND(ui_hslider("Frequency", &dsp->chan[i].freq, min, min, max, step));

				ui_declare(&dsp->chan[i].enable, "1", "");
				BIND(ui_checkbox("Enable", &dsp->chan[i].enable));
			ui_endgroup();

			ui_declare(&dsp->chan[i].amp, "0", "");
			BIND(ui_hslider("Volume", &dsp->chan[i].amp, 1.0f, 0.0f, 1.0f, 1.0f/15.0f));

			ui_declare(&dsp->chan[i].envelope, "1", "");
			BIND(ui_checkbox("Envelope", &dsp->chan[i].envelope));

			ui_declare(&dsp->chan[i].noise, "2", "");
			BIND(ui_checkbox("Noise", &dsp->chan[i].noise));
		ui_endgroup();
	}

	min = ceil(tickhz/496);
	max = floor(tickhz/16);
	step = MAX(1, tickhz/480 - tickhz/496);
	ui_declare(&dsp->noisefreq, "unit", "Hz");
	BIND(ui_hslider("Noise", &dsp->noisefreq, min, min, max, step));

	ui_vgroup("Envelope");
		min = MAX(1, 256000/tickhz);
		max = floor(16776960000LL/tickhz);
		ui_declare(&dsp->envperiod, "0", "");
		ui_declare(&dsp->envperiod, "unit", "ms");
		BIND(ui_hslider("Period", &dsp->envperiod, 500, min, max, 1));

		ui_declare(&dsp->hold, "1", "");
		BIND(ui_checkbox("Hold", &dsp->hold));

		ui_declare(&dsp->alternate, "2", "");
		BIND(ui_checkbox("Alternate", &dsp->alternate));

		ui_declare(&dsp->attack, "3", "");
		BIND(ui_checkbox("Attack", &dsp->attack));

		ui_declare(&dsp->cont, "4", "");
		BIND(ui_checkbox("Continue", &dsp->cont));

		ui_declare(&dsp->hit, "5", "");
		BIND(ui_button("Hit", &dsp->hit));
	ui_endgroup();

	ui_endgroup();

	return dsp;
}

static void
dspfree(Auxdsp *dsp)
{
	free(dsp);
}

static void
dspreset(Auxdsp *dsp)
{
	ay38910_reset(&dsp->ay);
}

static int
dspread(Auxdsp *dsp, float *b, int n)
{
	int i;

	for (i = 0; i < n; i++) {
		while (!ay38910_tick(&dsp->ay));
		b[i] = dsp->ay.sample;
	}

	return n;
}

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

static Fs fs = {
	.metadata ="name\tAY-3-8910\ngroup\tSynthesis\n",
	.dsp = {
		.new = dspnew,
		.free = dspfree,
		.reset = dspreset,
		.read = dspread,
	},
};

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

	fsinit(&fs);
	threadpostmountsrv(&fs.srv, srv, mtpt, MREPL);
	threadexits(nil);
}