2024-04-03 21:22:51 +00:00
|
|
|
/**
|
|
|
|
* emu2413 v1.5.9
|
|
|
|
* https://github.com/digital-sound-antiques/emu2413
|
|
|
|
* Copyright (C) 2020 Mitsutaka Okazaki
|
|
|
|
*
|
|
|
|
* This source refers to the following documents. The author would like to thank all the authors who have
|
|
|
|
* contributed to the writing of them.
|
|
|
|
* - [YM2413 notes](http://www.smspower.org/Development/YM2413) by andete
|
|
|
|
* - ymf262.c by Jarek Burczynski
|
|
|
|
* - [VRC7 presets](https://siliconpr0n.org/archive/doku.php?id=vendor:yamaha:opl2#opll_vrc7_patch_format) by Nuke.YKT
|
|
|
|
* - YMF281B presets by Chabin
|
|
|
|
*/
|
|
|
|
#include "emu2413.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#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
|
|
|
|
|
2024-04-07 19:24:00 +00:00
|
|
|
#define OPLL_TONE_NUM 4
|
2024-04-03 21:22:51 +00:00
|
|
|
/* clang-format off */
|
|
|
|
static uint8_t default_inst[OPLL_TONE_NUM][(16 + 3) * 8] = {{
|
|
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 0: User
|
|
|
|
0x71,0x61,0x1e,0x17,0xd0,0x78,0x00,0x17, // 1: Violin
|
|
|
|
0x13,0x41,0x1a,0x0d,0xd8,0xf7,0x23,0x13, // 2: Guitar
|
|
|
|
0x13,0x01,0x99,0x00,0xf2,0xc4,0x21,0x23, // 3: Piano
|
|
|
|
0x11,0x61,0x0e,0x07,0x8d,0x64,0x70,0x27, // 4: Flute
|
|
|
|
0x32,0x21,0x1e,0x06,0xe1,0x76,0x01,0x28, // 5: Clarinet
|
|
|
|
0x31,0x22,0x16,0x05,0xe0,0x71,0x00,0x18, // 6: Oboe
|
|
|
|
0x21,0x61,0x1d,0x07,0x82,0x81,0x11,0x07, // 7: Trumpet
|
|
|
|
0x33,0x21,0x2d,0x13,0xb0,0x70,0x00,0x07, // 8: Organ
|
|
|
|
0x61,0x61,0x1b,0x06,0x64,0x65,0x10,0x17, // 9: Horn
|
|
|
|
0x41,0x61,0x0b,0x18,0x85,0xf0,0x81,0x07, // A: Synthesizer
|
|
|
|
0x33,0x01,0x83,0x11,0xea,0xef,0x10,0x04, // B: Harpsichord
|
|
|
|
0x17,0xc1,0x24,0x07,0xf8,0xf8,0x22,0x12, // C: Vibraphone
|
|
|
|
0x61,0x50,0x0c,0x05,0xd2,0xf5,0x40,0x42, // D: Synthsizer Bass
|
|
|
|
0x01,0x01,0x55,0x03,0xe9,0x90,0x03,0x02, // E: Acoustic Bass
|
|
|
|
0x41,0x41,0x89,0x03,0xf1,0xe4,0xc0,0x13, // F: Electric Guitar
|
|
|
|
0x01,0x01,0x18,0x0f,0xdf,0xf8,0x6a,0x6d, // R: Bass Drum (from VRC7)
|
|
|
|
0x01,0x01,0x00,0x00,0xc8,0xd8,0xa7,0x68, // R: High-Hat(M) / Snare Drum(C) (from VRC7)
|
|
|
|
0x05,0x01,0x00,0x00,0xf8,0xaa,0x59,0x55, // R: Tom-tom(M) / Top Cymbal(C) (from VRC7)
|
|
|
|
},{
|
|
|
|
/* VRC7 presets from Nuke.YKT */
|
|
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
|
|
0x03,0x21,0x05,0x06,0xe8,0x81,0x42,0x27,
|
|
|
|
0x13,0x41,0x14,0x0d,0xd8,0xf6,0x23,0x12,
|
|
|
|
0x11,0x11,0x08,0x08,0xfa,0xb2,0x20,0x12,
|
|
|
|
0x31,0x61,0x0c,0x07,0xa8,0x64,0x61,0x27,
|
|
|
|
0x32,0x21,0x1e,0x06,0xe1,0x76,0x01,0x28,
|
|
|
|
0x02,0x01,0x06,0x00,0xa3,0xe2,0xf4,0xf4,
|
|
|
|
0x21,0x61,0x1d,0x07,0x82,0x81,0x11,0x07,
|
|
|
|
0x23,0x21,0x22,0x17,0xa2,0x72,0x01,0x17,
|
|
|
|
0x35,0x11,0x25,0x00,0x40,0x73,0x72,0x01,
|
|
|
|
0xb5,0x01,0x0f,0x0F,0xa8,0xa5,0x51,0x02,
|
|
|
|
0x17,0xc1,0x24,0x07,0xf8,0xf8,0x22,0x12,
|
|
|
|
0x71,0x23,0x11,0x06,0x65,0x74,0x18,0x16,
|
|
|
|
0x01,0x02,0xd3,0x05,0xc9,0x95,0x03,0x02,
|
|
|
|
0x61,0x63,0x0c,0x00,0x94,0xC0,0x33,0xf6,
|
|
|
|
0x21,0x72,0x0d,0x00,0xc1,0xd5,0x56,0x06,
|
|
|
|
0x01,0x01,0x18,0x0f,0xdf,0xf8,0x6a,0x6d,
|
|
|
|
0x01,0x01,0x00,0x00,0xc8,0xd8,0xa7,0x68,
|
|
|
|
0x05,0x01,0x00,0x00,0xf8,0xaa,0x59,0x55,
|
|
|
|
},{
|
|
|
|
/* YMF281B presets */
|
|
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 0: User
|
|
|
|
0x62,0x21,0x1a,0x07,0xf0,0x6f,0x00,0x16, // 1: Electric Strings (form Chabin's patch)
|
|
|
|
0x40,0x10,0x45,0x00,0xf6,0x83,0x73,0x63, // 2: Bow Wow (based on plgDavid's patch, KSL fixed)
|
|
|
|
0x13,0x01,0x99,0x00,0xf2,0xc3,0x21,0x23, // 3: Electric Guitar (similar to YM2413 but different DR(C))
|
|
|
|
0x01,0x61,0x0b,0x0f,0xf9,0x64,0x70,0x17, // 4: Organ (based on Chabin, TL/DR fixed)
|
|
|
|
0x32,0x21,0x1e,0x06,0xe1,0x76,0x01,0x28, // 5: Clarinet (identical to YM2413)
|
|
|
|
0x60,0x01,0x82,0x0e,0xf9,0x61,0x20,0x27, // 6: Saxophone (based on plgDavid, PM/EG fixed)
|
|
|
|
0x21,0x61,0x1c,0x07,0x84,0x81,0x11,0x07, // 7: Trumpet (similar to YM2413 but different TL/DR(M))
|
|
|
|
0x37,0x32,0xc9,0x01,0x66,0x64,0x40,0x28, // 8: Street Organ (from Chabin)
|
|
|
|
0x01,0x21,0x07,0x03,0xa5,0x71,0x51,0x07, // 9: Synth Brass (based on Chabin, TL fixed)
|
|
|
|
0x06,0x01,0x5e,0x07,0xf3,0xf3,0xf6,0x13, // A: Electric Piano (based on Chabin, DR/RR/KR fixed)
|
|
|
|
0x00,0x00,0x18,0x06,0xf5,0xf3,0x20,0x23, // B: Bass (based on Chabin, EG fixed)
|
|
|
|
0x17,0xc1,0x24,0x07,0xf8,0xf8,0x22,0x12, // C: Vibraphone (identical to YM2413)
|
|
|
|
0x35,0x64,0x00,0x00,0xff,0xf3,0x77,0xf5, // D: Chimes (from plgDavid)
|
|
|
|
0x11,0x31,0x00,0x07,0xdd,0xf3,0xff,0xfb, // E: Tom Tom II (from plgDavid)
|
|
|
|
0x3a,0x21,0x00,0x07,0x80,0x84,0x0f,0xf5, // F: Noise (based on plgDavid, AR fixed)
|
|
|
|
0x01,0x01,0x18,0x0f,0xdf,0xf8,0x6a,0x6d, // R: Bass Drum (identical to YM2413)
|
|
|
|
0x01,0x01,0x00,0x00,0xc8,0xd8,0xa7,0x68, // R: High-Hat(M) / Snare Drum(C) (identical to YM2413)
|
|
|
|
0x05,0x01,0x00,0x00,0xf8,0xaa,0x59,0x55, // R: Tom-tom(M) / Top Cymbal(C) (identical to YM2413)
|
2024-04-07 19:24:00 +00:00
|
|
|
},{
|
|
|
|
/* YM2423, taken from ymfm */
|
2024-04-07 19:26:27 +00:00
|
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
2024-04-07 19:24:00 +00:00
|
|
|
0x61,0x61,0x1B,0x07,0x94,0x5F,0x10,0x06,
|
|
|
|
0x93,0xB1,0x51,0x04,0xF3,0xF2,0x70,0xFB,
|
|
|
|
0x41,0x21,0x11,0x85,0xF2,0xF2,0x70,0x75,
|
|
|
|
0x93,0xB2,0x28,0x07,0xF3,0xF2,0x70,0xB4,
|
|
|
|
0x72,0x31,0x97,0x05,0x51,0x6F,0x60,0x09,
|
|
|
|
0x13,0x30,0x18,0x06,0xF7,0xF4,0x50,0x85,
|
|
|
|
0x51,0x31,0x1C,0x07,0x51,0x71,0x20,0x26,
|
|
|
|
0x41,0xF4,0x1B,0x07,0x74,0x34,0x00,0x06,
|
|
|
|
0x50,0x30,0x4D,0x03,0x42,0x65,0x20,0x06,
|
|
|
|
0x40,0x20,0x10,0x85,0xF3,0xF5,0x20,0x04,
|
|
|
|
0x61,0x61,0x1B,0x07,0xC5,0x96,0xF3,0xF6,
|
|
|
|
0xF9,0xF1,0xDC,0x00,0xF5,0xF3,0x77,0xF2,
|
|
|
|
0x60,0xA2,0x91,0x03,0x94,0xC1,0xF7,0xF7,
|
|
|
|
0x30,0x30,0x17,0x06,0xF3,0xF1,0xB7,0xFC,
|
|
|
|
0x31,0x36,0x0D,0x05,0xF2,0xF4,0x27,0x9C,
|
|
|
|
0x01,0x01,0x18,0x0F,0xDF,0xF8,0x6A,0x6D,
|
|
|
|
0x01,0x01,0x00,0x00,0xC8,0xD8,0xA7,0x48,
|
|
|
|
0x05,0x01,0x00,0x00,0xF8,0xAA,0x59,0x55,
|
2024-04-03 21:22:51 +00:00
|
|
|
}};
|
|
|
|
/* clang-format on */
|
|
|
|
|
|
|
|
/* phase increment counter */
|
|
|
|
#define DP_BITS 19
|
|
|
|
#define DP_WIDTH (1 << DP_BITS)
|
|
|
|
#define DP_BASE_BITS (DP_BITS - PG_BITS)
|
|
|
|
|
|
|
|
/* dynamic range of envelope output */
|
|
|
|
#define EG_STEP 0.375
|
|
|
|
#define EG_BITS 7
|
|
|
|
#define EG_MUTE ((1 << EG_BITS) - 1)
|
|
|
|
#define EG_MAX (EG_MUTE - 4)
|
|
|
|
|
|
|
|
/* 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(d) ((d) << 1)
|
|
|
|
|
|
|
|
/* sine table */
|
|
|
|
#define PG_BITS 10 /* 2^10 = 1024 length sine table */
|
|
|
|
#define PG_WIDTH (1 << PG_BITS)
|
|
|
|
|
|
|
|
/* clang-format off */
|
|
|
|
/* exp_table[x] = round((exp2((double)x / 256.0) - 1) * 1024) */
|
|
|
|
static uint16_t exp_table[256] = {
|
|
|
|
0, 3, 6, 8, 11, 14, 17, 20, 22, 25, 28, 31, 34, 37, 40, 42,
|
|
|
|
45, 48, 51, 54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90,
|
|
|
|
93, 96, 99, 102, 105, 108, 111, 114, 117, 120, 123, 126, 130, 133, 136, 139,
|
|
|
|
142, 145, 148, 152, 155, 158, 161, 164, 168, 171, 174, 177, 181, 184, 187, 190,
|
|
|
|
194, 197, 200, 204, 207, 210, 214, 217, 220, 224, 227, 231, 234, 237, 241, 244,
|
|
|
|
248, 251, 255, 258, 262, 265, 268, 272, 276, 279, 283, 286, 290, 293, 297, 300,
|
|
|
|
304, 308, 311, 315, 318, 322, 326, 329, 333, 337, 340, 344, 348, 352, 355, 359,
|
|
|
|
363, 367, 370, 374, 378, 382, 385, 389, 393, 397, 401, 405, 409, 412, 416, 420,
|
|
|
|
424, 428, 432, 436, 440, 444, 448, 452, 456, 460, 464, 468, 472, 476, 480, 484,
|
|
|
|
488, 492, 496, 501, 505, 509, 513, 517, 521, 526, 530, 534, 538, 542, 547, 551,
|
|
|
|
555, 560, 564, 568, 572, 577, 581, 585, 590, 594, 599, 603, 607, 612, 616, 621,
|
|
|
|
625, 630, 634, 639, 643, 648, 652, 657, 661, 666, 670, 675, 680, 684, 689, 693,
|
|
|
|
698, 703, 708, 712, 717, 722, 726, 731, 736, 741, 745, 750, 755, 760, 765, 770,
|
|
|
|
774, 779, 784, 789, 794, 799, 804, 809, 814, 819, 824, 829, 834, 839, 844, 849,
|
|
|
|
854, 859, 864, 869, 874, 880, 885, 890, 895, 900, 906, 911, 916, 921, 927, 932,
|
|
|
|
937, 942, 948, 953, 959, 964, 969, 975, 980, 986, 991, 996, 1002, 1007, 1013, 1018
|
|
|
|
};
|
|
|
|
/* fullsin_table[x] = round(-log2(sin((x + 0.5) * PI / (PG_WIDTH / 4) / 2)) * 256) */
|
|
|
|
static uint16_t fullsin_table[PG_WIDTH] = {
|
|
|
|
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,
|
|
|
|
};
|
|
|
|
/* clang-format on */
|
|
|
|
|
|
|
|
static uint16_t halfsin_table[PG_WIDTH];
|
|
|
|
static uint16_t *wave_table_map[2] = {fullsin_table, halfsin_table};
|
|
|
|
|
|
|
|
/* pitch modulator */
|
|
|
|
/* offset to fnum, rough approximation of 14 cents depth. */
|
|
|
|
static int8_t pm_table[8][8] = {
|
|
|
|
{0, 0, 0, 0, 0, 0, 0, 0}, // fnum = 000xxxxxx
|
|
|
|
{0, 0, 1, 0, 0, 0, -1, 0}, // fnum = 001xxxxxx
|
|
|
|
{0, 1, 2, 1, 0, -1, -2, -1}, // fnum = 010xxxxxx
|
|
|
|
{0, 1, 3, 1, 0, -1, -3, -1}, // fnum = 011xxxxxx
|
|
|
|
{0, 2, 4, 2, 0, -2, -4, -2}, // fnum = 100xxxxxx
|
|
|
|
{0, 2, 5, 2, 0, -2, -5, -2}, // fnum = 101xxxxxx
|
|
|
|
{0, 3, 6, 3, 0, -3, -6, -3}, // fnum = 110xxxxxx
|
|
|
|
{0, 3, 7, 3, 0, -3, -7, -3}, // fnum = 111xxxxxx
|
|
|
|
};
|
|
|
|
|
|
|
|
/* 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};
|
|
|
|
|
|
|
|
/* envelope decay increment step table */
|
|
|
|
/* based on andete's research */
|
|
|
|
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},
|
|
|
|
};
|
|
|
|
|
|
|
|
enum __OPLL_EG_STATE { ATTACK, DECAY, SUSTAIN, RELEASE, DAMP, UNKNOWN };
|
|
|
|
|
|
|
|
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};
|
|
|
|
|
|
|
|
#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)};
|
|
|
|
|
|
|
|
static uint32_t tll_table[8 * 16][1 << TL_BITS][4];
|
|
|
|
static int32_t rks_table[8 * 2][2];
|
|
|
|
|
|
|
|
static OPLL_PATCH null_patch = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
|
|
|
static OPLL_PATCH default_patch[OPLL_TONE_NUM][(16 + 3) * 2];
|
|
|
|
|
|
|
|
/* don't forget min/max is defined as a macro in stdlib.h of Visual C. */
|
|
|
|
#ifndef min
|
|
|
|
static INLINE int min(int i, int j) { return (i < j) ? i : j; }
|
|
|
|
#endif
|
|
|
|
#ifndef max
|
|
|
|
static INLINE int max(int i, int j) { return (i > j) ? i : j; }
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/***************************************************
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
/* 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 */
|
|
|
|
OPLL_RateConv *OPLL_RateConv_new(double f_inp, double f_out, int ch) {
|
|
|
|
OPLL_RateConv *conv = malloc(sizeof(OPLL_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 OPLL_RateConv_reset(OPLL_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 OPLL_RateConv_putData(OPLL_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 OPLL_RateConv_getData(OPLL_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 OPLL_RateConv_delete(OPLL_RateConv *conv) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < conv->ch; i++) {
|
|
|
|
free(conv->buf[i]);
|
|
|
|
}
|
|
|
|
free(conv->buf);
|
|
|
|
free(conv->sinc_table);
|
|
|
|
free(conv);
|
|
|
|
}
|
|
|
|
|
|
|
|
/***************************************************
|
|
|
|
|
|
|
|
Create tables
|
|
|
|
|
|
|
|
****************************************************/
|
|
|
|
|
|
|
|
static void makeSinTable(void) {
|
|
|
|
int x;
|
|
|
|
|
|
|
|
for (x = 0; x < PG_WIDTH / 4; x++) {
|
|
|
|
fullsin_table[PG_WIDTH / 4 + x] = fullsin_table[PG_WIDTH / 4 - x - 1];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (x = 0; x < PG_WIDTH / 2; x++) {
|
|
|
|
fullsin_table[PG_WIDTH / 2 + x] = 0x8000 | fullsin_table[x];
|
|
|
|
}
|
|
|
|
|
|
|
|
for (x = 0; x < PG_WIDTH / 2; x++)
|
|
|
|
halfsin_table[x] = fullsin_table[x];
|
|
|
|
|
|
|
|
for (x = PG_WIDTH / 2; x < PG_WIDTH; x++)
|
|
|
|
halfsin_table[x] = 0xfff;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void makeTllTable(void) {
|
|
|
|
|
|
|
|
int32_t tmp;
|
|
|
|
int32_t fnum, block, TL, KL;
|
|
|
|
|
|
|
|
for (fnum = 0; fnum < 16; fnum++) {
|
|
|
|
for (block = 0; block < 8; block++) {
|
|
|
|
for (TL = 0; TL < 64; TL++) {
|
|
|
|
for (KL = 0; KL < 4; KL++) {
|
|
|
|
if (KL == 0) {
|
|
|
|
tll_table[(block << 4) | fnum][TL][KL] = TL2EG(TL);
|
|
|
|
} else {
|
|
|
|
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 - KL)) / EG_STEP) + TL2EG(TL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void makeRksTable(void) {
|
|
|
|
int fnum8, block;
|
|
|
|
for (fnum8 = 0; fnum8 < 2; fnum8++)
|
|
|
|
for (block = 0; block < 8; block++) {
|
|
|
|
rks_table[(block << 1) | fnum8][1] = (block << 1) + fnum8;
|
|
|
|
rks_table[(block << 1) | fnum8][0] = block >> 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void makeDefaultPatch(void) {
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < OPLL_TONE_NUM; i++)
|
|
|
|
for (j = 0; j < 19; j++)
|
|
|
|
OPLL_getDefaultPatch(i, j, &default_patch[i][j * 2]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint8_t table_initialized = 0;
|
|
|
|
|
|
|
|
static void initializeTables(void) {
|
|
|
|
makeTllTable();
|
|
|
|
makeRksTable();
|
|
|
|
makeSinTable();
|
|
|
|
makeDefaultPatch();
|
|
|
|
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 OPLL_DEBUG
|
|
|
|
static void _debug_print_patch(OPLL_SLOT *slot) {
|
|
|
|
OPLL_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(OPLL_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(OPLL_SLOT *slot) {
|
|
|
|
char *name = _debug_eg_state_name(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);
|
|
|
|
_debug_print_patch(slot);
|
|
|
|
fflush(stdout);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static INLINE int get_parameter_rate(OPLL_SLOT *slot) {
|
|
|
|
|
|
|
|
if ((slot->type & 1) == 0 && slot->key_flag == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
|
|
|
if (slot->sus_flag) {
|
|
|
|
return 5;
|
|
|
|
} else if (slot->patch->EG) {
|
|
|
|
return slot->patch->RR;
|
|
|
|
} else {
|
|
|
|
return 7;
|
|
|
|
}
|
|
|
|
case DAMP:
|
|
|
|
return DAMPER_RATE;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum SLOT_UPDATE_FLAG {
|
|
|
|
UPDATE_WS = 1,
|
|
|
|
UPDATE_TLL = 2,
|
|
|
|
UPDATE_RKS = 4,
|
|
|
|
UPDATE_EG = 8,
|
|
|
|
UPDATE_ALL = 255,
|
|
|
|
};
|
|
|
|
|
|
|
|
static INLINE void request_update(OPLL_SLOT *slot, int flag) { slot->update_requests |= flag; }
|
|
|
|
|
|
|
|
static void commit_slot_update(OPLL_SLOT *slot) {
|
|
|
|
|
|
|
|
#if OPLL_DEBUG
|
|
|
|
if (slot->last_eg_state != slot->eg_state) {
|
|
|
|
_debug_print_slot_info(slot);
|
|
|
|
slot->last_eg_state = slot->eg_state;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (slot->update_requests & UPDATE_WS) {
|
|
|
|
slot->wave_table = wave_table_map[slot->patch->WS];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (slot->update_requests & UPDATE_TLL) {
|
|
|
|
if ((slot->type & 1) == 0) {
|
|
|
|
slot->tll = tll_table[slot->blk_fnum >> 5][slot->patch->TL][slot->patch->KL];
|
|
|
|
} else {
|
|
|
|
slot->tll = tll_table[slot->blk_fnum >> 5][slot->volume][slot->patch->KL];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (slot->update_requests & UPDATE_RKS) {
|
|
|
|
slot->rks = rks_table[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;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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) ? (13 - slot->eg_rate_h) : 0;
|
|
|
|
} else {
|
|
|
|
slot->eg_shift = (slot->eg_rate_h < 13) ? (13 - slot->eg_rate_h) : 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
slot->update_requests = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void reset_slot(OPLL_SLOT *slot, int number) {
|
|
|
|
slot->number = number;
|
|
|
|
slot->type = number % 2;
|
|
|
|
slot->pg_keep = 0;
|
|
|
|
slot->wave_table = wave_table_map[0];
|
|
|
|
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->key_flag = 0;
|
|
|
|
slot->sus_flag = 0;
|
|
|
|
slot->blk_fnum = 0;
|
|
|
|
slot->blk = 0;
|
|
|
|
slot->fnum = 0;
|
|
|
|
slot->volume = 0;
|
|
|
|
slot->pg_out = 0;
|
|
|
|
slot->eg_out = EG_MUTE;
|
|
|
|
slot->patch = &null_patch;
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void slotOn(OPLL *opll, int i) {
|
|
|
|
OPLL_SLOT *slot = &opll->slot[i];
|
|
|
|
slot->key_flag = 1;
|
|
|
|
slot->eg_state = DAMP;
|
|
|
|
request_update(slot, UPDATE_EG);
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void slotOff(OPLL *opll, int i) {
|
|
|
|
OPLL_SLOT *slot = &opll->slot[i];
|
|
|
|
slot->key_flag = 0;
|
|
|
|
if (slot->type & 1) {
|
|
|
|
slot->eg_state = RELEASE;
|
|
|
|
request_update(slot, UPDATE_EG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void update_key_status(OPLL *opll) {
|
|
|
|
const uint8_t r14 = opll->reg[0x0e];
|
|
|
|
const uint8_t rhythm_mode = BIT(r14, 5);
|
|
|
|
uint32_t new_slot_key_status = 0;
|
|
|
|
uint32_t updated_status;
|
|
|
|
int ch;
|
|
|
|
|
|
|
|
for (ch = 0; ch < 9; ch++)
|
|
|
|
if (opll->reg[0x20 + ch] & 0x10)
|
|
|
|
new_slot_key_status |= 3 << (ch * 2);
|
|
|
|
|
|
|
|
if (rhythm_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 = opll->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(opll, i);
|
|
|
|
} else {
|
|
|
|
slotOff(opll, i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
opll->slot_key_status = new_slot_key_status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void set_patch(OPLL *opll, int32_t ch, int32_t num) {
|
|
|
|
opll->patch_number[ch] = num;
|
|
|
|
MOD(opll, ch)->patch = &opll->patch[num * 2 + 0];
|
|
|
|
CAR(opll, ch)->patch = &opll->patch[num * 2 + 1];
|
|
|
|
request_update(MOD(opll, ch), UPDATE_ALL);
|
|
|
|
request_update(CAR(opll, ch), UPDATE_ALL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void set_sus_flag(OPLL *opll, int ch, int flag) {
|
|
|
|
CAR(opll, ch)->sus_flag = flag;
|
|
|
|
request_update(CAR(opll, ch), UPDATE_EG);
|
|
|
|
if (MOD(opll, ch)->type & 1) {
|
|
|
|
MOD(opll, ch)->sus_flag = flag;
|
|
|
|
request_update(MOD(opll, ch), UPDATE_EG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set volume ( volume : 6bit, register value << 2 ) */
|
|
|
|
static INLINE void set_volume(OPLL *opll, int ch, int volume) {
|
|
|
|
CAR(opll, ch)->volume = volume;
|
|
|
|
request_update(CAR(opll, ch), UPDATE_TLL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void set_slot_volume(OPLL_SLOT *slot, int volume) {
|
|
|
|
slot->volume = volume;
|
|
|
|
request_update(slot, UPDATE_TLL);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* set f-Nnmber ( fnum : 9bit ) */
|
|
|
|
static INLINE void set_fnumber(OPLL *opll, int ch, int fnum) {
|
|
|
|
OPLL_SLOT *car = CAR(opll, ch);
|
|
|
|
OPLL_SLOT *mod = MOD(opll, ch);
|
|
|
|
car->fnum = fnum;
|
|
|
|
car->blk_fnum = (car->blk_fnum & 0xe00) | (fnum & 0x1ff);
|
|
|
|
mod->fnum = fnum;
|
|
|
|
mod->blk_fnum = (mod->blk_fnum & 0xe00) | (fnum & 0x1ff);
|
|
|
|
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(OPLL *opll, int ch, int blk) {
|
|
|
|
OPLL_SLOT *car = CAR(opll, ch);
|
|
|
|
OPLL_SLOT *mod = MOD(opll, ch);
|
|
|
|
car->blk = blk;
|
|
|
|
car->blk_fnum = ((blk & 7) << 9) | (car->blk_fnum & 0x1ff);
|
|
|
|
mod->blk = blk;
|
|
|
|
mod->blk_fnum = ((blk & 7) << 9) | (mod->blk_fnum & 0x1ff);
|
|
|
|
request_update(car, UPDATE_EG | UPDATE_RKS | UPDATE_TLL);
|
|
|
|
request_update(mod, UPDATE_EG | UPDATE_RKS | UPDATE_TLL);
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void update_rhythm_mode(OPLL *opll) {
|
|
|
|
const uint8_t new_rhythm_mode = (opll->reg[0x0e] >> 5) & 1;
|
|
|
|
|
|
|
|
if (opll->rhythm_mode != new_rhythm_mode) {
|
|
|
|
|
|
|
|
if (new_rhythm_mode) {
|
|
|
|
opll->slot[SLOT_HH].type = 3;
|
|
|
|
opll->slot[SLOT_HH].pg_keep = 1;
|
|
|
|
opll->slot[SLOT_SD].type = 3;
|
|
|
|
opll->slot[SLOT_TOM].type = 3;
|
|
|
|
opll->slot[SLOT_CYM].type = 3;
|
|
|
|
opll->slot[SLOT_CYM].pg_keep = 1;
|
|
|
|
set_patch(opll, 6, 16);
|
|
|
|
set_patch(opll, 7, 17);
|
|
|
|
set_patch(opll, 8, 18);
|
|
|
|
set_slot_volume(&opll->slot[SLOT_HH], ((opll->reg[0x37] >> 4) & 15) << 2);
|
|
|
|
set_slot_volume(&opll->slot[SLOT_TOM], ((opll->reg[0x38] >> 4) & 15) << 2);
|
|
|
|
} else {
|
|
|
|
opll->slot[SLOT_HH].type = 0;
|
|
|
|
opll->slot[SLOT_HH].pg_keep = 0;
|
|
|
|
opll->slot[SLOT_SD].type = 1;
|
|
|
|
opll->slot[SLOT_TOM].type = 0;
|
|
|
|
opll->slot[SLOT_CYM].type = 1;
|
|
|
|
opll->slot[SLOT_CYM].pg_keep = 0;
|
|
|
|
set_patch(opll, 6, opll->reg[0x36] >> 4);
|
|
|
|
set_patch(opll, 7, opll->reg[0x37] >> 4);
|
|
|
|
set_patch(opll, 8, opll->reg[0x38] >> 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
opll->rhythm_mode = new_rhythm_mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_ampm(OPLL *opll) {
|
|
|
|
if (opll->test_flag & 2) {
|
|
|
|
opll->pm_phase = 0;
|
|
|
|
opll->am_phase = 0;
|
|
|
|
} else {
|
|
|
|
opll->pm_phase += (opll->test_flag & 8) ? 1024 : 1;
|
|
|
|
opll->am_phase += (opll->test_flag & 8) ? 64 : 1;
|
|
|
|
}
|
|
|
|
opll->lfo_am = am_table[(opll->am_phase >> 6) % sizeof(am_table)];
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_noise(OPLL *opll, int cycle) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < cycle; i++) {
|
|
|
|
if (opll->noise & 1) {
|
|
|
|
opll->noise ^= 0x800200;
|
|
|
|
}
|
|
|
|
opll->noise >>= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_short_noise(OPLL *opll) {
|
|
|
|
const uint32_t pg_hh = opll->slot[SLOT_HH].pg_out;
|
|
|
|
const uint32_t pg_cym = opll->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);
|
|
|
|
|
|
|
|
opll->short_noise = (h_bit2 ^ h_bit7) | (h_bit3 ^ c_bit5) | (c_bit3 ^ c_bit5);
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void calc_phase(OPLL_SLOT *slot, int32_t pm_phase, uint8_t reset) {
|
|
|
|
const int8_t pm = slot->patch->PM ? pm_table[(slot->fnum >> 6) & 7][(pm_phase >> 10) & 7] : 0;
|
|
|
|
if (reset) {
|
|
|
|
slot->pg_phase = 0;
|
|
|
|
}
|
|
|
|
slot->pg_phase += (((slot->fnum & 0x1ff) * 2 + pm) * ml_table[slot->patch->ML]) << slot->blk >> 2;
|
|
|
|
slot->pg_phase &= (DP_WIDTH - 1);
|
|
|
|
slot->pg_out = slot->pg_phase >> DP_BASE_BITS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE uint8_t lookup_attack_step(OPLL_SLOT *slot, uint32_t counter) {
|
|
|
|
int index;
|
|
|
|
|
|
|
|
switch (slot->eg_rate_h) {
|
|
|
|
case 12:
|
|
|
|
index = (counter & 0xc) >> 1;
|
|
|
|
return 4 - eg_step_tables[slot->eg_rate_l][index];
|
|
|
|
case 13:
|
|
|
|
index = (counter & 0xc) >> 1;
|
|
|
|
return 3 - eg_step_tables[slot->eg_rate_l][index];
|
|
|
|
case 14:
|
|
|
|
index = (counter & 0xc) >> 1;
|
|
|
|
return 2 - eg_step_tables[slot->eg_rate_l][index];
|
|
|
|
case 0:
|
|
|
|
case 15:
|
|
|
|
return 0;
|
|
|
|
default:
|
|
|
|
index = counter >> slot->eg_shift;
|
|
|
|
return eg_step_tables[slot->eg_rate_l][index & 7] ? 4 : 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE uint8_t lookup_decay_step(OPLL_SLOT *slot, uint32_t counter) {
|
|
|
|
int index;
|
|
|
|
|
|
|
|
switch (slot->eg_rate_h) {
|
|
|
|
case 0:
|
|
|
|
return 0;
|
|
|
|
case 13:
|
|
|
|
index = ((counter & 0xc) >> 1) | (counter & 1);
|
|
|
|
return eg_step_tables[slot->eg_rate_l][index];
|
|
|
|
case 14:
|
|
|
|
index = ((counter & 0xc) >> 1);
|
|
|
|
return eg_step_tables[slot->eg_rate_l][index] + 1;
|
|
|
|
case 15:
|
|
|
|
return 2;
|
|
|
|
default:
|
|
|
|
index = counter >> slot->eg_shift;
|
|
|
|
return eg_step_tables[slot->eg_rate_l][index & 7];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void start_envelope(OPLL_SLOT *slot) {
|
|
|
|
if (min(15, slot->patch->AR + (slot->rks >> 2)) == 15) {
|
|
|
|
slot->eg_state = DECAY;
|
|
|
|
slot->eg_out = 0;
|
|
|
|
} else {
|
|
|
|
slot->eg_state = ATTACK;
|
|
|
|
}
|
|
|
|
request_update(slot, UPDATE_EG);
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE void calc_envelope(OPLL_SLOT *slot, OPLL_SLOT *buddy, uint16_t eg_counter, uint8_t test) {
|
|
|
|
|
|
|
|
uint32_t mask = (1 << slot->eg_shift) - 1;
|
|
|
|
uint8_t s;
|
|
|
|
|
|
|
|
if (slot->eg_state == ATTACK) {
|
|
|
|
if (0 < slot->eg_out && 0 < slot->eg_rate_h && (eg_counter & mask & ~3) == 0) {
|
|
|
|
s = lookup_attack_step(slot, eg_counter);
|
|
|
|
if (0 < s) {
|
|
|
|
slot->eg_out = max(0, ((int)slot->eg_out - (slot->eg_out >> s) - 1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} 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 DAMP:
|
|
|
|
// DAMP to ATTACK transition is occured when the envelope reaches EG_MAX (max attenuation but it's not mute).
|
|
|
|
// Do not forget to check (eg_counter & mask) == 0 to synchronize it with the progress of the envelope.
|
|
|
|
if (slot->eg_out >= EG_MAX && (eg_counter & mask) == 0) {
|
|
|
|
start_envelope(slot);
|
|
|
|
if (slot->type & 1) {
|
|
|
|
if (!slot->pg_keep) {
|
|
|
|
slot->pg_phase = 0;
|
|
|
|
}
|
|
|
|
if (buddy && !buddy->pg_keep) {
|
|
|
|
buddy->pg_phase = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ATTACK:
|
|
|
|
if (slot->eg_out == 0) {
|
|
|
|
slot->eg_state = DECAY;
|
|
|
|
request_update(slot, UPDATE_EG);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DECAY:
|
|
|
|
// DECAY to SUSTAIN transition must be checked at every cycle regardless of the conditions of the envelope rate and
|
|
|
|
// counter. i.e. the transition is not synchronized with the progress of the envelope.
|
|
|
|
if ((slot->eg_out >> 3) == 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void update_slots(OPLL *opll) {
|
|
|
|
int i;
|
|
|
|
opll->eg_counter++;
|
|
|
|
|
|
|
|
for (i = 0; i < 18; i++) {
|
|
|
|
OPLL_SLOT *slot = &opll->slot[i];
|
|
|
|
OPLL_SLOT *buddy = NULL;
|
|
|
|
if (slot->type == 0) {
|
|
|
|
buddy = &opll->slot[i + 1];
|
|
|
|
}
|
|
|
|
if (slot->type == 1) {
|
|
|
|
buddy = &opll->slot[i - 1];
|
|
|
|
}
|
|
|
|
if (slot->update_requests) {
|
|
|
|
commit_slot_update(slot);
|
|
|
|
}
|
|
|
|
calc_envelope(slot, buddy, opll->eg_counter, opll->test_flag & 1);
|
|
|
|
calc_phase(slot, opll->pm_phase, opll->test_flag & 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* output: -4095...4095 */
|
|
|
|
static INLINE int16_t lookup_exp_table(uint16_t i) {
|
|
|
|
/* from andete's expression */
|
|
|
|
int16_t t = (exp_table[(i & 0xff) ^ 0xff] + 1024);
|
|
|
|
int16_t res = t >> ((i & 0x7f00) >> 8);
|
|
|
|
return ((i & 0x8000) ? ~res : res) << 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE int16_t to_linear(uint16_t h, OPLL_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)) << 4;
|
|
|
|
return lookup_exp_table(h + att);
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE int16_t calc_slot_car(OPLL *opll, int ch, int16_t fm) {
|
|
|
|
OPLL_SLOT *slot = CAR(opll, ch);
|
|
|
|
|
|
|
|
uint8_t am = slot->patch->AM ? opll->lfo_am : 0;
|
|
|
|
|
|
|
|
slot->output[1] = slot->output[0];
|
|
|
|
slot->output[0] = to_linear(slot->wave_table[(slot->pg_out + 2 * (fm >> 1)) & (PG_WIDTH - 1)], slot, am);
|
|
|
|
|
|
|
|
return slot->output[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE int16_t calc_slot_mod(OPLL *opll, int ch) {
|
|
|
|
OPLL_SLOT *slot = MOD(opll, 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 ? opll->lfo_am : 0;
|
|
|
|
|
|
|
|
slot->output[1] = slot->output[0];
|
|
|
|
slot->output[0] = to_linear(slot->wave_table[(slot->pg_out + fm) & (PG_WIDTH - 1)], slot, am);
|
|
|
|
|
|
|
|
return slot->output[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE int16_t calc_slot_tom(OPLL *opll) {
|
|
|
|
OPLL_SLOT *slot = MOD(opll, 8);
|
|
|
|
|
|
|
|
return to_linear(slot->wave_table[slot->pg_out], slot, 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)))
|
|
|
|
|
|
|
|
static INLINE int16_t calc_slot_snare(OPLL *opll) {
|
|
|
|
OPLL_SLOT *slot = CAR(opll, 7);
|
|
|
|
|
|
|
|
uint32_t phase;
|
|
|
|
|
|
|
|
if (BIT(slot->pg_out, PG_BITS - 2))
|
|
|
|
phase = (opll->noise & 1) ? _PD(0x300) : _PD(0x200);
|
|
|
|
else
|
|
|
|
phase = (opll->noise & 1) ? _PD(0x0) : _PD(0x100);
|
|
|
|
|
|
|
|
return to_linear(slot->wave_table[phase], slot, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE int16_t calc_slot_cym(OPLL *opll) {
|
|
|
|
OPLL_SLOT *slot = CAR(opll, 8);
|
|
|
|
|
|
|
|
uint32_t phase = opll->short_noise ? _PD(0x300) : _PD(0x100);
|
|
|
|
|
|
|
|
return to_linear(slot->wave_table[phase], slot, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
static INLINE int16_t calc_slot_hat(OPLL *opll) {
|
|
|
|
OPLL_SLOT *slot = MOD(opll, 7);
|
|
|
|
|
|
|
|
uint32_t phase;
|
|
|
|
|
|
|
|
if (opll->short_noise)
|
|
|
|
phase = (opll->noise & 1) ? _PD(0x2d0) : _PD(0x234);
|
|
|
|
else
|
|
|
|
phase = (opll->noise & 1) ? _PD(0x34) : _PD(0xd0);
|
|
|
|
|
|
|
|
return to_linear(slot->wave_table[phase], slot, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
#define _MO(x) (-(x) >> 1)
|
|
|
|
#define _RO(x) (x)
|
|
|
|
|
|
|
|
static void update_output(OPLL *opll) {
|
|
|
|
int16_t *out;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
update_ampm(opll);
|
|
|
|
update_short_noise(opll);
|
|
|
|
update_slots(opll);
|
|
|
|
|
|
|
|
out = opll->ch_out;
|
|
|
|
|
|
|
|
/* CH1-6 */
|
|
|
|
for (i = 0; i < 6; i++) {
|
|
|
|
if (!(opll->mask & OPLL_MASK_CH(i))) {
|
|
|
|
out[i] = _MO(calc_slot_car(opll, i, calc_slot_mod(opll, i)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* CH7 */
|
|
|
|
if (!opll->rhythm_mode) {
|
|
|
|
if (!(opll->mask & OPLL_MASK_CH(6))) {
|
|
|
|
out[6] = _MO(calc_slot_car(opll, 6, calc_slot_mod(opll, 6)));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!(opll->mask & OPLL_MASK_BD)) {
|
|
|
|
out[9] = _RO(calc_slot_car(opll, 6, calc_slot_mod(opll, 6)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
update_noise(opll, 14);
|
|
|
|
|
|
|
|
/* CH8 */
|
|
|
|
if (!opll->rhythm_mode) {
|
|
|
|
if (!(opll->mask & OPLL_MASK_CH(7))) {
|
|
|
|
out[7] = _MO(calc_slot_car(opll, 7, calc_slot_mod(opll, 7)));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!(opll->mask & OPLL_MASK_HH)) {
|
|
|
|
out[10] = _RO(calc_slot_hat(opll));
|
|
|
|
}
|
|
|
|
if (!(opll->mask & OPLL_MASK_SD)) {
|
|
|
|
out[11] = _RO(calc_slot_snare(opll));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
update_noise(opll, 2);
|
|
|
|
|
|
|
|
/* CH9 */
|
|
|
|
if (!opll->rhythm_mode) {
|
|
|
|
if (!(opll->mask & OPLL_MASK_CH(8))) {
|
|
|
|
out[8] = _MO(calc_slot_car(opll, 8, calc_slot_mod(opll, 8)));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!(opll->mask & OPLL_MASK_TOM)) {
|
|
|
|
out[12] = _RO(calc_slot_tom(opll));
|
|
|
|
}
|
|
|
|
if (!(opll->mask & OPLL_MASK_CYM)) {
|
|
|
|
out[13] = _RO(calc_slot_cym(opll));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
update_noise(opll, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
INLINE static void mix_output(OPLL *opll) {
|
|
|
|
int16_t out = 0;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 14; i++) {
|
|
|
|
out += opll->ch_out[i];
|
|
|
|
}
|
|
|
|
if (opll->conv) {
|
|
|
|
OPLL_RateConv_putData(opll->conv, 0, out);
|
|
|
|
} else {
|
|
|
|
opll->mix_out[0] = out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
INLINE static void mix_output_stereo(OPLL *opll) {
|
|
|
|
int16_t *out = opll->mix_out;
|
|
|
|
int i;
|
|
|
|
out[0] = out[1] = 0;
|
|
|
|
for (i = 0; i < 14; i++) {
|
|
|
|
if (opll->pan[i] & 2)
|
|
|
|
out[0] += (int16_t)(opll->ch_out[i] * opll->pan_fine[i][0]);
|
|
|
|
if (opll->pan[i] & 1)
|
|
|
|
out[1] += (int16_t)(opll->ch_out[i] * opll->pan_fine[i][1]);
|
|
|
|
}
|
|
|
|
if (opll->conv) {
|
|
|
|
OPLL_RateConv_putData(opll->conv, 0, out[0]);
|
|
|
|
OPLL_RateConv_putData(opll->conv, 1, out[1]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************
|
|
|
|
|
|
|
|
External Interfaces
|
|
|
|
|
|
|
|
***********************************************************/
|
|
|
|
|
|
|
|
OPLL *OPLL_new(uint32_t clk, uint32_t rate) {
|
|
|
|
OPLL *opll;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!table_initialized) {
|
|
|
|
initializeTables();
|
|
|
|
}
|
|
|
|
|
|
|
|
opll = (OPLL *)calloc(sizeof(OPLL), 1);
|
|
|
|
if (opll == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
for (i = 0; i < 19 * 2; i++)
|
|
|
|
memcpy(&opll->patch[i], &null_patch, sizeof(OPLL_PATCH));
|
|
|
|
|
|
|
|
opll->clk = clk;
|
|
|
|
opll->rate = rate;
|
|
|
|
opll->mask = 0;
|
|
|
|
opll->conv = NULL;
|
|
|
|
opll->mix_out[0] = 0;
|
|
|
|
opll->mix_out[1] = 0;
|
|
|
|
|
|
|
|
OPLL_reset(opll);
|
|
|
|
OPLL_setChipType(opll, 0);
|
|
|
|
OPLL_resetPatch(opll, 0);
|
|
|
|
return opll;
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_delete(OPLL *opll) {
|
|
|
|
if (opll->conv) {
|
|
|
|
OPLL_RateConv_delete(opll->conv);
|
|
|
|
opll->conv = NULL;
|
|
|
|
}
|
|
|
|
free(opll);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void reset_rate_conversion_params(OPLL *opll) {
|
|
|
|
const double f_out = opll->rate;
|
|
|
|
const double f_inp = opll->clk / 72.0;
|
|
|
|
|
|
|
|
opll->out_time = 0;
|
|
|
|
opll->out_step = f_inp;
|
|
|
|
opll->inp_step = f_out;
|
|
|
|
|
|
|
|
if (opll->conv) {
|
|
|
|
OPLL_RateConv_delete(opll->conv);
|
|
|
|
opll->conv = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (floor(f_inp) != f_out && floor(f_inp + 0.5) != f_out) {
|
|
|
|
opll->conv = OPLL_RateConv_new(f_inp, f_out, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (opll->conv) {
|
|
|
|
OPLL_RateConv_reset(opll->conv);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_reset(OPLL *opll) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!opll)
|
|
|
|
return;
|
|
|
|
|
|
|
|
opll->adr = 0;
|
|
|
|
|
|
|
|
opll->pm_phase = 0;
|
|
|
|
opll->am_phase = 0;
|
|
|
|
|
|
|
|
opll->noise = 0x1;
|
|
|
|
opll->mask = 0;
|
|
|
|
|
|
|
|
opll->rhythm_mode = 0;
|
|
|
|
opll->slot_key_status = 0;
|
|
|
|
opll->eg_counter = 0;
|
|
|
|
|
|
|
|
reset_rate_conversion_params(opll);
|
|
|
|
|
|
|
|
for (i = 0; i < 18; i++)
|
|
|
|
reset_slot(&opll->slot[i], i);
|
|
|
|
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
set_patch(opll, i, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 0x40; i++)
|
|
|
|
OPLL_writeReg(opll, i, 0);
|
|
|
|
|
|
|
|
for (i = 0; i < 15; i++) {
|
|
|
|
opll->pan[i] = 3;
|
|
|
|
opll->pan_fine[i][1] = opll->pan_fine[i][0] = 1.0f;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 14; i++) {
|
|
|
|
opll->ch_out[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_forceRefresh(OPLL *opll) {
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (opll == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
set_patch(opll, i, opll->patch_number[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 18; i++) {
|
|
|
|
request_update(&opll->slot[i], UPDATE_ALL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_setRate(OPLL *opll, uint32_t rate) {
|
|
|
|
opll->rate = rate;
|
|
|
|
reset_rate_conversion_params(opll);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_setQuality(OPLL *opll, uint8_t q) {}
|
|
|
|
|
|
|
|
void OPLL_setChipType(OPLL *opll, uint8_t type) { opll->chip_type = type; }
|
|
|
|
|
|
|
|
void OPLL_writeReg(OPLL *opll, uint32_t reg, uint8_t data) {
|
|
|
|
int ch, i;
|
|
|
|
|
|
|
|
if (reg >= 0x40)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* mirror registers */
|
|
|
|
if ((0x19 <= reg && reg <= 0x1f) || (0x29 <= reg && reg <= 0x2f) || (0x39 <= reg && reg <= 0x3f)) {
|
|
|
|
reg -= 9;
|
|
|
|
}
|
|
|
|
|
2024-04-08 04:06:53 +00:00
|
|
|
// is the compiler stupid or what?
|
|
|
|
// -Wstringop-overflow seems to be the buggiest thing known to humanity...
|
|
|
|
if (reg<0x40) {
|
|
|
|
opll->reg[reg] = (uint8_t)data;
|
|
|
|
}
|
2024-04-03 21:22:51 +00:00
|
|
|
|
|
|
|
switch (reg) {
|
|
|
|
case 0x00:
|
|
|
|
opll->patch[0].AM = (data >> 7) & 1;
|
|
|
|
opll->patch[0].PM = (data >> 6) & 1;
|
|
|
|
opll->patch[0].EG = (data >> 5) & 1;
|
|
|
|
opll->patch[0].KR = (data >> 4) & 1;
|
|
|
|
opll->patch[0].ML = (data)&15;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
if (opll->patch_number[i] == 0) {
|
|
|
|
request_update(MOD(opll, i), UPDATE_RKS | UPDATE_EG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x01:
|
|
|
|
opll->patch[1].AM = (data >> 7) & 1;
|
|
|
|
opll->patch[1].PM = (data >> 6) & 1;
|
|
|
|
opll->patch[1].EG = (data >> 5) & 1;
|
|
|
|
opll->patch[1].KR = (data >> 4) & 1;
|
|
|
|
opll->patch[1].ML = (data)&15;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
if (opll->patch_number[i] == 0) {
|
|
|
|
request_update(CAR(opll, i), UPDATE_RKS | UPDATE_EG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x02:
|
|
|
|
opll->patch[0].KL = (data >> 6) & 3;
|
|
|
|
opll->patch[0].TL = (data)&63;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
if (opll->patch_number[i] == 0) {
|
|
|
|
request_update(MOD(opll, i), UPDATE_TLL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x03:
|
|
|
|
opll->patch[1].KL = (data >> 6) & 3;
|
|
|
|
opll->patch[1].WS = (data >> 4) & 1;
|
|
|
|
opll->patch[0].WS = (data >> 3) & 1;
|
|
|
|
opll->patch[0].FB = (data)&7;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
if (opll->patch_number[i] == 0) {
|
|
|
|
request_update(MOD(opll, i), UPDATE_WS);
|
|
|
|
request_update(CAR(opll, i), UPDATE_WS | UPDATE_TLL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x04:
|
|
|
|
opll->patch[0].AR = (data >> 4) & 15;
|
|
|
|
opll->patch[0].DR = (data)&15;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
if (opll->patch_number[i] == 0) {
|
|
|
|
request_update(MOD(opll, i), UPDATE_EG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x05:
|
|
|
|
opll->patch[1].AR = (data >> 4) & 15;
|
|
|
|
opll->patch[1].DR = (data)&15;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
if (opll->patch_number[i] == 0) {
|
|
|
|
request_update(CAR(opll, i), UPDATE_EG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x06:
|
|
|
|
opll->patch[0].SL = (data >> 4) & 15;
|
|
|
|
opll->patch[0].RR = (data)&15;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
if (opll->patch_number[i] == 0) {
|
|
|
|
request_update(MOD(opll, i), UPDATE_EG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x07:
|
|
|
|
opll->patch[1].SL = (data >> 4) & 15;
|
|
|
|
opll->patch[1].RR = (data)&15;
|
|
|
|
for (i = 0; i < 9; i++) {
|
|
|
|
if (opll->patch_number[i] == 0) {
|
|
|
|
request_update(CAR(opll, i), UPDATE_EG);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0e:
|
|
|
|
if (opll->chip_type == 1)
|
|
|
|
break;
|
|
|
|
update_rhythm_mode(opll);
|
|
|
|
update_key_status(opll);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x0f:
|
|
|
|
opll->test_flag = data;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x10:
|
|
|
|
case 0x11:
|
|
|
|
case 0x12:
|
|
|
|
case 0x13:
|
|
|
|
case 0x14:
|
|
|
|
case 0x15:
|
|
|
|
case 0x16:
|
|
|
|
case 0x17:
|
|
|
|
case 0x18:
|
|
|
|
ch = reg - 0x10;
|
|
|
|
set_fnumber(opll, ch, data + ((opll->reg[0x20 + ch] & 1) << 8));
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x20:
|
|
|
|
case 0x21:
|
|
|
|
case 0x22:
|
|
|
|
case 0x23:
|
|
|
|
case 0x24:
|
|
|
|
case 0x25:
|
|
|
|
case 0x26:
|
|
|
|
case 0x27:
|
|
|
|
case 0x28:
|
|
|
|
ch = reg - 0x20;
|
|
|
|
set_fnumber(opll, ch, ((data & 1) << 8) + opll->reg[0x10 + ch]);
|
|
|
|
set_block(opll, ch, (data >> 1) & 7);
|
|
|
|
set_sus_flag(opll, ch, (data >> 5) & 1);
|
|
|
|
update_key_status(opll);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x30:
|
|
|
|
case 0x31:
|
|
|
|
case 0x32:
|
|
|
|
case 0x33:
|
|
|
|
case 0x34:
|
|
|
|
case 0x35:
|
|
|
|
case 0x36:
|
|
|
|
case 0x37:
|
|
|
|
case 0x38:
|
|
|
|
if ((opll->reg[0x0e] & 32) && (reg >= 0x36)) {
|
|
|
|
switch (reg) {
|
|
|
|
case 0x37:
|
|
|
|
set_slot_volume(MOD(opll, 7), ((data >> 4) & 15) << 2);
|
|
|
|
break;
|
|
|
|
case 0x38:
|
|
|
|
set_slot_volume(MOD(opll, 8), ((data >> 4) & 15) << 2);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
set_patch(opll, reg - 0x30, (data >> 4) & 15);
|
|
|
|
}
|
|
|
|
set_volume(opll, reg - 0x30, (data & 15) << 2);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_writeIO(OPLL *opll, uint32_t adr, uint8_t val) {
|
|
|
|
if (adr & 1)
|
|
|
|
OPLL_writeReg(opll, opll->adr, val);
|
|
|
|
else
|
|
|
|
opll->adr = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_setPan(OPLL *opll, uint32_t ch, uint8_t pan) { opll->pan[ch & 15] = pan; }
|
|
|
|
|
|
|
|
void OPLL_setPanFine(OPLL *opll, uint32_t ch, float pan[2]) {
|
|
|
|
opll->pan_fine[ch & 15][0] = pan[0];
|
|
|
|
opll->pan_fine[ch & 15][1] = pan[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_dumpToPatch(const uint8_t *dump, OPLL_PATCH *patch) {
|
|
|
|
patch[0].AM = (dump[0] >> 7) & 1;
|
|
|
|
patch[1].AM = (dump[1] >> 7) & 1;
|
|
|
|
patch[0].PM = (dump[0] >> 6) & 1;
|
|
|
|
patch[1].PM = (dump[1] >> 6) & 1;
|
|
|
|
patch[0].EG = (dump[0] >> 5) & 1;
|
|
|
|
patch[1].EG = (dump[1] >> 5) & 1;
|
|
|
|
patch[0].KR = (dump[0] >> 4) & 1;
|
|
|
|
patch[1].KR = (dump[1] >> 4) & 1;
|
|
|
|
patch[0].ML = (dump[0]) & 15;
|
|
|
|
patch[1].ML = (dump[1]) & 15;
|
|
|
|
patch[0].KL = (dump[2] >> 6) & 3;
|
|
|
|
patch[1].KL = (dump[3] >> 6) & 3;
|
|
|
|
patch[0].TL = (dump[2]) & 63;
|
|
|
|
patch[1].TL = 0;
|
|
|
|
patch[0].FB = (dump[3]) & 7;
|
|
|
|
patch[1].FB = 0;
|
|
|
|
patch[0].WS = (dump[3] >> 3) & 1;
|
|
|
|
patch[1].WS = (dump[3] >> 4) & 1;
|
|
|
|
patch[0].AR = (dump[4] >> 4) & 15;
|
|
|
|
patch[1].AR = (dump[5] >> 4) & 15;
|
|
|
|
patch[0].DR = (dump[4]) & 15;
|
|
|
|
patch[1].DR = (dump[5]) & 15;
|
|
|
|
patch[0].SL = (dump[6] >> 4) & 15;
|
|
|
|
patch[1].SL = (dump[7] >> 4) & 15;
|
|
|
|
patch[0].RR = (dump[6]) & 15;
|
|
|
|
patch[1].RR = (dump[7]) & 15;
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_getDefaultPatch(int32_t type, int32_t num, OPLL_PATCH *patch) {
|
|
|
|
OPLL_dumpToPatch(default_inst[type] + num * 8, patch);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_setPatch(OPLL *opll, const uint8_t *dump) {
|
|
|
|
OPLL_PATCH patch[2];
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 19; i++) {
|
|
|
|
OPLL_dumpToPatch(dump + i * 8, patch);
|
|
|
|
memcpy(&opll->patch[i * 2 + 0], &patch[0], sizeof(OPLL_PATCH));
|
|
|
|
memcpy(&opll->patch[i * 2 + 1], &patch[1], sizeof(OPLL_PATCH));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_patchToDump(const OPLL_PATCH *patch, uint8_t *dump) {
|
|
|
|
dump[0] = (uint8_t)((patch[0].AM << 7) + (patch[0].PM << 6) + (patch[0].EG << 5) + (patch[0].KR << 4) + patch[0].ML);
|
|
|
|
dump[1] = (uint8_t)((patch[1].AM << 7) + (patch[1].PM << 6) + (patch[1].EG << 5) + (patch[1].KR << 4) + patch[1].ML);
|
|
|
|
dump[2] = (uint8_t)((patch[0].KL << 6) + patch[0].TL);
|
|
|
|
dump[3] = (uint8_t)((patch[1].KL << 6) + (patch[1].WS << 4) + (patch[0].WS << 3) + patch[0].FB);
|
|
|
|
dump[4] = (uint8_t)((patch[0].AR << 4) + patch[0].DR);
|
|
|
|
dump[5] = (uint8_t)((patch[1].AR << 4) + patch[1].DR);
|
|
|
|
dump[6] = (uint8_t)((patch[0].SL << 4) + patch[0].RR);
|
|
|
|
dump[7] = (uint8_t)((patch[1].SL << 4) + patch[1].RR);
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_copyPatch(OPLL *opll, int32_t num, OPLL_PATCH *patch) {
|
|
|
|
memcpy(&opll->patch[num], patch, sizeof(OPLL_PATCH));
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_resetPatch(OPLL *opll, uint8_t type) {
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 19 * 2; i++)
|
|
|
|
OPLL_copyPatch(opll, i, &default_patch[type % OPLL_TONE_NUM][i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
int16_t OPLL_calc(OPLL *opll) {
|
|
|
|
while (opll->out_step > opll->out_time) {
|
|
|
|
opll->out_time += opll->inp_step;
|
|
|
|
update_output(opll);
|
|
|
|
mix_output(opll);
|
|
|
|
}
|
|
|
|
opll->out_time -= opll->out_step;
|
|
|
|
if (opll->conv) {
|
|
|
|
opll->mix_out[0] = OPLL_RateConv_getData(opll->conv, 0);
|
|
|
|
}
|
|
|
|
return opll->mix_out[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
void OPLL_calcStereo(OPLL *opll, int32_t out[2]) {
|
|
|
|
while (opll->out_step > opll->out_time) {
|
|
|
|
opll->out_time += opll->inp_step;
|
|
|
|
update_output(opll);
|
|
|
|
mix_output_stereo(opll);
|
|
|
|
}
|
|
|
|
opll->out_time -= opll->out_step;
|
|
|
|
if (opll->conv) {
|
|
|
|
out[0] = OPLL_RateConv_getData(opll->conv, 0);
|
|
|
|
out[1] = OPLL_RateConv_getData(opll->conv, 1);
|
|
|
|
} else {
|
|
|
|
out[0] = opll->mix_out[0];
|
|
|
|
out[1] = opll->mix_out[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t OPLL_setMask(OPLL *opll, uint32_t mask) {
|
|
|
|
uint32_t ret;
|
|
|
|
|
|
|
|
if (opll) {
|
|
|
|
ret = opll->mask;
|
|
|
|
opll->mask = mask;
|
|
|
|
return ret;
|
|
|
|
} else
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t OPLL_toggleMask(OPLL *opll, uint32_t mask) {
|
|
|
|
uint32_t ret;
|
|
|
|
|
|
|
|
if (opll) {
|
|
|
|
ret = opll->mask;
|
|
|
|
opll->mask ^= mask;
|
|
|
|
return ret;
|
|
|
|
} else
|
|
|
|
return 0;
|
|
|
|
}
|