ヨコハマ忘年会2026

思い思いのゲームで遊ぶイベントです。

ハッシュタグ(2026/12/19)

MiSTer FPGA SVI-328コアをNTTデータキーパッドだけで操作する

MiSTer FPGA SVI-328コアをNTTデータキーパッドだけで操作したい。

github.com

まずはMiSTerメニューをジョイパッド「.」、ボタンをジョイパッド「B」にアサインします。あとは数字キーが必要な事に気づきます。キーボードにジョイパッドの情報も連携して、数字キーをジョイパッド「0」「1」「2」「3」「4」「5」「6」「7」「8」「9」でも入力できるようにします。

Quartus Prime 17.0 Lite Editionをインストールして、SVI-328コアを改造していきます。

Svi328.sv

   "R0,Reset;",
//        "J,Button;",
//        "Jin,A;",
        "J1,B,0,1,2,3,4,5,6,7,8,9;",
    "V,v",`BUILD_DATE
};

(省略)

wire [3:0] svi_row;
wire [7:0] svi_col;
sviKeyboard KeyboardSVI
(
    .clk        (clk_sys),
    .reset  (reset),
    
    .keys       (PS2Keys),
    .joy        (joya),
    .svi_row (svi_row),
    .svi_col (svi_col)
    
);

keyboard_svi.vhd

    -- Ps2 Input From HPS_io
    keys        : in      std_logic_vector(10 downto 0);
    joy         : in      std_logic_vector(31 downto 0);

(省略)

architecture SYN of sviKeyboard is

  signal rst_n            : std_logic;
  signal ps2_chg     : std_logic;
  signal joy_old     : std_logic_vector(31 downto 0);

  type key_matrix is array (0 to 15) of std_logic_vector(7 downto 0);
  signal svi_matrix  : key_matrix;

(省略)

                    ps2_chg <= '0';
                    joy_old <= (others => '0');
        elsif rising_edge (clk) then
         -- note: all inputs are active HIGH
            
            ps2_chg <= keys(10);
            ps2_press := keys(9);
            ps2_scancode := keys(7 downto 0);
        
            -- Joystick buttons
            if joy(5) /= joy_old(5) then
                svi_matrix(0)(0) <= joy(5);
            end if;
            if joy(6) /= joy_old(6) then
                svi_matrix(0)(1) <= joy(6);
            end if;
            if joy(7) /= joy_old(7) then
                svi_matrix(0)(2) <= joy(7);
            end if;
            if joy(8) /= joy_old(8) then
                svi_matrix(0)(3) <= joy(8);
            end if;
            if joy(9) /= joy_old(9) then
                svi_matrix(0)(4) <= joy(9);
            end if;
            if joy(10) /= joy_old(10) then
                svi_matrix(0)(5) <= joy(10);
            end if;
            if joy(11) /= joy_old(11) then
                svi_matrix(0)(6) <= joy(11);
            end if;
            if joy(12) /= joy_old(12) then
                svi_matrix(0)(7) <= joy(12);
            end if;
            if joy(13) /= joy_old(13) then
                svi_matrix(1)(0) <= joy(13);
            end if;
            if joy(14) /= joy_old(14) then
                svi_matrix(1)(1) <= joy(14);
            end if;
            joy_old <= joy;
        
            -- svi key matrix
            if (ps2_chg /= keys(10)) then
                case ps2_scancode is
                    when SCANCODE_7         => svi_matrix(0)(7) <= ps2_press;
                    when SCANCODE_6         => svi_matrix(0)(6) <= ps2_press;
                    when SCANCODE_5            => svi_matrix(0)(5) <= ps2_press;
                    when SCANCODE_4             => svi_matrix(0)(4) <= ps2_press;
                    when SCANCODE_3         => svi_matrix(0)(3) <= ps2_press;
                    when SCANCODE_2         => svi_matrix(0)(2) <= ps2_press;
                    when SCANCODE_1         => svi_matrix(0)(1) <= ps2_press;
                    when SCANCODE_0         => svi_matrix(0)(0) <= ps2_press;

MiSTer FPGAぴゅう太コアをNTTデータキーパッドだけで操作する

MiSTer FPGAぴゅう太コアをNTTデータキーパッドだけで操作したい。

github.com

まずはMiSTerメニューをジョイパッド「.」、数字キーをジョイパッド「1」「2」、RTキーをジョイパッド「START」にアサインします。あとは↓キーが必要な事に気づきます。どうもジョイパッド「↓」を↓キー(joy0[2])にアサインしているのが原因で効かないようです。ジョイパッド「SELECT」を↓カーソルキー(joy0[9])にアサインします。

Quartus Prime 17.0 Lite Editionをインストールして、ぴゅう太コアを改造していきます。

TomyTutor.sv

   "R[0],Reset;",
//    "J,Fire 1,Fire 2,1,2,Enter/RT;",
    "J,Fire 1,Fire 2,1,2,Enter/RT,Down;",
    "V,v",`BUILD_DATE 
};

tutor.sv

//wire [0:7] keys7 = {btn_left | joy0[1], btn_up | joy0[3],  btn_down | joy0[2], btn_right | joy0[0], 1'b0,    1'b0,    1'b0,    1'b0  };
wire [0:7] keys7 = {btn_left | joy0[1], btn_up | joy0[3],  btn_down | joy0[9], btn_right | joy0[0], 1'b0,    1'b0,    1'b0,    1'b0  };

ついでに起動時にF18Aのバージョンが表示されるのが気になるため消してみます。

f18a_version.vhd

   process (clk) begin if rising_edge(clk) then
      if rst_n_i = '0' then
         timer_r <= (others => '0');
--         display_r <= '1';
         display_r <= '0';

コレコビジョンコアでHVC-051キーパッドをつかう

MiSTer FPGAコレコビジョンコアでHVC-051キーパッドを使いたい! github.com

まずはスーパーファミコンをUSB化する基板をプリントします。 github.com

NES拡張端子15ピンの1、12、13、14、15をSFCコントローラ端子に結線します。 www.raphnet.net

まずは以下のファームウェアに書き換えてNESとして動作確認します。 github.com

デフォルトでは4ボタン(SELECT、START、A、B)しか認識しないので、ソースコードを若干修正しました。

NESControllerUSB.ino

/*  DaemonBite NES Controllers to USB Adapter
 *  Author: Mikael Norrgård <mick@daemonbite.com>
 *
 *  Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
 *  
 *  GNU GENERAL PUBLIC LICENSE
 *  Version 3, 29 June 2007
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *  
 */

#include "Gamepad.h"

// ATT: 20 chars max (including NULL at the end) according to Arduino source code.
// Additionally serial number is used to differentiate arduino projects to have different button maps!
const char *gp_serial = "NES to USB";

//#define DEBUG

#define GAMEPAD_COUNT 1      // NOTE: No more than TWO gamepads are possible at the moment due to a USB HID issue.
#define GAMEPAD_COUNT_MAX 4  // NOTE: For some reason, can't have more than two gamepads without serial breaking. Can someone figure out why?
                             //       (It has something to do with how Arduino handles HID devices)
