Corman Lisp FFI Howto

June 7th, 2009

Update: 15 December 2003: Version 0.2
Update: 11 December 2003: Version 0.1

1) Scope
This document shall demonstrate the use of the Corman Common Lisp FFI. Specific examples demonstrate the use of SDL from Corman Common Lisp. As such, this document is not a general SDL tutorial, not is it to be considered a primer for learning Lisp.

2) Introduction
This document will describe how Lisp code interfaces with Dynamic Link Libraries (DLL’s) using the Corman Common Lisp FFI. The FFI bindings to SDL are used in many of the examples. This document makes use of an existing example contained in the SDL documentation, Using OpenGL With SDL.

Using the above example, the main components of the SDL library are described in Lisp: initialization, configuration, event handling, drawing to the screen, and exiting. Several Lisp ‘macros’ are later described in an attempt to show some of the advantages of developing using Lisp as opposed to, say Java or C++. The section on macros is TODO

See Section 6 for a list of references and resources for learning more about SDL and Lisp.

3) Basics of the Corman Lisp FFI
This section contains a brief introduction on the use of the Corman Lisp FFI. The FFI is used to call C style functions from Dynamic Link Libraries (DLL’s). The most common operations that a programmer may encounter when interfacing to a DLL are covered, including callbacks, creating and referencing arrays and structures, using pointers and managing memory.

The Corman Lisp manual describes how to create FFI definitions. This section assumes that the reader is using existing FFI definitions and wishes to make use of the functions defined therein. Advanced topics, such as interfacing to COM objects are not covered. Please see the Corman Lisp manual for more information on how to use COM objects.

3.1) Includes
FFI definitions are usually contained in a file, similar to C header files. And like C, these definitions must be included before use. In Lisp, require is used for this purpose.


C
#include “SDL/SDL.h”
#include “GL/gl.h”


Lisp
(require ’sdl)
(require ‘opengl)

3.2) Variables: C Datatypes
Lisp variables may be passed to or received from C style functions that take any of the standard C datatypes as parameters. C datatypes include byte/char, short, int, float, and double. Corman Lisp transparently takes care of much of the necessary casting and conversion between Lisp and C variables.


C
int width = 0;
int height = 0;


Lisp
(setf width 0)
(setf height 0)

3.3) Variables: C Arrays and Structures
Things begin to get interesting when structures and arrays are used. Pure Lisp arrays are not equivalent to C arrays and therefore it is not possible to pass a Lisp array to a C function. If a C function takes an array a C style array must be created from within Lisp.


C
static GLfloat v0[] = { -1.0f, -1.0f,  1.0f };

When using C, arrays may be created on the stack as shown in the C code above. When a C array is created in Lisp, it is always created on the heap - meaning it must be created using malloc. Because Lisp is garbage collected there is no need to explicitly free a variable created using malloc. The garbage collector will automatically free the memory for v0 when the variable goes out of scope. Although there is nothing stopping the reader from explicitly freeing a variable using the function free.


Lisp
(setf v0 (ct:malloc (ct:sizeof ‘(:single-float 3))))

A C array may be initialized when it is created. In Lisp because C arrays are created on the heap, assignment must take place as a separate step.


Lisp
(setf (ct:cref (:single-float 3) v0 0) -1.0)
(setf (ct:cref (:single-float 3) v0 1) -1.0)
(setf (ct:cref (:single-float 3) v0 2) 1.0)

cref (the ct: prefix identifies the package that contains cref) is used for accessing a C style array, or structure. Therefore, looking at the statements above:


ct:cref - accessing an array or structure.
(:single-float 3) - in this case, it is an array of 3 floats.
v0 - the name of the array we previously created using malloc.
0 - The index into the array

The Lisp implementation seems backward compared to the C-style the reader is probably used to, e.g v0[0] = -1. But that’s how it is done in CCL. It is possible to improve upon this using macros

The previous code described assignment. Retrieving a value is much the same, but without the setf.


Lisp
(ct:cref (:single-float 3) v0 0)

-1.0

The code for accessing a C struct is described below.


C
typedef struct {
	Sint16 x;
	Sint16 y;
	Uint16 w;
	Uint16 h;
} SDL_Rect;


Lisp
(setf a-rectangle (ct:malloc (ct:sizeof ’sdl:SDL_Rect)))

