Switch branches/tags
Nothing to show
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.



Written by Lasse Öörni (loorni@student.oulu.fi)
Some things in the sound engine by Olli Niemitalo (oniemita@student.oulu.fi)
Changes & improvements by Kalle Niemitalo (kon@iki.fi)


BME was formerly called JUDAS V2.07fw, but I finally decided it was time
for a change, mainly because JUDAS was originally just a soundsystem and, as
far as I know, it isn't developed anymore :-)

BME is a library for (game) programming with an oldskool (DOS-like)
attitude, containing:

- 256 color graphics routines
  * Using SDL for low level support
  * Palette, Block (tile), Sprite routines
  * Some primitive functions like Plot, Line, Fillscreen

- Sound routines
  * Using SDL for low level support
  * Softwaremixing with linear interpolation
  * RAW/WAV sample support
  * MOD/XM/S3M playing

- Keyboard, joystick & mouse routines
  * Using SDL events

- Timing routines
  * Using SDL timing functions

- Datafile routines
  * For merging all data into one file and possibly linking it with the EXE

- Text routines
  * For printing text with sprites

- Block(tile) map routines
  * For oldskool 2D game background graphics

Applications using BME should work on any platform that has SDL ported on it.
See SDL homepage at


To use BME library under Win32, you should install the MINGW development
enviroment. For filesize reasons, precompiled binaries exist only for Win32.

BME is distributed under the terms of the BSD license:

Copyright (c) 2000-2003, Lasse Öörni, Olli Niemitalo. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice,
  this list of conditions and the following disclaimer in the documentation
  and/or other materials provided with the distribution.
- The names of its contributors may not be used to endorse or promote products
  derived from this software without specific prior written permission.



Include "bme.h" in your program and compile with:
gcc yourprogram.c libbme.a -lmingw32 -lSDLmain -lSDL

Unix-like systems:
Include "bme.h" in your program and compile with:
gcc yourprogram.c libbme.a -L/usr/X11R6/lib -lXext -lm -ldl -lSDLmain
-lSDL `sdl-config --libs --cflags`

Look at the included utilities' source code to see the how the library
functions are being used in them. Also, look at the graphical C64 tools
at http://covertbitops.cjb.net, they use BME. And finally, a fairly complex
example of the use of BME is the game BOFH: Servers Under Siege, available at

        http://www.student.oulu.fi/~loorni/software/winbofh.zip (binary)
        http://www.student.oulu.fi/~loorni/software/bofhsrc.zip (source)

Purpose of the utilities:

BMECONV  - Graphics converter program which can read IFF/LBM (Deluxepaint
           format) pictures and make raw pictures, palettes, sprite & block
           files of them
BMEPLAY  - Simple example of XM/MOD/S3M playing with Waveout. (a console
DATAFILE - compiles datafiles to be used with the io_opendatafile() function
DAT2INC  - Creates a C file containing a big char array :) out of a
           datafile. Then you can use io_openlinkeddatafile(datafile) to
           open the datas, without having to do suspicious things like
           linking additional binary data to the exe
INFEDIT  - Utility to create collision maps (blockinfos) for the blocks
MAPEDIT  - Utility to create block maps up to 4 layers deep


For most functions that return something, zero return value means failure
(look at the variable bme_error for error cause) and nonzero success. Except
BME IO functions that work like their counterparts in io.h.


int snd_init(unsigned mixrate, unsigned mixmode, unsigned bufferlength,
        unsigned channels, int usedirectsound);

        Initializes sound with the parameters given. bufferlength is the
        sound buffer length in milliseconds and mixmode is combined of
        MONO, STEREO, EIGHTBIT & SIXTEENBIT bit flags. mixrate is in hertz.
        channels is the maximum number of simultaneous sounds that can be
        mixed. (In the other functions, channels are numbered 0 - channels-1)

        usedirectsound is obsolete as of V1.27. SDL uses what it wants to
        use :)

        If stereo or 16-bit modes aren't supported, or mixrate isn't
        supported, snd_init() should "fall back" to 8-bit or mono modes.

        Sound will be mixed & played with SDL's audio callback
        mechanism so the "main program" doesn't have to worry about
        updating the sound buffer.

        If sound is re-initted without changing number of channels then all
        channels will continue playing.

        Minimum sound buffer size for glitch-free sound seems to depend on
        OS used and the soundcard drivers. Use 200ms or higher to be safe.

