added VGM playback

This commit is contained in:
Ilu
2023-05-13 19:36:43 +02:00
parent 49af8d6c03
commit cbada2817f
10 changed files with 888 additions and 1 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ DOSBox/
LOVE/
rel/
test/
test2/

View File

@@ -10,6 +10,7 @@
* [love.filesystem](#lovefilesystem)
* [love.audio](#loveaudio)
* [love.event](#loveevent)
* [love.vgm](#lovevgm)
##### [Objects](#objects-1)
* [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.
### 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
Provides functions for drawing lines, shapes, text and images.

243
src/lib/pctimer/gccint8.c Normal file
View 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
View 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
View 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.

View File

@@ -22,11 +22,15 @@
#include "image.h"
#include "palette.h"
#include "package.h"
#include "vgm.h"
#include "lib/pctimer/gccint8.h"
static lua_State *L;
static void deinit(void) {
/* Deinit and clear up everything. Called at exit */
shutdown_vgm();
pctimer_exit();
audio_deinit();
vga_deinit();
keyboard_deinit();
@@ -60,6 +64,8 @@ int main(int argc, char **argv) {
palette_init();
keyboard_init();
mouse_init();
pctimer_init(1000);
init_vgm();
/* Init lua */
L = luaL_newstate();

View File

@@ -7,7 +7,7 @@
#include "luaobj.h"
#define LOVE_VERSION "0.42.23"
#define LOVE_VERSION "0.42.23.1"
int l_love_getVersion(lua_State *L) {
lua_pushstring(L, LOVE_VERSION);
@@ -26,6 +26,7 @@ int luaopen_audio(lua_State *L);
int luaopen_timer(lua_State *L);
int luaopen_keyboard(lua_State *L);
int luaopen_mouse(lua_State *L);
int luaopen_vgm(lua_State *L);
int luaopen_love(lua_State *L) {
int i;
@@ -53,6 +54,7 @@ int luaopen_love(lua_State *L) {
int (*fn)(lua_State *L);
} mods[] = {
{"system", luaopen_system},
{"vgm", luaopen_vgm},
{"event", luaopen_event},
{"filesystem", luaopen_filesystem},
{"graphics", luaopen_graphics},

40
src/modules/l_vgm.c Normal file
View 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
View 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
View 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__