(setf (ct:cref sdl:SDL_Rect a-rectangle sdl::x) 120)
(setf (ct:cref sdl:SDL_Rect a-rectangle sdl::x) 200)
(setf (ct:cref sdl:SDL_Rect a-rectangle sdl::w) 64)
(setf (ct:cref sdl:SDL_Rect a-rectangle sdl::h) 64)

The macro with-c-struct makes working with structs a lot easier, as shown below:


(with-c-struct (x rectangle sdl:SDL_Rect)
    (setf
          sdl::x 120
          sdl::y 200
          sdl::w 64
          sdl::h 64))

An important consideration is that the Lisp sizeof function can only be passed defined types, for example sdl:SDL_Rect. Calling sizeof on a variable such as a-rectangle will fail.

3.4) Functions
Calling C style functions in Lisp is straightforward.


C
SDL_Init( SDL_INIT_VIDEO );
SDL_GL_SetAttribute( SDL_GL_RED_SIZE, 5 );


Lisp
(SDL_Init SDL_INIT_VIDEO)
(SDL_GL_SetAttribute SDL_GL_RED_SIZE 5)

3.5) Callbacks
defun-c-callback is used to create a function callback in Lisp. As an example, the C prototype for the SDL function SDL_AddTimer is shown below. SDL_AddTimer takes a function as one of its parameters and calls that function at the specified interval.


C callback function prototype
typedef Uint32 (*SDL_NewTimerCallback)(Uint32 interval, void *param);

Lisp callback function
(ct:defun-c-callback timer-callback ((interval SDL:Uint32) (param (:void *)))
    (fformat “Yup, timer Fired”)
    (values interval))


C SDL_AddTimer prototype definition
SDL_TimerID SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param);

Lisp code using a callback
(setf param (ct:malloc (ct:sizeof :LONG)))

(setf a-timer-1
    (SDL:SDL_AddTimer 10000 (ct:get-callback-procinst ‘timer-callback) param))

3.6) Pointers
All C objects are referenced from Lisp using pointers. For example in Section 3.3, v0 is a foreign pointer that points to an array of 3 floats. The function create-foreign-ptr is used to create a foreign pointer. A foreign pointer is similar to void * in C and can be cast (or assigned) to any object. The function cpointer value returns the address of an object referenced by the foreign pointer. It is therefore possible to assign the pointer to the address of any object using setf. An example is described below. Values returned from Corman Lisp are in bold.


(setf f-pointer (ct:create-foreign-ptr))
#< FOREIGN PTR: #x0 >
(setf var-1 (ct:malloc (ct:sizeof :LONG)))
#< FOREIGN HEAP PTR: #xA63F78, length = 4 bytes >
(setf (ct:cref (:long *) var-1 0) 100)
100
(ct:cpointer-value f-pointer)
0
(ct:cpointer-value var-1)
10895224
(setf (ct:cpointer-value f-pointer) (ct:cpointer-value var-1))
(ct:cpointer-value f-pointer)
10895224
(ct:cref (:long *) f-pointer 0)
100
var-1
#< FOREIGN HEAP PTR: #xA63F78, length = 4 bytes >
f-pointer
#< FOREIGN PTR: #xA63F78 >

3.7) Defining types
The function defctype is similar to the C typedef. It allows one to create new types and reference them in Lisp code, greatly improving readability. The following code modifies the example in Section 3.3 and creates a new type called vertex-arrayf which is defined as an array of three floats.


(ct:defctype vertex-array (:single-float 3))

(setf v0 (ct:malloc (ct:sizeof 'vertex-arrayf)))

(setf (ct:cref vertex-array v0 0) -1.0)

3.8) Coerce
In C, 10 / 3 equals 0.3333333… In Lisp, 10 / 3 equals 10/3, allowing 10/3 * 3 to return 10 while in C the result is 0.999999.
This becomes problematic when calculations are performed in Lisp and the result must be passed to a C style function. coerce is used to force an object to a specific type. The following code gives an example where coerce is required.


The result of width / height is forced to the type ‘double-float
(setf ratio (coerce (/ width height) ‘double-float))

(gluPerspective 60.0d0 ratio 1.0d0 1024.0d0))

3.8) C Unions
The current version of Corman Lisp absolutely does not support unions. So the following is not possible:


