ref: dd368be0bdd5a2e7335fe5eab0cdbab965341925
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 "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*(*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(int *numin, int *numout) { 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; *numin = 0; *numout = 1; 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", "s"); BIND(ui_hslider("Period", &dsp->envperiod, 0.5, min/1000.0, max/1000.0, 0.001)); 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); }