ref: a85496cea4c5c62a2038b1b488f541e9030ff379
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 = RATE;
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);
}