#define BUTTON_COUNT      24 /// 8 // Standard NES controller has four buttons and four axes, totalling 8
#define BUTTON_READ_DELAY 20 // Delay between button reads in µs
#define MICROS_LATCH       8 // 12µs according to specs (8 seems to work fine)
#define MICROS_CLOCK       4 //  6µs according to specs (4 seems to work fine)
#define MICROS_PAUSE       4 //  6µs according to specs (4 seems to work fine)

#define UP    0x01
#define DOWN  0x02
#define LEFT  0x04
#define RIGHT 0x08

// Wire it all up according to the following table:
//
// NES           SNES        Arduino Pro Micro
// --------------------------------------
// VCC                       VCC (All gamepads)
// GND                       GND (All gamepads)
// OUT0 (LATCH)              2   (PD1, All gamepads)
// CUP  (CLOCK)              3   (PD0, All gamepads)
// D1   (GP1: DATA)          A0  (PF7, Gamepad 1) 
// D1   (GP2: DATA)          A1  (PF6, Gamepad 2)
// D1   (GP3: DATA)          A2  (PF5, Gamepad 3, not currently used)
// D1   (GP4: DATA)          A3  (PF4, Gamepad 4, not currently used)

// Set up USB HID gamepads
Gamepad_ Gamepad[GAMEPAD_COUNT];

// Controllers
/// uint8_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0};
/// uint8_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0};
uint32_t buttons[GAMEPAD_COUNT_MAX] = {0,0,0,0};
uint32_t buttonsPrev[GAMEPAD_COUNT_MAX] = {0,0,0,0};
uint8_t gpBit[GAMEPAD_COUNT_MAX] = {B10000000,B01000000,B00100000,B00010000};
/// uint8_t btnBits[BUTTON_COUNT] = {0x20,0x10,0x40,0x80,UP,DOWN,LEFT,RIGHT};
uint32_t btnBits[BUTTON_COUNT] =
{
  0x00000020, // B
  0x00000010, // A
  0x00000040, // Select
  0x00000080, // Start

  0x00000001, // Up
  0x00000002, // Down
  0x00000004, // Left
  0x00000008, // Right

  0x00001000, // 0
  0x00002000, // 1
  0x00004000, // 2
  0x00008000, // 3
  0x00010000, // 4
  0x00020000, // 5
  0x00040000, // 6
  0x00080000, // 7
  0x00100000, // 8
  0x00200000, // 9
  0x00400000, // *
  0x00800000, // #
  0x01000000, // .
  0x02000000, // C
  0x04000000, // unused
  0x08000000  // END
};
uint8_t gp = 0;

// Timing
uint32_t microsButtons = 0;

#ifdef DEBUG
uint32_t microsStart = 0;
uint32_t microsEnd = 0;
uint8_t counter = 0;
#endif

void setup()
{
  // Setup latch and clock pins (2,3 or PD1, PD0)
  DDRD  |=  B00000011; // output
  PORTD &= ~B00000011; // low

  // Setup data pins (A0-A3 or PF7-PF4)
  DDRF  &= ~B11110000; // inputs
  PORTF |=  B11110000; // enable internal pull-ups

  #ifdef DEBUG
  Serial.begin(115200);
  delay(2000);
  #endif

  // Short delay to let controllers stabilize
  delay(50);
}

void loop() { while(1)
{
  // See if enough time has passed since last button read
  if((micros() - microsButtons) > BUTTON_READ_DELAY)
  {    
    #ifdef DEBUG
    microsStart = micros();
    #endif
    
    // Pulse latch
    sendLatch();

    for(uint8_t btn=0; btn<BUTTON_COUNT; btn++)
    {
      for(gp=0; gp<GAMEPAD_COUNT; gp++) 
        (PINF & gpBit[gp]) ? buttons[gp] &= ~btnBits[btn] : buttons[gp] |= btnBits[btn];
      sendClock();
    }

    for(gp=0; gp<GAMEPAD_COUNT; gp++)
    {
      // Has any buttons changed state?
      if (buttons[gp] != buttonsPrev[gp])
      {
        Gamepad[gp]._GamepadReport.buttons = (buttons[gp] >> 4); // First 4 bits are the axes
        Gamepad[gp]._GamepadReport.Y = ((buttons[gp] & DOWN) >> 1) - (buttons[gp] & UP);
        Gamepad[gp]._GamepadReport.X = ((buttons[gp] & RIGHT) >> 3) - ((buttons[gp] & LEFT) >> 2);
///        Gamepad[gp]._GamepadReport.buttons = buttons[gp];
        buttonsPrev[gp] = buttons[gp];
        Gamepad[gp].send();
      }
    }

    microsButtons = micros();

    #ifdef DEBUG
    microsEnd = micros();
    if(counter < 20) {
      Serial.println(microsEnd-microsStart);
      counter++;
    }
    #endif
  }
}}

void sendLatch()
{
  // Send a latch pulse to the NES controller(s)
  PORTD |=  B00000010; // Set HIGH
  delayMicroseconds(MICROS_LATCH);
  PORTD &= ~B00000010; // Set LOW
  delayMicroseconds(MICROS_PAUSE); 
}

void sendClock()
{
  // Send a clock pulse to the NES controller(s)
  PORTD |=  B10000001; // Set HIGH
  delayMicroseconds(MICROS_CLOCK);
  PORTD &= ~B10000001; // Set LOW
  delayMicroseconds(MICROS_PAUSE); 
}

GamePad.h

/*  Gamepad.h
 *   
 *  Based on the advanced HID library for Arduino: 
 *  https://github.com/NicoHood/HID
 *  Copyright (c) 2014-2015 NicoHood
 * 
 *  Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
 *
 *  GNU GENERAL PUBLIC LICENSE
 *  Version 3, 29 June 2007
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *  
 */

#pragma once

#include "HID.h"

extern const char* gp_serial;

typedef struct {
  uint32_t buttons;
  int8_t X;
  int8_t Y;  
} GamepadReport;
///typedef struct {
///  uint32_t buttons;
///} GamepadReport;

class Gamepad_ : public PluggableUSBModule
{  
  private:
    uint8_t reportId;

  protected:
    int getInterface(uint8_t* interfaceCount);
    int getDescriptor(USBSetup& setup);
    uint8_t getShortName(char *name);
    bool setup(USBSetup& setup);
    
    uint8_t epType[1];
    uint8_t protocol;
    uint8_t idle;
    
  public:
    GamepadReport _GamepadReport;
    Gamepad_(void);
    void reset(void);
    void send();
};

GamePad.cpp

/*  Gamepad.cpp
 *   
 *  Based on the advanced HID library for Arduino: 
 *  https://github.com/NicoHood/HID
 *  Copyright (c) 2014-2015 NicoHood
 * 
 *  Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
 *
 *  GNU GENERAL PUBLIC LICENSE
 *  Version 3, 29 June 2007
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *  
 */
#include "Gamepad.h"

