diff --git a/src/emu/sound/sn76496.c b/src/emu/sound/sn76496.c index d92e50973c4..db7a4858cd2 100644 --- a/src/emu/sound/sn76496.c +++ b/src/emu/sound/sn76496.c @@ -111,7 +111,7 @@ * Test the NCR7496; Smspower says the whitenoise taps are A and E, but this needs verification on real hardware. * Factor out common code so that the SAA1099 can share some code. - * Convert to modern device + ***************************************************************************/ #include "emu.h" @@ -613,3 +613,301 @@ DEFINE_LEGACY_SOUND_DEVICE(SN94624, sn94624); DEFINE_LEGACY_SOUND_DEVICE(NCR7496, ncr7496); DEFINE_LEGACY_SOUND_DEVICE(GAMEGEAR, gamegear); DEFINE_LEGACY_SOUND_DEVICE(SEGAPSG, segapsg); + +/***************************************************************** + New class implementation + Michael Zapf, June 2012 +*****************************************************************/ + +sn76496_base_device::sn76496_base_device(const machine_config &mconfig, device_type type, const char *name, + const char *tag, int feedbackmask, int noisetap1, int noisetap2, bool negate, bool stereo, int clockdivider, int freq0, + device_t *owner, UINT32 clock) + + : device_t(mconfig, type, name, tag, owner, clock), + device_sound_interface(mconfig, *this), + m_feedback_mask(feedbackmask), + m_whitenoise_tap1(noisetap1), + m_whitenoise_tap2(noisetap2), + m_negate(negate), + m_stereo(stereo), + m_clock_divider(clockdivider), + m_freq0_is_max(freq0) +{ +} + +void sn76496_base_device::device_start() +{ + int sample_rate = clock()/2; + int i; + double out; + int gain; + + const sn76496_config *conf = reinterpret_cast(static_config()); + m_ready.resolve(conf->ready, *this); + + m_sound = machine().sound().stream_alloc(*this, 0, (m_stereo? 2:1), sample_rate, this); + + for (i = 0; i < 4; i++) m_volume[i] = 0; + + m_last_register = 0; + for (i = 0; i < 8; i+=2) + { + m_register[i] = 0; + m_register[i + 1] = 0x0f; // volume = 0 + } + + for (i = 0; i < 4; i++) + { + m_output[i] = 0; + m_period[i] = 0; + m_count[i] = 0; + } + + m_RNG = m_feedback_mask; + m_output[3] = m_RNG & 1; + + m_cycles_to_ready = 1; // assume ready is not active immediately on init. is this correct? + m_stereo_mask = 0xFF; // all channels enabled + m_current_clock = m_clock_divider-1; + + // set gain + gain = 0; + + gain &= 0xff; + + // increase max output basing on gain (0.2 dB per step) + out = MAX_OUTPUT / 4; // four channels, each gets 1/4 of the total range + while (gain-- > 0) + out *= 1.023292992; // = (10 ^ (0.2/20)) + + // build volume table (2dB per step) + for (i = 0; i < 15; i++) + { + // limit volume to avoid clipping + if (out > MAX_OUTPUT / 4) m_vol_table[i] = MAX_OUTPUT / 4; + else m_vol_table[i] = out; + + out /= 1.258925412; /* = 10 ^ (2/20) = 2dB */ + } + m_vol_table[15] = 0; + + m_ready_state = true; + + register_for_save_states(); +} + +READ_LINE_MEMBER( sn76496_base_device::ready_r ) +{ + m_sound->update(); + return (m_cycles_to_ready > 0)? FALSE : TRUE; +} + +WRITE8_MEMBER( sn76496_base_device::stereo_w ) +{ + m_sound->update(); + if (m_stereo) m_stereo_mask = data; + else fatalerror("sn76496_base_device: Call to stereo write with mono chip!\n"); +} + +WRITE8_MEMBER( sn76496_base_device::write ) +{ + int n, r, c; + + // update the output buffer before changing the registers + m_sound->update(); + + // set number of cycles until READY is active; this is always one + // 'sample', i.e. it equals the clock divider exactly; until the + // clock divider is fully supported, we delay until one sample has + // played. The fact that this below is '2' and not '1' is because + // of a ?race condition? in the mess crvision driver, where after + // any sample is played at all, no matter what, the cycles_to_ready + // ends up never being not ready, unless this value is greater than + // 1. Once the full clock divider stuff is written, this should no + // longer be an issue. + + m_cycles_to_ready = 2; + + if (data & 0x80) + { + r = (data & 0x70) >> 4; + m_last_register = r; + m_register[r] = (m_register[r] & 0x3f0) | (data & 0x0f); + } + else + { + r = m_last_register; + } + + c = r >> 1; + switch (r) + { + case 0: // tone 0: frequency + case 2: // tone 1: frequency + case 4: // tone 2: frequency + if ((data & 0x80) == 0) m_register[r] = (m_register[r] & 0x0f) | ((data & 0x3f) << 4); + if ((m_register[r] != 0) || (!m_freq0_is_max)) m_period[c] = m_register[r]; + else m_period[c] = 0x400; + + if (r == 4) + { + // update noise shift frequency + if ((m_register[6] & 0x03) == 0x03) m_period[3] = m_period[2]<<1; + } + break; + case 1: // tone 0: volume + case 3: // tone 1: volume + case 5: // tone 2: volume + case 7: // noise: volume + m_volume[c] = m_vol_table[data & 0x0f]; + if ((data & 0x80) == 0) m_register[r] = (m_register[r] & 0x3f0) | (data & 0x0f); + break; + case 6: // noise: frequency, mode + { + if ((data & 0x80) == 0) logerror("sn76496_base_device: write to reg 6 with bit 7 clear; data was %03x, new write is %02x! report this to LN!\n", m_register[6], data); + if ((data & 0x80) == 0) m_register[r] = (m_register[r] & 0x3f0) | (data & 0x0f); + n = m_register[6]; + // N/512,N/1024,N/2048,Tone #3 output + m_period[3] = ((n&3) == 3)? (m_period[2]<<1) : (1 << (5+(n&3))); + m_RNG = m_feedback_mask; + } + break; + } +} + +inline bool sn76496_base_device::in_noise_mode() +{ + return ((m_register[6] & 4)!=0); +} + +void sn76496_base_device::countdown_cycles() +{ + if (m_cycles_to_ready > 0) + { + m_cycles_to_ready--; + if (m_ready_state==true) m_ready(CLEAR_LINE); + m_ready_state = false; + } + else + { + if (m_ready_state==false) m_ready(ASSERT_LINE); + m_ready_state = true; + } +} + +void sn76496_base_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +{ + int i; + stream_sample_t *lbuffer = outputs[0]; + stream_sample_t *rbuffer = (m_stereo)? outputs[1] : NULL; + + INT16 out = 0; + INT16 out2 = 0; + + while (samples > 0) + { + // clock chip once + if (m_current_clock > 0) // not ready for new divided clock + { + m_current_clock--; + } + else // ready for new divided clock, make a new sample + { + m_current_clock = m_clock_divider-1; + // decrement Cycles to READY by one + countdown_cycles(); + + // handle channels 0,1,2 + for (i = 0; i < 3; i++) + { + m_count[i]--; + if (m_count[i] <= 0) + { + m_output[i] ^= 1; + m_count[i] = m_period[i]; + } + } + + // handle channel 3 + m_count[3]--; + if (m_count[3] <= 0) + { + // if noisemode is 1, both taps are enabled + // if noisemode is 0, the lower tap, whitenoisetap2, is held at 0 + // The != was a bit-XOR (^) before + if (((m_RNG & m_whitenoise_tap1)!=0) != (((m_RNG & m_whitenoise_tap2)!=0) && in_noise_mode())) + { + m_RNG >>= 1; + m_RNG |= m_feedback_mask; + } + else + { + m_RNG >>= 1; + } + m_output[3] = m_RNG & 1; + + m_count[3] = m_period[3]; + } + } + + if (m_stereo) + { + out = ((((m_stereo_mask & 0x10)!=0) && (m_output[0]!=0))? m_volume[0] : 0) + + ((((m_stereo_mask & 0x20)!=0) && (m_output[1]!=0))? m_volume[1] : 0) + + ((((m_stereo_mask & 0x40)!=0) && (m_output[2]!=0))? m_volume[2] : 0) + + ((((m_stereo_mask & 0x80)!=0) && (m_output[3]!=0))? m_volume[3] : 0); + + out2= ((((m_stereo_mask & 0x1)!=0) && (m_output[0]!=0))? m_volume[0] : 0) + + ((((m_stereo_mask & 0x2)!=0) && (m_output[1]!=0))? m_volume[1] : 0) + + ((((m_stereo_mask & 0x4)!=0) && (m_output[2]!=0))? m_volume[2] : 0) + + ((((m_stereo_mask & 0x8)!=0) && (m_output[3]!=0))? m_volume[3] : 0); + } + else + { + out= ((m_output[0]!=0)? m_volume[0]:0) + +((m_output[1]!=0)? m_volume[1]:0) + +((m_output[2]!=0)? m_volume[2]:0) + +((m_output[3]!=0)? m_volume[3]:0); + } + + if (m_negate) { out = -out; out2 = -out2; } + + *(lbuffer++) = out; + if (m_stereo) *(rbuffer++) = out2; + samples--; + } +} + +void sn76496_base_device::register_for_save_states() +{ + save_item(NAME(m_vol_table)); + save_item(NAME(m_register)); + save_item(NAME(m_last_register)); + save_item(NAME(m_volume)); + save_item(NAME(m_RNG)); +// save_item(NAME(m_clock_divider)); + save_item(NAME(m_current_clock)); +// save_item(NAME(m_feedback_mask)); +// save_item(NAME(m_whitenoise_tap1)); +// save_item(NAME(m_whitenoise_tap2)); +// save_item(NAME(m_negate)); +// save_item(NAME(m_stereo)); + save_item(NAME(m_stereo_mask)); + save_item(NAME(m_period)); + save_item(NAME(m_count)); + save_item(NAME(m_output)); + save_item(NAME(m_cycles_to_ready)); +// save_item(NAME(m_freq0_is_max)); +} + +const device_type SN76496N = &device_creator; +const device_type U8106N = &device_creator; +const device_type Y2404N = &device_creator; +const device_type SN76489N = &device_creator; +const device_type SN76489AN = &device_creator; +const device_type SN76494N = &device_creator; +const device_type SN94624N = &device_creator; +const device_type NCR7496N = &device_creator; +const device_type GAMEGEARN = &device_creator; +const device_type SEGAPSGN = &device_creator; + diff --git a/src/emu/sound/sn76496.h b/src/emu/sound/sn76496.h index 90bd4df3d8c..d94b7b95237 100644 --- a/src/emu/sound/sn76496.h +++ b/src/emu/sound/sn76496.h @@ -20,4 +20,161 @@ DECLARE_LEGACY_SOUND_DEVICE(NCR7496, ncr7496); DECLARE_LEGACY_SOUND_DEVICE(GAMEGEAR, gamegear); DECLARE_LEGACY_SOUND_DEVICE(SEGAPSG, segapsg); +/***************************************************************** + New class implementation + Michael Zapf, June 2012 +*****************************************************************/ + +extern const device_type SN76496N; +extern const device_type U8106N; +extern const device_type Y2404N; +extern const device_type SN76489N; +extern const device_type SN76489AN; +extern const device_type SN76494N; +extern const device_type SN94624N; +extern const device_type NCR7496N; +extern const device_type GAMEGEARN; +extern const device_type SEGAPSGN; + +typedef struct _sn76496_config +{ + devcb_write_line ready; +} sn76496_config; + +class sn76496_base_device : public device_t, public device_sound_interface +{ +public: + sn76496_base_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, + int feedbackmask, int noisetap1, int noisetap2, bool negate, bool stereo, int clockdivider, int freq0, + device_t *owner, UINT32 clock); + DECLARE_READ_LINE_MEMBER( ready_r ); + DECLARE_WRITE8_MEMBER( stereo_w ); + DECLARE_WRITE8_MEMBER( write ); + +protected: + void device_start(); + void sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples); + +private: + inline bool in_noise_mode(); + void register_for_save_states(); + void countdown_cycles(); + + bool m_ready_state; + + devcb_resolved_write_line m_ready; + + sound_stream* m_sound; + + const INT32 m_feedback_mask; // mask for feedback + const INT32 m_whitenoise_tap1; // mask for white noise tap 1 (higher one, usually bit 14) + const INT32 m_whitenoise_tap2; // mask for white noise tap 2 (lower one, usually bit 13) + const bool m_negate; // output negate flag + const bool m_stereo; // whether we're dealing with stereo or not + const INT32 m_clock_divider; // clock divider + const bool m_freq0_is_max; // flag for if frequency zero acts as if it is one more than max (0x3ff+1) or if it acts like 0 + + INT32 m_vol_table[16]; // volume table (for 4-bit to db conversion) + INT32 m_register[8]; // registers + INT32 m_last_register; // last register written + INT32 m_volume[4]; // db volume of voice 0-2 and noise + UINT32 m_RNG; // noise generator LFSR + INT32 m_current_clock; + INT32 m_stereo_mask; // the stereo output mask + INT32 m_period[4]; // Length of 1/2 of waveform + INT32 m_count[4]; // Position within the waveform + INT32 m_output[4]; // 1-bit output of each channel, pre-volume + INT32 m_cycles_to_ready; // number of cycles until the READY line goes active +}; + +// SN76496: Whitenoise verified, phase verified, periodic verified (by Michael Zapf) +class sn76496n_device : public sn76496_base_device +{ +public: + sn76496n_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, SN76496N, "SN76496N", tag, 0x10000, 0x04, 0x08, false, false, 8, true, owner, clock) + { } +}; + +// U8106 not verified yet. todo: verify; (a custom marked sn76489? only used on mr. do and maybe other universal games) +class u8106n_device : public sn76496_base_device +{ +public: + u8106n_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, U8106N, "U8106N", tag, 0x4000, 0x01, 0x02, true, false, 8, true, owner, clock) + { } +}; + +// Y2404 not verified yet. todo: verify; (don't be fooled by the Y, it's a TI chip, not Yamaha) +class y2404n_device : public sn76496_base_device +{ +public: + y2404n_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, Y2404N, "Y2404N", tag, 0x10000, 0x04, 0x08, false, false, 8, true, owner, clock) + { } +}; + +// SN76489 not verified yet. todo: verify; +class sn76489n_device : public sn76496_base_device +{ +public: + sn76489n_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, SN76489N, "SN76489N", tag, 0x4000, 0x01, 0x02, true, false, 8, true, owner, clock) + { } +}; + +// SN76489A: whitenoise verified, phase verified, periodic verified (by plgdavid) +class sn76489an_device : public sn76496_base_device +{ +public: + sn76489an_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, SN76489AN, "SN76489AN", tag, 0x10000, 0x04, 0x08, false, false, 8, true, owner, clock) + { } +}; + +// SN76494 not verified, (according to datasheet: same as sn76489a but without the /8 divider) +class sn76494n_device : public sn76496_base_device +{ +public: + sn76494n_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, SN76494N, "SN76494N", tag, 0x10000, 0x04, 0x08, false, false, 1, true, owner, clock) + { } +}; + +// SN94624 whitenoise verified, phase verified, period verified; verified by PlgDavid +class sn94624n_device : public sn76496_base_device +{ +public: + sn94624n_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, SN94624N, "SN94624N", tag, 0x4000, 0x01, 0x02, true, false, 1, true, owner, clock) + { } +}; + +// NCR7496 not verified; info from smspower wiki +class ncr7496n_device : public sn76496_base_device +{ +public: + ncr7496n_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, NCR7496N, "NCR7496N", tag, 0x8000, 0x02, 0x20, false, false, 8, true, owner, clock) + { } +}; + +// Verified by Justin Kerk +class gamegearn_device : public sn76496_base_device +{ +public: + gamegearn_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, GAMEGEARN, "Game Gear PSGN", tag, 0x8000, 0x01, 0x08, true, true, 8, false, owner, clock) + { } +}; + +// todo: verify; from smspower wiki, assumed to have same invert as gamegear +class segapsgn_device : public sn76496_base_device +{ +public: + segapsgn_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : sn76496_base_device(mconfig, SEGAPSGN, "SEGA VDP PSGN", tag, 0x8000, 0x01, 0x08, true, false, 8, false, owner, clock) + { } +}; + #endif /* __SN76496_H__ */