BEYOND SDK
April 2015.

Intro

The SDK designed to extend functionality ot BEYOND software. The SDK can not be used without BEYOND. This SDK is not for access to Pangolin hardware without Pangolin software. If you need only the hardware, then stop reading this file.

Why we keep access to Pangolin hardware closed. There are multiple reasons. One of this is design of new models. With some period of time we rework internals because of new ideas, new methods. Sometimes the change is radical, and keep a compatibility with old methods is problematic. With closed interface we have a freedom to change it any time we need. If the interface become open, then we partially lose this freedom. We count on your understanding.

What this SDK give me? Everything in BEYOND can be separated on two groups. The first group is "Image". Image generate points. As example - frames, text, abstract, clocks, they are Images. The second group of "Effect". Effect take points generated by Image, and modify them, producing output points. The Image has only "out", the effect has "in" and "out".  The SDK give you ability to stream the frames into special Image inside BEYOND.

BEYOND has special "SDK Image". DLL communicate with images. Right now for communication we use WM_COPYDATA and a couple of simple messages. At BEYOND side there is special window that receive messages generated by you (inside DLL) and does corresponding actions. In addition to that, each SDK Image create own window. All SDK Image use same class, and personal window name. We use FindWindow() call for search. After that, functions use SendMessage().

SDK Image

The image receive the frame from you over WM_COPYDAYA and keep it inside itself. Everytime as BEYOND calculate the next laser output, then it comes to SDK Image and take a copy of your frame. This part of code is pretty short and protected by critical section. By the nature, SDK Image is a buffer between your data and BEYOND calculation core. There are a few ways how SDK Image can be used.


SDK Image inside Cue.

Cue is a container of various types of Images. SDK Image work same as any other Image types. You place it into the Cue and after that you may use it inside Grid and inside Timeline. All rules for Image inside the Cue should be applicable to SDK Image too. The only what you need to understand. Each SDK Window has own communication window (see CreateWindow()) with a window name, and this name used for FindWindow(). Do not create duplicates of this Image. If you use it inside Timeline - keep it inside Cue List. In this case the timeline will not create a copies of this SDK Image.

SDK Image inside Projection Zone.

This very untypical way of how Image can be used. In fact, we simply reuse the functionality of SDK Image - a buffer. You have 3 commands. Create Image inside Zone, Send frame to this Image, and delete the image. Yes, it is simple as that, only 3 commands. When you create the image inside Zone, you specify zone index, and name of image (window name). Use some specific name to avoid name conflicts with other applications. Each Projection zone has list for such Images. You may add more than one. Other applications can do that too. BEYOND core unite output from all parts of BEYOND and send to the hardware.

BEYOND process SDK Images before "Also-To" (see in Projection Zones dilaog). Same as other frames, the output from SDK images will be modified by geometric correction, BAM and so on.


SDK Image inside Projector.

The third, and final place where you can stream is Projector. At first BEYOND calculate Cues, later it calculate Zones, and at final place it apply Projector settings. You have ability to insert your stream ignoring all zone settings, projector settings....except color and color shift. But, you definitely skip window clipping, vector to point transformation, zone related settings and multiple projector related settings.

Quick resume.

This model give you ability to insert your stream in very early stage (as Cue/Image), in the middle at Zone level, and also at the most final place - Projector phase.  

 
API

General functions

function ldbCreate:integer;
int ldbCreate();
Function initialize a few internal DLL such as events ( CreateEven() ) that you can use later inside WaitForSingleObject() function. Call ldCreate once, before you will use other functions. If function successful then result is 1. Otherwise 0.


function ldbDestroy:integer;
int ldbDestroy();
Function release allocated resources. Use it once at the end use.

function ldGetDllVersion:integer;
int ldGetDllVersion();
Function return version of DLL. This is a constant that will be changed if something in DLL will be changed. Current value is 100.

function ldbBeyondExeStarted:integer;
int ldbBeyondExeStarted();
Function return 1 if BEYOND.EXE started the execution. This is a detection of very early phase of  execution. After that BEYOND load workspace, settings files, start hardware and so on. The function confirm that execution started. See below

