diff --git a/.gitattributes b/.gitattributes index 6743ffa938b..a3fe781cf8a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2628,6 +2628,7 @@ src/mame/drivers/vastar.c svneol=native#text/plain src/mame/drivers/vball.c svneol=native#text/plain src/mame/drivers/vcombat.c svneol=native#text/plain src/mame/drivers/vd.c svneol=native#text/plain +src/mame/drivers/vectrex.c svneol=native#text/plain src/mame/drivers/vega.c svneol=native#text/plain src/mame/drivers/vegaeo.c svneol=native#text/plain src/mame/drivers/vegas.c svneol=native#text/plain @@ -3344,6 +3345,7 @@ src/mame/includes/usgames.h svneol=native#text/plain src/mame/includes/vaportra.h svneol=native#text/plain src/mame/includes/vastar.h svneol=native#text/plain src/mame/includes/vball.h svneol=native#text/plain +src/mame/includes/vectrex.h svneol=native#text/plain src/mame/includes/vendetta.h svneol=native#text/plain src/mame/includes/vertigo.h svneol=native#text/plain src/mame/includes/vicdual.h svneol=native#text/plain @@ -4460,6 +4462,7 @@ src/mame/video/vastar.c svneol=native#text/plain src/mame/video/vball.c svneol=native#text/plain src/mame/video/vdc.c svneol=native#text/plain src/mame/video/vdc.h svneol=native#text/plain +src/mame/video/vectrex.c svneol=native#text/plain src/mame/video/vendetta.c svneol=native#text/plain src/mame/video/vertigo.c svneol=native#text/plain src/mame/video/vicdual.c svneol=native#text/plain diff --git a/src/mame/drivers/vectrex.c b/src/mame/drivers/vectrex.c new file mode 100644 index 00000000000..7c039881b46 --- /dev/null +++ b/src/mame/drivers/vectrex.c @@ -0,0 +1,233 @@ +/***************************************************************** + +GCE Vectrex + +Mathis Rosenhauer +Christopher Salomon (technical advice) +Bruce Tomlin (hardware info) + +*****************************************************************/ + +#include "emu.h" +#include "cpu/m6809/m6809.h" +#include "video/vector.h" +#include "machine/6522via.h" +#include "includes/vectrex.h" +#include "imagedev/cartslot.h" +#include "sound/ay8910.h" +#include "sound/dac.h" +#include "machine/nvram.h" + + +static ADDRESS_MAP_START(vectrex_map, AS_PROGRAM, 8) + AM_RANGE(0x0000, 0x7fff) AM_ROM + AM_RANGE(0xc800, 0xcbff) AM_RAM AM_MIRROR(0x0400) AM_BASE_MEMBER(vectrex_state, m_gce_vectorram) AM_SIZE_MEMBER(vectrex_state, m_gce_vectorram_size) + AM_RANGE(0xd000, 0xd7ff) AM_READWRITE(vectrex_via_r, vectrex_via_w) + AM_RANGE(0xe000, 0xffff) AM_ROM +ADDRESS_MAP_END + +static INPUT_PORTS_START(vectrex) + PORT_START("CONTR1X") + PORT_BIT(0xff, 0x80, IPT_AD_STICK_X) PORT_MINMAX(0,0xff) PORT_SENSITIVITY(50) PORT_KEYDELTA(30) + + PORT_START("CONTR1Y") + PORT_BIT(0xff, 0x80, IPT_AD_STICK_Y) PORT_MINMAX(0,0xff) PORT_SENSITIVITY(50) PORT_KEYDELTA(30) PORT_REVERSE + + PORT_START("CONTR2X") + PORT_BIT(0xff, 0x80, IPT_AD_STICK_X) PORT_MINMAX(0,0xff) PORT_SENSITIVITY(50) PORT_KEYDELTA(30) PORT_PLAYER(2) + + PORT_START("CONTR2Y") + PORT_BIT(0xff, 0x80, IPT_AD_STICK_Y) PORT_MINMAX(0,0xff) PORT_SENSITIVITY(50) PORT_KEYDELTA(30) PORT_REVERSE PORT_PLAYER(2) + + PORT_START("BUTTONS") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_BUTTON1) PORT_PLAYER(1) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_BUTTON2) PORT_PLAYER(1) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_BUTTON3) PORT_PLAYER(1) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_BUTTON4) PORT_PLAYER(1) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_BUTTON1) PORT_PLAYER(2) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_BUTTON2) PORT_PLAYER(2) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_BUTTON3) PORT_PLAYER(2) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_BUTTON4) PORT_PLAYER(2) + + PORT_START("3DCONF") + PORT_CONFNAME(0x01, 0x00, "3D Imager") + PORT_CONFSETTING(0x00, DEF_STR(Off)) + PORT_CONFSETTING(0x01, DEF_STR(On)) + PORT_CONFNAME(0x02, 0x00, "Separate images") + PORT_CONFSETTING(0x00, DEF_STR(No)) + PORT_CONFSETTING(0x02, DEF_STR(Yes)) + PORT_CONFNAME(0x1c, 0x10, "Left eye") + PORT_CONFSETTING(0x00, "Black") + PORT_CONFSETTING(0x04, "Red") + PORT_CONFSETTING(0x08, "Green") + PORT_CONFSETTING(0x0c, "Blue") + PORT_CONFSETTING(0x10, "Color") + PORT_CONFNAME(0xe0, 0x80, "Right eye") + PORT_CONFSETTING(0x00, "Black") + PORT_CONFSETTING(0x20, "Red") + PORT_CONFSETTING(0x40, "Green") + PORT_CONFSETTING(0x60, "Blue") + PORT_CONFSETTING(0x80, "Color") + + PORT_START("LPENCONF") + PORT_CONFNAME(0x03, 0x00, "Lightpen") + PORT_CONFSETTING(0x00, DEF_STR(Off)) + PORT_CONFSETTING(0x01, "left port") + PORT_CONFSETTING(0x02, "right port") + PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_BUTTON5) PORT_CODE(MOUSECODE_BUTTON1) + + PORT_START("LPENY") + PORT_BIT(0xff, 0x80, IPT_LIGHTGUN_X) PORT_CROSSHAIR(Y, 1, 0, 0) PORT_MINMAX(0,0xff) PORT_SENSITIVITY(35) PORT_KEYDELTA(1) PORT_PLAYER(1) + + PORT_START("LPENX") + PORT_BIT(0xff, 0x80, IPT_LIGHTGUN_Y) PORT_CROSSHAIR(X, 1, 0, 0) PORT_MINMAX(0,0xff) PORT_SENSITIVITY(35) PORT_KEYDELTA(1) PORT_REVERSE PORT_PLAYER(1) + +INPUT_PORTS_END + + + +static const ay8910_interface vectrex_ay8910_interface = +{ + AY8910_LEGACY_OUTPUT, + AY8910_DEFAULT_LOADS, + DEVCB_INPUT_PORT("BUTTONS"), + DEVCB_NULL, + DEVCB_MEMORY_HANDLER("maincpu", PROGRAM, vectrex_psg_port_w), + DEVCB_NULL +}; + +static MACHINE_CONFIG_START( vectrex, vectrex_state ) + /* basic machine hardware */ + MCFG_CPU_ADD("maincpu", M6809, XTAL_6MHz / 4) + MCFG_CPU_PROGRAM_MAP(vectrex_map) + + /* video hardware */ + MCFG_SCREEN_ADD("screen", VECTOR) + MCFG_SCREEN_REFRESH_RATE(60) + MCFG_SCREEN_FORMAT(BITMAP_FORMAT_RGB32) + MCFG_SCREEN_SIZE(400, 300) + MCFG_SCREEN_VISIBLE_AREA(0, 399, 0, 299) + MCFG_SCREEN_UPDATE(vectrex) + + MCFG_VIDEO_START(vectrex) + + /* sound hardware */ + MCFG_SPEAKER_STANDARD_MONO("mono") + MCFG_SOUND_ADD("dac", DAC, 0) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.50) + MCFG_SOUND_ADD("ay8912", AY8912, 1500000) + MCFG_SOUND_CONFIG(vectrex_ay8910_interface) + MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 0.20) + + /* via */ + MCFG_VIA6522_ADD("via6522_0", 0, vectrex_via6522_interface) + + /* cartridge */ + MCFG_CARTSLOT_ADD("cart") + MCFG_CARTSLOT_EXTENSION_LIST("bin,gam,vec") + MCFG_CARTSLOT_NOT_MANDATORY + MCFG_CARTSLOT_LOAD(vectrex_cart) + MCFG_CARTSLOT_INTERFACE("vectrex_cart") + + /* software lists */ + MCFG_SOFTWARE_LIST_ADD("cart_list","vectrex") +MACHINE_CONFIG_END + +ROM_START(vectrex) + ROM_REGION(0x10000,"maincpu", 0) + ROM_LOAD("system.img", 0xe000, 0x2000, CRC(ba13fb57) SHA1(65d07426b520ddd3115d40f255511e0fd2e20ae7)) +ROM_END + + +/***************************************************************** + + RA+A Spectrum I+ + + The Spectrum I+ was a modified Vectrex. It had a 32K ROM cart + and 2K additional battery backed RAM (0x8000 - 0x87ff). PB6 + was used to signal inserted coins to the VIA. The unit was + controlled by 8 buttons (2x4 buttons of controller 1 and 2). + Each button had a LED which were mapped to 0xa000. + The srvice mode can be accessed by pressing button + 8 during startup. As soon as all LEDs light up, + press 2 and 3 without releasing 8. Then release 8 and + after that 2 and 3. You can leave the screen where you enter + ads by pressing 8 several times. + + Character matrix is: + + btn| 1 2 3 4 5 6 7 8 + ---+------------------------ + 1 | 0 1 2 3 4 5 6 7 + 2 | 8 9 A B C D E F + 3 | G H I J K L M N + 4 | O P Q R S T U V + 5 | W X Y Z sp ! " # + 6 | $ % & ' ( ) * + + 7 | , - _ / : ; ? = + 8 |bs ret up dn l r hom esc + + The first page of ads is shown with the "result" of the + test. Remaining pages are shown in attract mode. If no extra + ram is present, the word COLOR is scrolled in big vector! + letters in attract mode. + +*****************************************************************/ + +static ADDRESS_MAP_START(raaspec_map , AS_PROGRAM, 8) + AM_RANGE(0x0000, 0x7fff) AM_ROM + AM_RANGE(0x8000, 0x87ff) AM_RAM AM_SHARE("nvram") + AM_RANGE(0xa000, 0xa000) AM_WRITE(raaspec_led_w) + AM_RANGE(0xc800, 0xcbff) AM_RAM AM_MIRROR(0x0400) AM_BASE_MEMBER(vectrex_state, m_gce_vectorram) AM_SIZE_MEMBER(vectrex_state, m_gce_vectorram_size) + AM_RANGE(0xd000, 0xd7ff) AM_READWRITE (vectrex_via_r, vectrex_via_w) + AM_RANGE(0xe000, 0xffff) AM_ROM +ADDRESS_MAP_END + +static INPUT_PORTS_START(raaspec) + PORT_START("LPENCONF") + PORT_START("LPENY") + PORT_START("LPENX") + PORT_START("3DCONF") + PORT_START("BUTTONS") + PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_BUTTON1) + PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_BUTTON2) + PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_BUTTON3) + PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_BUTTON4) + PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_BUTTON5) + PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_BUTTON6) + PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_BUTTON7) + PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_BUTTON8) + PORT_START("COIN") + PORT_BIT(0x40, IP_ACTIVE_HIGH, IPT_COIN1) +INPUT_PORTS_END + + +static MACHINE_CONFIG_DERIVED( raaspec, vectrex ) + MCFG_CPU_MODIFY("maincpu") + MCFG_CPU_PROGRAM_MAP(raaspec_map) + MCFG_NVRAM_ADD_0FILL("nvram") + + MCFG_VIDEO_START(raaspec) + + /* via */ + MCFG_DEVICE_REMOVE("via6522_0") + MCFG_VIA6522_ADD("via6522_0", 0, spectrum1_via6522_interface) + + MCFG_DEVICE_REMOVE("cart") +MACHINE_CONFIG_END + +ROM_START(raaspec) + ROM_REGION(0x10000,"maincpu", 0) + ROM_LOAD("spectrum.bin", 0x0000, 0x8000, CRC(20af7f3f) SHA1(7ce85db8dd32687ad7629631ae113820371faf7c)) + ROM_LOAD("system.img", 0xe000, 0x2000, CRC(ba13fb57) SHA1(65d07426b520ddd3115d40f255511e0fd2e20ae7)) +ROM_END + +/*************************************************************************** + + Game driver(s) + +***************************************************************************/ + +/* YEAR NAME PARENT COMPAT MACHINE INPUT INIT COMPANY FULLNAME */ +CONS(1982, vectrex, 0, 0, vectrex, vectrex, vectrex, "General Consumer Electronics", "Vectrex" , ROT270) +CONS(1984, raaspec, vectrex, 0, raaspec, raaspec, vectrex, "Roy Abel & Associates", "Spectrum I+" , ROT270) diff --git a/src/mame/includes/vectrex.h b/src/mame/includes/vectrex.h new file mode 100644 index 00000000000..c5fdbeaa736 --- /dev/null +++ b/src/mame/includes/vectrex.h @@ -0,0 +1,103 @@ +/***************************************************************************** + * + * includes/vectrex.h + * + ****************************************************************************/ + +#ifndef VECTREX_H_ +#define VECTREX_H_ + +#include "machine/6522via.h" + +#define NVECT 10000 + +typedef struct _vectrex_point +{ + int x; int y; + rgb_t col; + int intensity; +} vectrex_point; + +class vectrex_state : public driver_device +{ +public: + vectrex_state(const machine_config &mconfig, device_type type, const char *tag) + : driver_device(mconfig, type, tag) { } + + UINT8 *m_gce_vectorram; + size_t m_gce_vectorram_size; + int m_imager_status; + UINT32 m_beam_color; + unsigned char m_via_out[2]; + double m_imager_freq; + emu_timer *m_imager_timer; + int m_lightpen_port; + int m_reset_refresh; + rgb_t m_imager_colors[6]; + const double *m_imager_angles; + unsigned char m_imager_pinlevel; + int m_old_mcontrol; + double m_sl; + double m_pwl; + int m_x_center; + int m_y_center; + int m_x_max; + int m_y_max; + int m_x_int; + int m_y_int; + int m_lightpen_down; + int m_pen_x; + int m_pen_y; + emu_timer *m_lp_t; + emu_timer *m_refresh; + UINT8 m_blank; + UINT8 m_ramp; + INT8 m_analog[5]; + int m_point_index; + int m_display_start; + int m_display_end; + vectrex_point m_points[NVECT]; + UINT16 m_via_timer2; + attotime m_vector_start_time; + UINT8 m_cb2; + void (*vector_add_point_function) (running_machine &, int, int, rgb_t, int); +}; + + +/*----------- defined in machine/vectrex.c -----------*/ + +DEVICE_IMAGE_LOAD( vectrex_cart ); + + +TIMER_CALLBACK(vectrex_imager_eye); +void vectrex_configuration(running_machine &machine); +READ8_DEVICE_HANDLER (vectrex_via_pa_r); +READ8_DEVICE_HANDLER(vectrex_via_pb_r ); +void vectrex_via_irq (device_t *device, int level); +WRITE8_HANDLER ( vectrex_psg_port_w ); + +DRIVER_INIT( vectrex ); + +/* for spectrum 1+ */ +READ8_DEVICE_HANDLER( vectrex_s1_via_pb_r ); + + +/*----------- defined in video/vectrex.c -----------*/ + +extern const via6522_interface vectrex_via6522_interface; +extern const via6522_interface spectrum1_via6522_interface; + +VIDEO_START( vectrex ); +SCREEN_UPDATE( vectrex ); + +VIDEO_START( raaspec ); + +WRITE8_HANDLER ( raaspec_led_w ); + +READ8_HANDLER ( vectrex_via_r ); +WRITE8_HANDLER ( vectrex_via_w ); + +void vectrex_add_point_stereo (running_machine &machine, int x, int y, rgb_t color, int intensity); +void vectrex_add_point (running_machine &machine, int x, int y, rgb_t color, int intensity); + +#endif /* VECTREX_H_ */ diff --git a/src/mame/video/vectrex.c b/src/mame/video/vectrex.c new file mode 100644 index 00000000000..4630ad72c97 --- /dev/null +++ b/src/mame/video/vectrex.c @@ -0,0 +1,496 @@ +#include +#include "emu.h" +#include "includes/vectrex.h" +#include "video/vector.h" +#include "machine/6522via.h" +#include "cpu/m6809/m6809.h" +#include "sound/ay8910.h" +#include "sound/dac.h" + + +#define ANALOG_DELAY 7800 + +#define INT_PER_CLOCK 550 + +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.70710678118654752440 +#endif + +/********************************************************************* + + Enums and typedefs + +*********************************************************************/ + +enum { + PORTB = 0, + PORTA +}; + +enum { + A_X = 0, + A_ZR, + A_Z, + A_AUDIO, + A_Y, +}; + + +/********************************************************************* + + Prototypes + +*********************************************************************/ + +static WRITE8_DEVICE_HANDLER (v_via_pa_w); +static WRITE8_DEVICE_HANDLER(v_via_pb_w); +static WRITE8_DEVICE_HANDLER (v_via_ca2_w); +static WRITE8_DEVICE_HANDLER (v_via_cb2_w); + + +/********************************************************************* + + Local variables + +*********************************************************************/ + +const via6522_interface vectrex_via6522_interface = +{ + DEVCB_HANDLER(vectrex_via_pa_r), DEVCB_HANDLER(vectrex_via_pb_r), /* read PA/B */ + DEVCB_NULL, DEVCB_NULL, DEVCB_NULL, DEVCB_NULL, /* read ca1, cb1, ca2, cb2 */ + DEVCB_HANDLER(v_via_pa_w), DEVCB_HANDLER(v_via_pb_w), /* write PA/B */ + DEVCB_NULL, DEVCB_NULL, DEVCB_HANDLER(v_via_ca2_w), DEVCB_HANDLER(v_via_cb2_w), /* write ca1, cb1, ca2, cb2 */ + DEVCB_LINE(vectrex_via_irq), /* IRQ */ +}; + + + +/********************************************************************* + + Lightpen + +*********************************************************************/ + +static TIMER_CALLBACK(lightpen_trigger) +{ + vectrex_state *state = machine.driver_data(); + if (state->m_lightpen_port & 1) + { + via6522_device *via_0 = machine.device("via6522_0"); + via_0->write_ca1(1); + via_0->write_ca1(0); + } + + if (state->m_lightpen_port & 2) + { + cputag_set_input_line(machine, "maincpu", M6809_FIRQ_LINE, PULSE_LINE); + } +} + + +/********************************************************************* + + VIA T2 configuration + + We need to snoop the frequency of VIA timer 2 here since most + vectrex games use that timer for steady screen refresh. Even if the + game stops T2 we continue refreshing the screen with the last known + frequency. Note that we quickly get out of sync in this case and the + screen will start flickering (see cut scenes in Spike). + + Note that the timer can be adjusted to the full period each time T2 + is restarted. This behaviour avoids flicker in most games. Some + games like mine 3d don't work well with this scheme though and show + severe jerking. So the second option is to leave the current period + alone (if the new period isn't shorter) and change only the next + full period. + +*********************************************************************/ + +READ8_HANDLER(vectrex_via_r) +{ + via6522_device *via = space->machine().device("via6522_0"); + return via->read(*space, offset); +} + +WRITE8_HANDLER(vectrex_via_w) +{ + vectrex_state *state = space->machine().driver_data(); + via6522_device *via = space->machine().device("via6522_0"); + attotime period; + + switch (offset) + { + case 8: + state->m_via_timer2 = (state->m_via_timer2 & 0xff00) | data; + break; + + case 9: + state->m_via_timer2 = (state->m_via_timer2 & 0x00ff) | (data << 8); + + period = (attotime::from_hz(space->machine().device("maincpu")->unscaled_clock()) * state->m_via_timer2); + + if (state->m_reset_refresh) + state->m_refresh->adjust(period, 0, period); + else + state->m_refresh->adjust( + min(period, state->m_refresh->remaining()), + 0, + period); + break; + } + via->write(*space, offset, data); +} + + +/********************************************************************* + + Screen refresh + +*********************************************************************/ + +static TIMER_CALLBACK(vectrex_refresh) +{ + vectrex_state *state = machine.driver_data(); + /* Refresh only marks the range of vectors which will be drawn + * during the next SCREEN_UPDATE. */ + state->m_display_start = state->m_display_end; + state->m_display_end = state->m_point_index; +} + + +SCREEN_UPDATE(vectrex) +{ + vectrex_state *state = screen->machine().driver_data(); + int i; + + vectrex_configuration(screen->machine()); + + /* start black */ + vector_add_point(screen->machine(), + state->m_points[state->m_display_start].x, + state->m_points[state->m_display_start].y, + state->m_points[state->m_display_start].col, + 0); + + for (i = state->m_display_start; i != state->m_display_end; i = (i + 1) % NVECT) + { + vector_add_point(screen->machine(), + state->m_points[i].x, + state->m_points[i].y, + state->m_points[i].col, + state->m_points[i].intensity); + } + + SCREEN_UPDATE_CALL(vector); + vector_clear_list(); + return 0; +} + + +/********************************************************************* + + Vector functions + +*********************************************************************/ + +void vectrex_add_point(running_machine &machine, int x, int y, rgb_t color, int intensity) +{ + vectrex_state *state = machine.driver_data(); + vectrex_point *newpoint; + + state->m_point_index = (state->m_point_index+1) % NVECT; + newpoint = &state->m_points[state->m_point_index]; + + newpoint->x = x; + newpoint->y = y; + newpoint->col = color; + newpoint->intensity = intensity; +} + + +void vectrex_add_point_stereo(running_machine &machine, int x, int y, rgb_t color, int intensity) +{ + vectrex_state *state = machine.driver_data(); + if (state->m_imager_status == 2) /* left = 1, right = 2 */ + vectrex_add_point(machine, (int)(y * M_SQRT1_2)+ state->m_x_center, + (int)(((state->m_x_max - x) * M_SQRT1_2)), + color, + intensity); + else + vectrex_add_point(machine, (int)(y * M_SQRT1_2), + (int)((state->m_x_max - x) * M_SQRT1_2), + color, + intensity); +} + + +static TIMER_CALLBACK(vectrex_zero_integrators) +{ + vectrex_state *state = machine.driver_data(); + state->m_x_int = state->m_x_center + (state->m_analog[A_ZR] * INT_PER_CLOCK); + state->m_y_int = state->m_y_center + (state->m_analog[A_ZR] * INT_PER_CLOCK); + (*state->vector_add_point_function)(machine, state->m_x_int, state->m_y_int, state->m_beam_color, 0); +} + + +/********************************************************************* + + Delayed signals + + The RAMP signal is delayed wrt. beam blanking. Getting this right + is important for text placement, the maze in Clean Sweep and many + other games. + +*********************************************************************/ + +static TIMER_CALLBACK(update_signal) +{ + vectrex_state *state = machine.driver_data(); + int length; + + if (!state->m_ramp) + { + length = machine.device("maincpu")->unscaled_clock() * INT_PER_CLOCK + * (machine.time() - state->m_vector_start_time).as_double(); + + state->m_x_int += length * (state->m_analog[A_X] + state->m_analog[A_ZR]); + state->m_y_int += length * (state->m_analog[A_Y] + state->m_analog[A_ZR]); + + (*state->vector_add_point_function)(machine, state->m_x_int, state->m_y_int, state->m_beam_color, 2 * state->m_analog[A_Z] * state->m_blank); + } + else + { + if (state->m_blank) + (*state->vector_add_point_function)(machine, state->m_x_int, state->m_y_int, state->m_beam_color, 2 * state->m_analog[A_Z]); + } + + state->m_vector_start_time = machine.time(); + + if (ptr) + * (UINT8 *) ptr = param; +} + + +/********************************************************************* + + Startup + +*********************************************************************/ + +VIDEO_START(vectrex) +{ + vectrex_state *state = machine.driver_data(); + screen_device *screen = machine.first_screen(); + const rectangle &visarea = screen->visible_area(); + + state->m_x_center=((visarea.max_x - visarea.min_x) / 2) << 16; + state->m_y_center=((visarea.max_y - visarea.min_y) / 2) << 16; + state->m_x_max = visarea.max_x << 16; + state->m_y_max = visarea.max_y << 16; + + state->m_imager_freq = 1; + + state->vector_add_point_function = vectrex_add_point; + state->m_imager_timer = machine.scheduler().timer_alloc(FUNC(vectrex_imager_eye)); + state->m_imager_timer->adjust( + attotime::from_hz(state->m_imager_freq), + 2, + attotime::from_hz(state->m_imager_freq)); + + state->m_lp_t = machine.scheduler().timer_alloc(FUNC(lightpen_trigger)); + + state->m_refresh = machine.scheduler().timer_alloc(FUNC(vectrex_refresh)); + + VIDEO_START_CALL(vector); +} + + +/********************************************************************* + + VIA interface functions + +*********************************************************************/ + +static void vectrex_multiplexer(running_machine &machine, int mux) +{ + vectrex_state *state = machine.driver_data(); + device_t *dac_device = machine.device("dac"); + + machine.scheduler().timer_set(attotime::from_nsec(ANALOG_DELAY), FUNC(update_signal), state->m_via_out[PORTA], &state->m_analog[mux]); + + if (mux == A_AUDIO) + dac_data_w(dac_device, state->m_via_out[PORTA]); +} + + +static WRITE8_DEVICE_HANDLER(v_via_pb_w) +{ + vectrex_state *state = device->machine().driver_data(); + if (!(data & 0x80)) + { + /* RAMP is active */ + if ((state->m_ramp & 0x80)) + { + /* RAMP was inactive before */ + + if (state->m_lightpen_down) + { + /* Simple lin. algebra to check if pen is near + * the line defined by (A_X,A_Y). + * If that is the case, set a timer which goes + * off when the beam reaches the pen. Exact + * timing is important here. + * + * lightpen + * ^ + * _ /| + * b / | + * / | + * / |d + * / | + * / | + * ------+---------> beam path + * l | _ + * a + */ + double a2, b2, ab, d2; + ab = (state->m_pen_x - state->m_x_int) * state->m_analog[A_X] + +(state->m_pen_y - state->m_y_int) * state->m_analog[A_Y]; + if (ab > 0) + { + a2 = (double)(state->m_analog[A_X] * state->m_analog[A_X] + +(double)state->m_analog[A_Y] * state->m_analog[A_Y]); + b2 = (double)(state->m_pen_x - state->m_x_int) * (state->m_pen_x - state->m_x_int) + +(double)(state->m_pen_y - state->m_y_int) * (state->m_pen_y - state->m_y_int); + d2 = b2 - ab * ab / a2; + if (d2 < 2e10 && state->m_analog[A_Z] * state->m_blank > 0) + state->m_lp_t->adjust(attotime::from_double(ab / a2 / (device->machine().device("maincpu")->unscaled_clock() * INT_PER_CLOCK))); + } + } + } + + if (!(data & 0x1) && (state->m_via_out[PORTB] & 0x1)) + { + /* MUX has been enabled */ + device->machine().scheduler().timer_set(attotime::from_nsec(ANALOG_DELAY), FUNC(update_signal)); + } + } + else + { + /* RAMP is inactive */ + if (!(state->m_ramp & 0x80)) + { + /* Cancel running timer, line already finished */ + if (state->m_lightpen_down) + state->m_lp_t->adjust(attotime::never); + } + } + + /* Sound */ + if (data & 0x10) + { + device_t *ay8912 = device->machine().device("ay8912"); + + if (data & 0x08) /* BC1 (do we select a reg or write it ?) */ + ay8910_address_w(ay8912, 0, state->m_via_out[PORTA]); + else + ay8910_data_w(ay8912, 0, state->m_via_out[PORTA]); + } + + if (!(data & 0x1) && (state->m_via_out[PORTB] & 0x1)) + vectrex_multiplexer (device->machine(), (data >> 1) & 0x3); + + state->m_via_out[PORTB] = data; + device->machine().scheduler().timer_set(attotime::from_nsec(ANALOG_DELAY), FUNC(update_signal), data & 0x80, &state->m_ramp); +} + + +static WRITE8_DEVICE_HANDLER(v_via_pa_w) +{ + vectrex_state *state = device->machine().driver_data(); + /* DAC output always goes to Y integrator */ + state->m_via_out[PORTA] = data; + device->machine().scheduler().timer_set(attotime::from_nsec(ANALOG_DELAY), FUNC(update_signal), data, &state->m_analog[A_Y]); + + if (!(state->m_via_out[PORTB] & 0x1)) + vectrex_multiplexer (device->machine(), (state->m_via_out[PORTB] >> 1) & 0x3); +} + + +static WRITE8_DEVICE_HANDLER(v_via_ca2_w) +{ + if (data == 0) + device->machine().scheduler().timer_set(attotime::from_nsec(ANALOG_DELAY), FUNC(vectrex_zero_integrators)); +} + + +static WRITE8_DEVICE_HANDLER(v_via_cb2_w) +{ + vectrex_state *state = device->machine().driver_data(); + int dx, dy; + + if (state->m_cb2 != data) + { + + /* Check lightpen */ + if (state->m_lightpen_port != 0) + { + state->m_lightpen_down = input_port_read(device->machine(), "LPENCONF") & 0x10; + + if (state->m_lightpen_down) + { + state->m_pen_x = input_port_read(device->machine(), "LPENX") * (state->m_x_max / 0xff); + state->m_pen_y = input_port_read(device->machine(), "LPENY") * (state->m_y_max / 0xff); + + dx = abs(state->m_pen_x - state->m_x_int); + dy = abs(state->m_pen_y - state->m_y_int); + if (dx < 500000 && dy < 500000 && data > 0) + device->machine().scheduler().timer_set(attotime::zero, FUNC(lightpen_trigger)); + } + } + + device->machine().scheduler().timer_set(attotime::zero, FUNC(update_signal), data, &state->m_blank); + state->m_cb2 = data; + } +} + + +/***************************************************************** + + RA+A Spectrum I+ + +*****************************************************************/ + +const via6522_interface spectrum1_via6522_interface = +{ + /*inputs : A/B,CA/B1,CA/B2 */ DEVCB_HANDLER(vectrex_via_pa_r), DEVCB_HANDLER(vectrex_s1_via_pb_r), DEVCB_NULL, DEVCB_NULL, DEVCB_NULL, DEVCB_NULL, + /*outputs: A/B,CA/B1,CA/B2 */ DEVCB_HANDLER(v_via_pa_w), DEVCB_HANDLER(v_via_pb_w), DEVCB_NULL, DEVCB_NULL, DEVCB_HANDLER(v_via_ca2_w), DEVCB_HANDLER(v_via_cb2_w), + /*irq */ DEVCB_LINE(vectrex_via_irq), +}; + + +WRITE8_HANDLER(raaspec_led_w) +{ + logerror("Spectrum I+ LED: %i%i%i%i%i%i%i%i\n", + (data>>7)&0x1, (data>>6)&0x1, (data>>5)&0x1, (data>>4)&0x1, + (data>>3)&0x1, (data>>2)&0x1, (data>>1)&0x1, data&0x1); +} + + +VIDEO_START(raaspec) +{ + vectrex_state *state = machine.driver_data(); + screen_device *screen = machine.first_screen(); + const rectangle &visarea = screen->visible_area(); + + state->m_x_center=((visarea.max_x - visarea.min_x) / 2) << 16; + state->m_y_center=((visarea.max_y - visarea.min_y) / 2) << 16; + state->m_x_max = visarea.max_x << 16; + state->m_y_max = visarea.max_y << 16; + + state->vector_add_point_function = vectrex_add_point; + state->m_refresh = machine.scheduler().timer_alloc(FUNC(vectrex_refresh)); + + VIDEO_START_CALL(vector); +}