Merge branch 'master' into sysmgrtooltip_syschaninfo

This commit is contained in:
Eknous 2024-08-18 22:03:29 +04:00 committed by GitHub
commit e50b3438f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
834 changed files with 780920 additions and 160607 deletions

View file

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

View file

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

View file

@ -0,0 +1,7 @@
#include <locale.h>
int main(int argc, char** argv) {
setlocale(LC_CTYPE,"");
//setlocale(LC_MESSAGES,"");
return 0;
}

View file

@ -1,6 +1,6 @@
#include <sys/io.h>
int main(int, char**) {
int main(int argc, char** argv) {
inb(0x61);
outb(0x00,0x61);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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","");

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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, &macros[index][type], Types[i], setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[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, &macros[sequenceIndex[k][j]][j], j, setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)j, true), &macros[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, &macros[sequenceIndex[k][Types[i]]][Types[i]], Types[i], setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[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, &macros[index][type], type, setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[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, &macros[index][type], type, setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[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, &macros[index][type], type, setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[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) {

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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];
}
}

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -62,6 +62,7 @@ class DivPlatformPCMDAC: public DivDispatch {
// - 2: cubic spline
// - 3: sinc
int interp;
int volMax;
bool outStereo;
friend void putDispatchChip(void*,int);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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