Merge branch 'master' into metal

This commit is contained in:
tildearrow 2024-04-14 16:40:25 -05:00
commit e6bead147a
22 changed files with 636 additions and 175 deletions

View file

@ -1,48 +0,0 @@
# EXTREMELY IMPORTANT NOTICE - PLEASE **READ**!!!!!!!
BY SUBMITTING AN ISSUE, YOU HEREBY AGREE TO COMPLY WITH THESE TERMS.
FAILURE TO DO SO MAY RESULT IN YOUR ISSUE BEING DECLARED VOID.
**ADDITIONALLY, FAILURE TO COMPLY WITH POINTS 1 AND 2 WILL RESULT IN THE INABILITY TO ISSUE FURTHER ISSUE REPORTS.**
1. this section is exclusively for ISSUES related to Furnace (bugs, major annoyances and others). ONLY THINGS THAT COUNT AS **ISSUES** (ad pedem litterae).
2. **THIS SECTION IS NOT FOR SUGGESTIONS, REQUESTS, QUESTIONS, SHOWCASE OR ANY OTHER DISCUSSIONS THAT DO NOT MEET THE CRITERIA AND DEFINITION OF AN __ISSUE__.**
- see the Discussions section if you wish to submit these.
3. check whether your issue has been reported already.
- go to the Issues section, and use the search bar that appears on top of the Issues list.
4. include the following information:
- version of Furnace (help > about)
- operating system (and version)
- whether you have downloaded Furnace, or built it from source.
5. provide these details if you believe the issue is operating system and/or computer-specific:
- CPU model
- Windows: go to Control Panel > System.
- macOS: go to the Apple menu and select About This Mac...
- Linux: use `lscpu` or `cat /proc/cpuinfo`.
- graphics card (and driver version)
- Windows: open `dxdiag` and observe the Render tab.
- macOS: go to the Apple menu and select About This Mac...
- this information is not always shown.
- this information is not necessary if you use an Apple silicon Mac.
- Linux: use `glxinfo | grep OpenGL`.
6. if your issue is an abnormal program termination (a "Crash"), you must provide additional details:
- the furnace_crash.txt file that is created by Furnace after a Crash. this file is located in the following paths:
- Windows: `C:\Users\<username>\furnace_crash.txt`
- Linux/other: `/tmp/furnace_crash.txt`
- on macOS this file is not generated. you may retrieve information about the Crash by clicking on "Report..." or "Show Details" in the "quit unexpectedly" dialog that appears following the Crash.
- make sure to remove any personal information for privacy reasons.
- be sure to select "Don't Send" afterwards.
- the furnace.log file located in:
- Windows: `C:\Users\<username>\AppData\Roaming\furnace\furnace.log`
- macOS: `~/Library/Application Support/furnace/furnace.log`
- Linux: `~/.config/furnace/furnace.log`
- make sure to remove any personal information for privacy reasons.
BY SUBMITTING AN ISSUE, YOU HEREBY AGREE TO COMPLY WITH THESE TERMS.
FAILURE TO DO SO MAY RESULT IN YOUR ISSUE BEING DECLARED VOID.
**ADDITIONALLY, FAILURE TO COMPLY WITH POINTS 1 AND 2 WILL RESULT IN THE INABILITY TO ISSUE FURTHER ISSUE REPORTS.**
***END OF NOTICE***
PLEASE REMOVE THIS NOTICE AFTER READING.
FAILURE TO REMOVE THIS NOTICE IS NEGLIGENCE.

1
.github/issue_template/config.yml vendored Normal file
View file

@ -0,0 +1 @@
blank_issues_enabled: false

33
.github/issue_template/issue.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: Issue Report
description: for issues (bugs, annoyances, crashes and similar).
body:
- type: markdown
attributes:
value: |
Suggestions, feature requests, questions, showcase or anything else? Go to the Discussions section.
- type: textarea
id: description
attributes:
label: Description
- type: textarea
id: steps
attributes:
label: Steps to Reproduce (if applicable)
- type: textarea
id: info
attributes:
label: Additional Information
- type: input
id: version
attributes:
label: Furnace version? (help > about)
validations:
required: true
- type: checkboxes
id: terms
attributes:
label: This is an issue
description: By submitting this issue, you affirm that this is an actual issue (not a suggestion, question or otherwise non-issue).
options:
- label: I hereby certify that the ticket I am submitting is an issue.
required: true

View file

@ -1,7 +1,6 @@
# to-do for 0.6.3
- 9xxx for more chips
- user presets
# to-do long term

BIN
demos/nes/infinity.fur Normal file

Binary file not shown.

View file

@ -15,6 +15,7 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
- DirectX 11: works with the majority of graphics chips/cards and is optimized specifically for Windows.
- SDL Renderer: this was the only available render backend prior to the addition of dedicated OpenGL/DirectX backends in 0.6. default on macOS.
- it is slower than the other backends.
- Software: this is a last resort backend which renders the interface in software. very slow!
- **Render driver**: this setting appears when using the SDL Renderer backend. it allows you to select an SDL render driver.
- **VSync**: synchronizes rendering to VBlank and eliminates tearing.
- **Frame rate limit**: allows you to set a frame rate limit (in frames per second).

62
doc/7-systems/5e01.md Normal file
View file

