ref: 29415dfac1fb18f32d86433c00b4b4942c4c24d6
dir: /ay/ay.c/
#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);
}