void snd_uninit(void);

        Shuts down the sound system. This is automatically registered by
        snd_init() as an atexit() function so you don't have to worry about
        this yourself.

void snd_setcustommixer(void (*custommixer)(Sint32 *dest, unsigned samples));

        Sets a custom mixer routine to be called when updating sound. The
        parameters are a pointer to a "clipping buffer" consisting of ints
        and the number of samples to calculate. (if snd_mixmode has the bit
        STEREO set, two ints need to be output for each sample)

        This function is only for those who know what they're doing and are
        implementing some kind of custom sound playing system (synthetic
        sounds perhaps.)

SAMPLE *snd_allocsample(int length);

        Allocate memory for a sample. Normally you don't need to call this.

void snd_freesample(SAMPLE *smp);

        Frees the sample.

void snd_playsample(SAMPLE *smp, unsigned chnum, unsigned frequency, unsigned char volume, unsigned char panning);

        Plays the sample on a channel with the given parameters. Volume is
        0-64 and panning is 0 (left) - 255 (right).

void snd_ipcorrect(SAMPLE *smp);

        Obsolete as of V1.27.

void snd_stopsample(unsigned chnum);

        Stops playing a sample.

void snd_preventdistortion(unsigned channels);

        Divides maximum mastervolume (255) with the amount of channels you
        are going to use, and sets it on all channels. This ensures that
        the sound output will not clip but will generally result in too
        quiet sound.

void snd_setmastervolume(unsigned chnum, unsigned char mastervol);

        Sets mastervolume on a channel. Mastervolume range is 0 (total silence)
        to 255 (maximum amplitude of the sound device)

void snd_setmusicmastervolume(unsigned musicchannels, unsigned char mastervol);

        Music is played on channels 0 - musicchannels-1. Sets mastervolume
        for them.

void snd_setsfxmastervolume(unsigned musicchannels, unsigned char mastervol);

        Sound effects can be played on channels musicchannels - channels-1.
        Sets mastervolume for them.

SAMPLE *snd_loadrawsample(char *name, int repeat, int end, unsigned char voicemode);

        Loads a 8bit signed data raw sample.

        Note for all sound/module loading functions from V1.21 onwards:
        Previously they didn't load anything if sound wasn't initialized first.
        But now this check is removed. So if you want to conserve memory when
        your program is in nosound-mode (check the variable snd_sndinitted,
        zero if sound is not initialized), don't call the loading functions :-)

SAMPLE *snd_loadwav(char *name);

        Loads a WAV sample. Stereo samples are converted to mono.

Next come the functions for XM loading/playing. MOD & S3M functions work
exactly the same way so they aren't explained.

int snd_loadxm(char *name);

        Loads an XM module.

void snd_freexm(void);

        Frees an XM module. The previous is automatically freed when loading
        a new module so you don't need to call this if you don't want to.

void snd_playxm(int pos);

        Starts playing from the position specified. This makes multi-songs
        possible in a single XM file. Playback will not start if sound was
        initted with fewer channels than in the XM.

void snd_stopxm(void);

        Stops XM playing.

unsigned char snd_getxmpos(void);

        Returns the current position.

unsigned char snd_getxmline(void);

        Returns the current pattern line.

unsigned char snd_getxmtick(void);

        Returns the current tick tempo counter.

unsigned char snd_getxmchannels(void);

        Returns number of channels used by the XM.

char *snd_getxmname(void);

        Returns name of the song.


int win_openwindow(char *appname, char *icon);

        Calls SDL_Init(), so this is the first thing you should call in 
        your program.

        win_fullscreen   - are graphics running in fullscreen mode
                           (default FALSE)
        win_quitted      - has the close button been pressed

void win_closewindow(void);

        Calls gfx_uninit().

void win_checkmessages(void);

        Checks SDL messages, like keyboard input, mouse move etc.
        win_getspeed() and kbd_waitkey() will always call this. If you
        spend a long time in your program without calling those functions
        it's a good idea to call this function once in a while.

        HINT: If you implement frame skipping in your program in such way:

                frameskip = win_getspeed(refreshrate);
                while (frameskip--) do_frame_movement();

        then putting a call to win_checkmessages() into the beginning of
        do_frame_movement() will achieve smoother keyboard & mouse control on
        slower machines.

