From 2aa5d8f5b29dcc4964c972d8d3a9f273d184b97e Mon Sep 17 00:00:00 2001 From: Adrien Moulin Date: Sat, 23 Jul 2022 13:38:10 +0200 Subject: [PATCH 01/32] PIT: add alternative faster PIT This is enabled by default on 486+ CPUs and can be forced disabled/enabled with pit_mode=0/1 --- src/86box.c | 1 + src/CMakeLists.txt | 2 +- src/config.c | 7 + src/device/keyboard_xt.c | 2 +- src/include/86box/86box.h | 1 + src/include/86box/pit.h | 43 ++- src/include/86box/pit_fast.h | 72 ++++ src/machine/m_amstrad.c | 2 +- src/machine/m_at.c | 2 +- src/machine/m_europc.c | 2 +- src/machine/m_pcjr.c | 16 +- src/machine/m_ps1.c | 2 +- src/machine/m_ps2_isa.c | 2 +- src/machine/m_ps2_mca.c | 3 +- src/machine/m_xt.c | 2 +- src/machine/m_xt_compaq.c | 4 +- src/machine/m_xt_laserxt.c | 2 +- src/machine/m_xt_olivetti.c | 6 +- src/machine/m_xt_philips.c | 2 +- src/machine/m_xt_t1000.c | 4 +- src/machine/m_xt_zenith.c | 2 +- src/machine/machine.c | 17 +- src/pic.c | 4 +- src/pit.c | 168 +++++---- src/pit_fast.c | 706 +++++++++++++++++++++++++++++++++++ src/port_6x.c | 2 +- 26 files changed, 968 insertions(+), 108 deletions(-) create mode 100644 src/include/86box/pit_fast.h create mode 100644 src/pit_fast.c diff --git a/src/86box.c b/src/86box.c index 805cc60bf..cb9fa52a7 100644 --- a/src/86box.c +++ b/src/86box.c @@ -182,6 +182,7 @@ int confirm_reset = 1; /* (C) enable reset confirmation */ int confirm_exit = 1; /* (C) enable exit confirmation */ int confirm_save = 1; /* (C) enable save confirmation */ int enable_discord = 0; /* (C) enable Discord integration */ +int pit_mode = -1; /* (C) force setting PIT mode */ /* Statistics. */ extern int mmuflush; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d1da45172..1420aaa89 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,7 +16,7 @@ # add_executable(86Box 86box.c config.c log.c random.c timer.c io.c acpi.c apm.c - dma.c ddma.c discord.c nmi.c pic.c pit.c port_6x.c port_92.c ppi.c pci.c + dma.c ddma.c discord.c nmi.c pic.c pit.c pit_fast.c port_6x.c port_92.c ppi.c pci.c mca.c usb.c fifo8.c device.c nvr.c nvr_at.c nvr_ps2.c machine_status.c) if(CMAKE_SYSTEM_NAME MATCHES "Linux") diff --git a/src/config.c b/src/config.c index c44c69d4a..8554dcc7a 100644 --- a/src/config.c +++ b/src/config.c @@ -886,6 +886,8 @@ load_machine(void) } else time_sync = !!config_get_int(cat, "enable_sync", 1); + pit_mode = config_get_int(cat, "pit_mode", -1); + /* Remove this after a while.. */ config_delete_var(cat, "nvr_path"); config_delete_var(cat, "enable_sync"); @@ -2479,6 +2481,11 @@ save_machine(void) else config_set_string(cat, "time_sync", "disabled"); + if (pit_mode == -1) + config_delete_var(cat, "pit_mode"); + else + config_set_int(cat, "pit_mode", pit_mode); + delete_section_if_empty(cat); } diff --git a/src/device/keyboard_xt.c b/src/device/keyboard_xt.c index 9b19669e2..d13bab56d 100644 --- a/src/device/keyboard_xt.c +++ b/src/device/keyboard_xt.c @@ -535,7 +535,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 1); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 1); if (val & 0x80) { kbd->pa = 0; diff --git a/src/include/86box/86box.h b/src/include/86box/86box.h index 6afa66695..86fe740c9 100644 --- a/src/include/86box/86box.h +++ b/src/include/86box/86box.h @@ -135,6 +135,7 @@ extern int is_pentium; /* TODO: Move back to cpu/cpu.h when it's figured out, how to remove that hack from the ET4000/W32p. */ extern int fixed_size_x, fixed_size_y; extern double mouse_sensitivity; /* (C) Mouse sensitivity scale */ +extern int pit_mode; /* (C) force setting PIT mode */ extern char exe_path[2048]; /* path (dir) of executable */ diff --git a/src/include/86box/pit.h b/src/include/86box/pit.h index e823794df..95541014b 100644 --- a/src/include/86box/pit.h +++ b/src/include/86box/pit.h @@ -58,9 +58,33 @@ typedef struct PIT { uint8_t ctrl; } pit_t; +enum { + PIT_8253 = 0, + PIT_8254, + PIT_8253_FAST, + PIT_8254_FAST +}; + +typedef struct { + uint8_t (*read)(uint16_t addr, void *priv); + void (*write)(uint16_t addr, uint8_t val, void *priv); + /* Gets a counter's count. */ + uint16_t (*get_count)(void *data, int counter_id); + /* Sets a counter's GATE input. */ + void (*set_gate)(void *data, int counter_id, int gate); + /* Sets if a counter's CLOCK input is from the timer or not - used by PCjr. */ + void(*set_using_timer)(void *data, int counter_id, int using_timer); + /* Sets a counter's OUT output handler. */ + void (*set_out_func)(void *data, int counter_id, void (*func)(int new_out, int old_out)); + /* Sets a counter's load count handler. */ + void (*set_load_func)(void *data, int counter_id, void (*func)(uint8_t new_m, int new_count)); + void (*ctr_clock)(void *data, int counter_id); + void *data; +} pit_intf_t; + +extern pit_intf_t pit_devs[2]; +extern const pit_intf_t pit_classic_intf; -extern pit_t *pit, - *pit2; extern double SYSCLK, PCICLK, AGPCLK; @@ -74,26 +98,13 @@ extern uint64_t PITCONST, ISACONST, extern int refresh_at_enable; - -/* Gets a counter's count. */ -extern uint16_t pit_ctr_get_count(ctr_t *ctr); -/* Sets a counter's load count handler. */ -extern void pit_ctr_set_load_func(ctr_t *ctr, void (*func)(uint8_t new_m, int new_count)); -/* Sets a counter's OUT output handler. */ -extern void pit_ctr_set_out_func(ctr_t *ctr, void (*func)(int new_out, int old_out)); -/* Sets a counter's GATE input. */ -extern void pit_ctr_set_gate(ctr_t *ctr, int gate); /* Sets a counter's CLOCK input. */ extern void pit_ctr_set_clock(ctr_t *ctr, int clock); -/* Sets if a counter's CLOCK input is from the timer or not - used by PCjr. */ -extern void pit_ctr_set_using_timer(ctr_t *ctr, int using_timer); extern pit_t * pit_common_init(int type, void (*out0)(int new_out, int old_out), void (*out1)(int new_out, int old_out)); -extern pit_t * pit_ps2_init(void); +extern pit_t * pit_ps2_init(int type); extern void pit_reset(pit_t *dev); -extern void pit_irq0_timer(int new_out, int old_out); -extern void pit_irq0_timer_pcjr(int new_out, int old_out); extern void pit_irq0_timer_ps2(int new_out, int old_out); extern void pit_refresh_timer_xt(int new_out, int old_out); diff --git a/src/include/86box/pit_fast.h b/src/include/86box/pit_fast.h new file mode 100644 index 000000000..bc09174fb --- /dev/null +++ b/src/include/86box/pit_fast.h @@ -0,0 +1,72 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Header of the implementation of the Intel 8253/8254 + * Programmable Interval Timer. + * + * + * + * Author: Miran Grca, + * Copyright 2019,2020 Miran Grca. + */ + +#ifndef EMU_PIT_FAST_H +#define EMU_PIT_FAST_H + +typedef struct { + uint8_t m, ctrl, + read_status, latch, bcd; + + uint16_t rl; + + int rm, wm, gate, out, + newcount, clock, using_timer, latched, + do_read_status; + int enabled; + int disabled; + int initial; + int thit; + int running; + int rereadlatch; + + union { + int count; + struct { + int units : 4; + int tens : 4; + int hundreds : 4; + int thousands : 4; + int myriads : 4; + }; + }; + + uint32_t l; + pc_timer_t timer; + + void (*load_func)(uint8_t new_m, int new_count); + void (*out_func)(int new_out, int old_out); +} ctrf_t; + +typedef struct { + int flags; + ctrf_t counters[3]; + + uint8_t ctrl; +} pitf_t; + +extern const pit_intf_t pit_fast_intf; + +#ifdef EMU_DEVICE_H +extern const device_t i8253_fast_device; +extern const device_t i8254_fast_device; +extern const device_t i8254_sec_fast_device; +extern const device_t i8254_ext_io_fast_device; +extern const device_t i8254_ps2_fast_device; +#endif + +#endif /*EMU_PIT_FAST_H*/ diff --git a/src/machine/m_amstrad.c b/src/machine/m_amstrad.c index 43a8093c8..3b0235d51 100644 --- a/src/machine/m_amstrad.c +++ b/src/machine/m_amstrad.c @@ -2059,7 +2059,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) speaker_enable = val & 0x02; if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 0x01); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 0x01); if (val & 0x80) { /* Keyboard enabled, so enable PA reading. */ diff --git a/src/machine/m_at.c b/src/machine/m_at.c index dc47b6207..df3c4bd8b 100644 --- a/src/machine/m_at.c +++ b/src/machine/m_at.c @@ -66,7 +66,7 @@ machine_at_common_init_ex(const machine_t *model, int type) machine_common_init(model); refresh_at_enable = 1; - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_at); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_at); pic2_init(); dma16_init(); diff --git a/src/machine/m_europc.c b/src/machine/m_europc.c index 2c035ded1..905515225 100644 --- a/src/machine/m_europc.c +++ b/src/machine/m_europc.c @@ -718,7 +718,7 @@ machine_europc_init(const machine_t *model) return ret; machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); nmi_init(); diff --git a/src/machine/m_pcjr.c b/src/machine/m_pcjr.c index 2491e5064..2665ea164 100644 --- a/src/machine/m_pcjr.c +++ b/src/machine/m_pcjr.c @@ -627,7 +627,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) speaker_enable = val & 2; if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 1); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 1); sn76489_mute = speaker_mute = 1; switch (val & 0x60) { case 0x00: @@ -642,7 +642,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) case 0xa0: nmi_mask = val & 0x80; - pit_ctr_set_using_timer(&pit->counters[1], !(val & 0x20)); + pit_devs[0].set_using_timer(pit_devs[0].data, 1, !(val & 0x20)); break; } } @@ -770,6 +770,18 @@ speed_changed(void *priv) recalc_timings(pcjr); } +void +pit_irq0_timer_pcjr(int new_out, int old_out) +{ + if (new_out && !old_out) { + picint(1); + pit_devs[0].ctr_clock(pit_devs[0].data, 1); + } + + if (!new_out) + picintc(1); +} + static const device_config_t pcjr_config[] = { { .name = "display_type", diff --git a/src/machine/m_ps1.c b/src/machine/m_ps1.c index 101746a03..19abb00bf 100644 --- a/src/machine/m_ps1.c +++ b/src/machine/m_ps1.c @@ -327,7 +327,7 @@ ps1_common_init(const machine_t *model) machine_common_init(model); refresh_at_enable = 1; - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_at); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_at); dma16_init(); pic2_init(); diff --git a/src/machine/m_ps2_isa.c b/src/machine/m_ps2_isa.c index 24fa0dfb1..094fc8a5f 100644 --- a/src/machine/m_ps2_isa.c +++ b/src/machine/m_ps2_isa.c @@ -190,7 +190,7 @@ ps2_isa_common_init(const machine_t *model) machine_common_init(model); refresh_at_enable = 1; - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_at); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_at); dma16_init(); pic2_init(); diff --git a/src/machine/m_ps2_mca.c b/src/machine/m_ps2_mca.c index 7aea5a319..665d42972 100644 --- a/src/machine/m_ps2_mca.c +++ b/src/machine/m_ps2_mca.c @@ -1363,7 +1363,8 @@ machine_ps2_common_init(const machine_t *model) device_add(&ps_no_nmi_nvr_device); pic2_init(); - pit_ps2_init(); + int pit_type = ((pit_mode == -1 && is486) || pit_mode == 1) ? PIT_8254_FAST : PIT_8254; + pit_ps2_init(pit_type); nmi_mask = 0x80; diff --git a/src/machine/m_xt.c b/src/machine/m_xt.c index f95ee2fe0..5b31920c5 100644 --- a/src/machine/m_xt.c +++ b/src/machine/m_xt.c @@ -24,7 +24,7 @@ machine_xt_common_init(const machine_t *model) { machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); if (fdc_type == FDC_INTERNAL) device_add(&fdc_xt_device); diff --git a/src/machine/m_xt_compaq.c b/src/machine/m_xt_compaq.c index bd355a161..a705f0e2b 100644 --- a/src/machine/m_xt_compaq.c +++ b/src/machine/m_xt_compaq.c @@ -50,7 +50,7 @@ machine_xt_compaq_deskpro_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_compaq_device); if (fdc_type == FDC_INTERNAL) @@ -78,7 +78,7 @@ machine_xt_compaq_portable_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_compaq_device); if (fdc_type == FDC_INTERNAL) diff --git a/src/machine/m_xt_laserxt.c b/src/machine/m_xt_laserxt.c index 58b7d3774..21681a5c2 100644 --- a/src/machine/m_xt_laserxt.c +++ b/src/machine/m_xt_laserxt.c @@ -167,7 +167,7 @@ machine_xt_lxt3_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_lxt3_device); diff --git a/src/machine/m_xt_olivetti.c b/src/machine/m_xt_olivetti.c index 716bb9c18..62fcda138 100644 --- a/src/machine/m_xt_olivetti.c +++ b/src/machine/m_xt_olivetti.c @@ -241,7 +241,7 @@ m24_kbd_write(uint16_t port, uint8_t val, void *priv) speaker_enable = val & 2; if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 1); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 1); break; } } @@ -792,7 +792,7 @@ machine_xt_m240_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); /* Address 66-67 = mainboard dip-switch settings */ io_sethandler(0x0066, 2, m24_read, NULL, NULL, NULL, NULL, NULL, NULL); @@ -846,7 +846,7 @@ machine_xt_m19_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); /* On-board FDC cannot be disabled */ device_add(&fdc_xt_device); diff --git a/src/machine/m_xt_philips.c b/src/machine/m_xt_philips.c index cafccf061..b10e3a37e 100644 --- a/src/machine/m_xt_philips.c +++ b/src/machine/m_xt_philips.c @@ -152,7 +152,7 @@ machine_xt_philips_common_init(const machine_t *model) { machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); nmi_init(); diff --git a/src/machine/m_xt_t1000.c b/src/machine/m_xt_t1000.c index 961cc627a..ba96b74e7 100644 --- a/src/machine/m_xt_t1000.c +++ b/src/machine/m_xt_t1000.c @@ -911,7 +911,7 @@ machine_xt_t1000_init(const machine_t *model) machine_common_init(model); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_device); t1000.fdc = device_add(&fdc_xt_device); nmi_init(); @@ -979,7 +979,7 @@ machine_xt_t1200_init(const machine_t *model) write_t1200_nvram, NULL, NULL, NULL, MEM_MAPPING_EXTERNAL, &t1000); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_device); t1000.fdc = device_add(&fdc_xt_t1x00_device); nmi_init(); diff --git a/src/machine/m_xt_zenith.c b/src/machine/m_xt_zenith.c index f9806b610..6eab9aee2 100644 --- a/src/machine/m_xt_zenith.c +++ b/src/machine/m_xt_zenith.c @@ -122,7 +122,7 @@ machine_zenith_init(const machine_t *model){ device_add(&zenith_scratchpad_device); - pit_ctr_set_out_func(&pit->counters[1], pit_refresh_timer_xt); + pit_devs[0].set_out_func(pit_devs[0].data, 1, pit_refresh_timer_xt); device_add(&keyboard_xt_zenith_device); diff --git a/src/machine/machine.c b/src/machine/machine.c index 67819b247..774b972c2 100644 --- a/src/machine/machine.c +++ b/src/machine/machine.c @@ -161,6 +161,16 @@ machine_available(int m) } +void +pit_irq0_timer(int new_out, int old_out) +{ + if (new_out && !old_out) + picint(1); + + if (!new_out) + picintc(1); +} + void machine_common_init(const machine_t *model) { @@ -168,5 +178,10 @@ machine_common_init(const machine_t *model) pic_init(); dma_init(); - pit_common_init(!!IS_AT(machine), pit_irq0_timer, NULL); + int pit_type = IS_AT(machine) ? PIT_8254 : PIT_8253; + /* Select fast PIT if needed */ + if ((pit_mode == -1 && is486) || pit_mode == 1) + pit_type += 2; + + pit_common_init(pit_type, pit_irq0_timer, NULL); } diff --git a/src/pic.c b/src/pic.c index ca2bff2f7..fe6b29fc8 100644 --- a/src/pic.c +++ b/src/pic.c @@ -750,8 +750,8 @@ picinterrupt() pic.interrupt |= 0x40; /* Mark slave pending. */ } - if ((pic.interrupt == 0) && (pit2 != NULL)) - pit_ctr_set_gate(&pit2->counters[0], 0); + if ((pic.interrupt == 0) && (pit_devs[1].data != NULL)) + pit_devs[1].set_gate(pit_devs[1].data, 0, 0); /* Two ACK's - do them in a loop to avoid potential compiler misoptimizations. */ for (i = 0; i < 2; i++) { diff --git a/src/pit.c b/src/pit.c index f19a1acf7..ba71928ca 100644 --- a/src/pit.c +++ b/src/pit.c @@ -34,14 +34,15 @@ #include <86box/pic.h> #include <86box/timer.h> #include <86box/pit.h> +#include <86box/pit_fast.h> #include <86box/ppi.h> #include <86box/machine.h> #include <86box/sound.h> #include <86box/snd_speaker.h> #include <86box/video.h> +pit_intf_t pit_devs[2]; -pit_t *pit, *pit2; double cpuclock, PITCONSTD, SYSCLK, isa_timing, @@ -67,12 +68,6 @@ int64_t firsttime = 1; #define PIT_SECONDARY 128 /* The PIT is secondary (ports 0048-004B). */ -enum { - PIT_8253 = 0, - PIT_8254 -}; - - #ifdef ENABLE_PIT_LOG int pit_do_log = ENABLE_PIT_LOG; @@ -293,8 +288,11 @@ ctr_tick(ctr_t *ctr) static void -ctr_clock(ctr_t *ctr) +ctr_clock(void *data, int counter_id) { + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + /* FIXME: Is this even needed? */ if ((ctr->state == 3) && (ctr->m != 2) && (ctr->m != 3)) return; @@ -379,35 +377,47 @@ ctr_latch_count(ctr_t *ctr) uint16_t -pit_ctr_get_count(ctr_t *ctr) +pit_ctr_get_count(void *data, int counter_id) { + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + return (uint16_t) ctr->l; } void -pit_ctr_set_load_func(ctr_t *ctr, void (*func)(uint8_t new_m, int new_count)) +pit_ctr_set_load_func(void *data, int counter_id, void (*func)(uint8_t new_m, int new_count)) { - if (ctr == NULL) + if (data == NULL) return; + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + ctr->load_func = func; } void -pit_ctr_set_out_func(ctr_t *ctr, void (*func)(int new_out, int old_out)) +pit_ctr_set_out_func(void *data, int counter_id, void (*func)(int new_out, int old_out)) { - if (ctr == NULL) + if (data == NULL) return; + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + ctr->out_func = func; } void -pit_ctr_set_gate(ctr_t *ctr, int gate) +pit_ctr_set_gate(void *data, int counter_id, int gate) { + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; + int old = ctr->gate; uint8_t mode = ctr->m & 3; @@ -470,10 +480,12 @@ pit_ctr_set_clock(ctr_t *ctr, int clock) void -pit_ctr_set_using_timer(ctr_t *ctr, int using_timer) +pit_ctr_set_using_timer(void *data, int counter_id, int using_timer) { - timer_process(); - + if (tsc > 0) + timer_process(); + pit_t *pit = (pit_t *)data; + ctr_t *ctr = &pit->counters[counter_id]; ctr->using_timer = using_timer; } @@ -673,46 +685,19 @@ pit_read(uint16_t addr, void *priv) } -/* FIXME: Should be moved to machine.c (default for most machine). */ -void -pit_irq0_timer(int new_out, int old_out) -{ - if (new_out && !old_out) - picint(1); - - if (!new_out) - picintc(1); -} - - -void -pit_irq0_timer_pcjr(int new_out, int old_out) -{ - if (new_out && !old_out) { - picint(1); - ctr_clock(&pit->counters[1]); - } - - if (!new_out) - picintc(1); -} - - void pit_irq0_timer_ps2(int new_out, int old_out) { - ctr_t *ctr = &pit2->counters[0]; - if (new_out && !old_out) { picint(1); - pit_ctr_set_gate(ctr, 1); + pit_devs[1].set_gate(pit_devs[1].data, 0, 1); } if (!new_out) picintc(1); if (!new_out && old_out) - ctr_clock(ctr); + pit_devs[1].ctr_clock(pit_devs[1].data, 0); } @@ -742,7 +727,8 @@ pit_speaker_timer(int new_out, int old_out) speaker_update(); - l = pit->counters[2].l ? pit->counters[2].l : 0x10000; + uint16_t count = pit_devs[0].get_count(pit_devs[0].data, 2); + l = count ? count : 0x10000; if (l < 25) speakon = 0; else @@ -809,11 +795,11 @@ pit_close(void *priv) { pit_t *dev = (pit_t *) priv; - if (dev == pit) - pit = NULL; + if (dev == pit_devs[0].data) + pit_devs[0].data = NULL; - if (dev == pit2) - pit2 = NULL; + if (dev == pit_devs[1].data) + pit_devs[1].data = NULL; if (dev != NULL) free(dev); @@ -915,47 +901,83 @@ pit_t * pit_common_init(int type, void (*out0)(int new_out, int old_out), void (*out1)(int new_out, int old_out)) { int i; + void *pit; + + pit_intf_t *pit_intf = &pit_devs[0]; switch (type) { case PIT_8253: default: pit = device_add(&i8253_device); + *pit_intf = pit_classic_intf; break; case PIT_8254: pit = device_add(&i8254_device); + *pit_intf = pit_classic_intf; break; + case PIT_8253_FAST: + pit = device_add(&i8253_fast_device); + *pit_intf = pit_fast_intf; + break; + case PIT_8254_FAST: + pit = device_add(&i8254_fast_device); + *pit_intf = pit_fast_intf; + break; + } + pit_intf->data = pit; + for (i = 0; i < 3; i++) { - pit->counters[i].gate = 1; - pit->counters[i].using_timer = 1; + pit_intf->set_gate(pit_intf->data, i, 1); + pit_intf->set_using_timer(pit_intf->data, i, 1); } - pit_ctr_set_out_func(&pit->counters[0], out0); - pit_ctr_set_out_func(&pit->counters[1], out1); - pit_ctr_set_out_func(&pit->counters[2], pit_speaker_timer); - pit_ctr_set_load_func(&pit->counters[2], speaker_set_count); - pit->counters[2].gate = 0; + pit_intf->set_out_func(pit_intf->data, 0, out0); + pit_intf->set_out_func(pit_intf->data, 1, out1); + pit_intf->set_out_func(pit_intf->data, 2, pit_speaker_timer); + pit_intf->set_load_func(pit_intf->data, 2, speaker_set_count); + + pit_intf->set_gate(pit_intf->data, 2, 0); return pit; } pit_t * -pit_ps2_init(void) +pit_ps2_init(int type) { - pit2 = device_add(&i8254_ps2_device); + void *pit; - pit_handler(1, 0x0044, 0x0001, pit2); - pit_handler(1, 0x0047, 0x0001, pit2); + pit_intf_t *ps2_pit = &pit_devs[1]; - pit2->counters[0].gate = 0; - pit2->counters[0].using_timer = pit2->counters[1].using_timer = pit2->counters[2].using_timer = 0; + switch (type) { + case PIT_8254: + default: + pit = device_add(&i8254_ps2_device); + *ps2_pit = pit_classic_intf; + break; - pit_ctr_set_out_func(&pit->counters[0], pit_irq0_timer_ps2); - pit_ctr_set_out_func(&pit2->counters[0], pit_nmi_timer_ps2); + case PIT_8254_FAST: + pit = device_add(&i8254_ps2_fast_device); + *ps2_pit = pit_fast_intf; + break; + } - return pit2; + ps2_pit->data = pit; + + ps2_pit->set_gate(ps2_pit->data, 0, 0); + for (int i = 0; i < 3; i++) { + ps2_pit->set_using_timer(ps2_pit->data, i, 0); + } + + io_sethandler(0x0044, 0x0001, ps2_pit->read, NULL, NULL, ps2_pit->write, NULL, NULL, pit); + io_sethandler(0x0047, 0x0001, ps2_pit->read, NULL, NULL, ps2_pit->write, NULL, NULL, pit); + + pit_devs[0].set_out_func(pit_devs[0].data, 0, pit_irq0_timer_ps2); + ps2_pit->set_out_func(ps2_pit->data, 0, pit_nmi_timer_ps2); + + return pit; } @@ -1063,3 +1085,15 @@ pit_set_clock(int clock) device_speed_changed(); } + +const pit_intf_t pit_classic_intf = { + &pit_read, + &pit_write, + &pit_ctr_get_count, + &pit_ctr_set_gate, + &pit_ctr_set_using_timer, + &pit_ctr_set_out_func, + &pit_ctr_set_load_func, + &ctr_clock, + NULL, +}; \ No newline at end of file diff --git a/src/pit_fast.c b/src/pit_fast.c new file mode 100644 index 000000000..704cfd68c --- /dev/null +++ b/src/pit_fast.c @@ -0,0 +1,706 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Implementation of the Intel 8253/8254 Programmable Interval + * Timer. + * + * + * + * Author: Miran Grca, + * Copyright 2019 Miran Grca. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include "cpu.h" +#include <86box/device.h> +#include <86box/timer.h> +#include <86box/cassette.h> +#include <86box/dma.h> +#include <86box/io.h> +#include <86box/nmi.h> +#include <86box/pic.h> +#include <86box/timer.h> +#include <86box/pit.h> +#include <86box/pit_fast.h> +#include <86box/ppi.h> +#include <86box/machine.h> +#include <86box/sound.h> +#include <86box/snd_speaker.h> +#include <86box/video.h> + +#define PIT_PS2 16 /* The PIT is the PS/2's second PIT. */ +#define PIT_EXT_IO 32 /* The PIT has externally specified port I/O. */ +#define PIT_CUSTOM_CLOCK 64 /* The PIT uses custom clock inputs provided by another provider. */ +#define PIT_SECONDARY 128 /* The PIT is secondary (ports 0048-004B). */ + +#ifdef ENABLE_PIT_LOG +int pit_do_log = ENABLE_PIT_LOG; + +static void +pit_log(const char *fmt, ...) +{ + va_list ap; + + if (pit_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +#define pit_log(fmt, ...) +#endif + +static void +pitf_ctr_set_out(ctrf_t *ctr, int out) +{ + if (ctr == NULL) + return; + + if (ctr->out_func != NULL) + ctr->out_func(out, ctr->out); + ctr->out = out; +} + +static void +pitf_ctr_set_load_func(void *data, int counter_id, void (*func)(uint8_t new_m, int new_count)) +{ + if (data == NULL) + return; + + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + + ctr->load_func = func; +} + +static uint16_t +pitf_ctr_get_count(void *data, int counter_id) +{ + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + return (uint16_t) ctr->l; +} + +static void +pitf_ctr_set_out_func(void *data, int counter_id, void (*func)(int new_out, int old_out)) +{ + if (data == NULL) + return; + + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + + ctr->out_func = func; +} + +static void +pitf_ctr_set_using_timer(void *data, int counter_id, int using_timer) +{ + if (tsc > 0) + timer_process(); + + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + ctr->using_timer = using_timer; +} + +static int +pitf_read_timer(ctrf_t *ctr) +{ + if (ctr->using_timer && !(ctr->m == 3 && !ctr->gate) && timer_is_enabled(&ctr->timer)) { + int read = (int) ((timer_get_remaining_u64(&ctr->timer)) / PITCONST); + if (ctr->m == 2) + read++; + if (read < 0) + read = 0; + if (read > 0x10000) + read = 0x10000; + if (ctr->m == 3) + read <<= 1; + return read; + } + if (ctr->m == 2) + return ctr->count + 1; + return ctr->count; +} + +/*Dump timer count back to pit->count[], and disable timer. This should be used + when stopping a PIT timer, to ensure the correct value can be read back.*/ +static void +pitf_dump_and_disable_timer(ctrf_t *ctr) +{ + if (ctr->using_timer && timer_is_enabled(&ctr->timer)) { + ctr->count = pitf_read_timer(ctr); + timer_disable(&ctr->timer); + } +} + +static void +pitf_ctr_load(ctrf_t *ctr) +{ + int l = ctr->l ? ctr->l : 0x10000; + + ctr->newcount = 0; + ctr->disabled = 0; + + switch (ctr->m) { + case 0: /*Interrupt on terminal count*/ + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 0); + ctr->thit = 0; + ctr->enabled = ctr->gate; + break; + case 1: /*Hardware retriggerable one-shot*/ + ctr->enabled = 1; + break; + case 2: /*Rate generator*/ + if (ctr->initial) { + ctr->count = l - 1; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) ((l - 1) * PITCONST)); + pitf_ctr_set_out(ctr, 1); + ctr->thit = 0; + } + ctr->enabled = ctr->gate; + break; + case 3: /*Square wave mode*/ + if (ctr->initial) { + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (((l + 1) >> 1) * PITCONST)); + pitf_ctr_set_out(ctr, 1); + ctr->thit = 0; + } + ctr->enabled = ctr->gate; + break; + case 4: /*Software triggered stobe*/ + if (!ctr->thit && !ctr->initial) + ctr->newcount = 1; + else { + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 0); + ctr->thit = 0; + } + ctr->enabled = ctr->gate; + break; + case 5: /*Hardware triggered stobe*/ + ctr->enabled = 1; + break; + } + + if (ctr->load_func != NULL) + ctr->load_func(ctr->m, l); + + ctr->initial = 0; + ctr->running = ctr->enabled && ctr->using_timer && !ctr->disabled; + if (ctr->using_timer && !ctr->running) + pitf_dump_and_disable_timer(ctr); +} + +static void +pitf_set_gate_no_timer(ctrf_t *ctr, int gate) +{ + int l = ctr->l ? ctr->l : 0x10000; + + if (ctr->disabled) { + ctr->gate = gate; + return; + } + + switch (ctr->m) { + case 0: /*Interrupt on terminal count*/ + case 4: /*Software triggered stobe*/ + if (ctr->using_timer && !ctr->running) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + ctr->enabled = gate; + break; + case 1: /*Hardware retriggerable one-shot*/ + case 5: /*Hardware triggered stobe*/ + if (gate && !ctr->gate) { + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 0); + ctr->thit = 0; + ctr->enabled = 1; + } + break; + case 2: /*Rate generator*/ + if (gate && !ctr->gate) { + ctr->count = l - 1; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 1); + ctr->thit = 0; + } + ctr->enabled = gate; + break; + case 3: /*Square wave mode*/ + if (gate && !ctr->gate) { + ctr->count = l; + if (ctr->using_timer) + timer_set_delay_u64(&ctr->timer, (uint64_t) (((l + 1) >> 1) * PITCONST)); + pitf_ctr_set_out(ctr, 1); + ctr->thit = 0; + } + ctr->enabled = gate; + break; + } + ctr->gate = gate; + ctr->running = ctr->enabled && ctr->using_timer && !ctr->disabled; + if (ctr->using_timer && !ctr->running) + pitf_dump_and_disable_timer(ctr); +} + +static void +pitf_ctr_set_gate(void *data, int counter_id, int gate) +{ + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + + if (ctr->disabled) { + ctr->gate = gate; + return; + } + + pitf_set_gate_no_timer(ctr, gate); +} + +static void +pitf_over(ctrf_t *ctr) +{ + int l = ctr->l ? ctr->l : 0x10000; + if (ctr->disabled) { + ctr->count += 0xffff; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * PITCONST)); + return; + } + + switch (ctr->m) { + case 0: /*Interrupt on terminal count*/ + case 1: /*Hardware retriggerable one-shot*/ + if (!ctr->thit) + pitf_ctr_set_out(ctr, 1); + ctr->thit = 1; + ctr->count += 0xffff; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * PITCONST)); + break; + case 2: /*Rate generator*/ + ctr->count += l; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + pitf_ctr_set_out(ctr, 0); + pitf_ctr_set_out(ctr, 1); + break; + case 3: /*Square wave mode*/ + if (ctr->out) { + pitf_ctr_set_out(ctr, 0); + ctr->count += (l >> 1); + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) ((l >> 1) * PITCONST)); + } else { + pitf_ctr_set_out(ctr, 1); + ctr->count += ((l + 1) >> 1); + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (((l + 1) >> 1) * PITCONST)); + } + // if (!t) pclog("pit_over: square wave mode c=%x %lli %f\n", pit.c[t], tsc, PITCONST); + break; + case 4: /*Software triggered strove*/ + if (!ctr->thit) { + pitf_ctr_set_out(ctr, 0); + pitf_ctr_set_out(ctr, 1); + } + if (ctr->newcount) { + ctr->newcount = 0; + ctr->count += l; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (l * PITCONST)); + } else { + ctr->thit = 1; + ctr->count += 0xffff; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * PITCONST)); + } + break; + case 5: /*Hardware triggered strove*/ + if (!ctr->thit) { + pitf_ctr_set_out(ctr, 0); + pitf_ctr_set_out(ctr, 1); + } + ctr->thit = 1; + ctr->count += 0xffff; + if (ctr->using_timer) + timer_advance_u64(&ctr->timer, (uint64_t) (0xffff * PITCONST)); + break; + } + ctr->running = ctr->enabled && ctr->using_timer && !ctr->disabled; + if (ctr->using_timer && !ctr->running) + pitf_dump_and_disable_timer(ctr); +} + +static __inline void +pitf_ctr_latch_count(ctrf_t *ctr) +{ + ctr->rl = pitf_read_timer(ctr); + // pclog("Timer latch %f %04X %04X\n",pit->c[0],pit->rl[0],pit->l[0]); + // pit->ctrl |= 0x30; + ctr->rereadlatch = 0; + ctr->rm = 3; + ctr->latched = 1; +} + +static __inline void +pitf_ctr_latch_status(ctrf_t *ctr) +{ + ctr->read_status = (ctr->ctrl & 0x3f) | (ctr->out ? 0x80 : 0); + ctr->do_read_status = 1; +} + +static void +pitf_write(uint16_t addr, uint8_t val, void *priv) +{ + pitf_t *dev = (pitf_t *) priv; + int t = (addr & 3); + ctrf_t *ctr; + + pit_log("[%04X:%08X] pit_write(%04X, %02X, %08X)\n", CS, cpu_state.pc, addr, val, priv); + + switch (addr & 3) { + case 3: /* control */ + t = val >> 6; + + if (t == 3) { + if (dev->flags & PIT_8254) { + /* This is 8254-only. */ + if (!(val & 0x20)) { + if (val & 2) + pitf_ctr_latch_count(&dev->counters[0]); + if (val & 4) + pitf_ctr_latch_count(&dev->counters[1]); + if (val & 8) + pitf_ctr_latch_count(&dev->counters[2]); + pit_log("PIT %i: Initiated readback command\n", t); + } + if (!(val & 0x10)) { + if (val & 2) + pitf_ctr_latch_status(&dev->counters[0]); + if (val & 4) + pitf_ctr_latch_status(&dev->counters[1]); + if (val & 8) + pitf_ctr_latch_status(&dev->counters[2]); + } + } + } else { + dev->ctrl = val; + ctr = &dev->counters[t]; + + if (!(dev->ctrl & 0x30)) { + pitf_ctr_latch_count(ctr); + dev->ctrl |= 0x30; + pit_log("PIT %i: Initiated latched read, %i bytes latched\n", + t, ctr->latched); + } else { + ctr->ctrl = val; + ctr->rm = ctr->wm = (ctr->ctrl >> 4) & 3; + ctr->m = (val >> 1) & 7; + if (ctr->m > 5) + ctr->m &= 3; + if (!(ctr->rm)) { + ctr->rm = 3; + ctr->rl = pitf_read_timer(ctr); + } + ctr->rereadlatch = 1; + ctr->initial = 1; + if (!ctr->m) + pitf_ctr_set_out(ctr, 0); + else + pitf_ctr_set_out(ctr, 1); + ctr->disabled = 1; + + pit_log("PIT %i: M = %i, RM/WM = %i, State = %i, Out = %i\n", t, ctr->m, ctr->rm, ctr->state, ctr->out); + } + ctr->thit = 0; + } + break; + + case 0: + case 1: + case 2: /* the actual timers */ + ctr = &dev->counters[t]; + + switch (ctr->wm) { + case 1: + ctr->l = val; + pitf_ctr_load(ctr); + break; + case 2: + ctr->l = (val << 8); + pitf_ctr_load(ctr); + break; + case 0: + ctr->l &= 0xFF; + ctr->l |= (val << 8); + pitf_ctr_load(ctr); + ctr->wm = 3; + break; + case 3: + ctr->l &= 0xFF00; + ctr->l |= val; + ctr->wm = 0; + break; + } + break; + } +} + +static uint8_t +pitf_read(uint16_t addr, void *priv) +{ + pitf_t *dev = (pitf_t *) priv; + uint8_t ret = 0xff; + int t = (addr & 3); + ctrf_t *ctr; + + switch (addr & 3) { + case 3: /* Control. */ + /* This is 8254-only, 8253 returns 0x00. */ + ret = (dev->flags & PIT_8254) ? dev->ctrl : 0x00; + break; + + case 0: + case 1: + case 2: /* The actual timers. */ + ctr = &dev->counters[t]; + + if (ctr->do_read_status) { + ctr->do_read_status = 0; + ret = ctr->read_status; + break; + } + + if (ctr->rereadlatch && !ctr->latched) { + ctr->rereadlatch = 0; + ctr->rl = pitf_read_timer(ctr); + } + switch (ctr->rm) { + case 0: + ret = ctr->rl >> 8; + ctr->rm = 3; + ctr->latched = 0; + ctr->rereadlatch = 1; + break; + case 1: + ret = (ctr->rl) & 0xFF; + ctr->latched = 0; + ctr->rereadlatch = 1; + break; + case 2: + ret = (ctr->rl) >> 8; + ctr->latched = 0; + ctr->rereadlatch = 1; + break; + case 3: + ret = (ctr->rl) & 0xFF; + if (ctr->m & 0x80) + ctr->m &= 7; + else + ctr->rm = 0; + break; + } + break; + } + + pit_log("[%04X:%08X] pit_read(%04X, %08X) = %02X\n", CS, cpu_state.pc, addr, priv, ret); + + return ret; +} + +static void +pitf_timer_over(void *p) +{ + ctrf_t *ctr = (ctrf_t *) p; + pitf_over(ctr); +} + +static void +pitf_ctr_clock(void *data, int counter_id) +{ + pitf_t *pit = (pitf_t *)data; + ctrf_t *ctr = &pit->counters[counter_id]; + + if (ctr->thit || !ctr->enabled) + return; + + if (ctr->using_timer) + return; + + ctr->count -= (ctr->m == 3) ? 2 : 1; + if (!ctr->count) + pitf_over(ctr); +} + +static void +ctr_reset(ctrf_t *ctr) +{ + ctr->ctrl = 0; + ctr->m = 0; + ctr->gate = 0; + ctr->l = 0xffff; + ctr->thit = 1; + ctr->using_timer = 1; +} + +static void +pitf_reset(pitf_t *dev) +{ + int i; + + memset(dev, 0, sizeof(pitf_t)); + + for (i = 0; i < 3; i++) + ctr_reset(&dev->counters[i]); + + /* Disable speaker gate. */ + dev->counters[2].gate = 0; +} + +static void +pitf_close(void *priv) +{ + pitf_t *dev = (pitf_t *) priv; + + if (dev == pit_devs[0].data) + pit_devs[0].data = NULL; + + if (dev == pit_devs[1].data) + pit_devs[1].data = NULL; + + if (dev != NULL) + free(dev); +} + +static void * +pitf_init(const device_t *info) +{ + pitf_t *dev = (pitf_t *) malloc(sizeof(pitf_t)); + pitf_reset(dev); + + dev->flags = info->local; + + if (!(dev->flags & PIT_PS2) && !(dev->flags & PIT_CUSTOM_CLOCK)) { + for (int i = 0; i < 3; i++) { + ctrf_t *ctr = &dev->counters[i]; + timer_add(&ctr->timer, pitf_timer_over, (void *)ctr, 0); + } + } + + if (!(dev->flags & PIT_EXT_IO)) { + io_sethandler((dev->flags & PIT_SECONDARY) ? 0x0048 : 0x0040, 0x0004, + pitf_read, NULL, NULL, pitf_write, NULL, NULL, dev); + } + + return dev; +} + +const device_t i8253_fast_device = { + .name = "Intel 8253/8253-5 Programmable Interval Timer", + .internal_name = "i8253_fast", + .flags = DEVICE_ISA, + .local = PIT_8253, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t i8254_fast_device = { + .name = "Intel 8254 Programmable Interval Timer", + .internal_name = "i8254_fast", + .flags = DEVICE_ISA, + .local = PIT_8254, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t i8254_sec_fast_device = { + .name = "Intel 8254 Programmable Interval Timer (Secondary)", + .internal_name = "i8254_sec_fast", + .flags = DEVICE_ISA, + .local = PIT_8254 | PIT_SECONDARY, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t i8254_ext_io_fast_device = { + .name = "Intel 8254 Programmable Interval Timer (External I/O)", + .internal_name = "i8254_ext_io_fast", + .flags = DEVICE_ISA, + .local = PIT_8254 | PIT_EXT_IO, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t i8254_ps2_fast_device = { + .name = "Intel 8254 Programmable Interval Timer (PS/2)", + .internal_name = "i8254_ps2_fast", + .flags = DEVICE_ISA, + .local = PIT_8254 | PIT_PS2 | PIT_EXT_IO, + .init = pitf_init, + .close = pitf_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const pit_intf_t pit_fast_intf = { + &pitf_read, + &pitf_write, + &pitf_ctr_get_count, + &pitf_ctr_set_gate, + &pitf_ctr_set_using_timer, + &pitf_ctr_set_out_func, + &pitf_ctr_set_load_func, + &pitf_ctr_clock, + NULL, +}; \ No newline at end of file diff --git a/src/port_6x.c b/src/port_6x.c index c583ffdf0..0fe168a3c 100644 --- a/src/port_6x.c +++ b/src/port_6x.c @@ -63,7 +63,7 @@ port_6x_write(uint16_t port, uint8_t val, void *priv) speaker_enable = val & 2; if (speaker_enable) was_speaker_enable = 1; - pit_ctr_set_gate(&pit->counters[2], val & 1); + pit_devs[0].set_gate(pit_devs[0].data, 2, val & 1); if (dev->flags & PORT_6X_TURBO) xi8088_turbo_set(!!(val & 0x04)); From 5d7db9d4c5f217b701a1e01d8c71e911dc2365b1 Mon Sep 17 00:00:00 2001 From: Adrien Moulin Date: Sat, 23 Jul 2022 14:03:54 +0200 Subject: [PATCH 02/32] Add pit_fast.o to Makefile.mingw --- src/win/Makefile.mingw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index 50b2b820b..8c29a24a1 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -535,7 +535,7 @@ CXXFLAGS := $(CFLAGS) # Create the (final) list of objects to build. # ######################################################################### MAINOBJ := 86box.o config.o log.o random.o timer.o io.o acpi.o apm.o dma.o ddma.o \ - nmi.o pic.o pit.o port_6x.o port_92.o ppi.o pci.o mca.o fifo8.o \ + nmi.o pic.o pit.o pit_fast.o port_6x.o port_92.o ppi.o pci.o mca.o fifo8.o \ usb.o device.o nvr.o nvr_at.o nvr_ps2.o machine_status.o \ $(VNCOBJ) From bcd8f7e75ad59d329ad4fad8ae1bcbf8dfe0fc1e Mon Sep 17 00:00:00 2001 From: OBattler Date: Sat, 23 Jul 2022 16:29:50 +0200 Subject: [PATCH 03/32] Added the ALi M5123 keyboard controller. --- src/device/keyboard_at.c | 43 ++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/device/keyboard_at.c b/src/device/keyboard_at.c index 087fa2b96..c4377bd97 100644 --- a/src/device/keyboard_at.c +++ b/src/device/keyboard_at.c @@ -89,6 +89,7 @@ #define KBC_VEN_OLIVETTI 0x24 #define KBC_VEN_NCR 0x28 #define KBC_VEN_SAMSUNG 0x2c +#define KBC_VEN_ALI 0x30 #define KBC_VEN_MASK 0x3c @@ -1093,6 +1094,8 @@ write_output(atkbd_t *dev, uint8_t val) softresetx86(); /*Pulse reset!*/ cpu_set_edx(); flushmmucache(); + if (kbc_ven == KBC_VEN_ALI) + smbase = 0x00030000; } } @@ -1353,6 +1356,7 @@ static uint8_t write64_ami(void *priv, uint8_t val) { atkbd_t *dev = (atkbd_t *)priv; + uint8_t kbc_ven = dev->flags & KBC_VEN_MASK; switch (val) { case 0x00: case 0x01: case 0x02: case 0x03: @@ -1386,7 +1390,15 @@ write64_ami(void *priv, uint8_t val) case 0xa1: /* get controller version */ kbd_log("ATkbc: AMI - get controller version\n"); - add_data(dev, 'H'); + if ((dev->flags & KBC_TYPE_MASK) >= KBC_TYPE_PS2_NOREF) { + if (kbc_ven == KBC_VEN_ALI) + add_data(dev, 'F'); + else if ((dev->flags & KBC_VEN_MASK) == KBC_VEN_INTEL_AMI) + add_data(dev, '5'); + else + add_data(dev, 'H'); + } else + add_data(dev, 'F'); return 0; case 0xa2: /* clear keyboard controller lines P22/P23 */ @@ -1456,9 +1468,14 @@ write64_ami(void *priv, uint8_t val) break; case 0xaf: /* set extended controller RAM */ - kbd_log("ATkbc: set extended controller RAM\n"); - dev->want60 = 1; - dev->secr_phase = 1; + if (kbc_ven == KBC_VEN_ALI) { + kbd_log("ATkbc: Award/ALi/VIA keyboard controller revision\n"); + add_to_kbc_queue_front(dev, 0x43, 0, 0x00); + } else { + kbd_log("ATkbc: set extended controller RAM\n"); + dev->want60 = 1; + dev->secr_phase = 1; + } return 0; case 0xb0: case 0xb1: case 0xb2: case 0xb3: @@ -1754,8 +1771,7 @@ kbd_write(uint16_t port, uint8_t val, void *priv) { atkbd_t *dev = (atkbd_t *)priv; int i = 0, bad = 1; - uint8_t mask, kbc_ven = 0x0; - kbc_ven = dev->flags & KBC_VEN_MASK; + uint8_t mask, kbc_ven = dev->flags & KBC_VEN_MASK; switch (port) { case 0x60: @@ -2315,6 +2331,7 @@ kbd_init(const device_t *info) case KBC_VEN_AMI: case KBC_VEN_INTEL_AMI: case KBC_VEN_SAMSUNG: + case KBC_VEN_ALI: dev->write60_ven = write60_ami; dev->write64_ven = write64_ami; break; @@ -2508,6 +2525,20 @@ const device_t keyboard_ps2_ami_device = { .config = NULL }; +const device_t keyboard_ps2_ali_device = { + .name = "PS/2 Keyboard (ALi M5123/M1543C)", + .internal_name = "keyboard_ps2_ali", + .flags = 0, + .local = KBC_TYPE_PS2_NOREF | KBC_VEN_ALI, + .init = kbd_init, + .close = kbd_close, + .reset = kbd_reset, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + const device_t keyboard_ps2_olivetti_device = { .name = "PS/2 Keyboard (Olivetti)", .internal_name = "keyboard_ps2_olivetti", From f9dbb5ea9f78c856a94ec281030aafed00f9a0eb Mon Sep 17 00:00:00 2001 From: OBattler Date: Sat, 23 Jul 2022 16:31:29 +0200 Subject: [PATCH 04/32] Fixes. --- src/device/keyboard_at.c | 28 ++++++++++++++-------------- src/include/86box/keyboard.h | 1 + 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/device/keyboard_at.c b/src/device/keyboard_at.c index c4377bd97..8bb436a0c 100644 --- a/src/device/keyboard_at.c +++ b/src/device/keyboard_at.c @@ -2525,20 +2525,6 @@ const device_t keyboard_ps2_ami_device = { .config = NULL }; -const device_t keyboard_ps2_ali_device = { - .name = "PS/2 Keyboard (ALi M5123/M1543C)", - .internal_name = "keyboard_ps2_ali", - .flags = 0, - .local = KBC_TYPE_PS2_NOREF | KBC_VEN_ALI, - .init = kbd_init, - .close = kbd_close, - .reset = kbd_reset, - { .available = NULL }, - .speed_changed = NULL, - .force_redraw = NULL, - .config = NULL -}; - const device_t keyboard_ps2_olivetti_device = { .name = "PS/2 Keyboard (Olivetti)", .internal_name = "keyboard_ps2_olivetti", @@ -2623,6 +2609,20 @@ const device_t keyboard_ps2_ami_pci_device = { .config = NULL }; +const device_t keyboard_ps2_ali_pci_device = { + .name = "PS/2 Keyboard (ALi M5123/M1543C)", + .internal_name = "keyboard_ps2_ali_pci", + .flags = DEVICE_PCI, + .local = KBC_TYPE_PS2_NOREF | KBC_VEN_ALI, + .init = kbd_init, + .close = kbd_close, + .reset = kbd_reset, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + const device_t keyboard_ps2_intel_ami_pci_device = { .name = "PS/2 Keyboard (AMI)", .internal_name = "keyboard_ps2_intel_ami_pci", diff --git a/src/include/86box/keyboard.h b/src/include/86box/keyboard.h index c07fe6a8b..29ea8e5fb 100644 --- a/src/include/86box/keyboard.h +++ b/src/include/86box/keyboard.h @@ -178,6 +178,7 @@ extern const device_t keyboard_ps2_pci_device; extern const device_t keyboard_ps2_ami_pci_device; extern const device_t keyboard_ps2_intel_ami_pci_device; extern const device_t keyboard_ps2_acer_pci_device; +extern const device_t keyboard_ps2_ali_pci_device; #endif extern void keyboard_init(void); From 758b8988e0a2fe954a80c5724189f278bdd258e6 Mon Sep 17 00:00:00 2001 From: OBattler Date: Sat, 23 Jul 2022 16:32:46 +0200 Subject: [PATCH 05/32] Preparations for ALi KBC. --- src/machine/m_at_slot1.c | 1 - src/machine/m_at_socket7.c | 2 -- src/machine/m_at_sockets7.c | 4 ---- 3 files changed, 7 deletions(-) diff --git a/src/machine/m_at_slot1.c b/src/machine/m_at_slot1.c index 188364e1c..f3b225ae1 100644 --- a/src/machine/m_at_slot1.c +++ b/src/machine/m_at_slot1.c @@ -717,7 +717,6 @@ machine_at_m729_init(const machine_t *model) pci_register_slot(0x10, PCI_CARD_NORMAL, 3, 4, 1, 2); device_add(&ali1621_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); diff --git a/src/machine/m_at_socket7.c b/src/machine/m_at_socket7.c index 25fa8483e..2856a599c 100644 --- a/src/machine/m_at_socket7.c +++ b/src/machine/m_at_socket7.c @@ -1005,7 +1005,6 @@ machine_at_m560_init(const machine_t *model) pci_register_slot(0x06, PCI_CARD_NORMAL, 4, 1, 2, 3); device_add(&ali1531_device); device_add(&ali1543_device); /* -5 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x3, 256); @@ -1040,7 +1039,6 @@ machine_at_ms5164_init(const machine_t *model) device_add(&ali1531_device); device_add(&ali1543_device); /* -5 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x3, 256); diff --git a/src/machine/m_at_sockets7.c b/src/machine/m_at_sockets7.c index c8eeccea0..a32bf3883 100644 --- a/src/machine/m_at_sockets7.c +++ b/src/machine/m_at_sockets7.c @@ -73,7 +73,6 @@ machine_at_p5a_init(const machine_t *model) pci_register_slot(0x06, PCI_CARD_NORMAL, 3, 4, 1, 2); device_add(&ali1541_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_39sf020_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); device_add(&w83781d_p5a_device); /* fans: Chassis, CPU, Power; temperatures: MB, unused, CPU */ @@ -107,7 +106,6 @@ machine_at_m579_init(const machine_t *model) pci_register_slot(0x14, PCI_CARD_NORMAL, 1, 2, 3, 4); device_add(&ali1541_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); @@ -140,7 +138,6 @@ machine_at_5aa_init(const machine_t *model) pci_register_slot(0x0A, PCI_CARD_NORMAL, 3, 4, 1, 2); device_add(&ali1541_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); @@ -175,7 +172,6 @@ machine_at_5ax_init(const machine_t *model) pci_register_slot(0x0C, PCI_CARD_NORMAL, 1, 2, 3, 4); device_add(&ali1541_device); device_add(&ali1543c_device); /* +0 */ - device_add(&keyboard_ps2_ami_pci_device); device_add(&sst_flash_29ee010_device); spd_register(SPD_TYPE_SDRAM, 0x7, 512); From e814fe606d398d4843777a0d017aed1a6644f1bd Mon Sep 17 00:00:00 2001 From: OBattler Date: Sat, 23 Jul 2022 16:33:27 +0200 Subject: [PATCH 06/32] ALi M5123 now adds the keyboard controller. --- src/sio/sio_ali5123.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sio/sio_ali5123.c b/src/sio/sio_ali5123.c index cabe38a58..3beb63590 100644 --- a/src/sio/sio_ali5123.c +++ b/src/sio/sio_ali5123.c @@ -456,6 +456,8 @@ ali5123_init(const device_t *info) io_sethandler(FDC_PRIMARY_ADDR, 0x0002, ali5123_read, NULL, NULL, ali5123_write, NULL, NULL, dev); + device_add(&keyboard_ps2_ali_pci_device); + return dev; } From 077f6174bd69ddfd0f8c91c47146a18d10fca05e Mon Sep 17 00:00:00 2001 From: OBattler Date: Sat, 23 Jul 2022 16:34:03 +0200 Subject: [PATCH 07/32] ALi M1543(C) log fixes. --- src/chipset/ali1543.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/chipset/ali1543.c b/src/chipset/ali1543.c index bde561c09..92d089a42 100644 --- a/src/chipset/ali1543.c +++ b/src/chipset/ali1543.c @@ -289,7 +289,7 @@ ali1533_write(int func, int addr, uint8_t val, void *priv) break; } pci_relocate_slot(PCI_CARD_SOUTHBRIDGE_IDE, ((int) dev->ide_slot) + dev->offset); - ali1543_log("IDE slot = %02X (A%0i)\n", dev->ide_slot/* - 5*/, dev->ide_slot + 11); + ali1543_log("IDE slot = %02X (A%0i)\n", ((int) dev->ide_slot) + dev->offset, dev->ide_slot + 11); ali5229_ide_irq_handler(dev); break; @@ -359,7 +359,7 @@ ali1533_write(int func, int addr, uint8_t val, void *priv) break; } pci_relocate_slot(PCI_CARD_SOUTHBRIDGE_PMU, ((int) dev->pmu_slot) + dev->offset); - ali1543_log("PMU slot = %02X (A%0i)\n", dev->pmu_slot/* - 5*/, dev->pmu_slot + 11); + ali1543_log("PMU slot = %02X (A%0i)\n", ((int) dev->pmu_slot) + dev->offset, dev->pmu_slot + 11); switch (val & 0x03) { case 0x00: dev->usb_slot = 0x14; /* A31 = slot 20 */ @@ -375,7 +375,7 @@ ali1533_write(int func, int addr, uint8_t val, void *priv) break; } pci_relocate_slot(PCI_CARD_SOUTHBRIDGE_USB, ((int) dev->usb_slot) + dev->offset); - ali1543_log("USB slot = %02X (A%0i)\n", dev->usb_slot/* - 5*/, dev->usb_slot + 11); + ali1543_log("USB slot = %02X (A%0i)\n", ((int) dev->usb_slot) + dev->offset, dev->usb_slot + 11); break; case 0x73: /* DDMA Base Address */ @@ -1555,6 +1555,7 @@ ali1543_init(const device_t *info) dev->offset = (info->local >> 8) & 0x7f; if (info->local & 0x8000) dev->offset = -dev->offset; + pclog("Offset = %i\n", dev->offset); pci_enable_mirq(0); pci_enable_mirq(1); From b8005eeb20e2bec6823f866f769b739181412337 Mon Sep 17 00:00:00 2001 From: OBattler Date: Sat, 23 Jul 2022 16:34:47 +0200 Subject: [PATCH 08/32] Include keyboard.h from the ALi M5123 code. --- src/sio/sio_ali5123.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sio/sio_ali5123.c b/src/sio/sio_ali5123.c index 3beb63590..79d9219ba 100644 --- a/src/sio/sio_ali5123.c +++ b/src/sio/sio_ali5123.c @@ -24,6 +24,7 @@ #include <86box/device.h> #include <86box/pic.h> #include <86box/pci.h> +#include <86box/keyboard.h> #include <86box/lpt.h> #include <86box/serial.h> #include <86box/hdc.h> From 25362803f68d5be846180c6ddac643b9402ce5b3 Mon Sep 17 00:00:00 2001 From: OBattler Date: Sat, 23 Jul 2022 16:35:24 +0200 Subject: [PATCH 09/32] Move the ALi M5123 initialization to the end of the M1543(C) initialization. --- src/chipset/ali1543.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/chipset/ali1543.c b/src/chipset/ali1543.c index 92d089a42..bbbf7d705 100644 --- a/src/chipset/ali1543.c +++ b/src/chipset/ali1543.c @@ -1548,9 +1548,6 @@ ali1543_init(const device_t *info) /* USB */ dev->usb = device_add(&usb_device); - /* Super I/O chip */ - device_add(&ali5123_device); - dev->type = info->local & 0xff; dev->offset = (info->local >> 8) & 0x7f; if (info->local & 0x8000) @@ -1565,6 +1562,9 @@ ali1543_init(const device_t *info) pci_enable_mirq(5); pci_enable_mirq(6); + /* Super I/O chip */ + device_add(&ali5123_device); + ali1543_reset(dev); return dev; From b20dcf2ee1fdb06d7fb18d256942655aedcbd308 Mon Sep 17 00:00:00 2001 From: OBattler Date: Sat, 23 Jul 2022 17:53:26 +0200 Subject: [PATCH 10/32] Reverted the Sound Blaster DSP changes. --- src/sound/snd_sb_dsp.c | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/sound/snd_sb_dsp.c b/src/sound/snd_sb_dsp.c index 3cbb223f3..c87f45e2f 100644 --- a/src/sound/snd_sb_dsp.c +++ b/src/sound/snd_sb_dsp.c @@ -326,14 +326,14 @@ void sb_dsp_speed_changed(sb_dsp_t *dsp) { if (dsp->sb_timeo < 256) - dsp->sblatcho = (256.0 - (double) dsp->sb_timeo); + dsp->sblatcho = TIMER_USEC * (256 - dsp->sb_timeo); else - dsp->sblatcho = ((1000000.0 / ((double) dsp->sb_timeo - 256.0))); + dsp->sblatcho = (uint64_t) (TIMER_USEC * (1000000.0f / (float) (dsp->sb_timeo - 256))); if (dsp->sb_timei < 256) - dsp->sblatchi = (256.0 - (double) dsp->sb_timei); + dsp->sblatchi = TIMER_USEC * (256 - dsp->sb_timei); else - dsp->sblatchi = ((1000000.0 / ((double) dsp->sb_timei - 256.0))); + dsp->sblatchi = (uint64_t) (TIMER_USEC * (1000000.0f / (float) (dsp->sb_timei - 256))); } void @@ -359,7 +359,7 @@ sb_start_dma(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_16_enable = 0; dsp->sb_8_output = 1; if (!timer_is_enabled(&dsp->output_timer)) - timer_on_auto(&dsp->output_timer, dsp->sblatcho); + timer_set_delay_u64(&dsp->output_timer, dsp->sblatcho); dsp->sbleftright = dsp->sbleftright_default; dsp->sbdacpos = 0; } else { @@ -372,7 +372,7 @@ sb_start_dma(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_8_enable = 0; dsp->sb_16_output = 1; if (!timer_is_enabled(&dsp->output_timer)) - timer_on_auto(&dsp->output_timer, dsp->sblatcho); + timer_set_delay_u64(&dsp->output_timer, dsp->sblatcho); } } @@ -389,7 +389,7 @@ sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_16_enable = 0; dsp->sb_8_output = 0; if (!timer_is_enabled(&dsp->input_timer)) - timer_on_auto(&dsp->input_timer, dsp->sblatchi); + timer_set_delay_u64(&dsp->input_timer, dsp->sblatchi); } else { dsp->sb_16_length = dsp->sb_16_origlength = len; dsp->sb_16_format = format; @@ -400,7 +400,7 @@ sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len) dsp->sb_8_enable = 0; dsp->sb_16_output = 0; if (!timer_is_enabled(&dsp->input_timer)) - timer_on_auto(&dsp->input_timer, dsp->sblatchi); + timer_set_delay_u64(&dsp->input_timer, dsp->sblatchi); } memset(dsp->record_buffer, 0, sizeof(dsp->record_buffer)); @@ -508,10 +508,10 @@ sb_exec_command(sb_dsp_t *dsp) mode does not imply such samplerate. Position is increased in sb_poll_i(). */ if (!timer_is_enabled(&dsp->input_timer)) { dsp->sb_timei = 256 - 22; - dsp->sblatchi = 22.0; + dsp->sblatchi = TIMER_USEC * 22; temp = 1000000 / 22; dsp->sb_freq = temp; - timer_on_auto(&dsp->input_timer, dsp->sblatchi); + timer_set_delay_u64(&dsp->input_timer, dsp->sblatchi); } break; case 0x24: /* 8-bit single cycle DMA input */ @@ -561,7 +561,7 @@ sb_exec_command(sb_dsp_t *dsp) break; case 0x40: /* Set time constant */ dsp->sb_timei = dsp->sb_timeo = dsp->sb_data[0]; - dsp->sblatcho = dsp->sblatchi = (256.0 - (double) dsp->sb_data[0]); + dsp->sblatcho = dsp->sblatchi = TIMER_USEC * (256 - dsp->sb_data[0]); temp = 256 - dsp->sb_data[0]; temp = 1000000 / temp; sb_dsp_log("Sample rate - %ihz (%i)\n", temp, dsp->sblatcho); @@ -572,8 +572,8 @@ sb_exec_command(sb_dsp_t *dsp) case 0x41: /* Set output sampling rate */ case 0x42: /* Set input sampling rate */ if (dsp->sb_type >= SB16) { - dsp->sblatcho = ((1000000.0 / (double) (dsp->sb_data[1] + (dsp->sb_data[0] << 8)))); - sb_dsp_log("Sample rate - %ihz (%lf)\n", dsp->sb_data[1] + (dsp->sb_data[0] << 8), dsp->sblatcho); + dsp->sblatcho = (uint64_t) (TIMER_USEC * (1000000.0f / (float) (dsp->sb_data[1] + (dsp->sb_data[0] << 8)))); + sb_dsp_log("Sample rate - %ihz (%i)\n", dsp->sb_data[1] + (dsp->sb_data[0] << 8), dsp->sblatcho); temp = dsp->sb_freq; dsp->sb_freq = dsp->sb_data[1] + (dsp->sb_data[0] << 8); dsp->sb_timeo = 256LL + dsp->sb_freq; @@ -631,7 +631,7 @@ sb_exec_command(sb_dsp_t *dsp) case 0x80: /* Pause DAC */ dsp->sb_pausetime = dsp->sb_data[0] + (dsp->sb_data[1] << 8); if (!timer_is_enabled(&dsp->output_timer)) - timer_on_auto(&dsp->output_timer, dsp->sblatcho); + timer_set_delay_u64(&dsp->output_timer, dsp->sblatcho); break; case 0x90: /* High speed 8-bit autoinit DMA output */ if (dsp->sb_type >= SB2) @@ -1200,7 +1200,7 @@ pollsb(void *p) int tempi, ref; int data[2]; - timer_on_auto(&dsp->output_timer, dsp->sblatcho); + timer_advance_u64(&dsp->output_timer, dsp->sblatcho); if (dsp->sb_8_enable && !dsp->sb_8_pause && dsp->sb_pausetime < 0 && dsp->sb_8_output) { sb_dsp_update(dsp); @@ -1457,7 +1457,7 @@ sb_poll_i(void *p) sb_dsp_t *dsp = (sb_dsp_t *) p; int processed = 0; - timer_on_auto(&dsp->input_timer, dsp->sblatchi); + timer_advance_u64(&dsp->input_timer, dsp->sblatchi); if (dsp->sb_8_enable && !dsp->sb_8_pause && dsp->sb_pausetime < 0 && !dsp->sb_8_output) { switch (dsp->sb_8_format) { From 02874f2ed265cc1b8de014e0c26fc690ac51e2f3 Mon Sep 17 00:00:00 2001 From: TC1995 Date: Sat, 23 Jul 2022 23:54:42 +0200 Subject: [PATCH 11/32] DMA: Implemented autoinit mode in the PS/2 MCA side (although the bit is undocumented in said side, but documented in the ISA/PCI side). Networking: Added the WD8013EP/A MCA nic, which is more supported than the WD80x3ET/A plus an initial ram size configuration before POS configuration. Sound: Added the Reply MCA OEM of SB16 with its own MCA POS ID and properly implemented the IRQ's and DMA's of the AdLib Gold in its EEPROM plus an initial configurable setting for them and an initial DRQ implementation into said card. --- src/dma.c | 3 + src/include/86box/net_wd8003.h | 4 +- src/include/86box/sound.h | 1 + src/network/net_wd8003.c | 121 ++++++++++++++++++++- src/network/network.c | 1 + src/sound/snd_adlibgold.c | 124 ++++++++++++++++----- src/sound/snd_sb.c | 191 +++++++++++++++++++++++++++++++++ src/sound/sound.c | 1 + 8 files changed, 414 insertions(+), 32 deletions(-) diff --git a/src/dma.c b/src/dma.c index c109f1b8d..5eb129860 100644 --- a/src/dma.c +++ b/src/dma.c @@ -62,6 +62,7 @@ static struct { #define DMA_PS2_IOA (1 << 0) +#define DMA_PS2_AUTOINIT (1 << 1) #define DMA_PS2_XFER_MEM_TO_IO (1 << 2) #define DMA_PS2_XFER_IO_TO_MEM (3 << 2) #define DMA_PS2_XFER_MASK (3 << 2) @@ -729,6 +730,8 @@ dma_ps2_write(uint16_t addr, uint8_t val, void *priv) else if ((val & DMA_PS2_XFER_MASK) == DMA_PS2_XFER_IO_TO_MEM) mode |= 4; dma_c->mode = (dma_c->mode & ~0x2c) | mode; + if (val & DMA_PS2_AUTOINIT) + dma_c->mode |= 0x10; dma_c->ps2_mode = val; dma_c->size = val & DMA_PS2_SIZE16; break; diff --git a/src/include/86box/net_wd8003.h b/src/include/86box/net_wd8003.h index 8fc89a88b..08bd901fe 100644 --- a/src/include/86box/net_wd8003.h +++ b/src/include/86box/net_wd8003.h @@ -50,7 +50,8 @@ enum { WD8003EB, /* WD8003EB : 8-bit ISA, 5x3 interface chip */ WD8013EBT, /* WD8013EBT : 16-bit ISA, no interface chip */ WD8003ETA, /* WD8003ET/A: 16-bit MCA, no interface chip */ - WD8003EA /* WD8003E/A : 16-bit MCA, 5x3 interface chip */ + WD8003EA, /* WD8003E/A : 16-bit MCA, 5x3 interface chip */ + WD8013EPA }; extern const device_t wd8003e_device; @@ -58,5 +59,6 @@ extern const device_t wd8003eb_device; extern const device_t wd8013ebt_device; extern const device_t wd8003eta_device; extern const device_t wd8003ea_device; +extern const device_t wd8013epa_device; #endif /*NET_WD8003_H*/ diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index 2696954ef..71d4942d0 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -120,6 +120,7 @@ extern const device_t sb_16_device; extern const device_t sb_16_pnp_device; extern const device_t sb_16_compat_device; extern const device_t sb_16_compat_nompu_device; +extern const device_t sb_16_reply_mca_device; extern const device_t sb_32_pnp_device; extern const device_t sb_awe32_device; extern const device_t sb_awe32_pnp_device; diff --git a/src/network/net_wd8003.c b/src/network/net_wd8003.c index 81e6d91ec..81429fe19 100644 --- a/src/network/net_wd8003.c +++ b/src/network/net_wd8003.c @@ -540,7 +540,6 @@ wd_mca_read(int port, void *priv) #define MCA_6FC0_IRQS { 3, 4, 10, 15 } - static void wd_mca_write(int port, uint8_t val, void *priv) { @@ -582,6 +581,68 @@ wd_mca_write(int port, uint8_t val, void *priv) dev->base_address, dev->irq, dev->ram_addr); } +static void +wd_8013epa_mca_write(int port, uint8_t val, void *priv) +{ + wd_t *dev = (wd_t *)priv; + + /* MCA does not write registers below 0x0100. */ + if (port < 0x0102) return; + + /* Save the MCA register value. */ + dev->pos_regs[port & 7] = val; + + /* + * The PS/2 Model 80 BIOS always enables a card if it finds one, + * even if no resources were assigned yet (because we only added + * the card, but have not run AutoConfig yet...) + * + * So, remove current address, if any. + */ + if (dev->base_address) + wd_io_remove(dev, dev->base_address); + + dev->base_address = 0x800 + ((dev->pos_regs[2] & 0xf0) << 8); + + switch (dev->pos_regs[5] & 0x0c) { + case 0: + dev->irq = 3; + break; + case 4: + dev->irq = 4; + break; + case 8: + dev->irq = 10; + break; + case 0x0c: + dev->irq = 14; + break; + } + + if (dev->pos_regs[3] & 0x10) + dev->ram_size = 0x4000; + else + dev->ram_size = 0x2000; + + dev->ram_addr = ((dev->pos_regs[3] & 0x0f) << 13) + 0xc0000; + if (dev->pos_regs[3] & 0x80) + dev->ram_addr += 0xf00000; + + /* Initialize the device if fully configured. */ + /* Register (new) I/O handler. */ + if (dev->pos_regs[2] & 0x01) + wd_io_set(dev, dev->base_address); + + mem_mapping_set_addr(&dev->ram_mapping, dev->ram_addr, dev->ram_size); + + mem_mapping_disable(&dev->ram_mapping); + if ((dev->msr & WE_MSR_ENABLE_RAM) && (dev->pos_regs[2] & 0x01)) + mem_mapping_enable(&dev->ram_mapping); + + wdlog("%s: attached IO=0x%X IRQ=%d, RAM addr=0x%06x\n", dev->name, + dev->base_address, dev->irq, dev->ram_addr); +} + static uint8_t wd_mca_feedb(void *priv) @@ -624,9 +685,12 @@ wd_init(const device_t *info) dev->maclocal[5] = (mac & 0xff); } - if ((dev->board == WD8003ETA) || (dev->board == WD8003EA)) - mca_add(wd_mca_read, wd_mca_write, wd_mca_feedb, NULL, dev); - else { + if ((dev->board == WD8003ETA) || (dev->board == WD8003EA) || dev->board == WD8013EPA) { + if (dev->board == WD8013EPA) + mca_add(wd_mca_read, wd_8013epa_mca_write, wd_mca_feedb, NULL, dev); + else + mca_add(wd_mca_read, wd_mca_write, wd_mca_feedb, NULL, dev); + } else { dev->base_address = device_get_config_hex16("base"); dev->irq = device_get_config_int("irq"); dev->ram_addr = device_get_config_hex20("ram_addr"); @@ -679,12 +743,20 @@ wd_init(const device_t *info) dev->board_chip = WE_ID_SOFT_CONFIG; /* Ethernet, MCA, no interface chip, RAM 16k */ case WD8003ETA: - dev->board_chip |= 0x05 | WE_ID_BUS_MCA; + dev->board_chip |= WE_TYPE_WD8013EBT | WE_ID_BUS_MCA; dev->ram_size = 0x4000; dev->pos_regs[0] = 0xC0; dev->pos_regs[1] = 0x6F; dev->bit16 = 3; break; + + case WD8013EPA: + dev->board_chip = WE_TYPE_WD8013EP | WE_ID_BUS_MCA; + dev->ram_size = device_get_config_int("ram_size"); + dev->pos_regs[0] = 0xC8; + dev->pos_regs[1] = 0x61; + dev->bit16 = 3; + break; } dev->irr |= WE_IRR_ENABLE_IRQ; @@ -969,6 +1041,31 @@ static const device_config_t wd8013_config[] = { { .name = "", .description = "", .type = CONFIG_END } }; +static const device_config_t wd8013epa_config[] = { + { + .name = "ram_size", + .description = "Initial RAM size", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 16384, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { .description = "8 kB", .value = 8192 }, + { .description = "16 kB", .value = 16384 }, + { .description = "" } + }, + }, + { + .name = "mac", + .description = "MAC Address", + .type = CONFIG_MAC, + .default_string = "", + .default_int = -1 + }, + { .name = "", .description = "", .type = CONFIG_END } +}; + static const device_config_t mca_mac_config[] = { { .name = "mac", @@ -1050,3 +1147,17 @@ const device_t wd8003ea_device = { .force_redraw = NULL, .config = mca_mac_config }; + +const device_t wd8013epa_device = { + .name = "Western Digital WD8013EP/A", + .internal_name = "wd8013epa", + .flags = DEVICE_MCA, + .local = WD8013EPA, + .init = wd_init, + .close = wd_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = wd8013epa_config +}; diff --git a/src/network/network.c b/src/network/network.c index 49915024a..6a7fbdfa5 100644 --- a/src/network/network.c +++ b/src/network/network.c @@ -103,6 +103,7 @@ static netcard_t net_cards[] = { { ðernext_mc_device, NULL }, { &wd8003eta_device, NULL }, { &wd8003ea_device, NULL }, + { &wd8013epa_device, NULL }, { &pcnet_am79c973_device, NULL }, { &pcnet_am79c970a_device, NULL }, { &rtl8029as_device, NULL }, diff --git a/src/sound/snd_adlibgold.c b/src/sound/snd_adlibgold.c index 652c672e2..655a93a1f 100644 --- a/src/sound/snd_adlibgold.c +++ b/src/sound/snd_adlibgold.c @@ -20,6 +20,7 @@ typedef struct adgold_t { int adgold_irq_status; + int irq, dma, hdma; uint8_t adgold_eeprom[0x1a]; @@ -157,7 +158,7 @@ adgold_update_irq_status(adgold_t *adgold) adgold->adgold_status = temp; if ((adgold->adgold_status ^ 0xf) && !adgold->adgold_irq_status) { - picint(0x80); + picint(1 << adgold->irq); } adgold->adgold_irq_status = adgold->adgold_status ^ 0xf; @@ -167,23 +168,26 @@ void adgold_getsamp_dma(adgold_t *adgold, int channel) { int temp; + dma_set_drq(adgold->dma, 1); if ((adgold->adgold_mma_regs[channel][0xc] & 0x60) && (((adgold->adgold_mma_fifo_end[channel] - adgold->adgold_mma_fifo_start[channel]) & 255) >= 127)) return; - temp = dma_channel_read(1); - if (temp == DMA_NODATA) + temp = dma_channel_read(adgold->dma); + if (temp == DMA_NODATA) { return; + } adgold->adgold_mma_fifo[channel][adgold->adgold_mma_fifo_end[channel]] = temp; - adgold->adgold_mma_fifo_end[channel] = (adgold->adgold_mma_fifo_end[channel] + 1) & 255; + adgold->adgold_mma_fifo_end[channel] = (adgold->adgold_mma_fifo_end[channel] + 1) & 255; if (adgold->adgold_mma_regs[channel][0xc] & 0x60) { - temp = dma_channel_read(1); + temp = dma_channel_read(adgold->dma); adgold->adgold_mma_fifo[channel][adgold->adgold_mma_fifo_end[channel]] = temp; - adgold->adgold_mma_fifo_end[channel] = (adgold->adgold_mma_fifo_end[channel] + 1) & 255; + adgold->adgold_mma_fifo_end[channel] = (adgold->adgold_mma_fifo_end[channel] + 1) & 255; } if (((adgold->adgold_mma_fifo_end[channel] - adgold->adgold_mma_fifo_start[channel]) & 255) >= adgold->adgold_mma_intpos[channel]) { adgold->adgold_mma_status &= ~(0x01 << channel); adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } @@ -335,10 +339,11 @@ adgold_write(uint16_t addr, uint8_t val, void *p) break; /* 7350 Hz*/ } if (val & 0x80) { - adgold->adgold_mma_enable[0] = 0; + adgold->adgold_mma_enable[0] = 0; adgold->adgold_mma_fifo_end[0] = adgold->adgold_mma_fifo_start[0] = 0; adgold->adgold_mma_status &= ~0x01; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } if ((val & 0x01)) /*Start playback*/ { @@ -347,7 +352,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (adgold->adgold_mma_regs[0][0xc] & 1) { if (adgold->adgold_mma_regs[0][0xc] & 0x80) { - adgold->adgold_mma_enable[1] = 1; + adgold->adgold_mma_enable[1] = 1; adgold->adgold_mma.voice_count[1] = adgold->adgold_mma.voice_latch[1]; while (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) < 128) { @@ -357,10 +362,12 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) >= adgold->adgold_mma_intpos[0]) { adgold->adgold_mma_status &= ~0x01; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } if (((adgold->adgold_mma_fifo_end[1] - adgold->adgold_mma_fifo_start[1]) & 255) >= adgold->adgold_mma_intpos[1]) { adgold->adgold_mma_status &= ~0x02; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } else { while (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) < 128) { @@ -369,6 +376,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) >= adgold->adgold_mma_intpos[0]) { adgold->adgold_mma_status &= ~0x01; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } } @@ -379,10 +387,11 @@ adgold_write(uint16_t addr, uint8_t val, void *p) case 0xb: if (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) < 128) { adgold->adgold_mma_fifo[0][adgold->adgold_mma_fifo_end[0]] = val; - adgold->adgold_mma_fifo_end[0] = (adgold->adgold_mma_fifo_end[0] + 1) & 255; + adgold->adgold_mma_fifo_end[0] = (adgold->adgold_mma_fifo_end[0] + 1) & 255; if (((adgold->adgold_mma_fifo_end[0] - adgold->adgold_mma_fifo_start[0]) & 255) >= adgold->adgold_mma_intpos[0]) { adgold->adgold_mma_status &= ~0x01; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } break; @@ -457,6 +466,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) adgold->adgold_mma_fifo_end[1] = adgold->adgold_mma_fifo_start[1] = 0; adgold->adgold_mma_status &= ~0x02; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } if ((val & 0x01)) /*Start playback*/ { @@ -479,6 +489,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (((adgold->adgold_mma_fifo_end[1] - adgold->adgold_mma_fifo_start[1]) & 255) >= adgold->adgold_mma_intpos[1]) { adgold->adgold_mma_status &= ~0x02; adgold_update_irq_status(adgold); + dma_set_drq(adgold->dma, 0); } } break; @@ -525,6 +536,7 @@ adgold_read(uint16_t addr, void *p) default: temp = adgold->adgold_38x_regs[adgold->adgold_38x_addr]; + break; } } else temp = opl3_read(addr, &adgold->opl); @@ -877,8 +889,9 @@ adgold_init(const device_t *info) adgold_t *adgold = malloc(sizeof(adgold_t)); memset(adgold, 0, sizeof(adgold_t)); + adgold->dma = device_get_config_int("dma"); + adgold->irq = device_get_config_int("irq"); adgold->surround_enabled = device_get_config_int("surround"); - adgold->gameport_enabled = device_get_config_int("gameport"); opl3_init(&adgold->opl); @@ -912,9 +925,9 @@ adgold_init(const device_t *info) adgold->adgold_eeprom[0x10] = 0xff; adgold->adgold_eeprom[0x11] = 0x20; adgold->adgold_eeprom[0x12] = 0x00; - adgold->adgold_eeprom[0x13] = 0x0b; /* IRQ 1, DMA1 */ - adgold->adgold_eeprom[0x14] = 0x00; /* DMA2 */ - adgold->adgold_eeprom[0x15] = 0x71; /* Port */ + adgold->adgold_eeprom[0x13] = 0xa0; + adgold->adgold_eeprom[0x14] = 0x00; + adgold->adgold_eeprom[0x15] = 0x388 / 8; /*Present at 388-38f*/ adgold->adgold_eeprom[0x16] = 0x00; adgold->adgold_eeprom[0x17] = 0x68; adgold->adgold_eeprom[0x18] = 0x00; /* Surround */ @@ -927,25 +940,36 @@ adgold_init(const device_t *info) fclose(f); } - adgold->adgold_status = 0xf; - adgold->adgold_38x_addr = 0; - adgold->adgold_eeprom[0x13] = 3 | (1 << 3); /*IRQ 7, DMA 1*/ - // adgold->adgold_eeprom[0x14] = 3 << 4; /*DMA 3 - Double check this */ - adgold->adgold_eeprom[0x14] = 0x00; /*DMA ?*/ - adgold->adgold_eeprom[0x15] = 0x388 / 8; /*Present at 388-38f*/ + adgold->adgold_status = 0xf; + adgold->adgold_38x_addr = 0; + switch (adgold->irq) { + case 3: + adgold->adgold_eeprom[0x13] |= 0x00; + break; + case 4: + adgold->adgold_eeprom[0x13] |= 0x01; + break; + case 5: + adgold->adgold_eeprom[0x13] |= 0x02; + break; + case 7: + adgold->adgold_eeprom[0x13] |= 0x03; + break; + } + adgold->adgold_eeprom[0x13] |= (adgold->dma << 3); memcpy(adgold->adgold_38x_regs, adgold->adgold_eeprom, 0x19); - adgold->vol_l = attenuation[adgold->adgold_eeprom[0x04] & 0x3f]; - adgold->vol_r = attenuation[adgold->adgold_eeprom[0x05] & 0x3f]; - adgold->bass = adgold->adgold_eeprom[0x06] & 0xf; - adgold->treble = adgold->adgold_eeprom[0x07] & 0xf; - adgold->fm_vol_l = (int) (int8_t) (adgold->adgold_eeprom[0x09] - 128); - adgold->fm_vol_r = (int) (int8_t) (adgold->adgold_eeprom[0x0a] - 128); + adgold->vol_l = attenuation[adgold->adgold_eeprom[0x04] & 0x3f]; + adgold->vol_r = attenuation[adgold->adgold_eeprom[0x05] & 0x3f]; + adgold->bass = adgold->adgold_eeprom[0x06] & 0xf; + adgold->treble = adgold->adgold_eeprom[0x07] & 0xf; + adgold->fm_vol_l = (int) (int8_t) (adgold->adgold_eeprom[0x09] - 128); + adgold->fm_vol_r = (int) (int8_t) (adgold->adgold_eeprom[0x0a] - 128); adgold->samp_vol_l = (int) (int8_t) (adgold->adgold_eeprom[0x0b] - 128); adgold->samp_vol_r = (int) (int8_t) (adgold->adgold_eeprom[0x0c] - 128); adgold->aux_vol_l = (int) (int8_t) (adgold->adgold_eeprom[0x0d] - 128); adgold->aux_vol_r = (int) (int8_t) (adgold->adgold_eeprom[0x0e] - 128); - adgold->adgold_mma_enable[0] = 0; + adgold->adgold_mma_enable[0] = 0; adgold->adgold_mma_fifo_start[0] = adgold->adgold_mma_fifo_end[0] = 0; /*388/389 are handled by adlib_init*/ @@ -982,6 +1006,54 @@ adgold_close(void *p) static const device_config_t adgold_config[] = { // clang-format off + { + .name = "irq", + .description = "IRQ", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 7, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { + .description = "IRQ 3", + .value = 3 + }, + { + .description = "IRQ 4", + .value = 4 + }, + { + .description = "IRQ 5", + .value = 5 + }, + { + .description = "IRQ 7", + .value = 7 + }, + { .description = "" } + } + }, + { + .name = "dma", + .description = "Low DMA channel", + .type = CONFIG_SELECTION, + .default_string = "", + .default_int = 1, + .file_filter = "", + .spinner = { 0 }, + .selection = { + { + .description = "DMA 1", + .value = 1 + }, + { + .description = "DMA 3", + .value = 3 + }, + { .description = "" } + } + }, { .name = "gameport", .description = "Enable Game port", diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index aaafd11fa..0a661f452 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -1198,6 +1198,148 @@ sb_pro_mcv_write(int port, uint8_t val, void *p) sb_dsp_setdma8(&sb->dsp, sb->pos_regs[4] & 3); } +static uint8_t +sb_16_reply_mca_read(int port, void *p) +{ + sb_t *sb = (sb_t *) p; + uint8_t ret = sb->pos_regs[port & 7]; + + sb_log("sb_16_reply_mca_read: port=%04x ret=%02x\n", port, ret); + + return ret; +} + +static void +sb_16_reply_mca_write(int port, uint8_t val, void *p) +{ + uint16_t addr, mpu401_addr; + int low_dma, high_dma; + sb_t *sb = (sb_t *) p; + + if (port < 0x102) + return; + + sb_log("sb_16_reply_mca_write: port=%04x val=%02x\n", port, val); + + switch (sb->pos_regs[2] & 0xc4) { + case 4: + addr = 0x220; + break; + case 0x44: + addr = 0x240; + break; + case 0x84: + addr = 0x260; + break; + case 0xc4: + addr = 0x280; + break; + case 0: + addr = 0; + break; + } + + if (addr) { + io_removehandler(addr, 0x0004, + opl3_read, NULL, NULL, + opl3_write, NULL, NULL, + &sb->opl); + io_removehandler(addr + 8, 0x0002, + opl3_read, NULL, NULL, + opl3_write, NULL, NULL, + &sb->opl); + io_removehandler(0x0388, 0x0004, + opl3_read, NULL, NULL, + opl3_write, NULL, NULL, + &sb->opl); + io_removehandler(addr + 4, 0x0002, + sb_ct1745_mixer_read, NULL, NULL, + sb_ct1745_mixer_write, NULL, NULL, + sb); + } + + /* DSP I/O handler is activated in sb_dsp_setaddr */ + sb_dsp_setaddr(&sb->dsp, 0); + mpu401_change_addr(sb->mpu, 0); + gameport_remap(sb->gameport, 0); + + sb->pos_regs[port & 7] = val; + + if (sb->pos_regs[2] & 1) { + switch (sb->pos_regs[2] & 0xc4) { + case 4: + addr = 0x220; + break; + case 0x44: + addr = 0x240; + break; + case 0x84: + addr = 0x260; + break; + case 0xc4: + addr = 0x280; + break; + case 0: + addr = 0; + break; + } + switch (sb->pos_regs[2] & 0x18) { + case 8: + mpu401_addr = 0x330; + break; + case 0x18: + mpu401_addr = 0x300; + break; + case 0: + mpu401_addr = 0; + break; + } + + if (addr) { + io_sethandler(addr, 0x0004, + opl3_read, NULL, NULL, + opl3_write, NULL, NULL, + &sb->opl); + io_sethandler(addr + 8, 0x0002, + opl3_read, NULL, NULL, + opl3_write, NULL, NULL, + &sb->opl); + io_sethandler(0x0388, 0x0004, + opl3_read, NULL, NULL, + opl3_write, NULL, NULL, &sb->opl); + io_sethandler(addr + 4, 0x0002, + sb_ct1745_mixer_read, NULL, NULL, + sb_ct1745_mixer_write, NULL, NULL, + sb); + } + + /* DSP I/O handler is activated in sb_dsp_setaddr */ + sb_dsp_setaddr(&sb->dsp, addr); + mpu401_change_addr(sb->mpu, mpu401_addr); + gameport_remap(sb->gameport, (sb->pos_regs[2] & 0x20) ? 0x200 : 0); + } + + switch (sb->pos_regs[4] & 0x60) { + case 0x20: + sb_dsp_setirq(&sb->dsp, 5); + break; + case 0x40: + sb_dsp_setirq(&sb->dsp, 7); + break; + case 0x60: + sb_dsp_setirq(&sb->dsp, 10); + break; + } + + low_dma = sb->pos_regs[3] & 3; + high_dma = (sb->pos_regs[3] >> 4) & 7; + if (!high_dma) + high_dma = low_dma; + + sb_dsp_setdma8(&sb->dsp, low_dma); + sb_dsp_setdma16(&sb->dsp, high_dma); +} + static void sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) { @@ -1785,6 +1927,41 @@ sb_16_init(const device_t *info) return sb; } +static void * +sb_16_reply_mca_init(const device_t *info) +{ + sb_t *sb = malloc(sizeof(sb_t)); + memset(sb, 0x00, sizeof(sb_t)); + + sb->opl_enabled = 1; + opl3_init(&sb->opl); + + sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); + sb_ct1745_mixer_reset(sb); + + sb->mixer_enabled = 1; + sb->mixer_sb16.output_filter = 1; + sound_add_handler(sb_get_buffer_sb16_awe32, sb); + sound_set_cd_audio_filter(sb16_awe32_filter_cd_audio, sb); + + sb->mpu = (mpu_t *) malloc(sizeof(mpu_t)); + memset(sb->mpu, 0, sizeof(mpu_t)); + mpu401_init(sb->mpu, 0, 0, M_UART, device_get_config_int("receive_input401")); + sb_dsp_set_mpu(&sb->dsp, sb->mpu); + + if (device_get_config_int("receive_input")) + midi_in_handler(1, sb_dsp_input_msg, sb_dsp_input_sysex, &sb->dsp); + + sb->gameport = gameport_add(&gameport_device); + + /* I/O handlers activated in sb_pro_mcv_write */ + mca_add(sb_16_reply_mca_read, sb_16_reply_mca_write, sb_mcv_feedb, NULL, sb); + sb->pos_regs[0] = 0x38; + sb->pos_regs[1] = 0x51; + + return sb; +} + static void * sb_16_pnp_init(const device_t *info) { @@ -3371,6 +3548,20 @@ const device_t sb_16_device = { .config = sb_16_config }; +const device_t sb_16_reply_mca_device = { + .name = "Sound Blaster 16 Reply MCA", + .internal_name = "sb16_reply_mca", + .flags = DEVICE_MCA, + .local = 0, + .init = sb_16_reply_mca_init, + .close = sb_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = sb_speed_changed, + .force_redraw = NULL, + .config = sb_16_pnp_config +}; + const device_t sb_16_pnp_device = { .name = "Sound Blaster 16 PnP", .internal_name = "sb16_pnp", diff --git a/src/sound/sound.c b/src/sound/sound.c index ec9e51f91..604dac38f 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -140,6 +140,7 @@ static const SOUND_CARD sound_cards[] = { { &ncr_business_audio_device }, { &sb_mcv_device }, { &sb_pro_mcv_device }, + { &sb_16_reply_mca_device }, { &cmi8338_device }, { &cmi8738_device }, { &es1371_device }, From 5292dcab322ba4e79fff12c0b8495559090fa232 Mon Sep 17 00:00:00 2001 From: OBattler Date: Sun, 24 Jul 2022 03:05:51 +0200 Subject: [PATCH 12/32] Warning fixes. --- src/cpu/x86_ops_misc.h | 4 ++-- src/sound/snd_sb.c | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/cpu/x86_ops_misc.h b/src/cpu/x86_ops_misc.h index 50da6f2a0..7fde5dacc 100644 --- a/src/cpu/x86_ops_misc.h +++ b/src/cpu/x86_ops_misc.h @@ -259,7 +259,7 @@ static int opF6_a32(uint32_t fetchdat) static int opF7_w_a16(uint32_t fetchdat) { - uint32_t templ, templ2; + uint32_t templ, templ2 = 0; int tempws, tempws2 = 0; int16_t temps16; uint16_t src, dst; @@ -356,7 +356,7 @@ static int opF7_w_a16(uint32_t fetchdat) } static int opF7_w_a32(uint32_t fetchdat) { - uint32_t templ, templ2; + uint32_t templ, templ2 = 0; int tempws, tempws2 = 1; int16_t temps16; uint16_t src, dst; diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index 0a661f452..1038567fa 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -1235,6 +1235,7 @@ sb_16_reply_mca_write(int port, uint8_t val, void *p) addr = 0x280; break; case 0: + default: addr = 0; break; } @@ -1280,6 +1281,7 @@ sb_16_reply_mca_write(int port, uint8_t val, void *p) addr = 0x280; break; case 0: + default: addr = 0; break; } @@ -1291,6 +1293,7 @@ sb_16_reply_mca_write(int port, uint8_t val, void *p) mpu401_addr = 0x300; break; case 0: + default: mpu401_addr = 0; break; } From b13bb3a2631b5cd77abfb9fad2f65cf417402690 Mon Sep 17 00:00:00 2001 From: OBattler Date: Sun, 24 Jul 2022 03:37:37 +0200 Subject: [PATCH 13/32] QT now uses the old Windows dynamic loading code when on Windows. --- src/qt/qt_platform.cpp | 62 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 91f76d900..9fdb2754d 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -431,6 +431,67 @@ void plat_language_code_r(uint32_t lcid, char* outbuf, int len) { return; } +#ifdef Q_OS_WINDOWS +#ifdef ENABLE_DYNLD_LOG +int dynld_do_log = ENABLE_DYNLD_LOG; + + +static void +dynld_log(const char *fmt, ...) +{ + va_list ap; + + if (dynld_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +#define dynld_log(fmt, ...) +#endif + + +void * +dynld_module(const char *name, dllimp_t *table) +{ + HMODULE h; + dllimp_t *imp; + void *func; + + /* See if we can load the desired module. */ + if ((h = LoadLibrary(name)) == NULL) { + dynld_log("DynLd(\"%s\"): library not found! (%08X)\n", name, GetLastError()); + return(NULL); + } + + /* Now load the desired function pointers. */ + for (imp=table; imp->name!=NULL; imp++) { + func = (void *) GetProcAddress(h, imp->name); + if (func == NULL) { + dynld_log("DynLd(\"%s\"): function '%s' not found! (%08X)\n", + name, imp->name, GetLastError()); + FreeLibrary(h); + return(NULL); + } + + /* To overcome typing issues.. */ + *(char **)imp->func = (char *)func; + } + + /* All good. */ + dynld_log("loaded %s\n", name); + return((void *)h); +} + + +void +dynld_close(void *handle) +{ + if (handle != NULL) + FreeLibrary((HMODULE)handle); +} +#else void* dynld_module(const char *name, dllimp_t *table) { QString libraryName = name; @@ -462,6 +523,7 @@ void dynld_close(void *handle) { delete reinterpret_cast(handle); } +#endif void startblit() { From c793fd71428614f4f26dc4a3d4c36aa5895d2199 Mon Sep 17 00:00:00 2001 From: OBattler Date: Sun, 24 Jul 2022 03:54:47 +0200 Subject: [PATCH 14/32] Proper separation. --- src/qt/CMakeLists.txt | 1 + src/qt/qt_platform.cpp | 62 +----------------------------- src/qt/win_dynld.c | 87 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 61 deletions(-) create mode 100644 src/qt/win_dynld.c diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index b9d0e40e9..0cb4560c4 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -178,6 +178,7 @@ endif() if(WIN32) enable_language(RC) target_sources(86Box PUBLIC ../win/86Box-qt.rc) + target_sources(plat PRIVATE win_dynld.c) target_sources(plat PRIVATE win_joystick_rawinput.c) target_sources(ui PRIVATE qt_d3d9renderer.hpp qt_d3d9renderer.cpp) target_link_libraries(86Box hid d3d9) diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index 9fdb2754d..eff022b3d 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -431,67 +431,7 @@ void plat_language_code_r(uint32_t lcid, char* outbuf, int len) { return; } -#ifdef Q_OS_WINDOWS -#ifdef ENABLE_DYNLD_LOG -int dynld_do_log = ENABLE_DYNLD_LOG; - - -static void -dynld_log(const char *fmt, ...) -{ - va_list ap; - - if (dynld_do_log) { - va_start(ap, fmt); - pclog_ex(fmt, ap); - va_end(ap); - } -} -#else -#define dynld_log(fmt, ...) -#endif - - -void * -dynld_module(const char *name, dllimp_t *table) -{ - HMODULE h; - dllimp_t *imp; - void *func; - - /* See if we can load the desired module. */ - if ((h = LoadLibrary(name)) == NULL) { - dynld_log("DynLd(\"%s\"): library not found! (%08X)\n", name, GetLastError()); - return(NULL); - } - - /* Now load the desired function pointers. */ - for (imp=table; imp->name!=NULL; imp++) { - func = (void *) GetProcAddress(h, imp->name); - if (func == NULL) { - dynld_log("DynLd(\"%s\"): function '%s' not found! (%08X)\n", - name, imp->name, GetLastError()); - FreeLibrary(h); - return(NULL); - } - - /* To overcome typing issues.. */ - *(char **)imp->func = (char *)func; - } - - /* All good. */ - dynld_log("loaded %s\n", name); - return((void *)h); -} - - -void -dynld_close(void *handle) -{ - if (handle != NULL) - FreeLibrary((HMODULE)handle); -} -#else +#ifndef Q_OS_WINDOWS void* dynld_module(const char *name, dllimp_t *table) { QString libraryName = name; diff --git a/src/qt/win_dynld.c b/src/qt/win_dynld.c new file mode 100644 index 000000000..98eb4739f --- /dev/null +++ b/src/qt/win_dynld.c @@ -0,0 +1,87 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Try to load a support DLL. + * + * + * + * Author: Fred N. van Kempen, + * + * Copyright 2017,2018 Fred N. van Kempen + */ +#include +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H +#include <86box/86box.h> +#include <86box/plat_dynld.h> + + +#ifdef ENABLE_DYNLD_LOG +int dynld_do_log = ENABLE_DYNLD_LOG; + + +static void +dynld_log(const char *fmt, ...) +{ + va_list ap; + + if (dynld_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +#define dynld_log(fmt, ...) +#endif + + +void * +dynld_module(const char *name, dllimp_t *table) +{ + HMODULE h; + dllimp_t *imp; + void *func; + + /* See if we can load the desired module. */ + if ((h = LoadLibrary(name)) == NULL) { + dynld_log("DynLd(\"%s\"): library not found! (%08X)\n", name, GetLastError()); + return(NULL); + } + + /* Now load the desired function pointers. */ + for (imp=table; imp->name!=NULL; imp++) { + func = GetProcAddress(h, imp->name); + if (func == NULL) { + dynld_log("DynLd(\"%s\"): function '%s' not found! (%08X)\n", + name, imp->name, GetLastError()); + FreeLibrary(h); + return(NULL); + } + + /* To overcome typing issues.. */ + *(char **)imp->func = (char *)func; + } + + /* All good. */ + dynld_log("loaded %s\n", name); + return((void *)h); +} + + +void +dynld_close(void *handle) +{ + if (handle != NULL) + FreeLibrary((HMODULE)handle); +} From 6f364f0395fc25e03905684fde0745ec0a26e108 Mon Sep 17 00:00:00 2001 From: OBattler Date: Sun, 24 Jul 2022 04:33:42 +0200 Subject: [PATCH 15/32] Fixed the entries for the DEC and Acer V10. --- src/machine/machine_table.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/machine/machine_table.c b/src/machine/machine_table.c index 2c4a3bba9..6ece57fdf 100644 --- a/src/machine/machine_table.c +++ b/src/machine/machine_table.c @@ -4721,7 +4721,7 @@ const machine_t machines[] = { .max_multi = 0 }, .bus_flags = MACHINE_PS2, - .flags = MACHINE_IDE_DUAL | MACHINE_VIDEO, + .flags = MACHINE_IDE_DUAL, /* No MACHINE_VIDEO yet, because on-board video is not yet implemented. */ .ram = { .min = 1024, .max = 32768, @@ -4756,7 +4756,7 @@ const machine_t machines[] = { .max_multi = 0 }, .bus_flags = MACHINE_PS2, - .flags = MACHINE_IDE_DUAL | MACHINE_VIDEO, + .flags = MACHINE_IDE_DUAL, .ram = { .min = 1024, .max = 32768, From af4c00d7088f7105a52a8715a1be60454ec8ebc8 Mon Sep 17 00:00:00 2001 From: richardg867 Date: Sun, 24 Jul 2022 00:18:26 -0300 Subject: [PATCH 16/32] Jenkins: Skip redundant tasks within the same build --- .ci/build.sh | 257 ++++++++++++++++++++++++++++++--------------------- 1 file changed, 150 insertions(+), 107 deletions(-) diff --git a/.ci/build.sh b/.ci/build.sh index 278e252d8..4c47f775f 100644 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -220,6 +220,8 @@ esac # Perform platform-specific setup. strip_binary=strip +cache_dir="$HOME/86box-build-cache" +[ ! -d "$cache_dir" ] && mkdir -p "$cache_dir" if is_windows then # Switch into the correct MSYSTEM if required. @@ -244,105 +246,120 @@ then fi echo [-] Using MSYSTEM [$MSYSTEM] - # Update keyring, as the package signing keys sometimes change. - echo [-] Updating package databases and keyring - yes | pacman -Sy --needed msys2-keyring + # Install dependencies only if we're in a new build and/or architecture. + freetype_dll="$cache_dir/freetype.$MSYSTEM.dll" + buildtag_file="$cache_dir/buildtag.$MSYSTEM" + if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_file" 2> /dev/null)" != "$BUILD_TAG" ] + then + # Update keyring, as the package signing keys sometimes change. + echo [-] Updating package databases and keyring + yes | pacman -Sy --needed msys2-keyring - # Query installed packages. - pacman -Qe > pacman.txt + # Query installed packages. + pacman -Qe > pacman.txt - # Download the specified versions of architecture-specific dependencies. - echo -n [-] Downloading dependencies: - pkg_dir="/var/cache/pacman/pkg" - repo_base="https://repo.msys2.org/mingw/$(echo $MSYSTEM | tr '[:upper:]' '[:lower:]')" - cat .ci/dependencies_msys.txt | tr -d '\r' > deps.txt - pkgs="" - while IFS=" " read pkg version - do - prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" - installed_version=$(grep -E "^$prefixed_pkg " pacman.txt | cut -d " " -f 2) - if [ "$installed_version" != "$version" ] # installed_version will be empty if not installed - then - echo -n " [$pkg" - - # Download package if not already present in the local cache. - pkg_tar="$prefixed_pkg-$version-any.pkg.tar" - if [ -s "$pkg_dir/$pkg_tar.xz" ] + # Download the specified versions of architecture-specific dependencies. + echo -n [-] Downloading dependencies: + pkg_dir="/var/cache/pacman/pkg" + repo_base="https://repo.msys2.org/mingw/$(echo $MSYSTEM | tr '[:upper:]' '[:lower:]')" + cat .ci/dependencies_msys.txt | tr -d '\r' > deps.txt + pkgs="" + while IFS=" " read pkg version + do + prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" + installed_version=$(grep -E "^$prefixed_pkg " pacman.txt | cut -d " " -f 2) + if [ "$installed_version" != "$version" ] # installed_version will be empty if not installed then - pkg_fn="$pkg_tar.xz" - pkg_dest="$pkg_dir/$pkg_fn" - else - pkg_fn="$pkg_tar.zst" - pkg_dest="$pkg_dir/$pkg_fn" - if [ ! -s "$pkg_dest" ] + echo -n " [$pkg" + + # Download package if not already present in the local cache. + pkg_tar="$prefixed_pkg-$version-any.pkg.tar" + if [ -s "$pkg_dir/$pkg_tar.xz" ] then - if ! wget -qO "$pkg_dest" "$repo_base/$pkg_fn" + pkg_fn="$pkg_tar.xz" + pkg_dest="$pkg_dir/$pkg_fn" + else + pkg_fn="$pkg_tar.zst" + pkg_dest="$pkg_dir/$pkg_fn" + if [ ! -s "$pkg_dest" ] then - rm -f "$pkg_dest" - pkg_fn="$pkg_tar.xz" - pkg_dest="$pkg_dir/$pkg_fn" - wget -qO "$pkg_dest" "$repo_base/$pkg_fn" || rm -f "$pkg_dest" - fi - if [ -s "$pkg_dest" ] - then - wget -qO "$pkg_dest.sig" "$repo_base/$pkg_fn.sig" || rm -f "$pkg_dest.sig" - [ ! -s "$pkg_dest.sig" ] && rm -f "$pkg_dest.sig" + if ! wget -qO "$pkg_dest" "$repo_base/$pkg_fn" + then + rm -f "$pkg_dest" + pkg_fn="$pkg_tar.xz" + pkg_dest="$pkg_dir/$pkg_fn" + wget -qO "$pkg_dest" "$repo_base/$pkg_fn" || rm -f "$pkg_dest" + fi + if [ -s "$pkg_dest" ] + then + wget -qO "$pkg_dest.sig" "$repo_base/$pkg_fn.sig" || rm -f "$pkg_dest.sig" + [ ! -s "$pkg_dest.sig" ] && rm -f "$pkg_dest.sig" + fi fi fi - fi - # Check if the cached package is valid. - if [ -s "$pkg_dest" ] + # Check if the cached package is valid. + if [ -s "$pkg_dest" ] + then + # Add cached zst package. + pkgs="$pkgs $pkg_fn" + else + # Not valid, remove if it exists. + rm -f "$pkg_dest" "$pkg_dest.sig" + echo -n " FAIL" + fi + echo -n "]" + fi + done < deps.txt + [ -z "$pkgs" ] && echo -n ' none required' + echo + + # Install the downloaded architecture-specific dependencies. + echo [-] Installing dependencies through pacman + if [ -n "$pkgs" ] + then + pushd "$pkg_dir" + yes | pacman -U --needed $pkgs + if [ $? -ne 0 ] then - # Add cached zst package. - pkgs="$pkgs $pkg_fn" - else - # Not valid, remove if it exists. - rm -f "$pkg_dest" "$pkg_dest.sig" - echo -n " FAIL" + # Install packages individually if installing them all together failed. + for pkg in $pkgs + do + yes | pacman -U --needed "$pkg" + done fi - echo -n "]" - fi - done < deps.txt - [ -z "$pkgs" ] && echo -n ' none required' - echo + popd - # Install the downloaded architecture-specific dependencies. - echo [-] Installing dependencies through pacman - if [ -n "$pkgs" ] - then - pushd "$pkg_dir" - yes | pacman -U --needed $pkgs + # Query installed packages again. + pacman -Qe > pacman.txt + fi + + # Install the latest versions for any missing packages (if the specified version couldn't be installed). + pkgs="git" + while IFS=" " read pkg version + do + prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" + grep -qE "^$prefixed_pkg " pacman.txt || pkgs="$pkgs $prefixed_pkg" + done < deps.txt + rm -f pacman.txt deps.txt + yes | pacman -S --needed $pkgs if [ $? -ne 0 ] then # Install packages individually if installing them all together failed. for pkg in $pkgs do - yes | pacman -U --needed "$pkg" + yes | pacman -S --needed "$pkg" done fi - popd - # Query installed packages again. - pacman -Qe > pacman.txt - fi + # Generate a new freetype DLL for this architecture. + rm -f "$freetype_dll" - # Install the latest versions for any missing packages (if the specified version couldn't be installed). - pkgs="git" - while IFS=" " read pkg version - do - prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" - grep -qE "^$prefixed_pkg " pacman.txt || pkgs="$pkgs $prefixed_pkg" - done < deps.txt - rm -f pacman.txt deps.txt - yes | pacman -S --needed $pkgs - if [ $? -ne 0 ] - then - # Install packages individually if installing them all together failed. - for pkg in $pkgs - do - yes | pacman -S --needed "$pkg" - done + # Save build tag to skip this later. Doing it here (once everything is + # in place) is important to avoid potential issues with retried builds. + echo "$BUILD_TAG" > "$buildtag_file" + else + echo [-] Not installing dependencies again fi # Point CMake to the toolchain file. @@ -352,7 +369,7 @@ then # macOS lacks nproc, but sysctl can do the same job. alias nproc='sysctl -n hw.logicalcpu' - # Handle universal build. + # Handle universal building. if echo "$arch" | grep -q '+' then # Create temporary directory for merging app bundles. @@ -526,10 +543,22 @@ then [ "$arch" = "x86_64" -a -e "/opt/intel/bin/port" ] && macports="/opt/intel" export PATH="$macports/bin:$macports/sbin:$macports/libexec/qt5/bin:$PATH" - # Install dependencies. - echo [-] Installing dependencies through MacPorts - sudo "$macports/bin/port" selfupdate - sudo "$macports/bin/port" install $(cat .ci/dependencies_macports.txt) + # Install dependencies only if we're in a new build and/or architecture. + buildtag_file="$cache_dir/buildtag.$(arch)" + if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_file" 2> /dev/null)" != "$BUILD_TAG" ] + then + # Install dependencies. + echo [-] Installing dependencies through MacPorts + sudo "$macports/bin/port" selfupdate + sudo "$macports/bin/port" install $(cat .ci/dependencies_macports.txt) + + # Save build tag to skip this later. Doing it here (once everything is + # in place) is important to avoid potential issues with retried builds. + echo "$BUILD_TAG" > "$buildtag_file" + else + echo [-] Not installing dependencies again + + fi # Point CMake to the toolchain file. [ -e "cmake/$toolchain.cmake" ] && cmake_flags_extra="$cmake_flags_extra -D \"CMAKE_TOOLCHAIN_FILE=cmake/$toolchain.cmake\"" @@ -606,11 +635,22 @@ EOF cmake_flags_extra="$cmake_flags_extra -D CMAKE_TOOLCHAIN_FILE=toolchain.cmake" strip_binary="$arch_triplet-strip" - # Install or update dependencies. - echo [-] Installing dependencies through apt - sudo apt-get update - DEBIAN_FRONTEND=noninteractive sudo apt-get -y install $pkgs $libpkgs - sudo apt-get clean + # Install dependencies only if we're in a new build and/or architecture. + buildtag_file="$cache_dir/buildtag.$arch_deb" + if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_file" 2> /dev/null)" != "$BUILD_TAG" ] + then + # Install or update dependencies. + echo [-] Installing dependencies through apt + sudo apt-get update + DEBIAN_FRONTEND=noninteractive sudo apt-get -y install $pkgs $libpkgs + sudo apt-get clean + + # Save build tag to skip this later. Doing it here (once everything is + # in place) is important to avoid potential issues with retried builds. + echo "$BUILD_TAG" > "$buildtag_file" + else + echo [-] Not installing dependencies again + fi # Link against the system libslirp instead of compiling ours. cmake_flags_extra="$cmake_flags_extra -D SLIRP_EXTERNAL=ON" @@ -618,12 +658,7 @@ fi # Clean workspace. echo [-] Cleaning workspace -if [ -d "build" ] -then - cmake --build build -j$(nproc) --target clean 2> /dev/null - rm -rf build -fi -find . \( -name Makefile -o -name CMakeCache.txt -o -name CMakeFiles \) -exec rm -rf "{}" \; 2> /dev/null +[ -d "build" ] && rm -rf build # Add ARCH to skip the arch_detect process. case $arch in @@ -671,17 +706,26 @@ then exit 4 fi -# Download Discord Game SDK from their CDN if necessary. -if [ ! -e "discord_game_sdk.zip" ] +# Download Discord Game SDK from their CDN if we're in a new build. +discord_zip="$cache_dir/discord_game_sdk.zip" +buildtag_file="$cache_dir/buildtag.any" +if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_file" 2> /dev/null)" != "$BUILD_TAG" ] then + # Download file. echo [-] Downloading Discord Game SDK - wget -qO discord_game_sdk.zip "https://dl-game-sdk.discordapp.net/latest/discord_game_sdk.zip" + wget -qO "$discord_zip" "https://dl-game-sdk.discordapp.net/latest/discord_game_sdk.zip" status=$? if [ $status -ne 0 ] then echo [!] Discord Game SDK download failed with status [$status] - rm -f discord_game_sdk.zip + rm -f "$discord_zip" + else + # Save build tag to skip this later. Doing it here (once everything is + # in place) is important to avoid potential issues with retried builds. + echo "$BUILD_TAG" > "$buildtag_file" fi +else + echo [-] Not downloading Discord Game SDK again fi # Determine Discord Game SDK architecture. @@ -713,8 +757,9 @@ then sevenzip="$pf/7-Zip/7z.exe" [ "$arch" = "32" -a -d "/c/Program Files (x86)" ] && pf="/c/Program Files (x86)" - # Archive freetype from local MSYS installation. - .ci/static2dll.sh -p freetype2 /$MSYSTEM/lib/libfreetype.a archive_tmp/freetype.dll + # Archive freetype from cache or generate it from local MSYS installation. + [ ! -e "$freetype_dll" ] && .ci/static2dll.sh -p freetype2 /$MSYSTEM/lib/libfreetype.a "$freetype_dll" + cp -p "$freetype_dll" archive_tmp/freetype.dll # Archive Ghostscript DLL from local official distribution installation. for gs in "$pf"/gs/gs*.*.* @@ -723,7 +768,7 @@ then done # Archive Discord Game SDK DLL. - "$sevenzip" e -y -o"archive_tmp" discord_game_sdk.zip "lib/$arch_discord/discord_game_sdk.dll" + "$sevenzip" e -y -o"archive_tmp" "$discord_zip" "lib/$arch_discord/discord_game_sdk.dll" [ ! -e "archive_tmp/discord_game_sdk.dll" ] && echo [!] No Discord Game SDK for architecture [$arch_discord] # Archive other DLLs from local directory. @@ -749,16 +794,14 @@ then if [ $status -eq 0 ] then # Archive Discord Game SDK library. - unzip -j discord_game_sdk.zip "lib/$arch_discord/discord_game_sdk.dylib" -d "archive_tmp/"*".app/Contents/Frameworks" + unzip -j "$discord_zip" "lib/$arch_discord/discord_game_sdk.dylib" -d "archive_tmp/"*".app/Contents/Frameworks" [ ! -e "archive_tmp/"*".app/Contents/Frameworks/discord_game_sdk.dylib" ] && echo [!] No Discord Game SDK for architecture [$arch_discord] # Sign app bundle, unless we're in an universal build. [ $skip_archive -eq 0 ] && codesign --force --deep -s - "archive_tmp/"*".app" fi else - cwd_root=$(pwd) - cache_dir="$HOME/86box-build-cache" - [ ! -d "$cache_dir" ] && mkdir -p "$cache_dir" + cwd_root="$(pwd)" if grep -q "OPENAL:BOOL=ON" build/CMakeCache.txt then @@ -842,7 +885,7 @@ else cmake --install "$cache_dir/sdlbuild" || exit 99 # Archive Discord Game SDK library. - 7z e -y -o"archive_tmp/usr/lib" discord_game_sdk.zip "lib/$arch_discord/discord_game_sdk.so" + 7z e -y -o"archive_tmp/usr/lib" "$discord_zip" "lib/$arch_discord/discord_game_sdk.so" [ ! -e "archive_tmp/usr/lib/discord_game_sdk.so" ] && echo [!] No Discord Game SDK for architecture [$arch_discord] # Archive readme with library package versions. From 8ceb5bf08146ed30a54c708ba144562a249f0827 Mon Sep 17 00:00:00 2001 From: OBattler Date: Sun, 24 Jul 2022 05:40:06 +0200 Subject: [PATCH 17/32] ROM, WD76C10, and Paradise fixes. --- src/chipset/wd76c10.c | 2 - src/mem/rom.c | 4 +- src/video/vid_paradise.c | 127 +++++++++++++++++++++------------------ 3 files changed, 71 insertions(+), 62 deletions(-) diff --git a/src/chipset/wd76c10.c b/src/chipset/wd76c10.c index 138349955..12b7e19a0 100644 --- a/src/chipset/wd76c10.c +++ b/src/chipset/wd76c10.c @@ -45,8 +45,6 @@ #define LOCK dev->lock #define UNLOCKED !dev->lock -#define ENABLE_WD76C10_LOG 1 - #ifdef ENABLE_WD76C10_LOG int wd76c10_do_log = ENABLE_WD76C10_LOG; static void diff --git a/src/mem/rom.c b/src/mem/rom.c index eb2b5791b..debdf5c39 100644 --- a/src/mem/rom.c +++ b/src/mem/rom.c @@ -646,7 +646,7 @@ rom_init_oddeven(rom_t *rom, char *fn, uint32_t addr, int sz, int mask, int off, addr, sz, rom_read, rom_readw, rom_readl, NULL, NULL, NULL, - rom->rom, flags | MEM_MAPPING_ROM, rom); + rom->rom, flags | MEM_MAPPING_ROM_WS, rom); return(0); } @@ -674,7 +674,7 @@ rom_init_interleaved(rom_t *rom, char *fnl, char *fnh, uint32_t addr, int sz, in addr, sz, rom_read, rom_readw, rom_readl, NULL, NULL, NULL, - rom->rom, flags | MEM_MAPPING_ROM, rom); + rom->rom, flags | MEM_MAPPING_ROM_WS, rom); return(0); } diff --git a/src/video/vid_paradise.c b/src/video/vid_paradise.c index 59b9f2247..3ca44746c 100644 --- a/src/video/vid_paradise.c +++ b/src/video/vid_paradise.c @@ -77,6 +77,64 @@ static video_timings_t timing_paradise_wd90c = {VIDEO_ISA, 3, 3, 6, 5, 5, 1 void paradise_remap(paradise_t *paradise); +uint8_t paradise_in(uint16_t addr, void *p) +{ + paradise_t *paradise = (paradise_t *)p; + svga_t *svga = ¶dise->svga; + + if (((addr & 0xfff0) == 0x3d0 || (addr & 0xfff0) == 0x3b0) && !(svga->miscout & 1)) + addr ^= 0x60; + + switch (addr) + { + case 0x3c5: + if (svga->seqaddr > 7) + { + if (paradise->type < WD90C11 || svga->seqregs[6] != 0x48) + return 0xff; + if (svga->seqaddr > 0x12) + return 0xff; + return svga->seqregs[svga->seqaddr & 0x1f]; + } + break; + + case 0x3c6: case 0x3c7: case 0x3c8: case 0x3c9: + if (paradise->type == WD90C30) + return sc1148x_ramdac_in(addr, 0, svga->ramdac, svga); + return svga_in(addr, svga); + + case 0x3cf: + if (svga->gdcaddr >= 9 && svga->gdcaddr <= 0x0e) { + if (svga->gdcreg[0x0f] & 0x10) + return 0xff; + } + switch (svga->gdcaddr) { + case 0x0b: + if (paradise->type == WD90C30) { + if (paradise->vram_mask == ((512 << 10) - 1)) { + svga->gdcreg[0x0b] |= 0xc0; + svga->gdcreg[0x0b] &= ~0x40; + } + } + return svga->gdcreg[0x0b]; + + case 0x0f: + return (svga->gdcreg[0x0f] & 0x17) | 0x80; + } + break; + + case 0x3D4: + return svga->crtcreg; + case 0x3D5: + if ((paradise->type == PVGA1A) && (svga->crtcreg & 0x20)) + return 0xff; + if (svga->crtcreg > 0x29 && svga->crtcreg < 0x30 && (svga->crtc[0x29] & 0x88) != 0x80) + return 0xff; + return svga->crtc[svga->crtcreg]; + } + return svga_in(addr, svga); +} + void paradise_out(uint16_t addr, uint8_t val, void *p) { paradise_t *paradise = (paradise_t *)p; @@ -187,69 +245,21 @@ void paradise_out(uint16_t addr, uint8_t val, void *p) } } break; + + case 0x46e8: + io_removehandler(0x03c0, 0x0020, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise); + mem_mapping_disable(¶dise->svga.mapping); + if (val & 8) + { + io_sethandler(0x03c0, 0x0020, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise); + mem_mapping_enable(¶dise->svga.mapping); + } + break; } svga_out(addr, val, svga); } -uint8_t paradise_in(uint16_t addr, void *p) -{ - paradise_t *paradise = (paradise_t *)p; - svga_t *svga = ¶dise->svga; - - if (((addr & 0xfff0) == 0x3d0 || (addr & 0xfff0) == 0x3b0) && !(svga->miscout & 1)) - addr ^= 0x60; - - switch (addr) - { - case 0x3c5: - if (svga->seqaddr > 7) - { - if (paradise->type < WD90C11 || svga->seqregs[6] != 0x48) - return 0xff; - if (svga->seqaddr > 0x12) - return 0xff; - return svga->seqregs[svga->seqaddr & 0x1f]; - } - break; - - case 0x3c6: case 0x3c7: case 0x3c8: case 0x3c9: - if (paradise->type == WD90C30) - return sc1148x_ramdac_in(addr, 0, svga->ramdac, svga); - return svga_in(addr, svga); - - case 0x3cf: - if (svga->gdcaddr >= 9 && svga->gdcaddr <= 0x0e) { - if (svga->gdcreg[0x0f] & 0x10) - return 0xff; - } - switch (svga->gdcaddr) { - case 0x0b: - if (paradise->type == WD90C30) { - if (paradise->vram_mask == ((512 << 10) - 1)) { - svga->gdcreg[0x0b] |= 0xc0; - svga->gdcreg[0x0b] &= ~0x40; - } - } - return svga->gdcreg[0x0b]; - - case 0x0f: - return (svga->gdcreg[0x0f] & 0x17) | 0x80; - } - break; - - case 0x3D4: - return svga->crtcreg; - case 0x3D5: - if ((paradise->type == PVGA1A) && (svga->crtcreg & 0x20)) - return 0xff; - if (svga->crtcreg > 0x29 && svga->crtcreg < 0x30 && (svga->crtc[0x29] & 0x88) != 0x80) - return 0xff; - return svga->crtc[svga->crtcreg]; - } - return svga_in(addr, svga); -} - void paradise_remap(paradise_t *paradise) { svga_t *svga = ¶dise->svga; @@ -579,6 +589,7 @@ void *paradise_init(const device_t *info, uint32_t memsize) case WD90C11: svga->crtc[0x36] = '1'; svga->crtc[0x37] = '1'; + io_sethandler(0x46e8, 0x0001, paradise_in, NULL, NULL, paradise_out, NULL, NULL, paradise); break; case WD90C30: svga->crtc[0x36] = '3'; From 85df0bf9a7509935e6c3fc9467e4f25b77265bde Mon Sep 17 00:00:00 2001 From: richardg867 Date: Sun, 24 Jul 2022 00:49:10 -0300 Subject: [PATCH 18/32] Jenkins: More build speed optimizations on the Linux side --- .ci/build.sh | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.ci/build.sh b/.ci/build.sh index 4c47f775f..594de8818 100644 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -572,12 +572,21 @@ else esac # Establish general dependencies. + buildtag_aptupdate_file="$cache_dir/buildtag.aptupdate" pkgs="cmake ninja-build pkg-config git wget p7zip-full wayland-protocols tar gzip file appstream" if [ "$(dpkg --print-architecture)" = "$arch_deb" ] then pkgs="$pkgs build-essential" else - sudo dpkg --add-architecture "$arch_deb" + # Add foreign architecture if required. + if ! dpkg --print-foreign-architectures | grep -qE '^'"$arch_deb"'$' + then + sudo dpkg --add-architecture "$arch_deb" + + # Force an apt-get update. + rm -f "$buildtag_aptupdate_file" + fi + pkgs="$pkgs crossbuild-essential-$arch_deb" fi @@ -641,7 +650,14 @@ EOF then # Install or update dependencies. echo [-] Installing dependencies through apt - sudo apt-get update + if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_aptupdate_file" 2> /dev/null)" != "$BUILD_TAG" ] + then + sudo apt-get update + + # Save build tag to skip apt-get update later, unless a new architecture + # is added to dpkg, in which case, this saved tag file gets removed. + echo "$BUILD_TAG" > "$buildtag_aptupdate_file" + fi DEBIAN_FRONTEND=noninteractive sudo apt-get -y install $pkgs $libpkgs sudo apt-get clean @@ -658,7 +674,7 @@ fi # Clean workspace. echo [-] Cleaning workspace -[ -d "build" ] && rm -rf build +rm -rf build # Add ARCH to skip the arch_detect process. case $arch in From 99f3cf8781ce4b6093dde6d22501649591b5b7e6 Mon Sep 17 00:00:00 2001 From: Adrien Moulin Date: Sun, 24 Jul 2022 10:01:36 +0200 Subject: [PATCH 19/32] Switch to C++ 14 to support ymfm --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f9e856faf..561e1eeda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,7 +118,7 @@ if(WIN32) endif() set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) # Optional features From b10cd69dca6b8725bfbdedee769a9389d95a4323 Mon Sep 17 00:00:00 2001 From: Adrien Moulin Date: Sun, 24 Jul 2022 10:09:12 +0200 Subject: [PATCH 20/32] Add ymfm library --- src/sound/ymfm/CMakeLists.txt | 1 + src/sound/ymfm/ymfm.h | 570 ++++++++ src/sound/ymfm/ymfm_adpcm.cpp | 807 +++++++++++ src/sound/ymfm/ymfm_adpcm.h | 411 ++++++ src/sound/ymfm/ymfm_fm.h | 463 ++++++ src/sound/ymfm/ymfm_fm.ipp | 1589 +++++++++++++++++++++ src/sound/ymfm/ymfm_misc.cpp | 175 +++ src/sound/ymfm/ymfm_misc.h | 93 ++ src/sound/ymfm/ymfm_opl.cpp | 2209 +++++++++++++++++++++++++++++ src/sound/ymfm/ymfm_opl.h | 902 ++++++++++++ src/sound/ymfm/ymfm_opm.cpp | 539 +++++++ src/sound/ymfm/ymfm_opm.h | 322 +++++ src/sound/ymfm/ymfm_opn.cpp | 2488 +++++++++++++++++++++++++++++++++ src/sound/ymfm/ymfm_opn.h | 802 +++++++++++ src/sound/ymfm/ymfm_opq.cpp | 480 +++++++ src/sound/ymfm/ymfm_opq.h | 293 ++++ src/sound/ymfm/ymfm_opx.h | 290 ++++ src/sound/ymfm/ymfm_opz.cpp | 808 +++++++++++ src/sound/ymfm/ymfm_opz.h | 332 +++++ src/sound/ymfm/ymfm_pcm.cpp | 715 ++++++++++ src/sound/ymfm/ymfm_pcm.h | 347 +++++ src/sound/ymfm/ymfm_ssg.cpp | 279 ++++ src/sound/ymfm/ymfm_ssg.h | 205 +++ 23 files changed, 15120 insertions(+) create mode 100644 src/sound/ymfm/CMakeLists.txt create mode 100644 src/sound/ymfm/ymfm.h create mode 100644 src/sound/ymfm/ymfm_adpcm.cpp create mode 100644 src/sound/ymfm/ymfm_adpcm.h create mode 100644 src/sound/ymfm/ymfm_fm.h create mode 100644 src/sound/ymfm/ymfm_fm.ipp create mode 100644 src/sound/ymfm/ymfm_misc.cpp create mode 100644 src/sound/ymfm/ymfm_misc.h create mode 100644 src/sound/ymfm/ymfm_opl.cpp create mode 100644 src/sound/ymfm/ymfm_opl.h create mode 100644 src/sound/ymfm/ymfm_opm.cpp create mode 100644 src/sound/ymfm/ymfm_opm.h create mode 100644 src/sound/ymfm/ymfm_opn.cpp create mode 100644 src/sound/ymfm/ymfm_opn.h create mode 100644 src/sound/ymfm/ymfm_opq.cpp create mode 100644 src/sound/ymfm/ymfm_opq.h create mode 100644 src/sound/ymfm/ymfm_opx.h create mode 100644 src/sound/ymfm/ymfm_opz.cpp create mode 100644 src/sound/ymfm/ymfm_opz.h create mode 100644 src/sound/ymfm/ymfm_pcm.cpp create mode 100644 src/sound/ymfm/ymfm_pcm.h create mode 100644 src/sound/ymfm/ymfm_ssg.cpp create mode 100644 src/sound/ymfm/ymfm_ssg.h diff --git a/src/sound/ymfm/CMakeLists.txt b/src/sound/ymfm/CMakeLists.txt new file mode 100644 index 000000000..da02f8132 --- /dev/null +++ b/src/sound/ymfm/CMakeLists.txt @@ -0,0 +1 @@ +add_library(ymfm STATIC ymfm_opl.cpp) \ No newline at end of file diff --git a/src/sound/ymfm/ymfm.h b/src/sound/ymfm/ymfm.h new file mode 100644 index 000000000..b983379b5 --- /dev/null +++ b/src/sound/ymfm/ymfm.h @@ -0,0 +1,570 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_H +#define YMFM_H + +#pragma once + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) + #define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include +#include +#include +#include +#include + +namespace ymfm +{ + +//********************************************************* +// DEBUGGING +//********************************************************* + +class debug +{ +public: + // masks to help isolate specific channels + static constexpr uint32_t GLOBAL_FM_CHANNEL_MASK = 0xffffffff; + static constexpr uint32_t GLOBAL_ADPCM_A_CHANNEL_MASK = 0xffffffff; + static constexpr uint32_t GLOBAL_ADPCM_B_CHANNEL_MASK = 0xffffffff; + static constexpr uint32_t GLOBAL_PCM_CHANNEL_MASK = 0xffffffff; + + // types of logging + static constexpr bool LOG_FM_WRITES = false; + static constexpr bool LOG_KEYON_EVENTS = false; + static constexpr bool LOG_UNEXPECTED_READ_WRITES = false; + + // helpers to write based on the log type + template static void log_fm_write(Params &&... args) { if (LOG_FM_WRITES) log(args...); } + template static void log_keyon(Params &&... args) { if (LOG_KEYON_EVENTS) log(args...); } + template static void log_unexpected_read_write(Params &&... args) { if (LOG_UNEXPECTED_READ_WRITES) log(args...); } + + // downstream helper to output log data; defaults to printf + template static void log(Params &&... args) { printf(args...); } +}; + + + +//********************************************************* +// GLOBAL HELPERS +//********************************************************* + +//------------------------------------------------- +// bitfield - extract a bitfield from the given +// value, starting at bit 'start' for a length of +// 'length' bits +//------------------------------------------------- + +inline uint32_t bitfield(uint32_t value, int start, int length = 1) +{ + return (value >> start) & ((1 << length) - 1); +} + + +//------------------------------------------------- +// clamp - clamp between the minimum and maximum +// values provided +//------------------------------------------------- + +inline int32_t clamp(int32_t value, int32_t minval, int32_t maxval) +{ + if (value < minval) + return minval; + if (value > maxval) + return maxval; + return value; +} + + +//------------------------------------------------- +// array_size - return the size of an array +//------------------------------------------------- + +template +constexpr uint32_t array_size(ArrayType (&array)[ArraySize]) +{ + return ArraySize; +} + + +//------------------------------------------------- +// count_leading_zeros - return the number of +// leading zeros in a 32-bit value; CPU-optimized +// versions for various architectures are included +// below +//------------------------------------------------- + +#if defined(__GNUC__) + +inline uint8_t count_leading_zeros(uint32_t value) +{ + if (value == 0) + return 32; + return __builtin_clz(value); +} + +#elif defined(_MSC_VER) + +inline uint8_t count_leading_zeros(uint32_t value) +{ + unsigned long index; + return _BitScanReverse(&index, value) ? uint8_t(31U - index) : 32U; +} + +#else + +inline uint8_t count_leading_zeros(uint32_t value) +{ + if (value == 0) + return 32; + uint8_t count; + for (count = 0; int32_t(value) >= 0; count++) + value <<= 1; + return count; +} + +#endif + + +// Many of the Yamaha FM chips emit a floating-point value, which is sent to +// a DAC for processing. The exact format of this floating-point value is +// documented below. This description only makes sense if the "internal" +// format treats sign as 1=positive and 0=negative, so the helpers below +// presume that. +// +// Internal OPx data 16-bit signed data Exp Sign Mantissa +// ================= ================= === ==== ======== +// 1 1xxxxxxxx------ -> 0 1xxxxxxxx------ -> 111 1 1xxxxxxx +// 1 01xxxxxxxx----- -> 0 01xxxxxxxx----- -> 110 1 1xxxxxxx +// 1 001xxxxxxxx---- -> 0 001xxxxxxxx---- -> 101 1 1xxxxxxx +// 1 0001xxxxxxxx--- -> 0 0001xxxxxxxx--- -> 100 1 1xxxxxxx +// 1 00001xxxxxxxx-- -> 0 00001xxxxxxxx-- -> 011 1 1xxxxxxx +// 1 000001xxxxxxxx- -> 0 000001xxxxxxxx- -> 010 1 1xxxxxxx +// 1 000000xxxxxxxxx -> 0 000000xxxxxxxxx -> 001 1 xxxxxxxx +// 0 111111xxxxxxxxx -> 1 111111xxxxxxxxx -> 001 0 xxxxxxxx +// 0 111110xxxxxxxx- -> 1 111110xxxxxxxx- -> 010 0 0xxxxxxx +// 0 11110xxxxxxxx-- -> 1 11110xxxxxxxx-- -> 011 0 0xxxxxxx +// 0 1110xxxxxxxx--- -> 1 1110xxxxxxxx--- -> 100 0 0xxxxxxx +// 0 110xxxxxxxx---- -> 1 110xxxxxxxx---- -> 101 0 0xxxxxxx +// 0 10xxxxxxxx----- -> 1 10xxxxxxxx----- -> 110 0 0xxxxxxx +// 0 0xxxxxxxx------ -> 1 0xxxxxxxx------ -> 111 0 0xxxxxxx + +//------------------------------------------------- +// encode_fp - given a 32-bit signed input value +// convert it to a signed 3.10 floating-point +// value +//------------------------------------------------- + +inline int16_t encode_fp(int32_t value) +{ + // handle overflows first + if (value < -32768) + return (7 << 10) | 0x000; + if (value > 32767) + return (7 << 10) | 0x3ff; + + // we need to count the number of leading sign bits after the sign + // we can use count_leading_zeros if we invert negative values + int32_t scanvalue = value ^ (int32_t(value) >> 31); + + // exponent is related to the number of leading bits starting from bit 14 + int exponent = 7 - count_leading_zeros(scanvalue << 17); + + // smallest exponent value allowed is 1 + exponent = std::max(exponent, 1); + + // mantissa + int32_t mantissa = value >> (exponent - 1); + + // assemble into final form, inverting the sign + return ((exponent << 10) | (mantissa & 0x3ff)) ^ 0x200; +} + + +//------------------------------------------------- +// decode_fp - given a 3.10 floating-point value, +// convert it to a signed 16-bit value +//------------------------------------------------- + +inline int16_t decode_fp(int16_t value) +{ + // invert the sign and the exponent + value ^= 0x1e00; + + // shift mantissa up to 16 bits then apply inverted exponent + return int16_t(value << 6) >> bitfield(value, 10, 3); +} + + +//------------------------------------------------- +// roundtrip_fp - compute the result of a round +// trip through the encode/decode process above +//------------------------------------------------- + +inline int16_t roundtrip_fp(int32_t value) +{ + // handle overflows first + if (value < -32768) + return -32768; + if (value > 32767) + return 32767; + + // we need to count the number of leading sign bits after the sign + // we can use count_leading_zeros if we invert negative values + int32_t scanvalue = value ^ (int32_t(value) >> 31); + + // exponent is related to the number of leading bits starting from bit 14 + int exponent = 7 - count_leading_zeros(scanvalue << 17); + + // smallest exponent value allowed is 1 + exponent = std::max(exponent, 1); + + // apply the shift back and forth to zero out bits that are lost + exponent -= 1; + return (value >> exponent) << exponent; +} + + + +//********************************************************* +// HELPER CLASSES +//********************************************************* + +// various envelope states +enum envelope_state : uint32_t +{ + EG_DEPRESS = 0, // OPLL only; set EG_HAS_DEPRESS to enable + EG_ATTACK = 1, + EG_DECAY = 2, + EG_SUSTAIN = 3, + EG_RELEASE = 4, + EG_REVERB = 5, // OPQ/OPZ only; set EG_HAS_REVERB to enable + EG_STATES = 6 +}; + +// external I/O access classes +enum access_class : uint32_t +{ + ACCESS_IO = 0, + ACCESS_ADPCM_A, + ACCESS_ADPCM_B, + ACCESS_PCM, + ACCESS_CLASSES +}; + + + +//********************************************************* +// HELPER CLASSES +//********************************************************* + +// ======================> ymfm_output + +// struct containing an array of output values +template +struct ymfm_output +{ + // clear all outputs to 0 + ymfm_output &clear() + { + for (uint32_t index = 0; index < NumOutputs; index++) + data[index] = 0; + return *this; + } + + // clamp all outputs to a 16-bit signed value + ymfm_output &clamp16() + { + for (uint32_t index = 0; index < NumOutputs; index++) + data[index] = clamp(data[index], -32768, 32767); + return *this; + } + + // run each output value through the floating-point processor + ymfm_output &roundtrip_fp() + { + for (uint32_t index = 0; index < NumOutputs; index++) + data[index] = ymfm::roundtrip_fp(data[index]); + return *this; + } + + // internal state + int32_t data[NumOutputs]; +}; + + +// ======================> ymfm_wavfile + +// this class is a debugging helper that accumulates data and writes it to wav files +template +class ymfm_wavfile +{ +public: + // construction + ymfm_wavfile(uint32_t samplerate = 44100) : + m_samplerate(samplerate) + { + } + + // configuration + ymfm_wavfile &set_index(uint32_t index) { m_index = index; return *this; } + ymfm_wavfile &set_samplerate(uint32_t samplerate) { m_samplerate = samplerate; return *this; } + + // destruction + ~ymfm_wavfile() + { + if (!m_buffer.empty()) + { + // create file + char name[20]; + sprintf(name, "wavlog-%02d.wav", m_index); + FILE *out = fopen(name, "wb"); + + // make the wav file header + uint8_t header[44]; + memcpy(&header[0], "RIFF", 4); + *(uint32_t *)&header[4] = m_buffer.size() * 2 + 44 - 8; + memcpy(&header[8], "WAVE", 4); + memcpy(&header[12], "fmt ", 4); + *(uint32_t *)&header[16] = 16; + *(uint16_t *)&header[20] = 1; + *(uint16_t *)&header[22] = _Channels; + *(uint32_t *)&header[24] = m_samplerate; + *(uint32_t *)&header[28] = m_samplerate * 2 * _Channels; + *(uint16_t *)&header[32] = 2 * _Channels; + *(uint16_t *)&header[34] = 16; + memcpy(&header[36], "data", 4); + *(uint32_t *)&header[40] = m_buffer.size() * 2 + 44 - 44; + + // write header then data + fwrite(&header[0], 1, sizeof(header), out); + fwrite(&m_buffer[0], 2, m_buffer.size(), out); + fclose(out); + } + } + + // add data to the file + template + void add(ymfm_output<_Outputs> output) + { + int16_t sum[_Channels] = { 0 }; + for (int index = 0; index < _Outputs; index++) + sum[index % _Channels] += output.data[index]; + for (int index = 0; index < _Channels; index++) + m_buffer.push_back(sum[index]); + } + + // add data to the file, using a reference + template + void add(ymfm_output<_Outputs> output, ymfm_output<_Outputs> const &ref) + { + int16_t sum[_Channels] = { 0 }; + for (int index = 0; index < _Outputs; index++) + sum[index % _Channels] += output.data[index] - ref.data[index]; + for (int index = 0; index < _Channels; index++) + m_buffer.push_back(sum[index]); + } + +private: + // internal state + uint32_t m_index; + uint32_t m_samplerate; + std::vector m_buffer; +}; + + +// ======================> ymfm_saved_state + +// this class contains a managed vector of bytes that is used to save and +// restore state +class ymfm_saved_state +{ +public: + // construction + ymfm_saved_state(std::vector &buffer, bool saving) : + m_buffer(buffer), + m_offset(saving ? -1 : 0) + { + if (saving) + buffer.resize(0); + } + + // are we saving or restoring? + bool saving() const { return (m_offset < 0); } + + // generic save/restore + template + void save_restore(DataType &data) + { + if (saving()) + save(data); + else + restore(data); + } + +public: + // save data to the buffer + void save(bool &data) { write(data ? 1 : 0); } + void save(int8_t &data) { write(data); } + void save(uint8_t &data) { write(data); } + void save(int16_t &data) { write(uint8_t(data)).write(data >> 8); } + void save(uint16_t &data) { write(uint8_t(data)).write(data >> 8); } + void save(int32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); } + void save(uint32_t &data) { write(data).write(data >> 8).write(data >> 16).write(data >> 24); } + void save(envelope_state &data) { write(uint8_t(data)); } + template + void save(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) save(data[index]); } + + // restore data from the buffer + void restore(bool &data) { data = read() ? true : false; } + void restore(int8_t &data) { data = read(); } + void restore(uint8_t &data) { data = read(); } + void restore(int16_t &data) { data = read(); data |= read() << 8; } + void restore(uint16_t &data) { data = read(); data |= read() << 8; } + void restore(int32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; } + void restore(uint32_t &data) { data = read(); data |= read() << 8; data |= read() << 16; data |= read() << 24; } + void restore(envelope_state &data) { data = envelope_state(read()); } + template + void restore(DataType (&data)[Count]) { for (uint32_t index = 0; index < Count; index++) restore(data[index]); } + + // internal helper + ymfm_saved_state &write(uint8_t data) { m_buffer.push_back(data); return *this; } + uint8_t read() { return (m_offset < int32_t(m_buffer.size())) ? m_buffer[m_offset++] : 0; } + + // internal state + std::vector &m_buffer; + int32_t m_offset; +}; + + + +//********************************************************* +// INTERFACE CLASSES +//********************************************************* + +// ======================> ymfm_engine_callbacks + +// this class represents functions in the engine that the ymfm_interface +// needs to be able to call; it is represented here as a separate interface +// that is independent of the actual engine implementation +class ymfm_engine_callbacks +{ +public: + // timer callback; called by the interface when a timer fires + virtual void engine_timer_expired(uint32_t tnum) = 0; + + // check interrupts; called by the interface after synchronization + virtual void engine_check_interrupts() = 0; + + // mode register write; called by the interface after synchronization + virtual void engine_mode_write(uint8_t data) = 0; +}; + + +// ======================> ymfm_interface + +// this class represents the interface between the fm_engine and the outside +// world; it provides hooks for timers, synchronization, and I/O +class ymfm_interface +{ + // the engine is our friend + template friend class fm_engine_base; + +public: + // the following functions must be implemented by any derived classes; the + // default implementations are sufficient for some minimal operation, but will + // likely need to be overridden to integrate with the outside world; they are + // all prefixed with ymfm_ to reduce the likelihood of namespace collisions + + // + // timing and synchronizaton + // + + // the chip implementation calls this when a write happens to the mode + // register, which could affect timers and interrupts; our responsibility + // is to ensure the system is up to date before calling the engine's + // engine_mode_write() method + virtual void ymfm_sync_mode_write(uint8_t data) { m_engine->engine_mode_write(data); } + + // the chip implementation calls this when the chip's status has changed, + // which may affect the interrupt state; our responsibility is to ensure + // the system is up to date before calling the engine's + // engine_check_interrupts() method + virtual void ymfm_sync_check_interrupts() { m_engine->engine_check_interrupts(); } + + // the chip implementation calls this when one of the two internal timers + // has changed state; our responsibility is to arrange to call the engine's + // engine_timer_expired() method after the provided number of clocks; if + // duration_in_clocks is negative, we should cancel any outstanding timers + virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) { } + + // the chip implementation calls this to indicate that the chip should be + // considered in a busy state until the given number of clocks has passed; + // our responsibility is to compute and remember the ending time based on + // the chip's clock for later checking + virtual void ymfm_set_busy_end(uint32_t clocks) { } + + // the chip implementation calls this to see if the chip is still currently + // is a busy state, as specified by a previous call to ymfm_set_busy_end(); + // our responsibility is to compare the current time against the previously + // noted busy end time and return true if we haven't yet passed it + virtual bool ymfm_is_busy() { return false; } + + // + // I/O functions + // + + // the chip implementation calls this when the state of the IRQ signal has + // changed due to a status change; our responsibility is to respond as + // needed to the change in IRQ state, signaling any consumers + virtual void ymfm_update_irq(bool asserted) { } + + // the chip implementation calls this whenever data is read from outside + // of the chip; our responsibility is to provide the data requested + virtual uint8_t ymfm_external_read(access_class type, uint32_t address) { return 0; } + + // the chip implementation calls this whenever data is written outside + // of the chip; our responsibility is to pass the written data on to any consumers + virtual void ymfm_external_write(access_class type, uint32_t address, uint8_t data) { } + +protected: + // pointer to engine callbacks -- this is set directly by the engine at + // construction time + ymfm_engine_callbacks *m_engine; +}; + +} + +#endif // YMFM_H diff --git a/src/sound/ymfm/ymfm_adpcm.cpp b/src/sound/ymfm/ymfm_adpcm.cpp new file mode 100644 index 000000000..4bc22beb2 --- /dev/null +++ b/src/sound/ymfm/ymfm_adpcm.cpp @@ -0,0 +1,807 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_adpcm.h" + +namespace ymfm +{ + +//********************************************************* +// ADPCM "A" REGISTERS +//********************************************************* + +//------------------------------------------------- +// reset - reset the register state +//------------------------------------------------- + +void adpcm_a_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // initialize the pans to on by default, and max instrument volume; + // some neogeo homebrews (for example ffeast) rely on this + m_regdata[0x08] = m_regdata[0x09] = m_regdata[0x0a] = + m_regdata[0x0b] = m_regdata[0x0c] = m_regdata[0x0d] = 0xdf; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_a_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_regdata); +} + + +//********************************************************* +// ADPCM "A" CHANNEL +//********************************************************* + +//------------------------------------------------- +// adpcm_a_channel - constructor +//------------------------------------------------- + +adpcm_a_channel::adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift) : + m_choffs(choffs), + m_address_shift(addrshift), + m_playing(0), + m_curnibble(0), + m_curbyte(0), + m_curaddress(0), + m_accumulator(0), + m_step_index(0), + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +void adpcm_a_channel::reset() +{ + m_playing = 0; + m_curnibble = 0; + m_curbyte = 0; + m_curaddress = 0; + m_accumulator = 0; + m_step_index = 0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_a_channel::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_playing); + state.save_restore(m_curnibble); + state.save_restore(m_curbyte); + state.save_restore(m_curaddress); + state.save_restore(m_accumulator); + state.save_restore(m_step_index); +} + + +//------------------------------------------------- +// keyonoff - signal key on/off +//------------------------------------------------- + +void adpcm_a_channel::keyonoff(bool on) +{ + // QUESTION: repeated key ons restart the sample? + m_playing = on; + if (m_playing) + { + m_curaddress = m_regs.ch_start(m_choffs) << m_address_shift; + m_curnibble = 0; + m_curbyte = 0; + m_accumulator = 0; + m_step_index = 0; + + // don't log masked channels + if (((debug::GLOBAL_ADPCM_A_CHANNEL_MASK >> m_choffs) & 1) != 0) + debug::log_keyon("KeyOn ADPCM-A%d: pan=%d%d start=%04X end=%04X level=%02X\n", + m_choffs, + m_regs.ch_pan_left(m_choffs), + m_regs.ch_pan_right(m_choffs), + m_regs.ch_start(m_choffs), + m_regs.ch_end(m_choffs), + m_regs.ch_instrument_level(m_choffs)); + } +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +bool adpcm_a_channel::clock() +{ + // if not playing, just output 0 + if (m_playing == 0) + { + m_accumulator = 0; + return false; + } + + // if we're about to read nibble 0, fetch the data + uint8_t data; + if (m_curnibble == 0) + { + // stop when we hit the end address; apparently only low 20 bits are used for + // comparison on the YM2610: this affects sample playback in some games, for + // example twinspri character select screen music will skip some samples if + // this is not correct + // + // note also: end address is inclusive, so wait until we are about to fetch + // the sample just after the end before stopping; this is needed for nitd's + // jump sound, for example + uint32_t end = (m_regs.ch_end(m_choffs) + 1) << m_address_shift; + if (((m_curaddress ^ end) & 0xfffff) == 0) + { + m_playing = m_accumulator = 0; + return true; + } + + m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_A, m_curaddress++); + data = m_curbyte >> 4; + m_curnibble = 1; + } + + // otherwise just extract from the previosuly-fetched byte + else + { + data = m_curbyte & 0xf; + m_curnibble = 0; + } + + // compute the ADPCM delta + static uint16_t const s_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 + }; + int32_t delta = (2 * bitfield(data, 0, 3) + 1) * s_steps[m_step_index] / 8; + if (bitfield(data, 3)) + delta = -delta; + + // the 12-bit accumulator wraps on the ym2610 and ym2608 (like the msm5205) + m_accumulator = (m_accumulator + delta) & 0xfff; + + // adjust ADPCM step + static int8_t const s_step_inc[8] = { -1, -1, -1, -1, 2, 5, 7, 9 }; + m_step_index = clamp(m_step_index + s_step_inc[bitfield(data, 0, 3)], 0, 48); + + return false; +} + + +//------------------------------------------------- +// output - return the computed output value, with +// panning applied +//------------------------------------------------- + +template +void adpcm_a_channel::output(ymfm_output &output) const +{ + // volume combines instrument and total levels + int vol = (m_regs.ch_instrument_level(m_choffs) ^ 0x1f) + (m_regs.total_level() ^ 0x3f); + + // if combined is maximum, don't add to outputs + if (vol >= 63) + return; + + // convert into a shift and a multiplier + // QUESTION: verify this from other sources + int8_t mul = 15 - (vol & 7); + uint8_t shift = 4 + 1 + (vol >> 3); + + // m_accumulator is a 12-bit value; shift up to sign-extend; + // the downshift is incorporated into 'shift' + int16_t value = ((int16_t(m_accumulator << 4) * mul) >> shift) & ~3; + + // apply to left/right as appropriate + if (NumOutputs == 1 || m_regs.ch_pan_left(m_choffs)) + output.data[0] += value; + if (NumOutputs > 1 && m_regs.ch_pan_right(m_choffs)) + output.data[1] += value; +} + + + +//********************************************************* +// ADPCM "A" ENGINE +//********************************************************* + +//------------------------------------------------- +// adpcm_a_engine - constructor +//------------------------------------------------- + +adpcm_a_engine::adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift) : + m_intf(intf) +{ + // create the channels + for (int chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum] = std::make_unique(*this, chnum, addrshift); +} + + +//------------------------------------------------- +// reset - reset the engine state +//------------------------------------------------- + +void adpcm_a_engine::reset() +{ + // reset register state + m_regs.reset(); + + // reset each channel + for (auto &chan : m_channel) + chan->reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_a_engine::save_restore(ymfm_saved_state &state) +{ + // save register state + m_regs.save_restore(state); + + // save channel state + for (int chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum]->save_restore(state); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +uint32_t adpcm_a_engine::clock(uint32_t chanmask) +{ + // clock each channel, setting a bit in result if it finished + uint32_t result = 0; + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + if (m_channel[chnum]->clock()) + result |= 1 << chnum; + + // return the bitmask of completed samples + return result; +} + + +//------------------------------------------------- +// update - master update function +//------------------------------------------------- + +template +void adpcm_a_engine::output(ymfm_output &output, uint32_t chanmask) +{ + // mask out some channels for debug purposes + chanmask &= debug::GLOBAL_ADPCM_A_CHANNEL_MASK; + + // compute the output of each channel + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + m_channel[chnum]->output(output); +} + +template void adpcm_a_engine::output<1>(ymfm_output<1> &output, uint32_t chanmask); +template void adpcm_a_engine::output<2>(ymfm_output<2> &output, uint32_t chanmask); + + +//------------------------------------------------- +// write - handle writes to the ADPCM-A registers +//------------------------------------------------- + +void adpcm_a_engine::write(uint32_t regnum, uint8_t data) +{ + // store the raw value to the register array; + // most writes are passive, consumed only when needed + m_regs.write(regnum, data); + + // actively handle writes to the control register + if (regnum == 0x00) + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(data, chnum)) + m_channel[chnum]->keyonoff(bitfield(~data, 7)); +} + + + +//********************************************************* +// ADPCM "B" REGISTERS +//********************************************************* + +//------------------------------------------------- +// reset - reset the register state +//------------------------------------------------- + +void adpcm_b_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // default limit to wide open + m_regdata[0x0c] = m_regdata[0x0d] = 0xff; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_b_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_regdata); +} + + + +//********************************************************* +// ADPCM "B" CHANNEL +//********************************************************* + +//------------------------------------------------- +// adpcm_b_channel - constructor +//------------------------------------------------- + +adpcm_b_channel::adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift) : + m_address_shift(addrshift), + m_status(STATUS_BRDY), + m_curnibble(0), + m_curbyte(0), + m_dummy_read(0), + m_position(0), + m_curaddress(0), + m_accumulator(0), + m_prev_accum(0), + m_adpcm_step(STEP_MIN), + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +void adpcm_b_channel::reset() +{ + m_status = STATUS_BRDY; + m_curnibble = 0; + m_curbyte = 0; + m_dummy_read = 0; + m_position = 0; + m_curaddress = 0; + m_accumulator = 0; + m_prev_accum = 0; + m_adpcm_step = STEP_MIN; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_b_channel::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_status); + state.save_restore(m_curnibble); + state.save_restore(m_curbyte); + state.save_restore(m_dummy_read); + state.save_restore(m_position); + state.save_restore(m_curaddress); + state.save_restore(m_accumulator); + state.save_restore(m_prev_accum); + state.save_restore(m_adpcm_step); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void adpcm_b_channel::clock() +{ + // only process if active and not recording (which we don't support) + if (!m_regs.execute() || m_regs.record() || (m_status & STATUS_PLAYING) == 0) + { + m_status &= ~STATUS_PLAYING; + return; + } + + // otherwise, advance the step + uint32_t position = m_position + m_regs.delta_n(); + m_position = uint16_t(position); + if (position < 0x10000) + return; + + // if we're about to process nibble 0, fetch sample + if (m_curnibble == 0) + { + // playing from RAM/ROM + if (m_regs.external()) + m_curbyte = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress); + } + + // extract the nibble from our current byte + uint8_t data = uint8_t(m_curbyte << (4 * m_curnibble)) >> 4; + m_curnibble ^= 1; + + // we just processed the last nibble + if (m_curnibble == 0) + { + // if playing from RAM/ROM, check the end/limit address or advance + if (m_regs.external()) + { + // handle the sample end, either repeating or stopping + if (at_end()) + { + // if repeating, go back to the start + if (m_regs.repeat()) + load_start(); + + // otherwise, done; set the EOS bit + else + { + m_accumulator = 0; + m_prev_accum = 0; + m_status = (m_status & ~STATUS_PLAYING) | STATUS_EOS; + debug::log_keyon("%s\n", "ADPCM EOS"); + return; + } + } + + // wrap at the limit address + else if (at_limit()) + m_curaddress = 0; + + // otherwise, advance the current address + else + { + m_curaddress++; + m_curaddress &= 0xffffff; + } + } + + // if CPU-driven, copy the next byte and request more + else + { + m_curbyte = m_regs.cpudata(); + m_status |= STATUS_BRDY; + } + } + + // remember previous value for interpolation + m_prev_accum = m_accumulator; + + // forecast to next forecast: 1/8, 3/8, 5/8, 7/8, 9/8, 11/8, 13/8, 15/8 + int32_t delta = (2 * bitfield(data, 0, 3) + 1) * m_adpcm_step / 8; + if (bitfield(data, 3)) + delta = -delta; + + // add and clamp to 16 bits + m_accumulator = clamp(m_accumulator + delta, -32768, 32767); + + // scale the ADPCM step: 0.9, 0.9, 0.9, 0.9, 1.2, 1.6, 2.0, 2.4 + static uint8_t const s_step_scale[8] = { 57, 57, 57, 57, 77, 102, 128, 153 }; + m_adpcm_step = clamp((m_adpcm_step * s_step_scale[bitfield(data, 0, 3)]) / 64, STEP_MIN, STEP_MAX); +} + + +//------------------------------------------------- +// output - return the computed output value, with +// panning applied +//------------------------------------------------- + +template +void adpcm_b_channel::output(ymfm_output &output, uint32_t rshift) const +{ + // mask out some channels for debug purposes + if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) == 0) + return; + + // do a linear interpolation between samples + int32_t result = (m_prev_accum * int32_t((m_position ^ 0xffff) + 1) + m_accumulator * int32_t(m_position)) >> 16; + + // apply volume (level) in a linear fashion and reduce + result = (result * int32_t(m_regs.level())) >> (8 + rshift); + + // apply to left/right + if (NumOutputs == 1 || m_regs.pan_left()) + output.data[0] += result; + if (NumOutputs > 1 && m_regs.pan_right()) + output.data[1] += result; +} + + +//------------------------------------------------- +// read - handle special register reads +//------------------------------------------------- + +uint8_t adpcm_b_channel::read(uint32_t regnum) +{ + uint8_t result = 0; + + // register 8 reads over the bus under some conditions + if (regnum == 0x08 && !m_regs.execute() && !m_regs.record() && m_regs.external()) + { + // two dummy reads are consumed first + if (m_dummy_read != 0) + { + load_start(); + m_dummy_read--; + } + + // read the data + else + { + // read from outside of the chip + result = m_owner.intf().ymfm_external_read(ACCESS_ADPCM_B, m_curaddress++); + + // did we hit the end? if so, signal EOS + if (at_end()) + { + m_status = STATUS_EOS | STATUS_BRDY; + debug::log_keyon("%s\n", "ADPCM EOS"); + } + else + { + // signal ready + m_status = STATUS_BRDY; + } + + // wrap at the limit address + if (at_limit()) + m_curaddress = 0; + } + } + return result; +} + + +//------------------------------------------------- +// write - handle special register writes +//------------------------------------------------- + +void adpcm_b_channel::write(uint32_t regnum, uint8_t value) +{ + // register 0 can do a reset; also use writes here to reset the + // dummy read counter + if (regnum == 0x00) + { + if (m_regs.execute()) + { + load_start(); + + // don't log masked channels + if ((debug::GLOBAL_ADPCM_B_CHANNEL_MASK & 1) != 0) + debug::log_keyon("KeyOn ADPCM-B: rep=%d spk=%d pan=%d%d dac=%d 8b=%d rom=%d ext=%d rec=%d start=%04X end=%04X pre=%04X dn=%04X lvl=%02X lim=%04X\n", + m_regs.repeat(), + m_regs.speaker(), + m_regs.pan_left(), + m_regs.pan_right(), + m_regs.dac_enable(), + m_regs.dram_8bit(), + m_regs.rom_ram(), + m_regs.external(), + m_regs.record(), + m_regs.start(), + m_regs.end(), + m_regs.prescale(), + m_regs.delta_n(), + m_regs.level(), + m_regs.limit()); + } + else + m_status &= ~STATUS_EOS; + if (m_regs.resetflag()) + reset(); + if (m_regs.external()) + m_dummy_read = 2; + } + + // register 8 writes over the bus under some conditions + else if (regnum == 0x08) + { + // if writing from the CPU during execute, clear the ready flag + if (m_regs.execute() && !m_regs.record() && !m_regs.external()) + m_status &= ~STATUS_BRDY; + + // if writing during "record", pass through as data + else if (!m_regs.execute() && m_regs.record() && m_regs.external()) + { + // clear out dummy reads and set start address + if (m_dummy_read != 0) + { + load_start(); + m_dummy_read = 0; + } + + // did we hit the end? if so, signal EOS + if (at_end()) + { + debug::log_keyon("%s\n", "ADPCM EOS"); + m_status = STATUS_EOS | STATUS_BRDY; + } + + // otherwise, write the data and signal ready + else + { + m_owner.intf().ymfm_external_write(ACCESS_ADPCM_B, m_curaddress++, value); + m_status = STATUS_BRDY; + } + } + } +} + + +//------------------------------------------------- +// address_shift - compute the current address +// shift amount based on register settings +//------------------------------------------------- + +uint32_t adpcm_b_channel::address_shift() const +{ + // if a constant address shift, just provide that + if (m_address_shift != 0) + return m_address_shift; + + // if ROM or 8-bit DRAM, shift is 5 bits + if (m_regs.rom_ram()) + return 5; + if (m_regs.dram_8bit()) + return 5; + + // otherwise, shift is 2 bits + return 2; +} + + +//------------------------------------------------- +// load_start - load the start address and +// initialize the state +//------------------------------------------------- + +void adpcm_b_channel::load_start() +{ + m_status = (m_status & ~STATUS_EOS) | STATUS_PLAYING; + m_curaddress = m_regs.external() ? (m_regs.start() << address_shift()) : 0; + m_curnibble = 0; + m_curbyte = 0; + m_position = 0; + m_accumulator = 0; + m_prev_accum = 0; + m_adpcm_step = STEP_MIN; +} + + + +//********************************************************* +// ADPCM "B" ENGINE +//********************************************************* + +//------------------------------------------------- +// adpcm_b_engine - constructor +//------------------------------------------------- + +adpcm_b_engine::adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift) : + m_intf(intf) +{ + // create the channel (only one supported for now, but leaving possibilities open) + m_channel = std::make_unique(*this, addrshift); +} + + +//------------------------------------------------- +// reset - reset the engine state +//------------------------------------------------- + +void adpcm_b_engine::reset() +{ + // reset registers + m_regs.reset(); + + // reset each channel + m_channel->reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void adpcm_b_engine::save_restore(ymfm_saved_state &state) +{ + // save our state + m_regs.save_restore(state); + + // save channel state + m_channel->save_restore(state); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void adpcm_b_engine::clock() +{ + // clock each channel, setting a bit in result if it finished + m_channel->clock(); +} + + +//------------------------------------------------- +// output - master output function +//------------------------------------------------- + +template +void adpcm_b_engine::output(ymfm_output &output, uint32_t rshift) +{ + // compute the output of each channel + m_channel->output(output, rshift); +} + +template void adpcm_b_engine::output<1>(ymfm_output<1> &output, uint32_t rshift); +template void adpcm_b_engine::output<2>(ymfm_output<2> &output, uint32_t rshift); + + +//------------------------------------------------- +// write - handle writes to the ADPCM-B registers +//------------------------------------------------- + +void adpcm_b_engine::write(uint32_t regnum, uint8_t data) +{ + // store the raw value to the register array; + // most writes are passive, consumed only when needed + m_regs.write(regnum, data); + + // let the channel handle any special writes + m_channel->write(regnum, data); +} + +} diff --git a/src/sound/ymfm/ymfm_adpcm.h b/src/sound/ymfm/ymfm_adpcm.h new file mode 100644 index 000000000..d74e24f27 --- /dev/null +++ b/src/sound/ymfm/ymfm_adpcm.h @@ -0,0 +1,411 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_ADPCM_H +#define YMFM_ADPCM_H + +#pragma once + +#include "ymfm.h" + +namespace ymfm +{ + +//********************************************************* +// INTERFACE CLASSES +//********************************************************* + +// forward declarations +class adpcm_a_engine; +class adpcm_b_engine; + + +// ======================> adpcm_a_registers + +// +// ADPCM-A register map: +// +// System-wide registers: +// 00 x------- Dump (disable=1) or keyon (0) control +// --xxxxxx Mask of channels to dump or keyon +// 01 --xxxxxx Total level +// 02 xxxxxxxx Test register +// 08-0D x------- Pan left +// -x------ Pan right +// ---xxxxx Instrument level +// 10-15 xxxxxxxx Start address (low) +// 18-1D xxxxxxxx Start address (high) +// 20-25 xxxxxxxx End address (low) +// 28-2D xxxxxxxx End address (high) +// +class adpcm_a_registers +{ +public: + // constants + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 6; + static constexpr uint32_t REGISTERS = 0x30; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + + // constructor + adpcm_a_registers() { } + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // direct read/write access + void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } + + // system-wide registers + uint32_t dump() const { return bitfield(m_regdata[0x00], 7); } + uint32_t dump_mask() const { return bitfield(m_regdata[0x00], 0, 6); } + uint32_t total_level() const { return bitfield(m_regdata[0x01], 0, 6); } + uint32_t test() const { return m_regdata[0x02]; } + + // per-channel registers + uint32_t ch_pan_left(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 7); } + uint32_t ch_pan_right(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 6); } + uint32_t ch_instrument_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x08], 0, 5); } + uint32_t ch_start(uint32_t choffs) const { return m_regdata[choffs + 0x10] | (m_regdata[choffs + 0x18] << 8); } + uint32_t ch_end(uint32_t choffs) const { return m_regdata[choffs + 0x20] | (m_regdata[choffs + 0x28] << 8); } + + // per-channel writes + void write_start(uint32_t choffs, uint32_t address) + { + write(choffs + 0x10, address); + write(choffs + 0x18, address >> 8); + } + void write_end(uint32_t choffs, uint32_t address) + { + write(choffs + 0x20, address); + write(choffs + 0x28, address >> 8); + } + +private: + // internal state + uint8_t m_regdata[REGISTERS]; // register data +}; + + +// ======================> adpcm_a_channel + +class adpcm_a_channel +{ +public: + // constructor + adpcm_a_channel(adpcm_a_engine &owner, uint32_t choffs, uint32_t addrshift); + + // reset the channel state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // signal key on/off + void keyonoff(bool on); + + // master clockingfunction + bool clock(); + + // return the computed output value, with panning applied + template + void output(ymfm_output &output) const; + +private: + // internal state + uint32_t const m_choffs; // channel offset + uint32_t const m_address_shift; // address bits shift-left + uint32_t m_playing; // currently playing? + uint32_t m_curnibble; // index of the current nibble + uint32_t m_curbyte; // current byte of data + uint32_t m_curaddress; // current address + int32_t m_accumulator; // accumulator + int32_t m_step_index; // index in the stepping table + adpcm_a_registers &m_regs; // reference to registers + adpcm_a_engine &m_owner; // reference to our owner +}; + + +// ======================> adpcm_a_engine + +class adpcm_a_engine +{ +public: + static constexpr int CHANNELS = adpcm_a_registers::CHANNELS; + + // constructor + adpcm_a_engine(ymfm_interface &intf, uint32_t addrshift); + + // reset our status + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // master clocking function + uint32_t clock(uint32_t chanmask); + + // compute sum of channel outputs + template + void output(ymfm_output &output, uint32_t chanmask); + + // write to the ADPCM-A registers + void write(uint32_t regnum, uint8_t data); + + // set the start/end address for a channel (for hardcoded YM2608 percussion) + void set_start_end(uint8_t chnum, uint16_t start, uint16_t end) + { + uint32_t choffs = adpcm_a_registers::channel_offset(chnum); + m_regs.write_start(choffs, start); + m_regs.write_end(choffs, end); + } + + // return a reference to our interface + ymfm_interface &intf() { return m_intf; } + + // return a reference to our registers + adpcm_a_registers ®s() { return m_regs; } + +private: + // internal state + ymfm_interface &m_intf; // reference to the interface + std::unique_ptr m_channel[CHANNELS]; // array of channels + adpcm_a_registers m_regs; // registers +}; + + +// ======================> adpcm_b_registers + +// +// ADPCM-B register map: +// +// System-wide registers: +// 00 x------- Start of synthesis/analysis +// -x------ Record +// --x----- External/manual driving +// ---x---- Repeat playback +// ----x--- Speaker off +// -------x Reset +// 01 x------- Pan left +// -x------ Pan right +// ----x--- Start conversion +// -----x-- DAC enable +// ------x- DRAM access (1=8-bit granularity; 0=1-bit) +// -------x RAM/ROM (1=ROM, 0=RAM) +// 02 xxxxxxxx Start address (low) +// 03 xxxxxxxx Start address (high) +// 04 xxxxxxxx End address (low) +// 05 xxxxxxxx End address (high) +// 06 xxxxxxxx Prescale value (low) +// 07 -----xxx Prescale value (high) +// 08 xxxxxxxx CPU data/buffer +// 09 xxxxxxxx Delta-N frequency scale (low) +// 0a xxxxxxxx Delta-N frequency scale (high) +// 0b xxxxxxxx Level control +// 0c xxxxxxxx Limit address (low) +// 0d xxxxxxxx Limit address (high) +// 0e xxxxxxxx DAC data [YM2608/10] +// 0f xxxxxxxx PCM data [YM2608/10] +// 0e xxxxxxxx DAC data high [Y8950] +// 0f xx------ DAC data low [Y8950] +// 10 -----xxx DAC data exponent [Y8950] +// +class adpcm_b_registers +{ +public: + // constants + static constexpr uint32_t REGISTERS = 0x11; + + // constructor + adpcm_b_registers() { } + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // direct read/write access + void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } + + // system-wide registers + uint32_t execute() const { return bitfield(m_regdata[0x00], 7); } + uint32_t record() const { return bitfield(m_regdata[0x00], 6); } + uint32_t external() const { return bitfield(m_regdata[0x00], 5); } + uint32_t repeat() const { return bitfield(m_regdata[0x00], 4); } + uint32_t speaker() const { return bitfield(m_regdata[0x00], 3); } + uint32_t resetflag() const { return bitfield(m_regdata[0x00], 0); } + uint32_t pan_left() const { return bitfield(m_regdata[0x01], 7); } + uint32_t pan_right() const { return bitfield(m_regdata[0x01], 6); } + uint32_t start_conversion() const { return bitfield(m_regdata[0x01], 3); } + uint32_t dac_enable() const { return bitfield(m_regdata[0x01], 2); } + uint32_t dram_8bit() const { return bitfield(m_regdata[0x01], 1); } + uint32_t rom_ram() const { return bitfield(m_regdata[0x01], 0); } + uint32_t start() const { return m_regdata[0x02] | (m_regdata[0x03] << 8); } + uint32_t end() const { return m_regdata[0x04] | (m_regdata[0x05] << 8); } + uint32_t prescale() const { return m_regdata[0x06] | (bitfield(m_regdata[0x07], 0, 3) << 8); } + uint32_t cpudata() const { return m_regdata[0x08]; } + uint32_t delta_n() const { return m_regdata[0x09] | (m_regdata[0x0a] << 8); } + uint32_t level() const { return m_regdata[0x0b]; } + uint32_t limit() const { return m_regdata[0x0c] | (m_regdata[0x0d] << 8); } + uint32_t dac() const { return m_regdata[0x0e]; } + uint32_t pcm() const { return m_regdata[0x0f]; } + +private: + // internal state + uint8_t m_regdata[REGISTERS]; // register data +}; + + +// ======================> adpcm_b_channel + +class adpcm_b_channel +{ + static constexpr int32_t STEP_MIN = 127; + static constexpr int32_t STEP_MAX = 24576; + +public: + static constexpr uint8_t STATUS_EOS = 0x01; + static constexpr uint8_t STATUS_BRDY = 0x02; + static constexpr uint8_t STATUS_PLAYING = 0x04; + + // constructor + adpcm_b_channel(adpcm_b_engine &owner, uint32_t addrshift); + + // reset the channel state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // signal key on/off + void keyonoff(bool on); + + // master clocking function + void clock(); + + // return the computed output value, with panning applied + template + void output(ymfm_output &output, uint32_t rshift) const; + + // return the status register + uint8_t status() const { return m_status; } + + // handle special register reads + uint8_t read(uint32_t regnum); + + // handle special register writes + void write(uint32_t regnum, uint8_t value); + +private: + // helper - return the current address shift + uint32_t address_shift() const; + + // load the start address + void load_start(); + + // limit checker; stops at the last byte of the chunk described by address_shift() + bool at_limit() const { return (m_curaddress == (((m_regs.limit() + 1) << address_shift()) - 1)); } + + // end checker; stops at the last byte of the chunk described by address_shift() + bool at_end() const { return (m_curaddress == (((m_regs.end() + 1) << address_shift()) - 1)); } + + // internal state + uint32_t const m_address_shift; // address bits shift-left + uint32_t m_status; // currently playing? + uint32_t m_curnibble; // index of the current nibble + uint32_t m_curbyte; // current byte of data + uint32_t m_dummy_read; // dummy read tracker + uint32_t m_position; // current fractional position + uint32_t m_curaddress; // current address + int32_t m_accumulator; // accumulator + int32_t m_prev_accum; // previous accumulator (for linear interp) + int32_t m_adpcm_step; // next forecast + adpcm_b_registers &m_regs; // reference to registers + adpcm_b_engine &m_owner; // reference to our owner +}; + + +// ======================> adpcm_b_engine + +class adpcm_b_engine +{ +public: + // constructor + adpcm_b_engine(ymfm_interface &intf, uint32_t addrshift = 0); + + // reset our status + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // master clocking function + void clock(); + + // compute sum of channel outputs + template + void output(ymfm_output &output, uint32_t rshift); + + // read from the ADPCM-B registers + uint32_t read(uint32_t regnum) { return m_channel->read(regnum); } + + // write to the ADPCM-B registers + void write(uint32_t regnum, uint8_t data); + + // status + uint8_t status() const { return m_channel->status(); } + + // return a reference to our interface + ymfm_interface &intf() { return m_intf; } + + // return a reference to our registers + adpcm_b_registers ®s() { return m_regs; } + +private: + // internal state + ymfm_interface &m_intf; // reference to our interface + std::unique_ptr m_channel; // channel pointer + adpcm_b_registers m_regs; // registers +}; + +} + +#endif // YMFM_ADPCM_H diff --git a/src/sound/ymfm/ymfm_fm.h b/src/sound/ymfm/ymfm_fm.h new file mode 100644 index 000000000..7c92c0f82 --- /dev/null +++ b/src/sound/ymfm/ymfm_fm.h @@ -0,0 +1,463 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_FM_H +#define YMFM_FM_H + +#pragma once + +#define DEBUG_LOG_WAVFILES (0) + +namespace ymfm +{ + +//********************************************************* +// GLOBAL ENUMERATORS +//********************************************************* + +// three different keyon sources; actual keyon is an OR over all of these +enum keyon_type : uint32_t +{ + KEYON_NORMAL = 0, + KEYON_RHYTHM = 1, + KEYON_CSM = 2 +}; + + + +//********************************************************* +// CORE IMPLEMENTATION +//********************************************************* + +// ======================> opdata_cache + +// this class holds data that is computed once at the start of clocking +// and remains static during subsequent sound generation +struct opdata_cache +{ + // set phase_step to this value to recalculate it each sample; needed + // in the case of PM LFO changes + static constexpr uint32_t PHASE_STEP_DYNAMIC = 1; + + uint16_t const *waveform; // base of sine table + uint32_t phase_step; // phase step, or PHASE_STEP_DYNAMIC if PM is active + uint32_t total_level; // total level * 8 + KSL + uint32_t block_freq; // raw block frequency value (used to compute phase_step) + int32_t detune; // detuning value (used to compute phase_step) + uint32_t multiple; // multiple value (x.1, used to compute phase_step) + uint32_t eg_sustain; // sustain level, shifted up to envelope values + uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR + uint8_t eg_shift = 0; // envelope shift amount +}; + + +// ======================> fm_registers_base + +// base class for family-specific register classes; this provides a few +// constants, common defaults, and helpers, but mostly each derived class is +// responsible for defining all commonly-called methods +class fm_registers_base +{ +public: + // this value is returned from the write() function for rhythm channels + static constexpr uint32_t RHYTHM_CHANNEL = 0xff; + + // this is the size of a full sin waveform + static constexpr uint32_t WAVEFORM_LENGTH = 0x400; + + // + // the following constants need to be defined per family: + // uint32_t OUTPUTS: The number of outputs exposed (1-4) + // uint32_t CHANNELS: The number of channels on the chip + // uint32_t ALL_CHANNELS: A bitmask of all channels + // uint32_t OPERATORS: The number of operators on the chip + // uint32_t WAVEFORMS: The number of waveforms offered + // uint32_t REGISTERS: The number of 8-bit registers allocated + // uint32_t DEFAULT_PRESCALE: The starting clock prescale + // uint32_t EG_CLOCK_DIVIDER: The clock divider of the envelope generator + // uint32_t CSM_TRIGGER_MASK: Mask of channels to trigger in CSM mode + // uint32_t REG_MODE: The address of the "mode" register controlling timers + // uint8_t STATUS_TIMERA: Status bit to set when timer A fires + // uint8_t STATUS_TIMERB: Status bit to set when tiemr B fires + // uint8_t STATUS_BUSY: Status bit to set when the chip is busy + // uint8_t STATUS_IRQ: Status bit to set when an IRQ is signalled + // + // the following constants are uncommon: + // bool DYNAMIC_OPS: True if ops/channel can be changed at runtime (OPL3+) + // bool EG_HAS_DEPRESS: True if the chip has a DP ("depress"?) envelope stage (OPLL) + // bool EG_HAS_REVERB: True if the chip has a faux reverb envelope stage (OPQ/OPZ) + // bool EG_HAS_SSG: True if the chip has SSG envelope support (OPN) + // bool MODULATOR_DELAY: True if the modulator is delayed by 1 sample (OPL pre-OPL3) + // + static constexpr bool DYNAMIC_OPS = false; + static constexpr bool EG_HAS_DEPRESS = false; + static constexpr bool EG_HAS_REVERB = false; + static constexpr bool EG_HAS_SSG = false; + static constexpr bool MODULATOR_DELAY = false; + + // system-wide register defaults + uint32_t status_mask() const { return 0; } // OPL only + uint32_t irq_reset() const { return 0; } // OPL only + uint32_t noise_enable() const { return 0; } // OPM only + uint32_t rhythm_enable() const { return 0; } // OPL only + + // per-operator register defaults + uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return 0; } // OPN(A) only + uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return 0; } // OPN(A) only + +protected: + // helper to encode four operator numbers into a 32-bit value in the + // operator maps for each register class + static constexpr uint32_t operator_list(uint8_t o1 = 0xff, uint8_t o2 = 0xff, uint8_t o3 = 0xff, uint8_t o4 = 0xff) + { + return o1 | (o2 << 8) | (o3 << 16) | (o4 << 24); + } + + // helper to apply KSR to the raw ADSR rate, ignoring ksr if the + // raw value is 0, and clamping to 63 + static constexpr uint32_t effective_rate(uint32_t rawrate, uint32_t ksr) + { + return (rawrate == 0) ? 0 : std::min(rawrate + ksr, 63); + } +}; + + + +//********************************************************* +// CORE ENGINE CLASSES +//********************************************************* + +// forward declarations +template class fm_engine_base; + +// ======================> fm_operator + +// fm_operator represents an FM operator (or "slot" in FM parlance), which +// produces an output sine wave modulated by an envelope +template +class fm_operator +{ + // "quiet" value, used to optimize when we can skip doing work + static constexpr uint32_t EG_QUIET = 0x380; + +public: + // constructor + fm_operator(fm_engine_base &owner, uint32_t opoffs); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset the operator state + void reset(); + + // return the operator/channel offset + uint32_t opoffs() const { return m_opoffs; } + uint32_t choffs() const { return m_choffs; } + + // set the current channel + void set_choffs(uint32_t choffs) { m_choffs = choffs; } + + // prepare prior to clocking + bool prepare(); + + // master clocking function + void clock(uint32_t env_counter, int32_t lfo_raw_pm); + + // return the current phase value + uint32_t phase() const { return m_phase >> 10; } + + // compute operator volume + int32_t compute_volume(uint32_t phase, uint32_t am_offset) const; + + // compute volume for the OPM noise channel + int32_t compute_noise_volume(uint32_t am_offset) const; + + // key state control + void keyonoff(uint32_t on, keyon_type type); + + // return a reference to our registers + RegisterType ®s() const { return m_regs; } + + // simple getters for debugging + envelope_state debug_eg_state() const { return m_env_state; } + uint16_t debug_eg_attenuation() const { return m_env_attenuation; } + uint8_t debug_ssg_inverted() const { return m_ssg_inverted; } + opdata_cache &debug_cache() { return m_cache; } + +private: + // start the attack phase + void start_attack(bool is_restart = false); + + // start the release phase + void start_release(); + + // clock phases + void clock_keystate(uint32_t keystate); + void clock_ssg_eg_state(); + void clock_envelope(uint32_t env_counter); + void clock_phase(int32_t lfo_raw_pm); + + // return effective attenuation of the envelope + uint32_t envelope_attenuation(uint32_t am_offset) const; + + // internal state + uint32_t m_choffs; // channel offset in registers + uint32_t m_opoffs; // operator offset in registers + uint32_t m_phase; // current phase value (10.10 format) + uint16_t m_env_attenuation; // computed envelope attenuation (4.6 format) + envelope_state m_env_state; // current envelope state + uint8_t m_ssg_inverted; // non-zero if the output should be inverted (bit 0) + uint8_t m_key_state; // current key state: on or off (bit 0) + uint8_t m_keyon_live; // live key on state (bit 0 = direct, bit 1 = rhythm, bit 2 = CSM) + opdata_cache m_cache; // cached values for performance + RegisterType &m_regs; // direct reference to registers + fm_engine_base &m_owner; // reference to the owning engine +}; + + +// ======================> fm_channel + +// fm_channel represents an FM channel which combines the output of 2 or 4 +// operators into a final result +template +class fm_channel +{ + using output_data = ymfm_output; + +public: + // constructor + fm_channel(fm_engine_base &owner, uint32_t choffs); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset the channel state + void reset(); + + // return the channel offset + uint32_t choffs() const { return m_choffs; } + + // assign operators + void assign(uint32_t index, fm_operator *op) + { + assert(index < array_size(m_op)); + m_op[index] = op; + if (op != nullptr) + op->set_choffs(m_choffs); + } + + // signal key on/off to our operators + void keyonoff(uint32_t states, keyon_type type, uint32_t chnum); + + // prepare prior to clocking + bool prepare(); + + // master clocking function + void clock(uint32_t env_counter, int32_t lfo_raw_pm); + + // specific 2-operator and 4-operator output handlers + void output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const; + void output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const; + + // compute the special OPL rhythm channel outputs + void output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const; + void output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const; + void output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const; + + // are we a 4-operator channel or a 2-operator one? + bool is4op() const + { + if (RegisterType::DYNAMIC_OPS) + return (m_op[2] != nullptr); + return (RegisterType::OPERATORS / RegisterType::CHANNELS == 4); + } + + // return a reference to our registers + RegisterType ®s() const { return m_regs; } + + // simple getters for debugging + fm_operator *debug_operator(uint32_t index) const { return m_op[index]; } + +private: + // helper to add values to the outputs based on channel enables + void add_to_output(uint32_t choffs, output_data &output, int32_t value) const + { + // create these constants to appease overzealous compilers checking array + // bounds in unreachable code (looking at you, clang) + constexpr int out0_index = 0; + constexpr int out1_index = 1 % RegisterType::OUTPUTS; + constexpr int out2_index = 2 % RegisterType::OUTPUTS; + constexpr int out3_index = 3 % RegisterType::OUTPUTS; + + if (RegisterType::OUTPUTS == 1 || m_regs.ch_output_0(choffs)) + output.data[out0_index] += value; + if (RegisterType::OUTPUTS >= 2 && m_regs.ch_output_1(choffs)) + output.data[out1_index] += value; + if (RegisterType::OUTPUTS >= 3 && m_regs.ch_output_2(choffs)) + output.data[out2_index] += value; + if (RegisterType::OUTPUTS >= 4 && m_regs.ch_output_3(choffs)) + output.data[out3_index] += value; + } + + // internal state + uint32_t m_choffs; // channel offset in registers + int16_t m_feedback[2]; // feedback memory for operator 1 + mutable int16_t m_feedback_in; // next input value for op 1 feedback (set in output) + fm_operator *m_op[4]; // up to 4 operators + RegisterType &m_regs; // direct reference to registers + fm_engine_base &m_owner; // reference to the owning engine +}; + + +// ======================> fm_engine_base + +// fm_engine_base represents a set of operators and channels which together +// form a Yamaha FM core; chips that implement other engines (ADPCM, wavetable, +// etc) take this output and combine it with the others externally +template +class fm_engine_base : public ymfm_engine_callbacks +{ +public: + // expose some constants from the registers + static constexpr uint32_t OUTPUTS = RegisterType::OUTPUTS; + static constexpr uint32_t CHANNELS = RegisterType::CHANNELS; + static constexpr uint32_t ALL_CHANNELS = RegisterType::ALL_CHANNELS; + static constexpr uint32_t OPERATORS = RegisterType::OPERATORS; + + // also expose status flags for consumers that inject additional bits + static constexpr uint8_t STATUS_TIMERA = RegisterType::STATUS_TIMERA; + static constexpr uint8_t STATUS_TIMERB = RegisterType::STATUS_TIMERB; + static constexpr uint8_t STATUS_BUSY = RegisterType::STATUS_BUSY; + static constexpr uint8_t STATUS_IRQ = RegisterType::STATUS_IRQ; + + // expose the correct output class + using output_data = ymfm_output; + + // constructor + fm_engine_base(ymfm_interface &intf); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset the overall state + void reset(); + + // master clocking function + uint32_t clock(uint32_t chanmask); + + // compute sum of channel outputs + void output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const; + + // write to the OPN registers + void write(uint16_t regnum, uint8_t data); + + // return the current status + uint8_t status() const; + + // set/reset bits in the status register, updating the IRQ status + uint8_t set_reset_status(uint8_t set, uint8_t reset) + { + m_status = (m_status | set) & ~(reset | STATUS_BUSY); + m_intf.ymfm_sync_check_interrupts(); + return m_status & ~m_regs.status_mask(); + } + + // set the IRQ mask + void set_irq_mask(uint8_t mask) { m_irq_mask = mask; m_intf.ymfm_sync_check_interrupts(); } + + // return the current clock prescale + uint32_t clock_prescale() const { return m_clock_prescale; } + + // set prescale factor (2/3/6) + void set_clock_prescale(uint32_t prescale) { m_clock_prescale = prescale; } + + // compute sample rate + uint32_t sample_rate(uint32_t baseclock) const + { +#if (DEBUG_LOG_WAVFILES) + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + m_wavfile[chnum].set_samplerate(baseclock / (m_clock_prescale * OPERATORS)); +#endif + return baseclock / (m_clock_prescale * OPERATORS); + } + + // return the owning device + ymfm_interface &intf() const { return m_intf; } + + // return a reference to our registers + RegisterType ®s() { return m_regs; } + + // invalidate any caches + void invalidate_caches() { m_modified_channels = RegisterType::ALL_CHANNELS; } + + // simple getters for debugging + fm_channel *debug_channel(uint32_t index) const { return m_channel[index].get(); } + fm_operator *debug_operator(uint32_t index) const { return m_operator[index].get(); } + +public: + // timer callback; called by the interface when a timer fires + virtual void engine_timer_expired(uint32_t tnum) override; + + // check interrupts; called by the interface after synchronization + virtual void engine_check_interrupts() override; + + // mode register write; called by the interface after synchronization + virtual void engine_mode_write(uint8_t data) override; + +protected: + // assign the current set of operators to channels + void assign_operators(); + + // update the state of the given timer + void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks); + + // internal state + ymfm_interface &m_intf; // reference to the system interface + uint32_t m_env_counter; // envelope counter; low 2 bits are sub-counter + uint8_t m_status; // current status register + uint8_t m_clock_prescale; // prescale factor (2/3/6) + uint8_t m_irq_mask; // mask of which bits signal IRQs + uint8_t m_irq_state; // current IRQ state + uint8_t m_timer_running[2]; // current timer running state + uint8_t m_total_clocks; // low 8 bits of the total number of clocks processed + uint32_t m_active_channels; // mask of active channels (computed by prepare) + uint32_t m_modified_channels; // mask of channels that have been modified + uint32_t m_prepare_count; // counter to do periodic prepare sweeps + RegisterType m_regs; // register accessor + std::unique_ptr> m_channel[CHANNELS]; // channel pointers + std::unique_ptr> m_operator[OPERATORS]; // operator pointers +#if (DEBUG_LOG_WAVFILES) + mutable ymfm_wavfile<1> m_wavfile[CHANNELS]; // for debugging +#endif +}; + +} + +#endif // YMFM_FM_H diff --git a/src/sound/ymfm/ymfm_fm.ipp b/src/sound/ymfm/ymfm_fm.ipp new file mode 100644 index 000000000..17bbc9150 --- /dev/null +++ b/src/sound/ymfm/ymfm_fm.ipp @@ -0,0 +1,1589 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +namespace ymfm +{ + +//********************************************************* +// GLOBAL TABLE LOOKUPS +//********************************************************* + +//------------------------------------------------- +// abs_sin_attenuation - given a sin (phase) input +// where the range 0-2*PI is mapped onto 10 bits, +// return the absolute value of sin(input), +// logarithmically-adjusted and treated as an +// attenuation value, in 4.8 fixed point format +//------------------------------------------------- + +inline uint32_t abs_sin_attenuation(uint32_t input) +{ + // the values here are stored as 4.8 logarithmic values for 1/4 phase + // this matches the internal format of the OPN chip, extracted from the die + static uint16_t const s_sin_table[256] = + { + 0x859,0x6c3,0x607,0x58b,0x52e,0x4e4,0x4a6,0x471,0x443,0x41a,0x3f5,0x3d3,0x3b5,0x398,0x37e,0x365, + 0x34e,0x339,0x324,0x311,0x2ff,0x2ed,0x2dc,0x2cd,0x2bd,0x2af,0x2a0,0x293,0x286,0x279,0x26d,0x261, + 0x256,0x24b,0x240,0x236,0x22c,0x222,0x218,0x20f,0x206,0x1fd,0x1f5,0x1ec,0x1e4,0x1dc,0x1d4,0x1cd, + 0x1c5,0x1be,0x1b7,0x1b0,0x1a9,0x1a2,0x19b,0x195,0x18f,0x188,0x182,0x17c,0x177,0x171,0x16b,0x166, + 0x160,0x15b,0x155,0x150,0x14b,0x146,0x141,0x13c,0x137,0x133,0x12e,0x129,0x125,0x121,0x11c,0x118, + 0x114,0x10f,0x10b,0x107,0x103,0x0ff,0x0fb,0x0f8,0x0f4,0x0f0,0x0ec,0x0e9,0x0e5,0x0e2,0x0de,0x0db, + 0x0d7,0x0d4,0x0d1,0x0cd,0x0ca,0x0c7,0x0c4,0x0c1,0x0be,0x0bb,0x0b8,0x0b5,0x0b2,0x0af,0x0ac,0x0a9, + 0x0a7,0x0a4,0x0a1,0x09f,0x09c,0x099,0x097,0x094,0x092,0x08f,0x08d,0x08a,0x088,0x086,0x083,0x081, + 0x07f,0x07d,0x07a,0x078,0x076,0x074,0x072,0x070,0x06e,0x06c,0x06a,0x068,0x066,0x064,0x062,0x060, + 0x05e,0x05c,0x05b,0x059,0x057,0x055,0x053,0x052,0x050,0x04e,0x04d,0x04b,0x04a,0x048,0x046,0x045, + 0x043,0x042,0x040,0x03f,0x03e,0x03c,0x03b,0x039,0x038,0x037,0x035,0x034,0x033,0x031,0x030,0x02f, + 0x02e,0x02d,0x02b,0x02a,0x029,0x028,0x027,0x026,0x025,0x024,0x023,0x022,0x021,0x020,0x01f,0x01e, + 0x01d,0x01c,0x01b,0x01a,0x019,0x018,0x017,0x017,0x016,0x015,0x014,0x014,0x013,0x012,0x011,0x011, + 0x010,0x00f,0x00f,0x00e,0x00d,0x00d,0x00c,0x00c,0x00b,0x00a,0x00a,0x009,0x009,0x008,0x008,0x007, + 0x007,0x007,0x006,0x006,0x005,0x005,0x005,0x004,0x004,0x004,0x003,0x003,0x003,0x002,0x002,0x002, + 0x002,0x001,0x001,0x001,0x001,0x001,0x001,0x001,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000 + }; + + // if the top bit is set, we're in the second half of the curve + // which is a mirror image, so invert the index + if (bitfield(input, 8)) + input = ~input; + + // return the value from the table + return s_sin_table[input & 0xff]; +} + + +//------------------------------------------------- +// attenuation_to_volume - given a 5.8 fixed point +// logarithmic attenuation value, return a 13-bit +// linear volume +//------------------------------------------------- + +inline uint32_t attenuation_to_volume(uint32_t input) +{ + // the values here are 10-bit mantissas with an implied leading bit + // this matches the internal format of the OPN chip, extracted from the die + + // as a nod to performance, the implicit 0x400 bit is pre-incorporated, and + // the values are left-shifted by 2 so that a simple right shift is all that + // is needed; also the order is reversed to save a NOT on the input +#define X(a) (((a) | 0x400) << 2) + static uint16_t const s_power_table[256] = + { + X(0x3fa),X(0x3f5),X(0x3ef),X(0x3ea),X(0x3e4),X(0x3df),X(0x3da),X(0x3d4), + X(0x3cf),X(0x3c9),X(0x3c4),X(0x3bf),X(0x3b9),X(0x3b4),X(0x3ae),X(0x3a9), + X(0x3a4),X(0x39f),X(0x399),X(0x394),X(0x38f),X(0x38a),X(0x384),X(0x37f), + X(0x37a),X(0x375),X(0x370),X(0x36a),X(0x365),X(0x360),X(0x35b),X(0x356), + X(0x351),X(0x34c),X(0x347),X(0x342),X(0x33d),X(0x338),X(0x333),X(0x32e), + X(0x329),X(0x324),X(0x31f),X(0x31a),X(0x315),X(0x310),X(0x30b),X(0x306), + X(0x302),X(0x2fd),X(0x2f8),X(0x2f3),X(0x2ee),X(0x2e9),X(0x2e5),X(0x2e0), + X(0x2db),X(0x2d6),X(0x2d2),X(0x2cd),X(0x2c8),X(0x2c4),X(0x2bf),X(0x2ba), + X(0x2b5),X(0x2b1),X(0x2ac),X(0x2a8),X(0x2a3),X(0x29e),X(0x29a),X(0x295), + X(0x291),X(0x28c),X(0x288),X(0x283),X(0x27f),X(0x27a),X(0x276),X(0x271), + X(0x26d),X(0x268),X(0x264),X(0x25f),X(0x25b),X(0x257),X(0x252),X(0x24e), + X(0x249),X(0x245),X(0x241),X(0x23c),X(0x238),X(0x234),X(0x230),X(0x22b), + X(0x227),X(0x223),X(0x21e),X(0x21a),X(0x216),X(0x212),X(0x20e),X(0x209), + X(0x205),X(0x201),X(0x1fd),X(0x1f9),X(0x1f5),X(0x1f0),X(0x1ec),X(0x1e8), + X(0x1e4),X(0x1e0),X(0x1dc),X(0x1d8),X(0x1d4),X(0x1d0),X(0x1cc),X(0x1c8), + X(0x1c4),X(0x1c0),X(0x1bc),X(0x1b8),X(0x1b4),X(0x1b0),X(0x1ac),X(0x1a8), + X(0x1a4),X(0x1a0),X(0x19c),X(0x199),X(0x195),X(0x191),X(0x18d),X(0x189), + X(0x185),X(0x181),X(0x17e),X(0x17a),X(0x176),X(0x172),X(0x16f),X(0x16b), + X(0x167),X(0x163),X(0x160),X(0x15c),X(0x158),X(0x154),X(0x151),X(0x14d), + X(0x149),X(0x146),X(0x142),X(0x13e),X(0x13b),X(0x137),X(0x134),X(0x130), + X(0x12c),X(0x129),X(0x125),X(0x122),X(0x11e),X(0x11b),X(0x117),X(0x114), + X(0x110),X(0x10c),X(0x109),X(0x106),X(0x102),X(0x0ff),X(0x0fb),X(0x0f8), + X(0x0f4),X(0x0f1),X(0x0ed),X(0x0ea),X(0x0e7),X(0x0e3),X(0x0e0),X(0x0dc), + X(0x0d9),X(0x0d6),X(0x0d2),X(0x0cf),X(0x0cc),X(0x0c8),X(0x0c5),X(0x0c2), + X(0x0be),X(0x0bb),X(0x0b8),X(0x0b5),X(0x0b1),X(0x0ae),X(0x0ab),X(0x0a8), + X(0x0a4),X(0x0a1),X(0x09e),X(0x09b),X(0x098),X(0x094),X(0x091),X(0x08e), + X(0x08b),X(0x088),X(0x085),X(0x082),X(0x07e),X(0x07b),X(0x078),X(0x075), + X(0x072),X(0x06f),X(0x06c),X(0x069),X(0x066),X(0x063),X(0x060),X(0x05d), + X(0x05a),X(0x057),X(0x054),X(0x051),X(0x04e),X(0x04b),X(0x048),X(0x045), + X(0x042),X(0x03f),X(0x03c),X(0x039),X(0x036),X(0x033),X(0x030),X(0x02d), + X(0x02a),X(0x028),X(0x025),X(0x022),X(0x01f),X(0x01c),X(0x019),X(0x016), + X(0x014),X(0x011),X(0x00e),X(0x00b),X(0x008),X(0x006),X(0x003),X(0x000) + }; +#undef X + + // look up the fractional part, then shift by the whole + return s_power_table[input & 0xff] >> (input >> 8); +} + + +//------------------------------------------------- +// attenuation_increment - given a 6-bit ADSR +// rate value and a 3-bit stepping index, +// return a 4-bit increment to the attenutaion +// for this step (or for the attack case, the +// fractional scale factor to decrease by) +//------------------------------------------------- + +inline uint32_t attenuation_increment(uint32_t rate, uint32_t index) +{ + static uint32_t const s_increment_table[64] = + { + 0x00000000, 0x00000000, 0x10101010, 0x10101010, // 0-3 (0x00-0x03) + 0x10101010, 0x10101010, 0x11101110, 0x11101110, // 4-7 (0x04-0x07) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 8-11 (0x08-0x0B) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 12-15 (0x0C-0x0F) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 16-19 (0x10-0x13) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 20-23 (0x14-0x17) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 24-27 (0x18-0x1B) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 28-31 (0x1C-0x1F) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 32-35 (0x20-0x23) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 36-39 (0x24-0x27) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 40-43 (0x28-0x2B) + 0x10101010, 0x10111010, 0x11101110, 0x11111110, // 44-47 (0x2C-0x2F) + 0x11111111, 0x21112111, 0x21212121, 0x22212221, // 48-51 (0x30-0x33) + 0x22222222, 0x42224222, 0x42424242, 0x44424442, // 52-55 (0x34-0x37) + 0x44444444, 0x84448444, 0x84848484, 0x88848884, // 56-59 (0x38-0x3B) + 0x88888888, 0x88888888, 0x88888888, 0x88888888 // 60-63 (0x3C-0x3F) + }; + return bitfield(s_increment_table[rate], 4*index, 4); +} + + +//------------------------------------------------- +// detune_adjustment - given a 5-bit key code +// value and a 3-bit detune parameter, return a +// 6-bit signed phase displacement; this table +// has been verified against Nuked's equations, +// but the equations are rather complicated, so +// we'll keep the simplicity of the table +//------------------------------------------------- + +inline int32_t detune_adjustment(uint32_t detune, uint32_t keycode) +{ + static uint8_t const s_detune_adjustment[32][4] = + { + { 0, 0, 1, 2 }, { 0, 0, 1, 2 }, { 0, 0, 1, 2 }, { 0, 0, 1, 2 }, + { 0, 1, 2, 2 }, { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, { 0, 1, 2, 3 }, + { 0, 1, 2, 4 }, { 0, 1, 3, 4 }, { 0, 1, 3, 4 }, { 0, 1, 3, 5 }, + { 0, 2, 4, 5 }, { 0, 2, 4, 6 }, { 0, 2, 4, 6 }, { 0, 2, 5, 7 }, + { 0, 2, 5, 8 }, { 0, 3, 6, 8 }, { 0, 3, 6, 9 }, { 0, 3, 7, 10 }, + { 0, 4, 8, 11 }, { 0, 4, 8, 12 }, { 0, 4, 9, 13 }, { 0, 5, 10, 14 }, + { 0, 5, 11, 16 }, { 0, 6, 12, 17 }, { 0, 6, 13, 19 }, { 0, 7, 14, 20 }, + { 0, 8, 16, 22 }, { 0, 8, 16, 22 }, { 0, 8, 16, 22 }, { 0, 8, 16, 22 } + }; + int32_t result = s_detune_adjustment[keycode][detune & 3]; + return bitfield(detune, 2) ? -result : result; +} + + +//------------------------------------------------- +// opm_key_code_to_phase_step - converts an +// OPM concatenated block (3 bits), keycode +// (4 bits) and key fraction (6 bits) to a 0.10 +// phase step, after applying the given delta; +// this applies to OPM and OPZ, so it lives here +// in a central location +//------------------------------------------------- + +inline uint32_t opm_key_code_to_phase_step(uint32_t block_freq, int32_t delta) +{ + // The phase step is essentially the fnum in OPN-speak. To compute this table, + // we used the standard formula for computing the frequency of a note, and + // then converted that frequency to fnum using the formula documented in the + // YM2608 manual. + // + // However, the YM2608 manual describes everything in terms of a nominal 8MHz + // clock, which produces an FM clock of: + // + // 8000000 / 24(operators) / 6(prescale) = 55555Hz FM clock + // + // Whereas the descriptions for the YM2151 use a nominal 3.579545MHz clock: + // + // 3579545 / 32(operators) / 2(prescale) = 55930Hz FM clock + // + // To correct for this, the YM2608 formula was adjusted to use a clock of + // 8053920Hz, giving this equation for the fnum: + // + // fnum = (double(144) * freq * (1 << 20)) / double(8053920) / 4; + // + // Unfortunately, the computed table differs in a few spots from the data + // verified from an actual chip. The table below comes from David Viens' + // analysis, used with his permission. + static const uint32_t s_phase_step[12*64] = + { + 41568,41600,41632,41664,41696,41728,41760,41792,41856,41888,41920,41952,42016,42048,42080,42112, + 42176,42208,42240,42272,42304,42336,42368,42400,42464,42496,42528,42560,42624,42656,42688,42720, + 42784,42816,42848,42880,42912,42944,42976,43008,43072,43104,43136,43168,43232,43264,43296,43328, + 43392,43424,43456,43488,43552,43584,43616,43648,43712,43744,43776,43808,43872,43904,43936,43968, + 44032,44064,44096,44128,44192,44224,44256,44288,44352,44384,44416,44448,44512,44544,44576,44608, + 44672,44704,44736,44768,44832,44864,44896,44928,44992,45024,45056,45088,45152,45184,45216,45248, + 45312,45344,45376,45408,45472,45504,45536,45568,45632,45664,45728,45760,45792,45824,45888,45920, + 45984,46016,46048,46080,46144,46176,46208,46240,46304,46336,46368,46400,46464,46496,46528,46560, + 46656,46688,46720,46752,46816,46848,46880,46912,46976,47008,47072,47104,47136,47168,47232,47264, + 47328,47360,47392,47424,47488,47520,47552,47584,47648,47680,47744,47776,47808,47840,47904,47936, + 48032,48064,48096,48128,48192,48224,48288,48320,48384,48416,48448,48480,48544,48576,48640,48672, + 48736,48768,48800,48832,48896,48928,48992,49024,49088,49120,49152,49184,49248,49280,49344,49376, + 49440,49472,49504,49536,49600,49632,49696,49728,49792,49824,49856,49888,49952,49984,50048,50080, + 50144,50176,50208,50240,50304,50336,50400,50432,50496,50528,50560,50592,50656,50688,50752,50784, + 50880,50912,50944,50976,51040,51072,51136,51168,51232,51264,51328,51360,51424,51456,51488,51520, + 51616,51648,51680,51712,51776,51808,51872,51904,51968,52000,52064,52096,52160,52192,52224,52256, + 52384,52416,52448,52480,52544,52576,52640,52672,52736,52768,52832,52864,52928,52960,52992,53024, + 53120,53152,53216,53248,53312,53344,53408,53440,53504,53536,53600,53632,53696,53728,53792,53824, + 53920,53952,54016,54048,54112,54144,54208,54240,54304,54336,54400,54432,54496,54528,54592,54624, + 54688,54720,54784,54816,54880,54912,54976,55008,55072,55104,55168,55200,55264,55296,55360,55392, + 55488,55520,55584,55616,55680,55712,55776,55808,55872,55936,55968,56032,56064,56128,56160,56224, + 56288,56320,56384,56416,56480,56512,56576,56608,56672,56736,56768,56832,56864,56928,56960,57024, + 57120,57152,57216,57248,57312,57376,57408,57472,57536,57568,57632,57664,57728,57792,57824,57888, + 57952,57984,58048,58080,58144,58208,58240,58304,58368,58400,58464,58496,58560,58624,58656,58720, + 58784,58816,58880,58912,58976,59040,59072,59136,59200,59232,59296,59328,59392,59456,59488,59552, + 59648,59680,59744,59776,59840,59904,59936,60000,60064,60128,60160,60224,60288,60320,60384,60416, + 60512,60544,60608,60640,60704,60768,60800,60864,60928,60992,61024,61088,61152,61184,61248,61280, + 61376,61408,61472,61536,61600,61632,61696,61760,61824,61856,61920,61984,62048,62080,62144,62208, + 62272,62304,62368,62432,62496,62528,62592,62656,62720,62752,62816,62880,62944,62976,63040,63104, + 63200,63232,63296,63360,63424,63456,63520,63584,63648,63680,63744,63808,63872,63904,63968,64032, + 64096,64128,64192,64256,64320,64352,64416,64480,64544,64608,64672,64704,64768,64832,64896,64928, + 65024,65056,65120,65184,65248,65312,65376,65408,65504,65536,65600,65664,65728,65792,65856,65888, + 65984,66016,66080,66144,66208,66272,66336,66368,66464,66496,66560,66624,66688,66752,66816,66848, + 66944,66976,67040,67104,67168,67232,67296,67328,67424,67456,67520,67584,67648,67712,67776,67808, + 67904,67936,68000,68064,68128,68192,68256,68288,68384,68448,68512,68544,68640,68672,68736,68800, + 68896,68928,68992,69056,69120,69184,69248,69280,69376,69440,69504,69536,69632,69664,69728,69792, + 69920,69952,70016,70080,70144,70208,70272,70304,70400,70464,70528,70560,70656,70688,70752,70816, + 70912,70976,71040,71104,71136,71232,71264,71360,71424,71488,71552,71616,71648,71744,71776,71872, + 71968,72032,72096,72160,72192,72288,72320,72416,72480,72544,72608,72672,72704,72800,72832,72928, + 72992,73056,73120,73184,73216,73312,73344,73440,73504,73568,73632,73696,73728,73824,73856,73952, + 74080,74144,74208,74272,74304,74400,74432,74528,74592,74656,74720,74784,74816,74912,74944,75040, + 75136,75200,75264,75328,75360,75456,75488,75584,75648,75712,75776,75840,75872,75968,76000,76096, + 76224,76288,76352,76416,76448,76544,76576,76672,76736,76800,76864,76928,77024,77120,77152,77248, + 77344,77408,77472,77536,77568,77664,77696,77792,77856,77920,77984,78048,78144,78240,78272,78368, + 78464,78528,78592,78656,78688,78784,78816,78912,78976,79040,79104,79168,79264,79360,79392,79488, + 79616,79680,79744,79808,79840,79936,79968,80064,80128,80192,80256,80320,80416,80512,80544,80640, + 80768,80832,80896,80960,80992,81088,81120,81216,81280,81344,81408,81472,81568,81664,81696,81792, + 81952,82016,82080,82144,82176,82272,82304,82400,82464,82528,82592,82656,82752,82848,82880,82976 + }; + + // extract the block (octave) first + uint32_t block = bitfield(block_freq, 10, 3); + + // the keycode (bits 6-9) is "gappy", mapping 12 values over 16 in each + // octave; to correct for this, we multiply the 4-bit value by 3/4 (or + // rather subtract 1/4); note that a (invalid) value of 15 will bleed into + // the next octave -- this is confirmed + uint32_t adjusted_code = bitfield(block_freq, 6, 4) - bitfield(block_freq, 8, 2); + + // now re-insert the 6-bit fraction + int32_t eff_freq = (adjusted_code << 6) | bitfield(block_freq, 0, 6); + + // now that the gaps are removed, add the delta + eff_freq += delta; + + // handle over/underflow by adjusting the block: + if (uint32_t(eff_freq) >= 768) + { + // minimum delta is -512 (PM), so we can only underflow by 1 octave + if (eff_freq < 0) + { + eff_freq += 768; + if (block-- == 0) + return s_phase_step[0] >> 7; + } + + // maximum delta is +512+608 (PM+detune), so we can overflow by up to 2 octaves + else + { + eff_freq -= 768; + if (eff_freq >= 768) + block++, eff_freq -= 768; + if (block++ >= 7) + return s_phase_step[767]; + } + } + + // look up the phase shift for the key code, then shift by octave + return s_phase_step[eff_freq] >> (block ^ 7); +} + + +//------------------------------------------------- +// opn_lfo_pm_phase_adjustment - given the 7 most +// significant frequency number bits, plus a 3-bit +// PM depth value and a signed 5-bit raw PM value, +// return a signed PM adjustment to the frequency; +// algorithm written to match Nuked behavior +//------------------------------------------------- + +inline int32_t opn_lfo_pm_phase_adjustment(uint32_t fnum_bits, uint32_t pm_sensitivity, int32_t lfo_raw_pm) +{ + // this table encodes 2 shift values to apply to the top 7 bits + // of fnum; it is effectively a cheap multiply by a constant + // value containing 0-2 bits + static uint8_t const s_lfo_pm_shifts[8][8] = + { + { 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77, 0x77 }, + { 0x77, 0x77, 0x77, 0x77, 0x72, 0x72, 0x72, 0x72 }, + { 0x77, 0x77, 0x77, 0x72, 0x72, 0x72, 0x17, 0x17 }, + { 0x77, 0x77, 0x72, 0x72, 0x17, 0x17, 0x12, 0x12 }, + { 0x77, 0x77, 0x72, 0x17, 0x17, 0x17, 0x12, 0x07 }, + { 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 }, + { 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 }, + { 0x77, 0x77, 0x17, 0x12, 0x07, 0x07, 0x02, 0x01 } + }; + + // look up the relevant shifts + int32_t abs_pm = (lfo_raw_pm < 0) ? -lfo_raw_pm : lfo_raw_pm; + uint32_t const shifts = s_lfo_pm_shifts[pm_sensitivity][bitfield(abs_pm, 0, 3)]; + + // compute the adjustment + int32_t adjust = (fnum_bits >> bitfield(shifts, 0, 4)) + (fnum_bits >> bitfield(shifts, 4, 4)); + if (pm_sensitivity > 5) + adjust <<= pm_sensitivity - 5; + adjust >>= 2; + + // every 16 cycles it inverts sign + return (lfo_raw_pm < 0) ? -adjust : adjust; +} + + + +//********************************************************* +// FM OPERATOR +//********************************************************* + +//------------------------------------------------- +// fm_operator - constructor +//------------------------------------------------- + +template +fm_operator::fm_operator(fm_engine_base &owner, uint32_t opoffs) : + m_choffs(0), + m_opoffs(opoffs), + m_phase(0), + m_env_attenuation(0x3ff), + m_env_state(EG_RELEASE), + m_ssg_inverted(false), + m_key_state(0), + m_keyon_live(0), + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +template +void fm_operator::reset() +{ + // reset our data + m_phase = 0; + m_env_attenuation = 0x3ff; + m_env_state = EG_RELEASE; + m_ssg_inverted = 0; + m_key_state = 0; + m_keyon_live = 0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void fm_operator::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_phase); + state.save_restore(m_env_attenuation); + state.save_restore(m_env_state); + state.save_restore(m_ssg_inverted); + state.save_restore(m_key_state); + state.save_restore(m_keyon_live); +} + + +//------------------------------------------------- +// prepare - prepare for clocking +//------------------------------------------------- + +template +bool fm_operator::prepare() +{ + // cache the data + m_regs.cache_operator_data(m_choffs, m_opoffs, m_cache); + + // clock the key state + clock_keystate(uint32_t(m_keyon_live != 0)); + m_keyon_live &= ~(1 << KEYON_CSM); + + // we're active until we're quiet after the release + return (m_env_state != (RegisterType::EG_HAS_REVERB ? EG_REVERB : EG_RELEASE) || m_env_attenuation < EG_QUIET); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +template +void fm_operator::clock(uint32_t env_counter, int32_t lfo_raw_pm) +{ + // clock the SSG-EG state (OPN/OPNA) + if (m_regs.op_ssg_eg_enable(m_opoffs)) + clock_ssg_eg_state(); + else + m_ssg_inverted = false; + + // clock the envelope if on an envelope cycle; env_counter is a x.2 value + if (bitfield(env_counter, 0, 2) == 0) + clock_envelope(env_counter >> 2); + + // clock the phase + clock_phase(lfo_raw_pm); +} + + +//------------------------------------------------- +// compute_volume - compute the 14-bit signed +// volume of this operator, given a phase +// modulation and an AM LFO offset +//------------------------------------------------- + +template +int32_t fm_operator::compute_volume(uint32_t phase, uint32_t am_offset) const +{ + // the low 10 bits of phase represents a full 2*PI period over + // the full sin wave + + // early out if the envelope is effectively off + if (m_env_attenuation > EG_QUIET) + return 0; + + // get the absolute value of the sin, as attenuation, as a 4.8 fixed point value + uint32_t sin_attenuation = m_cache.waveform[phase & (RegisterType::WAVEFORM_LENGTH - 1)]; + + // get the attenuation from the evelope generator as a 4.6 value, shifted up to 4.8 + uint32_t env_attenuation = envelope_attenuation(am_offset) << 2; + + // combine into a 5.8 value, then convert from attenuation to 13-bit linear volume + int32_t result = attenuation_to_volume((sin_attenuation & 0x7fff) + env_attenuation); + + // negate if in the negative part of the sin wave (sign bit gives 14 bits) + return bitfield(sin_attenuation, 15) ? -result : result; +} + + +//------------------------------------------------- +// compute_noise_volume - compute the 14-bit +// signed noise volume of this operator, given a +// noise input value and an AM offset +//------------------------------------------------- + +template +int32_t fm_operator::compute_noise_volume(uint32_t am_offset) const +{ + // application manual says the logarithmic transform is not applied here, so we + // just use the raw envelope attenuation, inverted (since 0 attenuation should be + // maximum), and shift it up from a 10-bit value to an 11-bit value + int32_t result = (envelope_attenuation(am_offset) ^ 0x3ff) << 1; + + // QUESTION: is AM applied still? + + // negate based on the noise state + return bitfield(m_regs.noise_state(), 0) ? -result : result; +} + + +//------------------------------------------------- +// keyonoff - signal a key on/off event +//------------------------------------------------- + +template +void fm_operator::keyonoff(uint32_t on, keyon_type type) +{ + m_keyon_live = (m_keyon_live & ~(1 << int(type))) | (bitfield(on, 0) << int(type)); +} + + +//------------------------------------------------- +// start_attack - start the attack phase; called +// when a keyon happens or when an SSG-EG cycle +// is complete and restarts +//------------------------------------------------- + +template +void fm_operator::start_attack(bool is_restart) +{ + // don't change anything if already in attack state + if (m_env_state == EG_ATTACK) + return; + m_env_state = EG_ATTACK; + + // generally not inverted at start, except if SSG-EG is enabled and + // one of the inverted modes is specified; leave this alone on a + // restart, as it is managed by the clock_ssg_eg_state() code + if (RegisterType::EG_HAS_SSG && !is_restart) + m_ssg_inverted = m_regs.op_ssg_eg_enable(m_opoffs) & bitfield(m_regs.op_ssg_eg_mode(m_opoffs), 2); + + // reset the phase when we start an attack due to a key on + // (but not when due to an SSG-EG restart except in certain cases + // managed directly by the SSG-EG code) + if (!is_restart) + m_phase = 0; + + // if the attack rate >= 62 then immediately go to max attenuation + if (m_cache.eg_rate[EG_ATTACK] >= 62) + m_env_attenuation = 0; +} + + +//------------------------------------------------- +// start_release - start the release phase; +// called when a keyoff happens +//------------------------------------------------- + +template +void fm_operator::start_release() +{ + // don't change anything if already in release state + if (m_env_state >= EG_RELEASE) + return; + m_env_state = EG_RELEASE; + + // if attenuation if inverted due to SSG-EG, snap the inverted attenuation + // as the starting point + if (RegisterType::EG_HAS_SSG && m_ssg_inverted) + { + m_env_attenuation = (0x200 - m_env_attenuation) & 0x3ff; + m_ssg_inverted = false; + } +} + + +//------------------------------------------------- +// clock_keystate - clock the keystate to match +// the incoming keystate +//------------------------------------------------- + +template +void fm_operator::clock_keystate(uint32_t keystate) +{ + assert(keystate == 0 || keystate == 1); + + // has the key changed? + if ((keystate ^ m_key_state) != 0) + { + m_key_state = keystate; + + // if the key has turned on, start the attack + if (keystate != 0) + { + // OPLL has a DP ("depress"?) state to bring the volume + // down before starting the attack + if (RegisterType::EG_HAS_DEPRESS && m_env_attenuation < 0x200) + m_env_state = EG_DEPRESS; + else + start_attack(); + } + + // otherwise, start the release + else + start_release(); + } +} + + +//------------------------------------------------- +// clock_ssg_eg_state - clock the SSG-EG state; +// should only be called if SSG-EG is enabled +//------------------------------------------------- + +template +void fm_operator::clock_ssg_eg_state() +{ + // work only happens once the attenuation crosses above 0x200 + if (!bitfield(m_env_attenuation, 9)) + return; + + // 8 SSG-EG modes: + // 000: repeat normally + // 001: run once, hold low + // 010: repeat, alternating between inverted/non-inverted + // 011: run once, hold high + // 100: inverted repeat normally + // 101: inverted run once, hold low + // 110: inverted repeat, alternating between inverted/non-inverted + // 111: inverted run once, hold high + uint32_t mode = m_regs.op_ssg_eg_mode(m_opoffs); + + // hold modes (1/3/5/7) + if (bitfield(mode, 0)) + { + // set the inverted flag to the end state (0 for modes 1/7, 1 for modes 3/5) + m_ssg_inverted = bitfield(mode, 2) ^ bitfield(mode, 1); + + // if holding, force the attenuation to the expected value once we're + // past the attack phase + if (m_env_state != EG_ATTACK) + m_env_attenuation = m_ssg_inverted ? 0x200 : 0x3ff; + } + + // continuous modes (0/2/4/6) + else + { + // toggle invert in alternating mode (even in attack state) + m_ssg_inverted ^= bitfield(mode, 1); + + // restart attack if in decay/sustain states + if (m_env_state == EG_DECAY || m_env_state == EG_SUSTAIN) + start_attack(true); + + // phase is reset to 0 in modes 0/4 + if (bitfield(mode, 1) == 0) + m_phase = 0; + } + + // in all modes, once we hit release state, attenuation is forced to maximum + if (m_env_state == EG_RELEASE) + m_env_attenuation = 0x3ff; +} + + +//------------------------------------------------- +// clock_envelope - clock the envelope state +// according to the given count +//------------------------------------------------- + +template +void fm_operator::clock_envelope(uint32_t env_counter) +{ + // handle attack->decay transitions + if (m_env_state == EG_ATTACK && m_env_attenuation == 0) + m_env_state = EG_DECAY; + + // handle decay->sustain transitions; it is important to do this immediately + // after the attack->decay transition above in the event that the sustain level + // is set to 0 (in which case we will skip right to sustain without doing any + // decay); as an example where this can be heard, check the cymbals sound + // in channel 0 of shinobi's test mode sound #5 + if (m_env_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain) + m_env_state = EG_SUSTAIN; + + // fetch the appropriate 6-bit rate value from the cache + uint32_t rate = m_cache.eg_rate[m_env_state]; + + // compute the rate shift value; this is the shift needed to + // apply to the env_counter such that it becomes a 5.11 fixed + // point number + uint32_t rate_shift = rate >> 2; + env_counter <<= rate_shift; + + // see if the fractional part is 0; if not, it's not time to clock + if (bitfield(env_counter, 0, 11) != 0) + return; + + // determine the increment based on the non-fractional part of env_counter + uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3); + uint32_t increment = attenuation_increment(rate, relevant_bits); + + // attack is the only one that increases + if (m_env_state == EG_ATTACK) + { + // glitch means that attack rates of 62/63 don't increment if + // changed after the initial key on (where they are handled + // specially); nukeykt confirms this happens on OPM, OPN, OPL/OPLL + // at least so assuming it is true for everyone + if (rate < 62) + m_env_attenuation += (~m_env_attenuation * increment) >> 4; + } + + // all other cases are similar + else + { + // non-SSG-EG cases just apply the increment + if (!m_regs.op_ssg_eg_enable(m_opoffs)) + m_env_attenuation += increment; + + // SSG-EG only applies if less than mid-point, and then at 4x + else if (m_env_attenuation < 0x200) + m_env_attenuation += 4 * increment; + + // clamp the final attenuation + if (m_env_attenuation >= 0x400) + m_env_attenuation = 0x3ff; + + // transition from depress to attack + if (RegisterType::EG_HAS_DEPRESS && m_env_state == EG_DEPRESS && m_env_attenuation >= 0x200) + start_attack(); + + // transition from release to reverb, should switch at -18dB + if (RegisterType::EG_HAS_REVERB && m_env_state == EG_RELEASE && m_env_attenuation >= 0xc0) + m_env_state = EG_REVERB; + } +} + + +//------------------------------------------------- +// clock_phase - clock the 10.10 phase value; the +// OPN version of the logic has been verified +// against the Nuked phase generator +//------------------------------------------------- + +template +void fm_operator::clock_phase(int32_t lfo_raw_pm) +{ + // read from the cache, or recalculate if PM active + uint32_t phase_step = m_cache.phase_step; + if (phase_step == opdata_cache::PHASE_STEP_DYNAMIC) + phase_step = m_regs.compute_phase_step(m_choffs, m_opoffs, m_cache, lfo_raw_pm); + + // finally apply the step to the current phase value + m_phase += phase_step; +} + + +//------------------------------------------------- +// envelope_attenuation - return the effective +// attenuation of the envelope +//------------------------------------------------- + +template +uint32_t fm_operator::envelope_attenuation(uint32_t am_offset) const +{ + uint32_t result = m_env_attenuation >> m_cache.eg_shift; + + // invert if necessary due to SSG-EG + if (RegisterType::EG_HAS_SSG && m_ssg_inverted) + result = (0x200 - result) & 0x3ff; + + // add in LFO AM modulation + if (m_regs.op_lfo_am_enable(m_opoffs)) + result += am_offset; + + // add in total level and KSL from the cache + result += m_cache.total_level; + + // clamp to max, apply shift, and return + return std::min(result, 0x3ff); +} + + + +//********************************************************* +// FM CHANNEL +//********************************************************* + +//------------------------------------------------- +// fm_channel - constructor +//------------------------------------------------- + +template +fm_channel::fm_channel(fm_engine_base &owner, uint32_t choffs) : + m_choffs(choffs), + m_feedback{ 0, 0 }, + m_feedback_in(0), + m_op{ nullptr, nullptr, nullptr, nullptr }, + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +template +void fm_channel::reset() +{ + // reset our data + m_feedback[0] = m_feedback[1] = 0; + m_feedback_in = 0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void fm_channel::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_feedback[0]); + state.save_restore(m_feedback[1]); + state.save_restore(m_feedback_in); +} + + +//------------------------------------------------- +// keyonoff - signal key on/off to our operators +//------------------------------------------------- + +template +void fm_channel::keyonoff(uint32_t states, keyon_type type, uint32_t chnum) +{ + for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + if (m_op[opnum] != nullptr) + m_op[opnum]->keyonoff(bitfield(states, opnum), type); + + if (debug::LOG_KEYON_EVENTS && ((debug::GLOBAL_FM_CHANNEL_MASK >> chnum) & 1) != 0) + for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + if (m_op[opnum] != nullptr) + debug::log_keyon("%c%s\n", bitfield(states, opnum) ? '+' : '-', m_regs.log_keyon(m_choffs, m_op[opnum]->opoffs()).c_str()); +} + + +//------------------------------------------------- +// prepare - prepare for clocking +//------------------------------------------------- + +template +bool fm_channel::prepare() +{ + uint32_t active_mask = 0; + + // prepare all operators and determine if they are active + for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + if (m_op[opnum] != nullptr) + if (m_op[opnum]->prepare()) + active_mask |= 1 << opnum; + + return (active_mask != 0); +} + + +//------------------------------------------------- +// clock - master clock of all operators +//------------------------------------------------- + +template +void fm_channel::clock(uint32_t env_counter, int32_t lfo_raw_pm) +{ + // clock the feedback through + m_feedback[0] = m_feedback[1]; + m_feedback[1] = m_feedback_in; + + for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + if (m_op[opnum] != nullptr) + m_op[opnum]->clock(env_counter, lfo_raw_pm); + +/* +useful temporary code for envelope debugging +if (m_choffs == 0x101) +{ + for (uint32_t opnum = 0; opnum < array_size(m_op); opnum++) + { + auto &op = *m_op[((opnum & 1) << 1) | ((opnum >> 1) & 1)]; + printf(" %c%03X%c%c ", + "PADSRV"[op.debug_eg_state()], + op.debug_eg_attenuation(), + op.debug_ssg_inverted() ? '-' : '+', + m_regs.op_ssg_eg_enable(op.opoffs()) ? '0' + m_regs.op_ssg_eg_mode(op.opoffs()) : ' '); + } +printf(" -- "); +} +*/ +} + + +//------------------------------------------------- +// output_2op - combine 4 operators according to +// the specified algorithm, returning a sum +// according to the rshift and clipmax parameters, +// which vary between different implementations +//------------------------------------------------- + +template +void fm_channel::output_2op(output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // The first 2 operators should be populated + assert(m_op[0] != nullptr); + assert(m_op[1] != nullptr); + + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + + // operator 1 has optional self-feedback + int32_t opmod = 0; + uint32_t feedback = m_regs.ch_feedback(m_choffs); + if (feedback != 0) + opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback); + + // compute the 14-bit volume/value of operator 1 and update the feedback + int32_t op1value = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset); + + // now that the feedback has been computed, skip the rest if all volumes + // are clear; no need to do all this work for nothing + if (m_regs.ch_output_any(m_choffs) == 0) + return; + + // Algorithms for two-operator case: + // 0: O1 -> O2 -> out + // 1: (O1 + O2) -> out + int32_t result; + if (bitfield(m_regs.ch_algorithm(m_choffs), 0) == 0) + { + // some OPL chips use the previous sample for modulation instead of + // the current sample + opmod = (RegisterType::MODULATOR_DELAY ? m_feedback[1] : op1value) >> 1; + result = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset) >> rshift; + } + else + { + result = (RegisterType::MODULATOR_DELAY ? m_feedback[1] : op1value) >> rshift; + result += m_op[1]->compute_volume(m_op[1]->phase(), am_offset) >> rshift; + int32_t clipmin = -clipmax - 1; + result = clamp(result, clipmin, clipmax); + } + + // add to the output + add_to_output(m_choffs, output, result); +} + + +//------------------------------------------------- +// output_4op - combine 4 operators according to +// the specified algorithm, returning a sum +// according to the rshift and clipmax parameters, +// which vary between different implementations +//------------------------------------------------- + +template +void fm_channel::output_4op(output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // all 4 operators should be populated + assert(m_op[0] != nullptr); + assert(m_op[1] != nullptr); + assert(m_op[2] != nullptr); + assert(m_op[3] != nullptr); + + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + + // operator 1 has optional self-feedback + int32_t opmod = 0; + uint32_t feedback = m_regs.ch_feedback(m_choffs); + if (feedback != 0) + opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback); + + // compute the 14-bit volume/value of operator 1 and update the feedback + int32_t op1value = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset); + + // now that the feedback has been computed, skip the rest if all volumes + // are clear; no need to do all this work for nothing + if (m_regs.ch_output_any(m_choffs) == 0) + return; + + // OPM/OPN offer 8 different connection algorithms for 4 operators, + // and OPL3 offers 4 more, which we designate here as 8-11. + // + // The operators are computed in order, with the inputs pulled from + // an array of values (opout) that is populated as we go: + // 0 = 0 + // 1 = O1 + // 2 = O2 + // 3 = O3 + // 4 = (O4) + // 5 = O1+O2 + // 6 = O1+O3 + // 7 = O2+O3 + // + // The s_algorithm_ops table describes the inputs and outputs of each + // algorithm as follows: + // + // ---------x use opout[x] as operator 2 input + // ------xxx- use opout[x] as operator 3 input + // ---xxx---- use opout[x] as operator 4 input + // --x------- include opout[1] in final sum + // -x-------- include opout[2] in final sum + // x--------- include opout[3] in final sum + #define ALGORITHM(op2in, op3in, op4in, op1out, op2out, op3out) \ + ((op2in) | ((op3in) << 1) | ((op4in) << 4) | ((op1out) << 7) | ((op2out) << 8) | ((op3out) << 9)) + static uint16_t const s_algorithm_ops[8+4] = + { + ALGORITHM(1,2,3, 0,0,0), // 0: O1 -> O2 -> O3 -> O4 -> out (O4) + ALGORITHM(0,5,3, 0,0,0), // 1: (O1 + O2) -> O3 -> O4 -> out (O4) + ALGORITHM(0,2,6, 0,0,0), // 2: (O1 + (O2 -> O3)) -> O4 -> out (O4) + ALGORITHM(1,0,7, 0,0,0), // 3: ((O1 -> O2) + O3) -> O4 -> out (O4) + ALGORITHM(1,0,3, 0,1,0), // 4: ((O1 -> O2) + (O3 -> O4)) -> out (O2+O4) + ALGORITHM(1,1,1, 0,1,1), // 5: ((O1 -> O2) + (O1 -> O3) + (O1 -> O4)) -> out (O2+O3+O4) + ALGORITHM(1,0,0, 0,1,1), // 6: ((O1 -> O2) + O3 + O4) -> out (O2+O3+O4) + ALGORITHM(0,0,0, 1,1,1), // 7: (O1 + O2 + O3 + O4) -> out (O1+O2+O3+O4) + ALGORITHM(1,2,3, 0,0,0), // 8: O1 -> O2 -> O3 -> O4 -> out (O4) [same as 0] + ALGORITHM(0,2,3, 1,0,0), // 9: (O1 + (O2 -> O3 -> O4)) -> out (O1+O4) [unique] + ALGORITHM(1,0,3, 0,1,0), // 10: ((O1 -> O2) + (O3 -> O4)) -> out (O2+O4) [same as 4] + ALGORITHM(0,2,0, 1,0,1) // 11: (O1 + (O2 -> O3) + O4) -> out (O1+O3+O4) [unique] + }; + uint32_t algorithm_ops = s_algorithm_ops[m_regs.ch_algorithm(m_choffs)]; + + // populate the opout table + int16_t opout[8]; + opout[0] = 0; + opout[1] = op1value; + + // compute the 14-bit volume/value of operator 2 + opmod = opout[bitfield(algorithm_ops, 0, 1)] >> 1; + opout[2] = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset); + opout[5] = opout[1] + opout[2]; + + // compute the 14-bit volume/value of operator 3 + opmod = opout[bitfield(algorithm_ops, 1, 3)] >> 1; + opout[3] = m_op[2]->compute_volume(m_op[2]->phase() + opmod, am_offset); + opout[6] = opout[1] + opout[3]; + opout[7] = opout[2] + opout[3]; + + // compute the 14-bit volume/value of operator 4; this could be a noise + // value on the OPM; all algorithms consume OP4 output at a minimum + int32_t result; + if (m_regs.noise_enable() && m_choffs == 7) + result = m_op[3]->compute_noise_volume(am_offset); + else + { + opmod = opout[bitfield(algorithm_ops, 4, 3)] >> 1; + result = m_op[3]->compute_volume(m_op[3]->phase() + opmod, am_offset); + } + result >>= rshift; + + // optionally add OP1, OP2, OP3 + int32_t clipmin = -clipmax - 1; + if (bitfield(algorithm_ops, 7) != 0) + result = clamp(result + (opout[1] >> rshift), clipmin, clipmax); + if (bitfield(algorithm_ops, 8) != 0) + result = clamp(result + (opout[2] >> rshift), clipmin, clipmax); + if (bitfield(algorithm_ops, 9) != 0) + result = clamp(result + (opout[3] >> rshift), clipmin, clipmax); + + // add to the output + add_to_output(m_choffs, output, result); +} + + +//------------------------------------------------- +// output_rhythm_ch6 - special case output +// computation for OPL channel 6 in rhythm mode, +// which outputs a Bass Drum instrument +//------------------------------------------------- + +template +void fm_channel::output_rhythm_ch6(output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + + // Bass Drum: this uses operators 12 and 15 (i.e., channel 6) + // in an almost-normal way, except that if the algorithm is 1, + // the first operator is ignored instead of added in + + // operator 1 has optional self-feedback + int32_t opmod = 0; + uint32_t feedback = m_regs.ch_feedback(m_choffs); + if (feedback != 0) + opmod = (m_feedback[0] + m_feedback[1]) >> (10 - feedback); + + // compute the 14-bit volume/value of operator 1 and update the feedback + int32_t opout1 = m_feedback_in = m_op[0]->compute_volume(m_op[0]->phase() + opmod, am_offset); + + // compute the 14-bit volume/value of operator 2, which is the result + opmod = bitfield(m_regs.ch_algorithm(m_choffs), 0) ? 0 : (opout1 >> 1); + int32_t result = m_op[1]->compute_volume(m_op[1]->phase() + opmod, am_offset) >> rshift; + + // add to the output + add_to_output(m_choffs, output, result * 2); +} + + +//------------------------------------------------- +// output_rhythm_ch7 - special case output +// computation for OPL channel 7 in rhythm mode, +// which outputs High Hat and Snare Drum +// instruments +//------------------------------------------------- + +template +void fm_channel::output_rhythm_ch7(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + uint32_t noise_state = bitfield(m_regs.noise_state(), 0); + + // High Hat: this uses the envelope from operator 13 (channel 7), + // and a combination of noise and the operator 13/17 phase select + // to compute the phase + uint32_t phase = (phase_select << 9) | (0xd0 >> (2 * (noise_state ^ phase_select))); + int32_t result = m_op[0]->compute_volume(phase, am_offset) >> rshift; + + // Snare Drum: this uses the envelope from operator 16 (channel 7), + // and a combination of noise and operator 13 phase to pick a phase + uint32_t op13phase = m_op[0]->phase(); + phase = (0x100 << bitfield(op13phase, 8)) ^ (noise_state << 8); + result += m_op[1]->compute_volume(phase, am_offset) >> rshift; + result = clamp(result, -clipmax - 1, clipmax); + + // add to the output + add_to_output(m_choffs, output, result * 2); +} + + +//------------------------------------------------- +// output_rhythm_ch8 - special case output +// computation for OPL channel 8 in rhythm mode, +// which outputs Tom Tom and Top Cymbal instruments +//------------------------------------------------- + +template +void fm_channel::output_rhythm_ch8(uint32_t phase_select, output_data &output, uint32_t rshift, int32_t clipmax) const +{ + // AM amount is the same across all operators; compute it once + uint32_t am_offset = m_regs.lfo_am_offset(m_choffs); + + // Tom Tom: this is just a single operator processed normally + int32_t result = m_op[0]->compute_volume(m_op[0]->phase(), am_offset) >> rshift; + + // Top Cymbal: this uses the envelope from operator 17 (channel 8), + // and the operator 13/17 phase select to compute the phase + uint32_t phase = 0x100 | (phase_select << 9); + result += m_op[1]->compute_volume(phase, am_offset) >> rshift; + result = clamp(result, -clipmax - 1, clipmax); + + // add to the output + add_to_output(m_choffs, output, result * 2); +} + + + +//********************************************************* +// FM ENGINE BASE +//********************************************************* + +//------------------------------------------------- +// fm_engine_base - constructor +//------------------------------------------------- + +template +fm_engine_base::fm_engine_base(ymfm_interface &intf) : + m_intf(intf), + m_env_counter(0), + m_status(0), + m_clock_prescale(RegisterType::DEFAULT_PRESCALE), + m_irq_mask(STATUS_TIMERA | STATUS_TIMERB), + m_irq_state(0), + m_timer_running{0,0}, + m_active_channels(ALL_CHANNELS), + m_modified_channels(ALL_CHANNELS), + m_prepare_count(0) +{ + // inform the interface of their engine + m_intf.m_engine = this; + + // create the channels + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum] = std::make_unique>(*this, RegisterType::channel_offset(chnum)); + + // create the operators + for (uint32_t opnum = 0; opnum < OPERATORS; opnum++) + m_operator[opnum] = std::make_unique>(*this, RegisterType::operator_offset(opnum)); + +#if (DEBUG_LOG_WAVFILES) + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + m_wavfile[chnum].set_index(chnum); +#endif + + // do the initial operator assignment + assign_operators(); +} + + +//------------------------------------------------- +// reset - reset the overall state +//------------------------------------------------- + +template +void fm_engine_base::reset() +{ + // reset all status bits + set_reset_status(0, 0xff); + + // register type-specific initialization + m_regs.reset(); + + // explicitly write to the mode register since it has side-effects + // QUESTION: old cores initialize this to 0x30 -- who is right? + write(RegisterType::REG_MODE, 0); + + // reset the channels + for (auto &chan : m_channel) + chan->reset(); + + // reset the operators + for (auto &op : m_operator) + op->reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void fm_engine_base::save_restore(ymfm_saved_state &state) +{ + // save our data + state.save_restore(m_env_counter); + state.save_restore(m_status); + state.save_restore(m_clock_prescale); + state.save_restore(m_irq_mask); + state.save_restore(m_irq_state); + state.save_restore(m_timer_running[0]); + state.save_restore(m_timer_running[1]); + state.save_restore(m_total_clocks); + + // save the register/family data + m_regs.save_restore(state); + + // save channel data + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum]->save_restore(state); + + // save operator data + for (uint32_t opnum = 0; opnum < OPERATORS; opnum++) + m_operator[opnum]->save_restore(state); + + // invalidate any caches + invalidate_caches(); +} + + +//------------------------------------------------- +// clock - iterate over all channels, clocking +// them forward one step +//------------------------------------------------- + +template +uint32_t fm_engine_base::clock(uint32_t chanmask) +{ + // update the clock counter + m_total_clocks++; + + // if something was modified, prepare + // also prepare every 4k samples to catch ending notes + if (m_modified_channels != 0 || m_prepare_count++ >= 4096) + { + // reassign operators to channels if dynamic + if (RegisterType::DYNAMIC_OPS) + assign_operators(); + + // call each channel to prepare + m_active_channels = 0; + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + if (m_channel[chnum]->prepare()) + m_active_channels |= 1 << chnum; + + // reset the modified channels and prepare count + m_modified_channels = m_prepare_count = 0; + } + + // if the envelope clock divider is 1, just increment by 4; + // otherwise, increment by 1 and manually wrap when we reach the divide count + if (RegisterType::EG_CLOCK_DIVIDER == 1) + m_env_counter += 4; + else if (bitfield(++m_env_counter, 0, 2) == RegisterType::EG_CLOCK_DIVIDER) + m_env_counter += 4 - RegisterType::EG_CLOCK_DIVIDER; + + // clock the noise generator + int32_t lfo_raw_pm = m_regs.clock_noise_and_lfo(); + + // now update the state of all the channels and operators + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + m_channel[chnum]->clock(m_env_counter, lfo_raw_pm); + + // return the envelope counter as it is used to clock ADPCM-A + return m_env_counter; +} + + +//------------------------------------------------- +// output - compute a sum over the relevant +// channels +//------------------------------------------------- + +template +void fm_engine_base::output(output_data &output, uint32_t rshift, int32_t clipmax, uint32_t chanmask) const +{ + // mask out some channels for debug purposes + chanmask &= debug::GLOBAL_FM_CHANNEL_MASK; + + // mask out inactive channels + if (!DEBUG_LOG_WAVFILES) + chanmask &= m_active_channels; + + // handle the rhythm case, where some of the operators are dedicated + // to percussion (this is an OPL-specific feature) + if (m_regs.rhythm_enable()) + { + // we don't support the OPM noise channel here; ensure it is off + assert(m_regs.noise_enable() == 0); + + // precompute the operator 13+17 phase selection value + uint32_t op13phase = m_operator[13]->phase(); + uint32_t op17phase = m_operator[17]->phase(); + uint32_t phase_select = (bitfield(op13phase, 2) ^ bitfield(op13phase, 7)) | bitfield(op13phase, 3) | (bitfield(op17phase, 5) ^ bitfield(op17phase, 3)); + + // sum over all the desired channels + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + { +#if (DEBUG_LOG_WAVFILES) + auto reference = output; +#endif + if (chnum == 6) + m_channel[chnum]->output_rhythm_ch6(output, rshift, clipmax); + else if (chnum == 7) + m_channel[chnum]->output_rhythm_ch7(phase_select, output, rshift, clipmax); + else if (chnum == 8) + m_channel[chnum]->output_rhythm_ch8(phase_select, output, rshift, clipmax); + else if (m_channel[chnum]->is4op()) + m_channel[chnum]->output_4op(output, rshift, clipmax); + else + m_channel[chnum]->output_2op(output, rshift, clipmax); +#if (DEBUG_LOG_WAVFILES) + m_wavfile[chnum].add(output, reference); +#endif + } + } + else + { + // sum over all the desired channels + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + { +#if (DEBUG_LOG_WAVFILES) + auto reference = output; +#endif + if (m_channel[chnum]->is4op()) + m_channel[chnum]->output_4op(output, rshift, clipmax); + else + m_channel[chnum]->output_2op(output, rshift, clipmax); +#if (DEBUG_LOG_WAVFILES) + m_wavfile[chnum].add(output, reference); +#endif + } + } +} + + +//------------------------------------------------- +// write - handle writes to the OPN registers +//------------------------------------------------- + +template +void fm_engine_base::write(uint16_t regnum, uint8_t data) +{ + debug::log_fm_write("%03X = %02X\n", regnum, data); + + // special case: writes to the mode register can impact IRQs; + // schedule these writes to ensure ordering with timers + if (regnum == RegisterType::REG_MODE) + { + m_intf.ymfm_sync_mode_write(data); + return; + } + + // for now just mark all channels as modified + m_modified_channels = ALL_CHANNELS; + + // most writes are passive, consumed only when needed + uint32_t keyon_channel; + uint32_t keyon_opmask; + if (m_regs.write(regnum, data, keyon_channel, keyon_opmask)) + { + // handle writes to the keyon register(s) + if (keyon_channel < CHANNELS) + { + // normal channel on/off + m_channel[keyon_channel]->keyonoff(keyon_opmask, KEYON_NORMAL, keyon_channel); + } + else if (CHANNELS >= 9 && keyon_channel == RegisterType::RHYTHM_CHANNEL) + { + // special case for the OPL rhythm channels + m_channel[6]->keyonoff(bitfield(keyon_opmask, 4) ? 3 : 0, KEYON_RHYTHM, 6); + m_channel[7]->keyonoff(bitfield(keyon_opmask, 0) | (bitfield(keyon_opmask, 3) << 1), KEYON_RHYTHM, 7); + m_channel[8]->keyonoff(bitfield(keyon_opmask, 2) | (bitfield(keyon_opmask, 1) << 1), KEYON_RHYTHM, 8); + } + } +} + + +//------------------------------------------------- +// status - return the current state of the +// status flags +//------------------------------------------------- + +template +uint8_t fm_engine_base::status() const +{ + return m_status & ~STATUS_BUSY & ~m_regs.status_mask(); +} + + +//------------------------------------------------- +// assign_operators - get the current mapping of +// operators to channels and assign them all +//------------------------------------------------- + +template +void fm_engine_base::assign_operators() +{ + typename RegisterType::operator_mapping map; + m_regs.operator_map(map); + + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + for (uint32_t index = 0; index < 4; index++) + { + uint32_t opnum = bitfield(map.chan[chnum], 8 * index, 8); + m_channel[chnum]->assign(index, (opnum == 0xff) ? nullptr : m_operator[opnum].get()); + } +} + + +//------------------------------------------------- +// update_timer - update the state of the given +// timer +//------------------------------------------------- + +template +void fm_engine_base::update_timer(uint32_t tnum, uint32_t enable, int32_t delta_clocks) +{ + // if the timer is live, but not currently enabled, set the timer + if (enable && !m_timer_running[tnum]) + { + // period comes from the registers, and is different for each + uint32_t period = (tnum == 0) ? (1024 - m_regs.timer_a_value()) : 16 * (256 - m_regs.timer_b_value()); + + // caller can also specify a delta to account for other effects + period += delta_clocks; + + // reset it + m_intf.ymfm_set_timer(tnum, period * OPERATORS * m_clock_prescale); + m_timer_running[tnum] = 1; + } + + // if the timer is not live, ensure it is not enabled + else if (!enable) + { + m_intf.ymfm_set_timer(tnum, -1); + m_timer_running[tnum] = 0; + } +} + + +//------------------------------------------------- +// engine_timer_expired - timer has expired - signal +// status and possibly IRQs +//------------------------------------------------- + +template +void fm_engine_base::engine_timer_expired(uint32_t tnum) +{ + // update status + if (tnum == 0 && m_regs.enable_timer_a()) + set_reset_status(STATUS_TIMERA, 0); + else if (tnum == 1 && m_regs.enable_timer_b()) + set_reset_status(STATUS_TIMERB, 0); + + // if timer A fired in CSM mode, trigger CSM on all relevant channels + if (tnum == 0 && m_regs.csm()) + for (uint32_t chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(RegisterType::CSM_TRIGGER_MASK, chnum)) + { + m_channel[chnum]->keyonoff(1, KEYON_CSM, chnum); + m_modified_channels |= 1 << chnum; + } + + // reset + m_timer_running[tnum] = false; + update_timer(tnum, 1, 0); +} + + +//------------------------------------------------- +// check_interrupts - check the interrupt sources +// for interrupts +//------------------------------------------------- + +template +void fm_engine_base::engine_check_interrupts() +{ + // update the state + uint8_t old_state = m_irq_state; + m_irq_state = ((m_status & m_irq_mask & ~m_regs.status_mask()) != 0); + + // set the IRQ status bit + if (m_irq_state) + m_status |= STATUS_IRQ; + else + m_status &= ~STATUS_IRQ; + + // if changed, signal the new state + if (old_state != m_irq_state) + m_intf.ymfm_update_irq(m_irq_state ? true : false); +} + + +//------------------------------------------------- +// engine_mode_write - handle a mode register write +// via timer callback +//------------------------------------------------- + +template +void fm_engine_base::engine_mode_write(uint8_t data) +{ + // mark all channels as modified + m_modified_channels = ALL_CHANNELS; + + // actually write the mode register now + uint32_t dummy1, dummy2; + m_regs.write(RegisterType::REG_MODE, data, dummy1, dummy2); + + // reset IRQ status -- when written, all other bits are ignored + // QUESTION: should this maybe just reset the IRQ bit and not all the bits? + // That is, check_interrupts would only set, this would only clear? + if (m_regs.irq_reset()) + set_reset_status(0, 0x78); + else + { + // reset timer status + uint8_t reset_mask = 0; + if (m_regs.reset_timer_b()) + reset_mask |= RegisterType::STATUS_TIMERB; + if (m_regs.reset_timer_a()) + reset_mask |= RegisterType::STATUS_TIMERA; + set_reset_status(0, reset_mask); + + // load timers; note that timer B gets a small negative adjustment because + // the *16 multiplier is free-running, so the first tick of the clock + // is a bit shorter + update_timer(1, m_regs.load_timer_b(), -(m_total_clocks & 15)); + update_timer(0, m_regs.load_timer_a(), 0); + } +} + +} diff --git a/src/sound/ymfm/ymfm_misc.cpp b/src/sound/ymfm/ymfm_misc.cpp new file mode 100644 index 000000000..fd0575f55 --- /dev/null +++ b/src/sound/ymfm/ymfm_misc.cpp @@ -0,0 +1,175 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_misc.h" + +namespace ymfm +{ + +//********************************************************* +// YM2149 +//********************************************************* + +//------------------------------------------------- +// ym2149 - constructor +//------------------------------------------------- + +ym2149::ym2149(ymfm_interface &intf) : + m_address(0), + m_ssg(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2149::reset() +{ + // reset the engines + m_ssg.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2149::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_ssg.save_restore(state); +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ym2149::read_data() +{ + return m_ssg.read(m_address & 0x0f); +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2149::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 3) // BC2,BC1 + { + case 0: // inactive + break; + + case 1: // address + break; + + case 2: // inactive + break; + + case 3: // read + result = read_data(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2149::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2149::write_data(uint8_t data) +{ + m_ssg.write(m_address & 0x0f, data); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2149::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) // BC2,BC1 + { + case 0: // address + write_address(data); + break; + + case 1: // inactive + break; + + case 2: // write + write_data(data); + break; + + case 3: // address + write_address(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of SSG sound +//------------------------------------------------- + +void ym2149::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the SSG + m_ssg.clock(); + + // YM2149 keeps the three SSG outputs independent + m_ssg.output(*output); + } +} + +} diff --git a/src/sound/ymfm/ymfm_misc.h b/src/sound/ymfm/ymfm_misc.h new file mode 100644 index 000000000..628d128f6 --- /dev/null +++ b/src/sound/ymfm/ymfm_misc.h @@ -0,0 +1,93 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_MISC_H +#define YMFM_MISC_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_adpcm.h" +#include "ymfm_ssg.h" + +namespace ymfm +{ + +//********************************************************* +// SSG IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2149 + +// ym2149 is just an SSG with no FM part, but we expose FM-like parts so that it +// integrates smoothly with everything else; they just don't do anything +class ym2149 +{ +public: + static constexpr uint32_t OUTPUTS = ssg_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = ssg_engine::OUTPUTS; + using output_data = ymfm_output; + + // constructor + ym2149(ymfm_interface &intf); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return input_clock / ssg_engine::CLOCK_DIVIDER / 8; } + + // read access + uint8_t read_data(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + ssg_engine m_ssg; // SSG engine +}; + +} + +#endif // YMFM_MISC_H diff --git a/src/sound/ymfm/ymfm_opl.cpp b/src/sound/ymfm/ymfm_opl.cpp new file mode 100644 index 000000000..86215c5b2 --- /dev/null +++ b/src/sound/ymfm/ymfm_opl.cpp @@ -0,0 +1,2209 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opl.h" +#include "ymfm_fm.ipp" + +namespace ymfm +{ + +//------------------------------------------------- +// opl_key_scale_atten - converts an +// OPL concatenated block (3 bits) and fnum +// (10 bits) into an attenuation offset; values +// here are for 6dB/octave, in 0.75dB units +// (matching total level LSB) +//------------------------------------------------- + +inline uint32_t opl_key_scale_atten(uint32_t block, uint32_t fnum_4msb) +{ + // this table uses the top 4 bits of FNUM and are the maximal values + // (for when block == 7). Values for other blocks can be computed by + // subtracting 8 for each block below 7. + static uint8_t const fnum_to_atten[16] = { 0,24,32,37,40,43,45,47,48,50,51,52,53,54,55,56 }; + int32_t result = fnum_to_atten[fnum_4msb] - 8 * (block ^ 7); + return std::max(0, result); +} + + +//********************************************************* +// OPL REGISTERS +//********************************************************* + +//------------------------------------------------- +// opl_registers_base - constructor +//------------------------------------------------- + +template +opl_registers_base::opl_registers_base() : + m_lfo_am_counter(0), + m_lfo_pm_counter(0), + m_noise_lfsr(1), + m_lfo_am(0) +{ + // create these pointers to appease overzealous compilers checking array + // bounds in unreachable code (looking at you, clang) + uint16_t *wf0 = &m_waveform[0][0]; + uint16_t *wf1 = &m_waveform[1 % WAVEFORMS][0]; + uint16_t *wf2 = &m_waveform[2 % WAVEFORMS][0]; + uint16_t *wf3 = &m_waveform[3 % WAVEFORMS][0]; + uint16_t *wf4 = &m_waveform[4 % WAVEFORMS][0]; + uint16_t *wf5 = &m_waveform[5 % WAVEFORMS][0]; + uint16_t *wf6 = &m_waveform[6 % WAVEFORMS][0]; + uint16_t *wf7 = &m_waveform[7 % WAVEFORMS][0]; + + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + wf0[index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + if (WAVEFORMS >= 4) + { + uint16_t zeroval = wf0[0]; + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + { + wf1[index] = bitfield(index, 9) ? zeroval : wf0[index]; + wf2[index] = wf0[index] & 0x7fff; + wf3[index] = bitfield(index, 8) ? zeroval : (wf0[index] & 0x7fff); + if (WAVEFORMS >= 8) + { + wf4[index] = bitfield(index, 9) ? zeroval : wf0[index * 2]; + wf5[index] = bitfield(index, 9) ? zeroval : wf0[(index * 2) & 0x1ff]; + wf6[index] = bitfield(index, 9) << 15; + wf7[index] = (bitfield(index, 9) ? (index ^ 0x13ff) : index) << 3; + } + } + } +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +template +void opl_registers_base::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void opl_registers_base::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_am_counter); + state.save_restore(m_lfo_pm_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_noise_lfsr); + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPL this is fixed +//------------------------------------------------- + +template +void opl_registers_base::operator_map(operator_mapping &dest) const +{ + if (Revision <= 2) + { + // OPL/OPL2 has a fixed map, all 2 operators + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 3 ), // Channel 0 operators + operator_list( 1, 4 ), // Channel 1 operators + operator_list( 2, 5 ), // Channel 2 operators + operator_list( 6, 9 ), // Channel 3 operators + operator_list( 7, 10 ), // Channel 4 operators + operator_list( 8, 11 ), // Channel 5 operators + operator_list( 12, 15 ), // Channel 6 operators + operator_list( 13, 16 ), // Channel 7 operators + operator_list( 14, 17 ), // Channel 8 operators + } }; + dest = s_fixed_map; + } + else + { + // OPL3/OPL4 can be configured for 2 or 4 operators + uint32_t fourop = fourop_enable(); + + dest.chan[ 0] = bitfield(fourop, 0) ? operator_list( 0, 3, 6, 9 ) : operator_list( 0, 3 ); + dest.chan[ 1] = bitfield(fourop, 1) ? operator_list( 1, 4, 7, 10 ) : operator_list( 1, 4 ); + dest.chan[ 2] = bitfield(fourop, 2) ? operator_list( 2, 5, 8, 11 ) : operator_list( 2, 5 ); + dest.chan[ 3] = bitfield(fourop, 0) ? operator_list() : operator_list( 6, 9 ); + dest.chan[ 4] = bitfield(fourop, 1) ? operator_list() : operator_list( 7, 10 ); + dest.chan[ 5] = bitfield(fourop, 2) ? operator_list() : operator_list( 8, 11 ); + dest.chan[ 6] = operator_list( 12, 15 ); + dest.chan[ 7] = operator_list( 13, 16 ); + dest.chan[ 8] = operator_list( 14, 17 ); + + dest.chan[ 9] = bitfield(fourop, 3) ? operator_list( 18, 21, 24, 27 ) : operator_list( 18, 21 ); + dest.chan[10] = bitfield(fourop, 4) ? operator_list( 19, 22, 25, 28 ) : operator_list( 19, 22 ); + dest.chan[11] = bitfield(fourop, 5) ? operator_list( 20, 23, 26, 29 ) : operator_list( 20, 23 ); + dest.chan[12] = bitfield(fourop, 3) ? operator_list() : operator_list( 24, 27 ); + dest.chan[13] = bitfield(fourop, 4) ? operator_list() : operator_list( 25, 28 ); + dest.chan[14] = bitfield(fourop, 5) ? operator_list() : operator_list( 26, 29 ); + dest.chan[15] = operator_list( 30, 33 ); + dest.chan[16] = operator_list( 31, 34 ); + dest.chan[17] = operator_list( 32, 35 ); + } +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +template +bool opl_registers_base::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // writes to the mode register with high bit set ignore the low bits + if (index == REG_MODE && bitfield(data, 7) != 0) + m_regdata[index] |= 0x80; + else + m_regdata[index] = data; + + // handle writes to the rhythm keyons + if (index == 0xbd) + { + channel = RHYTHM_CHANNEL; + opmask = bitfield(data, 5) ? bitfield(data, 0, 5) : 0; + return true; + } + + // handle writes to the channel keyons + if ((index & 0xf0) == 0xb0) + { + channel = index & 0x0f; + if (channel < 9) + { + if (IsOpl3Plus) + channel += 9 * bitfield(index, 8); + opmask = bitfield(data, 5) ? 15 : 0; + return true; + } + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +static int32_t opl_clock_noise_and_lfo(uint32_t &noise_lfsr, uint16_t &lfo_am_counter, uint16_t &lfo_pm_counter, uint8_t &lfo_am, uint32_t am_depth, uint32_t pm_depth) +{ + // OPL has a 23-bit noise generator for the rhythm section, running at + // a constant rate, used only for percussion input + noise_lfsr <<= 1; + noise_lfsr |= bitfield(noise_lfsr, 23) ^ bitfield(noise_lfsr, 9) ^ bitfield(noise_lfsr, 8) ^ bitfield(noise_lfsr, 1); + + // OPL has two fixed-frequency LFOs, one for AM, one for PM + + // the AM LFO has 210*64 steps; at a nominal 50kHz output, + // this equates to a period of 50000/(210*64) = 3.72Hz + uint32_t am_counter = lfo_am_counter++; + if (am_counter >= 210*64 - 1) + lfo_am_counter = 0; + + // low 8 bits are fractional; depth 0 is divided by 2, while depth 1 is times 2 + int shift = 9 - 2 * am_depth; + + // AM value is the upper bits of the value, inverted across the midpoint + // to produce a triangle + lfo_am = ((am_counter < 105*64) ? am_counter : (210*64+63 - am_counter)) >> shift; + + // the PM LFO has 8192 steps, or a nominal period of 6.1Hz + uint32_t pm_counter = lfo_pm_counter++; + + // PM LFO is broken into 8 chunks, each lasting 1024 steps; the PM value + // depends on the upper bits of FNUM, so this value is a fraction and + // sign to apply to that value, as a 1.3 value + static int8_t const pm_scale[8] = { 8, 4, 0, -4, -8, -4, 0, 4 }; + return pm_scale[bitfield(pm_counter, 10, 3)] >> (pm_depth ^ 1); +} + +template +int32_t opl_registers_base::clock_noise_and_lfo() +{ + return opl_clock_noise_and_lfo(m_noise_lfsr, m_lfo_am_counter, m_lfo_pm_counter, m_lfo_am, lfo_am_depth(), lfo_pm_depth()); +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data; note that this code is +// also used by ymopna_registers, so it must +// handle upper channels cleanly +//------------------------------------------------- + +template +void opl_registers_base::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // set up the easy stuff + cache.waveform = &m_waveform[op_waveform(opoffs) % WAVEFORMS][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // compute the keycode: block_freq is: + // + // 111 | + // 21098|76543210 + // BBBFF|FFFFFFFF + // ^^^?? + // + // the 4-bit keycode uses the top 3 bits plus one of the next two bits + uint32_t keycode = bitfield(block_freq, 10, 3) << 1; + + // lowest bit is determined by note_select(); note that it is + // actually reversed from what the manual says, however + keycode |= bitfield(block_freq, 9 - note_select(), 1); + + // no detune adjustment on OPL + cache.detune = 0; + + // multiple value, as an x.1 value (0 means 0.5) + // replace the low bit with a table lookup to give 0,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15 + uint32_t multiple = op_multiple(opoffs); + cache.multiple = ((multiple & 0xe) | bitfield(0xc2aa, multiple)) * 2; + if (cache.multiple == 0) + cache.multiple = 1; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on block_freq, detune, + // and multiple, so compute it after we've done those + if (op_lfo_pm_enable(opoffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + cache.total_level = op_total_level(opoffs) << 3; + + // pre-add key scale level + uint32_t ksl = op_ksl(opoffs); + if (ksl != 0) + cache.total_level += opl_key_scale_atten(bitfield(block_freq, 10, 3), bitfield(block_freq, 6, 4)) << ksl; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (2 * (op_ksr(opoffs) ^ 1)); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_SUSTAIN] = op_eg_sustain(opoffs) ? 0 : effective_rate(op_release_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_DEPRESS] = 0x3f; +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +static uint32_t opl_compute_phase_step(uint32_t block_freq, uint32_t multiple, int32_t lfo_raw_pm) +{ + // OPL phase calculation has no detuning, but uses FNUMs like + // the OPN version, and computes PM a bit differently + + // extract frequency number as a 12-bit fraction + uint32_t fnum = bitfield(block_freq, 0, 10) << 2; + + // apply the phase adjustment based on the upper 3 bits + // of FNUM and the PM depth parameters + fnum += (lfo_raw_pm * bitfield(block_freq, 7, 3)) >> 1; + + // keep fnum to 12 bits + fnum &= 0xfff; + + // apply block shift to compute phase step + uint32_t block = bitfield(block_freq, 10, 3); + uint32_t phase_step = (fnum << block) >> 2; + + // apply frequency multiplier (which is cached as an x.1 value) + return (phase_step * multiple) >> 1; +} + +template +uint32_t opl_registers_base::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + return opl_compute_phase_step(cache.block_freq, cache.multiple, op_lfo_pm_enable(opoffs) ? lfo_raw_pm : 0); +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +template +std::string opl_registers_base::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = (choffs & 15) + 9 * bitfield(choffs, 8); + uint32_t opnum = (opoffs & 31) - 2 * ((opoffs & 31) / 8) + 18 * bitfield(opoffs, 8); + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(end, "%2u.%02u freq=%04X fb=%u alg=%X mul=%X tl=%02X ksr=%u ns=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u", + chnum, opnum, + ch_block_freq(choffs), + ch_feedback(choffs), + ch_algorithm(choffs), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + note_select(), + op_ksl(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + op_eg_sustain(opoffs)); + + if (OUTPUTS > 1) + end += sprintf(end, " out=%c%c%c%c", + ch_output_0(choffs) ? 'L' : '-', + ch_output_1(choffs) ? 'R' : '-', + ch_output_2(choffs) ? '0' : '-', + ch_output_3(choffs) ? '1' : '-'); + if (op_lfo_am_enable(opoffs) != 0) + end += sprintf(end, " am=%u", lfo_am_depth()); + if (op_lfo_pm_enable(opoffs) != 0) + end += sprintf(end, " pm=%u", lfo_pm_depth()); + if (waveform_enable() && op_waveform(opoffs) != 0) + end += sprintf(end, " wf=%u", op_waveform(opoffs)); + if (is_rhythm(choffs)) + end += sprintf(end, " rhy=1"); + if (DYNAMIC_OPS) + { + operator_mapping map; + operator_map(map); + if (bitfield(map.chan[chnum], 16, 8) != 0xff) + end += sprintf(end, " 4op"); + } + + return buffer; +} + + +//********************************************************* +// OPLL SPECIFICS +//********************************************************* + +//------------------------------------------------- +// opll_registers - constructor +//------------------------------------------------- + +opll_registers::opll_registers() : + m_lfo_am_counter(0), + m_lfo_pm_counter(0), + m_noise_lfsr(1), + m_lfo_am(0) +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + uint16_t zeroval = m_waveform[0][0]; + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index]; + + // initialize the instruments to something sane + for (uint32_t choffs = 0; choffs < CHANNELS; choffs++) + m_chinst[choffs] = &m_regdata[0]; + for (uint32_t opoffs = 0; opoffs < OPERATORS; opoffs++) + m_opinst[opoffs] = &m_regdata[bitfield(opoffs, 0)]; +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +void opll_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opll_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_am_counter); + state.save_restore(m_lfo_pm_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_noise_lfsr); + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPLL this is fixed +//------------------------------------------------- + +void opll_registers::operator_map(operator_mapping &dest) const +{ + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 1 ), // Channel 0 operators + operator_list( 2, 3 ), // Channel 1 operators + operator_list( 4, 5 ), // Channel 2 operators + operator_list( 6, 7 ), // Channel 3 operators + operator_list( 8, 9 ), // Channel 4 operators + operator_list( 10, 11 ), // Channel 5 operators + operator_list( 12, 13 ), // Channel 6 operators + operator_list( 14, 15 ), // Channel 7 operators + operator_list( 16, 17 ), // Channel 8 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array; +// note that this code is also used by +// ymopl3_registers, so it must handle upper +// channels cleanly +//------------------------------------------------- + +bool opll_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + // unclear the address is masked down to 6 bits or if writes above + // the register top are ignored; assuming the latter for now + if (index >= REGISTERS) + return false; + + // write the new data + m_regdata[index] = data; + + // handle writes to the rhythm keyons + if (index == 0x0e) + { + channel = RHYTHM_CHANNEL; + opmask = bitfield(data, 5) ? bitfield(data, 0, 5) : 0; + return true; + } + + // handle writes to the channel keyons + if ((index & 0xf0) == 0x20) + { + channel = index & 0x0f; + if (channel < CHANNELS) + { + opmask = bitfield(data, 4) ? 3 : 0; + return true; + } + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +int32_t opll_registers::clock_noise_and_lfo() +{ + // implementation is the same as OPL with fixed depths + return opl_clock_noise_and_lfo(m_noise_lfsr, m_lfo_am_counter, m_lfo_pm_counter, m_lfo_am, 1, 1); +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data; note that this code is +// also used by ymopna_registers, so it must +// handle upper channels cleanly +//------------------------------------------------- + +void opll_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // first set up the instrument data + uint32_t instrument = ch_instrument(choffs); + if (rhythm_enable() && choffs >= 6) + m_chinst[choffs] = &m_instdata[8 * (15 + (choffs - 6))]; + else + m_chinst[choffs] = (instrument == 0) ? &m_regdata[0] : &m_instdata[8 * (instrument - 1)]; + m_opinst[opoffs] = m_chinst[choffs] + bitfield(opoffs, 0); + + // set up the easy stuff + cache.waveform = &m_waveform[op_waveform(opoffs) % WAVEFORMS][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // compute the keycode: block_freq is: + // + // 11 | + // 1098|76543210 + // BBBF|FFFFFFFF + // ^^^^ + // + // the 4-bit keycode uses the top 4 bits + uint32_t keycode = bitfield(block_freq, 8, 4); + + // no detune adjustment on OPLL + cache.detune = 0; + + // multiple value, as an x.1 value (0 means 0.5) + // replace the low bit with a table lookup to give 0,1,2,3,4,5,6,7,8,9,10,10,12,12,15,15 + uint32_t multiple = op_multiple(opoffs); + cache.multiple = ((multiple & 0xe) | bitfield(0xc2aa, multiple)) * 2; + if (cache.multiple == 0) + cache.multiple = 1; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those + if (op_lfo_pm_enable(opoffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8; for non-rhythm operator 0, this is the total + // level from the instrument data; for other operators it is 4*volume + if (bitfield(opoffs, 0) == 1 || (rhythm_enable() && choffs >= 7)) + cache.total_level = op_volume(opoffs) * 4; + else + cache.total_level = ch_total_level(choffs); + cache.total_level <<= 3; + + // pre-add key scale level + uint32_t ksl = op_ksl(opoffs); + if (ksl != 0) + cache.total_level += opl_key_scale_atten(bitfield(block_freq, 9, 3), bitfield(block_freq, 5, 4)) << ksl; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // The envelope diagram in the YM2413 datasheet gives values for these + // in ms from 0->48dB. The attack/decay tables give values in ms from + // 0->96dB, so to pick an equivalent decay rate, we want to find the + // closest match that is 2x the 0->48dB value: + // + // DP = 10ms (0->48db) -> 20ms (0->96db); decay of 12 gives 19.20ms + // RR = 310ms (0->48db) -> 620ms (0->96db); decay of 7 gives 613.76ms + // RS = 1200ms (0->48db) -> 2400ms (0->96db); decay of 5 gives 2455.04ms + // + // The envelope diagram for percussive sounds (eg_sustain() == 0) also uses + // "RR" to mean both the constant RR above and the Release Rate specified in + // the instrument data. In this case, Relief Pitcher's credit sound bears out + // that the Release Rate is used during sustain, and that the constant RR + // (or RS) is used during the release phase. + constexpr uint8_t DP = 12 * 4; + constexpr uint8_t RR = 7 * 4; + constexpr uint8_t RS = 5 * 4; + + // determine KSR adjustment for envelope rates + uint32_t ksrval = keycode >> (2 * (op_ksr(opoffs) ^ 1)); + cache.eg_rate[EG_DEPRESS] = DP; + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 4, ksrval); + if (op_eg_sustain(opoffs)) + { + cache.eg_rate[EG_SUSTAIN] = 0; + cache.eg_rate[EG_RELEASE] = ch_sustain(choffs) ? RS : effective_rate(op_release_rate(opoffs) * 4, ksrval); + } + else + { + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_release_rate(opoffs) * 4, ksrval); + cache.eg_rate[EG_RELEASE] = ch_sustain(choffs) ? RS : RR; + } +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +uint32_t opll_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // phase step computation is the same as OPL but the block_freq has one + // more bit, which we shift in + return opl_compute_phase_step(cache.block_freq << 1, cache.multiple, op_lfo_pm_enable(opoffs) ? lfo_raw_pm : 0); +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +std::string opll_registers::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = choffs; + uint32_t opnum = opoffs; + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(end, "%u.%02u freq=%04X inst=%X fb=%u mul=%X", + chnum, opnum, + ch_block_freq(choffs), + ch_instrument(choffs), + ch_feedback(choffs), + op_multiple(opoffs)); + + if (bitfield(opoffs, 0) == 1 || (is_rhythm(choffs) && choffs >= 6)) + end += sprintf(end, " vol=%X", op_volume(opoffs)); + else + end += sprintf(end, " tl=%02X", ch_total_level(choffs)); + + end += sprintf(end, " ksr=%u ksl=%u adr=%X/%X/%X sl=%X sus=%u/%u", + op_ksr(opoffs), + op_ksl(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + op_eg_sustain(opoffs), + ch_sustain(choffs)); + + if (op_lfo_am_enable(opoffs)) + end += sprintf(end, " am=1"); + if (op_lfo_pm_enable(opoffs)) + end += sprintf(end, " pm=1"); + if (op_waveform(opoffs) != 0) + end += sprintf(end, " wf=1"); + if (is_rhythm(choffs)) + end += sprintf(end, " rhy=1"); + + return buffer; +} + + + +//********************************************************* +// YM3526 +//********************************************************* + +//------------------------------------------------- +// ym3526 - constructor +//------------------------------------------------- + +ym3526::ym3526(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym3526::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym3526::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym3526::read_status() +{ + return m_fm.status() | 0x06; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym3526::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // status port + result = read_status(); + break; + + case 1: // when A0=1 datasheet says "the data on the bus are not guaranteed" + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym3526::write_address(uint8_t data) +{ + // YM3526 doesn't expose a busy signal, and the datasheets don't indicate + // delays, but all other OPL chips need 12 cycles for address writes + m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale()); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3526::write_data(uint8_t data) +{ + // YM3526 doesn't expose a busy signal, and the datasheets don't indicate + // delays, but all other OPL chips need 84 cycles for data writes + m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale()); + + // write to FM + m_fm.write(m_address, data); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3526::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void ym3526::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YM3526 need verification + m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS); + + // YM3526 uses an external DAC (YM3014) with mantissa/exponent format + // convert to 10.3 floating point value and back to simulate truncation + output->roundtrip_fp(); + } +} + + + +//********************************************************* +// Y8950 +//********************************************************* + +//------------------------------------------------- +// y8950 - constructor +//------------------------------------------------- + +y8950::y8950(ymfm_interface &intf) : + m_address(0), + m_io_ddr(0), + m_fm(intf), + m_adpcm_b(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void y8950::reset() +{ + // reset the engines + m_fm.reset(); + m_adpcm_b.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void y8950::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_io_ddr); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t y8950::read_status() +{ + // start with current FM status, masking out bits we might set + uint8_t status = m_fm.status() & ~(STATUS_ADPCM_B_EOS | STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_PLAYING); + + // insert the live ADPCM status bits + uint8_t adpcm_status = m_adpcm_b.status(); + if ((adpcm_status & adpcm_b_channel::STATUS_EOS) != 0) + status |= STATUS_ADPCM_B_EOS; + if ((adpcm_status & adpcm_b_channel::STATUS_BRDY) != 0) + status |= STATUS_ADPCM_B_BRDY; + if ((adpcm_status & adpcm_b_channel::STATUS_PLAYING) != 0) + status |= STATUS_ADPCM_B_PLAYING; + + // run it through the FM engine to handle interrupts for us + return m_fm.set_reset_status(status, ~status); +} + + +//------------------------------------------------- +// read_data - read the data port +//------------------------------------------------- + +uint8_t y8950::read_data() +{ + uint8_t result = 0xff; + switch (m_address) + { + case 0x05: // keyboard in + result = m_fm.intf().ymfm_external_read(ACCESS_IO, 1); + break; + + case 0x09: // ADPCM data + case 0x1a: + result = m_adpcm_b.read(m_address - 0x07); + break; + + case 0x19: // I/O data + result = m_fm.intf().ymfm_external_read(ACCESS_IO, 0); + break; + + default: + debug::log_unexpected_read_write("Unexpected read from Y8950 data port %02X\n", m_address); + break; + } + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t y8950::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // status port + result = read_status(); + break; + + case 1: // when A0=1 datasheet says "the data on the bus are not guaranteed" + result = read_data(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void y8950::write_address(uint8_t data) +{ + // Y8950 doesn't expose a busy signal, but it does indicate that + // address writes should be no faster than every 12 clocks + m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale()); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void y8950::write_data(uint8_t data) +{ + // Y8950 doesn't expose a busy signal, but it does indicate that + // data writes should be no faster than every 12 clocks for + // registers 00-1A, or every 84 clocks for other registers + m_fm.intf().ymfm_set_busy_end(((m_address <= 0x1a) ? 12 : 84) * m_fm.clock_prescale()); + + // handle special addresses + switch (m_address) + { + case 0x04: // IRQ control + m_fm.write(m_address, data); + read_status(); + break; + + case 0x06: // keyboard out + m_fm.intf().ymfm_external_write(ACCESS_IO, 1, data); + break; + + case 0x08: // split FM/ADPCM-B + m_adpcm_b.write(m_address - 0x07, (data & 0x0f) | 0x80); + m_fm.write(m_address, data & 0xc0); + break; + + case 0x07: // ADPCM-B registers + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x15: + case 0x16: + case 0x17: + m_adpcm_b.write(m_address - 0x07, data); + break; + + case 0x18: // I/O direction + m_io_ddr = data & 0x0f; + break; + + case 0x19: // I/O data + m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data & m_io_ddr); + break; + + default: // everything else to FM + m_fm.write(m_address, data); + break; + } +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void y8950::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void y8950::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + m_adpcm_b.clock(); + + // update the FM content; clipping need verification + m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS); + + // mix in the ADPCM; ADPCM-B is stereo, but only one channel + // not sure how it's wired up internally + m_adpcm_b.output(*output, 3); + + // Y8950 uses an external DAC (YM3014) with mantissa/exponent format + // convert to 10.3 floating point value and back to simulate truncation + output->roundtrip_fp(); + } +} + + + +//********************************************************* +// YM3812 +//********************************************************* + +//------------------------------------------------- +// ym3812 - constructor +//------------------------------------------------- + +ym3812::ym3812(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym3812::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym3812::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym3812::read_status() +{ + return m_fm.status() | 0x06; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym3812::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // status port + result = read_status(); + break; + + case 1: // "inhibit" according to datasheet + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym3812::write_address(uint8_t data) +{ + // YM3812 doesn't expose a busy signal, but it does indicate that + // address writes should be no faster than every 12 clocks + m_fm.intf().ymfm_set_busy_end(12 * m_fm.clock_prescale()); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3812::write_data(uint8_t data) +{ + // YM3812 doesn't expose a busy signal, but it does indicate that + // data writes should be no faster than every 84 clocks + m_fm.intf().ymfm_set_busy_end(84 * m_fm.clock_prescale()); + + // write to FM + m_fm.write(m_address, data); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3812::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void ym3812::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YM3812 need verification + m_fm.output(output->clear(), 1, 32767, fm_engine::ALL_CHANNELS); + + // YM3812 uses an external DAC (YM3014) with mantissa/exponent format + // convert to 10.3 floating point value and back to simulate truncation + output->roundtrip_fp(); + } +} + + + +//********************************************************* +// YMF262 +//********************************************************* + +//------------------------------------------------- +// ymf262 - constructor +//------------------------------------------------- + +ymf262::ymf262(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ymf262::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ymf262::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ymf262::read_status() +{ + return m_fm.status(); +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ymf262::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 3) + { + case 0: // status port + result = read_status(); + break; + + case 1: + case 2: + case 3: + debug::log_unexpected_read_write("Unexpected read from YMF262 offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ymf262::write_address(uint8_t data) +{ + // YMF262 doesn't expose a busy signal, but it does indicate that + // address writes should be no faster than every 32 clocks + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write_data - handle a write to the data +// register +//------------------------------------------------- + +void ymf262::write_data(uint8_t data) +{ + // YMF262 doesn't expose a busy signal, but it does indicate that + // data writes should be no faster than every 32 clocks + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); + + // write to FM + m_fm.write(m_address, data); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf262::write_address_hi(uint8_t data) +{ + // YMF262 doesn't expose a busy signal, but it does indicate that + // address writes should be no faster than every 32 clocks + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); + + // just set the address + m_address = data | 0x100; + + // tests reveal that in compatibility mode, upper bit is masked + // except for register 0x105 + if (m_fm.regs().newflag() == 0 && m_address != 0x105) + m_address &= 0xff; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ymf262::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // address port + write_address_hi(data); + break; + + case 3: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void ymf262::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YMF262 need verification + m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // YMF262 output is 16-bit offset serial via YAC512 DAC + output->clamp16(); + } +} + + + +//********************************************************* +// YMF289B +//********************************************************* + +// YMF289B is a YMF262 with the following changes: +// * "Power down" mode added +// * Bulk register clear added +// * Busy flag added to the status register +// * Shorter busy times +// * All registers can be read +// * Only 2 outputs exposed + +//------------------------------------------------- +// ymf289b - constructor +//------------------------------------------------- + +ymf289b::ymf289b(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ymf289b::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ymf289b::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ymf289b::read_status() +{ + uint8_t result = m_fm.status(); + + // YMF289B adds a busy flag + if (ymf289b_mode() && m_fm.intf().ymfm_is_busy()) + result |= STATUS_BUSY_FLAGS; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ymf289b::read_data() +{ + uint8_t result = 0xff; + + // YMF289B can read register data back + if (ymf289b_mode()) + result = m_fm.regs().read(m_address); + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ymf289b::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 3) + { + case 0: // status port + result = read_status(); + break; + + case 1: // data port + result = read_data(); + break; + + case 2: + case 3: + debug::log_unexpected_read_write("Unexpected read from YMF289B offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ymf289b::write_address(uint8_t data) +{ + m_address = data; + + // count busy time + m_fm.intf().ymfm_set_busy_end(56); +} + + +//------------------------------------------------- +// write_data - handle a write to the data +// register +//------------------------------------------------- + +void ymf289b::write_data(uint8_t data) +{ + // write to FM + m_fm.write(m_address, data); + + // writes to 0x108 with the CLR flag set clear the registers + if (m_address == 0x108 && bitfield(data, 2) != 0) + m_fm.regs().reset(); + + // count busy time + m_fm.intf().ymfm_set_busy_end(56); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf289b::write_address_hi(uint8_t data) +{ + // just set the address + m_address = data | 0x100; + + // tests reveal that in compatibility mode, upper bit is masked + // except for register 0x105 + if (m_fm.regs().newflag() == 0 && m_address != 0x105) + m_address &= 0xff; + + // count busy time + m_fm.intf().ymfm_set_busy_end(56); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ymf289b::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // address port + write_address_hi(data); + break; + + case 3: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate samples of sound +//------------------------------------------------- + +void ymf289b::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YMF262 need verification + fm_engine::output_data full; + m_fm.output(full.clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // YMF278B output is 16-bit offset serial via YAC512 DAC, but + // only 2 of the 4 outputs are exposed + output->data[0] = full.data[0]; + output->data[1] = full.data[1]; + output->clamp16(); + } +} + + + +//********************************************************* +// YMF278B +//********************************************************* + +//------------------------------------------------- +// ymf278b - constructor +//------------------------------------------------- + +ymf278b::ymf278b(ymfm_interface &intf) : + m_address(0), + m_fm_pos(0), + m_load_remaining(0), + m_next_status_id(false), + m_fm(intf), + m_pcm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ymf278b::reset() +{ + // reset the engines + m_fm.reset(); + m_pcm.reset(); + + // next status read will return ID + m_next_status_id = true; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ymf278b::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_fm_pos); + state.save_restore(m_load_remaining); + state.save_restore(m_next_status_id); + m_fm.save_restore(state); + m_pcm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ymf278b::read_status() +{ + uint8_t result; + + // first status read after initialization returns a chip ID, which + // varies based on the "new" flags, indicating the mode + if (m_next_status_id) + { + if (m_fm.regs().new2flag()) + result = 0x02; + else if (m_fm.regs().newflag()) + result = 0x00; + else + result = 0x06; + m_next_status_id = false; + } + else + { + result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= STATUS_BUSY; + if (m_load_remaining != 0) + result |= STATUS_LD; + + // if new2 flag is not set, we're in OPL2 or OPL3 mode + if (!m_fm.regs().new2flag()) + result &= ~(STATUS_BUSY | STATUS_LD); + } + return result; +} + + +//------------------------------------------------- +// write_data_pcm - handle a write to the PCM data +// register +//------------------------------------------------- + +uint8_t ymf278b::read_data_pcm() +{ + // write to FM + if (bitfield(m_address, 9) != 0) + return m_pcm.read(m_address & 0xff); + return 0; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ymf278b::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 7) + { + case 0: // status port + result = read_status(); + break; + + case 5: // PCM data port + result = read_data_pcm(); + break; + + default: + debug::log_unexpected_read_write("Unexpected read from ymf278b offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ymf278b::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write_data - handle a write to the data +// register +//------------------------------------------------- + +void ymf278b::write_data(uint8_t data) +{ + // write to FM + if (bitfield(m_address, 9) == 0) + { + uint8_t old = m_fm.regs().new2flag(); + m_fm.write(m_address, data); + + // changing NEW2 from 0->1 causes the next status read to + // return the chip ID + if (old == 0 && m_fm.regs().new2flag() != 0) + m_next_status_id = true; + } + + // BUSY goes for 56 clocks on FM writes + m_fm.intf().ymfm_set_busy_end(56); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf278b::write_address_hi(uint8_t data) +{ + // just set the address + m_address = data | 0x100; + + // YMF262, in compatibility mode, treats the upper bit as masked + // except for register 0x105; assuming YMF278B works the same way? + if (m_fm.regs().newflag() == 0 && m_address != 0x105) + m_address &= 0xff; +} + + +//------------------------------------------------- +// write_address_pcm - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf278b::write_address_pcm(uint8_t data) +{ + // just set the address + m_address = data | 0x200; +} + + +//------------------------------------------------- +// write_data_pcm - handle a write to the PCM data +// register +//------------------------------------------------- + +void ymf278b::write_data_pcm(uint8_t data) +{ + // ignore data writes if new2 is not yet set + if (m_fm.regs().new2flag() == 0) + return; + + // write to FM + if (bitfield(m_address, 9) != 0) + { + uint8_t addr = m_address & 0xff; + m_pcm.write(addr, data); + + // writes to the waveform number cause loads to happen for "about 300usec" + // which is ~13 samples at the nominal output frequency of 44.1kHz + if (addr >= 0x08 && addr <= 0x1f) + m_load_remaining = 13; + } + + // BUSY goes for 88 clocks on PCM writes + m_fm.intf().ymfm_set_busy_end(88); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ymf278b::write(uint32_t offset, uint8_t data) +{ + switch (offset & 7) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // address port + write_address_hi(data); + break; + + case 3: // data port + write_data(data); + break; + + case 4: // PCM address port + write_address_pcm(data); + break; + + case 5: // PCM address port + write_data_pcm(data); + break; + + default: + debug::log_unexpected_read_write("Unexpected write to ymf278b offset %d\n", offset & 7); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ymf278b::generate(output_data *output, uint32_t numsamples) +{ + static const int16_t s_mix_scale[8] = { 0x7fa, 0x5a4, 0x3fd, 0x2d2, 0x1fe, 0x169, 0xff, 0 }; + int32_t const pcm_l = s_mix_scale[m_pcm.regs().mix_pcm_l()]; + int32_t const pcm_r = s_mix_scale[m_pcm.regs().mix_pcm_r()]; + int32_t const fm_l = s_mix_scale[m_pcm.regs().mix_fm_l()]; + int32_t const fm_r = s_mix_scale[m_pcm.regs().mix_fm_r()]; + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm_pos += FM_EXTRA_SAMPLE_STEP; + if (m_fm_pos >= FM_EXTRA_SAMPLE_THRESH) + { + m_fm.clock(fm_engine::ALL_CHANNELS); + m_fm_pos -= FM_EXTRA_SAMPLE_THRESH; + } + m_fm.clock(fm_engine::ALL_CHANNELS); + m_pcm.clock(pcm_engine::ALL_CHANNELS); + + // update the FM content; mixing details for YMF278B need verification + fm_engine::output_data fmout; + m_fm.output(fmout.clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // update the PCM content + pcm_engine::output_data pcmout; + m_pcm.output(pcmout.clear(), pcm_engine::ALL_CHANNELS); + + // DO0 output: FM channels 2+3 only + output->data[0] = fmout.data[2]; + output->data[1] = fmout.data[3]; + + // DO1 output: wavetable channels 2+3 only + output->data[2] = pcmout.data[2]; + output->data[3] = pcmout.data[3]; + + // DO2 output: mixed FM channels 0+1 and wavetable channels 0+1 + output->data[4] = (fmout.data[0] * fm_l + pcmout.data[0] * pcm_l) >> 11; + output->data[5] = (fmout.data[1] * fm_r + pcmout.data[1] * pcm_r) >> 11; + + // YMF278B output is 16-bit 2s complement serial + output->clamp16(); + } + + // decrement the load waiting count + if (m_load_remaining > 0) + m_load_remaining -= std::min(m_load_remaining, numsamples); +} + + + +//********************************************************* +// OPLL BASE +//********************************************************* + +//------------------------------------------------- +// opll_base - constructor +//------------------------------------------------- + +opll_base::opll_base(ymfm_interface &intf, uint8_t const *instrument_data) : + m_address(0), + m_fm(intf) +{ + m_fm.regs().set_instrument_data(instrument_data); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void opll_base::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opll_base::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void opll_base::write_address(uint8_t data) +{ + // OPLL doesn't expose a busy signal, but datasheets are pretty consistent + // in indicating that address writes should be no faster than every 12 clocks + m_fm.intf().ymfm_set_busy_end(12); + + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void opll_base::write_data(uint8_t data) +{ + // OPLL doesn't expose a busy signal, but datasheets are pretty consistent + // in indicating that address writes should be no faster than every 84 clocks + m_fm.intf().ymfm_set_busy_end(84); + + // write to FM + m_fm.write(m_address, data); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void opll_base::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void opll_base::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; OPLL has a built-in 9-bit DAC + m_fm.output(output->clear(), 5, 256, fm_engine::ALL_CHANNELS); + + // final output is multiplexed; we don't simulate that here except + // to average over everything + output->data[0] = (output->data[0] * 128) / 9; + output->data[1] = (output->data[1] * 128) / 9; + } +} + + + +//********************************************************* +// YM2413 +//********************************************************* + +//------------------------------------------------- +// ym2413 - constructor +//------------------------------------------------- + +ym2413::ym2413(ymfm_interface &intf, uint8_t const *instrument_data) : + opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments) +{ +}; + +// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches +uint8_t const ym2413::s_default_instruments[] = +{ + //April 2015 David Viens, tweaked May 19-21th 2015 Hubert Lamontagne + 0x71, 0x61, 0x1E, 0x17, 0xEF, 0x7F, 0x00, 0x17, //Violin + 0x13, 0x41, 0x1A, 0x0D, 0xF8, 0xF7, 0x23, 0x13, //Guitar + 0x13, 0x01, 0x99, 0x00, 0xF2, 0xC4, 0x11, 0x23, //Piano + 0x31, 0x61, 0x0E, 0x07, 0x98, 0x64, 0x70, 0x27, //Flute + 0x22, 0x21, 0x1E, 0x06, 0xBF, 0x76, 0x00, 0x28, //Clarinet + 0x31, 0x22, 0x16, 0x05, 0xE0, 0x71, 0x0F, 0x18, //Oboe + 0x21, 0x61, 0x1D, 0x07, 0x82, 0x8F, 0x10, 0x07, //Trumpet + 0x23, 0x21, 0x2D, 0x14, 0xFF, 0x7F, 0x00, 0x07, //Organ + 0x41, 0x61, 0x1B, 0x06, 0x64, 0x65, 0x10, 0x17, //Horn + 0x61, 0x61, 0x0B, 0x18, 0x85, 0xFF, 0x81, 0x07, //Synthesizer + 0x13, 0x01, 0x83, 0x11, 0xFA, 0xE4, 0x10, 0x04, //Harpsichord + 0x17, 0x81, 0x23, 0x07, 0xF8, 0xF8, 0x22, 0x12, //Vibraphone + 0x61, 0x50, 0x0C, 0x05, 0xF2, 0xF5, 0x29, 0x42, //Synthesizer Bass + 0x01, 0x01, 0x54, 0x03, 0xC3, 0x92, 0x03, 0x02, //Acoustic Bass + 0x41, 0x41, 0x89, 0x03, 0xF1, 0xE5, 0x11, 0x13, //Electric Guitar + 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1 + 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2 + 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3 +}; + + + +//********************************************************* +// YM2423 +//********************************************************* + +//------------------------------------------------- +// ym2423 - constructor +//------------------------------------------------- + +ym2423::ym2423(ymfm_interface &intf, uint8_t const *instrument_data) : + opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments) +{ +}; + +// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches +uint8_t const ym2423::s_default_instruments[] = +{ + // May 4-6 2016 Hubert Lamontagne + // Doesn't seem to have any diff between opllx-x and opllx-y + // Drums seem identical to regular opll + 0x61, 0x61, 0x1B, 0x07, 0x94, 0x5F, 0x10, 0x06, //1 Strings Saw wave with vibrato Violin + 0x93, 0xB1, 0x51, 0x04, 0xF3, 0xF2, 0x70, 0xFB, //2 Guitar Jazz GuitarPiano + 0x41, 0x21, 0x11, 0x85, 0xF2, 0xF2, 0x70, 0x75, //3 Electric Guitar Same as OPLL No.15 Synth + 0x93, 0xB2, 0x28, 0x07, 0xF3, 0xF2, 0x70, 0xB4, //4 Electric Piano 2 Slow attack, tremoloDing-a-ling + 0x72, 0x31, 0x97, 0x05, 0x51, 0x6F, 0x60, 0x09, //5 Flute Same as OPLL No.4Clarinet + 0x13, 0x30, 0x18, 0x06, 0xF7, 0xF4, 0x50, 0x85, //6 Marimba Also be used as steel drumXyophone + 0x51, 0x31, 0x1C, 0x07, 0x51, 0x71, 0x20, 0x26, //7 Trumpet Same as OPLL No.7Trumpet + 0x41, 0xF4, 0x1B, 0x07, 0x74, 0x34, 0x00, 0x06, //8 Harmonica Harmonica synth + 0x50, 0x30, 0x4D, 0x03, 0x42, 0x65, 0x20, 0x06, //9 Tuba Tuba + 0x40, 0x20, 0x10, 0x85, 0xF3, 0xF5, 0x20, 0x04, //10 Synth Brass 2 Synth sweep + 0x61, 0x61, 0x1B, 0x07, 0xC5, 0x96, 0xF3, 0xF6, //11 Short Saw Saw wave with short envelopeSynth hit + 0xF9, 0xF1, 0xDC, 0x00, 0xF5, 0xF3, 0x77, 0xF2, //12 Vibraphone Bright vibraphoneVibes + 0x60, 0xA2, 0x91, 0x03, 0x94, 0xC1, 0xF7, 0xF7, //13 Electric Guitar 2 Clean guitar with feedbackHarmonic bass + 0x30, 0x30, 0x17, 0x06, 0xF3, 0xF1, 0xB7, 0xFC, //14 Synth Bass 2Snappy bass + 0x31, 0x36, 0x0D, 0x05, 0xF2, 0xF4, 0x27, 0x9C, //15 Sitar Also be used as ShamisenBanjo + 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1 + 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2 + 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3 +}; + + + +//********************************************************* +// YMF281 +//********************************************************* + +//------------------------------------------------- +// ymf281 - constructor +//------------------------------------------------- + +ymf281::ymf281(ymfm_interface &intf, uint8_t const *instrument_data) : + opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments) +{ +}; + +// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches +uint8_t const ymf281::s_default_instruments[] = +{ + // May 14th 2015 Hubert Lamontagne + 0x72, 0x21, 0x1A, 0x07, 0xF6, 0x64, 0x01, 0x16, // Clarinet ~~ Electric String Square wave with vibrato + 0x00, 0x10, 0x45, 0x00, 0xF6, 0x83, 0x73, 0x63, // Synth Bass ~~ Bow wow Triangular wave + 0x13, 0x01, 0x96, 0x00, 0xF1, 0xF4, 0x31, 0x23, // Piano ~~ Electric Guitar Despite of its name, same as Piano of YM2413. + 0x71, 0x21, 0x0B, 0x0F, 0xF9, 0x64, 0x70, 0x17, // Flute ~~ Organ Sine wave + 0x02, 0x21, 0x1E, 0x06, 0xF9, 0x76, 0x00, 0x28, // Square Wave ~~ Clarinet Same as ones of YM2413. + 0x00, 0x61, 0x82, 0x0E, 0xF9, 0x61, 0x20, 0x27, // Space Oboe ~~ Saxophone Saw wave with vibrato + 0x21, 0x61, 0x1B, 0x07, 0x84, 0x8F, 0x10, 0x07, // Trumpet ~~ Trumpet Same as ones of YM2413. + 0x37, 0x32, 0xCA, 0x02, 0x66, 0x64, 0x47, 0x29, // Wow Bell ~~ Street Organ Calliope + 0x41, 0x41, 0x07, 0x03, 0xF5, 0x70, 0x51, 0xF5, // Electric Guitar ~~ Synth Brass Same as Synthesizer of YM2413. + 0x36, 0x01, 0x5E, 0x07, 0xF2, 0xF3, 0xF7, 0xF7, // Vibes ~~ Electric Piano Simulate of Rhodes Piano + 0x00, 0x00, 0x18, 0x06, 0xC5, 0xF3, 0x20, 0xF2, // Bass ~~ Bass Electric bass + 0x17, 0x81, 0x25, 0x07, 0xF7, 0xF3, 0x21, 0xF7, // Vibraphone ~~ Vibraphone Same as ones of YM2413. + 0x35, 0x64, 0x00, 0x00, 0xFF, 0xF3, 0x77, 0xF5, // Vibrato Bell ~~ Chime Bell + 0x11, 0x31, 0x00, 0x07, 0xDD, 0xF3, 0xFF, 0xFB, // Click Sine ~~ Tom Tom II Tom + 0x3A, 0x21, 0x00, 0x07, 0x95, 0x84, 0x0F, 0xF5, // Noise and Tone ~~ Noise for S.E. + 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1 + 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2 + 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3 +}; + + + +//********************************************************* +// DS1001 +//********************************************************* + +//------------------------------------------------- +// ds1001 - constructor +//------------------------------------------------- + +ds1001::ds1001(ymfm_interface &intf, uint8_t const *instrument_data) : + opll_base(intf, (instrument_data != nullptr) ? instrument_data : s_default_instruments) +{ +}; + +// table below taken from https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches +uint8_t const ds1001::s_default_instruments[] = +{ + // May 15th 2015 Hubert Lamontagne & David Viens + 0x03, 0x21, 0x05, 0x06, 0xC8, 0x81, 0x42, 0x27, // Buzzy Bell + 0x13, 0x41, 0x14, 0x0D, 0xF8, 0xF7, 0x23, 0x12, // Guitar + 0x31, 0x11, 0x08, 0x08, 0xFA, 0xC2, 0x28, 0x22, // Wurly + 0x31, 0x61, 0x0C, 0x07, 0xF8, 0x64, 0x60, 0x27, // Flute + 0x22, 0x21, 0x1E, 0x06, 0xFF, 0x76, 0x00, 0x28, // Clarinet + 0x02, 0x01, 0x05, 0x00, 0xAC, 0xF2, 0x03, 0x02, // Synth + 0x21, 0x61, 0x1D, 0x07, 0x82, 0x8F, 0x10, 0x07, // Trumpet + 0x23, 0x21, 0x22, 0x17, 0xFF, 0x73, 0x00, 0x17, // Organ + 0x15, 0x11, 0x25, 0x00, 0x41, 0x71, 0x00, 0xF1, // Bells + 0x95, 0x01, 0x10, 0x0F, 0xB8, 0xAA, 0x50, 0x02, // Vibes + 0x17, 0xC1, 0x5E, 0x07, 0xFA, 0xF8, 0x22, 0x12, // Vibraphone + 0x71, 0x23, 0x11, 0x06, 0x65, 0x74, 0x10, 0x16, // Tutti + 0x01, 0x02, 0xD3, 0x05, 0xF3, 0x92, 0x83, 0xF2, // Fretless + 0x61, 0x63, 0x0C, 0x00, 0xA4, 0xFF, 0x30, 0x06, // Synth Bass + 0x21, 0x62, 0x0D, 0x00, 0xA1, 0xFF, 0x50, 0x08, // Sweep + 0x01, 0x01, 0x18, 0x0F, 0xDF, 0xF8, 0x6A, 0x6D, //rhythm 1 + 0x01, 0x01, 0x00, 0x00, 0xC8, 0xD8, 0xA7, 0x48, //rhythm 2 + 0x05, 0x01, 0x00, 0x00, 0xF8, 0xAA, 0x59, 0x55 //rhythm 3 +}; + + +//********************************************************* +// EXPLICIT INSTANTIATION +//********************************************************* + +template class opl_registers_base<4>; +template class fm_engine_base>; + +} diff --git a/src/sound/ymfm/ymfm_opl.h b/src/sound/ymfm/ymfm_opl.h new file mode 100644 index 000000000..843e5b274 --- /dev/null +++ b/src/sound/ymfm/ymfm_opl.h @@ -0,0 +1,902 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPL_H +#define YMFM_OPL_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_adpcm.h" +#include "ymfm_fm.h" +#include "ymfm_pcm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opl_registers_base + +// +// OPL/OPL2/OPL3/OPL4 register map: +// +// System-wide registers: +// 01 xxxxxxxx Test register +// --x----- Enable OPL compatibility mode [OPL2 only] (1 = enable) +// 02 xxxxxxxx Timer A value (4 * OPN) +// 03 xxxxxxxx Timer B value +// 04 x------- RST +// -x------ Mask timer A +// --x----- Mask timer B +// ------x- Load timer B +// -------x Load timer A +// 08 x------- CSM mode [OPL/OPL2 only] +// -x------ Note select +// BD x------- AM depth +// -x------ PM depth +// --x----- Rhythm enable +// ---x---- Bass drum key on +// ----x--- Snare drum key on +// -----x-- Tom key on +// ------x- Top cymbal key on +// -------x High hat key on +// 101 --xxxxxx Test register 2 [OPL3 only] +// 104 --x----- Channel 6 4-operator mode [OPL3 only] +// ---x---- Channel 5 4-operator mode [OPL3 only] +// ----x--- Channel 4 4-operator mode [OPL3 only] +// -----x-- Channel 3 4-operator mode [OPL3 only] +// ------x- Channel 2 4-operator mode [OPL3 only] +// -------x Channel 1 4-operator mode [OPL3 only] +// 105 -------x New [OPL3 only] +// ------x- New2 [OPL4 only] +// +// Per-channel registers (channel in address bits 0-3) +// Note that all these apply to address+100 as well on OPL3+ +// A0-A8 xxxxxxxx F-number (low 8 bits) +// B0-B8 --x----- Key on +// ---xxx-- Block (octvate, 0-7) +// ------xx F-number (high two bits) +// C0-C8 x------- CHD output (to DO0 pin) [OPL3+ only] +// -x------ CHC output (to DO0 pin) [OPL3+ only] +// --x----- CHB output (mixed right, to DO2 pin) [OPL3+ only] +// ---x---- CHA output (mixed left, to DO2 pin) [OPL3+ only] +// ----xxx- Feedback level for operator 1 (0-7) +// -------x Operator connection algorithm +// +// Per-operator registers (operator in bits 0-5) +// Note that all these apply to address+100 as well on OPL3+ +// 20-35 x------- AM enable +// -x------ PM enable (VIB) +// --x----- EG type +// ---x---- Key scale rate +// ----xxxx Multiple value (0-15) +// 40-55 xx------ Key scale level (0-3) +// --xxxxxx Total level (0-63) +// 60-75 xxxx---- Attack rate (0-15) +// ----xxxx Decay rate (0-15) +// 80-95 xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// E0-F5 ------xx Wave select (0-3) [OPL2 only] +// -----xxx Wave select (0-7) [OPL3+ only] +// + +template +class opl_registers_base : public fm_registers_base +{ + static constexpr bool IsOpl2 = (Revision == 2); + static constexpr bool IsOpl2Plus = (Revision >= 2); + static constexpr bool IsOpl3Plus = (Revision >= 3); + static constexpr bool IsOpl4Plus = (Revision >= 4); + +public: + // constants + static constexpr uint32_t OUTPUTS = IsOpl3Plus ? 4 : 1; + static constexpr uint32_t CHANNELS = IsOpl3Plus ? 18 : 9; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 2; + static constexpr uint32_t WAVEFORMS = IsOpl3Plus ? 8 : (IsOpl2Plus ? 4 : 1); + static constexpr uint32_t REGISTERS = IsOpl3Plus ? 0x200 : 0x100; + static constexpr uint32_t REG_MODE = 0x04; + static constexpr uint32_t DEFAULT_PRESCALE = IsOpl4Plus ? 19 : (IsOpl3Plus ? 8 : 4); + static constexpr uint32_t EG_CLOCK_DIVIDER = 1; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr bool DYNAMIC_OPS = IsOpl3Plus; + static constexpr bool MODULATOR_DELAY = !IsOpl3Plus; + static constexpr uint8_t STATUS_TIMERA = 0x40; + static constexpr uint8_t STATUS_TIMERB = 0x20; + static constexpr uint8_t STATUS_BUSY = 0; + static constexpr uint8_t STATUS_IRQ = 0x80; + + // constructor + opl_registers_base(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + if (!IsOpl3Plus) + return chnum; + else + return (chnum % 9) + 0x100 * (chnum / 9); + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + if (!IsOpl3Plus) + return opnum + 2 * (opnum / 6); + else + return (opnum % 18) + 2 * ((opnum % 18) / 6) + 0x100 * (opnum / 18); + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // OPL4 apparently can read back FM registers? + uint8_t read(uint16_t index) const { return m_regdata[index]; } + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // reset the LFO + void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; } + + // return the AM offset from LFO for the given channel + // on OPL this is just a fixed value + uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; } + + // return LFO/noise states + uint32_t noise_state() const { return m_noise_lfsr >> 23; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t test() const { return byte(0x01, 0, 8); } + uint32_t waveform_enable() const { return IsOpl2 ? byte(0x01, 5, 1) : (IsOpl3Plus ? 1 : 0); } + uint32_t timer_a_value() const { return byte(0x02, 0, 8) * 4; } // 8->10 bits + uint32_t timer_b_value() const { return byte(0x03, 0, 8); } + uint32_t status_mask() const { return byte(0x04, 0, 8) & 0x78; } + uint32_t irq_reset() const { return byte(0x04, 7, 1); } + uint32_t reset_timer_b() const { return byte(0x04, 7, 1) | byte(0x04, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x04, 7, 1) | byte(0x04, 6, 1); } + uint32_t enable_timer_b() const { return 1; } + uint32_t enable_timer_a() const { return 1; } + uint32_t load_timer_b() const { return byte(0x04, 1, 1); } + uint32_t load_timer_a() const { return byte(0x04, 0, 1); } + uint32_t csm() const { return IsOpl3Plus ? 0 : byte(0x08, 7, 1); } + uint32_t note_select() const { return byte(0x08, 6, 1); } + uint32_t lfo_am_depth() const { return byte(0xbd, 7, 1); } + uint32_t lfo_pm_depth() const { return byte(0xbd, 6, 1); } + uint32_t rhythm_enable() const { return byte(0xbd, 5, 1); } + uint32_t rhythm_keyon() const { return byte(0xbd, 4, 0); } + uint32_t newflag() const { return IsOpl3Plus ? byte(0x105, 0, 1) : 0; } + uint32_t new2flag() const { return IsOpl4Plus ? byte(0x105, 1, 1) : 0; } + uint32_t fourop_enable() const { return IsOpl3Plus ? byte(0x104, 0, 6) : 0; } + + // per-channel registers + uint32_t ch_block_freq(uint32_t choffs) const { return word(0xb0, 0, 5, 0xa0, 0, 8, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0xc0, 1, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xc0, 0, 1, choffs) | (IsOpl3Plus ? (8 | (byte(0xc3, 0, 1, choffs) << 1)) : 0); } + uint32_t ch_output_any(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 4) : 1; } + uint32_t ch_output_0(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 1) : 1; } + uint32_t ch_output_1(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 5, 1) : (IsOpl3Plus ? 1 : 0); } + uint32_t ch_output_2(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 6, 1) : 0; } + uint32_t ch_output_3(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 7, 1) : 0; } + + // per-operator registers + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0x20, 7, 1, opoffs); } + uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return byte(0x20, 6, 1, opoffs); } + uint32_t op_eg_sustain(uint32_t opoffs) const { return byte(0x20, 5, 1, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x20, 4, 1, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x20, 0, 4, opoffs); } + uint32_t op_ksl(uint32_t opoffs) const { uint32_t temp = byte(0x40, 6, 2, opoffs); return bitfield(temp, 1) | (bitfield(temp, 0) << 1); } + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x60, 4, 4, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 4, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return IsOpl2Plus ? byte(0xe0, 0, newflag() ? 3 : 2, opoffs) : 0; } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // helper to determine if the this channel is an active rhythm channel + bool is_rhythm(uint32_t choffs) const + { + return rhythm_enable() && (choffs >= 6 && choffs <= 8); + } + + // internal state + uint16_t m_lfo_am_counter; // LFO AM counter + uint16_t m_lfo_pm_counter; // LFO PM counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_lfo_am; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + +using opl_registers = opl_registers_base<1>; +using opl2_registers = opl_registers_base<2>; +using opl3_registers = opl_registers_base<3>; +using opl4_registers = opl_registers_base<4>; + + + +// ======================> opll_registers + +// +// OPLL register map: +// +// System-wide registers: +// 0E --x----- Rhythm enable +// ---x---- Bass drum key on +// ----x--- Snare drum key on +// -----x-- Tom key on +// ------x- Top cymbal key on +// -------x High hat key on +// 0F xxxxxxxx Test register +// +// Per-channel registers (channel in address bits 0-3) +// 10-18 xxxxxxxx F-number (low 8 bits) +// 20-28 --x----- Sustain on +// ---x---- Key on +// --- xxx- Block (octvate, 0-7) +// -------x F-number (high bit) +// 30-38 xxxx---- Instrument selection +// ----xxxx Volume +// +// User instrument registers (for carrier, modulator operators) +// 00-01 x------- AM enable +// -x------ PM enable (VIB) +// --x----- EG type +// ---x---- Key scale rate +// ----xxxx Multiple value (0-15) +// 02 xx------ Key scale level (carrier, 0-3) +// --xxxxxx Total level (modulator, 0-63) +// 03 xx------ Key scale level (modulator, 0-3) +// ---x---- Rectified wave (carrier) +// ----x--- Rectified wave (modulator) +// -----xxx Feedback level for operator 1 (0-7) +// 04-05 xxxx---- Attack rate (0-15) +// ----xxxx Decay rate (0-15) +// 06-07 xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// +// Internal (fake) registers: +// 40-48 xxxxxxxx Current instrument base address +// 4E-5F xxxxxxxx Current instrument base address + operator slot (0/1) +// 70-FF xxxxxxxx Data for instruments (1-16 plus 3 drums) +// + +class opll_registers : public fm_registers_base +{ +public: + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 9; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 2; + static constexpr uint32_t WAVEFORMS = 2; + static constexpr uint32_t REGISTERS = 0x40; + static constexpr uint32_t REG_MODE = 0x3f; + static constexpr uint32_t DEFAULT_PRESCALE = 4; + static constexpr uint32_t EG_CLOCK_DIVIDER = 1; + static constexpr uint32_t CSM_TRIGGER_MASK = 0; + static constexpr bool EG_HAS_DEPRESS = true; + static constexpr bool MODULATOR_DELAY = true; + static constexpr uint8_t STATUS_TIMERA = 0; + static constexpr uint8_t STATUS_TIMERB = 0; + static constexpr uint8_t STATUS_BUSY = 0; + static constexpr uint8_t STATUS_IRQ = 0; + + // OPLL-specific constants + static constexpr uint32_t INSTDATA_SIZE = 0x90; + + // constructor + opll_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // read a register value + uint8_t read(uint16_t index) const { return m_regdata[index]; } + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // reset the LFO + void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; } + + // return the AM offset from LFO for the given channel + // on OPL this is just a fixed value + uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; } + + // return LFO/noise states + uint32_t noise_state() const { return m_noise_lfsr >> 23; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // set the instrument data + void set_instrument_data(uint8_t const *data) + { + std::copy_n(data, INSTDATA_SIZE, &m_instdata[0]); + } + + // system-wide registers + uint32_t rhythm_enable() const { return byte(0x0e, 5, 1); } + uint32_t rhythm_keyon() const { return byte(0x0e, 4, 0); } + uint32_t test() const { return byte(0x0f, 0, 8); } + uint32_t waveform_enable() const { return 1; } + uint32_t timer_a_value() const { return 0; } + uint32_t timer_b_value() const { return 0; } + uint32_t status_mask() const { return 0; } + uint32_t irq_reset() const { return 0; } + uint32_t reset_timer_b() const { return 0; } + uint32_t reset_timer_a() const { return 0; } + uint32_t enable_timer_b() const { return 0; } + uint32_t enable_timer_a() const { return 0; } + uint32_t load_timer_b() const { return 0; } + uint32_t load_timer_a() const { return 0; } + uint32_t csm() const { return 0; } + + // per-channel registers + uint32_t ch_block_freq(uint32_t choffs) const { return word(0x20, 0, 4, 0x10, 0, 8, choffs); } + uint32_t ch_sustain(uint32_t choffs) const { return byte(0x20, 5, 1, choffs); } + uint32_t ch_total_level(uint32_t choffs) const { return instchbyte(0x02, 0, 6, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return instchbyte(0x03, 0, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return 0; } + uint32_t ch_instrument(uint32_t choffs) const { return byte(0x30, 4, 4, choffs); } + uint32_t ch_output_any(uint32_t choffs) const { return 1; } + uint32_t ch_output_0(uint32_t choffs) const { return !is_rhythm(choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return is_rhythm(choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + + // per-operator registers + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return instopbyte(0x00, 7, 1, opoffs); } + uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return instopbyte(0x00, 6, 1, opoffs); } + uint32_t op_eg_sustain(uint32_t opoffs) const { return instopbyte(0x00, 5, 1, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return instopbyte(0x00, 4, 1, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return instopbyte(0x00, 0, 4, opoffs); } + uint32_t op_ksl(uint32_t opoffs) const { return instopbyte(0x02, 6, 2, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return instchbyte(0x03, 3 + bitfield(opoffs, 0), 1, opoffs >> 1); } + uint32_t op_attack_rate(uint32_t opoffs) const { return instopbyte(0x04, 4, 4, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return instopbyte(0x04, 0, 4, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return instopbyte(0x06, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return instopbyte(0x06, 0, 4, opoffs); } + uint32_t op_volume(uint32_t opoffs) const { return byte(0x30, 4 * bitfield(~opoffs, 0), 4, opoffs >> 1); } + +private: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // helpers to read from instrument channel/operator data + uint32_t instchbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t choffs) const { return bitfield(m_chinst[choffs][offset], start, count); } + uint32_t instopbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t opoffs) const { return bitfield(m_opinst[opoffs][offset], start, count); } + + // helper to determine if the this channel is an active rhythm channel + bool is_rhythm(uint32_t choffs) const + { + return rhythm_enable() && choffs >= 6; + } + + // internal state + uint16_t m_lfo_am_counter; // LFO AM counter + uint16_t m_lfo_pm_counter; // LFO PM counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_lfo_am; // current LFO AM value + uint8_t const *m_chinst[CHANNELS]; // pointer to instrument data for each channel + uint8_t const *m_opinst[OPERATORS]; // pointer to instrument data for each operator + uint8_t m_regdata[REGISTERS]; // register data + uint8_t m_instdata[INSTDATA_SIZE]; // instrument data + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// OPL IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym3526 + +class ym3526 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + ym3526(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + +// ======================> y8950 + +class y8950 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x01; + static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08; + static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x10; + static constexpr uint8_t ALL_IRQS = STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_EOS | fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB; + + // constructor + y8950(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + uint8_t m_io_ddr; // data direction register for I/O + fm_engine m_fm; // core FM engine + adpcm_b_engine m_adpcm_b; // ADPCM-B engine +}; + + + +//********************************************************* +// OPL2 IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym3812 + +class ym3812 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + ym3812(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + + +//********************************************************* +// OPL3 IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ymf262 + +class ymf262 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + ymf262(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint16_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + +// ======================> ymf289b + +class ymf289b +{ + static constexpr uint8_t STATUS_BUSY_FLAGS = 0x05; + +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = 2; + + // constructor + ymf289b(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + bool ymf289b_mode() { return ((m_fm.regs().read(0x105) & 0x04) != 0); } + + // internal state + uint16_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + + +//********************************************************* +// OPL4 IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ymf278b + +class ymf278b +{ + // Using the nominal datasheet frequency of 33.868MHz, the output of the + // chip will be clock/768 = 44.1kHz. However, the FM engine is clocked + // internally at clock/(19*36), or 49.515kHz, so the FM output needs to + // be downsampled. We treat this as needing to clock the FM engine an + // extra tick every few samples. The exact ratio is 768/(19*36) or + // 768/684 = 192/171. So if we always clock the FM once, we'll have + // 192/171 - 1 = 21/171 left. Thus we count 21 for each sample and when + // it gets above 171, we tick an extra time. + static constexpr uint32_t FM_EXTRA_SAMPLE_THRESH = 171; + static constexpr uint32_t FM_EXTRA_SAMPLE_STEP = 192 - FM_EXTRA_SAMPLE_THRESH; + +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = 6; + using output_data = ymfm_output; + + static constexpr uint8_t STATUS_BUSY = 0x01; + static constexpr uint8_t STATUS_LD = 0x02; + + // constructor + ymf278b(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return input_clock / 768; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data_pcm(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_address_pcm(uint8_t data); + void write_data_pcm(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint16_t m_address; // address register + uint32_t m_fm_pos; // FM resampling position + uint32_t m_load_remaining; // how many more samples until LD flag clears + bool m_next_status_id; // flag to track which status ID to return + fm_engine m_fm; // core FM engine + pcm_engine m_pcm; // core PCM engine +}; + + + +//********************************************************* +// OPLL IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> opll_base + +class opll_base +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + opll_base(ymfm_interface &intf, uint8_t const *data); + + // configuration + void set_instrument_data(uint8_t const *data) { m_fm.regs().set_instrument_data(data); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access -- doesn't really have any, but provide these for consistency + uint8_t read_status() { return 0x00; } + uint8_t read(uint32_t offset) { return 0x00; } + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate samples of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + +// ======================> ym2413 + +class ym2413 : public opll_base +{ +public: + // constructor + ym2413(ymfm_interface &intf, uint8_t const *instrument_data = nullptr); + +private: + // internal state + static uint8_t const s_default_instruments[]; +}; + + +// ======================> ym2413 + +class ym2423 : public opll_base +{ +public: + // constructor + ym2423(ymfm_interface &intf, uint8_t const *instrument_data = nullptr); + +private: + // internal state + static uint8_t const s_default_instruments[]; +}; + + +// ======================> ymf281 + +class ymf281 : public opll_base +{ +public: + // constructor + ymf281(ymfm_interface &intf, uint8_t const *instrument_data = nullptr); + +private: + // internal state + static uint8_t const s_default_instruments[]; +}; + + +// ======================> ds1001 + +class ds1001 : public opll_base +{ +public: + // constructor + ds1001(ymfm_interface &intf, uint8_t const *instrument_data = nullptr); + +private: + // internal state + static uint8_t const s_default_instruments[]; +}; + +} + +#endif // YMFM_OPL_H diff --git a/src/sound/ymfm/ymfm_opm.cpp b/src/sound/ymfm/ymfm_opm.cpp new file mode 100644 index 000000000..544bbe89a --- /dev/null +++ b/src/sound/ymfm/ymfm_opm.cpp @@ -0,0 +1,539 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opm.h" +#include "ymfm_fm.ipp" + +namespace ymfm +{ + +//********************************************************* +// OPM REGISTERS +//********************************************************* + +//------------------------------------------------- +// opm_registers - constructor +//------------------------------------------------- + +opm_registers::opm_registers() : + m_lfo_counter(0), + m_noise_lfsr(1), + m_noise_counter(0), + m_noise_state(0), + m_noise_lfo(0), + m_lfo_am(0) +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + // create the LFO waveforms; AM in the low 8 bits, PM in the upper 8 + // waveforms are adjusted to match the pictures in the application manual + for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++) + { + // waveform 0 is a sawtooth + uint8_t am = index ^ 0xff; + int8_t pm = int8_t(index); + m_lfo_waveform[0][index] = am | (pm << 8); + + // waveform 1 is a square wave + am = bitfield(index, 7) ? 0 : 0xff; + pm = int8_t(am ^ 0x80); + m_lfo_waveform[1][index] = am | (pm << 8); + + // waveform 2 is a triangle wave + am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1); + pm = int8_t(bitfield(index, 6) ? am : ~am); + m_lfo_waveform[2][index] = am | (pm << 8); + + // waveform 3 is noise; it is filled in dynamically + m_lfo_waveform[3][index] = 0; + } +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +void opm_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // enable output on both channels by default + m_regdata[0x20] = m_regdata[0x21] = m_regdata[0x22] = m_regdata[0x23] = 0xc0; + m_regdata[0x24] = m_regdata[0x25] = m_regdata[0x26] = m_regdata[0x27] = 0xc0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opm_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_noise_lfsr); + state.save_restore(m_noise_counter); + state.save_restore(m_noise_state); + state.save_restore(m_noise_lfo); + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPM this is fixed +//------------------------------------------------- + +void opm_registers::operator_map(operator_mapping &dest) const +{ + // Note that the channel index order is 0,2,1,3, so we bitswap the index. + // + // This is because the order in the map is: + // carrier 1, carrier 2, modulator 1, modulator 2 + // + // But when wiring up the connections, the more natural order is: + // carrier 1, modulator 1, carrier 2, modulator 2 + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 16, 8, 24 ), // Channel 0 operators + operator_list( 1, 17, 9, 25 ), // Channel 1 operators + operator_list( 2, 18, 10, 26 ), // Channel 2 operators + operator_list( 3, 19, 11, 27 ), // Channel 3 operators + operator_list( 4, 20, 12, 28 ), // Channel 4 operators + operator_list( 5, 21, 13, 29 ), // Channel 5 operators + operator_list( 6, 22, 14, 30 ), // Channel 6 operators + operator_list( 7, 23, 15, 31 ), // Channel 7 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +bool opm_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // LFO AM/PM depth are written to the same register (0x19); + // redirect the PM depth to an unused neighbor (0x1a) + if (index == 0x19) + m_regdata[index + bitfield(data, 7)] = data; + else if (index != 0x1a) + m_regdata[index] = data; + + // handle writes to the key on index + if (index == 0x08) + { + channel = bitfield(data, 0, 3); + opmask = bitfield(data, 3, 4); + return true; + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +int32_t opm_registers::clock_noise_and_lfo() +{ + // base noise frequency is measured at 2x 1/2 FM frequency; this + // means each tick counts as two steps against the noise counter + uint32_t freq = noise_frequency(); + for (int rep = 0; rep < 2; rep++) + { + // evidence seems to suggest the LFSR is clocked continually and just + // sampled at the noise frequency for output purposes; note that the + // low 8 bits are the most recent 8 bits of history while bits 8-24 + // contain the 17 bit LFSR state + m_noise_lfsr <<= 1; + m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1; + + // compare against the frequency and latch when we exceed it + if (m_noise_counter++ >= freq) + { + m_noise_counter = 0; + m_noise_state = bitfield(m_noise_lfsr, 17); + } + } + + // treat the rate as a 4.4 floating-point step value with implied + // leading 1; this matches exactly the frequencies in the application + // manual, though it might not be implemented exactly this way on chip + uint32_t rate = lfo_rate(); + m_lfo_counter += (0x10 | bitfield(rate, 0, 4)) << bitfield(rate, 4, 4); + + // bit 1 of the test register is officially undocumented but has been + // discovered to hold the LFO in reset while active + if (lfo_reset()) + m_lfo_counter = 0; + + // now pull out the non-fractional LFO value + uint32_t lfo = bitfield(m_lfo_counter, 22, 8); + + // fill in the noise entry 1 ahead of our current position; this + // ensures the current value remains stable for a full LFO clock + // and effectively latches the running value when the LFO advances + uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8); + m_lfo_waveform[3][(lfo + 1) & 0xff] = lfo_noise | (lfo_noise << 8); + + // fetch the AM/PM values based on the waveform; AM is unsigned and + // encoded in the low 8 bits, while PM signed and encoded in the upper + // 8 bits + int32_t ampm = m_lfo_waveform[lfo_waveform()][lfo]; + + // apply depth to the AM value and store for later + m_lfo_am = ((ampm & 0xff) * lfo_am_depth()) >> 7; + + // apply depth to the PM value and return it + return ((ampm >> 8) * int32_t(lfo_pm_depth())) >> 7; +} + + +//------------------------------------------------- +// lfo_am_offset - return the AM offset from LFO +// for the given channel +//------------------------------------------------- + +uint32_t opm_registers::lfo_am_offset(uint32_t choffs) const +{ + // OPM maps AM quite differently from OPN + + // shift value for AM sensitivity is [*, 0, 1, 2], + // mapping to values of [0, 23.9, 47.8, and 95.6dB] + uint32_t am_sensitivity = ch_lfo_am_sens(choffs); + if (am_sensitivity == 0) + return 0; + + // QUESTION: see OPN note below for the dB range mapping; it applies + // here as well + + // raw LFO AM value on OPM is 0-FF, which is already a factor of 2 + // larger than the OPN below, putting our staring point at 2x theirs; + // this works out since our minimum is 2x their maximum + return m_lfo_am << (am_sensitivity - 1); +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data +//------------------------------------------------- + +void opm_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // set up the easy stuff + cache.waveform = &m_waveform[0][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // compute the keycode: block_freq is: + // + // BBBCCCCFFFFFF + // ^^^^^ + // + // the 5-bit keycode is just the top 5 bits (block + top 2 bits + // of the key code) + uint32_t keycode = bitfield(block_freq, 8, 5); + + // detune adjustment + cache.detune = detune_adjustment(op_detune(opoffs), keycode); + + // multiple value, as an x.1 value (0 means 0.5) + cache.multiple = op_multiple(opoffs) * 2; + if (cache.multiple == 0) + cache.multiple = 1; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those + if (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + cache.total_level = op_total_level(opoffs) << 3; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval); +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +uint32_t opm_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // OPM logic is rather unique here, due to extra detune + // and the use of key codes (not to be confused with keycode) + + // start with coarse detune delta; table uses cents value from + // manual, converted into 1/64ths + static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 }; + int32_t delta = s_detune2_delta[op_detune2(opoffs)]; + + // add in the PM delta + uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs); + if (pm_sensitivity != 0) + { + // raw PM value is -127..128 which is +/- 200 cents + // manual gives these magnitudes in cents: + // 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700 + // this roughly corresponds to shifting the 200-cent value: + // 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2 + if (pm_sensitivity < 6) + delta += lfo_raw_pm >> (6 - pm_sensitivity); + else + delta += lfo_raw_pm << (pm_sensitivity - 5); + } + + // apply delta and convert to a frequency number + uint32_t phase_step = opm_key_code_to_phase_step(cache.block_freq, delta); + + // apply detune based on the keycode + phase_step += cache.detune; + + // apply frequency multiplier (which is cached as an x.1 value) + return (phase_step * cache.multiple) >> 1; +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +std::string opm_registers::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = choffs; + uint32_t opnum = opoffs; + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(end, "%u.%02u freq=%04X dt2=%u dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c", + chnum, opnum, + ch_block_freq(choffs), + op_detune2(opoffs), + op_detune(opoffs), + ch_feedback(choffs), + ch_algorithm(choffs), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_sustain_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + ch_output_0(choffs) ? 'L' : '-', + ch_output_1(choffs) ? 'R' : '-'); + + bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0); + if (am) + end += sprintf(end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth()); + bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0); + if (pm) + end += sprintf(end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth()); + if (am || pm) + end += sprintf(end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]); + if (noise_enable() && opoffs == 31) + end += sprintf(end, " noise=1"); + + return buffer; +} + + + +//********************************************************* +// YM2151 +//********************************************************* + +//------------------------------------------------- +// ym2151 - constructor +//------------------------------------------------- + +ym2151::ym2151(ymfm_interface &intf, opm_variant variant) : + m_variant(variant), + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2151::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2151::save_restore(ymfm_saved_state &state) +{ + m_fm.save_restore(state); + state.save_restore(m_address); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2151::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2151::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // data port (unused) + debug::log_unexpected_read_write("Unexpected read from YM2151 offset %d\n", offset & 3); + break; + + case 1: // status port, YM2203 compatible + result = read_status(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2151::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2151::write_data(uint8_t data) +{ + // write the FM register + m_fm.write(m_address, data); + + // special cases + if (m_address == 0x1b) + { + // writes to register 0x1B send the upper 2 bits to the output lines + m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2151::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2151::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; OPM is full 14-bit with no intermediate clipping + m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // YM2151 uses an external DAC (YM3012) with mantissa/exponent format + // convert to 10.3 floating point value and back to simulate truncation + output->roundtrip_fp(); + } +} + +} diff --git a/src/sound/ymfm/ymfm_opm.h b/src/sound/ymfm/ymfm_opm.h new file mode 100644 index 000000000..b126135d4 --- /dev/null +++ b/src/sound/ymfm/ymfm_opm.h @@ -0,0 +1,322 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPM_H +#define YMFM_OPM_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_fm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opm_registers + +// +// OPM register map: +// +// System-wide registers: +// 01 xxxxxx-x Test register +// ------x- LFO reset +// 08 -x------ Key on/off operator 4 +// --x----- Key on/off operator 3 +// ---x---- Key on/off operator 2 +// ----x--- Key on/off operator 1 +// -----xxx Channel select +// 0F x------- Noise enable +// ---xxxxx Noise frequency +// 10 xxxxxxxx Timer A value (upper 8 bits) +// 11 ------xx Timer A value (lower 2 bits) +// 12 xxxxxxxx Timer B value +// 14 x------- CSM mode +// --x----- Reset timer B +// ---x---- Reset timer A +// ----x--- Enable timer B +// -----x-- Enable timer A +// ------x- Load timer B +// -------x Load timer A +// 18 xxxxxxxx LFO frequency +// 19 0xxxxxxx AM LFO depth +// 1xxxxxxx PM LFO depth +// 1B xx------ CT (2 output data lines) +// ------xx LFO waveform +// +// Per-channel registers (channel in address bits 0-2) +// 20-27 x------- Pan right +// -x------ Pan left +// --xxx--- Feedback level for operator 1 (0-7) +// -----xxx Operator connection algorithm (0-7) +// 28-2F -xxxxxxx Key code +// 30-37 xxxxxx-- Key fraction +// 38-3F -xxx---- LFO PM sensitivity +// ------xx LFO AM shift +// +// Per-operator registers (channel in address bits 0-2, operator in bits 3-4) +// 40-5F -xxx---- Detune value (0-7) +// ----xxxx Multiple value (0-15) +// 60-7F -xxxxxxx Total level (0-127) +// 80-9F xx------ Key scale rate (0-3) +// ---xxxxx Attack rate (0-31) +// A0-BF x------- LFO AM enable +// ---xxxxx Decay rate (0-31) +// C0-DF xx------ Detune 2 value (0-3) +// ---xxxxx Sustain rate (0-31) +// E0-FF xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// +// Internal (fake) registers: +// 1A -xxxxxxx PM depth +// + +class opm_registers : public fm_registers_base +{ + // LFO waveforms are 256 entries long + static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256; + +public: + // constants + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 8; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 4; + static constexpr uint32_t WAVEFORMS = 1; + static constexpr uint32_t REGISTERS = 0x100; + static constexpr uint32_t DEFAULT_PRESCALE = 2; + static constexpr uint32_t EG_CLOCK_DIVIDER = 3; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr uint32_t REG_MODE = 0x14; + static constexpr uint8_t STATUS_TIMERA = 0x01; + static constexpr uint8_t STATUS_TIMERB = 0x02; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opm_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return the current noise state, gated by the noise clock + uint32_t noise_state() const { return m_noise_state; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t test() const { return byte(0x01, 0, 8); } + uint32_t lfo_reset() const { return byte(0x01, 1, 1); } + uint32_t noise_frequency() const { return byte(0x0f, 0, 5) ^ 0x1f; } + uint32_t noise_enable() const { return byte(0x0f, 7, 1); } + uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); } + uint32_t timer_b_value() const { return byte(0x12, 0, 8); } + uint32_t csm() const { return byte(0x14, 7, 1); } + uint32_t reset_timer_b() const { return byte(0x14, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x14, 4, 1); } + uint32_t enable_timer_b() const { return byte(0x14, 3, 1); } + uint32_t enable_timer_a() const { return byte(0x14, 2, 1); } + uint32_t load_timer_b() const { return byte(0x14, 1, 1); } + uint32_t load_timer_a() const { return byte(0x14, 0, 1); } + uint32_t lfo_rate() const { return byte(0x18, 0, 8); } + uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); } + uint32_t lfo_pm_depth() const { return byte(0x1a, 0, 7); } + uint32_t output_bits() const { return byte(0x1b, 6, 2); } + uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); } + + // per-channel registers + uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 6, 2, choffs); } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); } + uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); } + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); } + uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter; // LFO counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_noise_counter; // noise counter + uint8_t m_noise_state; // latched noise state + uint8_t m_noise_lfo; // latched LFO noise value + uint8_t m_lfo_am; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8 + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// OPM IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2151 + +class ym2151 +{ +public: + using fm_engine = fm_engine_base; + using output_data = fm_engine::output_data; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + + // constructor + ym2151(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2151) { } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // variants + enum opm_variant + { + VARIANT_YM2151, + VARIANT_YM2164 + }; + + // internal constructor + ym2151(ymfm_interface &intf, opm_variant variant); + + // internal state + opm_variant m_variant; // chip variant + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + + + +//********************************************************* +// OPP IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2164 + +// the YM2164 is almost 100% functionally identical to the YM2151, except +// it apparently has some mystery registers in the 00-07 range, and timer +// B's frequency is half that of the 2151 +class ym2164 : public ym2151 +{ +public: + // constructor + ym2164(ymfm_interface &intf) : ym2151(intf, VARIANT_YM2164) { } +}; + +} + + +#endif // YMFM_OPM_H diff --git a/src/sound/ymfm/ymfm_opn.cpp b/src/sound/ymfm/ymfm_opn.cpp new file mode 100644 index 000000000..053ad9770 --- /dev/null +++ b/src/sound/ymfm/ymfm_opn.cpp @@ -0,0 +1,2488 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opn.h" +#include "ymfm_fm.ipp" + +namespace ymfm +{ + +//********************************************************* +// OPN/OPNA REGISTERS +//********************************************************* + +//------------------------------------------------- +// opn_registers_base - constructor +//------------------------------------------------- + +template +opn_registers_base::opn_registers_base() : + m_lfo_counter(0), + m_lfo_am(0) +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +template +void opn_registers_base::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + if (IsOpnA) + { + // enable output on both channels by default + m_regdata[0xb4] = m_regdata[0xb5] = m_regdata[0xb6] = 0xc0; + m_regdata[0x1b4] = m_regdata[0x1b5] = m_regdata[0x1b6] = 0xc0; + } +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void opn_registers_base::save_restore(ymfm_saved_state &state) +{ + if (IsOpnA) + { + state.save_restore(m_lfo_counter); + state.save_restore(m_lfo_am); + } + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPN this is fixed +//------------------------------------------------- + +template<> +void opn_registers_base::operator_map(operator_mapping &dest) const +{ + // Note that the channel index order is 0,2,1,3, so we bitswap the index. + // + // This is because the order in the map is: + // carrier 1, carrier 2, modulator 1, modulator 2 + // + // But when wiring up the connections, the more natural order is: + // carrier 1, modulator 1, carrier 2, modulator 2 + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 6, 3, 9 ), // Channel 0 operators + operator_list( 1, 7, 4, 10 ), // Channel 1 operators + operator_list( 2, 8, 5, 11 ), // Channel 2 operators + } }; + dest = s_fixed_map; +} + +template<> +void opn_registers_base::operator_map(operator_mapping &dest) const +{ + // Note that the channel index order is 0,2,1,3, so we bitswap the index. + // + // This is because the order in the map is: + // carrier 1, carrier 2, modulator 1, modulator 2 + // + // But when wiring up the connections, the more natural order is: + // carrier 1, modulator 1, carrier 2, modulator 2 + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 6, 3, 9 ), // Channel 0 operators + operator_list( 1, 7, 4, 10 ), // Channel 1 operators + operator_list( 2, 8, 5, 11 ), // Channel 2 operators + operator_list( 12, 18, 15, 21 ), // Channel 3 operators + operator_list( 13, 19, 16, 22 ), // Channel 4 operators + operator_list( 14, 20, 17, 23 ), // Channel 5 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +template +bool opn_registers_base::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // writes in the 0xa0-af/0x1a0-af region are handled as latched pairs + // borrow unused registers 0xb8-bf/0x1b8-bf as temporary holding locations + if ((index & 0xf0) == 0xa0) + { + if (bitfield(index, 0, 2) == 3) + return false; + + uint32_t latchindex = 0xb8 | bitfield(index, 3); + if (IsOpnA) + latchindex |= index & 0x100; + + // writes to the upper half just latch (only low 6 bits matter) + if (bitfield(index, 2)) + m_regdata[latchindex] = data | 0x80; + + // writes to the lower half only commit if the latch is there + else if (bitfield(m_regdata[latchindex], 7)) + { + m_regdata[index] = data; + m_regdata[index | 4] = m_regdata[latchindex] & 0x3f; + m_regdata[latchindex] = 0; + } + return false; + } + else if ((index & 0xf8) == 0xb8) + { + // registers 0xb8-0xbf are used internally + return false; + } + + // everything else is normal + m_regdata[index] = data; + + // handle writes to the key on index + if (index == 0x28) + { + channel = bitfield(data, 0, 2); + if (channel == 3) + return false; + if (IsOpnA) + channel += bitfield(data, 2, 1) * 3; + opmask = bitfield(data, 4, 4); + return true; + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +template +int32_t opn_registers_base::clock_noise_and_lfo() +{ + // OPN has no noise generation + + // if LFO not enabled (not present on OPN), quick exit with 0s + if (!IsOpnA || !lfo_enable()) + { + m_lfo_counter = 0; + + // special case: if LFO is disabled on OPNA, it basically just keeps the counter + // at 0; since position 0 gives an AM value of 0x3f, it is important to reflect + // that here; for example, MegaDrive Venom plays some notes with LFO globally + // disabled but enabling LFO on the operators, and it expects this added attenutation + m_lfo_am = IsOpnA ? 0x3f : 0x00; + return 0; + } + + // this table is based on converting the frequencies in the applications + // manual to clock dividers, based on the assumption of a 7-bit LFO value + static uint8_t const lfo_max_count[8] = { 109, 78, 72, 68, 63, 45, 9, 6 }; + uint32_t subcount = uint8_t(m_lfo_counter++); + + // when we cross the divider count, add enough to zero it and cause an + // increment at bit 8; the 7-bit value lives from bits 8-14 + if (subcount >= lfo_max_count[lfo_rate()]) + { + // note: to match the published values this should be 0x100 - subcount; + // however, tests on the hardware and nuked bear out an off-by-one + // error exists that causes the max LFO rate to be faster than published + m_lfo_counter += 0x101 - subcount; + } + + // AM value is 7 bits, staring at bit 8; grab the low 6 directly + m_lfo_am = bitfield(m_lfo_counter, 8, 6); + + // first half of the AM period (bit 6 == 0) is inverted + if (bitfield(m_lfo_counter, 8+6) == 0) + m_lfo_am ^= 0x3f; + + // PM value is 5 bits, starting at bit 10; grab the low 3 directly + int32_t pm = bitfield(m_lfo_counter, 10, 3); + + // PM is reflected based on bit 3 + if (bitfield(m_lfo_counter, 10+3)) + pm ^= 7; + + // PM is negated based on bit 4 + return bitfield(m_lfo_counter, 10+4) ? -pm : pm; +} + + +//------------------------------------------------- +// lfo_am_offset - return the AM offset from LFO +// for the given channel +//------------------------------------------------- + +template +uint32_t opn_registers_base::lfo_am_offset(uint32_t choffs) const +{ + // shift value for AM sensitivity is [7, 3, 1, 0], + // mapping to values of [0, 1.4, 5.9, and 11.8dB] + uint32_t am_shift = (1 << (ch_lfo_am_sens(choffs) ^ 3)) - 1; + + // QUESTION: max sensitivity should give 11.8dB range, but this value + // is directly added to an x.8 attenuation value, which will only give + // 126/256 or ~4.9dB range -- what am I missing? The calculation below + // matches several other emulators, including the Nuked implemenation. + + // raw LFO AM value on OPN is 0-3F, scale that up by a factor of 2 + // (giving 7 bits) before applying the final shift + return (m_lfo_am << 1) >> am_shift; +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data +//------------------------------------------------- + +template +void opn_registers_base::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // set up the easy stuff + cache.waveform = &m_waveform[0][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // if multi-frequency mode is enabled and this is channel 2, + // fetch one of the special frequencies + if (multi_freq() && choffs == 2) + { + if (opoffs == 2) + block_freq = cache.block_freq = multi_block_freq(1); + else if (opoffs == 10) + block_freq = cache.block_freq = multi_block_freq(2); + else if (opoffs == 6) + block_freq = cache.block_freq = multi_block_freq(0); + } + + // compute the keycode: block_freq is: + // + // BBBFFFFFFFFFFF + // ^^^^??? + // + // the 5-bit keycode uses the top 4 bits plus a magic formula + // for the final bit + uint32_t keycode = bitfield(block_freq, 10, 4) << 1; + + // lowest bit is determined by a mix of next lower FNUM bits + // according to this equation from the YM2608 manual: + // + // (F11 & (F10 | F9 | F8)) | (!F11 & F10 & F9 & F8) + // + // for speed, we just look it up in a 16-bit constant + keycode |= bitfield(0xfe80, bitfield(block_freq, 7, 4)); + + // detune adjustment + cache.detune = detune_adjustment(op_detune(opoffs), keycode); + + // multiple value, as an x.1 value (0 means 0.5) + cache.multiple = op_multiple(opoffs) * 2; + if (cache.multiple == 0) + cache.multiple = 1; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those + if (!IsOpnA || lfo_enable() == 0 || ch_lfo_pm_sens(choffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + cache.total_level = op_total_level(opoffs) << 3; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval); +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +template +uint32_t opn_registers_base::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // OPN phase calculation has only a single detune parameter + // and uses FNUMs instead of keycodes + + // extract frequency number (low 11 bits of block_freq) + uint32_t fnum = bitfield(cache.block_freq, 0, 11) << 1; + + // if there's a non-zero PM sensitivity, compute the adjustment + uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs); + if (pm_sensitivity != 0) + { + // apply the phase adjustment based on the upper 7 bits + // of FNUM and the PM depth parameters + fnum += opn_lfo_pm_phase_adjustment(bitfield(cache.block_freq, 4, 7), pm_sensitivity, lfo_raw_pm); + + // keep fnum to 12 bits + fnum &= 0xfff; + } + + // apply block shift to compute phase step + uint32_t block = bitfield(cache.block_freq, 11, 3); + uint32_t phase_step = (fnum << block) >> 2; + + // apply detune based on the keycode + phase_step += cache.detune; + + // clamp to 17 bits in case detune overflows + // QUESTION: is this specific to the YM2612/3438? + phase_step &= 0x1ffff; + + // apply frequency multiplier (which is cached as an x.1 value) + return (phase_step * cache.multiple) >> 1; +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +template +std::string opn_registers_base::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = (choffs & 3) + 3 * bitfield(choffs, 8); + uint32_t opnum = (opoffs & 15) - ((opoffs & 15) / 4) + 12 * bitfield(opoffs, 8); + + uint32_t block_freq = ch_block_freq(choffs); + if (multi_freq() && choffs == 2) + { + if (opoffs == 2) + block_freq = multi_block_freq(1); + else if (opoffs == 10) + block_freq = multi_block_freq(2); + else if (opoffs == 6) + block_freq = multi_block_freq(0); + } + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(end, "%u.%02u freq=%04X dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X", + chnum, opnum, + block_freq, + op_detune(opoffs), + ch_feedback(choffs), + ch_algorithm(choffs), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_sustain_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs)); + + if (OUTPUTS > 1) + end += sprintf(end, " out=%c%c", + ch_output_0(choffs) ? 'L' : '-', + ch_output_1(choffs) ? 'R' : '-'); + if (op_ssg_eg_enable(opoffs)) + end += sprintf(end, " ssg=%X", op_ssg_eg_mode(opoffs)); + bool am = (op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0); + if (am) + end += sprintf(end, " am=%u", ch_lfo_am_sens(choffs)); + bool pm = (ch_lfo_pm_sens(choffs) != 0); + if (pm) + end += sprintf(end, " pm=%u", ch_lfo_pm_sens(choffs)); + if (am || pm) + end += sprintf(end, " lfo=%02X", lfo_rate()); + if (multi_freq() && choffs == 2) + end += sprintf(end, " multi=1"); + + return buffer; +} + + + +//********************************************************* +// SSG RESAMPLER +//********************************************************* + +//------------------------------------------------- +// add_last - helper to add the last computed +// value to the sums, applying the given scale +//------------------------------------------------- + +template +void ssg_resampler::add_last(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale) +{ + sum0 += m_last.data[0] * scale; + sum1 += m_last.data[1] * scale; + sum2 += m_last.data[2] * scale; +} + + +//------------------------------------------------- +// clock_and_add - helper to clock a new value +// and then add it to the sums, applying the +// given scale +//------------------------------------------------- + +template +void ssg_resampler::clock_and_add(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale) +{ + m_ssg.clock(); + m_ssg.output(m_last); + add_last(sum0, sum1, sum2, scale); +} + + +//------------------------------------------------- +// write_to_output - helper to write the sums to +// the appropriate outputs, applying the given +// divisor to the final result +//------------------------------------------------- + +template +void ssg_resampler::write_to_output(OutputType *output, int32_t sum0, int32_t sum1, int32_t sum2, int32_t divisor) +{ + if (MixTo1) + { + // mixing to one, apply a 2/3 factor to prevent overflow + output->data[FirstOutput] = (sum0 + sum1 + sum2) * 2 / (3 * divisor); + } + else + { + // write three outputs in a row + output->data[FirstOutput + 0] = sum0 / divisor; + output->data[FirstOutput + 1] = sum1 / divisor; + output->data[FirstOutput + 2] = sum2 / divisor; + } + + // track the sample index here + m_sampindex++; +} + + +//------------------------------------------------- +// ssg_resampler - constructor +//------------------------------------------------- + +template +ssg_resampler::ssg_resampler(ssg_engine &ssg) : + m_ssg(ssg), + m_sampindex(0), + m_resampler(&ssg_resampler::resample_nop) +{ + m_last.clear(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +template +void ssg_resampler::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_sampindex); + state.save_restore(m_last.data); +} + + +//------------------------------------------------- +// configure - configure a new ratio +//------------------------------------------------- + +template +void ssg_resampler::configure(uint8_t outsamples, uint8_t srcsamples) +{ + switch (outsamples * 10 + srcsamples) + { + case 4*10 + 1: /* 4:1 */ m_resampler = &ssg_resampler::resample_n_1<4>; break; + case 2*10 + 1: /* 2:1 */ m_resampler = &ssg_resampler::resample_n_1<2>; break; + case 4*10 + 3: /* 4:3 */ m_resampler = &ssg_resampler::resample_4_3; break; + case 1*10 + 1: /* 1:1 */ m_resampler = &ssg_resampler::resample_n_1<1>; break; + case 2*10 + 3: /* 2:3 */ m_resampler = &ssg_resampler::resample_2_3; break; + case 1*10 + 3: /* 1:3 */ m_resampler = &ssg_resampler::resample_1_n<3>; break; + case 2*10 + 9: /* 2:9 */ m_resampler = &ssg_resampler::resample_2_9; break; + case 1*10 + 6: /* 1:6 */ m_resampler = &ssg_resampler::resample_1_n<6>; break; + case 0*10 + 0: /* 0:0 */ m_resampler = &ssg_resampler::resample_nop; break; + default: assert(false); break; + } +} + + +//------------------------------------------------- +// resample_n_1 - resample SSG output to the +// target at a rate of 1 SSG sample to every +// n output sample +//------------------------------------------------- + +template +template +void ssg_resampler::resample_n_1(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if (m_sampindex % Multiplier == 0) + { + m_ssg.clock(); + m_ssg.output(m_last); + } + write_to_output(output, m_last.data[0], m_last.data[1], m_last.data[2]); + } +} + + +//------------------------------------------------- +// resample_1_n - resample SSG output to the +// target at a rate of n SSG samples to every +// 1 output sample +//------------------------------------------------- + +template +template +void ssg_resampler::resample_1_n(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + int32_t sum0 = 0, sum1 = 0, sum2 = 0; + for (int rep = 0; rep < Divisor; rep++) + clock_and_add(sum0, sum1, sum2); + write_to_output(output, sum0, sum1, sum2, Divisor); + } +} + + +//------------------------------------------------- +// resample_2_9 - resample SSG output to the +// target at a rate of 9 SSG samples to every +// 2 output samples +//------------------------------------------------- + +template +void ssg_resampler::resample_2_9(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + int32_t sum0 = 0, sum1 = 0, sum2 = 0; + if (bitfield(m_sampindex, 0) != 0) + add_last(sum0, sum1, sum2, 1); + clock_and_add(sum0, sum1, sum2, 2); + clock_and_add(sum0, sum1, sum2, 2); + clock_and_add(sum0, sum1, sum2, 2); + clock_and_add(sum0, sum1, sum2, 2); + if (bitfield(m_sampindex, 0) == 0) + clock_and_add(sum0, sum1, sum2, 1); + write_to_output(output, sum0, sum1, sum2, 9); + } +} + + +//------------------------------------------------- +// resample_2_3 - resample SSG output to the +// target at a rate of 3 SSG samples to every +// 2 output samples +//------------------------------------------------- + +template +void ssg_resampler::resample_2_3(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + int32_t sum0 = 0, sum1 = 0, sum2 = 0; + if (bitfield(m_sampindex, 0) == 0) + { + clock_and_add(sum0, sum1, sum2, 2); + clock_and_add(sum0, sum1, sum2, 1); + } + else + { + add_last(sum0, sum1, sum2, 1); + clock_and_add(sum0, sum1, sum2, 2); + } + write_to_output(output, sum0, sum1, sum2, 3); + } +} + + +//------------------------------------------------- +// resample_4_3 - resample SSG output to the +// target at a rate of 3 SSG samples to every +// 4 output samples +//------------------------------------------------- + +template +void ssg_resampler::resample_4_3(OutputType *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + int32_t sum0 = 0, sum1 = 0, sum2 = 0; + int32_t step = bitfield(m_sampindex, 0, 2); + add_last(sum0, sum1, sum2, step); + if (step != 3) + clock_and_add(sum0, sum1, sum2, 3 - step); + write_to_output(output, sum0, sum1, sum2, 3); + } +} + + +//------------------------------------------------- +// resample_nop - no-op resampler +//------------------------------------------------- + +template +void ssg_resampler::resample_nop(OutputType *output, uint32_t numsamples) +{ + // nothing to do except increment the sample index + m_sampindex += numsamples; +} + + + +//********************************************************* +// YM2203 +//********************************************************* + +//------------------------------------------------- +// ym2203 - constructor +//------------------------------------------------- + +ym2203::ym2203(ymfm_interface &intf) : + m_fidelity(OPN_FIDELITY_MAX), + m_address(0), + m_fm(intf), + m_ssg(intf), + m_ssg_resampler(m_ssg) +{ + m_last_fm.clear(); + update_prescale(m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2203::reset() +{ + // reset the engines + m_fm.reset(); + m_ssg.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2203::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_last_fm.data); + + m_fm.save_restore(state); + m_ssg.save_restore(state); + m_ssg_resampler.save_restore(state); + + update_prescale(m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2203::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ym2203::read_data() +{ + uint8_t result = 0; + if (m_address < 0x10) + { + // 00-0F: Read from SSG + result = m_ssg.read(m_address & 0x0f); + } + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2203::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // status port + result = read_status(); + break; + + case 1: // data port (only SSG) + result = read_data(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2203::write_address(uint8_t data) +{ + // just set the address + m_address = data; + + // special case: update the prescale + if (m_address >= 0x2d && m_address <= 0x2f) + { + // 2D-2F: prescaler select + if (m_address == 0x2d) + update_prescale(6); + else if (m_address == 0x2e && m_fm.clock_prescale() == 6) + update_prescale(3); + else if (m_address == 0x2f) + update_prescale(2); + } +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2203::write_data(uint8_t data) +{ + if (m_address < 0x10) + { + // 00-0F: write to SSG + m_ssg.write(m_address & 0x0f, data); + } + else + { + // 10-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2203::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2203::generate(output_data *output, uint32_t numsamples) +{ + // FM output is just repeated the prescale number of times; note that + // 0 is a special 1.5 case + if (m_fm_samples_per_output != 0) + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0) + clock_fm(); + output->data[0] = m_last_fm.data[0]; + } + } + else + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3; + if (step == 0) + clock_fm(); + output->data[0] = m_last_fm.data[0]; + if (step == 1) + { + clock_fm(); + output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2; + } + } + } + + // resample the SSG as configured + m_ssg_resampler.resample(output - numsamples, numsamples); +} + + +//------------------------------------------------- +// update_prescale - update the prescale value, +// recomputing derived values +//------------------------------------------------- + +void ym2203::update_prescale(uint8_t prescale) +{ + // tell the FM engine + m_fm.set_clock_prescale(prescale); + m_ssg.prescale_changed(); + + // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum----- + // rate = clock/24 rate = clock/12 rate = clock/4 + // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate + // 6 3:1 2:3 6:1 4:3 18:1 4:1 + // 3 1.5:1 1:3 3:1 2:3 9:1 2:1 + // 2 1:1 1:6 2:1 1:3 6:1 1:1 + + // compute the number of FM samples per output sample, and select the + // resampler function + if (m_fidelity == OPN_FIDELITY_MIN) + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break; + case 3: m_fm_samples_per_output = 0; m_ssg_resampler.configure(1, 3); break; + case 2: m_fm_samples_per_output = 1; m_ssg_resampler.configure(1, 6); break; + } + } + else if (m_fidelity == OPN_FIDELITY_MED) + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 6; m_ssg_resampler.configure(4, 3); break; + case 3: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break; + case 2: m_fm_samples_per_output = 2; m_ssg_resampler.configure(1, 3); break; + } + } + else + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 18; m_ssg_resampler.configure(4, 1); break; + case 3: m_fm_samples_per_output = 9; m_ssg_resampler.configure(2, 1); break; + case 2: m_fm_samples_per_output = 6; m_ssg_resampler.configure(1, 1); break; + } + } + + // if overriding the SSG, override the configuration with the nop + // resampler to at least keep the sample index moving forward + if (m_ssg.overridden()) + m_ssg_resampler.configure(0, 0); +} + + +//------------------------------------------------- +// clock_fm - clock FM state +//------------------------------------------------- + +void ym2203::clock_fm() +{ + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; OPN is full 14-bit with no intermediate clipping + m_fm.output(m_last_fm.clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // convert to 10.3 floating point value for the DAC and back + m_last_fm.roundtrip_fp(); +} + + + +//********************************************************* +// YM2608 +//********************************************************* + +//------------------------------------------------- +// ym2608 - constructor +//------------------------------------------------- + +ym2608::ym2608(ymfm_interface &intf) : + m_fidelity(OPN_FIDELITY_MAX), + m_address(0), + m_irq_enable(0x1f), + m_flag_control(0x1c), + m_fm(intf), + m_ssg(intf), + m_ssg_resampler(m_ssg), + m_adpcm_a(intf, 0), + m_adpcm_b(intf) +{ + m_last_fm.clear(); + update_prescale(m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2608::reset() +{ + // reset the engines + m_fm.reset(); + m_ssg.reset(); + m_adpcm_a.reset(); + m_adpcm_b.reset(); + + // configure ADPCM percussion sounds; these are present in an embedded ROM + m_adpcm_a.set_start_end(0, 0x0000, 0x01bf); // bass drum + m_adpcm_a.set_start_end(1, 0x01c0, 0x043f); // snare drum + m_adpcm_a.set_start_end(2, 0x0440, 0x1b7f); // top cymbal + m_adpcm_a.set_start_end(3, 0x1b80, 0x1cff); // high hat + m_adpcm_a.set_start_end(4, 0x1d00, 0x1f7f); // tom tom + m_adpcm_a.set_start_end(5, 0x1f80, 0x1fff); // rim shot + + // initialize our special interrupt states, then read the upper status + // register, which updates the IRQs + m_irq_enable = 0x1f; + m_flag_control = 0x1c; + read_status_hi(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2608::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_irq_enable); + state.save_restore(m_flag_control); + state.save_restore(m_last_fm.data); + + m_fm.save_restore(state); + m_ssg.save_restore(state); + m_ssg_resampler.save_restore(state); + m_adpcm_a.save_restore(state); + m_adpcm_b.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2608::read_status() +{ + uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ym2608::read_data() +{ + uint8_t result = 0; + if (m_address < 0x10) + { + // 00-0F: Read from SSG + result = m_ssg.read(m_address & 0x0f); + } + else if (m_address == 0xff) + { + // FF: ID code + result = 1; + } + return result; +} + + +//------------------------------------------------- +// read_status_hi - read the extended status +// register +//------------------------------------------------- + +uint8_t ym2608::read_status_hi() +{ + // fetch regular status + uint8_t status = m_fm.status() & ~(STATUS_ADPCM_B_EOS | STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_PLAYING); + + // fetch ADPCM-B status, and merge in the bits + uint8_t adpcm_status = m_adpcm_b.status(); + if ((adpcm_status & adpcm_b_channel::STATUS_EOS) != 0) + status |= STATUS_ADPCM_B_EOS; + if ((adpcm_status & adpcm_b_channel::STATUS_BRDY) != 0) + status |= STATUS_ADPCM_B_BRDY; + if ((adpcm_status & adpcm_b_channel::STATUS_PLAYING) != 0) + status |= STATUS_ADPCM_B_PLAYING; + + // turn off any bits that have been requested to be masked + status &= ~(m_flag_control & 0x1f); + + // update the status so that IRQs are propagated + m_fm.set_reset_status(status, ~status); + + // merge in the busy flag + if (m_fm.intf().ymfm_is_busy()) + status |= fm_engine::STATUS_BUSY; + return status; +} + + +//------------------------------------------------- +// read_data_hi - read the upper data register +//------------------------------------------------- + +uint8_t ym2608::read_data_hi() +{ + uint8_t result = 0; + if ((m_address & 0xff) < 0x10) + { + // 00-0F: Read from ADPCM-B + result = m_adpcm_b.read(m_address & 0x0f); + } + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2608::read(uint32_t offset) +{ + uint8_t result = 0; + switch (offset & 3) + { + case 0: // status port, YM2203 compatible + result = read_status(); + break; + + case 1: // data port (only SSG) + result = read_data(); + break; + + case 2: // status port, extended + result = read_status_hi(); + break; + + case 3: // ADPCM-B data + result = read_data_hi(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2608::write_address(uint8_t data) +{ + // just set the address + m_address = data; + + // special case: update the prescale + if (m_address >= 0x2d && m_address <= 0x2f) + { + // 2D-2F: prescaler select + if (m_address == 0x2d) + update_prescale(6); + else if (m_address == 0x2e && m_fm.clock_prescale() == 6) + update_prescale(3); + else if (m_address == 0x2f) + update_prescale(2); + } +} + + +//------------------------------------------------- +// write - handle a write to the data register +//------------------------------------------------- + +void ym2608::write_data(uint8_t data) +{ + // ignore if paired with upper address + if (bitfield(m_address, 8)) + return; + + if (m_address < 0x10) + { + // 00-0F: write to SSG + m_ssg.write(m_address & 0x0f, data); + } + else if (m_address < 0x20) + { + // 10-1F: write to ADPCM-A + m_adpcm_a.write(m_address & 0x0f, data); + } + else if (m_address == 0x29) + { + // 29: special IRQ mask register + m_irq_enable = data; + m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x1f); + } + else + { + // 20-28, 2A-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ym2608::write_address_hi(uint8_t data) +{ + // just set the address + m_address = 0x100 | data; +} + + +//------------------------------------------------- +// write_data_hi - handle a write to the upper +// data register +//------------------------------------------------- + +void ym2608::write_data_hi(uint8_t data) +{ + // ignore if paired with upper address + if (!bitfield(m_address, 8)) + return; + + if (m_address < 0x110) + { + // 100-10F: write to ADPCM-B + m_adpcm_b.write(m_address & 0x0f, data); + } + else if (m_address == 0x110) + { + // 110: IRQ flag control + if (bitfield(data, 7)) + m_fm.set_reset_status(0, 0xff); + else + { + m_flag_control = data; + m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x1f); + } + } + else + { + // 111-1FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2608::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // upper address port + write_address_hi(data); + break; + + case 3: // upper data port + write_data_hi(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2608::generate(output_data *output, uint32_t numsamples) +{ + // FM output is just repeated the prescale number of times; note that + // 0 is a special 1.5 case + if (m_fm_samples_per_output != 0) + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + } + } + else + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3; + if (step == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + if (step == 1) + { + clock_fm_and_adpcm(); + output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2; + output->data[1] = (output->data[1] + m_last_fm.data[1]) / 2; + } + } + } + + // resample the SSG as configured + m_ssg_resampler.resample(output - numsamples, numsamples); +} + + +//------------------------------------------------- +// update_prescale - update the prescale value, +// recomputing derived values +//------------------------------------------------- + +void ym2608::update_prescale(uint8_t prescale) +{ + // tell the FM engine + m_fm.set_clock_prescale(prescale); + m_ssg.prescale_changed(); + + // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum----- + // rate = clock/48 rate = clock/24 rate = clock/8 + // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate + // 6 3:1 2:3 6:1 4:3 18:1 4:1 + // 3 1.5:1 1:3 3:1 2:3 9:1 2:1 + // 2 1:1 1:6 2:1 1:3 6:1 1:1 + + // compute the number of FM samples per output sample, and select the + // resampler function + if (m_fidelity == OPN_FIDELITY_MIN) + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break; + case 3: m_fm_samples_per_output = 0; m_ssg_resampler.configure(1, 3); break; + case 2: m_fm_samples_per_output = 1; m_ssg_resampler.configure(1, 6); break; + } + } + else if (m_fidelity == OPN_FIDELITY_MED) + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 6; m_ssg_resampler.configure(4, 3); break; + case 3: m_fm_samples_per_output = 3; m_ssg_resampler.configure(2, 3); break; + case 2: m_fm_samples_per_output = 2; m_ssg_resampler.configure(1, 3); break; + } + } + else + { + switch (prescale) + { + default: + case 6: m_fm_samples_per_output = 18; m_ssg_resampler.configure(4, 1); break; + case 3: m_fm_samples_per_output = 9; m_ssg_resampler.configure(2, 1); break; + case 2: m_fm_samples_per_output = 6; m_ssg_resampler.configure(1, 1); break; + } + } + + // if overriding the SSG, override the configuration with the nop + // resampler to at least keep the sample index moving forward + if (m_ssg.overridden()) + m_ssg_resampler.configure(0, 0); +} + + +//------------------------------------------------- +// clock_fm_and_adpcm - clock FM and ADPCM state +//------------------------------------------------- + +void ym2608::clock_fm_and_adpcm() +{ + // top bit of the IRQ enable flags controls 3-channel vs 6-channel mode + uint32_t fmmask = bitfield(m_irq_enable, 7) ? 0x3f : 0x07; + + // clock the system + uint32_t env_counter = m_fm.clock(fm_engine::ALL_CHANNELS); + + // clock the ADPCM-A engine on every envelope cycle + // (channels 4 and 5 clock every 2 envelope clocks) + if (bitfield(env_counter, 0, 2) == 0) + m_adpcm_a.clock(bitfield(env_counter, 2) ? 0x0f : 0x3f); + + // clock the ADPCM-B engine every cycle + m_adpcm_b.clock(); + + // update the FM content; OPNA is 13-bit with no intermediate clipping + m_fm.output(m_last_fm.clear(), 1, 32767, fmmask); + + // mix in the ADPCM and clamp + m_adpcm_a.output(m_last_fm, 0x3f); + m_adpcm_b.output(m_last_fm, 1); + m_last_fm.clamp16(); +} + + +//********************************************************* +// YMF288 +//********************************************************* + +// YMF288 is a YM2608 with the following changes: +// * ADPCM-B part removed +// * prescaler removed (fixed at 6) +// * CSM removed +// * Low power mode added +// * SSG tone frequency is altered in some way? (explicitly DC for Tp 0-7, also double volume in some cases) +// * I/O ports removed +// * Shorter busy times +// * All registers can be read + +//------------------------------------------------- +// ymf288 - constructor +//------------------------------------------------- + +ymf288::ymf288(ymfm_interface &intf) : + m_fidelity(OPN_FIDELITY_MAX), + m_address(0), + m_irq_enable(0x03), + m_flag_control(0x03), + m_fm(intf), + m_ssg(intf), + m_ssg_resampler(m_ssg), + m_adpcm_a(intf, 0) +{ + m_last_fm.clear(); + update_prescale(); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ymf288::reset() +{ + // reset the engines + m_fm.reset(); + m_ssg.reset(); + m_adpcm_a.reset(); + + // configure ADPCM percussion sounds; these are present in an embedded ROM + m_adpcm_a.set_start_end(0, 0x0000, 0x01bf); // bass drum + m_adpcm_a.set_start_end(1, 0x01c0, 0x043f); // snare drum + m_adpcm_a.set_start_end(2, 0x0440, 0x1b7f); // top cymbal + m_adpcm_a.set_start_end(3, 0x1b80, 0x1cff); // high hat + m_adpcm_a.set_start_end(4, 0x1d00, 0x1f7f); // tom tom + m_adpcm_a.set_start_end(5, 0x1f80, 0x1fff); // rim shot + + // initialize our special interrupt states, then read the upper status + // register, which updates the IRQs + m_irq_enable = 0x03; + m_flag_control = 0x00; + read_status_hi(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ymf288::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_irq_enable); + state.save_restore(m_flag_control); + state.save_restore(m_last_fm.data); + + m_fm.save_restore(state); + m_ssg.save_restore(state); + m_ssg_resampler.save_restore(state); + m_adpcm_a.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ymf288::read_status() +{ + uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ymf288::read_data() +{ + uint8_t result = 0; + if (m_address < 0x0e) + { + // 00-0D: Read from SSG + result = m_ssg.read(m_address & 0x0f); + } + else if (m_address < 0x10) + { + // 0E-0F: I/O ports not supported + result = 0xff; + } + else if (m_address == 0xff) + { + // FF: ID code + result = 2; + } + else if (ymf288_mode()) + { + // registers are readable in YMF288 mode + result = m_fm.regs().read(m_address); + } + return result; +} + + +//------------------------------------------------- +// read_status_hi - read the extended status +// register +//------------------------------------------------- + +uint8_t ymf288::read_status_hi() +{ + // fetch regular status + uint8_t status = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB); + + // turn off any bits that have been requested to be masked + status &= ~(m_flag_control & 0x03); + + // update the status so that IRQs are propagated + m_fm.set_reset_status(status, ~status); + + // merge in the busy flag + if (m_fm.intf().ymfm_is_busy()) + status |= fm_engine::STATUS_BUSY; + return status; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ymf288::read(uint32_t offset) +{ + uint8_t result = 0; + switch (offset & 3) + { + case 0: // status port, YM2203 compatible + result = read_status(); + break; + + case 1: // data port + result = read_data(); + break; + + case 2: // status port, extended + result = read_status_hi(); + break; + + case 3: // unmapped + debug::log_unexpected_read_write("Unexpected read from YMF288 offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ymf288::write_address(uint8_t data) +{ + // just set the address + m_address = data; + + // in YMF288 mode, busy is signaled after address writes too + if (ymf288_mode()) + m_fm.intf().ymfm_set_busy_end(16); +} + + +//------------------------------------------------- +// write - handle a write to the data register +//------------------------------------------------- + +void ymf288::write_data(uint8_t data) +{ + // ignore if paired with upper address + if (bitfield(m_address, 8)) + return; + + // wait times are shorter in YMF288 mode + int busy_cycles = ymf288_mode() ? 16 : 32 * m_fm.clock_prescale(); + if (m_address < 0x0e) + { + // 00-0D: write to SSG + m_ssg.write(m_address & 0x0f, data); + } + else if (m_address < 0x10) + { + // 0E-0F: I/O ports not supported + } + else if (m_address < 0x20) + { + // 10-1F: write to ADPCM-A + m_adpcm_a.write(m_address & 0x0f, data); + busy_cycles = 32 * m_fm.clock_prescale(); + } + else if (m_address == 0x27) + { + // 27: mode register; CSM isn't supported so disable it + data &= 0x7f; + m_fm.write(m_address, data); + } + else if (m_address == 0x29) + { + // 29: special IRQ mask register + m_irq_enable = data; + m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x03); + } + else + { + // 20-27, 2A-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(busy_cycles); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ymf288::write_address_hi(uint8_t data) +{ + // just set the address + m_address = 0x100 | data; + + // in YMF288 mode, busy is signaled after address writes too + if (ymf288_mode()) + m_fm.intf().ymfm_set_busy_end(16); +} + + +//------------------------------------------------- +// write_data_hi - handle a write to the upper +// data register +//------------------------------------------------- + +void ymf288::write_data_hi(uint8_t data) +{ + // ignore if paired with upper address + if (!bitfield(m_address, 8)) + return; + + // wait times are shorter in YMF288 mode + int busy_cycles = ymf288_mode() ? 16 : 32 * m_fm.clock_prescale(); + if (m_address == 0x110) + { + // 110: IRQ flag control + if (bitfield(data, 7)) + m_fm.set_reset_status(0, 0xff); + else + { + m_flag_control = data; + m_fm.set_irq_mask(m_irq_enable & ~m_flag_control & 0x03); + } + } + else + { + // 100-10F,111-1FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(busy_cycles); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ymf288::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // upper address port + write_address_hi(data); + break; + + case 3: // upper data port + write_data_hi(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ymf288::generate(output_data *output, uint32_t numsamples) +{ + // FM output is just repeated the prescale number of times; note that + // 0 is a special 1.5 case + if (m_fm_samples_per_output != 0) + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + } + } + else + { + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + uint32_t step = (m_ssg_resampler.sampindex() + samp) % 3; + if (step == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + if (step == 1) + { + clock_fm_and_adpcm(); + output->data[0] = (output->data[0] + m_last_fm.data[0]) / 2; + output->data[1] = (output->data[1] + m_last_fm.data[1]) / 2; + } + } + } + + // resample the SSG as configured + m_ssg_resampler.resample(output - numsamples, numsamples); +} + + +//------------------------------------------------- +// update_prescale - update the prescale value, +// recomputing derived values +//------------------------------------------------- + +void ymf288::update_prescale() +{ + // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum----- + // rate = clock/144 rate = clock/144 rate = clock/16 + // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate + // 6 1:1 2:9 1:1 2:9 9:1 2:1 + + // compute the number of FM samples per output sample, and select the + // resampler function + if (m_fidelity == OPN_FIDELITY_MIN || m_fidelity == OPN_FIDELITY_MED) + { + m_fm_samples_per_output = 1; + m_ssg_resampler.configure(2, 9); + } + else + { + m_fm_samples_per_output = 9; + m_ssg_resampler.configure(2, 1); + } + + // if overriding the SSG, override the configuration with the nop + // resampler to at least keep the sample index moving forward + if (m_ssg.overridden()) + m_ssg_resampler.configure(0, 0); +} + + +//------------------------------------------------- +// clock_fm_and_adpcm - clock FM and ADPCM state +//------------------------------------------------- + +void ymf288::clock_fm_and_adpcm() +{ + // top bit of the IRQ enable flags controls 3-channel vs 6-channel mode + uint32_t fmmask = bitfield(m_irq_enable, 7) ? 0x3f : 0x07; + + // clock the system + uint32_t env_counter = m_fm.clock(fm_engine::ALL_CHANNELS); + + // clock the ADPCM-A engine on every envelope cycle + // (channels 4 and 5 clock every 2 envelope clocks) + if (bitfield(env_counter, 0, 2) == 0) + m_adpcm_a.clock(bitfield(env_counter, 2) ? 0x0f : 0x3f); + + // update the FM content; OPNA is 13-bit with no intermediate clipping + m_fm.output(m_last_fm.clear(), 1, 32767, fmmask); + + // mix in the ADPCM + m_adpcm_a.output(m_last_fm, 0x3f); +} + + + +//********************************************************* +// YM2610 +//********************************************************* + +//------------------------------------------------- +// ym2610 - constructor +//------------------------------------------------- + +ym2610::ym2610(ymfm_interface &intf, uint8_t channel_mask) : + m_fidelity(OPN_FIDELITY_MAX), + m_address(0), + m_fm_mask(channel_mask), + m_eos_status(0x00), + m_flag_mask(EOS_FLAGS_MASK), + m_fm(intf), + m_ssg(intf), + m_ssg_resampler(m_ssg), + m_adpcm_a(intf, 8), + m_adpcm_b(intf, 8) +{ + update_prescale(); +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2610::reset() +{ + // reset the engines + m_fm.reset(); + m_ssg.reset(); + m_adpcm_a.reset(); + m_adpcm_b.reset(); + + // initialize our special interrupt states + m_eos_status = 0x00; + m_flag_mask = EOS_FLAGS_MASK; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2610::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_eos_status); + state.save_restore(m_flag_mask); + + m_fm.save_restore(state); + m_ssg.save_restore(state); + m_ssg_resampler.save_restore(state); + m_adpcm_a.save_restore(state); + m_adpcm_b.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2610::read_status() +{ + uint8_t result = m_fm.status() & (fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read_data - read the data register +//------------------------------------------------- + +uint8_t ym2610::read_data() +{ + uint8_t result = 0; + if (m_address < 0x0e) + { + // 00-0D: Read from SSG + result = m_ssg.read(m_address & 0x0f); + } + else if (m_address < 0x10) + { + // 0E-0F: I/O ports not supported + result = 0xff; + } + else if (m_address == 0xff) + { + // FF: ID code + result = 1; + } + return result; +} + + +//------------------------------------------------- +// read_status_hi - read the extended status +// register +//------------------------------------------------- + +uint8_t ym2610::read_status_hi() +{ + return m_eos_status & m_flag_mask; +} + + +//------------------------------------------------- +// read_data_hi - read the upper data register +//------------------------------------------------- + +uint8_t ym2610::read_data_hi() +{ + uint8_t result = 0; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2610::read(uint32_t offset) +{ + uint8_t result = 0; + switch (offset & 3) + { + case 0: // status port, YM2203 compatible + result = read_status(); + break; + + case 1: // data port (only SSG) + result = read_data(); + break; + + case 2: // status port, extended + result = read_status_hi(); + break; + + case 3: // ADPCM-B data + result = read_data_hi(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2610::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the data register +//------------------------------------------------- + +void ym2610::write_data(uint8_t data) +{ + // ignore if paired with upper address + if (bitfield(m_address, 8)) + return; + + if (m_address < 0x0e) + { + // 00-0D: write to SSG + m_ssg.write(m_address & 0x0f, data); + } + else if (m_address < 0x10) + { + // 0E-0F: I/O ports not supported + } + else if (m_address < 0x1c) + { + // 10-1B: write to ADPCM-B + // YM2610 effectively forces external mode on, and disables recording + if (m_address == 0x10) + data = (data | 0x20) & ~0x40; + m_adpcm_b.write(m_address & 0x0f, data); + } + else if (m_address == 0x1c) + { + // 1C: EOS flag reset + m_flag_mask = ~data & EOS_FLAGS_MASK; + m_eos_status &= ~(data & EOS_FLAGS_MASK); + } + else + { + // 1D-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ym2610::write_address_hi(uint8_t data) +{ + // just set the address + m_address = 0x100 | data; +} + + +//------------------------------------------------- +// write_data_hi - handle a write to the upper +// data register +//------------------------------------------------- + +void ym2610::write_data_hi(uint8_t data) +{ + // ignore if paired with upper address + if (!bitfield(m_address, 8)) + return; + + if (m_address < 0x130) + { + // 100-12F: write to ADPCM-A + m_adpcm_a.write(m_address & 0x3f, data); + } + else + { + // 130-1FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2610::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // upper address port + write_address_hi(data); + break; + + case 3: // upper data port + write_data_hi(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2610::generate(output_data *output, uint32_t numsamples) +{ + // FM output is just repeated the prescale number of times + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + if ((m_ssg_resampler.sampindex() + samp) % m_fm_samples_per_output == 0) + clock_fm_and_adpcm(); + output->data[0] = m_last_fm.data[0]; + output->data[1] = m_last_fm.data[1]; + } + + // resample the SSG as configured + m_ssg_resampler.resample(output - numsamples, numsamples); +} + + +//------------------------------------------------- +// update_prescale - update the prescale value, +// recomputing derived values +//------------------------------------------------- + +void ym2610::update_prescale() +{ + // Fidelity: ---- minimum ---- ---- medium ----- ---- maximum----- + // rate = clock/144 rate = clock/144 rate = clock/16 + // Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate + // 6 1:1 2:9 1:1 2:9 9:1 2:1 + + // compute the number of FM samples per output sample, and select the + // resampler function + if (m_fidelity == OPN_FIDELITY_MIN || m_fidelity == OPN_FIDELITY_MED) + { + m_fm_samples_per_output = 1; + m_ssg_resampler.configure(2, 9); + } + else + { + m_fm_samples_per_output = 9; + m_ssg_resampler.configure(2, 1); + } + + // if overriding the SSG, override the configuration with the nop + // resampler to at least keep the sample index moving forward + if (m_ssg.overridden()) + m_ssg_resampler.configure(0, 0); +} + + +//------------------------------------------------- +// clock_fm_and_adpcm - clock FM and ADPCM state +//------------------------------------------------- + +void ym2610::clock_fm_and_adpcm() +{ + // clock the system + uint32_t env_counter = m_fm.clock(m_fm_mask); + + // clock the ADPCM-A engine on every envelope cycle + if (bitfield(env_counter, 0, 2) == 0) + m_eos_status |= m_adpcm_a.clock(0x3f); + + // clock the ADPCM-B engine every cycle + m_adpcm_b.clock(); + + // we track the last ADPCM-B EOS value in bit 6 (which is hidden from callers); + // if it changed since the last sample, update the visible EOS state in bit 7 + uint8_t live_eos = ((m_adpcm_b.status() & adpcm_b_channel::STATUS_EOS) != 0) ? 0x40 : 0x00; + if (((live_eos ^ m_eos_status) & 0x40) != 0) + m_eos_status = (m_eos_status & ~0xc0) | live_eos | (live_eos << 1); + + // update the FM content; OPNB is 13-bit with no intermediate clipping + m_fm.output(m_last_fm.clear(), 1, 32767, m_fm_mask); + + // mix in the ADPCM and clamp + m_adpcm_a.output(m_last_fm, 0x3f); + m_adpcm_b.output(m_last_fm, 1); + m_last_fm.clamp16(); +} + + + +//********************************************************* +// YM2612 +//********************************************************* + +//------------------------------------------------- +// ym2612 - constructor +//------------------------------------------------- + +ym2612::ym2612(ymfm_interface &intf) : + m_address(0), + m_dac_data(0), + m_dac_enable(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2612::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2612::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_address); + state.save_restore(m_dac_data); + state.save_restore(m_dac_enable); + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2612::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2612::read(uint32_t offset) +{ + uint8_t result = 0; + switch (offset & 3) + { + case 0: // status port, YM2203 compatible + result = read_status(); + break; + + case 1: // data port (unused) + case 2: // status port, extended + case 3: // data port (unused) + debug::log_unexpected_read_write("Unexpected read from YM2612 offset %d\n", offset & 3); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2612::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write_data - handle a write to the data +// register +//------------------------------------------------- + +void ym2612::write_data(uint8_t data) +{ + // ignore if paired with upper address + if (bitfield(m_address, 8)) + return; + + if (m_address == 0x2a) + { + // 2A: DAC data (most significant 8 bits) + m_dac_data = (m_dac_data & ~0x1fe) | ((data ^ 0x80) << 1); + } + else if (m_address == 0x2b) + { + // 2B: DAC enable (bit 7) + m_dac_enable = bitfield(data, 7); + } + else if (m_address == 0x2c) + { + // 2C: test/low DAC bit + m_dac_data = (m_dac_data & ~1) | bitfield(data, 3); + } + else + { + // 00-29, 2D-FF: write to FM + m_fm.write(m_address, data); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write_address_hi - handle a write to the upper +// address register +//------------------------------------------------- + +void ym2612::write_address_hi(uint8_t data) +{ + // just set the address + m_address = 0x100 | data; +} + + +//------------------------------------------------- +// write_data_hi - handle a write to the upper +// data register +//------------------------------------------------- + +void ym2612::write_data_hi(uint8_t data) +{ + // ignore if paired with upper address + if (!bitfield(m_address, 8)) + return; + + // 100-1FF: write to FM + m_fm.write(m_address, data); + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2612::write(uint32_t offset, uint8_t data) +{ + switch (offset & 3) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + + case 2: // upper address port + write_address_hi(data); + break; + + case 3: // upper data port + write_data_hi(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2612::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // sum individual channels to apply DAC discontinuity on each + output->clear(); + output_data temp; + + // first do FM-only channels; OPN2 is 9-bit with intermediate clipping + int const last_fm_channel = m_dac_enable ? 5 : 6; + for (int chan = 0; chan < last_fm_channel; chan++) + { + m_fm.output(temp.clear(), 5, 256, 1 << chan); + output->data[0] += dac_discontinuity(temp.data[0]); + output->data[1] += dac_discontinuity(temp.data[1]); + } + + // add in DAC + if (m_dac_enable) + { + // DAC enabled: start with DAC value then add the first 5 channels only + int32_t dacval = dac_discontinuity(int16_t(m_dac_data << 7) >> 7); + output->data[0] += m_fm.regs().ch_output_0(0x102) ? dacval : dac_discontinuity(0); + output->data[1] += m_fm.regs().ch_output_1(0x102) ? dacval : dac_discontinuity(0); + } + + // output is technically multiplexed rather than mixed, but that requires + // a better sound mixer than we usually have, so just average over the six + // channels; also apply a 64/65 factor to account for the discontinuity + // adjustment above + output->data[0] = (output->data[0] * 128) * 64 / (6 * 65); + output->data[1] = (output->data[1] * 128) * 64 / (6 * 65); + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym3438::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // first do FM-only channels; OPN2C is 9-bit with intermediate clipping + if (!m_dac_enable) + { + // DAC disabled: all 6 channels sum together + m_fm.output(output->clear(), 5, 256, fm_engine::ALL_CHANNELS); + } + else + { + // DAC enabled: start with DAC value then add the first 5 channels only + int32_t dacval = int16_t(m_dac_data << 7) >> 7; + output->data[0] = m_fm.regs().ch_output_0(0x102) ? dacval : 0; + output->data[1] = m_fm.regs().ch_output_1(0x102) ? dacval : 0; + m_fm.output(*output, 5, 256, fm_engine::ALL_CHANNELS ^ (1 << 5)); + } + + // YM3438 doesn't have the same DAC discontinuity, though its output is + // multiplexed like the YM2612 + output->data[0] = (output->data[0] * 128) / 6; + output->data[1] = (output->data[1] * 128) / 6; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ymf276::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // first do FM-only channels; OPN2L is 14-bit with intermediate clipping + if (!m_dac_enable) + { + // DAC disabled: all 6 channels sum together + m_fm.output(output->clear(), 0, 8191, fm_engine::ALL_CHANNELS); + } + else + { + // DAC enabled: start with DAC value then add the first 5 channels only + int32_t dacval = int16_t(m_dac_data << 7) >> 7; + output->data[0] = m_fm.regs().ch_output_0(0x102) ? dacval : 0; + output->data[1] = m_fm.regs().ch_output_1(0x102) ? dacval : 0; + m_fm.output(*output, 0, 8191, fm_engine::ALL_CHANNELS ^ (1 << 5)); + } + + // YMF276 is properly mixed; it shifts down 1 bit before clamping + output->data[0] = clamp(output->data[0] >> 1, -32768, 32767); + output->data[1] = clamp(output->data[1] >> 1, -32768, 32767); + } +} + +} diff --git a/src/sound/ymfm/ymfm_opn.h b/src/sound/ymfm/ymfm_opn.h new file mode 100644 index 000000000..bab68ed93 --- /dev/null +++ b/src/sound/ymfm/ymfm_opn.h @@ -0,0 +1,802 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPN_H +#define YMFM_OPN_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_adpcm.h" +#include "ymfm_fm.h" +#include "ymfm_ssg.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opn_registers_base + +// +// OPN register map: +// +// System-wide registers: +// 21 xxxxxxxx Test register +// 22 ----x--- LFO enable [OPNA+ only] +// -----xxx LFO rate [OPNA+ only] +// 24 xxxxxxxx Timer A value (upper 8 bits) +// 25 ------xx Timer A value (lower 2 bits) +// 26 xxxxxxxx Timer B value +// 27 xx------ CSM/Multi-frequency mode for channel #2 +// --x----- Reset timer B +// ---x---- Reset timer A +// ----x--- Enable timer B +// -----x-- Enable timer A +// ------x- Load timer B +// -------x Load timer A +// 28 x------- Key on/off operator 4 +// -x------ Key on/off operator 3 +// --x----- Key on/off operator 2 +// ---x---- Key on/off operator 1 +// ------xx Channel select +// +// Per-channel registers (channel in address bits 0-1) +// Note that all these apply to address+100 as well on OPNA+ +// A0-A3 xxxxxxxx Frequency number lower 8 bits +// A4-A7 --xxx--- Block (0-7) +// -----xxx Frequency number upper 3 bits +// B0-B3 --xxx--- Feedback level for operator 1 (0-7) +// -----xxx Operator connection algorithm (0-7) +// B4-B7 x------- Pan left [OPNA] +// -x------ Pan right [OPNA] +// --xx---- LFO AM shift (0-3) [OPNA+ only] +// -----xxx LFO PM depth (0-7) [OPNA+ only] +// +// Per-operator registers (channel in address bits 0-1, operator in bits 2-3) +// Note that all these apply to address+100 as well on OPNA+ +// 30-3F -xxx---- Detune value (0-7) +// ----xxxx Multiple value (0-15) +// 40-4F -xxxxxxx Total level (0-127) +// 50-5F xx------ Key scale rate (0-3) +// ---xxxxx Attack rate (0-31) +// 60-6F x------- LFO AM enable [OPNA] +// ---xxxxx Decay rate (0-31) +// 70-7F ---xxxxx Sustain rate (0-31) +// 80-8F xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// 90-9F ----x--- SSG-EG enable +// -----xxx SSG-EG envelope (0-7) +// +// Special multi-frequency registers (channel implicitly #2; operator in address bits 0-1) +// A8-AB xxxxxxxx Frequency number lower 8 bits +// AC-AF --xxx--- Block (0-7) +// -----xxx Frequency number upper 3 bits +// +// Internal (fake) registers: +// B8-BB --xxxxxx Latched frequency number upper bits (from A4-A7) +// BC-BF --xxxxxx Latched frequency number upper bits (from AC-AF) +// + +template +class opn_registers_base : public fm_registers_base +{ +public: + // constants + static constexpr uint32_t OUTPUTS = IsOpnA ? 2 : 1; + static constexpr uint32_t CHANNELS = IsOpnA ? 6 : 3; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 4; + static constexpr uint32_t WAVEFORMS = 1; + static constexpr uint32_t REGISTERS = IsOpnA ? 0x200 : 0x100; + static constexpr uint32_t REG_MODE = 0x27; + static constexpr uint32_t DEFAULT_PRESCALE = 6; + static constexpr uint32_t EG_CLOCK_DIVIDER = 3; + static constexpr bool EG_HAS_SSG = true; + static constexpr bool MODULATOR_DELAY = false; + static constexpr uint32_t CSM_TRIGGER_MASK = 1 << 2; + static constexpr uint8_t STATUS_TIMERA = 0x01; + static constexpr uint8_t STATUS_TIMERB = 0x02; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opn_registers_base(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + if (!IsOpnA) + return chnum; + else + return (chnum % 3) + 0x100 * (chnum / 3); + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + if (!IsOpnA) + return opnum + opnum / 3; + else + return (opnum % 12) + ((opnum % 12) / 3) + 0x100 * (opnum / 12); + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // read a register value + uint8_t read(uint16_t index) const { return m_regdata[index]; } + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // reset the LFO + void reset_lfo() { m_lfo_counter = 0; } + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return LFO/noise states + uint32_t noise_state() const { return 0; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t test() const { return byte(0x21, 0, 8); } + uint32_t lfo_enable() const { return IsOpnA ? byte(0x22, 3, 1) : 0; } + uint32_t lfo_rate() const { return IsOpnA ? byte(0x22, 0, 3) : 0; } + uint32_t timer_a_value() const { return word(0x24, 0, 8, 0x25, 0, 2); } + uint32_t timer_b_value() const { return byte(0x26, 0, 8); } + uint32_t csm() const { return (byte(0x27, 6, 2) == 2); } + uint32_t multi_freq() const { return (byte(0x27, 6, 2) != 0); } + uint32_t reset_timer_b() const { return byte(0x27, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x27, 4, 1); } + uint32_t enable_timer_b() const { return byte(0x27, 3, 1); } + uint32_t enable_timer_a() const { return byte(0x27, 2, 1); } + uint32_t load_timer_b() const { return byte(0x27, 1, 1); } + uint32_t load_timer_a() const { return byte(0x27, 0, 1); } + uint32_t multi_block_freq(uint32_t num) const { return word(0xac, 0, 6, 0xa8, 0, 8, num); } + + // per-channel registers + uint32_t ch_block_freq(uint32_t choffs) const { return word(0xa4, 0, 6, 0xa0, 0, 8, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0xb0, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xb0, 0, 3, choffs); } + uint32_t ch_output_any(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 6, 2, choffs) : 1; } + uint32_t ch_output_0(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 7, 1, choffs) : 1; } + uint32_t ch_output_1(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 6, 1, choffs) : 0; } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 4, 2, choffs) : 0; } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return IsOpnA ? byte(0xb4, 0, 3, choffs) : 0; } + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x30, 4, 3, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x30, 0, 4, opoffs); } + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x50, 6, 2, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x50, 0, 5, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return IsOpnA ? byte(0x60, 7, 1, opoffs) : 0; } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0x70, 0, 5, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); } + uint32_t op_ssg_eg_enable(uint32_t opoffs) const { return byte(0x90, 3, 1, opoffs); } + uint32_t op_ssg_eg_mode(uint32_t opoffs) const { return byte(0x90, 0, 3, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter; // LFO counter + uint8_t m_lfo_am; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + +using opn_registers = opn_registers_base; +using opna_registers = opn_registers_base; + + + +//********************************************************* +// OPN IMPLEMENTATION CLASSES +//********************************************************* + +// A note about prescaling and sample rates. +// +// YM2203, YM2608, and YM2610 contain an onboard SSG (basically, a YM2149). +// In order to properly generate sound at fully fidelity, the output sample +// rate of the YM2149 must be input_clock / 8. This is much higher than the +// FM needs, but in the interest of keeping things simple, the OPN generate +// functions will output at the higher rate and just replicate the last FM +// sample as many times as needed. +// +// To make things even more complicated, the YM2203 and YM2608 allow for +// software-controlled prescaling, which affects the FM and SSG clocks in +// different ways. There are three settings: divide by 6/4 (FM/SSG); divide +// by 3/2; and divide by 2/1. +// +// Thus, the minimum output sample rate needed by each part of the chip +// varies with the prescale as follows: +// +// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 ----- +// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate +// 6 /72 /16 /144 /32 /144 /32 +// 3 /36 /8 /72 /16 +// 2 /24 /4 /48 /8 +// +// If we standardized on the fastest SSG rate, we'd end up with the following +// (ratios are output_samples:source_samples): +// +// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 ----- +// rate = clock/4 rate = clock/8 rate = clock/16 +// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate +// 6 18:1 4:1 18:1 4:1 9:1 2:1 +// 3 9:1 2:1 9:1 2:1 +// 2 6:1 1:1 6:1 1:1 +// +// However, that's a pretty big performance hit for minimal gain. Going to +// the other extreme, we could standardize on the fastest FM rate, but then +// at least one prescale case (3) requires the FM to be smeared across two +// output samples: +// +// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 ----- +// rate = clock/24 rate = clock/48 rate = clock/144 +// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate +// 6 3:1 2:3 3:1 2:3 1:1 2:9 +// 3 1.5:1 1:3 1.5:1 1:3 +// 2 1:1 1:6 1:1 1:6 +// +// Stepping back one factor of 2 addresses that issue: +// +// ---- YM2203 ----- ---- YM2608 ----- ---- YM2610 ----- +// rate = clock/12 rate = clock/24 rate = clock/144 +// Prescale FM rate SSG rate FM rate SSG rate FM rate SSG rate +// 6 6:1 4:3 6:1 4:3 1:1 2:9 +// 3 3:1 2:3 3:1 2:3 +// 2 2:1 1:3 2:1 1:3 +// +// This gives us three levels of output fidelity: +// OPN_FIDELITY_MAX -- highest sample rate, using fastest SSG rate +// OPN_FIDELITY_MIN -- lowest sample rate, using fastest FM rate +// OPN_FIDELITY_MED -- medium sample rate such that FM is never smeared +// +// At the maximum clocks for YM2203/YM2608 (4Mhz/8MHz), these rates will +// end up as: +// OPN_FIDELITY_MAX = 1000kHz +// OPN_FIDELITY_MIN = 166kHz +// OPN_FIEDLITY_MED = 333kHz + + +// ======================> opn_fidelity + +enum opn_fidelity : uint8_t +{ + OPN_FIDELITY_MAX, + OPN_FIDELITY_MIN, + OPN_FIDELITY_MED, + + OPN_FIDELITY_DEFAULT = OPN_FIDELITY_MAX +}; + + +// ======================> ssg_resampler + +template +class ssg_resampler +{ +private: + // helper to add the last computed value to the sums, applying the given scale + void add_last(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale = 1); + + // helper to clock a new value and then add it to the sums, applying the given scale + void clock_and_add(int32_t &sum0, int32_t &sum1, int32_t &sum2, int32_t scale = 1); + + // helper to write the sums to the appropriate outputs, applying the given + // divisor to the final result + void write_to_output(OutputType *output, int32_t sum0, int32_t sum1, int32_t sum2, int32_t divisor = 1); + +public: + // constructor + ssg_resampler(ssg_engine &ssg); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // get the current sample index + uint32_t sampindex() const { return m_sampindex; } + + // configure the ratio + void configure(uint8_t outsamples, uint8_t srcsamples); + + // resample + void resample(OutputType *output, uint32_t numsamples) + { + (this->*m_resampler)(output, numsamples); + } + +private: + // resample SSG output to the target at a rate of 1 SSG sample + // to every n output samples + template + void resample_n_1(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of n SSG samples + // to every 1 output sample + template + void resample_1_n(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of 9 SSG samples + // to every 2 output samples + void resample_2_9(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of 3 SSG samples + // to every 1 output sample + void resample_1_3(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of 3 SSG samples + // to every 2 output samples + void resample_2_3(OutputType *output, uint32_t numsamples); + + // resample SSG output to the target at a rate of 3 SSG samples + // to every 4 output samples + void resample_4_3(OutputType *output, uint32_t numsamples); + + // no-op resampler + void resample_nop(OutputType *output, uint32_t numsamples); + + // define a pointer type + using resample_func = void (ssg_resampler::*)(OutputType *output, uint32_t numsamples); + + // internal state + ssg_engine &m_ssg; + uint32_t m_sampindex; + resample_func m_resampler; + ssg_engine::output_data m_last; +}; + + +// ======================> ym2203 + +class ym2203 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = ssg_engine::OUTPUTS; + static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS; + using output_data = ymfm_output; + + // constructor + ym2203(ymfm_interface &intf); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(m_fm.clock_prescale()); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const + { + switch (m_fidelity) + { + case OPN_FIDELITY_MIN: return input_clock / 24; + case OPN_FIDELITY_MED: return input_clock / 12; + default: + case OPN_FIDELITY_MAX: return input_clock / 4; + } + } + uint32_t ssg_effective_clock(uint32_t input_clock) const { uint32_t scale = m_fm.clock_prescale() * 2 / 3; return input_clock * 2 / scale; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + void update_prescale(uint8_t prescale); + void clock_fm(); + + // internal state + opn_fidelity m_fidelity; // configured fidelity + uint8_t m_address; // address register + uint8_t m_fm_samples_per_output; // how many samples to repeat + fm_engine::output_data m_last_fm; // last FM output + fm_engine m_fm; // core FM engine + ssg_engine m_ssg; // SSG engine + ssg_resampler m_ssg_resampler; // SSG resampler helper +}; + + + +//********************************************************* +// OPNA IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2608 + +class ym2608 +{ + static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x04; + static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08; + static constexpr uint8_t STATUS_ADPCM_B_ZERO = 0x10; + static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x20; + +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = 1; + static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS; + using output_data = ymfm_output; + + // constructor + ym2608(ymfm_interface &intf); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(m_fm.clock_prescale()); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const + { + switch (m_fidelity) + { + case OPN_FIDELITY_MIN: return input_clock / 48; + case OPN_FIDELITY_MED: return input_clock / 24; + default: + case OPN_FIDELITY_MAX: return input_clock / 8; + } + } + uint32_t ssg_effective_clock(uint32_t input_clock) const { uint32_t scale = m_fm.clock_prescale() * 2 / 3; return input_clock / scale; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read_status_hi(); + uint8_t read_data_hi(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_data_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + void update_prescale(uint8_t prescale); + void clock_fm_and_adpcm(); + + // internal state + opn_fidelity m_fidelity; // configured fidelity + uint16_t m_address; // address register + uint8_t m_fm_samples_per_output; // how many samples to repeat + uint8_t m_irq_enable; // IRQ enable register + uint8_t m_flag_control; // flag control register + fm_engine::output_data m_last_fm; // last FM output + fm_engine m_fm; // core FM engine + ssg_engine m_ssg; // SSG engine + ssg_resampler m_ssg_resampler; // SSG resampler helper + adpcm_a_engine m_adpcm_a; // ADPCM-A engine + adpcm_b_engine m_adpcm_b; // ADPCM-B engine +}; + + +// ======================> ymf288 + +class ymf288 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = 1; + static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS; + using output_data = ymfm_output; + + // constructor + ymf288(ymfm_interface &intf); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const + { + switch (m_fidelity) + { + case OPN_FIDELITY_MIN: return input_clock / 144; + case OPN_FIDELITY_MED: return input_clock / 144; + default: + case OPN_FIDELITY_MAX: return input_clock / 16; + } + } + uint32_t ssg_effective_clock(uint32_t input_clock) const { return input_clock / 4; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read_status_hi(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_data_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + bool ymf288_mode() { return ((m_fm.regs().read(0x20) & 0x02) != 0); } + void update_prescale(); + void clock_fm_and_adpcm(); + + // internal state + opn_fidelity m_fidelity; // configured fidelity + uint16_t m_address; // address register + uint8_t m_fm_samples_per_output; // how many samples to repeat + uint8_t m_irq_enable; // IRQ enable register + uint8_t m_flag_control; // flag control register + fm_engine::output_data m_last_fm; // last FM output + fm_engine m_fm; // core FM engine + ssg_engine m_ssg; // SSG engine + ssg_resampler m_ssg_resampler; // SSG resampler helper + adpcm_a_engine m_adpcm_a; // ADPCM-A engine +}; + + +// ======================> ym2610/ym2610b + +class ym2610 +{ + static constexpr uint8_t EOS_FLAGS_MASK = 0xbf; + +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t FM_OUTPUTS = fm_engine::OUTPUTS; + static constexpr uint32_t SSG_OUTPUTS = 1; + static constexpr uint32_t OUTPUTS = FM_OUTPUTS + SSG_OUTPUTS; + using output_data = ymfm_output; + + // constructor + ym2610(ymfm_interface &intf, uint8_t channel_mask = 0x36); + + // configuration + void ssg_override(ssg_override &intf) { m_ssg.override(intf); } + void set_fidelity(opn_fidelity fidelity) { m_fidelity = fidelity; update_prescale(); } + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const + { + switch (m_fidelity) + { + case OPN_FIDELITY_MIN: return input_clock / 144; + case OPN_FIDELITY_MED: return input_clock / 144; + default: + case OPN_FIDELITY_MAX: return input_clock / 16; + } + } + uint32_t ssg_effective_clock(uint32_t input_clock) const { return input_clock / 4; } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read_data(); + uint8_t read_status_hi(); + uint8_t read_data_hi(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_data_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal helpers + void update_prescale(); + void clock_fm_and_adpcm(); + + // internal state + opn_fidelity m_fidelity; // configured fidelity + uint16_t m_address; // address register + uint8_t const m_fm_mask; // FM channel mask + uint8_t m_fm_samples_per_output; // how many samples to repeat + uint8_t m_eos_status; // end-of-sample signals + uint8_t m_flag_mask; // flag mask control + fm_engine::output_data m_last_fm; // last FM output + fm_engine m_fm; // core FM engine + ssg_engine m_ssg; // core FM engine + ssg_resampler m_ssg_resampler; // SSG resampler helper + adpcm_a_engine m_adpcm_a; // ADPCM-A engine + adpcm_b_engine m_adpcm_b; // ADPCM-B engine +}; + +class ym2610b : public ym2610 +{ +public: + // constructor + ym2610b(ymfm_interface &intf) : ym2610(intf, 0x3f) { } +}; + + +// ======================> ym2612 + +class ym2612 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + using output_data = fm_engine::output_data; + + // constructor + ym2612(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write_address_hi(uint8_t data); + void write_data_hi(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // simulate the DAC discontinuity + constexpr int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 3) : (value + 4); } + + // internal state + uint16_t m_address; // address register + uint16_t m_dac_data; // 9-bit DAC data + uint8_t m_dac_enable; // DAC enabled? + fm_engine m_fm; // core FM engine +}; + + +// ======================> ym3438 + +class ym3438 : public ym2612 +{ +public: + ym3438(ymfm_interface &intf) : ym2612(intf) { } + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); +}; + + +// ======================> ymf276 + +class ymf276 : public ym2612 +{ +public: + ymf276(ymfm_interface &intf) : ym2612(intf) { } + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples); +}; + +} + + +#endif // YMFM_OPN_H diff --git a/src/sound/ymfm/ymfm_opq.cpp b/src/sound/ymfm/ymfm_opq.cpp new file mode 100644 index 000000000..3467c0ddf --- /dev/null +++ b/src/sound/ymfm/ymfm_opq.cpp @@ -0,0 +1,480 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opq.h" +#include "ymfm_fm.ipp" + +#define TEMPORARY_DEBUG_PRINTS (0) + +// +// OPQ (aka YM3806/YM3533) +// +// This chip is not officially documented as far as I know. What I have +// comes from Jari Kangas' work on reverse engineering the PSR70: +// +// https://github.com/JKN0/PSR70-reverse +// +// OPQ appears be bsaically a mixture of OPM and OPN. +// + +namespace ymfm +{ + +//********************************************************* +// OPQ SPECIFICS +//********************************************************* + +//------------------------------------------------- +// opq_registers - constructor +//------------------------------------------------- + +opq_registers::opq_registers() : + m_lfo_counter(0), + m_lfo_am(0) +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + uint16_t zeroval = m_waveform[0][0]; + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[1][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index]; +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +void opq_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // enable output on both channels by default + m_regdata[0x10] = m_regdata[0x11] = m_regdata[0x12] = m_regdata[0x13] = 0xc0; + m_regdata[0x14] = m_regdata[0x15] = m_regdata[0x16] = m_regdata[0x17] = 0xc0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opq_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPM this is fixed +//------------------------------------------------- + +void opq_registers::operator_map(operator_mapping &dest) const +{ + // seems like the operators are not swizzled like they are on OPM/OPN? + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 8, 16, 24 ), // Channel 0 operators + operator_list( 1, 9, 17, 25 ), // Channel 1 operators + operator_list( 2, 10, 18, 26 ), // Channel 2 operators + operator_list( 3, 11, 19, 27 ), // Channel 3 operators + operator_list( 4, 12, 20, 28 ), // Channel 4 operators + operator_list( 5, 13, 21, 29 ), // Channel 5 operators + operator_list( 6, 14, 22, 30 ), // Channel 6 operators + operator_list( 7, 15, 23, 31 ), // Channel 7 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +bool opq_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // detune/multiple share a register based on the MSB of what is written + // remap the multiple values to 100-11F + if ((index & 0xe0) == 0x40 && bitfield(data, 7) != 0) + index += 0xc0; + + m_regdata[index] = data; + + // handle writes to the key on index + if (index == 0x05) + { + channel = bitfield(data, 0, 3); + opmask = bitfield(data, 3, 4); + return true; + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +int32_t opq_registers::clock_noise_and_lfo() +{ + // OPQ LFO is not well-understood, but the enable and rate values + // look a lot like OPN, so we'll crib from there as a starting point + + // if LFO not enabled (not present on OPN), quick exit with 0s + if (!lfo_enable()) + { + m_lfo_counter = 0; + m_lfo_am = 0; + return 0; + } + + // this table is based on converting the frequencies in the applications + // manual to clock dividers, based on the assumption of a 7-bit LFO value + static uint8_t const lfo_max_count[8] = { 109, 78, 72, 68, 63, 45, 9, 6 }; + uint32_t subcount = uint8_t(m_lfo_counter++); + + // when we cross the divider count, add enough to zero it and cause an + // increment at bit 8; the 7-bit value lives from bits 8-14 + if (subcount >= lfo_max_count[lfo_rate()]) + m_lfo_counter += 0x101 - subcount; + + // AM value is 7 bits, staring at bit 8; grab the low 6 directly + m_lfo_am = bitfield(m_lfo_counter, 8, 6); + + // first half of the AM period (bit 6 == 0) is inverted + if (bitfield(m_lfo_counter, 8+6) == 0) + m_lfo_am ^= 0x3f; + + // PM value is 5 bits, starting at bit 10; grab the low 3 directly + int32_t pm = bitfield(m_lfo_counter, 10, 3); + + // PM is reflected based on bit 3 + if (bitfield(m_lfo_counter, 10+3)) + pm ^= 7; + + // PM is negated based on bit 4 + return bitfield(m_lfo_counter, 10+4) ? -pm : pm; +} + + +//------------------------------------------------- +// lfo_am_offset - return the AM offset from LFO +// for the given channel +//------------------------------------------------- + +uint32_t opq_registers::lfo_am_offset(uint32_t choffs) const +{ + // OPM maps AM quite differently from OPN + + // shift value for AM sensitivity is [*, 0, 1, 2], + // mapping to values of [0, 23.9, 47.8, and 95.6dB] + uint32_t am_sensitivity = ch_lfo_am_sens(choffs); + if (am_sensitivity == 0) + return 0; + + // QUESTION: see OPN note below for the dB range mapping; it applies + // here as well + + // raw LFO AM value on OPM is 0-FF, which is already a factor of 2 + // larger than the OPN below, putting our staring point at 2x theirs; + // this works out since our minimum is 2x their maximum + return m_lfo_am << (am_sensitivity - 1); +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data +//------------------------------------------------- + +void opq_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // set up the easy stuff + cache.waveform = &m_waveform[op_waveform(opoffs)][0]; + + // get frequency from the appropriate registers + uint32_t block_freq = cache.block_freq = (opoffs & 8) ? ch_block_freq_24(choffs) : ch_block_freq_13(choffs); + + // compute the keycode: block_freq is: + // + // BBBFFFFFFFFFFFF + // ^^^^??? + // + // keycode is not understood, so just guessing it is like OPN: + // the 5-bit keycode uses the top 4 bits plus a magic formula + // for the final bit + uint32_t keycode = bitfield(block_freq, 11, 4) << 1; + + // lowest bit is determined by a mix of next lower FNUM bits + // according to this equation from the YM2608 manual: + // + // (F11 & (F10 | F9 | F8)) | (!F11 & F10 & F9 & F8) + // + // for speed, we just look it up in a 16-bit constant + keycode |= bitfield(0xfe80, bitfield(block_freq, 8, 4)); + + // detune adjustment: the detune values supported by the OPQ are + // a much larger range (6 bits vs 3 bits) compared to any other + // known FM chip; based on experiments, it seems that the extra + // bits provide a bigger detune range rather than finer control, + // so until we get true measurements just assemble a net detune + // value by summing smaller detunes + int32_t detune = int32_t(op_detune(opoffs)) - 0x20; + int32_t abs_detune = std::abs(detune); + int32_t adjust = (abs_detune / 3) * detune_adjustment(3, keycode) + detune_adjustment(abs_detune % 3, keycode); + cache.detune = (detune >= 0) ? adjust : -adjust; + + // multiple value, as an x.1 value (0 means 0.5) + static const uint8_t s_multiple_map[16] = { 1,2,4,6,8,10,12,14,16,18,20,24,30,32,34,36 }; + cache.multiple = s_multiple_map[op_multiple(opoffs)]; + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those + if (lfo_enable() == 0 || ch_lfo_pm_sens(choffs) == 0) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + cache.total_level = op_total_level(opoffs) << 3; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval); + cache.eg_rate[EG_REVERB] = (ch_reverb(choffs) != 0) ? 5*4 : cache.eg_rate[EG_RELEASE]; + cache.eg_shift = 0; +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +uint32_t opq_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // OPN phase calculation has only a single detune parameter + // and uses FNUMs instead of keycodes + + // extract frequency number (low 12 bits of block_freq) + uint32_t fnum = bitfield(cache.block_freq, 0, 12); + + // if there's a non-zero PM sensitivity, compute the adjustment + uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs); + if (pm_sensitivity != 0) + { + // apply the phase adjustment based on the upper 7 bits + // of FNUM and the PM depth parameters + fnum += opn_lfo_pm_phase_adjustment(bitfield(cache.block_freq, 5, 7), pm_sensitivity, lfo_raw_pm); + + // keep fnum to 12 bits + fnum &= 0xfff; + } + + // apply block shift to compute phase step + uint32_t block = bitfield(cache.block_freq, 12, 3); + uint32_t phase_step = (fnum << block) >> 2; + + // apply detune based on the keycode + phase_step += cache.detune; + + // clamp to 17 bits in case detune overflows + // QUESTION: is this specific to the YM2612/3438? + phase_step &= 0x1ffff; + + // apply frequency multiplier (which is cached as an x.1 value) + return (phase_step * cache.multiple) >> 1; +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +std::string opq_registers::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = choffs; + uint32_t opnum = opoffs; + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(end, "%u.%02u freq=%04X dt=%+2d fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c", + chnum, opnum, + (opoffs & 1) ? ch_block_freq_24(choffs) : ch_block_freq_13(choffs), + int32_t(op_detune(opoffs)) - 0x20, + ch_feedback(choffs), + ch_algorithm(choffs), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_sustain_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + ch_output_0(choffs) ? 'L' : '-', + ch_output_1(choffs) ? 'R' : '-'); + + bool am = (lfo_enable() && op_lfo_am_enable(opoffs) && ch_lfo_am_sens(choffs) != 0); + if (am) + end += sprintf(end, " am=%u", ch_lfo_am_sens(choffs)); + bool pm = (lfo_enable() && ch_lfo_pm_sens(choffs) != 0); + if (pm) + end += sprintf(end, " pm=%u", ch_lfo_pm_sens(choffs)); + if (am || pm) + end += sprintf(end, " lfo=%02X", lfo_rate()); + if (ch_reverb(choffs)) + end += sprintf(end, " reverb"); + + return buffer; +} + + + +//********************************************************* +// YM3806 +//********************************************************* + +//------------------------------------------------- +// ym3806 - constructor +//------------------------------------------------- + +ym3806::ym3806(ymfm_interface &intf) : + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym3806::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym3806::save_restore(ymfm_saved_state &state) +{ + m_fm.save_restore(state); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym3806::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym3806::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset) + { + case 0: // status port + result = read_status(); + break; + + default: // unknown + debug::log_unexpected_read_write("Unexpected read from YM3806 offset %02X\n", offset); + break; + } +if (TEMPORARY_DEBUG_PRINTS && offset != 0) printf("Read %02X = %02X\n", offset, result); + return result; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym3806::write(uint32_t offset, uint8_t data) +{ +if (TEMPORARY_DEBUG_PRINTS && (offset != 3 || data != 0x71)) printf("Write %02X = %02X\n", offset, data); + // write the FM register + m_fm.write(offset, data); +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym3806::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; YM3806 is full 14-bit with no intermediate clipping + m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // YM3608 appears to go through a YM3012 DAC, which means we want to apply + // the FP truncation logic to the outputs + output->roundtrip_fp(); + } +} + +} diff --git a/src/sound/ymfm/ymfm_opq.h b/src/sound/ymfm/ymfm_opq.h new file mode 100644 index 000000000..f530ac070 --- /dev/null +++ b/src/sound/ymfm/ymfm_opq.h @@ -0,0 +1,293 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPQ_H +#define YMFM_OPQ_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_fm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opq_registers + +// +// OPQ register map: +// +// System-wide registers: +// 03 xxxxxxxx Timer control (unknown; 0x71 causes interrupts at ~10ms) +// 04 ----x--- LFO disable +// -----xxx LFO frequency (0=~4Hz, 6=~10Hz, 7=~47Hz) +// 05 -x------ Key on/off operator 4 +// --x----- Key on/off operator 3 +// ---x---- Key on/off operator 2 +// ----x--- Key on/off operator 1 +// -----xxx Channel select +// +// Per-channel registers (channel in address bits 0-2) +// 10-17 x------- Pan right +// -x------ Pan left +// --xxx--- Feedback level for operator 1 (0-7) +// -----xxx Operator connection algorithm (0-7) +// 18-1F x------- Reverb +// -xxx---- PM sensitivity +// ------xx AM shift +// 20-27 -xxx---- Block (0-7), Operator 2 & 4 +// ----xxxx Frequency number upper 4 bits, Operator 2 & 4 +// 28-2F -xxx---- Block (0-7), Operator 1 & 3 +// ----xxxx Frequency number upper 4 bits, Operator 1 & 3 +// 30-37 xxxxxxxx Frequency number lower 8 bits, Operator 2 & 4 +// 38-3F xxxxxxxx Frequency number lower 8 bits, Operator 1 & 3 +// +// Per-operator registers (channel in address bits 0-2, operator in bits 3-4) +// 40-5F 0-xxxxxx Detune value (0-63) +// 1---xxxx Multiple value (0-15) +// 60-7F -xxxxxxx Total level (0-127) +// 80-9F xx------ Key scale rate (0-3) +// ---xxxxx Attack rate (0-31) +// A0-BF x------- LFO AM enable, retrigger disable +// x------ Waveform select +// ---xxxxx Decay rate (0-31) +// C0-DF ---xxxxx Sustain rate (0-31) +// E0-FF xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// +// Diffs from OPM: +// - 2 frequencies/channel +// - retrigger disable +// - 2 waveforms +// - uses FNUM +// - reverb behavior +// - larger detune range +// +// Questions: +// - timer information is pretty light +// - how does echo work? +// - + +class opq_registers : public fm_registers_base +{ +public: + // constants + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 8; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 4; + static constexpr uint32_t WAVEFORMS = 2; + static constexpr uint32_t REGISTERS = 0x120; + static constexpr uint32_t REG_MODE = 0x03; + static constexpr uint32_t DEFAULT_PRESCALE = 2; + static constexpr uint32_t EG_CLOCK_DIVIDER = 3; + static constexpr bool EG_HAS_REVERB = true; + static constexpr bool MODULATOR_DELAY = false; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr uint8_t STATUS_TIMERA = 0; + static constexpr uint8_t STATUS_TIMERB = 0x04; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opq_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // reset the LFO + void reset_lfo() { m_lfo_counter = 0; } + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return the current noise state, gated by the noise clock + uint32_t noise_state() const { return 0; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t timer_a_value() const { return 0; } + uint32_t timer_b_value() const { return byte(0x03, 2, 6) | 0xc0; } // ??? + uint32_t csm() const { return 0; } + uint32_t reset_timer_b() const { return byte(0x03, 0, 1); } // ??? + uint32_t reset_timer_a() const { return 0; } + uint32_t enable_timer_b() const { return byte(0x03, 0, 1); } // ??? + uint32_t enable_timer_a() const { return 0; } + uint32_t load_timer_b() const { return byte(0x03, 0, 1); } // ??? + uint32_t load_timer_a() const { return 0; } + uint32_t lfo_enable() const { return byte(0x04, 3, 1) ^ 1; } + uint32_t lfo_rate() const { return byte(0x04, 0, 3); } + + // per-channel registers + uint32_t ch_output_any(uint32_t choffs) const { return byte(0x10, 6, 2, choffs); } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x10, 6, 1, choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return byte(0x10, 7, 1, choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0x10, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x10, 0, 3, choffs); } + uint32_t ch_reverb(uint32_t choffs) const { return byte(0x18, 7, 1, choffs); } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x18, 4, 3, choffs); } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x18, 0, 2, choffs); } + uint32_t ch_block_freq_24(uint32_t choffs) const { return word(0x20, 0, 7, 0x30, 0, 8, choffs); } + uint32_t ch_block_freq_13(uint32_t choffs) const { return word(0x28, 0, 7, 0x38, 0, 8, choffs); } + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return byte(0xa0, 6, 1, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); } + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter; // LFO counter + uint8_t m_lfo_am; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym3806 + +class ym3806 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + using output_data = fm_engine::output_data; + + // constructor + ym3806(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data) { /* not supported; only direct writes */ } + void write_data(uint8_t data) { /* not supported; only direct writes */ } + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + fm_engine m_fm; // core FM engine +}; + + +// ======================> ym3533 + +class ym3533 : public ym3806 +{ +public: + // constructor + ym3533(ymfm_interface &intf) : + ym3806(intf) { } +}; + +} + + +#endif // YMFM_OPQ_H diff --git a/src/sound/ymfm/ymfm_opx.h b/src/sound/ymfm/ymfm_opx.h new file mode 100644 index 000000000..9f9bbdba7 --- /dev/null +++ b/src/sound/ymfm/ymfm_opx.h @@ -0,0 +1,290 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPX_H +#define YMFM_OPX_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_fm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opx_registers + +// +// OPX register map: +// +// System-wide registers: +// +// Per-channel registers (channel in address bits 0-2) +// +// Per-operator registers (4 banks): +// 00-0F x------- Enable +// -xxxx--- EXT out +// -------x Key on +// 10-1F xxxxxxxx LFO frequency +// 20-2F xx------ AM sensitivity (0-3) +// --xxx--- PM sensitivity (0-7) +// ------xx LFO waveform (0=disable, 1=saw, 2= +// 30-3F -xxx---- Detune (0-7) +// ----xxxx Multiple (0-15) +// 40-4F -xxxxxxx Total level (0-127) +// 50-5F xxx----- Key scale (0-7) +// ---xxxxx Attack rate (0-31) +// 60-6F ---xxxxx Decay rate (0-31) +// 70-7F ---xxxxx Sustain rate (0-31) +// 80-8F xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// 90-9F xxxxxxxx Frequency number (low 8 bits) +// A0-AF xxxx---- Block (0-15) +// ----xxxx Frequency number (high 4 bits) +// B0-BF x------- Acc on +// -xxx---- Feedback level (0-7) +// -----xxx Waveform (0-7, 7=PCM) +// C0-CF ----xxxx Algorithm (0-15) +// D0-DF xxxx---- CH0 level (0-15) +// ----xxxx CH1 level (0-15) +// E0-EF xxxx---- CH2 level (0-15) +// ----xxxx CH3 level (0-15) +// + +class opx_registers : public fm_registers_base +{ + // LFO waveforms are 256 entries long + static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256; + +public: + // constants + static constexpr uint32_t OUTPUTS = 8; + static constexpr uint32_t CHANNELS = 24; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 2; + static constexpr uint32_t WAVEFORMS = 8; + static constexpr uint32_t REGISTERS = 0x800; + static constexpr uint32_t DEFAULT_PRESCALE = 8; + static constexpr uint32_t EG_CLOCK_DIVIDER = 2; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr uint32_t REG_MODE = 0x14; + static constexpr uint8_t STATUS_TIMERA = 0x01; + static constexpr uint8_t STATUS_TIMERB = 0x02; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opz_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return the current noise state, gated by the noise clock + uint32_t noise_state() const { return m_noise_state; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t noise_frequency() const { return byte(0x0f, 0, 5); } + uint32_t noise_enable() const { return byte(0x0f, 7, 1); } + uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); } + uint32_t timer_b_value() const { return byte(0x12, 0, 8); } + uint32_t csm() const { return byte(0x14, 7, 1); } + uint32_t reset_timer_b() const { return byte(0x14, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x14, 4, 1); } + uint32_t enable_timer_b() const { return byte(0x14, 3, 1); } + uint32_t enable_timer_a() const { return byte(0x14, 2, 1); } + uint32_t load_timer_b() const { return byte(0x14, 1, 1); } + uint32_t load_timer_a() const { return byte(0x14, 0, 1); } + uint32_t lfo2_pm_depth() const { return byte(0x148, 0, 7); } // fake + uint32_t lfo2_rate() const { return byte(0x16, 0, 8); } + uint32_t lfo2_am_depth() const { return byte(0x17, 0, 7); } + uint32_t lfo_rate() const { return byte(0x18, 0, 8); } + uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); } + uint32_t lfo_pm_depth() const { return byte(0x149, 0, 7); } // fake + uint32_t output_bits() const { return byte(0x1b, 6, 2); } + uint32_t lfo2_sync() const { return byte(0x1b, 5, 1); } + uint32_t lfo_sync() const { return byte(0x1b, 4, 1); } + uint32_t lfo2_waveform() const { return byte(0x1b, 2, 2); } + uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); } + + // per-channel registers + uint32_t ch_volume(uint32_t choffs) const { return byte(0x00, 0, 8, choffs); } + uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_key_on(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); } + uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); } + uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x140, 4, 3, choffs); } // fake + uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x140, 0, 2, choffs); } // fake + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_fix_range(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_fix_frequency(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return byte(0x100, 4, 3, opoffs); } // fake + uint32_t op_fine(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } // fake + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); } + uint32_t op_fix_mode(uint32_t opoffs) const { return byte(0x80, 5, 1, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); } + uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); } + uint32_t op_eg_shift(uint32_t opoffs) const { return byte(0x120, 6, 2, opoffs); } // fake + uint32_t op_reverb_rate(uint32_t opoffs) const { return byte(0x120, 0, 3, opoffs); } // fake + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter[2]; // LFO counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_noise_counter; // noise counter + uint8_t m_noise_state; // latched noise state + uint8_t m_noise_lfo; // latched LFO noise value + uint8_t m_lfo_am[2]; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_phase_substep[OPERATORS]; // phase substep for fixed frequency + int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8 + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2414 + +class ym2414 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + using output_data = fm_engine::output_data; + + // constructor + ym2414(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + +} + + +#endif // YMFM_OPZ_H diff --git a/src/sound/ymfm/ymfm_opz.cpp b/src/sound/ymfm/ymfm_opz.cpp new file mode 100644 index 000000000..adeefd79f --- /dev/null +++ b/src/sound/ymfm/ymfm_opz.cpp @@ -0,0 +1,808 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_opz.h" +#include "ymfm_fm.ipp" + +#define TEMPORARY_DEBUG_PRINTS (0) + +// +// OPZ (aka YM2414) +// +// This chip is not officially documented as far as I know. What I have +// comes from this site: +// +// http://sr4.sakura.ne.jp/fmsound/opz.html +// +// and from reading the TX81Z operator manual, which describes how a number +// of these new features work. +// +// OPZ appears be bsaically OPM with a bunch of extra features. +// +// For starters, there are two LFO generators. I have presumed that they +// operate identically since identical parameters are offered for each. I +// have also presumed the effects are additive between them. The LFOs on +// the OPZ have an extra "sync" option which apparently causes the LFO to +// reset whenever a key on is received. +// +// At the channel level, there is an additional 8-bit volume control. This +// might work as an addition to total level, or some other way. Completely +// unknown, and unimplemented. +// +// At the operator level, there are a number of extra features. First, there +// are 8 different waveforms to choose from. These are different than the +// waveforms introduced in the OPL2 and later chips. +// +// Second, there is an additional "reverb" stage added to the envelope +// generator, which kicks in when the envelope reaches -18dB. It specifies +// a slower decay rate to produce a sort of faux reverb effect. +// +// The envelope generator also supports a 2-bit shift value, which can be +// used to reduce the effect of the envelope attenuation. +// +// OPZ supports a "fixed frequency" mode for each operator, with a 3-bit +// range and 4-bit frequency value, plus a 1-bit enable. Not sure how that +// works at all, so it's not implemented. +// +// There are also several mystery fields in the operators which I have no +// clue about: "fine" (4 bits), "eg_shift" (2 bits), and "rev" (3 bits). +// eg_shift is some kind of envelope generator effect, but how it works is +// unknown. +// +// Also, according to the site above, the panning controls are changed from +// OPM, with a "mono" bit and only one control bit for the right channel. +// Current implementation is just a guess. +// + +namespace ymfm +{ + +//********************************************************* +// OPZ REGISTERS +//********************************************************* + +//------------------------------------------------- +// opz_registers - constructor +//------------------------------------------------- + +opz_registers::opz_registers() : + m_lfo_counter{ 0, 0 }, + m_noise_lfsr(1), + m_noise_counter(0), + m_noise_state(0), + m_noise_lfo(0), + m_lfo_am{ 0, 0 } +{ + // create the waveforms + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[0][index] = abs_sin_attenuation(index) | (bitfield(index, 9) << 15); + + // we only have the diagrams to judge from, but suspecting waveform 1 (and + // derived waveforms) are sin^2, based on OPX description of similar wave- + // forms; since our sin table is logarithmic, this ends up just being + // 2*existing value + uint16_t zeroval = m_waveform[0][0]; + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + m_waveform[1][index] = std::min(2 * (m_waveform[0][index] & 0x7fff), zeroval) | (bitfield(index, 9) << 15); + + // remaining waveforms are just derivations of the 2 main ones + for (uint32_t index = 0; index < WAVEFORM_LENGTH; index++) + { + m_waveform[2][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index]; + m_waveform[3][index] = bitfield(index, 9) ? zeroval : m_waveform[1][index]; + m_waveform[4][index] = bitfield(index, 9) ? zeroval : m_waveform[0][index * 2]; + m_waveform[5][index] = bitfield(index, 9) ? zeroval : m_waveform[1][index * 2]; + m_waveform[6][index] = bitfield(index, 9) ? zeroval : m_waveform[0][(index * 2) & 0x1ff]; + m_waveform[7][index] = bitfield(index, 9) ? zeroval : m_waveform[1][(index * 2) & 0x1ff]; + } + + // create the LFO waveforms; AM in the low 8 bits, PM in the upper 8 + // waveforms are adjusted to match the pictures in the application manual + for (uint32_t index = 0; index < LFO_WAVEFORM_LENGTH; index++) + { + // waveform 0 is a sawtooth + uint8_t am = index ^ 0xff; + int8_t pm = int8_t(index); + m_lfo_waveform[0][index] = am | (pm << 8); + + // waveform 1 is a square wave + am = bitfield(index, 7) ? 0 : 0xff; + pm = int8_t(am ^ 0x80); + m_lfo_waveform[1][index] = am | (pm << 8); + + // waveform 2 is a triangle wave + am = bitfield(index, 7) ? (index << 1) : ((index ^ 0xff) << 1); + pm = int8_t(bitfield(index, 6) ? am : ~am); + m_lfo_waveform[2][index] = am | (pm << 8); + + // waveform 3 is noise; it is filled in dynamically + } +} + + +//------------------------------------------------- +// reset - reset to initial state +//------------------------------------------------- + +void opz_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + + // enable output on both channels by default + m_regdata[0x30] = m_regdata[0x31] = m_regdata[0x32] = m_regdata[0x33] = 0x01; + m_regdata[0x34] = m_regdata[0x35] = m_regdata[0x36] = m_regdata[0x37] = 0x01; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void opz_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_lfo_counter); + state.save_restore(m_lfo_am); + state.save_restore(m_noise_lfsr); + state.save_restore(m_noise_counter); + state.save_restore(m_noise_state); + state.save_restore(m_noise_lfo); + state.save_restore(m_regdata); + state.save_restore(m_phase_substep); +} + + +//------------------------------------------------- +// operator_map - return an array of operator +// indices for each channel; for OPZ this is fixed +//------------------------------------------------- + +void opz_registers::operator_map(operator_mapping &dest) const +{ + // Note that the channel index order is 0,2,1,3, so we bitswap the index. + // + // This is because the order in the map is: + // carrier 1, carrier 2, modulator 1, modulator 2 + // + // But when wiring up the connections, the more natural order is: + // carrier 1, modulator 1, carrier 2, modulator 2 + static const operator_mapping s_fixed_map = + { { + operator_list( 0, 16, 8, 24 ), // Channel 0 operators + operator_list( 1, 17, 9, 25 ), // Channel 1 operators + operator_list( 2, 18, 10, 26 ), // Channel 2 operators + operator_list( 3, 19, 11, 27 ), // Channel 3 operators + operator_list( 4, 20, 12, 28 ), // Channel 4 operators + operator_list( 5, 21, 13, 29 ), // Channel 5 operators + operator_list( 6, 22, 14, 30 ), // Channel 6 operators + operator_list( 7, 23, 15, 31 ), // Channel 7 operators + } }; + dest = s_fixed_map; +} + + +//------------------------------------------------- +// write - handle writes to the register array +//------------------------------------------------- + +bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint32_t &opmask) +{ + assert(index < REGISTERS); + + // special mappings: + // 0x16 -> 0x188 if bit 7 is set + // 0x19 -> 0x189 if bit 7 is set + // 0x38..0x3F -> 0x180..0x187 if bit 7 is set + // 0x40..0x5F -> 0x100..0x11F if bit 7 is set + // 0xC0..0xDF -> 0x120..0x13F if bit 5 is set + if (index == 0x17 && bitfield(data, 7) != 0) + m_regdata[0x188] = data; + else if (index == 0x19 && bitfield(data, 7) != 0) + m_regdata[0x189] = data; + else if ((index & 0xf8) == 0x38 && bitfield(data, 7) != 0) + m_regdata[0x180 + (index & 7)] = data; + else if ((index & 0xe0) == 0x40 && bitfield(data, 7) != 0) + m_regdata[0x100 + (index & 0x1f)] = data; + else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0) + m_regdata[0x120 + (index & 0x1f)] = data; + else if (index < 0x100) + m_regdata[index] = data; + + // preset writes restore some values from a preset memory; not sure + // how this really works but the TX81Z will overwrite the sustain level/ + // release rate register and the envelope shift/reverb rate register to + // dampen sound, then write the preset number to register 8 to restore them + if (index == 0x08) + { + int chan = bitfield(data, 0, 3); + if (TEMPORARY_DEBUG_PRINTS) + printf("Loading preset %d\n", chan); + m_regdata[0xe0 + chan + 0] = m_regdata[0x140 + chan + 0]; + m_regdata[0xe0 + chan + 8] = m_regdata[0x140 + chan + 8]; + m_regdata[0xe0 + chan + 16] = m_regdata[0x140 + chan + 16]; + m_regdata[0xe0 + chan + 24] = m_regdata[0x140 + chan + 24]; + m_regdata[0x120 + chan + 0] = m_regdata[0x160 + chan + 0]; + m_regdata[0x120 + chan + 8] = m_regdata[0x160 + chan + 8]; + m_regdata[0x120 + chan + 16] = m_regdata[0x160 + chan + 16]; + m_regdata[0x120 + chan + 24] = m_regdata[0x160 + chan + 24]; + } + + // store the presets under some unknown condition; the pattern of writes + // when setting a new preset is: + // + // 08 (0-7), 80-9F, A0-BF, C0-DF, C0-DF (alt), 20-27, 40-5F, 40-5F (alt), + // C0-DF (alt -- again?), 38-3F, 1B, 18, E0-FF + // + // So it writes 0-7 to 08 to either reset all presets or to indicate + // that we're going to be loading them. Immediately after all the writes + // above, the very next write will be temporary values to blow away the + // values loaded into E0-FF, so somehow it also knows that anything after + // that point is not part of the preset. + // + // For now, try using the 40-5F (alt) writes as flags that presets are + // being loaded until the E0-FF writes happen. + bool is_setting_preset = (bitfield(m_regdata[0x100 + (index & 0x1f)], 7) != 0); + if (is_setting_preset) + { + if ((index & 0xe0) == 0xe0) + { + m_regdata[0x140 + (index & 0x1f)] = data; + m_regdata[0x100 + (index & 0x1f)] &= 0x7f; + } + else if ((index & 0xe0) == 0xc0 && bitfield(data, 5) != 0) + m_regdata[0x160 + (index & 0x1f)] = data; + } + + // handle writes to the key on index + if ((index & 0xf8) == 0x20 && bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)) + { + channel = bitfield(index, 0, 3); + opmask = ch_key_on(channel) ? 0xf : 0; + + // according to the TX81Z manual, the sync option causes the LFOs + // to reset at each note on + if (opmask != 0) + { + if (lfo_sync()) + m_lfo_counter[0] = 0; + if (lfo2_sync()) + m_lfo_counter[1] = 0; + } + return true; + } + return false; +} + + +//------------------------------------------------- +// clock_noise_and_lfo - clock the noise and LFO, +// handling clock division, depth, and waveform +// computations +//------------------------------------------------- + +int32_t opz_registers::clock_noise_and_lfo() +{ + // base noise frequency is measured at 2x 1/2 FM frequency; this + // means each tick counts as two steps against the noise counter + uint32_t freq = noise_frequency(); + for (int rep = 0; rep < 2; rep++) + { + // evidence seems to suggest the LFSR is clocked continually and just + // sampled at the noise frequency for output purposes; note that the + // low 8 bits are the most recent 8 bits of history while bits 8-24 + // contain the 17 bit LFSR state + m_noise_lfsr <<= 1; + m_noise_lfsr |= bitfield(m_noise_lfsr, 17) ^ bitfield(m_noise_lfsr, 14) ^ 1; + + // compare against the frequency and latch when we exceed it + if (m_noise_counter++ >= freq) + { + m_noise_counter = 0; + m_noise_state = bitfield(m_noise_lfsr, 17); + } + } + + // treat the rate as a 4.4 floating-point step value with implied + // leading 1; this matches exactly the frequencies in the application + // manual, though it might not be implemented exactly this way on chip + uint32_t rate0 = lfo_rate(); + uint32_t rate1 = lfo2_rate(); + m_lfo_counter[0] += (0x10 | bitfield(rate0, 0, 4)) << bitfield(rate0, 4, 4); + m_lfo_counter[1] += (0x10 | bitfield(rate1, 0, 4)) << bitfield(rate1, 4, 4); + uint32_t lfo0 = bitfield(m_lfo_counter[0], 22, 8); + uint32_t lfo1 = bitfield(m_lfo_counter[1], 22, 8); + + // fill in the noise entry 1 ahead of our current position; this + // ensures the current value remains stable for a full LFO clock + // and effectively latches the running value when the LFO advances + uint32_t lfo_noise = bitfield(m_noise_lfsr, 17, 8); + m_lfo_waveform[3][(lfo0 + 1) & 0xff] = lfo_noise | (lfo_noise << 8); + m_lfo_waveform[3][(lfo1 + 1) & 0xff] = lfo_noise | (lfo_noise << 8); + + // fetch the AM/PM values based on the waveform; AM is unsigned and + // encoded in the low 8 bits, while PM signed and encoded in the upper + // 8 bits + int32_t ampm0 = m_lfo_waveform[lfo_waveform()][lfo0]; + int32_t ampm1 = m_lfo_waveform[lfo2_waveform()][lfo1]; + + // apply depth to the AM values and store for later + m_lfo_am[0] = ((ampm0 & 0xff) * lfo_am_depth()) >> 7; + m_lfo_am[1] = ((ampm1 & 0xff) * lfo2_am_depth()) >> 7; + + // apply depth to the PM values and return them combined into two + int32_t pm0 = ((ampm0 >> 8) * int32_t(lfo_pm_depth())) >> 7; + int32_t pm1 = ((ampm1 >> 8) * int32_t(lfo2_pm_depth())) >> 7; + return (pm0 & 0xff) | (pm1 << 8); +} + + +//------------------------------------------------- +// lfo_am_offset - return the AM offset from LFO +// for the given channel +//------------------------------------------------- + +uint32_t opz_registers::lfo_am_offset(uint32_t choffs) const +{ + // not sure how this works for real, but just adding the two + // AM LFOs together + uint32_t result = 0; + + // shift value for AM sensitivity is [*, 0, 1, 2], + // mapping to values of [0, 23.9, 47.8, and 95.6dB] + uint32_t am_sensitivity = ch_lfo_am_sens(choffs); + if (am_sensitivity != 0) + result = m_lfo_am[0] << (am_sensitivity - 1); + + // QUESTION: see OPN note below for the dB range mapping; it applies + // here as well + + // raw LFO AM value on OPZ is 0-FF, which is already a factor of 2 + // larger than the OPN below, putting our staring point at 2x theirs; + // this works out since our minimum is 2x their maximum + uint32_t am_sensitivity2 = ch_lfo2_am_sens(choffs); + if (am_sensitivity2 != 0) + result += m_lfo_am[1] << (am_sensitivity2 - 1); + + return result; +} + + +//------------------------------------------------- +// cache_operator_data - fill the operator cache +// with prefetched data +//------------------------------------------------- + +void opz_registers::cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache) +{ + // TODO: how does fixed frequency mode work? appears to be enabled by + // op_fix_mode(), and controlled by op_fix_range(), op_fix_frequency() + + // TODO: what is op_rev()? + + // set up the easy stuff + cache.waveform = &m_waveform[op_waveform(opoffs)][0]; + + // get frequency from the channel + uint32_t block_freq = cache.block_freq = ch_block_freq(choffs); + + // compute the keycode: block_freq is: + // + // BBBCCCCFFFFFF + // ^^^^^ + // + // the 5-bit keycode is just the top 5 bits (block + top 2 bits + // of the key code) + uint32_t keycode = bitfield(block_freq, 8, 5); + + // detune adjustment + cache.detune = detune_adjustment(op_detune(opoffs), keycode); + + // multiple value, as an x.4 value (0 means 0.5) + // the "fine" control provides the fractional bits + cache.multiple = op_multiple(opoffs) << 4; + if (cache.multiple == 0) + cache.multiple = 0x08; + cache.multiple |= op_fine(opoffs); + + // phase step, or PHASE_STEP_DYNAMIC if PM is active; this depends on + // block_freq, detune, and multiple, so compute it after we've done those; + // note that fix frequency mode is also treated as dynamic + if (!op_fix_mode(opoffs) && (lfo_pm_depth() == 0 || ch_lfo_pm_sens(choffs) == 0) && (lfo2_pm_depth() == 0 || ch_lfo2_pm_sens(choffs) == 0)) + cache.phase_step = compute_phase_step(choffs, opoffs, cache, 0); + else + cache.phase_step = opdata_cache::PHASE_STEP_DYNAMIC; + + // total level, scaled by 8 + // TODO: how does ch_volume() fit into this? + cache.total_level = op_total_level(opoffs) << 3; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = op_sustain_level(opoffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // determine KSR adjustment for enevlope rates + uint32_t ksrval = keycode >> (op_ksr(opoffs) ^ 3); + cache.eg_rate[EG_ATTACK] = effective_rate(op_attack_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_DECAY] = effective_rate(op_decay_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_SUSTAIN] = effective_rate(op_sustain_rate(opoffs) * 2, ksrval); + cache.eg_rate[EG_RELEASE] = effective_rate(op_release_rate(opoffs) * 4 + 2, ksrval); + cache.eg_rate[EG_REVERB] = cache.eg_rate[EG_RELEASE]; + uint32_t reverb = op_reverb_rate(opoffs); + if (reverb != 0) + cache.eg_rate[EG_REVERB] = std::min(effective_rate(reverb * 4 + 2, ksrval), cache.eg_rate[EG_REVERB]); + + // set the envelope shift; TX81Z manual says operator 1 shift is fixed at "off" + cache.eg_shift = ((opoffs & 0x18) == 0) ? 0 : op_eg_shift(opoffs); +} + + +//------------------------------------------------- +// compute_phase_step - compute the phase step +//------------------------------------------------- + +uint32_t opz_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm) +{ + // OPZ has a fixed frequency mode; it is unclear whether the + // detune and multiple parameters affect things + + uint32_t phase_step; + if (op_fix_mode(opoffs)) + { + // the baseline frequency in hz comes from the fix frequency and fine + // registers, which can specify values 8-255Hz in 1Hz increments; that + // value is then shifted up by the 3-bit range + uint32_t freq = op_fix_frequency(opoffs) << 4; + if (freq == 0) + freq = 8; + freq |= op_fine(opoffs); + freq <<= op_fix_range(opoffs); + + // there is not enough resolution in the plain phase step to track the + // full range of frequencies, so we keep a per-operator sub step with an + // additional 12 bits of resolution; this calculation gives us, for + // example, a frequency of 8.0009Hz when 8Hz is requested + uint32_t substep = m_phase_substep[opoffs]; + substep += 75 * freq; + phase_step = substep >> 12; + m_phase_substep[opoffs] = substep & 0xfff; + + // detune/multiple occupy the same space as fix_range/fix_frequency so + // don't apply them in addition + return phase_step; + } + else + { + // start with coarse detune delta; table uses cents value from + // manual, converted into 1/64ths + static const int16_t s_detune2_delta[4] = { 0, (600*64+50)/100, (781*64+50)/100, (950*64+50)/100 }; + int32_t delta = s_detune2_delta[op_detune2(opoffs)]; + + // add in the PM deltas + uint32_t pm_sensitivity = ch_lfo_pm_sens(choffs); + if (pm_sensitivity != 0) + { + // raw PM value is -127..128 which is +/- 200 cents + // manual gives these magnitudes in cents: + // 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700 + // this roughly corresponds to shifting the 200-cent value: + // 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2 + if (pm_sensitivity < 6) + delta += int8_t(lfo_raw_pm) >> (6 - pm_sensitivity); + else + delta += int8_t(lfo_raw_pm) << (pm_sensitivity - 5); + } + uint32_t pm_sensitivity2 = ch_lfo2_pm_sens(choffs); + if (pm_sensitivity2 != 0) + { + // raw PM value is -127..128 which is +/- 200 cents + // manual gives these magnitudes in cents: + // 0, +/-5, +/-10, +/-20, +/-50, +/-100, +/-400, +/-700 + // this roughly corresponds to shifting the 200-cent value: + // 0 >> 5, >> 4, >> 3, >> 2, >> 1, << 1, << 2 + if (pm_sensitivity2 < 6) + delta += int8_t(lfo_raw_pm >> 8) >> (6 - pm_sensitivity2); + else + delta += int8_t(lfo_raw_pm >> 8) << (pm_sensitivity2 - 5); + } + + // apply delta and convert to a frequency number; this translation is + // the same as OPM so just re-use that helper + phase_step = opm_key_code_to_phase_step(cache.block_freq, delta); + + // apply detune based on the keycode + phase_step += cache.detune; + + // apply frequency multiplier (which is cached as an x.4 value) + return (phase_step * cache.multiple) >> 4; + } +} + + +//------------------------------------------------- +// log_keyon - log a key-on event +//------------------------------------------------- + +std::string opz_registers::log_keyon(uint32_t choffs, uint32_t opoffs) +{ + uint32_t chnum = choffs; + uint32_t opnum = opoffs; + + char buffer[256]; + char *end = &buffer[0]; + + end += sprintf(end, "%u.%02u", chnum, opnum); + + if (op_fix_mode(opoffs)) + end += sprintf(end, " fixfreq=%X fine=%X shift=%X", op_fix_frequency(opoffs), op_fine(opoffs), op_fix_range(opoffs)); + else + end += sprintf(end, " freq=%04X dt2=%u fine=%X", ch_block_freq(choffs), op_detune2(opoffs), op_fine(opoffs)); + + end += sprintf(end, " dt=%u fb=%u alg=%X mul=%X tl=%02X ksr=%u adsr=%02X/%02X/%02X/%X sl=%X out=%c%c", + op_detune(opoffs), + ch_feedback(choffs), + ch_algorithm(choffs), + op_multiple(opoffs), + op_total_level(opoffs), + op_ksr(opoffs), + op_attack_rate(opoffs), + op_decay_rate(opoffs), + op_sustain_rate(opoffs), + op_release_rate(opoffs), + op_sustain_level(opoffs), + ch_output_0(choffs) ? 'L' : '-', + ch_output_1(choffs) ? 'R' : '-'); + + if (op_eg_shift(opoffs) != 0) + end += sprintf(end, " egshift=%u", op_eg_shift(opoffs)); + + bool am = (lfo_am_depth() != 0 && ch_lfo_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0); + if (am) + end += sprintf(end, " am=%u/%02X", ch_lfo_am_sens(choffs), lfo_am_depth()); + bool pm = (lfo_pm_depth() != 0 && ch_lfo_pm_sens(choffs) != 0); + if (pm) + end += sprintf(end, " pm=%u/%02X", ch_lfo_pm_sens(choffs), lfo_pm_depth()); + if (am || pm) + end += sprintf(end, " lfo=%02X/%c", lfo_rate(), "WQTN"[lfo_waveform()]); + + bool am2 = (lfo2_am_depth() != 0 && ch_lfo2_am_sens(choffs) != 0 && op_lfo_am_enable(opoffs) != 0); + if (am2) + end += sprintf(end, " am2=%u/%02X", ch_lfo2_am_sens(choffs), lfo2_am_depth()); + bool pm2 = (lfo2_pm_depth() != 0 && ch_lfo2_pm_sens(choffs) != 0); + if (pm2) + end += sprintf(end, " pm2=%u/%02X", ch_lfo2_pm_sens(choffs), lfo2_pm_depth()); + if (am2 || pm2) + end += sprintf(end, " lfo2=%02X/%c", lfo2_rate(), "WQTN"[lfo2_waveform()]); + + if (op_reverb_rate(opoffs) != 0) + end += sprintf(end, " rev=%u", op_reverb_rate(opoffs)); + if (op_waveform(opoffs) != 0) + end += sprintf(end, " wf=%u", op_waveform(opoffs)); + if (noise_enable() && opoffs == 31) + end += sprintf(end, " noise=1"); + + return buffer; +} + + + +//********************************************************* +// YM2414 +//********************************************************* + +//------------------------------------------------- +// ym2414 - constructor +//------------------------------------------------- + +ym2414::ym2414(ymfm_interface &intf) : + m_address(0), + m_fm(intf) +{ +} + + +//------------------------------------------------- +// reset - reset the system +//------------------------------------------------- + +void ym2414::reset() +{ + // reset the engines + m_fm.reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ym2414::save_restore(ymfm_saved_state &state) +{ + m_fm.save_restore(state); + state.save_restore(m_address); +} + + +//------------------------------------------------- +// read_status - read the status register +//------------------------------------------------- + +uint8_t ym2414::read_status() +{ + uint8_t result = m_fm.status(); + if (m_fm.intf().ymfm_is_busy()) + result |= fm_engine::STATUS_BUSY; + return result; +} + + +//------------------------------------------------- +// read - handle a read from the device +//------------------------------------------------- + +uint8_t ym2414::read(uint32_t offset) +{ + uint8_t result = 0xff; + switch (offset & 1) + { + case 0: // data port (unused) + debug::log_unexpected_read_write("Unexpected read from YM2414 offset %d\n", offset & 3); + break; + + case 1: // status port, YM2203 compatible + result = read_status(); + break; + } + return result; +} + + +//------------------------------------------------- +// write_address - handle a write to the address +// register +//------------------------------------------------- + +void ym2414::write_address(uint8_t data) +{ + // just set the address + m_address = data; +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2414::write_data(uint8_t data) +{ + // write the FM register + m_fm.write(m_address, data); + if (TEMPORARY_DEBUG_PRINTS) + { + switch (m_address & 0xe0) + { + case 0x00: + printf("CTL %02X = %02X\n", m_address, data); + break; + + case 0x20: + switch (m_address & 0xf8) + { + case 0x20: printf("R/FBL/ALG %d = %02X\n", m_address & 7, data); break; + case 0x28: printf("KC %d = %02X\n", m_address & 7, data); break; + case 0x30: printf("KF/M %d = %02X\n", m_address & 7, data); break; + case 0x38: printf("PMS/AMS %d = %02X\n", m_address & 7, data); break; + } + break; + + case 0x40: + if (bitfield(data, 7) == 0) + printf("DT1/MUL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + else + printf("OW/FINE %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0x60: + printf("TL %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0x80: + printf("KRS/FIX/AR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0xa0: + printf("A/D1R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0xc0: + if (bitfield(data, 5) == 0) + printf("DT2/D2R %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + else + printf("EGS/REV %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + + case 0xe0: + printf("D1L/RR %d.%d = %02X\n", m_address & 7, (m_address >> 3) & 3, data); + break; + } + } + + // special cases + if (m_address == 0x1b) + { + // writes to register 0x1B send the upper 2 bits to the output lines + m_fm.intf().ymfm_external_write(ACCESS_IO, 0, data >> 6); + } + + // mark busy for a bit + m_fm.intf().ymfm_set_busy_end(32 * m_fm.clock_prescale()); +} + + +//------------------------------------------------- +// write - handle a write to the register +// interface +//------------------------------------------------- + +void ym2414::write(uint32_t offset, uint8_t data) +{ + switch (offset & 1) + { + case 0: // address port + write_address(data); + break; + + case 1: // data port + write_data(data); + break; + } +} + + +//------------------------------------------------- +// generate - generate one sample of sound +//------------------------------------------------- + +void ym2414::generate(output_data *output, uint32_t numsamples) +{ + for (uint32_t samp = 0; samp < numsamples; samp++, output++) + { + // clock the system + m_fm.clock(fm_engine::ALL_CHANNELS); + + // update the FM content; YM2414 is full 14-bit with no intermediate clipping + m_fm.output(output->clear(), 0, 32767, fm_engine::ALL_CHANNELS); + + // unsure about YM2414 outputs; assume it is like YM2151 + output->roundtrip_fp(); + } +} + +} diff --git a/src/sound/ymfm/ymfm_opz.h b/src/sound/ymfm/ymfm_opz.h new file mode 100644 index 000000000..997ba32f9 --- /dev/null +++ b/src/sound/ymfm/ymfm_opz.h @@ -0,0 +1,332 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_OPZ_H +#define YMFM_OPZ_H + +#pragma once + +#include "ymfm.h" +#include "ymfm_fm.h" + +namespace ymfm +{ + +//********************************************************* +// REGISTER CLASSES +//********************************************************* + +// ======================> opz_registers + +// +// OPZ register map: +// +// System-wide registers: +// 08 -----xxx Load preset (not sure how it gets saved) +// 0F x------- Noise enable +// ---xxxxx Noise frequency +// 10 xxxxxxxx Timer A value (upper 8 bits) +// 11 ------xx Timer A value (lower 2 bits) +// 12 xxxxxxxx Timer B value +// 14 x------- CSM mode +// --x----- Reset timer B +// ---x---- Reset timer A +// ----x--- Enable timer B +// -----x-- Enable timer A +// ------x- Load timer B +// -------x Load timer A +// 16 xxxxxxxx LFO #2 frequency +// 17 0xxxxxxx AM LFO #2 depth +// 1xxxxxxx PM LFO #2 depth +// 18 xxxxxxxx LFO frequency +// 19 0xxxxxxx AM LFO depth +// 1xxxxxxx PM LFO depth +// 1B xx------ CT (2 output data lines) +// --x----- LFO #2 sync +// ---x---- LFO sync +// ----xx-- LFO #2 waveform +// ------xx LFO waveform +// +// Per-channel registers (channel in address bits 0-2) +// 00-07 xxxxxxxx Channel volume +// 20-27 x------- Pan right +// -x------ Key on (0)/off(1) +// --xxx--- Feedback level for operator 1 (0-7) +// -----xxx Operator connection algorithm (0-7) +// 28-2F -xxxxxxx Key code +// 30-37 xxxxxx-- Key fraction +// -------x Mono? mode +// 38-3F 0xxx---- LFO PM sensitivity +// -----0xx LFO AM shift +// 1xxx---- LFO #2 PM sensitivity +// -----1xx LFO #2 AM shift +// +// Per-operator registers (channel in address bits 0-2, operator in bits 3-4) +// 40-5F 0xxx---- Detune value (0-7) +// 0---xxxx Multiple value (0-15) +// 0xxx---- Fix range (0-15) +// 0---xxxx Fix frequency (0-15) +// 1xxx---- Oscillator waveform (0-7) +// 1---xxxx Fine? (0-15) +// 60-7F -xxxxxxx Total level (0-127) +// 80-9F xx------ Key scale rate (0-3) +// --x----- Fix frequency mode +// ---xxxxx Attack rate (0-31) +// A0-BF x------- LFO AM enable +// ---xxxxx Decay rate (0-31) +// C0-DF xx0----- Detune 2 value (0-3) +// --0xxxxx Sustain rate (0-31) +// xx1----- Envelope generator shift? (0-3) +// --1--xxx Rev? (0-7) +// E0-FF xxxx---- Sustain level (0-15) +// ----xxxx Release rate (0-15) +// +// Internal (fake) registers: +// 100-11F -xxx---- Oscillator waveform (0-7) +// ----xxxx Fine? (0-15) +// 120-13F xx------ Envelope generator shift (0-3) +// -----xxx Reverb rate (0-7) +// 140-15F xxxx---- Preset sustain level (0-15) +// ----xxxx Preset release rate (0-15) +// 160-17F xx------ Envelope generator shift (0-3) +// -----xxx Reverb rate (0-7) +// 180-187 -xxx---- LFO #2 PM sensitivity +// ---- xxx LFO #2 AM shift +// 188 -xxxxxxx LFO #2 PM depth +// 189 -xxxxxxx LFO PM depth +// + +class opz_registers : public fm_registers_base +{ + // LFO waveforms are 256 entries long + static constexpr uint32_t LFO_WAVEFORM_LENGTH = 256; + +public: + // constants + static constexpr uint32_t OUTPUTS = 2; + static constexpr uint32_t CHANNELS = 8; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + static constexpr uint32_t OPERATORS = CHANNELS * 4; + static constexpr uint32_t WAVEFORMS = 8; + static constexpr uint32_t REGISTERS = 0x190; + static constexpr uint32_t DEFAULT_PRESCALE = 2; + static constexpr uint32_t EG_CLOCK_DIVIDER = 3; + static constexpr bool EG_HAS_REVERB = true; + static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS; + static constexpr uint32_t REG_MODE = 0x14; + static constexpr uint8_t STATUS_TIMERA = 0x01; + static constexpr uint8_t STATUS_TIMERB = 0x02; + static constexpr uint8_t STATUS_BUSY = 0x80; + static constexpr uint8_t STATUS_IRQ = 0; + + // constructor + opz_registers(); + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // map channel number to register offset + static constexpr uint32_t channel_offset(uint32_t chnum) + { + assert(chnum < CHANNELS); + return chnum; + } + + // map operator number to register offset + static constexpr uint32_t operator_offset(uint32_t opnum) + { + assert(opnum < OPERATORS); + return opnum; + } + + // return an array of operator indices for each channel + struct operator_mapping { uint32_t chan[CHANNELS]; }; + void operator_map(operator_mapping &dest) const; + + // handle writes to the register array + bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask); + + // clock the noise and LFO, if present, returning LFO PM value + int32_t clock_noise_and_lfo(); + + // return the AM offset from LFO for the given channel + uint32_t lfo_am_offset(uint32_t choffs) const; + + // return the current noise state, gated by the noise clock + uint32_t noise_state() const { return m_noise_state; } + + // caching helpers + void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache); + + // compute the phase step, given a PM value + uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm); + + // log a key-on event + std::string log_keyon(uint32_t choffs, uint32_t opoffs); + + // system-wide registers + uint32_t noise_frequency() const { return byte(0x0f, 0, 5); } + uint32_t noise_enable() const { return byte(0x0f, 7, 1); } + uint32_t timer_a_value() const { return word(0x10, 0, 8, 0x11, 0, 2); } + uint32_t timer_b_value() const { return byte(0x12, 0, 8); } + uint32_t csm() const { return byte(0x14, 7, 1); } + uint32_t reset_timer_b() const { return byte(0x14, 5, 1); } + uint32_t reset_timer_a() const { return byte(0x14, 4, 1); } + uint32_t enable_timer_b() const { return byte(0x14, 3, 1); } + uint32_t enable_timer_a() const { return byte(0x14, 2, 1); } + uint32_t load_timer_b() const { return byte(0x14, 1, 1); } + uint32_t load_timer_a() const { return byte(0x14, 0, 1); } + uint32_t lfo2_pm_depth() const { return byte(0x188, 0, 7); } // fake + uint32_t lfo2_rate() const { return byte(0x16, 0, 8); } + uint32_t lfo2_am_depth() const { return byte(0x17, 0, 7); } + uint32_t lfo_rate() const { return byte(0x18, 0, 8); } + uint32_t lfo_am_depth() const { return byte(0x19, 0, 7); } + uint32_t lfo_pm_depth() const { return byte(0x189, 0, 7); } // fake + uint32_t output_bits() const { return byte(0x1b, 6, 2); } + uint32_t lfo2_sync() const { return byte(0x1b, 5, 1); } + uint32_t lfo_sync() const { return byte(0x1b, 4, 1); } + uint32_t lfo2_waveform() const { return byte(0x1b, 2, 2); } + uint32_t lfo_waveform() const { return byte(0x1b, 0, 2); } + + // per-channel registers + uint32_t ch_volume(uint32_t choffs) const { return byte(0x00, 0, 8, choffs); } + uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs); } + uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } + uint32_t ch_output_2(uint32_t choffs) const { return 0; } + uint32_t ch_output_3(uint32_t choffs) const { return 0; } + uint32_t ch_key_on(uint32_t choffs) const { return byte(0x20, 6, 1, choffs); } + uint32_t ch_feedback(uint32_t choffs) const { return byte(0x20, 3, 3, choffs); } + uint32_t ch_algorithm(uint32_t choffs) const { return byte(0x20, 0, 3, choffs); } + uint32_t ch_block_freq(uint32_t choffs) const { return word(0x28, 0, 7, 0x30, 2, 6, choffs); } + uint32_t ch_lfo_pm_sens(uint32_t choffs) const { return byte(0x38, 4, 3, choffs); } + uint32_t ch_lfo_am_sens(uint32_t choffs) const { return byte(0x38, 0, 2, choffs); } + uint32_t ch_lfo2_pm_sens(uint32_t choffs) const { return byte(0x180, 4, 3, choffs); } // fake + uint32_t ch_lfo2_am_sens(uint32_t choffs) const { return byte(0x180, 0, 2, choffs); } // fake + + // per-operator registers + uint32_t op_detune(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_multiple(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_fix_range(uint32_t opoffs) const { return byte(0x40, 4, 3, opoffs); } + uint32_t op_fix_frequency(uint32_t opoffs) const { return byte(0x40, 0, 4, opoffs); } + uint32_t op_waveform(uint32_t opoffs) const { return byte(0x100, 4, 3, opoffs); } // fake + uint32_t op_fine(uint32_t opoffs) const { return byte(0x100, 0, 4, opoffs); } // fake + uint32_t op_total_level(uint32_t opoffs) const { return byte(0x60, 0, 7, opoffs); } + uint32_t op_ksr(uint32_t opoffs) const { return byte(0x80, 6, 2, opoffs); } + uint32_t op_fix_mode(uint32_t opoffs) const { return byte(0x80, 5, 1, opoffs); } + uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x80, 0, 5, opoffs); } + uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0xa0, 7, 1, opoffs); } + uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0xa0, 0, 5, opoffs); } + uint32_t op_detune2(uint32_t opoffs) const { return byte(0xc0, 6, 2, opoffs); } + uint32_t op_sustain_rate(uint32_t opoffs) const { return byte(0xc0, 0, 5, opoffs); } + uint32_t op_eg_shift(uint32_t opoffs) const { return byte(0x120, 6, 2, opoffs); } // fake + uint32_t op_reverb_rate(uint32_t opoffs) const { return byte(0x120, 0, 3, opoffs); } // fake + uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0xe0, 4, 4, opoffs); } + uint32_t op_release_rate(uint32_t opoffs) const { return byte(0xe0, 0, 4, opoffs); } + +protected: + // return a bitfield extracted from a byte + uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const + { + return bitfield(m_regdata[offset + extra_offset], start, count); + } + + // return a bitfield extracted from a pair of bytes, MSBs listed first + uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const + { + return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset); + } + + // internal state + uint32_t m_lfo_counter[2]; // LFO counter + uint32_t m_noise_lfsr; // noise LFSR state + uint8_t m_noise_counter; // noise counter + uint8_t m_noise_state; // latched noise state + uint8_t m_noise_lfo; // latched LFO noise value + uint8_t m_lfo_am[2]; // current LFO AM value + uint8_t m_regdata[REGISTERS]; // register data + uint16_t m_phase_substep[OPERATORS]; // phase substep for fixed frequency + int16_t m_lfo_waveform[4][LFO_WAVEFORM_LENGTH]; // LFO waveforms; AM in low 8, PM in upper 8 + uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms +}; + + + +//********************************************************* +// IMPLEMENTATION CLASSES +//********************************************************* + +// ======================> ym2414 + +class ym2414 +{ +public: + using fm_engine = fm_engine_base; + static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS; + using output_data = fm_engine::output_data; + + // constructor + ym2414(ymfm_interface &intf); + + // reset + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // pass-through helpers + uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); } + void invalidate_caches() { m_fm.invalidate_caches(); } + + // read access + uint8_t read_status(); + uint8_t read(uint32_t offset); + + // write access + void write_address(uint8_t data); + void write_data(uint8_t data); + void write(uint32_t offset, uint8_t data); + + // generate one sample of sound + void generate(output_data *output, uint32_t numsamples = 1); + +protected: + // internal state + uint8_t m_address; // address register + fm_engine m_fm; // core FM engine +}; + +} + + +#endif // YMFM_OPZ_H diff --git a/src/sound/ymfm/ymfm_pcm.cpp b/src/sound/ymfm/ymfm_pcm.cpp new file mode 100644 index 000000000..50595133b --- /dev/null +++ b/src/sound/ymfm/ymfm_pcm.cpp @@ -0,0 +1,715 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_pcm.h" +#include "ymfm_fm.h" +#include "ymfm_fm.ipp" + +namespace ymfm +{ + +//********************************************************* +// PCM REGISTERS +//********************************************************* + +//------------------------------------------------- +// reset - reset the register state +//------------------------------------------------- + +void pcm_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); + m_regdata[0x02] = 0x20; + m_regdata[0xf8] = 0x1b; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void pcm_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_regdata); +} + + +//------------------------------------------------- +// cache_channel_data - update the cache with +// data from the registers +//------------------------------------------------- + +void pcm_registers::cache_channel_data(uint32_t choffs, pcm_cache &cache) +{ + // compute step from octave and fnumber; the math here implies + // a .18 fraction but .16 should be perfectly fine + int32_t octave = int8_t(ch_octave(choffs) << 4) >> 4; + uint32_t fnum = ch_fnumber(choffs); + cache.step = ((0x400 | fnum) << (octave + 7)) >> 2; + + // total level is computed as a .10 value for interpolation + cache.total_level = ch_total_level(choffs) << 10; + + // compute panning values in terms of envelope attenuation + int32_t panpot = int8_t(ch_panpot(choffs) << 4) >> 4; + if (panpot >= 0) + { + cache.pan_left = (panpot == 7) ? 0x3ff : 0x20 * panpot; + cache.pan_right = 0; + } + else if (panpot >= -7) + { + cache.pan_left = 0; + cache.pan_right = (panpot == -7) ? 0x3ff : -0x20 * panpot; + } + else + cache.pan_left = cache.pan_right = 0x3ff; + + // determine the LFO stepping value; this how much to add to a running + // x.18 value for the LFO; steps were derived from frequencies in the + // manual and come out very close with these values + static const uint8_t s_lfo_steps[8] = { 1, 12, 19, 25, 31, 35, 37, 42 }; + cache.lfo_step = s_lfo_steps[ch_lfo_speed(choffs)]; + + // AM LFO depth values, derived from the manual; note each has at most + // 2 bits to make the "multiply" easy in hardware + static const uint8_t s_am_depth[8] = { 0, 0x14, 0x20, 0x28, 0x30, 0x40, 0x50, 0x80 }; + cache.am_depth = s_am_depth[ch_am_depth(choffs)]; + + // PM LFO depth values; these are converted from the manual's cents values + // into f-numbers; the computations come out quite cleanly so pretty sure + // these are correct + static const uint8_t s_pm_depth[8] = { 0, 2, 3, 4, 6, 12, 24, 48 }; + cache.pm_depth = s_pm_depth[ch_vibrato(choffs)]; + + // 4-bit sustain level, but 15 means 31 so effectively 5 bits + cache.eg_sustain = ch_sustain_level(choffs); + cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10; + cache.eg_sustain <<= 5; + + // compute the key scaling correction factor; 15 means don't do any correction + int32_t correction = ch_rate_correction(choffs); + if (correction == 15) + correction = 0; + else + correction = (octave + correction) * 2 + bitfield(fnum, 9); + + // compute the envelope generator rates + cache.eg_rate[EG_ATTACK] = effective_rate(ch_attack_rate(choffs), correction); + cache.eg_rate[EG_DECAY] = effective_rate(ch_decay_rate(choffs), correction); + cache.eg_rate[EG_SUSTAIN] = effective_rate(ch_sustain_rate(choffs), correction); + cache.eg_rate[EG_RELEASE] = effective_rate(ch_release_rate(choffs), correction); + cache.eg_rate[EG_REVERB] = 5; + + // if damping is on, override some things; essentially decay at a hardcoded + // rate of 48 until -12db (0x80), then at maximum rate for the rest + if (ch_damp(choffs) != 0) + { + cache.eg_rate[EG_DECAY] = 48; + cache.eg_rate[EG_SUSTAIN] = 63; + cache.eg_rate[EG_RELEASE] = 63; + cache.eg_sustain = 0x80; + } +} + + +//------------------------------------------------- +// effective_rate - return the effective rate, +// clamping and applying corrections as needed +//------------------------------------------------- + +uint32_t pcm_registers::effective_rate(uint32_t raw, uint32_t correction) +{ + // raw rates of 0 and 15 just pin to min/max + if (raw == 0) + return 0; + if (raw == 15) + return 63; + + // otherwise add the correction and clamp to range + return clamp(raw * 4 + correction, 0, 63); +} + + + +//********************************************************* +// PCM CHANNEL +//********************************************************* + +//------------------------------------------------- +// pcm_channel - constructor +//------------------------------------------------- + +pcm_channel::pcm_channel(pcm_engine &owner, uint32_t choffs) : + m_choffs(choffs), + m_baseaddr(0), + m_endpos(0), + m_looppos(0), + m_curpos(0), + m_nextpos(0), + m_lfo_counter(0), + m_eg_state(EG_RELEASE), + m_env_attenuation(0x3ff), + m_total_level(0x7f << 10), + m_format(0), + m_key_state(0), + m_regs(owner.regs()), + m_owner(owner) +{ +} + + +//------------------------------------------------- +// reset - reset the channel state +//------------------------------------------------- + +void pcm_channel::reset() +{ + m_baseaddr = 0; + m_endpos = 0; + m_looppos = 0; + m_curpos = 0; + m_nextpos = 0; + m_lfo_counter = 0; + m_eg_state = EG_RELEASE; + m_env_attenuation = 0x3ff; + m_total_level = 0x7f << 10; + m_format = 0; + m_key_state = 0; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void pcm_channel::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_baseaddr); + state.save_restore(m_endpos); + state.save_restore(m_looppos); + state.save_restore(m_curpos); + state.save_restore(m_nextpos); + state.save_restore(m_lfo_counter); + state.save_restore(m_eg_state); + state.save_restore(m_env_attenuation); + state.save_restore(m_total_level); + state.save_restore(m_format); + state.save_restore(m_key_state); +} + + +//------------------------------------------------- +// prepare - prepare for clocking +//------------------------------------------------- + +bool pcm_channel::prepare() +{ + // cache the data + m_regs.cache_channel_data(m_choffs, m_cache); + + // clock the key state + if ((m_key_state & KEY_PENDING) != 0) + { + uint8_t oldstate = m_key_state; + m_key_state = (m_key_state >> 1) & KEY_ON; + if (((oldstate ^ m_key_state) & KEY_ON) != 0) + { + if ((m_key_state & KEY_ON) != 0) + start_attack(); + else + start_release(); + } + } + + // set the total level directly if not interpolating + if (m_regs.ch_level_direct(m_choffs)) + m_total_level = m_cache.total_level; + + // we're active until we're quiet after the release + return (m_eg_state < EG_RELEASE || m_env_attenuation < EG_QUIET); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void pcm_channel::clock(uint32_t env_counter) +{ + // clock the LFO, which is an x.18 value incremented based on the + // LFO speed value + m_lfo_counter += m_cache.lfo_step; + + // clock the envelope + clock_envelope(env_counter); + + // determine the step after applying vibrato + uint32_t step = m_cache.step; + if (m_cache.pm_depth != 0) + { + // shift the LFO by 1/4 cycle for PM so that it starts at 0 + uint32_t lfo_shifted = m_lfo_counter + (1 << 16); + int32_t lfo_value = bitfield(lfo_shifted, 10, 7); + if (bitfield(lfo_shifted, 17) != 0) + lfo_value ^= 0x7f; + lfo_value -= 0x40; + step += (lfo_value * int32_t(m_cache.pm_depth)) >> 7; + } + + // advance the sample step and loop as needed + m_curpos = m_nextpos; + m_nextpos = m_curpos + step; + if (m_nextpos >= m_endpos) + m_nextpos += m_looppos - m_endpos; + + // interpolate total level if needed + if (m_total_level != m_cache.total_level) + { + // max->min volume takes 156.4ms, or pretty close to 19/1024 per 44.1kHz sample + // min->max volume is half that, so advance by 38/1024 per sample + if (m_total_level < m_cache.total_level) + m_total_level = std::min(m_total_level + 19, m_cache.total_level); + else + m_total_level = std::max(m_total_level - 38, m_cache.total_level); + } +} + + +//------------------------------------------------- +// output - return the computed output value, with +// panning applied +//------------------------------------------------- + +void pcm_channel::output(output_data &output) const +{ + // early out if the envelope is effectively off + uint32_t envelope = m_env_attenuation; + if (envelope > EG_QUIET) + return; + + // add in LFO AM modulation + if (m_cache.am_depth != 0) + { + uint32_t lfo_value = bitfield(m_lfo_counter, 10, 7); + if (bitfield(m_lfo_counter, 17) != 0) + lfo_value ^= 0x7f; + envelope += (lfo_value * m_cache.am_depth) >> 7; + } + + // add in the current interpolated total level value, which is a .10 + // value shifted left by 2 + envelope += m_total_level >> 8; + + // add in panning effect and clamp + uint32_t lenv = std::min(envelope + m_cache.pan_left, 0x3ff); + uint32_t renv = std::min(envelope + m_cache.pan_right, 0x3ff); + + // convert to volume as a .11 fraction + int32_t lvol = attenuation_to_volume(lenv << 2); + int32_t rvol = attenuation_to_volume(renv << 2); + + // fetch current sample and add + int16_t sample = fetch_sample(); + uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2; + output.data[outnum + 0] += (lvol * sample) >> 15; + output.data[outnum + 1] += (rvol * sample) >> 15; +} + + +//------------------------------------------------- +// keyonoff - signal key on/off +//------------------------------------------------- + +void pcm_channel::keyonoff(bool on) +{ + // mark the key state as pending + m_key_state |= KEY_PENDING | (on ? KEY_PENDING_ON : 0); + + // don't log masked channels + if ((m_key_state & (KEY_PENDING_ON | KEY_ON)) == KEY_PENDING_ON && ((debug::GLOBAL_PCM_CHANNEL_MASK >> m_choffs) & 1) != 0) + { + debug::log_keyon("KeyOn PCM-%02d: num=%3d oct=%2d fnum=%03X level=%02X%c ADSR=%X/%X/%X/%X SL=%X", + m_choffs, + m_regs.ch_wave_table_num(m_choffs), + int8_t(m_regs.ch_octave(m_choffs) << 4) >> 4, + m_regs.ch_fnumber(m_choffs), + m_regs.ch_total_level(m_choffs), + m_regs.ch_level_direct(m_choffs) ? '!' : '/', + m_regs.ch_attack_rate(m_choffs), + m_regs.ch_decay_rate(m_choffs), + m_regs.ch_sustain_rate(m_choffs), + m_regs.ch_release_rate(m_choffs), + m_regs.ch_sustain_level(m_choffs)); + + if (m_regs.ch_rate_correction(m_choffs) != 15) + debug::log_keyon(" RC=%X", m_regs.ch_rate_correction(m_choffs)); + + if (m_regs.ch_pseudo_reverb(m_choffs) != 0) + debug::log_keyon(" %s", "REV"); + if (m_regs.ch_damp(m_choffs) != 0) + debug::log_keyon(" %s", "DAMP"); + + if (m_regs.ch_vibrato(m_choffs) != 0 || m_regs.ch_am_depth(m_choffs) != 0) + { + if (m_regs.ch_vibrato(m_choffs) != 0) + debug::log_keyon(" VIB=%d", m_regs.ch_vibrato(m_choffs)); + if (m_regs.ch_am_depth(m_choffs) != 0) + debug::log_keyon(" AM=%d", m_regs.ch_am_depth(m_choffs)); + debug::log_keyon(" LFO=%d", m_regs.ch_lfo_speed(m_choffs)); + } + debug::log_keyon("%s", "\n"); + } +} + + +//------------------------------------------------- +// load_wavetable - load a wavetable by fetching +// its data from external memory +//------------------------------------------------- + +void pcm_channel::load_wavetable() +{ + // determine the address of the wave table header + uint32_t wavnum = m_regs.ch_wave_table_num(m_choffs); + uint32_t wavheader = 12 * wavnum; + + // above 384 it may be in a different bank + if (wavnum >= 384) + { + uint32_t bank = m_regs.wave_table_header(); + if (bank != 0) + wavheader = 512*1024 * bank + (wavnum - 384) * 12; + } + + // fetch the 22-bit base address and 2-bit format + uint8_t byte = read_pcm(wavheader + 0); + m_format = bitfield(byte, 6, 2); + m_baseaddr = bitfield(byte, 0, 6) << 16; + m_baseaddr |= read_pcm(wavheader + 1) << 8; + m_baseaddr |= read_pcm(wavheader + 2) << 0; + + // fetch the 16-bit loop position + m_looppos = read_pcm(wavheader + 3) << 8; + m_looppos |= read_pcm(wavheader + 4); + m_looppos <<= 16; + + // fetch the 16-bit end position, which is stored as a negative value + // for some reason that is unclear + m_endpos = read_pcm(wavheader + 5) << 8; + m_endpos |= read_pcm(wavheader + 6); + m_endpos = -int32_t(m_endpos) << 16; + + // remaining data values set registers + m_owner.write(0x80 + m_choffs, read_pcm(wavheader + 7)); + m_owner.write(0x98 + m_choffs, read_pcm(wavheader + 8)); + m_owner.write(0xb0 + m_choffs, read_pcm(wavheader + 9)); + m_owner.write(0xc8 + m_choffs, read_pcm(wavheader + 10)); + m_owner.write(0xe0 + m_choffs, read_pcm(wavheader + 11)); + + // reset the envelope so we don't continue playing mid-sample from previous key ons + m_env_attenuation = 0x3ff; +} + + +//------------------------------------------------- +// read_pcm - read a byte from the external PCM +// memory interface +//------------------------------------------------- + +uint8_t pcm_channel::read_pcm(uint32_t address) const +{ + return m_owner.intf().ymfm_external_read(ACCESS_PCM, address); +} + + +//------------------------------------------------- +// start_attack - start the attack phase +//------------------------------------------------- + +void pcm_channel::start_attack() +{ + // don't change anything if already in attack state + if (m_eg_state == EG_ATTACK) + return; + m_eg_state = EG_ATTACK; + + // reset the LFO if requested + if (m_regs.ch_lfo_reset(m_choffs)) + m_lfo_counter = 0; + + // if the attack rate == 63 then immediately go to max attenuation + if (m_cache.eg_rate[EG_ATTACK] == 63) + m_env_attenuation = 0; + + // reset the positions + m_curpos = m_nextpos = 0; +} + + +//------------------------------------------------- +// start_release - start the release phase +//------------------------------------------------- + +void pcm_channel::start_release() +{ + // don't change anything if already in release or reverb state + if (m_eg_state >= EG_RELEASE) + return; + m_eg_state = EG_RELEASE; +} + + +//------------------------------------------------- +// clock_envelope - clock the envelope generator +//------------------------------------------------- + +void pcm_channel::clock_envelope(uint32_t env_counter) +{ + // handle attack->decay transitions + if (m_eg_state == EG_ATTACK && m_env_attenuation == 0) + m_eg_state = EG_DECAY; + + // handle decay->sustain transitions + if (m_eg_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain) + m_eg_state = EG_SUSTAIN; + + // fetch the appropriate 6-bit rate value from the cache + uint32_t rate = m_cache.eg_rate[m_eg_state]; + + // compute the rate shift value; this is the shift needed to + // apply to the env_counter such that it becomes a 5.11 fixed + // point number + uint32_t rate_shift = rate >> 2; + env_counter <<= rate_shift; + + // see if the fractional part is 0; if not, it's not time to clock + if (bitfield(env_counter, 0, 11) != 0) + return; + + // determine the increment based on the non-fractional part of env_counter + uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3); + uint32_t increment = attenuation_increment(rate, relevant_bits); + + // attack is the only one that increases + if (m_eg_state == EG_ATTACK) + m_env_attenuation += (~m_env_attenuation * increment) >> 4; + + // all other cases are similar + else + { + // apply the increment + m_env_attenuation += increment; + + // clamp the final attenuation + if (m_env_attenuation >= 0x400) + m_env_attenuation = 0x3ff; + + // transition to reverb at -18dB if enabled + if (m_env_attenuation >= 0xc0 && m_eg_state < EG_REVERB && m_regs.ch_pseudo_reverb(m_choffs)) + m_eg_state = EG_REVERB; + } +} + + +//------------------------------------------------- +// fetch_sample - fetch a sample at the current +// position +//------------------------------------------------- + +int16_t pcm_channel::fetch_sample() const +{ + uint32_t addr = m_baseaddr; + uint32_t pos = m_curpos >> 16; + + // 8-bit PCM: shift up by 8 + if (m_format == 0) + return read_pcm(addr + pos) << 8; + + // 16-bit PCM: assemble from 2 halves + if (m_format == 2) + { + addr += pos * 2; + return (read_pcm(addr) << 8) | read_pcm(addr + 1); + } + + // 12-bit PCM: assemble out of half of 3 bytes + addr += (pos / 2) * 3; + if ((pos & 1) == 0) + return (read_pcm(addr + 0) << 8) | ((read_pcm(addr + 1) << 4) & 0xf0); + else + return (read_pcm(addr + 2) << 8) | ((read_pcm(addr + 1) << 0) & 0xf0); +} + + + +//********************************************************* +// PCM ENGINE +//********************************************************* + +//------------------------------------------------- +// pcm_engine - constructor +//------------------------------------------------- + +pcm_engine::pcm_engine(ymfm_interface &intf) : + m_intf(intf), + m_env_counter(0), + m_modified_channels(ALL_CHANNELS), + m_active_channels(ALL_CHANNELS) +{ + // create the channels + for (int chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum] = std::make_unique(*this, chnum); +} + + +//------------------------------------------------- +// reset - reset the engine state +//------------------------------------------------- + +void pcm_engine::reset() +{ + // reset register state + m_regs.reset(); + + // reset each channel + for (auto &chan : m_channel) + chan->reset(); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void pcm_engine::save_restore(ymfm_saved_state &state) +{ + // save our data + state.save_restore(m_env_counter); + + // save channel state + for (int chnum = 0; chnum < CHANNELS; chnum++) + m_channel[chnum]->save_restore(state); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void pcm_engine::clock(uint32_t chanmask) +{ + // if something was modified, prepare + // also prepare every 4k samples to catch ending notes + if (m_modified_channels != 0 || m_prepare_count++ >= 4096) + { + // call each channel to prepare + m_active_channels = 0; + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + if (m_channel[chnum]->prepare()) + m_active_channels |= 1 << chnum; + + // reset the modified channels and prepare count + m_modified_channels = m_prepare_count = 0; + } + + // increment the envelope counter; the envelope generator + // only clocks every other sample in order to make the PCM + // envelopes line up with the FM envelopes (after taking into + // account the different FM sampling rate) + m_env_counter++; + + // now update the state of all the channels and operators + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + m_channel[chnum]->clock(m_env_counter >> 1); +} + + +//------------------------------------------------- +// update - master update function +//------------------------------------------------- + +void pcm_engine::output(output_data &output, uint32_t chanmask) +{ + // mask out some channels for debug purposes + chanmask &= debug::GLOBAL_PCM_CHANNEL_MASK; + + // compute the output of each channel + for (int chnum = 0; chnum < CHANNELS; chnum++) + if (bitfield(chanmask, chnum)) + m_channel[chnum]->output(output); +} + + +//------------------------------------------------- +// read - handle reads from the PCM registers +//------------------------------------------------- + +uint8_t pcm_engine::read(uint32_t regnum) +{ + // handle reads from the data register + if (regnum == 0x06 && m_regs.memory_access_mode() != 0) + return m_intf.ymfm_external_read(ACCESS_PCM, m_regs.memory_address_autoinc()); + + return m_regs.read(regnum); +} + + +//------------------------------------------------- +// write - handle writes to the PCM registers +//------------------------------------------------- + +void pcm_engine::write(uint32_t regnum, uint8_t data) +{ + // handle reads to the data register + if (regnum == 0x06 && m_regs.memory_access_mode() != 0) + { + m_intf.ymfm_external_write(ACCESS_PCM, m_regs.memory_address_autoinc(), data); + return; + } + + // for now just mark all channels as modified + m_modified_channels = ALL_CHANNELS; + + // most writes are passive, consumed only when needed + m_regs.write(regnum, data); + + // however, process keyons immediately + if (regnum >= 0x68 && regnum <= 0x7f) + m_channel[regnum - 0x68]->keyonoff(bitfield(data, 7)); + + // and also wavetable writes + else if (regnum >= 0x08 && regnum <= 0x1f) + m_channel[regnum - 0x08]->load_wavetable(); +} + +} diff --git a/src/sound/ymfm/ymfm_pcm.h b/src/sound/ymfm/ymfm_pcm.h new file mode 100644 index 000000000..2022a69b9 --- /dev/null +++ b/src/sound/ymfm/ymfm_pcm.h @@ -0,0 +1,347 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_PCM_H +#define YMFM_PCM_H + +#pragma once + +#include "ymfm.h" + +namespace ymfm +{ + +/* +Note to self: Sega "Multi-PCM" is almost identical to this + +28 channels + +Writes: +00 = data reg, causes write +01 = target slot = data - (data / 8) +02 = address (clamped to 7) + +Slot data (registers with ADSR/KSR seem to be inaccessible): +0: xxxx---- panpot +1: xxxxxxxx wavetable low +2: xxxxxx-- pitch low + -------x wavetable high +3: xxxx---- octave + ----xxxx pitch hi +4: x------- key on +5: xxxxxxx- total level + -------x level direct (0=interpolate) +6: --xxx--- LFO frequency + -----xxx PM sensitivity +7: -----xxx AM sensitivity + +Sample data: ++00: start hi ++01: start mid ++02: start low ++03: loop hi ++04: loop low ++05: -end hi ++06: -end low ++07: vibrato (reg 6) ++08: attack/decay ++09: sustain level/rate ++0A: ksr/release ++0B: LFO amplitude (reg 7) + +*/ + +//********************************************************* +// INTERFACE CLASSES +//********************************************************* + +class pcm_engine; + + +// ======================> pcm_cache + +// this class holds data that is computed once at the start of clocking +// and remains static during subsequent sound generation +struct pcm_cache +{ + uint32_t step; // sample position step, as a .16 value + uint32_t total_level; // target total level, as a .10 value + uint32_t pan_left; // left panning attenuation + uint32_t pan_right; // right panning attenuation + uint32_t eg_sustain; // sustain level, shifted up to envelope values + uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR + uint8_t lfo_step; // stepping value for LFO + uint8_t am_depth; // scale value for AM LFO + uint8_t pm_depth; // scale value for PM LFO +}; + + +// ======================> pcm_registers + +// +// PCM register map: +// +// System-wide registers: +// 00-01 xxxxxxxx LSI Test +// 02 -------x Memory access mode (0=sound gen, 1=read/write) +// ------x- Memory type (0=ROM, 1=ROM+SRAM) +// ---xxx-- Wave table header +// xxx----- Device ID (=1 for YMF278B) +// 03 --xxxxxx Memory address high +// 04 xxxxxxxx Memory address mid +// 05 xxxxxxxx Memory address low +// 06 xxxxxxxx Memory data +// F8 --xxx--- Mix control (FM_R) +// -----xxx Mix control (FM_L) +// F9 --xxx--- Mix control (PCM_R) +// -----xxx Mix control (PCM_L) +// +// Channel-specific registers: +// 08-1F xxxxxxxx Wave table number low +// 20-37 -------x Wave table number high +// xxxxxxx- F-number low +// 38-4F -----xxx F-number high +// ----x--- Pseudo-reverb +// xxxx---- Octave +// 50-67 xxxxxxx- Total level +// -------x Level direct +// 68-7F x------- Key on +// -x------ Damp +// --x----- LFO reset +// ---x---- Output channel +// ----xxxx Panpot +// 80-97 --xxx--- LFO speed +// -----xxx Vibrato +// 98-AF xxxx---- Attack rate +// ----xxxx Decay rate +// B0-C7 xxxx---- Sustain level +// ----xxxx Sustain rate +// C8-DF xxxx---- Rate correction +// ----xxxx Release rate +// E0-F7 -----xxx AM depth + +class pcm_registers +{ +public: + // constants + static constexpr uint32_t OUTPUTS = 4; + static constexpr uint32_t CHANNELS = 24; + static constexpr uint32_t REGISTERS = 0x100; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + + // constructor + pcm_registers() { } + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset to initial state + void reset(); + + // update cache information + void cache_channel_data(uint32_t choffs, pcm_cache &cache); + + // direct read/write access + uint8_t read(uint32_t index ) { return m_regdata[index]; } + void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } + + // system-wide registers + uint32_t memory_access_mode() const { return bitfield(m_regdata[0x02], 0); } + uint32_t memory_type() const { return bitfield(m_regdata[0x02], 1); } + uint32_t wave_table_header() const { return bitfield(m_regdata[0x02], 2, 3); } + uint32_t device_id() const { return bitfield(m_regdata[0x02], 5, 3); } + uint32_t memory_address() const { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; } + uint32_t memory_data() const { return m_regdata[0x06]; } + uint32_t mix_fm_r() const { return bitfield(m_regdata[0xf8], 3, 3); } + uint32_t mix_fm_l() const { return bitfield(m_regdata[0xf8], 0, 3); } + uint32_t mix_pcm_r() const { return bitfield(m_regdata[0xf9], 3, 3); } + uint32_t mix_pcm_l() const { return bitfield(m_regdata[0xf9], 0, 3); } + + // per-channel registers + uint32_t ch_wave_table_num(uint32_t choffs) const { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); } + uint32_t ch_fnumber(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); } + uint32_t ch_pseudo_reverb(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 3); } + uint32_t ch_octave(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 4, 4); } + uint32_t ch_total_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 1, 7); } + uint32_t ch_level_direct(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 0); } + uint32_t ch_keyon(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 7); } + uint32_t ch_damp(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 6); } + uint32_t ch_lfo_reset(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 5); } + uint32_t ch_output_channel(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 4); } + uint32_t ch_panpot(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 0, 4); } + uint32_t ch_lfo_speed(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 3, 3); } + uint32_t ch_vibrato(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 0, 3); } + uint32_t ch_attack_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 4, 4); } + uint32_t ch_decay_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 0, 4); } + uint32_t ch_sustain_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 4, 4); } + uint32_t ch_sustain_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 0, 4); } + uint32_t ch_rate_correction(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 4, 4); } + uint32_t ch_release_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 0, 4); } + uint32_t ch_am_depth(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xe0], 0, 3); } + + // return the memory address and increment it + uint32_t memory_address_autoinc() + { + uint32_t result = memory_address(); + uint32_t newval = result + 1; + m_regdata[0x05] = newval >> 0; + m_regdata[0x06] = newval >> 8; + m_regdata[0x07] = (newval >> 16) & 0x3f; + return result; + } + +private: + // internal helpers + uint32_t effective_rate(uint32_t raw, uint32_t correction); + + // internal state + uint8_t m_regdata[REGISTERS]; // register data +}; + + +// ======================> pcm_channel + +class pcm_channel +{ + static constexpr uint8_t KEY_ON = 0x01; + static constexpr uint8_t KEY_PENDING_ON = 0x02; + static constexpr uint8_t KEY_PENDING = 0x04; + + // "quiet" value, used to optimize when we can skip doing working + static constexpr uint32_t EG_QUIET = 0x200; + +public: + using output_data = ymfm_output; + + // constructor + pcm_channel(pcm_engine &owner, uint32_t choffs); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // reset the channel state + void reset(); + + // return the channel offset + uint32_t choffs() const { return m_choffs; } + + // prepare prior to clocking + bool prepare(); + + // master clocking function + void clock(uint32_t env_counter); + + // return the computed output value, with panning applied + void output(output_data &output) const; + + // signal key on/off + void keyonoff(bool on); + + // load a new wavetable entry + void load_wavetable(); + +private: + // internal helpers + void start_attack(); + void start_release(); + void clock_envelope(uint32_t env_counter); + int16_t fetch_sample() const; + uint8_t read_pcm(uint32_t address) const; + + // internal state + uint32_t const m_choffs; // channel offset + uint32_t m_baseaddr; // base address + uint32_t m_endpos; // ending position + uint32_t m_looppos; // loop position + uint32_t m_curpos; // current position + uint32_t m_nextpos; // next position + uint32_t m_lfo_counter; // LFO counter + envelope_state m_eg_state; // envelope state + uint16_t m_env_attenuation; // computed envelope attenuation + uint32_t m_total_level; // total level with as 7.10 for interp + uint8_t m_format; // sample format + uint8_t m_key_state; // current key state + pcm_cache m_cache; // cached data + pcm_registers &m_regs; // reference to registers + pcm_engine &m_owner; // reference to our owner +}; + + +// ======================> pcm_engine + +class pcm_engine +{ +public: + static constexpr int OUTPUTS = pcm_registers::OUTPUTS; + static constexpr int CHANNELS = pcm_registers::CHANNELS; + static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS; + using output_data = pcm_channel::output_data; + + // constructor + pcm_engine(ymfm_interface &intf); + + // reset our status + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // master clocking function + void clock(uint32_t chanmask); + + // compute sum of channel outputs + void output(output_data &output, uint32_t chanmask); + + // read from the PCM registers + uint8_t read(uint32_t regnum); + + // write to the PCM registers + void write(uint32_t regnum, uint8_t data); + + // return a reference to our interface + ymfm_interface &intf() { return m_intf; } + + // return a reference to our registers + pcm_registers ®s() { return m_regs; } + +private: + // internal state + ymfm_interface &m_intf; // reference to the interface + uint32_t m_env_counter; // envelope counter + uint32_t m_modified_channels; // bitmask of modified channels + uint32_t m_active_channels; // bitmask of active channels + uint32_t m_prepare_count; // counter to do periodic prepare sweeps + std::unique_ptr m_channel[CHANNELS]; // array of channels + pcm_registers m_regs; // registers +}; + +} + +#endif // YMFM_PCM_H diff --git a/src/sound/ymfm/ymfm_ssg.cpp b/src/sound/ymfm/ymfm_ssg.cpp new file mode 100644 index 000000000..1c477d0de --- /dev/null +++ b/src/sound/ymfm/ymfm_ssg.cpp @@ -0,0 +1,279 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "ymfm_ssg.h" + +namespace ymfm +{ + +//********************************************************* +// SSG REGISTERS +//********************************************************* + +//------------------------------------------------- +// reset - reset the register state +//------------------------------------------------- + +void ssg_registers::reset() +{ + std::fill_n(&m_regdata[0], REGISTERS, 0); +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ssg_registers::save_restore(ymfm_saved_state &state) +{ + state.save_restore(m_regdata); +} + + + +//********************************************************* +// SSG ENGINE +//********************************************************* + +//------------------------------------------------- +// ssg_engine - constructor +//------------------------------------------------- + +ssg_engine::ssg_engine(ymfm_interface &intf) : + m_intf(intf), + m_tone_count{ 0,0,0 }, + m_tone_state{ 0,0,0 }, + m_envelope_count(0), + m_envelope_state(0), + m_noise_count(0), + m_noise_state(1), + m_override(nullptr) +{ +} + + +//------------------------------------------------- +// reset - reset the engine state +//------------------------------------------------- + +void ssg_engine::reset() +{ + // defer to the override if present + if (m_override != nullptr) + return m_override->ssg_reset(); + + // reset register state + m_regs.reset(); + + // reset engine state + for (int chan = 0; chan < 3; chan++) + { + m_tone_count[chan] = 0; + m_tone_state[chan] = 0; + } + m_envelope_count = 0; + m_envelope_state = 0; + m_noise_count = 0; + m_noise_state = 1; +} + + +//------------------------------------------------- +// save_restore - save or restore the data +//------------------------------------------------- + +void ssg_engine::save_restore(ymfm_saved_state &state) +{ + // save register state + m_regs.save_restore(state); + + // save engine state + state.save_restore(m_tone_count); + state.save_restore(m_tone_state); + state.save_restore(m_envelope_count); + state.save_restore(m_envelope_state); + state.save_restore(m_noise_count); + state.save_restore(m_noise_state); +} + + +//------------------------------------------------- +// clock - master clocking function +//------------------------------------------------- + +void ssg_engine::clock() +{ + // clock tones; tone period units are clock/16 but since we run at clock/8 + // that works out for us to toggle the state (50% duty cycle) at twice the + // programmed period + for (int chan = 0; chan < 3; chan++) + { + m_tone_count[chan]++; + if (m_tone_count[chan] >= m_regs.ch_tone_period(chan)) + { + m_tone_state[chan] ^= 1; + m_tone_count[chan] = 0; + } + } + + // clock noise; noise period units are clock/16 but since we run at clock/8, + // our counter needs a right shift prior to compare; note that a period of 0 + // should produce an indentical result to a period of 1, so add a special + // check against that case + m_noise_count++; + if ((m_noise_count >> 1) >= m_regs.noise_period() && m_noise_count != 1) + { + m_noise_state ^= (bitfield(m_noise_state, 0) ^ bitfield(m_noise_state, 3)) << 17; + m_noise_state >>= 1; + m_noise_count = 0; + } + + // clock envelope; envelope period units are clock/8 (manual says clock/256 + // but that's for all 32 steps) + m_envelope_count++; + if (m_envelope_count >= m_regs.envelope_period()) + { + m_envelope_state++; + m_envelope_count = 0; + } +} + + +//------------------------------------------------- +// output - output the current state +//------------------------------------------------- + +void ssg_engine::output(output_data &output) +{ + // volume to amplitude table, taken from MAME's implementation but biased + // so that 0 == 0 + static int16_t const s_amplitudes[32] = + { + 0, 32, 78, 141, 178, 222, 262, 306, + 369, 441, 509, 585, 701, 836, 965, 1112, + 1334, 1595, 1853, 2146, 2576, 3081, 3576, 4135, + 5000, 6006, 7023, 8155, 9963,11976,14132,16382 + }; + + // compute the envelope volume + uint32_t envelope_volume; + if ((m_regs.envelope_hold() | (m_regs.envelope_continue() ^ 1)) && m_envelope_state >= 32) + { + m_envelope_state = 32; + envelope_volume = ((m_regs.envelope_attack() ^ m_regs.envelope_alternate()) & m_regs.envelope_continue()) ? 31 : 0; + } + else + { + uint32_t attack = m_regs.envelope_attack(); + if (m_regs.envelope_alternate()) + attack ^= bitfield(m_envelope_state, 5); + envelope_volume = (m_envelope_state & 31) ^ (attack ? 0 : 31); + } + + // iterate over channels + for (int chan = 0; chan < 3; chan++) + { + // noise depends on the noise state, which is the LSB of m_noise_state + uint32_t noise_on = m_regs.ch_noise_enable_n(chan) | m_noise_state; + + // tone depends on the current tone state + uint32_t tone_on = m_regs.ch_tone_enable_n(chan) | m_tone_state[chan]; + + // if neither tone nor noise enabled, return 0 + uint32_t volume; + if ((noise_on & tone_on) == 0) + volume = 0; + + // if the envelope is enabled, use its amplitude + else if (m_regs.ch_envelope_enable(chan)) + volume = envelope_volume; + + // otherwise, scale the tone amplitude up to match envelope values + // according to the datasheet, amplitude 15 maps to envelope 31 + else + { + volume = m_regs.ch_amplitude(chan) * 2; + if (volume != 0) + volume |= 1; + } + + // convert to amplitude + output.data[chan] = s_amplitudes[volume]; + } +} + + +//------------------------------------------------- +// read - handle reads from the SSG registers +//------------------------------------------------- + +uint8_t ssg_engine::read(uint32_t regnum) +{ + // defer to the override if present + if (m_override != nullptr) + return m_override->ssg_read(regnum); + + // read from the I/O ports call the handlers if they are configured for input + if (regnum == 0x0e && !m_regs.io_a_out()) + return m_intf.ymfm_external_read(ACCESS_IO, 0); + else if (regnum == 0x0f && !m_regs.io_b_out()) + return m_intf.ymfm_external_read(ACCESS_IO, 1); + + // otherwise just return the register value + return m_regs.read(regnum); +} + + +//------------------------------------------------- +// write - handle writes to the SSG registers +//------------------------------------------------- + +void ssg_engine::write(uint32_t regnum, uint8_t data) +{ + // defer to the override if present + if (m_override != nullptr) + return m_override->ssg_write(regnum, data); + + // store the raw value to the register array; + // most writes are passive, consumed only when needed + m_regs.write(regnum, data); + + // writes to the envelope shape register reset the state + if (regnum == 0x0d) + m_envelope_state = 0; + + // writes to the I/O ports call the handlers if they are configured for output + else if (regnum == 0x0e && m_regs.io_a_out()) + m_intf.ymfm_external_write(ACCESS_IO, 0, data); + else if (regnum == 0x0f && m_regs.io_b_out()) + m_intf.ymfm_external_write(ACCESS_IO, 1, data); +} + +} diff --git a/src/sound/ymfm/ymfm_ssg.h b/src/sound/ymfm/ymfm_ssg.h new file mode 100644 index 000000000..749ad146f --- /dev/null +++ b/src/sound/ymfm/ymfm_ssg.h @@ -0,0 +1,205 @@ +// BSD 3-Clause License +// +// Copyright (c) 2021, Aaron Giles +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef YMFM_SSG_H +#define YMFM_SSG_H + +#pragma once + +#include "ymfm.h" + +namespace ymfm +{ + +//********************************************************* +// OVERRIDE INTERFACE +//********************************************************* + +// ======================> ssg_override + +// this class describes a simple interface to allow the internal SSG to be +// overridden with another implementation +class ssg_override +{ +public: + // reset our status + virtual void ssg_reset() = 0; + + // read/write to the SSG registers + virtual uint8_t ssg_read(uint32_t regnum) = 0; + virtual void ssg_write(uint32_t regnum, uint8_t data) = 0; + + // notification when the prescale has changed + virtual void ssg_prescale_changed() = 0; +}; + + +//********************************************************* +// REGISTER CLASS +//********************************************************* + +// ======================> ssg_registers + +// +// SSG register map: +// +// System-wide registers: +// 06 ---xxxxx Noise period +// 07 x------- I/O B in(0) or out(1) +// -x------ I/O A in(0) or out(1) +// --x----- Noise enable(0) or disable(1) for channel C +// ---x---- Noise enable(0) or disable(1) for channel B +// ----x--- Noise enable(0) or disable(1) for channel A +// -----x-- Tone enable(0) or disable(1) for channel C +// ------x- Tone enable(0) or disable(1) for channel B +// -------x Tone enable(0) or disable(1) for channel A +// 0B xxxxxxxx Envelope period fine +// 0C xxxxxxxx Envelope period coarse +// 0D ----x--- Envelope shape: continue +// -----x-- Envelope shape: attack/decay +// ------x- Envelope shape: alternate +// -------x Envelope shape: hold +// 0E xxxxxxxx 8-bit parallel I/O port A +// 0F xxxxxxxx 8-bit parallel I/O port B +// +// Per-channel registers: +// 00,02,04 xxxxxxxx Tone period (fine) for channel A,B,C +// 01,03,05 ----xxxx Tone period (coarse) for channel A,B,C +// 08,09,0A ---x---- Mode: fixed(0) or variable(1) for channel A,B,C +// ----xxxx Amplitude for channel A,B,C +// +class ssg_registers +{ +public: + // constants + static constexpr uint32_t OUTPUTS = 3; + static constexpr uint32_t CHANNELS = 3; + static constexpr uint32_t REGISTERS = 0x10; + static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1; + + // constructor + ssg_registers() { } + + // reset to initial state + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // direct read/write access + uint8_t read(uint32_t index) { return m_regdata[index]; } + void write(uint32_t index, uint8_t data) { m_regdata[index] = data; } + + // system-wide registers + uint32_t noise_period() const { return bitfield(m_regdata[0x06], 0, 5); } + uint32_t io_b_out() const { return bitfield(m_regdata[0x07], 7); } + uint32_t io_a_out() const { return bitfield(m_regdata[0x07], 6); } + uint32_t envelope_period() const { return m_regdata[0x0b] | (m_regdata[0x0c] << 8); } + uint32_t envelope_continue() const { return bitfield(m_regdata[0x0d], 3); } + uint32_t envelope_attack() const { return bitfield(m_regdata[0x0d], 2); } + uint32_t envelope_alternate() const { return bitfield(m_regdata[0x0d], 1); } + uint32_t envelope_hold() const { return bitfield(m_regdata[0x0d], 0); } + uint32_t io_a_data() const { return m_regdata[0x0e]; } + uint32_t io_b_data() const { return m_regdata[0x0f]; } + + // per-channel registers + uint32_t ch_noise_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 3 + choffs); } + uint32_t ch_tone_enable_n(uint32_t choffs) const { return bitfield(m_regdata[0x07], 0 + choffs); } + uint32_t ch_tone_period(uint32_t choffs) const { return m_regdata[0x00 + 2 * choffs] | (bitfield(m_regdata[0x01 + 2 * choffs], 0, 4) << 8); } + uint32_t ch_envelope_enable(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 4); } + uint32_t ch_amplitude(uint32_t choffs) const { return bitfield(m_regdata[0x08 + choffs], 0, 4); } + +private: + // internal state + uint8_t m_regdata[REGISTERS]; // register data +}; + + +// ======================> ssg_engine + +class ssg_engine +{ +public: + static constexpr int OUTPUTS = ssg_registers::OUTPUTS; + static constexpr int CHANNELS = ssg_registers::CHANNELS; + static constexpr int CLOCK_DIVIDER = 8; + + using output_data = ymfm_output; + + // constructor + ssg_engine(ymfm_interface &intf); + + // configure an override + void override(ssg_override &override) { m_override = &override; } + + // reset our status + void reset(); + + // save/restore + void save_restore(ymfm_saved_state &state); + + // master clocking function + void clock(); + + // compute sum of channel outputs + void output(output_data &output); + + // read/write to the SSG registers + uint8_t read(uint32_t regnum); + void write(uint32_t regnum, uint8_t data); + + // return a reference to our interface + ymfm_interface &intf() { return m_intf; } + + // return a reference to our registers + ssg_registers ®s() { return m_regs; } + + // true if we are overridden + bool overridden() const { return (m_override != nullptr); } + + // indicate the prescale has changed + void prescale_changed() { if (m_override != nullptr) m_override->ssg_prescale_changed(); } + +private: + // internal state + ymfm_interface &m_intf; // reference to the interface + uint32_t m_tone_count[3]; // current tone counter + uint32_t m_tone_state[3]; // current tone state + uint32_t m_envelope_count; // envelope counter + uint32_t m_envelope_state; // envelope state + uint32_t m_noise_count; // current noise counter + uint32_t m_noise_state; // current noise state + ssg_registers m_regs; // registers + ssg_override *m_override; // override interface +}; + +} + +#endif // YMFM_SSG_H From 9f5c46d66a2e893c85cc73fdadc686fd05fa610d Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 24 Jul 2022 10:26:07 -0300 Subject: [PATCH 21/32] Update pt-BR.po --- src/qt/languages/pt-BR.po | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/qt/languages/pt-BR.po b/src/qt/languages/pt-BR.po index 7d1eecb09..b7f325a9a 100644 --- a/src/qt/languages/pt-BR.po +++ b/src/qt/languages/pt-BR.po @@ -116,7 +116,7 @@ msgid "VGA screen &type" msgstr "&Tipo de tela VGA" msgid "RGB &Color" -msgstr "&Cor RGB" +msgstr "&Cores RGB" msgid "&RGB Grayscale" msgstr "Tons de cinza &RGB" @@ -347,13 +347,13 @@ msgid "Time synchronization" msgstr "Sincronização da hora" msgid "Disabled" -msgstr "Desativada" +msgstr "Desativar" msgid "Enabled (local time)" -msgstr "Ativada (hora local)" +msgstr "Ativar (hora local)" msgid "Enabled (UTC)" -msgstr "Ativada (UTC)" +msgstr "Ativar (UTC)" msgid "Dynamic Recompiler" msgstr "Recompilador dinâmico" @@ -386,10 +386,10 @@ msgid "Sound card:" msgstr "Placa de som:" msgid "MIDI Out Device:" -msgstr "Disp. saída MIDI:" +msgstr "Disp. de saída MIDI:" msgid "MIDI In Device:" -msgstr "Disp. entrada MIDI:" +msgstr "Disp. de entrada MIDI:" msgid "Standalone MPU-401" msgstr "MPU-401 autônomo" @@ -506,7 +506,7 @@ msgid "&Remove" msgstr "&Remover" msgid "Bus:" -msgstr "Bar.:" +msgstr "Barramento:" msgid "Channel:" msgstr "Canal:" @@ -536,7 +536,7 @@ msgid "Image Format:" msgstr "Formato:" msgid "Block Size:" -msgstr "Bloco:" +msgstr "Blocos:" msgid "Floppy drives:" msgstr "Unidades de disquete:" @@ -599,7 +599,7 @@ msgid "Fatal error" msgstr "Erro fatal" msgid " - PAUSED" -msgstr " - PAUSED" +msgstr " - PAUSADO" msgid "Press Ctrl+Alt+PgDn to return to windowed mode." msgstr "Use Ctrl+Alt+PgDn para retornar ao modo janela" @@ -749,7 +749,7 @@ msgid "Microsoft SideWinder Pad" msgstr "Microsoft SideWinder Pad" msgid "Thrustmaster Flight Control System" -msgstr "Thrustmaster Flight Control System" +msgstr "Sistema de Controle de Voo Thrustmaster" msgid "None" msgstr "Nada" @@ -821,7 +821,7 @@ msgid "About 86Box" msgstr "Sobre o 86Box" msgid "86Box v" -msgstr "86Box versão" +msgstr "86Box versão " msgid "An emulator of old computers\n\nAuthors: Sarah Walker, Miran Grca, Fred N. van Kempen (waltje), SA1988, Tiseno100, reenigne, leilei, JohnElliott, greatpsycho, and others.\n\nReleased under the GNU General Public License version 2 or later. See LICENSE for more information." msgstr "Um emulador de computadores antigos\n\nAutores: Sarah Walker, Miran Grca, Fred N. van Kempen (waltje), SA1988, Tiseno100, reenigne, leilei, JohnElliott, greatpsycho, e outros.\n\nTraduzido por: Altieres Lima da Silva\n\nLançado sob a Licença Pública Geral GNU versão 2 ou posterior. Veja o arquivo LICENSE para mais informações." From 61cb47a2d674e08b123cb904682d5526de8816b2 Mon Sep 17 00:00:00 2001 From: altiereslima Date: Sun, 24 Jul 2022 10:26:09 -0300 Subject: [PATCH 22/32] Update pt-BR.rc --- src/win/languages/pt-BR.rc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/win/languages/pt-BR.rc b/src/win/languages/pt-BR.rc index 81b2e290a..5e23a9d71 100644 --- a/src/win/languages/pt-BR.rc +++ b/src/win/languages/pt-BR.rc @@ -80,7 +80,7 @@ BEGIN MENUITEM "Monitor VGA &invertido", IDM_VID_INVERT POPUP "&Tipo de tela VGA" BEGIN - MENUITEM "&Cor RGB", IDM_VID_GRAY_RGB + MENUITEM "&Cores RGB", IDM_VID_GRAY_RGB MENUITEM "Tons de cinza &RGB", IDM_VID_GRAY_MONO MENUITEM "Monitor &âmbar", IDM_VID_GRAY_AMBER MENUITEM "Monitor &verde", IDM_VID_GRAY_GREEN @@ -267,15 +267,15 @@ END #define STR_MB "MB" #define STR_MEMORY "Memória:" #define STR_TIME_SYNC "Sincronização da hora" -#define STR_DISABLED "Desativada" -#define STR_ENABLED_LOCAL "Ativada (hora local)" -#define STR_ENABLED_UTC "Ativada (UTC)" +#define STR_DISABLED "Desativar" +#define STR_ENABLED_LOCAL "Ativar (hora local)" +#define STR_ENABLED_UTC "Ativar (UTC)" #define STR_DYNAREC "Recompilador dinâmico" #define STR_VIDEO "Vídeo:" #define STR_VOODOO "3DFX Voodoo" -#define STR_IBM8514 "IBM 8514/a Graphics" -#define STR_XGA "XGA Graphics" +#define STR_IBM8514 "Gráficos IBM 8514/a" +#define STR_XGA "Gráficos XGA" #define STR_MOUSE "Mouse:" #define STR_JOYSTICK "Joystick:" @@ -340,7 +340,7 @@ END #define STR_SIZE_MB "Tamanho (MB):" #define STR_TYPE "Tipo:" #define STR_IMG_FORMAT "Formato:" -#define STR_BLOCK_SIZE "Bloco:" +#define STR_BLOCK_SIZE "Blocos:" #define STR_FLOPPY_DRIVES "Unidades de disquete:" #define STR_TURBO "Turbo" @@ -376,7 +376,7 @@ BEGIN 2048 "86Box" IDS_2049 "Erro" IDS_2050 "Erro fatal" - IDS_2051 " - PAUSED" + IDS_2051 " - PAUSADO" IDS_2052 "Use Ctrl+Alt+PgDn para retornar ao modo janela" IDS_2053 "Velocidade" IDS_2054 "ZIP %03i %i (%s): %ls" @@ -435,7 +435,7 @@ BEGIN IDS_2099 "Joystick padrão de 8 botões" IDS_2100 "CH Flightstick Pro" IDS_2101 "Microsoft SideWinder Pad" - IDS_2102 "Thrustmaster Flight Control System" + IDS_2102 "Sistema de Controle de Voo Thrustmaster" IDS_2103 "Nada" IDS_2104 "Não foi possível carregar os aceleradores do teclado." IDS_2105 "Não foi possível registrar a entrada bruta." From d62ef9b19f3673abcda44c1cfdfa8b157a19662a Mon Sep 17 00:00:00 2001 From: richardg867 Date: Sun, 24 Jul 2022 13:29:06 -0300 Subject: [PATCH 23/32] Remove now-redundant gitignore entries --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 5b1c5bbd1..267f3d766 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,6 @@ Makefile /archive_tmp /archive_tmp_universal /static2dll.* -/pacman.txt -/deps.txt -/universal_listing.txt /VERSION *.zip *.tar From 63ade10a563f67f7c9b099ef686b16c98c822a10 Mon Sep 17 00:00:00 2001 From: richardg867 Date: Sun, 24 Jul 2022 13:31:58 -0300 Subject: [PATCH 24/32] Jenkins: Continuing build speed optimizations --- .ci/build.sh | 89 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/.ci/build.sh b/.ci/build.sh index 594de8818..2c81248e8 100644 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -107,6 +107,19 @@ make_tar() { return $? } +cache_dir="$HOME/86box-build-cache" +[ ! -d "$cache_dir" ] && mkdir -p "$cache_dir" +check_buildtag() { + [ -z "$BUILD_TAG" -o "$BUILD_TAG" != "$(cat "$cache_dir/buildtag.$1" 2> /dev/null)" ] + return $? +} +save_buildtag() { + local contents="$BUILD_TAG" + [ -n "$2" ] && local contents="$2" + echo "$contents" > "$cache_dir/buildtag.$1" + return $? +} + # Set common variables. project=86Box cwd=$(pwd) @@ -220,8 +233,6 @@ esac # Perform platform-specific setup. strip_binary=strip -cache_dir="$HOME/86box-build-cache" -[ ! -d "$cache_dir" ] && mkdir -p "$cache_dir" if is_windows then # Switch into the correct MSYSTEM if required. @@ -248,26 +259,34 @@ then # Install dependencies only if we're in a new build and/or architecture. freetype_dll="$cache_dir/freetype.$MSYSTEM.dll" - buildtag_file="$cache_dir/buildtag.$MSYSTEM" - if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_file" 2> /dev/null)" != "$BUILD_TAG" ] + if check_buildtag "$MSYSTEM" then - # Update keyring, as the package signing keys sometimes change. - echo [-] Updating package databases and keyring - yes | pacman -Sy --needed msys2-keyring + # Update databases and keyring only if we're in a new build. + if check_buildtag pacmansync + then + # Update keyring as well, since the package signing keys sometimes change. + echo [-] Updating package databases and keyring + yes | pacman -Sy --needed msys2-keyring + + # Save build tag to skip pacman sync/keyring later. + save_buildtag pacmansync + else + echo [-] Not updating package databases and keyring again + fi # Query installed packages. - pacman -Qe > pacman.txt + pacman -Qe > "$cache_dir/pacman.txt" # Download the specified versions of architecture-specific dependencies. echo -n [-] Downloading dependencies: pkg_dir="/var/cache/pacman/pkg" repo_base="https://repo.msys2.org/mingw/$(echo $MSYSTEM | tr '[:upper:]' '[:lower:]')" - cat .ci/dependencies_msys.txt | tr -d '\r' > deps.txt + cat .ci/dependencies_msys.txt | tr -d '\r' > "$cache_dir/deps.txt" pkgs="" while IFS=" " read pkg version do prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" - installed_version=$(grep -E "^$prefixed_pkg " pacman.txt | cut -d " " -f 2) + installed_version=$(grep -E "^$prefixed_pkg " "$cache_dir/pacman.txt" | cut -d " " -f 2) if [ "$installed_version" != "$version" ] # installed_version will be empty if not installed then echo -n " [$pkg" @@ -310,7 +329,7 @@ then fi echo -n "]" fi - done < deps.txt + done < "$cache_dir/deps.txt" [ -z "$pkgs" ] && echo -n ' none required' echo @@ -331,7 +350,7 @@ then popd # Query installed packages again. - pacman -Qe > pacman.txt + pacman -Qe > "$cache_dir/pacman.txt" fi # Install the latest versions for any missing packages (if the specified version couldn't be installed). @@ -339,9 +358,9 @@ then while IFS=" " read pkg version do prefixed_pkg="$MINGW_PACKAGE_PREFIX-$pkg" - grep -qE "^$prefixed_pkg " pacman.txt || pkgs="$pkgs $prefixed_pkg" - done < deps.txt - rm -f pacman.txt deps.txt + grep -qE "^$prefixed_pkg " "$cache_dir/pacman.txt" || pkgs="$pkgs $prefixed_pkg" + done < "$cache_dir/deps.txt" + rm -f "$cache_dir/pacman.txt" "$cache_dir/deps.txt" yes | pacman -S --needed $pkgs if [ $? -ne 0 ] then @@ -357,7 +376,7 @@ then # Save build tag to skip this later. Doing it here (once everything is # in place) is important to avoid potential issues with retried builds. - echo "$BUILD_TAG" > "$buildtag_file" + save_buildtag "$MSYSTEM" else echo [-] Not installing dependencies again fi @@ -408,18 +427,18 @@ then echo [-] Merging app bundles [$merge_src] and [$arch_universal] into [$merge_dest] # Merge directory structures. - (cd "archive_tmp_universal/$merge_src.app" && find . -type d && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type d && cd ../..) | sort > universal_listing.txt - cat universal_listing.txt | uniq | while IFS= read line + (cd "archive_tmp_universal/$merge_src.app" && find . -type d && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type d && cd ../..) | sort > "$cache_dir/universal_listing.txt" + cat "$cache_dir/universal_listing.txt" | uniq | while IFS= read line do echo "> Directory: $line" mkdir -p "archive_tmp_universal/$merge_dest.app/$line" done # Create merged file listing. - (cd "archive_tmp_universal/$merge_src.app" && find . -type f && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type f && cd ../..) | sort > universal_listing.txt + (cd "archive_tmp_universal/$merge_src.app" && find . -type f && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type f && cd ../..) | sort > "$cache_dir/universal_listing.txt" # Copy files that only exist on one bundle. - cat universal_listing.txt | uniq -u | while IFS= read line + cat "$cache_dir/universal_listing.txt" | uniq -u | while IFS= read line do if [ -e "archive_tmp_universal/$merge_src.app/$line" ] then @@ -432,7 +451,7 @@ then done # Copy or lipo files that exist on both bundles. - cat universal_listing.txt | uniq -d | while IFS= read line + cat "$cache_dir/universal_listing.txt" | uniq -d | while IFS= read line do if cmp -s "archive_tmp_universal/$merge_src.app/$line" "archive_tmp_universal/$arch_universal.app/$line" then @@ -448,8 +467,8 @@ then done # Merge symlinks. - (cd "archive_tmp_universal/$merge_src.app" && find . -type l && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type l && cd ../..) | sort > universal_listing.txt - cat universal_listing.txt | uniq | while IFS= read line + (cd "archive_tmp_universal/$merge_src.app" && find . -type l && cd "../../archive_tmp_universal/$arch_universal.app" && find . -type l && cd ../..) | sort > "$cache_dir/universal_listing.txt" + cat "$cache_dir/universal_listing.txt" | uniq | while IFS= read line do # Get symlink destinations. other_link_dest= @@ -544,8 +563,7 @@ then export PATH="$macports/bin:$macports/sbin:$macports/libexec/qt5/bin:$PATH" # Install dependencies only if we're in a new build and/or architecture. - buildtag_file="$cache_dir/buildtag.$(arch)" - if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_file" 2> /dev/null)" != "$BUILD_TAG" ] + if check_buildtag "$(arch)" then # Install dependencies. echo [-] Installing dependencies through MacPorts @@ -554,7 +572,7 @@ then # Save build tag to skip this later. Doing it here (once everything is # in place) is important to avoid potential issues with retried builds. - echo "$BUILD_TAG" > "$buildtag_file" + save_buildtag "$(arch)" else echo [-] Not installing dependencies again @@ -572,7 +590,6 @@ else esac # Establish general dependencies. - buildtag_aptupdate_file="$cache_dir/buildtag.aptupdate" pkgs="cmake ninja-build pkg-config git wget p7zip-full wayland-protocols tar gzip file appstream" if [ "$(dpkg --print-architecture)" = "$arch_deb" ] then @@ -584,7 +601,7 @@ else sudo dpkg --add-architecture "$arch_deb" # Force an apt-get update. - rm -f "$buildtag_aptupdate_file" + save_buildtag aptupdate "arch_$arch_deb" fi pkgs="$pkgs crossbuild-essential-$arch_deb" @@ -645,25 +662,24 @@ EOF strip_binary="$arch_triplet-strip" # Install dependencies only if we're in a new build and/or architecture. - buildtag_file="$cache_dir/buildtag.$arch_deb" - if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_file" 2> /dev/null)" != "$BUILD_TAG" ] + if check_buildtag "$arch_deb" then # Install or update dependencies. echo [-] Installing dependencies through apt - if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_aptupdate_file" 2> /dev/null)" != "$BUILD_TAG" ] + if check_buildtag aptupdate then sudo apt-get update # Save build tag to skip apt-get update later, unless a new architecture - # is added to dpkg, in which case, this saved tag file gets removed. - echo "$BUILD_TAG" > "$buildtag_aptupdate_file" + # is added to dpkg, in which case, this saved tag file gets replaced. + save_buildtag aptupdate fi DEBIAN_FRONTEND=noninteractive sudo apt-get -y install $pkgs $libpkgs sudo apt-get clean # Save build tag to skip this later. Doing it here (once everything is # in place) is important to avoid potential issues with retried builds. - echo "$BUILD_TAG" > "$buildtag_file" + save_buildtag "$arch_deb" else echo [-] Not installing dependencies again fi @@ -724,8 +740,7 @@ fi # Download Discord Game SDK from their CDN if we're in a new build. discord_zip="$cache_dir/discord_game_sdk.zip" -buildtag_file="$cache_dir/buildtag.any" -if [ -z "$BUILD_TAG" -o "$(cat "$buildtag_file" 2> /dev/null)" != "$BUILD_TAG" ] +if check_buildtag discord then # Download file. echo [-] Downloading Discord Game SDK @@ -738,7 +753,7 @@ then else # Save build tag to skip this later. Doing it here (once everything is # in place) is important to avoid potential issues with retried builds. - echo "$BUILD_TAG" > "$buildtag_file" + save_buildtag discord fi else echo [-] Not downloading Discord Game SDK again From 681127e521e95d833b50c4d5978f24e3e4ea3ab5 Mon Sep 17 00:00:00 2001 From: richardg867 Date: Sun, 24 Jul 2022 14:25:24 -0300 Subject: [PATCH 25/32] Jenkins: Speed optimizations for library builds on Linux --- .ci/build.sh | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/.ci/build.sh b/.ci/build.sh index 2c81248e8..3ac28eb8a 100644 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -833,21 +833,21 @@ then fi else cwd_root="$(pwd)" + check_buildtag "libs.$arch_deb" if grep -q "OPENAL:BOOL=ON" build/CMakeCache.txt then # Build openal-soft 1.21.1 manually to fix audio issues. This is a temporary # workaround until a newer version of openal-soft trickles down to Debian repos. prefix="$cache_dir/openal-soft-1.21.1" - if [ -d "$prefix" ] + if [ ! -d "$prefix" ] then - rm -rf "$prefix/build" - else wget -qO - https://github.com/kcat/openal-soft/archive/refs/tags/1.21.1.tar.gz | tar zxf - -C "$cache_dir" || rm -rf "$prefix" fi - cmake -G Ninja -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix/build" || exit 99 - cmake --build "$prefix/build" -j$(nproc) || exit 99 - cmake --install "$prefix/build" || exit 99 + prefix_build="$prefix/build-$arch_deb" + cmake -G Ninja -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix_build" || exit 99 + cmake --build "$prefix_build" -j$(nproc) || exit 99 + cmake --install "$prefix_build" || exit 99 # Build SDL2 without sound systems. sdl_ss=OFF @@ -855,15 +855,14 @@ else # Build FAudio 22.03 manually to remove the dependency on GStreamer. This is a temporary # workaround until a newer version of FAudio trickles down to Debian repos. prefix="$cache_dir/FAudio-22.03" - if [ -d "$prefix" ] + if [ ! -d "$prefix" ] then - rm -rf "$prefix/build" - else wget -qO - https://github.com/FNA-XNA/FAudio/archive/refs/tags/22.03.tar.gz | tar zxf - -C "$cache_dir" || rm -rf "$prefix" fi - cmake -G Ninja -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix/build" || exit 99 - cmake --build "$prefix/build" -j$(nproc) || exit 99 - cmake --install "$prefix/build" || exit 99 + prefix_build="$prefix/build-$arch_deb" + cmake -G Ninja -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix_build" || exit 99 + cmake --build "$prefix_build" -j$(nproc) || exit 99 + cmake --install "$prefix_build" || exit 99 # Build SDL2 with sound systems. sdl_ss=ON @@ -875,15 +874,14 @@ else # Build rtmidi without JACK support to remove the dependency on libjack. prefix="$cache_dir/rtmidi-4.0.0" - if [ -d "$prefix" ] + if [ ! -d "$prefix" ] then - rm -rf "$prefix/build" - else wget -qO - https://github.com/thestk/rtmidi/archive/refs/tags/4.0.0.tar.gz | tar zxf - -C "$cache_dir" || rm -rf "$prefix" fi - cmake -G Ninja -D RTMIDI_API_JACK=OFF -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix/build" || exit 99 - cmake --build "$prefix/build" -j$(nproc) || exit 99 - cmake --install "$prefix/build" || exit 99 + prefix_build="$prefix/build-$arch_deb" + cmake -G Ninja -D RTMIDI_API_JACK=OFF -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" -S "$prefix" -B "$prefix_build" || exit 99 + cmake --build "$prefix_build" -j$(nproc) || exit 99 + cmake --install "$prefix_build" || exit 99 # Build SDL2 for joystick and FAudio support, with most components # disabled to remove the dependencies on PulseAudio and libdrm. @@ -892,7 +890,7 @@ else then wget -qO - https://www.libsdl.org/release/SDL2-2.0.20.tar.gz | tar zxf - -C "$cache_dir" || rm -rf "$prefix" fi - rm -rf "$cache_dir/sdlbuild" + prefix_build="$cache_dir/SDL2-2.0.20-build-$arch_deb" cmake -G Ninja -D SDL_SHARED=ON -D SDL_STATIC=OFF \ \ -D SDL_AUDIO=$sdl_ss -D SDL_DUMMYAUDIO=$sdl_ss -D SDL_DISKAUDIO=OFF -D SDL_OSS=OFF -D SDL_ALSA=$sdl_ss -D SDL_ALSA_SHARED=$sdl_ss \ @@ -911,9 +909,9 @@ else -D SDL_LOADSO=ON -D SDL_CPUINFO=ON -D SDL_FILESYSTEM=$sdl_ui -D SDL_DLOPEN=OFF -D SDL_SENSOR=OFF -D SDL_LOCALE=OFF \ \ -D "CMAKE_TOOLCHAIN_FILE=$cwd_root/toolchain.cmake" -D "CMAKE_INSTALL_PREFIX=$cwd_root/archive_tmp/usr" \ - -S "$prefix" -B "$cache_dir/sdlbuild" || exit 99 - cmake --build "$cache_dir/sdlbuild" -j$(nproc) || exit 99 - cmake --install "$cache_dir/sdlbuild" || exit 99 + -S "$prefix" -B "$prefix_build" || exit 99 + cmake --build "$prefix_build" -j$(nproc) || exit 99 + cmake --install "$prefix_build" || exit 99 # Archive Discord Game SDK library. 7z e -y -o"archive_tmp/usr/lib" "$discord_zip" "lib/$arch_discord/discord_game_sdk.so" From 80e547000673b1f8f9804a3cacbe5dc934077493 Mon Sep 17 00:00:00 2001 From: ts-korhonen Date: Mon, 25 Jul 2022 14:13:30 +0300 Subject: [PATCH 26/32] Fix crash at exit due to a unreleased mutex. Qt startblit() and endblit() use a mutex that can remain locked at exit. A thread static wrapper makes sure that each thread using the mutex will also release it before terminating. --- src/qt/qt_platform.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/qt/qt_platform.cpp b/src/qt/qt_platform.cpp index eff022b3d..89ead7acd 100644 --- a/src/qt/qt_platform.cpp +++ b/src/qt/qt_platform.cpp @@ -54,6 +54,7 @@ QElapsedTimer elapsed_timer; static std::atomic_int blitmx_contention = 0; static std::recursive_mutex blitmx; +static thread_local std::unique_lock blit_lock { blitmx, std::defer_lock }; class CharPointer { public: @@ -468,17 +469,17 @@ void dynld_close(void *handle) void startblit() { blitmx_contention++; - if (blitmx.try_lock()) { + if (blit_lock.try_lock()) { return; } - blitmx.lock(); + blit_lock.lock(); } void endblit() { blitmx_contention--; - blitmx.unlock(); + blit_lock.unlock(); if (blitmx_contention > 0) { // a deadlock has been observed on linux when toggling via video_toggle_option // because the mutex is typically unfair on linux From 808337aac32d83eaa140791b7bdb05f91f7d5885 Mon Sep 17 00:00:00 2001 From: Adrien Moulin Date: Mon, 25 Jul 2022 20:24:31 +0200 Subject: [PATCH 27/32] OPL: add the faster YMFM cores This refactors the OPL interface in two drivers : Nuked and YMFM Nuked is used by default, YMFM can be enabled with [Sound] fm_driver = ymfm --- src/86box.c | 1 + src/chipset/via_pipc.c | 14 +- src/config.c | 10 + src/include/86box/86box.h | 2 +- src/include/86box/snd_opl.h | 62 ++--- src/include/86box/snd_opl_nuked.h | 10 - src/include/86box/snd_sb.h | 2 +- src/include/86box/timer.h | 8 + src/sound/CMakeLists.txt | 5 +- src/sound/snd_adlib.c | 35 ++- src/sound/snd_adlibgold.c | 24 +- src/sound/snd_azt2316a.c | 8 +- src/sound/snd_cmi8x38.c | 28 +-- src/sound/snd_cs423x.c | 21 +- src/sound/snd_opl.c | 301 +++-------------------- src/sound/snd_opl_nuked.c | 283 ++++++++++++++++++++-- src/sound/snd_opl_ymfm.cpp | 387 ++++++++++++++++++++++++++++++ src/sound/snd_pas16.c | 14 +- src/sound/snd_sb.c | 277 ++++++++++----------- src/sound/snd_wss.c | 30 +-- src/sound/ymfm/CMakeLists.txt | 2 +- src/win/Makefile.mingw | 6 +- 22 files changed, 975 insertions(+), 555 deletions(-) create mode 100644 src/sound/snd_opl_ymfm.cpp diff --git a/src/86box.c b/src/86box.c index cb9fa52a7..f6b791901 100644 --- a/src/86box.c +++ b/src/86box.c @@ -183,6 +183,7 @@ int confirm_exit = 1; /* (C) enable exit confirmation */ int confirm_save = 1; /* (C) enable save confirmation */ int enable_discord = 0; /* (C) enable Discord integration */ int pit_mode = -1; /* (C) force setting PIT mode */ +int fm_driver = 0; /* (C) select FM sound driver */ /* Statistics. */ extern int mmuflush; diff --git a/src/chipset/via_pipc.c b/src/chipset/via_pipc.c index bcb54130f..b8c06b9f4 100644 --- a/src/chipset/via_pipc.c +++ b/src/chipset/via_pipc.c @@ -760,7 +760,7 @@ pipc_fm_read(uint16_t addr, void *priv) uint8_t ret = 0x00; #else pipc_t *dev = (pipc_t *) priv; - uint8_t ret = opl3_read(addr, &dev->sb->opl); + uint8_t ret = dev->sb->opl.read(addr, dev->sb->opl.priv); #endif pipc_log("PIPC: fm_read(%02X) = %02X\n", addr & 0x03, ret); @@ -794,7 +794,7 @@ pipc_fm_write(uint16_t addr, uint8_t val, void *priv) } } #else - opl3_write(addr, val, &dev->sb->opl); + dev->sb->opl.write(addr, val, dev->sb->opl.priv); #endif } @@ -807,22 +807,22 @@ pipc_sb_handlers(pipc_t *dev, uint8_t modem) sb_dsp_setaddr(&dev->sb->dsp, 0); if (dev->sb_base) { - io_removehandler(dev->sb_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); - io_removehandler(dev->sb_base + 8, 2, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->sb_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_removehandler(dev->sb_base + 8, 2, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_removehandler(dev->sb_base + 4, 2, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, dev->sb); } mpu401_change_addr(dev->sb->mpu, 0); mpu401_setirq(dev->sb->mpu, 0); - io_removehandler(0x388, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(0x388, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); if (dev->ac97_regs[0][0x42] & 0x01) { dev->sb_base = 0x220 + (0x20 * (dev->ac97_regs[0][0x43] & 0x03)); sb_dsp_setaddr(&dev->sb->dsp, dev->sb_base); if (dev->ac97_regs[0][0x42] & 0x04) { - io_sethandler(dev->sb_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); - io_sethandler(dev->sb_base + 8, 2, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->sb_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_sethandler(dev->sb_base + 8, 2, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); } io_sethandler(dev->sb_base + 4, 2, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, dev->sb); diff --git a/src/config.c b/src/config.c index 8554dcc7a..81bbc016f 100644 --- a/src/config.c +++ b/src/config.c @@ -68,6 +68,7 @@ #include <86box/plat.h> #include <86box/plat_dir.h> #include <86box/ui.h> +#include <86box/snd_opl.h> typedef struct _list_ { @@ -1110,6 +1111,13 @@ load_sound(void) sound_is_float = 1; else sound_is_float = 0; + + p = config_get_string(cat, "fm_driver", "nuked"); + if (!strcmp(p, "ymfm")) { + fm_driver = FM_DRV_YMFM; + } else { + fm_driver = FM_DRV_NUKED; + } } /* Load "Network" section. */ @@ -2630,6 +2638,8 @@ save_sound(void) else config_set_string(cat, "sound_type", (sound_is_float == 1) ? "float" : "int16"); + config_set_string(cat, "fm_driver", (fm_driver == FM_DRV_NUKED) ? "nuked" : "ymfm"); + delete_section_if_empty(cat); } diff --git a/src/include/86box/86box.h b/src/include/86box/86box.h index 86fe740c9..3d2806ce7 100644 --- a/src/include/86box/86box.h +++ b/src/include/86box/86box.h @@ -136,7 +136,7 @@ extern int is_pentium; /* TODO: Move back to cpu/cpu.h when it's figured out, extern int fixed_size_x, fixed_size_y; extern double mouse_sensitivity; /* (C) Mouse sensitivity scale */ extern int pit_mode; /* (C) force setting PIT mode */ - +extern int fm_driver; /* (C) select FM sound driver */ extern char exe_path[2048]; /* path (dir) of executable */ extern char usr_path[1024]; /* path (dir) of user data */ diff --git a/src/include/86box/snd_opl.h b/src/include/86box/snd_opl.h index ac8eef6fd..a2e8dd521 100644 --- a/src/include/86box/snd_opl.h +++ b/src/include/86box/snd_opl.h @@ -17,38 +17,40 @@ #ifndef SOUND_OPL_H #define SOUND_OPL_H -typedef void (*tmrfunc)(void *priv, int timer, uint64_t period); +enum fm_type { + FM_YM3812 = 0, + FM_YMF262, + FM_YMF289B, + FM_MAX +}; + +enum fm_driver { + FM_DRV_NUKED = 0, + FM_DRV_YMFM, + FM_DRV_MAX +}; -/* Define an OPLx chip. */ typedef struct { -#ifdef SOUND_OPL_NUKED_H - nuked_t *opl; -#else - void *opl; + uint8_t (*read)(uint16_t port, void *priv); + void (*write)(uint16_t port, uint8_t val, void *priv); + int32_t * (*update)(void *priv); + void (*reset_buffer)(void *priv); + void (*set_do_cycles)(void *priv, int8_t do_cycles); + void *priv; +} fm_drv_t; + +extern uint8_t fm_driver_get(int chip_id, fm_drv_t *drv); + +extern const fm_drv_t nuked_opl_drv; +extern const fm_drv_t ymfm_drv; + +#ifdef EMU_DEVICE_H +extern const device_t ym3812_nuked_device; +extern const device_t ymf262_nuked_device; + +extern const device_t ym3812_ymfm_device; +extern const device_t ymf262_ymfm_device; +extern const device_t ymf289b_ymfm_device; #endif - int8_t flags, pad; - - uint16_t port; - uint8_t status, timer_ctrl; - uint16_t timer_count[2], - timer_cur_count[2]; - - pc_timer_t timers[2]; - - int pos; - int32_t buffer[SOUNDBUFLEN * 2]; -} opl_t; - -extern void opl_set_do_cycles(opl_t *dev, int8_t do_cycles); - -extern uint8_t opl2_read(uint16_t port, void *); -extern void opl2_write(uint16_t port, uint8_t val, void *); -extern void opl2_init(opl_t *); -extern void opl2_update(opl_t *); - -extern uint8_t opl3_read(uint16_t port, void *); -extern void opl3_write(uint16_t port, uint8_t val, void *); -extern void opl3_init(opl_t *); -extern void opl3_update(opl_t *); #endif /*SOUND_OPL_H*/ diff --git a/src/include/86box/snd_opl_nuked.h b/src/include/86box/snd_opl_nuked.h index af65d266b..93ea4ba35 100644 --- a/src/include/86box/snd_opl_nuked.h +++ b/src/include/86box/snd_opl_nuked.h @@ -20,15 +20,5 @@ #ifndef SOUND_OPL_NUKED_H #define SOUND_OPL_NUKED_H -extern void *nuked_init(uint32_t sample_rate); -extern void nuked_close(void *); - -extern uint16_t nuked_write_addr(void *, uint16_t port, uint8_t val); -extern void nuked_write_reg(void *, uint16_t reg, uint8_t v); -extern void nuked_write_reg_buffered(void *, uint16_t reg, uint8_t v); - -extern void nuked_generate(void *, int32_t *buf); -extern void nuked_generate_resampled(void *, int32_t *buf); -extern void nuked_generate_stream(void *, int32_t *sndptr, uint32_t num); #endif /*SOUND_OPL_NUKED_H*/ diff --git a/src/include/86box/snd_sb.h b/src/include/86box/snd_sb.h index bf0b437b0..bf44d4d6d 100644 --- a/src/include/86box/snd_sb.h +++ b/src/include/86box/snd_sb.h @@ -129,7 +129,7 @@ typedef struct sb_t { opl_enabled, mixer_enabled; cms_t cms; - opl_t opl, + fm_drv_t opl, opl2; sb_dsp_t dsp; union { diff --git a/src/include/86box/timer.h b/src/include/86box/timer.h index 63ca8965b..afbcec140 100644 --- a/src/include/86box/timer.h +++ b/src/include/86box/timer.h @@ -56,6 +56,10 @@ typedef struct pc_timer_t struct pc_timer_t *prev, *next; } pc_timer_t; +#ifdef __cplusplus +extern "C" { +#endif + /*Timestamp of nearest enabled timer. CPU emulation must call timer_process() when TSC matches or exceeds this.*/ extern uint32_t timer_target; @@ -237,4 +241,8 @@ timer_process_inline(void) timer_target = timer_head->ts.ts32.integer; } +#ifdef __cplusplus +} +#endif + #endif /*_TIMER_H_*/ diff --git a/src/sound/CMakeLists.txt b/src/sound/CMakeLists.txt index c0aaa2790..581f8d517 100644 --- a/src/sound/CMakeLists.txt +++ b/src/sound/CMakeLists.txt @@ -13,7 +13,7 @@ # Copyright 2020,2021 David Hrdlička. # -add_library(snd OBJECT sound.c snd_opl.c snd_opl_nuked.c snd_resid.cc +add_library(snd OBJECT sound.c snd_opl.c snd_opl_nuked.c snd_opl_ymfm.cpp snd_resid.cc midi.c snd_speaker.c snd_pssj.c snd_lpt_dac.c snd_ac97_codec.c snd_ac97_via.c snd_lpt_dss.c snd_ps1.c snd_adlib.c snd_adlibgold.c snd_ad1848.c snd_audiopci.c snd_azt2316a.c snd_cms.c snd_cmi8x38.c snd_cs423x.c snd_gus.c snd_sb.c snd_sb_dsp.c @@ -105,6 +105,9 @@ if(MUNT) endif() endif() +add_subdirectory(ymfm) +target_link_libraries(86Box ymfm) + if(PAS16) target_compile_definitions(snd PRIVATE USE_PAS16) target_sources(snd PRIVATE snd_pas16.c) diff --git a/src/sound/snd_adlib.c b/src/sound/snd_adlib.c index 1592131d0..d4bc1e3ca 100644 --- a/src/sound/snd_adlib.c +++ b/src/sound/snd_adlib.c @@ -33,7 +33,7 @@ adlib_log(const char *fmt, ...) #endif typedef struct adlib_t { - opl_t opl; + fm_drv_t opl; uint8_t pos_regs[8]; } adlib_t; @@ -44,12 +44,12 @@ adlib_get_buffer(int32_t *buffer, int len, void *p) adlib_t *adlib = (adlib_t *) p; int c; - opl2_update(&adlib->opl); + int32_t *opl_buf = adlib->opl.update(adlib->opl.priv); for (c = 0; c < len * 2; c++) - buffer[c] += (int32_t) adlib->opl.buffer[c]; + buffer[c] += (int32_t) opl_buf[c]; - adlib->opl.pos = 0; + adlib->opl.reset_buffer(adlib->opl.priv); } uint8_t @@ -76,14 +76,14 @@ adlib_mca_write(int port, uint8_t val, void *p) case 0x102: if ((adlib->pos_regs[2] & 1) && !(val & 1)) io_removehandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &adlib->opl); + adlib->opl.read, NULL, NULL, + adlib->opl.write, NULL, NULL, + adlib->opl.priv); if (!(adlib->pos_regs[2] & 1) && (val & 1)) io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &adlib->opl); + adlib->opl.read, NULL, NULL, + adlib->opl.write, NULL, NULL, + adlib->opl.priv); break; } adlib->pos_regs[port & 7] = val; @@ -104,11 +104,11 @@ adlib_init(const device_t *info) memset(adlib, 0, sizeof(adlib_t)); adlib_log("adlib_init\n"); - opl2_init(&adlib->opl); + fm_driver_get(FM_YM3812, &adlib->opl); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &adlib->opl); + adlib->opl.read, NULL, NULL, + adlib->opl.write, NULL, NULL, + adlib->opl.priv); sound_add_handler(adlib_get_buffer, adlib); return adlib; } @@ -119,9 +119,9 @@ adlib_mca_init(const device_t *info) adlib_t *adlib = adlib_init(info); io_removehandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &adlib->opl); + adlib->opl.read, NULL, NULL, + adlib->opl.write, NULL, NULL, + adlib->opl.priv); mca_add(adlib_mca_read, adlib_mca_write, adlib_mca_feedb, @@ -137,7 +137,6 @@ void adlib_close(void *p) { adlib_t *adlib = (adlib_t *) p; - free(adlib); } diff --git a/src/sound/snd_adlibgold.c b/src/sound/snd_adlibgold.c index 652c672e2..0e504065b 100644 --- a/src/sound/snd_adlibgold.c +++ b/src/sound/snd_adlibgold.c @@ -54,7 +54,7 @@ typedef struct adgold_t { int voice_count[2], voice_latch[2]; } adgold_mma; - opl_t opl; + fm_drv_t opl; ym7128_t ym7128; int fm_vol_l, fm_vol_r; @@ -194,7 +194,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) switch (addr & 7) { case 0: case 1: - opl3_write(addr, val, &adgold->opl); + adgold->opl.write(addr, val, adgold->opl.priv); break; case 2: @@ -209,7 +209,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) if (adgold->adgold_38x_state) /*Write to control chip*/ adgold->adgold_38x_addr = val; else - opl3_write(addr, val, &adgold->opl); + adgold->opl.write(addr, val, adgold->opl.priv); break; case 3: if (adgold->adgold_38x_state) { @@ -275,7 +275,7 @@ adgold_write(uint16_t addr, uint8_t val, void *p) break; } } else - opl3_write(addr, val, &adgold->opl); + adgold->opl.write(addr, val, adgold->opl.priv); break; case 4: case 6: @@ -501,14 +501,14 @@ adgold_read(uint16_t addr, void *p) switch (addr & 7) { case 0: case 1: - temp = opl3_read(addr, &adgold->opl); + temp = adgold->opl.read(addr, adgold->opl.priv); break; case 2: if (adgold->adgold_38x_state) /*Read from control chip*/ temp = adgold->adgold_status; else - temp = opl3_read(addr, &adgold->opl); + temp = adgold->opl.read(addr, adgold->opl.priv); break; case 3: @@ -527,7 +527,7 @@ adgold_read(uint16_t addr, void *p) temp = adgold->adgold_38x_regs[adgold->adgold_38x_addr]; } } else - temp = opl3_read(addr, &adgold->opl); + temp = adgold->opl.read(addr, adgold->opl.priv); break; case 4: @@ -713,13 +713,13 @@ adgold_get_buffer(int32_t *buffer, int len, void *p) int c; - opl3_update(&adgold->opl); + int32_t *opl_buf = adgold->opl.update(adgold->opl.priv); adgold_update(adgold); for (c = 0; c < len * 2; c += 2) { - adgold_buffer[c] = ((adgold->opl.buffer[c] * adgold->fm_vol_l) >> 7) / 2; + adgold_buffer[c] = ((opl_buf[c] * adgold->fm_vol_l) >> 7) / 2; adgold_buffer[c] += ((adgold->mma_buffer[0][c >> 1] * adgold->samp_vol_l) >> 7) / 4; - adgold_buffer[c + 1] = ((adgold->opl.buffer[c + 1] * adgold->fm_vol_r) >> 7) / 2; + adgold_buffer[c + 1] = ((opl_buf[c + 1] * adgold->fm_vol_r) >> 7) / 2; adgold_buffer[c + 1] += ((adgold->mma_buffer[1][c >> 1] * adgold->samp_vol_r) >> 7) / 4; } @@ -808,7 +808,7 @@ adgold_get_buffer(int32_t *buffer, int len, void *p) buffer[c + 1] += temp; } - adgold->opl.pos = 0; + adgold->opl.reset_buffer(adgold->opl.priv); adgold->pos = 0; free(adgold_buffer); @@ -881,7 +881,7 @@ adgold_init(const device_t *info) adgold->gameport_enabled = device_get_config_int("gameport"); - opl3_init(&adgold->opl); + fm_driver_get(FM_YMF262, &adgold->opl); if (adgold->surround_enabled) ym7128_init(&adgold->ym7128); diff --git a/src/sound/snd_azt2316a.c b/src/sound/snd_azt2316a.c index a72e1dbe5..890734450 100644 --- a/src/sound/snd_azt2316a.c +++ b/src/sound/snd_azt2316a.c @@ -1149,7 +1149,7 @@ azt_init(const device_t *info) azt2316a->sb->dsp.azt_eeprom[i] = read_eeprom[i]; if (azt2316a->sb->opl_enabled) - opl3_init(&azt2316a->sb->opl); + fm_driver_get(FM_YMF262, &azt2316a->sb->opl); sb_dsp_init(&azt2316a->sb->dsp, SBPRO2, azt2316a->type, azt2316a); sb_dsp_setaddr(&azt2316a->sb->dsp, azt2316a->cur_addr); @@ -1158,9 +1158,9 @@ azt_init(const device_t *info) sb_ct1345_mixer_reset(azt2316a->sb); /* DSP I/O handler is activated in sb_dsp_setaddr */ if (azt2316a->sb->opl_enabled) { - io_sethandler(azt2316a->cur_addr + 0, 0x0004, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &azt2316a->sb->opl); - io_sethandler(azt2316a->cur_addr + 8, 0x0002, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &azt2316a->sb->opl); - io_sethandler(0x0388, 0x0004, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &azt2316a->sb->opl); + io_sethandler(azt2316a->cur_addr + 0, 0x0004, azt2316a->sb->opl.read, NULL, NULL, azt2316a->sb->opl.write, NULL, NULL, azt2316a->sb->opl.priv); + io_sethandler(azt2316a->cur_addr + 8, 0x0002, azt2316a->sb->opl.read, NULL, NULL, azt2316a->sb->opl.write, NULL, NULL, azt2316a->sb->opl.priv); + io_sethandler(0x0388, 0x0004, azt2316a->sb->opl.read, NULL, NULL, azt2316a->sb->opl.write, NULL, NULL, azt2316a->sb->opl.priv); } io_sethandler(azt2316a->cur_addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, azt2316a->sb); diff --git a/src/sound/snd_cmi8x38.c b/src/sound/snd_cmi8x38.c index 4ce76508e..d4a54880b 100644 --- a/src/sound/snd_cmi8x38.c +++ b/src/sound/snd_cmi8x38.c @@ -473,10 +473,10 @@ static void cmi8x38_remap_sb(cmi8x38_t *dev) { if (dev->sb_base) { - io_removehandler(dev->sb_base, 0x0004, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); - io_removehandler(dev->sb_base + 8, 0x0002, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->sb_base, 0x0004, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_removehandler(dev->sb_base + 8, 0x0002, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_removehandler(dev->sb_base + 4, 0x0002, cmi8x38_sb_mixer_read, NULL, NULL, cmi8x38_sb_mixer_write, NULL, NULL, dev); @@ -493,10 +493,10 @@ cmi8x38_remap_sb(cmi8x38_t *dev) cmi8x38_log("CMI8x38: remap_sb(%04X)\n", dev->sb_base); if (dev->sb_base) { - io_sethandler(dev->sb_base, 0x0004, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); - io_sethandler(dev->sb_base + 8, 0x0002, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->sb_base, 0x0004, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_sethandler(dev->sb_base + 8, 0x0002, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_sethandler(dev->sb_base + 4, 0x0002, cmi8x38_sb_mixer_read, NULL, NULL, cmi8x38_sb_mixer_write, NULL, NULL, dev); @@ -508,8 +508,8 @@ static void cmi8x38_remap_opl(cmi8x38_t *dev) { if (dev->opl_base) { - io_removehandler(dev->opl_base, 0x0004, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->opl_base, 0x0004, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); } dev->opl_base = (dev->type == CMEDIA_CMI8338) ? 0x388 : opl_ports_cmi8738[dev->io_regs[0x17] & 0x03]; @@ -520,8 +520,8 @@ cmi8x38_remap_opl(cmi8x38_t *dev) cmi8x38_log("CMI8x38: remap_opl(%04X)\n", dev->opl_base); if (dev->opl_base) { - io_sethandler(dev->opl_base, 0x0004, opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->opl_base, 0x0004, dev->sb->opl.read, NULL, NULL, + dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); } } @@ -593,7 +593,7 @@ cmi8x38_read(uint16_t addr, void *priv) if (dev->type == CMEDIA_CMI8338) goto io_reg; else - ret = opl3_read(addr, &dev->sb->opl); + ret = dev->sb->opl.read(addr, dev->sb->opl.priv); break; case 0x80: @@ -872,7 +872,7 @@ cmi8x38_write(uint16_t addr, uint8_t val, void *priv) case 0x50 ... 0x5f: if (dev->type != CMEDIA_CMI8338) - opl3_write(addr, val, &dev->sb->opl); + dev->sb->opl.write(addr, val, dev->sb->opl.priv); return; case 0x92: diff --git a/src/sound/snd_cs423x.c b/src/sound/snd_cs423x.c index 1f366e373..31f4e8e6c 100644 --- a/src/sound/snd_cs423x.c +++ b/src/sound/snd_cs423x.c @@ -505,18 +505,19 @@ cs423x_get_buffer(int32_t *buffer, int len, void *priv) { cs423x_t *dev = (cs423x_t *) priv; int c, opl_wss = dev->opl_wss; + int32_t *opl_buf = NULL; /* Output audio from the WSS codec, and also the OPL if we're in charge of it. */ ad1848_update(&dev->ad1848); if (opl_wss) - opl3_update(&dev->sb->opl); + opl_buf = dev->sb->opl.update(dev->sb->opl.priv); /* Don't output anything if the analog section is powered down. */ if (!(dev->indirect_regs[2] & 0xa4)) { for (c = 0; c < len * 2; c += 2) { if (opl_wss) { - buffer[c] += (dev->sb->opl.buffer[c] * dev->ad1848.fm_vol_l) >> 16; - buffer[c + 1] += (dev->sb->opl.buffer[c + 1] * dev->ad1848.fm_vol_r) >> 16; + buffer[c] += (opl_buf[c] * dev->ad1848.fm_vol_l) >> 16; + buffer[c + 1] += (opl_buf[c + 1] * dev->ad1848.fm_vol_r) >> 16; } buffer[c] += dev->ad1848.buffer[c] / 2; @@ -526,7 +527,7 @@ cs423x_get_buffer(int32_t *buffer, int len, void *priv) dev->ad1848.pos = 0; if (opl_wss) - dev->sb->opl.pos = 0; + dev->sb->opl.reset_buffer(dev->sb->opl.priv); } static void @@ -590,14 +591,14 @@ cs423x_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv } if (dev->opl_base) { - io_removehandler(dev->opl_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->opl_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); dev->opl_base = 0; } if (dev->sb_base) { sb_dsp_setaddr(&dev->sb->dsp, 0); - io_removehandler(dev->sb_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); - io_removehandler(dev->sb_base + 8, 2, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_removehandler(dev->sb_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_removehandler(dev->sb_base + 8, 2, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_removehandler(dev->sb_base + 4, 2, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, dev->sb); io_removehandler(dev->sb_base, 16, NULL, NULL, NULL, cs423x_ctxswitch_write, NULL, NULL, dev); dev->sb_base = 0; @@ -618,14 +619,14 @@ cs423x_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv if (config->io[1].base != ISAPNP_IO_DISABLED) { dev->opl_base = config->io[1].base; - io_sethandler(dev->opl_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->opl_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); } if (config->io[2].base != ISAPNP_IO_DISABLED) { dev->sb_base = config->io[2].base; sb_dsp_setaddr(&dev->sb->dsp, dev->sb_base); - io_sethandler(dev->sb_base, 4, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); - io_sethandler(dev->sb_base + 8, 2, opl3_read, NULL, NULL, opl3_write, NULL, NULL, &dev->sb->opl); + io_sethandler(dev->sb_base, 4, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); + io_sethandler(dev->sb_base + 8, 2, dev->sb->opl.read, NULL, NULL, dev->sb->opl.write, NULL, NULL, dev->sb->opl.priv); io_sethandler(dev->sb_base + 4, 2, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, dev->sb); io_sethandler(dev->sb_base, 16, NULL, NULL, NULL, cs423x_ctxswitch_write, NULL, NULL, dev); } diff --git a/src/sound/snd_opl.c b/src/sound/snd_opl.c index 6cba7b3e8..cce75bf39 100644 --- a/src/sound/snd_opl.c +++ b/src/sound/snd_opl.c @@ -28,285 +28,44 @@ #include "cpu.h" #include <86box/86box.h> +#include <86box/device.h> #include <86box/io.h> -#include <86box/timer.h> #include <86box/sound.h> #include <86box/snd_opl.h> -#include <86box/snd_opl_nuked.h> -enum { - FLAG_CYCLES = 0x02, - FLAG_OPL3 = 0x01 -}; - -enum { - STAT_TMR_OVER = 0x60, - STAT_TMR1_OVER = 0x40, - STAT_TMR2_OVER = 0x20, - STAT_TMR_ANY = 0x80 -}; - -enum { - CTRL_RESET = 0x80, - CTRL_TMR_MASK = 0x60, - CTRL_TMR1_MASK = 0x40, - CTRL_TMR2_MASK = 0x20, - CTRL_TMR2_START = 0x02, - CTRL_TMR1_START = 0x01 -}; - -#ifdef ENABLE_OPL_LOG -int opl_do_log = ENABLE_OPL_LOG; - -static void -opl_log(const char *fmt, ...) -{ - va_list ap; - - if (opl_do_log) { - va_start(ap, fmt); - pclog_ex(fmt, ap); - va_end(ap); - } -} -#else -# define opl_log(fmt, ...) -#endif - -static void -timer_tick(opl_t *dev, int tmr) -{ - dev->timer_cur_count[tmr] = (dev->timer_cur_count[tmr] + 1) & 0xff; - - opl_log("Ticking timer %i, count now %02X...\n", tmr, dev->timer_cur_count[tmr]); - - if (dev->timer_cur_count[tmr] == 0x00) { - dev->status |= ((STAT_TMR1_OVER >> tmr) & ~dev->timer_ctrl); - dev->timer_cur_count[tmr] = dev->timer_count[tmr]; - - opl_log("Count wrapped around to zero, reloading timer %i (%02X), status = %02X...\n", tmr, (STAT_TMR1_OVER >> tmr), dev->status); - } - - timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0); -} - -static void -timer_control(opl_t *dev, int tmr, int start) -{ - timer_on_auto(&dev->timers[tmr], 0.0); - - if (start) { - opl_log("Loading timer %i count: %02X = %02X\n", tmr, dev->timer_cur_count[tmr], dev->timer_count[tmr]); - dev->timer_cur_count[tmr] = dev->timer_count[tmr]; - if (dev->flags & FLAG_OPL3) - timer_tick(dev, tmr); /* Per the YMF 262 datasheet, OPL3 starts counting immediately, unlike OPL2. */ - else - timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0); - } else { - opl_log("Timer %i stopped\n", tmr); - if (tmr == 1) { - dev->status &= ~STAT_TMR2_OVER; - } else - dev->status &= ~STAT_TMR1_OVER; - } -} - -static void -timer_1(void *priv) -{ - opl_t *dev = (opl_t *) priv; - - timer_tick(dev, 0); -} - -static void -timer_2(void *priv) -{ - opl_t *dev = (opl_t *) priv; - - timer_tick(dev, 1); -} - -static uint8_t -opl_read(opl_t *dev, uint16_t port) -{ - uint8_t ret = 0xff; - - if ((port & 0x0003) == 0x0000) { - ret = dev->status; - if (dev->status & STAT_TMR_OVER) - ret |= STAT_TMR_ANY; - } - - opl_log("OPL statret = %02x, status = %02x\n", ret, dev->status); - - return ret; -} - -static void -opl_write(opl_t *dev, uint16_t port, uint8_t val) -{ - if ((port & 0x0001) == 0x0001) { - nuked_write_reg_buffered(dev->opl, dev->port, val); - - switch (dev->port) { - case 0x02: /* Timer 1 */ - dev->timer_count[0] = val; - opl_log("Timer 0 count now: %i\n", dev->timer_count[0]); - break; - - case 0x03: /* Timer 2 */ - dev->timer_count[1] = val; - opl_log("Timer 1 count now: %i\n", dev->timer_count[1]); - break; - - case 0x04: /* Timer control */ - if (val & CTRL_RESET) { - opl_log("Resetting timer status...\n"); - dev->status &= ~STAT_TMR_OVER; - } else { - dev->timer_ctrl = val; - timer_control(dev, 0, val & CTRL_TMR1_START); - timer_control(dev, 1, val & CTRL_TMR2_START); - opl_log("Status mask now %02X (val = %02X)\n", (val & ~CTRL_TMR_MASK) & CTRL_TMR_MASK, val); - } - break; - } - } else { - dev->port = nuked_write_addr(dev->opl, port, val) & 0x01ff; - - if (!(dev->flags & FLAG_OPL3)) - dev->port &= 0x00ff; - } -} - -void -opl_set_do_cycles(opl_t *dev, int8_t do_cycles) -{ - if (do_cycles) - dev->flags |= FLAG_CYCLES; - else - dev->flags &= ~FLAG_CYCLES; -} - -static void -opl_init(opl_t *dev, int is_opl3) -{ - memset(dev, 0x00, sizeof(opl_t)); - - dev->flags = FLAG_CYCLES; - if (is_opl3) - dev->flags |= FLAG_OPL3; - else - dev->status = 0x06; - - /* Create a NukedOPL object. */ - dev->opl = nuked_init(48000); - - timer_add(&dev->timers[0], timer_1, dev, 0); - timer_add(&dev->timers[1], timer_2, dev, 0); -} - -void -opl_close(opl_t *dev) -{ - /* Release the NukedOPL object. */ - if (dev->opl) { - nuked_close(dev->opl); - dev->opl = NULL; - } -} +static uint32_t fm_dev_inst[FM_DRV_MAX][FM_MAX]; uint8_t -opl2_read(uint16_t port, void *priv) -{ - opl_t *dev = (opl_t *) priv; +fm_driver_get(int chip_id, fm_drv_t *drv) { + switch (chip_id) { + case FM_YM3812: + if (fm_driver == FM_DRV_NUKED) { + *drv = nuked_opl_drv; + drv->priv = device_add_inst(&ym3812_nuked_device, fm_dev_inst[fm_driver][chip_id]++); + } else { + *drv = ymfm_drv; + drv->priv = device_add_inst(&ym3812_ymfm_device, fm_dev_inst[fm_driver][chip_id]++); + } + break; - if (dev->flags & FLAG_CYCLES) - cycles -= ((int) (isa_timing * 8)); + case FM_YMF262: + if (fm_driver == FM_DRV_NUKED) { + *drv = nuked_opl_drv; + drv->priv = device_add_inst(&ymf262_nuked_device, fm_dev_inst[fm_driver][chip_id]++); + } else { + *drv = ymfm_drv; + drv->priv = device_add_inst(&ymf262_ymfm_device, fm_dev_inst[fm_driver][chip_id]++); + } + break; - opl2_update(dev); - opl_log("OPL2 port read = %04x\n", port); + case FM_YMF289B: + *drv = ymfm_drv; + drv->priv = device_add_inst(&ymf289b_ymfm_device, fm_dev_inst[fm_driver][chip_id]++); + break; - return (opl_read(dev, port)); -} - -void -opl2_write(uint16_t port, uint8_t val, void *priv) -{ - opl_t *dev = (opl_t *) priv; - - opl2_update(dev); - - opl_log("OPL2 port write = %04x\n", port); - opl_write(dev, port, val); -} - -void -opl2_init(opl_t *dev) -{ - opl_init(dev, 0); -} - -void -opl2_update(opl_t *dev) -{ - if (dev->pos >= sound_pos_global) { - return; + default: + return 0; } - nuked_generate_stream(dev->opl, - &dev->buffer[dev->pos * 2], - sound_pos_global - dev->pos); - - for (; dev->pos < sound_pos_global; dev->pos++) { - dev->buffer[dev->pos * 2] /= 2; - dev->buffer[(dev->pos * 2) + 1] = dev->buffer[dev->pos * 2]; - } -} - -uint8_t -opl3_read(uint16_t port, void *priv) -{ - opl_t *dev = (opl_t *) priv; - - if (dev->flags & FLAG_CYCLES) - cycles -= ((int) (isa_timing * 8)); - - opl3_update(dev); - - return (opl_read(dev, port)); -} - -void -opl3_write(uint16_t port, uint8_t val, void *priv) -{ - opl_t *dev = (opl_t *) priv; - - opl3_update(dev); - - opl_write(dev, port, val); -} - -void -opl3_init(opl_t *dev) -{ - opl_init(dev, 1); -} - -/* API to sound interface. */ -void -opl3_update(opl_t *dev) -{ - if (dev->pos >= sound_pos_global) - return; - - nuked_generate_stream(dev->opl, - &dev->buffer[dev->pos * 2], - sound_pos_global - dev->pos); - - for (; dev->pos < sound_pos_global; dev->pos++) { - dev->buffer[dev->pos * 2] /= 2; - dev->buffer[(dev->pos * 2) + 1] /= 2; - } -} + return 1; +}; diff --git a/src/sound/snd_opl_nuked.c b/src/sound/snd_opl_nuked.c index 347316e4e..8e1a7e774 100644 --- a/src/sound/snd_opl_nuked.c +++ b/src/sound/snd_opl_nuked.c @@ -46,6 +46,8 @@ #include <86box/snd_opl_nuked.h> #include <86box/sound.h> #include <86box/timer.h> +#include <86box/device.h> +#include <86box/snd_opl.h> #define WRBUF_SIZE 1024 #define WRBUF_DELAY 1 @@ -169,6 +171,56 @@ typedef struct chip { wrbuf_t wrbuf[WRBUF_SIZE]; } nuked_t; +typedef struct { + nuked_t opl; + int8_t flags, pad; + + uint16_t port; + uint8_t status, timer_ctrl; + uint16_t timer_count[2], + timer_cur_count[2]; + + pc_timer_t timers[2]; + + int pos; + int32_t buffer[SOUNDBUFLEN * 2]; +} nuked_drv_t; + +enum { + FLAG_CYCLES = 0x02, + FLAG_OPL3 = 0x01 +}; + +enum { + STAT_TMR_OVER = 0x60, + STAT_TMR1_OVER = 0x40, + STAT_TMR2_OVER = 0x20, + STAT_TMR_ANY = 0x80 +}; + +enum { + CTRL_RESET = 0x80, + CTRL_TMR_MASK = 0x60, + CTRL_TMR1_MASK = 0x40, + CTRL_TMR2_MASK = 0x20, + CTRL_TMR2_START = 0x02, + CTRL_TMR1_START = 0x01 +}; + +#ifdef ENABLE_OPL_LOG +static void +nuked_log(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); +} +#else +# define nuked_log(fmt, ...) +#endif + // logsin table static const uint16_t logsinrom[256] = { 0x859, 0x6c3, 0x607, 0x58b, 0x52e, 0x4e4, 0x4a6, 0x471, @@ -1270,10 +1322,8 @@ nuked_generate(void *priv, int32_t *bufp) } void -nuked_generate_resampled(void *priv, int32_t *bufp) +nuked_generate_resampled(nuked_t *dev, int32_t *bufp) { - nuked_t *dev = (nuked_t *) priv; - while (dev->samplecnt >= dev->rateratio) { dev->oldsamples[0] = dev->samples[0]; dev->oldsamples[1] = dev->samples[1]; @@ -1292,9 +1342,8 @@ nuked_generate_resampled(void *priv, int32_t *bufp) } void -nuked_generate_stream(void *priv, int32_t *sndptr, uint32_t num) +nuked_generate_stream(nuked_t *dev, int32_t *sndptr, uint32_t num) { - nuked_t *dev = (nuked_t *) priv; uint32_t i; for (i = 0; i < num; i++) { @@ -1303,13 +1352,11 @@ nuked_generate_stream(void *priv, int32_t *sndptr, uint32_t num) } } -void * -nuked_init(uint32_t samplerate) +void +nuked_init(nuked_t *dev, uint32_t samplerate) { - nuked_t *dev; uint8_t i; - dev = (nuked_t *) malloc(sizeof(nuked_t)); memset(dev, 0x00, sizeof(nuked_t)); for (i = 0; i < 36; i++) { @@ -1350,14 +1397,222 @@ nuked_init(uint32_t samplerate) dev->rateratio = (samplerate << RSM_FRAC) / 49716; dev->tremoloshift = 4; dev->vibshift = 1; - - return (dev); } -void -nuked_close(void *priv) +static void +nuked_timer_tick(nuked_drv_t *dev, int tmr) { - nuked_t *dev = (nuked_t *) priv; + dev->timer_cur_count[tmr] = (dev->timer_cur_count[tmr] + 1) & 0xff; + nuked_log("Ticking timer %i, count now %02X...\n", tmr, dev->timer_cur_count[tmr]); + + if (dev->timer_cur_count[tmr] == 0x00) { + dev->status |= ((STAT_TMR1_OVER >> tmr) & ~dev->timer_ctrl); + dev->timer_cur_count[tmr] = dev->timer_count[tmr]; + + nuked_log("Count wrapped around to zero, reloading timer %i (%02X), status = %02X...\n", tmr, (STAT_TMR1_OVER >> tmr), dev->status); + } + + timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0); +} + +static void +nuked_timer_control(nuked_drv_t *dev, int tmr, int start) +{ + timer_on_auto(&dev->timers[tmr], 0.0); + + if (start) { + nuked_log("Loading timer %i count: %02X = %02X\n", tmr, dev->timer_cur_count[tmr], dev->timer_count[tmr]); + dev->timer_cur_count[tmr] = dev->timer_count[tmr]; + if (dev->flags & FLAG_OPL3) + nuked_timer_tick(dev, tmr); /* Per the YMF 262 datasheet, OPL3 starts counting immediately, unlike OPL2. */ + else + timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0); + } else { + nuked_log("Timer %i stopped\n", tmr); + if (tmr == 1) { + dev->status &= ~STAT_TMR2_OVER; + } else + dev->status &= ~STAT_TMR1_OVER; + } +} + +static void +nuked_timer_1(void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *) priv; + + nuked_timer_tick(dev, 0); +} + +static void +nuked_timer_2(void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *) priv; + + nuked_timer_tick(dev, 1); +} + +static void +nuked_drv_set_do_cycles(void *priv, int8_t do_cycles) +{ + nuked_drv_t *dev = (nuked_drv_t *)priv; + + if (do_cycles) + dev->flags |= FLAG_CYCLES; + else + dev->flags &= ~FLAG_CYCLES; +} + +static void * +nuked_drv_init(const device_t *info) +{ + nuked_drv_t *dev = (nuked_drv_t *) calloc(1, sizeof(nuked_drv_t)); + dev->flags = FLAG_CYCLES; + if (info->local == FM_YMF262) + dev->flags |= FLAG_OPL3; + else + dev->status = 0x06; + + /* Initialize the NukedOPL object. */ + nuked_init(&dev->opl, 48000); + + timer_add(&dev->timers[0], nuked_timer_1, dev, 0); + timer_add(&dev->timers[1], nuked_timer_2, dev, 0); + + return dev; +} + +static void +nuked_drv_close(void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *)priv; free(dev); } + +static int32_t * +nuked_drv_update(void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *)priv; + + if (dev->pos >= sound_pos_global) + return dev->buffer; + + nuked_generate_stream(&dev->opl, + &dev->buffer[dev->pos * 2], + sound_pos_global - dev->pos); + + for (; dev->pos < sound_pos_global; dev->pos++) { + dev->buffer[dev->pos * 2] /= 2; + dev->buffer[(dev->pos * 2) + 1] /= 2; + } + + return dev->buffer; +} + +static uint8_t +nuked_drv_read(uint16_t port, void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *) priv; + + if (dev->flags & FLAG_CYCLES) + cycles -= ((int) (isa_timing * 8)); + + nuked_drv_update(dev); + + uint8_t ret = 0xff; + + if ((port & 0x0003) == 0x0000) { + ret = dev->status; + if (dev->status & STAT_TMR_OVER) + ret |= STAT_TMR_ANY; + } + + nuked_log("OPL statret = %02x, status = %02x\n", ret, dev->status); + + return ret; +} + +static void +nuked_drv_write(uint16_t port, uint8_t val, void *priv) +{ + nuked_drv_t *dev = (nuked_drv_t *)priv; + nuked_drv_update(dev); + + if ((port & 0x0001) == 0x0001) { + nuked_write_reg_buffered(&dev->opl, dev->port, val); + + switch (dev->port) { + case 0x02: /* Timer 1 */ + dev->timer_count[0] = val; + nuked_log("Timer 0 count now: %i\n", dev->timer_count[0]); + break; + + case 0x03: /* Timer 2 */ + dev->timer_count[1] = val; + nuked_log("Timer 1 count now: %i\n", dev->timer_count[1]); + break; + + case 0x04: /* Timer control */ + if (val & CTRL_RESET) { + nuked_log("Resetting timer status...\n"); + dev->status &= ~STAT_TMR_OVER; + } else { + dev->timer_ctrl = val; + nuked_timer_control(dev, 0, val & CTRL_TMR1_START); + nuked_timer_control(dev, 1, val & CTRL_TMR2_START); + nuked_log("Status mask now %02X (val = %02X)\n", (val & ~CTRL_TMR_MASK) & CTRL_TMR_MASK, val); + } + break; + } + } else { + dev->port = nuked_write_addr(&dev->opl, port, val) & 0x01ff; + + if (!(dev->flags & FLAG_OPL3)) + dev->port &= 0x00ff; + } +} + +static void +nuked_drv_reset_buffer(void *priv) { + nuked_drv_t *dev = (nuked_drv_t *)priv; + + dev->pos = 0; +} + +const device_t ym3812_nuked_device = { + .name = "Yamaha YM3812 OPL2 (NUKED)", + .internal_name = "ym3812_nuked", + .flags = 0, + .local = FM_YM3812, + .init = nuked_drv_init, + .close = nuked_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t ymf262_nuked_device = { + .name = "Yamaha YMF262 OPL3 (NUKED)", + .internal_name = "ymf262_nuked", + .flags = 0, + .local = FM_YMF262, + .init = nuked_drv_init, + .close = nuked_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const fm_drv_t nuked_opl_drv = { + &nuked_drv_read, + &nuked_drv_write, + &nuked_drv_update, + &nuked_drv_reset_buffer, + &nuked_drv_set_do_cycles, + NULL, +}; \ No newline at end of file diff --git a/src/sound/snd_opl_ymfm.cpp b/src/sound/snd_opl_ymfm.cpp new file mode 100644 index 000000000..f82d64822 --- /dev/null +++ b/src/sound/snd_opl_ymfm.cpp @@ -0,0 +1,387 @@ +/* + * 86Box A hypervisor and IBM PC system emulator that specializes in + * running old operating systems and software designed for IBM + * PC systems and compatibles from 1981 through fairly recent + * system designs based on the PCI bus. + * + * This file is part of the 86Box distribution. + * + * Interface to the YMFM emulator. + * + * + * Authors: Adrien Moulin, + * + * Copyright 2022 Adrien Moulin. + */ + +#include +#include +#include +#include +#include "ymfm/ymfm_opl.h" + +extern "C" { +#include <86box/timer.h> +#include <86box/device.h> +#include <86box/sound.h> +#include <86box/snd_opl.h> +} + +#define RSM_FRAC 10 + +enum { + FLAG_CYCLES = (1 << 0) +}; + +class YMFMChipBase +{ +public: + YMFMChipBase(uint32_t clock, fm_type type, uint32_t samplerate) + : m_buf_pos(0), m_flags(0), m_type(type) + { + memset(m_buffer, 0, sizeof(m_buffer)); + } + + virtual ~YMFMChipBase() + { + } + + fm_type type() const { return m_type; } + int8_t flags() const { return m_flags; } + void set_do_cycles(int8_t do_cycles) { do_cycles ? m_flags |= FLAG_CYCLES : m_flags &= ~FLAG_CYCLES; } + int32_t *buffer() const { return (int32_t *)m_buffer; } + void reset_buffer() { m_buf_pos = 0; } + + virtual uint32_t sample_rate() const = 0; + + virtual void write(uint16_t addr, uint8_t data) = 0; + virtual void generate(int32_t *data, uint32_t num_samples) = 0; + virtual void generate_resampled(int32_t *data, uint32_t num_samples) = 0; + virtual int32_t * update() = 0; + virtual uint8_t read(uint16_t addr) = 0; + +protected: + int32_t m_buffer[SOUNDBUFLEN * 2]; + uint32_t m_buf_pos; + int8_t m_flags; + fm_type m_type; +}; + +template +class YMFMChip : public YMFMChipBase, public ymfm::ymfm_interface +{ +public: + YMFMChip(uint32_t clock, fm_type type, uint32_t samplerate) + : YMFMChipBase(clock, type, samplerate) + , m_chip(*this) + , m_clock(clock) + , m_samplecnt(0) + { + memset(m_samples, 0, sizeof(m_samples)); + memset(m_oldsamples, 0, sizeof(m_oldsamples)); + m_rateratio = (samplerate << RSM_FRAC) / m_chip.sample_rate(m_clock); + m_clock_us = 1000000 / (double) m_clock; + + timer_add(&m_timers[0], YMFMChip::timer1, this, 0); + timer_add(&m_timers[1], YMFMChip::timer2, this, 0); + } + + virtual uint32_t sample_rate() const override + { + return m_chip.sample_rate(m_clock); + } + + virtual void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) override + { + if (tnum > 1) + return; + + pc_timer_t *timer = &m_timers[tnum]; + if (duration_in_clocks < 0) { + timer_stop(timer); + } else { + double period = m_clock_us * duration_in_clocks; + timer_on_auto(timer, period); + } + } + + virtual void generate(int32_t *data, uint32_t num_samples) override + { + for (uint32_t i = 0; i < num_samples; i++) { + m_chip.generate(&m_output); + if (ChipType::OUTPUTS == 1) { + *data++ = m_output.data[0]; + *data++ = m_output.data[0]; + } else { + *data++ = m_output.data[0]; + *data++ = m_output.data[1 % ChipType::OUTPUTS]; + } + } + } + +virtual void generate_resampled(int32_t *data, uint32_t num_samples) override + { + for (uint32_t i = 0; i < num_samples; i++) { + while (m_samplecnt >= m_rateratio) { + m_oldsamples[0] = m_samples[0]; + m_oldsamples[1] = m_samples[1]; + m_chip.generate(&m_output); + if (ChipType::OUTPUTS == 1) { + m_samples[0] = m_output.data[0]; + m_samples[1] = m_output.data[0]; + } else { + m_samples[0] = m_output.data[0]; + m_samples[1] = m_output.data[1 % ChipType::OUTPUTS]; + } + m_samplecnt -= m_rateratio; + } + + *data++ = ((int32_t) ((m_oldsamples[0] * (m_rateratio - m_samplecnt) + + m_samples[0] * m_samplecnt) + / m_rateratio)); + *data++ = ((int32_t) ((m_oldsamples[1] * (m_rateratio - m_samplecnt) + + m_samples[1] * m_samplecnt) + / m_rateratio)); + + m_samplecnt += 1 << RSM_FRAC; + } + } + + + /*virtual void generate_resampled(int32_t *data, uint32_t num_samples) override + { + for (uint32_t i = 0; i < num_samples; i++) { + while (m_samplecnt >= m_rateratio) { + m_oldsamples[0] = m_samples[0]; + m_oldsamples[1] = m_samples[1]; + generate(m_samples, 1); + m_samplecnt -= m_rateratio; + } + + *data++ = ((int32_t) ((m_oldsamples[0] * (m_rateratio - m_samplecnt) + + m_samples[0] * m_samplecnt) + / m_rateratio)) / 2; + *data++ = ((int32_t) ((m_oldsamples[1] * (m_rateratio - m_samplecnt) + + m_samples[1] * m_samplecnt) + / m_rateratio)) / 2; + + m_samplecnt += 1 << RSM_FRAC; + } + }*/ + + virtual int32_t *update() override + { + if (m_buf_pos >= sound_pos_global) + return m_buffer; + + generate_resampled(&m_buffer[m_buf_pos * 2], sound_pos_global - m_buf_pos); + + for (; m_buf_pos < sound_pos_global; m_buf_pos++) { + m_buffer[m_buf_pos * 2] /= 2; + m_buffer[(m_buf_pos * 2) + 1] /= 2; + } + + return m_buffer; + } + + virtual void write(uint16_t addr, uint8_t data) override + { + m_chip.write(addr, data); + } + + virtual uint8_t read(uint16_t addr) override + { + return m_chip.read(addr); + } + + static void timer1(void *priv) + { + YMFMChip *drv = (YMFMChip *) priv; + drv->m_engine->engine_timer_expired(0); + } + + static void timer2(void *priv) + { + YMFMChip *drv = (YMFMChip *) priv; + drv->m_engine->engine_timer_expired(1); + } + +private: + ChipType m_chip; + uint32_t m_clock; + double m_clock_us; + typename ChipType::output_data m_output; + pc_timer_t m_timers[2]; + + // Resampling + int32_t m_rateratio; + int32_t m_samplecnt; + int32_t m_oldsamples[2]; + int32_t m_samples[2]; +}; + +extern "C" +{ +#include +#include +#include +#include +#include +#include +#define HAVE_STDARG_H + +#include "cpu.h" +#include <86box/86box.h> +#include <86box/io.h> +#include <86box/snd_opl.h> + +#ifdef ENABLE_OPL_LOG +static int opl_do_log = ENABLE_OPL_LOG; + +static void +opl_log(const char *fmt, ...) +{ + va_list ap; + + if (opl_do_log) { + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); + } +} +#else +# define opl_log(fmt, ...) +#endif + +static void * +ymfm_drv_init(const device_t *info) +{ + YMFMChipBase *fm; + + switch (info->local) { + case FM_YM3812: + default: + fm = (YMFMChipBase *) new YMFMChip(3579545, FM_YM3812, 48000); + break; + + case FM_YMF262: + fm = (YMFMChipBase *) new YMFMChip(14318181, FM_YMF262, 48000); + break; + + case FM_YMF289B: + fm = (YMFMChipBase *) new YMFMChip(33868800, FM_YMF289B, 48000); + break; + } + + fm->set_do_cycles(1); + + return fm; +} + +static void +ymfm_drv_close(void *priv) +{ + YMFMChipBase *drv = (YMFMChipBase *) priv; + + if (drv != NULL) + delete(drv); +} + +static uint8_t +ymfm_drv_read(uint16_t port, void *priv) +{ + YMFMChipBase *drv = (YMFMChipBase *) priv; + + if (drv->flags() & FLAG_CYCLES) { + cycles -= ((int) (isa_timing * 8)); + } + + uint8_t ret = drv->read(port); + drv->update(); + + opl_log("YMFM read port %04x, status = %02x\n", port, ret); + return ret; +} + +static void +ymfm_drv_write(uint16_t port, uint8_t val, void *priv) +{ + YMFMChipBase *drv = (YMFMChipBase *) priv; + opl_log("YMFM write port %04x value = %02x\n", port, val); + drv->write(port, val); + drv->update(); +} + +static int32_t * +ymfm_drv_update(void *priv) { + YMFMChipBase *drv = (YMFMChipBase *) priv; + + return drv->update(); +} + +static void +ymfm_drv_reset_buffer(void *priv) { + YMFMChipBase *drv = (YMFMChipBase *) priv; + + drv->reset_buffer(); +} + +static void +ymfm_drv_set_do_cycles(void *priv, int8_t do_cycles) +{ + YMFMChipBase *drv = (YMFMChipBase *) priv; + drv->set_do_cycles(do_cycles); +} + +const device_t ym3812_ymfm_device = { + .name = "Yamaha YM3812 OPL2 (YMFM)", + .internal_name = "ym3812_ymfm", + .flags = 0, + .local = FM_YM3812, + .init = ymfm_drv_init, + .close = ymfm_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t ymf262_ymfm_device = { + .name = "Yamaha YMF262 OPL3 (YMFM)", + .internal_name = "ymf262_ymfm", + .flags = 0, + .local = FM_YMF262, + .init = ymfm_drv_init, + .close = ymfm_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const device_t ymf289b_ymfm_device = { + .name = "Yamaha YMF289B OPL3-L (YMFM)", + .internal_name = "ymf289b_ymfm", + .flags = 0, + .local = FM_YMF289B, + .init = ymfm_drv_init, + .close = ymfm_drv_close, + .reset = NULL, + { .available = NULL }, + .speed_changed = NULL, + .force_redraw = NULL, + .config = NULL +}; + +const fm_drv_t ymfm_drv { + &ymfm_drv_read, + &ymfm_drv_write, + &ymfm_drv_update, + &ymfm_drv_reset_buffer, + &ymfm_drv_set_do_cycles, + NULL, +}; + +} diff --git a/src/sound/snd_pas16.c b/src/sound/snd_pas16.c index 33bd51b34..1662598c9 100644 --- a/src/sound/snd_pas16.c +++ b/src/sound/snd_pas16.c @@ -137,7 +137,7 @@ typedef struct pas16_t { int64_t enable[3]; } pit; - opl_t opl; + fm_drv_t opl; sb_dsp_t dsp; int16_t pcm_buffer[2][SOUNDBUFLEN]; @@ -201,7 +201,7 @@ pas16_in(uint16_t port, void *p) case 0x389: case 0x38a: case 0x38b: - temp = opl3_read((port - pas16->base) + 0x388, &pas16->opl); + temp = pas16->opl.read((port - pas16->base) + 0x388, pas16->opl.priv); break; case 0xb88: @@ -301,7 +301,7 @@ pas16_out(uint16_t port, uint8_t val, void *p) case 0x389: case 0x38a: case 0x38b: - opl3_write((port - pas16->base) + 0x388, val, &pas16->opl); + pas16->opl.write((port - pas16->base) + 0x388, val, pas16->opl.priv); break; case 0xb88: @@ -702,17 +702,17 @@ pas16_get_buffer(int32_t *buffer, int len, void *p) pas16_t *pas16 = (pas16_t *) p; int c; - opl3_update(&pas16->opl); + int32_t *opl_buf = pas16->opl.update(pas16->opl.priv); sb_dsp_update(&pas16->dsp); pas16_update(pas16); for (c = 0; c < len * 2; c++) { - buffer[c] += pas16->opl.buffer[c]; + buffer[c] += opl_buf[c]; buffer[c] += (int16_t) (sb_iir(0, c & 1, (double) pas16->dsp.buffer[c]) / 1.3) / 2; buffer[c] += (pas16->pcm_buffer[c & 1][c >> 1] / 2); } pas16->pos = 0; - pas16->opl.pos = 0; + pas16->opl.reset_buffer(pas16->opl.priv); pas16->dsp.pos = 0; } @@ -722,7 +722,7 @@ pas16_init(const device_t *info) pas16_t *pas16 = malloc(sizeof(pas16_t)); memset(pas16, 0, sizeof(pas16_t)); - opl3_init(&pas16->opl); + fm_driver_get(FM_YMF262, &pas16->opl); sb_dsp_init(&pas16->dsp, SB2, SB_SUBTYPE_DEFAULT, pas16); io_sethandler(0x9a01, 0x0001, NULL, NULL, NULL, pas16_out_base, NULL, NULL, pas16); diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index aaafd11fa..75c59e11e 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -183,9 +183,10 @@ sb_get_buffer_sb2(int32_t *buffer, int len, void *p) sb_ct1335_mixer_t *mixer = &sb->mixer_sb2; int c; double out_mono = 0.0, out_l = 0.0, out_r = 0.0; + int32_t *opl_buf = NULL; if (sb->opl_enabled) - opl2_update(&sb->opl); + opl_buf = sb->opl.update(sb->opl.priv); sb_dsp_update(&sb->dsp); @@ -198,7 +199,7 @@ sb_get_buffer_sb2(int32_t *buffer, int len, void *p) out_r = 0.0; if (sb->opl_enabled) - out_mono = ((double) sb->opl.buffer[c]) * 0.7171630859375; + out_mono = ((double) opl_buf[c]) * 0.7171630859375; if (sb->cms_enabled) { out_l += sb->cms.buffer[c]; @@ -234,7 +235,7 @@ sb_get_buffer_sb2(int32_t *buffer, int len, void *p) sb->pos = 0; if (sb->opl_enabled) - sb->opl.pos = 0; + sb->opl.reset_buffer(sb->opl.priv); sb->dsp.pos = 0; @@ -265,13 +266,14 @@ sb_get_buffer_sbpro(int32_t *buffer, int len, void *p) sb_ct1345_mixer_t *mixer = &sb->mixer_sbpro; int c; double out_l = 0.0, out_r = 0.0; + int32_t *opl_buf, *opl2_buf; if (sb->opl_enabled) { if (sb->dsp.sb_type == SBPRO) { - opl2_update(&sb->opl); - opl2_update(&sb->opl2); + opl_buf = sb->opl.update(sb->opl.priv); + opl2_buf = sb->opl2.update(sb->opl2.priv); } else - opl3_update(&sb->opl); + opl_buf = sb->opl.update(sb->opl.priv); } sb_dsp_update(&sb->dsp); @@ -283,11 +285,11 @@ sb_get_buffer_sbpro(int32_t *buffer, int len, void *p) if (sb->dsp.sb_type == SBPRO) { /* Two chips for LEFT and RIGHT channels. Each chip stores data into the LEFT channel only (no sample alternating.) */ - out_l = (((double) sb->opl.buffer[c]) * mixer->fm_l) * 0.7171630859375; - out_r = (((double) sb->opl2.buffer[c]) * mixer->fm_r) * 0.7171630859375; + out_l = (((double) opl_buf[c]) * mixer->fm_l) * 0.7171630859375; + out_r = (((double) opl2_buf[c]) * mixer->fm_r) * 0.7171630859375; } else { - out_l = (((double) sb->opl.buffer[c]) * mixer->fm_l) * 0.7171630859375; - out_r = (((double) sb->opl.buffer[c + 1]) * mixer->fm_r) * 0.7171630859375; + out_l = (((double) opl_buf[c]) * mixer->fm_l) * 0.7171630859375; + out_r = (((double) opl_buf[c + 1]) * mixer->fm_r) * 0.7171630859375; } } @@ -311,9 +313,9 @@ sb_get_buffer_sbpro(int32_t *buffer, int len, void *p) sb->pos = 0; if (sb->opl_enabled) { - sb->opl.pos = 0; + sb->opl.reset_buffer(sb->opl.priv); if (sb->dsp.sb_type != SBPRO) - sb->opl2.pos = 0; + sb->opl2.reset_buffer(sb->opl2.priv); } sb->dsp.pos = 0; @@ -345,9 +347,10 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p) int32_t in_l, in_r; double out_l = 0.0, out_r = 0.0; double bass_treble; + int32_t *opl_buf = NULL; if (sb->opl_enabled) - opl3_update(&sb->opl); + opl_buf = sb->opl.update(sb->opl.priv); if (sb->dsp.sb_type > SB16) emu8k_update(&sb->emu8k); @@ -361,8 +364,8 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p) c_emu8k = ((((c / 2) * 44100) / 48000) * 2); if (sb->opl_enabled) { - out_l = ((double) sb->opl.buffer[c]) * mixer->fm_l * 0.7171630859375; - out_r = ((double) sb->opl.buffer[c + 1]) * mixer->fm_r * 0.7171630859375; + out_l = ((double) opl_buf[c]) * mixer->fm_l * 0.7171630859375; + out_r = ((double) opl_buf[c + 1]) * mixer->fm_r * 0.7171630859375; } if (sb->dsp.sb_type > SB16) { @@ -456,7 +459,7 @@ sb_get_buffer_sb16_awe32(int32_t *buffer, int len, void *p) sb->pos = 0; if (sb->opl_enabled) - sb->opl.pos = 0; + sb->opl.reset_buffer(sb->opl.priv); sb->dsp.pos = 0; @@ -1088,13 +1091,13 @@ sb_mcv_write(int port, uint8_t val, void *p) addr = sb_mcv_addr[sb->pos_regs[4] & 7]; if (sb->opl_enabled) { io_removehandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } /* DSP I/O handler is activated in sb_dsp_setaddr */ sb_dsp_setaddr(&sb->dsp, 0); @@ -1106,13 +1109,13 @@ sb_mcv_write(int port, uint8_t val, void *p) if (sb->opl_enabled) { io_sethandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } /* DSP I/O handler is activated in sb_dsp_setaddr */ sb_dsp_setaddr(&sb->dsp, addr); @@ -1152,17 +1155,17 @@ sb_pro_mcv_write(int port, uint8_t val, void *p) addr = (sb->pos_regs[2] & 0x20) ? 0x220 : 0x240; io_removehandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, @@ -1176,16 +1179,16 @@ sb_pro_mcv_write(int port, uint8_t val, void *p) addr = (sb->pos_regs[2] & 0x20) ? 0x220 : 0x240; io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, sb->opl.priv); io_sethandler(addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, @@ -1208,13 +1211,13 @@ sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) switch (ld) { case 0: /* Audio */ io_removehandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL, sb_ct1745_mixer_write, NULL, NULL, @@ -1224,9 +1227,9 @@ sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) if (addr) { sb->opl_pnp_addr = 0; io_removehandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb_dsp_setaddr(&sb->dsp, 0); @@ -1240,13 +1243,13 @@ sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) addr = config->io[0].base; if (addr != ISAPNP_IO_DISABLED) { io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL, sb_ct1745_mixer_write, NULL, NULL, @@ -1263,9 +1266,9 @@ sb_16_pnp_config_changed(uint8_t ld, isapnp_device_config_t *config, void *priv) if (addr != ISAPNP_IO_DISABLED) { sb->opl_pnp_addr = addr; io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } val = config->irq[0].irq; @@ -1349,7 +1352,7 @@ sb_1_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl2_init(&sb->opl); + fm_driver_get(FM_YM3812, &sb->opl); sb_dsp_init(&sb->dsp, SB1, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1358,13 +1361,13 @@ sb_1_init(const device_t *info) /* DSP I/O handler is activated in sb_dsp_setaddr */ if (sb->opl_enabled) { io_sethandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->cms_enabled = 1; @@ -1397,7 +1400,7 @@ sb_15_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl2_init(&sb->opl); + fm_driver_get(FM_YM3812, &sb->opl); sb_dsp_init(&sb->dsp, SB15, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1406,13 +1409,13 @@ sb_15_init(const device_t *info) /* DSP I/O handler is activated in sb_dsp_setaddr */ if (sb->opl_enabled) { io_sethandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->cms_enabled = device_get_config_int("cms"); @@ -1445,7 +1448,7 @@ sb_mcv_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl2_init(&sb->opl); + fm_driver_get(FM_YM3812, &sb->opl); sb_dsp_init(&sb->dsp, SB15, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, 0); @@ -1490,7 +1493,7 @@ sb_2_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl2_init(&sb->opl); + fm_driver_get(FM_YM3812, &sb->opl); sb_dsp_init(&sb->dsp, SB2, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1504,18 +1507,18 @@ sb_2_init(const device_t *info) if (sb->opl_enabled) { if (!sb->cms_enabled) { io_sethandler(addr, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.write); } io_sethandler(addr + 8, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.write); io_sethandler(0x0388, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.write); } if (sb->cms_enabled) { @@ -1550,8 +1553,8 @@ sb_pro_v1_opl_read(uint16_t port, void *priv) cycles -= ((int) (isa_timing * 8)); - (void) opl2_read(port, &sb->opl2); // read, but ignore - return (opl2_read(port, &sb->opl)); + (void) sb->opl2.read(port, sb->opl2.priv); // read, but ignore + return (sb->opl.read(port, sb->opl.priv)); } static void @@ -1559,8 +1562,8 @@ sb_pro_v1_opl_write(uint16_t port, uint8_t val, void *priv) { sb_t *sb = (sb_t *) priv; - opl2_write(port, val, &sb->opl); - opl2_write(port, val, &sb->opl2); + sb->opl.write(port, val, sb->opl.priv); + sb->opl2.write(port, val, sb->opl2.priv); } static void * @@ -1578,10 +1581,10 @@ sb_pro_v1_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) { - opl2_init(&sb->opl); - opl_set_do_cycles(&sb->opl, 0); - opl2_init(&sb->opl2); - opl_set_do_cycles(&sb->opl2, 0); + fm_driver_get(FM_YM3812, &sb->opl); + sb->opl.set_do_cycles(sb->opl.priv, 0); + fm_driver_get(FM_YM3812, &sb->opl2); + sb->opl2.set_do_cycles(sb->opl2.priv, 0); } sb_dsp_init(&sb->dsp, SBPRO, SB_SUBTYPE_DEFAULT, sb); @@ -1592,13 +1595,13 @@ sb_pro_v1_init(const device_t *info) /* DSP I/O handler is activated in sb_dsp_setaddr */ if (sb->opl_enabled) { io_sethandler(addr, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 2, 0x0002, - opl2_read, NULL, NULL, - opl2_write, NULL, NULL, - &sb->opl2); + sb->opl2.read, NULL, NULL, + sb->opl2.write, NULL, NULL, + sb->opl2.priv); io_sethandler(addr + 8, 0x0002, sb_pro_v1_opl_read, NULL, NULL, sb_pro_v1_opl_write, NULL, NULL, @@ -1638,7 +1641,7 @@ sb_pro_v2_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SBPRO2, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1648,17 +1651,17 @@ sb_pro_v2_init(const device_t *info) /* DSP I/O handler is activated in sb_dsp_setaddr */ if (sb->opl_enabled) { io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->mixer_enabled = 1; @@ -1687,7 +1690,7 @@ sb_pro_mcv_init(const device_t *info) memset(sb, 0, sizeof(sb_t)); sb->opl_enabled = 1; - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SBPRO2, SB_SUBTYPE_DEFAULT, sb); sb_ct1345_mixer_reset(sb); @@ -1713,7 +1716,7 @@ sb_pro_compat_init(const device_t *info) sb_t *sb = malloc(sizeof(sb_t)); memset(sb, 0, sizeof(sb_t)); - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SBPRO2, SB_SUBTYPE_DEFAULT, sb); sb_ct1345_mixer_reset(sb); @@ -1740,7 +1743,7 @@ sb_16_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1751,17 +1754,17 @@ sb_16_init(const device_t *info) if (sb->opl_enabled) { io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->mixer_enabled = 1; @@ -1792,7 +1795,7 @@ sb_16_pnp_init(const device_t *info) memset(sb, 0x00, sizeof(sb_t)); sb->opl_enabled = 1; - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); sb_ct1745_mixer_reset(sb); @@ -1835,7 +1838,7 @@ sb_16_compat_init(const device_t *info) sb_t *sb = malloc(sizeof(sb_t)); memset(sb, 0, sizeof(sb_t)); - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); sb_ct1745_mixer_reset(sb); @@ -1900,7 +1903,7 @@ sb_awe32_init(const device_t *info) sb->opl_enabled = device_get_config_int("opl"); if (sb->opl_enabled) - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SBAWE32, SB_SUBTYPE_DEFAULT, sb); sb_dsp_setaddr(&sb->dsp, addr); @@ -1911,17 +1914,17 @@ sb_awe32_init(const device_t *info) if (sb->opl_enabled) { io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); } sb->mixer_enabled = 1; @@ -1956,7 +1959,7 @@ sb_awe32_pnp_init(const device_t *info) memset(sb, 0x00, sizeof(sb_t)); sb->opl_enabled = 1; - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, ((info->local == 2) || (info->local == 3) || (info->local == 4)) ? SBAWE64 : SBAWE32, SB_SUBTYPE_DEFAULT, sb); sb_ct1745_mixer_reset(sb); diff --git a/src/sound/snd_wss.c b/src/sound/snd_wss.c index 8df4791c6..b4859e128 100644 --- a/src/sound/snd_wss.c +++ b/src/sound/snd_wss.c @@ -52,7 +52,7 @@ typedef struct wss_t { uint8_t config; ad1848_t ad1848; - opl_t opl; + fm_drv_t opl; int opl_enabled; uint8_t pos_regs[8]; @@ -81,14 +81,14 @@ wss_get_buffer(int32_t *buffer, int len, void *priv) wss_t *wss = (wss_t *) priv; int c; - opl3_update(&wss->opl); + int32_t *opl_buf = wss->opl.update(wss->opl.priv); ad1848_update(&wss->ad1848); for (c = 0; c < len * 2; c++) { - buffer[c] += wss->opl.buffer[c]; + buffer[c] += opl_buf[c]; buffer[c] += wss->ad1848.buffer[c] / 2; } - wss->opl.pos = 0; + wss->opl.reset_buffer(wss->opl.priv); wss->ad1848.pos = 0; } @@ -102,7 +102,7 @@ wss_init(const device_t *info) wss->opl_enabled = device_get_config_int("opl"); if (wss->opl_enabled) - opl3_init(&wss->opl); + fm_driver_get(FM_YMF262, &wss->opl); ad1848_init(&wss->ad1848, AD1848_TYPE_DEFAULT); @@ -111,9 +111,9 @@ wss_init(const device_t *info) if (wss->opl_enabled) io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &wss->opl); + wss->opl.read, NULL, NULL, + wss->opl.write, NULL, NULL, + wss->opl.priv); io_sethandler(addr, 0x0004, wss_read, NULL, NULL, @@ -150,9 +150,9 @@ ncr_audio_mca_write(int port, uint8_t val, void *priv) addr = ports[(wss->pos_regs[2] & 0x18) >> 3]; io_removehandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &wss->opl); + wss->opl.read, NULL, NULL, + wss->opl.write, NULL, NULL, + wss->opl.priv); io_removehandler(addr, 0x0004, wss_read, NULL, NULL, wss_write, NULL, NULL, @@ -169,9 +169,9 @@ ncr_audio_mca_write(int port, uint8_t val, void *priv) if (wss->opl_enabled) io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &wss->opl); + wss->opl.read, NULL, NULL, + wss->opl.write, NULL, NULL, + wss->opl.priv); io_sethandler(addr, 0x0004, wss_read, NULL, NULL, @@ -197,7 +197,7 @@ ncr_audio_init(const device_t *info) wss_t *wss = malloc(sizeof(wss_t)); memset(wss, 0, sizeof(wss_t)); - opl3_init(&wss->opl); + fm_driver_get(FM_YMF262, &wss->opl); ad1848_init(&wss->ad1848, AD1848_TYPE_DEFAULT); ad1848_setirq(&wss->ad1848, 7); diff --git a/src/sound/ymfm/CMakeLists.txt b/src/sound/ymfm/CMakeLists.txt index da02f8132..2041719ae 100644 --- a/src/sound/ymfm/CMakeLists.txt +++ b/src/sound/ymfm/CMakeLists.txt @@ -1 +1 @@ -add_library(ymfm STATIC ymfm_opl.cpp) \ No newline at end of file +add_library(ymfm STATIC ymfm_misc.cpp ymfm_opl.cpp ymfm_opm.cpp ymfm_opn.cpp ymfm_opq.cpp ymfm_opz.cpp ymfm_pcm.cpp ymfm_adpcm.cpp) \ No newline at end of file diff --git a/src/win/Makefile.mingw b/src/win/Makefile.mingw index 8c29a24a1..75b847dde 100644 --- a/src/win/Makefile.mingw +++ b/src/win/Makefile.mingw @@ -241,7 +241,7 @@ VPATH := $(EXPATH) . $(CODEGEN) minitrace cpu \ sio sound \ sound/munt sound/munt/c_interface sound/munt/sha1 \ sound/munt/srchelper sound/munt/srchelper/srctools/src \ - sound/resid-fp \ + sound/resid-fp sound/ymfm \ scsi video network network/slirp win ifeq ($(X64), y) TOOL_PREFIX := x86_64-w64-mingw32- @@ -670,7 +670,9 @@ PRINTOBJ := png.o prt_cpmap.o \ prt_escp.o prt_text.o prt_ps.o SNDOBJ := sound.o \ - snd_opl.o snd_opl_nuked.o \ + snd_opl.o snd_opl_nuked.o snd_opl_ymfm.o \ + ymfm_adpcm.o ymfm_misc.o ymfm_opl.o ymfm_opm.o \ + ymfm_opn.o ymfm_opq.o ymfm_opz.o ymfm_pcm.o ymfm_ssg.o \ snd_resid.o \ convolve.o convolve-sse.o envelope.o extfilt.o \ filter.o pot.o sid.o voice.o wave6581__ST.o \ From a6a7f0ae978a646bbbd144f0b3aacb9f4bd4c373 Mon Sep 17 00:00:00 2001 From: Adrien Moulin Date: Mon, 25 Jul 2022 20:30:52 +0200 Subject: [PATCH 28/32] ymfm cleanup --- src/sound/snd_opl_ymfm.cpp | 39 +++++++------------------------------- 1 file changed, 7 insertions(+), 32 deletions(-) diff --git a/src/sound/snd_opl_ymfm.cpp b/src/sound/snd_opl_ymfm.cpp index f82d64822..6acbf79f3 100644 --- a/src/sound/snd_opl_ymfm.cpp +++ b/src/sound/snd_opl_ymfm.cpp @@ -147,28 +147,6 @@ virtual void generate_resampled(int32_t *data, uint32_t num_samples) override } } - - /*virtual void generate_resampled(int32_t *data, uint32_t num_samples) override - { - for (uint32_t i = 0; i < num_samples; i++) { - while (m_samplecnt >= m_rateratio) { - m_oldsamples[0] = m_samples[0]; - m_oldsamples[1] = m_samples[1]; - generate(m_samples, 1); - m_samplecnt -= m_rateratio; - } - - *data++ = ((int32_t) ((m_oldsamples[0] * (m_rateratio - m_samplecnt) - + m_samples[0] * m_samplecnt) - / m_rateratio)) / 2; - *data++ = ((int32_t) ((m_oldsamples[1] * (m_rateratio - m_samplecnt) - + m_samples[1] * m_samplecnt) - / m_rateratio)) / 2; - - m_samplecnt += 1 << RSM_FRAC; - } - }*/ - virtual int32_t *update() override { if (m_buf_pos >= sound_pos_global) @@ -236,21 +214,18 @@ extern "C" #include <86box/snd_opl.h> #ifdef ENABLE_OPL_LOG -static int opl_do_log = ENABLE_OPL_LOG; static void -opl_log(const char *fmt, ...) +ymfm_log(const char *fmt, ...) { va_list ap; - if (opl_do_log) { - va_start(ap, fmt); - pclog_ex(fmt, ap); - va_end(ap); - } + va_start(ap, fmt); + pclog_ex(fmt, ap); + va_end(ap); } #else -# define opl_log(fmt, ...) +# define ymfm_log(fmt, ...) #endif static void * @@ -299,7 +274,7 @@ ymfm_drv_read(uint16_t port, void *priv) uint8_t ret = drv->read(port); drv->update(); - opl_log("YMFM read port %04x, status = %02x\n", port, ret); + ymfm_log("YMFM read port %04x, status = %02x\n", port, ret); return ret; } @@ -307,7 +282,7 @@ static void ymfm_drv_write(uint16_t port, uint8_t val, void *priv) { YMFMChipBase *drv = (YMFMChipBase *) priv; - opl_log("YMFM write port %04x value = %02x\n", port, val); + ymfm_log("YMFM write port %04x value = %02x\n", port, val); drv->write(port, val); drv->update(); } From 4e02b18315e664600127e416e753ceba2c81c6a8 Mon Sep 17 00:00:00 2001 From: Adrien Moulin Date: Mon, 25 Jul 2022 21:19:46 +0200 Subject: [PATCH 29/32] Fix build --- src/sound/snd_sb.c | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/sound/snd_sb.c b/src/sound/snd_sb.c index e1546acdc..16fcfe9d3 100644 --- a/src/sound/snd_sb.c +++ b/src/sound/snd_sb.c @@ -1245,17 +1245,17 @@ sb_16_reply_mca_write(int port, uint8_t val, void *p) if (addr) { io_removehandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_removehandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL, sb_ct1745_mixer_write, NULL, NULL, @@ -1303,16 +1303,16 @@ sb_16_reply_mca_write(int port, uint8_t val, void *p) if (addr) { io_sethandler(addr, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(addr + 8, 0x0002, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, - &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, + sb->opl.priv); io_sethandler(0x0388, 0x0004, - opl3_read, NULL, NULL, - opl3_write, NULL, NULL, &sb->opl); + sb->opl.read, NULL, NULL, + sb->opl.write, NULL, NULL, sb->opl.priv); io_sethandler(addr + 4, 0x0002, sb_ct1745_mixer_read, NULL, NULL, sb_ct1745_mixer_write, NULL, NULL, @@ -1940,7 +1940,7 @@ sb_16_reply_mca_init(const device_t *info) memset(sb, 0x00, sizeof(sb_t)); sb->opl_enabled = 1; - opl3_init(&sb->opl); + fm_driver_get(FM_YMF262, &sb->opl); sb_dsp_init(&sb->dsp, SB16, SB_SUBTYPE_DEFAULT, sb); sb_ct1745_mixer_reset(sb); From 7a4e7c0855eedc96deadadb9355e6f60daba6347 Mon Sep 17 00:00:00 2001 From: Adrien Moulin Date: Mon, 25 Jul 2022 22:04:48 +0200 Subject: [PATCH 30/32] ymfm: try to fix GCC11 build error --- src/sound/ymfm/ymfm.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sound/ymfm/ymfm.h b/src/sound/ymfm/ymfm.h index b983379b5..d9c3bca3b 100644 --- a/src/sound/ymfm/ymfm.h +++ b/src/sound/ymfm/ymfm.h @@ -44,6 +44,7 @@ #include #include #include +#include namespace ymfm { From 42cb69fd66b82e03b39f4ba73108ec6ed0a19684 Mon Sep 17 00:00:00 2001 From: richardg867 Date: Mon, 25 Jul 2022 18:29:11 -0300 Subject: [PATCH 31/32] Jenkins: Move extra-cmake-modules to platform-independent dependencies --- .ci/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/build.sh b/.ci/build.sh index 3ac28eb8a..60917f43e 100644 --- a/.ci/build.sh +++ b/.ci/build.sh @@ -590,7 +590,7 @@ else esac # Establish general dependencies. - pkgs="cmake ninja-build pkg-config git wget p7zip-full wayland-protocols tar gzip file appstream" + pkgs="cmake ninja-build pkg-config git wget p7zip-full extra-cmake-modules wayland-protocols tar gzip file appstream" if [ "$(dpkg --print-architecture)" = "$arch_deb" ] then pkgs="$pkgs build-essential" @@ -608,7 +608,7 @@ else fi # Establish architecture-specific dependencies we don't want listed on the readme... - pkgs="$pkgs linux-libc-dev:$arch_deb extra-cmake-modules:$arch_deb qttools5-dev:$arch_deb qtbase5-private-dev:$arch_deb" + pkgs="$pkgs linux-libc-dev:$arch_deb qttools5-dev:$arch_deb qtbase5-private-dev:$arch_deb" # ...and the ones we do want listed. Non-dev packages fill missing spots on the list. libpkgs="" From a06491813389b11b9f45be6020532a557be93d8a Mon Sep 17 00:00:00 2001 From: OBattler Date: Tue, 26 Jul 2022 00:18:28 +0200 Subject: [PATCH 32/32] WIN_WRITE_MULTIPLE now correctly fails with command aborted if ide->blocksize = 0, instead of a fatal(). --- src/disk/hdc_ide.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/disk/hdc_ide.c b/src/disk/hdc_ide.c index 55f60c8e3..b49e2e926 100644 --- a/src/disk/hdc_ide.c +++ b/src/disk/hdc_ide.c @@ -1682,8 +1682,7 @@ ide_writeb(uint16_t addr, uint8_t val, void *priv) return; case WIN_WRITE_MULTIPLE: - if (!ide->blocksize && (ide->type != IDE_ATAPI)) - fatal("Write_MULTIPLE - blocksize = 0\n"); + /* Fatal removed for the same reason as for WIN_READ_MULTIPLE. */ ide->blockcount = 0; /* Turn on the activity indicator *here* so that it gets turned on less times. */ @@ -2410,7 +2409,13 @@ ide_callback(void *priv) return; case WIN_WRITE_MULTIPLE: - if (ide->type == IDE_ATAPI) + /* According to the official ATA reference: + + If the Read Multiple command is attempted before the Set Multiple Mode + command has been executed or when Read Multiple commands are + disabled, the Read Multiple operation is rejected with an Aborted Com- + mand error. */ + if ((ide->type == IDE_ATAPI) || !ide->blocksize) goto abort_cmd; if (!ide->lba && (ide->cfg_spt == 0)) goto id_not_found;