C
typedef union {
	Uint8 type;
	SDL_ActiveEvent active;
	SDL_KeyboardEvent key;
	SDL_MouseMotionEvent motion;
	SDL_MouseButtonEvent button;
	SDL_JoyAxisEvent jaxis;
	SDL_JoyBallEvent jball;
	SDL_JoyHatEvent jhat;
	SDL_JoyButtonEvent jbutton;
	SDL_ResizeEvent resize;
	SDL_ExposeEvent expose;
	SDL_QuitEvent quit;
	SDL_UserEvent user;
	SDL_SysWMEvent syswm;
} SDL_Event;

This is not as bad as it seems as it is possible to ‘fake’ a union in Lisp by replacing it within a struct. So the C struct equivalent of the previous code, as currently defined in the FFI, looks something like this.


The C code as defined in the Lisp SDL FFI
typedef struct {
    Uint8 type;
    Uint8 buffer[1023];
} SDL_Event;

Here, buffer is sized to be large enough to handle the largest variable in the union. See Section 4.4 for an example of how a union is handled

4) Interfacing to SDL
4.1) Initialization
Here is some standard code to initialize the SDL subsystems. The Lisp equivalent can be seen below. Notice that the C code uses an if construct whereas the Lisp code uses when for which there is no C equivalent. The reason for this is a matter of style and readability. Lisp does have an if construct, but in Lisp an if implies that there is also an associated else. In this case there is no else, therefore as a matter of style, when is used. when implies a single branch, if implies two possible branches.


C
if( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
   /* Failed, exit. */
   fprintf( stderr, "Video initialization failed: %sn",
        SDL_GetError( ) );
   quit_tutorial( 1 );
}


Lisp
(when (< 0 (SDL_Init SDL_INIT_VIDEO))
      (fformat "Video initialization failed: ~A " (SDL_GetError))
      (setf quit t))

4.2) Setting the video mode
Many functions in SDL take parameters that are set using a bitwise | (or). The
Lisp equivalent is logior.


C
flags = SDL_OPENGL | SDL_RESIZABLE;
SDL_SetVideoMode( width, height, bpp, flags );


Lisp
(setf flags (logior SDL_OPENGL SDL_RESIZABLE))
(SDL_SetVideoMode width height bpp flags)

4.3) The Main Loop
Games usually make use of a single loop that checks for events before executing the game logic and redrawing the screen. Here is the C and equivalent Lisp code for this main loop.


C
while( 1 ) {
    /* Process incoming events. */
    process_events( );
    /* Draw the screen. */
    draw_screen( );
}


Lisp
(do ()
    ((eql *quit* t))
	; Process incoming events.
    (process-events)
	; Draw the screen.
    (draw-screen))

There are several loops available in Lisp (loop, for, do, dotimes, dolist), and many more can be created using macros (e.g. while).

4.4) Processing Events
As described in Section 3.8, the current version of Corman Lisp does not support unions. This poses a problem in that events are returned from SDL_PollEvent in the form of an all-encompassing union.


C
typedef union {
	Uint8 type;
	SDL_ActiveEvent active;
	SDL_KeyboardEvent key;
	SDL_MouseMotionEvent motion;
	SDL_MouseButtonEvent button;
	SDL_JoyAxisEvent jaxis;
	SDL_JoyBallEvent jball;
	SDL_JoyHatEvent jhat;
	SDL_JoyButtonEvent jbutton;
	SDL_ResizeEvent resize;
	SDL_ExposeEvent expose;
	SDL_QuitEvent quit;
	SDL_UserEvent user;
	SDL_SysWMEvent syswm;
} SDL_Event;

Examining each struct within the union we see that Uint8 type is used as the type identifier.


typedef struct {
	Uint8 type;
	Uint8 gain;
	Uint8 state;
} SDL_ActiveEvent;

typedef struct {
	Uint8 type;
	Uint8 which;
	Uint8 state;
	SDL_keysym keysym;
} SDL_KeyboardEvent;

Therefore we are able to replace the union with the following struct in the FFI. This is transparent to the programmer.


The C code as defined in the Lisp SDL FFI
typedef struct {
    Uint8 type;
    Uint8 buffer[1023];
} SDL_Event;