void win_messagebox(char *string);

        Doesn't do anything in V1.27.

int win_getspeed(int framerate);

        Gets the number of frames elapsed since this function was last
        called. Use this to synchronize to the screen updates and handle

        Before a loop (for example the game main loop) you should call this
        once to clear time accumulated before the loop.

        Note that this function keeps returning you nonzero frame count even
        if the application is minimized or inactive.

void win_setmousemode(int mode)

        This controls if the mouse cursor is visible or hidden in
        fullscreen & windowed modes.

        MOUSE_ALWAYS_VISIBLE - mouse cursor is visible in both fullscreen &
        windowed modes. 

        MOUSE_FULLSCREEN_HIDDEN - mouse cursor is hidden in fullscreen mode
        but visible in windowed mode. 

        MOUSE_ALWAYS_HIDDEN - mouse cursor is hidden in both modes.


int gfx_init(unsigned xsize, unsigned ysize, unsigned framerate, unsigned flags);

        Initializes the 256 color graphics engine with the virtual screen 
        size and flags you specify. Framerate is obsolete as of V1.27

        Flags are:

        GFX_SCANLINES - Expand window size to 2x of the virtual screen size
        and make each other horizontal line blank for a TV-like feeling that's
        popular in emulators :-)

        GFX_DOUBLESIZE - Expand window size to 2x of the virtual screen size,
        doubling the pixels both horizontally and vertically. Be warned, this
        mode can increase CPU usage quite a lot because it requires the most
        accesses to the (slow) display adapter memory when updating the screen.

        GFX_FULLSCREEN - Initialize graphics in fullscreen mode.

        GFX_WINDOW - Initialize graphics in windowed mode.
        If neither of these is specified the last mode will be used. This is
        controlled by the variable win_fullscreen, and its default value
        at startup is 0 (windowed)

        GFX_NOSWITCHING - Disable automatic ALT-ENTER switching between
        windowed and fullscreen (this is taken care by the window procedure)

        In fullscreen mode there might not necessarily be a display mode that
        is exactly the size of the virtual screen. In this case SDL 
        displays a black border around the window.

void gfx_uninit(void);

        Frees all graphics engine resources.

int gfx_reinit(void);

        Re-initializes graphics engine with previous virtual screen size &
        mode. The window procedure uses this upon fullscreen/windowed

void gfx_updatepage(void);

        Updates the screen to match the contents of the virtual 

void gfx_blitwindow(void);

        Does nothing as of V1.27.

void gfx_setmaxcolors(int num);

        Sets number of colors in palettized modes. Call before calling
        gfx_init() for the first time. Does almost nothing as of V1.27.

int gfx_loadpalette(char *name);

        Loads an oldskool 768 byte palette. (can be created with the BMECONV

        All 256 colors can be used, but color 0 is hard-coded to black and
        color 255 to white. 

void gfx_calcpalette(int fade, int radd, int gadd, int badd);

        Calculates palette with for gfx_setpalette(). All parameters
        are in the range 0-64.

void gfx_setpalette(void);

        Sets the palette.

void gfx_setclipregion(unsigned left, unsigned top, unsigned right, unsigned bottom);

        Sets clipping area for the drawing operations. Left and top signify
        the leftmost/topmost visible pixel while right & bottom signify the
        first invisible pixel. For example a fullscreen clipping area for a
        320x200 display is 0,0,320,200.

int gfx_loadblocks(char *name);

        Loads a block-file made with BMECONV.
        Blocks are meant to be used for background graphics, unlike sprites
        they don't have a hotspot (it's always 0,0).

int gfx_loadsprites(int num, char *name);

        Loads a spritefile made with BMECONV. There can be many spritefiles
        loaded at once, the number signifies this (default maximum is 256
        sprite files, numbers 0-255). See below how to increase this maximum

void gfx_freesprites(int num);

        Frees a spritefile.

void gfx_setmaxspritefiles(int num);

        Call this before the first call to gfx_loadsprites() if you want more
        or less spritefiles than the default (256). The value can be set only
        once during runtime, subsequent calls do nothing.

void gfx_drawblock(int x, int y, unsigned num);

        Draws a block. Block numbering starts from 1 and 0 is always an empty
        (& transparent) block.

void gfx_drawsprite(int x, int y, unsigned num);

        Draws a sprite. High 16 bits of "num" indicate spritefile number
        and low 16 bits indicate sprite frame within the spritefile (starting
        from 1)

