MEG-4 User's Manual

Welcome to the manual for the MEG-4, the Free and Open Source virtual fantasy console.

console.png

Hint

This manual can be used off-line. From the right-click pop-up menu, choose "Save As". But it is also embedded in the MEG-4 emulator, just press F1 any time.



Getting Started

In Your Browser

If you don't want to install anything, just visit the website, it has an emulator running in your browser.

Installing

Go to the repository and download the archive for your operating system.

Windows

  1. download meg4-i686-win-sdl.zip
  2. extract it to C:\Program Files directory and enjoy!

This is a portable executable, no actual installation required.

Linux

  1. download meg4-x86_64-linux-sdl.tgz
  2. extract it to the /usr directory and enjoy!

Alternatively you can download the deb version for Ubuntu or RaspiOS and install that with

sudo dpkg -i meg4_*.deb

Running

When you run the MEG-4 emulator, your machine's localization will be autodetected, and if possible, the emulator will greet you in your own language. The first screen it will show you is the "MEG-4 Floppy Drive" screen.

load.png
MEG-4 Floppy Drive

Just drag'n'drop a floppy file onto the drive, or left click on the drive to open the file selector. These floppies are PNG images with additional data, and an empty one looks like this:

floppy.png
MEG-4 Floppy

Other formats are supported too, see file formats for more details on what else can be imported. Once your floppy is loaded, the screen will automatically change to the game screen.

Alternatively you can also press Esc here to get to the editor screens and start creating contents from ground up.

Command Line Options

On Windows, replace - with / for the flags (because that's the Windows' way of specifying flags, for example /n, /vv), otherwise all options are identical.

Use right-click on meg4.exe, and from the popup menu select Create shortcut. Then right-click on the newly created shortcut file, and from the popup menu choose Properties.

winshortcut.png
Setting command line options under Windows A window will appear, where you can specify the command line options in the Target field. Press OK to save it. After this, don't click on the program, rather click on this shortcut, and the program will start with those command line options. If you wish, you can have multiple shortcuts with different options.

meg4 [-L <xx>] [-z] [-n] [-v|-vv] [-s] [-d <dir>] [floppy]
Option Description
-L <xx> The argument of this flag can be "en", "es", "de", "fr" etc. Using this flag forces a specific language dictionary for the emulator and avoids automatic detection. If there's no such dictionary, then English is used.
-z On Linux by default, the GTK libraries are run-time linked to get the open file modal. Using this flag will make it call zenity instead (requires zenity to be installed on your computer).
-n Force using the "nearest" interpolation method. By default, it is only used if the screen size is multiple of 320 x 200.
-v, -vv Enable verbose mode. meg4 will print out detailed information to the standard output (as well as your script's trace calls), so run this from a terminal.
-s Enable strace, tracing of system calls (only if compiled with DEBUG).
-d <dir> Optional, if given, then floppies will be stored in this directory and no open file modal is used.
floppy If this parameter is given, then the floppy (or any other supported format) is automatically loaded on start.


File Formats

Although the built-in editors are pretty awesome, MEG-4 is capable of handling various file formats both on export and import side to ease creation of content, and make the use of MEG-4 actual fun.

Floppies

MEG-4 stores programs in "floppies". These are image files that look like a real floppy disk. You can save in this format by pressing Ctrl+S, or by selecting menu.png > Save from the menu (see interface). You'll be prompted to give a label to the floppy, which will be your program's title as well. To load such floppies, press Ctrl+L, or just simply drag'n'drop these image files onto the MEG-4 Floppy Drive.

The low level specification for this format can be found here.

Project Format

For convenience, there's also a project format, which is a zip archive containing the console's data in only common and well-known formats so that you can use your favourite editor or tool to modify them. You can save in this format by selecting the menu.png > Export ZIP menu option. To load, you can simply drag'n'drop such zip files into the MEG-4 Floppy Drive.

Hint

One of the test tools, the converter can be used to convert floppies into zip archives.

Files in the archive:

metainfo.txt

A plain text file with the MEG-4 firmware version and the program's title.

program.X

Your program's source code, created in the code editor, as a plain text file. You can use any text editor to modify. On export, newlines are replaced with CRLF for Windows compatibility, on import it doesn't matter if lines are ended with NL or with CRLF, both supported.

The source code must start with a special first line, a #! shebang followed by the programming language used. For example #!c or #!lua. This language code must also match the extension in the file name, eg: program.c or program.lua.

sprites.png

A 256 x 256 pixels indexed PNG file, containing the 256 colors palette and all of the 1024 sprites, each 8 x 8 pixels, created in the sprite editor. This image file is editable by GrafX2, GIMP, Paint etc. On import, true color images will be converted to the default MEG-4 palette using the smallest weighted sRGB distance method. This works, but not looking particularly good, therefore it is recommended to save and import paletted PNG images instead. Sprites can also be imported from Truevision TARGA (.tga) images, if they are indexed and have the correct dimensions of 256 x 256 pixels.

map.tmx

The map, created in the map editor, in a format that can be used with the Tiled MapEditor. Only CSV encoded .tmx is generated, but on import base64 encoded and compressed .tmx files can be loaded too (everything except zstd). Furthermore PNG or TARGA images are supported on import too, provided they are indexed and their dimensions match the required 320 x 200 pixels. The palette in these images aren't used, except the spritebank selector is stored in the first entry (index 0) alpha channel.

font.bdf

The font, created with the font editor, in a format that can be edited by many tools, like xmbdfed or gbdfed. Obviously I'd recommend my SFNEdit instead, and FontForge works prefectly too. On import, besides of X11 Bitmap Distribution Format (.bdf), PC Screen Font 2 (.psfu, .psf2), Scalable Screen Font (.sfn) and FontForge's native SplineFontDB (.sfd, bitmap variant only) supported too.

sounds.mod

The sound effects you created in the editor in Amiga MOD format. See music below. The song must be named MEG-4 SFX.

All 31 waveforms are stored in this file, and only the first pattern used, and only one channel (64 notes in total).

musicXX.mod

The music tracks you created in the editor in Amiga MOD format. The XX number in the file name is a two digit hexadecimal number, which represents the track number (from 00 to 07). The song must be named MEG-4 TRACKXX, where XX must be the same hexadecimal number as in the filename. There are dozens of third party programs to edit these files, just google "music tracker", for example MilkyTracker or OpenMPT, but for a true retro feeling, I'd recommend the moderized clone of FastTracker II, available for both Linux and Windows.

From music files, only those waveforms are loaded that are referenced from the notes.

You can find a huge database of downloadable Amiga MOD files at modarchive.org. But not all files are actually in .mod format on that site (some are .xm, .it or .s3m etc.); you'll have to load those in a tracker and save them as a .mod first.

Warning

The Amiga MOD format is a lot more featureful than what the MEG-4 DSP chip can handle. Keep this in mind when you edit .mod files in third party trackers! Waveforms can't be longer than 16376 samples and songs longer than 16 patterns (1024 rows) will be truncated on import. Pattern order will be linearized, and although pattern break 0xD handled, other pattern commands are simply skipped. Also if you import multiple tracks, then they will share the same 31 samples.

Music can also be imported from MIDI files, but these files only store the musical note sheet, and not the waveforms like Amiga MOD files do. General MIDI Patch has standardized the instrument codes though, and MEG-4 has some built-in wavepatterns for these, but due to size constraints those are not the best quality, so importing your own wavepatterns with MIDI files is strongly recommended.

memXX.txt

Hexdump of the memory overlays data (which are binary blobs by nature). Here XX is a hexadecimal number from 00 to FF. The format is the same as hexdump -C's, you can edit these with a text editor. On import, binary files by the name memXX.bin also supported. For example, if you want to embed a file into your MEG-4 program, then name it mem00.bin, drap'n'drop it into the emulator, and afterwards you'll be able to load it in your program with the memload function.

Other Formats

Normally waveforms are automatically loaded from Amiga MOD files, but you can also individually import and export wave patterns in .wav (RIFF Wave 44100 Hz, 8-bit, mono) format. These are editable with Audacity for example. If the imported file is named as dspXX.wav, where XX is a hexadecimal number between 01 and 1F, then the waveform is loaded at that position, otherwise at the first empty slot.

You can import MEG-4 Color Themes from "GIMP Palette" files. These are simple text files with a little header, and in each line red, green and blue numeric values. Each color entry line defines the color of a specific UI element, see the default theme src/misc/theme.gpl for an example. Theme files can also be edited visually using GIMP or Gpick programs too.

By default, MEG-4 supports the Raspberry Pi 3B+ GPIO pin layout, but you can load any arbitrary configuration from a plain text file. Here the first line must be "GPIO Layout", the second line is the name of the board, the third line is the device file, and the rest is a list of physical pin - GPIO register offset mappings (where -1 means the pin is not assigned to the GPIO chip, eg. voltage or ground pins). For an example, see src/misc/gpio.txt.

Furthermore, you can import PICO-8 cartridges (both in .p8 and .p8.png formats) and TIC-80 cartridges (both in .tic and .tic.png formats), however you'll have to adjust the imported source code, because their memory layouts and API calls are different to MEG-4's. But at least you'll get their assets properly. The TIC-80 project format isn't supported because those files are unidentifiable. If you really want to import such a file, then first you'll have to convert it using the prj2tic tool, which can be found in the TIC-80 source repo.

Exporting into these cartridges is not possible, because the MEG-4 is lot more featureful than the competition. There's simply no place for all the MEG-4 features in those files.



User Input

Gamepad

The first gamepad's buttons and joysticks are mapped to the keyboard, they are working simultaniously. For example it doesn't matter if you press Ⓧ on the controller, or X on the keyboard, in both case both gamepad button flag and keyboard state flag will be set. The mapping can be changed by writing the keyboard scancodes to MEG-4's memory, see memory map for details. The default mapping goes like cursor arrows are the directions ◁, △, ▽, ▷; the Space is the primary button Ⓐ, C is the secondary button Ⓑ and X is Ⓧ, Z is Ⓨ. The Konami Code is working too (see KEY_CHEAT scancode).

Pointer

Coordinates and button pressed states can be easily accessed from the MEG-4's memory. Scrolling (both vertical and if supported, horizontal) handled as if your mouse had up / down or left / right buttons.

Keyboard

For convenience, it has several shortcuts and multiple input methods.

Key Combination Description
GUI Or Super, sometimes has a gui.png logo on it. UNICODE codepoint input mode.
AltGr The Alt key right to the space key, activates Compose mode.
Alt+U In case your keyboard lacks the GUI key, UNICODE input mode too.
Alt+Space Fallback Compose, for keyboards without the AltGr key.
Alt+I Enter icon (emoticons) input mode.
Alt+K Enter Katakana input mode.
Alt+J Enter Hiragana input mode.
Alt+A For keyboards without such key, works as a & key (ampersand).
Alt+H For keyboards without such key, works as a # key (hashmark).
Alt+S For keyboards without such key, works as a $ key (dollar).
Alt+L For keyboards without such key, works as a £ key (pound).
Alt+E For keyboards without such key, works as a key (euro).
Alt+R For keyboards without such key, works as a key (rupee).
Alt+Y For keyboards without such key, works as a ¥ key (yen).
Alt+N For keyboards without such key, works as a key (yuan).
Alt+B For keyboards without such key, works as a key (bitcoin).
Ctrl+S Save floppy.
Ctrl+L Load floppy.
Ctrl+R Run your program.
Ctrl+⏎Enter Toggle fullscreen mode.
Ctrl+A Select all.
Ctrl+I Invert selection.
Ctrl+X Cut, copy to clipboard and delete.
Ctrl+C Copy to clipboard.
Ctrl+V Paste from clipboard.
Ctrl+Z Undo.
Ctrl+Y Redo.
F1 Built-in help pages (the API Reference in this manual, see interface).
F2 Code Editor
F3 Sprite Editor
F4 Map Editor
F5 Font Editor
F6 Sound Effects
F7 Music Tracks
F8 Memory Overlays Editor
F9 Visual Editor
F10 Debugger
F11 Toggle fullscreen mode.
F12 Save screen as meg4_scr_(unix timestamp).png.

UNICODE Codepoint Mode

In this mode you can enter hex numbers (0 to 9 and A to F). Instead of these separately pressed keys, the codepoint they describe will be added as if your keyboard had that key. For example the sequence GUI, 2, e, ⏎Enter will add a . dot, because codepoint U+0002E is the . dot character.

Note

Only the Basic Multilingual Plane (U+00000 to U+0FFFF) supported, with some exceptions for the emoticons range starting at U+1F600. Other codepoints will be simply skipped.

This mode automatically quits after the characterer is entered.

Compose Mode

In compose mode you can add acute, umlaut, tilde etc. to the characters. For example the sequence AltGr, a, ' will add á, or AltGr, s, s will add ß, and another example, AltGr, c, , will add ç, etc. You can use Shift in combination with the letter to get the uppercase variants.

This mode automatically quits after the characterer is entered.

Icon Mode

In icon mode you can add special icon characters, representing the emulator's input (like the sequence Alt+I, m will add the 🖱 mouse icon, and Alt+I, a will add the icon of the gamepad's button) as well as emoji icons (like Alt+I, ;, ) will add the character 😉, or Alt+I, <, 3 will produce ).

This mode remains active after the characterer is entered, press Esc to return to normal input mode.

Katakana and Hiragana Modes

Similar to icon mode, but here you can type the Romaji letters of pronounced sound to get the character. For example the sequence Alt+K, n, a, n, i, k, a is interpreted as three sounds, and therefore will add the three characters ナニヵ. Also, punctation works as expected, for example Alt+K, . will produce Japanese full stop character .

You can use Shift in combination with the first letter to get the uppercase variants, for example Alt+K, Shift+s, u will produce and not .

This mode remains active after the characterer is entered, press Esc to return to normal input mode.

Note

This feature is implemented using data tables, new combinations and even new writing systems can be added to src/inp.c any time without coding skills.



Interface

Game Screen

By default, you'll see the game screen, with your game running (or the MEG-4 Floppy Drive if there's no game loaded). When you press Esc on this screen, then you'll switch to editor mode.

Editor Screens

If you press Esc (no re-compilation) or Ctrl+R (re-compiles your program) in any of the editor modes then you'll return to the game screen.

All editors are themable, to recolor the entire interface, just drag'n'drop a GIMP Palette file into the screen. See other formats for details.

help.png
Help and the Menu

All editors have a menu on the top. By clicking on the menu.png icon, a pop up menu will appear, from where you can access various functions, most of which also accessible through keyboard shortcuts (see keyboard). You can also access the built-in help pages from here, however that's always accessible in all editors by pressing F1 too.

Help Pages

On the help pages screen, you can click on the links, and you can also press Backspace to return to the previous help page in history. If the history is empty, then this will return to the Table of Contents page. The help screen is the exception to the rule, because pressing Esc here does not always return to the game screen, instead it returns to the screen where it was invoked from.

To start a search, you can click on the search input box on the top right, or just start typing what you're looking for.

