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
This commit is contained in:
tildearrow 2021-05-11 15:08:08 -05:00
commit 783d56c72a
26 changed files with 1660 additions and 0 deletions

3
.gitignore vendored Normal file
View file

@ -0,0 +1,3 @@
.vscode/
build/
*.dmf

46
CMakeLists.txt Normal file
View file

@ -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()

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# divorce
that does it.
this is the result of a horrible decision to make it commercial...

30
src/audio/abstract.cpp Normal file
View file

@ -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;
}

156
src/audio/jack.cpp Normal file
View file

@ -0,0 +1,156 @@
#include <string.h>
#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; i<desc.inChans; i++) {
iInBufs[i]=(float*)jack_port_get_buffer(ai[i],nframes);
memcpy(iInBufs[i],inBufs[i],desc.bufsize*sizeof(float));
}
for (int i=0; i<desc.outChans; i++) {
iOutBufs[i]=(float*)jack_port_get_buffer(ao[i],nframes);
memcpy(iOutBufs[i],outBufs[i],desc.bufsize*sizeof(float));
}
}
void* TAAudioJACK::getContext() {
return (void*)ac;
}
bool TAAudioJACK::quit() {
if (!initialized) return false;
if (running) {
running=false;
if (jack_deactivate(ac)) return false;
}
for (int i=0; i<desc.inChans; i++) {
jack_port_unregister(ac,ai[i]);
ai[i]=NULL;
delete[] inBufs[i];
}
for (int i=0; i<desc.outChans; i++) {
jack_port_unregister(ac,ao[i]);
ao[i]=NULL;
delete[] outBufs[i];
}
delete[] iInBufs;
delete[] iOutBufs;
delete[] inBufs;
delete[] outBufs;
delete[] ai;
delete[] ao;
jack_client_close(ac);
ac=NULL;
initialized=false;
return true;
}
bool TAAudioJACK::setRun(bool run) {
if (!initialized) return false;
if (running==run) {
return running;
}
if (run) {
if (jack_activate(ac)) return false;
for (int i=0; i<desc.outChans; i++) {
jack_connect(ac,(desc.name+String(":out")+std::to_string(i)).c_str(),(String("system:playback_")+std::to_string(i+1)).c_str());
}
running=true;
} else {
if (jack_deactivate(ac)) return true;
running=false;
}
return running;
}
bool TAAudioJACK::init(TAAudioDesc& request, TAAudioDesc& response) {
if (initialized) return false;
desc=request;
desc.outFormat=TA_AUDIO_FORMAT_F32;
jack_status_t as;
ac=jack_client_open(desc.name.c_str(),JackNoStartServer,&as);
if (ac==NULL) return false;
desc.name=String(jack_get_client_name(ac));
jack_set_sample_rate_callback(ac,taJACKonSampleRate,this);
jack_set_buffer_size_callback(ac,taJACKonBufferSize,this);
jack_set_process_callback(ac,taJACKProcess,this);
jack_nframes_t count=jack_get_buffer_size(ac);
desc.bufsize=count;
desc.fragments=1;
jack_nframes_t sampleRate=jack_get_sample_rate(ac);
desc.rate=sampleRate;
if (desc.inChans>0) {
inBufs=new float*[desc.inChans];
iInBufs=new float*[desc.inChans];
ai=new jack_port_t*[desc.inChans];
for (int i=0; i<desc.inChans; i++) {
ai[i]=jack_port_register(ac,(String("in")+std::to_string(i)).c_str(),JACK_DEFAULT_AUDIO_TYPE,JackPortIsInput,0);
if (ai[i]==NULL) {
desc.inChans=i;
break;
}
inBufs[i]=new float[count];
}
}
if (desc.outChans>0) {
outBufs=new float*[desc.outChans];
iOutBufs=new float*[desc.outChans];
ao=new jack_port_t*[desc.outChans];
for (int i=0; i<desc.outChans; i++) {
ao[i]=jack_port_register(ac,(String("out")+std::to_string(i)).c_str(),JACK_DEFAULT_AUDIO_TYPE,JackPortIsOutput,0);
if (ao[i]==NULL) {
desc.outChans=i;
break;
}
outBufs[i]=new float[count];
}
}
response=desc;
initialized=true;
return true;
}

26
src/audio/jack.h Normal file
View file

@ -0,0 +1,26 @@
#include "taAudio.h"
#include <jack/jack.h>
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) {}
};

86
src/audio/sdl.cpp Normal file
View file