static const uint8_t _hidReportDescriptor[] PROGMEM = {
  0x05, 0x01,                       // USAGE_PAGE (Generic Desktop)
  0x09, 0x04,                       // USAGE (Joystick) (Maybe change to gamepad? I don't think so but...)
  0xa1, 0x01,                       // COLLECTION (Application)
    0xa1, 0x00,                       // COLLECTION (Physical)
    
///      0x05, 0x09,                       // USAGE_PAGE (Button)
///      0x19, 0x01,                       // USAGE_MINIMUM (Button 1)
///      0x29, 0x04,                       // USAGE_MAXIMUM (Button 4)
///      0x15, 0x00,                       // LOGICAL_MINIMUM (0)
///      0x25, 0x01,                       // LOGICAL_MAXIMUM (1)
///      0x95, 0x04,                       // REPORT_COUNT (4)
///      0x75, 0x01,                       // REPORT_SIZE (1)
///      0x81, 0x02,                       // INPUT (Data,Var,Abs)

///      0x95, 0x01,                       // REPORT_COUNT (1) ; pad out the bits into a number divisible by 8
///      0x75, 0x04,                       // REPORT_SIZE (4)
///      0x81, 0x03,                       // INPUT (Const,Var,Abs)
      0x05, 0x09,                       // USAGE_PAGE (Button)
      0x19, 0x01,                       // USAGE_MINIMUM (Button 1)
      0x29, 0x18,                       // USAGE_MAXIMUM (Button 24)
      0x15, 0x00,                       // LOGICAL_MINIMUM (0)
      0x25, 0x01,                       // LOGICAL_MAXIMUM (1)
      0x95, 0x18,                       // REPORT_COUNT (24)
      0x75, 0x01,                       // REPORT_SIZE (1)
      0x81, 0x02,                       // INPUT (Data,Var,Abs)

      0x95, 0x01,                       // REPORT_COUNT (1)
      0x75, 0x08,                       // REPORT_SIZE (8)
      0x81, 0x03,                       // INPUT (Const,Var,Abs)

      0x05, 0x01,                       // USAGE_PAGE (Generic Desktop)
      0x09, 0x01,                       // USAGE (pointer)
      0xa1, 0x00,                       // COLLECTION (Physical) 
        0x09, 0x30,                       // USAGE (X)
        0x09, 0x31,                       // USAGE (Y)
        0x15, 0xff,                       // LOGICAL_MINIMUM (-1)
        0x25, 0x01,                       // LOGICAL_MAXIMUM (1)
        0x95, 0x02,                       // REPORT_COUNT (2)
        0x75, 0x08,                       // REPORT_SIZE (8)
        0x81, 0x02,                       // INPUT (Data,Var,Abs)
      0xc0,                             // END_COLLECTION

    0xc0,                             // END_COLLECTION
  0xc0,                             // END_COLLECTION 
};

Gamepad_::Gamepad_(void) : PluggableUSBModule(1, 1, epType), protocol(HID_REPORT_PROTOCOL), idle(1)
{
  epType[0] = EP_TYPE_INTERRUPT_IN;
  PluggableUSB().plug(this);
}

int Gamepad_::getInterface(uint8_t* interfaceCount)
{
  *interfaceCount += 1; // uses 1
  HIDDescriptor hidInterface = {
    D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE),
    D_HIDREPORT(sizeof(_hidReportDescriptor)),
    D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01)
  };
  return USB_SendControl(0, &hidInterface, sizeof(hidInterface));
}

int Gamepad_::getDescriptor(USBSetup& setup)
{
  // Check if this is a HID Class Descriptor request
  if (setup.bmRequestType != REQUEST_DEVICETOHOST_STANDARD_INTERFACE) { return 0; }
  if (setup.wValueH != HID_REPORT_DESCRIPTOR_TYPE) { return 0; }

  // In a HID Class Descriptor wIndex cointains the interface number
  if (setup.wIndex != pluggedInterface) { return 0; }

  // Reset the protocol on reenumeration. Normally the host should not assume the state of the protocol
  // due to the USB specs, but Windows and Linux just assumes its in report mode.
  protocol = HID_REPORT_PROTOCOL;

  return USB_SendControl(TRANSFER_PGM, _hidReportDescriptor, sizeof(_hidReportDescriptor));
}

bool Gamepad_::setup(USBSetup& setup)
{
  if (pluggedInterface != setup.wIndex) {
    return false;
  }

  uint8_t request = setup.bRequest;
  uint8_t requestType = setup.bmRequestType;

  if (requestType == REQUEST_DEVICETOHOST_CLASS_INTERFACE)
  {
    if (request == HID_GET_REPORT) {
      // TODO: HID_GetReport();
      return true;
    }
    if (request == HID_GET_PROTOCOL) {
      // TODO: Send8(protocol);
      return true;
    }
  }

  if (requestType == REQUEST_HOSTTODEVICE_CLASS_INTERFACE)
  {
    if (request == HID_SET_PROTOCOL) {
      protocol = setup.wValueL;
      return true;
    }
    if (request == HID_SET_IDLE) {
      idle = setup.wValueL;
      return true;
    }
    if (request == HID_SET_REPORT)
    {
    }
  }

  return false;
}

void Gamepad_::reset()
{
  _GamepadReport.X = 0;
  _GamepadReport.Y = 0;
  _GamepadReport.buttons = 0;
  this->send();
}

void Gamepad_::send() 
{
  USB_Send(pluggedEndpoint | TRANSFER_RELEASE, &_GamepadReport, sizeof(GamepadReport));
}

uint8_t Gamepad_::getShortName(char *name)
{
  if(!next) 
  {
    strcpy(name, gp_serial);
    return strlen(name);
  }
  return 0;
}

コレコビジョンコアでNTT DATAキーパッドをつかう

MiSTer FPGAコレコビジョンコアでNTT DATAキーパッドを使いたい! github.com

まずはスーパーファミコンNTT DATAキーパッドをUSB化する基板を選びます。 www.ebay.com

Sparkfun Pro Microが載っているのでファームウェアをカスタマイズできそうです。

デフォルトのファームウェアでは、スーパーファミコンのボタンしか反応しなかったので、以下のものに書き換えました。 github.com

またMicro USBが使いずらいのでUSB CのPro Microに置き換えました。

Pro Micro 5V/16MHz/USB-C(互換品)|TALPKEYBOARD – TALPKEYBOARD SHOP

デフォルトではコントローラーは1つしか認識しないので、2つつなぐために若干修正しました。

/*  DaemonBite (S)NES Controllers to USB Adapter with NTT Datapad support
 *  Author: Mikael Norrgård <mick@daemonbite.com>
 *
 *  Copyright (c) 2020 Mikael Norrgård <http://daemonbite.com>
 *  
 *  GNU GENERAL PUBLIC LICENSE
 *  Version 3, 29 June 2007
 *  
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *  
 */

#include "Gamepad.h"

// ATT: 20 chars max (including NULL at the end) according to Arduino source code.
// Additionally serial number is used to differentiate arduino projects to have different button maps!
const char *gp_serial = "NES/SNES to USB";

