/** * emu8950 v1.1.0 * https://github.com/digital-sound-antiques/emu8950 * Copyright (C) 2001-2020 Mitsutaka Okazaki * Copyright (C) 2021-2022 Graham Sanderson * * SPDX-License-Identifier: MIT */ #if USE_EMU8950_OPL #include "emu8950.h" #include #include #include #include #include #define SAMPLE_BUF_SIZE 1024 #ifndef INLINE #if defined(_MSC_VER) #define INLINE __inline #elif defined(__GNUC__) #define INLINE __inline__ #else #define INLINE inline #endif #endif #define _PI_ 3.14159265358979323846264338327950288 /* dynamic range of envelope output */ #if !EMU8950_NO_FLOAT #define EG_STEP 0.1875 #else #define EG_STEPx16 3 #endif /* dynamic range of total level */ #define TL_STEP 0.75 #define TL_BITS 6 /* dynamic range of sustine level */ #define SL_STEP 3.0 #define SL_BITS 4 /* damper speed before key-on. key-scale affects. */ #define DAMPER_RATE 12 #define TL2EG(tl) ((tl) << 2) /* clang-format off */ /* exp_table[255-x] = round((exp2((double)x / 256.0) - 1) * 1024) */ static uint16_t exp_table[256] = { 1018, 1013, 1007, 1002, 996, 991, 986, 980, 975, 969, 964, 959, 953, 948, 942, 937, 932, 927, 921, 916, 911, 906, 900, 895, 890, 885, 880, 874, 869, 864, 859, 854, 849, 844, 839, 834, 829, 824, 819, 814, 809, 804, 799, 794, 789, 784, 779, 774, 770, 765, 760, 755, 750, 745, 741, 736, 731, 726, 722, 717, 712, 708, 703, 698, 693, 689, 684, 680, 675, 670, 666, 661, 657, 652, 648, 643, 639, 634, 630, 625, 621, 616, 612, 607, 603, 599, 594, 590, 585, 581, 577, 572, 568, 564, 560, 555, 551, 547, 542, 538, 534, 530, 526, 521, 517, 513, 509, 505, 501, 496, 492, 488, 484, 480, 476, 472, 468, 464, 460, 456, 452, 448, 444, 440, 436, 432, 428, 424, 420, 416, 412, 409, 405, 401, 397, 393, 389, 385, 382, 378, 374, 370, 367, 363, 359, 355, 352, 348, 344, 340, 337, 333, 329, 326, 322, 318, 315, 311, 308, 304, 300, 297, 293, 290, 286, 283, 279, 276, 272, 268, 265, 262, 258, 255, 251, 248, 244, 241, 237, 234, 231, 227, 224, 220, 217, 214, 210, 207, 204, 200, 197, 194, 190, 187, 184, 181, 177, 174, 171, 168, 164, 161, 158, 155, 152, 148, 145, 142, 139, 136, 133, 130, 126, 123, 120, 117, 114, 111, 108, 105, 102, 99, 96, 93, 90, 87, 84, 81, 78, 75, 72, 69, 66, 63, 60, 57, 54, 51, 48, 45, 42, 40, 37, 34, 31, 28, 25, 22, 20, 17, 14, 11, 8, 6, 3, 0, }; /* logsin_table[x] = round(-log2(sin((x + 0.5) * PI / (PG_WIDTH / 4) / 2)) * 256) */ #if !EMU8950_NO_WAVE_TABLE_MAP #define LOGSIN_TABLE_SIZE PG_WIDTH / 4 #else #define LOGSIN_TABLE_SIZE PG_WIDTH / 2 #endif static uint16_t logsin_table[LOGSIN_TABLE_SIZE] = { 2137, 1731, 1543, 1419, 1326, 1252, 1190, 1137, 1091, 1050, 1013, 979, 949, 920, 894, 869, 846, 825, 804, 785, 767, 749, 732, 717, 701, 687, 672, 659, 646, 633, 621, 609, 598, 587, 576, 566, 556, 546, 536, 527, 518, 509, 501, 492, 484, 476, 468, 461, 453, 446, 439, 432, 425, 418, 411, 405, 399, 392, 386, 380, 375, 369, 363, 358, 352, 347, 341, 336, 331, 326, 321, 316, 311, 307, 302, 297, 293, 289, 284, 280, 276, 271, 267, 263, 259, 255, 251, 248, 244, 240, 236, 233, 229, 226, 222, 219, 215, 212, 209, 205, 202, 199, 196, 193, 190, 187, 184, 181, 178, 175, 172, 169, 167, 164, 161, 159, 156, 153, 151, 148, 146, 143, 141, 138, 136, 134, 131, 129, 127, 125, 122, 120, 118, 116, 114, 112, 110, 108, 106, 104, 102, 100, 98, 96, 94, 92, 91, 89, 87, 85, 83, 82, 80, 78, 77, 75, 74, 72, 70, 69, 67, 66, 64, 63, 62, 60, 59, 57, 56, 55, 53, 52, 51, 49, 48, 47, 46, 45, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 23, 22, 21, 20, 20, 19, 18, 17, 17, 16, 15, 15, 14, 13, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, 7, 7, 6, 6, 5, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, #if EMU8950_NO_WAVE_TABLE_MAP // double the table size to include second 1/4 cycle 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 10, 11, 12, 12, 13, 13, 14, 15, 15, 16, 17, 17, 18, 19, 20, 20, 21, 22, 23, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 45, 46, 47, 48, 49, 51, 52, 53, 55, 56, 57, 59, 60, 62, 63, 64, 66, 67, 69, 70, 72, 74, 75, 77, 78, 80, 82, 83, 85, 87, 89, 91, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 125, 127, 129, 131, 134, 136, 138, 141, 143, 146, 148, 151, 153, 156, 159, 161, 164, 167, 169, 172, 175, 178, 181, 184, 187, 190, 193, 196, 199, 202, 205, 209, 212, 215, 219, 222, 226, 229, 233, 236, 240, 244, 248, 251, 255, 259, 263, 267, 271, 276, 280, 284, 289, 293, 297, 302, 307, 311, 316, 321, 326, 331, 336, 341, 347, 352, 358, 363, 369, 375, 380, 386, 392, 399, 405, 411, 418, 425, 432, 439, 446, 453, 461, 468, 476, 484, 492, 501, 509, 518, 527, 536, 546, 556, 566, 576, 587, 598, 609, 621, 633, 646, 659, 672, 687, 701, 717, 732, 749, 767, 785, 804, 825, 846, 869, 894, 920, 949, 979, 1013, 1050, 1091, 1137, 1190, 1252, 1326, 1419, 1543, 1731, 2137, #endif }; /* clang-format on */ /* amplitude lfo table */ /* The following envelop pattern is verified on real YM2413. */ /* each element repeates 64 cycles */ static uint8_t am_table[210] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, // 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, // 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, // 8, 8, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, // 10, 10, 10, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 11, 11, 11, // 12, 12, 12, 12, 12, 12, 12, 12, // 13, 13, 13, // 12, 12, 12, 12, 12, 12, 12, 12, // 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 10, 10, 10, 10, 10, 10, // 9, 9, 9, 9, 9, 9, 9, 9, 8, 8, 8, 8, 8, 8, 8, 8, // 7, 7, 7, 7, 7, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, // 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, // 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, // 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}; #if !EMU8950_SLOT_RENDER #if !EMU8950_NO_WAVE_TABLE_MAP static uint16_t wave_table_map[4][PG_WIDTH]; #else // we start with // _ _ // / \/ \ which is abs(sine) wave // static uint16_t wav_or_table_lookup[4][4] = { {0, 0, 0x8000, 0x8000}, // .. negate second half {0, 0, 0x0fff, 0x0fff}, // .. attenuate second half {0, 0, 0x0000, 0x0000}, // .. leave second half alone {0, 0xfff, 0, 0xfff}, // .. attenuate 1 and 3 }; #endif /* offset to fnum, rough approximation of 14 cents depth. */ static int8_t pm_table[8][PM_PG_WIDTH] = { {0, 0, 0, 0, 0, 0, 0, 0}, // fnum = 000xxxxx {0, 0, 1, 0, 0, 0, -1, 0}, // fnum = 001xxxxx {0, 1, 2, 1, 0, -1, -2, -1}, // fnum = 010xxxxx {0, 1, 3, 1, 0, -1, -3, -1}, // fnum = 011xxxxx {0, 2, 4, 2, 0, -2, -4, -2}, // fnum = 100xxxxx {0, 2, 5, 2, 0, -2, -5, -2}, // fnum = 101xxxxx {0, 3, 6, 3, 0, -3, -6, -3}, // fnum = 110xxxxx {0, 3, 7, 3, 0, -3, -7, -3}, // fnum = 111xxxxx }; /* envelope decay increment step table */ static uint8_t eg_step_tables[4][8] = { {0, 1, 0, 1, 0, 1, 0, 1}, {0, 1, 0, 1, 1, 1, 0, 1}, {0, 1, 1, 1, 0, 1, 1, 1}, {0, 1, 1, 1, 1, 1, 1, 1}, }; static uint8_t eg_step_tables_fast[4][8] = { {1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 1, 2, 1, 1, 1, 2}, {1, 2, 1, 2, 1, 2, 1, 2}, {1, 2, 2, 2, 1, 2, 2, 2}, }; static uint32_t ml_table[16] = {1, 1 * 2, 2 * 2, 3 * 2, 4 * 2, 5 * 2, 6 * 2, 7 * 2, 8 * 2, 9 * 2, 10 * 2, 10 * 2, 12 * 2, 12 * 2, 15 * 2, 15 * 2}; #endif #if !EMU8950_NO_TLL #if !EMU8950_NO_FLOAT #define dB2(x) ((x)*2) static double kl_table[16] = {dB2(0.000), dB2(9.000), dB2(12.000), dB2(13.875), dB2(15.000), dB2(16.125), dB2(16.875), dB2(17.625), dB2(18.000), dB2(18.750), dB2(19.125), dB2(19.500), dB2(19.875), dB2(20.250), dB2(20.625), dB2(21.000)}; #else #define dB2x16(x) ((uint16_t)((x)*32)) static int16_t kl_tablex16[16] = {dB2x16(0.000), dB2x16(9.000), dB2x16(12.000), dB2x16(13.875), dB2x16(15.000), dB2x16(16.125), dB2x16(16.875), dB2x16(17.625), dB2x16(18.000), dB2x16(18.750), dB2x16(19.125), dB2x16(19.500), dB2x16(19.875), dB2x16(20.250), dB2x16(20.625), dB2x16(21.000)}; #endif #endif #if !EMU8950_NO_TLL static uint32_t tll_table[8 * 16][1 << TL_BITS][4]; #endif static int32_t rks_table[2][32][2]; #define min(i, j) (((i) < (j)) ? (i) : (j)) #define max(i, j) (((i) > (j)) ? (i) : (j)) /*************************************************** Internal Sample Rate Converter ****************************************************/ /* Note: to disable internal rate converter, set clock/72 to output sampling rate. */ /* * LW is truncate length of sinc(x) calculation. * Lower LW is faster, higher LW results better quality. * LW must be a non-zero positive even number, no upper limit. * LW=16 or greater is recommended when upsampling. * LW=8 is practically okay for downsampling. */ #define LW 16 #if !EMU8950_NO_RATECONV /* resolution of sinc(x) table. sinc(x) where 0.0<=x<1.0 corresponds to sinc_table[0...SINC_RESO-1] */ #define SINC_RESO 256 #define SINC_AMP_BITS 12 // double hamming(double x) { return 0.54 - 0.46 * cos(2 * PI * x); } static double blackman(double x) { return 0.42 - 0.5 * cos(2 * _PI_ * x) + 0.08 * cos(4 * _PI_ * x); } static double sinc(double x) { return (x == 0.0 ? 1.0 : sin(_PI_ * x) / (_PI_ * x)); } static double windowed_sinc(double x) { return blackman(0.5 + 0.5 * x / (LW / 2)) * sinc(x); } /* f_inp: input frequency. f_out: output frequencey, ch: number of channels */ OPL_RateConv *OPL_RateConv_new(double f_inp, double f_out, int ch) { OPL_RateConv *conv = malloc(sizeof(OPL_RateConv)); int i; conv->ch = ch; conv->f_ratio = f_inp / f_out; conv->buf = malloc(sizeof(void *) * ch); for (i = 0; i < ch; i++) { conv->buf[i] = malloc(sizeof(conv->buf[0][0]) * LW); } /* create sinc_table for positive 0 <= x < LW/2 */ conv->sinc_table = malloc(sizeof(conv->sinc_table[0]) * SINC_RESO * LW / 2); for (i = 0; i < SINC_RESO * LW / 2; i++) { const double x = (double) i / SINC_RESO; if (f_out < f_inp) { /* for downsampling */ conv->sinc_table[i] = (int16_t) ((1 << SINC_AMP_BITS) * windowed_sinc(x / conv->f_ratio) / conv->f_ratio); } else { /* for upsampling */ conv->sinc_table[i] = (int16_t) ((1 << SINC_AMP_BITS) * windowed_sinc(x)); } } return conv; } static INLINE int16_t lookup_sinc_table(int16_t *table, double x) { int16_t index = (int16_t) (x * SINC_RESO); if (index < 0) index = -index; return table[min(SINC_RESO * LW / 2 - 1, index)]; } void OPL_RateConv_reset(OPL_RateConv *conv) { int i; conv->timer = 0; for (i = 0; i < conv->ch; i++) { memset(conv->buf[i], 0, sizeof(conv->buf[i][0]) * LW); } } /* put original data to this converter at f_inp. */ void OPL_RateConv_putData(OPL_RateConv *conv, int ch, int16_t data) { int16_t *buf = conv->buf[ch]; int i; for (i = 0; i < LW - 1; i++) { buf[i] = buf[i + 1]; } buf[LW - 1] = data; } /* get resampled data from this converter at f_out. */ /* this function must be called f_out / f_inp times per one putData call. */ int16_t OPL_RateConv_getData(OPL_RateConv *conv, int ch) { int16_t *buf = conv->buf[ch]; int32_t sum = 0; int k; double dn; conv->timer += conv->f_ratio; dn = conv->timer - floor(conv->timer); conv->timer = dn; for (k = 0; k < LW; k++) { double x = ((double) k - (LW / 2 - 1)) - dn; sum += buf[k] * lookup_sinc_table(conv->sinc_table, x); } return sum >> SINC_AMP_BITS; } void OPL_RateConv_delete(OPL_RateConv *conv) { int i; for (i = 0; i < conv->ch; i++) { free(conv->buf[i]); } free(conv->buf); free(conv->sinc_table); free(conv); } #endif /*************************************************** Create tables ****************************************************/ static void makeSinTable(void) { #if !EMU8950_NO_WAVE_TABLE_MAP int x; for (x = 0; x < PG_WIDTH; x++) { if (x < PG_WIDTH / 4) { wave_table_map[0][x] = logsin_table[x]; } else if (x < PG_WIDTH / 2) { wave_table_map[0][x] = logsin_table[PG_WIDTH / 2 - x - 1]; } else { wave_table_map[0][x] = 0x8000 | wave_table_map[0][PG_WIDTH - x - 1]; } } for (x = 0; x < PG_WIDTH; x++) { if (x < PG_WIDTH / 2) { wave_table_map[1][x] = wave_table_map[0][x]; } else { wave_table_map[1][x] = 0xfff; } } for (x = 0; x < PG_WIDTH; x++) { if (x < PG_WIDTH / 2) { wave_table_map[2][x] = wave_table_map[0][x]; } else { wave_table_map[2][x] = wave_table_map[0][x - PG_WIDTH / 2]; } } for (x = 0; x < PG_WIDTH; x++) { if (x < PG_WIDTH / 4) { wave_table_map[3][x] = wave_table_map[0][x]; } else if (x < PG_WIDTH / 2) { wave_table_map[3][x] = 0xfff; } else if (x < PG_WIDTH * 3 / 4) { wave_table_map[3][x] = wave_table_map[0][x - PG_WIDTH / 2]; } else { wave_table_map[3][x] = 0xfff; } } #endif } static void makeTllTable(void) { #if !EMU8950_NO_TLL int32_t tmp; int32_t fnum, block, TL, KL, kx; for (fnum = 0; fnum < 16; fnum++) { for (block = 0; block < 8; block++) { for (TL = 0; TL < 64; TL++) { for (KL = 0; KL < 4; KL++) { kx = ((KL & 1) << 1) | ((KL >> 1) & 1); if (KL == 0) { tll_table[(block << 4) | fnum][TL][KL] = TL2EG(TL); } else { #if !EMU8950_NO_FLOAT tmp = (int32_t)(kl_table[fnum] - dB2(3.000) * (7 - block)); if (tmp <= 0) tll_table[(block << 4) | fnum][TL][KL] = TL2EG(TL); else tll_table[(block << 4) | fnum][TL][KL] = (uint32_t)((tmp >> (3 - kx)) / EG_STEP) + TL2EG(TL); #else tmp = (int32_t)(kl_tablex16[fnum] - dB2x16(3.000) * (7 - block)); if (tmp <= 0) tll_table[(block << 4) | fnum][TL][KL] = TL2EG(TL); else tll_table[(block << 4) | fnum][TL][KL] = (uint32_t)((tmp >> (3 - kx)) / EG_STEPx16) + TL2EG(TL); #endif } } } } } #endif } static void makeRksTable(void) { int fnum8, fnum9, blk; int blk_fnum98; for (fnum8 = 0; fnum8 < 2; fnum8++) for (fnum9 = 0; fnum9 < 2; fnum9++) for (blk = 0; blk < 8; blk++) { blk_fnum98 = (blk << 2) | (fnum9 << 1) | fnum8; rks_table[0][blk_fnum98][1] = (blk << 1) + fnum9; rks_table[0][blk_fnum98][0] = blk >> 1; rks_table[1][blk_fnum98][1] = (blk << 1) + (fnum9 & fnum8); rks_table[1][blk_fnum98][0] = blk >> 1; } } static uint8_t table_initialized = 0; static void initializeTables() { makeTllTable(); makeRksTable(); makeSinTable(); table_initialized = 1; } /********************************************************* Synthesizing *********************************************************/ #define SLOT_BD1 12 #define SLOT_BD2 13 #define SLOT_HH 14 #define SLOT_SD 15 #define SLOT_TOM 16 #define SLOT_CYM 17 /* utility macros */ #define MOD(o, x) (&(o)->slot[(x) << 1]) #define CAR(o, x) (&(o)->slot[((x) << 1) | 1]) #define BIT(s, b) (((s) >> (b)) & 1) #if OPL_DEBUG static void _debug_print_patch(OPL_SLOT *slot) { OPL_PATCH *p = slot->patch; printf("[slot#%d am:%d pm:%d eg:%d kr:%d ml:%d kl:%d tl:%d ws:%d fb:%d A:%d D:%d S:%d R:%d]\n", slot->number, // p->AM, p->PM, p->EG, p->KR, p->ML, // p->KL, p->TL, p->WS, p->FB, // p->AR, p->DR, p->SL, p->RR); } static char *_debug_eg_state_name(OPL_SLOT *slot) { switch (slot->eg_state) { case ATTACK: return "attack"; case DECAY: return "decay"; case SUSTAIN: return "sustain"; case RELEASE: return "release"; case DAMP: return "damp"; default: return "unknown"; } } static INLINE void _debug_print_slot_info(OPL_SLOT *slot) { char *name = _debug_eg_state_name(slot); _debug_print_patch(slot); printf("[slot#%d state:%s fnum:%03x rate:%d-%d]\n", slot->number, name, slot->blk_fnum, slot->eg_rate_h, slot->eg_rate_l); fflush(stdout); } #endif enum SLOT_UPDATE_FLAG { UPDATE_WS = 1, UPDATE_TLL = 2, UPDATE_RKS = 4, UPDATE_EG = 8, UPDATE_ALL = 255, }; static INLINE void request_update(OPL_SLOT *slot, int flag) { slot->update_requests |= flag; } static INLINE int get_parameter_rate(OPL_SLOT *slot) { switch (slot->eg_state) { case ATTACK: return slot->patch->AR; case DECAY: return slot->patch->DR; case SUSTAIN: return slot->patch->EG ? 0 : slot->patch->RR; case RELEASE: return slot->patch->RR; default: return 0; } } static void commit_slot_update(OPL_SLOT *slot, uint8_t notesel) { if (slot->update_requests & UPDATE_WS) { #if !EMU8950_NO_WAVE_TABLE_MAP slot->wave_table = wave_table_map[slot->patch->WS & 3]; #else #if !EMU8950_SLOT_RENDER slot->wav_or_table = wav_or_table_lookup[slot->patch->WS & 3]; #endif #endif } if (slot->update_requests & UPDATE_TLL) { #if !EMU8950_NO_TLL if ((slot->type & 1) == 0) { slot->tll = tll_table[slot->blk_fnum >> 6][slot->patch->TL][slot->patch->KL]; } else { slot->tll = tll_table[slot->blk_fnum >> 6][slot->patch->TL][slot->patch->KL]; } #else static const uint8_t kslrom4[16] = { 0 * 4, 32 * 4, 40 * 4, 45 * 4, 48 * 4, 51 * 4, 53 * 4, 55 * 4, 56 * 4, 58 * 4, 59 * 4, 60 * 4, 61 * 4, 62 * 4, 63 * 4, 255 }; int fnum = (slot->blk_fnum >> 6) & 15; int block = (slot->blk_fnum >> 10); int16_t ksl = kslrom4[fnum] - ((0x08 - block) << 5); if (ksl < 0) { slot->tll = slot->patch->TL4; } else { slot->tll = slot->patch->TL4 + (ksl >> slot->patch->KL_SHIFT); } #endif } if (slot->update_requests & UPDATE_RKS) { slot->rks = rks_table[notesel][slot->blk_fnum >> 8][slot->patch->KR]; } if (slot->update_requests & (UPDATE_RKS | UPDATE_EG)) { int p_rate = get_parameter_rate(slot); if (p_rate == 0) { slot->eg_shift = 0; slot->eg_rate_h = 0; slot->eg_rate_l = 0; } else { slot->eg_rate_h = min(15, p_rate + (slot->rks >> 2)); slot->eg_rate_l = slot->rks & 3; if (slot->eg_state == ATTACK) { slot->eg_shift = (0 < slot->eg_rate_h && slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; } else { slot->eg_shift = (slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; } } } #if OPL_DEBUG if (slot->last_eg_state != slot->eg_state) { _debug_print_slot_info(slot); slot->last_eg_state = slot->eg_state; } #endif slot->update_requests = 0; } #if !EMU8950_SLOT_RENDER static void commit_slot_update_eg_only(OPL_SLOT *slot, uint8_t notesel) { assert(slot->update_requests == UPDATE_EG); int p_rate = get_parameter_rate(slot); if (p_rate == 0) { slot->eg_shift = 0; slot->eg_rate_h = 0; slot->eg_rate_l = 0; } else { slot->eg_rate_h = min(15, p_rate + (slot->rks >> 2)); slot->eg_rate_l = slot->rks & 3; if (slot->eg_state == ATTACK) { slot->eg_shift = (0 < slot->eg_rate_h && slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; } else { slot->eg_shift = (slot->eg_rate_h < 12) ? (12 - slot->eg_rate_h) : 0; } } slot->update_requests = 0; } #endif static void reset_slot(OPL_SLOT *slot, int number) { slot->patch = &(slot->__patch); memset(slot->patch, 0, sizeof(OPL_PATCH)); slot->number = number; #if !EMU8950_NO_PERCUSSION_MODE slot->type = number % 2; #endif // slot->pg_keep = 0; #if !EMU8950_NO_WAVE_TABLE_MAP slot->wave_table = wave_table_map[0]; #else #if !EMU8950_SLOT_RENDER slot->wav_or_table = wav_or_table_lookup[0]; #endif #endif // slot->pg_phase = 0; // slot->output[0] = 0; // slot->output[1] = 0; slot->eg_state = RELEASE; // slot->eg_shift = 0; // slot->rks = 0; // slot->tll = 0; // slot->blk_fnum = 0; // slot->blk = 0; // slot->fnum = 0; // slot->pg_out = 0; slot->eg_out = EG_MUTE; } #if !EMU8950_NO_PERCUSSION_MODE #define slot_pg_keep(slot) slot->pg_keep #define opl_perc_mode(opl) opl->perc_mode #else #define slot_pg_keep(slot) 0 #define opl_perc_mode(opl) 0 #endif static INLINE void slotOn(OPL *opl, int i) { OPL_SLOT *slot = &opl->slot[i]; if (min(15, slot->patch->AR + (slot->rks >> 2)) == 15) { slot->eg_state = DECAY; slot->eg_out = 0; } else { slot->eg_state = ATTACK; } if (!slot_pg_keep(slot)) { slot->pg_phase = 0; } request_update(slot, UPDATE_EG); } static INLINE void slotOff(OPL *opl, int i) { OPL_SLOT *slot = &opl->slot[i]; slot->eg_state = RELEASE; request_update(slot, UPDATE_EG); } static INLINE void update_key_status(OPL *opl) { const uint8_t r14 = opl->reg[0xbd]; const uint8_t perc_mode = BIT(r14, 5); uint32_t new_slot_key_status = 0; uint32_t updated_status; int ch; #if !EMU8950_NO_TIMER if (opl->csm_mode && opl->csm_key_count) { new_slot_key_status = 0x3ffff; } #endif for (ch = 0; ch < 9; ch++) if (opl->reg[0xB0 + ch] & 0x20) new_slot_key_status |= 3 << (ch * 2); if (perc_mode) { if (r14 & 0x10) new_slot_key_status |= 3 << SLOT_BD1; if (r14 & 0x01) new_slot_key_status |= 1 << SLOT_HH; if (r14 & 0x08) new_slot_key_status |= 1 << SLOT_SD; if (r14 & 0x04) new_slot_key_status |= 1 << SLOT_TOM; if (r14 & 0x02) new_slot_key_status |= 1 << SLOT_CYM; } updated_status = opl->slot_key_status ^ new_slot_key_status; if (updated_status) { int i; for (i = 0; i < 18; i++) if (BIT(updated_status, i)) { if (BIT(new_slot_key_status, i)) { slotOn(opl, i); } else { slotOff(opl, i); } } } opl->slot_key_status = new_slot_key_status; } /* set f-Nnmber ( fnum : 10bit ) */ static INLINE void set_fnumber(OPL *opl, int ch, int fnum) { OPL_SLOT *car = CAR(opl, ch); OPL_SLOT *mod = MOD(opl, ch); car->fnum = fnum; car->blk_fnum = (car->blk_fnum & 0x1c00) | (fnum & 0x3ff); mod->fnum = fnum; mod->blk_fnum = (mod->blk_fnum & 0x1c00) | (fnum & 0x3ff); request_update(car, UPDATE_EG | UPDATE_RKS | UPDATE_TLL); request_update(mod, UPDATE_EG | UPDATE_RKS | UPDATE_TLL); } /* set block data (blk : 3bit ) */ static INLINE void set_block(OPL *opl, int ch, int blk) { OPL_SLOT *car = CAR(opl, ch); OPL_SLOT *mod = MOD(opl, ch); car->blk = blk; car->blk_fnum = ((blk & 7) << 10) | (car->blk_fnum & 0x3ff); mod->blk = blk; mod->blk_fnum = ((blk & 7) << 10) | (mod->blk_fnum & 0x3ff); request_update(car, UPDATE_EG | UPDATE_RKS | UPDATE_TLL); request_update(mod, UPDATE_EG | UPDATE_RKS | UPDATE_TLL); } static INLINE void update_perc_mode(OPL *opl) { #if !EMU8950_NO_PERCUSSION_MODE const uint8_t new_perc_mode = (opl->reg[0xbd] >> 5) & 1; if (opl->perc_mode != new_perc_mode) { if (new_perc_mode) { opl->slot[SLOT_HH].type = 3; opl->slot[SLOT_HH].pg_keep = 1; opl->slot[SLOT_SD].type = 3; opl->slot[SLOT_TOM].type = 3; opl->slot[SLOT_CYM].type = 3; opl->slot[SLOT_CYM].pg_keep = 1; } else { opl->slot[SLOT_HH].type = 0; opl->slot[SLOT_HH].pg_keep = 0; opl->slot[SLOT_SD].type = 1; opl->slot[SLOT_TOM].type = 0; opl->slot[SLOT_CYM].type = 1; opl->slot[SLOT_CYM].pg_keep = 0; } } opl->perc_mode = new_perc_mode; #else assert(!((opl->reg[0xbd] >> 5) & 1)); // new_perc_mode should be 0 #endif } #if !EMU8950_LINEAR static INLINE void update_ampm(OPL *opl) { #if !EMU8950_NO_TEST_FLAG const uint32_t pm_inc = (opl_test_flag(opl) & 8) ? opl->pm_dphase << 10 : opl->pm_dphase; const uint32_t am_inc = opl_test_flag(opl) ? 64 : 1; if (opl_test_flag(opl) & 2) { opl->pm_phase = 0; opl->am_phase = 0; } else { opl->pm_phase = (opl->pm_phase + pm_inc) & (PM_DP_WIDTH - 1); opl->am_phase += am_inc; } opl->lfo_am = am_table[(opl->am_phase >> 6) % sizeof(am_table)] >> (opl->am_mode ? 0 : 2); #else opl->pm_phase = (opl->pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); opl->am_phase_index++; if (opl->am_phase_index == sizeof(am_table)) opl->am_phase_index = 0; opl->lfo_am = am_table[opl->am_phase_index] >> (opl->am_mode ? 0 : 2); #endif } static void update_noise(OPL *opl, int cycle) { #if !EMU8950_SIMPLER_NOISE int i; for (i = 0; i < cycle; i++) { if (opl->noise & 1) { opl->noise ^= 0x800200; } opl->noise >>= 1; } #endif } static int noise_bit(OPL *opl) { #if !EMU8950_SIMPLER_NOISE return opl->noise & 1; #else if (opl->noise & 1) { opl->noise ^= 0x800200; opl->noise >>= 1; return 1; } opl->noise >>= 1; return 0; #endif } static void update_short_noise(OPL *opl) { const uint32_t pg_hh = opl->slot[SLOT_HH].pg_out; const uint32_t pg_cym = opl->slot[SLOT_CYM].pg_out; const uint8_t h_bit2 = BIT(pg_hh, PG_BITS - 8); const uint8_t h_bit7 = BIT(pg_hh, PG_BITS - 3); const uint8_t h_bit3 = BIT(pg_hh, PG_BITS - 7); const uint8_t c_bit3 = BIT(pg_cym, PG_BITS - 7); const uint8_t c_bit5 = BIT(pg_cym, PG_BITS - 5); opl->short_noise = (h_bit2 ^ h_bit7) | (h_bit3 ^ c_bit5) | (c_bit3 ^ c_bit5); } #endif #if !EMU8950_SLOT_RENDER static INLINE void calc_phase(OPL_SLOT *slot, int32_t pm_phase, uint8_t pm_mode, uint8_t reset) { int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (pm_mode ? 0 : 1); } if (reset) { slot->pg_phase = 0; } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); slot->pg_out = slot->pg_phase >> DP_BASE_BITS; } static INLINE uint8_t lookup_attack_step(OPL_SLOT *slot, uint32_t counter) { int index = (counter >> slot->eg_shift) & 7; switch (slot->eg_rate_h) { case 13: return eg_step_tables_fast[slot->eg_rate_l][index]; case 14: return eg_step_tables_fast[slot->eg_rate_l][index] << 1; case 0: case 15: return 0; default: return eg_step_tables[slot->eg_rate_l][index]; } } static INLINE uint8_t lookup_decay_step(OPL_SLOT *slot, uint32_t counter) { int index = (counter >> slot->eg_shift) & 7; switch (slot->eg_rate_h) { case 0: return 0; case 13: return eg_step_tables_fast[slot->eg_rate_l][index]; case 14: return eg_step_tables_fast[slot->eg_rate_l][index] << 1; case 15: return 4; default: return eg_step_tables[slot->eg_rate_l][index]; } } static INLINE void calc_envelope(OPL_SLOT *slot, uint16_t eg_counter, uint8_t test) { uint16_t mask = (1 << slot->eg_shift) - 1; uint8_t step; if (slot->eg_state == ATTACK) { if (0 < slot->eg_out && slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { step = lookup_attack_step(slot, eg_counter); slot->eg_out += (~slot->eg_out * step) >> 3; } } else { if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); } } switch (slot->eg_state) { case ATTACK: if (slot->eg_out == 0) { slot->eg_state = DECAY; request_update(slot, UPDATE_EG); } break; case DECAY: if ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL) { slot->eg_state = SUSTAIN; request_update(slot, UPDATE_EG); } break; case SUSTAIN: case RELEASE: default: break; } if (test) { slot->eg_out = 0; } } #endif #if !EMU8950_LINEAR static void update_slots(OPL *opl) { int i; opl->eg_counter++; for (i = 0; i < 18; i++) { OPL_SLOT *slot = &opl->slot[i]; if (slot->update_requests) { commit_slot_update(slot, opl->notesel); } calc_envelope(slot, opl->eg_counter, opl_test_flag(opl) & 1); calc_phase(slot, opl->pm_phase, opl->pm_mode, opl_test_flag(opl) & 4); } } #endif /* input: 0..8191 output: -4095..4095 */ static int16_t lookup_exp_table(int16_t i) { /* from andete's expressoin */ int16_t t = (exp_table[(i & 0xffu)] + 1024); int16_t res = t >> ((i & 0x7f00) >> 8); #if EMU8950_LINEAR_NEG_NOT_NOT return ((i & 0x8000) ? -res : res) << 1; #else return ((i & 0x8000) ? ~res : res) << 1; #endif } static INLINE int16_t to_linear(uint16_t h, OPL_SLOT *slot, int16_t am) { uint16_t att; if (slot->eg_out >= EG_MAX) { return 0; } att = min(EG_MUTE, (slot->eg_out + slot->tll + am)) << 3; return lookup_exp_table(h + att); } #define LOGSIN_MASK (PG_WIDTH/4 - 1) #define LOGSIN_MASK2 (PG_WIDTH/2 - 1) //static INLINE uint16_t get_wave_table(OPL_SLOT *slot, uint32_t index) { static uint16_t get_wave_table(OPL_SLOT *slot, uint32_t index) { #if !EMU8950_NO_WAVE_TABLE_MAP return slot->wave_table[index]; #else #if !EMU8950_SLOT_RENDER return slot->wav_or_table[(index >> (PG_BITS - 2))&3] | logsin_table[(index & LOGSIN_MASK2)]; #else assert(0); return 0; #endif #if 0 switch (((index >> (PG_BITS - 4))&0xc) | (slot->patch->WS & 3)) { case 0b0000: case 0b0001: case 0b0010: case 0b0011: case 0b1010: case 0b1011: return logsin_table[index & LOGSIN_MASK]; case 0b0100: case 0b0101: case 0b0110: case 0b1110: return logsin_table[LOGSIN_MASK - (index & LOGSIN_MASK)]; case 0b1000: return 0x8000 | logsin_table[index & LOGSIN_MASK]; case 0b1100: return 0x8000 | logsin_table[LOGSIN_MASK - (index & LOGSIN_MASK)]; default: return 0xfff; } #endif #endif } static INLINE uint16_t get_wave_table_wrap(OPL_SLOT *slot, uint32_t index) { #if !EMU8950_NO_WAVE_TABLE_MAP return get_wave_table(slot, index & (PG_WIDTH - 1)); #else return get_wave_table(slot, index); #endif } static INLINE int16_t calc_slot_car(OPL *opl, int ch, int16_t fm) { OPL_SLOT *slot = CAR(opl, ch); uint8_t am = slot->patch->AM ? opl->lfo_am : 0; slot->output[1] = slot->output[0]; slot->output[0] = to_linear(get_wave_table_wrap(slot, slot->pg_out + 2 * (fm >> 1)), slot, am); return slot->output[0]; } static INLINE int16_t calc_slot_mod(OPL *opl, int ch) { OPL_SLOT *slot = MOD(opl, ch); int16_t fm = slot->patch->FB > 0 ? (slot->output[1] + slot->output[0]) >> (9 - slot->patch->FB) : 0; uint8_t am = slot->patch->AM ? opl->lfo_am : 0; slot->output[1] = slot->output[0]; slot->output[0] = to_linear(get_wave_table_wrap(slot, slot->pg_out + fm), slot, am); return slot->output[0]; } /* Specify phase offset directly based on 10-bit (1024-length) sine table */ #define _PD(phase) ((PG_BITS < 10) ? (phase >> (10 - PG_BITS)) : (phase << (PG_BITS - 10))) #if !EMU8950_NO_PERCUSSION_MODE static INLINE int16_t calc_slot_tom(OPL *opl) { OPL_SLOT *slot = &(opl->slot[SLOT_TOM]); return to_linear(get_wave_table(slot, slot->pg_out), slot, 0); } static INLINE int16_t calc_slot_snare(OPL *opl) { OPL_SLOT *slot = &(opl->slot[SLOT_SD]); uint32_t phase; if (BIT(opl->slot[SLOT_HH].pg_out, PG_BITS - 2)) phase = noise_bit(opl) ? _PD(0x300) : _PD(0x200); else phase = noise_bit(opl) ? _PD(0x0) : _PD(0x100); return to_linear(get_wave_table(slot, phase), slot, 0); } static INLINE int16_t calc_slot_cym(OPL *opl) { OPL_SLOT *slot = &(opl->slot[SLOT_CYM]); uint32_t phase = opl->short_noise ? _PD(0x300) : _PD(0x100); return to_linear(get_wave_table(slot, phase), slot, 0); } static INLINE int16_t calc_slot_hat(OPL *opl) { OPL_SLOT *slot = &(opl->slot[SLOT_HH]); uint32_t phase; if (opl->short_noise) phase = noise_bit(opl) ? _PD(0x2d0) : _PD(0x234); else phase = noise_bit(opl) ? _PD(0x34) : _PD(0xd0); return to_linear(get_wave_table(slot, phase), slot, 0); } #endif #define _MO(x) (-(x) >> 1) #define _RO(x) (x) static INLINE int16_t calc_fm(OPL *opl, int ch) { if (opl->ch_alg[ch]) { return calc_slot_car(opl, ch, 0) + calc_slot_mod(opl, ch); } return calc_slot_car(opl, ch, calc_slot_mod(opl, ch)); } #if !EMU8950_NO_TIMER static void latch_timer1(OPL *opl) { opl->timer1_counter = opl->reg[0x02] << 2; } static void latch_timer2(OPL *opl) { opl->timer2_counter = opl->reg[0x03] << 4; } static void csm_key_on(OPL *opl) { opl->csm_key_count = 1; update_key_status(opl); } static void csm_key_off(OPL *opl) { opl->csm_key_count = 0; update_key_status(opl); } static void update_timer(OPL *opl) { if (opl->csm_mode && 0 < opl->csm_key_count) { csm_key_off(opl); } if (opl->reg[0x04] & 0x01) { opl->timer1_counter++; if (opl->timer1_counter >> 10) { opl->status |= 0x40; // timer1 overflow if (opl->csm_mode) { csm_key_on(opl); } if (opl->timer1_func) { opl->timer1_func(opl->timer1_user_data); } latch_timer1(opl); } } if (opl->reg[0x04] & 0x02) { opl->timer2_counter++; if (opl->timer2_counter >> 12) { opl->status |= 0x20; // timer2 overflow if (opl->timer2_func) { opl->timer2_func(opl->timer2_user_data); } latch_timer2(opl); } } } #endif #if !EMU8950_LINEAR static void update_output(OPL *opl) { int16_t *out; int i; #if !EMU8950_NO_TIMER update_timer(opl); #endif // generate amplitude modulation same for all channels // need am_phase and lfo_am update_ampm(opl); #if EMU8950_SHORT_NOISE_UPDATE_CHECK if (opl->mask & (OPL_MASK_CYM | OPL_MASK_HH)) update_short_noise(opl); #else update_short_noise(opl); #endif update_slots(opl); out = opl->ch_out; /* CH1-6 */ for (i = 0; i < 6; i++) { if (!(opl->mask & OPL_MASK_CH(i))) { out[i] = _MO(calc_fm(opl, i)); } } /* CH7 */ if (!opl_perc_mode(opl)) { if (!(opl->mask & OPL_MASK_CH(6))) { out[6] = _MO(calc_fm(opl, 6)); } } else { if (!(opl->mask & OPL_MASK_BD)) { out[9] = _RO(calc_fm(opl, 6)); } } update_noise(opl, 14); /* CH8 */ if (!opl_perc_mode(opl)) { if (!(opl->mask & OPL_MASK_CH(7))) { out[7] = _MO(calc_fm(opl, 7)); } } else { if (!(opl->mask & OPL_MASK_HH)) { out[10] = _RO(calc_slot_hat(opl)); } if (!(opl->mask & OPL_MASK_SD)) { out[11] = _RO(calc_slot_snare(opl)); } } update_noise(opl, 2); /* CH9 */ if (!opl_perc_mode(opl)) { if (!(opl->mask & OPL_MASK_CH(8))) { out[8] = _MO(calc_fm(opl, 8)); } } else { if (!(opl->mask & OPL_MASK_TOM)) { out[12] = _RO(calc_slot_tom(opl)); } if (!(opl->mask & OPL_MASK_CYM)) { out[13] = _RO(calc_slot_cym(opl)); } } update_noise(opl, 2); } INLINE static void mix_output(OPL *opl) { int16_t out = 0; int i; for (i = 0; i < 15; i++) { out += opl->ch_out[i]; } #if !EMU8950_NO_RATECONV if (opl->conv) { OPL_RateConv_putData(opl->conv, 0, out); } else { opl->mix_out[0] = out; } #else opl->mix_out[0] = out; #endif } INLINE static int16_t mix_output_raw(OPL *opl) { int32_t out = 0; #if !EMU8950_NO_PERCUSSION_MODE for (int i = 0; i < 15; i++) { out += opl->ch_out[i]; } #else for (int i = 0; i < 9; i++) { out += opl->ch_out[i]; } #endif return out; } #endif /*********************************************************** External Interfaces ***********************************************************/ OPL *OPL_new(uint32_t clk, uint32_t rate) { OPL *opl; if (!table_initialized) { initializeTables(); } opl = (OPL *) calloc(sizeof(OPL), 1); if (opl == NULL) return NULL; opl->clk = clk; opl->rate = rate; // opl->mask = 0; #if !EMU8950_NO_RATECONV // opl->conv = NULL; #endif // opl->mix_out[0] = 0; // opl->mix_out[1] = 0; #if !EMU8950_NO_TIMER // opl->timer1_func = NULL; // opl->timer1_user_data = NULL; // opl->timer2_func = NULL; // opl->timer2_user_data = NULL; #endif OPL_reset(opl); return opl; } void OPL_delete(OPL *opl) { #if !EMU8950_NO_RATECONV if (opl->conv) { OPL_RateConv_delete(opl->conv); opl->conv = NULL; } #endif free(opl); } static void reset_rate_conversion_params(OPL *opl) { #if !EMU8950_NO_RATECONV const double f_out = opl->rate; const double f_inp = opl->clk / 72; opl->out_time = 0; opl->out_step = ((uint32_t) f_inp) << 8; opl->inp_step = ((uint32_t) f_out) << 8; if (opl->conv) { OPL_RateConv_delete(opl->conv); opl->conv = NULL; } if (floor(f_inp) != f_out && floor(f_inp + 0.5) != f_out) { opl->conv = OPL_RateConv_new(f_inp, f_out, 2); } if (opl->conv) { OPL_RateConv_reset(opl->conv); } #endif } void OPL_reset(OPL *opl) { int i; if (!opl) return; #if EMU8950_NO_RATECONV // no useful fields to preserve memset(opl, 0, sizeof(*opl)); #else // some fields are not reset opl->adr = 0; opl->notesel = 0; opl->status = 0; opl->csm_mode = 0; opl->csm_key_count = 0; opl->timer1_counter = 0; opl->timer2_counter = 0; opl->pm_phase = 0; opl->am_phase = 0; opl->mask = 0; opl->perc_mode = 0; opl->slot_key_status = 0; opl->eg_counter = 0; #endif #if !EMU8950_NO_PERCUSSION_MODE opl->noise = 1; #endif reset_rate_conversion_params(opl); for (i = 0; i < 18; i++) { reset_slot(&opl->slot[i], i); } // for (i = 0; i < 9; i++) { // opl->ch_alg[i] = 0; // } // for (i = 0; i < 0x100; i++) { // opl->reg[i] = 0; // } opl->reg[0x04] = 0x18; // MASK_EOS | MASK_BUF_RDY opl->pm_dphase = PM_DPHASE; // for (i = 0; i < 15; i++) { // opl->ch_out[i] = 0; // } } void OPL_setRate(OPL *opl, uint32_t rate) { opl->rate = rate; reset_rate_conversion_params(opl); } void OPL_setQuality(OPL *opl, uint8_t q) {} void OPL_setPan(OPL *opl, uint32_t ch, uint8_t pan) { opl->pan[ch & 15] = pan; } #if !EMU8950_LINEAR int16_t OPL_calc(OPL *opl) { while (opl->out_step > opl->out_time) { opl->out_time += opl->inp_step; update_output(opl); mix_output(opl); } opl->out_time -= opl->out_step; #if !EMU8950_NO_RATECONV if (opl->conv) { opl->mix_out[0] = OPL_RateConv_getData(opl->conv, 0); } #endif return opl->mix_out[0]; } void OPL_calc_buffer(OPL *opl, int16_t *buffer, uint32_t nsamples) { assert(opl->out_step == opl->inp_step); for (unsigned i = 0; i < nsamples; i++) { update_output(opl); buffer[i] = mix_output_raw(opl); } } #endif #if PICO_ON_DEVICE #include "hardware/gpio.h" #endif #if !LIB_PICO_PLATFORM #define __not_in_flash_func(x) x #endif #if EMU8950_LINEAR // these return number of samples rendered (can early out due to silence on the slot - not necessarily the channel) uint32_t slot_mod_linear(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase); uint32_t slot_car_linear_alg0(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase); uint32_t slot_car_linear_alg1(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase); #if DUMPO int hack_ch; #endif #if !EMU8950_SLOT_RENDER uint32_t __not_in_flash_func(slot_mod_linear)(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { uint32_t s = 0; uint32_t nsamples_bak = nsamples; if (slot->eg_state == ATTACK) { for (; s < nsamples; s++) { eg_counter++; pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); uint16_t mask = (1 << slot->eg_shift) - 1; if (0 < slot->eg_out && slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { uint8_t step = lookup_attack_step(slot, eg_counter); slot->eg_out += (~slot->eg_out * step) >> 3; } if (slot->eg_out == 0) { slot->eg_state = DECAY; // todo collapse request_update(slot, UPDATE_EG); commit_slot_update_eg_only(slot, opl->notesel); nsamples = s; } int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (opl->pm_mode ? 0 : 1); } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; int16_t fm = slot->patch->FB > 0 ? (slot->output[1] + slot->output[0]) >> (9 - slot->patch->FB) : 0; slot->output[1] = slot->output[0]; uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; opl->mod_buffer[s] = slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); } } if (slot->eg_state == DECAY) { nsamples = nsamples_bak; // todo if note ends we can stop early for (; s < nsamples; s++) { eg_counter++; pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); uint16_t mask = (1 << slot->eg_shift) - 1; if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); // todo check for decay to zero } if ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL) { slot->eg_state = SUSTAIN; request_update(slot, UPDATE_EG); commit_slot_update_eg_only(slot, opl->notesel); nsamples = s; } int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (opl->pm_mode ? 0 : 1); } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; int16_t fm = slot->patch->FB > 0 ? (slot->output[1] + slot->output[0]) >> (9 - slot->patch->FB) : 0; slot->output[1] = slot->output[0]; uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; opl->mod_buffer[s] = slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); } } if (slot->eg_state == SUSTAIN || slot->eg_state == RELEASE) { nsamples = nsamples_bak; for (; s < nsamples; s++) { eg_counter++; pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); uint16_t mask = (1 << slot->eg_shift) - 1; if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); } int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (opl->pm_mode ? 0 : 1); } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; int16_t fm = slot->patch->FB > 0 ? (slot->output[1] + slot->output[0]) >> (9 - slot->patch->FB) : 0; slot->output[1] = slot->output[0]; uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; opl->mod_buffer[s] = slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); } } return nsamples; } uint32_t __not_in_flash_func(slot_car_linear_alg1)(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { uint32_t s = 0; uint32_t nsamples_bak = nsamples; if (slot->eg_state == ATTACK) { for (; s < nsamples; s++) { eg_counter++; pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); uint16_t mask = (1 << slot->eg_shift) - 1; if (0 < slot->eg_out && slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { uint8_t step = lookup_attack_step(slot, eg_counter); slot->eg_out += (~slot->eg_out * step) >> 3; } if (slot->eg_out == 0) { slot->eg_state = DECAY; // todo collapse request_update(slot, UPDATE_EG); commit_slot_update_eg_only(slot, opl->notesel); nsamples = s; } int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (opl->pm_mode ? 0 : 1); } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; slot->output[1] = slot->output[0]; slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out), slot, am); opl->buffer[s] += slot->output[0] + opl->mod_buffer[s]; } } if (slot->eg_state == DECAY) { nsamples = nsamples_bak; // todo if note ends we can stop early for (; s < nsamples; s++) { eg_counter++; pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); uint16_t mask = (1 << slot->eg_shift) - 1; if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); // todo check for decay to zero } if ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL) { slot->eg_state = SUSTAIN; request_update(slot, UPDATE_EG); commit_slot_update_eg_only(slot, opl->notesel); nsamples = s; } int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (opl->pm_mode ? 0 : 1); } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; slot->output[1] = slot->output[0]; slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out), slot, am); opl->buffer[s] += slot->output[0] + opl->mod_buffer[s]; } } if (slot->eg_state == SUSTAIN || slot->eg_state == RELEASE) { nsamples = nsamples_bak; for (; s < nsamples; s++) { eg_counter++; pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); uint16_t mask = (1 << slot->eg_shift) - 1; if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); } int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (opl->pm_mode ? 0 : 1); } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; slot->output[1] = slot->output[0]; slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out), slot, am); opl->buffer[s] += slot->output[0] + opl->mod_buffer[s]; } } return nsamples; } uint32_t __not_in_flash_func(slot_car_linear_alg0)(OPL *opl, OPL_SLOT *slot, uint32_t nsamples, uint32_t eg_counter, uint32_t pm_phase) { uint32_t s = 0; uint32_t nsamples_bak = nsamples; if (slot->eg_state == ATTACK) { for (; s < nsamples; s++) { eg_counter++; pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); uint16_t mask = (1 << slot->eg_shift) - 1; if (0 < slot->eg_out && slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { uint8_t step = lookup_attack_step(slot, eg_counter); slot->eg_out += (~slot->eg_out * step) >> 3; } if (slot->eg_out == 0) { slot->eg_state = DECAY; // todo collapse request_update(slot, UPDATE_EG); commit_slot_update_eg_only(slot, opl->notesel); nsamples = s; } int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (opl->pm_mode ? 0 : 1); } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; slot->output[1] = slot->output[0]; // todo is this masking realy necessary; i doubt it. .. seems to be always even anyway // int32_t fm = 2 * (opl->mod_buffer[s] >> 1); int32_t fm = opl->mod_buffer[s]; slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); opl->buffer[s] += slot->output[0]; } } if (slot->eg_state == DECAY) { nsamples = nsamples_bak; // todo if note ends we can stop early for (; s < nsamples; s++) { if (hack_ch == 0 && s == 12) { breako(); } eg_counter++; pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); uint16_t mask = (1 << slot->eg_shift) - 1; if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); // todo check for decay to zero } if ((slot->patch->SL != 15) && (slot->eg_out >> 4) == slot->patch->SL) { slot->eg_state = SUSTAIN; request_update(slot, UPDATE_EG); commit_slot_update_eg_only(slot, opl->notesel); nsamples = s; } int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (opl->pm_mode ? 0 : 1); } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; slot->output[1] = slot->output[0]; // todo is this masking realy necessary; i doubt it. .. seems to be always even anyway // int32_t fm = 2 * (opl->mod_buffer[s] >> 1); int32_t fm = opl->mod_buffer[s]; slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); opl->buffer[s] += slot->output[0]; } } if (slot->eg_state == SUSTAIN || slot->eg_state == RELEASE) { nsamples = nsamples_bak; for (; s < nsamples; s++) { if (hack_ch == 0 && s == 12) { breako(); } eg_counter++; pm_phase = (pm_phase + opl->pm_dphase) & (PM_DP_WIDTH - 1); uint16_t mask = (1 << slot->eg_shift) - 1; if (slot->eg_rate_h > 0 && (eg_counter & mask) == 0) { slot->eg_out = min(EG_MUTE, slot->eg_out + lookup_decay_step(slot, eg_counter)); } int8_t pm = 0; if (slot->patch->PM) { pm = pm_table[(slot->fnum >> 7) & 7][pm_phase >> (PM_DP_BITS - PM_PG_BITS)]; pm >>= (opl->pm_mode ? 0 : 1); } slot->pg_phase += (((slot->fnum & 0x3ff) + pm) * ml_table[slot->patch->ML]) << slot->blk >> 1; slot->pg_phase &= (DP_WIDTH - 1); uint32_t pg_out = slot->pg_phase >> DP_BASE_BITS; uint8_t am = slot->patch->AM ? opl->lfo_am_buffer[s] : 0; slot->output[1] = slot->output[0]; // todo is this masking realy necessary; i doubt it. .. seems to be always even anyway // int32_t fm = 2 * (opl->mod_buffer[s] >> 1); int32_t fm = opl->mod_buffer[s]; slot->output[0] = to_linear(get_wave_table_wrap(slot, pg_out + fm), slot, am); opl->buffer[s] += slot->output[0]; } } return nsamples; } #endif static_assert(EMU8950_NO_PERCUSSION_MODE, ""); // this produces stereo void OPL_calc_buffer_linear(OPL *opl, int32_t *buffer, uint32_t nsamples) { int i; #if EMU8950_SLOT_RENDER // kind of a nit pick, but so cheap - saves a bug every 24 hours due to an optimization // (we require that incrementing eg_counter is never zero during the rendering loop) opl->eg_counter = (opl->eg_counter & 0x3fffffffu) | 0x80000000u; static uint8_t lfo_am_buffer_lsl3[SAMPLE_BUF_SIZE]; assert(nsamples <= sizeof(lfo_am_buffer_lsl3)); opl->lfo_am_buffer_lsl3 = lfo_am_buffer_lsl3; #else static uint8_t lfo_am_buffer[SAMPLE_BUF_SIZE]; assert(nsamples <= sizeof(lfo_am_buffer)); opl->lfo_am_buffer = lfo_am_buffer; #endif static int16_t mod_buffer[SAMPLE_BUF_SIZE]; opl->mod_buffer = mod_buffer; opl->buffer = buffer; // todo achievable by memcpy for(uint32_t s = 0; sam_phase_index++; if (opl->am_phase_index == sizeof(am_table)) opl->am_phase_index = 0; // todo this is a candidate for remove simply because it is not super noticeable without #if EMU8950_SLOT_RENDER // note <<3 still fits within 8 bits lfo_am_buffer_lsl3[s] = (am_table[opl->am_phase_index] >> (opl->am_mode ? 0 : 2)) << 3; #else lfo_am_buffer[s] = (am_table[opl->am_phase_index] >> (opl->am_mode ? 0 : 2)); #endif buffer[s]=0; } for (i = 0; i < 18; i++) { OPL_SLOT *slot = &opl->slot[i]; int ch = i >> 1; #if DUMPO hack_ch = ch; if (ch == 8 && bc == 1) { printf("HRMPH\n"); } #endif if (slot->update_requests) { commit_slot_update(slot, opl->notesel); } #if EMU8950_SLOT_RENDER slot->lfo_am_buffer_lsl3 = opl->lfo_am_buffer_lsl3; slot->pm_mode = opl->pm_mode; slot->mod_buffer = opl->mod_buffer; #endif if (!(i & 1)) { // ---- MOD SLOT ---- if ((slot+1)->eg_out >= EG_MUTE && (slot+1)->eg_state != ATTACK && !opl->ch_alg[ch]) { #if DUMPO memset(slot_output[i], 0, nsamples*2); memset(slot_output[i+1], 0, nsamples*2); #endif i++; continue; } uint32_t s_mod; #if EMU8950_LINEAR_SKIP // todo consider disabling as almost unnecessary with EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION if (slot->eg_out >= EG_MUTE && slot->eg_state != ATTACK) { s_mod = 0; } else #endif { // printf("MOD %d\n", i); // if (bc == 7 && i == 14) { // printf("WAM\n"); // } s_mod = slot_mod_linear(opl, slot, nsamples, opl->eg_counter, opl->pm_phase); } if (s_mod != nsamples) { memset(opl->mod_buffer + s_mod, 0, (nsamples - s_mod) * 2); } #if DUMPO memcpy(slot_output[i], opl->mod_buffer, nsamples * 2); #endif } else { #if EMU8950_LINEAR_SKIP #if EMU8950_SLOT_RENDER slot->buffer = opl->buffer; #endif uint32_t s_alg; #if EMU8950_LINEAR_SKIP // todo consider disabling as almost unnecessary with EMU8950_LINEAR_END_OF_NOTE_OPTIMIZATION if (slot->eg_out >= EG_MUTE && slot->eg_state != ATTACK) { s_alg = 0; } else #endif #if DUMPO memcpy(opl_buffer_bak, opl->buffer, nsamples * 4); memset(opl->buffer, 0, nsamples * 4); #endif { if (opl->ch_alg[ch]) { s_alg = slot_car_linear_alg1(opl, slot, nsamples, opl->eg_counter, opl->pm_phase); } else { s_alg = slot_car_linear_alg0(opl, slot, nsamples, opl->eg_counter, opl->pm_phase); } } if (s_alg != nsamples) { if (opl->ch_alg[ch]) { for (uint32_t s = s_alg; s < nsamples; s++) { opl->buffer[s] += opl->mod_buffer[s]; } } } #if DUMPO for(uint s = 0; s < nsamples; s++) { slot_output[i][s] = opl->buffer[s]; opl->buffer[s] += opl_buffer_bak[s]; } #endif #endif } } opl->pm_phase = (opl->pm_phase + opl->pm_dphase * nsamples) & (PM_DP_WIDTH - 1); opl->eg_counter += nsamples; } #endif void OPL_calc_buffer_stereo(OPL *opl, int32_t *buffer, uint32_t nsamples) { assert(opl->out_step == opl->inp_step); #if DUMPO bc++; #endif #if !EMU8950_LINEAR for (unsigned i = 0; i < nsamples; i++) { update_output(opl); uint16_t raw = mix_output_raw(opl); #if DUMPO printf("SND %d %d %08x : ", bc, i, raw); for (int i = 0; i < 9; i++) { printf("%04x ", (uint16_t) opl->ch_out[i]); } printf("\n"); #endif buffer[i] = (raw << 16u) | raw; } #else OPL_calc_buffer_linear(opl, buffer, nsamples); // if (0) for (unsigned i = 0; i < nsamples; i++) { #if DUMPO uint16_t raw = _MO(buffer[i]); printf("SND %d %d %08x : ", bc, i, raw); for (int j = 0; j < 18; j++) { printf("%04x ", (uint16_t) slot_output[j][i]); } printf("\n"); #else uint16_t raw = buffer[i] >> 1; // _MO() #endif // todo clamp? buffer[i] = (raw << 16u) | raw; } #endif } void OPL_writeReg(OPL *opl, uint32_t reg, uint8_t data) { // printf("WR %04x %2x\n", reg, data); int32_t s, c; static int32_t stbl[32] = {0, 2, 4, 1, 3, 5, -1, -1, 6, 8, 10, 7, 9, 11, -1, -1, 12, 14, 16, 13, 15, 17, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; reg = reg & 0xff; if ((reg == 0x04) && (data & 0x80)) { // IRQ RESET opl->status = 0; opl->reg[0x04] &= 0x7f; return; } opl->reg[reg] = data; if (reg == 0x01) { #if !EMU8950_NO_TEST_FLAG opl->test_flag = data; #endif } else if (reg == 0x04) { if (data & 0x01) { #if !EMU8950_NO_TIMER latch_timer1(opl); #else printf("WARNING TIMER1 LATCH\n"); #endif } if (data & 0x02) { #if !EMU8950_NO_TIMER latch_timer2(opl); #else printf("WARNING TIMER2 LATCH\n"); #endif } } else if (0x07 <= reg && reg <= 0x12) { if (reg == 0x08) { #if !EMU8950_NO_TIMER opl->csm_mode = (data >> 7) & 1; #else if ((data >> 7) & 1) { printf("WARNING SET CSM MODE\n"); } #endif opl->notesel = (data >> 6) & 1; } } else if (0x20 <= reg && reg < 0x40) { s = stbl[reg - 0x20]; if (s >= 0) { opl->slot[s].patch->AM = (data >> 7) & 1; opl->slot[s].patch->PM = (data >> 6) & 1; opl->slot[s].patch->EG = (data >> 5) & 1; opl->slot[s].patch->KR = (data >> 4) & 1; opl->slot[s].patch->ML = (data) & 15; request_update(&(opl->slot[s]), UPDATE_ALL); } } else if (0x40 <= reg && reg < 0x60) { s = stbl[reg - 0x40]; if (s >= 0) { #if !EMU8950_NO_TLL opl->slot[s].patch->TL = (data)&63; opl->slot[s].patch->KL = (data >> 6) & 3; #else opl->slot[s].patch->TL4 = data << 2; static const uint8_t kslshift[4] = { 8, 1, 2, 0 }; opl->slot[s].patch->KL_SHIFT = kslshift[(data >> 6) & 3]; #endif request_update(&(opl->slot[s]), UPDATE_ALL); } } else if (0x60 <= reg && reg < 0x80) { s = stbl[reg - 0x60]; if (s >= 0) { opl->slot[s].patch->AR = (data >> 4) & 15; opl->slot[s].patch->DR = (data) & 15; request_update(&(opl->slot[s]), UPDATE_EG); } } else if (0x80 <= reg && reg < 0xa0) { s = stbl[reg - 0x80]; if (s >= 0) { opl->slot[s].patch->SL = (data >> 4) & 15; opl->slot[s].patch->RR = (data) & 15; request_update(&(opl->slot[s]), UPDATE_EG); } } else if (0xa0 <= reg && reg < 0xa9) { c = reg - 0xa0; set_fnumber(opl, c, data + ((opl->reg[reg + 0x10] & 3) << 8)); } else if (0xb0 <= reg && reg < 0xb9) { c = reg - 0xb0; set_fnumber(opl, c, ((data & 3) << 8) + opl->reg[reg - 0x10]); set_block(opl, c, (data >> 2) & 7); update_key_status(opl); } else if (0xc0 <= reg && reg < 0xc9) { c = reg - 0xc0; opl->slot[c * 2].patch->FB = (data >> 1) & 7; opl->ch_alg[c] = data & 1; } else if (reg == 0xbd) { update_perc_mode(opl); update_key_status(opl); opl->am_mode = (data >> 7) & 1; opl->pm_mode = (data >> 6) & 1; } else if (0xe0 <= reg && reg < 0x100) { if (opl->reg[0x01] & 0x20) { s = stbl[reg - 0xe0]; if (s >= 0) { opl->slot[s].patch->WS = data & 3; request_update(&(opl->slot[s]), UPDATE_WS); } } } } #endif