summaryrefslogtreecommitdiffstatshomepage
path: root/3rdparty/ymfm/src/ymfm_pcm.h
blob: b471fa611a68c2ab1972564f6dc90cb36f9207de (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
//    list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#ifndef YMFM_PCM_H
#define YMFM_PCM_H

#pragma once

#include "ymfm.h"

namespace ymfm
{

/*
Note to self: Sega "Multi-PCM" is almost identical to this

28 channels

Writes:
00 = data reg, causes write
01 = target slot = data - (data / 8)
02 = address (clamped to 7)

Slot data (registers with ADSR/KSR seem to be inaccessible):
0: xxxx---- panpot
1: xxxxxxxx wavetable low
2: xxxxxx-- pitch low
   -------x wavetable high
3: xxxx---- octave
   ----xxxx pitch hi
4: x------- key on
5: xxxxxxx- total level
   -------x level direct (0=interpolate)
6: --xxx--- LFO frequency
   -----xxx PM sensitivity
7: -----xxx AM sensitivity

Sample data:
+00: start hi
+01: start mid
+02: start low
+03: loop hi
+04: loop low
+05: -end hi
+06: -end low
+07: vibrato (reg 6)
+08: attack/decay
+09: sustain level/rate
+0A: ksr/release
+0B: LFO amplitude (reg 7)

*/

//*********************************************************
//  INTERFACE CLASSES
//*********************************************************

class pcm_engine;


// ======================> pcm_cache

// this class holds data that is computed once at the start of clocking
// and remains static during subsequent sound generation
struct pcm_cache
{
	uint32_t step;                    // sample position step, as a .16 value
	uint32_t total_level;             // target total level, as a .10 value
	uint32_t pan_left;                // left panning attenuation
	uint32_t pan_right;               // right panning attenuation
	uint32_t eg_sustain;              // sustain level, shifted up to envelope values
	uint8_t eg_rate[EG_STATES];       // envelope rate, including KSR
	uint8_t lfo_step;                 // stepping value for LFO
	uint8_t am_depth;                 // scale value for AM LFO
	uint8_t pm_depth;                 // scale value for PM LFO
};


// ======================> pcm_registers

//
// PCM register map:
//
//      System-wide registers:
//        00-01 xxxxxxxx LSI Test
//           02 -------x Memory access mode (0=sound gen, 1=read/write)
//              ------x- Memory type (0=ROM, 1=ROM+SRAM)
//              ---xxx-- Wave table header
//              xxx----- Device ID (=1 for YMF278B)
//           03 --xxxxxx Memory address high
//           04 xxxxxxxx Memory address mid
//           05 xxxxxxxx Memory address low
//           06 xxxxxxxx Memory data
//           F8 --xxx--- Mix control (FM_R)
//              -----xxx Mix control (FM_L)
//           F9 --xxx--- Mix control (PCM_R)
//              -----xxx Mix control (PCM_L)
//
//      Channel-specific registers:
//        08-1F xxxxxxxx Wave table number low
//        20-37 -------x Wave table number high
//              xxxxxxx- F-number low
//        38-4F -----xxx F-number high
//              ----x--- Pseudo-reverb
//              xxxx---- Octave
//        50-67 xxxxxxx- Total level
//              -------x Level direct
//        68-7F x------- Key on
//              -x------ Damp
//              --x----- LFO reset
//              ---x---- Output channel
//              ----xxxx Panpot
//        80-97 --xxx--- LFO speed
//              -----xxx Vibrato
//        98-AF xxxx---- Attack rate
//              ----xxxx Decay rate
//        B0-C7 xxxx---- Sustain level
//              ----xxxx Sustain rate
//        C8-DF xxxx---- Rate correction
//              ----xxxx Release rate
//        E0-F7 -----xxx AM depth

class pcm_registers
{
public:
	// constants
	static constexpr uint32_t OUTPUTS = 4;
	static constexpr uint32_t CHANNELS = 24;
	static constexpr uint32_t REGISTERS = 0x100;
	static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;

	// constructor
	pcm_registers() { }

	// save/restore
	void save_restore(ymfm_saved_state &state);

	// reset to initial state
	void reset();

	// update cache information
	void cache_channel_data(uint32_t choffs, pcm_cache &cache);

	// direct read/write access
	uint8_t read(uint32_t index ) { return m_regdata[index]; }
	void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }

	// system-wide registers
	uint32_t memory_access_mode() const                 { return bitfield(m_regdata[0x02], 0); }
	uint32_t memory_type() const                        { return bitfield(m_regdata[0x02], 1); }
	uint32_t wave_table_header() const                  { return bitfield(m_regdata[0x02], 2, 3); }
	uint32_t device_id() const                          { return bitfield(m_regdata[0x02], 5, 3); }
	uint32_t memory_address() const                     { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; }
	uint32_t memory_data() const                        { return m_regdata[0x06]; }
	uint32_t mix_fm_r() const                           { return bitfield(m_regdata[0xf8], 3, 3); }
	uint32_t mix_fm_l() const                           { return bitfield(m_regdata[0xf8], 0, 3); }
	uint32_t mix_pcm_r() const                          { return bitfield(m_regdata[0xf9], 3, 3); }
	uint32_t mix_pcm_l() const                          { return bitfield(m_regdata[0xf9], 0, 3); }

	// per-channel registers
	uint32_t ch_wave_table_num(uint32_t choffs) const   { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); }
	uint32_t ch_fnumber(uint32_t choffs) const          { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); }
	uint32_t ch_pseudo_reverb(uint32_t choffs) const    { return bitfield(m_regdata[choffs + 0x38], 3); }
	uint32_t ch_octave(uint32_t choffs) const           { return bitfield(m_regdata[choffs + 0x38], 4, 4); }
	uint32_t ch_total_level(uint32_t choffs) const      { return bitfield(m_regdata[choffs + 0x50], 1, 7); }
	uint32_t ch_level_direct(uint32_t choffs) const     { return bitfield(m_regdata[choffs + 0x50], 0); }
	uint32_t ch_keyon(uint32_t choffs) const            { return bitfield(m_regdata[choffs + 0x68], 7); }
	uint32_t ch_damp(uint32_t choffs) const             { return bitfield(m_regdata[choffs + 0x68], 6); }
	uint32_t ch_lfo_reset(uint32_t choffs) const        { return bitfield(m_regdata[choffs + 0x68], 5); }
	uint32_t ch_output_channel(uint32_t choffs) const   { return bitfield(m_regdata[choffs + 0x68], 4); }
	uint32_t ch_panpot(uint32_t choffs) const           { return bitfield(m_regdata[choffs + 0x68], 0, 4); }
	uint32_t ch_lfo_speed(uint32_t choffs) const        { return bitfield(m_regdata[choffs + 0x80], 3, 3); }
	uint32_t ch_vibrato(uint32_t choffs) const          { return bitfield(m_regdata[choffs + 0x80], 0, 3); }
	uint32_t ch_attack_rate(uint32_t choffs) const      { return bitfield(m_regdata[choffs + 0x98], 4, 4); }
	uint32_t ch_decay_rate(uint32_t choffs) const       { return bitfield(m_regdata[choffs + 0x98], 0, 4); }
	uint32_t ch_sustain_level(uint32_t choffs) const    { return bitfield(m_regdata[choffs + 0xb0], 4, 4); }
	uint32_t ch_sustain_rate(uint32_t choffs) const     { return bitfield(m_regdata[choffs + 0xb0], 0, 4); }
	uint32_t ch_rate_correction(uint32_t choffs) const  { return bitfield(m_regdata[choffs + 0xc8], 4, 4); }
	uint32_t ch_release_rate(uint32_t choffs) const     { return bitfield(m_regdata[choffs + 0xc8], 0, 4); }
	uint32_t ch_am_depth(uint32_t choffs) const         { return bitfield(m_regdata[choffs + 0xe0], 0, 3); }

	// return the memory address and increment it
	uint32_t memory_address_autoinc()
	{
		uint32_t result = memory_address();
		uint32_t newval = result + 1;
		m_regdata[0x05] = newval >> 0;
		m_regdata[0x04] = newval >> 8;
		m_regdata[0x03] = (newval >> 16) & 0x3f;
		return result;
	}

private:
	// internal helpers
	uint32_t effective_rate(uint32_t raw, uint32_t correction);

	// internal state
	uint8_t m_regdata[REGISTERS];         // register data
};


// ======================> pcm_channel

class pcm_channel
{
	static constexpr uint8_t KEY_ON = 0x01;
	static constexpr uint8_t KEY_PENDING_ON = 0x02;
	static constexpr uint8_t KEY_PENDING = 0x04;

	// "quiet" value, used to optimize when we can skip doing working
	static constexpr uint32_t EG_QUIET = 0x200;

public:
	using output_data = ymfm_output<pcm_registers::OUTPUTS>;

	// constructor
	pcm_channel(pcm_engine &owner, uint32_t choffs);

	// save/restore
	void save_restore(ymfm_saved_state &state);

	// reset the channel state
	void reset();

	// return the channel offset
	uint32_t choffs() const { return m_choffs; }

	// prepare prior to clocking
	bool prepare();

	// master clocking function
	void clock(uint32_t env_counter);

	// return the computed output value, with panning applied
	void output(output_data &output) const;

	// signal key on/off
	void keyonoff(bool on);

	// load a new wavetable entry
	void load_wavetable();

private:
	// internal helpers
	void start_attack();
	void start_release();
	void clock_envelope(uint32_t env_counter);
	int16_t fetch_sample() const;
	uint8_t read_pcm(uint32_t address) const;

