mirror of
https://github.com/holub/mame
synced 2025-04-19 07:00:31 +03:00
192 lines
7.7 KiB
C++
192 lines
7.7 KiB
C++
// 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(); }
|
|
|
|
DECLARE_WRITE8_MEMBER(write);
|
|
DECLARE_WRITE8_MEMBER(inflection_w);
|
|
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;
|
|
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
|
|
|
|
// device_sound_interface overrides
|
|
virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override;
|
|
|
|
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_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
|