aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke Iannini <lukeiannini@users.sourceforge.net>2007-03-21 09:08:16 +0000
committerLuke Iannini <lukeiannini@users.sourceforge.net>2007-03-21 09:08:16 +0000
commit58e4256460bd553416498a8d9d8d7cdec50b4df6 (patch)
tree7fa5a82dc9628f9c812c2ecd4d78b05fda43fb40
This commit was generated by cvs2svn to compensate for changes in r7511,HEADsvn2git-rootsvn2git-headexternals/mmonoplayer
which included commits to RCS files with non-trunk default branches. svn path=/trunk/externals/mmonoplayer/; revision=7512
-rw-r--r--atari2600~.c485
1 files changed, 485 insertions, 0 deletions
diff --git a/atari2600~.c b/atari2600~.c
new file mode 100644
index 0000000..a0aff2d
--- /dev/null
+++ b/atari2600~.c
@@ -0,0 +1,485 @@
+/*****************************************************************************/
+/* */
+/* TIA Chip Sound Simulator for PD */
+/* Purpose: To emulate the sound generation hardware of the Atari TIA chip. */
+/* Author(s): Kyle Buza, Ron Fries */
+/* */
+/*****************************************************************************/
+
+#include <stdlib.h>
+#include "m_pd.h"
+
+static t_class *atari_2600_class;
+
+typedef signed char Int8;
+typedef signed int Int16;
+
+#ifdef WIN32
+#define int8 char
+#define int16 short
+#define int32 int
+#else
+#define int8 char
+#define int16 int
+#define int32 long
+#endif
+
+#define uint8 unsigned int8
+#define uint16 unsigned int16
+#define uint32 unsigned int32
+
+/* 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 DIV3_POLY5 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
+
+#define FALSE 0
+#define TRUE 1
+
+/* 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. */
+
+static uint8 Bit4[POLY4_SIZE] =
+ { 1,1,0,1,1,1,0,0,0,0,1,0,1,0,0 };
+
+static uint8 Bit5[POLY5_SIZE] =
+ { 0,0,1,0,1,1,0,0,1,1,1,1,1,0,0,0,1,1,0,1,1,1,0,1,0,1,0,0,0,0,1 };
+
+/* 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. */
+
+static uint8 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 };
+
+typedef struct _atari_2600
+{
+ t_object x_obj;
+ t_float x15;
+ t_float x16;
+ t_float x17;
+ t_float x18;
+ t_float x19;
+ t_float x1a;
+ long Bit9[POLY9_SIZE];
+ long P4[2];
+ long P5[2];
+ long P9[2];
+ long Div_n_cnt[2];
+ long Div_n_max[2];
+ long AUDC[2];
+ long AUDF[2];
+ long AUDV[2];
+ long Outvol[2];
+ long volume;
+} t_atari_2600;
+
+void *atari_2600_new(void);
+t_int *atari_2600_perform(t_int *w);
+void atari_2600_dsp(t_atari_2600 *x, t_signal **sp, short *count);
+
+void Tia_sound_init(t_atari_2600 *x, uint16 sample_freq, uint16 playback_freq);
+void Update_tia_sound(t_atari_2600 *x, uint16 addr, uint8 val);
+void Tia_process (t_atari_2600 *x, t_float *buffer, uint16 n);
+
+void atari_2600_tilde_setup(void)
+{
+ atari_2600_class = class_new(gensym("atari_2600~"),
+ (t_newmethod)atari_2600_new, 0, sizeof(t_atari_2600), CLASS_DEFAULT, A_DEFFLOAT, 0);
+ class_addmethod(atari_2600_class, (t_method)atari_2600_dsp, gensym("dsp"), 0);
+}
+
+void *atari_2600_new(void)
+{
+ t_atari_2600 *x = (t_atari_2600 *)pd_new(atari_2600_class);
+ x->x15 = 0;
+ x->x16 = 0;
+ x->x17 = 0;
+ x->x18 = 0;
+ x->x19 = 0;
+ x->x1a = 0;
+ floatinlet_new(&x->x_obj, &x->x15);
+ floatinlet_new(&x->x_obj, &x->x17);
+ floatinlet_new(&x->x_obj, &x->x19);
+ floatinlet_new(&x->x_obj, &x->x16);
+ floatinlet_new(&x->x_obj, &x->x18);
+ floatinlet_new(&x->x_obj, &x->x1a);
+ outlet_new(&x->x_obj, &s_signal);
+
+ Tia_sound_init(x, 32000, 32000);
+ return (x);
+}
+
+void atari_2600_dsp(t_atari_2600 *x, t_signal **sp, short *count)
+{
+ dsp_add(atari_2600_perform, 3, sp[0]->s_vec, sp[0]->s_n, x);
+}
+
+t_int *atari_2600_perform(t_int *w)
+{
+ t_float *outL = (t_float *)(w[1]);
+ t_atari_2600 *x = (t_atari_2600 *)(w[3]);
+
+ Update_tia_sound(x, 0x15, x->x15);
+ Update_tia_sound(x, 0x16, x->x16);
+ Update_tia_sound(x, 0x17, x->x17);
+ Update_tia_sound(x, 0x18, x->x18);
+ Update_tia_sound(x, 0x19, x->x19);
+ Update_tia_sound(x, 0x1a, x->x1a);
+
+ Tia_process(x, outL, (int)(w[2]));
+
+ return (w + 4);
+}
+
+
+/*****************************************************************************/
+/* */
+/* 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. */
+/* */
+/*****************************************************************************/
+/* */
+/* License Information and Copyright Notice */
+/* ======================================== */
+/* */
+/* TiaSound is Copyright(c) 1996 by 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. */
+/* */
+/*****************************************************************************/
+
+void Tia_sound_init (t_atari_2600 *x, uint16 sample_freq, uint16 playback_freq)
+{
+ uint8 chan;
+ int16 n;
+
+ /* fill the 9bit polynomial with random bits */
+ for (n=0; n<POLY9_SIZE; n++)
+ {
+ x->Bit9[n] = rand() & 0x01; /* fill poly9 with random bits */
+ }
+
+ /* initialize the local globals */
+ for (chan = CHAN1; chan <= CHAN2; chan++)
+ {
+ x->Outvol[chan] = 0;
+ x->Div_n_cnt[chan] = 0;
+ x->Div_n_max[chan] = 0;
+ x->AUDC[chan] = 0;
+ x->AUDF[chan] = 0;
+ x->AUDV[chan] = 0;
+ x->P4[chan] = 0;
+ x->P5[chan] = 0;
+ x->P9[chan] = 0;
+ }
+
+ x->volume = 100;
+}
+
+void Update_tia_sound(t_atari_2600 *x, uint16 addr, uint8 val)
+{
+ uint16 new_val = 0;
+ uint8 chan;
+
+ /* determine which address was changed */
+ switch (addr)
+ {
+ case AUDC0:
+ x->AUDC[0] = val & 0x0f;
+ chan = 0;
+ break;
+
+ case AUDC1:
+ x->AUDC[1] = val & 0x0f;
+ chan = 1;
+ break;
+
+ case AUDF0:
+ x->AUDF[0] = val & 0x1f;
+ chan = 0;
+ break;
+
+ case AUDF1:
+ x->AUDF[1] = val & 0x1f;
+ chan = 1;
+ break;
+
+ case AUDV0:
+ x->AUDV[0] = (val & 0x0f) << 3;
+ chan = 0;
+ break;
+
+ case AUDV1:
+ x->AUDV[1] = (val & 0x0f) << 3;
+ 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 (x->AUDC[chan] == SET_TO_1)
+ {
+ /* indicate the clock is zero so no processing will occur */
+ new_val = 0;
+
+ /* and set the output to the selected volume */
+ x->Outvol[chan] = x->AUDV[chan];
+ }
+ else
+ {
+ /* otherwise calculate the 'divide by N' value */
+ new_val = x->AUDF[chan] + 1;
+
+ /* if bits 2 & 3 are set, then multiply the 'div by n' count by 3 */
+ if ((x->AUDC[chan] & DIV3_MASK) == DIV3_MASK)
+ {
+ new_val *= 3;
+ }
+ }
+
+ /* only reset those channels that have changed */
+ if (new_val != x->Div_n_max[chan])
+ {
+ /* reset the divide by n counters */
+ x->Div_n_max[chan] = new_val;
+
+ /* if the channel is now volume only or was volume only */
+ if ((x->Div_n_cnt[chan] == 0) || (new_val == 0))
+ {
+ /* reset the counter (otherwise let it complete the previous) */
+ x->Div_n_cnt[chan] = new_val;
+ }
+ }
+ }
+
+}
+
+void Tia_process(t_atari_2600 *x, t_float *buffer, uint16 n)
+{
+ uint8 audc0,audv0,audc1,audv1;
+ uint8 div_n_cnt0,div_n_cnt1;
+ uint8 p5_0, p5_1,outvol_0,outvol_1;
+
+ audc0 = x->AUDC[0];
+ audv0 = x->AUDV[0];
+ audc1 = x->AUDC[1];
+ audv1 = x->AUDV[1];
+
+ /* make temporary local copy */
+ p5_0 = x->P5[0];
+ p5_1 = x->P5[1];
+ outvol_0 = x->Outvol[0];
+ outvol_1 = x->Outvol[1];
+ div_n_cnt0 = x->Div_n_cnt[0];
+ div_n_cnt1 = x->Div_n_cnt[1];
+
+ /* loop until the buffer is filled */
+ while (n)
+ {
+ /* Process channel 0 */
+ if (div_n_cnt0 > 1)
+ {
+ div_n_cnt0--;
+ }
+ else if (div_n_cnt0 == 1)
+ {
+ div_n_cnt0 = x->Div_n_max[0];
+
+ /* the 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) && Bit5[p5_0]))
+ {
+
+ if (audc0 & 0x04) /* pure modified clock selected */
+ {
+ 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 */
+ x->P9[0]++;
+ if (x->P9[0] == POLY9_SIZE)
+ x->P9[0] = 0;
+
+ if (x->Bit9[x->P9[0]]) {
+ outvol_0 = audv0;
+ }
+ else
+ outvol_0 = 0;
+ }
+ else /* must be poly5 */
+ {
+ if (Bit5[p5_0]) {
+ outvol_0 = audv0;
+ }
+ else
+ outvol_0 = 0;
+ }
+ }
+ else /* poly4 is the only remaining option */
+ {
+ /* inc the poly4 counter */
+ x->P4[0]++;
+ if (x->P4[0] == POLY4_SIZE)
+ x->P4[0] = 0;
+
+ if (Bit4[x->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)
+ {
+ div_n_cnt1 = x->Div_n_max[1];
+
+ /* the 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) && Bit5[p5_1]))
+ {
+ if (audc1 & 0x04) /* pure modified clock selected */
+ {
+ 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 */
+ x->P9[1]++;
+ if (x->P9[1] == POLY9_SIZE)
+ x->P9[1] = 0;
+
+ if (x->Bit9[x->P9[1]])
+ outvol_1 = audv1;
+ else
+ outvol_1 = 0;
+ }
+ else /* must be poly5 */
+ {
+ if (Bit5[p5_1])
+ outvol_1 = audv1;
+ else
+ outvol_1 = 0;
+ }
+ }
+ else /* poly4 is the only remaining option */
+ {
+ /* inc the poly4 counter */
+ x->P4[1]++;
+ if (x->P4[1] == POLY4_SIZE)
+ x->P4[1] = 0;
+
+ if (Bit4[x->P4[1]])
+ outvol_1 = audv1;
+ else
+ outvol_1 = 0;
+ }
+ }
+ }
+ {
+ uint32 val = ((((uint32)outvol_0 + (uint32)outvol_1) * x->volume)/100);
+ float va = (((float)val) - 100.0)/100.0;
+ *(buffer++) = va;
+ n--;
+ }
+ }
+
+ /* save for next round */
+ x->P5[0] = p5_0;
+ x->P5[1] = p5_1;
+ x->Outvol[0] = outvol_0;
+ x->Outvol[1] = outvol_1;
+ x->Div_n_cnt[0] = div_n_cnt0;
+ x->Div_n_cnt[1] = div_n_cnt1;
+}