Merge remote-tracking branch 'upstream/master' into multiKeybind

This commit is contained in:
Adam Lederer 2024-09-02 18:33:17 -07:00
commit e240c9996f
105 changed files with 73916 additions and 68420 deletions

View file

@ -231,7 +231,9 @@ jobs:
cp -vr ../po/locale locale
cp -vr ../papers ../${binPath}/furnace.exe ./
if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then
cp -v ../${binPath}/furnace.pdb ./
if [ -e ../${binPath}/furnace.pdb ]; then
cp -v ../${binPath}/furnace.pdb ./
fi
fi
sha256sum ../${binPath}/furnace.exe > checksum.txt

View file

@ -16,7 +16,7 @@ set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_PROJECT_VERSION_MAJOR 0)
set(CMAKE_PROJECT_VERSION_MINOR 6)
set(CMAKE_PROJECT_VERSION_PATCH 5)
set(CMAKE_PROJECT_VERSION_PATCH 6)
set(BUILD_GUI_DEFAULT ON)
set(USE_SDL2_DEFAULT ON)

View file

@ -15,8 +15,8 @@ android {
}
minSdkVersion 21
targetSdkVersion 26
versionCode 214
versionName "0.6.5"
versionCode 219
versionName "0.6.7"
externalNativeBuild {
cmake {
arguments "-DANDROID_APP_PLATFORM=android-21", "-DANDROID_STL=c++_static", "-DWARNINGS_ARE_ERRORS=ON", "-DWITH_LOCALE=ON", "-DUSE_MOMO=ON"

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.tildearrow.furnace"
android:versionCode="214"
android:versionName="0.6.5"
android:versionCode="219"
android:versionName="0.6.7"
android:installLocation="auto">
<!-- OpenGL ES 2.0 -->

View file

@ -12,6 +12,8 @@
**asset**: an instrument, wavetable or sample.
**bit**: a single binary on-off value.
**bitbang**: to achieve PCM sound by sending a rapid stream of volume commands to a non-PCM channel.
**bitmask**: a set of bits which represent individual single-bit toggles or groups representing small numbers. these are explained fully in the [hexadecimal primer](hex.md).
@ -72,6 +74,8 @@
**LFO**: low frequency oscillator. a wave with a slow period (often below hearing range) used to alter other sounds.
**LFSR**: linear-feedback shift register. a method to generate pseudo-random noise that loops, also known as "periodic noise". within a sequence of on-off bits, it does math to combine the bits at specified locations called "taps", then shifts the whole sequence and adds the resulting bit on the end, guaranteeing a different state for the next pass. depending on the locations of the taps, different lengths of noise loops are generated; for short loops, this will affect their tone.
**macro**: a sequence of values automatically applied while a note plays.
**noise bass**: the technique of using a PSG's periodic noise generator with a very short period to create low-frequency sounds.
@ -86,7 +90,7 @@
**period**: the length of a repeating waveform. as frequency rises, the period shortens.
**periodic noise**: an approximation of random noise generated algorithmically.
**periodic noise**: an approximation of random noise generated algorithmically with an LFSR.
- the period is the number of values generated until the algorithm repeats itself.
**phase reset**: to restart a waveform at its initial value.
@ -128,6 +132,8 @@
**supersaw**: a sound made up of multiple saw waves at slightly different frequencies to achieve a chorusing effect.
**tap**: a specified bit location within an LFSR.
**tick rate**: the number of times per second that the sound engine moves forward. all notes and effects are quantized to this rate.
- this usually corresponds to the frame rate the system uses for video, approximately 60 for NTSC and 50 for PAL.

View file

@ -13,10 +13,12 @@ buttons from left to right:
- right-clicking always brings up the menu.
- **Duplicate**: duplicates the currently selected instrument.
- **Open**: brings up a file dialog to load a file as a new instrument at the end of the list.
- if the file is an instrument bank, a dialog will appear to select which instruments to load.
- **Save**: brings up a file dialog to save the currently selected instrument.
- instruments are saved as Furnace instrument (.fui) files.
- right-clicking brings up a menu with the following options:
- **save instrument as .dmp...**: saves the selected instrument in DefleMask format.
- **save all instruments...**: saves all instruments to the selected folder as .fui files.
- **Toggle folders/standard view**: enables (and disables) folder view, explained below.
- **Move up**: moves the currently selected instrument up in the list. pattern data will automatically be adjusted to match.
- **Move down**: same, but downward.
@ -41,9 +43,10 @@ everything from the instrument list applies here also, with one major difference
wavetables are saved as Furnace wavetable (.fuw) files.
- right-clicking the Save button brings up a menu with the following options:
- **save wavetable as .dmw...**: saves the selected wavetable in DefleMask format.
- **save raw wavetable...**: saves the selected wavetable as raw data.
right-clicking the Save button brings up a menu with the following options:
- **save wavetable as .dmw...**: saves the selected wavetable in DefleMask format.
- **save raw wavetable...**: saves the selected wavetable as raw data.
- **save all wavetables...**: saves all wavetables to the selected folder as .fuw files.
## sample list
@ -57,6 +60,7 @@ samples are saved as standard wave (.wav) files.
right-clicking the Save button brings up a menu with the following options:
- **save raw sample...**: saves the selected sample as raw data.
- **save all samples...**: saves all samples to the selected folder as .wav files.
right-clicking a sample in the list brings up a menu:
- **make instrument**: creates a new instrument which is set to use the selected sample.

View file

@ -68,6 +68,13 @@ the following settings are available:
- **loop**: enables loop. if disabled, the song won't loop.
- **optimize size**: removes unnecessary commands to reduce size.
## ROM
depending on the system, this option may appear to allow you to export your song to a working ROM image or code that can be built into one. export options are explained in the system's accompanying documentation.
currently, only one system can be exported this way:
- [Atari 2600 (TIunA)](../7-systems/tia.md)
## text
this option allows you to export your song as a text file.

View file

@ -152,6 +152,7 @@ the keys in the "Global hotkeys" section can be used in any window, although not
| Expand song | — |
| Set note input latch | — |
| Clear note input latch | — |
| Absorb instrument/octave from status at cursor | — |
| | |
| **Instrument list** | |
| Add instrument | `Insert` |

View file

@ -93,6 +93,10 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
- **Export**: select an `.ini` file to save current settings.
- **Factory Reset**: resets all settings to default and purges settings backups.
### Import
- **Use OPL3 instead of OPL2 for S3M import**: changes which system is used for the import of S3M files that contain FM channels.
## Audio
### Output

View file

@ -15,9 +15,16 @@ however, effects are continuous (unless specified), which means you only need to
- `F8xx`: **Single tick volume up.** adds `x` to volume.
- `F9xx`: **Single tick volume down.** subtracts `x` from volume.
- ---
- `D3xx`: **Volume portamento.** slides volume toward the new value instead of jumping immediately. `x` is the speed of the slide.
- `D4xx`: **Volume portamento (fast).** same as `D3xx` but 256× faster.
- ---
- `07xy`: **Tremolo.** changes volume to be "wavy" with a sine LFO. `x` is the speed. `y` is the depth.
- tremolo is downward only.
- maximum tremolo depth is -60 volume steps.
- ---
- `D3xx`: **Volume portamento.** slides the volume to the one specified in the volume column. `x` is the slide speed.
- a volume _must_ be present with this effect for it to work.
- `D4xx`: **Volume portamento (fast).** like `D3xx` but four times faster.
## pitch

View file

@ -72,6 +72,10 @@ the following options are available in the Chip Manager window:
- DPCM: the default mode, playing 1-bit DPCM samples as supported by the hardware.
- PCM: this mode provides crispier samples by writing the delta counter directly. uses a lot of CPU time in console.
## DPCM sample loop
due to hardware limitations, a loop in a DPCM sample must start on a multiple of 512 samples (512, 1024, 1536...) and have a length that is a multiple of 128 plus 8 samples (136, 264, 392...)
## short noise frequencies (NTSC)
note | arpeggio | fundamental | MIDI note | pitch

View file

@ -27,13 +27,9 @@ Furnace isn't complete without this one...
- `E`: low pure buzzy
- `F`: low reedy
## ROM export
## info
this chip uses the [TIA](../4-instrument/tia.md) instrument editor.
the arp macro's fixed mode operates differently, writing the direct pitch to the chip. here's a list of pitches.
a song can be exported to assembly code for use with the TIunA software driver for the Atari 2600. see [TIunA on GitHub](https://github.com/AYCEdemo/twin-tiuna) for explanations of the export options.
## chip config
@ -47,6 +43,12 @@ the following options are available in the Chip Manager window:
- **Stereo**: output two channels on left and right.
- **PAL**: run slower blah blah blah
## info
this chip uses the [TIA](../4-instrument/tia.md) instrument editor.
the arp macro's fixed mode operates differently, writing the direct pitch to the chip. here's a list of pitches.
### shape 1
| pitch | NTSC | note | cent | PAL | note | cent

View file

@ -68,9 +68,13 @@ in most arcade boards the chip was used in combination with a PCM chip, like [Se
- `5Dxx`: **set D2R/SR of operator 2.**
- `5Exx`: **set D2R/SR of operator 3.**
- `5Fxx`: **set D2R/SR of operator 4.**
- `60xx`: **set operator mask.**
- `xx` goes from `0` to `F`. it is a bitfield.
- each bit corresponds to an operator.
- `60xy`: **set operator mask.**
- enables or disables operators.
- if `x` is `0`, `y` ranges from `0` to `F`. it is a bitfield, so `y` is the sum of the active operators' bits:
- OP1 is +1, OP2 is +2, OP3 is +4, and OP4 is +8.
- for example, having only OP2 and OP4 on would be 2 + 8 = 10, resulting in an `xy` value of `0A`.
- if `x` is `1` to `4`, the effect targets that operator; `y` turns it off with a value of `0` and on with a value of `1`.
- for example, the effect `6031` enables OP3.
## info

View file

@ -99,9 +99,13 @@ several variants of this chip were released as well, with more features.
- `5Dxx`: **set D2R/SR of operator 2.**
- `5Exx`: **set D2R/SR of operator 3.**
- `5Fxx`: **set D2R/SR of operator 4.**
- `60xx`: **set operator mask.**
- `xx` goes from `0` to `F`. it is a bitfield.
- each bit corresponds to an operator.
- `60xy`: **set operator mask.**
- enables or disables operators.
- if `x` is `0`, `y` ranges from `0` to `F`. it is a bitfield, so `y` is the sum of the active operators' bits:
- OP1 is +1, OP2 is +2, OP3 is +4, and OP4 is +8.
- for example, having only OP2 and OP4 on would be 2 + 8 = 10, resulting in an `xy` value of `0A`.
- if `x` is `1` to `4`, the effect targets that operator; `y` turns it off with a value of `0` and on with a value of `1`.
- for example, the effect `6031` enables OP3.
## extended channel 3

View file

@ -99,9 +99,13 @@ the YM2610 (OPNB) and YM2610B chips are very similar to this one, but the built-
- `5Dxx`: **set D2R/SR of operator 2.**
- `5Exx`: **set D2R/SR of operator 3.**
- `5Fxx`: **set D2R/SR of operator 4.**
- `60xx`: **set operator mask.**
- `xx` goes from `0` to `F`. it is a bitfield.
- each bit corresponds to an operator.
- `60xy`: **set operator mask.**
- enables or disables operators.
- if `x` is `0`, `y` ranges from `0` to `F`. it is a bitfield, so `y` is the sum of the active operators' bits:
- OP1 is +1, OP2 is +2, OP3 is +4, and OP4 is +8.
- for example, having only OP2 and OP4 on would be 2 + 8 = 10, resulting in an `xy` value of `0A`.
- if `x` is `1` to `4`, the effect targets that operator; `y` turns it off with a value of `0` and on with a value of `1`.
- for example, the effect `6031` enables OP3.
## extended channel 3

View file

@ -97,9 +97,13 @@ its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and [2 differen
- `5Dxx`: **set D2R/SR of operator 2.**
- `5Exx`: **set D2R/SR of operator 3.**
- `5Fxx`: **set D2R/SR of operator 4.**
- `60xx`: **set operator mask.**
- `xx` goes from `0` to `F`. it is a bitfield.
- each bit corresponds to an operator.
- `60xy`: **set operator mask.**
- enables or disables operators.
- if `x` is `0`, `y` ranges from `0` to `F`. it is a bitfield, so `y` is the sum of the active operators' bits:
- OP1 is +1, OP2 is +2, OP3 is +4, and OP4 is +8.
- for example, having only OP2 and OP4 on would be 2 + 8 = 10, resulting in an `xy` value of `0A`.
- if `x` is `1` to `4`, the effect targets that operator; `y` turns it off with a value of `0` and on with a value of `1`.
- for example, the effect `6031` enables OP3.
## extended channel 2

View file

@ -96,9 +96,13 @@ it is backward compatible with the original chip.
- `5Dxx`: **set D2R/SR of operator 2.**
- `5Exx`: **set D2R/SR of operator 3.**
- `5Fxx`: **set D2R/SR of operator 4.**
- `60xx`: **set operator mask.**
- `xx` goes from `0` to `F`. it is a bitfield.
- each bit corresponds to an operator.
- `60xy`: **set operator mask.**
- enables or disables operators.
- if `x` is `0`, `y` ranges from `0` to `F`. it is a bitfield, so `y` is the sum of the active operators' bits:
- OP1 is +1, OP2 is +2, OP3 is +4, and OP4 is +8.
- for example, having only OP2 and OP4 on would be 2 + 8 = 10, resulting in an `xy` value of `0A`.
- if `x` is `1` to `4`, the effect targets that operator; `y` turns it off with a value of `0` and on with a value of `1`.
- for example, the effect `6031` enables OP3.
## extended channel 3

View file

@ -82,9 +82,13 @@ thanks to the Z80 sound CPU, DualPCM can play two samples at once! this mode spl
- `5Dxx`: **set D2R/SR of operator 2.**
- `5Exx`: **set D2R/SR of operator 3.**
- `5Fxx`: **set D2R/SR of operator 4.**
- `60xx`: **set operator mask.**
- `xx` goes from `0` to `F`. it is a bitfield.
- each bit corresponds to an operator.
- `60xy`: **set operator mask.**
- enables or disables operators.
- if `x` is `0`, `y` ranges from `0` to `F`. it is a bitfield, so `y` is the sum of the active operators' bits:
- OP1 is +1, OP2 is +2, OP3 is +4, and OP4 is +8.
- for example, having only OP2 and OP4 on would be 2 + 8 = 10, resulting in an `xy` value of `0A`.
- if `x` is `1` to `4`, the effect targets that operator; `y` turns it off with a value of `0` and on with a value of `1`.
- for example, the effect `6031` enables OP3.
## info

View file

@ -12,6 +12,7 @@ chip configuration is exactly as in the [chip manager](chip-manager.md) window.
the **Advanced** field stores additional settings that are set when a new song is started. these are listed in "option=value" format, one per line.
- `tickRate`: sets tick rate.
- `chanMask`: sets which channels to hide. written as a comma-separated list of integers
**Save and Close**: as it says.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
instruments/OPL/marimba.fui Normal file

Binary file not shown.

BIN
instruments/OPL/sax.fui Normal file

Binary file not shown.

Binary file not shown.

View file

@ -6,7 +6,7 @@ when copying pattern data from Furnace, it's stored in the clipboard as plain te
org.tildearrow.furnace - Pattern Data (144)
```
this top line of text is always the same except for the number in parentheses, which is the internal build number. for example, 0.6.5 is `215`.
this top line of text is always the same except for the number in parentheses, which is the internal build number. for example, 0.6.7 is `219`.
the second line is a number between 0 and 18 (decimal) which indicates which column the clip starts from.
- `0`: note.

View file

@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are:
- 219: Furnace 0.6.7
- 218: Furnace 0.6.6
- 214: Furnace 0.6.5
- 212: Furnace 0.6.4
- 201: Furnace 0.6.3

6742
po/de.po

File diff suppressed because it is too large Load diff

6656
po/es.po

File diff suppressed because it is too large Load diff

6742
po/fi.po

File diff suppressed because it is too large Load diff

6742
po/fr.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

6742
po/hy.po

File diff suppressed because it is too large Load diff

6762
po/id.po

File diff suppressed because it is too large Load diff

6742
po/ja.po

File diff suppressed because it is too large Load diff

9190
po/ko.po

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

6753
po/nl.po

File diff suppressed because it is too large Load diff

6774
po/pl.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

11082
po/ru.po

File diff suppressed because it is too large Load diff

6768
po/sk.po

File diff suppressed because it is too large Load diff

6774
po/sv.po

File diff suppressed because it is too large Load diff

6774
po/th.po

File diff suppressed because it is too large Load diff

6742
po/tr.po

File diff suppressed because it is too large Load diff

6742
po/uk.po

File diff suppressed because it is too large Load diff

6748
po/zh.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -15,17 +15,17 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>0.6.5</string>
<string>0.6.7</string>
<key>CFBundleName</key>
<string>Furnace</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>0.6.5</string>
<string>0.6.7</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>0.6.5</string>
<string>0.6.7</string>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>NSHighResolutionCapable</key>

View file

@ -325,7 +325,7 @@ if __name__ == "__main__":
<h1>Furnace<br/>User Manual</h1>
</div>
<div>
<i>for version 0.6.5</i>
<i>for version 0.6.7</i>
</div>
</section>
<section id="authors">
@ -348,7 +348,7 @@ if __name__ == "__main__":
<p>this documentation is under the <a href="https://creativecommons.org/licenses/by/3.0/">Creative Commons Attribution 3.0 Unported</a> license.</p>
<p>you may reproduce, modify and/or distribute this documentation provided this copyright notice (including license and attribution) is present and any necessary disclaimers whether modifications have been made.</p>
<p>this documentation is provided as-is and without warranty of any kind.</p>
<p>this manual is written for version 0.6.5 of Furnace.<br/>it may not necessarily apply to previous or future versions.</p>
<p>this manual is written for version 0.6.7 of Furnace.<br/>it may not necessarily apply to previous or future versions.</p>
</section>
<section id="index">
%s

View file

@ -1,6 +1,6 @@
1 VERSIONINFO
FILEVERSION 0,6,5,0
PRODUCTVERSION 0,6,5,0
FILEVERSION 0,6,7,0
PRODUCTVERSION 0,6,7,0
{
BLOCK "VarFileInfo"
{
@ -33,10 +33,10 @@
"Furnace"
VALUE "ProductVersion",
"0.6.5"
"0.6.7"
VALUE "FileVersion",
"0.6.5"
"0.6.7"
VALUE "CompanyName",
"tildearrow"

View file

@ -16,7 +16,7 @@ if you find issues (e.g. bugs or annoyances), report them. links below.
- Furnace on GitHub (project page and issue tracker): https://github.com/tildearrow/furnace
- issues: https://github.com/tildearrow/furnace/issues
- discussion: https://github.com/tildearrow/furnace/discussions
- online manual: https://tildearrow.org/furnace/doc/v0.6.5/
- online manual: https://tildearrow.org/furnace/doc/v0.6.7/
# notes

View file

@ -26,7 +26,7 @@ if you find issues (e.g. bugs or annoyances), report them. links below.
- Furnace on GitHub (project page and issue tracker): https://github.com/tildearrow/furnace
- issues: https://github.com/tildearrow/furnace/issues
- discussion: https://github.com/tildearrow/furnace/discussions
- online manual: https://tildearrow.org/furnace/doc/v0.6.5/
- online manual: https://tildearrow.org/furnace/doc/v0.6.7/
# notes

View file

@ -16,7 +16,7 @@ if you find issues (e.g. bugs or annoyances), report them. links below.
- Furnace on GitHub (project page and issue tracker): https://github.com/tildearrow/furnace
- issues: https://github.com/tildearrow/furnace/issues
- discussion: https://github.com/tildearrow/furnace/discussions
- online manual: https://tildearrow.org/furnace/doc/v0.6.5/
- online manual: https://tildearrow.org/furnace/doc/v0.6.7/
# notes

View file

@ -1,6 +1,6 @@
#!/bin/bash
FUR_VERSION="0.6.5"
FUR_VERSION="0.6.7"
EXPORT_LANGS=("de" "es" "fr" "fi" "hy" "id" "ja" "ko" "nl" "pl" "pt_BR" "ru" "sk" "sv" "th" "tr" "uk" "zh" "zh_HK")

View file

@ -520,6 +520,15 @@ void DivEngine::initSongWithDesc(const char* description, bool inBase64, bool ol
if (song.subsong[0]->hz<1.0) song.subsong[0]->hz=1.0;
if (song.subsong[0]->hz>999.0) song.subsong[0]->hz=999.0;
curChanMask=c.getIntList("chanMask",{});
for (unsigned char i:curChanMask) {
int j=i-1;
if (j<0) j=0;
if (j>DIV_MAX_CHANS) j=DIV_MAX_CHANS-1;
curSubSong->chanShow[j]=false;
curSubSong->chanShowChanOsc[j]=false;
}
song.author=getConfString("defaultAuthorName","");
}
@ -754,6 +763,13 @@ int DivEngine::addSubSong() {
BUSY_BEGIN;
saveLock.lock();
song.subsong.push_back(new DivSubSong);
for (unsigned char i:curChanMask) {
int j=i-1;
if (j<0) j=0;
if (j>DIV_MAX_CHANS) j=DIV_MAX_CHANS-1;
song.subsong.back()->chanShow[j]=false;
song.subsong.back()->chanShowChanOsc[j]=false;
}
saveLock.unlock();
BUSY_END;
return song.subsong.size()-1;

View file

@ -54,8 +54,8 @@ class DivWorkPool;
#define DIV_UNSTABLE
#define DIV_VERSION "dev217"
#define DIV_ENGINE_VERSION 217
#define DIV_VERSION "dev220"
#define DIV_ENGINE_VERSION 220
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -518,6 +518,7 @@ class DivEngine {
std::vector<DivCommand> cmdStream;
std::vector<DivInstrumentType> possibleInsTypes;
std::vector<DivEffectContainer> effectInst;
std::vector<int> curChanMask;
static DivSysDef* sysDefs[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];

View file

@ -127,7 +127,6 @@ struct TiunaMatches {
static void writeCmd(std::vector<TiunaBytes>& cmds, TiunaCmd& cmd, unsigned char ch, int& lastWait, int fromTick, int toTick) {
while (fromTick<toTick) {
int val=MIN(toTick-fromTick,256);
assert(val>0);
if (lastWait!=val) {
cmd.wait=val;
lastWait=val;
@ -504,12 +503,6 @@ void DivExportTiuna::run() {
running=false;
return;
}
SafeWriter dbg;
dbg.init();
dbg.writeText(fmt::format("renderedCmds size={}\n",renderedCmds.size()));
for (const auto& i: confirmedMatches) {
dbg.writeText(fmt::format("pos={},end={},id={}\n",i.pos,i.endPos,i.id,i.size));
}
// write commands
int totalSize=0;

View file

@ -674,7 +674,10 @@ void DivExportZSM::run() {
if (writes.size()>0)
logD("zsmOps: Writing %d messages to chip %d",writes.size(),i);
for (DivRegWrite& write: writes) {
if (i==YM) zsm.writeYM(write.addr&0xff,write.val);
if (i==YM) {
if (done && write.addr==0x08 && (write.val&0x78)>0) continue; // don't process keydown on lookahead
zsm.writeYM(write.addr&0xff,write.val);
}
if (i==VERA) {
if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead
zsm.writePSG(write.addr&0xff,write.val);

View file

@ -2102,6 +2102,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len, int variantID) {
}
}
// SNES no anti-click
if (ds.version<220) {
for (int i=0; i<ds.systemLen; i++) {
if (ds.system[i]==DIV_SYSTEM_SNES) {
ds.systemFlags[i].set("antiClick",false);
}
}
}
if (active) quitDispatch();
BUSY_BEGIN_SOFT;
saveLock.lock();

View file

@ -762,7 +762,20 @@ int DivPlatformArcade::dispatch(DivCommand c) {
break;
}
case DIV_CMD_FM_OPMASK:
chan[c.chan].opMask=c.value&15;
switch (c.value>>4) {
case 1:
case 2:
case 3:
case 4:
chan[c.chan].opMask&=~(1<<((c.value>>4)-1));
if (c.value&15) {
chan[c.chan].opMask|=(1<<((c.value>>4)-1));
}
break;
default:
chan[c.chan].opMask=c.value&15;
break;
}
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}

View file

@ -113,14 +113,15 @@ const unsigned char dacLogTableAY[256]={
15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15
};
void DivPlatformAY8910::runDAC() {
void DivPlatformAY8910::runDAC(int runRate) {
if (runRate==0) runRate=dacRate;
for (int i=0; i<3; i++) {
if (chan[i].active && (chan[i].curPSGMode.val&8) && chan[i].dac.sample!=-1) {
chan[i].dac.period+=chan[i].dac.rate;
bool end=false;
bool changed=false;
int prevOut=chan[i].dac.out;
while (chan[i].dac.period>dacRate && !end) {
while (chan[i].dac.period>runRate && !end) {
DivSample* s=parent->getSample(chan[i].dac.sample);
if (s->samples<=0 || chan[i].dac.pos<0 || chan[i].dac.pos>=(int)s->samples) {
chan[i].dac.sample=-1;
@ -143,7 +144,7 @@ void DivPlatformAY8910::runDAC() {
end=true;
break;
}
chan[i].dac.period-=dacRate;
chan[i].dac.period-=runRate;
}
if (changed && !end) {
if (!isMuted[i]) {
@ -154,13 +155,15 @@ void DivPlatformAY8910::runDAC() {
}
}
void DivPlatformAY8910::runTFX() {
void DivPlatformAY8910::runTFX(int runRate) {
/*
developer's note: if you are checking for intellivision
make sure to add "&& selCore"
because for some reason, the register remap doesn't work
when the user uses AtomicSSG core
*/
float counterRatio=1.0;
if (runRate!=0) counterRatio=(double)rate/(double)runRate;
int timerPeriod, output;
for (int i=0; i<3; i++) {
if (chan[i].active && (chan[i].curPSGMode.val&16) && !(chan[i].curPSGMode.val&8) && chan[i].tfx.mode!=-1) {
@ -182,9 +185,9 @@ void DivPlatformAY8910::runTFX() {
continue;
}
}
chan[i].tfx.counter += 1;
chan[i].tfx.counter += counterRatio;
if (chan[i].tfx.counter >= chan[i].tfx.period && chan[i].tfx.mode == 0) {
chan[i].tfx.counter = 0;
chan[i].tfx.counter -= chan[i].tfx.period;
chan[i].tfx.out ^= 1;
output = ((chan[i].tfx.out) ? chan[i].outVol : (chan[i].tfx.lowBound-(15-chan[i].outVol)));
// TODO: fix this stupid crackling noise that happens
@ -201,7 +204,7 @@ void DivPlatformAY8910::runTFX() {
}
}
if (chan[i].tfx.counter >= chan[i].tfx.period && chan[i].tfx.mode == 1) {
chan[i].tfx.counter = 0;
chan[i].tfx.counter -= chan[i].tfx.period;
if (!isMuted[i]) {
if (intellivision && selCore) {
immWrite(0xa, ayEnvMode);
@ -211,7 +214,7 @@ void DivPlatformAY8910::runTFX() {
}
}
if (chan[i].tfx.counter >= chan[i].tfx.period && chan[i].tfx.mode == 2) {
chan[i].tfx.counter = 0;
chan[i].tfx.counter -= chan[i].tfx.period;
}
}
if (chan[i].tfx.num > 0) {
@ -327,12 +330,9 @@ void DivPlatformAY8910::acquire(short** buf, size_t len) {
void DivPlatformAY8910::fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len) {
writes.clear();
int rate=(int)(chipClock/sRate);
for (size_t i=0; i<len; i++) {
for (int h=0; h<rate; h++) {
runDAC();
runTFX();
}
runDAC(sRate);
runTFX(sRate);
while (!writes.empty()) {
QueuedWrite& w=writes.front();
stream.push_back(DivDelayedWrite(i,w.addr,w.val));

View file

@ -78,7 +78,9 @@ class DivPlatformAY8910: public DivDispatch {
} dac;
struct TFX {
int period, counter, offset, den, num, mode, lowBound, out;
int period;
float counter;
int offset, den, num, mode, lowBound, out;
TFX():
period(0),
counter(0),
@ -156,8 +158,8 @@ class DivPlatformAY8910: public DivDispatch {
friend void putDispatchChan(void*,int,int);
public:
void runDAC();
void runTFX();
void runDAC(int runRate=0);
void runTFX(int runRate=0);
void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8);
void acquire(short** buf, size_t len);
void fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len);

View file

@ -1465,7 +1465,20 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
switch (c.value>>4) {
case 1:
case 2:
case 3:
case 4:
chan[c.chan].opMask&=~(1<<((c.value>>4)-1));
if (c.value&15) {
chan[c.chan].opMask|=(1<<((c.value>>4)-1));
}
break;
default:
chan[c.chan].opMask=c.value&15;
break;
}
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}

View file

@ -175,6 +175,13 @@ void DivPlatformNES::acquire_NSFPlayE(short** buf, size_t len) {
int out2[2];
for (size_t i=0; i<len; i++) {
doPCM;
if (!writes.empty()) {
QueuedWrite w=writes.front();
doWrite(w.addr,w.val);
regPool[w.addr&0x1f]=w.val;
writes.pop();
}
e1_NP->Tick(8);
e2_NP->TickFrameSequence(8);
@ -436,7 +443,7 @@ void DivPlatformNES::tick(bool sysTick) {
// https://www.youtube.com/watch?v=vB4P8x2Am6Y
if (lsamp->loopEnd>lsamp->loopStart && goingToLoop) {
int loopStartAddr=(sampleOffDPCM[dacSample]+lsamp->loopStart)>>3;
int loopStartAddr=sampleOffDPCM[dacSample]+(lsamp->loopStart>>3);
int loopLen=(lsamp->loopEnd-lsamp->loopStart)>>3;
rWrite(0x4012,(loopStartAddr>>6)&0xff);
@ -492,14 +499,14 @@ int DivPlatformNES::dispatch(DivCommand c) {
}
}
if (c.value!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(c.value);
dacSample=(int)ins->amiga.getSample(c.value);
if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].sampleNote=c.value;
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].sampleNoteDelta=c.value-chan[c.chan].sampleNote;
}
} else if (chan[c.chan].sampleNote!=DIV_NOTE_NULL) {
dacSample=ins->amiga.getSample(chan[c.chan].sampleNote);
dacSample=(int)ins->amiga.getSample(chan[c.chan].sampleNote);
if (ins->type==DIV_INS_AMIGA) {
c.value=ins->amiga.getFreq(chan[c.chan].sampleNote);
}

View file

@ -27,6 +27,8 @@
#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} }
#define chWrite(c,a,v) {rWrite((a)+(c)*16,v)}
#define rWriteDelay(a,v,d) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v,d)); if (dumpWrites) {addWrite(a,v);} }
#define chWriteDelay(c,a,v,d) {rWrite((a)+(c)*16,v,d)}
#define sampleTableAddr(c) (sampleTableBase+(c)*4)
#define waveTableAddr(c) (sampleTableBase+8*4+(c)*9*16)
@ -77,7 +79,7 @@ void DivPlatformSNES::acquire(short** buf, size_t len) {
dsp.write(w.addr,w.val);
regPool[w.addr&0x7f]=w.val;
writes.pop();
delay=(w.addr==0x5c)?8:1;
delay=w.delay;
}
}
dsp.set_output(out,1);
@ -253,7 +255,18 @@ void DivPlatformSNES::tick(bool sysTick) {
}
}
if (koff!=0) {
rWrite(0x5c,koff);
// TODO: improve
if (antiClick) {
for (int i=0; i<8; i++) {
if (koff&(1<<i)) {
chWrite(i,5,0);
chWrite(i,7,0x9f);
chan[i].shallWriteEnv=true;
}
}
rWriteDelay(0x7e,0,64);
}
rWriteDelay(0x5c,koff,8);
}
if (writeControl) {
unsigned char control=(noiseFreq&0x1f)|(echoOn?0:0x20);
@ -314,10 +327,7 @@ void DivPlatformSNES::tick(bool sysTick) {
}
}
if (koff!=0) {
rWrite(0x5c,0);
}
if (kon!=0) {
rWrite(0x4c,kon);
rWriteDelay(0x5c,0,8);
}
for (int i=0; i<8; i++) {
if (chan[i].shallWriteVol) {
@ -325,6 +335,9 @@ void DivPlatformSNES::tick(bool sysTick) {
chan[i].shallWriteVol=false;
}
}
if (kon!=0) {
rWrite(0x4c,kon);
}
}
int DivPlatformSNES::dispatch(DivCommand c) {
@ -1027,6 +1040,7 @@ void DivPlatformSNES::setFlags(const DivConfig& flags) {
initEchoMask=flags.getInt("echoMask",0);
interpolationOff=flags.getBool("interpolationOff",false);
antiClick=flags.getBool("antiClick",true);
}
int DivPlatformSNES::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {

View file

@ -70,6 +70,7 @@ class DivPlatformSNES: public DivDispatch {
bool writeDryVol;
bool echoOn;
bool interpolationOff;
bool antiClick;
bool initEchoOn;
signed char initEchoVolL;
@ -82,8 +83,10 @@ class DivPlatformSNES: public DivDispatch {
struct QueuedWrite {
unsigned char addr;
unsigned char val;
QueuedWrite(): addr(0), val(0) {}
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
unsigned char delay;
unsigned char padding;
QueuedWrite(): addr(0), val(0), delay(0), padding(0) {}
QueuedWrite(unsigned char a, unsigned char v, unsigned char d=0): addr(a), val(v), delay(d), padding(0) {}
};
FixedQueue<QueuedWrite,256> writes;

View file

@ -173,6 +173,7 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->runTFX(rate);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
@ -255,6 +256,7 @@ void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->runTFX(rate);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
@ -311,6 +313,16 @@ void DivPlatformYM2203::acquire_lle(short** buf, size_t len) {
fmOut[i]=0;
}
// AY -> OPN
ay->runDAC();
ay->runTFX(rate);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
while (true) {
bool canWeWrite=fm_lle.prescaler_latch[1]&1;
@ -444,6 +456,10 @@ void DivPlatformYM2203::acquire_lle(short** buf, size_t len) {
}
}
void DivPlatformYM2203::fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len) {
ay->fillStream(stream,sRate,len);
}
void DivPlatformYM2203::tick(bool sysTick) {
// PSG
ay->tick(sysTick);
@ -1006,7 +1022,20 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
switch (c.value>>4) {
case 1:
case 2:
case 3:
case 4:
chan[c.chan].opMask&=~(1<<((c.value>>4)-1));
if (c.value&15) {
chan[c.chan].opMask|=(1<<((c.value>>4)-1));
}
break;
default:
chan[c.chan].opMask=c.value&15;
break;
}
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}

View file

@ -74,6 +74,7 @@ class DivPlatformYM2203: public DivPlatformOPN {
public:
void acquire(short** buf, size_t len);
void fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);

View file

@ -325,6 +325,7 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->runTFX(rate);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
@ -440,6 +441,7 @@ void DivPlatformYM2608::acquire_ymfm(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->runTFX(rate);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
@ -680,6 +682,10 @@ void DivPlatformYM2608::acquire_lle(short** buf, size_t len) {
}
}
void DivPlatformYM2608::fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len) {
ay->fillStream(stream,sRate,len);
}
void DivPlatformYM2608::tick(bool sysTick) {
// FM
for (int i=0; i<6; i++) {
@ -1539,7 +1545,20 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
switch (c.value>>4) {
case 1:
case 2:
case 3:
case 4:
chan[c.chan].opMask&=~(1<<((c.value>>4)-1));
if (c.value&15) {
chan[c.chan].opMask|=(1<<((c.value>>4)-1));
}
break;
default:
chan[c.chan].opMask=c.value&15;
break;
}
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}

View file

@ -93,6 +93,7 @@ class DivPlatformYM2608: public DivPlatformOPN {
public:
void acquire(short** buf, size_t len);
void fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch);

View file

@ -260,6 +260,7 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->runTFX(rate);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
@ -373,6 +374,7 @@ void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->runTFX(rate);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
@ -1509,7 +1511,20 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
switch (c.value>>4) {
case 1:
case 2:
case 3:
case 4:
chan[c.chan].opMask&=~(1<<((c.value>>4)-1));
if (c.value&15) {
chan[c.chan].opMask|=(1<<((c.value>>4)-1));
}
break;
default:
chan[c.chan].opMask=c.value&15;
break;
}
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}

View file

@ -324,6 +324,7 @@ void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->runTFX(rate);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
@ -439,6 +440,7 @@ void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
// AY -> OPN
ay->runDAC();
ay->runTFX(rate);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
@ -1578,7 +1580,20 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
switch (c.value>>4) {
case 1:
case 2:
case 3:
case 4:
chan[c.chan].opMask&=~(1<<((c.value>>4)-1));
if (c.value&15) {
chan[c.chan].opMask|=(1<<((c.value>>4)-1));
}
break;
default:
chan[c.chan].opMask=c.value&15;
break;
}
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}

View file

@ -108,6 +108,10 @@ class DivPlatformYM2610Base: public DivPlatformOPN {
}
public:
void fillStream(std::vector<DivDelayedWrite>& stream, int sRate, size_t len) {
ay->fillStream(stream,sRate,len);
}
void reset() {
writeADPCMAOff=0;
writeADPCMAOn=0;

View file

@ -69,6 +69,9 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) {
selStart.y=y;
selEnd.y=y;
} else {
if (xCoarse!=cursor.xCoarse || y!=cursor.y) {
makeCursorUndo();
}
cursor.xCoarse=xCoarse;
cursor.xFine=xFine;
cursor.y=y;
@ -208,6 +211,9 @@ void FurnaceGUI::finishSelection() {
}
void FurnaceGUI::moveCursor(int x, int y, bool select) {
if (y>=editStepCoarse || y<=-editStepCoarse || x<=-5 || x>=5 ) {
makeCursorUndo();
}
if (!select) {
finishSelection();
}
@ -326,6 +332,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
}
void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
makeCursorUndo();
finishSelection();
curNibble=false;
@ -354,6 +361,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
}
void FurnaceGUI::moveCursorNextChannel(bool overflow) {
makeCursorUndo();
finishSelection();
curNibble=false;
@ -382,6 +390,7 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) {
}
void FurnaceGUI::moveCursorTop(bool select) {
makeCursorUndo();
if (!select) {
finishSelection();
}
@ -403,6 +412,7 @@ void FurnaceGUI::moveCursorTop(bool select) {
}
void FurnaceGUI::moveCursorBottom(bool select) {
makeCursorUndo();
if (!select) {
finishSelection();
}

View file

@ -731,6 +731,25 @@ void FurnaceGUI::drawDebug() {
ImGui::Text("result: %.0f%%",realVol*100.0f);
ImGui::TreePop();
}
if (ImGui::TreeNode("Cursor Undo Debug")) {
auto DrawSpot=[&](const CursorJumpPoint& spot) {
ImGui::Text("[%d:%d] <%d:%d, %d>", spot.subSong, spot.order, spot.point.xCoarse, spot.point.xFine, spot.point.y);
};
if (ImGui::BeginChild("##CursorUndoDebugChild", ImVec2(0, 300), true)) {
if (ImGui::BeginTable("##CursorUndoDebug", 2, ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) {
for (size_t row=0; row<MAX(cursorUndoHist.size(),cursorRedoHist.size()); ++row) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (row<cursorUndoHist.size()) DrawSpot(cursorUndoHist[cursorUndoHist.size()-row-1]);
ImGui::TableNextColumn();
if (row<cursorRedoHist.size()) DrawSpot(cursorRedoHist[cursorRedoHist.size()-row-1]);
}
ImGui::EndTable();
}
}
ImGui::EndChild();
ImGui::TreePop();
}
if (ImGui::TreeNode("User Interface")) {
if (ImGui::Button("Inspect")) {
inspectorOpen=!inspectorOpen;

View file

@ -680,10 +680,15 @@ void FurnaceGUI::doAction(int what) {
latchTarget=0;
latchNibble=false;
break;
case GUI_ACTION_PAT_ABSORB_INSTRUMENT: {
case GUI_ACTION_PAT_ABSORB_INSTRUMENT:
doAbsorbInstrument();
break;
}
case GUI_ACTION_PAT_CURSOR_UNDO:
doCursorUndo();
break;
case GUI_ACTION_PAT_CURSOR_REDO:
doCursorRedo();
break;
case GUI_ACTION_INS_LIST_ADD:
if (settings.insTypeMenu) {

View file

@ -678,6 +678,7 @@ void FurnaceGUI::doPasteFurnace(PasteMode mode, int arg, bool readClipboard, Str
if (readClipboard) {
if (settings.cursorPastePos) {
makeCursorUndo();
cursor.y=j;
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
selStart=cursor;
@ -1220,6 +1221,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
if (readClipboard) {
if (settings.cursorPastePos) {
makeCursorUndo();
cursor.y=j;
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
selStart=cursor;
@ -1846,8 +1848,11 @@ void FurnaceGUI::doAbsorbInstrument() {
}
// absorb most recent octave (i.e. set curOctave such that the "main row" (QWERTY) of
// notes will result in an octave number equal to the previous note).
if (!foundOctave && pat->data[i][0] != 0) {
// notes will result in an octave number equal to the previous note). make sure to
// skip "special note values" like OFF/REL/=== and "none", since there won't be valid
// octave values
unsigned char note=pat->data[i][0];
if (!foundOctave && note!=0 && note!=100 && note!=101 && note!=102) {
foundOctave=true;
// decode octave data (was signed cast to unsigned char)
@ -2058,3 +2063,52 @@ void FurnaceGUI::doRedo() {
redoHist.pop_back();
}
CursorJumpPoint FurnaceGUI::getCurrentCursorJumpPoint() {
return CursorJumpPoint(cursor, curOrder, e->getCurrentSubSong());
}
void FurnaceGUI::applyCursorJumpPoint(const CursorJumpPoint& spot) {
cursor=spot.point;
curOrder=MIN(e->curSubSong->ordersLen-1, spot.order);
e->setOrder(curOrder);
e->changeSongP(spot.subSong);
if (!settings.cursorMoveNoScroll) {
updateScroll(cursor.y);
}
}
void FurnaceGUI::makeCursorUndo() {
CursorJumpPoint spot = getCurrentCursorJumpPoint();
if (!cursorUndoHist.empty() && spot == cursorUndoHist.back()) return;
if (cursorUndoHist.size()>=settings.maxUndoSteps) cursorUndoHist.pop_front();
cursorUndoHist.push_back(spot);
// redo history no longer relevant, we've changed timeline
cursorRedoHist.clear();
}
void FurnaceGUI::doCursorUndo() {
if (cursorUndoHist.empty()) return;
// allow returning to current spot
if (cursorRedoHist.size()>=settings.maxUndoSteps) cursorRedoHist.pop_front();
cursorRedoHist.push_back(getCurrentCursorJumpPoint());
// apply spot
applyCursorJumpPoint(cursorUndoHist.back());
cursorUndoHist.pop_back();
}
void FurnaceGUI::doCursorRedo() {
if (cursorRedoHist.empty()) return;
// allow returning to current spot
if (cursorUndoHist.size()>=settings.maxUndoSteps) cursorUndoHist.pop_front();
cursorUndoHist.push_back(getCurrentCursorJumpPoint());
// apply spot
applyCursorJumpPoint(cursorRedoHist.back());
cursorRedoHist.pop_back();
}

View file

@ -560,6 +560,7 @@ void FurnaceGUI::drawFindReplace() {
if (ImGui::TableNextColumn()) {
snprintf(tempID,1024,ICON_FA_CHEVRON_RIGHT "##_FR%d",index);
if (ImGui::Selectable(tempID)) {
makeCursorUndo();
e->changeSongP(i.subsong);
if (e->isPlaying()) {
followPattern=false;

View file

@ -993,11 +993,6 @@ Pos=339,177\n\
Size=601,400\n\
Collapsed=0\n\
\n\
[Window][Rendering...]\n\
Pos=585,342\n\
Size=600,100\n\
Collapsed=0\n\
\n\
[Window][Export VGM##FileDialog]\n\
Pos=340,177\n\
Size=600,400\n\
@ -1216,6 +1211,7 @@ void FurnaceGUI::play(int row) {
memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float));
e->walkSong(loopOrder,loopRow,loopEnd);
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
if (followPattern) makeCursorUndo();
if (!followPattern) e->setOrder(curOrder);
if (row>0) {
if (!e->playToRow(row)) {
@ -5874,7 +5870,8 @@ bool FurnaceGUI::loop() {
MEASURE_BEGIN(popup);
centerNextWindow(_("Rendering..."),canvasW,canvasH);
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove)) {
// ImGui::SetNextWindowSize(ImVec2(0.0f,0.0f));
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings)) {
// WHAT the HELL?!
WAKE_UP;
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_CHAN) {
@ -5890,35 +5887,32 @@ bool FurnaceGUI::loop() {
int curFile=0;
int* curFileLambda=&curFile;
if (e->isExporting()) {
e->lockEngine([this, progressLambda, curPosInRowsLambda, curFileLambda,
loopsLeftLambda, totalLoopsLambda] () {
int curRow=0; int curOrder=0;
e->getCurSongPos(curRow, curOrder); *curFileLambda=0;
e->getCurFileIndex(*curFileLambda);
*curPosInRowsLambda=curRow; for (int i=0; i<curOrder;
i++) {
*curPosInRowsLambda+=songOrdersLengths[i];}
if (!songHasSongEndCommand) {
e->getLoopsLeft(*loopsLeftLambda); e->getTotalLoops(*totalLoopsLambda); if ((*totalLoopsLambda)!=(*loopsLeftLambda)) //we are going 2nd, 3rd, etc. time through the song
{
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump?
}
if (e->getIsFadingOut()) //we are in fadeout??? why it works like that bruh
{
// LIVE WITH IT damn it
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); //a hack so progress bar does not jump?
}
}
// this horrible indentation courtesy of `indent`
*progressLambda=(float) ((*curPosInRowsLambda) + ((*totalLoopsLambda)- (*loopsLeftLambda)) * songLength + lengthOfOneFile * (*curFileLambda)) / (float) totalLength;});
e->lockEngine(
[this, progressLambda, curPosInRowsLambda, curFileLambda, loopsLeftLambda, totalLoopsLambda] () {
int curRow=0; int curOrder=0;
e->getCurSongPos(curRow, curOrder);
*curFileLambda=0;
e->getCurFileIndex(*curFileLambda);
*curPosInRowsLambda=curRow;
for (int i=0; i<curOrder; i++) *curPosInRowsLambda+=songOrdersLengths[i];
if (!songHasSongEndCommand) {
e->getLoopsLeft(*loopsLeftLambda);
e->getTotalLoops(*totalLoopsLambda);
if ((*totalLoopsLambda)!=(*loopsLeftLambda)) { // we are going 2nd, 3rd, etc. time through the song
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); // a hack so progress bar does not jump?
}
if (e->getIsFadingOut()) { // we are in fadeout??? why it works like that bruh
// LIVE WITH IT damn it
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); // a hack so progress bar does not jump?
}
}
*progressLambda=(float)((*curPosInRowsLambda)+((*totalLoopsLambda)-(*loopsLeftLambda))*songLength+lengthOfOneFile*(*curFileLambda))/(float)totalLength;
}
);
}
ImGui::Text(_("Row %d of %d"),curPosInRows+((totalLoops)-(loopsLeft))*songLength,lengthOfOneFile);
if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) {
ImGui::Text(_("Channel %d of %d"),curFile+1,totalFiles);
}
if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) ImGui::Text(_("Channel %d of %d"),curFile+1,totalFiles);
ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0),fmt::sprintf("%.2f%%",curProgress*100.0f).c_str());

View file

@ -818,6 +818,8 @@ enum FurnaceGUIActions {
GUI_ACTION_PAT_SCROLL_MODE,
GUI_ACTION_PAT_CLEAR_LATCH,
GUI_ACTION_PAT_ABSORB_INSTRUMENT,
GUI_ACTION_PAT_CURSOR_UNDO,
GUI_ACTION_PAT_CURSOR_REDO,
GUI_ACTION_PAT_MAX,
GUI_ACTION_INS_LIST_MIN,
@ -1103,6 +1105,22 @@ struct UndoStep {
newPatLen(0) {}
};
struct CursorJumpPoint {
SelectionPoint point;
int order;
int subSong;
CursorJumpPoint(const SelectionPoint& p, int o, int ss):
point(p), order(o), subSong(ss) {}
CursorJumpPoint():
point(), order(0), subSong(0) {}
bool operator== (const CursorJumpPoint& spot) {
return point.xCoarse==spot.point.xCoarse && point.xFine==spot.point.xFine && point.y==spot.point.y && order==spot.order && subSong==spot.subSong;
}
bool operator!= (const CursorJumpPoint& spot) {
return !(*this == spot);
}
};
// -1 = any
struct MIDIBind {
int type, channel, data1, data2;
@ -2500,6 +2518,8 @@ class FurnaceGUI {
std::map<unsigned short,DivPattern*> oldPatMap;
FixedQueue<UndoStep,256> undoHist;
FixedQueue<UndoStep,256> redoHist;
FixedQueue<CursorJumpPoint,256> cursorUndoHist;
FixedQueue<CursorJumpPoint,256> cursorRedoHist;
// sample editor specific
double sampleZoom;
@ -2936,6 +2956,12 @@ class FurnaceGUI {
void doGenerateWave();
CursorJumpPoint getCurrentCursorJumpPoint();
void applyCursorJumpPoint(const CursorJumpPoint& spot);
void makeCursorUndo();
void doCursorUndo();
void doCursorRedo();
void doUndoSample();
void doRedoSample();

Some files were not shown because too many files have changed in this diff Show more