added VGM playback
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ DOSBox/
|
|||||||
LOVE/
|
LOVE/
|
||||||
rel/
|
rel/
|
||||||
test/
|
test/
|
||||||
|
test2/
|
||||||
|
|||||||
13
doc/api.md
13
doc/api.md
@@ -10,6 +10,7 @@
|
|||||||
* [love.filesystem](#lovefilesystem)
|
* [love.filesystem](#lovefilesystem)
|
||||||
* [love.audio](#loveaudio)
|
* [love.audio](#loveaudio)
|
||||||
* [love.event](#loveevent)
|
* [love.event](#loveevent)
|
||||||
|
* [love.vgm](#lovevgm)
|
||||||
|
|
||||||
##### [Objects](#objects-1)
|
##### [Objects](#objects-1)
|
||||||
* [Image](#image)
|
* [Image](#image)
|
||||||
@@ -38,6 +39,18 @@ Returns the amount of memory in kilobytes which is being used by LoveDOS. This
|
|||||||
includes the memory used by both the loaded assets and lua.
|
includes the memory used by both the loaded assets and lua.
|
||||||
|
|
||||||
|
|
||||||
|
### love.vgm
|
||||||
|
VGM OPL2 playback
|
||||||
|
|
||||||
|
##### love.vgm.VgmLoad(filename)
|
||||||
|
Load (or replace) a VGM.
|
||||||
|
|
||||||
|
##### love.vgm.VgmPlay()
|
||||||
|
Start VGM playback.
|
||||||
|
|
||||||
|
##### love.vgm.VgmStop()
|
||||||
|
Stop VGM playback.
|
||||||
|
|
||||||
### love.graphics
|
### love.graphics
|
||||||
Provides functions for drawing lines, shapes, text and images.
|
Provides functions for drawing lines, shapes, text and images.
|
||||||
|
|
||||||
|
|||||||
243
src/lib/pctimer/gccint8.c
Normal file
243
src/lib/pctimer/gccint8.c
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
/* PCTIMER 1.4
|
||||||
|
Millisecond Resolution Timing Library for DJGPP V2
|
||||||
|
Release Date: 3/15/1998
|
||||||
|
Chih-Hao Tsai (hao520@yahoo.com)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <dos.h>
|
||||||
|
#include <dpmi.h>
|
||||||
|
#include <pc.h>
|
||||||
|
#include <go32.h>
|
||||||
|
#include <bios.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "gccint8.h"
|
||||||
|
|
||||||
|
#define IRQ0 0x8
|
||||||
|
#define PIT0 0x40
|
||||||
|
#define PIT1 0x41
|
||||||
|
#define PIT2 0x42
|
||||||
|
#define PITMODE 0x43
|
||||||
|
#define PITCONST 1193180L
|
||||||
|
#define PIT0DEF 18.2067597
|
||||||
|
#define KBCTRL 0x61
|
||||||
|
#define NEW8H 1
|
||||||
|
#define TRUE_FAILURE 0
|
||||||
|
#define CHAIN_TERMINATION 1
|
||||||
|
#define OUTPORTB_TERMINATION 2
|
||||||
|
|
||||||
|
static float tick_per_ms = 0.0182068;
|
||||||
|
static float ms_per_tick = 54.9246551;
|
||||||
|
static float freq8h = 18.2067597;
|
||||||
|
static unsigned char flag8h = 0;
|
||||||
|
static _go32_dpmi_seginfo rm_old_handler, rm_new_handler, pm_old_handler, pm_new_handler;
|
||||||
|
static _go32_dpmi_registers r, r1;
|
||||||
|
int counter_8h;
|
||||||
|
int counter_reset;
|
||||||
|
unsigned long int ticks_8h = 0;
|
||||||
|
unsigned char flag_pm_termination = TRUE_FAILURE;
|
||||||
|
|
||||||
|
static void (*hook_function)();
|
||||||
|
|
||||||
|
void pctimer_init(unsigned int Hz) {
|
||||||
|
unsigned int pit0_set, pit0_value;
|
||||||
|
|
||||||
|
if (flag8h != NEW8H) {
|
||||||
|
disable();
|
||||||
|
|
||||||
|
lock_rm_new8h();
|
||||||
|
lock_pm_new8h();
|
||||||
|
|
||||||
|
_go32_dpmi_lock_data(&hook_function, sizeof(hook_function));
|
||||||
|
_go32_dpmi_lock_data(&ticks_8h, sizeof(ticks_8h));
|
||||||
|
_go32_dpmi_lock_data(&counter_8h, sizeof(counter_8h));
|
||||||
|
_go32_dpmi_lock_data(&counter_8h, sizeof(counter_reset));
|
||||||
|
_go32_dpmi_lock_data(&flag_pm_termination, sizeof(flag_pm_termination));
|
||||||
|
_go32_dpmi_lock_data(&r, sizeof(r));
|
||||||
|
_go32_dpmi_lock_data(&r1, sizeof(r1));
|
||||||
|
|
||||||
|
_go32_dpmi_get_protected_mode_interrupt_vector(8, &pm_old_handler);
|
||||||
|
pm_new_handler.pm_offset = (int)pm_new8h;
|
||||||
|
pm_new_handler.pm_selector = _go32_my_cs();
|
||||||
|
_go32_dpmi_chain_protected_mode_interrupt_vector(8, &pm_new_handler);
|
||||||
|
|
||||||
|
_go32_dpmi_get_real_mode_interrupt_vector(8, &rm_old_handler);
|
||||||
|
rm_new_handler.pm_offset = (int)rm_new8h;
|
||||||
|
_go32_dpmi_allocate_real_mode_callback_iret(&rm_new_handler, &r1);
|
||||||
|
_go32_dpmi_set_real_mode_interrupt_vector(8, &rm_new_handler);
|
||||||
|
|
||||||
|
outportb(PITMODE, 0x36);
|
||||||
|
pit0_value = PITCONST / Hz;
|
||||||
|
pit0_set = (pit0_value & 0x00ff);
|
||||||
|
outportb(PIT0, pit0_set);
|
||||||
|
pit0_set = (pit0_value >> 8);
|
||||||
|
outportb(PIT0, pit0_set);
|
||||||
|
|
||||||
|
ticks_8h = 0;
|
||||||
|
flag8h = NEW8H;
|
||||||
|
freq8h = Hz;
|
||||||
|
counter_8h = 0;
|
||||||
|
counter_reset = freq8h / PIT0DEF;
|
||||||
|
tick_per_ms = freq8h / 1000;
|
||||||
|
ms_per_tick = 1000 / freq8h;
|
||||||
|
|
||||||
|
enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pctimer_exit(void) {
|
||||||
|
// unsigned int pit0_set, pit0_value;
|
||||||
|
unsigned long tick;
|
||||||
|
char *cmostime;
|
||||||
|
|
||||||
|
if (flag8h == NEW8H) {
|
||||||
|
disable();
|
||||||
|
|
||||||
|
outportb(PITMODE, 0x36);
|
||||||
|
outportb(PIT0, 0x00);
|
||||||
|
outportb(PIT0, 0x00);
|
||||||
|
|
||||||
|
_go32_dpmi_set_protected_mode_interrupt_vector(8, &pm_old_handler);
|
||||||
|
|
||||||
|
_go32_dpmi_set_real_mode_interrupt_vector(8, &rm_old_handler);
|
||||||
|
_go32_dpmi_free_real_mode_callback(&rm_new_handler);
|
||||||
|
|
||||||
|
enable();
|
||||||
|
|
||||||
|
cmostime = get_cmostime();
|
||||||
|
tick = PIT0DEF * ((((float)*cmostime) * 3600) + (((float)*(cmostime + 1)) * 60) + (((float)*(cmostime + 2))));
|
||||||
|
biostime(1, tick);
|
||||||
|
|
||||||
|
flag8h = 0;
|
||||||
|
freq8h = PIT0DEF;
|
||||||
|
counter_reset = freq8h / PIT0DEF;
|
||||||
|
tick_per_ms = freq8h / 1000;
|
||||||
|
ms_per_tick = 1000 / freq8h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void rm_new8h(void) {
|
||||||
|
disable();
|
||||||
|
|
||||||
|
if (flag_pm_termination == TRUE_FAILURE) {
|
||||||
|
ticks_8h++;
|
||||||
|
counter_8h++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((counter_8h == counter_reset) || (flag_pm_termination == CHAIN_TERMINATION)) {
|
||||||
|
flag_pm_termination = TRUE_FAILURE;
|
||||||
|
counter_8h = 0;
|
||||||
|
memset(&r, 0, sizeof(r));
|
||||||
|
r.x.cs = rm_old_handler.rm_segment;
|
||||||
|
r.x.ip = rm_old_handler.rm_offset;
|
||||||
|
r.x.ss = r.x.sp = 0;
|
||||||
|
_go32_dpmi_simulate_fcall_iret(&r);
|
||||||
|
enable();
|
||||||
|
} else {
|
||||||
|
flag_pm_termination = TRUE_FAILURE;
|
||||||
|
outportb(0x20, 0x20);
|
||||||
|
enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lock_rm_new8h(void) { _go32_dpmi_lock_code(rm_new8h, (unsigned long)(lock_rm_new8h - rm_new8h)); }
|
||||||
|
|
||||||
|
void pm_new8h(void) {
|
||||||
|
disable();
|
||||||
|
|
||||||
|
ticks_8h++;
|
||||||
|
counter_8h++;
|
||||||
|
|
||||||
|
flag_pm_termination = TRUE_FAILURE;
|
||||||
|
|
||||||
|
if (hook_function) {
|
||||||
|
hook_function();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (counter_8h == counter_reset) {
|
||||||
|
flag_pm_termination = CHAIN_TERMINATION;
|
||||||
|
counter_8h = 0;
|
||||||
|
enable();
|
||||||
|
} else {
|
||||||
|
flag_pm_termination = OUTPORTB_TERMINATION;
|
||||||
|
outportb(0x20, 0x20);
|
||||||
|
enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void lock_pm_new8h(void) { _go32_dpmi_lock_code(pm_new8h, (unsigned long)(lock_pm_new8h - pm_new8h)); }
|
||||||
|
|
||||||
|
unsigned long pctimer_get_ticks(void) { return ticks_8h; }
|
||||||
|
|
||||||
|
unsigned long pctimer_time(unsigned long start, unsigned long finish) {
|
||||||
|
unsigned long duration, millisec;
|
||||||
|
|
||||||
|
if (finish < start)
|
||||||
|
return 0;
|
||||||
|
else {
|
||||||
|
duration = finish - start;
|
||||||
|
millisec = duration * ms_per_tick;
|
||||||
|
return millisec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pctimer_sleep(unsigned int delayms) {
|
||||||
|
unsigned long int delaybegin = 0;
|
||||||
|
unsigned long int delayend = 0;
|
||||||
|
unsigned int delaytick;
|
||||||
|
|
||||||
|
delaytick = delayms * tick_per_ms;
|
||||||
|
|
||||||
|
if (flag8h == NEW8H)
|
||||||
|
delaybegin = pctimer_get_ticks();
|
||||||
|
else
|
||||||
|
delaybegin = biostime(0, 0);
|
||||||
|
|
||||||
|
do {
|
||||||
|
if (flag8h == NEW8H)
|
||||||
|
delayend = pctimer_get_ticks();
|
||||||
|
else
|
||||||
|
delayend = biostime(0, 0);
|
||||||
|
} while ((delayend - delaybegin) < delaytick);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pctimer_sound(int freq, int duration) {
|
||||||
|
int byte;
|
||||||
|
unsigned int freq1;
|
||||||
|
|
||||||
|
freq1 = PITCONST / freq;
|
||||||
|
outportb(PITMODE, 0xb6);
|
||||||
|
byte = (freq1 & 0xff);
|
||||||
|
outportb(PIT2, byte);
|
||||||
|
byte = (freq1 >> 8);
|
||||||
|
outportb(PIT2, byte);
|
||||||
|
byte = inportb(KBCTRL);
|
||||||
|
outportb(KBCTRL, (byte | 3));
|
||||||
|
|
||||||
|
pctimer_sleep(duration);
|
||||||
|
outportb(KBCTRL, (byte & 0xfc));
|
||||||
|
}
|
||||||
|
|
||||||
|
char *get_cmostime(void) {
|
||||||
|
char *buff;
|
||||||
|
static char buffer[6];
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
buff = buffer;
|
||||||
|
memset(&r, 0, sizeof(r));
|
||||||
|
r.h.ah = 0x02;
|
||||||
|
_go32_dpmi_simulate_int(0x1a, &r);
|
||||||
|
|
||||||
|
ch = r.h.ch;
|
||||||
|
buffer[0] = (char)((int)(ch & 0x0f) + (int)((ch >> 4) & 0x0f) * 10);
|
||||||
|
ch = r.h.cl;
|
||||||
|
buffer[1] = (char)((int)(ch & 0x0f) + (int)((ch >> 4) & 0x0f) * 10);
|
||||||
|
ch = r.h.dh;
|
||||||
|
buffer[2] = (char)((int)(ch & 0x0f) + (int)((ch >> 4) & 0x0f) * 10);
|
||||||
|
buffer[3] = r.h.dl;
|
||||||
|
buffer[4] = (char)(r.x.flags & 0x0001);
|
||||||
|
buffer[5] = 0x00;
|
||||||
|
|
||||||
|
return (buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pctimer_set_hook(void (*proc)()) { hook_function = proc; }
|
||||||
19
src/lib/pctimer/gccint8.h
Normal file
19
src/lib/pctimer/gccint8.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* PCTIMER 1.4
|
||||||
|
Millisecond Resolution Timing Library for DJGPP V2
|
||||||
|
Release Date: 3/15/1998
|
||||||
|
Chih-Hao Tsai (hao520@yahoo.com)
|
||||||
|
*/
|
||||||
|
|
||||||
|
void pctimer_init(unsigned int);
|
||||||
|
void pctimer_exit(void);
|
||||||
|
void rm_new8h(void);
|
||||||
|
void lock_rm_new8h(void);
|
||||||
|
void pm_new8h(void);
|
||||||
|
void lock_pm_new8h(void);
|
||||||
|
unsigned long pctimer_get_ticks(void);
|
||||||
|
unsigned long pctimer_time(unsigned long, unsigned long);
|
||||||
|
void pctimer_sleep(unsigned int);
|
||||||
|
void pctimer_sound(int, int);
|
||||||
|
char *get_cmostime(void);
|
||||||
|
|
||||||
|
void pctimer_set_hook(void (*proc)());
|
||||||
157
src/lib/pctimer/readme.txt
Normal file
157
src/lib/pctimer/readme.txt
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
PCTIMER: Millisecond Resolution Timing Library for DJGPP V2
|
||||||
|
Version 1.4 Release Notes
|
||||||
|
March 15, 1998
|
||||||
|
Status: Freeware.
|
||||||
|
Distribution status: Has to be distributed as this archive.
|
||||||
|
Send comments to: Chih-Hao Tsai (hao520@yahoo.com).
|
||||||
|
|
||||||
|
|
||||||
|
==== A FEW WORDS ON WIN95
|
||||||
|
|
||||||
|
Although I have only tested PCTIMER with Win95 and CWSDPMI,
|
||||||
|
PCTIMER should run on most DPMI servers.
|
||||||
|
|
||||||
|
However, theoretically, applications running under Win95
|
||||||
|
should not touch hardware interrupts. The "correct" method
|
||||||
|
of doing millisecond resolution timing under Win95 is to call
|
||||||
|
Windows API. The standard Multimedia Timer API can do
|
||||||
|
millisecond resolution timing. (You'll need to include
|
||||||
|
<mmsystem.h> and link with winmm.lib. But, as far as I know,
|
||||||
|
RSXNT does not provide Multimedia API access.)
|
||||||
|
|
||||||
|
If you need an example on using Windows API to do millisecond
|
||||||
|
resolution timing, check this out (I used Visual C++ 4.0):
|
||||||
|
|
||||||
|
Test Report: Millisecond Resolution Timing with Win95/VC4
|
||||||
|
http://www.geocities.com/hao510/w98timer/
|
||||||
|
|
||||||
|
|
||||||
|
==== BASIC LOGIC
|
||||||
|
|
||||||
|
PCTIMER reprograms the 8454 IC (Programmable Interrupt Timer)
|
||||||
|
to get high frequency of System Timer Interrupt (IRQ0) whose
|
||||||
|
default frequency is 18.2 Hz. Since the operating systems
|
||||||
|
rely heavily on the System Timer Interrupt, increasing its
|
||||||
|
frequency could cause instability. PCTIMER hooks both
|
||||||
|
protected and real mode int 8h handlers (default handler for
|
||||||
|
System Timer Interrupt) reprograms 8254 to get higher
|
||||||
|
frequency of interrupts, but calls the original handlers at
|
||||||
|
a fixed frequency of 18.2 Hz.
|
||||||
|
|
||||||
|
|
||||||
|
==== RELATIONSHIP BETWEEN PROTECTED & REAL MODE INT 8H
|
||||||
|
|
||||||
|
According to DJGPP V2 FAQ, hardware interrupts are always
|
||||||
|
passed to protected mode first, and only if unhandled are
|
||||||
|
they reflected to real mode. In other words, we must at least
|
||||||
|
hook protected mode int 8h. To avoid potential loss of ticks
|
||||||
|
when the protected mode fails to handle the hardware
|
||||||
|
interrupt, we should also hook real mode int 8h.
|
||||||
|
|
||||||
|
In actual implementation of the two handlers, things become
|
||||||
|
much more complex than that.
|
||||||
|
|
||||||
|
|
||||||
|
==== PCTIMER PROTECTED MODE INT8H HANDLER
|
||||||
|
|
||||||
|
Here is PCTIMER's protected mode int 8h in pseudo code. The
|
||||||
|
meanings of pm_termination_flag's values are:
|
||||||
|
|
||||||
|
TRUE_FAILURE: The handler failed to handle the hardware
|
||||||
|
interrupt.
|
||||||
|
|
||||||
|
CHAIN_TERMINATION: The handler terminated with chaining to
|
||||||
|
the old handler. Note that after chaining the old protected
|
||||||
|
mode int 8h handler, the *real* mode int 8h *will* get called.
|
||||||
|
We need to take care of this.
|
||||||
|
|
||||||
|
OUTPORTB_TERMINATION: The handler terminated with an
|
||||||
|
outportb (0x20, 0x20) instruction. This instruction sends
|
||||||
|
a hardware request to the Interrupt Controller to terminate
|
||||||
|
the interrupt. This works (although intrusive), but will
|
||||||
|
cause the DPMI server to believe that the protected mode
|
||||||
|
handler failed to do its job. Therefore, the real mode
|
||||||
|
handler *will* get called. We need to take care of this,
|
||||||
|
too.
|
||||||
|
|
||||||
|
(Read the real code for details.)
|
||||||
|
|
||||||
|
PCTIMER Protected Mode Int 8h Handler (Pseudo-code)
|
||||||
|
* pm_termination_flag = TRUE_FAILURE
|
||||||
|
* counter++
|
||||||
|
* if it is time to chain old handler
|
||||||
|
- pm_termination_flag = CHAIN_TERMINATION
|
||||||
|
- let the wrapper chains the old handler
|
||||||
|
* else
|
||||||
|
- pm_termination_flag = OUTPORTB_TERMINATION
|
||||||
|
- outportb (0x20, 0x20)
|
||||||
|
|
||||||
|
|
||||||
|
==== PCTIMER REAL MODE INT8H HANDLER
|
||||||
|
|
||||||
|
The real mode handler is considerably more complex than the
|
||||||
|
protected mode one. It depends on pm_termination_flag to
|
||||||
|
determine how it should behave. Always set
|
||||||
|
pm_termination_flag to TRUE_FAILURE before we leave, so in
|
||||||
|
case the protected mode handler should fail, the real mode
|
||||||
|
handler can detect it next time.
|
||||||
|
|
||||||
|
(Read the real code for details.)
|
||||||
|
|
||||||
|
PCTIMER Real Mode Int 8h Handler (Pseudo-code)
|
||||||
|
* if pm_termination_flag = TRUE_FAILURE
|
||||||
|
- counter++
|
||||||
|
* if it is time to chain the old handler, or if the protected
|
||||||
|
mode handler decided to do that (i.e.,
|
||||||
|
pm_termination_flag = CHAIN_TERMINATION)
|
||||||
|
- call old real mode handler
|
||||||
|
- pm_termination_flag = TRUE_FAILURE
|
||||||
|
* else
|
||||||
|
- outportb (0x20, 0x20)
|
||||||
|
- pm_termination_flag = TRUE_FAILURE
|
||||||
|
|
||||||
|
|
||||||
|
==== Example of Usage
|
||||||
|
|
||||||
|
#include <gccint8.h>
|
||||||
|
|
||||||
|
void main (void)
|
||||||
|
{
|
||||||
|
unsigned long int start, finish, elapsed_time;
|
||||||
|
|
||||||
|
/* initiate the timer with 1/1000 s resolution */
|
||||||
|
/* you can use different resolution, but resolution */
|
||||||
|
/* higher than 1000 is not recommended. */
|
||||||
|
|
||||||
|
pctimer_init (1000);
|
||||||
|
|
||||||
|
start = pctimer_get_ticks ();
|
||||||
|
/* do some stuff here */
|
||||||
|
finish = pctimer_get_ticks ();
|
||||||
|
elapsed_time = pctimer_time (start, finish);
|
||||||
|
/* always returns elapsed time in the unit of ms. */
|
||||||
|
|
||||||
|
pctimer_sleep (200); /* sleep 200 ms */
|
||||||
|
pctimer_sound (800, 200); /* 800 Hz sound for 200 ms */
|
||||||
|
|
||||||
|
/* never forget to exit the timer!!! */
|
||||||
|
/* otherwise, the system WILL crash!!! */
|
||||||
|
|
||||||
|
pctimer_exit ();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
==== HISTORY
|
||||||
|
3/15/98 Version 1.4
|
||||||
|
11/26/95 Version 1.3
|
||||||
|
1/29/95 Version 1.2
|
||||||
|
1/16/95 Version 1.1
|
||||||
|
11/5/94 Version 1.0
|
||||||
|
|
||||||
|
|
||||||
|
==== DISCLAIMER
|
||||||
|
|
||||||
|
I am not at all responsible for any damages, consequential
|
||||||
|
or incidental, and by using PCTIMER, you are agreeing not
|
||||||
|
to hold either of the above responsible for any problems
|
||||||
|
whatsoever.
|
||||||
@@ -22,11 +22,15 @@
|
|||||||
#include "image.h"
|
#include "image.h"
|
||||||
#include "palette.h"
|
#include "palette.h"
|
||||||
#include "package.h"
|
#include "package.h"
|
||||||
|
#include "vgm.h"
|
||||||
|
#include "lib/pctimer/gccint8.h"
|
||||||
|
|
||||||
static lua_State *L;
|
static lua_State *L;
|
||||||
|
|
||||||
static void deinit(void) {
|
static void deinit(void) {
|
||||||
/* Deinit and clear up everything. Called at exit */
|
/* Deinit and clear up everything. Called at exit */
|
||||||
|
shutdown_vgm();
|
||||||
|
pctimer_exit();
|
||||||
audio_deinit();
|
audio_deinit();
|
||||||
vga_deinit();
|
vga_deinit();
|
||||||
keyboard_deinit();
|
keyboard_deinit();
|
||||||
@@ -60,6 +64,8 @@ int main(int argc, char **argv) {
|
|||||||
palette_init();
|
palette_init();
|
||||||
keyboard_init();
|
keyboard_init();
|
||||||
mouse_init();
|
mouse_init();
|
||||||
|
pctimer_init(1000);
|
||||||
|
init_vgm();
|
||||||
|
|
||||||
/* Init lua */
|
/* Init lua */
|
||||||
L = luaL_newstate();
|
L = luaL_newstate();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
#include "luaobj.h"
|
#include "luaobj.h"
|
||||||
|
|
||||||
#define LOVE_VERSION "0.42.23"
|
#define LOVE_VERSION "0.42.23.1"
|
||||||
|
|
||||||
int l_love_getVersion(lua_State *L) {
|
int l_love_getVersion(lua_State *L) {
|
||||||
lua_pushstring(L, LOVE_VERSION);
|
lua_pushstring(L, LOVE_VERSION);
|
||||||
@@ -26,6 +26,7 @@ int luaopen_audio(lua_State *L);
|
|||||||
int luaopen_timer(lua_State *L);
|
int luaopen_timer(lua_State *L);
|
||||||
int luaopen_keyboard(lua_State *L);
|
int luaopen_keyboard(lua_State *L);
|
||||||
int luaopen_mouse(lua_State *L);
|
int luaopen_mouse(lua_State *L);
|
||||||
|
int luaopen_vgm(lua_State *L);
|
||||||
|
|
||||||
int luaopen_love(lua_State *L) {
|
int luaopen_love(lua_State *L) {
|
||||||
int i;
|
int i;
|
||||||
@@ -53,6 +54,7 @@ int luaopen_love(lua_State *L) {
|
|||||||
int (*fn)(lua_State *L);
|
int (*fn)(lua_State *L);
|
||||||
} mods[] = {
|
} mods[] = {
|
||||||
{"system", luaopen_system},
|
{"system", luaopen_system},
|
||||||
|
{"vgm", luaopen_vgm},
|
||||||
{"event", luaopen_event},
|
{"event", luaopen_event},
|
||||||
{"filesystem", luaopen_filesystem},
|
{"filesystem", luaopen_filesystem},
|
||||||
{"graphics", luaopen_graphics},
|
{"graphics", luaopen_graphics},
|
||||||
|
|||||||
40
src/modules/l_vgm.c
Normal file
40
src/modules/l_vgm.c
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2017 rxi
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the MIT license. See LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <dos.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "luaobj.h"
|
||||||
|
#include "vgm.h"
|
||||||
|
|
||||||
|
int l_vgm_load(lua_State *L) {
|
||||||
|
const char *filename = luaL_checkstring(L, 1);
|
||||||
|
const char *err = VgmLoad(filename);
|
||||||
|
if (err)
|
||||||
|
luaL_error(L, err);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_vgm_play(lua_State *L) {
|
||||||
|
VgmPlay();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int l_vgm_stop(lua_State *L) {
|
||||||
|
VgmStop();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int luaopen_vgm(lua_State *L) {
|
||||||
|
luaL_Reg reg[] = {
|
||||||
|
{"VgmLoad", l_vgm_load},
|
||||||
|
{"VgmPlay", l_vgm_play},
|
||||||
|
{"VgmStop", l_vgm_stop},
|
||||||
|
{0, 0},
|
||||||
|
};
|
||||||
|
luaL_newlib(L, reg);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
370
src/vgm.c
Normal file
370
src/vgm.c
Normal file
@@ -0,0 +1,370 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019-2023 Andre Seidelt <superilu@yahoo.com>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
VGM specs are at https://vgmrips.net/wiki/VGM_Specification
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <pc.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <go32.h>
|
||||||
|
#include <dpmi.h>
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "vgm.h"
|
||||||
|
#include "lib/pctimer/gccint8.h"
|
||||||
|
#include "filesystem.h"
|
||||||
|
|
||||||
|
#define VGM_RESOLUTION 44100
|
||||||
|
#define VGM_FACTOR 44
|
||||||
|
#define VGM_OPL_ADDR 0x388
|
||||||
|
#define VGM_OPL_DATA 0x389
|
||||||
|
|
||||||
|
#define END_OF_FUNCTION(x) \
|
||||||
|
static void x##_end(void); \
|
||||||
|
static void x##_end() {}
|
||||||
|
#define LOCK_VARIABLE(x) _go32_dpmi_lock_data((void *)&x, sizeof(x))
|
||||||
|
#define LOCK_FUNCTION(x) _go32_dpmi_lock_code(x, (long)x##_end - (long)x)
|
||||||
|
|
||||||
|
// #define VGM_DUMP
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t Vgmident;
|
||||||
|
uint32_t EoFoffset;
|
||||||
|
uint32_t Version;
|
||||||
|
uint32_t SN76489clock;
|
||||||
|
uint32_t YM2413clock;
|
||||||
|
uint32_t GD3offset;
|
||||||
|
uint32_t Totalsamples;
|
||||||
|
uint32_t Loopoffset;
|
||||||
|
uint32_t Loopsamples;
|
||||||
|
uint32_t Rate;
|
||||||
|
uint32_t SNFBSNWSF;
|
||||||
|
uint32_t YM2612clock;
|
||||||
|
uint32_t YM2151clock;
|
||||||
|
uint32_t VGMdataoffset;
|
||||||
|
uint32_t SegaPCMclock;
|
||||||
|
uint32_t SPCMInterface;
|
||||||
|
uint32_t RF5C68clock;
|
||||||
|
uint32_t YM2203clock;
|
||||||
|
uint32_t YM2608clock;
|
||||||
|
uint32_t YM2610Bclock;
|
||||||
|
uint32_t YM3812clock;
|
||||||
|
uint32_t YM3526clock;
|
||||||
|
uint32_t Y8950clock;
|
||||||
|
uint32_t YMF262clock;
|
||||||
|
uint32_t YMF278Bclock;
|
||||||
|
uint32_t YMF271clock;
|
||||||
|
uint32_t YMZ280Bclock;
|
||||||
|
uint32_t RF5C164clock;
|
||||||
|
uint32_t PWMclock;
|
||||||
|
uint32_t AY8910clock;
|
||||||
|
uint32_t AYTAYFlags;
|
||||||
|
uint32_t VMLBLM;
|
||||||
|
uint32_t GBDMGclock;
|
||||||
|
uint32_t NESAPUclock;
|
||||||
|
uint32_t MultiPCMclock;
|
||||||
|
uint32_t uPD7759clock;
|
||||||
|
uint32_t OKIM6258clock;
|
||||||
|
uint32_t OFKFCF;
|
||||||
|
uint32_t OKIM6295clock;
|
||||||
|
uint32_t K051649clock;
|
||||||
|
uint32_t K054539clock;
|
||||||
|
uint32_t HuC6280clock;
|
||||||
|
uint32_t C140clock;
|
||||||
|
uint32_t K053260clock;
|
||||||
|
uint32_t Pokeyclock;
|
||||||
|
uint32_t QSoundclock;
|
||||||
|
uint32_t SCSPclock;
|
||||||
|
uint32_t ExtraHdrofs;
|
||||||
|
uint32_t WonderSwanclock;
|
||||||
|
uint32_t VSUclock;
|
||||||
|
uint32_t SAA1099clock;
|
||||||
|
uint32_t ES5503clock;
|
||||||
|
uint32_t ES5506clock;
|
||||||
|
uint32_t ESchnsCD;
|
||||||
|
uint32_t X1010clock;
|
||||||
|
uint32_t C352clock;
|
||||||
|
uint32_t GA20clock;
|
||||||
|
uint32_t unused1;
|
||||||
|
uint32_t unused2;
|
||||||
|
uint32_t unused3;
|
||||||
|
uint32_t unused4;
|
||||||
|
uint32_t unused5;
|
||||||
|
uint32_t unused6;
|
||||||
|
uint32_t unused7;
|
||||||
|
} vgm_t;
|
||||||
|
|
||||||
|
static volatile uint8_t *vgm = NULL;
|
||||||
|
static volatile vgm_t *vgm_header = NULL;
|
||||||
|
static volatile bool vgm_playback = false;
|
||||||
|
static volatile uint8_t *vgm_data = NULL;
|
||||||
|
static volatile uint32_t vgm_pos = 0;
|
||||||
|
static volatile uint32_t vgm_end = 0;
|
||||||
|
static volatile int32_t vgm_wait = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the given byte of data to the given register of the OPL2 chip.
|
||||||
|
*/
|
||||||
|
static void vgm_opl_write(uint8_t reg, uint8_t val) {
|
||||||
|
// Select register
|
||||||
|
outp(VGM_OPL_ADDR, reg);
|
||||||
|
|
||||||
|
// Wait for card to accept value
|
||||||
|
for (int i = 1; i < 25; i++) {
|
||||||
|
inp(VGM_OPL_ADDR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send value
|
||||||
|
outp(VGM_OPL_DATA, val);
|
||||||
|
|
||||||
|
// Wait for card to accept value
|
||||||
|
for (int i = 1; i < 100; i++) {
|
||||||
|
inp(VGM_OPL_ADDR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END_OF_FUNCTION(vgm_opl_write);
|
||||||
|
|
||||||
|
static void vgm_int() {
|
||||||
|
if (vgm_wait > 0) {
|
||||||
|
vgm_wait -= VGM_FACTOR;
|
||||||
|
} else {
|
||||||
|
while (vgm_pos < vgm_end) {
|
||||||
|
uint8_t cmd = vgm_data[vgm_pos];
|
||||||
|
|
||||||
|
if (cmd >= 0x70 && cmd < 0x80) {
|
||||||
|
// wait n+1 samples, n can range from 0 to 15.
|
||||||
|
vgm_wait = 1 + (cmd & 0x0F);
|
||||||
|
vgm_pos++;
|
||||||
|
} else if (cmd == 0x5a) {
|
||||||
|
// YM2413, write value dd to register aa
|
||||||
|
uint8_t aa = vgm_data[vgm_pos + 1];
|
||||||
|
uint8_t dd = vgm_data[vgm_pos + 2];
|
||||||
|
vgm_opl_write(aa, dd);
|
||||||
|
vgm_pos += 3;
|
||||||
|
} else if (cmd == 0x61) {
|
||||||
|
// Wait n samples, n can range from 0 to 65535 (approx 1.49 seconds).
|
||||||
|
// Longer pauses than this are represented by multiple wait commands.
|
||||||
|
vgm_wait = *((uint16_t *)(&vgm_data[vgm_pos + 1]));
|
||||||
|
vgm_pos += 3;
|
||||||
|
} else if (cmd == 0x62) {
|
||||||
|
// wait 735 samples (60th of a second), a shortcut for 0x61 0xdf 0x02
|
||||||
|
vgm_wait = 735;
|
||||||
|
vgm_pos++;
|
||||||
|
} else if (cmd == 0x63) {
|
||||||
|
// wait 882 samples (50th of a second), a shortcut for 0x61 0x72 0x03
|
||||||
|
vgm_wait = 882;
|
||||||
|
vgm_pos++;
|
||||||
|
} else if (cmd == 0x66) {
|
||||||
|
// end of sound data --> loop
|
||||||
|
vgm_pos = 0;
|
||||||
|
break;
|
||||||
|
} else if (cmd == 0x67) {
|
||||||
|
// data block: see below
|
||||||
|
vgm_pos += 3; // cmd 0x66 and type
|
||||||
|
vgm_pos += *((uint32_t *)(&vgm_data[vgm_pos])); // add size
|
||||||
|
vgm_pos += 4; // add 4 bytes because of size
|
||||||
|
} else {
|
||||||
|
// unknown cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// only wait when the waiting time is longer than our IRQ interval
|
||||||
|
if (vgm_wait > VGM_FACTOR) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
vgm_wait = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END_OF_FUNCTION(vgm_int);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* detect the presence of a FM card.
|
||||||
|
*/
|
||||||
|
static bool vgm_opl_detect() {
|
||||||
|
uint8_t A, B;
|
||||||
|
|
||||||
|
vgm_opl_write(1, 0);
|
||||||
|
vgm_opl_write(4, 0x60);
|
||||||
|
vgm_opl_write(4, 0x80);
|
||||||
|
A = inp(VGM_OPL_ADDR);
|
||||||
|
vgm_opl_write(2, 0xFF);
|
||||||
|
vgm_opl_write(4, 0x21);
|
||||||
|
pctimer_sleep(80);
|
||||||
|
B = inp(VGM_OPL_ADDR);
|
||||||
|
vgm_opl_write(4, 0x60);
|
||||||
|
vgm_opl_write(4, 0x80);
|
||||||
|
if ((A & 0xE0) == 0 && (B & 0xE0) == 0xC0) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hard reset the OPL2 chip. This should be done before sending any register
|
||||||
|
* data to the chip.
|
||||||
|
*/
|
||||||
|
static void vgm_opl_reset() {
|
||||||
|
for (int i = 0; i < 256; i++) {
|
||||||
|
vgm_opl_write(i, 0x00);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef VGM_DUMP
|
||||||
|
static void vgm_dump() {
|
||||||
|
LOGF("VGM version %08X\n", vgm_header->Version);
|
||||||
|
|
||||||
|
int pos = 0;
|
||||||
|
while (pos < vgm_end) {
|
||||||
|
uint8_t cmd = vgm_data[pos];
|
||||||
|
|
||||||
|
if (cmd >= 0x70 && cmd < 0x80) {
|
||||||
|
// wait n+1 samples, n can range from 0 to 15.
|
||||||
|
LOGF("WAIT_7x %d\n", 1 + (cmd & 0x0F));
|
||||||
|
pos++;
|
||||||
|
} else if (cmd == 0x5a) {
|
||||||
|
// YM2413, write value dd to register aa
|
||||||
|
uint8_t aa = vgm_data[pos + 1];
|
||||||
|
uint8_t dd = vgm_data[pos + 2];
|
||||||
|
LOGF("WRITE 0x%02X 0x%02X\n", aa, dd);
|
||||||
|
pos += 3;
|
||||||
|
} else if (cmd == 0x61) {
|
||||||
|
// Wait n samples, n can range from 0 to 65535 (approx 1.49 seconds).
|
||||||
|
// Longer pauses than this are represented by multiple wait commands.
|
||||||
|
LOGF("WAIT_62 %d\n", *((uint16_t *)(&vgm_data[pos + 1])));
|
||||||
|
pos += 3;
|
||||||
|
} else if (cmd == 0x62) {
|
||||||
|
// wait 735 samples (60th of a second), a shortcut for 0x61 0xdf 0x02
|
||||||
|
LOG("WAIT_62 735\n");
|
||||||
|
pos++;
|
||||||
|
} else if (cmd == 0x63) {
|
||||||
|
// wait 882 samples (50th of a second), a shortcut for 0x61 0x72 0x03
|
||||||
|
LOG("WAIT_63 882\n");
|
||||||
|
pos++;
|
||||||
|
} else if (cmd == 0x66) {
|
||||||
|
// end of sound data
|
||||||
|
LOG("EOS\n");
|
||||||
|
break;
|
||||||
|
} else if (cmd == 0x67) {
|
||||||
|
// data block: see below
|
||||||
|
vgm_pos += 3; // cmd 0x66 and type
|
||||||
|
LOGF("DATA size=%ld\n", *((uint32_t *)(&vgm_data[vgm_pos])));
|
||||||
|
vgm_pos += *((uint32_t *)(&vgm_data[vgm_pos])); // add size
|
||||||
|
vgm_pos += 4; // add 4 bytes because of size
|
||||||
|
} else {
|
||||||
|
LOGF("UNKNOWN 0x%02X\n", cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void VgmPlay() {
|
||||||
|
if (vgm_data) {
|
||||||
|
vgm_pos = 0;
|
||||||
|
vgm_wait = 0;
|
||||||
|
vgm_playback = true;
|
||||||
|
|
||||||
|
pctimer_set_hook(vgm_int);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void VgmStop() {
|
||||||
|
vgm_playback = false;
|
||||||
|
pctimer_set_hook(NULL);
|
||||||
|
|
||||||
|
vgm_opl_reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void VgmDiscard() {
|
||||||
|
VgmStop();
|
||||||
|
if (vgm) {
|
||||||
|
filesystem_free((void *)vgm);
|
||||||
|
}
|
||||||
|
vgm = NULL;
|
||||||
|
vgm_playback = false;
|
||||||
|
vgm_pos = 0;
|
||||||
|
vgm_end = 0;
|
||||||
|
vgm_wait = 0;
|
||||||
|
vgm_header = NULL;
|
||||||
|
vgm_data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *VgmLoad(const char *fname) {
|
||||||
|
if (!vgm_opl_detect()) {
|
||||||
|
return "OPL2 not detected!";
|
||||||
|
}
|
||||||
|
|
||||||
|
VgmDiscard();
|
||||||
|
|
||||||
|
/* Load file data */
|
||||||
|
int size;
|
||||||
|
vgm = filesystem_read(fname, &size);
|
||||||
|
if (!vgm) {
|
||||||
|
return "could not read file";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memcmp((const void *)vgm, "Vgm ", 4) != 0) {
|
||||||
|
filesystem_free((void *)vgm);
|
||||||
|
return "VGM header error.";
|
||||||
|
}
|
||||||
|
|
||||||
|
vgm_header = (vgm_t *)vgm;
|
||||||
|
if (vgm_header->EoFoffset != size - 4) {
|
||||||
|
filesystem_free((void *)vgm);
|
||||||
|
return "VGM format error.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vgm_header->Version < 0x00000151) {
|
||||||
|
filesystem_free((void *)vgm);
|
||||||
|
return "only VGM >= 1.51 is supported.";
|
||||||
|
}
|
||||||
|
|
||||||
|
// set pointers
|
||||||
|
vgm_data =
|
||||||
|
((uint8_t *)&vgm_header->VGMdataoffset) + vgm_header->VGMdataoffset;
|
||||||
|
vgm_end = size - (vgm_data - vgm);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/***********************
|
||||||
|
** exported functions **
|
||||||
|
***********************/
|
||||||
|
/**
|
||||||
|
* @brief initialize vgm subsystem.
|
||||||
|
*
|
||||||
|
* @param J VM state.
|
||||||
|
*/
|
||||||
|
void init_vgm() {
|
||||||
|
// lock down
|
||||||
|
LOCK_FUNCTION(vgm_int);
|
||||||
|
LOCK_FUNCTION(vgm_opl_write);
|
||||||
|
LOCK_VARIABLE(vgm_playback);
|
||||||
|
LOCK_VARIABLE(vgm_pos);
|
||||||
|
LOCK_VARIABLE(vgm_data);
|
||||||
|
LOCK_VARIABLE(vgm_wait);
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown_vgm() { VgmDiscard(); }
|
||||||
36
src/vgm.h
Normal file
36
src/vgm.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019-2023 Andre Seidelt <superilu@yahoo.com>
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __VGMPLAY_H__
|
||||||
|
#define __VGMPLAY_H__
|
||||||
|
|
||||||
|
/*********************
|
||||||
|
** static functions **
|
||||||
|
*********************/
|
||||||
|
extern char *VgmLoad(const char *fname);
|
||||||
|
extern void VgmPlay(void);
|
||||||
|
extern void VgmStop(void);
|
||||||
|
|
||||||
|
extern void init_vgm(void);
|
||||||
|
extern void shutdown_vgm(void);
|
||||||
|
|
||||||
|
#endif // __VGMPLAY_H__
|
||||||
Reference in New Issue
Block a user