Merge branch 'master' into dx9

This commit is contained in:
tildearrow 2024-05-12 12:43:03 -05:00
commit c9147b5152
36 changed files with 3187 additions and 2894 deletions

View file

@ -11,7 +11,7 @@ defaults:
shell: bash
env:
BUILD_TYPE: RelWithDebInfo
BUILD_TYPE: Debug
jobs:
build:

1
.gitignore vendored
View file

@ -5,6 +5,7 @@ nosdl/
release/
t/
winbuild/
winCbuild/
win32build/
xpbuild/
macbuild/

View file

@ -122,6 +122,7 @@ option(WITH_DEMOS "Install demo songs" ON)
option(WITH_INSTRUMENTS "Install instruments" ON)
option(WITH_WAVETABLES "Install wavetables" ON)
option(SHOW_OPEN_ASSETS_MENU_ENTRY "Show option to open built-in assets directory (on supported platforms)" OFF)
option(CONSOLE_SUBSYSTEM "Build Furnace with Console subsystem on Windows" OFF)
if (APPLE)
option(FORCE_APPLE_BIN "Force enable binary installation to /bin" OFF)
else()
@ -1081,7 +1082,7 @@ elseif (APPLE)
find_library(COCOA Cocoa REQUIRED)
list(APPEND DEPENDENCIES_LIBRARIES ${COCOA})
else()
list(APPEND DEPENDENCIES_LIBRARIES dl)
list(APPEND DEPENDENCIES_LIBRARIES ${CMAKE_DL_LIBS})
endif()
if (NOT MSVC)
@ -1128,9 +1129,13 @@ if (NOT ANDROID OR TERMUX)
endif()
endif()
if (WIN32 AND CONSOLE_SUBSYSTEM)
list(APPEND DEPENDENCIES_DEFINES "TA_SUBSYSTEM_CONSOLE")
endif()
if(ANDROID AND NOT TERMUX)
add_library(furnace SHARED ${USED_SOURCES})
elseif(WIN32)
elseif(WIN32 AND NOT CONSOLE_SUBSYSTEM)
add_executable(furnace WIN32 ${USED_SOURCES})
else()
add_executable(furnace ${USED_SOURCES})

View file

@ -280,6 +280,7 @@ Available options:
| `WITH_INSTRUMENTS` | `ON` | Install demo instruments on `make install` |
| `WITH_WAVETABLES` | `ON` | Install wavetables on `make install` |
| `SHOW_OPEN_ASSETS_MENU_ENTRY` | `OFF` | Show option to open built-in assets directory (on supported platforms) |
| `CONSOLE_SUBSYSTEM` | `OFF` | Build with subsystem set to Console on Windows |
| `FORCE_APPLE_BIN` | `OFF` | Enable installation of binaries (when doing `make install`) to PREFIX/bin on Apple platforms |
(\*) `ON` if system-installed JACK detected, otherwise `OFF`

View file

@ -1,4 +1,12 @@
# to-do for 0.6.4
- revamp audio export dialog
- fix possible issues when moving selection
- fix Metal intro crash
# to-do long term
- finish auto-clone
- new pattern renderer - performance improvements
- new info header
- unlimited channels and chips

View file

@ -54,7 +54,7 @@ SOFTWARE.
#ifndef PATH_MAX
#define PATH_MAX 260
#endif // PATH_MAX
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) || defined(__HAIKU__)
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE__) || defined (__EMSCRIPTEN__) || defined(__HAIKU__)
#define UNIX
#define stricmp strcasecmp
#include <sys/types.h>

42
scripts/release-winconsole.sh Executable file
View file

@ -0,0 +1,42 @@
#!/bin/bash
# make Windows release
# this script shall be run from Arch Linux with MinGW installed!
if [ ! -e /tmp/furnace ]; then
ln -s "$PWD" /tmp/furnace || exit 1
fi
cd /tmp/furnace
if [ ! -e winCbuild ]; then
mkdir winCbuild || exit 1
fi
cd winCbuild
# TODO: potential Arch-ism?
x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Wno-deprecated-declarations -Werror" -DUSE_BACKWARD=ON -DCONSOLE_SUBSYSTEM=ON .. || exit 1
make -j8 || exit 1
cd ..
mkdir -p release/winconsole || exit 1
cd release/winconsole
cp ../../LICENSE LICENSE.txt || exit 1
cp ../../winCbuild/furnace.exe . || exit 1
cp ../../res/releaseReadme/stable-win.txt README.txt || exit 1
cp -r ../../papers papers || exit 1
cp -r ../../demos demos || exit 1
cp -r ../../instruments instruments || exit 1
cp -r ../../wavetables wavetables || exit 1
cp ../../res/docpdf/manual.pdf . || exit 1
x86_64-w64-mingw32-strip -s furnace.exe || exit 1
zip -r furnace.zip LICENSE.txt furnace.exe README.txt manual.pdf papers demos instruments wavetables
furName=$(git describe --tags | sed "s/v0/0/")
mv furnace.zip furnace-"$furName"-win64-console.zip

View file

