Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt

This commit is contained in:
cam900 2022-11-15 11:52:30 +09:00
commit 46880634b4
50 changed files with 2492 additions and 1960 deletions

View File

@ -308,6 +308,7 @@ endif()
set(ENGINE_SOURCES
src/log.cpp
src/baseutils.cpp
src/fileutils.cpp
src/utfutils.cpp
@ -572,6 +573,7 @@ src/gui/guiConst.cpp
src/gui/about.cpp
src/gui/channels.cpp
src/gui/chanOsc.cpp
src/gui/clock.cpp
src/gui/compatFlags.cpp
src/gui/cursor.cpp
src/gui/dataList.cpp

View File

@ -1,4 +1,4 @@
# Furnace Tracker
# Furnace (chiptune tracker)
![screenshot](papers/screenshot2.png)

BIN
demos/SeeingRed.fur Normal file

Binary file not shown.

Binary file not shown.

BIN
demos/splashingwater.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -3,7 +3,7 @@
a backwards-compatible successor to the AY-3-8910, with increased volume resolution, duty cycle control, three envelopes and highly configurable noise generator.
sadly, this soundchip has only ever observed minimal success, and has remained rather obscure since.
it is known for being used in the Covox Sound Master, which didn't sell well either.
it is best known for being used in the Covox Sound Master, which didn't sell well either. It also observed very minimal success in Merit's CRT-250 machines, but only as a replacement for the AY-3-8910.
emulation of this chip in Furnace is now complete thanks to community efforts and hardware testing, which an MSX board called Darky has permitted.

View File

@ -2,8 +2,6 @@
one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player.
For 0.6pre1, Furnace can now support advanced YM2612 features that [Fractal](https://gitlab.com/Natsumi/Fractal-Sound) sound driver adds: two software-mixed PCM channels (variable pitch, sample offsets, max 13.7 khz rate) and CSM - ch3 special mode feature that can be abused to produce rudimentary speech synthesis.
# effects
- `10xy`: set LFO parameters.

View File

@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are:
- 123: Furnace dev123
- 122: Furnace dev122
- 121: Furnace dev121
- 120: Furnace dev120
- 119: Furnace dev119
@ -1096,7 +1098,11 @@ size | description
| - 9: BRR (SNES)
| - 10: VOX
| - 16: 16-bit PCM
3 | reserved
1 | loop direction (>=123) or reserved
| - 0: forward
| - 0: backward
| - 0: ping-pong
2 | reserved
4 | loop start
| - -1 means no loop
4 | loop end

89
src/baseutils.cpp Normal file
View File

@ -0,0 +1,89 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 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 "baseutils.h"
#include <string.h>
const char* base64Table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string taEncodeBase64(const std::string& data) {
std::string ret;
ret.reserve((2+data.size()*4)/3);
unsigned int groupOfThree=0;
unsigned char pos=0;
for (const char& i: data) {
groupOfThree|=((unsigned char)i)<<((2-pos)<<3);
if (++pos>=3) {
pos=0;
ret+=base64Table[(groupOfThree>>18)&63];
ret+=base64Table[(groupOfThree>>12)&63];
ret+=base64Table[(groupOfThree>>6)&63];
ret+=base64Table[groupOfThree&63];
groupOfThree=0;
}
}
if (pos==2) {
ret+=base64Table[(groupOfThree>>18)&63];
ret+=base64Table[(groupOfThree>>12)&63];
ret+=base64Table[(groupOfThree>>6)&63];
ret+='=';
} else if (pos==1) {
ret+=base64Table[(groupOfThree>>18)&63];
ret+=base64Table[(groupOfThree>>12)&63];
ret+="==";
}
return ret;
}
std::string taDecodeBase64(const char* buf) {
std::string data;
unsigned int groupOfThree=0;
signed char pos=18;
for (const char* i=buf; *i; i++) {
unsigned char nextVal=0;
if ((*i)=='/') {
nextVal=63;
} else if ((*i)=='+') {
nextVal=62;
} else if ((*i)>='0' && (*i)<='9') {
nextVal=52+((*i)-'0');
} else if ((*i)>='a' && (*i)<='z') {
nextVal=26+((*i)-'a');
} else if ((*i)>='A' && (*i)<='Z') {
nextVal=((*i)-'A');
} else {
nextVal=0;
}
groupOfThree|=nextVal<<pos;
pos-=6;
if (pos<0) {
pos=18;
if ((groupOfThree>>16)&0xff) data+=(groupOfThree>>16)&0xff;
if ((groupOfThree>>8)&0xff) data+=(groupOfThree>>8)&0xff;
if (groupOfThree&0xff) data+=groupOfThree&0xff;
groupOfThree=0;
}
}
return data;
}

28
src/baseutils.h Normal file
View File

@ -0,0 +1,28 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 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 _BASEUTILS_H
#define _BASEUTILS_H
#include <string>
std::string taEncodeBase64(const std::string& data);
std::string taDecodeBase64(const char* str);
#endif

View File

@ -19,6 +19,7 @@
#include "config.h"
#include "../ta-log.h"
#include "../baseutils.h"
#include "../fileutils.h"
#include <fmt/printf.h>
@ -48,41 +49,9 @@ String DivConfig::toString() {
return ret;
}
const char* base64Table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
String DivConfig::toBase64() {
String data=toString();
String ret;
ret.reserve((2+data.size()*4)/3);
unsigned int groupOfThree=0;
unsigned char pos=0;
for (char& i: data) {
groupOfThree|=((unsigned char)i)<<((2-pos)<<3);
if (++pos>=3) {
pos=0;
ret+=base64Table[(groupOfThree>>18)&63];
ret+=base64Table[(groupOfThree>>12)&63];
ret+=base64Table[(groupOfThree>>6)&63];
ret+=base64Table[groupOfThree&63];
groupOfThree=0;
}
}
if (pos==2) {
ret+=base64Table[(groupOfThree>>18)&63];
ret+=base64Table[(groupOfThree>>12)&63];
ret+=base64Table[(groupOfThree>>6)&63];
ret+='=';
} else if (pos==1) {
ret+=base64Table[(groupOfThree>>18)&63];
ret+=base64Table[(groupOfThree>>12)&63];
ret+="==";
}
logV("toBase64: %s",ret);
return ret;
return taEncodeBase64(data);
}
void DivConfig::parseLine(const char* line) {
@ -143,38 +112,7 @@ bool DivConfig::loadFromMemory(const char* buf) {
}
bool DivConfig::loadFromBase64(const char* buf) {
String data;
unsigned int groupOfThree=0;
signed char pos=18;
for (const char* i=buf; *i; i++) {
unsigned char nextVal=0;
if ((*i)=='/') {
nextVal=63;
} else if ((*i)=='+') {
nextVal=62;
} else if ((*i)>='0' && (*i)<='9') {
nextVal=52+((*i)-'0');
} else if ((*i)>='a' && (*i)<='z') {
nextVal=26+((*i)-'a');
} else if ((*i)>='A' && (*i)<='Z') {
nextVal=((*i)-'A');
} else {
nextVal=0;
}
groupOfThree|=nextVal<<pos;
pos-=6;
if (pos<0) {
pos=18;
if ((groupOfThree>>16)&0xff) data+=(groupOfThree>>16)&0xff;
if ((groupOfThree>>8)&0xff) data+=(groupOfThree>>8)&0xff;
if (groupOfThree&0xff) data+=groupOfThree&0xff;
groupOfThree=0;
}
}
logV("fromBase64: %s",data);
String data=taDecodeBase64(buf);
return loadFromMemory(data.c_str());
}

View File

@ -1390,10 +1390,14 @@ String DivEngine::decodeSysDesc(String desc) {
return newDesc.toBase64();
}
void DivEngine::initSongWithDesc(const char* description) {
void DivEngine::initSongWithDesc(const char* description, bool inBase64) {
int chanCount=0;
DivConfig c;
c.loadFromBase64(description);
if (inBase64) {
c.loadFromBase64(description);
} else {
c.loadFromMemory(description);
}
int index=0;
for (; index<32; index++) {
song.system[index]=systemFromFileFur(c.getInt(fmt::sprintf("id%d",index),0));
@ -1414,7 +1418,7 @@ void DivEngine::initSongWithDesc(const char* description) {
song.systemLen=index;
}
void DivEngine::createNew(const char* description, String sysName) {
void DivEngine::createNew(const char* description, String sysName, bool inBase64) {
quitDispatch();
BUSY_BEGIN;
saveLock.lock();
@ -1422,7 +1426,7 @@ void DivEngine::createNew(const char* description, String sysName) {
song=DivSong();
changeSong(0);
if (description!=NULL) {
initSongWithDesc(description);
initSongWithDesc(description,inBase64);
}
if (sysName=="") {
song.systemName=getSongSystemLegacyName(song,!getConfInt("noMultiSystem",0));
@ -2358,6 +2362,9 @@ void DivEngine::reset() {
firstTick=false;
shallStop=false;
shallStopSched=false;
pendingMetroTick=0;
elapsedBars=0;
elapsedBeats=0;
nextSpeed=speed1;
divider=60;
if (curSubSong->customTempo) {
@ -2548,6 +2555,14 @@ int DivEngine::getRow() {
return prevRow;
}
int DivEngine::getElapsedBars() {
return elapsedBars;
}
int DivEngine::getElapsedBeats() {
return elapsedBeats;
}
size_t DivEngine::getCurrentSubSong() {
return curSubSongIndex;
}
@ -2673,12 +2688,17 @@ void DivEngine::unmuteAll() {
BUSY_END;
}
int DivEngine::addInstrument(int refChan) {
int DivEngine::addInstrument(int refChan, DivInstrumentType fallbackType) {
if (song.ins.size()>=256) return -1;
BUSY_BEGIN;
DivInstrument* ins=new DivInstrument;
int insCount=(int)song.ins.size();
DivInstrumentType prefType=getPreferInsType(refChan);
DivInstrumentType prefType;
if (refChan<0) {
prefType=fallbackType;
} else {
prefType=getPreferInsType(refChan);
}
switch (prefType) {
case DIV_INS_OPLL:
*ins=song.nullInsOPLL;
@ -2692,8 +2712,10 @@ int DivEngine::addInstrument(int refChan) {
default:
break;
}
if (sysOfChan[refChan]==DIV_SYSTEM_QSOUND) {
*ins=song.nullInsQSound;
if (refChan>=0) {
if (sysOfChan[refChan]==DIV_SYSTEM_QSOUND) {
*ins=song.nullInsQSound;
}
}
ins->name=fmt::sprintf("Instrument %d",insCount);
if (prefType!=DIV_INS_NULL) {

View File

@ -47,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev122"
#define DIV_ENGINE_VERSION 122
#define DIV_VERSION "dev125"
#define DIV_ENGINE_VERSION 125
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -351,14 +351,14 @@ class DivEngine {
bool midiOutClock;
int midiOutMode;
int softLockCount;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats;
size_t curSubSongIndex;
double divider;
int cycles;
double clockDrift;
int stepPlay;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
unsigned char extValue;
unsigned char extValue, pendingMetroTick;
unsigned char speed1, speed2;
short tempoAccum;
DivStatusView view;
@ -382,9 +382,9 @@ class DivEngine {
std::vector<String> midiOuts;
std::vector<DivCommand> cmdStream;
std::vector<DivInstrumentType> possibleInsTypes;
DivSysDef* sysDefs[256];
DivSystem sysFileMapFur[256];
DivSystem sysFileMapDMF[256];
static DivSysDef* sysDefs[256];
static DivSystem sysFileMapFur[256];
static DivSystem sysFileMapDMF[256];
struct SamplePreview {
double rate;
@ -440,8 +440,6 @@ class DivEngine {
void reset();
void playSub(bool preserveDrift, int goalRow=0);
void convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivSystem sys);
bool loadDMF(unsigned char* file, size_t len);
bool loadFur(unsigned char* file, size_t len);
bool loadMod(unsigned char* file, size_t len);
@ -469,7 +467,7 @@ class DivEngine {
bool deinitAudioBackend(bool dueToSwitchMaster=false);
void registerSystems();
void initSongWithDesc(const char* description);
void initSongWithDesc(const char* description, bool inBase64=true);
void exchangeIns(int one, int two);
void swapChannels(int src, int dest);
@ -503,7 +501,7 @@ class DivEngine {
// parse old system setup description
String decodeSysDesc(String desc);
// start fresh
void createNew(const char* description, String sysName);
void createNew(const char* description, String sysName, bool inBase64=true);
// load a file.
bool load(unsigned char* f, size_t length);
// save as .dmf.
@ -532,10 +530,14 @@ class DivEngine {
void notifyWaveChange(int wave);
// get system IDs
DivSystem systemFromFileFur(unsigned char val);
unsigned char systemToFileFur(DivSystem val);
DivSystem systemFromFileDMF(unsigned char val);
unsigned char systemToFileDMF(DivSystem val);
static DivSystem systemFromFileFur(unsigned char val);
static unsigned char systemToFileFur(DivSystem val);
static DivSystem systemFromFileDMF(unsigned char val);
static unsigned char systemToFileDMF(DivSystem val);
// convert old flags
static void convertOldFlags(unsigned int oldFlags, DivConfig& newFlags, DivSystem sys);
// benchmark (returns time in seconds)
double benchmarkPlayback();
@ -709,6 +711,10 @@ class DivEngine {
// get current row
int getRow();
// get beat/bar
int getElapsedBars();
int getElapsedBeats();
// get current subsong
size_t getCurrentSubSong();
@ -753,7 +759,7 @@ class DivEngine {
bool isExporting();
// add instrument
int addInstrument(int refChan=0);
int addInstrument(int refChan=0, DivInstrumentType fallbackType=DIV_INS_STD);
// add instrument from pointer
int addInstrumentPtr(DivInstrument* which);
@ -1058,6 +1064,8 @@ class DivEngine {
lastLoopPos(0),
exportLoopCount(0),
nextSpeed(3),
elapsedBars(0),
elapsedBeats(0),
curSubSongIndex(0),
divider(60),
cycles(0),
@ -1073,6 +1081,7 @@ class DivEngine {
cmdsPerSecond(0),
globalPitch(0),
extValue(0),
pendingMetroTick(0),
speed1(3),
speed2(3),
tempoAccum(0),

View File

@ -2383,11 +2383,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
if (isNewSample) {
sample->centerRate=reader.readI();
sample->depth=(DivSampleDepth)reader.readC();
if (ds.version>=123) {
sample->loopMode=(DivSampleLoopMode)reader.readC();
} else {
sample->loopMode=DIV_SAMPLE_LOOP_FORWARD;
reader.readC();
}
// reserved
reader.readC();
reader.readC();
reader.readC();
sample->loopStart=reader.readI();
sample->loopEnd=reader.readI();
@ -2611,6 +2616,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
}
}
// ExtCh compat flag
if (ds.version<125) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_YM2612_EXT ||
ds.system[i]==DIV_SYSTEM_YM2612_FRAC_EXT ||
ds.system[i]==DIV_SYSTEM_YM2610_EXT ||
ds.system[i]==DIV_SYSTEM_YM2610_FULL_EXT ||
ds.system[i]==DIV_SYSTEM_YM2610B_EXT ||
ds.system[i]==DIV_SYSTEM_OPN_EXT ||
ds.system[i]==DIV_SYSTEM_PC98_EXT) {
ds.systemFlags[i].set("noExtMacros",true);
}
}
}
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();
@ -2946,7 +2966,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
writeFxCol(fxTyp,fxVal);
break;
case 12: // set vol
data[row][3]=fxVal;
data[row][3]=MIN(0x40,fxVal);
break;
case 13: // break to row (BCD)
writeFxCol(fxTyp,((fxVal>>4)*10)+(fxVal&15));
@ -4624,9 +4644,9 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(sample->rate);
w->writeI(sample->centerRate);
w->writeC(sample->depth);
w->writeC(sample->loopMode);
w->writeC(0); // reserved
w->writeC(0);
w->writeC(0);
w->writeI(sample->loop?sample->loopStart:-1);
w->writeI(sample->loop?sample->loopEnd:-1);

View File

@ -285,7 +285,7 @@ void DivPlatformGenesis::tick(bool sysTick) {
chan[i].freqChanged=true;
}
} else {
if (chan[i].std.arp.had) {
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11);
}
@ -1224,6 +1224,7 @@ void DivPlatformGenesis::setFlags(const DivConfig& flags) {
break;
}
ladder=flags.getBool("ladderEffect",false);
noExtMacros=flags.getBool("noExtMacros",false);
OPN2_SetChipType(ladder?ym3438_mode_ym2612:0);
if (useYMFM) {
if (fm_ymfm!=NULL) delete fm_ymfm;

View File

@ -116,7 +116,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
int softPCMTimer;
bool extMode, softPCM, useYMFM;
bool extMode, softPCM, noExtMacros, useYMFM;
bool ladder;
unsigned char dacVolTable[128];

View File

@ -52,6 +52,15 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
chan[2].state.ams=ins->fm.ams;
chan[2].state.op[ordch]=ins->fm.op[ordch];
}
if (noExtMacros) {
opChan[ch].macroInit(NULL);
} else {
opChan[ch].macroInit(ins);
}
if (!opChan[ch].std.vol.will) {
opChan[ch].outVol=opChan[ch].vol;
}
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator& op=chan[2].state.op[ordch];
@ -60,7 +69,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
rWrite(baseAddr+0x40,127);
} else {
if (opChan[ch].insChanged) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
}
if (opChan[ch].insChanged) {
@ -81,6 +90,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
if (c.value!=DIV_NOTE_NULL) {
opChan[ch].baseFreq=NOTE_FNUM_BLOCK(c.value,11);
opChan[ch].portaPause=false;
opChan[ch].note=c.value;
opChan[ch].freqChanged=true;
}
opChan[ch].keyOn=true;
@ -92,14 +102,28 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
opChan[ch].keyOn=false;
opChan[ch].active=false;
break;
case DIV_CMD_NOTE_OFF_ENV:
if (noExtMacros) break;
opChan[ch].keyOff=true;
opChan[ch].keyOn=false;
opChan[ch].active=false;
opChan[ch].std.release();
break;
case DIV_CMD_ENV_RELEASE:
if (noExtMacros) break;
opChan[ch].std.release();
break;
case DIV_CMD_VOLUME: {
opChan[ch].vol=c.value;
if (!opChan[ch].std.vol.has) {
opChan[ch].outVol=c.value;
}
unsigned short baseAddr=chanOffs[2]|opOffs[ordch];
DivInstrumentFM::Operator& op=chan[2].state.op[ordch];
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
}
break;
}
@ -210,7 +234,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
if (isOpMuted[ch]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(2,c.value)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
@ -393,8 +417,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].vol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
immWrite(baseAddr+0x40,op.tl);
@ -438,6 +462,91 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
}
}
if (extMode && !noExtMacros) for (int i=0; i<4; i++) {
opChan[i].std.next();
if (opChan[i].std.vol.had) {
opChan[i].outVol=VOL_SCALE_LOG_BROKEN(opChan[i].vol,MIN(127,opChan[i].std.vol.val),127);
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]];
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (opChan[i].std.arp.had) {
if (!opChan[i].inPorta) {
opChan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(opChan[i].note,opChan[i].std.arp.val),11);
}
opChan[i].freqChanged=true;
}
if (opChan[i].std.pitch.had) {
if (opChan[i].std.pitch.mode) {
opChan[i].pitch2+=opChan[i].std.pitch.val;
CLAMP_VAR(opChan[i].pitch2,-32768,32767);
} else {
opChan[i].pitch2=opChan[i].std.pitch.val;
}
opChan[i].freqChanged=true;
}
// param macros
unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[i]];
DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[i]];
DivMacroInt::IntOp& m=opChan[i].std.op[orderedOps[i]];
if (m.am.had) {
op.am=m.am.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.ar.had) {
op.ar=m.ar.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dr.had) {
op.dr=m.dr.val;
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
}
if (m.mult.had) {
op.mult=m.mult.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.rr.had) {
op.rr=m.rr.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.sl.had) {
op.sl=m.sl.val;
rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4));
}
if (m.tl.had) {
op.tl=127-m.tl.val;
if (isOpMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[i].outVol&0x7f,127));
}
}
if (m.rs.had) {
op.rs=m.rs.val;
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6));
}
if (m.dt.had) {
op.dt=m.dt.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
}
if (m.d2r.had) {
op.d2r=m.d2r.val;
rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31);
}
if (m.ssg.had) {
op.ssgEnv=m.ssg.val;
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
}
}
DivPlatformGenesis::tick(sysTick);
bool writeNoteOn=false;
@ -527,7 +636,7 @@ void DivPlatformGenesisExt::forceIns() {
if (isOpMuted[j]) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].vol&0x7f,127));
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[j].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
}
@ -601,7 +710,9 @@ void DivPlatformGenesisExt::reset() {
for (int i=0; i<4; i++) {
opChan[i]=DivPlatformGenesisExt::OpChannel();
opChan[i].std.setEngine(parent);
opChan[i].vol=127;
opChan[i].outVol=127;
}
// channel 3 mode

View File

@ -25,11 +25,15 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
struct OpChannel {
DivMacroInt std;
unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins, note;
signed char konCycles;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
int vol;
int vol, outVol;
unsigned char pan;
void macroInit(DivInstrument* which) {
std.init(which);
pitch2=0;
}
OpChannel():
freqH(0),
freqL(0),
@ -39,6 +43,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
pitch2(0),
portaPauseFreq(0),
ins(-1),
note(0),
active(false),
insChanged(true),
freqChanged(false),
@ -48,6 +53,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
inPorta(false),
mask(true),
vol(0),
outVol(0),
pan(3) {}
};
OpChannel opChan[4];

View File

@ -587,7 +587,17 @@ bool DivPlatformPCSpeaker::keyOffAffectsArp(int ch) {
}
void DivPlatformPCSpeaker::setFlags(const DivConfig& flags) {
chipClock=COLOR_NTSC/3.0;
switch (flags.getInt("clockSel",0)) {
case 1: // PC-98
chipClock=38400*52;
break;
case 2: // PC-98
chipClock=38400*64;
break;
default: // IBM PC
chipClock=COLOR_NTSC/3.0;
break;
}
rate=chipClock/PCSPKR_DIVIDER;
speakerType=flags.getInt("speakerType",0)&3;
oscBuf->rate=rate;

View File

@ -129,7 +129,7 @@ void DivPlatformSMS::tick(bool sysTick) {
if (chan[i].outVol<0) chan[i].outVol=0;
// old formula
// ((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))>>4;
rWrite(0,0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15))));
chan[i].writeVol=true;
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
@ -235,6 +235,12 @@ void DivPlatformSMS::tick(bool sysTick) {
chan[3].freqChanged=false;
updateSNMode=false;
}
for (int i=0; i<4; i++) {
if (chan[i].writeVol) {
rWrite(0,0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15))));
chan[i].writeVol=false;
}
}
}
int DivPlatformSMS::dispatch(DivCommand c) {
@ -249,9 +255,11 @@ int DivPlatformSMS::dispatch(DivCommand c) {
chan[c.chan].actualNote=c.value;
}
chan[c.chan].active=true;
if (!parent->song.brokenOutVol2) {
rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
}
//if (!parent->song.brokenOutVol2) {
chan[c.chan].writeVol=true;
chan[c.chan].outVol=chan[c.chan].vol;
//rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
//}
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
chan[c.chan].outVol=chan[c.chan].vol;
@ -276,7 +284,9 @@ int DivPlatformSMS::dispatch(DivCommand c) {
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
}
if (chan[c.chan].active) rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
if (chan[c.chan].active) {
chan[c.chan].writeVol=true;
}
}
break;
case DIV_CMD_GET_VOLUME:
@ -356,7 +366,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
void DivPlatformSMS::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (chan[ch].active) rWrite(0,0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15))));
if (chan[ch].active) chan[ch].writeVol=true;
}
void DivPlatformSMS::forceIns() {

View File

@ -31,7 +31,7 @@ extern "C" {
class DivPlatformSMS: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, note, actualNote, ins;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, writeVol;
signed char vol, outVol;
DivMacroInt std;
void macroInit(DivInstrument* which) {
@ -52,6 +52,7 @@ class DivPlatformSMS: public DivDispatch {
keyOn(false),
keyOff(false),
inPorta(false),
writeVol(false),
vol(15),
outVol(15) {}
};

View File

@ -952,6 +952,7 @@ void DivPlatformYM2203::setFlags(const DivConfig& flags) {
ayDiv=16;
break;
}
noExtMacros=flags.getBool("noExtMacros",false);
rate=fm->sample_rate(chipClock);
for (int i=0; i<6; i++) {
oscBuf[i]->rate=rate;

View File

@ -91,7 +91,7 @@ class DivPlatformYM2203: public DivPlatformOPN {
DivPlatformAY8910* ay;
unsigned char sampleBank;
bool extMode;
bool extMode, noExtMacros;
unsigned char prescale;
friend void putDispatchChip(void*,int);

View File

@ -1394,6 +1394,7 @@ void DivPlatformYM2608::setFlags(const DivConfig& flags) {
ayDiv=32;
break;
}
noExtMacros=flags.getBool("noExtMacros",false);
rate=fm->sample_rate(chipClock);
for (int i=0; i<16; i++) {
oscBuf[i]->rate=rate;

View File

@ -106,7 +106,7 @@ class DivPlatformYM2608: public DivPlatformOPN {
unsigned char writeRSSOff, writeRSSOn;
int globalRSSVolume;
bool extMode;
bool extMode, noExtMacros;
unsigned char prescale;
double NOTE_OPNB(int ch, int note);

View File

@ -141,7 +141,7 @@ template<int ChanNum> class DivPlatformYM2610Base: public DivPlatformOPN {
unsigned char sampleBank;
bool extMode;
bool extMode, noExtMacros;
unsigned char writeADPCMAOff, writeADPCMAOn;
int globalADPCMAVolume;
@ -269,6 +269,7 @@ template<int ChanNum> class DivPlatformYM2610Base: public DivPlatformOPN {
chipClock=8000000.0;
break;
}
noExtMacros=flags.getBool("noExtMacros",false);
rate=chipClock/16;
for (int i=0; i<ChanNum; i++) {
oscBuf[i]->rate=rate;

View File

@ -975,6 +975,20 @@ void DivEngine::nextRow() {
printf("| %.2x:%s | \x1b[1;33m%3d%s\x1b[m\n",curOrder,pb1,curRow,pb3);
}
if (curSubSong->hilightA>0) {
if ((curRow%curSubSong->hilightA)==0) {
pendingMetroTick=1;
elapsedBeats++;
}
}
if (curSubSong->hilightB>0) {
if ((curRow%curSubSong->hilightB)==0) {
pendingMetroTick=2;
elapsedBars++;
elapsedBeats=0;
}
}
prevOrder=curOrder;
prevRow=curRow;
@ -1608,16 +1622,6 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
// 2. check whether we gonna tick
if (cycles<=0) {
// we have to tick
if (!freelance && stepPlay!=-1 && subticks==1) {
unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC);
if (realPos>=size) realPos=size-1;
if (curSubSong->hilightA>0) {
if ((curRow%curSubSong->hilightA)==0 && ticks==1) metroTick[realPos]=1;
}
if (curSubSong->hilightB>0) {
if ((curRow%curSubSong->hilightB)==0 && ticks==1) metroTick[realPos]=2;
}
}
if (nextTick()) {
lastLoopPos=size-(runLeftG>>MASTER_CLOCK_PREC);
logD("last loop pos: %d for a size of %d and runLeftG of %d",lastLoopPos,size,runLeftG);
@ -1634,6 +1638,12 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
}
}
}
if (pendingMetroTick) {
unsigned int realPos=size-(runLeftG>>MASTER_CLOCK_PREC);
if (realPos>=size) realPos=size-1;
metroTick[realPos]=pendingMetroTick;
pendingMetroTick=0;
}
} else {
// 3. tick the clock and fill buffers as needed
if (cycles<runLeftG) {

View File

@ -23,6 +23,10 @@
#include "song.h"
#include "../ta-log.h"
DivSysDef* DivEngine::sysDefs[256];
DivSystem DivEngine::sysFileMapFur[256];
DivSystem DivEngine::sysFileMapDMF[256];
DivSystem DivEngine::systemFromFileFur(unsigned char val) {
return sysFileMapFur[val];
}

View File

@ -94,10 +94,12 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
if (VERA >= 0) disCont[VERA].dispatch->toggleRegisterDump(true);
if (YM >= 0) {
disCont[YM].dispatch->toggleRegisterDump(true);
zsm.writeYM(0x18,0); // initialize the LFO freq to 0
// note - I think there's a bug where Furnace writes AMD/PMD=max
// that shouldn't be there, requiring this initialization that shouldn't
// be there for ZSM.
// 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.
}
while (!done) {

View File

@ -66,6 +66,7 @@ const char* aboutLine[]={
"BlueElectric05",
"breakthetargets",
"brickblock369",
"Burnt Fishy",
"CaptainMalware",
"DeMOSic",
"DevEd",
@ -91,6 +92,7 @@ const char* aboutLine[]={
"Raijin",
"SnugglyBun",
"SuperJet Spade",
"TakuikaNinja",
"TheDuccinator",
"theloredev",
"TheRealHedgehogSonic",
@ -153,7 +155,6 @@ const char* aboutLine[]={
"MSM6295 emulator by cam900",
"",
"greetings to:",
"Fractal Sound team",
"NEOART Costa Rica",
"all members of Deflers of Noice!",
"",

116
src/gui/clock.cpp Normal file
View File

@ -0,0 +1,116 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 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 "gui.h"
#include "imgui_internal.h"
#include "imgui.h"
void FurnaceGUI::drawClock() {
if (nextWindow==GUI_WINDOW_CLOCK) {
clockOpen=true;
ImGui::SetNextWindowFocus();
nextWindow=GUI_WINDOW_NOTHING;
}
if (!clockOpen) return;
if (ImGui::Begin("Clock",&clockOpen,globalWinFlags)) {
int row=e->getRow();
int elapsedBars=e->getElapsedBars();
int elapsedBeats=e->getElapsedBeats();
bool playing=e->isPlaying();
if (clockShowRow) {
ImGui::PushFont(bigFont);
ImGui::Text("%.3d:%.3d",e->getOrder(),row);
ImGui::PopFont();
}
if (clockShowBeat) {
ImGui::PushFont(bigFont);
ImGui::Text("%.3d:%.1d",elapsedBars,elapsedBeats+1);
ImGui::PopFont();
}
if (clockShowMetro) {
ImDrawList* dl=ImGui::GetWindowDrawList();
ImGuiWindow* window=ImGui::GetCurrentWindow();
ImVec2 size=ImGui::GetContentRegionAvail();
size.y=12.0f*dpiScale;
ImVec2 minArea=window->DC.CursorPos;
ImVec2 maxArea=ImVec2(
minArea.x+size.x,
minArea.y+size.y
);
ImRect rect=ImRect(minArea,maxArea);
/*ImRect inRect=rect;
inRect.Min.x+=dpiScale;
inRect.Min.y+=dpiScale;
inRect.Max.x-=dpiScale;
inRect.Max.y-=dpiScale;*/
ImGuiStyle& style=ImGui::GetStyle();
ImGui::ItemSize(size,style.FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("metroQ"))) {
int h1=e->curSubSong->hilightA;
int h2=e->curSubSong->hilightB;
if (h1>0 && h2>0) {
int beats=(h2+(h1-1))/h1;
if (beats<0) beats=1;
if (beats>16) beats=16;
if (playing) {
if (elapsedBeats!=oldBeat || elapsedBars!=oldBar) {
if (elapsedBeats>15) elapsedBeats=15;
clockMetroTick[elapsedBeats]=1.0f;
oldBeat=elapsedBeats;
oldBar=elapsedBars;
}
} else {
oldBeat=-1;
oldBar=-1;
}
for (int i=0; i<beats; i++) {
ImVec2 minB=ImLerp(minArea,maxArea,ImVec2((float)i/(float)beats,0.0f));
ImVec2 maxB=ImLerp(minArea,maxArea,ImVec2((float)(i+1)/(float)beats,1.0f));
ImVec4 col=ImLerp(uiColors[GUI_COLOR_CLOCK_BEAT_LOW],uiColors[GUI_COLOR_CLOCK_BEAT_HIGH],clockMetroTick[i]);
dl->AddQuadFilled(
ImLerp(minB,maxB,ImVec2(0.35f,0.0f)),
ImLerp(minB,maxB,ImVec2(0.9f,0.0f)),
ImLerp(minB,maxB,ImVec2(0.65f,1.0f)),
ImLerp(minB,maxB,ImVec2(0.1f,1.0f)),
ImGui::GetColorU32(col)
);
if (elapsedBeats==i && playing) {
clockMetroTick[i]-=0.1f*ImGui::GetIO().DeltaTime*60.0f;
if (clockMetroTick[i]<0.3f) clockMetroTick[i]=0.3f;
} else {
clockMetroTick[i]-=0.1f*ImGui::GetIO().DeltaTime*60.0f;
if (clockMetroTick[i]<0.0f) clockMetroTick[i]=0.0f;
}
}
}
}
}
if (clockShowTime) {
int totalTicks=e->getTotalTicks();
int totalSeconds=e->getTotalSeconds();
ImGui::PushFont(bigFont);
ImGui::Text("%.2d:%.2d.%.2d",(totalSeconds/60),totalSeconds%60,totalTicks/10000);
ImGui::PopFont();
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SPOILER;
ImGui::End();
}

View File

@ -60,6 +60,11 @@ void FurnaceGUI::drawInsList(bool asChild) {
}
ImGui::EndPopup();
}
} else {
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
displayInsTypeList=true;
displayInsTypeListMakeInsSample=-1;
}
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FILES_O "##InsClone")) {

View File

@ -1291,14 +1291,54 @@ void FurnaceGUI::doAction(int what) {
break;
case GUI_ACTION_SAMPLE_MAKE_INS: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
// determine instrument type
std::vector<DivInstrumentType> tempTypeList=e->getPossibleInsTypes();
makeInsTypeList.clear();
for (DivInstrumentType& i: tempTypeList) {
if (i==DIV_INS_PCE ||
i==DIV_INS_MSM6258 ||
i==DIV_INS_MSM6295 ||
i==DIV_INS_ADPCMA ||
i==DIV_INS_ADPCMB ||
i==DIV_INS_SEGAPCM ||
i==DIV_INS_QSOUND ||
i==DIV_INS_YMZ280B ||
i==DIV_INS_RF5C68 ||
i==DIV_INS_MULTIPCM ||
i==DIV_INS_MIKEY ||
i==DIV_INS_X1_010 ||
i==DIV_INS_SWAN ||
i==DIV_INS_AY ||
i==DIV_INS_AY8930 ||
i==DIV_INS_VRC6 ||
i==DIV_INS_SU ||
i==DIV_INS_SNES ||
i==DIV_INS_ES5506) {
makeInsTypeList.push_back(i);
}
}
if (makeInsTypeList.size()>1) {
displayInsTypeList=true;
displayInsTypeListMakeInsSample=curSample;
break;
}
DivInstrumentType insType=DIV_INS_AMIGA;
if (!makeInsTypeList.empty()) {
insType=makeInsTypeList[0];
}
DivSample* sample=e->song.sample[curSample];
curIns=e->addInstrument(cursor.xCoarse);
if (curIns==-1) {
showError("too many instruments!");
} else {
e->song.ins[curIns]->type=DIV_INS_AMIGA;
e->song.ins[curIns]->type=insType;
e->song.ins[curIns]->name=sample->name;
e->song.ins[curIns]->amiga.initSample=curSample;
if (insType!=DIV_INS_AMIGA) e->song.ins[curIns]->amiga.useSample=true;
nextWindow=GUI_WINDOW_INS_EDIT;
MARK_MODIFIED;
wavePreviewInit=true;

View File

@ -242,6 +242,7 @@ void FurnaceGUI::drawMobileControls() {
ImGui::SameLine();
if (ImGui::Button("Settings")) {
mobileMenuOpen=false;
settingsOpen=true;
}
ImGui::SameLine();
if (ImGui::Button("Log")) {

View File

@ -2651,8 +2651,11 @@ int _processEvent(void* instance, SDL_Event* event) {
int FurnaceGUI::processEvent(SDL_Event* ev) {
#ifdef IS_MOBILE
if (ev->type==SDL_APP_WILLENTERBACKGROUND) {
// TODO: save "last state" and potentially suspend engine
if (ev->type==SDL_APP_TERMINATING) {
// TODO: save last song state here
} else if (ev->type==SDL_APP_WILLENTERBACKGROUND) {
commitState();
e->saveConf();
}
#endif
if (ev->type==SDL_KEYDOWN) {
@ -2958,7 +2961,11 @@ bool FurnaceGUI::detectOutOfBoundsWindow() {
}
bool FurnaceGUI::loop() {
#ifdef IS_MOBILE
bool doThreadedInput=true;
#else
bool doThreadedInput=!settings.noThreadedInput;
#endif
if (doThreadedInput) {
logD("key input: event filter");
SDL_SetEventFilter(_processEvent,this);
@ -3620,6 +3627,7 @@ bool FurnaceGUI::loop() {
if (ImGui::MenuItem("oscilloscope (master)",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen;
if (ImGui::MenuItem("oscilloscope (per-channel)",BIND_FOR(GUI_ACTION_WINDOW_CHAN_OSC),chanOscOpen)) chanOscOpen=!chanOscOpen;
if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen;
if (ImGui::MenuItem("clock",BIND_FOR(GUI_ACTION_WINDOW_CLOCK),clockOpen)) clockOpen=!clockOpen;
if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen;
if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen;
if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen;
@ -3747,6 +3755,7 @@ bool FurnaceGUI::loop() {
}
globalWinFlags=0;
drawSettings();
drawDebug();
drawLog();
} else {
@ -3782,6 +3791,7 @@ bool FurnaceGUI::loop() {
drawChannels();
drawPatManager();
drawSysManager();
drawClock();
drawRegView();
drawLog();
drawEffectList();
@ -4399,6 +4409,11 @@ bool FurnaceGUI::loop() {
ImGui::OpenPopup("Import Raw Sample");
}
if (displayInsTypeList) {
displayInsTypeList=false;
ImGui::OpenPopup("InsTypeList");
}
if (displayExporting) {
displayExporting=false;
ImGui::OpenPopup("Rendering...");
@ -4428,9 +4443,14 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(canvasW,canvasH));
ImVec2 newSongMinSize=mobileUI?ImVec2(canvasW-(portrait?0:(60.0*dpiScale)),canvasH-60.0*dpiScale):ImVec2(400.0f*dpiScale,200.0f*dpiScale);
ImVec2 newSongMaxSize=ImVec2(canvasW-((mobileUI && !portrait)?(60.0*dpiScale):0),canvasH-(mobileUI?(60.0*dpiScale):0));
ImGui::SetNextWindowSizeConstraints(newSongMinSize,newSongMaxSize);
if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoScrollbar)) {
ImGui::SetWindowPos(ImVec2(((canvasW)-ImGui::GetWindowSize().x)*0.5,((canvasH)-ImGui::GetWindowSize().y)*0.5));
if (ImGui::GetWindowSize().x<newSongMinSize.x || ImGui::GetWindowSize().y<newSongMinSize.y) {
ImGui::SetWindowSize(newSongMinSize,ImGuiCond_Always);
}
drawNewSong();
ImGui::EndPopup();
}
@ -4774,6 +4794,31 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
if (ImGui::BeginPopup("InsTypeList",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) {
char temp[1024];
for (DivInstrumentType& i: makeInsTypeList) {
strncpy(temp,insTypes[i],1023);
if (ImGui::MenuItem(temp)) {
// create ins
curIns=e->addInstrument(-1,i);
if (curIns==-1) {
showError("too many instruments!");
} else {
if (displayInsTypeListMakeInsSample>=0 && displayInsTypeListMakeInsSample<(int)e->song.sample.size()) {
e->song.ins[curIns]->type=i;
e->song.ins[curIns]->name=e->song.sample[displayInsTypeListMakeInsSample]->name;
e->song.ins[curIns]->amiga.initSample=displayInsTypeListMakeInsSample;
if (i!=DIV_INS_AMIGA) e->song.ins[curIns]->amiga.useSample=true;
nextWindow=GUI_WINDOW_INS_EDIT;
wavePreviewInit=true;
}
MARK_MODIFIED;
}
}
}
ImGui::EndPopup();
}
// TODO:
// - multiple selection
// - replace instrument
@ -5020,6 +5065,7 @@ bool FurnaceGUI::init() {
channelsOpen=e->getConfBool("channelsOpen",false);
patManagerOpen=e->getConfBool("patManagerOpen",false);
sysManagerOpen=e->getConfBool("sysManagerOpen",false);
clockOpen=e->getConfBool("clockOpen",false);
regViewOpen=e->getConfBool("regViewOpen",false);
logOpen=e->getConfBool("logOpen",false);
effectListOpen=e->getConfBool("effectListOpen",false);
@ -5333,15 +5379,10 @@ bool FurnaceGUI::init() {
return true;
}
bool FurnaceGUI::finish() {
void FurnaceGUI::commitState() {
if (!mobileUI) {
ImGui::SaveIniSettingsToDisk(finalLayoutPath);
}
ImGui_ImplSDLRenderer_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_DestroyRenderer(sdlRend);
SDL_DestroyWindow(sdlWin);
e->setConf("configVersion",(int)DIV_ENGINE_VERSION);
@ -5383,6 +5424,7 @@ bool FurnaceGUI::finish() {
e->setConf("channelsOpen",channelsOpen);
e->setConf("patManagerOpen",patManagerOpen);
e->setConf("sysManagerOpen",sysManagerOpen);
e->setConf("clockOpen",clockOpen);
e->setConf("regViewOpen",regViewOpen);
e->setConf("logOpen",logOpen);
e->setConf("effectListOpen",effectListOpen);
@ -5454,6 +5496,15 @@ bool FurnaceGUI::finish() {
e->setConf(key,recentFile[i]);
}
}
}
bool FurnaceGUI::finish() {
commitState();
ImGui_ImplSDLRenderer_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_DestroyRenderer(sdlRend);
SDL_DestroyWindow(sdlWin);
for (int i=0; i<DIV_MAX_CHANS; i++) {
delete oldPat[i];
@ -5485,6 +5536,7 @@ FurnaceGUI::FurnaceGUI():
zsmExportLoop(true),
vgmExportPatternHints(false),
vgmExportDirectStream(false),
displayInsTypeList(false),
portrait(false),
injectBackUp(false),
mobileMenuOpen(false),
@ -5505,6 +5557,7 @@ FurnaceGUI::FurnaceGUI():
zsmExportTickRate(60),
macroPointSize(16),
waveEditStyle(0),
displayInsTypeListMakeInsSample(-1),
mobileMenuPos(0.0f),
autoButtonSize(0.0f),
curSysSection(NULL),
@ -5571,6 +5624,8 @@ FurnaceGUI::FurnaceGUI():
dragSourceY(0),
dragDestinationX(0),
dragDestinationY(0),
oldBeat(-1),
oldBar(-1),
exportFadeOut(5.0),
editControlsOpen(true),
ordersOpen(true),
@ -5603,6 +5658,12 @@ FurnaceGUI::FurnaceGUI():
spoilerOpen(false),
patManagerOpen(false),
sysManagerOpen(false),
clockOpen(false),
clockShowReal(true),
clockShowRow(true),
clockShowBeat(true),
clockShowMetro(true),
clockShowTime(true),
selecting(false),
selectingFull(false),
dragging(false),
@ -5631,7 +5692,6 @@ FurnaceGUI::FurnaceGUI():
curWindowLast(GUI_WINDOW_NOTHING),
curWindowThreadSafe(GUI_WINDOW_NOTHING),
lastPatternWidth(0.0f),
nextDesc(NULL),
latchNote(-1),
latchIns(-2),
latchVol(-1),

View File

@ -243,6 +243,10 @@ enum FurnaceGUIColors {
GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE,
GUI_COLOR_PIANO_KEY_TOP_ACTIVE,
GUI_COLOR_CLOCK_TEXT,
GUI_COLOR_CLOCK_BEAT_LOW,
GUI_COLOR_CLOCK_BEAT_HIGH,
GUI_COLOR_LOGLEVEL_ERROR,
GUI_COLOR_LOGLEVEL_WARNING,
GUI_COLOR_LOGLEVEL_INFO,
@ -285,6 +289,7 @@ enum FurnaceGUIWindows {
GUI_WINDOW_CHAN_OSC,
GUI_WINDOW_SUBSONGS,
GUI_WINDOW_FIND,
GUI_WINDOW_CLOCK,
GUI_WINDOW_SPOILER
};
@ -425,6 +430,7 @@ enum FurnaceGUIActions {
GUI_ACTION_WINDOW_CHAN_OSC,
GUI_ACTION_WINDOW_SUBSONGS,
GUI_ACTION_WINDOW_FIND,
GUI_ACTION_WINDOW_CLOCK,
GUI_ACTION_COLLAPSE_WINDOW,
GUI_ACTION_CLOSE_WINDOW,
@ -893,12 +899,22 @@ struct Gradient2D {
}
};
struct FurnaceGUISysDefChip {
DivSystem sys;
int vol, pan;
const char* flags;
FurnaceGUISysDefChip(DivSystem s, int v, int p, const char* f):
sys(s),
vol(v),
pan(p),
flags(f) {}
};
struct FurnaceGUISysDef {
const char* name;
std::vector<int> definition;
FurnaceGUISysDef(const char* n, std::initializer_list<int> def):
name(n), definition(def) {
}
String definition;
FurnaceGUISysDef(const char* n, std::initializer_list<int> def);
FurnaceGUISysDef(const char* n, std::initializer_list<FurnaceGUISysDefChip> def);
};
struct FurnaceGUISysCategory {
@ -945,6 +961,12 @@ struct FurnaceGUIMacroDesc {
}
};
struct FurnaceGUIMacroEditState {
int selectedMacro;
FurnaceGUIMacroEditState():
selectedMacro(0) {}
};
enum FurnaceGUIFindQueryModes {
GUI_QUERY_IGNORE=0,
GUI_QUERY_MATCH,
@ -1033,9 +1055,11 @@ class FurnaceGUI {
std::vector<DivSystem> sysSearchResults;
std::vector<FurnaceGUISysDef> newSongSearchResults;
std::deque<String> recentFile;
std::vector<DivInstrumentType> makeInsTypeList;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, vgmExportPatternHints;
bool vgmExportDirectStream;
bool vgmExportDirectStream, displayInsTypeList;
bool portrait, injectBackUp, mobileMenuOpen;
bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly;
@ -1046,6 +1070,7 @@ class FurnaceGUI {
int zsmExportTickRate;
int macroPointSize;
int waveEditStyle;
int displayInsTypeListMakeInsSample;
float mobileMenuPos, autoButtonSize;
const int* curSysSection;
@ -1216,6 +1241,7 @@ class FurnaceGUI {
int persistFadeOut;
int exportLoops;
double exportFadeOut;
int macroLayout;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -1346,6 +1372,7 @@ class FurnaceGUI {
persistFadeOut(1),
exportLoops(0),
exportFadeOut(0.0),
macroLayout(0),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -1367,7 +1394,7 @@ class FurnaceGUI {
int curIns, curWave, curSample, curOctave, curOrder, prevIns, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan, soloTimeout, orderEditMode, orderCursor;
int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory, latchTarget;
int wheelX, wheelY, dragSourceX, dragSourceY, dragDestinationX, dragDestinationY;
int wheelX, wheelY, dragSourceX, dragSourceY, dragDestinationX, dragDestinationY, oldBeat, oldBar;
double exportFadeOut;
@ -1375,7 +1402,10 @@ class FurnaceGUI {
bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen;
bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen;
bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen;
bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen;
bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen, clockOpen;
bool clockShowReal, clockShowRow, clockShowBeat, clockShowMetro, clockShowTime;
float clockMetroTick[16];
SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd;
bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
@ -1387,7 +1417,7 @@ class FurnaceGUI {
float patChanX[DIV_MAX_CHANS+1];
float patChanSlideY[DIV_MAX_CHANS+1];
float lastPatternWidth;
const int* nextDesc;
String nextDesc;
String nextDescName;
OperationMask opMaskDelete, opMaskPullDelete, opMaskInsert, opMaskPaste, opMaskTransposeNote, opMaskTransposeValue;
@ -1511,6 +1541,8 @@ class FurnaceGUI {
int macroLoopDragLen;
bool macroLoopDragActive;
FurnaceGUIMacroEditState macroEditStateFM, macroEditStateOP[4], macroEditStateMacros;
ImVec2 waveDragStart;
ImVec2 waveDragAreaSize;
int* waveDragTarget;
@ -1675,7 +1707,8 @@ class FurnaceGUI {
void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel);
void drawMacros(std::vector<FurnaceGUIMacroDesc>& macros);
void drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float availableWidth, int index);
void drawMacros(std::vector<FurnaceGUIMacroDesc>& macros, FurnaceGUIMacroEditState& state);
void actualWaveList();
void actualSampleList();
@ -1717,6 +1750,7 @@ class FurnaceGUI {
void drawSubSongs();
void drawFindReplace();
void drawSpoiler();
void drawClock();
void parseKeybinds();
void promptKey(int which);
@ -1736,6 +1770,7 @@ class FurnaceGUI {
void syncSettings();
void commitSettings();
void commitState();
void processDrags(int dragX, int dragY);
void processPoint(SDL_Event& ev);

View File

@ -513,6 +513,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("WINDOW_CHAN_OSC", "Oscilloscope (per-channel)", 0),
D("WINDOW_SUBSONGS", "Subsongs", 0),
D("WINDOW_FIND", "Find/Replace", FURKMOD_CMD|SDLK_f),
D("WINDOW_CLOCK", "Clock", 0),
D("COLLAPSE_WINDOW", "Collapse/expand current window", 0),
D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE),
@ -874,6 +875,10 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
D(GUI_COLOR_PIANO_KEY_BOTTOM_ACTIVE,"",ImVec4(0.5f,0.5f,0.5f,1.0f)),
D(GUI_COLOR_PIANO_KEY_TOP_ACTIVE,"",ImVec4(0.4f,0.4f,0.4f,1.0f)),
D(GUI_COLOR_CLOCK_TEXT,"",ImVec4(1.0f,1.0f,1.0f,1.0f)),
D(GUI_COLOR_CLOCK_BEAT_LOW,"",ImVec4(0.1f,0.13f,0.25f,1.0f)),
D(GUI_COLOR_CLOCK_BEAT_HIGH,"",ImVec4(0.5f,0.8f,1.0f,1.0f)),
D(GUI_COLOR_LOGLEVEL_ERROR,"",ImVec4(1.0f,0.2f,0.2f,1.0f)),
D(GUI_COLOR_LOGLEVEL_WARNING,"",ImVec4(1.0f,1.0f,0.2f,1.0f)),
D(GUI_COLOR_LOGLEVEL_INFO,"",ImVec4(0.4f,1.0f,0.4f,1.0f)),

File diff suppressed because it is too large Load Diff

View File

@ -107,7 +107,7 @@ void FurnaceGUI::drawNewSong() {
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) {
nextDesc=i.definition.data();
nextDesc=i.definition;
nextDescName=i.name;
accepted=true;
}
@ -129,7 +129,7 @@ void FurnaceGUI::drawNewSong() {
ImGui::CloseCurrentPopup();
} else {
unsigned int selection=rand()%newSystemCat->systems.size();
nextDesc=newSystemCat->systems[selection].definition.data();
nextDesc=newSystemCat->systems[selection].definition;
nextDescName=newSystemCat->systems[selection].name;
accepted=true;
}
@ -143,16 +143,7 @@ void FurnaceGUI::drawNewSong() {
}
if (accepted) {
// TODO: remove after porting all presets to new format
String oldDescFormat;
for (const int* i=nextDesc; *i; i+=4) {
oldDescFormat+=fmt::sprintf("%d ",e->systemToFileFur((DivSystem)i[0]));
oldDescFormat+=fmt::sprintf("%d ",i[1]);
oldDescFormat+=fmt::sprintf("%d ",i[2]);
oldDescFormat+=fmt::sprintf("%d ",i[3]);
}
String oldDesc=e->decodeSysDesc(oldDescFormat.c_str());
e->createNew(oldDesc.c_str(),nextDescName);
e->createNew(nextDesc.c_str(),nextDescName,false);
undoHist.clear();
redoHist.clear();
curFileName="";

View File

@ -219,11 +219,14 @@ void FurnaceGUI::drawPiano() {
// render piano
//ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) {
ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"));
bool canInput=false;
if (ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"))) {
canInput=true;
}
if (view) {
int notes=oct*12;
// evaluate input
for (TouchPoint& i: activePoints) {
if (canInput) for (TouchPoint& i: activePoints) {
if (rect.Contains(ImVec2(i.x,i.y))) {
int note=(((i.x-rect.Min.x)/(rect.Max.x-rect.Min.x))*notes)+12*off;
if (note<0) continue;

File diff suppressed because it is too large Load Diff

View File

@ -197,10 +197,12 @@ double getScaleFactor(const char* driverHint) {
// SDL fallback
#ifdef ANDROID
float dpiScaleF=192.0f;
float dpiScaleF=160.0f;
if (SDL_GetDisplayDPI(0,&dpiScaleF,NULL,NULL)==0) {
ret=round(dpiScaleF/192.0f);
ret=dpiScaleF/160.0f;
if (ret<1) ret=1;
logI("dpiScaleF: %f",dpiScaleF);
logI("ret: %f",ret);
}
#else

View File

@ -33,14 +33,14 @@
#define DEFAULT_NOTE_KEYS "5:7;6:4;7:3;8:16;10:6;11:8;12:24;13:10;16:11;17:9;18:26;19:28;20:12;21:17;22:1;23:19;24:23;25:5;26:14;27:2;28:21;29:0;30:100;31:13;32:15;34:18;35:20;36:22;38:25;39:27;43:100;46:101;47:29;48:31;53:102;"
#if defined(_WIN32) || defined(__APPLE__)
#if defined(_WIN32) || defined(__APPLE__) || defined(IS_MOBILE)
#define POWER_SAVE_DEFAULT 1
#else
// currently off on Linux/other due to Mesa catch-up behavior.
#define POWER_SAVE_DEFAULT 0
#endif
#if defined(__HAIKU__)
#if defined(__HAIKU__) || defined(IS_MOBILE)
// NFD doesn't support Haiku
#define SYS_FILE_DIALOG_DEFAULT 0
#else
@ -239,6 +239,12 @@ void FurnaceGUI::drawSettings() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!settingsOpen) return;
if (mobileUI) {
ImVec2 setWindowPos=ImVec2(0,0);
ImVec2 setWindowSize=ImVec2(canvasW,canvasH);
ImGui::SetNextWindowPos(setWindowPos);
ImGui::SetNextWindowSize(setWindowSize);
}
if (ImGui::Begin("Settings",&settingsOpen,ImGuiWindowFlags_NoDocking|globalWinFlags)) {
if (!settingsOpen) {
settingsOpen=true;
@ -332,7 +338,7 @@ void FurnaceGUI::drawSettings() {
settings.initialSys.set("pan0",0);
settings.initialSys.set("flags0","");
settings.initialSys.set("id1",e->systemToFileFur(DIV_SYSTEM_SMS));
settings.initialSys.set("vol1",64);
settings.initialSys.set("vol1",32);
settings.initialSys.set("pan1",0);
settings.initialSys.set("flags1","");
settings.initialSysName="Sega Genesis/Mega Drive";
@ -343,11 +349,15 @@ void FurnaceGUI::drawSettings() {
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputText("##InitSysName",&settings.initialSysName);
int sysCount=0;
int doRemove=-1;
for (size_t i=0; settings.initialSys.getInt(fmt::sprintf("id%d",i),0); i++) {
DivSystem sysID=e->systemFromFileFur(settings.initialSys.getInt(fmt::sprintf("id%d",i),0));
signed char sysVol=settings.initialSys.getInt(fmt::sprintf("vol%d",i),0);
signed char sysPan=settings.initialSys.getInt(fmt::sprintf("pan%d",i),0);
sysCount=i+1;
//bool doRemove=false;
bool doInvert=sysVol&128;
signed char vol=sysVol&127;
@ -373,7 +383,7 @@ void FurnaceGUI::drawSettings() {
ImGui::SameLine();
//ImGui::BeginDisabled(settings.initialSys.size()<=4);
if (ImGui::Button(ICON_FA_MINUS "##InitSysRemove")) {
//doRemove=true;
doRemove=i;
}
//ImGui::EndDisabled();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale));
@ -398,17 +408,31 @@ void FurnaceGUI::drawSettings() {
}
ImGui::PopID();
/*if (doRemove && settings.initialSys.size()>=8) {
settings.initialSys.erase(settings.initialSys.begin()+i,settings.initialSys.begin()+i+4);
i-=4;
}*/
}
if (ImGui::Button(ICON_FA_PLUS "##InitSysAdd")) {
/*settings.initialSys.push_back(DIV_SYSTEM_YM2612);
settings.initialSys.push_back(64);
settings.initialSys.push_back(0);
settings.initialSys.push_back(0);*/
if (doRemove>=0 && sysCount>1) {
for (int i=doRemove; i<sysCount-1; i++) {
int sysID=settings.initialSys.getInt(fmt::sprintf("id%d",i+1),0);
int sysVol=settings.initialSys.getInt(fmt::sprintf("vol%d",i+1),0);
int sysPan=settings.initialSys.getInt(fmt::sprintf("pan%d",i+1),0);
String sysFlags=settings.initialSys.getString(fmt::sprintf("flags%d",i+1),"");
settings.initialSys.set(fmt::sprintf("id%d",i),sysID);
settings.initialSys.set(fmt::sprintf("vol%d",i),sysVol);
settings.initialSys.set(fmt::sprintf("pan%d",i),sysPan);
settings.initialSys.set(fmt::sprintf("flags%d",i),sysFlags);
}
settings.initialSys.remove(fmt::sprintf("id%d",sysCount-1));
settings.initialSys.remove(fmt::sprintf("vol%d",sysCount-1));
settings.initialSys.remove(fmt::sprintf("pan%d",sysCount-1));
settings.initialSys.remove(fmt::sprintf("flags%d",sysCount-1));
}
if (sysCount<32) if (ImGui::Button(ICON_FA_PLUS "##InitSysAdd")) {
settings.initialSys.set(fmt::sprintf("id%d",sysCount),(int)e->systemToFileFur(DIV_SYSTEM_YM2612));
settings.initialSys.set(fmt::sprintf("vol%d",sysCount),64);
settings.initialSys.set(fmt::sprintf("pan%d",sysCount),0);
settings.initialSys.set(fmt::sprintf("flags%d",sysCount),"");
}
ImGui::Separator();
@ -522,6 +546,7 @@ void FurnaceGUI::drawSettings() {
ImGui::SetTooltip("saves power by lowering the frame rate to 2fps when idle.\nmay cause issues under Mesa drivers!");
}
#ifndef IS_MOBILE
bool noThreadedInputB=settings.noThreadedInput;
if (ImGui::Checkbox("Disable threaded input (restart after changing!)",&noThreadedInputB)) {
settings.noThreadedInput=noThreadedInputB;
@ -537,6 +562,7 @@ void FurnaceGUI::drawSettings() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("remembers the window's last position on startup.");
}
#endif
bool blankInsB=settings.blankIns;
if (ImGui::Checkbox("New instruments are blank",&blankInsB)) {
@ -1318,6 +1344,23 @@ void FurnaceGUI::drawSettings() {
settings.susPosition=1;
}
ImGui::Text("Macro editor layout:");
if (ImGui::RadioButton("Unified##mel0",settings.macroLayout==0)) {
settings.macroLayout=0;
}
if (ImGui::RadioButton("Mobile##mel1",settings.macroLayout==1)) {
settings.macroLayout=1;
}
if (ImGui::RadioButton("Grid##mel2",settings.macroLayout==2)) {
settings.macroLayout=2;
}
if (ImGui::RadioButton("Single (with list)##mel3",settings.macroLayout==3)) {
settings.macroLayout=3;
}
if (ImGui::RadioButton("Single (combo box)##mel4",settings.macroLayout==4)) {
settings.macroLayout=4;
}
ImGui::Separator();
ImGui::Text("Namco 163 chip name");
@ -2397,6 +2440,7 @@ void FurnaceGUI::syncSettings() {
settings.persistFadeOut=e->getConfInt("persistFadeOut",1);
settings.exportLoops=e->getConfInt("exportLoops",0);
settings.exportFadeOut=e->getConfDouble("exportFadeOut",0.0);
settings.macroLayout=e->getConfInt("macroLayout",0);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -2503,6 +2547,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.centerPattern,0,1);
clampSetting(settings.ordersCursor,0,1);
clampSetting(settings.persistFadeOut,0,1);
clampSetting(settings.macroLayout,0,4);
if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -2675,6 +2720,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("persistFadeOut",settings.persistFadeOut);
e->setConf("exportLoops",settings.exportLoops);
e->setConf("exportFadeOut",settings.exportFadeOut);
e->setConf("macroLayout",settings.macroLayout);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {
@ -3082,6 +3128,14 @@ void FurnaceGUI::popAccentColors() {
#define SYSTEM_PAT_FONT_PATH_1 "/System/Library/Fonts/SFNSMono.ttf"
#define SYSTEM_PAT_FONT_PATH_2 "/System/Library/Fonts/Courier New.ttf"
#define SYSTEM_PAT_FONT_PATH_3 "/System/Library/Fonts/Courier New.ttf"
#elif defined(ANDROID)
#define SYSTEM_FONT_PATH_1 "/system/fonts/Roboto-Regular.ttf"
#define SYSTEM_FONT_PATH_2 "/system/fonts/DroidSans.ttf"
#define SYSTEM_FONT_PATH_3 "/system/fonts/DroidSans.ttf"
// ???
#define SYSTEM_PAT_FONT_PATH_1 "/system/fonts/RobotoMono-Regular.ttf"
#define SYSTEM_PAT_FONT_PATH_2 "/system/fonts/DroidSansMono.ttf"
#define SYSTEM_PAT_FONT_PATH_3 "/system/fonts/CutiveMono.ttf"
#else
#define SYSTEM_FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"
#define SYSTEM_FONT_PATH_2 "/usr/share/fonts/TTF/DejaVuSans.ttf"

View File

@ -32,6 +32,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
case DIV_SYSTEM_YM2612_FRAC_EXT: {
int clockSel=flags.getInt("clockSel",0);
bool ladder=flags.getBool("ladderEffect",0);
bool noExtMacros=flags.getBool("noExtMacros",false);
if (ImGui::RadioButton("NTSC (7.67MHz)",clockSel==0)) {
clockSel=0;
@ -56,11 +57,17 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
if (ImGui::Checkbox("Enable DAC distortion",&ladder)) {
altered=true;
}
if (type==DIV_SYSTEM_YM2612_EXT || type==DIV_SYSTEM_YM2612_FRAC_EXT) {
if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) {
altered=true;
}
}
if (altered) {
e->lockSave([&]() {
flags.set("clockSel",clockSel);
flags.set("ladderEffect",ladder);
flags.set("noExtMacros",noExtMacros);
});
}
break;
@ -439,6 +446,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610B_EXT: {
int clockSel=flags.getInt("clockSel",0);
bool noExtMacros=flags.getBool("noExtMacros",false);
if (ImGui::RadioButton("8MHz (Neo Geo MVS)",clockSel==0)) {
clockSel=0;
@ -449,9 +457,16 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true;
}
if (type==DIV_SYSTEM_YM2610_EXT || type==DIV_SYSTEM_YM2610_FULL_EXT || type==DIV_SYSTEM_YM2610B_EXT) {
if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) {
altered=true;
}
}
if (altered) {
e->lockSave([&]() {
flags.set("clockSel",clockSel);
flags.set("noExtMacros",noExtMacros);
});
}
break;
@ -668,8 +683,23 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
break;
}
case DIV_SYSTEM_PCSPKR: {
int clockSel=flags.getInt("clockSel",0);
int speakerType=flags.getInt("speakerType",0);
ImGui::Text("Clock rate:");
if (ImGui::RadioButton("1.19MHz (PC)",clockSel==0)) {
clockSel=0;
altered=true;
}
if (ImGui::RadioButton("1.99MHz (PC-98)",clockSel==1)) {
clockSel=1;
altered=true;
}
if (ImGui::RadioButton("2.46MHz (PC-98)",clockSel==2)) {
clockSel=2;
altered=true;
}
ImGui::Text("Speaker type:");
if (ImGui::RadioButton("Unfiltered",speakerType==0)) {
speakerType=0;
@ -690,6 +720,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
if (altered) {
e->lockSave([&]() {
flags.set("clockSel",clockSel);
flags.set("speakerType",speakerType);
});
}
@ -805,6 +836,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
case DIV_SYSTEM_OPN_EXT: {
int clockSel=flags.getInt("clockSel",0);
int prescale=flags.getInt("prescale",0);
bool noExtMacros=flags.getBool("noExtMacros",false);
ImGui::Text("Clock rate:");
if (ImGui::RadioButton("3.58MHz (NTSC)",clockSel==0)) {
@ -845,10 +877,17 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true;
}
if (type==DIV_SYSTEM_OPN_EXT) {
if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) {
altered=true;
}
}
if (altered) {
e->lockSave([&]() {
flags.set("clockSel",clockSel);
flags.set("prescale",prescale);
flags.set("noExtMacros",noExtMacros);
});
}
break;
@ -857,6 +896,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
case DIV_SYSTEM_PC98_EXT: {
int clockSel=flags.getInt("clockSel",0);
int prescale=flags.getInt("prescale",0);
bool noExtMacros=flags.getBool("noExtMacros",false);
ImGui::Text("Clock rate:");
if (ImGui::RadioButton("8MHz (Arcade)",clockSel==0)) {
@ -881,10 +921,17 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true;
}
if (type==DIV_SYSTEM_PC98_EXT) {
if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) {
altered=true;
}
}
if (altered) {
e->lockSave([&]() {
flags.set("clockSel",clockSel);
flags.set("prescale",prescale);
flags.set("noExtMacros",noExtMacros);
});
}
break;