From 6fd10cdacb5cbe47a4fc339c20a733d4a9a384a1 Mon Sep 17 00:00:00 2001 From: Takamichi Horikawa Date: Sat, 26 Nov 2016 20:57:57 +0900 Subject: initial --- libopna/opna.c | 24 +++ libopna/opna.h | 30 +++ libopna/opnaadpcm.c | 208 +++++++++++++++++++++ libopna/opnaadpcm.h | 43 +++++ libopna/opnadrum.c | 141 ++++++++++++++ libopna/opnadrum.h | 57 ++++++ libopna/opnafm.c | 511 +++++++++++++++++++++++++++++++++++++++++++++++++++ libopna/opnafm.h | 111 +++++++++++ libopna/opnassg.c | 233 +++++++++++++++++++++++ libopna/opnassg.h | 60 ++++++ libopna/opnatables.h | 242 ++++++++++++++++++++++++ libopna/opnatimer.c | 79 ++++++++ libopna/opnatimer.h | 42 +++++ libopna/s98gen.c | 141 ++++++++++++++ libopna/s98gen.h | 34 ++++ 15 files changed, 1956 insertions(+) create mode 100644 libopna/opna.c create mode 100644 libopna/opna.h create mode 100644 libopna/opnaadpcm.c create mode 100644 libopna/opnaadpcm.h create mode 100644 libopna/opnadrum.c create mode 100644 libopna/opnadrum.h create mode 100644 libopna/opnafm.c create mode 100644 libopna/opnafm.h create mode 100644 libopna/opnassg.c create mode 100644 libopna/opnassg.h create mode 100644 libopna/opnatables.h create mode 100644 libopna/opnatimer.c create mode 100644 libopna/opnatimer.h create mode 100644 libopna/s98gen.c create mode 100644 libopna/s98gen.h (limited to 'libopna') diff --git a/libopna/opna.c b/libopna/opna.c new file mode 100644 index 0000000..4e198a0 --- /dev/null +++ b/libopna/opna.c @@ -0,0 +1,24 @@ +#include "opna.h" + +void opna_reset(struct opna *opna) { + opna_fm_reset(&opna->fm); + opna_ssg_reset(&opna->ssg); + opna_ssg_resampler_reset(&opna->resampler); + opna_drum_reset(&opna->drum); + opna_adpcm_reset(&opna->adpcm); +} + +void opna_writereg(struct opna *opna, unsigned reg, unsigned val) { + val &= 0xff; + opna_fm_writereg(&opna->fm, reg, val); + opna_ssg_writereg(&opna->ssg, reg, val); + opna_drum_writereg(&opna->drum, reg, val); + opna_adpcm_writereg(&opna->adpcm, reg, val); +} + +void opna_mix(struct opna *opna, int16_t *buf, unsigned samples) { + opna_fm_mix(&opna->fm, buf, samples); + opna_ssg_mix_55466(&opna->ssg, &opna->resampler, buf, samples); + opna_drum_mix(&opna->drum, buf, samples); + opna_adpcm_mix(&opna->adpcm, buf, samples); +} diff --git a/libopna/opna.h b/libopna/opna.h new file mode 100644 index 0000000..a056156 --- /dev/null +++ b/libopna/opna.h @@ -0,0 +1,30 @@ +#ifndef LIBOPNA_OPNA_H_INCLUDED +#define LIBOPNA_OPNA_H_INCLUDED + +#include "opnafm.h" +#include "opnassg.h" +#include "opnadrum.h" +#include "opnaadpcm.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct opna { + struct opna_fm fm; + struct opna_ssg ssg; + struct opna_drum drum; + struct opna_adpcm adpcm; + struct opna_ssg_resampler resampler; + +}; + +void opna_reset(struct opna *opna); +void opna_writereg(struct opna *opna, unsigned reg, unsigned val); +void opna_mix(struct opna *opna, int16_t *buf, unsigned samples); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNA_H_INCLUDED diff --git a/libopna/opnaadpcm.c b/libopna/opnaadpcm.c new file mode 100644 index 0000000..1157b56 --- /dev/null +++ b/libopna/opnaadpcm.c @@ -0,0 +1,208 @@ +#include "opnaadpcm.h" + +enum { + C1_START = 0x80, + C1_REC = 0x40, + C1_MEMEXT = 0x20, + C1_REPEAT = 0x10, + C1_RESET = 0x01, + C1_MASK = 0xf9, +}; + +enum { + C2_L = 0x80, + C2_R = 0x40, + C2_8BIT = 0x02, + C2_MASK = 0xcf, +}; + +static const uint8_t adpcm_table[8] = { + 57, 57, 57, 57, 77, 102, 128, 153, +}; + +void opna_adpcm_reset(struct opna_adpcm *adpcm) { + adpcm->control1 = 0; + adpcm->control2 = 0; + adpcm->vol = 0; + adpcm->delta = 0; + adpcm->start = 0; + adpcm->end = 0; + adpcm->limit = 0; + adpcm->ramptr = 0; + adpcm->step = 0; + adpcm->ram = 0; + adpcm->acc = 0; + adpcm->prev_acc = 0; + adpcm->adpcmd = 127; + adpcm->out = 0; +} + +static uint32_t addr_conv(const struct opna_adpcm *adpcm, uint16_t a) { + uint32_t a32 = a; + return (adpcm->control2 & C2_8BIT) ? (a32<<6) : (a32<<3); +} +static uint32_t addr_conv_e(const struct opna_adpcm *adpcm, uint16_t a) { + uint32_t a32 = a+1; + uint32_t ret = (adpcm->control2 & C2_8BIT) ? (a32<<6) : (a32<<3); + return ret-1; +} + +static void adpcm_calc(struct opna_adpcm *adpcm) { + uint32_t step = (uint32_t)adpcm->step + (uint32_t)adpcm->delta; + adpcm->step = step & 0xffff; + if (step >> 16) { + if (adpcm->ramptr == addr_conv(adpcm, adpcm->limit)) { + adpcm->ramptr = 0; + } + if (adpcm->ramptr == addr_conv_e(adpcm, adpcm->end)) { + if (adpcm->control1 & C1_REPEAT) { + adpcm->ramptr = addr_conv(adpcm, adpcm->start); + adpcm->acc = 0; + adpcm->adpcmd = 127; + adpcm->prev_acc = 0; + } else { + // TODO: set EOS + adpcm->control1 = 0; + adpcm->out = 0; + adpcm->prev_acc = 0; + } + } + uint8_t data = 0; + if (adpcm->ram) { + data = adpcm->ram[(adpcm->ramptr>>1)&(OPNA_ADPCM_RAM_SIZE-1)]; + } + if (adpcm->ramptr&1) { + data &= 0x0f; + } else { + data >>= 4; + } + adpcm->ramptr++; + adpcm->ramptr &= (1<<(24+1))-1; + + adpcm->prev_acc = adpcm->acc; + int32_t acc_d = (((data&7)<<1)|1); + if (data&8) acc_d = -acc_d; + int32_t acc = adpcm->acc + (acc_d * adpcm->adpcmd / 8); + if (acc < -32768) acc = -32768; + if (acc > 32767) acc = 32767; + adpcm->acc = acc; + + uint32_t adpcmd = (adpcm->adpcmd * adpcm_table[data&7] / 64); + if (adpcmd < 127) adpcmd = 127; + if (adpcmd > 24576) adpcmd = 24576; + adpcm->adpcmd = adpcmd; + } + int32_t out = (int32_t)adpcm->prev_acc * (0x10000-adpcm->step); + out += (int32_t)adpcm->acc * adpcm->step; + out >>= 16; + out *= adpcm->vol; + out >>= 8; + if (out < -32768) out = -32768; + if (out > 32767) out = 32767; + adpcm->out = out; +} + +void opna_adpcm_writereg(struct opna_adpcm *adpcm, unsigned reg, unsigned val) { + val &= 0xff; + if (reg < 0x100) return; + if (reg >= 0x111) return; + reg &= 0xff; + switch (reg) { + case 0x00: + adpcm->control1 = val & C1_MASK; + if (adpcm->control1 & C1_START) { + adpcm->step = 0; + adpcm->acc = 0; + adpcm->prev_acc = 0; + adpcm->out = 0; + adpcm->adpcmd = 127; + } + if (adpcm->control1 & C1_MEMEXT) { + adpcm->ramptr = addr_conv(adpcm, adpcm->start); + } + if (adpcm->control1 & C1_RESET) { + adpcm->control1 = 0; + // TODO: set BRDY + } + break; + case 0x01: + adpcm->control2 = val & C2_MASK; + break; + case 0x02: + adpcm->start &= 0xff00; + adpcm->start |= val; + break; + case 0x03: + adpcm->start &= 0x00ff; + adpcm->start |= (val<<8); + break; + case 0x04: + adpcm->end &= 0xff00; + adpcm->end |= val; + break; + case 0x05: + adpcm->end &= 0x00ff; + adpcm->end |= (val<<8); + break; + case 0x08: + // data write + if ((adpcm->control1 & (C1_START|C1_REC|C1_MEMEXT)) == (C1_REC|C1_MEMEXT)) { + // external memory write + if (adpcm->ramptr != addr_conv_e(adpcm, adpcm->end)) { + if (adpcm->ram) { + adpcm->ram[(adpcm->ramptr>>1)&(OPNA_ADPCM_RAM_SIZE-1)] = val; + } + adpcm->ramptr += 2; + } else { + // TODO: set EOS + } + } + break; + case 0x09: + adpcm->delta &= 0xff00; + adpcm->delta |= val; + break; + case 0x0a: + adpcm->delta &= 0x00ff; + adpcm->delta |= (val<<8); + break; + case 0x0b: + adpcm->vol = val; + break; + case 0x0c: + adpcm->limit &= 0xff00; + adpcm->limit |= val; + break; + case 0x0d: + adpcm->limit &= 0x00ff; + adpcm->limit |= (val<<8); + break; + } +} + +void opna_adpcm_mix(struct opna_adpcm *adpcm, int16_t *buf, unsigned samples) { + if (!adpcm->ram) return; + if (!(adpcm->control1 & C1_START)) return; + for (unsigned i = 0; i < samples; i++) { + adpcm_calc(adpcm); + int32_t lo = buf[i*2+0]; + int32_t ro = buf[i*2+1]; + if (adpcm->control2 & C2_L) lo += (adpcm->out>>1); + if (adpcm->control2 & C2_R) ro += (adpcm->out>>1); + if (lo < INT16_MIN) lo = INT16_MIN; + if (lo > INT16_MAX) lo = INT16_MAX; + if (ro < INT16_MIN) ro = INT16_MIN; + if (ro > INT16_MAX) ro = INT16_MAX; + buf[i*2+0] = lo; + buf[i*2+1] = ro; + if (!(adpcm->control1 & C1_START)) return; + } +} + +void opna_adpcm_set_ram_256k(struct opna_adpcm *adpcm, void *ram) { + adpcm->ram = ram; +} + +void *opna_adpcm_get_ram(struct opna_adpcm *adpcm) { + return adpcm->ram; +} diff --git a/libopna/opnaadpcm.h b/libopna/opnaadpcm.h new file mode 100644 index 0000000..27a1349 --- /dev/null +++ b/libopna/opnaadpcm.h @@ -0,0 +1,43 @@ +#ifndef LIBOPNA_OPNAADPCM_H_INCLUDED +#define LIBOPNA_OPNAADPCM_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct opna_adpcm { + uint8_t control1; + uint8_t control2; + uint8_t vol; + uint16_t delta; + uint16_t start; + uint16_t end; + uint16_t limit; + uint32_t ramptr; + uint16_t step; + uint8_t *ram; + int16_t acc; + int16_t prev_acc; + uint16_t adpcmd; + int16_t out; +}; + +void opna_adpcm_reset(struct opna_adpcm *adpcm); +void opna_adpcm_mix(struct opna_adpcm *adpcm, int16_t *buf, unsigned samples); +void opna_adpcm_writereg(struct opna_adpcm *adpcm, unsigned reg, unsigned val); + +enum { + OPNA_ADPCM_RAM_SIZE = (1<<18) +}; + +void opna_adpcm_set_ram_256k(struct opna_adpcm *adpcm, void *ram); +void *opna_adpcm_get_ram(struct opna_adpcm *adpcm); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNAADPCM_H_INCLUDED diff --git a/libopna/opnadrum.c b/libopna/opnadrum.c new file mode 100644 index 0000000..2527c1a --- /dev/null +++ b/libopna/opnadrum.c @@ -0,0 +1,141 @@ +#include "opnadrum.h" + +static const uint16_t steps[49] = { + 16, 17, 19, 21, 23, 25, 28, + 31, 34, 37, 41, 45, 50, 55, + 60, 66, 73, 80, 88, 97, 107, + 118, 130, 143, 157, 173, 190, 209, + 230, 253, 279, 307, 337, 371, 408, + 449, 494, 544, 598, 658, 724, 796, + 876, 963, 1060, 1166, 1282, 1411, 1552 +}; + +static const int8_t step_inc[8] = { + -1, -1, -1, -1, 2, 5, 7, 9 +}; + +void opna_drum_reset(struct opna_drum *drum) { + for (int d = 0; d < 6; d++) { + drum->drums[d].data = 0; + drum->drums[d].playing = false; + drum->drums[d].index = 0; + drum->drums[d].len = 0; + drum->drums[d].level = 0; + drum->drums[d].left = false; + drum->drums[d].right = false; + } + drum->total_level = 0; +} + +void opna_drum_set_rom(struct opna_drum *drum, void *romptr) { + uint8_t *rom = (uint8_t *)romptr; + static const struct { + unsigned start; + unsigned end; + int div; + } part[6] = { + {OPNA_ROM_BD_START, OPNA_ROM_SD_START-1, 3}, + {OPNA_ROM_SD_START, OPNA_ROM_TOP_START-1, 3}, + {OPNA_ROM_TOP_START, OPNA_ROM_HH_START-1, 3}, + {OPNA_ROM_HH_START, OPNA_ROM_TOM_START-1, 3}, + {OPNA_ROM_TOM_START, OPNA_ROM_RIM_START-1, 6}, + {OPNA_ROM_RIM_START, OPNA_ROM_SIZE-1, 6}, + }; + drum->drums[0].data = drum->rom_bd; + drum->drums[1].data = drum->rom_sd; + drum->drums[2].data = drum->rom_top; + drum->drums[3].data = drum->rom_hh; + drum->drums[4].data = drum->rom_tom; + drum->drums[5].data = drum->rom_rim; + for (int p = 0; p < 6; p++) { + drum->drums[p].playing = false; + drum->drums[p].index = 0; + unsigned addr = part[p].start << 1; + int step = 0; + unsigned acc = 0; + int outindex = 0; + for (;;) { + if ((addr>>1) == part[p].end) break; + unsigned data = rom[addr>>1]; + if (!(addr&1)) data >>= 4; + data &= ((1<<4)-1); + int acc_diff = ((((data&7)<<1)|1) * steps[step]) >> 3; + if (data&8) acc_diff = -acc_diff; + acc += acc_diff; + step += step_inc[data&7]; + if (step < 0) step = 0; + if (step > 48) step = 48; + addr++; + + int out = acc & ((1u<<12)-1); + if (out >= (1<<11)) out -= (1<<12); + int16_t out16 = out << 4; + for (int i = 0; i < part[p].div; i++) { + drum->drums[p].data[outindex] = out16; + outindex++; + } + } + drum->drums[p].len = outindex; + } +} + +void opna_drum_mix(struct opna_drum *drum, int16_t *buf, int samples) { + for (int i = 0; i < samples; i++) { + int32_t lo = buf[i*2+0]; + int32_t ro = buf[i*2+1]; + for (int d = 0; d < 6; d++) { + if (drum->drums[d].playing && drum->drums[d].data) { + int co = drum->drums[d].data[drum->drums[d].index]; + co >>= 4; + unsigned level = (drum->drums[d].level^0x1f) + (drum->total_level^0x3f); + co *= 15 - (level&7); + co >>= 1+(level>>3); + if (drum->drums[d].left) lo += co; + if (drum->drums[d].right) ro += co; + drum->drums[d].index++; + if (drum->drums[d].index == drum->drums[d].len) { + drum->drums[d].index = 0; + drum->drums[d].playing = false; + } + } + } + if (lo < INT16_MIN) lo = INT16_MIN; + if (lo > INT16_MAX) lo = INT16_MAX; + if (ro < INT16_MIN) ro = INT16_MIN; + if (ro > INT16_MAX) ro = INT16_MAX; + buf[i*2+0] = lo; + buf[i*2+1] = ro; + } +} + +void opna_drum_writereg(struct opna_drum *drum, unsigned reg, unsigned val) { + val &= 0xff; + switch (reg) { + case 0x10: + for (int d = 0; d < 6; d++) { + if (val & (1<drums[d].playing = !(val & 0x80); + drum->drums[d].index = 0; + } + } + break; + case 0x11: + drum->total_level = val & 0x3f; + break; + case 0x18: + case 0x19: + case 0x1a: + case 0x1b: + case 0x1c: + case 0x1d: + { + int d = reg - 0x18; + drum->drums[d].left = val & 0x80; + drum->drums[d].right = val & 0x40; + drum->drums[d].level = val & 0x1f; + } + break; + default: + break; + } +} diff --git a/libopna/opnadrum.h b/libopna/opnadrum.h new file mode 100644 index 0000000..24bcd48 --- /dev/null +++ b/libopna/opnadrum.h @@ -0,0 +1,57 @@ +#ifndef LIBOPNA_OPNADRUM_H_INCLUDED +#define LIBOPNA_OPNADRUM_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define OPNA_ROM_BD_START 0x0000 +#define OPNA_ROM_SD_START 0x01c0 +#define OPNA_ROM_TOP_START 0x0440 +#define OPNA_ROM_HH_START 0x1b80 +#define OPNA_ROM_TOM_START 0x1d00 +#define OPNA_ROM_RIM_START 0x1f80 +#define OPNA_ROM_SIZE 0x2000 + +#define OPNA_ROM_BD_SIZE ((OPNA_ROM_SD_START-OPNA_ROM_BD_START)*2*3) +#define OPNA_ROM_SD_SIZE ((OPNA_ROM_TOP_START-OPNA_ROM_SD_START)*2*3) +#define OPNA_ROM_TOP_SIZE ((OPNA_ROM_HH_START-OPNA_ROM_TOP_START)*2*3) +#define OPNA_ROM_HH_SIZE ((OPNA_ROM_TOM_START-OPNA_ROM_HH_START)*2*3) +#define OPNA_ROM_TOM_SIZE ((OPNA_ROM_RIM_START-OPNA_ROM_TOM_START)*2*6) +#define OPNA_ROM_RIM_SIZE ((OPNA_ROM_SIZE-OPNA_ROM_RIM_START)*2*6) + +struct opna_drum { + struct { + int16_t *data; + bool playing; + unsigned index; + unsigned len; + unsigned level; + bool left; + bool right; + } drums[6]; + unsigned total_level; + int16_t rom_bd[OPNA_ROM_BD_SIZE]; + int16_t rom_sd[OPNA_ROM_SD_SIZE]; + int16_t rom_top[OPNA_ROM_TOP_SIZE]; + int16_t rom_hh[OPNA_ROM_HH_SIZE]; + int16_t rom_tom[OPNA_ROM_TOM_SIZE]; + int16_t rom_rim[OPNA_ROM_RIM_SIZE]; +}; + +void opna_drum_reset(struct opna_drum *drum); +// set rom data, size: 0x2000 (8192) bytes +void opna_drum_set_rom(struct opna_drum *drum, void *rom); + +void opna_drum_mix(struct opna_drum *drum, int16_t *buf, int samples); + +void opna_drum_writereg(struct opna_drum *drum, unsigned reg, unsigned val); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNADRUM_H_INCLUDED diff --git a/libopna/opnafm.c b/libopna/opnafm.c new file mode 100644 index 0000000..c7512a8 --- /dev/null +++ b/libopna/opnafm.c @@ -0,0 +1,511 @@ +#include "opnafm.h" + +#include "opnatables.h" + +enum { + CH3_MODE_NORMAL = 0, + CH3_MODE_CSM = 1, + CH3_MODE_SE = 2 +}; + +static void opna_fm_slot_reset(struct opna_fm_slot *slot) { + slot->phase = 0; + slot->env = 1023; + slot->env_count = 0; + slot->env_state = ENV_RELEASE; + slot->rate_shifter = 0; + slot->rate_selector = 0; + slot->rate_mul = 0; + slot->tl = 0; + slot->sl = 0; + slot->ar = 0; + slot->dr = 0; + slot->sr = 0; + slot->rr = 0; + slot->mul = 0; + slot->det = 0; + slot->ks = 0; + slot->keyon = false; +} + + +void opna_fm_chan_reset(struct opna_fm_channel *chan) { + for (int i = 0; i < 4; i++) { + opna_fm_slot_reset(&chan->slot[i]); + } + + chan->fbmem1 = 0; + chan->fbmem2 = 0; + chan->alg_mem = 0; + + chan->alg = 0; + chan->fb = 0; + chan->fnum = 0; + chan->blk = 0; +} + +void opna_fm_reset(struct opna_fm *fm) { + for (int i = 0; i < 6; i++) { + opna_fm_chan_reset(&fm->channel[i]); + fm->lselect[i] = true; + fm->rselect[i] = true; + } + fm->blkfnum_h = 0; + fm->env_div3 = 0; + + fm->ch3.mode = CH3_MODE_NORMAL; + for (int i = 0; i < 3; i++) { + fm->ch3.fnum[i] = 0; + fm->ch3.blk[i] = 0; + } +} +#define LIBOPNA_ENABLE_HIRES +// maximum output: 2042<<2 = 8168 +static int16_t opna_fm_slotout(struct opna_fm_slot *slot, int16_t modulation) { + unsigned pind = (slot->phase >> 10); + pind += modulation >> 1; + bool minus = pind & (1<<(LOGSINTABLEBIT+1)); + bool reverse = pind & (1<phase >> 8); + pind_hires += modulation << 1; + minus = pind_hires & (1<<(LOGSINTABLEHIRESBIT+1)); + reverse = pind_hires & (1<env << 2) + (slot->tl << 5); +#else + int logout = logsintable[pind] + (slot->env << 2) + (slot->tl << 5); +#endif // LIBOPNA_ENABLE_HIRES + + int selector = logout & ((1<> EXPTABLEBIT; + if (shifter > 13) shifter = 13; + + int16_t out = (exptable[selector] << 2) >> shifter; + if (minus) out = -out; + return out; +} + +static unsigned blkfnum2freq(unsigned blk, unsigned fnum) { + return (fnum << blk) >> 1; +} + +#define F(n) (!!(fnum & (1 << ((n)-1)))) + +static unsigned blkfnum2keycode(unsigned blk, unsigned fnum) { + unsigned keycode = blk<<2; + keycode |= F(11) << 1; + keycode |= (F(11) && (F(10)||F(9)||F(8))) || ((!F(11))&&F(10)&&F(9)&&F(8)); + return keycode; +} + +#undef F + +static void opna_fm_slot_phase(struct opna_fm_slot *slot, unsigned freq) { +// TODO: detune +// freq += slot->dt; + unsigned det = dettable[slot->det & 0x3][slot->keycode]; + if (slot->det & 0x4) det = -det; + freq += det; + freq &= (1U<<17)-1; + int mul = slot->mul << 1; + if (!mul) mul = 1; + slot->phase += ((freq * mul)>>1); +} + +void opna_fm_chan_phase(struct opna_fm_channel *chan) { + unsigned freq = blkfnum2freq(chan->blk, chan->fnum); + for (int i = 0; i < 4; i++) { + opna_fm_slot_phase(&chan->slot[i], freq); + } +} + +static void opna_fm_chan_phase_se(struct opna_fm_channel *chan, struct opna_fm *fm) { + unsigned freq; + freq = blkfnum2freq(fm->ch3.blk[2], fm->ch3.fnum[1]); + opna_fm_slot_phase(&chan->slot[0], freq); + freq = blkfnum2freq(fm->ch3.blk[0], fm->ch3.fnum[0]); + opna_fm_slot_phase(&chan->slot[2], freq); + freq = blkfnum2freq(fm->ch3.blk[1], fm->ch3.fnum[2]); + opna_fm_slot_phase(&chan->slot[1], freq); + freq = blkfnum2freq(chan->blk, chan->fnum); + opna_fm_slot_phase(&chan->slot[3], freq); +} + +int16_t opna_fm_chanout(struct opna_fm_channel *chan) { + int16_t fb = chan->fbmem1 + chan->fbmem2; + int16_t slot0 = chan->fbmem1; + chan->fbmem1 = chan->fbmem2; + if (!chan->fb) fb = 0; + chan->fbmem2 = opna_fm_slotout(&chan->slot[0], fb >> (9 - chan->fb)); + + int16_t slot2; + int16_t out = 0; + + switch (chan->alg) { + case 0: + slot2 = opna_fm_slotout(&chan->slot[2], chan->alg_mem); + chan->alg_mem = opna_fm_slotout(&chan->slot[1], slot0); + out = opna_fm_slotout(&chan->slot[3], slot2); + break; + case 1: + slot2 = opna_fm_slotout(&chan->slot[2], chan->alg_mem); + chan->alg_mem = slot0; + chan->alg_mem += opna_fm_slotout(&chan->slot[1], 0); + out = opna_fm_slotout(&chan->slot[3], slot2); + break; + case 2: + slot2 = opna_fm_slotout(&chan->slot[2], chan->alg_mem); + chan->alg_mem = opna_fm_slotout(&chan->slot[1], 0); + out = opna_fm_slotout(&chan->slot[3], slot0 + slot2); + break; + case 3: + slot2 = opna_fm_slotout(&chan->slot[2], 0); + out = opna_fm_slotout(&chan->slot[3], slot2 + chan->alg_mem); + chan->alg_mem = opna_fm_slotout(&chan->slot[1], slot0); + break; + case 4: + out = opna_fm_slotout(&chan->slot[1], slot0); + slot2 = opna_fm_slotout(&chan->slot[2], 0); + out += opna_fm_slotout(&chan->slot[3], slot2); + break; + case 5: + out = opna_fm_slotout(&chan->slot[2], chan->alg_mem); + chan->alg_mem = slot0; + out += opna_fm_slotout(&chan->slot[1], slot0); + out += opna_fm_slotout(&chan->slot[3], slot0); + break; + case 6: + out = opna_fm_slotout(&chan->slot[1], slot0); + out += opna_fm_slotout(&chan->slot[2], 0); + out += opna_fm_slotout(&chan->slot[3], 0); + break; + case 7: + out = slot0; + out += opna_fm_slotout(&chan->slot[1], 0); + out += opna_fm_slotout(&chan->slot[2], 0); + out += opna_fm_slotout(&chan->slot[3], 0); + break; + } + + return out; +} + +static void opna_fm_slot_setrate(struct opna_fm_slot *slot, int status) { + int r; + switch (status) { + case ENV_ATTACK: + r = slot->ar; + break; + case ENV_DECAY: + r = slot->dr; + break; + case ENV_SUSTAIN: + r = slot->sr; + break; + case ENV_RELEASE: + r = (slot->rr*2+1); + break; + default: + return; + } + + if (!r) { + slot->rate_selector = 0; + slot->rate_mul = 0; + slot->rate_shifter = 0; + return; + } + + int rate = 2*r + (slot->keycode >> (3 - slot->ks)); + + if (rate > 63) rate = 63; + int rate_shifter = 11 - (rate >> 2); + if (rate_shifter < 0) { + slot->rate_selector = (rate & ((1<<2)-1)) + 4; + slot->rate_mul = 1<<(-rate_shifter-1); + slot->rate_shifter = 0; + } else { + slot->rate_selector = rate & ((1<<2)-1); + slot->rate_mul = 1; + slot->rate_shifter = rate_shifter; + } +} + +static void opna_fm_slot_env(struct opna_fm_slot *slot) { + slot->env_count++; + if (!(slot->env_count & ((1<rate_shifter)-1))) { + int rate_index = (slot->env_count >> slot->rate_shifter) & 7; + int env_inc = rateinctable[slot->rate_selector][rate_index]; + env_inc *= slot->rate_mul; + + switch (slot->env_state) { + int newenv; + int sl; + case ENV_ATTACK: + newenv = slot->env + (((-slot->env-1) * env_inc) >> 4); + if (newenv <= 0) { + slot->env = 0; + slot->env_state = ENV_DECAY; + opna_fm_slot_setrate(slot, ENV_DECAY); + } else { + slot->env = newenv; + } + break; + case ENV_DECAY: + slot->env += env_inc; + sl = slot->sl; + if (sl == 0xf) sl = 0x1f; + if (slot->env >= (sl << 5)) { + slot->env_state = ENV_SUSTAIN; + opna_fm_slot_setrate(slot, ENV_SUSTAIN); + } + break; + case ENV_SUSTAIN: + slot->env += env_inc; + if (slot->env >= 1023) slot->env = 1023; + break; + case ENV_RELEASE: + slot->env += env_inc; + if (slot->env >= 1023) { + slot->env = 1023; + slot->env_state = ENV_OFF; + } + break; + } + } +} + +void opna_fm_slot_key(struct opna_fm_channel *chan, int slotnum, bool keyon) { + struct opna_fm_slot *slot = &chan->slot[slotnum]; + if (keyon) { + if (!slot->keyon) { + slot->keyon = true; + slot->env_state = ENV_ATTACK; + slot->env_count = 0; + slot->phase = 0; + opna_fm_slot_setrate(slot, ENV_ATTACK); + } + } else { + if ((slot->env_state != ENV_OFF) && slot->keyon) { + slot->keyon = false; + slot->env_state = ENV_RELEASE; + opna_fm_slot_setrate(slot, ENV_RELEASE); + } + } +} + +void opna_fm_slot_set_det(struct opna_fm_slot *slot, unsigned det) { + det &= 0x7; + slot->det = det; +} + +void opna_fm_slot_set_mul(struct opna_fm_slot *slot, unsigned mul) { + mul &= 0xf; + slot->mul = mul; +} + +void opna_fm_slot_set_tl(struct opna_fm_slot *slot, unsigned tl) { + tl &= 0x7f; + slot->tl = tl; +} + +void opna_fm_slot_set_ks(struct opna_fm_slot *slot, unsigned ks) { + ks &= 0x3; + slot->ks = ks; +} + +void opna_fm_slot_set_ar(struct opna_fm_slot *slot, unsigned ar) { + ar &= 0x1f; + slot->ar = ar; + if (slot->env_state == ENV_ATTACK) { + opna_fm_slot_setrate(slot, ENV_ATTACK); + } +} + +void opna_fm_slot_set_dr(struct opna_fm_slot *slot, unsigned dr) { + dr &= 0x1f; + slot->dr = dr; + if (slot->env_state == ENV_DECAY) { + opna_fm_slot_setrate(slot, ENV_DECAY); + } +} + +void opna_fm_slot_set_sr(struct opna_fm_slot *slot, unsigned sr) { + sr &= 0x1f; + slot->sr = sr; + if (slot->env_state == ENV_SUSTAIN) { + opna_fm_slot_setrate(slot, ENV_SUSTAIN); + } +} + +void opna_fm_slot_set_sl(struct opna_fm_slot *slot, unsigned sl) { + sl &= 0xf; + slot->sl = sl; +} + +void opna_fm_slot_set_rr(struct opna_fm_slot *slot, unsigned rr) { + rr &= 0xf; + slot->rr = rr; + if (slot->env_state == ENV_RELEASE) { + opna_fm_slot_setrate(slot, ENV_RELEASE); + } +} + +void opna_fm_chan_set_blkfnum(struct opna_fm_channel *chan, unsigned blk, unsigned fnum) { + blk &= 0x7; + fnum &= 0x7ff; + chan->blk = blk; + chan->fnum = fnum; + for (int i = 0; i < 4; i++) { + chan->slot[i].keycode = blkfnum2keycode(chan->blk, chan->fnum); + opna_fm_slot_setrate(&chan->slot[i], chan->slot[i].env_state); + } +} + +void opna_fm_chan_set_alg(struct opna_fm_channel *chan, unsigned alg) { + alg &= 0x7; + chan->alg = alg; +} + +void opna_fm_chan_set_fb(struct opna_fm_channel *chan, unsigned fb) { + fb &= 0x7; + chan->fb = fb; +} +//#include +void opna_fm_writereg(struct opna_fm *fm, unsigned reg, unsigned val) { + val &= (1<<8)-1; + + if (reg > 0x1ff) return; + + switch (reg) { + case 0x27: + { + unsigned mode = val >> 6; + if (mode != fm->ch3.mode) { +// printf("0x27\n"); +// printf(" mode = %d\n", mode); + fm->ch3.mode = mode; + } + } + return; + case 0x28: + { + int c = val & 0x3; + if (c == 3) return; + if (val & 0x4) c += 3; + for (int i = 0; i < 4; i++) { + opna_fm_slot_key(&fm->channel[c], i, (val & (1<<(4+i)))); + } + } + return; + } + + int c = reg & 0x3; + if (c == 3) return; + if (reg & (1<<8)) c += 3; + int s = ((reg & (1<<3)) >> 3) | ((reg & (1<<2)) >> 1); + struct opna_fm_channel *chan = &fm->channel[c]; + struct opna_fm_slot *slot = &chan->slot[s]; + switch (reg & 0xf0) { + case 0x30: + opna_fm_slot_set_det(slot, (val >> 4) & 0x7); + opna_fm_slot_set_mul(slot, val & 0xf); + break; + case 0x40: + opna_fm_slot_set_tl(slot, val & 0x7f); + break; + case 0x50: + opna_fm_slot_set_ks(slot, (val >> 6) & 0x3); + opna_fm_slot_set_ar(slot, val & 0x1f); + break; + case 0x60: + opna_fm_slot_set_dr(slot, val & 0x1f); + break; + case 0x70: + opna_fm_slot_set_sr(slot, val & 0x1f); + break; + case 0x80: + opna_fm_slot_set_sl(slot, (val >> 4) & 0xf); + opna_fm_slot_set_rr(slot, val & 0xf); + break; + case 0xa0: + { + unsigned blk = (fm->blkfnum_h >> 3) & 0x7; + unsigned fnum = ((fm->blkfnum_h & 0x7) << 8) | (val & 0xff); + switch (reg & 0xc) { + case 0x0: + opna_fm_chan_set_blkfnum(chan, blk, fnum); + break; + case 0x8: + c %= 3; + fm->ch3.blk[c] = blk; + fm->ch3.fnum[c] = fnum; + break; + case 0x4: + case 0xc: + fm->blkfnum_h = val & 0x3f; + break; + } + } + break; + case 0xb0: + switch (reg & 0xc) { + case 0x0: + opna_fm_chan_set_alg(chan, val & 0x7); + opna_fm_chan_set_fb(chan, (val >> 3) & 0x7); + break; + case 0x4: + fm->lselect[c] = val & 0x80; + fm->rselect[c] = val & 0x40; + break; + } + break; + } +} + +void opna_fm_chan_env(struct opna_fm_channel *chan) { + for (int i = 0; i < 4; i++) { + opna_fm_slot_env(&chan->slot[i]); + } +} + +void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples) { + for (unsigned i = 0; i < samples; i++) { + if (!fm->env_div3) { + for (int c = 0; c < 6; c++) { + opna_fm_chan_env(&fm->channel[c]); + } + fm->env_div3 = 3; + } + fm->env_div3--; + + int32_t lo = buf[i*2+0]; + int32_t ro = buf[i*2+1]; + + for (int c = 0; c < 6; c++) { + int16_t o = opna_fm_chanout(&fm->channel[c]); + // TODO: CSM + if (c == 2 && fm->ch3.mode != CH3_MODE_NORMAL) { + opna_fm_chan_phase_se(&fm->channel[c], fm); + } else { + opna_fm_chan_phase(&fm->channel[c]); + } + o >>= 1; + if (fm->lselect[c]) lo += o; + if (fm->rselect[c]) ro += o; + } + + if (lo < INT16_MIN) lo = INT16_MIN; + if (lo > INT16_MAX) lo = INT16_MAX; + if (ro < INT16_MIN) ro = INT16_MIN; + if (ro > INT16_MAX) ro = INT16_MAX; + buf[i*2+0] = lo; + buf[i*2+1] = ro; + } +} diff --git a/libopna/opnafm.h b/libopna/opnafm.h new file mode 100644 index 0000000..acd673f --- /dev/null +++ b/libopna/opnafm.h @@ -0,0 +1,111 @@ +#ifndef LIBOPNA_OPNAFM_H_INCLUDED +#define LIBOPNA_OPNAFM_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + ENV_ATTACK, + ENV_DECAY, + ENV_SUSTAIN, + ENV_RELEASE, + ENV_OFF, +}; + +struct opna_fm_slot { + // 20bits, upper 10 bits will be the index to sine table + uint32_t phase; + // 10 bits + uint16_t env; + uint16_t env_count; + uint8_t env_state; + uint8_t rate_shifter; + uint8_t rate_selector; + uint8_t rate_mul; + + uint8_t tl; + uint8_t sl; + + uint8_t ar; + uint8_t dr; + uint8_t sr; + uint8_t rr; + + uint8_t mul; + uint8_t det; + uint8_t ks; + + uint8_t keycode; + + bool keyon; +}; + +struct opna_fm_channel { + struct opna_fm_slot slot[4]; + + // save 2 samples for slot 1 feedback + uint16_t fbmem1; + uint16_t fbmem2; + // save sample for long (>2) chain of slots + uint16_t alg_mem; + + uint8_t alg; + uint8_t fb; + uint16_t fnum; + uint8_t blk; +}; + +struct opna_fm { + struct opna_fm_channel channel[6]; + + // remember here what was written on higher byte, + // actually write when lower byte written + uint8_t blkfnum_h; + // channel 3 blk, fnum + struct { + uint16_t fnum[3]; + uint8_t blk[3]; + uint8_t mode; + } ch3; + + // do envelope once per 3 samples + uint8_t env_div3; + + // pan + bool lselect[6]; + bool rselect[6]; +}; + +void opna_fm_reset(struct opna_fm *fm); +void opna_fm_mix(struct opna_fm *fm, int16_t *buf, unsigned samples); +void opna_fm_writereg(struct opna_fm *fm, unsigned reg, unsigned val); + +// +void opna_fm_chan_reset(struct opna_fm_channel *chan); +void opna_fm_chan_phase(struct opna_fm_channel *chan); +void opna_fm_chan_env(struct opna_fm_channel *chan); +void opna_fm_chan_set_blkfnum(struct opna_fm_channel *chan, unsigned blk, unsigned fnum); +int16_t opna_fm_chanout(struct opna_fm_channel *chan); +void opna_fm_slot_key(struct opna_fm_channel *chan, int slotnum, bool keyon); + +void opna_fm_chan_set_alg(struct opna_fm_channel *chan, unsigned alg); +void opna_fm_chan_set_fb(struct opna_fm_channel *chan, unsigned fb); +void opna_fm_slot_set_ar(struct opna_fm_slot *slot, unsigned ar); +void opna_fm_slot_set_dr(struct opna_fm_slot *slot, unsigned dr); +void opna_fm_slot_set_sr(struct opna_fm_slot *slot, unsigned sr); +void opna_fm_slot_set_rr(struct opna_fm_slot *slot, unsigned rr); +void opna_fm_slot_set_sl(struct opna_fm_slot *slot, unsigned sl); +void opna_fm_slot_set_tl(struct opna_fm_slot *slot, unsigned tl); +void opna_fm_slot_set_ks(struct opna_fm_slot *slot, unsigned ks); +void opna_fm_slot_set_mul(struct opna_fm_slot *slot, unsigned mul); +void opna_fm_slot_set_det(struct opna_fm_slot *slot, unsigned det); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBOPNA_OPNAFM_H_INCLUDED */ diff --git a/libopna/opnassg.c b/libopna/opnassg.c new file mode 100644 index 0000000..d87dc91 --- /dev/null +++ b/libopna/opnassg.c @@ -0,0 +1,233 @@ +#include "opnassg.h" +/* +static const float voltable[32] = { + 0.0f, 0.0f, 0x1.ae89f9p-8f, 0x1.000000p-7f, + 0x1.306fe0p-7f, 0x1.6a09e6p-7f, 0x1.ae89f9p-7f, 0x1.000000p-6f, + 0x1.306fe0p-6f, 0x1.6a09e6p-6f, 0x1.ae89f9p-6f, 0x1.000000p-5f, + 0x1.306fe0p-5f, 0x1.6a09e6p-5f, 0x1.ae89f9p-5f, 0x1.000000p-4f, + 0x1.306fe0p-4f, 0x1.6a09e6p-4f, 0x1.ae89f9p-4f, 0x1.000000p-3f, + 0x1.306fe0p-3f, 0x1.6a09e6p-3f, 0x1.ae89f9p-3f, 0x1.000000p-2f, + 0x1.306fe0p-2f, 0x1.6a09e6p-2f, 0x1.ae89f9p-2f, 0x1.000000p-1f, + 0x1.306fe0p-1f, 0x1.6a09e6p-1f, 0x1.ae89f9p-1f, 0x1.000000p-0f +}; +*/ + +// if (i < 2) voltable[i] = 0; +// else voltable[i] = round((0x7fff / 3.0) * pow(2.0, (i - 31)/4.0)); + +static const int16_t voltable[32] = { + 0, 0, 72, 85, + 101, 121, 144, 171, + 203, 241, 287, 341, + 406, 483, 574, 683, + 812, 965, 1148, 1365, + 1624, 1931, 2296, 2731, + 3247, 3862, 4592, 5461, + 6494, 7723, 9185, 10922 +}; + +#define SINCTABLEBIT 7 +#define SINCTABLELEN (1<ch[i].tone_counter = 0; + ssg->ch[i].out = false; + } + for (int i = 0; i < 0x10; i++) { + ssg->regs[i] = 0; + } + ssg->noise_counter = 0; + ssg->lfsr = 0; + ssg->env_counter = 0; + ssg->env_level = 0; + ssg->env_att = false; + ssg->env_alt = false; + ssg->env_hld = false; + ssg->env_holding = false; +} + +void opna_ssg_resampler_reset(struct opna_ssg_resampler *resampler) { + for (int i = 0; i < SINCTABLELEN; i++) { + resampler->buf[i] = 0; + } + resampler->index = 0; +} + +void opna_ssg_writereg(struct opna_ssg *ssg, unsigned reg, unsigned val) { + if (reg > 0xfu) return; + val &= 0xff; + ssg->regs[reg] = val; + + if (reg == 0xd) { + ssg->env_att = ssg->regs[0xd] & 0x4; + if (ssg->regs[0xd] & 0x8) { + ssg->env_alt = ssg->regs[0xd] & 0x2; + ssg->env_hld = ssg->regs[0xd] & 0x1; + } else { + ssg->env_alt = ssg->env_att; + ssg->env_hld = true; + } + ssg->env_holding = false; + ssg->env_level = 0; + ssg->env_counter = 0; + } +} + +static int opna_ssg_tone_period(const struct opna_ssg *ssg, int chan) { + return ssg->regs[0+chan*2] | ((ssg->regs[1+chan*2] & 0xf) << 8); +} + +static bool opna_ssg_chan_env(const struct opna_ssg *ssg, int chan) { + return ssg->regs[0x8+chan] & 0x10; +} +static int opna_ssg_tone_volume(const struct opna_ssg *ssg, int chan) { + return ssg->regs[0x8+chan] & 0xf; +} + +static bool opna_ssg_tone_out(const struct opna_ssg *ssg, int chan) { + unsigned reg = ssg->regs[0x7] >> chan; + return (ssg->ch[chan].out || (reg & 0x1)) && ((ssg->lfsr & 1) || (reg & 0x8)); +} + +static bool opna_ssg_tone_silent(const struct opna_ssg *ssg, int chan) { + unsigned reg = ssg->regs[0x7] >> chan; + return (reg & 0x1) && (reg & 0x8); +} + +static int opna_ssg_noise_period(const struct opna_ssg *ssg) { + return ssg->regs[0x6] & 0x1f; +} + +static int opna_ssg_env_period(const struct opna_ssg *ssg) { + return (ssg->regs[0xc] << 8) | ssg->regs[0xb]; +} + +static int opna_ssg_env_level(const struct opna_ssg *ssg) { + return ssg->env_att ? ssg->env_level : 31-ssg->env_level; +} + +int opna_ssg_channel_level(const struct opna_ssg *ssg, int ch) { + return opna_ssg_chan_env(ssg, ch) + ? opna_ssg_env_level(ssg) + : (opna_ssg_tone_volume(ssg, ch) << 1) + 1; +} + +void opna_ssg_generate_raw(struct opna_ssg *ssg, int16_t *buf, int samples) { + for (int i = 0; i < samples; i++) { + if (((++ssg->noise_counter) >> 1) >= opna_ssg_noise_period(ssg)) { + ssg->noise_counter = 0; + ssg->lfsr |= (!((ssg->lfsr & 1) ^ ((ssg->lfsr >> 3) & 1))) << 17; + ssg->lfsr >>= 1; + } + if (!ssg->env_holding) { + if (++ssg->env_counter >= opna_ssg_env_period(ssg)) { + ssg->env_counter = 0; + ssg->env_level++; + if (ssg->env_level == 0x20) { + ssg->env_level = 0; + if (ssg->env_alt) { + ssg->env_att = !ssg->env_att; + } + if (ssg->env_hld) { + ssg->env_level = 0x1f; + ssg->env_holding = true; + } + } + } + } + + int16_t out = 0; + for (int ch = 0; ch < 3; ch++) { + if (++ssg->ch[ch].tone_counter >= opna_ssg_tone_period(ssg, ch)) { + ssg->ch[ch].tone_counter = 0; + ssg->ch[ch].out = !ssg->ch[ch].out; + } + /* + if (opna_ssg_tone_out(ssg, ch)) { + int level = opna_ssg_chan_env(ssg, ch) + ? opna_ssg_env_level(ssg) + : (opna_ssg_tone_volume(ssg, ch) << 1) + 1; + out += voltable[level]; + } + */ + if (!opna_ssg_tone_silent(ssg, ch)) { + int level = opna_ssg_channel_level(ssg, ch); + out += opna_ssg_tone_out(ssg, ch) ? voltable[level] : -voltable[level]; + } + + } + buf[i] = out / 4; + } +} + +#define BUFINDEX(n) ((((resampler->index)>>1)+n)&(SINCTABLELEN-1)) + +void opna_ssg_mix_55466( + struct opna_ssg *ssg, struct opna_ssg_resampler *resampler, + int16_t *buf, int samples) { + for (int i = 0; i < samples; i++) { + { + int ssg_samples = ((resampler->index + 9)>>1) - ((resampler->index)>>1); + int16_t ssgbuf[5]; + opna_ssg_generate_raw(ssg, ssgbuf, ssg_samples); + for (int j = 0; j < ssg_samples; j++) { + resampler->buf[BUFINDEX(j)] = ssgbuf[j]; + } + resampler->index += 9; + } + int32_t sample = 0; + for (int j = 0; j < SINCTABLELEN; j++) { + unsigned sincindex = j*2; + if (!(resampler->index&1)) sincindex++; + bool sincsign = sincindex & (1<<(SINCTABLEBIT)); + unsigned sincmask = ((1<<(SINCTABLEBIT))-1); + sincindex = (sincindex & sincmask) ^ (sincsign ? sincmask : 0); + sample += (resampler->buf[BUFINDEX(j)] * sinctable[sincindex])>>2; + } + + sample >>= 16; + sample *= 13000; + sample >>= 14; + + int32_t lo = buf[i*2+0]; + int32_t ro = buf[i*2+1]; + lo += sample; + ro += sample; + if (lo < INT16_MIN) lo = INT16_MIN; + if (lo > INT16_MAX) lo = INT16_MAX; + if (ro < INT16_MIN) ro = INT16_MIN; + if (ro > INT16_MAX) ro = INT16_MAX; + buf[i*2+0] = lo; + buf[i*2+1] = ro; + } +} +#undef BUFINDEX diff --git a/libopna/opnassg.h b/libopna/opnassg.h new file mode 100644 index 0000000..5928c49 --- /dev/null +++ b/libopna/opnassg.h @@ -0,0 +1,60 @@ +#ifndef LIBOPNA_OPNASSG_H_INCLUDED +#define LIBOPNA_OPNASSG_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct opna_ssg_ch { + uint16_t tone_counter; + bool out; +}; + +struct opna_ssg { + uint8_t regs[0x10]; + struct opna_ssg_ch ch[3]; + uint8_t noise_counter; + uint32_t lfsr; + uint16_t env_counter; + uint8_t env_level; + bool env_att; + bool env_alt; + bool env_hld; + bool env_holding; +}; + +struct opna_ssg_resampler { + int16_t buf[(1<<7)]; + unsigned index; +}; + +void opna_ssg_reset(struct opna_ssg *ssg); +void opna_ssg_resampler_reset(struct opna_ssg_resampler *resampler); +// generate raw data +// Monoral +// Output level: [-32766, 32766] +// Samplerate: clock / 8 +// (on opna: masterclock / 32 +// 7987200 / 32 = 249600) +void opna_ssg_generate_raw(struct opna_ssg *ssg, int16_t *buf, int samples); + +// mix samplerate converted data for mixing with OPNA output +// call to buffer written with OPNA output +// samplerate: 7987200/144 Hz +// (55466.66..) Hz +void opna_ssg_mix_55466( + struct opna_ssg *ssg, struct opna_ssg_resampler *resampler, + int16_t *buf, int samples); +void opna_ssg_writereg(struct opna_ssg *ssg, unsigned reg, unsigned val); + +// channel level (0 - 31) +int opna_ssg_channel_level(const struct opna_ssg *ssg, int ch); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNASSG_H_INCLUDED diff --git a/libopna/opnatables.h b/libopna/opnatables.h new file mode 100644 index 0000000..30140bf --- /dev/null +++ b/libopna/opnatables.h @@ -0,0 +1,242 @@ +#define LOGSINTABLEBIT 8 +#define LOGSINTABLELEN (1<opna = opna; + timer->status = 0; + timer->interrupt_cb = 0; + timer->interrupt_userptr = 0; + timer->mix_cb = 0; + timer->mix_userptr = 0; + timer->timerb = 0; + timer->timerb_load = false; + timer->timerb_enable = false; + timer->timerb_cnt = 0; +} + +uint8_t opna_timer_status(const struct opna_timer *timer) { + return timer->status; +} + +void opna_timer_set_int_callback(struct opna_timer *timer, opna_timer_int_cb_t func, void *userptr) { + timer->interrupt_cb = func; + timer->interrupt_userptr = userptr; +} + +void opna_timer_set_mix_callback(struct opna_timer *timer, opna_timer_mix_cb_t func, void *userptr) { + timer->mix_cb = func; + timer->mix_userptr = userptr; +} + +void opna_timer_writereg(struct opna_timer *timer, unsigned reg, unsigned val) { + val &= 0xff; + opna_writereg(timer->opna, reg, val); + switch (reg) { + case 0x26: + timer->timerb = val; + timer->timerb_cnt = timer->timerb << TIMERB_SHIFT; + break; + case 0x27: + timer->timerb_load = val & (1<<1); + timer->timerb_enable = val & (1<<3); + if (val & (1<<5)) { + //timer->timerb_cnt = timer->timerb << TIMERB_SHIFT; + timer->status &= ~(1<<1); + } + } +} + +void opna_timer_mix(struct opna_timer *timer, int16_t *buf, unsigned samples) { + do { + unsigned generate_samples = samples; + if (timer->timerb_enable) { + unsigned timerb_samples = (1<timerb_cnt; + if (timerb_samples < generate_samples) { + generate_samples = timerb_samples; + } + } + opna_mix(timer->opna, buf, generate_samples); + if (timer->mix_cb) { + timer->mix_cb(timer->mix_userptr, buf, generate_samples); + } + buf += generate_samples*2; + samples -= generate_samples; + if (timer->timerb_load) { + timer->timerb_cnt = (timer->timerb_cnt + generate_samples) & ((1<timerb_cnt && timer->timerb_enable) { + if (!(timer->status & (1<<1))) { + timer->status |= (1<<1); + timer->interrupt_cb(timer->interrupt_userptr); + } + } + } + } while (samples); +} diff --git a/libopna/opnatimer.h b/libopna/opnatimer.h new file mode 100644 index 0000000..f3094ba --- /dev/null +++ b/libopna/opnatimer.h @@ -0,0 +1,42 @@ +#ifndef LIBOPNA_OPNA_TIMER_H_INCLUDED +#define LIBOPNA_OPNA_TIMER_H_INCLUDED + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*opna_timer_int_cb_t)(void *ptr); +typedef void (*opna_timer_mix_cb_t)(void *ptr, int16_t *buf, unsigned samples); + +struct opna; + +struct opna_timer { + struct opna *opna; + uint8_t status; + opna_timer_int_cb_t interrupt_cb; + void *interrupt_userptr; + opna_timer_mix_cb_t mix_cb; + void *mix_userptr; + uint8_t timerb; + bool timerb_load; + bool timerb_enable; + uint16_t timerb_cnt; +}; + +void opna_timer_reset(struct opna_timer *timer, struct opna *opna); +uint8_t opna_timer_status(const struct opna_timer *timer); +void opna_timer_set_int_callback(struct opna_timer *timer, + opna_timer_int_cb_t func, void *userptr); +void opna_timer_set_mix_callback(struct opna_timer *timer, + opna_timer_mix_cb_t func, void *userptr); +void opna_timer_writereg(struct opna_timer *timer, unsigned reg, unsigned val); +void opna_timer_mix(struct opna_timer *timer, int16_t *buf, unsigned samples); + +#ifdef __cplusplus +} +#endif + +#endif // LIBOPNA_OPNA_TIMER_H_INCLUDED diff --git a/libopna/s98gen.c b/libopna/s98gen.c new file mode 100644 index 0000000..cc2b529 --- /dev/null +++ b/libopna/s98gen.c @@ -0,0 +1,141 @@ +#include "s98gen.h" + +static uint32_t read32le(const void *dataptr, size_t offset) { + const uint8_t *data = (const uint8_t *)dataptr; + return ((uint32_t)data[offset+0]) | (((uint32_t)data[offset+1])<<8) | + (((uint32_t)data[offset+2])<<16) | (((uint32_t)data[offset+3])<<24); +} + +bool s98gen_init(struct s98gen *s98, void *s98dataptr, size_t s98data_size) { + uint8_t *s98data = (uint8_t *)s98dataptr; + if (s98data_size < 0x20) { + // size of s98 header + return false; + } + if (s98data[0] != 'S' || s98data[1] != '9' || + s98data[2] != '8') { + // file magic / version check + return false; + } + if ((s98data[3] != '1') && (s98data[3] != '3')) { + return false; + } + if (s98data[0xc] != 0) { + // COMPRESSING flag not zero + return false; + } + uint32_t clock = 7987200; + if ((s98data[3] == '3') && s98data[0x1c]) { + if (s98data_size < 0x30) { + // size of s98 header + device info + return false; + } + uint32_t devicetype = read32le(s98data, 0x20); + if (devicetype != 0x04 && devicetype != 0x02) { + // OPN / OPNA only + return false; + } + clock = read32le(s98data, 0x24); + // convert OPN clock to OPNA clock + if (devicetype == 2) clock *= 2; + } + s98->s98data = s98data; + s98->s98data_size = s98data_size; + s98->current_offset = read32le(s98data, 0x14); + s98->opnaclock = clock; + s98->samples_to_generate = 0; + s98->samples_to_generate_frac = 0; + s98->timer_numerator = read32le(s98data, 0x04); + s98->timer_denominator = read32le(s98data, 0x08); + if (!s98->timer_numerator) s98->timer_numerator = 10; + if (!s98->timer_denominator) s98->timer_denominator = 1000; + opna_reset(&s98->opna); + return true; +} + +// returns 0 when failed +static size_t s98gen_getvv(struct s98gen *s98) { + unsigned shift = 0; + size_t value = 0; + do { + if (s98->s98data_size < (s98->current_offset+1)) return 0; + value |= (s98->s98data[s98->current_offset] & 0x7f) << ((shift++)*7); + } while (s98->s98data[s98->current_offset++] & 0x80); + return value + 2; +} + +static void s98gen_set_samples_to_generate(struct s98gen *s98, size_t ticks) { + uint64_t s = s98->opnaclock * s98->timer_numerator * ticks; + s <<= 16; + s /= 144 * s98->timer_denominator; + s += s98->samples_to_generate_frac; + s98->samples_to_generate = s >> 16; + s98->samples_to_generate_frac = s & ((((size_t)1)<<16)-1); +} + +static bool s98gen_parse_s98(struct s98gen *s98) { + for (;;) { + switch (s98->s98data[s98->current_offset]) { + case 0x00: + if (s98->s98data_size < (s98->current_offset + 3)) return false; + opna_writereg(&s98->opna, + s98->s98data[s98->current_offset+1], + s98->s98data[s98->current_offset+2]); + s98->current_offset += 3; + break; + case 0x01: + if (s98->s98data_size < (s98->current_offset + 3)) return false; + opna_writereg(&s98->opna, + s98->s98data[s98->current_offset+1] | 0x100, + s98->s98data[s98->current_offset+2]); + s98->current_offset += 3; + break; + case 0xfe: + if (s98->s98data_size < (s98->current_offset+1)) return false; + s98->current_offset++; + { + size_t vv = s98gen_getvv(s98); + if (!vv) return false; + s98gen_set_samples_to_generate(s98, vv); + } + return true; + case 0xff: + if (s98->s98data_size < (s98->current_offset + 1)) return false; + s98->current_offset++; + s98gen_set_samples_to_generate(s98, 1); + return true; + default: + return false; + } + } +} + +bool s98gen_generate(struct s98gen *s98, int16_t *buf, size_t samples) { + for (size_t i = 0; i < samples; i++) { + buf[i*2+0] = 0; + buf[i*2+1] = 0; + } + if (samples <= s98->samples_to_generate) { + opna_mix(&s98->opna, buf, samples); + s98->samples_to_generate -= samples; + } else { + opna_mix(&s98->opna, buf, s98->samples_to_generate); + buf += s98->samples_to_generate * 2; + samples -= s98->samples_to_generate; + s98->samples_to_generate = 0; + while (samples) { + if (!s98gen_parse_s98(s98)) return false; + if (s98->samples_to_generate >= samples) { + opna_mix(&s98->opna, buf, samples); + s98->samples_to_generate -= samples; + samples = 0; + } else { + opna_mix(&s98->opna, buf, s98->samples_to_generate); + buf += s98->samples_to_generate*2; + samples -= s98->samples_to_generate; + s98->samples_to_generate = 0; + } + } + } + return true; +} diff --git a/libopna/s98gen.h b/libopna/s98gen.h new file mode 100644 index 0000000..8fa458d --- /dev/null +++ b/libopna/s98gen.h @@ -0,0 +1,34 @@ +#ifndef MYON_S98GEN_H_INCLUDED +#define MYON_S98GEN_H_INCLUDED + +#include +#include +#include +#include "opna.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct s98gen { + struct opna opna; + uint8_t *s98data; + size_t s98data_size; + size_t current_offset; + uint32_t opnaclock; + uint32_t samples_to_generate; + uint16_t samples_to_generate_frac; + uint32_t timer_numerator; + uint32_t timer_denominator; +}; + +// returns true if initialization succeeded +// returns false without touching *s98 when initialization failed (invalid data) +bool s98gen_init(struct s98gen *s98, void *s98data, size_t s98data_size); +bool s98gen_generate(struct s98gen *s98, int16_t *buf, size_t samples); + +#ifdef __cplusplus +} +#endif + +#endif // MYON_S98GEN_H_INCLUDED -- cgit v1.2.3