ref: c32367bfe581fb67aff6597db4f24b3944028676
parent: 8e7cbb0dc376fc473f627250402fab3f20192e78
author: Sigrid Haflínudóttir <[email protected]>
date: Wed Mar 18 19:34:45 EDT 2020
rename ay38910 -> ay
--- /dev/null
+++ b/ay/README.md
@@ -1,0 +1,12 @@
+# ay
+
+AY-3-8910 as a filesystem, part of neindaw. WIP.
+
+This is a prototype built to achieve the following:
+
+ * figure out ways to deal with multi-voice/polyphony and mixing
+ * create a UDP-to-neindaw translation layer, a companion
+ app that allows using neindaw with [Orca](https://github.com/hundredrabbits/Orca).
+ * visualizing waveforms
+
+AY-3-8910 is emulated using [floooh/chips](https://github.com/floooh/chips).
--- /dev/null
+++ b/ay/ay.c
@@ -1,0 +1,452 @@
+#include <u.h>
+#include <libc.h>
+#include <fcall.h>
+#include <thread.h>
+#include <9p.h>
+#include "common.h"
+#include "aux.h"
+#define CHIPS_IMPL
+#define CHIPS_ASSERT assert
+#include "ay38910.h"
+#include "uiglue.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 {
+ Tickhz = 1000000,
+
+ 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;
+};
+
+static Aux rootaux[] = {
+ [Xctl] = {.type = Xctl},
+ [Xmetadata] = {.type = Xmetadata},
+ [Xclone] = {.type = Xclone},
+};
+
+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
+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
+tone(ay38910_t *ay, int chan, int hz)
+{
+ int tp;
+
+ tp = hz2tp(hz);
+ regw(ay, chan*2+0, tp & 0xff); /* fine */
+ regw(ay, chan*2+1, (tp>>8) & 0x0f); /* coarse */
+}
+
+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 void
+buildui(Auxdsp *dsp, UIGlue *ui)
+{
+ float min, step, max;
+ char s[32];
+ int i;
+
+ min = ceil(Tickhz/(16.0f*4095.0f));
+ max = floor(Tickhz/16.0f);
+ step = ceil(Tickhz/(16.0f*4094.0f)) - min;
+
+ 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->addHorizontalSlider(ui->f, "Tone", &dsp->chan[i].freq, 400.0f, min, max, step);
+
+ ui->declare(ui->f, &dsp->chan[i].amp, "1", "");
+ ui->addHorizontalSlider(ui->f, "Volume", &dsp->chan[i].amp, 15.0f, 0.0f, 15.0f, 1.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);
+
+ 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]\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;
+ default:
+ usage();
+ }ARGEND
+
+ 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);
+}
--- /dev/null
+++ b/ay/ay38910.h
@@ -1,0 +1,598 @@
+#pragma once
+/*
+ ay38910.h -- AY-3-8910/2/3 sound chip emulator
+
+ Do this:
+ #define CHIPS_IMPL
+ before you include this file in *one* C or C++ file to create the
+ implementation.
+
+ Optionally provde the following macros with your own implementation
+
+ CHIPS_ASSERT(c) -- your own assert macro (default: assert(c))
+
+ EMULATED PINS:
+
+ +-----------+
+ BC1 -->| |<-> DA0
+ BDIR -->| |...
+ | |<-> DA7
+ | |
+ | |<-> (IOA0)
+ | |...
+ | |<-> (IOA7)
+ | |
+ | |<-> (IOB0)
+ | |...
+ | |<-> (IOB7)
+ +-----------+
+
+ NOT EMULATED:
+
+ - the BC2 pin is ignored since it makes only sense when connected to
+ a CP1610 CPU
+ - the RESET pin state is ignored, instead call ay38910_reset()
+
+ ## zlib/libpng license
+
+ Copyright (c) 2018 Andre Weissflog
+ This software is provided 'as-is', without any express or implied warranty.
+ In no event will the authors be held liable for any damages arising from the
+ use of this software.
+ Permission is granted to anyone to use this software for any purpose,
+ including commercial applications, and to alter it and redistribute it
+ freely, subject to the following restrictions:
+ 1. The origin of this software must not be misrepresented; you must not
+ claim that you wrote the original software. If you use this software in a
+ product, an acknowledgment in the product documentation would be
+ appreciated but is not required.
+ 2. Altered source versions must be plainly marked as such, and must not
+ be misrepresented as being the original software.
+ 3. This notice may not be removed or altered from any source
+ distribution.
+*/
+#include <stdint.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ Pin definitions.
+
+ Note that the BC2 is not emulated since it is usually always
+ set to active when not connected to a CP1610 processor. The
+ remaining BDIR and BC1 pins are interpreted as follows:
+
+ |BDIR|BC1|
+ +----+---+
+ | 0 | 0 | INACTIVE
+ | 0 | 1 | READ FROM PSG
+ | 1 | 0 | WRITE TO PSG
+ | 1 | 1 | LATCH ADDRESS
+*/
+
+/* 8 bits data/address bus shared with CPU data bus */
+#define AY38910_DA0 (1ULL<<16)
+#define AY38910_DA1 (1ULL<<17)
+#define AY38910_DA2 (1ULL<<18)
+#define AY38910_DA3 (1ULL<<19)
+#define AY38910_DA4 (1ULL<<20)
+#define AY38910_DA5 (1ULL<<21)
+#define AY38910_DA6 (1ULL<<22)
+#define AY38910_DA7 (1ULL<<23)
+
+/* reset pin shared with CPU */
+#define AY38910_RESET (1ULL<<34)
+
+/* chip-specific pins start at position 44 */
+#define AY38910_BDIR (1ULL<<40)
+#define AY38910_BC1 (1ULL<<41)
+
+/* IO port pins */
+#define AY38910_IOA0 (1ULL<<48)
+#define AY38910_IOA1 (1ULL<<49)
+#define AY38910_IOA2 (1ULL<<50)
+#define AY38910_IOA3 (1ULL<<51)
+#define AY38910_IOA4 (1ULL<<52)
+#define AY38910_IOA5 (1ULL<<53)
+#define AY38910_IOA6 (1ULL<<54)
+#define AY38910_IOA7 (1ULL<<55)
+
+#define AY38910_IOB0 (1ULL<<56)
+#define AY38910_IOB1 (1ULL<<57)
+#define AY38910_IOB2 (1ULL<<58)
+#define AY38910_IOB3 (1ULL<<59)
+#define AY38910_IOB4 (1ULL<<60)
+#define AY38910_IOB5 (1ULL<<61)
+#define AY38910_IOB6 (1ULL<<62)
+#define AY38910_IOB7 (1ULL<<63)
+
+/* AY-3-8910 registers */
+#define AY38910_REG_PERIOD_A_FINE (0)
+#define AY38910_REG_PERIOD_A_COARSE (1)
+#define AY38910_REG_PERIOD_B_FINE (2)
+#define AY38910_REG_PERIOD_B_COARSE (3)
+#define AY38910_REG_PERIOD_C_FINE (4)
+#define AY38910_REG_PERIOD_C_COARSE (5)
+#define AY38910_REG_PERIOD_NOISE (6)
+#define AY38910_REG_ENABLE (7)
+#define AY38910_REG_AMP_A (8)
+#define AY38910_REG_AMP_B (9)
+#define AY38910_REG_AMP_C (10)
+#define AY38910_REG_ENV_PERIOD_FINE (11)
+#define AY38910_REG_ENV_PERIOD_COARSE (12)
+#define AY38910_REG_ENV_SHAPE_CYCLE (13)
+#define AY38910_REG_IO_PORT_A (14) /* not on AY-3-8913 */
+#define AY38910_REG_IO_PORT_B (15) /* not on AY-3-8912/3 */
+/* number of registers */
+#define AY38910_NUM_REGISTERS (16)
+/* error-accumulation precision boost */
+#define AY38910_FIXEDPOINT_SCALE (16)
+/* number of channels */
+#define AY38910_NUM_CHANNELS (3)
+/* DC adjustment buffer length */
+#define AY38910_DCADJ_BUFLEN (512)
+
+/* IO port names */
+#define AY38910_PORT_A (0)
+#define AY38910_PORT_B (1)
+
+/* envelope shape bits */
+#define AY38910_ENV_HOLD (1<<0)
+#define AY38910_ENV_ALTERNATE (1<<1)
+#define AY38910_ENV_ATTACK (1<<2)
+#define AY38910_ENV_CONTINUE (1<<3)
+
+/* callbacks for input/output on I/O ports */
+typedef uint8_t (*ay38910_in_t)(int port_id, void* user_data);
+typedef void (*ay38910_out_t)(int port_id, uint8_t data, void* user_data);
+
+/* chip subtypes */
+typedef enum {
+ AY38910_TYPE_8910 = 0,
+ AY38910_TYPE_8912,
+ AY38910_TYPE_8913
+} ay38910_type_t;
+
+/* setup parameters for ay38910_init() call */
+typedef struct {
+ ay38910_type_t type; /* the subtype (default 0 is AY-3-8910) */
+ int tick_hz; /* frequency at which ay38910_tick() will be called in Hz */
+ int sound_hz; /* number of samples that will be produced per second */
+ float magnitude; /* output sample magnitude, from 0.0 (silence) to 1.0 (max volume) */
+ ay38910_in_t in_cb; /* I/O port input callback */
+ ay38910_out_t out_cb; /* I/O port output callback */
+ void* user_data; /* optional user-data for callbacks */
+} ay38910_desc_t;
+
+/* a tone channel */
+typedef struct {
+ uint16_t period;
+ uint16_t counter;
+ uint32_t bit;
+ uint32_t tone_disable;
+ uint32_t noise_disable;
+} ay38910_tone_t;
+
+/* the noise channel state */
+typedef struct {
+ uint16_t period;
+ uint16_t counter;
+ uint32_t rng;
+ uint32_t bit;
+} ay38910_noise_t;
+
+/* the envelope generator */
+typedef struct {
+ uint16_t period;
+ uint16_t counter;
+ bool shape_holding;
+ bool shape_hold;
+ uint8_t shape_counter;
+ uint8_t shape_state;
+} ay38910_env_t;
+
+/* AY-3-8910 state */
+typedef struct {
+ ay38910_type_t type; /* the chip flavour */
+ ay38910_in_t in_cb; /* the port-input callback */
+ ay38910_out_t out_cb; /* the port-output callback */
+ void* user_data; /* optional user-data for callbacks */
+ uint32_t tick; /* a tick counter for internal clock division */
+ uint8_t addr; /* 4-bit address latch */
+ union { /* the register bank */
+ uint8_t reg[AY38910_NUM_REGISTERS];
+ struct {
+ uint8_t period_a_fine;
+ uint8_t period_a_coarse;
+ uint8_t period_b_fine;
+ uint8_t period_b_coarse;
+ uint8_t period_c_fine;
+ uint8_t period_c_coarse;
+ uint8_t period_noise;
+ uint8_t enable;
+ uint8_t amp_a;
+ uint8_t amp_b;
+ uint8_t amp_c;
+ uint8_t period_env_fine;
+ uint8_t period_env_coarse;
+ uint8_t env_shape_cycle;
+ uint8_t port_a;
+ uint8_t port_b;
+ };
+ };
+ ay38910_tone_t tone[AY38910_NUM_CHANNELS]; /* the 3 tone channels */
+ ay38910_noise_t noise; /* the noise generator state */
+ ay38910_env_t env; /* the envelope generator state */
+ uint64_t pins; /* last pin state for debug inspection */
+
+ /* sample generation state */
+ int sample_period;
+ int sample_counter;
+ float mag;
+ float sample;
+ float dcadj_sum;
+ uint32_t dcadj_pos;
+ float dcadj_buf[AY38910_DCADJ_BUFLEN];
+} ay38910_t;
+
+/* extract 8-bit data bus from 64-bit pins */
+#define AY38910_GET_DATA(p) ((uint8_t)(p>>16))
+/* merge 8-bit data bus value into 64-bit pins */
+#define AY38910_SET_DATA(p,d) {p=((p&~0xFF0000)|((d&0xFF)<<16));}
+/* set 8-bit port A data on 64-bit pin mask */
+#define AY38910_SET_PA(p,d) {p=((p&~0x00FF000000000000ULL)|((((uint64_t)d)&0xFFULL)<<48));}
+/* set 8-bit port B data on 64-bit pin mask */
+#define AY38910_SET_PB(p,d) {p=((p&~0xFF00000000000000ULL)|((((uint64_t)d)&0xFFULL)<<56));}
+
+/* initialize a AY-3-8910 instance */
+void ay38910_init(ay38910_t* ay, const ay38910_desc_t* desc);
+/* reset an existing AY-3-8910 instance */
+void ay38910_reset(ay38910_t* ay);
+/* perform an IO request machine cycle */
+uint64_t ay38910_iorq(ay38910_t* ay, uint64_t pins);
+/* tick the AY-3-8910, return true if a new sample is ready */
+bool ay38910_tick(ay38910_t* ay);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+/*-- IMPLEMENTATION ----------------------------------------------------------*/
+#ifdef CHIPS_IMPL
+#include <string.h>
+#ifndef CHIPS_ASSERT
+ #include <assert.h>
+ #define CHIPS_ASSERT(c) assert(c)
+#endif
+
+/* extract 8-bit data bus from 64-bit pins */
+#define AY38910_DATA(p) ((uint8_t)(p>>16))
+/* merge 8-bit data bus value into 64-bit pins */
+#define AY38910_SET_DATA(p,d) {p=((p&~0xFF0000)|((d&0xFF)<<16));}
+
+/* valid register content bitmasks */
+static const uint8_t _ay38910_reg_mask[AY38910_NUM_REGISTERS] = {
+ 0xFF, /* AY38910_REG_PERIOD_A_FINE */
+ 0x0F, /* AY38910_REG_PERIOD_A_COARSE */
+ 0xFF, /* AY38910_REG_PERIOD_B_FINE */
+ 0x0F, /* AY38910_REG_PERIOD_B_COARSE */
+ 0xFF, /* AY38910_REG_PERIOD_C_FINE */
+ 0x0F, /* AY38910_REG_PERIOD_C_COARSE */
+ 0x1F, /* AY38910_REG_PERIOD_NOISE */
+ 0xFF, /* AY38910_REG_ENABLE */
+ 0x1F, /* AY38910_REG_AMP_A (0..3: 4-bit volume, 4: use envelope) */
+ 0x1F, /* AY38910_REG_AMP_B (0..3: 4-bit volume, 4: use envelope) */
+ 0x1F, /* AY38910_REG_AMP_C (0..3: 4-bit volume, 4: use envelope) */
+ 0xFF, /* AY38910_REG_ENV_PERIOD_FINE */
+ 0xFF, /* AY38910_REG_ENV_PERIOD_COARSE */
+ 0x0F, /* AY38910_REG_ENV_SHAPE_CYCLE */
+ 0xFF, /* AY38910_REG_IO_PORT_A */
+ 0xFF, /* AY38910_REG_IO_PORT_B */
+};
+
+/* volume table from: https://github.com/true-grue/ayumi/blob/master/ayumi.c */
+static const float _ay38910_volumes[16] = {
+ 0.0f,
+ 0.00999465934234f,
+ 0.0144502937362f,
+ 0.0210574502174f,
+ 0.0307011520562f,
+ 0.0455481803616f,
+ 0.0644998855573f,
+ 0.107362478065f,
+ 0.126588845655f,
+ 0.20498970016f,
+ 0.292210269322f,
+ 0.372838941024f,
+ 0.492530708782f,
+ 0.635324635691f,
+ 0.805584802014f,
+ 1.0f
+};
+
+/* canned envelope generator shapes */
+static const uint8_t _ay38910_shapes[16][32] = {
+ /* CONTINUE ATTACK ALTERNATE HOLD */
+ /* 0 0 X X */
+ { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ /* 0 1 X X */
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ /* 1 0 0 0 */
+ { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 },
+ /* 1 0 0 1 */
+ { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+ /* 1 0 1 0 */
+ { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ /* 1 0 1 1 */
+ { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 },
+ /* 1 1 0 0 */
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
+ /* 1 1 0 1 */
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 },
+ /* 1 1 1 0 */
+ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 },
+ /* 1 1 1 1 */
+ { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+};
+
+/* DC adjustment filter from StSound, this moves an "offcenter"
+ signal back to the zero-line (e.g. the volume-level output
+ from the chip simulation which is >0.0 gets converted to
+ a +/- sample value)
+*/
+static float _ay38910_dcadjust(ay38910_t* ay, float s) {
+ ay->dcadj_sum -= ay->dcadj_buf[ay->dcadj_pos];
+ ay->dcadj_sum += s;
+ ay->dcadj_buf[ay->dcadj_pos] = s;
+ ay->dcadj_pos = (ay->dcadj_pos + 1) & (AY38910_DCADJ_BUFLEN-1);
+ return s - (ay->dcadj_sum / AY38910_DCADJ_BUFLEN);
+}
+
+/* update computed values after registers have been reprogrammed */
+static void _ay38910_update_values(ay38910_t* ay) {
+ for (int i = 0; i < AY38910_NUM_CHANNELS; i++) {
+ ay38910_tone_t* chn = &ay->tone[i];
+ /* "...Note also that due to the design technique used in the Tone Period
+ count-down, the lowest period value is 000000000001 (divide by 1)
+ and the highest period value is 111111111111 (divide by 4095)
+ */
+ chn->period = (ay->reg[2*i+1]<<8)|(ay->reg[2*i]);
+ if (0 == chn->period) {
+ chn->period = 1;
+ }
+ /* a set 'enable bit' actually means 'disabled' */
+ chn->tone_disable = (ay->enable>>i) & 1;
+ chn->noise_disable = (ay->enable>>(3+i)) & 1;
+ }
+ /* noise generator values */
+ ay->noise.period = ay->period_noise;
+ if (ay->noise.period == 0) {
+ ay->noise.period = 1;
+ }
+ /* envelope generator values */
+ ay->env.period = (ay->period_env_coarse<<8)|ay->period_env_fine;
+ if (ay->env.period == 0) {
+ ay->env.period = 1;
+ }
+}
+
+/* reset the env shape generator, only called when env-shape register is updated */
+static void _ay38910_restart_env_shape(ay38910_t* ay) {
+ ay->env.shape_holding = false;
+ ay->env.shape_counter = 0;
+ if (!(ay->env_shape_cycle & AY38910_ENV_CONTINUE) || (ay->env_shape_cycle & AY38910_ENV_HOLD)) {
+ ay->env.shape_hold = true;
+ }
+ else {
+ ay->env.shape_hold = false;
+ }
+}
+
+void ay38910_init(ay38910_t* ay, const ay38910_desc_t* desc) {
+ CHIPS_ASSERT(ay && desc);
+ CHIPS_ASSERT(desc->tick_hz > 0);
+ CHIPS_ASSERT(desc->sound_hz > 0);
+ memset(ay, 0, sizeof(*ay));
+ /* note: input and output callbacks are optional */
+ ay->in_cb = desc->in_cb;
+ ay->out_cb = desc->out_cb;
+ ay->user_data = desc->user_data;
+ ay->type = desc->type;
+ ay->noise.rng = 1;
+ ay->sample_period = (desc->tick_hz * AY38910_FIXEDPOINT_SCALE) / desc->sound_hz;
+ ay->sample_counter = ay->sample_period;
+ ay->mag = desc->magnitude;
+ _ay38910_update_values(ay);
+ _ay38910_restart_env_shape(ay);
+}
+
+void ay38910_reset(ay38910_t* ay) {
+ CHIPS_ASSERT(ay);
+ ay->addr = 0;
+ ay->tick = 0;
+ for (int i = 0; i < AY38910_NUM_REGISTERS; i++) {
+ ay->reg[i] = 0;
+ }
+ _ay38910_update_values(ay);
+ _ay38910_restart_env_shape(ay);
+}
+
+bool ay38910_tick(ay38910_t* ay) {
+ ay->tick++;
+ if ((ay->tick & 7) == 0) {
+ /* tick the tone channels */
+ for (int i = 0; i < AY38910_NUM_CHANNELS; i++) {
+ ay38910_tone_t* chn = &ay->tone[i];
+ if (++chn->counter >= chn->period) {
+ chn->counter = 0;
+ chn->bit ^= 1;
+ }
+ }
+
+ /* tick the noise channel */
+ if (++ay->noise.counter >= ay->noise.period) {
+ ay->noise.counter = 0;
+ ay->noise.bit ^= 1;
+ if (ay->noise.bit) {
+ // random number generator from MAME:
+ // https://github.com/mamedev/mame/blob/master/src/devices/sound/ay8910.cpp
+ // The Random Number Generator of the 8910 is a 17-bit shift
+ // register. The input to the shift register is bit0 XOR bit3
+ // (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips.
+ ay->noise.rng ^= (((ay->noise.rng & 1) ^ ((ay->noise.rng >> 3) & 1)) << 17);
+ ay->noise.rng >>= 1;
+ }
+ }
+ }
+
+ /* tick the envelope generator */
+ if ((ay->tick & 15) == 0) {
+ if (++ay->env.counter >= ay->env.period) {
+ ay->env.counter = 0;
+ if (!ay->env.shape_holding) {
+ ay->env.shape_counter = (ay->env.shape_counter + 1) & 0x1F;
+ if (ay->env.shape_hold && (0x1F == ay->env.shape_counter)) {
+ ay->env.shape_holding = true;
+ }
+ }
+ ay->env.shape_state = _ay38910_shapes[ay->env_shape_cycle][ay->env.shape_counter];
+ }
+ }
+
+ /* generate new sample? */
+ ay->sample_counter -= AY38910_FIXEDPOINT_SCALE;
+ if (ay->sample_counter <= 0) {
+ ay->sample_counter += ay->sample_period;
+ float sm = 0.0f;
+ for (int i = 0; i < AY38910_NUM_CHANNELS; i++) {
+ const ay38910_tone_t* chn = &ay->tone[i];
+ float vol;
+ if (0 == (ay->reg[AY38910_REG_AMP_A+i] & (1<<4))) {
+ /* fixed amplitude */
+ vol = _ay38910_volumes[ay->reg[AY38910_REG_AMP_A+i] & 0x0F];
+ }
+ else {
+ /* envelope control */
+ vol = _ay38910_volumes[ay->env.shape_state];
+ }
+ int vol_enable = (chn->bit|chn->tone_disable) & ((ay->noise.rng&1)|(chn->noise_disable));
+ if (vol_enable) {
+ sm += vol;
+ }
+ }
+ ay->sample = _ay38910_dcadjust(ay, sm) * ay->mag;
+ return true; /* new sample is ready */
+ }
+ /* fallthrough: no new sample ready yet */
+ return false;
+}
+
+uint64_t ay38910_iorq(ay38910_t* ay, uint64_t pins) {
+ if (pins & (AY38910_BDIR|AY38910_BC1)) {
+ if (pins & AY38910_BDIR) {
+ const uint8_t data = AY38910_DATA(pins);
+ if (pins & AY38910_BC1) {
+ /* latch address */
+ ay->addr = data;
+ }
+ else {
+ /* Write to register using the currently latched address.
+ The whole 8-bit address is considered, the low 4 bits
+ are the register index, and the upper bits are burned
+ into the chip as a 'chip select' and are usually 0
+ (this emulator assumes they are 0, so addresses greater
+ are ignored for reading and writing)
+ */
+ if (ay->addr < AY38910_NUM_REGISTERS) {
+ /* write register content, and update dependent values */
+ ay->reg[ay->addr] = data & _ay38910_reg_mask[ay->addr];
+ _ay38910_update_values(ay);
+ if (ay->addr == AY38910_REG_ENV_SHAPE_CYCLE) {
+ _ay38910_restart_env_shape(ay);
+ }
+ /* Handle port output:
+
+ If port A or B is in output mode, call the
+ port output callback to notify the outer world
+ about the new register value.
+
+ input/output mode is defined by bits 6 and 7 of
+ the 'enable' register
+ bit6 = 1: port A in output mode
+ bit7 = 1: port B in output mode
+ */
+ else if (ay->addr == AY38910_REG_IO_PORT_A) {
+ if (ay->enable & (1<<6)) {
+ if (ay->out_cb) {
+ ay->out_cb(AY38910_PORT_A, ay->port_a, ay->user_data);
+ }
+ }
+ }
+ else if (ay->addr == AY38910_REG_IO_PORT_B) {
+ if (ay->enable & (1<<7)) {
+ if (ay->out_cb) {
+ ay->out_cb(AY38910_PORT_B, ay->port_b, ay->user_data);
+ }
+ }
+ }
+ }
+ }
+ }
+ else {
+ /* Read from register using the currently latched address.
+ See 'write' for why the latched address must be in the
+ valid register range to have an effect.
+ */
+ if (ay->addr < AY38910_NUM_REGISTERS) {
+ /* Handle port input:
+
+ If port A or B is in input mode, first call the port
+ input callback to update the port register content.
+
+ input/output mode is defined by bits 6 and 7 of
+ the 'enable' register:
+ bit6 = 0: port A in input mode
+ bit7 = 0: port B in input mode
+ */
+ if (ay->addr == AY38910_REG_IO_PORT_A) {
+ if ((ay->enable & (1<<6)) == 0) {
+ if (ay->in_cb) {
+ ay->port_a = ay->in_cb(AY38910_PORT_A, ay->user_data);
+ }
+ else {
+ ay->port_a = 0xFF;
+ }
+ }
+ }
+ else if (ay->addr == AY38910_REG_IO_PORT_B) {
+ if ((ay->enable & (1<<7)) == 0) {
+ if (ay->in_cb) {
+ ay->port_b = ay->in_cb(AY38910_PORT_B, ay->user_data);
+ }
+ else {
+ ay->port_b = 0xFF;
+ }
+ }
+ }
+ /* read register content into data pins */
+ const uint8_t data = ay->reg[ay->addr];
+ AY38910_SET_DATA(pins, data);
+ }
+ }
+ AY38910_SET_PA(pins, ay->port_a);
+ AY38910_SET_PB(pins, ay->port_b);
+ ay->pins = pins;
+ }
+ return pins;
+}
+
+#endif /* CHIPS_IMPL */
--- /dev/null
+++ b/ay/mkfile
@@ -1,0 +1,20 @@
+</$objtype/mkfile
+
+TARG=ay
+CFLAGS=$CFLAGS -p -I..
+BIN=/$objtype/bin/daw
+
+OFILES=\
+ ay.$O\
+ common.$O\
+ uiglue.$O\
+
+default:V: all
+
+</sys/src/cmd/mkone
+
+common.$O: ../common.c
+ $CC $CFLAGS $prereq
+
+uiglue.$O: ../uiglue.c
+ $CC $CFLAGS $prereq
--- /dev/null
+++ b/ay/stdbool.h
@@ -1,0 +1,1 @@
+typedef enum { false, true } bool;
--- /dev/null
+++ b/ay/stdint.h
@@ -1,0 +1,4 @@
+typedef u8int uint8_t;
+typedef u16int uint16_t;
+typedef u32int uint32_t;
+typedef u64int uint64_t;
--- a/ay38910/README.md
+++ /dev/null
@@ -1,12 +1,0 @@
-# ay38910
-
-AY-3-8910 as a filesystem, part of neindaw. WIP.
-
-This is a prototype built to achieve the following:
-
- * figure out ways to deal with multi-voice/polyphony and mixing
- * create a UDP-to-neindaw translation layer, a companion
- app that allows using neindaw with [Orca](https://github.com/hundredrabbits/Orca).
- * visualizing waveforms
-
-AY-3-8910 is emulated using [floooh/chips](https://github.com/floooh/chips).
--- a/ay38910/ay38910.c
+++ /dev/null
@@ -1,452 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <fcall.h>
-#include <thread.h>
-#include <9p.h>
-#include "common.h"
-#include "aux.h"
-#define CHIPS_IMPL
-#define CHIPS_ASSERT assert
-#include "ay38910.h"
-#include "uiglue.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 {
- Tickhz = 1000000,
-
- 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;
-};
-
-static Aux rootaux[] = {
- [Xctl] = {.type = Xctl},
- [Xmetadata] = {.type = Xmetadata},
- [Xclone] = {.type = Xclone},
-};
-
-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
-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
-tone(ay38910_t *ay, int chan, int hz)
-{
- int tp;
-
- tp = hz2tp(hz);
- regw(ay, chan*2+0, tp & 0xff); /* fine */
- regw(ay, chan*2+1, (tp>>8) & 0x0f); /* coarse */
-}
-
-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 void
-buildui(Auxdsp *dsp, UIGlue *ui)
-{
- float min, step, max;
- char s[32];
- int i;
-
- min = ceil(Tickhz/(16.0f*4095.0f));
- max = floor(Tickhz/16.0f);
- step = ceil(Tickhz/(16.0f*4094.0f)) - min;
-
- 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->addHorizontalSlider(ui->f, "Tone", &dsp->chan[i].freq, 400.0f, min, max, step);
-
- ui->declare(ui->f, &dsp->chan[i].amp, "1", "");
- ui->addHorizontalSlider(ui->f, "Volume", &dsp->chan[i].amp, 15.0f, 0.0f, 15.0f, 1.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);
-
- 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]\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;
- default:
- usage();
- }ARGEND
-
- 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);
-}
--- a/ay38910/ay38910.h
+++ /dev/null
@@ -1,598 +1,0 @@
-#pragma once
-/*
- ay38910.h -- AY-3-8910/2/3 sound chip emulator
-
- Do this:
- #define CHIPS_IMPL
- before you include this file in *one* C or C++ file to create the
- implementation.
-
- Optionally provde the following macros with your own implementation
-
- CHIPS_ASSERT(c) -- your own assert macro (default: assert(c))
-
- EMULATED PINS:
-
- +-----------+
- BC1 -->| |<-> DA0
- BDIR -->| |...
- | |<-> DA7
- | |
- | |<-> (IOA0)
- | |...
- | |<-> (IOA7)
- | |
- | |<-> (IOB0)
- | |...
- | |<-> (IOB7)
- +-----------+
-
- NOT EMULATED:
-
- - the BC2 pin is ignored since it makes only sense when connected to
- a CP1610 CPU
- - the RESET pin state is ignored, instead call ay38910_reset()
-
- ## zlib/libpng license
-
- Copyright (c) 2018 Andre Weissflog
- This software is provided 'as-is', without any express or implied warranty.
- In no event will the authors be held liable for any damages arising from the
- use of this software.
- Permission is granted to anyone to use this software for any purpose,
- including commercial applications, and to alter it and redistribute it
- freely, subject to the following restrictions:
- 1. The origin of this software must not be misrepresented; you must not
- claim that you wrote the original software. If you use this software in a
- product, an acknowledgment in the product documentation would be
- appreciated but is not required.
- 2. Altered source versions must be plainly marked as such, and must not
- be misrepresented as being the original software.
- 3. This notice may not be removed or altered from any source
- distribution.
-*/
-#include <stdint.h>
-#include <stdbool.h>
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/*
- Pin definitions.
-
- Note that the BC2 is not emulated since it is usually always
- set to active when not connected to a CP1610 processor. The
- remaining BDIR and BC1 pins are interpreted as follows:
-
- |BDIR|BC1|
- +----+---+
- | 0 | 0 | INACTIVE
- | 0 | 1 | READ FROM PSG
- | 1 | 0 | WRITE TO PSG
- | 1 | 1 | LATCH ADDRESS
-*/
-
-/* 8 bits data/address bus shared with CPU data bus */
-#define AY38910_DA0 (1ULL<<16)
-#define AY38910_DA1 (1ULL<<17)
-#define AY38910_DA2 (1ULL<<18)
-#define AY38910_DA3 (1ULL<<19)
-#define AY38910_DA4 (1ULL<<20)
-#define AY38910_DA5 (1ULL<<21)
-#define AY38910_DA6 (1ULL<<22)
-#define AY38910_DA7 (1ULL<<23)
-
-/* reset pin shared with CPU */
-#define AY38910_RESET (1ULL<<34)
-
-/* chip-specific pins start at position 44 */
-#define AY38910_BDIR (1ULL<<40)
-#define AY38910_BC1 (1ULL<<41)
-
-/* IO port pins */
-#define AY38910_IOA0 (1ULL<<48)
-#define AY38910_IOA1 (1ULL<<49)
-#define AY38910_IOA2 (1ULL<<50)
-#define AY38910_IOA3 (1ULL<<51)
-#define AY38910_IOA4 (1ULL<<52)
-#define AY38910_IOA5 (1ULL<<53)
-#define AY38910_IOA6 (1ULL<<54)
-#define AY38910_IOA7 (1ULL<<55)
-
-#define AY38910_IOB0 (1ULL<<56)
-#define AY38910_IOB1 (1ULL<<57)
-#define AY38910_IOB2 (1ULL<<58)
-#define AY38910_IOB3 (1ULL<<59)
-#define AY38910_IOB4 (1ULL<<60)
-#define AY38910_IOB5 (1ULL<<61)
-#define AY38910_IOB6 (1ULL<<62)
-#define AY38910_IOB7 (1ULL<<63)
-
-/* AY-3-8910 registers */
-#define AY38910_REG_PERIOD_A_FINE (0)
-#define AY38910_REG_PERIOD_A_COARSE (1)
-#define AY38910_REG_PERIOD_B_FINE (2)
-#define AY38910_REG_PERIOD_B_COARSE (3)
-#define AY38910_REG_PERIOD_C_FINE (4)
-#define AY38910_REG_PERIOD_C_COARSE (5)
-#define AY38910_REG_PERIOD_NOISE (6)
-#define AY38910_REG_ENABLE (7)
-#define AY38910_REG_AMP_A (8)
-#define AY38910_REG_AMP_B (9)
-#define AY38910_REG_AMP_C (10)
-#define AY38910_REG_ENV_PERIOD_FINE (11)
-#define AY38910_REG_ENV_PERIOD_COARSE (12)
-#define AY38910_REG_ENV_SHAPE_CYCLE (13)
-#define AY38910_REG_IO_PORT_A (14) /* not on AY-3-8913 */
-#define AY38910_REG_IO_PORT_B (15) /* not on AY-3-8912/3 */
-/* number of registers */
-#define AY38910_NUM_REGISTERS (16)
-/* error-accumulation precision boost */
-#define AY38910_FIXEDPOINT_SCALE (16)
-/* number of channels */
-#define AY38910_NUM_CHANNELS (3)
-/* DC adjustment buffer length */
-#define AY38910_DCADJ_BUFLEN (512)
-
-/* IO port names */
-#define AY38910_PORT_A (0)
-#define AY38910_PORT_B (1)
-
-/* envelope shape bits */
-#define AY38910_ENV_HOLD (1<<0)
-#define AY38910_ENV_ALTERNATE (1<<1)
-#define AY38910_ENV_ATTACK (1<<2)
-#define AY38910_ENV_CONTINUE (1<<3)
-
-/* callbacks for input/output on I/O ports */
-typedef uint8_t (*ay38910_in_t)(int port_id, void* user_data);
-typedef void (*ay38910_out_t)(int port_id, uint8_t data, void* user_data);
-
-/* chip subtypes */
-typedef enum {
- AY38910_TYPE_8910 = 0,
- AY38910_TYPE_8912,
- AY38910_TYPE_8913
-} ay38910_type_t;
-
-/* setup parameters for ay38910_init() call */
-typedef struct {
- ay38910_type_t type; /* the subtype (default 0 is AY-3-8910) */
- int tick_hz; /* frequency at which ay38910_tick() will be called in Hz */
- int sound_hz; /* number of samples that will be produced per second */
- float magnitude; /* output sample magnitude, from 0.0 (silence) to 1.0 (max volume) */
- ay38910_in_t in_cb; /* I/O port input callback */
- ay38910_out_t out_cb; /* I/O port output callback */
- void* user_data; /* optional user-data for callbacks */
-} ay38910_desc_t;
-
-/* a tone channel */
-typedef struct {
- uint16_t period;
- uint16_t counter;
- uint32_t bit;
- uint32_t tone_disable;
- uint32_t noise_disable;
-} ay38910_tone_t;
-
-/* the noise channel state */
-typedef struct {
- uint16_t period;
- uint16_t counter;
- uint32_t rng;
- uint32_t bit;
-} ay38910_noise_t;
-
-/* the envelope generator */
-typedef struct {
- uint16_t period;
- uint16_t counter;
- bool shape_holding;
- bool shape_hold;
- uint8_t shape_counter;
- uint8_t shape_state;
-} ay38910_env_t;
-
-/* AY-3-8910 state */
-typedef struct {
- ay38910_type_t type; /* the chip flavour */
- ay38910_in_t in_cb; /* the port-input callback */
- ay38910_out_t out_cb; /* the port-output callback */
- void* user_data; /* optional user-data for callbacks */
- uint32_t tick; /* a tick counter for internal clock division */
- uint8_t addr; /* 4-bit address latch */
- union { /* the register bank */
- uint8_t reg[AY38910_NUM_REGISTERS];
- struct {
- uint8_t period_a_fine;
- uint8_t period_a_coarse;
- uint8_t period_b_fine;
- uint8_t period_b_coarse;
- uint8_t period_c_fine;
- uint8_t period_c_coarse;
- uint8_t period_noise;
- uint8_t enable;
- uint8_t amp_a;
- uint8_t amp_b;
- uint8_t amp_c;
- uint8_t period_env_fine;
- uint8_t period_env_coarse;
- uint8_t env_shape_cycle;
- uint8_t port_a;
- uint8_t port_b;
- };
- };
- ay38910_tone_t tone[AY38910_NUM_CHANNELS]; /* the 3 tone channels */
- ay38910_noise_t noise; /* the noise generator state */
- ay38910_env_t env; /* the envelope generator state */
- uint64_t pins; /* last pin state for debug inspection */
-
- /* sample generation state */
- int sample_period;
- int sample_counter;
- float mag;
- float sample;
- float dcadj_sum;
- uint32_t dcadj_pos;
- float dcadj_buf[AY38910_DCADJ_BUFLEN];
-} ay38910_t;
-
-/* extract 8-bit data bus from 64-bit pins */
-#define AY38910_GET_DATA(p) ((uint8_t)(p>>16))
-/* merge 8-bit data bus value into 64-bit pins */
-#define AY38910_SET_DATA(p,d) {p=((p&~0xFF0000)|((d&0xFF)<<16));}
-/* set 8-bit port A data on 64-bit pin mask */
-#define AY38910_SET_PA(p,d) {p=((p&~0x00FF000000000000ULL)|((((uint64_t)d)&0xFFULL)<<48));}
-/* set 8-bit port B data on 64-bit pin mask */
-#define AY38910_SET_PB(p,d) {p=((p&~0xFF00000000000000ULL)|((((uint64_t)d)&0xFFULL)<<56));}
-
-/* initialize a AY-3-8910 instance */
-void ay38910_init(ay38910_t* ay, const ay38910_desc_t* desc);
-/* reset an existing AY-3-8910 instance */
-void ay38910_reset(ay38910_t* ay);
-/* perform an IO request machine cycle */
-uint64_t ay38910_iorq(ay38910_t* ay, uint64_t pins);
-/* tick the AY-3-8910, return true if a new sample is ready */
-bool ay38910_tick(ay38910_t* ay);
-
-#ifdef __cplusplus
-} /* extern "C" */
-#endif
-
-/*-- IMPLEMENTATION ----------------------------------------------------------*/
-#ifdef CHIPS_IMPL
-#include <string.h>
-#ifndef CHIPS_ASSERT
- #include <assert.h>
- #define CHIPS_ASSERT(c) assert(c)
-#endif
-
-/* extract 8-bit data bus from 64-bit pins */
-#define AY38910_DATA(p) ((uint8_t)(p>>16))
-/* merge 8-bit data bus value into 64-bit pins */
-#define AY38910_SET_DATA(p,d) {p=((p&~0xFF0000)|((d&0xFF)<<16));}
-
-/* valid register content bitmasks */
-static const uint8_t _ay38910_reg_mask[AY38910_NUM_REGISTERS] = {
- 0xFF, /* AY38910_REG_PERIOD_A_FINE */
- 0x0F, /* AY38910_REG_PERIOD_A_COARSE */
- 0xFF, /* AY38910_REG_PERIOD_B_FINE */
- 0x0F, /* AY38910_REG_PERIOD_B_COARSE */
- 0xFF, /* AY38910_REG_PERIOD_C_FINE */
- 0x0F, /* AY38910_REG_PERIOD_C_COARSE */
- 0x1F, /* AY38910_REG_PERIOD_NOISE */
- 0xFF, /* AY38910_REG_ENABLE */
- 0x1F, /* AY38910_REG_AMP_A (0..3: 4-bit volume, 4: use envelope) */
- 0x1F, /* AY38910_REG_AMP_B (0..3: 4-bit volume, 4: use envelope) */
- 0x1F, /* AY38910_REG_AMP_C (0..3: 4-bit volume, 4: use envelope) */
- 0xFF, /* AY38910_REG_ENV_PERIOD_FINE */
- 0xFF, /* AY38910_REG_ENV_PERIOD_COARSE */
- 0x0F, /* AY38910_REG_ENV_SHAPE_CYCLE */
- 0xFF, /* AY38910_REG_IO_PORT_A */
- 0xFF, /* AY38910_REG_IO_PORT_B */
-};
-
-/* volume table from: https://github.com/true-grue/ayumi/blob/master/ayumi.c */
-static const float _ay38910_volumes[16] = {
- 0.0f,
- 0.00999465934234f,
- 0.0144502937362f,
- 0.0210574502174f,
- 0.0307011520562f,
- 0.0455481803616f,
- 0.0644998855573f,
- 0.107362478065f,
- 0.126588845655f,
- 0.20498970016f,
- 0.292210269322f,
- 0.372838941024f,
- 0.492530708782f,
- 0.635324635691f,
- 0.805584802014f,
- 1.0f
-};
-
-/* canned envelope generator shapes */
-static const uint8_t _ay38910_shapes[16][32] = {
- /* CONTINUE ATTACK ALTERNATE HOLD */
- /* 0 0 X X */
- { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- /* 0 1 X X */
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- /* 1 0 0 0 */
- { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 },
- /* 1 0 0 1 */
- { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
- /* 1 0 1 0 */
- { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
- /* 1 0 1 1 */
- { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 },
- /* 1 1 0 0 */
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 },
- /* 1 1 0 1 */
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 },
- /* 1 1 1 0 */
- { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 },
- /* 1 1 1 1 */
- { 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
-};
-
-/* DC adjustment filter from StSound, this moves an "offcenter"
- signal back to the zero-line (e.g. the volume-level output
- from the chip simulation which is >0.0 gets converted to
- a +/- sample value)
-*/
-static float _ay38910_dcadjust(ay38910_t* ay, float s) {
- ay->dcadj_sum -= ay->dcadj_buf[ay->dcadj_pos];
- ay->dcadj_sum += s;
- ay->dcadj_buf[ay->dcadj_pos] = s;
- ay->dcadj_pos = (ay->dcadj_pos + 1) & (AY38910_DCADJ_BUFLEN-1);
- return s - (ay->dcadj_sum / AY38910_DCADJ_BUFLEN);
-}
-
-/* update computed values after registers have been reprogrammed */
-static void _ay38910_update_values(ay38910_t* ay) {
- for (int i = 0; i < AY38910_NUM_CHANNELS; i++) {
- ay38910_tone_t* chn = &ay->tone[i];
- /* "...Note also that due to the design technique used in the Tone Period
- count-down, the lowest period value is 000000000001 (divide by 1)
- and the highest period value is 111111111111 (divide by 4095)
- */
- chn->period = (ay->reg[2*i+1]<<8)|(ay->reg[2*i]);
- if (0 == chn->period) {
- chn->period = 1;
- }
- /* a set 'enable bit' actually means 'disabled' */
- chn->tone_disable = (ay->enable>>i) & 1;
- chn->noise_disable = (ay->enable>>(3+i)) & 1;
- }
- /* noise generator values */
- ay->noise.period = ay->period_noise;
- if (ay->noise.period == 0) {
- ay->noise.period = 1;
- }
- /* envelope generator values */
- ay->env.period = (ay->period_env_coarse<<8)|ay->period_env_fine;
- if (ay->env.period == 0) {
- ay->env.period = 1;
- }
-}
-
-/* reset the env shape generator, only called when env-shape register is updated */
-static void _ay38910_restart_env_shape(ay38910_t* ay) {
- ay->env.shape_holding = false;
- ay->env.shape_counter = 0;
- if (!(ay->env_shape_cycle & AY38910_ENV_CONTINUE) || (ay->env_shape_cycle & AY38910_ENV_HOLD)) {
- ay->env.shape_hold = true;
- }
- else {
- ay->env.shape_hold = false;
- }
-}
-
-void ay38910_init(ay38910_t* ay, const ay38910_desc_t* desc) {
- CHIPS_ASSERT(ay && desc);
- CHIPS_ASSERT(desc->tick_hz > 0);
- CHIPS_ASSERT(desc->sound_hz > 0);
- memset(ay, 0, sizeof(*ay));
- /* note: input and output callbacks are optional */
- ay->in_cb = desc->in_cb;
- ay->out_cb = desc->out_cb;
- ay->user_data = desc->user_data;
- ay->type = desc->type;
- ay->noise.rng = 1;
- ay->sample_period = (desc->tick_hz * AY38910_FIXEDPOINT_SCALE) / desc->sound_hz;
- ay->sample_counter = ay->sample_period;
- ay->mag = desc->magnitude;
- _ay38910_update_values(ay);
- _ay38910_restart_env_shape(ay);
-}
-
-void ay38910_reset(ay38910_t* ay) {
- CHIPS_ASSERT(ay);
- ay->addr = 0;
- ay->tick = 0;
- for (int i = 0; i < AY38910_NUM_REGISTERS; i++) {
- ay->reg[i] = 0;
- }
- _ay38910_update_values(ay);
- _ay38910_restart_env_shape(ay);
-}
-
-bool ay38910_tick(ay38910_t* ay) {
- ay->tick++;
- if ((ay->tick & 7) == 0) {
- /* tick the tone channels */
- for (int i = 0; i < AY38910_NUM_CHANNELS; i++) {
- ay38910_tone_t* chn = &ay->tone[i];
- if (++chn->counter >= chn->period) {
- chn->counter = 0;
- chn->bit ^= 1;
- }
- }
-
- /* tick the noise channel */
- if (++ay->noise.counter >= ay->noise.period) {
- ay->noise.counter = 0;
- ay->noise.bit ^= 1;
- if (ay->noise.bit) {
- // random number generator from MAME:
- // https://github.com/mamedev/mame/blob/master/src/devices/sound/ay8910.cpp
- // The Random Number Generator of the 8910 is a 17-bit shift
- // register. The input to the shift register is bit0 XOR bit3
- // (bit0 is the output). This was verified on AY-3-8910 and YM2149 chips.
- ay->noise.rng ^= (((ay->noise.rng & 1) ^ ((ay->noise.rng >> 3) & 1)) << 17);
- ay->noise.rng >>= 1;
- }
- }
- }
-
- /* tick the envelope generator */
- if ((ay->tick & 15) == 0) {
- if (++ay->env.counter >= ay->env.period) {
- ay->env.counter = 0;
- if (!ay->env.shape_holding) {
- ay->env.shape_counter = (ay->env.shape_counter + 1) & 0x1F;
- if (ay->env.shape_hold && (0x1F == ay->env.shape_counter)) {
- ay->env.shape_holding = true;
- }
- }
- ay->env.shape_state = _ay38910_shapes[ay->env_shape_cycle][ay->env.shape_counter];
- }
- }
-
- /* generate new sample? */
- ay->sample_counter -= AY38910_FIXEDPOINT_SCALE;
- if (ay->sample_counter <= 0) {
- ay->sample_counter += ay->sample_period;
- float sm = 0.0f;
- for (int i = 0; i < AY38910_NUM_CHANNELS; i++) {
- const ay38910_tone_t* chn = &ay->tone[i];
- float vol;
- if (0 == (ay->reg[AY38910_REG_AMP_A+i] & (1<<4))) {
- /* fixed amplitude */
- vol = _ay38910_volumes[ay->reg[AY38910_REG_AMP_A+i] & 0x0F];
- }
- else {
- /* envelope control */
- vol = _ay38910_volumes[ay->env.shape_state];
- }
- int vol_enable = (chn->bit|chn->tone_disable) & ((ay->noise.rng&1)|(chn->noise_disable));
- if (vol_enable) {
- sm += vol;
- }
- }
- ay->sample = _ay38910_dcadjust(ay, sm) * ay->mag;
- return true; /* new sample is ready */
- }
- /* fallthrough: no new sample ready yet */
- return false;
-}
-
-uint64_t ay38910_iorq(ay38910_t* ay, uint64_t pins) {
- if (pins & (AY38910_BDIR|AY38910_BC1)) {
- if (pins & AY38910_BDIR) {
- const uint8_t data = AY38910_DATA(pins);
- if (pins & AY38910_BC1) {
- /* latch address */
- ay->addr = data;
- }
- else {
- /* Write to register using the currently latched address.
- The whole 8-bit address is considered, the low 4 bits
- are the register index, and the upper bits are burned
- into the chip as a 'chip select' and are usually 0
- (this emulator assumes they are 0, so addresses greater
- are ignored for reading and writing)
- */
- if (ay->addr < AY38910_NUM_REGISTERS) {
- /* write register content, and update dependent values */
- ay->reg[ay->addr] = data & _ay38910_reg_mask[ay->addr];
- _ay38910_update_values(ay);
- if (ay->addr == AY38910_REG_ENV_SHAPE_CYCLE) {
- _ay38910_restart_env_shape(ay);
- }
- /* Handle port output:
-
- If port A or B is in output mode, call the
- port output callback to notify the outer world
- about the new register value.
-
- input/output mode is defined by bits 6 and 7 of
- the 'enable' register
- bit6 = 1: port A in output mode
- bit7 = 1: port B in output mode
- */
- else if (ay->addr == AY38910_REG_IO_PORT_A) {
- if (ay->enable & (1<<6)) {
- if (ay->out_cb) {
- ay->out_cb(AY38910_PORT_A, ay->port_a, ay->user_data);
- }
- }
- }
- else if (ay->addr == AY38910_REG_IO_PORT_B) {
- if (ay->enable & (1<<7)) {
- if (ay->out_cb) {
- ay->out_cb(AY38910_PORT_B, ay->port_b, ay->user_data);
- }
- }
- }
- }
- }
- }
- else {
- /* Read from register using the currently latched address.
- See 'write' for why the latched address must be in the
- valid register range to have an effect.
- */
- if (ay->addr < AY38910_NUM_REGISTERS) {
- /* Handle port input:
-
- If port A or B is in input mode, first call the port
- input callback to update the port register content.
-
- input/output mode is defined by bits 6 and 7 of
- the 'enable' register:
- bit6 = 0: port A in input mode
- bit7 = 0: port B in input mode
- */
- if (ay->addr == AY38910_REG_IO_PORT_A) {
- if ((ay->enable & (1<<6)) == 0) {
- if (ay->in_cb) {
- ay->port_a = ay->in_cb(AY38910_PORT_A, ay->user_data);
- }
- else {
- ay->port_a = 0xFF;
- }
- }
- }
- else if (ay->addr == AY38910_REG_IO_PORT_B) {
- if ((ay->enable & (1<<7)) == 0) {
- if (ay->in_cb) {
- ay->port_b = ay->in_cb(AY38910_PORT_B, ay->user_data);
- }
- else {
- ay->port_b = 0xFF;
- }
- }
- }
- /* read register content into data pins */
- const uint8_t data = ay->reg[ay->addr];
- AY38910_SET_DATA(pins, data);
- }
- }
- AY38910_SET_PA(pins, ay->port_a);
- AY38910_SET_PB(pins, ay->port_b);
- ay->pins = pins;
- }
- return pins;
-}
-
-#endif /* CHIPS_IMPL */
--- a/ay38910/mkfile
+++ /dev/null
@@ -1,20 +1,0 @@
-</$objtype/mkfile
-
-TARG=ay38910
-CFLAGS=$CFLAGS -p -I..
-BIN=/$objtype/bin/daw
-
-OFILES=\
- ay38910.$O\
- common.$O\
- uiglue.$O\
-
-default:V: all
-
-</sys/src/cmd/mkone
-
-common.$O: ../common.c
- $CC $CFLAGS $prereq
-
-uiglue.$O: ../uiglue.c
- $CC $CFLAGS $prereq
--- a/ay38910/stdbool.h
+++ /dev/null
@@ -1,1 +1,0 @@
-typedef enum { false, true } bool;
--- a/ay38910/stdint.h
+++ /dev/null
@@ -1,4 +1,0 @@
-typedef u8int uint8_t;
-typedef u16int uint16_t;
-typedef u32int uint32_t;
-typedef u64int uint64_t;
--- a/mkfile
+++ b/mkfile
@@ -4,7 +4,7 @@
microui\
CMDS=\
- ay38910\
+ ay\
cfg\
dsp\
repeat\