@ -1095,7 +1095,7 @@ bool DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
}
bool DivEngine::addSystem(DivSystem which) {
if (song.systemLen>DIV_MAX_CHIPS) {
if (song.systemLen>=DIV_MAX_CHIPS) {
lastError=fmt::sprintf("max number of systems is %d",DIV_MAX_CHIPS);
return false;
}
@ -1149,7 +1149,7 @@ bool DivEngine::duplicateSystem(int index, bool pat, bool end) {
lastError="invalid index";
return false;
}
if (song.systemLen>DIV_MAX_CHIPS) {
if (song.systemLen>=DIV_MAX_CHIPS) {
lastError=fmt::sprintf("max number of systems is %d",DIV_MAX_CHIPS);
return false;
}

View file

@ -98,6 +98,35 @@ enum DivMIDIModes {
DIV_MIDI_MODE_LIGHT_SHOW
};
enum DivAudioExportFormats {
DIV_EXPORT_FORMAT_S16=0,
DIV_EXPORT_FORMAT_F32
};
struct DivAudioExportOptions {
DivAudioExportModes mode;
DivAudioExportFormats format;
int sampleRate;
int chans;
int loops;
double fadeOut;
int orderBegin, orderEnd;
bool channelMask[DIV_MAX_CHANS];
DivAudioExportOptions():
mode(DIV_EXPORT_MODE_ONE),
format(DIV_EXPORT_FORMAT_S16),
sampleRate(44100),
chans(2),
loops(0),
fadeOut(0.0),
orderBegin(-1),
orderEnd(-1) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
channelMask[i]=true;
}
}
};
struct DivChannelState {
std::vector<DivDelayedCommand> delayed;
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
@ -456,7 +485,10 @@ class DivEngine {
DivChannelState chan[DIV_MAX_CHANS];
DivAudioEngines audioEngine;
DivAudioExportModes exportMode;
DivAudioExportFormats exportFormat;
double exportFadeOut;
int exportOutputs;
bool exportChannelMask[DIV_MAX_CHANS];
DivConfig conf;
FixedQueue<DivNoteEvent,8192> pendingNotes;
// bitfield
@ -670,7 +702,7 @@ class DivEngine {
// export to text
SafeWriter* saveText(bool separatePatterns=true);
// export to an audio file
bool saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime=0.0);
bool saveAudio(const char* path, DivAudioExportOptions options);
// wait for audio export to finish
void waitAudioFile();
// stop audio file export
@ -1359,7 +1391,9 @@ class DivEngine {
haltOn(DIV_HALT_NONE),
audioEngine(DIV_AUDIO_NULL),
exportMode(DIV_EXPORT_MODE_ONE),
exportFormat(DIV_EXPORT_FORMAT_S16),
exportFadeOut(0.0),
exportOutputs(2),
cmdStreamInt(NULL),
midiBaseChan(0),
midiPoly(true),
@ -1411,6 +1445,7 @@ class DivEngine {
memset(sysDefs,0,DIV_MAX_CHIP_DEFS*sizeof(void*));
memset(walked,0,8192);
memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*)));
memset(exportChannelMask,1,DIV_MAX_CHANS*sizeof(bool));
for (int i=0; i<DIV_MAX_CHIP_DEFS; i++) {
sysFileMapFur[i]=DIV_SYSTEM_NULL;

View file

@ -362,6 +362,29 @@ void DivPlatformAY8930::tick(bool sysTick) {
immWrite(regPeriodL[i],chan[i].envelope.period);
immWrite(regPeriodH[i],chan[i].envelope.period>>8);
}
if (chan[i].freqChanged && chan[i].autoNoiseMode) {
int noiseFreq=chan[i].freq;
switch (chan[i].autoNoiseMode) {
case 1: // noise
noiseFreq+=chan[i].autoNoiseOff;
if (noiseFreq<0) noiseFreq=0;
if (noiseFreq>255) noiseFreq=255;
rWrite(0x06,noiseFreq);
break;
case 2: { // noise + OR mask
if (noiseFreq<0) noiseFreq=0;
int noiseDiv=(noiseFreq>>8)+1;
noiseFreq/=noiseDiv;
ayNoiseOr=noiseDiv;
immWrite(0x1a,ayNoiseOr);
noiseFreq+=chan[i].autoNoiseOff;
if (noiseFreq<0) noiseFreq=0;
if (noiseFreq>255) noiseFreq=255;
rWrite(0x06,noiseFreq);
break;
}
}
}
chan[i].freqChanged=false;
}
@ -632,6 +655,12 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
chan[c.chan].autoEnvDen=c.value&15;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_AY_AUTO_PWM:
chan[c.chan].autoNoiseMode=c.value>>4;
chan[c.chan].autoNoiseOff=c.value&15;
if (chan[c.chan].autoNoiseOff>=8) chan[c.chan].autoNoiseOff-=16;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_AY_IO_WRITE:
if (c.value==255) {
immWrite(0x1f,c.value2);

View file

@ -77,8 +77,8 @@ class DivPlatformAY8930: public DivDispatch {
setPos(false) {}
} dac;
unsigned char autoEnvNum, autoEnvDen, duty;
signed char konCycles;
unsigned char autoEnvNum, autoEnvDen, duty, autoNoiseMode;
signed char konCycles, autoNoiseOff;
Channel():
SharedChannel<int>(31),
envelope(Envelope()),
@ -88,7 +88,9 @@ class DivPlatformAY8930: public DivDispatch {
autoEnvNum(0),
autoEnvDen(0),
duty(4),
konCycles(0) {}
autoNoiseMode(0),
konCycles(0),
autoNoiseOff(0) {}
};
Channel chan[3];
bool isMuted[3];

View file

@ -437,14 +437,24 @@ void DivEngine::registerSystems() {
{0x2f, {DIV_CMD_AY_IO_WRITE, "2Fxx: Write to I/O port B", constVal<1>, effectVal}},
};
EffectHandlerMap ay8930PostEffectHandlerMap(ayPostEffectHandlerMap);
ay8930PostEffectHandlerMap.insert({
EffectHandlerMap ay8930PostEffectHandlerMap={
{0x20, {DIV_CMD_STD_NOISE_MODE, "20xx: Set channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"}},
{0x21, {DIV_CMD_STD_NOISE_FREQ, "21xx: Set noise frequency (0 to FF)"}},
{0x22, {DIV_CMD_AY_ENVELOPE_SET, "22xy: Set envelope mode (x: shape, y: enable for this channel)"}},
{0x23, {DIV_CMD_AY_ENVELOPE_LOW, "23xx: Set envelope period low byte"}},
{0x24, {DIV_CMD_AY_ENVELOPE_HIGH, "24xx: Set envelope period high byte"}},
{0x25, {DIV_CMD_AY_ENVELOPE_SLIDE, "25xx: Envelope slide up", negEffectVal}},
{0x26, {DIV_CMD_AY_ENVELOPE_SLIDE, "26xx: Envelope slide down"}},
{0x29, {DIV_CMD_AY_AUTO_ENVELOPE, "29xy: Set auto-envelope (x: numerator; y: denominator)"}},
{0x2e, {DIV_CMD_AY_IO_WRITE, "2Exx: Write to I/O port A", constVal<0>, effectVal}},
{0x2f, {DIV_CMD_AY_IO_WRITE, "2Fxx: Write to I/O port B", constVal<1>, effectVal}},
{0x12, {DIV_CMD_STD_NOISE_MODE, "12xx: Set duty cycle (0 to 8)",
[](unsigned char, unsigned char val) -> int { return 0x10+(val&15); }}},
{0x27, {DIV_CMD_AY_NOISE_MASK_AND, "27xx: Set noise AND mask"}},
{0x28, {DIV_CMD_AY_NOISE_MASK_OR, "28xx: Set noise OR mask"}},
{0x2c, {DIV_CMD_AY_AUTO_PWM, "2Cxy: Automatic noise frequency (x: mode (0: disable, 1: freq, 2: freq + OR mask); y: offset"}},
{0x2d, {DIV_CMD_AY_IO_WRITE, "2Dxx: NOT TO BE EMPLOYED BY THE COMPOSER", constVal<255>, effectVal}},
});
};
EffectHandlerMap fmEffectHandlerMap={
{0x30, {DIV_CMD_FM_HARD_RESET, "30xx: Toggle hard envelope reset on new notes"}},

View file

@ -45,8 +45,12 @@ void DivEngine::runExportThread() {
SF_INFO si;
SFWrapper sfWrap;
si.samplerate=got.rate;
si.channels=2;
si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
si.channels=exportOutputs;
if (exportFormat==DIV_EXPORT_FORMAT_S16) {
si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
} else {
si.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
}
sf=sfWrap.doOpen(exportPath.c_str(),SFM_WRITE,&si);
if (sf==NULL) {
@ -55,10 +59,12 @@ void DivEngine::runExportThread() {
return;
}
float* outBuf[3];
outBuf[0]=new float[EXPORT_BUFSIZE];
outBuf[1]=new float[EXPORT_BUFSIZE];
outBuf[2]=new float[EXPORT_BUFSIZE*2];
float* outBuf[DIV_MAX_OUTPUTS];
float* outBufFinal;
for (int i=0; i<exportOutputs; i++) {
outBuf[i]=new float[EXPORT_BUFSIZE];
}
outBufFinal=new float[EXPORT_BUFSIZE*exportOutputs];
// take control of audio output
deinitAudioBackend();
@ -68,24 +74,27 @@ void DivEngine::runExportThread() {
while (playing) {
size_t total=0;
nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE);
nextBuf(NULL,outBuf,0,exportOutputs,EXPORT_BUFSIZE);
if (totalProcessed>EXPORT_BUFSIZE) {
logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE);
totalProcessed=EXPORT_BUFSIZE;
}
int fi=0;
for (int i=0; i<(int)totalProcessed; i++) {
total++;
if (isFadingOut) {
double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples));
outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i]))*mul;
outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i]))*mul;
for (int j=0; j<exportOutputs; j++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[j][i]))*mul;
}
if (++curFadeOutSample>=fadeOutSamples) {
playing=false;
break;
}
} else {
outBuf[2][i<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][i]));
outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i]));
for (int j=0; j<exportOutputs; j++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[j][i]));
}
if (lastLoopPos>-1 && i>=lastLoopPos && totalLoops>=exportLoopCount) {
logD("start fading out...");
isFadingOut=true;
@ -94,15 +103,16 @@ void DivEngine::runExportThread() {
}
}
if (sf_writef_float(sf,outBuf[2],total)!=(int)total) {
if (sf_writef_float(sf,outBufFinal,total)!=(int)total) {
logE("error: failed to write entire buffer!");
break;
}
}
delete[] outBuf[0];
delete[] outBuf[1];
delete[] outBuf[2];
delete[] outBufFinal;
for (int i=0; i<exportOutputs; i++) {
delete[] outBuf[i];
}
if (sfWrap.doClose()!=0) {
logE("could not close audio file!");
@ -237,22 +247,29 @@ void DivEngine::runExportThread() {
// take control of audio output
deinitAudioBackend();
float* outBuf[3];
outBuf[0]=new float[EXPORT_BUFSIZE];
outBuf[1]=new float[EXPORT_BUFSIZE];
outBuf[2]=new float[EXPORT_BUFSIZE*2];
float* outBuf[DIV_MAX_OUTPUTS];
float* outBufFinal;
for (int i=0; i<exportOutputs; i++) {
outBuf[i]=new float[EXPORT_BUFSIZE];
}
outBufFinal=new float[EXPORT_BUFSIZE*exportOutputs];
logI("rendering to files...");
for (int i=0; i<chans; i++) {
if (!exportChannelMask[i]) continue;
SNDFILE* sf;
SF_INFO si;
SFWrapper sfWrap;
String fname=fmt::sprintf("%s_c%02d.wav",exportPath,i+1);
logI("- %s",fname.c_str());
si.samplerate=got.rate;
si.channels=2;
si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
si.channels=exportOutputs;
if (exportFormat==DIV_EXPORT_FORMAT_S16) {
si.format=SF_FORMAT_WAV|SF_FORMAT_PCM_16;
} else {
si.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
}
sf=sfWrap.doOpen(fname.c_str(),SFM_WRITE,&si);
if (sf==NULL) {
@ -287,24 +304,27 @@ void DivEngine::runExportThread() {
while (playing) {
size_t total=0;
nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE);
nextBuf(NULL,outBuf,0,exportOutputs,EXPORT_BUFSIZE);
if (totalProcessed>EXPORT_BUFSIZE) {
logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE);
totalProcessed=EXPORT_BUFSIZE;
}
int fi=0;
for (int j=0; j<(int)totalProcessed; j++) {
total++;
if (isFadingOut) {
double mul=(1.0-((double)curFadeOutSample/(double)fadeOutSamples));
outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j]))*mul;
outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j]))*mul;
for (int k=0; k<exportOutputs; k++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[k][j]))*mul;
}
if (++curFadeOutSample>=fadeOutSamples) {
playing=false;
break;
}
} else {
outBuf[2][j<<1]=MAX(-1.0f,MIN(1.0f,outBuf[0][j]));
outBuf[2][1+(j<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][j]));
for (int k=0; k<exportOutputs; k++) {
outBufFinal[fi++]=MAX(-1.0f,MIN(1.0f,outBuf[k][j]));
}
if (lastLoopPos>-1 && j>=lastLoopPos && totalLoops>=exportLoopCount) {
logD("start fading out...");
isFadingOut=true;
@ -312,7 +332,7 @@ void DivEngine::runExportThread() {
}
}
}
if (sf_writef_float(sf,outBuf[2],total)!=(int)total) {
if (sf_writef_float(sf,outBufFinal,total)!=(int)total) {
logE("error: failed to write entire buffer!");
break;
}
@ -335,9 +355,10 @@ void DivEngine::runExportThread() {
if (stopExport) break;
}
delete[] outBuf[0];
delete[] outBuf[1];
delete[] outBuf[2];
delete[] outBufFinal;
for (int i=0; i<exportOutputs; i++) {
delete[] outBuf[i];
}
for (int i=0; i<chans; i++) {
isMuted[i]=false;
@ -369,18 +390,19 @@ void DivEngine::runExportThread() {
#endif
bool DivEngine::shallSwitchCores() {
// TODO: detect whether we should
return true;
}
bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode, double fadeOutTime) {
bool DivEngine::saveAudio(const char* path, DivAudioExportOptions options) {
#ifndef HAVE_SNDFILE
logE("Furnace was not compiled with libsndfile. cannot export!");
return false;
#else
exportPath=path;
exportMode=mode;
exportFadeOut=fadeOutTime;
exportMode=options.mode;
exportFormat=options.format;
exportFadeOut=options.fadeOut;
memcpy(exportChannelMask,options.channelMask,DIV_MAX_CHANS*sizeof(bool));
if (exportMode!=DIV_EXPORT_MODE_ONE) {
// remove extension
String lowerCase=exportPath;
@ -398,6 +420,7 @@ bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode,
repeatPattern=false;
setOrder(0);
remainingLoops=-1;
got.rate=options.sampleRate;
if (shallSwitchCores()) {
bool isMutedBefore[DIV_MAX_CHANS];
@ -412,7 +435,11 @@ bool DivEngine::saveAudio(const char* path, int loops, DivAudioExportModes mode,
}
}
exportLoopCount=loops;
exportOutputs=options.chans;
if (exportOutputs<1) exportOutputs=1;
if (exportOutputs>DIV_MAX_OUTPUTS) exportOutputs=DIV_MAX_OUTPUTS;
exportLoopCount=options.loops+1;
exportThread=new std::thread(_runExportThread,this);
return true;
#endif

View file

@ -196,6 +196,10 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("behavior changed in 0.6.1\nthis flag will be removed if I find out that none of the songs break after disabling it.");
}
ImGui::Checkbox("Old sample offset effect",&e->song.oldSampleOffset);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("behavior changed in 0.6.3");
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(".mod import")) {

View file

@ -555,6 +555,18 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_PAT_SELECTION_DOWN_COARSE:
moveCursor(0,editStepCoarse,true);
break;
case GUI_ACTION_PAT_MOVE_UP:
moveSelected(0,-1);
break;
case GUI_ACTION_PAT_MOVE_DOWN:
moveSelected(0,1);
break;
case GUI_ACTION_PAT_MOVE_LEFT_CHANNEL:
moveSelected(-1,0);
break;
case GUI_ACTION_PAT_MOVE_RIGHT_CHANNEL:
moveSelected(1,0);
break;
case GUI_ACTION_PAT_DELETE:
doDelete();
if (settings.stepOnDelete) {

View file

@ -1842,6 +1842,25 @@ void FurnaceGUI::doDrag() {
makeUndo(GUI_UNDO_PATTERN_DRAG);
}
void FurnaceGUI::moveSelected(int x, int y) {
prepareUndo(GUI_UNDO_PATTERN_DRAG);
// copy and clear
String c=doCopy(true,false,selStart,selEnd);
logV("copy: %s",c);
// replace
selStart.xCoarse+=x;
selEnd.xCoarse+=x;
selStart.y+=y;
selEnd.y+=y;
cursor=selStart;
doPaste(GUI_PASTE_MODE_NORMAL,0,false,c);
makeUndo(GUI_UNDO_PATTERN_DRAG);
}
void FurnaceGUI::doUndo() {
if (undoHist.empty()) return;
UndoStep& us=undoHist.back();

View file

@ -26,14 +26,83 @@
void FurnaceGUI::drawExportAudio(bool onWindow) {
exitDisabledTimer=1;
ImGui::RadioButton("one file",&audioExportType,0);
ImGui::RadioButton("multiple files (one per chip)",&audioExportType,1);
ImGui::RadioButton("multiple files (one per channel)",&audioExportType,2);
if (ImGui::InputInt("Loops",&exportLoops,1,2)) {
if (exportLoops<0) exportLoops=0;
ImGui::Text("Export type:");
ImGui::Indent();
if (ImGui::RadioButton("one file",audioExportOptions.mode==DIV_EXPORT_MODE_ONE)) {
audioExportOptions.mode=DIV_EXPORT_MODE_ONE;
}
if (ImGui::InputDouble("Fade out (seconds)",&exportFadeOut,1.0,2.0,"%.1f")) {
if (exportFadeOut<0.0) exportFadeOut=0.0;
if (ImGui::RadioButton("multiple files (one per chip)",audioExportOptions.mode==DIV_EXPORT_MODE_MANY_SYS)) {
audioExportOptions.mode=DIV_EXPORT_MODE_MANY_SYS;
}
if (ImGui::RadioButton("multiple files (one per channel)",audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN)) {
audioExportOptions.mode=DIV_EXPORT_MODE_MANY_CHAN;
}
ImGui::Unindent();
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_SYS) {
ImGui::Text("Bit depth:");
ImGui::Indent();
if (ImGui::RadioButton("16-bit integer",audioExportOptions.format==DIV_EXPORT_FORMAT_S16)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_S16;
}
if (ImGui::RadioButton("32-bit float",audioExportOptions.format==DIV_EXPORT_FORMAT_F32)) {
audioExportOptions.format=DIV_EXPORT_FORMAT_F32;
}
ImGui::Unindent();
}
if (ImGui::InputInt("Sample rate",&audioExportOptions.sampleRate,100,10000)) {
if (audioExportOptions.sampleRate<8000) audioExportOptions.sampleRate=8000;
if (audioExportOptions.sampleRate>384000) audioExportOptions.sampleRate=384000;
}
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_SYS) {
if (ImGui::InputInt("Channels in file",&audioExportOptions.chans,1,1)) {
if (audioExportOptions.chans<1) audioExportOptions.chans=1;
if (audioExportOptions.chans>16) audioExportOptions.chans=16;
}
}
if (ImGui::InputInt("Loops",&audioExportOptions.loops,1,2)) {
if (audioExportOptions.loops<0) audioExportOptions.loops=0;
}
if (ImGui::InputDouble("Fade out (seconds)",&audioExportOptions.fadeOut,1.0,2.0,"%.1f")) {
if (audioExportOptions.fadeOut<0.0) audioExportOptions.fadeOut=0.0;
}
bool isOneOn=false;
if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) {
ImGui::Text("Channels to export:");
ImGui::SameLine();
if (ImGui::SmallButton("All")) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
audioExportOptions.channelMask[i]=true;
}
}
ImGui::SameLine();
if (ImGui::SmallButton("None")) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
audioExportOptions.channelMask[i]=false;
}
}
ImGui::SameLine();
if (ImGui::SmallButton("Invert")) {
for (int i=0; i<DIV_MAX_CHANS; i++) {
audioExportOptions.channelMask[i]=!audioExportOptions.channelMask[i];
}
}
if (ImGui::BeginChild("Channel Selection",ImVec2(0,200.0f*dpiScale))) {
for (int i=0; i<e->getTotalChannelCount(); i++) {
String name=fmt::sprintf("%d. %s##_CE%d",i+1,e->getChannelName(i),i);
ImGui::Checkbox(name.c_str(),&audioExportOptions.channelMask[i]);
if (audioExportOptions.channelMask[i]) isOneOn=true;
}
}
ImGui::EndChild();
} else {
isOneOn=true;
}
if (onWindow) {
@ -42,19 +111,23 @@ void FurnaceGUI::drawExportAudio(bool onWindow) {
ImGui::SameLine();
}
if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) {
switch (audioExportType) {
case 0:
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
break;
case 1:
openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS);
break;
case 2:
openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL);
break;
if (isOneOn) {
if (ImGui::Button("Export",ImVec2(200.0f*dpiScale,0))) {
switch (audioExportOptions.mode) {
case DIV_EXPORT_MODE_ONE:
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
break;
case DIV_EXPORT_MODE_MANY_SYS:
openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_SYS);
break;
case DIV_EXPORT_MODE_MANY_CHAN:
openFileDialog(GUI_FILE_EXPORT_AUDIO_PER_CHANNEL);
break;
}
ImGui::CloseCurrentPopup();
}
ImGui::CloseCurrentPopup();
} else {
ImGui::Text("select at least one channel");
}
}