	// internal state
	uint32_t const m_choffs;              // channel offset
	uint32_t m_baseaddr;                  // base address
	uint32_t m_endpos;                    // ending position
	uint32_t m_looppos;                   // loop position
	uint32_t m_curpos;                    // current position
	uint32_t m_nextpos;                   // next position
	uint32_t m_lfo_counter;               // LFO counter
	envelope_state m_eg_state;            // envelope state
	uint16_t m_env_attenuation;           // computed envelope attenuation
	uint32_t m_total_level;               // total level with as 7.10 for interp
	uint8_t m_format;                     // sample format
	uint8_t m_key_state;                  // current key state
	pcm_cache m_cache;                    // cached data
	pcm_registers &m_regs;                // reference to registers
	pcm_engine &m_owner;                  // reference to our owner
};


// ======================> pcm_engine

class pcm_engine
{
public:
	static constexpr int OUTPUTS = pcm_registers::OUTPUTS;
	static constexpr int CHANNELS = pcm_registers::CHANNELS;
	static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS;
	using output_data = pcm_channel::output_data;

	// constructor
	pcm_engine(ymfm_interface &intf);

	// reset our status
	void reset();

	// save/restore
	void save_restore(ymfm_saved_state &state);

	// master clocking function
	void clock(uint32_t chanmask);

	// compute sum of channel outputs
	void output(output_data &output, uint32_t chanmask);

	// read from the PCM registers
	uint8_t read(uint32_t regnum);

	// write to the PCM registers
	void write(uint32_t regnum, uint8_t data);

	// return a reference to our interface
	ymfm_interface &intf() { return m_intf; }

	// return a reference to our registers
	pcm_registers &regs() { return m_regs; }

private:
	// internal state
	ymfm_interface &m_intf;                           // reference to the interface
	uint32_t m_env_counter;                           // envelope counter
	uint32_t m_modified_channels;                     // bitmask of modified channels
	uint32_t m_active_channels;                       // bitmask of active channels
	uint32_t m_prepare_count;                         // counter to do periodic prepare sweeps
	std::unique_ptr<pcm_channel> m_channel[CHANNELS]; // array of channels
	pcm_registers m_regs;                             // registers
};

}

#endif // YMFM_PCM_H