@ -0,0 +1,86 @@
#include <string.h>
#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; i<desc.outChans; i++) {
int k=0;
for (size_t j=i; j<desc.bufsize*desc.outChans; j+=desc.outChans) {
fbuf[j]=outBufs[i][k++];
}
}
}
void* TAAudioSDL::getContext() {
return (void*)&ac;
}
bool TAAudioSDL::quit() {
if (!initialized) return false;
SDL_CloseAudioDevice(ai);
if (running) {
running=false;
}
for (int i=0; i<desc.outChans; i++) {
delete[] outBufs[i];
}
delete[] outBufs;
initialized=false;
return true;
}
bool TAAudioSDL::setRun(bool run) {
if (!initialized) return false;
SDL_PauseAudioDevice(ai,!run);
running=run;
return running;
}
bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) {
if (initialized) return false;
if (SDL_Init(SDL_INIT_AUDIO)<0) return false;
desc=request;
desc.outFormat=TA_AUDIO_FORMAT_F32;
ac.freq=desc.rate;
ac.format=AUDIO_F32;
ac.channels=desc.outChans;
ac.samples=desc.bufsize;
ac.callback=taSDLProcess;
ac.userdata=this;
ai=SDL_OpenAudioDevice(NULL,0,&ac,&ar,SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (ai==0) return false;
desc.name="";
desc.rate=ar.freq;
desc.inChans=0;
desc.outChans=ar.channels;
desc.bufsize=ar.samples;
desc.fragments=1;
if (desc.outChans>0) {
outBufs=new float*[desc.outChans];
for (int i=0; i<desc.outChans; i++) {
outBufs[i]=new float[desc.bufsize];
}
}
response=desc;
initialized=true;
return true;
}

18
src/audio/sdl.h Normal file
View file

@ -0,0 +1,18 @@
#include "taAudio.h"
#include <SDL2/SDL.h>
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);
};

79
src/audio/taAudio.h Normal file
View file

@ -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

31
src/engine/dispatch.h Normal file
View file

@ -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);
};

630
src/engine/engine.cpp Normal file
View file

@ -0,0 +1,630 @@
#include "engine.h"
#include "safeReader.h"
#include "../ta-log.h"
#include "../audio/sdl.h"
#include <zlib.h>
void process(float** in, float** out, int inChans, int outChans, unsigned int size) {
for (int i=0; i<outChans; i++) {
for (unsigned int j=0; j<size; j++) {
out[i][j]=(float)(j%128)/128.0f;
}
}
}
#define DIV_READ_SIZE 131072
#define DIV_DMF_MAGIC ".DelekDefleMask."
struct InflateBlock {
unsigned char* buf;
size_t len;
size_t blockSize;
InflateBlock(size_t s) {
buf=new unsigned char[s];
len=s;
blockSize=0;
}
~InflateBlock() {
delete[] buf;
len=0;
}
};
DivSystem systemFromFile(unsigned char val) {
switch (val) {
case 0x01:
return DIV_SYSTEM_YMU759;
case 0x02:
return DIV_SYSTEM_GENESIS;
case 0x03:
return DIV_SYSTEM_SMS;
case 0x04:
return DIV_SYSTEM_GB;
case 0x05:
return DIV_SYSTEM_PCE;
case 0x06:
return DIV_SYSTEM_NES;
case 0x07:
return DIV_SYSTEM_C64_8580;
case 0x08:
return DIV_SYSTEM_ARCADE;
case 0x09:
return DIV_SYSTEM_YM2610;
case 0x42:
return DIV_SYSTEM_GENESIS_EXT;
case 0x47:
return DIV_SYSTEM_C64_6581;
case 0x49:
return DIV_SYSTEM_YM2610_EXT;
}
return DIV_SYSTEM_NULL;
}
int getChannelCount(DivSystem sys) {
switch (sys) {
case DIV_SYSTEM_NULL:
return 0;
case DIV_SYSTEM_YMU759:
return 17;
case DIV_SYSTEM_GENESIS:
return 10;
case DIV_SYSTEM_SMS:
case DIV_SYSTEM_GB:
return 4;
case DIV_SYSTEM_PCE:
return 6;
case DIV_SYSTEM_NES:
return 5;
case DIV_SYSTEM_C64_6581:
case DIV_SYSTEM_C64_8580:
return 3;
case DIV_SYSTEM_ARCADE:
case DIV_SYSTEM_GENESIS_EXT:
case DIV_SYSTEM_YM2610:
return 13;
case DIV_SYSTEM_YM2610_EXT:
return 16;
}
return 0;
}
bool DivEngine::load(void* f, size_t slen) {
unsigned char* file;
size_t len;
if (slen<16) {
logE("too small!");
return false;
}
if (memcmp(f,DIV_DMF_MAGIC,16)!=0) {
logD("loading as zlib...\n");
// try zlib
z_stream zl;
memset(&zl,0,sizeof(z_stream));
zl.avail_in=slen;
zl.next_in=(Bytef*)f;
zl.zalloc=NULL;
zl.zfree=NULL;
zl.opaque=NULL;
int nextErr;
nextErr=inflateInit(&zl);
if (nextErr!=Z_OK) {
if (zl.msg==NULL) {
logE("zlib error: unknown! %d\n",nextErr);
} else {
logE("zlib error: %s\n",zl.msg);
}
return false;
}
std::vector<InflateBlock*> 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; i<getChannelCount(ds.system); i++) {
for (int j=0; j<ds.ordersLen; j++) {
ds.orders.ord[i][j]=reader.readC();
if (ds.orders.ord[i][j]>ds.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; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument;
if (ds.version>0x03) {
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; j<ins->fm.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; j<ins->std.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; j<ins->std.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; j<ins->std.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; j<ins->std.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; i<ds.waveLen; i++) {
DivWavetable* wave=new DivWavetable;
wave->len=(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; j<wave->len; 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; i<getChannelCount(ds.system); i++) {
DivChannelData* chan=new DivChannelData;
if (ds.version<0x0a) {
chan->effectRows=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; j<ds.ordersLen; j++) {
DivPattern* pat=new DivPattern;
for (int k=0; k<ds.patLen; k++) {
// note
pat->data[k][0]=reader.readS();
// octave
pat->data[k][1]=reader.readS();
// volume
pat->data[k][3]=reader.readS();
for (int l=0; l<chan->effectRows; 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; i<ds.sampleLen; i++) {
DivSample* sample=new DivSample;
sample->length=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()<reader.size()) {
logW("premature end of song (we are at %x, but size is %x)\n",reader.tell(),reader.size());
}
song=ds;
} catch (EndOfFileException e) {
logE("premature end of file!\n");
return false;
}
return true;
}
void DivEngine::play() {
}
bool DivEngine::init() {
output=new TAAudioSDL;
want.bufsize=1024;
want.fragments=2;
want.inChans=0;
want.outChans=2;
want.outFormat=TA_AUDIO_FORMAT_F32;
want.name="DivAudio";
output->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;
}

35
src/engine/engine.h Normal file
View file

@ -0,0 +1,35 @@
#include "song.h"
#include "dispatch.h"
#include "../audio/taAudio.h"
struct DivChannelState {
std::vector<DivDelayedCommand> 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<DivChannelState> 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();
};

48
src/engine/instrument.h Normal file
View file

@ -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;
};

3
src/engine/orders.h Normal file
View file

@ -0,0 +1,3 @@
struct DivOrders {
char ord[32][128];
};

15
src/engine/pattern.h Normal file
View file

@ -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<DivPattern*> data;
};

View file

View file

@ -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);
};

143
src/engine/safeReader.cpp Normal file
View file

@ -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<stlen) {
char c=readC();
if (c!=0) ret.push_back(c);
curPos++;
}
return ret;
}
String SafeReader::readString() {
String ret;
char c;
while ((c=readC())!=0) {
ret.push_back(c);
}
return ret;
}

