Merge branch 'master' into sample_banks

This commit is contained in:
tildearrow 2024-08-17 17:53:15 -05:00
commit e3e61c817c
183 changed files with 1384 additions and 777 deletions

View file

@ -3589,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();
@ -3916,6 +3922,9 @@ bool DivEngine::preInit(bool noSafeMode) {
// register systems
if (!systemsRegistered) registerSystems();
// register ROM exports
if (!romExportsRegistered) registerROMExports();
// TODO: re-enable with a better approach
// see issue #1581
/*

View file

@ -54,8 +54,8 @@ class DivWorkPool;
#define DIV_UNSTABLE
#define DIV_VERSION "Import Test"
#define DIV_ENGINE_VERSION 216
#define DIV_VERSION "dev217"
#define DIV_ENGINE_VERSION 217
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -465,6 +465,7 @@ class DivEngine {
bool midiIsDirectProgram;
bool lowLatency;
bool systemsRegistered;
bool romExportsRegistered;
bool hasLoadedSomething;
bool midiOutClock;
bool midiOutTime;
@ -518,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;
@ -635,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);
@ -665,6 +668,7 @@ class DivEngine {
// add every export method here
friend class DivROMExport;
friend class DivExportAmigaValidation;
friend class DivExportTiuna;
public:
DivSong song;
@ -895,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);
@ -1305,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);
@ -1372,6 +1382,7 @@ class DivEngine {
midiIsDirectProgram(false),
lowLatency(false),
systemsRegistered(false),
romExportsRegistered(false),
hasLoadedSomething(false),
midiOutClock(false),
midiOutTime(false),
@ -1478,6 +1489,7 @@ 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));

View file

@ -20,6 +20,7 @@
#include "engine.h"
#include "export/amigaValidation.h"
#include "export/tiuna.h"
DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
DivROMExport* exporter=NULL;
@ -27,6 +28,9 @@ DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
case DIV_ROM_AMIGA_VALIDATION:
exporter=new DivExportAmigaValidation;
break;
case DIV_ROM_TIUNA:
exporter=new DivExportTiuna;
break;
default:
exporter=new DivROMExport;
break;

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
};
@ -52,39 +54,54 @@ struct DivROMExportProgress {
class DivROMExport {
protected:
std::vector<String> exportLog;
DivConfig conf;
std::vector<DivROMExportOutput> output;
std::mutex logLock;
void logAppend(String what);
public:
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();
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

@ -36,9 +36,9 @@ bool DivROMExport::hasFailed() {
return true;
}
DivROMExportProgress DivROMExport::getProgress() {
DivROMExportProgress DivROMExport::getProgress(int index) {
DivROMExportProgress ret;
ret.name="Test";
ret.name="";
ret.amount=0.0f;
return ret;
}
@ -55,3 +55,7 @@ void DivROMExport::wait() {
bool DivROMExport::isRunning() {
return false;
}
void DivROMExport::setConf(DivConfig& c) {
conf=c;
}

View file

@ -70,6 +70,7 @@ void DivExportAmigaValidation::run() {
bool done=false;
// sample.bin
logAppend("writing samples...");
SafeWriter* sample=new SafeWriter;
sample->init();
for (int i=0; i<256; i++) {
@ -79,6 +80,7 @@ void DivExportAmigaValidation::run() {
if (sample->tell()&1) sample->writeC(0);
// seq.bin
logAppend("making sequence...");
SafeWriter* seq=new SafeWriter;
seq->init();
@ -239,6 +241,7 @@ void DivExportAmigaValidation::run() {
EXTERN_BUSY_END;
// wave.bin
logAppend("writing wavetables...");
SafeWriter* wave=new SafeWriter;
wave->init();
for (WaveEntry& i: waves) {
@ -246,6 +249,7 @@ void DivExportAmigaValidation::run() {
}
// sbook.bin
logAppend("writing sample book...");
SafeWriter* sbook=new SafeWriter;
sbook->init();
for (SampleBookEntry& i: sampleBook) {
@ -255,6 +259,7 @@ void DivExportAmigaValidation::run() {
}
// wbook.bin
logAppend("writing wavetable book...");
SafeWriter* wbook=new SafeWriter;
wbook->init();
for (WaveEntry& i: waves) {
@ -272,6 +277,8 @@ void DivExportAmigaValidation::run() {
output.push_back(DivROMExportOutput("wave.bin",wave));
output.push_back(DivROMExportOutput("seq.bin",seq));
logAppend("finished!");
running=false;
}
@ -296,3 +303,7 @@ void DivExportAmigaValidation::abort() {
bool DivExportAmigaValidation::isRunning() {
return running;
}
bool DivExportAmigaValidation::hasFailed() {
return false;
}

View file

@ -29,6 +29,7 @@ class DivExportAmigaValidation: public DivROMExport {
public:
bool go(DivEngine* e);
bool isRunning();
bool hasFailed();
void abort();
void wait();
~DivExportAmigaValidation() {}

View file

@ -17,13 +17,14 @@
* 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>
#include "engine.h"
#include "../fileutils.h"
#include "../ta-log.h"
struct TiunaNew {
short pitch;
@ -180,140 +181,161 @@ static void writeCmd(std::vector<TiunaBytes>& cmds, TiunaCmd& cmd, unsigned char
}
}
SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, int firstBankSize, int otherBankSize) {
stop();
repeatPattern=false;
shallStop=false;
setOrder(0);
BUSY_BEGIN_SOFT;
// determine loop point
// bool stopped=false;
int loopOrder=0;
int loopOrderRow=0;
int loopEnd=0;
walkSong(loopOrder,loopOrderRow,loopEnd);
logI("loop point: %d %d",loopOrder,loopOrderRow);
SafeWriter* w=new SafeWriter;
w->init();
int tiaIdx=-1;
for (int i=0; i<song.systemLen; i++) {
if (sysToExport!=NULL && !sysToExport[i]) continue;
if (song.system[i]==DIV_SYSTEM_TIA) {
tiaIdx=i;
disCont[i].dispatch->toggleRegisterDump(true);
break;
}
}
if (tiaIdx<0) {
lastError="selected TIA system not found";
return NULL;
}
// write patterns
// bool writeLoop=false;
bool done=false;
playSub(false);
void DivExportTiuna::run() {
int loopOrder, loopOrderRow, loopEnd;
int tick=0;
// int loopTick=-1;
TiunaLast last[2];
TiunaNew news[2];
SafeWriter* w;
std::map<int,TiunaCmd> allCmds[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 (nextTick(false,true) || !playing) {
// stopped=!playing;
done=true;
break;
}
for (int i=0; i<2; i++) {
news[i]=TiunaNew();
}
// get register dumps
std::vector<DivRegWrite>& writes=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;
}
cmdStream.clear();
tick++;
}
for (int i=0; i<song.systemLen; i++) {
disCont[i].dispatch->getRegisterWrites().clear();
disCont[i].dispatch->toggleRegisterDump(false);
}
remainingLoops=-1;
playing=false;
freelance=false;
extValuePresent=false;
BUSY_END;
// 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"
@ -321,7 +343,7 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel,
"; Author: {}\n"
"; Album: {}\n"
"; Subsong #{}: {}\n\n",
song.name,song.author,song.category,curSubSongIndex+1,curSubSong->name
e->song.name,e->song.author,e->song.category,e->curSubSongIndex+1,e->curSubSong->name
));
for (int i=0; i<2; i++) {
TiunaCmd lastCmd;
@ -349,15 +371,30 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel,
int cmdSize=renderedCmds.size();
bool* processed=new bool[cmdSize];
memset(processed,0,cmdSize*sizeof(bool));
logI("max cmId: %d",(MAX(firstBankSize/1024,1))*256);
while (firstBankSize>768 && cmId<(MAX(firstBankSize/1024,1))*256) {
logI("start CM %04x...",cmId);
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;
logD("scan %d size...",cmdSize-1);
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;) {
@ -427,12 +464,11 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel,
i++;
}
if (potentialMatches.empty()) {
logV("potentialMatches is empty");
logAppend("potentialMatches is empty");
break;
}
int maxPMIdx=0;
int maxPMVal=0;
logV("looking through potentialMatches...");
for (const auto& i: potentialMatches) {
if (i.second.bytesSaved>maxPMVal) {
maxPMVal=i.second.bytesSaved;
@ -440,16 +476,19 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel,
}
}
int maxPMLen=potentialMatches[maxPMIdx].length;
logV("the other step...");
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);
logI("CM %04x added: pos=%d,len=%d,matches=%d,saved=%d",cmId,maxPMIdx,maxPMLen,potentialMatches[maxPMIdx].pos.size(),maxPMVal);
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;
@ -460,8 +499,10 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel,
// overlap check
for (int i=1; i<(int)confirmedMatches.size(); i++) {
if (confirmedMatches[i-1].endPos<=confirmedMatches[i].pos) continue;
lastError="impossible overlap found in matches list, please report";
return NULL;
logAppend("ERROR: impossible overlap found in matches list, please report");
failed=true;
running=false;
return;
}
SafeWriter dbg;
dbg.init();
@ -510,8 +551,10 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel,
}
w->writeC('\n');
if (totalSize>firstBankSize) {
lastError="first bank is not large enough to contain call table";
return NULL;
logAppend("ERROR: first bank is not large enough to contain call table");
failed=true;
running=false;
return;
}
int curBank=0;
@ -572,13 +615,50 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel,
}
w->writeText(" .text x\"e0\"\n .endsection\n");
totalSize++;
logI("total size: %d bytes (%d banks)",totalSize,curBank+1);
//FILE* f=ps_fopen("confirmedMatches.txt","wb");
//if (f!=NULL) {
// fwrite(dbg.getFinalBuf(),1,dbg.size(),f);
// fclose(f);
//}
logAppendf("total size: %d bytes (%d banks)",totalSize,curBank+1);
return w;
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

@ -2094,6 +2094,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
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();

View file

@ -639,6 +639,8 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
logD("seek not needed...");
}
logV("reading sample data (%d)",s->samples);
if (flags&8) { // compressed sample
unsigned int ret=0;
logV("decompression begin... (%d)",s->samples);
@ -672,62 +674,66 @@ bool DivEngine::loadIT(unsigned char* file, size_t len) {
}
logV("got: %d",ret);
} else {
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
if (flags&4) { // downmix stereo
for (unsigned int i=0; i<s->samples; i++) {
short l;
if (convert&2) {
l=reader.readS_BE();
} else {
l=reader.readS();
try {
if (s->depth==DIV_SAMPLE_DEPTH_16BIT) {
if (flags&4) { // downmix stereo
for (unsigned int i=0; i<s->samples; i++) {
short l;
if (convert&2) {
l=reader.readS_BE();
} else {
l=reader.readS();
}
if (!(convert&1)) {
l^=0x8000;
}
s->data16[i]=l;
}
if (!(convert&1)) {
l^=0x8000;
for (unsigned int i=0; i<s->samples; i++) {
short r;
if (convert&2) {
r=reader.readS_BE();
} else {
r=reader.readS();
}
if (!(convert&1)) {
r^=0x8000;
}
s->data16[i]=(s->data16[i]+r)>>1;
}
s->data16[i]=l;
}
for (unsigned int i=0; i<s->samples; i++) {
short r;
if (convert&2) {
r=reader.readS_BE();
} else {
r=reader.readS();
} else {
for (unsigned int i=0; i<s->samples; i++) {
if (convert&2) {
s->data16[i]=reader.readS_BE()^((convert&1)?0:0x8000);
} else {
s->data16[i]=reader.readS()^((convert&1)?0:0x8000);
}
}
if (!(convert&1)) {
r^=0x8000;
}
s->data16[i]=(s->data16[i]+r)>>1;
}
} else {
for (unsigned int i=0; i<s->samples; i++) {
if (convert&2) {
s->data16[i]=reader.readS_BE()^((convert&1)?0:0x8000);
} else {
s->data16[i]=reader.readS()^((convert&1)?0:0x8000);
if (flags&4) { // downmix stereo
for (unsigned int i=0; i<s->samples; i++) {
signed char l=reader.readC();
if (!(convert&1)) {
l^=0x80;
}
s->data8[i]=l;
}
for (unsigned int i=0; i<s->samples; i++) {
signed char r=reader.readC();
if (!(convert&1)) {
r^=0x80;
}
s->data8[i]=(s->data8[i]+r)>>1;
}
} else {
for (unsigned int i=0; i<s->samples; i++) {
s->data8[i]=reader.readC()^((convert&1)?0:0x80);
}
}
}
} else {
if (flags&4) { // downmix stereo
for (unsigned int i=0; i<s->samples; i++) {
signed char l=reader.readC();
if (!(convert&1)) {
l^=0x80;
}
s->data8[i]=l;
}
for (unsigned int i=0; i<s->samples; i++) {
signed char r=reader.readC();
if (!(convert&1)) {
r^=0x80;
}
s->data8[i]=(s->data8[i]+r)>>1;
}
} else {
for (unsigned int i=0; i<s->samples; i++) {
s->data8[i]=reader.readC()^((convert&1)?0:0x80);
}
}
} catch (EndOfFileException& e) {
logW("premature end of file...");
}
}

View file

@ -51,6 +51,7 @@ static void readSbiOpData(sbi_t& sbi, SafeReader& reader) {
bool DivEngine::loadS3M(unsigned char* file, size_t len) {
struct InvalidHeaderException {};
bool success=false;
bool opl2=!getConfInt("s3mOPL3",0);
char magic[4]={0,0,0,0};
SafeReader reader=SafeReader(file,len);
warnings="";
@ -273,11 +274,16 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
bool hasPCM=false;
bool hasFM=false;
int numChans=0;
int realNumChans=0;
for (int i=0; i<32; i++) {
if (chanSettings[i]==255) continue;
if ((chanSettings[i]&127)>=32) continue;
if ((chanSettings[i]&127)>=16) {
for (int ch=0; ch<32; ch++) {
if (chanSettings[ch]!=255) realNumChans++;
}
for (int ch=0; ch<32; ch++) {
if (chanSettings[ch]==255) continue;
if ((chanSettings[ch]&127)>=32) continue;
if ((chanSettings[ch]&127)>=16) {
hasFM=true;
} else {
hasPCM=true;
@ -287,34 +293,69 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
if (hasFM && hasPCM) break;
}
int pcmChan=hasFM?9:0;
int pcmChan=hasFM?(opl2 ? 9 : 18):0;
int fmChan=hasPCM?32:0;
int invalidChan=40;
for (int i=0; i<32; i++) {
if (chanSettings[i]==255) {
chanMap[i]=invalidChan++;
for (int ch=0; ch<32; ch++) {
if (chanSettings[ch]==255) {
chanMap[ch]=invalidChan++;
continue;
}
if ((chanSettings[i]&127)>=32) {
chanMap[i]=invalidChan++;
if ((chanSettings[ch]&127)>=32) {
chanMap[ch]=invalidChan++;
continue;
}
if ((chanSettings[i]&127)>=16) {
chanMap[i]=fmChan++;
if ((chanSettings[ch]&127)>=16) {
chanMap[ch]=fmChan++;
} else {
chanMap[i]=pcmChan++;
chanMap[ch]=pcmChan++;
}
}
char buffer[40];
int chanIndex = 1;
if (hasPCM) {
for (int i=pcmChan; i<32; i++) {
ds.subsong[0]->chanShow[i]=false;
ds.subsong[0]->chanShowChanOsc[i]=false;
for(int ch = 0; ch < pcmChan - (realNumChans - (hasFM ? 9 : 0)); ch++)
{
ds.subsong[0]->chanShow[ch]=false;
ds.subsong[0]->chanShowChanOsc[ch]=false;
}
for (int ch=pcmChan; ch<32; ch++) {
ds.subsong[0]->chanShow[ch]=false;
ds.subsong[0]->chanShowChanOsc[ch]=false;
}
for(int ch = 0; ch < 32; ch++)
{
if(ds.subsong[0]->chanShow[ch])
{
snprintf(buffer, 40, _("Channel %d"), chanIndex);
ds.subsong[0]->chanName[ch] = buffer;
chanIndex++;
}
}
}
if (hasFM && !opl2) {
for (int ch=(hasPCM?32:0) + 9; ch<(hasPCM?32:0) + 18; ch++) {
ds.subsong[0]->chanShow[ch]=false;
ds.subsong[0]->chanShowChanOsc[ch]=false;
}
chanIndex = 1;
for (int ch=(hasPCM?32:0); ch<(hasPCM?32:0) + 9; ch++) {
snprintf(buffer, 40, _("FM %d"), chanIndex);
ds.subsong[0]->chanName[ch] = buffer;
chanIndex++;
}
}
logV("numChans: %d",numChans);
logV("realNumChans: %d",realNumChans);
ds.systemName="PC";
if (hasPCM) {
@ -327,7 +368,7 @@ bool DivEngine::loadS3M(unsigned char* file, size_t len) {
ds.systemLen++;
}
if (hasFM) {
ds.system[ds.systemLen]=DIV_SYSTEM_OPL2;
ds.system[ds.systemLen]=opl2 ? DIV_SYSTEM_OPL2 : DIV_SYSTEM_OPL3;
ds.systemVol[ds.systemLen]=1.0f;
ds.systemPan[ds.systemLen]=0;
ds.systemLen++;

View file

@ -155,43 +155,76 @@ void DivPlatformAY8910::runDAC() {
}
void DivPlatformAY8910::runTFX() {
if (selCore) return;
/*
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 = MAX(0, ((chan[i].tfx.out) ? (chan[i].outVol&15) : (chan[i].tfx.lowBound-(15-chan[i].outVol))));
output &= 15;
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]) {
immWrite(0x08+i,output|(chan[i].curPSGMode.getEnvelope()<<2));
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]) {
immWrite(0xd, ayEnvMode);
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.mode == -1 && !isMuted[i]) {
if (intellivision && chan[i].curPSGMode.getEnvelope()) {
immWrite(0x08+i,(chan[i].outVol&0xc)<<2);
} else {
immWrite(0x08+i,(chan[i].outVol&15)|((chan[i].curPSGMode.getEnvelope())<<2));
}
}
}
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;
}
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;
}
}
@ -388,6 +421,7 @@ 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) {
@ -726,12 +760,10 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
break;
}
case DIV_CMD_STD_NOISE_MODE:
if (c.value&0xf0 && !(chan[c.chan].nextPSGMode.val&8)) {
chan[c.chan].nextPSGMode.val|=16;
chan[c.chan].tfx.mode = (c.value&3);
}
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) {
@ -805,9 +837,16 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
updateOutSel(true);
immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal));
break;
case DIV_CMD_AY_AUTO_PWM:
chan[c.chan].tfx.offset=c.value;
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;

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

@ -5,6 +5,7 @@
// Chip revisions
// 0: V 0.3.0
// 1: V 47.0.0 (9-bit volume, phase reset on mute)
// 2: V 47.0.2 (Pulse Width XOR on Saw and Triangle)
#include "vera_psg.h"
@ -88,8 +89,8 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right)
uint8_t v = 0;
switch (ch->waveform) {
case WF_PULSE: v = (ch->phase >> 10) > ch->pw ? 0 : 63; break;
case WF_SAWTOOTH: v = ch->phase >> 11; break;
case WF_TRIANGLE: v = (ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F); break;
case WF_SAWTOOTH: v = (ch->phase >> 11) ^ (psg->chipType < 2 ? 0 : (ch->pw ^ 0x3f) & 0x3f); break;
case WF_TRIANGLE: v = ((ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F)) ^ (psg->chipType < 2 ? 0 : (ch->pw ^ 0x3f) & 0x3f); break;
case WF_NOISE: v = ch->noiseval; break;
}
int8_t sv = (v ^ 0x20);

View file

@ -532,7 +532,7 @@ void DivPlatformVERA::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformVERA::setFlags(const DivConfig& flags) {
psg->chipType=flags.getInt("chipType",1);
psg->chipType=flags.getInt("chipType",2);
chipClock=25000000;
CHECK_CUSTOM_CLOCK;
rate=chipClock/512;

View file

@ -1647,26 +1647,34 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
chan[i].panPos+=chan[i].panRate;
chan[i].panPos&=255;
// calculate...
// calculate inverted...
switch (chan[i].panPos&0xc0) {
case 0: // center -> right
chan[i].panL=0xff-((chan[i].panPos&0x3f)<<2);
chan[i].panR=0xff;
chan[i].panL=((chan[i].panPos&0x3f)<<2);
chan[i].panR=0;
break;
case 0x40: // right -> center
chan[i].panL=(chan[i].panPos&0x3f)<<2;
chan[i].panR=0xff;
chan[i].panL=0xff-((chan[i].panPos&0x3f)<<2);
chan[i].panR=0;
break;
case 0x80: // center -> left
chan[i].panL=0xff;
chan[i].panR=0xff-((chan[i].panPos&0x3f)<<2);
chan[i].panL=0;
chan[i].panR=((chan[i].panPos&0x3f)<<2);
break;
case 0xc0: // left -> center
chan[i].panL=0xff;
chan[i].panR=(chan[i].panPos&0x3f)<<2;
chan[i].panL=0;
chan[i].panR=0xff-((chan[i].panPos&0x3f)<<2);
break;
}
// multiply by depth
chan[i].panL=(chan[i].panL*chan[i].panDepth)/15;
chan[i].panR=(chan[i].panR*chan[i].panDepth)/15;
// then invert it to get final panning
chan[i].panL^=0xff;
chan[i].panR^=0xff;
dispatchCmd(DivCommand(DIV_CMD_PANNING,i,chan[i].panL,chan[i].panR));
}

View file

@ -141,6 +141,8 @@ enum DivSystem {
DIV_SYSTEM_5E01,
DIV_SYSTEM_BIFURCATOR,
DIV_SYSTEM_SID2,
DIV_SYSTEM_MAX
};
enum DivEffectType: unsigned short {

View file

@ -433,6 +433,7 @@ void DivEngine::registerSystems() {
{0x25, {DIV_CMD_AY_ENVELOPE_SLIDE, _("25xx: Envelope slide up"), negEffectVal}},
{0x26, {DIV_CMD_AY_ENVELOPE_SLIDE, _("26xx: Envelope slide down")}},
{0x29, {DIV_CMD_AY_AUTO_ENVELOPE, _("29xy: Set auto-envelope (x: numerator; y: denominator)")}},
{0x2c, {DIV_CMD_AY_AUTO_PWM, _("2Cxx: Set timer period offset (bit 7: sign)")}},
{0x2e, {DIV_CMD_AY_IO_WRITE, _("2Exx: Write to I/O port A"), constVal<0>, effectVal}},
{0x2f, {DIV_CMD_AY_IO_WRITE, _("2Fxx: Write to I/O port B"), constVal<1>, effectVal}},
};

View file

@ -133,14 +133,6 @@ void DivZSM::writePSG(unsigned char a, unsigned char v) {
} else if (a>=64) {
return writePCM(a-64,v);
}
if (optimize) {
if ((a&3)==3 && v>64) {
// Pulse width on non-pulse waves is nonsense and wasteful
// No need to preserve state here because the next write that
// selects pulse will also set the pulse width in this register
v&=0xc0;
}
}
if (psgState[psg_PREV][a]==v) {
if (psgState[psg_NEW][a]!=v) {
// NEW value is being reset to the same as PREV value

View file

@ -75,7 +75,7 @@ const char* aboutLine[]={
"Polski: freq-mod, PoznańskiSzybkowiec",
"Português (Brasil): Kagamiin~",
"Русский: Background2982, LTVA",
"Slovenčina: Mr. Hassium",
"Slovenčina: Wegfrei",
"Svenska: RevvoBolt",
"ไทย: akumanatt",
"",
@ -206,6 +206,7 @@ const char* aboutLine[]={
"Ultraprogramer",
"UserSniper",
"Weeppiko",
"Wegfrei",
"Xan",
"Yuzu4K",
"Zabir",

View file

@ -36,6 +36,8 @@ static float oscDebugMax=1.0;
static float oscDebugPower=1.0;
static int oscDebugRepeat=1;
static int numApples=1;
static int getGainChan=0;
static int getGainVol=0;
static void _drawOsc(const ImDrawList* drawList, const ImDrawCmd* cmd) {
if (cmd!=NULL) {
@ -721,6 +723,13 @@ void FurnaceGUI::drawDebug() {
ImGui::TreePop();
}
#endif
if (ImGui::TreeNode("Get Gain Test")) {
float realVol=e->getGain(getGainChan,getGainVol);
ImGui::InputInt("Chan",&getGainChan);
ImGui::InputInt("Vol",&getGainVol);
ImGui::Text("result: %.0f%%",realVol*100.0f);
ImGui::TreePop();
}
if (ImGui::TreeNode("User Interface")) {
if (ImGui::Button("Inspect")) {
inspectorOpen=!inspectorOpen;

View file

@ -676,6 +676,17 @@ void FurnaceGUI::doAction(int what) {
latchTarget=0;
latchNibble=false;
break;
case GUI_ACTION_PAT_ABSORB_INSTRUMENT: {
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false);
if (!pat) break;
for (int i=cursor.y; i>=0; i--) {
if (pat->data[i][2] >= 0) {
curIns=pat->data[i][2];
break;
}
}
break;
}
case GUI_ACTION_INS_LIST_ADD:
if (settings.insTypeMenu) {

View file

@ -130,7 +130,7 @@ const bool mobileButtonPersist[32]={
// page 1
false,
false,
false,
true,
false,
true,
true,

View file

@ -239,6 +239,107 @@ void FurnaceGUI::drawExportVGM(bool onWindow) {
}
}
void FurnaceGUI::drawExportROM(bool onWindow) {
exitDisabledTimer=1;
const DivROMExportDef* def=e->getROMExportDef(romTarget);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::BeginCombo("##ROMTarget",def==NULL?"<select one>":def->name)) {
for (int i=0; i<DIV_ROM_MAX; i++) {
const DivROMExportDef* newDef=e->getROMExportDef((DivROMExportOptions)i);
if (newDef!=NULL) {
if (romExportAvail[i]) {
if (ImGui::Selectable(newDef->name)) {
romTarget=(DivROMExportOptions)i;
romMultiFile=newDef->multiOutput;
romConfig=DivConfig();
if (newDef->fileExt==NULL) {
romFilterName="";
romFilterExt="";
} else {
romFilterName=newDef->fileType;
romFilterExt=newDef->fileExt;
}
}
}
}
}
ImGui::EndCombo();
}
if (def!=NULL) {
ImGui::Text("by %s",def->author);
ImGui::TextWrapped("%s",def->description);
}
ImGui::Separator();
bool altered=false;
switch (romTarget) {
case DIV_ROM_TIUNA: {
String asmBaseLabel=romConfig.getString("baseLabel","song");
int firstBankSize=romConfig.getInt("firstBankSize",3072);
int otherBankSize=romConfig.getInt("otherBankSize",4096-48);
int sysToExport=romConfig.getInt("sysToExport",-1);
// TODO; validate label
if (ImGui::InputText(_("base song label name"),&asmBaseLabel)) {
altered=true;
}
if (ImGui::InputInt(_("max size in first bank"),&firstBankSize,1,100)) {
if (firstBankSize<0) firstBankSize=0;
if (firstBankSize>4096) firstBankSize=4096;
altered=true;
}
if (ImGui::InputInt(_("max size in other banks"),&otherBankSize,1,100)) {
if (otherBankSize<16) otherBankSize=16;
if (otherBankSize>4096) otherBankSize=4096;
altered=true;
}
ImGui::Text(_("chip to export:"));
for (int i=0; i<e->song.systemLen; i++) {
DivSystem sys=e->song.system[i];
bool isTIA=(sys==DIV_SYSTEM_TIA);
ImGui::BeginDisabled(!isTIA);
if (ImGui::RadioButton(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),sysToExport==i)) {
sysToExport=i;
altered=true;
}
ImGui::EndDisabled();
}
if (altered) {
romConfig.set("baseLabel",asmBaseLabel);
romConfig.set("firstBankSize",firstBankSize);
romConfig.set("otherBankSize",otherBankSize);
romConfig.set("sysToExport",sysToExport);
}
break;
}
case DIV_ROM_ABSTRACT:
ImGui::TextWrapped("%s",_("select a target from the menu at the top of this dialog."));
break;
default:
ImGui::TextWrapped("%s",_("this export method doesn't offer any options."));
break;
}
/*
*/
if (onWindow) {
ImGui::Separator();
if (ImGui::Button(_("Cancel"),ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup();
ImGui::SameLine();
}
if (ImGui::Button(_("Export"),ImVec2(200.0f*dpiScale,0))) {
openFileDialog(GUI_FILE_EXPORT_ROM);
ImGui::CloseCurrentPopup();
}
}
void FurnaceGUI::drawExportZSM(bool onWindow) {
exitDisabledTimer=1;
@ -261,103 +362,6 @@ void FurnaceGUI::drawExportZSM(bool onWindow) {
}
}
void FurnaceGUI::drawExportTiuna(bool onWindow) {
exitDisabledTimer=1;
ImGui::Text(_("for use with TIunA driver. outputs asm source."));
ImGui::InputText(_("base song label name"),&asmBaseLabel); // TODO: validate label
if (ImGui::InputInt(_("max size in first bank"),&tiunaFirstBankSize,1,100)) {
if (tiunaFirstBankSize<0) tiunaFirstBankSize=0;
if (tiunaFirstBankSize>4096) tiunaFirstBankSize=4096;
}
if (ImGui::InputInt(_("max size in other banks"),&tiunaOtherBankSize,1,100)) {
if (tiunaOtherBankSize<16) tiunaOtherBankSize=16;
if (tiunaOtherBankSize>4096) tiunaOtherBankSize=4096;
}
ImGui::Text(_("chips to export:"));
int selected=0;
for (int i=0; i<e->song.systemLen; i++) {
DivSystem sys=e->song.system[i];
bool isTIA=sys==DIV_SYSTEM_TIA;
ImGui::BeginDisabled((!isTIA) || (selected>=1));
ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]);
ImGui::EndDisabled();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) {
if (!isTIA) {
ImGui::SetTooltip(_("this chip is not supported by the file format!"));
} else if (selected>=1) {
ImGui::SetTooltip(_("only one Atari TIA is supported!"));
}
}
if (isTIA && willExport[i]) selected++;
}
if (selected>0) {
if (onWindow) {
ImGui::Separator();
if (ImGui::Button(_("Cancel"),ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup();
ImGui::SameLine();
}
if (ImGui::Button(_("Export"),ImVec2(200.0f*dpiScale,0))) {
openFileDialog(GUI_FILE_EXPORT_TIUNA);
ImGui::CloseCurrentPopup();
}
} else {
ImGui::Text(_("nothing to export"));
if (onWindow) {
ImGui::Separator();
if (ImGui::Button(_("Cancel"),ImVec2(400.0f*dpiScale,0))) ImGui::CloseCurrentPopup();
}
}
}
void FurnaceGUI::drawExportAmigaVal(bool onWindow) {
exitDisabledTimer=1;
ImGui::Text(_(
"this is NOT ROM export! only use for making sure the\n"
"Furnace Amiga emulator is working properly by\n"
"comparing it with real Amiga output."
));
ImGui::AlignTextToFramePadding();
ImGui::Text(_("Directory"));
ImGui::SameLine();
ImGui::InputText("##AVDPath",&workingDirROMExport);
if (onWindow) {
ImGui::Separator();
if (ImGui::Button(_("Cancel"),ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup();
ImGui::SameLine();
}
if (ImGui::Button(_("Bake Data"),ImVec2(200.0f*dpiScale,0))) {
DivROMExport* ex=e->buildROM(DIV_ROM_AMIGA_VALIDATION);
if (ex->go(e)) {
ex->wait();
if (ex->hasFailed()) {
showError("error!");
} else {
if (workingDirROMExport.size()>0) {
if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR;
}
for (DivROMExportOutput& i: ex->getResult()) {
String path=workingDirROMExport+i.name;
FILE* outFile=ps_fopen(path.c_str(),"wb");
if (outFile!=NULL) {
fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile);
fclose(outFile);
}
i.data->finish();
delete i.data;
}
showError(fmt::sprintf(_("Done! Baked %d files."),(int)ex->getResult().size()));
}
} else {
showError("error!");
}
delete ex;
ImGui::CloseCurrentPopup();
}
}
void FurnaceGUI::drawExportText(bool onWindow) {
exitDisabledTimer=1;
@ -434,6 +438,12 @@ void FurnaceGUI::drawExport() {
drawExportVGM(true);
ImGui::EndTabItem();
}
if (romExportExists) {
if (ImGui::BeginTabItem(_("ROM"))) {
drawExportROM(true);
ImGui::EndTabItem();
}
}
int numZSMCompat=0;
for (int i=0; i<e->song.systemLen; i++) {
if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++;
@ -444,29 +454,6 @@ void FurnaceGUI::drawExport() {
ImGui::EndTabItem();
}
}
bool hasTiunaCompat=false;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_TIA) {
hasTiunaCompat=true;
break;
}
}
if (hasTiunaCompat) {
if (ImGui::BeginTabItem("TIunA")) {
drawExportTiuna(true);
ImGui::EndTabItem();
}
}
int numAmiga=0;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++;
}
if (numAmiga && settings.iCannotWait) {
if (ImGui::BeginTabItem(_("Amiga Validation"))) {
drawExportAmigaVal(true);
ImGui::EndTabItem();
}
}
if (ImGui::BeginTabItem(_("Text"))) {
drawExportText(true);
ImGui::EndTabItem();
@ -488,15 +475,12 @@ void FurnaceGUI::drawExport() {
case GUI_EXPORT_VGM:
drawExportVGM(true);
break;
case GUI_EXPORT_ROM:
drawExportROM(true);
break;
case GUI_EXPORT_ZSM:
drawExportZSM(true);
break;
case GUI_EXPORT_TIUNA:
drawExportTiuna(true);
break;
case GUI_EXPORT_AMIGA_VAL:
drawExportAmigaVal(true);
break;
case GUI_EXPORT_TEXT:
drawExportText(true);
break;

View file

@ -752,6 +752,88 @@ void FurnaceGUI::autoDetectSystem() {
}
}
void FurnaceGUI::updateROMExportAvail() {
unsigned char sysReqCount[DIV_SYSTEM_MAX];
unsigned char defReqCount[DIV_SYSTEM_MAX];
memset(sysReqCount,0,DIV_SYSTEM_MAX);
for (int i=0; i<e->song.systemLen; i++) {
sysReqCount[e->song.system[i]]++;
}
memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX);
romExportExists=false;
for (int i=0; i<DIV_ROM_MAX; i++) {
const DivROMExportDef* newDef=e->getROMExportDef((DivROMExportOptions)i);
if (newDef!=NULL) {
// check for viability
bool viable=true;
memset(defReqCount,0,DIV_SYSTEM_MAX);
for (DivSystem j: newDef->requisites) {
defReqCount[j]++;
}
switch (newDef->requisitePolicy) {
case DIV_REQPOL_EXACT:
for (int j=0; j<DIV_SYSTEM_MAX; j++) {
if (defReqCount[j]!=sysReqCount[j]) {
viable=false;
break;
}
}
break;
case DIV_REQPOL_ANY:
for (int j=0; j<DIV_SYSTEM_MAX; j++) {
if (defReqCount[j]>sysReqCount[j]) {
viable=false;
break;
}
}
break;
case DIV_REQPOL_LAX:
viable=false;
for (DivSystem j: newDef->requisites) {
if (defReqCount[j]<=sysReqCount[j]) {
viable=true;
break;
}
}
break;
}
if (viable) {
romExportAvail[i]=true;
romExportExists=true;
}
}
}
if (!romExportAvail[romTarget]) {
// find a new one
romTarget=DIV_ROM_ABSTRACT;
for (int i=0; i<DIV_ROM_MAX; i++) {
const DivROMExportDef* newDef=e->getROMExportDef((DivROMExportOptions)i);
if (newDef!=NULL) {
if (romExportAvail[i]) {
romTarget=(DivROMExportOptions)i;
romMultiFile=newDef->multiOutput;
romConfig=DivConfig();
if (newDef->fileExt==NULL) {
romFilterName="";
romFilterExt="";
} else {
romFilterName=newDef->fileType;
romFilterExt=newDef->fileExt;
}
break;
}
}
}
}
}
ImVec4 FurnaceGUI::channelColor(int ch) {
switch (settings.channelColors) {
case 0:
@ -1950,15 +2032,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
(settings.autoFillSave)?shortName:""
);
break;
case GUI_FILE_EXPORT_TIUNA:
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
hasOpened=fileDialog->openSave(
"Export TIunA",
{"assembly files", "*.asm"},
workingDirROMExport,
dpiScale
);
break;
case GUI_FILE_EXPORT_TEXT:
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
hasOpened=fileDialog->openSave(
@ -1980,7 +2053,22 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
);
break;
case GUI_FILE_EXPORT_ROM:
showError(_("Coming soon!"));
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
if (romMultiFile) {
hasOpened=fileDialog->openSelectDir(
_("Export ROM"),
workingDirROMExport,
dpiScale
);
} else {
hasOpened=fileDialog->openSave(
_("Export ROM"),
{romFilterName, "*"+romFilterExt},
workingDirROMExport,
dpiScale,
(settings.autoFillSave)?shortName:""
);
}
break;
case GUI_FILE_LOAD_MAIN_FONT:
if (!dirExists(workingDirFont)) workingDirFont=getHomeDir();
@ -2353,6 +2441,7 @@ int FurnaceGUI::load(String path) {
undoHist.clear();
redoHist.clear();
updateWindowTitle();
updateROMExportAvail();
updateScroll(0);
if (!e->getWarnings().empty()) {
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
@ -4310,6 +4399,12 @@ bool FurnaceGUI::loop() {
drawExportVGM();
ImGui::EndMenu();
}
if (romExportExists) {
if (ImGui::BeginMenu(_("export ROM..."))) {
drawExportROM();
ImGui::EndMenu();
}
}
int numZSMCompat=0;
for (int i=0; i<e->song.systemLen; i++) {
if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++;
@ -4320,29 +4415,6 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
}
bool hasTiunaCompat=false;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_TIA) {
hasTiunaCompat=true;
break;
}
}
if (hasTiunaCompat) {
if (ImGui::BeginMenu(_("export TIunA..."))) {
drawExportTiuna();
ImGui::EndMenu();
}
}
int numAmiga=0;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++;
}
if (numAmiga && settings.iCannotWait) {
if (ImGui::BeginMenu(_("export Amiga validation data..."))) {
drawExportAmigaVal();
ImGui::EndMenu();
}
}
if (ImGui::BeginMenu(_("export text..."))) {
drawExportText();
ImGui::EndMenu();
@ -4364,6 +4436,12 @@ bool FurnaceGUI::loop() {
curExportType=GUI_EXPORT_VGM;
displayExport=true;
}
if (romExportExists) {
if (ImGui::MenuItem(_("export ROM..."))) {
curExportType=GUI_EXPORT_ROM;
displayExport=true;
}
}
int numZSMCompat=0;
for (int i=0; i<e->song.systemLen; i++) {
if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++;
@ -4374,29 +4452,6 @@ bool FurnaceGUI::loop() {
displayExport=true;
}
}
bool hasTiunaCompat=false;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_TIA) {
hasTiunaCompat=true;
break;
}
}
if (hasTiunaCompat) {
if (ImGui::MenuItem(_("export TIunA..."))) {
curExportType=GUI_EXPORT_TIUNA;
displayExport=true;
}
}
int numAmiga=0;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++;
}
if (numAmiga && settings.iCannotWait) {
if (ImGui::MenuItem(_("export Amiga validation data..."))) {
curExportType=GUI_EXPORT_AMIGA_VAL;
displayExport=true;
}
}
if (ImGui::MenuItem(_("export text..."))) {
curExportType=GUI_EXPORT_TEXT;
displayExport=true;
@ -4434,6 +4489,7 @@ bool FurnaceGUI::loop() {
autoDetectSystem();
}
updateWindowTitle();
updateROMExportAvail();
}
ImGui::EndMenu();
}
@ -4460,6 +4516,7 @@ bool FurnaceGUI::loop() {
autoDetectSystem();
}
updateWindowTitle();
updateROMExportAvail();
} else {
showError(fmt::sprintf(_("cannot change chip! (%s)"),e->getLastError()));
}
@ -4484,6 +4541,7 @@ bool FurnaceGUI::loop() {
autoDetectSystem();
updateWindowTitle();
}
updateROMExportAvail();
}
}
ImGui::EndMenu();
@ -4980,7 +5038,6 @@ bool FurnaceGUI::loop() {
workingDirZSMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_EXPORT_ROM:
case GUI_FILE_EXPORT_TIUNA:
case GUI_FILE_EXPORT_TEXT:
case GUI_FILE_EXPORT_CMDSTREAM:
workingDirROMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
@ -5076,6 +5133,9 @@ bool FurnaceGUI::loop() {
if (curFileDialog==GUI_FILE_EXPORT_VGM) {
checkExtension(".vgm");
}
if (curFileDialog==GUI_FILE_EXPORT_ROM) {
checkExtension(romFilterExt.c_str());
}
if (curFileDialog==GUI_FILE_EXPORT_ZSM) {
checkExtension(".zsm");
}
@ -5575,29 +5635,20 @@ bool FurnaceGUI::loop() {
}
break;
}
case GUI_FILE_EXPORT_TIUNA: {
SafeWriter* w=e->saveTiuna(willExport,asmBaseLabel.c_str(),tiunaFirstBankSize,tiunaOtherBankSize);
if (w!=NULL) {
FILE* f=ps_fopen(copyOfName.c_str(),"wb");
if (f!=NULL) {
fwrite(w->getFinalBuf(),1,w->size(),f);
fclose(f);
pushRecentSys(copyOfName.c_str());
} else {
showError("could not open file!");
}
w->finish();
delete w;
if (!e->getWarnings().empty()) {
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
}
} else {
showError(fmt::sprintf("Could not write TIunA! (%s)",e->getLastError()));
}
break;
}
case GUI_FILE_EXPORT_ROM:
showError(_("Coming soon!"));
romExportPath=copyOfName;
pendingExport=e->buildROM(romTarget);
if (pendingExport==NULL) {
showError("could not create exporter! you may want to report this issue...");
} else {
pendingExport->setConf(romConfig);
if (pendingExport->go(e)) {
displayExportingROM=true;
romExportSave=true;
} else {
showError("could not begin exporting process! TODO: elaborate");
}
}
break;
case GUI_FILE_EXPORT_TEXT: {
SafeWriter* w=e->saveText(false);
@ -5769,6 +5820,11 @@ bool FurnaceGUI::loop() {
ImGui::OpenPopup(_("Rendering..."));
}
if (displayExportingROM) {
displayExportingROM=false;
ImGui::OpenPopup(_("ROM Export Progress"));
}
if (displayNew) {
newSongQuery="";
newSongFirstFrame=true;
@ -5788,6 +5844,7 @@ bool FurnaceGUI::loop() {
selEnd=SelectionPoint();
cursor=SelectionPoint();
updateWindowTitle();
updateROMExportAvail();
} else {
ImGui::OpenPopup(_("New Song"));
}
@ -5834,6 +5891,94 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
ImVec2 romExportMinSize=mobileUI?ImVec2(canvasW-(portrait?0:(60.0*dpiScale)),canvasH-60.0*dpiScale):ImVec2(400.0f*dpiScale,200.0f*dpiScale);
ImVec2 romExportMaxSize=ImVec2(canvasW-((mobileUI && !portrait)?(60.0*dpiScale):0),canvasH-(mobileUI?(60.0*dpiScale):0));
centerNextWindow(_("ROM Export Progress"),canvasW,canvasH);
ImGui::SetNextWindowSizeConstraints(romExportMinSize,romExportMaxSize);
if (ImGui::BeginPopupModal(_("ROM Export Progress"),NULL)) {
if (pendingExport==NULL) {
ImGui::TextWrapped("%s",_("...ooooor you could try asking me a new ROM export?"));
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::Button(_("Erm what the sigma???"),ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) {
ImGui::CloseCurrentPopup();
}
} else {
int progIndex=0;
while (true) {
DivROMExportProgress p=pendingExport->getProgress(progIndex);
if (p.name.empty()) break;
ImGui::Text("%s: %d%%",p.name.c_str(),(int)round(p.amount*100.0f));
ImGui::ProgressBar(p.amount,ImVec2(-FLT_MIN,0));
progIndex++;
}
ImVec2 romLogSize=ImGui::GetContentRegionAvail();
romLogSize.y-=ImGui::GetFrameHeightWithSpacing();
if (romLogSize.y<60.0f*dpiScale) romLogSize.y=60.0f*dpiScale;
if (ImGui::BeginChild("Export Log",romLogSize,true)) {
pendingExport->logLock.lock();
ImGui::PushFont(patFont);
for (String& i: pendingExport->exportLog) {
ImGui::TextWrapped("%s",i.c_str());
}
if (romExportSave) {
ImGui::SetScrollY(ImGui::GetScrollMaxY());
}
ImGui::PopFont();
pendingExport->logLock.unlock();
}
ImGui::EndChild();
if (pendingExport->isRunning()) {
WAKE_UP;
if (ImGui::Button(_("Abort"),ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) {
pendingExport->abort();
delete pendingExport;
pendingExport=NULL;
romExportSave=false;
ImGui::CloseCurrentPopup();
}
} else {
if (romExportSave) {
pendingExport->wait();
if (!pendingExport->hasFailed()) {
// save files here (romExportPath)
for (DivROMExportOutput& i: pendingExport->getResult()) {
String path=romExportPath;
if (romMultiFile) {
path+=DIR_SEPARATOR_STR;
path+=i.name;
}
FILE* outFile=ps_fopen(path.c_str(),"wb");
if (outFile!=NULL) {
fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile);
fclose(outFile);
} else {
// TODO: handle failure here
}
i.data->finish();
delete i.data;
}
}
romExportSave=false;
}
if (pendingExport!=NULL) {
if (pendingExport->hasFailed()) {
ImGui::AlignTextToFramePadding();
ImGui::TextUnformatted(_("Error!"));
ImGui::SameLine();
}
}
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::Button(_("OK"),ImVec2(ImGui::GetContentRegionAvail().x,0.0f))) {
delete pendingExport;
pendingExport=NULL;
ImGui::CloseCurrentPopup();
}
}
}
ImGui::EndPopup();
}
drawTutorial();
ImVec2 newSongMinSize=mobileUI?ImVec2(canvasW-(portrait?0:(60.0*dpiScale)),canvasH-60.0*dpiScale):ImVec2(400.0f*dpiScale,200.0f*dpiScale);
@ -6250,6 +6395,7 @@ bool FurnaceGUI::loop() {
updateWindowTitle();
MARK_MODIFIED;
}
updateROMExportAvail();
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
@ -7450,6 +7596,7 @@ bool FurnaceGUI::init() {
}
updateWindowTitle();
updateROMExportAvail();
logV("max texture size: %dx%d",rend->getMaxTextureWidth(),rend->getMaxTextureHeight());
@ -8137,6 +8284,7 @@ FurnaceGUI::FurnaceGUI():
displayEditString(false),
displayPendingSamples(false),
replacePendingSample(false),
displayExportingROM(false),
changeCoarse(false),
mobileEdit(false),
killGraphics(false),
@ -8150,9 +8298,6 @@ FurnaceGUI::FurnaceGUI():
vgmExportTrailingTicks(-1),
drawHalt(10),
zsmExportTickRate(60),
asmBaseLabel(""),
tiunaFirstBankSize(3072),
tiunaOtherBankSize(4096-48),
macroPointSize(16),
waveEditStyle(0),
displayInsTypeListMakeInsSample(-1),
@ -8613,7 +8758,12 @@ FurnaceGUI::FurnaceGUI():
curTutorial(-1),
curTutorialStep(0),
dmfExportVersion(0),
curExportType(GUI_EXPORT_NONE) {
curExportType(GUI_EXPORT_NONE),
romTarget(DIV_ROM_ABSTRACT),
romMultiFile(false),
romExportSave(false),
pendingExport(NULL),
romExportExists(false) {
// value keys
valueKeys[SDLK_0]=0;
valueKeys[SDLK_1]=1;
@ -8732,6 +8882,8 @@ FurnaceGUI::FurnaceGUI():
// effect sorting
memset(effectsShow,1,sizeof(bool)*10);
memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX);
strncpy(noteOffLabel,"OFF",32);
strncpy(noteRelLabel,"===",32);
strncpy(macroRelLabel,"REL",32);

View file

@ -599,7 +599,6 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_EXPORT_AUDIO_PER_CHANNEL,
GUI_FILE_EXPORT_VGM,
GUI_FILE_EXPORT_ZSM,
GUI_FILE_EXPORT_TIUNA,
GUI_FILE_EXPORT_CMDSTREAM,
GUI_FILE_EXPORT_TEXT,
GUI_FILE_EXPORT_ROM,
@ -651,10 +650,9 @@ enum FurnaceGUIExportTypes {
GUI_EXPORT_AUDIO=0,
GUI_EXPORT_VGM,
GUI_EXPORT_ROM,
GUI_EXPORT_ZSM,
GUI_EXPORT_TIUNA,
GUI_EXPORT_CMD_STREAM,
GUI_EXPORT_AMIGA_VAL,
GUI_EXPORT_TEXT,
GUI_EXPORT_DMF
};
@ -818,6 +816,7 @@ enum FurnaceGUIActions {
GUI_ACTION_PAT_LATCH,
GUI_ACTION_PAT_SCROLL_MODE,
GUI_ACTION_PAT_CLEAR_LATCH,
GUI_ACTION_PAT_ABSORB_INSTRUMENT,
GUI_ACTION_PAT_MAX,
GUI_ACTION_INS_LIST_MIN,
@ -1622,6 +1621,7 @@ class FurnaceGUI {
bool wantScrollListIns, wantScrollListWave, wantScrollListSample;
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString;
bool displayPendingSamples, replacePendingSample;
bool displayExportingROM;
bool changeCoarse;
bool mobileEdit;
bool killGraphics;
@ -1635,9 +1635,6 @@ class FurnaceGUI {
int cvHiScore;
int drawHalt;
int zsmExportTickRate;
String asmBaseLabel;
int tiunaFirstBankSize;
int tiunaOtherBankSize;
int macroPointSize;
int waveEditStyle;
int displayInsTypeListMakeInsSample;
@ -1963,6 +1960,7 @@ class FurnaceGUI {
unsigned int maxUndoSteps;
float vibrationStrength;
int vibrationLength;
int s3mOPL3;
String mainFontPath;
String headFontPath;
String patFontPath;
@ -2219,6 +2217,7 @@ class FurnaceGUI {
maxUndoSteps(100),
vibrationStrength(0.5f),
vibrationLength(20),
s3mOPL3(0),
mainFontPath(""),
headFontPath(""),
patFontPath(""),
@ -2682,6 +2681,17 @@ class FurnaceGUI {
int dmfExportVersion;
FurnaceGUIExportTypes curExportType;
// ROM export specific
DivROMExportOptions romTarget;
DivConfig romConfig;
bool romMultiFile;
bool romExportSave;
String romFilterName, romFilterExt;
String romExportPath;
DivROMExport* pendingExport;
bool romExportAvail[DIV_ROM_MAX];
bool romExportExists;
// user presets window
std::vector<int> selectedUserPreset;
@ -2689,9 +2699,8 @@ class FurnaceGUI {
void drawExportAudio(bool onWindow=false);
void drawExportVGM(bool onWindow=false);
void drawExportROM(bool onWindow=false);
void drawExportZSM(bool onWindow=false);
void drawExportTiuna(bool onWindow=false);
void drawExportAmigaVal(bool onWindow=false);
void drawExportText(bool onWindow=false);
void drawExportCommand(bool onWindow=false);
void drawExportDMF(bool onWindow=false);
@ -2731,6 +2740,7 @@ class FurnaceGUI {
bool portSet(String label, unsigned int portSetID, int ins, int outs, int activeIns, int activeOuts, int& clickedPort, std::map<unsigned int,ImVec2>& portPos);
void updateWindowTitle();
void updateROMExportAvail();
void autoDetectSystem();
void autoDetectSystemIter(std::vector<FurnaceGUISysDef>& category, bool& isMatch, std::map<DivSystem,int>& defCountMap, std::map<DivSystem,DivConfig>& defConfMap, std::map<DivSystem,int>& sysCountMap, std::map<DivSystem,DivConfig>& sysConfMap);
void prepareLayout();

View file

@ -687,6 +687,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("PAT_LATCH", _N("Set note input latch"), 0),
D("PAT_SCROLL_MODE", _N("Change mobile scroll mode"), 0),
D("PAT_CLEAR_LATCH", _N("Clear note input latch"), 0),
D("PAT_ABSORB_INSTRUMENT", _N("Set current instrument to channel's current instrument column"), 0),
D("PAT_MAX", "", NOT_AN_ACTION),
D("INS_LIST_MIN", _N("---Instrument list"), NOT_AN_ACTION),

View file

@ -7569,6 +7569,10 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc(_("Timer Num"),&ins->std.ex8Macro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER]));
macroList.push_back(FurnaceGUIMacroDesc(_("Timer Den"),&ins->std.fmsMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER]));
macroList.push_back(FurnaceGUIMacroDesc(_("PWM Boundary"),&ins->std.amsMacro,0,15,64,uiColors[GUI_COLOR_MACRO_OTHER]));
// workaround, because the gui will not set
// zoom or scroll if we're not in macros tab
ins->std.ex7Macro.vZoom=128;
ins->std.ex7Macro.vScroll=2048-64;
drawMacros(macroList,macroEditStateMacros);
ImGui::EndTabItem();
}

View file

@ -289,6 +289,7 @@ void FurnaceGUI::drawNewSong() {
selEnd=SelectionPoint();
cursor=SelectionPoint();
updateWindowTitle();
updateROMExportAvail();
ImGui::CloseCurrentPopup();
}

View file

@ -26,6 +26,8 @@
#include "imgui.h"
#include "imgui_internal.h"
#include "../ta-utils.h"
struct FurnacePlotArrayGetterData
{
const float* Values;
@ -270,12 +272,13 @@ int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx),
float lineHeight=ImGui::GetTextLineHeight()/2.0;
for (int i=0; i<bits && overlay_text[i]; i++) {
ImGui::PushStyleColor(ImGuiCol_Text,ImVec4(0,0,0,1.0f));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight-1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight+1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight-1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight+1), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
const char* text=_(overlay_text[i]);
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight-1), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x-1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x-1,frame_bb.Max.y+lineHeight+1), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight-1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight-1), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x+1, frame_bb.Min.y-lineHeight+1), ImVec2(frame_bb.Max.x+1,frame_bb.Max.y+lineHeight+1), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::PopStyleColor();
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y-lineHeight), ImVec2(frame_bb.Max.x,frame_bb.Max.y+lineHeight), overlay_text[i], NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
ImGui::RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y-lineHeight), ImVec2(frame_bb.Max.x,frame_bb.Max.y+lineHeight), text, NULL, NULL, ImVec2(0.0f, (0.5+double(bits-1-i))/double(bits)));
}
}