function ldbBeyondExeReady:integer;
int ldbBeyondExeReady();
The function return 1 of BEYOND SDK ready to be used. The SDK class created at very end of startup process what guarantee that all systems started and initialized. If you get result of 1, then go ahead and use other function. Otherwise, check ldbBeyondExeStarted and if BEYOND is not started then you need to start BEYOND.EXE.Otherwise, wait for "ready" state.

function ldbGetBeyondVersion:integer;
int ldbGetBeyondVersion();
Function return build number of BEYOND. If BEYOND is not started, then result is zero. Build number is one integer value, currently it is 712. If you need more detailed info, then use
GetFileVersionInfo from Win API.
This function work pretty fast, one SendMessage(), and you may use it for detection of started and ready for use BEYOND.

function ldbEnableLaserOutput:integer;
int ldbEnableLaserOutput();
Function enable laser output, what is equal to click on Enable Laser Output button. If the output already enabled the function does nothing. In case of successful execution result of function is 1, otherwise 0.

function ldbDisableLaserOutput:integer;
int ldbDisableLaserOutput();
Function disable laser output, what is equal to click on Disable Laser Output button. If the output already disbled the function does nothing. In case of successful execution result of function is 1, otherwise 0.

function ldbBlackout:integer;
int ldbBlackout();
Function does a click on Blackout button of the main toolbar. The blackout stop all players as well as clear SDK images output.

function ldbGetProjectorCount:integer;
int ldbGetProjectorCount();
Function return number of Projectors in BEYOND. If zero then BEYOND is not active;

function ldbGetZoneCount:integer;
int ldbGetZoneCount();
Function return number of Projection Zones in BEYOND. If zero then BEYOND is not active;

function ldGetProjectorEvent(AIndex:integer):integer;
int ldGetProjectorEvent(int AIndex);
DLL create named Event (see CreateEvent() in MSDN) for each Projector. BEYOND create events with same names too. At the end of calculation for the projector BEYOND does PulseEvent. From your side you may use WaitForSingleObject or WaitForMultipleObjects with event handle. Function return Event handle (THandle). Argument of function is index of Projector (counting from 0);

Create and delete Image

function ldbCreateZoneImage(AZoneIndex:integer; AImageName:PChar):integer;
int ldbCreateZoneImage(int AZoneIndex; pchar AImageName:PChar);
Function create new SDK Image and put it into image-list of corresponding Projection Zone.
AZoneIndex - Projection Zones index. Counting from 0. If more or less - operation will be ignored.
If the Image already exists - operation will be ignored. BEYOND use AImageName, check list of all existing SDK Images inside Projection zones, and if the Image with such name found, then BEYOND ignore this call
AImageName- zero terminated, ansii string, PChar, non unicode. Define a name of window, and this name will be used for identification of your Image. See ldSendFrameToImage(), the first parameter is AImageName.


function ldbCreateProjectorImage(AProjIndex:integer; AImageName:PChar):integer;
int ldbCreateProjectorImage(int AProjIndex; pchar AImageName);
Function create new SDK Image and put it into image-list of corresponding Projector.
AProjIndex - Projector index. counting from 0. If more or less - operation will be ignored.
If the Image already exists - operation will be ignored. BEYOND use AImageName, check list of all existing SDK Images inside Projector list, and if the Image with such name found, then BEYOND ignore this call.
AImageName- zero terminated, ansii string, PChar, non unicode. Define a name of window, and this name will be used for identification of your Image. See ldSendFrameToImage(), the first parameter is AImageName.


function ldbDeleteZoneImage(AImageName:PChar):integer;
int ldbDeleteZoneImage(pchar AImageName);
Function delete SDK Image from Projection Zone image-list. Name of Image defined in zero terminated AImageName string.


function ldbDeleteProjectorImage(AImageName:PChar):integer;
int ldbDeleteProjectorImage(pchar AImageName);
Function delete SDK Image from Projector image-list. Name of Image defined in zero terminated AImageName string.


Send frame to BEYOND

function ldbSendFrameToImage(AImageName:PChar; ACount:integer; AFrame:pointer; AZones:pointer; ARate:integer):integer;
int ldbSendFrameToImage(pchar AImageName; int ACount; void*AFrame; void*AZones; int ARate);


