diff options
Diffstat (limited to 'trunk/src/mame/audio/beezer.c')
-rw-r--r-- | trunk/src/mame/audio/beezer.c | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/trunk/src/mame/audio/beezer.c b/trunk/src/mame/audio/beezer.c new file mode 100644 index 00000000000..208cd606c9b --- /dev/null +++ b/trunk/src/mame/audio/beezer.c @@ -0,0 +1,576 @@ +/************************************************************************* + + Beezer 6522+6840 audio hardware + driver based on exidy audio hardware which it shares some similarity to + Original driver by ? (Nicola or Aaron, I think) + Preliminary, partially functional driver by Jonathan Gevaryahu + AKA Lord Nightmare + + TODO: + * Several inexplicable things on the schematic are not handled, such as the + 'VCO' input for 6840 channel 2 external clock whose source does not appear + anywhere on the schematic, nor does it handle the 'DMOD DATA' and 'DMOD + CLR' outputs for some sort of digital modulator (perhaps an hc55516?) + from the VIA, which also does not appear anywhere on schematic. + The latter the VIA *DOES* seem to write something to, but it may be just + a silence waveform for 55516, alternating zeroes and ones. + * The channel mixing is done additively at the moment rather than + emulating the original multiplexer, which is actually not that hard to do + but adds a bit of complexity to the render loop. + * The 'FM OR AM' output of the audio via (pb1) appears to control some sort + of suppression or filtering change of the post-DAC amplifier when enabled, + only during the TIMER1 OUT time-slot of the multiplexer, see page 1B 3-3 + of schematics. This will be a MESS to emulate since theres a lot of analog + crap involved. + * The /INT line and related logic of the 6840 is not emulated, and should + be hooked to the audio 6809. + * Convert this to a modern device instead of a deprecated old style device + + + Notes on multiplexer: + The sound output is from a DAC76xx 8-bit dac; driving this dac is a pair + of 74ls670 4x4 register files wired up as four 8-bit words; + The four words are used as the volumes for four 1-bit channels by inversion + of the MSB of the 8 bit value. + NOISE is the output of an MM5837 whitenoise generator, a self-clocked (at + around 100khz) LFSR with taps on bits (base-0) 16 and 13. + The four channels are: + CNT1 CNT0 + 0 0 6522 pin 7 output (squarewave); 'FM or AM' affects this slot only + 0 1 6840 channel 1 clocked by E1(int) OR by 6522 PB7-latched NOISE + 1 0 6840 channel 2 clocked by E1(int) OR by "VCO" ext (Huh?) + 1 1 6840 channel 3 clocked by E1(int) OR by channel 2-latched NOISE + + The four slots determine which address is selected of the 8-bit words and + which source will XOR against the MSB of the 8-bit word before it goes + to the DAC (effectively making the 8-bit word be a 7-bit volume control + plus optional wave invert). The speed which cnt0 and cnt1 count at is + E1/16 or 62500Hz. + +*************************************************************************/ + +#include "emu.h" +#include "machine/rescap.h" +#include "cpu/m6809/m6809.h" +#include "includes/beezer.h" + + + +/************************************* + * + * Constants + * + *************************************/ + +#define CRYSTAL_OSC (XTAL_12MHz) +#define SH6840_CLOCK (CRYSTAL_OSC / 12) +#define MULTIPLEX_FREQ (SH6840_CLOCK / 16) + +/************************************* + * + * Local variables + * + *************************************/ + +/* 6840 variables */ +struct sh6840_timer_channel +{ + UINT8 cr; + UINT8 state; + UINT8 leftovers; + UINT16 timer; + UINT32 clocks; + UINT8 int_flag; + union + { +#ifdef LSB_FIRST + struct { UINT8 l, h; } b; +#else + struct { UINT8 h, l; } b; +#endif + UINT16 w; + } counter; +}; + +typedef struct _beezer_sound_state beezer_sound_state; +struct _beezer_sound_state +{ + device_t *m_maincpu; + + /* IRQ variable */ + UINT8 m_ptm_irq_state; + + struct sh6840_timer_channel m_sh6840_timer[3]; + UINT8 m_sh6840_volume[4]; + UINT8 m_sh6840_MSB_latch; + UINT8 m_sh6840_LSB_latch; + UINT32 m_sh6840_LFSR; + UINT32 m_sh6840_LFSR_clocks; + UINT32 m_sh6840_clocks_per_sample; + UINT32 m_sh6840_clock_count; + + UINT32 m_sh6840_latchwrite; + UINT32 m_sh6840_latchwriteold; + UINT32 m_sh6840_noiselatch1; + UINT32 m_sh6840_noiselatch3; + + /* sound streaming variables */ + sound_stream *m_stream; + double m_freq_to_step; + +}; + + +INLINE beezer_sound_state *get_safe_token(device_t *device) +{ + assert(device != NULL); + assert(device->type() == BEEZER); + + return (beezer_sound_state *)downcast<legacy_device_base *>(device)->token(); +} + +/************************************* + * + * Interrupt generation helper + * TODO: make this actually do something useful + *************************************/ + +/*static WRITE_LINE_DEVICE_HANDLER( update_irq_state ) +{ + beezer_sound_state *sndstate = get_safe_token(device); + cputag_set_input_line(device->machine(), "audiocpu", M6809_IRQ_LINE, (sndstate->ptm_irq_state) ? ASSERT_LINE : CLEAR_LINE); +}*/ + + + +/************************************* + * + * 6840 clock counting helper + * + *************************************/ +// need to set int_flag properly here +INLINE void sh6840_apply_clock(struct sh6840_timer_channel *t, int clocks) +{ + /* dual 8-bit case */ + if (t->cr & 0x04) + { + /* handle full decrements */ + while (clocks > t->counter.b.l) + { + clocks -= t->counter.b.l + 1; + t->counter.b.l = t->timer; + + /* decrement MSB */ + if (!t->counter.b.h--) + { + t->state = 0; + t->counter.w = t->timer; + } + + /* state goes high when MSB is 0 */ + else if (!t->counter.b.h) + { + t->state = 1; + t->clocks++; + } + } + + /* subtract off the remainder */ + t->counter.b.l -= clocks; + } + + /* 16-bit case */ + else + { + /* handle full decrements */ + while (clocks > t->counter.w) + { + clocks -= t->counter.w + 1; + t->state ^= 1; + t->clocks += t->state; + t->counter.w = t->timer; + } + + /* subtract off the remainder */ + t->counter.w -= clocks; + } +} + + + +/************************************* + * + * Noise generation helper + * + *************************************/ + +INLINE int sh6840_update_noise(beezer_sound_state *state, int clocks) +{ + UINT32 newxor; + int noise_clocks = 0; + int i; + + /* loop over clocks */ + for (i = 0; i < clocks; i++) + { + state->m_sh6840_LFSR_clocks++; + if (state->m_sh6840_LFSR_clocks >= 10) // about 10 clocks per 6840 clock, as MM5837 runs at around 100kHz, while clock is 1MHz + { + state->m_sh6840_LFSR_clocks = 0; + /* shift the LFSR. finally or in the result and see if we've + * had a 0->1 transition */ + newxor = (((state->m_sh6840_LFSR&0x10000)?1:0) ^ ((state->m_sh6840_LFSR&0x2000)?1:0))?1:0; + state->m_sh6840_LFSR <<= 1; + state->m_sh6840_LFSR |= newxor; + /*printf("LFSR: %4x, %4x, %4x, %4x\n", sh6840_LFSR_3, sh6840_LFSR_2, sh6840_LFSR_1, sh6840_LFSR_0);*/ + /* if we clocked 0->1, that will serve as an external clock */ + if ((state->m_sh6840_LFSR & 0x01) == 0x01) /* tap is at bit 0, GUESSED */ + { + noise_clocks++; + } + } + } + return noise_clocks; +} + + + +/************************************* + * + * 6840 state saving + * + *************************************/ + +static void sh6840_register_state_globals(device_t *device) +{ + beezer_sound_state *state = get_safe_token(device); + + device->save_item(NAME(state->m_sh6840_volume)); + device->save_item(NAME(state->m_sh6840_MSB_latch)); + device->save_item(NAME(state->m_sh6840_LSB_latch)); + device->save_item(NAME(state->m_sh6840_LFSR)); + device->save_item(NAME(state->m_sh6840_LFSR_clocks)); + device->save_item(NAME(state->m_sh6840_clock_count)); + device->save_item(NAME(state->m_sh6840_latchwrite)); + device->save_item(NAME(state->m_sh6840_latchwriteold)); + device->save_item(NAME(state->m_sh6840_noiselatch1)); + device->save_item(NAME(state->m_sh6840_noiselatch3)); + device->save_item(NAME(state->m_sh6840_timer[0].cr)); + device->save_item(NAME(state->m_sh6840_timer[0].state)); + device->save_item(NAME(state->m_sh6840_timer[0].leftovers)); + device->save_item(NAME(state->m_sh6840_timer[0].timer)); + device->save_item(NAME(state->m_sh6840_timer[0].clocks)); + device->save_item(NAME(state->m_sh6840_timer[0].counter.w)); + device->save_item(NAME(state->m_sh6840_timer[1].cr)); + device->save_item(NAME(state->m_sh6840_timer[1].state)); + device->save_item(NAME(state->m_sh6840_timer[1].leftovers)); + device->save_item(NAME(state->m_sh6840_timer[1].timer)); + device->save_item(NAME(state->m_sh6840_timer[1].clocks)); + device->save_item(NAME(state->m_sh6840_timer[1].counter.w)); + device->save_item(NAME(state->m_sh6840_timer[2].cr)); + device->save_item(NAME(state->m_sh6840_timer[2].state)); + device->save_item(NAME(state->m_sh6840_timer[2].leftovers)); + device->save_item(NAME(state->m_sh6840_timer[2].timer)); + device->save_item(NAME(state->m_sh6840_timer[2].clocks)); + device->save_item(NAME(state->m_sh6840_timer[2].counter.w)); +} + + + +/************************************* + * + * Core sound generation + * + *************************************/ + +static STREAM_UPDATE( beezer_stream_update ) +{ + beezer_sound_state *state = get_safe_token(device); + struct sh6840_timer_channel *sh6840_timer = state->m_sh6840_timer; + + /* hack to skip the expensive lfsr noise generation unless at least one of the 2 channels which actually depend on it are set to use it as a source */ + int noisy = ((sh6840_timer[0].cr & sh6840_timer[2].cr & 0x02) == 0); + stream_sample_t *buffer = outputs[0]; + + /* loop over samples */ + while (samples--) + { + struct sh6840_timer_channel *t; + int clocks_this_sample; + int clocks; + INT16 sample1, sample2, sample3, sample0; + INT16 sample = 0; + sample1 = sample2 = sample3 = sample0 = 0; + + /* determine how many 6840 clocks this sample */ + state->m_sh6840_clock_count += state->m_sh6840_clocks_per_sample; + clocks_this_sample = state->m_sh6840_clock_count >> 24; + state->m_sh6840_clock_count &= (1 << 24) - 1; + + /* skip if nothing enabled */ + if ((sh6840_timer[0].cr & 0x01) == 0) // if we're not in reset... + { +// int noise_clocks_this_sample = 0; + UINT32 chan1_clocks; + + /* generate noise if configured to do so */ + if (noisy != 0) + sh6840_update_noise(state, clocks_this_sample); + + /* handle timer 0 if enabled */ + t = &sh6840_timer[0]; + clocks = (t->cr & 0x02) ? clocks_this_sample : state->m_sh6840_noiselatch1; + sh6840_apply_clock(t, clocks); + sample1 = (t->state && (t->cr & 0x80))?1:0; + + /* handle timer 1 if enabled */ + t = &sh6840_timer[1]; + chan1_clocks = t->clocks; + clocks = (t->cr & 0x02) ? clocks_this_sample : 0; // TODO: this is WRONG: channel 1 is clocked by a mystery "VCO CLOCK" signal if not set to E clock. it may not even be connected to anything! + sh6840_apply_clock(t, clocks); + sample2 = (t->state && (t->cr & 0x80))?1:0; + + /* generate channel 1-clocked noise if configured to do so */ + if (noisy != 0) + { + sh6840_update_noise(state, t->clocks - chan1_clocks); + if (clocks) state->m_sh6840_noiselatch3 = (state->m_sh6840_LFSR&0x1); + } + + /* handle timer 2 if enabled */ + t = &sh6840_timer[2]; + clocks = (t->cr & 0x02) ? clocks_this_sample : state->m_sh6840_noiselatch3; + /* prescale */ + if (t->cr & 0x01) + { + clocks += t->leftovers; + t->leftovers = clocks % 8; + clocks /= 8; + } + sh6840_apply_clock(t, clocks); + sample3 = (t->state && (t->cr & 0x80))?1:0; + } + sample0 = state->m_sh6840_latchwrite?1:0; + + /* stash */ + /* each sample feeds an xor bit on the sign bit of a sign-magnitude (NOT 2'S COMPLEMENT) + * DAC. This requires some rather convoluted processing: + * samplex*0x80 brings the sample to the sign bit + * state->m_sh6840_volume[x]&0x80 pulls the sign bit from the dac sample + * state->m_sh6840_volume[x]&0x7F pulls the magnitude from the dac sample + */ + sample += (((sample0*0x80)^(state->m_sh6840_volume[0]&0x80))?-1:1)*(state->m_sh6840_volume[0]&0x7F); + sample += (((sample1*0x80)^(state->m_sh6840_volume[1]&0x80))?-1:1)*(state->m_sh6840_volume[1]&0x7F); + sample += (((sample2*0x80)^(state->m_sh6840_volume[2]&0x80))?-1:1)*(state->m_sh6840_volume[2]&0x7F); + sample += (((sample3*0x80)^(state->m_sh6840_volume[3]&0x80))?-1:1)*(state->m_sh6840_volume[3]&0x7F); + *buffer++ = sample*64; // adding 4 numbers ranging from -128 to 127 yields a range of -512 to 508; to scale that to '-32768 to 32767' we multiply by 64 + } +} + + + +/************************************* + * + * Audio startup routines + * + *************************************/ + +static DEVICE_START( common_sh_start ) +{ + beezer_sound_state *state = get_safe_token(device); + int sample_rate = MULTIPLEX_FREQ; + + state->m_sh6840_clocks_per_sample = (int)(((double)SH6840_CLOCK / (double)sample_rate) * (double)(1 << 24)); + + /* allocate the stream */ + state->m_stream = device->machine().sound().stream_alloc(*device, 0, 1, sample_rate, NULL, beezer_stream_update); + state->m_maincpu = device->machine().device("maincpu"); + + sh6840_register_state_globals(device); +} + +static DEVICE_START( beezer_sound ) +{ + //beezer_sound_state *state = get_safe_token(device); + DEVICE_START_CALL(common_sh_start); +} + +DEFINE_LEGACY_SOUND_DEVICE(BEEZER, beezer_sound); + +/************************************* + * + * Audio reset routines + * + *************************************/ + +static DEVICE_RESET( common_sh_reset ) +{ + beezer_sound_state *state = get_safe_token(device); + + /* 6840 */ + memset(state->m_sh6840_timer, 0, sizeof(state->m_sh6840_timer)); + state->m_sh6840_MSB_latch = 0; + state->m_sh6840_LSB_latch = 0; + state->m_sh6840_volume[0] = 0; + state->m_sh6840_volume[1] = 0; + state->m_sh6840_volume[2] = 0; + state->m_sh6840_volume[3] = 0; + state->m_sh6840_clock_count = 0; + state->m_sh6840_latchwrite = 0; + state->m_sh6840_latchwriteold = 0; + state->m_sh6840_noiselatch1 = 0; + state->m_sh6840_noiselatch3 = 0; + + /* LFSR */ + state->m_sh6840_LFSR = 0xffffffff; + state->m_sh6840_LFSR_clocks = 0; +} + +static DEVICE_RESET( beezer_sound ) +{ + DEVICE_RESET_CALL(common_sh_reset); +} + + +DEVICE_GET_INFO( beezer_sound ) +{ + switch (state) + { + /* --- the following bits of info are returned as 64-bit signed integers --- */ + case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(beezer_sound_state); break; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME(beezer_sound); break; + case DEVINFO_FCT_RESET: info->reset = DEVICE_RESET_NAME(beezer_sound); break; + + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case DEVINFO_STR_NAME: strcpy(info->s, "beezer SFX"); break; + case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; + } +} + +/************************************* + * + * 6840 timer handlers + * + *************************************/ + +READ8_DEVICE_HANDLER( beezer_sh6840_r ) +{ + beezer_sound_state *state = get_safe_token(device); + + /* force an update of the stream */ + state->m_stream->update(); + + switch (offset) + { + /* offset 0: Motorola datasheet says it isn't used, Hitachi datasheet says it reads as 0s always*/ + case 0: + return 0; + /* offset 1 reads the status register: bits 2 1 0 correspond to ints on channels 2,1,0, and bit 7 is an 'OR' of bits 2,1,0 */ + case 1: + logerror("%04X:beezer_sh6840_r - unexpected read, status register is TODO!\n", cpu_get_pc(state->m_maincpu)); + return 0; + /* offsets 2,4,6 read channel 0,1,2 MSBs and latch the LSB*/ + case 2: case 4: case 6: + state->m_sh6840_LSB_latch = state->m_sh6840_timer[((offset<<1)-1)].counter.b.l; + return state->m_sh6840_timer[((offset<<1)-1)].counter.b.h; + /* offsets 3,5,7 read the LSB latch*/ + default: /* case 3,5,7 */ + return state->m_sh6840_LSB_latch; + } +} + +WRITE8_DEVICE_HANDLER( beezer_timer1_w ) +{ + beezer_sound_state *state = get_safe_token(device); + + /* force an update of the stream */ + state->m_stream->update(); + state->m_sh6840_latchwriteold = state->m_sh6840_latchwrite; + state->m_sh6840_latchwrite = data&0x80; + if ((!state->m_sh6840_latchwriteold) && (state->m_sh6840_latchwrite)) // rising edge + { + state->m_sh6840_noiselatch1 = (state->m_sh6840_LFSR&0x1); + } +} + +READ8_DEVICE_HANDLER( beezer_noise_r ) +{ + beezer_sound_state *state = get_safe_token(device); + + /* force an update of the stream */ + state->m_stream->update(); + return (state->m_sh6840_LFSR&0x1); +} + +WRITE8_DEVICE_HANDLER( beezer_sh6840_w ) +{ + beezer_sound_state *state = get_safe_token(device); + struct sh6840_timer_channel *sh6840_timer = state->m_sh6840_timer; + + /* force an update of the stream */ + state->m_stream->update(); + + switch (offset) + { + /* offset 0 writes to either channel 0 control or channel 2 control */ + case 0: + if (sh6840_timer[1].cr & 0x01) + sh6840_timer[0].cr = data; + else + sh6840_timer[2].cr = data; + + /* only support mode 0 and 2 */ + if (((data >> 3) & 5) != 0) + fprintf(stderr,"beezer_sh6840_w - channel %d configured for mode %d (write was %02x to offset 0)", (sh6840_timer[1].cr & 0x01) ? 0 : 2, (data >> 3) & 7, data); + break; + + /* offset 1 writes to channel 1 control */ + case 1: + sh6840_timer[1].cr = data; + + /* only support mode 0 and 2 */ + if (((data >> 3) & 5) != 0) + fprintf(stderr,"beezer_sh6840_w - channel 1 configured for mode %d (write was %02x to offset 1)", (data >> 3) & 7, data); + break; + + /* offsets 2/4/6 write to the common MSB latch */ + case 2: + case 4: + case 6: + state->m_sh6840_MSB_latch = data; + break; + + /* offsets 3/5/7 write to the LSB controls */ + case 3: + case 5: + case 7: + { + /* latch the timer value */ + int ch = (offset - 3) / 2; + sh6840_timer[ch].timer = (state->m_sh6840_MSB_latch << 8) | (data & 0xff); + + /* if CR4 is clear, the value is loaded immediately */ + if (!(sh6840_timer[ch].cr & 0x10)) + sh6840_timer[ch].counter.w = sh6840_timer[ch].timer; + break; + } + } +} + + + +/************************************* + * + * DAC write handler + * + *************************************/ + +WRITE8_DEVICE_HANDLER( beezer_sfxctrl_w ) +{ + beezer_sound_state *state = get_safe_token(device); + state->m_stream->update(); + state->m_sh6840_volume[offset] = data; +} + |