openMSX
YM2413NukeYKT.hh
Go to the documentation of this file.
1/*
2* Original copyright:
3* -------------------------------------------------------------------
4* Copyright (C) 2019 Nuke.YKT
5*
6* This program is free software; you can redistribute it and/or
7* modify it under the terms of the GNU General Public License
8* as published by the Free Software Foundation; either version 2
9* of the License, or (at your option) any later version.
10*
11* This program is distributed in the hope that it will be useful,
12* but WITHOUT ANY WARRANTY; without even the implied warranty of
13* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14* GNU General Public License for more details.
15*
16*
17* Yamaha YM2413 emulator
18* Thanks:
19* siliconpr0n.org(digshadow, John McMaster):
20* VRC VII decap and die shot.
21*
22* version: 0.9
23* -------------------------------------------------------------------
24*
25* Heavily modified by Wouter Vermaelen for use in openMSX.
26*
27* The most important changes are:
28* - Adapt to the openMSX API (the 'YM2413Core' base-class).
29* - The original NukeYKT code is probably close to how the real hardware is
30* implemented, this is very useful while reverse engineering the YM2413. For
31* example it faithfully implements the YM2413 'pipeline' (how the sound
32* generation suboperations are spread over time). Though, for use in openMSX,
33* we're only interested in the final result. So as long as the generated
34* sound remains 100% identical, we're free to transform the code to a form
35* that's more suited for an efficient software implementation. Of course
36* large parts of this pipeline remain in the transformed code because it does
37* play an important role in for example the exact timing of when register
38* changes take effect. So all transformations have to be tested very
39* carefully.
40* - The current code runs (on average) over 3x faster than the original, while
41* still generating identical output. This was measured/tested on a large set
42* of vgm files.
43* - TODO document better what kind of transformations were done.
44* * Emulate 18-steps at-a-time.
45* * Specialize for the common case that testMode==0 (but still have slower
46* fallback code).
47* * Move sub-operations in the pipeline (e.g. to eliminate temporary state)
48* when this doesn't have an observable effect.
49* * Lots of small tweak.
50* * ...
51*
52* TODO:
53* - In openMSX the YM2413 is often silent for large periods of time (e.g. maybe
54* the emulated MSX program doesn't use the YM2413). Can we easily detect an
55* idle YM2413 and then bypass large parts of the emulation?
56*/
57
58#ifndef YM2413NUKEYKT_HH
59#define YM2413NUKEYKT_HH
60
61#include "YM2413Core.hh"
62#include "inline.hh"
63#include <array>
64#include <span>
65
66namespace openmsx::YM2413NukeYKT {
67
68class YM2413 final : public YM2413Core
69{
70public:
71 YM2413();
72 void reset() override;
73 void writePort(bool port, uint8_t value, int cycle_offset) override;
74 void pokeReg(uint8_t reg, uint8_t value) override;
75 [[nodiscard]] uint8_t peekReg(uint8_t reg) const override;
76 void generateChannels(std::span<float*, 9 + 5> out, uint32_t n) override;
77 [[nodiscard]] float getAmplificationFactor() const override;
78 void setSpeed(double speed) override;
79
80 template<typename Archive>
81 void serialize(Archive& ar, unsigned version);
82
83 enum class EgState : uint8_t {
84 attack,
85 decay,
86 sustain,
87 release,
88 };
89
90private:
91 using bool_2 = std::array<bool, 2>;
92 using int8_t_2 = std::array<int8_t, 2>;
93 using uint8_t_2 = std::array<uint8_t, 2>;
94 struct Patch {
95 constexpr Patch() = default;
96 constexpr Patch(uint8_t tl_, uint8_t dcm_, uint8_t fb_,
97 bool_2 am_, bool_2 vib_, bool_2 et_, bool_2 ksr_, uint8_t_2 multi_,
98 uint8_t_2 ksl_, uint8_t_2 ar_, uint8_t_2 dr_, uint8_t_2 sl_, uint8_t_2 rr_)
99 : dcm(dcm_), vib(vib_), et(et_), sl(sl_) {
100 setTL(tl_);
101 setFB(fb_);
102 setAM(0, am_[0]);
103 setAM(1, am_[1]);
104 setKSR(0, ksr_[0]);
105 setKSR(1, ksr_[1]);
106 setMulti(0, multi_[0]);
107 setMulti(1, multi_[1]);
108 setKSL(0, ksl_[0]);
109 setKSL(1, ksl_[1]);
110 setAR(0, ar_[0]);
111 setAR(1, ar_[1]);
112 setDR(0, dr_[0]);
113 setDR(1, dr_[1]);
114 setRR(0, rr_[0]);
115 setRR(1, rr_[1]);
116 }
117
118 constexpr void setTL(uint8_t tl) { tl2 = 2 * tl; }
119 constexpr void setFB(uint8_t fb) { fb_t = fb ? (8 - fb) : 31; }
120 constexpr void setAM(int i, bool am) { am_t[i] = am ? -1 : 0; }
121 constexpr void setKSR(int i, bool ksr) { ksr_t[i] = ksr ? 0 : 2; }
122 constexpr void setMulti(int i, uint8_t multi) {
123 constexpr std::array<uint8_t, 16> PG_MULTI = {
124 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
125 };
126 multi_t[i] = PG_MULTI[multi];
127 }
128 constexpr void setKSL(int i, uint8_t ksl) { ksl_t[i] = ksl ? (3 - ksl) : 31; }
129 constexpr void setAR (int i, uint8_t ar) { ar4[i] = 4 * ar; }
130 constexpr void setDR (int i, uint8_t dr) { dr4[i] = 4 * dr; }
131 constexpr void setRR (int i, uint8_t rr) { rr4[i] = 4 * rr; }
132
133 uint8_t tl2 = 0; // multiplied by 2
134 uint8_t dcm = 0;
135 uint8_t fb_t = 31; // 0->31, 1->7, 2->6, .., 6->2, 7->1
136 int8_t_2 am_t = {0, 0}; // false->0, true->-1
137 bool_2 vib = {false, false};
138 bool_2 et = {false, false};
139 uint8_t_2 ksr_t = {2, 2}; // 0->2, 1->0
140 uint8_t_2 multi_t = {1, 1}; // transformed via PG_MULTI[]
141 uint8_t_2 ksl_t = {31, 31}; // 0->31, 1->2, 2->1, 3->0
142 uint8_t_2 ar4 = {0, 0}; // multiplied by 4
143 uint8_t_2 dr4 = {0, 0}; // multiplied by 4
144 uint8_t_2 sl = {0, 0};
145 uint8_t_2 rr4 = {0, 0}; // multiplied by 4
146 };
147 struct Locals {
148 Locals(std::span<float*, 9 + 5> out_) : out(out_) {}
149
150 std::span<float*, 9 + 5> out;
151 uint8_t rm_hh_bits;
152 bool use_rm_patches;
153 bool lfo_am_car;
154 bool eg_timer_carry;
155 };
156 struct Write {
157 uint8_t port;
158 uint8_t value;
159
160 template<typename Archive>
161 void serialize(Archive& ar, unsigned version);
162 };
163
164private:
165 template<bool TEST_MODE> NEVER_INLINE void step18(std::span<float*, 9 + 5> out);
166 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void step(Locals& l);
167
168 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE uint32_t phaseCalcIncrement(const Patch& patch1) const;
169 template<uint32_t CYCLES> ALWAYS_INLINE void channelOutput(std::span<float*, 9 + 5> out, int32_t ch_out);
170 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE const Patch& preparePatch1(bool use_rm_patches) const;
171 template<uint32_t CYCLES, bool TEST_MODE> [[nodiscard]] ALWAYS_INLINE uint32_t getPhase(uint8_t& rm_hh_bits);
172 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE bool keyOnEvent() const;
173 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void incrementPhase(uint32_t phase_incr, bool prev_rhythm);
174 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE uint32_t getPhaseMod(uint8_t fb_t);
175 template<uint32_t CYCLES> ALWAYS_INLINE void doOperator(std::span<float*, 9 + 5> out, bool eg_silent);
176 template<uint32_t CYCLES, bool TEST_MODE> [[nodiscard]] ALWAYS_INLINE uint8_t envelopeOutput(uint32_t ksltl, int8_t am_t) const;
177 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE uint32_t envelopeKSLTL(const Patch& patch1, bool use_rm_patches) const;
178 template<uint32_t CYCLES> ALWAYS_INLINE void envelopeTimer1();
179 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void envelopeTimer2(bool& eg_timer_carry);
180 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE bool envelopeGenerate1();
181 template<uint32_t CYCLES> ALWAYS_INLINE void envelopeGenerate2(const Patch& patch1, bool use_rm_patches);
182 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void doLFO(bool& lfo_am_car);
183 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void doRhythm();
184 template<uint32_t CYCLES> ALWAYS_INLINE void doRegWrite();
185 template<uint32_t CYCLES> ALWAYS_INLINE void doIO();
186
187 NEVER_INLINE void doRegWrite(uint32_t channel);
188 void doRegWrite(uint8_t block, uint8_t channel, uint8_t data);
189 NEVER_INLINE void doIO(uint32_t cycles_plus_1, Write& write);
190
191 void doModeWrite(uint8_t address, uint8_t value);
192 void changeFnumBlock(uint32_t ch);
193
194private:
195 static const std::array<Patch, 15> m_patches;
196 static const std::array<Patch, 6> r_patches;
197
198 // IO
199 std::array<Write, 18> writes;
200 uint8_t write_data;
201 uint8_t fm_data;
202 uint8_t write_address;
203 uint8_t write_fm_cycle;
204 bool fast_fm_rewrite;
205 bool test_mode_active;
206
207 // Envelope generator
208 std::span<const uint8_t, 64> attackPtr; // redundant: calculate from eg_timer_shift_lock, eg_timer_lock
209 std::span<const uint8_t, 64> releasePtr; // redundant: calculate from eg_timer_shift_lock, eg_timer_lock, eg_counter_state
210 uint32_t eg_timer;
211 std::array<uint8_t, 2> eg_sl;
212 std::array<uint8_t, 2> eg_out;
213 uint8_t eg_counter_state; // 0..3
214 uint8_t eg_timer_shift;
215 uint8_t eg_timer_shift_lock;
216 uint8_t eg_timer_lock;
217 std::array<EgState, 18> eg_state;
218 std::array<uint8_t, 18> eg_level;
219 std::array<uint8_t, 2> eg_rate;
220 std::array<bool, 18> eg_dokon;
221 std::array<bool, 2> eg_kon;
222 std::array<bool, 2> eg_off;
223 bool eg_timer_shift_stop;
224
225 // Phase generator
226 std::array<uint32_t, 18> pg_phase;
227
228 // Operator
229 std::array<int16_t, 9> op_fb1;
230 std::array<int16_t, 9> op_fb2;
231 int16_t op_mod;
232 std::array<uint16_t, 2> op_phase;
233
234 // LFO
235 uint16_t lfo_counter;
236 uint16_t lfo_am_counter;
237 uint8_t lfo_vib_counter;
238 int8_t lfo_vib; // redundant: equal to VIB_TAB[lfo_vib_counter]
239 uint8_t lfo_am_out;
240 bool lfo_am_step;
241 bool lfo_am_dir;
242
243 // Register set
244 std::array<uint16_t, 9> fnum;
245 std::array<uint8_t, 9> block;
246 std::array<uint8_t, 9> p_ksl; // redundant: calculate from fnum[] and block[]
247 std::array<uint16_t, 9> p_incr; // redundant: calculate from fnum[] and block[]
248 std::array<uint8_t, 9> p_ksr_freq; // redundant: calculate from fnum[] and block[]
249 std::array<uint8_t, 9> sk_on;
250 std::array<uint8_t, 9> vol8; // multiplied by 8
251 std::array<uint8_t, 9> inst;
252 std::array<const Patch*, 9> p_inst; // redundant: &patches[inst[]]
253 uint8_t rhythm;
254 uint8_t testMode;
255 std::array<Patch, 1 + 15> patches; // user patch (modifiable) + 15 ROM patches
256 std::array<uint8_t, 3> c_dcm;
257
258 // Rhythm mode
259 uint32_t rm_noise;
260 uint8_t rm_tc_bits;
261
262 int delay6;
263 int delay7;
264 int delay10;
265 int delay11;
266 int delay12;
267
268 // only used for peekReg();
269 std::array<uint8_t, 64> regs;
270 uint8_t latch;
271
272 int allowed_offset = 0; // Hack: see comments in writePort()
273 bool speedUpHack = false;
274};
275
276} // namespace openmsx::NukeYKT
277
278#endif
Abstract interface for the YM2413 core.
Definition: YM2413Core.hh:28
uint8_t peekReg(uint8_t reg) const override
Read from a YM2413 register (for debug).
void writePort(bool port, uint8_t value, int cycle_offset) override
Write to the YM2413 register/data port.
void setSpeed(double speed) override
Sets real-time speed factor (aka the openMSX 'speed' setting).
float getAmplificationFactor() const override
Returns normalization factor.
void pokeReg(uint8_t reg, uint8_t value) override
Write to a YM2413 register (for debug).
void serialize(Archive &ar, unsigned version)
void generateChannels(std::span< float *, 9+5 > out, uint32_t n) override
void reset() override
Reset this YM2413 core.
#define NEVER_INLINE
Definition: inline.hh:17
#define ALWAYS_INLINE
Definition: inline.hh:16