All that remains is to check the type and ‘cast’ to the appropriate struct. In C checking the type is something that must be done anyway. In Lisp, ‘cast’ means referring to one type as another using cref. So for example, ‘casting’ from SDL_Event to SDL_KeyboardEvent is as simple as…


(ct:cref sdl:SDL_Event sdl-event sdl::type)

(ct:cref sdl:SDL_KeyboardEvent sdl-event sdl::type)

The following code describes the conversion to Lisp from C style event processing.


C
static void process_events( void )
{
    SDL_Event event;

    /* Grab all the events off the queue. */
    while( SDL_PollEvent( &event ) ) {

        switch( event.type ) {
        case SDL_KEYDOWN:
            /* Handle key presses. */
            handle_key_down( &event.key.keysym );
            break;
        case SDL_QUIT:
            /* Handle quit requests (like Ctrl-c). */
            quit_tutorial( 0 );
            break;
        }
    }
}


Lisp
(defun process-events ()
    (let ((sdl-event (ct:malloc (ct:sizeof ’sdl:SDL_Event))))
    ; Grab all the events off the queue.
    (do ((poll-event 1 (sdl:SDL_PollEvent sdl-event)))
        ((eql poll-event 0) ‘done)
        (cond
            Here we check the type
            ((eql sdl:SDL_KEYDOWN (ct:cref sdl:SDL_Event sdl-event sdl::type))
                ; Handle key presses.
                And then reference it as SDL_KeyboardEvent. Fortunately Lisp doesn’t care what we do.
                (handle-key-down (ct:cref sdl:SDL_KeyboardEvent sdl-event sdl::keysym)))
            ((eql sdl:SDL_QUIT (ct:cref sdl:SDL_Event sdl-event sdl::type))
                ; Handle quit requests (like Ctrl-c)
                (setf *quit* t))))))

The FFI forces us to create the sdl-event variable on the heap using malloc. But because Lisp is garbage collected there is no need to use free, although there is nothing stopping the reader from explicitly freeing a variable using free. In the Lisp code above, the sdl-event variable has scope only within the function process-events. Therefore the garbage collector will automatically free the memory for sdl-event when it goes out of scope.

4.5) Handling Events
The following example describes how to handle key presses.


C
static void handle_key_down( SDL_keysym* keysym )
{
    switch( keysym->sym ) {
    case SDLK_ESCAPE:
        quit_tutorial( 0 );
        break;
    case SDLK_SPACE:
        should_rotate = !should_rotate;
        break;
    default:
        break;
    }
}


Lisp
(defun handle-key-down (keysym)
    (cond
        ((eql sdl:SDLK_ESCAPE (ct:cref sdl:SDL_keysym keysym sdl::sym))
            (setf *quit* t))
        ((eql sdl:SDLK_SPACE (ct:cref sdl:SDL_keysym keysym sdl::sym))
            (setf *rotate* (not *rotate*)))))

5) Conclusion
Hopefully this has helped you better understand how to make use of FFI bindings for the Corman Common Lisp. If you have any corrections, additions or comments, please let me know.

6) References and Resources

Efficiency of the MPEG2, MPEG4-2 and MPEG4-10 (MPEG4 part 10, h.264 or h.26l) codecs

June 7th, 2009

Description

Around 2001, I conducted a technology review comparing the coding efficiency of the MPEG2, MPEG4-2 and MPEG4-10 (MPEG4 part 10, h.264 or h.26l) codecs in order to pick an effective and efficient compression algorithm to be used in a new satellite DBS system. The goal of these tests was to achieve the highest quality video at the lowest possible bit rate, thus allowing a large number of video channels to be placed into a single satellite transponder.

We were hoping to fit upwards of 25 full screen video channels into a transponder by encoding each channel at an average of 1.2Mbps. All channels would be stat muxed within a transponder. Stat muxing channels allows an encoder to maintain video quality by varying the bit rate for that channel above or below the 1.2Mpbs average, as dictated by the source material. In a nutshell,

“the objective of statistical multiplexing is to dynamically distribute the available channel bandwidth among the video programs in order to maximize the overall picture quality of the system” [IBM]

The difference in video quality at 1.2Mbps between the older MPEG4-2 algorithm and the new MPEG4-10 (h.264) algorithm is quite marked, as can be seen in the following screen shots.

H.264/MPEG2H.264/MPEG2H.264/MPEG2H.264/MPEG2

