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))))
|
||||
|
||||
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
|
||||
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
|
||||
%.o: %.vag
|
||||
$(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