OXYGENHUB
◆ main openmsx / src / sound / YM2413NukeYKT.hh
10.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
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
69
namespace openmsx::YM2413NukeYKT {
70
71
class YM2413 final : public YM2413Core
72
{
73
public:
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]] std::span<const uint8_t, 64> peekRegs() 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
102
private:
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 = 0;
170
		uint8_t value = 0;
171
172
		template<typename Archive>
173
		void serialize(Archive& ar, unsigned version);
174
	};
175
176
private:
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
206
private:
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