Function deliver frame to corresponding SDK Image in BEYOND. This function work with SDK Image independently on the type.
AImageName - zero terminated string, PChar. This is a name of Image. You define the name in ldCreate.. functions or when you create SDK Image in BEYOND Cue.
ACount - number of points in the frame. Maximum 8192 points. This is max size of internal BEYOND buffer for a frame. Do not exceed.
AFrame - pointer on array with points. Details below.
AZones - pointed on array with Zone information. Details below.
ARate - Scan rate or sample rate.  Scan rate is a relative value, percents of defalt sample rate. 100 means 100% of default projector sample rate. If value of ARate is negative, this this is sample rate. Sorry, a bit tricky, the idea is use one variable for two possible options. So, of value is negative then it is sample rate. If you want 30K, then value should be -30000 (minus thirty k). Sample rate means - points per second.

Under the hood it work this way. DLL has global variable - a structure. Inside it a field for header, after zone array, and then points array. This static struct used for WM_COPYDATA. The function use critical section - code of function is inside critical section.

Point format:

  TSdkImagePoint=packed record
    X,Y,Z       :single;  // 32bit float point, Coordinate system -32K to +32K
    Color       :integer; // RGB in Windows style
    RepCount    :byte;    // Repeat counter
    Focus       :byte;    // Beam brush reserved, leave it zero
    Status      :byte;    // bitmask - attributes
    Zero        :byte;    // Leave it zero
  end;

typedef struct
{
    float X,Y,Z; // 32bit float point, Coordinate system -32K to +32K
    int Color; // RGB in Windows style
    BYTE RepCount;    // Repeat counter
    BYTE Focus;   // Beam brush reserved, leave it zero
    BYTE Status;    // bitmask - attributes
    BYTE Zero;    // Leave it zero
} TSdkImagePoint;


X,Y,Z - 32 bit float point value. Standard "single". The coordinate system is -32K...+32K. Please fit your data in the range.
Color - 32 bit integer number. Color is 24bit RGB, standard encoding in windows format. Red bytes comes low (00..FF), Green after that, Blue the most signification. It exactly as in GDI.
RepCount -  usigned byte. Repeat counter of the point. 0 - no repeats. 1 - one repeat and so on. - usigned byte.
Focus - usigned byte. Now it unused
Status - flags, now eave it zero.
Zero - usigned byte. leave it zero.

You need have array with points and supply pointed on this array into ldSendFrameToImage.

Zone data.

This part is a bit tricky. BEYOND may have up to 200 projection zones. Sometimes one frame goes to one zones, but may go to many, so, we need a way to address many zones. LD2000 SDK use a bit mask, but we do not fit into 32 bit or even 64 bit. Second detail - order of zones is also important, because of time-shift functions, where order of zones used for time shift calculation. The solution is this. Declare array of 256 unsigned bytes, and it will contain a sequence of zone indexes. Counting from 1. Value 0 is a termination, like in strings. As example if you need send frame to zones one, two and five, then the array must have values 1,2,5,0. You should supply a pointer on this array as argument of ldSendFrameToImage.

Quick resume.

ldbSendFrameToImage use a few pointers - Image name, point array, and zone indexes. The scanrate / sample rate is a simple number, nothing special. ACount is number of points inside points array. Code inside function use WM_COPYDATA. Seems like work relatively fast, ~2500 times per second in a dead loop on MacBook Pro under Win 7, x64. The speed may variate, in the past I had much lower results.

Timecode

We have a couple of functions that allow you read and write the timecode of BEYOND form the place where timecode goes in and out. BEYOND work with  multiple input types. All input device types, at the end of code flow, call the same function that deliver to Timeline, Grid or Play list. Two functions defined below allow write timecode to BEYOND, same as it comes from TC2000, MIDI or ArtNet. Or, you can read the last value of incoming timecode.

function ldbGetTimeCode:integer;
int ldbGetTimeCode();

BEYOND support multiple types of timecode - from MIDI, from SMPTE, and so on. When the time code value comes the last value stored in local variable. Function ldGetTimeCode return value of this variable. Value measured in milliseconds.

function ldbSetTimeCode(AValueMS:integer):integer;
int ldbSetTimeCode(int AValueMS);
BEYOND support multiple types of timecode - from MIDI, from SMPTE, and so on. Time information may come from different places but to same point. Function ldSetTimeCode() supply time information to the same place as any other time code sources. Value in milliseconds. Result value must be equal to value you supply as argument.


MIDI

