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