View file

@ -1254,6 +1254,14 @@ void FurnaceGUI::drawSettings() {
}
popDestColor();
// SUBSECTION IMPORT
CONFIG_SUBSECTION(_("Import"));
bool s3mOPL3B=settings.s3mOPL3;
if (ImGui::Checkbox(_("Use OPL3 instead of OPL2 for S3M import"),&s3mOPL3B)) {
settings.s3mOPL3=s3mOPL3B;
settingsChanged=true;
}
END_SECTION;
}
CONFIG_SECTION(_("Audio")) {
@ -2415,6 +2423,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_EXPAND_SONG);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_LATCH);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CLEAR_LATCH);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_ABSORB_INSTRUMENT);
KEYBIND_CONFIG_END;
ImGui::TreePop();
@ -4745,6 +4754,8 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
settings.vibrationStrength=conf.getFloat("vibrationStrength",0.5f);
settings.vibrationLength=conf.getInt("vibrationLength",20);
settings.s3mOPL3=conf.getInt("s3mOPL3",0);
settings.backupEnable=conf.getInt("backupEnable",1);
settings.backupInterval=conf.getInt("backupInterval",30);
settings.backupMaxCopies=conf.getInt("backupMaxCopies",5);
@ -5257,6 +5268,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
clampSetting(settings.backupMaxCopies,1,100);
clampSetting(settings.autoFillSave,0,1);
clampSetting(settings.autoMacroStepSize,0,1);
clampSetting(settings.s3mOPL3,0,1);
if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -5330,6 +5342,8 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
conf.set("vibrationStrength",settings.vibrationStrength);
conf.set("vibrationLength",settings.vibrationLength);
conf.set("s3mOPL3",settings.s3mOPL3);
conf.set("backupEnable",settings.backupEnable);
conf.set("backupInterval",settings.backupInterval);
conf.set("backupMaxCopies",settings.backupMaxCopies);

