3775 lines
158 KiB
C
3775 lines
158 KiB
C
/*
|
|
------------------------------------------------------------------------------
|
|
Licensing information can be found at the end of the file.
|
|
------------------------------------------------------------------------------
|
|
|
|
Note that different files in this project has different licenses. Each source
|
|
file contains licensing information for the code in that file.
|
|
|
|
*/
|
|
|
|
#ifndef dos_h
|
|
#define dos_h
|
|
|
|
enum videomode_t {
|
|
videomode_40x25_8x8,
|
|
videomode_40x25_9x16,
|
|
videomode_80x25_8x8,
|
|
videomode_80x25_8x16,
|
|
videomode_80x25_9x16,
|
|
videomode_80x43_8x8,
|
|
videomode_80x50_8x8,
|
|
videomode_320x200,
|
|
videomode_320x240,
|
|
videomode_320x400,
|
|
videomode_640x200,
|
|
videomode_640x350,
|
|
videomode_640x400,
|
|
videomode_640x480,
|
|
|
|
force_size_videomode = 0x7ffffff // ensure videomode_t is 32-bit value
|
|
};
|
|
|
|
void setvideomode( enum videomode_t mode );
|
|
void setdoublebuffer( int enabled );
|
|
int screenwidth( void );
|
|
int screenheight( void );
|
|
unsigned char* screenbuffer( void );
|
|
unsigned char* swapbuffers( void );
|
|
void waitvbl( void );
|
|
void setpal( int index, int r, int g, int b );
|
|
void getpal( int index, int* r, int* g, int* b );
|
|
|
|
int shuttingdown( void );
|
|
|
|
|
|
void cputs( char const* string );
|
|
void textcolor( int color );
|
|
void textbackground( int color );
|
|
void gotoxy( int x, int y );
|
|
int wherex( void );
|
|
int wherey( void );
|
|
void clrscr( void );
|
|
void curson( void );
|
|
void cursoff( void );
|
|
|
|
unsigned char* loadgif( char const* filename, int* width, int* height, int* palcount, unsigned char palette[ 768 ] );
|
|
|
|
void blit( int x, int y, unsigned char* source, int width, int height, int srcx, int srcy, int srcw, int srch );
|
|
void maskblit( int x, int y, unsigned char* source, int width, int height, int srcx, int srcy, int srcw, int srch,
|
|
int colorkey );
|
|
|
|
void clearscreen( void );
|
|
int getpixel( int x, int y );
|
|
void hline( int x, int y, int len, int color );
|
|
void putpixel( int x, int y, int color );
|
|
|
|
void setdrawtarget( unsigned char* pixels, int width, int height );
|
|
void resetdrawtarget( void );
|
|
|
|
void setcolor( int color );
|
|
int getcolor( void );
|
|
void line( int x1, int y1, int x2, int y2 );
|
|
void rectangle( int x, int y, int w, int h );
|
|
void bar( int x, int y, int w, int h );
|
|
void circle( int x, int y, int r );
|
|
void fillcircle( int x, int y, int r );
|
|
void ellipse( int x, int y, int rx, int ry );
|
|
void fillellipse( int x, int y, int rx, int ry );
|
|
void drawpoly( int* points_xy, int count );
|
|
void fillpoly( int* points_xy, int count );
|
|
void floodfill( int x, int y );
|
|
void boundaryfill( int x, int y, int boundary );
|
|
|
|
void outtextxy( int x, int y, char const* text );
|
|
void wraptextxy( int x, int y, char const* text, int width );
|
|
void centertextxy( int x, int y, char const* text, int width );
|
|
|
|
enum {
|
|
DEFAULT_FONT_8X8 = 1,
|
|
DEFAULT_FONT_8X16 = 2,
|
|
DEFAULT_FONT_9X16 = 3,
|
|
};
|
|
|
|
void settextstyle( int font, int bold, int italic, int underline );
|
|
int installuserfont( char const* filename );
|
|
|
|
|
|
enum {
|
|
DEFAULT_SOUNDBANK_AWE32 = 1,
|
|
DEFAULT_SOUNDBANK_SB16 = 2,
|
|
};
|
|
|
|
void setsoundbank( int soundbank );
|
|
int installusersoundbank( char const* filename );
|
|
|
|
|
|
#define MUSIC_CHANNELS 16
|
|
void noteon( int channel, int note, int velocity);
|
|
void noteoff( int channel, int note );
|
|
void allnotesoff( int channel );
|
|
void setinstrument( int channel, int instrument );
|
|
|
|
struct music_t;
|
|
struct music_t* loadmid( char const* filename );
|
|
struct music_t* loadmus( char const* filename );
|
|
struct music_t* loadmod( char const* filename );
|
|
struct music_t* loadopb( char const* filename );
|
|
struct music_t* createmus( void* data, int size );
|
|
void playmusic( struct music_t* music, int loop, int volume );
|
|
void stopmusic( void );
|
|
int musicplaying( void );
|
|
void musicvolume( int volume );
|
|
|
|
enum soundmode_t {
|
|
soundmode_8bit_mono_5000,
|
|
soundmode_8bit_mono_8000,
|
|
soundmode_8bit_mono_11025,
|
|
soundmode_8bit_mono_16000,
|
|
soundmode_8bit_mono_22050,
|
|
soundmode_8bit_mono_32000,
|
|
soundmode_8bit_mono_44100,
|
|
soundmode_16bit_mono_5000,
|
|
soundmode_16bit_mono_8000,
|
|
soundmode_16bit_mono_11025,
|
|
soundmode_16bit_mono_16000,
|
|
soundmode_16bit_mono_22050,
|
|
soundmode_16bit_mono_32000,
|
|
soundmode_16bit_mono_44100,
|
|
soundmode_8bit_stereo_5000,
|
|
soundmode_8bit_stereo_8000,
|
|
soundmode_8bit_stereo_11025,
|
|
soundmode_8bit_stereo_16000,
|
|
soundmode_8bit_stereo_22050,
|
|
soundmode_8bit_stereo_32000,
|
|
soundmode_8bit_stereo_44100,
|
|
soundmode_16bit_stereo_5000,
|
|
soundmode_16bit_stereo_8000,
|
|
soundmode_16bit_stereo_11025,
|
|
soundmode_16bit_stereo_16000,
|
|
soundmode_16bit_stereo_22050,
|
|
soundmode_16bit_stereo_32000,
|
|
soundmode_16bit_stereo_44100,
|
|
};
|
|
|
|
void setsoundmode( enum soundmode_t mode );
|
|
|
|
#define SOUND_CHANNELS 16
|
|
struct sound_t;
|
|
struct sound_t* loadwav( char const* filename );
|
|
struct sound_t* createsound( int channels, int samplerate, int framecount, short* samples );
|
|
void playsound( int channel, struct sound_t* sound, int loop, int volume );
|
|
void stopsound( int channel );
|
|
int soundplaying( int channel );
|
|
void soundvolume( int channel, int left, int right );
|
|
|
|
|
|
enum keycode_t {
|
|
KEY_INVALID, KEY_LBUTTON, KEY_RBUTTON, KEY_CANCEL, KEY_MBUTTON, KEY_XBUTTON1, KEY_XBUTTON2, KEY_BACK, KEY_TAB,
|
|
KEY_CLEAR, KEY_RETURN, KEY_SHIFT, KEY_CONTROL, KEY_MENU, KEY_PAUSE, KEY_CAPITAL, KEY_KANA, KEY_HANGUL = KEY_KANA,
|
|
KEY_JUNJA, KEY_FINAL, KEY_HANJA, KEY_KANJI = KEY_HANJA, KEY_ESCAPE, KEY_CONVERT, KEY_NONCONVERT, KEY_ACCEPT,
|
|
KEY_MODECHANGE, KEY_SPACE, KEY_PRIOR, KEY_NEXT, KEY_END, KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN,
|
|
KEY_SELECT, KEY_PRINT, KEY_EXEC, KEY_SNAPSHOT, KEY_INSERT, KEY_DELETE, KEY_HELP, KEY_0, KEY_1, KEY_2, KEY_3, KEY_4,
|
|
KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K,
|
|
KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_LWIN,
|
|
KEY_RWIN, KEY_APPS, KEY_SLEEP, KEY_NUMPAD0, KEY_NUMPAD1, KEY_NUMPAD2, KEY_NUMPAD3, KEY_NUMPAD4, KEY_NUMPAD5,
|
|
KEY_NUMPAD6, KEY_NUMPAD7, KEY_NUMPAD8, KEY_NUMPAD9, KEY_MULTIPLY, KEY_ADD, KEY_SEPARATOR, KEY_SUBTRACT, KEY_DECIMAL,
|
|
KEY_DIVIDE, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12,
|
|
KEY_F13, KEY_F14, KEY_F15, KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20, KEY_F21, KEY_F22, KEY_F23, KEY_F24,
|
|
KEY_NUMLOCK, KEY_SCROLL, KEY_LSHIFT, KEY_RSHIFT, KEY_LCONTROL, KEY_RCONTROL, KEY_LMENU, KEY_RMENU, KEY_BROWSER_BACK,
|
|
KEY_BROWSER_FORWARD, KEY_BROWSER_REFRESH, KEY_BROWSER_STOP, KEY_BROWSER_SEARCH, KEY_BROWSER_FAVORITES,
|
|
KEY_BROWSER_HOME, KEY_VOLUME_MUTE, KEY_VOLUME_DOWN, KEY_VOLUME_UP, KEY_MEDIA_NEXT_TRACK, KEY_MEDIA_PREV_TRACK,
|
|
KEY_MEDIA_STOP, KEY_MEDIA_PLAY_PAUSE, KEY_LAUNCH_MAIL, KEY_LAUNCH_MEDIA_SELECT, KEY_LAUNCH_APP1, KEY_LAUNCH_APP2,
|
|
KEY_OEM_1, KEY_OEM_PLUS, KEY_OEM_COMMA, KEY_OEM_MINUS, KEY_OEM_PERIOD, KEY_OEM_2, KEY_OEM_3, KEY_OEM_4, KEY_OEM_5,
|
|
KEY_OEM_6, KEY_OEM_7, KEY_OEM_8, KEY_OEM_102, KEY_PROCESSKEY, KEY_ATTN, KEY_CRSEL, KEY_EXSEL, KEY_EREOF, KEY_PLAY,
|
|
KEY_ZOOM, KEY_NONAME, KEY_PA1, KEY_OEM_CLEAR,
|
|
KEYCOUNT, KEYPADDING = 0xFFFFFFFF
|
|
};
|
|
|
|
|
|
int keystate( enum keycode_t key );
|
|
|
|
#define KEY_MODIFIER_RELEASED 0x80000000
|
|
enum keycode_t* readkeys( void );
|
|
char const* readchars( void );
|
|
|
|
int mousex( void );
|
|
int mousey( void );
|
|
int mouserelx( void );
|
|
int mouserely( void );
|
|
|
|
#endif /* dos_h */
|
|
|
|
|
|
#ifdef DOS_IMPLEMENTATION
|
|
|
|
#ifndef NO_MAIN_DEF
|
|
#ifdef main
|
|
#undef main
|
|
#endif
|
|
#endif
|
|
|
|
#define _CRT_NONSTDC_NO_DEPRECATE
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "libs/app.h"
|
|
#include "libs/crtemu_pc.h"
|
|
#include "libs/dr_wav.h"
|
|
#include "libs/frametimer.h"
|
|
#include "libs/mus.h"
|
|
#include "libs/opblib.h"
|
|
#include "libs/opl.h"
|
|
#include "libs/pixelfont.h"
|
|
#include "libs/tsf.h"
|
|
|
|
#define JAR_MOD_IMPLEMENTATION
|
|
#pragma warning( push )
|
|
#pragma warning( disable: 4100 )
|
|
#pragma warning( disable: 4242 )
|
|
#pragma warning( disable: 4244 )
|
|
#include "libs/jar_mod.h"
|
|
#pragma warning( pop )
|
|
|
|
#ifdef _WIN32
|
|
#pragma warning( push )
|
|
#pragma warning( disable: 4201 )
|
|
#endif
|
|
#include "libs/tml.h"
|
|
#ifdef _WIN32
|
|
#pragma warning( pop)
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#pragma warning( push )
|
|
#pragma warning( disable: 4024 )
|
|
#pragma warning( disable: 4047 )
|
|
#pragma warning( disable: 4242 )
|
|
#pragma warning( disable: 4244 )
|
|
#pragma warning( disable: 4701 )
|
|
#endif
|
|
#include "libs/gif_load.h"
|
|
#ifdef _WIN32
|
|
#pragma warning( pop )
|
|
#endif
|
|
|
|
bool app_has_focus( app_t* app );
|
|
|
|
#include "libs/awe32rom.h"
|
|
#include "libs/crtframe.h"
|
|
#include "libs/thread.h"
|
|
#ifdef __wasm__
|
|
#define WA_CORO_IMPLEMENT_NANOSLEEP
|
|
#include <wajic_coro.h>
|
|
#endif
|
|
|
|
static uint32_t default_palette[ 256 ] = {
|
|
0x000000, 0xaa0000, 0x00aa00, 0xaaaa00, 0x0000aa, 0xaa00aa, 0x0055aa, 0xaaaaaa, 0x555555, 0xff5555, 0x55ff55, 0xffff55, 0x5555ff, 0xff55ff, 0x55ffff, 0xffffff, 0x000000, 0x141414, 0x202020, 0x2c2c2c, 0x383838, 0x454545, 0x515151, 0x616161, 0x717171, 0x828282, 0x929292, 0xa2a2a2, 0xb6b6b6, 0xcbcbcb, 0xe3e3e3, 0xffffff, 0xff0000, 0xff0041, 0xff007d, 0xff00be, 0xff00ff, 0xbe00ff, 0x7d00ff, 0x4100ff, 0x0000ff, 0x0041ff, 0x007dff, 0x00beff, 0x00ffff, 0x00ffbe, 0x00ff7d, 0x00ff41, 0x00ff00, 0x41ff00, 0x7dff00, 0xbeff00, 0xffff00, 0xffbe00, 0xff7d00, 0xff4100, 0xff7d7d, 0xff7d9e, 0xff7dbe, 0xff7ddf, 0xff7dff, 0xdf7dff, 0xbe7dff, 0x9e7dff,
|
|
0x7f7dff, 0x7d9eff, 0x7dbeff, 0x7ddfff, 0x7dffff, 0x7dffdf, 0x7dffbe, 0x7dff9e, 0x7dff7d, 0x9eff7d, 0xbeff7d, 0xdfff7d, 0xffff7d, 0xffdf7d, 0xffbe7d, 0xff9e7d, 0xffb6b6, 0xffb6c7, 0xffb6db, 0xffb6eb, 0xffb6ff, 0xebb6ff, 0xdbb6ff, 0xc7b6ff, 0xb6b6ff, 0xb6c7ff, 0xb6dbff, 0xb6ebff, 0xb6ffff, 0xb6ffeb, 0xb6ffdb, 0xb6ffc7, 0xb6ffb6, 0xc7ffb6, 0xdbffb6, 0xebffb6, 0xffffb6, 0xffebb6, 0xffdbb6, 0xffc7b6, 0x710000, 0x71001c, 0x710038, 0x710055, 0x710071, 0x550071, 0x380071, 0x1c0071, 0x000071, 0x001c71, 0x003871, 0x005571, 0x007171, 0x007155, 0x007138, 0x00711c, 0x007100, 0x1c7100, 0x387100, 0x557100, 0x717100, 0x715500, 0x713800, 0x711c00,
|
|
0x713838, 0x713845, 0x713855, 0x713861, 0x713871, 0x613871, 0x553871, 0x453871, 0x383871, 0x384571, 0x385571, 0x386171, 0x387171, 0x387161, 0x387155, 0x387145, 0x387138, 0x457138, 0x557138, 0x617138, 0x717138, 0x716138, 0x715538, 0x714538, 0x715151, 0x715159, 0x715161, 0x715169, 0x715171, 0x695171, 0x615171, 0x595171, 0x515171, 0x515971, 0x516171, 0x516971, 0x517171, 0x517169, 0x517161, 0x517159, 0x517151, 0x597151, 0x617151, 0x697151, 0x717151, 0x716951, 0x716151, 0x715951, 0x410000, 0x410010, 0x410020, 0x410030, 0x410041, 0x300041, 0x200041, 0x100041, 0x000041, 0x001041, 0x002041, 0x003041, 0x004141, 0x004130, 0x004120, 0x004110,
|
|
0x004100, 0x104100, 0x204100, 0x304100, 0x414100, 0x413000, 0x412000, 0x411000, 0x412020, 0x412028, 0x412030, 0x412038, 0x412041, 0x382041, 0x302041, 0x282041, 0x202041, 0x202841, 0x203041, 0x203841, 0x204141, 0x204138, 0x204130, 0x204128, 0x204120, 0x284120, 0x304120, 0x384120, 0x414120, 0x413820, 0x413020, 0x412820, 0x412c2c, 0x412c30, 0x412c34, 0x412c3c, 0x412c41, 0x3c2c41, 0x342c41, 0x302c41, 0x2c2c41, 0x2c3041, 0x2c3441, 0x2c3c41, 0x2c4141, 0x2c413c, 0x2c4134, 0x2c4130, 0x2c412c, 0x30412c, 0x34412c, 0x3c412c, 0x41412c, 0x413c2c, 0x41342c, 0x41302c, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
|
|
};
|
|
|
|
|
|
static uint32_t font8x8[] = { \
|
|
8, 8, 6,
|
|
0x367e7e00,0x00081c08,0xf0ff00ff,0x18fefc3c,0x66184001,0x18007cfe,0x00001818,0x00000000,0x7fff8100,0x00083e1c,0xe0c33cff,0xdbc6cc66,0x663c7007,0x3c00c6db,0x0c18183c,0xff182400,0x7fdba500,0x181c1c3e,0xf09966e7,0x3cfefc66,0x667e7c1f,0x7e001cdb,0x0630187e,0xff3c6603,0x7fff8100,0x3c3e7f7f,0xbebd42c3,0xe7c60c66,0x66187f7f,0x180036de,0x7f7f1818,0x7e7eff03,0x3ec3bd00,0x3c7f7f3e,0x33bd42c3,0xe7c60c3c,0x66187c1f,0x7e7e36d8,0x06307e18,0x3cff6603,0x1ce79900,0x183e6b1c,0x339966e7,0x3ce60e18,0x007e7007,0x3c7e1cd8,0x0c183c18,0x18ff247f,0x08ff8100,0x00080808,0x33c33cff,0xdb670f7e,0x663c4001,0x187e33d8,0x00001818,0x00000000,0x007e7e00,0x001c1c00,0x1eff00ff,0x18030718,0x00180000,0xff001e00,0x00000000,0x00000000,0x36360c00,0x061c000c,0x00000618,0x60000000,0x1e1e0c3e,0x3f1c3f38,0x00001e1e,0x1e060018,0x36361e00,0x0636633e,0x0c660c0c,0x30000000,0x33330e63,0x3306033c,0x0c0c3333,0x330c000c,0x7f361e00,0x031c3303,0x0c3c1806,0x18000000,0x30300c73,0x30031f36,0x0c0c3333,0x30183f06,0x36000c00,0x006e181e,0x3fff1806,0x0c003f00,0x1c1c0c7b,0x181f3033,0x00003e1e,0x18300003,0x7f000c00,0x003b0c30,0x0c3c1806,0x06000000,0x30060c6f,0x0c33307f,0x00003033,0x0c180006,0x36000000,0x0033661f,0x0c660c0c,0x030c000c,0x33330c67,0x0c333330,0x0c0c1833,0x000c3f0c,0x36000c00,0x006e630c,0x00000618,0x010c000c,0x1e3f3f3e,0x0c1e1e78,0x0c0c0e1e,0x0c060018,0x00000000,0x00000000,0x00000000,0x00000006,0x00000000,0x00000000,0x06000000,0x00000000, \
|
|
0x3c3f0c3e,0x3c7f7f1f,0x67781e33,0x1c63630f,0x1e3f1e3f,0x6333333f,0x1e7f3363,0x00081e03,0x66661e63,0x66464636,0x66300c33,0x36677706,0x33663366,0x6333332d,0x06633363,0x001c1806,0x0366337b,0x03161666,0x36300c33,0x636f7f06,0x06663366,0x6333330c,0x06313336,0x0036180c,0x033e337b,0x031e1e66,0x1e300c3f,0x637b7f06,0x0c3e333e,0x6b33330c,0x06181e1c,0x00631818,0x03663f7b,0x73161666,0x36330c33,0x63736b46,0x18363b06,0x7f33330c,0x064c0c1c,0x00001830,0x66663303,0x66064636,0x66330c33,0x36636366,0x33661e06,0x771e330c,0x06660c36,0x00001860,0x3c3f331e,0x7c0f7f1f,0x671e1e33,0x1c63637f,0x1e67380f,0x630c3f1e,0x1e7f1e63,0x00001e40,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xff000000,0x0007000c,0x001c0038,0x07300c07,0x0000000e,0x00000000,0x00000008,0x38000000,0x006e0718,0x0006000c,0x00360030,0x06000006,0x0000000c,0x00000000,0x0000000c,0x0c000000,0x083b0c18,0x1e061e18,0x6e061e30,0x66300e36,0x1e1f330c,0x3e3b6e3b,0x6333333e,0x0c3f3363,0x1c000c18,0x333e3000,0x330f333e,0x36300c6e,0x33337f0c,0x036e3366,0x6b33330c,0x07193336,0x36003800,0x03663e00,0x33063f33,0x1e300c66,0x33337f0c,0x1e663366,0x7f33330c,0x0c0c331c,0x63000c18,0x33663300,0x3e060333,0x36330c66,0x33336b0c,0x30063e3e,0x7f1e332c,0x0c263e36,0x63000c18,0x1e3b6e00,0x300f1e6e,0x67331e67,0x1e33631e,0x1f0f3006,0x360c6e18,0x383f3063,0x7f000718,0x00000000,0x1f000000,0x001e0000,0x00000000,0x0000780f,0x00000000,0x00001f00,0x00000000, \
|
|
0x7e38001e,0x000c0733,0x3307337e,0x0c63073e,0x1e7c0038,0x001e0000,0x1833c300,0x701f331c,0xc3003333,0x000c0000,0x000000c3,0x0c1c0063,0x33360000,0x07330733,0x18001833,0xd8333336,0x3c1e0003,0x1e1e1e1e,0x0e1e1e3c,0x00360e1c,0x0033fe3f,0x00000000,0x7e333c00,0x18331e26,0x60333333,0x03303030,0x0c333366,0x1e630c18,0x1e7f3006,0x33331e1e,0x03336633,0x3c5f3f0f,0x7c3f331e,0x033e3e3e,0x0c3f3f7e,0x337f0c18,0x3333fe1e,0x33333333,0x03336633,0x18630c06,0x66033318,0x1e333333,0x0c030306,0x3f630c18,0x33333306,0x33333333,0x7e333c3e,0x18f33f67,0xfc1e7e30,0x307e7e7e,0x1e1e1e3c,0x33631e3c,0x1e73fe3f,0x7e7e1e1e,0x181e1830,0x1b630c3f,0x0000001e,0x1c000000,0x00000000,0x00000000,0x00000000,0x00000000,0x1800001f,0x0ee30c00,0x00001c38,0x1c3c3f00,0xc300000c,0x000018c3,0x18dbaa44,0x006c1818,0x006c6c00,0x00186c6c,0x38380000,0x3636001f,0x63000000,0x33cc1863,0x18ee5511,0x006c1818,0x006c6c00,0x00186c6c,0x00000e1e,0x36363300,0x3300000c,0x66660033,0x18dbaa44,0x006c1f18,0x7f6c6f1f,0x001f6c6f,0x331e0c30,0x1c7c371f,0x7b3f3f06,0xcc3318db,0x18775511,0x006c1818,0x606c6018,0x00186c60,0x33330c3e,0x00003f33,0xcc300303,0x666618ec,0x18dbaa44,0x7f6f1f1f,0x6f6c6f1f,0x1f1f7f7f,0x33330c33,0x3e7e3b33,0x66300333,0x33cc18f6,0x18ee5511,0x6c6c1818,0x6c6c6c18,0x18000000,0x7e1e1e7e,0x00003333,0x3300001e,0x000018f3,0x18dbaa44,0x6c6c1818,0x6c6c6c18,0x18000000,0x00000000,0x00000000,0xf0000000,0x000000c0,0x18775511,0x6c6c1818,0x6c6c6c18,0x18000000, \
|
|
0x18001818,0x6c181800,0x006c006c,0x186c006c,0x6c00006c,0x6c000018,0xff001818,0xfff00f00,0x18001818,0x6c181800,0x006c006c,0x186c006c,0x6c00006c,0x6c000018,0xff001818,0xfff00f00,0x18001818,0x6cf81800,0xffeffcec,0xffefffec,0x6c00ff6c,0x6c00f8f8,0xff0018ff,0xfff00f00,0x18001818,0x6c181800,0x00000c0c,0x0000000c,0x6c00006c,0x6c001818,0xff001818,0xfff00f00,0xf8fffff8,0xecf8ffff,0xefffecfc,0xffefffec,0xfcffffff,0xfffcf8f8,0xfff81fff,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x00000000,0x0000003f,0x381c1c3f,0x1e1c6000,0x18060c00,0x000c1870,0xf000001c,0x00000e1e,0x7f3f1e00,0x6e660033,0x0c36360c,0x33063000,0x0c0c0c3f,0x6e0c18d8,0x30000036,0x00001836,0x3633336e,0x3b667e06,0x1863631e,0x33037e7e,0x06183f00,0x3b0018d8,0x30000036,0x003c0c36,0x36031f3b,0x18661b0c,0x3e637f33,0x331fdbdb,0x0c0c0c3f,0x003f1818,0x3000181c,0x003c0636,0x36033313,0x18661b06,0x33366333,0x3303dbdb,0x18060c00,0x6e001818,0x37181800,0x003c1e36,0x36031f3b,0x183e1b33,0x3336361e,0x33067e7e,0x0000003f,0x3b0c1b18,0x36000000,0x003c0000,0x3603036e,0x18060e3f,0x1e771c0c,0x331c0600,0x3f3f3f00,0x000c1b18,0x3c000000,0x00000000,0x00000300,0x00030000,0x0000003f,0x00000300,0x00000000,0x00000e18,0x38000000,0x00000000, \
|
|
};
|
|
|
|
|
|
static uint32_t font8x16[] = { \
|
|
8, 16, 11,
|
|
0x00000000,0x00000000,0x00ff00ff,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00ff00ff,0x00000000,0x00004001,0x00003e00,0x00000000,0x00000000,0x007e7e00,0x00000000,0x78ff00ff,0x00fefc3c,0x66186003,0x180063fe,0x00001818,0x00000000,0x00ff8100,0x00181800,0x70ff00ff,0x18c6cc66,0x663c7007,0x3c0006db,0x0000183c,0x00000000,0x36dba500,0x003c3c08,0x58ff00ff,0x18fefc66,0x667e780f,0x7e001cdb,0x0000187e,0x7f080000,0x7fff8100,0x007e3c1c,0x4cc33cff,0xdbc60c66,0x66187c1f,0x180036db,0x0c181818,0x7f1c1400,0x7fff8100,0x18ffe73e,0x1e9966e7,0x3cc60c66,0x66187f7f,0x180063de,0x06301818,0x3e1c3603,0x7fc3bd00,0x3cffe77f,0x33bd42c3,0xe7c60c3c,0x66187c1f,0x180063d8,0x7f7f1818,0x3e3e7f03,0x7fe79900,0x3c7ee73e,0x33bd42c3,0x3cc60c18,0x667e780f,0x7e7f36d8,0x06301818,0x1c3e3603,0x3eff8100,0x1818181c,0x339966e7,0xdbe60e7e,0x003c7007,0x3c7f1cd8,0x0c187e18,0x1c7f147f,0x1cff8100,0x00181808,0x33c33cff,0x18e70f18,0x66186003,0x187f30d8,0x00003c18,0x087f0000,0x087e7e00,0x003c3c00,0x1eff00ff,0x18670718,0x66004001,0x7e7f63d8,0x00001818,0x00000000,0x00000000,0x00000000,0x00ff00ff,0x00030000,0x00000000,0x00003e00,0x00000000,0x00000000,0x00000000,0x00000000,0x00ff00ff,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00ff00ff,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00ff00ff,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, \
|
|
0x00000000,0x00000018,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00660000,0x0c000018,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00661800,0x0c1c003e,0x00000c30,0x00000000,0x3e3e181c,0x7f1c7f30,0x00003e3e,0x3e000000,0x36663c00,0x0c360063,0x00001818,0x00000000,0x63631c36,0x63060338,0x00006363,0x63060060,0x36243c00,0x06364343,0x0000300c,0x40000000,0x60601e63,0x6003033c,0x18186363,0x630c0030,0x7f003c00,0x001c6303,0x1866300c,0x60000000,0x60301863,0x60030336,0x18186363,0x30187e18,0x36001800,0x006e303e,0x183c300c,0x30000000,0x3c18186b,0x303f3f33,0x00007e3e,0x1830000c,0x36001800,0x003b1860,0x7eff300c,0x18007f00,0x600c186b,0x1863607f,0x00006063,0x18600006,0x36001800,0x00330c60,0x183c300c,0x0c000000,0x60061863,0x0c636030,0x00006063,0x18307e0c,0x7f000000,0x00330661,0x1866300c,0x06000018,0x60031863,0x0c636030,0x18186063,0x00180018,0x36001800,0x00336363,0x00001818,0x03180018,0x63631836,0x0c636330,0x18183063,0x180c0030,0x36001800,0x006e613e,0x00000c30,0x01180018,0x3e7f7e1c,0x0c3e3e78,0x0c001e3e,0x18060060,0x00000000,0x00000018,0x00000000,0x0000000c,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000018,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, \
|
|
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00080000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x001c0000,0x3c3f0800,0x3c7f7f1f,0x67783c63,0x3e63630f,0x3e3f3e3f,0x6363637e,0x3c7f6663,0x00363c00,0x66661c3e,0x66666636,0x66301863,0x63677706,0x63666366,0x6363637e,0x0c636663,0x00633001,0x43663663,0x43464666,0x66301863,0x636f7f06,0x63666366,0x6363635a,0x0c616636,0x00003003,0x03666363,0x03161666,0x36301863,0x637f7f06,0x06666366,0x63636318,0x0c30663e,0x00003007,0x033e637b,0x031e1e66,0x1e30187f,0x637b6b06,0x1c3e633e,0x6b636318,0x0c183c1c,0x0000300e,0x03667f7b,0x7b161666,0x1e301863,0x63736306,0x30366306,0x6b636318,0x0c0c181c,0x0000301c,0x0366637b,0x63060666,0x36331863,0x63636306,0x60666306,0x6b636318,0x0c06183e,0x00003038,0x4366633b,0x63064666,0x66331863,0x63636346,0x63666b06,0x7f366318,0x0c431836,0x00003070,0x66666303,0x66066636,0x66331863,0x63636366,0x63667b06,0x771c6318,0x0c631863,0x00003060,0x3c3f633e,0x5c0f7f1f,0x671e3c63,0x3e63637f,0x3e673e0f,0x36083e3c,0x3c7f3c63,0x00003c40,0x00000000,0x00000000,0x00000000,0x00000000,0x00003000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00007000,0x00000000,0x00000000,0xff000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, \
|
|
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x0000000c,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x006e0000,0x00070018,0x00380038,0x07601807,0x0000001c,0x00000000,0x00000008,0x70000000,0x003b0e18,0x00060030,0x006c0030,0x06601806,0x00000018,0x00000000,0x0000000c,0x18000000,0x00001818,0x00060000,0x004c0030,0x06000006,0x00000018,0x00000000,0x0000000c,0x18000000,0x08001818,0x3e1e1e00,0x6e0c3e3c,0x66701c36,0x3e3b3718,0x3e3b6e3b,0x6363333f,0x187f6363,0x1c001818,0x63363000,0x331e6336,0x3660186e,0x63667f18,0x636e3366,0x6363330c,0x0e336336,0x36007018,0x03663e00,0x330c7f33,0x1e601866,0x63666b18,0x06663366,0x6b63330c,0x1818631c,0x63001818,0x03663300,0x330c0333,0x1e601866,0x63666b18,0x1c063366,0x6b63330c,0x180c631c,0x63001818,0x03663300,0x330c0333,0x36601866,0x63666b18,0x30063366,0x6b63330c,0x1806631c,0x63001818,0x63663300,0x330c6333,0x66601866,0x63666b18,0x63063366,0x7f36336c,0x18636336,0x7f001818,0x3e3e6e00,0x3e1e3e6e,0x67603c67,0x3e66633c,0x3e0f3e3e,0x361c6e38,0x707f7e63,0x00000e18,0x00000000,0x30000000,0x00660000,0x00000000,0x00003006,0x00000000,0x00006000,0x00000000,0x00000000,0x33000000,0x00660000,0x00000000,0x00003006,0x00000000,0x00003000,0x00000000,0x00000000,0x1e000000,0x003c0000,0x00000000,0x0000780f,0x00000000,0x00001f00,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, \
|
|
0x00000000,0x00000000,0x00000000,0x1c000000,0x00000030,0x00000000,0x00000000,0x00000000,0x08300000,0x001c0600,0x00060008,0x36630618,0x08000018,0x060c0600,0x18636300,0x701f001c,0x1c18333c,0x00360c33,0x660c631c,0x1c000c3c,0x1c7c0000,0x0c1e0c63,0x18000063,0xd8336636,0x360c0066,0x001c1800,0x00180036,0x08081866,0x3636007f,0x18331800,0x3e633e00,0x18336626,0x00000043,0x00000000,0x00000000,0x1c1c0000,0x00330066,0x00000000,0x63636300,0x181f3c06,0x1e3e3303,0x3e1e1e1e,0x1c3e3e3e,0x36361c1c,0x3e333746,0x33333e3e,0x03636363,0x1823180f,0x30633303,0x63303030,0x18636363,0x63631818,0x637f6c16,0x33336363,0x03636363,0x7e337e06,0x3e7f3303,0x033e3e3e,0x187f7f7f,0x7f631818,0x63336c1e,0x33336363,0x03636363,0x187b1806,0x33033303,0x03333333,0x18030303,0x637f1818,0x63337e16,0x33336363,0x63636363,0x18337e06,0x33033343,0x03333333,0x18030303,0x63631818,0x63331b46,0x33336363,0x3e636363,0x18331806,0x33633366,0x63333333,0x18636363,0x63631818,0x63331b66,0x33336363,0x18636363,0x1b331867,0x6e3e6e3c,0x3e6e6e6e,0x3c3e3e3e,0x63633c3c,0x3e73767f,0x6e6e3e3e,0x183e3e7e,0x0e63183f,0x00000018,0x18000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000060,0x00000000,0x0000000e,0x0e000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000030,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x0000001e,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, \
|
|
0x00000000,0x00006e00,0x00000000,0x00000000,0x18bbaa88,0x006c1818,0x006c6c00,0x00186c6c,0x18183018,0x00003b00,0x06000000,0x00000006,0x18ee5522,0x006c1818,0x006c6c00,0x00186c6c,0x0c0c180c,0x1c3c006e,0x0700000c,0x00001807,0x18bbaa88,0x006c1818,0x006c6c00,0x00186c6c,0x06060c06,0x3636633b,0x4600000c,0x00001846,0x18ee5522,0x006c1818,0x006c6c00,0x00186c6c,0x00000000,0x36366700,0x66000000,0x00000066,0x18bbaa88,0x006c1818,0x006c6c00,0x00186c6c,0x333e1c1e,0x1c7c6f3b,0x3600000c,0x1b6c1836,0x18ee5522,0x006c1f18,0x7f6c6f1f,0x001f6c6f,0x33631830,0x00007f66,0x187f7f0c,0x36361818,0x18bbaa88,0x006c1818,0x606c6018,0x00186c60,0x3363183e,0x3e7e7b66,0x0c600306,0x6c1b180c,0x18ee5522,0x7f6f1f1f,0x6f6c6f1f,0x1f1f7f7f,0x33631833,0x00007366,0x06600303,0x36363c66,0x18bbaa88,0x6c6c1818,0x6c6c6c18,0x18000000,0x33631833,0x00006366,0x3b600363,0x1b6c3c73,0x18ee5522,0x6c6c1818,0x6c6c6c18,0x18000000,0x33631833,0x00006366,0x61600363,0x00003c59,0x18bbaa88,0x6c6c1818,0x6c6c6c18,0x18000000,0x6e3e3c6e,0x00006366,0x3000003e,0x000018fc,0x18ee5522,0x6c6c1818,0x6c6c6c18,0x18000000,0x00000000,0x00000000,0x18000000,0x00000060,0x18bbaa88,0x6c6c1818,0x6c6c6c18,0x18000000,0x00000000,0x00000000,0x7c000000,0x00000060,0x18ee5522,0x6c6c1818,0x6c6c6c18,0x18000000,0x00000000,0x00000000,0x00000000,0x00000000,0x18bbaa88,0x6c6c1818,0x6c6c6c18,0x18000000,0x00000000,0x00000000,0x00000000,0x00000000,0x18ee5522,0x6c6c1818,0x6c6c6c18,0x18000000, \
|
|
0x18001818,0x6c181800,0x006c006c,0x186c006c,0x6c00006c,0x6c000018,0xff001818,0xfff00f00,0x18001818,0x6c181800,0x006c006c,0x186c006c,0x6c00006c,0x6c000018,0xff001818,0xfff00f00,0x18001818,0x6c181800,0x006c006c,0x186c006c,0x6c00006c,0x6c000018,0xff001818,0xfff00f00,0x18001818,0x6c181800,0x006c006c,0x186c006c,0x6c00006c,0x6c000018,0xff001818,0xfff00f00,0x18001818,0x6c181800,0x006c006c,0x186c006c,0x6c00006c,0x6c000018,0xff001818,0xfff00f00,0x18001818,0x6cf81800,0xffeffcec,0xffefffec,0x6c00ff6c,0x6c00f8f8,0xff0018ff,0xfff00f00,0x18001818,0x6c181800,0x00000c0c,0x0000000c,0x6c00006c,0x6c001818,0xff001818,0xfff00f00,0xf8fffff8,0xecf8ffff,0xefffecfc,0xffefffec,0xfcffffff,0xfffcf8f8,0xfff81fff,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff,0x18180000,0x6c181800,0x6c006c00,0x006c006c,0x006c1800,0x6c6c1800,0xff180018,0x00f00fff, \
|
|
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00001800,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00001800,0xf000001c,0x00003c36,0x007f1e00,0x0000007f,0x781c1c7e,0x00380000,0x00000000,0x00001870,0x30000036,0x0000666c,0x00633300,0x00000063,0x0c363618,0x3e0cc000,0x300c0000,0x000018d8,0x30000036,0x0000306c,0x00633300,0x6e000006,0x1863633c,0x63066000,0x1818187f,0x000018d8,0x3000001c,0x007e186c,0x7f03336e,0x3b667e0c,0x30636366,0x63067e7e,0x0c301800,0x6e181818,0x30000000,0x007e4c6c,0x36031b3b,0x18661b18,0x7c637f66,0x633edbdb,0x06607e00,0x3b001818,0x30000000,0x007e7e6c,0x3603331b,0x18661b18,0x66366366,0x6306dbdb,0x0c30187f,0x007e1818,0x37181800,0x007e0000,0x3603631b,0x18661b0c,0x66366366,0x6306cfdb,0x18181800,0x6e001818,0x36001800,0x007e0000,0x3603631b,0x18661b06,0x6636633c,0x63067e7e,0x300c0000,0x3b181b18,0x36000000,0x007e0000,0x3603633b,0x18661b63,0x66363618,0x630c0600,0x0000007f,0x00001b18,0x3c000000,0x007e0000,0x3603336e,0x183e0e7f,0x3c771c7e,0x63380300,0x7e7e7e00,0x00001b18,0x38000000,0x00000000,0x00000000,0x00060000,0x00000000,0x00000000,0x00000000,0x00000e18,0x00000000,0x00000000,0x00000000,0x00060000,0x00000000,0x00000000,0x00000000,0x00000018,0x00000000,0x00000000,0x00000000,0x00030000,0x00000000,0x00000000,0x00000000,0x00000018,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000018,0x00000000,0x00000000, \
|
|
};
|
|
|
|
static uint32_t font9x16[] = {
|
|
9, 16, 11,
|
|
0x00000000,0x00000000,0xfc00ff00,0x00000003,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xfc00ff00,0x00000003,0x80010000,0xc0000000,0x00000007,0x00000000,0x01f8fc00,0x00000000,0xfc00ff00,0x9f83c3c3,0xc003003f,0x6fe33060,0x180c000c,0x00000030,0x03fd0200,0x06030000,0xfc00ff00,0x99866383,0xe0070c31,0xcdb330f0,0x3c1e0000,0x00000030,0xb36d4a00,0x0f078081,0xfc00ff00,0x9f8662c3,0xf00f0c3f,0x8db331f8,0x7e3f0003,0x00000030,0xfbfd0200,0x1f8781c3,0x0c78ff00,0x81866263,0xf81f6db1,0xcdb33060,0x180c0006,0x00606030,0xfbfd0200,0x3fdce3e3,0x64cce70c,0x818660f2,0xfe7f1e31,0x6de33060,0x180c000c,0x0030c030,0xfb0d7a00,0x3fdce7f3,0xf484c31e,0x8183c19a,0xf81f73b1,0x6d833060,0x180c000c,0x03f9fc30,0xfb9d3200,0x1f9ce3e3,0xf484c31e,0x8181819a,0xf00f1e31,0xcd8331f8,0x183f1fc6,0x0030c030,0xf3fd0200,0x060301c1,0x64cce70c,0x81c7e19a,0xe0076db9,0x8d8000f0,0x181e1fc3,0x006060fc,0xe3fd0200,0x06030080,0x0c78ff00,0xc1e1819b,0xc0030c39,0x0d833060,0x180c1fc6,0x00000078,0x41f8fc00,0x0f078000,0xfc00ff00,0xc0e180f3,0x80010c19,0x6d833000,0x183f1fcc,0x00000030,0x00000000,0x00000000,0xfc00ff00,0xc0000003,0x00000000,0xc0000000,0x00000007,0x00000000,0x00000000,0x00000000,0xfc00ff00,0x00000003,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xfc00ff00,0x00000003,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xfc00ff00,0x00000003,0x00000000,0x00000000,0x00000000,0x00000000,
|
|
0x00000000,0x00000000,0x00001800,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x19800000,0x00001800,0x00000060,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x19830000,0x70003e00,0x01830060,0x00000000,0x03c00000,0x301f0f83,0x03f870fe,0x00000000,0x19878000,0xd800631b,0x03018060,0x00000000,0x86600000,0x383198c3,0x03181806,0xf8200000,0x09078003,0xd886431b,0x0600c030,0x00000000,0xcc320000,0x3c301803,0x03000c06,0xf8704800,0x80078003,0x70c6033f,0x8600c000,0x00000c19,0x0c330000,0x36300c03,0x03000c06,0xf070cc03,0x00030001,0xb8603e1b,0x0600c001,0x00000c0f,0x0db18000,0x331e0603,0x0180fc7e,0xf0f9fe03,0x00030001,0xec30601b,0xc600c000,0xfe003f3f,0x0db0c000,0x7f300303,0x00c18cc0,0xe0f8cc03,0x00030000,0xcc18601b,0x0600c000,0x00000c0f,0x0c306000,0x30300183,0x00618cc0,0xe1fc487f,0x80000000,0xcc0c613f,0x8600c000,0x00180c19,0x0c303000,0x303000c3,0x00618cc0,0x41fc0000,0x00030000,0xccc6631b,0x03018000,0x00180000,0x06601860,0x303198c3,0x00618cc6,0x00000000,0x00030000,0xb8c23e1b,0x01830001,0x00180000,0xc3c00860,0x781f1fcf,0x0060f87c,0x00000000,0x00000000,0x00001800,0x00000000,0x000c0000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00001800,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
|
|
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00007c3e,0x00000000,0xfc10001f,0xcfe1f1e0,0x78631e1f,0x60f339e0,0x3f1f18d8,0x01f0fc7c,0x0000c663,0x81800600,0x98383e31,0x8cc36331,0x30633319,0xe06330c0,0x663199dc,0x031998c6,0xc060c663,0x83000300,0x986c6331,0x88c66219,0x30632191,0xe06330c0,0x66319bdf,0x031998c6,0xc060c663,0x060fc180,0x98c66318,0x82c66019,0x30630185,0xe061b0c0,0x66319fdf,0x003198c6,0x0000fc3e,0x0c0000c0,0xf8c67b0c,0x83c66018,0x307f0187,0x6060f0c0,0x3e319edb,0x00e0f8c6,0x0000c063,0x18000060,0x98fe7b0c,0x82c66019,0x30633d85,0x6060f0c0,0x06319cd8,0x0180d8c6,0x0000c063,0x0c0fc0c0,0x98c67b0c,0x80c66019,0x30633181,0x6061b0cc,0x063198d8,0x030198c6,0xc060c063,0x06000180,0x98c63b00,0x88c66219,0x30633181,0x646330cc,0x063198d8,0x031998d6,0xc0606063,0x03000300,0x98c6030c,0x8cc36331,0x30633301,0x666330cc,0x063198d8,0x031998f6,0x60003c3e,0x01800600,0xfcc63e0c,0xcfe1f1e0,0x78632e03,0x67f33878,0x0f1f18d8,0x01f19c7c,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000060,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x000000e0,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
|
|
0x00000000,0x00000000,0x20000000,0x0000c000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x70000000,0x0000c000,0x00000000,0x00000000,0x00000000,0x00000000,0x1b0cc6ff,0x3fd86c36,0xd878001e,0xc0018000,0x00380001,0x00700070,0x1c039803,0x00000000,0x1b0cc6db,0x30d86c36,0x8c600106,0x80000001,0x00300001,0x006000d8,0x18031803,0x00000000,0x1b0cc699,0x18586666,0x00600306,0x80000000,0x00300001,0x00600098,0x18030000,0x00000000,0x1b0cc618,0x0c0cc3c6,0x00600706,0x83c00000,0x7c3c1f07,0x83637018,0x18331c03,0x01f0ecce,0x1b0cc618,0x06078186,0x00600e06,0x86000000,0xc636318d,0x06e1983c,0x181b1803,0x031999fe,0xdb0cc618,0x03030186,0x00601c06,0x87c00000,0xfe330199,0x06619818,0x180f1803,0x031999b6,0xdb0cc618,0x018303c6,0x00603806,0x86600000,0x06330199,0x06619818,0x180f1803,0x031999b6,0xf998c618,0x20c30667,0x00607006,0x86600000,0x06330199,0x06619818,0x181b1803,0x031999b6,0x30f0c618,0x30c30c33,0x00606006,0x86600000,0xc6333199,0x06619818,0x18331803,0x031999b6,0x30607c3c,0x3fc78c33,0x0078401e,0x8dc00000,0x7c6e1f0f,0x8671f03c,0x3c339807,0x01f199b6,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00018000,0x00001980,0x00000000,0x00000000,0x00000000,0x00000000,0x000007f8,0x00000000,0x00019800,0x00001980,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x0000f000,0x00000f00,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
|
|
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0xc00040c0,0x08000700,0x00001800,0x00000000,0x00000080,0x00000000,0x81c18380,0x663c001b,0x8330e060,0x1c000d81,0x033030c6,0x00000000,0x000000c0,0x00000000,0xc30180c0,0x0066000e,0x0001b030,0x36000703,0x00006000,0x00000000,0x000000c0,0x00000000,0x030180c0,0x00430400,0x00000000,0x001e0000,0x00000000,0xf0ecdc3b,0xb0c663f1,0xfcc6c361,0x030180c1,0x66030e00,0xc1e0f0f8,0x3e330783,0x00e0f87c,0x19b86666,0xb0c660c3,0xccc66661,0x0e000070,0x66031b00,0x0301818c,0x63030c06,0x00c18cc6,0x31986666,0xb0c660c0,0x60c63c61,0x030180c0,0x66033180,0xc3e1f1fc,0x7f030f87,0x00c1fcfe,0xe0186666,0xb0c660c0,0x30c6186d,0x030180c0,0x66433180,0x6331980c,0x03330cc6,0x00c00c06,0x80186666,0x998660c1,0x18c63c6d,0x030180c0,0x66663180,0x6331980c,0x031e0cc6,0x00c00c06,0x18186666,0x8f0666c3,0x8cc6667f,0x030180c1,0x663c3f80,0x6331998c,0x63180cc6,0x00c18cc6,0xf03c7c3e,0x060dc381,0xfcfcc333,0x01c18381,0xdc300000,0xc6e370f8,0x3e301b8d,0x01e0f87c,0x00006006,0x00000000,0x00c00000,0x00000000,0x00600000,0x00000000,0x001e0000,0x00000000,0x00006006,0x00000000,0x00600000,0x00000000,0x003e0000,0x00000000,0x00000000,0x00000000,0x0000f00f,0x00000000,0x003e0000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
|
|
0xe0000000,0x00000180,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x000000dc,0xb18c0c18,0x000000c1,0x300c0004,0xcc600030,0x001c0c18,0x018380fc,0x000c0606,0x00e0f076,0xe000183c,0x1f000060,0x7818630e,0x00063060,0x86360c00,0x00c6c199,0x6e060303,0x01b0d800,0x00203066,0x0d800000,0xcc30001b,0xc7c000c0,0xcc263f18,0x8060c198,0x3b030181,0x01b0d8c6,0xe0700000,0x0cc007f0,0x00000000,0xcc600000,0x78066198,0x0000c0f8,0x00000000,0x00e1f0ce,0xb0d8381c,0x0ccec661,0xcc7c3e1f,0xcc663198,0x300f0198,0x81e0c118,0x3b198f83,0x000000de,0x198c3018,0x9fdb8063,0xccc66331,0xcc663198,0xfe060198,0x0303f199,0x661998c3,0x01f1f8fe,0x198c3018,0x8cdb03e3,0xccc66331,0xcc663198,0x30060198,0x03e0c3d8,0x661998c3,0x000000f6,0xf9fc3018,0x8ccfc063,0xccc66331,0xcc663198,0xfe066198,0x0330c199,0x661998c3,0x000000e6,0x198c3018,0x8cc36063,0xccc66331,0xcc663198,0x30063f18,0x0330c198,0x661998c3,0x000000c6,0x198c3018,0x8cc76663,0xccc66331,0xcc663198,0x30670c18,0x0330c198,0x661998c3,0x000000c6,0x198c783c,0x1cddc7f3,0xb87c3e1f,0x87c7e371,0x303f0c0f,0x86e0c33c,0x66370f87,0x000000c6,0x00000000,0x00000000,0x00000000,0x00060000,0x00000000,0x0000d800,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00030000,0x00000000,0x00007000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x0001e000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
|
|
0x00000000,0x00000000,0xef558800,0x030180c6,0xd800001b,0x86c001b0,0x1800060d,0x00c00030,0x18000000,0x00000030,0xb8aa2200,0x030180c7,0xd800001b,0x86c001b0,0x1800060d,0x00c00030,0x1800000c,0x00030030,0xef558800,0x030180c6,0xd800001b,0x86c001b0,0x1800060d,0x00c00030,0x1800000c,0x00030432,0xb8aa2200,0x030180c7,0xd800001b,0x86c001b0,0x1800060d,0x00c00030,0x18000000,0x00000633,0xef558800,0x030180c6,0xd800001b,0x86c001b0,0x1800060d,0x00c00030,0x9800000c,0x9b030331,0xb8aa220d,0x03e180c7,0xde1f001b,0x86f3f9b0,0x180007cd,0x00c00030,0xc1fcfe0c,0x0d830180,0xef55881b,0x030180c6,0xc018001b,0x860301b0,0x1800060d,0x00c00030,0x61800606,0x06c300c0,0xb8aa2236,0xc3e1f0c7,0xde1f3f9b,0xe7f379b0,0xf80f87cf,0x0fc7ffff,0x31800603,0x0d878660,0xef55881b,0x030180c6,0xd818361b,0x000361b0,0x000c0000,0x00c06000,0x99800663,0x9b078733,0xb8aa220d,0x030180c7,0xd818361b,0x000361b0,0x000c0000,0x00c06000,0xc9800663,0x00078696,0xef558800,0x030180c6,0xd818361b,0x000361b0,0x000c0000,0x00c06000,0x0000003e,0x000307c3,0xb8aa2200,0x030180c7,0xd818361b,0x000361b0,0x000c0000,0x00c06000,0x80000000,0x00000601,0xef558800,0x030180c6,0xd818361b,0x000361b0,0x000c0000,0x00c06000,0xc0000000,0x00000607,0xb8aa2200,0x030180c7,0xd818361b,0x000361b0,0x000c0000,0x00c06000,0x00000000,0x00000000,0xef558800,0x030180c6,0xd818361b,0x000361b0,0x000c0000,0x00c06000,0x00000000,0x00000000,0xb8aa2200,0x030180c7,0xd818361b,0x000361b0,0x000c0000,0x00c06000,
|
|
0x60603000,0x1b0006c3,0xb0006c00,0x0006c0c1,0x00183600,0x01836000,0x00ff8003,0x0fffc01e,0x60603000,0x1b0006c3,0xb0006c00,0x0006c0c1,0x00183600,0x01836000,0x00ff8003,0x0fffc01e,0x60603000,0x1b0006c3,0xb0006c00,0x0006c0c1,0x00183600,0x01836000,0x00ff8003,0x0fffc01e,0x60603000,0x1b0006c3,0xb0006c00,0x0006c0c1,0x00183600,0x01836000,0x00ff8003,0x0fffc01e,0x60603000,0x1b0006c3,0xb0006c00,0x0006c0c1,0x00183600,0x01836000,0x00ff8003,0x0fffc01e,0x67e03000,0xfbff9ec3,0xbfffecff,0x3fe6cfff,0xf1f83600,0x1ff36003,0x00ff8003,0x0fffc01e,0x60603000,0x000180c3,0x00000c00,0x0006c000,0x30183600,0x01836000,0x00ff8003,0x0fffc01e,0x67e3ffff,0xfffd9fcf,0xbfffecf7,0xffffffff,0xf1f8fe7f,0xfffffff3,0xfffffe03,0x0007c01f,0x60603000,0x000d8003,0xb0006c36,0x03000001,0x3000001b,0x018361b0,0xffff8600,0x0007c01f,0x60603000,0x000d8003,0xb0006c36,0x03000001,0x3000001b,0x018361b0,0xffff8600,0x0007c01f,0x60603000,0x000d8003,0xb0006c36,0x03000001,0x3000001b,0x018361b0,0xffff8600,0x0007c01f,0x60603000,0x000d8003,0xb0006c36,0x03000001,0x3000001b,0x018361b0,0xffff8600,0x0007c01f,0x60603000,0x000d8003,0xb0006c36,0x03000001,0x3000001b,0x018361b0,0xffff8600,0x0007c01f,0x60603000,0x000d8003,0xb0006c36,0x03000001,0x3000001b,0x018361b0,0xffff8600,0x0007c01f,0x60603000,0x000d8003,0xb0006c36,0x03000001,0x3000001b,0x018361b0,0xffff8600,0x0007c01f,0x60603000,0x000d8003,0xb0006c36,0x03000001,0x3000001b,0x018361b0,0xffff8600,0x0007c01f,
|
|
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000003,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x1c000003,0x07800000,0x01fc3c00,0x00000000,0x70000000,0x000003c0,0x0000000e,0x07000000,0x36000003,0x01800000,0x018c6600,0x000007f0,0xd8387e00,0x18000060,0x00001f03,0x0d818030,0x36000003,0x01800000,0xf98c6600,0x19800633,0x8c6c1837,0x8c0000c1,0x307f3181,0x0d80c060,0x1c000603,0x01800000,0xb00c666e,0x998fc061,0x8cc63c1d,0x8fc7e181,0x30003181,0x018060c0,0x00370603,0x01800000,0xb00c363b,0x198360c1,0x8cc6660c,0x9b6db3e1,0xfc00318f,0x01803180,0x001d8003,0x01800000,0xb00c661b,0x19836181,0xd8fe660c,0x9b6db330,0x307f3181,0x018060c0,0x00001f83,0x01b80030,0xb00cc61b,0x198360c1,0xd8c6660c,0x99edb330,0x30003181,0x6180c060,0x00370003,0x01b06030,0xb00cc61b,0x0f836061,0xd8c63c0c,0x8fc7e330,0x00003181,0x61818030,0x001d8603,0x01b00000,0xb00cc63b,0x01836631,0xd86c180c,0x00c00330,0x007f3183,0x61800000,0x00000603,0x01e00000,0xb00c666e,0x0181c7f1,0xdc387e0c,0x006001e1,0xfe00318e,0xc183f1f9,0x00000001,0x01c00000,0x00000000,0x00c00000,0x00000000,0x00000000,0x00000000,0x01800000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x01800000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x01800000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x01800000,0x00000000,0x00000000,
|
|
0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00001c1b,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00003636,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00001836,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00f80c36,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00f82636,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00f83e36,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00f80000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00f80000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00f80000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00f80000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,
|
|
};
|
|
|
|
|
|
pixelfont_t* internals_build_font( uint32_t* font ) {
|
|
uint32_t const* data = font;
|
|
int chr_width = (int)*data++;
|
|
int chr_height = (int)*data++;
|
|
int chr_baseline = (int)*data++;
|
|
int chr_mod = 256 / chr_width;
|
|
PIXELFONT_U8 pixels[ 32 * 32 ];
|
|
pixelfont_builder_t* builder = pixelfont_builder_create( chr_height, chr_baseline, chr_height, NULL );
|
|
for( int c = 0; c < 256; ++c ) {
|
|
int sx = ( c % chr_mod ) * chr_width;
|
|
int sy = ( c / chr_mod ) * chr_height;
|
|
for( int y = 0; y < chr_height; ++y ) {
|
|
for( int x = 0; x < chr_width; ++x ) {
|
|
int v = ( sx + x ) / 32;
|
|
int u = ( sx + x ) - ( v * 32 );
|
|
uint32_t b = data[ v + ( sy + y ) * 8 ];
|
|
if( b & ( 1 << u ) ) {
|
|
pixels[ x + y * chr_width ] = 1;
|
|
} else {
|
|
pixels[ x + y * chr_width ] = 0;
|
|
}
|
|
}
|
|
}
|
|
pixelfont_builder_glyph( builder, c, chr_width, pixels, chr_width, 0, chr_width );
|
|
}
|
|
pixelfont_t* pixelfont = pixelfont_builder_font( builder );
|
|
pixelfont_t* output = (pixelfont_t*) malloc( pixelfont->size_in_bytes );
|
|
memcpy( output, pixelfont, pixelfont->size_in_bytes );
|
|
pixelfont_builder_destroy( builder );
|
|
return output;
|
|
}
|
|
|
|
|
|
#define DEFAULT_SOUNDBANK_NONE 0
|
|
|
|
|
|
enum audio_command_type_t {
|
|
AUDIO_COMMAND_NOTE_ON,
|
|
AUDIO_COMMAND_NOTE_OFF,
|
|
AUDIO_COMMAND_NOTE_OFF_ALL,
|
|
AUDIO_COMMAND_SET_INSTRUMENT,
|
|
};
|
|
|
|
|
|
struct audio_command_t {
|
|
enum audio_command_type_t type;
|
|
int channel;
|
|
int note;
|
|
int velocity;
|
|
int instrument;
|
|
int frame_stamp;
|
|
};
|
|
|
|
|
|
enum soundbank_type_t {
|
|
SOUNDBANK_TYPE_NONE,
|
|
SOUNDBANK_TYPE_SF2,
|
|
//SOUNDBANK_TYPE_IBK,
|
|
SOUNDBANK_TYPE_OP2,
|
|
};
|
|
|
|
|
|
struct internals_t {
|
|
thread_mutex_t mutex;
|
|
thread_atomic_int_t exit_flag;
|
|
|
|
struct {
|
|
thread_signal_t signal;
|
|
thread_atomic_int_t count;
|
|
} vbl;
|
|
|
|
struct {
|
|
enum videomode_t mode;
|
|
int width;
|
|
int height;
|
|
uint32_t* font;
|
|
int cellwidth;
|
|
int cellheight;
|
|
bool doublebuffer;
|
|
uint8_t* buffer;
|
|
uint8_t buffer0[ 1024 * 1024 ];
|
|
uint8_t buffer1[ 1024 * 1024 ];
|
|
uint32_t palette[ 256 ];
|
|
} screen;
|
|
|
|
struct {
|
|
uint8_t* buffer;
|
|
int width;
|
|
int height;
|
|
} draw;
|
|
|
|
struct {
|
|
int color;
|
|
|
|
pixelfont_t* fonts[ 256 ];
|
|
int fonts_count;
|
|
|
|
int current_font;
|
|
int bold;
|
|
int italic;
|
|
int underline;
|
|
} graphics;
|
|
|
|
struct {
|
|
int x;
|
|
int y;
|
|
int fg;
|
|
int bg;
|
|
bool curs;
|
|
} conio;
|
|
|
|
struct {
|
|
bool keystate[ KEYCOUNT ];
|
|
enum keycode_t* keybuffer;
|
|
enum keycode_t keybuffer0[ 256 ];
|
|
enum keycode_t keybuffer1[ 256 ];
|
|
char* charbuffer;
|
|
char charbuffer0[ 256 ];
|
|
char charbuffer1[ 256 ];
|
|
int mouse_x;
|
|
int mouse_y;
|
|
int mouse_relx;
|
|
int mouse_rely;
|
|
} input;
|
|
|
|
struct {
|
|
int frame_stamp;
|
|
int commands_count;
|
|
struct audio_command_t commands[ 256 ];
|
|
struct music_t* current_music;
|
|
bool loop_music;
|
|
int music_volume;
|
|
int music_play_counter;
|
|
enum soundmode_t soundmode;
|
|
struct {
|
|
struct sound_t* sound;
|
|
bool loop;
|
|
int volume_left;
|
|
int volume_right;
|
|
int play_counter;
|
|
} channels[ SOUND_CHANNELS ];
|
|
|
|
int current_soundbank;
|
|
int soundbanks_count;
|
|
struct {
|
|
enum soundbank_type_t type;
|
|
tsf* sf2;
|
|
void* data;
|
|
size_t size;
|
|
} soundbanks[ 256 ];
|
|
} audio;
|
|
|
|
#ifdef __wasm__
|
|
struct {
|
|
int swap_counts;
|
|
int read_counts;
|
|
WaCoro user_coro;
|
|
} wasm;
|
|
#endif
|
|
|
|
}* internals;
|
|
|
|
|
|
static void internals_create( int sound_buffer_size ) {
|
|
(void) sound_buffer_size;
|
|
internals = (struct internals_t*) malloc( sizeof( struct internals_t ) );
|
|
memset( internals, 0, sizeof( *internals ) );
|
|
|
|
thread_mutex_init( &internals->mutex );
|
|
|
|
thread_signal_init( &internals->vbl.signal );
|
|
thread_atomic_int_store( &internals->vbl.count, 0 );
|
|
|
|
internals->screen.mode = videomode_80x25_9x16;
|
|
internals->screen.width = 80;
|
|
internals->screen.height = 25;
|
|
internals->screen.font = font9x16;
|
|
internals->screen.cellwidth = 9;
|
|
internals->screen.cellheight = 16;
|
|
internals->screen.buffer = internals->screen.buffer0;
|
|
memcpy( internals->screen.palette, default_palette, 1024 );
|
|
|
|
internals->draw.buffer = internals->screen.buffer;
|
|
internals->draw.width = internals->screen.width;
|
|
internals->draw.height = internals->screen.height;
|
|
|
|
internals->graphics.color = 15;
|
|
internals->graphics.fonts_count = 4;
|
|
internals->graphics.current_font = 1;
|
|
|
|
internals->conio.fg = 7;
|
|
internals->conio.curs = true;
|
|
|
|
internals->input.keybuffer = internals->input.keybuffer0;
|
|
internals->input.charbuffer = internals->input.charbuffer0;
|
|
|
|
internals->graphics.fonts[ DEFAULT_FONT_8X8 ] = internals_build_font( font8x8 );
|
|
internals->graphics.fonts[ DEFAULT_FONT_8X16 ] = internals_build_font( font8x16 );
|
|
internals->graphics.fonts[ DEFAULT_FONT_9X16 ] = internals_build_font( font9x16 );
|
|
|
|
internals->audio.current_soundbank = DEFAULT_SOUNDBANK_NONE;
|
|
internals->audio.soundbanks_count = 3;
|
|
internals->audio.soundbanks[ DEFAULT_SOUNDBANK_AWE32 ].type = SOUNDBANK_TYPE_SF2;
|
|
internals->audio.soundbanks[ DEFAULT_SOUNDBANK_AWE32 ].sf2 = NULL; // load when first used
|
|
internals->audio.soundbanks[ DEFAULT_SOUNDBANK_AWE32 ].data = NULL;
|
|
internals->audio.soundbanks[ DEFAULT_SOUNDBANK_AWE32 ].size = 0;
|
|
internals->audio.soundbanks[ DEFAULT_SOUNDBANK_SB16 ].type = SOUNDBANK_TYPE_NONE;
|
|
internals->audio.soundbanks[ DEFAULT_SOUNDBANK_SB16 ].sf2 = NULL;
|
|
internals->audio.soundbanks[ DEFAULT_SOUNDBANK_SB16 ].data = NULL;
|
|
internals->audio.soundbanks[ DEFAULT_SOUNDBANK_SB16 ].size = 0;
|
|
|
|
internals->audio.soundmode = soundmode_8bit_mono_22050;
|
|
}
|
|
|
|
|
|
static void internals_destroy( void ) {
|
|
for( int i = 1; i < internals->graphics.fonts_count; ++i ) {
|
|
if( internals->graphics.fonts[ i ] ) {
|
|
free( internals->graphics.fonts[ i ] );
|
|
}
|
|
}
|
|
for( int i = 1; i < internals->audio.soundbanks_count; ++i ) {
|
|
if( internals->audio.soundbanks[ i ].data ) {
|
|
free( internals->audio.soundbanks[ i ].data );
|
|
} else if( internals->audio.soundbanks[ i ].sf2 ) {
|
|
tsf_close( internals->audio.soundbanks[ i ].sf2 );
|
|
}
|
|
}
|
|
thread_signal_term( &internals->vbl.signal );
|
|
thread_mutex_term( &internals->mutex );
|
|
free( internals );
|
|
internals = NULL;
|
|
}
|
|
|
|
|
|
int shuttingdown( void ) {
|
|
|
|
return thread_atomic_int_load( &internals->exit_flag );
|
|
}
|
|
|
|
|
|
void setvideomode( enum videomode_t mode ) {
|
|
thread_mutex_lock( &internals->mutex );
|
|
internals->screen.mode = mode;
|
|
memcpy( internals->screen.palette, default_palette, 1024 );
|
|
internals->conio.curs = true;
|
|
switch( mode ) {
|
|
case videomode_40x25_8x8:
|
|
internals->screen.width = 40;
|
|
internals->screen.height = 25;
|
|
internals->screen.font = font8x8;
|
|
internals->screen.cellwidth = 8;
|
|
internals->screen.cellheight = 8;
|
|
break;
|
|
case videomode_40x25_9x16:
|
|
internals->screen.width = 40;
|
|
internals->screen.height = 25;
|
|
internals->screen.font = font9x16;
|
|
internals->screen.cellwidth = 9;
|
|
internals->screen.cellheight = 16;
|
|
break;
|
|
case videomode_80x25_8x8:
|
|
internals->screen.width = 80;
|
|
internals->screen.height = 25;
|
|
internals->screen.font = font8x8;
|
|
internals->screen.cellwidth = 8;
|
|
internals->screen.cellheight = 8;
|
|
break;
|
|
case videomode_80x25_8x16:
|
|
internals->screen.width = 80;
|
|
internals->screen.height = 25;
|
|
internals->screen.font = font8x16;
|
|
internals->screen.cellwidth = 8;
|
|
internals->screen.cellheight = 16;
|
|
break;
|
|
case videomode_80x25_9x16:
|
|
internals->screen.width = 80;
|
|
internals->screen.height = 25;
|
|
internals->screen.font = font9x16;
|
|
internals->screen.cellwidth = 9;
|
|
internals->screen.cellheight = 16;
|
|
break;
|
|
case videomode_80x43_8x8:
|
|
internals->screen.width = 80;
|
|
internals->screen.height = 43;
|
|
internals->screen.font = font8x8;
|
|
internals->screen.cellwidth = 8;
|
|
internals->screen.cellheight = 8;
|
|
break;
|
|
case videomode_80x50_8x8:
|
|
internals->screen.width = 80;
|
|
internals->screen.height = 50;
|
|
internals->screen.font = font8x8;
|
|
internals->screen.cellwidth = 8;
|
|
internals->screen.cellheight = 8;
|
|
break;
|
|
case videomode_320x200:
|
|
internals->screen.width = 320;
|
|
internals->screen.height = 200;
|
|
internals->screen.font = NULL;
|
|
internals->screen.cellwidth = 1;
|
|
internals->screen.cellheight = 1;
|
|
break;
|
|
case videomode_320x240:
|
|
internals->screen.width = 320;
|
|
internals->screen.height = 240;
|
|
internals->screen.font = NULL;
|
|
internals->screen.cellwidth = 1;
|
|
internals->screen.cellheight = 1;
|
|
break;
|
|
case videomode_320x400:
|
|
internals->screen.width = 320;
|
|
internals->screen.height = 400;
|
|
internals->screen.font = NULL;
|
|
internals->screen.cellwidth = 1;
|
|
internals->screen.cellheight = 1;
|
|
break;
|
|
case videomode_640x200:
|
|
internals->screen.width = 640;
|
|
internals->screen.height = 200;
|
|
internals->screen.font = NULL;
|
|
internals->screen.cellwidth = 1;
|
|
internals->screen.cellheight = 1;
|
|
break;
|
|
case videomode_640x350:
|
|
internals->screen.width = 640;
|
|
internals->screen.height = 350;
|
|
internals->screen.font = NULL;
|
|
internals->screen.cellwidth = 1;
|
|
internals->screen.cellheight = 1;
|
|
break;
|
|
case videomode_640x400:
|
|
internals->screen.width = 640;
|
|
internals->screen.height = 400;
|
|
internals->screen.font = NULL;
|
|
internals->screen.cellwidth = 1;
|
|
internals->screen.cellheight = 1;
|
|
break;
|
|
case videomode_640x480:
|
|
internals->screen.width = 640;
|
|
internals->screen.height = 480;
|
|
internals->screen.font = NULL;
|
|
internals->screen.cellwidth = 1;
|
|
internals->screen.cellheight = 1;
|
|
break;
|
|
default: {
|
|
uint32_t custom_mode = (uint32_t)mode;
|
|
internals->screen.width = ( ( custom_mode & 0xffc00 ) >> 10 ) + 1;
|
|
internals->screen.height = ( custom_mode & 0x003ff ) + 1;
|
|
if( custom_mode & 0x100000 ) {
|
|
if( custom_mode & 0x200000 ) {
|
|
internals->screen.font = font9x16;
|
|
internals->screen.cellwidth = 9;
|
|
internals->screen.cellheight = 16;
|
|
} else {
|
|
internals->screen.font = font8x8;
|
|
internals->screen.cellwidth = 8;
|
|
internals->screen.cellheight = 8;
|
|
}
|
|
} else {
|
|
internals->screen.font = NULL;
|
|
internals->screen.cellwidth = 1;
|
|
internals->screen.cellheight = 1;
|
|
}
|
|
}
|
|
|
|
}
|
|
memset( internals->screen.buffer0, 0, internals->screen.width * internals->screen.height * ( internals->screen.font ? 2 : 1 ) );
|
|
memset( internals->screen.buffer1, 0, internals->screen.width * internals->screen.height * ( internals->screen.font ? 2 : 1 ) );
|
|
resetdrawtarget();
|
|
thread_mutex_unlock( &internals->mutex );
|
|
};
|
|
|
|
|
|
int screenwidth( void ) {
|
|
return internals->screen.width;
|
|
}
|
|
|
|
|
|
int screenheight( void ) {
|
|
return internals->screen.height;
|
|
}
|
|
|
|
|
|
unsigned char* screenbuffer( void ) {
|
|
return internals->screen.buffer;
|
|
}
|
|
|
|
|
|
void setdoublebuffer( int enabled ) {
|
|
internals->screen.doublebuffer = ( enabled != 0 );
|
|
}
|
|
|
|
|
|
unsigned char* swapbuffers( void ) {
|
|
#ifdef __wasm__
|
|
if (internals->wasm.swap_counts++ > 2) {
|
|
// In WebAssembly without real threads, if a dos-like application never calls waitvbl
|
|
// by itself, we force a call to it every few calls to swapbuffer
|
|
waitvbl();
|
|
}
|
|
#endif
|
|
if( internals->screen.doublebuffer ) {
|
|
thread_mutex_lock( &internals->mutex );
|
|
if( internals->screen.buffer == internals->screen.buffer0 ) {
|
|
if( internals->draw.buffer == internals->screen.buffer ) {
|
|
internals->draw.buffer = internals->screen.buffer1;
|
|
}
|
|
internals->screen.buffer = internals->screen.buffer1;
|
|
} else {
|
|
if( internals->draw.buffer == internals->screen.buffer ) {
|
|
internals->draw.buffer = internals->screen.buffer0;
|
|
}
|
|
internals->screen.buffer = internals->screen.buffer0;
|
|
}
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
return internals->screen.buffer;
|
|
}
|
|
|
|
|
|
void setpal( int index, int r, int g, int b ) {
|
|
if( index < 0 || index >= 256 ) {
|
|
return;
|
|
}
|
|
|
|
r = ( r & 63 ) << 2;
|
|
g = ( g & 63 ) << 2;
|
|
b = ( b & 63 ) << 2;
|
|
internals->screen.palette[ index ] = ( b << 16 ) | ( g << 8 ) | ( r );
|
|
}
|
|
|
|
|
|
void getpal( int index, int* r, int* g, int* b ) {
|
|
if( index < 0 || index >= 256 ) {
|
|
return;
|
|
}
|
|
|
|
uint32_t c = internals->screen.palette[ index ];
|
|
uint32_t cr = ( c ) & 0xff;
|
|
uint32_t cg = ( c >> 8 ) & 0xff;
|
|
uint32_t cb = ( c >> 16 ) & 0xff;
|
|
if( r ) {
|
|
*r = cr >> 2;
|
|
}
|
|
if( g ) {
|
|
*g = cg >> 2;
|
|
}
|
|
if( b ) {
|
|
*b = cb >> 2;
|
|
}
|
|
}
|
|
|
|
|
|
int getpixel( int x, int y ) {
|
|
if( internals->screen.font ) return 0;
|
|
if( x >= 0 && y >= 0 && x < internals->draw.width && y < internals->draw.height ) {
|
|
return internals->draw.buffer[ x + internals->draw.width * y ];
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
void putpixel( int x, int y, int color ) {
|
|
if( internals->screen.font ) return;
|
|
if( x >= 0 && y >= 0 && x < internals->draw.width && y < internals->draw.height ) {
|
|
internals->draw.buffer[ x + internals->draw.width * y ] = (uint8_t)color;
|
|
}
|
|
}
|
|
|
|
|
|
void setdrawtarget( unsigned char* pixels, int width, int height ) {
|
|
if( internals->screen.font ) return;
|
|
internals->draw.buffer = pixels;
|
|
internals->draw.width = width;
|
|
internals->draw.height = height;
|
|
}
|
|
|
|
|
|
void resetdrawtarget( void ) {
|
|
if( internals->screen.font ) return;
|
|
internals->draw.buffer = internals->screen.buffer;
|
|
internals->draw.width = internals->screen.width;
|
|
internals->draw.height = internals->screen.height;
|
|
}
|
|
|
|
|
|
void setcolor( int color ) {
|
|
if( internals->screen.font ) return;
|
|
if( color >= 0 && color <= 255 ) {
|
|
internals->graphics.color = color;
|
|
}
|
|
}
|
|
|
|
|
|
int getcolor( void ) {
|
|
if( internals->screen.font ) return 0;
|
|
return internals->graphics.color;
|
|
}
|
|
|
|
|
|
int installuserfont( char const* filename ) {
|
|
if( internals->graphics.fonts_count >= sizeof( internals->graphics.fonts ) / sizeof( *internals->graphics.fonts ) ) {
|
|
return 0;
|
|
}
|
|
FILE* fp = fopen( filename, "rb" );
|
|
if( !fp ) return 0;
|
|
fseek( fp, 0, SEEK_END );
|
|
size_t sz = ftell( fp );
|
|
fseek( fp, 0, SEEK_SET );
|
|
uint8_t* data = (uint8_t*) malloc( sz );
|
|
fread( data, 1, sz, fp );
|
|
fclose( fp );
|
|
|
|
internals->graphics.fonts[ internals->graphics.fonts_count ] = (pixelfont_t*) data;
|
|
|
|
return internals->graphics.fonts_count++;
|
|
}
|
|
|
|
|
|
void settextstyle( int font, int bold, int italic, int underline ) {
|
|
if( internals->screen.font ) return;
|
|
if( font >= 1 && font < internals->graphics.fonts_count ) {
|
|
internals->graphics.current_font = font;
|
|
internals->graphics.bold = bold;
|
|
internals->graphics.italic = italic;
|
|
internals->graphics.underline = underline;
|
|
}
|
|
}
|
|
|
|
void wraptextxy( int x, int y, char const* text, int wrap_width ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
pixelfont_t* font = internals->graphics.fonts[ internals->graphics.current_font ];
|
|
PIXELFONT_COLOR* target = internals->draw.buffer;
|
|
int width = internals->draw.width;
|
|
int height = internals->draw.height;
|
|
pixelfont_align_t align = PIXELFONT_ALIGN_LEFT;
|
|
int hspacing = 0;
|
|
int vspacing = 0;
|
|
int limit = -1;
|
|
pixelfont_bold_t bold = internals->graphics.bold ? PIXELFONT_BOLD_ON : PIXELFONT_BOLD_OFF;
|
|
pixelfont_italic_t italic = internals->graphics.italic ? PIXELFONT_ITALIC_ON : PIXELFONT_ITALIC_OFF;
|
|
pixelfont_underline_t underline = internals->graphics.underline ? PIXELFONT_UNDERLINE_ON : PIXELFONT_UNDERLINE_OFF;
|
|
|
|
pixelfont_blit( font, x, y, text, (PIXELFONT_COLOR)color, target, width, height, align, wrap_width, hspacing, vspacing, limit, bold,
|
|
italic, underline, NULL );
|
|
}
|
|
|
|
|
|
void centertextxy( int x, int y, char const* text, int wrap_width ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
pixelfont_t* font = internals->graphics.fonts[ internals->graphics.current_font ];
|
|
PIXELFONT_COLOR* target = internals->draw.buffer;
|
|
int width = internals->draw.width;
|
|
int height = internals->draw.height;
|
|
pixelfont_align_t align = PIXELFONT_ALIGN_CENTER;
|
|
int hspacing = 0;
|
|
int vspacing = 0;
|
|
int limit = -1;
|
|
pixelfont_bold_t bold = internals->graphics.bold ? PIXELFONT_BOLD_ON : PIXELFONT_BOLD_OFF;
|
|
pixelfont_italic_t italic = internals->graphics.italic ? PIXELFONT_ITALIC_ON : PIXELFONT_ITALIC_OFF;
|
|
pixelfont_underline_t underline = internals->graphics.underline ? PIXELFONT_UNDERLINE_ON : PIXELFONT_UNDERLINE_OFF;
|
|
|
|
pixelfont_blit( font, x, y, text, (PIXELFONT_COLOR)color, target, width, height, align, wrap_width, hspacing, vspacing, limit, bold,
|
|
italic, underline, NULL );
|
|
}
|
|
|
|
|
|
void outtextxy( int x, int y, char const* text ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
pixelfont_t* font = internals->graphics.fonts[ internals->graphics.current_font ];
|
|
PIXELFONT_COLOR* target = internals->draw.buffer;
|
|
int width = internals->draw.width;
|
|
int height = internals->draw.height;
|
|
pixelfont_align_t align = PIXELFONT_ALIGN_LEFT;
|
|
int wrap_width = 0;
|
|
int hspacing = 0;
|
|
int vspacing = 0;
|
|
int limit = -1;
|
|
pixelfont_bold_t bold = internals->graphics.bold ? PIXELFONT_BOLD_ON : PIXELFONT_BOLD_OFF;
|
|
pixelfont_italic_t italic = internals->graphics.italic ? PIXELFONT_ITALIC_ON : PIXELFONT_ITALIC_OFF;
|
|
pixelfont_underline_t underline = internals->graphics.underline ? PIXELFONT_UNDERLINE_ON : PIXELFONT_UNDERLINE_OFF;
|
|
|
|
pixelfont_blit( font, x, y, text, (PIXELFONT_COLOR)color, target, width, height, align, wrap_width, hspacing, vspacing, limit, bold,
|
|
italic, underline, NULL );
|
|
}
|
|
|
|
|
|
void waitvbl( void ) {
|
|
if( thread_atomic_int_load( &internals->exit_flag ) == 0 ) {
|
|
#ifndef __wasm__
|
|
int current_vbl_count = thread_atomic_int_load( &internals->vbl.count );
|
|
while( current_vbl_count == thread_atomic_int_load( &internals->vbl.count ) ) {
|
|
thread_signal_wait( &internals->vbl.signal, 1000 );
|
|
}
|
|
#else
|
|
WaCoroSwitch(0);
|
|
internals->wasm.swap_counts = internals->wasm.read_counts = 0;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
static void signalvbl( void ) {
|
|
thread_atomic_int_inc( &internals->vbl.count );
|
|
thread_signal_raise( &internals->vbl.signal );
|
|
#ifdef __wasm__
|
|
WaCoroSwitch(internals->wasm.user_coro);
|
|
#endif
|
|
}
|
|
|
|
|
|
void clearscreen( void ) {
|
|
memset( internals->screen.buffer, 0, internals->screen.width * internals->screen.height * ( internals->screen.font ? 2 : 1 ) );
|
|
}
|
|
|
|
|
|
static bool blitclip( int* px, int *py, int width, int height, int* psrcx, int* psrcy, int* psrcw, int* psrch ) {
|
|
int x = *px;
|
|
int y = *py;
|
|
int srcx = *psrcx;
|
|
int srcy = *psrcy;
|
|
int srcw = *psrcw;
|
|
int srch = *psrch;
|
|
if( x < 0 ) {
|
|
srcx -= x;
|
|
srcw += x;
|
|
x = 0;
|
|
}
|
|
if( y < 0 ) {
|
|
srcy -= y;
|
|
srch += y;
|
|
y = 0;
|
|
}
|
|
if( srcx < 0 ) {
|
|
x += srcx;
|
|
srcw += srcx;
|
|
srcx = 0;
|
|
}
|
|
if( srcy < 0 ) {
|
|
y += srcy;
|
|
srch += srcy;
|
|
srcy = 0;
|
|
}
|
|
if( srcx + srcw >= width ) {
|
|
srcw += ( width - ( srcx + srcw ) );
|
|
}
|
|
if( srcy + srch >= height ) {
|
|
srch += ( height - ( srcy + srch ) );
|
|
}
|
|
if( x + srcw >= internals->draw.width ) {
|
|
srcw += ( internals->draw.width - ( x + srcw ) );
|
|
}
|
|
if( y + srch >= internals->draw.height ) {
|
|
srch += ( internals->draw.height - ( y + srch ) );
|
|
}
|
|
if( srcw <= 0 || srch <= 0 || x + srcw < 0 || y + srch < 0 || x > internals->draw.width || y > internals->draw.height ) {
|
|
return false;
|
|
} else {
|
|
*px = x;
|
|
*py = y;
|
|
*psrcx = srcx;
|
|
*psrcy = srcy;
|
|
*psrcw = srcw;
|
|
*psrch = srch;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
void blit( int x, int y, unsigned char* source, int width, int height, int srcx, int srcy, int srcw, int srch ) {
|
|
if( internals->screen.font ) return;
|
|
if( !blitclip( &x, &y, width, height, &srcx, &srcy, &srcw, &srch ) ) {
|
|
return;
|
|
}
|
|
|
|
uint8_t* dst = internals->draw.buffer + x + y * internals->draw.width;
|
|
uint8_t* src = source + srcx + srcy * width;
|
|
for( int iy = 0; iy < srch; ++iy ) {
|
|
memcpy( dst, src, srcw );
|
|
src += width;
|
|
dst += internals->draw.width;
|
|
}
|
|
}
|
|
|
|
|
|
void maskblit( int x, int y, unsigned char* source, int width, int height, int srcx, int srcy, int srcw, int srch, int colorkey ) {
|
|
if( internals->screen.font ) return;
|
|
if( !blitclip( &x, &y, width, height, &srcx, &srcy, &srcw, &srch ) ) {
|
|
return;
|
|
}
|
|
|
|
uint8_t* dst = internals->draw.buffer + x + y * internals->draw.width;
|
|
uint8_t* src = source + srcx + srcy * width;
|
|
for( int iy = 0; iy < srch; ++iy ) {
|
|
for( int ix = 0; ix < srcw; ++ix ) {
|
|
if( src[ ix ] != colorkey ) {
|
|
dst[ ix ] = src[ ix ];
|
|
}
|
|
}
|
|
src += width;
|
|
dst += internals->draw.width;
|
|
}
|
|
}
|
|
|
|
|
|
struct gif_load_context_t {
|
|
int width;
|
|
int height;
|
|
uint8_t* pixels;
|
|
int palcount;
|
|
uint8_t palette[ 768 ];
|
|
};
|
|
|
|
|
|
static void load_gif_frame( void* data, struct GIF_WHDR* whdr ) {
|
|
struct gif_load_context_t* context = (struct gif_load_context_t*) data;
|
|
if( context->pixels ) {
|
|
return;
|
|
}
|
|
context->width = whdr->xdim;
|
|
context->height = whdr->ydim;
|
|
context->palcount = whdr->clrs;
|
|
memcpy( context->palette, whdr->cpal, whdr->clrs * 3 );
|
|
context->pixels = (uint8_t*)malloc( whdr->xdim * whdr->ydim );
|
|
memset( context->pixels, 0, whdr->xdim * whdr->ydim );
|
|
|
|
uint32_t ddst = (uint32_t)( whdr->xdim * whdr->fryo + whdr->frxo );
|
|
uint32_t iter = ( whdr->intr ) ? 0 : 4;
|
|
uint32_t ifin = ( !iter ) ? 4 : 5; // interlacing support
|
|
for( uint32_t dsrc = (uint32_t)-1; iter < ifin; ++iter ) {
|
|
for( uint32_t yoff = 16U >> ((iter > 1)? iter : 1), y = (8 >> iter) & 7;
|
|
y < (uint32_t)whdr->fryd; y += yoff) {
|
|
for( uint32_t x = 0; x < (uint32_t)whdr->frxd; x++) {
|
|
if (whdr->tran != (long)whdr->bptr[++dsrc]) {
|
|
context->pixels[(uint32_t)whdr->xdim * y + x + ddst] = whdr->bptr[dsrc];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
unsigned char* loadgif( char const* filename, int* width, int* height, int* palcount, unsigned char palette[ 768 ] ) {
|
|
FILE* fp = fopen( filename, "rb" );
|
|
if( !fp ) return NULL;
|
|
fseek( fp, 0, SEEK_END );
|
|
size_t sz = ftell( fp );
|
|
fseek( fp, 0, SEEK_SET );
|
|
uint8_t* data = (uint8_t*) malloc( sz );
|
|
fread( data, 1, sz, fp );
|
|
fclose( fp );
|
|
|
|
struct gif_load_context_t context;
|
|
memset( &context, 0, sizeof( context ) );
|
|
GIF_Load( data, (long)sz, load_gif_frame, NULL, &context, 0 );
|
|
free( data );
|
|
|
|
if( width ) {
|
|
*width = context.width;
|
|
}
|
|
|
|
if( height ) {
|
|
*height = context.height;
|
|
}
|
|
|
|
if( palcount) {
|
|
*palcount = context.palcount;
|
|
}
|
|
|
|
if( palette ) {
|
|
for( int i = 0; i < context.palcount * 3; ++i ) {
|
|
palette[ i ] = context.palette[ i ] >> 2;
|
|
}
|
|
}
|
|
return context.pixels;
|
|
}
|
|
|
|
|
|
void hline( int x, int y, int len, int color ) {
|
|
if( internals->screen.font ) return;
|
|
if( y < 0 || y >= internals->draw.height ) {
|
|
return;
|
|
}
|
|
if( x < 0 ) {
|
|
len += x; x = 0;
|
|
}
|
|
if( x + len > internals->draw.width ) {
|
|
len = internals->draw.width - x;
|
|
}
|
|
uint8_t* scr = internals->draw.buffer + y * internals->draw.width + x;
|
|
uint8_t* end = scr + len;
|
|
while( scr < end ) *scr++ = (uint8_t)color;
|
|
}
|
|
|
|
|
|
void line( int x1, int y1, int x2, int y2 ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
int dx = x2 - x1;
|
|
dx = dx < 0 ? -dx : dx;
|
|
int sx = x1 < x2 ? 1 : -1;
|
|
int dy = y2 - y1;
|
|
dy = dy < 0 ? -dy : dy;
|
|
int sy = y1 < y2 ? 1 : -1;
|
|
int err = ( dx > dy ? dx : -dy ) / 2;
|
|
int x = x1;
|
|
int y = y1;
|
|
while( x != x2 || y != y2 ) {
|
|
putpixel( x, y, color );
|
|
int e2 = err;
|
|
if( e2 > -dx ) {
|
|
err -= dy;
|
|
x += sx;
|
|
}
|
|
if( e2 < dy ) {
|
|
err += dx;
|
|
y += sy;
|
|
}
|
|
}
|
|
putpixel( x, y, color );
|
|
}
|
|
|
|
|
|
void rectangle( int x, int y, int w, int h ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
hline( x, y, w, color );
|
|
hline( x, y + h, w, color );
|
|
line( x, y, x, y + h );
|
|
line( x + w - 1, y, x + w - 1, y + h );
|
|
}
|
|
|
|
|
|
void bar( int x, int y, int w, int h ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
for( int i = y; i < y + h; ++i ) {
|
|
hline( x, i, w, color );
|
|
}
|
|
}
|
|
|
|
|
|
void circle( int x, int y, int r ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
int f = 1 - r;
|
|
int dx = 0;
|
|
int dy = -2 * r;
|
|
int ix = 0;
|
|
int iy = r;
|
|
|
|
putpixel( x, y + r, color );
|
|
putpixel( x, y - r, color );
|
|
putpixel( x + r, y, color );
|
|
putpixel( x - r, y, color );
|
|
|
|
while( ix < iy ) {
|
|
if( f >= 0 ) {
|
|
--iy;
|
|
dy += 2;
|
|
f += dy;
|
|
}
|
|
++ix;
|
|
dx += 2;
|
|
f += dx + 1;
|
|
|
|
putpixel( x + ix, y + iy, color );
|
|
putpixel( x - ix, y + iy, color );
|
|
putpixel( x + ix, y - iy, color );
|
|
putpixel( x - ix, y - iy, color );
|
|
putpixel( x + iy, y + ix, color );
|
|
putpixel( x - iy, y + ix, color );
|
|
putpixel( x + iy, y - ix, color );
|
|
putpixel( x - iy, y - ix, color );
|
|
}
|
|
}
|
|
|
|
|
|
void fillcircle( int x, int y, int r ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
int f = 1 - r;
|
|
int dx = 0;
|
|
int dy = -2 * r;
|
|
int ix = 0;
|
|
int iy = r;
|
|
|
|
while( ix <= iy ) {
|
|
hline( x - iy, y + ix, 2 * iy, color );
|
|
hline( x - iy, y - ix, 2 * iy, color );
|
|
if( f >= 0 ) {
|
|
hline( x - ix, y + iy, 2 * ix, color );
|
|
hline( x - ix, y - iy, 2 * ix, color );
|
|
|
|
--iy;
|
|
dy += 2;
|
|
f += dy;
|
|
}
|
|
++ix;
|
|
dx += 2;
|
|
f += dx + 1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void ellipse( int x, int y, int rx, int ry ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
int asq = rx * rx;
|
|
int bsq = ry * ry;
|
|
|
|
putpixel( x, y + ry, color );
|
|
putpixel( x, y - ry, color );
|
|
|
|
int wx = 0;
|
|
int wy = ry;
|
|
int xa = 0;
|
|
int ya = asq * 2 * ry;
|
|
int thresh = asq / 4 - asq * ry;
|
|
|
|
for( ; ; ) {
|
|
thresh += xa + bsq;
|
|
|
|
if( thresh >= 0 ) {
|
|
ya -= asq * 2;
|
|
thresh -= ya;
|
|
--wy;
|
|
}
|
|
|
|
xa += bsq * 2;
|
|
++wx;
|
|
|
|
if( xa >= ya ) {
|
|
break;
|
|
}
|
|
|
|
putpixel( x + wx, y - wy, color );
|
|
putpixel( x - wx, y - wy, color );
|
|
putpixel( x + wx, y + wy, color );
|
|
putpixel( x - wx, y + wy, color );
|
|
}
|
|
|
|
putpixel( x + rx, y, color );
|
|
putpixel( x - rx, y, color );
|
|
|
|
wx = rx;
|
|
wy = 0;
|
|
xa = bsq * 2 * rx;
|
|
|
|
ya = 0;
|
|
thresh = bsq / 4 - bsq * rx;
|
|
|
|
for( ; ; ) {
|
|
thresh += ya + asq;
|
|
|
|
if( thresh >= 0 ) {
|
|
xa -= bsq * 2;
|
|
thresh = thresh - xa;
|
|
--wx;
|
|
}
|
|
|
|
ya += asq * 2;
|
|
++wy;
|
|
|
|
if( ya > xa ) {
|
|
break;
|
|
}
|
|
|
|
putpixel( x + wx, y - wy, color );
|
|
putpixel( x - wx, y - wy, color );
|
|
putpixel( x + wx, y + wy, color );
|
|
putpixel( x - wx, y + wy, color );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void fillellipse( int x, int y, int rx, int ry ) {
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
int asq = rx * rx;
|
|
int bsq = ry * ry;
|
|
|
|
int wx = 0;
|
|
int wy = ry;
|
|
int xa = 0;
|
|
int ya = asq * 2 * ry;
|
|
int thresh = asq / 4 - asq * ry;
|
|
|
|
for( ; ; ) {
|
|
thresh += xa + bsq;
|
|
|
|
if( thresh >= 0 ) {
|
|
ya -= asq * 2;
|
|
thresh -= ya;
|
|
hline( x - wx, y - wy, wx * 2, color );
|
|
hline( x - wx, y + wy, wx * 2, color );
|
|
--wy;
|
|
}
|
|
|
|
xa += bsq * 2;
|
|
++wx;
|
|
if( xa >= ya ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
hline( x - rx, y, rx * 2, color );
|
|
|
|
wx = rx;
|
|
wy = 0;
|
|
xa = bsq * 2 * rx;
|
|
|
|
ya = 0;
|
|
thresh = bsq / 4 - bsq * rx;
|
|
|
|
for( ; ; ) {
|
|
thresh += ya + asq;
|
|
|
|
if( thresh >= 0 ) {
|
|
xa -= bsq * 2;
|
|
thresh = thresh - xa;
|
|
--wx;
|
|
}
|
|
|
|
ya += asq * 2;
|
|
++wy;
|
|
|
|
if( ya > xa ) {
|
|
break;
|
|
}
|
|
|
|
hline( x - wx, y - wy, wx * 2, color );
|
|
hline( x - wx, y + wy, wx * 2, color );
|
|
}
|
|
}
|
|
|
|
|
|
void drawpoly( int* points_xy, int count ) {
|
|
if( internals->screen.font ) return;
|
|
for( int i = 0; i < count - 1; ++i ) {
|
|
line( points_xy[ i * 2 + 0 ], points_xy[ i * 2 + 1 ],
|
|
points_xy[ ( i + 1 ) * 2 + 0 ], points_xy[ ( i + 1 ) * 2 + 1 ] );
|
|
}
|
|
}
|
|
|
|
|
|
void fillpoly( int* points_xy, int count ) {
|
|
#define MAX_POLYGON_POINTS 256
|
|
static int node_x[ MAX_POLYGON_POINTS ];
|
|
|
|
if( internals->screen.font ) return;
|
|
|
|
if( count <= 0 || count > MAX_POLYGON_POINTS ) {
|
|
return;
|
|
}
|
|
|
|
int color = internals->graphics.color;
|
|
|
|
int min_y = points_xy[ 0 + 1 ];
|
|
int max_y = min_y;
|
|
|
|
// find extents
|
|
for( int i = 1; i < count; ++i ) {
|
|
if( points_xy[ i * 2 + 1 ] < min_y ) min_y = points_xy[ i * 2 + 1 ];
|
|
if( points_xy[ i * 2 + 1 ] > max_y ) max_y = points_xy[ i * 2 + 1 ];
|
|
}
|
|
|
|
for( int y = min_y; y < max_y; ++y ) {
|
|
// find intersection points_xy
|
|
int nodes = 0;
|
|
int j = count - 1;
|
|
for( int i = 0; i < count; ++i ) {
|
|
if( ( points_xy[ i * 2 + 1 ] <= y && points_xy[ j * 2 + 1 ] > y ) ||
|
|
( points_xy[ j * 2 + 1 ] <= y && points_xy[ i * 2 + 1 ] > y ) ) {
|
|
|
|
int dx = points_xy[ j * 2 + 0 ] - points_xy[ i * 2 + 0 ];
|
|
int dy = points_xy[ j * 2 + 1 ] - points_xy[ i * 2 + 1 ];
|
|
node_x[ nodes++ ] = points_xy[ i * 2 + 0 ] + ( ( y - points_xy[ i * 2 + 1 ] ) * dx ) / dy ;
|
|
}
|
|
j = i;
|
|
}
|
|
|
|
// sort by x
|
|
int xi = 0;
|
|
while( xi < nodes - 1 ) {
|
|
if( node_x[ xi ] > node_x[ xi + 1 ] ) {
|
|
int swap = node_x[ xi ];
|
|
node_x[ xi ] = node_x[ xi + 1 ];
|
|
node_x[ xi + 1 ] = swap;
|
|
if( xi ) --xi;
|
|
} else {
|
|
++xi;
|
|
}
|
|
}
|
|
|
|
for( int i = 0; i < nodes; i += 2 ) {
|
|
hline( node_x[ i ], y, node_x[ i + 1 ] - node_x[ i ], color );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* A Seed Fill Algorithm
|
|
* by Paul Heckbert
|
|
* from "Graphics Gems", Academic Press, 1990
|
|
*
|
|
* user provides pixelread() and pixelwrite() routines
|
|
*
|
|
* fill.c : simple seed fill program
|
|
* Calls pixelread() to read pixels, pixelwrite() to write pixels.
|
|
*
|
|
* Paul Heckbert 13 Sept 1982, 28 Jan 1987
|
|
*
|
|
* fill: set the pixel at (x,y) and all of its 4-connected neighbors
|
|
* with the same pixel value to the new pixel value nv.
|
|
* A 4-connected neighbor is a pixel above, below, left, or right of a pixel.
|
|
*
|
|
* LICENSE
|
|
* The Graphics Gems code is copyright-protected. In other words, you cannot
|
|
* claim the text of the code as your own and resell it. Using the code is
|
|
* permitted in any program, product, or library, non-commercial or commercial.
|
|
* Giving credit is not required, though is a nice gesture. The code comes
|
|
* as-is, and if there are any flaws or problems with any Gems code, nobody
|
|
* involved with Gems - authors, editors, publishers, or webmasters - are to be
|
|
* held responsible. Basically, don't be a jerk, and remember that anything
|
|
* free comes with no guarantee.
|
|
*/
|
|
|
|
void floodfill( int x, int y ) {
|
|
#define FILLMAX 10000 /* FILLMAX depth of stack */
|
|
|
|
#define FILLPUSH( Y, XL, XR, DY ) /* FILLPUSH new segment on stack */ \
|
|
if( sp < stack + FILLMAX && Y + ( DY ) >= 0 && Y + ( DY ) < internals->draw.height ) \
|
|
{ sp->y = Y; sp->xl = XL; sp->xr = XR; sp->dy = DY; ++sp; }
|
|
|
|
#define FILLPOP( Y, XL, XR, DY ) /* FILLPOP segment off stack */ \
|
|
{ --sp; Y = sp->y + ( DY = sp->dy ); XL = sp->xl; XR = sp->xr; }
|
|
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
|
|
/*
|
|
* Filled horizontal segment of scanline y for xl<=x<=xr.
|
|
* Parent segment was on line y-dy. dy=1 or -1
|
|
*/
|
|
struct segment_t { int y, xl, xr, dy; };
|
|
|
|
int l, x1, x2, dy;
|
|
int ov; /* old pixel value */
|
|
struct segment_t stack[ FILLMAX ], *sp = stack; /* stack of filled segments */
|
|
|
|
ov = getpixel( x, y ); /* read pv at seed point */
|
|
if( ov == color || x < 0 || x >= internals->draw.width || y < 0 || y >= internals->draw.height ) return;
|
|
FILLPUSH( y, x, x, 1 ); /* needed in some cases */
|
|
FILLPUSH( y + 1, x, x, -1 ); /* seed segment (FILLPOPped 1st) */
|
|
|
|
while( sp > stack ) {
|
|
/* FILLPOP segment off stack and fill a neighboring scan line */
|
|
FILLPOP( y, x1, x2, dy );
|
|
/*
|
|
* segment of scan line y-dy for x1<=x<=x2 was previously filled,
|
|
* now explore adjacent pixels in scan line y
|
|
*/
|
|
int xs;
|
|
for( x = x1; x >= 0 && getpixel( x, y ) == ov; --x ) /* nothing */;
|
|
hline( x + 1, y, x1 - x, color );
|
|
if( x >= x1 ) goto skip;
|
|
l = x + 1;
|
|
if( l < x1 ) FILLPUSH( y, l, x1 - 1, -dy ); /* leak on left? */
|
|
x = x1 + 1;
|
|
do {
|
|
xs = x;
|
|
for( ; x < internals->draw.width && getpixel( x, y ) == ov; ++x ) /* nothing */;
|
|
hline( xs, y, x - xs, color );
|
|
FILLPUSH( y, l, x - 1, dy);
|
|
if( x > x2 + 1 ) FILLPUSH( y, x2 + 1, x - 1, -dy); /* leak on right? */
|
|
skip:
|
|
for( x++; x <= x2 && getpixel( x, y ) != ov; ++x );
|
|
l = x;
|
|
} while( x <= x2 );
|
|
}
|
|
|
|
#undef FILLMAX
|
|
}
|
|
|
|
|
|
void boundaryfill( int x, int y, int boundary ) {
|
|
#define FILLMAX 10000 /* FILLMAX depth of stack */
|
|
|
|
#define FILLPUSH( Y, XL, XR, DY ) /* FILLPUSH new segment on stack */ \
|
|
if( sp < stack + FILLMAX && Y + ( DY ) >= 0 && Y + ( DY ) < internals->draw.height ) \
|
|
{ sp->y = Y; sp->xl = XL; sp->xr = XR; sp->dy = DY; ++sp; }
|
|
|
|
#define FILLPOP( Y, XL, XR, DY ) /* FILLPOP segment off stack */ \
|
|
{ --sp; Y = sp->y + ( DY = sp->dy ); XL = sp->xl; XR = sp->xr; }
|
|
|
|
if( internals->screen.font ) return;
|
|
int color = internals->graphics.color;
|
|
|
|
/*
|
|
* Filled horizontal segment of scanline y for xl<=x<=xr.
|
|
* Parent segment was on line y-dy. dy=1 or -1
|
|
*/
|
|
struct segment_t { int y, xl, xr, dy; };
|
|
|
|
int l, x1, x2, dy;
|
|
int ov; /* old pixel value */
|
|
struct segment_t stack[ FILLMAX ], *sp = stack; /* stack of filled segments */
|
|
|
|
ov = boundary;
|
|
if( x < 0 || x >= internals->draw.width || y < 0 || y >= internals->draw.height ) return;
|
|
FILLPUSH( y, x, x, 1 ); /* needed in some cases */
|
|
FILLPUSH( y + 1, x, x, -1 ); /* seed segment (FILLPOPped 1st) */
|
|
|
|
while( sp > stack ) {
|
|
/* FILLPOP segment off stack and fill a neighboring scan line */
|
|
FILLPOP( y, x1, x2, dy );
|
|
/*
|
|
* segment of scan line y-dy for x1<=x<=x2 was previously filled,
|
|
* now explore adjacent pixels in scan line y
|
|
*/
|
|
int xs;
|
|
for( x = x1; x >= 0 && getpixel( x, y ) != ov; --x ) /* nothing */;
|
|
hline( x + 1, y, x1 - x, color );
|
|
if( x >= x1 ) goto skip;
|
|
l = x + 1;
|
|
if( l < x1 ) FILLPUSH( y, l, x1 - 1, -dy ); /* leak on left? */
|
|
x = x1 + 1;
|
|
do {
|
|
xs = x;
|
|
for( ; x < internals->draw.width && getpixel( x, y ) != ov; ++x ) /* nothing */;
|
|
hline( xs, y, x - xs, color );
|
|
FILLPUSH( y, l, x - 1, dy);
|
|
if( x > x2 + 1 ) FILLPUSH( y, x2 + 1, x - 1, -dy); /* leak on right? */
|
|
skip:
|
|
for( x++; x <= x2 && getpixel( x, y ) == ov; ++x );
|
|
l = x;
|
|
} while( x <= x2 );
|
|
}
|
|
|
|
#undef FILLMAX
|
|
}
|
|
|
|
|
|
void cputs( char const* string ) {
|
|
if( !internals->screen.font ) return;
|
|
|
|
while( *string ) {
|
|
if( internals->conio.y >= internals->screen.height ) break;
|
|
|
|
uint16_t ch = (uint16_t) (unsigned char) *string;
|
|
ch |= ( internals->conio.fg & 0xf ) << 8;
|
|
ch |= ( internals->conio.bg & 0xf ) << 12;
|
|
|
|
( (uint16_t*)internals->screen.buffer )[ internals->conio.x + internals->conio.y * internals->screen.width ] = ch;
|
|
|
|
++internals->conio.x;
|
|
if( internals->conio.x >= internals->screen.width ) {
|
|
if( internals->conio.y < internals->screen.height - 1 ) {
|
|
++internals->conio.y;
|
|
} else {
|
|
--internals->conio.x;
|
|
break;
|
|
}
|
|
internals->conio.x = 0;
|
|
}
|
|
|
|
++string;
|
|
}
|
|
}
|
|
|
|
|
|
void curson( void ) {
|
|
if( !internals->screen.font ) return;
|
|
internals->conio.curs = 1;
|
|
}
|
|
|
|
|
|
void cursoff( void ) {
|
|
if( !internals->screen.font ) return;
|
|
internals->conio.curs = 0;
|
|
}
|
|
|
|
|
|
void gotoxy( int x, int y ) {
|
|
if( !internals->screen.font ) return;
|
|
internals->conio.x = x;
|
|
internals->conio.y = y;
|
|
}
|
|
|
|
|
|
int wherex( void ) {
|
|
if( !internals->screen.font ) return 0;
|
|
return internals->conio.x;
|
|
}
|
|
|
|
|
|
int wherey( void ) {
|
|
if( !internals->screen.font ) return 0;
|
|
return internals->conio.y;
|
|
}
|
|
|
|
|
|
void textcolor( int color ) {
|
|
if( !internals->screen.font ) return;
|
|
internals->conio.fg = color < 0 ? 0 : color > 15 ? 15 : color;
|
|
}
|
|
|
|
|
|
void textbackground( int color ) {
|
|
if( !internals->screen.font ) return;
|
|
internals->conio.bg = color < 0 ? 0 : color > 15 ? 15 : color;
|
|
}
|
|
|
|
|
|
void clrscr( void ) {
|
|
if( !internals->screen.font ) return;
|
|
uint16_t* p = ( (uint16_t*)internals->screen.buffer );
|
|
uint16_t c = (uint16_t) ' ';
|
|
c |= ( internals->conio.fg & 0xf ) << 8;
|
|
c |= ( internals->conio.bg & 0xf ) << 12;
|
|
for( int y = 0; y < screenheight(); ++y ) {
|
|
for( int x = 0; x < screenwidth(); ++x ) {
|
|
*p++ = c;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int keystate( enum keycode_t key ) {
|
|
int index = (int) key;
|
|
if( index >= 0 && index < KEYCOUNT ) {
|
|
return internals->input.keystate[ index ];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
enum keycode_t* readkeys( void ) {
|
|
#ifdef __wasm__
|
|
if (internals->wasm.read_counts++ > 100) {
|
|
// In WebAssembly without real threads, if a dos-like application never calls waitvbl
|
|
// by itself, we force a call to it every 100 calls to readkeys/readchars
|
|
waitvbl();
|
|
}
|
|
#endif
|
|
thread_mutex_lock( &internals->mutex );
|
|
memset( internals->input.keybuffer, 0, sizeof( internals->input.keybuffer0 ) );
|
|
if( internals->input.keybuffer == internals->input.keybuffer0 ) {
|
|
internals->input.keybuffer = internals->input.keybuffer1;
|
|
} else {
|
|
internals->input.keybuffer = internals->input.keybuffer0;
|
|
}
|
|
thread_mutex_unlock( &internals->mutex );
|
|
return internals->input.keybuffer;
|
|
};
|
|
|
|
|
|
char const* readchars( void ) {
|
|
#ifdef __wasm__
|
|
if (internals->wasm.read_counts++ > 100) {
|
|
// In WebAssembly without real threads, if a dos-like application never calls waitvbl
|
|
// by itself, we force a call to it every 100 calls to readkeys/readchars
|
|
waitvbl();
|
|
}
|
|
#endif
|
|
thread_mutex_lock( &internals->mutex );
|
|
memset( internals->input.charbuffer, 0, sizeof( internals->input.charbuffer0 ) );
|
|
if( internals->input.charbuffer == internals->input.charbuffer0 ) {
|
|
internals->input.charbuffer = internals->input.charbuffer1;
|
|
} else {
|
|
internals->input.charbuffer = internals->input.charbuffer0;
|
|
}
|
|
thread_mutex_unlock( &internals->mutex );
|
|
return internals->input.charbuffer;
|
|
}
|
|
|
|
|
|
int mousex( void ) {
|
|
return internals->input.mouse_x;
|
|
}
|
|
|
|
|
|
int mousey( void ) {
|
|
return internals->input.mouse_y;
|
|
}
|
|
|
|
|
|
int mouserelx( void ) {
|
|
return internals->input.mouse_relx;
|
|
}
|
|
|
|
|
|
int mouserely( void ) {
|
|
return internals->input.mouse_rely;
|
|
}
|
|
|
|
|
|
void setsoundbank( int soundbank ) {
|
|
if( soundbank >= 1 && soundbank < internals->audio.soundbanks_count ) {
|
|
internals->audio.current_soundbank = soundbank;
|
|
}
|
|
}
|
|
|
|
|
|
int installusersoundbank( char const* filename ) {
|
|
if( internals->audio.soundbanks_count >= sizeof( internals->audio.soundbanks ) / sizeof( *internals->audio.soundbanks ) ) {
|
|
return 0;
|
|
}
|
|
char const* pext = strrchr( filename, '.' );
|
|
if( !pext || strlen( pext ) != 4 ) return 0;
|
|
char ext[4] = { 0 };
|
|
memcpy( ext, pext + 1, 3 );
|
|
ext[0]=(char)tolower(ext[0]);
|
|
ext[1]=(char)tolower(ext[1]);
|
|
ext[2]=(char)tolower(ext[2]);
|
|
enum soundbank_type_t type = SOUNDBANK_TYPE_NONE;
|
|
if( strcmp( ext, "sf2" ) == 0 ) {
|
|
type = SOUNDBANK_TYPE_SF2;
|
|
//} else if( strcmp( ext, "ibk" ) == 0 ) {
|
|
// type = SOUNDBANK_TYPE_IBK;
|
|
} else if( strcmp( ext, "op2" ) == 0 ) {
|
|
type = SOUNDBANK_TYPE_OP2;
|
|
}
|
|
if( type == SOUNDBANK_TYPE_NONE ) return 0;
|
|
|
|
FILE* fp = fopen( filename, "rb" );
|
|
if( !fp ) return 0;
|
|
fseek( fp, 0, SEEK_END );
|
|
size_t sz = ftell( fp );
|
|
fseek( fp, 0, SEEK_SET );
|
|
void* data = malloc( sz );
|
|
fread( data, 1, sz, fp );
|
|
fclose( fp );
|
|
|
|
internals->audio.soundbanks[ internals->audio.soundbanks_count ].type = type;
|
|
if( type == SOUNDBANK_TYPE_SF2 ) {
|
|
internals->audio.soundbanks[ internals->audio.soundbanks_count ].sf2 = tsf_load_memory( data, (int)sz );
|
|
internals->audio.soundbanks[ internals->audio.soundbanks_count ].data = NULL;
|
|
internals->audio.soundbanks[ internals->audio.soundbanks_count ].size = 0;
|
|
free( data );
|
|
} else {
|
|
internals->audio.soundbanks[ internals->audio.soundbanks_count ].sf2 = NULL;
|
|
internals->audio.soundbanks[ internals->audio.soundbanks_count ].data = data;
|
|
internals->audio.soundbanks[ internals->audio.soundbanks_count ].size = sz;
|
|
}
|
|
|
|
return internals->audio.soundbanks_count++;
|
|
}
|
|
|
|
|
|
static void load_default_sf2( void ) {
|
|
// Delay loading of built-in soundfont until first used
|
|
// This also allows the linker to not include the entire large sf2 file if this function is not used
|
|
if (!internals->audio.soundbanks[ DEFAULT_SOUNDBANK_AWE32 ].sf2) {
|
|
internals->audio.soundbanks[ DEFAULT_SOUNDBANK_AWE32 ].sf2 = tsf_load_memory( awe32rom, sizeof( awe32rom ) );
|
|
if (internals->audio.current_soundbank == DEFAULT_SOUNDBANK_NONE) {
|
|
setsoundbank(DEFAULT_SOUNDBANK_AWE32);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void noteon( int channel, int note, int velocity) {
|
|
load_default_sf2();
|
|
if( channel < 0 || channel > MUSIC_CHANNELS || note < 0 || note > 127 || velocity < 0 || velocity > 127 ) return;
|
|
struct audio_command_t command;
|
|
command.type = AUDIO_COMMAND_NOTE_ON;
|
|
command.channel = channel;
|
|
command.note = note;
|
|
command.velocity = velocity;
|
|
|
|
thread_mutex_lock( &internals->mutex );
|
|
command.frame_stamp = internals->audio.frame_stamp;
|
|
if( internals->audio.commands_count < sizeof( internals->audio.commands ) / sizeof( *internals->audio.commands ) ) {
|
|
internals->audio.commands[ internals->audio.commands_count++ ] = command;
|
|
}
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
void noteoff( int channel, int note ) {
|
|
load_default_sf2();
|
|
if( channel < 0 || channel > MUSIC_CHANNELS || note < 0 || note > 127 ) return;
|
|
struct audio_command_t command;
|
|
command.type = AUDIO_COMMAND_NOTE_OFF;
|
|
command.channel = channel;
|
|
command.note = note;
|
|
|
|
thread_mutex_lock( &internals->mutex );
|
|
command.frame_stamp = internals->audio.frame_stamp;
|
|
if( internals->audio.commands_count < sizeof( internals->audio.commands ) / sizeof( *internals->audio.commands ) ) {
|
|
internals->audio.commands[ internals->audio.commands_count++ ] = command;
|
|
}
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
void allnotesoff( int channel ) {
|
|
load_default_sf2();
|
|
if( channel < 0 || channel > MUSIC_CHANNELS ) return;
|
|
struct audio_command_t command;
|
|
command.type = AUDIO_COMMAND_NOTE_OFF_ALL;
|
|
command.channel = channel;
|
|
|
|
thread_mutex_lock( &internals->mutex );
|
|
command.frame_stamp = internals->audio.frame_stamp;
|
|
if( internals->audio.commands_count < sizeof( internals->audio.commands ) / sizeof( *internals->audio.commands ) ) {
|
|
internals->audio.commands[ internals->audio.commands_count++ ] = command;
|
|
}
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
void setinstrument( int channel, int instrument ) {
|
|
load_default_sf2();
|
|
if( channel < 0 || channel > MUSIC_CHANNELS || instrument < 0 || instrument > 128 ) return;
|
|
struct audio_command_t command;
|
|
command.type = AUDIO_COMMAND_SET_INSTRUMENT;
|
|
command.channel = channel;
|
|
command.instrument = instrument;
|
|
|
|
thread_mutex_lock( &internals->mutex );
|
|
command.frame_stamp = internals->audio.frame_stamp;
|
|
if( internals->audio.commands_count < sizeof( internals->audio.commands ) / sizeof( *internals->audio.commands ) ) {
|
|
internals->audio.commands[ internals->audio.commands_count++ ] = command;
|
|
}
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
enum music_format_t {
|
|
MUSIC_FORMAT_MID,
|
|
MUSIC_FORMAT_MUS,
|
|
MUSIC_FORMAT_MOD,
|
|
MUSIC_FORMAT_OPB,
|
|
};
|
|
|
|
|
|
struct music_t {
|
|
enum music_format_t format;
|
|
};
|
|
|
|
|
|
void* tml_mus_custom_malloc( size_t size ) {
|
|
struct music_t* data = (struct music_t*) malloc( size + sizeof( struct music_t ) );
|
|
return (void*)( data + 1);
|
|
}
|
|
|
|
|
|
void* tml_mus_custom_realloc( void* ptr, size_t size ) {
|
|
ptr = ptr ? (void*)( ( (struct music_t*)ptr ) - 1 ) : NULL;
|
|
struct music_t* data = (struct music_t*) realloc( ptr, size + sizeof( struct music_t ) );
|
|
return (void*)( data + 1);
|
|
}
|
|
|
|
|
|
void tml_mus_custom_free( void* ptr ) {
|
|
ptr = ptr ? (void*)( ( (struct music_t*)ptr ) - 1 ) : NULL;
|
|
free( ptr );
|
|
}
|
|
|
|
|
|
struct music_t* loadmid( char const* filename ) {
|
|
load_default_sf2();
|
|
tml_message* mid = tml_load_filename( filename );
|
|
if( !mid ) return NULL;
|
|
struct music_t* music = ( (struct music_t*)mid ) - 1;
|
|
music->format = MUSIC_FORMAT_MID;
|
|
return music;
|
|
}
|
|
|
|
|
|
struct music_t* loadmus( char const* filename ) {
|
|
load_default_sf2();
|
|
FILE* fp = fopen( filename, "rb" );
|
|
if( !fp ) return NULL;
|
|
fseek( fp, 0, SEEK_END );
|
|
size_t sz = ftell( fp );
|
|
fseek( fp, 0, SEEK_SET );
|
|
uint8_t* data = (uint8_t*) malloc( sz );
|
|
fread( data, 1, sz, fp );
|
|
fclose( fp );
|
|
if( !data ) return NULL;
|
|
mus_t* mus = mus_create( data, sz, NULL );
|
|
free( data );
|
|
if( !mus ) return NULL;
|
|
struct music_t* music = ( (struct music_t*)mus ) - 1;
|
|
music->format = MUSIC_FORMAT_MUS;
|
|
return music;
|
|
}
|
|
|
|
|
|
struct music_t* createmus( void* data, int size ) {
|
|
load_default_sf2();
|
|
mus_t* mus = mus_create( data, size, NULL );
|
|
if( !mus ) return NULL;
|
|
struct music_t* music = ( (struct music_t*)mus ) - 1;
|
|
music->format = MUSIC_FORMAT_MUS;
|
|
return music;
|
|
}
|
|
|
|
|
|
|
|
struct music_t* loadmod( char const* filename ) {
|
|
|
|
FILE* fp = fopen( filename, "rb" );
|
|
if( !fp ) return NULL;
|
|
fseek( fp, 0, SEEK_END );
|
|
size_t sz = ftell( fp );
|
|
fseek( fp, 0, SEEK_SET );
|
|
uint8_t* data = (uint8_t*) malloc( sz + sizeof( struct music_t ) + sizeof( jar_mod_context_t ) );
|
|
uint8_t* file = data + sizeof( struct music_t ) + sizeof( jar_mod_context_t );
|
|
fread( file, 1, sz, fp );
|
|
fclose( fp );
|
|
if( !data ) return NULL;
|
|
struct music_t* music = (struct music_t*)data;
|
|
music->format = MUSIC_FORMAT_MOD;
|
|
jar_mod_context_t* modctx = (jar_mod_context_t*)(music + 1);
|
|
if( !jar_mod_init( modctx ) || !jar_mod_load( modctx, (void*)file, (int)sz ) ) {
|
|
free( data );
|
|
return NULL;
|
|
}
|
|
modctx->modfile = (muchar*)file;
|
|
modctx->modfilesize = (int) sz;
|
|
return music;
|
|
}
|
|
|
|
|
|
struct opb_context_t {
|
|
OPB_Command* commands;
|
|
int capacity;
|
|
int count;
|
|
};
|
|
|
|
|
|
int opb_callback( OPB_Command* commands, size_t count, void* user_data ) {
|
|
struct opb_context_t* context = (struct opb_context_t*) user_data;
|
|
if( context->count + (int)count > context->capacity ) {
|
|
context->capacity = context->count + (int)count > context->capacity * 2 ? context->count + (int)count : context->capacity * 2;
|
|
context->commands = (OPB_Command*) realloc( context->commands, sizeof( OPB_Command ) * context->capacity );
|
|
}
|
|
memcpy( context->commands + context->count, commands, sizeof( OPB_Command ) * count );
|
|
context->count += (int)count;
|
|
return 0;
|
|
}
|
|
|
|
|
|
struct opb_t {
|
|
int position;
|
|
double accumulated_time;
|
|
int commands_count;
|
|
OPB_Command commands[ 1 ];
|
|
};
|
|
|
|
|
|
struct music_t* loadopb( char const* filename ) {
|
|
struct opb_context_t context;
|
|
context.count = 0;
|
|
context.capacity = 4096;
|
|
context.commands = (OPB_Command*) malloc( sizeof( OPB_Command ) * context.capacity );
|
|
|
|
int res = OPB_FileToOpl( filename, opb_callback, &context );
|
|
|
|
if( res != 0 || context.count == 0) {
|
|
free( context.commands );
|
|
return NULL;
|
|
}
|
|
|
|
struct music_t* music = (struct music_t*)malloc( sizeof( struct music_t ) + sizeof( struct opb_t ) + sizeof( OPB_Command ) * ( context.count - 1 ) );
|
|
struct opb_t* opb = (struct opb_t*)( music + 1 );
|
|
music->format = MUSIC_FORMAT_OPB;
|
|
opb->position = 0;
|
|
opb->accumulated_time = 0.0;
|
|
opb->commands_count = context.count;
|
|
memcpy( opb->commands, context.commands, sizeof( OPB_Command ) * context.count );
|
|
free( context.commands );
|
|
return music;
|
|
}
|
|
|
|
|
|
void playmusic( struct music_t* music, int loop, int volume ) {
|
|
if( !music ) return;
|
|
if( volume < 0 ) volume = 0;
|
|
if( volume > 255 ) volume = 255;
|
|
thread_mutex_lock( &internals->mutex );
|
|
internals->audio.current_music = music;
|
|
internals->audio.loop_music = loop;
|
|
internals->audio.music_volume = volume;
|
|
internals->audio.music_play_counter++;
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
void stopmusic( void ) {
|
|
thread_mutex_lock( &internals->mutex );
|
|
internals->audio.current_music = NULL;
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
int musicplaying( void ) {
|
|
return internals->audio.current_music != NULL;
|
|
}
|
|
|
|
|
|
void musicvolume( int volume ) {
|
|
if( volume < 0 ) volume = 0;
|
|
if( volume > 255 ) volume = 255;
|
|
thread_mutex_lock( &internals->mutex );
|
|
internals->audio.music_volume = volume;
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
void setsoundmode( enum soundmode_t mode ) {
|
|
thread_mutex_lock( &internals->mutex );
|
|
internals->audio.soundmode = mode;
|
|
thread_mutex_unlock( &internals->mutex );
|
|
};
|
|
|
|
|
|
struct sound_t {
|
|
int channels;
|
|
int samplerate;
|
|
int framecount;
|
|
};
|
|
|
|
void* wav_custom_malloc( size_t size ) {
|
|
struct sound_t* data = (struct sound_t*) malloc( size + sizeof( struct sound_t ) );
|
|
return (void*)( data + 1);
|
|
}
|
|
|
|
|
|
void* wav_custom_realloc( void* ptr, size_t size ) {
|
|
ptr = ptr ? (void*)( ( (struct sound_t*)ptr ) - 1 ) : NULL;
|
|
struct sound_t* data = (struct sound_t*) realloc( ptr, size + sizeof( struct sound_t ) );
|
|
return (void*)( data + 1);
|
|
}
|
|
|
|
|
|
void wav_custom_free( void* ptr ) {
|
|
ptr = ptr ? (void*)( ( (struct sound_t*)ptr ) - 1 ) : NULL;
|
|
free( ptr );
|
|
}
|
|
|
|
|
|
struct sound_t* loadwav( char const* filename ) {
|
|
unsigned int channels = 0;
|
|
unsigned int samplerate = 0;
|
|
drwav_uint64 framecount = 0;
|
|
drwav_int16* samples = drwav_open_file_and_read_pcm_frames_s16( filename, &channels, &samplerate, &framecount, NULL);
|
|
if( samples == NULL || channels <= 0 || channels > 2 || samplerate < 4000 || samplerate > 48000 || framecount == 0 || framecount >= 0x7fffffff ) {
|
|
if( samples ) {
|
|
drwav_free( samples, NULL );
|
|
}
|
|
return NULL;
|
|
}
|
|
struct sound_t* sound = ( (struct sound_t*)samples ) - 1;
|
|
sound->channels = (int)channels;
|
|
sound->samplerate = (int)samplerate;
|
|
sound->framecount = (int)framecount;
|
|
return sound;
|
|
}
|
|
|
|
|
|
struct sound_t* createsound( int channels, int samplerate, int framecount, short* samples ) {
|
|
if( channels < 1 || channels > 2 || samplerate < 1000 || samplerate > 44100 || framecount <= 0 || !samples ) {
|
|
return NULL;
|
|
}
|
|
struct sound_t* sound = (struct sound_t*) malloc( sizeof( struct sound_t ) + framecount * channels * sizeof( short ) );
|
|
memcpy( sound + 1, samples, framecount * channels * sizeof( short ) );
|
|
sound->channels = channels;
|
|
sound->samplerate = samplerate;
|
|
sound->framecount = framecount;
|
|
return sound;
|
|
}
|
|
|
|
|
|
void playsound( int channel, struct sound_t* sound, int loop, int volume ) {
|
|
if( channel < 0 || channel >= SOUND_CHANNELS ) return;
|
|
if( !sound ) return;
|
|
if( volume < 0 ) volume = 0;
|
|
if( volume > 255 ) volume = 255;
|
|
thread_mutex_lock( &internals->mutex );
|
|
internals->audio.channels[ channel ].sound = sound;
|
|
internals->audio.channels[ channel ].loop = loop;
|
|
internals->audio.channels[ channel ].volume_left = volume;
|
|
internals->audio.channels[ channel ].volume_right = volume;
|
|
internals->audio.channels[ channel ].play_counter++;
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
void stopsound( int channel ) {
|
|
if( channel < 0 || channel >= SOUND_CHANNELS ) return;
|
|
thread_mutex_lock( &internals->mutex );
|
|
internals->audio.channels[ channel ].sound = NULL;
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
int soundplaying( int channel ) {
|
|
if( channel < 0 || channel >= SOUND_CHANNELS ) return 0;
|
|
return internals->audio.channels[ channel ].sound != NULL;
|
|
}
|
|
|
|
|
|
void soundvolume( int channel, int left, int right ) {
|
|
if( channel < 0 || channel >= SOUND_CHANNELS ) return;
|
|
if( left < 0 ) left = 0;
|
|
if( left > 255 ) left = 255;
|
|
if( right < 0 ) right = 0;
|
|
if( right > 255 ) right = 255;
|
|
thread_mutex_lock( &internals->mutex );
|
|
internals->audio.channels[ channel ].volume_left = left;
|
|
internals->audio.channels[ channel ].volume_right = right;
|
|
thread_mutex_unlock( &internals->mutex );
|
|
}
|
|
|
|
|
|
struct app_context_t {
|
|
int argc;
|
|
char** argv;
|
|
};
|
|
|
|
|
|
struct user_thread_context_t {
|
|
struct app_context_t* app_context;
|
|
int sound_buffer_size;
|
|
thread_signal_t user_thread_initialized;
|
|
thread_atomic_int_t user_thread_finished;
|
|
thread_signal_t app_loop_finished;
|
|
thread_signal_t user_thread_terminated;
|
|
};
|
|
|
|
|
|
int dosmain( int argc, char* argv[] );
|
|
|
|
#ifndef __wasm__
|
|
static
|
|
#else
|
|
WA_EXPORT(user_thread_proc)
|
|
#endif
|
|
int user_thread_proc( void* user_data ) {
|
|
struct user_thread_context_t* context = (struct user_thread_context_t*) user_data;
|
|
|
|
internals_create( context->sound_buffer_size );
|
|
|
|
thread_signal_raise( &context->user_thread_initialized );
|
|
|
|
waitvbl();
|
|
|
|
int result = dosmain( context->app_context->argc, context->app_context->argv );
|
|
|
|
thread_atomic_int_store( &context->user_thread_finished, 1 );
|
|
thread_signal_wait( &context->app_loop_finished, 5000 );
|
|
#ifdef __wasm__
|
|
WaCoroSwitch(0);
|
|
#endif
|
|
internals_destroy();
|
|
|
|
thread_signal_raise( &context->user_thread_terminated );
|
|
#ifdef __wasm__
|
|
WaCoroSwitch(0);
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
void initsoundmode( enum soundmode_t mode, int* freq, bool* is8bit, bool* mono ) {
|
|
switch( mode ) {
|
|
case soundmode_8bit_mono_5000:
|
|
*freq = 5000;
|
|
*is8bit = true;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_8bit_mono_8000:
|
|
*freq = 8000;
|
|
*is8bit = true;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_8bit_mono_11025:
|
|
*freq = 11025;
|
|
*is8bit = true;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_8bit_mono_16000:
|
|
*freq = 16000;
|
|
*is8bit = true;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_8bit_mono_22050:
|
|
*freq = 22050;
|
|
*is8bit = true;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_8bit_mono_32000:
|
|
*freq = 32000;
|
|
*is8bit = true;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_8bit_mono_44100:
|
|
*freq = 44100;
|
|
*is8bit = true;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_16bit_mono_5000:
|
|
*freq = 5000;
|
|
*is8bit = false;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_16bit_mono_8000:
|
|
*freq = 8000;
|
|
*is8bit = false;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_16bit_mono_11025:
|
|
*freq = 11025;
|
|
*is8bit = false;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_16bit_mono_16000:
|
|
*freq = 16000;
|
|
*is8bit = false;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_16bit_mono_22050:
|
|
*freq = 22050;
|
|
*is8bit = false;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_16bit_mono_32000:
|
|
*freq = 32000;
|
|
*is8bit = false;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_16bit_mono_44100:
|
|
*freq = 44100;
|
|
*is8bit = false;
|
|
*mono = true;
|
|
break;
|
|
case soundmode_8bit_stereo_5000:
|
|
*freq = 5000;
|
|
*is8bit = true;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_8bit_stereo_8000:
|
|
*freq = 8000;
|
|
*is8bit = true;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_8bit_stereo_11025:
|
|
*freq = 11025;
|
|
*is8bit = true;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_8bit_stereo_16000:
|
|
*freq = 16000;
|
|
*is8bit = true;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_8bit_stereo_22050:
|
|
*freq = 220050;
|
|
*is8bit = true;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_8bit_stereo_32000:
|
|
*freq = 32000;
|
|
*is8bit = true;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_8bit_stereo_44100:
|
|
*freq = 44100;
|
|
*is8bit = true;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_16bit_stereo_5000:
|
|
*freq = 5000;
|
|
*is8bit = false;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_16bit_stereo_8000:
|
|
*freq = 8000;
|
|
*is8bit = false;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_16bit_stereo_11025:
|
|
*freq = 11025;
|
|
*is8bit = false;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_16bit_stereo_16000:
|
|
*freq = 16000;
|
|
*is8bit = false;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_16bit_stereo_22050:
|
|
*freq = 22050;
|
|
*is8bit = false;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_16bit_stereo_32000:
|
|
*freq = 32000;
|
|
*is8bit = false;
|
|
*mono = false;
|
|
break;
|
|
case soundmode_16bit_stereo_44100:
|
|
*freq = 44100;
|
|
*is8bit = false;
|
|
*mono = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
tml_message* render_mid_tsf( struct music_t* mid, tml_message* next, double* msec, int loop, APP_S16* sample_pairs, int sample_pairs_count, tsf* soundfont ) {
|
|
int SampleBlock, SampleCount = sample_pairs_count;
|
|
for (SampleBlock = 64; SampleCount; SampleCount -= SampleBlock, sample_pairs += (SampleBlock *2)) {
|
|
if (SampleBlock > SampleCount) SampleBlock = SampleCount;
|
|
|
|
for ((*msec) += SampleBlock * (1000.0 / 44100.0); next && (*msec) >= next->time; next = next->next) {
|
|
switch (next->type) {
|
|
case TML_PROGRAM_CHANGE:
|
|
tsf_channel_set_presetnumber(soundfont, next->channel, next->program, (next->channel == 9));
|
|
break;
|
|
case TML_NOTE_ON:
|
|
tsf_channel_note_on(soundfont, next->channel, next->key, next->velocity / 127.0f);
|
|
break;
|
|
case TML_NOTE_OFF:
|
|
tsf_channel_note_off(soundfont, next->channel, next->key);
|
|
break;
|
|
case TML_PITCH_BEND:
|
|
tsf_channel_set_pitchwheel(soundfont, next->channel, next->pitch_bend);
|
|
break;
|
|
case TML_CONTROL_CHANGE:
|
|
tsf_channel_midi_control(soundfont, next->channel, next->control, next->control_value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
tsf_render_short( soundfont, sample_pairs, SampleBlock, 1 );
|
|
if( next == NULL ) {
|
|
if( loop ) {
|
|
next = (tml_message*)( mid + 1 );
|
|
(*msec) = 0.0;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
return next;
|
|
}
|
|
|
|
|
|
tml_message* render_mid_opl( struct music_t* mid, tml_message* next, double* msec, int loop, APP_S16* sample_pairs, int sample_pairs_count, opl_t* opl ) {
|
|
int SampleBlock, SampleCount = sample_pairs_count;
|
|
for (SampleBlock = 64; SampleCount; SampleCount -= SampleBlock, sample_pairs += (SampleBlock *2)) {
|
|
if (SampleBlock > SampleCount) SampleBlock = SampleCount;
|
|
|
|
for ((*msec) += SampleBlock * (1000.0 / 44100.0); next && (*msec) >= next->time; next = next->next) {
|
|
switch (next->type) {
|
|
case TML_PROGRAM_CHANGE: //channel program (preset) change (special handling for 10th MIDI channel with drums)
|
|
opl_midi_changeprog( opl, next->channel, next->program );
|
|
break;
|
|
case TML_NOTE_ON: //play a note
|
|
opl_midi_noteon(opl, next->channel, next->key, next->velocity);
|
|
break;
|
|
case TML_NOTE_OFF: //stop a note
|
|
opl_midi_noteoff(opl, next->channel, next->key);
|
|
break;
|
|
case TML_PITCH_BEND: //pitch wheel modification
|
|
opl_midi_pitchwheel(opl, next->channel, ( next->pitch_bend - 8192 ) / 64 );
|
|
break;
|
|
case TML_CONTROL_CHANGE: //MIDI controller messages
|
|
opl_midi_controller( opl, next->channel, next->control, next->control_value );
|
|
break;
|
|
}
|
|
}
|
|
|
|
opl_render( opl, sample_pairs, SampleBlock, internals->audio.music_volume / 255.0f );
|
|
if( next == NULL ) {
|
|
if( loop ) {
|
|
next = (tml_message*)( mid + 1 );
|
|
(*msec) = 0.0;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
return next;
|
|
}
|
|
|
|
|
|
int render_mus_tsf( mus_t* mus, int left_over, int loop, APP_S16* sample_pairs, int sample_pairs_count, tsf* soundfont ) {
|
|
int left_over_from_previous = left_over;
|
|
int remaining = sample_pairs_count;
|
|
APP_S16* output = sample_pairs;
|
|
left_over = 0;
|
|
if( left_over_from_previous ) {
|
|
int count = left_over_from_previous ;
|
|
if( count > remaining ) {
|
|
left_over = count - remaining;
|
|
count = remaining;
|
|
}
|
|
tsf_render_short( soundfont, output, count, 1 );
|
|
remaining -= count;
|
|
output += count * 2;
|
|
}
|
|
if( left_over ) {
|
|
return left_over;
|
|
}
|
|
|
|
while( remaining ) {
|
|
mus_event_t event;
|
|
mus_next_event( mus, &event );
|
|
switch( event.cmd ) {
|
|
case MUS_CMD_RELEASE_NOTE: {
|
|
tsf_channel_note_off( soundfont, event.channel, event.data.release_note.note );
|
|
} break;
|
|
case MUS_CMD_PLAY_NOTE: {
|
|
tsf_channel_note_on( soundfont, event.channel, event.data.play_note.note, event.data.play_note.volume / 127.0f );
|
|
} break;
|
|
case MUS_CMD_PITCH_BEND: {
|
|
int pitch_bend = ( event.data.pitch_bend.bend_amount - 128 ) * 64 + 8192;
|
|
tsf_channel_set_pitchwheel( soundfont, event.channel, pitch_bend );
|
|
} break;
|
|
case MUS_CMD_SYSTEM_EVENT: {
|
|
switch( event.data.system_event.event ) {
|
|
case MUS_SYSTEM_EVENT_ALL_SOUNDS_OFF: {
|
|
tsf_channel_sounds_off_all( soundfont, event.channel );
|
|
} break;
|
|
case MUS_SYSTEM_EVENT_ALL_NOTES_OFF: {
|
|
tsf_channel_note_off_all( soundfont, event.channel );
|
|
} break;
|
|
case MUS_SYSTEM_EVENT_MONO: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_SYSTEM_EVENT_POLY: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_SYSTEM_EVENT_RESET_ALL_CONTROLLERS: {
|
|
tsf_channel_midi_control( soundfont, event.channel, 121, 0 );
|
|
} break;
|
|
}
|
|
} break;
|
|
case MUS_CMD_CONTROLLER: {
|
|
int value = event.data.controller.value;
|
|
switch( event.data.controller.controller ) {
|
|
case MUS_CONTROLLER_CHANGE_INSTRUMENT: {
|
|
if( event.channel == 15 ) {
|
|
tsf_channel_set_presetnumber( soundfont, 15, 0, 1 );
|
|
} else {
|
|
tsf_channel_set_presetnumber( soundfont, event.channel, value, 0 );
|
|
}
|
|
} break;
|
|
case MUS_CONTROLLER_BANK_SELECT: {
|
|
tsf_channel_set_bank( soundfont, event.channel, value );
|
|
} break;
|
|
case MUS_CONTROLLER_MODULATION: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_VOLUME: {
|
|
tsf_channel_midi_control( soundfont, event.channel, 7, value );
|
|
} break;
|
|
case MUS_CONTROLLER_PAN: {
|
|
tsf_channel_midi_control( soundfont, event.channel, 10, value );
|
|
} break;
|
|
case MUS_CONTROLLER_EXPRESSION: {
|
|
tsf_channel_midi_control( soundfont, event.channel, 11, value );
|
|
} break;
|
|
case MUS_CONTROLLER_REVERB_DEPTH: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_CHORUS_DEPTH: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_SUSTAIN_PEDAL: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_SOFT_PEDAL: {
|
|
// Not supported
|
|
} break;
|
|
}
|
|
} break;
|
|
case MUS_CMD_END_OF_MEASURE: {
|
|
// Not used
|
|
} break;
|
|
case MUS_CMD_FINISH: {
|
|
if( loop ) {
|
|
mus_restart( mus );
|
|
} else {
|
|
memset( output, 0, remaining * 2 * sizeof( *output ) );
|
|
return -1;
|
|
}
|
|
} break;
|
|
case MUS_CMD_RENDER_SAMPLES: {
|
|
int count = event.data.render_samples.samples_count;
|
|
if( count > remaining ) {
|
|
left_over = count - remaining;
|
|
count = remaining;
|
|
}
|
|
tsf_render_short( soundfont, output, count, 1 );
|
|
remaining -= count;
|
|
output += count * 2;
|
|
} break;
|
|
}
|
|
}
|
|
return left_over;
|
|
}
|
|
|
|
|
|
int render_mus_opl( mus_t* mus, int left_over, int loop, APP_S16* sample_pairs, int sample_pairs_count, opl_t* opl ) {
|
|
int left_over_from_previous = left_over;
|
|
int remaining = sample_pairs_count;
|
|
APP_S16* output = sample_pairs;
|
|
left_over = 0;
|
|
if( left_over_from_previous ) {
|
|
int count = left_over_from_previous ;
|
|
if( count > remaining ) {
|
|
left_over = count - remaining;
|
|
count = remaining;
|
|
}
|
|
opl_render( opl, output, count, internals->audio.music_volume / 255.0f );
|
|
remaining -= count;
|
|
output += count * 2;
|
|
}
|
|
if( left_over ) {
|
|
return left_over;
|
|
}
|
|
|
|
while( remaining ) {
|
|
mus_event_t event;
|
|
mus_next_event( mus, &event );
|
|
if( event.channel == 15 ) {
|
|
event.channel = 9;
|
|
} else if( event.channel == 9 ) {
|
|
event.channel = 15;
|
|
}
|
|
switch( event.cmd ) {
|
|
case MUS_CMD_RELEASE_NOTE: {
|
|
opl_midi_noteoff( opl, event.channel, event.data.release_note.note );
|
|
} break;
|
|
case MUS_CMD_PLAY_NOTE: {
|
|
opl_midi_noteon( opl, event.channel, event.data.play_note.note, event.data.play_note.volume );
|
|
} break;
|
|
case MUS_CMD_PITCH_BEND: {
|
|
opl_midi_pitchwheel( opl, event.channel, event.data.pitch_bend.bend_amount - 0x80 );
|
|
} break;
|
|
case MUS_CMD_SYSTEM_EVENT: {
|
|
switch( event.data.system_event.event ) {
|
|
case MUS_SYSTEM_EVENT_ALL_SOUNDS_OFF: {
|
|
opl_midi_controller( opl, event.channel, 120, 0 );
|
|
} break;
|
|
case MUS_SYSTEM_EVENT_ALL_NOTES_OFF: {
|
|
opl_midi_controller( opl, event.channel, 123, 0 );
|
|
} break;
|
|
case MUS_SYSTEM_EVENT_MONO: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_SYSTEM_EVENT_POLY: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_SYSTEM_EVENT_RESET_ALL_CONTROLLERS: {
|
|
// Not supported
|
|
} break;
|
|
}
|
|
} break;
|
|
case MUS_CMD_CONTROLLER: {
|
|
int value = event.data.controller.value;
|
|
switch( event.data.controller.controller ) {
|
|
case MUS_CONTROLLER_CHANGE_INSTRUMENT: {
|
|
opl_midi_changeprog( opl, event.channel, value );
|
|
} break;
|
|
case MUS_CONTROLLER_BANK_SELECT: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_MODULATION: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_VOLUME: {
|
|
opl_midi_controller( opl, event.channel, 11, value );
|
|
} break;
|
|
case MUS_CONTROLLER_PAN: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_EXPRESSION: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_REVERB_DEPTH: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_CHORUS_DEPTH: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_SUSTAIN_PEDAL: {
|
|
// Not supported
|
|
} break;
|
|
case MUS_CONTROLLER_SOFT_PEDAL: {
|
|
// Not supported
|
|
} break;
|
|
}
|
|
} break;
|
|
case MUS_CMD_END_OF_MEASURE: {
|
|
// Not used
|
|
} break;
|
|
case MUS_CMD_FINISH: {
|
|
if( loop ) {
|
|
mus_restart( mus );
|
|
} else {
|
|
memset( output, 0, remaining * 2 * sizeof( *output ) );
|
|
return -1;
|
|
}
|
|
} break;
|
|
case MUS_CMD_RENDER_SAMPLES: {
|
|
int count = event.data.render_samples.samples_count;
|
|
if( count > remaining ) {
|
|
left_over = count - remaining;
|
|
count = remaining;
|
|
}
|
|
opl_render( opl, output, count, internals->audio.music_volume / 255.0f );
|
|
remaining -= count;
|
|
output += count * 2;
|
|
} break;
|
|
}
|
|
}
|
|
return left_over;
|
|
}
|
|
|
|
|
|
float mix_sound_channel( struct sound_t* sound, bool loop, float volume_left, float volume_right, float position, float* sample_pairs, int sample_pairs_count ) {
|
|
float ratio = sound->samplerate / 44100.0f;
|
|
int16_t* samples = (int16_t*)( sound + 1 );
|
|
if( sound->channels == 2 ) {
|
|
for( int i = 0; i < sample_pairs_count; ++i ) {
|
|
int p = (int) position;
|
|
if( p >= sound->framecount && loop ) {
|
|
position = 0.0f;
|
|
p = 0;
|
|
}
|
|
float s0 = p < sound->framecount ? samples[ p * 2 + 0 ] / 32768.0f : 0.0f;
|
|
float s1 = p < sound->framecount ? samples[ p * 2 + 1 ] / 32768.0f : 0.0f;
|
|
sample_pairs[ i * 2 + 0 ] += s0 * volume_left;
|
|
sample_pairs[ i * 2 + 1 ] += s1 * volume_right;
|
|
position += ratio;
|
|
}
|
|
} else {
|
|
for( int i = 0; i < sample_pairs_count; ++i ) {
|
|
int p = (int) position;
|
|
if( p >= sound->framecount && loop ) {
|
|
position = 0.0f;
|
|
p = 0;
|
|
}
|
|
float s = p < sound->framecount ? samples[ p + 0 ] / 32768.0f : 0.0f;
|
|
sample_pairs[ i * 2 + 0 ] += s * volume_left;
|
|
sample_pairs[ i * 2 + 1 ] += s * volume_right;
|
|
position += ratio;
|
|
}
|
|
}
|
|
return position;
|
|
}
|
|
|
|
|
|
float soft_clipping( float s ) {
|
|
return s < -1.0f ? -2.0f / 3.0f : s > 1.0f ? 2.0f / 3.0f : s - ( s * s * s ) / 3.0f;
|
|
}
|
|
|
|
|
|
#define SOUND_BUFFER_SIZE ( 735 * 3 ) /* Three frames worth of sound buffering */
|
|
|
|
struct sound_context_t {
|
|
thread_mutex_t mutex;
|
|
tsf* soundfont;
|
|
opl_t* opl;
|
|
int commands_count;
|
|
struct audio_command_t commands[ 512 ];
|
|
struct music_t* current_music;
|
|
bool loop_music;
|
|
int music_volume;
|
|
tml_message* music_next;
|
|
int left_over;
|
|
double music_msec;
|
|
bool music_done;
|
|
int music_play_counter;
|
|
int sound_freq;
|
|
bool sound_8bit;
|
|
bool sound_mono;
|
|
struct {
|
|
struct sound_t* sound;
|
|
bool loop;
|
|
int volume_left;
|
|
int volume_right;
|
|
int play_counter;
|
|
float position;
|
|
bool done;
|
|
} sound_channels[ SOUND_CHANNELS ];
|
|
};
|
|
|
|
static void app_sound_callback( APP_S16* sample_pairs, int sample_pairs_count, void* user_data ) {
|
|
struct sound_context_t* context = (struct sound_context_t*) user_data;
|
|
static float mixbuffer[ SOUND_BUFFER_SIZE * 10 ];
|
|
static short modbuffer[ SOUND_BUFFER_SIZE * 10 ];
|
|
int in_count = sample_pairs_count;
|
|
|
|
thread_mutex_lock( &context->mutex );
|
|
|
|
if( !context->music_done && context->current_music && context->current_music->format == MUSIC_FORMAT_MOD ) {
|
|
memset( modbuffer, 0, sample_pairs_count * 2 * sizeof( short ) );
|
|
jar_mod_context_t* modctx = (jar_mod_context_t*)( context->current_music + 1 );
|
|
jar_mod_fillbuffer( modctx, modbuffer, sample_pairs_count, NULL );
|
|
for( int i = 0; i < sample_pairs_count * 2; ++i ) {
|
|
mixbuffer[ i ] = ( modbuffer[ i ] / 32768.0f ) * ( context->music_volume / 255.0f );
|
|
}
|
|
} else {
|
|
memset( mixbuffer, 0, sample_pairs_count * 2 * sizeof( float) );
|
|
}
|
|
|
|
for( int i = 0; i < SOUND_CHANNELS; ++i ) {
|
|
if( context->sound_channels[ i ].sound && !context->sound_channels[ i ].done ) {
|
|
float result = mix_sound_channel( context->sound_channels[ i ].sound, context->sound_channels[ i ].loop,
|
|
context->sound_channels[ i ].volume_left / 255.0f, context->sound_channels[ i ].volume_right / 255.0f,
|
|
context->sound_channels[ i ].position,
|
|
mixbuffer, sample_pairs_count );
|
|
if( result >= context->sound_channels[ i ].sound->framecount ) {
|
|
context->sound_channels[ i ].done = true;
|
|
} else {
|
|
context->sound_channels[ i ].position = result;
|
|
}
|
|
}
|
|
}
|
|
|
|
int freq = context->sound_freq;
|
|
int is8bit = context->sound_8bit;
|
|
int ismono = context->sound_mono;
|
|
|
|
static float freqbuffer[ SOUND_BUFFER_SIZE * 10 ];
|
|
float ratio = freq / 44100.0f;
|
|
float outpos = 0.0f;
|
|
for( int i = 0; i < in_count; ++i ) {
|
|
int o = (int) outpos;
|
|
float s0 = mixbuffer[ i * 2 + 0 ];
|
|
float s1 = mixbuffer[ i * 2 + 1 ];
|
|
freqbuffer[ o * 2 + 0 ] = s0;
|
|
freqbuffer[ o * 2 + 1 ] = s1;
|
|
outpos += ratio;
|
|
}
|
|
|
|
float inpos = 0.0f;
|
|
for( int i = 0; i < in_count; ++i ) {
|
|
int in = (int) inpos;
|
|
float s0 = freqbuffer[ in * 2 + 0 ];
|
|
float s1 = freqbuffer[ in * 2 + 1 ];
|
|
if( ismono ) {
|
|
float s = ( s0 + s1 ) / 2.0f;
|
|
s0 = s;
|
|
s1 = s;
|
|
}
|
|
if( is8bit ) {
|
|
s0 *= 127.0f;
|
|
s1 *= 127.0f;
|
|
s0 = ( (int) s0 ) / 127.0f;
|
|
s1 = ( (int) s1 ) / 127.0f;
|
|
}
|
|
s0 = soft_clipping( s0 ) * 32700.0f;
|
|
s1 = soft_clipping( s1 ) * 32700.0f;
|
|
sample_pairs[ i * 2 + 0 ] = (APP_S16) s0;
|
|
sample_pairs[ i * 2 + 1 ] = (APP_S16) s1;
|
|
inpos += ratio;
|
|
}
|
|
|
|
|
|
if( !context->music_done && context->current_music && context->current_music->format == MUSIC_FORMAT_MUS ) {
|
|
int result = 0;
|
|
if( context->soundfont ) {
|
|
result = render_mus_tsf( (mus_t*)( context->current_music + 1 ), context->left_over,
|
|
context->loop_music, sample_pairs, sample_pairs_count, context->soundfont );
|
|
} else {
|
|
result = render_mus_opl( (mus_t*)( context->current_music + 1 ), context->left_over,
|
|
context->loop_music, modbuffer, sample_pairs_count, context->opl );
|
|
for( int i = 0; i < sample_pairs_count * 2; ++i ) {
|
|
int s = ( modbuffer[ i ] );
|
|
s += sample_pairs[ i ];
|
|
if( s > 32700 ) s = 32700;
|
|
if( s < -32700 ) s = -32700;
|
|
sample_pairs[ i ] = (short)( s );
|
|
}
|
|
}
|
|
if( result >= 0 ) {
|
|
context->left_over = result;
|
|
} else {
|
|
context->music_done = true;
|
|
context->left_over = 0;;
|
|
}
|
|
} else if( !context->music_done && context->current_music && context->current_music->format == MUSIC_FORMAT_MID ) {
|
|
if( context->soundfont ) {
|
|
context->music_next = render_mid_tsf( context->current_music, context->music_next, &context->music_msec,
|
|
context->loop_music, sample_pairs, sample_pairs_count, context->soundfont );
|
|
} else {
|
|
context->music_next = render_mid_opl( context->current_music, context->music_next, &context->music_msec,
|
|
context->loop_music, modbuffer, sample_pairs_count, context->opl );
|
|
for( int i = 0; i < sample_pairs_count * 2; ++i ) {
|
|
int s = ( modbuffer[ i ] );
|
|
s += sample_pairs[ i ];
|
|
if( s > 32700 ) s = 32700;
|
|
if( s < -32700 ) s = -32700;
|
|
sample_pairs[ i ] = (short)( s );
|
|
}
|
|
}
|
|
if( context->music_next == NULL ) {
|
|
context->music_done = true;
|
|
}
|
|
} else if( !context->music_done && context->current_music && context->current_music->format == MUSIC_FORMAT_OPB ) {
|
|
struct opb_t* opb = (struct opb_t*)( context->current_music + 1 );
|
|
double current_time = opb->accumulated_time;
|
|
opb->accumulated_time += sample_pairs_count / 44100.0;
|
|
short* outsamples = modbuffer;
|
|
int samples_remaining = sample_pairs_count;
|
|
int buffered_count = 0;
|
|
uint16_t buffered_regs[ 256 ];
|
|
uint8_t buffered_data[ 256 ];
|
|
while( opb->position < opb->commands_count ) {
|
|
OPB_Command cmd = opb->commands[ opb->position ];
|
|
if( cmd.Time >= opb->accumulated_time ) {
|
|
break;
|
|
}
|
|
if( cmd.Time > current_time ) {
|
|
int samples_to_render = (int)( ( cmd.Time - current_time ) * 44100.0 );
|
|
if( samples_to_render > 0 ) {
|
|
if( samples_to_render > samples_remaining ) {
|
|
samples_to_render = samples_remaining;
|
|
}
|
|
opl_write( context->opl, buffered_count, buffered_regs, buffered_data );
|
|
buffered_count = 0;
|
|
opl_render( context->opl, outsamples, samples_to_render, context->music_volume / 255.0f );
|
|
outsamples += samples_to_render * 2;
|
|
samples_remaining -= samples_to_render;
|
|
if( samples_remaining <= 0 ) {
|
|
break;
|
|
}
|
|
current_time = cmd.Time;
|
|
}
|
|
}
|
|
buffered_regs[ buffered_count ] = cmd.Addr;
|
|
buffered_data[ buffered_count ] = cmd.Data;
|
|
++buffered_count;
|
|
if( buffered_count >= 256 ) {
|
|
opl_write( context->opl, buffered_count, buffered_regs, buffered_data );
|
|
buffered_count = 0;
|
|
}
|
|
++opb->position;
|
|
}
|
|
opl_write( context->opl, buffered_count, buffered_regs, buffered_data );
|
|
buffered_count = 0;
|
|
if( samples_remaining > 0 ) {
|
|
opl_render( context->opl, outsamples, samples_remaining, context->music_volume / 255.0f );
|
|
}
|
|
for( int i = 0; i < sample_pairs_count * 2; ++i ) {
|
|
int s = ( modbuffer[ i ] );
|
|
s += sample_pairs[ i ];
|
|
if( s > 32700 ) s = 32700;
|
|
if( s < -32700 ) s = -32700;
|
|
sample_pairs[ i ] = (short)( s );
|
|
}
|
|
} else {
|
|
if( context->soundfont ) {
|
|
if( context->commands_count > 0 ) {
|
|
int current_stamp = context->commands[ 0 ].frame_stamp;
|
|
for( int i = 0; i < context->commands_count; ++i ) {
|
|
struct audio_command_t* cmd = &context->commands[ i ];
|
|
if( cmd->frame_stamp != current_stamp ) {
|
|
if( sample_pairs_count > 0 ) {
|
|
tsf_render_short( context->soundfont, sample_pairs, 735, 1 );
|
|
}
|
|
sample_pairs += 2 * 735;
|
|
sample_pairs_count -= 735;
|
|
current_stamp = cmd->frame_stamp;
|
|
}
|
|
switch( cmd->type ) {
|
|
case AUDIO_COMMAND_NOTE_ON:
|
|
tsf_channel_note_on( context->soundfont, cmd->channel, cmd->note, cmd->velocity / 127.0f );
|
|
break;
|
|
case AUDIO_COMMAND_NOTE_OFF:
|
|
tsf_channel_note_off( context->soundfont, cmd->channel, cmd->note );
|
|
break;
|
|
case AUDIO_COMMAND_NOTE_OFF_ALL:
|
|
tsf_channel_note_off_all( context->soundfont, cmd->channel );
|
|
break;
|
|
case AUDIO_COMMAND_SET_INSTRUMENT:
|
|
tsf_channel_set_presetnumber( context->soundfont, cmd->channel, cmd->instrument,
|
|
cmd->instrument == 128 ? 1 : 0 );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if( sample_pairs_count > 0 ) {
|
|
tsf_render_short( context->soundfont, sample_pairs, sample_pairs_count, 1 );
|
|
}
|
|
} else {
|
|
if( context->commands_count > 0 ) {
|
|
int current_stamp = context->commands[ 0 ].frame_stamp;
|
|
for( int j = 0; j < context->commands_count; ++j ) {
|
|
struct audio_command_t* cmd = &context->commands[ j ];
|
|
if( cmd->frame_stamp != current_stamp ) {
|
|
if( sample_pairs_count > 0 ) {
|
|
opl_render( context->opl, modbuffer, 735, 1.0f );
|
|
for( int i = 0; i < 735 * 2; ++i ) {
|
|
int s = ( modbuffer[ i ] );
|
|
s += sample_pairs[ i ];
|
|
if( s > 32700 ) s = 32700;
|
|
if( s < -32700 ) s = -32700;
|
|
sample_pairs[ i ] = (short)( s );
|
|
}
|
|
}
|
|
sample_pairs += 2 * 735;
|
|
sample_pairs_count -= 735;
|
|
current_stamp = cmd->frame_stamp;
|
|
}
|
|
switch( cmd->type ) {
|
|
case AUDIO_COMMAND_NOTE_ON:
|
|
opl_midi_noteon( context->opl, cmd->channel, cmd->note, cmd->velocity );
|
|
break;
|
|
case AUDIO_COMMAND_NOTE_OFF:
|
|
opl_midi_noteoff( context->opl, cmd->channel, cmd->note );
|
|
break;
|
|
case AUDIO_COMMAND_NOTE_OFF_ALL:
|
|
opl_midi_controller( context->opl, cmd->channel, 123, 0 );
|
|
break;
|
|
case AUDIO_COMMAND_SET_INSTRUMENT:
|
|
opl_midi_changeprog( context->opl, cmd->channel, cmd->instrument );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if( sample_pairs_count > 0 ) {
|
|
opl_render( context->opl, modbuffer, sample_pairs_count, 1.0f );
|
|
for( int i = 0; i < sample_pairs_count * 2; ++i ) {
|
|
int s = ( modbuffer[ i ] );
|
|
s += sample_pairs[ i ];
|
|
if( s > 32700 ) s = 32700;
|
|
if( s < -32700 ) s = -32700;
|
|
sample_pairs[ i ] = (short)( s );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
context->commands_count = 0;
|
|
|
|
thread_mutex_unlock( &context->mutex );
|
|
}
|
|
|
|
|
|
static void load_crt_frame_col( void* data, struct GIF_WHDR* whdr ) {
|
|
APP_U32* pixels = (APP_U32*) data;
|
|
for( int i = 0; i < 1024 * 1024; ++i ) {
|
|
uint8_t v = whdr->bptr[ i ];
|
|
pixels[ i ] = ( whdr->cpal[ v ].B << 16 ) |( whdr->cpal[ v ].G << 8 ) | ( whdr->cpal[ v ].R );
|
|
}
|
|
}
|
|
|
|
static void load_crt_frame_alpha( void* data, struct GIF_WHDR* whdr ) {
|
|
APP_U32* pixels = (APP_U32*) data;
|
|
for( int i = 0; i < 1024 * 1024; ++i ) {
|
|
uint8_t v = whdr->bptr[ i ];
|
|
pixels[ i ] = pixels[ i ] | ( whdr->cpal[ v ].R << 24);
|
|
}
|
|
}
|
|
|
|
static APP_U32* load_crt_frame( void ) {
|
|
APP_U32* pixels = (APP_U32*) malloc( 1024 * 1024 * sizeof( APP_U32 ) );
|
|
memset( pixels, 0, 1024 * 1024 * sizeof( APP_U32 ) );
|
|
GIF_Load( crtframecol, (long)sizeof( crtframecol ), load_crt_frame_col, NULL, (void*)pixels, 0L );
|
|
GIF_Load( crtframealpha, (long)sizeof( crtframealpha), load_crt_frame_alpha, NULL, (void*)pixels, 0L );
|
|
return pixels;
|
|
}
|
|
|
|
|
|
static int app_proc( app_t* app, void* user_data ) {
|
|
struct app_context_t* app_context = (struct app_context_t*) user_data;
|
|
|
|
app_title( app, app_filename( app ) );
|
|
|
|
bool fullscreen = false;
|
|
|
|
int modargc = 0;
|
|
char* modargv[ 256 ];
|
|
for( int i = 0; i < app_context->argc; ++i ) {
|
|
if( strcmp( app_context->argv[ i ], "--window" ) == 0 ) {
|
|
fullscreen = false;
|
|
}
|
|
else if( strcmp( app_context->argv[ i ], "--fullscreen" ) == 0 ) {
|
|
fullscreen = true;
|
|
} else {
|
|
if( modargc >= sizeof( modargv ) / sizeof( *modargv ) ) {
|
|
break;
|
|
}
|
|
modargv[ modargc++ ] = app_context->argv[ i ];
|
|
}
|
|
}
|
|
app_context->argc = modargc;
|
|
app_context->argv = modargv;
|
|
|
|
int pointer_width = 0;
|
|
int pointer_height = 0;
|
|
int pointer_hotspot_x = 0;
|
|
int pointer_hotspot_y = 0;
|
|
static APP_U32 pointer_pixels[ 256 * 256 ];
|
|
app_pointer_default( app, &pointer_width, &pointer_height, pointer_pixels, &pointer_hotspot_x, &pointer_hotspot_y );
|
|
|
|
app_screenmode( app, fullscreen ? APP_SCREENMODE_FULLSCREEN : APP_SCREENMODE_WINDOW );
|
|
#ifdef DISABLE_SYSTEM_CURSOR
|
|
APP_U32 blank = 0;
|
|
app_pointer( app, 1, 1, &blank, 0, 0 );
|
|
#else
|
|
if( fullscreen ) {
|
|
APP_U32 blank = 0;
|
|
app_pointer( app, 1, 1, &blank, 0, 0 );
|
|
} else {
|
|
app_pointer( app, pointer_width, pointer_height, pointer_pixels, pointer_hotspot_x, pointer_hotspot_y );
|
|
}
|
|
#endif
|
|
|
|
app_displays_t displays = app_displays( app );
|
|
if( displays.count > 0 ) {
|
|
int disp = 0;
|
|
for( int i = 0; i < displays.count; ++i ) {
|
|
if( displays.displays[ i ].x == 0 && displays.displays[ i ].y == 0 ) {
|
|
disp = i;
|
|
break;
|
|
}
|
|
}
|
|
int scrwidth = displays.displays[ disp ].width - 80;
|
|
int scrheight = displays.displays[ disp ].height - 80;
|
|
int aspect_width = (int)( ( scrheight * 4.25f ) / 3 );
|
|
int aspect_height = (int)( ( scrwidth * 3 ) / 4.25f );
|
|
int target_width, target_height;
|
|
if( aspect_height <= scrheight ) {
|
|
target_width = scrwidth;
|
|
target_height = aspect_height;
|
|
} else {
|
|
target_width = aspect_width;
|
|
target_height = scrheight;
|
|
}
|
|
|
|
int x = displays.displays[ disp ].x + ( displays.displays[ disp ].width - target_width ) / 2;
|
|
int y = displays.displays[ disp ].y + ( displays.displays[ disp ].height - target_height ) / 2;
|
|
int w = target_width;
|
|
int h = target_height;
|
|
app_window_pos( app, x, y );
|
|
app_window_size( app, w, h );
|
|
}
|
|
|
|
struct user_thread_context_t user_thread_context;
|
|
user_thread_context.app_context = app_context;
|
|
user_thread_context.sound_buffer_size = SOUND_BUFFER_SIZE;
|
|
thread_signal_init( &user_thread_context.user_thread_initialized );
|
|
thread_atomic_int_store( &user_thread_context.user_thread_finished, 0 );
|
|
thread_signal_init( &user_thread_context.app_loop_finished );
|
|
thread_signal_init( &user_thread_context.user_thread_terminated );
|
|
|
|
#ifndef __wasm__
|
|
thread_ptr_t user_thread = thread_create( user_thread_proc, &user_thread_context,
|
|
THREAD_STACK_SIZE_DEFAULT );
|
|
|
|
if( !thread_signal_wait( &user_thread_context.user_thread_initialized, 5000 ) ) {
|
|
thread_signal_term( &user_thread_context.user_thread_initialized );
|
|
thread_signal_term( &user_thread_context.app_loop_finished );
|
|
thread_signal_term( &user_thread_context.user_thread_terminated );
|
|
return EXIT_FAILURE;
|
|
}
|
|
#else
|
|
// WebAssembly has no real threads so we use coroutines which can switch context between two
|
|
// callstacks to simulate the behavior from native platforms
|
|
WaCoro user_coro = WaCoroInitNew( user_thread_proc, "user_thread_proc", &user_thread_context, 0 );
|
|
WaCoroSwitch(user_coro);
|
|
internals->wasm.user_coro = user_coro; // only now internals exists
|
|
#endif
|
|
|
|
#ifdef NULL_PLATFORM
|
|
crtemu_pc_t* crt = NULL;
|
|
#else
|
|
crtemu_pc_t* crt = crtemu_pc_create( NULL );
|
|
#ifndef DISABLE_SCREEN_FRAME
|
|
APP_U32* frame = load_crt_frame();
|
|
crtemu_pc_frame( crt, frame, 1024, 1024 );
|
|
free( frame );
|
|
#endif
|
|
#endif
|
|
|
|
// Create the frametimer instance, and set it to fixed 60hz update. This will ensure we never run faster than that,
|
|
// even if the user have disabled vsync in their graphics card settings.
|
|
frametimer_t* frametimer = frametimer_create( NULL );
|
|
frametimer_lock_rate( frametimer, 60 );
|
|
|
|
// Start sound playback
|
|
struct sound_context_t sound_context;
|
|
memset( &sound_context, 0, sizeof( sound_context ) );
|
|
thread_mutex_init( &sound_context.mutex );
|
|
sound_context.opl = opl_create();
|
|
sound_context.commands_count = 0;
|
|
sound_context.current_music = NULL;
|
|
sound_context.loop_music = false;
|
|
sound_context.music_volume = 0;
|
|
initsoundmode( internals->audio.soundmode, &sound_context.sound_freq, &sound_context.sound_8bit, &sound_context.sound_mono );
|
|
app_sound( app, SOUND_BUFFER_SIZE * 2, app_sound_callback, &sound_context );
|
|
int previous_soundbank = internals->audio.current_soundbank;
|
|
|
|
signalvbl();
|
|
|
|
struct {
|
|
struct sound_t* sound;
|
|
bool loop;
|
|
int volume_left;
|
|
int volume_right;
|
|
int play_counter;
|
|
} sound_channels[ SOUND_CHANNELS ] = { { NULL } };
|
|
|
|
enum soundmode_t sound_mode = internals->audio.soundmode;
|
|
int music_play_counter = 0;
|
|
|
|
// Main loop
|
|
static APP_U32 screen_xbgr[ sizeof( internals->screen.buffer0 ) ];
|
|
int width = 0;
|
|
int height = 0;
|
|
int curs_vis = 0;
|
|
int curs_x = 0;
|
|
int curs_y = 0;
|
|
bool keystate[ KEYCOUNT ] = { 0 };
|
|
enum keycode_t keys[ 256 ] = { (enum keycode_t)0 };
|
|
char chars[ 256] = { 0 };
|
|
APP_U64 crt_time_us = 0;
|
|
APP_U64 prev_time = app_time_count( app );
|
|
while( !thread_atomic_int_load( &user_thread_context.user_thread_finished ) ) {
|
|
app_state_t app_state = app_yield( app );
|
|
frametimer_update( frametimer );
|
|
|
|
int keys_index = 0;
|
|
memset( keys, 0, sizeof( keys ) );
|
|
int chars_index = 0;
|
|
memset( chars, 0, sizeof( chars ) );
|
|
float relx = 0;
|
|
float rely = 0;
|
|
app_input_t input = app_input( app );
|
|
for( int i = 0; i < input.count; ++i ) {
|
|
app_input_event_t* event = &input.events[ i ];
|
|
if( event->type == APP_INPUT_KEY_DOWN ) {
|
|
int index = (int)event->data.key;
|
|
if( index > 0 && index < KEYCOUNT ) {
|
|
keystate[ index ] = true;
|
|
if( keys_index < 255 ) {
|
|
keys[ keys_index++ ] = (enum keycode_t)event->data.key;
|
|
}
|
|
}
|
|
if( event->data.key == APP_KEY_F11 ) {
|
|
fullscreen = !fullscreen;
|
|
app_screenmode( app, fullscreen ? APP_SCREENMODE_FULLSCREEN : APP_SCREENMODE_WINDOW );
|
|
#ifdef DISABLE_SYSTEM_CURSOR
|
|
APP_U32 blank = 0;
|
|
app_pointer( app, 1, 1, &blank, 0, 0 );
|
|
#else
|
|
if( fullscreen ) {
|
|
APP_U32 blank = 0;
|
|
app_pointer( app, 1, 1, &blank, 0, 0 );
|
|
} else {
|
|
app_pointer( app, pointer_width, pointer_height, pointer_pixels, pointer_hotspot_x, pointer_hotspot_y );
|
|
}
|
|
#endif
|
|
}
|
|
} else if( event->type == APP_INPUT_KEY_UP ) {
|
|
int index = (int)event->data.key;
|
|
if( index >= 0 && index < KEYCOUNT ) {
|
|
keystate[ index ] = false;
|
|
if( keys_index < 255 ) {
|
|
keys[ keys_index++ ] = (enum keycode_t)( ( (uint32_t)event->data.key ) | KEY_MODIFIER_RELEASED );
|
|
}
|
|
}
|
|
} else if( event->type == APP_INPUT_CHAR ) {
|
|
if( event->data.char_code > 0 ) {
|
|
if( chars_index < 255 ) {
|
|
chars[ chars_index++ ] = event->data.char_code;
|
|
}
|
|
}
|
|
} else if( event->type == APP_INPUT_MOUSE_DELTA ) {
|
|
relx += event->data.mouse_delta.x;
|
|
rely += event->data.mouse_delta.y;
|
|
}
|
|
}
|
|
internals->input.mouse_relx = (int)relx;
|
|
internals->input.mouse_rely = (int)rely;
|
|
|
|
// Check if the close button on the window was clicked (or Alt+F4 was pressed)
|
|
if( app_state == APP_STATE_EXIT_REQUESTED ) {
|
|
// Signal that we need to force the user thread to exit
|
|
thread_atomic_int_store( &internals->exit_flag, 1 );
|
|
signalvbl();
|
|
break;
|
|
}
|
|
|
|
// Copy data from user thread
|
|
thread_mutex_lock( &internals->mutex );
|
|
|
|
width = internals->screen.width;
|
|
height = internals->screen.height;
|
|
uint8_t* internals_screen = internals->screen.buffer;
|
|
uint32_t* font = internals->screen.font;
|
|
if( internals->screen.doublebuffer ) {
|
|
if( internals->screen.buffer == internals->screen.buffer0 ) {
|
|
internals_screen = internals->screen.buffer1;
|
|
} else {
|
|
internals_screen = internals->screen.buffer0;
|
|
}
|
|
}
|
|
static uint8_t screen[ sizeof( internals->screen.buffer0 ) ];
|
|
if( font ) {
|
|
memcpy( screen, internals_screen, width * height * 2 );
|
|
} else {
|
|
memcpy( screen, internals_screen, width * height );
|
|
}
|
|
|
|
static uint32_t palette[ 256 ];
|
|
memcpy( palette, internals->screen.palette, 1024 );
|
|
|
|
bool curs = internals->conio.curs;
|
|
if( internals->conio.x != curs_x || internals->conio.y != curs_y ) {
|
|
curs_x = internals->conio.x;
|
|
curs_y = internals->conio.y;
|
|
curs_vis = 0;
|
|
}
|
|
|
|
int mouse_x = app_pointer_x( app );
|
|
int mouse_y = app_pointer_y( app );
|
|
if( crt ) {
|
|
crtemu_pc_coordinates_window_to_bitmap( crt, width * internals->screen.cellwidth,
|
|
height * internals->screen.cellheight, &mouse_x, &mouse_y );
|
|
}
|
|
internals->input.mouse_x = mouse_x / internals->screen.cellwidth;
|
|
internals->input.mouse_y = mouse_y / internals->screen.cellheight;
|
|
|
|
memcpy( internals->input.keystate, keystate, sizeof( internals->input.keystate ) );
|
|
|
|
enum keycode_t* internals_keybuffer;
|
|
if( internals->input.keybuffer == internals->input.keybuffer0 ) {
|
|
internals_keybuffer = internals->input.keybuffer1;
|
|
} else {
|
|
internals_keybuffer = internals->input.keybuffer0;
|
|
}
|
|
enum keycode_t* keyin = keys;
|
|
enum keycode_t* keyout = internals_keybuffer;
|
|
enum keycode_t* keyend = internals_keybuffer + sizeof( internals->input.keybuffer0 ) / sizeof( *internals->input.keybuffer0 ) - 1;
|
|
while( *keyout && keyout < keyend ) {
|
|
++keyout;
|
|
}
|
|
while( *keyin ) {
|
|
if( keyout >= keyend ) {
|
|
memmove( internals_keybuffer + 1, internals_keybuffer, sizeof( internals->input.keybuffer0 ) / sizeof( *internals->input.keybuffer0 ) - 1 );
|
|
--keyout;
|
|
}
|
|
*keyout++ = *keyin++;
|
|
}
|
|
*keyout = KEY_INVALID;
|
|
|
|
char* internals_charbuffer;
|
|
if( internals->input.charbuffer == internals->input.charbuffer0 ) {
|
|
internals_charbuffer = internals->input.charbuffer1;
|
|
} else {
|
|
internals_charbuffer = internals->input.charbuffer0;
|
|
}
|
|
char* charin = chars;
|
|
char* charout = internals_charbuffer;
|
|
char* charend = internals_charbuffer + sizeof( internals->input.charbuffer0 ) / sizeof( *internals->input.charbuffer0 ) - 1;
|
|
while( *charout && charout < charend ) {
|
|
++charout;
|
|
}
|
|
while( *charin ) {
|
|
if( charout >= charend ) {
|
|
memmove( internals_charbuffer + 1, internals_charbuffer, sizeof( internals->input.charbuffer0 ) / sizeof( *internals->input.charbuffer0 ) - 1 );
|
|
--charout;
|
|
}
|
|
*charout++ = *charin++;
|
|
}
|
|
*charout = '\0';
|
|
|
|
int audio_commands_count = internals->audio.commands_count;
|
|
internals->audio.commands_count = 0;
|
|
struct audio_command_t audio_commands[ 256 ];
|
|
memcpy( audio_commands, internals->audio.commands, audio_commands_count * sizeof( struct audio_command_t) );
|
|
++internals->audio.frame_stamp;
|
|
|
|
struct music_t* current_music = internals->audio.current_music;
|
|
music_play_counter = internals->audio.music_play_counter;
|
|
bool loop_music = internals->audio.loop_music;
|
|
int music_volume = internals->audio.music_volume;
|
|
|
|
for( int i = 0; i < SOUND_CHANNELS; ++i ) {
|
|
sound_channels[ i ].sound = internals->audio.channels[ i ].sound;
|
|
sound_channels[ i ].loop = internals->audio.channels[ i ].loop;
|
|
sound_channels[ i ].volume_left = internals->audio.channels[ i ].volume_left;
|
|
sound_channels[ i ].volume_right = internals->audio.channels[ i ].volume_right;
|
|
sound_channels[ i ].play_counter = internals->audio.channels[ i ].play_counter;
|
|
}
|
|
|
|
sound_mode = internals->audio.soundmode;
|
|
|
|
int current_soundbank = internals->audio.current_soundbank;
|
|
|
|
thread_mutex_unlock( &internals->mutex );
|
|
|
|
// Signal to the game that the frame is completed, and that we are just starting the next one
|
|
signalvbl();
|
|
|
|
// Process audio commands
|
|
thread_mutex_lock( &sound_context.mutex );
|
|
if( previous_soundbank != current_soundbank ) {
|
|
enum soundbank_type_t type = internals->audio.soundbanks[ current_soundbank ].type;
|
|
if( type == SOUNDBANK_TYPE_SF2 ) {
|
|
sound_context.soundfont = internals->audio.soundbanks[ current_soundbank ].sf2;
|
|
tsf_reset( sound_context.soundfont );
|
|
for( int i = 0; i < MUSIC_CHANNELS; ++i ) {
|
|
tsf_channel_set_presetnumber( sound_context.soundfont, i, 0, i == 9 ? 1 : 0 );
|
|
}
|
|
//} else if( type == SOUNDBANK_TYPE_IBK ) {
|
|
// opl_loadbank_ibk( sound_context.opl, internals->audio.soundbanks[ current_soundbank ].data, internals->audio.soundbanks[ current_soundbank ].size );
|
|
// sound_context.soundfont = NULL;
|
|
} else if( type == SOUNDBANK_TYPE_OP2 ) {
|
|
opl_loadbank_op2( sound_context.opl, internals->audio.soundbanks[ current_soundbank ].data, (int)internals->audio.soundbanks[ current_soundbank ].size );
|
|
sound_context.soundfont = NULL;
|
|
} else if( type == SOUNDBANK_TYPE_NONE ) {
|
|
opl_destroy( sound_context.opl );
|
|
sound_context.opl = opl_create();
|
|
sound_context.soundfont = NULL;
|
|
}
|
|
sound_context.music_play_counter--;
|
|
previous_soundbank = current_soundbank;
|
|
}
|
|
for( int i = 0; i < audio_commands_count; ++i ) {
|
|
if( sound_context.commands_count >= sizeof( sound_context.commands ) / sizeof( *sound_context.commands ) ) {
|
|
break;
|
|
}
|
|
sound_context.commands[ sound_context.commands_count++ ] = audio_commands[ i ];
|
|
}
|
|
|
|
sound_context.music_volume = music_volume;
|
|
if( sound_context.soundfont ) {
|
|
tsf_set_volume( sound_context.soundfont, music_volume / 255.0f );
|
|
}
|
|
if( current_music && sound_context.music_play_counter != music_play_counter ) {
|
|
if( sound_context.soundfont ) {
|
|
tsf_reset( sound_context.soundfont );
|
|
tsf_set_volume( sound_context.soundfont, music_volume / 255.0f );
|
|
} else {
|
|
opl_clear( sound_context.opl );
|
|
}
|
|
sound_context.current_music = current_music;
|
|
sound_context.loop_music = loop_music;
|
|
sound_context.music_volume = music_volume;
|
|
if( current_music->format == MUSIC_FORMAT_MID ) {
|
|
sound_context.music_next = (tml_message*)( current_music + 1 );
|
|
if( sound_context.soundfont ) {
|
|
tsf_reset( sound_context.soundfont );
|
|
for( int i = 0; i < MUSIC_CHANNELS; ++i ) {
|
|
tsf_channel_set_presetnumber( sound_context.soundfont, i, 0, i == 9 ? 1 : 0 );
|
|
}
|
|
} else {
|
|
opl_clear( sound_context.opl );
|
|
}
|
|
} else if( current_music->format == MUSIC_FORMAT_MUS ) {
|
|
sound_context.music_next = NULL;
|
|
mus_t* mus = (mus_t*)( current_music + 1 );
|
|
mus_restart( mus );
|
|
} else if( current_music->format == MUSIC_FORMAT_MOD ) {
|
|
jar_mod_context_t* modctx = (jar_mod_context_t*)( current_music + 1 );
|
|
jar_mod_seek_start( modctx );
|
|
} else if( current_music->format == MUSIC_FORMAT_OPB ) {
|
|
struct opb_t* opb = (struct opb_t*)( current_music + 1 );
|
|
opb->position = 0;
|
|
opb->accumulated_time = 0.0;
|
|
opl_clear( sound_context.opl );
|
|
}
|
|
sound_context.music_msec = 0.0;
|
|
sound_context.music_done = false;
|
|
sound_context.left_over = 0;
|
|
sound_context.music_play_counter = music_play_counter;
|
|
} else if( current_music == NULL && sound_context.current_music != NULL ) {
|
|
sound_context.current_music = NULL;
|
|
if( sound_context.soundfont ) {
|
|
tsf_reset( sound_context.soundfont );
|
|
for( int i = 0; i < MUSIC_CHANNELS; ++i ) {
|
|
tsf_channel_set_presetnumber( sound_context.soundfont, i, 0, i == 9 ? 1 : 0 );
|
|
}
|
|
} else {
|
|
opl_clear( sound_context.opl );
|
|
}
|
|
} else if( sound_context.music_done ) {
|
|
internals->audio.current_music = NULL;
|
|
if( sound_context.soundfont ) {
|
|
tsf_reset( sound_context.soundfont );
|
|
for( int i = 0; i < MUSIC_CHANNELS; ++i ) {
|
|
tsf_channel_set_presetnumber( sound_context.soundfont, i, 0, i == 9 ? 1 : 0 );
|
|
}
|
|
} else {
|
|
opl_clear( sound_context.opl );
|
|
}
|
|
}
|
|
initsoundmode( sound_mode, &sound_context.sound_freq, &sound_context.sound_8bit, &sound_context.sound_mono );
|
|
for( int i = 0; i < SOUND_CHANNELS; ++i ) {
|
|
if( sound_channels[ i ].sound != sound_context.sound_channels[ i ].sound
|
|
|| sound_channels[ i ].play_counter != sound_context.sound_channels[ i ].play_counter ) {
|
|
sound_context.sound_channels[ i ].sound = sound_channels[ i ].sound;
|
|
sound_context.sound_channels[ i ].loop = sound_channels[ i ].loop;
|
|
sound_context.sound_channels[ i ].play_counter = sound_channels[ i ].play_counter;
|
|
sound_context.sound_channels[ i ].position = 0.0f;
|
|
sound_context.sound_channels[ i ].done = false;
|
|
} else if( sound_context.sound_channels[ i ].done ) {
|
|
internals->audio.channels[ i ].sound = NULL;
|
|
}
|
|
sound_context.sound_channels[ i ].volume_left = sound_channels[ i ].volume_left;
|
|
sound_context.sound_channels[ i ].volume_right = sound_channels[ i ].volume_right;
|
|
}
|
|
thread_mutex_unlock( &sound_context.mutex );
|
|
|
|
// Render screen buffer
|
|
if( font ) {
|
|
memset( screen_xbgr, 0, sizeof( screen_xbgr ) );
|
|
uint32_t const* data = font;
|
|
int chr_width = *data++;
|
|
int chr_height = *data++;
|
|
int chr_baseline = *data++;
|
|
(void) chr_baseline;
|
|
int chr_mod = 256 / chr_width;
|
|
for( int y = 0; y < height; ++y ) {
|
|
for( int x = 0; x < width; ++x ) {
|
|
uint8_t c = screen[ ( x + y * width ) * 2 + 0 ];
|
|
uint8_t attr = screen[ ( x + y * width ) * 2 + 1 ];
|
|
int fg = ( attr & 0xf );
|
|
int bg = ( ( attr >> 4 ) & 0xf );
|
|
int sx = ( c % chr_mod ) * chr_width;
|
|
int sy = ( c / chr_mod ) * chr_height;
|
|
int dx = x * chr_width;
|
|
int dy = y * chr_height;
|
|
for( int iy = 0; iy < chr_height; ++iy ) {
|
|
for( int ix = 0; ix < chr_width; ++ix ) {
|
|
int v = ( sx + ix ) / 32;
|
|
int u = ( sx + ix ) - ( v * 32 );
|
|
uint32_t b = data[ v + ( sy + iy ) * 8 ];
|
|
int xp = dx + ix;
|
|
int yp = dy + iy;
|
|
if( b & ( 1 << u ) ) {
|
|
screen_xbgr[ xp + yp * ( width * chr_width ) ] = palette[ fg ];
|
|
} else {
|
|
screen_xbgr[ xp + yp * ( width * chr_width ) ] = palette[ bg ];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
++curs_vis;
|
|
if( curs && curs_x >= 0 && curs_x < width && curs_y >= 0 && curs_y < height ) {
|
|
int vis = ( curs_vis % 50 ) < 25;
|
|
if( vis ) {
|
|
int xp = curs_x * chr_width;
|
|
int yp = curs_y * chr_height;
|
|
int cs = chr_height == 16 ? 13 : 7;
|
|
int ce = chr_height == 16 ? 15 : 9;
|
|
APP_U32 col = palette[ 7 ];
|
|
for( int y = cs; y < ce; ++y ) {
|
|
for( int x = 0; x < chr_width; ++x ) {
|
|
screen_xbgr[ ( x + xp ) + ( y + yp ) * width * chr_width ] = col;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
width *= chr_width;
|
|
height *= chr_height;
|
|
} else {
|
|
for( int y = 0; y < height; ++y ) {
|
|
for( int x = 0; x < width; ++x ) {
|
|
screen_xbgr[ x + y * width ] = palette[ screen[ x + y * width ] ];
|
|
}
|
|
}
|
|
}
|
|
|
|
if( !app_has_focus( app ) ) {
|
|
continue;
|
|
}
|
|
APP_U64 time = app_time_count( app );
|
|
APP_U64 freq = app_time_freq( app );
|
|
APP_U64 delta_time_us = ( time - prev_time ) / ( ( freq > 1000000 ? freq / 1000000 : 1 ) );
|
|
prev_time = time;
|
|
crt_time_us += delta_time_us;
|
|
if( crt ) {
|
|
#ifndef DISABLE_SCREEN_FRAME
|
|
crtemu_pc_present( crt, crt_time_us, screen_xbgr, width, height, 0xffffff, 0xff1a1a1a );
|
|
#else
|
|
crtemu_pc_present( crt, crt_time_us, screen_xbgr, width, height, 0xffffff, 0xff000000 );
|
|
#endif
|
|
}
|
|
app_present( app, NULL, 1, 1, 0xffffff, 0xff1a1a1a );
|
|
}
|
|
|
|
app_sound( app, 0, NULL, NULL );
|
|
|
|
thread_signal_raise( &user_thread_context.app_loop_finished );
|
|
int user_exit = thread_signal_wait( &user_thread_context.user_thread_terminated, 170 );
|
|
#ifdef __wasm__
|
|
WaCoroSwitch(user_coro);
|
|
user_exit = 0; // always show fade out animation
|
|
#endif
|
|
if( !user_exit ) {
|
|
for( int i = 0; i < 60; ++i ) {
|
|
APP_U64 time = app_time_count( app );
|
|
APP_U64 delta_time_us = ( time - prev_time ) / ( app_time_freq( app ) / 1000000 );
|
|
prev_time = time;
|
|
crt_time_us += delta_time_us;
|
|
int v = ( ( 60 - i ) * 255 ) / 60;
|
|
uint32_t fade = ( v << 16 ) | v << 8 | v;
|
|
if( crt ) {
|
|
crtemu_pc_present( crt, crt_time_us, screen_xbgr, width, height, fade, 0xff1a1a1a );
|
|
}
|
|
app_present( app, NULL, 1, 1, 0xffffff, 0xff1a1a1a );
|
|
frametimer_update( frametimer );
|
|
}
|
|
user_exit = thread_signal_wait( &user_thread_context.user_thread_terminated, 30 );
|
|
if( !user_exit ) {
|
|
exit( EXIT_FAILURE );
|
|
}
|
|
}
|
|
thread_signal_term( &user_thread_context.user_thread_initialized );
|
|
thread_signal_term( &user_thread_context.app_loop_finished );
|
|
thread_signal_term( &user_thread_context.user_thread_terminated );
|
|
frametimer_destroy( frametimer );
|
|
opl_destroy( sound_context.opl );
|
|
thread_mutex_term( &sound_context.mutex );
|
|
if( crt ) {
|
|
crtemu_pc_destroy( crt );
|
|
}
|
|
#ifndef __wasm__
|
|
return thread_join( user_thread );
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
|
|
#define APP_IMPLEMENTATION
|
|
#ifdef NULL_PLATFORM
|
|
#define APP_NULL
|
|
#elif defined( _WIN32 )
|
|
#define APP_WINDOWS
|
|
#elif defined( __wasm__ )
|
|
#define APP_WASM
|
|
#else
|
|
#define APP_SDL
|
|
#endif
|
|
#define APP_LOG( ctx, level, message )
|
|
#include "libs/app.h"
|
|
|
|
#define CRTEMU_PC_IMPLEMENTATION
|
|
#include "libs/crtemu_pc.h"
|
|
|
|
#define DR_WAV_IMPLEMENTATION
|
|
#define DRWAV_MALLOC( sz ) wav_custom_malloc( sz )
|
|
#define DRWAV_REALLOC( p, sz ) wav_custom_realloc( p, sz )
|
|
#define DRWAV_FREE( p ) wav_custom_free( p );
|
|
#include "libs/dr_wav.h"
|
|
|
|
#define FRAMETIMER_IMPLEMENTATION
|
|
#include "libs/frametimer.h"
|
|
|
|
#define MUS_IMPLEMENTATION
|
|
#define MUS_MALLOC tml_mus_custom_malloc
|
|
#define MUS_FREE tml_mus_custom_free
|
|
#include "libs/mus.h"
|
|
|
|
#define OPBLIB_IMPLEMENTATION
|
|
#pragma warning( push )
|
|
#pragma warning( disable: 4189 )
|
|
#pragma warning( disable: 4204 )
|
|
#pragma warning( disable: 4244 )
|
|
#pragma warning( disable: 4296 )
|
|
#pragma warning( disable: 4388 )
|
|
#pragma warning( disable: 4457 )
|
|
#pragma warning( disable: 4706 )
|
|
#include "libs/opblib.h"
|
|
#pragma warning( pop )
|
|
|
|
#define OPL_IMPLEMENTATION
|
|
#pragma warning( push )
|
|
#pragma warning( disable: 4100 )
|
|
#pragma warning( disable: 4127 )
|
|
#pragma warning( disable: 4189 )
|
|
#pragma warning( disable: 4242 )
|
|
#pragma warning( disable: 4244 )
|
|
#pragma warning( disable: 4245 )
|
|
#include "libs/opl.h"
|
|
#pragma warning( pop )
|
|
|
|
#define PIXELFONT_IMPLEMENTATION
|
|
#define PIXELFONT_BUILDER_IMPLEMENTATION
|
|
#include "libs/pixelfont.h"
|
|
|
|
#define THREAD_IMPLEMENTATION
|
|
#include "libs/thread.h"
|
|
|
|
#define TSF_IMPLEMENTATION
|
|
#define TSF_POW pow
|
|
#define TSF_POWF (float)pow
|
|
#define TSF_EXPF (float)exp
|
|
#define TSF_LOG log
|
|
#define TSF_TAN tan
|
|
#define TSF_LOG10 log10
|
|
#define TSF_SQRT (float)sqrt
|
|
#define TSF_SQRTF (float)sqrt
|
|
#include <math.h>
|
|
#include "libs/tsf.h"
|
|
|
|
#define TML_IMPLEMENTATION
|
|
#ifdef _WIN32
|
|
#pragma warning( push )
|
|
#pragma warning( disable: 4201 )
|
|
#endif
|
|
#define TML_MALLOC tml_mus_custom_malloc
|
|
#define TML_REALLOC tml_mus_custom_realloc
|
|
#define TML_FREE tml_mus_custom_free
|
|
#include "libs/tml.h"
|
|
#ifdef _WIN32
|
|
#pragma warning( pop)
|
|
#endif
|
|
|
|
|
|
bool app_has_focus( app_t* app ) {
|
|
#ifdef ALWAYS_UPDATE
|
|
return true;
|
|
#else
|
|
#ifndef NULL_PLATFORM
|
|
return app->has_focus;
|
|
#else
|
|
return true;
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
|
|
#include <inttypes.h>
|
|
|
|
/*
|
|
void bin2arr( char const* src, char const* dst, char const* name ) {
|
|
FILE* fp = fopen( src, "rb" );
|
|
fseek( fp, 0, SEEK_END );
|
|
size_t sz = ftell( fp );
|
|
fseek( fp, 0, SEEK_SET );
|
|
uint8_t* data = (uint8_t*) malloc( sz );
|
|
fread( data, 1, sz, fp );
|
|
fclose( fp );
|
|
|
|
fp = fopen( dst, "w" );
|
|
unsigned long long count = ( (unsigned long long) sz );
|
|
fprintf( fp, "unsigned char %s[ %llu ] = {\n", name, count );
|
|
for( size_t i = 0; i < sz; ++i ) {
|
|
if( i > 0 && ( i % 400 ) == 0 ) fprintf( fp, "\n" );
|
|
fprintf( fp, "%d,", data[ i ] );
|
|
}
|
|
fprintf( fp, "\n" );
|
|
fprintf( fp, "};\n" );
|
|
free( data );
|
|
fclose( fp );
|
|
}
|
|
*/
|
|
|
|
//*** main ***
|
|
|
|
int main( int argc, char** argv ) {
|
|
(void) argc, (void) argv;
|
|
|
|
//bin2arr( "framecol.gif", "crtframecol.h", "crtframecol" );
|
|
//bin2arr( "framealpha.gif", "crtframealpha.h", "crtframealpha" );
|
|
//bin2arr( "aweromgm.sf2", "awe32rom.h", "awe32rom" );
|
|
|
|
struct app_context_t app_context;
|
|
app_context.argc = argc;
|
|
app_context.argv = argv;
|
|
return app_run( app_proc, &app_context, NULL, NULL, NULL );
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
// pass-through so the program will build with either /SUBSYSTEM:WINDOWS or /SUBSYSTEM:CONSOLE
|
|
int WINAPI __stdcall WinMain( HINSTANCE a, HINSTANCE b, char* c, int d ) {
|
|
(void) a, b, c, d;
|
|
return main( __argc, __argv );
|
|
}
|
|
#endif
|
|
|
|
|
|
#endif /* DOS_IMPLEMENTATION */
|
|
|
|
|
|
#ifndef NO_MAIN_DEF
|
|
#define main dosmain
|
|
#endif
|
|
|
|
/*
|
|
------------------------------------------------------------------------------
|
|
|
|
This software is available under 2 licenses - you may choose the one you like.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
ALTERNATIVE A - MIT License
|
|
|
|
Copyright (c) 2021 Mattias Gustavsson
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
this software and associated documentation files (the "Software"), to deal in
|
|
the Software without restriction, including without limitation the rights to
|
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
of the Software, and to permit persons to whom the Software is furnished to do
|
|
so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
------------------------------------------------------------------------------
|
|
|
|
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
|
|
|
This is free and unencumbered software released into the public domain.
|
|
|
|
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
|
software, either in source code form or as a compiled binary, for any purpose,
|
|
commercial or non-commercial, and by any means.
|
|
|
|
In jurisdictions that recognize copyright laws, the author or authors of this
|
|
software dedicate any and all copyright interest in the software to the public
|
|
domain. We make this dedication for the benefit of the public at large and to
|
|
the detriment of our heirs and successors. We intend this dedication to be an
|
|
overt act of relinquishment in perpetuity of all present and future rights to
|
|
this software under copyright law.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
------------------------------------------------------------------------------
|
|
*/
|