Modplayer wip
This commit is contained in:
parent
ec76da6b79
commit
097597d75e
BIN
HIT/shine.hit
Normal file
BIN
HIT/shine.hit
Normal file
Binary file not shown.
BIN
HIT/shine.mod
Normal file
BIN
HIT/shine.mod
Normal file
Binary file not shown.
BIN
HIT/shine.o
Normal file
BIN
HIT/shine.o
Normal file
Binary file not shown.
3
Makefile
3
Makefile
@ -10,5 +10,8 @@ TARGET = hello_str
|
||||
|
||||
SRCS = hello_str.c \
|
||||
src/str.c \
|
||||
src/mod.c \
|
||||
src/modplayer.c \
|
||||
HIT/shine.hit \
|
||||
|
||||
include common.mk
|
||||
|
26
STR/avi2str.scr
Normal file
26
STR/avi2str.scr
Normal file
@ -0,0 +1,26 @@
|
||||
Avi2strMdecV(
|
||||
.\idle_loop_mc.avi,
|
||||
.\idle_loop_mc.str,
|
||||
x2, # CD-ROM speed
|
||||
15fps, # Frame rate
|
||||
2, # Number of channels
|
||||
2, # MDEC version
|
||||
FALSE # Leap sector
|
||||
);
|
||||
|
||||
Avi2strMdecV(
|
||||
.\transition_mc.avi,
|
||||
.\transition_mc.str,
|
||||
x2, # CD-ROM speed
|
||||
15fps, # Frame rate
|
||||
2, # Number of channels
|
||||
2, # MDEC version
|
||||
FALSE # Leap sector
|
||||
);
|
||||
|
||||
|
||||
Pack2ch(
|
||||
.\menu.str, FALSE, # Output file name(Fn), Subheader(Sub)
|
||||
.\idle_loop_mc.str, FALSE, 0, 0, # ch0; Fn,sub,TermType,TermLength
|
||||
.\transition_mc.str, FALSE, 0, 0 # ch1; Fn sub,TermType,TermLength
|
||||
);
|
BIN
STR/menu.str
Normal file
BIN
STR/menu.str
Normal file
Binary file not shown.
BIN
VAG/select.wav
Normal file
BIN
VAG/select.wav
Normal file
Binary file not shown.
BIN
VAG/switch.wav
Normal file
BIN
VAG/switch.wav
Normal file
Binary file not shown.
@ -51,3 +51,8 @@ endef
|
||||
# convert VAG files to bin
|
||||
%.o: %.vag
|
||||
$(call OBJCOPYME)
|
||||
|
||||
# convert HIT to bin
|
||||
%.o: %.hit
|
||||
$(call OBJCOPYME)
|
||||
|
||||
|
123
hello_str.c
123
hello_str.c
@ -14,9 +14,10 @@
|
||||
// CODEC library
|
||||
#include <libpress.h>
|
||||
// printf
|
||||
#include "../nolibgs_hello_worlds/thirdparty/nugget/common/syscalls/syscalls.h"
|
||||
//~ #include "../nolibgs_hello_worlds/thirdparty/nugget/common/syscalls/syscalls.h"
|
||||
// str playback
|
||||
#include "src/str.h"
|
||||
#include "src/mod.h"
|
||||
|
||||
#define VMODE 0 // Video Mode : 0 : NTSC, 1: PAL
|
||||
#define SCREENXRES 320 // Screen width
|
||||
@ -109,6 +110,7 @@ void display(void)
|
||||
{
|
||||
DrawSync(0); // Wait for all drawing to terminate
|
||||
VSync(0); // Wait for the next vertical blank
|
||||
//~ checkMusic();
|
||||
PutDispEnv(&disp[db]); // set alternate disp and draw environnments
|
||||
PutDrawEnv(&draw[db]);
|
||||
DrawOTag(&ot[db][OTLEN - 1]);
|
||||
@ -153,55 +155,6 @@ void drawBG(void)
|
||||
addPrim(ot[db], poly); // add poly to the Ordering table
|
||||
nextpri += sizeof(POLY_FT4); // increment nextpri address with size of a POLY_F4 struct
|
||||
}
|
||||
int main() {
|
||||
curStr = &(menu[0]);
|
||||
// Set pointers to the relevant buffer addresses
|
||||
u_long * curVLCptr = &VlcBuff[0];
|
||||
u_short * curIMGptr = &ImgBuff[0];
|
||||
|
||||
init();
|
||||
PadInit(0);
|
||||
VSyncCallback(checkPad);
|
||||
// Init CDrom system
|
||||
CdInit();
|
||||
// Init MDEC and load STR
|
||||
initSTR(curStr);
|
||||
// Set Channel
|
||||
StSetChannel( curStr->channel );
|
||||
// Main loop
|
||||
while (1) {
|
||||
// Only display background STR if drawMenu is set
|
||||
if (drawMenu)
|
||||
{
|
||||
SetDispMask(1);
|
||||
drawBG();
|
||||
}
|
||||
// While end of str is not reached, play it
|
||||
if (curStr->endPlayback == 1)
|
||||
{
|
||||
// Replay STR
|
||||
resetSTR(curStr);
|
||||
}
|
||||
while ( curStr->endPlayback == 0)
|
||||
{
|
||||
playSTR(&curStr);
|
||||
if ( !curStr->channel )
|
||||
{
|
||||
FntPrint("%s", menu_items[0].title);
|
||||
if ( sectorHeader->frameCount > 5 )
|
||||
{
|
||||
FntFlush(-1);
|
||||
} else if ( (sectorHeader->frameCount % 2) && sectorHeader->frameCount < 5 )
|
||||
{
|
||||
FntFlush(-1);
|
||||
}
|
||||
}
|
||||
display();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void checkPad(void)
|
||||
{
|
||||
u_short pad;
|
||||
@ -222,4 +175,74 @@ void checkPad(void)
|
||||
{
|
||||
oldPad = pad;
|
||||
}
|
||||
}
|
||||
int main() {
|
||||
curStr = &(menu[0]);
|
||||
// Set pointers to the relevant buffer addresses
|
||||
u_long * curVLCptr = &VlcBuff[0];
|
||||
u_short * curIMGptr = &ImgBuff[0];
|
||||
|
||||
init();
|
||||
PadInit(0);
|
||||
VSyncCallback(checkPad);
|
||||
// Init CDrom system
|
||||
CdInit();
|
||||
// Init MDEC and load STR
|
||||
initSTR(curStr);
|
||||
// Set Channel
|
||||
StSetChannel( curStr->channel );
|
||||
// Main loop
|
||||
//~ loadMod();
|
||||
printf("Loading MOD:\'%s\'\n", HITFILE);
|
||||
// We are going to use timer1 and its hblank counter to tell us when
|
||||
// we need to call MOD_Poll again. For this, we need timer1 to be
|
||||
// counting hblanks instead of the system clock.
|
||||
COUNTERS[1].mode = 0x0100;
|
||||
MOD_Load((struct MODFileFormat*)HITFILE);
|
||||
printf("%02d Channels, %02d Orders\n", MOD_Channels, MOD_SongLength);
|
||||
unsigned row = 0xffffffff;
|
||||
unsigned order = 0xffffffff;
|
||||
unsigned pattern = 0xffffffff;
|
||||
//~ uint16_t s_nextCounter = 0;
|
||||
// Giving our initial counter a proper value.
|
||||
s_nextCounter = COUNTERS[1].value + MOD_hblanks;
|
||||
while (1)
|
||||
//~ while (VSync(-1))
|
||||
{
|
||||
playMod(row, order, pattern);
|
||||
checkMusic();
|
||||
// Only display background STR if drawMenu is set
|
||||
if (drawMenu)
|
||||
{
|
||||
SetDispMask(1);
|
||||
drawBG();
|
||||
}
|
||||
// While end of str is not reached, play it
|
||||
if (curStr->endPlayback == 1)
|
||||
{
|
||||
// Replay STR
|
||||
resetSTR(curStr);
|
||||
}
|
||||
if ( curStr->endPlayback == 0)
|
||||
{
|
||||
playSTR(&curStr);
|
||||
if ( !curStr->channel )
|
||||
{
|
||||
// Display title
|
||||
FntPrint("%s", menu_items[0].title);
|
||||
// Flickering text
|
||||
if ( sectorHeader->frameCount > 5 )
|
||||
{
|
||||
FntFlush(-1);
|
||||
} else if ( (sectorHeader->frameCount % 2) && sectorHeader->frameCount < 5 )
|
||||
{
|
||||
FntFlush(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
//~ playMod(row, order, pattern);
|
||||
//~ waitVSync();
|
||||
display();
|
||||
}
|
||||
return 0;
|
||||
}
|
@ -86,7 +86,7 @@
|
||||
<!-- Stores system.txt as system.cnf -->
|
||||
<file name="system.cnf" type="data" source="system.cnf"/>
|
||||
<file name="SCES_313.37" type="data" source="hello_str.ps-exe"/>
|
||||
<file name="MENU.STR" type="str" source="render/menu.str"/>
|
||||
<file name="MENU.STR" type="str" source="STR/menu.str"/>
|
||||
|
||||
<dummy sectors="1024"/>
|
||||
|
||||
|
@ -1,5 +0,0 @@
|
||||
Pack2ch(
|
||||
z:\home\arthus\build\psxdev\nolibgs_demo\render\bg_only_i.str, FALSE, # Output file name(Fn), Subheader(Sub)
|
||||
z:\home\arthus\build\psxdev\nolibgs_demo\render\bg_only_1_mc.str, FALSE, 0, 2, # ch0; Fn,sub,TermType,TermLength
|
||||
z:\home\arthus\build\psxdev\nolibgs_demo\render\bg_only_mc.str, FALSE, 0, 2 # ch1; Fn sub,TermType,TermLength
|
||||
);
|
91
src/mod.c
Normal file
91
src/mod.c
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
|
||||
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 "mod.h"
|
||||
|
||||
uint16_t s_nextCounter = 0;
|
||||
|
||||
// Function to call periodically if we can't use interrupts for some reason.
|
||||
// If the code is running slower than the normal vsync speed, then you should
|
||||
// call this during your frame computation code, in order to make sure you're
|
||||
// not missing any tick.
|
||||
void checkMusic() {
|
||||
if (((int16_t)(s_nextCounter - COUNTERS[1].value)) <= 0) {
|
||||
printf("cnt: %d\n", s_nextCounter);
|
||||
MOD_Poll();
|
||||
s_nextCounter += MOD_hblanks;
|
||||
}
|
||||
}
|
||||
|
||||
void waitVSync() {
|
||||
int wasLocked = enterCriticalSection();
|
||||
uint32_t imask = IMASK;
|
||||
|
||||
IMASK = imask | IRQ_VBLANK;
|
||||
|
||||
while ((IREG & IRQ_VBLANK) == 0) {
|
||||
// Since our vsync is a kludge, we can't use the root counter IRQ
|
||||
// mechanism to call MOD_Poll, and so we have to poll here, during
|
||||
// vsync, for when our timer has passed the target value manually.
|
||||
|
||||
// We *could* set up the timer properly using the target value system,
|
||||
// but this might make a few emulators sad. Beside, this is a perfectly
|
||||
// valid and common solution.
|
||||
checkMusic();
|
||||
}
|
||||
IREG &= ~IRQ_VBLANK;
|
||||
IMASK = imask;
|
||||
if (!wasLocked) leaveCriticalSection();
|
||||
}
|
||||
|
||||
void playMod(unsigned row, unsigned order, unsigned pattern)
|
||||
{
|
||||
if (row != MOD_CurrentRow || order != MOD_CurrentOrder || pattern != MOD_CurrentPattern) {
|
||||
row = MOD_CurrentRow;
|
||||
order = MOD_CurrentOrder;
|
||||
pattern = MOD_CurrentPattern;
|
||||
printf("Row: %02d, Order: %02d, Pattern: %02d\n", row, order, pattern);
|
||||
}
|
||||
}
|
||||
|
||||
//~ void loadMod() {
|
||||
//~ printf("Loading MOD:\'%s\'\n", HITFILE);
|
||||
//~ // We are going to use timer1 and its hblank counter to tell us when
|
||||
//~ // we need to call MOD_Poll again. For this, we need timer1 to be
|
||||
//~ // counting hblanks instead of the system clock.
|
||||
//~ COUNTERS[1].mode = 0x0100;
|
||||
//~ MOD_Load((struct MODFileFormat*)HITFILE);
|
||||
//~ printf("%02d Channels, %02d Orders\n", MOD_Channels, MOD_SongLength);
|
||||
//~ unsigned row = 0xffffffff;
|
||||
//~ unsigned order = 0xffffffff;
|
||||
//~ unsigned pattern = 0xffffffff;
|
||||
//~ // Giving our initial counter a proper value.
|
||||
//~ s_nextCounter = COUNTERS[1].value + MOD_hblanks;
|
||||
//~ while (1) {
|
||||
//~ playMod(row, order, pattern);
|
||||
//~ waitVSync();
|
||||
//~ }
|
||||
//~ }
|
15
src/mod.h
Normal file
15
src/mod.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "../../nugget/common/hardware/hwregs.h"
|
||||
#include "../../nugget/common/hardware/irq.h"
|
||||
#include "../../nugget/common/syscalls/syscalls.h"
|
||||
#include "modplayer.h"
|
||||
|
||||
extern const uint8_t _binary_HIT_shine_hit_start[];
|
||||
#define HITFILE _binary_HIT_shine_hit_start
|
||||
#define printf ramsyscall_printf
|
||||
|
||||
extern uint16_t s_nextCounter;
|
||||
void checkMusic();
|
||||
void waitVSync();
|
||||
void loadMod();
|
||||
void playMod();
|
806
src/modplayer.c
Normal file
806
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 "../../nugget/common/hardware/dma.h"
|
||||
#include "../../nugget/common/hardware/spu.h"
|
||||
#include "../../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(190);
|
||||
|
||||
// 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
src/modplayer.h
Normal file
150
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
|
||||
#define printf ramsyscall_printf
|
||||
#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();
|
17
src/str.c
17
src/str.c
@ -45,16 +45,16 @@ void resetSTR(STR * str)
|
||||
void switchStrCh(STR ** str)
|
||||
{
|
||||
// Switch current STR channel
|
||||
ramsyscall_printf("p0: %p - %d - ", *str, (*str)->channel);
|
||||
printf("p0: %p - %d - ", *str, (*str)->channel);
|
||||
sectorHeader->frameCount = 0;
|
||||
*str = &menu[!((*str)->channel)];
|
||||
ramsyscall_printf("p1: %p\n", *str);
|
||||
printf("p1: %p\n", *str);
|
||||
StSetChannel( (*str)->channel );
|
||||
(*str)->endPlayback = 1;
|
||||
}
|
||||
void playSTR(STR ** str)
|
||||
{
|
||||
//~ ramsyscall_printf("Frame %d / %d,ch: %d, p: %p, m0: %p, m1: %p\n", sectorHeader->frameCount, (*str)->length, (*str)->channel, str, &menu[0], &menu[1]);
|
||||
//~ printf("Frame %d / %d,ch: %d, p: %p, m0: %p, m1: %p\n", sectorHeader->frameCount, (*str)->length, (*str)->channel, str, &menu[0], &menu[1]);
|
||||
// Use this area to draw the slices
|
||||
RECT curSlice = { STR_POS_X,
|
||||
STR_POS_Y,
|
||||
@ -139,15 +139,4 @@ void playSTR(STR ** str)
|
||||
drawMenu = 1;
|
||||
}
|
||||
}
|
||||
//~ if ( (!(*str)->channel) )
|
||||
//~ {
|
||||
//~ FntPrint("Hello menu!\n");
|
||||
//~ if ( sectorHeader->frameCount > 5 )
|
||||
//~ {
|
||||
//~ FntFlush(-1);
|
||||
//~ } else if ( (sectorHeader->frameCount % 2) && sectorHeader->frameCount < 5 )
|
||||
//~ {
|
||||
//~ FntFlush(-1);
|
||||
//~ }
|
||||
//~ }
|
||||
}
|
@ -10,13 +10,14 @@
|
||||
// CODEC library
|
||||
#include <libpress.h>
|
||||
// printf
|
||||
#include "../../nolibgs_hello_worlds/thirdparty/nugget/common/syscalls/syscalls.h"
|
||||
//~ #include "../../nolibgs_hello_worlds/thirdparty/nugget/common/syscalls/syscalls.h"
|
||||
//~ #define printf ramsyscall_printf
|
||||
#define SCREENXRES 320
|
||||
#define SCREENYRES 240
|
||||
#define STR_POS_X SCREENXRES
|
||||
#define STR_POS_Y 0
|
||||
// Ring Buffer size (reduce if flickering occurs)
|
||||
#define RING_SIZE 16
|
||||
#define RING_SIZE 24
|
||||
#define PPW 1
|
||||
#define DCT_MODE 2
|
||||
typedef struct STR {
|
||||
|
Loading…
Reference in New Issue
Block a user