/****************************************************************************** PeT mess@utanet.at 2000,2001 ******************************************************************************/ #include #include #include "driver.h" #include "mame.h" #include "streams.h" #include "timer.h" #include "includes/lynx.h" /* accordingly to atari's reference manual there were no stereo lynx produced (the manual knows only production until mid 1991) the howard/developement board might have stereo support the revised lynx 2 hardware might have stereo support at least at the stereo jacks some games support stereo */ /* AUDIO_A EQU $FD20 AUDIO_B EQU $FD28 AUDIO_C EQU $FD30 AUDIO_D EQU $FD38 VOLUME_CNTRL EQU 0 FEEDBACK_ENABLE EQU 1 ; enables 11/10/5..0 OUTPUT_VALUE EQU 2 SHIFTER_L EQU 3 AUD_BAKUP EQU 4 AUD_CNTRL1 EQU 5 AUD_COUNT EQU 6 AUD_CNTRL2 EQU 7 ; AUD_CNTRL1 FEEDBACK_7 EQU %10000000 AUD_RESETDONE EQU %01000000 AUD_INTEGRATE EQU %00100000 AUD_RELOAD EQU %00010000 AUD_CNTEN EQU %00001000 AUD_LINK EQU %00000111 ; link timers (0->2->4 / 1->3->5->7->Aud0->Aud1->Aud2->Aud3->1 AUD_64us EQU %00000110 AUD_32us EQU %00000101 AUD_16us EQU %00000100 AUD_8us EQU %00000011 AUD_4us EQU %00000010 AUD_2us EQU %00000001 AUD_1us EQU %00000000 ; AUD_CNTRL2 (read only) ; B7..B4 ; shifter bits 11..8 ; B3 ; who knows ; B2 ; last clock state (0->1 causes count) ; B1 ; borrow in (1 causes count) ; B0 ; borrow out (count EQU 0 and borrow in) ATTEN_A EQU $FD40 ATTEN_B EQU $FD41 ATTEN_C EQU $FD42 ATTEN_D EQU $FD43 ; B7..B4 attenuation left ear (0 silent ..15/16 volume) ; B3..B0 " right ear MPAN EQU $FD44 ; B7..B4 left ear ; B3..B0 right ear ; B7/B3 EQU Audio D ; a 1 enables attenuation for channel and side MSTEREO EQU $FD50 ; a 1 disables audio connection AUD_D_LEFT EQU %10000000 AUD_C_LEFT EQU %01000000 AUD_B_LEFT EQU %00100000 AUD_A_LEFT EQU %00010000 AUD_D_RIGHT EQU %00001000 AUD_C_RIGHT EQU %00000100 AUD_B_RIGHT EQU %00000010 AUD_A_RIGHT EQU %00000001 */ static sound_stream *mixer_channel; static int usec_per_sample; static int *shift_mask; static int *shift_xor; typedef struct { int nr; union { UINT8 data[8]; struct { UINT8 volume; UINT8 feedback; INT8 output; UINT8 shifter; UINT8 bakup; UINT8 control1; UINT8 counter; UINT8 control2; } n; } reg; UINT8 attenuation; int mask; int shifter; int ticks; int count; } LYNX_AUDIO; static LYNX_AUDIO lynx_audio[4]= { { 0 }, { 1 }, { 2 }, { 3 } }; static void lynx_audio_reset_channel(LYNX_AUDIO *This) { memset(This->reg.data, 0, (char*)(This+1)-(char*)(This->reg.data)); } void lynx_audio_count_down(int nr) { LYNX_AUDIO *This=lynx_audio+nr; if (This->reg.n.control1&8 && (This->reg.n.control1&7)!=7) return; if (nr==0) stream_update(mixer_channel); This->count--; } static void lynx_audio_shift(LYNX_AUDIO *channel) { channel->shifter=((channel->shifter<<1)&0x3ff) |shift_xor[channel->shifter&channel->mask]; if (channel->reg.n.control1&0x20) { if (channel->shifter&1) { channel->reg.n.output+=channel->reg.n.volume; } else { channel->reg.n.output-=channel->reg.n.volume; } } switch (channel->nr) { case 0: lynx_audio_count_down(1); break; case 1: lynx_audio_count_down(2); break; case 2: lynx_audio_count_down(3); break; case 3: lynx_timer_count_down(1); break; } } static void lynx_audio_execute(LYNX_AUDIO *channel) { if (channel->reg.n.control1&8) { // count_enable channel->ticks+=usec_per_sample; if ((channel->reg.n.control1&7)==7) { // timer input if (channel->count<0) { channel->count+=channel->reg.n.counter; lynx_audio_shift(channel); } } else { int t=1<<(channel->reg.n.control1&7); for (;;) { for (;(channel->ticks>=t)&&channel->count>=0; channel->ticks-=t) channel->count--; if (channel->tickscount<0) { channel->count=channel->reg.n.counter; lynx_audio_shift(channel); } } } if (!(channel->reg.n.control1&0x20)) { channel->reg.n.output=channel->shifter&1?0-channel->reg.n.volume:channel->reg.n.volume; } } else { channel->ticks=0; channel->count=0; } } static UINT8 attenuation_enable; static UINT8 master_enable; UINT8 lynx_audio_read(int offset) { UINT8 data=0; stream_update(mixer_channel); switch (offset) { case 0x20: case 0x21: case 0x22: case 0x24: case 0x25: case 0x28: case 0x29: case 0x2a: case 0x2c: case 0x2d: case 0x30: case 0x31: case 0x32: case 0x34: case 0x35: case 0x38: case 0x39: case 0x3a: case 0x3c: case 0x3d: data=lynx_audio[(offset>>3)&3].reg.data[offset&7]; break; case 0x23: case 0x2b: case 0x33: case 0x3b: data=lynx_audio[(offset>>3)&3].shifter&0xff; break; case 0x26:case 0x2e:case 0x36:case 0x3e: data=lynx_audio[(offset>>3)&3].count; break; case 0x27: case 0x2f: case 0x37: case 0x3f: data=(lynx_audio[(offset>>3)&3].shifter>>4)&0xf0; data|=lynx_audio[(offset>>3)&3].reg.data[offset&7]&0x0f; break; case 0x40: case 0x41: case 0x42: case 0x43: data=lynx_audio[offset&3].attenuation; break; case 0x44: data=attenuation_enable; break; case 0x50: data=master_enable; break; } return data; } void lynx_audio_write(int offset, UINT8 data) { // logerror("%.6f audio write %.2x %.2x\n", mame_timer_get_time(), offset, data); LYNX_AUDIO *channel=lynx_audio+((offset>>3)&3); stream_update(mixer_channel); switch (offset) { case 0x20: case 0x22: case 0x24: case 0x26: case 0x28: case 0x2a: case 0x2c: case 0x2e: case 0x30: case 0x32: case 0x34: case 0x36: case 0x38: case 0x3a: case 0x3c: case 0x3e: lynx_audio[(offset>>3)&3].reg.data[offset&7]=data; break; case 0x23: case 0x2b: case 0x33: case 0x3b: lynx_audio[(offset>>3)&3].reg.data[offset&7]=data; lynx_audio[(offset>>3)&3].shifter&=~0xff; lynx_audio[(offset>>3)&3].shifter|=data; break; case 0x27: case 0x2f: case 0x37: case 0x3f: lynx_audio[(offset>>3)&3].reg.data[offset&7]=data; lynx_audio[(offset>>3)&3].shifter&=~0xf00; lynx_audio[(offset>>3)&3].shifter|=(data&0xf0)<<4; break; case 0x21: case 0x25: case 0x29: case 0x2d: case 0x31: case 0x35: case 0x39: case 0x3d: channel->reg.data[offset&7]=data; channel->mask=channel->reg.n.feedback; channel->mask|=(channel->reg.data[5]&0x80)<<1; break; case 0x40: case 0x41: case 0x42: case 0x43: // lynx2 only, howard extension board lynx_audio[offset&3].attenuation=data; break; case 0x44: attenuation_enable=data; //lynx2 only, howard extension board break; case 0x50: master_enable=data;//lynx2 only, howard write only break; } } /************************************/ /* Sound handler update */ /************************************/ static void lynx_update (void *param,stream_sample_t **inputs, stream_sample_t **_buffer,int length) { int i, j; LYNX_AUDIO *channel; int v; stream_sample_t *buffer = _buffer[0]; for (i = 0; i < length; i++, buffer++) { *buffer = 0; for (channel=lynx_audio, j=0; jreg.n.output; *buffer+=v*15; } } } static void lynx2_update (void *param,stream_sample_t **inputs, stream_sample_t **buffer,int length) { stream_sample_t *left=buffer[0], *right=buffer[1]; int i, j; LYNX_AUDIO *channel; int v; for (i = 0; i < length; i++, left++, right++) { *left = 0; *right= 0; for (channel=lynx_audio, j=0; jreg.n.output; if (!(master_enable&(0x10<attenuation>>4); } else { *left+=v*15; } } if (!(master_enable&(1<attenuation&0xf); } else { *right+=v*15; } } } } } static void lynx_audio_init(void) { int i; shift_mask = (int *) auto_malloc(512 * sizeof(int)); assert(shift_mask); shift_xor = (int *) auto_malloc(4096 * sizeof(int)); assert(shift_xor); for (i=0; i<512; i++) { shift_mask[i]=0; if (i&1) shift_mask[i]|=1; if (i&2) shift_mask[i]|=2; if (i&4) shift_mask[i]|=4; if (i&8) shift_mask[i]|=8; if (i&0x10) shift_mask[i]|=0x10; if (i&0x20) shift_mask[i]|=0x20; if (i&0x40) shift_mask[i]|=0x400; if (i&0x80) shift_mask[i]|=0x800; if (i&0x100) shift_mask[i]|=0x80; } for (i=0; i<4096; i++) { int j; shift_xor[i]=1; for (j=4096/2; j>0; j>>=1) { if (i & j) shift_xor[i] ^= 1; } } } void lynx_audio_reset(void) { int i; for (i=0; isample_rate, 0, lynx_update); usec_per_sample = 1000000 / Machine->sample_rate; lynx_audio_init(); return (void *) ~0; } void *lynx2_custom_start(int clock, const struct CustomSound_interface *config) { mixer_channel = stream_create(0, 2, Machine->sample_rate, 0, lynx2_update); usec_per_sample = 1000000 / Machine->sample_rate; lynx_audio_init(); return (void *) ~0; }