void gfx_drawspritec(int x, int y, unsigned num, int color);

        Draws a sprite in a certain single color. Useful for example when
        making enemies "flash" while being hit in an oldskool action game.

void gfx_drawspritex(int x, int y, unsigned num, unsigned char *xlattable);

        Draws a sprite using a translation-table for colors. The translation
        table is an array of 256 bytes that maps each color to another. Note
        that this function is slower than the two above, but it's still quite
        useful for example drawing text with the same font in different colors.

void gfx_getspriteinfo(unsigned num);

        Returns sprite info in the following variables:
        extern int spr_xsize;
        extern int spr_ysize;
        extern int spr_xhotspot;
        extern int spr_yhotspot;

void gfx_fillscreen(int color);

        Fills the whole screen with a single color.

void gfx_plot(int x, int y, int color);

        Draws a point.

void gfx_line(int x1, int y1, int x2, int y2, int color);

        Draws a line. Supports full clipping!


int kbd_init(void);

        Does nothing, returns always success.

void kbd_uninit(void);

        Does nothing.

int kbd_waitkey(void);

        Waits for a key and returns the SDL keycode. The key's state
        will be reset.

int kbd_getkey(void);

        Gets the SDL keycode if a key was pressed, otherwise returns zero.
        The key's state will be reset.

int kbd_checkkey(int rawcode);

        Checks if a key is down. Its state will also be reset.

int kbd_getascii(void);

        Get the latest ASCII code of a pressed key.
        Before a loop (for example a hiscore entry loop), you should
        call this once to clear the buffered character.

char *kbd_getkeyname(int rawcode);

        Get name for the key.

extern unsigned char win_keytable[];

        This is the keyboard state table for each key; zero is not
        pressed and nonzero pressed. kbd_checkkey(), kbd_getkey() etc.
        automatically zero entries of this table for key repeat.

extern unsigned char win_keystate[];

        This is a copy of the state table, that kbd_checkkey(), 
        kbd_getkey() etc. don't clear. Use this to check continuous 
        pressing of keys.


int mou_init(void);

        Initializes the mouse system, basically just clears the mouse button
        status. Returns always success.

void mou_uninit(void);

        Does nothing. :-)

void mou_getpos(unsigned *x, unsigned *y);

        Gets current mouse position within the application window. It's always
        scaled to the size of the virtual screen so you don't have to make
        adjustments when using the doublesize-mode.

        In fullscreen mode the mouse pointer moves over the whole screen and
        the coordinates are once again scaled to the size of the virtual
        screen. However, this can introduce some distortion of the motion if
        the aspect ratios of virtual screen and the whole screen don't match.

        For accurate (and unlimited by the application window borders)
        mouse movement it is recommended to set the mouse cursor hidden and
        use only mou_getmove() function.

void mou_getmove(int *dx, int *dy);

        Gets relative mouse movement. Works by centering the (invisible)
        mouse pointer on the screen (or window) after each call and measuring
        the displacement from the center. Works only if the cursor is hidden
        (see win_setmousemode()), otherwise returns 0,0.

unsigned mou_getbuttons(void);

        Gets mouse button status. For the bit values, look at bme_main.h

        NOTE: In windowed mode there's a "bug" that if you press down a mouse
        button and release it outside the window the program will think the
        button is still down. By hiding the mouse cursor the cursor will be
        "trapped" inside the window and this "bug" never appears.


int joy_detect(unsigned id);

        Detects presence of joystick (id ranging from 0 to n). As of V1.27,
        this is necessary before getting input from joystick: SDL requires
        joysticks to be "opened" first.

unsigned joy_getstatus(unsigned id, unsigned threshold);

        Returns joystick status (directions are "digital" too for true
        oldskool experience, look for bit values in bme_main.h)
        Threshold is for the directions, between 0-32768. (16384 in the
        halfway is quite good I think :-))


These are used by all other BME modules, so if you open a datafile
all BME functions will load the requested files from the datafile.
A datafile can be compiled with the DATAFILE program.

int io_open(char *name);
int io_lseek(int handle, int bytes, int whence);
int io_read(int handle, void *buffer, int size);
void io_close(int handle);

        work like the ones in io.h

int io_opendatafile(char *name);

        Opens a datafile. Data appended to an executable is no longer 

