diff --git a/scripts/src/bus.lua b/scripts/src/bus.lua index 1b5484abf5855..21f0ff355456e 100644 --- a/scripts/src/bus.lua +++ b/scripts/src/bus.lua @@ -3015,6 +3015,10 @@ if (BUSES["A2BUS"]~=null) then MAME_DIR .. "src/devices/bus/a2bus/a2dx1.h", MAME_DIR .. "src/devices/bus/a2bus/a2echoii.cpp", MAME_DIR .. "src/devices/bus/a2bus/a2echoii.h", + MAME_DIR .. "src/devices/bus/a2bus/a2frob.cpp", + MAME_DIR .. "src/devices/bus/a2bus/a2frob.h", + MAME_DIR .. "src/devices/bus/a2bus/a2frobtia.cpp", + MAME_DIR .. "src/devices/bus/a2bus/a2frobtia.h", MAME_DIR .. "src/devices/bus/a2bus/a2eext80col.cpp", MAME_DIR .. "src/devices/bus/a2bus/a2eext80col.h", MAME_DIR .. "src/devices/bus/a2bus/a2eramworks3.cpp", diff --git a/src/devices/bus/a2bus/a2frob.cpp b/src/devices/bus/a2bus/a2frob.cpp new file mode 100644 index 0000000000000..0cf22678f8c13 --- /dev/null +++ b/src/devices/bus/a2bus/a2frob.cpp @@ -0,0 +1,438 @@ +// license:BSD-3-Clause +// copyright-holders:Nathan Woods, Wilbert Pol, David Shah, Golden Child +/*************************************************************************** + + Apple 2 Frob Card by Frobco + + The Frob Card is a ROM emulator device for the Atari 2600 that operates + under the control of an Apple II. It also allows the Apple II and Atari 2600 + to exchange information with a bidirectional port. + + The Atari 2600 code comes directly from a2600.cpp and has been converted + to be a device instead of a driver. + + The slot interface for the a2600 has been removed for simplicity as well as + the PAL a2600 variant. + + Note: Once the Frob memory is loaded, you can reset the 2600 with Numpad Minus + +***************************************************************************/ + + +#include "emu.h" + +#include "a2bus.h" +#include "a2frobtia.h" +#include "a2frob.h" + +#include "bus/vcs/rom.h" +#include "bus/vcs/vcs_slot.h" +#include "bus/vcs_ctrl/ctrl.h" +#include "cpu/m6502/m6507.h" +#include "machine/mos6530.h" +#include "sound/tiaintf.h" + +#include "emupal.h" +#include "screen.h" +#include "softlist_dev.h" +#include "speaker.h" + +//#define VERBOSE (LOG_GENERAL) +#include "logmacro.h" + + +namespace { + +static const uint16_t supported_screen_heights[4] = { 262, 312, 328, 342 }; + +class a2600_frob_base_device : + public device_t, + public device_a2bus_card_interface +{ + +public: + a2600_frob_base_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock); + + INPUT_CHANGED_MEMBER(reset_a2600); + +protected: + a2600_frob_base_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock) : + device_t(mconfig, type, tag, owner, clock), + device_a2bus_card_interface(mconfig, *this), + m_riot_ram(*this, "riot_ram"), + m_tia(*this, "tia_video"), + m_maincpu(*this, "maincpu"), + m_riot(*this, "riot"), + m_joy1(*this, "joyport1"), + m_joy2(*this, "joyport2"), + m_screen(*this, "tia_screen"), + m_xtal(3.579575_MHz_XTAL) + { } + + virtual void device_start() override ATTR_COLD; + + void a2600_mem(address_map &map) ATTR_COLD; + + virtual tiny_rom_entry const *device_rom_region() const override ATTR_COLD; + virtual void device_add_mconfig(machine_config &config) override ATTR_COLD; + virtual ioport_constructor device_input_ports() const override ATTR_COLD; + + virtual u8 read_c0nx(u8 offset) override; + virtual void write_c0nx(u8 offset, u8 data) override; + + virtual u8 read_cnxx(u8 offset) override + { return !m_frob_apple_control ? m_frob_mem[(offset & 0xff)|(m_frob_page << 8)] : 0; } + + virtual void write_cnxx(u8 offset, u8 data) override + { if (!m_frob_apple_control) m_frob_mem[(offset & 0xff)|(m_frob_page << 8)] = data; } + + virtual bool take_c800() override { return false; } + + + u8 read_frob_mem(offs_t offset) { return !m_frob_apple_control ? 0 : m_frob_mem[offset & 0xfff]; } + + void write_frob_mem(offs_t offset, u8 data) { m_frob_mem[offset & 0xfff] = data; } + + + void a2600_write_fff0(offs_t offset, u8 data) + { + m_byte_for_apple = data; + machine().scheduler().synchronize( + timer_expired_delegate(FUNC(a2600_frob_base_device::sync_write_byte_waiting_flag_apple), this), 1); + } + + u8 a2600_read_fff2(offs_t offset) + { + if (!machine().side_effects_disabled()) + { + machine().scheduler().synchronize( + timer_expired_delegate(FUNC(a2600_frob_base_device::sync_write_byte_waiting_flag_vcs), this), 0); + } + return m_byte_for_vcs; + } + + u8 a2600_read_fff1(offs_t offset) + { + return (m_byte_waiting_flag_apple ? 0 : 1) << 7 | (m_byte_waiting_flag_vcs << 6); + } + + void switch_A_w(uint8_t data); + uint8_t switch_A_r(); + void switch_B_w(uint8_t data); + void irq_callback(int state); + uint16_t a2600_read_input_port(offs_t offset); + uint8_t a2600_get_databus_contents(offs_t offset); + void a2600_tia_vsync_callback(uint16_t data); + + TIMER_CALLBACK_MEMBER(sync_write_byte_waiting_flag_vcs) { m_byte_waiting_flag_vcs = (u8) param; }; + TIMER_CALLBACK_MEMBER(sync_write_byte_waiting_flag_apple) { m_byte_waiting_flag_apple = (u8) param;}; + + required_shared_ptr m_riot_ram; + required_device m_tia; + required_device m_maincpu; + required_device m_riot; + required_device m_joy1; + required_device m_joy2; + required_device m_screen; + + u8 m_frob_mem[0x1000] = { 0 }; // 4k frob memory + u8 m_frob_apple_control = 0; // 0 means that it's under apple control + u8 m_byte_waiting_flag_apple = 0; + u8 m_byte_waiting_flag_vcs = 0; + u8 m_byte_for_vcs = 0; + u8 m_byte_for_apple = 0; + u8 m_bidirectional_active = 0; + u8 m_frob_page = 0; + u8 m_frob_control_reg = 0; + +private: + uint16_t m_current_screen_height = 0U; + XTAL m_xtal; + +}; + +a2600_frob_base_device::a2600_frob_base_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + a2600_frob_base_device(mconfig, A2BUS_FROB, tag, owner, clock) +{ +} + + +u8 a2600_frob_base_device::read_c0nx(u8 offset) +{ + // offset 0 is read from frob status (bits 7 = write ok, 6 = read ok) (other bits will float) + switch (offset & 0x1) + { + case 0: + return ((m_byte_waiting_flag_vcs ? 0 : 1) << 7) | + (m_byte_waiting_flag_apple << 6); + break; + case 1: + if (!machine().side_effects_disabled()) + { + machine().scheduler().synchronize( + timer_expired_delegate(FUNC(a2600_frob_base_device::sync_write_byte_waiting_flag_apple), this), 0); + } + return m_byte_for_apple; + default: + return 0; + } +} + +void a2600_frob_base_device::write_c0nx(u8 offset, u8 data) +{ + // offset 0 is write to frob control register + // offset 1 is write to bidirectional register + switch (offset & 0x1) + { + case 0: + m_frob_control_reg = data; + m_frob_apple_control = BIT(data, 4); + m_bidirectional_active = BIT(data, 5); + m_frob_page = BIT(data, 0, 4); + LOG("m_frob_apple_control = %x m_frob_page = %x\n",m_frob_apple_control, m_frob_page); + break; + case 1: + machine().scheduler().synchronize( + timer_expired_delegate(FUNC(a2600_frob_base_device::sync_write_byte_waiting_flag_vcs), this), 1); + m_byte_for_vcs = data; + break; + default: + ; + } +} + +void a2600_frob_base_device::a2600_mem(address_map &map) // 6507 has 13-bit address space, 0x0000 - 0x1fff +{ + map(0x0000, 0x007f).mirror(0x0f00).rw(m_tia, FUNC(a2frobtia_video_device::read), FUNC(a2frobtia_video_device::write)); + map(0x0080, 0x00ff).mirror(0x0d00).ram().share("riot_ram"); + map(0x0280, 0x029f).mirror(0x0d00).m("riot", FUNC(mos6532_device::io_map)); + map(0x1000, 0x1fff).r(FUNC(a2600_frob_base_device::read_frob_mem)); + map(0x1ff0, 0x1ff0).w(FUNC(a2600_frob_base_device::a2600_write_fff0)); + map(0x1ff1, 0x1ff1).r(FUNC(a2600_frob_base_device::a2600_read_fff1)); + map(0x1ff2, 0x1ff2).r(FUNC(a2600_frob_base_device::a2600_read_fff2)); +} + +void a2600_frob_base_device::switch_A_w(uint8_t data) +{ + /* Left controller port */ + m_joy1->joy_w(data >> 4); + + /* Right controller port */ + m_joy2->joy_w(data & 0x0f); +} + +uint8_t a2600_frob_base_device::switch_A_r() +{ + uint8_t val = 0; + + // Left controller port PINs 1-4 ( 4321 ) + val |= (m_joy1->read_joy() & 0x0f) << 4; + + // Right controller port PINs 1-4 ( 4321 ) + val |= m_joy2->read_joy() & 0x0f; + + return val; +} + +void a2600_frob_base_device::switch_B_w(uint8_t data) +{ +} + +void a2600_frob_base_device::irq_callback(int state) +{ +} + +uint16_t a2600_frob_base_device::a2600_read_input_port(offs_t offset) +{ + switch (offset) + { + case 0: // Left controller port PIN 5 + return m_joy1->read_pot_x(); + + case 1: // Left controller port PIN 9 + return m_joy1->read_pot_y(); + + case 2: // Right controller port PIN 5 + return m_joy2->read_pot_x(); + + case 3: // Right controller port PIN 9 + return m_joy2->read_pot_y(); + + case 4: // Left controller port PIN 6 + return (m_joy1->read_joy() & 0x20) ? 0xff : 0x7f; + + case 5: // Right controller port PIN 6 + return (m_joy2->read_joy() & 0x20) ? 0xff : 0x7f; + } + return 0xff; +} + +/* There are a few games that do an LDA ($80-$FF),Y instruction. + The contents off the databus then depend on whatever was read + from the RAM. To do this really properly the 6502 core would + need to keep track of the last databus contents so we can query + that. For now this is a quick hack to determine that value anyway. + Examples: + Q-Bert's Qubes (NTSC,F6) at 0x1594 + Berzerk at 0xF093. +*/ +uint8_t a2600_frob_base_device::a2600_get_databus_contents(offs_t offset) +{ + uint16_t last_address, prev_address; + uint8_t last_byte, prev_byte; + address_space& prog_space = m_maincpu->space(AS_PROGRAM); + + last_address = m_maincpu->pc() + 1; + if ( ! ( last_address & 0x1080 ) ) + { + return offset; + } + last_byte = prog_space.read_byte(last_address ); + if ( last_byte < 0x80 || last_byte == 0xFF ) + { + return last_byte; + } + prev_address = last_address - 1; + if ( ! ( prev_address & 0x1080 ) ) + { + return last_byte; + } + prev_byte = prog_space.read_byte(prev_address ); + if ( prev_byte == 0xB1 ) + { /* LDA (XX),Y */ + return prog_space.read_byte(last_byte + 1 ); + } + return last_byte; +} + +#if 0 +static const rectangle visarea[4] = { + { 26, 26 + 160 + 16, 24, 24 + 192 + 31 }, /* 262 */ + { 26, 26 + 160 + 16, 32, 32 + 228 + 31 }, /* 312 */ + { 26, 26 + 160 + 16, 45, 45 + 240 + 31 }, /* 328 */ + { 26, 26 + 160 + 16, 48, 48 + 240 + 31 } /* 342 */ +}; +#endif + +void a2600_frob_base_device::a2600_tia_vsync_callback(uint16_t data) +{ + for (int i = 0; i < std::size(supported_screen_heights); i++) + { + if (data >= supported_screen_heights[i] - 3 && data <= supported_screen_heights[i] + 3) + { + if (supported_screen_heights[i] != m_current_screen_height) + { + m_current_screen_height = supported_screen_heights[i]; +// m_screen->configure(228, m_current_screen_height, visarea[i], HZ_TO_ATTOSECONDS(m_xtal) * 228 * m_current_screen_height); + } + } + } +} + +//void a2600_frob_base_device::machine_start() +void a2600_frob_base_device::device_start() +{ + m_current_screen_height = m_screen->height(); + memset(m_riot_ram, 0x00, 0x80); + + save_item(NAME(m_current_screen_height)); + save_pointer(NAME(m_frob_mem), 0x1000); + + save_item(NAME(m_frob_apple_control)); + save_item(NAME(m_byte_waiting_flag_apple)); + save_item(NAME(m_byte_waiting_flag_vcs)); + save_item(NAME(m_byte_for_vcs)); + save_item(NAME(m_byte_for_apple)); + save_item(NAME(m_bidirectional_active)); + save_item(NAME(m_frob_page)); + save_item(NAME(m_frob_control_reg)); +} + + +INPUT_CHANGED_MEMBER( a2600_frob_base_device::reset_a2600 ) +{ + if (newval == 0) + { + m_maincpu->reset(); // reset a2600 cpu + m_tia->reset(); // need to reset tia or will segfault +// this->reset(); // reset whole device, too much? + m_byte_waiting_flag_apple = 0; + m_byte_waiting_flag_vcs = 0; + } +} + +static INPUT_PORTS_START( a2600_frob ) + PORT_START("SWB") + PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("Reset Game") PORT_CODE(KEYCODE_2_PAD) + PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_OTHER ) PORT_NAME("Select Game") PORT_CODE(KEYCODE_1_PAD) + PORT_BIT ( 0x04, IP_ACTIVE_LOW, IPT_UNUSED ) + PORT_CONFNAME( 0x08, 0x08, "TV Type" ) PORT_CODE(KEYCODE_C) PORT_TOGGLE + PORT_CONFSETTING( 0x08, "Color" ) + PORT_CONFSETTING( 0x00, "B&W" ) + PORT_BIT ( 0x10, IP_ACTIVE_LOW, IPT_UNUSED ) + PORT_BIT ( 0x20, IP_ACTIVE_LOW, IPT_UNUSED ) + PORT_CONFNAME( 0x40, 0x00, "Left Diff. Switch" ) PORT_CODE(KEYCODE_3) PORT_TOGGLE + PORT_CONFSETTING( 0x40, "A" ) + PORT_CONFSETTING( 0x00, "B" ) + PORT_CONFNAME( 0x80, 0x00, "Right Diff. Switch" ) PORT_CODE(KEYCODE_4) PORT_TOGGLE + PORT_CONFSETTING( 0x80, "A" ) + PORT_CONFSETTING( 0x00, "B" ) + + PORT_START("RESET") + PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_UNKNOWN ) PORT_CHANGED_MEMBER(DEVICE_SELF, FUNC(a2600_frob_base_device::reset_a2600), 0x00) PORT_NAME("Reset 2600") PORT_CODE(KEYCODE_MINUS_PAD) +INPUT_PORTS_END + + +void a2600_frob_base_device::device_add_mconfig(machine_config &config) +{ + /* basic machine hardware */ + M6507(config, m_maincpu, m_xtal / 3); + m_maincpu->set_addrmap(AS_PROGRAM, &a2600_frob_base_device::a2600_mem); + + + SCREEN(config, m_screen, SCREEN_TYPE_RASTER); + m_screen->set_raw(m_xtal, 228, 26, 26 + 160 + 16, 262, 24 , 24 + 192 + 31); + m_screen->set_screen_update("tia_video", FUNC(a2frobtia_video_device::screen_update)); + + /* video hardware */ + A2FROBTIA_NTSC_VIDEO(config, m_tia, 0, "tia"); + m_tia->read_input_port_callback().set(FUNC(a2600_frob_base_device::a2600_read_input_port)); + m_tia->databus_contents_callback().set(FUNC(a2600_frob_base_device::a2600_get_databus_contents)); + m_tia->vsync_callback().set(FUNC(a2600_frob_base_device::a2600_tia_vsync_callback)); + m_tia->set_screen("tia_screen"); // need to specify screen + + /* sound hardware */ + SPEAKER(config, "mono").front_center(); + TIA(config, "tia", m_xtal/114).add_route(ALL_OUTPUTS, "mono", 0.90); + + /* devices */ + MOS6532(config, m_riot, m_xtal / 3); + m_riot->pa_rd_callback().set(FUNC(a2600_frob_base_device::switch_A_r)); + m_riot->pa_wr_callback().set(FUNC(a2600_frob_base_device::switch_A_w)); + m_riot->pb_rd_callback().set_ioport("SWB"); + m_riot->pb_wr_callback().set(FUNC(a2600_frob_base_device::switch_B_w)); + m_riot->irq_wr_callback().set(FUNC(a2600_frob_base_device::irq_callback)); + + VCS_CONTROL_PORT(config, m_joy1, vcs_control_port_devices, "joy"); + VCS_CONTROL_PORT(config, m_joy2, vcs_control_port_devices, "joy"); +} + +// actually I think the a2600 doesn't have any rom in this context, +// since the frob supplies the ram (acting as ROM) +ROM_START(a2600_frob) + ROM_REGION(0x2000, "maincpu", ROMREGION_ERASEFF) +ROM_END + +tiny_rom_entry const * a2600_frob_base_device::device_rom_region() const +{ + return ROM_NAME(a2600_frob); +} + +ioport_constructor a2600_frob_base_device::device_input_ports() const +{ + return INPUT_PORTS_NAME(a2600_frob); +} + +} // anonymous namespace + +DEFINE_DEVICE_TYPE_PRIVATE(A2BUS_FROB, device_a2bus_card_interface, a2600_frob_base_device, "a2frob", "Apple II Frob Card") diff --git a/src/devices/bus/a2bus/a2frob.h b/src/devices/bus/a2bus/a2frob.h new file mode 100644 index 0000000000000..a48d18402fd39 --- /dev/null +++ b/src/devices/bus/a2bus/a2frob.h @@ -0,0 +1,17 @@ +// license:BSD-3-Clause +// copyright-holders: +/*********************************************************************** + + Apple 2 Frob Card by Frobco + +***********************************************************************/ +#ifndef MAME_BUS_A2BUS_A2FROB_H +#define MAME_BUS_A2BUS_A2FROB_H + +#pragma once + +#include "a2bus.h" + +DECLARE_DEVICE_TYPE(A2BUS_FROB, device_a2bus_card_interface) + +#endif // MAME_BUS_A2BUS_A2FROB_H diff --git a/src/devices/bus/a2bus/a2frobtia.cpp b/src/devices/bus/a2bus/a2frobtia.cpp new file mode 100644 index 0000000000000..4a785c72f3472 --- /dev/null +++ b/src/devices/bus/a2bus/a2frobtia.cpp @@ -0,0 +1,2227 @@ +// license:BSD-3-Clause +// copyright-holders:Wilbert Pol,Stefan Jokisch +/*************************************************************************** + + Atari Frob TIA video emulation + (copied from of mame/atari/tia.cpp for use with the frob card) + +***************************************************************************/ + +#include "emu.h" +#include "a2frobtia.h" +#include "screen.h" + +static const int nusiz[8][3] = +{ + { 1, 1, 0 }, + { 2, 1, 1 }, + { 2, 1, 3 }, + { 3, 1, 1 }, + { 2, 1, 7 }, + { 1, 2, 0 }, + { 3, 1, 3 }, + { 1, 4, 0 } +}; + +void a2frobtia_video_device::extend_palette() +{ + for (int i = 0; i < 128; i++) + { + rgb_t new_rgb = pen_color( i ); + uint8_t new_r = new_rgb .r(); + uint8_t new_g = new_rgb .g(); + uint8_t new_b = new_rgb .b(); + + for (int j = 0; j < 128; j++) + { + rgb_t old_rgb = pen_color( j ); + uint8_t old_r = old_rgb .r(); + uint8_t old_g = old_rgb .g(); + uint8_t old_b = old_rgb .b(); + + set_pen_color(( ( i + 1 ) << 7 ) | j, + ( new_r + old_r ) / 2, + ( new_g + old_g ) / 2, + ( new_b + old_b ) / 2 ); + } + } +} + +void a2frobtia_ntsc_video_device::init_palette() +{ + /******************************************************************** + Atari 2600 NTSC Palette Notes: + + Palette on a modern flat panel display (LCD, LED, Plasma, etc.) + appears different from a traditional CRT. The most outstanding + difference is Hue 1x, the hue begin point. Hue 1x looks very + 'green' (~-60 to -45 degrees - depending on how poor or well it + handles the signal conversion and its calibration) on a modern + flat panel display, as opposed to 'gold' (~-33 degrees) on a CRT. + + The official technical documents: "Television Interface Adaptor + [TIA] (Model 1A)", "Atari VCS POP Field Service Manual", and + "Stella Programmer's Guide" stipulate Hue 1x to be gold. + + The system's pot adjustment manually manipulates the degree of + phase shift, while the system 'warming-up' will automatically + push whatever degrees has been manually set, higher. According + to the Atari VCS POP Field Service Manual and system diagnostic + and test (color) cart, instructions are provide to set the pot + adjustment having Hue 1x and Hue 15x (F$) match or within one + shade of each other, both a 'goldenrod'. + + At power on, the system's phase shift appears as low as ~23 + degrees and after a considerable consistent runtime, can be as + high as ~28 degrees. + + In general, the low end of ~23 degrees lasts for several seconds, + whereas higher values such as ~25-27 degrees are the most + dominant during system run time. 180 degrees colorburst takes + place at ~25.7 degrees (A near exact match of Hue 1x and 15x - + To the naked eye they appear to be the same). + + However, if the system is adjusted within the first several + minutes of running, the warm up, consistent system run time, + causes Hue 15x (F$) to become stronger/darker gold (More brown + then ultimately red-brown); as well as leans Hue 14x (E$) more + brown than green. Once achieving a phase shift of 27.7 degrees, + Hue 14x (E$) and Hue 15x (F$) near-exact match Hue 1x and 2x + respectively. + + Therefore, an ideal phase shift while accounting for properly + calibrating a system's color palette within the first several + minutes of it running via the pot adjustment, the reality of + shifting while warming up, as well as maintaining differences + between Hues 1x, 2x and 14x, 15x, would likely fall between 25.7 + and 27.7 degrees. Phase shifts 26.2 and 26.7 places Hue 15x/F$ + between Hue 1x and Hue 2x, having 26.2 degrees leaning closer to + Hue 1x and 26.7 degrees leaning closer to Hue 2x. + + The above notion would also harmonize with what has been + documented within "Stella Programmer's Guide" for the colors of + 1x, 2x, 14x, 15x on the 2600 and 7800. 1x = Gold, 2x = Orange, + 14x (E$) = Orange-Green. 15x (F$) = Light Orange. Color + descriptions are best measured in the middle of the brightness + scale. It should be mentioned that Green-Yellow is referenced + at Hue 13x (D$), nowhere near Hue 1x. A Green-Yellow Hue 1x is + how the palette is manipulated and modified (in part) under a + modern flat panel display. + + Additionally, the blue to red (And consequently blue to green) + ratio proportions may appear different on a modern flat panel + display than a CRT in some instances for the Atari 2600 system. + Furthermore, you may have some variation of proportions even + within the same display type. + + One side effect of this on the console's palette is that some + values of red may appear too pinkish - Too much blue to red. + This is not the same as a traditional tint-hue control adjustment; + rather, can be demonstrated by changing the blue ratio values + via MAME HLSL settings. + + Lastly, the Atari 5200 & 7800 NTSC color palettes hold the same + hue structure order and have similar appearance differences that + are dependent upon display type. + ********************************************************************/ + /********************************* + Phase Shift 24.7 + { 0.000, 0.000 }, + { 0.192, -0.127 }, + { 0.239, -0.052 }, + { 0.244, 0.030 }, + { 0.201, 0.108 }, + { 0.125, 0.166 }, + { 0.026, 0.194 }, + { -0.080, 0.185 }, + { -0.169, 0.145 }, + { -0.230, 0.077 }, + { -0.247, -0.006 }, + { -0.220, -0.087 }, + { -0.152, -0.153 }, + { -0.057, -0.189 }, + { 0.049, -0.193 }, + { 0.144, -0.161 } + + Phase Shift 25.2 + { 0.000, 0.000 }, + { 0.192, -0.127 }, + { 0.239, -0.052 }, + { 0.244, 0.033 }, + { 0.200, 0.113 }, + { 0.119, 0.169 }, + { 0.013, 0.195 }, + { -0.094, 0.183 }, + { -0.182, 0.136 }, + { -0.237, 0.062 }, + { -0.245, -0.020 }, + { -0.210, -0.103 }, + { -0.131, -0.164 }, + { -0.027, -0.193 }, + { 0.079, -0.187 }, + { 0.169, -0.145 } + + Phase Shift 25.7 + { 0.000, 0.000 }, + { 0.192, -0.127 }, + { 0.243, -0.049 }, + { 0.242, 0.038 }, + { 0.196, 0.116 }, + { 0.109, 0.172 }, + { 0.005, 0.196 }, + { -0.104, 0.178 }, + { -0.192, 0.127 }, + { -0.241, 0.051 }, + { -0.244, -0.037 }, + { -0.197, -0.115 }, + { -0.112, -0.173 }, + { -0.004, -0.197 }, + { 0.102, -0.179 }, + { 0.190, -0.128 } + + Phase Shift 26.7 + { 0.000, 0.000 }, + { 0.192, -0.127 }, + { 0.242, -0.046 }, + { 0.240, 0.044 }, + { 0.187, 0.125 }, + { 0.092, 0.180 }, + { -0.020, 0.195 }, + { -0.128, 0.170 }, + { -0.210, 0.107 }, + { -0.247, 0.022 }, + { -0.231, -0.067 }, + { -0.166, -0.142 }, + { -0.064, -0.188 }, + { 0.049, -0.193 }, + { 0.154, -0.155 }, + { 0.227, -0.086 } + + Phase Shift 27.2 + { 0.000, 0.000 }, + { 0.192, -0.127 }, + { 0.243, -0.044 }, + { 0.239, 0.047 }, + { 0.183, 0.129 }, + { 0.087, 0.181 }, + { -0.029, 0.195 }, + { -0.138, 0.164 }, + { -0.217, 0.098 }, + { -0.246, 0.009 }, + { -0.223, -0.081 }, + { -0.149, -0.153 }, + { -0.041, -0.192 }, + { 0.073, -0.188 }, + { 0.173, -0.142 }, + { 0.235, -0.067 } + + Phase Shift 27.7 + { 0.000, 0.000 }, + { 0.192, -0.127 }, + { 0.243, -0.044 }, + { 0.238, 0.051 }, + { 0.178, 0.134 }, + { 0.078, 0.184 }, + { -0.041, 0.194 }, + { -0.151, 0.158 }, + { -0.224, 0.087 }, + { -0.248, -0.005 }, + { -0.214, -0.096 }, + { -0.131, -0.164 }, + { -0.019, -0.195 }, + { 0.099, -0.182 }, + { 0.194, -0.126 }, + { 0.244, -0.042 } + *********************************/ + + /********************************* + Phase Shift 26.2 + **********************************/ + static constexpr double color[16][2] = + { + { 0.000, 0.000 }, + { 0.192, -0.127 }, + { 0.241, -0.048 }, + { 0.240, 0.040 }, + { 0.191, 0.121 }, + { 0.103, 0.175 }, + { -0.008, 0.196 }, + { -0.116, 0.174 }, + { -0.199, 0.118 }, + { -0.243, 0.037 }, + { -0.237, -0.052 }, + { -0.180, -0.129 }, + { -0.087, -0.181 }, + { 0.021, -0.196 }, + { 0.130, -0.169 }, + { 0.210, -0.107 } + }; + + for (int i = 0; i < 16; i++) + { + double const I = color[i][0]; + double const Q = color[i][1]; + + for (int j = 0; j < 8; j++) + { + double const Y = j / 7.0; + + double R = Y + 0.956 * I + 0.621 * Q; + double G = Y - 0.272 * I - 0.647 * Q; + double B = Y - 1.106 * I + 1.703 * Q; + + if (R < 0) R = 0; + if (G < 0) G = 0; + if (B < 0) B = 0; + + R = pow(R, 0.9); + G = pow(G, 0.9); + B = pow(B, 0.9); + + if (R > 1) R = 1; + if (G > 1) G = 1; + if (B > 1) B = 1; + + set_pen_color( + 8 * i + j, + uint8_t(255 * R + 0.5), + uint8_t(255 * G + 0.5), + uint8_t(255 * B + 0.5)); + } + } + extend_palette(); +} + + +void a2frobtia_pal_video_device::init_palette() +{ + static constexpr double color[16][2] = + { + { 0.000, 0.000 }, + { 0.000, 0.000 }, + { -0.222, 0.032 }, + { -0.199, -0.027 }, + { -0.173, 0.117 }, + { -0.156, -0.197 }, + { -0.094, 0.200 }, + { -0.071, -0.229 }, + { 0.070, 0.222 }, + { 0.069, -0.204 }, + { 0.177, 0.134 }, + { 0.163, -0.130 }, + { 0.219, 0.053 }, + { 0.259, -0.042 }, + { 0.000, 0.000 }, + { 0.000, 0.000 } + }; + + for (int i = 0; i < 16; i++) + { + double const U = color[i][0]; + double const V = color[i][1]; + + for (int j = 0; j < 8; j++) + { + double const Y = j / 7.0; + + double R = Y + 1.403 * V; + double G = Y - 0.344 * U - 0.714 * V; + double B = Y + 1.770 * U; + + if (R < 0) R = 0; + if (G < 0) G = 0; + if (B < 0) B = 0; + + R = pow(R, 1.2); + G = pow(G, 1.2); + B = pow(B, 1.2); + + if (R > 1) R = 1; + if (G > 1) G = 1; + if (B > 1) B = 1; + + set_pen_color( + 8 * i + j, + uint8_t(255 * R + 0.5), + uint8_t(255 * G + 0.5), + uint8_t(255 * B + 0.5)); + } + } + extend_palette(); +} + +a2frobtia_video_device::a2frobtia_video_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, type, tag, owner, clock) + , device_video_interface(mconfig, *this) + , device_palette_interface(mconfig, *this) + , m_read_input_port_cb(*this, TIA_INPUT_PORT_ALWAYS_ON) + , m_databus_contents_cb(*this, 0x3f) + , m_vsync_cb(*this) + , m_maincpu(*this, "^maincpu") + , m_tia(*this, finder_base::DUMMY_TAG) +{ +} + +// device type definition +DEFINE_DEVICE_TYPE(A2FROBTIA_PAL_VIDEO, a2frobtia_pal_video_device, "a2frobtia_pal_video", "A2Frob TIA Video (PAL)") + +//------------------------------------------------- +// a2frobtia_pal_video_device - constructor +//------------------------------------------------- + +a2frobtia_pal_video_device::a2frobtia_pal_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : a2frobtia_video_device(mconfig, A2FROBTIA_PAL_VIDEO, tag, owner, clock) +{ +} + +// device type definition +DEFINE_DEVICE_TYPE(A2FROBTIA_NTSC_VIDEO, a2frobtia_ntsc_video_device, "a2frobtia_ntsc_video", "A2Frob TIA Video (NTSC)") + +//------------------------------------------------- +// a2frobtia_ntsc_video_device - constructor +//------------------------------------------------- + +a2frobtia_ntsc_video_device::a2frobtia_ntsc_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : a2frobtia_video_device(mconfig, A2FROBTIA_NTSC_VIDEO, tag, owner, clock) +{ +} + +//------------------------------------------------- +// device_start - device-specific startup +//------------------------------------------------- + +void a2frobtia_video_device::device_start() +{ + init_palette(); + + int cx = screen().width(); + + screen_height = screen().height(); + helper[0].allocate(cx, TIA_MAX_SCREEN_HEIGHT); + helper[1].allocate(cx, TIA_MAX_SCREEN_HEIGHT); + buffer.allocate(cx, TIA_MAX_SCREEN_HEIGHT); + + register_save_state(); +} + + +//------------------------------------------------- +// screen_update - +//------------------------------------------------- + +uint32_t a2frobtia_video_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) +{ + screen_height = screen.height(); + copybitmap(bitmap, buffer, 0, 0, 0, 0, cliprect); + return 0; +} + + +void a2frobtia_video_device::draw_sprite_helper(uint8_t* p, uint8_t *col, struct player_gfx *gfx, + uint8_t GRP, uint8_t COLUP, uint8_t REFP) +{ + int i; + int j; + int k; + + if (REFP & 8) + { + GRP = bitswap<8>(GRP, 0, 1, 2, 3, 4, 5, 6, 7); + } + + for (i = 0; i < PLAYER_GFX_SLOTS; i++) + { + int start_pos = gfx->start_drawing[i]; + for (j = gfx->start_pixel[i]; j < 8; j++) + { + for (k = 0; k < gfx->size[i]; k++) + { + if (GRP & (0x80 >> j)) + { + if ( start_pos < 160 || ! gfx->skipclip[i] ) { + p[start_pos % 160] = COLUP >> 1; + col[start_pos % 160] = COLUP >> 1; + } + } + start_pos++; + } + } + } +} + + +void a2frobtia_video_device::draw_missile_helper(uint8_t* p, uint8_t* col, int horz, int skipdelay, int latch, int start, + uint8_t RESMP, uint8_t ENAM, uint8_t NUSIZ, uint8_t COLUM) +{ + int num = nusiz[NUSIZ & 7][0]; + int skp = nusiz[NUSIZ & 7][2]; + + int width = 1 << ((NUSIZ >> 4) & 3); + + int i; + int j; + + for (i = 0; i < num; i++) + { + if ( i == 0 ) + horz -= skipdelay; + if ( i == 1 ) + horz += skipdelay; + if ( i > 0 || start ) { + for (j = 0; j < width; j++) + { + if ((ENAM & 2) && !(RESMP & 2)) + { + if ( latch ) { + switch ( horz % 4 ) { + case 1: + if ( horz >= 0 ) + { + if ( horz < 156 ) { + p[(horz + 1) % 160] = COLUM >> 1; + col[(horz + 1) % 160] = COLUM >> 1; + } + p[horz % 160] = COLUM >> 1; + col[horz % 160] = COLUM >> 1; + } + break; + case 2: + case 3: + if ( horz >= 0 ) + { + p[horz % 160] = COLUM >> 1; + col[horz % 160] = COLUM >> 1; + } + break; + } + } else { + if ( horz >= 0 ) + { + p[horz % 160] = COLUM >> 1; + col[horz % 160] = COLUM >> 1; + } + } + } + + horz++; + } + } else { + horz+= width; + } + + horz += 8 * (skp + 1) - width; + } +} + + +void a2frobtia_video_device::draw_playfield_helper(uint8_t* p, uint8_t* col, int horz, + uint8_t COLU, uint8_t REFPF) +{ + uint32_t PF = + (bitswap<8>(PF0, 0, 1, 2, 3, 4, 5, 6, 7) << 0x10) | + (bitswap<8>(PF1, 7, 6, 5, 4, 3, 2, 1, 0) << 0x08) | + (bitswap<8>(PF2, 0, 1, 2, 3, 4, 5, 6, 7) << 0x00); + + int i; + int j; + + if (REFPF) + { + uint32_t swap = 0; + + for (i = 0; i < 20; i++) + { + swap <<= 1; + + if (PF & 1) + { + swap |= 1; + } + + PF >>= 1; + } + + PF = swap; + } + + for (i = 0; i < 20; i++) + { + for (j = 0; j < 4; j++) + { + if (PF & (0x80000 >> i)) + { + p[horz] = COLU >> 1; + col[horz] = COLU >> 1; + } + + horz++; + } + } +} + + +void a2frobtia_video_device::draw_ball_helper(uint8_t* p, uint8_t* col, int horz, uint8_t ENAB) +{ + int width = 1 << ((CTRLPF >> 4) & 3); + + int i; + + for (i = 0; i < width; i++) + { + if (ENAB & 2) + { + p[horz % 160] = COLUPF >> 1; + col[horz % 160] = COLUPF >> 1; + } + + horz++; + } +} + + +void a2frobtia_video_device::drawS0(uint8_t* p, uint8_t* col) +{ + draw_sprite_helper(p, col, &p0gfx, (VDELP0 & 1) ? prevGRP0 : GRP0, COLUP0, REFP0); +} + + +void a2frobtia_video_device::drawS1(uint8_t* p, uint8_t* col) +{ + draw_sprite_helper(p, col, &p1gfx, (VDELP1 & 1) ? prevGRP1 : GRP1, COLUP1, REFP1); +} + + +void a2frobtia_video_device::drawM0(uint8_t* p, uint8_t* col) +{ + draw_missile_helper(p, col, horzM0, skipM0delay, HMM0_latch, startM0, RESMP0, ENAM0, NUSIZ0, COLUP0); +} + + +void a2frobtia_video_device::drawM1(uint8_t* p, uint8_t* col) +{ + draw_missile_helper(p, col, horzM1, skipM1delay, HMM1_latch, startM1, RESMP1, ENAM1, NUSIZ1, COLUP1); +} + + +void a2frobtia_video_device::drawBL(uint8_t* p, uint8_t* col) +{ + draw_ball_helper(p, col, horzBL, (VDELBL & 1) ? prevENABL : ENABL); +} + + +void a2frobtia_video_device::drawPF(uint8_t* p, uint8_t *col) +{ + draw_playfield_helper(p, col, 0, + ((CTRLPF & 6) == 2) ? COLUP0 : COLUPF, 0); + + draw_playfield_helper(p, col, 80, + ((CTRLPF & 6) == 2) ? COLUP1 : COLUPF, REFLECT); +} + + +int a2frobtia_video_device::collision_check(uint8_t* p1, uint8_t* p2, int x1, int x2) +{ + int i; + + for (i = x1; i < x2; i++) + { + if (p1[i] != 0xFF && p2[i] != 0xFF) + { + return 1; + } + } + + return 0; +} + + +int a2frobtia_video_device::current_x() +{ + return 3 * ((m_maincpu->total_cycles() - frame_cycles) % 76) - 68; +} + + +int a2frobtia_video_device::current_y() +{ + return (m_maincpu->total_cycles() - frame_cycles) / 76; +} + + +void a2frobtia_video_device::setup_pXgfx(void) +{ + int i; + for ( i = 0; i < PLAYER_GFX_SLOTS; i++ ) + { + if ( i < nusiz[NUSIZ0 & 7][0] && i >= ( startP0 ? 0 : 1 ) ) + { + p0gfx.size[i] = nusiz[NUSIZ0 & 7][1]; + if ( i ) + { + p0gfx.start_drawing[i] = ( horzP0 + (p0gfx.size[i] > 1 ? 1 : 0) + + i * 8 * ( nusiz[NUSIZ0 & 7][2] + p0gfx.size[i] ) ) % 160; + p0gfx.skipclip[i] = 0; + } + else + { + p0gfx.start_drawing[i] = horzP0 + (p0gfx.size[i] > 1 ? 1 : 0 ); + p0gfx.skipclip[i] = skipclipP0; + } + p0gfx.start_pixel[i] = 0; + } + else + { + p0gfx.start_pixel[i] = 8; + } + if ( i < nusiz[NUSIZ1 & 7][0] && i >= ( startP1 ? 0 : 1 ) ) + { + p1gfx.size[i] = nusiz[NUSIZ1 & 7][1]; + if ( i ) + { + p1gfx.start_drawing[i] = ( horzP1 + (p1gfx.size[i] > 1 ? 1 : 0) + + i * 8 * ( nusiz[NUSIZ1 & 7][2] + p1gfx.size[i] ) ) % 160; + p1gfx.skipclip[i] = 0; + } + else + { + p1gfx.start_drawing[i] = horzP1 + (p1gfx.size[i] > 1 ? 1 : 0); + p1gfx.skipclip[i] = skipclipP1; + } + p1gfx.start_pixel[i] = 0; + } + else + { + p1gfx.start_pixel[i] = 8; + } + } +} + +void a2frobtia_video_device::update_bitmap(int next_x, int next_y) +{ + uint8_t linePF[160]; + uint8_t lineP0[160]; + uint8_t lineP1[160]; + uint8_t lineM0[160]; + uint8_t lineM1[160]; + uint8_t lineBL[160]; + + uint8_t temp[160]; + + if (prev_y >= next_y && prev_x >= next_x) + { + return; + } + + memset(linePF, 0xFF, sizeof linePF); + memset(lineP0, 0xFF, sizeof lineP0); + memset(lineP1, 0xFF, sizeof lineP1); + memset(lineM0, 0xFF, sizeof lineM0); + memset(lineM1, 0xFF, sizeof lineM1); + memset(lineBL, 0xFF, sizeof lineBL); + + if (VBLANK & 2) + { + memset(temp, 0, 160); + } + else + { + memset(temp, COLUBK >> 1, 160); + + if (CTRLPF & 4) + { + drawS1(temp, lineP1); + drawM1(temp, lineM1); + drawS0(temp, lineP0); + drawM0(temp, lineM0); + drawPF(temp, linePF); + drawBL(temp, lineBL); + } + else + { + drawPF(temp, linePF); + drawBL(temp, lineBL); + drawS1(temp, lineP1); + drawM1(temp, lineM1); + drawS0(temp, lineP0); + drawM0(temp, lineM0); + } + } + + for (int y = prev_y; y <= next_y; y++) + { + int x1 = prev_x; + int x2 = next_x; + int colx1; + + /* Check if we have crossed a line boundary */ + if (y != prev_y ) { + int redraw_line = 0; + + HMOVE_started_previous = HMOVE_INACTIVE; + + if ( HMOVE_started != HMOVE_INACTIVE ) { + /* Apply pending motion clocks from a HMOVE initiated during the scanline */ + if ( HMOVE_started >= 97 && HMOVE_started < 157 ) { + horzP0 -= motclkP0; + horzP1 -= motclkP1; + horzM0 -= motclkM0; + horzM1 -= motclkM1; + horzBL -= motclkBL; + if (horzP0 < 0) + horzP0 += 160; + if (horzP1 < 0) + horzP1 += 160; + if (horzM0 < 0) + horzM0 += 160; + if (horzM1 < 0) + horzM1 += 160; + if (horzBL < 0) + horzBL += 160; + HMOVE_started_previous = HMOVE_started; + } + HMOVE_started = HMOVE_INACTIVE; + redraw_line = 1; + } + + /* Redraw line if the playfield reflect bit was changed after the center of the screen */ + if ( REFLECT != ( CTRLPF & 0x01 ) ) { + REFLECT = CTRLPF & 0x01; + redraw_line = 1; + } + + /* Redraw line if a RESPx or NUSIZx occurred during the last line */ + if ( ! startP0 || ! startP1 || ! startM0 || ! startM1) { + startP0 = 1; + startP1 = 1; + startM0 = 1; + startM1 = 1; + + redraw_line = 1; + } + + if ( skipclipP0 ) { + skipclipP0--; + redraw_line = 1; + } + + if ( skipclipP1 ) { + skipclipP1--; + redraw_line = 1; + } + + /* Redraw line if HMP0 latch is still set */ + if ( HMP0_latch ) { + horzP0 -= 17; + if ( horzP0 < 0 ) + horzP0 += 160; + redraw_line = 1; + } + + /* Redraw line if HMP1 latch is still set */ + if ( HMP1_latch ) { + horzP1 -= 17; + if ( horzP1 < 0 ) + horzP1 += 160; + redraw_line = 1; + } + + /* Redraw line if HMM0 latch is still set */ + if ( HMM0_latch ) { + horzM0 -= 17; + if ( horzM0 < 0 ) + horzM0 += 160; + redraw_line = 1; + } + + /* Redraw line if HMM1 latch is still set */ + if ( HMM1_latch ) { + horzM1 -= 17; + if ( horzM1 < 0 ) + horzM1 += 160; + redraw_line = 1; + } + + /* Redraw line if HMBL latch is still set */ + if ( HMBL_latch ) { + horzBL -= 17; + if ( horzBL < 0 ) + horzBL += 160; + redraw_line = 1; + } + + /* Redraw line if NUSIZx data was changed */ + if ( NUSIZx_changed ) { + NUSIZx_changed = 0; + redraw_line = 1; + } + + if ( skipM0delay || skipM1delay ) { + skipM0delay = 0; + skipM1delay = 0; + redraw_line = 1; + } + + if ( redraw_line ) { + if (VBLANK & 2) + { + setup_pXgfx(); + memset(temp, 0, 160); + } + else + { + memset(linePF, 0xFF, sizeof linePF); + memset(lineP0, 0xFF, sizeof lineP0); + memset(lineP1, 0xFF, sizeof lineP1); + memset(lineM0, 0xFF, sizeof lineM0); + memset(lineM1, 0xFF, sizeof lineM1); + memset(lineBL, 0xFF, sizeof lineBL); + + memset(temp, COLUBK >> 1, 160); + + setup_pXgfx(); + + if (CTRLPF & 4) + { + drawS1(temp, lineP1); + drawM1(temp, lineM1); + drawS0(temp, lineP0); + drawM0(temp, lineM0); + drawPF(temp, linePF); + drawBL(temp, lineBL); + } + else + { + drawPF(temp, linePF); + drawBL(temp, lineBL); + drawS1(temp, lineP1); + drawM1(temp, lineM1); + drawS0(temp, lineP0); + drawM0(temp, lineM0); + } + } + } + } + if (y != prev_y || x1 < 0) + { + x1 = 0; + } + if (y != next_y || x2 > 160) + { + x2 = 160; + } + + /* Collision detection also takes place under the extended hblank area */ + colx1 = ( x1 == 8 && HMOVE_started != HMOVE_INACTIVE ) ? 0 : x1; + + if (collision_check(lineM0, lineP1, colx1, x2)) + CXM0P |= 0x80; + if (collision_check(lineM0, lineP0, colx1, x2)) + CXM0P |= 0x40; + if (collision_check(lineM1, lineP0, colx1, x2)) + CXM1P |= 0x80; + if (collision_check(lineM1, lineP1, colx1, x2)) + CXM1P |= 0x40; + if (collision_check(lineP0, linePF, colx1, x2)) + CXP0FB |= 0x80; + if (collision_check(lineP0, lineBL, colx1, x2)) + CXP0FB |= 0x40; + if (collision_check(lineP1, linePF, colx1, x2)) + CXP1FB |= 0x80; + if (collision_check(lineP1, lineBL, colx1, x2)) + CXP1FB |= 0x40; + if (collision_check(lineM0, linePF, colx1, x2)) + CXM0FB |= 0x80; + if (collision_check(lineM0, lineBL, colx1, x2)) + CXM0FB |= 0x40; + if (collision_check(lineM1, linePF, colx1, x2)) + CXM1FB |= 0x80; + if (collision_check(lineM1, lineBL, colx1, x2)) + CXM1FB |= 0x40; + if (collision_check(lineBL, linePF, colx1, x2)) + CXBLPF |= 0x80; + if (collision_check(lineP0, lineP1, colx1, x2)) + CXPPMM |= 0x80; + if (collision_check(lineM0, lineM1, colx1, x2)) + CXPPMM |= 0x40; + + uint16_t *p = &helper[current_bitmap].pix(y % screen_height, 34); + + for (int x = x1; x < x2; x++) + { + p[x] = temp[x]; + } + + if ( x2 == 160 && y % screen_height == (screen_height - 1) ) { + for ( int t_y = 0; t_y < buffer.height(); t_y++ ) { + uint16_t* l0 = &helper[current_bitmap].pix(t_y); + uint16_t* l1 = &helper[1 - current_bitmap].pix(t_y); + uint32_t* l2 = &buffer.pix(t_y); + int t_x; + for( t_x = 0; t_x < buffer.width(); t_x++ ) { + if ( l0[t_x] != l1[t_x] ) { + /* Combine both entries */ + l2[t_x] = pen(( ( l0[t_x] + 1 ) << 7 ) | l1[t_x]); + } else { + l2[t_x] = pen(l0[t_x]); + } + } + } + current_bitmap ^= 1; + } + } + + prev_x = next_x; + prev_y = next_y; +} + + +void a2frobtia_video_device::WSYNC_w() +{ + int cycles = m_maincpu->total_cycles() - frame_cycles; + + if (cycles % 76) + { + m_maincpu->adjust_icount(cycles % 76 - 76); + } +} + + +void a2frobtia_video_device::VSYNC_w(uint8_t data) +{ + if (data & 2) + { + if (!(VSYNC & 2)) + { + int curr_y = current_y(); + + if ( curr_y > 5 ) + update_bitmap(screen().width(), screen().height()); + + m_vsync_cb(0, curr_y, 0xFFFF); + + prev_y = 0; + prev_x = 0; + + frame_cycles += 76 * current_y(); + } + } + + VSYNC = data; +} + + +void a2frobtia_video_device::VBLANK_w(uint8_t data) +{ + if (data & 0x80) + { + paddle_start = m_maincpu->total_cycles(); + } + if ( ! ( VBLANK & 0x40 ) ) { + INPT4 = 0x80; + INPT5 = 0x80; + } + VBLANK = data; +} + + +void a2frobtia_video_device::CTRLPF_w(uint8_t data) +{ + int curr_x = current_x(); + + CTRLPF = data; + if ( curr_x < 80 ) { + REFLECT = CTRLPF & 1; + } +} + +void a2frobtia_video_device::HMP0_w(uint8_t data) +{ + int curr_x = current_x(); + + data &= 0xF0; + + if ( data == HMP0 ) + return; + + /* Check if HMOVE cycles are still being applied */ + if ( HMOVE_started != HMOVE_INACTIVE && curr_x < std::min( HMOVE_started + 6 + motclkP0 * 4, 7 ) ) { + int new_motclkP0 = ( data ^ 0x80 ) >> 4; + + /* Check if new horizontal move can still be applied normally */ + if ( new_motclkP0 > motclkP0 || curr_x <= std::min( HMOVE_started + 6 + new_motclkP0 * 4, 7 ) ) { + horzP0 -= ( new_motclkP0 - motclkP0 ); + motclkP0 = new_motclkP0; + } else { + horzP0 -= ( 15 - motclkP0 ); + motclkP0 = 15; + if ( data != 0x70 && data != 0x80 ) { + HMP0_latch = 1; + } + } + if ( horzP0 < 0 ) + horzP0 += 160; + horzP0 %= 160; + setup_pXgfx(); + } + HMP0 = data; +} + +void a2frobtia_video_device::HMP1_w(uint8_t data) +{ + int curr_x = current_x(); + + data &= 0xF0; + + if ( data == HMP1 ) + return; + + /* Check if HMOVE cycles are still being applied */ + if ( HMOVE_started != HMOVE_INACTIVE && curr_x < std::min( HMOVE_started + 6 + motclkP1 * 4, 7 ) ) { + int new_motclkP1 = ( data ^ 0x80 ) >> 4; + + /* Check if new horizontal move can still be applied normally */ + if ( new_motclkP1 > motclkP1 || curr_x <= std::min( HMOVE_started + 6 + new_motclkP1 * 4, 7 ) ) { + horzP1 -= ( new_motclkP1 - motclkP1 ); + motclkP1 = new_motclkP1; + } else { + horzP1 -= ( 15 - motclkP1 ); + motclkP1 = 15; + if ( data != 0x70 && data != 0x80 ) { + HMP1_latch = 1; + } + } + if ( horzP1 < 0 ) + horzP1 += 160; + horzP1 %= 160; + setup_pXgfx(); + } + HMP1 = data; +} + +void a2frobtia_video_device::HMM0_w(uint8_t data) +{ + int curr_x = current_x(); + + data &= 0xF0; + + if ( data == HMM0 ) + return; + + /* Check if HMOVE cycles are still being applied */ + if ( HMOVE_started != HMOVE_INACTIVE && curr_x < std::min( HMOVE_started + 6 + motclkM0 * 4, 7 ) ) { + int new_motclkM0 = ( data ^ 0x80 ) >> 4; + + /* Check if new horizontal move can still be applied normally */ + if ( new_motclkM0 > motclkM0 || curr_x <= std::min( HMOVE_started + 6 + new_motclkM0 * 4, 7 ) ) { + horzM0 -= ( new_motclkM0 - motclkM0 ); + motclkM0 = new_motclkM0; + } else { + horzM0 -= ( 15 - motclkM0 ); + motclkM0 = 15; + if ( data != 0x70 && data != 0x80 ) { + HMM0_latch = 1; + } + } + if ( horzM0 < 0 ) + horzM0 += 160; + horzM0 %= 160; + } + HMM0 = data; +} + +void a2frobtia_video_device::HMM1_w(uint8_t data) +{ + int curr_x = current_x(); + + data &= 0xF0; + + if ( data == HMM1 ) + return; + + /* Check if HMOVE cycles are still being applied */ + if ( HMOVE_started != HMOVE_INACTIVE && curr_x < std::min( HMOVE_started + 6 + motclkM1 * 4, 7 ) ) { + int new_motclkM1 = ( data ^ 0x80 ) >> 4; + + /* Check if new horizontal move can still be applied normally */ + if ( new_motclkM1 > motclkM1 || curr_x <= std::min( HMOVE_started + 6 + new_motclkM1 * 4, 7 ) ) { + horzM1 -= ( new_motclkM1 - motclkM1 ); + motclkM1 = new_motclkM1; + } else { + horzM1 -= ( 15 - motclkM1 ); + motclkM1 = 15; + if ( data != 0x70 && data != 0x80 ) { + HMM1_latch = 1; + } + } + if ( horzM1 < 0 ) + horzM1 += 160; + horzM1 %= 160; + } + HMM1 = data; +} + +void a2frobtia_video_device::HMBL_w(uint8_t data) +{ + int curr_x = current_x(); + + data &= 0xF0; + + if ( data == HMBL ) + return; + + /* Check if HMOVE cycles are still being applied */ + if ( HMOVE_started != HMOVE_INACTIVE && curr_x < std::min( HMOVE_started + 6 + motclkBL * 4, 7 ) ) { + int new_motclkBL = ( data ^ 0x80 ) >> 4; + + /* Check if new horizontal move can still be applied normally */ + if ( new_motclkBL > motclkBL || curr_x <= std::min( HMOVE_started + 6 + new_motclkBL * 4, 7 ) ) { + horzBL -= ( new_motclkBL - motclkBL ); + motclkBL = new_motclkBL; + } else { + horzBL -= ( 15 - motclkBL ); + motclkBL = 15; + if ( data != 0x70 && data != 0x80 ) { + HMBL_latch = 1; + } + } + if ( horzBL < 0 ) + horzBL += 160; + horzBL %= 160; + } + HMBL = data; +} + +void a2frobtia_video_device::HMOVE_w(uint8_t data) +{ + int curr_x = current_x(); + int curr_y = current_y(); + + HMOVE_started = curr_x; + + /* Check if we have to undo some of the already applied cycles from an active graphics latch */ + if ( curr_x + 68 < 17 * 4 ) { + int cycle_fix = 17 - ( ( curr_x + 68 + 7 ) / 4 ); + if ( HMP0_latch ) + horzP0 = ( horzP0 + cycle_fix ) % 160; + if ( HMP1_latch ) + horzP1 = ( horzP1 + cycle_fix ) % 160; + if ( HMM0_latch ) + horzM0 = ( horzM0 + cycle_fix ) % 160; + if ( HMM1_latch ) + horzM1 = ( horzM1 + cycle_fix ) % 160; + if ( HMBL_latch ) + horzBL = ( horzBL + cycle_fix ) % 160; + } + + HMP0_latch = 0; + HMP1_latch = 0; + HMM0_latch = 0; + HMM1_latch = 0; + HMBL_latch = 0; + + /* Check if HMOVE activities can be ignored */ + if ( curr_x >= -5 && curr_x < 97 ) { + motclkP0 = 0; + motclkP1 = 0; + motclkM0 = 0; + motclkM1 = 0; + motclkBL = 0; + HMOVE_started = HMOVE_INACTIVE; + return; + } + + motclkP0 = ( HMP0 ^ 0x80 ) >> 4; + motclkP1 = ( HMP1 ^ 0x80 ) >> 4; + motclkM0 = ( HMM0 ^ 0x80 ) >> 4; + motclkM1 = ( HMM1 ^ 0x80 ) >> 4; + motclkBL = ( HMBL ^ 0x80 ) >> 4; + + /* Adjust number of graphics motion clocks for active display */ + if ( curr_x >= 97 && curr_x < 151 ) { + int skip_motclks = ( 160 - HMOVE_started - 6 ) / 4; + motclkP0 -= skip_motclks; + motclkP1 -= skip_motclks; + motclkM0 -= skip_motclks; + motclkM1 -= skip_motclks; + motclkBL -= skip_motclks; + if ( motclkP0 < 0 ) + motclkP0 = 0; + if ( motclkP1 < 0 ) + motclkP1 = 0; + if ( motclkM0 < 0 ) + motclkM0 = 0; + if ( motclkM1 < 0 ) + motclkM1 = 0; + if ( motclkBL < 0 ) + motclkBL = 0; + } + + if ( curr_x >= -56 && curr_x < -5 ) { + int max_motclks = ( 7 - ( HMOVE_started + 5 ) ) / 4; + if ( motclkP0 > max_motclks ) + motclkP0 = max_motclks; + if ( motclkP1 > max_motclks ) + motclkP1 = max_motclks; + if ( motclkM0 > max_motclks ) + motclkM0 = max_motclks; + if ( motclkM1 > max_motclks ) + motclkM1 = max_motclks; + if ( motclkBL > max_motclks ) + motclkBL = max_motclks; + } + + /* Apply horizontal motion */ + if ( curr_x < -5 || curr_x >= 157 ) { + horzP0 += 8 - motclkP0; + horzP1 += 8 - motclkP1; + horzM0 += 8 - motclkM0; + horzM1 += 8 - motclkM1; + horzBL += 8 - motclkBL; + + if (horzP0 < 0) + horzP0 += 160; + if (horzP1 < 0) + horzP1 += 160; + if (horzM0 < 0) + horzM0 += 160; + if (horzM1 < 0) + horzM1 += 160; + if (horzBL < 0) + horzBL += 160; + + horzP0 %= 160; + horzP1 %= 160; + horzM0 %= 160; + horzM1 %= 160; + horzBL %= 160; + + /* When HMOVE is triggered on CPU cycle 75, the HBlank period on the + next line is also extended. */ + if (curr_x >= 157) + { + curr_y += 1; + update_bitmap( -8, curr_y ); + } + else + { + setup_pXgfx(); + } + if (curr_y < screen_height) + { + memset(&helper[current_bitmap].pix(curr_y, 34), 0, 16); + } + + prev_x = 8; + } +} + + +void a2frobtia_video_device::RSYNC_w() +{ + /* this address is used in chip testing */ +} + + +void a2frobtia_video_device::NUSIZ0_w(uint8_t data) +{ + int curr_x = current_x(); + + /* Check if relevant bits have changed */ + if ( ( data & 7 ) != ( NUSIZ0 & 7 ) ) { + int i; + /* Check if we are (about to start) drawing a copy of the player 0 graphics */ + for ( i = 0; i < PLAYER_GFX_SLOTS; i++ ) { + if ( p0gfx.start_pixel[i] < 8 ) { + int min_x = p0gfx.start_drawing[i]; + int size = ( 8 - p0gfx.start_pixel[i] ) * p0gfx.size[i]; + if ( curr_x >= ( min_x - 5 ) % 160 && curr_x < ( min_x + size ) % 160 ) { + if ( curr_x >= min_x % 160 || p0gfx.start_pixel[i] != 0 ) { + /* This copy has started drawing */ + if ( p0gfx.size[i] == 1 && nusiz[data & 7][1] > 1 ) { + int delay = 1 + ( ( p0gfx.start_pixel[i] + ( curr_x - p0gfx.start_drawing[i] ) ) & 1 ); + update_bitmap( curr_x + delay, current_y() ); + p0gfx.start_pixel[i] += ( curr_x + delay - p0gfx.start_drawing[i] ); + if ( p0gfx.start_pixel[i] > 8 ) + p0gfx.start_pixel[i] = 8; + p0gfx.start_drawing[i] = curr_x + delay; + } else if ( p0gfx.size[1] > 1 && nusiz[data & 7][1] == 1 ) { + int delay = ( curr_x - p0gfx.start_drawing[i] ) & ( p0gfx.size[i] - 1 ); + if ( delay ) { + delay = p0gfx.size[i] - delay; + } + update_bitmap( curr_x + delay, current_y() ); + p0gfx.start_pixel[i] += ( curr_x - p0gfx.start_drawing[i] ) / p0gfx.size[i]; + p0gfx.start_drawing[i] = curr_x + delay; + } else { + p0gfx.start_pixel[i] += ( curr_x - p0gfx.start_drawing[i] ) / p0gfx.size[i]; + p0gfx.start_drawing[i] = curr_x; + } + p0gfx.size[i] = nusiz[data & 7][1]; + } else { + /* This copy was just about to start drawing (meltdown) */ + /* Adjust for 1 clock delay between zoomed and non-zoomed sprites */ + if ( p0gfx.size[i] == 1 && nusiz[data & 7][1] > 1 ) { + /* Check for hardware oddity */ + if ( p0gfx.start_drawing[i] - curr_x == 2 ) { + p0gfx.start_drawing[i]--; + } else { + p0gfx.start_drawing[i]++; + } + } else if ( p0gfx.size[i] > 1 && nusiz[data & 7][1] == 1 ) { + p0gfx.start_drawing[i]--; + } + p0gfx.size[i] = nusiz[data & 7][1]; + } + } else { + /* We are passed the copy or the copy still needs to be done. Mark + it as done/invalid, the data will be reset in the next loop. */ + p0gfx.start_pixel[i] = 8; + } + } + } + /* Apply NUSIZ updates to not yet drawn copies */ + for ( i = ( startP0 ? 0 : 1 ); i < nusiz[data & 7][0]; i++ ) { + int j; + /* Find an unused p0gfx entry */ + for ( j = 0; j < PLAYER_GFX_SLOTS; j++ ) { + if ( p0gfx.start_pixel[j] == 8 ) + break; + } + p0gfx.size[j] = nusiz[data & 7][1]; + p0gfx.start_drawing[j] = ( horzP0 + (p0gfx.size[j] > 1 ? 1 : 0) + + i * 8 * ( nusiz[data & 7][2] + p0gfx.size[j] ) ) % 160; + if ( curr_x < p0gfx.start_drawing[j] % 160 ) { + p0gfx.start_pixel[j] = 0; + } + } + NUSIZx_changed = 1; + } + NUSIZ0 = data; +} + + +void a2frobtia_video_device::NUSIZ1_w(uint8_t data) +{ + int curr_x = current_x(); + + /* Check if relevant bits have changed */ + if ( ( data & 7 ) != ( NUSIZ1 & 7 ) ) { + int i; + /* Check if we are (about to start) drawing a copy of the player 1 graphics */ + for ( i = 0; i < PLAYER_GFX_SLOTS; i++ ) { + if ( p1gfx.start_pixel[i] < 8 ) { + int min_x = p1gfx.start_drawing[i]; + int size = ( 8 - p1gfx.start_pixel[i] ) * p1gfx.size[i]; + if ( curr_x >= ( min_x - 5 ) % 160 && curr_x < ( min_x + size ) % 160 ) { + if ( curr_x >= min_x % 160 || p1gfx.start_pixel[i] != 0 ) { + /* This copy has started drawing */ + if ( p1gfx.size[i] == 1 && nusiz[data & 7][1] > 1 ) { + int delay = 1 + ( ( p0gfx.start_pixel[i] + ( curr_x - p0gfx.start_drawing[i] ) ) & 1 ); + update_bitmap( curr_x + delay, current_y() ); + p1gfx.start_pixel[i] += ( curr_x + delay - p1gfx.start_drawing[i] ); + if ( p1gfx.start_pixel[i] > 8 ) + p1gfx.start_pixel[i] = 8; + p1gfx.start_drawing[i] = curr_x + delay; + } else if ( p1gfx.size[1] > 1 && nusiz[data & 7][1] == 1 ) { + int delay = ( curr_x - p1gfx.start_drawing[i] ) & ( p1gfx.size[i] - 1 ); + if ( delay ) { + delay = p1gfx.size[i] - delay; + } + update_bitmap( curr_x + delay, current_y() ); + p1gfx.start_pixel[i] += ( curr_x - p1gfx.start_drawing[i] ) / p1gfx.size[i]; + p1gfx.start_drawing[i] = curr_x + delay; + } else { + p1gfx.start_pixel[i] += ( curr_x - p1gfx.start_drawing[i] ) / p1gfx.size[i]; + p1gfx.start_drawing[i] = curr_x; + } + p1gfx.size[i] = nusiz[data & 7][1]; + } else { + /* This copy was just about to start drawing (meltdown) */ + /* Adjust for 1 clock delay between zoomed and non-zoomed sprites */ + if ( p1gfx.size[i] == 1 && nusiz[data & 7][1] > 1 ) { + /* Check for hardware oddity */ + if ( p1gfx.start_drawing[i] - curr_x == 2 ) { + p1gfx.start_drawing[i]--; + } else { + p1gfx.start_drawing[i]++; + } + } else if ( p1gfx.size[i] > 1 && nusiz[data & 7][1] == 1 ) { + p1gfx.start_drawing[i]--; + } + p1gfx.size[i] = nusiz[data & 7][1]; + } + } else { + /* We are passed the copy or the copy still needs to be done. Mark + it as done/invalid, the data will be reset in the next loop. */ + p1gfx.start_pixel[i] = 8; + } + } + } + /* Apply NUSIZ updates to not yet drawn copies */ + for ( i = ( startP1 ? 0 : 1 ); i < nusiz[data & 7][0]; i++ ) { + int j; + /* Find an unused p1gfx entry */ + for ( j = 0; j < PLAYER_GFX_SLOTS; j++ ) { + if ( p1gfx.start_pixel[j] == 8 ) + break; + } + p1gfx.size[j] = nusiz[data & 7][1]; + p1gfx.start_drawing[j] = ( horzP1 + (p1gfx.size[j] > 1 ? 1 : 0) + + i * 8 * ( nusiz[data & 7][2] + p1gfx.size[j] ) ) % 160; + if ( curr_x < p1gfx.start_drawing[j] % 160 ) { + p1gfx.start_pixel[j] = 0; + } + } + NUSIZx_changed = 1; + } + NUSIZ1 = data; +} + + +void a2frobtia_video_device::HMCLR_w(uint8_t data) +{ + HMP0_w( 0 ); + HMP1_w( 0 ); + HMM0_w( 0 ); + HMM1_w( 0 ); + HMBL_w( 0 ); +} + + +void a2frobtia_video_device::CXCLR_w() +{ + CXM0P = 0; + CXM1P = 0; + CXP0FB = 0; + CXP1FB = 0; + CXM0FB = 0; + CXM1FB = 0; + CXBLPF = 0; + CXPPMM = 0; +} + + +#define RESXX_APPLY_ACTIVE_HMOVE(HORZ,MOTION,MOTCLK) \ + if ( curr_x < std::min( HMOVE_started + 6 + 16 * 4, 7 ) ) { \ + int decrements_passed = ( curr_x - ( HMOVE_started + 4 ) ) / 4; \ + HORZ += 8; \ + if ( ( MOTCLK - decrements_passed ) > 0 ) { \ + HORZ -= ( MOTCLK - decrements_passed ); \ + if ( HORZ < 0 ) \ + HORZ += 160; \ + } \ + } + +#define RESXX_APPLY_PREVIOUS_HMOVE(HORZ,MOTION) \ + if ( HMOVE_started_previous != HMOVE_INACTIVE ) \ + { \ + uint8_t motclk = ( MOTION ^ 0x80 ) >> 4; \ + if ( curr_x <= HMOVE_started_previous - 228 + 5 + motclk * 4 ) \ + { \ + uint8_t motclk_passed = ( curr_x - ( HMOVE_started_previous - 228 + 6 ) ) / 4; \ + HORZ -= ( motclk - motclk_passed ); \ + } \ + } + +void a2frobtia_video_device::RESP0_w() +{ + int curr_x = current_x(); + int new_horzP0; + + /* Check if HMOVE is activated during this line */ + if ( HMOVE_started != HMOVE_INACTIVE ) { + new_horzP0 = ( curr_x < 7 ) ? 3 : ( curr_x + 5 ); + /* If HMOVE is active, adjust for remaining horizontal move clocks if any */ + RESXX_APPLY_ACTIVE_HMOVE( new_horzP0, HMP0, motclkP0 ); + } else { + new_horzP0 = ( curr_x < -2 ) ? 3 : ( curr_x + 5 ); + RESXX_APPLY_PREVIOUS_HMOVE( new_horzP0, HMP0 ); + } + + if ( new_horzP0 != horzP0 ) { + int i; + horzP0 = new_horzP0; + startP0 = 0; + skipclipP0 = 2; + /* Check if we are (about to start) drawing a copy of the player 0 graphics */ + for ( i = 0; i < PLAYER_GFX_SLOTS; i++ ) { + if ( p0gfx.start_pixel[i] < 8 ) { + int min_x = p0gfx.start_drawing[i]; + int size = ( 8 - p0gfx.start_pixel[i] ) * p0gfx.size[i]; + if ( curr_x >= ( min_x - 5 ) % 160 && curr_x < ( min_x + size ) % 160 ) { + if ( curr_x >= min_x ) { + /* This copy has started drawing */ + p0gfx.start_pixel[i] += ( curr_x - p0gfx.start_drawing[i] ) / p0gfx.size[i]; + p0gfx.start_drawing[i] = curr_x; + } else { + /* This copy is waiting to start drawing */ + p0gfx.start_drawing[i] = horzP0; + } + } else { + /* We are passed the copy or the copy still needs to be done. Mark + it as done/invalid, the data will be reset in the next loop. */ + p0gfx.start_pixel[i] = 8; + } + } + } + /* Apply NUSIZ and position updates to not yet drawn copies */ + for ( i = 1; i < nusiz[NUSIZ0 & 7][0]; i++ ) { + int j; + /* Find an unused p0gfx entry */ + for ( j = 0; j < PLAYER_GFX_SLOTS; j++ ) { + if ( p0gfx.start_pixel[j] == 8 ) + break; + } + p0gfx.size[j] = nusiz[NUSIZ0 & 7][1]; + p0gfx.start_drawing[j] = ( horzP0 + (p0gfx.size[j] > 1 ? 1 : 0) + + i * 8 * ( nusiz[NUSIZ0 & 7][2] + p0gfx.size[j] ) ) % 160; + if ( curr_x < p0gfx.start_drawing[j] % 160 ) { + p0gfx.start_pixel[j] = 0; + } + } + } +} + + +void a2frobtia_video_device::RESP1_w() +{ + int curr_x = current_x(); + int new_horzP1; + + /* Check if HMOVE is activated during this line */ + if ( HMOVE_started != HMOVE_INACTIVE ) { + new_horzP1 = ( curr_x < 7 ) ? 3 : ( curr_x + 5 ); + /* If HMOVE is active, adjust for remaining horizontal move clocks if any */ + RESXX_APPLY_ACTIVE_HMOVE( new_horzP1, HMP1, motclkP1 ); + } else { + new_horzP1 = ( curr_x < -2 ) ? 3 : ( curr_x + 5 ); + RESXX_APPLY_PREVIOUS_HMOVE( new_horzP1, HMP1 ); + } + + if ( new_horzP1 != horzP1 ) { + int i; + horzP1 = new_horzP1; + startP1 = 0; + skipclipP1 = 2; + /* Check if we are (about to start) drawing a copy of the player 1 graphics */ + for ( i = 0; i < PLAYER_GFX_SLOTS; i++ ) { + if ( p1gfx.start_pixel[i] < 8 ) { + int min_x = p1gfx.start_drawing[i]; + int size = ( 8 - p1gfx.start_pixel[i] ) * p1gfx.size[i]; + if ( curr_x >= ( min_x - 5 ) % 160 && curr_x < ( min_x + size ) % 160 ) { + if ( curr_x >= min_x ) { + /* This copy has started drawing */ + p1gfx.start_pixel[i] += ( curr_x - p1gfx.start_drawing[i] ) / p1gfx.size[i]; + p1gfx.start_drawing[i] = curr_x; + } else { + /* This copy is waiting to start drawing */ + p1gfx.start_drawing[i] = horzP1; + } + } else { + /* We are passed the copy or the copy still needs to be done. Mark + it as done/invalid, the data will be reset in the next loop. */ + p1gfx.start_pixel[i] = 8; + } + } + } + /* Apply NUSIZ and position updates to not yet drawn copies */ + for ( i = 1; i < nusiz[NUSIZ1 & 7][0]; i++ ) { + int j; + /* Find an unused p1gfx entry */ + for ( j = 0; j < PLAYER_GFX_SLOTS; j++ ) { + if ( p1gfx.start_pixel[j] == 8 ) + break; + } + p1gfx.size[j] = nusiz[NUSIZ1 & 7][1]; + p1gfx.start_drawing[j] = ( horzP1 + (p1gfx.size[j] > 1 ? 1 : 0) + + i * 8 * ( nusiz[NUSIZ1 & 7][2] + p1gfx.size[j] ) ) % 160; + if ( curr_x < p1gfx.start_drawing[j] % 160 ) { + p1gfx.start_pixel[j] = 0; + } + } + } +} + + +void a2frobtia_video_device::RESM0_w() +{ + int curr_x = current_x(); + int new_horzM0; + + /* Check if HMOVE is activated during this line */ + if ( HMOVE_started != HMOVE_INACTIVE ) { + new_horzM0 = ( curr_x < 7 ) ? 2 : ( ( curr_x + 4 ) % 160 ); + /* If HMOVE is active, adjust for remaining horizontal move clocks if any */ + RESXX_APPLY_ACTIVE_HMOVE( new_horzM0, HMM0, motclkM0 ); + } else { + new_horzM0 = ( curr_x < -1 ) ? 2 : ( ( curr_x + 4 ) % 160 ); + skipM0delay = ( curr_x < -1 && horzM0 % 160 >= 0 && horzM0 % 160 < 1 ) ? 4 : 0; + RESXX_APPLY_PREVIOUS_HMOVE( new_horzM0, HMM0 ); + } + if ( new_horzM0 != horzM0 ) { + startM0 = skipM0delay ? 1 : 0; + horzM0 = new_horzM0; + } +} + + +void a2frobtia_video_device::RESM1_w() +{ + int curr_x = current_x(); + int new_horzM1; + + /* Check if HMOVE is activated during this line */ + if ( HMOVE_started != HMOVE_INACTIVE ) { + new_horzM1 = ( curr_x < 7 ) ? 2 : ( ( curr_x + 4 ) % 160 ); + /* If HMOVE is active, adjust for remaining horizontal move clocks if any */ + RESXX_APPLY_ACTIVE_HMOVE( new_horzM1, HMM1, motclkM1 ); + } else { + new_horzM1 = ( curr_x < -1 ) ? 2 : ( ( curr_x + 4 ) % 160 ); + skipM1delay = ( curr_x < -1 && horzM1 % 160 >= 0 && horzM1 % 160 < 1 ) ? 4 : 0; + RESXX_APPLY_PREVIOUS_HMOVE( new_horzM1, HMM1 ); + } + if ( new_horzM1 != horzM1 ){ + startM1 = skipM1delay ? 1 : 0; + horzM1 = new_horzM1; + } +} + + +void a2frobtia_video_device::RESBL_w() +{ + int curr_x = current_x(); + + /* Check if HMOVE is activated during this line */ + if ( HMOVE_started != HMOVE_INACTIVE ) { + horzBL = ( curr_x < 7 ) ? 2 : ( ( curr_x + 4 ) % 160 ); + /* If HMOVE is active, adjust for remaining horizontal move clocks if any */ + RESXX_APPLY_ACTIVE_HMOVE( horzBL, HMBL, motclkBL ); + } else { + horzBL = ( curr_x < 0 ) ? 2 : ( ( curr_x + 4 ) % 160 ); + RESXX_APPLY_PREVIOUS_HMOVE( horzBL, HMBL ); + } +} + + +void a2frobtia_video_device::RESMP0_w(uint8_t data) +{ + if (RESMP0 & 2) + { + if ( nusiz[NUSIZ0 & 7][1] > 1 ) { + horzM0 = horzP0 + 3 * nusiz[NUSIZ0 & 7][1] - 1; + } else { + horzM0 = horzP0 + 4 * nusiz[NUSIZ0 & 7][1]; + } + if ( HMOVE_started != HMOVE_INACTIVE ) { + horzM0 -= ( 8 - motclkP0 ); + horzM0 += 8 - motclkM0; + if ( horzM0 < 0 ) + horzM0 += 160; + } + horzM0 %= 160; + } + + RESMP0 = data; +} + + +void a2frobtia_video_device::RESMP1_w(uint8_t data) +{ + if (RESMP1 & 2) + { + if ( nusiz[NUSIZ1 & 7][1] > 1 ) { + horzM1 = horzP1 + 3 * nusiz[NUSIZ1 & 7][1] - 1; + } else { + horzM1 = horzP1 + 4 * nusiz[NUSIZ1 & 7][1]; + } + if ( HMOVE_started != HMOVE_INACTIVE ) { + horzM1 -= ( 8 - motclkP1 ); + horzM1 += 8 - motclkM1; + if ( horzM1 < 0 ) + horzM1 += 160; + } + horzM1 %= 160; + } + + RESMP1 = data; +} + + +void a2frobtia_video_device::GRP0_w(uint8_t data) +{ + prevGRP1 = GRP1; + + GRP0 = data; +} + + +void a2frobtia_video_device::GRP1_w(uint8_t data) +{ + prevGRP0 = GRP0; + + GRP1 = data; + + prevENABL = ENABL; +} + + +uint8_t a2frobtia_video_device::INPT_r(offs_t offset) +{ + const uint64_t elapsed = m_maincpu->total_cycles() - paddle_start; + const uint16_t input = m_read_input_port_cb(offset & 3, 0xffff); + + if (input == TIA_INPUT_PORT_ALWAYS_ON) + return 0x80; + if (input == TIA_INPUT_PORT_ALWAYS_OFF) + return 0x00; + + const uint16_t paddle_cycles = input * 76; + return elapsed > paddle_cycles ? 0x80 : 0x00; +} + + +uint8_t a2frobtia_video_device::read(offs_t offset) +{ + /* lower bits 0 - 5 seem to depend on the last byte on the + data bus. If the driver supplied a routine to retrieve + that we will call that, otherwise we will use the lower + bit of the offset. + */ + uint8_t data = offset & 0x3f; + + if (!m_databus_contents_cb.isunset()) + { + data = m_databus_contents_cb(offset) & 0x3f; + } + + if (!(offset & 0x8)) + { + update_bitmap(current_x(), current_y()); + } + + switch (offset & 0xF) + { + case 0x0: + return data | CXM0P; + case 0x1: + return data | CXM1P; + case 0x2: + return data | CXP0FB; + case 0x3: + return data | CXP1FB; + case 0x4: + return data | CXM0FB; + case 0x5: + return data | CXM1FB; + case 0x6: + return data | CXBLPF; + case 0x7: + return data | CXPPMM; + case 0x8: + return data | INPT_r(0); + case 0x9: + return data | INPT_r(1); + case 0xA: + return data | INPT_r(2); + case 0xB: + return data | INPT_r(3); + case 0xC: + { + int button = !m_read_input_port_cb.isunset() ? ( m_read_input_port_cb(4,0xFFFF) & 0x80 ) : 0x80; + INPT4 = ( VBLANK & 0x40) ? ( INPT4 & button ) : button; + } + return data | INPT4; + case 0xD: + { + int button = !m_read_input_port_cb.isunset() ? ( m_read_input_port_cb(5,0xFFFF) & 0x80 ) : 0x80; + INPT5 = ( VBLANK & 0x40) ? ( INPT5 & button ) : button; + } + return data | INPT5; + } + + return data; +} + + +void a2frobtia_video_device::write(offs_t offset, uint8_t data) +{ + static const int delay[0x40] = + { + 0, // VSYNC + 0, // VBLANK + 0, // WSYNC + 0, // RSYNC + 0, // NUSIZ0 + 0, // NUSIZ1 + 0, // COLUP0 + 0, // COLUP1 + 0, // COLUPF + 0, // COLUBK + 0, // CTRLPF + 1, // REFP0 + 1, // REFP1 + 4, // PF0 + 4, // PF1 + 4, // PF2 + 0, // RESP0 + 0, // RESP1 + 0, // RESM0 + 0, // RESM1 + 0, // RESBL + -1, // AUDC0 + -1, // AUDC1 + -1, // AUDF0 + -1, // AUDF1 + -1, // AUDV0 + -1, // AUDV1 + 1, // GRP0 + 1, // GRP1 + 1, // ENAM0 + 1, // ENAM1 + 1, // ENABL + 0, // HMP0 + 0, // HMP1 + 0, // HMM0 + 0, // HMM1 + 0, // HMBL + 0, // VDELP0 + 0, // VDELP1 + 0, // VDELBL + 0, // RESMP0 + 0, // RESMP1 + 3, // HMOVE + 0, // HMCLR + 0, // CXCLR + }; + int curr_x = current_x(); + int curr_y = current_y(); + + offset &= 0x3F; + + if (offset >= 0x0D && offset <= 0x0F) + { + curr_x = ( curr_x + 1 ) & ~3; + } + + if (delay[offset] >= 0) + { + update_bitmap(curr_x + delay[offset], curr_y); + } + + switch (offset) + { + case 0x00: + VSYNC_w(data); + break; + case 0x01: + VBLANK_w(data); + break; + case 0x02: + WSYNC_w(); + break; + case 0x03: + RSYNC_w(); + break; + case 0x04: + NUSIZ0_w(data); + break; + case 0x05: + NUSIZ1_w(data); + break; + case 0x06: + COLUP0 = data; + break; + case 0x07: + COLUP1 = data; + break; + case 0x08: + COLUPF = data; + break; + case 0x09: + COLUBK = data; + break; + case 0x0A: + CTRLPF_w(data); + break; + case 0x0B: + REFP0 = data; + break; + case 0x0C: + REFP1 = data; + break; + case 0x0D: + PF0 = data; + break; + case 0x0E: + PF1 = data; + break; + case 0x0F: + PF2 = data; + break; + case 0x10: + RESP0_w(); + break; + case 0x11: + RESP1_w(); + break; + case 0x12: + RESM0_w(); + break; + case 0x13: + RESM1_w(); + break; + case 0x14: + RESBL_w(); + break; + + case 0x15: /* AUDC0 */ + case 0x16: /* AUDC1 */ + case 0x17: /* AUDF0 */ + case 0x18: /* AUDF1 */ + case 0x19: /* AUDV0 */ + case 0x1A: /* AUDV1 */ + m_tia->tia_sound_w(offset, data); + break; + + case 0x1B: + GRP0_w(data); + break; + case 0x1C: + GRP1_w(data); + break; + case 0x1D: + ENAM0 = data; + break; + case 0x1E: + ENAM1 = data; + break; + case 0x1F: + ENABL = data; + break; + case 0x20: + HMP0_w(data); + break; + case 0x21: + HMP1_w(data); + break; + case 0x22: + HMM0_w(data); + break; + case 0x23: + HMM1_w(data); + break; + case 0x24: + HMBL_w(data); + break; + case 0x25: + VDELP0 = data; + break; + case 0x26: + VDELP1 = data; + break; + case 0x27: + VDELBL = data; + break; + case 0x28: + RESMP0_w(data); + break; + case 0x29: + RESMP1_w(data); + break; + case 0x2A: + HMOVE_w(data); + break; + case 0x2B: + HMCLR_w(data); + break; + case 0x2C: + CXCLR_w(); + break; + } +} + + +//------------------------------------------------- +// device_reset - device-specific reset +//------------------------------------------------- + +void a2frobtia_video_device::device_reset() +{ + int i; + + frame_cycles = 0; + + INPT4 = 0x80; + INPT5 = 0x80; + + HMP0 = 0; + HMP1 = 0; + HMM0 = 0; + HMM1 = 0; + HMBL = 0; + + startP0 = 1; + startP1 = 1; + + HMP0_latch = 0; + HMP1_latch = 0; + HMM0_latch = 0; + HMM1_latch = 0; + HMBL_latch = 0; + + startM0 = 1; + startM1 = 1; + skipM0delay = 0; + skipM1delay = 0; + + REFLECT = 0; + + prev_x = 0; + prev_y = 0; + + HMOVE_started = HMOVE_INACTIVE; + + motclkP0 = 0; + motclkP1 = 0; + motclkM0 = 0; + motclkM1 = 0; + motclkBL = 0; + + horzP0 = 0; + horzP1 = 0; + + for( i = 0; i < PLAYER_GFX_SLOTS; i++ ) + { + p0gfx.start_pixel[i] = 8; + p0gfx.size[i] = 1; + p1gfx.start_pixel[i] = 8; + p1gfx.size[i] = 1; + } + + current_bitmap = 0; + + NUSIZx_changed = 0; + + VBLANK = 0; + CTRLPF = 0; + PF0 = 0; + PF1 = 0; + PF2 = 0; + VDELBL = 0; + prevENABL = 0; + ENABL = 0; + VDELP0 = 0; + VDELP1 = 0; + prevGRP0 = 0; + GRP0 = 0; + prevGRP1 = 0; + GRP1 = 0; + COLUP0 = 0; + COLUP1 = 0; + REFP0 = 0; + REFP1 = 0; + NUSIZ0 = 0; + NUSIZ1 = 0; + horzM0 = 0; + horzM1 = 0; + RESMP0 = 0; + RESMP1 = 0; + ENAM0 = 0; + ENAM1 = 0; + skipclipP0 = 0; + skipclipP1 = 0; + horzBL = 0; + VSYNC = 0; + CXM0P = 0; + COLUBK = 0; + COLUPF = 0; +} + + +void a2frobtia_video_device::register_save_state() +{ + save_item(NAME(p0gfx.start_pixel)); + save_item(NAME(p0gfx.start_drawing)); + save_item(NAME(p0gfx.size)); + save_item(NAME(p0gfx.skipclip)); + save_item(NAME(p1gfx.start_pixel)); + save_item(NAME(p1gfx.start_drawing)); + save_item(NAME(p1gfx.size)); + save_item(NAME(p1gfx.skipclip)); + save_item(NAME(frame_cycles)); + save_item(NAME(paddle_start)); + save_item(NAME(horzP0)); + save_item(NAME(horzP1)); + save_item(NAME(horzM0)); + save_item(NAME(horzM1)); + save_item(NAME(horzBL)); + save_item(NAME(motclkP0)); + save_item(NAME(motclkP1)); + save_item(NAME(motclkM0)); + save_item(NAME(motclkM1)); + save_item(NAME(motclkBL)); + save_item(NAME(startP0)); + save_item(NAME(startP1)); + save_item(NAME(startM0)); + save_item(NAME(startM1)); + save_item(NAME(skipclipP0)); + save_item(NAME(skipclipP1)); + save_item(NAME(skipM0delay)); + save_item(NAME(skipM1delay)); + save_item(NAME(current_bitmap)); + save_item(NAME(prev_x)); + save_item(NAME(prev_y)); + save_item(NAME(VSYNC)); + save_item(NAME(VBLANK)); + save_item(NAME(COLUP0)); + save_item(NAME(COLUP1)); + save_item(NAME(COLUBK)); + save_item(NAME(COLUPF)); + save_item(NAME(CTRLPF)); + save_item(NAME(GRP0)); + save_item(NAME(GRP1)); + save_item(NAME(REFP0)); + save_item(NAME(REFP1)); + save_item(NAME(HMP0)); + save_item(NAME(HMP1)); + save_item(NAME(HMM0)); + save_item(NAME(HMM1)); + save_item(NAME(HMBL)); + save_item(NAME(VDELP0)); + save_item(NAME(VDELP1)); + save_item(NAME(VDELBL)); + save_item(NAME(NUSIZ0)); + save_item(NAME(NUSIZ1)); + save_item(NAME(ENAM0)); + save_item(NAME(ENAM1)); + save_item(NAME(ENABL)); + save_item(NAME(CXM0P)); + save_item(NAME(CXM1P)); + save_item(NAME(CXP0FB)); + save_item(NAME(CXP1FB)); + save_item(NAME(CXM0FB)); + save_item(NAME(CXM1FB)); + save_item(NAME(CXBLPF)); + save_item(NAME(CXPPMM)); + save_item(NAME(RESMP0)); + save_item(NAME(RESMP1)); + save_item(NAME(PF0)); + save_item(NAME(PF1)); + save_item(NAME(PF2)); + save_item(NAME(INPT4)); + save_item(NAME(INPT5)); + save_item(NAME(prevGRP0)); + save_item(NAME(prevGRP1)); + save_item(NAME(prevENABL)); + save_item(NAME(HMOVE_started)); + save_item(NAME(HMOVE_started_previous)); + save_item(NAME(HMP0_latch)); + save_item(NAME(HMP1_latch)); + save_item(NAME(HMM0_latch)); + save_item(NAME(HMM1_latch)); + save_item(NAME(HMBL_latch)); + save_item(NAME(REFLECT)); + save_item(NAME(NUSIZx_changed)); + save_item(NAME(helper[0])); + save_item(NAME(helper[1])); + save_item(NAME(buffer)); +} diff --git a/src/devices/bus/a2bus/a2frobtia.h b/src/devices/bus/a2bus/a2frobtia.h new file mode 100644 index 0000000000000..52c07df92c8ed --- /dev/null +++ b/src/devices/bus/a2bus/a2frobtia.h @@ -0,0 +1,251 @@ +// license:BSD-3-Clause +// copyright-holders:Wilbert Pol,Stefan Jokisch +#ifndef MAME_BUS_A2BUS_A2FROBTIA_H +#define MAME_BUS_A2BUS_A2FROBTIA_H + +#pragma once + +#include "sound/tiaintf.h" + +/*************************************************************************** + + Atari Frob TIA video emulation + (copied from of mame/atari/tia.h for use with the frob card) + +***************************************************************************/ + +//************************************************************************** +// MACROS / CONSTANTS +//************************************************************************** + + +#define TIA_PALETTE_LENGTH 128 + 128 * 128 +#define TIA_INPUT_PORT_ALWAYS_ON 0 +#define TIA_INPUT_PORT_ALWAYS_OFF 0xff +#define TIA_MAX_SCREEN_HEIGHT 342 + +#define HMOVE_INACTIVE -200 +#define PLAYER_GFX_SLOTS 4 +// Per player graphic +// - pixel number to start drawing from (0-7, from GRPx) / number of pixels drawn from GRPx +// - display position to start drawing +// - size to use +struct player_gfx { + int start_pixel[PLAYER_GFX_SLOTS]; + int start_drawing[PLAYER_GFX_SLOTS]; + int size[PLAYER_GFX_SLOTS]; + int skipclip[PLAYER_GFX_SLOTS]; +}; + + +//************************************************************************** +// TYPE DEFINITIONS +//************************************************************************** + + +// ======================> a2frobtia_video_device + +class a2frobtia_video_device : public device_t, public device_video_interface, public device_palette_interface +{ +public: + uint32_t screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); + + auto read_input_port_callback() { return m_read_input_port_cb.bind(); } + auto databus_contents_callback() { return m_databus_contents_cb.bind(); } + auto vsync_callback() { return m_vsync_cb.bind(); } + + uint8_t read(offs_t offset); + void write(offs_t offset, uint8_t data); + +protected: + // construction/destruction + a2frobtia_video_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); + + template void set_a2frobtia_tag(T &&tag) { m_tia.set_tag(std::forward(tag)); } + + // device-level overrides + virtual void device_start() override ATTR_COLD; + virtual void device_reset() override ATTR_COLD; + + // device_palette_interface overrides + virtual uint32_t palette_entries() const noexcept override { return TIA_PALETTE_LENGTH; } + + void extend_palette(); + virtual void init_palette() = 0; + void draw_sprite_helper(uint8_t* p, uint8_t *col, struct player_gfx *gfx, uint8_t GRP, uint8_t COLUP, uint8_t REFP); + void draw_missile_helper(uint8_t* p, uint8_t* col, int horz, int skipdelay, int latch, int start, uint8_t RESMP, uint8_t ENAM, uint8_t NUSIZ, uint8_t COLUM); + void draw_playfield_helper(uint8_t* p, uint8_t* col, int horz, uint8_t COLU, uint8_t REFPF); + void draw_ball_helper(uint8_t* p, uint8_t* col, int horz, uint8_t ENAB); + void drawS0(uint8_t* p, uint8_t* col); + void drawS1(uint8_t* p, uint8_t* col); + void drawM0(uint8_t* p, uint8_t* col); + void drawM1(uint8_t* p, uint8_t* col); + void drawBL(uint8_t* p, uint8_t* col); + void drawPF(uint8_t* p, uint8_t *col); + int collision_check(uint8_t* p1, uint8_t* p2, int x1, int x2); + int current_x(); + int current_y(); + void setup_pXgfx(void); + void update_bitmap(int next_x, int next_y); + void WSYNC_w(); + void VSYNC_w(uint8_t data); + void VBLANK_w(uint8_t data); + void CTRLPF_w(uint8_t data); + void HMP0_w(uint8_t data); + void HMP1_w(uint8_t data); + void HMM0_w(uint8_t data); + void HMM1_w(uint8_t data); + void HMBL_w(uint8_t data); + void HMOVE_w(uint8_t data); + void RSYNC_w(); + void NUSIZ0_w(uint8_t data); + void NUSIZ1_w(uint8_t data); + void HMCLR_w(uint8_t data); + void CXCLR_w(); + void RESP0_w(); + void RESP1_w(); + void RESM0_w(); + void RESM1_w(); + void RESBL_w(); + void RESMP0_w(uint8_t data); + void RESMP1_w(uint8_t data); + void GRP0_w(uint8_t data); + void GRP1_w(uint8_t data); + uint8_t INPT_r(offs_t offset); + + +private: + devcb_read16 m_read_input_port_cb; + devcb_read8 m_databus_contents_cb; + devcb_write16 m_vsync_cb; + + required_device m_maincpu; + required_device m_tia; + + struct player_gfx p0gfx; + struct player_gfx p1gfx; + + uint64_t frame_cycles; + uint64_t paddle_start; + + int horzP0; + int horzP1; + int horzM0; + int horzM1; + int horzBL; + int motclkP0; + int motclkP1; + int motclkM0; + int motclkM1; + int motclkBL; + int startP0; + int startP1; + int startM0; + int startM1; + int skipclipP0; + int skipclipP1; + int skipM0delay; + int skipM1delay; + + int current_bitmap; + + int prev_x; + int prev_y; + + uint8_t VSYNC; + uint8_t VBLANK; + uint8_t COLUP0; + uint8_t COLUP1; + uint8_t COLUBK; + uint8_t COLUPF; + uint8_t CTRLPF; + uint8_t GRP0; + uint8_t GRP1; + uint8_t REFP0; + uint8_t REFP1; + uint8_t HMP0; + uint8_t HMP1; + uint8_t HMM0; + uint8_t HMM1; + uint8_t HMBL; + uint8_t VDELP0; + uint8_t VDELP1; + uint8_t VDELBL; + uint8_t NUSIZ0; + uint8_t NUSIZ1; + uint8_t ENAM0; + uint8_t ENAM1; + uint8_t ENABL; + uint8_t CXM0P; + uint8_t CXM1P; + uint8_t CXP0FB; + uint8_t CXP1FB; + uint8_t CXM0FB; + uint8_t CXM1FB; + uint8_t CXBLPF; + uint8_t CXPPMM; + uint8_t RESMP0; + uint8_t RESMP1; + uint8_t PF0; + uint8_t PF1; + uint8_t PF2; + uint8_t INPT4; + uint8_t INPT5; + + uint8_t prevGRP0; + uint8_t prevGRP1; + uint8_t prevENABL; + + int HMOVE_started; + int HMOVE_started_previous; + uint8_t HMP0_latch; + uint8_t HMP1_latch; + uint8_t HMM0_latch; + uint8_t HMM1_latch; + uint8_t HMBL_latch; + uint8_t REFLECT; /* Should playfield be reflected or not */ + uint8_t NUSIZx_changed; + + bitmap_ind16 helper[2]; + bitmap_rgb32 buffer; + + uint16_t screen_height; + + void register_save_state(); +}; + +class a2frobtia_pal_video_device : public a2frobtia_video_device +{ +public: + template a2frobtia_pal_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&a2frobtia_tag) + : a2frobtia_pal_video_device(mconfig, tag, owner, clock) + { + set_a2frobtia_tag(std::forward(a2frobtia_tag)); + } + + a2frobtia_pal_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + +protected: + virtual void init_palette() override; +}; + +class a2frobtia_ntsc_video_device : public a2frobtia_video_device +{ +public: + template a2frobtia_ntsc_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&a2frobtia_tag) + : a2frobtia_ntsc_video_device(mconfig, tag, owner, clock) + { + set_a2frobtia_tag(std::forward(a2frobtia_tag)); + } + + a2frobtia_ntsc_video_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + +protected: + virtual void init_palette() override; +}; + + +DECLARE_DEVICE_TYPE(A2FROBTIA_PAL_VIDEO, a2frobtia_pal_video_device) +DECLARE_DEVICE_TYPE(A2FROBTIA_NTSC_VIDEO, a2frobtia_ntsc_video_device) + +#endif // MAME_BUS_A2BUS_A2FROBTIA_H diff --git a/src/devices/bus/a2bus/cards.cpp b/src/devices/bus/a2bus/cards.cpp index cc2380fd45542..00624defe0d6a 100644 --- a/src/devices/bus/a2bus/cards.cpp +++ b/src/devices/bus/a2bus/cards.cpp @@ -21,6 +21,7 @@ #include "a2diskiing.h" #include "a2dx1.h" #include "a2echoii.h" +#include "a2frob.h" #include "a2hsscsi.h" #include "a2iwm.h" #include "a2mcms.h" @@ -106,6 +107,7 @@ void apple2_cards(device_slot_interface &device) device.option_add("sam", A2BUS_SAM); // SAM Software Automated Mouth (8-bit DAC + speaker) device.option_add("alfam2", A2BUS_ALFAM2); // ALF Apple Music II device.option_add("echoii", A2BUS_ECHOII); // Street Electronics Echo II + device.option_add("frob", A2BUS_FROB); // FrobCo Frob device.option_add("ap16", A2BUS_IBSAP16); // IBS AP16 (German VideoTerm clone) device.option_add("ap16alt", A2BUS_IBSAP16ALT); // IBS AP16 (German VideoTerm clone), alternate revision device.option_add("vtc1", A2BUS_VTC1); // Unknown VideoTerm clone @@ -178,6 +180,7 @@ void apple2e_cards(device_slot_interface &device) device.option_add("sam", A2BUS_SAM); // SAM Software Automated Mouth (8-bit DAC + speaker) device.option_add("alfam2", A2BUS_ALFAM2); // ALF Apple Music II device.option_add("echoii", A2BUS_ECHOII); // Street Electronics Echo II + device.option_add("frob", A2BUS_FROB); // FrobCo Frob device.option_add("ap16", A2BUS_IBSAP16); // IBS AP16 (German VideoTerm clone) device.option_add("ap16alt", A2BUS_IBSAP16ALT); // IBS AP16 (German VideoTerm clone), alternate revision device.option_add("vtc1", A2BUS_VTC1); // Unknown VideoTerm clone @@ -258,6 +261,7 @@ void apple2gs_cards(device_slot_interface &device) device.option_add("sam", A2BUS_SAM); // SAM Software Automated Mouth (8-bit DAC + speaker) device.option_add("alfam2", A2BUS_ALFAM2); // ALF Apple Music II device.option_add("echoii", A2BUS_ECHOII); // Street Electronics Echo II + device.option_add("frob", A2BUS_FROB); // FrobCo Frob device.option_add("ap16", A2BUS_IBSAP16); // IBS AP16 (German VideoTerm clone) device.option_add("ap16alt", A2BUS_IBSAP16ALT); // IBS AP16 (German VideoTerm clone), alternate revision device.option_add("vtc1", A2BUS_VTC1); // Unknown VideoTerm clone