mirror of
https://github.com/tildearrow/furnace.git
synced 2024-11-25 22:15:14 +00:00
Merge branch 'master' into metal
This commit is contained in:
commit
e6bead147a
22 changed files with 636 additions and 175 deletions
48
.github/issue_template.md
vendored
48
.github/issue_template.md
vendored
|
@ -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
1
.github/issue_template/config.yml
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
blank_issues_enabled: false
|
33
.github/issue_template/issue.yml
vendored
Normal file
33
.github/issue_template/issue.yml
vendored
Normal 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
|
1
TODO.md
1
TODO.md
|
@ -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
BIN
demos/nes/infinity.fur
Normal file
Binary file not shown.
|
@ -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
62
doc/7-systems/5e01.md
Normal 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.
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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, ¯os[index][type], Types[i], setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[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, ¯os[sequenceIndex[k][j]][j], j, setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)j, true), ¯os[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, ¯os[sequenceIndex[k][Types[i]]][Types[i]], Types[i], setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[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, ¯os[index][type], type, setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[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, ¯os[index][type], type, setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[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, ¯os[index][type], type, setting);
|
||||
// memcpy(ins->std.get_macro(DIV_MACRO_VOL + (DivMacroType)Types[i], true), ¯os[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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -71,6 +71,7 @@ const char* aboutLine[]={
|
|||
"Aburtos",
|
||||
"ActualNK358",
|
||||
"akumanatt",
|
||||
"aloelucidity",
|
||||
"AmigaX",
|
||||
"AquaDoesStuff",
|
||||
"AURORA*FIELDS",
|
||||
|
|
|
@ -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))) {
|
||||
|
|
|
@ -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(""),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -453,7 +453,7 @@ struct FurnaceCV {
|
|||
newHiScore(false),
|
||||
playSongs(true),
|
||||
pleaseInitSongs(false),
|
||||
lives(3),
|
||||
lives(5),
|
||||
respawnTime(0),
|
||||
stage(0),
|
||||
shotType(0),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue