// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/*************************************************************************
ldplayer.c
Laserdisc player driver.
**************************************************************************/
#include "emu.h"
#include "emuopts.h"
#include "chd.h"
#include "cpu/mcs48/mcs48.h"
#include "machine/ldpr8210.h"
#include "machine/ldv1000.h"
#include <ctype.h>
#include "pr8210.lh"
class ldplayer_state : public driver_device
{
public:
// construction/destruction
ldplayer_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag),
m_last_controls(0),
m_playing(false) { }
// callback hook
static chd_file *get_disc_static(device_t *dummy, laserdisc_device &device) { return device.machine().driver_data<ldplayer_state>()->get_disc(); }
protected:
// device overrides
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
virtual void machine_start() override;
virtual void machine_reset() override;
// internal helpers
chd_file *get_disc();
void process_commands();
// derived classes
virtual void execute_command(int command) { assert(false); }
// timer IDs
enum
{
TIMER_ID_AUTOPLAY,
TIMER_ID_VSYNC_UPDATE
};
// commands
enum
{
CMD_SCAN_REVERSE,
CMD_STEP_REVERSE,
CMD_SLOW_REVERSE,
CMD_FAST_REVERSE,
CMD_SCAN_FORWARD,
CMD_STEP_FORWARD,
CMD_SLOW_FORWARD,
CMD_FAST_FORWARD,
CMD_PLAY,
CMD_PAUSE,
CMD_FRAME_TOGGLE,
CMD_CHAPTER_TOGGLE,
CMD_CH1_TOGGLE,
CMD_CH2_TOGGLE,
CMD_0,
CMD_1,
CMD_2,
CMD_3,
CMD_4,
CMD_5,
CMD_6,
CMD_7,
CMD_8,
CMD_9,
CMD_SEARCH
};
// internal state
std::string m_filename;
ioport_value m_last_controls;
bool m_playing;
};
class pr8210_state : public ldplayer_state
{
public:
// construction/destruction
pr8210_state(const machine_config &mconfig, device_type type, const char *tag)
: ldplayer_state(mconfig, type, tag),
m_laserdisc(*this, "laserdisc"),
m_command_buffer_in(0),
m_command_buffer_out(0) { }
protected:
// device overrides
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
virtual void machine_start() override;
virtual void machine_reset() override;
// command execution hook
virtual void execute_command(int command) override;
// internal helpers
inline void add_command(uint8_t command);
// timer IDs
enum
{
TIMER_ID_BIT = 100,
TIMER_ID_BIT_OFF
};
required_device<pioneer_pr8210_device> m_laserdisc;
// internal state
emu_timer *m_bit_timer;
uint32_t m_command_buffer_in;
uint32_t m_command_buffer_out;
uint8_t m_command_buffer[10];
};
class ldv1000_state : public ldplayer_state
{
public:
// construction/destruction
ldv1000_state(const machine_config &mconfig, device_type type, const char *tag)
: ldplayer_state(mconfig, type, tag),
m_laserdisc(*this, "laserdisc") { }
protected:
required_device<pioneer_ldv1000_device> m_laserdisc;
// command execution hook
virtual void execute_command(int command) override;
};
/*************************************
*
* Disc location
*
*************************************/
chd_file *ldplayer_state::get_disc()
{
bool found = false;
// open a path to the ROMs and find the first CHD file
file_enumerator path(machine().options().media_path());
// iterate while we get new objects
const osd::directory::entry *dir;
while ((dir = path.next()) != NULL)
{
int length = strlen(dir->name);
// look for files ending in .chd
if (length > 4 &&
dir->name[length - 4] == '.' &&
tolower(dir->name[length - 3]) == 'c' &&
tolower(dir->name[length - 2]) == 'h' &&
tolower(dir->name[length - 1]) == 'd')
{
// open the file itself via our search path
emu_file image_file(machine().options().media_path(), OPEN_FLAG_READ);
osd_file::error filerr = image_file.open(dir->name);
if (filerr == osd_file::error::NONE)
{
std::string fullpath(image_file.fullpath());
image_file.close();
// try to open the CHD
if (machine().rom_load().set_disk_handle("laserdisc", fullpath.c_str()) == CHDERR_NONE)
{
m_filename.assign(dir->name);
found = true;
break;
}
}
}
}
// if we failed, pop a message and exit
if (found == false)
throw emu_fatalerror("No valid image file found!\n");
return machine().rom_load().get_disk_handle("laserdisc");
}
/*************************************
*
* Timers and sync
*
*************************************/
void ldplayer_state::process_commands()
{
ioport_value controls = ioport("controls")->read();
int number;
// step backwards
if (!(m_last_controls & 0x01) && (controls & 0x01))
execute_command(CMD_STEP_REVERSE);
// step forwards
if (!(m_last_controls & 0x02) && (controls & 0x02))
execute_command(CMD_STEP_FORWARD);
// scan backwards
if (controls & 0x04)
execute_command(CMD_SCAN_REVERSE);
// scan forwards
if (controls & 0x08)
execute_command(CMD_SCAN_FORWARD);
// slow backwards
if (!(m_last_controls & 0x10) && (controls & 0x10))
execute_command(CMD_SLOW_REVERSE);
// slow forwards
if (!(m_last_controls & 0x20) && (controls & 0x20))
execute_command(CMD_SLOW_FORWARD);
// fast backwards
if (controls & 0x40)
execute_command(CMD_FAST_REVERSE);
// fast forwards
if (controls & 0x80)
execute_command(CMD_FAST_FORWARD);
// play/pause
if (!(m_last_controls & 0x100) && (controls & 0x100))
{
m_playing = !m_playing;
execute_command(m_playing ? CMD_PLAY : CMD_PAUSE);
}
// toggle frame display
if (!(m_last_controls & 0x200) && (controls & 0x200))
execute_command(CMD_FRAME_TOGGLE);
// toggle chapter display
if (!(m_last_controls & 0x400) && (controls & 0x400))
execute_command(CMD_CHAPTER_TOGGLE);
// toggle left channel
if (!(m_last_controls & 0x800) && (controls & 0x800))
execute_command(CMD_CH1_TOGGLE);
// toggle right channel
if (!(m_last_controls & 0x1000) && (controls & 0x1000))
execute_command(CMD_CH2_TOGGLE);
// numbers
for (number = 0; number < 10; number++)
if (!(m_last_controls & (0x10000 << number)) && (controls & (0x10000 << number)))
execute_command(CMD_0 + number);
// enter
if (!(m_last_controls & 0x4000000) && (controls & 0x4000000))
execute_command(CMD_SEARCH);
m_last_controls = controls;
}
void ldplayer_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TIMER_ID_VSYNC_UPDATE:
{
// handle commands
if (param == 0)
process_commands();
// set a timer to go off on the next VBLANK
int vblank_scanline = machine().first_screen()->visible_area().max_y + 1;
attotime target = machine().first_screen()->time_until_pos(vblank_scanline);
timer_set(target, TIMER_ID_VSYNC_UPDATE);
break;
}
case TIMER_ID_AUTOPLAY:
// start playing
execute_command(CMD_PLAY);
m_playing = true;
break;
}
}
void ldplayer_state::machine_start()
{
// start the vsync timer going
timer_set(attotime::zero, TIMER_ID_VSYNC_UPDATE, 1);
}
void ldplayer_state::machine_reset()
{
// set up a timer to start playing immediately
timer_set(attotime::zero, TIMER_ID_AUTOPLAY);
// indicate the name of the file we opened
popmessage("Opened %s\n", m_filename.c_str());
}
/*************************************
*
* PR-8210 implementation
*
*************************************/
void pr8210_state::add_command(uint8_t command)
{
m_command_buffer[m_command_buffer_in++ % ARRAY_LENGTH(m_command_buffer)] = (command & 0x1f) | 0x20;
m_command_buffer[m_command_buffer_in++ % ARRAY_LENGTH(m_command_buffer)] = 0x00 | 0x20;
}
void pr8210_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TIMER_ID_BIT:
{
attotime duration = attotime::from_msec(30);
uint8_t bitsleft = param >> 16;
uint8_t data = param;
// if we have bits, process
if (bitsleft != 0)
{
// assert the line and set a timer for deassertion
m_laserdisc->control_w(ASSERT_LINE);
timer_set(attotime::from_usec(250), TIMER_ID_BIT_OFF);
// space 0 bits apart by 1msec, and 1 bits by 2msec
duration = attotime::from_msec((data & 0x80) ? 2 : 1);
data <<= 1;
bitsleft--;
}
// if we're out of bits, queue up the next command
else if (bitsleft == 0 && m_command_buffer_in != m_command_buffer_out)
{
data = m_command_buffer[m_command_buffer_out++ % ARRAY_LENGTH(m_command_buffer)];
bitsleft = 12;
}
m_bit_timer->adjust(duration, (bitsleft << 16) | data);
break;
}
// deassert the control line
case TIMER_ID_BIT_OFF:
m_laserdisc->control_w(CLEAR_LINE);
break;
// others to the parent class
default:
ldplayer_state::device_timer(timer, id, param, ptr);
break;
}
}
void pr8210_state::machine_start()
{
ldplayer_state::machine_start();
m_bit_timer = timer_alloc(TIMER_ID_BIT);
}
void pr8210_state::machine_reset()
{
ldplayer_state::machine_reset();
m_bit_timer->adjust(attotime::zero);
}
void pr8210_state::execute_command(int command)
{
static const uint8_t digits[10] = { 0x01, 0x11, 0x09, 0x19, 0x05, 0x15, 0x0d, 0x1d, 0x03, 0x13 };
switch (command)
{
case CMD_SCAN_REVERSE:
if (m_command_buffer_in == m_command_buffer_out ||
m_command_buffer_in == (m_command_buffer_out + 1) % ARRAY_LENGTH(m_command_buffer))
{
add_command(0x1c);
m_playing = true;
}
break;
case CMD_STEP_REVERSE:
add_command(0x12);
m_playing = false;
break;
case CMD_SLOW_REVERSE:
add_command(0x02);
m_playing = true;
break;
case CMD_FAST_REVERSE:
if (m_command_buffer_in == m_command_buffer_out ||
m_command_buffer_in == (m_command_buffer_out + 1) % ARRAY_LENGTH(m_command_buffer))
{
add_command(0x0c);
m_playing = true;
}
break;
case CMD_SCAN_FORWARD:
if (m_command_buffer_in == m_command_buffer_out ||
m_command_buffer_in == (m_command_buffer_out + 1) % ARRAY_LENGTH(m_command_buffer))
{
add_command(0x08);
m_playing = true;
}
break;
case CMD_STEP_FORWARD:
add_command(0x04);
m_playing = false;
break;
case CMD_SLOW_FORWARD:
add_command(0x18);
m_playing = true;
break;
case CMD_FAST_FORWARD:
if (m_command_buffer_in == m_command_buffer_out ||
m_command_buffer_in == (m_command_buffer_out + 1) % ARRAY_LENGTH(m_command_buffer))
{
add_command(0x10);
m_playing = true;
}
break;
case CMD_PLAY:
add_command(0x14);
m_playing = true;
break;
case CMD_PAUSE:
add_command(0x0a);
m_playing = false;
break;
case CMD_FRAME_TOGGLE:
add_command(0x0b);
break;
case CMD_CHAPTER_TOGGLE:
add_command(0x06);
break;
case CMD_CH1_TOGGLE:
add_command(0x0e);
break;
case CMD_CH2_TOGGLE:
add_command(0x16);
break;
case CMD_0:
case CMD_1:
case CMD_2:
case CMD_3:
case CMD_4:
case CMD_5:
case CMD_6:
case CMD_7:
case CMD_8:
case CMD_9:
add_command(digits[command - CMD_0]);
break;
case CMD_SEARCH:
add_command(0x1a);
m_playing = false;
break;
}
}
/*************************************
*
* LD-V1000 implementation
*
*************************************/
void ldv1000_state::execute_command(int command)
{
static const uint8_t digits[10] = { 0x3f, 0x0f, 0x8f, 0x4f, 0x2f, 0xaf, 0x6f, 0x1f, 0x9f, 0x5f };
switch (command)
{
case CMD_SCAN_REVERSE:
m_laserdisc->data_w(0xf8);
m_playing = true;
break;
case CMD_STEP_REVERSE:
m_laserdisc->data_w(0xfe);
m_playing = false;
break;
case CMD_SCAN_FORWARD:
m_laserdisc->data_w(0xf0);
m_playing = true;
break;
case CMD_STEP_FORWARD:
m_laserdisc->data_w(0xf6);
m_playing = false;
break;
case CMD_PLAY:
m_laserdisc->data_w(0xfd);
m_playing = true;
break;
case CMD_PAUSE:
m_laserdisc->data_w(0xa0);
m_playing = false;
break;
case CMD_FRAME_TOGGLE:
m_laserdisc->data_w(0xf1);
break;
case CMD_0:
case CMD_1:
case CMD_2:
case CMD_3:
case CMD_4:
case CMD_5:
case CMD_6:
case CMD_7:
case CMD_8:
case CMD_9:
m_laserdisc->data_w(digits[command - CMD_0]);
break;
case CMD_SEARCH:
m_laserdisc->data_w(0xf7);
m_playing = false;
break;
}
}
/*************************************
*
* Port definitions
*
*************************************/
static INPUT_PORTS_START( ldplayer )
PORT_START("controls")
PORT_BIT( 0x0000001, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_NAME("Step reverse") PORT_CODE(KEYCODE_LEFT)
PORT_BIT( 0x0000002, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_NAME("Step forward") PORT_CODE(KEYCODE_RIGHT)
PORT_BIT( 0x0000004, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_NAME("Scan reverse") PORT_CODE(KEYCODE_UP)
PORT_BIT( 0x0000008, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_NAME("Scan forward") PORT_CODE(KEYCODE_DOWN)
PORT_BIT( 0x0000010, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_NAME("Slow reverse") PORT_CODE(KEYCODE_OPENBRACE)
PORT_BIT( 0x0000020, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_NAME("Slow forward") PORT_CODE(KEYCODE_CLOSEBRACE)
PORT_BIT( 0x0000040, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_NAME("Fast reverse") PORT_CODE(KEYCODE_COMMA)
PORT_BIT( 0x0000080, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_NAME("Fast forward") PORT_CODE(KEYCODE_STOP)
PORT_BIT( 0x0000100, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_NAME("Play/Pause") PORT_CODE(KEYCODE_SPACE)
PORT_BIT( 0x0000200, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_NAME("Toggle frame display") PORT_CODE(KEYCODE_F)
PORT_BIT( 0x0000400, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_NAME("Toggle chapter display") PORT_CODE(KEYCODE_C)
PORT_BIT( 0x0000800, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_NAME("Toggle left channel") PORT_CODE(KEYCODE_L)
PORT_BIT( 0x0001000, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_NAME("Toggle right channel") PORT_CODE(KEYCODE_R)
PORT_BIT( 0x0010000, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_NAME("0") PORT_PLAYER(2) PORT_CODE(KEYCODE_0_PAD) PORT_CODE(KEYCODE_0)
PORT_BIT( 0x0020000, IP_ACTIVE_HIGH, IPT_BUTTON2 ) PORT_NAME("1") PORT_PLAYER(2) PORT_CODE(KEYCODE_1_PAD) PORT_CODE(KEYCODE_1)
PORT_BIT( 0x0040000, IP_ACTIVE_HIGH, IPT_BUTTON3 ) PORT_NAME("2") PORT_PLAYER(2) PORT_CODE(KEYCODE_2_PAD) PORT_CODE(KEYCODE_2)
PORT_BIT( 0x0080000, IP_ACTIVE_HIGH, IPT_BUTTON4 ) PORT_NAME("3") PORT_PLAYER(2) PORT_CODE(KEYCODE_3_PAD) PORT_CODE(KEYCODE_3)
PORT_BIT( 0x0100000, IP_ACTIVE_HIGH, IPT_BUTTON5 ) PORT_NAME("4") PORT_PLAYER(2) PORT_CODE(KEYCODE_4_PAD) PORT_CODE(KEYCODE_4)
PORT_BIT( 0x0200000, IP_ACTIVE_HIGH, IPT_BUTTON6 ) PORT_NAME("5") PORT_PLAYER(2) PORT_CODE(KEYCODE_5_PAD) PORT_CODE(KEYCODE_5)
PORT_BIT( 0x0400000, IP_ACTIVE_HIGH, IPT_BUTTON7 ) PORT_NAME("6") PORT_PLAYER(2) PORT_CODE(KEYCODE_6_PAD) PORT_CODE(KEYCODE_6)
PORT_BIT( 0x0800000, IP_ACTIVE_HIGH, IPT_BUTTON8 ) PORT_NAME("7") PORT_PLAYER(2) PORT_CODE(KEYCODE_7_PAD) PORT_CODE(KEYCODE_7)
PORT_BIT( 0x1000000, IP_ACTIVE_HIGH, IPT_BUTTON9 ) PORT_NAME("8") PORT_PLAYER(2) PORT_CODE(KEYCODE_8_PAD) PORT_CODE(KEYCODE_8)
PORT_BIT( 0x2000000, IP_ACTIVE_HIGH, IPT_BUTTON10 ) PORT_NAME("9") PORT_PLAYER(2) PORT_CODE(KEYCODE_9_PAD) PORT_CODE(KEYCODE_9)
PORT_BIT( 0x4000000, IP_ACTIVE_HIGH, IPT_BUTTON11 ) PORT_NAME("Enter") PORT_PLAYER(2) PORT_CODE(KEYCODE_ENTER_PAD) PORT_CODE(KEYCODE_ENTER)
INPUT_PORTS_END
/*************************************
*
* Machine drivers
*
*************************************/
static MACHINE_CONFIG_START( ldplayer_ntsc, ldplayer_state )
MACHINE_CONFIG_END
static MACHINE_CONFIG_DERIVED_CLASS( ldv1000, ldplayer_ntsc, ldv1000_state )
MCFG_LASERDISC_LDV1000_ADD("laserdisc")
MCFG_LASERDISC_GET_DISC(laserdisc_get_disc_delegate(&ldplayer_state::get_disc_static, device))
MCFG_LASERDISC_SCREEN_ADD_NTSC("screen", "laserdisc")
MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker")
MCFG_SOUND_MODIFY("laserdisc")
MCFG_SOUND_ROUTE(0, "lspeaker", 1.0)
MCFG_SOUND_ROUTE(1, "rspeaker", 1.0)
MACHINE_CONFIG_END
static MACHINE_CONFIG_DERIVED_CLASS( pr8210, ldplayer_ntsc, pr8210_state )
MCFG_LASERDISC_PR8210_ADD("laserdisc")
MCFG_LASERDISC_GET_DISC(laserdisc_get_disc_delegate(&ldplayer_state::get_disc_static, device))
MCFG_LASERDISC_SCREEN_ADD_NTSC("screen", "laserdisc")
MCFG_SPEAKER_STANDARD_STEREO("lspeaker", "rspeaker")
MCFG_SOUND_MODIFY("laserdisc")
MCFG_SOUND_ROUTE(0, "lspeaker", 1.0)
MCFG_SOUND_ROUTE(1, "rspeaker", 1.0)
MACHINE_CONFIG_END
/*************************************
*
* ROM definitions
*
*************************************/
ROM_START( ldv1000 )
DISK_REGION( "laserdisc" )
ROM_END
ROM_START( pr8210 )
DISK_REGION( "laserdisc" )
ROM_END
/*************************************
*
* Game drivers
*
*************************************/
GAME( 2008, ldv1000, 0, ldv1000, ldplayer, driver_device, 0, ROT0, "MAME", "Pioneer LDV-1000 Simulator", 0 )
GAMEL(2008, pr8210, 0, pr8210, ldplayer, driver_device, 0, ROT0, "MAME", "Pioneer PR-8210 Simulator", 0, layout_pr8210 )