The built-in help pages reader is actually a very minimal MarkDown viewer, and it shows exactly the same info that were used to generate this manual (but this manual you're reading now has more sections, the built-in one is limited to the API Reference).



Code Editor

code.png Click on the pencil icon (or press F2) to write your program's source code.

Code has three sub-pages, one where you can write the source code (this one), the Visual Editor, where you can do the same using structograms, and your program's machine code can be seen in the debugger.

Here the entire area is one big input field for the source code. At the bottom, you can see the status bar, with the current's row and coloumn, the UNICODE codepoint of the character under the cursor, and if you're standing in an API's argument list, a quick help on that API function's parameters (suitable for all programming languages).

codescr.png
Code Editor

Programming Language

The program must start with a special line, with the characters #! followed by the language you want to use. By default, it uses MEG-4 C (a subset of ANSI C), but you can choose others as well. See the list under "Programming" in the table of contents on the left.

User Provided Functions

Regardless to the scripting language you choose, there are two functions that you should implement. They have no arguments and they return no value.

  • setup function is optional and runs only once when your program gets loaded.
  • loop function is mandatory, and runs every time a frame is generated. At 60 FPS this means a 16.6 msecs timeframe, but the MEG-4 "hardware" itself takes about 2-3 msecs, which leaves you a 12-13 msecs for your function to fill. You can query this value from the performance counter MMIO register, see memory map. If it takes longer to run the loop function, then the screen might became laggy, and the emulator will be less responsive than usual.

Additional Shortcuts

In addition to standard keyboard shortcuts and input methods (see keyboard), these work too in the code editor:

Key Combination Description
Ctrl+F Find string.
Ctrl+G Find again.
Ctrl+H Search and replace (in the selected text, or in lack of that, in the entire source).
Ctrl+J Go to line.
Ctrl+D Go to function definition.
Ctrl+N List bookmarks.
Ctrl+B Toggle bookmark on current line.
Ctrl+ Go to previous bookmark.
Ctrl+ Go to next bookmark.
Home Move cursor to the beginning of the line.
End Move cursor to the end of the line.
Page Up Move cursor 42 lines (one page) up.
Page Down Move cursor 42 lines (one page) down.
F1 If the cursor is in an API's argument list, then takes to that function's built-in help page.

From the menu, you can also access Find, Replace, Go to, Undo, Redo as well as the list of bookmarks and the defined functions.



Sprite Editor

sprite.png Click on the stamp icon (or press F3) to modify the sprites.

The sprites you create here can be displayed using spr, and also used by dlg to generate a dialog window and by stext when it displays text on screen.

The editor has three main boxes, two on the top, and one below.

spritescr.png
Sprite Editor

Sprite Editor Box

The one on the left is the sprite editor box. This is where you can draw to modify the sprite. places the selected pixel on the sprite, clears it to empty.

Sprite Selector

On the right you can see the sprite selector. The sprite you select here will be editable on the left. You can select multiple adjacent sprites and edit them together.

Palette

Below you can see the palette. The first item cannot be set, because that's for erase. If you select any other color, then the hsv.png palette button will become active. Clicking on it will bring up the HSV color picker, and with that you can modify the color for that palette entry.

The default MEG-4 palette uses 32 colors of the DawnBringer32 palette, 8 grayscale gradients, and 6 x 6 x 6 RGB combinations.

Toolbar

toolbox.png
Sprite Toolbar

Under the sprite editor, you see the tool buttons. With these you can easily modify the sprite. Shifting in different directions, rotating clock-wise, flipping etc. If there's an active selection, then they operate on the selection only, otherwise on the entire sprite. For the rotation, you must select an area which has the same width as height, otherwise rotation would be impossible.

The flood fill tool only fills adjacent same pixels, unless there's a selection. With a selection the entire selected area is filled, no matter what pixels the selection contains.

Selections

There are two kinds of selections: rectangle selection and fuzzy select. The former selects a rectangular area, the other selects continous regions with the same pixel. You can press and hold Shift to expand the selection, and Ctrl to intersect and make it smaller.

Pressing Ctrl+A will select all, and Ctrl+I will invert the current selection.

When a selection is active, you can press Ctrl+C to copy the area to the clipboard. Later, you can press Ctrl+V to paste it. Pasting works exactly like the pencil tool, except now you can paint with the whole copied area like a brush. It worth noting that empty pixels are copied to. If you don't want your brush to clear the area, then only select non-empty pixels (you can use Ctrl+fuzzy.png to deselect the empty areas) before copy, that way the empty pixels won't be copied to the clipboard, and in return won't be used as part of the brush.



Map Editor

map.png Clicking on the jigsaw icon (or pressing F4) will bring up the map editor. Here you can place sprites as tiles on the map.

The map you've created here (or any parts of it) can be put on screen using the map or with the maze commands (see below).

mapscr.png
Map Editor

The map is special in a way that it can only display 256 different tiles at once out of the 1024 sprites. For each sprite bank, the first sprite is always reserved for the empty tile, so sprites 0, 256, 512, and 768 cannot be used on maps.

Map Editor Box

On top the big area is where you can see and edit the maps. It is shown as one big map, 320 columns wide and 200 rows high. You can use the zoom in and zoom out buttons on the toolbar, or the mouse wheel to zoom. By pressing right click and holding it down, you can drag the map, but you can also use the scrollbars on the right and on the bottom.

Clicking with left button will set the selected sprite on the map. Select the first sprite to clear the map ( right click does not clear here, instead it moves the map).

Toolbar

Below the map editor area is the toolbar, same as on the sprite editor page, with exactly the same functionality and same keyboard shortcuts (but it can use sprite patterns too, see below). Next to the tool buttons you can find the zoom buttons and the map selector. This latter selects which sprite bank is used on the map (just for the editor. When your game runs, you'll have to set the byte at offset 0007F to change the map's sprite bank, see Graphics Processing Unit).

Sprite Palette

On the right to the buttons is the sprite selector, where you can select the sprite you want to draw with. As said earlier, the first sprite in every 256 sprites bank is unusable, reserved to the empty map tile.

The difference to the sprite editor is (where you can choose just one color from the palette), here on the map you can select multiple adjacent sprites at once. With paint, all of them will be painted at once (exactly the same way as if they were pasted from the clipboard), and what's more, flood fill will use them as a brush too, filling the selected area with that multi-sprite pattern.

Even more, clicking Shift+ with the fill.png fill tool, it will choose one sprite from the pattern randomly. For example, let's assume you have 4 sprites with different looking trees. If you select all of them and fill an area on the map, then those sprites will be placed always in the same order, repeating one after another, which isn't looking good for a forest. But if you press and hold down Shift during clicking with the fill tool, then each tile will be choosen randomly from those selected 4 tree sprites, which looks much more a real forest.

3D Maze

You can also display the map as a 3D maze with the maze function. For this, the turtle position and direction is used as the player's view point to the maze, but to accomodate sub-tile positions, here the turtle's coordinate is multiplied by 128 (originally I've used 8 to match pixels on the map, but movement was too blocky that way). So for example (64,64) is the centre of the top left field on the map, and (320,192) is the centre of the third coloumn and second row.

Here scale parameter acts differently too: when set to 0, then the maze will use 32 x 8 tiles as seen on the sprite palette, one for each tile, each 8 x 8 pixels in size. When set to 1, then there will be 16 x 16 tiles, each tile will use 2 x 2 sprites, so 16 x 16 pixels in size. With 3, you'll have 4 x 4 kinds of tiles, so 16 different tiles in total, each 64 x 64 pixels. In this case the map will select these larger tiles, so tile id only equals with the sprite id if the scale is 0. For example if map has the id of 1 with scale set to 1, then instead of sprite id 1, this will select sprites 2, 3, 34, 35.

Tile id 1 with scale 0          Tile id 1 with scale 1
+---+===+---+---+-              +---+---+===+===+-
|  0|::1|  2|  3| ...           |  0|  1|::2|::3| ...
+---+===+---+---+-              +---+---+===+===+-
| 32| 33| 34| 35| ...           | 32| 33|:34|:35| ...
+---+---+---+---+-              +---+---+===+===+-

Regardless you'll have to place sprite id 1 on the map from the palette to get these sprites. Tile id 0 as usual means empty.

If sky is set, then that tile will be displayed as a ceiling to the maze. On the other hand, grd is only displayed as floor where the map is empty. When you call the maze, you separate the tile ids into ranges, and this specifies how a tile is displayed (floor, wall or sprite). Everything greater than or equal to wall will be non-walkable and displayed as a cube, with the selected tile sprites on the cube's sides without transparency. Tiles greater than or equal to obj will be non-walkable too, but displayed as a properly scaled 2D sprite always facing towards the player (the turtle's position) and with alpha channel applied, so unlike the walls the objects can be transparent.

Tile id Description
0 Always walkable, grd displayed as floor instead
1 <= x < door Walkable, displayed as floor
door <= x < wall Displayed as a wall, but walkable
wall <= x < obj Non-walkable, displayed as a wall
obj <= x Non-walkable, displayed as an object sprite

You can also add non-player characters (or other objects) to the maze independently to the map (in an int array with x, y, tile id triplets, where the coordinates are multiplied by 128). These will be walkable and will be displayed the same as object sprites; collision detection, movement and all the other aspects have to be implemented in your game by you. The maze command just diplays these. It does a favour for you though, if the given NPC can directly see the player, then the tile id's most significant 4 bits in the array will be set. Which bits depends on their distance to each other: the most significant bit (0x80000000) is set if their distance is smaller than 8 map fields, next bit (0x40000000) if less than 4 fields, next bit (0x20000000) if less than 2 map fields, and finally last bit (0x10000000) if they are on the same field or neightbouring map fields.

Furthermore this command also takes care of navigation in the maze, / △ moves the turtle forward, / ▽ backward; / ◁ turns left, and / ▷ turns right (keyboard mappings for the gamepad can be changed, see memory map for details). Handling all the other gamepad buttons and interactions are up to you to code in your game, the maze only helps you with moving the player and handling collisions with the walls.

Note

Don't forget that you always have to divide the turtle's position by 128 to get the player's position on the map.



Font Editor

font.png Click on the letter icon (or press F5) to modify the fonts.

This font will be used by width when you measure a string, and also by text when you display text from your program.

This page has a similar arrangement as the sprite editor. On the left you can find the glyph editor area, and on the right the glyph selector. (Glyph is the displayed typeface of a UNICODE character.)

fontscr.png
Font Editor

Glyph Editor

It is as simple as left clicking sets typeface (foreground), and clears to empty (background).

Glyph Selector

You can search for a UNICODE codepoint, but if you press a key, the glyph selector will jump to its glyph. If your keyboard layout lacks some keys, you can use one of the special input modes, see keyboard.

Toolbar

The toolbar here is limited, only shifting, rotating and flipping allowed, there are no selection tools. However copy (Ctrl+C) and paste (Ctrl+V) works as usual on the glyph selector table.



Sound Effects

sound.png Click on the speaker icon (or press F6) to bring up the sound effects editor.

You can play these sound effects from your program using the sfx command.

On the left, you'll see the waveform editor and its toolbar, on the right the effect selector, and below the effect editor.

soundscr.png
Sound Effects

Effect Selector

On the right you see the list of effects, each represented by a music note (technically all sound effects are music notes, with selectable waveforms and special effects option). Clicking on this list will edit that effect.

Effect Editor

The piano at the bottom, looks like and works like the note editor on the music tracks, except it has less options selectable. You can find further info there, including the keyboard layout.

Waveform Toolbar

Normally the waveform is read-only, and you'll only see what wave the sound effect uses. You'll have to click on the button with the lock icon to make it editable (but first, make sure your sound effect actually has a waveform choosen).

wavescr.png
Waveform Toolbar

When the toolbar is unlocked, then clicking on the wave will change it.

Warning

If you change a waveform, then effective immediately all sound effects and music tracks will change too that use that waveform.

Using the toolbar you can change the finetuning value (-8 to 7), the volume (0 to 64) and the repeat interval. If you click on the repeat button, then it will remain pressed, and you can select a loop range on the waveform. The wave will be played first through, then it will jump to the beginning of the selected range, and it will repeat that range infinitely.

For convenience, you have 4 default wave generator buttons, one to load the default pattern (the one that General MIDI uses) from the soundfont for this wave and various tools to set the length, rotate, enlarge, shrink, negate, flip etc. the wave. The button before the last will continously play it using its current configuration (even if no loop range defined).

Finally the last button, the Export will export the sample in RIFF Wave format. You can edit this with a third party tool, and to load it back, just drag'n'drop the modified file to the MEG-4 screen.



Music Tracks

music.png Click on the music note icon (or press F7) to edit the music tracks.

The music you create here can be played in your program using the music command.

You'll see five coloumns and below a piano.

musicscr.png
Music Tracks

Tracks

On the left the first coloumn selects which music track to edit.

Key Combination Description
Page Up Switch to previous track.
Page Down Switch to next track.
Space Start / stop playing the track.

Below the track selector you can see the DSP status registers, but this block only comes alive when music playback is on.

Channels

Next to it, you'll see four similar coloumns, each with notes. These are the 4 channels that the music can simultaniously play. This is similar to standard music sheets, for more info read the General MIDI section below.

Key Combination Description
Switch to previous channel.
Switch to next channel.
Switch to previous row.
Switch to next row.
Home Switch to first row.
End Switch to last row.
Ins Insert a row. Move everything below down one row.
Del Delete a row. Move everything below up one row.
Backspace Clear note.

Note Editor

Under the channels you can see the note editor, with some buttons on the left and a big piano on the right.

Notes have three parts, the first one (on the top in the editor), the pitch consist of further three sub-parts: the note itself, like C or D. Then the - character if it's a flat note, or # for sharp notes. The third part is simply the octave, from 0 (lowest pitch) to 7 (highest pitch). The 440 Hz pitch for the standard musical note A for example is therefore written as A-4. Using the piano you can easily select the pitch.

After the pitch comes the aforementioned instrument (the middle part of the note) that chooses the waveform to be used, from 01 to 1F. The value 0 is printed as .. and means keep using the same waveform as before.

Finally, you can also add special effects to the notes (the last part of the note), like arpeggio (make it sound like a chord), portamento, vibrato, tremolo etc. See the memory map for a full list. This has a numeric parameter, usually interpreted as "how much". It is printed as three hex numbers, where the first represents the effect type and the last two are the parameter, or ... if it's not set. For example C00 means set the volume to zero, so silence the channel.

Keyboard

pianokbd.png
Piano Keyboard arrangement
  1. wave selectors
  2. effect selectors
  3. octave selectors
  4. piano keyboard
Key Combination Description
1 - 0 Select wave 1 to 10 (or if pressed with Shift 11 to 20).
Q Clear all effects on note (but leave pitch and sample untouched).
W Arpeggio dur (major chord).
E Arpeggio moll (minor chord).
R Slide up a full note.
T Slide up a half note.
Y Slide down a half note.
U Slide down a full note.
I Vibrato small.
O Vibrato large.
P Tremolo small.
[ Tremolo large.
] Silence the channel "effect".
Z Switch one octave down.
. Switch one octave up.
X C note on the current octave.
D C# note on the current octave.
C D note on the current octave.
F D# note on the current octave.
V E note on the current octave.
B F note on the current octave.
H F# note on the current octave.
N G note on the current octave.
J G# note on the current octave.
M A note on the current octave.
K A# note on the current octave.
, B note on the current octave.

Note

Keys on the English keyboard have been used in this table. But it doesn't actually matter what keyboard layout you use, the only thing that matters is the location of these keys on the English keyboard. For example, if you have AZERTY layout, then clearing effects will be A for you, and on a QWERTZ keyboard Z will add slide down effect, and Y will change the octave.

It is important to mention that not all features have a keyboard shortcut. For example you might have 31 wavepatterns, but only the first 20 have shortcuts. Similarily, there are several magnitudes more effects than what's accessible through shortcuts.

General MIDI

You can import music (at least the note sheets) from MIDI files. Very simply put, if a classic music note sheet is stored on a computer in a digitalized form, then it is using the MIDI format to do so. Now these are suitable from a single instrument to a huge orchestra, so they can store a lot more than what the MEG-4 is capable of, therefore

Warning

Not all MIDI files can be imported properly.

Before we could continue, we must talk about the terms, because unfortunately both the MIDI specification and the MEG-4 uses the same nomenclature - but for totally different things.

  • MIDI song: a single .mid file (SMF2 format not supported).
  • MIDI track: one row on a classic music notes sheet.
  • MIDI channel: only exists because MIDI was created by morons, who thought it is fun to squash everything into a single track when storing in files, otherwise exactly the same as a track. You have 16 of these.
  • MIDI instrument: a code (standardized by the General MIDI Patch), which describes the instrument a particular channel is using (except when that's channel 10, don't even ask), you have 128 of these instrument codes.
  • MIDI note: one note in the range C on octave -1 to G on octave 9, 128 different notes in total.
  • MIDI concurrent notes: the number of notes that can be played at once at any given time. There are 16 channels and 128 notes in MIDI, so this is 2048.
  • MEG-4 track: a song, you can have 8 of these.
  • MEG-4 sample: a wavepattern stored as a series of PCM samples, analogous to instrument, you have 31 of these.
  • MEG-4 channel: the number of concurrent notes that can be played at once at any given time, this is 4.
  • MEG-4 note: one note in the range C on octave 0 to B on octave 7, 96 different notes in total.

To avoid confusion, hereafter we'll talk about one MEG-4 track only, and "track" will refer to MIDI channels, and "channel" will refer to MEG-4 channels.

Instruments

Concerning instruments, in total there are 16 families with 8 instruments in each. MEG-4 doesn't have 128 wave banks, so best it can do is, assigning two wavepatterns per family (families 15 and 16 are for sound effects):

Family SF Patch How should sound like SF Patch How should sound like
Piano 01 1-4 Acoustic Grand Piano 02 5-8 Electric Piano
Chromatic 03 9-12 Celesta 04 13-16 Tubular Bells
Organ 05 17-20 Church Organ 06 21-24 Accordion
Guitar 07 25-28 Acoustic Guitar 08 29-32 Electric Guitar
Bass 09 33-36 Acoustic Bass 0A 37-40 Slap Bass
Strings 0B 41-44 Violin 0C 45-48 Orchestral Harp
Ensemble 0D 49-52 String Ensemble 0E 53-56 Choir Aahs
Brass 0F 57-60 Trumpet 10 61-64 French Horn
Reed 11 65-68 Saxophone 12 69-72 Oboe
Pipe 13 73-76 Piccolo 14 77-80 Blown Bottle
Synth Lead 15 81-84 Synth 1 16 85-88 Synth 2
Synth Pad 17 89-92 Synth 3 18 93-96 Synth 3

In short, the General MIDI instrument will become the (patch - 1) / 4 + 1th wave in the soundfont.

Note that MEG-4 assigns waves dynamically, so these number mean the soundfont's wave number. For example if your MIDI file uses two instruments only, let's say Grand Piano and Electric Guitar, then piano would be assigned wave 1, and guitar wave 2. You can load all waves apriori from the soundfont in the sound effects editor, and then your imported MIDI file will use exactly these wave numbers.

Patterns

The MEG-4 patterns are analogous to classic music note sheets, but while on a classic sheet time goes from left to right and each MIDI track is tied to an instrument, in MEG-4 the time goes from top to bottom, and you can dynamically assign which waveform to use on a particular channel. Consider the following example (taken from the General MIDI specification):

notes.png
Classic musical note sheet on the left, its MEG-4 pattern equivalent on the right

On the left we have three tracks, Electric Piano 2, Harp and Bassoon. The first notes to be played are on the bassoon, right two notes at the same time. Note the bass key on the 3rd track, so these are notes C on octave 3 and C on octave 4, and they both last 4 quoter-notes, so whole notes.

On the right you can see the MEG-4 pattern equivalent. The first row describes these two notes played on a bassoon: C-3 on the first channel, and C-4 on the second. The sample 12 (hex, provided that you have manually loaded the soundfont apriori, otherwise the number would be different) selects the oboe waveform, which isn't exactly a bassoon, but that's the closest we have in the soundfont. The C30 part means the velocity at which these notes are played, which is analogous to the volume (the harder you hit a key on the piano, the louder its sound will be). MEG-4 note volumes go from 0 (silence) to 64 (40 in hex, full volume). So 30 in hex means 75% of the full volume.

The next note to be played is on the harp, starts a quoter-note later than the bassoon, it is a G on octave 4 and lasts 3 quoter-notes long. On the MEG-4 pattern you can see this as G-4 in the fourth row (because it starts at that time), and since channels 1 and 2 are still playing the bassoon, it is played on channel 3. If we were to put this on channel 1 or 2, then the previously played note on that channel would be silenced and replaced by this new one. The sample here is 0C (hex), which is Orchestral Harp.

The last note is on the first MIDI track, which starts half a note later from the start, is an E on octave 5, and lasts 2 quoter-notes, or with other words a half-note long. Because it starts half a note from the start, you can see E-5 in the 8th row, and since we already have 3 notes to be played, so it is assigned to channel 4. The sample 02 selects the waveform for Electric Piano, which isn't the same as MIDI Electric Piano 2, but pretty close.

Now we have two whole long notes, one half and a quoter long and another half note long; started at the beginning, quoter and half note later in this order. This means they all must end at the same time. You can see this in the 16th (or 10 in hex) row, all channels have a C00, "set volume to 0" command.

Tempo

MIDI silently assumes 120 BPM and defines a divisor to quoter-notes. Then it might also define the length of a quoter-note in milliseconds, or not. The point is, it is very complex, and not all combinations can be translated to MEG-4 properly. I've written the importer in a way to discard accumulating rounding errors, and only care about the same relative delta times between two consecutive notes. This way the MEG-4 song's tempo never will be exactly the same as the MIDI song's, but it should sound similar and should never deviate too much either.

The tempo on the MEG-4 is much simpler. You have a fixed 3000 ticks per minute, and the default tempo is 6 ticks per row. This means to achive 125 BPM using the default tempo, you should put notes in every 4th rows (because 3000 / 6 / 4 = 125). If you set the tempo (see effect Fxx) to half of that, 3 ticks per row, then each row will last half the time, therefore you'll get 250 BPM if you were using every 4th row. To get 125 BPM with tempo 3, you would have to use every 8th rows. If you set the tempo to 12, then each row will last twice the time, therefore every 4th row will get you 62.5 BPM, and you'd have to use every 2nd rows for 125 BPM. Hope this makes sense to you.

This only sets when a note should be started to play, and totally independent of how long that sound lasts. For the latter you should use a new note on the same channel, or you should use a C00 "set volume to 0" effect in the row when you want the note to be cut off. If you change the tempo in between, that won't influence the sound played, just how long it's played (because the row to turn it off would now reached at a different time).

Now notes will stop playing too if their wavepattern ends. When that happens, depends on the pitch and the number of samples in the wavepattern (playing at C-4 requires 882 samples of the wave to be sent to the speaker on every ticks). There's a trick here: you can set a so called "loop" on the wavepattern, which means after all the samples are played once, the selected region of the wave will be repeated indefinitely (so you'll have to explicitly cut it off, otherwise the sound would really never stop).



Memory Overlays

overlay.png Click on the RAM icon (or press F8) to modify the memory overlays.

Overlays are very useful, because they allow switching portions of the RAM, so that you can handle more data than what actually fits in the memory. You can use these to dynamically load sprites, maps, fonts or any arbitrary program data in run-time with the memload function.

They have another very useful feature: if you use memsave in your program, then the contents of the overlay will be saved on the user's computer. Next time you call memload, it won't load the overlay's data from your floppy, rather from the user's computer. With this, you can create permanent storage to store high-scores for example.

overlayscr.png
Memory Overlays

Overlay Selector

On the top you can see the memory overlays' overview with each overlay's length. The darker entries mean that particular overlay isn't set. You have 256 overlay slots, from 00 to FF.

Overlay Contents

Below the table you can see the hexdump of the overlay's contents (only if a non-empty overlay is selected).

Hexdump is a pretty simple and straightforward format: in the first coloumn you can see the address, which is always dividable by 16. This is followed by the hex representation of 16 bytes at that address, followed by the character representation of the same 16 bytes. That's all to it.

Overlay Menu

On the menu bar, you can specify a memory address and a size, and press on the Save button to store data in the selected overlay. Pressing the Load button will load the contents of the overlay into the specified memory address, but this time the size only specifies the upper limit how much bytes to load.

The Export button will bring up the save file modal, and will allow you to save and modify the binary data with a third party editor. To import a memory overlay back, all you need to do is naming the file memXX.bin, where XX is the number of the overlay you want use, from 00 to FF, and just drag'n'drop that file into the MEG-4's screen.



Visual Editor

visual.png Click on the flowchart icon (or press F9) to edit the source code visually using structograms.

Code has three sub-pages, one allows you to edit the source code visually (this one), textual edit can be done in the Code Editor, and your program's machine code can be seen in the debugger.

TODO



Debugger

debug.png Click on the ladybug icon (or press F10) to examine your program's machine code.

Code has three sub-pages, one where you can see your program's machine code (this one), the Code Editor where you can write the source code as text, and the Visual Editor, where you can do the same using structograms.

Warning

The debugger only works with the built-in languages. It is not available with third party languages like Lua for example, those are not, and cannot be supported.

Here you can see how the CPU sees your program. By pressing Space you can do a step by step execution and see the registers and the memory change. Clicking on the Code / Data button in the menu (or pressing the Tab key) will switch between code and data views.

debugscr.png
Debugger

Code View

On the left you can see the callstack. This is used to backtrace function calls. It also displays the corresponding source line where the function was called. This is a link, clicking on it will bring up the Code Editor, positioned at the line in question. The top of the list is always the line which is currently being executed.