I downloaded several uncompressed clips in Berkley YUV format and, using netPBM, converted these to Windows AVI. Each clip was encoded at an average of 2Mbps and 1.2Mbps using the Microsoft ISO MPEG4-2 encoder and the MPEG committee MPEG4-10 (h.264) reference encoder.

The processor power required by the new MPEG4-10 algorithm is an order of magnitude greater than the older MPEG4-2 algorithm. Using a 2Ghz Pentium 4 with 768MB RAM, encoding an 8 second sequence using the Microsoft MPEG4-2 encoder took under 60 seconds. Encoding the same sequence using the reference MPEG4-10 encoder took two hours. Note that the reference MPEG4-10 encoder is a proof of concept and is not yet optimized.

I have created a DVD that can be viewed in a consumer DVD player showing the results of these tests. The DVD contains split screen views, full screen views etc. The above frames are taken from the video on the DVD.

Process

Here is the workflow that I used in order to convert a h.264 compressed video to AVI format so that it can be viewed in a media player in MS Windows (e.g. Windows Media Player).

Required:

Converting the VCEQ ( http://www.vqeg.org ) ( ftp://ftp.crc.ca/pub/crc/vqeg/ ) video sequences to 4:2:0 Berkley YUV, suitable for encoding:


  1. Use yuv2avi to convert the 4:2:2 source material to an avi file. This conversion process also decimates the video from 4:2:2 to 4:2:0:

    • Example command line: yuv2avi.exe src19_ref__525.yuv src19_ref__525.avi 525 UYVY

    • NOTE: `It is essential to convert whatever video you want to encode into a base format, like ppm. So if you are unable to use yuv2avi , then use an application like VirtualDub to save your video to a sequence of images in .BMP format (e.g. image_0001.bmp, image_0002.bmp, image_0002.bmp)


  2. Use VirtualDub to:

    1. Crop the x/y video resolutions to multiples of 16, e.g 720×480, 352×288 etc.

    2. Split the video into separate .bmp images.



  3. Use bmptoppm in the Netpbm package to convert each .bmp image to a .ppm image.

  4. * Use ppmtoeyuv in the Netpbm package to convert each .ppm image to a corresponding Berkley yuv image.

    • “for i in image*.bmp; do bmptopnm.exe $i | ppmtoeyuv.exe >`basename $i .bmp`.yuv; done”


  5. Use the MSDOS copy command to create a single eyuv video file from the individual images:

    • “copy.exe /b *.yuv video.yuv”



  • Encode your source video (4:2:0) using the h.264 encoder. The h.264 codec (at least the version I used) does not support 4:2:2 video. This may have changed.

  • Decode the compressed file using the h.264 decoder. This results in a Berkley EYUV encoded file.

Converting the h.264 encoded file into an .avi file that can be viewed in Windows:


  1. Use eyuvtoppm in the Netpbm package to convert the Berkley yuv encoded video to a single ppm file containing the video frames.

  2. * Use pnmsplit in the Netpbm package to write out the individual frames from the ppm sequence:

    • eyuvtoppm.exe -w $WIDTH -h $HEIGHT < decoded_file.yuv | pnmsplit.exe


  3. * Convert each .ppm image to a .bmp image:

    • for i in image*; do ppmtobmp.exe $i >`basename $i `.bmp; done


  4. Use VirtualDub to create an avi file from the sequence of .bmp images

If you want to view a file you have encoded using h.264, you must first decode it to uncompressed EYUV format and then either view the uncompressed video in a application capable of playing this format, or convert the file to AVI. In my case, I took an 8 second clip of 177 Megabytes in size and compressed it down to around 2 Megabytes. To watch it I had to decompress it (back to 177 Megabytes) and then convert it to AVI format.

And there you have it. Easy huh ?

Interactive TV

June 7th, 2009

iTV
I worked on an iTV (Interactive Television) project from ‘96 to mid ‘98 for Astro (Measat), a Malaysian satellite DBS company (spending nine months in Kuala Lumpur).

Philips was the primary vendor and supplied the head-end video and compression equipment and the set top box (STB).

The company I work for, HD+ Associates was responsible for the entire DBS project. This included project management, systems integration, creating RFPs, selecting vendors, Alpha and Beta testing, business, marketing and contract management. Yes, pretty much everything (including site selection and the design and architecture of the facility itself)
The DBS system went live at the end of ‘96. The five initial applications were to be part of an interactive package that was to eventually include an Internet over satellite service. All services were designed to run on consumer level STBs (set top boxes) running circa ‘96 hardware.

iTV Welcome
An early ‘Welcome’ screen, Stocks Application

The initial interactive applications were: Horse Racing (betting), Home Banking, ETS General, Stock Trading (stock market) and a Lottery application.
I specified and developed the Lottery and the Stock Trading applications. I was also involved in the definition of requirements and the creation of functional specifications for the the Horse Racing and Home Banking applications, which were developed by Philips.

iTV Trading
Early design for the main menu, Stocks Application

The applications were developed in MHEG5, an authoring environment designed for creating interactive applications for TV.
All applications were multi-screen. The Home Banking application was huge, my Stock Trading application ended up being around 10 or so screens when development finally ended. A PPro 200 took several minutes just to compile and prepare the source files for the simulator. The code itself for one of the more complicated screens in the Stock Trading application was almost 90KB - without any data, logos, bitmaps or icons.

iTV Betting
Betting screen for the horse racing application

We used the MHEG engine developed by GMD Fokus to debug and test the applications on a PC. All development was done in Windows Notepad. Philips provided an MHEG simulator, that simulated the interactive environment on the G+2 (the generation of STB that Astro was using at the time of launch). This allowed us to use the same remote control as was used by the consumer STBs to run additional tests.

iTV FOREX
The ForEx rates from the Home Banking application

At the time, using MHEG to develop anything more than a simple one or two screen application was extremely slow going. The only authoring tool available was a plugin to xxxx that was being developed by GMD Fokus. Within a couple of months I had to move to Windows Notepad as the plugin did not allow me to implement any of the complicated logic required by the applications (Token Groups spring immediately to mind).
The only reference material available was the MHEG5 specification document. Learning the language involved flipping between the MHEG syntax definition and the appendix describing the ASN.1 notation.

iTV Transfer
Account transfer and bill payment screen

Commodore 64 DOS Wedge Commands

May 11th, 2009

This page contains a summary of DOS Wedge commands found on the web. Notable sources include Commodore DOS Wedges, An Overview: Jim Butterfield, the Wikipedia DOS Wedge entry, THE COMMODORE 64 MACRO ASSEMBLER DEVELOPMENT SYSTEM, Gaelyne’s Survival Guide for New C64/128 Users, and the Wikipedia Commodore DOS entry.

The primary wedge commands must always be prefixed with > or @ and must appear in the first column of the screen.

Drive Status: @
Returns then clears the current drive status or drive error.
Example: @

Deactivate DOS Wedge: @Q
Deactivates the DOS wedge.
Example: @Q

Reactivate DOS Wedge: SYS 52224
Reactivates the DOS wedge.
Example: SYS 52224

Device: @(device_number)
Set the default device number (8, 9, 10, 11, etc) for two or more device systems, where a device is a single or dual disk drive chassis. All subsequent DOS commands will use this default device.
Example: @0

Directory: @$(drive_number):(filename)(*)([volume])
Shortcut: $(drive_number)
Displays the disk directory in drive (drive_number) without overwriting a BASIC program in memory. If using a dual disk drive chassis then (drive_number) must be specified and must be either zero (0) or one (1). For a single drive chassis, a (drive_number) of 0 may be specified or may be omitted altogether. If (filename) is specified, only that file, if present, will be displayed. Wildcards are allowed; use ? to match any single character; and * to match any stream of characters. For example, @$0:D* will return only programs whose file names start with the letter D. @$0:??? will return programs with exactly three characters in their file names. If ([volume]) is specified (where volume is the character id of that volume), then only those files contained on that volume will be displayed. A ([volume]) is any character enclosed in square brackets.
Example: @$0

Reset: @U
Reset DOS. Note: THE COMMODORE 64 MACRO ASSEMBLER DEVELOPMENT SYSTEM states @UJ and @UI(drive) will reset DOS. But this does not seem to be the case.
Example: @U

Initialize: @I(drive_number):
Initialize drive (drive_number). Causes drive and drive electronics to shake hands. Should be done each time a disk is inserted into the drive.
Example: @I0:

Format: @N(drive_number):(disk_name),(disk_ID)
Performs a low level format of the disk in drive (drive_number). In the example, the disk in drive 0 is formatted and given the ID of “JB” with a disk header of “DISKNAME”. Use a different ID for each of your disks. Omitting ID will perform a quick format.
Example: @N0:DISKNAME,JB

QuickFormat: @N(drive_number):(disk_name)
Performs a quick format of the disk in drive (drive_number). In the example, the disk in drive 0 is formatted and given a disk header of “DISKNAME”.
Example: @N0:DISKNAME

Delete (Scratch): @S(drive_number):(file_name)(*)([volume])
Deletes the file specified by (file_name) on the disk in drive (drive_number). Wildcards are allowed; A command of @S0:C* will remove all files which start with the letter C. A command of @S0:* will remove all files. If ([volume]) is specified (where volume is the character id of that volume), then only those files contained on that volume will be displayed. A ([volume]) is any character enclosed in square brackets.
Example: @S0:FILENAME

Validate: @V(drive_number):
Validates the disk in in drive (drive_number). Rebuilds the map of free blocks and removes incomplete files. Incomplete files will appear prefixed by an asterix. These files must be removed using @V0 as soon as possible. Do not attempt to delete these files.

Example: @V0:

Rename: @R(drive_number):(new_file)=(old_file)([volume])
Renames the file specified by (old_file) to (new_file). If ([volume]) is specified (where volume is the character id of that volume), then only those files contained on that volume will be displayed. A ([volume]) is any character enclosed in square brackets.
Example: @R0:ICE=WATER

Copy: @C(drive_number):(new_file)=0:(existing_file)([volume])
Copies the existing file specified by (existing_file) to a new file name (new_file), on the same disk (or another disk in the same two-drive chassis). Copying to another disk without a two-drive chassis is not supported by this command and requires a 3′rd party utility program. This command also supports concatenation of files. If ([volume]) is specified (where volume is the character id of that volume), then only those files contained on that volume will be displayed. A ([volume]) is any character enclosed in square brackets.
Example: @C0:DOG=0:CAT

Contatenate: @C(drive_number):(new_file)=0:(file_1),0:(file_2)([volume])
Creates a new file specfied by (new_file) from the concatenation of the files (file_1) and (file_2). If ([volume]) is specified (where volume is the character id of that volume), then only those files contained on that volume will be displayed. A ([volume]) is any character enclosed in square brackets.
Example: @C0:FIGHT=0:DOG,0:CAT

Duplicate Disk: @D(dest_drive_number)=(src_drive_number)
Duplicates the disk in (src_drive_number) onto (dest_drive_number). only to be used on dual drives (two drives in the same chassis) and not two single drives. Duplication across drive chassis (for example, across single drives) is not supported by this command and requires a 3′rd party utility program. The example will copy the disk in drive 1 to drive 0 in a single drive chassis.
Example: @D0=1

Load BASIC File: /(drive_number):(file_name)
Loads a BASIC program specified by (file_name) into RAM. This command can only be used to load BASIC programs or machine code programs that are booted from BASIC. The file’s owm load address is ignored and instead the file is loaded at the current “Start of BASIC Text” area. For a single drive chassis, a (drive_number) of 0 may be specified or may be ommitted altogether.
Example: /FILENAME

Load & Run BASIC File: ↑(drive_number):(file_name)
Loads and runs the BASIC program specified by (file_name). This command can only be used to load BASIC programs or machine code programs that are booted from BASIC. The file’s owm load address is ignored and instead the file is loaded at the current “Start of BASIC Text” area. For a single drive chassis, a (drive_number) of 0 may be specified or may be ommitted altogether.
Example: ↑FILENAME

Load Machine Code File: %(drive_number):(file_name)
Loads a machine code program specified by (file_name) into RAM at its own load address. For a single drive chassis, a (drive_number) of 0 may be specified or may be ommitted altogether.
Example: %FILENAME

Save BASIC File: ←(drive_number):(file_name)
Saves a BASIC program. For a single drive chassis, a (drive_number) of 0 may be specified or may be ommitted altogether.
Example: ←FILENAME

Verify File: '(drive_number):(file_name)
Verifies that a file has been saved correctly. For a single drive chassis, a (drive_number) of 0 may be specified or may be ommitted altogether.
Example: 'FILENAME