//#define DEBUG

#define GAMEPAD_COUNT 2      // NOTE: To have more than 2 two gamepads you need to disable the CDC of the Arduino.

電子ゲームで遊ぼう会3(ヨコハマ)

電子ゲームで遊ぶイベントです。

  • FLゲーム
  • LSIゲーム
  • LCDゲーム

ハッシュタグ(2026/9/27)

MAMEVECTOR64その2

DeepSeek、Gemini Notebookによるシリアル通信の改善

// license:BSD-3-Clause
// copyright-holders:Brad Oliver,Aaron Giles,Bernd Wiebelt,Allard van der Bas
/******************************************************************************
 *
 * vector.c
 *
 *        anti-alias code by Andrew Caldwell
 *        (still more to add)
 *
 * 040227 Fixed miny clip scaling which was breaking in mhavoc. AREK
 * 010903 added support for direct RGB modes MLR
 * 980611 use translucent vectors. Thanks to Peter Hirschberg
 *        and Neil Bradley for the inspiration. BW
 * 980307 added cleverer dirty handling. BW, ASG
 *        fixed antialias table .ac
 * 980221 rewrote anti-alias line draw routine
 *        added inline assembly multiply fuction for 8086 based machines
 *        beam diameter added to draw routine
 *        beam diameter is accurate in anti-alias line draw (Tcosin)
 *        flicker added .ac
 * 980203 moved LBO's routines for drawing into a buffer of vertices
 *        from avgdvg.c to this location. Scaling is now initialized
 *        by calling vector_init(...). BW
 * 980202 moved out of msdos.c ASG
 * 980124 added anti-alias line draw routine
 *        modified avgdvg.c and sega.c to support new line draw routine
 *        added two new tables Tinten and Tmerge (for 256 color support)
 *        added find_color routine to build above tables .ac
 *
 **************************************************************************** */

#include "emu.h"
#include "emuopts.h"
#include "rendutil.h"
#include "vector.h"

// Serial port related includes
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <errno.h>

#include <inttypes.h>
#include <sys/time.h>
 

#define FLT_EPSILON 1E-5

#define VECTOR_WIDTH_DENOM 512

#define MAX_POINTS 10000

#define VECTOR_SERIAL_MAX 4095

#define VECTOR_TEAM \
   "-* Vector Heads *-\n" \
   "Brad Oliver\n" \
   "Aaron Giles\n" \
   "Bernd Wiebelt\n" \
   "Allard van der Bas\n" \
   "Al Kossow (VECSIM)\n" \
   "Hedley Rainnie (VECSIM)\n" \
   "Eric Smith (VECSIM)\n" \
   "Neil Bradley (technical advice)\n" \
   "Andrew Caldwell (anti-aliasing)\n" \
   "- *** -\n"

///-----
#ifdef __cplusplus > 201711L
  #define TERMIWIN_MAYBE_UNUSED [[maybe_unused]]
#else
  #ifdef __GNUC__
    #define TERMIWIN_MAYBE_UNUSED __attribute__((unused))
  #else
    #define TERMIWIN_MAYBE_UNUSED
  #endif
#endif

#include <fcntl.h>
#include <stdlib.h>

typedef struct COM {
  HANDLE hComm;
  int fd; //Actually it's completely useless
  char port[128];
} COM;

DCB SerialParams = { 0 }; //Initializing DCB structure
struct COM com;
COMMTIMEOUTS timeouts = { 0 }; //Initializing COMMTIMEOUTS structure

//LOCAL functions

//nbyte 0->7

int getByte(tcflag_t flag, int nbyte, int nibble) {

  int byte;
  if (nibble == 1)
    byte = (flag >> (8 * (nbyte)) & 0x0f);
  else
    byte = (flag >> (8 * (nbyte)) & 0xf0);
  return byte;
}

//INPUT FUNCTIONS

enum{
  i_IXOFF = 0x01,
  i_IXON = 0x02,
  i_IXOFF_IXON = 0x03,
  i_PARMRK = 0x04,
  i_PARMRK_IXOFF = 0x05,
  i_PARMRK_IXON = 0x06,
  i_PARMRK_IXON_IXOFF = 0x07
};

int getIXOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);

  return byte;
}

//LOCALOPT FUNCTIONS

enum{
  l_NOECHO = 0x00,
  l_ECHO = 0x01,
  l_ECHO_ECHOE = 0x03,
  l_ECHO_ECHOK = 0x05,
  l_ECHO_ECHONL = 0x09,
  l_ECHO_ECHOE_ECHOK = 0x07,
  l_ECHO_ECHOE_ECHONL = 0x0b,
  l_ECHO_ECHOE_ECHOK_ECHONL = 0x0f,
  l_ECHO_ECHOK_ECHONL = 0x0d,
  l_ECHOE = 0x02,
  l_ECHOE_ECHOK = 0x06,
  l_ECHOE_ECHONL = 0x0a,
  l_ECHOE_ECHOK_ECHONL = 0x0e,
  l_ECHOK = 0x04,
  l_ECHOK_ECHONL = 0x0c,
  l_ECHONL = 0x08
};

int getEchoOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

enum{
  l_ICANON = 0x10,
  l_ICANON_ISIG = 0x50,
  l_ICANON_IEXTEN = 0x30,
  l_ICANON_NOFLSH = 0x90,
  l_ICANON_ISIG_IEXTEN = 0x70,
  l_ICANON_ISIG_NOFLSH = 0xd0,
  l_ICANON_IEXTEN_NOFLSH = 0xb0,
  l_ICANON_ISIG_IEXTEN_NOFLSH = 0xf0,
  l_ISIG = 0x40,
  l_ISIG_IEXTEN = 0x60,
  l_ISIG_NOFLSH = 0xc0,
  l_ISIG_IEXTEN_NOFLSH = 0xe0,
  l_IEXTEN = 0x20,
  l_IEXTEN_NOFLSH = 0xa0,
  l_NOFLSH = 0x80,
};

int getLocalOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

enum{
  l_TOSTOP = 0x01
};

int getToStop(tcflag_t flag) {
  int byte = getByte(flag, 1, 1);
  return byte;
}

//CONTROLOPT FUNCTIONS

int getCharSet(tcflag_t flag) {

  //FLAG IS MADE UP OF 8 BYTES, A FLAG IS MADE UP OF A NIBBLE -> 4 BITS, WE NEED TO EXTRACT THE SECOND NIBBLE (1st) FROM THE FIFTH BYTE (6th).
  int byte = getByte(flag, 1, 1);

  switch (byte) {

  case 0X0:
    return CS5;
    break;

  case 0X4:
    return CS6;
    break;

  case 0X8:
    return CS7;
    break;

  case 0Xc:
    return CS8;
    break;

  default:
    return CS8;
    break;
  }
}

