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 # to-do for 0.6.3
- 9xxx for more chips - 9xxx for more chips
- user presets
# to-do long term # 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. - 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. - 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. - 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. - **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. - **VSync**: synchronizes rendering to VBlank and eliminates tearing.
- **Frame rate limit**: allows you to set a frame rate limit (in frames per second). - **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. 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 ## 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. - **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! - 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+ // portions apparently taken from FamiTracker source, which is under GPLv2+
// TODO: // TODO:
// - audit for CVEs
// - format code? // - format code?
#include "fileOpsCommon.h" #include "fileOpsCommon.h"
@ -258,6 +257,7 @@ const int eff_conversion_050[][2] = {
}; };
constexpr int ftEffectMapSize = sizeof(ftEffectMap) / sizeof(int); 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 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}; 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]; bool hasSequence[256][8];
unsigned char sequenceIndex[256][8]; unsigned char sequenceIndex[256][8];
unsigned char macro_types[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 char map_channels[DIV_MAX_CHANS];
unsigned int hilightA = 4; unsigned int hilightA = 4;
unsigned int hilightB = 16; 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)); memset(map_channels, 0xfe, DIV_MAX_CHANS * sizeof(unsigned char));
for (int i = 0; i < 256; i++) { for (int i = 0; i < 256; i++) {
std::vector<DivInstrumentMacro> mac;
for (int j = 0; j < 8; j++) { 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)) { 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; return false;
} }
for (DivSubSong* i : ds.subsong) { for (DivSubSong* i: ds.subsong) {
i->clearData(); i->clearData();
delete i; delete i;
} }
@ -501,13 +498,30 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
} }
// not the end // 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); blockName = reader.readString(16);
unsigned int blockVersion = (unsigned int)reader.readI(); unsigned int blockVersion = (unsigned int)reader.readI();
unsigned int blockSize = (unsigned int)reader.readI(); unsigned int blockSize = (unsigned int)reader.readI();
size_t blockStart = reader.tell(); size_t blockStart = reader.tell();
logD("reading block %s (version %d, %d bytes, position %x)", blockName, blockVersion, blockSize, 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") { if (blockName == "PARAMS") {
// versions 7-9 don't change anything? // versions 7-9 don't change anything?
CHECK_BLOCK_VERSION(9); 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(); 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) { if (tchans == 5) {
expansions = 0; // This is strange. Sometimes expansion chip is set to 0xFF in files 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) { if (blockVersion >= 7) {
// advanced Hz control // advanced Hz control
int controlType = reader.readI(); int controlType = reader.readI();
switch (controlType) { int readHz=reader.readI();
if (readHz<=0) {
customHz=60.0;
} else switch (controlType) {
case 1: case 1:
customHz = 1000000.0 / (double)reader.readI(); customHz = 1000000.0 / (double)readHz;
break; break;
default: default:
reader.readI(); logW("unsupported tick rate control type %d",controlType);
break; break;
} }
} else { } else {
@ -553,6 +577,10 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
customHz = reader.readI(); customHz = reader.readI();
} }
logV("before clamp: %f",customHz);
if (customHz>1000.0) customHz=1000.0;
unsigned int newVibrato = 0; unsigned int newVibrato = 0;
bool sweepReset = false; bool sweepReset = false;
unsigned int speedSplitPoint = 0; 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 if ((expansions & 16) && blockVersion >= 5) { // N163 channels
n163Chans = reader.readI(); 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) { if (blockVersion >= 6) {
speedSplitPoint = reader.readI(); speedSplitPoint = reader.readI();
@ -779,6 +813,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
} }
} }
if (calcChans != tchans) { 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! 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); 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) { if (tchans > DIV_MAX_CHANS) {
tchans = DIV_MAX_CHANS; logE("too many channels!");
logW("too many channels!"); lastError = "too many channels";
delete[] file;
return false;
} }
if (blockVersion == 9 && blockSize - (reader.tell() - blockStart) == 2) // weird 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") { } else if (blockName == "INFO") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
ds.name = reader.readString(32); 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++) { for (int j = 0; j <= totalSongs; j++) {
unsigned char effectCols = reader.readC(); 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) { if (map_channels[i] == 0xfe) {
ds.subsong[j]->pat[i].effectCols = 1; ds.subsong[j]->pat[i].effectCols = 1;
logV("- song %d has %d effect columns", j, effectCols); 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") { } else if (blockName == "INSTRUMENTS") {
CHECK_BLOCK_VERSION(9); CHECK_BLOCK_VERSION(9);
// reader.seek(blockSize,SEEK_CUR);
ds.insLen = reader.readI(); ds.insLen = reader.readI();
if (ds.insLen < 0 || ds.insLen > 256) { if (ds.insLen < 0 || ds.insLen > 256) {
logE("too many instruments/out of range!"); 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++) { for (int i = 0; i < ds.insLen; i++) {
unsigned int insIndex = reader.readI(); unsigned int insIndex = reader.readI();
if (insIndex >= ds.ins.size()) { if (insIndex >= ds.ins.size()) {
// logE("instrument index %d is out of range!",insIndex); logE("instrument index %d is out of range!",insIndex);
// lastError="instrument index out of range"; lastError="instrument index out of range";
// delete[] file; delete[] file;
// return false; return false;
} }
DivInstrument* ins = ds.ins[insIndex]; 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) { if (blockVersion >= 7) {
note = reader.readC(); 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; ins->amiga.noteMap[note].map = (short)((unsigned char)reader.readC()) - 1;
unsigned char freq = reader.readC(); unsigned char freq = reader.readC();
ins->amiga.noteMap[note].dpcmFreq = (freq & 15); // 0-15 = 0-15 unlooped, 128-143 = 0-15 looped 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: { case DIV_INS_OPLL: {
ins->fm.opllPreset = (unsigned int)reader.readI(); 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++) { for (int i = 0; i < 8; i++) {
custom_patch[i] = reader.readC(); 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++) { for (int j = 0; j < 64; j++) {
wave->data[j] = reader.readC(); 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++) { for (int j = 0; j < 32; j++) {
ins->fds.modTable[j] = reader.readC() - 3; ins->fds.modTable[j] = reader.readC() - 3;
} }
ins->fds.modSpeed = reader.readI(); ins->fds.modSpeed = reader.readI();
ins->fds.modDepth = reader.readI(); ins->fds.modDepth = reader.readI();
reader.readI(); // this is delay. currently ignored. TODO. reader.readI(); // this is delay. currently ignored. TODO.
ds.wave.push_back(wave); if (ds.wave.size()>=256) {
ds.waveLen++; 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 a = reader.readI();
unsigned int b = 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) { if (a < 256 && (b & 0xFF) != 0x00) {
// don't look at me like this. I don't know why this should be like this either! // 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 { } else {
ins->std.volMacro.len = reader.readC(); ins->std.volMacro.len = reader.readC();
ins->std.volMacro.loop = reader.readI(); 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) { if (blockVersion >= 8) {
unsigned int autopos = reader.readI(); unsigned int autopos = reader.readI();
(void)autopos; logV("autopos: %d",autopos);
} }
unsigned int wave_count = reader.readI(); unsigned int wave_count = reader.readI();
size_t waveOff = ds.wave.size(); 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++) { for (unsigned int ii = 0; ii < wave_count; ii++) {
DivWavetable* wave = new DivWavetable(); DivWavetable* wave = new DivWavetable();
wave->len = wave_size; 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) { if (ds.wave.size()<256) {
ds.wave.push_back(wave); ds.wave.push_back(wave);
} else { } else {
logW("too many waves...");
delete wave; delete wave;
} }
} }
@ -1296,16 +1365,30 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
} }
if (instVersion == 2) { 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++) /*for(int tti = 0; tti < 20; tti++)
{ {
char aaaa = reader.readC(); char aaaa = reader.readC();
logV("\'%c\'", aaaa); logV("\'%c\'", aaaa);
}*/ }*/
} else { } 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; break;
@ -1325,7 +1408,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
} }
ds.insLen = 128; ds.insLen = 128;
} else if (blockName == "SEQUENCES") { } else if (blockName == "SEQUENCES") {
CHECK_BLOCK_VERSION(6); 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 index = reader.readI();
unsigned int type = reader.readI(); unsigned int type = reader.readI();
unsigned char size = reader.readC(); 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; macros[index][type].len = size;
for (int j = 0; j < size; j++) { 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* Indices = new unsigned char[128 * 5];
unsigned char* Types = 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++) { for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI(); 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; Indices[i] = index;
unsigned int type = reader.readI(); 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; 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 char size = reader.readC();
unsigned int setting = 0; 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]; DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) { if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) {
copyMacro(ins, &macros[index][type], Types[i], setting); 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; macro_types[k][j] = setting;
copyMacro(ins, &macros[sequenceIndex[k][j]][j], 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 release = reader.readI();
unsigned int setting = 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++) { for (int k = 0; k < (int)ds.ins.size(); k++) {
DivInstrument* ins = ds.ins[k]; DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_NES && hasSequence[k][Types[i]]) { 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; macro_types[k][Types[i]] = setting;
copyMacro(ins, &macros[sequenceIndex[k][Types[i]]][Types[i]], 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") { } else if (blockName == "GROOVES") {
CHECK_BLOCK_VERSION(6); CHECK_BLOCK_VERSION(6);
// reader.seek(blockSize,SEEK_CUR);
unsigned char num_grooves = reader.readC(); unsigned char num_grooves = reader.readC();
int max_groove = 0; int max_groove = 0;
for (int i = 0; i < 0xff; i++) { for (int i = 0; i < 256; i++) {
ds.grooves.push_back(DivGroovePattern()); 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 index = reader.readC();
unsigned char size = reader.readC(); unsigned char size = reader.readC();
if (index > max_groove) if (index > max_groove) {
max_groove = index + 1; max_groove = index + 1;
}
DivGroovePattern gp; DivGroovePattern gp;
gp.len = size; gp.len = size;
for (int sz = 0; sz < size; sz++) { for (int sz = 0; sz < size; sz++) {
unsigned char value = reader.readC(); unsigned char value = reader.readC();
gp.val[sz] = value; if (sz<16) {
gp.val[sz] = value;
}
} }
ds.grooves[index] = gp; 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) { if ((reader.tell() - blockStart) != blockSize) {
logE("block %s size does not match! block size %d curr pos %d", blockName, blockSize, reader.tell() - blockStart); logE("block %s size does not match! block size %d curr pos %d", blockName, blockSize, reader.tell() - blockStart);
} }
} else if (blockName == "FRAMES") { } else if (blockName == "FRAMES") {
CHECK_BLOCK_VERSION(3); CHECK_BLOCK_VERSION(3);
for (size_t i = 0; i < ds.subsong.size(); i++) { for (size_t i = 0; i < ds.subsong.size(); i++) {
DivSubSong* s = ds.subsong[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) { if (blockVersion >= 3) {
s->speeds.val[0] = reader.readI(); 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->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; int why = tchans;
if (blockVersion == 1) { if (blockVersion == 1) {
why = reader.readI(); 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); logV("reading %d and %d orders", tchans, s->ordersLen);
for (int j = 0; j < s->ordersLen; j++) { for (int j = 0; j < s->ordersLen; j++) {
for (int k = 0; k < why; k++) { for (int k = 0; k < why; k++) {
unsigned char o = reader.readC(); unsigned char o = reader.readC();
// logV("%.2x",o); if (map_channels[k]>=DIV_MAX_CHANS) continue;
s->orders.ord[map_channels[k]][j] = o; 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) { if (blockVersion == 1) {
int patLenOld = reader.readI(); 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) { for (DivSubSong* i : ds.subsong) {
i->patLen = patLenOld; 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("patNum: %d",patNum);
logV("rows: %d",numRows); 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); DivPattern* pat = ds.subsong[subs]->pat[map_channels[ch]].getPattern(patNum, true);
for (int i = 0; i < numRows; i++) { for (int i = 0; i < numRows; i++) {
unsigned int row = 0; 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(); 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 nextNote = reader.readC();
unsigned char nextOctave = 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(); unsigned char nextIns = reader.readC();
// TODO: you sure about 0xff?
if (map_channels[ch] != 0xff) { if (map_channels[ch] != 0xff) {
if (nextIns < 0x40 && nextNote != 0x0d && nextNote != 0x0e) { if (nextIns < 0x40 && nextNote != 0x0d && nextNote != 0x0e) {
pat->data[row][2] = nextIns; 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; pat->data[row][3] = nextVol;
if (map_channels[ch] == vrc6_saw_chan) // scale volume 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; 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; effectCols = 1;
} }
logV("effectCols: %d",effectCols);
unsigned char nextEffectVal = 0; unsigned char nextEffectVal = 0;
unsigned char nextEffect = 0; unsigned char nextEffect = 0;
//logV("row %d effects are read at %x",row,reader.tell());
for (int j = 0; j < effectCols; j++) { for (int j = 0; j < effectCols; j++) {
nextEffect = reader.readC(); 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 (map_channels[ch] != 0xff) {
if (nextEffect == 0 && nextEffectVal == 0) { if (nextEffect == 0 && nextEffectVal == 0) {
pat->data[row][4 + (j * 2)] = -1; pat->data[row][4 + (j * 2)] = -1;
pat->data[row][5 + (j * 2)] = -1; pat->data[row][5 + (j * 2)] = -1;
} else { } else {
if (nextEffect < ftEffectMapSize) { if ((eft && nextEffect<eftEffectMapSize) || (!eft && nextEffect<ftEffectMapSize)) {
if (eft) { if (eft) {
pat->data[row][4 + (j * 2)] = eftEffectMap[nextEffect]; pat->data[row][4 + (j * 2)] = eftEffectMap[nextEffect];
pat->data[row][5 + (j * 2)] = eftEffectMap[nextEffect] == -1 ? -1 : nextEffectVal; 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") { } else if (blockName == "DPCM SAMPLES") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
unsigned char num_samples = reader.readC(); unsigned char num_samples = reader.readC();
for (int i = 0; i < 256; i++) { 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(); 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); true_size = sample_len + ((1 - (int)sample_len) & 0x0f);
sample->lengthDPCM = true_size; sample->init(true_size * 8);
sample->samples = true_size * 8;
sample->dataDPCM = new unsigned char[true_size];
memset(sample->dataDPCM, 0xAA, true_size); memset(sample->dataDPCM, 0xAA, true_size);
reader.read(sample->dataDPCM, sample_len); 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--) { for (int i = 255; i > 0; i--) {
DivSample* s = ds.sample[i]; DivSample* s = ds.sample[i];
if (s->dataDPCM) { if (s->samples>0) {
last_non_empty_sample = i; last_non_empty_sample = i;
break; break;
} }
@ -1835,21 +2004,42 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
} }
ds.sampleLen = ds.sample.size(); ds.sampleLen = ds.sample.size();
} else if (blockName == "SEQUENCES_VRC6") { } else if (blockName == "SEQUENCES_VRC6") {
CHECK_BLOCK_VERSION(6); CHECK_BLOCK_VERSION(6);
unsigned char* Indices = new unsigned char[128 * 5]; unsigned char* Indices = new unsigned char[128 * 5];
unsigned char* Types = 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(); unsigned int seq_count = reader.readI();
for (unsigned int i = 0; i < seq_count; i++) { for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI(); 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; Indices[i] = index;
unsigned int type = reader.readI(); 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; 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 char size = reader.readC();
unsigned int setting = 0; 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 release = reader.readI();
unsigned int setting = 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++) { for (int k = 0; k < (int)ds.ins.size(); k++) {
DivInstrument* ins = ds.ins[k]; DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_VRC6 && hasSequence[k][Types[i]]) { 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; delete[] Types;
} else if (blockName == "SEQUENCES_N163" || blockName == "SEQUENCES_N106") { } else if (blockName == "SEQUENCES_N163" || blockName == "SEQUENCES_N106") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
unsigned char* Indices = new unsigned char[128 * 5]; unsigned char* Indices = new unsigned char[128 * 5];
unsigned char* Types = 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(); unsigned int seq_count = reader.readI();
for (unsigned int i = 0; i < seq_count; i++) { for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI(); 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; Indices[i] = index;
unsigned int type = reader.readI(); 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; 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 char size = reader.readC();
unsigned int setting = 0; 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]; DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_N163 && hasSequence[k][Types[i]]) { if (sequenceIndex[k][Types[i]] == Indices[i] && ins->type == DIV_INS_N163 && hasSequence[k][Types[i]]) {
copyMacro(ins, &macros[index][type], type, setting); 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") { } else if (blockName == "SEQUENCES_S5B") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
unsigned char* Indices = new unsigned char[128 * 5]; unsigned char* Indices = new unsigned char[128 * 5];
unsigned char* Types = 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(); unsigned int seq_count = reader.readI();
for (unsigned int i = 0; i < seq_count; i++) { for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI(); 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; Indices[i] = index;
unsigned int type = reader.readI(); 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; 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 char size = reader.readC();
unsigned int setting = 0; 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]; DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_AY && hasSequence[k][type]) { if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_AY && hasSequence[k][type]) {
copyMacro(ins, &macros[index][type], type, setting); 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* Indices = new unsigned char[128 * 5];
unsigned char* Types = 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(); unsigned int seq_count = reader.readI();
for (unsigned int i = 0; i < seq_count; i++) { for (unsigned int i = 0; i < seq_count; i++) {
unsigned int index = reader.readI(); 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; Indices[i] = index;
unsigned int type = reader.readI(); 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; 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 char size = reader.readC();
unsigned int setting = 0; 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]; DivInstrument* ins = ds.ins[k];
if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_C64 && hasSequence[k][type]) { if (sequenceIndex[k][type] == Indices[i] && ins->type == DIV_INS_C64 && hasSequence[k][type]) {
copyMacro(ins, &macros[index][type], type, setting); 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; delete[] Types;
} else if (blockName == "JSON") { } else if (blockName == "JSON") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
logW("block JSON not supported...");
reader.seek(blockSize, SEEK_CUR); reader.seek(blockSize, SEEK_CUR);
} else if (blockName == "PARAMS_EMU") { } else if (blockName == "PARAMS_EMU") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
logW("block PARAMS_EMU not supported...");
reader.seek(blockSize, SEEK_CUR); reader.seek(blockSize, SEEK_CUR);
} else if (blockName == "DETUNETABLES") { } else if (blockName == "DETUNETABLES") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
logW("block DETUNETABLES not supported...");
reader.seek(blockSize, SEEK_CUR); reader.seek(blockSize, SEEK_CUR);
} else if (blockName == "COMMENTS") { } else if (blockName == "COMMENTS") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
unsigned int display_comment = reader.readI(); 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(); ch = reader.readC();
String sss = String() + ch; if (ch==0) break;
ds.subsong[0]->notes += sss; ds.subsong[0]->notes += ch;
} while (ch != 0); }
// ds.subsong[0]->notes = reader.readS();
} else if (blockName == "PARAMS_EXTRA") { } else if (blockName == "PARAMS_EXTRA") {
CHECK_BLOCK_VERSION(3); CHECK_BLOCK_VERSION(3);
// reader.seek(blockSize,SEEK_CUR);
unsigned int linear_pitch = reader.readI(); unsigned int linear_pitch = reader.readI();
ds.linearPitch = linear_pitch == 0 ? 0 : 2; 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) { if (blockVersion >= 3) {
unsigned char flats = reader.readC(); unsigned char flats = reader.readC();
(void)flats; logV("flats: %d",(int)flats);
} }
} else if (blockName == "TUNING") { } else if (blockName == "TUNING") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
// reader.seek(blockSize,SEEK_CUR);
if (blockVersion == 1) { if (blockVersion == 1) {
int fineTuneCents = reader.readC() * 100; int fineTuneCents = reader.readC() * 100;
fineTuneCents += reader.readC(); 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") { } else if (blockName == "BOOKMARKS") {
CHECK_BLOCK_VERSION(1); CHECK_BLOCK_VERSION(1);
logW("block BOOKMARKS not supported...");
reader.seek(blockSize, SEEK_CUR); reader.seek(blockSize, SEEK_CUR);
} else { } else {
logE("block %s is unknown!", blockName); 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) if (index < 0)
index = 0; index = 0;
if (index>=(int)ds.ins.size()) continue;
DivInstrument* ins = ds.ins[index]; DivInstrument* ins = ds.ins[index];
if (ins->type == DIV_INS_FM) { if (ins->type == DIV_INS_FM) {

View file

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

View file

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

View file

@ -1303,7 +1303,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef( sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef(
"Konami VRC7", NULL, 0x9d, 0, 6, true, false, 0x151, false, 0, 0, 0, "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"}, {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "F3", "F4", "F5", "F6"}, {"F1", "F2", "F3", "F4", "F5", "F6"},
{DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM, DIV_CH_FM}, {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", "Aburtos",
"ActualNK358", "ActualNK358",
"akumanatt", "akumanatt",
"aloelucidity",
"AmigaX", "AmigaX",
"AquaDoesStuff", "AquaDoesStuff",
"AURORA*FIELDS", "AURORA*FIELDS",

View file

@ -4391,7 +4391,7 @@ bool FurnaceGUI::loop() {
toggleMobileUI(!mobileUI); toggleMobileUI(!mobileUI);
} }
#endif #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; userPresetsOpen=true;
} }
if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) {

View file

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

View file

@ -7469,7 +7469,7 @@ void FurnaceGUI::drawInsEdit() {
memset(oldData,0,256*sizeof(int)); memset(oldData,0,256*sizeof(int));
memcpy(oldData,lastMacroDesc.macro->val,lastMacroDesc.macro->len*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++) { for (int i=0; i<lastMacroDesc.macro->len; i++) {
int val=0; int val=0;

View file

@ -3102,11 +3102,9 @@ void FurnaceGUI::initSystemPresets() {
CATEGORY_END; CATEGORY_END;
} }
FurnaceGUISysDef::FurnaceGUISysDef(const char* n, std::initializer_list<FurnaceGUISysDefChip> def, const char* e): void FurnaceGUISysDef::bake() {
name(n),
extra((e==NULL)?"":e) {
orig=def;
int index=0; int index=0;
definition="";
for (FurnaceGUISysDefChip& i: orig) { for (FurnaceGUISysDefChip& i: orig) {
definition+=fmt::sprintf( definition+=fmt::sprintf(
"id%d=%d\nvol%d=%f\npan%d=%f\nflags%d=%s\n", "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): FurnaceGUISysDef::FurnaceGUISysDef(const char* n, const char* def, DivEngine* e):
name(n), name(n),
definition(def) { definition(taDecodeBase64(def)) {
// extract definition // extract definition
DivConfig conf; DivConfig conf;
conf.loadFromBase64(def); conf.loadFromMemory(definition.c_str());
for (int i=0; i<DIV_MAX_CHIPS; i++) { for (int i=0; i<DIV_MAX_CHIPS; i++) {
String nextStr=fmt::sprintf("id%d",i); String nextStr=fmt::sprintf("id%d",i);
int id=conf.getInt(nextStr.c_str(),0); 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.centerPopup=conf.getInt("centerPopup",1);
settings.vibrationStrength=conf.getFloat("vibrationStrength",0.5f); settings.vibrationStrength=conf.getFloat("vibrationStrength",0.5f);
settings.vibrationLength=conf.getInt("vibrationLength",100); settings.vibrationLength=conf.getInt("vibrationLength",20);
} }
if (groups&GUI_SETTINGS_AUDIO) { 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; if (sampRate>65536) sampRate=65536;
altered=true; altered=true;
} rightClickable } rightClickable
DivPlatformGBAMinMod* dispatch=(DivPlatformGBAMinMod*)e->getDispatch(chan); if (chan>=0) {
float maxCPU=dispatch->maxCPU*100; DivPlatformGBAMinMod* dispatch=(DivPlatformGBAMinMod*)e->getDispatch(chan);
ImGui::Text("Actual sample rate: %d Hz", dispatch->chipClock); if (dispatch!=NULL) {
if (maxCPU>90) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]); float maxCPU=dispatch->maxCPU*100;
ImGui::Text("Max mixer CPU usage: %.0f%%", maxCPU); ImGui::Text("Actual sample rate: %d Hz", dispatch->chipClock);
if (maxCPU>90) ImGui::PopStyleColor(); if (maxCPU>90) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]);
FurnaceGUI::popWarningColor(); ImGui::Text("Max mixer CPU usage: %.0f%%",maxCPU);
if (maxCPU>90) ImGui::PopStyleColor();
FurnaceGUI::popWarningColor();
}
}
if (altered) { if (altered) {
e->lockSave([&]() { e->lockSave([&]() {
flags.set("volScale",volScale); flags.set("volScale",volScale);

View file

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

View file

@ -177,7 +177,7 @@ bool FurnaceGUI::loadUserPresets(bool redundancy) {
} }
indent>>=1; indent>>=1;
if (!key.empty() && !value.empty()) { if (!key.empty()) {
std::vector<FurnaceGUISysDef>* where=digDeep(userCategory->systems,indent); std::vector<FurnaceGUISysDef>* where=digDeep(userCategory->systems,indent);
where->push_back(FurnaceGUISysDef(key.c_str(),value.c_str(),e)); 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++) { for (int i=0; i<depth; i++) {
data+=" "; 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); fputs(data.c_str(),f);
writeSubEntries(f,i.subDefs,depth+1); writeSubEntries(f,i.subDefs,depth+1);
@ -322,7 +322,7 @@ void FurnaceGUI::drawUserPresets() {
nextWindow=GUI_WINDOW_NOTHING; nextWindow=GUI_WINDOW_NOTHING;
} }
if (!userPresetsOpen) return; if (!userPresetsOpen) return;
if (ImGui::Begin("User Presets",&userPresetsOpen,globalWinFlags)) { if (ImGui::Begin("User Systems",&userPresetsOpen,globalWinFlags)) {
FurnaceGUISysCategory* userCategory=NULL; FurnaceGUISysCategory* userCategory=NULL;
for (FurnaceGUISysCategory& i: sysCategories) { for (FurnaceGUISysCategory& i: sysCategories) {
if (strcmp(i.name,"User")==0) { if (strcmp(i.name,"User")==0) {
@ -335,34 +335,173 @@ void FurnaceGUI::drawUserPresets() {
if (userCategory==NULL) { if (userCategory==NULL) {
ImGui::Text("Error! User category does not exist!"); 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 // preset list
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if (ImGui::Button(ICON_FA_PLUS "##AddPreset")) { if (ImGui::BeginChild("UList",ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()))) {
userCategory->systems.push_back(FurnaceGUISysDef("New Preset",{})); ImGui::AlignTextToFramePadding();
selectedUserPreset.clear(); ImGui::Text("Systems");
selectedUserPreset.push_back(userCategory->systems.size()-1); 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 // editor
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if (selectedUserPreset.empty()) { if (ImGui::BeginChild("UEdit",ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()))) {
ImGui::Text("select a preset"); if (selectedUserPreset.empty()) {
} else { ImGui::Text("select a preset");
FurnaceGUISysDef* preset=selectPreset(userCategory->systems); } else {
FurnaceGUISysDef* preset=selectPreset(userCategory->systems);
bool doRemovePreset=false;
if (preset!=NULL) { if (preset!=NULL) {
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::Text("Name"); ImGui::Text("Name");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); 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::InputText("##PName",&preset->name);
ImGui::Separator(); ImGui::SameLine();
ImGui::Text("the rest..."); 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(); ImGui::EndTable();
} }