2396 lines
107 KiB
C
2396 lines
107 KiB
C
#include <inttypes.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <wchar.h>
|
|
#define _USE_MATH_DEFINES
|
|
#include <math.h>
|
|
#define HAVE_STDARG_H
|
|
|
|
#include <86box/86box.h>
|
|
#include <86box/device.h>
|
|
#include <86box/io.h>
|
|
#include <86box/mem.h>
|
|
#include <86box/rom.h>
|
|
#include <86box/sound.h>
|
|
#include <86box/snd_emu8k.h>
|
|
#include <86box/timer.h>
|
|
#include <86box/plat_unused.h>
|
|
|
|
#if !defined FILTER_INITIAL && !defined FILTER_MOOG && !defined FILTER_CONSTANT
|
|
#if 0
|
|
#define FILTER_INITIAL
|
|
#endif
|
|
# define FILTER_MOOG
|
|
#if 0
|
|
#define FILTER_CONSTANT
|
|
#endif
|
|
#endif
|
|
|
|
#if !defined RESAMPLER_LINEAR && !defined RESAMPLER_CUBIC
|
|
#if 0
|
|
#define RESAMPLER_LINEAR
|
|
#endif
|
|
# define RESAMPLER_CUBIC
|
|
#endif
|
|
|
|
#if 0
|
|
#define EMU8K_DEBUG_REGISTERS
|
|
#endif
|
|
|
|
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;
|
|
int dmareadbit = 0;
|
|
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 << CUBIC_RESOLUTION_LOG)
|
|
/* cubic_table coefficients. */
|
|
static float cubic_table[CUBIC_RESOLUTION * 4];
|
|
|
|
/* conversion from current pitch to linear frequency change (in 32.32 fixed point). */
|
|
static int64_t freqtable[65536];
|
|
/* Conversion from initial attenuation to 16 bit unsigned lineal amplitude (currently only a way to update volume target register) */
|
|
static int32_t attentable[256];
|
|
/* Conversion from envelope dbs (once rigth shifted) (0 = 0dBFS, 65535 = -96dbFS and silence ) to 16 bit unsigned lineal amplitude,
|
|
* to convert to current volume. (0 to 65536) */
|
|
static int32_t env_vol_db_to_vol_target[65537];
|
|
/* Same as above, but to convert amplitude (once rigth shifted) (0 to 65536) to db (0 = 0dBFS, 65535 = -96dbFS and silence ).
|
|
* it is needed so that the delay, attack and hold phase can be added to initial attenuation and tremolo */
|
|
static int32_t env_vol_amplitude_to_db[65537];
|
|
/* Conversion from envelope herts (once right shifted) to octave . it is needed so that the delay, attack and hold phase can be
|
|
* added to initial pitch ,lfos pitch , initial filter and lfo filter */
|
|
static int32_t env_mod_hertz_to_octave[65537];
|
|
/* Conversion from envelope amount to time in samples. */
|
|
static int32_t env_attack_to_samples[128];
|
|
|
|
/* This table has been generated using the following formula:
|
|
* Get the amount of dBs that have to be added each sample to reach 96dBs in the amount
|
|
* of time determined by the encoded value "i".
|
|
* float d = 1.0/((env_decay_to_millis[i]/96.0)*44.1);
|
|
* int result = round(d*21845);
|
|
* The multiplication by 21845 gives a minimum value of 1, and a maximum accumulated value of 1<<21
|
|
* The accumulated value has to be converted to amplitude, and that can be done with the
|
|
* env_vol_db_to_vol_target and shifting by 8
|
|
* In other words, the unit of the table is the 1/21845th of a dB per sample frame, to be added or
|
|
* substracted to the accumulating value_db of the envelope. */
|
|
static int32_t env_decay_to_dbs_or_oct[128] = {
|
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
|
16, 17, 18, 19, 20, 20, 21, 22, 23, 24, 25, 27, 28, 29, 30, 32,
|
|
33, 34, 36, 38, 39, 41, 43, 45, 49, 51, 53, 55, 58, 60, 63, 66,
|
|
69, 72, 75, 78, 82, 85, 89, 93, 97, 102, 106, 111, 116, 121, 126, 132,
|
|
138, 144, 150, 157, 164, 171, 179, 186, 195, 203, 212, 222, 232, 243, 253, 264,
|
|
276, 288, 301, 315, 328, 342, 358, 374, 390, 406, 425, 444, 466, 485, 506, 528,
|
|
553, 580, 602, 634, 660, 689, 721, 755, 780, 820, 849, 897, 932, 970, 1012, 1057,
|
|
1106, 1160, 1219, 1285, 1321, 1399, 1441, 1534, 1585, 1640, 1698, 1829, 1902, 1981, 2068, 2162
|
|
};
|
|
|
|
/* The table "env_decay_to_millis" is based on the table "decay_time_tbl" found in the freebsd/linux
|
|
* AWE32 driver.
|
|
* I tried calculating it using the instructions in awe32p10 from Judge Dredd, but the formula there
|
|
* is wrong.
|
|
*
|
|
*/
|
|
#if 0
|
|
static int32_t env_decay_to_millis[128] = {
|
|
0, 45120, 22614, 15990, 11307, 9508, 7995, 6723, 5653, 5184, 4754, 4359, 3997, 3665, 3361, 3082,
|
|
2828, 2765, 2648, 2535, 2428, 2325, 2226, 2132, 2042, 1955, 1872, 1793, 1717, 1644, 1574, 1507,
|
|
1443, 1382, 1324, 1267, 1214, 1162, 1113, 1066, 978, 936, 897, 859, 822, 787, 754, 722,
|
|
691, 662, 634, 607, 581, 557, 533, 510, 489, 468, 448, 429, 411, 393, 377, 361,
|
|
345, 331, 317, 303, 290, 278, 266, 255, 244, 234, 224, 214, 205, 196, 188, 180,
|
|
172, 165, 158, 151, 145, 139, 133, 127, 122, 117, 112, 107, 102, 98, 94, 90,
|
|
86, 82, 79, 75, 72, 69, 66, 63, 61, 58, 56, 53, 51, 49, 47, 45,
|
|
43, 41, 39, 37, 36, 34, 33, 31, 30, 29, 28, 26, 25, 24, 23, 22,
|
|
};
|
|
#endif
|
|
|
|
/* Table represeting the LFO waveform (signed 16bits with 32768 max int. >> 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
|
|
uint32_t dw_value = 0;
|
|
uint32_t last_read = 0;
|
|
uint32_t last_write = 0;
|
|
uint32_t rep_count_r = 0;
|
|
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) { \
|
|
/*emu8k_log("EMU8K READ %04X-%02X(%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice, emu8k->cur_voice,ret);*/ \
|
|
} else { \
|
|
emu8k_log("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) { \
|
|
/*emu8k_log("EMU8K WRITE %04X-%02X(%d): %04X\n",addr,(emu8k->cur_reg)<<5|emu8k->cur_voice,emu8k->cur_voice, val);*/ \
|
|
} else { \
|
|
emu8k_log("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
|
|
|
|
#ifdef ENABLE_EMU8K_LOG
|
|
int emu8k_do_log = ENABLE_EMU8K_LOG;
|
|
|
|
static void
|
|
emu8k_log(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (emu8k_do_log) {
|
|
va_start(ap, fmt);
|
|
pclog_ex(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
}
|
|
#else
|
|
# define emu8k_log(fmt, ...)
|
|
#endif
|
|
|
|
static inline int16_t
|
|
EMU8K_READ(emu8k_t *emu8k, uint32_t addr)
|
|
{
|
|
const register emu8k_mem_pointers_t addrmem = { { addr } };
|
|
return emu8k->ram_pointers[addrmem.hb_address][addrmem.lw_address];
|
|
}
|
|
|
|
#if NOTUSED
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
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 0
|
|
if ((addr & EMU8K_FM_MEM_ADDRESS) == EMU8K_FM_MEM_ADDRESS) {}
|
|
#endif
|
|
|
|
/* 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(uint16_t addr, void *priv)
|
|
{
|
|
emu8k_t *emu8k = (emu8k_t *) priv;
|
|
uint16_t ret = 0xffff;
|
|
|
|
#ifdef EMU8K_DEBUG_REGISTERS
|
|
if (addr == 0xE22) {
|
|
emu8k_log("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) {
|
|
emu8k_log("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) {
|
|
emu8k_log("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) {
|
|
emu8k_log("EMU8K ...... for %d times\n", rep_count_r);
|
|
rep_count_r = 0;
|
|
}
|
|
last_read = tmpz;
|
|
emu8k_log("EMU8K READ RAM I/O or configuration or clock \n");
|
|
}
|
|
// emu8k_log("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) {
|
|
emu8k_log("EMU8K ...... for %d times\n", rep_count_r);
|
|
rep_count_r = 0;
|
|
}
|
|
last_read = tmpz;
|
|
emu8k_log("EMU8K READ INIT \n");
|
|
}
|
|
// emu8k_log("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) {
|
|
emu8k_log("EMU8K ...... for %d times\n", rep_count_r);
|
|
}
|
|
if (name == 0) {
|
|
emu8k_log("EMU8K READ %04X-%02X(%d/%d): %04X\n", addr, (emu8k->cur_reg) << 5 | emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice, val);
|
|
} else {
|
|
emu8k_log("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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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->wc;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
emu8k_log("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(uint16_t addr, uint16_t val, void *priv)
|
|
{
|
|
emu8k_t *emu8k = (emu8k_t *) priv;
|
|
|
|
/*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);
|
|
|
|
#ifdef EMU8K_DEBUG_REGISTERS
|
|
if (addr == 0xE22) {
|
|
// emu8k_log("EMU8K WRITE POINTER: %d\n", val);
|
|
} else if ((addr & 0xF00) == 0x600) {
|
|
/* These are automatically reported by WRITE16 */
|
|
if (rep_count_w > 1) {
|
|
emu8k_log("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) {
|
|
emu8k_log("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) {
|
|
emu8k_log("EMU8K ...... for %d times\n", rep_count_w);
|
|
rep_count_w = 0;
|
|
}
|
|
last_write = tmpz;
|
|
emu8k_log("EMU8K WRITE RAM I/O or configuration \n");
|
|
}
|
|
// emu8k_log("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) {
|
|
emu8k_log("EMU8K ...... for %d times\n", rep_count_w);
|
|
rep_count_w = 0;
|
|
}
|
|
last_write = tmpz;
|
|
emu8k_log("EMU8K WRITE INIT \n");
|
|
}
|
|
// emu8k_log("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) {
|
|
emu8k_log("EMU8K ...... for %d times\n", rep_count_w);
|
|
}
|
|
if (name == 0) {
|
|
emu8k_log("EMU8K WRITE %04X-%02X(%d/%d): %04X\n", addr, (emu8k->cur_reg) << 5 | emu8k->cur_voice, emu8k->cur_reg, emu8k->cur_voice, val);
|
|
} else {
|
|
emu8k_log("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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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:
|
|
{
|
|
for (uint8_t 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:
|
|
#if 0
|
|
emu8k->reverb_engine.reflections[0].feedback_r = (val&0xF)/15.0;
|
|
#endif
|
|
break;
|
|
case 0x11:
|
|
emu8k->reverb_engine.reflections[1].feedback = (val & 0xF) / 15.0;
|
|
break;
|
|
case 0x13:
|
|
#if 0
|
|
emu8k->reverb_engine.reflections[1].feedback_r = (val&0xF)/15.0;
|
|
#endif
|
|
break;
|
|
case 0x19:
|
|
emu8k->reverb_engine.reflections[2].feedback = (val & 0xF) / 15.0;
|
|
break;
|
|
case 0x1B:
|
|
#if 0
|
|
emu8k->reverb_engine.reflections[2].feedback_r = (val&0xF)/15.0;
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
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:
|
|
#if 0
|
|
emu8k->reverb_engine.refl_in_amp_r = val&0xFF;
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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:
|
|
{
|
|
for (uint8_t 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:
|
|
#if 0
|
|
emu8k->reverb_engine.reflections[3].feedback_r = (val&0xF)/15.0;
|
|
#endif
|
|
break;
|
|
case 0x9:
|
|
emu8k->reverb_engine.reflections[4].feedback = (val & 0xF) / 15.0;
|
|
break;
|
|
case 0xb:
|
|
#if 0
|
|
emu8k->reverb_engine.reflections[4].feedback_r = (val&0xF)/15.0;
|
|
#endif
|
|
break;
|
|
case 0x11:
|
|
emu8k->reverb_engine.reflections[5].feedback = (val & 0xF) / 15.0;
|
|
break;
|
|
case 0x13:
|
|
#if 0
|
|
emu8k->reverb_engine.reflections[5].feedback_r = (val&0xF)/15.0;
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
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;
|
|
|
|
default:
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
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;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0xE02: /* Pointer. also known as BLASTER+0xC02 and EMU+0x802 */
|
|
emu8k->cur_voice = (val & 31);
|
|
emu8k->cur_reg = ((val >> 5) & 7);
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
emu8k_log("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(uint16_t addr, void *priv)
|
|
{
|
|
/* 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(addr & ~1, priv) >> 1;
|
|
return emu8k_inw(addr, priv) & 0xff;
|
|
}
|
|
|
|
void
|
|
emu8k_outb(uint16_t addr, uint8_t val, void *priv)
|
|
{
|
|
/* 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(addr & ~1, val << 8, priv);
|
|
else
|
|
emu8k_outw(addr, val, priv);
|
|
}
|
|
|
|
/* 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)
|
|
{
|
|
for (int pos = 0; pos < count; pos++) {
|
|
double lfo_inter1 = chortable[engine->lfo_pos.int_address];
|
|
#if 0
|
|
double lfo_inter2 = chortable[(engine->lfo_pos.int_address+1)&0xFFFF];
|
|
#endif
|
|
|
|
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;
|
|
|
|
#if 0
|
|
output = emu8k_reverb_allpass_work(&allpasses[0],output);
|
|
#endif
|
|
output = emu8k_reverb_diffuser_work(&allpasses[1], output);
|
|
output = emu8k_reverb_diffuser_work(&allpasses[2], output);
|
|
#if 0
|
|
output = emu8k_reverb_allpass_work(&allpasses[3],output);
|
|
#endif
|
|
|
|
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;
|
|
int32_t dat2;
|
|
int32_t in;
|
|
int32_t 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;
|
|
int32_t dat2;
|
|
int32_t in;
|
|
int32_t 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(UNUSED(int32_t *inoutbuf), UNUSED(int count))
|
|
{
|
|
// TODO: Work EQ over buf
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
#if 0
|
|
int32_t old_pitch[32] = { 0 };
|
|
int32_t old_cut[32] = { 0 };
|
|
int32_t old_vol[32] = { 0 };
|
|
#endif
|
|
void
|
|
emu8k_update(emu8k_t *emu8k)
|
|
{
|
|
int new_pos = (sound_pos_global * FREQ_44100) / SOUND_FREQ;
|
|
if (emu8k->pos >= new_pos)
|
|
return;
|
|
|
|
int32_t *buf;
|
|
emu8k_voice_t *emu_voice;
|
|
int pos;
|
|
|
|
/* 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 (uint8_t 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;
|
|
|
|
default:
|
|
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;
|
|
|
|
default:
|
|
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 0
|
|
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);
|
|
#endif
|
|
}
|
|
|
|
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->wc += (new_pos - emu8k->pos);
|
|
|
|
emu8k->pos = new_pos;
|
|
}
|
|
|
|
void
|
|
emu8k_change_addr(emu8k_t *emu8k, uint16_t emu_addr)
|
|
{
|
|
if (emu8k->addr) {
|
|
io_removehandler(emu8k->addr, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
|
|
io_removehandler(emu8k->addr + 0x400, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
|
|
io_removehandler(emu8k->addr + 0x800, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
|
|
emu8k->addr = 0;
|
|
}
|
|
if (emu_addr) {
|
|
emu8k->addr = emu_addr;
|
|
io_sethandler(emu8k->addr, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
|
|
io_sethandler(emu8k->addr + 0x400, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
|
|
io_sethandler(emu8k->addr + 0x800, 0x0004, emu8k_inb, emu8k_inw, NULL, emu8k_outb, emu8k_outw, NULL, emu8k);
|
|
}
|
|
}
|
|
|
|
/* onboard_ram in kilobytes */
|
|
void
|
|
emu8k_init(emu8k_t *emu8k, uint16_t emu_addr, int onboard_ram)
|
|
{
|
|
uint32_t const BLOCK_SIZE_WORDS = 0x10000;
|
|
FILE *fp;
|
|
int c;
|
|
double out;
|
|
|
|
fp = rom_fopen("roms/sound/creative/awe32.raw", "rb");
|
|
if (!fp)
|
|
fatal("AWE32.RAW not found\n");
|
|
|
|
emu8k->rom = malloc(1024 * 1024);
|
|
if (fread(emu8k->rom, 1, 1048576, fp) != 1048576)
|
|
fatal("emu8k_init(): Error reading data\n");
|
|
fclose(fp);
|
|
/*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 = malloc(2 * BLOCK_SIZE_WORDS);
|
|
memset(emu8k->empty, 0, 2 * BLOCK_SIZE_WORDS);
|
|
|
|
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) {
|
|
/*Clip to 28MB, since that's the max that we can address. */
|
|
if (onboard_ram > 0x7000)
|
|
onboard_ram = 0x7000;
|
|
emu8k->ram = malloc(onboard_ram * 1024);
|
|
memset(emu8k->ram, 0, onboard_ram * 1024);
|
|
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 = 0;
|
|
emu8k->ram_end_addr = EMU8K_RAM_MEM_START;
|
|
}
|
|
for (; j < 0x100; j++) {
|
|
emu8k->ram_pointers[j] = emu8k->empty;
|
|
}
|
|
|
|
emu8k_change_addr(emu8k, emu_addr);
|
|
|
|
/*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++) {
|
|
#if 0
|
|
double db = -(c*6.0205999/65535.0)*16.0;
|
|
out = powf(10.f,db/20.f) * 65536.0;
|
|
#endif
|
|
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 accept 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) */
|
|
for (uint8_t 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 */
|
|
}
|
|
}
|
|
/* 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 (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;
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
/* 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;
|
|
}
|
|
|
|
void
|
|
emu8k_close(emu8k_t *emu8k)
|
|
{
|
|
free(emu8k->rom);
|
|
free(emu8k->ram);
|
|
}
|