From c807d03b054c653ea8ffb4d0a6806c2dbded86cb Mon Sep 17 00:00:00 2001 From: Javier Date: Tue, 8 Feb 2022 02:53:21 +0100 Subject: emu8000: save/load RAM contents in saved states, fix PDM config names --- Emu8000.cpp | 100 +- README.md | 2 +- emu8k.c | 4883 ++++++++++++++++++++++++++-------------------------- emu8k.h | 2 +- emu8k_internal.h | 5 +- scripts/disable.sh | 2 + scripts/enable.sh | 2 +- 7 files changed, 2505 insertions(+), 2491 deletions(-) diff --git a/Emu8000.cpp b/Emu8000.cpp index 8cf471b..0497982 100644 --- a/Emu8000.cpp +++ b/Emu8000.cpp @@ -84,7 +84,7 @@ typedef PCMOutWin PCMOutBackend; #define EMU_DEFAULT_SAMPLE_RATE 44100 /* Hz */ #define EMU_NUM_CHANNELS 2 -#define EMU_DEFAULT_ONBOARD_RAM (8 * 1024) /* KiB */ +#define EMU_DEFAULT_RAM_SIZE (8 * _1M) enum { EMU_PORT_DATA0 = 0, @@ -114,8 +114,8 @@ typedef struct { RTIOPORT uPort; /** Sample rate for PCM output. */ uint16_t uSampleRate; - /** Size of onboard RAM in KiB. */ - uint16_t uOnboardRAM; + /** Size of onboard RAM. */ + uint32_t uRAMSize; /** Path to find ROM file. */ R3PTRTYPE(char *) pszROMFile; /** Device for PCM output. */ @@ -138,10 +138,12 @@ typedef struct { /** (Virtual clock) timestamp of last frame rendered. */ uint64_t tmLastRender; - /** To protect access to opl3_chip from the render thread and main thread. */ - RTCRITSECT critSect; + /** To protect access to emu8k_t from the render thread and main thread. */ + PDMCRITSECT critSect; /** Handle to emu8k. */ R3PTRTYPE(emu8k_t*) emu; + /** Onboard RAM. */ + R3PTRTYPE(void*) ram; /** Contents of ROM file. */ R3PTRTYPE(void*) rom; @@ -169,8 +171,6 @@ DECLINLINE(size_t) emuCalculateBytesFromFrames(PEMUSTATE pThis, uint64_t frames) return frames * sizeof(uint16_t) * EMU_NUM_CHANNELS; } - - /** * The render thread calls into the emulator to render audio frames, and then pushes them * on the PCM output device. @@ -200,10 +200,10 @@ static DECLCALLBACK(int) emuRenderThread(RTTHREAD ThreadSelf, void *pvUser) && ASMAtomicReadU64(&pThis->tmLastWrite) + EMU_RENDER_SUSPEND_TIMEOUT >= RTTimeSystemMilliTS()) { Log9(("rendering %lld frames\n", buf_frames)); - RTCritSectEnter(&pThis->critSect); + PDMDevHlpCritSectEnter(pDevIns, &pThis->critSect, VERR_SEM_BUSY); emu8k_render(pThis->emu, buf, buf_frames); pThis->tmLastRender = PDMDevHlpTMTimeVirtGetNano(pDevIns); - RTCritSectLeave(&pThis->critSect); + PDMDevHlpCritSectLeave(pDevIns, &pThis->critSect); Log9(("writing %lld frames\n", buf_frames)); @@ -304,7 +304,7 @@ static DECLCALLBACK(VBOXSTRICTRC) emuIoPortRead(PPDMDEVINS pDevIns, void *pvUser PEMUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PEMUSTATE); - RTCritSectEnter(&pThis->critSect); + PDMDevHlpCritSectEnter(pDevIns, &pThis->critSect, VERR_SEM_BUSY); uint64_t frames_since_last_render = emuCalculateFramesFromNano(pThis, PDMDevHlpTMTimeVirtGetNano(pDevIns) - pThis->tmLastRender); emu8k_update_virtual_sample_count(pThis->emu, frames_since_last_render); @@ -324,7 +324,7 @@ static DECLCALLBACK(VBOXSTRICTRC) emuIoPortRead(PPDMDEVINS pDevIns, void *pvUser break; } - RTCritSectLeave(&pThis->critSect); + PDMDevHlpCritSectLeave(pDevIns, &pThis->critSect); Log9Func(("read port 0x%X (%u): %#04x\n", port, cb, *pu32)); @@ -342,7 +342,7 @@ static DECLCALLBACK(VBOXSTRICTRC) emuIoPortWrite(PPDMDEVINS pDevIns, void *pvUse PEMUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PEMUSTATE); - RTCritSectEnter(&pThis->critSect); + PDMDevHlpCritSectEnter(pDevIns, &pThis->critSect, VERR_SEM_BUSY); switch (cb) { case sizeof(uint8_t): @@ -359,7 +359,7 @@ static DECLCALLBACK(VBOXSTRICTRC) emuIoPortWrite(PPDMDEVINS pDevIns, void *pvUse break; } - RTCritSectLeave(&pThis->critSect); + PDMDevHlpCritSectLeave(pDevIns, &pThis->critSect); emuWakeRenderThread(pDevIns); @@ -374,10 +374,12 @@ static DECLCALLBACK(VBOXSTRICTRC) emuIoPortWrite(PPDMDEVINS pDevIns, void *pvUse static DECLCALLBACK(int) emuR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) { PEMUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PEMUSTATE); - PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; - // TODO: Save contents of ROM & RAM? - RT_NOREF(pSSM, pThis, pHlp); + pHlp->pfnSSMPutU32(pSSM, pThis->uRAMSize); + pHlp->pfnSSMPutMem(pSSM, pThis->ram, pThis->uRAMSize); + + // TODO Should save the rest of the device state, too. return 0; } @@ -388,13 +390,20 @@ static DECLCALLBACK(int) emuR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) static DECLCALLBACK(int) emuR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) { PEMUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PEMUSTATE); - PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); - // TODO - RT_NOREF(pSSM, pThis, pHlp); + uint32_t uRAMSize = pThis->uRAMSize; + pHlp->pfnSSMGetU32(pSSM, &uRAMSize); + + if (uRAMSize == pThis->uRAMSize) { + pHlp->pfnSSMGetMem(pSSM, pThis->ram, uRAMSize); + } else { + LogWarn(("emu8000#%d: RAM size has changed, ignoring saved RAM contents\n", pDevIns->iInstance)); + pHlp->pfnSSMSkip(pSSM, uRAMSize); + } pThis->tmLastWrite = RTTimeSystemMilliTS(); @@ -414,10 +423,10 @@ static DECLCALLBACK(void) emuR3Reset(PPDMDEVINS pDevIns) { PEMUSTATE pThis = PDMDEVINS_2_DATA(pDevIns, PEMUSTATE); - RTCritSectEnter(&pThis->critSect); + PDMDevHlpCritSectEnter(pDevIns, &pThis->critSect, VERR_SEM_BUSY); emu8k_reset(pThis->emu); pThis->tmLastRender = PDMDevHlpTMTimeVirtGetNano(pDevIns); - RTCritSectLeave(&pThis->critSect); + PDMDevHlpCritSectLeave(pDevIns, &pThis->critSect); } /** @@ -449,17 +458,17 @@ static DECLCALLBACK(int) emuR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGM Assert(iInstance == 0); // Validate and read the configuration - PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "Port|OnboardRAM|ROMFile|OutDevice|SampleRate", ""); + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "Port|RamSize|RomFile|OutDevice|SampleRate", ""); rc = pHlp->pfnCFGMQueryPortDef(pCfg, "Port", &pThis->uPort, EMU_DEFAULT_IO_BASE); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"Port\" from the config")); - rc = pHlp->pfnCFGMQueryU16Def(pCfg, "Port", &pThis->uOnboardRAM, EMU_DEFAULT_ONBOARD_RAM); + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "RamSize", &pThis->uRAMSize, EMU_DEFAULT_RAM_SIZE); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"OnboardRAM\" from the config")); - rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "ROMFile", &pThis->pszROMFile); + rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "RomFile", &pThis->pszROMFile); if (RT_FAILURE(rc)) return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to query \"RomFile\" from the config")); @@ -476,39 +485,43 @@ static DECLCALLBACK(int) emuR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGM uint64_t uROMSize; rc = RTFileOpen(&fROM, pThis->pszROMFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); if (RT_FAILURE(rc)) - return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to open ROMFile")); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("emu8000: Failed to open ROMFile")); rc = RTFileQuerySize(fROM, &uROMSize); if (RT_FAILURE(rc) || uROMSize != _1M) - return PDMDEV_SET_ERROR(pDevIns, rc, N_("ROMFile is not of correct size (expecting 1MiB file)")); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("emu8000: ROMFile is not of correct size (expecting 1MiB file)")); - pThis->rom = RTMemAlloc(uROMSize); + pThis->rom = PDMDevHlpMMHeapAlloc(pDevIns, uROMSize); AssertPtrReturn(pThis->rom, VERR_NO_MEMORY); rc = RTFileRead(fROM, pThis->rom, uROMSize, NULL); if (RT_FAILURE(rc)) - return PDMDEV_SET_ERROR(pDevIns, rc, N_("Failed to read ROMFile")); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("emu8000: Failed to read ROMFile")); + + // Allocate RAM + pThis->ram = PDMDevHlpMMHeapAllocZ(pDevIns, pThis->uRAMSize); + AssertPtrReturn(pThis->ram, VERR_NO_MEMORY); // Create the device - pThis->emu = emu8k_alloc(pThis->rom, pThis->uOnboardRAM); + pThis->emu = emu8k_alloc(pThis->rom, pThis->ram, pThis->uRAMSize); AssertPtrReturn(pThis->emu, VERR_NO_MEMORY); - // Initialize the device - emuR3Reset(pDevIns); - - /* Initialize now the buffer that will be used by the render thread. */ + // Initialize now the buffer that will be used by the render thread. size_t renderBlockSize = emuCalculateBytesFromFrames(pThis, emuCalculateFramesFromMilli(pThis, EMU_RENDER_BLOCK_TIME)); pThis->pbRenderBuf = (uint8_t *) RTMemAlloc(renderBlockSize); AssertPtrReturn(pThis->pbRenderBuf, VERR_NO_MEMORY); - /* Prepare the render thread, but not create it yet. */ + // Prepare the render thread, but not create it yet. pThis->fShutdown = false; pThis->fStopped = false; pThis->hRenderThread = NIL_RTTHREAD; pThis->tmLastWrite = 0; - rc = RTCritSectInit(&pThis->critSect); + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->critSect, RT_SRC_POS, "emu8000#%d", iInstance); AssertRCReturn(rc, rc); + // Initialize the device. + emuR3Reset(pDevIns); + // Register IO ports. const RTIOPORT numPorts = sizeof(uint32_t); // Each port is a "doubleword" or at least 2 words. rc = PDMDevHlpIoPortCreateFlagsAndMap(pDevIns, pThis->uPort + EMU_PORT_DATA0, numPorts, IOM_IOPORT_F_ABS, @@ -521,13 +534,11 @@ static DECLCALLBACK(int) emuR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGM emuIoPortWrite, emuIoPortRead, "EMU8000 Data3/Ptr", NULL, &pThis->hIoPorts[3]); AssertRCReturn(rc, rc); - /* - * Register saved state. - */ + // Register saved state. rc = PDMDevHlpSSMRegister(pDevIns, EMU_SAVED_STATE_VERSION, sizeof(*pThis), emuR3SaveExec, emuR3LoadExec); AssertRCReturn(rc, rc); - LogRel(("emu8000#%i: Using %hu KiB of onboard RAM\n", iInstance, pThis->uOnboardRAM)); + LogRel(("emu8000#%i: Using %hu KiB of onboard RAM\n", iInstance, pThis->uRAMSize / _1K)); LogRel(("emu8000#%i: Configured on ports 0x%X-0x%X, 0x%X-0x%X, 0x%X-0x%X\n", iInstance, pThis->uPort + EMU_PORT_DATA0, pThis->uPort + EMU_PORT_DATA0 + numPorts - 1, @@ -548,7 +559,7 @@ static DECLCALLBACK(int) emuR3Destruct(PPDMDEVINS pDevIns) emuStopRenderThread(pDevIns, true); if (pThis->pbRenderBuf) { - RTMemFree(pThis->pbRenderBuf); + PDMDevHlpMMHeapFree(pDevIns, pThis->pbRenderBuf); pThis->pbRenderBuf = NULL; } @@ -562,11 +573,18 @@ static DECLCALLBACK(int) emuR3Destruct(PPDMDEVINS pDevIns) pThis->emu = NULL; } + if (pThis->ram) { + PDMDevHlpMMHeapFree(pDevIns, pThis->ram); + pThis->ram = NULL; + } + if (pThis->rom) { - RTMemFree(pThis->rom); + PDMDevHlpMMHeapFree(pDevIns, pThis->rom); pThis->rom = NULL; } + PDMDevHlpCritSectDelete(pDevIns, &pThis->critSect); + return VINF_SUCCESS; } diff --git a/README.md b/README.md index 2f70ecd..35450b2 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ VBoxManage setextradata "$vm" VBoxInternal/Devices/mpu401/0/Trusted 1 # To enable the Adlib device VBoxManage setextradata "$vm" VBoxInternal/Devices/adlib/0/Trusted 1 # To enable the EMU8000 device -VBoxManage setextradata "$vm" VBoxInternal/Devices/emu8000/0/Config/ROMFile "$HOME/.pcem/roms/awe32.raw" +VBoxManage setextradata "$vm" VBoxInternal/Devices/emu8000/0/Config/RomFile "$HOME/.pcem/roms/awe32.raw" # Optional: to enable the Adlib device on the default SB16 ports too VBoxManage setextradata "$vm" VBoxInternal/Devices/adlib/0/Config/MirrorPort "0x220" diff --git a/emu8k.c b/emu8k.c index 194ff58..b41cf82 100644 --- a/emu8k.c +++ b/emu8k.c @@ -1,2443 +1,2440 @@ -/* - * PCem - IBM PC emulator - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/* - * Portions: - * VMusic - a VirtualBox extension pack with various music devices - * Copyright (C) 2022 Javier S. Pedro - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -#include - -#define LOG_ENABLED 1 -#define LOG_ENABLE_FLOW 1 -#define LOG_GROUP LOG_GROUP_DEV_SB16 -#include -#include -#include -#include - -#include "emu8k_internal.h" - -#define pclog(...) LogFlow((__VA_ARGS__)) - -#if !defined FILTER_INITIAL && !defined FILTER_MOOG && !defined FILTER_CONSTANT -//#define FILTER_INITIAL -#define FILTER_MOOG -//#define FILTER_CONSTANT -#endif - -#if !defined RESAMPLER_LINEAR && !defined RESAMPLER_CUBIC -//#define RESAMPLER_LINEAR -#define RESAMPLER_CUBIC -#endif - -//#define EMU8K_DEBUG_REGISTERS - -char* PORT_NAMES[][8] = - { - /* Data 0 ( 0x620/0x622) */ - { "AWE_CPF", - "AWE_PTRX", - "AWE_CVCF", - "AWE_VTFT", - "Unk-620-4", - "Unk-620-5", - "AWE_PSST", - "AWE_CSL", - }, - /* Data 1 0xA20 */ - { "AWE_CCCA", - 0, - /* - "AWE_HWCF4" - "AWE_HWCF5" - "AWE_HWCF6" - "AWE_HWCF7" - "AWE_SMALR" - "AWE_SMARR" - "AWE_SMALW" - "AWE_SMARW" - "AWE_SMLD" - "AWE_SMRD" - "AWE_WC" - "AWE_HWCF1" - "AWE_HWCF2" - "AWE_HWCF3" - */ - 0,//"AWE_INIT1", - 0,//"AWE_INIT3", - "AWE_ENVVOL", - "AWE_DCYSUSV", - "AWE_ENVVAL", - "AWE_DCYSUS", - }, - /* Data 2 0xA22 */ - { "AWE_CCCA", - 0, - 0,//"AWE_INIT2", - 0,//"AWE_INIT4", - "AWE_ATKHLDV", - "AWE_LFO1VAL", - "AWE_ATKHLD", - "AWE_LFO2VAL", - }, - /* Data 3 0xE20 */ - { "AWE_IP", - "AWE_IFATN", - "AWE_PEFE", - "AWE_FMMOD", - "AWE_TREMFRQ", - "AWE_FM2FRQ2", - 0, - 0, - }, - }; - -enum -{ - ENV_STOPPED = 0, - ENV_DELAY = 1, - ENV_ATTACK = 2, - ENV_HOLD = 3, - //ENV_DECAY = 4, - ENV_SUSTAIN = 5, - //ENV_RELEASE = 6, - ENV_RAMP_DOWN = 7, - ENV_RAMP_UP = 8 -}; - -static int random_helper = 0; -static int dmareadbit = 0; -static int dmawritebit = 0; - - -/* cubic and linear tables resolution. Note: higher than 10 does not improve the result. */ -#define CUBIC_RESOLUTION_LOG 10 -#define CUBIC_RESOLUTION (1<> 15 to move back to +/-1 range). */ -static int32_t lfotable[65536]; -/* Table to transform the speed parameter to emu8k_mem_internal_t range. */ -static int64_t lfofreqtospeed[256]; - -/* LFO used for the chorus. a sine wave.(signed 16bits with 32768 max int. >> 15 to move back to +/-1 range). */ -static double chortable[65536]; - -static const int REV_BUFSIZE_STEP = 242; - -/* These lines come from the awe32faq, describing the NRPN control for the initial filter - * where it describes a linear increment filter instead of an octave-incremented one. - * NRPN LSB 21 (Initial Filter Cutoff) - * Range : [0, 127] - * Unit : 62Hz - * Filter cutoff from 100Hz to 8000Hz - - * This table comes from the awe32faq, describing the NRPN control for the filter Q. - * I don't know if is meant to be interpreted as the actual measured output of the - * filter or what. Especially, I don't understand the "low" and "high" ranges. - * What is otherwise documented is that the Q ranges from 0dB to 24dB and the attenuation - * is half of the Q ( i.e. for 12dB Q, attenuate the input signal with -6dB) -Coeff Low Fc(Hz)Low Q(dB)High Fc(kHz)High Q(dB)DC Attenuation(dB) -* 0 92 5 Flat Flat -0.0 -* 1 93 6 8.5 0.5 -0.5 -* 2 94 8 8.3 1 -1.2 -* 3 95 10 8.2 2 -1.8 -* 4 96 11 8.1 3 -2.5 -* 5 97 13 8.0 4 -3.3 -* 6 98 14 7.9 5 -4.1 -* 7 99 16 7.8 6 -5.5 -* 8 100 17 7.7 7 -6.0 -* 9 100 19 7.5 9 -6.6 -* 10 100 20 7.4 10 -7.2 -* 11 100 22 7.3 11 -7.9 -* 12 100 23 7.2 13 -8.5 -* 13 100 25 7.1 15 -9.3 -* 14 100 26 7.1 16 -10.1 -* 15 100 28 7.0 18 -11.0 -* -* Attenuation as above, codified in amplitude.*/ -static int32_t filter_atten[16] = - { - 65536, 61869, 57079, 53269, 49145, 44820, 40877, 34792, 32845, 30653, 28607, - 26392, 24630, 22463, 20487, 18470 - }; - -/*Coefficients for the filters for a defined Q and cutoff.*/ -static int32_t filt_coeffs[16][256][3]; - -#define READ16_SWITCH(addr, var) switch ((addr) & 2) \ - { \ - case 0: ret = (var) & 0xffff; break; \ - case 2: ret = ((var) >> 16) & 0xffff; break; \ - } - -#define WRITE16_SWITCH(addr, var, val) switch ((addr) & 2) \ - { \ - case 0: var = (var & 0xffff0000) | (val); break; \ - case 2: var = (var & 0x0000ffff) | ((val) << 16); break; \ - } - -#ifdef EMU8K_DEBUG_REGISTERS -static uint32_t last_read = 0; -static uint32_t last_write = 0; -static uint32_t rep_count_r = 0; -static uint32_t rep_count_w = 0; - -# define READ16(addr, var) READ16_SWITCH(addr, var) \ - { \ - const char *name=0; \ - switch(addr&0xF02) \ - { \ - case 0x600: case 0x602: \ - name = PORT_NAMES[0][emu8k->cur_reg]; \ - break; \ - case 0xA00: \ - name = PORT_NAMES[1][emu8k->cur_reg]; \ - break; \ - case 0xA02: \ - name = PORT_NAMES[2][emu8k->cur_reg]; \ - break; \ - } \ - if (name == 0) \ - { \ - /*pclog("EMU8K READ %04X-%02X(%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice, emu8k->cur_voice,ret);*/ \ - } \ - else \ - { \ - pclog("EMU8K READ %s(%d) (%d): %04X\n",name, (addr&0x2), emu8k->cur_voice, ret); \ - }\ - } -# define WRITE16(addr, var, val) WRITE16_SWITCH(addr, var, val) \ - { \ - const char *name=0; \ - switch(addr&0xF02) \ - { \ - case 0x600: case 0x602: \ - name = PORT_NAMES[0][emu8k->cur_reg]; \ - break; \ - case 0xA00: \ - name = PORT_NAMES[1][emu8k->cur_reg]; \ - break; \ - case 0xA02: \ - name = PORT_NAMES[2][emu8k->cur_reg]; \ - break; \ - } \ - if (name == 0) \ - { \ - /*pclog("EMU8K WRITE %04X-%02X(%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice,emu8k->cur_voice, val);*/ \ - } \ - else \ - { \ - pclog("EMU8K WRITE %s(%d) (%d): %04X\n",name, (addr&0x2), emu8k->cur_voice,val); \ - }\ - } - -#else -# define READ16(addr, var) READ16_SWITCH(addr, var) -# define WRITE16(addr, var, val) WRITE16_SWITCH(addr, var, val) -#endif //EMU8K_DEBUG_REGISTERS - -static inline int16_t EMU8K_READ(emu8k_t* emu8k, uint32_t addr) -{ - const emu8k_mem_pointers_t addrmem = {{ addr }}; - return emu8k->ram_pointers[addrmem.hb_address][addrmem.lw_address]; -} - -static inline int16_t EMU8K_READ_INTERP_LINEAR(emu8k_t* emu8k, uint32_t int_addr, uint16_t fract) -{ - /* The interpolation in AWE32 used a so-called patented 3-point interpolation - * ( I guess some sort of spline having one point before and one point after). - * Also, it has the consequence that the playback is delayed by one sample. - * I simulate the "one sample later" than the address with addr+1 and addr+2 - * instead of +0 and +1 */ - int16_t dat1 = EMU8K_READ(emu8k, int_addr + 1); - int32_t dat2 = EMU8K_READ(emu8k, int_addr + 2); - dat1 += ((dat2 - (int32_t)dat1) * fract) >> 16; - return dat1; -} - -static inline int32_t EMU8K_READ_INTERP_CUBIC(emu8k_t* emu8k, uint32_t int_addr, uint16_t fract) -{ - /*Since there are four floats in the table for each fraction, the position is 16byte aligned. */ - fract >>= 16 - CUBIC_RESOLUTION_LOG; - fract <<= 2; - - /* TODO: I still have to verify how this works, but I think that - * the card could use two oscillators (usually 31 and 32) where it would - * be writing the OPL3 output, and to which, chorus and reverb could be applied to get - * those effects for OPL3 sounds.*/ -// if ((addr & EMU8K_FM_MEM_ADDRESS) == EMU8K_FM_MEM_ADDRESS) {} - - /* This is cubic interpolation. - * Not the same than 3-point interpolation, but a better approximation than linear - * interpolation. - * Also, it takes into account the "Note that the actual audio location is the point - * 1 word higher than this value due to interpolation offset". - * That's why the pointers are 0, 1, 2, 3 and not -1, 0, 1, 2 */ - int32_t dat2 = EMU8K_READ(emu8k, int_addr + 1); - const float* table = &cubic_table[fract]; - const int32_t dat1 = EMU8K_READ(emu8k, int_addr); - const int32_t dat3 = EMU8K_READ(emu8k, int_addr + 2); - const int32_t dat4 = EMU8K_READ(emu8k, int_addr + 3); - /* Note: I've ended using float for the table values to avoid some cases of integer overflow. */ - dat2 = dat1 * table[0] + dat2 * table[1] + dat3 * table[2] + dat4 * table[3]; - return dat2; -} - -static inline void EMU8K_WRITE(emu8k_t* emu8k, uint32_t addr, uint16_t val) -{ - addr &= EMU8K_MEM_ADDRESS_MASK; - if (!emu8k->ram || addr < EMU8K_RAM_MEM_START || addr >= EMU8K_FM_MEM_ADDRESS) - return; - - /* It looks like if an application writes to a memory part outside of the available - * amount on the card, it wraps, and opencubicplayer uses that to detect the amount - * of memory, as opposed to simply check at the address that it has just tried to write. */ - while (addr >= emu8k->ram_end_addr) - addr -= emu8k->ram_end_addr - EMU8K_RAM_MEM_START; - - emu8k->ram[addr - EMU8K_RAM_MEM_START] = val; -} - -uint16_t emu8k_inw(emu8k_t *emu8k, uint16_t addr) -{ - uint16_t ret = 0xffff; - -#ifdef EMU8K_DEBUG_REGISTERS - if (addr == 0xE22) - { - pclog("EMU8K READ POINTER: %d\n", - ((0x80 | ((random_helper + 1) & 0x1F)) << 8) | (emu8k->cur_reg << 5) | emu8k->cur_voice); - } - else if ((addr&0xF00) == 0x600) - { - /* These are automatically reported by READ16 */ - if (rep_count_r>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_r); - rep_count_r=0; - } - last_read=0; - } - else if ((addr&0xF00) == 0xA00 && emu8k->cur_reg == 0) - { - /* These are automatically reported by READ16 */ - if (rep_count_r>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_r); - rep_count_r=0; - } - last_read=0; - } - else if ((addr&0xF00) == 0xA00 && emu8k->cur_reg == 1) - { - uint32_t tmpz = ((addr&0xF00) << 16)|(emu8k->cur_reg<<5); - if (tmpz != last_read) - { - if (rep_count_r>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_r); - rep_count_r=0; - } - last_read=tmpz; - pclog("EMU8K READ RAM I/O or configuration or clock \n"); - } - //pclog("EMU8K READ %04X-%02X(%d/%d)\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice); - } - else if ((addr&0xF00) == 0xA00 && (emu8k->cur_reg == 2 || emu8k->cur_reg == 3)) - { - uint32_t tmpz = ((addr&0xF00) << 16); - if (tmpz != last_read) - { - if (rep_count_r>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_r); - rep_count_r=0; - } - last_read=tmpz; - pclog("EMU8K READ INIT \n"); - } - //pclog("EMU8K READ %04X-%02X(%d/%d)\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice); - } - else - { - uint32_t tmpz = (addr << 16)|(emu8k->cur_reg<<5)| emu8k->cur_voice; - if (tmpz != last_read) - { - char* name = 0; - uint16_t val = 0xBAAD; - if (addr == 0xA20) - { - name = PORT_NAMES[1][emu8k->cur_reg]; - switch (emu8k->cur_reg) - { - case 2: val = emu8k->init1[emu8k->cur_voice]; break; - case 3: val = emu8k->init3[emu8k->cur_voice]; break; - case 4: val = emu8k->voice[emu8k->cur_voice].envvol; break; - case 5: val = emu8k->voice[emu8k->cur_voice].dcysusv; break; - case 6: val = emu8k->voice[emu8k->cur_voice].envval; break; - case 7: val = emu8k->voice[emu8k->cur_voice].dcysus; break; - } - } - else if (addr == 0xA22) - { - name = PORT_NAMES[2][emu8k->cur_reg]; - switch (emu8k->cur_reg) - { - case 2: val = emu8k->init2[emu8k->cur_voice]; break; - case 3: val = emu8k->init4[emu8k->cur_voice]; break; - case 4: val = emu8k->voice[emu8k->cur_voice].atkhldv; break; - case 5: val = emu8k->voice[emu8k->cur_voice].lfo1val; break; - case 6: val = emu8k->voice[emu8k->cur_voice].atkhld; break; - case 7: val = emu8k->voice[emu8k->cur_voice].lfo2val; break; - } - } - else if (addr == 0xE20) - { - name = PORT_NAMES[3][emu8k->cur_reg]; - switch (emu8k->cur_reg) - { - case 0: val = emu8k->voice[emu8k->cur_voice].ip; break; - case 1: val = emu8k->voice[emu8k->cur_voice].ifatn; break; - case 2: val = emu8k->voice[emu8k->cur_voice].pefe; break; - case 3: val = emu8k->voice[emu8k->cur_voice].fmmod; break; - case 4: val = emu8k->voice[emu8k->cur_voice].tremfrq; break; - case 5: val = emu8k->voice[emu8k->cur_voice].fm2frq2;break; - case 6: val = 0xffff; break; - case 7: val = 0x1c | ((emu8k->id & 0x0002) ? 0xff02 : 0); break; - } - } - if (rep_count_r>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_r); - } - if (name == 0) - { - pclog("EMU8K READ %04X-%02X(%d/%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice,val); - } - else - { - pclog("EMU8K READ %s (%d): %04X\n",name,emu8k->cur_voice, val); - } - - rep_count_r=0; - last_read=tmpz; - } - rep_count_r++; - } -#endif // EMU8K_DEBUG_REGISTERS - - switch (addr & 0xF02) - { - case 0x600: - case 0x602: /*Data0. also known as BLASTER+0x400 and EMU+0x000 */ - switch (emu8k->cur_reg) - { - case 0: - READ16(addr, emu8k->voice[emu8k->cur_voice].cpf); - return ret; - - case 1: - READ16(addr, emu8k->voice[emu8k->cur_voice].ptrx); - return ret; - - case 2: - READ16(addr, emu8k->voice[emu8k->cur_voice].cvcf); - return ret; - - case 3: - READ16(addr, emu8k->voice[emu8k->cur_voice].vtft); - return ret; - - case 4: - READ16(addr, emu8k->voice[emu8k->cur_voice].unknown_data0_4); - return ret; - - case 5: - READ16(addr, emu8k->voice[emu8k->cur_voice].unknown_data0_5); - return ret; - - case 6: - READ16(addr, emu8k->voice[emu8k->cur_voice].psst); - return ret; - - case 7: - READ16(addr, emu8k->voice[emu8k->cur_voice].csl); - return ret; - } - break; - - case 0xA00: /*Data1. also known as BLASTER+0x800 and EMU+0x400 */ - switch (emu8k->cur_reg) - { - case 0: - READ16(addr, emu8k->voice[emu8k->cur_voice].ccca); - return ret; - - case 1: - switch (emu8k->cur_voice) - { - case 9: - READ16(addr, emu8k->hwcf4); - return ret; - case 10: - READ16(addr, emu8k->hwcf5); - return ret; - /* Actually, these two might be command words rather than registers, or some LFO position/buffer reset.*/ - case 13: - READ16(addr, emu8k->hwcf6); - return ret; - case 14: - READ16(addr, emu8k->hwcf7); - return ret; - - case 20: - READ16(addr, emu8k->smalr); - return ret; - case 21: - READ16(addr, emu8k->smarr); - return ret; - case 22: - READ16(addr, emu8k->smalw); - return ret; - case 23: - READ16(addr, emu8k->smarw); - return ret; - - case 26: - { - uint16_t val = emu8k->smld_buffer; - emu8k->smld_buffer = EMU8K_READ(emu8k, emu8k->smalr); - emu8k->smalr = (emu8k->smalr + 1) & EMU8K_MEM_ADDRESS_MASK; - return val; - } - - /*The EMU8000 PGM describes the return values of these registers as 'a VLSI error'*/ - case 29: /*Configuration Word 1*/ - return (emu8k->hwcf1 & 0xfe) | (emu8k->hwcf3 & 0x01); - case 30: /*Configuration Word 2*/ - return ((emu8k->hwcf2 >> 4) & 0x0e) | (emu8k->hwcf1 & 0x01) | ((emu8k->hwcf3 & 0x02) ? 0x10 : 0) | ((emu8k->hwcf3 & 0x04) ? 0x40 : 0) - | ((emu8k->hwcf3 & 0x08) ? 0x20 : 0) | ((emu8k->hwcf3 & 0x10) ? 0x80 : 0); - case 31: /*Configuration Word 3*/ - return emu8k->hwcf2 & 0x1f; - } - break; - - case 2: - return emu8k->init1[emu8k->cur_voice]; - - case 3: - return emu8k->init3[emu8k->cur_voice]; - - case 4: - return emu8k->voice[emu8k->cur_voice].envvol; - - case 5: - return emu8k->voice[emu8k->cur_voice].dcysusv; - - case 6: - return emu8k->voice[emu8k->cur_voice].envval; - - case 7: - return emu8k->voice[emu8k->cur_voice].dcysus; - } - break; - - case 0xA02: /*Data2. also known as BLASTER+0x802 and EMU+0x402 */ - switch (emu8k->cur_reg) - { - case 0: - READ16(addr, emu8k->voice[emu8k->cur_voice].ccca); - return ret; - - case 1: - switch (emu8k->cur_voice) - { - case 9: - READ16(addr, emu8k->hwcf4); - return ret; - case 10: - READ16(addr, emu8k->hwcf5); - return ret; - /* Actually, these two might be command words rather than registers, or some LFO position/buffer reset. */ - case 13: - READ16(addr, emu8k->hwcf6); - return ret; - case 14: - READ16(addr, emu8k->hwcf7); - return ret; - - /* Simulating empty/full bits by unsetting it once read. */ - case 20: - READ16(addr, emu8k->smalr | dmareadbit); - /* xor with itself to set to zero faster. */ - dmareadbit ^= dmareadbit; - return ret; - case 21: - READ16(addr, emu8k->smarr | dmareadbit); - /* xor with itself to set to zero faster.*/ - dmareadbit ^= dmareadbit; - return ret; - case 22: - READ16(addr, emu8k->smalw | dmawritebit); - /*xor with itself to set to zero faster.*/ - dmawritebit ^= dmawritebit; - return ret; - case 23: - READ16(addr, emu8k->smarw | dmawritebit); - /*xor with itself to set to zero faster.*/ - dmawritebit ^= dmawritebit; - return ret; - - case 26: - { - uint16_t val = emu8k->smrd_buffer; - emu8k->smrd_buffer = EMU8K_READ(emu8k, emu8k->smarr); - emu8k->smarr = (emu8k->smarr + 1) & EMU8K_MEM_ADDRESS_MASK; - return val; - } - /*TODO: We need to improve the precision of this clock, since - it is used by programs to wait. Not critical, but should help reduce - the amount of calls and wait time */ - case 27: /*Sample Counter ( 44Khz clock) */ - return emu8k->sample_count + emu8k->sample_count_virtual; - } - break; - - case 2: - return emu8k->init2[emu8k->cur_voice]; - - case 3: - return emu8k->init4[emu8k->cur_voice]; - - case 4: - return emu8k->voice[emu8k->cur_voice].atkhldv; - - case 5: - return emu8k->voice[emu8k->cur_voice].lfo1val; - - case 6: - return emu8k->voice[emu8k->cur_voice].atkhld; - - case 7: - return emu8k->voice[emu8k->cur_voice].lfo2val; - } - break; - - case 0xE00: /*Data3. also known as BLASTER+0xC00 and EMU+0x800 */ - switch (emu8k->cur_reg) - { - case 0: - return emu8k->voice[emu8k->cur_voice].ip; - - case 1: - return emu8k->voice[emu8k->cur_voice].ifatn; - - case 2: - return emu8k->voice[emu8k->cur_voice].pefe; - - case 3: - return emu8k->voice[emu8k->cur_voice].fmmod; - - case 4: - return emu8k->voice[emu8k->cur_voice].tremfrq; - - case 5: - return emu8k->voice[emu8k->cur_voice].fm2frq2; - - case 6: - return 0xffff; - - case 7: /*ID?*/ - return 0x1c | ((emu8k->id & 0x0002) ? 0xff02 : 0); - } - break; - - case 0xE02: /* Pointer. also known as BLASTER+0xC02 and EMU+0x802 */ - /* LS five bits = channel number, next 3 bits = register number - * and MS 8 bits = VLSI test register. - * Impulse tracker tests the non variability of the LS byte that it has set, and the variability - * of the MS byte to determine that it really is an AWE32. - * cubic player has a similar code, where it waits until value & 0x1000 is nonzero, and then waits again until it changes to zero.*/ - random_helper = (random_helper + 1) & 0x1F; - return ((0x80 | random_helper) << 8) | (emu8k->cur_reg << 5) | emu8k->cur_voice; - } - pclog("EMU8K READ : Unknown register read: %04X-%02X(%d/%d) \n", addr, (emu8k->cur_reg << 5) | emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice); - return 0xffff; -} - -void emu8k_outw(emu8k_t *emu8k, uint16_t addr, uint16_t val) -{ -#if 0 - /*TODO: I would like to not call this here, but i found it was needed or else cubic player would not finish opening (take a looot more of time than usual). - * Basically, being here means that the audio is generated in the emulation thread, instead of the audio thread.*/ - emu8k_update(emu8k); -#endif - -#ifdef EMU8K_DEBUG_REGISTERS - if (addr == 0xE22) - { - //pclog("EMU8K WRITE POINTER: %d\n", val); - } - else if ((addr&0xF00) == 0x600) - { - /* These are automatically reported by WRITE16 */ - if (rep_count_w>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_w); - rep_count_w=0; - } - last_write=0; - } - else if ((addr&0xF00) == 0xA00 && emu8k->cur_reg == 0) - { - /* These are automatically reported by WRITE16 */ - if (rep_count_w>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_w); - rep_count_w=0; - } - last_write=0; - } - else if ((addr&0xF00) == 0xA00 && emu8k->cur_reg == 1) - { - uint32_t tmpz = ((addr&0xF00) << 16)|(emu8k->cur_reg<<5); - if (tmpz != last_write) - { - if (rep_count_w>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_w); - rep_count_w=0; - } - last_write=tmpz; - pclog("EMU8K WRITE RAM I/O or configuration \n"); - } - //pclog("EMU8K WRITE %04X-%02X(%d/%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice,emu8k->cur_reg,emu8k->cur_voice, val); - } - else if ((addr&0xF00) == 0xA00 && (emu8k->cur_reg == 2 || emu8k->cur_reg == 3)) - { - uint32_t tmpz = ((addr&0xF00) << 16); - if (tmpz != last_write) - { - if (rep_count_w>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_w); - rep_count_w=0; - } - last_write=tmpz; - pclog("EMU8K WRITE INIT \n"); - } - //pclog("EMU8K WRITE %04X-%02X(%d/%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice,emu8k->cur_reg,emu8k->cur_voice, val); - } - else if (addr != 0xE22) - { - uint32_t tmpz = (addr << 16)|(emu8k->cur_reg<<5)| emu8k->cur_voice; - //if (tmpz != last_write) - if(1) - { - char* name = 0; - if (addr == 0xA20) - { - name = PORT_NAMES[1][emu8k->cur_reg]; - } - else if (addr == 0xA22) - { - name = PORT_NAMES[2][emu8k->cur_reg]; - } - else if (addr == 0xE20) - { - name = PORT_NAMES[3][emu8k->cur_reg]; - } - - if (rep_count_w>1) - { - pclog("EMU8K ...... for %d times\n", rep_count_w); - } - if (name == 0) - { - pclog("EMU8K WRITE %04X-%02X(%d/%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice,emu8k->cur_reg,emu8k->cur_voice, val); - } - else - { - pclog("EMU8K WRITE %s (%d): %04X\n",name,emu8k->cur_voice, val); - } - - rep_count_w=0; - last_write=tmpz; - } - rep_count_w++; - } -#endif //EMU8K_DEBUG_REGISTERS - - switch (addr & 0xF02) - { - case 0x600: - case 0x602: /*Data0. also known as BLASTER+0x400 and EMU+0x000 */ - switch (emu8k->cur_reg) - { - case 0: - /* The docs says that this value is constantly updating, and it should have no actual effect. Actions should be done over ptrx */ - WRITE16(addr, emu8k->voice[emu8k->cur_voice].cpf, val); - return; - - case 1: - WRITE16(addr, emu8k->voice[emu8k->cur_voice].ptrx, val); - return; - - case 2: - /* The docs says that this value is constantly updating, and it should have no actual effect. Actions should be done over vtft */ - WRITE16(addr, emu8k->voice[emu8k->cur_voice].cvcf, val); - return; - - case 3: - WRITE16(addr, emu8k->voice[emu8k->cur_voice].vtft, val); - return; - - case 4: - WRITE16(addr, emu8k->voice[emu8k->cur_voice].unknown_data0_4, val); - return; - - case 5: - WRITE16(addr, emu8k->voice[emu8k->cur_voice].unknown_data0_5, val); - return; - - case 6: - { - emu8k_voice_t* emu_voice = &emu8k->voice[emu8k->cur_voice]; - WRITE16(addr, emu_voice->psst, val); - /* TODO: Should we update only on MSB update, or this could be used as some sort of hack by applications? */ - emu_voice->loop_start.int_address = emu_voice->psst & EMU8K_MEM_ADDRESS_MASK; - if (addr & 2) - { - emu_voice->vol_l = emu_voice->psst_pan; - emu_voice->vol_r = 255 - (emu_voice->psst_pan); - } - } - return; - - case 7: - WRITE16(addr, emu8k->voice[emu8k->cur_voice].csl, val); - /* TODO: Should we update only on MSB update, or this could be used as some sort of hack by applications? */ - emu8k->voice[emu8k->cur_voice].loop_end.int_address = emu8k->voice[emu8k->cur_voice].csl & EMU8K_MEM_ADDRESS_MASK; - return; - } - break; - - case 0xA00: /*Data1. also known as BLASTER+0x800 and EMU+0x400 */ - switch (emu8k->cur_reg) - { - case 0: - WRITE16(addr, emu8k->voice[emu8k->cur_voice].ccca, val); - /* TODO: Should we update only on MSB update, or this could be used as some sort of hack by applications? */ - emu8k->voice[emu8k->cur_voice].addr.int_address = emu8k->voice[emu8k->cur_voice].ccca & EMU8K_MEM_ADDRESS_MASK; - return; - - case 1: - switch (emu8k->cur_voice) - { - case 9: - WRITE16(addr, emu8k->hwcf4, val); - return; - case 10: - WRITE16(addr, emu8k->hwcf5, val); - return; - /* Actually, these two might be command words rather than registers, or some LFO position/buffer reset. */ - case 13: - WRITE16(addr, emu8k->hwcf6, val); - return; - case 14: - WRITE16(addr, emu8k->hwcf7, val); - return; - - case 20: - WRITE16(addr, emu8k->smalr, val); - return; - case 21: - WRITE16(addr, emu8k->smarr, val); - return; - case 22: - WRITE16(addr, emu8k->smalw, val); - return; - case 23: - WRITE16(addr, emu8k->smarw, val); - return; - - case 26: - EMU8K_WRITE(emu8k, emu8k->smalw, val); - emu8k->smalw = (emu8k->smalw + 1) & EMU8K_MEM_ADDRESS_MASK; - return; - - case 29: - emu8k->hwcf1 = val; - return; - case 30: - emu8k->hwcf2 = val; - return; - case 31: - emu8k->hwcf3 = val; - return; - } - break; - - case 2: - emu8k->init1[emu8k->cur_voice] = val; - /* Skip if in first/second initialization step */ - if (emu8k->init1[0] != 0x03FF) - { - switch (emu8k->cur_voice) - { - case 0x3: - emu8k->reverb_engine.out_mix = val & 0xFF; - break; - case 0x5: - { - int c; - for (c = 0; c < 8; c++) - { - emu8k->reverb_engine.allpass[c].feedback = (val & 0xFF) / ((float)0xFF); - } - } - break; - case 0x7: - emu8k->reverb_engine.link_return_type = (val == 0x8474) ? 1 : 0; - break; - case 0xF: - emu8k->reverb_engine.reflections[0].output_gain = ((val & 0xF0) >> 4) / 15.0; - break; - case 0x17: - emu8k->reverb_engine.reflections[1].output_gain = ((val & 0xF0) >> 4) / 15.0; - break; - case 0x1F: - emu8k->reverb_engine.reflections[2].output_gain = ((val & 0xF0) >> 4) / 15.0; - break; - case 0x9: - emu8k->reverb_engine.reflections[0].feedback = (val & 0xF) / 15.0; - break; - case 0xB: //emu8k->reverb_engine.reflections[0].feedback_r = (val&0xF)/15.0; - break; - case 0x11: - emu8k->reverb_engine.reflections[1].feedback = (val & 0xF) / 15.0; - break; - case 0x13: //emu8k->reverb_engine.reflections[1].feedback_r = (val&0xF)/15.0; - break; - case 0x19: - emu8k->reverb_engine.reflections[2].feedback = (val & 0xF) / 15.0; - break; - case 0x1B: //emu8k->reverb_engine.reflections[2].feedback_r = (val&0xF)/15.0; - break; - } - } - return; - - case 3: - emu8k->init3[emu8k->cur_voice] = val; - /* Skip if in first/second initialization step */ - if (emu8k->init1[0] != 0x03FF) - { - switch (emu8k->cur_voice) - { - case 9: - emu8k->chorus_engine.feedback = (val & 0xFF); - break; - case 12: - /* Limiting this to a sane value given our buffer. */ - emu8k->chorus_engine.delay_samples_central = (val & 0x1FFF); - break; - - case 1: - emu8k->reverb_engine.refl_in_amp = val & 0xFF; - break; - case 3: //emu8k->reverb_engine.refl_in_amp_r = val&0xFF; - break; - } - } - return; - - case 4: - emu8k->voice[emu8k->cur_voice].envvol = val; - emu8k->voice[emu8k->cur_voice].vol_envelope.delay_samples = ENVVOL_TO_EMU_SAMPLES(val); - return; - - case 5: - { - emu8k->voice[emu8k->cur_voice].dcysusv = val; - emu8k_envelope_t* const vol_env = &emu8k->voice[emu8k->cur_voice].vol_envelope; - int old_on = emu8k->voice[emu8k->cur_voice].env_engine_on; - emu8k->voice[emu8k->cur_voice].env_engine_on = DCYSUSV_GENERATOR_ENGINE_ON(val); - - if (emu8k->voice[emu8k->cur_voice].env_engine_on && - old_on != emu8k->voice[emu8k->cur_voice].env_engine_on) - { - if (emu8k->hwcf3 != 0x04) - { - /* This is a hack for some programs like Doom or cubic player 1.7 that don't initialize - the hwcfg and init registers (doom does not init the card at all. only tests the cfg registers) */ - emu8k->hwcf3 = 0x04; - } - - //reset lfos. - emu8k->voice[emu8k->cur_voice].lfo1_count.addr = 0; - emu8k->voice[emu8k->cur_voice].lfo2_count.addr = 0; - // Trigger envelopes - if (ATKHLDV_TRIGGER(emu8k->voice[emu8k->cur_voice].atkhldv)) - { - vol_env->value_amp_hz = 0; - if (vol_env->delay_samples) - { - vol_env->state = ENV_DELAY; - } - else if (vol_env->attack_amount_amp_hz == 0) - { - vol_env->state = ENV_STOPPED; - } - else - { - vol_env->state = ENV_ATTACK; - /* TODO: Verify if "never attack" means eternal mute, - * or it means skip attack, go to hold". - if (vol_env->attack_amount == 0) - { - vol_env->value = (1 << 21); - vol_env->state = ENV_HOLD; - }*/ - } - } - - if (ATKHLD_TRIGGER(emu8k->voice[emu8k->cur_voice].atkhld)) - { - emu8k_envelope_t* const mod_env = &emu8k->voice[emu8k->cur_voice].mod_envelope; - mod_env->value_amp_hz = 0; - mod_env->value_db_oct = 0; - if (mod_env->delay_samples) - { - mod_env->state = ENV_DELAY; - } - else if (mod_env->attack_amount_amp_hz == 0) - { - mod_env->state = ENV_STOPPED; - } - else - { - mod_env->state = ENV_ATTACK; - /* TODO: Verify if "never attack" means eternal start, - * or it means skip attack, go to hold". - if (mod_env->attack_amount == 0) - { - mod_env->value = (1 << 21); - mod_env->state = ENV_HOLD; - }*/ - } - } - } - - - /* Converting the input in dBs to envelope value range. */ - vol_env->sustain_value_db_oct = DCYSUSV_SUS_TO_ENV_RANGE(DCYSUSV_SUSVALUE_GET(val)); - vol_env->ramp_amount_db_oct = env_decay_to_dbs_or_oct[DCYSUSV_DECAYRELEASE_GET(val)]; - if (DCYSUSV_IS_RELEASE(val)) - { - if (vol_env->state == ENV_DELAY || vol_env->state == ENV_ATTACK || vol_env->state == ENV_HOLD) - { - vol_env->value_db_oct = env_vol_amplitude_to_db[vol_env->value_amp_hz >> 5] << 5; - if (vol_env->value_db_oct > (1 << 21)) - vol_env->value_db_oct = 1 << 21; - } - - vol_env->state = (vol_env->value_db_oct >= vol_env->sustain_value_db_oct) ? ENV_RAMP_DOWN : ENV_RAMP_UP; - } - } - return; - - case 6: - emu8k->voice[emu8k->cur_voice].envval = val; - emu8k->voice[emu8k->cur_voice].mod_envelope.delay_samples = ENVVAL_TO_EMU_SAMPLES(val); - return; - - case 7: - { - //TODO: Look for a bug on delay (first trigger it works, next trigger it doesn't) - emu8k->voice[emu8k->cur_voice].dcysus = val; - emu8k_envelope_t* const mod_env = &emu8k->voice[emu8k->cur_voice].mod_envelope; - /* Converting the input in octaves to envelope value range. */ - mod_env->sustain_value_db_oct = DCYSUS_SUS_TO_ENV_RANGE(DCYSUS_SUSVALUE_GET(val)); - mod_env->ramp_amount_db_oct = env_decay_to_dbs_or_oct[DCYSUS_DECAYRELEASE_GET(val)]; - if (DCYSUS_IS_RELEASE(val)) - { - if (mod_env->state == ENV_DELAY || mod_env->state == ENV_ATTACK || mod_env->state == ENV_HOLD) - { - mod_env->value_db_oct = env_mod_hertz_to_octave[mod_env->value_amp_hz >> 9] << 9; - if (mod_env->value_db_oct >= (1 << 21)) - mod_env->value_db_oct = (1 << 21) - 1; - } - - mod_env->state = (mod_env->value_db_oct >= mod_env->sustain_value_db_oct) ? ENV_RAMP_DOWN : ENV_RAMP_UP; - } - } - return; - } - break; - - case 0xA02: /*Data2. also known as BLASTER+0x802 and EMU+0x402 */ - switch (emu8k->cur_reg) - { - case 0: - { - emu8k_voice_t* emu_voice = &emu8k->voice[emu8k->cur_voice]; - WRITE16(addr, emu_voice->ccca, val); - emu_voice->addr.int_address = emu_voice->ccca & EMU8K_MEM_ADDRESS_MASK; - uint32_t paramq = CCCA_FILTQ_GET(emu_voice->ccca); - emu_voice->filt_att = filter_atten[paramq]; - emu_voice->filterq_idx = paramq; - } - return; - - case 1: - switch (emu8k->cur_voice) - { - case 9: - WRITE16(addr, emu8k->hwcf4, val); - /* Skip if in first/second initialization step */ - if (emu8k->init1[0] != 0x03FF) - { - /*(1/256th of a 44Khz sample) */ - /* clip the value to a reasonable value given our buffer */ - int32_t tmp = emu8k->hwcf4 & 0x1FFFFF; - emu8k->chorus_engine.delay_offset_samples_right = ((double)tmp) / 256.0; - } - return; - case 10: - WRITE16(addr, emu8k->hwcf5, val); - /* Skip if in first/second initialization step */ - if (emu8k->init1[0] != 0x03FF) - { - /* The scale of this value is unknown. I've taken it as milliHz. - * Another interpretation could be periods. (and so, Hz = 1/period)*/ - double osc_speed = emu8k->hwcf5;//*1.316; -#if 1 // milliHz - /*milliHz to lfotable samples.*/ - osc_speed *= 65.536 / 44100.0; -#elif 0 //periods - /* 44.1Khz ticks to lfotable samples.*/ - osc_speed = 65.536/osc_speed; -#endif - /*left shift 32bits for 32.32 fixed.point*/ - osc_speed *= 65536.0 * 65536.0; - emu8k->chorus_engine.lfo_inc.addr = (uint64_t)osc_speed; - } - return; - /* Actually, these two might be command words rather than registers, or some LFO position/buffer reset.*/ - case 13: - WRITE16(addr, emu8k->hwcf6, val); - return; - case 14: - WRITE16(addr, emu8k->hwcf7, val); - return; - - case 20: /*Top 8 bits are for Empty (MT) bit or non-addressable.*/ - WRITE16(addr, emu8k->smalr, val & 0xFF); - dmareadbit = 0x8000; - return; - case 21: /*Top 8 bits are for Empty (MT) bit or non-addressable.*/ - WRITE16(addr, emu8k->smarr, val & 0xFF); - dmareadbit = 0x8000; - return; - case 22: /*Top 8 bits are for full bit or non-addressable.*/ - WRITE16(addr, emu8k->smalw, val & 0xFF); - return; - case 23: /*Top 8 bits are for full bit or non-addressable.*/ - WRITE16(addr, emu8k->smarw, val & 0xFF); - return; - - case 26: - dmawritebit = 0x8000; - EMU8K_WRITE(emu8k, emu8k->smarw, val); - emu8k->smarw++; - return; - } - break; - - case 2: - emu8k->init2[emu8k->cur_voice] = val; - /* Skip if in first/second initialization step */ - if (emu8k->init1[0] != 0x03FF) - { - switch (emu8k->cur_voice) - { - case 0x14: - { - int multip = ((val & 0xF00) >> 8) + 18; - emu8k->reverb_engine.reflections[5].bufsize = multip * REV_BUFSIZE_STEP; - emu8k->reverb_engine.tailL.bufsize = (multip + 1) * REV_BUFSIZE_STEP; - if (emu8k->reverb_engine.link_return_type == 0) - { - emu8k->reverb_engine.tailR.bufsize = (multip + 1) * REV_BUFSIZE_STEP; - } - } - break; - case 0x16: - if (emu8k->reverb_engine.link_return_type == 1) - { - int multip = ((val & 0xF00) >> 8) + 18; - emu8k->reverb_engine.tailR.bufsize = (multip + 1) * REV_BUFSIZE_STEP; - } - break; - case 0x7: - emu8k->reverb_engine.reflections[3].output_gain = ((val & 0xF0) >> 4) / 15.0; - break; - case 0xf: - emu8k->reverb_engine.reflections[4].output_gain = ((val & 0xF0) >> 4) / 15.0; - break; - case 0x17: - emu8k->reverb_engine.reflections[5].output_gain = ((val & 0xF0) >> 4) / 15.0; - break; - case 0x1d: - { - int c; - for (c = 0; c < 6; c++) - { - emu8k->reverb_engine.reflections[c].damp1 = (val & 0xFF) / 255.0; - emu8k->reverb_engine.reflections[c].damp2 = (0xFF - (val & 0xFF)) / 255.0; - emu8k->reverb_engine.reflections[c].filterstore = 0; - } - emu8k->reverb_engine.damper.damp1 = (val & 0xFF) / 255.0; - emu8k->reverb_engine.damper.damp2 = (0xFF - (val & 0xFF)) / 255.0; - emu8k->reverb_engine.damper.filterstore = 0; - } - break; - case 0x1f: /* filter r */ - break; - case 0x1: - emu8k->reverb_engine.reflections[3].feedback = (val & 0xF) / 15.0; - break; - case 0x3: //emu8k->reverb_engine.reflections[3].feedback_r = (val&0xF)/15.0; - break; - case 0x9: - emu8k->reverb_engine.reflections[4].feedback = (val & 0xF) / 15.0; - break; - case 0xb: //emu8k->reverb_engine.reflections[4].feedback_r = (val&0xF)/15.0; - break; - case 0x11: - emu8k->reverb_engine.reflections[5].feedback = (val & 0xF) / 15.0; - break; - case 0x13: //emu8k->reverb_engine.reflections[5].feedback_r = (val&0xF)/15.0; - break; - } - } - return; - - case 3: - emu8k->init4[emu8k->cur_voice] = val; - /* Skip if in first/second initialization step */ - if (emu8k->init1[0] != 0x03FF) - { - switch (emu8k->cur_voice) - { - case 0x3: - { - int32_t samples = ((val & 0xFF) * emu8k->chorus_engine.delay_samples_central) >> 8; - emu8k->chorus_engine.lfodepth_multip = samples; - - } - break; - - case 0x1F: - emu8k->reverb_engine.link_return_amp = val & 0xFF; - break; - } - } - return; - - case 4: - { - emu8k->voice[emu8k->cur_voice].atkhldv = val; - emu8k_envelope_t* const vol_env = &emu8k->voice[emu8k->cur_voice].vol_envelope; - vol_env->attack_samples = env_attack_to_samples[ATKHLDV_ATTACK(val)]; - if (vol_env->attack_samples == 0) - { - vol_env->attack_amount_amp_hz = 0; - } - else - { - /* Linear amplitude increase each sample. */ - vol_env->attack_amount_amp_hz = (1 << 21) / vol_env->attack_samples; - } - vol_env->hold_samples = ATKHLDV_HOLD_TO_EMU_SAMPLES(val); - if (ATKHLDV_TRIGGER(val) && emu8k->voice[emu8k->cur_voice].env_engine_on) - { - /*TODO: I assume that "envelope trigger" is the same as new note - * (since changing the IP can be done when modulating pitch too) */ - emu8k->voice[emu8k->cur_voice].lfo1_count.addr = 0; - emu8k->voice[emu8k->cur_voice].lfo2_count.addr = 0; - - vol_env->value_amp_hz = 0; - if (vol_env->delay_samples) - { - vol_env->state = ENV_DELAY; - } - else if (vol_env->attack_amount_amp_hz == 0) - { - vol_env->state = ENV_STOPPED; - } - else - { - vol_env->state = ENV_ATTACK; - /* TODO: Verify if "never attack" means eternal mute, - * or it means skip attack, go to hold". - if (vol_env->attack_amount == 0) - { - vol_env->value = (1 << 21); - vol_env->state = ENV_HOLD; - }*/ - } - } - } - return; - - case 5: - emu8k->voice[emu8k->cur_voice].lfo1val = val; - /* TODO: verify if this is set once, or set every time. */ - emu8k->voice[emu8k->cur_voice].lfo1_delay_samples = LFOxVAL_TO_EMU_SAMPLES(val); - return; - - case 6: - { - emu8k->voice[emu8k->cur_voice].atkhld = val; - emu8k_envelope_t* const mod_env = &emu8k->voice[emu8k->cur_voice].mod_envelope; - mod_env->attack_samples = env_attack_to_samples[ATKHLD_ATTACK(val)]; - if (mod_env->attack_samples == 0) - { - mod_env->attack_amount_amp_hz = 0; - } - else - { - /* Linear amplitude increase each sample. */ - mod_env->attack_amount_amp_hz = (1 << 21) / mod_env->attack_samples; - } - mod_env->hold_samples = ATKHLD_HOLD_TO_EMU_SAMPLES(val); - if (ATKHLD_TRIGGER(val) && emu8k->voice[emu8k->cur_voice].env_engine_on) - { - mod_env->value_amp_hz = 0; - mod_env->value_db_oct = 0; - if (mod_env->delay_samples) - { - mod_env->state = ENV_DELAY; - } - else if (mod_env->attack_amount_amp_hz == 0) - { - mod_env->state = ENV_STOPPED; - } - else - { - mod_env->state = ENV_ATTACK; - /* TODO: Verify if "never attack" means eternal start, - * or it means skip attack, go to hold". - if (mod_env->attack_amount == 0) - { - mod_env->value = (1 << 21); - mod_env->state = ENV_HOLD; - }*/ - } - } - } - return; - - case 7: - emu8k->voice[emu8k->cur_voice].lfo2val = val; - emu8k->voice[emu8k->cur_voice].lfo2_delay_samples = LFOxVAL_TO_EMU_SAMPLES(val); - - return; - } - break; - - case 0xE00: /*Data3. also known as BLASTER+0xC00 and EMU+0x800 */ - switch (emu8k->cur_reg) - { - case 0: - emu8k->voice[emu8k->cur_voice].ip = val; - emu8k->voice[emu8k->cur_voice].ptrx_pit_target = freqtable[val] >> 18; - return; - - case 1: - { - emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; - if ((val & 0xFF) == 0 && the_voice->cvcf_curr_volume == 0 && the_voice->vtft_vol_target == 0 - && the_voice->dcysusv == 0x80 && the_voice->ip == 0) - { - // Patch to avoid some clicking noises with Impulse tracker or other software that sets - // different values to 0 to set noteoff, but here, 0 means no attenuation = full volume. - return; - } - the_voice->ifatn = val; - the_voice->initial_att = (((int32_t)the_voice->ifatn_attenuation << 21) / 0xFF); - the_voice->vtft_vol_target = attentable[the_voice->ifatn_attenuation]; - - the_voice->initial_filter = (((int32_t)the_voice->ifatn_init_filter << 21) / 0xFF); - if (the_voice->ifatn_init_filter == 0xFF) - { - the_voice->vtft_filter_target = 0xFFFF; - } - else - { - the_voice->vtft_filter_target = the_voice->initial_filter >> 5; - } - } - return; - - case 2: - { - emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; - the_voice->pefe = val; - - int divider = (the_voice->pefe_modenv_filter_height < 0) ? 0x80 : 0x7F; - the_voice->fixed_modenv_filter_height = ((int32_t)the_voice->pefe_modenv_filter_height) * 0x4000 / divider; - - divider = (the_voice->pefe_modenv_pitch_height < 0) ? 0x80 : 0x7F; - the_voice->fixed_modenv_pitch_height = ((int32_t)the_voice->pefe_modenv_pitch_height) * 0x4000 / divider; - } - return; - - case 3: - { - emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; - the_voice->fmmod = val; - - int divider = (the_voice->fmmod_lfo1_filt_mod < 0) ? 0x80 : 0x7F; - the_voice->fixed_lfo1_filt_mod = ((int32_t)the_voice->fmmod_lfo1_filt_mod) * 0x4000 / divider; - - divider = (the_voice->fmmod_lfo1_vibrato < 0) ? 0x80 : 0x7F; - the_voice->fixed_lfo1_vibrato = ((int32_t)the_voice->fmmod_lfo1_vibrato) * 0x4000 / divider; - } - return; - - case 4: - { - emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; - the_voice->tremfrq = val; - the_voice->lfo1_speed = lfofreqtospeed[the_voice->tremfrq_lfo1_freq]; - - int divider = (the_voice->tremfrq_lfo1_tremolo < 0) ? 0x80 : 0x7F; - the_voice->fixed_lfo1_tremolo = ((int32_t)the_voice->tremfrq_lfo1_tremolo) * 0x4000 / divider; - } - return; - - case 5: - { - emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; - the_voice->fm2frq2 = val; - the_voice->lfo2_speed = lfofreqtospeed[the_voice->fm2frq2_lfo2_freq]; - - int divider = (the_voice->fm2frq2_lfo2_vibrato < 0) ? 0x80 : 0x7F; - the_voice->fixed_lfo2_vibrato = ((int32_t)the_voice->fm2frq2_lfo2_vibrato) * 0x4000 / divider; - } - return; - - case 7: /*ID? I believe that this allows applications to know if the emu is in use by another application */ - emu8k->id = val; - return; - } - break; - - case 0xE02: /* Pointer. also known as BLASTER+0xC02 and EMU+0x802 */ - emu8k->cur_voice = (val & 31); - emu8k->cur_reg = ((val >> 5) & 7); - return; - } - pclog("EMU8K WRITE: Unknown register write: %04X-%02X(%d/%d): %04X \n", addr, (emu8k->cur_reg) << 5 | emu8k->cur_voice, - emu8k->cur_reg, emu8k->cur_voice, val); - -} - -uint8_t emu8k_inb(emu8k_t *emu8k, uint16_t addr) -{ - /* Reading a single byte is a feature that at least Impulse tracker uses, - * but only on detection code and not for odd addresses.*/ - if (addr & 1) - return emu8k_inw(emu8k, addr & ~1) >> 1; - return emu8k_inw(emu8k, addr) & 0xff; -} - -void emu8k_outb(emu8k_t *emu8k, uint16_t addr, uint8_t val) -{ - /* TODO: AWE32 docs says that you cannot write in bytes, but if - * an app were to use this implementation, the content of the LS Byte would be lost.*/ - if (addr & 1) - emu8k_outw(emu8k, addr & ~1, val << 8); - else - emu8k_outw(emu8k, addr, val); -} - -/* TODO: This is not a correct emulation, just a workalike implementation. */ -void emu8k_work_chorus(int32_t* inbuf, int32_t* outbuf, emu8k_chorus_eng_t* engine, int count) -{ - int pos; - for (pos = 0; pos < count; pos++) - { - double lfo_inter1 = chortable[engine->lfo_pos.int_address]; - // double lfo_inter2 = chortable[(engine->lfo_pos.int_address+1)&0xFFFF]; - - double offset_lfo = lfo_inter1; //= lfo_inter1 + ((lfo_inter2-lfo_inter1)*engine->lfo_pos.fract_address/65536.0); - offset_lfo *= engine->lfodepth_multip; - - /* Work left */ - double readdouble = (double)engine->write - (double)engine->delay_samples_central - offset_lfo; - int read = (int32_t)floor(readdouble); - int fraction_part = (readdouble - (double)read) * 65536.0; - int next_value = read + 1; - if (read < 0) - { - read += EMU8K_LFOCHORUS_SIZE; - if (next_value < 0) next_value += EMU8K_LFOCHORUS_SIZE; - } - else if (next_value >= EMU8K_LFOCHORUS_SIZE) - { - next_value -= EMU8K_LFOCHORUS_SIZE; - if (read >= EMU8K_LFOCHORUS_SIZE) read -= EMU8K_LFOCHORUS_SIZE; - } - int32_t dat1 = engine->chorus_left_buffer[read]; - int32_t dat2 = engine->chorus_left_buffer[next_value]; - dat1 += ((dat2 - dat1) * fraction_part) >> 16; - - engine->chorus_left_buffer[engine->write] = *inbuf + ((dat1 * engine->feedback) >> 8); - - - /* Work right */ - readdouble = (double)engine->write - (double)engine->delay_samples_central - engine->delay_offset_samples_right - offset_lfo; - read = (int32_t)floor(readdouble); - next_value = read + 1; - if (read < 0) - { - read += EMU8K_LFOCHORUS_SIZE; - if (next_value < 0) next_value += EMU8K_LFOCHORUS_SIZE; - } - else if (next_value >= EMU8K_LFOCHORUS_SIZE) - { - next_value -= EMU8K_LFOCHORUS_SIZE; - if (read >= EMU8K_LFOCHORUS_SIZE) read -= EMU8K_LFOCHORUS_SIZE; - } - int32_t dat3 = engine->chorus_right_buffer[read]; - int32_t dat4 = engine->chorus_right_buffer[next_value]; - dat3 += ((dat4 - dat3) * fraction_part) >> 16; - - engine->chorus_right_buffer[engine->write] = *inbuf + ((dat3 * engine->feedback) >> 8); - - ++engine->write; - engine->write %= EMU8K_LFOCHORUS_SIZE; - engine->lfo_pos.addr += engine->lfo_inc.addr; - engine->lfo_pos.int_address &= 0xFFFF; - - (*outbuf++) += dat1; - (*outbuf++) += dat3; - inbuf++; - } - -} - -int32_t emu8k_reverb_comb_work(emu8k_reverb_combfilter_t* comb, int32_t in) -{ - - int32_t bufin; - /* get echo */ - int32_t output = comb->reflection[comb->read_pos]; - /* apply lowpass */ - comb->filterstore = (output * comb->damp2) + (comb->filterstore * comb->damp1); - /* appply feedback */ - bufin = in - (comb->filterstore * comb->feedback); - /* store new value in delayed buffer */ - comb->reflection[comb->read_pos] = bufin; - - if (++comb->read_pos >= comb->bufsize) comb->read_pos = 0; - - return output * comb->output_gain; -} - -int32_t emu8k_reverb_diffuser_work(emu8k_reverb_combfilter_t* comb, int32_t in) -{ - - int32_t bufout = comb->reflection[comb->read_pos]; - /*diffuse*/ - int32_t bufin = -in + (bufout * comb->feedback); - int32_t output = bufout - (bufin * comb->feedback); - /* store new value in delayed buffer */ - comb->reflection[comb->read_pos] = bufin; - - if (++comb->read_pos >= comb->bufsize) comb->read_pos = 0; - - return output; -} - -int32_t emu8k_reverb_tail_work(emu8k_reverb_combfilter_t* comb, emu8k_reverb_combfilter_t* allpasses, int32_t in) -{ - int32_t output = comb->reflection[comb->read_pos]; - /* store new value in delayed buffer */ - comb->reflection[comb->read_pos] = in; - - //output = emu8k_reverb_allpass_work(&allpasses[0],output); - output = emu8k_reverb_diffuser_work(&allpasses[1], output); - output = emu8k_reverb_diffuser_work(&allpasses[2], output); - //output = emu8k_reverb_allpass_work(&allpasses[3],output); - - if (++comb->read_pos >= comb->bufsize) comb->read_pos = 0; - - return output; -} -int32_t emu8k_reverb_damper_work(emu8k_reverb_combfilter_t* comb, int32_t in) -{ - /* apply lowpass */ - comb->filterstore = (in * comb->damp2) + (comb->filterstore * comb->damp1); - return comb->filterstore; -} - -/* TODO: This is not a correct emulation, just a workalike implementation. */ -void emu8k_work_reverb(int32_t* inbuf, int32_t* outbuf, emu8k_reverb_eng_t* engine, int count) -{ - int pos; - if (engine->link_return_type) - { - for (pos = 0; pos < count; pos++) - { - int32_t dat1, dat2, in, in2; - in = emu8k_reverb_damper_work(&engine->damper, inbuf[pos]); - in2 = (in * engine->refl_in_amp) >> 8; - dat2 = emu8k_reverb_comb_work(&engine->reflections[0], in2); - dat2 += emu8k_reverb_comb_work(&engine->reflections[1], in2); - dat1 = emu8k_reverb_comb_work(&engine->reflections[2], in2); - dat2 += emu8k_reverb_comb_work(&engine->reflections[3], in2); - dat1 += emu8k_reverb_comb_work(&engine->reflections[4], in2); - dat2 += emu8k_reverb_comb_work(&engine->reflections[5], in2); - - dat1 += (emu8k_reverb_tail_work(&engine->tailL, &engine->allpass[0], in + dat1) * engine->link_return_amp) >> 8; - dat2 += (emu8k_reverb_tail_work(&engine->tailR, &engine->allpass[4], in + dat2) * engine->link_return_amp) >> 8; - - (*outbuf++) += (dat1 * engine->out_mix) >> 8; - (*outbuf++) += (dat2 * engine->out_mix) >> 8; - } - } - else - { - for (pos = 0; pos < count; pos++) - { - int32_t dat1, dat2, in, in2; - in = emu8k_reverb_damper_work(&engine->damper, inbuf[pos]); - in2 = (in * engine->refl_in_amp) >> 8; - dat1 = emu8k_reverb_comb_work(&engine->reflections[0], in2); - dat1 += emu8k_reverb_comb_work(&engine->reflections[1], in2); - dat1 += emu8k_reverb_comb_work(&engine->reflections[2], in2); - dat1 += emu8k_reverb_comb_work(&engine->reflections[3], in2); - dat1 += emu8k_reverb_comb_work(&engine->reflections[4], in2); - dat1 += emu8k_reverb_comb_work(&engine->reflections[5], in2); - dat2 = dat1; - - dat1 += (emu8k_reverb_tail_work(&engine->tailL, &engine->allpass[0], in + dat1) * engine->link_return_amp) >> 8; - dat2 += (emu8k_reverb_tail_work(&engine->tailR, &engine->allpass[4], in + dat2) * engine->link_return_amp) >> 8; - - (*outbuf++) += (dat1 * engine->out_mix) >> 8; - (*outbuf++) += (dat2 * engine->out_mix) >> 8; - } - } -} -void emu8k_work_eq(int32_t* inoutbuf, int count) -{ - // TODO: Work EQ over buf - NOREF(inoutbuf); - NOREF(count); -} - -int32_t emu8k_vol_slide(emu8k_slide_t* slide, int32_t target) -{ - if (slide->last < target) - { - slide->last += 0x400; - if (slide->last > target) slide->last = target; - } - else if (slide->last > target) - { - slide->last -= 0x400; - if (slide->last < target) slide->last = target; - } - return slide->last; -} - -//int32_t old_pitch[32]={0}; -//int32_t old_cut[32]={0}; -//int32_t old_vol[32]={0}; -void emu8k_update(emu8k_t* emu8k, int new_pos) -{ - if (emu8k->pos >= new_pos) - return; - - AssertLogRelReturnVoid(new_pos <= MAXSOUNDBUFLEN); - - int32_t* buf; - emu8k_voice_t* emu_voice; - int pos; - int c; - - /* Clean the buffers since we will accumulate into them. */ - buf = &emu8k->buffer[emu8k->pos * 2]; - memset(buf, 0, 2 * (new_pos - emu8k->pos) * sizeof(emu8k->buffer[0])); - memset(&emu8k->chorus_in_buffer[emu8k->pos], 0, (new_pos - emu8k->pos) * sizeof(emu8k->chorus_in_buffer[0])); - memset(&emu8k->reverb_in_buffer[emu8k->pos], 0, (new_pos - emu8k->pos) * sizeof(emu8k->reverb_in_buffer[0])); - - /* Voices section */ - for (c = 0; c < 32; c++) - { - emu_voice = &emu8k->voice[c]; - buf = &emu8k->buffer[emu8k->pos * 2]; - - for (pos = emu8k->pos; pos < new_pos; pos++) - { - int32_t dat; - - if (emu_voice->cvcf_curr_volume) - { - /* Waveform oscillator */ -#ifdef RESAMPLER_LINEAR - dat = EMU8K_READ_INTERP_LINEAR(emu8k, emu_voice->addr.int_address, - emu_voice->addr.fract_address); - -#elif defined RESAMPLER_CUBIC - dat = EMU8K_READ_INTERP_CUBIC(emu8k, emu_voice->addr.int_address, - emu_voice->addr.fract_address); -#endif - - /* Filter section */ - if (emu_voice->filterq_idx || emu_voice->cvcf_curr_filt_ctoff != 0xFFFF) - { - int cutoff = emu_voice->cvcf_curr_filt_ctoff >> 8; - const int64_t coef0 = filt_coeffs[emu_voice->filterq_idx][cutoff][0]; - const int64_t coef1 = filt_coeffs[emu_voice->filterq_idx][cutoff][1]; - const int64_t coef2 = filt_coeffs[emu_voice->filterq_idx][cutoff][2]; - /* clip at twice the range */ -#define ClipBuffer(buf) (buf < -16777216) ? -16777216 : (buf > 16777216) ? 16777216 : buf - -#ifdef FILTER_INITIAL -#define NOOP(x) (void)x; - NOOP(coef1) - /* Apply expected attenuation. (FILTER_MOOG does it implicitly, but this one doesn't). - * Work in 24bits. */ - dat = (dat * emu_voice->filt_att) >> 8; - - int64_t vhp = ((-emu_voice->filt_buffer[0] * coef2) >> 24) - emu_voice->filt_buffer[1] - dat; - emu_voice->filt_buffer[1] += (emu_voice->filt_buffer[0] * coef0) >> 24; - emu_voice->filt_buffer[0] += (vhp * coef0) >> 24; - dat = (int32_t)(emu_voice->filt_buffer[1] >> 8); - if (dat > 32767) { dat = 32767; } - else if (dat < -32768) { dat = -32768; } - -#elif defined FILTER_MOOG - - /*move to 24bits*/ - dat <<= 8; - - dat -= (coef2 * emu_voice->filt_buffer[4]) >> 24; /*feedback*/ - int64_t t1 = emu_voice->filt_buffer[1]; - emu_voice->filt_buffer[1] = ((dat + emu_voice->filt_buffer[0]) * coef0 - emu_voice->filt_buffer[1] * coef1) >> 24; - emu_voice->filt_buffer[1] = ClipBuffer(emu_voice->filt_buffer[1]); - - int64_t t2 = emu_voice->filt_buffer[2]; - emu_voice->filt_buffer[2] = ((emu_voice->filt_buffer[1] + t1) * coef0 - emu_voice->filt_buffer[2] * coef1) >> 24; - emu_voice->filt_buffer[2] = ClipBuffer(emu_voice->filt_buffer[2]); - - int64_t t3 = emu_voice->filt_buffer[3]; - emu_voice->filt_buffer[3] = ((emu_voice->filt_buffer[2] + t2) * coef0 - emu_voice->filt_buffer[3] * coef1) >> 24; - emu_voice->filt_buffer[3] = ClipBuffer(emu_voice->filt_buffer[3]); - - emu_voice->filt_buffer[4] = ((emu_voice->filt_buffer[3] + t3) * coef0 - emu_voice->filt_buffer[4] * coef1) >> 24; - emu_voice->filt_buffer[4] = ClipBuffer(emu_voice->filt_buffer[4]); - - emu_voice->filt_buffer[0] = ClipBuffer(dat); - - dat = (int32_t)(emu_voice->filt_buffer[4] >> 8); - if (dat > 32767) - { dat = 32767; } - else if (dat < -32768) - { dat = -32768; } - -#elif defined FILTER_CONSTANT - - /* Apply expected attenuation. (FILTER_MOOG does it implicitly, but this one is constant gain). - * Also stay at 24bits.*/ - dat = (dat * emu_voice->filt_att) >> 8; - - emu_voice->filt_buffer[0] = (coef1 * emu_voice->filt_buffer[0] - + coef0 * (dat + - ((coef2 * (emu_voice->filt_buffer[0] - emu_voice->filt_buffer[1]))>>24)) - ) >> 24; - emu_voice->filt_buffer[1] = (coef1 * emu_voice->filt_buffer[1] - + coef0 * emu_voice->filt_buffer[0]) >> 24; - - emu_voice->filt_buffer[0] = ClipBuffer(emu_voice->filt_buffer[0]); - emu_voice->filt_buffer[1] = ClipBuffer(emu_voice->filt_buffer[1]); - - dat = (int32_t)(emu_voice->filt_buffer[1] >> 8); - if (dat > 32767) { dat = 32767; } - else if (dat < -32768) { dat = -32768; } - -#endif - - } - if ((emu8k->hwcf3 & 0x04) && !CCCA_DMA_ACTIVE(emu_voice->ccca)) - { - /*volume and pan*/ - dat = (dat * emu_voice->cvcf_curr_volume) >> 16; - - (*buf++) += (dat * emu_voice->vol_l) >> 8; - (*buf++) += (dat * emu_voice->vol_r) >> 8; - - /* Effects section */ - if (emu_voice->ptrx_revb_send > 0) - { - emu8k->reverb_in_buffer[pos] += (dat * emu_voice->ptrx_revb_send) >> 8; - } - if (emu_voice->csl_chor_send > 0) - { - emu8k->chorus_in_buffer[pos] += (dat * emu_voice->csl_chor_send) >> 8; - } - } - } - - if (emu_voice->env_engine_on) - { - int32_t attenuation = emu_voice->initial_att; - int32_t filtercut = emu_voice->initial_filter; - int32_t currentpitch = emu_voice->ip; - /* run envelopes */ - emu8k_envelope_t* volenv = &emu_voice->vol_envelope; - switch (volenv->state) - { - case ENV_DELAY: - volenv->delay_samples--; - if (volenv->delay_samples <= 0) - { - volenv->state = ENV_ATTACK; - volenv->delay_samples = 0; - } - attenuation = 0x1FFFFF; - break; - - case ENV_ATTACK: - /* Attack amount is in linear amplitude */ - volenv->value_amp_hz += volenv->attack_amount_amp_hz; - if (volenv->value_amp_hz >= (1 << 21)) - { - volenv->value_amp_hz = 1 << 21; - volenv->value_db_oct = 0; - if (volenv->hold_samples) - { - volenv->state = ENV_HOLD; - } - else - { - /* RAMP_UP since db value is inverted and it is 0 at this point. */ - volenv->state = ENV_RAMP_UP; - } - } - attenuation += env_vol_amplitude_to_db[volenv->value_amp_hz >> 5] << 5; - break; - - case ENV_HOLD: - volenv->hold_samples--; - if (volenv->hold_samples <= 0) - { - volenv->state = ENV_RAMP_UP; - } - attenuation += volenv->value_db_oct; - break; - - case ENV_RAMP_DOWN: - /* Decay/release amount is in fraction of dBs and is always positive */ - volenv->value_db_oct -= volenv->ramp_amount_db_oct; - if (volenv->value_db_oct <= volenv->sustain_value_db_oct) - { - volenv->value_db_oct = volenv->sustain_value_db_oct; - volenv->state = ENV_SUSTAIN; - } - attenuation += volenv->value_db_oct; - break; - - case ENV_RAMP_UP: - /* Decay/release amount is in fraction of dBs and is always positive */ - volenv->value_db_oct += volenv->ramp_amount_db_oct; - if (volenv->value_db_oct >= volenv->sustain_value_db_oct) - { - volenv->value_db_oct = volenv->sustain_value_db_oct; - volenv->state = ENV_SUSTAIN; - } - attenuation += volenv->value_db_oct; - break; - - case ENV_SUSTAIN: - attenuation += volenv->value_db_oct; - break; - - case ENV_STOPPED: - attenuation = 0x1FFFFF; - break; - } - - emu8k_envelope_t* modenv = &emu_voice->mod_envelope; - switch (modenv->state) - { - case ENV_DELAY: - modenv->delay_samples--; - if (modenv->delay_samples <= 0) - { - modenv->state = ENV_ATTACK; - modenv->delay_samples = 0; - } - break; - - case ENV_ATTACK: - /* Attack amount is in linear amplitude */ - modenv->value_amp_hz += modenv->attack_amount_amp_hz; - modenv->value_db_oct = env_mod_hertz_to_octave[modenv->value_amp_hz >> 5] << 5; - if (modenv->value_amp_hz >= (1 << 21)) - { - modenv->value_amp_hz = 1 << 21; - modenv->value_db_oct = 1 << 21; - if (modenv->hold_samples) - { - modenv->state = ENV_HOLD; - } - else - { - modenv->state = ENV_RAMP_DOWN; - } - } - break; - - case ENV_HOLD: - modenv->hold_samples--; - if (modenv->hold_samples <= 0) - { - modenv->state = ENV_RAMP_UP; - } - break; - - case ENV_RAMP_DOWN: - /* Decay/release amount is in fraction of octave and is always positive */ - modenv->value_db_oct -= modenv->ramp_amount_db_oct; - if (modenv->value_db_oct <= modenv->sustain_value_db_oct) - { - modenv->value_db_oct = modenv->sustain_value_db_oct; - modenv->state = ENV_SUSTAIN; - } - break; - - case ENV_RAMP_UP: - /* Decay/release amount is in fraction of octave and is always positive */ - modenv->value_db_oct += modenv->ramp_amount_db_oct; - if (modenv->value_db_oct >= modenv->sustain_value_db_oct) - { - modenv->value_db_oct = modenv->sustain_value_db_oct; - modenv->state = ENV_SUSTAIN; - } - break; - } - - /* run lfos */ - if (emu_voice->lfo1_delay_samples) - { - emu_voice->lfo1_delay_samples--; - } - else - { - emu_voice->lfo1_count.addr += emu_voice->lfo1_speed; - emu_voice->lfo1_count.int_address &= 0xFFFF; - } - if (emu_voice->lfo2_delay_samples) - { - emu_voice->lfo2_delay_samples--; - } - else - { - emu_voice->lfo2_count.addr += emu_voice->lfo2_speed; - emu_voice->lfo2_count.int_address &= 0xFFFF; - } - - if (emu_voice->fixed_modenv_pitch_height) - { - /* modenv range 1<<21, pitch height range 1<<14 desired range 0x1000 (+/-one octave) */ - currentpitch += ((modenv->value_db_oct >> 9) * emu_voice->fixed_modenv_pitch_height) >> 14; - } - - if (emu_voice->fixed_lfo1_vibrato) - { - /* table range 1<<15, pitch mod range 1<<14 desired range 0x1000 (+/-one octave) */ - int32_t lfo1_vibrato = (lfotable[emu_voice->lfo1_count.int_address] * emu_voice->fixed_lfo1_vibrato) >> 17; - currentpitch += lfo1_vibrato; - } - if (emu_voice->fixed_lfo2_vibrato) - { - /* table range 1<<15, pitch mod range 1<<14 desired range 0x1000 (+/-one octave) */ - int32_t lfo2_vibrato = (lfotable[emu_voice->lfo2_count.int_address] * emu_voice->fixed_lfo2_vibrato) >> 17; - currentpitch += lfo2_vibrato; - } - - if (emu_voice->fixed_modenv_filter_height) - { - /* modenv range 1<<21, pitch height range 1<<14 desired range 0x200000 (+/-full filter range) */ - filtercut += ((modenv->value_db_oct >> 9) * emu_voice->fixed_modenv_filter_height) >> 5; - } - - if (emu_voice->fixed_lfo1_filt_mod) - { - /* table range 1<<15, pitch mod range 1<<14 desired range 0x100000 (+/-three octaves) */ - int32_t lfo1_filtmod = (lfotable[emu_voice->lfo1_count.int_address] * emu_voice->fixed_lfo1_filt_mod) >> 9; - filtercut += lfo1_filtmod; - } - - if (emu_voice->fixed_lfo1_tremolo) - { - /* table range 1<<15, pitch mod range 1<<14 desired range 0x40000 (+/-12dBs). */ - int32_t lfo1_tremolo = (lfotable[emu_voice->lfo1_count.int_address] * emu_voice->fixed_lfo1_tremolo) >> 11; - attenuation += lfo1_tremolo; - } - - if (currentpitch > 0xFFFF) currentpitch = 0xFFFF; - if (currentpitch < 0) currentpitch = 0; - if (attenuation > 0x1FFFFF) attenuation = 0x1FFFFF; - if (attenuation < 0) attenuation = 0; - if (filtercut > 0x1FFFFF) filtercut = 0x1FFFFF; - if (filtercut < 0) filtercut = 0; - - emu_voice->vtft_vol_target = env_vol_db_to_vol_target[attenuation >> 5]; - emu_voice->vtft_filter_target = filtercut >> 5; - emu_voice->ptrx_pit_target = freqtable[currentpitch] >> 18; - - } -/* -I've recopilated these sentences to get an idea of how to loop - -- Set its PSST register and its CLS register to zero to cause no loops to occur. --Setting the Loop Start Offset and the Loop End Offset to the same value, will cause the oscillator to loop the entire memory. - --Setting the PlayPosition greater than the Loop End Offset, will cause the oscillator to play in reverse, back to the Loop End Offset. - It's pretty neat, but appears to be uncontrollable (the rate at which the samples are played in reverse). - --Note that due to interpolator offset, the actual loop point is one greater than the start address --Note that due to interpolator offset, the actual loop point will end at an address one greater than the loop address --Note that the actual audio location is the point 1 word higher than this value due to interpolation offset --In programs that use the awe, they generally set the loop address as "loopaddress -1" to compensate for the above. -(Note: I am already using address+1 in the interpolators so these things are already as they should.) -*/ - emu_voice->addr.addr += ((uint64_t)emu_voice->cpf_curr_pitch) << 18; - if (emu_voice->addr.addr >= emu_voice->loop_end.addr) - { - emu_voice->addr.int_address -= (emu_voice->loop_end.int_address - emu_voice->loop_start.int_address); - emu_voice->addr.int_address &= EMU8K_MEM_ADDRESS_MASK; - } - - /* TODO: How and when are the target and current values updated */ - emu_voice->cpf_curr_pitch = emu_voice->ptrx_pit_target; - emu_voice->cvcf_curr_volume = emu8k_vol_slide(&emu_voice->volumeslide, emu_voice->vtft_vol_target); - emu_voice->cvcf_curr_filt_ctoff = emu_voice->vtft_filter_target; - } - - /* Update EMU voice registers. */ - emu_voice->ccca = (((uint32_t)emu_voice->ccca_qcontrol) << 24) | emu_voice->addr.int_address; - emu_voice->cpf_curr_frac_addr = emu_voice->addr.fract_address; - - //if ( emu_voice->cvcf_curr_volume != old_vol[c]) { - // pclog("EMUVOL (%d):%d\n", c, emu_voice->cvcf_curr_volume); - // old_vol[c]=emu_voice->cvcf_curr_volume; - //} - //pclog("EMUFILT :%d\n", emu_voice->cvcf_curr_filt_ctoff); - } - - buf = &emu8k->buffer[emu8k->pos * 2]; - emu8k_work_reverb(&emu8k->reverb_in_buffer[emu8k->pos], buf, &emu8k->reverb_engine, new_pos - emu8k->pos); - emu8k_work_chorus(&emu8k->chorus_in_buffer[emu8k->pos], buf, &emu8k->chorus_engine, new_pos - emu8k->pos); - emu8k_work_eq(buf, new_pos - emu8k->pos); - - // Clip signal - for (pos = emu8k->pos; pos < new_pos; pos++) - { - if (buf[0] < -32768) - buf[0] = -32768; - else if (buf[0] > 32767) - buf[0] = 32767; - - if (buf[1] < -32768) - buf[1] = -32768; - else if (buf[1] > 32767) - buf[1] = 32767; - - buf += 2; - } - - /* Update EMU clock. */ - emu8k->sample_count += (new_pos - emu8k->pos); - emu8k->sample_count_virtual = 0; - - emu8k->pos = new_pos; -} - -static void emu8k_init_globals() -{ - int c; - double out; - - /*Create frequency table. (Convert initial pitch register value to a linear speed change) - * The input is encoded such as 0xe000 is center note (no pitch shift) - * and from then on , changing up or down 0x1000 (4096) increments/decrements an octave. - * Note that this is in reference to the 44.1Khz clock that the channels play at. - * The 65536 * 65536 is in order to left-shift the 32bit value to a 64bit value as a 32.32 fixed point. - */ - for (c = 0; c < 0x10000; c++) - { - freqtable[c] = (uint64_t)(exp2((double)(c - 0xe000) / 4096.0) * 65536.0 * 65536.0); - } - /* Shortcut: minimum pitch equals stopped. I don't really know if this is true, but it's better - * since some programs set the pitch to 0 for unused channels. */ - freqtable[0] = 0; - - /* starting at 65535 because it is used for "volume target" register conversion. */ - out = 65535.0; - for (c = 0; c < 256; c++) - { - attentable[c] = (int32_t)out; - out /= sqrt(1.09018); /*0.375 dB steps*/ - } - /* Shortcut: max attenuation is silent, not -96dB. */ - attentable[255] = 0; - - /* Note: these two tables have "db" inverted: 0 dB is max volume, 65535 "db" (-96.32dBFS) is silence. - * Important: Using 65535 as max output value because this is intended to be used with the volume target register! */ - out = 65535.0; - for (c = 0; c < 0x10000; c++) - { - //double db = -(c*6.0205999/65535.0)*16.0; - //out = powf(10.f,db/20.f) * 65536.0; - env_vol_db_to_vol_target[c] = (int32_t)out; - /* calculated from the 65536th root of 65536 */ - out /= 1.00016923970; - } - /* Shortcut: max attenuation is silent, not -96dB. */ - env_vol_db_to_vol_target[0x10000 - 1] = 0; - /* One more position to accept max value being 65536. */ - env_vol_db_to_vol_target[0x10000] = 0; - - for (c = 1; c < 0x10000; c++) - { - out = -680.32142884264 * 20.0 * log10(((double)c) / 65535.0); - env_vol_amplitude_to_db[c] = (int32_t)out; - } - /*Shortcut: max attenuation is silent, not -96dB.*/ - env_vol_amplitude_to_db[0] = 65535; - /* One more position to acceMpt max value being 65536. */ - env_vol_amplitude_to_db[0x10000] = 0; - - for (c = 1; c < 0x10000; c++) - { - out = log2((((double)c) / 0x10000) + 1.0) * 65536.0; - env_mod_hertz_to_octave[c] = (int32_t)out; - } - /*No hertz change, no octave change. */ - env_mod_hertz_to_octave[0] = 0; - /* One more position to accept max value being 65536. */ - env_mod_hertz_to_octave[0x10000] = 65536; - - - /* This formula comes from vince vu/judge dredd's awe32p10 and corresponds to what the freebsd/linux AWE32 driver has. */ - float millis; - for (c = 0; c < 128; c++) - { - if (c == 0) - millis = 0; /* This means never attack. */ - else if (c < 32) - millis = 11878.0 / c; - else - millis = 360 * exp((c - 32) / (16.0 / log(1.0 / 2.0))); - - env_attack_to_samples[c] = 44.1 * millis; - /* This is an alternate formula with linear increments, but probably incorrect: - * millis = (256+4096*(0x7F-c)) */ - } - - /* The LFOs use a triangular waveform starting at zero and going 1/-1/1/-1. - * This table is stored in signed 16bits precision, with a period of 65536 samples */ - for (c = 0; c < 65536; c++) - { - int d = (c + 16384) & 65535; - if (d >= 32768) - lfotable[c] = 32768 + ((32768 - d) * 2); - else - lfotable[c] = (d * 2) - 32768; - } - /* The 65536 * 65536 is in order to left-shift the 32bit value to a 64bit value as a 32.32 fixed point. */ - out = 0.01; - for (c = 0; c < 256; c++) - { - lfofreqtospeed[c] = (uint64_t)(out * 65536.0 / 44100.0 * 65536.0 * 65536.0); - out += 0.042; - } - - for (c = 0; c < 65536; c++) - { - chortable[c] = sin(c * M_PI / 32768.0); - } - - - /* Filter coefficients tables. Note: Values are multiplied by *16777216 to left shift 24 bits. (i.e. 8.24 fixed point) */ - int qidx; - for (qidx = 0; qidx < 16; qidx++) - { - out = 125.0; /* Start at 125Hz */ - for (c = 0; c < 256; c++) - { -#ifdef FILTER_INITIAL - float w0 = sin(2.0*M_PI*out / 44100.0); - /* The value 102.5f has been selected a bit randomly. Pretends to reach 0.2929 at w0 = 1.0 */ - float q = (qidx / 102.5f) * (1.0 + 1.0 / w0); - /* Limit max value. Else it would be 470. */ - if (q > 200) q=200; - filt_coeffs[qidx][c][0] = (int32_t)(w0 * 16777216.0); - filt_coeffs[qidx][c][1] = 16777216.0; - filt_coeffs[qidx][c][2] = (int32_t)((1.0f / (0.7071f + q)) * 16777216.0); -#elif defined FILTER_MOOG - float w0 = sin(2.0 * M_PI * out / 44100.0); - float q_factor = 1.0f - w0; - float p = w0 + 0.8f * w0 * q_factor; - float f = p + p - 1.0f; - float resonance = (1.0 - pow(2.0, -qidx * 24.0 / 90.0)) * 0.8; - float q = resonance * (1.0f + 0.5f * q_factor * (w0 + 5.6f * q_factor * q_factor)); - filt_coeffs[qidx][c][0] = (int32_t)(p * 16777216.0); - filt_coeffs[qidx][c][1] = (int32_t)(f * 16777216.0); - filt_coeffs[qidx][c][2] = (int32_t)(q * 16777216.0); -#elif defined FILTER_CONSTANT - float q = (1.0-pow(2.0,-qidx*24.0/90.0))*0.8; - float coef0 = sin(2.0*M_PI*out / 44100.0); - float coef1 = 1.0 - coef0; - float coef2 = q * (1.0 + 1.0 / coef1); - filt_coeffs[qidx][c][0] = (int32_t)(coef0 * 16777216.0); - filt_coeffs[qidx][c][1] = (int32_t)(coef1 * 16777216.0); - filt_coeffs[qidx][c][2] = (int32_t)(coef2 * 16777216.0); -#endif //FILTER_TYPE - /* 42.66 divisions per octave (the doc says quarter seminotes which is 48, but then it would be almost an octave less) */ - out *= 1.016378315; - /* 42 divisions. This moves the max frequency to 8.5Khz.*/ - //out *= 1.0166404394; - /* This is a linear increment method, that corresponds to the NRPN table, but contradicts the EMU8KPRM doc: */ - //out = 100.0 + (c+1.0)*31.25; //31.25Hz steps */ - } - } - - /* Cubic Resampling ( 4point cubic spline) */ - double const resdouble = 1.0 / (double)CUBIC_RESOLUTION; - for (c = 0; c < CUBIC_RESOLUTION; c++) - { - double x = (double)c * resdouble; - /* Cubic resolution is made of four table, but I've put them all in one table to optimize memory access. */ - cubic_table[c * 4] = (-0.5 * x * x * x + x * x - 0.5 * x); - cubic_table[c * 4 + 1] = (1.5 * x * x * x - 2.5 * x * x + 1.0); - cubic_table[c * 4 + 2] = (-1.5 * x * x * x + 2.0 * x * x + 0.5 * x); - cubic_table[c * 4 + 3] = (0.5 * x * x * x - 0.5 * x * x); - } -} - -emu8k_t* emu8k_alloc(void *rom, size_t onboard_ram) -{ - emu8k_t *emu8k = RTMemAlloc(sizeof(emu8k_t)); - AssertPtrReturn(emu8k, NULL); - - emu8k_init_globals(); - - emu8k->rom = rom; - - /*AWE-DUMP creates ROM images offset by 2 bytes, so if we detect this - then correct it*/ - if (emu8k->rom[3] == 0x314d && emu8k->rom[4] == 0x474d) - { - memmove(&emu8k->rom[0], &emu8k->rom[1], (1024 * 1024) - 2); - emu8k->rom[0x7ffff] = 0; - } - - emu8k->empty = RTMemAllocZ(2*BLOCK_SIZE_WORDS); - AssertPtr(emu8k->empty); - - // Initialize ram_pointers - int j = 0; - for (; j < 0x8; j++) - { - emu8k->ram_pointers[j] = emu8k->rom + (j * BLOCK_SIZE_WORDS); - } - for (; j < 0x20; j++) - { - emu8k->ram_pointers[j] = emu8k->empty; - } - - if (onboard_ram > 0) - { - /*Clip to 28MB, since that's the max that we can address. */ - Assert(onboard_ram <= 0x7000); - emu8k->ram = RTMemAllocZ(onboard_ram * _1K); - AssertPtr(emu8k->ram); - - const int i_end = onboard_ram >> 7; - int i = 0; - for (; i < i_end; i++, j++) - { - emu8k->ram_pointers[j] = emu8k->ram + (i * BLOCK_SIZE_WORDS); - } - emu8k->ram_end_addr = EMU8K_RAM_MEM_START + (onboard_ram << 9); - } - else - { - emu8k->ram = NULL; - emu8k->ram_end_addr = EMU8K_RAM_MEM_START; - } - for (; j < 0x100; j++) - { - emu8k->ram_pointers[j] = emu8k->empty; - - } - - return emu8k; -} - -void emu8k_free(emu8k_t* emu8k) -{ - RTMemFree(emu8k->empty); - RTMemFree(emu8k->ram); - - RTMemFree(emu8k); -} - -void emu8k_reset(emu8k_t* emu8k) -{ - /* NOTE! read_pos and buffer content is implicitly initialized to zero by the sb_t structure memset on sb_awe32_init() */ - emu8k->reverb_engine.reflections[0].bufsize = 2 * REV_BUFSIZE_STEP; - emu8k->reverb_engine.reflections[1].bufsize = 4 * REV_BUFSIZE_STEP; - emu8k->reverb_engine.reflections[2].bufsize = 8 * REV_BUFSIZE_STEP; - emu8k->reverb_engine.reflections[3].bufsize = 13 * REV_BUFSIZE_STEP; - emu8k->reverb_engine.reflections[4].bufsize = 19 * REV_BUFSIZE_STEP; - emu8k->reverb_engine.reflections[5].bufsize = 26 * REV_BUFSIZE_STEP; - - /*This is a bit random.*/ - for (int c = 0; c < 4; c++) - { - emu8k->reverb_engine.allpass[3 - c].feedback = 0.5; - emu8k->reverb_engine.allpass[3 - c].bufsize = (4 * c) * REV_BUFSIZE_STEP + 55; - emu8k->reverb_engine.allpass[7 - c].feedback = 0.5; - emu8k->reverb_engine.allpass[7 - c].bufsize = (4 * c) * REV_BUFSIZE_STEP + 55; - } - - /* Even when the documentation says that this has to be written by applications to initialize the card, - * several applications and drivers ( aweman on windows, linux oss driver..) read it to detect an AWE card. */ - emu8k->hwcf1 = 0x59; - emu8k->hwcf2 = 0x20; - /* Initial state is muted. 0x04 is unmuted. */ - emu8k->hwcf3 = 0x00; - - emu8k->sample_count = 0; - emu8k->sample_count_virtual = 0; -} - -void emu8k_render(emu8k_t *emu8k, int16_t *buf, size_t frames) -{ - emu8k_update(emu8k, frames); - - // Convert from int32_t samples to int16_t - for (unsigned int i = 0; i < frames * 2; i++) - { - buf[i] = RT_CLAMP(emu8k->buffer[i], INT16_MIN, INT16_MAX); - } - - emu8k->pos = 0; -} - -void emu8k_update_virtual_sample_count(emu8k_t *emu8k, uint16_t sample_count) -{ -#if 0 - if (sample_count > emu8k->sample_count_virtual + 1) { - Log5Func(("big vsc increment: %u : %u -> %u\n", sample_count - emu8k->sample_count_virtual, emu8k->sample_count_virtual, sample_count)); - } -#endif - emu8k->sample_count_virtual = sample_count; -} +/* + * PCem - IBM PC emulator + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* + * Portions: + * VMusic - a VirtualBox extension pack with various music devices + * Copyright (C) 2022 Javier S. Pedro + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#define LOG_ENABLED 1 +#define LOG_ENABLE_FLOW 1 +#define LOG_GROUP LOG_GROUP_DEV_SB16 +#include +#include +#include +#include + +#include "emu8k_internal.h" + +#define pclog(...) LogFlow((__VA_ARGS__)) + +#if !defined FILTER_INITIAL && !defined FILTER_MOOG && !defined FILTER_CONSTANT +//#define FILTER_INITIAL +#define FILTER_MOOG +//#define FILTER_CONSTANT +#endif + +#if !defined RESAMPLER_LINEAR && !defined RESAMPLER_CUBIC +//#define RESAMPLER_LINEAR +#define RESAMPLER_CUBIC +#endif + +//#define EMU8K_DEBUG_REGISTERS + +char* PORT_NAMES[][8] = + { + /* Data 0 ( 0x620/0x622) */ + { "AWE_CPF", + "AWE_PTRX", + "AWE_CVCF", + "AWE_VTFT", + "Unk-620-4", + "Unk-620-5", + "AWE_PSST", + "AWE_CSL", + }, + /* Data 1 0xA20 */ + { "AWE_CCCA", + 0, + /* + "AWE_HWCF4" + "AWE_HWCF5" + "AWE_HWCF6" + "AWE_HWCF7" + "AWE_SMALR" + "AWE_SMARR" + "AWE_SMALW" + "AWE_SMARW" + "AWE_SMLD" + "AWE_SMRD" + "AWE_WC" + "AWE_HWCF1" + "AWE_HWCF2" + "AWE_HWCF3" + */ + 0,//"AWE_INIT1", + 0,//"AWE_INIT3", + "AWE_ENVVOL", + "AWE_DCYSUSV", + "AWE_ENVVAL", + "AWE_DCYSUS", + }, + /* Data 2 0xA22 */ + { "AWE_CCCA", + 0, + 0,//"AWE_INIT2", + 0,//"AWE_INIT4", + "AWE_ATKHLDV", + "AWE_LFO1VAL", + "AWE_ATKHLD", + "AWE_LFO2VAL", + }, + /* Data 3 0xE20 */ + { "AWE_IP", + "AWE_IFATN", + "AWE_PEFE", + "AWE_FMMOD", + "AWE_TREMFRQ", + "AWE_FM2FRQ2", + 0, + 0, + }, + }; + +enum +{ + ENV_STOPPED = 0, + ENV_DELAY = 1, + ENV_ATTACK = 2, + ENV_HOLD = 3, + //ENV_DECAY = 4, + ENV_SUSTAIN = 5, + //ENV_RELEASE = 6, + ENV_RAMP_DOWN = 7, + ENV_RAMP_UP = 8 +}; + +static int random_helper = 0; +static int dmareadbit = 0; +static int dmawritebit = 0; + + +/* cubic and linear tables resolution. Note: higher than 10 does not improve the result. */ +#define CUBIC_RESOLUTION_LOG 10 +#define CUBIC_RESOLUTION (1<> 15 to move back to +/-1 range). */ +static int32_t lfotable[65536]; +/* Table to transform the speed parameter to emu8k_mem_internal_t range. */ +static int64_t lfofreqtospeed[256]; + +/* LFO used for the chorus. a sine wave.(signed 16bits with 32768 max int. >> 15 to move back to +/-1 range). */ +static double chortable[65536]; + +static const int REV_BUFSIZE_STEP = 242; + +/* These lines come from the awe32faq, describing the NRPN control for the initial filter + * where it describes a linear increment filter instead of an octave-incremented one. + * NRPN LSB 21 (Initial Filter Cutoff) + * Range : [0, 127] + * Unit : 62Hz + * Filter cutoff from 100Hz to 8000Hz + + * This table comes from the awe32faq, describing the NRPN control for the filter Q. + * I don't know if is meant to be interpreted as the actual measured output of the + * filter or what. Especially, I don't understand the "low" and "high" ranges. + * What is otherwise documented is that the Q ranges from 0dB to 24dB and the attenuation + * is half of the Q ( i.e. for 12dB Q, attenuate the input signal with -6dB) +Coeff Low Fc(Hz)Low Q(dB)High Fc(kHz)High Q(dB)DC Attenuation(dB) +* 0 92 5 Flat Flat -0.0 +* 1 93 6 8.5 0.5 -0.5 +* 2 94 8 8.3 1 -1.2 +* 3 95 10 8.2 2 -1.8 +* 4 96 11 8.1 3 -2.5 +* 5 97 13 8.0 4 -3.3 +* 6 98 14 7.9 5 -4.1 +* 7 99 16 7.8 6 -5.5 +* 8 100 17 7.7 7 -6.0 +* 9 100 19 7.5 9 -6.6 +* 10 100 20 7.4 10 -7.2 +* 11 100 22 7.3 11 -7.9 +* 12 100 23 7.2 13 -8.5 +* 13 100 25 7.1 15 -9.3 +* 14 100 26 7.1 16 -10.1 +* 15 100 28 7.0 18 -11.0 +* +* Attenuation as above, codified in amplitude.*/ +static int32_t filter_atten[16] = + { + 65536, 61869, 57079, 53269, 49145, 44820, 40877, 34792, 32845, 30653, 28607, + 26392, 24630, 22463, 20487, 18470 + }; + +/*Coefficients for the filters for a defined Q and cutoff.*/ +static int32_t filt_coeffs[16][256][3]; + +#define READ16_SWITCH(addr, var) switch ((addr) & 2) \ + { \ + case 0: ret = (var) & 0xffff; break; \ + case 2: ret = ((var) >> 16) & 0xffff; break; \ + } + +#define WRITE16_SWITCH(addr, var, val) switch ((addr) & 2) \ + { \ + case 0: var = (var & 0xffff0000) | (val); break; \ + case 2: var = (var & 0x0000ffff) | ((val) << 16); break; \ + } + +#ifdef EMU8K_DEBUG_REGISTERS +static uint32_t last_read = 0; +static uint32_t last_write = 0; +static uint32_t rep_count_r = 0; +static uint32_t rep_count_w = 0; + +# define READ16(addr, var) READ16_SWITCH(addr, var) \ + { \ + const char *name=0; \ + switch(addr&0xF02) \ + { \ + case 0x600: case 0x602: \ + name = PORT_NAMES[0][emu8k->cur_reg]; \ + break; \ + case 0xA00: \ + name = PORT_NAMES[1][emu8k->cur_reg]; \ + break; \ + case 0xA02: \ + name = PORT_NAMES[2][emu8k->cur_reg]; \ + break; \ + } \ + if (name == 0) \ + { \ + /*pclog("EMU8K READ %04X-%02X(%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice, emu8k->cur_voice,ret);*/ \ + } \ + else \ + { \ + pclog("EMU8K READ %s(%d) (%d): %04X\n",name, (addr&0x2), emu8k->cur_voice, ret); \ + }\ + } +# define WRITE16(addr, var, val) WRITE16_SWITCH(addr, var, val) \ + { \ + const char *name=0; \ + switch(addr&0xF02) \ + { \ + case 0x600: case 0x602: \ + name = PORT_NAMES[0][emu8k->cur_reg]; \ + break; \ + case 0xA00: \ + name = PORT_NAMES[1][emu8k->cur_reg]; \ + break; \ + case 0xA02: \ + name = PORT_NAMES[2][emu8k->cur_reg]; \ + break; \ + } \ + if (name == 0) \ + { \ + /*pclog("EMU8K WRITE %04X-%02X(%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice,emu8k->cur_voice, val);*/ \ + } \ + else \ + { \ + pclog("EMU8K WRITE %s(%d) (%d): %04X\n",name, (addr&0x2), emu8k->cur_voice,val); \ + }\ + } + +#else +# define READ16(addr, var) READ16_SWITCH(addr, var) +# define WRITE16(addr, var, val) WRITE16_SWITCH(addr, var, val) +#endif //EMU8K_DEBUG_REGISTERS + +static inline int16_t EMU8K_READ(emu8k_t* emu8k, uint32_t addr) +{ + const emu8k_mem_pointers_t addrmem = {{ addr }}; + return emu8k->ram_pointers[addrmem.hb_address][addrmem.lw_address]; +} + +static inline int16_t EMU8K_READ_INTERP_LINEAR(emu8k_t* emu8k, uint32_t int_addr, uint16_t fract) +{ + /* The interpolation in AWE32 used a so-called patented 3-point interpolation + * ( I guess some sort of spline having one point before and one point after). + * Also, it has the consequence that the playback is delayed by one sample. + * I simulate the "one sample later" than the address with addr+1 and addr+2 + * instead of +0 and +1 */ + int16_t dat1 = EMU8K_READ(emu8k, int_addr + 1); + int32_t dat2 = EMU8K_READ(emu8k, int_addr + 2); + dat1 += ((dat2 - (int32_t)dat1) * fract) >> 16; + return dat1; +} + +static inline int32_t EMU8K_READ_INTERP_CUBIC(emu8k_t* emu8k, uint32_t int_addr, uint16_t fract) +{ + /*Since there are four floats in the table for each fraction, the position is 16byte aligned. */ + fract >>= 16 - CUBIC_RESOLUTION_LOG; + fract <<= 2; + + /* TODO: I still have to verify how this works, but I think that + * the card could use two oscillators (usually 31 and 32) where it would + * be writing the OPL3 output, and to which, chorus and reverb could be applied to get + * those effects for OPL3 sounds.*/ +// if ((addr & EMU8K_FM_MEM_ADDRESS) == EMU8K_FM_MEM_ADDRESS) {} + + /* This is cubic interpolation. + * Not the same than 3-point interpolation, but a better approximation than linear + * interpolation. + * Also, it takes into account the "Note that the actual audio location is the point + * 1 word higher than this value due to interpolation offset". + * That's why the pointers are 0, 1, 2, 3 and not -1, 0, 1, 2 */ + int32_t dat2 = EMU8K_READ(emu8k, int_addr + 1); + const float* table = &cubic_table[fract]; + const int32_t dat1 = EMU8K_READ(emu8k, int_addr); + const int32_t dat3 = EMU8K_READ(emu8k, int_addr + 2); + const int32_t dat4 = EMU8K_READ(emu8k, int_addr + 3); + /* Note: I've ended using float for the table values to avoid some cases of integer overflow. */ + dat2 = dat1 * table[0] + dat2 * table[1] + dat3 * table[2] + dat4 * table[3]; + return dat2; +} + +static inline void EMU8K_WRITE(emu8k_t* emu8k, uint32_t addr, uint16_t val) +{ + addr &= EMU8K_MEM_ADDRESS_MASK; + if (!emu8k->ram || addr < EMU8K_RAM_MEM_START || addr >= EMU8K_FM_MEM_ADDRESS) + return; + + /* It looks like if an application writes to a memory part outside of the available + * amount on the card, it wraps, and opencubicplayer uses that to detect the amount + * of memory, as opposed to simply check at the address that it has just tried to write. */ + while (addr >= emu8k->ram_end_addr) + addr -= emu8k->ram_end_addr - EMU8K_RAM_MEM_START; + + emu8k->ram[addr - EMU8K_RAM_MEM_START] = val; +} + +uint16_t emu8k_inw(emu8k_t *emu8k, uint16_t addr) +{ + uint16_t ret = 0xffff; + +#ifdef EMU8K_DEBUG_REGISTERS + if (addr == 0xE22) + { + pclog("EMU8K READ POINTER: %d\n", + ((0x80 | ((random_helper + 1) & 0x1F)) << 8) | (emu8k->cur_reg << 5) | emu8k->cur_voice); + } + else if ((addr&0xF00) == 0x600) + { + /* These are automatically reported by READ16 */ + if (rep_count_r>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_r); + rep_count_r=0; + } + last_read=0; + } + else if ((addr&0xF00) == 0xA00 && emu8k->cur_reg == 0) + { + /* These are automatically reported by READ16 */ + if (rep_count_r>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_r); + rep_count_r=0; + } + last_read=0; + } + else if ((addr&0xF00) == 0xA00 && emu8k->cur_reg == 1) + { + uint32_t tmpz = ((addr&0xF00) << 16)|(emu8k->cur_reg<<5); + if (tmpz != last_read) + { + if (rep_count_r>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_r); + rep_count_r=0; + } + last_read=tmpz; + pclog("EMU8K READ RAM I/O or configuration or clock \n"); + } + //pclog("EMU8K READ %04X-%02X(%d/%d)\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice); + } + else if ((addr&0xF00) == 0xA00 && (emu8k->cur_reg == 2 || emu8k->cur_reg == 3)) + { + uint32_t tmpz = ((addr&0xF00) << 16); + if (tmpz != last_read) + { + if (rep_count_r>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_r); + rep_count_r=0; + } + last_read=tmpz; + pclog("EMU8K READ INIT \n"); + } + //pclog("EMU8K READ %04X-%02X(%d/%d)\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice); + } + else + { + uint32_t tmpz = (addr << 16)|(emu8k->cur_reg<<5)| emu8k->cur_voice; + if (tmpz != last_read) + { + char* name = 0; + uint16_t val = 0xBAAD; + if (addr == 0xA20) + { + name = PORT_NAMES[1][emu8k->cur_reg]; + switch (emu8k->cur_reg) + { + case 2: val = emu8k->init1[emu8k->cur_voice]; break; + case 3: val = emu8k->init3[emu8k->cur_voice]; break; + case 4: val = emu8k->voice[emu8k->cur_voice].envvol; break; + case 5: val = emu8k->voice[emu8k->cur_voice].dcysusv; break; + case 6: val = emu8k->voice[emu8k->cur_voice].envval; break; + case 7: val = emu8k->voice[emu8k->cur_voice].dcysus; break; + } + } + else if (addr == 0xA22) + { + name = PORT_NAMES[2][emu8k->cur_reg]; + switch (emu8k->cur_reg) + { + case 2: val = emu8k->init2[emu8k->cur_voice]; break; + case 3: val = emu8k->init4[emu8k->cur_voice]; break; + case 4: val = emu8k->voice[emu8k->cur_voice].atkhldv; break; + case 5: val = emu8k->voice[emu8k->cur_voice].lfo1val; break; + case 6: val = emu8k->voice[emu8k->cur_voice].atkhld; break; + case 7: val = emu8k->voice[emu8k->cur_voice].lfo2val; break; + } + } + else if (addr == 0xE20) + { + name = PORT_NAMES[3][emu8k->cur_reg]; + switch (emu8k->cur_reg) + { + case 0: val = emu8k->voice[emu8k->cur_voice].ip; break; + case 1: val = emu8k->voice[emu8k->cur_voice].ifatn; break; + case 2: val = emu8k->voice[emu8k->cur_voice].pefe; break; + case 3: val = emu8k->voice[emu8k->cur_voice].fmmod; break; + case 4: val = emu8k->voice[emu8k->cur_voice].tremfrq; break; + case 5: val = emu8k->voice[emu8k->cur_voice].fm2frq2;break; + case 6: val = 0xffff; break; + case 7: val = 0x1c | ((emu8k->id & 0x0002) ? 0xff02 : 0); break; + } + } + if (rep_count_r>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_r); + } + if (name == 0) + { + pclog("EMU8K READ %04X-%02X(%d/%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice,val); + } + else + { + pclog("EMU8K READ %s (%d): %04X\n",name,emu8k->cur_voice, val); + } + + rep_count_r=0; + last_read=tmpz; + } + rep_count_r++; + } +#endif // EMU8K_DEBUG_REGISTERS + + switch (addr & 0xF02) + { + case 0x600: + case 0x602: /*Data0. also known as BLASTER+0x400 and EMU+0x000 */ + switch (emu8k->cur_reg) + { + case 0: + READ16(addr, emu8k->voice[emu8k->cur_voice].cpf); + return ret; + + case 1: + READ16(addr, emu8k->voice[emu8k->cur_voice].ptrx); + return ret; + + case 2: + READ16(addr, emu8k->voice[emu8k->cur_voice].cvcf); + return ret; + + case 3: + READ16(addr, emu8k->voice[emu8k->cur_voice].vtft); + return ret; + + case 4: + READ16(addr, emu8k->voice[emu8k->cur_voice].unknown_data0_4); + return ret; + + case 5: + READ16(addr, emu8k->voice[emu8k->cur_voice].unknown_data0_5); + return ret; + + case 6: + READ16(addr, emu8k->voice[emu8k->cur_voice].psst); + return ret; + + case 7: + READ16(addr, emu8k->voice[emu8k->cur_voice].csl); + return ret; + } + break; + + case 0xA00: /*Data1. also known as BLASTER+0x800 and EMU+0x400 */ + switch (emu8k->cur_reg) + { + case 0: + READ16(addr, emu8k->voice[emu8k->cur_voice].ccca); + return ret; + + case 1: + switch (emu8k->cur_voice) + { + case 9: + READ16(addr, emu8k->hwcf4); + return ret; + case 10: + READ16(addr, emu8k->hwcf5); + return ret; + /* Actually, these two might be command words rather than registers, or some LFO position/buffer reset.*/ + case 13: + READ16(addr, emu8k->hwcf6); + return ret; + case 14: + READ16(addr, emu8k->hwcf7); + return ret; + + case 20: + READ16(addr, emu8k->smalr); + return ret; + case 21: + READ16(addr, emu8k->smarr); + return ret; + case 22: + READ16(addr, emu8k->smalw); + return ret; + case 23: + READ16(addr, emu8k->smarw); + return ret; + + case 26: + { + uint16_t val = emu8k->smld_buffer; + emu8k->smld_buffer = EMU8K_READ(emu8k, emu8k->smalr); + emu8k->smalr = (emu8k->smalr + 1) & EMU8K_MEM_ADDRESS_MASK; + return val; + } + + /*The EMU8000 PGM describes the return values of these registers as 'a VLSI error'*/ + case 29: /*Configuration Word 1*/ + return (emu8k->hwcf1 & 0xfe) | (emu8k->hwcf3 & 0x01); + case 30: /*Configuration Word 2*/ + return ((emu8k->hwcf2 >> 4) & 0x0e) | (emu8k->hwcf1 & 0x01) | ((emu8k->hwcf3 & 0x02) ? 0x10 : 0) | ((emu8k->hwcf3 & 0x04) ? 0x40 : 0) + | ((emu8k->hwcf3 & 0x08) ? 0x20 : 0) | ((emu8k->hwcf3 & 0x10) ? 0x80 : 0); + case 31: /*Configuration Word 3*/ + return emu8k->hwcf2 & 0x1f; + } + break; + + case 2: + return emu8k->init1[emu8k->cur_voice]; + + case 3: + return emu8k->init3[emu8k->cur_voice]; + + case 4: + return emu8k->voice[emu8k->cur_voice].envvol; + + case 5: + return emu8k->voice[emu8k->cur_voice].dcysusv; + + case 6: + return emu8k->voice[emu8k->cur_voice].envval; + + case 7: + return emu8k->voice[emu8k->cur_voice].dcysus; + } + break; + + case 0xA02: /*Data2. also known as BLASTER+0x802 and EMU+0x402 */ + switch (emu8k->cur_reg) + { + case 0: + READ16(addr, emu8k->voice[emu8k->cur_voice].ccca); + return ret; + + case 1: + switch (emu8k->cur_voice) + { + case 9: + READ16(addr, emu8k->hwcf4); + return ret; + case 10: + READ16(addr, emu8k->hwcf5); + return ret; + /* Actually, these two might be command words rather than registers, or some LFO position/buffer reset. */ + case 13: + READ16(addr, emu8k->hwcf6); + return ret; + case 14: + READ16(addr, emu8k->hwcf7); + return ret; + + /* Simulating empty/full bits by unsetting it once read. */ + case 20: + READ16(addr, emu8k->smalr | dmareadbit); + /* xor with itself to set to zero faster. */ + dmareadbit ^= dmareadbit; + return ret; + case 21: + READ16(addr, emu8k->smarr | dmareadbit); + /* xor with itself to set to zero faster.*/ + dmareadbit ^= dmareadbit; + return ret; + case 22: + READ16(addr, emu8k->smalw | dmawritebit); + /*xor with itself to set to zero faster.*/ + dmawritebit ^= dmawritebit; + return ret; + case 23: + READ16(addr, emu8k->smarw | dmawritebit); + /*xor with itself to set to zero faster.*/ + dmawritebit ^= dmawritebit; + return ret; + + case 26: + { + uint16_t val = emu8k->smrd_buffer; + emu8k->smrd_buffer = EMU8K_READ(emu8k, emu8k->smarr); + emu8k->smarr = (emu8k->smarr + 1) & EMU8K_MEM_ADDRESS_MASK; + return val; + } + /*TODO: We need to improve the precision of this clock, since + it is used by programs to wait. Not critical, but should help reduce + the amount of calls and wait time */ + case 27: /*Sample Counter ( 44Khz clock) */ + return emu8k->sample_count + emu8k->sample_count_virtual; + } + break; + + case 2: + return emu8k->init2[emu8k->cur_voice]; + + case 3: + return emu8k->init4[emu8k->cur_voice]; + + case 4: + return emu8k->voice[emu8k->cur_voice].atkhldv; + + case 5: + return emu8k->voice[emu8k->cur_voice].lfo1val; + + case 6: + return emu8k->voice[emu8k->cur_voice].atkhld; + + case 7: + return emu8k->voice[emu8k->cur_voice].lfo2val; + } + break; + + case 0xE00: /*Data3. also known as BLASTER+0xC00 and EMU+0x800 */ + switch (emu8k->cur_reg) + { + case 0: + return emu8k->voice[emu8k->cur_voice].ip; + + case 1: + return emu8k->voice[emu8k->cur_voice].ifatn; + + case 2: + return emu8k->voice[emu8k->cur_voice].pefe; + + case 3: + return emu8k->voice[emu8k->cur_voice].fmmod; + + case 4: + return emu8k->voice[emu8k->cur_voice].tremfrq; + + case 5: + return emu8k->voice[emu8k->cur_voice].fm2frq2; + + case 6: + return 0xffff; + + case 7: /*ID?*/ + return 0x1c | ((emu8k->id & 0x0002) ? 0xff02 : 0); + } + break; + + case 0xE02: /* Pointer. also known as BLASTER+0xC02 and EMU+0x802 */ + /* LS five bits = channel number, next 3 bits = register number + * and MS 8 bits = VLSI test register. + * Impulse tracker tests the non variability of the LS byte that it has set, and the variability + * of the MS byte to determine that it really is an AWE32. + * cubic player has a similar code, where it waits until value & 0x1000 is nonzero, and then waits again until it changes to zero.*/ + random_helper = (random_helper + 1) & 0x1F; + return ((0x80 | random_helper) << 8) | (emu8k->cur_reg << 5) | emu8k->cur_voice; + } + pclog("EMU8K READ : Unknown register read: %04X-%02X(%d/%d) \n", addr, (emu8k->cur_reg << 5) | emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice); + return 0xffff; +} + +void emu8k_outw(emu8k_t *emu8k, uint16_t addr, uint16_t val) +{ +#if 0 + /*TODO: I would like to not call this here, but i found it was needed or else cubic player would not finish opening (take a looot more of time than usual). + * Basically, being here means that the audio is generated in the emulation thread, instead of the audio thread.*/ + emu8k_update(emu8k); +#endif + +#ifdef EMU8K_DEBUG_REGISTERS + if (addr == 0xE22) + { + //pclog("EMU8K WRITE POINTER: %d\n", val); + } + else if ((addr&0xF00) == 0x600) + { + /* These are automatically reported by WRITE16 */ + if (rep_count_w>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_w); + rep_count_w=0; + } + last_write=0; + } + else if ((addr&0xF00) == 0xA00 && emu8k->cur_reg == 0) + { + /* These are automatically reported by WRITE16 */ + if (rep_count_w>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_w); + rep_count_w=0; + } + last_write=0; + } + else if ((addr&0xF00) == 0xA00 && emu8k->cur_reg == 1) + { + uint32_t tmpz = ((addr&0xF00) << 16)|(emu8k->cur_reg<<5); + if (tmpz != last_write) + { + if (rep_count_w>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_w); + rep_count_w=0; + } + last_write=tmpz; + pclog("EMU8K WRITE RAM I/O or configuration \n"); + } + //pclog("EMU8K WRITE %04X-%02X(%d/%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice,emu8k->cur_reg,emu8k->cur_voice, val); + } + else if ((addr&0xF00) == 0xA00 && (emu8k->cur_reg == 2 || emu8k->cur_reg == 3)) + { + uint32_t tmpz = ((addr&0xF00) << 16); + if (tmpz != last_write) + { + if (rep_count_w>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_w); + rep_count_w=0; + } + last_write=tmpz; + pclog("EMU8K WRITE INIT \n"); + } + //pclog("EMU8K WRITE %04X-%02X(%d/%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice,emu8k->cur_reg,emu8k->cur_voice, val); + } + else if (addr != 0xE22) + { + uint32_t tmpz = (addr << 16)|(emu8k->cur_reg<<5)| emu8k->cur_voice; + //if (tmpz != last_write) + if(1) + { + char* name = 0; + if (addr == 0xA20) + { + name = PORT_NAMES[1][emu8k->cur_reg]; + } + else if (addr == 0xA22) + { + name = PORT_NAMES[2][emu8k->cur_reg]; + } + else if (addr == 0xE20) + { + name = PORT_NAMES[3][emu8k->cur_reg]; + } + + if (rep_count_w>1) + { + pclog("EMU8K ...... for %d times\n", rep_count_w); + } + if (name == 0) + { + pclog("EMU8K WRITE %04X-%02X(%d/%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice,emu8k->cur_reg,emu8k->cur_voice, val); + } + else + { + pclog("EMU8K WRITE %s (%d): %04X\n",name,emu8k->cur_voice, val); + } + + rep_count_w=0; + last_write=tmpz; + } + rep_count_w++; + } +#endif //EMU8K_DEBUG_REGISTERS + + switch (addr & 0xF02) + { + case 0x600: + case 0x602: /*Data0. also known as BLASTER+0x400 and EMU+0x000 */ + switch (emu8k->cur_reg) + { + case 0: + /* The docs says that this value is constantly updating, and it should have no actual effect. Actions should be done over ptrx */ + WRITE16(addr, emu8k->voice[emu8k->cur_voice].cpf, val); + return; + + case 1: + WRITE16(addr, emu8k->voice[emu8k->cur_voice].ptrx, val); + return; + + case 2: + /* The docs says that this value is constantly updating, and it should have no actual effect. Actions should be done over vtft */ + WRITE16(addr, emu8k->voice[emu8k->cur_voice].cvcf, val); + return; + + case 3: + WRITE16(addr, emu8k->voice[emu8k->cur_voice].vtft, val); + return; + + case 4: + WRITE16(addr, emu8k->voice[emu8k->cur_voice].unknown_data0_4, val); + return; + + case 5: + WRITE16(addr, emu8k->voice[emu8k->cur_voice].unknown_data0_5, val); + return; + + case 6: + { + emu8k_voice_t* emu_voice = &emu8k->voice[emu8k->cur_voice]; + WRITE16(addr, emu_voice->psst, val); + /* TODO: Should we update only on MSB update, or this could be used as some sort of hack by applications? */ + emu_voice->loop_start.int_address = emu_voice->psst & EMU8K_MEM_ADDRESS_MASK; + if (addr & 2) + { + emu_voice->vol_l = emu_voice->psst_pan; + emu_voice->vol_r = 255 - (emu_voice->psst_pan); + } + } + return; + + case 7: + WRITE16(addr, emu8k->voice[emu8k->cur_voice].csl, val); + /* TODO: Should we update only on MSB update, or this could be used as some sort of hack by applications? */ + emu8k->voice[emu8k->cur_voice].loop_end.int_address = emu8k->voice[emu8k->cur_voice].csl & EMU8K_MEM_ADDRESS_MASK; + return; + } + break; + + case 0xA00: /*Data1. also known as BLASTER+0x800 and EMU+0x400 */ + switch (emu8k->cur_reg) + { + case 0: + WRITE16(addr, emu8k->voice[emu8k->cur_voice].ccca, val); + /* TODO: Should we update only on MSB update, or this could be used as some sort of hack by applications? */ + emu8k->voice[emu8k->cur_voice].addr.int_address = emu8k->voice[emu8k->cur_voice].ccca & EMU8K_MEM_ADDRESS_MASK; + return; + + case 1: + switch (emu8k->cur_voice) + { + case 9: + WRITE16(addr, emu8k->hwcf4, val); + return; + case 10: + WRITE16(addr, emu8k->hwcf5, val); + return; + /* Actually, these two might be command words rather than registers, or some LFO position/buffer reset. */ + case 13: + WRITE16(addr, emu8k->hwcf6, val); + return; + case 14: + WRITE16(addr, emu8k->hwcf7, val); + return; + + case 20: + WRITE16(addr, emu8k->smalr, val); + return; + case 21: + WRITE16(addr, emu8k->smarr, val); + return; + case 22: + WRITE16(addr, emu8k->smalw, val); + return; + case 23: + WRITE16(addr, emu8k->smarw, val); + return; + + case 26: + EMU8K_WRITE(emu8k, emu8k->smalw, val); + emu8k->smalw = (emu8k->smalw + 1) & EMU8K_MEM_ADDRESS_MASK; + return; + + case 29: + emu8k->hwcf1 = val; + return; + case 30: + emu8k->hwcf2 = val; + return; + case 31: + emu8k->hwcf3 = val; + return; + } + break; + + case 2: + emu8k->init1[emu8k->cur_voice] = val; + /* Skip if in first/second initialization step */ + if (emu8k->init1[0] != 0x03FF) + { + switch (emu8k->cur_voice) + { + case 0x3: + emu8k->reverb_engine.out_mix = val & 0xFF; + break; + case 0x5: + { + int c; + for (c = 0; c < 8; c++) + { + emu8k->reverb_engine.allpass[c].feedback = (val & 0xFF) / ((float)0xFF); + } + } + break; + case 0x7: + emu8k->reverb_engine.link_return_type = (val == 0x8474) ? 1 : 0; + break; + case 0xF: + emu8k->reverb_engine.reflections[0].output_gain = ((val & 0xF0) >> 4) / 15.0; + break; + case 0x17: + emu8k->reverb_engine.reflections[1].output_gain = ((val & 0xF0) >> 4) / 15.0; + break; + case 0x1F: + emu8k->reverb_engine.reflections[2].output_gain = ((val & 0xF0) >> 4) / 15.0; + break; + case 0x9: + emu8k->reverb_engine.reflections[0].feedback = (val & 0xF) / 15.0; + break; + case 0xB: //emu8k->reverb_engine.reflections[0].feedback_r = (val&0xF)/15.0; + break; + case 0x11: + emu8k->reverb_engine.reflections[1].feedback = (val & 0xF) / 15.0; + break; + case 0x13: //emu8k->reverb_engine.reflections[1].feedback_r = (val&0xF)/15.0; + break; + case 0x19: + emu8k->reverb_engine.reflections[2].feedback = (val & 0xF) / 15.0; + break; + case 0x1B: //emu8k->reverb_engine.reflections[2].feedback_r = (val&0xF)/15.0; + break; + } + } + return; + + case 3: + emu8k->init3[emu8k->cur_voice] = val; + /* Skip if in first/second initialization step */ + if (emu8k->init1[0] != 0x03FF) + { + switch (emu8k->cur_voice) + { + case 9: + emu8k->chorus_engine.feedback = (val & 0xFF); + break; + case 12: + /* Limiting this to a sane value given our buffer. */ + emu8k->chorus_engine.delay_samples_central = (val & 0x1FFF); + break; + + case 1: + emu8k->reverb_engine.refl_in_amp = val & 0xFF; + break; + case 3: //emu8k->reverb_engine.refl_in_amp_r = val&0xFF; + break; + } + } + return; + + case 4: + emu8k->voice[emu8k->cur_voice].envvol = val; + emu8k->voice[emu8k->cur_voice].vol_envelope.delay_samples = ENVVOL_TO_EMU_SAMPLES(val); + return; + + case 5: + { + emu8k->voice[emu8k->cur_voice].dcysusv = val; + emu8k_envelope_t* const vol_env = &emu8k->voice[emu8k->cur_voice].vol_envelope; + int old_on = emu8k->voice[emu8k->cur_voice].env_engine_on; + emu8k->voice[emu8k->cur_voice].env_engine_on = DCYSUSV_GENERATOR_ENGINE_ON(val); + + if (emu8k->voice[emu8k->cur_voice].env_engine_on && + old_on != emu8k->voice[emu8k->cur_voice].env_engine_on) + { + if (emu8k->hwcf3 != 0x04) + { + /* This is a hack for some programs like Doom or cubic player 1.7 that don't initialize + the hwcfg and init registers (doom does not init the card at all. only tests the cfg registers) */ + emu8k->hwcf3 = 0x04; + } + + //reset lfos. + emu8k->voice[emu8k->cur_voice].lfo1_count.addr = 0; + emu8k->voice[emu8k->cur_voice].lfo2_count.addr = 0; + // Trigger envelopes + if (ATKHLDV_TRIGGER(emu8k->voice[emu8k->cur_voice].atkhldv)) + { + vol_env->value_amp_hz = 0; + if (vol_env->delay_samples) + { + vol_env->state = ENV_DELAY; + } + else if (vol_env->attack_amount_amp_hz == 0) + { + vol_env->state = ENV_STOPPED; + } + else + { + vol_env->state = ENV_ATTACK; + /* TODO: Verify if "never attack" means eternal mute, + * or it means skip attack, go to hold". + if (vol_env->attack_amount == 0) + { + vol_env->value = (1 << 21); + vol_env->state = ENV_HOLD; + }*/ + } + } + + if (ATKHLD_TRIGGER(emu8k->voice[emu8k->cur_voice].atkhld)) + { + emu8k_envelope_t* const mod_env = &emu8k->voice[emu8k->cur_voice].mod_envelope; + mod_env->value_amp_hz = 0; + mod_env->value_db_oct = 0; + if (mod_env->delay_samples) + { + mod_env->state = ENV_DELAY; + } + else if (mod_env->attack_amount_amp_hz == 0) + { + mod_env->state = ENV_STOPPED; + } + else + { + mod_env->state = ENV_ATTACK; + /* TODO: Verify if "never attack" means eternal start, + * or it means skip attack, go to hold". + if (mod_env->attack_amount == 0) + { + mod_env->value = (1 << 21); + mod_env->state = ENV_HOLD; + }*/ + } + } + } + + + /* Converting the input in dBs to envelope value range. */ + vol_env->sustain_value_db_oct = DCYSUSV_SUS_TO_ENV_RANGE(DCYSUSV_SUSVALUE_GET(val)); + vol_env->ramp_amount_db_oct = env_decay_to_dbs_or_oct[DCYSUSV_DECAYRELEASE_GET(val)]; + if (DCYSUSV_IS_RELEASE(val)) + { + if (vol_env->state == ENV_DELAY || vol_env->state == ENV_ATTACK || vol_env->state == ENV_HOLD) + { + vol_env->value_db_oct = env_vol_amplitude_to_db[vol_env->value_amp_hz >> 5] << 5; + if (vol_env->value_db_oct > (1 << 21)) + vol_env->value_db_oct = 1 << 21; + } + + vol_env->state = (vol_env->value_db_oct >= vol_env->sustain_value_db_oct) ? ENV_RAMP_DOWN : ENV_RAMP_UP; + } + } + return; + + case 6: + emu8k->voice[emu8k->cur_voice].envval = val; + emu8k->voice[emu8k->cur_voice].mod_envelope.delay_samples = ENVVAL_TO_EMU_SAMPLES(val); + return; + + case 7: + { + //TODO: Look for a bug on delay (first trigger it works, next trigger it doesn't) + emu8k->voice[emu8k->cur_voice].dcysus = val; + emu8k_envelope_t* const mod_env = &emu8k->voice[emu8k->cur_voice].mod_envelope; + /* Converting the input in octaves to envelope value range. */ + mod_env->sustain_value_db_oct = DCYSUS_SUS_TO_ENV_RANGE(DCYSUS_SUSVALUE_GET(val)); + mod_env->ramp_amount_db_oct = env_decay_to_dbs_or_oct[DCYSUS_DECAYRELEASE_GET(val)]; + if (DCYSUS_IS_RELEASE(val)) + { + if (mod_env->state == ENV_DELAY || mod_env->state == ENV_ATTACK || mod_env->state == ENV_HOLD) + { + mod_env->value_db_oct = env_mod_hertz_to_octave[mod_env->value_amp_hz >> 9] << 9; + if (mod_env->value_db_oct >= (1 << 21)) + mod_env->value_db_oct = (1 << 21) - 1; + } + + mod_env->state = (mod_env->value_db_oct >= mod_env->sustain_value_db_oct) ? ENV_RAMP_DOWN : ENV_RAMP_UP; + } + } + return; + } + break; + + case 0xA02: /*Data2. also known as BLASTER+0x802 and EMU+0x402 */ + switch (emu8k->cur_reg) + { + case 0: + { + emu8k_voice_t* emu_voice = &emu8k->voice[emu8k->cur_voice]; + WRITE16(addr, emu_voice->ccca, val); + emu_voice->addr.int_address = emu_voice->ccca & EMU8K_MEM_ADDRESS_MASK; + uint32_t paramq = CCCA_FILTQ_GET(emu_voice->ccca); + emu_voice->filt_att = filter_atten[paramq]; + emu_voice->filterq_idx = paramq; + } + return; + + case 1: + switch (emu8k->cur_voice) + { + case 9: + WRITE16(addr, emu8k->hwcf4, val); + /* Skip if in first/second initialization step */ + if (emu8k->init1[0] != 0x03FF) + { + /*(1/256th of a 44Khz sample) */ + /* clip the value to a reasonable value given our buffer */ + int32_t tmp = emu8k->hwcf4 & 0x1FFFFF; + emu8k->chorus_engine.delay_offset_samples_right = ((double)tmp) / 256.0; + } + return; + case 10: + WRITE16(addr, emu8k->hwcf5, val); + /* Skip if in first/second initialization step */ + if (emu8k->init1[0] != 0x03FF) + { + /* The scale of this value is unknown. I've taken it as milliHz. + * Another interpretation could be periods. (and so, Hz = 1/period)*/ + double osc_speed = emu8k->hwcf5;//*1.316; +#if 1 // milliHz + /*milliHz to lfotable samples.*/ + osc_speed *= 65.536 / 44100.0; +#elif 0 //periods + /* 44.1Khz ticks to lfotable samples.*/ + osc_speed = 65.536/osc_speed; +#endif + /*left shift 32bits for 32.32 fixed.point*/ + osc_speed *= 65536.0 * 65536.0; + emu8k->chorus_engine.lfo_inc.addr = (uint64_t)osc_speed; + } + return; + /* Actually, these two might be command words rather than registers, or some LFO position/buffer reset.*/ + case 13: + WRITE16(addr, emu8k->hwcf6, val); + return; + case 14: + WRITE16(addr, emu8k->hwcf7, val); + return; + + case 20: /*Top 8 bits are for Empty (MT) bit or non-addressable.*/ + WRITE16(addr, emu8k->smalr, val & 0xFF); + dmareadbit = 0x8000; + return; + case 21: /*Top 8 bits are for Empty (MT) bit or non-addressable.*/ + WRITE16(addr, emu8k->smarr, val & 0xFF); + dmareadbit = 0x8000; + return; + case 22: /*Top 8 bits are for full bit or non-addressable.*/ + WRITE16(addr, emu8k->smalw, val & 0xFF); + return; + case 23: /*Top 8 bits are for full bit or non-addressable.*/ + WRITE16(addr, emu8k->smarw, val & 0xFF); + return; + + case 26: + dmawritebit = 0x8000; + EMU8K_WRITE(emu8k, emu8k->smarw, val); + emu8k->smarw++; + return; + } + break; + + case 2: + emu8k->init2[emu8k->cur_voice] = val; + /* Skip if in first/second initialization step */ + if (emu8k->init1[0] != 0x03FF) + { + switch (emu8k->cur_voice) + { + case 0x14: + { + int multip = ((val & 0xF00) >> 8) + 18; + emu8k->reverb_engine.reflections[5].bufsize = multip * REV_BUFSIZE_STEP; + emu8k->reverb_engine.tailL.bufsize = (multip + 1) * REV_BUFSIZE_STEP; + if (emu8k->reverb_engine.link_return_type == 0) + { + emu8k->reverb_engine.tailR.bufsize = (multip + 1) * REV_BUFSIZE_STEP; + } + } + break; + case 0x16: + if (emu8k->reverb_engine.link_return_type == 1) + { + int multip = ((val & 0xF00) >> 8) + 18; + emu8k->reverb_engine.tailR.bufsize = (multip + 1) * REV_BUFSIZE_STEP; + } + break; + case 0x7: + emu8k->reverb_engine.reflections[3].output_gain = ((val & 0xF0) >> 4) / 15.0; + break; + case 0xf: + emu8k->reverb_engine.reflections[4].output_gain = ((val & 0xF0) >> 4) / 15.0; + break; + case 0x17: + emu8k->reverb_engine.reflections[5].output_gain = ((val & 0xF0) >> 4) / 15.0; + break; + case 0x1d: + { + int c; + for (c = 0; c < 6; c++) + { + emu8k->reverb_engine.reflections[c].damp1 = (val & 0xFF) / 255.0; + emu8k->reverb_engine.reflections[c].damp2 = (0xFF - (val & 0xFF)) / 255.0; + emu8k->reverb_engine.reflections[c].filterstore = 0; + } + emu8k->reverb_engine.damper.damp1 = (val & 0xFF) / 255.0; + emu8k->reverb_engine.damper.damp2 = (0xFF - (val & 0xFF)) / 255.0; + emu8k->reverb_engine.damper.filterstore = 0; + } + break; + case 0x1f: /* filter r */ + break; + case 0x1: + emu8k->reverb_engine.reflections[3].feedback = (val & 0xF) / 15.0; + break; + case 0x3: //emu8k->reverb_engine.reflections[3].feedback_r = (val&0xF)/15.0; + break; + case 0x9: + emu8k->reverb_engine.reflections[4].feedback = (val & 0xF) / 15.0; + break; + case 0xb: //emu8k->reverb_engine.reflections[4].feedback_r = (val&0xF)/15.0; + break; + case 0x11: + emu8k->reverb_engine.reflections[5].feedback = (val & 0xF) / 15.0; + break; + case 0x13: //emu8k->reverb_engine.reflections[5].feedback_r = (val&0xF)/15.0; + break; + } + } + return; + + case 3: + emu8k->init4[emu8k->cur_voice] = val; + /* Skip if in first/second initialization step */ + if (emu8k->init1[0] != 0x03FF) + { + switch (emu8k->cur_voice) + { + case 0x3: + { + int32_t samples = ((val & 0xFF) * emu8k->chorus_engine.delay_samples_central) >> 8; + emu8k->chorus_engine.lfodepth_multip = samples; + + } + break; + + case 0x1F: + emu8k->reverb_engine.link_return_amp = val & 0xFF; + break; + } + } + return; + + case 4: + { + emu8k->voice[emu8k->cur_voice].atkhldv = val; + emu8k_envelope_t* const vol_env = &emu8k->voice[emu8k->cur_voice].vol_envelope; + vol_env->attack_samples = env_attack_to_samples[ATKHLDV_ATTACK(val)]; + if (vol_env->attack_samples == 0) + { + vol_env->attack_amount_amp_hz = 0; + } + else + { + /* Linear amplitude increase each sample. */ + vol_env->attack_amount_amp_hz = (1 << 21) / vol_env->attack_samples; + } + vol_env->hold_samples = ATKHLDV_HOLD_TO_EMU_SAMPLES(val); + if (ATKHLDV_TRIGGER(val) && emu8k->voice[emu8k->cur_voice].env_engine_on) + { + /*TODO: I assume that "envelope trigger" is the same as new note + * (since changing the IP can be done when modulating pitch too) */ + emu8k->voice[emu8k->cur_voice].lfo1_count.addr = 0; + emu8k->voice[emu8k->cur_voice].lfo2_count.addr = 0; + + vol_env->value_amp_hz = 0; + if (vol_env->delay_samples) + { + vol_env->state = ENV_DELAY; + } + else if (vol_env->attack_amount_amp_hz == 0) + { + vol_env->state = ENV_STOPPED; + } + else + { + vol_env->state = ENV_ATTACK; + /* TODO: Verify if "never attack" means eternal mute, + * or it means skip attack, go to hold". + if (vol_env->attack_amount == 0) + { + vol_env->value = (1 << 21); + vol_env->state = ENV_HOLD; + }*/ + } + } + } + return; + + case 5: + emu8k->voice[emu8k->cur_voice].lfo1val = val; + /* TODO: verify if this is set once, or set every time. */ + emu8k->voice[emu8k->cur_voice].lfo1_delay_samples = LFOxVAL_TO_EMU_SAMPLES(val); + return; + + case 6: + { + emu8k->voice[emu8k->cur_voice].atkhld = val; + emu8k_envelope_t* const mod_env = &emu8k->voice[emu8k->cur_voice].mod_envelope; + mod_env->attack_samples = env_attack_to_samples[ATKHLD_ATTACK(val)]; + if (mod_env->attack_samples == 0) + { + mod_env->attack_amount_amp_hz = 0; + } + else + { + /* Linear amplitude increase each sample. */ + mod_env->attack_amount_amp_hz = (1 << 21) / mod_env->attack_samples; + } + mod_env->hold_samples = ATKHLD_HOLD_TO_EMU_SAMPLES(val); + if (ATKHLD_TRIGGER(val) && emu8k->voice[emu8k->cur_voice].env_engine_on) + { + mod_env->value_amp_hz = 0; + mod_env->value_db_oct = 0; + if (mod_env->delay_samples) + { + mod_env->state = ENV_DELAY; + } + else if (mod_env->attack_amount_amp_hz == 0) + { + mod_env->state = ENV_STOPPED; + } + else + { + mod_env->state = ENV_ATTACK; + /* TODO: Verify if "never attack" means eternal start, + * or it means skip attack, go to hold". + if (mod_env->attack_amount == 0) + { + mod_env->value = (1 << 21); + mod_env->state = ENV_HOLD; + }*/ + } + } + } + return; + + case 7: + emu8k->voice[emu8k->cur_voice].lfo2val = val; + emu8k->voice[emu8k->cur_voice].lfo2_delay_samples = LFOxVAL_TO_EMU_SAMPLES(val); + + return; + } + break; + + case 0xE00: /*Data3. also known as BLASTER+0xC00 and EMU+0x800 */ + switch (emu8k->cur_reg) + { + case 0: + emu8k->voice[emu8k->cur_voice].ip = val; + emu8k->voice[emu8k->cur_voice].ptrx_pit_target = freqtable[val] >> 18; + return; + + case 1: + { + emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; + if ((val & 0xFF) == 0 && the_voice->cvcf_curr_volume == 0 && the_voice->vtft_vol_target == 0 + && the_voice->dcysusv == 0x80 && the_voice->ip == 0) + { + // Patch to avoid some clicking noises with Impulse tracker or other software that sets + // different values to 0 to set noteoff, but here, 0 means no attenuation = full volume. + return; + } + the_voice->ifatn = val; + the_voice->initial_att = (((int32_t)the_voice->ifatn_attenuation << 21) / 0xFF); + the_voice->vtft_vol_target = attentable[the_voice->ifatn_attenuation]; + + the_voice->initial_filter = (((int32_t)the_voice->ifatn_init_filter << 21) / 0xFF); + if (the_voice->ifatn_init_filter == 0xFF) + { + the_voice->vtft_filter_target = 0xFFFF; + } + else + { + the_voice->vtft_filter_target = the_voice->initial_filter >> 5; + } + } + return; + + case 2: + { + emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; + the_voice->pefe = val; + + int divider = (the_voice->pefe_modenv_filter_height < 0) ? 0x80 : 0x7F; + the_voice->fixed_modenv_filter_height = ((int32_t)the_voice->pefe_modenv_filter_height) * 0x4000 / divider; + + divider = (the_voice->pefe_modenv_pitch_height < 0) ? 0x80 : 0x7F; + the_voice->fixed_modenv_pitch_height = ((int32_t)the_voice->pefe_modenv_pitch_height) * 0x4000 / divider; + } + return; + + case 3: + { + emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; + the_voice->fmmod = val; + + int divider = (the_voice->fmmod_lfo1_filt_mod < 0) ? 0x80 : 0x7F; + the_voice->fixed_lfo1_filt_mod = ((int32_t)the_voice->fmmod_lfo1_filt_mod) * 0x4000 / divider; + + divider = (the_voice->fmmod_lfo1_vibrato < 0) ? 0x80 : 0x7F; + the_voice->fixed_lfo1_vibrato = ((int32_t)the_voice->fmmod_lfo1_vibrato) * 0x4000 / divider; + } + return; + + case 4: + { + emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; + the_voice->tremfrq = val; + the_voice->lfo1_speed = lfofreqtospeed[the_voice->tremfrq_lfo1_freq]; + + int divider = (the_voice->tremfrq_lfo1_tremolo < 0) ? 0x80 : 0x7F; + the_voice->fixed_lfo1_tremolo = ((int32_t)the_voice->tremfrq_lfo1_tremolo) * 0x4000 / divider; + } + return; + + case 5: + { + emu8k_voice_t* const the_voice = &emu8k->voice[emu8k->cur_voice]; + the_voice->fm2frq2 = val; + the_voice->lfo2_speed = lfofreqtospeed[the_voice->fm2frq2_lfo2_freq]; + + int divider = (the_voice->fm2frq2_lfo2_vibrato < 0) ? 0x80 : 0x7F; + the_voice->fixed_lfo2_vibrato = ((int32_t)the_voice->fm2frq2_lfo2_vibrato) * 0x4000 / divider; + } + return; + + case 7: /*ID? I believe that this allows applications to know if the emu is in use by another application */ + emu8k->id = val; + return; + } + break; + + case 0xE02: /* Pointer. also known as BLASTER+0xC02 and EMU+0x802 */ + emu8k->cur_voice = (val & 31); + emu8k->cur_reg = ((val >> 5) & 7); + return; + } + pclog("EMU8K WRITE: Unknown register write: %04X-%02X(%d/%d): %04X \n", addr, (emu8k->cur_reg) << 5 | emu8k->cur_voice, + emu8k->cur_reg, emu8k->cur_voice, val); + +} + +uint8_t emu8k_inb(emu8k_t *emu8k, uint16_t addr) +{ + /* Reading a single byte is a feature that at least Impulse tracker uses, + * but only on detection code and not for odd addresses.*/ + if (addr & 1) + return emu8k_inw(emu8k, addr & ~1) >> 1; + return emu8k_inw(emu8k, addr) & 0xff; +} + +void emu8k_outb(emu8k_t *emu8k, uint16_t addr, uint8_t val) +{ + /* TODO: AWE32 docs says that you cannot write in bytes, but if + * an app were to use this implementation, the content of the LS Byte would be lost.*/ + if (addr & 1) + emu8k_outw(emu8k, addr & ~1, val << 8); + else + emu8k_outw(emu8k, addr, val); +} + +/* TODO: This is not a correct emulation, just a workalike implementation. */ +void emu8k_work_chorus(int32_t* inbuf, int32_t* outbuf, emu8k_chorus_eng_t* engine, int count) +{ + int pos; + for (pos = 0; pos < count; pos++) + { + double lfo_inter1 = chortable[engine->lfo_pos.int_address]; + // double lfo_inter2 = chortable[(engine->lfo_pos.int_address+1)&0xFFFF]; + + double offset_lfo = lfo_inter1; //= lfo_inter1 + ((lfo_inter2-lfo_inter1)*engine->lfo_pos.fract_address/65536.0); + offset_lfo *= engine->lfodepth_multip; + + /* Work left */ + double readdouble = (double)engine->write - (double)engine->delay_samples_central - offset_lfo; + int read = (int32_t)floor(readdouble); + int fraction_part = (readdouble - (double)read) * 65536.0; + int next_value = read + 1; + if (read < 0) + { + read += EMU8K_LFOCHORUS_SIZE; + if (next_value < 0) next_value += EMU8K_LFOCHORUS_SIZE; + } + else if (next_value >= EMU8K_LFOCHORUS_SIZE) + { + next_value -= EMU8K_LFOCHORUS_SIZE; + if (read >= EMU8K_LFOCHORUS_SIZE) read -= EMU8K_LFOCHORUS_SIZE; + } + int32_t dat1 = engine->chorus_left_buffer[read]; + int32_t dat2 = engine->chorus_left_buffer[next_value]; + dat1 += ((dat2 - dat1) * fraction_part) >> 16; + + engine->chorus_left_buffer[engine->write] = *inbuf + ((dat1 * engine->feedback) >> 8); + + + /* Work right */ + readdouble = (double)engine->write - (double)engine->delay_samples_central - engine->delay_offset_samples_right - offset_lfo; + read = (int32_t)floor(readdouble); + next_value = read + 1; + if (read < 0) + { + read += EMU8K_LFOCHORUS_SIZE; + if (next_value < 0) next_value += EMU8K_LFOCHORUS_SIZE; + } + else if (next_value >= EMU8K_LFOCHORUS_SIZE) + { + next_value -= EMU8K_LFOCHORUS_SIZE; + if (read >= EMU8K_LFOCHORUS_SIZE) read -= EMU8K_LFOCHORUS_SIZE; + } + int32_t dat3 = engine->chorus_right_buffer[read]; + int32_t dat4 = engine->chorus_right_buffer[next_value]; + dat3 += ((dat4 - dat3) * fraction_part) >> 16; + + engine->chorus_right_buffer[engine->write] = *inbuf + ((dat3 * engine->feedback) >> 8); + + ++engine->write; + engine->write %= EMU8K_LFOCHORUS_SIZE; + engine->lfo_pos.addr += engine->lfo_inc.addr; + engine->lfo_pos.int_address &= 0xFFFF; + + (*outbuf++) += dat1; + (*outbuf++) += dat3; + inbuf++; + } + +} + +int32_t emu8k_reverb_comb_work(emu8k_reverb_combfilter_t* comb, int32_t in) +{ + + int32_t bufin; + /* get echo */ + int32_t output = comb->reflection[comb->read_pos]; + /* apply lowpass */ + comb->filterstore = (output * comb->damp2) + (comb->filterstore * comb->damp1); + /* appply feedback */ + bufin = in - (comb->filterstore * comb->feedback); + /* store new value in delayed buffer */ + comb->reflection[comb->read_pos] = bufin; + + if (++comb->read_pos >= comb->bufsize) comb->read_pos = 0; + + return output * comb->output_gain; +} + +int32_t emu8k_reverb_diffuser_work(emu8k_reverb_combfilter_t* comb, int32_t in) +{ + + int32_t bufout = comb->reflection[comb->read_pos]; + /*diffuse*/ + int32_t bufin = -in + (bufout * comb->feedback); + int32_t output = bufout - (bufin * comb->feedback); + /* store new value in delayed buffer */ + comb->reflection[comb->read_pos] = bufin; + + if (++comb->read_pos >= comb->bufsize) comb->read_pos = 0; + + return output; +} + +int32_t emu8k_reverb_tail_work(emu8k_reverb_combfilter_t* comb, emu8k_reverb_combfilter_t* allpasses, int32_t in) +{ + int32_t output = comb->reflection[comb->read_pos]; + /* store new value in delayed buffer */ + comb->reflection[comb->read_pos] = in; + + //output = emu8k_reverb_allpass_work(&allpasses[0],output); + output = emu8k_reverb_diffuser_work(&allpasses[1], output); + output = emu8k_reverb_diffuser_work(&allpasses[2], output); + //output = emu8k_reverb_allpass_work(&allpasses[3],output); + + if (++comb->read_pos >= comb->bufsize) comb->read_pos = 0; + + return output; +} +int32_t emu8k_reverb_damper_work(emu8k_reverb_combfilter_t* comb, int32_t in) +{ + /* apply lowpass */ + comb->filterstore = (in * comb->damp2) + (comb->filterstore * comb->damp1); + return comb->filterstore; +} + +/* TODO: This is not a correct emulation, just a workalike implementation. */ +void emu8k_work_reverb(int32_t* inbuf, int32_t* outbuf, emu8k_reverb_eng_t* engine, int count) +{ + int pos; + if (engine->link_return_type) + { + for (pos = 0; pos < count; pos++) + { + int32_t dat1, dat2, in, in2; + in = emu8k_reverb_damper_work(&engine->damper, inbuf[pos]); + in2 = (in * engine->refl_in_amp) >> 8; + dat2 = emu8k_reverb_comb_work(&engine->reflections[0], in2); + dat2 += emu8k_reverb_comb_work(&engine->reflections[1], in2); + dat1 = emu8k_reverb_comb_work(&engine->reflections[2], in2); + dat2 += emu8k_reverb_comb_work(&engine->reflections[3], in2); + dat1 += emu8k_reverb_comb_work(&engine->reflections[4], in2); + dat2 += emu8k_reverb_comb_work(&engine->reflections[5], in2); + + dat1 += (emu8k_reverb_tail_work(&engine->tailL, &engine->allpass[0], in + dat1) * engine->link_return_amp) >> 8; + dat2 += (emu8k_reverb_tail_work(&engine->tailR, &engine->allpass[4], in + dat2) * engine->link_return_amp) >> 8; + + (*outbuf++) += (dat1 * engine->out_mix) >> 8; + (*outbuf++) += (dat2 * engine->out_mix) >> 8; + } + } + else + { + for (pos = 0; pos < count; pos++) + { + int32_t dat1, dat2, in, in2; + in = emu8k_reverb_damper_work(&engine->damper, inbuf[pos]); + in2 = (in * engine->refl_in_amp) >> 8; + dat1 = emu8k_reverb_comb_work(&engine->reflections[0], in2); + dat1 += emu8k_reverb_comb_work(&engine->reflections[1], in2); + dat1 += emu8k_reverb_comb_work(&engine->reflections[2], in2); + dat1 += emu8k_reverb_comb_work(&engine->reflections[3], in2); + dat1 += emu8k_reverb_comb_work(&engine->reflections[4], in2); + dat1 += emu8k_reverb_comb_work(&engine->reflections[5], in2); + dat2 = dat1; + + dat1 += (emu8k_reverb_tail_work(&engine->tailL, &engine->allpass[0], in + dat1) * engine->link_return_amp) >> 8; + dat2 += (emu8k_reverb_tail_work(&engine->tailR, &engine->allpass[4], in + dat2) * engine->link_return_amp) >> 8; + + (*outbuf++) += (dat1 * engine->out_mix) >> 8; + (*outbuf++) += (dat2 * engine->out_mix) >> 8; + } + } +} +void emu8k_work_eq(int32_t* inoutbuf, int count) +{ + // TODO: Work EQ over buf + NOREF(inoutbuf); + NOREF(count); +} + +int32_t emu8k_vol_slide(emu8k_slide_t* slide, int32_t target) +{ + if (slide->last < target) + { + slide->last += 0x400; + if (slide->last > target) slide->last = target; + } + else if (slide->last > target) + { + slide->last -= 0x400; + if (slide->last < target) slide->last = target; + } + return slide->last; +} + +//int32_t old_pitch[32]={0}; +//int32_t old_cut[32]={0}; +//int32_t old_vol[32]={0}; +void emu8k_update(emu8k_t* emu8k, int new_pos) +{ + if (emu8k->pos >= new_pos) + return; + + AssertLogRelReturnVoid(new_pos <= MAXSOUNDBUFLEN); + + int32_t* buf; + emu8k_voice_t* emu_voice; + int pos; + int c; + + /* Clean the buffers since we will accumulate into them. */ + buf = &emu8k->buffer[emu8k->pos * 2]; + memset(buf, 0, 2 * (new_pos - emu8k->pos) * sizeof(emu8k->buffer[0])); + memset(&emu8k->chorus_in_buffer[emu8k->pos], 0, (new_pos - emu8k->pos) * sizeof(emu8k->chorus_in_buffer[0])); + memset(&emu8k->reverb_in_buffer[emu8k->pos], 0, (new_pos - emu8k->pos) * sizeof(emu8k->reverb_in_buffer[0])); + + /* Voices section */ + for (c = 0; c < 32; c++) + { + emu_voice = &emu8k->voice[c]; + buf = &emu8k->buffer[emu8k->pos * 2]; + + for (pos = emu8k->pos; pos < new_pos; pos++) + { + int32_t dat; + + if (emu_voice->cvcf_curr_volume) + { + /* Waveform oscillator */ +#ifdef RESAMPLER_LINEAR + dat = EMU8K_READ_INTERP_LINEAR(emu8k, emu_voice->addr.int_address, + emu_voice->addr.fract_address); + +#elif defined RESAMPLER_CUBIC + dat = EMU8K_READ_INTERP_CUBIC(emu8k, emu_voice->addr.int_address, + emu_voice->addr.fract_address); +#endif + + /* Filter section */ + if (emu_voice->filterq_idx || emu_voice->cvcf_curr_filt_ctoff != 0xFFFF) + { + int cutoff = emu_voice->cvcf_curr_filt_ctoff >> 8; + const int64_t coef0 = filt_coeffs[emu_voice->filterq_idx][cutoff][0]; + const int64_t coef1 = filt_coeffs[emu_voice->filterq_idx][cutoff][1]; + const int64_t coef2 = filt_coeffs[emu_voice->filterq_idx][cutoff][2]; + /* clip at twice the range */ +#define ClipBuffer(buf) (buf < -16777216) ? -16777216 : (buf > 16777216) ? 16777216 : buf + +#ifdef FILTER_INITIAL +#define NOOP(x) (void)x; + NOOP(coef1) + /* Apply expected attenuation. (FILTER_MOOG does it implicitly, but this one doesn't). + * Work in 24bits. */ + dat = (dat * emu_voice->filt_att) >> 8; + + int64_t vhp = ((-emu_voice->filt_buffer[0] * coef2) >> 24) - emu_voice->filt_buffer[1] - dat; + emu_voice->filt_buffer[1] += (emu_voice->filt_buffer[0] * coef0) >> 24; + emu_voice->filt_buffer[0] += (vhp * coef0) >> 24; + dat = (int32_t)(emu_voice->filt_buffer[1] >> 8); + if (dat > 32767) { dat = 32767; } + else if (dat < -32768) { dat = -32768; } + +#elif defined FILTER_MOOG + + /*move to 24bits*/ + dat <<= 8; + + dat -= (coef2 * emu_voice->filt_buffer[4]) >> 24; /*feedback*/ + int64_t t1 = emu_voice->filt_buffer[1]; + emu_voice->filt_buffer[1] = ((dat + emu_voice->filt_buffer[0]) * coef0 - emu_voice->filt_buffer[1] * coef1) >> 24; + emu_voice->filt_buffer[1] = ClipBuffer(emu_voice->filt_buffer[1]); + + int64_t t2 = emu_voice->filt_buffer[2]; + emu_voice->filt_buffer[2] = ((emu_voice->filt_buffer[1] + t1) * coef0 - emu_voice->filt_buffer[2] * coef1) >> 24; + emu_voice->filt_buffer[2] = ClipBuffer(emu_voice->filt_buffer[2]); + + int64_t t3 = emu_voice->filt_buffer[3]; + emu_voice->filt_buffer[3] = ((emu_voice->filt_buffer[2] + t2) * coef0 - emu_voice->filt_buffer[3] * coef1) >> 24; + emu_voice->filt_buffer[3] = ClipBuffer(emu_voice->filt_buffer[3]); + + emu_voice->filt_buffer[4] = ((emu_voice->filt_buffer[3] + t3) * coef0 - emu_voice->filt_buffer[4] * coef1) >> 24; + emu_voice->filt_buffer[4] = ClipBuffer(emu_voice->filt_buffer[4]); + + emu_voice->filt_buffer[0] = ClipBuffer(dat); + + dat = (int32_t)(emu_voice->filt_buffer[4] >> 8); + if (dat > 32767) + { dat = 32767; } + else if (dat < -32768) + { dat = -32768; } + +#elif defined FILTER_CONSTANT + + /* Apply expected attenuation. (FILTER_MOOG does it implicitly, but this one is constant gain). + * Also stay at 24bits.*/ + dat = (dat * emu_voice->filt_att) >> 8; + + emu_voice->filt_buffer[0] = (coef1 * emu_voice->filt_buffer[0] + + coef0 * (dat + + ((coef2 * (emu_voice->filt_buffer[0] - emu_voice->filt_buffer[1]))>>24)) + ) >> 24; + emu_voice->filt_buffer[1] = (coef1 * emu_voice->filt_buffer[1] + + coef0 * emu_voice->filt_buffer[0]) >> 24; + + emu_voice->filt_buffer[0] = ClipBuffer(emu_voice->filt_buffer[0]); + emu_voice->filt_buffer[1] = ClipBuffer(emu_voice->filt_buffer[1]); + + dat = (int32_t)(emu_voice->filt_buffer[1] >> 8); + if (dat > 32767) { dat = 32767; } + else if (dat < -32768) { dat = -32768; } + +#endif + + } + if ((emu8k->hwcf3 & 0x04) && !CCCA_DMA_ACTIVE(emu_voice->ccca)) + { + /*volume and pan*/ + dat = (dat * emu_voice->cvcf_curr_volume) >> 16; + + (*buf++) += (dat * emu_voice->vol_l) >> 8; + (*buf++) += (dat * emu_voice->vol_r) >> 8; + + /* Effects section */ + if (emu_voice->ptrx_revb_send > 0) + { + emu8k->reverb_in_buffer[pos] += (dat * emu_voice->ptrx_revb_send) >> 8; + } + if (emu_voice->csl_chor_send > 0) + { + emu8k->chorus_in_buffer[pos] += (dat * emu_voice->csl_chor_send) >> 8; + } + } + } + + if (emu_voice->env_engine_on) + { + int32_t attenuation = emu_voice->initial_att; + int32_t filtercut = emu_voice->initial_filter; + int32_t currentpitch = emu_voice->ip; + /* run envelopes */ + emu8k_envelope_t* volenv = &emu_voice->vol_envelope; + switch (volenv->state) + { + case ENV_DELAY: + volenv->delay_samples--; + if (volenv->delay_samples <= 0) + { + volenv->state = ENV_ATTACK; + volenv->delay_samples = 0; + } + attenuation = 0x1FFFFF; + break; + + case ENV_ATTACK: + /* Attack amount is in linear amplitude */ + volenv->value_amp_hz += volenv->attack_amount_amp_hz; + if (volenv->value_amp_hz >= (1 << 21)) + { + volenv->value_amp_hz = 1 << 21; + volenv->value_db_oct = 0; + if (volenv->hold_samples) + { + volenv->state = ENV_HOLD; + } + else + { + /* RAMP_UP since db value is inverted and it is 0 at this point. */ + volenv->state = ENV_RAMP_UP; + } + } + attenuation += env_vol_amplitude_to_db[volenv->value_amp_hz >> 5] << 5; + break; + + case ENV_HOLD: + volenv->hold_samples--; + if (volenv->hold_samples <= 0) + { + volenv->state = ENV_RAMP_UP; + } + attenuation += volenv->value_db_oct; + break; + + case ENV_RAMP_DOWN: + /* Decay/release amount is in fraction of dBs and is always positive */ + volenv->value_db_oct -= volenv->ramp_amount_db_oct; + if (volenv->value_db_oct <= volenv->sustain_value_db_oct) + { + volenv->value_db_oct = volenv->sustain_value_db_oct; + volenv->state = ENV_SUSTAIN; + } + attenuation += volenv->value_db_oct; + break; + + case ENV_RAMP_UP: + /* Decay/release amount is in fraction of dBs and is always positive */ + volenv->value_db_oct += volenv->ramp_amount_db_oct; + if (volenv->value_db_oct >= volenv->sustain_value_db_oct) + { + volenv->value_db_oct = volenv->sustain_value_db_oct; + volenv->state = ENV_SUSTAIN; + } + attenuation += volenv->value_db_oct; + break; + + case ENV_SUSTAIN: + attenuation += volenv->value_db_oct; + break; + + case ENV_STOPPED: + attenuation = 0x1FFFFF; + break; + } + + emu8k_envelope_t* modenv = &emu_voice->mod_envelope; + switch (modenv->state) + { + case ENV_DELAY: + modenv->delay_samples--; + if (modenv->delay_samples <= 0) + { + modenv->state = ENV_ATTACK; + modenv->delay_samples = 0; + } + break; + + case ENV_ATTACK: + /* Attack amount is in linear amplitude */ + modenv->value_amp_hz += modenv->attack_amount_amp_hz; + modenv->value_db_oct = env_mod_hertz_to_octave[modenv->value_amp_hz >> 5] << 5; + if (modenv->value_amp_hz >= (1 << 21)) + { + modenv->value_amp_hz = 1 << 21; + modenv->value_db_oct = 1 << 21; + if (modenv->hold_samples) + { + modenv->state = ENV_HOLD; + } + else + { + modenv->state = ENV_RAMP_DOWN; + } + } + break; + + case ENV_HOLD: + modenv->hold_samples--; + if (modenv->hold_samples <= 0) + { + modenv->state = ENV_RAMP_UP; + } + break; + + case ENV_RAMP_DOWN: + /* Decay/release amount is in fraction of octave and is always positive */ + modenv->value_db_oct -= modenv->ramp_amount_db_oct; + if (modenv->value_db_oct <= modenv->sustain_value_db_oct) + { + modenv->value_db_oct = modenv->sustain_value_db_oct; + modenv->state = ENV_SUSTAIN; + } + break; + + case ENV_RAMP_UP: + /* Decay/release amount is in fraction of octave and is always positive */ + modenv->value_db_oct += modenv->ramp_amount_db_oct; + if (modenv->value_db_oct >= modenv->sustain_value_db_oct) + { + modenv->value_db_oct = modenv->sustain_value_db_oct; + modenv->state = ENV_SUSTAIN; + } + break; + } + + /* run lfos */ + if (emu_voice->lfo1_delay_samples) + { + emu_voice->lfo1_delay_samples--; + } + else + { + emu_voice->lfo1_count.addr += emu_voice->lfo1_speed; + emu_voice->lfo1_count.int_address &= 0xFFFF; + } + if (emu_voice->lfo2_delay_samples) + { + emu_voice->lfo2_delay_samples--; + } + else + { + emu_voice->lfo2_count.addr += emu_voice->lfo2_speed; + emu_voice->lfo2_count.int_address &= 0xFFFF; + } + + if (emu_voice->fixed_modenv_pitch_height) + { + /* modenv range 1<<21, pitch height range 1<<14 desired range 0x1000 (+/-one octave) */ + currentpitch += ((modenv->value_db_oct >> 9) * emu_voice->fixed_modenv_pitch_height) >> 14; + } + + if (emu_voice->fixed_lfo1_vibrato) + { + /* table range 1<<15, pitch mod range 1<<14 desired range 0x1000 (+/-one octave) */ + int32_t lfo1_vibrato = (lfotable[emu_voice->lfo1_count.int_address] * emu_voice->fixed_lfo1_vibrato) >> 17; + currentpitch += lfo1_vibrato; + } + if (emu_voice->fixed_lfo2_vibrato) + { + /* table range 1<<15, pitch mod range 1<<14 desired range 0x1000 (+/-one octave) */ + int32_t lfo2_vibrato = (lfotable[emu_voice->lfo2_count.int_address] * emu_voice->fixed_lfo2_vibrato) >> 17; + currentpitch += lfo2_vibrato; + } + + if (emu_voice->fixed_modenv_filter_height) + { + /* modenv range 1<<21, pitch height range 1<<14 desired range 0x200000 (+/-full filter range) */ + filtercut += ((modenv->value_db_oct >> 9) * emu_voice->fixed_modenv_filter_height) >> 5; + } + + if (emu_voice->fixed_lfo1_filt_mod) + { + /* table range 1<<15, pitch mod range 1<<14 desired range 0x100000 (+/-three octaves) */ + int32_t lfo1_filtmod = (lfotable[emu_voice->lfo1_count.int_address] * emu_voice->fixed_lfo1_filt_mod) >> 9; + filtercut += lfo1_filtmod; + } + + if (emu_voice->fixed_lfo1_tremolo) + { + /* table range 1<<15, pitch mod range 1<<14 desired range 0x40000 (+/-12dBs). */ + int32_t lfo1_tremolo = (lfotable[emu_voice->lfo1_count.int_address] * emu_voice->fixed_lfo1_tremolo) >> 11; + attenuation += lfo1_tremolo; + } + + if (currentpitch > 0xFFFF) currentpitch = 0xFFFF; + if (currentpitch < 0) currentpitch = 0; + if (attenuation > 0x1FFFFF) attenuation = 0x1FFFFF; + if (attenuation < 0) attenuation = 0; + if (filtercut > 0x1FFFFF) filtercut = 0x1FFFFF; + if (filtercut < 0) filtercut = 0; + + emu_voice->vtft_vol_target = env_vol_db_to_vol_target[attenuation >> 5]; + emu_voice->vtft_filter_target = filtercut >> 5; + emu_voice->ptrx_pit_target = freqtable[currentpitch] >> 18; + + } +/* +I've recopilated these sentences to get an idea of how to loop + +- Set its PSST register and its CLS register to zero to cause no loops to occur. +-Setting the Loop Start Offset and the Loop End Offset to the same value, will cause the oscillator to loop the entire memory. + +-Setting the PlayPosition greater than the Loop End Offset, will cause the oscillator to play in reverse, back to the Loop End Offset. + It's pretty neat, but appears to be uncontrollable (the rate at which the samples are played in reverse). + +-Note that due to interpolator offset, the actual loop point is one greater than the start address +-Note that due to interpolator offset, the actual loop point will end at an address one greater than the loop address +-Note that the actual audio location is the point 1 word higher than this value due to interpolation offset +-In programs that use the awe, they generally set the loop address as "loopaddress -1" to compensate for the above. +(Note: I am already using address+1 in the interpolators so these things are already as they should.) +*/ + emu_voice->addr.addr += ((uint64_t)emu_voice->cpf_curr_pitch) << 18; + if (emu_voice->addr.addr >= emu_voice->loop_end.addr) + { + emu_voice->addr.int_address -= (emu_voice->loop_end.int_address - emu_voice->loop_start.int_address); + emu_voice->addr.int_address &= EMU8K_MEM_ADDRESS_MASK; + } + + /* TODO: How and when are the target and current values updated */ + emu_voice->cpf_curr_pitch = emu_voice->ptrx_pit_target; + emu_voice->cvcf_curr_volume = emu8k_vol_slide(&emu_voice->volumeslide, emu_voice->vtft_vol_target); + emu_voice->cvcf_curr_filt_ctoff = emu_voice->vtft_filter_target; + } + + /* Update EMU voice registers. */ + emu_voice->ccca = (((uint32_t)emu_voice->ccca_qcontrol) << 24) | emu_voice->addr.int_address; + emu_voice->cpf_curr_frac_addr = emu_voice->addr.fract_address; + + //if ( emu_voice->cvcf_curr_volume != old_vol[c]) { + // pclog("EMUVOL (%d):%d\n", c, emu_voice->cvcf_curr_volume); + // old_vol[c]=emu_voice->cvcf_curr_volume; + //} + //pclog("EMUFILT :%d\n", emu_voice->cvcf_curr_filt_ctoff); + } + + buf = &emu8k->buffer[emu8k->pos * 2]; + emu8k_work_reverb(&emu8k->reverb_in_buffer[emu8k->pos], buf, &emu8k->reverb_engine, new_pos - emu8k->pos); + emu8k_work_chorus(&emu8k->chorus_in_buffer[emu8k->pos], buf, &emu8k->chorus_engine, new_pos - emu8k->pos); + emu8k_work_eq(buf, new_pos - emu8k->pos); + + // Clip signal + for (pos = emu8k->pos; pos < new_pos; pos++) + { + if (buf[0] < -32768) + buf[0] = -32768; + else if (buf[0] > 32767) + buf[0] = 32767; + + if (buf[1] < -32768) + buf[1] = -32768; + else if (buf[1] > 32767) + buf[1] = 32767; + + buf += 2; + } + + /* Update EMU clock. */ + emu8k->sample_count += (new_pos - emu8k->pos); + emu8k->sample_count_virtual = 0; + + emu8k->pos = new_pos; +} + +static void emu8k_init_globals() +{ + int c; + double out; + + /*Create frequency table. (Convert initial pitch register value to a linear speed change) + * The input is encoded such as 0xe000 is center note (no pitch shift) + * and from then on , changing up or down 0x1000 (4096) increments/decrements an octave. + * Note that this is in reference to the 44.1Khz clock that the channels play at. + * The 65536 * 65536 is in order to left-shift the 32bit value to a 64bit value as a 32.32 fixed point. + */ + for (c = 0; c < 0x10000; c++) + { + freqtable[c] = (uint64_t)(exp2((double)(c - 0xe000) / 4096.0) * 65536.0 * 65536.0); + } + /* Shortcut: minimum pitch equals stopped. I don't really know if this is true, but it's better + * since some programs set the pitch to 0 for unused channels. */ + freqtable[0] = 0; + + /* starting at 65535 because it is used for "volume target" register conversion. */ + out = 65535.0; + for (c = 0; c < 256; c++) + { + attentable[c] = (int32_t)out; + out /= sqrt(1.09018); /*0.375 dB steps*/ + } + /* Shortcut: max attenuation is silent, not -96dB. */ + attentable[255] = 0; + + /* Note: these two tables have "db" inverted: 0 dB is max volume, 65535 "db" (-96.32dBFS) is silence. + * Important: Using 65535 as max output value because this is intended to be used with the volume target register! */ + out = 65535.0; + for (c = 0; c < 0x10000; c++) + { + //double db = -(c*6.0205999/65535.0)*16.0; + //out = powf(10.f,db/20.f) * 65536.0; + env_vol_db_to_vol_target[c] = (int32_t)out; + /* calculated from the 65536th root of 65536 */ + out /= 1.00016923970; + } + /* Shortcut: max attenuation is silent, not -96dB. */ + env_vol_db_to_vol_target[0x10000 - 1] = 0; + /* One more position to accept max value being 65536. */ + env_vol_db_to_vol_target[0x10000] = 0; + + for (c = 1; c < 0x10000; c++) + { + out = -680.32142884264 * 20.0 * log10(((double)c) / 65535.0); + env_vol_amplitude_to_db[c] = (int32_t)out; + } + /*Shortcut: max attenuation is silent, not -96dB.*/ + env_vol_amplitude_to_db[0] = 65535; + /* One more position to acceMpt max value being 65536. */ + env_vol_amplitude_to_db[0x10000] = 0; + + for (c = 1; c < 0x10000; c++) + { + out = log2((((double)c) / 0x10000) + 1.0) * 65536.0; + env_mod_hertz_to_octave[c] = (int32_t)out; + } + /*No hertz change, no octave change. */ + env_mod_hertz_to_octave[0] = 0; + /* One more position to accept max value being 65536. */ + env_mod_hertz_to_octave[0x10000] = 65536; + + + /* This formula comes from vince vu/judge dredd's awe32p10 and corresponds to what the freebsd/linux AWE32 driver has. */ + float millis; + for (c = 0; c < 128; c++) + { + if (c == 0) + millis = 0; /* This means never attack. */ + else if (c < 32) + millis = 11878.0 / c; + else + millis = 360 * exp((c - 32) / (16.0 / log(1.0 / 2.0))); + + env_attack_to_samples[c] = 44.1 * millis; + /* This is an alternate formula with linear increments, but probably incorrect: + * millis = (256+4096*(0x7F-c)) */ + } + + /* The LFOs use a triangular waveform starting at zero and going 1/-1/1/-1. + * This table is stored in signed 16bits precision, with a period of 65536 samples */ + for (c = 0; c < 65536; c++) + { + int d = (c + 16384) & 65535; + if (d >= 32768) + lfotable[c] = 32768 + ((32768 - d) * 2); + else + lfotable[c] = (d * 2) - 32768; + } + /* The 65536 * 65536 is in order to left-shift the 32bit value to a 64bit value as a 32.32 fixed point. */ + out = 0.01; + for (c = 0; c < 256; c++) + { + lfofreqtospeed[c] = (uint64_t)(out * 65536.0 / 44100.0 * 65536.0 * 65536.0); + out += 0.042; + } + + for (c = 0; c < 65536; c++) + { + chortable[c] = sin(c * M_PI / 32768.0); + } + + + /* Filter coefficients tables. Note: Values are multiplied by *16777216 to left shift 24 bits. (i.e. 8.24 fixed point) */ + int qidx; + for (qidx = 0; qidx < 16; qidx++) + { + out = 125.0; /* Start at 125Hz */ + for (c = 0; c < 256; c++) + { +#ifdef FILTER_INITIAL + float w0 = sin(2.0*M_PI*out / 44100.0); + /* The value 102.5f has been selected a bit randomly. Pretends to reach 0.2929 at w0 = 1.0 */ + float q = (qidx / 102.5f) * (1.0 + 1.0 / w0); + /* Limit max value. Else it would be 470. */ + if (q > 200) q=200; + filt_coeffs[qidx][c][0] = (int32_t)(w0 * 16777216.0); + filt_coeffs[qidx][c][1] = 16777216.0; + filt_coeffs[qidx][c][2] = (int32_t)((1.0f / (0.7071f + q)) * 16777216.0); +#elif defined FILTER_MOOG + float w0 = sin(2.0 * M_PI * out / 44100.0); + float q_factor = 1.0f - w0; + float p = w0 + 0.8f * w0 * q_factor; + float f = p + p - 1.0f; + float resonance = (1.0 - pow(2.0, -qidx * 24.0 / 90.0)) * 0.8; + float q = resonance * (1.0f + 0.5f * q_factor * (w0 + 5.6f * q_factor * q_factor)); + filt_coeffs[qidx][c][0] = (int32_t)(p * 16777216.0); + filt_coeffs[qidx][c][1] = (int32_t)(f * 16777216.0); + filt_coeffs[qidx][c][2] = (int32_t)(q * 16777216.0); +#elif defined FILTER_CONSTANT + float q = (1.0-pow(2.0,-qidx*24.0/90.0))*0.8; + float coef0 = sin(2.0*M_PI*out / 44100.0); + float coef1 = 1.0 - coef0; + float coef2 = q * (1.0 + 1.0 / coef1); + filt_coeffs[qidx][c][0] = (int32_t)(coef0 * 16777216.0); + filt_coeffs[qidx][c][1] = (int32_t)(coef1 * 16777216.0); + filt_coeffs[qidx][c][2] = (int32_t)(coef2 * 16777216.0); +#endif //FILTER_TYPE + /* 42.66 divisions per octave (the doc says quarter seminotes which is 48, but then it would be almost an octave less) */ + out *= 1.016378315; + /* 42 divisions. This moves the max frequency to 8.5Khz.*/ + //out *= 1.0166404394; + /* This is a linear increment method, that corresponds to the NRPN table, but contradicts the EMU8KPRM doc: */ + //out = 100.0 + (c+1.0)*31.25; //31.25Hz steps */ + } + } + + /* Cubic Resampling ( 4point cubic spline) */ + double const resdouble = 1.0 / (double)CUBIC_RESOLUTION; + for (c = 0; c < CUBIC_RESOLUTION; c++) + { + double x = (double)c * resdouble; + /* Cubic resolution is made of four table, but I've put them all in one table to optimize memory access. */ + cubic_table[c * 4] = (-0.5 * x * x * x + x * x - 0.5 * x); + cubic_table[c * 4 + 1] = (1.5 * x * x * x - 2.5 * x * x + 1.0); + cubic_table[c * 4 + 2] = (-1.5 * x * x * x + 2.0 * x * x + 0.5 * x); + cubic_table[c * 4 + 3] = (0.5 * x * x * x - 0.5 * x * x); + } +} + +emu8k_t* emu8k_alloc(void *rom, void *ram, size_t ram_size) +{ + emu8k_t *emu8k = RTMemAlloc(sizeof(emu8k_t)); + AssertPtrReturn(emu8k, NULL); + + emu8k_init_globals(); + + emu8k->rom = rom; + emu8k->ram = ram; + + /*AWE-DUMP creates ROM images offset by 2 bytes, so if we detect this + then correct it*/ + if (emu8k->rom[3] == 0x314d && emu8k->rom[4] == 0x474d) + { + memmove(&emu8k->rom[0], &emu8k->rom[1], (1024 * 1024) - 2); + emu8k->rom[0x7ffff] = 0; + } + + emu8k->empty = RTMemAllocZ(2*BLOCK_SIZE_WORDS); + AssertPtr(emu8k->empty); + + // Initialize ram_pointers + int j = 0; + for (; j < 0x8; j++) + { + emu8k->ram_pointers[j] = emu8k->rom + (j * BLOCK_SIZE_WORDS); + } + for (; j < 0x20; j++) + { + emu8k->ram_pointers[j] = emu8k->empty; + } + + if (ram_size > 0) + { + /*Clip to 28MB, since that's the max that we can address. */ + Assert(ram_size <= 28 * _1M); + AssertPtr(emu8k->ram); + + const int i_end = ram_size / (sizeof(uint16_t) * BLOCK_SIZE_WORDS); + int i = 0; + for (; i < i_end; i++, j++) + { + emu8k->ram_pointers[j] = emu8k->ram + (i * BLOCK_SIZE_WORDS); + } + emu8k->ram_end_addr = EMU8K_RAM_MEM_START + (ram_size / sizeof(uint16_t)); + } + else + { + emu8k->ram_end_addr = EMU8K_RAM_MEM_START; + } + for (; j < 0x100; j++) + { + emu8k->ram_pointers[j] = emu8k->empty; + + } + + return emu8k; +} + +void emu8k_free(emu8k_t* emu8k) +{ + RTMemFree(emu8k->empty); + RTMemFree(emu8k); +} + +void emu8k_reset(emu8k_t* emu8k) +{ + /* NOTE! read_pos and buffer content is implicitly initialized to zero by the sb_t structure memset on sb_awe32_init() */ + emu8k->reverb_engine.reflections[0].bufsize = 2 * REV_BUFSIZE_STEP; + emu8k->reverb_engine.reflections[1].bufsize = 4 * REV_BUFSIZE_STEP; + emu8k->reverb_engine.reflections[2].bufsize = 8 * REV_BUFSIZE_STEP; + emu8k->reverb_engine.reflections[3].bufsize = 13 * REV_BUFSIZE_STEP; + emu8k->reverb_engine.reflections[4].bufsize = 19 * REV_BUFSIZE_STEP; + emu8k->reverb_engine.reflections[5].bufsize = 26 * REV_BUFSIZE_STEP; + + /*This is a bit random.*/ + for (int c = 0; c < 4; c++) + { + emu8k->reverb_engine.allpass[3 - c].feedback = 0.5; + emu8k->reverb_engine.allpass[3 - c].bufsize = (4 * c) * REV_BUFSIZE_STEP + 55; + emu8k->reverb_engine.allpass[7 - c].feedback = 0.5; + emu8k->reverb_engine.allpass[7 - c].bufsize = (4 * c) * REV_BUFSIZE_STEP + 55; + } + + /* Even when the documentation says that this has to be written by applications to initialize the card, + * several applications and drivers ( aweman on windows, linux oss driver..) read it to detect an AWE card. */ + emu8k->hwcf1 = 0x59; + emu8k->hwcf2 = 0x20; + /* Initial state is muted. 0x04 is unmuted. */ + emu8k->hwcf3 = 0x00; + + emu8k->sample_count = 0; + emu8k->sample_count_virtual = 0; +} + +void emu8k_render(emu8k_t *emu8k, int16_t *buf, size_t frames) +{ + emu8k_update(emu8k, frames); + + // Convert from int32_t samples to int16_t + for (unsigned int i = 0; i < frames * 2; i++) + { + buf[i] = RT_CLAMP(emu8k->buffer[i], INT16_MIN, INT16_MAX); + } + + emu8k->pos = 0; +} + +void emu8k_update_virtual_sample_count(emu8k_t *emu8k, uint16_t sample_count) +{ +#if 0 + if (sample_count > emu8k->sample_count_virtual + 1) { + Log5Func(("big vsc increment: %u : %u -> %u\n", sample_count - emu8k->sample_count_virtual, emu8k->sample_count_virtual, sample_count)); + } +#endif + emu8k->sample_count_virtual = sample_count; +} diff --git a/emu8k.h b/emu8k.h index f5f77fb..f6b7b0f 100644 --- a/emu8k.h +++ b/emu8k.h @@ -48,7 +48,7 @@ extern "C" { typedef struct emu8k_t emu8k_t; -emu8k_t* emu8k_alloc(void *rom, size_t onboard_ram); +emu8k_t* emu8k_alloc(void *rom, void *ram, size_t ram_size); void emu8k_free(emu8k_t *emu8k); void emu8k_reset(emu8k_t *emu8k); diff --git a/emu8k_internal.h b/emu8k_internal.h index 19132fa..376f716 100644 --- a/emu8k_internal.h +++ b/emu8k_internal.h @@ -43,9 +43,8 @@ #define MAXSOUNDBUFLEN (48000 / 10) -#define BLOCK_SIZE_WORDS 0x10000 - /* All these defines are in samples, not in bytes. */ +#define BLOCK_SIZE_WORDS 0x10000 #define EMU8K_MEM_ADDRESS_MASK 0xFFFFFF #define EMU8K_RAM_MEM_START 0x200000 #define EMU8K_FM_MEM_ADDRESS 0xFFFFE0 @@ -415,8 +414,6 @@ typedef struct emu8k_t int cur_reg, cur_voice; - int16_t out_l, out_r; - emu8k_chorus_eng_t chorus_engine; int32_t chorus_in_buffer[MAXSOUNDBUFLEN]; emu8k_reverb_eng_t reverb_engine; diff --git a/scripts/disable.sh b/scripts/disable.sh index 8031d25..1abe8ba 100755 --- a/scripts/disable.sh +++ b/scripts/disable.sh @@ -7,3 +7,5 @@ vm="$1" VBoxManage setextradata "$vm" VBoxInternal/Devices/adlib/0/Trusted VBoxManage setextradata "$vm" VBoxInternal/Devices/adlib/0/Config/MirrorPort VBoxManage setextradata "$vm" VBoxInternal/Devices/mpu401/0/Trusted +VBoxManage setextradata "$vm" VBoxInternal/Devices/mpu401/0/Config/IRQ +VBoxManage setextradata "$vm" VBoxInternal/Devices/emu8000/0/Config/RomFile diff --git a/scripts/enable.sh b/scripts/enable.sh index 315f0ac..3ba42a5 100755 --- a/scripts/enable.sh +++ b/scripts/enable.sh @@ -13,4 +13,4 @@ VBoxManage setextradata "$vm" VBoxInternal/Devices/mpu401/0/Trusted 1 # EMU8000 awe32_romfile=~/.pcem/roms/awe32.raw # Mandatory! -VBoxManage setextradata "$vm" VBoxInternal/Devices/emu8000/0/Config/ROMFile "$awe32_romfile" +VBoxManage setextradata "$vm" VBoxInternal/Devices/emu8000/0/Config/RomFile "$awe32_romfile" -- cgit v1.2.3