@ -0,0 +1,62 @@
# 5E01
a fantasy sound chip created by Euly, based on the Ricoh 2A03, with some improvements:
- a 37.5% duty cycle,
- 32 noise pitches instead of 16, and
- triangle channel becomes a wave channel, with four available waveforms: triangle, saw, sine and square.
## effects
- `11xx`: **write to delta modulation counter.** range is `00` to `7F`.
- this may be used to attenuate the triangle and noise channels; at `7F`, they will be at about 57% volume.
- will not work if a sample is playing.
- `12xx`: **set duty cycle/noise mode/waveform of channel.**
- may be `0` to `3` for the pulse channels:
- `0`: 12.5%
- `1`: 25%
- `2`: 37.5%
- `3`: 50%
- may be `0` or `1` for the noise channel:
- `0`: long (15-bit LFSR, 32767-step)
- `1`: short (9-bit LFSR, 93-step)
- may be `0` to `3` for the wave channel:
- `0`: triangle
- `1`: saw
- `2`: square
- `3`: sine
- `13xy`: **setup sweep up.**
- `x` is the time.
- `y` is the shift.
- set to `0` to disable it.
- `14xy`: **setup sweep down.**
- `x` is the time.
- `y` is the shift.
- set to `0` to disable it.
- `15xx`: **set envelope mode.**
- `0`: envelope + length counter. volume represents envelope duration.
- `1`: length counter. volume represents output volume.
- `2`: looping envelope. volume represents envelope duration.
- `3`: constant volume. default value. volume represents output volume.
- pulse and noise channels only.
- you may need to apply a phase reset (using the macro) to make the envelope effective.
- `16xx`: **set length counter.**
- see [NES](nes.md) for more information.
- this will trigger phase reset.
- `17xx`: **set frame counter mode.**
- `0`: 4-step.
- `1`: 5-step.
- `18xx`: **set PCM channel mode.**
- `00`: PCM (software).
- `01`: DPCM (hardware).
- when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited, and loop point is ignored.
- `19xx`: **set triangle linear counter.**
- `00` to `7F` set the counter.
- `80` and higher halt it.
- `20xx`: **set DPCM frequency.**
- only works in DPCM mode.
## info
this chip uses the [NES](../4-instrument/nes.md) instrument editor.

View file

@ -2,7 +2,9 @@
the console from Nintendo that plays Super Mario Bros. and helped revive the agonizing video game market in the US during mid-80s.
also known as Famicom. it is a five-channel sound generator: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel.
also known as Family Computer (Famicom), especially in Japan.
the console is powered by the Ricoh 2A03, a CPU with sound generator built-in. it has five channels: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel.
## effects

View file

@ -47,3 +47,10 @@ Furnace achieves the authentic sound of videogame hardware by emulating sound ch
- **YMF262-LLE**: a new core written by the author of the Nuked cores. it features extremely accurate emulation.
- this core uses even more CPU than YM3812-LLE. not suitable for playback or even rendering if you're impatient!
- **ESFM core**:
- **ESFMu**: the ESFM emulator. best choice but CPU intensive.
- **ESFMu (fast)**: this is a modification of ESFMu to reduce CPU usage at the cost of less accuracy.
- **OPLL core**:
- **Nuked-OPLL**: this core is accurate and the default.
- **emu2413**: a less accurate core that uses less CPU.

View file