int io_openlinkeddatafile(unsigned char *ptr)

        Opens a datafile from memory. Use DAT2INC to create an array out
        of your datafile, include the output in your program and then call

void io_setfilemode(int usedf);

        Use this to momentarily switch datafile use on/off. 1 is on and
        0 is off.


void txt_print(int x, int y, unsigned spritefile, char *string);

        Prints text to coordinates x,y using the specified spritefile as fonts.
        The spritefile should contain 64 sprites which define the charset in
        the same order as C64 screen codes: Letters first (ascii codes
        64-95) and then numbers and other special characters (ascii codes
        32-63). Look at the included example FONTS.LBM to see how this is
        exactly done.

void txt_printcenter(int y, unsigned spritefile, char *string);

        Same as above, but centers the text automatically in X-direction.

void txt_printx(int x, int y, unsigned spritefile, char *string, unsigned char *xlattable);

        Like txt_print() but uses gfx_drawspritex() for color translation.

void txt_printcenterx(int y, unsigned spritefile, char *string, unsigned char *xlattable);

        Like txt_printcenter() but uses gfx_drawspritex() for color

extern int txt_lastx, txt_lasty;

        Contain the X,Y position of last letter printed after calls to the
        above functions.

extern int txt_spacing;

        Amount of pixels to be added to the next letter's X position, in
        addition to the last letter's X size. By default this is -1.


int map_loadmap(char *name);

        Load a map created with MAPEDIT. Note: Palette and blocks aren't
        loaded automatically, you must load them yourself!

void map_freemap(void);

        Frees a map that has been loaded. map_loadmap() does this automatically
        when loading a new map.

int map_loadblockinfo(char *name);

        Loads a block-infofile (for background collision detection) created
        with INFEDIT.

void map_drawlayer(int l, int xpos, int ypos, int xorigin, int yorigin,
        int xblocks, int yblocks);

        Draws a layer (0-3) of the map you specify. xpos and ypos are the
        position on the map (in pixels), xorigin & yorigin are the starting
        place on screen (normally 0,0 but nonzero values can be used for
        splitscreen effects) and xblocks and yblocks are the number of blocks
        you want to be drawn in X and Y directions. For example on a 320x200
        display with 16x16 blocks, 21x14 blocks must be drawn to ensure all
        of the screen is covered.

void map_drawalllayers(int xpos, int ypos, int xorigin, int yorigin,
        int xblocks, int yblocks);

        Does the same as above, but for all layers.

unsigned char map_getblockinfo(int l, int xpos, int ypos);

        Gets blockinfobyte from layer l (0-3), map position xpos,ypos.
        What the blockinfobyte means, depends on you and the kind of BG
        collisions you want to detect. For example, I usually use this for
        platform games:

                Bit 0 - can walk on
                Bit 1 - is an obstacle
                Bit 2 - is a ladder
                Bit 3 - is a door

unsigned map_getblocknum(int l, int xpos, int ypos);

        Gets block number from layer l (0-3), map position xpos,ypos
        (in pixels.)

void map_setblocknum(int l, int xpos, int ypos, unsigned num);

        Sets block number to num at layer l (0-3), map position xpos,ypos
        (in pixels.)

void map_shiftblocksback(unsigned first, int amount, int step);

        Shifts blocks backwards in a circular fashion. first is the first block
        to be shifted, amount is the length of the shifting chain (in blocks)
        and step indicates by how many blocks they're shifted. Block 0 (empty)
        cannot be part of the shifting chain. This function doesn't actually
        move the block data around but just the pointers to blocks, so it can
        be used to achieve background animation fast & easily.

        An example: Let's say blocks 5-20 contain 4 frames of animation
        for some background "object" that consists of 4 blocks. The blocks
        are ordered this way:

                5-8   first frame
                9-12  second frame
                13-16 third frame
                17-20 fourth frame

        To animate this "object" correctly, map_shiftblocksback(5, 16, 4) would
        be used. (16 blocks, starting from 5, step 4)

void map_shiftblocksforward(unsigned first, int amount, int step);

        Works the same way as above, but shifts blocks forwards instead.
        Note that names are misleading because of the way animation works:
        when you have a block animation defined like in the example above, you
        need to call map_shitblocksback() to make the animation go forwards.



BMECONV creates raw-bitmap, palette, block and spritedata files out of
LBM pictures.

