diff --git a/CMakeLists.txt b/CMakeLists.txt index 58743bd48..49a4e22bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -498,6 +498,7 @@ extern/YMF262-LLE/fmopl3.c extern/YMF276-LLE/fmopn2.c extern/ESFMu/esfm.c extern/ESFMu/esfm_registers.c +extern/emu2413/emu2413.c extern/pwrnoise/pwrnoise.c @@ -1132,4 +1133,4 @@ if (NOT ANDROID OR TERMUX) include(CPack) endif() -target_compile_definitions(furnace PRIVATE ${DEPENDENCIES_DEFINES}) \ No newline at end of file +target_compile_definitions(furnace PRIVATE ${DEPENDENCIES_DEFINES}) diff --git a/extern/emu2413/.clang-format b/extern/emu2413/.clang-format new file mode 100644 index 000000000..173f8a6a8 --- /dev/null +++ b/extern/emu2413/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM +ColumnLimit: 120 diff --git a/extern/emu2413/.gitignore b/extern/emu2413/.gitignore new file mode 100644 index 000000000..7032896cd --- /dev/null +++ b/extern/emu2413/.gitignore @@ -0,0 +1,60 @@ +# Object files +*.o +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ + +# OSX metadata +.DS_Store + +# CMake objects +CMakeCache.txt +CMakeFiles +CMakeScripts +Makefile +cmake_install.cmake +install_manifest.txt + +# VS Projects +*.vcxproj +*.vcxproj.filters +Project.VC.opendb +Project.sdf +Project.sln + +# VS Build Results +.vs/ +[Dd]ebug/ +[Rr]elease/ +x64/ +x86/ +Win32/ +*.dir/ +build/ \ No newline at end of file diff --git a/extern/emu2413/CHANGELOG.md b/extern/emu2413/CHANGELOG.md new file mode 100644 index 000000000..425a48e84 --- /dev/null +++ b/extern/emu2413/CHANGELOG.md @@ -0,0 +1,130 @@ +# v1.5.9 (2022-09-21) +- Fix the envelope threshold for DAMP to ATTACK state transition (Issue #12). + +# v1.5.7 (2022-09-14) +- Silence some pedantic warnings. +- Update minimum cmake version to 3.0. +- Fix the problem where min/max function conflict with the Visual C++ macros. + +# v1.5.6 (2021-02-28) +- Update YMF281 ROM patches. + +# v1.5.5 (2021-02-05) +- Fix the problem where the output sound is broken due to the mixing of integer and floating point types in the process of rate conversion calculation (degraded at v1.5.4). + +# v1.5.4 (2021 02-04) +- Fix the problem where the internal sample rate is calculated as int instead of double. +- Replace older "OPLL_dump2patch" to "OPLL_dumpToPatch". + +# v1.5.3 (2021 01-31) +- Change min/max macros to inline functions to suppress compiler errors/warnings. + +# v1.5.2 (2020 03-04) +- Fix unused constants and variables. +- Fix comments. + +# v1.5.1 (2020 02-18) +- Fix piano attack rate. + +# v1.5.0 (2020 02-12) +- Fix the modulator decay rate of the acoustic bass patch. +- Fix the modulator's key-off release rate. +- Do not reset carrier's phase when modulator DP finishes. +- Remove deferred rhythm mode switching. +- Improve white noise emulation. + +# v1.4.0 (2020 02-08) +- Refactor API and internals. +- Add OPLL_setChipType. OPLL_setChipMode is deprecated. + +# v1.3.0 (2020 02-03) +- Add fine-grained panning (OPLL_setPanFine). + +# v1.2.7 (2020 01-12) +- Reactivate output array of carrier slot for backward compatibility. + +# v1.2.6 (2020 01-11) +- Fix [timing of envelope damping](https://github.com/digital-sound-antiques/emu2413/wiki/Envelope-Damp-and-KeyOn-Noise). + +# v1.2.4 (2020 01-07) +- Fix top-cym and hi-hat calculation. + +# v1.2.3 (2020 01-07) +- Remove modulator phase delay. + +# v1.2.2 (2020 01-06) +- Fix envelope behavior if ARx4+Rks >= 60 is set during attack phase. +- Tweak ROM voice parameters. +- Refactor envelope generator. + +# v1.2.0 (2020 01-05) +- Support mirror registers: 0x19-1f, 0x29-1f and 0x39-3f. +- Fix feedback model. + +# v1.1.0 (2020 01-03) +Major Update: playback quality and emulation accuracy have been improved drastically. + +- Improve [ROM instruments](https://github.com/digital-sound-antiques/emu2413/wiki/YM2413-Estimated-ROM-Instruments). +- Change dB-based sine and exp tables to log2-based. +- Improve damper rate when key-on. +- Improve pitch and amplitude modulator. +- Improve envelope generator. +- Fix the problem where key-on flags are not shared between rhythm and melody slots. +- Improve internal [sample rate converter](https://github.com/digital-sound-antiques/emu2413/wiki/Sample-Rate-Converter). +- Implement test register. + - Both [test mode](https://github.com/digital-sound-antiques/emu2413/wiki/DAC-in-test-mode) and [non-test mode](https://github.com/digital-sound-antiques/emu2413/wiki/Use-FM-channel-as-DAC) DAC patterns are supported. + - There are still very few VGMs using YM2413 DAC on the web. If you would like to test it, try [vgm-conv](https://github.com/digital-sound-antiques/vgm-conv) which is capable to generate DAC stream from YM2612 VGM files. +- Semantic versioning. +- Support VS2010 again. + +# v0.74 (2019 10-24) + +- Fix broken AM and PM waves. + +# v0.73 (2019 10-22) + +- Fix top-cym volume. + +# v0.72 (2019 10-21) + +- Fix critical bug on force damp routine. +- Fix top-cym, hi-hat waveform and white noise freq. + +# v0.71 (2019 10-20) + +- Fix too strong LPF on rate conversion. +- Improve shape of envelope in attack phase. + +# v0.70 (2019 10-13) + +- Force to damp before keyon +- Dump size changed from to 8 bytes per voice. +- Replaced snare, hi-hat, top-cym generator. + +# v0.65 (2019 05-24) + +- Fix YM2413 and VRC7 patches. + +# v0.63 (2016 09-06) + +- Support per-channel output. + +# v0.62 (2015 12-13) + +- Changed own integer types to C99 stdint.h types. + +# v0.61 (2004 04-10) + +- Added YMF281B tone (defined by Chabin). + +# v0.30 (2001 01-16) + +- 1st beta release. + +# v0.20 (2001 01-15) + +- 1st alpha release. + +# v0.10 (2001 01-08) + +- 1st experimental version. diff --git a/extern/emu2413/CMakeLists.txt b/extern/emu2413/CMakeLists.txt new file mode 100644 index 000000000..83e154ac1 --- /dev/null +++ b/extern/emu2413/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.0) +project(emu2413) + +if(MSVC) + set(CMAKE_C_FLAGS "/Ox /W3 /wd4996") +else() + set(CMAKE_C_FLAGS "-O3 -Wall") +endif() + +add_library(emu2413 STATIC emu2413.c) diff --git a/extern/emu2413/LICENSE b/extern/emu2413/LICENSE new file mode 100644 index 000000000..df4ae1cbb --- /dev/null +++ b/extern/emu2413/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2001-2019 Mitsutaka Okazaki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/extern/emu2413/README.md b/extern/emu2413/README.md new file mode 100644 index 000000000..7abfc3b03 --- /dev/null +++ b/extern/emu2413/README.md @@ -0,0 +1,14 @@ +# emu2413 + +A YM2413 emulator written in C. + +Audio demos are available at [MSXplay.com](https://msxplay.com) + +# Acknowledgements +emu2413 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 +- [plgDavid's YMF281 ROM patches](https://github.com/plgDavid/misc/wiki/Copyright-free-OPLL(x)-ROM-patches) diff --git a/extern/emu2413/emu2413.c b/extern/emu2413/emu2413.c new file mode 100644 index 000000000..7b50a6701 --- /dev/null +++ b/extern/emu2413/emu2413.c @@ -0,0 +1,1505 @@ +/** + * 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 +#include +#include +#include + +#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 + +#define OPLL_TONE_NUM 3 +/* 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) +}}; +/* 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; + } + + opll->reg[reg] = (uint8_t)data; + + 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; +} diff --git a/extern/emu2413/emu2413.h b/extern/emu2413/emu2413.h new file mode 100644 index 000000000..dbc0af1d9 --- /dev/null +++ b/extern/emu2413/emu2413.h @@ -0,0 +1,237 @@ +#ifndef _EMU2413_H_ +#define _EMU2413_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define OPLL_DEBUG 0 + +enum OPLL_TONE_ENUM { OPLL_2413_TONE = 0, OPLL_VRC7_TONE = 1, OPLL_281B_TONE = 2 }; + +/* voice data */ +typedef struct __OPLL_PATCH { + uint32_t TL, FB, EG, ML, AR, DR, SL, RR, KR, KL, AM, PM, WS; +} OPLL_PATCH; + +/* slot */ +typedef struct __OPLL_SLOT { + uint8_t number; + + /* type flags: + * 000000SM + * |+-- M: 0:modulator 1:carrier + * +--- S: 0:normal 1:single slot mode (sd, tom, hh or cym) + */ + uint8_t type; + + OPLL_PATCH *patch; /* voice parameter */ + + /* slot output */ + int32_t output[2]; /* output value, latest and previous. */ + + /* phase generator (pg) */ + uint16_t *wave_table; /* wave table */ + uint32_t pg_phase; /* pg phase */ + uint32_t pg_out; /* pg output, as index of wave table */ + uint8_t pg_keep; /* if 1, pg_phase is preserved when key-on */ + uint16_t blk_fnum; /* (block << 9) | f-number */ + uint16_t fnum; /* f-number (9 bits) */ + uint8_t blk; /* block (3 bits) */ + + /* envelope generator (eg) */ + uint8_t eg_state; /* current state */ + int32_t volume; /* current volume */ + uint8_t key_flag; /* key-on flag 1:on 0:off */ + uint8_t sus_flag; /* key-sus option 1:on 0:off */ + uint16_t tll; /* total level + key scale level*/ + uint8_t rks; /* key scale offset (rks) for eg speed */ + uint8_t eg_rate_h; /* eg speed rate high 4bits */ + uint8_t eg_rate_l; /* eg speed rate low 2bits */ + uint32_t eg_shift; /* shift for eg global counter, controls envelope speed */ + uint32_t eg_out; /* eg output */ + + uint32_t update_requests; /* flags to debounce update */ + +#if OPLL_DEBUG + uint8_t last_eg_state; +#endif +} OPLL_SLOT; + +/* mask */ +#define OPLL_MASK_CH(x) (1 << (x)) +#define OPLL_MASK_HH (1 << (9)) +#define OPLL_MASK_CYM (1 << (10)) +#define OPLL_MASK_TOM (1 << (11)) +#define OPLL_MASK_SD (1 << (12)) +#define OPLL_MASK_BD (1 << (13)) +#define OPLL_MASK_RHYTHM (OPLL_MASK_HH | OPLL_MASK_CYM | OPLL_MASK_TOM | OPLL_MASK_SD | OPLL_MASK_BD) + +/* rate conveter */ +typedef struct __OPLL_RateConv { + int ch; + double timer; + double f_ratio; + int16_t *sinc_table; + int16_t **buf; +} OPLL_RateConv; + +OPLL_RateConv *OPLL_RateConv_new(double f_inp, double f_out, int ch); +void OPLL_RateConv_reset(OPLL_RateConv *conv); +void OPLL_RateConv_putData(OPLL_RateConv *conv, int ch, int16_t data); +int16_t OPLL_RateConv_getData(OPLL_RateConv *conv, int ch); +void OPLL_RateConv_delete(OPLL_RateConv *conv); + +typedef struct __OPLL { + uint32_t clk; + uint32_t rate; + + uint8_t chip_type; + + uint32_t adr; + + double inp_step; + double out_step; + double out_time; + + uint8_t reg[0x40]; + uint8_t test_flag; + uint32_t slot_key_status; + uint8_t rhythm_mode; + + uint32_t eg_counter; + + uint32_t pm_phase; + int32_t am_phase; + + uint8_t lfo_am; + + uint32_t noise; + uint8_t short_noise; + + int32_t patch_number[9]; + OPLL_SLOT slot[18]; + OPLL_PATCH patch[19 * 2]; + + uint8_t pan[16]; + float pan_fine[16][2]; + + uint32_t mask; + + /* channel output */ + /* 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym */ + int16_t ch_out[14]; + + int16_t mix_out[2]; + + OPLL_RateConv *conv; +} OPLL; + +OPLL *OPLL_new(uint32_t clk, uint32_t rate); +void OPLL_delete(OPLL *); + +void OPLL_reset(OPLL *); +void OPLL_resetPatch(OPLL *, uint8_t); + +/** + * Set output wave sampling rate. + * @param rate sampling rate. If clock / 72 (typically 49716 or 49715 at 3.58MHz) is set, the internal rate converter is + * disabled. + */ +void OPLL_setRate(OPLL *opll, uint32_t rate); + +/** + * Set internal calcuration quality. Currently no effects, just for compatibility. + * >= v1.0.0 always synthesizes internal output at clock/72 Hz. + */ +void OPLL_setQuality(OPLL *opll, uint8_t q); + +/** + * Set pan pot (extra function - not YM2413 chip feature) + * @param ch 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym 14,15:reserved + * @param pan 0:mute 1:right 2:left 3:center + * ``` + * pan: 76543210 + * |+- bit 1: enable Left output + * +-- bit 0: enable Right output + * ``` + */ +void OPLL_setPan(OPLL *opll, uint32_t ch, uint8_t pan); + +/** + * Set fine-grained panning + * @param ch 0..8:tone 9:bd 10:hh 11:sd 12:tom 13:cym 14,15:reserved + * @param pan output strength of left/right channel. + * pan[0]: left, pan[1]: right. pan[0]=pan[1]=1.0f for center. + */ +void OPLL_setPanFine(OPLL *opll, uint32_t ch, float pan[2]); + +/** + * Set chip type. If vrc7 is selected, r#14 is ignored. + * This method not change the current ROM patch set. + * To change ROM patch set, use OPLL_resetPatch. + * @param type 0:YM2413 1:VRC7 + */ +void OPLL_setChipType(OPLL *opll, uint8_t type); + +void OPLL_writeIO(OPLL *opll, uint32_t reg, uint8_t val); +void OPLL_writeReg(OPLL *opll, uint32_t reg, uint8_t val); + +/** + * Calculate one sample + */ +int16_t OPLL_calc(OPLL *opll); + +/** + * Calulate stereo sample + */ +void OPLL_calcStereo(OPLL *opll, int32_t out[2]); + +void OPLL_setPatch(OPLL *, const uint8_t *dump); +void OPLL_copyPatch(OPLL *, int32_t, OPLL_PATCH *); + +/** + * Force to refresh. + * External program should call this function after updating patch parameters. + */ +void OPLL_forceRefresh(OPLL *); + +void OPLL_dumpToPatch(const uint8_t *dump, OPLL_PATCH *patch); +void OPLL_patchToDump(const OPLL_PATCH *patch, uint8_t *dump); +void OPLL_getDefaultPatch(int32_t type, int32_t num, OPLL_PATCH *); + +/** + * Set channel mask + * @param mask mask flag: OPLL_MASK_* can be used. + * - bit 0..8: mask for ch 1 to 9 (OPLL_MASK_CH(i)) + * - bit 9: mask for Hi-Hat (OPLL_MASK_HH) + * - bit 10: mask for Top-Cym (OPLL_MASK_CYM) + * - bit 11: mask for Tom (OPLL_MASK_TOM) + * - bit 12: mask for Snare Drum (OPLL_MASK_SD) + * - bit 13: mask for Bass Drum (OPLL_MASK_BD) + */ +uint32_t OPLL_setMask(OPLL *, uint32_t mask); + +/** + * Toggler channel mask flag + */ +uint32_t OPLL_toggleMask(OPLL *, uint32_t mask); + +/* for compatibility */ +#define OPLL_set_rate OPLL_setRate +#define OPLL_set_quality OPLL_setQuality +#define OPLL_set_pan OPLL_setPan +#define OPLL_set_pan_fine OPLL_setPanFine +#define OPLL_calc_stereo OPLL_calcStereo +#define OPLL_reset_patch OPLL_resetPatch +#define OPLL_dump2patch OPLL_dumpToPatch +#define OPLL_patch2dump OPLL_patchToDump +#define OPLL_setChipMode OPLL_setChipType + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/extern/emu2413/sample2413.c b/extern/emu2413/sample2413.c new file mode 100644 index 000000000..dd23bb847 --- /dev/null +++ b/extern/emu2413/sample2413.c @@ -0,0 +1,108 @@ +/*============================================================ + + Test code for emu2413.c + + Write 2 seconds of the piano tone into temp.wav + + gcc -Wall -lm sample2413.c emu2413.c + (The author had tried to compile on Solaris7 with gcc 2.8.1) + +=============================================================*/ +#include "emu2413.h" +#include +#include + +/* + * Standard clock = MSX clock + */ +#define MSX_CLK 3579545 + +#define SAMPLERATE 44100 +#define DATALENGTH (SAMPLERATE * 8) + +static void WORD(char *buf, uint32_t data) { + + buf[0] = data & 0xff; + buf[1] = (data & 0xff00) >> 8; +} + +static void DWORD(char *buf, uint32_t data) { + + buf[0] = data & 0xff; + buf[1] = (data & 0xff00) >> 8; + buf[2] = (data & 0xff0000) >> 16; + buf[3] = (data & 0xff000000) >> 24; +} + +static void chunkID(char *buf, char id[4]) { + + buf[0] = id[0]; + buf[1] = id[1]; + buf[2] = id[2]; + buf[3] = id[3]; +} + +int main(void) { + + static char wave[DATALENGTH * 2]; + char filename[16] = "temp.wav"; + char header[46]; + int i; + clock_t start, finish; + + FILE *fp; + OPLL *opll; + + /* + * Create WAVE header + */ + chunkID(header, "RIFF"); + DWORD(header + 4, DATALENGTH * 2 + 36); + chunkID(header + 8, "WAVE"); + chunkID(header + 12, "fmt "); + DWORD(header + 16, 16); + WORD(header + 20, 1); /* WAVE_FORMAT_PCM */ + WORD(header + 22, 1); /* channel 1=mono,2=stereo */ + DWORD(header + 24, SAMPLERATE); /* samplesPerSec */ + DWORD(header + 28, 2 * SAMPLERATE); /* bytesPerSec */ + WORD(header + 32, 2); /* blockSize */ + WORD(header + 34, 16); /* bitsPerSample */ + chunkID(header + 36, "data"); + DWORD(header + 40, 2 * DATALENGTH); + + opll = OPLL_new(MSX_CLK, SAMPLERATE); + OPLL_reset(opll); + OPLL_writeReg(opll, 0x30, 0x30); /* select PIANO Voice to ch1. */ + OPLL_writeReg(opll, 0x10, 0x80); /* set F-Number(L). */ + OPLL_writeReg(opll, 0x20, 0x15); /* set BLK & F-Number(H) and + * keyon. */ + + start = clock(); + + i = 0; + + for (i = 0; i < DATALENGTH; i++) { + WORD(wave + i * 2, OPLL_calc(opll)); + } + + finish = clock(); + OPLL_delete(opll); + + printf("It has been %f sec to calc %d waves.\n", (double)(finish - start) / CLOCKS_PER_SEC, DATALENGTH); + printf("%f times faster than real YM2413.\n", + ((double)DATALENGTH / SAMPLERATE) / ((double)(finish - start) / CLOCKS_PER_SEC)); + + fp = fopen(filename, "wb"); + + if (fp == NULL) + return 1; + + fwrite(header, 46, 1, fp); + fwrite(wave, DATALENGTH, 2, fp); + + fclose(fp); + + printf("Wrote : %s\n", filename); + + return 0; +} diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0a88e5407..e2d9dd546 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -428,6 +428,11 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: dispatch=new DivPlatformOPLL; + if (isRender) { + ((DivPlatformOPLL*)dispatch)->setCore(eng->getConfInt("opllCoreRender",0)); + } else { + ((DivPlatformOPLL*)dispatch)->setCore(eng->getConfInt("opllCore",0)); + } ((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7); ((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS); break; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index d1195b9e4..2674ed6d6 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -93,8 +93,18 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) { void DivPlatformOPLL::acquire_ymfm(short** buf, size_t len) { } +void DivPlatformOPLL::acquire_emu(short** buf, size_t len) { +} + void DivPlatformOPLL::acquire(short** buf, size_t len) { - acquire_nuked(buf,len); + switch (selCore) { + case 0: + acquire_nuked(buf,len); + break; + case 1: + acquire_emu(buf,len); + break; + } } void DivPlatformOPLL::tick(bool sysTick) { @@ -1073,8 +1083,8 @@ int DivPlatformOPLL::getPortaFloor(int ch) { return (ch>5)?12:0; } -void DivPlatformOPLL::setYMFM(bool use) { - useYMFM=use; +void DivPlatformOPLL::setCore(unsigned char which) { + selCore=which; } float DivPlatformOPLL::getPostAmp() { diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index 53e5a2aa0..efa75fa51 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -26,6 +26,7 @@ extern "C" { #include "../../../extern/Nuked-OPLL/opll.h" } +#include "../../../extern/emu2413/emu2413.h" class DivPlatformOPLL: public DivDispatch { protected: @@ -55,6 +56,7 @@ class DivPlatformOPLL: public DivDispatch { }; FixedQueue writes; opll_t fm; + OPLL* fm_emu; int delay, lastCustomMemory; unsigned char lastBusy; unsigned char drumState; @@ -68,7 +70,7 @@ class DivPlatformOPLL: public DivDispatch { unsigned char regPool[256]; - bool useYMFM; + unsigned char selCore; bool crapDrums; bool properDrums, properDrumsSys, noTopHatFreq, fixedAll; bool vrc7; @@ -88,6 +90,7 @@ class DivPlatformOPLL: public DivDispatch { void acquire_nuked(short** buf, size_t len); void acquire_ymfm(short** buf, size_t len); + void acquire_emu(short** buf, size_t len); public: void acquire(short** buf, size_t len); @@ -102,7 +105,7 @@ class DivPlatformOPLL: public DivDispatch { void forceIns(); void tick(bool sysTick=true); void muteChannel(int ch, bool mute); - void setYMFM(bool use); + void setCore(unsigned char which); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); bool getLegacyAlwaysSetVolume(); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 211a73caf..46465ead7 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -88,12 +88,10 @@ void DivPlatformPCE::acquire(short** buf, size_t len) { } // PCE part - cycles=0; - while (!writes.empty() && cycles<24) { + while (!writes.empty()) { QueuedWrite w=writes.front(); - pce->Write(cycles,w.addr,w.val); + pce->Write(0,w.addr,w.val); regPool[w.addr&0x0f]=w.val; - //cycles+=2; writes.pop(); } memset(tempL,0,24*sizeof(int)); @@ -585,7 +583,6 @@ void DivPlatformPCE::reset() { lastPan=0xff; memset(tempL,0,32*sizeof(int)); memset(tempR,0,32*sizeof(int)); - cycles=0; curChan=-1; sampleBank=0; lfoMode=0; @@ -599,7 +596,6 @@ void DivPlatformPCE::reset() { for (int i=0; i<6; i++) { chWrite(i,0x05,isMuted[i]?0:chan[i].pan); } - delay=500; } int DivPlatformPCE::getOutputCount() { diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 4d434e9cc..967bcac5b 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -68,7 +68,7 @@ class DivPlatformPCE: public DivDispatch { FixedQueue writes; unsigned char lastPan; - int cycles, curChan, delay; + int curChan; int tempL[32]; int tempR[32]; unsigned char sampleBank, lfoMode, lfoSpeed; diff --git a/src/gui/gui.h b/src/gui/gui.h index 4248fa5a7..7d4788abc 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1628,6 +1628,18 @@ class FurnaceGUI { int opl2Core; int opl3Core; int esfmCore; + int opllCore; + int bubsysQuality; + int dsidQuality; + int gbQuality; + int ndsQuality; + int pceQuality; + int pnQuality; + int saaQuality; + int sccQuality; + int smQuality; + int swanQuality; + int vbQuality; int arcadeCoreRender; int ym2612CoreRender; int snCoreRender; @@ -1639,6 +1651,18 @@ class FurnaceGUI { int opl2CoreRender; int opl3CoreRender; int esfmCoreRender; + int opllCoreRender; + int bubsysQualityRender; + int dsidQualityRender; + int gbQualityRender; + int ndsQualityRender; + int pceQualityRender; + int pnQualityRender; + int saaQualityRender; + int sccQualityRender; + int smQualityRender; + int swanQualityRender; + int vbQualityRender; int pcSpeakerOutMethod; String yrw801Path; String tg100Path; @@ -1838,6 +1862,7 @@ class FurnaceGUI { opl2Core(0), opl3Core(0), esfmCore(0), + opllCore(0), arcadeCoreRender(1), ym2612CoreRender(0), snCoreRender(0), @@ -1849,6 +1874,7 @@ class FurnaceGUI { opl2CoreRender(0), opl3CoreRender(0), esfmCoreRender(0), + opllCoreRender(0), pcSpeakerOutMethod(0), yrw801Path(""), tg100Path(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 3c0018fea..4476c07fd 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -174,6 +174,20 @@ const char* esfmCores[]={ "ESFMu (fast)" }; +const char* opllCores[]={ + "Nuked-OPLL", + "emu2413" +}; + +const char* coreQualities[]={ + "Horrible", + "Low", + "Medium", + "High", + "Ultra", + "Ultimate" +}; + const char* pcspkrOutMethods[]={ "evdev SND_TONE", "KIOCSOUND on /dev/tty1", @@ -1663,6 +1677,17 @@ void FurnaceGUI::drawSettings() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::Combo("##ESFMCoreRender",&settings.esfmCoreRender,esfmCores,2)) settingsChanged=true; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::AlignTextToFramePadding(); + ImGui::Text("OPLL"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##OPLLCore",&settings.opllCore,opllCores,2)) settingsChanged=true; + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##OPLLCoreRender",&settings.opllCoreRender,opllCores,2)) settingsChanged=true; + ImGui::EndTable(); } ImGui::Separator(); @@ -4165,6 +4190,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.opl2Core=conf.getInt("opl2Core",0); settings.opl3Core=conf.getInt("opl3Core",0); settings.esfmCore=conf.getInt("esfmCore",0); + settings.opllCore=conf.getInt("opllCore",0); settings.arcadeCoreRender=conf.getInt("arcadeCoreRender",1); settings.ym2612CoreRender=conf.getInt("ym2612CoreRender",0); settings.snCoreRender=conf.getInt("snCoreRender",0); @@ -4176,6 +4202,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { settings.opl2CoreRender=conf.getInt("opl2CoreRender",0); settings.opl3CoreRender=conf.getInt("opl3CoreRender",0); settings.esfmCoreRender=conf.getInt("esfmCoreRender",0); + settings.opllCoreRender=conf.getInt("opllCoreRender",0); settings.pcSpeakerOutMethod=conf.getInt("pcSpeakerOutMethod",0); @@ -4205,6 +4232,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.opl2Core,0,2); clampSetting(settings.opl3Core,0,2); clampSetting(settings.esfmCore,0,1); + clampSetting(settings.opllCore,0,1); clampSetting(settings.arcadeCoreRender,0,1); clampSetting(settings.ym2612CoreRender,0,2); clampSetting(settings.snCoreRender,0,1); @@ -4216,6 +4244,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { clampSetting(settings.opl2CoreRender,0,2); clampSetting(settings.opl3CoreRender,0,2); clampSetting(settings.esfmCoreRender,0,1); + clampSetting(settings.opllCoreRender,0,1); clampSetting(settings.pcSpeakerOutMethod,0,4); clampSetting(settings.mainFont,0,6); clampSetting(settings.patFont,0,6); @@ -4647,6 +4676,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("opl2Core",settings.opl2Core); conf.set("opl3Core",settings.opl3Core); conf.set("esfmCore",settings.esfmCore); + conf.set("opllCore",settings.opllCore); conf.set("arcadeCoreRender",settings.arcadeCoreRender); conf.set("ym2612CoreRender",settings.ym2612CoreRender); conf.set("snCoreRender",settings.snCoreRender); @@ -4658,6 +4688,7 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) { conf.set("opl2CoreRender",settings.opl2CoreRender); conf.set("opl3CoreRender",settings.opl3CoreRender); conf.set("esfmCoreRender",settings.esfmCoreRender); + conf.set("opllCoreRender",settings.opllCoreRender); conf.set("pcSpeakerOutMethod",settings.pcSpeakerOutMethod); @@ -4703,6 +4734,7 @@ void FurnaceGUI::commitSettings() { settings.opl2Core!=e->getConfInt("opl2Core",0) || settings.opl3Core!=e->getConfInt("opl3Core",0) || settings.esfmCore!=e->getConfInt("esfmCore",0) || + settings.opllCore!=e->getConfInt("opllCore",0) || settings.arcadeCoreRender!=e->getConfInt("arcadeCoreRender",0) || settings.ym2612CoreRender!=e->getConfInt("ym2612CoreRender",0) || settings.snCoreRender!=e->getConfInt("snCoreRender",0) || @@ -4714,6 +4746,7 @@ void FurnaceGUI::commitSettings() { settings.opl2CoreRender!=e->getConfInt("opl2CoreRender",0) || settings.opl3CoreRender!=e->getConfInt("opl3CoreRender",0) || settings.esfmCoreRender!=e->getConfInt("esfmCoreRender",0) || + settings.opllCoreRender!=e->getConfInt("opllCoreRender",0) || settings.audioQuality!=e->getConfInt("audioQuality",0) || settings.audioHiPass!=e->getConfInt("audioHiPass",1) );