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
|
// license:BSD-3-Clause
// copyright-holders:m1macrophage
#ifndef MAME_SOUND_VA_EG_H
#define MAME_SOUND_VA_EG_H
#pragma once
DECLARE_DEVICE_TYPE(VA_RC_EG, va_rc_eg_device)
DECLARE_DEVICE_TYPE(VA_OTA_EG, va_ota_eg_device)
// Building block for emulating envelope generators (EGs) based on a single RC
// circuit. The controlling source sets a target voltage and the device
// interpolates up or down to it through a RC circuit. The voltage is published
// as a sound stream and also available from `get_v()`.
//
// For example, emulating a CPU-controlled ADSR EG will look something like:
// - Machine configuration: rc_eg.set_c(C);
// - Start attack: rc_eg.set_r(attack_r).set_target_v(max_v);
// - Start decay: rc_eg.set_r(decay_r).set_target_v(sustain_v);
// - Start release: rc_eg.set_r(release_r).set_target_v(0);
//
// The set_*() methods expect a monotonically increasing machine time, and will
// assert()-fail if that's not the case.
// If a single instance is used by multiple CPUs, this requirement can be met by
// calling set_*() from within timer callbacks invoked with:
// machine().scheduler().synchronize(timer_expired_delegate(...)).
// See src/mame/moog/source.cpp for examples.
//
// Depending on the details of the multi-CPU system being emulated, additional
// synchronization mitigations may be warranted, such as add_quantum(),
// set_perfect_quantum(), etc.
class va_rc_eg_device : public device_t, public device_sound_interface
{
public:
va_rc_eg_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock = 0) ATTR_COLD;
// These setters can be used during both: configuration and normal operation.
// `r` and `c` must be > 0. To instantly set the capacitor's voltage to
// a specific value, use set_instant_v().
va_rc_eg_device &set_r(float r);
va_rc_eg_device &set_c(float v);
// Sets target voltage to (dis)charge towards.
va_rc_eg_device &set_target_v(float v);
float get_target_v() const { return m_v_end; }
// Sets the voltage to the given value, instantly.
va_rc_eg_device &set_instant_v(float v);
float get_v(const attotime &t) const;
float get_v() const; // Get voltage at the machine's current time.
// Returns the remaining time required to reach voltage `v`.
// Returns attotime::never if `v` was reached in the past, or if it is
// impossible to reach.
attotime get_dt(float v) const;
// Returns true if the voltage converged to the target by time `t`.
bool converged(const attotime &t) const { return t >= m_t_end_approx; }
protected:
void device_start() override ATTR_COLD;
void sound_stream_update(sound_stream &stream) override;
private:
// Takes a snapshot of the current voltage into m_v_start and m_t_start.
void snapshot();
sound_stream *m_stream;
float m_r;
float m_c;
float m_rc_inv;
float m_v_start;
float m_v_end;
attotime m_t_start;
attotime m_t_end_approx;
};
// An envelope generator (EG) built around an operational transconductance
// amplifier (OTA). Its rate is controlled by Iabc.
//
// When the EG output is far from the target (e.g. > ~0.2V), the EG will ramp
// towards the target linearly. As the output approaches the target, the EG ramp
// will start curving. When the difference is < ~0.02V, the ramp will closely
// approximate an "RC" curve with R = (2 * VT) / (s * Iabc). Where 's' is a
// device-specific constant (typically 1) and VT is the thermal voltage.
//
// x Iabc
// ___|__ |
// | | \ |
// Target V ---------- |+ Id \ |
// | \ |
// | \ |
// | OTA OO ---+---- BUFFER --- EG output V
// | / | |
// | / C |
// +------- |- / | |
// | |______/ GND |
// | |
// +------------------------------------------+
//
// Note that this configuration does not make use of the linearizing diodes (Id
// input). The emulation here assumes the Id input is disconnected or connected
// to the negative supply, for OTAs that have it.
//
// A variant of the above uses anti-parallel diodes to connect the inputs (e.g.
// prophet 5 glide EG). This configuration protects the OTA from large voltage
// differences, but otherwise behaves the same to the above. The values of R
// don't matter.
//
// x Iabc
// ___|__ |
// | | \ |
// Target V --- R ---+---+--- |+ Id \ |
// | | | \ |
// D ^ | \ |
// v D | OTA OO ---+---- BUFFER --- EG output V
// | | | / | |
// | | | / C |
// +---+--- |- / | |
// | |______/ GND |
// | |
// +-------------------- R -------------------+
//
// Another common variant uses voltage dividers at the two inputs. These ensure
// the voltage difference at the inputs is small, keeping the OTA in its linear
// region, and producing "RC" curves. In this configuration, the output will
// converge to: TargetV * scale+ / scale-, where scale = Rgnd / (Rin + Rgnd).
// For small enough scaling factors, R ~= (2 * VT) / (s * Iabc * scale-).
//
// x Iabc
// ___|__ |
// | | \ |
// Target V - Rin+- -+------- |+ Id \ |
// | | \ |
// Rgnd+ | \ |
// | | OTA OO ---+---- BUFFER --- EG output V
// GND | / | |
// | / C |
// +------- |- / | |
// | |______/ GND |
// | |
// GND --- RGnd- --- +-------------------- Rin- ----------------+
//
class va_ota_eg_device : public device_t, public device_sound_interface
{
public:
enum class ota_type : s8
{
LM13600 = 0,
LM13700,
CA3080,
CA3280,
};
va_ota_eg_device(const machine_config &mconfig, const char *tag, device_t *owner, ota_type ota, float c) ATTR_COLD;
va_ota_eg_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) ATTR_COLD;
// These configure the "scaled input" variant.
va_ota_eg_device &configure_plus_divider(float r_in, float r_gnd);
va_ota_eg_device &configure_minus_divider(float r_in, float r_gnd);
void set_target_v(float v);
void set_iabc(float iabc); // Iabc control current in Amperes.
protected:
void device_start() override ATTR_COLD;
void device_reset() override ATTR_COLD;
void sound_stream_update(sound_stream &stream) override;
private:
void recalc();
// configuration
const float m_c;
float m_max_iout_scale;
float m_plus_scale;
float m_minus_scale;
sound_stream *m_stream;
// state
bool m_converged;
float m_g; // Caches the product of multiple factors. See recalc().
float m_target_v;
float m_iabc;
float m_v;
};
#endif // MAME_SOUND_VA_EG_H
|