51
src/engine/safeReader.h Normal file
View file

@ -0,0 +1,51 @@
#ifndef _SAFEREADER_H
#define _SAFEREADER_H
#include <stdio.h>
#include <stdlib.h>
#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

7
src/engine/sample.h Normal file
View file

@ -0,0 +1,7 @@
struct DivSample {
String name;
int length, rate;
char vol, pitch, depth;
short* data;
char* data8;
};

96
src/engine/song.h Normal file
View file

@ -0,0 +1,96 @@
#include <stdio.h>
#include <vector>
#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<DivInstrument*> ins;
std::vector<DivChannelData*> pat;
std::vector<DivWavetable*> wave;
std::vector<DivSample*> sample;
};

4
src/engine/wavetable.h Normal file
View file

@ -0,0 +1,4 @@
struct DivWavetable {
int len;
int data[32];
};

47
src/log.cpp Normal file
View file

@ -0,0 +1,47 @@
#include "ta-log.h"
int logD(const char* format, ...) {
va_list va;
int ret;
if (logLevel<LOGLEVEL_DEBUG) return 0;
printf("\x1b[1;34m[debug]\x1b[m ");
va_start(va,format);
ret=vprintf(format,va);
va_end(va);
fflush(stdout);
return ret;
}
int logI(const char* format, ...) {
va_list va;
int ret;
if (logLevel<LOGLEVEL_INFO) return 0;
printf("\x1b[1;32m[info]\x1b[m ");
va_start(va,format);
ret=vprintf(format,va);
va_end(va);
return ret;
}
int logW(const char* format, ...) {
va_list va;
int ret;
if (logLevel<LOGLEVEL_WARN) return 0;
printf("\x1b[1;33m[warning]\x1b[m ");
va_start(va,format);
ret=vprintf(format,va);
va_end(va);
return ret;
}
int logE(const char* format, ...) {
va_list va;
int ret;
if (logLevel<LOGLEVEL_ERROR) return 0;
printf("\x1b[1;31m[ERROR]\x1b[m ");
va_start(va,format);
ret=vprintf(format,va);
va_end(va);
return ret;
}

65
src/main.cpp Normal file
View file

@ -0,0 +1,65 @@
#include <stdio.h>
#include <mutex>
#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;
}

17
src/ta-log.h Normal file
View file

@ -0,0 +1,17 @@
#ifndef _TA_LOG_H
#define _TA_LOG_H
#include <stdio.h>
#include <stdarg.h>
#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

9
src/ta-utils.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef _TA_UTILS_H
#define _TA_UTILS_H
#include <stdio.h>
#include <string.h>
#include <string>
typedef std::string String;
#endif