diff --git a/HIT/shine.hit b/HIT/shine.hit new file mode 100644 index 0000000..970e703 Binary files /dev/null and b/HIT/shine.hit differ diff --git a/HIT/shine.mod b/HIT/shine.mod new file mode 100644 index 0000000..4c3d453 Binary files /dev/null and b/HIT/shine.mod differ diff --git a/HIT/shine.o b/HIT/shine.o new file mode 100644 index 0000000..2dd1fcf Binary files /dev/null and b/HIT/shine.o differ diff --git a/Makefile b/Makefile index c09159f..72f8778 100644 --- a/Makefile +++ b/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 diff --git a/STR/avi2str.scr b/STR/avi2str.scr new file mode 100644 index 0000000..d9460eb --- /dev/null +++ b/STR/avi2str.scr @@ -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 +); diff --git a/STR/menu.str b/STR/menu.str new file mode 100644 index 0000000..f2b101f Binary files /dev/null and b/STR/menu.str differ diff --git a/render/png2str.sh b/STR/png2str.sh similarity index 100% rename from render/png2str.sh rename to STR/png2str.sh diff --git a/VAG/select.wav b/VAG/select.wav new file mode 100644 index 0000000..63c971e Binary files /dev/null and b/VAG/select.wav differ diff --git a/VAG/switch.wav b/VAG/switch.wav new file mode 100644 index 0000000..8758445 Binary files /dev/null and b/VAG/switch.wav differ diff --git a/common.mk b/common.mk index 817efc3..cf73e77 100644 --- a/common.mk +++ b/common.mk @@ -51,3 +51,8 @@ endef # convert VAG files to bin %.o: %.vag $(call OBJCOPYME) + +# convert HIT to bin +%.o: %.hit + $(call OBJCOPYME) + diff --git a/hello_str.c b/hello_str.c index 0a15227..2f72667 100644 --- a/hello_str.c +++ b/hello_str.c @@ -14,9 +14,10 @@ // CODEC library #include // 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; } \ No newline at end of file diff --git a/isoconfig.xml b/isoconfig.xml index f985ff6..20ac9be 100644 --- a/isoconfig.xml +++ b/isoconfig.xml @@ -86,7 +86,7 @@ - + diff --git a/render/pack2.scr b/render/pack2.scr deleted file mode 100644 index 2d076a2..0000000 --- a/render/pack2.scr +++ /dev/null @@ -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 -); \ No newline at end of file diff --git a/src/mod.c b/src/mod.c new file mode 100644 index 0000000..abc584a --- /dev/null +++ b/src/mod.c @@ -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(); + //~ } +//~ } diff --git a/src/mod.h b/src/mod.h new file mode 100644 index 0000000..d1b74c5 --- /dev/null +++ b/src/mod.h @@ -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(); diff --git a/src/modplayer.c b/src/modplayer.c new file mode 100644 index 0000000..d27b21a --- /dev/null +++ b/src/modplayer.c @@ -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 +#include + +#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); +} diff --git a/src/modplayer.h b/src/modplayer.h new file mode 100644 index 0000000..990a623 --- /dev/null +++ b/src/modplayer.h @@ -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 + +// 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(); diff --git a/src/str.c b/src/str.c index fa7f0be..e17a9ae 100644 --- a/src/str.c +++ b/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); - //~ } - //~ } } \ No newline at end of file diff --git a/src/str.h b/src/str.h index 268763a..fe0b6ea 100644 --- a/src/str.h +++ b/src/str.h @@ -10,13 +10,14 @@ // CODEC library #include // 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 {