ref: 8b0c34fef29634833180f62135c3f8b33cc0be04
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" /* 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 = 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 int hz2tp(int hz) { return MAX(1, MIN(4095, tickhz / (MAX(1, hz) * 16))); } static int tp2hz(int tp) { return tickhz / tp / 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 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 int writefreq(UI *ui, int auxtype, char *s) { Auxdsp *dsp; int r, chan, tp; if ((r = ui_writestr(ui, auxtype, s)) != 0) return r; dsp = ui->userdata; for (chan = 0; ui->zone != &dsp->chan[chan].freq; chan++); tp = hz2tp(*ui->zone); regw(&dsp->ay, chan*2+0, tp & 0xff); /* fine */ regw(&dsp->ay, chan*2+1, (tp>>8) & 0x0f); /* coarse */ *ui->zone = tp2hz(tp); 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->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->writestr = writefreq; ui->addHorizontalSlider(ui->f, "Tone", &dsp->chan[i].freq, min, min, max, step); ui->writestr = nil; 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 = 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, 4000, 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); }