summaryrefslogtreecommitdiffstatshomepage
path: root/src/mame/audio/galaxian.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mame/audio/galaxian.c')
-rw-r--r--src/mame/audio/galaxian.c533
1 files changed, 533 insertions, 0 deletions
diff --git a/src/mame/audio/galaxian.c b/src/mame/audio/galaxian.c
new file mode 100644
index 00000000000..fca80a2e262
--- /dev/null
+++ b/src/mame/audio/galaxian.c
@@ -0,0 +1,533 @@
+#include "driver.h"
+#include "streams.h"
+#include "sound/samples.h"
+#include "includes/galaxian.h"
+
+#define VERBOSE 0
+
+#define NEW_LFO 0
+
+#define XTAL 18432000
+
+#define SOUND_CLOCK (XTAL/6/2) /* 1.536 MHz */
+
+#define RNG_RATE (XTAL/3) /* RNG clock is XTAL/3 */
+#define NOISE_RATE (XTAL/3/192/2/2) /* 2V = 8kHz */
+#define NOISE_LENGTH (NOISE_RATE*4) /* four seconds of noise */
+
+#define SHOOT_RATE 2672
+#define SHOOT_LENGTH 13000
+
+#define TOOTHSAW_LENGTH 16
+#define TOOTHSAW_VOLUME 36
+#define STEPS 16
+#define LFO_VOLUME 0.06
+#define SHOOT_VOLUME 0.50
+#define NOISE_VOLUME 0.50
+#define NOISE_AMPLITUDE (70*256)
+#define TOOTHSAW_AMPLITUDE (64*256)
+
+/* see comments in galaxian_sh_update() */
+#define MINFREQ (139-139/3)
+#define MAXFREQ (139+139/3)
+
+#if VERBOSE
+#define LOG(x) logerror x
+#else
+#define LOG(x)
+#endif
+
+static emu_timer *lfotimer = NULL;
+static INT32 freq = MAXFREQ;
+
+#define STEP 1
+
+static INT32 noise_enable;
+static INT32 noisevolume;
+static INT16 *noisewave;
+static INT16 *shootwave;
+static INT32 shoot_length;
+static INT32 shoot_rate;
+
+static UINT8 last_port2=0;
+
+static INT16 tonewave[4][TOOTHSAW_LENGTH];
+static INT32 pitch,vol;
+
+static INT32 counter, countdown;
+static INT32 lfobit[4];
+
+static INT16 backgroundwave[32] =
+{
+ 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000,
+ 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000, 0x4000,
+ -0x4000,-0x4000,-0x4000,-0x4000,-0x4000,-0x4000,-0x4000,-0x4000,
+ -0x4000,-0x4000,-0x4000,-0x4000,-0x4000,-0x4000,-0x4000,-0x4000,
+};
+
+#define CHANNEL_NOISE 0
+#define CHANNEL_SHOOT 1
+#define CHANNEL_LFO 2
+static sound_stream * tone_stream;
+static emu_timer *noisetimer;
+
+static TIMER_CALLBACK( lfo_timer_cb );
+static TIMER_CALLBACK( galaxian_sh_update );
+
+static void tone_update(void *param, stream_sample_t **input, stream_sample_t **output, int length)
+{
+ stream_sample_t *buffer = output[0];
+ int i,j;
+ INT16 *w = tonewave[vol];
+
+ /* only update if we have non-zero volume and frequency */
+ if( pitch != 0xff )
+ {
+ for (i = 0; i < length; i++)
+ {
+ int mix = 0;
+
+ for (j = 0;j < STEPS;j++)
+ {
+ if (countdown >= 256)
+ {
+ counter = (counter + 1) % TOOTHSAW_LENGTH;
+ countdown = pitch;
+ }
+ countdown++;
+
+ mix += w[counter];
+ }
+ *buffer++ = mix / STEPS;
+ }
+ }
+ else
+ {
+ for( i = 0; i < length; i++ )
+ *buffer++ = 0;
+ }
+}
+
+WRITE8_HANDLER( galaxian_pitch_w )
+{
+ stream_update(tone_stream);
+
+ pitch = data;
+}
+
+WRITE8_HANDLER( galaxian_vol_w )
+{
+ stream_update(tone_stream);
+
+ /* offset 0 = bit 0, offset 1 = bit 1 */
+ vol = (vol & ~(1 << offset)) | ((data & 1) << offset);
+}
+
+
+static TIMER_CALLBACK( noise_timer_cb )
+{
+ if( !noise_enable && noisevolume > 0 )
+ {
+ noisevolume -= (noisevolume / 10) + 1;
+ sample_set_volume(CHANNEL_NOISE,noisevolume / 100.0);
+ }
+}
+
+WRITE8_HANDLER( galaxian_noise_enable_w )
+{
+ noise_enable = data & 1;
+
+ if( noise_enable )
+ {
+ noisevolume = 100;
+ sample_set_volume(CHANNEL_NOISE,noisevolume / 100.0);
+ }
+}
+
+WRITE8_HANDLER( galaxian_shoot_enable_w )
+{
+ if( data & 1 && !(last_port2 & 1) )
+ {
+ sample_start_raw(CHANNEL_SHOOT, shootwave, shoot_length, shoot_rate, 0);
+ sample_set_volume(CHANNEL_SHOOT,SHOOT_VOLUME);
+ }
+ last_port2=data;
+}
+
+
+static void galaxian_sh_start(void)
+{
+ int i, j, sweep, charge, countdown, generator, bit1, bit2;
+
+ sample_set_volume(CHANNEL_NOISE, NOISE_VOLUME);
+ sample_set_volume(CHANNEL_SHOOT, SHOOT_VOLUME);
+ sample_set_volume(CHANNEL_LFO+0, LFO_VOLUME);
+ sample_set_volume(CHANNEL_LFO+1, LFO_VOLUME);
+ sample_set_volume(CHANNEL_LFO+2, LFO_VOLUME);
+
+ noisewave = auto_malloc(NOISE_LENGTH * sizeof(INT16));
+
+#define SHOOT_SEC 2
+ shoot_rate = Machine->sample_rate;
+ shoot_length = SHOOT_SEC * shoot_rate;
+ shootwave = auto_malloc(shoot_length * sizeof(INT16));
+
+ /*
+ * The RNG shifter is clocked with RNG_RATE, bit 17 is
+ * latched every 2V cycles (every 2nd scanline).
+ * This signal is used as a noise source.
+ */
+ generator = 0;
+ countdown = NOISE_RATE / 2;
+ for( i = 0; i < NOISE_LENGTH; i++ )
+ {
+ countdown -= RNG_RATE;
+ while( countdown < 0 )
+ {
+ generator <<= 1;
+ bit1 = (~generator >> 17) & 1;
+ bit2 = (generator >> 5) & 1;
+ if (bit1 ^ bit2) generator |= 1;
+ countdown += NOISE_RATE;
+ }
+ noisewave[i] = ((generator >> 17) & 1) ? NOISE_AMPLITUDE : -NOISE_AMPLITUDE;
+ }
+
+ /* dummy */
+ sweep = 100;
+ charge = +2;
+ j=0;
+ {
+#define R41__ 100000
+#define R44__ 10000
+#define R45__ 22000
+#define R46__ 10000
+#define R47__ 2200
+#define R48__ 2200
+#define C25__ 0.000001
+#define C27__ 0.00000001
+#define C28__ 0.000047
+#define C29__ 0.00000001
+#define IC8L3_L 0.2 /* 7400 L level */
+#define IC8L3_H 4.5 /* 7400 H level */
+#define NOISE_L 0.2 /* 7474 L level */
+#define NOISE_H 4.5 /* 7474 H level */
+/*
+ key on/off time is programmable
+ Therefore, it is necessary to make separate sample with key on/off.
+ And, calculate the playback point according to the voltage of c28.
+*/
+#define SHOOT_KEYON_TIME 0.1 /* second */
+/*
+ NE555-FM input calculation is wrong.
+ The frequency is not proportional to the voltage of FM input.
+ And, duty will be changed,too.
+*/
+#define NE555_FM_ADJUST_RATE 0.80
+ /* discharge : 100K * 1uF */
+ double v = 5.0;
+ double vK = (shoot_rate) ? exp(-1 / (R41__*C25__) / shoot_rate) : 0;
+ /* -- SHOOT KEY port -- */
+ double IC8L3 = IC8L3_L; /* key on */
+ int IC8Lcnt = SHOOT_KEYON_TIME * shoot_rate; /* count for key off */
+ /* C28 : KEY port capacity */
+ /* connection : 8L-3 - R47(2.2K) - C28(47u) - R48(2.2K) - C29 */
+ double c28v = IC8L3_H - (IC8L3_H-(NOISE_H+NOISE_L)/2)/(R46__+R47__+R48__)*R47__;
+ double c28K = (shoot_rate) ? exp(-1 / (22000 * 0.000047 ) / shoot_rate) : 0;
+ /* C29 : NOISE capacity */
+ /* connection : NOISE - R46(10K) - C29(0.1u) - R48(2.2K) - C28 */
+ double c29v = IC8L3_H - (IC8L3_H-(NOISE_H+NOISE_L)/2)/(R46__+R47__+R48__)*(R47__+R48__);
+ double c29K1 = (shoot_rate) ? exp(-1 / (22000 * 0.00000001 ) / shoot_rate) : 0; /* form C28 */
+ double c29K2 = (shoot_rate) ? exp(-1 / (100000 * 0.00000001 ) / shoot_rate) : 0; /* from noise */
+ /* NE555 timer */
+ /* RA = 10K , RB = 22K , C=.01u ,FM = C29 */
+ double ne555cnt = 0;
+ double ne555step = (shoot_rate) ? ((1.44/((R44__+R45__*2)*C27__)) / shoot_rate) : 0;
+ double ne555duty = (double)(R44__+R45__)/(R44__+R45__*2); /* t1 duty */
+ double ne555sr; /* threshold (FM) rate */
+ /* NOISE source */
+ double ncnt = 0.0;
+ double nstep = (shoot_rate) ? ((double)NOISE_RATE / shoot_rate) : 0;
+ double noise_sh2; /* voltage level */
+
+ for( i = 0; i < shoot_length; i++ )
+ {
+ /* noise port */
+ noise_sh2 = noisewave[(int)ncnt % NOISE_LENGTH] == NOISE_AMPLITUDE ? NOISE_H : NOISE_L;
+ ncnt+=nstep;
+ /* calculate NE555 threshold level by FM input */
+ ne555sr = c29v*NE555_FM_ADJUST_RATE / (5.0*2/3);
+ /* calc output */
+ ne555cnt += ne555step;
+ if( ne555cnt >= ne555sr) ne555cnt -= ne555sr;
+ if( ne555cnt < ne555sr*ne555duty )
+ {
+ /* t1 time */
+ shootwave[i] = v/5*0x7fff;
+ /* discharge output level */
+ if(IC8L3==IC8L3_H)
+ v *= vK;
+ }
+ else
+ shootwave[i] = 0;
+ /* C28 charge/discharge */
+ c28v += (IC8L3-c28v) - (IC8L3-c28v)*c28K; /* from R47 */
+ c28v += (c29v-c28v) - (c29v-c28v)*c28K; /* from R48 */
+ /* C29 charge/discharge */
+ c29v += (c28v-c29v) - (c28v-c29v)*c29K1; /* from R48 */
+ c29v += (noise_sh2-c29v) - (noise_sh2-c29v)*c29K2; /* from R46 */
+ /* key off */
+ if(IC8L3==IC8L3_L && --IC8Lcnt==0)
+ IC8L3=IC8L3_H;
+ }
+ }
+
+ memset(tonewave, 0, sizeof(tonewave));
+
+ for( i = 0; i < TOOTHSAW_LENGTH; i++ )
+ {
+ #define V(r0,r1) 2*TOOTHSAW_AMPLITUDE*(r0)/(r0+r1)-TOOTHSAW_AMPLITUDE
+ double r0a = 1.0/1e12, r1a = 1.0/1e12;
+ double r0b = 1.0/1e12, r1b = 1.0/1e12;
+
+ /* #0: VOL1=0 and VOL2=0
+ * only the 33k and the 22k resistors R51 and R50
+ */
+ if( i & 1 )
+ {
+ r1a += 1.0/33000;
+ r1b += 1.0/33000;
+ }
+ else
+ {
+ r0a += 1.0/33000;
+ r0b += 1.0/33000;
+ }
+ if( i & 4 )
+ {
+ r1a += 1.0/22000;
+ r1b += 1.0/22000;
+ }
+ else
+ {
+ r0a += 1.0/22000;
+ r0b += 1.0/22000;
+ }
+ tonewave[0][i] = V(1.0/r0a, 1.0/r1a);
+
+ /* #1: VOL1=1 and VOL2=0
+ * add the 10k resistor R49 for bit QC
+ */
+ if( i & 4 )
+ r1a += 1.0/10000;
+ else
+ r0a += 1.0/10000;
+ tonewave[1][i] = V(1.0/r0a, 1.0/r1a);
+
+ /* #2: VOL1=0 and VOL2=1
+ * add the 15k resistor R52 for bit QD
+ */
+ if( i & 8 )
+ r1b += 1.0/15000;
+ else
+ r0b += 1.0/15000;
+ tonewave[2][i] = V(1.0/r0b, 1.0/r1b);
+
+ /* #3: VOL1=1 and VOL2=1
+ * add the 10k resistor R49 for QC
+ */
+ if( i & 4 )
+ r0b += 1.0/10000;
+ else
+ r1b += 1.0/10000;
+ tonewave[3][i] = V(1.0/r0b, 1.0/r1b);
+ LOG(("tone[%2d]: $%02x $%02x $%02x $%02x\n", i, tonewave[0][i], tonewave[1][i], tonewave[2][i], tonewave[3][i]));
+ }
+
+ pitch = 0xff;
+ vol = 0;
+
+ tone_stream = stream_create(0,1,SOUND_CLOCK/STEPS,NULL,tone_update);
+ stream_set_output_gain(tone_stream, 0, TOOTHSAW_VOLUME / 100.0);
+
+ sample_set_volume(CHANNEL_NOISE,0);
+ sample_start_raw(CHANNEL_NOISE,noisewave,NOISE_LENGTH,NOISE_RATE,1);
+
+ sample_set_volume(CHANNEL_SHOOT,0);
+ sample_start_raw(CHANNEL_SHOOT,shootwave,SHOOT_LENGTH,SHOOT_RATE,1);
+
+ sample_set_volume(CHANNEL_LFO+0,0);
+ sample_start_raw(CHANNEL_LFO+0,backgroundwave,sizeof(backgroundwave)/2,1000,1);
+ sample_set_volume(CHANNEL_LFO+1,0);
+ sample_start_raw(CHANNEL_LFO+1,backgroundwave,sizeof(backgroundwave)/2,1000,1);
+ sample_set_volume(CHANNEL_LFO+2,0);
+ sample_start_raw(CHANNEL_LFO+2,backgroundwave,sizeof(backgroundwave)/2,1000,1);
+
+ noisetimer = timer_alloc(noise_timer_cb);
+ timer_adjust(noisetimer, ATTOTIME_IN_NSEC((155000+22000)/100*693*22), 0, ATTOTIME_IN_NSEC((155000+22000)/100*693*22));
+
+ lfotimer = timer_alloc(lfo_timer_cb);
+
+ timer_pulse(attotime_make(0, Machine->screen[0].refresh), 0, galaxian_sh_update);
+
+ state_save_register_global(freq);
+ state_save_register_global(noise_enable);
+ state_save_register_global(noisevolume);
+ state_save_register_global(last_port2);
+ state_save_register_global(pitch);
+ state_save_register_global(vol);
+ state_save_register_global(counter);
+ state_save_register_global(countdown);
+ state_save_register_global_array(lfobit);
+}
+
+
+
+WRITE8_HANDLER( galaxian_background_enable_w )
+{
+ sample_set_volume(CHANNEL_LFO+offset,(data & 1) ? LFO_VOLUME : 0);
+}
+
+static TIMER_CALLBACK( lfo_timer_cb )
+{
+ if( freq > MINFREQ )
+ freq--;
+ else
+ freq = MAXFREQ;
+}
+
+WRITE8_HANDLER( galaxian_lfo_freq_w )
+{
+#if NEW_LFO
+ /* R18 1M,R17 470K,R16 220K,R15 100K */
+ static const int rv[4] = { 1000000,470000,220000,100000};
+ double r1,r2,Re,td;
+ int i;
+
+ if( (data & 1) == lfobit[offset] )
+ return;
+
+ /*
+ * NE555 9R is setup as astable multivibrator
+ * - this circuit looks LINEAR RAMP V-F converter
+ I = 1/Re * ( R1/(R1+R2)-Vbe)
+ td = (2/3VCC*Re*(R1+R2)*C) / (R1*VCC-Vbe*(R1+R2))
+ parts assign
+ R1 : (R15* L1)|(R16* L2)|(R17* L3)|(R18* L1)
+ R2 : (R15*~L1)|(R16*~L2)|(R17*~L3)|(R18*~L4)|R??(330K)
+ Re : R21(100K)
+ Vbe : Q2(2SA1015)-Vbe
+ * - R20(15K) and Q1 is unknown,maybe current booster.
+ */
+
+ lfobit[offset] = data & 1;
+
+ /* R20 15K */
+ r1 = 1e12;
+ /* R19? 330k to gnd */
+ r2 = 330000;
+ //r1 = 15000;
+ /* R21 100K */
+ Re = 100000;
+ /* register calculation */
+ for(i=0;i<4;i++)
+ {
+ if(lfobit[i])
+ r1 = (r1*rv[i])/(r1+rv[i]); /* Hi */
+ else
+ r2 = (r2*rv[i])/(r2+rv[i]); /* Low */
+ }
+
+#define Vcc 5.0
+#define Vbe 0.65 /* 2SA1015 */
+#define Cap 0.000001 /* C15 1uF */
+ td = (Vcc*2/3*Re*(r1+r2)*Cap) / (r1*Vcc - Vbe*(r1+r2) );
+#undef Cap
+#undef Vbe
+#undef Vcc
+ logerror("lfo timer bits:%d%d%d%d r1:%d, r2:%d, re: %d, td: %9.2fsec\n", lfobit[0], lfobit[1], lfobit[2], lfobit[3], (int)r1, (int)r2, (int)Re, td);
+ timer_adjust(lfotimer, attotime_make(0, ATTOSECONDS_PER_SECOND / (MAXFREQ-MINFREQ) * td), 0, attotime_make(0, ATTOSECONDS_PER_SECOND / (MAXFREQ-MINFREQ) * td));
+#else
+ double r0, r1, rx = 100000.0;
+
+ if( (data & 1) == lfobit[offset] )
+ return;
+
+ /*
+ * NE555 9R is setup as astable multivibrator
+ * - Ra is between 100k and ??? (open?)
+ * - Rb is zero here (bridge between pins 6 and 7)
+ * - C is 1uF
+ * charge time t1 = 0.693 * (Ra + Rb) * C
+ * discharge time t2 = 0.693 * (Rb) * C
+ * period T = t1 + t2 = 0.693 * (Ra + 2 * Rb) * C
+ * -> min period: 0.693 * 100 kOhm * 1uF -> 69300 us = 14.4Hz
+ * -> max period: no idea, since I don't know the max. value for Ra :(
+ */
+
+ lfobit[offset] = data & 1;
+
+ /* R?? 330k to gnd */
+ r0 = 1.0/330000;
+ /* open is a very high value really ;-) */
+ r1 = 1.0/1e12;
+
+ /* R18 1M */
+ if( lfobit[0] )
+ r1 += 1.0/1000000;
+ else
+ r0 += 1.0/1000000;
+
+ /* R17 470k */
+ if( lfobit[1] )
+ r1 += 1.0/470000;
+ else
+ r0 += 1.0/470000;
+
+ /* R16 220k */
+ if( lfobit[2] )
+ r1 += 1.0/220000;
+ else
+ r0 += 1.0/220000;
+
+ /* R15 100k */
+ if( lfobit[3] )
+ r1 += 1.0/100000;
+ else
+ r0 += 1.0/100000;
+
+ r0 = 1.0/r0;
+ r1 = 1.0/r1;
+
+ /* I used an arbitrary value for max. Ra of 2M */
+ rx = rx + 2000000.0 * r0 / (r0+r1);
+
+ LOG(("lfotimer bits:%d%d%d%d r0:%d, r1:%d, rx: %d, time: %9.2fus\n", lfobit[3], lfobit[2], lfobit[1], lfobit[0], (int)r0, (int)r1, (int)rx, 0.639 * rx));
+ timer_adjust(lfotimer, attotime_make(0, ATTOSECONDS_PER_SECOND / 1000000000 / (MAXFREQ-MINFREQ) * 639 * rx), 0, attotime_make(0, ATTOSECONDS_PER_SECOND / 1000000000 / (MAXFREQ-MINFREQ) * 639 * rx));
+#endif
+}
+
+static TIMER_CALLBACK( galaxian_sh_update )
+{
+ /*
+ * NE555 8R, 8S and 8T are used as pulse position modulators
+ * FS1 Ra=100k, Rb=470k and C=0.01uF
+ * -> 0.693 * 1040k * 0.01uF -> 7207.2us = 139Hz
+ * FS2 Ra=100k, Rb=330k and C=0.01uF
+ * -> 0.693 * 760k * 0.01uF -> 5266.8us = 190Hz
+ * FS2 Ra=100k, Rb=220k and C=0.01uF
+ * -> 0.693 * 540k * 0.01uF -> 3742.2us = 267Hz
+ */
+
+ sample_set_freq(CHANNEL_LFO+0, sizeof(backgroundwave)*freq*(100+2*470)/(100+2*470) );
+ sample_set_freq(CHANNEL_LFO+1, sizeof(backgroundwave)*freq*(100+2*330)/(100+2*470) );
+ sample_set_freq(CHANNEL_LFO+2, sizeof(backgroundwave)*freq*(100+2*220)/(100+2*470) );
+}
+
+
+struct Samplesinterface galaxian_samples_interface =
+{
+ 5,
+ NULL,
+ galaxian_sh_start
+};