diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index c917183..957fbeb 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -5,6 +5,7 @@ on: branches: [ "master" ] pull_request: branches: [ "master" ] + workflow_dispatch: jobs: linux: @@ -17,3 +18,25 @@ jobs: submodules: true - name: make run: make + - name: Set outputs + id: vars + run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: mdxtools-${{steps.vars.outputs.sha_short}} + path: | + adpcm-decode + adpcm-encode + mdx2midi + mdx2mml + mdx2opm + mdxdump + mdxinfo + mdxplay + mdx2pcm + mkpdx + mml2mdx + pdx2sf2 + pdx2wav + pdxinfo diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index e30a6e0..b800f9a 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -5,6 +5,7 @@ on: branches: [ "master" ] pull_request: branches: [ "master" ] + workflow_dispatch: jobs: build: diff --git a/.github/workflows/msys-mingw64-release.yml b/.github/workflows/msys-mingw64-release.yml index cf4daac..bfbe764 100644 --- a/.github/workflows/msys-mingw64-release.yml +++ b/.github/workflows/msys-mingw64-release.yml @@ -4,6 +4,7 @@ on: push: tags: - '*' + workflow_dispatch: jobs: msys2-mingw64: @@ -19,7 +20,7 @@ jobs: with: msystem: MINGW64 update: true - install: git make pkg-config mingw64/mingw-w64-x86_64-gcc mingw64/mingw-w64-x86_64-portaudio mingw64/mingw-w64-x86_64-libsndfile mingw64/mingw-w64-x86_64-nsis bison flex unzip + install: git make pkg-config mingw64/mingw-w64-x86_64-gcc mingw64/mingw-w64-x86_64-portaudio mingw64/mingw-w64-x86_64-libsndfile mingw64/mingw-w64-x86_64-nsis bison flex unzip cmake - name: Build with make run: make - name: Copy DLLs over diff --git a/.github/workflows/msys2-mingw64.yml b/.github/workflows/msys2-mingw64.yml index 932f691..04aed4f 100644 --- a/.github/workflows/msys2-mingw64.yml +++ b/.github/workflows/msys2-mingw64.yml @@ -5,6 +5,7 @@ on: branches: [ "master" ] pull_request: branches: [ "master" ] + workflow_dispatch: jobs: msys2-mingw64: @@ -20,7 +21,7 @@ jobs: with: msystem: MINGW64 update: true - install: git make pkg-config mingw64/mingw-w64-x86_64-gcc mingw64/mingw-w64-x86_64-portaudio mingw64/mingw-w64-x86_64-libsndfile bison flex + install: git make pkg-config mingw64/mingw-w64-x86_64-gcc mingw64/mingw-w64-x86_64-portaudio mingw64/mingw-w64-x86_64-libsndfile bison flex cmake - name: Build with make run: make - run: rm -f gensinc.exe diff --git a/.gitmodules b/.gitmodules index 0419d43..0286457 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "midilib"] path = midilib url = https://github.com/vampirefrog/midilib.git +[submodule "sf2cute"] + path = sf2cute + url = https://github.com/Thysbelon/sf2cute.git diff --git a/Makefile b/Makefile index 0863760..fa8a5d9 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,12 @@ CC=gcc +CPPC=g++ YACC=bison LEX=flex CFLAGS=-ggdb -Wall -DFIXED_POINT -DOUTSIDE_SPEEX -DRANDOM_PREFIX=speex -DEXPORT= -D_GNU_SOURCE -DHAVE_MEMCPY -DSAMPLE_BITS=16 -Ix68ksjis $(shell pkg-config portaudio-2.0 sndfile --cflags) LIBS=$(shell pkg-config portaudio-2.0 sndfile --libs) ifneq (,$(findstring MINGW,$(shell uname -s))) -LIBS+=-liconv -lws2_32 -static-libgcc +LIBS+= -liconv -lws2_32 -static-libgcc endif PROGS=\ @@ -51,7 +52,7 @@ mdxplay_LIBS=$(shell pkg-config portaudio-2.0 --libs) mdx2pcm_SRCS=mdx_driver.c timer_driver.c adpcm_driver.c fm_driver.c tools.c adpcm.c speex_resampler.c ym2151.c fixed_resampler.c mdx.c pdx.c cmdline.c adpcm_pcm_mix_driver.c fm_opm_emu_driver.c pcm_timer_driver.c fm_opm_driver.c sinctbl4.h sinctbl3.h vgm_logger.c mml2mdx_SRCS=mml2mdx.c mmlc.tab.c mmlc.yy.c cmdline.c tools.c mdx_compiler.c mmlc.yy.h mmlc.tab.h pdx2wav_SRCS=pdx.c tools.c adpcm.c -pdx2sf2_SRCS=pdx.c tools.c adpcm.c Soundfont.c +pdx2sf2_SRCS=pdx.c tools.c adpcm.c pdxinfo_SRCS=pdx.c tools.c cmdline.c md5.c adpcm.c gensinc_SRCS=gensinc.c cmdline.c @@ -60,6 +61,14 @@ mdx2midi: midilib/libmidi.a mdx2midi_LIBS=midilib/libmidi.a mml2mdx_LIBS=midilib/libmidi.a +pdx2sf2: sf2cute/build/libsf2cute.a + +pdx2sf2_LIBS=sf2cute/build/libsf2cute.a + +ifneq (,$(findstring MINGW,$(shell uname -s))) +pdx2sf2_LIBS+= -static-libstdc++ -static +endif + # Tests resample-test_SRCS=resample-test.c fixed_resampler.c sinctbl4.h sinctbl3.h fm-driver-test_SRCS=tools.c ym2151.c fm_driver.c fm_opm_driver.c fm_opm_emu_driver.c mdx.c vgm_logger.c vgm_logger.h @@ -78,6 +87,9 @@ $(OBJS): Makefile $(TARGETS):$$(sort $$@.o $$(patsubst %.cpp,%.o,$$(patsubst %.c,%.o,$$($$@_SRCS)))) $(CC) $(filter %.o, $^) -o $@ $(CFLAGS) $(LIBS) $($@_LIBS) +pdx2sf2:$$(sort $$@.o $$(patsubst %.cpp,%.o,$$(patsubst %.c,%.o,$$($$@_SRCS)))) + $(CPPC) $(filter %.o, $^) -o $@ $(CFLAGS) $(LIBS) $($@_LIBS) + mmlc.tab.c mmlc.tab.h: mmlc.y $(YACC) -v -o mmlc.tab.c --defines=mmlc.tab.h $(filter %.y,$^) @@ -87,6 +99,9 @@ mmlc.yy.h: mmlc.tab.h mmlc.yy.c mmlc.yy.h: mmlc.l $(LEX) -o mmlc.yy.c --header-file=$(patsubst %.c,%.h,$@) $(filter %.l,$^) +$(CPPOBJS): %.cpp + $(CPPC) -MMD -c $< -o $@ $(CFLAGS) + $(COBJS): %.c $(CC) -MMD -c $< -o $@ $(CFLAGS) @@ -99,8 +114,12 @@ sinctbl3.h: gensinc midilib/libmidi.a: cd midilib && make CC=gcc libmidi.a +sf2cute/build/libsf2cute.a: + cd sf2cute && cmake -B build . && cd build && make + -include $(OBJS:.o=.d) clean: rm -f $(TARGETS) $(addsuffix .exe,$(TARGETS)) *.o *.d cd midilib && make clean + cd sf2cute && rm -rf build diff --git a/Soundfont.c b/Soundfont.c deleted file mode 100644 index d71487b..0000000 --- a/Soundfont.c +++ /dev/null @@ -1,341 +0,0 @@ -#include -#include -#include -#include -#include // for NULL -#include - -#include "Soundfont.h" - - -SF2_DATA* CreateSF2Base(const char* SoundfontName) -{ - SF2_DATA* SF2Data; - LIST_CHUNK* LstInfo; - LIST_CHUNK* LstSample; - LIST_CHUNK* LstPreset; - ITEM_CHUNK* CurItm; - sfVersionTag SFVer; - char TempStr[0x40]; - time_t CurTime; - struct tm* TimeInfo; - - SF2Data = (SF2_DATA*)malloc(sizeof(SF2_DATA)); - if (SF2Data == NULL) - return NULL; - - // generate Header - SF2Data->fccRIFF = FCC_RIFF; - SF2Data->RiffSize = 0x00; - SF2Data->RiffType = FCC_sfbk; - - // Now generate the 3 LIST chunks. - LstInfo = List_MakeChunk(FCC_INFO); - LstSample = List_MakeChunk(FCC_sdta); - LstPreset = List_MakeChunk(FCC_pdta); - - // Link them correctly and set the pointers in SF2Data. - LstInfo->next = LstSample; - LstSample->next = LstPreset; - SF2Data->Lists = LstInfo; - SF2Data->LastLst = LstPreset; - - - // make the 'INFO' Items - - // Version (ifil) - SFVer.wMajor = 2; - SFVer.wMinor = 0; - CurItm = Item_MakeChunk(FCC_ifil, sizeof(sfVersionTag), &SFVer, 0x01); - List_AddItem(LstInfo, CurItm); - - // Sound Engine (isng) - CurItm = Item_MakeChunk_String(FCC_isng, "EMU8000", 0x01); - List_AddItem(LstInfo, CurItm); - - // Soundfont Name (INAM) - CurItm = Item_MakeChunk_String(FCC_INAM, SoundfontName, 0x01); - List_AddItem(LstInfo, CurItm); - - // Creation Date (ICRD) - time(&CurTime); - TimeInfo = localtime(&CurTime); - strftime(TempStr, 0x40, "%#d %b %Y", TimeInfo); - CurItm = Item_MakeChunk_String(FCC_ICRD, TempStr, 0x01); - List_AddItem(LstInfo, CurItm); - - return SF2Data; -} - -void FreeSF2Data(SF2_DATA* SF2Data) -{ - LIST_CHUNK* CurLst; - LIST_CHUNK* LastLst; - - CurLst = SF2Data->Lists; - while(CurLst != NULL) - { - List_FreeListItems(CurLst); - - LastLst = CurLst; - CurLst = CurLst->next; - free(LastLst); - } - free(SF2Data); - - return; -} - -void CalculateBlockSizes(SF2_DATA* SF2Data) -{ - UINT32 RIFFSize; - LIST_CHUNK* CurLst; - - RIFFSize = 0x04; // RIFF Type - - CurLst = SF2Data->Lists; - while(CurLst != NULL) - { - List_CalculateSize(CurLst); - RIFFSize += 0x08 + CurLst->ckSize; - - CurLst = CurLst->next; - } - SF2Data->RiffSize = RIFFSize; - - return; -} - -UINT8 WriteSF2toFile(SF2_DATA* SF2Data, const char* FileName) -{ - FILE* hFile; - LIST_CHUNK* CurLst; - - hFile = fopen(FileName, "wb"); - if (hFile == NULL) - return 0xFF; - - CalculateBlockSizes(SF2Data); - - // write RIFF header - fwrite(&SF2Data->fccRIFF, 0x04, 0x01, hFile); - fwrite(&SF2Data->RiffSize, 0x04, 0x01, hFile); - fwrite(&SF2Data->RiffType, 0x04, 0x01, hFile); - - CurLst = SF2Data->Lists; - while(CurLst != NULL) - { - List_WriteToFile(CurLst, hFile); - - CurLst = CurLst->next; - } - - fclose(hFile); - - return 0x00; -} - - -// --- List Chunk Handling --- -LIST_CHUNK* List_MakeChunk(const FOURCC fccID) -{ - LIST_CHUNK* ListChk; - - ListChk = (LIST_CHUNK*)malloc(sizeof(LIST_CHUNK)); - if (ListChk == NULL) - return NULL; - - ListChk->ckID = FCC_LIST; - ListChk->ckSize = 0x00; - ListChk->ckType = fccID; - ListChk->Items = NULL; - ListChk->LastItem = ListChk->Items; - ListChk->next = NULL; - - return ListChk; -} - -LIST_CHUNK* List_GetChunk(const LIST_CHUNK* FirstChk, const FOURCC fccID) -{ - const LIST_CHUNK* CurChk; - - CurChk = FirstChk; - while(CurChk != NULL) - { - if (CurChk->ckType == fccID) - return (LIST_CHUNK*)CurChk; - CurChk = CurChk->next; - } - - return NULL; -} - -void List_AddItem(LIST_CHUNK* Chunk, ITEM_CHUNK* Item) -{ - if (Chunk->Items == NULL) - { - Chunk->Items = Item; - Chunk->LastItem = Item; - } - else - { - if (Chunk->LastItem == NULL) // this should really not happen - { - ITEM_CHUNK* CurChk; - - CurChk = Chunk->Items; - while(CurChk->next != NULL) - CurChk = CurChk->next; - Chunk->LastItem = CurChk; - } - - Chunk->LastItem->next = Item; - Chunk->LastItem = Item; - } - - return; -} - -void List_FreeListItems(LIST_CHUNK* Chunk) -{ - ITEM_CHUNK* CurItm; - ITEM_CHUNK* LastItm; - - CurItm = Chunk->Items; - while(CurItm != NULL) - { - Item_FreeItemData(CurItm); - - LastItm = CurItm; - CurItm = CurItm->next; - free(LastItm); - } - Chunk->Items = NULL; - Chunk->LastItem = NULL; - - return; -} - -void List_CalculateSize(LIST_CHUNK* Chunk) -{ - ITEM_CHUNK* CurItm; - UINT32 LstSize; - - CurItm = Chunk->Items; - LstSize = 0x04; // List Type - while(CurItm != NULL) - { - LstSize += 0x08 + CurItm->ckSize; - CurItm = CurItm->next; - } - Chunk->ckSize = LstSize; - - return; -} - -void List_WriteToFile(LIST_CHUNK* Chunk, FILE* hFile) -{ - ITEM_CHUNK* CurItm; - - fwrite(&Chunk->ckID, 0x04, 0x01, hFile); - fwrite(&Chunk->ckSize, 0x04, 0x01, hFile); - fwrite(&Chunk->ckType, 0x04, 0x01, hFile); - - CurItm = Chunk->Items; - while(CurItm != NULL) - { - Item_WriteToFile(CurItm, hFile); - CurItm = CurItm->next; - } - - return; -} - -// --- Item Chunk Handling --- -ITEM_CHUNK* Item_MakeChunk(const FOURCC fccID, DWORD DataSize, void* Data, UINT8 CopyData) -{ - ITEM_CHUNK* ItemChk; - - ItemChk = (ITEM_CHUNK*)malloc(sizeof(ITEM_CHUNK)); - if (ItemChk == NULL) - return NULL; - - ItemChk->ckID = fccID; - ItemChk->ckSize = DataSize; - if (! CopyData) - { - // the Data was already allocated with malloc - ItemChk->ckData = Data; - } - else - { - // We need to make copies of static structures (which are sometimes nicer to work with). - ItemChk->ckData = malloc(DataSize); - memcpy(ItemChk->ckData, Data, DataSize); - } - ItemChk->next = NULL; - - return ItemChk; -} - -ITEM_CHUNK* Item_MakeChunk_String(const FOURCC fccID, const char* Data, UINT8 CopyData) -{ - ITEM_CHUNK* ItemChk; - UINT32 StrSize; - - ItemChk = (ITEM_CHUNK*)malloc(sizeof(ITEM_CHUNK)); - if (ItemChk == NULL) - return NULL; - - StrSize = strlen(Data) + 1; - if (StrSize & 1) - StrSize ++; // pad to even size - - ItemChk->ckID = fccID; - ItemChk->ckSize = StrSize; - if (! CopyData) - { - ItemChk->ckData = (void*)Data; - } - else - { - ItemChk->ckData = malloc(StrSize); - strncpy((char *)ItemChk->ckData, Data, StrSize); // strncpy includes padding :) - } - ItemChk->next = NULL; - - return ItemChk; -} - -ITEM_CHUNK* Item_GetChunk(const ITEM_CHUNK* FirstChk, const FOURCC fccID) -{ - const ITEM_CHUNK* CurChk; - - CurChk = FirstChk; - while(CurChk != NULL) - { - if (CurChk->ckID == fccID) - return (ITEM_CHUNK*)CurChk; - CurChk = CurChk->next; - } - - return NULL; -} - -void Item_FreeItemData(ITEM_CHUNK* Chunk) -{ - free(Chunk->ckData); - Chunk->ckData = NULL; - Chunk->ckSize = 0x00; - - return; -} - -void Item_WriteToFile(ITEM_CHUNK* Chunk, FILE* hFile) -{ - fwrite(&Chunk->ckID, 0x04, 0x01, hFile); - fwrite(&Chunk->ckSize, 0x04, 0x01, hFile); - fwrite(Chunk->ckData, 0x01, Chunk->ckSize, hFile); - - return; -} diff --git a/Soundfont.h b/Soundfont.h deleted file mode 100644 index 680b328..0000000 --- a/Soundfont.h +++ /dev/null @@ -1,359 +0,0 @@ -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __MINGW32__ -#include "windef.h" -#include "mmsystem.h" -#else -#include -typedef uint8_t UINT8; -typedef int16_t INT16; -typedef uint16_t UINT16; -typedef uint32_t UINT32; -typedef char CHAR; -typedef UINT8 BYTE; -typedef INT16 SHORT; -typedef UINT16 WORD; -typedef UINT32 DWORD; -typedef DWORD FOURCC; -#endif - -#include - -// --- General Types --- -typedef struct _item_chunk ITEM_CHUNK; -struct _item_chunk -{ - FOURCC ckID; - DWORD ckSize; - void* ckData; - ITEM_CHUNK* next; -}; - -typedef struct _list_chunk LIST_CHUNK; -struct _list_chunk -{ - FOURCC ckID; - DWORD ckSize; - FOURCC ckType; - ITEM_CHUNK* Items; - ITEM_CHUNK* LastItem; // not really necessary, but improves performance - LIST_CHUNK* next; -}; - -typedef struct _sf2_data -{ - FOURCC fccRIFF; - DWORD RiffSize; - FOURCC RiffType; - LIST_CHUNK* Lists; - LIST_CHUNK* LastLst; // not really necessary, but improves performance -} SF2_DATA; - - -// --- Item Types --- -/*enum SF2_TYPES -{ - SFT_ANY = 0x00, - SFT_STRING = 0x01, - SFT_VER_TAG = 0x10, - SFT_PRESET_HDR, - SFT_PRESET_BAG, - SFT_MOD_LIST, - SFT_GEN_LIST, - SFT_INST, - SFT_INST_BAG, - SFT_INST_MOD_LIST, - SFT_INST_GEN_LIST, - SFT_SAMPLE -};*/ - -#pragma pack(1) - -// -- Info Data Types -- -// -typedef struct -{ - WORD wMajor; - WORD wMinor; -} sfVersionTag; - - -// -- Preset Data Types -- -// -typedef struct -{ - CHAR achPresetName[20]; - WORD wPreset; - WORD wBank; - WORD wPresetBagNdx; - DWORD dwLibrary; - DWORD dwGenre; - DWORD dwMorphology; -} sfPresetHeader; - -// -typedef struct -{ - WORD wGenNdx; - WORD wModNdx; -} sfPresetBag; - -// -typedef struct -{ - UINT16 sfModSrcOper; // sfModulator - UINT16 sfModDestOper; // sfGenerator - SHORT modAmount; - UINT16 sfModAmtSrcOper; // sfModulator - UINT16 sfModTransOper; // sfTransform -} sfModList; - -// -typedef struct -{ - BYTE byLo; - BYTE byHi; -} rangesType; - -typedef union -{ - rangesType ranges; - SHORT shAmount; - WORD wAmount; -} genAmountType; - -typedef enum -{ - startAddrsOffset = 0, // instrument only - endAddrsOffset = 1, // instrument only - startloopAddrsOffset = 2, // instrument only - endloopAddrsOffset = 3, // instrument only - startAddrsCoarseOffset = 4, // instrument only - modLfoToPitch = 5, - vibLfoToPitch = 6, - modEnvToPitch = 7, - initialFilterFc = 8, - initialFilterQ = 9, - modLfoToFilterFc = 10, - modEnvToFilterFc = 11, - endAddrsCoarseOffset = 12, // instrument only - modLfoToVolume = 13, - chorusEffectsSend = 15, - reverbEffectsSend = 16, - pan = 17, - delayModLFO = 21, - freqModLFO = 22, - delayVibLFO = 23, - freqVibLFO = 24, - delayModEnv = 25, - attackModEnv = 26, - holdModEnv = 27, - decayModEnv = 28, - sustainModEnv = 29, - releaseModEnv = 30, - keynumToModEnvHold = 31, - keynumToModEnvDecay = 32, - delayVolEnv = 33, - attackVolEnv = 34, - holdVolEnv = 35, - decayVolEnv = 36, - sustainVolEnv = 37, - releaseVolEnv = 38, - keynumToVolEnvHold = 39, - keynumToVolEnvDecay = 40, - instrument = 41, // preset only - keyRange = 43, - velRange = 44, - startloopAddrsCoarseOffset = 45, // instrument only - keynum = 46, // instrument only - velocity = 47, // instrument only - initialAttenuation = 48, - endloopAddrsCoarseOffset = 50, // instrument only - coarseTune = 51, - fineTune = 52, - sampleID = 53, - sampleModes = 54, // instrument only - scaleTuning = 56, - exclusiveClass = 57, // instrument only - overridingRootKey = 58, // instrument only - endOper = 60 -} sfGenerator; - -typedef struct -{ - UINT16 sfGenOper; // sfGenerator - genAmountType genAmount; -} sfGenList; - -// -typedef struct -{ - CHAR achInstName[20]; - WORD wInstBagNdx; -} sfInst; - -// -typedef struct -{ - WORD wInstGenNdx; - WORD wInstModNdx; -} sfInstBag; - -// -typedef struct -{ - UINT16 sfModSrcOper; // sfModulator - UINT16 sfModDestOper; // sfGenerator - SHORT modAmount; - UINT16 sfModAmtSrcOper; // sfModulator - UINT16 sfModTransOper; // sfTransform -} sfInstModList; - -// -typedef struct -{ - UINT16 sfGenOper; // sfGenerator - genAmountType genAmount; -} sfInstGenList; - -// -typedef enum -{ - monoSample = 1, - rightSample = 2, - leftSample = 4, - linkedSample = 8, - RomMonoSample = 0x8001, - RomRightSample = 0x8002, - RomLeftSample = 0x8004, - RomLinkedSample = 0x8008 -} SFSampleLink; - -typedef struct -{ - CHAR achSampleName[20]; - DWORD dwStart; - DWORD dwEnd; - DWORD dwStartloop; - DWORD dwEndloop; - DWORD dwSampleRate; - BYTE byOriginalKey; - CHAR chCorrection; - WORD wSampleLink; - UINT16 sfSampleType; // SFSampleLink -} sfSample; - -#pragma pack() - -/* -Format: (mostly copy-paste from SF2 Specification 2.1 and 2.4) - -RIFF 'sfbk' -{ - LIST 'INFO' // Supplemental Information - { - // Refers to the version of the Sound Font RIFF file - // Refers to the target Sound Engine - // Refers to the Sound Font Bank Name - [] // Refers to the Sound ROM Name - [] // Refers to the Sound ROM Version - [] // Refers to the Date of Creation of the Bank - [] // Sound Designers and Engineers for the Bank - [] // Product for which the Bank was intended - [] // Contains any Copyright message - [] // Contains any Comments on the Bank - [] // The SoundFont tools used to create and alter the bank - } - - LIST 'SDTA' // The Sample Binary Data - { - [ // The Preset Headers - // The Preset Index list - // The Preset Modulator list - // The Preset Generator list - // The Instrument Names and Indices - // The Instrument Index list - // The Instrument Modulator list - // The Instrument Generator list - // The Sample Headers - } -} -*/ - - -#ifndef MAKEFOURCC -// macro from mmsystem.h -#define MAKEFOURCC(ch0, ch1, ch2, ch3) \ - ((DWORD)(BYTE)(ch0) | ((DWORD)(BYTE)(ch1) << 8) | \ - ((DWORD)(BYTE)(ch2) << 16) | ((DWORD)(BYTE)(ch3) << 24 )) -#endif - -// RIFF header FCCs -#define FCC_RIFF MAKEFOURCC('R', 'I', 'F', 'F') -#define FCC_sfbk MAKEFOURCC('s', 'f', 'b', 'k') -#define FCC_LIST MAKEFOURCC('L', 'I', 'S', 'T') - -// LIST chunk FCCs -#define FCC_INFO MAKEFOURCC('I', 'N', 'F', 'O') -#define FCC_sdta MAKEFOURCC('s', 'd', 't', 'a') -#define FCC_pdta MAKEFOURCC('p', 'd', 't', 'a') - -// 'INFO' chunk FCCs -#define FCC_ifil MAKEFOURCC('i', 'f', 'i', 'l') -#define FCC_isng MAKEFOURCC('i', 's', 'n', 'g') -#define FCC_INAM MAKEFOURCC('I', 'N', 'A', 'M') -#define FCC_irom MAKEFOURCC('i', 'r', 'o', 'm') -#define FCC_iver MAKEFOURCC('i', 'v', 'e', 'r') -#define FCC_ICRD MAKEFOURCC('I', 'C', 'R', 'D') -#define FCC_IENG MAKEFOURCC('I', 'E', 'N', 'G') -#define FCC_IPRD MAKEFOURCC('i', 'P', 'R', 'D') -#define FCC_ICOP MAKEFOURCC('I', 'C', 'O', 'P') -#define FCC_ICMT MAKEFOURCC('I', 'C', 'M', 'T') -#define FCC_ISFT MAKEFOURCC('I', 'S', 'F', 'T') - -// 'sdta' chunk FCCs -#define FCC_smpl MAKEFOURCC('s', 'm', 'p', 'l') -#define FCC_sm24 MAKEFOURCC('s', 'm', '2', '4') - -// 'pdta' chunk FCCs -#define FCC_phdr MAKEFOURCC('p', 'h', 'd', 'r') -#define FCC_pbag MAKEFOURCC('p', 'b', 'a', 'g') -#define FCC_pmod MAKEFOURCC('p', 'm', 'o', 'd') -#define FCC_pgen MAKEFOURCC('p', 'g', 'e', 'n') -#define FCC_inst MAKEFOURCC('i', 'n', 's', 't') -#define FCC_ibag MAKEFOURCC('i', 'b', 'a', 'g') -#define FCC_imod MAKEFOURCC('i', 'm', 'o', 'd') -#define FCC_igen MAKEFOURCC('i', 'g', 'e', 'n') -#define FCC_shdr MAKEFOURCC('s', 'h', 'd', 'r') - - -SF2_DATA* CreateSF2Base(const char* SoundfontName); -void FreeSF2Data(SF2_DATA* SF2Data); -void CalculateBlockSizes(SF2_DATA* SF2Data); -UINT8 WriteSF2toFile(SF2_DATA* SF2Data, const char* FileName); - -// --- List Chunk Handling --- -LIST_CHUNK* List_MakeChunk(const FOURCC fccID); -LIST_CHUNK* List_GetChunk(const LIST_CHUNK* FirstChk, const FOURCC fccID); -void List_AddItem(LIST_CHUNK* Chunk, ITEM_CHUNK* Item); -void List_FreeListItems(LIST_CHUNK* Chunk); -void List_CalculateSize(LIST_CHUNK* Chunk); -void List_WriteToFile(LIST_CHUNK* Chunk, FILE* hFile); - -// --- Item Chunk Handling --- -ITEM_CHUNK* Item_MakeChunk(const FOURCC fccID, DWORD DataSize, void* Data, UINT8 CopyData); -ITEM_CHUNK* Item_MakeChunk_String(const FOURCC fccID, const char* Data, UINT8 CopyData); -ITEM_CHUNK* Item_GetChunk(const ITEM_CHUNK* FirstChk, const FOURCC fccID); -void Item_FreeItemData(ITEM_CHUNK* Chunk); -void Item_WriteToFile(ITEM_CHUNK* Chunk, FILE* hFile); - -#ifdef __cplusplus -}; -#endif diff --git a/adpcm-driver-test.c b/adpcm-driver-test.c index e2c731f..ca05300 100644 --- a/adpcm-driver-test.c +++ b/adpcm-driver-test.c @@ -25,9 +25,9 @@ int main(int argc, char **argv) { return 1; } - adpcm_driver_play((struct adpcm_driver *)&driver, 2, sample, sample_len, 4, 255); - adpcm_driver_play((struct adpcm_driver *)&driver, 4, sample, sample_len, 2, 255); - adpcm_driver_play((struct adpcm_driver *)&driver, 3, sample, sample_len, 0, 255); + adpcm_driver_play((struct adpcm_driver *)&driver, 2, sample, sample_len, 4, 255, 0); + adpcm_driver_play((struct adpcm_driver *)&driver, 4, sample, sample_len, 2, 255, 0); + adpcm_driver_play((struct adpcm_driver *)&driver, 3, sample, sample_len, 0, 255, 0); SF_INFO sfinfo = { .samplerate = opt_sample_rate, @@ -35,8 +35,8 @@ int main(int argc, char **argv) { .format = SF_FORMAT_WAV | SF_FORMAT_PCM_16, }; char wavname[256]; - printf("Outputting to %s\n", wavname); replace_ext(wavname, sizeof(wavname), argv[1], "wav"); + printf("Outputting to %s\n", wavname); SNDFILE *file = sf_open(wavname, SFM_WRITE, &sfinfo); if(!file) { printf("Failed to open file for writing\n"); diff --git a/adpcm_driver.c b/adpcm_driver.c index c47e966..047bfe9 100644 --- a/adpcm_driver.c +++ b/adpcm_driver.c @@ -10,9 +10,9 @@ void adpcm_driver_init(struct adpcm_driver *driver) { driver->pan = 3; } -int adpcm_driver_play(struct adpcm_driver *d, uint8_t channel, uint8_t *data, int len, uint8_t freq, uint8_t vol) { +int adpcm_driver_play(struct adpcm_driver *d, uint8_t channel, uint8_t *data, int len, uint8_t freq, uint8_t vol, int note) { if(d->play) - return d->play(d, channel, data, len, freq, vol); + return d->play(d, channel, data, len, freq, vol, note); return 0; } diff --git a/adpcm_driver.h b/adpcm_driver.h index 9dba5dd..c9f17b2 100644 --- a/adpcm_driver.h +++ b/adpcm_driver.h @@ -6,7 +6,7 @@ struct adpcm_driver { uint8_t pan; - int (*play)(struct adpcm_driver *d, uint8_t channel, uint8_t *data, int len, uint8_t freq, uint8_t vol); + int (*play)(struct adpcm_driver *d, uint8_t channel, uint8_t *data, int len, uint8_t freq, uint8_t vol, int note); int (*stop)(struct adpcm_driver *d, uint8_t channel); int (*set_volume)(struct adpcm_driver *d, uint8_t channel, uint8_t vol); int (*set_freq)(struct adpcm_driver *d, uint8_t channel, uint8_t freq); @@ -14,7 +14,7 @@ struct adpcm_driver { }; void adpcm_driver_init(struct adpcm_driver *driver); -int adpcm_driver_play(struct adpcm_driver *d, uint8_t channel, uint8_t *data, int len, uint8_t freq, uint8_t vol); +int adpcm_driver_play(struct adpcm_driver *d, uint8_t channel, uint8_t *data, int len, uint8_t freq, uint8_t vol, int note); int adpcm_driver_stop(struct adpcm_driver *d, uint8_t channel); int adpcm_driver_set_freq(struct adpcm_driver *d, uint8_t channel, uint8_t freq); int adpcm_driver_set_volume(struct adpcm_driver *d, uint8_t channel, uint8_t vol); diff --git a/adpcm_midi_driver.c b/adpcm_midi_driver.c index 852ae8d..3530be0 100644 --- a/adpcm_midi_driver.c +++ b/adpcm_midi_driver.c @@ -1,14 +1,57 @@ #include "adpcm_midi_driver.h" -int adpcm_midi_driver_play(struct adpcm_driver *d, uint8_t channel, uint8_t *data, int len, uint8_t freq, uint8_t vol) { +int adpcm_midi_driver_play(struct adpcm_driver *d, uint8_t channel, uint8_t *data, int len, uint8_t freq, uint8_t vol, int note) { + // play an entire sample. data is a pointer to an array containing the sample data. len is the length of the sample data. + #define BASE_NOTE 36 + + struct adpcm_midi_driver *mididrv = (struct adpcm_midi_driver *)d; + + if (mididrv->channels[channel].midi_note != -1) { // make sure that there are no endless notes. + midi_track_write_note_off(&mididrv->midi_file->tracks[channel + 9], mididrv->channels[channel].ticks, channel+8, mididrv->channels[channel].midi_note, 127); + mididrv->channels[channel].ticks = 0; + } + + if (vol>8) vol=8; + int midi_vol = ((double)vol / (double)8) * 127; + if (midi_vol != mididrv->channels[channel].midi_vol) { + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 9], mididrv->channels[channel].ticks, channel+8, 7, midi_vol); + mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_vol = midi_vol; + } + + int midi_note = note + BASE_NOTE; + midi_track_write_note_on(&mididrv->midi_file->tracks[channel + 9], mididrv->channels[channel].ticks, channel+8, midi_note, 127); + mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_note = midi_note; // save the currently playing midi note. + return 0; } int adpcm_midi_driver_stop(struct adpcm_driver *d, uint8_t channel) { + struct adpcm_midi_driver *mididrv = (struct adpcm_midi_driver *)d; + + if (mididrv->channels[channel].midi_note != -1) { + midi_track_write_note_off(&mididrv->midi_file->tracks[channel + 9], mididrv->channels[channel].ticks, channel+8, mididrv->channels[channel].midi_note, 127); + mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_note = -1; // no note is playing. + } + return 0; } -int adpcm_midi_driver_set_volume(struct adpcm_driver *d, uint8_t channel, uint8_t vol) { +int adpcm_midi_driver_set_volume(struct adpcm_driver *d, uint8_t channel, uint8_t vol) { // TODO: research the adpcm chip's volume control more. + + struct adpcm_midi_driver *mididrv = (struct adpcm_midi_driver *)d; + + if (vol>8) vol=8; + int midi_vol = ((double)vol / (double)8) * 127; + if (midi_vol != mididrv->channels[channel].midi_vol) { + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 9], mididrv->channels[channel].ticks, channel+8, 7, midi_vol); + mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_vol = midi_vol; + } + + return 0; } @@ -16,7 +59,31 @@ int adpcm_midi_driver_set_freq(struct adpcm_driver *d, uint8_t channel, uint8_t return 0; } -int adpcm_midi_driver_set_pan(struct adpcm_driver *d, uint8_t pan) { +int adpcm_midi_driver_set_pan(struct adpcm_driver *d, uint8_t pan) { // sets pan for the entire chip + struct adpcm_midi_driver *mididrv = (struct adpcm_midi_driver *)d; + + int midi_pan = 64; + + switch (pan) { + case 0b10: // right + midi_pan = 127; + break; + case 0b01: // left + midi_pan = 0; + break; + case 0b11: // center + default: + midi_pan = 64; + break; + } + + if (midi_pan != mididrv->midi_pan) { + for (int channel=0; channel < 8; channel++){ + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 9], mididrv->channels[channel].ticks, channel+8, 10, midi_pan); + mididrv->channels[channel].ticks = 0; + } + mididrv->midi_pan = midi_pan; + } return 0; } @@ -29,8 +96,12 @@ int adpcm_midi_driver_init(struct adpcm_midi_driver *driver, struct midi_file *m driver->adpcm_driver.set_pan = adpcm_midi_driver_set_pan; driver->midi_file = midi_file; - for(int i = 0; i < 8; i++) + driver->midi_pan = -1; + for(int i = 0; i < 8; i++) { driver->channels[i].ticks = 0; + driver->channels[i].midi_note = -1; + driver->channels[i].midi_vol = -1; + } return 0; } diff --git a/adpcm_midi_driver.h b/adpcm_midi_driver.h index b0ef8bc..f808607 100644 --- a/adpcm_midi_driver.h +++ b/adpcm_midi_driver.h @@ -6,12 +6,14 @@ struct adpcm_midi_driver_channel { int ticks; + int midi_note, midi_vol; }; struct adpcm_midi_driver { struct adpcm_driver adpcm_driver; // parent struct midi_file *midi_file; struct adpcm_midi_driver_channel channels[8]; + int midi_pan; }; int adpcm_midi_driver_init(struct adpcm_midi_driver *driver, struct midi_file *midi_file); int adpcm_midi_driver_tick(struct adpcm_midi_driver *driver); diff --git a/adpcm_pcm_mix_driver.c b/adpcm_pcm_mix_driver.c index 1486675..6f6a50a 100644 --- a/adpcm_pcm_mix_driver.c +++ b/adpcm_pcm_mix_driver.c @@ -96,7 +96,7 @@ static stream_sample_t adpcm_mix_driver_channel_get_sample(struct adpcm_mix_driv return sample; } -static int adpcm_mix_driver_channel_play(struct adpcm_mix_driver_channel *channel, uint8_t *data, int data_len, uint8_t freq_num, uint8_t volume) { +static int adpcm_mix_driver_channel_play(struct adpcm_mix_driver_channel *channel, uint8_t *data, int data_len, uint8_t freq_num, uint8_t volume, int note) { adpcm_init(&channel->decoder_status); channel->data = data; @@ -141,9 +141,9 @@ static int adpcm_pcm_mix_driver_alloc_buffers(struct adpcm_pcm_mix_driver *drive return 0; } -static int adpcm_pcm_mix_driver_play(struct adpcm_driver *driver, uint8_t channel, uint8_t *data, int data_len, uint8_t freq_num, uint8_t volume) { +static int adpcm_pcm_mix_driver_play(struct adpcm_driver *driver, uint8_t channel, uint8_t *data, int data_len, uint8_t freq_num, uint8_t volume, int note) { struct adpcm_pcm_mix_driver *pdrv = (struct adpcm_pcm_mix_driver *)driver; - return adpcm_mix_driver_channel_play(&pdrv->channels[channel], data, data_len, freq_num, volume); + return adpcm_mix_driver_channel_play(&pdrv->channels[channel], data, data_len, freq_num, volume, note); } static int adpcm_pcm_mix_driver_stop(struct adpcm_driver *driver, uint8_t channel) { diff --git a/fm_midi_driver.c b/fm_midi_driver.c index 4d1bb67..2d867dc 100644 --- a/fm_midi_driver.c +++ b/fm_midi_driver.c @@ -1,26 +1,37 @@ #include "fm_midi_driver.h" +#define TRANSPOSE 3 // add 3 semitones to every note. this matches mdx.c line 217. void fm_midi_driver_reset_key_sync(struct fm_driver *driver, int channel) { struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; - (void)mididrv; + + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 74, 127); + mididrv->channels[channel].ticks = 0; + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 74, 0); // TEST. I don't know if this will break the reset. + mididrv->channels[channel].ticks = 0; } void fm_midi_driver_set_pms_ams(struct fm_driver *driver, int channel, uint8_t pms_ams) { struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; - (void)mididrv; + + uint8_t pms = (pms_ams>>4) & 7; + uint8_t ams = (pms_ams & 3); + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 75, ((double)pms / (double)7) * 127); + mididrv->channels[channel].ticks = 0; + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 76, ((double)ams / (double)3) * 127); + mididrv->channels[channel].ticks = 0; } void fm_midi_driver_set_pitch(struct fm_driver *driver, int channel, int pitch) { struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; if(mididrv->channels[channel].on && pitch != mididrv->channels[channel].pitch) { - int root_pitch = mididrv->channels[channel].note << 6; - int detune = ((pitch >> 8) - 5 - root_pitch); - - if(detune != mididrv->channels[channel].detune) { - midi_track_write_pitch_bend(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 8192 + detune * 4); - mididrv->channels[channel].detune = detune; - mididrv->channels[channel].ticks = 0; - } + + int orig_note_pitch = ((pitch >> 14) & 0x7F) + TRANSPOSE; + int orig_pitch_bend = ((pitch >> 8)) & 0x3F; + int note_difference = orig_note_pitch - mididrv->channels[channel].midi_note; // the number of semitones by which the pitch has changed. If negative, go down that many number of semitones. + int midi_pitch_bend = 8192 + (note_difference * 256 /*pitch bend value of one semitone, when the pitch bend range is 32*/) + (orig_pitch_bend * 4); // midi_pitch_bend needs to include both orig_note_pitch and orig_pitch_bend. + midi_track_write_pitch_bend(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, midi_pitch_bend); + mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_pitch_bend = midi_pitch_bend; } mididrv->channels[channel].pitch = pitch; } @@ -28,49 +39,138 @@ void fm_midi_driver_set_pitch(struct fm_driver *driver, int channel, int pitch) void fm_midi_driver_set_tl(struct fm_driver *driver, int channel, uint8_t tl, uint8_t *v) { struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; mididrv->channels[channel].tl = tl; + + const uint8_t con_masks[8] = { + 0x08, 0x08, 0x08, 0x08, 0x0c, 0x0e, 0x0e, 0x0f, + }; + int mask = 1; + for(int i = 0; i < 4; i++, mask <<= 1) { + int op_vol = v[7 + i]; // op_vol is a uint8_t. i is a single operator. There are 4 operators for each channel. There are 8 FM channels total. + int true_op = 0; + switch (i) { // TODO: TEST if op vol is being assigned to each operator correctly. + case 1: + true_op = 2; + break; + case 2: + true_op = 1; + break; + default: + true_op = i; + break; + } + int midi_vol = 0; + if((con_masks[v[1] & 0x07] & mask) > 0) { + int vol = tl + op_vol; + if(vol > 0x7f) vol = 0x7f; + midi_vol = 127 - vol; // volume inputs for OPM registers are inverted, so 0 in a register is the loudest. VOPMex by default handles it in a way that's more intuitive where 127 is the loudest, so the volume must be inverted here. + } else { + midi_vol = 127 - op_vol; + } + if (midi_vol != mididrv->channels[channel].midi_vol[true_op]) { + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 16 + true_op /*VOPMax TL CC*/, midi_vol); + mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_vol[true_op] = midi_vol; + } + } } -void fm_midi_driver_note_on(struct fm_driver *driver, int channel, uint8_t op_mask, uint8_t *v) { +void fm_midi_driver_note_on(struct fm_driver *driver, int channel, uint8_t op_mask, uint8_t *v) { // *v is a pointer to the orignal voice data from the MDX, and "channel" is the new track being written. + struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; + + if (mididrv->channels[channel].on == 1 && mididrv->channels[channel].midi_note != -1) { + midi_track_write_note_off(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, mididrv->channels[channel].midi_note, 127); + mididrv->channels[channel].ticks = 0; + } + + // force pitch bend range to 32 for every note. Simple fix for when the DAW / VOPMex forgets pitch bend range. TODO: have this be a command line option for mdx2midi? + midi_track_write_rpn_msb(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 0, 32); // Pitch Bend Range + mididrv->channels[channel].ticks = 0; + + if (mididrv->channels[channel].midi_pan == -1) mididrv->channels[channel].midi_pan = 64; + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 10, mididrv->channels[channel].midi_pan); // prevent VOPMex from forgetting panning when skipping through the song. + mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].on = 1; - int pitch = (mididrv->channels[channel].pitch >> 8) - 5; - int detune = pitch & 0x3f; - int note = pitch >> 6; - mididrv->channels[channel].note = note; - if(detune != mididrv->channels[channel].detune) { - midi_track_write_pitch_bend(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 8192 + detune * 4); + + int orig_pitch_bend = (mididrv->channels[channel].pitch >> 8) & 0x3F; + int orig_note = ((mididrv->channels[channel].pitch >> 14) & 0x7F) + TRANSPOSE; + int midi_note = orig_note; + int midi_pitch_bend = 8192; // centered. + if (orig_pitch_bend > 32 && 0 /*disable this branch for now for stability*/) { // if the pitch bend would raise the pitch by more than a quarter tone (half a semitone), raise the note by one semitone and invert the pitch bend. This is done to avoid too many large pitch bends in the output. + midi_note++; + int downward_orig_pitch_bend = 64 - orig_pitch_bend; + midi_pitch_bend = 8192 - (downward_orig_pitch_bend * 4); + } else { + midi_pitch_bend = 8192 + (orig_pitch_bend * 4); + } + //if(midi_pitch_bend != mididrv->channels[channel].midi_pitch_bend) { // I disabled this check because the DAW / VOPMex was forgetting the current pitch bend. + midi_track_write_pitch_bend(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, midi_pitch_bend); mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_pitch_bend = midi_pitch_bend; + //} + + for (int i=0; i<4; i++){ // ensure VOPMex doesn't discard operator volume after a PC. + if (mididrv->channels[channel].midi_vol[i] != -1) { + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 16 + i, mididrv->channels[channel].midi_vol[i]); + mididrv->channels[channel].ticks = 0; + } } - midi_track_write_note_on(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, note, 127); + + midi_track_write_note_on(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, midi_note, 127); mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_note = midi_note; // save the currently playing midi note. } void fm_midi_driver_note_off(struct fm_driver *driver, int channel) { struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; - (void)mididrv; + mididrv->channels[channel].on = 0; - int pitch = (mididrv->channels[channel].pitch >> 8) - 5; - int detune = pitch & 0x3f; - int note = pitch >> 6; - mididrv->channels[channel].detune = detune; - midi_track_write_note_off(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, note, 127); + midi_track_write_note_off(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, mididrv->channels[channel].midi_note, 127); mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_note = -1; // no note is playing. } -void fm_midi_driver_write_opm_reg(struct fm_driver *driver, uint8_t reg, uint8_t data) { - struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; - (void)mididrv; +void fm_midi_driver_write_opm_reg(struct fm_driver *driver, uint8_t reg, uint8_t data) { // TODO: finish implementing all midi functions (just this opm reg write one now). TEST all functions in this file. + // struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; } void fm_midi_driver_set_pan(struct fm_driver *driver, int channel, uint8_t pan, uint8_t *v) { struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; - (void)mididrv; + + int midi_pan = 64; + + switch (pan) { + case 0b10: // right + midi_pan = 127; + break; + case 0b01: // left + midi_pan = 0; + break; + case 0b11: // center + default: + midi_pan = 64; + break; + } + + //if (midi_pan != mididrv->channels[channel].midi_pan) { // pan changes don't seem to be frequent enough to need this check. + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 10, midi_pan); + mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].midi_pan = midi_pan; + //} } void fm_midi_driver_set_noise_freq(struct fm_driver *driver, int channel, int freq) { struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; - (void)mididrv; + + int midi_freq = ((double)freq / (double)0x1f) * 127; + + if (midi_freq != mididrv->channels[channel].freq) { + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 82, midi_freq); + mididrv->channels[channel].ticks = 0; + mididrv->channels[channel].freq = midi_freq; + } } void fm_midi_driver_load_voice(struct fm_driver *driver, int channel, uint8_t *v, int voice_num, int volume, int pan) { @@ -81,7 +181,25 @@ void fm_midi_driver_load_voice(struct fm_driver *driver, int channel, uint8_t *v void fm_midi_driver_load_lfo(struct fm_driver *driver, int channel, uint8_t wave, uint8_t freq, uint8_t pmd, uint8_t amd) { struct fm_midi_driver *mididrv = (struct fm_midi_driver *)driver; - (void)mididrv; + + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 12, ((double)wave / (double)3) * 127); + mididrv->channels[channel].ticks = 0; + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 1, freq); // TODO: research freq more and make sure it's implemented correctly. + mididrv->channels[channel].ticks = 0; + + if (pmd & 0b10000000 || amd & 0b10000000 /*TODO: try removing amd here*/) { // only one of amd or pmd is active at a time. + // PMD + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 2, pmd); + mididrv->channels[channel].ticks = 0; + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 3, 0); + mididrv->channels[channel].ticks = 0; + } else { + // AMD + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 3, amd); + mididrv->channels[channel].ticks = 0; + midi_track_write_control_change(&mididrv->midi_file->tracks[channel + 1], mididrv->channels[channel].ticks, channel, 2, 0); + mididrv->channels[channel].ticks = 0; + } } void fm_midi_driver_init(struct fm_midi_driver *driver, struct midi_file *midi_file) { @@ -106,6 +224,13 @@ void fm_midi_driver_init(struct fm_midi_driver *driver, struct midi_file *midi_f driver->channels[i].detune = 0; driver->channels[i].tl = 0; driver->channels[i].ticks = 0; + driver->channels[i].midi_note = -1; + driver->channels[i].midi_pitch_bend = -1; + driver->channels[i].midi_pan = -1; + driver->channels[i].freq = -1; + for(int i2 = 0; i2 < 4; i2++) { + driver->channels[i].midi_vol[i2] = -1; + } } } diff --git a/fm_midi_driver.h b/fm_midi_driver.h index cdc6c24..0da4576 100644 --- a/fm_midi_driver.h +++ b/fm_midi_driver.h @@ -6,6 +6,8 @@ struct fm_midi_driver_channel { int on, pitch, note, detune, tl, ticks; + int midi_note, midi_pitch_bend, midi_pan, freq; + int midi_vol[4]; }; /* MIDI driver */ diff --git a/mdx2midi.c b/mdx2midi.c index 739ea6a..4a7ec47 100644 --- a/mdx2midi.c +++ b/mdx2midi.c @@ -28,7 +28,7 @@ static int write_cb(void *buf, size_t len, void *data_ptr) { return fwrite(buf, 1, len, (FILE *)data_ptr); } -int main(int argc, char **argv) { +int main(int argc, char **argv) { // NOTE: the tempo in the output midi does not always match the musical tempo of the song. for Bathead (DRA01) it does, but not Moon Fight (DRA17). This program will output midi conversions that play at the correct speed, but it seems to be impossible to automatically create a good musical structure for the output. int optind = cmdline_parse_args(argc, argv, (struct cmdline_option[]){ { 'm', "mask", @@ -74,11 +74,12 @@ int main(int argc, char **argv) { midi_track_write_track_name(track, 0, buf, -1); midi_track_write_bank_select(track, 0, t, 0); midi_track_write_control_change(track, 0, t, 126, 127); // Monophonic mode - if(t >= 8) { // OPM only - midi_track_write_nrpn_msb(track, 0, t, 0, 112); // OPM Clock = 4MHz + if(t < 8) { // OPM only + midi_track_write_nrpn_msb(track, 0, t, 0, 112); // OPM Clock = 4MHz // TODO: set this constantly throughout track, to ensure skipping through a song doesn't erase it? } - midi_track_write_control_change(track, 0, t, 12, 0); // Amplitude LFO level - midi_track_write_control_change(track, 0, t, 13, 0); // Pitch LFO level + // All of the Midi controls changes are designed for VOPMex extended mode (the default). Output Midi files will not work with VOPM. + midi_track_write_control_change(track, 0, t, 3, 0); // Amplitude LFO level. + midi_track_write_control_change(track, 0, t, 2, 0); // Pitch LFO level midi_track_write_rpn_msb(track, 0, t, 0, 32); // Pitch Bend Sensitivity midi_track_write_pitch_bend(track, 0, t, 0x2000); } @@ -94,7 +95,7 @@ int main(int argc, char **argv) { struct adpcm_midi_driver adpcm_driver; struct fm_midi_driver fm_driver; - midi_timer_driver_init(&timer_driver); + midi_timer_driver_init(&timer_driver, &midi_file); adpcm_midi_driver_init(&adpcm_driver, &midi_file); fm_midi_driver_init(&fm_driver, &midi_file); mdx_driver_init( @@ -116,6 +117,7 @@ int main(int argc, char **argv) { printf("Loaded MDX file \"%s\"\n", argv[i]); mdx_driver_load(&mdx_driver, &mdx_file, &pdx_file); + printf("Loaded MDX driver.\n"); while(!mdx_driver.ended) { adpcm_midi_driver_tick(&adpcm_driver); diff --git a/mdx2opm.c b/mdx2opm.c index 3234d02..3123aea 100644 --- a/mdx2opm.c +++ b/mdx2opm.c @@ -28,21 +28,16 @@ static void printVoice(uint8_t *v, int num) { } } -void printEmptyVoice(int num) { - printf("@:%d no Name %d\r\n", num, num); - printf("LFO: 0 0 0 0 0\r\n"); - printf("CH: 64 0 0 0 0 64 0\r\n"); - printf("M1: 31 0 0 4 0 0 0 1 0 0 0\r\n"); - printf("C1: 31 0 0 4 0 0 0 1 0 0 0\r\n"); - printf("M2: 31 0 0 4 0 0 0 1 0 0 0\r\n"); - printf("C2: 31 0 0 4 0 0 0 1 0 0 0\r\n"); -} - int main(int argc, char **argv) { + if (argc == 1) { + printf("Usage: mdx2opm your_mdx_file.mdx\nThis program will output text to the command line; this text can be saved to a .opm file to make an instrument set for VOPM.\nYou can immediately write this program's output to a .opm file by using the below command structure. This works on both Windows and Linux.\nmdx2opm your_mdx_file.mdx > your_instruments.opm\n"); + return 1; + } for(int i = 1; i < argc; i++) { size_t l; uint8_t *mdx_data = load_file(argv[i], &l); if(!mdx_data) { + printf("Failed to load file.\r\n"); return 1; } struct mdx_file f; @@ -59,8 +54,7 @@ int main(int argc, char **argv) { printf("// CH: PAN FL CON AMS PMS SLOT NE\r\n"); printf("// [OPname]: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN\r\n"); for(int i = 0; i < 256; i++) { - if(i < f.num_voices) printVoice(f.voices[i], i); - else printEmptyVoice(i); + if(f.voices[i] != NULL) printVoice(f.voices[i], i); } } return 0; diff --git a/mdx_driver.c b/mdx_driver.c index 66377c6..4a21cf8 100644 --- a/mdx_driver.c +++ b/mdx_driver.c @@ -176,7 +176,7 @@ static void mdx_driver_note_on(struct mdx_driver *r, int track_num) { } else printf("Note on !v %d\n", track_num); } } else if(track->note < 96 && r->pdx_file) - adpcm_driver_play(r->adpcm_driver, track_num - 8, r->pdx_file->samples[track->note].data, r->pdx_file->samples[track->note].len, track->adpcm_freq_num, mdx_adpcm_volume_from_opm(track->opm_volume + r->fade_value)); + adpcm_driver_play(r->adpcm_driver, track_num - 8, r->pdx_file->samples[track->note].data, r->pdx_file->samples[track->note].len, track->adpcm_freq_num, mdx_adpcm_volume_from_opm(track->opm_volume + r->fade_value), track->note); } static void mdx_driver_note_off(struct mdx_driver *driver, int track_num) { diff --git a/midi_timer_driver.c b/midi_timer_driver.c index 3417378..180b238 100644 --- a/midi_timer_driver.c +++ b/midi_timer_driver.c @@ -1,13 +1,27 @@ #include "midi_timer_driver.h" +#include static void midi_timer_driver_set_opm_tempo(struct timer_driver *driver, int opm_tempo) { - + struct midi_timer_driver *mididrv = (struct midi_timer_driver *)driver; + + uint32_t BPM = 60 * 4000000 / (48 * 1024 * (256 - opm_tempo)); // Convert opm_tempo to Beats per Minute + printf("opm_tempo: %d, BPM: %d\n", opm_tempo, BPM); + + if (mididrv->tempoBPM != BPM) { + midi_track_write_tempo(&mididrv->midi_file->tracks[0], mididrv->ticks, 60000000 / BPM); // the tempo function takes microseconds per quarter note. + mididrv->ticks = 0; + mididrv->tempoBPM = BPM; + } } -int midi_timer_driver_init(struct midi_timer_driver *driver) { +int midi_timer_driver_init(struct midi_timer_driver *driver, struct midi_file *midi_file) { + printf("Run midi_timer_driver_init\n"); timer_driver_init(&driver->timer_driver); driver->ticks_per_quarter_note = 48; driver->timer_driver.set_opm_tempo = midi_timer_driver_set_opm_tempo; + driver->ticks = 0; + driver->midi_file = midi_file; + driver->tempoBPM=0; return 0; } @@ -15,7 +29,10 @@ void midi_timer_driver_deinit(struct midi_timer_driver *driver) { } int midi_timer_driver_tick(struct midi_timer_driver *driver) { + if(!driver->timer_driver.tick) return -1; driver->timer_driver.tick((struct timer_driver *)&driver, driver->timer_driver.data_ptr); + + driver->ticks++; return 0; } diff --git a/midi_timer_driver.h b/midi_timer_driver.h index a96b091..8ec7abd 100644 --- a/midi_timer_driver.h +++ b/midi_timer_driver.h @@ -2,12 +2,16 @@ #define MIDI_TIMER_DRIVER_H_ #include "timer_driver.h" +#include "midilib/midi_file.h" struct midi_timer_driver { struct timer_driver timer_driver; + struct midi_file *midi_file; int ticks_per_quarter_note; + int ticks; + uint32_t tempoBPM; }; -int midi_timer_driver_init(struct midi_timer_driver *driver); +int midi_timer_driver_init(struct midi_timer_driver *driver, struct midi_file *midi_file); void midi_timer_driver_deinit(struct midi_timer_driver *driver); int midi_timer_driver_tick(struct midi_timer_driver *driver); diff --git a/pdx.c b/pdx.c index f2b2f33..a6bf4cc 100644 --- a/pdx.c +++ b/pdx.c @@ -1,42 +1,82 @@ -#include +#include // modified by Claude to implement out-of-bounds checks. #include "pdx.h" #include "adpcm.h" -// TODO: check out of bounds conditions +#define PDX_HEADER_SIZE (PDX_NUM_SAMPLES * 8) + int pdx_file_load(struct pdx_file *f, uint8_t *data, int len) { - if(data == 0 || len == 0) return -1; + if(data == NULL || len <= 0) return -1; + + /* The header table must fit entirely within the file. */ + if(len < PDX_HEADER_SIZE) return -1; int total_samples = 0; - for(int i = 0; i < 96; i++) { - int ofs = (data[i * 8] << 24) | (data[i * 8 + 1] << 16) | (data[i * 8 + 2] << 8) | data[i * 8 + 3]; - int l = (data[i * 8 + 4] << 24) | (data[i * 8 + 5] << 16) | (data[i * 8 + 6] << 8) | data[i * 8 + 7]; - if(ofs < len && ofs + l <= len) { + for(int i = 0; i < PDX_NUM_SAMPLES; i++) { + /* Read big-endian offset and length from the header table. + * Cast to unsigned first to avoid sign-extension issues, then + * range-check before using as array indices. */ + unsigned int ofs = ((unsigned int)data[i * 8] << 24) + | ((unsigned int)data[i * 8 + 1] << 16) + | ((unsigned int)data[i * 8 + 2] << 8) + | (unsigned int)data[i * 8 + 3]; + unsigned int l = ((unsigned int)data[i * 8 + 4] << 24) + | ((unsigned int)data[i * 8 + 5] << 16) + | ((unsigned int)data[i * 8 + 6] << 8) + | (unsigned int)data[i * 8 + 7]; + + /* Reject entries whose sample data lies outside the file. + * Both ofs and l are unsigned so ofs + l cannot be negative, + * but it can wrap around — check for that too. */ + if(l > 0 + && ofs < (unsigned int)len + && l <= (unsigned int)len - ofs /* no wrap, fits in file */ + && ofs + l <= (unsigned int)len) /* explicit upper-bound */ + { f->samples[i].data = data + ofs; - if(ofs + l > len) - l = len - ofs; - f->samples[i].len = l; - total_samples += l * 2; + f->samples[i].len = (int)l; + + /* Guard against integer overflow when accumulating the total + * decoded sample count. Each compressed byte expands to 2 + * int16_t samples, so the decoded size is l * 2 * sizeof(int16_t). + * Bail out if any of those multiplications would overflow int. */ + if(l > (unsigned int)(INT32_MAX / 2 - total_samples)) return -1; + total_samples += (int)l * 2; } else { - f->samples[i].data = 0; - f->samples[i].len = 0; + f->samples[i].data = NULL; + f->samples[i].len = 0; + } + } + + /* Nothing to decode — initialise all decoded_data pointers and return. */ + if(total_samples == 0) { + for(int i = 0; i < PDX_NUM_SAMPLES; i++) { + f->samples[i].decoded_data = NULL; + f->samples[i].num_samples = 0; } + return 0; } - int16_t *sample_data = malloc(total_samples * 2); + + int16_t *sample_data = malloc((size_t)total_samples * sizeof(int16_t)); + if(sample_data == NULL) return -1; + int16_t *cur_sample = sample_data; - for(int i = 0; i < 96; i++) { + for(int i = 0; i < PDX_NUM_SAMPLES; i++) { if(f->samples[i].len > 0) { f->samples[i].decoded_data = cur_sample; - f->samples[i].num_samples = f->samples[i].len * 2; + f->samples[i].num_samples = f->samples[i].len * 2; struct adpcm_status st; adpcm_init(&st); for(int j = 0; j < f->samples[i].len; j++) { - uint8_t c = f->samples[i].data[j]; + uint8_t c = f->samples[i].data[j]; int16_t d1 = adpcm_decode(c & 0x0f, &st); - int16_t d2 = adpcm_decode(c >> 4, &st); + int16_t d2 = adpcm_decode(c >> 4, &st); *(cur_sample++) = d1; *(cur_sample++) = d2; } + } else { + f->samples[i].decoded_data = NULL; + f->samples[i].num_samples = 0; } } diff --git a/pdx.h b/pdx.h index 8b8c38a..30371ea 100644 --- a/pdx.h +++ b/pdx.h @@ -9,12 +9,12 @@ struct pdx_sample { uint8_t *data; int len; int16_t *decoded_data; - int num_samples; + int num_samples; // refers to https://en.wikipedia.org/wiki/Sample_(signal) }; struct pdx_file { struct pdx_sample samples[PDX_NUM_SAMPLES]; - int num_samples; + int num_samples; // refers to https://en.wikipedia.org/wiki/Sampling_(music) }; int pdx_file_load(struct pdx_file *pdx, uint8_t *data, int data_len); diff --git a/pdx2sf2.c b/pdx2sf2.c deleted file mode 100644 index 834837d..0000000 --- a/pdx2sf2.c +++ /dev/null @@ -1,162 +0,0 @@ -#include - -#include "pdx.h" -#include "adpcm.h" -#include "tools.h" -#include "Soundfont.h" - -int main(int argc, char **argv) { - printf("ass %d\n", argc); - for(int i = 1; i < argc; i++) { - printf("balls %s\n", argv[i]); - size_t data_len = 0; - uint8_t *data = load_file(argv[1], &data_len); - if(!data) { - fprintf(stderr, "Could not load %s\n", argv[1]); - return 1; - } - - struct pdx_file p; - pdx_file_load(&p, data, data_len); - - SF2_DATA* s = CreateSF2Base(argv[i]); - int totalSamplesSize = 0; - int totalSamples = 0; - for(int j = 0; j < PDX_NUM_SAMPLES; j++) { - if(p.samples[j].len > 0) { - totalSamplesSize += p.samples[j].num_samples + 46; - totalSamples++; - } - } - - int16_t *samples = (int16_t *)malloc(totalSamplesSize * 2); - memset(samples, 0, totalSamplesSize * 2); - int curPos = 0; - sfSample *sampleHeaders = (sfSample *)malloc((totalSamples + 1) * sizeof(sfSample)); - int x = 0; - for(int j = 0; j < PDX_NUM_SAMPLES; j++) { - if(p.samples[j].len > 0) { - memcpy(samples + curPos, p.samples[j].decoded_data, p.samples[j].num_samples * sizeof(int16_t)); - - sfSample *h = sampleHeaders + x; - h->dwSampleRate = 15600; - h->byOriginalKey = 60; - h->chCorrection = 0; - h->wSampleLink = 0; - h->sfSampleType = monoSample; - snprintf(h->achSampleName, sizeof(h->achSampleName), "Sample %03X", x); - h->dwStart = curPos; - h->dwEnd = curPos + p.samples[j].num_samples + 46; - h->dwStartloop = curPos; - h->dwEndloop = h->dwEnd; - - curPos += p.samples[j].num_samples + 46; - x++; - } - } - sfSample *h = sampleHeaders + x; - memset(h, 0, sizeof(sfSample)); - strcpy(h->achSampleName, "EOS"); // write "End Of Samples" header - - LIST_CHUNK *LstChk = List_GetChunk(s->Lists, FCC_sdta); - ITEM_CHUNK *ItmChk = Item_MakeChunk(FCC_smpl, totalSamplesSize * 2, samples, 0x00); - List_AddItem(LstChk, ItmChk); - - LstChk = List_GetChunk(s->Lists, FCC_pdta); - ItmChk = Item_MakeChunk(FCC_shdr, (totalSamples + 1) * sizeof(sfSample), sampleHeaders, 0x00); // no free() needed either - List_AddItem(LstChk, ItmChk); - - sfInst inst[2]; - memset(inst, 0, sizeof(inst)); - snprintf(inst[0].achInstName, sizeof(inst[0].achInstName), "Instrument %d", 1); - inst[0].wInstBagNdx = 0; - snprintf(inst[1].achInstName, sizeof(inst[1].achInstName), "EOI"); - inst[1].wInstBagNdx = totalSamples; - ItmChk = Item_MakeChunk(FCC_inst, sizeof(inst), &inst, 0); - List_AddItem(LstChk, ItmChk); - int bagSize = (totalSamples + 1) * sizeof(sfInstBag); - sfInstBag *bags = (sfInstBag *)malloc(bagSize); -#define GENS_PER_ZONE 3 -#define BASE_NOTE 36 - for(int j = 0; j <= totalSamples; j++) { - bags[j].wInstGenNdx = j * GENS_PER_ZONE; - bags[j].wInstModNdx = 0; - } - ItmChk = Item_MakeChunk(FCC_ibag, bagSize, bags, 0); - List_AddItem(LstChk, ItmChk); - - int genSize = ((totalSamples) * GENS_PER_ZONE + 1) * sizeof(sfInstGenList); - sfInstGenList *instGenLists = (sfInstGenList *)malloc(genSize); - memset(instGenLists, 0, genSize); - x = 0; - for(int j = 0; j < PDX_NUM_SAMPLES; j++) { - if(p.samples[j].num_samples > 0) { - int k = x * GENS_PER_ZONE; - instGenLists[k].sfGenOper = keyRange; - instGenLists[k].genAmount.ranges.byHi = BASE_NOTE + j; - instGenLists[k].genAmount.ranges.byLo = BASE_NOTE + j; - k++; - instGenLists[k].sfGenOper = overridingRootKey; - instGenLists[k].genAmount.wAmount = BASE_NOTE + j; - k++; - instGenLists[k].sfGenOper = sampleID; - instGenLists[k].genAmount.wAmount = x; - x++; - } - } - ItmChk = Item_MakeChunk(FCC_igen, genSize, instGenLists, 0); - List_AddItem(LstChk, ItmChk); - - sfModList modList; - memset(&modList, 0, sizeof(modList)); - ItmChk = Item_MakeChunk(FCC_imod, sizeof(modList), &modList, 0); - List_AddItem(LstChk, ItmChk); - - sfPresetHeader presetHeaders[2]; - snprintf(presetHeaders[0].achPresetName, sizeof(presetHeaders[0].achPresetName), "Preset 1"); - presetHeaders[0].wPreset = 0; - presetHeaders[0].wBank = 0; - presetHeaders[0].wPresetBagNdx = 0; - presetHeaders[0].dwLibrary = 0; - presetHeaders[0].dwGenre = 0; - presetHeaders[0].dwMorphology = 0; - memset(presetHeaders + 1, 0, sizeof(sfPresetHeader)); - snprintf(presetHeaders[1].achPresetName, sizeof(presetHeaders[1].achPresetName), "EOP"); - presetHeaders[i].wPreset = 0xff; - presetHeaders[i].wBank = 0xff; - presetHeaders[i].wPresetBagNdx = 1; - ItmChk = Item_MakeChunk(FCC_phdr, sizeof(presetHeaders), presetHeaders, 0); - List_AddItem(LstChk, ItmChk); - - sfPresetBag presetBags[2]; - presetBags[0].wGenNdx = 0; - presetBags[0].wModNdx = 0; - presetBags[1].wGenNdx = 1; - presetBags[1].wModNdx = 0; - ItmChk = Item_MakeChunk(FCC_pbag, sizeof(presetBags), presetBags, 0); - List_AddItem(LstChk, ItmChk); - - sfGenList presetGenOpers[2]; - presetGenOpers[0].sfGenOper = instrument; - presetGenOpers[0].genAmount.wAmount = 0; - presetGenOpers[1].sfGenOper = 0; - presetGenOpers[1].genAmount.wAmount = 0; - ItmChk = Item_MakeChunk(FCC_pgen, sizeof(presetGenOpers), presetGenOpers, 0); - List_AddItem(LstChk, ItmChk); - - sfModList presetModList; - memset(&presetModList, 0, sizeof(sfModList)); - ItmChk = Item_MakeChunk(FCC_pmod, sizeof(presetModList), &presetModList, 0); - List_AddItem(LstChk, ItmChk); - - char buf[256]; - replace_ext(buf, sizeof(buf), argv[i], "sf2"); - printf("Writing to file %s\n", buf); - WriteSF2toFile(s, buf); - - free(samples); - free(sampleHeaders); - free(bags); - free(instGenLists); - } -} diff --git a/pdx2sf2.cpp b/pdx2sf2.cpp new file mode 100644 index 0000000..ac7fceb --- /dev/null +++ b/pdx2sf2.cpp @@ -0,0 +1,132 @@ +#include + +extern "C" { + #include "pdx.h" + #include "adpcm.h" + #include "tools.h" +} + +#include +#include +#include +#include +#include +#include + +#include "sf2cute/include/sf2cute.hpp" + +using namespace sf2cute; + +int main(int argc, char **argv) { // TEST more PDX files to ensure no samples are so loud they clip. + if (argc < 2) + printf("Usage: pdx2sf2 your_pdx_file.pdx\n"); + for(int i = 1; i < argc; i++) { + printf("argv[%d]: %s\n", i, argv[i]); + size_t data_len = 0; + uint8_t *data = load_file(argv[i], &data_len); + if(!data) { + fprintf(stderr, "Could not load %s\n", argv[i]); + return 1; + } + + struct pdx_file p; + { + int result = pdx_file_load(&p, data, data_len); + if (result == 0) + printf("Successfully loaded PDX file %s.\n", argv[i]); + else + printf("Failed to load PDX file %s.\n", argv[i]); + } + + SoundFont sf2; + sf2.set_sound_engine("EMU8000"); // ? + int totalSamplesSize = 0; + int totalSamples = 0; + for(int j = 0; j < PDX_NUM_SAMPLES; j++) { + if(p.samples[j].len > 0) { + totalSamplesSize += p.samples[j].num_samples + 46; + totalSamples++; + } + } + + int curPos = 0; + int x = 0; + // make new instrument here. + std::shared_ptr instrument_1 = sf2.NewInstrument("Instrument 1"); + +#define GENS_PER_ZONE 3 +#define BASE_NOTE 36 + x = 0; + int16_t largest_sample = -0x8000; + int16_t smallest_sample = 0x7FFF; + for(int j = 0; j < PDX_NUM_SAMPLES; j++) { + + std::vector converted_sample_data(p.samples[j].decoded_data, p.samples[j].decoded_data + p.samples[j].num_samples); + for (int sampleI = 0; sampleI < converted_sample_data.size(); sampleI++) { + // debug + if (converted_sample_data[sampleI] > largest_sample) + largest_sample = converted_sample_data[sampleI]; + if (converted_sample_data[sampleI] < smallest_sample) + smallest_sample = converted_sample_data[sampleI]; + + converted_sample_data[sampleI] = converted_sample_data[sampleI] << 4; // convert from sint12 sample to sint16 sample. + } + + if(p.samples[j].num_samples > 0 && p.samples[j].len > 0) { // Each note in the single instrument plays a different sample. + char sample_name[13]; // Sample 0x060 + snprintf(sample_name, 13, "Sample 0x%03X", j); + std::shared_ptr my_sample_pointer = sf2.NewSample( + std::string(sample_name), // name + converted_sample_data, // sample data + 0, // start loop + uint32_t(p.samples[j].num_samples), // end loop + 15600, // sample rate + 60, // root key + 0, // microtuning + std::weak_ptr(), // pointer to other sample in a left-right stereo sample pair. NULL here because these are all mono samples. + SFSampleLink::kMonoSample); + + SFInstrumentZone instrument_zone(my_sample_pointer); + instrument_zone.SetGenerator(SFGeneratorItem(SFGenerator::kKeyRange, RangesType(BASE_NOTE + j, BASE_NOTE + j))); + instrument_zone.SetGenerator(SFGeneratorItem(SFGenerator::kOverridingRootKey, BASE_NOTE + j)); + // add zone to instrument here. + instrument_1->AddZone(instrument_zone); + } + } + printf("largest_sample: %d, smallest_sample: %d\n", largest_sample, smallest_sample); + + std::shared_ptr preset_50 = sf2.NewPreset( + "Preset 1", 0, 0, + std::vector{ + SFPresetZone(instrument_1) + }); + + char buf[256]; + replace_ext(buf, sizeof(buf), argv[i], "sf2"); + printf("Writing to file %s\n", buf); + try { + std::ofstream ofs(std::string(buf), std::ios::binary); + sf2.Write(ofs); + return 0; + } + catch (const std::fstream::failure & e) { + // File output error. + std::cerr << e.what() << std::endl; + return 1; + } + catch (const std::exception & e) { + // Other errors. + // For example: Too many samples. + std::cerr << e.what() << std::endl; + return 1; + } + + free(data); + for(int j = 0; j < PDX_NUM_SAMPLES; j++) { + if(p.samples[j].decoded_data != NULL) { + free(p.samples[j].decoded_data); + break; // only one allocation; stop after freeing the root pointer + } + } + } +} diff --git a/sf2cute b/sf2cute new file mode 160000 index 0000000..2382536 --- /dev/null +++ b/sf2cute @@ -0,0 +1 @@ +Subproject commit 23825368d2025a51ea60d89edc5066a3ea21febb