Creating palettes and raw-bitmaps is quite straightforward. :-)

For blockfiles you just draw all the blocks in the picture, left to right,
up to down, without any spaces between them.

Blocks can use a transparent color whose number is specified on the command
line (default 252). Note that completely non-transparent blocks can be drawn
in an optimized manner so use them whenever possible!

For an example of blocks, look at TEST.LBM

For spritefiles you need to draw rectangles around the sprites
in the "rectangle color" that can be specified on the command line (default
254). BMECONV will sweep the picture left-right, top-bottom in search
of sprite rectangles and order them in that order (leftmost & topmost sprite
gets number 1, next on the right number 2 etc.)

Sprites have a hotspot as well which defines the "origin" of the sprite. This
can be marked on the sides of the sprite rectangle like in the picture below:
(r = rectangle color, h = hotspot color (command-line definable as wel, default
253) * = sprite pixels)

        r r r r r h r r r r
        r                 r
        r     * * * *     r
        r   * * * * * *   r
        r   * * * * * *   r
        h   * * * * * *   r
        r   * * * * * *   r
        r     * * * *     r
        r                 r
        r r r r r r r r r r

If a hotspot is not defined it is 0,0.

For an example of sprites, look at FONTS.LBM & EDITOR.LBM (sprites used by


Press F10 in the programs for quite good online help. :-)
In INFEDIT the idea is that the leftmost 4x4 square represents bit 0 in the
blockinfodata, the next 4x4 square represents bit 1 and so on.

Note that regardless of the block size, INFEDIT currently supports only a
4x4 square. This will mean that each small square is 4 pixels for a 16x16
block, 8 pixels for 32x32 etc.


These programs explain themselves quite well by running them without


To do this, you must generate the makefile yourself. BME shouldn't 
depend on any evil things specific to GCC for example. Also, as of V1.27
it's 100% C code - no ASM modules at all.


V1.0    - Original release

V1.01   - gfx_line() didn't store EBX register, fixed
        - gfx_line() popped ESI & EDI in the wrong order, fixed
        - gfx_line() exited thru wrong exitpoint if the line was point-like
          and was outside the screen, fixed
        - Corrected clipping behaviour in gfx_line()
        - One extra palettechange when initting graphics; trying to make
          sure colors actually stay the way they should be in 8bpp mode
        - Fixed handling of long (>= 126 pixels) empty spriteslices in BMECONV

V1.02   - Added support for the STRETCHBLT doublesize mode, though it's
          generally quite slow (at least on NT4.0)
        - MAPEDIT and INFEDIT are now true windowed applications (no
          unnecessary console window opened anymore)

V1.05   - Scrapped the DIBSection code and changed to DirectDraw
        - Full 256 color palettes now supported (or at least 254)
        - Added fullscreen mode with automatic ALT-ENTER switching
          (automatic switching can also be disabled)
        - Added mou_setmode() to control visibility of mouse cursor in both
          windowed and fullscreen modes
        - Added mou_getmove() to get relative mouse movement
        - Added gfx_setmaxspritefiles() for dynamic allocation of the
          spritefile table
        - Added gfx_drawspritex() for drawing sprites with color translation
        - Fixed a text-positioning bug in MAPEDIT's help screen
        - Fixed the "window activation mouse click", it is ignored now
        - Changed snd_init() to just malloc() the sound buffers instead of
        - Sound buffer length setting simplified: just the total length in
          milliseconds is told to snd_init() and it divides it into the
          required amount of 25ms fragments.
        - MAPEDIT & INFEDIT utilize the new functionality (fullscreen
          switching and relative mouse movement)

V1.06   - Added a balancing system to the sound buffer filling algorithm,
          trying to achieve even-sounding playback for fast repeating sound
        - Fixed windowed mode mouse movement when the window center point
          is outside the screen
        - Changed sound mixing to use a second thread instead of a callback
        - Brought back DIBSection support to gfx_init() as optional
        - gfx_init() will always place the program window topmost
        - Renamed mou_setmode() to win_setmousemode() to avoid unnecessary
          inclusion of mouse module to every program that uses graphics

