Vectrex_Interface

Vectrex Interface


Startup

Before initializing the Vectrex interface you should have called the PiTrex initialization.

  vectrexinit();


Before using the Vectrex interface you must call:

  v_init();


This initializes internal variables, sets up the Vectrex and (on baremetal) mounts the SD-Card filesystem.
If you want to change default variables you must do so AFTER
v_init() - otherwise the changes are overwritten.

The "main" functions usable from the user programs are:

HOUSEKEEPING

v_WaitRecal

Function:

void v_WaitRecal();
This function must be called once per "round" to ensure a stable image and to
supply a timing frame of reference.
The timing frame default is 50Hz, the timing frame can be changed with the function "v_setRefresh(int hz)". If the vector pipeline is enabled, v_WaitRecal will also schedule the handling/drawing of the pipeline as well as the buffer type.
v_WaitRecal also checks the standard inputs for vectrexInterface specific configuration options. On baremetal v_WaitRecal also provides the UART Terminal service.
A typical "start frame" function would be:

  void startFrame()
{
v_WaitRecal();
v_doSound();
v_readButtons();
v_readJoystick1Analog();
v_playAllSFX();
}

(if you don't use sound/SFX you can leave those out)

v_setRefresh

Function:

 v_setRefresh(int hz);

Sets the refresh frequency of the display. Calling v_WaitRecal() in appropriate intervals is necessary. v_WaitRecal() ensures (if called fast enough) the given display frequency.

v_error

Functions:

  void v_error(char *message); 
void v_errori(char *message, int i);


Very simple means to HALT the program in case of an emergency and give out a desperate error message on the Vectrex screen.

DRAWING

Using the function:

  void setCustomClipping(int enabled, int x0, int y0, int x1, int y1);


all input to the following draw routines (except v_printStringRaster) are "clipped" to the rectangle given by the coordinates. Clipping is done before any processing on the coordinates is done and is as such in the "user" space.

pipeline <> no pipeline

By default all drawings are done through a "pipeline".
Meaning:
All draw requests are collected during a game "round". And the call of "v_WaitRecal" processes all draw requests, optimizes them and draws them on screen.
The drawing process as such is delayed and not done at the time when calling the draw function.
If you want to enable immediate drawing you can disable the pipeline:

  extern int usePipeline;
usePipeline = 0; // disable
usePipeline = 1; // enable


The "direct" draw method is "deprecated" and "good" results can not be assured!

bufferType


  extern int bufferType;
bufferType = 0;

Using the display pipelineit as of now has 4 bufferTypes:

  • 0 (default) display the generated vectorlist each round after it was "generated"
  • 1 double redraw this displays each frame twice - without doing any "emulation" between the two. This works e.g. for tailgunner, which is originally a 38Hz game. You can run tailgunner in 76Hz with double buffer on. Than the display is 76Hz - but the game is at the speed of 38Hz. (Only way to do that is changing the code or via the UART terminal)
  • 2 Smart redraw If the game does not produce ANY output in a "round" - than the last round is redisplayed. Battle Zone uses this, as BZ only generated a frame every 3 rounds. With this active the display is nice and smooth.
  • 3 No display This actually switches off the complete display and the game just runs (fullspeed! - without any sync) without displaying. (Battlezone crashes really quickly then!)


v_directDraw32

Functions:

 void v_directDraw32(int32_t xStart, 
int32_t yStart,
int32_t xEnd,
int32_t yEnd,
uint8_t brightness);

or

 void v_directDraw32Hinted(int32_t xStart, 
int32_t yStart,
int32_t xEnd,
int32_t yEnd,
uint8_t brightness,
int force);



Will draw a vector on screen with the given start and end coordinates.
Possible coordinates range from: -32768 to +32767 which is larger than the Vectrex screen. Visible area of a "normal" Vectrex is approximately -18000 to +18000 across and 24000 to -24000 down. Using these coordinates any X by X square should appear square on the display. This is different from the native Vectrex display coordinate system, where both axes are -128 to 127 and reflect the full width and height of the screen, so that an X by X square would be drawn as a 3 by 4 aspect ratio rectangle.

The given brightness is a "Vectrex" brightness in the range from 0 (no intensity) to 127 (brightest intensity). Negative values or values higher than 127 result in a vector not drawn!

Force

Can be set to a specific draw item and can be one of:
(flags, ORed)

 PL_BASE_NO_FORCE  (default)
PL_BASE_FORCE_ZERO
PL_BASE_FORCE_NO_ZERO
PL_BASE_FORCE_RESET_ZERO_REF
PL_BASE_FORCE_CALIBRATE_INTEGRATORS
PL_BASE_FORCE_STABLE_VECTOR
PL_BASE_FORCE_EMPTY
PL_BASE_FORCE_NOT_CLIPPED
PL_BASE_FORCE_STABLE
PL_BASE_FORCE_DEFLOK


PL_BASE_FORCE_ZERO
Before the line is drawn force a reset to zero and move to the starting location.

PL_BASE_FORCE_NO_ZERO
Ensures that no zero is done before the next draw (move to position is done).

PL_BASE_FORCE_DEFLOK
Ensures a DEFLOK before drawing this line. Over-come screen collapse circuitry. It has been necessary with
some games to add additional ‘DEFLOK’s to prevent long-term screen collapse.

PL_BASE_FORCE_RESET_ZERO_REF
Before the line is drawn forces a reset of the zero reference of the integrators. (0, ZERO - this ignores the set calibration value).

PL_BASE_FORCE_CALIBRATE_INTEGRATORS
Before the line is drawn forces a reset of the zero reference of the integrators to the currently set calibration value.

PL_BASE_FORCE_STABLE
This marks the vector as a "non-moving" vector. If possible it is therefore drawn at the beginning of the pipeline.

PL_BASE_FORCE_EMPTY
This vector will not be drawn (more an internal state).

PL_BASE_FORCE_NOT_CLIPPED
Vectors with this flag will not be clipped by the pipeline handling.
(User clipping will still be done.)

commonHints

All hints can be given via the above function (ORed) or as a "global" variable

  commonHints

The current contents of the variable "commonHints" is applied to all drawings.

v_printString

Function:

  void v_printString(int8_t x, int8_t y, char* string, uint8_t textSize, uint8_t brightness);

Will print the given string in a vector font on screen.

v_printStringRaster

Function:

  int v_printStringRaster(int8_t x, int8_t y, char* _string, int8_t xSize, int8_t ySize, unsigned char delimiter);

Will print the given string in a raster font on screen.

INPUT

As of now, only the Joystick position values in port one are available.
The functions:

  uint8_t v_readButtons();
void v_readJoystick1Digital();
or
void v_readJoystick1Analog();


Read input values from the vectrex, the result is held in global variables:

  uint8_t currentButtonState;
int8_t currentJoy1X;
int8_t currentJoy1Y;
(not yet supported: int8_t currentJoy2X; int8_t currentJoy2Y;)


currentButtonState
Bit values:

  • bit 0 - Joyport 1 button 1
  • bit 1 - Joyport 1 button 2
  • bit 2 - Joyport 1 button 3
  • bit 3 - Joyport 1 button 4
  • bit 5 - Joyport 2 button 1
  • bit 6 - Joyport 2 button 2
  • bit 7 - Joyport 2 button 3
  • bit 8 - Joyport 2 button 4


currentJoy1X - currentJoy1Y

  • digital values: -1, 0, +1
  • analog values: -128, 0, +127

SOUND

Accessing PSG is "expensive" (to set one register 6/8 (write/read) VIA access are necessary!)!

To "directly" access PSG registers use functions:

  void v_writePSG(uint8_t reg, uint8_t data)
uint8_t v_readPSG(uint8_t reg)


However since it is so "expansive" there is a buffer thru which all PSG acccess should be done. Use the buffer system, if all reads/writes are done using the buffer functions

  void v_writePSG_buffered(uint8_t reg, uint8_t data)
uint8_t v_readPSG_buffered(uint8_t reg)


we create two significant improvements:

  • all reads are done via the buffer (saves 8 VIA accesses)
  • only data is written to the PSG, that is not the same as in the buffer already (and thus in the PSG)


Even more however:
Also a double buffer is implemented!

This can be used if you e.g.:

  • play a YM file
  • and want to "overlay" distinct channels with a sound effect


The functions are:

  void v_writePSG_double_buffered(uint8_t reg, uint8_t data)
uint8_t v_readPSG_double_buffered(uint8_t reg)
void v_PSG_writeDoubleBuffer()


The read/write functions are more or less the same as the "buffered" ones, but the data is not written to VIA but written to another (double) buffer. So you can "overwrite" PSG data in the order you intend, and not actually put the data to the PSG directly.

When all PSG stuff is done in your "round" - than a call to "v_PSG_writeDoubleBuffer()" must be made, which transports the complete contents of double buffer to the PSG (with respect to already buffered data - meaning if the PSG already contains a value, that also is not overwritten!)

In conclusion:

  • use the double buffer functions!
  • once per round write the double buffer!


  void v_noSound();

Stops all sounds.

  void v_playDirectSampleAll(char *ymBufferLoad, int fsize, int rate);

Plays a sample from the buffer, the function is synchron and returns only after the sample is completely played!

  void v_doSound();

Write the doublebuffer contents to the PSG (when necessary).

  void v_playSFXCont(unsigned char *buffer, int channel, int loop);

Starts playing a AYFX from buffer in the given channel and enables looping (or not).
If the same sfx is already playing - nothing is done. If a different sfx is playing, that sfx is stopped and the new one played instead.

  void v_playSFXStop(unsigned char *buffer, int channel);

If the given sfx is playing in the given channel -> it is stopped.

  void v_playAllSFX();

Plays the next part of all current active sfx in all channels.
Playing is done into the doublebuffer.
(This just calls all the "single" play_sfx#() )

  void v_initYM(unsigned char *ymBuffer, uint16_t length, int loop);

Initiate the given YM (in buffer) and prepare playing.
The buffer is expected to be filled with completely unpacked and preprocessed YM data. An example how to "prepare" a ym from the filesystem can be seen in the "PitrexLoader".

  int v_playYM();

Plays the next part of the current active ym in all channels.
Playing is done into the doublebuffer.

  void v_writePSG_double_buffered(uint8_t reg, uint8_t data);

Write a "single" register of the PSG (to the double buffer).

  void v_writePSG_buffered(uint8_t reg, uint8_t data);

Write a "single" register of the PSG (to the buffer).

  void v_writePSG(uint8_t reg, uint8_t data);

Write a "single" register of the PSG (directly).

  uint8_t v_readPSG_double_buffered(uint8_t reg);
uint8_t v_readPSG_buffered(uint8_t reg);
uint8_t v_readPSG(uint8_t reg);

As above.

  int play_sfx1();
int play_sfx2();
int play_sfx3();

Plays the next part of all current active sfx in the respective channels.
Playing is done into the doublebuffer.

  void v_PSG_writeDoubleBuffer();

Write the doublebuffer contents to the PSG (when necessary).
(This is also called from v_doSound())

AYFX
https://shiru.untergrund.net/software.shtml outlink
https://www.youtube.com/watch?v=XI6aW2QSUXw outlink

configuration "online"

Is called from v_WaitRecal. Buttons are only recognized if button status is polled at some stage (v_WaitRecal does NOT poll any inputs!)

Enter Configuration Menu by pressing button 2+3 port 1 at the same time.

If debug enabled:
Press Button 3+4 at the same time gives speed measurements to UART/command line.

Baremetal:
Reset to the load menu: press all buttons port 1 at the same time.

  TODO ...

DEBUGING

v_WaitRecal does not execute the input methods!

 TODO ...



Baremetal vs Raspbian

TODO ...

  File access
exit()
possibly types / strings /IO

getParameter
Todo - make it so, that main() is called with this as standard args!

Examples

Basic Drawing:

 /* Draws a box and prints text on the Vectrex screen using the vectrexInterface library. */
#include <pitrex/pitrexio-gpio.h>
#include <vectrex/vectrexInterface.h>
void startFrame()
{
v_WaitRecal();
//v_doSound();
v_setBrightness(64); /* set intensity of vector beam... */
v_readButtons();
v_readJoystick1Analog();
//v_playAllSFX();
}
int main(int argc, char **argv) {
vectrexinit(1);
v_init();
usePipeline = 1;
v_setRefresh(60);
for (;;) {
startFrame();
v_directDraw32(-10000, -10000, -10000, 10000, 64);
v_directDraw32(-10000, 10000, 10000, 10000, 64);
v_directDraw32( 10000, 10000, 10000, -10000, 64);
v_directDraw32( 10000, -10000, -10000, -10000, 64);
v_printString(-10,0, "HELLO", 7, 64);
v_printStringRaster(-8,-4, "WORLD", 5*8, -7, '');
}
return 0;
}


Here is a slightly more complex version which allows the use of a user coordinate space. This may move to the vectrex library later.

 #include <stdio.h>
#include <pitrex/pitrexio-gpio.h>
#include <vectrex/vectrexInterface.h>
#ifndef TRUE
#define TRUE (0==0)
#define FALSE (!TRUE)
#endif
void startFrame()
{
v_WaitRecal();
//v_doSound();
v_setBrightness(64); /* set intensity of vector beam... */
v_readButtons();
v_readJoystick1Analog();
//v_playAllSFX();
}
static int64_t ScaleXMul=1LL, ScaleXDiv=1LL, ScaleXOffsetPre=0LL, ScaleXOffsetPost=0LL,
ScaleYMul=1LL, ScaleYDiv=1LL, ScaleYOffsetPre=0LL, ScaleYOffsetPost=0LL;
int tx(int x) { // convert x from window to viewport
return (int)(((((int64_t)x)+ScaleXOffsetPre)*ScaleXMul)/ScaleXDiv + ScaleXOffsetPost);
}
int ty(int y) { // and y
return (int)(((((int64_t)y)+ScaleYOffsetPre)*ScaleYMul)/ScaleYDiv + ScaleYOffsetPost);
}
void window(int xl, int yb, int xr, int yt) {
// Does not yet support flip/rotate
// We will use normalised viewport coordinates of x: -18000 : 18000 and y: -24000 : 24000
int64_t width, height;
int xc, yc;
int oxl = xl, oyb = yb, oxr = xr, oyt = yt;
width = (int64_t)xr-(int64_t)xl;
height = (int64_t)yt-(int64_t)yb;
if (width*4 > height*3) {
// window is wider than aspect ratio, so we will have black bars at the top and bottom
height = (width * 4) / 3;
yc = (yb+yt)/2;
yb = yc - height/2;
yt = yc + height/2;
} else if (width*4 < height*3) {
// window is taller than aspect ratio, so we will have black bars at the sides
width = (height*3) / 4;
xc = (xl+xr)/2;
xl = xc - width/2;
xr = xc + width/2;
}
ScaleXMul = 36000LL; ScaleXDiv = width; ScaleXOffsetPre = -width/2LL; ScaleXOffsetPost = 0LL; ScaleXOffsetPost = (tx(xr) - tx(oxr)) / 2LL;
ScaleYMul = 48000LL; ScaleYDiv = height; ScaleYOffsetPre = -height/2LL; ScaleYOffsetPost = 0LL; ScaleYOffsetPost = (ty(yt) - ty(oyt)) / 2LL;
// setCustomClipping(TRUE, tx(oxl), ty(oyb), tx(oxr), ty(oyt)); // transform world (window) coordinates to viewport (normalised device coordinates) before
// clipping. That way clipping code does not need to know about world coordinates.
// not implemented as I don't have the updated library yet
}
void line(int xl, int yb, int xr, int yt, int col) {
//fprintf(stdout, "line(%d,%d, %d,%d, %d);n", tx(xl),ty(yb), tx(xr),ty(yt), col);
v_directDraw32(tx(xl),ty(yb), tx(xr),ty(yt), col);
}
int main(int argc, char **argv) {
vectrexinit(1);
v_init();
usePipeline = 1;
v_setRefresh(60);
window(0,0, 360,480); // both scaled and offset...
for (;;) {
startFrame();
line( 0, 0, 0, 480, 96);
line( 0, 480, 360, 480, 96);
line(360, 480, 360, 0, 96);
line(360, 0, 0, 0, 96);
line( 80, 140, 80, 340, 64);
line( 80, 340, 280, 340, 64);
line( 280, 340, 280, 140, 64);
line( 280, 140, 80, 140, 64);
v_printString(-10,0, "HELLO", 7, 64);
v_printStringRaster(-8,-4, "WORLD", 5*8, -7, '');
}
return 0;
}