View file

@ -2436,7 +2436,7 @@ int FurnaceGUI::loadStream(String path) {
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
e->saveAudio(path.c_str(),exportLoops+1,mode,exportFadeOut);
e->saveAudio(path.c_str(),audioExportOptions);
displayExporting=true;
}
@ -6761,10 +6761,10 @@ bool FurnaceGUI::init() {
followOrders=e->getConfBool("followOrders",true);
followPattern=e->getConfBool("followPattern",true);
noteInputPoly=e->getConfBool("noteInputPoly",true);
exportLoops=e->getConfInt("exportLoops",0);
if (exportLoops<0) exportLoops=0;
exportFadeOut=e->getConfDouble("exportFadeOut",0.0);
if (exportFadeOut<0.0) exportFadeOut=0.0;
audioExportOptions.loops=e->getConfInt("exportLoops",0);
if (audioExportOptions.loops<0) audioExportOptions.loops=0;
audioExportOptions.fadeOut=e->getConfDouble("exportFadeOut",0.0);
if (audioExportOptions.fadeOut<0.0) audioExportOptions.fadeOut=0.0;
orderEditMode=e->getConfInt("orderEditMode",0);
if (orderEditMode<0) orderEditMode=0;
if (orderEditMode>3) orderEditMode=3;
@ -6826,8 +6826,8 @@ bool FurnaceGUI::init() {
syncTutorial();
if (!settings.persistFadeOut) {
exportLoops=settings.exportLoops;
exportFadeOut=settings.exportFadeOut;
audioExportOptions.loops=settings.exportLoops;
audioExportOptions.fadeOut=settings.exportFadeOut;
}
for (int i=0; i<settings.maxRecentFile; i++) {
@ -6982,12 +6982,19 @@ bool FurnaceGUI::init() {
return false;
}
rend->preInit();
rend->preInit(e->getConfObject());
logD("creating window...");
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW,scrH,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)|rend->getWindowFlags());
if (sdlWin==NULL) {
lastError=fmt::sprintf("could not open window! %s",SDL_GetError());
const char* sdlErr=SDL_GetError();
lastError=fmt::sprintf("could not open window! %s",sdlErr);
if (settings.renderBackend!="Software" && strcmp(sdlErr,"No matching GL pixel format available")==0) {
settings.renderBackend="Software";
e->setConf("renderBackend","Software");
e->saveConf();
lastError+="\r\nfalling back to software renderer. please restart Furnace.";
}
return false;
}
@ -7313,8 +7320,8 @@ void FurnaceGUI::commitState() {
e->setConf("orderEditMode",orderEditMode);
e->setConf("noteInputPoly",noteInputPoly);
if (settings.persistFadeOut) {
e->setConf("exportLoops",exportLoops);
e->setConf("exportFadeOut",exportFadeOut);
e->setConf("exportLoops",audioExportOptions.loops);
e->setConf("exportFadeOut",audioExportOptions.fadeOut);
}
// commit oscilloscope state
@ -7574,7 +7581,6 @@ FurnaceGUI::FurnaceGUI():
oldRow(0),
editStep(1),
editStepCoarse(16),
exportLoops(0),
soloChan(-1),
orderEditMode(0),
orderCursor(-1),
@ -7597,7 +7603,6 @@ FurnaceGUI::FurnaceGUI():
curPaletteChoice(0),
curPaletteType(0),
soloTimeout(0.0f),
exportFadeOut(5.0),
patExtraButtons(false),
patChannelNames(false),
patChannelPairs(true),
@ -7948,7 +7953,6 @@ FurnaceGUI::FurnaceGUI():
introStopped(false),
curTutorial(-1),
curTutorialStep(0),
audioExportType(0),
dmfExportVersion(0),
curExportType(GUI_EXPORT_NONE) {
// value keys

View file

@ -778,6 +778,10 @@ enum FurnaceGUIActions {
GUI_ACTION_PAT_SELECTION_END,
GUI_ACTION_PAT_SELECTION_UP_COARSE,
GUI_ACTION_PAT_SELECTION_DOWN_COARSE,
GUI_ACTION_PAT_MOVE_UP,
GUI_ACTION_PAT_MOVE_DOWN,
GUI_ACTION_PAT_MOVE_LEFT_CHANNEL,
GUI_ACTION_PAT_MOVE_RIGHT_CHANNEL,
GUI_ACTION_PAT_DELETE,
GUI_ACTION_PAT_PULL_DELETE,
GUI_ACTION_PAT_INSERT,
@ -1505,7 +1509,7 @@ class FurnaceGUIRender {
virtual const char* getDeviceName();
virtual const char* getAPIVersion();
virtual void setSwapInterval(int swapInterval);
virtual void preInit();
virtual void preInit(const DivConfig& conf);
virtual bool init(SDL_Window* win, int swapInterval);
virtual void initGUI(SDL_Window* win);
virtual void quitGUI();
@ -1892,6 +1896,12 @@ class FurnaceGUI {
int frameRateLimit;
int displayRenderTime;
int inputRepeat;
int glRedSize;
int glGreenSize;
int glBlueSize;
int glAlphaSize;
int glDepthSize;
int glDoubleBuffer;
unsigned int maxUndoSteps;
float vibrationStrength;
int vibrationLength;
@ -2135,6 +2145,12 @@ class FurnaceGUI {
frameRateLimit(60),
displayRenderTime(0),
inputRepeat(0),
glRedSize(8),
glGreenSize(8),
glBlueSize(8),
glAlphaSize(0),
glDepthSize(24),
glDoubleBuffer(1),
maxUndoSteps(100),
vibrationStrength(0.5f),
vibrationLength(20),
@ -2178,15 +2194,13 @@ class FurnaceGUI {
int pendingLayoutImportStep;
FixedQueue<bool*,64> pendingLayoutImportReopen;
int curIns, curWave, curSample, curOctave, curOrder, playOrder, prevIns, oldRow, editStep, editStepCoarse, exportLoops, soloChan, orderEditMode, orderCursor;
int curIns, curWave, curSample, curOctave, curOrder, playOrder, prevIns, oldRow, editStep, editStepCoarse, soloChan, orderEditMode, orderCursor;
int loopOrder, loopRow, loopEnd, isClipping, newSongCategory, latchTarget;
int wheelX, wheelY, dragSourceX, dragSourceXFine, dragSourceY, dragDestinationX, dragDestinationXFine, dragDestinationY, oldBeat, oldBar;
int curGroove, exitDisabledTimer;
int curPaletteChoice, curPaletteType;
float soloTimeout;
double exportFadeOut;
bool patExtraButtons, patChannelNames, patChannelPairs;
unsigned char patChannelHints;
@ -2587,7 +2601,7 @@ class FurnaceGUI {
ImGuiListClipper csClipper;
// export options
int audioExportType;
DivAudioExportOptions audioExportOptions;
int dmfExportVersion;
FurnaceGUIExportTypes curExportType;
@ -2665,6 +2679,7 @@ class FurnaceGUI {
void drawMacros(std::vector<FurnaceGUIMacroDesc>& macros, FurnaceGUIMacroEditState& state);
void alterSampleMap(int column, int val);
void insTabFM(DivInstrument* ins);
void insTabSample(DivInstrument* ins);
void drawOrderButtons();
@ -2782,6 +2797,7 @@ class FurnaceGUI {
void doDelete();
void doPullDelete();
void doInsert();
void moveSelected(int x, int y);
void doTranspose(int amount, OperationMask& mask);
String doCopy(bool cut, bool writeClipboard, const SelectionPoint& sStart, const SelectionPoint& sEnd);
void doPasteFurnace(PasteMode mode, int arg, bool readClipboard, String clipb, std::vector<String> data, int startOff, bool invalidData, UndoRegion ur);

View file

@ -659,6 +659,10 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("PAT_SELECTION_END", "Expand selection to end of pattern", 0),
D("PAT_SELECTION_UP_COARSE", "Expand selection upwards (coarse)", FURKMOD_SHIFT|SDLK_PAGEUP),
D("PAT_SELECTION_DOWN_COARSE", "Expand selection downwards (coarse)", FURKMOD_SHIFT|SDLK_PAGEDOWN),
D("PAT_MOVE_UP", "Move selection up", FURKMOD_ALT|SDLK_UP),
D("PAT_MOVE_DOWN", "Move selection down", FURKMOD_ALT|SDLK_DOWN),
D("PAT_MOVE_LEFT_CHANNEL", "Move selection to previous channel", FURKMOD_ALT|SDLK_LEFT),
D("PAT_MOVE_RIGHT_CHANNEL", "Move selection to next channel", FURKMOD_ALT|SDLK_RIGHT),
D("PAT_DELETE", "Delete", SDLK_DELETE),
D("PAT_PULL_DELETE", "Pull delete", SDLK_BACKSPACE),
D("PAT_INSERT", "Insert", SDLK_INSERT),

File diff suppressed because it is too large Load diff

View file

@ -128,7 +128,7 @@ const char* FurnaceGUIRender::getAPIVersion() {
void FurnaceGUIRender::setSwapInterval(int swapInterval) {
}
void FurnaceGUIRender::preInit() {
void FurnaceGUIRender::preInit(const DivConfig& conf) {
}
bool FurnaceGUIRender::init(SDL_Window* win, int swapInterval) {

View file

@ -397,7 +397,7 @@ void FurnaceGUIRenderDX11::setSwapInterval(int swapInt) {
swapInterval=swapInt;
}
void FurnaceGUIRenderDX11::preInit() {
void FurnaceGUIRenderDX11::preInit(const DivConfig& conf) {
}
const float wipeVertices[4][4]={

View file

@ -89,7 +89,7 @@ class FurnaceGUIRenderDX11: public FurnaceGUIRender {
const char* getDeviceName();
const char* getAPIVersion();
void setSwapInterval(int swapInterval);
void preInit();
void preInit(const DivConfig& conf);
bool init(SDL_Window* win, int swapInterval);
void initGUI(SDL_Window* win);
void quitGUI();

View file

@ -580,7 +580,7 @@ void FurnaceGUIRenderGL::setSwapInterval(int swapInterval) {
}
}
void FurnaceGUIRenderGL::preInit() {
void FurnaceGUIRenderGL::preInit(const DivConfig& conf) {
#if defined(USE_GLES)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS,0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,SDL_GL_CONTEXT_PROFILE_ES);
@ -603,12 +603,12 @@ void FurnaceGUIRenderGL::preInit() {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,0);
#endif
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,24);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,conf.getInt("glRedSize",8));
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,conf.getInt("glGreenSize",8));
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,conf.getInt("glBlueSize",8));
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,conf.getInt("glAlphaSize",0));
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,conf.getInt("glDoubleBuffer",1));
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,conf.getInt("glDepthSize",24));
}
#define LOAD_PROC_MANDATORY(_v,_t,_s) \

View file

@ -82,7 +82,7 @@ class FurnaceGUIRenderGL: public FurnaceGUIRender {
const char* getDeviceName();
const char* getAPIVersion();
void setSwapInterval(int swapInterval);
void preInit();
void preInit(const DivConfig& conf);
bool init(SDL_Window* win, int swapInterval);
void initGUI(SDL_Window* win);
void quitGUI();

View file

@ -245,18 +245,18 @@ void FurnaceGUIRenderGL1::setSwapInterval(int swapInterval) {
}
}
void FurnaceGUIRenderGL1::preInit() {
void FurnaceGUIRenderGL1::preInit(const DivConfig& conf) {
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS,0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,1);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,1);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,24);
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,conf.getInt("glRedSize",8));
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,conf.getInt("glGreenSize",8));
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,conf.getInt("glBlueSize",8));
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,conf.getInt("glAlphaSize",0));
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,conf.getInt("glDoubleBuffer",1));
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,conf.getInt("glDepthSize",24));
}
#define LOAD_PROC_MANDATORY(_v,_t,_s) \
@ -334,4 +334,4 @@ void FurnaceGUIRenderGL1::quitGUI() {
// sadly, OpenGL 1.1 doesn't have the ability to recover from death...
bool FurnaceGUIRenderGL1::isDead() {
return false;
}
}

View file

@ -56,7 +56,7 @@ class FurnaceGUIRenderGL1: public FurnaceGUIRender {
const char* getDeviceName();
const char* getAPIVersion();
void setSwapInterval(int swapInterval);
void preInit();
void preInit(const DivConfig& conf);
bool init(SDL_Window* win, int swapInterval);
void initGUI(SDL_Window* win);
void quitGUI();

View file

@ -52,7 +52,7 @@ class FurnaceGUIRenderMetal: public FurnaceGUIRender {
const char* getDeviceName();
const char* getAPIVersion();
void setSwapInterval(int swapInterval);
void preInit();
void preInit(const DivConfig& conf);
bool init(SDL_Window* win, int swapInterval);
void initGUI(SDL_Window* win);
void quitGUI();

View file

@ -218,7 +218,7 @@ void FurnaceGUIRenderMetal::setSwapInterval(int swapInterval) {
}
}
void FurnaceGUIRenderMetal::preInit() {
void FurnaceGUIRenderMetal::preInit(const DivConfig& conf) {
SDL_SetHint(SDL_HINT_RENDER_DRIVER,"metal");
priv=new FurnaceGUIRenderMetalPrivate;
}

View file

@ -182,7 +182,7 @@ void FurnaceGUIRenderSDL::setSwapInterval(int swapInterval) {
}
}
void FurnaceGUIRenderSDL::preInit() {
void FurnaceGUIRenderSDL::preInit(const DivConfig& conf) {
}
bool FurnaceGUIRenderSDL::init(SDL_Window* win, int swapInterval) {

View file

@ -50,7 +50,7 @@ class FurnaceGUIRenderSDL: public FurnaceGUIRender {
const char* getDeviceName();
const char* getAPIVersion();
void setSwapInterval(int swapInterval);
void preInit();
void preInit(const DivConfig& conf);
bool init(SDL_Window* win, int swapInterval);
void initGUI(SDL_Window* win);
void quitGUI();

View file

@ -160,7 +160,7 @@ const char* FurnaceGUIRenderSoftware::getAPIVersion() {
void FurnaceGUIRenderSoftware::setSwapInterval(int swapInterval) {
}
void FurnaceGUIRenderSoftware::preInit() {
void FurnaceGUIRenderSoftware::preInit(const DivConfig& conf) {
}
bool FurnaceGUIRenderSoftware::init(SDL_Window* win, int swapInterval) {

View file

@ -47,7 +47,7 @@ class FurnaceGUIRenderSoftware: public FurnaceGUIRender {
const char* getDeviceName();
const char* getAPIVersion();
void setSwapInterval(int swapInterval);
void preInit();
void preInit(const DivConfig& conf);
bool init(SDL_Window* win, int swapInterval);
void initGUI(SDL_Window* win);
void quitGUI();

View file

@ -449,23 +449,61 @@ void FurnaceGUI::drawSettings() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("you may need to restart Furnace for this setting to take effect.");
}
if (curRenderBackend=="SDL") {
if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) {
if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) {
settings.renderDriver="";
settingsChanged=true;
}
for (String& i: availRenderDrivers) {
if (ImGui::Selectable(i.c_str(),i==settings.renderDriver)) {
settings.renderDriver=i;
if (ImGui::TreeNode("Advanced render backend settings")) {
if (curRenderBackend=="SDL") {
if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) {
if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) {
settings.renderDriver="";
settingsChanged=true;
}
for (String& i: availRenderDrivers) {
if (ImGui::Selectable(i.c_str(),i==settings.renderDriver)) {
settings.renderDriver=i;
settingsChanged=true;
}
}
ImGui::EndCombo();
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("you may need to restart Furnace for this setting to take effect.");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("you may need to restart Furnace for this setting to take effect.");
}
} else if (curRenderBackend.find("OpenGL")==0) {
ImGui::TextWrapped("beware: changing these settings may render Furnace unusable! do so at your own risk.\nstart Furnace with -safemode if you mess something up.");
if (ImGui::InputInt("Red bits",&settings.glRedSize)) {
if (settings.glRedSize<0) settings.glRedSize=0;
if (settings.glRedSize>32) settings.glRedSize=32;
settingsChanged=true;
}
if (ImGui::InputInt("Green bits",&settings.glGreenSize)) {
if (settings.glGreenSize<0) settings.glGreenSize=0;
if (settings.glGreenSize>32) settings.glGreenSize=32;
settingsChanged=true;
}
if (ImGui::InputInt("Blue bits",&settings.glBlueSize)) {
if (settings.glBlueSize<0) settings.glBlueSize=0;
if (settings.glBlueSize>32) settings.glBlueSize=32;
settingsChanged=true;
}
if (ImGui::InputInt("Alpha bits",&settings.glAlphaSize)) {
if (settings.glAlphaSize<0) settings.glAlphaSize=0;
if (settings.glAlphaSize>32) settings.glAlphaSize=32;
settingsChanged=true;
}
if (ImGui::InputInt("Color depth",&settings.glDepthSize)) {
if (settings.glDepthSize<0) settings.glDepthSize=0;
if (settings.glDepthSize>128) settings.glDepthSize=128;
settingsChanged=true;
}
bool glDoubleBufferB=settings.glDoubleBuffer;
if (ImGui::Checkbox("Double buffer",&glDoubleBufferB)) {
settings.glDoubleBuffer=glDoubleBufferB;
settingsChanged=true;
}
ImGui::TextWrapped("the following values are common (in red, green, blue, alpha order):\n- 24 bits: 8, 8, 8, 0\n- 16 bits: 5, 6, 5, 0\n- 32 bits (with alpha): 8, 8, 8, 8\n- 30 bits (deep): 10, 10, 10, 0");
}
ImGui::TreePop();
}
ImGui::TextWrapped("current backend: %s\n%s\n%s\n%s",rend->getBackendName(),rend->getVendorName(),rend->getDeviceName(),rend->getAPIVersion());
@ -667,13 +705,13 @@ void FurnaceGUI::drawSettings() {
ImGui::BeginDisabled(settings.persistFadeOut);
ImGui::Indent();
if (ImGui::InputInt("Loops",&settings.exportLoops,1,2)) {
if (exportLoops<0) exportLoops=0;
exportLoops=settings.exportLoops;
if (settings.exportLoops<0) settings.exportLoops=0;
audioExportOptions.loops=settings.exportLoops;
settingsChanged=true;
}
if (ImGui::InputDouble("Fade out (seconds)",&settings.exportFadeOut,1.0,2.0,"%.1f")) {
if (exportFadeOut<0.0) exportFadeOut=0.0;
exportFadeOut=settings.exportFadeOut;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
audioExportOptions.fadeOut=settings.exportFadeOut;
settingsChanged=true;
}
ImGui::Unindent();
@ -2106,6 +2144,10 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_END);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP_COARSE);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN_COARSE);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_MOVE_UP);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_MOVE_DOWN);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_MOVE_LEFT_CHANNEL);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_MOVE_RIGHT_CHANNEL);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_DELETE);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PULL_DELETE);
UI_KEYBIND_CONFIG(GUI_ACTION_PAT_INSERT);
@ -4119,6 +4161,13 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
settings.renderBackend=conf.getString("renderBackend",GUI_BACKEND_DEFAULT_NAME);
settings.renderClearPos=conf.getInt("renderClearPos",0);
settings.glRedSize=conf.getInt("glRedSize",8);
settings.glGreenSize=conf.getInt("glGreenSize",8);
settings.glBlueSize=conf.getInt("glBlueSize",8);
settings.glAlphaSize=conf.getInt("glAlphaSize",0);
settings.glDepthSize=conf.getInt("glDepthSize",24);
settings.glDoubleBuffer=conf.getInt("glDoubleBuffer",1);
settings.vsync=conf.getInt("vsync",1);
settings.frameRateLimit=conf.getInt("frameRateLimit",100);
settings.displayRenderTime=conf.getInt("displayRenderTime",0);
@ -4653,6 +4702,12 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
clampSetting(settings.vibrationStrength,0.0f,1.0f);
clampSetting(settings.vibrationLength,10,500);
clampSetting(settings.inputRepeat,0,1);
clampSetting(settings.glRedSize,0,32);
clampSetting(settings.glGreenSize,0,32);
clampSetting(settings.glBlueSize,0,32);
clampSetting(settings.glAlphaSize,0,32);
clampSetting(settings.glDepthSize,0,128);
clampSetting(settings.glDoubleBuffer,0,1);
if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -4676,6 +4731,13 @@ void FurnaceGUI::writeConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
conf.set("renderBackend",settings.renderBackend);
conf.set("renderClearPos",settings.renderClearPos);
conf.set("glRedSize",settings.glRedSize);
conf.set("glGreenSize",settings.glGreenSize);
conf.set("glBlueSize",settings.glBlueSize);
conf.set("glAlphaSize",settings.glAlphaSize);
conf.set("glDepthSize",settings.glDepthSize);
conf.set("glDoubleBuffer",settings.glDoubleBuffer);
conf.set("vsync",settings.vsync);
conf.set("frameRateLimit",settings.frameRateLimit);
conf.set("displayRenderTime",settings.displayRenderTime);

View file

@ -66,10 +66,9 @@ String outName;
String vgmOutName;
String zsmOutName;
String cmdOutName;
int loops=1;
int benchMode=0;
int subsong=-1;
DivAudioExportModes outMode=DIV_EXPORT_MODE_ONE;
DivAudioExportOptions exportOptions;
#ifdef HAVE_GUI
bool consoleMode=false;
@ -299,9 +298,9 @@ TAParamResult pLoops(String val) {
try {
int count=std::stoi(val);
if (count<0) {
loops=0;
exportOptions.loops=0;
} else {
loops=count+1;
exportOptions.loops=count;
}
} catch (std::exception& e) {
logE("loop count shall be a number.");
@ -327,11 +326,11 @@ TAParamResult pSubSong(String val) {
TAParamResult pOutMode(String val) {
if (val=="one") {
outMode=DIV_EXPORT_MODE_ONE;
exportOptions.mode=DIV_EXPORT_MODE_ONE;
} else if (val=="persys") {
outMode=DIV_EXPORT_MODE_MANY_SYS;
exportOptions.mode=DIV_EXPORT_MODE_MANY_SYS;
} else if (val=="perchan") {
outMode=DIV_EXPORT_MODE_MANY_CHAN;
exportOptions.mode=DIV_EXPORT_MODE_MANY_CHAN;
} else {
logE("invalid value for outmode! valid values are: one, persys and perchan.");
return TA_PARAM_ERROR;
@ -401,7 +400,7 @@ void initParams() {
params.push_back(TAParam("n","nostatus",false,pNoStatus,"","disable playback status in console mode"));
params.push_back(TAParam("N","nocontrols",false,pNoControls,"","disable standard input controls in console mode"));
params.push_back(TAParam("l","loops",true,pLoops,"<count>","set number of loops (-1 means loop forever)"));
params.push_back(TAParam("l","loops",true,pLoops,"<count>","set number of loops"));
params.push_back(TAParam("s","subsong",true,pSubSong,"<number>","set sub-song"));
params.push_back(TAParam("o","outmode",true,pOutMode,"one|persys|perchan","set file output mode"));
params.push_back(TAParam("S","safemode",false,pSafeMode,"","enable safe mode (software rendering and no audio)"));
@ -451,12 +450,13 @@ int main(int argc, char** argv) {
// Windows console thing - thanks dj.tuBIG/MaliceX
#ifdef _WIN32
#ifndef TA_SUBSYSTEM_CONSOLE
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
freopen("CONIN$", "r", stdin);
}
#endif
#endif
srand(time(NULL));
@ -715,7 +715,7 @@ int main(int argc, char** argv) {
}
if (outName!="") {
e.setConsoleMode(true);
e.saveAudio(outName.c_str(),loops,outMode);
e.saveAudio(outName.c_str(),exportOptions);
e.waitAudioFile();
}
finishLogFile();