// license:GPL-2.0+ // copyright-holders:Ron Fries,Dan Boris /*****************************************************************************/ /* */ /* Module: TIA Chip Sound Simulator */ /* Purpose: To emulate the sound generation hardware of the Atari TIA chip. */ /* Author: Ron Fries */ /* */ /* Revision History: */ /* 10-Sep-96 - V1.0 - Initial Release */ /* 14-Jan-97 - V1.1 - Cleaned up sound output by eliminating counter */ /* reset. */ /* 30-Oct-98 - Modified for use in MESS by Dan Boris */ /* 28-Jul-01 - Added support for sample rates > TIA clock rate, */ /* through oversampling */ /* 30-Jun-07 - Updated the poly generation. Improved handling of the */ /* POLY5_DIV3 mode. (Wilbert Pol) */ /* */ /* */ /* */ /*****************************************************************************/ /* */ /* License Information and Copyright Notice */ /* ======================================== */ /* */ /* TiaSound is Copyright Ron Fries */ /* */ /* This library is free software; you can redistribute it and/or modify it */ /* under the terms of version 2 of the GNU Library General Public License */ /* as published by the Free Software Foundation. */ /* */ /* This library is distributed in the hope that it will be useful, but */ /* WITHOUT ANY WARRANTY; without even the implied warranty of */ /* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library */ /* General Public License for more details. */ /* To obtain a copy of the GNU Library General Public License, write to the */ /* Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* */ /* Any permitted reproduction of these routines, in whole or in part, must */ /* bear this legend. */ /* */ /*****************************************************************************/ #include "emu.h" #include "tiaintf.h" #include "tiasound.h" /* number of bits to shift left AUDV0/AUDV1 registers for 16 bit volume */ #define AUDV_SHIFT 10 /* CONSTANT DEFINITIONS */ /* definitions for AUDCx (15, 16) */ #define SET_TO_1 0x00 /* 0000 */ #define POLY4 0x01 /* 0001 */ #define DIV31_POLY4 0x02 /* 0010 */ #define POLY5_POLY4 0x03 /* 0011 */ #define PURE 0x04 /* 0100 */ #define PURE2 0x05 /* 0101 */ #define DIV31_PURE 0x06 /* 0110 */ #define POLY5_2 0x07 /* 0111 */ #define POLY9 0x08 /* 1000 */ #define POLY5 0x09 /* 1001 */ #define DIV31_POLY5 0x0a /* 1010 */ #define POLY5_POLY5 0x0b /* 1011 */ #define DIV3_PURE 0x0c /* 1100 */ #define DIV3_PURE2 0x0d /* 1101 */ #define DIV93_PURE 0x0e /* 1110 */ #define POLY5_DIV3 0x0f /* 1111 */ #define DIV3_MASK 0x0c #define AUDC0 0x15 #define AUDC1 0x16 #define AUDF0 0x17 #define AUDF1 0x18 #define AUDV0 0x19 #define AUDV1 0x1a /* the size (in entries) of the 4 polynomial tables */ #define POLY4_SIZE 0x000f #define POLY5_SIZE 0x001f #define POLY9_SIZE 0x01ff /* channel definitions */ #define CHAN1 0 #define CHAN2 1 /* LOCAL GLOBAL VARIABLE DEFINITIONS */ namespace { struct tia { /* structures to hold the 6 tia sound control bytes */ uint8_t AUDC[2]; /* AUDCx (15, 16) */ uint8_t AUDF[2]; /* AUDFx (17, 18) */ int16_t AUDV[2]; /* AUDVx (19, 1A) */ int16_t Outvol[2]; /* last output volume for each channel */ int tia_gain; /* initialized in tia_sound_init() */ /* Initialze the bit patterns for the polynomials. */ /* The 4bit and 5bit patterns are the identical ones used in the tia chip. */ /* Though the patterns could be packed with 8 bits per byte, using only a */ /* single bit per byte keeps the math simple, which is important for */ /* efficient processing. */ /* HJB: poly bits are initialized at runtime */ uint8_t Bit4[POLY4_SIZE]; uint8_t Bit5[POLY5_SIZE]; uint8_t Bit9[POLY9_SIZE]; uint8_t P4[2]; /* Position pointer for the 4-bit POLY array */ uint8_t P5[2]; /* Position pointer for the 5-bit POLY array */ uint16_t P9[2]; /* Position pointer for the 9-bit POLY array */ uint8_t Div_n_cnt[2]; /* Divide by n counter. one for each channel */ uint8_t Div_n_max[2]; /* Divide by n maximum, one for each channel */ uint8_t Div_3_cnt[2]; /* Div 3 counter, used for POLY5_DIV3 mode */ /* In my routines, I treat the sample output as another divide by N counter. */ /* For better accuracy, the Samp_n_cnt has a fixed binary decimal point */ /* which has 8 binary digits to the right of the decimal point. */ uint16_t Samp_n_max; /* Sample max, multiplied by 256 */ uint16_t Samp_n_cnt; /* Sample cnt. */ int oversampling; /* Added oversampling for sample_rate > clock_rate */ }; /* I've treated the 'Div by 31' counter as another polynomial because of */ /* the way it operates. It does not have a 50% duty cycle, but instead */ /* has a 13:18 ratio (of course, 13+18 = 31). This could also be */ /* implemented by using counters. */ constexpr uint8_t Div31[POLY5_SIZE] = {0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; } // anonymous namespace /*****************************************************************************/ /* Module: tia_sound_w() */ /* Purpose: To process the latest control values stored in the AUDF, AUDC, */ /* and AUDV registers. It pre-calculates as much information as */ /* possible for better performance. This routine has not been */ /* optimized. */ /* */ /* Author: Ron Fries */ /* Date: January 14, 1997 */ /* */ /* Inputs: addr - the address of the parameter to be changed */ /* val - the new value to be placed in the specified address */ /* */ /* Outputs: Adjusts local globals - no return value */ /* */ /*****************************************************************************/ void tia_write(void *_chip, offs_t offset, uint8_t data) { struct tia *chip = (struct tia *)_chip; uint16_t new_val; uint8_t chan; /* determine which address was changed */ switch (offset) { case AUDC0: chip->AUDC[0] = data & 0x0f; chan = 0; break; case AUDC1: chip->AUDC[1] = data & 0x0f; chan = 1; break; case AUDF0: chip->AUDF[0] = data & 0x1f; chan = 0; break; case AUDF1: chip->AUDF[1] = data & 0x1f; chan = 1; break; case AUDV0: chip->AUDV[0] = ((data & 0x0f) << AUDV_SHIFT); chan = 0; break; case AUDV1: chip->AUDV[1] = ((data & 0x0f) << AUDV_SHIFT); chan = 1; break; default: chan = 255; break; } /* if the output value changed */ if (chan != 255) { /* an AUDC value of 0 is a special case */ if (chip->AUDC[chan] == SET_TO_1 || chip->AUDC[chan] == POLY5_POLY5) { /* indicate the clock is zero so no processing will occur */ new_val = 0; /* and set the output to the selected volume */ chip->Outvol[chan] = chip->AUDV[chan]; } else { /* otherwise calculate the 'divide by N' value */ new_val = chip->AUDF[chan] + 1; /* if bits 2 & 3 are set, then multiply the 'div by n' count by 3 */ if ((chip->AUDC[chan] & DIV3_MASK) == DIV3_MASK && chip->AUDC[chan] != POLY5_DIV3) { new_val *= 3; } } /* only reset those channels that have changed */ if (new_val != chip->Div_n_max[chan]) { /* reset the divide by n counters */ chip->Div_n_max[chan] = new_val; /* if the channel is now volume only or was volume only */ if ((chip->Div_n_cnt[chan] == 0) || (new_val == 0)) { /* reset the counter (otherwise let it complete the previous) */ chip->Div_n_cnt[chan] = new_val; } } } } /*****************************************************************************/ /* Module: tia_process() */ /* Purpose: To fill the output buffer with the sound output based on the */ /* tia chip parameters. This routine has been optimized. */ /* */ /* Author: Ron Fries */ /* Date: September 10, 1996 */ /* */ /* Inputs: *buffer - pointer to the buffer where the audio output will */ /* be placed */ /* n - size of the playback buffer */ /* */ /* Outputs: the buffer will be filled with n bytes of audio - no return val */ /* */ /*****************************************************************************/ void tia_process(void *_chip, stream_sample_t *buffer, int length) { struct tia *chip = (struct tia *)_chip; uint8_t audc0, audc1; uint8_t div_n_cnt0, div_n_cnt1; uint8_t p5_0, p5_1; int16_t audv0, audv1, outvol_0, outvol_1; audc0 = chip->AUDC[0]; audc1 = chip->AUDC[1]; audv0 = chip->AUDV[0]; audv1 = chip->AUDV[1]; /* make temporary local copy */ p5_0 = chip->P5[0]; p5_1 = chip->P5[1]; outvol_0 = chip->Outvol[0]; outvol_1 = chip->Outvol[1]; div_n_cnt0 = chip->Div_n_cnt[0]; div_n_cnt1 = chip->Div_n_cnt[1]; /* loop until the buffer is filled */ while (length > 0) { /* Process channel 0 */ if (div_n_cnt0 > 1) { div_n_cnt0--; } else if (div_n_cnt0 == 1) { int prev_bit5 = chip->Bit5[p5_0]; div_n_cnt0 = chip->Div_n_max[0]; /* the chip->P5 counter has multiple uses, so we inc it here */ p5_0++; if (p5_0 == POLY5_SIZE) p5_0 = 0; /* check clock modifier for clock tick */ if ((audc0 & 0x02) == 0 || ((audc0 & 0x01) == 0 && Div31[p5_0]) || ((audc0 & 0x01) == 1 && chip->Bit5[p5_0]) || ((audc0 & 0x0f) == POLY5_DIV3 && chip->Bit5[p5_0] != prev_bit5)) { if (audc0 & 0x04) /* pure modified clock selected */ { if ((audc0 & 0x0f) == POLY5_DIV3) /* POLY5 -> DIV3 mode */ { if ( chip->Bit5[p5_0] != prev_bit5 ) { chip->Div_3_cnt[0]--; if ( ! chip->Div_3_cnt[0] ) { chip->Div_3_cnt[0] = 3; if (outvol_0) outvol_0 = 0; else outvol_0 = audv0; } } } else if (outvol_0) /* if the output was set */ outvol_0 = 0; /* turn it off */ else outvol_0 = audv0; /* else turn it on */ } else if (audc0 & 0x08) /* check for p5/p9 */ { if (audc0 == POLY9) /* check for poly9 */ { /* inc the poly9 counter */ chip->P9[0]++; if (chip->P9[0] == POLY9_SIZE) chip->P9[0] = 0; if (chip->Bit9[chip->P9[0]]) outvol_0 = audv0; else outvol_0 = 0; } else if ( audc0 & 0x02 ) { if (outvol_0 || audc0 & 0x01 ) outvol_0 = 0; else outvol_0 = audv0; } else /* must be poly5 */ { if (chip->Bit5[p5_0]) outvol_0 = audv0; else outvol_0 = 0; } } else /* poly4 is the only remaining option */ { /* inc the poly4 counter */ chip->P4[0]++; if (chip->P4[0] == POLY4_SIZE) chip->P4[0] = 0; if (chip->Bit4[chip->P4[0]]) outvol_0 = audv0; else outvol_0 = 0; } } } /* Process channel 1 */ if (div_n_cnt1 > 1) { div_n_cnt1--; } else if (div_n_cnt1 == 1) { int prev_bit5 = chip->Bit5[p5_1]; div_n_cnt1 = chip->Div_n_max[1]; /* the chip->P5 counter has multiple uses, so we inc it here */ p5_1++; if (p5_1 == POLY5_SIZE) p5_1 = 0; /* check clock modifier for clock tick */ if ((audc1 & 0x02) == 0 || ((audc1 & 0x01) == 0 && Div31[p5_1]) || ((audc1 & 0x01) == 1 && chip->Bit5[p5_1]) || ((audc1 & 0x0f) == POLY5_DIV3 && chip->Bit5[p5_1] != prev_bit5)) { if (audc1 & 0x04) /* pure modified clock selected */ { if ((audc1 & 0x0f) == POLY5_DIV3) /* POLY5 -> DIV3 mode */ { if ( chip->Bit5[p5_1] != prev_bit5 ) { chip->Div_3_cnt[1]--; if ( ! chip->Div_3_cnt[1] ) { chip->Div_3_cnt[1] = 3; if (outvol_1) outvol_1 = 0; else outvol_1 = audv1; } } } else if (outvol_1) /* if the output was set */ outvol_1 = 0; /* turn it off */ else outvol_1 = audv1; /* else turn it on */ } else if (audc1 & 0x08) /* check for p5/p9 */ { if (audc1 == POLY9) /* check for poly9 */ { /* inc the poly9 counter */ chip->P9[1]++; if (chip->P9[1] == POLY9_SIZE) chip->P9[1] = 0; if (chip->Bit9[chip->P9[1]]) outvol_1 = audv1; else outvol_1 = 0; } else if ( audc1 & 0x02 ) { if (outvol_1 || audc1 & 0x01 ) outvol_1 = 0; else outvol_1 = audv1; } else /* must be poly5 */ { if (chip->Bit5[p5_1]) outvol_1 = audv1; else outvol_1 = 0; } } else /* poly4 is the only remaining option */ { /* inc the poly4 counter */ chip->P4[1]++; if (chip->P4[1] == POLY4_SIZE) chip->P4[1] = 0; if (chip->Bit4[chip->P4[1]]) outvol_1 = audv1; else outvol_1 = 0; } } } if (!chip->oversampling) { /* decrement the sample counter - value is 256 since the lower * byte contains the fractional part */ chip->Samp_n_cnt -= 256; /* if the count down has reached zero */ if (chip->Samp_n_cnt < 256) { /* adjust the sample counter */ chip->Samp_n_cnt += chip->Samp_n_max; /* calculate the latest output value and place in buffer */ *buffer++ = outvol_0 + outvol_1; /* and indicate one less byte to process */ length--; } } else { do { /* decrement the sample counter - value is 256 since the lower * byte contains the fractional part */ chip->Samp_n_cnt -= 256; /* calculate the latest output value and place in buffer */ *buffer++ = outvol_0 + outvol_1; length--; } while ((chip->Samp_n_cnt >= 256) && (length > 0)); /* adjust the sample counter if necessary */ if (chip->Samp_n_cnt < 256) chip->Samp_n_cnt += chip->Samp_n_max; } } /* save for next round */ chip->P5[0] = p5_0; chip->P5[1] = p5_1; chip->Outvol[0] = outvol_0; chip->Outvol[1] = outvol_1; chip->Div_n_cnt[0] = div_n_cnt0; chip->Div_n_cnt[1] = div_n_cnt1; } static void poly_init(uint8_t *poly, int size, int f0, int f1) { int mask = (1 << size) - 1; int i, x = mask; for (i = 0; i < mask; i++) { int bit0 = ( ( size - f0 ) ? ( x >> ( size - f0 ) ) : x ) & 0x01; int bit1 = ( ( size - f1 ) ? ( x >> ( size - f1 ) ) : x ) & 0x01; poly[i] = x & 1; /* calculate next bit */ x = ( x >> 1 ) | ( ( bit0 ^ bit1 ) << ( size - 1) ); } } static void tia_save_state(device_t *device, tia *tia) { device->save_item(NAME(tia->AUDC)); device->save_item(NAME(tia->AUDF)); device->save_item(NAME(tia->AUDV)); device->save_item(NAME(tia->Outvol)); device->save_item(NAME(tia->P4)); device->save_item(NAME(tia->P5)); device->save_item(NAME(tia->P9)); device->save_item(NAME(tia->Div_n_cnt)); device->save_item(NAME(tia->Div_n_max)); device->save_item(NAME(tia->Div_3_cnt)); device->save_item(NAME(tia->Samp_n_cnt)); device->save_item(NAME(tia->oversampling)); } /*****************************************************************************/ /* Module: tia_sh_start() */ /* Purpose: to handle the power-up initialization functions */ /* these functions should only be executed on a cold-restart */ /* */ /* Author: Ron Fries */ /* Date: September 10, 1996 */ /* */ /* Inputs: sound_config *msound */ /* is a pointer to the struct TIAInterface parameters */ /* */ /* Outputs: returns zero on success */ /* */ /*****************************************************************************/ void *tia_sound_init(device_t *device, int clock, int sample_rate, int gain) { struct tia *chip; int chan; chip = global_alloc_clear(); /* set the gain factor (normally use TIA_DEFAULT_GAIN) */ chip->tia_gain = gain; /* fill the polynomials */ poly_init(chip->Bit4, 4, 4, 3); poly_init(chip->Bit5, 5, 5, 3); poly_init(chip->Bit9, 9, 9, 5); /* calculate the sample 'divide by N' value based on the playback freq. */ chip->Samp_n_max = ((uint16_t)(uint32_t)clock << 8) / sample_rate; chip->Samp_n_cnt = chip->Samp_n_max; /* initialize all bits of the sample counter */ if (chip->Samp_n_max < 256) /* we need to use oversampling for sample_rate > clock_rate */ { chip->Samp_n_max = ((uint16_t)(uint32_t)sample_rate << 8) / clock; chip->Samp_n_cnt = chip->Samp_n_max; chip->oversampling = 1; } /* initialize the local globals */ for (chan = CHAN1; chan <= CHAN2; chan++) { chip->Outvol[chan] = 0; chip->Div_n_cnt[chan] = 0; chip->Div_n_max[chan] = 0; chip->Div_3_cnt[chan] = 3; chip->AUDC[chan] = 0; chip->AUDF[chan] = 0; chip->AUDV[chan] = 0; chip->P4[chan] = 0; chip->P5[chan] = 0; chip->P9[chan] = 0; } tia_save_state(device, chip); return chip; } void tia_sound_free(void *chip) { global_free((struct tia *)chip); }