mirror of
https://github.com/tildearrow/furnace.git
synced 2024-12-03 01:37:26 +00:00
Merge remote-tracking branch 'upstream/master' into multiKeybind
This commit is contained in:
commit
e240c9996f
105 changed files with 73916 additions and 68420 deletions
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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` |
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
BIN
instruments/OPL/2-op strings.fui
Normal file
BIN
instruments/OPL/2-op strings.fui
Normal file
Binary file not shown.
BIN
instruments/OPL/asterix gb clarinet.fui
Normal file
BIN
instruments/OPL/asterix gb clarinet.fui
Normal file
Binary file not shown.
BIN
instruments/OPL/clavinetmaybe.fui
Normal file
BIN
instruments/OPL/clavinetmaybe.fui
Normal file
Binary file not shown.
BIN
instruments/OPL/grand piano.fui
Normal file
BIN
instruments/OPL/grand piano.fui
Normal file
Binary file not shown.
BIN
instruments/OPL/kalimba or plain sine i cant tell.fui
Normal file
BIN
instruments/OPL/kalimba or plain sine i cant tell.fui
Normal file
Binary file not shown.
BIN
instruments/OPL/marimba.fui
Normal file
BIN
instruments/OPL/marimba.fui
Normal file
Binary file not shown.
BIN
instruments/OPL/sax.fui
Normal file
BIN
instruments/OPL/sax.fui
Normal file
Binary file not shown.
BIN
instruments/OPL/tuba trumpet thing.fui
Normal file
BIN
instruments/OPL/tuba trumpet thing.fui
Normal file
Binary file not shown.
|
@ -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.
|
||||
|
|
|
@ -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/furnace.pot
6742
po/furnace.pot
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.
Binary file not shown.
Binary file not shown.
6769
po/pt_BR.po
6769
po/pt_BR.po
File diff suppressed because it is too large
Load diff
6748
po/zh_HK.po
6748
po/zh_HK.po
File diff suppressed because it is too large
Load diff
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue