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:Olivier Galibert
/***************************************************************************
votrax.h
Votrax SC01A simulation
***************************************************************************/
#ifndef MAME_SOUND_VOTRAX_H
#define MAME_SOUND_VOTRAX_H
#pragma once
class votrax_sc01_device : public device_t,
public device_sound_interface
{
public:
static constexpr feature_type imperfect_features() { return feature::SOUND; }
// construction/destruction
votrax_sc01_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
auto ar_callback() { return m_ar_cb.bind(); }
void write(uint8_t data);
void inflection_w(uint8_t data);
DECLARE_READ_LINE_MEMBER(request) { m_stream->update(); return m_ar_state; }
protected:
// device-level overrides
virtual const tiny_rom_entry *device_rom_region() const override;
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_clock_changed() override;
// device_sound_interface overrides
virtual void sound_stream_update(sound_stream &stream, std::vector<read_stream_view> const &inputs, std::vector<write_stream_view> &outputs) override;
TIMER_CALLBACK_MEMBER(phone_tick);
private:
// Possible timer parameters
enum {
T_COMMIT_PHONE,
T_END_OF_PHONE
};
static const char *const s_phone_table[64];
static const double s_glottal_wave[9];
sound_stream *m_stream; // Output stream
emu_timer *m_timer; // General timer
required_memory_region m_rom; // Internal ROM
u32 m_mainclock; // Current main clock
double m_sclock; // Stream sample clock (40KHz, main/18)
double m_cclock; // 20KHz capacitor switching clock (main/36)
u32 m_sample_count; // Sample counter, to cadence chip updates
// Inputs
u8 m_inflection; // 2-bit inflection value
u8 m_phone; // 6-bit phone value
// Outputs
devcb_write_line m_ar_cb; // Callback for ar
bool m_ar_state; // Current ar state
// "Unpacked" current rom values
u8 m_rom_duration; // Duration in 5KHz units (main/144) of one tick, 16 ticks per phone, 7 bits
u8 m_rom_vd, m_rom_cld; // Duration in ticks of the "voice" and "closure" delays, 4 bits
u8 m_rom_fa, m_rom_fc, m_rom_va; // Analog parameters, noise volume, noise freq cutoff and voice volume, 4 bits each
u8 m_rom_f1, m_rom_f2, m_rom_f2q, m_rom_f3; // Analog parameters, formant frequencies and Q, 4 bits each
bool m_rom_closure; // Closure bit, true = silence at cld
bool m_rom_pause; // Pause bit
// Current interpolated values (8 bits each)
u8 m_cur_fa, m_cur_fc, m_cur_va;
u8 m_cur_f1, m_cur_f2, m_cur_f2q, m_cur_f3;
// Current committed values
u8 m_filt_fa, m_filt_fc, m_filt_va; // Analog parameters, noise volume, noise freq cutoff and voice volume, 4 bits each
u8 m_filt_f1, m_filt_f2, m_filt_f2q, m_filt_f3; // Analog parameters, formant frequencies/Q on 4 bits except f2 on 5 bits
// Internal counters
u16 m_phonetick; // 9-bits phone tick duration counter
u8 m_ticks; // 5-bits tick counter
u8 m_pitch; // 7-bits pitch counter
u8 m_closure; // 5-bits glottal closure counter
u8 m_update_counter; // 6-bits counter for the 625Hz (main/1152) and 208Hz (main/3456) update timing generators
// Internal state
bool m_cur_closure; // Current internal closure state
u16 m_noise; // 15-bit noise shift register
bool m_cur_noise; // Current noise output
// Filter coefficients and level histories
double m_voice_1[4];
double m_voice_2[4];
double m_voice_3[4];
double m_noise_1[3];
double m_noise_2[3];
double m_noise_3[2];
double m_noise_4[2];
double m_vn_1[4];
double m_vn_2[4];
double m_vn_3[4];
double m_vn_4[4];
double m_vn_5[2];
double m_vn_6[2];
double m_f1_a[4], m_f1_b[4]; // F1 filtering
double m_f2v_a[4], m_f2v_b[4]; // F2 voice filtering
double m_f2n_a[2], m_f2n_b[2]; // F2 noise filtering
double m_f3_a[4], m_f3_b[4]; // F3 filtering
double m_f4_a[4], m_f4_b[4]; // F4 filtering
double m_fx_a[1], m_fx_b[2]; // Final filtering
double m_fn_a[3], m_fn_b[3]; // Noise shaping
// Compute a total capacitor value based on which bits are currently active
static double bits_to_caps(u32 value, std::initializer_list<double> caps_values) {
double total = 0;
for(double d : caps_values) {
if(value & 1)
total += d;
value >>= 1;
}
return total;
}
// Shift a history of values by one and insert the new value at the front
template<u32 N> static void shift_hist(double val, double (&hist_array)[N]) {
for(u32 i=N-1; i>0; i--)
hist_array[i] = hist_array[i-1];
hist_array[0] = val;
}
// Apply a filter and compute the result. 'a' is applied to x (inputs) and 'b' to y (outputs)
template<u32 Nx, u32 Ny, u32 Na, u32 Nb> static double apply_filter(const double (&x)[Nx], const double (&y)[Ny], const double (&a)[Na], const double (&b)[Nb]) {
double total = 0;
for(u32 i=0; i<Na; i++)
total += x[i] * a[i];
for(u32 i=1; i<Nb; i++)
total -= y[i-1] * b[i];
return total / b[0];
}
void build_standard_filter(double *a, double *b,
double c1t, // Unswitched cap, input, top
double c1b, // Switched cap, input, bottom
double c2t, // Unswitched cap, over first amp-op, top
double c2b, // Switched cap, over first amp-op, bottom
double c3, // Cap between the two op-amps
double c4); // Cap over second op-amp
void build_noise_shaper_filter(double *a, double *b,
double c1, // Cap over first amp-op
double c2t, // Unswitched cap between amp-ops, input, top
double c2b, // Switched cap between amp-ops, input, bottom
double c3, // Cap over second amp-op
double c4); // Switched cap after second amp-op
void build_lowpass_filter(double *a, double *b,
double c1t, // Unswitched cap, over amp-op, top
double c1b); // Switched cap, over amp-op, bottom
void build_injection_filter(double *a, double *b,
double c1b, // Switched cap, input, bottom
double c2t, // Unswitched cap, over first amp-op, top
double c2b, // Switched cap, over first amp-op, bottom
double c3, // Cap between the two op-amps
double c4); // Cap over second op-amp
static void interpolate(u8 ®, u8 target); // Do one interpolation step
void chip_update(); // Global update called at 20KHz (main/36)
void filters_commit(bool force); // Commit the currently computed interpolation values to the filters
void phone_commit(); // Commit the current phone id
stream_buffer::sample_t analog_calc(); // Compute one more sample
};
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
// device type definition
DECLARE_DEVICE_TYPE(VOTRAX_SC01, votrax_sc01_device)
#endif // MAME_SOUND_VOTRAX_H
|