enum{
  c_ALL_ENABLED = 0xd0,
  c_PAREVEN_CSTOPB = 0x50,
  c_PAREVEN_NOCSTOPB = 0x40,
  c_PARODD_NOCSTOPB = 0xc0,
  c_NOPARENB_CSTOPB = 0x10,
  c_ALL_DISABLED = 0x00,
};

int getControlOptions(tcflag_t flag) {
  int byte = getByte(flag, 1, 0);
  return byte;
}

//LIBFUNCTIONS

int tcgetattr(int fd, struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  if (fd != com.fd) return -1;
  int TERMIWIN_MAYBE_UNUSED ret = 0;

  ret = GetCommState(com.hComm, &SerialParams);

  return 0;
}

int tcsetattr(int fd, int TERMIWIN_MAYBE_UNUSED optional_actions, const struct termios* termios_p) {

  if (fd != com.fd) return -1;
  int ret = 0;

  //Store flags into local variables
  tcflag_t iflag = termios_p->c_iflag;
  tcflag_t lflag = termios_p->c_lflag;
  tcflag_t cflag = termios_p->c_cflag;
  tcflag_t TERMIWIN_MAYBE_UNUSED oflag = termios_p->c_oflag;

  //iflag

  int IX = getIXOptions(iflag);

  if ((IX == i_IXOFF_IXON) || (IX == i_PARMRK_IXON_IXOFF)) {

    SerialParams.fOutX = TRUE;
    SerialParams.fInX = TRUE;
    SerialParams.fTXContinueOnXoff = TRUE;
  }

  //lflag
  int TERMIWIN_MAYBE_UNUSED EchoOpt = getEchoOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED l_opt = getLocalOptions(lflag);
  int TERMIWIN_MAYBE_UNUSED tostop = getToStop(lflag);

  //Missing parameters...

  //cflags

  int CharSet = getCharSet(cflag);
  int c_opt = getControlOptions(cflag);

  switch (CharSet) {

  case CS5:
    SerialParams.ByteSize = 5;
    break;

  case CS6:
    SerialParams.ByteSize = 6;
    break;

  case CS7:
    SerialParams.ByteSize = 7;
    break;

  case CS8:
    SerialParams.ByteSize = 8;
    break;
  }

  switch (c_opt) {

  case c_ALL_ENABLED:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_ALL_DISABLED:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PAREVEN_CSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;

  case c_PAREVEN_NOCSTOPB:
    SerialParams.Parity = EVENPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_PARODD_NOCSTOPB:
    SerialParams.Parity = ODDPARITY;
    SerialParams.StopBits = ONESTOPBIT;
    break;

  case c_NOPARENB_CSTOPB:
    SerialParams.Parity = NOPARITY;
    SerialParams.StopBits = TWOSTOPBITS;
    break;
  }

  //aflags

  /*
  int OP;
  if(oflag == OPOST)
  else ...
  */
  //Missing parameters...

  //special characters

  if (termios_p->c_cc[VEOF] != 0) SerialParams.EofChar = (char)termios_p->c_cc[VEOF];
  if (termios_p->c_cc[VINTR] != 0) SerialParams.EvtChar = (char)termios_p->c_cc[VINTR];

  if (termios_p->c_cc[VMIN] == 1) { //Blocking

    timeouts.ReadIntervalTimeout = 0;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = 0;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = 0;  // in milliseconds
///    timeouts.WriteTotalTimeoutConstant = 0;   // in milliseconds
    timeouts.WriteTotalTimeoutConstant = 1000;   // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds

  } else { //Non blocking

    timeouts.ReadIntervalTimeout = termios_p->c_cc[VTIME] * 100;         // in milliseconds
    timeouts.ReadTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;    // in milliseconds
    timeouts.ReadTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100;  // in milliseconds
///    timeouts.WriteTotalTimeoutConstant = termios_p->c_cc[VTIME] * 100;   // in milliseconds
    timeouts.WriteTotalTimeoutConstant = 1000;   // in milliseconds
///    timeouts.WriteTotalTimeoutMultiplier = termios_p->c_cc[VTIME] * 100; // in milliseconds
    timeouts.WriteTotalTimeoutMultiplier = 0; // in milliseconds
  }

  SetCommTimeouts(com.hComm, &timeouts);

  //EOF

  ret = SetCommState(com.hComm, &SerialParams);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcsendbreak(int fd, int TERMIWIN_MAYBE_UNUSED duration) {

  if (fd != com.fd) return -1;

  int ret = 0;
  ret = TransmitCommChar(com.hComm, '\x00');
  if (ret != 0)
    return 0;
  else
    return -1;
}

int tcdrain(int fd) {

///  if (fd != com.fd) return -1;
///  return FlushFileBuffers(com.hComm);
    if (fd != com.fd) return -1;

    // FlushFileBuffers(com.hComm) を削除し、即座に成功(0)を返すようにします。
    // これにより、ハードウェア側の送信完了を待たずに次の処理へ進めるようになります。
    return 0; 
}

int tcflush(int fd, int queue_selector) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (queue_selector) {

  case TCIFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    break;

  case TCOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  case TCIOFLUSH:
    rc = PurgeComm(com.hComm, PURGE_RXCLEAR);
    rc *= PurgeComm(com.hComm, PURGE_TXCLEAR);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

int tcflow(int fd, int action) {

  if (fd != com.fd) return -1;
  int rc = 0;

  switch (action) {

  case TCOOFF:
    rc = PurgeComm(com.hComm, PURGE_TXABORT);
    break;

  case TCOON:
    rc = ClearCommBreak(com.hComm);
    break;

  case TCIOFF:
    rc = PurgeComm(com.hComm, PURGE_RXABORT);
    break;

  case TCION:
    rc = ClearCommBreak(com.hComm);
    break;

  default:
    rc = 0;
    break;
  }

  if (rc != 0)
    return 0;
  else
    return -1;
}

void cfmakeraw(struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  SerialParams.ByteSize = 8;
  SerialParams.StopBits = ONESTOPBIT;
  SerialParams.Parity = NOPARITY;
}

speed_t cfgetispeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

speed_t cfgetospeed(const struct termios* TERMIWIN_MAYBE_UNUSED termios_p) {

  return SerialParams.BaudRate;
}

int cfsetispeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetospeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

int cfsetspeed(struct termios* TERMIWIN_MAYBE_UNUSED termios_p, speed_t speed) {

  SerialParams.BaudRate = speed;
  return 0;
}

ssize_t read_serial(int fd, void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = ReadFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

ssize_t write_serial(int fd, const void* buffer, size_t count) {

  if (fd != com.fd) return -1;
///  int rc = 0;
  DWORD rc = 0;
  int ret;

  ret = WriteFile(com.hComm, buffer, count, &rc, NULL);

  if (ret == 0)
    return -1;
  else
    return rc;
}

int open_serial(const char* portname, int opt) {

  if (strlen(portname) < 4) return -1;

  // Set to zero
  memset(com.port, 0x00, 128);

  //COMxx
  size_t portSize = 0;
  if (strlen(portname) > 4) {
    portSize = sizeof(char) * strlen("\\\\.\\COM10") + 1;
#ifdef _MSC_VER
    strncat_s(com.port, portSize, "\\\\.\\", strlen("\\\\.\\"));
#else
    strncat(com.port, "\\\\.\\", strlen("\\\\.\\"));
#endif
  }
  //COMx
  else {
    portSize = sizeof(char) * 5;
  }

#ifdef _MSC_VER
  strncat_s(com.port, portSize, portname, 4);
#else
  strncat(com.port, portname, 4);
#endif
  com.port[portSize] = 0x00;

  switch (opt) {

  case O_RDWR:
    com.hComm = CreateFile(com.port, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_RDONLY:
    com.hComm = CreateFile(com.port, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;

  case O_WRONLY:
    com.hComm = CreateFile(com.port, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
    break;
  }

  if (com.hComm == INVALID_HANDLE_VALUE) {
    return -1;
  }
  com.fd = atoi(portname + 3); // COMx and COMxx
  SerialParams.DCBlength = sizeof(SerialParams);
  return com.fd;
}

int close_serial(int TERMIWIN_MAYBE_UNUSED fd) {

  int ret = CloseHandle(com.hComm);
  if (ret != 0)
    return 0;
  else
    return -1;
}

int select_serial(int TERMIWIN_MAYBE_UNUSED nfds, fd_set* readfds, fd_set* TERMIWIN_MAYBE_UNUSED writefds, fd_set* TERMIWIN_MAYBE_UNUSED exceptfds, struct timeval* TERMIWIN_MAYBE_UNUSED timeout) {

    DWORD dwErrors;
    COMSTAT cs;

    // 現在のシリアルポートの状態を取得し、エラーをクリアする
    if (!ClearCommError(com.hComm, &dwErrors, &cs)) {
        return -1; // 失敗した場合はエラーを返す
    }

    // 受信バッファにデータ(cbInQue)があるか確認する
    if (cs.cbInQue > 0) {
        return com.fd; // データがあればファイル記述子(ポート番号)を返す
    } else {
        if (readfds) {
            // データがない場合は、呼び出し元のセットから記述子をクリアする
            FD_CLR(com.fd, readfds);
        }
    }

///  SetCommMask(com.hComm, EV_RXCHAR);
///  DWORD dwEventMask;
///  if (WaitCommEvent(com.hComm, &dwEventMask, NULL) == 0) {
///    return -1; // Return -1 if failed
///  }
///  if (dwEventMask == EV_RXCHAR) {
///    return com.fd;
///  } else {
///    if (readfds) {
      // Clear file descriptor if event is not RXCHAR
///      FD_CLR(com.fd, readfds);
///    }
///  }
  // NOTE: write event not detectable!
  // NOTE: no timeout
  return 0; // No data
}

//Returns hComm from the COM structure
HANDLE getHandle() {
  return com.hComm;
}
///-----

#define VCLEAN  0
#define VDIRTY  1
#define VCLIP   2

// device type definition
const device_type VECTOR = &device_creator<vector_device>;

vector_device::vector_device(const machine_config &mconfig, device_type type, const char *name, const char *tag, device_t *owner, UINT32 clock, const char *shortname, const char *source)
    : device_t(mconfig, type, name, tag, owner, clock, shortname, source),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

vector_device::vector_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock)
    : device_t(mconfig, VECTOR, "VECTOR", tag, owner, clock, "vector_device", __FILE__),
        device_video_interface(mconfig, *this),
        m_vector_list(nullptr),
        m_min_intensity(255),
        m_max_intensity(0)
{
}

float vector_device::m_flicker = 0.0f;
float vector_device::m_beam_width_min = 0.0f;
float vector_device::m_beam_width_max = 0.0f;
float vector_device::m_beam_intensity_weight = 0.0f;
int vector_device::m_vector_index;

struct serial_segment_t {
    struct serial_segment_t * next;
    int intensity;
    int x0;
    int y0;
    int x1;
    int y1;

    serial_segment_t(
        int x0,
        int y0,
        int x1,
        int y1,
        int intensity
    ) :
        next(NULL),
        intensity(intensity),
        x0(x0),
        y0(y0),
        x1(x1),
        y1(y1)
    {
    }
};

int
serial_open(
        const char * const dev
)
{
///        const int fd = open(dev, O_RDWR | O_NONBLOCK | O_NOCTTY, 0666);
        const int fd = open(dev, O_RDWR, 0666);
        if (fd < 0)
                return -1;

        // Disable modem control signals
        struct termios attr;
        tcgetattr(fd, &attr);
        attr.c_cflag |= CLOCAL | CREAD;
        attr.c_oflag &= ~OPOST;
        tcsetattr(fd, TCSANOW, &attr);

        return fd;
}

void vector_device::serial_draw_point(
    unsigned x,
    unsigned y,
    int intensity
)
{
    // make sure that we are in range; should always be
    // due to clipping on the window, but just in case
    if (x < 0) x = 0;
    if (y < 0) y = 0;

    if (x > VECTOR_SERIAL_MAX) x = VECTOR_SERIAL_MAX;
    if (y > VECTOR_SERIAL_MAX) y = VECTOR_SERIAL_MAX;

    // always flip the Y, since the vectorscope measures
    // 0,0 at the bottom left corner, but this coord uses
    // the top left corner.
    y = VECTOR_SERIAL_MAX - y;

    unsigned bright;
    if (intensity > m_serial_bright)
        bright = 63;
    else
    if (intensity <= 0)
        bright = 0;
    else
        bright = (intensity * 64) / 256;

    if (bright > 63)
        bright = 63;

    if (m_serial_rotate == 1)
    {
        // +90
        unsigned tmp = x;
        x = VECTOR_SERIAL_MAX - y;
        y = tmp;
    } else
    if (m_serial_rotate == 2)
    {
        // +180
        x = VECTOR_SERIAL_MAX - x;
        y = VECTOR_SERIAL_MAX - y;
    } else
    if (m_serial_rotate == 3)
    {
        // -90
        unsigned t = x;
        x = y;
        y = VECTOR_SERIAL_MAX - t;
    }

    uint32_t cmd = 0
        | (2 << 30)
        | (bright & 0x3F) << 24
        | (x & 0xFFF) << 12
        | (y & 0xFFF) <<  0
        ;

    //printf("%08x %8d %8d %3d\n", cmd, x, y, intensity);

    m_serial_buf[m_serial_offset++] = cmd >> 24;
    m_serial_buf[m_serial_offset++] = cmd >> 16;
    m_serial_buf[m_serial_offset++] = cmd >>  8;
    m_serial_buf[m_serial_offset++] = cmd >>  0;

    // todo: check for overflow;
    // should always have enough points
}


// This will only be called with non-zero intensity lines.
// we keep a linked list of the vectors and sort them with
// a greedy insertion sort.
void vector_device::serial_draw_line(
    float xf0,
    float yf0,
    float xf1,
    float yf1,
    int intensity
)
{
    if (m_serial_fd < 0)
        return;

    // scale and shift each of the axes.
    const int x0 = (xf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y0 = (yf0 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;
    const int x1 = (xf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_x + m_serial_offset_x;
    const int y1 = (yf1 * VECTOR_SERIAL_MAX - VECTOR_SERIAL_MAX/2) * m_serial_scale_y + m_serial_offset_y;

    serial_segment_t * const new_segment
        = new serial_segment_t(x0, y0, x1, y1, intensity);

    if (this->m_serial_segments_tail)
        this->m_serial_segments_tail->next = new_segment;
    else
        this->m_serial_segments = new_segment;

    this->m_serial_segments_tail = new_segment;
}


void vector_device::serial_reset()
{
    m_serial_offset = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;
    m_serial_buf[m_serial_offset++] = 0;

    m_vector_transit[0] = 0;
    m_vector_transit[1] = 0;
    m_vector_transit[2] = 0;
}


void vector_device::serial_send()
{
    if (m_serial_fd < 0)
        return;

    int last_x = -1;
    int last_y = -1;

    // find the next closest point to the last one.
    // greedy sorting algorithm reduces beam transit time
    // fairly significantly. doesn't matter for the
    // vectorscope, but makes a big difference for Vectrex
    // and other slower displays.
    while(this->m_serial_segments)
    {
        int reverse = 0;
        int min = 1e6;
        serial_segment_t ** min_seg
            = &this->m_serial_segments;

        if (m_serial_sort)
        for(serial_segment_t ** s = min_seg ; *s ; s = &(*s)->next)
        {
            int dx0 = (*s)->x0 - last_x;
            int dy0 = (*s)->y0 - last_y;
            int dx1 = (*s)->x1 - last_x;
            int dy1 = (*s)->y1 - last_y;
            int d0 = sqrt(dx0*dx0 + dy0*dy0);
            int d1 = sqrt(dx1*dx1 + dy1*dy1);

            if(d0 < min)
            {
                min_seg = s;
                min = d0;
                reverse = 0;
            }

            if (d1 < min)
            {
                min_seg = s;
                min = d1;
                reverse = 1;
            }

            // if we have hit two identical points,
            // then stop the search here.
            if (min == 0)
                break;
        }

        serial_segment_t * const s = *min_seg;
        if (!s)
            break;
    
        const int x0 = reverse ? s->x1 : s->x0;
        const int y0 = reverse ? s->y1 : s->y0;
        const int x1 = reverse ? s->x0 : s->x1;
        const int y1 = reverse ? s->y0 : s->y1;

        // if this is not a continuous segment,
        // we must add a transit command
        if (last_x != x0 || last_y != y0)
        {
            serial_draw_point(x0, y0, 0);
            int dx = x0 - last_x;
            int dy = y0 - last_y;
            m_vector_transit[0] += sqrt(dx*dx + dy*dy);
        }

        // transit to the new point
        int dx = x1 - x0;
        int dy = y1 - y0;
        int dist = sqrt(dx*dx + dy*dy);

        serial_draw_point(x1, y1, s->intensity);
        last_x = x1;
        last_y = y1;

        if (s->intensity > m_serial_bright)
            m_vector_transit[2] += dist;
        else
            m_vector_transit[1] += dist;

        // delete this segment from the list
        *min_seg = s->next;
        delete s;
    }

    // ensure that we erase our tracks
    if(this->m_serial_segments != NULL)
        fprintf(stderr, "errr?\n");
    this->m_serial_segments = NULL;
    this->m_serial_segments_tail = NULL;

    // add the "done" command to the message
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;
    m_serial_buf[m_serial_offset++] = 1;

    size_t offset = 0;

///    if(1)
///    printf("%zu vectors: off=%u on=%u bright=%u%s\n",
///        m_serial_offset/4,
///        m_vector_transit[0],
///        m_vector_transit[1],
///        m_vector_transit[2],
///        m_serial_drop_frame ? " !" : ""
///    );

    static unsigned skip_frame;
    unsigned eagain = 0;

    if (m_serial_drop_frame || skip_frame++ % 2 != 0)
    {
        // we skipped a frame, don't skip the next one
        m_serial_drop_frame = 0;
    } else
    while (offset < m_serial_offset)
    {
        size_t wlen = m_serial_offset - offset;
///        if (wlen > 64)
///            wlen = 64;
        if (wlen > 4096)
            wlen = 4096;

///        ssize_t rc = write(m_serial_fd, m_serial_buf + offset, m_serial_offset - offset);
        ssize_t rc = write(m_serial_fd, m_serial_buf + offset, wlen);
///        if (rc <= 0)
///        {
///            eagain++;
///            if (errno == EAGAIN)
///                continue;
///            perror(m_serial);
///            close(m_serial_fd);
///            m_serial_fd = -1;
///            break;
///        }
        if (rc <= 0)
        {
            if (rc == 0) {
                eagain++;
///                Sleep(1);
                continue;
            }
            // Real error: close and bail
            perror(m_serial);
            close(m_serial_fd);
            m_serial_fd = -1;
            break;
        }

        offset += rc;
    }

///    printf("%d eagain.\n", eagain);
///    if (eagain > 20)
    if (eagain > 5)
        m_serial_drop_frame = 1;

    serial_reset();
}



void vector_device::device_start()
{
    /* Grab the settings for this session */
    m_beam_width_min = machine().options().beam_width_min();
    m_beam_width_max = machine().options().beam_width_max();
    m_beam_intensity_weight = machine().options().beam_intensity_weight();
    m_flicker = machine().options().flicker();

    m_vector_index = 0;

    /* allocate memory for tables */
    m_vector_list = make_unique_clear<point[]>(MAX_POINTS);

    /* Setup the serial output of the XY coords if configured */
    m_serial = machine().options().vector_serial();
    const float scale = machine().options().vector_scale();
    if (scale != 0.0)
    {
        // user specified a scale on the command line
        m_serial_scale_x = m_serial_scale_y = scale;
    } else {
        // use the per-axis scales
        m_serial_scale_x = machine().options().vector_scale_x();
        m_serial_scale_y = machine().options().vector_scale_y();
    }

    m_serial_segments = m_serial_segments_tail = NULL;

    m_serial_offset_x = machine().options().vector_offset_x();
    m_serial_offset_y = machine().options().vector_offset_y();
    m_serial_rotate = machine().options().vector_rotate();
    m_serial_bright = machine().options().vector_bright();
    m_serial_drop_frame = 0;
    m_serial_sort = 1;

    // allocate enough buffer space, although we should never use this much
    m_serial_buf = auto_alloc_array_clear(machine(), unsigned char, (MAX_POINTS+2) * 4);
    if (!m_serial_buf)
    {
        // todo: how to signal an error?
    }

    serial_reset();

    if (!m_serial || strcmp(m_serial,"") == 0)
    {
        fprintf(stderr, "no serial vector display configured\n");
        m_serial_fd = -1;
    } else {
        m_serial_fd = serial_open(m_serial);
        fprintf(stderr, "serial dev='%s' fd=%d\n", m_serial, m_serial_fd);
    }
}

void vector_device::set_flicker(float newval)
{
    m_flicker = newval;
}

float vector_device::get_flicker()
{
    return m_flicker;
}

void vector_device::set_beam_width_min(float newval)
{
    m_beam_width_min = newval;
}

float vector_device::get_beam_width_min()
{
    return m_beam_width_min;
}

void vector_device::set_beam_width_max(float newval)
{
    m_beam_width_max = newval;
}

float vector_device::get_beam_width_max()
{
    return m_beam_width_max;
}

void vector_device::set_beam_intensity_weight(float newval)
{
    m_beam_intensity_weight = newval;
}

float vector_device::get_beam_intensity_weight()
{
    return m_beam_intensity_weight;
}


/*
 * www.dinodini.wordpress.com/2010/04/05/normalized-tunable-sigmoid-functions/
 */
float vector_device::normalized_sigmoid(float n, float k)
{
    // valid for n and k in range of -1.0 and 1.0
    return (n - n * k) / (k - fabs(n) * 2.0f * k + 1.0f);
}


/*
 * Adds a line end point to the vertices list. The vector processor emulation
 * needs to call this.
 */
void vector_device::add_point(int x, int y, rgb_t color, int intensity)
{
    point *newpoint;

//printf("%d %d: %d,%d,%d @ %d\n", x, y, color.r(), color.b(), color.g(), intensity);

    // hack for the vectrex
    // -- convert "128,128,128" @ 255 to "255,255,255" @ 127
    if (color.r() == 128
    &&  color.b() == 128
    &&  color.g() == 128
    &&  intensity == 255)
    {
        color = rgb_t(255,255,255);
        intensity = 128;
    }

    intensity = MAX(0, MIN(255, intensity));

    m_min_intensity = intensity > 0 ? MIN(m_min_intensity, intensity) : m_min_intensity;
    m_max_intensity = intensity > 0 ? MAX(m_max_intensity, intensity) : m_max_intensity;

    if (m_flicker && (intensity > 0))
    {
        float random = (float)(machine().rand() & 255) / 255.0f; // random value between 0.0 and 1.0

        intensity -= (int)(intensity * random * m_flicker);

        intensity = MAX(0, MIN(255, intensity));
    }

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x;
    newpoint->y = y;
    newpoint->col = color;
    newpoint->intensity = intensity;
    newpoint->status = VDIRTY; /* mark identical lines as clean later */

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * Add new clipping info to the list
 */
void vector_device::add_clip(int x1, int yy1, int x2, int y2)
{
    point *newpoint;

    newpoint = &m_vector_list[m_vector_index];
    newpoint->x = x1;
    newpoint->y = yy1;
    newpoint->arg1 = x2;
    newpoint->arg2 = y2;
    newpoint->status = VCLIP;

    m_vector_index++;
    if (m_vector_index >= MAX_POINTS)
    {
        m_vector_index--;
        logerror("*** Warning! Vector list overflow!\n");
    }
}


/*
 * The vector CPU creates a new display list. We save the old display list,
 * but only once per refresh.
 */
void vector_device::clear_list(void)
{
    m_vector_index = 0;
}


UINT32 vector_device::screen_update(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
    UINT32 flags = PRIMFLAG_ANTIALIAS(screen.machine().options().antialias() ? 1 : 0) | PRIMFLAG_BLENDMODE(BLENDMODE_ADD) | PRIMFLAG_VECTOR(1);
    const rectangle &visarea = screen.visible_area();
    float xscale = 1.0f / (65536 * visarea.width());
    float yscale = 1.0f / (65536 * visarea.height());
    float xoffs = (float)visarea.min_x;
    float yoffs = (float)visarea.min_y;
    float xratio = xscale / yscale;
    float yratio = yscale / xscale;
    xratio = (xratio < 1.0f) ? xratio : 1.0f;
    yratio = (yratio < 1.0f) ? yratio : 1.0f;

    point *curpoint;
    render_bounds clip;
    int lastx = 0;
    int lasty = 0;

    curpoint = m_vector_list.get();

    screen.container().empty();
    screen.container().add_rect(0.0f, 0.0f, 1.0f, 1.0f, rgb_t(0xff,0x00,0x00,0x00), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA) | PRIMFLAG_VECTORBUF(1));

    clip.x0 = clip.y0 = 0.0f;
    clip.x1 = clip.y1 = 1.0f;

    for (int i = 0; i < m_vector_index; i++)
    {
        render_bounds coords;

        if (curpoint->status == VCLIP)
        {
            coords.x0 = ((float)curpoint->x - xoffs) * xscale;
            coords.y0 = ((float)curpoint->y - yoffs) * yscale;
            coords.x1 = ((float)curpoint->arg1 - xoffs) * xscale;
            coords.y1 = ((float)curpoint->arg2 - yoffs) * yscale;

            clip.x0 = (coords.x0 > 0.0f) ? coords.x0 : 0.0f;
            clip.y0 = (coords.y0 > 0.0f) ? coords.y0 : 0.0f;
            clip.x1 = (coords.x1 < 1.0f) ? coords.x1 : 1.0f;
            clip.y1 = (coords.y1 < 1.0f) ? coords.y1 : 1.0f;
        }
        else
        {
            float beam_intensity_width = m_beam_width_min;

            float intensity = (float)curpoint->intensity / 255.0f;

            // check for dynamic intensity
            if (m_min_intensity != m_max_intensity)
            {
                float intensity_weight = normalized_sigmoid(intensity, m_beam_intensity_weight);
                beam_intensity_width = (m_beam_width_max - m_beam_width_min) * intensity_weight + m_beam_width_min;
            }

            float beam_width = beam_intensity_width * (1.0f / (float)VECTOR_WIDTH_DENOM);

            coords.x0 = ((float)lastx - xoffs) * xscale;
            coords.y0 = ((float)lasty - yoffs) * yscale;
            coords.x1 = ((float)curpoint->x - xoffs) * xscale;
            coords.y1 = ((float)curpoint->y - yoffs) * yscale;

            // extend zero-length vector line (vector point) by quarter beam_width on both sides
            if (fabs(coords.x0 - coords.x1) < FLT_EPSILON &&
                fabs(coords.y0 - coords.y1) < FLT_EPSILON)
            {
                coords.x0 += xratio * beam_width * 0.25f;
                coords.y0 += yratio * beam_width * 0.25f;
                coords.x1 -= xratio * beam_width * 0.25f;
                coords.y1 -= yratio * beam_width * 0.25f;
            }

            if (curpoint->intensity != 0 && !render_clip_line(&coords, &clip))
            {
                screen.container().add_line(
                    coords.x0, coords.y0, coords.x1, coords.y1,
                    beam_width,
                    (curpoint->intensity << 24) | (curpoint->col & 0xffffff),
                    flags);

                serial_draw_line(
                    coords.x0, coords.y0,
                    coords.x1, coords.y1,
                    curpoint->intensity);
            }

            lastx = curpoint->x;
            lasty = curpoint->y;
        }

        curpoint++;
    }

    serial_send();

    return 0;
}