Merge branch 'master' of https://github.com/tildearrow/furnace into ymf278b
This commit is contained in:
commit
3e1e2fc2a6
73 changed files with 1657 additions and 663 deletions
|
|
@ -46,7 +46,9 @@ void* TAAudioSDL::getContext() {
|
|||
bool TAAudioSDL::quit() {
|
||||
if (!initialized) return false;
|
||||
|
||||
SDL_CloseAudioDevice(ai);
|
||||
if (ai!=0) {
|
||||
SDL_CloseAudioDevice(ai);
|
||||
}
|
||||
|
||||
if (running) {
|
||||
running=false;
|
||||
|
|
|
|||
|
|
@ -34,5 +34,6 @@ class TAAudioSDL: public TAAudio {
|
|||
std::vector<String> listAudioDevices();
|
||||
bool init(TAAudioDesc& request, TAAudioDesc& response);
|
||||
TAAudioSDL():
|
||||
ai(0),
|
||||
audioSysStarted(false) {}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -215,6 +215,10 @@ bool DivCSPlayer::tick() {
|
|||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
arg0=(short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||
arg0=(short)stream.readS();
|
||||
arg1=(short)stream.readS();
|
||||
break;
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
arg0=(unsigned char)stream.readC();
|
||||
if (arg0==0xff) {
|
||||
|
|
@ -321,6 +325,11 @@ bool DivCSPlayer::tick() {
|
|||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
chan[i].volSpeed=arg0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||
chan[i].volSpeed=arg0;
|
||||
chan[i].volSpeedTarget=arg0==0 ? -1 : arg1;
|
||||
break;
|
||||
case DIV_CMD_HINT_PITCH:
|
||||
chan[i].pitch=arg0;
|
||||
|
|
@ -356,13 +365,29 @@ bool DivCSPlayer::tick() {
|
|||
|
||||
if (sendVolume || chan[i].volSpeed!=0) {
|
||||
chan[i].volume+=chan[i].volSpeed;
|
||||
if (chan[i].volSpeedTarget!=-1) {
|
||||
bool atTarget=false;
|
||||
if (chan[i].volSpeed>0) {
|
||||
atTarget=(chan[i].volume>=chan[i].volSpeedTarget);
|
||||
} else if (chan[i].volSpeed<0) {
|
||||
atTarget=(chan[i].volume<=chan[i].volSpeedTarget);
|
||||
} else {
|
||||
atTarget=true;
|
||||
chan[i].volSpeedTarget=chan[i].volume;
|
||||
}
|
||||
|
||||
if (atTarget) {
|
||||
chan[i].volume=chan[i].volSpeedTarget;
|
||||
chan[i].volSpeed=0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
}
|
||||
}
|
||||
if (chan[i].volume<0) {
|
||||
chan[i].volume=0;
|
||||
}
|
||||
if (chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
}
|
||||
|
||||
e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ struct DivCSChannelState {
|
|||
int lastWaitLen;
|
||||
|
||||
int note, pitch;
|
||||
int volume, volMax, volSpeed;
|
||||
int volume, volMax, volSpeed, volSpeedTarget;
|
||||
int vibratoDepth, vibratoRate, vibratoPos;
|
||||
int portaTarget, portaSpeed;
|
||||
unsigned char arp, arpStage, arpTicks;
|
||||
|
|
@ -56,6 +56,7 @@ struct DivCSChannelState {
|
|||
volume(0x7f00),
|
||||
volMax(0),
|
||||
volSpeed(0),
|
||||
volSpeedTarget(-1),
|
||||
vibratoDepth(0),
|
||||
vibratoRate(0),
|
||||
vibratoPos(0),
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
|||
case DIV_CMD_HINT_VOLUME:
|
||||
case DIV_CMD_HINT_PORTA:
|
||||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||
case DIV_CMD_HINT_LEGATO:
|
||||
w->writeC((unsigned char)c.cmd+0xb4);
|
||||
break;
|
||||
|
|
@ -100,6 +101,10 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
|||
case DIV_CMD_HINT_VOL_SLIDE:
|
||||
w->writeS(c.value);
|
||||
break;
|
||||
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||
w->writeS(c.value);
|
||||
w->writeS(c.value2);
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_MODE:
|
||||
case DIV_CMD_SAMPLE_FREQ:
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ enum DivDispatchCmds {
|
|||
DIV_CMD_HINT_ARPEGGIO, // (note1, note2)
|
||||
DIV_CMD_HINT_VOLUME, // (vol)
|
||||
DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick)
|
||||
DIV_CMD_HINT_VOL_SLIDE_TARGET, // (amount, target)
|
||||
DIV_CMD_HINT_PORTA, // (target, speed)
|
||||
DIV_CMD_HINT_LEGATO, // (note)
|
||||
|
||||
|
|
@ -265,6 +266,8 @@ enum DivDispatchCmds {
|
|||
|
||||
DIV_CMD_FDS_MOD_AUTO,
|
||||
|
||||
DIV_CMD_FM_OPMASK, // (mask)
|
||||
|
||||
DIV_CMD_MULTIPCM_MIX_FM, // (value)
|
||||
DIV_CMD_MULTIPCM_MIX_PCM, // (value)
|
||||
DIV_CMD_MULTIPCM_LFO, // (value)
|
||||
|
|
|
|||
|
|
@ -98,6 +98,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
|
|||
break;
|
||||
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
||||
return _("Cxxx: Set tick rate (hz)");
|
||||
case 0xd3:
|
||||
return _("D3xx: Volume portamento");
|
||||
case 0xd4:
|
||||
return _("D4xx: Volume portamento (fast)");
|
||||
case 0xdc:
|
||||
return _("DCxx: Delayed mute");
|
||||
case 0xe0:
|
||||
|
|
@ -199,6 +203,12 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivEngine::findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, int& length) {
|
||||
if (curSubSong!=NULL) {
|
||||
curSubSong->findLength(loopOrder,loopRow,fadeoutLen,rowsForFadeout,hasFFxx,orders,song.grooves,length,chans,song.jumpTreatment,song.ignoreJumpAtEnd);
|
||||
}
|
||||
}
|
||||
|
||||
#define EXPORT_BUFSIZE 2048
|
||||
|
||||
double DivEngine::benchmarkPlayback() {
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ struct DivAudioExportOptions {
|
|||
struct DivChannelState {
|
||||
std::vector<DivDelayedCommand> delayed;
|
||||
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
|
||||
int volume, volSpeed, cut, volCut, legatoDelay, legatoTarget, rowDelay, volMax;
|
||||
int volume, volSpeed, volSpeedTarget, cut, volCut, legatoDelay, legatoTarget, rowDelay, volMax;
|
||||
int delayOrder, delayRow, retrigSpeed, retrigTick;
|
||||
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoShape, vibratoFine;
|
||||
int tremoloDepth, tremoloRate, tremoloPos;
|
||||
|
|
@ -157,6 +157,7 @@ struct DivChannelState {
|
|||
portaNote(-1),
|
||||
volume(0x7f00),
|
||||
volSpeed(0),
|
||||
volSpeedTarget(-1),
|
||||
cut(-1),
|
||||
volCut(-1),
|
||||
legatoDelay(-1),
|
||||
|
|
@ -474,7 +475,7 @@ class DivEngine {
|
|||
int midiOutTimeRate;
|
||||
float midiVolExp;
|
||||
int softLockCount;
|
||||
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
|
||||
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, curExportChan, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
|
||||
size_t curSubSongIndex;
|
||||
size_t bufferPos;
|
||||
double divider;
|
||||
|
|
@ -498,6 +499,7 @@ class DivEngine {
|
|||
DivAudioExportModes exportMode;
|
||||
DivAudioExportFormats exportFormat;
|
||||
double exportFadeOut;
|
||||
bool isFadingOut;
|
||||
int exportOutputs;
|
||||
bool exportChannelMask[DIV_MAX_CHANS];
|
||||
DivConfig conf;
|
||||
|
|
@ -579,7 +581,7 @@ class DivEngine {
|
|||
void processRow(int i, bool afterDelay);
|
||||
void nextOrder();
|
||||
void nextRow();
|
||||
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream);
|
||||
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable);
|
||||
// returns true if end of song.
|
||||
bool nextTick(bool noAccum=false, bool inhibitLowLat=false);
|
||||
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);
|
||||
|
|
@ -669,6 +671,7 @@ class DivEngine {
|
|||
friend class DivROMExport;
|
||||
friend class DivExportAmigaValidation;
|
||||
friend class DivExportTiuna;
|
||||
friend class DivExportZSM;
|
||||
|
||||
public:
|
||||
DivSong song;
|
||||
|
|
@ -721,8 +724,6 @@ class DivEngine {
|
|||
// - -1 to auto-determine trailing
|
||||
// - -2 to add a whole loop of trailing
|
||||
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.
|
||||
|
|
@ -817,6 +818,9 @@ class DivEngine {
|
|||
// find song loop position
|
||||
void walkSong(int& loopOrder, int& loopRow, int& loopEnd);
|
||||
|
||||
// find song length in rows (up to specified loop point), and find length of every order
|
||||
void findSongLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, int& length);
|
||||
|
||||
// play (returns whether successful)
|
||||
bool play();
|
||||
|
||||
|
|
@ -1008,6 +1012,24 @@ class DivEngine {
|
|||
// is exporting
|
||||
bool isExporting();
|
||||
|
||||
// get how many loops is left
|
||||
void getLoopsLeft(int& loops);
|
||||
|
||||
// get how many loops in total export needs to do
|
||||
void getTotalLoops(int& loops);
|
||||
|
||||
// get current position in song
|
||||
void getCurSongPos(int& row, int& order);
|
||||
|
||||
// get how many files export needs to create
|
||||
void getTotalAudioFiles(int& files);
|
||||
|
||||
// get which file is processed right now (progress for e.g. per-channel export)
|
||||
void getCurFileIndex(int& file);
|
||||
|
||||
// get fadeout state
|
||||
bool getIsFadingOut();
|
||||
|
||||
// add instrument
|
||||
int addInstrument(int refChan=0, DivInstrumentType fallbackType=DIV_INS_STD);
|
||||
|
||||
|
|
@ -1401,6 +1423,7 @@ class DivEngine {
|
|||
totalLoops(0),
|
||||
lastLoopPos(0),
|
||||
exportLoopCount(0),
|
||||
curExportChan(0),
|
||||
nextSpeed(3),
|
||||
elapsedBars(0),
|
||||
elapsedBeats(0),
|
||||
|
|
@ -1439,6 +1462,7 @@ class DivEngine {
|
|||
exportMode(DIV_EXPORT_MODE_ONE),
|
||||
exportFormat(DIV_EXPORT_FORMAT_S16),
|
||||
exportFadeOut(0.0),
|
||||
isFadingOut(false),
|
||||
exportOutputs(2),
|
||||
cmdStreamInt(NULL),
|
||||
midiBaseChan(0),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include "export/amigaValidation.h"
|
||||
#include "export/tiuna.h"
|
||||
#include "export/zsm.h"
|
||||
|
||||
DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
|
||||
DivROMExport* exporter=NULL;
|
||||
|
|
@ -31,6 +32,9 @@ DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
|
|||
case DIV_ROM_TIUNA:
|
||||
exporter=new DivExportTiuna;
|
||||
break;
|
||||
case DIV_ROM_ZSM:
|
||||
exporter=new DivExportZSM;
|
||||
break;
|
||||
default:
|
||||
exporter=new DivROMExport;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -18,9 +18,77 @@
|
|||
*/
|
||||
|
||||
#include "zsm.h"
|
||||
#include "../engine.h"
|
||||
#include "../ta-log.h"
|
||||
#include "../utfutils.h"
|
||||
#include "song.h"
|
||||
#include <fmt/printf.h>
|
||||
|
||||
/// DivZSM definitions
|
||||
|
||||
#define ZSM_HEADER_SIZE 16
|
||||
#define ZSM_VERSION 1
|
||||
#define ZSM_YM_CMD 0x40
|
||||
#define ZSM_DELAY_CMD 0x80
|
||||
#define ZSM_YM_MAX_WRITES 63
|
||||
#define ZSM_SYNC_MAX_WRITES 31
|
||||
#define ZSM_DELAY_MAX 127
|
||||
#define ZSM_EOF ZSM_DELAY_CMD
|
||||
|
||||
#define ZSM_EXT ZSM_YM_CMD
|
||||
#define ZSM_EXT_PCM 0x00
|
||||
#define ZSM_EXT_CHIP 0x40
|
||||
#define ZSM_EXT_SYNC 0x80
|
||||
#define ZSM_EXT_CUSTOM 0xC0
|
||||
|
||||
enum YM_STATE { ym_PREV, ym_NEW, ym_STATES };
|
||||
enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES };
|
||||
|
||||
class DivZSM {
|
||||
private:
|
||||
struct S_pcmInst {
|
||||
int geometry;
|
||||
unsigned int offset, length, loopPoint;
|
||||
bool isLooped;
|
||||
};
|
||||
SafeWriter* w;
|
||||
int ymState[ym_STATES][256];
|
||||
int psgState[psg_STATES][64];
|
||||
int pcmRateCache;
|
||||
int pcmCtrlRVCache;
|
||||
int pcmCtrlDCCache;
|
||||
unsigned int pcmLoopPointCache;
|
||||
bool pcmIsLooped;
|
||||
std::vector<DivRegWrite> ymwrites;
|
||||
std::vector<DivRegWrite> pcmMeta;
|
||||
std::vector<unsigned char> pcmData;
|
||||
std::vector<unsigned char> pcmCache;
|
||||
std::vector<S_pcmInst> pcmInsts;
|
||||
std::vector<DivRegWrite> syncCache;
|
||||
int loopOffset;
|
||||
int numWrites;
|
||||
int ticks;
|
||||
int tickRate;
|
||||
int ymMask;
|
||||
int psgMask;
|
||||
bool optimize;
|
||||
public:
|
||||
DivZSM();
|
||||
~DivZSM();
|
||||
void init(unsigned int rate = 60);
|
||||
int getoffset();
|
||||
void writeYM(unsigned char a, unsigned char v);
|
||||
void writePSG(unsigned char a, unsigned char v);
|
||||
void writePCM(unsigned char a, unsigned char v);
|
||||
void writeSync(unsigned char a, unsigned char v);
|
||||
void setOptimize(bool o);
|
||||
void tick(int numticks = 1);
|
||||
void setLoopPoint();
|
||||
SafeWriter* finish();
|
||||
private:
|
||||
void flushWrites();
|
||||
void flushTicks();
|
||||
};
|
||||
|
||||
/// DivZSM implementation
|
||||
|
||||
DivZSM::DivZSM() {
|
||||
w=NULL;
|
||||
|
|
@ -447,3 +515,240 @@ void DivZSM::flushTicks() {
|
|||
}
|
||||
ticks=0;
|
||||
}
|
||||
|
||||
/// ZSM export
|
||||
|
||||
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
||||
constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0;
|
||||
|
||||
void DivExportZSM::run() {
|
||||
// settings
|
||||
unsigned int zsmrate=conf.getInt("zsmrate",60);
|
||||
bool loop=conf.getBool("loop",true);
|
||||
bool optimize=conf.getBool("optimize",true);
|
||||
|
||||
// system IDs
|
||||
int VERA=-1;
|
||||
int YM=-1;
|
||||
int IGNORED=0;
|
||||
|
||||
// find indexes for YM and VERA. Ignore other systems.
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
switch (e->song.system[i]) {
|
||||
case DIV_SYSTEM_VERA:
|
||||
if (VERA>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
VERA=i;
|
||||
logAppendf("VERA detected as chip id %d",i);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2151:
|
||||
if (YM>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
YM=i;
|
||||
logAppendf("YM detected as chip id %d",i);
|
||||
break;
|
||||
default:
|
||||
IGNORED++;
|
||||
logAppendf("Ignoring chip %d systemID %d",i,(int)e->song.system[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (VERA<0 && YM<0) {
|
||||
logAppend("ERROR: No supported systems for ZSM");
|
||||
failed=true;
|
||||
running=false;
|
||||
return;
|
||||
}
|
||||
if (IGNORED>0) {
|
||||
logAppendf("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
|
||||
}
|
||||
|
||||
DivZSM zsm;
|
||||
|
||||
e->stop();
|
||||
e->repeatPattern=false;
|
||||
e->setOrder(0);
|
||||
e->synchronizedSoft([&]() {
|
||||
double origRate=e->got.rate;
|
||||
e->got.rate=zsmrate&0xffff;
|
||||
|
||||
// determine loop point
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
logAppendf("loop point: %d %d",loopOrder,loopRow);
|
||||
|
||||
zsm.init(zsmrate);
|
||||
|
||||
// reset the playback state
|
||||
e->curOrder=0;
|
||||
e->freelance=false;
|
||||
e->playing=false;
|
||||
e->extValuePresent=false;
|
||||
e->remainingLoops=-1;
|
||||
|
||||
// Prepare to write song data
|
||||
e->playSub(false);
|
||||
//size_t tickCount=0;
|
||||
bool done=false;
|
||||
bool loopNow=false;
|
||||
int loopPos=-1;
|
||||
int fracWait=0; // accumulates fractional ticks
|
||||
if (VERA>=0) e->disCont[VERA].dispatch->toggleRegisterDump(true);
|
||||
if (YM>=0) {
|
||||
e->disCont[YM].dispatch->toggleRegisterDump(true);
|
||||
// emit LFO initialization commands
|
||||
zsm.writeYM(0x18,0); // freq=0
|
||||
zsm.writeYM(0x19,0x7F); // AMD =7F
|
||||
zsm.writeYM(0x19,0xFF); // PMD =7F
|
||||
// TODO: incorporate the Furnace meta-command for init data and filter
|
||||
// out writes to otherwise-unused channels.
|
||||
}
|
||||
// Indicate the song's tuning as a sync meta-event
|
||||
// specified in terms of how many 1/256th semitones
|
||||
// the song is offset from standard A-440 tuning.
|
||||
// This is mainly to benefit visualizations in players
|
||||
// for non-standard tunings so that they can avoid
|
||||
// displaying the entire song held in pitch bend.
|
||||
// Tunings offsets that exceed a half semitone
|
||||
// will simply be represented in a different key
|
||||
// by nature of overflowing the signed char value
|
||||
signed char tuningoffset=(signed char)(round(3072*(log(e->song.tuning/440.0)/log(2))))&0xff;
|
||||
zsm.writeSync(0x01,tuningoffset);
|
||||
// Set optimize flag, which mainly buffers PSG writes
|
||||
// whenever the channel is silent
|
||||
zsm.setOptimize(optimize);
|
||||
|
||||
while (!done) {
|
||||
if (loopPos==-1) {
|
||||
if (loopOrder==e->curOrder && loopRow==e->curRow && loop)
|
||||
loopNow=true;
|
||||
if (loopNow) {
|
||||
// If Virtual Tempo is in use, our exact loop point
|
||||
// might be skipped due to quantization error.
|
||||
// If this happens, the tick immediately following is our loop point.
|
||||
if (e->ticks==1 || !(loopOrder==e->curOrder && loopRow==e->curRow)) {
|
||||
loopPos=zsm.getoffset();
|
||||
zsm.setLoopPoint();
|
||||
loopNow=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (e->nextTick() || !e->playing) {
|
||||
done=true;
|
||||
if (!loop) {
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
e->disCont[i].dispatch->getRegisterWrites().clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!e->playing) {
|
||||
loopPos=-1;
|
||||
}
|
||||
}
|
||||
// get register dumps
|
||||
for (int j=0; j<2; j++) {
|
||||
int i=0;
|
||||
// dump YM writes first
|
||||
if (j==0) {
|
||||
if (YM<0) {
|
||||
continue;
|
||||
} else {
|
||||
i=YM;
|
||||
}
|
||||
}
|
||||
// dump VERA writes second
|
||||
if (j==1) {
|
||||
if (VERA<0) {
|
||||
continue;
|
||||
} else {
|
||||
i=VERA;
|
||||
}
|
||||
}
|
||||
std::vector<DivRegWrite>& writes=e->disCont[i].dispatch->getRegisterWrites();
|
||||
if (writes.size()>0)
|
||||
logD("zsmOps: Writing %d messages to chip %d",writes.size(),i);
|
||||
for (DivRegWrite& write: writes) {
|
||||
if (i==YM) zsm.writeYM(write.addr&0xff,write.val);
|
||||
if (i==VERA) {
|
||||
if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead
|
||||
zsm.writePSG(write.addr&0xff,write.val);
|
||||
}
|
||||
}
|
||||
writes.clear();
|
||||
}
|
||||
|
||||
// write wait
|
||||
int totalWait=e->cycles>>MASTER_CLOCK_PREC;
|
||||
fracWait+=e->cycles&MASTER_CLOCK_MASK;
|
||||
totalWait+=fracWait>>MASTER_CLOCK_PREC;
|
||||
fracWait&=MASTER_CLOCK_MASK;
|
||||
if (totalWait>0 && !done) {
|
||||
zsm.tick(totalWait);
|
||||
//tickCount+=totalWait;
|
||||
}
|
||||
}
|
||||
// end of song
|
||||
|
||||
// done - close out.
|
||||
e->got.rate=origRate;
|
||||
if (VERA>=0) e->disCont[VERA].dispatch->toggleRegisterDump(false);
|
||||
if (YM>=0) e->disCont[YM].dispatch->toggleRegisterDump(false);
|
||||
|
||||
e->remainingLoops=-1;
|
||||
e->playing=false;
|
||||
e->freelance=false;
|
||||
e->extValuePresent=false;
|
||||
});
|
||||
|
||||
progress[0].amount=1.0f;
|
||||
|
||||
logAppend("finished!");
|
||||
|
||||
output.push_back(DivROMExportOutput("out.zsm",zsm.finish()));
|
||||
running=false;
|
||||
}
|
||||
|
||||
/// DivExpottZSM - FRONTEND
|
||||
|
||||
bool DivExportZSM::go(DivEngine* eng) {
|
||||
progress[0].name="Generate";
|
||||
progress[0].amount=0.0f;
|
||||
|
||||
e=eng;
|
||||
running=true;
|
||||
failed=false;
|
||||
mustAbort=false;
|
||||
exportThread=new std::thread(&DivExportZSM::run,this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivExportZSM::wait() {
|
||||
if (exportThread!=NULL) {
|
||||
exportThread->join();
|
||||
delete exportThread;
|
||||
}
|
||||
}
|
||||
|
||||
void DivExportZSM::abort() {
|
||||
mustAbort=true;
|
||||
wait();
|
||||
}
|
||||
|
||||
bool DivExportZSM::isRunning() {
|
||||
return running;
|
||||
}
|
||||
|
||||
bool DivExportZSM::hasFailed() {
|
||||
return failed;
|
||||
}
|
||||
|
||||
DivROMExportProgress DivExportZSM::getProgress(int index) {
|
||||
if (index<0 || index>1) return progress[1];
|
||||
return progress[index];
|
||||
}
|
||||
38
src/engine/export/zsm.h
Normal file
38
src/engine/export/zsm.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2024 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "../export.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
class DivExportZSM: public DivROMExport {
|
||||
DivEngine* e;
|
||||
std::thread* exportThread;
|
||||
DivROMExportProgress progress[2];
|
||||
bool running, failed, mustAbort;
|
||||
void run();
|
||||
public:
|
||||
bool go(DivEngine* e);
|
||||
bool isRunning();
|
||||
bool hasFailed();
|
||||
void abort();
|
||||
void wait();
|
||||
DivROMExportProgress getProgress(int index=0);
|
||||
~DivExportZSM() {}
|
||||
};
|
||||
|
|
@ -401,8 +401,8 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
|
|||
sample->loop=false;
|
||||
}
|
||||
|
||||
if (sample->centerRate<4000) sample->centerRate=4000;
|
||||
if (sample->centerRate>64000) sample->centerRate=64000;
|
||||
if (sample->centerRate<100) sample->centerRate=100;
|
||||
if (sample->centerRate>384000) sample->centerRate=384000;
|
||||
sfWrap.doClose();
|
||||
BUSY_END;
|
||||
ret.push_back(sample);
|
||||
|
|
|
|||
|
|
@ -367,6 +367,141 @@ void DivInstrument::writeFeatureFM(SafeWriter* w, bool fui) {
|
|||
FEATURE_END;
|
||||
}
|
||||
|
||||
bool MemPatch::calcDiff(const void* pre, const void* post, size_t inputSize) {
|
||||
bool diffValid=false;
|
||||
size_t firstDiff=0;
|
||||
size_t lastDiff=0;
|
||||
const unsigned char* preBytes=(const unsigned char*)pre;
|
||||
const unsigned char* postBytes=(const unsigned char*)post;
|
||||
|
||||
// @NOTE: consider/profile using a memcmp==0 check to early-out, if it's potentially faster
|
||||
// for the common case, which is no change
|
||||
for (size_t ii=0; ii<inputSize; ++ii) {
|
||||
if (preBytes[ii] != postBytes[ii]) {
|
||||
lastDiff=ii;
|
||||
firstDiff=diffValid ? firstDiff : ii;
|
||||
diffValid=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (diffValid) {
|
||||
offset=firstDiff;
|
||||
size=lastDiff - firstDiff + 1;
|
||||
data=new unsigned char[size];
|
||||
|
||||
// the diff is to make pre into post (MemPatch is general, not specific to
|
||||
// undo), so copy from postBytes
|
||||
memcpy(data, postBytes+offset, size);
|
||||
}
|
||||
|
||||
return diffValid;
|
||||
}
|
||||
|
||||
void MemPatch::applyAndReverse(void* target, size_t targetSize) {
|
||||
if (size==0) return;
|
||||
if (offset+size>targetSize) {
|
||||
logW("MemPatch (offset %d, size %d) exceeds target size (%d), can't apply!",offset,size,targetSize);
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned char* targetBytes=(unsigned char*)target;
|
||||
|
||||
// swap this->data and its segment on target
|
||||
for (size_t ii=0; ii<size; ++ii) {
|
||||
unsigned char tmp=targetBytes[offset+ii];
|
||||
targetBytes[offset+ii] = data[ii];
|
||||
data[ii] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
void DivInstrumentUndoStep::applyAndReverse(DivInstrument* target) {
|
||||
if (nameValid) {
|
||||
name.swap(target->name);
|
||||
}
|
||||
podPatch.applyAndReverse((DivInstrumentPOD*)target, sizeof(DivInstrumentPOD));
|
||||
}
|
||||
|
||||
bool DivInstrumentUndoStep::makeUndoPatch(size_t processTime_, const DivInstrument* pre, const DivInstrument* post) {
|
||||
processTime=processTime_;
|
||||
|
||||
// create the patch that will make post into pre
|
||||
podPatch.calcDiff((const DivInstrumentPOD*)post, (const DivInstrumentPOD*)pre, sizeof(DivInstrumentPOD));
|
||||
if (pre->name!=post->name) {
|
||||
nameValid=true;
|
||||
name=pre->name;
|
||||
}
|
||||
|
||||
return nameValid || podPatch.isValid();
|
||||
}
|
||||
|
||||
bool DivInstrument::recordUndoStepIfChanged(size_t processTime, const DivInstrument* old) {
|
||||
DivInstrumentUndoStep step;
|
||||
|
||||
// generate a patch to go back to old
|
||||
if (step.makeUndoPatch(processTime, old, this)) {
|
||||
|
||||
// make room
|
||||
if (undoHist.size()>=undoHist.capacity()) {
|
||||
delete undoHist.front();
|
||||
undoHist.pop_front();
|
||||
}
|
||||
|
||||
// clear redo
|
||||
while (!redoHist.empty()) {
|
||||
delete redoHist.back();
|
||||
redoHist.pop_back();
|
||||
}
|
||||
|
||||
DivInstrumentUndoStep* stepPtr=new DivInstrumentUndoStep;
|
||||
*stepPtr=step;
|
||||
step.podPatch.data=NULL; // don't let it delete the data ptr that's been copied!
|
||||
undoHist.push_back(stepPtr);
|
||||
|
||||
// logI("DivInstrument::undoHist push (%u off, %u size)", stepPtr->podPatch.offset, stepPtr->podPatch.size);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int DivInstrument::undo() {
|
||||
if (undoHist.empty()) return 0;
|
||||
|
||||
DivInstrumentUndoStep* step=undoHist.back();
|
||||
undoHist.pop_back();
|
||||
// logI("DivInstrument::undo (%u off, %u size)", step->podPatch.offset, step->podPatch.size);
|
||||
step->applyAndReverse(this);
|
||||
|
||||
// make room
|
||||
if (redoHist.size()>=redoHist.capacity()) {
|
||||
DivInstrumentUndoStep* step=redoHist.front();
|
||||
delete step;
|
||||
redoHist.pop_front();
|
||||
}
|
||||
redoHist.push_back(step);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DivInstrument::redo() {
|
||||
if (redoHist.empty()) return 0;
|
||||
|
||||
DivInstrumentUndoStep* step = redoHist.back();
|
||||
redoHist.pop_back();
|
||||
// logI("DivInstrument::redo (%u off, %u size)", step->podPatch.offset, step->podPatch.size);
|
||||
step->applyAndReverse(this);
|
||||
|
||||
// make room
|
||||
if (undoHist.size()>=undoHist.capacity()) {
|
||||
DivInstrumentUndoStep* step=undoHist.front();
|
||||
delete step;
|
||||
undoHist.pop_front();
|
||||
}
|
||||
undoHist.push_back(step);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivInstrument::writeMacro(SafeWriter* w, const DivInstrumentMacro& m) {
|
||||
if (!m.len) return;
|
||||
|
||||
|
|
@ -3345,3 +3480,28 @@ bool DivInstrument::saveDMP(const char* path) {
|
|||
w->finish();
|
||||
return true;
|
||||
}
|
||||
|
||||
DivInstrument::~DivInstrument() {
|
||||
// free undoHist/redoHist
|
||||
while (!undoHist.empty()) {
|
||||
delete undoHist.back();
|
||||
undoHist.pop_back();
|
||||
}
|
||||
while (!redoHist.empty()) {
|
||||
delete redoHist.back();
|
||||
redoHist.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
DivInstrument::DivInstrument( const DivInstrument& ins ) {
|
||||
// undo/redo history is specifically not copied
|
||||
*(DivInstrumentPOD*)this=ins;
|
||||
name=ins.name;
|
||||
}
|
||||
|
||||
DivInstrument& DivInstrument::operator=( const DivInstrument& ins ) {
|
||||
// undo/redo history is specifically not copied
|
||||
*(DivInstrumentPOD*)this=ins;
|
||||
name=ins.name;
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -23,8 +23,10 @@
|
|||
#include "dataErrors.h"
|
||||
#include "../ta-utils.h"
|
||||
#include "../pch.h"
|
||||
#include "../fixedQueue.h"
|
||||
|
||||
struct DivSong;
|
||||
struct DivInstrument;
|
||||
|
||||
// NOTICE!
|
||||
// before adding new instrument types to this struct, please ask me first.
|
||||
|
|
@ -865,8 +867,7 @@ struct DivInstrumentSID2 {
|
|||
noiseMode(0) {}
|
||||
};
|
||||
|
||||
struct DivInstrument {
|
||||
String name;
|
||||
struct DivInstrumentPOD {
|
||||
DivInstrumentType type;
|
||||
DivInstrumentFM fm;
|
||||
DivInstrumentSTD std;
|
||||
|
|
@ -885,6 +886,77 @@ struct DivInstrument {
|
|||
DivInstrumentPowerNoise powernoise;
|
||||
DivInstrumentSID2 sid2;
|
||||
|
||||
DivInstrumentPOD() :
|
||||
type(DIV_INS_FM) {
|
||||
}
|
||||
};
|
||||
|
||||
struct MemPatch {
|
||||
MemPatch() :
|
||||
data(NULL)
|
||||
, offset(0)
|
||||
, size(0) {
|
||||
}
|
||||
|
||||
~MemPatch() {
|
||||
if (data) {
|
||||
delete[] data;
|
||||
data=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool calcDiff(const void* pre, const void* post, size_t size);
|
||||
void applyAndReverse(void* target, size_t inputSize);
|
||||
bool isValid() const { return size>0; }
|
||||
|
||||
unsigned char* data;
|
||||
size_t offset;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct DivInstrumentUndoStep {
|
||||
DivInstrumentUndoStep() :
|
||||
name(""),
|
||||
nameValid(false),
|
||||
processTime(0) {
|
||||
}
|
||||
|
||||
MemPatch podPatch;
|
||||
String name;
|
||||
bool nameValid;
|
||||
size_t processTime;
|
||||
|
||||
void applyAndReverse(DivInstrument* target);
|
||||
bool makeUndoPatch(size_t processTime_, const DivInstrument* pre, const DivInstrument* post);
|
||||
};
|
||||
|
||||
struct DivInstrument : DivInstrumentPOD {
|
||||
String name;
|
||||
|
||||
DivInstrument() :
|
||||
name("") {
|
||||
// clear and construct DivInstrumentPOD so it doesn't have any garbage in the padding
|
||||
memset((unsigned char*)(DivInstrumentPOD*)this, 0, sizeof(DivInstrumentPOD));
|
||||
new ((DivInstrumentPOD*)this) DivInstrumentPOD;
|
||||
}
|
||||
|
||||
~DivInstrument();
|
||||
|
||||
/**
|
||||
* copy/assignment to specifically avoid leaking or dangling pointers to undo step
|
||||
*/
|
||||
DivInstrument( const DivInstrument& ins );
|
||||
DivInstrument& operator=( const DivInstrument& ins );
|
||||
|
||||
/**
|
||||
* undo stuff
|
||||
*/
|
||||
FixedQueue<DivInstrumentUndoStep*, 128> undoHist;
|
||||
FixedQueue<DivInstrumentUndoStep*, 128> redoHist;
|
||||
bool recordUndoStepIfChanged(size_t processTime, const DivInstrument* old);
|
||||
int undo();
|
||||
int redo();
|
||||
|
||||
/**
|
||||
* these are internal functions.
|
||||
*/
|
||||
|
|
@ -969,9 +1041,5 @@ struct DivInstrument {
|
|||
* @return whether it was successful.
|
||||
*/
|
||||
bool saveDMP(const char* path);
|
||||
DivInstrument():
|
||||
name(""),
|
||||
type(DIV_INS_FM) {
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -761,6 +761,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
immWrite(0x19,0x80|pmDepth);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -423,11 +423,11 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
chan[i].tfx.counter = 0;
|
||||
chan[i].tfx.out = 0;
|
||||
if (chan[i].nextPSGMode.val&8) {
|
||||
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
|
||||
//if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
|
||||
if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
|
||||
if (dumpWrites) {
|
||||
rWrite(0x08+i,0);
|
||||
addWrite(0xffff0000+(i<<8),chan[i].dac.sample);
|
||||
//addWrite(0xffff0000+(i<<8),chan[i].dac.sample);
|
||||
}
|
||||
if (chan[i].dac.setPos) {
|
||||
chan[i].dac.setPos=false;
|
||||
|
|
@ -517,7 +517,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
chan[i].dac.rate=((double)rate*((sunsoft||clockSel)?8.0:16.0))/(double)(MAX(1,off*chan[i].freq));
|
||||
if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate);
|
||||
//if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate);
|
||||
}
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
|
|
@ -604,12 +604,12 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
}
|
||||
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].dac.sample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
break;
|
||||
} else {
|
||||
if (dumpWrites) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
//addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
}
|
||||
}
|
||||
if (chan[c.chan].dac.setPos) {
|
||||
|
|
@ -637,10 +637,10 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
chan[c.chan].dac.sample=12*sampleBank+chan[c.chan].note%12;
|
||||
if (chan[c.chan].dac.sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].dac.sample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
break;
|
||||
} else {
|
||||
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
//if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
|
||||
}
|
||||
if (chan[c.chan].dac.setPos) {
|
||||
chan[c.chan].dac.setPos=false;
|
||||
|
|
@ -651,7 +651,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*2048;
|
||||
if (dumpWrites) {
|
||||
rWrite(0x08+c.chan,0);
|
||||
addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate);
|
||||
//addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate);
|
||||
}
|
||||
chan[c.chan].dac.furnaceDAC=false;
|
||||
}
|
||||
|
|
@ -686,7 +686,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].dac.sample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
chan[c.chan].nextPSGMode.val&=~8;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].active=false;
|
||||
|
|
@ -867,7 +867,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
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);
|
||||
//if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dac.pos);
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
|
|
|
|||
|
|
@ -1463,6 +1463,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
if (c.chan>=6) break;
|
||||
chan[c.chan].hardReset=c.value;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ struct _nla_table nla_table;
|
|||
|
||||
#define CHIP_DIVIDER 16
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite((a),v)); if (dumpWrites) {addWrite((a),v);} }
|
||||
|
||||
const char* regCheatSheetNES[]={
|
||||
"S0Volume", "4000",
|
||||
|
|
@ -86,10 +86,10 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
|||
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \
|
||||
if (dacAntiClickOn && dacAntiClick<next) { \
|
||||
dacAntiClick+=8; \
|
||||
rWrite(0x4011,dacAntiClick); \
|
||||
doWrite(0x4011,dacAntiClick); \
|
||||
} else { \
|
||||
dacAntiClickOn=false; \
|
||||
rWrite(0x4011,next); \
|
||||
doWrite(0x4011,next); \
|
||||
} \
|
||||
} \
|
||||
dacPos++; \
|
||||
|
|
@ -108,6 +108,13 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
|||
void DivPlatformNES::acquire_puNES(short** buf, size_t len) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
doPCM;
|
||||
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
doWrite(w.addr,w.val);
|
||||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
apu_tick(nes,NULL);
|
||||
nes->apu.odd_cycle=!nes->apu.odd_cycle;
|
||||
|
|
@ -134,6 +141,13 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) {
|
|||
int out2[2];
|
||||
for (size_t i=0; i<len; i++) {
|
||||
doPCM;
|
||||
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
doWrite(w.addr,w.val);
|
||||
regPool[w.addr&0x1f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
nes1_NP->Tick(8);
|
||||
nes2_NP->TickFrameSequence(8);
|
||||
|
|
@ -297,7 +311,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
if (chan[i].sweepChanged) {
|
||||
chan[i].sweepChanged=false;
|
||||
if (i==0) {
|
||||
//rWrite(16+i*5,chan[i].sweep);
|
||||
// rWrite(16+i*5,chan[i].sweep);
|
||||
}
|
||||
}
|
||||
if (i<3) if (chan[i].std.phaseReset.had) {
|
||||
|
|
@ -338,7 +352,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
//rWrite(16+i*5+2,8);
|
||||
// rWrite(16+i*5+2,8);
|
||||
if (i==2) { // triangle
|
||||
rWrite(0x4000+i*4,0x00);
|
||||
} else {
|
||||
|
|
@ -400,6 +414,34 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
logV("switching bank to %d",dpcmBank);
|
||||
if (dumpWrites) addWrite(0xffff0004,dpcmBank);
|
||||
}
|
||||
|
||||
// sample custom loop point...
|
||||
DivSample* lsamp=parent->getSample(dacSample);
|
||||
|
||||
// how it works:
|
||||
// when the initial sample info is written (see above) and playback is launched,
|
||||
// the parameters (start point in memory and length) are locked until sample end
|
||||
// is reached.
|
||||
|
||||
// thus, if we write new data after just several APU clock cycles, it will be used only when
|
||||
// sample finishes one full loop.
|
||||
|
||||
// thus we can write sample's loop point as "start address" and sample's looped part length
|
||||
// as "full sample length".
|
||||
|
||||
// APU will play full sample once and then repeatedly cycle through the looped part.
|
||||
|
||||
// sources:
|
||||
// https://www.nesdev.org/wiki/APU_DMC
|
||||
// https://www.youtube.com/watch?v=vB4P8x2Am6Y
|
||||
|
||||
if (lsamp->loopEnd>lsamp->loopStart && goingToLoop) {
|
||||
int loopStartAddr=(sampleOffDPCM[dacSample]+lsamp->loopStart)>>3;
|
||||
int loopLen=(lsamp->loopEnd-lsamp->loopStart)>>3;
|
||||
|
||||
rWrite(0x4012,(loopStartAddr>>6)&0xff);
|
||||
rWrite(0x4013,(loopLen>>4)&0xff);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (nextDPCMFreq>=0) {
|
||||
|
|
@ -790,6 +832,7 @@ float DivPlatformNES::getPostAmp() {
|
|||
}
|
||||
|
||||
void DivPlatformNES::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
for (int i=0; i<5; i++) {
|
||||
chan[i]=DivPlatformNES::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include "sound/nes_nsfplay/nes_apu.h"
|
||||
#include "sound/nes_nsfplay/5e01_apu.h"
|
||||
#include "../../fixedQueue.h"
|
||||
|
||||
class DivPlatformNES: public DivDispatch {
|
||||
struct Channel: public SharedChannel<signed char> {
|
||||
|
|
@ -44,6 +45,13 @@ class DivPlatformNES: public DivDispatch {
|
|||
Channel chan[5];
|
||||
DivDispatchOscBuffer* oscBuf[5];
|
||||
bool isMuted[5];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
QueuedWrite(): addr(0), val(0) {}
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
FixedQueue<QueuedWrite,128> writes;
|
||||
int dacPeriod, dacRate, dpcmPos;
|
||||
unsigned int dacPos, dacAntiClick;
|
||||
int dacSample;
|
||||
|
|
|
|||
|
|
@ -165,8 +165,12 @@ int DivPlatformSMS::snCalcFreq(int ch) {
|
|||
if (ch==3) CHIP_DIVIDER=noiseDivider;
|
||||
int easyStartingPeriod=16;
|
||||
int easyThreshold=round(128.0*12.0*log((chipClock/(easyStartingPeriod*CHIP_DIVIDER))/(0.0625*parent->song.tuning))/log(2.0))-384+64;
|
||||
if (parent->song.linearPitch==2 && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(easyThreshold)) {
|
||||
int ret=(((easyStartingPeriod<<7))-(chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2-(easyThreshold)))>>7;
|
||||
int curFreq=chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2+(chan[ch].arpOff<<7);
|
||||
if (chan[ch].fixedArp) {
|
||||
curFreq=chan[ch].baseNoteOverride<<7;
|
||||
}
|
||||
if (parent->song.linearPitch==2 && easyNoise && curFreq>easyThreshold) {
|
||||
int ret=(((easyStartingPeriod<<7))-(curFreq-(easyThreshold)))>>7;
|
||||
if (ret<0) ret=0;
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -841,6 +841,8 @@ void DivPlatformSNES::reset() {
|
|||
memcpy(sampleMem,copyOfSampleMem,65536);
|
||||
dsp.init(sampleMem);
|
||||
dsp.set_output(NULL,0);
|
||||
dsp.setupInterpolation(!interpolationOff);
|
||||
|
||||
memset(regPool,0,128);
|
||||
// this can't be 0 or channel 1 won't play
|
||||
// this can't be 0x100 either as that's used by SPC700 page 1 and the stack
|
||||
|
|
@ -1023,6 +1025,8 @@ void DivPlatformSNES::setFlags(const DivConfig& flags) {
|
|||
initEchoFIR[7]=flags.getInt("echoFilter7",0);
|
||||
|
||||
initEchoMask=flags.getInt("echoMask",0);
|
||||
|
||||
interpolationOff=flags.getBool("interpolationOff",false);
|
||||
}
|
||||
|
||||
int DivPlatformSNES::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ class DivPlatformSNES: public DivDispatch {
|
|||
bool writeEcho;
|
||||
bool writeDryVol;
|
||||
bool echoOn;
|
||||
bool interpolationOff;
|
||||
|
||||
bool initEchoOn;
|
||||
signed char initEchoVolL;
|
||||
|
|
|
|||
|
|
@ -135,24 +135,30 @@ static short const gauss [512] =
|
|||
1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,
|
||||
};
|
||||
|
||||
void SPC_DSP::setupInterpolation(bool interpolate){for(int i=0;i<voice_count;i++){m.voices[i].interpolate=interpolate;}}
|
||||
|
||||
inline int SPC_DSP::interpolate( voice_t const* v )
|
||||
{
|
||||
// Make pointers into gaussian based on fractional position between samples
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
if (v->interpolate) {
|
||||
int offset = v->interp_pos >> 4 & 0xFF;
|
||||
short const* fwd = gauss + 255 - offset;
|
||||
short const* rev = gauss + offset; // mirror left half of gaussian
|
||||
|
||||
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
|
||||
int out;
|
||||
out = (fwd [ 0] * in [0]) >> 11;
|
||||
out += (fwd [256] * in [1]) >> 11;
|
||||
out += (rev [256] * in [2]) >> 11;
|
||||
out = (int16_t) out;
|
||||
out += (rev [ 0] * in [3]) >> 11;
|
||||
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
|
||||
int out;
|
||||
out = (fwd [ 0] * in [0]) >> 11;
|
||||
out += (fwd [256] * in [1]) >> 11;
|
||||
out += (rev [256] * in [2]) >> 11;
|
||||
out = (int16_t) out;
|
||||
out += (rev [ 0] * in [3]) >> 11;
|
||||
|
||||
CLAMP16( out );
|
||||
out &= ~1;
|
||||
return out;
|
||||
CLAMP16( out );
|
||||
out &= ~1;
|
||||
return out;
|
||||
} else {
|
||||
return v->buf [(v->interp_pos >> 12) + v->buf_pos]; //Furnace addition -- no interpolation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ public:
|
|||
// output buffer could hold.
|
||||
int sample_count() const;
|
||||
|
||||
// Furnace addition: disable/enable Gaussian interpolation
|
||||
void setupInterpolation(bool interpolate);
|
||||
|
||||
// Emulation
|
||||
|
||||
// Resets DSP to power-on state
|
||||
|
|
@ -122,6 +125,7 @@ public:
|
|||
int hidden_env; // used by GAIN mode 7, very obscure quirk
|
||||
uint8_t t_envx_out;
|
||||
sample_t out[2]; // Furnace addition, for per-channel oscilloscope
|
||||
bool interpolate; // Furnace addition, to disable interpolation
|
||||
};
|
||||
|
||||
// Furnace addition, gets a voice
|
||||
|
|
|
|||
|
|
@ -858,6 +858,9 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
|||
immWrite(0x17,0x80|pmDepth);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
// TODO: if OPZ supports op mask
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1004,6 +1004,13 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1537,6 +1537,13 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1507,6 +1507,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1576,6 +1576,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_OPMASK:
|
||||
if (c.chan>=psgChanOffs) break;
|
||||
chan[c.chan].opMask=c.value&15;
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].opMaskChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_FM_HARD_RESET:
|
||||
chan[c.chan].hardReset=c.value;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ const char* cmdName[]={
|
|||
"HINT_ARPEGGIO",
|
||||
"HINT_VOLUME",
|
||||
"HINT_VOL_SLIDE",
|
||||
"HINT_VOL_SLIDE_TARGET",
|
||||
"HINT_PORTA",
|
||||
"HINT_LEGATO",
|
||||
|
||||
|
|
@ -265,6 +266,8 @@ const char* cmdName[]={
|
|||
|
||||
"FDS_MOD_AUTO",
|
||||
|
||||
"FM_OPMASK",
|
||||
|
||||
"MULTIPCM_MIX_FM",
|
||||
"MULTIPCM_MIX_PCM",
|
||||
"MULTIPCM_LFO",
|
||||
|
|
@ -655,7 +658,23 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
}
|
||||
|
||||
// volume
|
||||
if (pat->data[whatRow][3]!=-1) {
|
||||
int volPortaTarget=-1;
|
||||
bool noApplyVolume=false;
|
||||
for (int j=0; j<curPat[i].effectCols; j++) {
|
||||
short effect=pat->data[whatRow][4+(j<<1)];
|
||||
if (effect==0xd3 || effect==0xd4) { // vol porta
|
||||
volPortaTarget=pat->data[whatRow][3]<<8; // can be -256
|
||||
|
||||
short effectVal=pat->data[whatRow][5+(j<<1)];
|
||||
if (effectVal==-1) effectVal=0;
|
||||
effectVal&=255;
|
||||
|
||||
noApplyVolume=effectVal>0; // "D3.." or "D300" shouldn't stop volume from applying
|
||||
break; // technically you could have both D3 and D4... let's not care
|
||||
}
|
||||
}
|
||||
|
||||
if (pat->data[whatRow][3]!=-1 && !noApplyVolume) {
|
||||
if (!song.oldAlwaysSetVolume || disCont[dispatchOfChan[i]].dispatch->getLegacyAlwaysSetVolume() || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->data[whatRow][3]) {
|
||||
if (pat->data[whatRow][0]==0 && pat->data[whatRow][1]==0) {
|
||||
chan[i].midiAftertouch=true;
|
||||
|
|
@ -844,6 +863,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0x06: // vol slide + porta
|
||||
|
|
@ -885,6 +905,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0x07: // tremolo
|
||||
|
|
@ -895,6 +916,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].tremoloRate=effectVal>>4;
|
||||
if (chan[i].tremoloDepth!=0) {
|
||||
chan[i].volSpeed=0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
} else {
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
|
|
@ -914,6 +936,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0x00: // arpeggio
|
||||
|
|
@ -956,6 +979,22 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].cutType=0;
|
||||
}
|
||||
break;
|
||||
case 0xd3: // volume portamento (vol porta)
|
||||
// tremolo and vol slides are incompatible
|
||||
chan[i].tremoloDepth=0;
|
||||
chan[i].tremoloRate=0;
|
||||
chan[i].volSpeed=volPortaTarget<0 ? 0 : volPortaTarget>chan[i].volume ? effectVal : -effectVal;
|
||||
chan[i].volSpeedTarget=chan[i].volSpeed==0 ? -1 : volPortaTarget;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE_TARGET,i,chan[i].volSpeed,chan[i].volSpeedTarget));
|
||||
break;
|
||||
case 0xd4: // volume portamento fast (vol porta fast)
|
||||
// tremolo and vol slides are incompatible
|
||||
chan[i].tremoloDepth=0;
|
||||
chan[i].tremoloRate=0;
|
||||
chan[i].volSpeed=volPortaTarget<0 ? 0 : volPortaTarget>chan[i].volume ? 256*effectVal : -256*effectVal;
|
||||
chan[i].volSpeedTarget=chan[i].volSpeed==0 ? -1 : volPortaTarget;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE_TARGET,i,chan[i].volSpeed,chan[i].volSpeedTarget));
|
||||
break;
|
||||
case 0xe0: // arp speed
|
||||
if (effectVal>0) {
|
||||
curSubSong->arpLen=effectVal;
|
||||
|
|
@ -1093,6 +1132,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].tremoloDepth=0;
|
||||
chan[i].tremoloRate=0;
|
||||
chan[i].volSpeed=effectVal;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0xf4: // fine volume ramp down
|
||||
|
|
@ -1100,6 +1140,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
chan[i].tremoloDepth=0;
|
||||
chan[i].tremoloRate=0;
|
||||
chan[i].volSpeed=-effectVal;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
case 0xf5: // disable macro
|
||||
|
|
@ -1113,12 +1154,14 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
break;
|
||||
case 0xf8: // single volume ramp up
|
||||
chan[i].volSpeed=0; // add compat flag?
|
||||
chan[i].volSpeedTarget=-1;
|
||||
chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax);
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
break;
|
||||
case 0xf9: // single volume ramp down
|
||||
chan[i].volSpeed=0; // add compat flag?
|
||||
chan[i].volSpeedTarget=-1;
|
||||
chan[i].volume=MAX(chan[i].volume-effectVal*256,0);
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
|
|
@ -1136,9 +1179,9 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
|||
} else {
|
||||
chan[i].volSpeed=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||
break;
|
||||
|
||||
case 0xfc: // delayed note release
|
||||
if (song.delayBehavior==2 || effectVal<nextSpeed) {
|
||||
chan[i].cut=effectVal+1;
|
||||
|
|
@ -1606,9 +1649,30 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
if (chan[i].volSpeed!=0) {
|
||||
chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8);
|
||||
chan[i].volume+=chan[i].volSpeed;
|
||||
if (chan[i].volSpeedTarget!=-1) {
|
||||
bool atTarget=false;
|
||||
if (chan[i].volSpeed>0) {
|
||||
atTarget=(chan[i].volume>=chan[i].volSpeedTarget);
|
||||
} else if (chan[i].volSpeed<0) {
|
||||
atTarget=(chan[i].volume<=chan[i].volSpeedTarget);
|
||||
} else {
|
||||
atTarget=true;
|
||||
chan[i].volSpeedTarget=chan[i].volume;
|
||||
}
|
||||
|
||||
if (atTarget) {
|
||||
chan[i].volume=chan[i].volSpeedTarget;
|
||||
chan[i].volSpeed=0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
|
||||
}
|
||||
}
|
||||
if (chan[i].volume>chan[i].volMax) {
|
||||
chan[i].volume=chan[i].volMax;
|
||||
chan[i].volSpeed=0;
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
|
||||
|
|
@ -1620,6 +1684,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
|||
} else {
|
||||
chan[i].volume=0;
|
||||
}
|
||||
chan[i].volSpeedTarget=-1;
|
||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "sfWrapper.h"
|
||||
#include "../fileutils.h"
|
||||
#include "../ta-log.h"
|
||||
#include "sndfile.h"
|
||||
|
||||
sf_count_t _vioGetSize(void* user) {
|
||||
|
|
@ -80,6 +81,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
vio.seek=_vioSeek;
|
||||
vio.tell=_vioTell;
|
||||
vio.write=_vioWrite;
|
||||
logV("SFWrapper: opening %s",path);
|
||||
|
||||
const char* modeC="rb";
|
||||
if (mode==SFM_WRITE) {
|
||||
|
|
@ -91,10 +93,12 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
|
||||
f=ps_fopen(path,modeC);
|
||||
if (f==NULL) {
|
||||
logE("SFWrapper: failed to open (%s)",strerror(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (fseek(f,0,SEEK_END)==-1) {
|
||||
logE("SFWrapper: failed to seek to end (%s)",strerror(errno));
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
return NULL;
|
||||
|
|
@ -102,6 +106,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
|
||||
len=ftell(f);
|
||||
if (len==(SIZE_MAX>>1)) {
|
||||
logE("SFWrapper: failed to tell (%s)",strerror(errno));
|
||||
len=0;
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
|
|
@ -109,6 +114,7 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
}
|
||||
|
||||
if (fseek(f,0,SEEK_SET)==-1) {
|
||||
logE("SFWrapper: failed to seek to beginning (%s)",strerror(errno));
|
||||
len=0;
|
||||
fclose(f);
|
||||
f=NULL;
|
||||
|
|
@ -117,5 +123,8 @@ SNDFILE* SFWrapper::doOpen(const char* path, int mode, SF_INFO* sfinfo) {
|
|||
|
||||
sf=sf_open_virtual(&vio,mode,sfinfo,this);
|
||||
if (sf!=NULL) fileMode=mode;
|
||||
if (sf==NULL) {
|
||||
logE("SFWrapper: WHY IS IT NULL?!");
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,173 @@ bool DivSubSong::walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int
|
|||
return false;
|
||||
}
|
||||
|
||||
double calcRowLenInSeconds(const DivGroovePattern& speeds, float hz, int vN, int vD, int timeBaseFromSong) {
|
||||
double hl=1; //count for 1 row
|
||||
if (hl<=0.0) hl=4.0;
|
||||
double timeBase=timeBaseFromSong+1;
|
||||
double speedSum=0;
|
||||
for (int i=0; i<MIN(16,speeds.len); i++) {
|
||||
speedSum+=speeds.val[i];
|
||||
}
|
||||
speedSum/=MAX(1,speeds.len);
|
||||
if (timeBase<1.0) timeBase=1.0;
|
||||
if (speedSum<1.0) speedSum=1.0;
|
||||
if (vD<1) vD=1;
|
||||
return 1.0/((60.0*hz/(timeBase*hl*speedSum))*(double)vN/(double)vD/60.0);
|
||||
}
|
||||
|
||||
void DivSubSong::findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders_vec, std::vector<DivGroovePattern>& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat) {
|
||||
length=0;
|
||||
hasFFxx=false;
|
||||
rowsForFadeout=0;
|
||||
|
||||
float secondsPerThisRow=0.0f;
|
||||
|
||||
DivGroovePattern curSpeeds=speeds; //simulate that we are playing the song, track all speed/BPM/tempo/engine rate changes
|
||||
short curVirtualTempoN=virtualTempoN;
|
||||
short curVirtualTempoD=virtualTempoD;
|
||||
float curHz=hz;
|
||||
double curDivider=(double)timeBase;
|
||||
|
||||
double curLen=0.0; //how many seconds passed since the start of song
|
||||
|
||||
int nextOrder=-1;
|
||||
int nextRow=0;
|
||||
int effectVal=0;
|
||||
int lastSuspectedLoopEnd=-1;
|
||||
DivPattern* subPat[DIV_MAX_CHANS];
|
||||
unsigned char wsWalked[8192];
|
||||
memset(wsWalked,0,8192);
|
||||
if (firstPat>0) {
|
||||
memset(wsWalked,255,32*firstPat);
|
||||
}
|
||||
for (int i=firstPat; i<ordersLen; i++) {
|
||||
bool jumped=false;
|
||||
|
||||
for (int j=0; j<chans; j++) {
|
||||
subPat[j]=pat[j].getPattern(orders.ord[j][i],false);
|
||||
}
|
||||
if (i>lastSuspectedLoopEnd) {
|
||||
lastSuspectedLoopEnd=i;
|
||||
}
|
||||
for (int j=nextRow; j<patLen; j++) {
|
||||
nextRow=0;
|
||||
bool changingOrder=false;
|
||||
bool jumpingOrder=false;
|
||||
if (wsWalked[((i<<5)+(j>>3))&8191]&(1<<(j&7))) {
|
||||
return;
|
||||
}
|
||||
for (int k=0; k<chans; k++) {
|
||||
for (int l=0; l<pat[k].effectCols; l++) {
|
||||
effectVal=subPat[k]->data[j][5+(l<<1)];
|
||||
if (effectVal<0) effectVal=0;
|
||||
|
||||
if (subPat[k]->data[j][4+(l<<1)]==0xff) {
|
||||
hasFFxx=true;
|
||||
|
||||
// FFxx makes YOU SHALL NOT PASS!!! move
|
||||
orders_vec.push_back(j+1); // order len
|
||||
length+=j+1; // add length of order to song length
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (subPat[k]->data[j][4+(l<<1)]) {
|
||||
case 0x09: { // select groove pattern/speed 1
|
||||
if (grooves.empty()) {
|
||||
if (effectVal>0) curSpeeds.val[0]=effectVal;
|
||||
} else {
|
||||
if (effectVal<(short)grooves.size()) {
|
||||
curSpeeds=grooves[effectVal];
|
||||
//curSpeed=0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x0f: { // speed 1/speed 2
|
||||
if (curSpeeds.len==2 && grooves.empty()) {
|
||||
if (effectVal>0) curSpeeds.val[1]=effectVal;
|
||||
} else {
|
||||
if (effectVal>0) curSpeeds.val[0]=effectVal;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0xfd: { // virtual tempo num
|
||||
if (effectVal>0) curVirtualTempoN=effectVal;
|
||||
break;
|
||||
}
|
||||
case 0xfe: { // virtual tempo den
|
||||
if (effectVal>0) curVirtualTempoD=effectVal;
|
||||
break;
|
||||
}
|
||||
case 0xf0: { // set Hz by tempo (set bpm)
|
||||
curDivider=(double)effectVal*2.0/5.0;
|
||||
if (curDivider<1) curDivider=1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (subPat[k]->data[j][4+(l<<1)]==0x0d) {
|
||||
if (jumpTreatment==2) {
|
||||
if ((i<ordersLen-1 || !ignoreJumpAtEnd)) {
|
||||
nextOrder=i+1;
|
||||
nextRow=effectVal;
|
||||
jumpingOrder=true;
|
||||
}
|
||||
} else if (jumpTreatment==1) {
|
||||
if (nextOrder==-1 && (i<ordersLen-1 || !ignoreJumpAtEnd)) {
|
||||
nextOrder=i+1;
|
||||
nextRow=effectVal;
|
||||
jumpingOrder=true;
|
||||
}
|
||||
} else {
|
||||
if ((i<ordersLen-1 || !ignoreJumpAtEnd)) {
|
||||
if (!changingOrder) {
|
||||
nextOrder=i+1;
|
||||
}
|
||||
jumpingOrder=true;
|
||||
nextRow=effectVal;
|
||||
}
|
||||
}
|
||||
} else if (subPat[k]->data[j][4+(l<<1)]==0x0b) {
|
||||
if (nextOrder==-1 || jumpTreatment==0) {
|
||||
nextOrder=effectVal;
|
||||
if (jumpTreatment==1 || jumpTreatment==2 || !jumpingOrder) {
|
||||
nextRow=0;
|
||||
}
|
||||
changingOrder=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (i>loopOrder || (i==loopOrder && j>loopRow)) {
|
||||
// we count each row fadeout lasts. When our time is greater than fadeout length we successfully counted the number of fadeout rows
|
||||
if (curLen<=fadeoutLen && fadeoutLen>0.0) {
|
||||
secondsPerThisRow=calcRowLenInSeconds(speeds,curHz,curVirtualTempoN,curVirtualTempoD,curDivider);
|
||||
curLen+=secondsPerThisRow;
|
||||
rowsForFadeout++;
|
||||
}
|
||||
}
|
||||
|
||||
wsWalked[((i<<5)+(j>>3))&8191]|=1<<(j&7);
|
||||
|
||||
if (nextOrder!=-1) {
|
||||
i=nextOrder-1;
|
||||
orders_vec.push_back(j+1); // order len
|
||||
length+=j+1; // add length of order to song length
|
||||
jumped=true;
|
||||
nextOrder=-1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!jumped) { // if no jump occured we add full pattern length
|
||||
orders_vec.push_back(patLen); // order len
|
||||
length+=patLen; // add length of order to song length
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivSubSong::clearData() {
|
||||
for (int i=0; i<DIV_MAX_CHANS; i++) {
|
||||
pat[i].wipePatterns();
|
||||
|
|
|
|||
|
|
@ -185,6 +185,11 @@ struct DivSubSong {
|
|||
*/
|
||||
bool walk(int& loopOrder, int& loopRow, int& loopEnd, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0);
|
||||
|
||||
/**
|
||||
* find song length in rows (up to specified loop point).
|
||||
*/
|
||||
void findLength(int loopOrder, int loopRow, double fadeoutLen, int& rowsForFadeout, bool& hasFFxx, std::vector<int>& orders, std::vector<DivGroovePattern>& grooves, int& length, int chans, int jumpTreatment, int ignoreJumpAtEnd, int firstPat=0);
|
||||
|
||||
void clearData();
|
||||
void optimizePatterns();
|
||||
void rearrangePatterns();
|
||||
|
|
|
|||
|
|
@ -504,6 +504,7 @@ void DivEngine::registerSystems() {
|
|||
{0x5d, {DIV_CMD_FM_D2R, _("5Dxx: Set decay 2 of operator 2 (0 to 1F)"), constVal<1>, effectValAnd<31>}},
|
||||
{0x5e, {DIV_CMD_FM_D2R, _("5Exx: Set decay 2 of operator 3 (0 to 1F)"), constVal<2>, effectValAnd<31>}},
|
||||
{0x5f, {DIV_CMD_FM_D2R, _("5Fxx: Set decay 2 of operator 4 (0 to 1F)"), constVal<3>, effectValAnd<31>}},
|
||||
{0x60, {DIV_CMD_FM_OPMASK, _("60xx: Set operator mask (bits 0-3)")}},
|
||||
};
|
||||
|
||||
EffectHandlerMap fmOPMPostEffectHandlerMap(fmOPNPostEffectHandlerMap);
|
||||
|
|
@ -514,6 +515,7 @@ void DivEngine::registerSystems() {
|
|||
{0x1e, {DIV_CMD_FM_AM_DEPTH, _("1Exx: Set AM depth (0 to 7F)"), effectValAnd<127>}},
|
||||
{0x1f, {DIV_CMD_FM_PM_DEPTH, _("1Fxx: Set PM depth (0 to 7F)"), effectValAnd<127>}},
|
||||
{0x55, {DIV_CMD_FM_SSG, _("55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)"), effectOpVal<4>, effectValAnd<3>}},
|
||||
{0x60, {DIV_CMD_FM_OPMASK, _("60xx: Set operator mask (bits 0-3)")}},
|
||||
});
|
||||
|
||||
EffectHandlerMap fmOPZPostEffectHandlerMap(fmOPMPostEffectHandlerMap);
|
||||
|
|
|
|||
|
|
@ -24,7 +24,9 @@
|
|||
|
||||
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
||||
|
||||
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream) {
|
||||
// this function is so long
|
||||
// may as well make it something else
|
||||
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable) {
|
||||
unsigned char baseAddr1=isSecond?0xa0:0x50;
|
||||
unsigned char baseAddr2=isSecond?0x80:0;
|
||||
unsigned short baseAddr2S=isSecond?0x8000:0;
|
||||
|
|
@ -737,6 +739,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID);
|
||||
switch (write.addr&0xff) {
|
||||
case 0: // play sample
|
||||
sampleStoppable[streamID]=true;
|
||||
if (write.val<(unsigned int)song.sampleLen) {
|
||||
if (playingSample[streamID]!=(int)write.val) {
|
||||
pendingFreq[streamID]=write.val;
|
||||
|
|
@ -775,6 +778,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
}
|
||||
break;
|
||||
case 1: { // set sample freq
|
||||
sampleStoppable[streamID]=true;
|
||||
int realFreq=write.val;
|
||||
if (realFreq<0) realFreq=0;
|
||||
if (realFreq>44100) realFreq=44100;
|
||||
|
|
@ -818,11 +822,14 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
|
|||
break;
|
||||
}
|
||||
case 2: // stop sample
|
||||
w->writeC(0x94);
|
||||
w->writeC(streamID);
|
||||
loopSample[streamID]=-1;
|
||||
playingSample[streamID]=-1;
|
||||
pendingFreq[streamID]=-1;
|
||||
if (sampleStoppable[streamID]) {
|
||||
w->writeC(0x94);
|
||||
w->writeC(streamID);
|
||||
loopSample[streamID]=-1;
|
||||
playingSample[streamID]=-1;
|
||||
pendingFreq[streamID]=-1;
|
||||
sampleStoppable[streamID]=false;
|
||||
}
|
||||
break;
|
||||
case 3: // set sample direction
|
||||
sampleDir[streamID]=write.val;
|
||||
|
|
@ -1321,6 +1328,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
bool sampleDir[DIV_MAX_CHANS];
|
||||
int pendingFreq[DIV_MAX_CHANS];
|
||||
int playingSample[DIV_MAX_CHANS];
|
||||
bool sampleStoppable[DIV_MAX_CHANS];
|
||||
int setPos[DIV_MAX_CHANS];
|
||||
std::vector<unsigned int> chipVol;
|
||||
std::vector<DivDelayedWrite> delayedWrites[DIV_MAX_CHIPS];
|
||||
|
|
@ -1343,6 +1351,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
playingSample[i]=-1;
|
||||
setPos[i]=0;
|
||||
sampleDir[i]=false;
|
||||
sampleStoppable[i]=true;
|
||||
}
|
||||
|
||||
bool writeDACSamples=false;
|
||||
|
|
@ -2274,8 +2283,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
w->writeI(0);
|
||||
w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
|
||||
}
|
||||
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
||||
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
||||
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage(1)>0) {
|
||||
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage(1)+0xffff)&(~0xffff);
|
||||
if (blockSize > 0x1000000) {
|
||||
blockSize = 0x1000000;
|
||||
}
|
||||
|
|
@ -2283,7 +2292,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
w->writeC(0x66);
|
||||
w->writeC(0x8F);
|
||||
w->writeI((blockSize+8)|(i*0x80000000));
|
||||
w->writeI(writeQSound[i]->getSampleMemCapacity());
|
||||
w->writeI(writeQSound[i]->getSampleMemCapacity(1));
|
||||
w->writeI(0);
|
||||
w->write(writeQSound[i]->getSampleMem(),blockSize);
|
||||
}
|
||||
|
|
@ -2638,7 +2647,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
for (int i=0; i<song.systemLen; i++) {
|
||||
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
|
||||
for (DivRegWrite& j: writes) {
|
||||
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i],directStream);
|
||||
performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i],directStream,sampleStoppable);
|
||||
writeCount++;
|
||||
}
|
||||
writes.clear();
|
||||
|
|
@ -2678,7 +2687,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
lastOne=i.second.time;
|
||||
}
|
||||
// write write
|
||||
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream);
|
||||
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream,sampleStoppable);
|
||||
// handle global Furnace commands
|
||||
|
||||
writeCount++;
|
||||
|
|
@ -2794,7 +2803,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
|||
w->writeWString(ws,false); // japanese author name
|
||||
w->writeS(0); // date
|
||||
w->writeWString(L"Furnace (chiptune tracker)",false); // ripper
|
||||
w->writeS(0); // notes
|
||||
ws=utf8To16(song.notes.c_str());
|
||||
w->writeWString(ws,false); // notes
|
||||
|
||||
int gd3Len=w->tell()-gd3Off-12;
|
||||
|
||||
|
|
|
|||
|
|
@ -33,11 +33,78 @@ bool DivEngine::isExporting() {
|
|||
return exporting;
|
||||
}
|
||||
|
||||
void DivEngine::getLoopsLeft(int &loops) {
|
||||
if (totalLoops<0 || exportLoopCount==0) {
|
||||
loops=0;
|
||||
return;
|
||||
}
|
||||
loops=exportLoopCount-1-totalLoops;
|
||||
}
|
||||
|
||||
void DivEngine::getTotalLoops(int &loops) {
|
||||
loops=exportLoopCount-1;
|
||||
}
|
||||
|
||||
void DivEngine::getCurSongPos(int &row, int &order) {
|
||||
row=curRow;
|
||||
order=curOrder;
|
||||
}
|
||||
|
||||
void DivEngine::getTotalAudioFiles(int &files) {
|
||||
files=0;
|
||||
|
||||
switch (exportMode) {
|
||||
case DIV_EXPORT_MODE_ONE: {
|
||||
files=1;
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_SYS: {
|
||||
files=1; // there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_CHAN: {
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (exportChannelMask[i]) {
|
||||
files++;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void DivEngine::getCurFileIndex(int &file) {
|
||||
file=0;
|
||||
|
||||
switch (exportMode) {
|
||||
case DIV_EXPORT_MODE_ONE: {
|
||||
file=0;
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_SYS: {
|
||||
file=0; // there actually are several files but they are processed in the same loop, so to correctly draw progress we think of them as one file
|
||||
break;
|
||||
}
|
||||
case DIV_EXPORT_MODE_MANY_CHAN: {
|
||||
file=curExportChan;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool DivEngine::getIsFadingOut() {
|
||||
return isFadingOut;
|
||||
}
|
||||
|
||||
#ifdef HAVE_SNDFILE
|
||||
void DivEngine::runExportThread() {
|
||||
size_t fadeOutSamples=got.rate*exportFadeOut;
|
||||
size_t curFadeOutSample=0;
|
||||
bool isFadingOut=false;
|
||||
isFadingOut=false;
|
||||
|
||||
switch (exportMode) {
|
||||
case DIV_EXPORT_MODE_ONE: {
|
||||
|
|
@ -140,7 +207,11 @@ void DivEngine::runExportThread() {
|
|||
sf[i]=NULL;
|
||||
si[i].samplerate=got.rate;
|
||||
si[i].channels=disCont[i].dispatch->getOutputCount();
|
||||
si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
|
||||
if (exportFormat==DIV_EXPORT_FORMAT_S16) {
|
||||
si[i].format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
|
||||
} else {
|
||||
si[i].format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
|
|
@ -247,6 +318,8 @@ void DivEngine::runExportThread() {
|
|||
// take control of audio output
|
||||
deinitAudioBackend();
|
||||
|
||||
curExportChan=0;
|
||||
|
||||
float* outBuf[DIV_MAX_OUTPUTS];
|
||||
float* outBufFinal;
|
||||
for (int i=0; i<exportOutputs; i++) {
|
||||
|
|
@ -258,6 +331,7 @@ void DivEngine::runExportThread() {
|
|||
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (!exportChannelMask[i]) continue;
|
||||
|
||||
SNDFILE* sf;
|
||||
SF_INFO si;
|
||||
SFWrapper sfWrap;
|
||||
|
|
@ -338,6 +412,8 @@ void DivEngine::runExportThread() {
|
|||
}
|
||||
}
|
||||
|
||||
curExportChan++;
|
||||
|
||||
if (sfWrap.doClose()!=0) {
|
||||
logE("could not close audio file!");
|
||||
}
|
||||
|
|
@ -378,6 +454,7 @@ void DivEngine::runExportThread() {
|
|||
}
|
||||
logI("done!");
|
||||
exporting=false;
|
||||
curExportChan=0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,92 +0,0 @@
|
|||
/**
|
||||
* 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 _ZSM_H
|
||||
#define _ZSM_H
|
||||
|
||||
//#include "engine.h"
|
||||
#include "safeWriter.h"
|
||||
#include "dispatch.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
#define ZSM_HEADER_SIZE 16
|
||||
#define ZSM_VERSION 1
|
||||
#define ZSM_YM_CMD 0x40
|
||||
#define ZSM_DELAY_CMD 0x80
|
||||
#define ZSM_YM_MAX_WRITES 63
|
||||
#define ZSM_SYNC_MAX_WRITES 31
|
||||
#define ZSM_DELAY_MAX 127
|
||||
#define ZSM_EOF ZSM_DELAY_CMD
|
||||
|
||||
#define ZSM_EXT ZSM_YM_CMD
|
||||
#define ZSM_EXT_PCM 0x00
|
||||
#define ZSM_EXT_CHIP 0x40
|
||||
#define ZSM_EXT_SYNC 0x80
|
||||
#define ZSM_EXT_CUSTOM 0xC0
|
||||
|
||||
enum YM_STATE { ym_PREV, ym_NEW, ym_STATES };
|
||||
enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES };
|
||||
|
||||
class DivZSM {
|
||||
private:
|
||||
struct S_pcmInst {
|
||||
int geometry;
|
||||
unsigned int offset, length, loopPoint;
|
||||
bool isLooped;
|
||||
};
|
||||
SafeWriter* w;
|
||||
int ymState[ym_STATES][256];
|
||||
int psgState[psg_STATES][64];
|
||||
int pcmRateCache;
|
||||
int pcmCtrlRVCache;
|
||||
int pcmCtrlDCCache;
|
||||
unsigned int pcmLoopPointCache;
|
||||
bool pcmIsLooped;
|
||||
std::vector<DivRegWrite> ymwrites;
|
||||
std::vector<DivRegWrite> pcmMeta;
|
||||
std::vector<unsigned char> pcmData;
|
||||
std::vector<unsigned char> pcmCache;
|
||||
std::vector<S_pcmInst> pcmInsts;
|
||||
std::vector<DivRegWrite> syncCache;
|
||||
int loopOffset;
|
||||
int numWrites;
|
||||
int ticks;
|
||||
int tickRate;
|
||||
int ymMask;
|
||||
int psgMask;
|
||||
bool optimize;
|
||||
public:
|
||||
DivZSM();
|
||||
~DivZSM();
|
||||
void init(unsigned int rate = 60);
|
||||
int getoffset();
|
||||
void writeYM(unsigned char a, unsigned char v);
|
||||
void writePSG(unsigned char a, unsigned char v);
|
||||
void writePCM(unsigned char a, unsigned char v);
|
||||
void writeSync(unsigned char a, unsigned char v);
|
||||
void setOptimize(bool o);
|
||||
void tick(int numticks = 1);
|
||||
void setLoopPoint();
|
||||
SafeWriter* finish();
|
||||
private:
|
||||
void flushWrites();
|
||||
void flushTicks();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,208 +0,0 @@
|
|||
/**
|
||||
* 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"
|
||||
#include "../ta-log.h"
|
||||
#include "../utfutils.h"
|
||||
#include "song.h"
|
||||
#include "zsm.h"
|
||||
|
||||
constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;
|
||||
constexpr int MASTER_CLOCK_MASK=(sizeof(void*)==8)?0xff:0;
|
||||
|
||||
SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop, bool optimize) {
|
||||
int VERA=-1;
|
||||
int YM=-1;
|
||||
int IGNORED=0;
|
||||
|
||||
// find indexes for YM and VERA. Ignore other systems.
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
switch (song.system[i]) {
|
||||
case DIV_SYSTEM_VERA:
|
||||
if (VERA>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
VERA=i;
|
||||
logD("VERA detected as chip id %d",i);
|
||||
break;
|
||||
case DIV_SYSTEM_YM2151:
|
||||
if (YM>=0) {
|
||||
IGNORED++;
|
||||
break;
|
||||
}
|
||||
YM=i;
|
||||
logD("YM detected as chip id %d",i);
|
||||
break;
|
||||
default:
|
||||
IGNORED++;
|
||||
logD("Ignoring chip %d systemID %d",i,(int)song.system[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (VERA<0 && YM<0) {
|
||||
logE("No supported systems for ZSM");
|
||||
return NULL;
|
||||
}
|
||||
if (IGNORED>0) {
|
||||
logW("ZSM export ignoring %d unsupported system%c",IGNORED,IGNORED>1?'s':' ');
|
||||
}
|
||||
|
||||
stop();
|
||||
repeatPattern=false;
|
||||
setOrder(0);
|
||||
BUSY_BEGIN_SOFT;
|
||||
|
||||
double origRate=got.rate;
|
||||
got.rate=zsmrate&0xffff;
|
||||
|
||||
// determine loop point
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
walkSong(loopOrder,loopRow,loopEnd);
|
||||
logI("loop point: %d %d",loopOrder,loopRow);
|
||||
warnings="";
|
||||
|
||||
DivZSM zsm;
|
||||
zsm.init(zsmrate);
|
||||
|
||||
// reset the playback state
|
||||
curOrder=0;
|
||||
freelance=false;
|
||||
playing=false;
|
||||
extValuePresent=false;
|
||||
remainingLoops=-1;
|
||||
|
||||
// Prepare to write song data
|
||||
playSub(false);
|
||||
//size_t tickCount=0;
|
||||
bool done=false;
|
||||
bool loopNow=false;
|
||||
int loopPos=-1;
|
||||
int fracWait=0; // accumulates fractional ticks
|
||||
if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(true);
|
||||
if (YM>=0) {
|
||||
disCont[YM].dispatch->toggleRegisterDump(true);
|
||||
// emit LFO initialization commands
|
||||
zsm.writeYM(0x18,0); // freq=0
|
||||
zsm.writeYM(0x19,0x7F); // AMD =7F
|
||||
zsm.writeYM(0x19,0xFF); // PMD =7F
|
||||
// TODO: incorporate the Furnace meta-command for init data and filter
|
||||
// out writes to otherwise-unused channels.
|
||||
}
|
||||
// Indicate the song's tuning as a sync meta-event
|
||||
// specified in terms of how many 1/256th semitones
|
||||
// the song is offset from standard A-440 tuning.
|
||||
// This is mainly to benefit visualizations in players
|
||||
// for non-standard tunings so that they can avoid
|
||||
// displaying the entire song held in pitch bend.
|
||||
// Tunings offsets that exceed a half semitone
|
||||
// will simply be represented in a different key
|
||||
// by nature of overflowing the signed char value
|
||||
signed char tuningoffset=(signed char)(round(3072*(log(song.tuning/440.0)/log(2))))&0xff;
|
||||
zsm.writeSync(0x01,tuningoffset);
|
||||
// Set optimize flag, which mainly buffers PSG writes
|
||||
// whenever the channel is silent
|
||||
zsm.setOptimize(optimize);
|
||||
|
||||
while (!done) {
|
||||
if (loopPos==-1) {
|
||||
if (loopOrder==curOrder && loopRow==curRow && loop)
|
||||
loopNow=true;
|
||||
if (loopNow) {
|
||||
// If Virtual Tempo is in use, our exact loop point
|
||||
// might be skipped due to quantization error.
|
||||
// If this happens, the tick immediately following is our loop point.
|
||||
if (ticks==1 || !(loopOrder==curOrder && loopRow==curRow)) {
|
||||
loopPos=zsm.getoffset();
|
||||
zsm.setLoopPoint();
|
||||
loopNow=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (nextTick() || !playing) {
|
||||
done=true;
|
||||
if (!loop) {
|
||||
for (int i=0; i<song.systemLen; i++) {
|
||||
disCont[i].dispatch->getRegisterWrites().clear();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (!playing) {
|
||||
loopPos=-1;
|
||||
}
|
||||
}
|
||||
// get register dumps
|
||||
for (int j=0; j<2; j++) {
|
||||
int i=0;
|
||||
// dump YM writes first
|
||||
if (j==0) {
|
||||
if (YM<0) {
|
||||
continue;
|
||||
} else {
|
||||
i=YM;
|
||||
}
|
||||
}
|
||||
// dump VERA writes second
|
||||
if (j==1) {
|
||||
if (VERA<0) {
|
||||
continue;
|
||||
} else {
|
||||
i=VERA;
|
||||
}
|
||||
}
|
||||
std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
|
||||
if (writes.size()>0)
|
||||
logD("zsmOps: Writing %d messages to chip %d",writes.size(),i);
|
||||
for (DivRegWrite& write: writes) {
|
||||
if (i==YM) zsm.writeYM(write.addr&0xff,write.val);
|
||||
if (i==VERA) {
|
||||
if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead
|
||||
zsm.writePSG(write.addr&0xff,write.val);
|
||||
}
|
||||
}
|
||||
writes.clear();
|
||||
}
|
||||
|
||||
// write wait
|
||||
int totalWait=cycles>>MASTER_CLOCK_PREC;
|
||||
fracWait+=cycles&MASTER_CLOCK_MASK;
|
||||
totalWait+=fracWait>>MASTER_CLOCK_PREC;
|
||||
fracWait&=MASTER_CLOCK_MASK;
|
||||
if (totalWait>0 && !done) {
|
||||
zsm.tick(totalWait);
|
||||
//tickCount+=totalWait;
|
||||
}
|
||||
}
|
||||
// end of song
|
||||
|
||||
// done - close out.
|
||||
got.rate=origRate;
|
||||
if (VERA>=0) disCont[VERA].dispatch->toggleRegisterDump(false);
|
||||
if (YM>=0) disCont[YM].dispatch->toggleRegisterDump(false);
|
||||
|
||||
remainingLoops=-1;
|
||||
playing=false;
|
||||
freelance=false;
|
||||
extValuePresent=false;
|
||||
|
||||
BUSY_END;
|
||||
return zsm.finish();
|
||||
}
|
||||
|
|
@ -42,6 +42,7 @@ template<typename T, size_t items> struct FixedQueue {
|
|||
void clear();
|
||||
bool empty();
|
||||
size_t size();
|
||||
size_t capacity();
|
||||
FixedQueue():
|
||||
readPos(0),
|
||||
writePos(0) {}
|
||||
|
|
@ -177,4 +178,8 @@ template <typename T, size_t items> size_t FixedQueue<T,items>::size() {
|
|||
return writePos-readPos;
|
||||
}
|
||||
|
||||
template <typename T, size_t items> size_t FixedQueue<T,items>::capacity() {
|
||||
return items-1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ const char* aboutLine[]={
|
|||
_N("-- program --"),
|
||||
"tildearrow",
|
||||
_N("A M 4 N (intro tune)"),
|
||||
"Adam Lederer",
|
||||
"akumanatt",
|
||||
"cam900",
|
||||
"djtuBIG-MaliceX",
|
||||
|
|
@ -302,6 +303,7 @@ const char* aboutLine[]={
|
|||
_N("openMSX YMF278 emulator (modified version) by the openMSX developers"),
|
||||
"",
|
||||
_N("greetings to:"),
|
||||
"floxy!",
|
||||
"NEOART Costa Rica",
|
||||
"Xenium Demoparty",
|
||||
"@party",
|
||||
|
|
|
|||
|
|
@ -161,6 +161,8 @@ void FurnaceGUI::drawCSPlayer() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::Text(_("vols"));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(_("volst"));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(_("vib"));
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text(_("porta"));
|
||||
|
|
@ -189,6 +191,8 @@ void FurnaceGUI::drawCSPlayer() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%+d",state->volSpeed);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%+d",state->volSpeedTarget);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%d/%d (%d)",state->vibratoDepth,state->vibratoRate,state->vibratoPos);
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("-> %d (%d)",state->portaTarget,state->portaSpeed);
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ void FurnaceGUI::drawDebug() {
|
|||
ImGui::Text("- portaNote = %d",ch->portaNote);
|
||||
ImGui::Text("- volume = %.4x",ch->volume);
|
||||
ImGui::Text("- volSpeed = %d",ch->volSpeed);
|
||||
ImGui::Text("- volSpeedTarget = %d",ch->volSpeedTarget);
|
||||
ImGui::Text("- cut = %d",ch->cut);
|
||||
ImGui::Text("- rowDelay = %d",ch->rowDelay);
|
||||
ImGui::Text("- volMax = %.4x",ch->volMax);
|
||||
|
|
|
|||
|
|
@ -73,6 +73,8 @@ void FurnaceGUI::doAction(int what) {
|
|||
case GUI_ACTION_UNDO:
|
||||
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
|
||||
doUndoSample();
|
||||
} else if (curWindow==GUI_WINDOW_INS_EDIT) {
|
||||
doUndoInstrument();
|
||||
} else {
|
||||
doUndo();
|
||||
}
|
||||
|
|
@ -80,6 +82,8 @@ void FurnaceGUI::doAction(int what) {
|
|||
case GUI_ACTION_REDO:
|
||||
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
|
||||
doRedoSample();
|
||||
} else if (curWindow==GUI_WINDOW_INS_EDIT) {
|
||||
doRedoInstrument();
|
||||
} else {
|
||||
doRedo();
|
||||
}
|
||||
|
|
@ -123,16 +127,16 @@ void FurnaceGUI::doAction(int what) {
|
|||
pendingStepUpdate=1;
|
||||
break;
|
||||
case GUI_ACTION_OCTAVE_UP:
|
||||
if (++curOctave>7) {
|
||||
curOctave=7;
|
||||
if (++curOctave>GUI_EDIT_OCTAVE_MAX) {
|
||||
curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
} else {
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
}
|
||||
break;
|
||||
case GUI_ACTION_OCTAVE_DOWN:
|
||||
if (--curOctave<-5) {
|
||||
curOctave=-5;
|
||||
if (--curOctave<GUI_EDIT_OCTAVE_MIN) {
|
||||
curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
} else {
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
|
@ -677,14 +681,7 @@ void FurnaceGUI::doAction(int what) {
|
|||
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;
|
||||
}
|
||||
}
|
||||
doAbsorbInstrument();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -508,7 +508,7 @@ void FurnaceGUI::drawMobileControls() {
|
|||
mobileMenuOpen=false;
|
||||
doAction(GUI_ACTION_SAVE_AS);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(_("Export"))) {
|
||||
doAction(GUI_ACTION_EXPORT);
|
||||
}
|
||||
|
|
@ -533,6 +533,10 @@ void FurnaceGUI::drawMobileControls() {
|
|||
drawSpeed(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
if (ImGui::BeginTabItem(_("Comments"))) {
|
||||
drawNotes(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
break;
|
||||
|
|
@ -647,8 +651,8 @@ void FurnaceGUI::drawEditControls() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
|
||||
if (curOctave>7) curOctave=7;
|
||||
if (curOctave<-5) curOctave=-5;
|
||||
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
||||
|
|
@ -808,8 +812,8 @@ void FurnaceGUI::drawEditControls() {
|
|||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(96.0f*dpiScale);
|
||||
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
|
||||
if (curOctave>7) curOctave=7;
|
||||
if (curOctave<-5) curOctave=-5;
|
||||
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
||||
|
|
@ -926,8 +930,8 @@ void FurnaceGUI::drawEditControls() {
|
|||
float avail=ImGui::GetContentRegionAvail().x;
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
if (ImGui::InputInt("##Octave",&curOctave,0,0)) {
|
||||
if (curOctave>7) curOctave=7;
|
||||
if (curOctave<-5) curOctave=-5;
|
||||
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
||||
|
|
@ -1093,8 +1097,8 @@ void FurnaceGUI::drawEditControls() {
|
|||
float avail=ImGui::GetContentRegionAvail().x;
|
||||
ImGui::SetNextItemWidth(avail);
|
||||
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
|
||||
if (curOctave>7) curOctave=7;
|
||||
if (curOctave<-5) curOctave=-5;
|
||||
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
|
||||
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
|
||||
e->autoNoteOffAll();
|
||||
failedNoteOn=false;
|
||||
|
||||
|
|
|
|||
|
|
@ -1823,6 +1823,52 @@ void FurnaceGUI::doExpandSong(int multiplier) {
|
|||
if (e->isPlaying()) e->play();
|
||||
}
|
||||
|
||||
void FurnaceGUI::doAbsorbInstrument() {
|
||||
bool foundIns=false;
|
||||
bool foundOctave=false;
|
||||
auto foundAll = [&]() { return foundIns && foundOctave; };
|
||||
|
||||
// search this order and all prior until we find all the data we need
|
||||
int orderIdx=curOrder;
|
||||
for (; orderIdx>=0 && !foundAll(); orderIdx--) {
|
||||
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][orderIdx],false);
|
||||
if (!pat) continue;
|
||||
|
||||
// start on current row when searching current order, but start from end when searching
|
||||
// prior orders.
|
||||
int searchStartRow=orderIdx==curOrder ? cursor.y : e->curSubSong->patLen-1;
|
||||
for (int i=searchStartRow; i>=0 && !foundAll(); i--) {
|
||||
|
||||
// absorb most recent instrument
|
||||
if (!foundIns && pat->data[i][2] >= 0) {
|
||||
foundIns=true;
|
||||
curIns=pat->data[i][2];
|
||||
}
|
||||
|
||||
// absorb most recent octave (i.e. set curOctave such that the "main row" (QWERTY) of
|
||||
// notes will result in an octave number equal to the previous note).
|
||||
if (!foundOctave && pat->data[i][0] != 0) {
|
||||
foundOctave=true;
|
||||
|
||||
// decode octave data (was signed cast to unsigned char)
|
||||
int octave=pat->data[i][1];
|
||||
if (octave>128) octave-=256;
|
||||
|
||||
// @NOTE the special handling when note==12, which is really an octave above what's
|
||||
// stored in the octave data. without this handling, if you press Q, then
|
||||
// "ABSORB_INSTRUMENT", then Q again, you'd get a different octave!
|
||||
if (pat->data[i][0]==12) octave++;
|
||||
curOctave=CLAMP(octave-1, GUI_EDIT_OCTAVE_MIN, GUI_EDIT_OCTAVE_MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no instrument has been set at this point, the only way to match it is to use "none"
|
||||
if (!foundIns) curIns=-1;
|
||||
|
||||
logD("doAbsorbInstrument -- searched %d orders", curOrder-orderIdx);
|
||||
}
|
||||
|
||||
void FurnaceGUI::doDrag() {
|
||||
int len=dragEnd.xCoarse-dragStart.xCoarse+1;
|
||||
|
||||
|
|
|
|||
|
|
@ -319,6 +319,29 @@ void FurnaceGUI::drawExportROM(bool onWindow) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case DIV_ROM_ZSM: {
|
||||
int zsmExportTickRate=romConfig.getInt("zsmrate",60);
|
||||
bool zsmExportLoop=romConfig.getBool("loop",true);
|
||||
bool zsmExportOptimize=romConfig.getBool("optimize",true);
|
||||
|
||||
if (ImGui::InputInt(_("Tick Rate (Hz)"),&zsmExportTickRate,1,2)) {
|
||||
if (zsmExportTickRate<1) zsmExportTickRate=1;
|
||||
if (zsmExportTickRate>44100) zsmExportTickRate=44100;
|
||||
altered=true;
|
||||
}
|
||||
if (ImGui::Checkbox(_("loop"),&zsmExportLoop)) {
|
||||
altered=true;
|
||||
}
|
||||
if (ImGui::Checkbox(_("optimize size"),&zsmExportOptimize)) {
|
||||
altered=true;
|
||||
}
|
||||
if (altered) {
|
||||
romConfig.set("zsmrate",zsmExportTickRate);
|
||||
romConfig.set("loop",zsmExportLoop);
|
||||
romConfig.set("optimize",zsmExportOptimize);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_ROM_ABSTRACT:
|
||||
ImGui::TextWrapped("%s",_("select a target from the menu at the top of this dialog."));
|
||||
break;
|
||||
|
|
@ -340,28 +363,6 @@ void FurnaceGUI::drawExportROM(bool onWindow) {
|
|||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::drawExportZSM(bool onWindow) {
|
||||
exitDisabledTimer=1;
|
||||
|
||||
ImGui::Text(_("Commander X16 Zsound Music File"));
|
||||
if (ImGui::InputInt(_("Tick Rate (Hz)"),&zsmExportTickRate,1,2)) {
|
||||
if (zsmExportTickRate<1) zsmExportTickRate=1;
|
||||
if (zsmExportTickRate>44100) zsmExportTickRate=44100;
|
||||
}
|
||||
ImGui::Checkbox(_("loop"),&zsmExportLoop);
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox(_("optimize size"),&zsmExportOptimize);
|
||||
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_ZSM);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::drawExportText(bool onWindow) {
|
||||
exitDisabledTimer=1;
|
||||
|
||||
|
|
@ -444,16 +445,6 @@ void FurnaceGUI::drawExport() {
|
|||
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++;
|
||||
}
|
||||
if (numZSMCompat>0) {
|
||||
if (ImGui::BeginTabItem(_("ZSM"))) {
|
||||
drawExportZSM(true);
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginTabItem(_("Text"))) {
|
||||
drawExportText(true);
|
||||
ImGui::EndTabItem();
|
||||
|
|
@ -478,9 +469,6 @@ void FurnaceGUI::drawExport() {
|
|||
case GUI_EXPORT_ROM:
|
||||
drawExportROM(true);
|
||||
break;
|
||||
case GUI_EXPORT_ZSM:
|
||||
drawExportZSM(true);
|
||||
break;
|
||||
case GUI_EXPORT_TEXT:
|
||||
drawExportText(true);
|
||||
break;
|
||||
|
|
|
|||
176
src/gui/gui.cpp
176
src/gui/gui.cpp
|
|
@ -995,7 +995,7 @@ Collapsed=0\n\
|
|||
\n\
|
||||
[Window][Rendering...]\n\
|
||||
Pos=585,342\n\
|
||||
Size=114,71\n\
|
||||
Size=600,100\n\
|
||||
Collapsed=0\n\
|
||||
\n\
|
||||
[Window][Export VGM##FileDialog]\n\
|
||||
|
|
@ -2022,16 +2022,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
|
|||
(settings.autoFillSave)?shortName:""
|
||||
);
|
||||
break;
|
||||
case GUI_FILE_EXPORT_ZSM:
|
||||
if (!dirExists(workingDirZSMExport)) workingDirZSMExport=getHomeDir();
|
||||
hasOpened=fileDialog->openSave(
|
||||
_("Export ZSM"),
|
||||
{_("ZSM file"), "*.zsm"},
|
||||
workingDirZSMExport,
|
||||
dpiScale,
|
||||
(settings.autoFillSave)?shortName:""
|
||||
);
|
||||
break;
|
||||
case GUI_FILE_EXPORT_TEXT:
|
||||
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
|
||||
hasOpened=fileDialog->openSave(
|
||||
|
|
@ -2592,7 +2582,40 @@ int FurnaceGUI::loadStream(String path) {
|
|||
|
||||
|
||||
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
|
||||
songOrdersLengths.clear();
|
||||
|
||||
int loopOrder=0;
|
||||
int loopRow=0;
|
||||
int loopEnd=0;
|
||||
e->walkSong(loopOrder,loopRow,loopEnd);
|
||||
|
||||
e->findSongLength(loopOrder,loopRow,audioExportOptions.fadeOut,songFadeoutSectionLength,songHasSongEndCommand,songOrdersLengths,songLength); // for progress estimation
|
||||
|
||||
songLoopedSectionLength=songLength;
|
||||
for (int i=0; i<loopOrder; i++) {
|
||||
songLoopedSectionLength-=songOrdersLengths[i];
|
||||
}
|
||||
songLoopedSectionLength-=loopRow;
|
||||
|
||||
e->saveAudio(path.c_str(),audioExportOptions);
|
||||
|
||||
totalFiles=0;
|
||||
e->getTotalAudioFiles(totalFiles);
|
||||
int totalLoops=0;
|
||||
|
||||
lengthOfOneFile=songLength;
|
||||
|
||||
if (!songHasSongEndCommand) {
|
||||
e->getTotalLoops(totalLoops);
|
||||
|
||||
lengthOfOneFile+=songLoopedSectionLength*totalLoops;
|
||||
lengthOfOneFile+=songFadeoutSectionLength; // account for fadeout
|
||||
}
|
||||
|
||||
totalLength=lengthOfOneFile*totalFiles;
|
||||
|
||||
curProgress=0.0f;
|
||||
|
||||
displayExporting=true;
|
||||
}
|
||||
|
||||
|
|
@ -3714,6 +3737,7 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::GetIO().AddKeyEvent(ImGuiKey_Backspace,false);
|
||||
injectBackUp=false;
|
||||
}
|
||||
|
||||
while (SDL_PollEvent(&ev)) {
|
||||
WAKE_UP;
|
||||
ImGui_ImplSDL2_ProcessEvent(&ev);
|
||||
|
|
@ -3730,13 +3754,16 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
case SDL_MOUSEBUTTONUP:
|
||||
pointUp(ev.button.x,ev.button.y,ev.button.button);
|
||||
insEditMayBeDirty=true;
|
||||
break;
|
||||
case SDL_MOUSEBUTTONDOWN:
|
||||
pointDown(ev.button.x,ev.button.y,ev.button.button);
|
||||
insEditMayBeDirty=true;
|
||||
break;
|
||||
case SDL_MOUSEWHEEL:
|
||||
wheelX+=ev.wheel.x;
|
||||
wheelY+=ev.wheel.y;
|
||||
insEditMayBeDirty=true;
|
||||
break;
|
||||
case SDL_WINDOWEVENT:
|
||||
switch (ev.window.event) {
|
||||
|
|
@ -3813,12 +3840,14 @@ bool FurnaceGUI::loop() {
|
|||
if (!ImGui::GetIO().WantCaptureKeyboard) {
|
||||
keyDown(ev);
|
||||
}
|
||||
insEditMayBeDirty=true;
|
||||
#ifdef IS_MOBILE
|
||||
injectBackUp=true;
|
||||
#endif
|
||||
break;
|
||||
case SDL_KEYUP:
|
||||
// for now
|
||||
insEditMayBeDirty=true;
|
||||
break;
|
||||
case SDL_DROPFILE:
|
||||
if (ev.drop.file!=NULL) {
|
||||
|
|
@ -4405,16 +4434,6 @@ bool FurnaceGUI::loop() {
|
|||
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++;
|
||||
}
|
||||
if (numZSMCompat>0) {
|
||||
if (ImGui::BeginMenu(_("export ZSM..."))) {
|
||||
drawExportZSM();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginMenu(_("export text..."))) {
|
||||
drawExportText();
|
||||
ImGui::EndMenu();
|
||||
|
|
@ -4442,16 +4461,6 @@ bool FurnaceGUI::loop() {
|
|||
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++;
|
||||
}
|
||||
if (numZSMCompat>0) {
|
||||
if (ImGui::MenuItem(_("export ZSM..."))) {
|
||||
curExportType=GUI_EXPORT_ZSM;
|
||||
displayExport=true;
|
||||
}
|
||||
}
|
||||
if (ImGui::MenuItem(_("export text..."))) {
|
||||
curExportType=GUI_EXPORT_TEXT;
|
||||
displayExport=true;
|
||||
|
|
@ -4477,7 +4486,7 @@ bool FurnaceGUI::loop() {
|
|||
} else {
|
||||
if (ImGui::BeginMenu(_("add chip..."))) {
|
||||
exitDisabledTimer=1;
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
if (!e->addSystem(picked)) {
|
||||
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));
|
||||
|
|
@ -4508,7 +4517,7 @@ bool FurnaceGUI::loop() {
|
|||
ImGui::Checkbox(_("Preserve channel positions"),&preserveChanPos);
|
||||
for (int i=0; i<e->song.systemLen; i++) {
|
||||
if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
if (e->changeSystem(i,picked,preserveChanPos)) {
|
||||
MARK_MODIFIED;
|
||||
|
|
@ -4778,7 +4787,7 @@ bool FurnaceGUI::loop() {
|
|||
info=fmt::sprintf(_("Set volume: %d (%.2X, INVALID!)"),p->data[cursor.y][3],p->data[cursor.y][3]);
|
||||
} else {
|
||||
float realVol=e->getGain(cursor.xCoarse,p->data[cursor.y][3]);
|
||||
info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->data[cursor.y][3],p->data[cursor.y][3],(int)(realVol*100.0f/(float)maxVol));
|
||||
info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->data[cursor.y][3],p->data[cursor.y][3],(int)(realVol*100.0f));
|
||||
}
|
||||
hasInfo=true;
|
||||
}
|
||||
|
|
@ -5034,9 +5043,6 @@ bool FurnaceGUI::loop() {
|
|||
case GUI_FILE_EXPORT_VGM:
|
||||
workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||
break;
|
||||
case GUI_FILE_EXPORT_ZSM:
|
||||
workingDirZSMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
|
||||
break;
|
||||
case GUI_FILE_EXPORT_ROM:
|
||||
case GUI_FILE_EXPORT_TEXT:
|
||||
case GUI_FILE_EXPORT_CMDSTREAM:
|
||||
|
|
@ -5136,9 +5142,6 @@ bool FurnaceGUI::loop() {
|
|||
if (curFileDialog==GUI_FILE_EXPORT_ROM) {
|
||||
checkExtension(romFilterExt.c_str());
|
||||
}
|
||||
if (curFileDialog==GUI_FILE_EXPORT_ZSM) {
|
||||
checkExtension(".zsm");
|
||||
}
|
||||
if (curFileDialog==GUI_FILE_EXPORT_TEXT) {
|
||||
checkExtension(".txt");
|
||||
}
|
||||
|
|
@ -5614,27 +5617,6 @@ bool FurnaceGUI::loop() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case GUI_FILE_EXPORT_ZSM: {
|
||||
SafeWriter* w=e->saveZSM(zsmExportTickRate,zsmExportLoop,zsmExportOptimize);
|
||||
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 ZSM! (%s)"),e->getLastError()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GUI_FILE_EXPORT_ROM:
|
||||
romExportPath=copyOfName;
|
||||
pendingExport=e->buildROM(romTarget);
|
||||
|
|
@ -5877,8 +5859,54 @@ bool FurnaceGUI::loop() {
|
|||
MEASURE_BEGIN(popup);
|
||||
|
||||
centerNextWindow(_("Rendering..."),canvasW,canvasH);
|
||||
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::Text(_("Please wait..."));
|
||||
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove)) {
|
||||
// WHAT the HELL?!
|
||||
WAKE_UP;
|
||||
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_CHAN) {
|
||||
ImGui::Text(_("Please wait..."));
|
||||
}
|
||||
float* progressLambda=&curProgress;
|
||||
int curPosInRows=0;
|
||||
int* curPosInRowsLambda=&curPosInRows;
|
||||
int loopsLeft=0;
|
||||
int* loopsLeftLambda=&loopsLeft;
|
||||
int totalLoops=0;
|
||||
int* totalLoopsLambda=&totalLoops;
|
||||
int curFile=0;
|
||||
int* curFileLambda=&curFile;
|
||||
if (e->isExporting()) {
|
||||
e->lockEngine([this, progressLambda, curPosInRowsLambda, curFileLambda,
|
||||
loopsLeftLambda, totalLoopsLambda] () {
|
||||
int curRow=0; int curOrder=0;
|
||||
e->getCurSongPos(curRow, curOrder); *curFileLambda=0;
|
||||
e->getCurFileIndex(*curFileLambda);
|
||||
*curPosInRowsLambda=curRow; for (int i=0; i<curOrder;
|
||||
i++) {
|
||||
*curPosInRowsLambda+=songOrdersLengths[i];}
|
||||
|
||||
if (!songHasSongEndCommand) {
|
||||
e->getLoopsLeft(*loopsLeftLambda); e->getTotalLoops(*totalLoopsLambda); if ((*totalLoopsLambda)!=(*loopsLeftLambda)) //we are going 2nd, 3rd, etc. time through the song
|
||||
{
|
||||
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump?
|
||||
}
|
||||
if (e->getIsFadingOut()) //we are in fadeout??? why it works like that bruh
|
||||
{
|
||||
// LIVE WITH IT damn it
|
||||
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump?
|
||||
}
|
||||
}
|
||||
// this horrible indentation courtesy of `indent`
|
||||
*progressLambda=(float) ((*curPosInRowsLambda) + ((*totalLoopsLambda)- (*loopsLeftLambda)) * songLength + lengthOfOneFile * (*curFileLambda)) / (float) totalLength;});
|
||||
}
|
||||
|
||||
ImGui::Text(_("Row %d of %d"),curPosInRows+((totalLoops)-(loopsLeft))*songLength,lengthOfOneFile);
|
||||
|
||||
if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) {
|
||||
ImGui::Text(_("Channel %d of %d"),curFile+1,totalFiles);
|
||||
}
|
||||
|
||||
ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0),fmt::sprintf("%.2f%%",curProgress*100.0f).c_str());
|
||||
|
||||
if (ImGui::Button(_("Abort"))) {
|
||||
if (e->haltAudioFile()) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
|
|
@ -7202,6 +7230,11 @@ bool FurnaceGUI::loop() {
|
|||
willCommit=false;
|
||||
}
|
||||
|
||||
// To check for instrument editor modification, we need an up-to-date `insEditMayBeDirty`
|
||||
// (based on incoming user input events), and we want any possible instrument modifications
|
||||
// to already have been made.
|
||||
checkRecordInstrumentUndoStep();
|
||||
|
||||
if (shallDetectScale) {
|
||||
if (--shallDetectScale<1) {
|
||||
if (settings.dpiScale<0.5f) {
|
||||
|
|
@ -7879,7 +7912,6 @@ void FurnaceGUI::syncState() {
|
|||
workingDirSample=e->getConfString("lastDirSample",workingDir);
|
||||
workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir);
|
||||
workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir);
|
||||
workingDirZSMExport=e->getConfString("lastDirZSMExport",workingDir);
|
||||
workingDirROMExport=e->getConfString("lastDirROMExport",workingDir);
|
||||
workingDirFont=e->getConfString("lastDirFont",workingDir);
|
||||
workingDirColors=e->getConfString("lastDirColors",workingDir);
|
||||
|
|
@ -8038,7 +8070,6 @@ void FurnaceGUI::commitState(DivConfig& conf) {
|
|||
conf.set("lastDirSample",workingDirSample);
|
||||
conf.set("lastDirAudioExport",workingDirAudioExport);
|
||||
conf.set("lastDirVGMExport",workingDirVGMExport);
|
||||
conf.set("lastDirZSMExport",workingDirZSMExport);
|
||||
conf.set("lastDirROMExport",workingDirROMExport);
|
||||
conf.set("lastDirFont",workingDirFont);
|
||||
conf.set("lastDirColors",workingDirColors);
|
||||
|
|
@ -8256,8 +8287,6 @@ FurnaceGUI::FurnaceGUI():
|
|||
displayError(false),
|
||||
displayExporting(false),
|
||||
vgmExportLoop(true),
|
||||
zsmExportLoop(true),
|
||||
zsmExportOptimize(true),
|
||||
vgmExportPatternHints(false),
|
||||
vgmExportDirectStream(false),
|
||||
displayInsTypeList(false),
|
||||
|
|
@ -8300,7 +8329,6 @@ FurnaceGUI::FurnaceGUI():
|
|||
vgmExportVersion(0x171),
|
||||
vgmExportTrailingTicks(-1),
|
||||
drawHalt(10),
|
||||
zsmExportTickRate(60),
|
||||
macroPointSize(16),
|
||||
waveEditStyle(0),
|
||||
displayInsTypeListMakeInsSample(-1),
|
||||
|
|
@ -8367,11 +8395,21 @@ FurnaceGUI::FurnaceGUI():
|
|||
bigFont(NULL),
|
||||
headFont(NULL),
|
||||
fontRange(NULL),
|
||||
songLength(0),
|
||||
songLoopedSectionLength(0),
|
||||
songFadeoutSectionLength(0),
|
||||
songHasSongEndCommand(false),
|
||||
lengthOfOneFile(0),
|
||||
totalLength(0),
|
||||
curProgress(0.0f),
|
||||
totalFiles(0),
|
||||
localeRequiresJapanese(false),
|
||||
localeRequiresChinese(false),
|
||||
localeRequiresChineseTrad(false),
|
||||
localeRequiresKorean(false),
|
||||
prevInsData(NULL),
|
||||
cachedCurInsPtr(NULL),
|
||||
insEditMayBeDirty(false),
|
||||
pendingLayoutImport(NULL),
|
||||
pendingLayoutImportLen(0),
|
||||
pendingLayoutImportStep(0),
|
||||
|
|
@ -8887,6 +8925,8 @@ FurnaceGUI::FurnaceGUI():
|
|||
|
||||
memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX);
|
||||
|
||||
songOrdersLengths.clear();
|
||||
|
||||
strncpy(noteOffLabel,"OFF",32);
|
||||
strncpy(noteRelLabel,"===",32);
|
||||
strncpy(macroRelLabel,"REL",32);
|
||||
|
|
|
|||
|
|
@ -137,6 +137,9 @@ enum FurnaceGUIRenderBackend {
|
|||
#define ngettext momo_ngettext
|
||||
#endif
|
||||
|
||||
#define GUI_EDIT_OCTAVE_MIN -5
|
||||
#define GUI_EDIT_OCTAVE_MAX 7
|
||||
|
||||
// TODO:
|
||||
// - add colors for FM envelope and waveform
|
||||
// - maybe add "alternate" color for FM modulators/carriers (a bit difficult)
|
||||
|
|
@ -598,7 +601,6 @@ enum FurnaceGUIFileDialogs {
|
|||
GUI_FILE_EXPORT_AUDIO_PER_SYS,
|
||||
GUI_FILE_EXPORT_AUDIO_PER_CHANNEL,
|
||||
GUI_FILE_EXPORT_VGM,
|
||||
GUI_FILE_EXPORT_ZSM,
|
||||
GUI_FILE_EXPORT_CMDSTREAM,
|
||||
GUI_FILE_EXPORT_TEXT,
|
||||
GUI_FILE_EXPORT_ROM,
|
||||
|
|
@ -651,7 +653,6 @@ enum FurnaceGUIExportTypes {
|
|||
GUI_EXPORT_AUDIO=0,
|
||||
GUI_EXPORT_VGM,
|
||||
GUI_EXPORT_ROM,
|
||||
GUI_EXPORT_ZSM,
|
||||
GUI_EXPORT_CMD_STREAM,
|
||||
GUI_EXPORT_TEXT,
|
||||
GUI_EXPORT_DMF
|
||||
|
|
@ -1594,7 +1595,7 @@ class FurnaceGUI {
|
|||
|
||||
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery, sampleBankSearchQuery;
|
||||
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport;
|
||||
String workingDirVGMExport, workingDirZSMExport, workingDirROMExport;
|
||||
String workingDirVGMExport, workingDirROMExport;
|
||||
String workingDirFont, workingDirColors, workingDirKeybinds;
|
||||
String workingDirLayout, workingDirROM, workingDirTest;
|
||||
String workingDirConfig;
|
||||
|
|
@ -1613,7 +1614,7 @@ class FurnaceGUI {
|
|||
std::vector<String> availRenderDrivers;
|
||||
std::vector<String> availAudioDrivers;
|
||||
|
||||
bool quit, warnQuit, willCommit, edit, editClone, isPatUnique, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, zsmExportOptimize, vgmExportPatternHints;
|
||||
bool quit, warnQuit, willCommit, edit, editClone, isPatUnique, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints;
|
||||
bool vgmExportDirectStream, displayInsTypeList, displayWaveSizeList;
|
||||
bool portrait, injectBackUp, mobileMenuOpen, warnColorPushed;
|
||||
bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
|
||||
|
|
@ -1634,7 +1635,6 @@ class FurnaceGUI {
|
|||
int vgmExportTrailingTicks;
|
||||
int cvHiScore;
|
||||
int drawHalt;
|
||||
int zsmExportTickRate;
|
||||
int macroPointSize;
|
||||
int waveEditStyle;
|
||||
int displayInsTypeListMakeInsSample;
|
||||
|
|
@ -1723,6 +1723,16 @@ class FurnaceGUI {
|
|||
char emptyLabel[32];
|
||||
char emptyLabel2[32];
|
||||
|
||||
std::vector<int> songOrdersLengths; // lengths of all orders (for drawing song export progress)
|
||||
int songLength; // length of all the song in rows
|
||||
int songLoopedSectionLength; // length of looped part of the song
|
||||
int songFadeoutSectionLength; // length of fading part of the song
|
||||
bool songHasSongEndCommand; // song has "Song end" command (FFxx)
|
||||
int lengthOfOneFile; // length of one rendering pass. song length times num of loops + fadeout
|
||||
int totalLength; // total length of render (lengthOfOneFile times num of files for per-channel export)
|
||||
float curProgress;
|
||||
int totalFiles;
|
||||
|
||||
struct Settings {
|
||||
bool settingsChanged;
|
||||
int mainFontSize, patFontSize, headFontSize, iconSize;
|
||||
|
|
@ -2263,6 +2273,9 @@ class FurnaceGUI {
|
|||
std::vector<ImWchar> localeExtraRanges;
|
||||
|
||||
DivInstrument* prevInsData;
|
||||
DivInstrument cachedCurIns;
|
||||
DivInstrument* cachedCurInsPtr;
|
||||
bool insEditMayBeDirty;
|
||||
|
||||
unsigned char* pendingLayoutImport;
|
||||
size_t pendingLayoutImportLen;
|
||||
|
|
@ -2704,7 +2717,6 @@ class FurnaceGUI {
|
|||
void drawExportAudio(bool onWindow=false);
|
||||
void drawExportVGM(bool onWindow=false);
|
||||
void drawExportROM(bool onWindow=false);
|
||||
void drawExportZSM(bool onWindow=false);
|
||||
void drawExportText(bool onWindow=false);
|
||||
void drawExportCommand(bool onWindow=false);
|
||||
void drawExportDMF(bool onWindow=false);
|
||||
|
|
@ -2824,7 +2836,7 @@ class FurnaceGUI {
|
|||
void drawMemory();
|
||||
void drawCompatFlags();
|
||||
void drawPiano();
|
||||
void drawNotes();
|
||||
void drawNotes(bool asChild=false);
|
||||
void drawChannels();
|
||||
void drawPatManager();
|
||||
void drawSysManager();
|
||||
|
|
@ -2914,13 +2926,14 @@ class FurnaceGUI {
|
|||
void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd);
|
||||
void doCollapseSong(int divider);
|
||||
void doExpandSong(int multiplier);
|
||||
void doAbsorbInstrument();
|
||||
void doUndo();
|
||||
void doRedo();
|
||||
void doFind();
|
||||
void doReplace();
|
||||
void doDrag();
|
||||
void editOptions(bool topMenu);
|
||||
DivSystem systemPicker();
|
||||
DivSystem systemPicker(bool fullWidth);
|
||||
void noteInput(int num, int key, int vol=-1);
|
||||
void valueInput(int num, bool direct=false, int target=-1);
|
||||
void orderInput(int num);
|
||||
|
|
@ -2930,6 +2943,10 @@ class FurnaceGUI {
|
|||
void doUndoSample();
|
||||
void doRedoSample();
|
||||
|
||||
void checkRecordInstrumentUndoStep();
|
||||
void doUndoInstrument();
|
||||
void doRedoInstrument();
|
||||
|
||||
void play(int row=0);
|
||||
void setOrder(unsigned char order, bool forced=false);
|
||||
void stop();
|
||||
|
|
|
|||
|
|
@ -473,8 +473,8 @@ const FurnaceGUIColors fxColors[256]={
|
|||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_VOLUME,
|
||||
GUI_COLOR_PATTERN_EFFECT_VOLUME,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
||||
|
|
@ -687,7 +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_ABSORB_INSTRUMENT", _N("Absorb instrument/octave from status at cursor"), 0),
|
||||
D("PAT_MAX", "", NOT_AN_ACTION),
|
||||
|
||||
D("INS_LIST_MIN", _N("---Instrument list"), NOT_AN_ACTION),
|
||||
|
|
|
|||
|
|
@ -5250,6 +5250,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH));
|
||||
}
|
||||
if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking),_("Instrument Editor"))) {
|
||||
DivInstrument* ins=NULL;
|
||||
if (curIns==-2) {
|
||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)*0.5f);
|
||||
CENTER_TEXT(_("waiting..."));
|
||||
|
|
@ -5277,6 +5278,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
curIns=i;
|
||||
wavePreviewInit=true;
|
||||
updateFMPreview=true;
|
||||
ins = e->song.ins[curIns];
|
||||
}
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
|
|
@ -5299,7 +5301,7 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::EndTable();
|
||||
}
|
||||
} else {
|
||||
DivInstrument* ins=e->song.ins[curIns];
|
||||
ins=e->song.ins[curIns];
|
||||
if (updateFMPreview) {
|
||||
renderFMPreview(ins);
|
||||
updateFMPreview=false;
|
||||
|
|
@ -7196,6 +7198,8 @@ void FurnaceGUI::drawInsEdit() {
|
|||
macroList.push_back(FurnaceGUIMacroDesc(_("Noise"),&ins->std.dutyMacro,0,8,160,uiColors[GUI_COLOR_MACRO_NOISE]));
|
||||
}
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Waveform"),&ins->std.waveMacro,0,waveCount,160,uiColors[GUI_COLOR_MACRO_WAVE],false,NULL,NULL,false,NULL));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Panning (left)"),&ins->std.panLMacro,0,15,46,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Panning (right)"),&ins->std.panRMacro,0,15,46,uiColors[GUI_COLOR_MACRO_OTHER]));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode));
|
||||
macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||
break;
|
||||
|
|
@ -7755,6 +7759,63 @@ void FurnaceGUI::drawInsEdit() {
|
|||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT;
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void FurnaceGUI::checkRecordInstrumentUndoStep() {
|
||||
if (insEditOpen && curIns>=0 && curIns<(int)e->song.ins.size()) {
|
||||
DivInstrument* ins=e->song.ins[curIns];
|
||||
|
||||
// invalidate cachedCurIns/any possible changes if the cachedCurIns was referencing a different
|
||||
// instrument altgoether
|
||||
bool insChanged=ins!=cachedCurInsPtr;
|
||||
if (insChanged) {
|
||||
insEditMayBeDirty=false;
|
||||
cachedCurInsPtr=ins;
|
||||
cachedCurIns=*ins;
|
||||
}
|
||||
|
||||
cachedCurInsPtr=ins;
|
||||
|
||||
// check against the last cached to see if diff -- note that modifications to instruments
|
||||
// happen outside drawInsEdit (e.g. cursor inputs are processed and can directly modify
|
||||
// macro data). but don't check until we think the user input is complete.
|
||||
bool delayDiff=ImGui::IsMouseDown(ImGuiMouseButton_Left) || ImGui::IsMouseDown(ImGuiMouseButton_Right) || ImGui::GetIO().WantCaptureKeyboard;
|
||||
if (!delayDiff && insEditMayBeDirty) {
|
||||
bool hasChange=ins->recordUndoStepIfChanged(e->processTime, &cachedCurIns);
|
||||
if (hasChange) {
|
||||
cachedCurIns=*ins;
|
||||
}
|
||||
insEditMayBeDirty=false;
|
||||
}
|
||||
} else {
|
||||
cachedCurInsPtr=NULL;
|
||||
insEditMayBeDirty=false;
|
||||
}
|
||||
}
|
||||
|
||||
void FurnaceGUI::doUndoInstrument() {
|
||||
if (!insEditOpen) return;
|
||||
if (curIns<0 || curIns>=(int)e->song.ins.size()) return;
|
||||
DivInstrument* ins=e->song.ins[curIns];
|
||||
// is locking the engine necessary? copied from doUndoSample
|
||||
e->lockEngine([this,ins]() {
|
||||
ins->undo();
|
||||
cachedCurInsPtr=ins;
|
||||
cachedCurIns=*ins;
|
||||
});
|
||||
}
|
||||
|
||||
void FurnaceGUI::doRedoInstrument() {
|
||||
if (!insEditOpen) return;
|
||||
if (curIns<0 || curIns>=(int)e->song.ins.size()) return;
|
||||
DivInstrument* ins=e->song.ins[curIns];
|
||||
// is locking the engine necessary? copied from doRedoSample
|
||||
e->lockEngine([this,ins]() {
|
||||
ins->redo();
|
||||
cachedCurInsPtr=ins;
|
||||
cachedCurIns=*ins;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1500,6 +1500,7 @@ void FurnaceGUI::drawPattern() {
|
|||
i.cmd==DIV_CMD_HINT_PORTA ||
|
||||
i.cmd==DIV_CMD_HINT_LEGATO ||
|
||||
i.cmd==DIV_CMD_HINT_VOL_SLIDE ||
|
||||
i.cmd==DIV_CMD_HINT_VOL_SLIDE_TARGET ||
|
||||
i.cmd==DIV_CMD_HINT_ARPEGGIO ||
|
||||
i.cmd==DIV_CMD_HINT_PITCH ||
|
||||
i.cmd==DIV_CMD_HINT_VIBRATO ||
|
||||
|
|
|
|||
|
|
@ -1216,62 +1216,62 @@ void FurnaceGUI::initSystemPresets() {
|
|||
}
|
||||
);
|
||||
SUB_ENTRY(
|
||||
"Sega TeraDrive", {
|
||||
CH(DIV_SYSTEM_YM2612, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive", {
|
||||
CH(DIV_SYSTEM_YM2612, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive (extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (CSM)", {
|
||||
CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive (CSM)", {
|
||||
CH(DIV_SYSTEM_YM2612_CSM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (DualPCM)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive (DualPCM)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
SUB_SUB_ENTRY(
|
||||
"Sega TeraDrive (DualPCM, extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
"Sega TeraDrive (DualPCM, extended channel 3)", {
|
||||
CH(DIV_SYSTEM_YM2612_DUALPCM_EXT, 1.0f, 0, ""),
|
||||
CH(DIV_SYSTEM_SMS, 0.5f, 0, ""),
|
||||
CH(DIV_SYSTEM_PCSPKR, 1.0f, 0, "")
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
"Sharp X1", {
|
||||
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3")
|
||||
}
|
||||
}
|
||||
);
|
||||
SUB_ENTRY(
|
||||
"Sharp X1 + FM Addon", {
|
||||
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=3"),
|
||||
CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2")
|
||||
}
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
"Sharp X68000", {
|
||||
CH(DIV_SYSTEM_YM2151, 1.0f, 0, "clockSel=2"),
|
||||
CH(DIV_SYSTEM_MSM6258, 1.0f, 0, "clockSel=2")
|
||||
}
|
||||
}
|
||||
);
|
||||
ENTRY(
|
||||
"FM-7", {
|
||||
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=12"),
|
||||
CH(DIV_SYSTEM_YM2203, 1.0f, 0, "clockSel=5")
|
||||
}
|
||||
}
|
||||
);
|
||||
SUB_ENTRY(
|
||||
"FM-7 (extended channel 3)", {
|
||||
|
|
|
|||
|
|
@ -253,16 +253,27 @@ void FurnaceGUI::drawSampleEdit() {
|
|||
SAMPLE_WARN(warnLength,"QSound: maximum sample length is 65535");
|
||||
}
|
||||
break;
|
||||
case DIV_SYSTEM_NES:
|
||||
case DIV_SYSTEM_NES: {
|
||||
if (sample->loop) {
|
||||
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
|
||||
SAMPLE_WARN(warnLoopPos,_("NES: loop point ignored on DPCM (may only loop entire sample)"));
|
||||
if (sample->loopStart&511) {
|
||||
int tryWith=(sample->loopStart)&(~511);
|
||||
if (tryWith>(int)sample->samples) tryWith-=512;
|
||||
String alignHint=fmt::sprintf(_("NES: loop start must be a multiple of 512 (try with %d)"),tryWith);
|
||||
SAMPLE_WARN(warnLoopStart,alignHint);
|
||||
}
|
||||
if ((sample->loopEnd)&127) {
|
||||
// +1 bc of how sample length is treated: https://www.nesdev.org/wiki/APU_DMC
|
||||
int tryWith=(sample->loopEnd + 1)&(~127);
|
||||
if (tryWith>(int)sample->samples) tryWith-=128;
|
||||
String alignHint=fmt::sprintf(_("NES: loop end must be a multiple of 128 (try with %d)"),tryWith);
|
||||
SAMPLE_WARN(warnLoopEnd,alignHint);
|
||||
}
|
||||
}
|
||||
if (sample->samples>32648) {
|
||||
SAMPLE_WARN(warnLength,_("NES: maximum DPCM sample length is 32648"));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_SYSTEM_X1_010:
|
||||
if (sample->loop) {
|
||||
SAMPLE_WARN(warnLoop,_("X1-010: samples can't loop"));
|
||||
|
|
|
|||
|
|
@ -1089,15 +1089,19 @@ void FurnaceGUI::drawSettings() {
|
|||
ImGui::PushID(i);
|
||||
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(_("Invert")).x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0);
|
||||
if (ImGui::BeginCombo("##System",getSystemName(sysID))) {
|
||||
for (int j=0; availableSystems[j]; j++) {
|
||||
if (ImGui::Selectable(getSystemName((DivSystem)availableSystems[j]),sysID==availableSystems[j])) {
|
||||
sysID=(DivSystem)availableSystems[j];
|
||||
settings.initialSys.set(fmt::sprintf("id%d",i),(int)e->systemToFileFur(sysID));
|
||||
settings.initialSys.set(fmt::sprintf("flags%d",i),"");
|
||||
settingsChanged=true;
|
||||
}
|
||||
if (ImGui::BeginCombo("##System",getSystemName(sysID),ImGuiComboFlags_HeightLargest)) {
|
||||
|
||||
sysID=systemPicker(true);
|
||||
|
||||
if (sysID!=DIV_SYSTEM_NULL)
|
||||
{
|
||||
settings.initialSys.set(fmt::sprintf("id%d",i),(int)e->systemToFileFur(sysID));
|
||||
settings.initialSys.set(fmt::sprintf("flags%d",i),"");
|
||||
settingsChanged=true;
|
||||
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,18 +22,23 @@
|
|||
|
||||
// NOTE: please don't ask me to enable text wrap.
|
||||
// Dear ImGui doesn't have that feature. D:
|
||||
void FurnaceGUI::drawNotes() {
|
||||
void FurnaceGUI::drawNotes(bool asChild) {
|
||||
if (nextWindow==GUI_WINDOW_NOTES) {
|
||||
notesOpen=true;
|
||||
ImGui::SetNextWindowFocus();
|
||||
nextWindow=GUI_WINDOW_NOTHING;
|
||||
}
|
||||
if (!notesOpen) return;
|
||||
if (ImGui::Begin("Song Comments",¬esOpen,globalWinFlags,_("Song Comments"))) {
|
||||
if (!notesOpen && !asChild) return;
|
||||
bool began=asChild?ImGui::BeginChild("Song Info##Song Information"):ImGui::Begin("Song Comments",¬esOpen,globalWinFlags,_("Song Comments"));
|
||||
if (began) {
|
||||
if (ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) {
|
||||
MARK_MODIFIED;
|
||||
}
|
||||
}
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES;
|
||||
ImGui::End();
|
||||
if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES;
|
||||
if (asChild) {
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
ImGui::End();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1969,6 +1969,8 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
|
|||
echoFilter[6]=flags.getInt("echoFilter6",0);
|
||||
echoFilter[7]=flags.getInt("echoFilter7",0);
|
||||
|
||||
bool interpolationOff=flags.getBool("interpolationOff",false);
|
||||
|
||||
ImGui::Text(_("Volume scale:"));
|
||||
if (CWSliderInt(_("Left##VolScaleL"),&vsL,0,127)) {
|
||||
if (vsL<0) vsL=0;
|
||||
|
|
@ -2084,6 +2086,10 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
|
|||
ImGui::Text(_("sum: %d"),filterSum);
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::Checkbox(_("Disable Gaussian interpolation"),&interpolationOff)) {
|
||||
altered=true;
|
||||
}
|
||||
|
||||
if (altered) {
|
||||
e->lockSave([&]() {
|
||||
flags.set("volScaleL",127-vsL);
|
||||
|
|
@ -2102,6 +2108,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
|
|||
flags.set("echoFilter6",echoFilter[6]);
|
||||
flags.set("echoFilter7",echoFilter[7]);
|
||||
flags.set("echoMask",echoMask);
|
||||
flags.set("interpolationOff",interpolationOff);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ void FurnaceGUI::drawSysManager() {
|
|||
ImGui::SameLine();
|
||||
ImGui::Button(_("Change##SysChange"));
|
||||
if (ImGui::BeginPopupContextItem("SysPickerC",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
if (e->changeSystem(i,picked,preserveChanPos)) {
|
||||
MARK_MODIFIED;
|
||||
|
|
@ -138,7 +138,7 @@ void FurnaceGUI::drawSysManager() {
|
|||
ImGui::TableNextColumn();
|
||||
ImGui::Button(ICON_FA_PLUS "##SysAdd");
|
||||
if (ImGui::BeginPopupContextItem("SysPickerA",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
if (!e->addSystem(picked)) {
|
||||
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
#include "guiConst.h"
|
||||
#include <imgui.h>
|
||||
|
||||
DivSystem FurnaceGUI::systemPicker() {
|
||||
DivSystem FurnaceGUI::systemPicker(bool fullWidth) {
|
||||
DivSystem ret=DIV_SYSTEM_NULL;
|
||||
DivSystem hoveredSys=DIV_SYSTEM_NULL;
|
||||
bool reissueSearch=false;
|
||||
|
|
@ -61,7 +61,7 @@ DivSystem FurnaceGUI::systemPicker() {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (ImGui::BeginTable("SysList",1,ImGuiTableFlags_ScrollY,ImVec2(500.0f*dpiScale,200.0*dpiScale))) {
|
||||
if (ImGui::BeginTable("SysList",1,ImGuiTableFlags_ScrollY,ImVec2(fullWidth ? ImGui::GetContentRegionAvail().x : 500.0f*dpiScale,200.0f*dpiScale))) {
|
||||
if (sysSearchQuery.empty()) {
|
||||
// display chip list
|
||||
for (int j=0; curSysSection[j]; j++) {
|
||||
|
|
|
|||
|
|
@ -392,7 +392,7 @@ void FurnaceGUI::drawUserPresets() {
|
|||
tempID=fmt::sprintf("%s##USystem",getSystemName(chip.sys));
|
||||
ImGui::Button(tempID.c_str(),ImVec2(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(_("Invert")).x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0,0));
|
||||
if (ImGui::BeginPopupContextItem("SysPickerCU",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
chip.sys=picked;
|
||||
mustBake=true;
|
||||
|
|
@ -456,7 +456,7 @@ void FurnaceGUI::drawUserPresets() {
|
|||
|
||||
ImGui::Button(ICON_FA_PLUS "##SysAddU");
|
||||
if (ImGui::BeginPopupContextItem("SysPickerU",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||
DivSystem picked=systemPicker();
|
||||
DivSystem picked=systemPicker(false);
|
||||
if (picked!=DIV_SYSTEM_NULL) {
|
||||
preset->orig.push_back(FurnaceGUISysDefChip(picked,1.0f,0.0f,""));
|
||||
mustBake=true;
|
||||
|
|
|
|||
38
src/main.cpp
38
src/main.cpp
|
|
@ -85,7 +85,6 @@ FurnaceCLI cli;
|
|||
|
||||
String outName;
|
||||
String vgmOutName;
|
||||
String zsmOutName;
|
||||
String cmdOutName;
|
||||
int benchMode=0;
|
||||
int subsong=-1;
|
||||
|
|
@ -430,12 +429,6 @@ TAParamResult pVGMOut(String val) {
|
|||
return TA_PARAM_SUCCESS;
|
||||
}
|
||||
|
||||
TAParamResult pZSMOut(String val) {
|
||||
zsmOutName=val;
|
||||
e.setAudio(DIV_AUDIO_DUMMY);
|
||||
return TA_PARAM_SUCCESS;
|
||||
}
|
||||
|
||||
TAParamResult pCmdOut(String val) {
|
||||
cmdOutName=val;
|
||||
e.setAudio(DIV_AUDIO_DUMMY);
|
||||
|
|
@ -458,7 +451,6 @@ void initParams() {
|
|||
params.push_back(TAParam("o","output",true,pOutput,"<filename>","output audio to file"));
|
||||
params.push_back(TAParam("O","vgmout",true,pVGMOut,"<filename>","output .vgm data"));
|
||||
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("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)"));
|
||||
|
|
@ -561,7 +553,6 @@ int main(int argc, char** argv) {
|
|||
#endif
|
||||
outName="";
|
||||
vgmOutName="";
|
||||
zsmOutName="";
|
||||
cmdOutName="";
|
||||
|
||||
// load config for locale
|
||||
|
|
@ -730,14 +721,14 @@ int main(int argc, char** argv) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
if (fileName.empty() && (benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
|
||||
if (fileName.empty() && (benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) {
|
||||
logE("provide a file!");
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_GUI
|
||||
if (e.preInit(consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
|
||||
if (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="") {
|
||||
if (e.preInit(consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) {
|
||||
if (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="") {
|
||||
logW("engine wants safe mode, but Furnace GUI is not going to start.");
|
||||
} else {
|
||||
safeMode=true;
|
||||
|
|
@ -749,7 +740,7 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
#endif
|
||||
|
||||
if (safeMode && (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="")) {
|
||||
if (safeMode && (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) {
|
||||
logE("you can't use safe mode and console/export mode together.");
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -758,7 +749,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!="")) {
|
||||
if (!fileName.empty() && ((!e.getConfBool("tutIntroPlayed",TUT_INTRO_PLAYED)) || e.getConfInt("alwaysPlayIntro",0)!=3 || consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) {
|
||||
logI("loading module...");
|
||||
FILE* f=ps_fopen(fileName.c_str(),"rb");
|
||||
if (f==NULL) {
|
||||
|
|
@ -850,7 +841,7 @@ int main(int argc, char** argv) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="") {
|
||||
if (outName!="" || vgmOutName!="" || cmdOutName!="") {
|
||||
if (cmdOutName!="") {
|
||||
SafeWriter* w=e.saveCommand();
|
||||
if (w!=NULL) {
|
||||
|
|
@ -883,23 +874,6 @@ int main(int argc, char** argv) {
|
|||
reportError(_("could not write VGM!"));
|
||||
}
|
||||
}
|
||||
if (zsmOutName!="") {
|
||||
// TODO: changing parameters
|
||||
SafeWriter* w=e.saveZSM(60,true,true);
|
||||
if (w!=NULL) {
|
||||
FILE* f=ps_fopen(zsmOutName.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 ZSM! (%s)"),e.getLastError()));
|
||||
}
|
||||
}
|
||||
if (outName!="") {
|
||||
e.setConsoleMode(true);
|
||||
e.saveAudio(outName.c_str(),exportOptions);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue