commit 783d56c72ae6cb85f9e06809dced8d9b3d1323ff Author: tildearrow Date: Tue May 11 15:08:08 2021 -0500 initial commit took me a day to make the base... ...and ~12 hours to write a reader that reads 100% of all demo songs in 1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..effc3fe8c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.vscode/ +build/ +*.dmf diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..917350118 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,46 @@ +cmake_minimum_required(VERSION 3.0) +project(divorce) + +set(CMAKE_CXX_STANDARD 11) + +if (WIN32) + add_subdirectory(SDL) + set(HAVE_SDL2 SDL2-static) +else() + find_library(HAVE_SDL2 SDL2) + find_library(HAVE_JACK jack) +endif() + +include_directories(include) + +set(AUDIO_SOURCES src/audio/abstract.cpp) +if (HAVE_SDL2) + list(APPEND AUDIO_SOURCES src/audio/sdl.cpp) +endif() +if (HAVE_JACK) + list(APPEND AUDIO_SOURCES src/audio/jack.cpp) +endif() + +set(ENGINE_SOURCES src/log.cpp src/engine/safeReader.cpp src/engine/engine.cpp) + +#imgui/imgui.cpp +#imgui/imgui_demo.cpp +#imgui/imgui_draw.cpp +#imgui/imgui_tables.cpp +#imgui/imgui_widgets.cpp +#imgui/backends/imgui_impl_opengl3.cpp +#imgui/backends/imgui_impl_sdl.cpp +#src/gui/main.cpp) + +add_executable(divorce ${ENGINE_SOURCES} ${AUDIO_SOURCES} +src/main.cpp) + +target_link_libraries(divorce ${HAVE_SDL2} z GL GLEW) + +if (HAVE_JACK) + target_link_libraries(divorce ${HAVE_JACK}) +endif() + +if (WIN32) + target_link_libraries(divorce SDL2main) +endif() diff --git a/README.md b/README.md new file mode 100644 index 000000000..dc2420d29 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# divorce + +that does it. + +this is the result of a horrible decision to make it commercial... diff --git a/src/audio/abstract.cpp b/src/audio/abstract.cpp new file mode 100644 index 000000000..93981801b --- /dev/null +++ b/src/audio/abstract.cpp @@ -0,0 +1,30 @@ +#include "taAudio.h" + +void TAAudio::setSampleRateChangeCallback(void (*callback)(SampleRateChangeEvent)) { + sampleRateChanged=callback; +} + +void TAAudio::setBufferSizeChangeCallback(void (*callback)(BufferSizeChangeEvent)) { + bufferSizeChanged=callback; +} + +void TAAudio::setCallback(void (*callback)(float**,float**,int,int,unsigned int)) { + audioProcCallback=callback; +} + +void* TAAudio::getContext() { + return NULL; +} + +bool TAAudio::quit() { + return true; +} + +bool TAAudio::setRun(bool run) { + running=run; + return running; +} + +bool TAAudio::init(TAAudioDesc& request, TAAudioDesc& response) { + return false; +} \ No newline at end of file diff --git a/src/audio/jack.cpp b/src/audio/jack.cpp new file mode 100644 index 000000000..52bdd625e --- /dev/null +++ b/src/audio/jack.cpp @@ -0,0 +1,156 @@ +#include +#include "jack.h" + +int taJACKonSampleRate(jack_nframes_t rate, void* inst) { + TAAudioJACK* in=(TAAudioJACK*)inst; + in->onSampleRate(rate); + return 0; +} + +int taJACKonBufferSize(jack_nframes_t bufsize, void* inst) { + TAAudioJACK* in=(TAAudioJACK*)inst; + in->onBufferSize(bufsize); + return 0; +} + +int taJACKProcess(jack_nframes_t nframes, void* inst) { + TAAudioJACK* in=(TAAudioJACK*)inst; + in->onProcess(nframes); + return 0; +} + +void TAAudioJACK::onSampleRate(jack_nframes_t rate) { + if (sampleRateChanged!=NULL) { + sampleRateChanged(SampleRateChangeEvent(rate)); + } +} + +void TAAudioJACK::onBufferSize(jack_nframes_t bufsize) { + if (bufferSizeChanged!=NULL) { + bufferSizeChanged(BufferSizeChangeEvent(bufsize)); + } +} + +void TAAudioJACK::onProcess(jack_nframes_t nframes) { + if (audioProcCallback!=NULL) { + audioProcCallback(inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize); + } + for (int i=0; i0) { + inBufs=new float*[desc.inChans]; + iInBufs=new float*[desc.inChans]; + ai=new jack_port_t*[desc.inChans]; + for (int i=0; i0) { + outBufs=new float*[desc.outChans]; + iOutBufs=new float*[desc.outChans]; + ao=new jack_port_t*[desc.outChans]; + for (int i=0; i + +class TAAudioJACK: public TAAudio { + jack_client_t* ac; + jack_port_t** ai; + jack_port_t** ao; + + float** iInBufs; + float** iOutBufs; + + public: + void onSampleRate(jack_nframes_t rate); + void onBufferSize(jack_nframes_t bufsize); + void onProcess(jack_nframes_t nframes); + + void* getContext(); + bool quit(); + bool setRun(bool run); + bool init(TAAudioDesc& request, TAAudioDesc& response); + + TAAudioJACK(): + ac(NULL), + ai(NULL), + ao(NULL) {} +}; \ No newline at end of file diff --git a/src/audio/sdl.cpp b/src/audio/sdl.cpp new file mode 100644 index 000000000..cd9045588 --- /dev/null +++ b/src/audio/sdl.cpp @@ -0,0 +1,86 @@ +#include +#include "sdl.h" + +void taSDLProcess(void* inst, unsigned char* buf, int nframes) { + TAAudioSDL* in=(TAAudioSDL*)inst; + in->onProcess(buf,nframes); +} + +void TAAudioSDL::onProcess(unsigned char* buf, int nframes) { + if (audioProcCallback!=NULL) { + audioProcCallback(inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize); + } + float* fbuf=(float*)buf; + for (size_t i=0; i0) { + outBufs=new float*[desc.outChans]; + for (int i=0; i + +class TAAudioSDL: public TAAudio { + SDL_AudioSpec ac, ar; + SDL_AudioDeviceID ai; + + float** iInBufs; + float** iOutBufs; + + public: + void onProcess(unsigned char* buf, int nframes); + + void* getContext(); + bool quit(); + bool setRun(bool run); + bool init(TAAudioDesc& request, TAAudioDesc& response); +}; diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h new file mode 100644 index 000000000..0801932cd --- /dev/null +++ b/src/audio/taAudio.h @@ -0,0 +1,79 @@ +#ifndef _TAAUDIO_H +#define _TAAUDIO_H +#include "../ta-utils.h" + +struct SampleRateChangeEvent { + double rate; + SampleRateChangeEvent(double r): + rate(r) {} +}; + +struct BufferSizeChangeEvent { + unsigned int bufsize; + BufferSizeChangeEvent(unsigned int bs): + bufsize(bs) {} +}; + +enum TAAudioFormat { + TA_AUDIO_FORMAT_F32=0, + TA_AUDIO_FORMAT_F64, + TA_AUDIO_FORMAT_U8, + TA_AUDIO_FORMAT_S8, + TA_AUDIO_FORMAT_U16, + TA_AUDIO_FORMAT_S16, + TA_AUDIO_FORMAT_U32, + TA_AUDIO_FORMAT_S32, + TA_AUDIO_FORMAT_U16BE, + TA_AUDIO_FORMAT_S16BE, + TA_AUDIO_FORMAT_U32BE, + TA_AUDIO_FORMAT_S32BE +}; + +struct TAAudioDesc { + String name; + double rate; + unsigned int bufsize, fragments; + unsigned char inChans, outChans; + TAAudioFormat outFormat; + + TAAudioDesc(): + rate(0.0), + bufsize(0), + fragments(0), + inChans(0), + outChans(0), + outFormat(TA_AUDIO_FORMAT_F32) {} +}; + +class TAAudio { + protected: + TAAudioDesc desc; + TAAudioFormat outFormat; + bool running, initialized; + float** inBufs; + float** outBufs; + void (*audioProcCallback)(float**,float**,int,int,unsigned int); + void (*sampleRateChanged)(SampleRateChangeEvent); + void (*bufferSizeChanged)(BufferSizeChangeEvent); + public: + void setSampleRateChangeCallback(void (*callback)(SampleRateChangeEvent)); + void setBufferSizeChangeCallback(void (*callback)(BufferSizeChangeEvent)); + + void setCallback(void (*callback)(float**,float**,int,int,unsigned int)); + + virtual void* getContext(); + virtual bool quit(); + virtual bool setRun(bool run); + virtual bool init(TAAudioDesc& request, TAAudioDesc& response); + + TAAudio(): + outFormat(TA_AUDIO_FORMAT_F32), + running(false), + initialized(false), + inBufs(NULL), + outBufs(NULL), + audioProcCallback(NULL), + sampleRateChanged(NULL), + bufferSizeChanged(NULL) {} +}; +#endif diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h new file mode 100644 index 000000000..507d7fc7e --- /dev/null +++ b/src/engine/dispatch.h @@ -0,0 +1,31 @@ +enum DivDispatchCmds { + DIV_CMD_NOTE_ON=0, + DIV_CMD_NOTE_OFF, + DIV_CMD_INSTRUMENT, + DIV_CMD_VOLUME, + DIV_CMD_PITCH_UP, + DIV_CMD_PITCH_DOWN, + DIV_CMD_PITCH_TO +}; + +struct DivCommand { + DivDispatchCmds cmd; +}; + +struct DivDelayedCommand { + int ticks; + DivCommand cmd; +}; + +class DivDispatch { + public: + virtual void acquire(float& l, float& r); + virtual int dispatch(DivCommand c); + + /** + * initialize this DivDispatch. + * @param channels the number of channels to acquire. + * @return the number of channels allocated. + */ + virtual int init(int channels); +}; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp new file mode 100644 index 000000000..bb20de5bc --- /dev/null +++ b/src/engine/engine.cpp @@ -0,0 +1,630 @@ +#include "engine.h" +#include "safeReader.h" +#include "../ta-log.h" +#include "../audio/sdl.h" +#include + +void process(float** in, float** out, int inChans, int outChans, unsigned int size) { + for (int i=0; i blocks; + while (true) { + InflateBlock* ib=new InflateBlock(DIV_READ_SIZE); + zl.next_out=ib->buf; + zl.avail_out=ib->len; + + nextErr=inflate(&zl,Z_SYNC_FLUSH); + if (nextErr!=Z_OK && nextErr!=Z_STREAM_END) { + if (zl.msg==NULL) { + logE("zlib error: unknown error! %d\n",nextErr); + } else { + logE("zlib inflate: %s\n",zl.msg); + } + for (InflateBlock* i: blocks) delete i; + blocks.clear(); + delete ib; + return false; + } + ib->blockSize=ib->len-zl.avail_out; + blocks.push_back(ib); + if (nextErr==Z_STREAM_END) { + break; + } + } + nextErr=inflateEnd(&zl); + if (nextErr!=Z_OK) { + if (zl.msg==NULL) { + logE("zlib end error: unknown error! %d\n",nextErr); + } else { + logE("zlib end: %s\n",zl.msg); + } + for (InflateBlock* i: blocks) delete i; + blocks.clear(); + return false; + } + + size_t finalSize=0; + size_t curSeek=0; + for (InflateBlock* i: blocks) { + finalSize+=i->blockSize; + } + if (finalSize<1) { + logE("compressed too small!\n"); + for (InflateBlock* i: blocks) delete i; + blocks.clear(); + return false; + } + file=new unsigned char[finalSize]; + for (InflateBlock* i: blocks) { + memcpy(&file[curSeek],i->buf,i->blockSize); + curSeek+=i->blockSize; + delete i; + } + blocks.clear(); + len=finalSize; + } else { + logD("loading as uncompressed\n"); + file=(unsigned char*)f; + len=slen; + } + if (memcmp(file,DIV_DMF_MAGIC,16)!=0) { + logE("not a valid module!\n"); + return false; + } + SafeReader reader=SafeReader(file,len); + try { + DivSong ds; + + if (!reader.seek(16,SEEK_SET)) { + logE("premature end of file!"); + return false; + } + ds.version=reader.readC(); + logI("module version %d (0x%.2x)\n",ds.version,ds.version); + char sys=0; + if (ds.version<0x09) { + // V E R S I O N -> 3 <- + // AWESOME + ds.system=DIV_SYSTEM_YMU759; + } else { + sys=reader.readC(); + ds.system=systemFromFile(sys); + } + if (ds.system==DIV_SYSTEM_NULL) { + logE("invalid system 0x%.2x!",sys); + return false; + } + + if (ds.system==DIV_SYSTEM_YMU759 && ds.version<0x10) { // TODO + ds.vendor=reader.readString((unsigned char)reader.readC()); + ds.carrier=reader.readString((unsigned char)reader.readC()); + ds.category=reader.readString((unsigned char)reader.readC()); + ds.name=reader.readString((unsigned char)reader.readC()); + ds.author=reader.readString((unsigned char)reader.readC()); + ds.writer=reader.readString((unsigned char)reader.readC()); + ds.composer=reader.readString((unsigned char)reader.readC()); + ds.arranger=reader.readString((unsigned char)reader.readC()); + ds.copyright=reader.readString((unsigned char)reader.readC()); + ds.manGroup=reader.readString((unsigned char)reader.readC()); + ds.manInfo=reader.readString((unsigned char)reader.readC()); + ds.createdDate=reader.readString((unsigned char)reader.readC()); + ds.revisionDate=reader.readString((unsigned char)reader.readC()); + logI("%s by %s\n",ds.name.c_str(),ds.author.c_str()); + logI("has YMU-specific data:\n"); + logI("- carrier: %s\n",ds.carrier.c_str()); + logI("- category: %s\n",ds.category.c_str()); + logI("- vendor: %s\n",ds.vendor.c_str()); + logI("- writer: %s\n",ds.writer.c_str()); + logI("- composer: %s\n",ds.composer.c_str()); + logI("- arranger: %s\n",ds.arranger.c_str()); + logI("- copyright: %s\n",ds.copyright.c_str()); + logI("- management group: %s\n",ds.manGroup.c_str()); + logI("- management info: %s\n",ds.manInfo.c_str()); + logI("- created on: %s\n",ds.createdDate.c_str()); + logI("- revision date: %s\n",ds.revisionDate.c_str()); + } else { + ds.name=reader.readString((unsigned char)reader.readC()); + ds.author=reader.readString((unsigned char)reader.readC()); + logI("%s by %s\n",ds.name.c_str(),ds.author.c_str()); + } + + logI("reading module data...\n"); + if (ds.version>0x0c) { + ds.hilightA=reader.readC(); + ds.hilightB=reader.readC(); + } + + ds.timeBase=reader.readC(); + ds.speed1=reader.readC(); + if (ds.version>0x03) { + ds.speed2=reader.readC(); + ds.pal=reader.readC(); + ds.customTempo=reader.readC(); + } else { + ds.speed2=ds.speed1; + } + if (ds.version>0x0a) { + String hz=reader.readString(3); + if (ds.customTempo) { + ds.hz=std::stoi(hz); + } + } + // TODO + if (ds.version>0x17) { + ds.patLen=reader.readI(); + } else { + ds.patLen=(unsigned char)reader.readC(); + } + ds.ordersLen=(unsigned char)reader.readC(); + + if (ds.version<20 && ds.version>3) { + ds.arpLen=reader.readC(); + } else { + ds.arpLen=1; + } + + logI("reading pattern matrix (%d)...\n",ds.ordersLen); + for (int i=0; ids.ordersLen) { + logW("pattern %d exceeds order count %d!\n",ds.orders.ord[i][j],ds.ordersLen); + } + } + } + + if (ds.version>0x03) { + ds.insLen=reader.readC(); + } else { + ds.insLen=16; + } + logI("reading instruments (%d)...\n",ds.insLen); + for (int i=0; i0x03) { + ins->name=reader.readString((unsigned char)reader.readC()); + } + logD("%d name: %s\n",i,ins->name.c_str()); + if (ds.version<0x0b) { + // instruments in ancient versions were all FM or STD. + ins->mode=1; + } else { + ins->mode=reader.readC(); + } + + if (ins->mode) { // FM + if (ds.system!=DIV_SYSTEM_GENESIS && + ds.system!=DIV_SYSTEM_GENESIS_EXT && + ds.system!=DIV_SYSTEM_ARCADE && + ds.system!=DIV_SYSTEM_YM2610 && + ds.system!=DIV_SYSTEM_YM2610_EXT && + ds.system!=DIV_SYSTEM_YMU759) { + logE("FM instrument in non-FM system. oopsie?\n"); + return false; + } + ins->fm.alg=reader.readC(); + if (ds.version<0x13) { + reader.readC(); + } + ins->fm.fb=reader.readC(); + if (ds.version<0x13) { + reader.readC(); + } + ins->fm.fms=reader.readC(); + if (ds.version<0x13) { + reader.readC(); + ins->fm.ops=2+reader.readC()*2; + if (ds.system!=DIV_SYSTEM_YMU759) ins->fm.ops=4; + } else { + ins->fm.ops=4; + } + if (ins->fm.ops!=2 && ins->fm.ops!=4) { + logE("invalid op count %d. did we read it wrong?\n",ins->fm.ops); + return false; + } + ins->fm.ams=reader.readC(); + + for (int j=0; jfm.ops; j++) { + ins->fm.op[j].am=reader.readC(); + ins->fm.op[j].ar=reader.readC(); + if (ds.version<0x13) { + ins->fm.op[j].dam=reader.readC(); + } + ins->fm.op[j].dr=reader.readC(); + if (ds.version<0x13) { + ins->fm.op[j].dvb=reader.readC(); + ins->fm.op[j].egt=reader.readC(); + ins->fm.op[j].ksl=reader.readC(); + if (ds.version<0x11) { // TODO: don't know when did this change + ins->fm.op[j].ksr=reader.readC(); + } + } + ins->fm.op[j].mult=reader.readC(); + ins->fm.op[j].rr=reader.readC(); + ins->fm.op[j].sl=reader.readC(); + if (ds.version<0x13) { + ins->fm.op[j].sus=reader.readC(); + } + ins->fm.op[j].tl=reader.readC(); + if (ds.version<0x13) { + ins->fm.op[j].vib=reader.readC(); + ins->fm.op[j].ws=reader.readC(); + } else { + ins->fm.op[j].dt2=reader.readC(); + } + if (ds.version>0x03) { + ins->fm.op[j].rs=reader.readC(); + ins->fm.op[j].dt=reader.readC(); + ins->fm.op[j].d2r=reader.readC(); + ins->fm.op[j].ssgEnv=reader.readC(); + } + + logD("OP%d: AM %d AR %d DAM %d DR %d DVB %d EGT %d KSL %d MULT %d RR %d SL %d SUS %d TL %d VIB %d WS %d RS %d DT %d D2R %d SSG-EG %d\n",j, + ins->fm.op[j].am, + ins->fm.op[j].ar, + ins->fm.op[j].dam, + ins->fm.op[j].dr, + ins->fm.op[j].dvb, + ins->fm.op[j].egt, + ins->fm.op[j].ksl, + ins->fm.op[j].mult, + ins->fm.op[j].rr, + ins->fm.op[j].sl, + ins->fm.op[j].sus, + ins->fm.op[j].tl, + ins->fm.op[j].vib, + ins->fm.op[j].ws, + ins->fm.op[j].rs, + ins->fm.op[j].dt, + ins->fm.op[j].d2r, + ins->fm.op[j].ssgEnv + ); + } + } else { // STD + if (ds.system!=DIV_SYSTEM_GB || ds.version<0x12) { + ins->std.volMacroLen=reader.readC(); + for (int j=0; jstd.volMacroLen; j++) { + if (ds.version<0x0e) { + ins->std.volMacro[j]=reader.readC(); + } else { + ins->std.volMacro[j]=reader.readI(); + } + } + if (ins->std.volMacroLen>0) { + ins->std.volMacroLoop=reader.readC(); + } + } + + ins->std.arpMacroLen=reader.readC(); + for (int j=0; jstd.arpMacroLen; j++) { + if (ds.version<0x0e) { + ins->std.arpMacro[j]=reader.readC(); + } else { + ins->std.arpMacro[j]=reader.readI(); + } + } + if (ins->std.arpMacroLen>0) { + ins->std.arpMacroLoop=reader.readC(); + } + if (ds.version>0x0f) { // TODO + ins->std.arpMacroMode=reader.readC(); + } + + ins->std.dutyMacroLen=reader.readC(); + for (int j=0; jstd.dutyMacroLen; j++) { + if (ds.version<0x0e) { + ins->std.dutyMacro[j]=reader.readC(); + } else { + ins->std.dutyMacro[j]=reader.readI(); + } + } + if (ins->std.dutyMacroLen>0) { + ins->std.dutyMacroLoop=reader.readC(); + } + + ins->std.waveMacroLen=reader.readC(); + for (int j=0; jstd.waveMacroLen; j++) { + if (ds.version<0x0e) { + ins->std.waveMacro[j]=reader.readC(); + } else { + ins->std.waveMacro[j]=reader.readI(); + } + } + if (ins->std.waveMacroLen>0) { + ins->std.waveMacroLoop=reader.readC(); + } + + if (ds.system==DIV_SYSTEM_C64_6581 || ds.system==DIV_SYSTEM_C64_8580) { + ins->c64.triOn=reader.readC(); + ins->c64.sawOn=reader.readC(); + ins->c64.pulseOn=reader.readC(); + ins->c64.noiseOn=reader.readC(); + + ins->c64.a=reader.readC(); + ins->c64.d=reader.readC(); + ins->c64.s=reader.readC(); + ins->c64.r=reader.readC(); + + ins->c64.duty=reader.readC(); + + ins->c64.ringMod=reader.readC(); + ins->c64.oscSync=reader.readC(); + ins->c64.toFilter=reader.readC(); + if (ds.version<0x11) { + ins->c64.volIsCutoff=reader.readI(); + } else { + ins->c64.volIsCutoff=reader.readC(); + } + ins->c64.initFilter=reader.readC(); + + ins->c64.res=reader.readC(); + ins->c64.cut=reader.readC(); + ins->c64.hp=reader.readC(); + ins->c64.lp=reader.readC(); + ins->c64.bp=reader.readC(); + ins->c64.ch3off=reader.readC(); + } + + if (ds.system==DIV_SYSTEM_GB && ds.version>0x11) { + ins->gb.envVol=reader.readC(); + ins->gb.envDir=reader.readC(); + ins->gb.envLen=reader.readC(); + ins->gb.soundLen=reader.readC(); + } + } + + ds.ins.push_back(ins); + } + + if (ds.version>0x0b) { + ds.waveLen=(unsigned char)reader.readC(); + logI("reading wavetables (%d)...\n",ds.waveLen); + for (int i=0; ilen=(unsigned char)reader.readI(); + if (wave->len>32) { + logE("invalid wave length %d. are we doing something wrong?\n",wave->len); + return false; + } + logD("%d length %d\n",i,wave->len); + for (int j=0; jlen; j++) { + if (ds.version<0x0e) { + wave->data[j]=reader.readC(); + } else { + wave->data[j]=reader.readI(); + } + } + ds.wave.push_back(wave); + } + } + + logI("reading patterns (%d channels, %d orders)...\n",getChannelCount(ds.system),ds.ordersLen); + for (int i=0; ieffectRows=1; + } else { + chan->effectRows=reader.readC(); + } + logD("%d fx rows: %d\n",i,chan->effectRows); + if (chan->effectRows>4 || chan->effectRows<1) { + logE("invalid effect row count %d. are you sure everything is ok?\n",chan->effectRows); + return false; + } + for (int j=0; jdata[k][0]=reader.readS(); + // octave + pat->data[k][1]=reader.readS(); + // volume + pat->data[k][3]=reader.readS(); + for (int l=0; leffectRows; l++) { + // effect + pat->data[k][4+(l<<1)]=reader.readS(); + pat->data[k][5+(l<<1)]=reader.readS(); + } + // instrument + pat->data[k][2]=reader.readS(); + } + chan->data.push_back(pat); + } + ds.pat.push_back(chan); + } + + ds.sampleLen=reader.readC(); + logI("reading samples (%d)...\n",ds.sampleLen); + if (ds.version<0x0b && ds.sampleLen>0) { // TODO what is this for? + reader.readC(); + } + for (int i=0; ilength=reader.readI(); + if (sample->length<0) { + logE("invalid sample length %d. are we doing something wrong?\n",sample->length); + return false; + } + if (ds.version>0x16) { + sample->name=reader.readString((unsigned char)reader.readC()); + } else { + sample->name=""; + } + logD("%d name %s (%d)\n",i,sample->name.c_str(),sample->length); + if (ds.version<0x0b) { + sample->rate=0; + sample->pitch=0; + sample->vol=0; + } else { + sample->rate=reader.readC(); + sample->pitch=reader.readC(); + sample->vol=reader.readC(); + } + if (ds.version>0x15) { + sample->depth=reader.readC(); + } else { + sample->depth=16; + } + if (sample->length>0) { + if (ds.version<0x0b) { + sample->data=new short[1+(sample->length/2)]; + reader.read(sample->data,sample->length); + sample->length/=2; + } else { + sample->data=new short[sample->length]; + reader.read(sample->data,sample->length*2); + } + } + ds.sample.push_back(sample); + } + + if (reader.tell()setCallback(process); + + logI("initializing audio.\n"); + if (!output->init(want,got)) { + logE("error while initializing audio!\n"); + return false; + } + + if (!output->setRun(true)) { + printf("error while activating!\n"); + return false; + } + return true; +} diff --git a/src/engine/engine.h b/src/engine/engine.h new file mode 100644 index 000000000..23176d343 --- /dev/null +++ b/src/engine/engine.h @@ -0,0 +1,35 @@ +#include "song.h" +#include "dispatch.h" +#include "../audio/taAudio.h" + +struct DivChannelState { + std::vector delayed; + int rampSpeed, portaSpeed, portaNote; + int volSpeed; + int vibratoDepth, vibratoRate; + int tremoloDepth, tremoloRate; +}; + +class DivEngine { + DivSong song; + DivDispatch* dispatch; + TAAudio* output; + TAAudioDesc want, got; + int chans; + bool playing; + bool speedAB; + int ticks, curRow, curOrder; + std::vector chan; + + public: + // load a .dmf. + bool load(void* f, size_t length); + // save as .dmf. + bool save(FILE* f); + + // play + void play(); + + // initialize the engine. + bool init(); +}; diff --git a/src/engine/instrument.h b/src/engine/instrument.h new file mode 100644 index 000000000..d72a90193 --- /dev/null +++ b/src/engine/instrument.h @@ -0,0 +1,48 @@ +enum DivInstrumentType { + DIV_INS_FM, + DIV_INS_STD, + DIV_INS_GB, + DIV_INS_C64 +}; + +struct DivInstrumentFM { + char alg, fb, fms, ams, ops; + struct { + char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; + char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759 + } op[4]; +}; + +struct DivInstrumentSTD { + int volMacro[256]; + int arpMacro[256]; + int dutyMacro[256]; + int waveMacro[256]; + bool arpMacroMode; + unsigned char volMacroLen, arpMacroLen, dutyMacroLen, waveMacroLen; + unsigned char volMacroLoop, arpMacroLoop, dutyMacroLoop, waveMacroLoop; +}; + +struct DivInstrumentGB { + unsigned char envVol, envDir, envLen, soundLen; +}; + +struct DivInstrumentC64 { + bool triOn, sawOn, pulseOn, noiseOn; + unsigned char a, d, s, r; + unsigned char duty; + unsigned char ringMod, oscSync; + bool toFilter, volIsCutoff, initFilter; + unsigned char res, cut; + bool hp, lp, bp, ch3off; +}; + +struct DivInstrument { + String name; + bool mode; + DivInstrumentType type; + DivInstrumentFM fm; + DivInstrumentSTD std; + DivInstrumentGB gb; + DivInstrumentC64 c64; +}; diff --git a/src/engine/orders.h b/src/engine/orders.h new file mode 100644 index 000000000..ba2e85829 --- /dev/null +++ b/src/engine/orders.h @@ -0,0 +1,3 @@ +struct DivOrders { + char ord[32][128]; +}; diff --git a/src/engine/pattern.h b/src/engine/pattern.h new file mode 100644 index 000000000..a9ce417b7 --- /dev/null +++ b/src/engine/pattern.h @@ -0,0 +1,15 @@ +struct DivPattern { + char data[256][16]; +}; + +struct DivChannelData { + char effectRows; + // data goes as follows: data[ROW][TYPE] + // TYPE is: + // 0: note + // 1: octave + // 2: instrument + // 3: volume + // 4-5+: effect/effect value + std::vector data; +}; \ No newline at end of file diff --git a/src/engine/platform/dummy.cpp b/src/engine/platform/dummy.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/src/engine/platform/dummy.h b/src/engine/platform/dummy.h new file mode 100644 index 000000000..e90013bdb --- /dev/null +++ b/src/engine/platform/dummy.h @@ -0,0 +1,10 @@ +#include "../dispatch.h" + +// the dummy platform outputs square waves, interprets STD instruments and plays samples. +// used when a DivDispatch for a system is not found. +class DivPlatformDummy: public DivDispatch { + public: + void acquire(float& l, float& r); + int dispatch(DivCommand c); + int init(int channels); +}; \ No newline at end of file diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp new file mode 100644 index 000000000..474efe0cc --- /dev/null +++ b/src/engine/safeReader.cpp @@ -0,0 +1,143 @@ +#include "safeReader.h" +#include "../ta-log.h" + +//#define READ_DEBUG + +bool SafeReader::seek(ssize_t where, int whence) { + switch (whence) { + case SEEK_SET: + if (where<0) return false; + if (where>(ssize_t)len) return false; + curSeek=where; + break; + case SEEK_CUR: { + ssize_t finalSeek=len+where; + if (finalSeek<0) return false; + if (finalSeek>(ssize_t)len) return false; + curSeek=finalSeek; + break; + } + case SEEK_END: { + ssize_t finalSeek=len-where; + if (finalSeek<0) return false; + if (finalSeek>(ssize_t)len) return false; + curSeek=finalSeek; + break; + } + } + return true; +} + +size_t SafeReader::tell() { + return curSeek; +} + +size_t SafeReader::size() { + return len; +} + +int SafeReader::read(void* where, size_t count) { +#ifdef READ_DEBUG + logD("SR: reading %d bytes at %x\n",count,curSeek); +#endif + if (count==0) return 0; + if (curSeek+count>len) throw EndOfFileException(this,len); + memcpy(where,&buf[curSeek],count); + curSeek+=count; + return count; +} + +char SafeReader::readC() { +#ifdef READ_DEBUG + logD("SR: reading char %x:\n",curSeek); +#endif + if (curSeek+1>len) throw EndOfFileException(this,len); +#ifdef READ_DEBUG + logD("SR: %.2x\n",buf[curSeek]); +#endif + return (signed char)buf[curSeek++]; +} + +short SafeReader::readS() { +#ifdef READ_DEBUG + logD("SR: reading short %x:\n",curSeek); +#endif + if (curSeek+2>len) throw EndOfFileException(this,len); + short ret=*(short*)(&buf[curSeek]); +#ifdef READ_DEBUG + logD("SR: %.4x\n",ret); +#endif + curSeek+=2; + return ret; +} + +short SafeReader::readS_BE() { + if (curSeek+1>len) throw EndOfFileException(this,len); + short ret=*(short*)(&buf[curSeek]); + curSeek+=2; + return (ret>>8)|((ret&0xff)<<8); +} + +int SafeReader::readI() { +#ifdef READ_DEBUG + logD("SR: reading int %x:\n",curSeek); +#endif + if (curSeek+4>len) throw EndOfFileException(this,len); + int ret=*(int*)(&buf[curSeek]); + curSeek+=4; +#ifdef READ_DEBUG + logD("SR: %.8x\n",ret); +#endif + return ret; +} + +int SafeReader::readI_BE() { + if (curSeek+4>len) throw EndOfFileException(this,len); + int ret=*(int*)(&buf[curSeek]); + curSeek+=4; + return (ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24); +} + +int64_t SafeReader::readL() { + if (curSeek+8>len) throw EndOfFileException(this,len); + int64_t ret=*(int64_t*)(&buf[curSeek]); + curSeek+=8; + return ret; +} + +float SafeReader::readF() { + if (curSeek+4>len) throw EndOfFileException(this,len); + float ret=*(float*)(&buf[curSeek]); + curSeek+=4; + return ret; +} + +double SafeReader::readD() { + if (curSeek+8>len) throw EndOfFileException(this,len); + double ret=*(double*)(&buf[curSeek]); + curSeek+=8; + return ret; +} + +String SafeReader::readString(size_t stlen) { + String ret; +#ifdef READ_DEBUG + logD("SR: reading string len %d at %x\n",stlen,curSeek); +#endif + size_t curPos=0; + while (curPos +#include +#include "../ta-utils.h" + +class SafeReader; + +struct EndOfFileException { + SafeReader* reader; + size_t finalSize; + EndOfFileException(SafeReader* r, size_t fs): + reader(r), + finalSize(fs) {} +}; + +class SafeReader { + unsigned char* buf; + size_t len; + + size_t curSeek; + + public: + bool seek(ssize_t where, int whence); + size_t tell(); + size_t size(); + + int read(void* where, size_t count); + + // these functions may throw EndOfFileException. + char readC(); + short readS(); + short readS_BE(); + int readI(); + int readI_BE(); + int64_t readL(); + int64_t readL_BE(); + float readF(); + float readF_BE(); + double readD(); + double readD_BE(); + String readString(); + String readString(size_t len); + + SafeReader(void* b, size_t l): + buf((unsigned char*)b), + len(l), + curSeek(0) {} +}; + +#endif diff --git a/src/engine/sample.h b/src/engine/sample.h new file mode 100644 index 000000000..0be271b2a --- /dev/null +++ b/src/engine/sample.h @@ -0,0 +1,7 @@ +struct DivSample { + String name; + int length, rate; + char vol, pitch, depth; + short* data; + char* data8; +}; diff --git a/src/engine/song.h b/src/engine/song.h new file mode 100644 index 000000000..e94d51711 --- /dev/null +++ b/src/engine/song.h @@ -0,0 +1,96 @@ +#include +#include +#include "../ta-utils.h" +#include "orders.h" +#include "instrument.h" +#include "pattern.h" +#include "wavetable.h" +#include "sample.h" + +enum DivSystem { + DIV_SYSTEM_NULL=0, + DIV_SYSTEM_YMU759, + DIV_SYSTEM_GENESIS, + DIV_SYSTEM_GENESIS_EXT, + DIV_SYSTEM_SMS, + DIV_SYSTEM_GB, + DIV_SYSTEM_PCE, + DIV_SYSTEM_NES, + DIV_SYSTEM_C64_6581, + DIV_SYSTEM_C64_8580, + DIV_SYSTEM_ARCADE, + DIV_SYSTEM_YM2610, + DIV_SYSTEM_YM2610_EXT +}; + +struct DivSong { + // version number used for saving the song. + // divorce will save using the latest possible version, + // but eventually I will and 0x80 to this value to indicate a divorce module + // known version numbers: + // - 24: v0.12/0.13/1.0 + // - current format version + // - changes pattern length from char to int, probably to allow for size 256 + // - 23: ??? + // - + // - 19: v11 + // - introduces Arcade system + // - changes to the FM instrument format due to YMU759 being dropped + // - 18: v10 + // - radically changes STD instrument for Game Boy + // - 17: v9 + // - changes C64 volIsCutoff flag from int to char for unknown reasons + // - 16: v8 (?) + // - introduces C64 system + // - 15: v7 (?) + // - 14: v6 (?) + // - introduces NES system + // - changes macro and wave values from char to int + // - 13: v5.1 + // - introduces PC Engine system in later version (how?) + // - stores highlight in file + // - 12: v5 (?) + // - introduces Game Boy system + // - introduces wavetables + // - 11: ??? + // - introduces Sega Master System + // - custom Hz support + // - instrument type (FM/STD) present + // - prior to this version the instrument type depended on the system + // - 10: ??? + // - introduces multiple effect columns + // - 9: v3.9 + // - introduces Genesis system + // - introduces system number + // - 7: ??? + // - 5: ??? + // - 3: BETA 3 (?) + // - possibly the first version that could save + // - basic format, no system number, 16 instruments, one speed, YMU759-only + // - if somebody manages to find a version 2 or even 1 module, please tell me as it will be worth more than a luxury vehicle + unsigned char version; + + // system + DivSystem system; + + // song information + String name, author; + + // legacy song information + String carrier, composer, vendor, category, writer, arranger, copyright, manGroup, manInfo, createdDate, revisionDate; + + // highlight + unsigned char hilightA, hilightB; + + // module details + unsigned char timeBase, speed1, speed2, arpLen; + bool pal; + bool customTempo; + int hz, patLen, ordersLen, insLen, waveLen, sampleLen; + + DivOrders orders; + std::vector ins; + std::vector pat; + std::vector wave; + std::vector sample; +}; diff --git a/src/engine/wavetable.h b/src/engine/wavetable.h new file mode 100644 index 000000000..64fcd9525 --- /dev/null +++ b/src/engine/wavetable.h @@ -0,0 +1,4 @@ +struct DivWavetable { + int len; + int data[32]; +}; diff --git a/src/log.cpp b/src/log.cpp new file mode 100644 index 000000000..ff0f222ad --- /dev/null +++ b/src/log.cpp @@ -0,0 +1,47 @@ +#include "ta-log.h" + +int logD(const char* format, ...) { + va_list va; + int ret; + if (logLevel +#include +#include "ta-log.h" +#include "engine/engine.h" + +DivEngine e; + +std::mutex m; + +int main(int argc, char** argv) { + if (argc<2) { + logI("usage: %s file\n",argv[0]); + return 1; + } + logI("divorce dev0\n"); + logI("loading module...\n"); + FILE* f=fopen(argv[1],"rb"); + if (f==NULL) { + perror("error"); + return 1; + } + if (fseek(f,0,SEEK_END)<0) { + perror("size error"); + fclose(f); + return 1; + } + ssize_t len=ftell(f); + if (len<1) { + if (len==0) { + printf("that file is empty!\n"); + } else { + perror("tell error"); + } + fclose(f); + return 1; + } + unsigned char* file=new unsigned char[len]; + if (fseek(f,0,SEEK_SET)<0) { + perror("size error"); + fclose(f); + return 1; + } + if (fread(file,1,(size_t)len,f)!=(size_t)len) { + perror("read error"); + fclose(f); + return 1; + } + fclose(f); + if (!e.load((void*)file,(size_t)len)) { + logE("could not open file!\n"); + return 1; + } + if (!e.init()) { + logE("could not initialize engine!\n"); + return 1; + } + logI("loaded! :o\n"); + while (true) { + logI("locking...\n"); + e.play(); + m.lock(); + m.lock(); + } + return 0; +} diff --git a/src/ta-log.h b/src/ta-log.h new file mode 100644 index 000000000..ee5a23b90 --- /dev/null +++ b/src/ta-log.h @@ -0,0 +1,17 @@ +#ifndef _TA_LOG_H +#define _TA_LOG_H +#include +#include + +#define LOGLEVEL_ERROR 0 +#define LOGLEVEL_WARN 1 +#define LOGLEVEL_INFO 2 +#define LOGLEVEL_DEBUG 3 + +#define logLevel 3 + +int logD(const char* format, ...); +int logI(const char* format, ...); +int logW(const char* format, ...); +int logE(const char* format, ...); +#endif diff --git a/src/ta-utils.h b/src/ta-utils.h new file mode 100644 index 000000000..23acd3c5b --- /dev/null +++ b/src/ta-utils.h @@ -0,0 +1,9 @@ +#ifndef _TA_UTILS_H +#define _TA_UTILS_H +#include +#include +#include + +typedef std::string String; + +#endif \ No newline at end of file