@ -23,7 +23,6 @@
// portions apparently taken from FamiTracker source, which is under GPLv2+
// TODO:
// - audit for CVEs
// - format code?
#include "fileOpsCommon.h"
@ -258,6 +257,7 @@ const int eff_conversion_050[][2] = {
};
constexpr int ftEffectMapSize = sizeof(ftEffectMap) / sizeof(int);
constexpr int eftEffectMapSize = sizeof(eftEffectMap) / sizeof(int);
int convertMacros2A03[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY};
int convertMacrosVRC6[5] = {(int)DIV_MACRO_VOL, (int)DIV_MACRO_ARP, (int)DIV_MACRO_PITCH, -1, (int)DIV_MACRO_DUTY};
@ -434,7 +434,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
bool hasSequence[256][8];
unsigned char sequenceIndex[256][8];
unsigned char macro_types[256][8];
std::vector<std::vector<DivInstrumentMacro>> macros;
std::vector<DivInstrumentMacro> macros[256];
std::vector<String> encounteredBlocks;
unsigned char map_channels[DIV_MAX_CHANS];
unsigned int hilightA = 4;
unsigned int hilightB = 16;
@ -457,13 +458,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
memset(map_channels, 0xfe, DIV_MAX_CHANS * sizeof(unsigned char));
for (int i = 0; i < 256; i++) {
std::vector<DivInstrumentMacro> mac;
for (int j = 0; j < 8; j++) {
mac.push_back(DivInstrumentMacro(DIV_MACRO_VOL));
macros[i].push_back(DivInstrumentMacro(DIV_MACRO_VOL));
}
macros.push_back(mac);
}
if (!reader.seek((dnft && dnft_sig) ? 21 : 18, SEEK_SET)) {
@ -482,7 +479,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
return false;
}
for (DivSubSong* i : ds.subsong) {
for (DivSubSong* i: ds.subsong) {
i->clearData();
delete i;
}
@ -501,13 +498,30 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
// not the end
reader.seek(-3, SEEK_CUR);
if (!reader.seek(-3, SEEK_CUR)) {
logE("couldn't seek back by 3!");
lastError = "couldn't seek back by 3";
delete[] file;
return false;
}
blockName = reader.readString(16);
unsigned int blockVersion = (unsigned int)reader.readI();
unsigned int blockSize = (unsigned int)reader.readI();
size_t blockStart = reader.tell();
logD("reading block %s (version %d, %d bytes, position %x)", blockName, blockVersion, blockSize, reader.tell());
for (String& i: encounteredBlocks) {
if (blockName==i) {
logE("duplicate block %s!",blockName);
lastError = "duplicate block "+blockName;
ds.unload();
delete[] file;
return false;
}
}
encounteredBlocks.push_back(blockName);
if (blockName == "PARAMS") {
// versions 7-9 don't change anything?
CHECK_BLOCK_VERSION(9);
@ -529,6 +543,13 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
tchans = reader.readI();
if (tchans<0 || tchans>=DIV_MAX_CHANS) {
logE("invalid channel count! %d",tchans);
lastError = "invalid channel count";
delete[] file;
return false;
}
if (tchans == 5) {
expansions = 0; // This is strange. Sometimes expansion chip is set to 0xFF in files
}
@ -538,12 +559,15 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (blockVersion >= 7) {
// advanced Hz control
int controlType = reader.readI();
switch (controlType) {
int readHz=reader.readI();
if (readHz<=0) {
customHz=60.0;
} else switch (controlType) {
case 1:
customHz = 1000000.0 / (double)reader.readI();
customHz = 1000000.0 / (double)readHz;
break;
default:
reader.readI();
logW("unsupported tick rate control type %d",controlType);
break;
}
} else {
@ -553,6 +577,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
customHz = reader.readI();
}
logV("before clamp: %f",customHz);
if (customHz>1000.0) customHz=1000.0;
unsigned int newVibrato = 0;
bool sweepReset = false;
unsigned int speedSplitPoint = 0;
@ -576,6 +604,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if ((expansions & 16) && blockVersion >= 5) { // N163 channels
n163Chans = reader.readI();
if (n163Chans<1 || n163Chans>=9) {
logE("invalid Namco 163 channel count! %d",n163Chans);
lastError = "invalid Namco 163 channel count";
delete[] file;
return false;
}
}
if (blockVersion >= 6) {
speedSplitPoint = reader.readI();
@ -779,6 +813,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
}
if (calcChans != tchans) {
// TODO: would ignore trigger CVE? too bad if so!
if (!eft || (eft && (expansions & 8) == 0)) // ignore since I have no idea how to tell apart E-FT versions which do or do not have PCM chan. Yes, this may lead to all the righer channels to be shifted but at least you still get note data!
{
logE("channel counts do not match! %d != %d", tchans, calcChans);
@ -788,17 +823,20 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
}
if (tchans > DIV_MAX_CHANS) {
tchans = DIV_MAX_CHANS;
logW("too many channels!");
logE("too many channels!");
lastError = "too many channels";
delete[] file;
return false;
}
if (blockVersion == 9 && blockSize - (reader.tell() - blockStart) == 2) // weird
{
reader.seek(2, SEEK_CUR);
if (!reader.seek(2, SEEK_CUR)) {
logE("could not weird-seek by 2!");
lastError = "could not weird-seek by 2";
delete[] file;
return false;
}
}
if (eft) {
// reader.seek(8,SEEK_CUR);
}
} else if (blockName == "INFO") {
CHECK_BLOCK_VERSION(1);
ds.name = reader.readString(32);
@ -831,6 +869,13 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
for (int j = 0; j <= totalSongs; j++) {
unsigned char effectCols = reader.readC();
if (effectCols>7) {
logE("too many effect columns!");
lastError = "too many effect columns";
delete[] file;
return false;
}
if (map_channels[i] == 0xfe) {
ds.subsong[j]->pat[i].effectCols = 1;
logV("- song %d has %d effect columns", j, effectCols);
@ -850,8 +895,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
} else if (blockName == "INSTRUMENTS") {
CHECK_BLOCK_VERSION(9);
// reader.seek(blockSize,SEEK_CUR);
ds.insLen = reader.readI();
if (ds.insLen < 0 || ds.insLen > 256) {
logE("too many instruments/out of range!");
@ -870,10 +913,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
for (int i = 0; i < ds.insLen; i++) {
unsigned int insIndex = reader.readI();
if (insIndex >= ds.ins.size()) {
// logE("instrument index %d is out of range!",insIndex);
// lastError="instrument index out of range";
// delete[] file;
// return false;
logE("instrument index %d is out of range!",insIndex);
lastError="instrument index out of range";
delete[] file;
return false;
}
DivInstrument* ins = ds.ins[insIndex];
@ -937,6 +980,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (blockVersion >= 7) {
note = reader.readC();
}
if (note<0 || note>=120) {
logE("DPCM note %d out of range!",note);
lastError = "DPCM note out of range";
delete[] file;
return false;
}
ins->amiga.noteMap[note].map = (short)((unsigned char)reader.readC()) - 1;
unsigned char freq = reader.readC();
ins->amiga.noteMap[note].dpcmFreq = (freq & 15); // 0-15 = 0-15 unlooped, 128-143 = 0-15 looped
@ -981,8 +1030,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
case DIV_INS_OPLL: {
ins->fm.opllPreset = (unsigned int)reader.readI();
ins->fm.opllPreset&=15;
unsigned char custom_patch[8] = {0};
unsigned char custom_patch[8];
for (int i = 0; i < 8; i++) {
custom_patch[i] = reader.readC();
@ -1020,24 +1070,35 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
for (int j = 0; j < 64; j++) {
wave->data[j] = reader.readC();
}
ins->std.waveMacro.len = 1;
ins->std.waveMacro.val[0] = ds.wave.size();
for (int j = 0; j < 32; j++) {
ins->fds.modTable[j] = reader.readC() - 3;
}
ins->fds.modSpeed = reader.readI();
ins->fds.modDepth = reader.readI();
reader.readI(); // this is delay. currently ignored. TODO.
ds.wave.push_back(wave);
ds.waveLen++;
if (ds.wave.size()>=256) {
logW("too many waves! ignoring...");
delete wave;
} else {
ins->std.waveMacro.len = 1;
ins->std.waveMacro.val[0] = ds.wave.size();
ds.wave.push_back(wave);
ds.waveLen++;
}
unsigned int a = reader.readI();
unsigned int b = reader.readI();
reader.seek(-8, SEEK_CUR);
if (!reader.seek(-8, SEEK_CUR)) {
logE("couldn't seek back by 8 reading FDS ins");
lastError = "couldn't seek back by 8 reading FDS ins";
delete[] file;
return false;
}
if (a < 256 && (b & 0xFF) != 0x00) {
// don't look at me like this. I don't know why this should be like this either!
logW("a is less than 256 and b is not zero!");
} else {
ins->std.volMacro.len = reader.readC();
ins->std.volMacro.loop = reader.readI();
@ -1112,12 +1173,19 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (blockVersion >= 8) {
unsigned int autopos = reader.readI();
(void)autopos;
logV("autopos: %d",autopos);
}
unsigned int wave_count = reader.readI();
size_t waveOff = ds.wave.size();
if (wave_size>256) {
logE("wave size %d out of range",wave_size);
lastError = "wave size out of range";
delete[] file;
return false;
}
for (unsigned int ii = 0; ii < wave_count; ii++) {
DivWavetable* wave = new DivWavetable();
wave->len = wave_size;
@ -1131,6 +1199,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (ds.wave.size()<256) {
ds.wave.push_back(wave);
} else {
logW("too many waves...");
delete wave;
}
}
@ -1296,16 +1365,30 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
if (instVersion == 2) {
reader.seek(seek_amount, SEEK_CUR); // what the fuck
// I know right?
if (!reader.seek(seek_amount, SEEK_CUR)) {
logE("EFT seek fail");
lastError = "EFT seek fail";
delete[] file;
return false;
}
}
// this commented out block left here intentionally.
// total mess of code style... for with no space, UNDEFINED CHAR, escaping the unescapable, silly var names...
// ...whatever.
/*for(int tti = 0; tti < 20; tti++)
{
char aaaa = reader.readC();
logV("\'%c\'", aaaa);
}*/
} else {
reader.seek(-4, SEEK_CUR);
if (!reader.seek(-4, SEEK_CUR)) {
logE("EFT -4 seek fail");
lastError = "EFT -4 seek fail";
delete[] file;
return false;
}
}
break;
@ -1325,7 +1408,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
ds.insLen = 128;
} else if (blockName == "SEQUENCES") {
CHECK_BLOCK_VERSION(6);
@ -1342,6 +1424,14 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
unsigned int index = reader.readI();
unsigned int type = reader.readI();
unsigned char size = reader.readC();
if (index>=256 || type>=8) {
logE("%d: index/type out of range",i);
lastError = "sequence index/type out of range";
delete[] file;
return false;
}
macros[index][type].len = size;
for (int j = 0; j < size; j++) {
@ -1361,12 +1451,34 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
unsigned char* Indices = new unsigned char[128 * 5];
unsigned char* Types = new unsigned char[128 * 5];
memset(Indices,0,128*5);
memset(Types,0,128*5);
for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI();
if (index>=128*5) {
logE("%d: index out of range",i);
lastError = "sequence index out of range";
delete[] file;
return false;
}
Indices[i] = index;
unsigned int type = reader.readI();
if (type>=128*5) {
logE("%d: type out of range",i);
lastError = "sequence type out of range";
delete[] file;
return false;
}
Types[i] = type;
if (index>=256 || type>=8) {
logE("%d: index/type out of range",i);
lastError = "sequence index/type out of range";
delete[] file;
return false;
}
unsigned char size = reader.readC();
unsigned int setting = 0;
@ -1393,7 +1505,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) {
copyMacro(ins, &macros[index][type], Types[i], setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro));
}
}
}
@ -1412,7 +1523,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
macro_types[k][j] = setting;
copyMacro(ins, &macros[sequenceIndex[k][j]][j], j, setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)j, true), &macros[sequenceIndex[k][j]][j], sizeof(DivInstrumentMacro));
}
}
}
@ -1425,9 +1535,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
unsigned int release = reader.readI();
unsigned int setting = reader.readI();
// macros[index][type].rel = release;
// macro_types[index][type] = setting;
for (int k = 0; k < (int)ds.ins.size(); k++) {
DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) {
@ -1435,7 +1542,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
macro_types[k][Types[i]] = setting;
copyMacro(ins, &macros[sequenceIndex[k][Types[i]]][Types[i]], Types[i], setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[sequenceIndex[k][Types[i]]][Types[i]], sizeof(DivInstrumentMacro));
}
}
}
@ -1446,12 +1552,11 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
} else if (blockName == "GROOVES") {
CHECK_BLOCK_VERSION(6);
// reader.seek(blockSize,SEEK_CUR);
unsigned char num_grooves = reader.readC();
int max_groove = 0;
for (int i = 0; i < 0xff; i++) {
for (int i = 0; i < 256; i++) {
ds.grooves.push_back(DivGroovePattern());
}
@ -1459,15 +1564,18 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
unsigned char index = reader.readC();
unsigned char size = reader.readC();
if (index > max_groove)
if (index > max_groove) {
max_groove = index + 1;
}
DivGroovePattern gp;
gp.len = size;
for (int sz = 0; sz < size; sz++) {
unsigned char value = reader.readC();
gp.val[sz] = value;
if (sz<16) {
gp.val[sz] = value;
}
}
ds.grooves[index] = gp;
@ -1488,14 +1596,21 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if ((reader.tell() - blockStart) != blockSize) {
logE("block %s size does not match! block size %d curr pos %d", blockName, blockSize, reader.tell() - blockStart);
}
} else if (blockName == "FRAMES") {
CHECK_BLOCK_VERSION(3);
for (size_t i = 0; i < ds.subsong.size(); i++) {
DivSubSong* s = ds.subsong[i];
s->ordersLen = reader.readI();
int framesLen=reader.readI();
if (framesLen<=1 || framesLen>256) {
logE("frames out of range");
lastError = "frames out of range";
delete[] file;
return false;
}
s->ordersLen = framesLen;
if (blockVersion >= 3) {
s->speeds.val[0] = reader.readI();
}
@ -1510,18 +1625,31 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
s->virtualTempoN = tempo;
}
s->patLen = reader.readI();
int patLen=reader.readI();
if (patLen<1 || patLen>256) {
logE("pattern length out of range");
lastError = "pattern length out of range";
delete[] file;
return false;
}
s->patLen = patLen;
}
int why = tchans;
if (blockVersion == 1) {
why = reader.readI();
if (why<0 || why>=DIV_MAX_CHANS) {
logE("why out of range!");
lastError = "why out of range";
delete[] file;
return false;
}
}
logV("reading %d and %d orders", tchans, s->ordersLen);
for (int j = 0; j < s->ordersLen; j++) {
for (int k = 0; k < why; k++) {
unsigned char o = reader.readC();
// logV("%.2x",o);
if (map_channels[k]>=DIV_MAX_CHANS) continue;
s->orders.ord[map_channels[k]][j] = o;
}
}
@ -1533,6 +1661,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (blockVersion == 1) {
int patLenOld = reader.readI();
if (patLenOld<1 || patLenOld>=256) {
logE("old pattern length out of range");
lastError = "old pattern length out of range";
delete[] file;
return false;
}
for (DivSubSong* i : ds.subsong) {
i->patLen = patLenOld;
}
@ -1553,6 +1687,37 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
logV("patNum: %d",patNum);
logV("rows: %d",numRows);
if (subs<0 || subs>=(int)ds.subsong.size()) {
logE("subsong out of range!");
lastError = "subsong out of range";
delete[] file;
return false;
}
if (ch<0 || ch>=DIV_MAX_CHANS) {
logE("channel out of range!");
lastError = "channel out of range";
delete[] file;
return false;
}
if (map_channels[ch]>=DIV_MAX_CHANS) {
logE("mapped channel out of range!");
lastError = "mapped channel out of range";
delete[] file;
return false;
}
if (patNum<0 || patNum>=256) {
logE("pattern number out of range!");
lastError = "pattern number out of range";
delete[] file;
return false;
}
if (numRows<0) {
logE("row count is negative!");
lastError = "row count is negative";
delete[] file;
return false;
}
DivPattern* pat = ds.subsong[subs]->pat[map_channels[ch]].getPattern(patNum, true);
for (int i = 0; i < numRows; i++) {
unsigned int row = 0;
@ -1562,6 +1727,13 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
row = reader.readI();
}
if (row>=256) {
logE("row index out of range");
lastError = "row index out of range";
delete[] file;
return false;
}
unsigned char nextNote = reader.readC();
unsigned char nextOctave = reader.readC();
@ -1592,6 +1764,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
unsigned char nextIns = reader.readC();
// TODO: you sure about 0xff?
if (map_channels[ch] != 0xff) {
if (nextIns < 0x40 && nextNote != 0x0d && nextNote != 0x0e) {
pat->data[row][2] = nextIns;
@ -1606,6 +1779,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
pat->data[row][3] = nextVol;
if (map_channels[ch] == vrc6_saw_chan) // scale volume
{
// TODO: shouldn't it be 32?
pat->data[row][3] = (pat->data[row][3] * 42) / 15;
}
@ -1625,13 +1799,9 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
effectCols = 1;
}
logV("effectCols: %d",effectCols);
unsigned char nextEffectVal = 0;
unsigned char nextEffect = 0;
//logV("row %d effects are read at %x",row,reader.tell());
for (int j = 0; j < effectCols; j++) {
nextEffect = reader.readC();
@ -1727,14 +1897,12 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
// logW("next effect %d val %d", nextEffect, nextEffectVal);
if (map_channels[ch] != 0xff) {
if (nextEffect == 0 && nextEffectVal == 0) {
pat->data[row][4 + (j * 2)] = -1;
pat->data[row][5 + (j * 2)] = -1;
} else {
if (nextEffect < ftEffectMapSize) {
if ((eft && nextEffect<eftEffectMapSize) || (!eft && nextEffect<ftEffectMapSize)) {
if (eft) {
pat->data[row][4 + (j * 2)] = eftEffectMap[nextEffect];
pat->data[row][5 + (j * 2)] = eftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal;
@ -1782,7 +1950,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
} else if (blockName == "DPCM SAMPLES") {
CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
unsigned char num_samples = reader.readC();
for (int i = 0; i < 256; i++) {
@ -1808,14 +1975,16 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
unsigned int sample_len = reader.readI();
if (sample_len>=2097152) {
logE("%d: sample too large! %d",index,sample_len);
lastError = "sample too large";
delete[] file;
return false;
}
true_size = sample_len + ((1 - (int)sample_len) & 0x0f);
sample->lengthDPCM = true_size;
sample->samples = true_size * 8;
sample->dataDPCM = new unsigned char[true_size];
sample->init(true_size * 8);
memset(sample->dataDPCM, 0xAA, true_size);
reader.read(sample->dataDPCM, sample_len);
}
@ -1824,7 +1993,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
for (int i = 255; i > 0; i--) {
DivSample* s = ds.sample[i];
if (s->dataDPCM) {
if (s->samples>0) {
last_non_empty_sample = i;
break;
}
@ -1835,21 +2004,42 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
ds.sampleLen = ds.sample.size();
} else if (blockName == "SEQUENCES_VRC6") {
CHECK_BLOCK_VERSION(6);
unsigned char* Indices = new unsigned char[128 * 5];
unsigned char* Types = new unsigned char[128 * 5];
memset(Indices,0,128*5);
memset(Types,0,128*5);
unsigned int seq_count = reader.readI();
for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI();
if (index>=128*5) {
logE("%d: index out of range",i);
lastError = "sequence index out of range";
delete[] file;
return false;
}
Indices[i] = index;
unsigned int type = reader.readI();
if (type>=128*5) {
logE("%d: type out of range",i);
lastError = "sequence type out of range";
delete[] file;
return false;
}
Types[i] = type;
if (index>=256 || type>=8) {
logE("%d: index/type out of range",i);
lastError = "sequence index/type out of range";
delete[] file;
return false;
}
unsigned char size = reader.readC();
unsigned int setting = 0;
@ -1914,9 +2104,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
unsigned int release = reader.readI();
unsigned int setting = reader.readI();
// macros[index][type].rel = release;
// macro_types[index][type] = setting;
for (int k = 0; k < (int)ds.ins.size(); k++) {
DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_VRC6 && hasSequence[k][Types[i]]) {
@ -1937,19 +2124,40 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
delete[] Types;
} else if (blockName == "SEQUENCES_N163" || blockName == "SEQUENCES_N106") {
CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
unsigned char* Indices = new unsigned char[128 * 5];
unsigned char* Types = new unsigned char[128 * 5];
memset(Indices,0,128*5);
memset(Types,0,128*5);
unsigned int seq_count = reader.readI();
for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI();
if (index>=128*5) {
logE("%d: index out of range",i);
lastError = "sequence index out of range";
delete[] file;
return false;
}
Indices[i] = index;
unsigned int type = reader.readI();
if (type>=128*5) {
logE("%d: type out of range",i);
lastError = "sequence type out of range";
delete[] file;
return false;
}
Types[i] = type;
if (index>=256 || type>=8) {
logE("%d: index/type out of range",i);
lastError = "sequence index/type out of range";
delete[] file;
return false;
}
unsigned char size = reader.readC();
unsigned int setting = 0;
@ -1974,7 +2182,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_N163 && hasSequence[k][Types[i]]) {
copyMacro(ins, &macros[index][type], type, setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro));
}
}
}
@ -1984,19 +2191,40 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
} else if (blockName == "SEQUENCES_S5B") {
CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
unsigned char* Indices = new unsigned char[128 * 5];
unsigned char* Types = new unsigned char[128 * 5];
memset(Indices,0,128*5);
memset(Types,0,128*5);
unsigned int seq_count = reader.readI();
for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI();
if (index>=128*5) {
logE("%d: index out of range",i);
lastError = "sequence index out of range";
delete[] file;
return false;
}
Indices[i] = index;
unsigned int type = reader.readI();
if (type>=128*5) {
logE("%d: type out of range",i);
lastError = "sequence type out of range";
delete[] file;
return false;
}
Types[i] = type;
if (index>=256 || type>=8) {
logE("%d: index/type out of range",i);
lastError = "sequence index/type out of range";
delete[] file;
return false;
}
unsigned char size = reader.readC();
unsigned int setting = 0;
@ -2021,7 +2249,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_AY && hasSequence[k][type]) {
copyMacro(ins, &macros[index][type], type, setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro));
}
}
}
@ -2034,14 +2261,36 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
unsigned char* Indices = new unsigned char[128 * 5];
unsigned char* Types = new unsigned char[128 * 5];
memset(Indices,0,128*5);
memset(Types,0,128*5);
unsigned int seq_count = reader.readI();
for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI();
if (index>=128*5) {
logE("%d: index out of range",i);
lastError = "sequence index out of range";
delete[] file;
return false;
}
Indices[i] = index;
unsigned int type = reader.readI();
if (type>=128*5) {
logE("%d: type out of range",i);
lastError = "sequence type out of range";
delete[] file;
return false;
}
Types[i] = type;
if (index>=256 || type>=8) {
logE("%d: index/type out of range",i);
lastError = "sequence index/type out of range";
delete[] file;
return false;
}
unsigned char size = reader.readC();
unsigned int setting = 0;
@ -2066,7 +2315,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_C64 && hasSequence[k][type]) {
copyMacro(ins, &macros[index][type], type, setting);
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), &macros[sequenceIndex[index][type]][type], sizeof(DivInstrumentMacro));
}
}
}
@ -2075,31 +2323,33 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
delete[] Types;
} else if (blockName == "JSON") {
CHECK_BLOCK_VERSION(1);
logW("block JSON not supported...");
reader.seek(blockSize, SEEK_CUR);
} else if (blockName == "PARAMS_EMU") {
CHECK_BLOCK_VERSION(1);
logW("block PARAMS_EMU not supported...");
reader.seek(blockSize, SEEK_CUR);
} else if (blockName == "DETUNETABLES") {
CHECK_BLOCK_VERSION(1);
logW("block DETUNETABLES not supported...");
reader.seek(blockSize, SEEK_CUR);
} else if (blockName == "COMMENTS") {
CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
unsigned int display_comment = reader.readI();
(void)display_comment;
char ch = 1;
logV("displayComment: %d",display_comment);
do {
char ch = 0;
// why not readString?
while (true) {
ch = reader.readC();
String sss = String() + ch;
ds.subsong[0]->notes += sss;
} while (ch != 0);
if (ch==0) break;
ds.subsong[0]->notes += ch;
}
// ds.subsong[0]->notes = reader.readS();
} else if (blockName == "PARAMS_EXTRA") {
CHECK_BLOCK_VERSION(3);
// reader.seek(blockSize,SEEK_CUR);
unsigned int linear_pitch = reader.readI();
ds.linearPitch = linear_pitch == 0 ? 0 : 2;
@ -2112,11 +2362,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
if (blockVersion >= 3) {
unsigned char flats = reader.readC();
(void)flats;
logV("flats: %d",(int)flats);
}
} else if (blockName == "TUNING") {
CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
if (blockVersion == 1) {
int fineTuneCents = reader.readC() * 100;
fineTuneCents += reader.readC();
@ -2125,6 +2374,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
}
} else if (blockName == "BOOKMARKS") {
CHECK_BLOCK_VERSION(1);
logW("block BOOKMARKS not supported...");
reader.seek(blockSize, SEEK_CUR);
} else {
logE("block %s is unknown!", blockName);
@ -2152,6 +2402,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (index < 0)
index = 0;
if (index>=(int)ds.ins.size()) continue;
DivInstrument* ins = ds.ins[index];
if (ins->type == DIV_INS_FM) {

View file

@ -1897,7 +1897,7 @@ std::vector<DivInstrument*> DivEngine::instrumentFromFile(const char* path, bool
bool isOldFurnaceIns=false;
try {
reader.read(magic,4);
if (memcmp("FINS",magic,4)==0) {
if (memcmp("FINS",magic,4)==0 || memcmp("FINB",magic,4)==0) {
isFurnaceInstr=true;
logV("found a new Furnace ins");
} else {

View file

@ -3005,6 +3005,8 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version, DivS
type=1;
} else if (memcmp(magic,"FINS",4)==0) {
type=2;
} else if (memcmp(magic,"FINB",4)==0) { // DIV_FUR_VARIANT_B
type=2;
} else {
logE("invalid instrument header!");
return DIV_DATA_INVALID_HEADER;

View file

@ -1303,7 +1303,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef(
"Konami VRC7", NULL, 0x9d, 0, 6, true, false, 0x151, false, 0, 0, 0,
"like OPLL, but even more cost reductions applied. three less FM channels, and no drums mode...",
"like OPLL, but even more cost reductions applied. three FM channels went missing, and drums mode did as well...",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "F3", "F4", "F5", "F6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM},

View file

@ -71,6 +71,7 @@ const char* aboutLine[]={
"Aburtos",
"ActualNK358",
"akumanatt",
"aloelucidity",
"AmigaX",
"AquaDoesStuff",
"AURORA*FIELDS",

View file

@ -4391,7 +4391,7 @@ bool FurnaceGUI::loop() {
toggleMobileUI(!mobileUI);
}
#endif
if (ImGui::MenuItem("manage presets...",BIND_FOR(GUI_ACTION_WINDOW_USER_PRESETS))) {
if (ImGui::MenuItem("user systems...",BIND_FOR(GUI_ACTION_WINDOW_USER_PRESETS))) {
userPresetsOpen=true;
}
if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) {

View file

@ -1265,6 +1265,7 @@ struct FurnaceGUISysDef {
String definition;
std::vector<FurnaceGUISysDefChip> orig;
std::vector<FurnaceGUISysDef> subDefs;
void bake();
FurnaceGUISysDef(const char* n, std::initializer_list<FurnaceGUISysDefChip> def, const char* e=NULL);
FurnaceGUISysDef(const char* n, const char* def, DivEngine* e);
};
@ -2089,7 +2090,7 @@ class FurnaceGUI {
displayRenderTime(0),
maxUndoSteps(100),
vibrationStrength(0.5f),
vibrationLength(100),
vibrationLength(20),
mainFontPath(""),
headFontPath(""),
patFontPath(""),

View file

@ -7469,7 +7469,7 @@ void FurnaceGUI::drawInsEdit() {
memset(oldData,0,256*sizeof(int));
memcpy(oldData,lastMacroDesc.macro->val,lastMacroDesc.macro->len*sizeof(int));
lastMacroDesc.macro->len=MIN(128,((double)lastMacroDesc.macro->len*(macroScaleX/100.0)));
lastMacroDesc.macro->len=MIN(255,((double)lastMacroDesc.macro->len*(macroScaleX/100.0)));
for (int i=0; i<lastMacroDesc.macro->len; i++) {
int val=0;

View file

@ -3102,11 +3102,9 @@ void FurnaceGUI::initSystemPresets() {
CATEGORY_END;
}
FurnaceGUISysDef::FurnaceGUISysDef(const char* n, std::initializer_list<FurnaceGUISysDefChip> def, const char* e):
name(n),
extra((e==NULL)?"":e) {
orig=def;
void FurnaceGUISysDef::bake() {
int index=0;
definition="";
for (FurnaceGUISysDefChip& i: orig) {
definition+=fmt::sprintf(
"id%d=%d\nvol%d=%f\npan%d=%f\nflags%d=%s\n",
@ -3126,12 +3124,19 @@ FurnaceGUISysDef::FurnaceGUISysDef(const char* n, std::initializer_list<FurnaceG
}
}
FurnaceGUISysDef::FurnaceGUISysDef(const char* n, std::initializer_list<FurnaceGUISysDefChip> def, const char* e):
name(n),
extra((e==NULL)?"":e) {
orig=def;
bake();
}
FurnaceGUISysDef::FurnaceGUISysDef(const char* n, const char* def, DivEngine* e):
name(n),
definition(def) {
definition(taDecodeBase64(def)) {
// extract definition
DivConfig conf;
conf.loadFromBase64(def);
conf.loadFromMemory(definition.c_str());
for (int i=0; i<DIV_MAX_CHIPS; i++) {
String nextStr=fmt::sprintf("id%d",i);
int id=conf.getInt(nextStr.c_str(),0);

View file

@ -4110,7 +4110,7 @@ void FurnaceGUI::readConfig(DivConfig& conf, FurnaceGUISettingGroups groups) {
settings.centerPopup=conf.getInt("centerPopup",1);
settings.vibrationStrength=conf.getFloat("vibrationStrength",0.5f);
settings.vibrationLength=conf.getInt("vibrationLength",100);
settings.vibrationLength=conf.getInt("vibrationLength",20);
}
if (groups&GUI_SETTINGS_AUDIO) {

View file

@ -453,13 +453,17 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
if (sampRate>65536) sampRate=65536;
altered=true;
} rightClickable
DivPlatformGBAMinMod* dispatch=(DivPlatformGBAMinMod*)e->getDispatch(chan);
float maxCPU=dispatch->maxCPU*100;
ImGui::Text("Actual sample rate: %d Hz", dispatch->chipClock);
if (maxCPU>90) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]);
ImGui::Text("Max mixer CPU usage: %.0f%%", maxCPU);
if (maxCPU>90) ImGui::PopStyleColor();
FurnaceGUI::popWarningColor();
if (chan>=0) {
DivPlatformGBAMinMod* dispatch=(DivPlatformGBAMinMod*)e->getDispatch(chan);
if (dispatch!=NULL) {
float maxCPU=dispatch->maxCPU*100;
ImGui::Text("Actual sample rate: %d Hz", dispatch->chipClock);
if (maxCPU>90) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]);
ImGui::Text("Max mixer CPU usage: %.0f%%",maxCPU);
if (maxCPU>90) ImGui::PopStyleColor();
FurnaceGUI::popWarningColor();
}
}
if (altered) {
e->lockSave([&]() {
flags.set("volScale",volScale);

View file

@ -453,7 +453,7 @@ struct FurnaceCV {
newHiScore(false),
playSongs(true),
pleaseInitSongs(false),
lives(3),
lives(5),
respawnTime(0),
stage(0),
shotType(0),

View file

@ -177,7 +177,7 @@ bool FurnaceGUI::loadUserPresets(bool redundancy) {
}
indent>>=1;
if (!key.empty() && !value.empty()) {
if (!key.empty()) {
std::vector<FurnaceGUISysDef>* where=digDeep(userCategory->systems,indent);
where->push_back(FurnaceGUISysDef(key.c_str(),value.c_str(),e));
}
@ -203,7 +203,7 @@ void writeSubEntries(FILE* f, std::vector<FurnaceGUISysDef>& entries, int depth)
for (int i=0; i<depth; i++) {
data+=" ";
}
data+=fmt::sprintf("%s=%s\n",safeName,i.definition);
data+=fmt::sprintf("%s=%s\n",safeName,taEncodeBase64(i.definition));
fputs(data.c_str(),f);
writeSubEntries(f,i.subDefs,depth+1);
@ -322,7 +322,7 @@ void FurnaceGUI::drawUserPresets() {
nextWindow=GUI_WINDOW_NOTHING;
}
if (!userPresetsOpen) return;
if (ImGui::Begin("User Presets",&userPresetsOpen,globalWinFlags)) {
if (ImGui::Begin("User Systems",&userPresetsOpen,globalWinFlags)) {
FurnaceGUISysCategory* userCategory=NULL;
for (FurnaceGUISysCategory& i: sysCategories) {
if (strcmp(i.name,"User")==0) {
@ -335,34 +335,173 @@ void FurnaceGUI::drawUserPresets() {
if (userCategory==NULL) {
ImGui::Text("Error! User category does not exist!");
} else if (ImGui::BeginTable("UserPresets",2,ImGuiTableFlags_BordersInnerV)) {
} else if (ImGui::BeginTable("UserPresets",2,ImGuiTableFlags_BordersInnerV,ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()))) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.25f);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.75f);
// preset list
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Button(ICON_FA_PLUS "##AddPreset")) {
userCategory->systems.push_back(FurnaceGUISysDef("New Preset",{}));
selectedUserPreset.clear();
selectedUserPreset.push_back(userCategory->systems.size()-1);
if (ImGui::BeginChild("UList",ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()))) {
ImGui::AlignTextToFramePadding();
ImGui::Text("Systems");
ImGui::SameLine();
if (ImGui::Button(ICON_FA_PLUS "##AddPreset")) {
userCategory->systems.push_back(FurnaceGUISysDef("New Preset",{}));
selectedUserPreset.clear();
selectedUserPreset.push_back(userCategory->systems.size()-1);
}
printPresets(userCategory->systems,0,depthStack);
}
printPresets(userCategory->systems,0,depthStack);
ImGui::EndChild();
// editor
ImGui::TableNextColumn();
if (selectedUserPreset.empty()) {
ImGui::Text("select a preset");
} else {
FurnaceGUISysDef* preset=selectPreset(userCategory->systems);
if (ImGui::BeginChild("UEdit",ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()))) {
if (selectedUserPreset.empty()) {
ImGui::Text("select a preset");
} else {
FurnaceGUISysDef* preset=selectPreset(userCategory->systems);
bool doRemovePreset=false;
if (preset!=NULL) {
ImGui::AlignTextToFramePadding();
ImGui::Text("Name");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::InputText("##PName",&preset->name);
ImGui::Separator();
ImGui::Text("the rest...");
if (preset!=NULL) {
ImGui::AlignTextToFramePadding();
ImGui::Text("Name");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize("Remove").x-ImGui::GetStyle().ItemSpacing.x*2.0-ImGui::GetStyle().ItemInnerSpacing.x*2.0);
ImGui::InputText("##PName",&preset->name);
ImGui::SameLine();
pushDestColor();
if (ImGui::Button("Remove##UPresetRemove")) {
doRemovePreset=true;
}
popDestColor();
ImGui::Separator();
int doRemove=-1;
bool mustBake=false;
for (size_t i=0; i<preset->orig.size(); i++) {
String tempID;
FurnaceGUISysDefChip& chip=preset->orig[i];
bool doInvert=(chip.vol<0);
float vol=fabs(chip.vol);
ImGui::PushID(i);
tempID=fmt::sprintf("%s##USystem",getSystemName(chip.sys));
ImGui::Button(tempID.c_str(),ImVec2(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize("Invert").x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0,0));
if (ImGui::BeginPopupContextItem("SysPickerCU",ImGuiPopupFlags_MouseButtonLeft)) {
DivSystem picked=systemPicker();
if (picked!=DIV_SYSTEM_NULL) {
chip.sys=picked;
mustBake=true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::SameLine();
if (ImGui::Checkbox("Invert",&doInvert)) {
chip.vol=-chip.vol;
mustBake=true;
}
ImGui::SameLine();
pushDestColor();
if (ImGui::Button(ICON_FA_MINUS "##USysRemove")) {
doRemove=i;
mustBake=true;
}
popDestColor();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0);
if (CWSliderFloat("Volume",&vol,0.0f,3.0f)) {
if (doInvert) {
if (vol<0.0001) vol=0.0001;
}
if (vol<0) vol=0;
if (vol>10) vol=10;
chip.vol=doInvert?-vol:vol;
mustBake=true;
} rightClickable
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0);
if (CWSliderFloat("Panning",&chip.pan,-1.0f,1.0f)) {
if (chip.pan<-1.0f) chip.pan=-1.0f;
if (chip.pan>1.0f) chip.pan=1.0f;
mustBake=true;
} rightClickable
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0);
if (CWSliderFloat("Front/Rear",&chip.panFR,-1.0f,1.0f)) {
if (chip.panFR<-1.0f) chip.panFR=-1.0f;
if (chip.panFR>1.0f) chip.panFR=1.0f;
mustBake=true;
} rightClickable
if (ImGui::TreeNode("Configure")) {
DivConfig sysFlags;
sysFlags.loadFromBase64(chip.flags.c_str());
if (drawSysConf(-1,i,chip.sys,sysFlags,false)) {
chip.flags=sysFlags.toBase64();
mustBake=true;
}
ImGui::TreePop();
}
ImGui::PopID();
}
if (doRemove>=0) {
preset->orig.erase(preset->orig.begin()+doRemove);
mustBake=true;
}
ImGui::Button(ICON_FA_PLUS "##SysAddU");
if (ImGui::BeginPopupContextItem("SysPickerU",ImGuiPopupFlags_MouseButtonLeft)) {
DivSystem picked=systemPicker();
if (picked!=DIV_SYSTEM_NULL) {
preset->orig.push_back(FurnaceGUISysDefChip(picked,1.0f,0.0f,""));
mustBake=true;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::Separator();
ImGui::Text("Advanced");
if (ImGui::InputTextMultiline("##UExtra",&preset->extra,ImVec2(ImGui::GetContentRegionAvail().x,120.0f*dpiScale),ImGuiInputTextFlags_UndoRedo)) {
mustBake=true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(
"insert additional settings in `option=value` format.\n"
"available options:\n"
"- tickRate"
);
}
if (mustBake) preset->bake();
} else {
selectedUserPreset.clear();
}
if (doRemovePreset) {
std::vector<FurnaceGUISysDef>& items=userCategory->systems;
FurnaceGUISysDef* target=NULL;
for (size_t i=0; i<selectedUserPreset.size(); i++) {
if (selectedUserPreset[i]<0 || selectedUserPreset[i]>(int)items.size()) break;
target=&items[selectedUserPreset[i]];
if (i<selectedUserPreset.size()-1) {
items=target->subDefs;
} else {
items.erase(items.begin()+selectedUserPreset[i]);
}
}
selectedUserPreset.clear();
}
}
}
ImGui::EndChild();
ImGui::EndTable();
}