Merge branch 'master' into sysmgrtooltip_syschaninfo
This commit is contained in:
commit
e50b3438f2
834 changed files with 780920 additions and 160607 deletions
|
|
@ -23,6 +23,7 @@
|
|||
#ifdef _WIN32
|
||||
#include <pa_win_wasapi.h>
|
||||
#endif
|
||||
#include <fmt/printf.h>
|
||||
|
||||
int taPAProcess(const void* in, void* out, unsigned long nframes, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags flags, void* inst) {
|
||||
TAAudioPA* instance=(TAAudioPA*)inst;
|
||||
|
|
|
|||
138
src/audio/pipe.cpp
Normal file
138
src/audio/pipe.cpp
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "../ta-log.h"
|
||||
#include "pipe.h"
|
||||
|
||||
void taPipeThread(void* inst) {
|
||||
TAAudioPipe* in=(TAAudioPipe*)inst;
|
||||
in->runThread();
|
||||
}
|
||||
|
||||
void TAAudioPipe::runThread() {
|
||||
while (running) {
|
||||
onProcess((unsigned char*)sbuf,desc.bufsize);
|
||||
}
|
||||
}
|
||||
|
||||
void TAAudioPipe::onProcess(unsigned char* buf, int nframes) {
|
||||
if (audioProcCallback!=NULL) {
|
||||
if (midiIn!=NULL) midiIn->gather();
|
||||
audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize);
|
||||
}
|
||||
short* sb=(short*)buf;
|
||||
|
||||
if (sb==NULL) return;
|
||||
|
||||
for (size_t j=0; j<desc.bufsize; j++) {
|
||||
for (size_t i=0; i<desc.outChans; i++) {
|
||||
if (outBufs[i][j]<-1.0) outBufs[i][j]=-1.0;
|
||||
if (outBufs[i][j]>1.0) outBufs[i][j]=1.0;
|
||||
sb[j*desc.outChans+i]=outBufs[i][j]*32767.0;
|
||||
}
|
||||
}
|
||||
|
||||
fwrite(sb,2,desc.bufsize*desc.outChans,stdout);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void* TAAudioPipe::getContext() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool TAAudioPipe::quit() {
|
||||
if (!initialized) return false;
|
||||
|
||||
if (running) {
|
||||
running=false;
|
||||
if (outThread) {
|
||||
outThread->join();
|
||||
delete outThread;
|
||||
outThread=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<desc.outChans; i++) {
|
||||
delete[] outBufs[i];
|
||||
}
|
||||
|
||||
delete[] outBufs;
|
||||
|
||||
if (sbuf) {
|
||||
delete[] sbuf;
|
||||
sbuf=NULL;
|
||||
}
|
||||
|
||||
initialized=false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TAAudioPipe::setRun(bool run) {
|
||||
if (!initialized) return false;
|
||||
|
||||
if (running!=run) {
|
||||
running=run;
|
||||
if (running) {
|
||||
outThread=new std::thread(taPipeThread,this);
|
||||
} else if (outThread) {
|
||||
outThread->join();
|
||||
delete outThread;
|
||||
outThread=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return running;
|
||||
}
|
||||
|
||||
std::vector<String> TAAudioPipe::listAudioDevices() {
|
||||
std::vector<String> ret;
|
||||
|
||||
ret.push_back("stdout");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool TAAudioPipe::init(TAAudioDesc& request, TAAudioDesc& response) {
|
||||
if (initialized) {
|
||||
logE("audio already initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
desc=request;
|
||||
desc.outFormat=TA_AUDIO_FORMAT_F32;
|
||||
response=desc;
|
||||
|
||||
logV("opening stdout for audio...");
|
||||
|
||||
if (desc.outChans>0) {
|
||||
outBufs=new float*[desc.outChans];
|
||||
for (int i=0; i<desc.outChans; i++) {
|
||||
outBufs[i]=new float[desc.bufsize];
|
||||
}
|
||||
|
||||
sbuf=new short[desc.bufsize*desc.outChans];
|
||||
} else {
|
||||
sbuf=NULL;
|
||||
}
|
||||
|
||||
response=desc;
|
||||
initialized=true;
|
||||
return true;
|
||||
}
|
||||
38
src/audio/pipe.h
Normal file
38
src/audio/pipe.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "taAudio.h"
|
||||
#include <thread>
|
||||
|
||||
class TAAudioPipe: public TAAudio {
|
||||
std::thread* outThread;
|
||||
short* sbuf;
|
||||
|
||||
public:
|
||||
void runThread();
|
||||
void onProcess(unsigned char* buf, int nframes);
|
||||
|
||||
void* getContext();
|
||||
bool quit();
|
||||
bool setRun(bool run);
|
||||
std::vector<String> listAudioDevices();
|
||||
bool init(TAAudioDesc& request, TAAudioDesc& response);
|
||||
TAAudioPipe():
|
||||
sbuf(NULL) {}
|
||||
};
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include <dirent.h>
|
||||
|
||||
int main(int, char**) {
|
||||
int main(int argc, char** argv) {
|
||||
struct dirent deTest = { };
|
||||
unsigned char deType = deTest.d_type;
|
||||
return 0;
|
||||
|
|
|
|||
7
src/check/check_setlocale.c
Normal file
7
src/check/check_setlocale.c
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#include <locale.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
setlocale(LC_CTYPE,"");
|
||||
//setlocale(LC_MESSAGES,"");
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
#include <sys/io.h>
|
||||
|
||||
int main(int, char**) {
|
||||
int main(int argc, char** argv) {
|
||||
inb(0x61);
|
||||
outb(0x00,0x61);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,11 +28,30 @@ static void handleTerm(int) {
|
|||
}
|
||||
#endif
|
||||
|
||||
void FurnaceCLI::noStatus() {
|
||||
disableStatus=true;
|
||||
}
|
||||
|
||||
void FurnaceCLI::noControls() {
|
||||
disableControls=true;
|
||||
}
|
||||
|
||||
void FurnaceCLI::bindEngine(DivEngine* eng) {
|
||||
e=eng;
|
||||
}
|
||||
|
||||
bool FurnaceCLI::loop() {
|
||||
if (disableControls) {
|
||||
while (!cliQuit) {
|
||||
#ifdef _WIN32
|
||||
Sleep(1000);
|
||||
#else
|
||||
pause();
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool escape=false;
|
||||
bool escapeSecondStage=false;
|
||||
while (!cliQuit) {
|
||||
|
|
@ -98,6 +117,7 @@ bool FurnaceCLI::loop() {
|
|||
}
|
||||
|
||||
bool FurnaceCLI::finish() {
|
||||
if (disableControls) return true;
|
||||
#ifdef _WIN32
|
||||
#else
|
||||
if (tcsetattr(0,TCSAFLUSH,&termpropold)!=0) {
|
||||
|
|
@ -112,6 +132,8 @@ bool FurnaceCLI::finish() {
|
|||
// blatantly copied from tildearrow/tfmxplay
|
||||
bool FurnaceCLI::init() {
|
||||
#ifdef _WIN32
|
||||
if (disableControls) return true;
|
||||
|
||||
winin=GetStdHandle(STD_INPUT_HANDLE);
|
||||
winout=GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
int termprop=0;
|
||||
|
|
@ -128,6 +150,8 @@ bool FurnaceCLI::init() {
|
|||
intsa.sa_handler=handleTerm;
|
||||
sigaction(SIGINT,&intsa,NULL);
|
||||
|
||||
if (disableControls) return true;
|
||||
|
||||
if (tcgetattr(0,&termprop)!=0) {
|
||||
logE("could not get console attributes!");
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@
|
|||
|
||||
class FurnaceCLI {
|
||||
DivEngine* e;
|
||||
bool disableStatus;
|
||||
bool disableControls;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE winin;
|
||||
|
|
@ -46,6 +48,8 @@ class FurnaceCLI {
|
|||
#endif
|
||||
|
||||
public:
|
||||
void noStatus();
|
||||
void noControls();
|
||||
void bindEngine(DivEngine* eng);
|
||||
bool loop();
|
||||
bool finish();
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ void brrEncodeBlock(const short* buf, unsigned char* out, unsigned char range, u
|
|||
}
|
||||
}
|
||||
|
||||
long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigned char emphasis) {
|
||||
long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigned char emphasis, unsigned char noFilter) {
|
||||
if (len==0) return 0;
|
||||
|
||||
// encoding process:
|
||||
|
|
@ -157,6 +157,7 @@ long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigne
|
|||
long total=0;
|
||||
unsigned char filter=0;
|
||||
unsigned char range=0;
|
||||
unsigned char numFilters=noFilter?2:4;
|
||||
|
||||
short x0=0;
|
||||
short x1=0;
|
||||
|
|
@ -211,7 +212,7 @@ long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigne
|
|||
}
|
||||
|
||||
// encode
|
||||
for (int j=0; j<4; j++) {
|
||||
for (int j=0; j<numFilters; j++) {
|
||||
for (int k=0; k<13; k++) {
|
||||
brrEncodeBlock(in,possibleOut[j][k],k,j,&last1[j][k],&last2[j][k],&avgError[j][k]);
|
||||
}
|
||||
|
|
@ -228,7 +229,7 @@ long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigne
|
|||
}
|
||||
}
|
||||
} else {
|
||||
for (int j=0; j<4; j++) {
|
||||
for (int j=0; j<numFilters; j++) {
|
||||
for (int k=0; k<13; k++) {
|
||||
if (avgError[j][k]<candError) {
|
||||
candError=avgError[j][k];
|
||||
|
|
@ -245,7 +246,7 @@ long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigne
|
|||
out[j+1]=possibleOut[filter][range][j];
|
||||
}
|
||||
|
||||
for (int j=0; j<4; j++) {
|
||||
for (int j=0; j<numFilters; j++) {
|
||||
for (int k=0; k<13; k++) {
|
||||
last1[j][k]=last1[filter][range];
|
||||
last2[j][k]=last2[filter][range];
|
||||
|
|
|
|||
|
|
@ -34,9 +34,10 @@ extern "C" {
|
|||
* @param len input length (should be a multiple of 16. if it isn't, the output will be padded).
|
||||
* @param loopStart beginning of loop area (may be -1 for no loop). this is used to ensure the respective block has no filter in order to loop properly.
|
||||
* @param emphasis apply filter to compensate for Gaussian interpolation high frequency loss.
|
||||
* @param noFilter do not use filters in any block. this is used to allow seeking to any sample position.
|
||||
* @return number of written samples.
|
||||
*/
|
||||
long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigned char emphasis);
|
||||
long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigned char emphasis, unsigned char noFilter);
|
||||
|
||||
/**
|
||||
* read len bytes from buf, decode BRR and output to out.
|
||||
|
|
|
|||
|
|
@ -19,11 +19,13 @@
|
|||
|
||||
#include "engine.h"
|
||||
#include "../ta-log.h"
|
||||
#include "../fileutils.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "winStuff.h"
|
||||
#define CONFIG_FILE "\\furnace.cfg"
|
||||
#define LOG_FILE "\\furnace.log"
|
||||
#define LAYOUT_INI "\\layout.ini"
|
||||
#else
|
||||
#ifdef __HAIKU__
|
||||
#include <support/SupportDefs.h>
|
||||
|
|
@ -33,6 +35,8 @@
|
|||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#define CONFIG_FILE "/furnace.cfg"
|
||||
#define LOG_FILE "/furnace.log"
|
||||
#define LAYOUT_INI "/layout.ini"
|
||||
#endif
|
||||
|
||||
#ifdef IS_MOBILE
|
||||
|
|
@ -172,3 +176,23 @@ bool DivEngine::hasConf(String key) {
|
|||
DivConfig& DivEngine::getConfObject() {
|
||||
return conf;
|
||||
}
|
||||
|
||||
void DivEngine::factoryReset() {
|
||||
conf.clear();
|
||||
String confPath=configPath+String(CONFIG_FILE);
|
||||
String layoutPath=configPath+String(LAYOUT_INI);
|
||||
|
||||
for (int i=0; i<10; i++) {
|
||||
String path=confPath;
|
||||
if (i>0) path+=fmt::sprintf(".%d",i);
|
||||
deleteFile(path.c_str());
|
||||
}
|
||||
|
||||
for (int i=0; i<10; i++) {
|
||||
String path=layoutPath;
|
||||
if (i>0) path+=fmt::sprintf(".%d",i);
|
||||
deleteFile(path.c_str());
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -260,6 +260,11 @@ enum DivDispatchCmds {
|
|||
|
||||
DIV_CMD_MINMOD_ECHO,
|
||||
|
||||
DIV_CMD_BIFURCATOR_STATE_LOAD,
|
||||
DIV_CMD_BIFURCATOR_PARAMETER,
|
||||
|
||||
DIV_CMD_FDS_MOD_AUTO,
|
||||
|
||||
DIV_CMD_MAX
|
||||
};
|
||||
|
||||
|
|
@ -678,6 +683,14 @@ class DivDispatch {
|
|||
*/
|
||||
virtual int mapVelocity(int ch, float vel);
|
||||
|
||||
/**
|
||||
* map chip volume to gain.
|
||||
* @param ch the chip channel. -1 means N/A.
|
||||
* @param vol input volume.
|
||||
* @return output gain fron 0.0 to 1.0.
|
||||
*/
|
||||
virtual float getGain(int ch, int vol);
|
||||
|
||||
/**
|
||||
* get the lowest note in a portamento.
|
||||
* @param ch the channel in question.
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@
|
|||
#include "platform/powernoise.h"
|
||||
#include "platform/dave.h"
|
||||
#include "platform/nds.h"
|
||||
#include "platform/bifurcator.h"
|
||||
#include "platform/sid2.h"
|
||||
#include "platform/dummy.h"
|
||||
#include "../ta-log.h"
|
||||
#include "song.h"
|
||||
|
|
@ -299,9 +301,19 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_GB:
|
||||
dispatch=new DivPlatformGB;
|
||||
if (isRender) {
|
||||
((DivPlatformGB*)dispatch)->setCoreQuality(eng->getConfInt("gbQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformGB*)dispatch)->setCoreQuality(eng->getConfInt("gbQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_PCE:
|
||||
dispatch=new DivPlatformPCE;
|
||||
if (isRender) {
|
||||
((DivPlatformPCE*)dispatch)->setCoreQuality(eng->getConfInt("pceQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformPCE*)dispatch)->setCoreQuality(eng->getConfInt("pceQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
dispatch=new DivPlatformNES;
|
||||
|
|
@ -316,8 +328,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
dispatch=new DivPlatformC64;
|
||||
if (isRender) {
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64CoreRender",1));
|
||||
((DivPlatformC64*)dispatch)->setCoreQuality(eng->getConfInt("dsidQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0));
|
||||
((DivPlatformC64*)dispatch)->setCoreQuality(eng->getConfInt("dsidQuality",3));
|
||||
}
|
||||
((DivPlatformC64*)dispatch)->setChipModel(true);
|
||||
break;
|
||||
|
|
@ -325,8 +339,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
dispatch=new DivPlatformC64;
|
||||
if (isRender) {
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64CoreRender",1));
|
||||
((DivPlatformC64*)dispatch)->setCoreQuality(eng->getConfInt("dsidQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0));
|
||||
((DivPlatformC64*)dispatch)->setCoreQuality(eng->getConfInt("dsidQuality",3));
|
||||
}
|
||||
((DivPlatformC64*)dispatch)->setChipModel(false);
|
||||
break;
|
||||
|
|
@ -342,34 +358,34 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_YM2610_FULL:
|
||||
dispatch=new DivPlatformYM2610;
|
||||
if (isRender) {
|
||||
((DivPlatformYM2610*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
((DivPlatformYM2610*)dispatch)->setCombo(eng->getConfInt("opnbCoreRender",1));
|
||||
} else {
|
||||
((DivPlatformYM2610*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
((DivPlatformYM2610*)dispatch)->setCombo(eng->getConfInt("opnbCore",1));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610_EXT:
|
||||
case DIV_SYSTEM_YM2610_FULL_EXT:
|
||||
dispatch=new DivPlatformYM2610Ext;
|
||||
if (isRender) {
|
||||
((DivPlatformYM2610Ext*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
((DivPlatformYM2610Ext*)dispatch)->setCombo(eng->getConfInt("opnbCoreRender",1));
|
||||
} else {
|
||||
((DivPlatformYM2610Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
((DivPlatformYM2610Ext*)dispatch)->setCombo(eng->getConfInt("opnbCore",1));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610B:
|
||||
dispatch=new DivPlatformYM2610B;
|
||||
if (isRender) {
|
||||
((DivPlatformYM2610B*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
((DivPlatformYM2610B*)dispatch)->setCombo(eng->getConfInt("opnbCoreRender",1));
|
||||
} else {
|
||||
((DivPlatformYM2610B*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
((DivPlatformYM2610B*)dispatch)->setCombo(eng->getConfInt("opnbCore",1));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2610B_EXT:
|
||||
dispatch=new DivPlatformYM2610BExt;
|
||||
if (isRender) {
|
||||
((DivPlatformYM2610BExt*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
((DivPlatformYM2610BExt*)dispatch)->setCombo(eng->getConfInt("opnbCoreRender",1));
|
||||
} else {
|
||||
((DivPlatformYM2610BExt*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
((DivPlatformYM2610BExt*)dispatch)->setCombo(eng->getConfInt("opnbCore",1));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_AMIGA:
|
||||
|
|
@ -377,6 +393,11 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_AY8910:
|
||||
dispatch=new DivPlatformAY8910;
|
||||
if (isRender) {
|
||||
((DivPlatformAY8910*)dispatch)->setCore(eng->getConfInt("ayCoreRender",0)==1);
|
||||
} else {
|
||||
((DivPlatformAY8910*)dispatch)->setCore(eng->getConfInt("ayCore",0)==1);
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_AY8930:
|
||||
dispatch=new DivPlatformAY8930;
|
||||
|
|
@ -395,39 +416,44 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_YM2203:
|
||||
dispatch=new DivPlatformYM2203;
|
||||
if (isRender) {
|
||||
((DivPlatformYM2203*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
((DivPlatformYM2203*)dispatch)->setCombo(eng->getConfInt("opn1CoreRender",1));
|
||||
} else {
|
||||
((DivPlatformYM2203*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
((DivPlatformYM2203*)dispatch)->setCombo(eng->getConfInt("opn1Core",1));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2203_EXT:
|
||||
dispatch=new DivPlatformYM2203Ext;
|
||||
if (isRender) {
|
||||
((DivPlatformYM2203Ext*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
((DivPlatformYM2203Ext*)dispatch)->setCombo(eng->getConfInt("opn1CoreRender",1));
|
||||
} else {
|
||||
((DivPlatformYM2203Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
((DivPlatformYM2203Ext*)dispatch)->setCombo(eng->getConfInt("opn1Core",1));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2608:
|
||||
dispatch=new DivPlatformYM2608;
|
||||
if (isRender) {
|
||||
((DivPlatformYM2608*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
((DivPlatformYM2608*)dispatch)->setCombo(eng->getConfInt("opnaCoreRender",1));
|
||||
} else {
|
||||
((DivPlatformYM2608*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
((DivPlatformYM2608*)dispatch)->setCombo(eng->getConfInt("opnaCore",1));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YM2608_EXT:
|
||||
dispatch=new DivPlatformYM2608Ext;
|
||||
if (isRender) {
|
||||
((DivPlatformYM2608Ext*)dispatch)->setCombo(eng->getConfInt("opnCoreRender",1)==1);
|
||||
((DivPlatformYM2608Ext*)dispatch)->setCombo(eng->getConfInt("opnaCoreRender",1));
|
||||
} else {
|
||||
((DivPlatformYM2608Ext*)dispatch)->setCombo(eng->getConfInt("opnCore",1)==1);
|
||||
((DivPlatformYM2608Ext*)dispatch)->setCombo(eng->getConfInt("opnaCore",1));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_OPLL:
|
||||
case DIV_SYSTEM_OPLL_DRUMS:
|
||||
case DIV_SYSTEM_VRC7:
|
||||
dispatch=new DivPlatformOPLL;
|
||||
if (isRender) {
|
||||
((DivPlatformOPLL*)dispatch)->setCore(eng->getConfInt("opllCoreRender",0));
|
||||
} else {
|
||||
((DivPlatformOPLL*)dispatch)->setCore(eng->getConfInt("opllCore",0));
|
||||
}
|
||||
((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7);
|
||||
((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS);
|
||||
break;
|
||||
|
|
@ -508,6 +534,11 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_SAA1099: {
|
||||
dispatch=new DivPlatformSAA1099;
|
||||
if (isRender) {
|
||||
((DivPlatformSAA1099*)dispatch)->setCoreQuality(eng->getConfInt("saaQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformSAA1099*)dispatch)->setCoreQuality(eng->getConfInt("saaQuality",3));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_PCSPKR:
|
||||
|
|
@ -545,18 +576,33 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_SWAN:
|
||||
dispatch=new DivPlatformSwan;
|
||||
if (isRender) {
|
||||
((DivPlatformSwan*)dispatch)->setCoreQuality(eng->getConfInt("swanQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformSwan*)dispatch)->setCoreQuality(eng->getConfInt("swanQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_T6W28:
|
||||
dispatch=new DivPlatformT6W28;
|
||||
break;
|
||||
case DIV_SYSTEM_VBOY:
|
||||
dispatch=new DivPlatformVB;
|
||||
if (isRender) {
|
||||
((DivPlatformVB*)dispatch)->setCoreQuality(eng->getConfInt("vbQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformVB*)dispatch)->setCoreQuality(eng->getConfInt("vbQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_VERA:
|
||||
dispatch=new DivPlatformVERA;
|
||||
break;
|
||||
case DIV_SYSTEM_BUBSYS_WSG:
|
||||
dispatch=new DivPlatformBubSysWSG;
|
||||
if (isRender) {
|
||||
((DivPlatformBubSysWSG*)dispatch)->setCoreQuality(eng->getConfInt("bubsysQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformBubSysWSG*)dispatch)->setCoreQuality(eng->getConfInt("bubsysQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_N163:
|
||||
dispatch=new DivPlatformN163;
|
||||
|
|
@ -582,10 +628,20 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_SCC:
|
||||
dispatch=new DivPlatformSCC;
|
||||
((DivPlatformSCC*)dispatch)->setChipModel(false);
|
||||
if (isRender) {
|
||||
((DivPlatformSCC*)dispatch)->setCoreQuality(eng->getConfInt("sccQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformSCC*)dispatch)->setCoreQuality(eng->getConfInt("sccQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_SCC_PLUS:
|
||||
dispatch=new DivPlatformSCC;
|
||||
((DivPlatformSCC*)dispatch)->setChipModel(true);
|
||||
if (isRender) {
|
||||
((DivPlatformSCC*)dispatch)->setCoreQuality(eng->getConfInt("sccQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformSCC*)dispatch)->setCoreQuality(eng->getConfInt("sccQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_YMZ280B:
|
||||
dispatch=new DivPlatformYMZ280B;
|
||||
|
|
@ -630,6 +686,11 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_SM8521:
|
||||
dispatch=new DivPlatformSM8521;
|
||||
if (isRender) {
|
||||
((DivPlatformSM8521*)dispatch)->setCoreQuality(eng->getConfInt("smQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformSM8521*)dispatch)->setCoreQuality(eng->getConfInt("smQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_PV1000:
|
||||
dispatch=new DivPlatformPV1000;
|
||||
|
|
@ -654,6 +715,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
case DIV_SYSTEM_GBA_MINMOD:
|
||||
dispatch=new DivPlatformGBAMinMod;
|
||||
break;
|
||||
case DIV_SYSTEM_BIFURCATOR:
|
||||
dispatch=new DivPlatformBifurcator;
|
||||
break;
|
||||
case DIV_SYSTEM_PCM_DAC:
|
||||
dispatch=new DivPlatformPCMDAC;
|
||||
break;
|
||||
|
|
@ -667,12 +731,22 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
break;
|
||||
case DIV_SYSTEM_POWERNOISE:
|
||||
dispatch=new DivPlatformPowerNoise;
|
||||
if (isRender) {
|
||||
((DivPlatformPowerNoise*)dispatch)->setCoreQuality(eng->getConfInt("pnQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformPowerNoise*)dispatch)->setCoreQuality(eng->getConfInt("pnQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_DAVE:
|
||||
dispatch=new DivPlatformDave;
|
||||
break;
|
||||
case DIV_SYSTEM_NDS:
|
||||
dispatch=new DivPlatformNDS;
|
||||
if (isRender) {
|
||||
((DivPlatformNDS*)dispatch)->setCoreQuality(eng->getConfInt("ndsQualityRender",3));
|
||||
} else {
|
||||
((DivPlatformNDS*)dispatch)->setCoreQuality(eng->getConfInt("ndsQuality",3));
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_5E01:
|
||||
dispatch=new DivPlatformNES;
|
||||
|
|
@ -683,6 +757,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
|
|||
}
|
||||
((DivPlatformNES*)dispatch)->set5E01(true);
|
||||
break;
|
||||
case DIV_SYSTEM_SID2:
|
||||
dispatch=new DivPlatformSID2;
|
||||
break;
|
||||
case DIV_SYSTEM_DUMMY:
|
||||
dispatch=new DivPlatformDummy;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@
|
|||
#ifdef HAVE_PA
|
||||
#include "../audio/pa.h"
|
||||
#endif
|
||||
#include "../audio/pipe.h"
|
||||
#include <math.h>
|
||||
#include <float.h>
|
||||
#include <fmt/printf.h>
|
||||
|
|
@ -47,115 +48,131 @@ void process(void* u, float** in, float** out, int inChans, int outChans, unsign
|
|||
const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNull) {
|
||||
switch (effect) {
|
||||
case 0x00:
|
||||
return "00xy: Arpeggio";
|
||||
return _("00xy: Arpeggio");
|
||||
case 0x01:
|
||||
return "01xx: Pitch slide up";
|
||||
return _("01xx: Pitch slide up");
|
||||
case 0x02:
|
||||
return "02xx: Pitch slide down";
|
||||
return _("02xx: Pitch slide down");
|
||||
case 0x03:
|
||||
return "03xx: Portamento";
|
||||
return _("03xx: Portamento");
|
||||
case 0x04:
|
||||
return "04xy: Vibrato (x: speed; y: depth)";
|
||||
return _("04xy: Vibrato (x: speed; y: depth)");
|
||||
case 0x05:
|
||||
return "05xy: Volume slide + vibrato (compatibility only!)";
|
||||
return _("05xy: Volume slide + vibrato (compatibility only!)");
|
||||
case 0x06:
|
||||
return "06xy: Volume slide + portamento (compatibility only!)";
|
||||
return _("06xy: Volume slide + portamento (compatibility only!)");
|
||||
case 0x07:
|
||||
return "07xy: Tremolo (x: speed; y: depth)";
|
||||
return _("07xy: Tremolo (x: speed; y: depth)");
|
||||
case 0x08:
|
||||
return "08xy: Set panning (x: left; y: right)";
|
||||
return _("08xy: Set panning (x: left; y: right)");
|
||||
case 0x09:
|
||||
return "09xx: Set groove pattern (speed 1 if no grooves exist)";
|
||||
return _("09xx: Set groove pattern (speed 1 if no grooves exist)");
|
||||
case 0x0a:
|
||||
return "0Axy: Volume slide (0y: down; x0: up)";
|
||||
return _("0Axy: Volume slide (0y: down; x0: up)");
|
||||
case 0x0b:
|
||||
return "0Bxx: Jump to pattern";
|
||||
return _("0Bxx: Jump to pattern");
|
||||
case 0x0c:
|
||||
return "0Cxx: Retrigger";
|
||||
return _("0Cxx: Retrigger");
|
||||
case 0x0d:
|
||||
return "0Dxx: Jump to next pattern";
|
||||
return _("0Dxx: Jump to next pattern");
|
||||
case 0x0f:
|
||||
return "0Fxx: Set speed (speed 2 if no grooves exist)";
|
||||
return _("0Fxx: Set speed (speed 2 if no grooves exist)");
|
||||
case 0x80:
|
||||
return "80xx: Set panning (00: left; 80: center; FF: right)";
|
||||
return _("80xx: Set panning (00: left; 80: center; FF: right)");
|
||||
case 0x81:
|
||||
return "81xx: Set panning (left channel)";
|
||||
return _("81xx: Set panning (left channel)");
|
||||
case 0x82:
|
||||
return "82xx: Set panning (right channel)";
|
||||
return _("82xx: Set panning (right channel)");
|
||||
case 0x83:
|
||||
return _("83xy: Panning slide (x0: left; 0y: right)");
|
||||
case 0x84:
|
||||
return _("84xy: Panbrello (x: speed; y: depth)");
|
||||
case 0x88:
|
||||
return "88xy: Set panning (rear channels; x: left; y: right)";
|
||||
return _("88xy: Set panning (rear channels; x: left; y: right)");
|
||||
break;
|
||||
case 0x89:
|
||||
return "89xx: Set panning (rear left channel)";
|
||||
return _("89xx: Set panning (rear left channel)");
|
||||
break;
|
||||
case 0x8a:
|
||||
return "8Axx: Set panning (rear right channel)";
|
||||
return _("8Axx: Set panning (rear right channel)");
|
||||
break;
|
||||
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
||||
return "Cxxx: Set tick rate (hz)";
|
||||
return _("Cxxx: Set tick rate (hz)");
|
||||
case 0xdc:
|
||||
return _("DCxx: Delayed mute");
|
||||
case 0xe0:
|
||||
return "E0xx: Set arp speed";
|
||||
return _("E0xx: Set arp speed");
|
||||
case 0xe1:
|
||||
return "E1xy: Note slide up (x: speed; y: semitones)";
|
||||
return _("E1xy: Note slide up (x: speed; y: semitones)");
|
||||
case 0xe2:
|
||||
return "E2xy: Note slide down (x: speed; y: semitones)";
|
||||
return _("E2xy: Note slide down (x: speed; y: semitones)");
|
||||
case 0xe3:
|
||||
return "E3xx: Set vibrato shape (0: up/down; 1: up only; 2: down only)";
|
||||
return _("E3xx: Set vibrato shape");
|
||||
case 0xe4:
|
||||
return "E4xx: Set vibrato range";
|
||||
return _("E4xx: Set vibrato range");
|
||||
case 0xe5:
|
||||
return "E5xx: Set pitch (80: center)";
|
||||
return _("E5xx: Set pitch (80: center)");
|
||||
case 0xe6:
|
||||
return "E6xy: Quick legato (x: time (0-7 up; 8-F down); y: semitones)";
|
||||
return _("E6xy: Quick legato (x: time (0-7 up; 8-F down); y: semitones)");
|
||||
case 0xe7:
|
||||
return "E7xx: Macro release";
|
||||
return _("E7xx: Macro release");
|
||||
case 0xe8:
|
||||
return "E8xy: Quick legato up (x: time; y: semitones)";
|
||||
return _("E8xy: Quick legato up (x: time; y: semitones)");
|
||||
case 0xe9:
|
||||
return "E9xy: Quick legato down (x: time; y: semitones)";
|
||||
return _("E9xy: Quick legato down (x: time; y: semitones)");
|
||||
case 0xea:
|
||||
return "EAxx: Legato";
|
||||
return _("EAxx: Legato");
|
||||
case 0xeb:
|
||||
return "EBxx: Set LEGACY sample mode bank";
|
||||
return _("EBxx: Set LEGACY sample mode bank");
|
||||
case 0xec:
|
||||
return "ECxx: Note cut";
|
||||
return _("ECxx: Note cut");
|
||||
case 0xed:
|
||||
return "EDxx: Note delay";
|
||||
return _("EDxx: Note delay");
|
||||
case 0xee:
|
||||
return "EExx: Send external command";
|
||||
return _("EExx: Send external command");
|
||||
case 0xf0:
|
||||
return "F0xx: Set tick rate (bpm)";
|
||||
return _("F0xx: Set tick rate (bpm)");
|
||||
case 0xf1:
|
||||
return "F1xx: Single tick note slide up";
|
||||
return _("F1xx: Single tick note slide up");
|
||||
case 0xf2:
|
||||
return "F2xx: Single tick note slide down";
|
||||
return _("F2xx: Single tick note slide down");
|
||||
case 0xf3:
|
||||
return "F3xx: Fine volume slide up";
|
||||
return _("F3xx: Fine volume slide up");
|
||||
case 0xf4:
|
||||
return "F4xx: Fine volume slide down";
|
||||
return _("F4xx: Fine volume slide down");
|
||||
case 0xf5:
|
||||
return "F5xx: Disable macro (see manual)";
|
||||
return _("F5xx: Disable macro (see manual)");
|
||||
case 0xf6:
|
||||
return "F6xx: Enable macro (see manual)";
|
||||
return _("F6xx: Enable macro (see manual)");
|
||||
case 0xf7:
|
||||
return "F7xx: Restart macro (see manual)";
|
||||
return _("F7xx: Restart macro (see manual)");
|
||||
case 0xf8:
|
||||
return "F8xx: Single tick volume slide up";
|
||||
return _("F8xx: Single tick volume slide up");
|
||||
case 0xf9:
|
||||
return "F9xx: Single tick volume slide down";
|
||||
return _("F9xx: Single tick volume slide down");
|
||||
case 0xfa:
|
||||
return "FAxx: Fast volume slide (0y: down; x0: up)";
|
||||
return _("FAxx: Fast volume slide (0y: down; x0: up)");
|
||||
case 0xfc:
|
||||
return "FCxx: Note release";
|
||||
return _("FCxx: Note release");
|
||||
case 0xfd:
|
||||
return "FDxx: Set virtual tempo numerator";
|
||||
return _("FDxx: Set virtual tempo numerator");
|
||||
case 0xfe:
|
||||
return "FExx: Set virtual tempo denominator";
|
||||
return _("FExx: Set virtual tempo denominator");
|
||||
case 0xff:
|
||||
return "FFxx: Stop song";
|
||||
return _("FFxx: Stop song");
|
||||
default:
|
||||
if ((effect&0xf0)==0x90) {
|
||||
return "9xxx: Set sample offset*256";
|
||||
if (song.oldSampleOffset) {
|
||||
return _("9xxx: Set sample offset*256");
|
||||
}
|
||||
switch (effect) {
|
||||
case 0x90:
|
||||
return _("90xx: Set sample offset (first byte)");
|
||||
case 0x91:
|
||||
return _("91xx: Set sample offset (second byte, ×256)");
|
||||
case 0x92:
|
||||
return _("92xx: Set sample offset (third byte, ×65536)");
|
||||
}
|
||||
} else if (chan>=0 && chan<chans) {
|
||||
DivSysDef* sysDef=sysDefs[sysOfChan[chan]];
|
||||
auto iter=sysDef->effectHandlers.find(effect);
|
||||
|
|
@ -173,83 +190,12 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
|
|||
}
|
||||
break;
|
||||
}
|
||||
return notNull?"Invalid effect":NULL;
|
||||
return notNull?_("Invalid effect"):NULL;
|
||||
}
|
||||
|
||||
void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
||||
loopOrder=0;
|
||||
loopRow=0;
|
||||
loopEnd=-1;
|
||||
int nextOrder=-1;
|
||||
int nextRow=0;
|
||||
int effectVal=0;
|
||||
int lastSuspectedLoopEnd=-1;
|
||||
DivPattern* pat[DIV_MAX_CHANS];
|
||||
unsigned char wsWalked[8192];
|
||||
memset(wsWalked,0,8192);
|
||||
for (int i=0; i<curSubSong->ordersLen; i++) {
|
||||
for (int j=0; j<chans; j++) {
|
||||
pat[j]=curPat[j].getPattern(curOrders->ord[j][i],false);
|
||||
}
|
||||
if (i>lastSuspectedLoopEnd) {
|
||||
lastSuspectedLoopEnd=i;
|
||||
}
|
||||
for (int j=nextRow; j<curSubSong->patLen; j++) {
|
||||
nextRow=0;
|
||||
bool changingOrder=false;
|
||||
bool jumpingOrder=false;
|
||||
if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) {
|
||||
loopOrder=i;
|
||||
loopRow=j;
|
||||
loopEnd=lastSuspectedLoopEnd;
|
||||
return;
|
||||
}
|
||||
for (int k=0; k<chans; k++) {
|
||||
for (int l=0; l<curPat[k].effectCols; l++) {
|
||||
effectVal=pat[k]->data[j][5+(l<<1)];
|
||||
if (effectVal<0) effectVal=0;
|
||||
if (pat[k]->data[j][4+(l<<1)]==0x0d) {
|
||||
if (song.jumpTreatment==2) {
|
||||
if ((i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||
nextOrder=i+1;
|
||||
nextRow=effectVal;
|
||||
jumpingOrder=true;
|
||||
}
|
||||
} else if (song.jumpTreatment==1) {
|
||||
if (nextOrder==-1 && (i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||
nextOrder=i+1;
|
||||
nextRow=effectVal;
|
||||
jumpingOrder=true;
|
||||
}
|
||||
} else {
|
||||
if ((i<curSubSong->ordersLen-1 || !song.ignoreJumpAtEnd)) {
|
||||
if (!changingOrder) {
|
||||
nextOrder=i+1;
|
||||
}
|
||||
jumpingOrder=true;
|
||||
nextRow=effectVal;
|
||||
}
|
||||
}
|
||||
} else if (pat[k]->data[j][4+(l<<1)]==0x0b) {
|
||||
if (nextOrder==-1 || song.jumpTreatment==0) {
|
||||
nextOrder=effectVal;
|
||||
if (song.jumpTreatment==1 || song.jumpTreatment==2 || !jumpingOrder) {
|
||||
nextRow=0;
|
||||
}
|
||||
changingOrder=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7);
|
||||
|
||||
if (nextOrder!=-1) {
|
||||
i=nextOrder-1;
|
||||
nextOrder=-1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (curSubSong!=NULL) {
|
||||
curSubSong->walk(loopOrder,loopRow,loopEnd,chans,song.jumpTreatment,song.ignoreJumpAtEnd);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -340,43 +286,43 @@ int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*&
|
|||
}
|
||||
if (fseek(f,0,SEEK_END)<0) {
|
||||
logE("size error: %s",strerror(errno));
|
||||
lastError=fmt::sprintf("on seek: %s",strerror(errno));
|
||||
lastError=fmt::sprintf(_("on seek: %s"),strerror(errno));
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
ssize_t len=ftell(f);
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
logE("could not get file length: %s",strerror(errno));
|
||||
lastError=fmt::sprintf("on pre tell: %s",strerror(errno));
|
||||
lastError=fmt::sprintf(_("on pre tell: %s"),strerror(errno));
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
if (len<1) {
|
||||
if (len==0) {
|
||||
logE("that file is empty!");
|
||||
lastError="file is empty";
|
||||
lastError=_("file is empty");
|
||||
} else {
|
||||
logE("tell error: %s",strerror(errno));
|
||||
lastError=fmt::sprintf("on tell: %s",strerror(errno));
|
||||
lastError=fmt::sprintf(_("on tell: %s"),strerror(errno));
|
||||
}
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
if (len!=expectedSize) {
|
||||
logE("ROM size mismatch, expected: %d bytes, was: %d bytes", expectedSize, len);
|
||||
lastError=fmt::sprintf("ROM size mismatch, expected: %d bytes, was: %d", expectedSize, len);
|
||||
lastError=fmt::sprintf(_("ROM size mismatch, expected: %d bytes, was: %d"), expectedSize, len);
|
||||
return -1;
|
||||
}
|
||||
if (fseek(f,0,SEEK_SET)<0) {
|
||||
logE("size error: %s",strerror(errno));
|
||||
lastError=fmt::sprintf("on get size: %s",strerror(errno));
|
||||
lastError=fmt::sprintf(_("on get size: %s"),strerror(errno));
|
||||
fclose(f);
|
||||
return -1;
|
||||
}
|
||||
unsigned char* file=new unsigned char[len];
|
||||
if (fread(file,1,(size_t)len,f)!=(size_t)len) {
|
||||
logE("read error: %s",strerror(errno));
|
||||
lastError=fmt::sprintf("on read: %s",strerror(errno));
|
||||
lastError=fmt::sprintf(_("on read: %s"),strerror(errno));
|
||||
fclose(f);
|
||||
delete[] file;
|
||||
return -1;
|
||||
|
|
@ -561,6 +507,9 @@ void DivEngine::initSongWithDesc(const char* description, bool inBase64, bool ol
|
|||
|
||||
// extra attributes
|
||||
song.subsong[0]->hz=c.getDouble("tickRate",60.0);
|
||||
if (song.subsong[0]->hz<1.0) song.subsong[0]->hz=1.0;
|
||||
if (song.subsong[0]->hz>999.0) song.subsong[0]->hz=999.0;
|
||||
|
||||
song.author=getConfString("defaultAuthorName","");
|
||||
}
|
||||
|
||||
|
|
@ -962,7 +911,7 @@ void DivEngine::delUnusedSamples() {
|
|||
bool isUsed[256];
|
||||
memset(isUsed,0,256*sizeof(bool));
|
||||
|
||||
// scan
|
||||
// scan in instruments
|
||||
for (DivInstrument* i: song.ins) {
|
||||
if ((i->type==DIV_INS_PCE && i->amiga.useSample) ||
|
||||
i->type==DIV_INS_MSM6258 ||
|
||||
|
|
@ -1005,6 +954,38 @@ void DivEngine::delUnusedSamples() {
|
|||
}
|
||||
}
|
||||
|
||||
// scan in pattern (legacy sample mode)
|
||||
// disabled because it is unreliable
|
||||
/*
|
||||
for (DivSubSong* i: song.subsong) {
|
||||
for (int j=0; j<getTotalChannelCount(); j++) {
|
||||
bool is17On=false;
|
||||
int bank=0;
|
||||
for (int k=0; k<i->ordersLen; k++) {
|
||||
DivPattern* p=i->pat[j].getPattern(i->orders.ord[j][k],false);
|
||||
for (int l=0; l<i->patLen; l++) {
|
||||
for (int m=0; m<i->pat[j].effectCols; m++) {
|
||||
if (p->data[l][4+(m<<1)]==0x17) {
|
||||
is17On=(p->data[l][5+(m<<1)]>0);
|
||||
}
|
||||
if (p->data[l][4+(m<<1)]==0xeb) {
|
||||
bank=p->data[l][5+(m<<1)];
|
||||
if (bank==-1) bank=0;
|
||||
}
|
||||
}
|
||||
if (is17On) {
|
||||
if (p->data[l][1]!=0 || p->data[l][0]!=0) {
|
||||
if (p->data[l][0]<=12) {
|
||||
int note=(12*bank)+(p->data[l][0]%12);
|
||||
if (note<256) isUsed[note]=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
// delete
|
||||
for (int i=0; i<song.sampleLen; i++) {
|
||||
if (!isUsed[i]) {
|
||||
|
|
@ -1027,11 +1008,11 @@ void DivEngine::delUnusedSamples() {
|
|||
|
||||
bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
|
||||
if (index<0 || index>=song.systemLen) {
|
||||
lastError="invalid index";
|
||||
lastError=_("invalid index");
|
||||
return false;
|
||||
}
|
||||
if (chans-getChannelCount(song.system[index])+getChannelCount(which)>DIV_MAX_CHANS) {
|
||||
lastError=fmt::sprintf("max number of total channels is %d",DIV_MAX_CHANS);
|
||||
lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -1081,12 +1062,12 @@ bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
|
|||
}
|
||||
|
||||
bool DivEngine::addSystem(DivSystem which) {
|
||||
if (song.systemLen>DIV_MAX_CHIPS) {
|
||||
lastError=fmt::sprintf("max number of systems is %d",DIV_MAX_CHIPS);
|
||||
if (song.systemLen>=DIV_MAX_CHIPS) {
|
||||
lastError=fmt::sprintf(_("max number of systems is %d"),DIV_MAX_CHIPS);
|
||||
return false;
|
||||
}
|
||||
if (chans+getChannelCount(which)>DIV_MAX_CHANS) {
|
||||
lastError=fmt::sprintf("max number of total channels is %d",DIV_MAX_CHANS);
|
||||
lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS);
|
||||
return false;
|
||||
}
|
||||
quitDispatch();
|
||||
|
|
@ -1132,15 +1113,15 @@ bool DivEngine::addSystem(DivSystem which) {
|
|||
|
||||
bool DivEngine::duplicateSystem(int index, bool pat, bool end) {
|
||||
if (index<0 || index>=song.systemLen) {
|
||||
lastError="invalid index";
|
||||
lastError=_("invalid index");
|
||||
return false;
|
||||
}
|
||||
if (song.systemLen>DIV_MAX_CHIPS) {
|
||||
lastError=fmt::sprintf("max number of systems is %d",DIV_MAX_CHIPS);
|
||||
if (song.systemLen>=DIV_MAX_CHIPS) {
|
||||
lastError=fmt::sprintf(_("max number of systems is %d"),DIV_MAX_CHIPS);
|
||||
return false;
|
||||
}
|
||||
if (chans+getChannelCount(song.system[index])>DIV_MAX_CHANS) {
|
||||
lastError=fmt::sprintf("max number of total channels is %d",DIV_MAX_CHANS);
|
||||
lastError=fmt::sprintf(_("max number of total channels is %d"),DIV_MAX_CHANS);
|
||||
return false;
|
||||
}
|
||||
quitDispatch();
|
||||
|
|
@ -1237,11 +1218,11 @@ bool DivEngine::duplicateSystem(int index, bool pat, bool end) {
|
|||
// TODO: maybe issue with subsongs?
|
||||
bool DivEngine::removeSystem(int index, bool preserveOrder) {
|
||||
if (song.systemLen<=1) {
|
||||
lastError="cannot remove the last one";
|
||||
lastError=_("cannot remove the last one");
|
||||
return false;
|
||||
}
|
||||
if (index<0 || index>=song.systemLen) {
|
||||
lastError="invalid index";
|
||||
lastError=_("invalid index");
|
||||
return false;
|
||||
}
|
||||
int chanCount=chans;
|
||||
|
|
@ -1411,15 +1392,15 @@ void DivEngine::swapSystemUnsafe(int src, int dest, bool preserveOrder) {
|
|||
|
||||
bool DivEngine::swapSystem(int src, int dest, bool preserveOrder) {
|
||||
if (src==dest) {
|
||||
lastError="source and destination are equal";
|
||||
lastError=_("source and destination are equal");
|
||||
return false;
|
||||
}
|
||||
if (src<0 || src>=song.systemLen) {
|
||||
lastError="invalid source index";
|
||||
lastError=_("invalid source index");
|
||||
return false;
|
||||
}
|
||||
if (dest<0 || dest>=song.systemLen) {
|
||||
lastError="invalid destination index";
|
||||
lastError=_("invalid destination index");
|
||||
return false;
|
||||
}
|
||||
//int chanCount=chans;
|
||||
|
|
@ -2344,6 +2325,13 @@ int DivEngine::mapVelocity(int ch, float vel) {
|
|||
return disCont[dispatchOfChan[ch]].dispatch->mapVelocity(dispatchChanOfChan[ch],vel);
|
||||
}
|
||||
|
||||
float DivEngine::getGain(int ch, int vol) {
|
||||
if (ch<0) return 0;
|
||||
if (ch>=chans) return 0;
|
||||
if (disCont[dispatchOfChan[ch]].dispatch==NULL) return 0;
|
||||
return disCont[dispatchOfChan[ch]].dispatch->getGain(dispatchChanOfChan[ch],vol);
|
||||
}
|
||||
|
||||
unsigned char DivEngine::getOrder() {
|
||||
return prevOrder;
|
||||
}
|
||||
|
|
@ -2587,7 +2575,7 @@ int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) {
|
|||
*ins=song.nullInsQSound;
|
||||
}
|
||||
}
|
||||
ins->name=fmt::sprintf("Instrument %d",insCount);
|
||||
ins->name=fmt::sprintf(_("Instrument %d"),insCount);
|
||||
if (prefType!=DIV_INS_NULL) {
|
||||
ins->type=prefType;
|
||||
}
|
||||
|
|
@ -2661,7 +2649,7 @@ void DivEngine::delInstrument(int index) {
|
|||
|
||||
int DivEngine::addWave() {
|
||||
if (song.wave.size()>=256) {
|
||||
lastError="too many wavetables!";
|
||||
lastError=_("too many wavetables!");
|
||||
return -1;
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
|
|
@ -2678,7 +2666,7 @@ int DivEngine::addWave() {
|
|||
|
||||
int DivEngine::addWavePtr(DivWavetable* which) {
|
||||
if (song.wave.size()>=256) {
|
||||
lastError="too many wavetables!";
|
||||
lastError=_("too many wavetables!");
|
||||
delete which;
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -2703,35 +2691,35 @@ DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) {
|
|||
ssize_t len;
|
||||
if (fseek(f,0,SEEK_END)!=0) {
|
||||
fclose(f);
|
||||
lastError=fmt::sprintf("could not seek to end: %s",strerror(errno));
|
||||
lastError=fmt::sprintf(_("could not seek to end: %s"),strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
len=ftell(f);
|
||||
if (len<0) {
|
||||
fclose(f);
|
||||
lastError=fmt::sprintf("could not determine file size: %s",strerror(errno));
|
||||
lastError=fmt::sprintf(_("could not determine file size: %s"),strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
fclose(f);
|
||||
lastError="file size is invalid!";
|
||||
lastError=_("file size is invalid!");
|
||||
return NULL;
|
||||
}
|
||||
if (len==0) {
|
||||
fclose(f);
|
||||
lastError="file is empty";
|
||||
lastError=_("file is empty");
|
||||
return NULL;
|
||||
}
|
||||
if (fseek(f,0,SEEK_SET)!=0) {
|
||||
fclose(f);
|
||||
lastError=fmt::sprintf("could not seek to beginning: %s",strerror(errno));
|
||||
lastError=fmt::sprintf(_("could not seek to beginning: %s"),strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
buf=new unsigned char[len];
|
||||
if (fread(buf,1,len,f)!=(size_t)len) {
|
||||
logW("did not read entire wavetable file buffer!");
|
||||
delete[] buf;
|
||||
lastError=fmt::sprintf("could not read entire file: %s",strerror(errno));
|
||||
lastError=fmt::sprintf(_("could not read entire file: %s"),strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
fclose(f);
|
||||
|
|
@ -2757,7 +2745,7 @@ DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) {
|
|||
reader.readS(); // reserved
|
||||
reader.seek(20,SEEK_SET);
|
||||
if (wave->readWaveData(reader,version)!=DIV_DATA_SUCCESS) {
|
||||
lastError="invalid wavetable header/data!";
|
||||
lastError=_("invalid wavetable header/data!");
|
||||
delete wave;
|
||||
delete[] buf;
|
||||
return NULL;
|
||||
|
|
@ -2828,7 +2816,7 @@ DivWavetable* DivEngine::waveFromFile(const char* path, bool addRaw) {
|
|||
} catch (EndOfFileException& e) {
|
||||
delete wave;
|
||||
delete[] buf;
|
||||
lastError="premature end of file";
|
||||
lastError=_("premature end of file");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -2855,14 +2843,14 @@ void DivEngine::delWave(int index) {
|
|||
|
||||
int DivEngine::addSample() {
|
||||
if (song.sample.size()>=256) {
|
||||
lastError="too many samples!";
|
||||
lastError=_("too many samples!");
|
||||
return -1;
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
saveLock.lock();
|
||||
DivSample* sample=new DivSample;
|
||||
int sampleCount=(int)song.sample.size();
|
||||
sample->name=fmt::sprintf("Sample %d",sampleCount);
|
||||
sample->name=fmt::sprintf(_("Sample %d"),sampleCount);
|
||||
song.sample.push_back(sample);
|
||||
song.sampleLen=sampleCount+1;
|
||||
sPreview.sample=-1;
|
||||
|
|
@ -2877,7 +2865,7 @@ int DivEngine::addSample() {
|
|||
|
||||
int DivEngine::addSamplePtr(DivSample* which) {
|
||||
if (song.sample.size()>=256) {
|
||||
lastError="too many samples!";
|
||||
lastError=_("too many samples!");
|
||||
delete which;
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -3007,7 +2995,7 @@ void DivEngine::deepCloneOrder(int pos, bool where) {
|
|||
}
|
||||
}
|
||||
if (didNotFind) {
|
||||
addWarning(fmt::sprintf("no free patterns in channel %d!",i));
|
||||
addWarning(fmt::sprintf(_("no free patterns in channel %d!"),i));
|
||||
}
|
||||
}
|
||||
if (where) { // at the end
|
||||
|
|
@ -3522,8 +3510,9 @@ void DivEngine::setSamplePreviewVol(float vol) {
|
|||
previewVol=vol;
|
||||
}
|
||||
|
||||
void DivEngine::setConsoleMode(bool enable) {
|
||||
void DivEngine::setConsoleMode(bool enable, bool statusOut) {
|
||||
consoleMode=enable;
|
||||
disableStatusOut=!statusOut;
|
||||
}
|
||||
|
||||
bool DivEngine::switchMaster(bool full) {
|
||||
|
|
@ -3600,6 +3589,12 @@ void DivEngine::synchronized(const std::function<void()>& what) {
|
|||
BUSY_END;
|
||||
}
|
||||
|
||||
void DivEngine::synchronizedSoft(const std::function<void()>& what) {
|
||||
BUSY_BEGIN_SOFT;
|
||||
what();
|
||||
BUSY_END;
|
||||
}
|
||||
|
||||
void DivEngine::lockSave(const std::function<void()>& what) {
|
||||
saveLock.lock();
|
||||
what();
|
||||
|
|
@ -3800,6 +3795,9 @@ bool DivEngine::initAudioBackend() {
|
|||
output=new TAAudio;
|
||||
#endif
|
||||
break;
|
||||
case DIV_AUDIO_PIPE:
|
||||
output=new TAAudioPipe;
|
||||
break;
|
||||
case DIV_AUDIO_DUMMY:
|
||||
output=new TAAudio;
|
||||
break;
|
||||
|
|
@ -3906,15 +3904,27 @@ bool DivEngine::deinitAudioBackend(bool dueToSwitchMaster) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DivEngine::preInit(bool noSafeMode) {
|
||||
bool wantSafe=false;
|
||||
// register systems
|
||||
if (!systemsRegistered) registerSystems();
|
||||
|
||||
bool DivEngine::prePreInit() {
|
||||
// init config
|
||||
initConfDir();
|
||||
logD("config path: %s",configPath.c_str());
|
||||
|
||||
configLoaded=true;
|
||||
return loadConf();
|
||||
}
|
||||
|
||||
bool DivEngine::preInit(bool noSafeMode) {
|
||||
bool wantSafe=false;
|
||||
if (!configLoaded) prePreInit();
|
||||
|
||||
logI("Furnace version " DIV_VERSION ".");
|
||||
|
||||
// register systems
|
||||
if (!systemsRegistered) registerSystems();
|
||||
|
||||
// register ROM exports
|
||||
if (!romExportsRegistered) registerROMExports();
|
||||
|
||||
// TODO: re-enable with a better approach
|
||||
// see issue #1581
|
||||
/*
|
||||
|
|
@ -3929,9 +3939,21 @@ bool DivEngine::preInit(bool noSafeMode) {
|
|||
String logPath=configPath+DIR_SEPARATOR_STR+"furnace.log";
|
||||
startLogFile(logPath.c_str());
|
||||
|
||||
logI("Furnace version " DIV_VERSION ".");
|
||||
|
||||
loadConf();
|
||||
if (!conf.has("opn1Core")) {
|
||||
if (conf.has("opnCore")) {
|
||||
conf.set("opn1Core",conf.getString("opnCore",""));
|
||||
}
|
||||
}
|
||||
if (!conf.has("opnaCore")) {
|
||||
if (conf.has("opnCore")) {
|
||||
conf.set("opnaCore",conf.getString("opnCore",""));
|
||||
}
|
||||
}
|
||||
if (!conf.has("opnbCore")) {
|
||||
if (conf.has("opnCore")) {
|
||||
conf.set("opnbCore",conf.getString("opnCore",""));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_SDL2
|
||||
String audioDriver=getConfString("sdlAudioDriver","");
|
||||
|
|
|
|||
|
|
@ -54,13 +54,16 @@ class DivWorkPool;
|
|||
|
||||
#define DIV_UNSTABLE
|
||||
|
||||
#define DIV_VERSION "dev196"
|
||||
#define DIV_ENGINE_VERSION 196
|
||||
#define DIV_VERSION "dev217"
|
||||
#define DIV_ENGINE_VERSION 217
|
||||
// for imports
|
||||
#define DIV_VERSION_MOD 0xff01
|
||||
#define DIV_VERSION_FC 0xff02
|
||||
#define DIV_VERSION_S3M 0xff03
|
||||
#define DIV_VERSION_FTM 0xff04
|
||||
#define DIV_VERSION_TFE 0xff05
|
||||
#define DIV_VERSION_XM 0xff06
|
||||
#define DIV_VERSION_IT 0xff07
|
||||
|
||||
enum DivStatusView {
|
||||
DIV_STATUS_NOTHING=0,
|
||||
|
|
@ -72,6 +75,7 @@ enum DivAudioEngines {
|
|||
DIV_AUDIO_JACK=0,
|
||||
DIV_AUDIO_SDL=1,
|
||||
DIV_AUDIO_PORTAUDIO=2,
|
||||
DIV_AUDIO_PIPE=3,
|
||||
|
||||
DIV_AUDIO_NULL=126,
|
||||
DIV_AUDIO_DUMMY=127
|
||||
|
|
@ -97,16 +101,47 @@ enum DivMIDIModes {
|
|||
DIV_MIDI_MODE_LIGHT_SHOW
|
||||
};
|
||||
|
||||
enum DivAudioExportFormats {
|
||||
DIV_EXPORT_FORMAT_S16=0,
|
||||
DIV_EXPORT_FORMAT_F32
|
||||
};
|
||||
|
||||
struct DivAudioExportOptions {
|
||||
DivAudioExportModes mode;
|
||||
DivAudioExportFormats format;
|
||||
int sampleRate;
|
||||
int chans;
|
||||
int loops;
|
||||
double fadeOut;
|
||||
int orderBegin, orderEnd;
|
||||
bool channelMask[DIV_MAX_CHANS];
|
||||
DivAudioExportOptions():
|
||||
mode(DIV_EXPORT_MODE_ONE),
|
||||
format(DIV_EXPORT_FORMAT_S16),
|
||||
sampleRate(44100),
|
||||
chans(2),
|
||||
loops(0),
|
||||
fadeOut(0.0),
|
||||
orderBegin(-1),
|
||||
orderEnd(-1) {
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
channelMask[i]=true;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct DivChannelState {
|
||||
std::vector<DivDelayedCommand> delayed;
|
||||
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
|
||||
int volume, volSpeed, cut, legatoDelay, legatoTarget, rowDelay, volMax;
|
||||
int volume, volSpeed, cut, volCut, legatoDelay, legatoTarget, rowDelay, volMax;
|
||||
int delayOrder, delayRow, retrigSpeed, retrigTick;
|
||||
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoDir, vibratoFine;
|
||||
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoShape, vibratoFine;
|
||||
int tremoloDepth, tremoloRate, tremoloPos;
|
||||
int panDepth, panRate, panPos, panSpeed;
|
||||
int sampleOff;
|
||||
unsigned char arp, arpStage, arpTicks, panL, panR, panRL, panRR, lastVibrato, lastPorta, cutType;
|
||||
bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff, releasing;
|
||||
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp;
|
||||
bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, wasShorthandPorta, noteOnInhibit, resetArp, sampleOffSet;
|
||||
bool wentThroughNote, goneThroughNote;
|
||||
|
||||
int midiNote, curMidiNote, midiPitch;
|
||||
|
|
@ -123,6 +158,7 @@ struct DivChannelState {
|
|||
volume(0x7f00),
|
||||
volSpeed(0),
|
||||
cut(-1),
|
||||
volCut(-1),
|
||||
legatoDelay(-1),
|
||||
legatoTarget(0),
|
||||
rowDelay(0),
|
||||
|
|
@ -135,11 +171,16 @@ struct DivChannelState {
|
|||
vibratoRate(0),
|
||||
vibratoPos(0),
|
||||
vibratoPosGiant(0),
|
||||
vibratoDir(0),
|
||||
vibratoShape(0),
|
||||
vibratoFine(15),
|
||||
tremoloDepth(0),
|
||||
tremoloRate(0),
|
||||
tremoloPos(0),
|
||||
panDepth(0),
|
||||
panRate(0),
|
||||
panPos(0),
|
||||
panSpeed(0),
|
||||
sampleOff(0),
|
||||
arp(0),
|
||||
arpStage(-1),
|
||||
arpTicks(1),
|
||||
|
|
@ -166,6 +207,7 @@ struct DivChannelState {
|
|||
wasShorthandPorta(false),
|
||||
noteOnInhibit(false),
|
||||
resetArp(false),
|
||||
sampleOffSet(false),
|
||||
wentThroughNote(false),
|
||||
goneThroughNote(false),
|
||||
midiNote(-1),
|
||||
|
|
@ -397,6 +439,7 @@ class DivEngine {
|
|||
String exportPath;
|
||||
std::thread* exportThread;
|
||||
int chans;
|
||||
bool configLoaded;
|
||||
bool active;
|
||||
bool lowQuality;
|
||||
bool dcHiPass;
|
||||
|
|
@ -405,6 +448,7 @@ class DivEngine {
|
|||
bool shallStop, shallStopSched;
|
||||
bool endOfSong;
|
||||
bool consoleMode;
|
||||
bool disableStatusOut;
|
||||
bool extValuePresent;
|
||||
bool repeatPattern;
|
||||
bool metronome;
|
||||
|
|
@ -421,6 +465,7 @@ class DivEngine {
|
|||
bool midiIsDirectProgram;
|
||||
bool lowLatency;
|
||||
bool systemsRegistered;
|
||||
bool romExportsRegistered;
|
||||
bool hasLoadedSomething;
|
||||
bool midiOutClock;
|
||||
bool midiOutTime;
|
||||
|
|
@ -451,7 +496,10 @@ class DivEngine {
|
|||
DivChannelState chan[DIV_MAX_CHANS];
|
||||
DivAudioEngines audioEngine;
|
||||
DivAudioExportModes exportMode;
|
||||
DivAudioExportFormats exportFormat;
|
||||
double exportFadeOut;
|
||||
int exportOutputs;
|
||||
bool exportChannelMask[DIV_MAX_CHANS];
|
||||
DivConfig conf;
|
||||
FixedQueue<DivNoteEvent,8192> pendingNotes;
|
||||
// bitfield
|
||||
|
|
@ -471,6 +519,7 @@ class DivEngine {
|
|||
static DivSysDef* sysDefs[DIV_MAX_CHIP_DEFS];
|
||||
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
|
||||
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
|
||||
static DivROMExportDef* romExportDefs[DIV_ROM_MAX];
|
||||
|
||||
DivCSPlayer* cmdStreamInt;
|
||||
|
||||
|
|
@ -549,8 +598,12 @@ class DivEngine {
|
|||
bool loadFur(unsigned char* file, size_t len, int variantID=0);
|
||||
bool loadMod(unsigned char* file, size_t len);
|
||||
bool loadS3M(unsigned char* file, size_t len);
|
||||
bool loadXM(unsigned char* file, size_t len);
|
||||
bool loadIT(unsigned char* file, size_t len);
|
||||
bool loadFTM(unsigned char* file, size_t len, bool dnft, bool dnftSig, bool eft);
|
||||
bool loadFC(unsigned char* file, size_t len);
|
||||
bool loadTFMv1(unsigned char* file, size_t len);
|
||||
bool loadTFMv2(unsigned char* file, size_t len);
|
||||
|
||||
void loadDMP(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadTFI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
|
|
@ -566,6 +619,17 @@ class DivEngine {
|
|||
void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadWOPL(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
void loadWOPN(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
|
||||
|
||||
//sample banks
|
||||
void loadP(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
|
||||
void loadPPC(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
|
||||
void loadPPS(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
|
||||
void loadPVI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
|
||||
void loadPDX(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
|
||||
void loadPZI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
|
||||
void loadP86(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath);
|
||||
|
||||
|
||||
|
||||
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
|
||||
|
||||
|
|
@ -573,6 +637,7 @@ class DivEngine {
|
|||
bool deinitAudioBackend(bool dueToSwitchMaster=false);
|
||||
|
||||
void registerSystems();
|
||||
void registerROMExports();
|
||||
void initSongWithDesc(const char* description, bool inBase64=true, bool oldVol=false);
|
||||
|
||||
void exchangeIns(int one, int two);
|
||||
|
|
@ -603,6 +668,7 @@ class DivEngine {
|
|||
// add every export method here
|
||||
friend class DivROMExport;
|
||||
friend class DivExportAmigaValidation;
|
||||
friend class DivExportTiuna;
|
||||
|
||||
public:
|
||||
DivSong song;
|
||||
|
|
@ -646,9 +712,8 @@ class DivEngine {
|
|||
// save as .fur.
|
||||
// if notPrimary is true then the song will not be altered
|
||||
SafeWriter* saveFur(bool notPrimary=false, bool newPatternFormat=true);
|
||||
// build a ROM file (TODO).
|
||||
// specify system to build ROM for.
|
||||
std::vector<DivROMExportOutput> buildROM(DivROMExportOptions sys);
|
||||
// return a ROM exporter.
|
||||
DivROMExport* buildROM(DivROMExportOptions sys);
|
||||
// dump to VGM.
|
||||
// set trailingTicks to:
|
||||
// - 0 to add one tick of trailing
|
||||
|
|
@ -658,12 +723,14 @@ class DivEngine {
|
|||
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false, int trailingTicks=-1);
|
||||
// dump to ZSM.
|
||||
SafeWriter* saveZSM(unsigned int zsmrate=60, bool loop=true, bool optimize=true);
|
||||
// dump to TIunA.
|
||||
SafeWriter* saveTiuna(const bool* sysToExport, const char* baseLabel, int firstBankSize, int otherBankSize);
|
||||
// dump command stream.
|
||||
SafeWriter* saveCommand();
|
||||
// export to text
|
||||
SafeWriter* saveText(bool separatePatterns=true);
|
||||
// export to an audio file
|
||||
bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0);
|
||||
bool saveAudio(const char* path, DivAudioExportOptions options);
|
||||
// wait for audio export to finish
|
||||
void waitAudioFile();
|
||||
// stop audio file export
|
||||
|
|
@ -727,6 +794,9 @@ class DivEngine {
|
|||
// get whether config value exists
|
||||
bool hasConf(String key);
|
||||
|
||||
// reset all settings
|
||||
void factoryReset();
|
||||
|
||||
// calculate base frequency/period
|
||||
double calcBaseFreq(double clock, double divider, int note, bool period);
|
||||
|
||||
|
|
@ -829,6 +899,9 @@ class DivEngine {
|
|||
// get sys definition
|
||||
const DivSysDef* getSystemDef(DivSystem sys);
|
||||
|
||||
// get ROM export definition
|
||||
const DivROMExportDef* getROMExportDef(DivROMExportOptions opt);
|
||||
|
||||
// convert sample rate format
|
||||
int fileToDivRate(int frate);
|
||||
int divToFileRate(int drate);
|
||||
|
|
@ -869,6 +942,9 @@ class DivEngine {
|
|||
// map MIDI velocity to volume
|
||||
int mapVelocity(int ch, float vel);
|
||||
|
||||
// map volume to gain
|
||||
float getGain(int ch, int vol);
|
||||
|
||||
// get current order
|
||||
unsigned char getOrder();
|
||||
|
||||
|
|
@ -969,7 +1045,8 @@ class DivEngine {
|
|||
int addSamplePtr(DivSample* which);
|
||||
|
||||
// get sample from file
|
||||
DivSample* sampleFromFile(const char* path);
|
||||
//DivSample* sampleFromFile(const char* path);
|
||||
std::vector<DivSample*> sampleFromFile(const char* path);
|
||||
|
||||
// get raw sample
|
||||
DivSample* sampleFromFileRaw(const char* path, DivSampleDepth depth, int channels, bool bigEndian, bool unsign, bool swapNibbles, int rate);
|
||||
|
|
@ -1099,7 +1176,7 @@ class DivEngine {
|
|||
void rescanMidiDevices();
|
||||
|
||||
// set the console mode.
|
||||
void setConsoleMode(bool enable);
|
||||
void setConsoleMode(bool enable, bool statusOut=true);
|
||||
|
||||
// get metronome
|
||||
bool getMetronome();
|
||||
|
|
@ -1235,6 +1312,9 @@ class DivEngine {
|
|||
// perform secure/sync operation
|
||||
void synchronized(const std::function<void()>& what);
|
||||
|
||||
// perform secure/sync operation (soft)
|
||||
void synchronizedSoft(const std::function<void()>& what);
|
||||
|
||||
// perform secure/sync song operation
|
||||
void lockSave(const std::function<void()>& what);
|
||||
|
||||
|
|
@ -1253,6 +1333,9 @@ class DivEngine {
|
|||
// quit dispatch
|
||||
void quitDispatch();
|
||||
|
||||
// pre-pre-initialize the engine.
|
||||
bool prePreInit();
|
||||
|
||||
// pre-initialize the engine. returns whether Furnace should run in safe mode.
|
||||
bool preInit(bool noSafeMode=true);
|
||||
|
||||
|
|
@ -1273,6 +1356,7 @@ class DivEngine {
|
|||
output(NULL),
|
||||
exportThread(NULL),
|
||||
chans(0),
|
||||
configLoaded(false),
|
||||
active(false),
|
||||
lowQuality(false),
|
||||
dcHiPass(true),
|
||||
|
|
@ -1282,6 +1366,7 @@ class DivEngine {
|
|||
shallStopSched(false),
|
||||
endOfSong(false),
|
||||
consoleMode(false),
|
||||
disableStatusOut(false),
|
||||
extValuePresent(false),
|
||||
repeatPattern(false),
|
||||
metronome(false),
|
||||
|
|
@ -1297,6 +1382,7 @@ class DivEngine {
|
|||
midiIsDirectProgram(false),
|
||||
lowLatency(false),
|
||||
systemsRegistered(false),
|
||||
romExportsRegistered(false),
|
||||
hasLoadedSomething(false),
|
||||
midiOutClock(false),
|
||||
midiOutTime(false),
|
||||
|
|
@ -1351,7 +1437,9 @@ class DivEngine {
|
|||
haltOn(DIV_HALT_NONE),
|
||||
audioEngine(DIV_AUDIO_NULL),
|
||||
exportMode(DIV_EXPORT_MODE_ONE),
|
||||
exportFormat(DIV_EXPORT_FORMAT_S16),
|
||||
exportFadeOut(0.0),
|
||||
exportOutputs(2),
|
||||
cmdStreamInt(NULL),
|
||||
midiBaseChan(0),
|
||||
midiPoly(true),
|
||||
|
|
@ -1401,8 +1489,10 @@ class DivEngine {
|
|||
memset(pitchTable,0,4096*sizeof(int));
|
||||
memset(effectSlotMap,-1,4096*sizeof(short));
|
||||
memset(sysDefs,0,DIV_MAX_CHIP_DEFS*sizeof(void*));
|
||||
memset(romExportDefs,0,DIV_ROM_MAX*sizeof(void*));
|
||||
memset(walked,0,8192);
|
||||
memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*)));
|
||||
memset(exportChannelMask,1,DIV_MAX_CHANS*sizeof(bool));
|
||||
|
||||
for (int i=0; i<DIV_MAX_CHIP_DEFS; i++) {
|
||||
sysFileMapFur[i]=DIV_SYSTEM_NULL;
|
||||
|
|
|
|||
|
|
@ -20,18 +20,20 @@
|
|||
#include "engine.h"
|
||||
|
||||
#include "export/amigaValidation.h"
|
||||
#include "export/tiuna.h"
|
||||
|
||||
std::vector<DivROMExportOutput> DivEngine::buildROM(DivROMExportOptions sys) {
|
||||
DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
|
||||
DivROMExport* exporter=NULL;
|
||||
switch (sys) {
|
||||
case DIV_ROM_AMIGA_VALIDATION:
|
||||
exporter=new DivExportAmigaValidation;
|
||||
break;
|
||||
case DIV_ROM_TIUNA:
|
||||
exporter=new DivExportTiuna;
|
||||
break;
|
||||
default:
|
||||
exporter=new DivROMExport;
|
||||
break;
|
||||
}
|
||||
std::vector<DivROMExportOutput> ret=exporter->go(this);
|
||||
delete exporter;
|
||||
return ret;
|
||||
return exporter;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ class DivEngine;
|
|||
enum DivROMExportOptions {
|
||||
DIV_ROM_ABSTRACT=0,
|
||||
DIV_ROM_AMIGA_VALIDATION,
|
||||
DIV_ROM_ZSM,
|
||||
DIV_ROM_TIUNA,
|
||||
|
||||
DIV_ROM_MAX
|
||||
};
|
||||
|
|
@ -45,30 +47,61 @@ struct DivROMExportOutput {
|
|||
data(NULL) {}
|
||||
};
|
||||
|
||||
struct DivROMExportProgress {
|
||||
String name;
|
||||
float amount;
|
||||
};
|
||||
|
||||
class DivROMExport {
|
||||
protected:
|
||||
DivConfig conf;
|
||||
std::vector<DivROMExportOutput> output;
|
||||
void logAppend(String what);
|
||||
public:
|
||||
virtual std::vector<DivROMExportOutput> go(DivEngine* e);
|
||||
std::vector<String> exportLog;
|
||||
std::mutex logLock;
|
||||
|
||||
void setConf(DivConfig& c);
|
||||
virtual bool go(DivEngine* eng);
|
||||
virtual void abort();
|
||||
virtual void wait();
|
||||
std::vector<DivROMExportOutput>& getResult();
|
||||
virtual bool hasFailed();
|
||||
virtual bool isRunning();
|
||||
virtual DivROMExportProgress getProgress(int index=0);
|
||||
virtual ~DivROMExport() {}
|
||||
};
|
||||
|
||||
#define logAppendf(...) logAppend(fmt::sprintf(__VA_ARGS__))
|
||||
|
||||
enum DivROMExportReqPolicy {
|
||||
// exactly these chips.
|
||||
DIV_REQPOL_EXACT=0,
|
||||
// specified chips must be present but any amount of them is acceptable.
|
||||
DIV_REQPOL_ANY,
|
||||
// at least one of the specified chips.
|
||||
DIV_REQPOL_LAX
|
||||
};
|
||||
|
||||
struct DivROMExportDef {
|
||||
const char* name;
|
||||
const char* author;
|
||||
const char* description;
|
||||
DivSystem requisites[32];
|
||||
int requisitesLen;
|
||||
const char* fileType;
|
||||
const char* fileExt;
|
||||
std::vector<DivSystem> requisites;
|
||||
bool multiOutput;
|
||||
DivROMExportReqPolicy requisitePolicy;
|
||||
|
||||
DivROMExportDef(const char* n, const char* a, const char* d, std::initializer_list<DivSystem> req, bool multiOut):
|
||||
DivROMExportDef(const char* n, const char* a, const char* d, const char* ft, const char* fe, std::initializer_list<DivSystem> req, bool multiOut, DivROMExportReqPolicy reqPolicy):
|
||||
name(n),
|
||||
author(a),
|
||||
description(d),
|
||||
multiOutput(multiOut) {
|
||||
requisitesLen=0;
|
||||
memset(requisites,0,32*sizeof(DivSystem));
|
||||
for (DivSystem i: req) {
|
||||
requisites[requisitesLen++]=i;
|
||||
}
|
||||
fileType(ft),
|
||||
fileExt(fe),
|
||||
multiOutput(multiOut),
|
||||
requisitePolicy(reqPolicy) {
|
||||
requisites=req;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,42 @@
|
|||
#include "../export.h"
|
||||
#include "../../ta-log.h"
|
||||
|
||||
std::vector<DivROMExportOutput> DivROMExport::go(DivEngine* e) {
|
||||
bool DivROMExport::go(DivEngine* eng) {
|
||||
logW("what's this? the null ROM export?");
|
||||
return std::vector<DivROMExportOutput>();
|
||||
return false;
|
||||
}
|
||||
|
||||
void DivROMExport::abort() {
|
||||
}
|
||||
|
||||
std::vector<DivROMExportOutput>& DivROMExport::getResult() {
|
||||
return output;
|
||||
}
|
||||
|
||||
bool DivROMExport::hasFailed() {
|
||||
return true;
|
||||
}
|
||||
|
||||
DivROMExportProgress DivROMExport::getProgress(int index) {
|
||||
DivROMExportProgress ret;
|
||||
ret.name="";
|
||||
ret.amount=0.0f;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void DivROMExport::logAppend(String what) {
|
||||
logLock.lock();
|
||||
exportLog.push_back(what);
|
||||
logLock.unlock();
|
||||
}
|
||||
|
||||
void DivROMExport::wait() {
|
||||
}
|
||||
|
||||
bool DivROMExport::isRunning() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DivROMExport::setConf(DivConfig& c) {
|
||||
conf=c;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ struct SampleBookEntry {
|
|||
len(0) {}
|
||||
};
|
||||
|
||||
std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
|
||||
std::vector<DivROMExportOutput> ret;
|
||||
void DivExportAmigaValidation::run() {
|
||||
std::vector<WaveEntry> waves;
|
||||
std::vector<SampleBookEntry> sampleBook;
|
||||
unsigned int wavesDataPtr=0;
|
||||
|
|
@ -71,6 +70,7 @@ std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
|
|||
bool done=false;
|
||||
|
||||
// sample.bin
|
||||
logAppend("writing samples...");
|
||||
SafeWriter* sample=new SafeWriter;
|
||||
sample->init();
|
||||
for (int i=0; i<256; i++) {
|
||||
|
|
@ -80,6 +80,7 @@ std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
|
|||
if (sample->tell()&1) sample->writeC(0);
|
||||
|
||||
// seq.bin
|
||||
logAppend("making sequence...");
|
||||
SafeWriter* seq=new SafeWriter;
|
||||
seq->init();
|
||||
|
||||
|
|
@ -240,6 +241,7 @@ std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
|
|||
EXTERN_BUSY_END;
|
||||
|
||||
// wave.bin
|
||||
logAppend("writing wavetables...");
|
||||
SafeWriter* wave=new SafeWriter;
|
||||
wave->init();
|
||||
for (WaveEntry& i: waves) {
|
||||
|
|
@ -247,6 +249,7 @@ std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
|
|||
}
|
||||
|
||||
// sbook.bin
|
||||
logAppend("writing sample book...");
|
||||
SafeWriter* sbook=new SafeWriter;
|
||||
sbook->init();
|
||||
for (SampleBookEntry& i: sampleBook) {
|
||||
|
|
@ -256,6 +259,7 @@ std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
|
|||
}
|
||||
|
||||
// wbook.bin
|
||||
logAppend("writing wavetable book...");
|
||||
SafeWriter* wbook=new SafeWriter;
|
||||
wbook->init();
|
||||
for (WaveEntry& i: waves) {
|
||||
|
|
@ -266,12 +270,40 @@ std::vector<DivROMExportOutput> DivExportAmigaValidation::go(DivEngine* e) {
|
|||
}
|
||||
|
||||
// finish
|
||||
ret.reserve(5);
|
||||
ret.push_back(DivROMExportOutput("sbook.bin",sbook));
|
||||
ret.push_back(DivROMExportOutput("wbook.bin",wbook));
|
||||
ret.push_back(DivROMExportOutput("sample.bin",sample));
|
||||
ret.push_back(DivROMExportOutput("wave.bin",wave));
|
||||
ret.push_back(DivROMExportOutput("seq.bin",seq));
|
||||
output.reserve(5);
|
||||
output.push_back(DivROMExportOutput("sbook.bin",sbook));
|
||||
output.push_back(DivROMExportOutput("wbook.bin",wbook));
|
||||
output.push_back(DivROMExportOutput("sample.bin",sample));
|
||||
output.push_back(DivROMExportOutput("wave.bin",wave));
|
||||
output.push_back(DivROMExportOutput("seq.bin",seq));
|
||||
|
||||
return ret;
|
||||
logAppend("finished!");
|
||||
|
||||
running=false;
|
||||
}
|
||||
|
||||
bool DivExportAmigaValidation::go(DivEngine* eng) {
|
||||
e=eng;
|
||||
running=true;
|
||||
exportThread=new std::thread(&DivExportAmigaValidation::run,this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivExportAmigaValidation::wait() {
|
||||
if (exportThread!=NULL) {
|
||||
exportThread->join();
|
||||
delete exportThread;
|
||||
}
|
||||
}
|
||||
|
||||
void DivExportAmigaValidation::abort() {
|
||||
wait();
|
||||
}
|
||||
|
||||
bool DivExportAmigaValidation::isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
bool DivExportAmigaValidation::hasFailed() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,8 +19,18 @@
|
|||
|
||||
#include "../export.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
class DivExportAmigaValidation: public DivROMExport {
|
||||
DivEngine* e;
|
||||
std::thread* exportThread;
|
||||
bool running;
|
||||
void run();
|
||||
public:
|
||||
std::vector<DivROMExportOutput> go(DivEngine* e);
|
||||
bool go(DivEngine* e);
|
||||
bool isRunning();
|
||||
bool hasFailed();
|
||||
void abort();
|
||||
void wait();
|
||||
~DivExportAmigaValidation() {}
|
||||
};
|
||||
|
|
|
|||
664
src/engine/export/tiuna.cpp
Normal file
664
src/engine/export/tiuna.cpp
Normal file
|
|
@ -0,0 +1,664 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "tiuna.h"
|
||||
#include "../engine.h"
|
||||
#include "../ta-log.h"
|
||||
#include <fmt/printf.h>
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
struct TiunaNew {
|
||||
short pitch;
|
||||
signed char ins;
|
||||
signed char vol;
|
||||
short sync;
|
||||
TiunaNew():
|
||||
pitch(-1),
|
||||
ins(-1),
|
||||
vol(-1),
|
||||
sync(-1) {}
|
||||
};
|
||||
|
||||
struct TiunaLast {
|
||||
short pitch;
|
||||
signed char ins;
|
||||
signed char vol;
|
||||
int tick;
|
||||
bool forcePitch;
|
||||
TiunaLast():
|
||||
pitch(0),
|
||||
ins(0),
|
||||
vol(0),
|
||||
tick(1),
|
||||
forcePitch(true) {}
|
||||
};
|
||||
|
||||
struct TiunaCmd {
|
||||
signed char pitchChange;
|
||||
short pitchSet;
|
||||
signed char ins;
|
||||
signed char vol;
|
||||
short sync;
|
||||
short wait;
|
||||
TiunaCmd():
|
||||
pitchChange(-1),
|
||||
pitchSet(-1),
|
||||
ins(-1),
|
||||
vol(-1),
|
||||
sync(-1),
|
||||
wait(-1) {}
|
||||
};
|
||||
|
||||
struct TiunaBytes {
|
||||
unsigned char ch;
|
||||
int ticks;
|
||||
unsigned char size;
|
||||
unsigned char buf[16];
|
||||
friend bool operator==(const TiunaBytes& l, const TiunaBytes& r) {
|
||||
if (l.size!=r.size) return false;
|
||||
if (l.ticks!=r.ticks) return false;
|
||||
return memcmp(l.buf,r.buf,l.size)==0;
|
||||
}
|
||||
TiunaBytes(unsigned char c, int t, unsigned char s, std::initializer_list<unsigned char> b):
|
||||
ch(c),
|
||||
ticks(t),
|
||||
size(s) {
|
||||
// because C++14 does not support data() on initializer_list
|
||||
unsigned char p=0;
|
||||
for (unsigned char i: b) {
|
||||
buf[p++]=i;
|
||||
}
|
||||
}
|
||||
TiunaBytes():
|
||||
ch(0),
|
||||
ticks(0),
|
||||
size(0) {
|
||||
memset(buf,0,16);
|
||||
}
|
||||
};
|
||||
|
||||
struct TiunaMatch {
|
||||
int pos;
|
||||
int endPos;
|
||||
int size;
|
||||
int id;
|
||||
TiunaMatch(int p, int ep, int s, int _i):
|
||||
pos(p),
|
||||
endPos(ep),
|
||||
size(s),
|
||||
id(_i) {}
|
||||
TiunaMatch():
|
||||
pos(0),
|
||||
endPos(0),
|
||||
size(0),
|
||||
id(0) {}
|
||||
};
|
||||
|
||||
struct TiunaMatches {
|
||||
int bytesSaved;
|
||||
int length;
|
||||
int ticks;
|
||||
std::vector<int> pos;
|
||||
TiunaMatches():
|
||||
bytesSaved(INT32_MIN),
|
||||
length(0),
|
||||
ticks(0) {}
|
||||
};
|
||||
|
||||
static void writeCmd(std::vector<TiunaBytes>& cmds, TiunaCmd& cmd, unsigned char ch, int& lastWait, int fromTick, int toTick) {
|
||||
while (fromTick<toTick) {
|
||||
int val=MIN(toTick-fromTick,256);
|
||||
assert(val>0);
|
||||
if (lastWait!=val) {
|
||||
cmd.wait=val;
|
||||
lastWait=val;
|
||||
}
|
||||
TiunaBytes nbuf;
|
||||
unsigned char vcw1=0x80;
|
||||
unsigned char vcw2=0;
|
||||
unsigned char vcwLen=0;
|
||||
unsigned char nlen=0;
|
||||
nbuf.ch=ch;
|
||||
nbuf.ticks=val;
|
||||
if (cmd.sync>=0) {
|
||||
nbuf.buf[nlen++]=0b00111110;
|
||||
nbuf.buf[nlen++]=cmd.sync;
|
||||
}
|
||||
if (cmd.wait>=17) {
|
||||
nbuf.buf[nlen++]=0b00111111;
|
||||
nbuf.buf[nlen++]=cmd.wait-1;
|
||||
}
|
||||
if (cmd.pitchChange>=0) {
|
||||
nbuf.buf[nlen++]=0b01000000|cmd.pitchChange;
|
||||
}
|
||||
if (cmd.pitchSet>=0) {
|
||||
nbuf.buf[nlen++]=0b01100000|(cmd.pitchSet>>8);
|
||||
nbuf.buf[nlen++]=cmd.pitchSet&0xff;
|
||||
}
|
||||
if (cmd.vol>=1) {
|
||||
vcw1|=0x40|cmd.vol;
|
||||
vcwLen++;
|
||||
}
|
||||
if (cmd.ins>=0) {
|
||||
vcw1|=0x20;
|
||||
if (vcwLen==0) vcw1|=cmd.ins;
|
||||
else vcw2=cmd.ins;
|
||||
vcwLen++;
|
||||
}
|
||||
if (cmd.wait>=1 && cmd.wait<17) {
|
||||
unsigned char val=cmd.wait-1;
|
||||
vcw1|=0x10;
|
||||
if (vcwLen==0) vcw1|=val;
|
||||
else if (vcwLen==1) vcw2=val;
|
||||
else vcw2|=(val<<4);
|
||||
vcwLen++;
|
||||
}
|
||||
nbuf.buf[nlen++]=vcw1;
|
||||
if (vcwLen>=2) nbuf.buf[nlen++]=vcw2;
|
||||
nbuf.size=nlen;
|
||||
cmds.push_back(nbuf);
|
||||
cmd=TiunaCmd();
|
||||
fromTick+=val;
|
||||
}
|
||||
}
|
||||
|
||||
void DivExportTiuna::run() {
|
||||
int loopOrder, loopOrderRow, loopEnd;
|
||||
int tick=0;
|
||||
SafeWriter* w;
|
||||
std::map<int,TiunaCmd> allCmds[2];
|
||||
|
||||
// config
|
||||
String baseLabel=conf.getString("baseLabel","song");
|
||||
int firstBankSize=conf.getInt("firstBankSize",3072);
|
||||
int otherBankSize=conf.getInt("otherBankSize",4096-48);
|
||||
int tiaIdx=conf.getInt("sysToExport",-1);
|
||||
|
||||
e->stop();
|
||||
e->repeatPattern=false;
|
||||
e->shallStop=false;
|
||||
e->setOrder(0);
|
||||
e->synchronizedSoft([&]() {
|
||||
// determine loop point
|
||||
// bool stopped=false;
|
||||
loopOrder=0;
|
||||
loopOrderRow=0;
|
||||
loopEnd=0;
|
||||
e->walkSong(loopOrder,loopOrderRow,loopEnd);
|
||||
logAppendf("loop point: %d %d",loopOrder,loopOrderRow);
|
||||
|
||||
w=new SafeWriter;
|
||||
w->init();
|
||||
|
||||
if (tiaIdx<0 || tiaIdx>=e->song.systemLen) {
|
||||
tiaIdx=-1;
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
if (e->song.system[i]==DIV_SYSTEM_TIA) {
|
||||
tiaIdx=i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (tiaIdx<0) {
|
||||
logAppend("ERROR: selected TIA system not found");
|
||||
failed=true;
|
||||
running=false;
|
||||
return;
|
||||
}
|
||||
} else if (e->song.system[tiaIdx]!=DIV_SYSTEM_TIA) {
|
||||
logAppend("ERROR: selected chip is not a TIA!");
|
||||
failed=true;
|
||||
running=false;
|
||||
return;
|
||||
}
|
||||
|
||||
e->disCont[tiaIdx].dispatch->toggleRegisterDump(true);
|
||||
|
||||
// write patterns
|
||||
// bool writeLoop=false;
|
||||
logAppend("recording sequence...");
|
||||
bool done=false;
|
||||
e->playSub(false);
|
||||
|
||||
// int loopTick=-1;
|
||||
TiunaLast last[2];
|
||||
TiunaNew news[2];
|
||||
while (!done) {
|
||||
// TODO implement loop
|
||||
// if (loopTick<0 && loopOrder==curOrder && loopOrderRow==curRow
|
||||
// && (ticks-((tempoAccum+virtualTempoN)/virtualTempoD))<=0
|
||||
// ) {
|
||||
// writeLoop=true;
|
||||
// loopTick=tick;
|
||||
// // invalidate last register state so it always force an absolute write after loop
|
||||
// for (int i=0; i<2; i++) {
|
||||
// last[i]=TiunaLast();
|
||||
// last[i].pitch=-1;
|
||||
// last[i].ins=-1;
|
||||
// last[i].vol=-1;
|
||||
// }
|
||||
// }
|
||||
if (e->nextTick(false,true) || !e->playing) {
|
||||
// stopped=!playing;
|
||||
done=true;
|
||||
break;
|
||||
}
|
||||
for (int i=0; i<2; i++) {
|
||||
news[i]=TiunaNew();
|
||||
}
|
||||
// get register dumps
|
||||
std::vector<DivRegWrite>& writes=e->disCont[tiaIdx].dispatch->getRegisterWrites();
|
||||
for (const DivRegWrite& i: writes) {
|
||||
switch (i.addr) {
|
||||
case 0xfffe0000:
|
||||
case 0xfffe0001:
|
||||
news[i.addr&1].pitch=i.val;
|
||||
break;
|
||||
case 0xfffe0002:
|
||||
news[0].sync=i.val;
|
||||
break;
|
||||
case 0x15:
|
||||
case 0x16:
|
||||
news[i.addr-0x15].ins=i.val;
|
||||
break;
|
||||
case 0x19:
|
||||
case 0x1a:
|
||||
news[i.addr-0x19].vol=i.val;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
writes.clear();
|
||||
// collect changes
|
||||
for (int i=0; i<2; i++) {
|
||||
TiunaCmd cmds;
|
||||
bool hasCmd=false;
|
||||
if (news[i].pitch>=0 && (last[i].forcePitch || news[i].pitch!=last[i].pitch)) {
|
||||
int dt=news[i].pitch-last[i].pitch;
|
||||
if (!last[i].forcePitch && abs(dt)<=16) {
|
||||
if (dt<0) cmds.pitchChange=15-dt;
|
||||
else cmds.pitchChange=dt-1;
|
||||
}
|
||||
else cmds.pitchSet=news[i].pitch;
|
||||
last[i].pitch=news[i].pitch;
|
||||
last[i].forcePitch=false;
|
||||
hasCmd=true;
|
||||
}
|
||||
if (news[i].ins>=0 && news[i].ins!=last[i].ins) {
|
||||
cmds.ins=news[i].ins;
|
||||
last[i].ins=news[i].ins;
|
||||
hasCmd=true;
|
||||
}
|
||||
if (news[i].vol>=0 && news[i].vol!=last[i].vol) {
|
||||
cmds.vol=(news[i].vol-last[i].vol)&0xf;
|
||||
last[i].vol=news[i].vol;
|
||||
hasCmd=true;
|
||||
}
|
||||
if (news[i].sync>=0) {
|
||||
cmds.sync=news[i].sync;
|
||||
hasCmd=true;
|
||||
}
|
||||
if (hasCmd) allCmds[i][tick]=cmds;
|
||||
}
|
||||
e->cmdStream.clear();
|
||||
tick++;
|
||||
}
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
e->disCont[i].dispatch->getRegisterWrites().clear();
|
||||
e->disCont[i].dispatch->toggleRegisterDump(false);
|
||||
}
|
||||
|
||||
e->remainingLoops=-1;
|
||||
e->playing=false;
|
||||
e->freelance=false;
|
||||
e->extValuePresent=false;
|
||||
});
|
||||
|
||||
if (failed) return;
|
||||
|
||||
// render commands
|
||||
logAppend("rendering commands...");
|
||||
std::vector<TiunaBytes> renderedCmds;
|
||||
w->writeText(fmt::format(
|
||||
"; Generated by Furnace " DIV_VERSION "\n"
|
||||
"; Name: {}\n"
|
||||
"; Author: {}\n"
|
||||
"; Album: {}\n"
|
||||
"; Subsong #{}: {}\n\n",
|
||||
e->song.name,e->song.author,e->song.category,e->curSubSongIndex+1,e->curSubSong->name
|
||||
));
|
||||
for (int i=0; i<2; i++) {
|
||||
TiunaCmd lastCmd;
|
||||
int lastTick=0;
|
||||
int lastWait=0;
|
||||
// bool looped=false;
|
||||
for (auto& kv: allCmds[i]) {
|
||||
// if (!looped && !stopped && loopTick>=0 && kv.first>=loopTick) {
|
||||
// writeCmd(w,&lastCmd,&lastWait,loopTick-lastTick);
|
||||
// w->writeText(".loop\n");
|
||||
// lastTick=loopTick;
|
||||
// looped=true;
|
||||
// }
|
||||
writeCmd(renderedCmds,lastCmd,i,lastWait,lastTick,kv.first);
|
||||
lastTick=kv.first;
|
||||
lastCmd=kv.second;
|
||||
}
|
||||
writeCmd(renderedCmds,lastCmd,i,lastWait,lastTick,tick);
|
||||
// if (stopped || loopTick<0) w->writeText(".loop\n db 0\n");
|
||||
}
|
||||
// compress commands
|
||||
std::vector<TiunaMatch> confirmedMatches;
|
||||
std::vector<int> callTicks;
|
||||
int cmId=0;
|
||||
int cmdSize=renderedCmds.size();
|
||||
bool* processed=new bool[cmdSize];
|
||||
memset(processed,0,cmdSize*sizeof(bool));
|
||||
logAppend("compressing! this may take a while.");
|
||||
int maxCmId=(MAX(firstBankSize/1024,1))*256;
|
||||
int lastMaxPMVal=100000;
|
||||
logAppendf("max cmId: %d",maxCmId);
|
||||
logAppendf("commands: %d",cmdSize);
|
||||
while (firstBankSize>768 && cmId<maxCmId) {
|
||||
if (mustAbort) {
|
||||
logAppend("aborted!");
|
||||
failed=true;
|
||||
running=false;
|
||||
delete[] processed;
|
||||
return;
|
||||
}
|
||||
|
||||
float theOtherSide=pow(1.0/float(MAX(1,lastMaxPMVal)),0.2)*0.98;
|
||||
progress[0].amount=theOtherSide+(1.0-theOtherSide)*((float)cmId/(float)maxCmId);
|
||||
|
||||
logAppendf("start CM %04x...",cmId);
|
||||
std::map<int,TiunaMatches> potentialMatches;
|
||||
for (int i=0; i<cmdSize-1;) {
|
||||
// continue and skip if it's part of previous confirmed matches
|
||||
while (i<cmdSize-1 && processed[i]) i++;
|
||||
if (i>=cmdSize-1) break;
|
||||
progress[1].amount=(float)i/(float)(cmdSize-1);
|
||||
std::vector<TiunaMatch> match;
|
||||
int ch=renderedCmds[i].ch;
|
||||
for (int j=i+1; j<cmdSize;) {
|
||||
if (processed[i]) break;
|
||||
//while (j<cmdSize && processed[i]) j++;
|
||||
if (j>=cmdSize) break;
|
||||
int k=0;
|
||||
int ticks=0;
|
||||
int size=0;
|
||||
while (
|
||||
(i+k)<j && (i+k)<cmdSize && (j+k)<cmdSize &&
|
||||
(ticks+renderedCmds[i+k].ticks)<=256 &&
|
||||
// match runs can't cross channels
|
||||
// as channel end command would be insterted there later
|
||||
renderedCmds[i+k].ch==ch &&
|
||||
renderedCmds[j+k].ch==ch &&
|
||||
renderedCmds[i+k]==renderedCmds[j+k] &&
|
||||
!processed[i+k] && !processed[j+k]
|
||||
) {
|
||||
ticks+=renderedCmds[i+k].ticks;
|
||||
size+=renderedCmds[i+k].size;
|
||||
k++;
|
||||
}
|
||||
if (size>2) match.push_back(TiunaMatch(j,j+k,size,0));
|
||||
if (k==0) k++;
|
||||
j+=k;
|
||||
}
|
||||
if (match.empty()) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
// find a length that results in most bytes saved
|
||||
TiunaMatches matches;
|
||||
int curSize=0;
|
||||
int curLength=1;
|
||||
int curTicks=0;
|
||||
while (true) {
|
||||
int bytesSaved=-4;
|
||||
bool found=false;
|
||||
for (const TiunaMatch& j: match) {
|
||||
if ((j.endPos-j.pos)>=curLength) {
|
||||
if (!found) {
|
||||
found=true;
|
||||
curSize+=renderedCmds[i+curLength-1].size;
|
||||
curTicks+=renderedCmds[i+curLength-1].ticks;
|
||||
}
|
||||
bytesSaved+=curSize-2;
|
||||
}
|
||||
}
|
||||
if (!found) break;
|
||||
if (bytesSaved>matches.bytesSaved) {
|
||||
matches.length=curLength;
|
||||
matches.bytesSaved=bytesSaved;
|
||||
matches.ticks=curTicks;
|
||||
}
|
||||
curLength++;
|
||||
}
|
||||
if (matches.bytesSaved>0) {
|
||||
matches.pos.push_back(i);
|
||||
for (const TiunaMatch& j: match) {
|
||||
if ((j.endPos-j.pos)>=matches.length) {
|
||||
matches.pos.push_back(j.pos);
|
||||
}
|
||||
}
|
||||
potentialMatches[i]=matches;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (potentialMatches.empty()) {
|
||||
logAppend("potentialMatches is empty");
|
||||
break;
|
||||
}
|
||||
int maxPMIdx=0;
|
||||
int maxPMVal=0;
|
||||
for (const auto& i: potentialMatches) {
|
||||
if (i.second.bytesSaved>maxPMVal) {
|
||||
maxPMVal=i.second.bytesSaved;
|
||||
maxPMIdx=i.first;
|
||||
}
|
||||
}
|
||||
int maxPMLen=potentialMatches[maxPMIdx].length;
|
||||
for (const int i: potentialMatches[maxPMIdx].pos) {
|
||||
confirmedMatches.push_back({i,i+maxPMLen,0,cmId});
|
||||
memset(processed+i,1,maxPMLen);
|
||||
//std::fill(processed.begin()+i,processed.begin()+(i+maxPMLen),true);
|
||||
}
|
||||
callTicks.push_back(potentialMatches[maxPMIdx].ticks);
|
||||
logAppendf("CM %04x added: pos=%d,len=%d,matches=%d,saved=%d",cmId,maxPMIdx,maxPMLen,potentialMatches[maxPMIdx].pos.size(),maxPMVal);
|
||||
lastMaxPMVal=maxPMVal;
|
||||
cmId++;
|
||||
}
|
||||
progress[0].amount=1.0f;
|
||||
progress[1].amount=1.0f;
|
||||
logAppend("generating data...");
|
||||
delete[] processed;
|
||||
std::sort(confirmedMatches.begin(),confirmedMatches.end(),[](const TiunaMatch& l, const TiunaMatch& r){
|
||||
return l.pos<r.pos;
|
||||
});
|
||||
// ignore last call IDs >256 that don't fill up a page
|
||||
// as they tends to increase the final size due to page alignment
|
||||
int cmIdLen=cmId>256?(cmId&~255):cmId;
|
||||
// overlap check
|
||||
for (int i=1; i<(int)confirmedMatches.size(); i++) {
|
||||
if (confirmedMatches[i-1].endPos<=confirmedMatches[i].pos) continue;
|
||||
logAppend("ERROR: impossible overlap found in matches list, please report");
|
||||
failed=true;
|
||||
running=false;
|
||||
return;
|
||||
}
|
||||
SafeWriter dbg;
|
||||
dbg.init();
|
||||
dbg.writeText(fmt::format("renderedCmds size={}\n",renderedCmds.size()));
|
||||
for (const auto& i: confirmedMatches) {
|
||||
dbg.writeText(fmt::format("pos={},end={},id={}\n",i.pos,i.endPos,i.id,i.size));
|
||||
}
|
||||
|
||||
// write commands
|
||||
int totalSize=0;
|
||||
int cnt=cmIdLen;
|
||||
w->writeText(fmt::format(" .section {0}_bank0\n .align $100\n{0}_calltable",baseLabel));
|
||||
while (cnt>0) {
|
||||
int cnt2=MIN(cnt,256);
|
||||
w->writeText("\n .byte ");
|
||||
for (int j=0; j<cnt2; j++) {
|
||||
w->writeText(fmt::format("<{}_c{},",baseLabel,cmIdLen-cnt+j));
|
||||
}
|
||||
for (int j=cnt2; j<256; j++) {
|
||||
w->writeText("0,");
|
||||
}
|
||||
w->seek(-1,SEEK_CUR);
|
||||
w->writeText("\n .byte ");
|
||||
for (int j=0; j<cnt2; j++) {
|
||||
w->writeText(fmt::format(">{}_c{},",baseLabel,cmIdLen-cnt+j));
|
||||
}
|
||||
for (int j=cnt2; j<256; j++) {
|
||||
w->writeText("0,");
|
||||
}
|
||||
w->seek(-1,SEEK_CUR);
|
||||
w->writeText("\n .byte ");
|
||||
for (int j=0; j<cnt2; j++) {
|
||||
w->writeText(fmt::format("{}_c{}>>13,",baseLabel,cmIdLen-cnt+j));
|
||||
}
|
||||
for (int j=cnt2; j<256; j++) {
|
||||
w->writeText("0,");
|
||||
}
|
||||
w->seek(-1,SEEK_CUR);
|
||||
w->writeText("\n .byte ");
|
||||
for (int j=0; j<cnt2; j++) {
|
||||
w->writeText(fmt::format("{},",callTicks[cmIdLen-cnt+j]&0xff));
|
||||
}
|
||||
w->seek(-1,SEEK_CUR);
|
||||
totalSize+=768+cnt2;
|
||||
cnt-=cnt2;
|
||||
}
|
||||
w->writeC('\n');
|
||||
if (totalSize>firstBankSize) {
|
||||
logAppend("ERROR: first bank is not large enough to contain call table");
|
||||
failed=true;
|
||||
running=false;
|
||||
return;
|
||||
}
|
||||
|
||||
int curBank=0;
|
||||
int bankSize=totalSize;
|
||||
int maxBankSize=firstBankSize;
|
||||
int curCh=-1;
|
||||
std::vector<bool> callVisited=std::vector<bool>(cmIdLen,false);
|
||||
auto cmIter=confirmedMatches.begin();
|
||||
for (int i=0; i<(int)renderedCmds.size(); i++) {
|
||||
int writeCall=-1;
|
||||
TiunaBytes cmd=renderedCmds[i];
|
||||
if (cmIter!=confirmedMatches.end() && i==cmIter->pos) {
|
||||
if (cmIter->id<cmIdLen) {
|
||||
if (callVisited[cmIter->id]) {
|
||||
unsigned char idLo=cmIter->id&0xff;
|
||||
unsigned char idHi=cmIter->id>>8;
|
||||
cmd=TiunaBytes(cmd.ch,0,2,{idHi,idLo});
|
||||
i=cmIter->endPos-1;
|
||||
} else {
|
||||
writeCall=cmIter->id;
|
||||
callVisited[writeCall]=true;
|
||||
}
|
||||
}
|
||||
cmIter++;
|
||||
}
|
||||
if (cmd.ch!=curCh) {
|
||||
if (curCh>=0) {
|
||||
w->writeText(" .text x\"e0\"\n");
|
||||
totalSize++;
|
||||
bankSize++;
|
||||
}
|
||||
if (bankSize+cmd.size>=maxBankSize) {
|
||||
maxBankSize=otherBankSize;
|
||||
curBank++;
|
||||
w->writeText(fmt::format(" .endsection\n\n .section {}_bank{}",baseLabel,curBank));
|
||||
bankSize=0;
|
||||
}
|
||||
w->writeText(fmt::format("\n{}_ch{}\n",baseLabel,cmd.ch));
|
||||
curCh=cmd.ch;
|
||||
}
|
||||
if (bankSize+cmd.size+1>=maxBankSize) {
|
||||
maxBankSize=otherBankSize;
|
||||
curBank++;
|
||||
w->writeText(fmt::format(" .text x\"c0\"\n .endsection\n\n .section {}_bank{}\n",baseLabel,curBank));
|
||||
totalSize++;
|
||||
bankSize=0;
|
||||
}
|
||||
if (writeCall>=0) {
|
||||
w->writeText(fmt::format("{}_c{}\n",baseLabel,writeCall));
|
||||
}
|
||||
w->writeText(" .text x\"");
|
||||
for (int j=0; j<cmd.size; j++) {
|
||||
w->writeText(fmt::format("{:02x}",cmd.buf[j]));
|
||||
}
|
||||
w->writeText("\"\n");
|
||||
totalSize+=cmd.size;
|
||||
bankSize+=cmd.size;
|
||||
}
|
||||
w->writeText(" .text x\"e0\"\n .endsection\n");
|
||||
totalSize++;
|
||||
logAppendf("total size: %d bytes (%d banks)",totalSize,curBank+1);
|
||||
|
||||
output.push_back(DivROMExportOutput("export.asm",w));
|
||||
|
||||
logAppend("finished!");
|
||||
|
||||
running=false;
|
||||
}
|
||||
|
||||
bool DivExportTiuna::go(DivEngine* eng) {
|
||||
progress[0].name="Compression";
|
||||
progress[0].amount=0.0f;
|
||||
progress[1].name="Confirmed Matches";
|
||||
progress[1].amount=0.0f;
|
||||
|
||||
e=eng;
|
||||
running=true;
|
||||
failed=false;
|
||||
mustAbort=false;
|
||||
exportThread=new std::thread(&DivExportTiuna::run,this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivExportTiuna::wait() {
|
||||
if (exportThread!=NULL) {
|
||||
exportThread->join();
|
||||
delete exportThread;
|
||||
}
|
||||
}
|
||||
|
||||
void DivExportTiuna::abort() {
|
||||
mustAbort=true;
|
||||
wait();
|
||||
}
|
||||
|
||||
bool DivExportTiuna::isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
bool DivExportTiuna::hasFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
DivROMExportProgress DivExportTiuna::getProgress(int index) {
|
||||
if (index<0 || index>2) return progress[2];
|
||||
return progress[index];
|
||||
}
|
||||
38
src/engine/export/tiuna.h
Normal file
38
src/engine/export/tiuna.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "../export.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
class DivExportTiuna: public DivROMExport {
|
||||
DivEngine* e;
|
||||
std::thread* exportThread;
|
||||
DivROMExportProgress progress[3];
|
||||
bool running, failed, mustAbort;
|
||||
void run();
|
||||
public:
|
||||
bool go(DivEngine* e);
|
||||
bool isRunning();
|
||||
bool hasFailed();
|
||||
void abort();
|
||||
void wait();
|
||||
DivROMExportProgress getProgress(int index=0);
|
||||
~DivExportTiuna() {}
|
||||
};
|
||||
63
src/engine/exportDef.cpp
Normal file
63
src/engine/exportDef.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "engine.h"
|
||||
|
||||
DivROMExportDef* DivEngine::romExportDefs[DIV_ROM_MAX];
|
||||
|
||||
const DivROMExportDef* DivEngine::getROMExportDef(DivROMExportOptions opt) {
|
||||
return romExportDefs[opt];
|
||||
}
|
||||
|
||||
void DivEngine::registerROMExports() {
|
||||
logD("registering ROM exports...");
|
||||
|
||||
romExportDefs[DIV_ROM_AMIGA_VALIDATION]=new DivROMExportDef(
|
||||
"Amiga Validation", "tildearrow",
|
||||
"a test export for ensuring Amiga emulation is accurate. do not use!",
|
||||
NULL, NULL,
|
||||
{DIV_SYSTEM_AMIGA},
|
||||
true, DIV_REQPOL_EXACT
|
||||
);
|
||||
|
||||
romExportDefs[DIV_ROM_ZSM]=new DivROMExportDef(
|
||||
"Commander X16 ZSM", "ZeroByteOrg and MooingLemur",
|
||||
"Commander X16 Zsound Music File.\n"
|
||||
"for use with Melodius, Calliope and/or ZSMKit:\n"
|
||||
"- https://github.com/mooinglemur/zsmkit (development)\n"
|
||||
"- https://github.com/mooinglemur/melodius (player)\n"
|
||||
"- https://github.com/ZeroByteOrg/calliope (player)\n",
|
||||
"ZSM file", ".zsm",
|
||||
{
|
||||
DIV_SYSTEM_YM2151, DIV_SYSTEM_VERA
|
||||
},
|
||||
false, DIV_REQPOL_LAX
|
||||
);
|
||||
|
||||
romExportDefs[DIV_ROM_TIUNA]=new DivROMExportDef(
|
||||
"Atari 2600 (TIunA)", "Natt Akuma",
|
||||
"advanced driver with software tuning support.\n"
|
||||
"see https://github.com/AYCEdemo/twin-tiuna for code.",
|
||||
"assembly files", ".asm",
|
||||
{
|
||||
DIV_SYSTEM_TIA
|
||||
},
|
||||
false, DIV_REQPOL_ANY
|
||||
);
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ bool DivEngine::load(unsigned char* f, size_t slen, const char* nameHint) {
|
|||
len=slen;
|
||||
}
|
||||
|
||||
// step 2: try loading as .fur or .dmf
|
||||
// step 2: try loading as .fur, .dmf, or another magic-ful format
|
||||
if (memcmp(file,DIV_DMF_MAGIC,16)==0) {
|
||||
return loadDMF(file,len);
|
||||
} else if (memcmp(file,DIV_FTM_MAGIC,18)==0) {
|
||||
|
|
@ -152,10 +152,22 @@ bool DivEngine::load(unsigned char* f, size_t slen, const char* nameHint) {
|
|||
return loadFur(file,len,DIV_FUR_VARIANT_B);
|
||||
} else if (memcmp(file,DIV_FC13_MAGIC,4)==0 || memcmp(file,DIV_FC14_MAGIC,4)==0) {
|
||||
return loadFC(file,len);
|
||||
} else if (memcmp(file,DIV_TFM_MAGIC,8)==0) {
|
||||
return loadTFMv2(file,len);
|
||||
} else if (memcmp(file,DIV_IT_MAGIC,4)==0) {
|
||||
return loadIT(file,len);
|
||||
} else if (len>=48) {
|
||||
if (memcmp(&file[0x2c],DIV_S3M_MAGIC,4)==0) {
|
||||
return loadS3M(file,len);
|
||||
} else if (memcmp(file,DIV_XM_MAGIC,17)==0) {
|
||||
return loadXM(file,len);
|
||||
}
|
||||
}
|
||||
|
||||
// step 3: try loading as .mod
|
||||
if (loadMod(file,len)) {
|
||||
// step 3: try loading as .mod or TFEv1 (if the file extension matches)
|
||||
if (extS==".tfe") {
|
||||
return loadTFMv1(file,len);
|
||||
} else if (loadMod(file,len)) {
|
||||
delete[] f;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ struct NotZlibException {
|
|||
#define DIV_FC13_MAGIC "SMOD"
|
||||
#define DIV_FC14_MAGIC "FC14"
|
||||
#define DIV_S3M_MAGIC "SCRM"
|
||||
#define DIV_XM_MAGIC "Extended Module: "
|
||||
#define DIV_IT_MAGIC "IMPM"
|
||||
#define DIV_TFM_MAGIC "TFMfmtV2"
|
||||
|
||||
#define DIV_FUR_MAGIC_DS0 "Furnace-B module"
|
||||
|
||||
|
|
@ -60,3 +63,34 @@ enum DivFurVariants: int {
|
|||
DIV_FUR_VARIANT_VANILLA=0,
|
||||
DIV_FUR_VARIANT_B=1,
|
||||
};
|
||||
|
||||
// MIDI-related
|
||||
struct midibank_t {
|
||||
String name;
|
||||
uint8_t bankMsb,
|
||||
bankLsb;
|
||||
};
|
||||
|
||||
// Reused patch data structures
|
||||
|
||||
// SBI and some other OPL containers
|
||||
|
||||
struct sbi_t {
|
||||
uint8_t Mcharacteristics,
|
||||
Ccharacteristics,
|
||||
Mscaling_output,
|
||||
Cscaling_output,
|
||||
Meg_AD,
|
||||
Ceg_AD,
|
||||
Meg_SR,
|
||||
Ceg_SR,
|
||||
Mwave,
|
||||
Cwave,
|
||||
FeedConnect;
|
||||
};
|
||||
|
||||
//bool stringNotBlank(String& str);
|
||||
// detune needs extra translation from register to furnace format
|
||||
//uint8_t fmDtRegisterToFurnace(uint8_t&& dtNative);
|
||||
|
||||
//void readSbiOpData(sbi_t& sbi, SafeReader& reader);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
// portions apparently taken from FamiTracker source, which is under GPLv2+
|
||||
|
||||
// TODO:
|
||||
// - audit for CVEs
|
||||
// - format code?
|
||||
|
||||
#include "fileOpsCommon.h"
|
||||
|
|
@ -258,6 +257,7 @@ const int eff_conversion_050[][2] = {
|
|||
};
|
||||
|
||||
constexpr int ftEffectMapSize = sizeof(ftEffectMap) / sizeof(int);
|
||||
constexpr int eftEffectMapSize = sizeof(eftEffectMap) / sizeof(int);
|
||||
|
||||
int convertMacros2A03[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY};
|
||||
int convertMacrosVRC6[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY};
|
||||
|
|
@ -434,7 +434,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
bool hasSequence[256][8];
|
||||
unsigned char sequenceIndex[256][8];
|
||||
unsigned char macro_types[256][8];
|
||||
std::vector<std::vector<DivInstrumentMacro>> macros;
|
||||
std::vector<DivInstrumentMacro> macros[256];
|
||||
std::vector<String> encounteredBlocks;
|
||||
unsigned char map_channels[DIV_MAX_CHANS];
|
||||
unsigned int hilightA = 4;
|
||||
unsigned int hilightB = 16;
|
||||
|
|
@ -457,13 +458,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
memset(map_channels, 0xfe, DIV_MAX_CHANS * sizeof(unsigned char));
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
std::vector<DivInstrumentMacro> mac;
|
||||
|
||||
for (int j = 0; j < 8; j++) {
|
||||
mac.push_back(DivInstrumentMacro(DIV_MACRO_VOL));
|
||||
macros[i].push_back(DivInstrumentMacro(DIV_MACRO_VOL));
|
||||
}
|
||||
|
||||
macros.push_back(mac);
|
||||
}
|
||||
|
||||
if (!reader.seek((dnft && dnft_sig) ? 21 : 18, SEEK_SET)) {
|
||||
|
|
@ -482,7 +479,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
return false;
|
||||
}
|
||||
|
||||
for (DivSubSong* i : ds.subsong) {
|
||||
for (DivSubSong* i: ds.subsong) {
|
||||
i->clearData();
|
||||
delete i;
|
||||
}
|
||||
|
|
@ -501,13 +498,30 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
|
||||
// not the end
|
||||
reader.seek(-3, SEEK_CUR);
|
||||
if (!reader.seek(-3, SEEK_CUR)) {
|
||||
logE("couldn't seek back by 3!");
|
||||
lastError = "couldn't seek back by 3";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
blockName = reader.readString(16);
|
||||
unsigned int blockVersion = (unsigned int)reader.readI();
|
||||
unsigned int blockSize = (unsigned int)reader.readI();
|
||||
size_t blockStart = reader.tell();
|
||||
|
||||
logD("reading block %s (version %d, %d bytes, position %x)", blockName, blockVersion, blockSize, reader.tell());
|
||||
|
||||
for (String& i: encounteredBlocks) {
|
||||
if (blockName==i) {
|
||||
logE("duplicate block %s!",blockName);
|
||||
lastError = "duplicate block "+blockName;
|
||||
ds.unload();
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
encounteredBlocks.push_back(blockName);
|
||||
|
||||
if (blockName == "PARAMS") {
|
||||
// versions 7-9 don't change anything?
|
||||
CHECK_BLOCK_VERSION(9);
|
||||
|
|
@ -529,6 +543,13 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
|
||||
tchans = reader.readI();
|
||||
|
||||
if (tchans>=DIV_MAX_CHANS) {
|
||||
logE("invalid channel count! %d",tchans);
|
||||
lastError = "invalid channel count";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tchans == 5) {
|
||||
expansions = 0; // This is strange. Sometimes expansion chip is set to 0xFF in files
|
||||
}
|
||||
|
|
@ -538,12 +559,15 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
if (blockVersion >= 7) {
|
||||
// advanced Hz control
|
||||
int controlType = reader.readI();
|
||||
switch (controlType) {
|
||||
int readHz=reader.readI();
|
||||
if (readHz<=0) {
|
||||
customHz=60.0;
|
||||
} else switch (controlType) {
|
||||
case 1:
|
||||
customHz = 1000000.0 / (double)reader.readI();
|
||||
customHz = 1000000.0 / (double)readHz;
|
||||
break;
|
||||
default:
|
||||
reader.readI();
|
||||
logW("unsupported tick rate control type %d",controlType);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -553,6 +577,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
customHz = reader.readI();
|
||||
}
|
||||
|
||||
logV("before clamp: %f",customHz);
|
||||
|
||||
if (customHz>1000.0) customHz=1000.0;
|
||||
|
||||
unsigned int newVibrato = 0;
|
||||
bool sweepReset = false;
|
||||
unsigned int speedSplitPoint = 0;
|
||||
|
|
@ -576,6 +604,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
|
||||
if ((expansions & 16) && blockVersion >= 5) { // N163 channels
|
||||
n163Chans = reader.readI();
|
||||
if (n163Chans<1 || n163Chans>=9) {
|
||||
logE("invalid Namco 163 channel count! %d",n163Chans);
|
||||
lastError = "invalid Namco 163 channel count";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (blockVersion >= 6) {
|
||||
speedSplitPoint = reader.readI();
|
||||
|
|
@ -779,6 +813,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
}
|
||||
if (calcChans != tchans) {
|
||||
// TODO: would ignore trigger CVE? too bad if so!
|
||||
if (!eft || (eft && (expansions & 8) == 0)) // ignore since I have no idea how to tell apart E-FT versions which do or do not have PCM chan. Yes, this may lead to all the righer channels to be shifted but at least you still get note data!
|
||||
{
|
||||
logE("channel counts do not match! %d != %d", tchans, calcChans);
|
||||
|
|
@ -788,17 +823,20 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
}
|
||||
if (tchans > DIV_MAX_CHANS) {
|
||||
tchans = DIV_MAX_CHANS;
|
||||
logW("too many channels!");
|
||||
logE("too many channels!");
|
||||
lastError = "too many channels";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (blockVersion == 9 && blockSize - (reader.tell() - blockStart) == 2) // weird
|
||||
{
|
||||
reader.seek(2, SEEK_CUR);
|
||||
if (!reader.seek(2, SEEK_CUR)) {
|
||||
logE("could not weird-seek by 2!");
|
||||
lastError = "could not weird-seek by 2";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (eft) {
|
||||
// reader.seek(8,SEEK_CUR);
|
||||
}
|
||||
|
||||
} else if (blockName == "INFO") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
ds.name = reader.readString(32);
|
||||
|
|
@ -831,6 +869,13 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
for (int j = 0; j <= totalSongs; j++) {
|
||||
unsigned char effectCols = reader.readC();
|
||||
|
||||
if (effectCols>7) {
|
||||
logE("too many effect columns!");
|
||||
lastError = "too many effect columns";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (map_channels[i] == 0xfe) {
|
||||
ds.subsong[j]->pat[i].effectCols = 1;
|
||||
logV("- song %d has %d effect columns", j, effectCols);
|
||||
|
|
@ -850,8 +895,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
} else if (blockName == "INSTRUMENTS") {
|
||||
CHECK_BLOCK_VERSION(9);
|
||||
|
||||
// reader.seek(blockSize,SEEK_CUR);
|
||||
|
||||
ds.insLen = reader.readI();
|
||||
if (ds.insLen < 0 || ds.insLen > 256) {
|
||||
logE("too many instruments/out of range!");
|
||||
|
|
@ -870,10 +913,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
for (int i = 0; i < ds.insLen; i++) {
|
||||
unsigned int insIndex = reader.readI();
|
||||
if (insIndex >= ds.ins.size()) {
|
||||
// logE("instrument index %d is out of range!",insIndex);
|
||||
// lastError="instrument index out of range";
|
||||
// delete[] file;
|
||||
// return false;
|
||||
logE("instrument index %d is out of range!",insIndex);
|
||||
lastError="instrument index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
DivInstrument* ins = ds.ins[insIndex];
|
||||
|
|
@ -937,6 +980,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
if (blockVersion >= 7) {
|
||||
note = reader.readC();
|
||||
}
|
||||
if (note<0 || note>=120) {
|
||||
logE("DPCM note %d out of range!",note);
|
||||
lastError = "DPCM note out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
ins->amiga.noteMap[note].map = (short)((unsigned char)reader.readC()) - 1;
|
||||
unsigned char freq = reader.readC();
|
||||
ins->amiga.noteMap[note].dpcmFreq = (freq & 15); // 0-15 = 0-15 unlooped, 128-143 = 0-15 looped
|
||||
|
|
@ -981,8 +1030,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
case DIV_INS_OPLL: {
|
||||
ins->fm.opllPreset = (unsigned int)reader.readI();
|
||||
ins->fm.opllPreset&=15;
|
||||
|
||||
unsigned char custom_patch[8] = {0};
|
||||
unsigned char custom_patch[8];
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
custom_patch[i] = reader.readC();
|
||||
|
|
@ -1020,24 +1070,35 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
for (int j = 0; j < 64; j++) {
|
||||
wave->data[j] = reader.readC();
|
||||
}
|
||||
ins->std.waveMacro.len = 1;
|
||||
ins->std.waveMacro.val[0] = ds.wave.size();
|
||||
for (int j = 0; j < 32; j++) {
|
||||
ins->fds.modTable[j] = reader.readC() - 3;
|
||||
}
|
||||
ins->fds.modSpeed = reader.readI();
|
||||
ins->fds.modDepth = reader.readI();
|
||||
reader.readI(); // this is delay. currently ignored. TODO.
|
||||
ds.wave.push_back(wave);
|
||||
ds.waveLen++;
|
||||
if (ds.wave.size()>=256) {
|
||||
logW("too many waves! ignoring...");
|
||||
delete wave;
|
||||
} else {
|
||||
ins->std.waveMacro.len = 1;
|
||||
ins->std.waveMacro.val[0] = ds.wave.size();
|
||||
ds.wave.push_back(wave);
|
||||
ds.waveLen++;
|
||||
}
|
||||
|
||||
unsigned int a = reader.readI();
|
||||
unsigned int b = reader.readI();
|
||||
|
||||
reader.seek(-8, SEEK_CUR);
|
||||
if (!reader.seek(-8, SEEK_CUR)) {
|
||||
logE("couldn't seek back by 8 reading FDS ins");
|
||||
lastError = "couldn't seek back by 8 reading FDS ins";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a < 256 && (b & 0xFF) != 0x00) {
|
||||
// don't look at me like this. I don't know why this should be like this either!
|
||||
logW("a is less than 256 and b is not zero!");
|
||||
} else {
|
||||
ins->std.volMacro.len = reader.readC();
|
||||
ins->std.volMacro.loop = reader.readI();
|
||||
|
|
@ -1112,12 +1173,19 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
|
||||
if (blockVersion >= 8) {
|
||||
unsigned int autopos = reader.readI();
|
||||
(void)autopos;
|
||||
logV("autopos: %d",autopos);
|
||||
}
|
||||
|
||||
unsigned int wave_count = reader.readI();
|
||||
size_t waveOff = ds.wave.size();
|
||||
|
||||
if (wave_size>256) {
|
||||
logE("wave size %d out of range",wave_size);
|
||||
lastError = "wave size out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned int ii = 0; ii < wave_count; ii++) {
|
||||
DivWavetable* wave = new DivWavetable();
|
||||
wave->len = wave_size;
|
||||
|
|
@ -1131,6 +1199,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
if (ds.wave.size()<256) {
|
||||
ds.wave.push_back(wave);
|
||||
} else {
|
||||
logW("too many waves...");
|
||||
delete wave;
|
||||
}
|
||||
}
|
||||
|
|
@ -1296,16 +1365,30 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
|
||||
if (instVersion == 2) {
|
||||
reader.seek(seek_amount, SEEK_CUR); // what the fuck
|
||||
// I know right?
|
||||
if (!reader.seek(seek_amount, SEEK_CUR)) {
|
||||
logE("EFT seek fail");
|
||||
lastError = "EFT seek fail";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// this commented out block left here intentionally.
|
||||
// total mess of code style... for with no space, UNDEFINED CHAR, escaping the unescapable, silly var names...
|
||||
// ...whatever.
|
||||
/*for(int tti = 0; tti < 20; tti++)
|
||||
{
|
||||
char aaaa = reader.readC();
|
||||
logV("\'%c\'", aaaa);
|
||||
}*/
|
||||
} else {
|
||||
reader.seek(-4, SEEK_CUR);
|
||||
if (!reader.seek(-4, SEEK_CUR)) {
|
||||
logE("EFT -4 seek fail");
|
||||
lastError = "EFT -4 seek fail";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
@ -1325,7 +1408,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
|
||||
ds.insLen = 128;
|
||||
|
||||
} else if (blockName == "SEQUENCES") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
|
||||
|
|
@ -1342,6 +1424,14 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
unsigned int index = reader.readI();
|
||||
unsigned int type = reader.readI();
|
||||
unsigned char size = reader.readC();
|
||||
|
||||
if (index>=256 || type>=8) {
|
||||
logE("%d: index/type out of range",i);
|
||||
lastError = "sequence index/type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
macros[index][type].len = size;
|
||||
|
||||
for (int j = 0; j < size; j++) {
|
||||
|
|
@ -1361,12 +1451,34 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
unsigned char* Indices = new unsigned char[128 * 5];
|
||||
unsigned char* Types = new unsigned char[128 * 5];
|
||||
|
||||
memset(Indices,0,128*5);
|
||||
memset(Types,0,128*5);
|
||||
|
||||
for (unsigned int i = 0; i < seq_count; i++) {
|
||||
unsigned int index = reader.readI();
|
||||
if (index>=128*5) {
|
||||
logE("%d: index out of range",i);
|
||||
lastError = "sequence index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Indices[i] = index;
|
||||
unsigned int type = reader.readI();
|
||||
if (type>=128*5) {
|
||||
logE("%d: type out of range",i);
|
||||
lastError = "sequence type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Types[i] = type;
|
||||
|
||||
if (index>=256 || type>=8) {
|
||||
logE("%d: index/type out of range",i);
|
||||
lastError = "sequence index/type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char size = reader.readC();
|
||||
unsigned int setting = 0;
|
||||
|
||||
|
|
@ -1393,7 +1505,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
DivInstrument* ins = ds.ins[k];
|
||||
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) {
|
||||
copyMacro(ins, ¯os[index][type], Types[i], setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1412,7 +1523,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
macro_types[k][j] = setting;
|
||||
|
||||
copyMacro(ins, ¯os[sequenceIndex[k][j]][j], j, setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)j, true), ¯os[sequenceIndex[k][j]][j], sizeof(DivInstrumentMacro));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1425,9 +1535,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
unsigned int release = reader.readI();
|
||||
unsigned int setting = reader.readI();
|
||||
|
||||
// macros[index][type].rel = release;
|
||||
// macro_types[index][type] = setting;
|
||||
|
||||
for (int k = 0; k < (int)ds.ins.size(); k++) {
|
||||
DivInstrument* ins = ds.ins[k];
|
||||
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) {
|
||||
|
|
@ -1435,7 +1542,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
macro_types[k][Types[i]] = setting;
|
||||
|
||||
copyMacro(ins, ¯os[sequenceIndex[k][Types[i]]][Types[i]], Types[i], setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[k][Types[i]]][Types[i]], sizeof(DivInstrumentMacro));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1446,12 +1552,11 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
} else if (blockName == "GROOVES") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
// reader.seek(blockSize,SEEK_CUR);
|
||||
|
||||
unsigned char num_grooves = reader.readC();
|
||||
int max_groove = 0;
|
||||
|
||||
for (int i = 0; i < 0xff; i++) {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
ds.grooves.push_back(DivGroovePattern());
|
||||
}
|
||||
|
||||
|
|
@ -1459,15 +1564,18 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
unsigned char index = reader.readC();
|
||||
unsigned char size = reader.readC();
|
||||
|
||||
if (index > max_groove)
|
||||
if (index > max_groove) {
|
||||
max_groove = index + 1;
|
||||
}
|
||||
|
||||
DivGroovePattern gp;
|
||||
gp.len = size;
|
||||
|
||||
for (int sz = 0; sz < size; sz++) {
|
||||
unsigned char value = reader.readC();
|
||||
gp.val[sz] = value;
|
||||
if (sz<16) {
|
||||
gp.val[sz] = value;
|
||||
}
|
||||
}
|
||||
|
||||
ds.grooves[index] = gp;
|
||||
|
|
@ -1488,14 +1596,21 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
if ((reader.tell() - blockStart) != blockSize) {
|
||||
logE("block %s size does not match! block size %d curr pos %d", blockName, blockSize, reader.tell() - blockStart);
|
||||
}
|
||||
|
||||
} else if (blockName == "FRAMES") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
|
||||
for (size_t i = 0; i < ds.subsong.size(); i++) {
|
||||
DivSubSong* s = ds.subsong[i];
|
||||
|
||||
s->ordersLen = reader.readI();
|
||||
int framesLen=reader.readI();
|
||||
if (framesLen<1 || framesLen>256) {
|
||||
logE("frames out of range (%d)",framesLen);
|
||||
lastError = "frames out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
s->ordersLen = framesLen;
|
||||
if (blockVersion >= 3) {
|
||||
s->speeds.val[0] = reader.readI();
|
||||
}
|
||||
|
|
@ -1510,18 +1625,31 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
s->virtualTempoN = tempo;
|
||||
}
|
||||
|
||||
s->patLen = reader.readI();
|
||||
int patLen=reader.readI();
|
||||
if (patLen<1 || patLen>256) {
|
||||
logE("pattern length out of range");
|
||||
lastError = "pattern length out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
s->patLen = patLen;
|
||||
}
|
||||
int why = tchans;
|
||||
if (blockVersion == 1) {
|
||||
why = reader.readI();
|
||||
if (why<0 || why>=DIV_MAX_CHANS) {
|
||||
logE("why out of range!");
|
||||
lastError = "why out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
logV("reading %d and %d orders", tchans, s->ordersLen);
|
||||
|
||||
for (int j = 0; j < s->ordersLen; j++) {
|
||||
for (int k = 0; k < why; k++) {
|
||||
unsigned char o = reader.readC();
|
||||
// logV("%.2x",o);
|
||||
if (map_channels[k]>=DIV_MAX_CHANS) continue;
|
||||
s->orders.ord[map_channels[k]][j] = o;
|
||||
}
|
||||
}
|
||||
|
|
@ -1533,6 +1661,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
|
||||
if (blockVersion == 1) {
|
||||
int patLenOld = reader.readI();
|
||||
if (patLenOld<1 || patLenOld>=256) {
|
||||
logE("old pattern length out of range");
|
||||
lastError = "old pattern length out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
for (DivSubSong* i : ds.subsong) {
|
||||
i->patLen = patLenOld;
|
||||
}
|
||||
|
|
@ -1553,6 +1687,37 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
logV("patNum: %d",patNum);
|
||||
logV("rows: %d",numRows);
|
||||
|
||||
if (subs<0 || subs>=(int)ds.subsong.size()) {
|
||||
logE("subsong out of range!");
|
||||
lastError = "subsong out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (ch<0 || ch>=DIV_MAX_CHANS) {
|
||||
logE("channel out of range!");
|
||||
lastError = "channel out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (map_channels[ch]>=DIV_MAX_CHANS) {
|
||||
logE("mapped channel out of range!");
|
||||
lastError = "mapped channel out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (patNum<0 || patNum>=256) {
|
||||
logE("pattern number out of range!");
|
||||
lastError = "pattern number out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
if (numRows<0) {
|
||||
logE("row count is negative!");
|
||||
lastError = "row count is negative";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
DivPattern* pat = ds.subsong[subs]->pat[map_channels[ch]].getPattern(patNum, true);
|
||||
for (int i = 0; i < numRows; i++) {
|
||||
unsigned int row = 0;
|
||||
|
|
@ -1562,6 +1727,13 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
row = reader.readI();
|
||||
}
|
||||
|
||||
if (row>=256) {
|
||||
logE("row index out of range");
|
||||
lastError = "row index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char nextNote = reader.readC();
|
||||
unsigned char nextOctave = reader.readC();
|
||||
|
||||
|
|
@ -1592,6 +1764,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
|
||||
unsigned char nextIns = reader.readC();
|
||||
// TODO: you sure about 0xff?
|
||||
if (map_channels[ch] != 0xff) {
|
||||
if (nextIns < 0x40 && nextNote != 0x0d && nextNote != 0x0e) {
|
||||
pat->data[row][2] = nextIns;
|
||||
|
|
@ -1606,6 +1779,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
pat->data[row][3] = nextVol;
|
||||
if (map_channels[ch] == vrc6_saw_chan) // scale volume
|
||||
{
|
||||
// TODO: shouldn't it be 32?
|
||||
pat->data[row][3] = (pat->data[row][3] * 42) / 15;
|
||||
}
|
||||
|
||||
|
|
@ -1625,13 +1799,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
effectCols = 1;
|
||||
}
|
||||
|
||||
logV("effectCols: %d",effectCols);
|
||||
|
||||
unsigned char nextEffectVal = 0;
|
||||
unsigned char nextEffect = 0;
|
||||
|
||||
//logV("row %d effects are read at %x",row,reader.tell());
|
||||
|
||||
for (int j = 0; j < effectCols; j++) {
|
||||
nextEffect = reader.readC();
|
||||
|
||||
|
|
@ -1727,14 +1897,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
|
||||
}
|
||||
|
||||
// logW("next effect %d val %d", nextEffect, nextEffectVal);
|
||||
|
||||
if (map_channels[ch] != 0xff) {
|
||||
if (nextEffect == 0 && nextEffectVal == 0) {
|
||||
pat->data[row][4 + (j * 2)] = -1;
|
||||
pat->data[row][5 + (j * 2)] = -1;
|
||||
} else {
|
||||
if (nextEffect < ftEffectMapSize) {
|
||||
if ((eft && nextEffect<eftEffectMapSize) || (!eft && nextEffect<ftEffectMapSize)) {
|
||||
if (eft) {
|
||||
pat->data[row][4 + (j * 2)] = eftEffectMap[nextEffect];
|
||||
pat->data[row][5 + (j * 2)] = eftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal;
|
||||
|
|
@ -1782,7 +1950,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
} else if (blockName == "DPCM SAMPLES") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
// reader.seek(blockSize,SEEK_CUR);
|
||||
unsigned char num_samples = reader.readC();
|
||||
|
||||
for (int i = 0; i < 256; i++) {
|
||||
|
|
@ -1808,14 +1975,16 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
|
||||
unsigned int sample_len = reader.readI();
|
||||
|
||||
if (sample_len>=2097152) {
|
||||
logE("%d: sample too large! %d",index,sample_len);
|
||||
lastError = "sample too large";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
true_size = sample_len + ((1 - (int)sample_len) & 0x0f);
|
||||
sample->lengthDPCM = true_size;
|
||||
sample->samples = true_size * 8;
|
||||
|
||||
sample->dataDPCM = new unsigned char[true_size];
|
||||
|
||||
sample->init(true_size * 8);
|
||||
memset(sample->dataDPCM, 0xAA, true_size);
|
||||
|
||||
reader.read(sample->dataDPCM, sample_len);
|
||||
}
|
||||
|
||||
|
|
@ -1824,7 +1993,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
for (int i = 255; i > 0; i--) {
|
||||
DivSample* s = ds.sample[i];
|
||||
|
||||
if (s->dataDPCM) {
|
||||
if (s->samples>0) {
|
||||
last_non_empty_sample = i;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1835,21 +2004,42 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
|
||||
ds.sampleLen = ds.sample.size();
|
||||
|
||||
} else if (blockName == "SEQUENCES_VRC6") {
|
||||
CHECK_BLOCK_VERSION(6);
|
||||
|
||||
unsigned char* Indices = new unsigned char[128 * 5];
|
||||
unsigned char* Types = new unsigned char[128 * 5];
|
||||
|
||||
memset(Indices,0,128*5);
|
||||
memset(Types,0,128*5);
|
||||
|
||||
unsigned int seq_count = reader.readI();
|
||||
|
||||
for (unsigned int i = 0; i < seq_count; i++) {
|
||||
unsigned int index = reader.readI();
|
||||
if (index>=128*5) {
|
||||
logE("%d: index out of range",i);
|
||||
lastError = "sequence index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Indices[i] = index;
|
||||
unsigned int type = reader.readI();
|
||||
if (type>=128*5) {
|
||||
logE("%d: type out of range",i);
|
||||
lastError = "sequence type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Types[i] = type;
|
||||
|
||||
if (index>=256 || type>=8) {
|
||||
logE("%d: index/type out of range",i);
|
||||
lastError = "sequence index/type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char size = reader.readC();
|
||||
unsigned int setting = 0;
|
||||
|
||||
|
|
@ -1914,9 +2104,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
unsigned int release = reader.readI();
|
||||
unsigned int setting = reader.readI();
|
||||
|
||||
// macros[index][type].rel = release;
|
||||
// macro_types[index][type] = setting;
|
||||
|
||||
for (int k = 0; k < (int)ds.ins.size(); k++) {
|
||||
DivInstrument* ins = ds.ins[k];
|
||||
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_VRC6 && hasSequence[k][Types[i]]) {
|
||||
|
|
@ -1937,19 +2124,40 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
delete[] Types;
|
||||
} else if (blockName == "SEQUENCES_N163" || blockName == "SEQUENCES_N106") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
// reader.seek(blockSize,SEEK_CUR);
|
||||
|
||||
unsigned char* Indices = new unsigned char[128 * 5];
|
||||
unsigned char* Types = new unsigned char[128 * 5];
|
||||
|
||||
memset(Indices,0,128*5);
|
||||
memset(Types,0,128*5);
|
||||
|
||||
unsigned int seq_count = reader.readI();
|
||||
|
||||
for (unsigned int i = 0; i < seq_count; i++) {
|
||||
unsigned int index = reader.readI();
|
||||
if (index>=128*5) {
|
||||
logE("%d: index out of range",i);
|
||||
lastError = "sequence index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Indices[i] = index;
|
||||
unsigned int type = reader.readI();
|
||||
if (type>=128*5) {
|
||||
logE("%d: type out of range",i);
|
||||
lastError = "sequence type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Types[i] = type;
|
||||
|
||||
if (index>=256 || type>=8) {
|
||||
logE("%d: index/type out of range",i);
|
||||
lastError = "sequence index/type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char size = reader.readC();
|
||||
unsigned int setting = 0;
|
||||
|
||||
|
|
@ -1974,7 +2182,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
DivInstrument* ins = ds.ins[k];
|
||||
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_N163 && hasSequence[k][Types[i]]) {
|
||||
copyMacro(ins, ¯os[index][type], type, setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1984,19 +2191,40 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
|
||||
} else if (blockName == "SEQUENCES_S5B") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
// reader.seek(blockSize,SEEK_CUR);
|
||||
|
||||
unsigned char* Indices = new unsigned char[128 * 5];
|
||||
unsigned char* Types = new unsigned char[128 * 5];
|
||||
|
||||
memset(Indices,0,128*5);
|
||||
memset(Types,0,128*5);
|
||||
|
||||
unsigned int seq_count = reader.readI();
|
||||
|
||||
for (unsigned int i = 0; i < seq_count; i++) {
|
||||
unsigned int index = reader.readI();
|
||||
if (index>=128*5) {
|
||||
logE("%d: index out of range",i);
|
||||
lastError = "sequence index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Indices[i] = index;
|
||||
unsigned int type = reader.readI();
|
||||
if (type>=128*5) {
|
||||
logE("%d: type out of range",i);
|
||||
lastError = "sequence type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Types[i] = type;
|
||||
|
||||
if (index>=256 || type>=8) {
|
||||
logE("%d: index/type out of range",i);
|
||||
lastError = "sequence index/type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char size = reader.readC();
|
||||
unsigned int setting = 0;
|
||||
|
||||
|
|
@ -2021,7 +2249,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
DivInstrument* ins = ds.ins[k];
|
||||
if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_AY && hasSequence[k][type]) {
|
||||
copyMacro(ins, ¯os[index][type], type, setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2034,14 +2261,36 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
unsigned char* Indices = new unsigned char[128 * 5];
|
||||
unsigned char* Types = new unsigned char[128 * 5];
|
||||
|
||||
memset(Indices,0,128*5);
|
||||
memset(Types,0,128*5);
|
||||
|
||||
unsigned int seq_count = reader.readI();
|
||||
|
||||
for (unsigned int i = 0; i < seq_count; i++) {
|
||||
unsigned int index = reader.readI();
|
||||
if (index>=128*5) {
|
||||
logE("%d: index out of range",i);
|
||||
lastError = "sequence index out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Indices[i] = index;
|
||||
unsigned int type = reader.readI();
|
||||
if (type>=128*5) {
|
||||
logE("%d: type out of range",i);
|
||||
lastError = "sequence type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
Types[i] = type;
|
||||
|
||||
if (index>=256 || type>=8) {
|
||||
logE("%d: index/type out of range",i);
|
||||
lastError = "sequence index/type out of range";
|
||||
delete[] file;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char size = reader.readC();
|
||||
unsigned int setting = 0;
|
||||
|
||||
|
|
@ -2066,7 +2315,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
DivInstrument* ins = ds.ins[k];
|
||||
if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_C64 && hasSequence[k][type]) {
|
||||
copyMacro(ins, ¯os[index][type], type, setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2075,31 +2323,33 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
delete[] Types;
|
||||
} else if (blockName == "JSON") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
logW("block JSON not supported...");
|
||||
reader.seek(blockSize, SEEK_CUR);
|
||||
} else if (blockName == "PARAMS_EMU") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
logW("block PARAMS_EMU not supported...");
|
||||
reader.seek(blockSize, SEEK_CUR);
|
||||
} else if (blockName == "DETUNETABLES") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
logW("block DETUNETABLES not supported...");
|
||||
reader.seek(blockSize, SEEK_CUR);
|
||||
} else if (blockName == "COMMENTS") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
// reader.seek(blockSize,SEEK_CUR);
|
||||
unsigned int display_comment = reader.readI();
|
||||
(void)display_comment;
|
||||
|
||||
char ch = 1;
|
||||
logV("displayComment: %d",display_comment);
|
||||
|
||||
do {
|
||||
char ch = 0;
|
||||
|
||||
// why not readString?
|
||||
while (true) {
|
||||
ch = reader.readC();
|
||||
String sss = String() + ch;
|
||||
ds.subsong[0]->notes += sss;
|
||||
} while (ch != 0);
|
||||
if (ch==0) break;
|
||||
ds.subsong[0]->notes += ch;
|
||||
}
|
||||
|
||||
// ds.subsong[0]->notes = reader.readS();
|
||||
} else if (blockName == "PARAMS_EXTRA") {
|
||||
CHECK_BLOCK_VERSION(3);
|
||||
// reader.seek(blockSize,SEEK_CUR);
|
||||
unsigned int linear_pitch = reader.readI();
|
||||
|
||||
ds.linearPitch = linear_pitch == 0 ? 0 : 2;
|
||||
|
|
@ -2112,11 +2362,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
if (blockVersion >= 3) {
|
||||
unsigned char flats = reader.readC();
|
||||
(void)flats;
|
||||
logV("flats: %d",(int)flats);
|
||||
}
|
||||
} else if (blockName == "TUNING") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
// reader.seek(blockSize,SEEK_CUR);
|
||||
if (blockVersion == 1) {
|
||||
int fineTuneCents = reader.readC() * 100;
|
||||
fineTuneCents += reader.readC();
|
||||
|
|
@ -2125,6 +2374,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
}
|
||||
} else if (blockName == "BOOKMARKS") {
|
||||
CHECK_BLOCK_VERSION(1);
|
||||
logW("block BOOKMARKS not supported...");
|
||||
reader.seek(blockSize, SEEK_CUR);
|
||||
} else {
|
||||
logE("block %s is unknown!", blockName);
|
||||
|
|
@ -2152,6 +2402,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
|
|||
if (index < 0)
|
||||
index = 0;
|
||||
|
||||
if (index>=(int)ds.ins.size()) continue;
|
||||
|
||||
DivInstrument* ins = ds.ins[index];
|
||||
|
||||
if (ins->type == DIV_INS_FM) {
|
||||
|
|
|
|||
|
|
@ -861,6 +861,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
|
|||
if (ds.version<191) {
|
||||
ds.oldAlwaysSetVolume=true;
|
||||
}
|
||||
if (ds.version<200) {
|
||||
ds.oldSampleOffset=true;
|
||||
}
|
||||
ds.isDMF=false;
|
||||
|
||||
reader.readS(); // reserved
|
||||
|
|
@ -1414,7 +1417,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
|
|||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
for (int i=0; i<1; i++) {
|
||||
if (ds.version>=200) {
|
||||
ds.oldSampleOffset=reader.readC();
|
||||
} else {
|
||||
reader.readC();
|
||||
}
|
||||
}
|
||||
|
|
@ -2078,6 +2083,25 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
|
|||
}
|
||||
}
|
||||
|
||||
// VERA old chip revision
|
||||
// TIA old tuning
|
||||
if (ds.version<213) {
|
||||
for (int i=0; i<ds.systemLen; i++) {
|
||||
if (ds.system[i]==DIV_SYSTEM_VERA) {
|
||||
ds.systemFlags[i].set("chipType",0);
|
||||
}
|
||||
if (ds.system[i]==DIV_SYSTEM_TIA) {
|
||||
ds.systemFlags[i].set("oldPitch",true);
|
||||
}
|
||||
}
|
||||
} else if (ds.version<217) {
|
||||
for (int i=0; i<ds.systemLen; i++) {
|
||||
if (ds.system[i]==DIV_SYSTEM_VERA) {
|
||||
ds.systemFlags[i].set("chipType",1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (active) quitDispatch();
|
||||
BUSY_BEGIN_SOFT;
|
||||
saveLock.lock();
|
||||
|
|
@ -2402,9 +2426,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary, bool newPatternFormat) {
|
|||
w->writeC(song.resetArpPhaseOnNewNote);
|
||||
w->writeC(song.ceilVolumeScaling);
|
||||
w->writeC(song.oldAlwaysSetVolume);
|
||||
for (int i=0; i<1; i++) {
|
||||
w->writeC(0);
|
||||
}
|
||||
w->writeC(song.oldSampleOffset);
|
||||
|
||||
// speeds of first song
|
||||
w->writeC(subSong->speeds.len);
|
||||
|
|
|
|||
1586
src/engine/fileOps/it.cpp
Normal file
1586
src/engine/fileOps/it.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -96,7 +96,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
logD("couldn't seek to 0");
|
||||
throw EndOfFileException(&reader,reader.tell());
|
||||
}
|
||||
ds.name=reader.readString(20);
|
||||
ds.name=reader.readStringLatin1(20);
|
||||
logI("%s",ds.name);
|
||||
|
||||
// samples
|
||||
|
|
@ -105,7 +105,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
for (int i=0; i<insCount; i++) {
|
||||
DivSample* sample=new DivSample;
|
||||
sample->depth=DIV_SAMPLE_DEPTH_8BIT;
|
||||
sample->name=reader.readString(22);
|
||||
sample->name=reader.readStringLatin1(22);
|
||||
logD("%d: %s",i+1,sample->name);
|
||||
int slen=((unsigned short)reader.readS_BE())*2;
|
||||
sampLens[i]=slen;
|
||||
|
|
@ -322,7 +322,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
setEffectState[3]=fxVal;
|
||||
break;
|
||||
case 9: // set offset
|
||||
writeFxCol(0x90,fxVal);
|
||||
writeFxCol(0x91,fxVal);
|
||||
break;
|
||||
case 10: // vol slide
|
||||
effectState[4]=fxVal;
|
||||
|
|
@ -417,6 +417,9 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
|
|||
ds.ins.push_back(ins);
|
||||
}
|
||||
ds.insLen=ds.ins.size();
|
||||
|
||||
// find subsongs
|
||||
ds.findSubSongs(chCount);
|
||||
|
||||
if (active) quitDispatch();
|
||||
BUSY_BEGIN_SOFT;
|
||||
|
|
|
|||
124
src/engine/fileOps/p.cpp
Normal file
124
src/engine/fileOps/p.cpp
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "fileOpsCommon.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
//P VOX ADPCM sample bank
|
||||
|
||||
/* =======================================
|
||||
Header
|
||||
=======================================
|
||||
|
||||
0x0000 - 0x03FF 256 * {
|
||||
Sample start (uint32_t)
|
||||
- 0x00000000 = unused
|
||||
}
|
||||
|
||||
|
||||
=======================================
|
||||
Body
|
||||
=======================================
|
||||
|
||||
Stream of Sample Data {
|
||||
|
||||
MSM6258 ADPCM encoding
|
||||
nibble-swapped VOX / Dialogic ADPCM
|
||||
Mono
|
||||
Sample rate?
|
||||
16000Hz seems fine
|
||||
|
||||
} */
|
||||
|
||||
#define P_BANK_SIZE 256
|
||||
#define P_SAMPLE_RATE 16000
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t start_pointer;
|
||||
} P_HEADER;
|
||||
|
||||
|
||||
void DivEngine::loadP(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader.seek(0, SEEK_SET);
|
||||
|
||||
P_HEADER headers[P_BANK_SIZE];
|
||||
|
||||
for(int i = 0; i < P_BANK_SIZE; i++)
|
||||
{
|
||||
headers[i].start_pointer = (unsigned int)reader.readI_BE();
|
||||
}
|
||||
|
||||
for(int i = 0; i < P_BANK_SIZE; i++)
|
||||
{
|
||||
if(headers[i].start_pointer != 0)
|
||||
{
|
||||
DivSample* s = new DivSample;
|
||||
|
||||
s->rate = P_SAMPLE_RATE;
|
||||
s->centerRate = P_SAMPLE_RATE;
|
||||
s->depth = DIV_SAMPLE_DEPTH_VOX;
|
||||
|
||||
reader.seek((int)headers[i].start_pointer, SEEK_SET);
|
||||
|
||||
int sample_pos = 0;
|
||||
int sample_len = 0;
|
||||
|
||||
if(i < P_BANK_SIZE - 1)
|
||||
{
|
||||
sample_len = headers[i + 1].start_pointer - headers[i].start_pointer;
|
||||
}
|
||||
else
|
||||
{
|
||||
sample_len = (int)reader.size() - headers[i].start_pointer;
|
||||
}
|
||||
|
||||
if(sample_len > 0)
|
||||
{
|
||||
s->init(sample_len * 2);
|
||||
|
||||
for(int j = 0; j < sample_len; j++)
|
||||
{
|
||||
unsigned char curr_byte = (unsigned char)reader.readC();
|
||||
curr_byte = (curr_byte << 4) | (curr_byte >> 4);
|
||||
|
||||
s->dataVOX[sample_pos] = curr_byte;
|
||||
sample_pos++;
|
||||
}
|
||||
|
||||
ret.push_back(s);
|
||||
logI("p: start %d len %d", headers[i].start_pointer, sample_len);
|
||||
}
|
||||
else
|
||||
{
|
||||
delete s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EndOfFileException& e)
|
||||
{
|
||||
lastError=_("premature end of file");
|
||||
logE("premature end of file");
|
||||
}
|
||||
}
|
||||
142
src/engine/fileOps/p86.cpp
Normal file
142
src/engine/fileOps/p86.cpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "fileOpsCommon.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
//P86 8-bit PCM sample bank
|
||||
|
||||
/* =======================================
|
||||
Header
|
||||
=======================================
|
||||
|
||||
0x0000 Identifier (12b)
|
||||
"PCM86 DATA(\n)(\0)"
|
||||
0x000C Targeted P86DRV version (1b)
|
||||
version <high nibble>.<low nibble>
|
||||
0x000D File Length (3b)
|
||||
0x0010 - 0x060F 256 * {
|
||||
|
||||
Pointer to Sample Data Start (3b)
|
||||
Length of Sample Data (3b)
|
||||
|
||||
(0x000000 0x000000 -> no sample for this instrument ID)
|
||||
|
||||
}
|
||||
|
||||
|
||||
=======================================
|
||||
Body
|
||||
=======================================
|
||||
|
||||
Stream of Sample Data {
|
||||
|
||||
8-Bit Signed
|
||||
Mono
|
||||
16540Hz
|
||||
(above sample rate according to KAJA's documentation
|
||||
any sample rate possible, for different base note & octave)
|
||||
|
||||
} */
|
||||
|
||||
#define P86_BANK_SIZE 256
|
||||
#define P86_SAMPLE_RATE 16540
|
||||
|
||||
#define P86_FILE_SIG "PCM86 DATA\n\0"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t start_pointer;
|
||||
uint32_t sample_length;
|
||||
} P86_HEADER;
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
uint32_t read_3bytes(SafeReader& reader)
|
||||
{
|
||||
unsigned char arr[3];
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
arr[i] = (unsigned char)reader.readC();
|
||||
}
|
||||
|
||||
return (arr[0] | (arr[1] << 8) | (arr[2] << 16));
|
||||
}
|
||||
|
||||
void DivEngine::loadP86(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader.seek(0, SEEK_SET);
|
||||
|
||||
P86_HEADER headers[P86_BANK_SIZE];
|
||||
|
||||
String file_sig = reader.readString(12);
|
||||
if(file_sig != P86_FILE_SIG) return;
|
||||
|
||||
uint8_t version = reader.readC();
|
||||
UNUSED(version);
|
||||
|
||||
uint32_t file_size = read_3bytes(reader);
|
||||
UNUSED(file_size);
|
||||
|
||||
for(int i = 0; i < P86_BANK_SIZE; i++)
|
||||
{
|
||||
headers[i].start_pointer = read_3bytes(reader);
|
||||
headers[i].sample_length = read_3bytes(reader);
|
||||
}
|
||||
|
||||
for(int i = 0; i < P86_BANK_SIZE; i++)
|
||||
{
|
||||
if(headers[i].start_pointer != 0 && headers[i].sample_length != 0)
|
||||
{
|
||||
DivSample* s = new DivSample;
|
||||
|
||||
s->rate = P86_SAMPLE_RATE;
|
||||
s->centerRate = P86_SAMPLE_RATE;
|
||||
s->depth = DIV_SAMPLE_DEPTH_8BIT;
|
||||
s->init(headers[i].sample_length); //byte per sample
|
||||
|
||||
reader.seek((int)headers[i].start_pointer, SEEK_SET);
|
||||
|
||||
int sample_pos = 0;
|
||||
|
||||
for(uint32_t j = 0; j < headers[i].sample_length; j++)
|
||||
{
|
||||
unsigned char curr_byte = (unsigned char)reader.readC();
|
||||
//curr_byte += 0x80;
|
||||
|
||||
s->data8[sample_pos] = curr_byte;
|
||||
sample_pos++;
|
||||
}
|
||||
|
||||
ret.push_back(s);
|
||||
|
||||
logI("p86: start %06X len %06X", headers[i].start_pointer, headers[i].sample_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EndOfFileException& e)
|
||||
{
|
||||
lastError=_("premature end of file");
|
||||
logE("premature end of file");
|
||||
}
|
||||
}
|
||||
101
src/engine/fileOps/pdx.cpp
Normal file
101
src/engine/fileOps/pdx.cpp
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "fileOpsCommon.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
//PDX 8-bit OKI ADPCM sample bank
|
||||
|
||||
/* File format
|
||||
The file starts with a header with 96 8-byte pairs, with each pair containing offset and length of ADPCM data chunks for each note value, like so:
|
||||
|
||||
[[byte pointer to sample in file -- 32-bit unsigned integer (4 bytes)] [empty: $0000 -- 16-bit integer] [length of sample, amount in bytes -- 16-bit unsigned integer] ×96] [ADPCM data] EOF
|
||||
|
||||
The first sample (1) is mapped to 0x80 (= the lowest note within MXDRV) and the last sample (96) is mapped to 0xDF (= the highest note within MXDRV).
|
||||
|
||||
|
||||
Samples are encoded in 4-bit OKI ADPCM encoded nibbles, where each byte contains 2 nibbles: [nibble 2 << 4 || nibble 1]
|
||||
|
||||
|
||||
Unfortunately, sample rates for each samples are not defined within the .PDX file and have to be set manually with the appropriate command for that at play-time */
|
||||
|
||||
#define PDX_BANK_SIZE 96
|
||||
#define PDX_SAMPLE_RATE 16000
|
||||
|
||||
typedef struct
|
||||
{
|
||||
unsigned int start_pointer;
|
||||
unsigned short sample_length;
|
||||
} PDX_HEADER;
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
void DivEngine::loadPDX(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader.seek(0, SEEK_SET);
|
||||
|
||||
PDX_HEADER headers[PDX_BANK_SIZE];
|
||||
|
||||
for(int i = 0; i < PDX_BANK_SIZE; i++)
|
||||
{
|
||||
headers[i].start_pointer = (unsigned int)reader.readI_BE();
|
||||
unsigned short empty = (unsigned short)reader.readS_BE(); //skip 1st 2 bytes
|
||||
UNUSED(empty);
|
||||
headers[i].sample_length = (unsigned short)reader.readS_BE();
|
||||
}
|
||||
|
||||
for(int i = 0; i < PDX_BANK_SIZE; i++)
|
||||
{
|
||||
if(headers[i].start_pointer != 0 && headers[i].sample_length != 0)
|
||||
{
|
||||
DivSample* s = new DivSample;
|
||||
|
||||
s->rate = PDX_SAMPLE_RATE;
|
||||
s->centerRate = PDX_SAMPLE_RATE;
|
||||
s->depth = DIV_SAMPLE_DEPTH_VOX;
|
||||
s->init(headers[i].sample_length * 2);
|
||||
|
||||
reader.seek((int)headers[i].start_pointer, SEEK_SET);
|
||||
|
||||
int sample_pos = 0;
|
||||
|
||||
for(unsigned short j = 0; j < headers[i].sample_length; j++)
|
||||
{
|
||||
unsigned char curr_byte = (unsigned char)reader.readC();
|
||||
curr_byte = (curr_byte << 4) | (curr_byte >> 4);
|
||||
|
||||
s->dataVOX[sample_pos] = curr_byte;
|
||||
sample_pos++;
|
||||
}
|
||||
|
||||
ret.push_back(s);
|
||||
|
||||
logI("pdx: start %d len %d", headers[i].start_pointer, headers[i].sample_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EndOfFileException& e)
|
||||
{
|
||||
lastError=_("premature end of file");
|
||||
logE("premature end of file");
|
||||
}
|
||||
}
|
||||
142
src/engine/fileOps/ppc.cpp
Normal file
142
src/engine/fileOps/ppc.cpp
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "fileOpsCommon.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
//PPC PMD's YM2608 ADPCM-B sample bank
|
||||
|
||||
/* ========================================
|
||||
General
|
||||
========================================
|
||||
|
||||
ADPCM RAM addresses: see docs/common.txt {
|
||||
|
||||
address_start = 0x0026
|
||||
file_header_size = 0x0420
|
||||
|
||||
}
|
||||
|
||||
|
||||
========================================
|
||||
Header
|
||||
========================================
|
||||
|
||||
0x0000 Identifier (30b)
|
||||
"ADPCM DATA for PMD ver.4.4- "
|
||||
0x001E Address of End of Data (32B blocks) in ADPCM RAM (1h)
|
||||
File Size == address -> file offset
|
||||
|
||||
0x0020 - 0x041F 256 * {
|
||||
|
||||
Start of Sample (32b blocks) in ADPCM RAM (1h)
|
||||
End of Sample (32b blocks) in ADPCM RAM (1h)
|
||||
|
||||
(0x0000 0x0000 -> no sample for this instrument ID)
|
||||
|
||||
}
|
||||
|
||||
|
||||
========================================
|
||||
Body
|
||||
========================================
|
||||
|
||||
Stream of Sample Data {
|
||||
|
||||
Yamaha ADPCM-B encoding (4-Bit Signed ADPCM)
|
||||
Mono
|
||||
16kHz
|
||||
(above sample rate according to KAJA's documentation
|
||||
any sample rate possible, for different base note & octave)
|
||||
|
||||
} */
|
||||
|
||||
#define PPC_FILE_SIG "ADPCM DATA for PMD ver.4.4- "
|
||||
|
||||
#define PPC_BANK_SIZE 256
|
||||
#define PPC_SAMPLE_RATE 16000
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t start_pointer;
|
||||
uint16_t end_pointer;
|
||||
} PPC_HEADER;
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
#define ADPCM_DATA_START 0x0420
|
||||
|
||||
void DivEngine::loadPPC(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader.seek(0, SEEK_SET);
|
||||
|
||||
String file_sig = reader.readString(30);
|
||||
unsigned short end_of_data = (unsigned short)reader.readS();
|
||||
UNUSED(end_of_data);
|
||||
|
||||
if(file_sig != PPC_FILE_SIG) return;
|
||||
|
||||
PPC_HEADER headers[PPC_BANK_SIZE];
|
||||
|
||||
for(int i = 0; i < PPC_BANK_SIZE; i++)
|
||||
{
|
||||
headers[i].start_pointer = (unsigned short)reader.readS();
|
||||
headers[i].end_pointer = (unsigned short)reader.readS();
|
||||
}
|
||||
|
||||
for(int i = 0; i < PPC_BANK_SIZE; i++)
|
||||
{
|
||||
if((headers[i].start_pointer != 0 || headers[i].end_pointer != 0) && headers[i].start_pointer < headers[i].end_pointer)
|
||||
{
|
||||
DivSample* s = new DivSample;
|
||||
|
||||
s->rate = PPC_SAMPLE_RATE;
|
||||
s->centerRate = PPC_SAMPLE_RATE;
|
||||
s->depth = DIV_SAMPLE_DEPTH_ADPCM_B;
|
||||
s->init((headers[i].end_pointer - headers[i].start_pointer) * 32 * 2);
|
||||
|
||||
int sample_pos = 0;
|
||||
int sample_length = (headers[i].end_pointer - headers[i].start_pointer) * 32;
|
||||
|
||||
//reader.seek(ADPCM_DATA_START + headers[i].start_pointer * 32, SEEK_SET);
|
||||
|
||||
for(int j = 0; j < sample_length; j++)
|
||||
{
|
||||
unsigned char curr_byte = (unsigned char)reader.readC();
|
||||
//curr_byte=(curr_byte<<4)|(curr_byte>>4);
|
||||
|
||||
s->dataB[sample_pos] = curr_byte;
|
||||
sample_pos++;
|
||||
}
|
||||
|
||||
logI("ppc: start %d end %d len in bytes %d", headers[i].start_pointer, headers[i].end_pointer, (headers[i].end_pointer - headers[i].start_pointer) * 32);
|
||||
|
||||
ret.push_back(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EndOfFileException& e)
|
||||
{
|
||||
lastError=_("premature end of file");
|
||||
logE("premature end of file");
|
||||
}
|
||||
}
|
||||
125
src/engine/fileOps/pps.cpp
Normal file
125
src/engine/fileOps/pps.cpp
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "fileOpsCommon.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
//PPS AY-3-8910 sample bank
|
||||
|
||||
/* =======================================
|
||||
Header
|
||||
=======================================
|
||||
|
||||
0x0000 - 0x0053 14 * {
|
||||
|
||||
Pointer to Sample Data Start (1h)
|
||||
Length of Sample Data (1h)
|
||||
"Pitch"(?) (1b)
|
||||
Volume Reduction (1b)
|
||||
|
||||
}
|
||||
|
||||
(0x0000 0x0000 [0x00 0x00] -> no sample for this instrument ID)
|
||||
|
||||
}
|
||||
|
||||
|
||||
=======================================
|
||||
Body
|
||||
=======================================
|
||||
|
||||
Stream of Sample Data {
|
||||
|
||||
4-Bit Unsigned
|
||||
(afaict)
|
||||
Mono
|
||||
16Hz
|
||||
(based on tests, maybe alternatively 8kHz)
|
||||
|
||||
} */
|
||||
|
||||
#define PPS_BANK_SIZE 14
|
||||
#define PPS_SAMPLE_RATE 16000
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t start_pointer;
|
||||
uint16_t sample_length;
|
||||
uint8_t _pitch;
|
||||
uint8_t _vol;
|
||||
} PPS_HEADER;
|
||||
|
||||
|
||||
void DivEngine::loadPPS(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader.seek(0, SEEK_SET);
|
||||
|
||||
PPS_HEADER headers[PPS_BANK_SIZE];
|
||||
|
||||
for(int i = 0; i < PPS_BANK_SIZE; i++)
|
||||
{
|
||||
headers[i].start_pointer = (unsigned short)reader.readS();
|
||||
headers[i].sample_length = (unsigned short)reader.readS();
|
||||
headers[i]._pitch = (unsigned char)reader.readC();
|
||||
headers[i]._vol = (unsigned char)reader.readC();
|
||||
}
|
||||
|
||||
for(int i = 0; i < PPS_BANK_SIZE; i++)
|
||||
{
|
||||
if(headers[i].start_pointer != 0 || headers[i].sample_length != 0
|
||||
|| headers[i]._pitch != 0 || headers[i]._vol != 0)
|
||||
{
|
||||
DivSample* s = new DivSample;
|
||||
|
||||
s->rate = PPS_SAMPLE_RATE;
|
||||
s->centerRate = PPS_SAMPLE_RATE;
|
||||
s->depth = DIV_SAMPLE_DEPTH_8BIT;
|
||||
s->init(headers[i].sample_length * 2); //byte per sample
|
||||
|
||||
reader.seek((int)headers[i].start_pointer, SEEK_SET);
|
||||
|
||||
int sample_pos = 0;
|
||||
|
||||
for(int j = 0; j < headers[i].sample_length; j++)
|
||||
{
|
||||
unsigned char curr_byte = (unsigned char)reader.readC();
|
||||
|
||||
s->data8[sample_pos] = (curr_byte >> 4) | (curr_byte & 0xf0);
|
||||
s->data8[sample_pos] += 0x80;
|
||||
sample_pos++;
|
||||
s->data8[sample_pos] = (curr_byte << 4) | (curr_byte & 0xf);
|
||||
s->data8[sample_pos] += 0x80;
|
||||
sample_pos++;
|
||||
}
|
||||
|
||||
ret.push_back(s);
|
||||
|
||||
logI("pps: start %d len %d", headers[i].start_pointer, headers[i].sample_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EndOfFileException& e)
|
||||
{
|
||||
lastError=_("premature end of file");
|
||||
logE("premature end of file");
|
||||
}
|
||||
}
|
||||
158
src/engine/fileOps/pvi.cpp
Normal file
158
src/engine/fileOps/pvi.cpp
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "fileOpsCommon.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
//PVI YM2608 ADPCM-B sample bank
|
||||
|
||||
/* =======================================
|
||||
General
|
||||
=======================================
|
||||
|
||||
ADPCM RAM addresses: see docs/common.txt {
|
||||
|
||||
address_start = 0x0000
|
||||
file_header_size = 0x0210
|
||||
|
||||
}
|
||||
|
||||
=======================================
|
||||
Header
|
||||
=======================================
|
||||
|
||||
0x0000 Identifier (4b)
|
||||
"PVI2"
|
||||
0x0004 - 0x0007 Unknown Settings (4b)
|
||||
Unknown mappings PCME switches <-> Values
|
||||
"0x10 0x00 0x10 0x02" in all example files
|
||||
First 1h may be Start Address in ADPCM RAM?
|
||||
0x0008 - 0x0009 "Delta-N" playback frequency (1h)
|
||||
Default 0x49BA "== 16kHz"??
|
||||
0x000A Unknown (1b)
|
||||
RAM type? (Docs indicate values 0 or 8, all example files have value 0x02?)
|
||||
0x000B Amount of defined Samples (1b)
|
||||
0x000C - 0x000F Unknown (4b)
|
||||
Padding?
|
||||
"0x00 0x00 0x00 0x00" in all example files
|
||||
0x0010 - 0x020F 128 * {
|
||||
|
||||
Start of Sample (32b blocks) in ADPCM RAM (1h)
|
||||
End of Sample (32b blocks) in ADPCM RAM (1h)
|
||||
|
||||
(0x0000 0x0000 -> no sample for this instrument ID)
|
||||
|
||||
}
|
||||
|
||||
|
||||
=======================================
|
||||
Body
|
||||
=======================================
|
||||
|
||||
Stream of Sample Data {
|
||||
|
||||
Yamaha ADPCM-B encoding (4-Bit Signed ADPCM)
|
||||
Mono
|
||||
Sample rate as specified earlier
|
||||
(examples i have seems Stereo or 32kHz despite "16kHz" playback frequency setting?)
|
||||
|
||||
} */
|
||||
|
||||
#define PVIV2_FILE_SIG "PVI2"
|
||||
#define PVIV1_FILE_SIG "PVI1"
|
||||
|
||||
#define PVI_BANK_SIZE 128
|
||||
#define PVI_SAMPLE_RATE 16000
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint16_t start_pointer;
|
||||
uint16_t end_pointer;
|
||||
} PVI_HEADER;
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
#define ADPCM_DATA_START 0x0210
|
||||
|
||||
void DivEngine::loadPVI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader.seek(0, SEEK_SET);
|
||||
|
||||
String file_sig = reader.readString(4);
|
||||
if(file_sig != PVIV1_FILE_SIG && file_sig != PVIV2_FILE_SIG) return;
|
||||
|
||||
unsigned int unknown_settings = (unsigned int)reader.readI();
|
||||
UNUSED(unknown_settings);
|
||||
unsigned short delta_n = (unsigned short)reader.readS();
|
||||
UNUSED(delta_n);
|
||||
unsigned char one_byte = (unsigned char)reader.readC();
|
||||
UNUSED(one_byte);
|
||||
unsigned char amount_of_samples = (unsigned char)reader.readC();
|
||||
UNUSED(amount_of_samples);
|
||||
unsigned int padding = (unsigned int)reader.readI();
|
||||
UNUSED(padding);
|
||||
|
||||
PVI_HEADER headers[PVI_BANK_SIZE];
|
||||
|
||||
for(int i = 0; i < PVI_BANK_SIZE; i++)
|
||||
{
|
||||
headers[i].start_pointer = (unsigned short)reader.readS();
|
||||
headers[i].end_pointer = (unsigned short)reader.readS();
|
||||
}
|
||||
|
||||
for(int i = 0; i < PVI_BANK_SIZE; i++)
|
||||
{
|
||||
if((headers[i].start_pointer != 0 || headers[i].end_pointer != 0) && headers[i].start_pointer < headers[i].end_pointer)
|
||||
{
|
||||
DivSample* s = new DivSample;
|
||||
|
||||
s->rate = PVI_SAMPLE_RATE;
|
||||
s->centerRate = PVI_SAMPLE_RATE;
|
||||
s->depth = DIV_SAMPLE_DEPTH_ADPCM_B;
|
||||
s->init((headers[i].end_pointer - headers[i].start_pointer) * 32 * 2);
|
||||
|
||||
int sample_pos = 0;
|
||||
int sample_length = (headers[i].end_pointer - headers[i].start_pointer) * 32;
|
||||
|
||||
reader.seek(ADPCM_DATA_START + headers[i].start_pointer * 32, SEEK_SET);
|
||||
|
||||
for(int j = 0; j < sample_length; j++)
|
||||
{
|
||||
unsigned char curr_byte = (unsigned char)reader.readC();
|
||||
//curr_byte=(curr_byte<<4)|(curr_byte>>4);
|
||||
|
||||
s->dataB[sample_pos] = curr_byte;
|
||||
sample_pos++;
|
||||
}
|
||||
|
||||
logI("pvi: start %d end %d len in bytes %d", headers[i].start_pointer, headers[i].end_pointer, (headers[i].end_pointer - headers[i].start_pointer) * 32);
|
||||
|
||||
ret.push_back(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EndOfFileException& e)
|
||||
{
|
||||
lastError=_("premature end of file");
|
||||
logE("premature end of file");
|
||||
}
|
||||
}
|
||||
155
src/engine/fileOps/pzi.cpp
Normal file
155
src/engine/fileOps/pzi.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "fileOpsCommon.h"
|
||||
|
||||
class DivEngine;
|
||||
|
||||
//PZI 8-bit PCM sample bank
|
||||
|
||||
/* =======================================
|
||||
Header
|
||||
=======================================
|
||||
|
||||
0x0000 Identifier (4b)
|
||||
"PZI1"
|
||||
0x0004 - 0x001F Unknown (28b)
|
||||
Part of identifier? Settings?
|
||||
All (\0)s in all example files
|
||||
0x0020 - 0x091F 128 * {
|
||||
|
||||
Start of Sample after header (2h)
|
||||
Length of Sample (2h)
|
||||
Offset of loop start from sample start (2h)
|
||||
Offset of loop end from sample start (2h)
|
||||
Sample rate (1h)
|
||||
|
||||
(0xFFFFFFFF 0xFFFFFFFF loop offsets -> no loop information)
|
||||
|
||||
}
|
||||
|
||||
|
||||
=======================================
|
||||
Body
|
||||
=======================================
|
||||
|
||||
Stream of Sample Data {
|
||||
|
||||
Unsigned 8-Bit
|
||||
Mono
|
||||
Sample rate as specified in header
|
||||
|
||||
} */
|
||||
|
||||
#define PZI_BANK_SIZE 128
|
||||
|
||||
#define PZI_FILE_SIG "PZI1"
|
||||
|
||||
#define NO_LOOP (0xFFFFFFFFU)
|
||||
|
||||
#define SAMPLE_DATA_OFFSET 0x0920
|
||||
|
||||
#define MAX_SANITY_CAP 9999999
|
||||
|
||||
#define HEADER_JUNK_SIZE 28
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t start_pointer;
|
||||
uint32_t sample_length;
|
||||
uint32_t loop_start;
|
||||
uint32_t loop_end;
|
||||
uint16_t sample_rate;
|
||||
} PZI_HEADER;
|
||||
|
||||
#define UNUSED(x) (void)(x)
|
||||
|
||||
void DivEngine::loadPZI(SafeReader& reader, std::vector<DivSample*>& ret, String& stripPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
reader.seek(0, SEEK_SET);
|
||||
|
||||
PZI_HEADER headers[PZI_BANK_SIZE];
|
||||
|
||||
String file_sig = reader.readString(4);
|
||||
if(file_sig != PZI_FILE_SIG) return;
|
||||
|
||||
for (int i = 0; i < HEADER_JUNK_SIZE; i++)
|
||||
{
|
||||
unsigned char curr_byte = (unsigned char)reader.readC();
|
||||
UNUSED(curr_byte);
|
||||
}
|
||||
|
||||
for(int i = 0; i < PZI_BANK_SIZE; i++)
|
||||
{
|
||||
headers[i].start_pointer = (unsigned int)reader.readI();
|
||||
headers[i].sample_length = (unsigned int)reader.readI();
|
||||
headers[i].loop_start = (unsigned int)reader.readI();
|
||||
headers[i].loop_end = (unsigned int)reader.readI();
|
||||
headers[i].sample_rate = (unsigned short)reader.readS();
|
||||
}
|
||||
|
||||
for(int i = 0; i < PZI_BANK_SIZE; i++)
|
||||
{
|
||||
if (headers[i].start_pointer < MAX_SANITY_CAP && headers[i].sample_length < MAX_SANITY_CAP &&
|
||||
headers[i].loop_start < MAX_SANITY_CAP && headers[i].loop_end < MAX_SANITY_CAP &&
|
||||
headers[i].start_pointer > 0 && headers[i].sample_length > 0)
|
||||
{
|
||||
DivSample* s = new DivSample;
|
||||
|
||||
s->rate = headers[i].sample_rate;
|
||||
s->centerRate = headers[i].sample_rate;
|
||||
s->depth = DIV_SAMPLE_DEPTH_8BIT;
|
||||
s->init(headers[i].sample_length); //byte per sample
|
||||
|
||||
reader.seek((int)headers[i].start_pointer + SAMPLE_DATA_OFFSET, SEEK_SET);
|
||||
|
||||
int sample_pos = 0;
|
||||
|
||||
for (uint32_t j = 0; j < headers[i].sample_length; j++)
|
||||
{
|
||||
unsigned char curr_byte = (unsigned char)reader.readC();
|
||||
curr_byte += 0x80;
|
||||
|
||||
s->data8[sample_pos] = curr_byte;
|
||||
sample_pos++;
|
||||
}
|
||||
|
||||
if (headers[i].loop_start != NO_LOOP && headers[i].loop_end != NO_LOOP)
|
||||
{
|
||||
s->loop = true;
|
||||
s->loopMode = DIV_SAMPLE_LOOP_FORWARD;
|
||||
s->loopStart = headers[i].loop_start;
|
||||
s->loopEnd = headers[i].loop_end;
|
||||
}
|
||||
|
||||
ret.push_back(s);
|
||||
|
||||
logI("pzi: start %d len %d sample rate %d loop start %d loop end %d", headers[i].start_pointer, headers[i].sample_length,
|
||||
headers[i].sample_rate, headers[i].loop_start, headers[i].loop_end);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EndOfFileException& e)
|
||||
{
|
||||
lastError=_("premature end of file");
|
||||
logE("premature end of file");
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -261,6 +261,7 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) {
|
|||
w->writeText(fmt::sprintf(" - mode: %s\n",sampleLoopModes[sample->loopMode&3]));
|
||||
}
|
||||
w->writeText(fmt::sprintf("- BRR emphasis: %s\n",trueFalse[sample->brrEmphasis?1:0]));
|
||||
w->writeText(fmt::sprintf("- no BRR filters: %s\n",trueFalse[sample->brrNoFilter?1:0]));
|
||||
w->writeText(fmt::sprintf("- dither: %s\n",trueFalse[sample->dither?1:0]));
|
||||
|
||||
// TODO' render matrix
|
||||
|
|
|
|||
906
src/engine/fileOps/tfm.cpp
Normal file
906
src/engine/fileOps/tfm.cpp
Normal file
|
|
@ -0,0 +1,906 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "fileOpsCommon.h"
|
||||
|
||||
class TFMRLEReader;
|
||||
|
||||
struct TFMEndOfFileException {
|
||||
TFMRLEReader* reader;
|
||||
size_t finalSize;
|
||||
TFMEndOfFileException(TFMRLEReader* r, size_t fs):
|
||||
reader(r),
|
||||
finalSize(fs) {}
|
||||
};
|
||||
|
||||
|
||||
class TFMRLEReader {
|
||||
const unsigned char* buf;
|
||||
size_t len;
|
||||
size_t curSeek;
|
||||
bool inTag;
|
||||
int tagLenLeft;
|
||||
signed char tagChar;
|
||||
|
||||
void decodeRLE(unsigned char prevChar) {
|
||||
int lenShift=0;
|
||||
tagLenLeft=0;
|
||||
unsigned char rleTag=0;
|
||||
|
||||
do {
|
||||
rleTag=readCNoRLE();
|
||||
tagLenLeft|=(rleTag&0x7F)<<lenShift;
|
||||
lenShift+=7;
|
||||
logD("offset: %x, RLE tag: %X, len shift: %d, len left: %d",curSeek,rleTag,lenShift,tagLenLeft);
|
||||
} while (!(rleTag&0x80));
|
||||
|
||||
if (tagLenLeft) {
|
||||
// sync back since we've already read one character
|
||||
inTag=true;
|
||||
tagLenLeft--;
|
||||
tagChar=prevChar;
|
||||
} else {
|
||||
tagChar=0x80;
|
||||
}
|
||||
logD("tag finished: len left: %d, char: %X",tagLenLeft,tagChar);
|
||||
}
|
||||
|
||||
public:
|
||||
TFMRLEReader(const void* b, size_t l) :
|
||||
buf((const unsigned char*)b),
|
||||
len(l),
|
||||
curSeek(0),
|
||||
inTag(false),
|
||||
tagLenLeft(0),
|
||||
tagChar(0) {}
|
||||
|
||||
// these functions may throw TFMEndOfFileException
|
||||
unsigned char readC() {
|
||||
if (inTag) {
|
||||
if (tagLenLeft<=0) {
|
||||
inTag=false;
|
||||
return readC();
|
||||
}
|
||||
tagLenLeft--;
|
||||
logD("one char RLE decompressed, tag left: %d, char: %d",tagLenLeft,tagChar);
|
||||
return tagChar;
|
||||
}
|
||||
if (curSeek>len) throw TFMEndOfFileException(this,len);
|
||||
|
||||
unsigned char ret=buf[curSeek++];
|
||||
|
||||
// MISLEADING DOCUMENTATION: while TFM music maker's documentation says if the next byte
|
||||
// is zero, then it's not a tag but just 0x80 (for example: 0x00 0x80 0x00 = 0x00 0x80)
|
||||
// this is actually wrong
|
||||
// through research and experimentation, there are times that TFM music maker
|
||||
// will use 0x80 0x00 for actual tags (for example: 0x00 0x80 0x00 0x84 = 512 times 0x00
|
||||
// in certain parts of the header and footer)
|
||||
// TFM music maker actually uses double 0x80 to escape the 0x80
|
||||
// for example: 0xDA 0x80 0x80 0x00 0x23 = 0xDA 0x80 0x00 0x23)
|
||||
if (ret==0x80) {
|
||||
decodeRLE(buf[curSeek-2]);
|
||||
tagLenLeft--;
|
||||
return tagChar;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
signed char readCNoRLE() {
|
||||
if (curSeek>len) throw TFMEndOfFileException(this,len);
|
||||
return buf[curSeek++];
|
||||
}
|
||||
|
||||
void read(unsigned char* b, size_t l) {
|
||||
int i=0;
|
||||
while(l--) {
|
||||
unsigned char nextChar=readC();
|
||||
b[i++]=nextChar;
|
||||
logD("read next char: %x, index: %d",nextChar,i);
|
||||
}
|
||||
}
|
||||
|
||||
void readNoRLE(unsigned char *b, size_t l) {
|
||||
int i=0;
|
||||
while (l--) {
|
||||
b[i++]=buf[curSeek++];
|
||||
if (curSeek>len) throw TFMEndOfFileException(this,len);
|
||||
}
|
||||
}
|
||||
|
||||
short readS() {
|
||||
return readC()|readC()<<8;
|
||||
}
|
||||
|
||||
short readSNoRLE() {
|
||||
if (curSeek+2>len) throw TFMEndOfFileException(this,len);
|
||||
short ret=buf[curSeek]|buf[curSeek+1]<<8;
|
||||
curSeek+=2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
String readString(size_t l) {
|
||||
String ret;
|
||||
ret.reserve(l);
|
||||
while (l--) {
|
||||
unsigned char byte=readC();
|
||||
if (!byte) {
|
||||
skip(l);
|
||||
break;
|
||||
}
|
||||
ret += byte;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
void skip(size_t l) {
|
||||
// quick and dirty
|
||||
while (l--) {
|
||||
logD("skipping l %d",l);
|
||||
readC();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
String TFMparseDate(short date) {
|
||||
return fmt::sprintf("%02d.%02d.%02d",date>>11,(date>>7)&0xF,date&0x7F);
|
||||
}
|
||||
|
||||
struct TFMSpeed {
|
||||
unsigned char speedEven;
|
||||
unsigned char speedOdd;
|
||||
unsigned char interleaveFactor;
|
||||
|
||||
bool operator==(const TFMSpeed &s) const {
|
||||
return speedEven==s.speedEven && speedOdd==s.speedOdd && interleaveFactor==s.interleaveFactor;
|
||||
}
|
||||
};
|
||||
|
||||
// to make it work with map
|
||||
namespace std {
|
||||
template<> struct hash<TFMSpeed>
|
||||
{
|
||||
size_t operator()(const TFMSpeed& s) const noexcept {
|
||||
return s.speedEven<<16|s.speedOdd<<8|s.interleaveFactor;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
struct TFMParsePatternInfo {
|
||||
TFMRLEReader* reader;
|
||||
unsigned char maxPat;
|
||||
unsigned char* patLens;
|
||||
unsigned char* orderList;
|
||||
unsigned char speedEven;
|
||||
unsigned char speedOdd;
|
||||
unsigned char interleaveFactor;
|
||||
bool* patExists;
|
||||
DivSong* ds;
|
||||
int* insNumMaps;
|
||||
bool v2;
|
||||
};
|
||||
|
||||
void TFMParsePattern(struct TFMParsePatternInfo info) {
|
||||
// PATTERN DATA FORMAT (not described properly in the documentation)
|
||||
// for each channel in a pattern:
|
||||
// - note data (256 bytes)
|
||||
// - volume data (256 bytes, values always 0x00-0x1F)
|
||||
// - instrument number data (256 bytes)
|
||||
// - effect number (256 bytes, values 0x0-0x23 (to represent 0-F and G-Z))
|
||||
// - effect value (256 bytes)
|
||||
// - extra 3 effects (1536 bytes 256x3x2) (ONLY ON V2)
|
||||
// notes are stored as an inverted value of note+octave*12
|
||||
// key-offs are stored in the note data as 0x01
|
||||
unsigned char patDataBuf[256];
|
||||
unsigned short lastSlide=0;
|
||||
unsigned short lastVibrato=0;
|
||||
|
||||
struct TFMSpeed speed;
|
||||
DivGroovePattern groove;
|
||||
speed.speedEven=info.speedEven;
|
||||
speed.speedOdd=info.speedOdd;
|
||||
speed.interleaveFactor=info.interleaveFactor;
|
||||
int speedGrooveIndex=1;
|
||||
|
||||
int usedEffectsCol=0;
|
||||
std::unordered_map<TFMSpeed, int> speeds({{speed, 0}});
|
||||
|
||||
// initialize the global groove pattern first
|
||||
if (speed.interleaveFactor>8) {
|
||||
logW("speed interleave factor is bigger than 8, speed information may be inaccurate");
|
||||
speed.interleaveFactor=8;
|
||||
}
|
||||
for (int i=0; i<speed.interleaveFactor; i++) {
|
||||
groove.val[i]=speed.speedEven;
|
||||
groove.val[i+speed.interleaveFactor]=speed.speedOdd;
|
||||
}
|
||||
groove.len=speed.interleaveFactor*2;
|
||||
|
||||
info.ds->grooves.push_back(groove);
|
||||
|
||||
for (int i=0; i<256; i++) {
|
||||
if (i>info.maxPat) break;
|
||||
else if (!info.patExists[i]) {
|
||||
logD("skipping pattern %d",i);
|
||||
info.reader->skip((info.v2) ? 16896 : 7680);
|
||||
continue;
|
||||
}
|
||||
|
||||
logD("parsing pattern %d",i);
|
||||
for (int j=0; j<6; j++) {
|
||||
DivPattern* pat = info.ds->subsong[0]->pat[j].data[i];
|
||||
|
||||
// notes
|
||||
info.reader->read(patDataBuf,256);
|
||||
|
||||
logD("parsing notes of pattern %d channel %d",i,j);
|
||||
for (int k=0; k<256; k++) {
|
||||
if (patDataBuf[k]==0) continue;
|
||||
else if (patDataBuf[k]==1) {
|
||||
// note off
|
||||
pat->data[k][0]=100;
|
||||
} else {
|
||||
unsigned char invertedNote=~patDataBuf[k];
|
||||
pat->data[k][0]=invertedNote%12;
|
||||
pat->data[k][1]=(invertedNote/12)-1;
|
||||
|
||||
if (pat->data[k][0]==0) {
|
||||
pat->data[k][0]=12;
|
||||
pat->data[k][1]--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// volume
|
||||
info.reader->read(patDataBuf,256);
|
||||
|
||||
logD("parsing volumes of pattern %d channel %d",i,j);
|
||||
for (int k=0; k<256; k++) {
|
||||
if (patDataBuf[k]==0) continue;
|
||||
else pat->data[k][3]=0x60+patDataBuf[k];
|
||||
}
|
||||
|
||||
// instrument
|
||||
info.reader->read(patDataBuf,256);
|
||||
|
||||
logD("parsing instruments of pattern %d channel %d",i,j);
|
||||
for (int k=0; k<256; k++) {
|
||||
if (patDataBuf[k]==0) continue;
|
||||
pat->data[k][2]=info.insNumMaps[patDataBuf[k]-1];
|
||||
}
|
||||
|
||||
// effects
|
||||
|
||||
int numEffectsCol=(info.v2) ? 4 : 1;
|
||||
for (int l=0; l<numEffectsCol; l++) {
|
||||
unsigned char effectNum[256];
|
||||
unsigned char effectVal[256];
|
||||
info.reader->read(effectNum,256);
|
||||
info.reader->read(effectVal,256);
|
||||
|
||||
for (int k=0; k<256; k++) {
|
||||
if (effectNum[k] || effectVal[k]) usedEffectsCol=l+1;
|
||||
switch (effectNum[k]) {
|
||||
case 0:
|
||||
// arpeggio or no effect (if effect val is 0)
|
||||
if (effectVal[k]==0) break;
|
||||
pat->data[k][4+(l*2)]=effectNum[k];
|
||||
pat->data[k][5+(l*2)]=effectVal[k];
|
||||
break;
|
||||
case 1:
|
||||
// pitch slide up
|
||||
case 2:
|
||||
// pitch slide down
|
||||
pat->data[k][4+(l*2)]=effectNum[k];
|
||||
if (effectVal[k]) {
|
||||
lastSlide=effectVal[k];
|
||||
pat->data[k][5+(l*2)]=effectVal[k];
|
||||
} else {
|
||||
pat->data[k][5+(l*2)]=lastSlide;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
// portamento
|
||||
case 4:
|
||||
// vibrato
|
||||
pat->data[k][5+(l*2)]=0;
|
||||
if (effectVal[k]&0xF0) {
|
||||
pat->data[k][5+(l*2)]|=effectVal[k]&0xF0;
|
||||
} else {
|
||||
pat->data[k][5+(l*2)]|=lastVibrato&0xF0;
|
||||
}
|
||||
if (effectVal[k]&0x0F) {
|
||||
pat->data[k][5+(l*2)]|=effectVal[k]&0x0F;
|
||||
} else {
|
||||
pat->data[k][5+(l*2)]|=lastVibrato&0x0F;
|
||||
}
|
||||
pat->data[k][4+(l*2)]=effectNum[k];
|
||||
lastVibrato=pat->data[k][5+(l*2)];
|
||||
break;
|
||||
case 5:
|
||||
// poramento + volume slide
|
||||
pat->data[k][4+(l*2)]=0x06;
|
||||
pat->data[k][5+(l*2)]=effectVal[k];
|
||||
break;
|
||||
case 6:
|
||||
// vibrato + volume slide
|
||||
pat->data[k][4+(l*2)]=0x05;
|
||||
pat->data[k][5+(l*2)]=effectVal[k];
|
||||
break;
|
||||
case 8:
|
||||
// modify TL of operator 1
|
||||
pat->data[k][4+(l*2)]=0x12;
|
||||
pat->data[k][5+(l*2)]=effectVal[k];
|
||||
break;
|
||||
case 9:
|
||||
// modify TL of operator 2
|
||||
pat->data[k][4+(l*2)]=0x13;
|
||||
pat->data[k][5+(l*2)]=effectVal[k];
|
||||
break;
|
||||
case 10:
|
||||
// volume slide
|
||||
pat->data[k][4+(l*2)]=0xA;
|
||||
pat->data[k][5+(l*2)]=effectVal[k];
|
||||
break;
|
||||
case 11:
|
||||
// multi-frequency mode of CH3 control
|
||||
// TODO
|
||||
case 12:
|
||||
// modify TL of operator 3
|
||||
pat->data[k][4+(l*2)]=0x14;
|
||||
pat->data[k][5+(l*2)]=effectVal[k];
|
||||
break;
|
||||
case 13:
|
||||
// modify TL of operator 2
|
||||
pat->data[k][4+(l*2)]=0x15;
|
||||
pat->data[k][5+(l*2)]=effectVal[k];
|
||||
break;
|
||||
case 14:
|
||||
switch (effectVal[k]>>4) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
// modify multiplier of operators
|
||||
pat->data[k][4+(l*2)]=0x16;
|
||||
pat->data[k][5+(l*2)]=((effectVal[k]&0xF0)+0x100)|(effectVal[k]&0xF);
|
||||
break;
|
||||
case 8:
|
||||
// pan
|
||||
pat->data[k][4+(l*2)]=0x80;
|
||||
if ((effectVal[k]&0xF)==1) {
|
||||
pat->data[k][5+(l*2)]=0;
|
||||
} else if ((effectVal[k]&0xF)==2) {
|
||||
pat->data[k][5+(l*2)]=0xFF;
|
||||
} else {
|
||||
pat->data[k][5+(l*2)]=0x80;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 15:
|
||||
// speed
|
||||
|
||||
if (effectVal[k]==0) {
|
||||
// if speed is set to zero (reset to global values)
|
||||
speed.speedEven=info.speedEven;
|
||||
speed.speedOdd=info.speedOdd;
|
||||
speed.interleaveFactor=info.interleaveFactor;
|
||||
} else if (effectVal[k]>>4==0) {
|
||||
// if the top nibble is set to zero (set interleave factor)
|
||||
speed.interleaveFactor=effectVal[k]&0xF;
|
||||
} else if ((effectVal[k]>>4)==(effectVal[k]&0xF)) {
|
||||
// if both speeds are equal
|
||||
pat->data[k][4+(l*2)]=0x0F;
|
||||
unsigned char speedSet=effectVal[k]>>4;
|
||||
pat->data[k][5+(l*2)]=speedSet;
|
||||
break;
|
||||
} else {
|
||||
speed.speedEven=effectVal[k]>>4;
|
||||
speed.speedOdd=effectVal[k]&0xF;
|
||||
}
|
||||
|
||||
auto speedIndex = speeds.find(speed);
|
||||
if (speedIndex != speeds.end()) {
|
||||
pat->data[k][4+(l*2)]=0x09;
|
||||
pat->data[k][5+(l*2)]=speedIndex->second;
|
||||
break;
|
||||
}
|
||||
if (speed.interleaveFactor>8) {
|
||||
logW("speed interleave factor is bigger than 8, speed information may be inaccurate");
|
||||
speed.interleaveFactor=8;
|
||||
}
|
||||
for (int i=0; i<speed.interleaveFactor; i++) {
|
||||
groove.val[i]=speed.speedEven;
|
||||
if (i+speed.interleaveFactor<16) {
|
||||
groove.val[i+speed.interleaveFactor]=speed.speedOdd;
|
||||
}
|
||||
}
|
||||
groove.len=speed.interleaveFactor*2;
|
||||
|
||||
info.ds->grooves.push_back(groove);
|
||||
speeds[speed]=speedGrooveIndex;
|
||||
|
||||
pat->data[k][4+(l*2)]=0x09;
|
||||
pat->data[k][5+(l*2)]=speedGrooveIndex;
|
||||
speedGrooveIndex++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
info.ds->subsong[0]->pat[j].effectCols=(usedEffectsCol*2)+1;
|
||||
|
||||
// put a "jump to next pattern" effect if the pattern is smaller than the maximum pattern length
|
||||
if (info.patLens[i]!=0 && info.patLens[i]<info.ds->subsong[0]->patLen) {
|
||||
pat->data[info.patLens[i]-1][4+(usedEffectsCol*4)]=0x0D;
|
||||
pat->data[info.patLens[i]-1][5+(usedEffectsCol*4)]=0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2nd pass: fixing pitch slides, arpeggios, etc. so the result doesn't sound weird.
|
||||
|
||||
bool chArpeggio[6]={false};
|
||||
bool chVibrato[6]={false};
|
||||
bool chPorta[6]={false};
|
||||
bool chVolumeSlide[6]={false};
|
||||
|
||||
for (int i=0; i<info.ds->subsong[0]->ordersLen; i++) {
|
||||
for (int j=0; j<6; j++) {
|
||||
for (int l=0; l<usedEffectsCol; l++) {
|
||||
DivPattern* pat = info.ds->subsong[0]->pat[j].data[info.orderList[i]];
|
||||
|
||||
// default instrument
|
||||
if (i==0 && pat->data[0][2]==-1) pat->data[0][2]=0;
|
||||
|
||||
unsigned char truePatLen=(info.patLens[info.orderList[i]]<info.ds->subsong[0]->patLen) ? info.patLens[info.orderList[i]] : info.ds->subsong[0]->patLen;
|
||||
|
||||
for (int k=0; k<truePatLen; k++) {
|
||||
if (chArpeggio[j] && pat->data[k][4+(l*2)]!=0x00 && pat->data[k][0]!=-1) {
|
||||
pat->data[k][4+usedEffectsCol*2+(l*2)]=0x00;
|
||||
pat->data[k][5+usedEffectsCol*2+(l*2)]=0;
|
||||
chArpeggio[j]=false;
|
||||
} else if (chPorta[j] && pat->data[k][4+(l*2)]!=0x03 && pat->data[k][4+(l*2)]!=0x01 && pat->data[k][4+(l*2)]!=0x02) {
|
||||
pat->data[k][4+usedEffectsCol*2+(l*2)]=0x03;
|
||||
pat->data[k][5+usedEffectsCol*2+(l*2)]=0;
|
||||
chPorta[j]=false;
|
||||
} else if (chVibrato[j] && pat->data[k][4+(l*2)]!=0x04 && pat->data[k][0]!=-1) {
|
||||
pat->data[k][4+usedEffectsCol*2+(l*2)]=0x04;
|
||||
pat->data[k][5+usedEffectsCol*2+(l*2)]=0;
|
||||
chVibrato[j]=false;
|
||||
} else if (chVolumeSlide[j] && pat->data[k][4+(l*2)]!=0x0A) {
|
||||
pat->data[k][4+usedEffectsCol*2+(l*2)]=0x0A;
|
||||
pat->data[k][5+usedEffectsCol*2+(l*2)]=0;
|
||||
chVolumeSlide[j]=false;
|
||||
}
|
||||
|
||||
switch (pat->data[k][4+l]) {
|
||||
case 0:
|
||||
chArpeggio[j]=true;
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
chPorta[j]=true;
|
||||
break;
|
||||
case 4:
|
||||
chVibrato[j]=true;
|
||||
break;
|
||||
case 0xA:
|
||||
chVolumeSlide[j]=true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DivEngine::loadTFMv1(unsigned char* file, size_t len) {
|
||||
// the documentation for this version is in russian only
|
||||
struct InvalidHeaderException {};
|
||||
bool success=false;
|
||||
TFMRLEReader reader=TFMRLEReader(file,len);
|
||||
|
||||
try {
|
||||
DivSong ds;
|
||||
ds.version=DIV_VERSION_TFE;
|
||||
ds.systemName="Sega Genesis/Mega Drive or TurboSound FM";
|
||||
ds.subsong[0]->hz=50;
|
||||
ds.systemLen=1;
|
||||
|
||||
ds.system[0]=DIV_SYSTEM_YM2612;
|
||||
ds.loopModality=1;
|
||||
|
||||
unsigned char speed=reader.readCNoRLE();
|
||||
unsigned char interleaveFactor=reader.readCNoRLE();
|
||||
|
||||
// TODO: due to limitations with the groove pattern, only interleave factors up to 8
|
||||
// are allowed in furnace
|
||||
if (interleaveFactor>8) {
|
||||
logW("interleave factor is bigger than 8, speed information may be inaccurate");
|
||||
interleaveFactor=8;
|
||||
}
|
||||
if ((speed>>4)==(speed&0xF)) {
|
||||
ds.subsong[0]->speeds.val[0]=speed&0xF;
|
||||
ds.subsong[0]->speeds.len=1;
|
||||
} else {
|
||||
for (int i=0; i<interleaveFactor; i++) {
|
||||
ds.subsong[0]->speeds.val[i]=speed>>4;
|
||||
ds.subsong[0]->speeds.val[i+interleaveFactor]=speed&0xF;
|
||||
}
|
||||
ds.subsong[0]->speeds.len=interleaveFactor*2;
|
||||
}
|
||||
ds.subsong[0]->ordersLen=reader.readCNoRLE();
|
||||
|
||||
// order loop position, unused
|
||||
(void)reader.readCNoRLE();
|
||||
|
||||
ds.createdDate=TFMparseDate(reader.readSNoRLE());
|
||||
ds.revisionDate=TFMparseDate(reader.readSNoRLE());
|
||||
|
||||
// TODO: use this for something, number of saves
|
||||
(void)reader.readSNoRLE();
|
||||
|
||||
// author
|
||||
logD("parsing author");
|
||||
ds.author=reader.readString(64);
|
||||
|
||||
// name
|
||||
logD("parsing name");
|
||||
ds.name=reader.readString(64);
|
||||
|
||||
// notes
|
||||
logD("parsing notes");
|
||||
String notes=reader.readString(384);
|
||||
|
||||
// fix \r\n to \n
|
||||
for (auto& c : notes) {
|
||||
if (c=='\r') {
|
||||
notes.erase(c,1);
|
||||
}
|
||||
}
|
||||
|
||||
// order list
|
||||
logD("parsing order list");
|
||||
unsigned char orderList[256];
|
||||
reader.read(orderList,256);
|
||||
|
||||
bool patExists[256];
|
||||
unsigned char maxPat=0;
|
||||
for (int i=0; i<ds.subsong[0]->ordersLen; i++) {
|
||||
patExists[orderList[i]]=true;
|
||||
if (maxPat<orderList[i]) maxPat=orderList[i];
|
||||
|
||||
for (int j=0; j<6; j++) {
|
||||
ds.subsong[0]->orders.ord[j][i]=orderList[i];
|
||||
ds.subsong[0]->pat[j].data[orderList[i]]=new DivPattern;
|
||||
}
|
||||
}
|
||||
|
||||
DivInstrument* insMaps[256];
|
||||
int insNumMaps[256];
|
||||
|
||||
// instrument names
|
||||
logD("parsing instruments");
|
||||
unsigned char insName[16];
|
||||
int insCount=0;
|
||||
for (int i=0; i<255; i++) {
|
||||
reader.read(insName,16);
|
||||
|
||||
if (memcmp(insName,"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",16)==0) {
|
||||
logD("instrument unused");
|
||||
insNumMaps[i]=i;
|
||||
insMaps[i]=NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
ins->type=DIV_INS_FM;
|
||||
ins->name=String((const char*)insName,strnlen((const char*)insName,16));
|
||||
ds.ins.push_back(ins);
|
||||
|
||||
insNumMaps[i]=insCount;
|
||||
insCount++;
|
||||
|
||||
insMaps[i]=ins;
|
||||
}
|
||||
|
||||
ds.insLen=insCount;
|
||||
|
||||
// instrument data
|
||||
for (int i=0; i<255; i++) {
|
||||
if (!insMaps[i]) {
|
||||
reader.skip(42);
|
||||
continue;
|
||||
}
|
||||
|
||||
insMaps[i]->fm.alg=reader.readC();
|
||||
insMaps[i]->fm.fb=reader.readC();
|
||||
|
||||
for (int j=0; j<4; j++) {
|
||||
insMaps[i]->fm.op[j].mult=reader.readC();
|
||||
insMaps[i]->fm.op[j].dt=reader.readC();
|
||||
insMaps[i]->fm.op[j].tl=reader.readC()^0x7F;
|
||||
insMaps[i]->fm.op[j].rs=reader.readC();
|
||||
insMaps[i]->fm.op[j].ar=reader.readC();
|
||||
insMaps[i]->fm.op[j].dr=reader.readC();
|
||||
insMaps[i]->fm.op[j].d2r=reader.readC();
|
||||
insMaps[i]->fm.op[j].rr=reader.readC();
|
||||
insMaps[i]->fm.op[j].sl=reader.readC();
|
||||
insMaps[i]->fm.op[j].ssgEnv=reader.readC();
|
||||
}
|
||||
}
|
||||
|
||||
ds.notes=notes;
|
||||
|
||||
unsigned char patLens[256];
|
||||
int maxPatLen=0;
|
||||
reader.read(patLens, 256);
|
||||
for (int i=0; i<256; i++) {
|
||||
if (patLens[i]==0) {
|
||||
maxPatLen=256;
|
||||
break;
|
||||
} else if (patLens[i]>maxPatLen) {
|
||||
maxPatLen=patLens[i];
|
||||
}
|
||||
}
|
||||
|
||||
ds.subsong[0]->patLen=maxPatLen;
|
||||
|
||||
struct TFMParsePatternInfo info;
|
||||
info.ds=&ds;
|
||||
info.insNumMaps=insNumMaps;
|
||||
info.maxPat=maxPat;
|
||||
info.patExists=patExists;
|
||||
info.orderList=orderList;
|
||||
info.speedEven=speed>>4;
|
||||
info.speedOdd=speed&0xF;
|
||||
info.interleaveFactor=interleaveFactor;
|
||||
info.patLens=patLens;
|
||||
info.reader=&reader;
|
||||
info.v2=false;
|
||||
TFMParsePattern(info);
|
||||
|
||||
if (active) quitDispatch();
|
||||
BUSY_BEGIN_SOFT;
|
||||
saveLock.lock();
|
||||
song.unload();
|
||||
song=ds;
|
||||
changeSong(0);
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
if (active) {
|
||||
initDispatch();
|
||||
BUSY_BEGIN;
|
||||
renderSamples();
|
||||
reset();
|
||||
BUSY_END;
|
||||
}
|
||||
success=true;
|
||||
} catch(TFMEndOfFileException& e) {
|
||||
lastError="incomplete file!";
|
||||
} catch(InvalidHeaderException& e) {
|
||||
lastError="invalid info header!";
|
||||
}
|
||||
|
||||
delete[] file;
|
||||
return success;
|
||||
}
|
||||
|
||||
bool DivEngine::loadTFMv2(unsigned char* file, size_t len) {
|
||||
struct InvalidHeaderException {};
|
||||
bool success=false;
|
||||
TFMRLEReader reader=TFMRLEReader(file,len);
|
||||
|
||||
try {
|
||||
DivSong ds;
|
||||
ds.version=DIV_VERSION_TFE;
|
||||
ds.systemName="Sega Genesis/Mega Drive or TurboSound FM";
|
||||
ds.subsong[0]->hz=50;
|
||||
ds.systemLen=1;
|
||||
|
||||
ds.system[0]=DIV_SYSTEM_YM2612;
|
||||
ds.loopModality=1;
|
||||
|
||||
unsigned char magic[8]={0};
|
||||
|
||||
reader.readNoRLE(magic,8);
|
||||
if (memcmp(magic,DIV_TFM_MAGIC,8)!=0) throw InvalidHeaderException();
|
||||
|
||||
unsigned char speedEven=reader.readCNoRLE();
|
||||
unsigned char speedOdd=reader.readCNoRLE();
|
||||
unsigned char interleaveFactor=reader.readCNoRLE();
|
||||
|
||||
// TODO: due to limitations with the groove pattern, only interleave factors up to 8
|
||||
// are allowed in furnace
|
||||
if (interleaveFactor>8) {
|
||||
addWarning("interleave factor is bigger than 8, speed information may be inaccurate");
|
||||
interleaveFactor=8;
|
||||
}
|
||||
|
||||
if (speedEven==speedOdd) {
|
||||
ds.subsong[0]->speeds.val[0]=speedEven;
|
||||
ds.subsong[0]->speeds.len=1;
|
||||
} else {
|
||||
for (int i=0; i<interleaveFactor; i++) {
|
||||
ds.subsong[0]->speeds.val[i]=speedEven;
|
||||
ds.subsong[0]->speeds.val[i+interleaveFactor]=speedOdd;
|
||||
}
|
||||
ds.subsong[0]->speeds.len=interleaveFactor*2;
|
||||
}
|
||||
ds.subsong[0]->ordersLen=reader.readCNoRLE();
|
||||
|
||||
// order loop position, unused
|
||||
(void)reader.readCNoRLE();
|
||||
|
||||
ds.createdDate=TFMparseDate(reader.readSNoRLE());
|
||||
ds.revisionDate=TFMparseDate(reader.readSNoRLE());
|
||||
|
||||
// TODO: use this for something, number of saves
|
||||
(void)reader.readSNoRLE();
|
||||
|
||||
// author
|
||||
logD("parsing author");
|
||||
ds.author=reader.readString(64);
|
||||
|
||||
// name
|
||||
logD("parsing name");
|
||||
ds.name=reader.readString(64);
|
||||
|
||||
// notes
|
||||
logD("parsing notes");
|
||||
String notes=reader.readString(384);
|
||||
|
||||
// fix \r\n to \n
|
||||
for (auto& c : notes) {
|
||||
if (c=='\r') {
|
||||
notes.erase(c,1);
|
||||
}
|
||||
}
|
||||
|
||||
// order list
|
||||
logD("parsing order list");
|
||||
unsigned char orderList[256];
|
||||
reader.read(orderList,256);
|
||||
|
||||
bool patExists[256];
|
||||
unsigned char maxPat=0;
|
||||
for (int i=0; i<ds.subsong[0]->ordersLen; i++) {
|
||||
patExists[orderList[i]]=true;
|
||||
if (maxPat<orderList[i]) maxPat=orderList[i];
|
||||
|
||||
for (int j=0; j<6; j++) {
|
||||
ds.subsong[0]->orders.ord[j][i]=orderList[i];
|
||||
ds.subsong[0]->pat[j].data[orderList[i]]=new DivPattern;
|
||||
}
|
||||
}
|
||||
|
||||
DivInstrument* insMaps[256];
|
||||
int insNumMaps[256];
|
||||
|
||||
// instrument names
|
||||
logD("parsing instruments");
|
||||
unsigned char insName[16];
|
||||
int insCount=0;
|
||||
for (int i=0; i<255; i++) {
|
||||
reader.read(insName,16);
|
||||
|
||||
if (memcmp(insName,"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF",16)==0) {
|
||||
logD("instrument unused");
|
||||
insNumMaps[i]=i;
|
||||
insMaps[i]=NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
DivInstrument* ins=new DivInstrument;
|
||||
ins->type=DIV_INS_FM;
|
||||
ins->name=String((const char*)insName,strnlen((const char*)insName,16));
|
||||
ds.ins.push_back(ins);
|
||||
|
||||
insNumMaps[i]=insCount;
|
||||
insCount++;
|
||||
|
||||
insMaps[i]=ins;
|
||||
}
|
||||
|
||||
ds.insLen=insCount;
|
||||
|
||||
// instrument data
|
||||
for (int i=0; i<255; i++) {
|
||||
if (!insMaps[i]) {
|
||||
reader.skip(42);
|
||||
continue;
|
||||
}
|
||||
|
||||
insMaps[i]->fm.alg=reader.readC();
|
||||
insMaps[i]->fm.fb=reader.readC();
|
||||
|
||||
for (int j=0; j<4; j++) {
|
||||
insMaps[i]->fm.op[j].mult=reader.readC();
|
||||
insMaps[i]->fm.op[j].dt=reader.readC();
|
||||
insMaps[i]->fm.op[j].tl=reader.readC()^0x7F;
|
||||
insMaps[i]->fm.op[j].rs=reader.readC();
|
||||
insMaps[i]->fm.op[j].ar=reader.readC()^0x1F;
|
||||
insMaps[i]->fm.op[j].dr=reader.readC()^0x1F;
|
||||
insMaps[i]->fm.op[j].d2r=reader.readC()^0x1F;
|
||||
insMaps[i]->fm.op[j].rr=reader.readC()^0xF;
|
||||
insMaps[i]->fm.op[j].sl=reader.readC();
|
||||
insMaps[i]->fm.op[j].ssgEnv=reader.readC();
|
||||
}
|
||||
}
|
||||
|
||||
ds.notes=notes;
|
||||
|
||||
unsigned char patLens[256];
|
||||
int maxPatLen=0;
|
||||
reader.read(patLens, 256);
|
||||
for (int i=0; i<256; i++) {
|
||||
if (patLens[i]==0) {
|
||||
maxPatLen=256;
|
||||
break;
|
||||
} else if (patLens[i]>maxPatLen) {
|
||||
maxPatLen=patLens[i];
|
||||
}
|
||||
}
|
||||
|
||||
ds.subsong[0]->patLen=maxPatLen;
|
||||
|
||||
struct TFMParsePatternInfo info;
|
||||
info.ds=&ds;
|
||||
info.insNumMaps=insNumMaps;
|
||||
info.maxPat=maxPat;
|
||||
info.patExists=patExists;
|
||||
info.orderList=orderList;
|
||||
info.speedEven=speedEven;
|
||||
info.speedOdd=speedOdd;
|
||||
info.interleaveFactor=interleaveFactor;
|
||||
info.patLens=patLens;
|
||||
info.reader=&reader;
|
||||
info.v2=true;
|
||||
TFMParsePattern(info);
|
||||
|
||||
if (active) quitDispatch();
|
||||
BUSY_BEGIN_SOFT;
|
||||
saveLock.lock();
|
||||
song.unload();
|
||||
song=ds;
|
||||
changeSong(0);
|
||||
recalcChans();
|
||||
saveLock.unlock();
|
||||
BUSY_END;
|
||||
if (active) {
|
||||
initDispatch();
|
||||
BUSY_BEGIN;
|
||||
renderSamples();
|
||||
reset();
|
||||
BUSY_END;
|
||||
}
|
||||
success=true;
|
||||
} catch(TFMEndOfFileException& e) {
|
||||
lastError="incomplete file!";
|
||||
} catch(InvalidHeaderException& e) {
|
||||
lastError="invalid info header!";
|
||||
}
|
||||
|
||||
delete[] file;
|
||||
return success;
|
||||
}
|
||||
1317
src/engine/fileOps/xm.cpp
Normal file
1317
src/engine/fileOps/xm.cpp
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1897,7 +1897,7 @@ std::vector<DivInstrument*> DivEngine::instrumentFromFile(const char* path, bool
|
|||
bool isOldFurnaceIns=false;
|
||||
try {
|
||||
reader.read(magic,4);
|
||||
if (memcmp("FINS",magic,4)==0) {
|
||||
if (memcmp("FINS",magic,4)==0 || memcmp("FINB",magic,4)==0) {
|
||||
isFurnaceInstr=true;
|
||||
logV("found a new Furnace ins");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@
|
|||
#include "sfWrapper.h"
|
||||
#endif
|
||||
|
||||
DivSample* DivEngine::sampleFromFile(const char* path) {
|
||||
std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
|
||||
std::vector<DivSample*> ret;
|
||||
|
||||
if (song.sample.size()>=256) {
|
||||
lastError="too many samples!";
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
BUSY_BEGIN;
|
||||
warnings="";
|
||||
|
|
@ -58,6 +60,110 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
}
|
||||
extS+=i;
|
||||
}
|
||||
|
||||
if(extS == ".pps" || extS == ".ppc" || extS == ".pvi" ||
|
||||
extS == ".pdx" || extS == ".pzi" || extS == ".p86" ||
|
||||
extS == ".p") //sample banks!
|
||||
{
|
||||
String stripPath;
|
||||
const char* pathReduxEnd=strrchr(pathRedux,'.');
|
||||
if (pathReduxEnd==NULL) {
|
||||
stripPath=pathRedux;
|
||||
} else {
|
||||
for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) {
|
||||
stripPath+=*i;
|
||||
}
|
||||
}
|
||||
|
||||
FILE* f=ps_fopen(path,"rb");
|
||||
if (f==NULL) {
|
||||
lastError=strerror(errno);
|
||||
return ret;
|
||||
}
|
||||
unsigned char* buf;
|
||||
ssize_t len;
|
||||
if (fseek(f,0,SEEK_END)!=0) {
|
||||
lastError=strerror(errno);
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
len=ftell(f);
|
||||
if (len<0) {
|
||||
lastError=strerror(errno);
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
lastError=strerror(errno);
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
if (len==0) {
|
||||
lastError=strerror(errno);
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
if (fseek(f,0,SEEK_SET)!=0) {
|
||||
lastError=strerror(errno);
|
||||
fclose(f);
|
||||
return ret;
|
||||
}
|
||||
buf=new unsigned char[len];
|
||||
if (fread(buf,1,len,f)!=(size_t)len) {
|
||||
logW("did not read entire sample bank file buffer!");
|
||||
lastError=_("did not read entire sample bank file!");
|
||||
delete[] buf;
|
||||
return ret;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
SafeReader reader = SafeReader(buf,len);
|
||||
|
||||
if(extS == ".pps")
|
||||
{
|
||||
loadPPS(reader,ret,stripPath);
|
||||
}
|
||||
if(extS == ".ppc")
|
||||
{
|
||||
loadPPC(reader,ret,stripPath);
|
||||
}
|
||||
if(extS == ".pvi")
|
||||
{
|
||||
loadPVI(reader,ret,stripPath);
|
||||
}
|
||||
if(extS == ".pdx")
|
||||
{
|
||||
loadPDX(reader,ret,stripPath);
|
||||
}
|
||||
if(extS == ".pzi")
|
||||
{
|
||||
loadPZI(reader,ret,stripPath);
|
||||
}
|
||||
if(extS == ".p86")
|
||||
{
|
||||
loadP86(reader,ret,stripPath);
|
||||
}
|
||||
if(extS == ".p")
|
||||
{
|
||||
loadP(reader,ret,stripPath);
|
||||
}
|
||||
|
||||
if((int)ret.size() > 0)
|
||||
{
|
||||
int counter = 0;
|
||||
|
||||
for(DivSample* s: ret)
|
||||
{
|
||||
s->name = fmt::sprintf("%s sample %d", stripPath, counter);
|
||||
counter++;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] buf; //done with buffer
|
||||
BUSY_END;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (extS==".dmc" || extS==".brr") { // read as .dmc or .brr
|
||||
size_t len=0;
|
||||
DivSample* sample=new DivSample;
|
||||
|
|
@ -68,7 +174,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_END)<0) {
|
||||
|
|
@ -76,7 +182,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not get file length! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
len=ftell(f);
|
||||
|
|
@ -86,7 +192,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError="file is empty!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
|
|
@ -94,7 +200,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError="file is invalid!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_SET)<0) {
|
||||
|
|
@ -102,7 +208,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not seek to beginning of file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (extS==".dmc") {
|
||||
|
|
@ -120,7 +226,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError="wait... is that right? no I don't think so...";
|
||||
delete sample;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsigned char* dataBuf=sample->dataDPCM;
|
||||
|
|
@ -147,14 +253,14 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError="BRR sample is empty!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
} else if ((len%9)!=0) {
|
||||
fclose(f);
|
||||
BUSY_END;
|
||||
lastError="possibly corrupt BRR sample!";
|
||||
delete sample;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -163,16 +269,17 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
BUSY_END;
|
||||
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));
|
||||
delete sample;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
BUSY_END;
|
||||
return sample;
|
||||
ret.push_back(sample);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef HAVE_SNDFILE
|
||||
lastError="Furnace was not compiled with libsndfile!";
|
||||
return NULL;
|
||||
return ret;
|
||||
#else
|
||||
SF_INFO si;
|
||||
SFWrapper sfWrap;
|
||||
|
|
@ -186,13 +293,13 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
} else {
|
||||
lastError=fmt::sprintf("could not open file! (%s)\nif this is raw sample data, you may import it by right-clicking the Load Sample icon and selecting \"import raw\".",sf_error_number(err));
|
||||
}
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
if (si.frames>16777215) {
|
||||
lastError="this sample is too big! max sample size is 16777215.";
|
||||
sfWrap.doClose();
|
||||
BUSY_END;
|
||||
return NULL;
|
||||
return ret;
|
||||
}
|
||||
void* buf=NULL;
|
||||
sf_count_t sampleLen=sizeof(short);
|
||||
|
|
@ -298,7 +405,8 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
|
|||
if (sample->centerRate>64000) sample->centerRate=64000;
|
||||
sfWrap.doClose();
|
||||
BUSY_END;
|
||||
return sample;
|
||||
ret.push_back(sample);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -397,6 +505,9 @@ DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth,
|
|||
case DIV_SAMPLE_DEPTH_VOX:
|
||||
samples=lenDivided*2;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_IMA_ADPCM:
|
||||
samples=(lenDivided-4)*2;
|
||||
break;
|
||||
case DIV_SAMPLE_DEPTH_8BIT:
|
||||
case DIV_SAMPLE_DEPTH_MULAW:
|
||||
case DIV_SAMPLE_DEPTH_C219:
|
||||
|
|
|
|||
|
|
@ -258,6 +258,14 @@ bool DivInstrumentPowerNoise::operator==(const DivInstrumentPowerNoise& other) {
|
|||
return _C(octave);
|
||||
}
|
||||
|
||||
bool DivInstrumentSID2::operator==(const DivInstrumentSID2& other) {
|
||||
return (
|
||||
_C(volume) &&
|
||||
_C(mixMode) &&
|
||||
_C(noiseMode)
|
||||
);
|
||||
}
|
||||
|
||||
#undef _C
|
||||
|
||||
#define CONSIDER(x,t) \
|
||||
|
|
@ -473,7 +481,9 @@ void DivInstrument::writeFeature64(SafeWriter* w) {
|
|||
w->writeC(((c64.a&15)<<4)|(c64.d&15));
|
||||
w->writeC(((c64.s&15)<<4)|(c64.r&15));
|
||||
w->writeS(c64.duty);
|
||||
w->writeS((unsigned short)((c64.cut&2047)|(c64.res<<12)));
|
||||
w->writeS((unsigned short)((c64.cut&4095)|((c64.res&15)<<12)));
|
||||
|
||||
w->writeC((c64.res>>4)&15);
|
||||
|
||||
FEATURE_END;
|
||||
}
|
||||
|
|
@ -830,6 +840,14 @@ void DivInstrument::writeFeaturePN(SafeWriter* w) {
|
|||
FEATURE_END;
|
||||
}
|
||||
|
||||
void DivInstrument::writeFeatureS2(SafeWriter* w) {
|
||||
FEATURE_BEGIN("S2");
|
||||
|
||||
w->writeC((sid2.volume&15)|((sid2.mixMode&3)<<4)|((sid2.noiseMode&3)<<6));
|
||||
|
||||
FEATURE_END;
|
||||
}
|
||||
|
||||
void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bool insName) {
|
||||
size_t blockStartSeek=0;
|
||||
size_t blockEndSeek=0;
|
||||
|
|
@ -875,6 +893,7 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
|
|||
bool featureNE=false;
|
||||
bool featureEF=false;
|
||||
bool featurePN=false;
|
||||
bool featureS2=false;
|
||||
|
||||
bool checkForWL=false;
|
||||
|
||||
|
|
@ -1112,6 +1131,12 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
|
|||
featureSM=true;
|
||||
featureSL=true;
|
||||
break;
|
||||
case DIV_INS_BIFURCATOR:
|
||||
break;
|
||||
case DIV_INS_SID2:
|
||||
feature64=true;
|
||||
featureS2=true;
|
||||
break;
|
||||
case DIV_INS_MAX:
|
||||
break;
|
||||
case DIV_INS_NULL:
|
||||
|
|
@ -1165,6 +1190,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
|
|||
if (powernoise!=defaultIns.powernoise) {
|
||||
featurePN=true;
|
||||
}
|
||||
if (sid2!=defaultIns.sid2) {
|
||||
featureS2=true;
|
||||
}
|
||||
}
|
||||
|
||||
// check ins name
|
||||
|
|
@ -1313,6 +1341,9 @@ void DivInstrument::putInsData2(SafeWriter* w, bool fui, const DivSong* song, bo
|
|||
if (featurePN) {
|
||||
writeFeaturePN(w);
|
||||
}
|
||||
if (featureS2) {
|
||||
writeFeatureS2(w);
|
||||
}
|
||||
|
||||
if (fui && (featureSL || featureWL)) {
|
||||
w->write("EN",2);
|
||||
|
|
@ -1459,6 +1490,12 @@ void DivInstrument::readFeatureMA(SafeReader& reader, short version) {
|
|||
|
||||
unsigned short macroHeaderLen=reader.readS();
|
||||
|
||||
if (macroHeaderLen==0) {
|
||||
logW("invalid macro header length!");
|
||||
READ_FEAT_END;
|
||||
return;
|
||||
}
|
||||
|
||||
DivInstrumentMacro* target=&std.volMacro;
|
||||
|
||||
while (reader.tell()<endOfFeat) {
|
||||
|
|
@ -1618,9 +1655,13 @@ void DivInstrument::readFeature64(SafeReader& reader, bool& volIsCutoff, short v
|
|||
c64.duty=reader.readS()&4095;
|
||||
|
||||
unsigned short cr=reader.readS();
|
||||
c64.cut=cr&2047;
|
||||
c64.cut=cr&4095;
|
||||
c64.res=cr>>12;
|
||||
|
||||
if (version>=199) {
|
||||
c64.res|=((unsigned char)reader.readC())<<4;
|
||||
}
|
||||
|
||||
READ_FEAT_END;
|
||||
}
|
||||
|
||||
|
|
@ -1681,6 +1722,12 @@ void DivInstrument::readFeatureOx(SafeReader& reader, int op, short version) {
|
|||
|
||||
unsigned short macroHeaderLen=reader.readS();
|
||||
|
||||
if (macroHeaderLen==0) {
|
||||
logW("invalid macro header length!");
|
||||
READ_FEAT_END;
|
||||
return;
|
||||
}
|
||||
|
||||
DivInstrumentMacro* target=&std.opMacros[op].amMacro;
|
||||
|
||||
while (reader.tell()<endOfFeat) {
|
||||
|
|
@ -2125,6 +2172,18 @@ void DivInstrument::readFeaturePN(SafeReader& reader, short version) {
|
|||
READ_FEAT_END;
|
||||
}
|
||||
|
||||
void DivInstrument::readFeatureS2(SafeReader& reader, short version) {
|
||||
READ_FEAT_BEGIN;
|
||||
|
||||
unsigned char next=reader.readC();
|
||||
|
||||
sid2.volume=next&0xf;
|
||||
sid2.mixMode=(next>>4)&3;
|
||||
sid2.noiseMode=next>>6;
|
||||
|
||||
READ_FEAT_END;
|
||||
}
|
||||
|
||||
DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song) {
|
||||
unsigned char featCode[2];
|
||||
bool volIsCutoff=false;
|
||||
|
|
@ -2197,6 +2256,8 @@ DivDataErrors DivInstrument::readInsDataNew(SafeReader& reader, short version, b
|
|||
readFeatureEF(reader,version);
|
||||
} else if (memcmp(featCode,"PN",2)==0) { // PowerNoise
|
||||
readFeaturePN(reader,version);
|
||||
} else if (memcmp(featCode,"S2",2)==0) { // SID2
|
||||
readFeatureS2(reader,version);
|
||||
} else {
|
||||
if (song==NULL && (memcmp(featCode,"SL",2)==0 || (memcmp(featCode,"WL",2)==0))) {
|
||||
// nothing
|
||||
|
|
@ -3005,6 +3066,8 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version, DivS
|
|||
type=1;
|
||||
} else if (memcmp(magic,"FINS",4)==0) {
|
||||
type=2;
|
||||
} else if (memcmp(magic,"FINB",4)==0) { // DIV_FUR_VARIANT_B
|
||||
type=2;
|
||||
} else {
|
||||
logE("invalid instrument header!");
|
||||
return DIV_DATA_INVALID_HEADER;
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ enum DivInstrumentType: unsigned short {
|
|||
DIV_INS_NDS=59,
|
||||
DIV_INS_GBA_DMA=60,
|
||||
DIV_INS_GBA_MINMOD=61,
|
||||
DIV_INS_BIFURCATOR=62,
|
||||
DIV_INS_SID2=63, // coincidence!
|
||||
DIV_INS_MAX,
|
||||
DIV_INS_NULL
|
||||
};
|
||||
|
|
@ -843,6 +845,21 @@ struct DivInstrumentPowerNoise {
|
|||
octave(0) {}
|
||||
};
|
||||
|
||||
struct DivInstrumentSID2 {
|
||||
unsigned char volume;
|
||||
unsigned char mixMode;
|
||||
unsigned char noiseMode;
|
||||
|
||||
bool operator==(const DivInstrumentSID2& other);
|
||||
bool operator!=(const DivInstrumentSID2& other) {
|
||||
return !(*this==other);
|
||||
}
|
||||
DivInstrumentSID2():
|
||||
volume(15),
|
||||
mixMode(0),
|
||||
noiseMode(0) {}
|
||||
};
|
||||
|
||||
struct DivInstrument {
|
||||
String name;
|
||||
DivInstrumentType type;
|
||||
|
|
@ -861,6 +878,7 @@ struct DivInstrument {
|
|||
DivInstrumentSNES snes;
|
||||
DivInstrumentESFM esfm;
|
||||
DivInstrumentPowerNoise powernoise;
|
||||
DivInstrumentSID2 sid2;
|
||||
|
||||
/**
|
||||
* these are internal functions.
|
||||
|
|
@ -887,6 +905,7 @@ struct DivInstrument {
|
|||
void writeFeatureNE(SafeWriter* w);
|
||||
void writeFeatureEF(SafeWriter* w);
|
||||
void writeFeaturePN(SafeWriter* w);
|
||||
void writeFeatureS2(SafeWriter* w);
|
||||
|
||||
void readFeatureNA(SafeReader& reader, short version);
|
||||
void readFeatureFM(SafeReader& reader, short version);
|
||||
|
|
@ -909,6 +928,7 @@ struct DivInstrument {
|
|||
void readFeatureNE(SafeReader& reader, short version);
|
||||
void readFeatureEF(SafeReader& reader, short version);
|
||||
void readFeaturePN(SafeReader& reader, short version);
|
||||
void readFeatureS2(SafeReader& reader, short version);
|
||||
|
||||
DivDataErrors readInsDataOld(SafeReader& reader, short version);
|
||||
DivDataErrors readInsDataNew(SafeReader& reader, short version, bool fui, DivSong* song);
|
||||
|
|
|
|||
|
|
@ -143,7 +143,11 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
|
|||
if (!linger) has=false;
|
||||
break;
|
||||
}
|
||||
val=ADSR_LOW+((pos+(ADSR_HIGH-ADSR_LOW)*pos)>>8);
|
||||
if (ADSR_HIGH>ADSR_LOW) {
|
||||
val=ADSR_LOW+((pos+(ADSR_HIGH-ADSR_LOW)*pos)>>8);
|
||||
} else {
|
||||
val=ADSR_HIGH+(((255-pos)+(ADSR_LOW-ADSR_HIGH)*(255-pos))>>8);
|
||||
}
|
||||
}
|
||||
if (type==2) { // LFO
|
||||
lfoPos+=LFO_SPEED;
|
||||
|
|
@ -161,7 +165,11 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
|
|||
lfoOut=(lfoPos&512)?255:0;
|
||||
break;
|
||||
}
|
||||
val=ADSR_LOW+((lfoOut+(ADSR_HIGH-ADSR_LOW)*lfoOut)>>8);
|
||||
if (ADSR_HIGH>ADSR_LOW) {
|
||||
val=ADSR_LOW+((lfoOut+(ADSR_HIGH-ADSR_LOW)*lfoOut)>>8);
|
||||
} else {
|
||||
val=ADSR_HIGH+(((255-lfoOut)+(ADSR_LOW-ADSR_HIGH)*(255-lfoOut))>>8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,11 @@ int DivDispatch::mapVelocity(int ch, float vel) {
|
|||
return round(vel*volMax);
|
||||
}
|
||||
|
||||
float DivDispatch::getGain(int ch, int vol) {
|
||||
const float volMax=MAX(1,dispatch(DivCommand(DIV_CMD_GET_VOLMAX,MAX(ch,0))));
|
||||
return (float)vol/volMax;
|
||||
}
|
||||
|
||||
int DivDispatch::getPortaFloor(int ch) {
|
||||
return 0x00;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ void DivPlatformAY8910::runDAC() {
|
|||
int prevOut=chan[i].dac.out;
|
||||
while (chan[i].dac.period>dacRate && !end) {
|
||||
DivSample* s=parent->getSample(chan[i].dac.sample);
|
||||
if (s->samples<=0) {
|
||||
if (s->samples<=0 || chan[i].dac.pos<0 || chan[i].dac.pos>=(int)s->samples) {
|
||||
chan[i].dac.sample=-1;
|
||||
immWrite(0x08+i,0);
|
||||
end=true;
|
||||
|
|
@ -154,6 +154,80 @@ void DivPlatformAY8910::runDAC() {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::runTFX() {
|
||||
/*
|
||||
developer's note: if you are checking for intellivision
|
||||
make sure to add "&& selCore"
|
||||
because for some reason, the register remap doesn't work
|
||||
when the user uses AtomicSSG core
|
||||
*/
|
||||
int timerPeriod, output;
|
||||
for (int i=0; i<3; i++) {
|
||||
if (chan[i].active && (chan[i].curPSGMode.val&16) && !(chan[i].curPSGMode.val&8) && chan[i].tfx.mode!=-1) {
|
||||
if (chan[i].tfx.mode == -1 && !isMuted[i]) {
|
||||
/*
|
||||
bug: if in the timer FX macro the user enables
|
||||
and then disables PWM while there is no volume macro
|
||||
there is now a random chance that the resulting output
|
||||
is silent or has volume set incorrectly
|
||||
i've tried to implement a fix, but it seems to be
|
||||
ineffective, so...
|
||||
TODO: actually implement a proper fix
|
||||
*/
|
||||
if (intellivision && chan[i].curPSGMode.getEnvelope()) {
|
||||
immWrite(0x08+i,(chan[i].outVol&0xc)<<2);
|
||||
continue;
|
||||
} else {
|
||||
immWrite(0x08+i,(chan[i].outVol&15)|((chan[i].curPSGMode.getEnvelope())<<2));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
chan[i].tfx.counter += 1;
|
||||
if (chan[i].tfx.counter >= chan[i].tfx.period && chan[i].tfx.mode == 0) {
|
||||
chan[i].tfx.counter = 0;
|
||||
chan[i].tfx.out ^= 1;
|
||||
output = ((chan[i].tfx.out) ? chan[i].outVol : (chan[i].tfx.lowBound-(15-chan[i].outVol)));
|
||||
// TODO: fix this stupid crackling noise that happens
|
||||
// everytime the volume changes
|
||||
output = (output <= 0) ? 0 : output; // underflow
|
||||
output = (output >= 15) ? 15 : output; // overflow
|
||||
output &= 15; // i don't know if i need this but i'm too scared to remove it
|
||||
if (!isMuted[i]) {
|
||||
if (intellivision && selCore) {
|
||||
immWrite(0x0b+i,(output&0xc)<<2);
|
||||
} else {
|
||||
immWrite(0x08+i,output|(chan[i].curPSGMode.getEnvelope()<<2));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].tfx.counter >= chan[i].tfx.period && chan[i].tfx.mode == 1) {
|
||||
chan[i].tfx.counter = 0;
|
||||
if (!isMuted[i]) {
|
||||
if (intellivision && selCore) {
|
||||
immWrite(0xa, ayEnvMode);
|
||||
} else {
|
||||
immWrite(0xd, ayEnvMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].tfx.counter >= chan[i].tfx.period && chan[i].tfx.mode == 2) {
|
||||
chan[i].tfx.counter = 0;
|
||||
}
|
||||
}
|
||||
if (chan[i].tfx.num > 0) {
|
||||
timerPeriod = chan[i].freq*chan[i].tfx.den/chan[i].tfx.num;
|
||||
} else {
|
||||
timerPeriod = chan[i].freq*chan[i].tfx.den;
|
||||
}
|
||||
if (chan[i].tfx.num > 0 && chan[i].tfx.den > 0) chan[i].tfx.period=timerPeriod+chan[i].tfx.offset;
|
||||
// stupid pitch correction because:
|
||||
// YM2149 half-clock and Sunsoft 5B: timers run an octave too high
|
||||
// on AtomicSSG core timers run 2 octaves too high
|
||||
if (clockSel || sunsoft) chan[i].tfx.period = chan[i].tfx.period * 2;
|
||||
if (selCore) chan[i].tfx.period = chan[i].tfx.period * 4;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::checkWrites() {
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
|
|
@ -169,7 +243,7 @@ void DivPlatformAY8910::checkWrites() {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::acquire(short** buf, size_t len) {
|
||||
void DivPlatformAY8910::acquire_mame(short** buf, size_t len) {
|
||||
if (ayBufLen<len) {
|
||||
ayBufLen=len;
|
||||
for (int i=0; i<3; i++) {
|
||||
|
|
@ -181,6 +255,7 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) {
|
|||
if (sunsoft) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
runDAC();
|
||||
runTFX();
|
||||
checkWrites();
|
||||
|
||||
ay->sound_stream_update(ayBuf,1);
|
||||
|
|
@ -194,6 +269,7 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) {
|
|||
} else {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
runDAC();
|
||||
runTFX();
|
||||
checkWrites();
|
||||
|
||||
ay->sound_stream_update(ayBuf,1);
|
||||
|
|
@ -212,6 +288,59 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::acquire_atomic(short** buf, size_t len) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
runDAC();
|
||||
runTFX();
|
||||
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
SSG_Write(&ay_atomic,w.addr&0x0f,w.val);
|
||||
regPool[w.addr&0x0f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
SSG_Clock(&ay_atomic,0);
|
||||
SSG_Clock(&ay_atomic,1);
|
||||
|
||||
if (stereo) {
|
||||
buf[0][i]=ay_atomic.o_analog[0]+ay_atomic.o_analog[1]+((ay_atomic.o_analog[2]*stereoSep)>>8);
|
||||
buf[1][i]=((ay_atomic.o_analog[0]*stereoSep)>>8)+ay_atomic.o_analog[1]+ay_atomic.o_analog[2];
|
||||
} else {
|
||||
buf[0][i]=ay_atomic.o_analog[0]+ay_atomic.o_analog[1]+ay_atomic.o_analog[2];
|
||||
buf[1][i]=buf[0][i];
|
||||
}
|
||||
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=ay_atomic.o_analog[0];
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=ay_atomic.o_analog[1];
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=ay_atomic.o_analog[2];
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::acquire(short** buf, size_t len) {
|
||||
if (selCore) {
|
||||
acquire_atomic(buf,len);
|
||||
} else {
|
||||
acquire_mame(buf,len);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len) {
|
||||
writes.clear();
|
||||
int rate=(int)(chipClock/sRate);
|
||||
for (size_t i=0; i<len; i++) {
|
||||
for (int h=0; h<rate; h++) {
|
||||
runDAC();
|
||||
runTFX();
|
||||
}
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite& w=writes.front();
|
||||
stream.push_back(DivDelayedWrite(i,w.addr,w.val));
|
||||
writes.pop_front();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::updateOutSel(bool immediate) {
|
||||
if (immediate) {
|
||||
immWrite(0x07,
|
||||
|
|
@ -243,7 +372,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15));
|
||||
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||
if (!(chan[i].nextPSGMode.val&8)) {
|
||||
if (!(chan[i].nextPSGMode.val&8) || !(chan[i].nextPSGMode.val&16)) {
|
||||
if (isMuted[i]) {
|
||||
rWrite(0x08+i,0);
|
||||
} else if (intellivision && (chan[i].nextPSGMode.getEnvelope())) {
|
||||
|
|
@ -267,6 +396,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
if (chan[i].std.wave.had) {
|
||||
if (!(chan[i].nextPSGMode.val&8)) {
|
||||
chan[i].nextPSGMode.val=chan[i].std.wave.val&7;
|
||||
chan[i].nextPSGMode.val|=(chan[i].curPSGMode.val&16);
|
||||
if (chan[i].active) {
|
||||
chan[i].curPSGMode.val=chan[i].nextPSGMode.val;
|
||||
}
|
||||
|
|
@ -290,6 +420,8 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].tfx.counter = 0;
|
||||
chan[i].tfx.out = 0;
|
||||
if (chan[i].nextPSGMode.val&8) {
|
||||
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
|
||||
if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
|
||||
|
|
@ -297,7 +429,11 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
rWrite(0x08+i,0);
|
||||
addWrite(0xffff0000+(i<<8),chan[i].dac.sample);
|
||||
}
|
||||
chan[i].dac.pos=0;
|
||||
if (chan[i].dac.setPos) {
|
||||
chan[i].dac.setPos=false;
|
||||
} else {
|
||||
chan[i].dac.pos=0;
|
||||
}
|
||||
chan[i].dac.period=0;
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
|
|
@ -320,6 +456,54 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
chan[i].freqChanged=true;
|
||||
if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1;
|
||||
}
|
||||
if (chan[i].std.ex4.had) {
|
||||
chan[i].fixedFreq=chan[i].std.ex4.val;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.ex5.had) {
|
||||
ayEnvPeriod=chan[i].std.ex5.val;
|
||||
immWrite(0x0b,ayEnvPeriod);
|
||||
immWrite(0x0c,ayEnvPeriod>>8);
|
||||
}
|
||||
if (chan[i].std.ex6.had) {
|
||||
// 0 - disable timer
|
||||
// 1 - pwm
|
||||
// 2 - syncbuzzer
|
||||
switch (chan[i].std.ex6.val) {
|
||||
case 1:
|
||||
chan[i].nextPSGMode.val|=16;
|
||||
chan[i].tfx.mode = 0;
|
||||
break;
|
||||
case 2:
|
||||
chan[i].nextPSGMode.val|=16;
|
||||
chan[i].tfx.mode = 1;
|
||||
break;
|
||||
case 3:
|
||||
chan[i].nextPSGMode.val|=16;
|
||||
chan[i].tfx.mode = 2;
|
||||
break;
|
||||
default:
|
||||
chan[i].nextPSGMode.val|=16;
|
||||
chan[i].tfx.mode = -1; // this is a workaround!
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.ex7.had) {
|
||||
chan[i].tfx.offset=chan[i].std.ex7.val;
|
||||
}
|
||||
if (chan[i].std.ex8.had) {
|
||||
chan[i].tfx.num=chan[i].std.ex8.val;
|
||||
chan[i].freqChanged=true;
|
||||
if (!chan[i].std.fms.will) chan[i].tfx.den=1;
|
||||
}
|
||||
if (chan[i].std.fms.had) {
|
||||
chan[i].tfx.den=chan[i].std.fms.val;
|
||||
chan[i].freqChanged=true;
|
||||
if (!chan[i].std.ex8.will) chan[i].tfx.num=1;
|
||||
}
|
||||
if (chan[i].std.ams.had) {
|
||||
chan[i].tfx.lowBound=chan[i].std.ams.val;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].dac.furnaceDAC) {
|
||||
|
|
@ -337,6 +521,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
if (chan[i].fixedFreq>4095) chan[i].fixedFreq=4095;
|
||||
if (chan[i].keyOn) {
|
||||
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
//rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
|
||||
|
|
@ -348,8 +533,13 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
chan[i].curPSGMode.val=0;
|
||||
rWrite(0x08+i,0);
|
||||
}
|
||||
rWrite((i)<<1,chan[i].freq&0xff);
|
||||
rWrite(1+((i)<<1),chan[i].freq>>8);
|
||||
if (chan[i].fixedFreq>0) {
|
||||
rWrite((i)<<1,chan[i].fixedFreq&0xff);
|
||||
rWrite(1+((i)<<1),chan[i].fixedFreq>>8);
|
||||
} else {
|
||||
rWrite((i)<<1,chan[i].freq&0xff);
|
||||
rWrite(1+((i)<<1),chan[i].freq>>8);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) {
|
||||
|
|
@ -422,7 +612,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
}
|
||||
}
|
||||
chan[c.chan].dac.pos=0;
|
||||
if (chan[c.chan].dac.setPos) {
|
||||
chan[c.chan].dac.setPos=false;
|
||||
} else {
|
||||
chan[c.chan].dac.pos=0;
|
||||
}
|
||||
chan[c.chan].dac.period=0;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
|
|
@ -448,7 +642,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
}
|
||||
chan[c.chan].dac.pos=0;
|
||||
if (chan[c.chan].dac.setPos) {
|
||||
chan[c.chan].dac.setPos=false;
|
||||
} else {
|
||||
chan[c.chan].dac.pos=0;
|
||||
}
|
||||
chan[c.chan].dac.period=0;
|
||||
chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*2048;
|
||||
if (dumpWrites) {
|
||||
|
|
@ -468,6 +666,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].fixedFreq=0;
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
|
|
@ -562,8 +761,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
}
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
if (!(chan[c.chan].nextPSGMode.val&8)) {
|
||||
if (c.value<16) {
|
||||
chan[c.chan].nextPSGMode.val|=16;
|
||||
chan[c.chan].tfx.mode=(((c.value&0xf0)>>4)&3)-1;
|
||||
if ((c.value&15)<16) {
|
||||
chan[c.chan].nextPSGMode.val=(c.value+1)&7;
|
||||
chan[c.chan].nextPSGMode.val|=chan[c.chan].curPSGMode.val&16;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].curPSGMode.val=chan[c.chan].nextPSGMode.val;
|
||||
}
|
||||
|
|
@ -635,6 +837,16 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
updateOutSel(true);
|
||||
immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal));
|
||||
break;
|
||||
case DIV_CMD_AY_NOISE_MASK_AND:
|
||||
chan[c.chan].tfx.num=c.value>>4;
|
||||
chan[c.chan].tfx.den=c.value&15;
|
||||
break;
|
||||
case DIV_CMD_AY_AUTO_PWM: {
|
||||
// best way i could find to do signed :/
|
||||
signed char signVal=c.value;
|
||||
chan[c.chan].tfx.offset=signVal;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
if (c.value>0) {
|
||||
chan[c.chan].nextPSGMode.val|=8;
|
||||
|
|
@ -652,6 +864,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].dac.pos=c.value;
|
||||
chan[c.chan].dac.setPos=true;
|
||||
if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dac.pos);
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
break;
|
||||
|
|
@ -697,6 +914,8 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) {
|
|||
|
||||
void DivPlatformAY8910::forceIns() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].curPSGMode.val&=~8;
|
||||
chan[i].nextPSGMode.val&=~8;
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
|
|
@ -730,6 +949,11 @@ int DivPlatformAY8910::mapVelocity(int ch, float vel) {
|
|||
return round(15.0*pow(vel,0.33));
|
||||
}
|
||||
|
||||
float DivPlatformAY8910::getGain(int ch, int vol) {
|
||||
if (vol==0) return 0;
|
||||
return 1.0/pow(10.0,(float)(15-vol)*2.0/20.0);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformAY8910::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
|
@ -759,6 +983,12 @@ void DivPlatformAY8910::reset() {
|
|||
addWrite(0xffffffff,0);
|
||||
}
|
||||
|
||||
SSG_Reset(&ay_atomic);
|
||||
SSG_SetType(&ay_atomic,
|
||||
(yamaha?0:1)|
|
||||
(intellivision?2:0)
|
||||
);
|
||||
|
||||
for (int i=0; i<16; i++) {
|
||||
oldWrites[i]=-1;
|
||||
pendingWrites[i]=-1;
|
||||
|
|
@ -811,6 +1041,10 @@ void DivPlatformAY8910::setExtClockDiv(unsigned int eclk, unsigned char ediv) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::setCore(unsigned char core) {
|
||||
selCore=core;
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::setFlags(const DivConfig& flags) {
|
||||
if (extMode) {
|
||||
chipClock=extClock;
|
||||
|
|
@ -869,8 +1103,13 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
|
|||
break;
|
||||
}
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/8;
|
||||
dacRate=rate;
|
||||
if (selCore) {
|
||||
rate=chipClock/2;
|
||||
dacRate=chipClock*2;
|
||||
} else {
|
||||
rate=chipClock/8;
|
||||
dacRate=rate;
|
||||
}
|
||||
}
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
|
|
@ -881,23 +1120,27 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
|
|||
case 1:
|
||||
clockSel=flags.getBool("halfClock",false);
|
||||
ay=new ym2149_device(rate,clockSel);
|
||||
yamaha=true;
|
||||
sunsoft=false;
|
||||
intellivision=false;
|
||||
break;
|
||||
case 2:
|
||||
ay=new sunsoft_5b_sound_device(rate);
|
||||
yamaha=true;
|
||||
sunsoft=true;
|
||||
intellivision=false;
|
||||
clockSel=false;
|
||||
break;
|
||||
case 3:
|
||||
ay=new ay8914_device(rate);
|
||||
yamaha=false;
|
||||
sunsoft=false;
|
||||
intellivision=true;
|
||||
clockSel=false;
|
||||
break;
|
||||
default:
|
||||
ay=new ay8910_device(rate);
|
||||
yamaha=false;
|
||||
sunsoft=false;
|
||||
intellivision=false;
|
||||
clockSel=false;
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@
|
|||
#include "../dispatch.h"
|
||||
#include "../../fixedQueue.h"
|
||||
#include "sound/ay8910.h"
|
||||
extern "C" {
|
||||
#include "sound/atomicssg/ssg.h"
|
||||
}
|
||||
|
||||
class DivPlatformAY8910: public DivDispatch {
|
||||
protected:
|
||||
|
|
@ -31,6 +34,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
inline unsigned char regRemap(unsigned char reg) { return intellivision?AY8914RegRemap[reg&0x0f]:reg&0x0f; }
|
||||
struct Channel: public SharedChannel<int> {
|
||||
struct PSGMode {
|
||||
// bit 4: timer FX
|
||||
// bit 3: DAC
|
||||
// bit 2: envelope
|
||||
// bit 1: noise
|
||||
|
|
@ -49,6 +53,10 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
return (val&8)?0:(val&4);
|
||||
}
|
||||
|
||||
unsigned char getTimerFX() {
|
||||
return (val&8)?0:(val&16);
|
||||
}
|
||||
|
||||
PSGMode(unsigned char v=1):
|
||||
val(v) {}
|
||||
};
|
||||
|
|
@ -57,7 +65,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
|
||||
struct DAC {
|
||||
int sample, rate, period, pos, out;
|
||||
bool furnaceDAC;
|
||||
bool furnaceDAC, setPos;
|
||||
|
||||
DAC():
|
||||
sample(-1),
|
||||
|
|
@ -65,19 +73,36 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
period(0),
|
||||
pos(0),
|
||||
out(0),
|
||||
furnaceDAC(false) {}
|
||||
furnaceDAC(false),
|
||||
setPos(false) {}
|
||||
} dac;
|
||||
|
||||
struct TFX {
|
||||
int period, counter, offset, den, num, mode, lowBound, out;
|
||||
TFX():
|
||||
period(0),
|
||||
counter(0),
|
||||
offset(1),
|
||||
den(1),
|
||||
num(1),
|
||||
mode(0),
|
||||
lowBound(0),
|
||||
out(0) {}
|
||||
} tfx;
|
||||
|
||||
unsigned char autoEnvNum, autoEnvDen;
|
||||
signed char konCycles;
|
||||
unsigned short fixedFreq;
|
||||
Channel():
|
||||
SharedChannel<int>(15),
|
||||
curPSGMode(PSGMode(0)),
|
||||
nextPSGMode(PSGMode(1)),
|
||||
dac(DAC()),
|
||||
tfx(TFX()),
|
||||
autoEnvNum(0),
|
||||
autoEnvDen(0),
|
||||
konCycles(0) {}
|
||||
konCycles(0),
|
||||
fixedFreq(0) {}
|
||||
};
|
||||
Channel chan[3];
|
||||
bool isMuted[3];
|
||||
|
|
@ -96,6 +121,9 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
|
||||
unsigned char sampleBank;
|
||||
unsigned char stereoSep;
|
||||
unsigned char selCore;
|
||||
|
||||
ssg_t ay_atomic;
|
||||
|
||||
int delay;
|
||||
|
||||
|
|
@ -105,7 +133,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
unsigned char extDiv;
|
||||
unsigned char dacRateDiv;
|
||||
|
||||
bool stereo, sunsoft, intellivision, clockSel;
|
||||
bool stereo, sunsoft, intellivision, clockSel, yamaha;
|
||||
bool ioPortA, ioPortB;
|
||||
unsigned char portAVal, portBVal;
|
||||
|
||||
|
|
@ -120,20 +148,27 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
|
||||
void checkWrites();
|
||||
void updateOutSel(bool immediate=false);
|
||||
|
||||
void acquire_mame(short** buf, size_t len);
|
||||
void acquire_atomic(short** buf, size_t len);
|
||||
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void runDAC();
|
||||
void runTFX();
|
||||
void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8);
|
||||
void acquire(short** buf, size_t len);
|
||||
void fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
int mapVelocity(int ch, float vel);
|
||||
float getGain(int ch, int vol);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void setCore(unsigned char core);
|
||||
void flushWrites();
|
||||
void reset();
|
||||
void forceIns();
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ void DivPlatformAY8930::runDAC() {
|
|||
int prevOut=chan[i].dac.out;
|
||||
while (chan[i].dac.period>rate && !end) {
|
||||
DivSample* s=parent->getSample(chan[i].dac.sample);
|
||||
if (s->samples<=0) {
|
||||
if (s->samples<=0 || chan[i].dac.pos<0 || chan[i].dac.pos>=(int)s->samples) {
|
||||
chan[i].dac.sample=-1;
|
||||
immWrite(0x08+i,0);
|
||||
end=true;
|
||||
|
|
@ -285,7 +285,11 @@ void DivPlatformAY8930::tick(bool sysTick) {
|
|||
rWrite(0x08+i,0);
|
||||
addWrite(0xffff0000+(i<<8),chan[i].dac.sample);
|
||||
}
|
||||
chan[i].dac.pos=0;
|
||||
if (chan[i].dac.setPos) {
|
||||
chan[i].dac.setPos=false;
|
||||
} else {
|
||||
chan[i].dac.pos=0;
|
||||
}
|
||||
chan[i].dac.period=0;
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
|
|
@ -319,6 +323,15 @@ void DivPlatformAY8930::tick(bool sysTick) {
|
|||
ayNoiseOr=chan[i].std.fms.val;
|
||||
immWrite(0x1a,ayNoiseOr);
|
||||
}
|
||||
if (chan[i].std.ex4.had) {
|
||||
chan[i].fixedFreq=chan[i].std.ex4.val;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.ex5.had) {
|
||||
chan[i].envelope.period=chan[i].std.ex5.val;
|
||||
immWrite(regPeriodL[i],chan[i].envelope.period);
|
||||
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].dac.furnaceDAC) {
|
||||
|
|
@ -349,8 +362,13 @@ void DivPlatformAY8930::tick(bool sysTick) {
|
|||
chan[i].curPSGMode.val=0;
|
||||
rWrite(0x08+i,0);
|
||||
}
|
||||
rWrite((i)<<1,chan[i].freq&0xff);
|
||||
rWrite(1+((i)<<1),chan[i].freq>>8);
|
||||
if (chan[i].fixedFreq>0) {
|
||||
rWrite((i)<<1,chan[i].fixedFreq&0xff);
|
||||
rWrite(1+((i)<<1),chan[i].fixedFreq>>8);
|
||||
} else {
|
||||
rWrite((i)<<1,chan[i].freq&0xff);
|
||||
rWrite(1+((i)<<1),chan[i].freq>>8);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) {
|
||||
|
|
@ -358,6 +376,29 @@ void DivPlatformAY8930::tick(bool sysTick) {
|
|||
immWrite(regPeriodL[i],chan[i].envelope.period);
|
||||
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
|
||||
}
|
||||
if (chan[i].freqChanged && chan[i].autoNoiseMode) {
|
||||
int noiseFreq=chan[i].freq;
|
||||
switch (chan[i].autoNoiseMode) {
|
||||
case 1: // noise
|
||||
noiseFreq+=chan[i].autoNoiseOff;
|
||||
if (noiseFreq<0) noiseFreq=0;
|
||||
if (noiseFreq>255) noiseFreq=255;
|
||||
rWrite(0x06,noiseFreq);
|
||||
break;
|
||||
case 2: { // noise + OR mask
|
||||
if (noiseFreq<0) noiseFreq=0;
|
||||
int noiseDiv=(noiseFreq>>8)+1;
|
||||
noiseFreq/=noiseDiv;
|
||||
ayNoiseOr=noiseDiv;
|
||||
immWrite(0x1a,ayNoiseOr);
|
||||
noiseFreq+=chan[i].autoNoiseOff;
|
||||
if (noiseFreq<0) noiseFreq=0;
|
||||
if (noiseFreq>255) noiseFreq=255;
|
||||
rWrite(0x06,noiseFreq);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
|
||||
|
|
@ -423,7 +464,11 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
}
|
||||
}
|
||||
chan[c.chan].dac.pos=0;
|
||||
if (chan[c.chan].dac.setPos) {
|
||||
chan[c.chan].dac.setPos=false;
|
||||
} else {
|
||||
chan[c.chan].dac.pos=0;
|
||||
}
|
||||
chan[c.chan].dac.period=0;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
|
|
@ -449,7 +494,11 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
}
|
||||
chan[c.chan].dac.pos=0;
|
||||
if (chan[c.chan].dac.setPos) {
|
||||
chan[c.chan].dac.setPos=false;
|
||||
} else {
|
||||
chan[c.chan].dac.pos=0;
|
||||
}
|
||||
chan[c.chan].dac.period=0;
|
||||
chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*4096;
|
||||
if (dumpWrites) {
|
||||
|
|
@ -469,6 +518,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].fixedFreq=0;
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
|
|
@ -620,6 +670,12 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
chan[c.chan].autoEnvDen=c.value&15;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_AY_AUTO_PWM:
|
||||
chan[c.chan].autoNoiseMode=c.value>>4;
|
||||
chan[c.chan].autoNoiseOff=c.value&15;
|
||||
if (chan[c.chan].autoNoiseOff>=8) chan[c.chan].autoNoiseOff-=16;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_AY_IO_WRITE:
|
||||
if (c.value==255) {
|
||||
immWrite(0x1f,c.value2);
|
||||
|
|
@ -654,6 +710,11 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].dac.pos=c.value;
|
||||
chan[c.chan].dac.setPos=true;
|
||||
if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dac.pos);
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
break;
|
||||
|
|
@ -698,6 +759,8 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) {
|
|||
void DivPlatformAY8930::forceIns() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].curPSGMode.val&=~8;
|
||||
chan[i].nextPSGMode.val&=~8;
|
||||
immWrite(regPeriodL[i],chan[i].envelope.period);
|
||||
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
|
||||
immWrite(regMode[i],chan[i].envelope.mode);
|
||||
|
|
@ -729,6 +792,11 @@ int DivPlatformAY8930::mapVelocity(int ch, float vel) {
|
|||
return round(31.0*pow(vel,0.22));
|
||||
}
|
||||
|
||||
float DivPlatformAY8930::getGain(int ch, int vol) {
|
||||
if (vol==0) return 0;
|
||||
return 1.0/pow(10.0,(float)(31-vol)*1.5/20.0);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformAY8930::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
|
||||
struct DAC {
|
||||
int sample, rate, period, pos, out;
|
||||
bool furnaceDAC;
|
||||
bool furnaceDAC, setPos;
|
||||
|
||||
DAC():
|
||||
sample(-1),
|
||||
|
|
@ -73,11 +73,13 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
period(0),
|
||||
pos(0),
|
||||
out(0),
|
||||
furnaceDAC(false) {}
|
||||
furnaceDAC(false),
|
||||
setPos(false) {}
|
||||
} dac;
|
||||
|
||||
unsigned char autoEnvNum, autoEnvDen, duty;
|
||||
signed char konCycles;
|
||||
unsigned char autoEnvNum, autoEnvDen, duty, autoNoiseMode;
|
||||
signed char konCycles, autoNoiseOff;
|
||||
unsigned short fixedFreq;
|
||||
Channel():
|
||||
SharedChannel<int>(31),
|
||||
envelope(Envelope()),
|
||||
|
|
@ -87,7 +89,10 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
autoEnvNum(0),
|
||||
autoEnvDen(0),
|
||||
duty(4),
|
||||
konCycles(0) {}
|
||||
autoNoiseMode(0),
|
||||
konCycles(0),
|
||||
autoNoiseOff(0),
|
||||
fixedFreq(0) {}
|
||||
};
|
||||
Channel chan[3];
|
||||
bool isMuted[3];
|
||||
|
|
@ -133,6 +138,7 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
int mapVelocity(int ch, float vel);
|
||||
float getGain(int ch, int vol);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
|
|
|
|||
379
src/engine/platform/bifurcator.cpp
Normal file
379
src/engine/platform/bifurcator.cpp
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "bifurcator.h"
|
||||
#include "../engine.h"
|
||||
#include "../filter.h"
|
||||
#include <math.h>
|
||||
|
||||
#define CHIP_FREQBASE 65536
|
||||
|
||||
#define rWrite(a,v) {if(!skipRegisterWrites) {regPool[a]=v; if(dumpWrites) addWrite(a,v); }}
|
||||
|
||||
const char* regCheatSheetBifurcator[]={
|
||||
"CHx_State", "x*8+0",
|
||||
"CHx_Param", "x*8+2",
|
||||
"CHx_Freq", "x*8+4",
|
||||
"CHx_LVol", "x*8+6",
|
||||
"CHx_RVol", "x*8+7",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformBifurcator::getRegisterSheet() {
|
||||
return regCheatSheetBifurcator;
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::acquire(short** buf, size_t len) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].curx=regPool[i*8]|(regPool[i*8+1]<<8);
|
||||
chan[i].param=regPool[i*8+2]|(regPool[i*8+3]<<8);
|
||||
chan[i].freq=regPool[i*8+4]|(regPool[i*8+5]<<8);
|
||||
chan[i].chVolL=regPool[i*8+6];
|
||||
chan[i].chVolR=regPool[i*8+7];
|
||||
}
|
||||
for (size_t h=0; h<len; h++) {
|
||||
int l=0;
|
||||
int r=0;
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].audSub+=chan[i].freq;
|
||||
if (chan[i].audSub>=65536) {
|
||||
int64_t newx=(int64_t)chan[i].curx*(chan[i].param+65536)/32768;
|
||||
newx*=65536-chan[i].curx;
|
||||
chan[i].curx=(int)(newx/65536);
|
||||
chan[i].audSub&=65535;
|
||||
}
|
||||
int out=chan[i].curx-32768;
|
||||
int outL=out*chan[i].chVolL/256;
|
||||
int outR=out*chan[i].chVolR/256;
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(short)((outL+outR)/2);
|
||||
l+=outL/4;
|
||||
r+=outR/4;
|
||||
}
|
||||
buf[0][h]=(short)l;
|
||||
buf[1][h]=(short)r;
|
||||
}
|
||||
for (int i=0; i<4; i++) {
|
||||
regPool[i*8]=chan[i].curx&0xff;
|
||||
regPool[i*8+1]=chan[i].curx>>8;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=(chan[i].vol*MIN(chan[i].std.vol.val,255))/255;
|
||||
chan[i].volChangedL=true;
|
||||
chan[i].volChangedR=true;
|
||||
}
|
||||
if (NEW_ARP_STRAT) {
|
||||
chan[i].handleArp();
|
||||
} else if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
rWrite(i*8+2,chan[i].std.duty.val&0xff);
|
||||
rWrite(i*8+3,chan[i].std.duty.val>>8);
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-32768,32767);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].chPanL=(255*(chan[i].std.panL.val&255))/255;
|
||||
chan[i].volChangedL=true;
|
||||
}
|
||||
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[i].chPanR=(255*(chan[i].std.panR.val&255))/255;
|
||||
chan[i].volChangedR=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if ((chan[i].std.phaseReset.val==1) && chan[i].active) {
|
||||
rWrite(i*8,1);
|
||||
rWrite(i*8+1,0);
|
||||
}
|
||||
}
|
||||
if (chan[i].std.ex1.had) {
|
||||
rWrite(i*8,chan[i].std.ex1.val&0xff);
|
||||
rWrite(i*8+1,chan[i].std.ex1.val>>8);
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq>65535) chan[i].freq=65535;
|
||||
rWrite(i*8+4,chan[i].freq&0xff);
|
||||
rWrite(i*8+5,chan[i].freq>>8);
|
||||
if (chan[i].keyOn) {
|
||||
if (!chan[i].std.vol.had) {
|
||||
chan[i].outVol=chan[i].vol;
|
||||
}
|
||||
chan[i].volChangedL=true;
|
||||
chan[i].volChangedR=true;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
chan[i].volChangedL=true;
|
||||
chan[i].volChangedR=true;
|
||||
chan[i].keyOff=false;
|
||||
}
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
if (chan[i].volChangedL) {
|
||||
int vol=(isMuted[i] || !chan[i].active)?0:(chan[i].outVol*chan[i].chPanL/255);
|
||||
rWrite(i*8+6,vol);
|
||||
chan[i].volChangedL=false;
|
||||
}
|
||||
if (chan[i].volChangedR) {
|
||||
int vol=(isMuted[i] || !chan[i].active)?0:(chan[i].outVol*chan[i].chPanR/255);
|
||||
rWrite(i*8+7,vol);
|
||||
chan[i].volChangedR=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformBifurcator::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_BIFURCATOR);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
}
|
||||
chan[c.chan].volChangedL=true;
|
||||
chan[c.chan].volChangedR=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.vol.has) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
chan[c.chan].chPanL=c.value;
|
||||
chan[c.chan].chPanR=c.value2;
|
||||
chan[c.chan].volChangedL=true;
|
||||
chan[c.chan].volChangedR=true;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val-12):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_BIFURCATOR_STATE_LOAD:
|
||||
rWrite(c.chan*8+c.value,c.value2);
|
||||
break;
|
||||
case DIV_CMD_BIFURCATOR_PARAMETER:
|
||||
rWrite(c.chan*8+2+c.value,c.value2);
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
break;
|
||||
case DIV_CMD_MACRO_ON:
|
||||
chan[c.chan].std.mask(c.value,false);
|
||||
break;
|
||||
case DIV_CMD_MACRO_RESTART:
|
||||
chan[c.chan].std.restart(c.value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
chan[ch].volChangedL=true;
|
||||
chan[ch].volChangedR=true;
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::forceIns() {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].volChangedL=true;
|
||||
chan[i].volChangedR=true;
|
||||
chan[i].active=false;
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformBifurcator::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformBifurcator::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformBifurcator::getRegisterPool() {
|
||||
return (unsigned char*)regPool;
|
||||
}
|
||||
|
||||
int DivPlatformBifurcator::getRegisterPoolSize() {
|
||||
return 8*4;
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::reset() {
|
||||
memset(regPool,0,8*4);
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i]=DivPlatformBifurcator::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
rWrite(i*8,chan[i].curx&0xff);
|
||||
rWrite(i*8+1,chan[i].curx>>8);
|
||||
rWrite(i*8+2,chan[i].param&0xff);
|
||||
rWrite(i*8+3,chan[i].param>>8);
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformBifurcator::getOutputCount() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
DivMacroInt* DivPlatformBifurcator::getChanMacroInt(int ch) {
|
||||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
unsigned short DivPlatformBifurcator::getPan(int ch) {
|
||||
return (chan[ch].chPanL<<8)|(chan[ch].chPanR);
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::notifyInsChange(int ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::setFlags(const DivConfig& flags) {
|
||||
chipClock=1000000;
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/16;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
int DivPlatformBifurcator::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
for (int i=0; i<4; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 4;
|
||||
}
|
||||
|
||||
void DivPlatformBifurcator::quit() {
|
||||
for (int i=0; i<4; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
77
src/engine/platform/bifurcator.h
Normal file
77
src/engine/platform/bifurcator.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _BIFURCATOR_H
|
||||
#define _BIFURCATOR_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include "../waveSynth.h"
|
||||
|
||||
class DivPlatformBifurcator: public DivDispatch {
|
||||
struct Channel: public SharedChannel<int> {
|
||||
int param, curx;
|
||||
int audSub;
|
||||
bool volChangedL, volChangedR;
|
||||
int chPanL, chPanR;
|
||||
int chVolL, chVolR;
|
||||
Channel():
|
||||
SharedChannel<int>(255),
|
||||
param(47360),
|
||||
curx(1),
|
||||
audSub(0),
|
||||
volChangedL(false),
|
||||
volChangedR(false),
|
||||
chPanL(255),
|
||||
chPanR(255),
|
||||
chVolL(0),
|
||||
chVolR(0) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
unsigned char regPool[8*4];
|
||||
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void acquire(short** buf, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
int getOutputCount();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
unsigned short getPan(int chan);
|
||||
void setFlags(const DivConfig& flags);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -44,7 +44,7 @@ void DivPlatformBubSysWSG::acquire(short** buf, size_t len) {
|
|||
for (size_t h=0; h<len; h++) {
|
||||
signed int out=0;
|
||||
// K005289 part
|
||||
k005289.tick(8);
|
||||
k005289.tick(coreQuality);
|
||||
|
||||
// Wavetable part
|
||||
for (int i=0; i<2; i++) {
|
||||
|
|
@ -332,7 +332,7 @@ void DivPlatformBubSysWSG::notifyInsDeletion(void* ins) {
|
|||
void DivPlatformBubSysWSG::setFlags(const DivConfig& flags) {
|
||||
chipClock=COLOR_NTSC;
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/8;
|
||||
rate=chipClock/coreQuality;
|
||||
for (int i=0; i<2; i++) {
|
||||
oscBuf[i]->rate=rate/8;
|
||||
}
|
||||
|
|
@ -346,6 +346,32 @@ void DivPlatformBubSysWSG::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformBubSysWSG::setCoreQuality(unsigned char q) {
|
||||
switch (q) {
|
||||
case 0:
|
||||
coreQuality=64;
|
||||
break;
|
||||
case 1:
|
||||
coreQuality=32;
|
||||
break;
|
||||
case 2:
|
||||
coreQuality=16;
|
||||
break;
|
||||
case 3:
|
||||
coreQuality=8;
|
||||
break;
|
||||
case 4:
|
||||
coreQuality=4;
|
||||
break;
|
||||
case 5:
|
||||
coreQuality=1;
|
||||
break;
|
||||
default:
|
||||
coreQuality=8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformBubSysWSG::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ class DivPlatformBubSysWSG: public DivDispatch {
|
|||
bool isMuted[2];
|
||||
unsigned char writeOscBuf;
|
||||
|
||||
int coreQuality;
|
||||
k005289_core k005289;
|
||||
unsigned short regPool[4];
|
||||
void updateWave(int ch);
|
||||
|
|
@ -64,6 +65,7 @@ class DivPlatformBubSysWSG: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
void setCoreQuality(unsigned char q);
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
~DivPlatformBubSysWSG();
|
||||
|
|
|
|||
|
|
@ -182,6 +182,7 @@ void DivPlatformC64::tick(bool sysTick) {
|
|||
chan[i].duty-=chan[i].std.duty.val;
|
||||
}
|
||||
}
|
||||
chan[i].duty&=4095;
|
||||
rWrite(i*7+2,chan[i].duty&0xff);
|
||||
rWrite(i*7+3,chan[i].duty>>8);
|
||||
}
|
||||
|
|
@ -221,6 +222,10 @@ void DivPlatformC64::tick(bool sysTick) {
|
|||
filtRes=chan[i].std.ex2.val&15;
|
||||
willUpdateFilter=true;
|
||||
}
|
||||
if (chan[i].std.ex3.had) {
|
||||
chan[i].filter=(chan[i].std.ex3.val&1);
|
||||
willUpdateFilter=true;
|
||||
}
|
||||
if (chan[i].std.ex4.had) {
|
||||
chan[i].gate=chan[i].std.ex4.val&1;
|
||||
chan[i].sync=chan[i].std.ex4.val&2;
|
||||
|
|
@ -726,7 +731,7 @@ void DivPlatformC64::setFlags(const DivConfig& flags) {
|
|||
oscBuf[i]->rate=rate/16;
|
||||
}
|
||||
if (sidCore>0) {
|
||||
rate/=4;
|
||||
rate/=(sidCore==2)?coreQuality:4;
|
||||
if (sidCore==1) sid_fp->setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0);
|
||||
}
|
||||
keyPriority=flags.getBool("keyPriority",true);
|
||||
|
|
@ -757,6 +762,32 @@ void DivPlatformC64::setFlags(const DivConfig& flags) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformC64::setCoreQuality(unsigned char q) {
|
||||
switch (q) {
|
||||
case 0:
|
||||
coreQuality=32;
|
||||
break;
|
||||
case 1:
|
||||
coreQuality=16;
|
||||
break;
|
||||
case 2:
|
||||
coreQuality=8;
|
||||
break;
|
||||
case 3:
|
||||
coreQuality=4;
|
||||
break;
|
||||
case 4:
|
||||
coreQuality=2;
|
||||
break;
|
||||
case 5:
|
||||
coreQuality=1;
|
||||
break;
|
||||
default:
|
||||
coreQuality=4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ class DivPlatformC64: public DivDispatch {
|
|||
SID* sid;
|
||||
reSIDfp::SID* sid_fp;
|
||||
struct SID_chip* sid_d;
|
||||
int coreQuality;
|
||||
unsigned char regPool[32];
|
||||
|
||||
friend void putDispatchChip(void*,int);
|
||||
|
|
@ -121,6 +122,7 @@ class DivPlatformC64: public DivDispatch {
|
|||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void setChipModel(bool is6581);
|
||||
void setCore(unsigned char which);
|
||||
void setCoreQuality(unsigned char q);
|
||||
void quit();
|
||||
~DivPlatformC64();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -70,10 +70,11 @@ void DivPlatformDave::acquire(short** buf, size_t len) {
|
|||
chan[i].dacPeriod+=chan[i].dacRate;
|
||||
while (chan[i].dacPeriod>rate) {
|
||||
DivSample* s=parent->getSample(chan[i].dacSample);
|
||||
if (s->samples<=0) {
|
||||
if (s->samples<=0 || chan[i].dacPos>=s->samples) {
|
||||
chan[i].dacSample=-1;
|
||||
writeControl=true;
|
||||
chan[0].writeVol=true;
|
||||
chan[i].dacPeriod-=rate;
|
||||
continue;
|
||||
}
|
||||
signed char dacData=(s->data8[chan[i].dacPos]*chan[i].outVol)>>8;
|
||||
|
|
@ -196,7 +197,11 @@ void DivPlatformDave::tick(bool sysTick) {
|
|||
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
|
||||
if (i>=4) {
|
||||
if (chan[i].active && chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
|
||||
chan[i].dacPos=0;
|
||||
if (chan[i].setPos) {
|
||||
chan[i].setPos=false;
|
||||
} else {
|
||||
chan[i].dacPos=0;
|
||||
}
|
||||
chan[i].dacPeriod=0;
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
|
|
@ -324,7 +329,11 @@ int DivPlatformDave::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].dacPos=0;
|
||||
if (chan[c.chan].setPos) {
|
||||
chan[c.chan].setPos=false;
|
||||
} else {
|
||||
chan[c.chan].dacPos=0;
|
||||
}
|
||||
chan[c.chan].dacPeriod=0;
|
||||
writeControl=true;
|
||||
} else {
|
||||
|
|
@ -459,6 +468,11 @@ int DivPlatformDave::dispatch(DivCommand c) {
|
|||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
if (c.chan<4) break;
|
||||
chan[c.chan].dacPos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 63;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class DivPlatformDave: public DivDispatch {
|
|||
unsigned char panL;
|
||||
unsigned char panR;
|
||||
unsigned char wave;
|
||||
bool writeVol, highPass, ringMod, swapCounters, lowPass, resetPhase;
|
||||
bool writeVol, highPass, ringMod, swapCounters, lowPass, resetPhase, setPos;
|
||||
Channel():
|
||||
SharedChannel<signed char>(63),
|
||||
dacPeriod(0),
|
||||
|
|
@ -50,7 +50,8 @@ class DivPlatformDave: public DivDispatch {
|
|||
ringMod(false),
|
||||
swapCounters(false),
|
||||
lowPass(false),
|
||||
resetPhase(false) {}
|
||||
resetPhase(false),
|
||||
setPos(false) {}
|
||||
};
|
||||
Channel chan[6];
|
||||
DivDispatchOscBuffer* oscBuf[6];
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#include <math.h>
|
||||
|
||||
#define PITCH_OFFSET ((double)(16*2048*(chanMax+1)))
|
||||
#define NOTE_ES5506(c,note) (parent->calcBaseFreq(chipClock,chan[c].pcm.freqOffs,note,false))
|
||||
#define NOTE_ES5506(c,note) ((amigaPitch && parent->song.linearPitch!=2)?parent->calcBaseFreq(COLOR_NTSC,chan[c].pcm.freqOffs,note,true):parent->calcBaseFreq(chipClock,chan[c].pcm.freqOffs,note,false))
|
||||
|
||||
#define rWrite(a,...) {if(!skipRegisterWrites) {hostIntf32.push_back(QueuedHostIntf(4,(a),__VA_ARGS__)); }}
|
||||
#define immWrite(a,...) {hostIntf32.push_back(QueuedHostIntf(4,(a),__VA_ARGS__));}
|
||||
|
|
@ -181,6 +181,75 @@ void DivPlatformES5506::irqb(bool state) {
|
|||
*/
|
||||
}
|
||||
|
||||
// modified GUSVolumeTable from Impulse Tracker (SoundDrivers/GUS.INC)
|
||||
static const short amigaVolTable[65]={
|
||||
0x000, 0x8FF, 0x9FF, 0xA80,
|
||||
0xAFF, 0xB40, 0xB80, 0xBC0,
|
||||
0xBFF, 0xC20, 0xC40, 0xC60,
|
||||
0xC80, 0xCA0, 0xCC0, 0xCE0,
|
||||
0xCFF, 0xD10, 0xD20, 0xD30,
|
||||
0xD40, 0xD50, 0xD60, 0xD70,
|
||||
0xD80, 0xD90, 0xDA0, 0xDB0,
|
||||
0xDC0, 0xDD0, 0xDE0, 0xDF0,
|
||||
0xDFF, 0xE08, 0xE10, 0xE18,
|
||||
0xE20, 0xE28, 0xE30, 0xE38,
|
||||
0xE40, 0xE48, 0xE50, 0xE58,
|
||||
0xE60, 0xE68, 0xE70, 0xE78,
|
||||
0xE80, 0xE88, 0xE90, 0xE98,
|
||||
0xEA0, 0xEA8, 0xEB0, 0xEB8,
|
||||
0xEC0, 0xEC8, 0xED0, 0xED8,
|
||||
0xEE0, 0xEE8, 0xEF0, 0xEF8,
|
||||
0xEFF
|
||||
};
|
||||
|
||||
// same thing
|
||||
static const short amigaPanTable[128]={
|
||||
0x000, 0x8FF, 0x9FF, 0xA80, 0xAFF, 0xB40, 0xB80, 0xBC0,
|
||||
0xBFF, 0xC20, 0xC40, 0xC60, 0xC80, 0xCA0, 0xCC0, 0xCE0,
|
||||
0xCFF, 0xD10, 0xD20, 0xD30, 0xD40, 0xD50, 0xD60, 0xD70,
|
||||
0xD80, 0xD90, 0xDA0, 0xDB0, 0xDC0, 0xDD0, 0xDE0, 0xDF0,
|
||||
0xDFF, 0xE08, 0xE10, 0xE18, 0xE20, 0xE28, 0xE30, 0xE38,
|
||||
0xE40, 0xE48, 0xE50, 0xE58, 0xE60, 0xE68, 0xE70, 0xE78,
|
||||
0xE80, 0xE88, 0xE90, 0xE98, 0xEA0, 0xEA8, 0xEB0, 0xEB8,
|
||||
0xEC0, 0xEC8, 0xED0, 0xED8, 0xEE0, 0xEE8, 0xEF0, 0xEF8,
|
||||
0xEFF, 0xF04, 0xF08, 0xF0C, 0xF10, 0xF14, 0xF18, 0xF1C,
|
||||
0xF20, 0xF24, 0xF28, 0xF2C, 0xF30, 0xF34, 0xF38, 0xF3C,
|
||||
0xF40, 0xF44, 0xF48, 0xF4C, 0xF50, 0xF54, 0xF58, 0xF5C,
|
||||
0xF60, 0xF64, 0xF68, 0xF6C, 0xF70, 0xF74, 0xF78, 0xF7C,
|
||||
0xF80, 0xF84, 0xF88, 0xF8C, 0xF90, 0xF94, 0xF98, 0xF9C,
|
||||
0xFA0, 0xFA4, 0xFA8, 0xFAC, 0xFB0, 0xFB4, 0xFB8, 0xFBC,
|
||||
0xFC0, 0xFC4, 0xFC8, 0xFCC, 0xFD0, 0xFD4, 0xFD8, 0xFDC,
|
||||
0xFE0, 0xFE4, 0xFE8, 0xFEC, 0xFF0, 0xFF4, 0xFF8, 0xFFF
|
||||
};
|
||||
|
||||
void DivPlatformES5506::updateNoteChangesAsNeeded(int ch) {
|
||||
if (chan[ch].noteChanged.changed) { // note value changed or frequency offset is changed
|
||||
if (chan[ch].noteChanged.offs) {
|
||||
if (chan[ch].pcm.freqOffs!=chan[ch].pcm.nextFreqOffs) {
|
||||
chan[ch].pcm.freqOffs=chan[ch].pcm.nextFreqOffs;
|
||||
chan[ch].nextFreq=NOTE_ES5506(ch,chan[ch].currNote);
|
||||
chan[ch].noteChanged.freq=1;
|
||||
chan[ch].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[ch].noteChanged.note) {
|
||||
chan[ch].currNote=chan[ch].nextNote;
|
||||
const int nextFreq=NOTE_ES5506(ch,chan[ch].nextNote);
|
||||
if (chan[ch].nextFreq!=nextFreq) {
|
||||
chan[ch].nextFreq=nextFreq;
|
||||
chan[ch].noteChanged.freq=1;
|
||||
}
|
||||
}
|
||||
if (chan[ch].noteChanged.freq) {
|
||||
if (chan[ch].baseFreq!=chan[ch].nextFreq) {
|
||||
chan[ch].baseFreq=chan[ch].nextFreq;
|
||||
chan[ch].freqChanged=true;
|
||||
}
|
||||
}
|
||||
chan[ch].noteChanged.changed=0;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformES5506::tick(bool sysTick) {
|
||||
for (int i=0; i<=chanMax; i++) {
|
||||
chan[i].std.next();
|
||||
|
|
@ -188,7 +257,12 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
signed int k1=chan[i].k1Prev,k2=chan[i].k2Prev;
|
||||
// volume/panning macros
|
||||
if (chan[i].std.vol.had) {
|
||||
const int nextVol=VOL_SCALE_LOG((0xfff*chan[i].vol)/0xff,(0xfff*chan[i].std.vol.val)/chan[i].volMacroMax,0xfff);
|
||||
int nextVol;
|
||||
if (amigaVol) {
|
||||
nextVol=VOL_SCALE_LINEAR(MIN(64,chan[i].vol),MIN(64,chan[i].std.vol.val),64);
|
||||
} else {
|
||||
nextVol=VOL_SCALE_LOG((0xfff*chan[i].vol)/0xff,(0xfff*chan[i].std.vol.val)/chan[i].volMacroMax,0xfff);
|
||||
}
|
||||
if (chan[i].outVol!=nextVol) {
|
||||
chan[i].outVol=nextVol;
|
||||
chan[i].volChanged.lVol=1;
|
||||
|
|
@ -363,7 +437,11 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
if (chan[i].volChanged.changed) {
|
||||
// calculate volume (16 bit)
|
||||
if (chan[i].volChanged.lVol) {
|
||||
chan[i].resLVol=VOL_SCALE_LOG(chan[i].outVol,chan[i].outLVol,0xfff);
|
||||
if (amigaVol) {
|
||||
chan[i].resLVol=VOL_SCALE_LOG(amigaVolTable[CLAMP(chan[i].outVol,0,64)],chan[i].outLVol,0xfff);
|
||||
} else {
|
||||
chan[i].resLVol=VOL_SCALE_LOG(chan[i].outVol,chan[i].outLVol,0xfff);
|
||||
}
|
||||
chan[i].resLVol-=volScale;
|
||||
if (chan[i].resLVol<0) chan[i].resLVol=0;
|
||||
chan[i].resLVol<<=4;
|
||||
|
|
@ -372,7 +450,11 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].volChanged.rVol) {
|
||||
chan[i].resRVol=VOL_SCALE_LOG(chan[i].outVol,chan[i].outRVol,0xfff);
|
||||
if (amigaVol) {
|
||||
chan[i].resRVol=VOL_SCALE_LOG(amigaVolTable[CLAMP(chan[i].outVol,0,64)],chan[i].outRVol,0xfff);
|
||||
} else {
|
||||
chan[i].resRVol=VOL_SCALE_LOG(chan[i].outVol,chan[i].outRVol,0xfff);
|
||||
}
|
||||
chan[i].resRVol-=volScale;
|
||||
if (chan[i].resRVol<0) chan[i].resRVol=0;
|
||||
chan[i].resRVol<<=4;
|
||||
|
|
@ -419,7 +501,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
const unsigned int length=s->samples-1;
|
||||
const unsigned int end=start+(length<<11);
|
||||
const unsigned int nextBank=(offES5506>>22)&3;
|
||||
const double nextFreqOffs=PITCH_OFFSET*off;
|
||||
const double nextFreqOffs=((amigaPitch && parent->song.linearPitch!=2)?16:PITCH_OFFSET)*off;
|
||||
chan[i].pcm.loopMode=loopMode;
|
||||
chan[i].pcm.bank=nextBank;
|
||||
chan[i].pcm.start=start;
|
||||
|
|
@ -451,7 +533,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
DivSample* s=parent->getSample(chan[i].pcm.index);
|
||||
const unsigned int start=sampleOffES5506[chan[i].pcm.index]<<10;
|
||||
const unsigned int nextLoopStart=(start+(s->loopStart<<11))&0xfffff800;
|
||||
const unsigned int nextLoopEnd=(start+((s->loopEnd-1)<<11))&0xffffff80;
|
||||
const unsigned int nextLoopEnd=(start+((s->loopEnd)<<11))&0xffffff80;
|
||||
if ((chan[i].pcm.loopStart!=nextLoopStart) || (chan[i].pcm.loopEnd!=nextLoopEnd)) {
|
||||
chan[i].pcm.loopStart=nextLoopStart;
|
||||
chan[i].pcm.loopEnd=nextLoopEnd;
|
||||
|
|
@ -538,33 +620,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].envChanged.changed=0;
|
||||
}
|
||||
if (chan[i].noteChanged.changed) { // note value changed or frequency offset is changed
|
||||
if (chan[i].noteChanged.offs) {
|
||||
if (chan[i].pcm.freqOffs!=chan[i].pcm.nextFreqOffs) {
|
||||
chan[i].pcm.freqOffs=chan[i].pcm.nextFreqOffs;
|
||||
chan[i].nextFreq=NOTE_ES5506(i,chan[i].currNote);
|
||||
chan[i].noteChanged.freq=1;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].noteChanged.note) {
|
||||
if (chan[i].currNote!=chan[i].nextNote) {
|
||||
chan[i].currNote=chan[i].nextNote;
|
||||
const int nextFreq=NOTE_ES5506(i,chan[i].nextNote);
|
||||
if (chan[i].nextFreq!=nextFreq) {
|
||||
chan[i].nextFreq=nextFreq;
|
||||
chan[i].noteChanged.freq=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].noteChanged.freq) {
|
||||
if (chan[i].baseFreq!=chan[i].nextFreq) {
|
||||
chan[i].baseFreq=chan[i].nextFreq;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
chan[i].noteChanged.changed=0;
|
||||
}
|
||||
updateNoteChangesAsNeeded(i);
|
||||
if (chan[i].pcm.setPos) {
|
||||
if (chan[i].active) {
|
||||
const unsigned int start=chan[i].pcm.start;
|
||||
|
|
@ -582,7 +638,12 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
chan[i].pcm.nextPos=0;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=CLAMP(parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,chan[i].pcm.freqOffs),0,0x1ffff);
|
||||
if (amigaPitch && parent->song.linearPitch!=2) {
|
||||
chan[i].freq=CLAMP(parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,2,chan[i].pitch2,COLOR_NTSC,chan[i].pcm.freqOffs),1,0xffff);
|
||||
chan[i].freq=32768*(COLOR_NTSC/chan[i].freq)/(chipClock/32.0);
|
||||
} else {
|
||||
chan[i].freq=CLAMP(parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,chan[i].pcm.freqOffs),0,0x1ffff);
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
if (chan[i].pcm.index>=0 && chan[i].pcm.index<parent->song.sampleLen) {
|
||||
const int ind=chan[i].pcm.index;
|
||||
|
|
@ -596,8 +657,8 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
off=(double)center/8363.0;
|
||||
}
|
||||
chan[i].pcm.loopStart=(chan[i].pcm.start+(s->loopStart<<11))&0xfffff800;
|
||||
chan[i].pcm.loopEnd=(chan[i].pcm.start+((s->loopEnd-1)<<11))&0xffffff80;
|
||||
chan[i].pcm.freqOffs=PITCH_OFFSET*off;
|
||||
chan[i].pcm.loopEnd=(chan[i].pcm.start+((s->loopEnd)<<11))&0xffffff80;
|
||||
chan[i].pcm.freqOffs=((amigaPitch && parent->song.linearPitch!=2)?16:PITCH_OFFSET)*off;
|
||||
unsigned int startPos=chan[i].pcm.direction?chan[i].pcm.end:chan[i].pcm.start;
|
||||
if (chan[i].pcm.nextPos) {
|
||||
const unsigned int start=chan[i].pcm.start;
|
||||
|
|
@ -735,7 +796,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
chan[c.chan].sampleNote=c.value;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
sampleValid=true;
|
||||
chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xfff;
|
||||
chan[c.chan].volMacroMax=(ins->type==DIV_INS_AMIGA || amigaVol)?64:0xfff;
|
||||
chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xfff;
|
||||
chan[c.chan].pcm.next=ins->amiga.useNoteMap?c.value:sample;
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
|
|
@ -750,7 +811,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
int sample=ins->amiga.getSample(chan[c.chan].sampleNote);
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
sampleValid=true;
|
||||
chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xfff;
|
||||
chan[c.chan].volMacroMax=(ins->type==DIV_INS_AMIGA || amigaVol)?64:0xfff;
|
||||
chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xfff;
|
||||
chan[c.chan].pcm.next=ins->amiga.useNoteMap?chan[c.chan].sampleNote:sample;
|
||||
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
|
||||
|
|
@ -775,15 +836,29 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
chan[c.chan].pcmChanged.changed=0xff;
|
||||
chan[c.chan].noteChanged.changed=0xff;
|
||||
chan[c.chan].volChanged.changed=0xff;
|
||||
updateNoteChangesAsNeeded(c.chan);
|
||||
}
|
||||
if (!chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=(0xfff*chan[c.chan].vol)/0xff;
|
||||
if (amigaVol) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
} else {
|
||||
chan[c.chan].outVol=(0xfff*chan[c.chan].vol)/0xff;
|
||||
}
|
||||
}
|
||||
if (!chan[c.chan].std.panL.will) {
|
||||
chan[c.chan].outLVol=(0xfff*chan[c.chan].lVol)/0xff;
|
||||
}
|
||||
if (!chan[c.chan].std.panR.will) {
|
||||
chan[c.chan].outRVol=(0xfff*chan[c.chan].rVol)/0xff;
|
||||
if (amigaVol) {
|
||||
if (!chan[c.chan].std.panL.will) {
|
||||
chan[c.chan].outLVol=amigaPanTable[(chan[c.chan].lVol>>1)&127];
|
||||
}
|
||||
if (!chan[c.chan].std.panR.will) {
|
||||
chan[c.chan].outRVol=amigaPanTable[(chan[c.chan].rVol>>1)&127];
|
||||
}
|
||||
} else {
|
||||
if (!chan[c.chan].std.panL.will) {
|
||||
chan[c.chan].outLVol=(0xfff*chan[c.chan].lVol)/0xff;
|
||||
}
|
||||
if (!chan[c.chan].std.panR.will) {
|
||||
chan[c.chan].outRVol=(0xfff*chan[c.chan].rVol)/0xff;
|
||||
}
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
|
|
@ -810,7 +885,11 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=(0xfff*c.value)/0xff;
|
||||
if (amigaVol) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
} else {
|
||||
chan[c.chan].outVol=(0xfff*c.value)/0xff;
|
||||
}
|
||||
chan[c.chan].volChanged.changed=0xff;
|
||||
}
|
||||
}
|
||||
|
|
@ -823,20 +902,39 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
chan[c.chan].ca=0;
|
||||
chan[c.chan].volChanged.ca=1;
|
||||
}
|
||||
// Left volume
|
||||
if (chan[c.chan].lVol!=c.value) {
|
||||
chan[c.chan].lVol=c.value;
|
||||
if (!chan[c.chan].std.panL.has) {
|
||||
chan[c.chan].outLVol=(0xfff*c.value)/0xff;
|
||||
chan[c.chan].volChanged.lVol=1;
|
||||
if (amigaVol) {
|
||||
// Left volume
|
||||
if (chan[c.chan].lVol!=c.value) {
|
||||
chan[c.chan].lVol=c.value;
|
||||
if (!chan[c.chan].std.panL.has) {
|
||||
chan[c.chan].outLVol=amigaPanTable[(c.value>>1)&127];
|
||||
chan[c.chan].volChanged.lVol=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Right volume
|
||||
if (chan[c.chan].rVol!=c.value2) {
|
||||
chan[c.chan].rVol=c.value2;
|
||||
if (!chan[c.chan].std.panR.has) {
|
||||
chan[c.chan].outRVol=(0xfff*c.value2)/0xff;
|
||||
chan[c.chan].volChanged.rVol=1;
|
||||
// Right volume
|
||||
if (chan[c.chan].rVol!=c.value2) {
|
||||
chan[c.chan].rVol=c.value2;
|
||||
if (!chan[c.chan].std.panR.has) {
|
||||
chan[c.chan].outRVol=amigaPanTable[(c.value2>>1)&127];
|
||||
chan[c.chan].volChanged.rVol=1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Left volume
|
||||
if (chan[c.chan].lVol!=c.value) {
|
||||
chan[c.chan].lVol=c.value;
|
||||
if (!chan[c.chan].std.panL.has) {
|
||||
chan[c.chan].outLVol=(0xfff*c.value)/0xff;
|
||||
chan[c.chan].volChanged.lVol=1;
|
||||
}
|
||||
}
|
||||
// Right volume
|
||||
if (chan[c.chan].rVol!=c.value2) {
|
||||
chan[c.chan].rVol=c.value2;
|
||||
if (!chan[c.chan].std.panR.has) {
|
||||
chan[c.chan].outRVol=(0xfff*c.value2)/0xff;
|
||||
chan[c.chan].volChanged.rVol=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -986,7 +1084,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int nextFreq=chan[c.chan].baseFreq;
|
||||
const int destFreq=NOTE_ES5506(c.chan,c.value2+chan[c.chan].sampleNoteDelta);
|
||||
int destFreq=NOTE_ES5506(c.chan,c.value2+chan[c.chan].sampleNoteDelta);
|
||||
bool return2=false;
|
||||
if (destFreq>nextFreq) {
|
||||
nextFreq+=c.value;
|
||||
|
|
@ -1038,7 +1136,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
return amigaVol?64:255;
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
|
|
@ -1093,6 +1191,8 @@ void DivPlatformES5506::reset() {
|
|||
while (!hostIntf8.empty()) hostIntf8.pop();
|
||||
for (int i=0; i<32; i++) {
|
||||
chan[i]=DivPlatformES5506::Channel();
|
||||
chan[i].vol=amigaVol?64:255;
|
||||
chan[i].outVol=amigaVol?64:255;
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
es5506.reset();
|
||||
|
|
@ -1148,6 +1248,8 @@ void DivPlatformES5506::setFlags(const DivConfig& flags) {
|
|||
|
||||
initChanMax=MAX(4,flags.getInt("channels",0x1f)&0x1f);
|
||||
volScale=4095-flags.getInt("volScale",4095);
|
||||
amigaVol=flags.getBool("amigaVol",false);
|
||||
amigaPitch=flags.getBool("amigaPitch",false);
|
||||
chanMax=initChanMax;
|
||||
pageWriteMask(0x00,0x60,0x0b,chanMax);
|
||||
|
||||
|
|
@ -1216,7 +1318,7 @@ void DivPlatformES5506::renderSamples(int sysID) {
|
|||
memCompo=DivMemoryComposition();
|
||||
memCompo.name="Sample Memory";
|
||||
|
||||
size_t memPos=128; // add silent at begin and end of each bank for reverse playback
|
||||
size_t memPos=128; // add silent at begin and end of each bank for reverse playback and add 1 for loop
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
if (!s->renderOn[0][sysID]) {
|
||||
|
|
@ -1241,6 +1343,11 @@ void DivPlatformES5506::renderSamples(int sysID) {
|
|||
logW("out of ES5506 memory for sample %d!",i);
|
||||
} else {
|
||||
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,length);
|
||||
// inject loop sample
|
||||
if (s->loop && s->loopEnd>=0 && s->loopEnd<=(int)s->samples && s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
sampleMem[(memPos/sizeof(short))+s->loopEnd]=s->data16[s->loopStart];
|
||||
if (s->loopEnd>=(int)s->samples) length+=2;
|
||||
}
|
||||
}
|
||||
sampleOffES5506[i]=memPos;
|
||||
sampleLoaded[i]=true;
|
||||
|
|
|
|||
|
|
@ -272,7 +272,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
unsigned char maskedVal;
|
||||
unsigned int irqv;
|
||||
bool isMasked, isReaded;
|
||||
bool irqTrigger;
|
||||
bool irqTrigger, amigaVol, amigaPitch;
|
||||
unsigned int curCR;
|
||||
|
||||
unsigned char initChanMax, chanMax;
|
||||
|
|
@ -281,6 +281,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
|
|||
DivMemoryComposition memCompo;
|
||||
unsigned char regPool[4*16*128]; // 7 bit page x 16 registers per page x 32 bit per registers
|
||||
|
||||
void updateNoteChangesAsNeeded(int ch);
|
||||
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
|
|
|
|||
|
|
@ -200,6 +200,15 @@ void DivPlatformFDS::tick(bool sysTick) {
|
|||
}
|
||||
rWrite(0x4082,chan[i].freq&0xff);
|
||||
rWrite(0x4083,(chan[i].freq>>8)&15);
|
||||
|
||||
if (chan[i].autoModNum>0 && chan[i].autoModDen>0) {
|
||||
chan[i].modFreq=(chan[i].freq*chan[i].autoModNum)/chan[i].autoModDen;
|
||||
if (chan[i].modFreq>4095) chan[i].modFreq=4095;
|
||||
if (chan[i].modFreq<0) chan[i].modFreq=0;
|
||||
rWrite(0x4086,chan[i].modFreq&0xff);
|
||||
rWrite(0x4087,chan[i].modFreq>>8);
|
||||
}
|
||||
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
|
|
@ -342,6 +351,13 @@ int DivPlatformFDS::dispatch(DivCommand c) {
|
|||
rWrite(0x4087,chan[c.chan].modFreq>>8);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FDS_MOD_AUTO:
|
||||
chan[c.chan].autoModNum=c.value>>4;
|
||||
chan[c.chan].autoModDen=c.value&15;
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].modOn=(chan[c.chan].autoModNum && chan[c.chan].autoModDen);
|
||||
rWrite(0x4084,(chan[c.chan].modOn<<7)|0x40|chan[c.chan].modDepth);
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class DivPlatformFDS: public DivDispatch {
|
|||
struct Channel: public SharedChannel<signed char> {
|
||||
int prevFreq, modFreq;
|
||||
unsigned char duty, sweep, modDepth, modPos;
|
||||
unsigned char autoModNum, autoModDen;
|
||||
bool sweepChanged, modOn;
|
||||
signed short wave;
|
||||
signed char modTable[32];
|
||||
|
|
@ -40,6 +41,8 @@ class DivPlatformFDS: public DivDispatch {
|
|||
sweep(8),
|
||||
modDepth(0),
|
||||
modPos(0),
|
||||
autoModNum(0),
|
||||
autoModDen(0),
|
||||
sweepChanged(false),
|
||||
modOn(false),
|
||||
wave(-1) {
|
||||
|
|
|
|||
|
|
@ -158,7 +158,8 @@ class DivPlatformOPN: public DivPlatformFMBase {
|
|||
unsigned char lastExtChPan;
|
||||
unsigned short ssgVol;
|
||||
unsigned short fmVol;
|
||||
bool extSys, useCombo, fbAllOps;
|
||||
bool extSys, fbAllOps;
|
||||
unsigned char useCombo;
|
||||
|
||||
DivConfig ayFlags;
|
||||
|
||||
|
|
@ -180,10 +181,10 @@ class DivPlatformOPN: public DivPlatformFMBase {
|
|||
ssgVol(128),
|
||||
fmVol(256),
|
||||
extSys(isExtSys),
|
||||
useCombo(false),
|
||||
fbAllOps(false) {}
|
||||
fbAllOps(false),
|
||||
useCombo(0) {}
|
||||
public:
|
||||
void setCombo(bool combo) {
|
||||
void setCombo(unsigned char combo) {
|
||||
useCombo=combo;
|
||||
}
|
||||
virtual int mapVelocity(int ch, float vel) {
|
||||
|
|
@ -197,6 +198,19 @@ class DivPlatformOPN: public DivPlatformFMBase {
|
|||
if (ch>=psgChanOffs) return round(15.0*pow(vel,0.33));
|
||||
return DivPlatformFMBase::mapVelocity(ch,vel);
|
||||
}
|
||||
virtual float getGain(int ch, int vol) {
|
||||
if (vol==0) return 0;
|
||||
if (ch==csmChan) return 1;
|
||||
if (ch==adpcmBChanOffs) return (float)vol/255.0;
|
||||
if (ch>=adpcmAChanOffs) {
|
||||
return 1.0/pow(10.0,(float)(31-vol)*0.75/20.0);
|
||||
}
|
||||
if (ch>=psgChanOffs) {
|
||||
return 1.0/pow(10.0,(float)(15-vol)*1.5/20.0);
|
||||
}
|
||||
return DivPlatformFMBase::getGain(ch,vol);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -136,6 +136,11 @@ class DivPlatformFMBase: public DivDispatch {
|
|||
return CLAMP(round(128.0-(56.0-log2(vel*127.0)*8.0)),0,127);
|
||||
}
|
||||
|
||||
virtual float getGain(int ch, int vol) {
|
||||
if (vol==0) return 0;
|
||||
return 1.0/pow(10.0,(float)(127-vol)*0.75/20.0);
|
||||
}
|
||||
|
||||
bool getLegacyAlwaysSetVolume() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ void DivPlatformGB::acquire(short** buf, size_t len) {
|
|||
writes.pop();
|
||||
}
|
||||
|
||||
GB_advance_cycles(gb,16);
|
||||
GB_advance_cycles(gb,coreQuality);
|
||||
buf[0][i]=gb->apu_output.final_sample.left;
|
||||
buf[1][i]=gb->apu_output.final_sample.right;
|
||||
|
||||
|
|
@ -651,7 +651,7 @@ void DivPlatformGB::reset() {
|
|||
immWrite(0x26,0x8f);
|
||||
lastPan=0xff;
|
||||
immWrite(0x25,procMute());
|
||||
immWrite(0x24,0x77);
|
||||
immWrite(0x24,0xff);
|
||||
|
||||
antiClickPeriodCount=0;
|
||||
antiClickWavePos=0;
|
||||
|
|
@ -722,12 +722,39 @@ void DivPlatformGB::setFlags(const DivConfig& flags) {
|
|||
|
||||
chipClock=4194304;
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/16;
|
||||
rate=chipClock/coreQuality;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformGB::setCoreQuality(unsigned char q) {
|
||||
switch (q) {
|
||||
case 0:
|
||||
// sorry...
|
||||
coreQuality=64;
|
||||
break;
|
||||
case 1:
|
||||
coreQuality=64;
|
||||
break;
|
||||
case 2:
|
||||
coreQuality=32;
|
||||
break;
|
||||
case 3:
|
||||
coreQuality=16;
|
||||
break;
|
||||
case 4:
|
||||
coreQuality=4;
|
||||
break;
|
||||
case 5:
|
||||
coreQuality=1;
|
||||
break;
|
||||
default:
|
||||
coreQuality=16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ class DivPlatformGB: public DivDispatch {
|
|||
|
||||
int antiClickPeriodCount, antiClickWavePos;
|
||||
|
||||
int coreQuality;
|
||||
GB_gameboy_t* gb;
|
||||
GB_model_t model;
|
||||
unsigned char regPool[128];
|
||||
|
|
@ -104,6 +105,7 @@ class DivPlatformGB: public DivDispatch {
|
|||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
void setFlags(const DivConfig& flags);
|
||||
void setCoreQuality(unsigned char q);
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
~DivPlatformGB();
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ void DivPlatformGBAMinMod::tick(bool sysTick) {
|
|||
size_t maxPos=getSampleMemCapacity();
|
||||
start=sampleOff[chan[i].sample];
|
||||
if (s->isLoopable()) {
|
||||
end=MIN(start+MAX(s->length8,1),maxPos);
|
||||
end=MIN(start+MAX(s->loopEnd,1),maxPos);
|
||||
loop=start+s->loopStart;
|
||||
} else {
|
||||
end=MIN(start+s->length8+16,maxPos);
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ void DivYM2612Interface::clock() {
|
|||
}
|
||||
|
||||
void DivPlatformGenesis::processDAC(int iRate) {
|
||||
if (interruptSim>0) {
|
||||
interruptSim--;
|
||||
return;
|
||||
}
|
||||
if (softPCM) {
|
||||
softPCMTimer+=chipClock/576;
|
||||
if (softPCMTimer>iRate) {
|
||||
|
|
@ -594,6 +598,10 @@ void DivPlatformGenesis::fillStream(std::vector<DivDelayedWrite>& stream, int sR
|
|||
}
|
||||
|
||||
void DivPlatformGenesis::tick(bool sysTick) {
|
||||
if (sysTick) {
|
||||
interruptSim=interruptSimCycles*(useYMFM==0?4:1);
|
||||
}
|
||||
|
||||
for (int i=0; i<(softPCM?7:6); i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
chan[i].std.next();
|
||||
|
|
@ -1661,6 +1669,7 @@ void DivPlatformGenesis::reset() {
|
|||
OPN2_SetChipType(&fm,0);
|
||||
break;
|
||||
}
|
||||
OPN2_SetMSW(&fm,msw?1:0);
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
|
|
@ -1682,6 +1691,7 @@ void DivPlatformGenesis::reset() {
|
|||
flushFirst=false;
|
||||
dacWrite=-1;
|
||||
canWriteDAC=true;
|
||||
interruptSim=0;
|
||||
|
||||
if (softPCM) {
|
||||
chan[5].dacMode=true;
|
||||
|
|
@ -1766,6 +1776,8 @@ void DivPlatformGenesis::setFlags(const DivConfig& flags) {
|
|||
}
|
||||
noExtMacros=flags.getBool("noExtMacros",false);
|
||||
fbAllOps=flags.getBool("fbAllOps",false);
|
||||
msw=flags.getBool("msw",false);
|
||||
interruptSimCycles=flags.getInt("interruptSimCycles",0);
|
||||
switch (chipType) {
|
||||
case 1: // YM2612
|
||||
OPN2_SetChipType(&fm,ym3438_mode_ym2612);
|
||||
|
|
@ -1777,6 +1789,7 @@ void DivPlatformGenesis::setFlags(const DivConfig& flags) {
|
|||
OPN2_SetChipType(&fm,0);
|
||||
break;
|
||||
}
|
||||
OPN2_SetMSW(&fm,msw?1:0);
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
if (useYMFM==1) {
|
||||
if (fm_ymfm!=NULL) delete fm_ymfm;
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
|||
|
||||
int softPCMTimer;
|
||||
|
||||
bool extMode, softPCM, noExtMacros, canWriteDAC;
|
||||
bool extMode, softPCM, noExtMacros, canWriteDAC, msw;
|
||||
unsigned char useYMFM;
|
||||
unsigned char chipType;
|
||||
short dacWrite;
|
||||
|
|
@ -96,6 +96,9 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
|||
int llePrevCycle;
|
||||
int lleOscData[6];
|
||||
int dacShifter, o_lro, o_bco;
|
||||
|
||||
int interruptSim;
|
||||
int interruptSimCycles;
|
||||
|
||||
unsigned char dacVolTable[128];
|
||||
|
||||
|
|
|
|||
|
|
@ -135,7 +135,11 @@ void DivPlatformLynx::acquire(short** buf, size_t len) {
|
|||
if (isMuted[i]) {
|
||||
WRITE_OUTPUT(i,0);
|
||||
} else {
|
||||
WRITE_OUTPUT(i,CLAMP((s->data8[chan[i].samplePos]*chan[i].outVol)>>7,-128,127));
|
||||
if (chan[i].samplePos<0 || chan[i].samplePos>=(int)s->samples) {
|
||||
WRITE_OUTPUT(i,0);
|
||||
} else {
|
||||
WRITE_OUTPUT(i,CLAMP((s->data8[chan[i].samplePos]*chan[i].outVol)>>7,-128,127));
|
||||
}
|
||||
}
|
||||
chan[i].samplePos++;
|
||||
|
||||
|
|
@ -209,7 +213,11 @@ void DivPlatformLynx::tick(bool sysTick) {
|
|||
if (chan[i].std.phaseReset.val==1) {
|
||||
if (chan[i].pcm && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
chan[i].sampleAccum=0;
|
||||
chan[i].samplePos=0;
|
||||
if (chan[i].setPos) {
|
||||
chan[i].setPos=false;
|
||||
} else {
|
||||
chan[i].samplePos=0;
|
||||
}
|
||||
}
|
||||
WRITE_LFSR(i, 0);
|
||||
WRITE_OTHER(i, 0);
|
||||
|
|
@ -294,7 +302,11 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
|
||||
}
|
||||
chan[c.chan].sampleAccum=0;
|
||||
chan[c.chan].samplePos=0;
|
||||
if (chan[c.chan].setPos) {
|
||||
chan[c.chan].setPos=false;
|
||||
} else {
|
||||
chan[c.chan].samplePos=0;
|
||||
}
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
|
|
@ -400,6 +412,10 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].samplePos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 127;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class DivPlatformLynx: public DivDispatch {
|
|||
MikeyDuty duty;
|
||||
int actualNote, lfsr, sample, samplePos, sampleAccum, sampleBaseFreq, sampleFreq;
|
||||
unsigned char pan;
|
||||
bool pcm;
|
||||
bool pcm, setPos;
|
||||
int macroVolMul;
|
||||
Channel():
|
||||
SharedChannel<signed char>(127),
|
||||
|
|
@ -60,6 +60,7 @@ class DivPlatformLynx: public DivDispatch {
|
|||
sampleFreq(0),
|
||||
pan(0xff),
|
||||
pcm(false),
|
||||
setPos(false),
|
||||
macroVolMul(127) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ void DivPlatformMMC5::acquire(short** buf, size_t len) {
|
|||
dacPeriod+=dacRate;
|
||||
if (dacPeriod>=rate) {
|
||||
DivSample* s=parent->getSample(dacSample);
|
||||
if (s->samples>0) {
|
||||
if (s->samples>0 && dacPos<s->samples) {
|
||||
if (!isMuted[2]) {
|
||||
rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80));
|
||||
}
|
||||
|
|
@ -192,7 +192,11 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
dacPos=0;
|
||||
if (chan[c.chan].setPos) {
|
||||
chan[c.chan].setPos=false;
|
||||
} else {
|
||||
dacPos=0;
|
||||
}
|
||||
dacPeriod=0;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
|
|
@ -214,7 +218,11 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
dacPos=0;
|
||||
if (chan[c.chan].setPos) {
|
||||
chan[c.chan].setPos=false;
|
||||
} else {
|
||||
dacPos=0;
|
||||
}
|
||||
dacPeriod=0;
|
||||
dacRate=parent->getSample(dacSample)->rate;
|
||||
if (dumpWrites) addWrite(0xffff0001,dacRate);
|
||||
|
|
@ -307,6 +315,11 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
if (c.chan!=2) break;
|
||||
dacPos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
if (c.chan==2) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+chan[c.chan].sampleNoteDelta+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)),false);
|
||||
|
|
|
|||
|
|
@ -26,14 +26,15 @@ class DivPlatformMMC5: public DivDispatch {
|
|||
struct Channel: public SharedChannel<signed char> {
|
||||
int prevFreq;
|
||||
unsigned char duty, sweep;
|
||||
bool sweepChanged, furnaceDac;
|
||||
bool sweepChanged, furnaceDac, setPos;
|
||||
Channel():
|
||||
SharedChannel<signed char>(15),
|
||||
prevFreq(65535),
|
||||
duty(0),
|
||||
sweep(8),
|
||||
sweepChanged(false),
|
||||
furnaceDac(false) {}
|
||||
furnaceDac(false),
|
||||
setPos(false) {}
|
||||
};
|
||||
Channel chan[5];
|
||||
DivDispatchOscBuffer* oscBuf[3];
|
||||
|
|
|
|||
|
|
@ -39,14 +39,25 @@ const char** DivPlatformMSM6295::getRegisterSheet() {
|
|||
}
|
||||
|
||||
u8 DivPlatformMSM6295::read_byte(u32 address) {
|
||||
if (adpcmMem==NULL || address>=getSampleMemCapacity(0)) {
|
||||
if (adpcmMem==NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (isBanked) {
|
||||
if (address<0x400) {
|
||||
return adpcmMem[(bank[(address>>8)&0x3]<<16)|(address&0x3ff)];
|
||||
unsigned int bankedAddress=(bank[(address>>8)&0x3]<<16)|(address&0x3ff);
|
||||
if (bankedAddress>=getSampleMemCapacity(0)) {
|
||||
return 0;
|
||||
}
|
||||
return adpcmMem[bankedAddress&0xffffff];
|
||||
}
|
||||
return adpcmMem[(bank[(address>>16)&0x3]<<16)|(address&0xffff)];
|
||||
unsigned int bankedAddress=(bank[(address>>16)&0x3]<<16)|(address&0xffff);
|
||||
if (bankedAddress>=getSampleMemCapacity(0)) {
|
||||
return 0;
|
||||
}
|
||||
return adpcmMem[bankedAddress&0xffffff];
|
||||
}
|
||||
if (address>=getSampleMemCapacity(0)) {
|
||||
return 0;
|
||||
}
|
||||
return adpcmMem[address&0x3ffff];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@
|
|||
#include <math.h>
|
||||
|
||||
#define CHIP_DIVIDER 32
|
||||
#define CLOCK_DIVIDER 128 // for match to output rate
|
||||
|
||||
#define rRead8(a) (nds.read8(a))
|
||||
#define rWrite8(a,v) {if(!skipRegisterWrites) {nds.write8((a),(v)); regPool[(a)]=(v); if(dumpWrites) addWrite((a),(v)); }}
|
||||
|
|
@ -71,7 +70,7 @@ const char** DivPlatformNDS::getRegisterSheet() {
|
|||
|
||||
void DivPlatformNDS::acquire(short** buf, size_t len) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
nds.tick(CLOCK_DIVIDER);
|
||||
nds.tick(coreQuality);
|
||||
int lout=((nds.loutput()-0x200)<<5); // scale to 16 bit
|
||||
int rout=((nds.routput()-0x200)<<5); // scale to 16 bit
|
||||
if (lout>32767) lout=32767;
|
||||
|
|
@ -262,6 +261,8 @@ int DivPlatformNDS::dispatch(DivCommand c) {
|
|||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_NDS);
|
||||
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample || (c.chan<8)) {
|
||||
chan[c.chan].pcm=true;
|
||||
} else {
|
||||
chan[c.chan].pcm=false;
|
||||
}
|
||||
if (chan[c.chan].pcm || (c.chan<8)) {
|
||||
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127;
|
||||
|
|
@ -561,10 +562,37 @@ void DivPlatformNDS::renderSamples(int sysID) {
|
|||
void DivPlatformNDS::setFlags(const DivConfig& flags) {
|
||||
isDSi=flags.getBool("chipType",0);
|
||||
chipClock=33513982;
|
||||
rate=chipClock/2/CLOCK_DIVIDER;
|
||||
rate=chipClock/2/coreQuality;
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
memCompo.capacity=(isDSi?16777216:4194304);
|
||||
}
|
||||
|
||||
void DivPlatformNDS::setCoreQuality(unsigned char q) {
|
||||
switch (q) {
|
||||
case 0:
|
||||
coreQuality=1024;
|
||||
break;
|
||||
case 1:
|
||||
coreQuality=512;
|
||||
break;
|
||||
case 2:
|
||||
coreQuality=256;
|
||||
break;
|
||||
case 3:
|
||||
coreQuality=128;
|
||||
break;
|
||||
case 4:
|
||||
coreQuality=32;
|
||||
break;
|
||||
case 5:
|
||||
coreQuality=8;
|
||||
break;
|
||||
default:
|
||||
coreQuality=128;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformNDS::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf {
|
|||
|
||||
unsigned char* sampleMem;
|
||||
size_t sampleMemLen;
|
||||
int coreQuality;
|
||||
nds_sound_t nds;
|
||||
DivMemoryComposition memCompo;
|
||||
unsigned char regPool[288];
|
||||
|
|
@ -91,6 +92,7 @@ class DivPlatformNDS: public DivDispatch, public nds_sound_intf {
|
|||
virtual const DivMemoryComposition* getMemCompo(int index) override;
|
||||
virtual void renderSamples(int chipID) override;
|
||||
virtual void setFlags(const DivConfig& flags) override;
|
||||
void setCoreQuality(unsigned char q);
|
||||
virtual int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags) override;
|
||||
virtual void quit() override;
|
||||
DivPlatformNDS():
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
|||
dacPeriod+=dacRate; \
|
||||
if (dacPeriod>=rate) { \
|
||||
DivSample* s=parent->getSample(dacSample); \
|
||||
if (s->samples>0) { \
|
||||
if (s->samples>0 && dacPos<s->samples) { \
|
||||
if (!isMuted[4]) { \
|
||||
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \
|
||||
if (dacAntiClickOn && dacAntiClick<next) { \
|
||||
|
|
@ -332,6 +332,10 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
// retrigger if sweep is on
|
||||
if (chan[i].sweep!=0x08) {
|
||||
chan[i].prevFreq=-1;
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
//rWrite(16+i*5+2,8);
|
||||
|
|
@ -371,8 +375,9 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
dacRate=MIN(chan[4].freq*off,32000);
|
||||
if (chan[4].keyOn) {
|
||||
if (dpcmMode && !skipRegisterWrites && dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||
unsigned int dpcmAddr=sampleOffDPCM[dacSample];
|
||||
unsigned int dpcmLen=parent->getSample(dacSample)->lengthDPCM>>4;
|
||||
unsigned int dpcmAddr=sampleOffDPCM[dacSample]+(dacPos>>3);
|
||||
int dpcmLen=(parent->getSample(dacSample)->lengthDPCM-(dacPos>>3))>>4;
|
||||
if (dpcmLen<0) dpcmLen=0;
|
||||
if (dpcmLen>255) dpcmLen=255;
|
||||
goingToLoop=parent->getSample(dacSample)->isLoopable();
|
||||
// write DPCM
|
||||
|
|
@ -464,7 +469,11 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
} else {
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
dacPos=0;
|
||||
if (chan[c.chan].setPos) {
|
||||
chan[c.chan].setPos=false;
|
||||
} else {
|
||||
dacPos=0;
|
||||
}
|
||||
dacPeriod=0;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
|
|
@ -486,14 +495,19 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
} else {
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
|
||||
}
|
||||
dacPos=0;
|
||||
if (chan[c.chan].setPos) {
|
||||
chan[c.chan].setPos=false;
|
||||
} else {
|
||||
dacPos=0;
|
||||
}
|
||||
dacPeriod=0;
|
||||
dacRate=parent->getSample(dacSample)->rate;
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
|
||||
chan[c.chan].furnaceDac=false;
|
||||
if (dpcmMode && !skipRegisterWrites) {
|
||||
unsigned int dpcmAddr=sampleOffDPCM[dacSample];
|
||||
unsigned int dpcmLen=parent->getSample(dacSample)->lengthDPCM>>4;
|
||||
unsigned int dpcmAddr=sampleOffDPCM[dacSample]+(dacPos>>3);
|
||||
int dpcmLen=(parent->getSample(dacSample)->lengthDPCM-(dacPos>>3))>>4;
|
||||
if (dpcmLen<0) dpcmLen=0;
|
||||
if (dpcmLen>255) dpcmLen=255;
|
||||
goingToLoop=parent->getSample(dacSample)->isLoopable();
|
||||
// write DPCM
|
||||
|
|
@ -682,6 +696,15 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
if (c.chan!=4) break;
|
||||
dacPos=c.value;
|
||||
dpcmPos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
if (c.chan==3) break;
|
||||
if (c.chan==4) {
|
||||
|
|
@ -777,6 +800,7 @@ void DivPlatformNES::reset() {
|
|||
|
||||
dacPeriod=0;
|
||||
dacPos=0;
|
||||
dpcmPos=0;
|
||||
dacRate=0;
|
||||
dacSample=-1;
|
||||
sampleBank=0;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ class DivPlatformNES: public DivDispatch {
|
|||
struct Channel: public SharedChannel<signed char> {
|
||||
int prevFreq;
|
||||
unsigned char duty, sweep, envMode, len;
|
||||
bool sweepChanged, furnaceDac;
|
||||
bool sweepChanged, furnaceDac, setPos;
|
||||
Channel():
|
||||
SharedChannel<signed char>(15),
|
||||
prevFreq(65535),
|
||||
|
|
@ -38,12 +38,13 @@ class DivPlatformNES: public DivDispatch {
|
|||
envMode(3),
|
||||
len(0x1f),
|
||||
sweepChanged(false),
|
||||
furnaceDac(false) {}
|
||||
furnaceDac(false),
|
||||
setPos(false) {}
|
||||
};
|
||||
Channel chan[5];
|
||||
DivDispatchOscBuffer* oscBuf[5];
|
||||
bool isMuted[5];
|
||||
int dacPeriod, dacRate;
|
||||
int dacPeriod, dacRate, dpcmPos;
|
||||
unsigned int dacPos, dacAntiClick;
|
||||
int dacSample;
|
||||
unsigned char* dpcmMem;
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) {
|
|||
}
|
||||
}
|
||||
|
||||
if (fm.rhy&0x20) {
|
||||
if (properDrums) {
|
||||
for (int i=0; i<melodicChans+1; i++) {
|
||||
unsigned char ch=outChanMap[i];
|
||||
int chOut=0;
|
||||
|
|
@ -709,7 +709,7 @@ void DivPlatformOPL::acquire_nukedLLE3(short** buf, size_t len) {
|
|||
fm_lle3.input.address=(w.addr&0x100)?3:1;
|
||||
fm_lle3.input.data_i=w.val;
|
||||
writes.pop();
|
||||
delay=16;
|
||||
delay=18;
|
||||
} else {
|
||||
fm_lle3.input.cs=0;
|
||||
fm_lle3.input.rd=1;
|
||||
|
|
@ -718,7 +718,7 @@ void DivPlatformOPL::acquire_nukedLLE3(short** buf, size_t len) {
|
|||
fm_lle3.input.data_i=w.addr&0xff;
|
||||
w.addrOrVal=true;
|
||||
// weird. wasn't it 12?
|
||||
delay=16;
|
||||
delay=18;
|
||||
}
|
||||
|
||||
waitingBusy=true;
|
||||
|
|
@ -1039,7 +1039,7 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
|
||||
if (chan[adpcmChan].std.vol.had) {
|
||||
chan[adpcmChan].outVol=(chan[adpcmChan].vol*MIN(chan[adpcmChan].macroVolMul,chan[adpcmChan].std.vol.val))/chan[adpcmChan].macroVolMul;
|
||||
immWrite(18,chan[adpcmChan].outVol);
|
||||
immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol));
|
||||
}
|
||||
|
||||
if (NEW_ARP_STRAT) {
|
||||
|
|
@ -1217,7 +1217,10 @@ int DivPlatformOPL::toFreq(int freq) {
|
|||
|
||||
void DivPlatformOPL::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
if (ch==adpcmChan) return;
|
||||
if (ch==adpcmChan) {
|
||||
immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol));
|
||||
return;
|
||||
}
|
||||
if (oplType<3 && ch<melodicChans) {
|
||||
fm.channel[outChanMap[ch]].muted=mute;
|
||||
}
|
||||
|
|
@ -1380,7 +1383,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
chan[c.chan].fixedFreq=0;
|
||||
if (!chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
immWrite(18,chan[c.chan].outVol);
|
||||
immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol));
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
|
|
@ -1511,7 +1514,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=c.value;
|
||||
}
|
||||
if (c.chan==adpcmChan) { // ADPCM-B
|
||||
immWrite(18,chan[c.chan].outVol);
|
||||
immWrite(18,(isMuted[adpcmChan]?0:chan[adpcmChan].outVol));
|
||||
break;
|
||||
}
|
||||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
|
|
@ -2137,6 +2140,11 @@ int DivPlatformOPL::mapVelocity(int ch, float vel) {
|
|||
return CLAMP(round(64.0-(56.0-log2(vel*127.0)*8.0)),0,63);
|
||||
}
|
||||
|
||||
float DivPlatformOPL::getGain(int ch, int vol) {
|
||||
if (vol==0) return 0;
|
||||
return 1.0/pow(10.0,(float)(63-vol)*0.75/20.0);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformOPL::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
|
@ -2243,7 +2251,7 @@ void DivPlatformOPL::reset() {
|
|||
adpcmB->reset();
|
||||
|
||||
// volume
|
||||
immWrite(18,0xff);
|
||||
immWrite(18,(isMuted[adpcmChan]?0:0xff));
|
||||
// ADPCM limit
|
||||
immWrite(20,0xff);
|
||||
immWrite(19,0xff);
|
||||
|
|
@ -2461,7 +2469,11 @@ void DivPlatformOPL::setFlags(const DivConfig& flags) {
|
|||
switch (flags.getInt("chipType",0)) {
|
||||
case 1: // YMF289B
|
||||
chipFreqBase=32768*684;
|
||||
rate=chipClock/768;
|
||||
if (emuCore==2) {
|
||||
rate=chipClock/684;
|
||||
} else {
|
||||
rate=chipClock/768;
|
||||
}
|
||||
chipRateBase=chipClock/684;
|
||||
downsample=true;
|
||||
totalOutputs=2; // Stereo output only
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
bool lastSH2;
|
||||
bool lastSY;
|
||||
bool waitingBusy;
|
||||
int downsamplerStep;
|
||||
|
||||
unsigned char* adpcmBMem;
|
||||
size_t adpcmBMemLen;
|
||||
|
|
@ -153,6 +154,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
DivChannelPair getPaired(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
int mapVelocity(int ch, float vel);
|
||||
float getGain(int ch, int vol);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ uint8_t DivOPLAInterface::ymfm_external_read(ymfm::access_class type, uint32_t a
|
|||
if (adpcmBMem==NULL) {
|
||||
return 0;
|
||||
}
|
||||
return adpcmBMem[address&0xffffff];
|
||||
return adpcmBMem[address&0x3ffff];
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,8 +93,45 @@ void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) {
|
|||
void DivPlatformOPLL::acquire_ymfm(short** buf, size_t len) {
|
||||
}
|
||||
|
||||
static const unsigned char freakingDrumMap[5]={
|
||||
9, 11, 12, 13, 10
|
||||
};
|
||||
|
||||
void DivPlatformOPLL::acquire_emu(short** buf, size_t len) {
|
||||
thread_local int os;
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite& w=writes.front();
|
||||
OPLL_writeReg(fm_emu,w.addr,w.val);
|
||||
writes.pop();
|
||||
}
|
||||
os=-OPLL_calc(fm_emu);
|
||||
os=os+(os<<1);
|
||||
if (os<-32768) os=-32768;
|
||||
if (os>32767) os=32767;
|
||||
|
||||
buf[0][h]=os;
|
||||
|
||||
for (int i=0; i<11; i++) {
|
||||
if (i>=6 && properDrums) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(-fm_emu->ch_out[freakingDrumMap[i-6]])<<3;
|
||||
} else {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(-fm_emu->ch_out[i])<<3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformOPLL::acquire(short** buf, size_t len) {
|
||||
acquire_nuked(buf,len);
|
||||
switch (selCore) {
|
||||
case 0:
|
||||
acquire_nuked(buf,len);
|
||||
break;
|
||||
case 1:
|
||||
acquire_emu(buf,len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformOPLL::tick(bool sysTick) {
|
||||
|
|
@ -357,6 +394,19 @@ int DivPlatformOPLL::toFreq(int freq) {
|
|||
|
||||
void DivPlatformOPLL::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
if (selCore==1) {
|
||||
OPLL_setMask(fm_emu,
|
||||
(isMuted[0]?1:0)|
|
||||
(isMuted[1]?2:0)|
|
||||
(isMuted[2]?4:0)|
|
||||
(isMuted[3]?8:0)|
|
||||
(isMuted[4]?16:0)|
|
||||
(isMuted[5]?32:0)|
|
||||
(isMuted[6]?64:0)|
|
||||
(isMuted[7]?128:0)|
|
||||
(isMuted[8]?256:0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformOPLL::commitState(int ch, DivInstrument* ins) {
|
||||
|
|
@ -642,6 +692,13 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_WAVE: {
|
||||
if (c.chan>=6 && (crapDrums || properDrums)) break;
|
||||
if (c.chan>=9) break;
|
||||
chan[c.chan].state.opllPreset=c.value&15;
|
||||
rWrite(0x30+c.chan,((15-VOL_SCALE_LOG_BROKEN(chan[c.chan].outVol,15-chan[c.chan].state.op[1].tl,15))&15)|(chan[c.chan].state.opllPreset<<4));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_FB: {
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
//DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0];
|
||||
|
|
@ -958,7 +1015,7 @@ DivMacroInt* DivPlatformOPLL::getChanMacroInt(int ch) {
|
|||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformOPLL::getOscBuffer(int ch) {
|
||||
if (ch>=9) return NULL;
|
||||
if (ch>=9 && selCore==0) return NULL;
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
|
|
@ -969,6 +1026,11 @@ int DivPlatformOPLL::mapVelocity(int ch, float vel) {
|
|||
return CLAMP(round(16.0-(14.0-log2(vel*127.0)*2.0)),0,15);
|
||||
}
|
||||
|
||||
float DivPlatformOPLL::getGain(int ch, int vol) {
|
||||
if (vol==0) return 0;
|
||||
return 1.0/pow(10.0,(float)(15-vol)*3.0/20.0);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformOPLL::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
|
@ -977,6 +1039,10 @@ int DivPlatformOPLL::getRegisterPoolSize() {
|
|||
return 64;
|
||||
}
|
||||
|
||||
static const unsigned char nukedToEmuPatch[4]={
|
||||
0, 2, 3, 1
|
||||
};
|
||||
|
||||
void DivPlatformOPLL::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
memset(regPool,0,256);
|
||||
|
|
@ -1002,6 +1068,22 @@ void DivPlatformOPLL::reset() {
|
|||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
if (selCore==1) {
|
||||
OPLL_reset(fm_emu);
|
||||
OPLL_setChipType(fm_emu,vrc7?1:0);
|
||||
OPLL_resetPatch(fm_emu,vrc7?1:nukedToEmuPatch[patchSet&3]);
|
||||
OPLL_setMask(fm_emu,
|
||||
(isMuted[0]?1:0)|
|
||||
(isMuted[1]?2:0)|
|
||||
(isMuted[2]?4:0)|
|
||||
(isMuted[3]?8:0)|
|
||||
(isMuted[4]?16:0)|
|
||||
(isMuted[5]?32:0)|
|
||||
(isMuted[6]?64:0)|
|
||||
(isMuted[7]?128:0)|
|
||||
(isMuted[8]?256:0)
|
||||
);
|
||||
}
|
||||
for (int i=0; i<11; i++) {
|
||||
chan[i]=DivPlatformOPLL::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
|
|
@ -1073,8 +1155,8 @@ int DivPlatformOPLL::getPortaFloor(int ch) {
|
|||
return (ch>5)?12:0;
|
||||
}
|
||||
|
||||
void DivPlatformOPLL::setYMFM(bool use) {
|
||||
useYMFM=use;
|
||||
void DivPlatformOPLL::setCore(unsigned char which) {
|
||||
selCore=which;
|
||||
}
|
||||
|
||||
float DivPlatformOPLL::getPostAmp() {
|
||||
|
|
@ -1093,10 +1175,22 @@ void DivPlatformOPLL::setFlags(const DivConfig& flags) {
|
|||
chipClock=COLOR_NTSC;
|
||||
}
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/36;
|
||||
patchSet=flags.getInt("patchSet",0);
|
||||
if (selCore==1) {
|
||||
rate=chipClock/72;
|
||||
} else {
|
||||
rate=chipClock/36;
|
||||
}
|
||||
for (int i=0; i<11; i++) {
|
||||
oscBuf[i]->rate=rate/2;
|
||||
if (selCore==1) {
|
||||
oscBuf[i]->rate=rate;
|
||||
} else {
|
||||
if (i>=6 && properDrumsSys) {
|
||||
oscBuf[i]->rate=rate;
|
||||
} else {
|
||||
oscBuf[i]->rate=rate/2;
|
||||
}
|
||||
}
|
||||
}
|
||||
noTopHatFreq=flags.getBool("noTopHatFreq",false);
|
||||
fixedAll=flags.getBool("fixedAll",true);
|
||||
|
|
@ -1107,6 +1201,12 @@ int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, const DivConf
|
|||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
patchSet=0;
|
||||
fm_emu=NULL;
|
||||
if (selCore==1) {
|
||||
fm_emu=OPLL_new(72,1);
|
||||
OPLL_setChipType(fm_emu,vrc7?1:0);
|
||||
}
|
||||
|
||||
for (int i=0; i<11; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
|
|
@ -1121,6 +1221,10 @@ void DivPlatformOPLL::quit() {
|
|||
for (int i=0; i<11; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
if (fm_emu!=NULL) {
|
||||
OPLL_delete(fm_emu);
|
||||
fm_emu=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformOPLL::~DivPlatformOPLL() {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
extern "C" {
|
||||
#include "../../../extern/Nuked-OPLL/opll.h"
|
||||
}
|
||||
#include "../../../extern/emu2413/emu2413.h"
|
||||
|
||||
class DivPlatformOPLL: public DivDispatch {
|
||||
protected:
|
||||
|
|
@ -55,6 +56,7 @@ class DivPlatformOPLL: public DivDispatch {
|
|||
};
|
||||
FixedQueue<QueuedWrite,512> writes;
|
||||
opll_t fm;
|
||||
OPLL* fm_emu;
|
||||
int delay, lastCustomMemory;
|
||||
unsigned char lastBusy;
|
||||
unsigned char drumState;
|
||||
|
|
@ -68,7 +70,7 @@ class DivPlatformOPLL: public DivDispatch {
|
|||
|
||||
unsigned char regPool[256];
|
||||
|
||||
bool useYMFM;
|
||||
unsigned char selCore;
|
||||
bool crapDrums;
|
||||
bool properDrums, properDrumsSys, noTopHatFreq, fixedAll;
|
||||
bool vrc7;
|
||||
|
|
@ -88,6 +90,7 @@ class DivPlatformOPLL: public DivDispatch {
|
|||
|
||||
void acquire_nuked(short** buf, size_t len);
|
||||
void acquire_ymfm(short** buf, size_t len);
|
||||
void acquire_emu(short** buf, size_t len);
|
||||
|
||||
public:
|
||||
void acquire(short** buf, size_t len);
|
||||
|
|
@ -96,13 +99,14 @@ class DivPlatformOPLL: public DivDispatch {
|
|||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
int mapVelocity(int ch, float vel);
|
||||
float getGain(int ch, int vol);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setYMFM(bool use);
|
||||
void setCore(unsigned char which);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
bool keyOffAffectsPorta(int ch);
|
||||
bool getLegacyAlwaysSetVolume();
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ void DivPlatformPCE::acquire(short** buf, size_t len) {
|
|||
chan[i].dacPeriod+=chan[i].dacRate;
|
||||
if (chan[i].dacPeriod>rate) {
|
||||
DivSample* s=parent->getSample(chan[i].dacSample);
|
||||
if (s->samples<=0) {
|
||||
if (s->samples<=0 || chan[i].dacPos>=s->samples) {
|
||||
chan[i].dacSample=-1;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -88,17 +88,15 @@ void DivPlatformPCE::acquire(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
// PCE part
|
||||
cycles=0;
|
||||
while (!writes.empty() && cycles<24) {
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
pce->Write(cycles,w.addr,w.val);
|
||||
pce->Write(0,w.addr,w.val);
|
||||
regPool[w.addr&0x0f]=w.val;
|
||||
//cycles+=2;
|
||||
writes.pop();
|
||||
}
|
||||
memset(tempL,0,24*sizeof(int));
|
||||
memset(tempR,0,24*sizeof(int));
|
||||
pce->Update(24);
|
||||
tempL[0]=0;
|
||||
tempR[0]=0;
|
||||
pce->Update(coreQuality);
|
||||
pce->ResetTS(0);
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
|
|
@ -206,7 +204,11 @@ void DivPlatformPCE::tick(bool sysTick) {
|
|||
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
|
||||
if (chan[i].furnaceDac && chan[i].pcm) {
|
||||
if (chan[i].active && chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
|
||||
chan[i].dacPos=0;
|
||||
if (chan[i].setPos) {
|
||||
chan[i].setPos=false;
|
||||
} else {
|
||||
chan[i].dacPos=0;
|
||||
}
|
||||
chan[i].dacPeriod=0;
|
||||
chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].vol));
|
||||
addWrite(0xffff0000+(i<<8),chan[i].dacSample);
|
||||
|
|
@ -304,7 +306,11 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample);
|
||||
}
|
||||
}
|
||||
chan[c.chan].dacPos=0;
|
||||
if (chan[c.chan].setPos) {
|
||||
chan[c.chan].setPos=false;
|
||||
} else {
|
||||
chan[c.chan].dacPos=0;
|
||||
}
|
||||
chan[c.chan].dacPeriod=0;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
|
|
@ -333,7 +339,11 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample);
|
||||
}
|
||||
chan[c.chan].dacPos=0;
|
||||
if (chan[c.chan].setPos) {
|
||||
chan[c.chan].setPos=false;
|
||||
} else {
|
||||
chan[c.chan].dacPos=0;
|
||||
}
|
||||
chan[c.chan].dacPeriod=0;
|
||||
chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate;
|
||||
if (dumpWrites) {
|
||||
|
|
@ -459,6 +469,10 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].dacPos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4);
|
||||
chWrite(c.chan,0x05,isMuted[c.chan]?0:chan[c.chan].pan);
|
||||
|
|
@ -561,6 +575,11 @@ int DivPlatformPCE::mapVelocity(int ch, float vel) {
|
|||
return round(31.0*pow(vel,0.22));
|
||||
}
|
||||
|
||||
float DivPlatformPCE::getGain(int ch, int vol) {
|
||||
if (vol==0) return 0;
|
||||
return 1.0/pow(10.0,(float)(31-vol)*3.0/20.0);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformPCE::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
|
@ -585,7 +604,6 @@ void DivPlatformPCE::reset() {
|
|||
lastPan=0xff;
|
||||
memset(tempL,0,32*sizeof(int));
|
||||
memset(tempR,0,32*sizeof(int));
|
||||
cycles=0;
|
||||
curChan=-1;
|
||||
sampleBank=0;
|
||||
lfoMode=0;
|
||||
|
|
@ -599,7 +617,6 @@ void DivPlatformPCE::reset() {
|
|||
for (int i=0; i<6; i++) {
|
||||
chWrite(i,0x05,isMuted[i]?0:chan[i].pan);
|
||||
}
|
||||
delay=500;
|
||||
}
|
||||
|
||||
int DivPlatformPCE::getOutputCount() {
|
||||
|
|
@ -633,7 +650,7 @@ void DivPlatformPCE::setFlags(const DivConfig& flags) {
|
|||
}
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
antiClickEnabled=!flags.getBool("noAntiClick",false);
|
||||
rate=chipClock/12;
|
||||
rate=chipClock/(coreQuality>>1);
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
|
@ -653,6 +670,32 @@ void DivPlatformPCE::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformPCE::setCoreQuality(unsigned char q) {
|
||||
switch (q) {
|
||||
case 0:
|
||||
coreQuality=192;
|
||||
break;
|
||||
case 1:
|
||||
coreQuality=96;
|
||||
break;
|
||||
case 2:
|
||||
coreQuality=48;
|
||||
break;
|
||||
case 3:
|
||||
coreQuality=24;
|
||||
break;
|
||||
case 4:
|
||||
coreQuality=6;
|
||||
break;
|
||||
case 5:
|
||||
coreQuality=2;
|
||||
break;
|
||||
default:
|
||||
coreQuality=24;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
unsigned int dacPos;
|
||||
int dacSample;
|
||||
unsigned char pan;
|
||||
bool noise, pcm, furnaceDac, deferredWaveUpdate;
|
||||
bool noise, pcm, furnaceDac, deferredWaveUpdate, setPos;
|
||||
signed short wave;
|
||||
int macroVolMul, noiseSeek;
|
||||
DivWaveSynth ws;
|
||||
|
|
@ -50,6 +50,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
pcm(false),
|
||||
furnaceDac(false),
|
||||
deferredWaveUpdate(false),
|
||||
setPos(false),
|
||||
wave(-1),
|
||||
macroVolMul(31),
|
||||
noiseSeek(0) {}
|
||||
|
|
@ -68,10 +69,11 @@ class DivPlatformPCE: public DivDispatch {
|
|||
FixedQueue<QueuedWrite,512> writes;
|
||||
unsigned char lastPan;
|
||||
|
||||
int cycles, curChan, delay;
|
||||
int curChan;
|
||||
int tempL[32];
|
||||
int tempR[32];
|
||||
unsigned char sampleBank, lfoMode, lfoSpeed;
|
||||
int coreQuality;
|
||||
PCE_PSG* pce;
|
||||
unsigned char regPool[128];
|
||||
void updateWave(int ch);
|
||||
|
|
@ -88,6 +90,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
int mapVelocity(int ch, float vel);
|
||||
float getGain(int ch, int vol);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
|
|
@ -96,6 +99,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
void muteChannel(int ch, bool mute);
|
||||
int getOutputCount();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setCoreQuality(unsigned char q);
|
||||
void setFlags(const DivConfig& flags);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
|
|
|
|||
|
|
@ -227,7 +227,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
|
|||
if (isMuted) {
|
||||
output=0;
|
||||
} else {
|
||||
output=output*chan[0].vol*chan[0].envVol/16384;
|
||||
output=((output*MIN(volMax,chan[0].vol)*MIN(chan[0].envVol,64))>>6)/volMax;
|
||||
}
|
||||
oscBuf->data[oscBuf->needle++]=((output>>depthScale)<<depthScale)>>1;
|
||||
if (outStereo) {
|
||||
|
|
@ -451,7 +451,7 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) {
|
|||
chan[0].setPos=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
return volMax;
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
|
|
@ -543,6 +543,8 @@ void DivPlatformPCMDAC::setFlags(const DivConfig& flags) {
|
|||
outStereo=flags.getBool("stereo",true);
|
||||
interp=flags.getInt("interpolation",0);
|
||||
oscBuf->rate=rate;
|
||||
volMax=flags.getInt("volMax",255);
|
||||
if (volMax<1) volMax=1;
|
||||
}
|
||||
|
||||
int DivPlatformPCMDAC::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ class DivPlatformPCMDAC: public DivDispatch {
|
|||
// - 2: cubic spline
|
||||
// - 3: sinc
|
||||
int interp;
|
||||
int volMax;
|
||||
bool outStereo;
|
||||
|
||||
friend void putDispatchChip(void*,int);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ void DivPlatformPowerNoise::acquire(short** buf, size_t len) {
|
|||
short left, right;
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
pwrnoise_step(&pn,32,&left,&right);
|
||||
pwrnoise_step(&pn,coreQuality,&left,&right);
|
||||
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=mapAmp((pn.n1.out_latch&0xf)+(pn.n1.out_latch>>4));
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=mapAmp((pn.n2.out_latch&0xf)+(pn.n2.out_latch>>4));
|
||||
|
|
@ -307,6 +307,7 @@ int DivPlatformPowerNoise::dispatch(DivCommand c) {
|
|||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
chWrite(c.chan,0x06,isMuted[c.chan]?0:volPan(chan[c.chan].outVol,chan[c.chan].pan));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -464,7 +465,7 @@ void DivPlatformPowerNoise::reset() {
|
|||
rWrite(0,0x87);
|
||||
// set per-channel panning
|
||||
for (int i=0; i<4; i++) {
|
||||
chWrite(i,0x06,volPan(chan[i].outVol,chan[i].pan));
|
||||
chWrite(i,0x06,isMuted[i]?0:volPan(chan[i].outVol,chan[i].pan));
|
||||
}
|
||||
// set default params so we have sound
|
||||
// noise
|
||||
|
|
@ -502,7 +503,7 @@ void DivPlatformPowerNoise::setFlags(const DivConfig& flags) {
|
|||
chipClock=16000000;
|
||||
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/32;
|
||||
rate=chipClock/coreQuality;
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
|
|
@ -517,6 +518,32 @@ void DivPlatformPowerNoise::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformPowerNoise::setCoreQuality(unsigned char q) {
|
||||
switch (q) {
|
||||
case 0:
|
||||
coreQuality=256;
|
||||
break;
|
||||
case 1:
|
||||
coreQuality=128;
|
||||
break;
|
||||
case 2:
|
||||
coreQuality=64;
|
||||
break;
|
||||
case 3:
|
||||
coreQuality=32;
|
||||
break;
|
||||
case 4:
|
||||
coreQuality=8;
|
||||
break;
|
||||
case 5:
|
||||
coreQuality=1;
|
||||
break;
|
||||
default:
|
||||
coreQuality=32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformPowerNoise::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ class DivPlatformPowerNoise: public DivDispatch {
|
|||
bool isMuted[4];
|
||||
unsigned char regPool[32];
|
||||
|
||||
int coreQuality;
|
||||
power_noise_t pn;
|
||||
|
||||
friend void putDispatchChip(void*,int);
|
||||
|
|
@ -100,6 +101,7 @@ class DivPlatformPowerNoise: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
void setCoreQuality(unsigned char q);
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
~DivPlatformPowerNoise();
|
||||
|
|
|
|||
|
|
@ -391,17 +391,23 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,440.0,4096.0);
|
||||
if (chan[i].freq>0xefff) chan[i].freq=0xefff;
|
||||
if (chan[i].keyOn) {
|
||||
if (chan[i].setPos) {
|
||||
chan[i].setPos=false;
|
||||
} else {
|
||||
chan[i].audPos=0;
|
||||
}
|
||||
|
||||
if (i<16) {
|
||||
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
|
||||
rWrite(q1_reg_map[Q1V_END][i], qsound_end);
|
||||
rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop);
|
||||
rWrite(q1_reg_map[Q1V_START][i], qsound_addr);
|
||||
rWrite(q1_reg_map[Q1V_START][i], qsound_addr+chan[i].audPos);
|
||||
rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000);
|
||||
} else {
|
||||
rWrite(Q1A_KEYON+(i-16),0);
|
||||
rWrite(q1a_bank_map[i-16], qsound_bank);
|
||||
rWrite(q1a_end_map[i-16], qsound_end);
|
||||
rWrite(q1a_start_map[i-16], qsound_addr);
|
||||
rWrite(q1a_start_map[i-16], qsound_addr+chan[i].audPos);
|
||||
rWrite(Q1A_KEYON+(i-16),1);
|
||||
}
|
||||
//logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop);
|
||||
|
|
@ -580,6 +586,13 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].audPos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ class DivPlatformQSound: public DivDispatch {
|
|||
int sample, wave;
|
||||
int panning;
|
||||
int echo;
|
||||
bool useWave, surround, isNewQSound;
|
||||
int audPos;
|
||||
bool useWave, surround, isNewQSound, setPos;
|
||||
Channel():
|
||||
SharedChannel<int>(255),
|
||||
resVol(4095),
|
||||
|
|
@ -37,9 +38,11 @@ class DivPlatformQSound: public DivDispatch {
|
|||
wave(-1),
|
||||
panning(0x10),
|
||||
echo(0),
|
||||
audPos(0),
|
||||
useWave(false),
|
||||
surround(true),
|
||||
isNewQSound(false) {}
|
||||
isNewQSound(false),
|
||||
setPos(false) {}
|
||||
};
|
||||
Channel chan[19];
|
||||
DivDispatchOscBuffer* oscBuf[19];
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ void DivPlatformSAA1099::setFlags(const DivConfig& flags) {
|
|||
chipClock=8000000;
|
||||
}
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/32;
|
||||
rate=chipClock/coreQuality;
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
|
|
@ -473,6 +473,32 @@ void DivPlatformSAA1099::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformSAA1099::setCoreQuality(unsigned char q) {
|
||||
switch (q) {
|
||||
case 0:
|
||||
coreQuality=256;
|
||||
break;
|
||||
case 1:
|
||||
coreQuality=128;
|
||||
break;
|
||||
case 2:
|
||||
coreQuality=64;
|
||||
break;
|
||||
case 3:
|
||||
coreQuality=32;
|
||||
break;
|
||||
case 4:
|
||||
coreQuality=8;
|
||||
break;
|
||||
case 5:
|
||||
coreQuality=1;
|
||||
break;
|
||||
default:
|
||||
coreQuality=32;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformSAA1099::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ class DivPlatformSAA1099: public DivDispatch {
|
|||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
FixedQueue<QueuedWrite,256> writes;
|
||||
int coreQuality;
|
||||
CSAASound* saa_saaSound;
|
||||
unsigned char regPool[32];
|
||||
unsigned char lastBusy;
|
||||
|
|
@ -96,6 +97,7 @@ class DivPlatformSAA1099: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
void setCoreQuality(unsigned char q);
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ const char** DivPlatformSCC::getRegisterSheet() {
|
|||
|
||||
void DivPlatformSCC::acquire(short** buf, size_t len) {
|
||||
for (size_t h=0; h<len; h++) {
|
||||
scc->tick(16);
|
||||
scc->tick(coreQuality);
|
||||
short out=(short)scc->out()<<5;
|
||||
buf[0][h]=out;
|
||||
|
||||
|
|
@ -383,12 +383,38 @@ void DivPlatformSCC::setFlags(const DivConfig& flags) {
|
|||
break;
|
||||
}
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/8;
|
||||
rate=chipClock/(coreQuality>>1);
|
||||
for (int i=0; i<5; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSCC::setCoreQuality(unsigned char q) {
|
||||
switch (q) {
|
||||
case 0:
|
||||
coreQuality=128;
|
||||
break;
|
||||
case 1:
|
||||
coreQuality=64;
|
||||
break;
|
||||
case 2:
|
||||
coreQuality=32;
|
||||
break;
|
||||
case 3:
|
||||
coreQuality=16;
|
||||
break;
|
||||
case 4:
|
||||
coreQuality=8;
|
||||
break;
|
||||
case 5:
|
||||
coreQuality=2;
|
||||
break;
|
||||
default:
|
||||
coreQuality=16;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ class DivPlatformSCC: public DivDispatch {
|
|||
unsigned char writeOscBuf;
|
||||
int lastUpdated34;
|
||||
|
||||
int coreQuality;
|
||||
scc_core* scc;
|
||||
bool isPlus;
|
||||
unsigned char regBase;
|
||||
|
|
@ -68,6 +69,7 @@ class DivPlatformSCC: public DivDispatch {
|
|||
const char** getRegisterSheet();
|
||||
void setFlags(const DivConfig& flags);
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void setCoreQuality(unsigned char q);
|
||||
void setChipModel(bool isPlus);
|
||||
void quit();
|
||||
~DivPlatformSCC();
|
||||
|
|
|
|||
|
|
@ -128,41 +128,47 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
|
|||
if (chan[i].keyOn || chan[i].keyOff) {
|
||||
if (chan[i].keyOn && !chan[i].keyOff) {
|
||||
rWrite(0x86+(i<<3),3);
|
||||
chan[i].pcm.pos=0;
|
||||
if (chan[i].setPos) {
|
||||
chan[i].setPos=false;
|
||||
} else {
|
||||
chan[i].pcm.pos=0;
|
||||
}
|
||||
if (chan[i].furnacePCM) {
|
||||
DivSample* s=parent->getSample(chan[i].pcm.sample);
|
||||
int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT));
|
||||
if (actualLength>0xfeff) actualLength=0xfeff;
|
||||
rWrite(0x86+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
|
||||
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
|
||||
int actualPos=sampleOffSegaPCM[chan[i].pcm.sample]+chan[i].pcm.pos;
|
||||
rWrite(0x86+(i<<3),3+((actualPos>>16)<<3));
|
||||
rWrite(0x84+(i<<3),(actualPos)&0xff);
|
||||
rWrite(0x85+(i<<3),(actualPos>>8)&0xff);
|
||||
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
|
||||
if (!s->isLoopable()) {
|
||||
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
rWrite(0x86+(i<<3),2+((actualPos>>16)<<3));
|
||||
} else {
|
||||
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
|
||||
logV("sampleOff: %x loopPos: %x",sampleOffSegaPCM[chan[i].pcm.sample],loopPos);
|
||||
int loopPos=(actualPos&0xffff)+loopStart;
|
||||
logV("sampleOff: %x loopPos: %x",actualPos,loopPos);
|
||||
rWrite(4+(i<<3),loopPos&0xff);
|
||||
rWrite(5+(i<<3),(loopPos>>8)&0xff);
|
||||
rWrite(0x86+(i<<3),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
rWrite(0x86+(i<<3),((actualPos>>16)<<3));
|
||||
}
|
||||
} else {
|
||||
DivSample* s=parent->getSample(chan[i].pcm.sample);
|
||||
int loopStart=s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
int actualLength=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT));
|
||||
if (actualLength>0xfeff) actualLength=0xfeff;
|
||||
rWrite(0x86+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
|
||||
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
|
||||
int actualPos=sampleOffSegaPCM[chan[i].pcm.sample]+chan[i].pcm.pos;
|
||||
rWrite(0x86+(i<<3),3+((actualPos>>16)<<3));
|
||||
rWrite(0x84+(i<<3),(actualPos)&0xff);
|
||||
rWrite(0x85+(i<<3),(actualPos>>8)&0xff);
|
||||
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
|
||||
if (!s->isLoopable()) {
|
||||
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
rWrite(0x86+(i<<3),2+((actualPos>>16)<<3));
|
||||
} else {
|
||||
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
|
||||
int loopPos=(actualPos&0xffff)+loopStart;
|
||||
rWrite(4+(i<<3),loopPos&0xff);
|
||||
rWrite(5+(i<<3),(loopPos>>8)&0xff);
|
||||
rWrite(0x86+(i<<3),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
rWrite(0x86+(i<<3),((actualPos>>16)<<3));
|
||||
}
|
||||
rWrite(7+(i<<3),chan[i].pcm.freq);
|
||||
}
|
||||
|
|
@ -335,6 +341,13 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
chan[c.chan].pcm.pos=c.value;
|
||||
chan[c.chan].setPos=true;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
class DivPlatformSegaPCM: public DivDispatch {
|
||||
protected:
|
||||
struct Channel: public SharedChannel<int> {
|
||||
bool furnacePCM, isNewSegaPCM;
|
||||
bool furnacePCM, isNewSegaPCM, setPos;
|
||||
unsigned char chVolL, chVolR;
|
||||
unsigned char chPanL, chPanR;
|
||||
int macroVolMul;
|
||||
|
|
@ -44,6 +44,7 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
SharedChannel<int>(127),
|
||||
furnacePCM(false),
|
||||
isNewSegaPCM(false),
|
||||
setPos(false),
|
||||
chVolL(127),
|
||||
chVolR(127),
|
||||
chPanL(127),
|
||||
|
|
|
|||
719
src/engine/platform/sid2.cpp
Normal file
719
src/engine/platform/sid2.cpp
Normal file
|
|
@ -0,0 +1,719 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
// thanks LTVA
|
||||
|
||||
#include "sid2.h"
|
||||
#include "../engine.h"
|
||||
#include "IconsFontAwesome4.h"
|
||||
#include <math.h>
|
||||
#include "../../ta-log.h"
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#define CHIP_FREQBASE 524288
|
||||
|
||||
const char* regCheatSheetSID2[]={
|
||||
"FreqL0", "00",
|
||||
"FreqH0", "01",
|
||||
"PWL0", "02",
|
||||
"PWH0Vol", "03",
|
||||
"Control0", "04",
|
||||
"AtkDcy0", "05",
|
||||
"StnRis0", "06",
|
||||
"FreqL1", "07",
|
||||
"FreqH1", "08",
|
||||
"PWL1", "09",
|
||||
"PWH1Vol", "0A",
|
||||
"Control1", "0B",
|
||||
"AtkDcy1", "0C",
|
||||
"StnRis1", "0D",
|
||||
"FreqL2", "0E",
|
||||
"FreqH2", "0F",
|
||||
"PWL2", "10",
|
||||
"PWH2Vol", "11",
|
||||
"Control2", "12",
|
||||
"AtkDcy2", "13",
|
||||
"StnRis2", "14",
|
||||
|
||||
"FCL0Ctrl", "15",
|
||||
"FCH0", "16",
|
||||
"FilterRes0", "17",
|
||||
|
||||
"FCL1Ctrl", "18",
|
||||
"FCH1", "19",
|
||||
"FilterRes1", "1A",
|
||||
|
||||
"FCL2Ctrl", "1B",
|
||||
"FCH2", "1C",
|
||||
"FilterRes2", "1D",
|
||||
|
||||
"NoiModeFrMSB01", "1E",
|
||||
"WaveMixModeFrMSB2", "1F",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformSID2::getRegisterSheet() {
|
||||
return regCheatSheetSID2;
|
||||
}
|
||||
|
||||
void DivPlatformSID2::acquire(short** buf, size_t len)
|
||||
{
|
||||
for (size_t i=0; i<len; i++)
|
||||
{
|
||||
if (!writes.empty())
|
||||
{
|
||||
QueuedWrite w=writes.front();
|
||||
sid2->write(w.addr,w.val);
|
||||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
sid2->clock();
|
||||
buf[0][i]=sid2->output();
|
||||
if (++writeOscBuf>=16)
|
||||
{
|
||||
writeOscBuf=0;
|
||||
|
||||
for(int j = 0; j < 3; j++)
|
||||
{
|
||||
oscBuf[j]->data[oscBuf[j]->needle++] = sid2->chan_out[j] / 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSID2::updateFilter(int channel)
|
||||
{
|
||||
rWrite(0x15 + 3 * channel,(chan[channel].filtCut&15) | ((chan[channel].filtControl & 7) << 4) | (chan[channel].filter << 7));
|
||||
rWrite(0x16 + 3 * channel,(chan[channel].filtCut >> 4));
|
||||
rWrite(0x17 + 3 * channel,chan[channel].filtRes);
|
||||
}
|
||||
|
||||
void DivPlatformSID2::tick(bool sysTick) {
|
||||
for (int _i=0; _i<3; _i++) {
|
||||
int i=chanOrder[_i];
|
||||
|
||||
bool willUpdateFilter = false;
|
||||
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
|
||||
rWrite(i*7+3,(chan[i].duty>>8) | (chan[i].outVol << 4));
|
||||
}
|
||||
|
||||
if (NEW_ARP_STRAT) {
|
||||
chan[i].handleArp();
|
||||
} else if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SID2);
|
||||
if (ins->c64.dutyIsAbs) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
} else {
|
||||
chan[i].duty-=chan[i].std.duty.val;
|
||||
}
|
||||
chan[i].duty&=4095;
|
||||
rWrite(i*7+2,chan[i].duty&0xff);
|
||||
rWrite(i*7+3,(chan[i].duty>>8) | (chan[i].outVol << 4));
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
chan[i].wave=chan[i].std.wave.val;
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|0|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active && chan[i].gate));
|
||||
|
||||
chan[i].freqChanged=true; //to update freq (if only noise was enabled/disabled)
|
||||
}
|
||||
if (chan[i].std.fms.had) {
|
||||
chan[i].noise_mode=chan[i].std.fms.val;
|
||||
rWrite(0x1e, (chan[0].noise_mode) | (chan[1].noise_mode << 2) | (chan[2].noise_mode << 4) | ((chan[0].freq >> 16) << 6) | ((chan[1].freq >> 16) << 7));
|
||||
|
||||
chan[i].freqChanged=true; //to update freq (if only noise was enabled and periodic noise mode is set)
|
||||
}
|
||||
if (chan[i].std.ams.had) {
|
||||
chan[i].mix_mode=chan[i].std.ams.val;
|
||||
rWrite(0x1f, (chan[0].mix_mode) | (chan[1].mix_mode << 2) | (chan[2].mix_mode << 4) | ((chan[2].freq >> 16) << 6));
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-65535,65535);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.alg.had) { // new cutoff macro
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SID2);
|
||||
if (ins->c64.filterIsAbs) {
|
||||
chan[i].filtCut=MIN(4095,chan[i].std.alg.val);
|
||||
} else {
|
||||
chan[i].filtCut+=chan[i].std.alg.val;
|
||||
if (chan[i].filtCut > 4095) chan[i].filtCut = 4095;
|
||||
if (chan[i].filtCut<0) chan[i].filtCut=0;
|
||||
}
|
||||
willUpdateFilter=true;
|
||||
}
|
||||
if (chan[i].std.ex1.had) {
|
||||
chan[i].filtControl=chan[i].std.ex1.val&15;
|
||||
willUpdateFilter=true;
|
||||
}
|
||||
if (chan[i].std.ex2.had) {
|
||||
chan[i].filtRes=chan[i].std.ex2.val&255;
|
||||
willUpdateFilter=true;
|
||||
}
|
||||
if (chan[i].std.ex3.had) {
|
||||
chan[i].filter=(chan[i].std.ex3.val&1);
|
||||
willUpdateFilter=true;
|
||||
}
|
||||
if (chan[i].std.ex4.had) {
|
||||
chan[i].gate=chan[i].std.ex4.val&1;
|
||||
chan[i].sync=chan[i].std.ex4.val&2;
|
||||
chan[i].ring=chan[i].std.ex4.val&4;
|
||||
chan[i].freqChanged=true;
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|0|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active && chan[i].gate));
|
||||
}
|
||||
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
chan[i].test=(chan[i].std.phaseReset.val&1);
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test << 3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active && chan[i].gate));
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|0|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active && chan[i].gate));
|
||||
chan[i].test = false;
|
||||
}
|
||||
|
||||
if (chan[i].std.ex5.had) {
|
||||
chan[i].attack=chan[i].std.ex5.val&15;
|
||||
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));
|
||||
}
|
||||
|
||||
if (chan[i].std.ex6.had) {
|
||||
chan[i].decay=chan[i].std.ex6.val&15;
|
||||
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));
|
||||
}
|
||||
|
||||
if (chan[i].std.ex7.had) {
|
||||
chan[i].sustain=chan[i].std.ex7.val&15;
|
||||
rWrite(i*7+6,(chan[i].sustain<<4)|(chan[i].release));
|
||||
}
|
||||
|
||||
if (chan[i].std.ex8.had) {
|
||||
chan[i].release=chan[i].std.ex8.val&15;
|
||||
rWrite(i*7+6,(chan[i].sustain<<4)|(chan[i].release));
|
||||
}
|
||||
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,8,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>0x1ffff) chan[i].freq=0x1ffff;
|
||||
if (chan[i].keyOn)
|
||||
{
|
||||
if(!chan[i].resetMask)
|
||||
{
|
||||
chan[i].gate = true;
|
||||
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|0|(chan[i].ring<<2)|(chan[i].sync<<1)|(0));
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|0|(chan[i].ring<<2)|(chan[i].sync<<1)|(chan[i].gate?1:0));
|
||||
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));
|
||||
rWrite(i*7+6,(chan[i].sustain<<4)|(chan[i].release));
|
||||
}
|
||||
|
||||
rWrite(i*7+3, (chan[i].duty>>8) | (isMuted[i] ? 0 : (chan[i].outVol << 4))); //set volume
|
||||
|
||||
rWrite(0x1e, (chan[0].noise_mode) | (chan[1].noise_mode << 2) | (chan[2].noise_mode << 4) | ((chan[0].freq >> 16) << 6) | ((chan[1].freq >> 16) << 7));
|
||||
rWrite(0x1f, (chan[0].mix_mode) | (chan[1].mix_mode << 2) | (chan[2].mix_mode << 4) | ((chan[2].freq >> 16) << 6));
|
||||
}
|
||||
if (chan[i].keyOff)
|
||||
{
|
||||
chan[i].gate = false;
|
||||
rWrite(i*7+5,(chan[i].attack<<4)|(chan[i].decay));
|
||||
rWrite(i*7+6,(chan[i].sustain<<4)|(chan[i].release));
|
||||
rWrite(i*7+4,(chan[i].wave<<4)|0|(chan[i].ring<<2)|(chan[i].sync<<1)|0);
|
||||
}
|
||||
|
||||
if(chan[i].wave == 0x8) //if we have noise (noise only, since tone frequency would be wrong)
|
||||
{
|
||||
if(chan[i].noise_mode == 1) //1st short noise
|
||||
{
|
||||
chan[i].freq = (int)((double)chan[i].freq * 523.25 / 349.0); //these numbers and later determined by spectrum analyzer peak of looped noise signal at known frequency (frequency known for tone that would play if tone wave was enabled)
|
||||
}
|
||||
|
||||
if(chan[i].noise_mode == 2) //2nd short noise
|
||||
{
|
||||
chan[i].freq = (int)((double)chan[i].freq * 523.25 / 270.0);
|
||||
}
|
||||
|
||||
if(chan[i].noise_mode == 3) //3rd short noise
|
||||
{
|
||||
chan[i].freq = (int)((double)chan[i].freq * 523.25 / 133.0);
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>0x1ffff) chan[i].freq=0x1ffff;
|
||||
|
||||
rWrite(i*7,chan[i].freq&0xff);
|
||||
rWrite(i*7+1,chan[i].freq>>8);
|
||||
rWrite(0x1e, (chan[0].noise_mode) | (chan[1].noise_mode << 2) | (chan[2].noise_mode << 4) | ((chan[0].freq >> 16) << 6) | ((chan[1].freq >> 16) << 7));
|
||||
rWrite(0x1f, (chan[0].mix_mode) | (chan[1].mix_mode << 2) | (chan[2].mix_mode << 4) | ((chan[2].freq >> 16) << 6));
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
|
||||
if (willUpdateFilter) updateFilter(i);
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformSID2::dispatch(DivCommand c) {
|
||||
if (c.chan>2) return 0;
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SID2);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].test=false;
|
||||
|
||||
if (chan[c.chan].insChanged || chan[c.chan].resetDuty || ins->std.waveMacro.len>0) {
|
||||
chan[c.chan].duty=ins->c64.duty;
|
||||
rWrite(c.chan*7+2,chan[c.chan].duty&0xff);
|
||||
rWrite(c.chan*7+3,(chan[c.chan].duty>>8) | (chan[c.chan].outVol << 4));
|
||||
}
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].wave=(ins->c64.noiseOn<<3)|(ins->c64.pulseOn<<2)|(ins->c64.sawOn<<1)|(int)(ins->c64.triOn);
|
||||
chan[c.chan].attack=ins->c64.a;
|
||||
chan[c.chan].decay=(ins->c64.s==15)?0:ins->c64.d;
|
||||
chan[c.chan].sustain=ins->c64.s;
|
||||
chan[c.chan].release=ins->c64.r;
|
||||
chan[c.chan].ring=ins->c64.ringMod;
|
||||
chan[c.chan].sync=ins->c64.oscSync;
|
||||
|
||||
chan[c.chan].noise_mode = ins->sid2.noiseMode;
|
||||
chan[c.chan].mix_mode = ins->sid2.mixMode;
|
||||
}
|
||||
if (chan[c.chan].insChanged || chan[c.chan].resetFilter) {
|
||||
chan[c.chan].filter=ins->c64.toFilter;
|
||||
if (ins->c64.initFilter) {
|
||||
chan[c.chan].filtCut=ins->c64.cut;
|
||||
chan[c.chan].filtRes=ins->c64.res;
|
||||
chan[c.chan].filtControl=(int)(ins->c64.lp)|(ins->c64.bp<<1)|(ins->c64.hp<<2);
|
||||
}
|
||||
updateFilter(c.chan);
|
||||
}
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].insChanged=false;
|
||||
}
|
||||
if (keyPriority) {
|
||||
if (chanOrder[1]==c.chan) {
|
||||
chanOrder[1]=chanOrder[2];
|
||||
chanOrder[2]=c.chan;
|
||||
} else if (chanOrder[0]==c.chan) {
|
||||
chanOrder[0]=chanOrder[1];
|
||||
chanOrder[1]=chanOrder[2];
|
||||
chanOrder[2]=c.chan;
|
||||
}
|
||||
}
|
||||
chan[c.chan].macroInit(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
//chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].insChanged=true;
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
chan[c.chan].vol=chan[c.chan].outVol;
|
||||
rWrite(c.chan*7+3,(chan[c.chan].duty>>8) | (chan[c.chan].vol << 4));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.vol.has) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_C64_FINE_DUTY:
|
||||
chan[c.chan].duty=c.value;
|
||||
rWrite(c.chan*7+2,chan[c.chan].duty&0xff);
|
||||
rWrite(c.chan*7+3,(chan[c.chan].duty>>8) | (chan[c.chan].outVol << 4));
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
chan[c.chan].wave=c.value;
|
||||
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|0|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active && chan[c.chan].gate));
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta || parent->song.preNoteNoEffect) {
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SID2));
|
||||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 15;
|
||||
break;
|
||||
case DIV_CMD_C64_FINE_CUTOFF:
|
||||
chan[c.chan].filtCut=c.value;
|
||||
updateFilter(c.chan);
|
||||
break;
|
||||
case DIV_CMD_C64_RESONANCE:
|
||||
if (c.value>255) c.value=255;
|
||||
chan[c.chan].filtRes=c.value;
|
||||
updateFilter(c.chan);
|
||||
break;
|
||||
case DIV_CMD_C64_FILTER_MODE:
|
||||
chan[c.chan].filtControl=c.value&7;
|
||||
updateFilter(c.chan);
|
||||
break;
|
||||
case DIV_CMD_C64_RESET_MASK:
|
||||
chan[c.chan].resetMask=c.value;
|
||||
break;
|
||||
case DIV_CMD_C64_FILTER_RESET:
|
||||
if (c.value&15) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SID2);
|
||||
if (ins->c64.initFilter) {
|
||||
chan[c.chan].filtCut=ins->c64.cut;
|
||||
updateFilter(c.chan);
|
||||
}
|
||||
}
|
||||
chan[c.chan].resetFilter=c.value>>4;
|
||||
break;
|
||||
case DIV_CMD_C64_DUTY_RESET:
|
||||
if (c.value&15) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SID2);
|
||||
chan[c.chan].duty=ins->c64.duty;
|
||||
rWrite(c.chan*7+2,chan[c.chan].duty&0xff);
|
||||
rWrite(c.chan*7+3,(chan[c.chan].duty>>8) | (chan[c.chan].outVol << 4));
|
||||
}
|
||||
chan[c.chan].resetDuty=c.value>>4;
|
||||
break;
|
||||
case DIV_CMD_C64_EXTENDED:
|
||||
switch (c.value>>4) {
|
||||
case 0:
|
||||
chan[c.chan].attack=c.value&15;
|
||||
rWrite(c.chan*7+5,(chan[c.chan].attack<<4)|(chan[c.chan].decay));
|
||||
break;
|
||||
case 1:
|
||||
chan[c.chan].decay=c.value&15;
|
||||
rWrite(c.chan*7+5,(chan[c.chan].attack<<4)|(chan[c.chan].decay));
|
||||
break;
|
||||
case 2:
|
||||
chan[c.chan].sustain=c.value&15;
|
||||
rWrite(c.chan*7+6,(chan[c.chan].sustain<<4)|(chan[c.chan].release));
|
||||
break;
|
||||
case 3:
|
||||
chan[c.chan].release=c.value&15;
|
||||
rWrite(c.chan*7+6,(chan[c.chan].sustain<<4)|(chan[c.chan].release));
|
||||
break;
|
||||
case 4:
|
||||
chan[c.chan].ring=c.value;
|
||||
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|0|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active && chan[c.chan].gate));
|
||||
break;
|
||||
case 5:
|
||||
chan[c.chan].sync=c.value;
|
||||
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|0|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active && chan[c.chan].gate));
|
||||
break;
|
||||
case 6:
|
||||
chan[c.chan].filtControl = c.value;
|
||||
updateFilter(c.chan);
|
||||
break;
|
||||
case 7:
|
||||
chan[c.chan].mix_mode=(c.value & 3);
|
||||
rWrite(0x1f, (chan[0].mix_mode) | (chan[1].mix_mode << 2) | (chan[2].mix_mode << 4) | ((chan[2].freq >> 16) << 6));
|
||||
break;
|
||||
case 8:
|
||||
chan[c.chan].noise_mode=(c.value & 3);
|
||||
chan[c.chan].freqChanged = true;
|
||||
rWrite(0x1e, (chan[0].noise_mode) | (chan[1].noise_mode << 2) | (chan[2].noise_mode << 4) | ((chan[0].freq >> 16) << 6) | ((chan[1].freq >> 16) << 7));
|
||||
break;
|
||||
case 9: //phase reset
|
||||
chan[c.chan].test=true;
|
||||
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|(chan[c.chan].test << 3)|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active && chan[c.chan].gate));
|
||||
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|0|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active && chan[c.chan].gate));
|
||||
chan[c.chan].test = false;
|
||||
break;
|
||||
case 0xa: //envelope on/off
|
||||
chan[c.chan].gate=(c.value & 1);
|
||||
rWrite(c.chan*7+4,(chan[c.chan].wave<<4)|0|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active && chan[c.chan].gate));
|
||||
break;
|
||||
case 0xb: //filter on/off
|
||||
chan[c.chan].filter=(c.value & 1);
|
||||
updateFilter(c.chan);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
break;
|
||||
case DIV_CMD_MACRO_ON:
|
||||
chan[c.chan].std.mask(c.value,false);
|
||||
break;
|
||||
case DIV_CMD_MACRO_RESTART:
|
||||
chan[c.chan].std.restart(c.value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformSID2::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
sid2->set_is_muted(ch,mute);
|
||||
}
|
||||
|
||||
void DivPlatformSID2::forceIns() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].insChanged=true;
|
||||
if (chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
updateFilter(i);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSID2::notifyInsChange(int ins) {
|
||||
for (int i=0; i<3; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
chan[i].insChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSID2::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformSID2::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivMacroInt* DivPlatformSID2::getChanMacroInt(int ch) {
|
||||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivChannelModeHints DivPlatformSID2::getModeHints(int ch) {
|
||||
DivChannelModeHints ret;
|
||||
ret.count=1;
|
||||
ret.hint[0]=ICON_FA_BELL_SLASH_O;
|
||||
ret.type[0]=0;
|
||||
if (ch == 2 && (chan[ch].filtControl & 8)) {
|
||||
ret.type[0] = 7;
|
||||
}
|
||||
else if (!chan[ch].gate) {
|
||||
ret.type[0]=4;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformSID2::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformSID2::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformSID2::getRegisterPoolSize() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
bool DivPlatformSID2::getDCOffRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformSID2::getWantPreNote() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformSID2::isVolGlobal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
float DivPlatformSID2::getPostAmp() {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
void DivPlatformSID2::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=DivPlatformSID2::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
fakeLow[i]=0;
|
||||
fakeBand[i]=0;
|
||||
chan[i].vol = 15;
|
||||
|
||||
chan[i].filtControl = 7;
|
||||
chan[i].filtRes = 0;
|
||||
chan[i].filtCut = 4095;
|
||||
|
||||
chan[i].noise_mode = 0;
|
||||
|
||||
rWrite(0x3 + 7 * i,0xf0);
|
||||
}
|
||||
|
||||
sid2->reset();
|
||||
memset(regPool,0,32);
|
||||
|
||||
chanOrder[0]=0;
|
||||
chanOrder[1]=1;
|
||||
chanOrder[2]=2;
|
||||
}
|
||||
|
||||
void DivPlatformSID2::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformSID2::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformSID2::setFlags(const DivConfig& flags) {
|
||||
switch (flags.getInt("clockSel",0)) {
|
||||
case 0x0: // NTSC C64
|
||||
chipClock=COLOR_NTSC*2.0/7.0;
|
||||
break;
|
||||
case 0x1: // PAL C64
|
||||
chipClock=COLOR_PAL*2.0/9.0;
|
||||
break;
|
||||
case 0x2: // SSI 2001
|
||||
default:
|
||||
chipClock=14318180.0/16.0;
|
||||
break;
|
||||
}
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate/16;
|
||||
}
|
||||
keyPriority=flags.getBool("keyPriority",true);
|
||||
|
||||
// init fake filter table
|
||||
// taken from dSID
|
||||
double cutRatio=-2.0*3.14*(12500.0/256.0)/(double)oscBuf[0]->rate;
|
||||
|
||||
for (int i=0; i<4095; i++)
|
||||
{
|
||||
double c=(double)i/16.0+0.2;
|
||||
c=1-exp(c*cutRatio);
|
||||
fakeCutTable[i]=c;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformSID2::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
needInitTables=true;
|
||||
writeOscBuf=0;
|
||||
|
||||
for (int i=0; i<3; i++)
|
||||
{
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
|
||||
sid2=new SID2;
|
||||
|
||||
sid2->set_chip_model(MOS8580_2);
|
||||
|
||||
setFlags(flags);
|
||||
|
||||
reset();
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
void DivPlatformSID2::quit() {
|
||||
for (int i=0; i<3; i++)
|
||||
{
|
||||
delete oscBuf[i];
|
||||
}
|
||||
if (sid2!=NULL) delete sid2;
|
||||
}
|
||||
|
||||
DivPlatformSID2::~DivPlatformSID2() {
|
||||
}
|
||||
123
src/engine/platform/sid2.h
Normal file
123
src/engine/platform/sid2.h
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _SID2_H
|
||||
#define _SID2_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include "../../fixedQueue.h"
|
||||
#include "sound/sid2/sid.h"
|
||||
|
||||
class DivPlatformSID2: public DivDispatch {
|
||||
struct Channel: public SharedChannel<signed char> {
|
||||
int prevFreq;
|
||||
unsigned char wave, attack, decay, sustain, release;
|
||||
short duty;
|
||||
bool filter;
|
||||
bool resetMask, resetFilter, resetDuty, gate, ring, sync, test;
|
||||
unsigned char vol;
|
||||
unsigned char filtControl, filtRes;
|
||||
unsigned char noise_mode;
|
||||
unsigned char mix_mode;
|
||||
int filtCut;
|
||||
bool do_pw_sweep_writes, do_cutoff_sweep_writes;
|
||||
Channel():
|
||||
SharedChannel<signed char>(15),
|
||||
prevFreq(0x1ffff),
|
||||
wave(0),
|
||||
attack(0),
|
||||
decay(0),
|
||||
sustain(0),
|
||||
release(0),
|
||||
duty(0),
|
||||
filter(false),
|
||||
resetMask(false),
|
||||
resetFilter(false),
|
||||
resetDuty(false),
|
||||
gate(true),
|
||||
ring(false),
|
||||
sync(false),
|
||||
test(false),
|
||||
vol(15),
|
||||
filtControl(0),
|
||||
filtRes(0),
|
||||
noise_mode(0),
|
||||
mix_mode(0),
|
||||
filtCut(0),
|
||||
do_pw_sweep_writes(false),
|
||||
do_cutoff_sweep_writes(false) {}
|
||||
};
|
||||
Channel chan[3];
|
||||
DivDispatchOscBuffer* oscBuf[3];
|
||||
bool isMuted[3];
|
||||
float fakeLow[3];
|
||||
float fakeBand[3];
|
||||
float fakeCutTable[4096];
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
unsigned char val;
|
||||
QueuedWrite(): addr(0), val(0) {}
|
||||
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
FixedQueue<QueuedWrite,128> writes;
|
||||
|
||||
unsigned char writeOscBuf;
|
||||
|
||||
bool keyPriority, needInitTables;
|
||||
unsigned char chanOrder[3];
|
||||
unsigned char testAD, testSR;
|
||||
|
||||
SID2* sid2;
|
||||
unsigned char regPool[32];
|
||||
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
void updateFilter(int channel);
|
||||
public:
|
||||
void acquire(short** buf, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setFlags(const DivConfig& flags);
|
||||
void notifyInsChange(int ins);
|
||||
bool getDCOffRequired();
|
||||
bool getWantPreNote();
|
||||
bool isVolGlobal();
|
||||
float getPostAmp();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivChannelModeHints getModeHints(int chan);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void setChipModel(bool is6581);
|
||||
void setCore(unsigned char which);
|
||||
void quit();
|
||||
~DivPlatformSID2();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -55,7 +55,7 @@ void DivPlatformSM8521::acquire(short** buf, size_t len) {
|
|||
writes.pop();
|
||||
}
|
||||
for (size_t h=0; h<len; h++) {
|
||||
sm8521_sound_tick(&sm8521,8);
|
||||
sm8521_sound_tick(&sm8521,coreQuality);
|
||||
buf[0][h]=sm8521.out<<6;
|
||||
for (int i=0; i<2; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=sm8521.sg[i].base.out<<7;
|
||||
|
|
@ -367,7 +367,7 @@ void DivPlatformSM8521::setFlags(const DivConfig& flags) {
|
|||
chipClock=11059200;
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
antiClickEnabled=!flags.getBool("noAntiClick",false);
|
||||
rate=chipClock/4/8; // CKIN -> fCLK(/2) -> Function blocks (/2)
|
||||
rate=chipClock/4/coreQuality; // CKIN -> fCLK(/2) -> Function blocks (/2)
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
|
@ -381,6 +381,32 @@ void DivPlatformSM8521::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformSM8521::setCoreQuality(unsigned char q) {
|
||||
switch (q) {
|
||||
case 0:
|
||||
coreQuality=64;
|
||||
break;
|
||||
case 1:
|
||||
coreQuality=32;
|
||||
break;
|
||||
case 2:
|
||||
coreQuality=16;
|
||||
break;
|
||||
case 3:
|
||||
coreQuality=8;
|
||||
break;
|
||||
case 4:
|
||||
coreQuality=4;
|
||||
break;
|
||||
case 5:
|
||||
coreQuality=1;
|
||||
break;
|
||||
default:
|
||||
coreQuality=8;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformSM8521::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue