diff options
Diffstat (limited to 'src/emu/sound/pokey.c')
-rw-r--r-- | src/emu/sound/pokey.c | 1599 |
1 files changed, 1599 insertions, 0 deletions
diff --git a/src/emu/sound/pokey.c b/src/emu/sound/pokey.c new file mode 100644 index 00000000000..fcb1b097aaf --- /dev/null +++ b/src/emu/sound/pokey.c @@ -0,0 +1,1599 @@ +/***************************************************************************** + * + * POKEY chip emulator 4.51 + * Copyright (c) 2000-2007 by The MAME Team + * + * Based on original info found in Ron Fries' Pokey emulator, + * with additions by Brad Oliver, Eric Smith and Juergen Buchmueller, + * paddle (a/d conversion) details from the Atari 400/800 Hardware Manual. + * Polynome algorithms according to info supplied by Perry McFarlane. + * + * This code is subject to the MAME license, which besides other + * things means it is distributed as is, no warranties whatsoever. + * For more details read mame.txt that comes with MAME. + * + * 4.51: + * - changed to use the attotime datatype + * 4.5: + * - changed the 9/17 bit polynomial formulas such that the values + * required for the Tempest Pokey protection will be found. + * Tempest expects the upper 4 bits of the RNG to appear in the + * lower 4 bits after four cycles, so there has to be a shift + * of 1 per cycle (which was not the case before). Bits #6-#13 of the + * new RNG give this expected result now, bits #0-7 of the 9 bit poly. + * - reading the RNG returns the shift register contents ^ 0xff. + * That way resetting the Pokey with SKCTL (which resets the + * polynome shifters to 0) returns the expected 0xff value. + * 4.4: + * - reversed sample values to make OFF channels produce a zero signal. + * actually de-reversed them; don't remember that I reversed them ;-/ + * 4.3: + * - for POT inputs returning zero, immediately assert the ALLPOT + * bit after POTGO is written, otherwise start trigger timer + * depending on SK_PADDLE mode, either 1-228 scanlines or 1-2 + * scanlines, depending on the SK_PADDLE bit of SKCTL. + * 4.2: + * - half volume for channels which are inaudible (this should be + * close to the real thing). + * 4.1: + * - default gain increased to closely match the old code. + * - random numbers repeat rate depends on POLY9 flag too! + * - verified sound output with many, many Atari 800 games, + * including the SUPPRESS_INAUDIBLE optimizations. + * 4.0: + * - rewritten from scratch. + * - 16bit stream interface. + * - serout ready/complete delayed interrupts. + * - reworked pot analog/digital conversion timing. + * - optional non-indexing pokey update functions. + * + *****************************************************************************/ + +#include "sndintrf.h" +#include "streams.h" +#include "cpuintrf.h" +#include "pokey.h" + +/* + * Defining this produces much more (about twice as much) + * but also more efficient code. Ideally this should be set + * for processors with big code cache and for healthy compilers :) + */ +#ifndef BIG_SWITCH +#ifndef HEAVY_MACRO_USAGE +#define HEAVY_MACRO_USAGE 1 +#endif +#else +#define HEAVY_MACRO_USAGE BIG_SWITCH +#endif + +#define SUPPRESS_INAUDIBLE 1 + +/* Four channels with a range of 0..32767 and volume 0..15 */ +//#define POKEY_DEFAULT_GAIN (32767/15/4) + +/* + * But we raise the gain and risk clipping, the old Pokey did + * this too. It defined POKEY_DEFAULT_GAIN 6 and this was + * 6 * 15 * 4 = 360, 360/256 = 1.40625 + * I use 15/11 = 1.3636, so this is a little lower. + */ +#define POKEY_DEFAULT_GAIN (32767/11/4) + +#define VERBOSE 0 +#define VERBOSE_SOUND 0 +#define VERBOSE_TIMER 0 +#define VERBOSE_POLY 0 +#define VERBOSE_RAND 0 + +#if VERBOSE +#define LOG(x) logerror x +#else +#define LOG(x) +#endif + +#if VERBOSE_SOUND +#define LOG_SOUND(x) logerror x +#else +#define LOG_SOUND(x) +#endif + +#if VERBOSE_TIMER +#define LOG_TIMER(x) logerror x +#else +#define LOG_TIMER(x) +#endif + +#if VERBOSE_POLY +#define LOG_POLY(x) logerror x +#else +#define LOG_POLY(x) +#endif + +#if VERBOSE_RAND +#define LOG_RAND(x) logerror x +#else +#define LOG_RAND(x) +#endif + +#define CHAN1 0 +#define CHAN2 1 +#define CHAN3 2 +#define CHAN4 3 + +#define TIMER1 0 +#define TIMER2 1 +#define TIMER4 2 + +/* values to add to the divisors for the different modes */ +#define DIVADD_LOCLK 1 +#define DIVADD_HICLK 4 +#define DIVADD_HICLK_JOINED 7 + +/* AUDCx */ +#define NOTPOLY5 0x80 /* selects POLY5 or direct CLOCK */ +#define POLY4 0x40 /* selects POLY4 or POLY17 */ +#define PURE 0x20 /* selects POLY4/17 or PURE tone */ +#define VOLUME_ONLY 0x10 /* selects VOLUME OUTPUT ONLY */ +#define VOLUME_MASK 0x0f /* volume mask */ + +/* AUDCTL */ +#define POLY9 0x80 /* selects POLY9 or POLY17 */ +#define CH1_HICLK 0x40 /* selects 1.78979 MHz for Ch 1 */ +#define CH3_HICLK 0x20 /* selects 1.78979 MHz for Ch 3 */ +#define CH12_JOINED 0x10 /* clocks channel 1 w/channel 2 */ +#define CH34_JOINED 0x08 /* clocks channel 3 w/channel 4 */ +#define CH1_FILTER 0x04 /* selects channel 1 high pass filter */ +#define CH2_FILTER 0x02 /* selects channel 2 high pass filter */ +#define CLK_15KHZ 0x01 /* selects 15.6999 kHz or 63.9211 kHz */ + +/* IRQEN (D20E) */ +#define IRQ_BREAK 0x80 /* BREAK key pressed interrupt */ +#define IRQ_KEYBD 0x40 /* keyboard data ready interrupt */ +#define IRQ_SERIN 0x20 /* serial input data ready interrupt */ +#define IRQ_SEROR 0x10 /* serial output register ready interrupt */ +#define IRQ_SEROC 0x08 /* serial output complete interrupt */ +#define IRQ_TIMR4 0x04 /* timer channel #4 interrupt */ +#define IRQ_TIMR2 0x02 /* timer channel #2 interrupt */ +#define IRQ_TIMR1 0x01 /* timer channel #1 interrupt */ + +/* SKSTAT (R/D20F) */ +#define SK_FRAME 0x80 /* serial framing error */ +#define SK_OVERRUN 0x40 /* serial overrun error */ +#define SK_KBERR 0x20 /* keyboard overrun error */ +#define SK_SERIN 0x10 /* serial input high */ +#define SK_SHIFT 0x08 /* shift key pressed */ +#define SK_KEYBD 0x04 /* keyboard key pressed */ +#define SK_SEROUT 0x02 /* serial output active */ + +/* SKCTL (W/D20F) */ +#define SK_BREAK 0x80 /* serial out break signal */ +#define SK_BPS 0x70 /* bits per second */ +#define SK_FM 0x08 /* FM mode */ +#define SK_PADDLE 0x04 /* fast paddle a/d conversion */ +#define SK_RESET 0x03 /* reset serial/keyboard interface */ + +#define DIV_64 28 /* divisor for 1.78979 MHz clock to 63.9211 kHz */ +#define DIV_15 114 /* divisor for 1.78979 MHz clock to 15.6999 kHz */ + +struct POKEYregisters +{ + INT32 counter[4]; /* channel counter */ + INT32 divisor[4]; /* channel divisor (modulo value) */ + UINT32 volume[4]; /* channel volume - derived */ + UINT8 output[4]; /* channel output signal (1 active, 0 inactive) */ + UINT8 audible[4]; /* channel plays an audible tone/effect */ + UINT32 samplerate_24_8; /* sample rate in 24.8 format */ + UINT32 samplepos_fract; /* sample position fractional part */ + UINT32 samplepos_whole; /* sample position whole part */ + UINT32 polyadjust; /* polynome adjustment */ + UINT32 p4; /* poly4 index */ + UINT32 p5; /* poly5 index */ + UINT32 p9; /* poly9 index */ + UINT32 p17; /* poly17 index */ + UINT32 r9; /* rand9 index */ + UINT32 r17; /* rand17 index */ + UINT32 clockmult; /* clock multiplier */ + sound_stream * channel; /* streams channel */ + emu_timer *timer[3]; /* timers for channel 1,2 and 4 events */ + attotime timer_period[3]; /* computed periods for these timers */ + int timer_param[3]; /* computed parameters for these timers */ + emu_timer *rtimer; /* timer for calculating the random offset */ + emu_timer *ptimer[8]; /* pot timers */ + read8_handler pot_r[8]; + read8_handler allpot_r; + read8_handler serin_r; + write8_handler serout_w; + void (*interrupt_cb)(int mask); + UINT8 AUDF[4]; /* AUDFx (D200, D202, D204, D206) */ + UINT8 AUDC[4]; /* AUDCx (D201, D203, D205, D207) */ + UINT8 POTx[8]; /* POTx (R/D200-D207) */ + UINT8 AUDCTL; /* AUDCTL (W/D208) */ + UINT8 ALLPOT; /* ALLPOT (R/D208) */ + UINT8 KBCODE; /* KBCODE (R/D209) */ + UINT8 RANDOM; /* RANDOM (R/D20A) */ + UINT8 SERIN; /* SERIN (R/D20D) */ + UINT8 SEROUT; /* SEROUT (W/D20D) */ + UINT8 IRQST; /* IRQST (R/D20E) */ + UINT8 IRQEN; /* IRQEN (W/D20E) */ + UINT8 SKSTAT; /* SKSTAT (R/D20F) */ + UINT8 SKCTL; /* SKCTL (W/D20F) */ + struct POKEYinterface intf; + attotime clock_period; + attotime ad_time_fast; + attotime ad_time_slow; + int index; + + UINT8 poly4[0x0f]; + UINT8 poly5[0x1f]; + UINT8 poly9[0x1ff]; + UINT8 poly17[0x1ffff]; + + UINT8 rand9[0x1ff]; + UINT8 rand17[0x1ffff]; +}; + + +#define P4(chip) chip->poly4[chip->p4] +#define P5(chip) chip->poly5[chip->p5] +#define P9(chip) chip->poly9[chip->p9] +#define P17(chip) chip->poly17[chip->p17] + +static TIMER_CALLBACK_PTR( pokey_timer_expire_1 ); +static TIMER_CALLBACK_PTR( pokey_timer_expire_2 ); +static TIMER_CALLBACK_PTR( pokey_timer_expire_4 ); +static TIMER_CALLBACK_PTR( pokey_pot_trigger_0 ); +static TIMER_CALLBACK_PTR( pokey_pot_trigger_1 ); +static TIMER_CALLBACK_PTR( pokey_pot_trigger_2 ); +static TIMER_CALLBACK_PTR( pokey_pot_trigger_3 ); +static TIMER_CALLBACK_PTR( pokey_pot_trigger_4 ); +static TIMER_CALLBACK_PTR( pokey_pot_trigger_5 ); +static TIMER_CALLBACK_PTR( pokey_pot_trigger_6 ); +static TIMER_CALLBACK_PTR( pokey_pot_trigger_7 ); + + +#define SAMPLE -1 + +#define ADJUST_EVENT(chip) \ + chip->counter[CHAN1] -= event; \ + chip->counter[CHAN2] -= event; \ + chip->counter[CHAN3] -= event; \ + chip->counter[CHAN4] -= event; \ + chip->samplepos_whole -= event; \ + chip->polyadjust += event + +#if SUPPRESS_INAUDIBLE + +#define PROCESS_CHANNEL(chip,ch) \ + int toggle = 0; \ + ADJUST_EVENT(chip); \ + /* reset the channel counter */ \ + if( chip->audible[ch] ) \ + chip->counter[ch] = chip->divisor[ch]; \ + else \ + chip->counter[ch] = 0x7fffffff; \ + chip->p4 = (chip->p4+chip->polyadjust)%0x0000f; \ + chip->p5 = (chip->p5+chip->polyadjust)%0x0001f; \ + chip->p9 = (chip->p9+chip->polyadjust)%0x001ff; \ + chip->p17 = (chip->p17+chip->polyadjust)%0x1ffff; \ + chip->polyadjust = 0; \ + if( (chip->AUDC[ch] & NOTPOLY5) || P5(chip) ) \ + { \ + if( chip->AUDC[ch] & PURE ) \ + toggle = 1; \ + else \ + if( chip->AUDC[ch] & POLY4 ) \ + toggle = chip->output[ch] == !P4(chip); \ + else \ + if( chip->AUDCTL & POLY9 ) \ + toggle = chip->output[ch] == !P9(chip); \ + else \ + toggle = chip->output[ch] == !P17(chip); \ + } \ + if( toggle ) \ + { \ + if( chip->audible[ch] ) \ + { \ + if( chip->output[ch] ) \ + sum -= chip->volume[ch]; \ + else \ + sum += chip->volume[ch]; \ + } \ + chip->output[ch] ^= 1; \ + } \ + /* is this a filtering channel (3/4) and is the filter active? */ \ + if( chip->AUDCTL & ((CH1_FILTER|CH2_FILTER) & (0x10 >> ch)) ) \ + { \ + if( chip->output[ch-2] ) \ + { \ + chip->output[ch-2] = 0; \ + if( chip->audible[ch] ) \ + sum -= chip->volume[ch-2]; \ + } \ + } \ + +#else + +#define PROCESS_CHANNEL(chip,ch) \ + int toggle = 0; \ + ADJUST_EVENT(chip); \ + /* reset the channel counter */ \ + chip->counter[ch] = p[chip].divisor[ch]; \ + chip->p4 = (chip->p4+chip->polyadjust)%0x0000f; \ + chip->p5 = (chip->p5+chip->polyadjust)%0x0001f; \ + chip->p9 = (chip->p9+chip->polyadjust)%0x001ff; \ + chip->p17 = (chip->p17+chip->polyadjust)%0x1ffff; \ + chip->polyadjust = 0; \ + if( (chip->AUDC[ch] & NOTPOLY5) || P5(chip) ) \ + { \ + if( chip->AUDC[ch] & PURE ) \ + toggle = 1; \ + else \ + if( chip->AUDC[ch] & POLY4 ) \ + toggle = chip->output[ch] == !P4(chip); \ + else \ + if( chip->AUDCTL & POLY9 ) \ + toggle = chip->output[ch] == !P9(chip); \ + else \ + toggle = chip->output[ch] == !P17(chip); \ + } \ + if( toggle ) \ + { \ + if( chip->output[ch] ) \ + sum -= chip->volume[ch]; \ + else \ + sum += chip->volume[ch]; \ + chip->output[ch] ^= 1; \ + } \ + /* is this a filtering channel (3/4) and is the filter active? */ \ + if( chip->AUDCTL & ((CH1_FILTER|CH2_FILTER) & (0x10 >> ch)) ) \ + { \ + if( chip->output[ch-2] ) \ + { \ + chip->output[ch-2] = 0; \ + sum -= chip->volume[ch-2]; \ + } \ + } \ + +#endif + +#define PROCESS_SAMPLE(chip) \ + ADJUST_EVENT(chip); \ + /* adjust the sample position */ \ + chip->samplepos_whole++; \ + /* store sum of output signals into the buffer */ \ + *buffer++ = (sum > 0x7fff) ? 0x7fff : sum; \ + length-- + +#if HEAVY_MACRO_USAGE + +/* + * This version of PROCESS_POKEY repeats the search for the minimum + * event value without using an index to the channel. That way the + * PROCESS_CHANNEL macros can be called with fixed values and expand + * to much more efficient code + */ + +#define PROCESS_POKEY(chip) \ + UINT32 sum = 0; \ + if( chip->output[CHAN1] ) \ + sum += chip->volume[CHAN1]; \ + if( chip->output[CHAN2] ) \ + sum += chip->volume[CHAN2]; \ + if( chip->output[CHAN3] ) \ + sum += chip->volume[CHAN3]; \ + if( chip->output[CHAN4] ) \ + sum += chip->volume[CHAN4]; \ + while( length > 0 ) \ + { \ + if( chip->counter[CHAN1] < chip->samplepos_whole ) \ + { \ + if( chip->counter[CHAN2] < chip->counter[CHAN1] ) \ + { \ + if( chip->counter[CHAN3] < chip->counter[CHAN2] ) \ + { \ + if( chip->counter[CHAN4] < chip->counter[CHAN3] ) \ + { \ + UINT32 event = chip->counter[CHAN4]; \ + PROCESS_CHANNEL(chip,CHAN4); \ + } \ + else \ + { \ + UINT32 event = chip->counter[CHAN3]; \ + PROCESS_CHANNEL(chip,CHAN3); \ + } \ + } \ + else \ + if( chip->counter[CHAN4] < chip->counter[CHAN2] ) \ + { \ + UINT32 event = chip->counter[CHAN4]; \ + PROCESS_CHANNEL(chip,CHAN4); \ + } \ + else \ + { \ + UINT32 event = chip->counter[CHAN2]; \ + PROCESS_CHANNEL(chip,CHAN2); \ + } \ + } \ + else \ + if( chip->counter[CHAN3] < chip->counter[CHAN1] ) \ + { \ + if( chip->counter[CHAN4] < chip->counter[CHAN3] ) \ + { \ + UINT32 event = chip->counter[CHAN4]; \ + PROCESS_CHANNEL(chip,CHAN4); \ + } \ + else \ + { \ + UINT32 event = chip->counter[CHAN3]; \ + PROCESS_CHANNEL(chip,CHAN3); \ + } \ + } \ + else \ + if( chip->counter[CHAN4] < chip->counter[CHAN1] ) \ + { \ + UINT32 event = chip->counter[CHAN4]; \ + PROCESS_CHANNEL(chip,CHAN4); \ + } \ + else \ + { \ + UINT32 event = chip->counter[CHAN1]; \ + PROCESS_CHANNEL(chip,CHAN1); \ + } \ + } \ + else \ + if( chip->counter[CHAN2] < chip->samplepos_whole ) \ + { \ + if( chip->counter[CHAN3] < chip->counter[CHAN2] ) \ + { \ + if( chip->counter[CHAN4] < chip->counter[CHAN3] ) \ + { \ + UINT32 event = chip->counter[CHAN4]; \ + PROCESS_CHANNEL(chip,CHAN4); \ + } \ + else \ + { \ + UINT32 event = chip->counter[CHAN3]; \ + PROCESS_CHANNEL(chip,CHAN3); \ + } \ + } \ + else \ + if( chip->counter[CHAN4] < chip->counter[CHAN2] ) \ + { \ + UINT32 event = chip->counter[CHAN4]; \ + PROCESS_CHANNEL(chip,CHAN4); \ + } \ + else \ + { \ + UINT32 event = chip->counter[CHAN2]; \ + PROCESS_CHANNEL(chip,CHAN2); \ + } \ + } \ + else \ + if( chip->counter[CHAN3] < chip->samplepos_whole ) \ + { \ + if( chip->counter[CHAN4] < chip->counter[CHAN3] ) \ + { \ + UINT32 event = chip->counter[CHAN4]; \ + PROCESS_CHANNEL(chip,CHAN4); \ + } \ + else \ + { \ + UINT32 event = chip->counter[CHAN3]; \ + PROCESS_CHANNEL(chip,CHAN3); \ + } \ + } \ + else \ + if( chip->counter[CHAN4] < chip->samplepos_whole ) \ + { \ + UINT32 event = chip->counter[CHAN4]; \ + PROCESS_CHANNEL(chip,CHAN4); \ + } \ + else \ + { \ + UINT32 event = chip->samplepos_whole; \ + PROCESS_SAMPLE(chip); \ + } \ + } \ + timer_adjust(chip->rtimer, attotime_never, 0, attotime_zero) + +#else /* no HEAVY_MACRO_USAGE */ +/* + * And this version of PROCESS_POKEY uses event and channel variables + * so that the PROCESS_CHANNEL macro needs to index memory at runtime. + */ + +#define PROCESS_POKEY(chip) \ + UINT32 sum = 0; \ + if( chip->output[CHAN1] ) \ + sum += chip->volume[CHAN1]; \ + if( chip->output[CHAN2] ) \ + sum += chip->volume[CHAN2]; \ + if( chip->output[CHAN3] ) \ + sum += chip->volume[CHAN3]; \ + if( chip->output[CHAN4] ) \ + sum += chip->volume[CHAN4]; \ + while( length > 0 ) \ + { \ + UINT32 event = chip->samplepos_whole; \ + UINT32 channel = SAMPLE; \ + if( chip->counter[CHAN1] < event ) \ + { \ + event = chip->counter[CHAN1]; \ + channel = CHAN1; \ + } \ + if( chip->counter[CHAN2] < event ) \ + { \ + event = chip->counter[CHAN2]; \ + channel = CHAN2; \ + } \ + if( chip->counter[CHAN3] < event ) \ + { \ + event = chip->counter[CHAN3]; \ + channel = CHAN3; \ + } \ + if( chip->counter[CHAN4] < event ) \ + { \ + event = chip->counter[CHAN4]; \ + channel = CHAN4; \ + } \ + if( channel == SAMPLE ) \ + { \ + PROCESS_SAMPLE(chip); \ + } \ + else \ + { \ + PROCESS_CHANNEL(chip,channel); \ + } \ + } \ + timer_adjust(chip->rtimer, attotime_never, 0, attotime_zero) + +#endif + + +void pokey_update(void *param, stream_sample_t **inputs, stream_sample_t **_buffer, int length) +{ + struct POKEYregisters *chip = param; + stream_sample_t *buffer = _buffer[0]; + PROCESS_POKEY(chip); +} + + +static void poly_init(UINT8 *poly, int size, int left, int right, int add) +{ + int mask = (1 << size) - 1; + int i, x = 0; + + LOG_POLY(("poly %d\n", size)); + for( i = 0; i < mask; i++ ) + { + *poly++ = x & 1; + LOG_POLY(("%05x: %d\n", x, x&1)); + /* calculate next bit */ + x = ((x << left) + (x >> right) + add) & mask; + } +} + +static void rand_init(UINT8 *rng, int size, int left, int right, int add) +{ + int mask = (1 << size) - 1; + int i, x = 0; + + LOG_RAND(("rand %d\n", size)); + for( i = 0; i < mask; i++ ) + { + if (size == 17) + *rng = x >> 6; /* use bits 6..13 */ + else + *rng = x; /* use bits 0..7 */ + LOG_RAND(("%05x: %02x\n", x, *rng)); + rng++; + /* calculate next bit */ + x = ((x << left) + (x >> right) + add) & mask; + } +} + + +static void register_for_save(struct POKEYregisters *chip, int index) +{ + state_save_register_item_array("pokey", index, chip->counter); + state_save_register_item_array("pokey", index, chip->divisor); + state_save_register_item_array("pokey", index, chip->volume); + state_save_register_item_array("pokey", index, chip->output); + state_save_register_item_array("pokey", index, chip->audible); + state_save_register_item("pokey", index, chip->samplepos_fract); + state_save_register_item("pokey", index, chip->samplepos_whole); + state_save_register_item("pokey", index, chip->polyadjust); + state_save_register_item("pokey", index, chip->p4); + state_save_register_item("pokey", index, chip->p5); + state_save_register_item("pokey", index, chip->p9); + state_save_register_item("pokey", index, chip->p17); + state_save_register_item("pokey", index, chip->r9); + state_save_register_item("pokey", index, chip->r17); + state_save_register_item("pokey", index, chip->clockmult); + state_save_register_item("pokey", index, chip->timer_period[0].seconds); + state_save_register_item("pokey", index, chip->timer_period[0].attoseconds); + state_save_register_item("pokey", index, chip->timer_period[1].seconds); + state_save_register_item("pokey", index, chip->timer_period[1].attoseconds); + state_save_register_item("pokey", index, chip->timer_period[2].seconds); + state_save_register_item("pokey", index, chip->timer_period[2].attoseconds); + state_save_register_item_array("pokey", index, chip->timer_param); + state_save_register_item_array("pokey", index, chip->AUDF); + state_save_register_item_array("pokey", index, chip->AUDC); + state_save_register_item_array("pokey", index, chip->POTx); + state_save_register_item("pokey", index, chip->AUDCTL); + state_save_register_item("pokey", index, chip->ALLPOT); + state_save_register_item("pokey", index, chip->KBCODE); + state_save_register_item("pokey", index, chip->RANDOM); + state_save_register_item("pokey", index, chip->SERIN); + state_save_register_item("pokey", index, chip->SEROUT); + state_save_register_item("pokey", index, chip->IRQST); + state_save_register_item("pokey", index, chip->IRQEN); + state_save_register_item("pokey", index, chip->SKSTAT); + state_save_register_item("pokey", index, chip->SKCTL); +} + + +static void *pokey_start(int sndindex, int clock, const void *config) +{ + struct POKEYregisters *chip; + int sample_rate = clock; + + chip = auto_malloc(sizeof(*chip)); + memset(chip, 0, sizeof(*chip)); + + if (config) + memcpy(&chip->intf, config, sizeof(struct POKEYinterface)); + chip->clock_period = ATTOTIME_IN_HZ(clock); + chip->index = sndindex; + + /* calculate the A/D times + * In normal, slow mode (SKCTL bit SK_PADDLE is clear) the conversion + * takes N scanlines, where N is the paddle value. A single scanline + * takes approximately 64us to finish (1.78979MHz clock). + * In quick mode (SK_PADDLE set) the conversion is done very fast + * (takes two scanlines) but the result is not as accurate. + */ + chip->ad_time_fast = attotime_div(attotime_mul(ATTOTIME_IN_NSEC(64000*2/228), FREQ_17_EXACT), clock); + chip->ad_time_slow = attotime_div(attotime_mul(ATTOTIME_IN_NSEC(64000 ), FREQ_17_EXACT), clock); + + /* initialize the poly counters */ + poly_init(chip->poly4, 4, 3, 1, 0x00004); + poly_init(chip->poly5, 5, 3, 2, 0x00008); + poly_init(chip->poly9, 9, 8, 1, 0x00180); + poly_init(chip->poly17, 17,16, 1, 0x1c000); + + /* initialize the random arrays */ + rand_init(chip->rand9, 9, 8, 1, 0x00180); + rand_init(chip->rand17, 17,16, 1, 0x1c000); + + chip->samplerate_24_8 = (clock << 8) / sample_rate; + chip->divisor[CHAN1] = 4; + chip->divisor[CHAN2] = 4; + chip->divisor[CHAN3] = 4; + chip->divisor[CHAN4] = 4; + chip->clockmult = DIV_64; + chip->KBCODE = 0x09; /* Atari 800 'no key' */ + chip->SKCTL = SK_RESET; /* let the RNG run after reset */ + chip->rtimer = timer_alloc(NULL); + + chip->timer[0] = timer_alloc_ptr(pokey_timer_expire_1, chip); + chip->timer[1] = timer_alloc_ptr(pokey_timer_expire_2, chip); + chip->timer[2] = timer_alloc_ptr(pokey_timer_expire_4, chip); + + chip->ptimer[0] = timer_alloc_ptr(pokey_pot_trigger_0, chip); + chip->ptimer[1] = timer_alloc_ptr(pokey_pot_trigger_1, chip); + chip->ptimer[2] = timer_alloc_ptr(pokey_pot_trigger_2, chip); + chip->ptimer[3] = timer_alloc_ptr(pokey_pot_trigger_3, chip); + chip->ptimer[4] = timer_alloc_ptr(pokey_pot_trigger_4, chip); + chip->ptimer[5] = timer_alloc_ptr(pokey_pot_trigger_5, chip); + chip->ptimer[6] = timer_alloc_ptr(pokey_pot_trigger_6, chip); + chip->ptimer[7] = timer_alloc_ptr(pokey_pot_trigger_7, chip); + + chip->pot_r[0] = chip->intf.pot_r[0]; + chip->pot_r[1] = chip->intf.pot_r[1]; + chip->pot_r[2] = chip->intf.pot_r[2]; + chip->pot_r[3] = chip->intf.pot_r[3]; + chip->pot_r[4] = chip->intf.pot_r[4]; + chip->pot_r[5] = chip->intf.pot_r[5]; + chip->pot_r[6] = chip->intf.pot_r[6]; + chip->pot_r[7] = chip->intf.pot_r[7]; + chip->allpot_r = chip->intf.allpot_r; + chip->serin_r = chip->intf.serin_r; + chip->serout_w = chip->intf.serout_w; + chip->interrupt_cb = chip->intf.interrupt_cb; + + chip->channel = stream_create(0, 1, sample_rate, chip, pokey_update); + + register_for_save(chip, sndindex); + + return chip; +} + +static void pokey_timer_expire_common(struct POKEYregisters *p, int timers) +{ + LOG_TIMER(("POKEY #%p timer %d with IRQEN $%02x\n", p, timers, p->IRQEN)); + + /* check if some of the requested timer interrupts are enabled */ + timers &= p->IRQEN; + + if( timers ) + { + /* set the enabled timer irq status bits */ + p->IRQST |= timers; + /* call back an application supplied function to handle the interrupt */ + if( p->interrupt_cb ) + (*p->interrupt_cb)(timers); + } +} + +static TIMER_CALLBACK_PTR( pokey_timer_expire_1 ) +{ + struct POKEYregisters *chip = param; + pokey_timer_expire_common(chip, chip->timer_param[TIMER1]); +} +static TIMER_CALLBACK_PTR( pokey_timer_expire_2 ) +{ + struct POKEYregisters *chip = param; + pokey_timer_expire_common(chip, chip->timer_param[TIMER2]); +} +static TIMER_CALLBACK_PTR( pokey_timer_expire_4 ) +{ + struct POKEYregisters *chip = param; + pokey_timer_expire_common(chip, chip->timer_param[TIMER4]); +} + +#if VERBOSE_SOUND +static char *audc2str(int val) +{ + static char buff[80]; + if( val & NOTPOLY5 ) + { + if( val & PURE ) + strcpy(buff,"pure"); + else + if( val & POLY4 ) + strcpy(buff,"poly4"); + else + strcpy(buff,"poly9/17"); + } + else + { + if( val & PURE ) + strcpy(buff,"poly5"); + else + if( val & POLY4 ) + strcpy(buff,"poly4+poly5"); + else + strcpy(buff,"poly9/17+poly5"); + } + return buff; +} + +static char *audctl2str(int val) +{ + static char buff[80]; + if( val & POLY9 ) + strcpy(buff,"poly9"); + else + strcpy(buff,"poly17"); + if( val & CH1_HICLK ) + strcat(buff,"+ch1hi"); + if( val & CH3_HICLK ) + strcat(buff,"+ch3hi"); + if( val & CH12_JOINED ) + strcat(buff,"+ch1/2"); + if( val & CH34_JOINED ) + strcat(buff,"+ch3/4"); + if( val & CH1_FILTER ) + strcat(buff,"+ch1filter"); + if( val & CH2_FILTER ) + strcat(buff,"+ch2filter"); + if( val & CLK_15KHZ ) + strcat(buff,"+clk15"); + return buff; +} +#endif + +static TIMER_CALLBACK_PTR( pokey_serin_ready ) +{ + struct POKEYregisters *p = param; + if( p->IRQEN & IRQ_SERIN ) + { + /* set the enabled timer irq status bits */ + p->IRQST |= IRQ_SERIN; + /* call back an application supplied function to handle the interrupt */ + if( p->interrupt_cb ) + (*p->interrupt_cb)(IRQ_SERIN); + } +} + +static TIMER_CALLBACK_PTR( pokey_serout_ready ) +{ + struct POKEYregisters *p = param; + if( p->IRQEN & IRQ_SEROR ) + { + p->IRQST |= IRQ_SEROR; + if( p->interrupt_cb ) + (*p->interrupt_cb)(IRQ_SEROR); + } +} + +static TIMER_CALLBACK_PTR( pokey_serout_complete ) +{ + struct POKEYregisters *p = param; + if( p->IRQEN & IRQ_SEROC ) + { + p->IRQST |= IRQ_SEROC; + if( p->interrupt_cb ) + (*p->interrupt_cb)(IRQ_SEROC); + } +} + +static void pokey_pot_trigger_common(struct POKEYregisters *p, int pot) +{ + LOG(("POKEY #%p POT%d triggers after %dus\n", p, pot, (int)(1000000 * attotime_to_double(timer_timeelapsed(p->ptimer[pot]))))); + p->ALLPOT &= ~(1 << pot); /* set the enabled timer irq status bits */ +} + +static TIMER_CALLBACK_PTR( pokey_pot_trigger_0 ) { pokey_pot_trigger_common(param, 0); } +static TIMER_CALLBACK_PTR( pokey_pot_trigger_1 ) { pokey_pot_trigger_common(param, 1); } +static TIMER_CALLBACK_PTR( pokey_pot_trigger_2 ) { pokey_pot_trigger_common(param, 2); } +static TIMER_CALLBACK_PTR( pokey_pot_trigger_3 ) { pokey_pot_trigger_common(param, 3); } +static TIMER_CALLBACK_PTR( pokey_pot_trigger_4 ) { pokey_pot_trigger_common(param, 4); } +static TIMER_CALLBACK_PTR( pokey_pot_trigger_5 ) { pokey_pot_trigger_common(param, 5); } +static TIMER_CALLBACK_PTR( pokey_pot_trigger_6 ) { pokey_pot_trigger_common(param, 6); } +static TIMER_CALLBACK_PTR( pokey_pot_trigger_7 ) { pokey_pot_trigger_common(param, 7); } + +#define AD_TIME ((p->SKCTL & SK_PADDLE) ? p->ad_time_fast : p->ad_time_slow) + +static void pokey_potgo(struct POKEYregisters *p) +{ + int pot; + + LOG(("POKEY #%p pokey_potgo\n", p)); + + p->ALLPOT = 0xff; + + for( pot = 0; pot < 8; pot++ ) + { + p->POTx[pot] = 0xff; + if( p->pot_r[pot] ) + { + int r = (*p->pot_r[pot])(pot); + + LOG(("POKEY #%d pot_r(%d) returned $%02x\n", p->index, pot, r)); + if( r != -1 ) + { + if (r > 228) + r = 228; + + /* final value */ + p->POTx[pot] = r; + timer_adjust_ptr(p->ptimer[pot], attotime_mul(AD_TIME, r), attotime_zero); + } + } + } +} + +static int pokey_register_r(int chip, int offs) +{ + struct POKEYregisters *p = sndti_token(SOUND_POKEY, chip); + int data = 0, pot; + UINT32 adjust = 0; + +#ifdef MAME_DEBUG + if( !p ) + { + logerror("POKEY #%d is >= number of Pokeys!\n", chip); + return data; + } +#endif + + switch (offs & 15) + { + case POT0_C: case POT1_C: case POT2_C: case POT3_C: + case POT4_C: case POT5_C: case POT6_C: case POT7_C: + pot = offs & 7; + if( p->pot_r[pot] ) + { + /* + * If the conversion is not yet finished (ptimer running), + * get the current value by the linear interpolation of + * the final value using the elapsed time. + */ + if( p->ALLPOT & (1 << pot) ) + { + data = timer_timeelapsed(p->ptimer[pot]).attoseconds / AD_TIME.attoseconds; + LOG(("POKEY #%d read POT%d (interpolated) $%02x\n", chip, pot, data)); + } + else + { + data = p->POTx[pot]; + LOG(("POKEY #%d read POT%d (final value) $%02x\n", chip, pot, data)); + } + } + else + logerror("PC %04x: warning - read p[chip] #%d POT%d\n", activecpu_get_pc(), chip, pot); + break; + + case ALLPOT_C: + /**************************************************************** + * If the 2 least significant bits of SKCTL are 0, the ALLPOTs + * are disabled (SKRESET). Thanks to MikeJ for pointing this out. + ****************************************************************/ + if( (p->SKCTL & SK_RESET) == 0) + { + data = 0; + LOG(("POKEY #%d ALLPOT internal $%02x (reset)\n", chip, data)); + } + else if( p->allpot_r ) + { + data = (*p->allpot_r)(offs); + LOG(("POKEY #%d ALLPOT callback $%02x\n", chip, data)); + } + else + { + data = p->ALLPOT; + LOG(("POKEY #%d ALLPOT internal $%02x\n", chip, data)); + } + break; + + case KBCODE_C: + data = p->KBCODE; + break; + + case RANDOM_C: + /**************************************************************** + * If the 2 least significant bits of SKCTL are 0, the random + * number generator is disabled (SKRESET). Thanks to Eric Smith + * for pointing out this critical bit of info! If the random + * number generator is enabled, get a new random number. Take + * the time gone since the last read into account and read the + * new value from an appropriate offset in the rand17 table. + ****************************************************************/ + if( p->SKCTL & SK_RESET ) + { + adjust = attotime_to_double(timer_timeelapsed(p->rtimer)) / attotime_to_double(p->clock_period); + p->r9 = (p->r9 + adjust) % 0x001ff; + p->r17 = (p->r17 + adjust) % 0x1ffff; + } + else + { + adjust = 1; + p->r9 = 0; + p->r17 = 0; + LOG_RAND(("POKEY #%d rand17 frozen (SKCTL): $%02x\n", chip, p->RANDOM)); + } + if( p->AUDCTL & POLY9 ) + { + p->RANDOM = p->rand9[p->r9]; + LOG_RAND(("POKEY #%d adjust %u rand9[$%05x]: $%02x\n", chip, adjust, p->r9, p->RANDOM)); + } + else + { + p->RANDOM = p->rand17[p->r17]; + LOG_RAND(("POKEY #%d adjust %u rand17[$%05x]: $%02x\n", chip, adjust, p->r17, p->RANDOM)); + } + if (adjust > 0) + timer_adjust(p->rtimer, attotime_never, 0, attotime_zero); + data = p->RANDOM ^ 0xff; + break; + + case SERIN_C: + if( p->serin_r ) + p->SERIN = (*p->serin_r)(offs); + data = p->SERIN; + LOG(("POKEY #%d SERIN $%02x\n", chip, data)); + break; + + case IRQST_C: + /* IRQST is an active low input port; we keep it active high */ + /* internally to ease the (un-)masking of bits */ + data = p->IRQST ^ 0xff; + LOG(("POKEY #%d IRQST $%02x\n", chip, data)); + break; + + case SKSTAT_C: + /* SKSTAT is also an active low input port */ + data = p->SKSTAT ^ 0xff; + LOG(("POKEY #%d SKSTAT $%02x\n", chip, data)); + break; + + default: + LOG(("POKEY #%d register $%02x\n", chip, offs)); + break; + } + return data; +} + +READ8_HANDLER( pokey1_r ) +{ + return pokey_register_r(0, offset); +} + +READ8_HANDLER( pokey2_r ) +{ + return pokey_register_r(1, offset); +} + +READ8_HANDLER( pokey3_r ) +{ + return pokey_register_r(2, offset); +} + +READ8_HANDLER( pokey4_r ) +{ + return pokey_register_r(3, offset); +} + +READ8_HANDLER( quad_pokey_r ) +{ + int pokey_num = (offset >> 3) & ~0x04; + int control = (offset & 0x20) >> 2; + int pokey_reg = (offset % 8) | control; + + return pokey_register_r(pokey_num, pokey_reg); +} + + +void pokey_register_w(int chip, int offs, int data) +{ + struct POKEYregisters *p = sndti_token(SOUND_POKEY, chip); + int ch_mask = 0, new_val; + +#ifdef MAME_DEBUG + if( !p ) + { + logerror("POKEY #%d is >= number of Pokeys!\n", chip); + return; + } +#endif + stream_update(p->channel); + + /* determine which address was changed */ + switch (offs & 15) + { + case AUDF1_C: + if( data == p->AUDF[CHAN1] ) + return; + LOG_SOUND(("POKEY #%d AUDF1 $%02x\n", chip, data)); + p->AUDF[CHAN1] = data; + ch_mask = 1 << CHAN1; + if( p->AUDCTL & CH12_JOINED ) /* if ch 1&2 tied together */ + ch_mask |= 1 << CHAN2; /* then also change on ch2 */ + break; + + case AUDC1_C: + if( data == p->AUDC[CHAN1] ) + return; + LOG_SOUND(("POKEY #%d AUDC1 $%02x (%s)\n", chip, data, audc2str(data))); + p->AUDC[CHAN1] = data; + ch_mask = 1 << CHAN1; + break; + + case AUDF2_C: + if( data == p->AUDF[CHAN2] ) + return; + LOG_SOUND(("POKEY #%d AUDF2 $%02x\n", chip, data)); + p->AUDF[CHAN2] = data; + ch_mask = 1 << CHAN2; + break; + + case AUDC2_C: + if( data == p->AUDC[CHAN2] ) + return; + LOG_SOUND(("POKEY #%d AUDC2 $%02x (%s)\n", chip, data, audc2str(data))); + p->AUDC[CHAN2] = data; + ch_mask = 1 << CHAN2; + break; + + case AUDF3_C: + if( data == p->AUDF[CHAN3] ) + return; + LOG_SOUND(("POKEY #%d AUDF3 $%02x\n", chip, data)); + p->AUDF[CHAN3] = data; + ch_mask = 1 << CHAN3; + + if( p->AUDCTL & CH34_JOINED ) /* if ch 3&4 tied together */ + ch_mask |= 1 << CHAN4; /* then also change on ch4 */ + break; + + case AUDC3_C: + if( data == p->AUDC[CHAN3] ) + return; + LOG_SOUND(("POKEY #%d AUDC3 $%02x (%s)\n", chip, data, audc2str(data))); + p->AUDC[CHAN3] = data; + ch_mask = 1 << CHAN3; + break; + + case AUDF4_C: + if( data == p->AUDF[CHAN4] ) + return; + LOG_SOUND(("POKEY #%d AUDF4 $%02x\n", chip, data)); + p->AUDF[CHAN4] = data; + ch_mask = 1 << CHAN4; + break; + + case AUDC4_C: + if( data == p->AUDC[CHAN4] ) + return; + LOG_SOUND(("POKEY #%d AUDC4 $%02x (%s)\n", chip, data, audc2str(data))); + p->AUDC[CHAN4] = data; + ch_mask = 1 << CHAN4; + break; + + case AUDCTL_C: + if( data == p->AUDCTL ) + return; + LOG_SOUND(("POKEY #%d AUDCTL $%02x (%s)\n", chip, data, audctl2str(data))); + p->AUDCTL = data; + ch_mask = 15; /* all channels */ + /* determine the base multiplier for the 'div by n' calculations */ + p->clockmult = (p->AUDCTL & CLK_15KHZ) ? DIV_15 : DIV_64; + break; + + case STIMER_C: + /* first remove any existing timers */ + LOG_TIMER(("POKEY #%d STIMER $%02x\n", chip, data)); + + timer_adjust_ptr(p->timer[TIMER1], attotime_never, attotime_zero); + timer_adjust_ptr(p->timer[TIMER2], attotime_never, attotime_zero); + timer_adjust_ptr(p->timer[TIMER4], attotime_never, attotime_zero); + + /* reset all counters to zero (side effect) */ + p->polyadjust = 0; + p->counter[CHAN1] = 0; + p->counter[CHAN2] = 0; + p->counter[CHAN3] = 0; + p->counter[CHAN4] = 0; + + /* joined chan#1 and chan#2 ? */ + if( p->AUDCTL & CH12_JOINED ) + { + if( p->divisor[CHAN2] > 4 ) + { + LOG_TIMER(("POKEY #%d timer1+2 after %d clocks\n", chip, p->divisor[CHAN2])); + /* set timer #1 _and_ #2 event after timer_div clocks of joined CHAN1+CHAN2 */ + p->timer_period[TIMER2] = attotime_mul(p->clock_period, p->divisor[CHAN2]); + p->timer_param[TIMER2] = (chip<<3)|IRQ_TIMR2|IRQ_TIMR1; + timer_adjust_ptr(p->timer[TIMER2], p->timer_period[TIMER2], p->timer_period[TIMER2]); + } + } + else + { + if( p->divisor[CHAN1] > 4 ) + { + LOG_TIMER(("POKEY #%d timer1 after %d clocks\n", chip, p->divisor[CHAN1])); + /* set timer #1 event after timer_div clocks of CHAN1 */ + p->timer_period[TIMER1] = attotime_mul(p->clock_period, p->divisor[CHAN1]); + p->timer_param[TIMER1] = (chip<<3)|IRQ_TIMR1; + timer_adjust_ptr(p->timer[TIMER1], p->timer_period[TIMER1], p->timer_period[TIMER1]); + } + + if( p->divisor[CHAN2] > 4 ) + { + LOG_TIMER(("POKEY #%d timer2 after %d clocks\n", chip, p->divisor[CHAN2])); + /* set timer #2 event after timer_div clocks of CHAN2 */ + p->timer_period[TIMER2] = attotime_mul(p->clock_period, p->divisor[CHAN2]); + p->timer_param[TIMER2] = (chip<<3)|IRQ_TIMR2; + timer_adjust_ptr(p->timer[TIMER2], p->timer_period[TIMER2], p->timer_period[TIMER2]); + } + } + + /* Note: p[chip] does not have a timer #3 */ + + if( p->AUDCTL & CH34_JOINED ) + { + /* not sure about this: if audc4 == 0000xxxx don't start timer 4 ? */ + if( p->AUDC[CHAN4] & 0xf0 ) + { + if( p->divisor[CHAN4] > 4 ) + { + LOG_TIMER(("POKEY #%d timer4 after %d clocks\n", chip, p->divisor[CHAN4])); + /* set timer #4 event after timer_div clocks of CHAN4 */ + p->timer_period[TIMER4] = attotime_mul(p->clock_period, p->divisor[CHAN4]); + p->timer_param[TIMER4] = (chip<<3)|IRQ_TIMR4; + timer_adjust_ptr(p->timer[TIMER4], p->timer_period[TIMER4], p->timer_period[TIMER4]); + } + } + } + else + { + if( p->divisor[CHAN4] > 4 ) + { + LOG_TIMER(("POKEY #%d timer4 after %d clocks\n", chip, p->divisor[CHAN4])); + /* set timer #4 event after timer_div clocks of CHAN4 */ + p->timer_period[TIMER4] = attotime_mul(p->clock_period, p->divisor[CHAN4]); + p->timer_param[TIMER4] = (chip<<3)|IRQ_TIMR4; + timer_adjust_ptr(p->timer[TIMER4], p->timer_period[TIMER4], p->timer_period[TIMER4]); + } + } + + timer_enable(p->timer[TIMER1], p->IRQEN & IRQ_TIMR1); + timer_enable(p->timer[TIMER2], p->IRQEN & IRQ_TIMR2); + timer_enable(p->timer[TIMER4], p->IRQEN & IRQ_TIMR4); + break; + + case SKREST_C: + /* reset SKSTAT */ + LOG(("POKEY #%d SKREST $%02x\n", chip, data)); + p->SKSTAT &= ~(SK_FRAME|SK_OVERRUN|SK_KBERR); + break; + + case POTGO_C: + LOG(("POKEY #%d POTGO $%02x\n", chip, data)); + pokey_potgo(p); + break; + + case SEROUT_C: + LOG(("POKEY #%d SEROUT $%02x\n", chip, data)); + if (p->serout_w) + (*p->serout_w)(offs, data); + p->SKSTAT |= SK_SEROUT; + /* + * These are arbitrary values, tested with some custom boot + * loaders from Ballblazer and Escape from Fractalus + * The real times are unknown + */ + timer_set_ptr(ATTOTIME_IN_USEC(200), p, pokey_serout_ready); + /* 10 bits (assumption 1 start, 8 data and 1 stop bit) take how long? */ + timer_set_ptr(ATTOTIME_IN_USEC(2000), p, pokey_serout_complete); + break; + + case IRQEN_C: + LOG(("POKEY #%d IRQEN $%02x\n", chip, data)); + + /* acknowledge one or more IRQST bits ? */ + if( p->IRQST & ~data ) + { + /* reset IRQST bits that are masked now */ + p->IRQST &= data; + } + else + { + /* enable/disable timers now to avoid unneeded + breaking of the CPU cores for masked timers */ + if( p->timer[TIMER1] && ((p->IRQEN^data) & IRQ_TIMR1) ) + timer_enable(p->timer[TIMER1], data & IRQ_TIMR1); + if( p->timer[TIMER2] && ((p->IRQEN^data) & IRQ_TIMR2) ) + timer_enable(p->timer[TIMER2], data & IRQ_TIMR2); + if( p->timer[TIMER4] && ((p->IRQEN^data) & IRQ_TIMR4) ) + timer_enable(p->timer[TIMER4], data & IRQ_TIMR4); + } + /* store irq enable */ + p->IRQEN = data; + break; + + case SKCTL_C: + if( data == p->SKCTL ) + return; + LOG(("POKEY #%d SKCTL $%02x\n", chip, data)); + p->SKCTL = data; + if( !(data & SK_RESET) ) + { + pokey_register_w(chip, IRQEN_C, 0); + pokey_register_w(chip, SKREST_C, 0); + } + break; + } + + /************************************************************ + * As defined in the manual, the exact counter values are + * different depending on the frequency and resolution: + * 64 kHz or 15 kHz - AUDF + 1 + * 1.79 MHz, 8-bit - AUDF + 4 + * 1.79 MHz, 16-bit - AUDF[CHAN1]+256*AUDF[CHAN2] + 7 + ************************************************************/ + + /* only reset the channels that have changed */ + + if( ch_mask & (1 << CHAN1) ) + { + /* process channel 1 frequency */ + if( p->AUDCTL & CH1_HICLK ) + new_val = p->AUDF[CHAN1] + DIVADD_HICLK; + else + new_val = (p->AUDF[CHAN1] + DIVADD_LOCLK) * p->clockmult; + + LOG_SOUND(("POKEY #%d chan1 %d\n", chip, new_val)); + + p->volume[CHAN1] = (p->AUDC[CHAN1] & VOLUME_MASK) * POKEY_DEFAULT_GAIN; + p->divisor[CHAN1] = new_val; + if( new_val < p->counter[CHAN1] ) + p->counter[CHAN1] = new_val; + if( p->interrupt_cb && p->timer[TIMER1] ) + timer_adjust_ptr(p->timer[TIMER1], attotime_mul(p->clock_period, new_val), p->timer_period[TIMER1]); + p->audible[CHAN1] = !( + (p->AUDC[CHAN1] & VOLUME_ONLY) || + (p->AUDC[CHAN1] & VOLUME_MASK) == 0 || + ((p->AUDC[CHAN1] & PURE) && new_val < (p->samplerate_24_8 >> 8))); + if( !p->audible[CHAN1] ) + { + p->output[CHAN1] = 1; + p->counter[CHAN1] = 0x7fffffff; + /* 50% duty cycle should result in half volume */ + p->volume[CHAN1] >>= 1; + } + } + + if( ch_mask & (1 << CHAN2) ) + { + /* process channel 2 frequency */ + if( p->AUDCTL & CH12_JOINED ) + { + if( p->AUDCTL & CH1_HICLK ) + new_val = p->AUDF[CHAN2] * 256 + p->AUDF[CHAN1] + DIVADD_HICLK_JOINED; + else + new_val = (p->AUDF[CHAN2] * 256 + p->AUDF[CHAN1] + DIVADD_LOCLK) * p->clockmult; + LOG_SOUND(("POKEY #%d chan1+2 %d\n", chip, new_val)); + } + else + { + new_val = (p->AUDF[CHAN2] + DIVADD_LOCLK) * p->clockmult; + LOG_SOUND(("POKEY #%d chan2 %d\n", chip, new_val)); + } + + p->volume[CHAN2] = (p->AUDC[CHAN2] & VOLUME_MASK) * POKEY_DEFAULT_GAIN; + p->divisor[CHAN2] = new_val; + if( new_val < p->counter[CHAN2] ) + p->counter[CHAN2] = new_val; + if( p->interrupt_cb && p->timer[TIMER2] ) + timer_adjust_ptr(p->timer[TIMER2], attotime_mul(p->clock_period, new_val), p->timer_period[TIMER2]); + p->audible[CHAN2] = !( + (p->AUDC[CHAN2] & VOLUME_ONLY) || + (p->AUDC[CHAN2] & VOLUME_MASK) == 0 || + ((p->AUDC[CHAN2] & PURE) && new_val < (p->samplerate_24_8 >> 8))); + if( !p->audible[CHAN2] ) + { + p->output[CHAN2] = 1; + p->counter[CHAN2] = 0x7fffffff; + /* 50% duty cycle should result in half volume */ + p->volume[CHAN2] >>= 1; + } + } + + if( ch_mask & (1 << CHAN3) ) + { + /* process channel 3 frequency */ + if( p->AUDCTL & CH3_HICLK ) + new_val = p->AUDF[CHAN3] + DIVADD_HICLK; + else + new_val = (p->AUDF[CHAN3] + DIVADD_LOCLK) * p->clockmult; + + LOG_SOUND(("POKEY #%d chan3 %d\n", chip, new_val)); + + p->volume[CHAN3] = (p->AUDC[CHAN3] & VOLUME_MASK) * POKEY_DEFAULT_GAIN; + p->divisor[CHAN3] = new_val; + if( new_val < p->counter[CHAN3] ) + p->counter[CHAN3] = new_val; + /* channel 3 does not have a timer associated */ + p->audible[CHAN3] = !( + (p->AUDC[CHAN3] & VOLUME_ONLY) || + (p->AUDC[CHAN3] & VOLUME_MASK) == 0 || + ((p->AUDC[CHAN3] & PURE) && new_val < (p->samplerate_24_8 >> 8))) || + (p->AUDCTL & CH1_FILTER); + if( !p->audible[CHAN3] ) + { + p->output[CHAN3] = 1; + p->counter[CHAN3] = 0x7fffffff; + /* 50% duty cycle should result in half volume */ + p->volume[CHAN3] >>= 1; + } + } + + if( ch_mask & (1 << CHAN4) ) + { + /* process channel 4 frequency */ + if( p->AUDCTL & CH34_JOINED ) + { + if( p->AUDCTL & CH3_HICLK ) + new_val = p->AUDF[CHAN4] * 256 + p->AUDF[CHAN3] + DIVADD_HICLK_JOINED; + else + new_val = (p->AUDF[CHAN4] * 256 + p->AUDF[CHAN3] + DIVADD_LOCLK) * p->clockmult; + LOG_SOUND(("POKEY #%d chan3+4 %d\n", chip, new_val)); + } + else + { + new_val = (p->AUDF[CHAN4] + DIVADD_LOCLK) * p->clockmult; + LOG_SOUND(("POKEY #%d chan4 %d\n", chip, new_val)); + } + + p->volume[CHAN4] = (p->AUDC[CHAN4] & VOLUME_MASK) * POKEY_DEFAULT_GAIN; + p->divisor[CHAN4] = new_val; + if( new_val < p->counter[CHAN4] ) + p->counter[CHAN4] = new_val; + if( p->interrupt_cb && p->timer[TIMER4] ) + timer_adjust_ptr(p->timer[TIMER4], attotime_mul(p->clock_period, new_val), p->timer_period[TIMER4]); + p->audible[CHAN4] = !( + (p->AUDC[CHAN4] & VOLUME_ONLY) || + (p->AUDC[CHAN4] & VOLUME_MASK) == 0 || + ((p->AUDC[CHAN4] & PURE) && new_val < (p->samplerate_24_8 >> 8))) || + (p->AUDCTL & CH2_FILTER); + if( !p->audible[CHAN4] ) + { + p->output[CHAN4] = 1; + p->counter[CHAN4] = 0x7fffffff; + /* 50% duty cycle should result in half volume */ + p->volume[CHAN4] >>= 1; + } + } +} + +WRITE8_HANDLER( pokey1_w ) +{ + pokey_register_w(0,offset,data); +} + +WRITE8_HANDLER( pokey2_w ) +{ + pokey_register_w(1,offset,data); +} + +WRITE8_HANDLER( pokey3_w ) +{ + pokey_register_w(2,offset,data); +} + +WRITE8_HANDLER( pokey4_w ) +{ + pokey_register_w(3,offset,data); +} + +WRITE8_HANDLER( quad_pokey_w ) +{ + int pokey_num = (offset >> 3) & ~0x04; + int control = (offset & 0x20) >> 2; + int pokey_reg = (offset % 8) | control; + + pokey_register_w(pokey_num, pokey_reg, data); +} + +void pokey1_serin_ready(int after) +{ + struct POKEYregisters *p = sndti_token(SOUND_POKEY, 0); + timer_set_ptr(attotime_mul(p->clock_period, after), p, pokey_serin_ready); +} + +void pokey2_serin_ready(int after) +{ + struct POKEYregisters *p = sndti_token(SOUND_POKEY, 1); + timer_set_ptr(attotime_mul(p->clock_period, after), p, pokey_serin_ready); +} + +void pokey3_serin_ready(int after) +{ + struct POKEYregisters *p = sndti_token(SOUND_POKEY, 2); + timer_set_ptr(attotime_mul(p->clock_period, after), p, pokey_serin_ready); +} + +void pokey4_serin_ready(int after) +{ + struct POKEYregisters *p = sndti_token(SOUND_POKEY, 3); + timer_set_ptr(attotime_mul(p->clock_period, after), p, pokey_serin_ready); +} + +void pokey_break_w(int chip, int shift) +{ + struct POKEYregisters *p = sndti_token(SOUND_POKEY, chip); + if( shift ) /* shift code ? */ + p->SKSTAT |= SK_SHIFT; + else + p->SKSTAT &= ~SK_SHIFT; + /* check if the break IRQ is enabled */ + if( p->IRQEN & IRQ_BREAK ) + { + /* set break IRQ status and call back the interrupt handler */ + p->IRQST |= IRQ_BREAK; + if( p->interrupt_cb ) + (*p->interrupt_cb)(IRQ_BREAK); + } +} + +void pokey1_break_w(int shift) +{ + pokey_break_w(0, shift); +} + +void pokey2_break_w(int shift) +{ + pokey_break_w(1, shift); +} + +void pokey3_break_w(int shift) +{ + pokey_break_w(2, shift); +} + +void pokey4_break_w(int shift) +{ + pokey_break_w(3, shift); +} + +void pokey_kbcode_w(int chip, int kbcode, int make) +{ + struct POKEYregisters *p = sndti_token(SOUND_POKEY, chip); + /* make code ? */ + if( make ) + { + p->KBCODE = kbcode; + p->SKSTAT |= SK_KEYBD; + if( kbcode & 0x40 ) /* shift code ? */ + p->SKSTAT |= SK_SHIFT; + else + p->SKSTAT &= ~SK_SHIFT; + + if( p->IRQEN & IRQ_KEYBD ) + { + /* last interrupt not acknowledged ? */ + if( p->IRQST & IRQ_KEYBD ) + p->SKSTAT |= SK_KBERR; + p->IRQST |= IRQ_KEYBD; + if( p->interrupt_cb ) + (*p->interrupt_cb)(IRQ_KEYBD); + } + } + else + { + p->KBCODE = kbcode; + p->SKSTAT &= ~SK_KEYBD; + } +} + +void pokey1_kbcode_w(int kbcode, int make) +{ + pokey_kbcode_w(0, kbcode, make); +} + +void pokey2_kbcode_w(int kbcode, int make) +{ + pokey_kbcode_w(1, kbcode, make); +} + +void pokey3_kbcode_w(int kbcode, int make) +{ + pokey_kbcode_w(2, kbcode, make); +} + +void pokey4_kbcode_w(int kbcode, int make) +{ + pokey_kbcode_w(3, kbcode, make); +} + + + + +/************************************************************************** + * Generic get_info + **************************************************************************/ + +static void pokey_set_info(void *token, UINT32 state, sndinfo *info) +{ + switch (state) + { + /* no parameters to set */ + } +} + + +void pokey_get_info(void *token, UINT32 state, sndinfo *info) +{ + switch (state) + { + /* --- the following bits of info are returned as 64-bit signed integers --- */ + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case SNDINFO_PTR_SET_INFO: info->set_info = pokey_set_info; break; + case SNDINFO_PTR_START: info->start = pokey_start; break; + case SNDINFO_PTR_STOP: /* Nothing */ break; + case SNDINFO_PTR_RESET: /* Nothing */ break; + + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case SNDINFO_STR_NAME: info->s = "POKEY"; break; + case SNDINFO_STR_CORE_FAMILY: info->s = "Atari custom"; break; + case SNDINFO_STR_CORE_VERSION: info->s = "4.51"; break; + case SNDINFO_STR_CORE_FILE: info->s = __FILE__; break; + case SNDINFO_STR_CORE_CREDITS: info->s = "Copyright (c) 2000-2007, The MAME Team"; break; + } +} + |