// license:BSD-3-Clause
// copyright-holders:Couriersud
#ifndef MAME_SOUND_AY8910_H
#define MAME_SOUND_AY8910_H
#pragma once
#define ALL_8910_CHANNELS -1
/* Internal resistance at Volume level 7. */
#define AY8910_INTERNAL_RESISTANCE (356)
#define YM2149_INTERNAL_RESISTANCE (353)
/*
* The following is used by all drivers not reviewed yet.
* This will like the old behavior, output between
* 0 and 7FFF
*/
#define AY8910_LEGACY_OUTPUT (0x01)
/*
* Specifying the next define will simulate the special
* cross channel mixing if outputs are tied together.
* The driver will only provide one stream in this case.
*/
#define AY8910_SINGLE_OUTPUT (0x02)
/*
* The following define is the default behavior.
* Output level 0 is 0V and 7ffff corresponds to 5V.
* Use this to specify that a discrete mixing stage
* follows.
*/
#define AY8910_DISCRETE_OUTPUT (0x04)
/*
* The following define causes the driver to output
* resistor values. Intended to be used for
* netlist interfacing.
*/
#define AY8910_RESISTOR_OUTPUT (0x08)
/*
* This define specifies the initial state of
* YM2149, YM3439, AY8930 pin 26 (SEL pin).
* By default it is set to high,
* compatible with AY8910.
*/
/* TODO: make it controllable while it's running (used by any hw???) */
#define YM2149_PIN26_HIGH (0x00) /* or N/C */
#define YM2149_PIN26_LOW (0x10)
class ay8910_device : public device_t, public device_sound_interface
{
public:
enum psg_type_t
{
PSG_TYPE_AY,
PSG_TYPE_YM
};
enum config_t
{
PSG_DEFAULT = 0x0,
PSG_PIN26_IS_CLKSEL = 0x1,
PSG_HAS_INTERNAL_DIVIDER = 0x2,
PSG_EXTENDED_ENVELOPE = 0x4,
PSG_HAS_EXPANDED_MODE = 0x8
};
// construction/destruction
ay8910_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
// configuration helpers
void set_flags(int flags) { m_flags = flags; }
void set_psg_type(psg_type_t psg_type) { set_type(psg_type); }
void set_resistors_load(int res_load0, int res_load1, int res_load2) { m_res_load[0] = res_load0; m_res_load[1] = res_load1; m_res_load[2] = res_load2; }
auto port_a_read_callback() { return m_port_a_read_cb.bind(); }
auto port_b_read_callback() { return m_port_b_read_cb.bind(); }
auto port_a_write_callback() { return m_port_a_write_cb.bind(); }
auto port_b_write_callback() { return m_port_b_write_cb.bind(); }
u8 data_r() { return ay8910_read_ym(); }
void address_w(u8 data);
void data_w(u8 data);
// /RES
void reset_w(u8 data = 0) { ay8910_reset_ym(); }
// /SEL
void set_pin26_low_w(u8 data = 0);
void set_pin26_high_w(u8 data = 0);
// use this when BC1 == A0; here, BC1=0 selects 'data' and BC1=1 selects 'latch address'
void data_address_w(offs_t offset, u8 data) { ay8910_write_ym(~offset & 1, data); } // note that directly connecting BC1 to A0 puts data on 0 and address on 1
// use this when BC1 == !A0; here, BC1=0 selects 'latch address' and BC1=1 selects 'data'
void address_data_w(offs_t offset, u8 data) { ay8910_write_ym(offset & 1, data); }
// bc1=a0, bc2=a1
void write_bc1_bc2(offs_t offset, u8 data);
void set_volume(int channel,int volume);
void ay_set_clock(int clock);
struct ay_ym_param
{
double r_up;
double r_down;
int res_count;
double res[32];
};
struct mosfet_param
{
double m_Vth;
double m_Vg;
int m_count;
double m_Kn[32];
};
// internal interface for PSG component of YM device
// FIXME: these should be private, but vector06 accesses them directly
protected:
ay8910_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner,
u32 clock, psg_type_t psg_type, int streams, int ioports, int feature = PSG_DEFAULT);
// device-level overrides
virtual void device_start() override;
virtual void device_reset() override;
virtual void device_clock_changed() override;
// sound stream update overrides
virtual void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) override;
// trampolines for callbacks from fm.cpp
static void psg_set_clock(device_t *device, int clock) { downcast<ay8910_device *>(device)->ay_set_clock(clock); }
static void psg_write(device_t *device, int address, int data) { downcast<ay8910_device *>(device)->ay8910_write_ym(address, data); }
static int psg_read(device_t *device) { return downcast<ay8910_device *>(device)->ay8910_read_ym(); }
static void psg_reset(device_t *device) { downcast<ay8910_device *>(device)->ay8910_reset_ym(); }
private:
static constexpr unsigned NUM_CHANNELS = 3;
/* register id's */
enum
{
AY_AFINE = 0x00,
AY_ACOARSE = 0x01,
AY_BFINE = 0x02,
AY_BCOARSE = 0x03,
AY_CFINE = 0x04,
AY_CCOARSE = 0x05,
AY_NOISEPER = 0x06,
AY_ENABLE = 0x07,
AY_AVOL = 0x08,
AY_BVOL = 0x09,
AY_CVOL = 0x0a,
AY_EAFINE = 0x0b,
AY_EACOARSE = 0x0c,
AY_EASHAPE = 0x0d,
AY_PORTA = 0x0e,
AY_PORTB = 0x0f,
AY_EBFINE = 0x10,
AY_EBCOARSE = 0x11,
AY_ECFINE = 0x12,
AY_ECCOARSE = 0x13,
AY_EBSHAPE = 0x14,
AY_ECSHAPE = 0x15,
AY_ADUTY = 0x16,
AY_BDUTY = 0x17,
AY_CDUTY = 0x18,
AY_NOISEAND = 0x19,
AY_NOISEOR = 0x1a,
AY_TEST = 0x1f
};
// structs
struct tone_t
{
u32 period;
u8 volume;
u8 duty;
s32 count;
u8 duty_cycle;
u8 output;
void reset()
{
period = 0;
volume = 0;
duty = 0;
count = 0;
duty_cycle = 0;
output = 0;
}
void set_period(u8 fine, u8 coarse)
{
period = fine | (coarse << 8);
}
void set_volume(u8 val)
{
volume = val;
}
void set_duty(u8 val)
{
duty = val;
}
};
struct envelope_t
{
u32 period;
s32 count;
s8 step;
u32 volume;
u8 hold, alternate, attack, holding;
void reset()
{
period = 0;
count = 0;
step = 0;
volume = 0;
hold = 0;
alternate = 0;
attack = 0;
holding = 0;
}
void set_period(u8 fine, u8 coarse)
{
period = fine | (coarse << 8);
}
void set_shape(u8 shape, u8 mask)
{
attack = (shape & 0x04) ? mask : 0x00;
if ((shape & 0x08) == 0)
{
/* if Continue = 0, map the shape to the equivalent one which has Continue = 1 */
hold = 1;
alternate = attack;
}
else
{
hold = shape & 0x01;
alternate = shape & 0x02;
}
step = mask;
holding = 0;
volume = (step ^ attack);
}
};
// inlines
inline bool tone_enable(int chan) { return BIT(m_regs[AY_ENABLE], chan); }
inline u8 tone_volume(tone_t *tone) { return tone->volume & (is_expanded_mode() ? 0x1f : 0x0f); }
inline u8 tone_envelope(tone_t *tone) { return (tone->volume >> (is_expanded_mode() ? 5 : 4)) & ((m_feature & PSG_EXTENDED_ENVELOPE) ? 3 : 1); }
inline u8 tone_duty(tone_t *tone) { return is_expanded_mode() ? (tone->duty & 0x8 ? 0x8 : (tone->duty & 0xf)) : 0x4; }
inline u8 get_envelope_chan(int chan) { return is_expanded_mode() ? chan : 0; }
inline bool noise_enable(int chan) { return BIT(m_regs[AY_ENABLE], 3 + chan); }
inline u8 noise_period() { return is_expanded_mode() ? m_regs[AY_NOISEPER] & 0xff : (m_regs[AY_NOISEPER] & 0x1f) << 1; }
inline u8 noise_output() { return m_rng & 1; }
inline bool is_expanded_mode() { return ((m_feature & PSG_HAS_EXPANDED_MODE) && ((m_mode & 0xe) == 0xa)); }
inline u8 get_register_bank() { return is_expanded_mode() ? (m_mode & 0x1) << 4 : 0; }
// internal helpers
void set_type(psg_type_t psg_type);
inline u16 mix_3D();
void ay8910_write_reg(int r, int v);
void build_mixer_table();
void ay8910_statesave();
void ay8910_write_ym(int addr, u8 data);
u8 ay8910_read_ym();
void ay8910_reset_ym();
// internal state
psg_type_t m_type;
int m_streams;
int m_ioports;
int m_ready;
sound_stream *m_channel;
bool m_active;
s32 m_register_latch;
u8 m_regs[16 * 2];
s32 m_last_enable;
tone_t m_tone[NUM_CHANNELS];
envelope_t m_envelope[NUM_CHANNELS];
u8 m_prescale_noise;
s32 m_count_noise;
s32 m_rng;
u8 m_mode;
u8 m_env_step_mask;
/* init parameters ... */
int m_step;
int m_zero_is_off;
u8 m_vol_enabled[NUM_CHANNELS];
const ay_ym_param *m_par;
const ay_ym_param *m_par_env;
s32 m_vol_table[NUM_CHANNELS][16];
s32 m_env_table[NUM_CHANNELS][32];
std::unique_ptr<s32[]> m_vol3d_table;
int m_flags; /* Flags */
int m_feature; /* Chip specific features */
int m_res_load[3]; /* Load on channel in ohms */
devcb_read8 m_port_a_read_cb;
devcb_read8 m_port_b_read_cb;
devcb_write8 m_port_a_write_cb;
devcb_write8 m_port_b_write_cb;
};
DECLARE_DEVICE_TYPE(AY8910, ay8910_device)
class ay8912_device : public ay8910_device
{
public:
ay8912_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
DECLARE_DEVICE_TYPE(AY8912, ay8912_device)
class ay8913_device : public ay8910_device
{
public:
ay8913_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
DECLARE_DEVICE_TYPE(AY8913, ay8913_device)
class ay8914_device : public ay8910_device
{
public:
ay8914_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
/* AY8914 handlers needed due to different register map */
u8 read(offs_t offset);
void write(offs_t offset, u8 data);
};
DECLARE_DEVICE_TYPE(AY8914, ay8914_device)
class ay8930_device : public ay8910_device
{
public:
ay8930_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
DECLARE_DEVICE_TYPE(AY8930, ay8930_device)
class ym2149_device : public ay8910_device
{
public:
ym2149_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
DECLARE_DEVICE_TYPE(YM2149, ym2149_device)
class ym3439_device : public ay8910_device
{
public:
ym3439_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
DECLARE_DEVICE_TYPE(YM3439, ym3439_device)
class ymz284_device : public ay8910_device
{
public:
ymz284_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
DECLARE_DEVICE_TYPE(YMZ284, ymz284_device)
class ymz294_device : public ay8910_device
{
public:
ymz294_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
DECLARE_DEVICE_TYPE(YMZ294, ymz294_device)
class sunsoft_5b_sound_device : public ay8910_device
{
public:
sunsoft_5b_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock);
};
DECLARE_DEVICE_TYPE(SUNSOFT_5B_SOUND, sunsoft_5b_sound_device)
#endif // MAME_DEVICES_SOUND_AY8910_H