Add Hello_mod example
This commit is contained in:
parent
2a0348b88b
commit
d04e01159d
@ -4,6 +4,7 @@ TYPE = ps-exe
|
|||||||
THISDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
THISDIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
|
||||||
SRCS += $(THISDIR)thirdparty/nugget/common/crt0/crt0.s
|
SRCS += $(THISDIR)thirdparty/nugget/common/crt0/crt0.s
|
||||||
|
SRCS += $(THISDIR)thirdparty/nugget/common/syscalls/printf.s
|
||||||
|
|
||||||
CPPFLAGS += -I$(THISDIR)thirdparty/nugget/psyq/include -I$(THISDIR)psyq-4_7-converted/include -I$(THISDIR)psyq-4.7-converted-full/include -I$(THISDIR)psyq/include
|
CPPFLAGS += -I$(THISDIR)thirdparty/nugget/psyq/include -I$(THISDIR)psyq-4_7-converted/include -I$(THISDIR)psyq-4.7-converted-full/include -I$(THISDIR)psyq/include
|
||||||
LDFLAGS += -L$(THISDIR)thirdparty/nugget/psyq/lib -L$(THISDIR)psyq-4_7-converted/lib -L$(THISDIR)psyq-4.7-converted-full/lib -L$(THISDIR)psyq/lib
|
LDFLAGS += -L$(THISDIR)thirdparty/nugget/psyq/lib -L$(THISDIR)psyq-4_7-converted/lib -L$(THISDIR)psyq-4.7-converted-full/lib -L$(THISDIR)psyq/lib
|
||||||
@ -49,3 +50,7 @@ endef
|
|||||||
# convert VAG files to bin
|
# convert VAG files to bin
|
||||||
%.o: %.vag
|
%.o: %.vag
|
||||||
$(call OBJCOPYME)
|
$(call OBJCOPYME)
|
||||||
|
|
||||||
|
# convert HIT to bin
|
||||||
|
%.o: %.HIT
|
||||||
|
$(call OBJCOPYME)
|
BIN
hello_mod/HIT/STAR.HIT
Normal file
BIN
hello_mod/HIT/STAR.HIT
Normal file
Binary file not shown.
8
hello_mod/Makefile
Normal file
8
hello_mod/Makefile
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
TARGET = hello_mod
|
||||||
|
|
||||||
|
SRCS = hello_mod.c \
|
||||||
|
src/mod.c \
|
||||||
|
src/modplayer.c \
|
||||||
|
HIT/STAR.HIT \
|
||||||
|
|
||||||
|
include ../common.mk
|
6
hello_mod/README.md
Normal file
6
hello_mod/README.md
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
See the **wiki** for more details on MOD usage : [https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/MOD](https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/MOD).
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
MOD : stardust memories by Jester : https://modarchive.org/index.php?request=view_by_moduleid&query=59344
|
||||||
|
Modplayer port by @NicolasNoble : https://github.com/grumpycoders/pcsx-redux/tree/main/src/mips/modplayer
|
229
hello_mod/hello_mod.c
Normal file
229
hello_mod/hello_mod.c
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
// Play a MOD file converted to HIT
|
||||||
|
// MOD Wiki page : https://github.com/ABelliqueux/nolibgs_hello_worlds/wiki/MOD
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <libgte.h>
|
||||||
|
#include <libetc.h>
|
||||||
|
#include <libgpu.h>
|
||||||
|
// Mod playback
|
||||||
|
#include "src/mod.h"
|
||||||
|
|
||||||
|
#define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL
|
||||||
|
#define SCREENXRES 320 // Screen width
|
||||||
|
#define SCREENYRES 240 // Screen height : If VMODE is 0 = 240, if VMODE is 1 = 256
|
||||||
|
#define CENTERX SCREENXRES/2 // Center of screen on x
|
||||||
|
#define CENTERY SCREENYRES/2 // Center of screen on y
|
||||||
|
#define FONTX 960
|
||||||
|
#define FONTY 0
|
||||||
|
#define OTLEN 8
|
||||||
|
DISPENV disp[2]; // Double buffered DISPENV and DRAWENV
|
||||||
|
DRAWENV draw[2];
|
||||||
|
short db = 1; // index of which buffer is used, values 0, 1
|
||||||
|
|
||||||
|
// Font color
|
||||||
|
CVECTOR fntColor = { 128, 255, 0 };
|
||||||
|
CVECTOR fntColorBG = { 0, 0, 0 };
|
||||||
|
|
||||||
|
// Playback state
|
||||||
|
enum PLAYBACK {
|
||||||
|
STOP = 0,
|
||||||
|
PLAY = 1,
|
||||||
|
PAUSE = 2,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum PLAYBACK state = PLAY;
|
||||||
|
|
||||||
|
void init(void);
|
||||||
|
void FntColor(CVECTOR fgcol, CVECTOR bgcol );
|
||||||
|
void display(void);
|
||||||
|
void drawBG(void);
|
||||||
|
void checkPad(void);
|
||||||
|
|
||||||
|
void FntColor(CVECTOR fgcol, CVECTOR bgcol )
|
||||||
|
{
|
||||||
|
// The debug font clut is at tx, ty + 128
|
||||||
|
// tx = bg color
|
||||||
|
// tx + 1 = fg color
|
||||||
|
// We can override the color by drawing a rect at these coordinates
|
||||||
|
//
|
||||||
|
RECT fg = { FONTX+1, FONTY + 128, 1, 1 };
|
||||||
|
RECT bg = { FONTX, FONTY + 128, 1, 1 };
|
||||||
|
ClearImage(&fg, fgcol.r, fgcol.g, fgcol.b);
|
||||||
|
ClearImage(&bg, bgcol.r, bgcol.g, bgcol.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
void init(void)
|
||||||
|
{
|
||||||
|
ResetCallback();
|
||||||
|
ResetGraph(0); // Initialize drawing engine with a complete reset (0)
|
||||||
|
InitGeom();
|
||||||
|
SetGeomOffset(CENTERX,CENTERY);
|
||||||
|
SetGeomScreen(CENTERX);
|
||||||
|
SetDefDispEnv(&disp[0], 0, 0 , SCREENXRES, SCREENYRES); // Set display area for both &disp[0] and &disp[1]
|
||||||
|
SetDefDispEnv(&disp[1], 0, SCREENYRES, SCREENXRES, SCREENYRES); // &disp[0] is on top of &disp[1]
|
||||||
|
SetDefDrawEnv(&draw[0], 0, SCREENYRES, SCREENXRES, SCREENYRES); // Set draw for both &draw[0] and &draw[1]
|
||||||
|
SetDefDrawEnv(&draw[1], 0, 0 , SCREENXRES, SCREENYRES); // &draw[0] is below &draw[1]
|
||||||
|
// Set video mode
|
||||||
|
#if VMODE
|
||||||
|
SetVideoMode(MODE_PAL);
|
||||||
|
disp[0].disp.y = 8;
|
||||||
|
disp[1].disp.y = 8;
|
||||||
|
#endif
|
||||||
|
SetDispMask(1); // Display on screen
|
||||||
|
setRGB0(&draw[0], 40, 40, 40); // set color for first draw area
|
||||||
|
setRGB0(&draw[1], 40, 40, 40); // set color for second draw area
|
||||||
|
draw[0].isbg = 1; // set mask for draw areas. 1 means repainting the area with the RGB color each frame
|
||||||
|
draw[1].isbg = 1;
|
||||||
|
PutDispEnv(&disp[db]); // set the disp and draw environnments
|
||||||
|
PutDrawEnv(&draw[db]);
|
||||||
|
FntLoad(FONTX, FONTY); // Load font to vram at 960,0(+128)
|
||||||
|
FntOpen(32, 64, 260, 120, 0, 120 ); // FntOpen(x, y, width, height, black_bg, max. nbr. chars
|
||||||
|
FntColor(fntColor, fntColorBG);
|
||||||
|
}
|
||||||
|
void display(void)
|
||||||
|
{
|
||||||
|
DrawSync(0); // Wait for all drawing to terminate
|
||||||
|
VSync(0); // Wait for the next vertical blank
|
||||||
|
PutDispEnv(&disp[db]); // set alternate disp and draw environnments
|
||||||
|
PutDrawEnv(&draw[db]);
|
||||||
|
db = !db;
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkPad(void)
|
||||||
|
{
|
||||||
|
u_short pad = 0;
|
||||||
|
static u_short oldPad;
|
||||||
|
pad = PadRead(0);
|
||||||
|
|
||||||
|
// Up
|
||||||
|
if ( pad & PADLup && !(oldPad & PADLup) )
|
||||||
|
{
|
||||||
|
MOD_PlayNote(11, 25, 15, 63);
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADLup) && oldPad & PADLup )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
// Down
|
||||||
|
if ( pad & PADLdown && !(oldPad & PADLdown) )
|
||||||
|
{
|
||||||
|
MOD_PlayNote(12, 26, 15, 63);
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADLdown) && oldPad & PADLdown )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
// Left
|
||||||
|
if ( pad & PADLleft && !(oldPad & PADLleft) )
|
||||||
|
{
|
||||||
|
MOD_PlayNote(13, 27, 15, 63);
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADLleft) && oldPad & PADLleft )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
// Right
|
||||||
|
if ( pad & PADLright && !(oldPad & PADLright) )
|
||||||
|
{
|
||||||
|
// Channel 1 is transition anim, only take input when !transition
|
||||||
|
MOD_PlayNote(6, 21, 15, 63);
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADLright) && oldPad & PADLright )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
// Cross button
|
||||||
|
if ( pad & PADRdown && !(oldPad & PADRdown) )
|
||||||
|
{
|
||||||
|
// Select sound
|
||||||
|
MOD_PlayNote(7, 22, 15, 63);
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADRdown) && oldPad & PADRdown )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
// Square button
|
||||||
|
if ( pad & PADRleft && !(oldPad & PADRleft) )
|
||||||
|
{
|
||||||
|
// Select sound
|
||||||
|
MOD_PlayNote(8, 23, 15, 63);
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADRleft) && oldPad & PADRleft )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
// Circle button
|
||||||
|
if ( pad & PADRright && !(oldPad & PADRright) )
|
||||||
|
{
|
||||||
|
// Select sound
|
||||||
|
MOD_PlayNote(9, 28, 15, 63);
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADRright) && oldPad & PADRright )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
// Circle button
|
||||||
|
if ( pad & PADRup && !(oldPad & PADRup) )
|
||||||
|
{
|
||||||
|
// Select sound
|
||||||
|
MOD_PlayNote(9, 24, 15, 63);
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADRup) && oldPad & PADRup )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
// Select button
|
||||||
|
if ( pad & PADselect && !(oldPad & PADselect) )
|
||||||
|
{
|
||||||
|
if ( state == PLAY ) { stopMusic(); state = STOP; }
|
||||||
|
else if ( state == STOP ) { startMusic(); state = PLAY; }
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADselect) && oldPad & PADselect )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
// Start button
|
||||||
|
if ( pad & PADstart && !(oldPad & PADstart) )
|
||||||
|
{
|
||||||
|
if ( state == PLAY ) { pauseMusic(); state = PAUSE; }
|
||||||
|
else if ( state == PAUSE ) { resumeMusic(); state = PLAY; }
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
if ( !(pad & PADstart) && oldPad & PADstart )
|
||||||
|
{
|
||||||
|
oldPad = pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
int main() {
|
||||||
|
u_int t = 0;
|
||||||
|
init();
|
||||||
|
PadInit(0);
|
||||||
|
VSyncCallback(checkPad);
|
||||||
|
// Mod Playback
|
||||||
|
loadMod();
|
||||||
|
startMusic();
|
||||||
|
// Main loop
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// TODO: change volume, restart playback
|
||||||
|
t++;
|
||||||
|
FntPrint("Hello mod ! %d\nUse pad buttons to play sounds.\n", t);
|
||||||
|
FntPrint("State: %d\n", state);
|
||||||
|
FntPrint("Start : play/pause music.\n");
|
||||||
|
FntFlush(-1);
|
||||||
|
display();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
66
hello_mod/src/mod.c
Normal file
66
hello_mod/src/mod.c
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#include "mod.h"
|
||||||
|
|
||||||
|
long musicEvent;
|
||||||
|
typedef struct SpuVoiceVolume {
|
||||||
|
short volL, volR;
|
||||||
|
} SpuVoiceVolume;
|
||||||
|
|
||||||
|
SpuVoiceVolume volumeState[24] = {0};
|
||||||
|
|
||||||
|
void muteSPUvoices() {
|
||||||
|
for (unsigned i = 0; i < 24; i++) {
|
||||||
|
// Store current volume
|
||||||
|
SpuGetVoiceVolume(i, &(volumeState[i].volL), &(volumeState[i].volR) );
|
||||||
|
// Mute
|
||||||
|
SpuSetVoiceVolume(i, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void restoreSPUvoices() {
|
||||||
|
for (unsigned i = 0; i < 24; i++) {
|
||||||
|
// Restore volume
|
||||||
|
SpuSetVoiceVolume(i, volumeState[i].volL, volumeState[i].volR );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Playing a sound effect (aka mod note): https://discord.com/channels/642647820683444236/642848592754901033/898249196174458900
|
||||||
|
// Code by NicolasNoble : https://discord.com/channels/642647820683444236/663664210525290507/902624952715452436
|
||||||
|
void loadMod() {
|
||||||
|
printf("Loading MOD:\'%s\'\n", HITFILE);
|
||||||
|
MOD_Load((struct MODFileFormat*)HITFILE);
|
||||||
|
printf("%02d Channels, %02d Orders\n", MOD_Channels, MOD_SongLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startMusic() {
|
||||||
|
ResetRCnt(RCntCNT1);
|
||||||
|
SetRCnt(RCntCNT1, MOD_hblanks, RCntMdINTR);
|
||||||
|
StartRCnt(RCntCNT1);
|
||||||
|
musicEvent = OpenEvent(RCntCNT1, EvSpINT, EvMdINTR, processMusic);
|
||||||
|
EnableEvent(musicEvent);
|
||||||
|
restoreSPUvoices();
|
||||||
|
}
|
||||||
|
|
||||||
|
long processMusic() {
|
||||||
|
uint32_t old_hblanks = MOD_hblanks;
|
||||||
|
MOD_Poll();
|
||||||
|
uint32_t new_hblanks = MOD_hblanks;
|
||||||
|
if (old_hblanks != new_hblanks) SetRCnt(RCntCNT1, new_hblanks, RCntMdINTR);
|
||||||
|
return MOD_hblanks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pauseMusic() {
|
||||||
|
muteSPUvoices();
|
||||||
|
DisableEvent(musicEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resumeMusic() {
|
||||||
|
restoreSPUvoices();
|
||||||
|
EnableEvent(musicEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopMusic() {
|
||||||
|
MOD_Silence();
|
||||||
|
StopRCnt(RCntCNT1);
|
||||||
|
DisableEvent(musicEvent);
|
||||||
|
CloseEvent(musicEvent);
|
||||||
|
}
|
22
hello_mod/src/mod.h
Normal file
22
hello_mod/src/mod.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <libapi.h>
|
||||||
|
#include <libspu.h>
|
||||||
|
#include "../../thirdparty/nugget/common/hardware/hwregs.h"
|
||||||
|
#include "../../thirdparty/nugget/common/hardware/irq.h"
|
||||||
|
#include "../../thirdparty/nugget/common/syscalls/syscalls.h"
|
||||||
|
#define printf ramsyscall_printf
|
||||||
|
|
||||||
|
// Mod Playback
|
||||||
|
#include "modplayer.h"
|
||||||
|
extern const uint8_t _binary_HIT_STAR_HIT_start[];
|
||||||
|
#define HITFILE _binary_HIT_STAR_HIT_start
|
||||||
|
extern long musicEvent;
|
||||||
|
|
||||||
|
void muteSPUvoices();
|
||||||
|
void restoreSPUvoices();
|
||||||
|
void loadMod();
|
||||||
|
long processMusic();
|
||||||
|
void startMusic();
|
||||||
|
void pauseMusic();
|
||||||
|
void resumeMusic();
|
||||||
|
void stopMusic();
|
806
hello_mod/src/modplayer.c
Normal file
806
hello_mod/src/modplayer.c
Normal file
@ -0,0 +1,806 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 PCSX-Redux authors
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "modplayer.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "../../thirdparty/nugget/common/hardware/dma.h"
|
||||||
|
#include "../../thirdparty/nugget/common/hardware/spu.h"
|
||||||
|
#include "../../thirdparty/nugget/common/syscalls/syscalls.h"
|
||||||
|
|
||||||
|
/* This code is a reverse engineering of the file MODPLAY.BIN, located in the zip file
|
||||||
|
"Asm-Mod" from http://hitmen.c02.at/html/psx_tools.html, that has the CRC32 bb91769f. */
|
||||||
|
|
||||||
|
struct MODSampleData {
|
||||||
|
char name[22];
|
||||||
|
union {
|
||||||
|
uint16_t length;
|
||||||
|
uint8_t lenarr[2];
|
||||||
|
};
|
||||||
|
uint8_t finetune;
|
||||||
|
uint8_t volume;
|
||||||
|
uint16_t repeatLocation;
|
||||||
|
uint16_t repeatLength;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MODFileFormat {
|
||||||
|
char title[20];
|
||||||
|
struct MODSampleData samples[31];
|
||||||
|
uint8_t songLength;
|
||||||
|
uint8_t padding;
|
||||||
|
uint8_t patternTable[128];
|
||||||
|
uint8_t signature[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SPUChannelData {
|
||||||
|
uint16_t note;
|
||||||
|
int16_t period;
|
||||||
|
uint16_t slideTo;
|
||||||
|
uint8_t slideSpeed;
|
||||||
|
uint8_t volume;
|
||||||
|
uint8_t sampleID;
|
||||||
|
int8_t vibrato;
|
||||||
|
uint8_t fx[4];
|
||||||
|
uint16_t samplePos;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpuInstrumentData {
|
||||||
|
uint16_t baseAddress;
|
||||||
|
uint8_t finetune;
|
||||||
|
uint8_t volume;
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct SpuInstrumentData s_spuInstrumentData[31];
|
||||||
|
|
||||||
|
static void SPUInit() {
|
||||||
|
DPCR |= 0x000b0000;
|
||||||
|
SPU_VOL_MAIN_LEFT = 0x3800;
|
||||||
|
SPU_VOL_MAIN_RIGHT = 0x3800;
|
||||||
|
SPU_CTRL = 0;
|
||||||
|
SPU_KEY_ON_LOW = 0;
|
||||||
|
SPU_KEY_ON_HIGH = 0;
|
||||||
|
SPU_KEY_OFF_LOW = 0xffff;
|
||||||
|
SPU_KEY_OFF_HIGH = 0xffff;
|
||||||
|
SPU_RAM_DTC = 4;
|
||||||
|
SPU_VOL_CD_LEFT = 0;
|
||||||
|
SPU_VOL_CD_RIGHT = 0;
|
||||||
|
SPU_PITCH_MOD_LOW = 0;
|
||||||
|
SPU_PITCH_MOD_HIGH = 0;
|
||||||
|
SPU_NOISE_EN_LOW = 0;
|
||||||
|
SPU_NOISE_EN_HIGH = 0;
|
||||||
|
SPU_REVERB_EN_LOW = 0;
|
||||||
|
SPU_REVERB_EN_HIGH = 0;
|
||||||
|
SPU_VOL_EXT_LEFT = 0;
|
||||||
|
SPU_VOL_EXT_RIGHT = 0;
|
||||||
|
SPU_CTRL = 0x8000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SPUResetVoice(int voiceID) {
|
||||||
|
SPU_VOICES[voiceID].volumeLeft = 0;
|
||||||
|
SPU_VOICES[voiceID].volumeRight = 0;
|
||||||
|
SPU_VOICES[voiceID].sampleRate = 0;
|
||||||
|
SPU_VOICES[voiceID].sampleStartAddr = 0;
|
||||||
|
SPU_VOICES[voiceID].ad = 0x000f;
|
||||||
|
SPU_VOICES[voiceID].currentVolume = 0;
|
||||||
|
SPU_VOICES[voiceID].sampleRepeatAddr = 0;
|
||||||
|
SPU_VOICES[voiceID].sr = 0x0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SPUUploadInstruments(uint32_t SpuAddr, const uint8_t* data, uint32_t size) {
|
||||||
|
uint32_t bcr = size >> 6;
|
||||||
|
if (size & 0x3f) bcr++;
|
||||||
|
bcr <<= 16;
|
||||||
|
bcr |= 0x10;
|
||||||
|
|
||||||
|
SPU_RAM_DTA = SpuAddr >> 3;
|
||||||
|
SPU_CTRL = (SPU_CTRL & ~0x0030) | 0x0020;
|
||||||
|
while ((SPU_CTRL & 0x0030) != 0x0020)
|
||||||
|
;
|
||||||
|
// original code erroneously was doing SBUS_DEV4_CTRL = SBUS_DEV4_CTRL;
|
||||||
|
SBUS_DEV4_CTRL &= ~0x0f000000;
|
||||||
|
DMA_CTRL[DMA_SPU].MADR = (uint32_t)data;
|
||||||
|
DMA_CTRL[DMA_SPU].BCR = bcr;
|
||||||
|
DMA_CTRL[DMA_SPU].CHCR = 0x01000201;
|
||||||
|
|
||||||
|
while ((DMA_CTRL[DMA_SPU].CHCR & 0x01000000) != 0)
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SPUUnMute() { SPU_CTRL = 0xc000; }
|
||||||
|
|
||||||
|
static void SPUSetVoiceVolume(int voiceID, uint16_t left, uint16_t right) {
|
||||||
|
SPU_VOICES[voiceID].volumeLeft = left >> 2;
|
||||||
|
SPU_VOICES[voiceID].volumeRight = right >> 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SPUSetStartAddress(int voiceID, uint32_t spuAddr) { SPU_VOICES[voiceID].sampleStartAddr = spuAddr >> 3; }
|
||||||
|
|
||||||
|
static void SPUWaitIdle() {
|
||||||
|
do {
|
||||||
|
for (unsigned c = 0; c < 2045; c++) __asm__ volatile("");
|
||||||
|
} while ((SPU_STATUS & 0x07ff) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SPUKeyOn(uint32_t voiceBits) {
|
||||||
|
SPU_KEY_ON_LOW = voiceBits;
|
||||||
|
SPU_KEY_ON_HIGH = voiceBits >> 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SPUSetVoiceSampleRate(int voiceID, uint16_t sampleRate) { SPU_VOICES[voiceID].sampleRate = sampleRate; }
|
||||||
|
|
||||||
|
unsigned MOD_Check(const struct MODFileFormat* module) {
|
||||||
|
if (syscall_strncmp(module->signature, "HIT", 3) == 0) {
|
||||||
|
return module->signature[3] - '0';
|
||||||
|
} else if (syscall_strncmp(module->signature, "HM", 2) == 0) {
|
||||||
|
return ((module->signature[2] - '0') * 10) + module->signature[3] - '0';
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned MOD_Channels = 0;
|
||||||
|
unsigned MOD_SongLength = 0;
|
||||||
|
// original code keeps this one to the very beginning of the file,
|
||||||
|
// while this code keeps the pointer to the beginning of the order table
|
||||||
|
static const uint8_t* MOD_ModuleData = NULL;
|
||||||
|
unsigned MOD_CurrentOrder = 0;
|
||||||
|
unsigned MOD_CurrentPattern = 0;
|
||||||
|
unsigned MOD_CurrentRow = 0;
|
||||||
|
unsigned MOD_Speed = 0;
|
||||||
|
unsigned MOD_Tick = 0;
|
||||||
|
// this never seems to be updated in the original code, which is a
|
||||||
|
// mistake; the F command handler was all wrong
|
||||||
|
unsigned MOD_BPM = 0;
|
||||||
|
// original code keeps this one to the NEXT row,
|
||||||
|
// while this code keeps the pointer to the CURRENT row
|
||||||
|
const uint8_t* MOD_RowPointer = NULL;
|
||||||
|
int MOD_ChangeRowNextTick = 0;
|
||||||
|
unsigned MOD_NextRow = 0;
|
||||||
|
int MOD_ChangeOrderNextTick = 0;
|
||||||
|
unsigned MOD_NextOrder = 0;
|
||||||
|
uint8_t MOD_PatternDelay = 0;
|
||||||
|
unsigned MOD_LoopStart = 0;
|
||||||
|
unsigned MOD_LoopCount = 0;
|
||||||
|
int MOD_Stereo = 0;
|
||||||
|
uint32_t MOD_hblanks;
|
||||||
|
|
||||||
|
// This function is now more of a helper to calculate the number of hsync
|
||||||
|
// values to wait until the next call to MOD_Poll. If the user wants to use
|
||||||
|
// another method, they will have to inspect MOD_BPM manually and make their
|
||||||
|
// own math based on their own timer.
|
||||||
|
static void MOD_SetBPM(unsigned bpm) {
|
||||||
|
MOD_BPM = bpm;
|
||||||
|
// The original code only uses 39000 here but the reality is a bit more
|
||||||
|
// complex than that, as not all clocks are exactly the same, depending
|
||||||
|
// on the machine's region, and the video mode selected.
|
||||||
|
|
||||||
|
uint32_t status = GPU_STATUS;
|
||||||
|
int isPalConsole = *((const char*)0xbfc7ff52) == 'E';
|
||||||
|
int isPal = (status & 0x00100000) != 0;
|
||||||
|
uint32_t base;
|
||||||
|
if (isPal && isPalConsole) { // PAL video on PAL console
|
||||||
|
base = 39062; // 312.5 * 125 * 50.000 / 50 or 314 * 125 * 49.761 / 50
|
||||||
|
} else if (isPal && !isPalConsole) { // PAL video on NTSC console
|
||||||
|
base = 39422; // 312.5 * 125 * 50.460 / 50 or 314 * 125 * 50.219 / 50
|
||||||
|
} else if (!isPal && isPalConsole) { // NTSC video on PAL console
|
||||||
|
base = 38977; // 262.5 * 125 * 59.393 / 50 or 263 * 125 * 59.280 / 50
|
||||||
|
} else { // NTSC video on NTSC console
|
||||||
|
base = 39336; // 262.5 * 125 * 59.940 / 50 or 263 * 125 * 59.826 / 50
|
||||||
|
}
|
||||||
|
MOD_hblanks = base / bpm;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct SPUChannelData s_channelData[24];
|
||||||
|
|
||||||
|
uint32_t MOD_Load(const struct MODFileFormat* module) {
|
||||||
|
SPUInit();
|
||||||
|
MOD_Channels = MOD_Check(module);
|
||||||
|
|
||||||
|
if (MOD_Channels == 0) return 0;
|
||||||
|
|
||||||
|
uint32_t currentSpuAddress = 0x1010;
|
||||||
|
for (unsigned i = 0; i < 31; i++) {
|
||||||
|
s_spuInstrumentData[i].baseAddress = currentSpuAddress >> 4;
|
||||||
|
s_spuInstrumentData[i].finetune = module->samples[i].finetune;
|
||||||
|
s_spuInstrumentData[i].volume = module->samples[i].volume;
|
||||||
|
currentSpuAddress += module->samples[i].lenarr[0] * 0x100 + module->samples[i].lenarr[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
MOD_SongLength = module->songLength;
|
||||||
|
|
||||||
|
unsigned maxPatternID = 0;
|
||||||
|
for (unsigned i = 0; i < 128; i++) {
|
||||||
|
if (maxPatternID < module->patternTable[i]) maxPatternID = module->patternTable[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
MOD_ModuleData = (const uint8_t*)&module->patternTable[0];
|
||||||
|
|
||||||
|
SPUUploadInstruments(0x1010, MOD_ModuleData + 4 + 128 + MOD_Channels * 0x100 * (maxPatternID + 1),
|
||||||
|
currentSpuAddress - 0x1010);
|
||||||
|
|
||||||
|
MOD_CurrentOrder = 0;
|
||||||
|
MOD_CurrentPattern = module->patternTable[0];
|
||||||
|
MOD_CurrentRow = 0;
|
||||||
|
MOD_Speed = 6;
|
||||||
|
MOD_Tick = 6;
|
||||||
|
MOD_RowPointer = MOD_ModuleData + 4 + 128 + MOD_CurrentPattern * MOD_Channels * 0x100;
|
||||||
|
// original code goes only up to MOD_Channels; let's reset all 24
|
||||||
|
for (unsigned i = 0; i < 24; i++) SPUResetVoice(i);
|
||||||
|
MOD_ChangeRowNextTick = 0;
|
||||||
|
MOD_ChangeOrderNextTick = 0;
|
||||||
|
MOD_LoopStart = 0;
|
||||||
|
MOD_LoopCount = 0;
|
||||||
|
|
||||||
|
// these two are erroneously missing from the original code, at
|
||||||
|
// least for being able to play more than one music
|
||||||
|
MOD_PatternDelay = 0;
|
||||||
|
syscall_memset(s_channelData, 0, sizeof(s_channelData));
|
||||||
|
|
||||||
|
SPUUnMute();
|
||||||
|
|
||||||
|
// this one is also missing, and is necessary, for being able to call MOD_Load
|
||||||
|
// after another song that changed the tempo previously
|
||||||
|
MOD_SetBPM(125);
|
||||||
|
|
||||||
|
// the original code would do:
|
||||||
|
// return MOD_Channels;
|
||||||
|
// but we are returning the size for the MOD_Relocate call
|
||||||
|
return 4 + 128 + MOD_Channels * 0x100 * (maxPatternID + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MOD_Silence() {
|
||||||
|
SPUInit();
|
||||||
|
for (unsigned i = 0; i < 24; i++) {
|
||||||
|
SPUResetVoice(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MOD_Relocate(uint8_t* s1) {
|
||||||
|
if (MOD_ModuleData == s1) return;
|
||||||
|
unsigned maxPatternID = 0;
|
||||||
|
for (unsigned i = 0; i < 128; i++) {
|
||||||
|
if (maxPatternID < MOD_ModuleData[i]) maxPatternID = MOD_ModuleData[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t n = 4 + 128 + MOD_Channels * 0x100 * (maxPatternID + 1);
|
||||||
|
|
||||||
|
const uint8_t* s2 = MOD_ModuleData;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
if (s1 < s2) {
|
||||||
|
for (i = 0; i < n; i++) *s1++ = *s2++;
|
||||||
|
} else if (s1 > s2) {
|
||||||
|
s1 += n;
|
||||||
|
s2 += n;
|
||||||
|
for (i = 0; i < n; i++) *--s1 = *--s2;
|
||||||
|
}
|
||||||
|
|
||||||
|
MOD_ModuleData = s1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const uint8_t MOD_SineTable[32] = {
|
||||||
|
0x00, 0x18, 0x31, 0x4a, 0x61, 0x78, 0x8d, 0xa1, 0xb4, 0xc5, 0xd4, 0xe0, 0xeb, 0xf4, 0xfa, 0xfd,
|
||||||
|
0xff, 0xfd, 0xfa, 0xf4, 0xeb, 0xe0, 0xd4, 0xc5, 0xb4, 0xa1, 0x8d, 0x78, 0x61, 0x4a, 0x31, 0x18,
|
||||||
|
};
|
||||||
|
|
||||||
|
// C C# D D# E F F# G G# A A# B
|
||||||
|
const uint16_t MOD_PeriodTable[36 * 16] = {
|
||||||
|
856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, 453, // octave 1 tune 0
|
||||||
|
428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, 226, // octave 2 tune 0
|
||||||
|
214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, 113, // octave 3 tune 0
|
||||||
|
850, 802, 757, 715, 674, 637, 601, 567, 535, 505, 477, 450, // octave 1 tune 1
|
||||||
|
425, 401, 379, 357, 337, 318, 300, 284, 268, 253, 239, 225, // octave 2 tune 1
|
||||||
|
213, 201, 189, 179, 169, 159, 150, 142, 134, 126, 119, 113, // octave 3 tune 1
|
||||||
|
844, 796, 752, 709, 670, 632, 597, 563, 532, 502, 474, 447, // octave 1 tune 2
|
||||||
|
422, 398, 376, 355, 335, 316, 298, 282, 266, 251, 237, 224, // octave 2 tune 2
|
||||||
|
211, 199, 188, 177, 167, 158, 149, 141, 133, 125, 118, 112, // octave 3 tune 2
|
||||||
|
838, 791, 746, 704, 665, 628, 592, 559, 528, 498, 470, 444, // octave 1 tune 3
|
||||||
|
419, 395, 373, 352, 332, 314, 296, 280, 264, 249, 235, 222, // octave 2 tune 3
|
||||||
|
209, 198, 187, 176, 166, 157, 148, 140, 132, 125, 118, 111, // octave 3 tune 3
|
||||||
|
832, 785, 741, 699, 660, 623, 588, 555, 524, 495, 467, 441, // octave 1 tune 4
|
||||||
|
416, 392, 370, 350, 330, 312, 294, 278, 262, 247, 233, 220, // octave 2 tune 4
|
||||||
|
208, 196, 185, 175, 165, 156, 147, 139, 131, 124, 117, 110, // octave 3 tune 4
|
||||||
|
826, 779, 736, 694, 655, 619, 584, 551, 520, 491, 463, 437, // octave 1 tune 5
|
||||||
|
413, 390, 368, 347, 328, 309, 292, 276, 260, 245, 232, 219, // octave 2 tune 5
|
||||||
|
206, 195, 184, 174, 164, 155, 146, 138, 130, 123, 116, 109, // octave 3 tune 5
|
||||||
|
820, 774, 730, 689, 651, 614, 580, 547, 516, 487, 460, 434, // octave 1 tune 6
|
||||||
|
410, 387, 365, 345, 325, 307, 290, 274, 258, 244, 230, 217, // octave 2 tune 6
|
||||||
|
205, 193, 183, 172, 163, 154, 145, 137, 129, 122, 115, 109, // octave 3 tune 6
|
||||||
|
814, 768, 725, 684, 646, 610, 575, 543, 513, 484, 457, 431, // octave 1 tune 7
|
||||||
|
407, 384, 363, 342, 323, 305, 288, 272, 256, 242, 228, 216, // octave 2 tune 7
|
||||||
|
204, 192, 181, 171, 161, 152, 144, 136, 128, 121, 114, 108, // octave 3 tune 7
|
||||||
|
907, 856, 808, 762, 720, 678, 640, 604, 570, 538, 508, 480, // octave 1 tune -8
|
||||||
|
453, 428, 404, 381, 360, 339, 320, 302, 285, 269, 254, 240, // octave 2 tune -8
|
||||||
|
226, 214, 202, 190, 180, 170, 160, 151, 143, 135, 127, 120, // octave 3 tune -8
|
||||||
|
900, 850, 802, 757, 715, 675, 636, 601, 567, 535, 505, 477, // octave 1 tune -7
|
||||||
|
450, 425, 401, 379, 357, 337, 318, 300, 284, 268, 253, 238, // octave 2 tune -7
|
||||||
|
225, 212, 200, 189, 179, 169, 159, 150, 142, 134, 126, 119, // octave 3 tune -7
|
||||||
|
894, 844, 796, 752, 709, 670, 632, 597, 563, 532, 502, 474, // octave 1 tune -6
|
||||||
|
447, 422, 398, 376, 355, 335, 316, 298, 282, 266, 251, 237, // octave 2 tune -6
|
||||||
|
223, 211, 199, 188, 177, 167, 158, 149, 141, 133, 125, 118, // octave 3 tune -6
|
||||||
|
887, 838, 791, 746, 704, 665, 628, 592, 559, 528, 498, 470, // octave 1 tune -5
|
||||||
|
444, 419, 395, 373, 352, 332, 314, 296, 280, 264, 249, 235, // octave 2 tune -5
|
||||||
|
222, 209, 198, 187, 176, 166, 157, 148, 140, 132, 125, 118, // octave 3 tune -5
|
||||||
|
881, 832, 785, 741, 699, 660, 623, 588, 555, 524, 494, 467, // octave 1 tune -4
|
||||||
|
441, 416, 392, 370, 350, 330, 312, 294, 278, 262, 247, 233, // octave 2 tune -4
|
||||||
|
220, 208, 196, 185, 175, 165, 156, 147, 139, 131, 123, 117, // octave 3 tune -4
|
||||||
|
875, 826, 779, 736, 694, 655, 619, 584, 551, 520, 491, 463, // octave 1 tune -3
|
||||||
|
437, 413, 390, 368, 347, 328, 309, 292, 276, 260, 245, 232, // octave 2 tune -3
|
||||||
|
219, 206, 195, 184, 174, 164, 155, 146, 138, 130, 123, 116, // octave 3 tune -3
|
||||||
|
868, 820, 774, 730, 689, 651, 614, 580, 547, 516, 487, 460, // octave 1 tune -2
|
||||||
|
434, 410, 387, 365, 345, 325, 307, 290, 274, 258, 244, 230, // octave 2 tune -2
|
||||||
|
217, 205, 193, 183, 172, 163, 154, 145, 137, 129, 122, 115, // octave 3 tune -2
|
||||||
|
862, 814, 768, 725, 684, 646, 610, 575, 543, 513, 484, 457, // octave 1 tune -1
|
||||||
|
431, 407, 384, 363, 342, 323, 305, 288, 272, 256, 242, 228, // octave 2 tune -1
|
||||||
|
216, 203, 192, 181, 171, 161, 152, 144, 136, 128, 121, 114, // octave 3 tune -1
|
||||||
|
};
|
||||||
|
|
||||||
|
#define SETVOICESAMPLERATE(channel, newPeriod) \
|
||||||
|
SPUSetVoiceSampleRate(channel, ((7093789 / (newPeriod * 2)) << 12) / 44100)
|
||||||
|
#define SETVOICEVOLUME(channel, volume) \
|
||||||
|
volume <<= 8; \
|
||||||
|
if (MOD_Stereo) { \
|
||||||
|
int pan = (channel & 1) ^ (channel >> 1); \
|
||||||
|
int16_t left = pan == 0 ? volume : 0; \
|
||||||
|
int16_t right = pan == 0 ? 0 : volume; \
|
||||||
|
SPUSetVoiceVolume(channel, left, right); \
|
||||||
|
} else { \
|
||||||
|
SPUSetVoiceVolume(channel, volume, volume); \
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MOD_UpdateEffect() {
|
||||||
|
const uint8_t* rowPointer = MOD_RowPointer;
|
||||||
|
const unsigned channels = MOD_Channels;
|
||||||
|
for (unsigned channel = 0; channel < channels; channel++) {
|
||||||
|
uint8_t effectNibble23 = rowPointer[3];
|
||||||
|
uint8_t effectNibble1 = rowPointer[2] & 0x0f;
|
||||||
|
uint8_t effectNibble2 = effectNibble23 & 0x0f;
|
||||||
|
uint8_t effectNibble3 = effectNibble23 >> 4;
|
||||||
|
|
||||||
|
uint8_t arpeggioTick;
|
||||||
|
int32_t newPeriod;
|
||||||
|
int16_t volume;
|
||||||
|
uint16_t slideTo;
|
||||||
|
uint8_t fx;
|
||||||
|
uint32_t mutation;
|
||||||
|
int8_t newValue;
|
||||||
|
|
||||||
|
struct SPUChannelData* const channelData = &s_channelData[channel];
|
||||||
|
|
||||||
|
switch (effectNibble1) {
|
||||||
|
case 0: // arpeggio
|
||||||
|
if (effectNibble23 == 0) break;
|
||||||
|
arpeggioTick = MOD_Tick;
|
||||||
|
arpeggioTick %= 3;
|
||||||
|
switch (arpeggioTick) {
|
||||||
|
case 0:
|
||||||
|
newPeriod = channelData->period;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
newPeriod = MOD_PeriodTable[channelData->note + effectNibble3];
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
newPeriod = MOD_PeriodTable[channelData->note + effectNibble2];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
SETVOICESAMPLERATE(channel, newPeriod);
|
||||||
|
break;
|
||||||
|
case 1: // portamento up
|
||||||
|
newPeriod = channelData->period;
|
||||||
|
newPeriod -= effectNibble23;
|
||||||
|
if (newPeriod < 108) newPeriod = 108;
|
||||||
|
channelData->period = newPeriod;
|
||||||
|
SETVOICESAMPLERATE(channel, newPeriod);
|
||||||
|
break;
|
||||||
|
case 2: // portamento down
|
||||||
|
newPeriod = channelData->period;
|
||||||
|
newPeriod += effectNibble23;
|
||||||
|
if (newPeriod > 907) newPeriod = 907;
|
||||||
|
channelData->period = newPeriod;
|
||||||
|
SETVOICESAMPLERATE(channel, newPeriod);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
volume = channelData->volume;
|
||||||
|
if (effectNibble23 <= 0x10) {
|
||||||
|
volume -= effectNibble23;
|
||||||
|
if (volume < 0) volume = 0;
|
||||||
|
} else {
|
||||||
|
volume += effectNibble3;
|
||||||
|
if (volume > 63) volume = 63;
|
||||||
|
}
|
||||||
|
channelData->volume = volume;
|
||||||
|
SETVOICEVOLUME(channel, volume);
|
||||||
|
/* fall through */
|
||||||
|
case 3: // glissando
|
||||||
|
newPeriod = channelData->period;
|
||||||
|
slideTo = channelData->slideTo;
|
||||||
|
if (newPeriod < slideTo) {
|
||||||
|
newPeriod += channelData->slideSpeed;
|
||||||
|
if (newPeriod > slideTo) newPeriod = slideTo;
|
||||||
|
} else if (newPeriod > slideTo) {
|
||||||
|
newPeriod -= channelData->slideSpeed;
|
||||||
|
if (newPeriod < slideTo) newPeriod = slideTo;
|
||||||
|
}
|
||||||
|
channelData->period = newPeriod;
|
||||||
|
SETVOICESAMPLERATE(channel, newPeriod);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
volume = channelData->volume;
|
||||||
|
if (effectNibble23 <= 0x10) {
|
||||||
|
volume -= effectNibble23;
|
||||||
|
if (volume < 0) volume = 0;
|
||||||
|
} else {
|
||||||
|
volume += effectNibble3;
|
||||||
|
if (volume > 63) volume = 63;
|
||||||
|
}
|
||||||
|
channelData->volume = volume;
|
||||||
|
SETVOICEVOLUME(channel, volume);
|
||||||
|
/* fall through */
|
||||||
|
case 4: // vibrato
|
||||||
|
mutation = channelData->vibrato & 0x1f;
|
||||||
|
switch (channelData->fx[3] & 3) {
|
||||||
|
case 0:
|
||||||
|
case 3: // 3 is technically random
|
||||||
|
mutation = MOD_SineTable[mutation];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (channelData->vibrato < 0) {
|
||||||
|
mutation *= -8;
|
||||||
|
mutation += 0xff;
|
||||||
|
} else {
|
||||||
|
mutation *= 8;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
mutation = 0xff;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mutation *= channelData->fx[1] >> 4;
|
||||||
|
mutation >>= 7;
|
||||||
|
newPeriod = channelData->period;
|
||||||
|
if (channelData->vibrato < 0) {
|
||||||
|
newPeriod -= mutation;
|
||||||
|
} else {
|
||||||
|
newPeriod += mutation;
|
||||||
|
}
|
||||||
|
newValue = channelData->vibrato;
|
||||||
|
newValue += channelData->fx[1] & 0x0f;
|
||||||
|
if (newValue >= 32) newValue -= 64;
|
||||||
|
channelData->vibrato = newValue;
|
||||||
|
SETVOICESAMPLERATE(channel, newPeriod);
|
||||||
|
break;
|
||||||
|
case 7: // tremolo
|
||||||
|
mutation = s_channelData[0].fx[0] & 0x1f;
|
||||||
|
switch (s_channelData[0].fx[3] & 3) {
|
||||||
|
case 0:
|
||||||
|
case 3: // 3 is technically random
|
||||||
|
mutation = MOD_SineTable[mutation];
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (channelData->fx[0] & 0x80) {
|
||||||
|
mutation *= -8;
|
||||||
|
mutation += 0xff;
|
||||||
|
} else {
|
||||||
|
mutation *= 8;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
mutation = 0xff;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mutation *= channelData->fx[3] >> 4;
|
||||||
|
mutation >>= 6;
|
||||||
|
volume = channelData->volume;
|
||||||
|
if (channelData->fx[0] & 0x80) {
|
||||||
|
volume -= mutation;
|
||||||
|
} else {
|
||||||
|
volume += mutation;
|
||||||
|
}
|
||||||
|
newValue = channelData->fx[0] + (channelData->fx[2] & 0x0f);
|
||||||
|
if (newValue >= 32) newValue -= 64;
|
||||||
|
channelData->fx[0] = newValue;
|
||||||
|
if (volume > 63) volume = 63;
|
||||||
|
SETVOICEVOLUME(channel, volume);
|
||||||
|
break;
|
||||||
|
case 10: // volume slide
|
||||||
|
volume = channelData->volume;
|
||||||
|
if (effectNibble23 <= 0x10) {
|
||||||
|
volume -= effectNibble23;
|
||||||
|
if (volume < 0) volume = 0;
|
||||||
|
} else {
|
||||||
|
volume += effectNibble3;
|
||||||
|
if (volume > 63) volume = 63;
|
||||||
|
}
|
||||||
|
channelData->volume = volume;
|
||||||
|
SETVOICEVOLUME(channel, volume);
|
||||||
|
break;
|
||||||
|
case 14: // extended
|
||||||
|
switch (effectNibble3) {
|
||||||
|
case 9: // retrigger sample
|
||||||
|
// this doesn't look right, we probably want to reset the sample location
|
||||||
|
if ((MOD_Tick % effectNibble2) == 0) SPUKeyOn(1 << channel);
|
||||||
|
break;
|
||||||
|
case 12: // cut sample
|
||||||
|
if (MOD_Tick != effectNibble2) break;
|
||||||
|
channelData->volume = 0;
|
||||||
|
SPUSetVoiceVolume(channel, 0, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rowPointer += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void MOD_UpdateRow() {
|
||||||
|
const unsigned channels = MOD_Channels;
|
||||||
|
if (MOD_ChangeOrderNextTick) {
|
||||||
|
unsigned newOrder = MOD_NextOrder;
|
||||||
|
if (newOrder >= MOD_SongLength) newOrder = 0;
|
||||||
|
MOD_CurrentRow = 0;
|
||||||
|
MOD_CurrentOrder = newOrder;
|
||||||
|
MOD_CurrentPattern = MOD_ModuleData[newOrder];
|
||||||
|
}
|
||||||
|
if (MOD_ChangeRowNextTick) {
|
||||||
|
unsigned newRow = (MOD_NextRow >> 4) * 10 + (MOD_NextRow & 0x0f);
|
||||||
|
if (newRow >= 64) newRow = 0;
|
||||||
|
MOD_CurrentRow = newRow;
|
||||||
|
if (MOD_ChangeOrderNextTick) {
|
||||||
|
if (++MOD_CurrentOrder >= MOD_SongLength) MOD_CurrentOrder = 0;
|
||||||
|
MOD_CurrentPattern = MOD_ModuleData[MOD_CurrentOrder];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MOD_ChangeRowNextTick = 0;
|
||||||
|
MOD_ChangeOrderNextTick = 0;
|
||||||
|
MOD_RowPointer =
|
||||||
|
MOD_ModuleData + 128 + 4 + MOD_CurrentPattern * MOD_Channels * 0x100 + MOD_CurrentRow * channels * 4;
|
||||||
|
const uint8_t* rowPointer = MOD_RowPointer;
|
||||||
|
|
||||||
|
for (unsigned channel = 0; channel < channels; channel++) {
|
||||||
|
int16_t volume;
|
||||||
|
struct SPUChannelData* const channelData = &s_channelData[channel];
|
||||||
|
|
||||||
|
uint8_t effectNibble1 = rowPointer[2];
|
||||||
|
uint8_t effectNibble23 = rowPointer[3];
|
||||||
|
uint16_t nibble0 = rowPointer[0];
|
||||||
|
unsigned sampleID = (nibble0 & 0xf0) | (effectNibble1 >> 4);
|
||||||
|
uint8_t effectNibble2 = effectNibble23 & 0x0f;
|
||||||
|
uint8_t effectNibble3 = effectNibble23 >> 4;
|
||||||
|
unsigned period = ((nibble0 & 0x0f) << 8) | rowPointer[1];
|
||||||
|
int32_t newPeriod;
|
||||||
|
uint8_t fx;
|
||||||
|
effectNibble1 &= 0x0f;
|
||||||
|
|
||||||
|
if (effectNibble1 != 9) channelData->samplePos = 0;
|
||||||
|
if (sampleID != 0) {
|
||||||
|
channelData->sampleID = --sampleID;
|
||||||
|
volume = s_spuInstrumentData[sampleID].volume;
|
||||||
|
if (volume > 63) volume = 63;
|
||||||
|
channelData->volume = volume;
|
||||||
|
if (effectNibble1 != 7) {
|
||||||
|
SETVOICEVOLUME(channel, volume);
|
||||||
|
}
|
||||||
|
SPUSetStartAddress(channel, s_spuInstrumentData[sampleID].baseAddress << 4 + channelData->samplePos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (period != 0) {
|
||||||
|
int periodIndex;
|
||||||
|
// original code erroneously does >= 0
|
||||||
|
for (periodIndex = 35; periodIndex--; periodIndex > 0) {
|
||||||
|
if (MOD_PeriodTable[periodIndex] == period) break;
|
||||||
|
}
|
||||||
|
channelData->note = periodIndex + s_spuInstrumentData[channelData->sampleID].finetune * 36;
|
||||||
|
fx = channelData->fx[3];
|
||||||
|
if ((fx & 0x0f) < 4) {
|
||||||
|
channelData->vibrato = 0;
|
||||||
|
}
|
||||||
|
if ((fx >> 4) < 4) {
|
||||||
|
channelData->fx[0] = 0;
|
||||||
|
}
|
||||||
|
if ((effectNibble1 != 3) && (effectNibble1 != 5)) {
|
||||||
|
SPUWaitIdle();
|
||||||
|
SPUKeyOn(1 << channel);
|
||||||
|
channelData->period = MOD_PeriodTable[channelData->note];
|
||||||
|
}
|
||||||
|
newPeriod = channelData->period;
|
||||||
|
SETVOICESAMPLERATE(channel, newPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (effectNibble1) {
|
||||||
|
case 3: // glissando
|
||||||
|
if (effectNibble23 != 0) {
|
||||||
|
channelData->slideSpeed = effectNibble23;
|
||||||
|
}
|
||||||
|
if (period != 0) {
|
||||||
|
channelData->slideTo = MOD_PeriodTable[channelData->note];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4: // vibrato
|
||||||
|
if (effectNibble3 != 0) {
|
||||||
|
fx = channelData->fx[1];
|
||||||
|
fx &= ~0x0f;
|
||||||
|
fx |= effectNibble3;
|
||||||
|
channelData->fx[1] = fx;
|
||||||
|
}
|
||||||
|
if (effectNibble2 != 0) {
|
||||||
|
fx = channelData->fx[1];
|
||||||
|
fx &= ~0xf0;
|
||||||
|
fx |= effectNibble3 << 4;
|
||||||
|
channelData->fx[1] = fx;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 7: // tremolo
|
||||||
|
if (effectNibble3 != 0) {
|
||||||
|
fx = channelData->fx[2];
|
||||||
|
fx &= ~0x0f;
|
||||||
|
fx |= effectNibble3;
|
||||||
|
channelData->fx[2] = fx;
|
||||||
|
}
|
||||||
|
if (effectNibble2 != 0) {
|
||||||
|
fx = channelData->fx[2];
|
||||||
|
fx &= ~0xf0;
|
||||||
|
fx |= effectNibble2 << 4;
|
||||||
|
channelData->fx[2] = fx;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 9: // sample jump
|
||||||
|
if (effectNibble23 != 0) {
|
||||||
|
uint16_t newSamplePos = effectNibble23;
|
||||||
|
channelData->samplePos = newSamplePos << 7;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 11: // order jump
|
||||||
|
if (!MOD_ChangeOrderNextTick) {
|
||||||
|
MOD_ChangeOrderNextTick = 1;
|
||||||
|
MOD_NextOrder = effectNibble23;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 12: // set volume
|
||||||
|
volume = effectNibble23;
|
||||||
|
if (volume > 64) volume = 63;
|
||||||
|
channelData->volume = volume;
|
||||||
|
SETVOICEVOLUME(channel, volume);
|
||||||
|
break;
|
||||||
|
case 13: // pattern break
|
||||||
|
if (!MOD_ChangeRowNextTick) {
|
||||||
|
MOD_ChangeRowNextTick = 1;
|
||||||
|
MOD_NextRow = effectNibble23;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 14: // extended
|
||||||
|
switch (effectNibble3) {
|
||||||
|
case 1: // fineslide up
|
||||||
|
newPeriod = channelData->period;
|
||||||
|
newPeriod -= effectNibble2;
|
||||||
|
channelData->period = newPeriod;
|
||||||
|
SETVOICESAMPLERATE(channel, newPeriod);
|
||||||
|
break;
|
||||||
|
case 2: // fineslide down
|
||||||
|
newPeriod = channelData->period;
|
||||||
|
newPeriod += effectNibble2;
|
||||||
|
channelData->period = newPeriod;
|
||||||
|
SETVOICESAMPLERATE(channel, newPeriod);
|
||||||
|
break;
|
||||||
|
case 4: // set vibrato waveform
|
||||||
|
fx = channelData->fx[3];
|
||||||
|
fx &= ~0x0f;
|
||||||
|
fx |= effectNibble2;
|
||||||
|
channelData->fx[3] = fx;
|
||||||
|
break;
|
||||||
|
case 5: // set finetune value
|
||||||
|
s_spuInstrumentData[sampleID].finetune = effectNibble2;
|
||||||
|
break;
|
||||||
|
case 6: // loop pattern
|
||||||
|
if (MOD_LoopCount-- == 0) {
|
||||||
|
MOD_LoopCount = effectNibble2;
|
||||||
|
}
|
||||||
|
if (MOD_LoopCount != 0) {
|
||||||
|
MOD_CurrentRow = MOD_LoopStart;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 7: // set tremolo waveform
|
||||||
|
fx = channelData->fx[3];
|
||||||
|
fx &= ~0xf0;
|
||||||
|
fx |= effectNibble2 << 4;
|
||||||
|
channelData->fx[3] = fx;
|
||||||
|
break;
|
||||||
|
case 10: // fine volume up
|
||||||
|
volume = channelData->volume;
|
||||||
|
volume += effectNibble2;
|
||||||
|
if (volume > 63) volume = 63;
|
||||||
|
channelData->volume = volume;
|
||||||
|
SETVOICEVOLUME(channel, volume);
|
||||||
|
break;
|
||||||
|
case 11: // fine volume down
|
||||||
|
volume = channelData->volume;
|
||||||
|
volume -= effectNibble2;
|
||||||
|
if (volume < 0) volume = 0;
|
||||||
|
channelData->volume = volume;
|
||||||
|
SETVOICEVOLUME(channel, volume);
|
||||||
|
break;
|
||||||
|
case 14: // delay pattern
|
||||||
|
MOD_PatternDelay = effectNibble2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 15: // set speed
|
||||||
|
// the original code here is very wrong with regards to
|
||||||
|
// how to interpret the command; also it was very opinionated
|
||||||
|
// about using timer1 for its clock source
|
||||||
|
if (effectNibble23 == 0) break;
|
||||||
|
if (effectNibble23 < 32) {
|
||||||
|
MOD_Speed = effectNibble23;
|
||||||
|
} else {
|
||||||
|
MOD_SetBPM(effectNibble23);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
rowPointer += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MOD_Poll() {
|
||||||
|
// the original code is getting the delay pattern wrong here, and
|
||||||
|
// isn't processing them as actual line delays, rather as a sort
|
||||||
|
// of ticks delay, and was basically going too fast
|
||||||
|
uint8_t newPatternDelay = MOD_PatternDelay;
|
||||||
|
if (++MOD_Tick < MOD_Speed) {
|
||||||
|
MOD_UpdateEffect();
|
||||||
|
} else {
|
||||||
|
MOD_Tick = 0;
|
||||||
|
if (newPatternDelay-- == 0) {
|
||||||
|
MOD_UpdateRow();
|
||||||
|
newPatternDelay = MOD_PatternDelay;
|
||||||
|
// I don't think the original code was handling this properly...
|
||||||
|
if (++MOD_CurrentRow >= 64 || MOD_ChangeRowNextTick) {
|
||||||
|
MOD_CurrentRow = 0;
|
||||||
|
if (++MOD_CurrentOrder >= MOD_SongLength) {
|
||||||
|
MOD_CurrentOrder = 0;
|
||||||
|
}
|
||||||
|
MOD_CurrentPattern = MOD_ModuleData[MOD_CurrentOrder];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MOD_UpdateEffect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MOD_PatternDelay = newPatternDelay;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MOD_PlayNote(unsigned channel, unsigned sampleID, unsigned note, int16_t volume) {
|
||||||
|
if (volume < 0) volume = 0;
|
||||||
|
if (volume > 63) volume = 63;
|
||||||
|
struct SPUChannelData* const channelData = &s_channelData[channel];
|
||||||
|
channelData->samplePos = 0;
|
||||||
|
SPUSetVoiceVolume(channel, volume << 8, volume << 8);
|
||||||
|
SPUSetStartAddress(channel, s_spuInstrumentData[sampleID].baseAddress << 4 + channelData->samplePos);
|
||||||
|
SPUWaitIdle();
|
||||||
|
SPUKeyOn(1 << channel);
|
||||||
|
channelData->note = note = note + s_spuInstrumentData[sampleID].finetune * 36;
|
||||||
|
int32_t newPeriod = channelData->period = MOD_PeriodTable[note];
|
||||||
|
SETVOICESAMPLERATE(channel, newPeriod);
|
||||||
|
}
|
150
hello_mod/src/modplayer.h
Normal file
150
hello_mod/src/modplayer.h
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 PCSX-Redux authors
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Once MOD_Load returns, these values will be valid.
|
||||||
|
// Unless specified, consider them read only, but
|
||||||
|
// modifying them might be doable if you know what you are doing.
|
||||||
|
extern unsigned MOD_Channels;
|
||||||
|
extern unsigned MOD_SongLength;
|
||||||
|
extern unsigned MOD_CurrentOrder;
|
||||||
|
extern unsigned MOD_CurrentPattern;
|
||||||
|
extern unsigned MOD_CurrentRow;
|
||||||
|
extern unsigned MOD_Speed;
|
||||||
|
extern unsigned MOD_Tick;
|
||||||
|
extern unsigned MOD_BPM;
|
||||||
|
extern unsigned MOD_LoopStart;
|
||||||
|
extern unsigned MOD_LoopCount;
|
||||||
|
extern uint8_t MOD_PatternDelay;
|
||||||
|
|
||||||
|
// This is a pointer to the current row that's
|
||||||
|
// being played. Used for decoding. The number
|
||||||
|
// of relevant bytes for a row is 4 * MOD_Channels.
|
||||||
|
extern const uint8_t* MOD_RowPointer;
|
||||||
|
|
||||||
|
// These four are fine to change outside of MOD_Poll.
|
||||||
|
// The first two are booleans, and the next two are the values
|
||||||
|
// you need them to be set at when MOD_Poll is called next.
|
||||||
|
// If you need immediate row / pattern change, also set
|
||||||
|
// MOD_Tick to MOD_Speed.
|
||||||
|
extern int MOD_ChangeRowNextTick;
|
||||||
|
extern int MOD_ChangeOrderNextTick;
|
||||||
|
extern unsigned MOD_NextRow;
|
||||||
|
extern unsigned MOD_NextOrder;
|
||||||
|
|
||||||
|
// This can be used to decode MOD_RowPointer.
|
||||||
|
extern const uint16_t MOD_PeriodTable[];
|
||||||
|
|
||||||
|
// Internal HIT file structure, but conformant to
|
||||||
|
// http://www.aes.id.au/modformat.html
|
||||||
|
struct MODFileFormat;
|
||||||
|
|
||||||
|
// Returns the number of channel from this module,
|
||||||
|
// or 0 if the module is invalid.
|
||||||
|
unsigned MOD_Check(const struct MODFileFormat* module);
|
||||||
|
|
||||||
|
// Loads the specified module and gets it ready for
|
||||||
|
// playback. Returns the number of bytes needed if
|
||||||
|
// relocation is desired. The pointer has to be
|
||||||
|
// aligned to a 4-bytes boundary. Will also setup
|
||||||
|
// the SPU.
|
||||||
|
uint32_t MOD_Load(const struct MODFileFormat* module);
|
||||||
|
|
||||||
|
// Call this function periodically to play sound. The
|
||||||
|
// frequency at which this is called will determine the
|
||||||
|
// actual playback speed of the module. Most modules will
|
||||||
|
// not change the default tempo, which requires calling
|
||||||
|
// MOD_Poll 50 times per second, or exactly the vertical
|
||||||
|
// refresh rate in PAL. Preferably call this from timer1's
|
||||||
|
// IRQ however, and look up MOD_hblanks to decide of the
|
||||||
|
// next target value to use.
|
||||||
|
// To pause or stop playback, simply stop calling this
|
||||||
|
// function. The internal player doesn't need any
|
||||||
|
// sort of cleanup, and switching to another song simply
|
||||||
|
// requires calling MOD_Load with a new file.
|
||||||
|
void MOD_Poll();
|
||||||
|
|
||||||
|
// New APIs from the original code from there on.
|
||||||
|
|
||||||
|
// Defaults to 0. This is a boolean indicating if we
|
||||||
|
// want the volume settings to be monaural or the same
|
||||||
|
// as the original Amiga's Paula chip.
|
||||||
|
extern int MOD_Stereo;
|
||||||
|
|
||||||
|
// Indicates the number of hblank ticks to wait before
|
||||||
|
// calling MOD_Poll. This value may or may not change
|
||||||
|
// after a call to MOD_Poll, if the track requested a
|
||||||
|
// tempo change.
|
||||||
|
extern uint32_t MOD_hblanks;
|
||||||
|
|
||||||
|
// It is possible to reclaim memory from the initial call
|
||||||
|
// to MOD_Load, in case the module was loaded from an
|
||||||
|
// external source. The number of bytes needed for the
|
||||||
|
// player will be returned by MOD_Load. Call MOD_Relocate
|
||||||
|
// with a new memory buffer that has at least this many bytes.
|
||||||
|
// Caller is responsible for managing the memory.
|
||||||
|
// It is fine to reuse the same buffer as the original input,
|
||||||
|
// if you wish to simply realloc it after relocating it,
|
||||||
|
// provided your realloc implementation guarantees that the
|
||||||
|
// shrunk buffer will remain at the same location.
|
||||||
|
//
|
||||||
|
// For example, this pseudo-code is valid:
|
||||||
|
// bool load_mod_file(File mod_file) {
|
||||||
|
// void * buffer = malloc(file_size(mod_file));
|
||||||
|
// readfile(mod_file, buffer);
|
||||||
|
// uint32_t size = MOD_Load(buffer);
|
||||||
|
// if (size == 0) {
|
||||||
|
// free(buffer);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// MOD_Relocate(buffer);
|
||||||
|
// void * newbuffer = realloc(buffer, size);
|
||||||
|
// if (newbuffer != buffer) {
|
||||||
|
// free(newbuffer);
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
void MOD_Relocate(uint8_t* buffer);
|
||||||
|
|
||||||
|
// Plays an arbitrary note from the MOD's samples bank.
|
||||||
|
// The volume will always be centered, so the sample will
|
||||||
|
// be monaural. The voiceID ideally should be set to a
|
||||||
|
// value that is less than MOD_Channels. Remember the PS1
|
||||||
|
// has 24 channels total, so voiceID can be between 0 and 23.
|
||||||
|
// The note is a value between 0 and 35. The exact note played
|
||||||
|
// is on the normal 12-notes, C, C#, D, ... scale, and there
|
||||||
|
// are three octaves available, which gives the 12*3=36
|
||||||
|
// interval value of the note argument. The volume argument
|
||||||
|
// is between 0 and 63. You can simulate KeyOff by simply
|
||||||
|
// setting the volume of the voice to 0.
|
||||||
|
void MOD_PlayNote(unsigned voiceID, unsigned sampleID, unsigned note, int16_t volume);
|
||||||
|
|
||||||
|
// Added API to reset the SPU and silence everything.
|
||||||
|
void MOD_Silence();
|
Loading…
Reference in New Issue
Block a user