View file

@ -2482,7 +2482,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
break;
}
case DIV_SYSTEM_VERA: {
int chipType=flags.getInt("chipType",1);
int chipType=flags.getInt("chipType",2);
ImGui::Text(_("Chip revision:"));
ImGui::Indent();
@ -2494,6 +2494,10 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
chipType=1;
altered=true;
}
if (ImGui::RadioButton(_("V 47.0.2 (Tri/Saw PW XOR)"),chipType==2)) {
chipType=2;
altered=true;
}
ImGui::Unindent();
if (altered) {

View file

@ -91,6 +91,11 @@ void FurnaceGUI::drawSysManager() {
if (!e->duplicateSystem(i,sysDupCloneChannels,sysDupEnd)) {
showError(fmt::sprintf(_("cannot clone chip! (%s)"),e->getLastError()));
} else {
if (e->song.autoSystem) {
autoDetectSystem();
updateWindowTitle();
}
updateROMExportAvail();
MARK_MODIFIED;
}
}
@ -105,6 +110,7 @@ void FurnaceGUI::drawSysManager() {
autoDetectSystem();
}
updateWindowTitle();
updateROMExportAvail();
} else {
showError(fmt::sprintf(_("cannot change chip! (%s)"),e->getLastError()));
}
@ -143,6 +149,7 @@ void FurnaceGUI::drawSysManager() {
autoDetectSystem();
}
updateWindowTitle();
updateROMExportAvail();
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();