On the right is the list of the bytecode instructions in Assembly that the CPU actually executes.

Data View

On the left is the list of your global variables with their actual values.

On the right you can see the stack, which is splitted into separate parts. Everything above the BP register is the argument list to the currently running function, and everything below that but above the SP register is the area for the local variables.

Registers

Independently to which view is active, you can always see the CPU registers at the bottom. With third party languages, only the FLG, TMR and PC registers are available. See mnemonics for more details on each register.



C

If you want to use this language, then start your program with a #!c line.

Example Program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!c /* global variables */ int acounter = 123; float anumber = 3.1415; addr_t anaddress = 0x0048C; str_t astring = "something"; uint32_t anarray[10]; /* Things to do on startup */ void setup() { /* local variables */ int iamlocal = 123; } /* Things to run for every frame, at 60 FPS */ void loop() { /* Get MEG-4 style outputs */ printf("a counter %d, left shift %d\n", acounter, getkey(KEY_LSHIFT)); }

Description

The default language of the console is MEG-4 C. Despite being a very simple language, it is for somewhat intermediate programmers. If you're a total beginner, then I'd recommend using BASIC instead.

It was created as a deliberately simplified ANSI C to help learning programming. Hence it is limited, not everything is supported that ANSI C expects, but replacing

#!c

with

1
2
3
#include <stdint.h> typedef char* str_t; typedef void* addr_t;

will make the MEG-4 C source to compile with any standard ANSI C compiler (gcc, clang, tcc etc.).

It has one non-standard keyword, the debug;, which you can place anywhere in your code and will invoke the built-in debugger. After this, you can execute your code step by step, watching what it is doing.

Here comes a gentle introduction to the C language, focusing on what's special in MEG-4 C.

Pre-compiler

Because there's only one source, and system function prototypes are supported out-of-the-box, there's no need for header files. The pre-compiler therefore is limited to simple (non-macro) defines and conditional source code blocks only.

1
2
3
4
5
6
7
8
9
10
11
12
/* replace all occurance of (defvar) with (expression) */ #define (defvar) (expression) /* include code block if (defvar) is defined */ #ifdef (defvar) /* include code block if (defvar) is not defined */ #ifndef (defvar) /* include code block if (expression) is true */ #if (expression) /* else block */ #else /* end of conditional code block inclusion */ #endif

You can use enumerations with the enum keyword, separated by commas and enclosed in curly brackets. Each element will be one bigger than the previous one. These act as if you had multiple rows of defines. For example the following two are identical:

1
2
3
#define A 0 #define B 1 #define C 2
1
enum { A, B, C };

It is also possible to assign direct values using the equal sign, for example:

1
enum { ONE = 1, TWO, THREE, FIVE = 5, SIX };

Literals

MEG-4 C understands decimal based numbers (either integer or floating point with or without scientific notation). Hexadecimal numbers must be prefixed by 0x, binary by 0b, octal by 0; characters must be surrounded by apostrophes and strings must be enclosed in double quotes:

1
2
3
4
5
6
7
8
42 3.1415 .123e-10 0777 0b00100110 0x0048C 'á' "Goodbye and thanks for all the fish!\n"

Variables

Unlike in BASIC, variables must be declared. You can place these declaration in two places: at the top level, or at the beginning of a function body. The former become global variable (accessible by all functions), while the latter will be a local variable, accessible only to the function where it was declared. Another difference, that global variables can be initialized (a value assigned to them using =), while local variables cannot be, you must explicitly write code to set their values.

A declaration consist of two things: a type and a name. MEG-4 C supports all ANSI C types: char (signed byte), short (signed word), int (signed integer), float (floating point). You might also put unsigned in front of these to make them, well, unsigned. In ANSI C int can be omitted with short, but in MEG-4 you must not use it. So short int isn't a valid type, short in itself is. Furthermore MEG-4 C supports and prefers standard types instead (defined in stdint.h under ANSI C). These have some simple rules: if they are unsigned, then they start with the letter u; then int means integer type, followed by the number of bits they occupy, and finally suffixed by a _t which stands for type. For example, int is the same as int32_t and unsigned short is the same as uint16_t. Examples:

1
2
3
4
5
int a = 42; uint8_t b = 0xFF; short c = 0xFFFF; str_t d = "Something"; float e = 3.1415;

Unlike in ANSI C, which allows only English letters in variable names, MEG-4 C allows anything that does not start with a number and isn't a keyword. For example, int déjà_vu; is perfectly valid (note how the name contains non-English letters).

Arrays and Pointers

Multiple elements of the same type can be assigned to a single variable, which is called an array. Pointers are special variables that contain a memory address which points to a list of variables of the same type. The similarity between these two isn't a coincidence, but there are subtle differences.

There's no special command for arrays like in BASIC, instead you just specify the number of elements between [ and ] after the name.

1
int anarray[10];

Referencing an array's value happens similarily, with an index between [ and ]. The index starts at 0, and array bounds are checked. MEG-4 supports up to 4 dimensions.

To declare a pointer, one has to prefix the variable name with a *. Because C does not recognize string type, and strings are actually just bytes one after another in memory, therefore we use char* pointer. This might be strange at first, so MEG-4 C defines the str_t type, but this is actually the same as char*.

Because pointers hold an address, you must give an address as their value (using & returns the address of the variables), and pointers always return an address. In order to get the value at that address, you must de-reference the pointer. You have two options to do this: either prefix it with *, or suffix it with an index between [ and ], just like with arrays. For example the two reference in the second printf are the same:

1
2
3
4
5
int variable = 1; int *pointer = &variable; printf("pointer's value (address): %x\n", pointer); printf("pointed value: %x %x\n", *pointer, pointer[0]);

You cannot mix pointers with arrays, because that would be ambiguous. For example

1
int *a[10];
Could mean a single pointer that points to a list of 10 contiguous integers (*(int[10])), but it could also mean 10 independent pointers each pointing to a single, not neccessarily contiguous integers ((*int)[10]). Not obvious which one, so pointers and arrays cannot be mixed within the same declaration.

Warning

Unlike arrays, there's no bound check with pointers!

Operators

In descending order of precedence:

Arithmetic

* / %
+ -
The first one is multiplication, division and % is modulus (aka. remainder after division, eg. 10 % 3 is 1), addition and subtraction.

Relational

!= ==
<= >= < >
Not equal, equal, less or equal, greater or equal, less than, greater than.

Logical

!
&&
||
Logical negation (0 becomes 1, everything non-zero becomes 0), and (returns 1 if both arguments are non-zero), or (returns 1 if at least one of the arguments are non-zero).

Bitwise

~
&
|
<< >>
Bitwise negation, bitwise and, bitwise or, bitwise shift to the left and right. Note that these are the same operators like logical ones, but instead of the whole value, they operate on each binary digits individually. For example logical not !0x0100 == 0, but the bitwise not ~0x0100 == 0x1011. Bitwise shifting is the same as multiplying or dividing by power of two. For example shifting a value to the left by 1 is the same as multipying it by 2.

Incremental

++ --
Increment and decrement. You can use these as a prefix and as a suffix as well. When used as prefix, like ++a, then the variable is increased and the increased value is returned; as a suffix a++ it will increase the value too, but returns the value before the increment.

1
2
a = 0; b = ++a * 3; /* a == 1, b == 3 */ a = 0; b = a++ * 3; /* a == 1, b == 0 */

Conditional

?:
This is a trinary operator, with three operands, separated by ? and :. If the first operand is true, then the whole expression is replaced by the second operand, otherwise with the third. For this the type of the second and third operands must be the same. For example a >= 0 ? "positive" : "negative".

Assignment

= *= /= %= += -= ~= &= |= <<= >>=
The first places the value of an expression in a variable. The others execute the operation on that variable, and then store the result in the very same variable. For example these are identical: a *= 3; and a = a * 3;.

Unlike other languages, in C the assignment is an operator too. This means they can appear anywhere in an expression, for example a > 0 && (b = 2). That's why assignment is = and logical equal is ==, so that you can use both in the same expression.

There's also the address-of operator, the & which returns the address of a variable. This is usable when the MEG-4 API expects an addr_t address parameter.

Operators are executed in precedence order, for example in 1+2*3 we have two operators, + and *, but * has the higher precedence, therefore first 2*3 is calculated, and then 1+6. That's why the result is 7 and not 9.

Control Flow

Unlike BASIC where the primary way of altering the control flow is defining labels, in C (as being a structured language) you specify blocks of instructions instead. If you want to handle multiple instructions together, then you place them between { and } (but it is possible to put only one instruction in a block).

Like everythig else, the conditionals use such blocks too:

1
2
3
4
5
if(a != b) { printf("a not equal to b\n"); } else { printf("a equals b\n"); }

You can add an else branch, which executes when then the expression is false, but using else is optional.

With multiple possible values, you can use a switch statement. Here each case acts as a label, being choosen depending on the expression's value.

1
2
3
4
5
6
switch(a) { case 1: printf("a is 1.\n"); case 2: printf("a is either 1 or 2.\n"); break; case 3: printf("a is 3.\n"); break; default: printf("a is something else.\n"); break; }

There's a special block, defined by the label default, which matches any value that doesn't have its own case. These blocks are concatenated, so if the control jumps to a case, then that block, and every other blocks after that is executed. In the example above, if a is 1, then two printfs will be called. To stop this from happening, you must use break to exit the switch.

C supports three iteration types: pre-testing loop, post-testing loop and counting loop.

The pre-testing loop checks the conditional expression first, and does not run the iteration body at all if its false.

1
2
3
while(a != 1) { ... }

The post-testing loop runs the iteration body at least once, it checks the condition afterwards, and repeats only if its true.

1
2
3
do { ... } while(a != 1);

The counting loop in C is pretty universal. It expects three expressions, in order: initialization, conditional, stepping. Because you can freely define these, it is possible to use multiple variables or whatever expressions you like (not necessarily counting). Example:

1
2
3
for(a = 0; a < 10; a++) { ... }

This is the same as:

1
2
3
4
5
a = 0; while(a < 10) { ... a++; }

You can exit the iteration by using the break statement in its body, but with loops you can also use continue, which breaks the execution of the block too, but instead of exiting it continues from the next iteration.

1
2
3
4
5
for(a = 0; a < 100; a++) { if(a < 10) continue; if(a > 50) break; printf("a value between 10 and 50: %d\n", a); }

Functions

Important

No statements allowed outside of function bodies.

You must divide your programs into smaller programs which might be called multiple times, these are called functions. These are declared as return value's type, name, argument list in ( and ) parenthesis, and function body in a { and } block. Two of these, setup and loop has special meaning, see code editor. The C language does not differentiate between subroutines and functions; everything is a function. The only difference is, functions that do not return a value has the return value's type as void.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void are_we_there_yet(int A) { if(A > 0) { printf("Not yet\n") return; } printf("YES! Do things we wanted to do on arrival\n"); return; } void setup() { /* do it once */ are_we_there_yet(1); /* then do it again */ are_we_there_yet(0); }

Functions are simply called from programs by their names, followed by their argument list in parenthesis (the parenthesis is mandatory, even if the argument list is empty). There's no special command and there's no difference in the call if the function returns a value or not.

Returning from a function is done by the return; instruction. If the function has a return value, then you must specify an expression after the return, and that expression's type must be the same as the function's return type. Specifying return (independly if the function has a return value or not) is mandatory.

1
2
3
4
5
6
7
8
9
str_t mystringfunc() { return "a string"; } void setup() { a = mystringfunc(); }

Provided Functions

The C language has no special commands for input or output; you simply just do MEG-4 API calls for those. The getc returns a character, gets returns a string and you can print out strings with printf.

Under MEG-4 C you use the MEG-4 API exactly as it is specified in this documentation, there are no tricks, no renaming, no suffixes, nor substitutions either.



BASIC

If you want to use this language, then start your program with a #!bas line.

Example Program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!bas REM global variables LET acounter% = 123 LET anumber = 3.1415 LET anaddress% = $0048C LET astring$ = "something" DIM anarray(10) REM Things to do on startup SUB setup REM local variables LET iamlocal = 123 END SUB REM Things to run for every frame, at 60 FPS SUB loop REM BASIC style print PRINT "I"; " am"; " running" REM Get MEG-4 style outputs GOSUB printf("a counter %d, left shift %d\n", acounter%, getkey%(KEY_LSHIFT)) END SUB

Dialect

BASIC stands for Beginners' All-purpose Symbolic Instruction Code. It was created by John Kemeny in 1963 with the explicit goal to teach students programming with it. The MEG-4 BASIC is a bit more modern than that, it supports all of ANSI X3.60-1978 (ECMA-55) and many featues from ANSI X3.113-1987 (ECMA-116) too, with minor deviations. It allows longer than 2 characters identifiers, and its floating point as well as integer arithmetics are 32 bit. Most important differences: no interactive mode, therefore you don't have to number each instruction any more (you can use labels instead), and all BASIC keywords are case-insensitive as in the specification, but variable and function names are case-sensitive. The MEG-4 API function calls must be in lower-case; as for the rest it is up to you, but those are case-sensitive too (for example APPLE, Apple and apple are three distinct variables).

It has one non-standard keyword, the DEBUG, which you can place anywhere in your code and will invoke the built-in debugger. After this, you can execute your code step by step, watching what it is doing.

Here goes a detailed description with examples, and all differences noted.

Literals

MEG-4 BASIC understands decimal based numbers (either integer or floating point with or without scientific notation). Hexadecimal numbers must be prefixed by $ (not in the specification, but this was usual in Commodore BASIC and pretty much in every other dialects of the '80s) and strings must be enclosed in double quotes:

1
2
3
4
5
42 3.1415 .123e-10 $0048C "Goodbye and thanks for all the fish!\n"

Warning

The specification expects 7-bit ASCII, but MEG-4 BASIC uses zero terminated UTF-8 encoding. It also accepts C-like escape sequences (eg. \" is double quotes, \t is tab, \n is the newline character), and the string's maximum size is limited to 255 bytes (the specification requires 18 bytes).

Variables

Variables aren't declared, instead the last letter in their name identifies their type. This can be % for integers, $ for strings, and not in the specification, but MEG-4 BASIC accepts ! for bytes and # for double bytes (word). Anything else is interpreted as a floating point variable.

1
2
3
4
5
LET A% = 42 LET B! = $FF LET C# = $FFFF LET D$ = "string" LET E = 3.1415

The conversion between byte, integer and floating point is automatic and fully transparent. However trying to use a string literal in a number variable or storing a number literal in a string variable would reasult in an error (you must explicitly use STR$ and VAL).

When you assign values to variables, the LET command can be omited.

Literals can also be added to your program using the DATA statement, and then can be assigned to variables with the READ command. READ reads in as many data literals as many variables its argument has, and can be called repeatedly. To reset to the first DATA statement where READ reads from, use RESTORE.

1
2
3
4
RESTORE READ name$, income DATA "Joe", 1234 DATA "John", 2345

There are a few special variables, provided by the system. RND returns a floating point random number between 0 and 1, INKEY$ returns the key the user has pressed or an empty string, TIME returns the number of ticks (1/1000th seconds) since power on, and finally NOW% returns the number of elapsed seconds since Jan 1, 1970 midnight in Greenwich Mean Time.

Arrays

Multiple elements of the same type can be assigned to a single variable, which is called an array.

1
2
3
DIM A(10) DIM B(10, 10) DIM C(1 TO 6)

The BASIC specification expects two-dimensional arrays to be handled, but MEG-4 BASIC supports up to 4 dimensions. Array elements can be bytes, integers, numbers or strings. Dynamically resizing arrays with REDIM is not possible, all are statically allocated using DIM. When the size isn't given, one dimension and 10 elements are assumed.

Warning

Index starts at 1 (as in ANSI and not at 0 as in ECMA-55). The OPTION BASE statement isn't supported, but you can set the first index of each array with the TO keyword.

Operators

In descending order of precedence:

Arithmetic

^
* / MOD
+ -
The first one is for exponentiation (eg. b squared is b^2), multiplication, division and MOD is modulus (aka. remainder after division, eg. 10 MOD 3 is 1), addition and subtraction.

Relational

<> =
<= >= < >
Not equal, equal, less or equal, greater or equal, less than, greater than.

Logical

NOT
AND
OR
Logical negation (0 becomes 1, everything non-zero becomes 0), and (returns 1 if both arguments are non-zero), or (returns 1 if at least one of the arguments are non-zero).

There's one non-standard operator, the @ returns the address of a variable. This is usable when the MEG-4 API expects an addr_t address parameter.

Operators are executed in precedence order, for example in 1+2*3 we have two operators, + and *, but * has the higher precedence, therefore first 2*3 is calculated, and then 1+6. That's why the result is 7 and not 9.

Control Flow

The statement END stops control flow (exists your program).

MEG-4 BASIC does not use line numbers any more, instead it supports GOTO with labels, for example:

1
2
3
this_is_a_label: GOTO this_is_a_label

Warning

Some BASIC dialects allow you to use multiple commands separated by : in one line. In MEG-4 BASIC : identifies a label, so you must use one command per line (as expected by ECMA-55).

Conditional jumps use labels too:

1
2
IF a$ <> b$ THEN this_is_a_label ON a GOTO label1, label2, label3 ELSE labelother

The ON .. GOTO always needs a numerical expression, and chooses the label accordingly, starting from 1 (if the expression is zero or negative, that always jumps to the ELSE label). There's no ON .. GOSUB, because GOSUB does not accept labels in MEG-4 BASIC.

Warning

The GOSUB statement does not accept labels at all, and its semantics are a bit changed in MEG-4 BASIC, see below.

For IF, both numerical and relational expressions are accepted (every non-zero expression considered true), and what's more, multi line IF .. THEN .. ELSE .. END IF blocks are supported too (but no SELECT CASE).

1
2
3
4
5
IF var >= 0 THEN PRINT "var is positive" ELSE PRINT "var is negative" END IF

As an exception, one command is allowed in a single line IF, provided that's a GOTO or an END:

1
2
IF a < 0 THEN GOTO label IF b > 42 THEN END

For iterations, the counting loop checks its condition before the iteration (does not execute the block if the initial value is greater (or less) than the limit), and looks like this:

1
2
3
FOR i = 1 TO 100 STEP 2 ... NEXT i

This FOR .. NEXT is essentially the same as:

1
2
3
4
5
6
7
8
9
LET i = 1 LET lim = 100 LET inc = 2 line1: IF (i - lim) * SGN(inc) > 0 THEN line2 ... LET i = i + inc GOTO line1 line2:

The loop variable must be of float type. The STEP is optional (defaults to 1.0), and the expression after that can be a float literal or another float variable. The from and limit both can be more complex expressions, but they too must return a floating point value.

1
2
3
FOR i = (1+2+a)*3 TO 4*(5+6)/b+c STEP j ... NEXT i

Warning

Unlike the specification, which allows multiple variables after NEXT, MEG-4 BASIC accepts exactly one. So for nested loops you'll have to use multiple NEXT commands, exactly as many as FOR statements there are.

1
2
3
4
5
FOR y = 1 TO 10 FOR x = 1 TO 100 ... NEXT x NEXT y

MEG-4 BASIC has no other kind of loops like C, but if you want a non-counting pre-testing loop, you can do this:

1
2
3
4
5
again: IF a > 0 THEN ... GOTO again END IF

And instead of a post-testing loop:

1
2
3
again: ... IF a > 0 THEN again

Subroutines and Functions

Important

Statements not inside of any subroutine are simply threated as if they were inside the setup subroutine.

You can divide your programs into smaller programs which might be called multiple times, these are called subroutines. They are defined between SUB and END SUB blocks. Two of these, setup and loop has special meaning, see code editor. As mentioned earlier, in MEG-4 BASIC GOSUB does not accept labels, and that's because here it accepts subroutine names only.

1
2
3
4
5
6
7
8
SUB mysubroutine PRINT "do something that you want to do multiple times" END SUB REM do it once GOSUB mysubroutine REM then do it again GOSUB mysubroutine

Control is transferred on GOSUB, and it is returned to the line after GOSUB when END SUB (or the optional RETURN) reached. Subroutines can access global variables and they might have parameters.

1
2
3
4
5
6
7
8
9
10
11
12
SUB are_we_there_yet(A) IF A > 0 THEN PRINT "Not yet" RETURN END IF PRINT "YES! Do things we wanted to do on arrival" END SUB REM do it once GOSUB are_we_there_yet(1) REM then do it again GOSUB are_we_there_yet(0)

Functions are very similar, but they must have a RETURN and their RETURN statement must contain a returned value, same type as identified by the function's name. Functions are simply called from programs by their names, followed by their argument list in parenthesis (the parenthesis is mandatory, even if the argument list is empty). For example:

1
2
3
4
5
FUNCTION mystringfunc$() RETURN "a string" END FUNCTION LET a$ = mystringfunc$()

Print Statement

PRINT expression [;|,] [expression [;|,] [expression [;|,]]] ...

Prints one or more exporession on screen. If the expressions are separated by ; semi-colon, then tightly one after another. If by , colon, then the output will be splitted into coloumns. Numbers are always prefixed by a space, and if the command ends in an expression (not in ; nor in ,), then a newline character will be printed at the end as well.

Input Statement

INPUT "prompt" [;|,] variable

Prints out prompt, then reads in a value from user and stores it in the given variable. If the prompt and the variable is spearated by a , colon, then it also prints a ? question-mark after the prompt.

Warning

The ECMA-55 specification allows multiple variables to be specified, but the MEG-4 BASIC allows only one.

Peek and Poke

You can directly access the MEG-4 memory with these commands, even the MMIO area.

Read
variable = PEEK(address)

Reads in the byte at the given address, converts it into a floating point and stores it in the given variable.

For example, to check if the keyboard queue is not empty:

1
IF PEEK($1A) <> PEEK($1B) THEN

Store
POKE address, expression

Calculates the expression, converts it into a byte and stores that byte value at the given address.

For example, to set the palette for color index 1:

1
2
3
4
5
6
7
8
REM red component POKE $84, 10 REM green component POKE $85, 10 REM blue component POKE $86, 10 REM alpha (transparency) POKE $87, 255

Provided Functions

Some MEG-4 API are provided as system variables, RND (rnd), TIME (time), NOW% (now), and INKEY$ (getc).

Others are provided as commands, INPUT (gets + val), PRINT (printf), PEEK (inb), POKE (outb).

As to comply with ECMA-55, two functions are renamed: SQR (sqrt) and ATN% (atan). All the rest is used as they appear in this documentation, except they are properly suffixed according their return types (for example, str returns a string, so it is called STR$).

Note that ECMA-55 expects that trigonometrical functions use radians by default (with an OPTION command to switch to degrees), however the MEG-4 API always uses degrees, 0 to 359, where 0 is up, and 90 is to the right. That's why the ATN% gets an integer type suffix for example, as it returns degrees in an integer.



Assembly

If you want to use this language, then start your program with a #!asm line.

Example Program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!asm .data /* global variables */ acounter: di 123 anumber: df 3.1415 anaddress: di 0x0048C astring: db "something" anarray: di 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 fmt: db "a counter %d, left shift %d\n" .code /* Things to do on startup */ setup: /* local variables (not really, just reserve space on the stack) */ sp -4 ret /* Things to run for every frame, at 60 FPS */ loop: /* Get MEG-4 style outputs */ pshci KEY_LSHIFT scall getkey sp 4 pushi ci acounter ldi pushi pshci fmt scall printf sp 12 ret

Description

This isn't an actual programming language. When you compile one of the built-in languages, the compiler generates bytecode that the CPU executes. Assembly is a one-to-one transcription of that bytecode with human-readable textual mnemonics. It has two sections, data and code, both containing a series of labels and instructions. Instructions are mnemonics with an optional parameter.

You can play around and experiment with this if you feel brave.

Literals

Same as MEG-4 C literals.

Variables

There's no such thing as a variable in Assembly. Instead you specify the data section with .data, and just place the data after one of the db (byte), dw (word), di (integer), df (float) instructions. In this stream of data, before the instruction, you can place labels which will hold the address of that data. To load a value in your code section, first you put that label in the accumulator register using ci, and then issue one of the ldb (load byte), ldw (load word), ldi (load integer) or ldf (load float) instructions. If the ldb or ldw instructions has a non-zero argument, then they sign-extend the value to 32 bit.

Control Flow

Everything that you place after the .code keyword will be code. There's no implicit control flow, each instruction is executed one after another; you have to alter the PC (program counter) using one of the jmp (jump), jz (jump if zero), jnz (jump if not zero) or sw (switch, case selection) instructions to change the control flow manually.

Functions

There's no function declaration. You just use a label to mark a spot in the code. You push all arguments on the stack in reverse order using pushi and pushf, then you use a call mnemonic with that label. Within the function, you can get the address of these parameters with the adr (address) instruction, with its parameter being the function parameter's number multiplied by four. For example adr 0 loads the first function parameter's address in the accumulator register, and adr 4 loads the second's. You can return from the function with the ret instruction. Return values are returned in the accumulator register, which you can set directly with the ci (constant integer) and cf (constant float) instructions, and indirectly with the popi, popf, ldb, ldw, ldi and ldf instructions. After the call it is the caller's responsibility to remove the parameters from the stack by using an sp + number of parameters times four instruction.

Provided Functions

You have all the MEG-4 API functions at your disposal; using the exact names as they are listed in this documentation.

You have to push all arguments on the stack in reverse order, then use an scall (system call) mnemonic with a MEG-4 API function name as parameter. After the call it is the caller's responsibility to remove the parameters from the stack.

Mnemonics

Before we go into the details, we must talk about the MEG-4 CPU specification.

The MEG-4 CPU is a 32-bit, little endian CPU. All values are stored in a way that smallest digits are placed on the smaller addresses. It is capable of performing operations on 8 bit, 16 bit and 32 bit integers (signed and unsigned) and on 32 bit floating point numbers.

The memory model is flat, meaning all data can be accessed through a single offset. Has no paging and no virtual address translation, no segmentation, except all data and code segment references are implicit (aka. no segment prefixes, referencing segments are automatic).

For security reasons code segment and data segment are separated, as well as the call stack and the data stack. Stack overflow and any other code injection through buffer overflow attacks are simply impossible on this CPU, which makes it very secure and bullet-proof (also supporting 3rd party bytecode like Lua would be impossible without code separation). In this reagard it is more like a Harvard architecture, but in every other aspect it's more like a von Neumann architecture.

The CPU has the following registers:

  • AC: accumulator register, with an integer value
  • AF: accumulator register, with a floating point value
  • FLG: processor flags (setup is done, blocked for I/O, blocked for timer, execution stopped)
  • TMR: the timer register's current value
  • DP: data pointer, this points to the top of the used global variable memory
  • BP: base pointer, marks the top of the function stack frame
  • SP: stack pointer, the bottom of the stack
  • CP: callstack pointer, the top of the callstack
  • PC: program counter, the address of the instruction currently being executed

The data segment is byte based, meaning DP, BP and SP registers point to 8 bit units. You can place data to the data segment using the db (8 bit), dw (16 bit), di (32 bit) and df (32 bit float) mnemonics. These might have one or more comma separated arguments, and for db strings literals and character literals can also be used.

The code segment has a 32 bit granularity, meaning that's the smallest address unit you can use. For this reason the PC points to these 32 bit units and not bytes. The following mnemonics can be used to place instructions to the code segment:

Mnemonic Parameter Description
debug Invoke the built-in debugger (nop for MEG-4 PRO)
ret Return from call, pops from the call stack
scall MEG-4 API function System call
call address/code label Pushes the position to the call stack and then calls a function
jmp address/code label Jump to address
jz address/code label Jump to address if accumulator is zero
jnz address/code label Jump to address if accumulator isn't zero
js address/code label Pop value, adjust its sign and jump to address if negative or zero
jns address/code label Pop value, adjust its sign and jump to address if positive
sw num,addr,addr0,addr1... Switch (see below)
ci number/data label Place an integer value into the accumulator
cf number Place a floating point number into the accumulator
bnd number Checks if the accumulator's value is between 0 and number
lea number Loads the address DP + number into the accumulator
adr number Loads the address BP + number into the accumulator
sp number Adjust SP register by number
pshci number/data label Push an integer constant to the data stack
pshcf number Push a float constant to the data stack
pushi Push the accumulator as integer to the data stack
pushf Push the accumulator as float to the data stack
popi Pop an integer value from the data stack into the accumulator
popf Pop a float value from the data stack into the accumulator
cnvi Convert the value on the top of the stack into an integer
cnvf Convert the value on the top of the stack into a float
ldb 0/1 Loads a byte from the address in the accumulator (sign extend if arg is non-zero)
ldw 0/1 Loads a word from the address in the accumulator (sign extend if arg is non-zero)
ldi Loads an integer from the address in the accumulator
ldf Loads a float from the address in the accumulator
stb Pops the address from stack and stores a byte from accumulator
stw Pops the address from stack and stores a word from accumulator
sti Pops the address from stack and stores an int from accumulator
stf Pops the address from stack and stores a float from accumulator
incb number Pops the address from stack and increase the byte at address by number
incw number Pops the address from stack and increase the word at address by number
inci number Pops the address from stack and increase the integer at address by number
decb number Pops the address from stack and decrease the byte at address by number
decw number Pops the address from stack and decrease the word at address by number
deci number Pops the address from stack and decrease the integer at address by number
not Perform logical NOT on the accumulator
neg Perform bitwise NOT on the accumulator
or Pop a value from stack and perform bitwise OR on the accumulator
xor Pop a value from stack and perform EXCLUSIVE OR on the accumulator
and Pop a value from stack and perform bitwise AND on the accumulator
shl Pop a value and shift accumulator bits to the left, place result in accumulator
shr Pop a value and shift accumulator bits to the right, place result in accumulator
eq Pop a value and set accumulator if accumulator is the same as the popped
ne Pop a value and set accumulator if accumulator isn't the same
lts Pop a value and set accumulator if it's less than as signed
gts Pop a value and set accumulator if it's greater than as signed
les Pop a value and set accumulator if it's less or equal as signed
ges Pop a value and set accumulator if it's greater or equal as signed
ltu Pop a value and set accumulator if it's less than as unsigned
gtu Pop a value and set accumulator if it's greater than as unsigned
leu Pop a value and set accumulator if it's less or equal as unsigned
geu Pop a value and set accumulator if it's greater or equal as unsigned
ltf Pop a value and set accumulator if it's less than as float
gtf Pop a value and set accumulator if it's greater than as float
lef Pop a value and set accumulator if it's less or equal as float
gef Pop a value and set accumulator if it's greater or equal as float
addi Pop a value and add it to the accumulator as integer
subi Pop a value and subtract the accumulator from it as integer
muli Pop a value and multiply the accumulator with it as integer
divi Pop a value and divide by the accumulator as integer
modi Pop a value, divide and put the remainder in accumulator as integer
powi Pop a value and raise to the power of accumulator as interger
addf Pop a value and add it to the accumulator as float
subf Pop a value and subtract the accumulator from it as float
mulf Pop a value and multiply the accumulator with it as float
divf Pop a value and divide by the accumulator as float
modf Pop a value, divide and put the fractional in accumulator as float
powf Pop a value and raise to the power of accumulator as float

The sw mnemonic has variable number (but at least three) arguments. The first one is a number, the second is a code label, as well as all the rest are code labels. It subtracts the number from the accumulator and checks if the result is positive and less than the number of the labels given. If not, then it jumps to the first label in the second argument. If it is, then it picks the accumulatorth label starting from the third parameter (second label), and jumps there.

So in a nutshell it is

sw (value), (label to jump to otherwise),
  (label to jump to if accumulator equals value),
  (label to jump to if accumulator equals value + 1),
  (label to jump to if accumulator equals value + 2),
  (label to jump to if accumulator equals value + 3),
  ...
  (label to jump to if accumulator equals value + N)

Every sw mnemonic might have up to 256 value labels.



Lua

If you want to use this language, then start your program with a #!lua line.

Example Program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!lua -- global variables acounter = 123 anumber = 3.1415 anaddress = 0x0048C astring = "something" anarray = {} -- Things to do on startup function setup() -- local variables iamlocal = 234 end -- Things to run for every frame, at 60 FPS function loop() -- Lua style print print("I", "am", "running") -- Get MEG-4 style outputs printf("a counter %d, left shift %d\n", acounter, getkey(KEY_LSHIFT)) end

Further Info

Unlike the other languages, this isn't integral part of MEG-4, rather provided by a thrid party library. Due to that it does not (and cannot) have perfect integration (no debugger and no translated error messages for example). Its runner is bloated and much slower compared to the other languages, but it works, and you can use it.

The embedded version is Lua 5.4.6, with modifications. For security reasons it lacks concurrency, as well as module loading, file access, pipes, command execution. The coroutine, io and os modules and their functions aren't available (but the language features and all the other parts of the baselib are still there). Instead of these, it has the MEG-4 API, which can be used as in any other language, with some slight, minor differences for better integration.

If you're interested in this language then you can find more information in the Programming in Lua documentation.

API Differences

  • memsave can accept either a MEG-4 memory address (integer) or a Lua table with integers (supposed to be a byte array).
  • memload on call, a valid MEG-4 memory address must be passed, but then it returns a Lua table anyway. If you don't want to load to a specific MMIO area, then specify MEM_USER (0x30000) and just simply use the data in the returned Lua table.
  • memcpy parameters can be two MEG-4 memory addresses as usual, or one of them can be a Lua table (just one, not both). You can use this function to copy data between MEG-4 memory and Lua (but inb and outb also works).
  • remap only accepts a Lua table (with 256 integer values).
  • maze instead of the last two parameters (numnpc and npc) one single Lua table (with each element being another table) can be used.
  • printf, sprintf and trace does not use MEG-4's format string rules, but Lua's (however these two are almost entirely identical).


Memory Map

Misc

All values are little endian, so the smaller digit is stored on the smaller address.

Offset Size Description
00000 1 MEG-4 firmware version major
00001 1 MEG-4 firmware version minor
00002 1 MEG-4 firmware version bugfix
00003 1 performance counter, last frame's unspent time in 1/1000th secs
00004 4 number of 1/1000th second ticks since power on
00008 8 UTC unix timestamp
00010 2 current locale

The performance counter shows the time unspent when the last frame was generated. If this is zero or negative, then it means how much your loop() function has overstepped its available timeframe.

Pointer

Offset Size Description
00012 2 pointer buttons state (see getbtn and getclk)
00014 2 pointer sprite index
00016 2 pointer X coordinate
00018 2 pointer Y coordinate

The pointer buttons are as follows:

Define Bitmask Description
BTN_L 1 Left mouse button
BTN_M 2 Middle mouse button
BTN_R 4 Right mouse button
SCR_U 8 Scroll up
SCR_D 16 Scroll down
SCR_L 32 Scroll left
SCR_R 64 Scroll right

The upper bits of the pointer sprite index are used for hotspots: bit 13-15 hotspot Y, bit 10-12 hotspot X, bit 0-9 sprite. There are some predefined built-in cursors:

Define Value Description
PTR_NORM 03fb Normal (arrow) pointer
PTR_TEXT 03fc Text pointer
PTR_HAND 0bfd Link pointer
PTR_ERR 93fe Error pointer
PTR_NONE ffff The pointer is hidden

Keyboard

Offset Size Description
0001A 1 keyboard queue tail
0001B 1 keyboard queue head
0001C 64 keyboard queue, 16 elements, each 4 bytes (see popkey)
0005C 18 keyboard keys pressed state by scancodes (see getkey)

The keys popped from the queue are represented in UTF-8. Some invalid UTF-8 sequences represent special (non-printable) keys, for example:

Keycode Description
\x8 The character 8, Backspace key
\x9 The character 9, Tab key
\n The character 10, ⏎Enter key
\x1b The character 27, Esc key
Del The Del key
Up The cursor arrow key
Down The cursor arrow key
Left The cursor arrow key
Rght The cursor arrow key
Cut The Cut key (or Ctrl+X)
Cpy The Copy key (or Ctrl+C)
Pst The Paste key (or Ctrl+V)

The scancodes are as follows:

ScanCode Address Bitmask Define
0 0005C 1 KEY_CHEAT
1 0005C 2 KEY_F1
2 0005C 4 KEY_F2
3 0005C 8 KEY_F3
4 0005C 16 KEY_F4
5 0005C 32 KEY_F5
6 0005C 64 KEY_F6
7 0005C 128 KEY_F7
8 0005D 1 KEY_F8
9 0005D 2 KEY_F9
10 0005D 4 KEY_F10
11 0005D 8 KEY_F11
12 0005D 16 KEY_F12
13 0005D 32 KEY_PRSCR
14 0005D 64 KEY_SCRLOCK
15 0005D 128 KEY_PAUSE
16 0005E 1 KEY_BACKQUOTE
17 0005E 2 KEY_1
18 0005E 4 KEY_2
19 0005E 8 KEY_3
20 0005E 16 KEY_4
21 0005E 32 KEY_5
22 0005E 64 KEY_6
23 0005E 128 KEY_7
24 0005F 1 KEY_8
25 0005F 2 KEY_9
26 0005F 4 KEY_0
27 0005F 8 KEY_MINUS
28 0005F 16 KEY_EQUAL
29 0005F 32 KEY_BACKSPACE
30 0005F 64 KEY_TAB
31 0005F 128 KEY_Q
32 00060 1 KEY_W
33 00060 2 KEY_E
34 00060 4 KEY_R
35 00060 8 KEY_T
36 00060 16 KEY_Y
37 00060 32 KEY_U
38 00060 64 KEY_I
39 00060 128 KEY_O
40 00061 1 KEY_P
41 00061 2 KEY_LBRACKET
42 00061 4 KEY_RBRACKET
43 00061 8 KEY_ENTER
44 00061 16 KEY_CAPSLOCK
45 00061 32 KEY_A
46 00061 64 KEY_S
47 00061 128 KEY_D
48 00062 1 KEY_F
49 00062 2 KEY_G
50 00062 4 KEY_H
51 00062 8 KEY_J
52 00062 16 KEY_K
53 00062 32 KEY_L
54 00062 64 KEY_SEMICOLON
55 00062 128 KEY_APOSTROPHE
56 00063 1 KEY_BACKSLASH
57 00063 2 KEY_LSHIFT
58 00063 4 KEY_LESS
59 00063 8 KEY_Z
60 00063 16 KEY_X
61 00063 32 KEY_C
62 00063 64 KEY_V
63 00063 128 KEY_B
64 00064 1 KEY_N
65 00064 2 KEY_M
66 00064 4 KEY_COMMA
67 00064 8 KEY_PERIOD
68 00064 16 KEY_SLASH
69 00064 32 KEY_RSHIFT
70 00064 64 KEY_LCTRL
71 00064 128 KEY_LSUPER
72 00065 1 KEY_LALT
73 00065 2 KEY_SPACE
74 00065 4 KEY_RALT
75 00065 8 KEY_RSUPER
76 00065 16 KEY_MENU
77 00065 32 KEY_RCTRL
78 00065 64 KEY_INS
79 00065 128 KEY_HOME
80 00066 1 KEY_PGUP
81 00066 2 KEY_DEL
82 00066 4 KEY_END
83 00066 8 KEY_PGDN
84 00066 16 KEY_UP
85 00066 32 KEY_LEFT
86 00066 64 KEY_DOWN
87 00066 128 KEY_RIGHT
88 00067 1 KEY_NUMLOCK
89 00067 2 KEY_KP_DIV
90 00067 4 KEY_KP_MUL
91 00067 8 KEY_KP_SUB
92 00067 16 KEY_KP_7
93 00067 32 KEY_KP_8
94 00067 64 KEY_KP_9
95 00067 128 KEY_KP_ADD
96 00068 1 KEY_KP_4
97 00068 2 KEY_KP_5
98 00068 4 KEY_KP_6
99 00068 8 KEY_KP_1
100 00068 16 KEY_KP_2
101 00068 32 KEY_KP_3
102 00068 64 KEY_KP_ENTER
103 00068 128 KEY_KP_0
104 00069 1 KEY_KP_DEC
105 00069 2 KEY_INT1
106 00069 4 KEY_INT2
107 00069 8 KEY_INT3
108 00069 16 KEY_INT4
109 00069 32 KEY_INT5
110 00069 64 KEY_INT6
111 00069 128 KEY_INT7
112 0006A 1 KEY_INT8
113 0006A 2 KEY_LNG1
114 0006A 4 KEY_LNG2
115 0006A 8 KEY_LNG3
116 0006A 16 KEY_LNG4
117 0006A 32 KEY_LNG5
118 0006A 64 KEY_LNG6
119 0006A 128 KEY_LNG7
120 0006B 1 KEY_LNG8
121 0006B 2 KEY_APP
122 0006B 4 KEY_POWER
123 0006B 8 KEY_KP_EQUAL
124 0006B 16 KEY_EXEC
125 0006B 32 KEY_HELP
126 0006B 64 KEY_SELECT
127 0006B 128 KEY_STOP
128 0006C 1 KEY_AGAIN
129 0006C 2 KEY_UNDO
130 0006C 4 KEY_CUT
131 0006C 8 KEY_COPY
132 0006C 16 KEY_PASTE
133 0006C 32 KEY_FIND
134 0006C 64 KEY_MUTE
135 0006C 128 KEY_VOLUP
136 0006D 1 KEY_VOLDN

Gamepad

Offset Size Description
0006E 2 gamepad joystick threshold (defaults to 8000)
00070 8 primary gamepad - keyboard scancode mappings (see keyboard)
00078 4 4 gamepads button pressed state (see getpad)

The gamepad buttons are as follows:

Define Bitmask Description
BTN_L 1 The button or joystick left
BTN_U 2 The button or joystick up
BTN_R 4 The button or joystick right
BTN_D 8 The button or joystick down
BTN_A 16 The button
BTN_B 32 The button
BTN_X 64 The button
BTN_Y 128 The button

The △△▽▽◁▷◁▷ⒷⒶ sequence makes the KEY_CHEAT "key" pressed.

Graphics Processing Unit

Offset Size Description
0007E 1 UNICODE code point upper bits for font glyph mapping
0007F 1 sprite bank selector for the map
00080 1024 palette, 256 colors, each entry 4 bytes, RGBA
00480 2 x0, crop area X start in pixels (for all drawing functions)
00482 2 x1, crop area X end in pixels
00484 2 y0, crop area Y start in pixels
00486 2 y1, crop area Y end in pixels
00488 2 displayed vram X offset in pixels or 0xffff
0048A 2 displayed vram Y offset in pixels or 0xffff
0048C 1 turtle pen down flag (see up, down)
0048D 1 turtle pen color, palette index 0 to 255 (see color)
0048E 2 turtle direction in degrees, 0 to 359 (see left, right)
00490 2 turtle X offset in pixels (see move)
00492 2 turtle Y offset in pixels
00494 2 maze walking speed in 1/128 tiles (see maze)
00496 2 maze rotating speed in degrees (1 to 90)
00498 1 console foreground color, palette index 0 to 255 (see printf)
00499 1 console background color, palette index 0 to 255
0049A 2 console X offset in pixels
0049C 2 console Y offset in pixels
0049E 2 camera X offset in 3D space (see tri3d, tritx, mesh)
004A0 2 camera Y offset
004A2 2 camera Z offset
004A4 2 camera direction, pitch (0 up, 90 forward)
004A6 2 camera direction, yaw (0 left, 90 forward)
004A8 1 camera field of view in angles (45, negative gives orthographic)
004AA 2 light source position X offset (see tri3d, tritx, mesh)
004AC 2 light source position Y offset
004AE 2 light source position Z offset
00600 64000 map, 320 x 200 sprite indices (see map and maze)
10000 65536 sprites, 256 x 256 palette indices, 1024 8 x 8 pixels (see spr)
28000 2048 window for 4096 font glyphs (see 0007E, width and text)

Digital Signal Processor

Offset Size Description
0007C 1 waveform bank selector (1 to 31)
0007D 1 music track bank selector (0 to 7)
004BA 1 current tempo (in ticks per row, read-only)
004BB 1 current track being played (read-only)
004BC 2 current row being played (read-only)
004BE 2 number of total rows in current track (read-only)
004C0 64 16 channel status registers, each 4 bytes (read-only)
00500 256 64 sound effects, each 4 bytes
20000 16384 window for waveform samples (see 0007C)
24000 16384 window for music patterns (see 0007D)

The DSP status registers are all read-only, and for each channel they look like:

Offset Size Description
0 2 current position in the waveform being played
2 1 current waveform (1 to 31, 0 if the channel is silent)
3 1 current volume (0 means channel is turned off)

The first 4 channels are for the music, the rest for the sound effects.

Note that the waveform index 0 means "use the previous waveform", so index 0 cannot be used in the selector. The format of every other waveform:

Offset Size Description
0 2 number of samples
2 2 loop start
4 2 loop length
6 1 finetune value, -8 to 7
7 1 volume, 0 to 64
8 16376 signed 8-bit mono samples

The format of the sound effects and the music tracks are the same, the only difference is, music tracks have 4 notes per row, one for each channel, and there are 1024 rows; while for sound effects there's only one note and 64 rows.

Offset Size Description
0 1 note number, see NOTE_x defines, 0 to 96
1 1 waveform index, 0 to 31
2 1 effect type, 0 to 255 (see note effects)
3 1 effect parameter

The counting of notes goes as follows: 0 means no note set. Followed by 8 octaves, each with 12 notes, so 1 equals to C-0, 12 is B-0 (on the lowest octave), 13 is C-1 (one octave higher) and 14 is C#1 (C sharp, semitone higher). For example the D note on the 4th octave would be 1 + 4*12 + 2 = 51. The B-7 is 96, the highest note on the highest octave. You also have built-in defines, for example C-1 is NOTE_C_1 and C#1 is NOTE_Cs1, if you don't want to count then you can use these as well in your program.

Note Effects

For simplicity, MEG-4 uses the same codes as the Amiga MOD file (this way you'll see the same in the built-in editor as well as in a third party music tracker), but it does not support all of them. As said earlier, these codes are represented by three hex numbers, the first being the type t, and the last two the parameter, xy (or xx). The types E1 to ED are all stored in the type byte, although it looks like one of their nibble might belong to the parameter, but it's not.

Effect Code Description
... 000 No effect
Arp 0xy Arpeggio, play note, note+x semitone and note+y semitone
Po/ 1xx Portamento up, slide period by x up
Po\ 2xx Portamento down, slide period by x down
Ptn 3xx Tone portamento, slide period to period x
Vib 4xy Vibrato, oscillate the pitch by y semitones at x freq
Ctv 5xy Continue Tone portamento + volume slide by x up or y down
Cvv 6xy Continue Vibrato + volume slide by x up or by y down
Trm 7xy Tremolo, oscillate the volume by y amplitude at x freq
Ofs 9xx Set sample offset to x * 256
Vls Axy Slide volume by x up or by y down
Jmp Bxx Position jump, set row to x * 64
Vol Cxx Set volume to x
Fp/ E1x Fine portamento up, increase period by x
Fp\ E2x Fine portamento down, decrease period by x
Svw E4x Set vibrato waveform, 0 sine, 1 saw, 2 square, 3 noise
Ftn E5x Set finetune, change tuning by x (-8 to 7)
Stw E7x Set tremolo waveform, 0 sine, 1 saw, 2 square, 3 noise
Rtg E9x Retrigger note, trigger current sample every x ticks
Fv/ EAx Fine volume slide up, increase by x
Fv\ EBx Fine volume slide down, decrease by x
Cut ECx Cut note in x ticks
Dly EDx Delay note in x ticks
Tpr Fxx Set number of ticks per row to x (tick defalts to 6)

User Memory

Memory addresses from 00000 to 2FFFF belong to the MMIO, everything above (starting from 30000 or MEM_USER) is freely usable user memory.

Offset Size Description
30000 4 (BASIC only) offset of DATA
30004 4 (BASIC only) current READ counter
30008 4 (BASIC only) maximum READ, number of DATA

This is followed by the global variables that you have declared in your program, followed by the constants, like string literals. In case of BASIC, then this is followed by the actual DATA records.

Memory addresses above the initialized data can be dynamically allocated and freed in your program via the malloc and free calls.

Lastly the stack, which is at the top (starting from C0000 or MEM_LIMIT) and growing downwards. Your program's local variables (that you declared inside a function) go here. The size of the stack always changes depending on which function calls which other function in your program.

If by any chance the top of the dynamically allocated data and the bottom of the stack would overlap, then MEG-4 throws an "Out of memory" error.

Format String

Some functions, printf, sprintf and trace use a format string that may contain special characters to reference arguments and to describe how to display them. These are:

Code Description
%% The % character
%d Next parameter as a decimal number
%u Next parameter as an unsigned decimal number
%x Next parameter as a hexadecimal number
%o Next parameter as an octal number
%b Next parameter as a binary number
%f Next parameter as a floating point number
%s Next parameter as a string
%c Next parameter as an UTF-8 character
%p Next parameter as an address (pointer)
\t Tab, fix vertical position before continue
\n Start a new line

You can add padding by specifying the length between % and the code. If that starts with 0, then value will be padded with zeros, otherwise with spaces. For example %4d will pad the value to the right with spaces, and %04x with zeros. The f accepts a number after a dot, which tells the number of digits in the fractional part (up to 8), eg. %.6f.

3D Space

In MEG-4, the 3 dimensional space is handled according to the right-hand rule: +X is on the right, +Y is up, and +Z is towards the viewer.

  +Y
   |
   |__ +X
  /
+Z

Each point must be placed in the range -32767 to +32767. How this 3D world is projected to your 2D screen depends on how you configure the camera (see Graphics Processing Unit address 0049E). Of course, you have to place the camera in the world, with X, Y, Z coordinates. Then you have to tell where the camera is looking at, using pitch and yaw. Finally you also have to tell what kind of lens the camera has, by specifying the field of view angle. That latter normally should be between 30 (very narrow) and 180 degrees (like fish and birds). MEG-4 supports up to 127 degrees, but there's a trick. Positive FOV values will be projected as perspective (the farther the object is, the smaller it is), but negative values also handled, just with orthographic projection (no matter the distance, the object's size will be the same). Perspective is used in FPS games, while the orthographic projection is mostly preferred by strategy games.

You can display a set of triangles (a complete 3D model) using the mesh function efficiently. Because models probably have local coordinates, that would draw all models one on top of another around the origo. So if you want to dispay multiple models in the world, first you should translate them (place them) into world coordinates using trns, and then use the translated vertex cloud with mesh (moving and rotating the model around won't change the triangles, just their vertex coordinates).



Console

putc

1
void putc(uint32_t chr)
Description
Prints a character to console.
Parameters
Argument Description
chr UTF-8 character

printf

1
void printf(str_t fmt, ...)
Description
Prints text to console.
Parameters
Argument Description
fmt string to display, a format string
... optional arguments

getc

1
uint32_t getc(void)
Description
Reads a character from console, blocks program when there's no input.
Return Value
An UTF-8 character the user entered.
See Also
popkey

gets

1
str_t gets(void)
Description
Reads in a newline terminated string from user (does not return the newline).
Return Value
The bytes read in a string.

trace

1
void trace(str_t fmt, ...)
Description
Trace execution by printing to stdout. Only works if meg4 was started with the -v verbose flag.
Parameters
Argument Description
fmt format string
... optional arguments

delay

1
void delay(uint16_t msec)
Description
Delays program execution.
Parameters
Argument Description
msec delay in milliseconds

exit

1
void exit(void)
Description
Exits program.


Audio

sfx

1
void sfx(uint8_t sfx, uint8_t channel, uint8_t volume)
Description
Plays a sound effect.
Parameters
Argument Description
sfx the index of the sound effect, 0 to 63
channel channel to be used, 0 to 11
volume volume to be used, 0 to 255, 0 turns off channel

music

1
void music(uint8_t track, uint16_t row, uint8_t volume)
Description
Plays a music track.
Parameters
Argument Description
track the index of the music track, 0 to 7
row row to start playing from, 0 to 1023 (max song length)
volume volume to be used, 0 to 255, 0 turns off music


GPIO

gpio_rev

1
uint32_t gpio_rev(void)
Description
Query the GPIO board's revision number. Returns 0 if the platform isn't GPIO capable.
Return Value
Board's revision number or 0 if not supported.

gpio_get

1
int gpio_get(uint8_t pin)
Description
Read the value of a GPIO pin.
Parameters
Argument Description
pin physical pin number, 1 to 40
Return Value
Returns 1 if the pin is high, 0 if it's low, -1 on error (GPIO pin not supported).
See Also
gpio_set

gpio_set

1
int gpio_set(uint8_t pin, int value)
Description
Write the value to a GPIO pin.
Parameters
Argument Description
pin physical pin number, 1 to 40
value 1 to set the pin high, 0 for low
Return Value
Returns 0 on success, -1 on error (GPIO pin not supported).
See Also
gpio_get


Graphics

cls

1
void cls(uint8_t palidx)
Description
Clears the entire screen and resets display offsets, also sets the console's background color.
Parameters
Argument Description
palidx color, palette index 0 to 255
See Also
pget, pset

cget

1
uint32_t cget(uint16_t x, uint16_t y)
Description
Get pixel at a coordinate and return color RGBA.
Parameters
Argument Description
x X coordinate in pixels
y Y coordinate in pixels
Return Value
A packed color, with RGBA channels (red is in the least significant byte).
See Also
cls, pget, pset

pget

1
uint8_t pget(uint16_t x, uint16_t y)
Description
Get pixel at a coordinate and return its palette index.
Parameters
Argument Description
x X coordinate in pixels
y Y coordinate in pixels
Return Value
Color in palette index, 0 to 255.
See Also
cls, pset, cget

pset

1
void pset(uint8_t palidx, uint16_t x, uint16_t y)
Description
Plots a pixel at a coordinate.
Parameters
Argument Description
palidx color, palette index 0 to 255
x X coordinate in pixels
y Y coordinate in pixels
See Also
cls, pget

width

1
uint16_t width(int8_t type, str_t str)
Description
Returns displayed text's width in pixels.
Parameters
Argument Description
type font type, -4 to 4
str string to measure
Return Value
Number of pixels required to display text.
See Also
text

text

1
void text(uint8_t palidx, int16_t x, int16_t y, int8_t type, uint8_t shidx, uint8_t sha, str_t str)
Description
Prints text on screen.
Parameters
Argument Description
palidx color, palette index 0 to 255
x X coordinate in pixels
y Y coordinate in pixels
type font type, -4 to -1 monospace, 1 to 4 proportional
shidx shadow's color, palette index 0 to 255
sha shadow's alpha, 0 (fully transparent) to 255 (fully opaque)
str string to display
See Also
width

line

1
void line(uint8_t palidx, int16_t x0, int16_t y0, int16_t x1, int16_t y1)
Description
Draws an anti-aliased line.
Parameters
Argument Description
palidx color, palette index 0 to 255
x0 starting X coordinate in pixels
y0 starting Y coordinate in pixels
x1 ending X coordinate in pixels
y1 ending Y coordinate in pixels
See Also
qbez, cbez

qbez

1
2
void qbez(uint8_t palidx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t cx, int16_t cy)
Description
Draws a quadratic Bezier curve.
Parameters
Argument Description
palidx color, palette index 0 to 255
x0 starting X coordinate in pixels
y0 starting Y coordinate in pixels
x1 ending X coordinate in pixels
y1 ending Y coordinate in pixels
cx control point X coordinate in pixels
cy control point Y coordinate in pixels
See Also
line, cbez

cbez

1
2
void cbez(uint8_t palidx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t cx0, int16_t cy0, int16_t cx1, int16_t cy1)
Description
Draws a cubic Bezier curve.
Parameters
Argument Description
palidx color, palette index 0 to 255
x0 starting X coordinate in pixels
y0 starting Y coordinate in pixels
x1 ending X coordinate in pixels
y1 ending Y coordinate in pixels
cx0 first control point X coordinate in pixels
cy0 first control point Y coordinate in pixels
cx1 second control point X coordinate in pixels
cy1 second control point Y coordinate in pixels
See Also
line, qbez

tri

1
void tri(uint8_t palidx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2)
Description
Draws a triangle.
Parameters
Argument Description
palidx color, palette index 0 to 255
x0 first edge X coordinate in pixels
y0 first edge Y coordinate in pixels
x1 second edge X coordinate in pixels
y1 second edge Y coordinate in pixels
x2 third edge X coordinate in pixels
y2 third edge Y coordinate in pixels
See Also
ftri, tri2d, tri3d, tritx, mesh, trns

ftri

1
void ftri(uint8_t palidx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2)
Description
Draws a filled triangle.
Parameters
Argument Description
palidx color, palette index 0 to 255
x0 first edge X coordinate in pixels
y0 first edge Y coordinate in pixels
x1 second edge X coordinate in pixels
y1 second edge Y coordinate in pixels
x2 third edge X coordinate in pixels
y2 third edge Y coordinate in pixels
See Also
tri, tri2d, tri3d, tritx, mesh, trns

tri2d

1
2
3
void tri2d(uint8_t pi0, int16_t x0, int16_t y0, uint8_t pi1, int16_t x1, int16_t y1, uint8_t pi2, int16_t x2, int16_t y2)
Description
Draws a filled triangle with color gradients.
Parameters
Argument Description
pi0 first edge color, palette index 0 to 255
x0 first edge X coordinate in pixels
y0 first edge Y coordinate in pixels
pi1 second edge color, palette index 0 to 255
x1 second edge X coordinate in pixels
y1 second edge Y coordinate in pixels
pi2 third edge color, palette index 0 to 255
x2 third edge X coordinate in pixels
y2 third edge Y coordinate in pixels
See Also
tri, ftri, tri3d, tritx, mesh, trns

tri3d

1
2
3
void tri3d(uint8_t pi0, int16_t x0, int16_t y0, int16_t z0, uint8_t pi1, int16_t x1, int16_t y1, int16_t z1, uint8_t pi2, int16_t x2, int16_t y2, int16_t z2)
Description
Draws a filled triangle with color gradients in 3D space.
Parameters
Argument Description
pi0 first edge color, palette index 0 to 255
x0 first edge X coordinate in space
y0 first edge Y coordinate in space
z0 first edge Z coordinate in space
pi1 second edge color, palette index 0 to 255
x1 second edge X coordinate in space
y1 second edge Y coordinate in space
z1 second edge Z coordinate in space
pi2 third edge color, palette index 0 to 255
x2 third edge X coordinate in space
y2 third edge Y coordinate in space
z2 third edge Z coordinate in space
See Also
tri, ftri, tri2d, tritx, mesh, trns

tritx

1
2
3
void tritx(uint8_t u0, uint8_t v0, int16_t x0, int16_t y0, int16_t z0, uint8_t u1, uint8_t v1, int16_t x1, int16_t y1, int16_t z1, uint8_t u2, uint8_t v2, int16_t x2, int16_t y2, int16_t z2)
Description
Draws a textured triangle in 3D space.
Parameters
Argument Description
u0 first edge texture X coordinate 0 to 255
v0 first edge texture Y coordinate 0 to 255
x0 first edge X coordinate in space
y0 first edge Y coordinate in space
z0 first edge Z coordinate in space
u1 second edge texture X coordinate 0 to 255
v1 second edge texture Y coordinate 0 to 255
x1 second edge X coordinate in space
y1 second edge Y coordinate in space
z1 second edge Z coordinate in space
u2 third edge texture X coordinate 0 to 255
v2 third edge texture Y coordinate 0 to 255
x2 third edge X coordinate in space
y2 third edge Y coordinate in space
z2 third edge Z coordinate in space
See Also
tri, ftri, tri2d, tri3d, mesh, trns

mesh

1
void mesh(addr_t verts, addr_t uvs, uint16_t numtri, addr_t tris)
Description
Draws a mesh made of triangles in 3D space, using indices to vertices and texture coordinates (or palette).
Parameters
Argument Description
verts address of vertices array, 3 x 2 bytes each, X, Y, Z
uvs address of UVs array (if 0, then palette is used), 2 x 1 bytes each, texture X, Y
numtri number of triangles
tris address of triangles array with indices, 6 x 1 bytes each, vi1, ui1/pi1, vi2, ui2/pi2, vi3, ui3/pi3
See Also
tri, ftri, tri2d, tri3d, tritx, trns

rect

1
void rect(uint8_t palidx, int16_t x0, int16_t y0, int16_t x1, int16_t y1)
Description
Draws a rectangle.
Parameters
Argument Description
palidx color, palette index 0 to 255
x0 top left corner X coordinate in pixels
y0 top left corner Y coordinate in pixels
x1 bottom right X coordinate in pixels
y1 bottom right Y coordinate in pixels
See Also
frect

frect

1
void frect(uint8_t palidx, int16_t x0, int16_t y0, int16_t x1, int16_t y1)
Description
Draws a filled rectangle.
Parameters
Argument Description
palidx color, palette index 0 to 255
x0 top left corner X coordinate in pixels
y0 top left corner Y coordinate in pixels
x1 bottom right X coordinate in pixels
y1 bottom right Y coordinate in pixels
See Also
rect

circ

1
void circ(uint8_t palidx, int16_t x, int16_t y, uint16_t r)
Description
Draws a circle.
Parameters
Argument Description
palidx color, palette index 0 to 255
x center X coordinate in pixels
y center Y coordinate in pixels
r radius in pixels
See Also
fcirc, ellip, fellip

fcirc

1
void fcirc(uint8_t palidx, int16_t x, int16_t y, uint16_t r)
Description
Draws a filled circle.
Parameters
Argument Description
palidx color, palette index 0 to 255
x center X coordinate in pixels
y center Y coordinate in pixels
r radius in pixels
See Also
circ, ellip, fellip

ellip

1
void ellip(uint8_t palidx, int16_t x0, int16_t y0, int16_t x1, int16_t y1)
Description
Draws an ellipse.
Parameters
Argument Description
palidx color, palette index 0 to 255
x0 top left corner X coordinate in pixels
y0 top left corner Y coordinate in pixels
x1 bottom right X coordinate in pixels
y1 bottom right Y coordinate in pixels
See Also
circ, fcirc, fellip

fellip

1
void fellip(uint8_t palidx, int16_t x0, int16_t y0, int16_t x1, int16_t y1)
Description
Draws a filled ellipse.
Parameters
Argument Description
palidx color, palette index 0 to 255
x0 top left corner X coordinate in pixels
y0 top left corner Y coordinate in pixels
x1 bottom right X coordinate in pixels
y1 bottom right Y coordinate in pixels
See Also
circ, fcirc, ellip

move

1
void move(int16_t x, int16_t y, uint16_t deg)
Description
Moves turtle to the given position on screen or in the maze.
Parameters
Argument Description
x X coordinate in pixels (or 1/128 tiles with maze)
y Y coordinate in pixels
deg direction in degrees, 0 to 359, 0 is upwards on screen, 90 is to the right
See Also
left, right, up, down, color, forw, back

left

1
void left(uint16_t deg)
Description
Turns turtle left.
Parameters
Argument Description
deg change in degrees, 0 to 359
See Also
move, right, up, down, color, forw, back

1
void right(uint16_t deg)
Description
Turns turtle right.
Parameters
Argument Description
deg change in degrees, 0 to 359
See Also
move, left, up, down, color, forw, back

up

1
void up(void)
Description
Lifts turtle's tail up. The turtle will move without drawing a line.
See Also
move, left, right, down, color, forw, back

down

1
void down(void)
Description
Puts turtle's tail down. The turtle will move drawing a line (see color).
See Also
move, left, right, up, color, forw, back

color

1
void color(uint8_t palidx)
Description
Sets turtle paint color.
Parameters
Argument Description
palidx color, palette index 0 to 255
See Also
move, left, right, up, down, forw, back

forw

1
void forw(uint16_t cnt)
Description
Moves turtle forward.
Parameters
Argument Description
cnt amount in pixels (or 1/128 tiles with maze)
See Also
move, left, right, up, down, color, back

back

1
void back(uint16_t cnt)
Description
Moves turtle backward.
Parameters
Argument Description
cnt amount in pixels (or 1/128 tiles with maze)
See Also
move, left, right, up, down, color, forw

spr

1
void spr(int16_t x, int16_t y, uint16_t sprite, uint8_t sw, uint8_t sh, int8_t scale, uint8_t type)
Description
Displays a sprite, or multiple adjacent sprites.
Parameters
Argument Description
x X coordinate in pixels
y Y coordinate in pixels
sprite sprite id, 0 to 1023
sw number of horizontal sprites
sh number of vertical sprites
scale scale, -3 to 4
type transform, 0=normal, 1=rotate 90, 2=rotate 180, 3=rotate 270, 4=flip vertically, 5=flip+90, 6=flip horizontally, 7=flip+270
See Also
dlg, stext

dlg

1
2
3
4
void dlg(int16_t x, int16_t y, uint16_t w, uint16_t h, int8_t scale, uint16_t tl, uint16_t tm, uint16_t tr, uint16_t ml, uint16_t bg, uint16_t mr, uint16_t bl, uint16_t bm, uint16_t br)
Description
Displays a dialog window using sprites.
Parameters
Argument Description
x X coordinate in pixels
y Y coordinate in pixels
w dialog width in pixels
h dialog height in pixels
scale scale, -3 to 4
tl top left corner sprite id
tm top middle sprite id
tr top right corner sprite id
ml middle left side sprite id
bg background sprite id
mr middle right side sprite id
bl bottom left corner sprite id
bm bottom middle sprite id
br bottom right corner sprite id
See Also
spr, stext

stext

1
void stext(int16_t x, int16_t y, uint16_t fs, uint16_t fu, uint8_t sw, uint8_t sh, int8_t scale, str_t str)
Description
Displays text on screen using sprites.
Parameters
Argument Description
x X coordinate in pixels
y Y coordinate in pixels
fs first sprite id to be used for displaying
fu first UNICODE (smallest character) in string
sw number of horizontal sprites
sh number of vertical sprites
scale scale, -3 to 4
str zero terminated UTF-8 string
See Also
spr, dlg

remap

1
void remap(addr_t replace)
Description
Replaces tiles on map. Can be used to animate tiles on the map.
Parameters
Argument Description
replace an array of 256 sprite ids
See Also
mget, mset, map, maze

mget

1
uint16_t mget(uint16_t mx, uint16_t my)
Description
Returns one tile on map.
Parameters
Argument Description
mx X coordinate on map in tiles
my Y coordinate on map in tiles
Return Value
The sprite id of the tile at the given coordinate.
See Also
remap, mset, map, maze

mset

1
void mset(uint16_t mx, uint16_t my, uint16_t sprite)
Description
Sets one tile on map.
Parameters
Argument Description
mx X coordinate on map in tiles
my Y coordinate on map in tiles
sprite sprite id, 0 to 1023
See Also
remap, mget, map, maze

map

1
void map(int16_t x, int16_t y, uint16_t mx, uint16_t my, uint16_t mw, uint16_t mh, int8_t scale)
Description
Draws (part of) the map.
Parameters
Argument Description
x X coordinate in pixels
y Y coordinate in pixels
mx X coordinate on map in tiles
my Y coordinate on map in tiles
mw number of horizontal tiles
mh number of vertical tiles
scale scale, -3 to 4
See Also
remap, mget, mset, maze

maze

1
2
void maze(uint16_t mx, uint16_t my, uint16_t mw, uint16_t mh, uint8_t scale, uint16_t sky, uint16_t grd, uint16_t door, uint16_t wall, uint16_t obj, uint8_t numnpc, addr_t npc)
Description
Displays map as a 3D maze, using turtle's position.
Parameters
Argument Description
mx X coordinate on map in tiles
my Y coordinate on map in tiles
mw number of horizontal tiles
mh number of vertical tiles
scale number of sprites per tiles in power of two, 0 to 3
sky sky tile index
grd ground tile index
door first door tile index
wall first wall tile index
obj first object tile index
numnpc number of NPC records
npc an uint32_t array of numnpc times x,y,tile index triplets
See Also
remap, mget, mset, map


Input

getpad

1
int getpad(int pad, int btn)
Description
Gets the current state of a gamepad button.
Parameters
Argument Description
pad gamepad index, 0 to 3
btn one of the gamepad buttons, BTN_
Return Value
Zero if not pressed, non-zero if pressed.
See Also
prspad, relpad, getbtn, getclk, getkey

prspad

1
int prspad(int pad, int btn)
Description
Returns true if gamepad button was pressed since last call.
Parameters
Argument Description
pad gamepad index, 0 to 3
btn one of the gamepad buttons, BTN_
Return Value
Zero if not pressed, non-zero if pressed.
See Also
relpad, getpad, getbtn, getclk, getkey

relpad

1
int relpad(int pad, int btn)
Description
Returns true if gamepad button was released since last call.
Parameters
Argument Description
pad gamepad index, 0 to 3
btn one of the gamepad buttons, BTN_
Return Value
Zero if wasn't released, non-zero if released.
See Also
prspad, getpad, getbtn, getclk, getkey

getbtn

1
int getbtn(int btn)
Description
Gets the mouse buttons state.
Parameters
Argument Description
btn one of the pointer buttons, BTN_ or SCR_
Return Value
Zero if not pressed, non-zero if pressed.
See Also
prspad, relpad, getpad, getclk, getkey

getclk

1
int getclk(int btn)
Description
Gets mouse button clicking.
Parameters
Argument Description
btn one of the pointer buttons, BTN_
Return Value
Zero if not clicked, non-zero if clicked.
See Also
prspad, relpad, getpad, getbtn, getkey

getkey

1
int getkey(int sc)
Description
Gets the current state of a key.
Parameters
Argument Description
sc scancode, 1 to 144, see keyboard
Return Value
Zero if not pressed, non-zero if pressed.
See Also
prspad, relpad, getpad, getbtn, getclk

popkey

1
uint32_t popkey(void)
Description
Pop an UTF-8 key from the keyboard queue. See keyboard, and for the blocking version getc.
Return Value
The UTF-8 representation of the key, or 0 if the queue was empty (no blocking).
See Also
pendkey, lenkey, speckey, getc

pendkey

1
int pendkey(void)
Description
Returns true if there's a key pending in the queue (but leaves the key in the queue, does not pop it).
Return Value
Returns 1 if there are keys in the queue pending.
See Also
popkey, lenkey, speckey

lenkey

1
int lenkey(uint32_t key)
Description
Returns the length of a UTF-8 key in bytes.
Parameters
Argument Description
key the key, popped from the queue
Return Value
UTF-8 representation's length in bytes.
See Also
popkey, pendkey, speckey

speckey

1
int speckey(uint32_t key)
Description
Returns true if key is a special key.
Parameters
Argument Description
key the key, popped from the queue
Return Value
Returns 1 if it's a special key, and 0 if it's a printable one.
See Also
popkey, pendkey, lenkey


Mathematics

rand

1
uint32_t rand(void)
Description
Get random. Use % modulo to make it smaller, for example 1 + rand() % 6 returns random between 1 and 6, like a dice.
Return Value
A random number between 0 and 232-1 (4294967295).
See Also
rnd

rnd

1
float rnd(void)
Description
Get random. Same as rand, but returns a floating point number.
Return Value
A random number between 0.0 and 1.0.
See Also
rand

float

1
float float(int val)
Description
Returns the floating point equivalent of an integer number.
Parameters
Argument Description
val value
Return Value
The floating point of value.
See Also
int

int

1
int int(float val)
Description
Returns the integer equivalent of a floating point number.
Parameters
Argument Description
val value
Return Value
The integer of value.
See Also
float

floor

1
float floor(float val)
Description
Returns the largest integral number that's not greater than value.
Parameters
Argument Description
val value
Return Value
The floor of value.
See Also
ceil

ceil

1
float ceil(float val)
Description
Returns the smallest integral number that's not less than value.
Parameters
Argument Description
val value
Return Value
The ceiling of value.
See Also
floor

sgn

1
float sgn(float val)
Description
Returns the sign of the value.
Parameters
Argument Description
val value
Return Value
Either 1.0 or -1.0.
See Also
abs

abs

1
float abs(float val)
Description
Returns the absolute of the value.
Parameters
Argument Description
val value
Return Value
Either value or -value, always positive.
See Also
sgn

exp

1
float exp(float val)
Description
Returns the exponential of the value, i.e. base of natural logarithms raised to power of the value.
Parameters
Argument Description
val value
Return Value
Returns eval.
See Also
log, pow

log

1
float log(float val)
Description
Returns the natural logarithm of the value.
Parameters
Argument Description
val value
Return Value
Returns natural logarithm of value.
See Also
exp

pow

1
float pow(float val, float exp)
Description
Returns the value raised to the power of exponent. This is a slow operation, try to avoid.
Parameters
Argument Description
val value
exp exponent
Return Value
Returns valexp.
See Also
exp, sqrt, rsqrt

sqrt

1
float sqrt(float val)
Description
Returns the square root of the value. This is a slow operation, try to avoid.
Parameters
Argument Description
val value
Return Value
Square root of the value.
See Also
pow, rsqrt

rsqrt

1
float rsqrt(float val)
Description
Returns the reciprocal of the square root of the value (1 / sqrt(val)). Uses John Carmack's fast method.
Parameters
Argument Description
val value
Return Value
Reciprocal of the square root of the value.
See Also
pow, sqrt

clamp

1
float clamp(float val, float minv, float maxv)
Description
Clamps a value between the limits.
Parameters
Argument Description
val value
minv minimum value
maxv maximum value
Return Value
Clamped value.
See Also
clampv2, clampv3, clampv4

lerp

1
float lerp(float a, float b, float t)
Description
Linear interpolates two numbers.
Parameters
Argument Description
a first float number
b second float number
t interpolation value between 0.0 and 1.0
See Also
lerpv2, lerpv3, lerpv4, lerpq, slerpq

pi

1
float pi(void)
Description
Returns π as a floating point number.
Return Value
The value 3.14159265358979323846.

cos

1
float cos(uint16_t deg)
Description
Returns cosine.
Parameters
Argument Description
deg degree, 0 to 359, 0 is up, 90 on the right
Return Value
Cosine of the degree, between -1.0 to 1.0.
See Also
sin, tan, acos, asin, atan, atan2

sin

1
float sin(uint16_t deg)
Description
Returns sine.
Parameters
Argument Description
deg degree, 0 to 359, 0 is up, 90 to the right
Return Value
Sine of the degree, between -1.0 to 1.0.
See Also
cos, tan, acos, asin, atan, atan2

tan

1
float tan(uint16_t deg)
Description
Returns tangent.
Parameters
Argument Description
deg degree, 0 to 359, 0 is up, 90 to the right
Return Value
Tangent of the degree, between -1.0 to 1.0.
See Also
cos, sin, acos, asin, atan, atan2

acos

1
uint16_t acos(float val)
Description
Returns arcus cosine.
Parameters
Argument Description
val value, -1.0 to 1.0
Return Value
Arcus cosine in degree, 0 to 359, 0 is up, 90 to the right.
See Also
cos, sin, tan, asin, atan, atan2

asin

1
uint16_t asin(float val)
Description
Returns arcus sine.
Parameters
Argument Description
val value, -1.0 to 1.0
Return Value
Arcus sine in degree, 0 to 359, 0 is up, 90 to the right.
See Also
cos, sin, tan, acos, atan, atan2

atan

1
uint16_t atan(float val)
Description
Returns arcus tangent.
Parameters
Argument Description
val value, -1.0 to 1.0
Return Value
Arcus tangent in degree, 0 to 359, 0 is up, 90 to the right.
See Also
cos, sin, tan, acos, asin, atan2

atan2

1
uint16_t atan2(float y, float x)
Description
Returns arcus tangent for y/x, using the signs of y and x to determine the quadrant.
Parameters
Argument Description
y Y coordinate
x X coordinate
Return Value
Arcus tangent in degree, 0 to 359, 0 is up, 90 to the right.
See Also
cos, sin, tan, acos, asin

dotv2

1
float dotv2(addr_t a, addr_t b)
Description
Calculates dot product of two vectors with two elements.
Parameters
Argument Description
a address of two floats
b address of two floats
Return Value
Dot product of the vectors.
See Also
lenv2, scalev2, negv2, addv2, subv2, mulv2, divv2, clampv2, lerpv2, normv2

lenv2

1
float lenv2(addr_t a)
Description
Calculates the length of a vector with two elements. This is slow, try to avoid (see normv2).
Parameters
Argument Description
a address of two floats
Return Value
Length of the vector.
See Also
dotv2, scalev2, negv2, addv2, subv2, mulv2, divv2, clampv2, lerpv2, normv2

scalev2

1
void scalev2(addr_t a, float s)
Description
Scales a vector with two elements.
Parameters
Argument Description
a address of two floats
s scaler value
See Also
dotv2, lenv2, negv2, addv2, subv2, mulv2, divv2, clampv2, lerpv2, normv2

negv2

1
void negv2(addr_t a)
Description
Negates a vector with two elements.
Parameters
Argument Description
a address of two floats
See Also
dotv2, lenv2, scalev2, addv2, subv2, mulv2, divv2, clampv2, lerpv2, normv2

addv2

1
void addv2(addr_t dst, addr_t a, addr_t b)
Description
Adds together vectors with two elements.
Parameters
Argument Description
dst address of two floats
a address of two floats
b address of two floats
See Also
dotv2, lenv2, scalev2, negv2, subv2, mulv2, divv2, clampv2, lerpv2, normv2

subv2

1
void subv2(addr_t dst, addr_t a, addr_t b)
Description
Subtracts vectors with two elements.
Parameters
Argument Description
dst address of two floats
a address of two floats
b address of two floats
See Also
dotv2, lenv2, scalev2, negv2, addv2, mulv2, divv2, clampv2, lerpv2, normv2

mulv2

1
void mulv2(addr_t dst, addr_t a, addr_t b)
Description
Multiplies vectors with two elements.
Parameters
Argument Description
dst address of two floats
a address of two floats
b address of two floats
See Also
dotv2, lenv2, scalev2, negv2, addv2, subv2, divv2, clampv2, lerpv2, normv2

divv2

1
void divv2(addr_t dst, addr_t a, addr_t b)
Description
Divides vectors with two elements.
Parameters
Argument Description
dst address of two floats
a address of two floats
b address of two floats
See Also
dotv2, lenv2, scalev2, negv2, addv2, subv2, mulv2, clampv2, lerpv2, normv2

clampv2

1
void clampv2(addr_t dst, addr_t v, addr_t minv, addr_t maxv)
Description
Clamps vectors with two elements.
Parameters
Argument Description
dst address of two floats
v address of two floats, input
minv address of two floats, minimum
maxv address of two floats, maximum
See Also
dotv2, lenv2, scalev2, negv2, addv2, subv2, mulv2, divv2, lerpv2, normv2

lerpv2

1
void lerpv2(addr_t dst, addr_t a, addr_t b, float t)
Description
Linear interpolates vectors with two elements.
Parameters
Argument Description
dst address of two floats
a address of two floats
b address of two floats
t interpolation value between 0.0 and 1.0
See Also
dotv2, lenv2, scalev2, negv2, addv2, subv2, mulv2, divv2, clampv2, normv2

normv2

1
void normv2(addr_t a)
Description
Normalizes a vector with two elements.
Parameters
Argument Description
a address of two floats
See Also
dotv2, lenv2, scalev2, negv2, addv2, subv2, mulv2, divv2, clampv2, lerpv2

dotv3

1
float dotv3(addr_t a, addr_t b)
Description
Calculates dot product of two vectors with three elements.
Parameters
Argument Description
a address of three floats
b address of three floats
Return Value
Dot product of the vectors.
See Also
lenv3, scalev3, negv3, addv3, subv3, mulv3, divv3, crossv3, clampv3, lerpv3, normv3

lenv3

1
float lenv3(addr_t a)
Description
Calculates the length of a vector with three elements. This is slow, try to avoid (see normv3).
Parameters
Argument Description
a address of three floats
Return Value
Length of the vector.
See Also
dotv3, scalev3, negv3, addv3, subv3, mulv3, divv3, crossv3, clampv3, lerpv3, normv3

scalev3

1
void scalev3(addr_t a, float s)
Description
Scales a vector with three elements.
Parameters
Argument Description
a address of three floats
s scaler value
See Also
dotv3, lenv3, negv3, addv3, subv3, mulv3, divv3, crossv3, clampv3, lerpv3, normv3

negv3

1
void negv3(addr_t a)
Description
Negates a vector with three elements.
Parameters
Argument Description
a address of three floats
See Also
dotv3, lenv3, scalev3, addv3, subv3, mulv3, divv3, crossv3, clampv3, lerpv3, normv3

addv3

1
void addv3(addr_t dst, addr_t a, addr_t b)
Description
Adds together vectors with three elements.
Parameters
Argument Description
dst address of three floats
a address of three floats
b address of three floats
See Also
dotv3, lenv3, scalev3, negv3, subv3, mulv3, divv3, crossv3, clampv3, lerpv3, normv3

subv3

1
void subv3(addr_t dst, addr_t a, addr_t b)
Description
Subtracts vectors with three elements.
Parameters
Argument Description
dst address of three floats
a address of three floats
b address of three floats
See Also
dotv3, lenv3, scalev3, negv3, addv3, mulv3, divv3, crossv3, clampv3, lerpv3, normv3

mulv3

1
void mulv3(addr_t dst, addr_t a, addr_t b)
Description
Multiplies vectors with three elements.
Parameters
Argument Description
dst address of three floats
a address of three floats
b address of three floats
See Also
dotv3, lenv3, scalev3, negv3, addv3, subv3, divv3, crossv3, clampv3, lerpv3, normv3

divv3

1
void divv3(addr_t dst, addr_t a, addr_t b)
Description
Divides vectors with three elements.
Parameters
Argument Description
dst address of three floats
a address of three floats
b address of three floats
See Also
dotv3, lenv3, scalev3, negv3, addv3, subv3, mulv3, crossv3, clampv3, lerpv3, normv3

crossv3

1
void crossv3(addr_t dst, addr_t a, addr_t b)
Description
Calculates cross product of vectors with three elements.
Parameters
Argument Description
dst address of three floats
a address of three floats
b address of three floats
See Also
dotv3, lenv3, scalev3, negv3, addv3, subv3, mulv3, divv3, clampv3, lerpv3, normv3

clampv3

1
void clampv3(addr_t dst, addr_t v, addr_t minv, addr_t maxv)
Description
Clamps vectors with three elements.
Parameters
Argument Description
dst address of three floats
v address of three floats, input
minv address of three floats, minimum
maxv address of three floats, maximum
See Also
dotv3, lenv3, scalev3, negv3, addv3, subv3, mulv3, divv3, crossv3, lerpv3, normv3

lerpv3

1
void lerpv3(addr_t dst, addr_t a, addr_t b, float t)
Description
Linear interpolates vectors with three elements.
Parameters
Argument Description
dst address of three floats
a address of three floats
b address of three floats
t interpolation value between 0.0 and 1.0
See Also
dotv3, lenv3, scalev3, negv3, addv3, subv3, mulv3, divv3, crossv3, clampv3, normv3

normv3

1
void normv3(addr_t a)
Description
Normalizes a vector with three elements.
Parameters
Argument Description
a address of three floats
See Also
dotv3, lenv3, scalev3, negv3, addv3, subv3, mulv3, divv3, crossv3, clampv3, lerpv3

dotv4

1
float dotv4(addr_t a, addr_t b)
Description
Calculates dot product of two vectors with four elements.
Parameters
Argument Description
a address of four floats
b address of four floats
Return Value
Dot product of the vectors.
See Also
lenv4, scalev4, negv4, addv4, subv4, mulv4, divv4, clampv4, lerpv4, normv4

lenv4

1
float lenv4(addr_t a)
Description
Calculates the length of a vector with four elements. This is slow, try to avoid (see normv4).
Parameters
Argument Description
a address of four floats
Return Value
Length of the vector.
See Also
dotv4, scalev4, negv4, addv4, subv4, mulv4, divv4, clampv4, lerpv4, normv4

scalev4

1
void scalev4(addr_t a, float s)
Description
Scales a vector with four elements.
Parameters
Argument Description
a address of four floats
s scaler value
See Also
dotv4, lenv4, negv4, addv4, subv4, mulv4, divv4, clampv4, lerpv4, normv4

negv4

1
void negv4(addr_t a)
Description
Negates a vector with four elements.
Parameters
Argument Description
a address of four floats
See Also
dotv4, lenv4, scalev4, addv4, subv4, mulv4, divv4, clampv4, lerpv4, normv4

addv4

1
void addv4(addr_t dst, addr_t a, addr_t b)
Description
Adds together vectors with four elements.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
See Also
dotv4, lenv4, negv4, scalev4, subv4, mulv4, divv4, clampv4, lerpv4, normv4

subv4

1
void subv4(addr_t dst, addr_t a, addr_t b)
Description
Subtracts vectors with four elements.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
See Also
dotv4, lenv4, scalev4, negv4, addv4, mulv4, divv4, clampv4, lerpv4, normv4

mulv4

1
void mulv4(addr_t dst, addr_t a, addr_t b)
Description
Multiplies vectors with four elements.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
See Also
dotv4, lenv4, scalev4, negv4, addv4, subv4, divv4, clampv4, lerpv4, normv4

divv4

1
void divv4(addr_t dst, addr_t a, addr_t b)
Description
Divides vectors with four elements.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
See Also
dotv4, lenv4, scalev4, negv4, addv4, subv4, mulv4, clampv4, lerpv4, normv4

clampv4

1
void clampv4(addr_t dst, addr_t v, addr_t minv, addr_t maxv)
Description
Clamps vectors with four elements.
Parameters
Argument Description
dst address of four floats
v address of four floats, input
minv address of four floats, minimum
maxv address of four floats, maximum
See Also
dotv4, lenv4, scalev4, negv4, addv4, subv4, mulv4, divv4, lerpv4, normv4

lerpv4

1
void lerpv4(addr_t dst, addr_t a, addr_t b, float t)
Description
Linear interpolates vectors with four elements.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
t interpolation value between 0.0 and 1.0
See Also
dotv4, lenv4, scalev4, negv4, addv4, subv4, mulv4, divv4, clampv4, normv4

normv4

1
void normv4(addr_t a)
Description
Normalizes a vector with four elements.
Parameters
Argument Description
a address of four floats
See Also
dotv4, lenv4, scalev4, negv4, addv4, subv4, mulv4, divv4, clampv4, lerpv4

idq

1
void idq(addr_t a)
Description
Loads the identity quaternion.
Parameters
Argument Description
a address of four floats
See Also
eulerq, dotq, lenq, scaleq, negq, addq, subq, mulq, rotq, lerpq, slerpq, normq

eulerq

1
void eulerq(addr_t dst, uint16_t pitch, uint16_t yaw, uint16_t roll)
Description
Loads a quaternion using Euler angles.
Parameters
Argument Description
dst address of four floats
pitch rotation around X axis in degrees, 0 to 359
yaw rotation around Y axis in degrees, 0 to 359
roll rotation around Z axis in degrees, 0 to 359
See Also
idq, dotq, lenq, scaleq, negq, addq, subq, mulq, rotq, lerpq, slerpq, normq

dotq

1
float dotq(addr_t a, addr_t b)
Description
Calculates dot product of a quaternion.
Parameters
Argument Description
a address of four floats
b address of four floats
Return Value
Dot product of the quaternion.
See Also
idq, eulerq, lenq, scaleq, negq, addq, subq, mulq, rotq, lerpq, slerpq, normq

lenq

1
float lenq(addr_t a)
Description
Calculates the length of a quaternion.
Parameters
Argument Description
a address of four floats
Return Value
Length of the quaternion.
See Also
idq, eulerq, dotq, scaleq, negq, addq, subq, mulq, rotq, lerpq, slerpq, normq

scaleq

1
void scaleq(addr_t a, float s)
Description
Scales a quaternion.
Parameters
Argument Description
a address of four floats
s scaler value
See Also
idq, eulerq, dotq, lenq, negq, addq, subq, mulq, rotq, lerpq, slerpq, normq

negq

1
void negq(addr_t a)
Description
Negates a quaternion.
Parameters
Argument Description
a address of four floats
See Also
idq, eulerq, dotq, lenq, scaleq, addq, subq, mulq, rotq, lerpq, slerpq, normq

addq

1
void addq(addr_t dst, addr_t a, addr_t b)
Description
Adds together quaternions.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
See Also
idq, eulerq, dotq, lenq, scaleq, negq, subq, mulq, rotq, lerpq, slerpq, normq

subq

1
void subq(addr_t dst, addr_t a, addr_t b)
Description
Subtracts quaternions.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
See Also
idq, eulerq, dotq, lenq, scaleq, negq, addq, mulq, rotq, lerpq, slerpq, normq

mulq

1
void mulq(addr_t dst, addr_t a, addr_t b)
Description
Multiplies quaternions.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
See Also
idq, eulerq, dotq, lenq, scaleq, negq, addq, subq, rotq, lerpq, slerpq, normq

rotq

1
void rotq(addr_t dst, addr_t q, addr_t v)
Description
Rotates a vector with three elements by a quaternion.
Parameters
Argument Description
dst address of three floats
q address of four floats
v address of three floats
See Also
idq, eulerq, dotq, lenq, scaleq, negq, addq, subq, mulq, lerpq, slerpq, normq

lerpq

1
void lerpq(addr_t dst, addr_t a, addr_t b, float t)
Description
Linear interpolates two quaternions.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
t interpolation value between 0.0 and 1.0
See Also
idq, eulerq, dotq, lenq, scaleq, negq, addq, subq, mulq, rotq, slerpq, normq

slerpq

1
void slerpq(addr_t dst, addr_t a, addr_t b, float t)
Description
Spherical interpolates a quaternion.
Parameters
Argument Description
dst address of four floats
a address of four floats
b address of four floats
t interpolation value between 0.0 and 1.0
See Also
idq, eulerq, dotq, lenq, scaleq, negq, addq, subq, mulq, rotq, lerpq, normq

normq

1
void normq(addr_t a)
Description
Normalizes a quaternion.
Parameters
Argument Description
a address of four floats
See Also
idq, eulerq, dotq, lenq, scaleq, negq, addq, subq, mulq, rotq, lerpq, slerpq

idm4

1
void idm4(addr_t a)
Description
Loads a 4 x 4 identity matrix.
Parameters
Argument Description
a address of 16 floats
See Also
trsm4, detm4, addm4, subm4, mulm4, mulm4v3, mulm4v4, invm4, trpm4

trsm4

1
void trsm4(addr_t dst, addr_t t, addr_t r, addr_t s)
Description
Creates a 4 x 4 matrix with translation, rotation and scaling.
Parameters
Argument Description
dst address of 16 floats, destination matrix
t address of three floats, translation vector
r address of four floats, rotation quaternion
s address of three floats, scaling vector
See Also
idm4, detm4, addm4, subm4, mulm4, mulm4v3, mulm4v4, invm4, trpm4

detm4

1
float detm4(addr_t a)
Description
Returns the matrix's determinant.
Parameters
Argument Description
a address of 16 floats
Return Value
The matrix's determinant.
See Also
idm4, trsm4, addm4, subm4, mulm4, mulm4v3, mulm4v4, invm4, trpm4

addm4

1
void addm4(addr_t dst, addr_t a, addr_t b)
Description
Adds matrices together.
Parameters
Argument Description
dst address of 16 floats
a address of 16 floats
b address of 16 floats
See Also
idm4, trsm4, detm4, subm4, mulm4, mulm4v3, mulm4v4, invm4, trpm4

subm4

1
void subm4(addr_t dst, addr_t a, addr_t b)
Description
Subtracts matrices.
Parameters
Argument Description
dst address of 16 floats
a address of 16 floats
b address of 16 floats
See Also
idm4, trsm4, detm4, addm4, mulm4, mulm4v3, mulm4v4, invm4, trpm4

mulm4

1
void mulm4(addr_t dst, addr_t a, addr_t b)
Description
Multiplies matrices.
Parameters
Argument Description
dst address of 16 floats
a address of 16 floats
b address of 16 floats
See Also
idm4, trsm4, detm4, addm4, subm4, mulm4v3, mulm4v4, invm4, trpm4

mulm4v3

1
void mulm4v3(addr_t dst, addr_t m, addr_t v)
Description
Multiplies a vector with three elements by a matrix.
Parameters
Argument Description
dst address of three floats
m address of 16 floats
v address of three floats
See Also
idm4, trsm4, detm4, addm4, subm4, mulm4, mulm4v4, invm4, trpm4

mulm4v4

1
void mulm4v4(addr_t dst, addr_t m, addr_t v)
Description
Multiplies a vector with four elements by a matrix.
Parameters
Argument Description
dst address of four floats
m address of 16 floats
v address of four floats
See Also
idm4, trsm4, detm4, addm4, subm4, mulm4, mulm4v3, invm4, trpm4

invm4

1
void invm4(addr_t dst, addr_t a)
Description
Calculates inverse matrix.
Parameters
Argument Description
dst address of 16 floats
a address of 16 floats
See Also
idm4, trsm4, detm4, addm4, subm4, mulm4, mulm4v3, mulm4v4, trpm4

trpm4

1
void trpm4(addr_t dst, addr_t a)
Description
Transpose matrix.
Parameters
Argument Description
dst address of 16 floats
a address of 16 floats
See Also
idm4, trsm4, detm4, addm4, subm4, mulm4, mulm4v3, mulm4v4, invm4

trns

1
2
3
4
void trns(addr_t dst, addr_t src, uint8_t num, int16_t x, int16_t y, int16_t z, uint16_t pitch, uint16_t yaw, uint16_t roll, float scale)
Description
Translate a vertex cloud, aka. place a 3D model in 3D space.
Parameters
Argument Description
dst destination vertices array, 3 x 2 bytes each, X, Y, Z
src source vertices array, 3 x 2 bytes each, X, Y, Z
num number of vertex coordinate triplets in the array
x world X coordinate, -32767 to 32767
y world Y coordinate, -32767 to 32767
z world Z coordinate, -32767 to 32767
pitch rotation around X axis in degrees, 0 to 359
yaw rotation around Y axis in degrees, 0 to 359
roll rotation around Z axis in degrees, 0 to 359
scale scale, use 1.0 to keep original size
See Also
mesh


Memory

inb

1
uint8_t inb(addr_t src)
Description
Read in one byte from memory.
Parameters
Argument Description
src address, 0x00000 to 0xBFFFF
Return Value
Returns the value at that address.

inw

1
uint16_t inw(addr_t src)
Description
Read in a word (two bytes) from memory.
Parameters
Argument Description
src address, 0x00000 to 0xBFFFE
Return Value
Returns the value at that address.

ini

1
uint32_t ini(addr_t src)
Description
Read in an integer (four bytes) from memory.
Parameters
Argument Description
src address, 0x00000 to 0xBFFFC
Return Value
Returns the value at that address.

outb

1
void outb(addr_t dst, uint8_t value)
Description
Write out one byte to memory.
Parameters
Argument Description
dst address, 0x00000 to 0xBFFFF
value value to set, 0 to 255

outw

1
void outw(addr_t dst, uint16_t value)
Description
Write out a word (two bytes) to memory.
Parameters
Argument Description
dst address, 0x00000 to 0xBFFFE
value value to set, 0 to 65535

outi

1
void outi(addr_t dst, uint32_t value)
Description
Write out an integer (four bytes) to memory.
Parameters
Argument Description
dst address, 0x00000 to 0xBFFFC
value value to set, 0 to 4294967295

memsave

1
int memsave(uint8_t overlay, addr_t src, uint32_t size)
Description
Saves memory area to overlay.
Parameters
Argument Description
overlay index of overlay to write to, 0 to 255
src memory offset to save from, 0x00000 to 0xBFFFF
size number of bytes to save
Return Value
Returns 1 on success, 0 on error.
See Also
memload

memload

1
int memload(addr_t dst, uint8_t overlay, uint32_t maxsize)
Description
Loads an overlay into the specified memory area.
Parameters
Argument Description
dst memory offset to load to, 0x00000 to 0xBFFFF
overlay index of overlay to read from, 0 to 255
maxsize maximum number of bytes to load
Return Value
Returns the number of bytes actually loaded.
See Also
memsave

memcpy

1
void memcpy(addr_t dst, addr_t src, uint32_t len)
Description
Copy memory regions.
Parameters
Argument Description
dst destination address, 0x00000 to 0xBFFFF
src source address, 0x00000 to 0xBFFFF
len number of bytes to copy

memset

1
void memset(addr_t dst, uint8_t value, uint32_t len)
Description
Set memory region to a given value.
Parameters
Argument Description
dst destination address, 0x00000 to 0xBFFFF
value value to set, 0 to 255
len number of bytes to set

memcmp

1
int memcmp(addr_t addr0, addr_t addr1, uint32_t len)
Description
Compare memory regions.
Parameters
Argument Description
addr0 first address, 0x00000 to 0xBFFFF
addr1 second address, 0x00000 to 0xBFFFF
len number of bytes to compare
Return Value
Returns difference, 0 if the two memory region matches.

deflate

1
int deflate(addr_t dst, addr_t src, uint32_t len)
Description
Compress a buffer using RFC1950 deflate (zlib).
Parameters
Argument Description
dst destination address, 0x30000 to 0xBFFFF
src source address, 0x30000 to 0xBFFFF
len number of bytes to compress
Return Value
0 or negative on error, otherwise the length of the compressed buffer and compressed data in dst.
See Also
inflate

inflate

1
int inflate(addr_t dst, addr_t src, uint32_t len)
Description
Uncompress a buffer with RFC1950 deflate (zlib) compressed data.
Parameters
Argument Description
dst destination address, 0x30000 to 0xBFFFF
src source address, 0x30000 to 0xBFFFF
len number of compressed bytes
Return Value
0 or negative on error, otherwise the length of the uncompressed buffer and uncompressed data in dst.
See Also
deflate

time

1
float time(void)
Description
Returns the number of ticks since power on.
Return Value
The elapsed time in milliseconds since power on.
See Also
now

now

1
uint32_t now(void)
Description
Returns the UNIX timestamp. Check the byte at offset 0000C to see if it overflows.
Return Value
The elapsed time in seconds since 1 Jan 1970 midnight, Greenwich Mean Time.
See Also
time

atoi

1
int atoi(str_t src)
Description
Converts an ASCII decimal string into an integer number.
Parameters
Argument Description
src string address, 0x00000 to 0xBFFFF
Return Value
The number value of the string.
See Also
itoa, str, val

itoa

1
str_t itoa(int value)
Description
Converts an integer number into an ASCII decimal string.
Parameters
Argument Description
value the value, -2147483648 to 2147483647
Return Value
The converted string.
See Also
atoi, str, val

val

1
float val(str_t src)
Description
Converts an ASCII decimal string into a floating point number.
Parameters
Argument Description
src string address, 0x00000 to 0xBFFFF
Return Value
The number value of the string.
See Also
itoa, atoi, str

str

1
str_t str(float value)
Description
Converts a floating point number into an ASCII decimal string.
Parameters
Argument Description
value the value
Return Value
The converted string.
See Also
atoi, itoa, val

sprintf

1
str_t sprintf(str_t fmt, ...)
Description
Returns a zero terminated UTF-8 string created using format and arguments.
Parameters
Argument Description
fmt format string
... optional arguments
Return Value
Constructed string.

strlen

1
int strlen(str_t src)
Description
Return the number of bytes in a string (without the terminating zero).
Parameters
Argument Description
src string address, 0x00000 to 0xBFFFF
Return Value
The number of bytes in the string.
See Also
mblen

mblen

1
int mblen(str_t src)
Description
Return the number of UTF-8 multi-byte characters in a string (without the terminating zero).
Parameters
Argument Description
src string address, 0x00000 to 0xBFFFF
Return Value
The number of characters in the string.
See Also
strlen

malloc

1
addr_t malloc(uint32_t size)
Description
Allocates user memory dynamically.
Parameters
Argument Description
size number of bytes to allocate
Return Value
Address of the new allocated buffer or NULL on error.
See Also
realloc, free

realloc

1
addr_t realloc(addr_t addr, uint32_t size)
Description
Resize a previously allocated buffer.
Parameters
Argument Description
addr address of the allocated buffer
size number of bytes to resize to
Return Value
Address of the new allocated buffer or NULL on error.
See Also
malloc, free

free

1
int free(addr_t addr)
Description
Frees dynamically allocated user memory.
Parameters
Argument Description
addr address of the allocated buffer
Return Value
1 on success, 0 on error.
See Also
malloc, realloc


Bouncing Ball

In this tutorial we'll create a program that bounces a ball on the screen.

Displaying the Ball

First things first, start meg4 and select the Sprite Editor (press F3). Select the first sprite on the right, and edit it on the left.

tut_ball1.png
Drawing the ball

Now go to the Code Editor (press F2). At first, our program will be an empty skeleton.

1
2
3
4
5
6
7
8
9
10
11
#!c void setup() { /* Things to do on startup */ } void loop() { /* Things to run for every frame, at 60 FPS */ }

Let's place this newly drawn sprite on screen! You can do this by calling the spr function. Go to the body of the setup() function, start typing, and at the bottom in the statusbar you'll see the required parameters.

tut_ball2.png

We can see that the first two arguments are x, y, the screen coordinate where we want to place the sprite (if it's not obvious from the name what a parameter does, just press F1 and a detailed help will show up. Pressing Esc there will bring you back to the code editor). The screen is 320 pixels wide and 200 pixels tall, so in order to place at the centre, let's use 160, 100. Next argument is the sprite. We've drawn our ball at the 0th sprite, so use 0. After comes sw (sprite width) and sh (sprite height), which tells how many sprites we want to display. Our ball only occupies 1 sprite, so simply write 1, 1. Then comes scale, but we don't want to magnify our ball, so just use 1 here too. Finally, the last parameter is type, which can be used to display the sprite transformed. We don't want any transformation, so just use 0.

1
2
3
4
5
6
7
8
9
10
11
12
#!c void setup() { /* Things to do on startup */ spr(160, 100, 0, 1, 1, 1, 0); } void loop() { /* Things to run for every frame, at 60 FPS */ }

Now try running this code by pressing Ctrl+R, and let's see what happens. If you have made some mistake by typing the code, an error message will show up in the status bar, and the cursor will be positioned to the faulting part.

If everything went well, then the editor screen will disappear, and black screen with the ball in the middle will appear instead. Our ball isn't exactly at the centre, because we forgot to subtract the half of the sprite's size from the coordinates (we are displaying only one sprite here (sw = 1 and sh = 1), so 8 x 8 pixels in total, half of that is 4). Press F2 to go back to the editor, and let's fix this.

1
2
3
4
5
6
7
8
9
10
11
12
#!c void setup() { /* Things to do on startup */ spr(156, 96, 0, 1, 1, 1, 0); } void loop() { /* Things to run for every frame, at 60 FPS */ }

Let's run this again! We have the ball at proper position, but we can still see it at the wrong position! That's because we haven't cleared the screen. We can do that by calling cls, so let's add that before drawing the sprite.

1
2
3
4
5
6
7
8
9
10
11
12
13
#!c void setup() { /* Things to do on startup */ cls(0); spr(156, 96, 0, 1, 1, 1, 0); } void loop() { /* Things to run for every frame, at 60 FPS */ }

Now everything is fine, our ball shown at exactly the centre of the screen.

Moving the Ball

There's a problem in our code. We display the ball in setup() which only runs once when our program starts. To move the ball, we have to display it over and over again, at different positions. To achieve this, let's move the code that displays our ball into the loop() function. This runs every time when the screen is redrawn.

1
2
3
4
5
6
7
8
9
10
11
12
13
#!c void setup() { /* Things to do on startup */ } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(156, 96, 0, 1, 1, 1, 0);}

If we press Ctrl+R now, then the ball will be displayed exactly as before. What we can't see is that the ball is now drawn not only once, but over and over again.

Let's move that ball! It is displayed at the same position, because we have used constant coordinates. Let's fix this by introducing two variables, which will store the ball's current position on screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!c int x, y; void setup() { /* Things to do on startup */ } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(156, 96, 0, 1, 1, 1, 0); }

Replace the coordinates in drawing call to these variables, and let's assign them values on program start.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!c int x, y; void setup() { /* Things to do on startup */ x = 156; y = 96;} void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(x, y, 0, 1, 1, 1, 0); }

If we run our program now, there'll be still no change. However using variables we can now change the ball's position in run-time, let's do that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!c int x, y; void setup() { /* Things to do on startup */ x = 156; y = 96; } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(x, y, 0, 1, 1, 1, 0); x = x + 1; y = y + 1;}

Run this program, and you'll see the ball moving!

Bouncing the Ball

We are not ready yet, because our ball disappears pretty quickly from the screen. This is because we constantly increasing its coordinates, and we don't change its direction when it reaches the edge of the screen.

Just like as we did with the coordinates at first, we are using a constant and now we want to change the direction in our program dynamically. The solution is the same, we replace the constants with two new variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!c int x, y, dx, dy; void setup() { /* Things to do on startup */ x = 156; y = 96; dx = dy = 1; } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(x, y, 0, 1, 1, 1, 0); x = x + dx; y = y + dy; }

Great! As mentioned before, the screen is 320 pixels wide and 200 pixels tall. This means that the valid values for the x coordinate are between 0 and 319, and for y between 0 and 199. But we don't want our ball to disappear from the screen, so we have to subtract the sprite's size (8 x 8 pixels) from these. This gives 0 to 311 for x, and 0 to 191 for y. If our ball's coordinate reaches one of these values, then we must change it's direction so that it won't leave the screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!c int x, y, dx, dy; void setup() { /* Things to do on startup */ x = 156; y = 96; dx = dy = 1; } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(x, y, 0, 1, 1, 1, 0); x = x + dx; y = y + dy; if(x == 0 || x == 311) dx = -dx; if(y == 0 || y == 191) dy = -dy;}

Run this program by pressing Ctrl+R. Congratulations, you have a moving ball that bounces off the screen edges!

Adding a Bat

A game that we can't play with isn't very interesing. So we'll add a bat that the player can control.

Go to the Sprite Editor (press F3) and let's draw the bat. This time we'll make it three sprites wide. You can draw these one by one, or you can select multiple sprites on the right and edit them together on the left.

tut_ball3.png
Drawing the bat

Just like with the ball, we'll use spr to display it on screen. But we also know that we'll need a variable to store its position, so let's add that too at once.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!c int x, y, dx, dy, px; void setup() { /* Things to do on startup */ x = 156; y = 96; dx = dy = 1; } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(x, y, 0, 1, 1, 1, 0); spr(px, 191, 1, 3, 1, 1, 0); x = x + dx; y = y + dy; if(x == 0 || x == 311) dx = -dx; if(y == 0 || y == 191) dy = -dy; }

So the x argument becomes px, and we don't want to move the bat vertically, just horizontally, so the y argument is a constant 191. Because we draw the bat at the 1st sprite, the sprite parameter is 1. And because it is three sprites wide, sw is 3, but it is still just one sprite tall, so sh is 1.

So far so good, but how will the player move this bat with the mouse? We'll set the mouse coordinate to the px variable for that. If you go the memory map, then under pointer you can see that the mouse's X coordinate is stored on 2 bytes at address 00016. To get this value, we use the inw function (word, because we need 2 bytes). But we also must not allow moving the bat off screen, so we clamp if the coordinate is bigger than the screen size minus the bat's size (which is three sprites, so 24 pixels). One more thing, the offsets in the memory map are given in hexadecimal, so we need the 0x prefix to tell the compiler that this is a hexadecimal number.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!c int x, y, dx, dy, px; void setup() { /* Things to do on startup */ x = 156; y = 96; dx = dy = 1; } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(x, y, 0, 1, 1, 1, 0); px = inw(0x16); if(px > 296) px = 296; spr(px, 191, 1, 3, 1, 1, 0); x = x + dx; y = y + dy; if(x == 0 || x == 311) dx = -dx; if(y == 0 || y == 191) dy = -dy; }

Let's run this program! We can see the bat and we can also move it around with the mouse. But there's a problem. The ball doesn't care where the bat is. Let's fix this by modifying the screen bottom check to a bat position check so that it would bounce on the bat only. Don't forget that the ball is 8 pixels tall, so we must check at a smaller coordinate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!c int x, y, dx, dy, px; void setup() { /* Things to do on startup */ x = 156; y = 96; dx = dy = 1; } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(x, y, 0, 1, 1, 1, 0); px = inw(0x16); if(px > 296) px = 296; spr(px, 191, 1, 3, 1, 1, 0); x = x + dx; y = y + dy; if(x == 0 || x == 311) dx = -dx; if(y == 0 || (y == 183 && x >= px && x <= px + 24)) dy = -dy; }

This means the y coordinate is zero, or it is 183 and at the same time the x coordinate is between px and px + 24 (where px is the bat's position).

Game Over

Now that we have changed the bottom check, we have to add a new check to see if the ball has left the screen. Of course this would mean game over.

First, remember that loop() runs constantly, so to stop moving the ball any further, we set the dx and dy variables to zero. Second, we want to display a game over message.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!c int x, y, dx, dy, px; str_t msg = "GAME OVER!"; void setup() { /* Things to do on startup */ x = 156; y = 96; dx = dy = 1; } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(x, y, 0, 1, 1, 1, 0); px = inw(0x16); if(px > 296) px = 296; spr(px, 191, 1, 3, 1, 1, 0); x = x + dx; y = y + dy; if(x == 0 || x == 311) dx = -dx; if(y == 0 || (y == 183 && x >= px && x <= px + 24)) dy = -dy; if(y > 199) { dx = dy = 0; text(184, (320 - width(2, msg)) / 2, 90, 2, 112, 128, msg); }}

We can display text on screen using the text function. At the bottom, the quick help shows what arguments it has. The first one is palidx, which is a palette index, the color of the text. Press F3 and click on the desired color. At the bottom, we'll see the index in hexadecimal and in parenthesis in decimal as well.

tut_ball4.png

I have choosen a red color, by the index B8 or 184 in decimal. Let's go back to the code editor by pressing F2, and enter this number. The next two arguments are x and y, the position on screen. We could have count the number of pixels in the text, but we are lazy, so we use the width function for that. Subtracting this from the width of the screen and dividing by two will place the text exactly at the middle. Because we are too lazy to type the text twice, we've also used a string variable msg to store the message. Therefore we use msg once when we calculate its size, and also when we pass it to the display function to say what to print. After the coordinates comes the type, which is the font's type, or more specifically its size. We want big letters, so we've used 2, double size. After this comes the shadow, shidx, which is also a color index. I've choosen a darker red here. For a shadow, it is important how transparent it is, we can specify this in the sha argument. That is an alpha channel value from 0 (fully transparent) to 255 (fully opaque). By using 128, which is half way between these values, I've told to use half transparent. And finally the str argument specifies the text to be displayed, which we store in the msg variable.

Restart

And last, if the player clicks, we want to restart the game.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!c int x, y, dx, dy, px; str_t msg = "GAME OVER!"; void setup() { /* Things to do on startup */ x = 156; y = 96; dx = dy = 1; } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(x, y, 0, 1, 1, 1, 0); px = inw(0x16); if(px > 296) px = 296; spr(px, 191, 1, 3, 1, 1, 0); x = x + dx; y = y + dy; if(x == 0 || x == 311) dx = -dx; if(y == 0 || (y == 183 && x >= px && x <= px + 24)) dy = -dy; if(y > 199) { dx = dy = 0; text(184, (320 - width(2, msg)) / 2, 90, 2, 112, 128, msg); if(getclk(BTN_L)) setup(); } }

To query if the user has clicked, we use the getclk function, with a BTN_L argument, meaning is the left button clicked. We've used the setup() function to set the default values for our little bouncing ball game. This is very convenient, because calling setup() now will therefore simply reset our game.



Walking

In this tutorial we'll create a walking character using sprites. This is the basis of many adventure and rouge-like games.

Get the Spritesheet

We could draw the sprites ourselves, but for simplicity I've downloaded a public domain sheet from the internet. This contains three animation phases in every line, and has one line per all 4 directions. There are lots of spritesheets on the net like this, because this is the popular RPG Maker's sprite layout.

Warning

Always check the licensing terms when you use assets downloaded from the internet. Do not use the asset if you're unsure about its terms of use.

The downloaded image can't be imported as-is. You'll have to change the image's dimensions to 256 x 256 pixels. Do not use resize, rather in GIMP select Image > Canvas size..., and in the popup window set width and height to 256. This way the spritesheet's size will be kept intact, just padded with transparent pixels. Start meg4 and drag'n'drop this 256 x 256 image on the window to import.

tut_walk1.png
The imported sprite sheet

As you can see, one character sprite is made up of 4 x 4 sprites. Let's display that! Press F2 to go to the Code Editor.

Display Character

We start with the usual skeleton. We know from the previous tutorial that we'll have to clear the screen and display the sprites using spr in the loop() function, because animation requires constant redrawing.

1
2
3
4
5
6
7
8
9
10
11
12
13
#!c void setup() { /* Things to do on startup */ } void loop() { /* Things to run for every frame, at 60 FPS */ cls(0); spr(144, 84, 0, 4, 4, 0, 0);}

The centre of the screen is at 160, 100 but our character is 4 x 4 sprites in size (32 x 32 pixels), so we have to subtract the half of that. Then comes 0 for sprite, meaning the first sprite, followed by 4, 4 because we want to display that many sprites. The last two parameters are 0, 0 because we don't want to scale nor transform, we want the sprites to be displayed exactly as they appear in the Sprite Editor.

Changing Directions

Next, let's allow changing the direction in which the character is pointing to. For that, we'll use getpad, which returns the gamepad's state. The primary gamepad controller is mapped to the keyboard, so pressing the cursor arrow keys will work too. To handle the direction, we'll need a variable to store the current direction, and this should select the sprite we draw.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!c int dir; void setup() { /* Things to do on startup */ } void loop() { /* Get user input */ if(getpad(0, BTN_D)) { dir = 0; } else if(getpad(0, BTN_L)) { dir = 128; } else if(getpad(0, BTN_R)) { dir = 256; } else if(getpad(0, BTN_U)) { dir = 384; } /* Display the character */ cls(0); spr(144, 84, dir, 4, 4, 0, 0); }

You can press F3 and click on the top left sprite of the character frame to get the sprite id for that direction. We set these ids in the dir variable, and then we'll use this variable in place of the sprite parameter.

Try it out! You'll see that by pressing the arrows our character will change directions.

Adding Animation

Our character doesn't walk yet. Let's fix it! We want our character to walk when a button (or arrow key) is pressed, and stop when that's released. For that, we'll need a variable to keep track if the button is currently pressed. We'll also need a variable to tell which animation frame to display. We could have used some funky expression to get the sprite id for the frame, but it is a lot easier to use an array instead. Because our character sprite is 4 x 4 sprites big, we can also precalculate these sprite id offsets in this array. One more thing, we have three animation sprites, but we'll have to display four frames, the sprite in the middle needs to be displayed twice to get a proper back and forth animation for moving the legs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!c int dir, pressed, frame; int anim[4] = { 4, 8, 4, 0 }; void setup() { /* Things to do on startup */ } void loop() { /* Get user input */ pressed = 0; if(getpad(0, BTN_D)) { dir = 0; pressed = 1; } else if(getpad(0, BTN_L)) { dir = 128; pressed = 1; } else if(getpad(0, BTN_R)) { dir = 256; pressed = 1; } else if(getpad(0, BTN_U)) { dir = 384; pressed = 1; } /* Display the character */ frame = pressed ? (frame + 1) & 3 : 0; cls(0); spr(144, 84, dir + anim[frame], 4, 4, 0, 0); }

First, we clear the pressed variable. Then in the if blocks we set it to 1. This way when we press a button, the variable becomes 1, but as soon as we release the button, it will be cleared to 0.

Next, we calculate which animation frame to display, but only if a button is pressed. If not, then we use a constant 0, meaning the first frame. Otherwise we increase frame to get the next frame, and we use bitwise AND to avoid overflow. When frame becomes 4 (which is 0b100 in binary) and we AND that with 3 (0b011), then the result will be 0, so the frame counter wraps around. We could have used "modulo number of frames" as well, but this is faster. Finally, we get the sprite id offset for this animation frame (stored in the anim array) and we add that to the dir variable to get which sprite to display.

Try it out, press Ctrl+R! It works fine, except our character is animated way too fast. That's because we increase the frame counter on every refresh, so 60 times per second. To fix this, we should get the ticks from the MMIO and calculate the frame independently to the refresh rate. However ticks counter is in millisec, so we should divide it. If we would divide that by 100, then we'd get 10 frames per second. We'll use shifting to the right 7 bits instead, which is equivalent of diving by 128. So first, press F1, and click on Misc. We can see that the ticks counter is at address 0x4, and it is 4 bytes long (so we'll have to use ini). Go back to the code and replace the frame calculation with this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!c int dir, pressed, frame; int anim[4] = { 4, 8, 4, 0 }; void setup() { /* Things to do on startup */ } void loop() { /* Get user input */ pressed = 0; if(getpad(0, BTN_D)) { dir = 0; pressed = 1; } else if(getpad(0, BTN_L)) { dir = 128; pressed = 1; } else if(getpad(0, BTN_R)) { dir = 256; pressed = 1; } else if(getpad(0, BTN_U)) { dir = 384; pressed = 1; } /* Display the character */ frame = pressed ? (ini(0x4) >> 7) & 3 : 0; cls(0); spr(144, 84, dir + anim[frame], 4, 4, 0, 0); }

And we're done! We have a nicely walking character animation that we can control in our game. You could also move the character on screen by using variables for the x, y arguments, but it is very common in such games to move the map under the character in the opposite direction instead.



Sound Effect

In this tutorial we'll prepare and import a sound effect. We'll use Audacity, but other wave editors should work too.

Load Wave

First, let's open the desired wave file in Audacity.

tut_snd1.png
Open the sound wave in Audacity

We can see right away that there are two waves, meaning our sound sample is stereo. MEG-4 can only handle mono, so go to Tracks > Mix > Mix Stereo Down to Mono to convert it. If you see only one waveform, you can skip this step.

Tuning and Volume

Now MEG-4 does tune the samples on its own, and for that to work, all imported waves must be tuned to a specific pitch. For some reason pitch detection is broken in Audacity, so you'll have to do it manually. Press Ctrl+A to select the wave, and then go to Analyze > Plot Spectrum....

tut_snd2.png
Analyzing the spectrum

Move your cursor above the biggest peak, and the current pitch will show up below (which is C3 in this example). If the shown note isn't C4, then select Effect > Pitch and Tempo > Change Pitch....

tut_snd3.png
Changing the pitch

In the "from" field, enter the value that you saw in the spectrum window, and in the "to" field enter C-4, then press "Apply".

tut_snd4.png
Changing the volume

Next thing is to normalize the volume. Go to Effect > Volume and Compression > Amplify.... In the popup window just press "Apply" (everything is autodetected correctly, no need to change anything).

Number of Samples

MEG-4 supports no more than 16376 samples per waves. If you have fewer samples than this in the first place, then you can skip this step.

Under the waveform you'll see the selection in milliseconds, click on that small "s" and change it to "samples".

tut_snd5.png
Changing the unit

In our example that's more than the allowed maximum. The number of samples is calculated as the value under "Project Sample Rate" multiplied by the length. So to lower the number of samples, either we lower the rate or we cut off the end of the wave. In this tutorial we'll do both.

Select everything let's say after 1.0, and press Del to delete. This does the trick, but makes the ending sound harsh. To fix that, select a reasonable portion at the end and go to Effect > Fading > Fade Out. This will make the wave end nicely.

tut_snd6.png
Chopping off and fading out the end

Our wave is still too long (44380 samples), but we can't cut off more without ruining the sample. This is where the sample rate comes in. In previous versions of Audacity, this was comfortably displayed at the bottom on the toolbar as "Project Rate (Hz)". But not any more, on newer Audacity it is a lot more complicated. First, click Audio Setup on the toolbar and select Audio Settings.... In the popup window, look for "Quality" / "Project Sample Rate", and from the drop-down select "Other..." to make the actual input field editable.

Warning

Make sure you calculate the number correctly. Audacity is incapable of undoing this step, so you can't give it another try!

Enter a number here, which is 16376 divided by the length of your wave (1.01 secs in our example) and press "OK".

tut_snd7.png
Lowering the number of samples

Select the entire wave (press Ctrl+A) then you should see that the end of the selection is below 16376.

Save and Import

Finally, save the new modified wave by selecting File > Export > Export as WAV. Make sure encoding is "Unsigned 8-bit PCM". As filename, enter dspXX.wav, where XX is a hex number between 01 and 1F, the MEG-4 wave slot where you want to load this sample (using a different filename works too, but then the wave will be loaded at the first free slot).

tut_snd8.png
Imported wave form

Once you have the file, just drag'n'drop it into the MEG-4 window and you're done.



MEG-4 License

gplv3.pngThe MEG-4 fantasy console is Free and Open Source software available under GPLv3+ or any later version of that license.

 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, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.