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
63#include "inline.hh"
64#include "stl.hh"
65
66#include <array>
67#include <span>
68
69namespace openmsx::YM2413NukeYKT {
70
71class YM2413 final : public YM2413Core
72{
73public:
74 YM2413();
75 void reset() override;
76 void writePort(bool port, uint8_t value, int cycle_offset) override;
77 void pokeReg(uint8_t reg, uint8_t value) override;
78 [[nodiscard]] uint8_t peekReg(uint8_t reg) const override;
79 void generateChannels(std::span<float*, 9 + 5> out, uint32_t n) override;
80 [[nodiscard]] float getAmplificationFactor() const override;
81 void setSpeed(double speed) override;
82
83 template<typename Archive>
84 void serialize(Archive& ar, unsigned version);
85
86 enum class EgState : uint8_t {
87 attack,
88 decay,
89 sustain,
90 release,
91 };
92 enum class RmNum : uint8_t {
93 bd0, // cycles == 11
94 hh, // 12
95 tom, // 13
96 bd1, // 14
97 sd, // 15
98 tc, // 16
99 NUM
100 };
101
102private:
103 using bool_2 = std::array<bool, 2>;
104 using int8_t_2 = std::array<int8_t, 2>;
105 using uint8_t_2 = std::array<uint8_t, 2>;
106 struct Patch {
107 constexpr Patch() = default;
108 constexpr Patch(uint8_t tl_, uint8_t dcm_, uint8_t fb_,
109 bool_2 am_, bool_2 vib_, bool_2 et_, bool_2 ksr_, uint8_t_2 multi_,
110 uint8_t_2 ksl_, uint8_t_2 ar_, uint8_t_2 dr_, uint8_t_2 sl_, uint8_t_2 rr_)
111 : dcm(dcm_), vib(vib_), et(et_), sl(sl_) {
112 setTL(tl_);
113 setFB(fb_);
114 setAM(0, am_[0]);
115 setAM(1, am_[1]);
116 setKSR(0, ksr_[0]);
117 setKSR(1, ksr_[1]);
118 setMulti(0, multi_[0]);
119 setMulti(1, multi_[1]);
120 setKSL(0, ksl_[0]);
121 setKSL(1, ksl_[1]);
122 setAR(0, ar_[0]);
123 setAR(1, ar_[1]);
124 setDR(0, dr_[0]);
125 setDR(1, dr_[1]);
126 setRR(0, rr_[0]);
127 setRR(1, rr_[1]);
128 }
129
130 constexpr void setTL(uint8_t tl) { tl2 = 2 * tl; }
131 constexpr void setFB(uint8_t fb) { fb_t = fb ? (8 - fb) : 31; }
132 constexpr void setAM(int i, bool am) { am_t[i] = am ? -1 : 0; }
133 constexpr void setKSR(int i, bool ksr) { ksr_t[i] = ksr ? 0 : 2; }
134 constexpr void setMulti(int i, uint8_t multi) {
135 constexpr std::array<uint8_t, 16> PG_MULTI = {
136 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 20, 24, 24, 30, 30
137 };
138 multi_t[i] = PG_MULTI[multi];
139 }
140 constexpr void setKSL(int i, uint8_t ksl) { ksl_t[i] = ksl ? (3 - ksl) : 31; }
141 constexpr void setAR (int i, uint8_t ar) { ar4[i] = 4 * ar; }
142 constexpr void setDR (int i, uint8_t dr) { dr4[i] = 4 * dr; }
143 constexpr void setRR (int i, uint8_t rr) { rr4[i] = 4 * rr; }
144
145 uint8_t tl2 = 0; // multiplied by 2
146 uint8_t dcm = 0;
147 uint8_t fb_t = 31; // 0->31, 1->7, 2->6, .., 6->2, 7->1
148 int8_t_2 am_t = {0, 0}; // false->0, true->-1
149 bool_2 vib = {false, false};
150 bool_2 et = {false, false};
151 uint8_t_2 ksr_t = {2, 2}; // 0->2, 1->0
152 uint8_t_2 multi_t = {1, 1}; // transformed via PG_MULTI[]
153 uint8_t_2 ksl_t = {31, 31}; // 0->31, 1->2, 2->1, 3->0
154 uint8_t_2 ar4 = {0, 0}; // multiplied by 4
155 uint8_t_2 dr4 = {0, 0}; // multiplied by 4
156 uint8_t_2 sl = {0, 0};
157 uint8_t_2 rr4 = {0, 0}; // multiplied by 4
158 };
159 struct Locals {
160 explicit Locals(std::span<float*, 9 + 5> out_) : out(out_) {}
161
162 std::span<float*, 9 + 5> out;
163 uint8_t rm_hh_bits = 0;
164 bool use_rm_patches = false;
165 bool lfo_am_car = false; // between cycle 17 and 0 'lfo_am_car' is always =0
166 bool eg_timer_carry = false;
167 };
168 struct Write {
169 uint8_t port;
170 uint8_t value;
171
172 template<typename Archive>
173 void serialize(Archive& ar, unsigned version);
174 };
175
176private:
177 template<bool TEST_MODE> NEVER_INLINE void step18(std::span<float*, 9 + 5> out);
178 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void step(Locals& l);
179
180 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE uint32_t phaseCalcIncrement(const Patch& patch1) const;
181 template<uint32_t CYCLES> ALWAYS_INLINE void channelOutput(std::span<float*, 9 + 5> out, int32_t ch_out);
182 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE const Patch& preparePatch1(bool use_rm_patches) const;
183 template<uint32_t CYCLES, bool TEST_MODE> [[nodiscard]] ALWAYS_INLINE uint32_t getPhase(uint8_t& rm_hh_bits);
184 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE bool keyOnEvent() const;
185 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void incrementPhase(uint32_t phase_incr, bool prev_rhythm);
186 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE uint32_t getPhaseMod(uint8_t fb_t);
187 template<uint32_t CYCLES> ALWAYS_INLINE void doOperator(std::span<float*, 9 + 5> out, bool eg_silent);
188 template<uint32_t CYCLES, bool TEST_MODE> [[nodiscard]] ALWAYS_INLINE uint8_t envelopeOutput(uint32_t ksltl, int8_t am_t) const;
189 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE uint32_t envelopeKSLTL(const Patch& patch1, bool use_rm_patches) const;
190 template<uint32_t CYCLES> ALWAYS_INLINE void envelopeTimer1();
191 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void envelopeTimer2(bool& eg_timer_carry);
192 template<uint32_t CYCLES> [[nodiscard]] ALWAYS_INLINE bool envelopeGenerate1();
193 template<uint32_t CYCLES> ALWAYS_INLINE void envelopeGenerate2(const Patch& patch1, bool use_rm_patches);
194 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void doLFO(bool& lfo_am_car);
195 template<uint32_t CYCLES, bool TEST_MODE> ALWAYS_INLINE void doRhythm();
196 template<uint32_t CYCLES> ALWAYS_INLINE void doRegWrite();
197 template<uint32_t CYCLES> ALWAYS_INLINE void doIO();
198
199 NEVER_INLINE void doRegWrite(uint8_t channel);
200 void doRegWrite(uint8_t block, uint8_t channel, uint8_t data);
201 NEVER_INLINE void doIO(uint32_t cycles_plus_1, Write& write);
202
203 void doModeWrite(uint8_t address, uint8_t value);
204 void changeFnumBlock(uint32_t ch);
205
206private:
207 static const std::array<Patch, 15> m_patches;
208 static const array_with_enum_index<RmNum, Patch> r_patches;
209
210 // IO
211 std::array<Write, 18> writes;
212 uint8_t write_data;
213 uint8_t fm_data;
214 uint8_t write_address;
215 uint8_t write_fm_cycle;
216 bool fast_fm_rewrite;
217 bool test_mode_active;
218
219 // Envelope generator
220 std::span<const uint8_t, 64> attackPtr; // redundant: calculate from eg_timer_shift_lock, eg_timer_lock
221 std::span<const uint8_t, 64> releasePtr; // redundant: calculate from eg_timer_shift_lock, eg_timer_lock, eg_counter_state
222 uint32_t eg_timer;
223 std::array<uint8_t, 2> eg_sl;
224 std::array<uint8_t, 2> eg_out;
225 uint8_t eg_counter_state; // 0..3
226 uint8_t eg_timer_shift;
227 uint8_t eg_timer_shift_lock;
228 uint8_t eg_timer_lock;
229 std::array<EgState, 18> eg_state;
230 std::array<uint8_t, 18> eg_level;
231 std::array<uint8_t, 2> eg_rate;
232 std::array<bool, 18> eg_dokon;
233 std::array<bool, 2> eg_kon;
234 std::array<bool, 2> eg_off;
235 bool eg_timer_shift_stop;
236
237 // Phase generator
238 std::array<uint32_t, 18> pg_phase;
239
240 // Operator
241 std::array<int16_t, 9> op_fb1;
242 std::array<int16_t, 9> op_fb2;
243 int16_t op_mod;
244 std::array<uint16_t, 2> op_phase;
245
246 // LFO
247 uint16_t lfo_counter;
248 uint16_t lfo_am_counter;
249 uint8_t lfo_vib_counter;
250 int8_t lfo_vib; // redundant: equal to VIB_TAB[lfo_vib_counter]
251 uint8_t lfo_am_out;
252 bool lfo_am_step;
253 bool lfo_am_dir;
254
255 // Register set
256 std::array<uint16_t, 9> fnum;
257 std::array<uint8_t, 9> block;
258 std::array<uint8_t, 9> p_ksl; // redundant: calculate from fnum[] and block[]
259 std::array<uint16_t, 9> p_incr; // redundant: calculate from fnum[] and block[]
260 std::array<uint8_t, 9> p_ksr_freq; // redundant: calculate from fnum[] and block[]
261 std::array<uint8_t, 9> sk_on;
262 std::array<uint8_t, 9> vol8; // multiplied by 8
263 std::array<uint8_t, 9> inst;
264 std::array<const Patch*, 9> p_inst; // redundant: &patches[inst[]]
265 uint8_t rhythm;
266 uint8_t testMode;
267 std::array<Patch, 1 + 15> patches; // user patch (modifiable) + 15 ROM patches
268 std::array<uint8_t, 3> c_dcm;
269
270 // Rhythm mode
271 uint32_t rm_noise;
272 uint8_t rm_tc_bits;
273
274 int delay6;
275 int delay7;
276 int delay10;
277 int delay11;
278 int delay12;
279
280 // only used for peekReg();
281 std::array<uint8_t, 64> regs;
282 uint8_t latch;
283
284 int allowed_offset = 0; // Hack: see comments in writePort()
285 bool speedUpHack = false;
286};
287
288} // namespace openmsx::NukeYKT
289
290#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
void serialize(Archive &ar, T &t, unsigned version)