function ldbSetMidiIn(ACmd, AData1, AData2, ADevIndex:byte):integer;
int ldbSetMidiIn(BYTE ACmd, AData1, AData2, ADevIndex);

Function allow write MIDI message into BEYOND same as it would come from real MIDI IN device. So, this is simulation of MIDI IN.
ACmd - MIDI command
AData1, AData2 - MIDI command parameters
ADevIndex - device pair index. BEYOND has four MIDI IN/OUT pairs. ADevIndex parameter define index of the pair. Value in range of 0 and 3.

function ldbSetMidiOut(ACmd, AData1, AData2, ADevIndex:byte):integer;
int ldbSetMidiOut(BYTE ACmd, AData1, AData2, ADevIndex);

Function allow to send MIDI OUT message. Technically, BEYDON receive your message and immediately call send to MIDI out command. This function might help if you need to send MIDI OUT message to device that already in use by BEYOND
ACmd - MIDI command
AData1, AData2 - MIDI command parameters
ADevIndex - device pair index. BEYOND has four MIDI IN/OUT pairs. ADevIndex parameter define index of the pair. Value in range of 0 and 3.

Kinect

function ldbSetKinect(AIndex:integer; AData:pointer):integer;
int ldbSetKinect(int AIndex; void* AData);

Function allow supply the Skeleton data into BEYOND. BEYOND can use two types of data from Kinect controller - skeleton node coordinates and the raster image for tracing. This function allow supply skeleton information same as it would come from the controller.
AIndex - index of skeleton. BEYOND support 2. Index of skeleton must be 0 or 1
AData - pointer on array of 20 skeleton node coordinates structures. Array must contain 20 elements (nodes). The node definition

  TKPoint=packed record
    X,Y,Z:single;
    Active:boolean;
  end;

typedef struct
{
    float X,Y,Z;
    BYTE Active; // value must be 0 or 1. It will be translated into Delphi  boolean
} TKPoint;


X,Y,Z are coorinates of the node. Value in meters, 1 means one meter.
Active - 1 if the node is active, and 0 if node inactive.


DMX

function ldbSetDmx(AIndex:integer; AData:pointer):integer;
int ldbSetDmx(int AIndex; void* AData);

Function supply one DMX array of 512 values into input buffer of BEYOND. This is emulation of DMX IN.
AIndex - index of the input, value must be in range of 0 and 3.
AData - pointer on array of 512 bytes. Do not supply less than 512, it will cause an incorrect data in the incoming buffer.

Channels

function ldbSetChannels(AChannels:pointer; ACount:integer):integer;
int ldbSetChannels(void* AChannels; int ACount);

Function allow control up to channels of BEYOND.
AChannels - pointer on array of Single.
ACount - number of channels (number of elements in the array), must be between 1 and 255.

The Channel is array of normalized variables, value must be in range of 0 and 1. Maximum channel number is 255. AData point on the beginning of array with channel values. If you do not want to supply value of some channel, the set the value of corresponding item of array to -1.


Timeline editor.

There are a few calls that allow to communicate with the timeline editor of BEYOND.

function ldbTimelinePlay:integer;
int ldbTimelinePlay();
Start playback of current show in the timeline editor. If timeline already in play mode then no action

function ldbTimelineStop:integer;
int ldbTimelineStop();

Stop playback of current show in the timeline editor. If timeline already in stop/pause mode then no action

function ldbTimelineGetPlaying:integer;
int ldbTimelineGetPlaying();
Function return 1 of timeline is on play mode. Otherwise 0.

function ldbTimelineSetPos(ATime:integer):integer;
int ldbTimelineSetPos(int ATime);
Set the timeline position.
ATime - time in milliseconds.

function ldbTimelineGetPos:integer;
int ldbTimelineGetPos();
Function return current timeline position. Value in milliseconds. Function can be used in playback as well as in edit/pause mode.

function ldbTimelineGetDuration:integer;
int ldbTimelineGetDuration();
Function return duration of timeline/show. Value in milliseconds.

function ldbTimelineSetOnline(AEnabled:integer):integer;
int ldbTimelineSetOnline(int AEnabled);
Function control timeline editor state. AEnabled equal to 1 means enable output (online mode). Value 0 means offline mode (no laser output)

function ldbTimelineGetOnline:integer;
int ldbTimelineGetOnline();
Function return current state of timeline editor. Result value 1 means that laser output enabled.