View file

@ -87,7 +87,6 @@ String outName;
String vgmOutName;
String zsmOutName;
String cmdOutName;
String tiunaOutName;
int benchMode=0;
int subsong=-1;
DivAudioExportOptions exportOptions;
@ -112,9 +111,6 @@ bool infoMode=false;
bool noReportError=false;
int tiunaFirstBankSize=3072;
int tiunaOtherBankSize=4096-48;
std::vector<TAParam> params;
#ifdef HAVE_LOCALE
@ -445,12 +441,6 @@ TAParamResult pCmdOut(String val) {
return TA_PARAM_SUCCESS;
}
TAParamResult pTiunaOut(String val) {
tiunaOutName=val;
e.setAudio(DIV_AUDIO_DUMMY);
return TA_PARAM_SUCCESS;
}
bool needsValue(String param) {
for (size_t i=0; i<params.size(); i++) {
if (params[i].name==param) {
@ -469,7 +459,6 @@ void initParams() {
params.push_back(TAParam("D","direct",false,pDirect,"","set VGM export direct stream mode"));
params.push_back(TAParam("Z","zsmout",true,pZSMOut,"<filename>","output .zsm data for Commander X16 Zsound"));
params.push_back(TAParam("C","cmdout",true,pCmdOut,"<filename>","output command stream"));
params.push_back(TAParam("T","tiunaout",true,pTiunaOut,"<filename>","output .asm data with TIunA sound data (TIA only)"));
params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)"));
params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (nothing by default)"));
params.push_back(TAParam("i","info",false,pInfo,"","get info about a song"));
@ -573,7 +562,6 @@ int main(int argc, char** argv) {
vgmOutName="";
zsmOutName="";
cmdOutName="";
tiunaOutName="";
// load config for locale
e.prePreInit();
@ -741,14 +729,14 @@ int main(int argc, char** argv) {
return 1;
}
if (fileName.empty() && (benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="")) {
if (fileName.empty() && (benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
logE("provide a file!");
return 1;
}
#ifdef HAVE_GUI
if (e.preInit(consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="")) {
if (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="") {
if (e.preInit(consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
if (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="") {
logW("engine wants safe mode, but Furnace GUI is not going to start.");
} else {
safeMode=true;
@ -760,7 +748,7 @@ int main(int argc, char** argv) {
}
#endif
if (safeMode && (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="")) {
if (safeMode && (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
logE("you can't use safe mode and console/export mode together.");
return 1;
}
@ -769,7 +757,7 @@ int main(int argc, char** argv) {
e.setAudio(DIV_AUDIO_DUMMY);
}
if (!fileName.empty() && ((!e.getConfBool("tutIntroPlayed",TUT_INTRO_PLAYED)) || e.getConfInt("alwaysPlayIntro",0)!=3 || consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="")) {
if (!fileName.empty() && ((!e.getConfBool("tutIntroPlayed",TUT_INTRO_PLAYED)) || e.getConfInt("alwaysPlayIntro",0)!=3 || consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
logI("loading module...");
FILE* f=ps_fopen(fileName.c_str(),"rb");
if (f==NULL) {
@ -861,7 +849,7 @@ int main(int argc, char** argv) {
return 0;
}
if (outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="") {
if (outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="") {
if (cmdOutName!="") {
SafeWriter* w=e.saveCommand();
if (w!=NULL) {
@ -911,22 +899,6 @@ int main(int argc, char** argv) {
reportError(fmt::sprintf(_("could not write ZSM! (%s)"),e.getLastError()));
}
}
if (tiunaOutName!="") {
SafeWriter* w=e.saveTiuna(NULL,"asmBaseLabel",tiunaFirstBankSize,tiunaOtherBankSize);
if (w!=NULL) {
FILE* f=ps_fopen(tiunaOutName.c_str(),"wb");
if (f!=NULL) {
fwrite(w->getFinalBuf(),1,w->size(),f);
fclose(f);
} else {
reportError(fmt::sprintf(_("could not open file! (%s)"),e.getLastError()));
}
w->finish();
delete w;
} else {
reportError(fmt::sprintf("could not write TIunA! (%s)",e.getLastError()));
}
}
if (outName!="") {
e.setConsoleMode(true);
e.saveAudio(outName.c_str(),exportOptions);