V1.1    - Added support for LCC compiler
        - Added map_setblocknum()
        - Added map_shiftblocksback()
        - Added map_shiftblocksforward()
        - Fixed some limit checks in bme_map.c
        - Changed directory structure to include subdirectories
        - Changed all assembly code modules to NASM format
        - Changed sound engine to use 20ms buffer fragments instead
        - Removed the intense unrolling from the sound mixing inner loops.
          (there was about 10KB of them) This increases CPU use somewhat but
          reduces memory use: now each loaded sample doesn't need the 3KB
          safety buffer anymore
        - Removed some strange-named (heh) unused variables from bme_joy.c

V1.2    - Added DirectSound support
        - Added a warning of LCC bugs in the documentation; all utilities
          have now been compiled with BCC (as it was before) and there's no
          longer a precompiled library for LCC
        - Added read error checks to bme_gfx.c
        - Added a new utility, STRIPXM.EXE, which separates the song data and
          samples from an XM module, detecting any duplicate samples
        - Added better online-help to the commandline-utilities :-)
        - Fixed a crash in XM loading (pingpong loop in a zero-length sample)
        - Fixed window flicker in the BME example program; it was initting
          the graphics mode twice even when the first time (with DirectDraw)
        - Changed Waveout operation to be single-buffered
        - Changed sound update interval to 15ms
        - Changed the datafile identification string :-)
        - Removed some more vulgarities from the source code :-)

V1.21   - Added requirement for the DirectSound buffer to be a software buffer
          (because its contents will be constantly changing; any "upload" to
          soundcard memory is unacceptable)
        - Added the mixmode name table from JUDAS2.05y to BMEPLAY; it now
          displays the actual mixrate & mixmode in use
        - Changed some error codes for DirectDraw & DirectSound: now
          the error is BME_OPEN_ERROR if the DLL cannot be opened or the
          interface cannot be created, and BME_GRAPHICS_ERROR / BME_SOUND_ERROR
          in other cases.
        - Changed sound update interval back to 20ms
        - Removed all mixrate checks & adjustments from snd_init()
        - Removed checks for sound initialization from all snd_loadsomething()
        - Removed the error code BME_OUT_OF_CHANNELS; channel amount check
          moved from XM/S3M/MOD load functions to XM/S3M/MOD play functions
        - Removed WAVE_ALLOWSYNC from the WaveOut open call, because
          it really isn't what we want :-)
        - Removed some nonexistant variables from bme_snd.h

V1.22   - Added MINGW support
        - Added directory path stripping to DATAFILE.EXE: there is only room
          for 8+3 chars in the datafile's fileheaders. Now files can be
          collected to the datafile from different subdirectories, without
        - Added a reminder of the 8+3 chars filename limit to DATAFILE.EXE's
          usage text
        - Brought back the LCC compiled library
        - Fixed a memory leak in DATAFILE.EXE
        - Changed DirectDraw fullscreen mode palette to have PC_NOCOLLAPSE
          flag set in every color; don't think this is actually needed but
          doesn't hurt anyway
        - Moved the variable win_hwnd to bme.c to be sure any window/graphics
          code isn't included in pure sound-only applications (for example

V1.23   - Added the function gfx_setmaxcolors() to control palette size in
          8-bit screen modes
        - Added configurable text spacing
        - Added a gfx_updatepage() call in gfx_init(), to prevent random memory
          data displayed in windowed mode before main program's first call
          to gfx_updatepage()
        - Added wildcard support to DATAFILE.EXE (now there can be wildcards
          in the filelist-file)
        - Fixed crash related to re-entry of gfx_uninit() & gfx_init() when
          switching from fullscreen to windowed mode manually

V1.24   - Added kbd_getvirtualkey()
        - Icon can be specified in win_openwindow()

V1.25   - Added snd_setcustommixer()

V1.26   - Sound mixing thread exit check done in a different way -> no more
          lockup on my W2K system.

V1.27   - BME goes cross-platform by using the SDL library!
        - No ASM code anymore
        - Should be endian-independent
        - Removed EXELINK utility, replaced with DAT2INC for more portability
          (no binary data appended to executable)
        - Removed STRIPXM utility
        - Removed the example program :)

V1.28   - Uses the BSD license
        - Added some numeric keypad key codes to bme_main.h

V1.29   - Changes by Kalle Niemitalo integrated

V1.3    - endian.h renamed to fileio.h for possible clashes with system 
          include files
        - ifndef guards added to include files

V1.31   - Fixed handling of raw keycodes over 511 (ignored for now for the key
          state, still readable from kbd_getvirtualkey)