Merge branch 'master' into getSampleMemOffset

This commit is contained in:
tildearrow 2025-07-25 15:16:43 -05:00
commit 9ddcb0bcdc
128 changed files with 323308 additions and 317346 deletions

1
.gitignore vendored
View file

@ -1,6 +1,7 @@
.vscode/ .vscode/
build/ build/
clangbuild/ clangbuild/
oldwinbuild/
nosdl/ nosdl/
release/ release/
t/ t/

View file

@ -139,6 +139,7 @@ option(SHOW_OPEN_ASSETS_MENU_ENTRY "Show option to open built-in assets director
option(CONSOLE_SUBSYSTEM "Build Furnace with Console subsystem on Windows" OFF) option(CONSOLE_SUBSYSTEM "Build Furnace with Console subsystem on Windows" OFF)
option(FORCE_CODEVIEW "Force -gcodeview on MinGW GCC" OFF) option(FORCE_CODEVIEW "Force -gcodeview on MinGW GCC" OFF)
option(FLATPAK_WORKAROUNDS "Enable Flatpak-specific workaround for system file picker" OFF) option(FLATPAK_WORKAROUNDS "Enable Flatpak-specific workaround for system file picker" OFF)
option(NO_INTRO "Disable intro animation entirely" OFF)
if (APPLE) if (APPLE)
option(FORCE_APPLE_BIN "Force enable binary installation to /bin" OFF) option(FORCE_APPLE_BIN "Force enable binary installation to /bin" OFF)
option(MAKE_BUNDLE "Make a bundle" OFF) option(MAKE_BUNDLE "Make a bundle" OFF)
@ -821,6 +822,8 @@ src/engine/export/amigaValidation.cpp
src/engine/export/sapr.cpp src/engine/export/sapr.cpp
src/engine/export/tiuna.cpp src/engine/export/tiuna.cpp
src/engine/export/zsm.cpp src/engine/export/zsm.cpp
src/engine/export/ipod.cpp
src/engine/export/grub.cpp
src/engine/effect/abstract.cpp src/engine/effect/abstract.cpp
src/engine/effect/dummy.cpp src/engine/effect/dummy.cpp
@ -879,12 +882,6 @@ src/gui/fonts.cpp
src/gui/fontzlib.cpp src/gui/fontzlib.cpp
src/gui/image_icon.cpp src/gui/image_icon.cpp
src/gui/image_talogo.cpp
src/gui/image_tachip.cpp
src/gui/image_logo.cpp
src/gui/image_wordmark.cpp
src/gui/image_introbg.cpp
src/gui/image_pat.cpp
src/gui/image.cpp src/gui/image.cpp
src/gui/debug.cpp src/gui/debug.cpp
@ -893,8 +890,6 @@ src/gui/fileDialog.cpp
src/gui/intConst.cpp src/gui/intConst.cpp
src/gui/guiConst.cpp src/gui/guiConst.cpp
src/gui/introTune.cpp
src/gui/about.cpp src/gui/about.cpp
src/gui/channels.cpp src/gui/channels.cpp
src/gui/chanOsc.cpp src/gui/chanOsc.cpp
@ -914,7 +909,6 @@ src/gui/fmPreview.cpp
src/gui/gradient.cpp src/gui/gradient.cpp
src/gui/grooves.cpp src/gui/grooves.cpp
src/gui/insEdit.cpp src/gui/insEdit.cpp
src/gui/intro.cpp
src/gui/log.cpp src/gui/log.cpp
src/gui/memory.cpp src/gui/memory.cpp
src/gui/mixer.cpp src/gui/mixer.cpp
@ -951,6 +945,21 @@ src/gui/xyOsc.cpp
src/gui/gui.cpp src/gui/gui.cpp
) )
if (NOT NO_INTRO)
list(APPEND GUI_SOURCES src/gui/introTune.cpp)
list(APPEND GUI_SOURCES src/gui/intro.cpp)
list(APPEND GUI_SOURCES
src/gui/image_talogo.cpp
src/gui/image_tachip.cpp
src/gui/image_logo.cpp
src/gui/image_wordmark.cpp
src/gui/image_introbg.cpp
src/gui/image_pat.cpp
)
else()
list(APPEND DEPENDENCIES_DEFINES NO_INTRO)
endif()
if (WIN32 AND NOT SUPPORT_XP) if (WIN32 AND NOT SUPPORT_XP)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_common.cpp) list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_common.cpp)
list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_win.cpp) list(APPEND GUI_SOURCES extern/nfd-modified/src/nfd_win.cpp)

View file

@ -159,6 +159,8 @@ for other operating systems, you may [build the source](#developer-info).
- [documentation](doc/README.md). - [documentation](doc/README.md).
- [frequently asked questions (FAQ)](doc/1-intro/faq.md). - [frequently asked questions (FAQ)](doc/1-intro/faq.md).
- **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or the [Discord](https://discord.gg/QhA26dXD23). - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or the [Discord](https://discord.gg/QhA26dXD23).
- these are the only **official** discussion channels for Furnace. **any other places are not official and not managed by me (tildearrow).**
- no, there isn't an official Furnace Facebook group. the one that seemingly exists isn't mine.
## packages ## packages

View file

@ -61,7 +61,7 @@ public class SDLActivity extends Activity implements View.OnSystemUiVisibilityCh
private static final String TAG = "SDL"; private static final String TAG = "SDL";
private static final int SDL_MAJOR_VERSION = 2; private static final int SDL_MAJOR_VERSION = 2;
private static final int SDL_MINOR_VERSION = 32; private static final int SDL_MINOR_VERSION = 32;
private static final int SDL_MICRO_VERSION = 4; private static final int SDL_MICRO_VERSION = 8;
/* /*
// Display InputType.SOURCE/CLASS of events and devices // Display InputType.SOURCE/CLASS of events and devices
// //

View file

@ -19,7 +19,6 @@ these demo songs are not under the GPL. all rights are reserved to the original
- AquaDoesStuff - AquaDoesStuff
- asikwus - asikwus
- AstralBlue - AstralBlue
- Background2982
- battybeats - battybeats
- bbqzzd - bbqzzd
- Bernie - Bernie
@ -55,7 +54,7 @@ these demo songs are not under the GPL. all rights are reserved to the original
- Forte - Forte
- Fragmare - Fragmare
- freq-mod - freq-mod
- Fur - Furmilion
- gtr3qq - gtr3qq
- halberd (lordlydumbass) - halberd (lordlydumbass)
- Heckett Heriot - Heckett Heriot
@ -63,6 +62,7 @@ these demo songs are not under the GPL. all rights are reserved to the original
- Hortus - Hortus
- ifrit05 - ifrit05
- iyatemu - iyatemu
- jaezu
- JayBOB18 - JayBOB18
- Jimmy-DS - Jimmy-DS
- jvsTSX - jvsTSX
@ -71,7 +71,6 @@ these demo songs are not under the GPL. all rights are reserved to the original
- kleeder - kleeder
- Korbo - Korbo
- KungFuFurby - KungFuFurby
- jaezu
- Laggy - Laggy
- leejh20 - leejh20
- LovelyA72 - LovelyA72
@ -84,6 +83,7 @@ these demo songs are not under the GPL. all rights are reserved to the original
- Miker - Miker
- Molkirill - Molkirill
- MrCoolDude - MrCoolDude
- Mr. Saturn
- NeoWar - NeoWar
- Nerreave - Nerreave
- niffuM - niffuM
@ -92,6 +92,7 @@ these demo songs are not under the GPL. all rights are reserved to the original
- Notakin - Notakin
- nwcr - nwcr
- NyaongI - NyaongI
- OddPandemonium
- PeyPey - PeyPey
- PichuMario - PichuMario
- pixelated - pixelated
@ -129,11 +130,11 @@ these demo songs are not under the GPL. all rights are reserved to the original
- TCORPStudios - TCORPStudios
- Teuthida - Teuthida
- ThaCuber - ThaCuber
- The Beesh-Spweesh!
- The Blender Fiddler - The Blender Fiddler
- TheDuccinator - TheDuccinator
- theloredev
- The Beesh-Spweesh!
- The Goofy-Mouse - The Goofy-Mouse
- theloredev
- TheRealHedgehogSonic - TheRealHedgehogSonic
- tildearrow - tildearrow
- tom\_atom - tom\_atom
@ -145,7 +146,6 @@ these demo songs are not under the GPL. all rights are reserved to the original
- Wegfrei - Wegfrei
- Xan - Xan
- Yuzugure! - Yuzugure!
- z1cmr40
- Zabir - Zabir
- Zaxinoth Digital - Zaxinoth Digital
- Zaxolotl - Zaxolotl

Binary file not shown.

BIN
demos/blank/map01.fur Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -53,6 +53,9 @@ the following settings exist:
- **chips to export**: select which chips are going to be exported. - **chips to export**: select which chips are going to be exported.
- due to VGM format limitations, you can only select up to two of each chip type. - due to VGM format limitations, you can only select up to two of each chip type.
- some chips will not be available, either because VGM doesn't support these yet, or because you selected an old format version. - some chips will not be available, either because VGM doesn't support these yet, or because you selected an old format version.
- **speed drift compensation**:
- **none**: normal export.
- **DeadFish VgmPlay (1.02×)**: adjusts speed to account for inaccuracy in [VgmPlay](https://www.mjsstuf.x10host.com/pages/vgmPlay/vgmPlay.htm), a Sega Genesis VGM player by DeadFish.
## ZSM ## ZSM
@ -72,8 +75,10 @@ the following settings are available:
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. 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: the following formats and systems are supported:
- [Atari 2600 (TIunA)](../7-systems/tia.md) - TIunA assembly, using [Atari 2600 (with software pitch driver)](../7-systems/tia.md).
- iPod .tone alarm, using [PC Speaker](../7-systems/pcspkr.md).
- GRUB_INIT_TUNE, using [PC Speaker](../7-systems/pcspkr.md).
## text ## text

View file

@ -84,7 +84,7 @@ the keys in the "Global hotkeys" section can be used in any window, although not
| Samples (Palette) | — | | Samples (Palette) | — |
| | | | | |
| **Note input** | | | **Note input** | |
| _see "note input" section after table_ | | | _see [settings](./settings.md#note-input)._ | |
| | | | | |
| **Pattern** | | | **Pattern** | |
| Transpose (+1) | `Ctrl-F2` | | Transpose (+1) | `Ctrl-F2` |

View file

@ -356,6 +356,10 @@ below all the binds, select a key from the dropdown list to add it. it will appe
- **No** - **No**
- **Yes** - **Yes**
- **Inverted** - **Inverted**
- **How many steps to move with each scroll wheel step?**: only appears when "Move cursor with scroll wheel" is set to "Yes" or "Inverted".
- **One**
- **Edit Step**
- **Coarse Step**
### Assets ### Assets

View file

@ -4,7 +4,7 @@ Bifurcator instrument editor consists of these macros:
- **Volume**: volume sequence. - **Volume**: volume sequence.
- **Arpeggio**: pitch sequence. - **Arpeggio**: pitch sequence.
- **Parametet**: set parameter of logistic map function. - **Parameter**: set parameter of logistic map function.
- **Panning (left)**: output level for left channel. - **Panning (left)**: output level for left channel.
- **Panning (right)**: output level for right channel. - **Panning (right)**: output level for right channel.
- **Pitch**: fine pitch. - **Pitch**: fine pitch.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 214 KiB

After

Width:  |  Height:  |  Size: 237 KiB

View file

@ -34,6 +34,17 @@ ha! effects...
this chip uses the [Beeper](../4-instrument/beeper.md) instrument editor. this chip uses the [Beeper](../4-instrument/beeper.md) instrument editor.
## ROM export
two ROM export options exist:
- **iPod .tone alarm**: with the iPod _in disk mode,_ drag the export file into the `iPod_Control/Tones` folder.
- **GRUB_INIT_TUNE**: use with the GRUB bootloader.
- into the file `/etc/default/grub` add the following line with the text output copied and pasted where `text` is:\
`GRUB_INIT_TUNE="text"`\
then regenerate GRUB config.
- **export binary file**: creates a binary file instead of text. in either the GRUB shell or the GRUB config file, use the `play` command followed by the exported file's name.
## chip config ## chip config
the following options are available in the Chip Manager window: the following options are available in the Chip Manager window:

Binary file not shown.

After

Width:  |  Height:  |  Size: 730 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

View file

@ -22,12 +22,15 @@ additionally, channel 5 offers a modulation/sweep unit. the former is similar to
- `y` sets the speed and direction. - `y` sets the speed and direction.
- `0-7`: down - `0-7`: down
- `8-F`: up - `8-F`: up
- IMPORTANT: if the envelope ends, you will not be able to trigger another envelope until you reset the channel's phase! (this is a Virtual Boy hardware bug)
- setting a phase reset macro at the beginning will do.
- `13xy`: **setup sweep.** - `13xy`: **setup sweep.**
- `x` sets the speed. - `x` sets the speed.
- `0` and `8` are "speed 0" - sweep is ineffective. - `0` and `8` are "speed 0" - sweep is ineffective.
- `y` sets the shift (`0` to `7`). - `y` sets the shift (`0` to `7`).
- `8` and higher will mute the channel. - `8` and higher will mute the channel.
- only in channel 5. - only in channel 5.
- if modulation ends, you cannot sweep until the next phase reset.
- `14xy`: **setup modulation.** - `14xy`: **setup modulation.**
- `x` determines whether it's enabled or not. - `x` determines whether it's enabled or not.
- 0: disabled - 0: disabled
@ -38,6 +41,8 @@ additionally, channel 5 offers a modulation/sweep unit. the former is similar to
- `0` and `8` are "speed 0" - modulation is ineffective. - `0` and `8` are "speed 0" - modulation is ineffective.
- no, you can't really do Yamaha FM using this. - no, you can't really do Yamaha FM using this.
- only in channel 5. - only in channel 5.
- IMPORTANT: if the modulation ends, you will not be able to trigger another envelope until you reset the channel's phase! (this is a Virtual Boy hardware bug)
- setting a phase reset macro at the beginning will do.
- `15xx`: **set modulation wave.** - `15xx`: **set modulation wave.**
- `xx` points to a wavetable. range is `0` to `FF`. - `xx` points to a wavetable. range is `0` to `FF`.
- this is an alternative to setting the modulation wave through the instrument. - this is an alternative to setting the modulation wave through the instrument.
@ -46,6 +51,14 @@ additionally, channel 5 offers a modulation/sweep unit. the former is similar to
this chip uses the [Virtual Boy](../4-instrument/virtual-boy.md) instrument editor. this chip uses the [Virtual Boy](../4-instrument/virtual-boy.md) instrument editor.
## channel status
the following icons are displayed when channel status is enabled in the pattern view:
- ![envelope off](status-VBoy-env-none.png) hardware envelope is disabled or running OK
- ![envelope warning](status-VBoy-env-warn.png) hardware envelope has finished - attempting to write a new envelope won't work without phase reset!
- ![envelope error](status-VBoy-env-error.png) can't start hardware envelope - please reset phase
## chip config ## chip config
the following options are available in the Chip Manager window: the following options are available in the Chip Manager window:

2
extern/SDL vendored

@ -1 +1 @@
Subproject commit d9a31df26d9bb91486db15c3732e8fa04c4449e2 Subproject commit 6371fd44c8a3cdfb3166b36d4798f5daeca2eeee

BIN
instruments/SAA/EnvBass.fui Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -397,7 +397,7 @@ size | description
1 | legacy "always set volume" behavior (>=191) 1 | legacy "always set volume" behavior (>=191)
1 | legacy sample offset effect (>=200) 1 | legacy sample offset effect (>=200)
--- | **speed pattern of first song** (>=139) --- | **speed pattern of first song** (>=139)
1 | length of speed pattern (fail if this is lower than 0 or higher than 16) 1 | length of speed pattern (fail if this is lower than 1 or higher than 16)
16 | speed pattern (this overrides speed 1 and speed 2 settings) 16 | speed pattern (this overrides speed 1 and speed 2 settings)
--- | **groove list** (>=139) --- | **groove list** (>=139)
1 | number of entries 1 | number of entries
@ -478,7 +478,7 @@ size | description
S?? | channel short names S?? | channel short names
| - same as above | - same as above
--- | **speed pattern** (>=139) --- | **speed pattern** (>=139)
1 | length of speed pattern (fail if this is lower than 0 or higher than 16) 1 | length of speed pattern (fail if this is lower than 1 or higher than 16)
16 | speed pattern (this overrides speed 1 and speed 2 settings) 16 | speed pattern (this overrides speed 1 and speed 2 settings)
``` ```

30815
po/de.po

File diff suppressed because it is too large Load diff

34829
po/es.po

File diff suppressed because it is too large Load diff

30815
po/fi.po

File diff suppressed because it is too large Load diff

30902
po/fr.po

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

30825
po/hy.po

File diff suppressed because it is too large Load diff

31049
po/id.po

File diff suppressed because it is too large Load diff

30803
po/ja.po

File diff suppressed because it is too large Load diff

31667
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.

30915
po/nl.po

File diff suppressed because it is too large Load diff

34733
po/pl.po

File diff suppressed because it is too large Load diff

34763
po/pt_BR.po

File diff suppressed because it is too large Load diff

35376
po/ru.po

File diff suppressed because it is too large Load diff

30835
po/sk.po

File diff suppressed because it is too large Load diff

32379
po/sv.po

File diff suppressed because it is too large Load diff

30847
po/th.po

File diff suppressed because it is too large Load diff

30815
po/tr.po

File diff suppressed because it is too large Load diff

30831
po/uk.po

File diff suppressed because it is too large Load diff

31503
po/zh.po

File diff suppressed because it is too large Load diff

31503
po/zh_HK.po

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@ fi
cd winbuild cd winbuild
# TODO: potential Arch-ism? # TODO: potential Arch-ism?
x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Wno-deprecated-declarations -Werror" -DWITH_LOCALE=ON -DUSE_MOMO=ON -DUSE_BACKWARD=ON -DFORCE_CODEVIEW=ON -DSUPPORT_VISTA=ON .. || exit 1 x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Wno-deprecated-declarations -Werror" -DWITH_LOCALE=ON -DUSE_MOMO=ON -DUSE_BACKWARD=ON -DFORCE_CODEVIEW=OFF -DSUPPORT_VISTA=ON .. || exit 1
make -j8 || exit 1 make -j8 || exit 1
cd .. cd ..

View file

@ -16,7 +16,7 @@ fi
cd winCbuild cd winCbuild
# TODO: potential Arch-ism? # TODO: potential Arch-ism?
x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Wno-deprecated-declarations -Werror" -DUSE_BACKWARD=ON -DCONSOLE_SUBSYSTEM=ON -DWITH_LOCALE=ON -DUSE_MOMO=ON -DFORCE_CODEVIEW=ON .. || exit 1 x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Wno-deprecated-declarations -Werror" -DUSE_BACKWARD=ON -DCONSOLE_SUBSYSTEM=ON -DWITH_LOCALE=ON -DUSE_MOMO=ON -DFORCE_CODEVIEW=OFF .. || exit 1
make -j8 || exit 1 make -j8 || exit 1
cd .. cd ..

View file

@ -16,7 +16,7 @@ fi
cd xpbuild cd xpbuild
# TODO: potential Arch-ism? # TODO: potential Arch-ism?
i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON -DWITH_RENDER_DX11=OFF -DSDL_SSE=OFF -DSDL_SSE2=OFF -DSDL_SSE3=OFF -DENABLE_SSE=OFF -DENABLE_SSE2=OFF -DENABLE_AVX=OFF -DENABLE_AVX2=OFF -DUSE_BACKWARD=OFF -DCONSOLE_SUBSYSTEM=OFF -DWITH_LOCALE=ON -DUSE_MOMO=ON -DFORCE_CODEVIEW=OFF .. || exit 1 i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type" -DBUILD_SHARED_LIBS=OFF -DSUPPORT_XP=ON -DWITH_RENDER_DX11=OFF -DSDL_SSE=OFF -DSDL_SSE2=OFF -DSDL_SSE3=OFF -DENABLE_SSE=OFF -DENABLE_SSE2=OFF -DENABLE_AVX=OFF -DENABLE_AVX2=OFF -DUSE_BACKWARD=OFF -DCONSOLE_SUBSYSTEM=OFF -DWITH_LOCALE=ON -DUSE_MOMO=ON -DFORCE_CODEVIEW=OFF -DNO_INTRO=ON .. || exit 1
make -j8 || exit 1 make -j8 || exit 1
cd .. cd ..

View file

@ -193,6 +193,4 @@ void DivEngine::factoryReset() {
if (i>0) path+=fmt::sprintf(".%d",i); if (i>0) path+=fmt::sprintf(".%d",i);
deleteFile(path.c_str()); deleteFile(path.c_str());
} }
exit(0);
} }

View file

@ -564,6 +564,8 @@ struct DivChannelModeHints {
// - 18: inc linear // - 18: inc linear
// - 19: inc bent // - 19: inc bent
// - 20: direct // - 20: direct
// - 21: warning
// - 22: error
unsigned char type[4]; unsigned char type[4];
// up to 4 // up to 4
unsigned char count; unsigned char count;

View file

@ -3847,7 +3847,7 @@ bool DivEngine::initAudioBackend() {
if (audioEngine==DIV_AUDIO_SDL) { if (audioEngine==DIV_AUDIO_SDL) {
String audioDriver=getConfString("sdlAudioDriver",""); String audioDriver=getConfString("sdlAudioDriver","");
if (!audioDriver.empty()) { if (!audioDriver.empty()) {
SDL_SetHint("SDL_HINT_AUDIODRIVER",audioDriver.c_str()); SDL_SetHint(SDL_HINT_AUDIODRIVER,audioDriver.c_str());
} }
} }
#endif #endif
@ -4072,7 +4072,7 @@ bool DivEngine::preInit(bool noSafeMode) {
#ifdef HAVE_SDL2 #ifdef HAVE_SDL2
String audioDriver=getConfString("sdlAudioDriver",""); String audioDriver=getConfString("sdlAudioDriver","");
if (!audioDriver.empty()) { if (!audioDriver.empty()) {
SDL_SetHint("SDL_HINT_AUDIODRIVER",audioDriver.c_str()); SDL_SetHint(SDL_HINT_AUDIODRIVER,audioDriver.c_str());
} }
#endif #endif

View file

@ -54,8 +54,8 @@ class DivWorkPool;
#define DIV_UNSTABLE #define DIV_UNSTABLE
#define DIV_VERSION "dev231" #define DIV_VERSION "dev232"
#define DIV_ENGINE_VERSION 231 #define DIV_ENGINE_VERSION 232
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02 #define DIV_VERSION_FC 0xff02
@ -577,13 +577,13 @@ class DivEngine {
DivWorkPool* renderPool; DivWorkPool* renderPool;
// MIDI stuff // MIDI stuff
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;}; std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -3;};
void processRowPre(int i); void processRowPre(int i);
void processRow(int i, bool afterDelay); void processRow(int i, bool afterDelay);
void nextOrder(); void nextOrder();
void nextRow(); void nextRow();
void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable, bool dpcm07, DivDispatch** writeNES); void performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable, bool dpcm07, DivDispatch** writeNES, int rateCorrection);
// returns true if end of song. // returns true if end of song.
bool nextTick(bool noAccum=false, bool inhibitLowLat=false); bool nextTick(bool noAccum=false, bool inhibitLowLat=false);
bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal); bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal);
@ -675,6 +675,8 @@ class DivEngine {
friend class DivExportSAPR; friend class DivExportSAPR;
friend class DivExportTiuna; friend class DivExportTiuna;
friend class DivExportZSM; friend class DivExportZSM;
friend class DivExportiPod;
friend class DivExportGRUB;
public: public:
DivSong song; DivSong song;
@ -726,7 +728,7 @@ class DivEngine {
// - x to add x+1 ticks of trailing // - x to add x+1 ticks of trailing
// - -1 to auto-determine trailing // - -1 to auto-determine trailing
// - -2 to add a whole loop of trailing // - -2 to add a whole loop of trailing
SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false, int trailingTicks=-1, bool dpcm07=false); SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171, bool patternHints=false, bool directStream=false, int trailingTicks=-1, bool dpcm07=false, int correctedRate=44100);
// dump to TIunA. // dump to TIunA.
SafeWriter* saveTiuna(const bool* sysToExport, const char* baseLabel, int firstBankSize, int otherBankSize); SafeWriter* saveTiuna(const bool* sysToExport, const char* baseLabel, int firstBankSize, int otherBankSize);
// dump command stream. // dump command stream.
@ -1336,7 +1338,7 @@ class DivEngine {
void setMidiVolExp(float value); void setMidiVolExp(float value);
// set MIDI input callback // set MIDI input callback
// if the specified function returns -2, note feedback will be inhibited. // if the specified function returns -3, note feedback will be inhibited.
void setMidiCallback(std::function<int(const TAMidiMessage&)> what); void setMidiCallback(std::function<int(const TAMidiMessage&)> what);
// send MIDI message // send MIDI message

View file

@ -23,6 +23,8 @@
#include "export/sapr.h" #include "export/sapr.h"
#include "export/tiuna.h" #include "export/tiuna.h"
#include "export/zsm.h" #include "export/zsm.h"
#include "export/ipod.h"
#include "export/grub.h"
DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) { DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
DivROMExport* exporter=NULL; DivROMExport* exporter=NULL;
@ -39,6 +41,12 @@ DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) {
case DIV_ROM_SAP_R: case DIV_ROM_SAP_R:
exporter=new DivExportSAPR; exporter=new DivExportSAPR;
break; break;
case DIV_ROM_IPOD:
exporter=new DivExportiPod;
break;
case DIV_ROM_GRUB:
exporter=new DivExportGRUB;
break;
default: default:
exporter=new DivROMExport; exporter=new DivROMExport;
break; break;

View file

@ -32,6 +32,8 @@ enum DivROMExportOptions {
DIV_ROM_ZSM, DIV_ROM_ZSM,
DIV_ROM_TIUNA, DIV_ROM_TIUNA,
DIV_ROM_SAP_R, DIV_ROM_SAP_R,
DIV_ROM_IPOD,
DIV_ROM_GRUB,
DIV_ROM_MAX DIV_ROM_MAX
}; };

204
src/engine/export/grub.cpp Normal file
View file

@ -0,0 +1,204 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "grub.h"
#include "../engine.h"
#include "../ta-log.h"
#include <fmt/printf.h>
#include <array>
#include <vector>
void DivExportGRUB::run() {
bool grubExportBin=conf.getBool("exportBin",false);
int BEEPER=-1;
int IGNORED=0;
// Locate system index.
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i] == DIV_SYSTEM_PCSPKR) {
if (BEEPER>=0) {
IGNORED++;
logAppendf("Ignoring duplicate Beeper id %d",i);
break;
}
BEEPER=i;
logAppendf("PC Speaker detected as chip id %d",i);
break;
} else {
IGNORED++;
logAppendf("Ignoring chip id %d, system id %d",i,(int)e->song.system[i]);
break;
}
}
if (BEEPER<0) {
logAppendf("ERROR: Could not find PC Speaker/Beeper");
failed=true;
running=false;
return;
}
if (IGNORED>0) {
logAppendf("WARNING: GRUB export ignoring unsup sys count: %d",IGNORED);
}
size_t tickCount=0;
e->stop();
e->repeatPattern=false;
e->setOrder(0);
logAppend("playing and logging register writes...");
int oldFreq = 0;
int freq = 0;
e->synchronizedSoft([&]() {
double origRate = e->got.rate;
double rate = MIN(e->curSubSong->hz,1000.0);
logAppendf("export rate is %d hz",(int)rate);
int tempo = (int)(60000.0/(1000.0/rate));
e->got.rate=rate;
// Determine loop point.
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
e->walkSong(loopOrder,loopRow,loopEnd);
logAppendf("loop point: %d %d",loopOrder,loopRow);
e->warnings="";
auto w = new SafeWriter;
w->init();
// Reset the playback state.
e->curOrder=0;
e->freelance=false;
e->playing=false;
e->extValuePresent=false;
e->remainingLoops=-1;
e->disCont[BEEPER].dispatch->toggleRegisterDump(true);
// Prepare to write song data.
e->playSub(false);
bool done=false;
logAppend("writing data...");
progress[0].amount=0.15f;
int wait_tempo = 0;
if (grubExportBin)
w->writeI(tempo); // write tempo
else
w->writeText(fmt::sprintf("%d",tempo)); // write tempo
while (!done) {
if (e->nextTick(false,true) || !e->playing) {
done=true;
}
// get register dumps
uint8_t* regPool = e->disCont[BEEPER].dispatch->getRegisterPool();
int chipClock = e->disCont[BEEPER].dispatch->chipClock;
freq = (int)(regPool[0]|(regPool[1]<<8));
if (freq > 0) freq = chipClock/freq;
// write wait
tickCount++;
int totalWait=e->cycles;
if (totalWait>0 && !done) {
while (totalWait) {
wait_tempo++;
if (freq != oldFreq || wait_tempo == 65535) {
if (grubExportBin) {
w->writeS(oldFreq); // pitch
w->writeS(wait_tempo); // duration
} else {
w->writeText(fmt::sprintf(" %d %d", oldFreq, wait_tempo));
}
oldFreq = freq;
wait_tempo = 0;
}
totalWait--;
tickCount++;
}
}
}
if (!grubExportBin) w->writeText(fmt::sprintf("\n")); // end song
// end of song
// done - close out.
e->got.rate=origRate;
e->disCont[BEEPER].dispatch->getRegisterWrites().clear();
e->disCont[BEEPER].dispatch->toggleRegisterDump(false);
e->remainingLoops=-1;
e->playing=false;
e->freelance=false;
e->extValuePresent=false;
output.push_back(DivROMExportOutput(grubExportBin?"export.bin":"export.txt",w));
});
progress[0].amount=1.0f;
logAppend("finished!");
running=false;
}
bool DivExportGRUB::go(DivEngine* eng) {
progress[0].name="Progress";
progress[0].amount=0.0f;
e=eng;
running=true;
failed=false;
mustAbort=false;
exportThread=new std::thread(&DivExportGRUB::run,this);
return true;
}
void DivExportGRUB::wait() {
if (exportThread!=NULL) {
logV("waiting for export thread...");
exportThread->join();
delete exportThread;
}
}
void DivExportGRUB::abort() {
mustAbort=true;
wait();
}
bool DivExportGRUB::isRunning() {
return running;
}
bool DivExportGRUB::hasFailed() {
return failed;
}
DivROMExportProgress DivExportGRUB::getProgress(int index) {
if (index<0 || index>1) return progress[1];
return progress[index];
}

38
src/engine/export/grub.h Normal file
View file

@ -0,0 +1,38 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../export.h"
#include <thread>
class DivExportGRUB: public DivROMExport {
DivEngine* e;
std::thread* exportThread;
DivROMExportProgress progress[2];
bool running, failed, mustAbort;
void run();
public:
bool go(DivEngine* e);
bool isRunning();
bool hasFailed();
void abort();
void wait();
DivROMExportProgress getProgress(int index=0);
~DivExportGRUB() {}
};

194
src/engine/export/ipod.cpp Normal file
View file

@ -0,0 +1,194 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
// thanks asiekierka! (I used your SAP-R export code as a base for this)
#include "ipod.h"
#include "../engine.h"
#include "../ta-log.h"
#include <fmt/printf.h>
#include <array>
#include <vector>
void DivExportiPod::run() {
int BEEPER=-1;
int IGNORED=0;
// Locate system index.
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i] == DIV_SYSTEM_PCSPKR) {
if (BEEPER>=0) {
IGNORED++;
logAppendf("Ignoring duplicate Beeper id %d",i);
break;
}
BEEPER=i;
logAppendf("PC Speaker detected as chip id %d",i);
break;
} else {
IGNORED++;
logAppendf("Ignoring chip id %d, system id %d",i,(int)e->song.system[i]);
break;
}
}
if (BEEPER<0) {
logAppendf("ERROR: Could not find PC Speaker/Beeper");
failed=true;
running=false;
return;
}
if (IGNORED>0) {
logAppendf("WARNING: tone export ignoring unsup sys count: %d",IGNORED);
}
size_t tickCount=0;
double rate = 1000.0;
e->stop();
e->repeatPattern=false;
e->setOrder(0);
logAppend("playing and logging register writes...");
int oldFreq = 0;
int freq = 0;
e->synchronizedSoft([&]() {
double origRate = e->got.rate;
e->got.rate=rate;
// Determine loop point.
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
e->walkSong(loopOrder,loopRow,loopEnd);
logAppendf("loop point: %d %d",loopOrder,loopRow);
e->warnings="";
auto w = new SafeWriter;
w->init();
w->writeText(fmt::sprintf("%s\n", e->song.name));
// Reset the playback state.
e->curOrder=0;
e->freelance=false;
e->playing=false;
e->extValuePresent=false;
e->remainingLoops=-1;
e->disCont[BEEPER].dispatch->toggleRegisterDump(true);
// Prepare to write song data.
e->playSub(false);
bool done=false;
logAppend("writing data...");
progress[0].amount=0.15f;
int wait_ms = 0;
while (!done) {
if (e->nextTick(false,true) || !e->playing) {
done=true;
}
// get register dumps
uint8_t* regPool = e->disCont[BEEPER].dispatch->getRegisterPool();
int chipClock = e->disCont[BEEPER].dispatch->chipClock;
freq = (int)(regPool[0]|(regPool[1]<<8));
if (freq > 0) freq = chipClock/freq;
// write wait
tickCount++;
int totalWait=e->cycles;
if (totalWait>0 && !done) {
while (totalWait) {
wait_ms++;
if (freq != oldFreq) {
w->writeText(fmt::sprintf("%d %d\n", oldFreq, wait_ms));
oldFreq = freq;
wait_ms = 0;
}
totalWait--;
tickCount++;
}
}
}
// end of song
// done - close out.
e->got.rate=origRate;
e->disCont[BEEPER].dispatch->getRegisterWrites().clear();
e->disCont[BEEPER].dispatch->toggleRegisterDump(false);
e->remainingLoops=-1;
e->playing=false;
e->freelance=false;
e->extValuePresent=false;
output.push_back(DivROMExportOutput("export.tone",w));
});
progress[0].amount=1.0f;
logAppend("finished!");
running=false;
}
bool DivExportiPod::go(DivEngine* eng) {
progress[0].name="Progress";
progress[0].amount=0.0f;
e=eng;
running=true;
failed=false;
mustAbort=false;
exportThread=new std::thread(&DivExportiPod::run,this);
return true;
}
void DivExportiPod::wait() {
if (exportThread!=NULL) {
logV("waiting for export thread...");
exportThread->join();
delete exportThread;
}
}
void DivExportiPod::abort() {
mustAbort=true;
wait();
}
bool DivExportiPod::isRunning() {
return running;
}
bool DivExportiPod::hasFailed() {
return failed;
}
DivROMExportProgress DivExportiPod::getProgress(int index) {
if (index<0 || index>1) return progress[1];
return progress[index];
}

38
src/engine/export/ipod.h Normal file
View file

@ -0,0 +1,38 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2025 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../export.h"
#include <thread>
class DivExportiPod: public DivROMExport {
DivEngine* e;
std::thread* exportThread;
DivROMExportProgress progress[2];
bool running, failed, mustAbort;
void run();
public:
bool go(DivEngine* e);
bool isRunning();
bool hasFailed();
void abort();
void wait();
DivROMExportProgress getProgress(int index=0);
~DivExportiPod() {}
};

View file

@ -119,4 +119,27 @@ void DivEngine::registerROMExports() {
}, },
false, DIV_REQPOL_EXACT false, DIV_REQPOL_EXACT
); );
romExportDefs[DIV_ROM_IPOD]=new DivROMExportDef(
"iPod .tone alarm", "AArt1256",
"iPod Beeper (.tone) Alarm export\n"
"for playback, you can drag the resulting file\n"
"into iPod_Control/Tones to your iPod IN DISK MODE",
"alarm tone files", ".tone",
{
DIV_SYSTEM_PCSPKR
},
false, DIV_REQPOL_ANY
);
romExportDefs[DIV_ROM_GRUB]=new DivROMExportDef(
"GRUB_INIT_TUNE", "AArt1256",
"GRUB_INIT_TUNE export\n"
"for use with the GRUB bootloader using the \"play\" command",
"Text/Binary files", NULL,
{
DIV_SYSTEM_PCSPKR
},
false, DIV_REQPOL_ANY
);
} }

View file

@ -436,6 +436,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
unsigned int expansions = 0; unsigned int expansions = 0;
unsigned int tchans = 0; unsigned int tchans = 0;
unsigned int n163Chans = 0; unsigned int n163Chans = 0;
int n163WaveOff[128];
bool hasSequence[256][8]; bool hasSequence[256][8];
unsigned char sequenceIndex[256][8]; unsigned char sequenceIndex[256][8];
unsigned char macro_types[256][8]; unsigned char macro_types[256][8];
@ -459,6 +460,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
int total_chans = 0; int total_chans = 0;
memset(n163WaveOff,0,128*sizeof(int));
memset(hasSequence, 0, 256 * 8 * sizeof(bool)); memset(hasSequence, 0, 256 * 8 * sizeof(bool));
memset(sequenceIndex, 0, 256 * 8); memset(sequenceIndex, 0, 256 * 8);
memset(macro_types, 0, 256 * 8); memset(macro_types, 0, 256 * 8);
@ -653,6 +655,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
int map_ch = 0; int map_ch = 0;
ds.system[systemID++] = DIV_SYSTEM_NES; ds.system[systemID++] = DIV_SYSTEM_NES;
ds.systemFlags[0].set("resetSweep",true); // FamiTracker behavior
if (pal) { if (pal) {
ds.systemFlags[0].set("clockSel", 1); // PAL clock ds.systemFlags[0].set("clockSel", 1); // PAL clock
@ -1187,7 +1190,8 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
} }
unsigned int wave_count = reader.readI(); unsigned int wave_count = reader.readI();
size_t waveOff = ds.wave.size(); n163WaveOff[insIndex] = ds.wave.size();
ins->n163.wave = n163WaveOff[insIndex];
if (wave_size>256) { if (wave_size>256) {
logE("wave size %d out of range",wave_size); logE("wave size %d out of range",wave_size);
@ -1214,17 +1218,6 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
} }
} }
// offset wave macro
if (ins->std.waveMacro.len == 0) // empty wave macro
{
ins->std.waveMacro.len = 1;
ins->std.waveMacro.val[0] = waveOff;
} else {
for (int p=0; p<ins->std.waveMacro.len; p++) {
ins->std.waveMacro.val[p] += waveOff;
}
}
break; break;
} }
@ -1945,7 +1938,7 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
for (int v = 0; v < 8; v++) { for (int v = 0; v < 8; v++) {
if (map_channels[ch] == n163_chans[v]) { if (map_channels[ch] == n163_chans[v]) {
if (pat->data[row][4 + (j * 2)] == 0x12) { if (pat->data[row][4 + (j * 2)] == 0x12) {
pat->data[row][4 + (j * 2)] = 0x10; // TODO: map wave pat->data[row][4 + (j * 2)] = 0x110; // N163 wave change (we'll map this later)
} }
} }
} }
@ -2773,6 +2766,46 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len, bool dnft, bool dnft_si
if (i->virtualTempoD<1) i->virtualTempoD=1; if (i->virtualTempoD<1) i->virtualTempoD=1;
} }
// offset N163 wave macros (local -> global wave conversion)
for (size_t i=0; i<ds.ins.size(); i++) {
DivInstrument* ins=ds.ins[i];
int waveOff=n163WaveOff[i];
if (ins->type==DIV_INS_N163) {
for (int j=0; j<ins->std.waveMacro.len; j++) {
ins->std.waveMacro.val[j]+=waveOff;
}
}
}
// offset N163 wave change effects whether possible
for (DivSubSong* i: ds.subsong) {
for (int j=0; j<total_chans; j++) {
int curWaveOff=0;
for (int k=0; k<i->ordersLen; k++) {
DivPattern* p=i->pat[j].getPattern(i->orders.ord[j][k],true);
for (int l=0; l<i->patLen; l++) {
// check for instrument change
if (p->data[l][2]!=-1) {
curWaveOff=n163WaveOff[p->data[l][2]&127];
}
// check effect columns for 0x110 (dummy wave change)
for (int m=0; m<i->pat[j].effectCols; m++) {
if (p->data[l][4+(m<<1)]==0x110) {
// map wave
p->data[l][4+(m<<1)]=0x10;
if (p->data[l][5+(m<<1)]==-1) {
p->data[l][5+(m<<1)]=curWaveOff&0xff;
} else {
p->data[l][5+(m<<1)]=(p->data[l][5+(m<<1)]+curWaveOff)&0xff;
}
}
}
}
}
}
}
if (active) quitDispatch(); if (active) quitDispatch();
BUSY_BEGIN_SOFT; BUSY_BEGIN_SOFT;
saveLock.lock(); saveLock.lock();

View file

@ -92,23 +92,14 @@ void DivEngine::loadPPS(SafeReader& reader, std::vector<DivSample*>& ret, String
s->rate = PPS_SAMPLE_RATE; s->rate = PPS_SAMPLE_RATE;
s->centerRate = PPS_SAMPLE_RATE; s->centerRate = PPS_SAMPLE_RATE;
s->depth = DIV_SAMPLE_DEPTH_8BIT; s->depth = DIV_SAMPLE_DEPTH_4BIT;
s->init(headers[i].sample_length * 2); //byte per sample s->init(headers[i].sample_length*2); // bytes->samples
reader.seek((int)headers[i].start_pointer, SEEK_SET); reader.seek((int)headers[i].start_pointer, SEEK_SET);
int sample_pos = 0;
for(int j = 0; j < headers[i].sample_length; j++) for(int j = 0; j < headers[i].sample_length; j++)
{ {
unsigned char curr_byte = (unsigned char)reader.readC(); s->data4[j] = reader.readC();
s->data8[sample_pos] = (curr_byte >> 4) | (curr_byte & 0xf0);
s->data8[sample_pos] += 0x80;
sample_pos++;
s->data8[sample_pos] = (curr_byte << 4) | (curr_byte & 0xf);
s->data8[sample_pos] += 0x80;
sample_pos++;
} }
ret.push_back(s); ret.push_back(s);

View file

@ -126,6 +126,13 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) {
w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type)); w->writeText(fmt::sprintf("- type: %d\n",(int)ins->type));
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPL_DRUMS || ins->type==DIV_INS_OPM || ins->type==DIV_INS_ESFM) {
int opCount=4;
if (ins->type==DIV_INS_OPLL) {
opCount=2;
} else if (ins->type==DIV_INS_OPL) {
opCount=(ins->fm.ops==4)?4:2;
}
w->writeText("- FM parameters:\n"); w->writeText("- FM parameters:\n");
w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg)); w->writeText(fmt::sprintf(" - ALG: %d\n",ins->fm.alg));
w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb)); w->writeText(fmt::sprintf(" - FB: %d\n",ins->fm.fb));
@ -133,14 +140,14 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) {
w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams)); w->writeText(fmt::sprintf(" - AMS: %d\n",ins->fm.ams));
w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2)); w->writeText(fmt::sprintf(" - FMS2: %d\n",ins->fm.fms2));
w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2)); w->writeText(fmt::sprintf(" - AMS2: %d\n",ins->fm.ams2));
w->writeText(fmt::sprintf(" - operators: %d\n",ins->fm.ops)); w->writeText(fmt::sprintf(" - operators: %d\n",opCount));
w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset)); w->writeText(fmt::sprintf(" - OPLL patch: %d\n",ins->fm.opllPreset));
w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0])); w->writeText(fmt::sprintf(" - fixed drum freq: %s\n",trueFalse[ins->fm.fixedDrums?1:0]));
w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq)); w->writeText(fmt::sprintf(" - kick freq: %.4X\n",ins->fm.kickFreq));
w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq)); w->writeText(fmt::sprintf(" - snare/hat freq: %.4X\n",ins->fm.snareHatFreq));
w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq)); w->writeText(fmt::sprintf(" - tom/top freq: %.4X\n",ins->fm.tomTopFreq));
for (int j=0; j<ins->fm.ops; j++) { for (int j=0; j<opCount; j++) {
DivInstrumentFM::Operator& op=ins->fm.op[j]; DivInstrumentFM::Operator& op=ins->fm.op[j];
w->writeText(fmt::sprintf(" - operator %d:\n",j)); w->writeText(fmt::sprintf(" - operator %d:\n",j));
@ -173,7 +180,7 @@ SafeWriter* DivEngine::saveText(bool separatePatterns) {
w->writeText("- ESFM parameters:\n"); w->writeText("- ESFM parameters:\n");
w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise)); w->writeText(fmt::sprintf(" - noise mode: %d\n",ins->esfm.noise));
for (int j=0; j<ins->fm.ops; j++) { for (int j=0; j<4; j++) {
DivInstrumentESFM::Operator& opE=ins->esfm.op[j]; DivInstrumentESFM::Operator& opE=ins->esfm.op[j];
w->writeText(fmt::sprintf(" - operator %d:\n",j)); w->writeText(fmt::sprintf(" - operator %d:\n",j));

View file

@ -313,12 +313,18 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
logD("sample is 32-bit float"); logD("sample is 32-bit float");
buf=new float[si.channels*si.frames]; buf=new float[si.channels*si.frames];
sampleLen=sizeof(float); sampleLen=sizeof(float);
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_DOUBLE) {
logD("sample is 64-bit float");
buf=new float[si.channels*si.frames];
sampleLen=sizeof(double);
} else { } else {
logD("sample is 16-bit signed"); logD("sample is 16-bit signed");
buf=new short[si.channels*si.frames]; buf=new short[si.channels*si.frames];
sampleLen=sizeof(short); sampleLen=sizeof(short);
} }
if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8 || (si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT) { if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8 ||
(si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_FLOAT ||
(si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_DOUBLE) {
if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) { if (sf_read_raw(f,buf,si.frames*si.channels*sampleLen)!=(si.frames*si.channels*sampleLen)) {
logW("sample read size mismatch!"); logW("sample read size mismatch!");
} }
@ -361,6 +367,19 @@ std::vector<DivSample*> DivEngine::sampleFromFile(const char* path) {
sample->data16[index++]=averaged; sample->data16[index++]=averaged;
} }
delete[] (float*)buf; delete[] (float*)buf;
} else if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_DOUBLE) {
for (int i=0; i<si.frames*si.channels; i+=si.channels) {
double averaged=0.0f;
for (int j=0; j<si.channels; j++) {
averaged+=((double*)buf)[i+j];
}
averaged/=si.channels;
averaged*=32767.0;
if (averaged<-32768.0) averaged=-32768.0;
if (averaged>32767.0) averaged=32767.0;
sample->data16[index++]=averaged;
}
delete[] (double*)buf;
} else { } else {
for (int i=0; i<si.frames*si.channels; i+=si.channels) { for (int i=0; i<si.frames*si.channels; i+=si.channels) {
int averaged=0; int averaged=0;
@ -505,6 +524,7 @@ DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth,
case DIV_SAMPLE_DEPTH_ADPCM_B: case DIV_SAMPLE_DEPTH_ADPCM_B:
case DIV_SAMPLE_DEPTH_ADPCM_K: case DIV_SAMPLE_DEPTH_ADPCM_K:
case DIV_SAMPLE_DEPTH_VOX: case DIV_SAMPLE_DEPTH_VOX:
case DIV_SAMPLE_DEPTH_4BIT:
samples=lenDivided*2; samples=lenDivided*2;
break; break;
case DIV_SAMPLE_DEPTH_IMA_ADPCM: case DIV_SAMPLE_DEPTH_IMA_ADPCM:
@ -617,6 +637,7 @@ DivSample* DivEngine::sampleFromFileRaw(const char* path, DivSampleDepth depth,
case DIV_SAMPLE_DEPTH_ADPCM_B: case DIV_SAMPLE_DEPTH_ADPCM_B:
case DIV_SAMPLE_DEPTH_ADPCM_K: case DIV_SAMPLE_DEPTH_ADPCM_K:
case DIV_SAMPLE_DEPTH_VOX: case DIV_SAMPLE_DEPTH_VOX:
case DIV_SAMPLE_DEPTH_4BIT:
// swap nibbles // swap nibbles
for (unsigned int i=0; i<sample->getCurBufLen(); i++) { for (unsigned int i=0; i<sample->getCurBufLen(); i++) {
b[i]=(b[i]<<4)|(b[i]>>4); b[i]=(b[i]<<4)|(b[i]>>4);

View file

@ -748,7 +748,7 @@ void DivPlatformES5506::tick(bool sysTick) {
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
if (amigaPitch && parent->song.linearPitch!=2) { if (amigaPitch && parent->song.linearPitch!=2) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch*16,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,2,chan[i].pitch2*16,16*COLOR_NTSC,chan[i].pcm.freqOffs); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch*16,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,2,chan[i].pitch2*16,16*COLOR_NTSC,chan[i].pcm.freqOffs);
chan[i].freq=524288*(COLOR_NTSC/chan[i].freq)/(chipClock/32.0); chan[i].freq=PITCH_OFFSET*(COLOR_NTSC/chan[i].freq)/(chipClock/16.0);
chan[i].freq=CLAMP(chan[i].freq,0,0x1ffff); chan[i].freq=CLAMP(chan[i].freq,0,0x1ffff);
} else { } else {
chan[i].freq=CLAMP(parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,chan[i].pcm.freqOffs),0,0x1ffff); chan[i].freq=CLAMP(parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,chan[i].pcm.freqOffs),0,0x1ffff);

View file

@ -79,8 +79,10 @@ class DivPlatformFMBase: public DivDispatch {
unsigned int addr; unsigned int addr;
unsigned short val; unsigned short val;
bool addrOrVal; bool addrOrVal;
QueuedWrite(): addr(0), val(0), addrOrVal(false) {} bool urgent;
QueuedWrite(unsigned int a, unsigned char v): addr(a), val(v), addrOrVal(false) {} QueuedWrite(): addr(0), val(0), addrOrVal(false), urgent(false) {}
QueuedWrite(unsigned int a, unsigned char v): addr(a), val(v), addrOrVal(false), urgent(false) {}
QueuedWrite(unsigned int a, unsigned char v, bool u): addr(a), val(v), addrOrVal(false), urgent(u) {}
}; };
FixedQueue<QueuedWrite,2048> writes; FixedQueue<QueuedWrite,2048> writes;
@ -108,14 +110,7 @@ class DivPlatformFMBase: public DivDispatch {
// only used by OPN2 for DAC writes // only used by OPN2 for DAC writes
inline void urgentWrite(unsigned short a, unsigned char v) { inline void urgentWrite(unsigned short a, unsigned char v) {
if (!skipRegisterWrites && !flushFirst) { if (!skipRegisterWrites && !flushFirst) {
if (!writes.empty()) { writes.push_front(QueuedWrite(a,v,true));
// check for hard reset
if (writes.front().addr==0xf0) {
// replace hard reset with DAC write
writes.pop_front();
}
}
writes.push_front(QueuedWrite(a,v));
if (dumpWrites) { if (dumpWrites) {
addWrite(a,v); addWrite(a,v);
} }

View file

@ -150,17 +150,34 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
os[0]=0; os[1]=0; os[0]=0; os[1]=0;
for (int i=0; i<6; i++) { for (int i=0; i<6; i++) {
if (delay<=0 && !writes.empty()) { if (!writes.empty()) {
QueuedWrite& w=writes.front(); QueuedWrite& w=writes.front();
if (w.addr==0xfffffffe) { if (delay<=0 || w.urgent) {
delay=w.val*3; if (w.addr==0xfffffffe) {
writes.pop_front(); delay=w.val*3;
} else if (w.addrOrVal) { writes.pop_front();
//logV("%.3x=%.2x",w.addr,w.val); } else if (w.addrOrVal) {
OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val); //logV("%.3x=%.2x",w.addr,w.val);
regPool[w.addr&0x1ff]=w.val; OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val);
writes.pop_front(); regPool[w.addr&0x1ff]=w.val;
writes.pop_front();
if (dacWrite>=0) {
if (!canWriteDAC) {
canWriteDAC=true;
} else {
urgentWrite(0x2a,dacWrite);
dacWrite=-1;
canWriteDAC=writes.empty();
}
}
} else {
if (fm.write_busy==0) {
OPN2_Write(&fm,0x0+((w.addr>>8)<<1),w.addr);
w.addrOrVal=true;
}
}
} else {
if (dacWrite>=0) { if (dacWrite>=0) {
if (!canWriteDAC) { if (!canWriteDAC) {
canWriteDAC=true; canWriteDAC=true;
@ -170,11 +187,6 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
canWriteDAC=writes.empty(); canWriteDAC=writes.empty();
} }
} }
} else {
if (fm.write_busy==0) {
OPN2_Write(&fm,0x0+((w.addr>>8)<<1),w.addr);
w.addrOrVal=true;
}
} }
} else { } else {
canWriteDAC=true; canWriteDAC=true;
@ -244,24 +256,36 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) {
if (delay>0) delay--; if (delay>0) delay--;
os[0]=0; os[1]=0; os[0]=0; os[1]=0;
if (delay<=0 && !writes.empty()) { if (!writes.empty()) {
QueuedWrite& w=writes.front(); QueuedWrite& w=writes.front();
if (w.addr==0xfffffffe) { if (delay<=0 || w.urgent) {
delay=w.val; if (w.addr==0xfffffffe) {
} else { delay=w.val;
fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
regPool[w.addr&0x1ff]=w.val;
}
writes.pop_front();
if (dacWrite>=0) {
if (!canWriteDAC) {
canWriteDAC=true;
} else { } else {
urgentWrite(0x2a,dacWrite); fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
dacWrite=-1; fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
canWriteDAC=writes.empty(); regPool[w.addr&0x1ff]=w.val;
}
writes.pop_front();
if (dacWrite>=0) {
if (!canWriteDAC) {
canWriteDAC=true;
} else {
urgentWrite(0x2a,dacWrite);
dacWrite=-1;
canWriteDAC=writes.empty();
}
}
} else {
if (dacWrite>=0) {
if (!canWriteDAC) {
canWriteDAC=true;
} else {
urgentWrite(0x2a,dacWrite);
dacWrite=-1;
canWriteDAC=writes.empty();
}
} }
} }
} else { } else {
@ -402,55 +426,89 @@ void DivPlatformGenesis::acquire_nuked276(short** buf, size_t len) {
if (delay>0) delay--; if (delay>0) delay--;
if (delay<=0 && !writes.empty()) { if (!writes.empty()) {
QueuedWrite& w=writes.front(); QueuedWrite& w=writes.front();
if (w.addr==0xfffffffe) { if (delay<=0 || w.urgent) {
delay=w.val; if (w.addr==0xfffffffe) {
writes.pop_front(); delay=w.val;
} else if (w.addrOrVal) { writes.pop_front();
//logV("%.3x=%.2x",w.addr,w.val); } else if (w.addrOrVal) {
//OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val); //logV("%.3x=%.2x",w.addr,w.val);
was_reg_write=true; //OPN2_Write(&fm,0x1+((w.addr>>8)<<1),w.val);
was_reg_write=true;
fm_276.input.address=w.addr<0x100?0:2; fm_276.input.address=w.addr<0x100?0:2;
fm_276.input.data=w.addr&0xff; fm_276.input.data=w.addr&0xff;
fm_276.input.wr=1; fm_276.input.wr=1;
FMOPN2_Clock(&fm_276,0);
sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r;
acquire276OscSub(h);
fm_276.input.wr=0;
FMOPN2_Clock(&fm_276,1);
sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r;
acquire276OscSub(h);
if (chipType==2) {
if (!o_bco && fm_276.o_bco) {
dacShifter=(dacShifter<<1)|fm_276.o_so;
if (o_lro!=fm_276.o_lro) {
if (o_lro)
sample_l=dacShifter;
else
sample_r=dacShifter;
}
o_lro=fm_276.o_lro;
}
o_bco=fm_276.o_bco;
}
for (int c=0; c<17; c++) {
FMOPN2_Clock(&fm_276,0); FMOPN2_Clock(&fm_276,0);
sum_l+=fm_276.out_l; sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r; sum_r+=fm_276.out_r;
acquire276OscSub(h); acquire276OscSub(h);
fm_276.input.wr=0;
FMOPN2_Clock(&fm_276,1);
sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r;
acquire276OscSub(h);
if (chipType==2) {
if (!o_bco && fm_276.o_bco) {
dacShifter=(dacShifter<<1)|fm_276.o_so;
if (o_lro!=fm_276.o_lro) {
if (o_lro)
sample_l=dacShifter;
else
sample_r=dacShifter;
}
o_lro=fm_276.o_lro;
}
o_bco=fm_276.o_bco;
}
for (int c=0; c<17; c++) {
FMOPN2_Clock(&fm_276,0);
sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r;
acquire276OscSub(h);
FMOPN2_Clock(&fm_276,1);
sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r;
acquire276OscSub(h);
if (chipType==2) {
if (!o_bco && fm_276.o_bco) {
dacShifter=(dacShifter<<1)|fm_276.o_so;
if (o_lro!=fm_276.o_lro) {
if (o_lro)
sample_l=dacShifter;
else
sample_r=dacShifter;
}
o_lro=fm_276.o_lro;
}
o_bco=fm_276.o_bco;
}
}
fm_276.input.address=w.addr<0x100?1:3;
fm_276.input.data=w.val;
fm_276.input.wr=1;
FMOPN2_Clock(&fm_276,0);
sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r;
fm_276.input.wr=0;
acquire276OscSub(h);
FMOPN2_Clock(&fm_276,1); FMOPN2_Clock(&fm_276,1);
sum_l+=fm_276.out_l; sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r; sum_r+=fm_276.out_r;
@ -472,74 +530,54 @@ void DivPlatformGenesis::acquire_nuked276(short** buf, size_t len) {
} }
o_bco=fm_276.o_bco; o_bco=fm_276.o_bco;
} }
}
fm_276.input.address=w.addr<0x100?1:3; for (int c=0; c<83; c++) {
fm_276.input.data=w.val; FMOPN2_Clock(&fm_276,0);
fm_276.input.wr=1; sum_l+=fm_276.out_l;
FMOPN2_Clock(&fm_276,0); sum_r+=fm_276.out_r;
sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r;
fm_276.input.wr=0;
acquire276OscSub(h); acquire276OscSub(h);
FMOPN2_Clock(&fm_276,1); FMOPN2_Clock(&fm_276,1);
sum_l+=fm_276.out_l; sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r; sum_r+=fm_276.out_r;
acquire276OscSub(h); acquire276OscSub(h);
if (chipType==2) { if (chipType==2) {
if (!o_bco && fm_276.o_bco) { if (!o_bco && fm_276.o_bco) {
dacShifter=(dacShifter<<1)|fm_276.o_so; dacShifter=(dacShifter<<1)|fm_276.o_so;
if (o_lro!=fm_276.o_lro) { if (o_lro!=fm_276.o_lro) {
if (o_lro) if (o_lro) {
sample_l=dacShifter; sample_l=dacShifter;
else } else {
sample_r=dacShifter; sample_r=dacShifter;
} }
o_lro=fm_276.o_lro;
}
o_bco=fm_276.o_bco;
}
for (int c=0; c<83; c++) {
FMOPN2_Clock(&fm_276,0);
sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r;
acquire276OscSub(h);
FMOPN2_Clock(&fm_276,1);
sum_l+=fm_276.out_l;
sum_r+=fm_276.out_r;
acquire276OscSub(h);
if (chipType==2) {
if (!o_bco && fm_276.o_bco) {
dacShifter=(dacShifter<<1)|fm_276.o_so;
if (o_lro!=fm_276.o_lro) {
if (o_lro) {
sample_l=dacShifter;
} else {
sample_r=dacShifter;
} }
o_lro=fm_276.o_lro;
} }
o_bco=fm_276.o_bco;
o_lro=fm_276.o_lro;
} }
o_bco=fm_276.o_bco;
} }
regPool[w.addr&0x1ff]=w.val;
writes.pop_front();
if (dacWrite>=0) {
if (!canWriteDAC) {
canWriteDAC=true;
} else {
urgentWrite(0x2a,dacWrite);
dacWrite=-1;
canWriteDAC=writes.empty();
}
}
} else {
w.addrOrVal=true;
} }
} else {
regPool[w.addr&0x1ff]=w.val;
writes.pop_front();
if (dacWrite>=0) { if (dacWrite>=0) {
if (!canWriteDAC) { if (!canWriteDAC) {
canWriteDAC=true; canWriteDAC=true;
@ -549,8 +587,6 @@ void DivPlatformGenesis::acquire_nuked276(short** buf, size_t len) {
canWriteDAC=writes.empty(); canWriteDAC=writes.empty();
} }
} }
} else {
w.addrOrVal=true;
} }
} else { } else {
canWriteDAC=true; canWriteDAC=true;

View file

@ -352,9 +352,6 @@ void DivPlatformNES::tick(bool sysTick) {
} }
if (chan[i].sweepChanged) { if (chan[i].sweepChanged) {
chan[i].sweepChanged=false; chan[i].sweepChanged=false;
if (i==0) {
// rWrite(16+i*5,chan[i].sweep);
}
} }
if (i<3) if (chan[i].std.phaseReset.had) { if (i<3) if (chan[i].std.phaseReset.had) {
if (chan[i].std.phaseReset.val==1) { if (chan[i].std.phaseReset.val==1) {
@ -637,6 +634,13 @@ int DivPlatformNES::dispatch(DivCommand c) {
} else if (!parent->song.brokenOutVol2) { } else if (!parent->song.brokenOutVol2) {
rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
} }
if (resetSweep && c.chan<2) {
if (chan[c.chan].sweep!=0x08 && !chan[c.chan].sweepChanged) {
chan[c.chan].sweep=0x08;
chan[c.chan].prevFreq=-1;
rWrite(0x4001+(c.chan*4),chan[c.chan].sweep);
}
}
break; break;
case DIV_CMD_NOTE_OFF: case DIV_CMD_NOTE_OFF:
if (c.chan==4) { if (c.chan==4) {
@ -724,6 +728,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
} }
} }
rWrite(0x4001+(c.chan*4),chan[c.chan].sweep); rWrite(0x4001+(c.chan*4),chan[c.chan].sweep);
chan[c.chan].sweepChanged=true;
break; break;
case DIV_CMD_NES_ENV_MODE: case DIV_CMD_NES_ENV_MODE:
chan[c.chan].envMode=c.value&3; chan[c.chan].envMode=c.value&3;
@ -989,6 +994,7 @@ void DivPlatformNES::setFlags(const DivConfig& flags) {
} }
dpcmModeDefault=flags.getBool("dpcmMode",true); dpcmModeDefault=flags.getBool("dpcmMode",true);
resetSweep=flags.getBool("resetSweep",false);
} }
void DivPlatformNES::notifyInsDeletion(void* ins) { void DivPlatformNES::notifyInsDeletion(void* ins) {

View file

@ -68,6 +68,7 @@ class DivPlatformNES: public DivDispatch {
signed char lastDPCMFreq; signed char lastDPCMFreq;
bool dpcmMode; bool dpcmMode;
bool dpcmModeDefault; bool dpcmModeDefault;
bool resetSweep;
bool dacAntiClickOn; bool dacAntiClickOn;
bool useNP; bool useNP;
bool goingToLoop; bool goingToLoop;

View file

@ -1338,7 +1338,7 @@ void DivPlatformOPL::tick(bool sysTick) {
unsigned char slot=slots[j][i]; unsigned char slot=slots[j][i];
if (slot==255) continue; if (slot==255) continue;
unsigned short baseAddr=slotMap[slot]; unsigned short baseAddr=slotMap[slot];
if (baseAddr>0x100) { if (baseAddr>=0x100) {
weWillWriteRRLater[(baseAddr&0xff)|32]=true; weWillWriteRRLater[(baseAddr&0xff)|32]=true;
} else { } else {
weWillWriteRRLater[(baseAddr&0xff)]=true; weWillWriteRRLater[(baseAddr&0xff)]=true;
@ -1464,38 +1464,43 @@ void DivPlatformOPL::tick(bool sysTick) {
chan[i].freqL=(chan[i].freq>>chan[i].freqH)&0x3ff; chan[i].freqL=(chan[i].freq>>chan[i].freqH)&0x3ff;
chan[i].freqH=8^chan[i].freqH; chan[i].freqH=8^chan[i].freqH;
ctrl|=(chan[i].active?0x80:0)|(chan[i].damp?0x40:0)|(chan[i].lfoReset?0x20:0)|(chan[i].ch?0x10:0)|(isMuted[i]?8:(chan[i].pan&0xf)); ctrl|=(chan[i].active?0x80:0)|(chan[i].damp?0x40:0)|(chan[i].lfoReset?0x20:0)|(chan[i].ch?0x10:0)|(isMuted[i]?8:(chan[i].pan&0xf));
unsigned int waveNum=chan[i].sample; int waveNum=chan[i].sample;
if (ramSize<=0x200000) { if (waveNum>=0) {
waveNum=CLAMP(waveNum,0,0x7f)|0x180; if (ramSize<=0x200000) {
} waveNum=CLAMP(waveNum,0,0x7f)|0x180;
if (chan[i].keyOn) {
immWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80); // force keyoff first
immWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1));
immWrite(PCM_ADDR_WAVE_L+PCM_REG(i),waveNum&0xff);
immWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib));
immWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r));
immWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r));
immWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr));
immWrite(PCM_ADDR_AM+PCM_REG(i),chan[i].am);
if (!chan[i].std.vol.had) {
chan[i].outVol=chan[i].vol;
immWrite(PCM_ADDR_TL+(PCM_REG(i)),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0));
} }
chan[i].writeCtrl=true; if (chan[i].keyOn) {
chan[i].keyOn=false; immWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80); // force keyoff first
} immWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1));
if (chan[i].keyOff) { immWrite(PCM_ADDR_WAVE_L+PCM_REG(i),waveNum&0xff);
chan[i].writeCtrl=true; immWrite(PCM_ADDR_LFO_VIB+PCM_REG(i),(chan[i].lfo<<3)|(chan[i].vib));
chan[i].keyOff=false; immWrite(PCM_ADDR_AR_D1R+PCM_REG(i),(chan[i].ar<<4)|(chan[i].d1r));
} immWrite(PCM_ADDR_DL_D2R+PCM_REG(i),(chan[i].dl<<4)|(chan[i].d2r));
if (chan[i].freqChanged) { immWrite(PCM_ADDR_RC_RR+PCM_REG(i),(chan[i].rc<<4)|(chan[i].rr));
immWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1)); immWrite(PCM_ADDR_AM+PCM_REG(i),chan[i].am);
immWrite(PCM_ADDR_FN_H_PR_OCT+PCM_REG(i),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7)); if (!chan[i].std.vol.had) {
chan[i].freqChanged=false; chan[i].outVol=chan[i].vol;
} immWrite(PCM_ADDR_TL+(PCM_REG(i)),((0x7f-chan[i].outVol)<<1)|(chan[i].levelDirect?1:0));
if (chan[i].writeCtrl) { }
immWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl); chan[i].writeCtrl=true;
chan[i].writeCtrl=false; chan[i].keyOn=false;
}
if (chan[i].keyOff) {
chan[i].writeCtrl=true;
chan[i].keyOff=false;
}
if (chan[i].freqChanged) {
immWrite(PCM_ADDR_WAVE_H_FN_L+PCM_REG(i),((chan[i].freqL&0x7f)<<1)|((waveNum>>8)&1));
immWrite(PCM_ADDR_FN_H_PR_OCT+PCM_REG(i),((chan[i].freqH&0xf)<<4)|(chan[i].pseudoReverb?0x08:0x00)|((chan[i].freqL>>7)&0x7));
chan[i].freqChanged=false;
}
if (chan[i].writeCtrl) {
immWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl);
chan[i].writeCtrl=false;
}
} else {
// cut if we don't have a sample
immWrite(PCM_ADDR_KEY_DAMP_LFORST_CH_PAN+PCM_REG(i),ctrl&~0x80);
} }
} }
} else { } else {

View file

@ -202,81 +202,109 @@ void DivPlatformOPLL::tick(bool sysTick) {
} }
} }
if (chan[i].state.opllPreset==0) { if (chan[i].std.alg.had) { // SUS
if (chan[i].std.alg.had) { // SUS chan[i].state.alg=chan[i].std.alg.val;
chan[i].state.alg=chan[i].std.alg.val; if (chan[i].state.opllPreset==0) {
chan[i].freqChanged=true; chan[i].freqChanged=true;
} }
if (chan[i].std.fb.had) { }
chan[i].state.fb=chan[i].std.fb.val; if (chan[i].std.fb.had) {
chan[i].state.fb=chan[i].std.fb.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb);
} }
if (chan[i].std.fms.had) { }
chan[i].state.fms=chan[i].std.fms.val; if (chan[i].std.fms.had) {
chan[i].state.fms=chan[i].std.fms.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb);
} }
if (chan[i].std.ams.had) { }
chan[i].state.ams=chan[i].std.ams.val; if (chan[i].std.ams.had) {
chan[i].state.ams=chan[i].std.ams.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb);
} }
}
for (int j=0; j<2; j++) { for (int j=0; j<2; j++) {
DivInstrumentFM::Operator& op=chan[i].state.op[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j];
DivMacroInt::IntOp& m=chan[i].std.op[j]; DivMacroInt::IntOp& m=chan[i].std.op[j];
if (m.am.had) { if (m.am.had) {
op.am=m.am.val; op.am=m.am.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult));
} }
if (m.ar.had) { }
op.ar=m.ar.val; if (m.ar.had) {
op.ar=m.ar.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x04+j,(op.ar<<4)|(op.dr)); rWrite(0x04+j,(op.ar<<4)|(op.dr));
} }
if (m.dr.had) { }
op.dr=m.dr.val; if (m.dr.had) {
op.dr=m.dr.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x04+j,(op.ar<<4)|(op.dr)); rWrite(0x04+j,(op.ar<<4)|(op.dr));
} }
if (m.mult.had) { }
op.mult=m.mult.val; if (m.mult.had) {
op.mult=m.mult.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult));
} }
if (m.rr.had) { }
op.rr=m.rr.val; if (m.rr.had) {
op.rr=m.rr.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x06+j,(op.sl<<4)|(op.rr)); rWrite(0x06+j,(op.sl<<4)|(op.rr));
} }
if (m.sl.had) { }
op.sl=m.sl.val; if (m.sl.had) {
op.sl=m.sl.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x06+j,(op.sl<<4)|(op.rr)); rWrite(0x06+j,(op.sl<<4)|(op.rr));
} }
if (m.tl.had) { }
op.tl=m.tl.val&((j==1)?15:63); if (m.tl.had) {
if (j==1) { op.tl=m.tl.val&((j==1)?15:63);
if (i<9) { if (j==1) {
rWrite(0x30+i,((15-VOL_SCALE_LOG_BROKEN(chan[i].outVol,15-chan[i].state.op[1].tl,15))&15)|(chan[i].state.opllPreset<<4)); if (i<9) {
} rWrite(0x30+i,((15-VOL_SCALE_LOG_BROKEN(chan[i].outVol,15-chan[i].state.op[1].tl,15))&15)|(chan[i].state.opllPreset<<4));
} else { }
} else {
if (chan[i].state.opllPreset==0) {
rWrite(0x02,(chan[i].state.op[0].ksl<<6)|(op.tl&63)); rWrite(0x02,(chan[i].state.op[0].ksl<<6)|(op.tl&63));
} }
} }
}
if (m.egt.had) { if (m.egt.had) {
op.ssgEnv=(m.egt.val&1)?8:0; op.ssgEnv=(m.egt.val&1)?8:0;
if (chan[i].state.opllPreset==0) {
rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult));
} }
if (m.ksl.had) { }
op.ksl=m.ksl.val; if (m.ksl.had) {
op.ksl=m.ksl.val;
if (chan[i].state.opllPreset==0) {
if (j==1) { if (j==1) {
rWrite(0x02,(chan[i].state.op[0].ksl<<6)|(chan[i].state.op[0].tl&63)); rWrite(0x02,(chan[i].state.op[0].ksl<<6)|(chan[i].state.op[0].tl&63));
} else { } else {
rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb);
} }
} }
if (m.ksr.had) { }
op.ksr=m.ksr.val; if (m.ksr.had) {
op.ksr=m.ksr.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult));
} }
if (m.vib.had) { }
op.vib=m.vib.val; if (m.vib.had) {
op.vib=m.vib.val;
if (chan[i].state.opllPreset==0) {
rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult));
} }
} }
@ -390,6 +418,7 @@ int DivPlatformOPLL::toFreq(int freq, int fixedBlock) {
block=freq/OPLL_C_NUM; block=freq/OPLL_C_NUM;
if (block>0) block=bsr(block); if (block>0) block=bsr(block);
} }
if (block>7) block=7;
freq>>=block; freq>>=block;
if (freq>0x1ff) freq=0x1ff; if (freq>0x1ff) freq=0x1ff;
return (block<<9)|freq; return (block<<9)|freq;

View file

@ -72,11 +72,19 @@ void sm8521_noise_tick(struct sm8521_noise_t *noise, const int cycle)
noise->base.counter += cycle; noise->base.counter += cycle;
while (noise->base.counter >= (noise->base.t + 1)) while (noise->base.counter >= (noise->base.t + 1))
{ {
// https://github.com/tildearrow/furnace/issues/2567
// unknown algorithm, but don't use rand() // unknown algorithm, but don't use rand()
//
// some research suggests VIC-like noise, although
// that remains to be confirmed
noise->oldLFSR = noise->lfsr & 1;
noise->lfsr = ( noise->lfsr>>1|(((noise->lfsr) ^ (noise->lfsr >> 5) ^ (noise->lfsr >> 8) ^ (noise->lfsr >> 13) ) & 1)<<31); noise->lfsr = ( noise->lfsr>>1|(((noise->lfsr) ^ (noise->lfsr >> 5) ^ (noise->lfsr >> 8) ^ (noise->lfsr >> 13) ) & 1)<<31);
noise->base.counter -= (noise->base.t + 1); noise->base.counter -= (noise->base.t + 1);
if (noise->oldLFSR^(noise->lfsr&1)) {
noise->out ^= 1;
}
} }
noise->base.out = (((noise->lfsr & 0x1) ? 7 : -8) * noise->base.level) >> 1; // scale out to 8bit noise->base.out = ((noise->out ? 7 : -8) * noise->base.level) >> 1; // scale out to 8bit
} }
void sm8521_sound_tick(struct sm8521_t *sm8521, const int cycle) void sm8521_sound_tick(struct sm8521_t *sm8521, const int cycle)
@ -130,6 +138,8 @@ void sm8521_reset(struct sm8521_t *sm8521)
sm8521->noise.base.out = 0; sm8521->noise.base.out = 0;
sm8521->noise.base.counter = 0; sm8521->noise.base.counter = 0;
sm8521->noise.lfsr = 0x89abcdef; sm8521->noise.lfsr = 0x89abcdef;
sm8521->noise.oldLFSR = 1;
sm8521->noise.out = 0;
sm8521->out = 0; sm8521->out = 0;
sm8521->sgda = 0; sm8521->sgda = 0;
sm8521->sgc = 0; sm8521->sgc = 0;

View file

@ -65,7 +65,8 @@ struct sm8521_wave_t
struct sm8521_noise_t struct sm8521_noise_t
{ {
struct sm8521_sg_t base; struct sm8521_sg_t base;
unsigned int lfsr; // LFSR unsigned int lfsr, oldLFSR; // LFSR
unsigned char out;
}; };
struct sm8521_t struct sm8521_t

View file

@ -51,6 +51,8 @@ void VSU::Power(void)
SweepControl = 0; SweepControl = 0;
SweepModCounter = 0; SweepModCounter = 0;
SweepModClockDivider = 1; SweepModClockDivider = 1;
ModState = 0;
ModLock = 0;
for(int ch = 0; ch < 6; ch++) for(int ch = 0; ch < 6; ch++)
{ {
@ -62,7 +64,9 @@ void VSU::Power(void)
RAMAddress[ch] = 0; RAMAddress[ch] = 0;
EffFreq[ch] = 0; EffFreq[ch] = 0;
Envelope[ch] = 0; EnvelopeReload[ch] = 0;
EnvelopeValue[ch] = 0;
EnvelopeModMask[ch] = 0;
WavePos[ch] = 0; WavePos[ch] = 0;
FreqCounter[ch] = 1; FreqCounter[ch] = 1;
IntervalCounter[ch] = 0; IntervalCounter[ch] = 0;
@ -100,6 +104,8 @@ void VSU::Write(int timestamp, unsigned int A, unsigned char V)
Update(timestamp); Update(timestamp);
ModLock = 0;
//printf("VSU Write: %d, %08x %02x\n", timestamp, A, V); //printf("VSU Write: %d, %08x %02x\n", timestamp, A, V);
if(A < 0x280) if(A < 0x280)
@ -133,7 +139,6 @@ void VSU::Write(int timestamp, unsigned int A, unsigned char V)
if(V & 0x80) if(V & 0x80)
{ {
EffFreq[ch] = Frequency[ch];
if(ch == 5) if(ch == 5)
FreqCounter[ch] = 10 * (2048 - EffFreq[ch]); FreqCounter[ch] = 10 * (2048 - EffFreq[ch]);
else else
@ -146,15 +151,20 @@ void VSU::Write(int timestamp, unsigned int A, unsigned char V)
SweepModCounter = (SweepControl >> 4) & 7; SweepModCounter = (SweepControl >> 4) & 7;
SweepModClockDivider = (SweepControl & 0x80) ? 8 : 1; SweepModClockDivider = (SweepControl & 0x80) ? 8 : 1;
ModWavePos = 0; ModWavePos = 0;
ModState = 0;
} }
WavePos[ch] = 0; WavePos[ch] = 0;
if(ch == 5) // Not sure if this is correct. if(ch == 5) { // Not sure if this is correct.
lfsr = 1; lfsr = 1;
}
//if(!(IntlControl[ch] & 0x80)) EnvelopeModMask[ch] = 0;
// Envelope[ch] = (EnvControl[ch] >> 4) & 0xF; if(!(EnvControl[ch] & 0x200) && (
(EnvelopeValue[ch] == 0 && !(EnvControl[ch] & 0x0008)) ||
(EnvelopeValue[ch] == 0xF && (EnvControl[ch] & 0x0008))))
EnvelopeModMask[ch] = 1;
EffectsClockDivider[ch] = 4800; EffectsClockDivider[ch] = 4800;
IntervalClockDivider[ch] = 4; IntervalClockDivider[ch] = 4;
@ -167,21 +177,27 @@ void VSU::Write(int timestamp, unsigned int A, unsigned char V)
break; break;
case 0x2: Frequency[ch] &= 0xFF00; case 0x2: Frequency[ch] &= 0xFF00;
Frequency[ch] |= V << 0; Frequency[ch] |= V << 0;
EffFreq[ch] &= 0xFF00; EffFreq[ch] &= 0xFF00;
EffFreq[ch] |= V << 0; EffFreq[ch] |= V << 0;
ModLock = 1;
break; break;
case 0x3: Frequency[ch] &= 0x00FF; case 0x3: Frequency[ch] &= 0x00FF;
Frequency[ch] |= (V & 0x7) << 8; Frequency[ch] |= (V & 0x7) << 8;
EffFreq[ch] &= 0x00FF; EffFreq[ch] &= 0x00FF;
EffFreq[ch] |= (V & 0x7) << 8; EffFreq[ch] |= (V & 0x7) << 8;
ModLock = 2;
break; break;
case 0x4: EnvControl[ch] &= 0xFF00; case 0x4: EnvControl[ch] &= 0xFF00;
EnvControl[ch] |= V << 0; EnvControl[ch] |= V << 0;
Envelope[ch] = (V >> 4) & 0xF; EnvelopeReload[ch] = (V >> 4) & 0xF;
EnvelopeValue[ch] = (V >> 4) & 0xF;
if(EnvelopeModMask[ch] == 1)
EnvelopeModMask[ch] = 2;
break; break;
case 0x5: EnvControl[ch] &= 0x00FF; case 0x5: EnvControl[ch] &= 0x00FF;
@ -194,6 +210,12 @@ void VSU::Write(int timestamp, unsigned int A, unsigned char V)
} }
else else
EnvControl[ch] |= (V & 0x03) << 8; EnvControl[ch] |= (V & 0x03) << 8;
if(EnvelopeModMask[ch] == 0 && !(EnvControl[ch] & 0x200) && (
(EnvelopeValue[ch] == 0 && !(EnvControl[ch] & 0x0008)) ||
(EnvelopeValue[ch] == 0xF && (EnvControl[ch] & 0x0008))))
EnvelopeModMask[ch] = 1;
break; break;
case 0x6: RAMAddress[ch] = V & 0xF; case 0x6: RAMAddress[ch] = V & 0xF;
@ -228,14 +250,14 @@ inline void VSU::CalcCurrentOutput(int ch, int &left, int &right)
else else
WD = WaveData[RAMAddress[ch]][WavePos[ch]]; // - 0x20; WD = WaveData[RAMAddress[ch]][WavePos[ch]]; // - 0x20;
} }
l_ol = Envelope[ch] * LeftLevel[ch]; l_ol = EnvelopeValue[ch] * LeftLevel[ch];
if(l_ol) if(l_ol)
{ {
l_ol >>= 3; l_ol >>= 3;
l_ol += 1; l_ol += 1;
} }
r_ol = Envelope[ch] * RightLevel[ch]; r_ol = EnvelopeValue[ch] * RightLevel[ch];
if(r_ol) if(r_ol)
{ {
r_ol >>= 3; r_ol >>= 3;
@ -260,11 +282,11 @@ void VSU::Update(int timestamp)
CalcCurrentOutput(ch, left, right); CalcCurrentOutput(ch, left, right);
if (left!=last_output[ch][0]) { if (left!=last_output[ch][0]) {
blip_add_delta(bb[0],running_timestamp,left - last_output[ch][0]); blip_add_delta(bb[0],running_timestamp,left - last_output[ch][0]);
last_output[ch][0] = left; last_output[ch][0] = left;
} }
if (right!=last_output[ch][1]) { if (right!=last_output[ch][1]) {
blip_add_delta(bb[1],running_timestamp,right - last_output[ch][1]); blip_add_delta(bb[1],running_timestamp,right - last_output[ch][1]);
last_output[ch][1] = right; last_output[ch][1] = right;
} }
oscBuf[ch]->putSample(running_timestamp,(left+right)*8); oscBuf[ch]->putSample(running_timestamp,(left+right)*8);
@ -355,23 +377,27 @@ void VSU::Update(int timestamp)
{ {
EnvelopeClockDivider[ch] += 4; EnvelopeClockDivider[ch] += 4;
int new_envelope = EnvelopeValue[ch];
if(EnvelopeValue[ch] < 0xF && (EnvControl[ch] & 0x0008))
new_envelope++;
else if(EnvelopeValue[ch] > 0 && !(EnvControl[ch] & 0x0008))
new_envelope--;
else if((EnvControl[ch] & 0x200) && EnvelopeModMask[ch] != 2)
{
new_envelope = EnvelopeReload[ch];
EnvelopeModMask[ch] = 0;
}
else if(EnvelopeModMask[ch] == 0)
EnvelopeModMask[ch] = 1;
if(EnvControl[ch] & 0x0100) // Enveloping enabled? if(EnvControl[ch] & 0x0100) // Enveloping enabled?
{ {
EnvelopeCounter[ch]--; EnvelopeCounter[ch]--;
if(!EnvelopeCounter[ch]) if(!EnvelopeCounter[ch])
{ {
EnvelopeCounter[ch] = (EnvControl[ch] & 0x7) + 1; EnvelopeCounter[ch] = (EnvControl[ch] & 0x7) + 1;
if(EnvelopeModMask[ch] == 0)
if(EnvControl[ch] & 0x0008) // Grow EnvelopeValue[ch] = new_envelope;
{
if(Envelope[ch] < 0xF || (EnvControl[ch] & 0x200))
Envelope[ch] = (Envelope[ch] + 1) & 0xF;
}
else // Decay
{
if(Envelope[ch] > 0 || (EnvControl[ch] & 0x200))
Envelope[ch] = (Envelope[ch] - 1) & 0xF;
}
} }
} }
@ -380,6 +406,19 @@ void VSU::Update(int timestamp)
if(ch == 4) if(ch == 4)
{ {
// Calculate sweep early
int delta = EffFreq[ch] >> (SweepControl & 0x7);
int NewSweepFreq = EffFreq[ch] + ((SweepControl & 0x8) ? delta : -delta);
if(!(EnvControl[ch] & 0x1000))
{
if(NewSweepFreq < 0)
NewSweepFreq = 0;
else if(NewSweepFreq > 0x7FF)
IntlControl[ch] &= ~0x80;
}
SweepModClockDivider--; SweepModClockDivider--;
while(SweepModClockDivider <= 0) while(SweepModClockDivider <= 0)
{ {
@ -394,33 +433,30 @@ void VSU::Update(int timestamp)
{ {
SweepModCounter = (SweepControl >> 4) & 0x7; SweepModCounter = (SweepControl >> 4) & 0x7;
if(EnvControl[ch] & 0x1000) // Modulation if(EnvControl[ch] & 0x1000) // Modulation
{ {
if(ModWavePos < 32 || (EnvControl[ch] & 0x2000)) if(ModState == 0 || (EnvControl[ch] & 0x2000))
{ EffFreq[ch] = (Frequency[ch] + (signed char)ModData[ModWavePos]) & 0x7FF;
ModWavePos &= 0x1F; if(ModState == 1)
ModState = 2;
EffFreq[ch] = (Frequency[ch] + (signed char)ModData[ModWavePos]) & 0x7FF; // Hardware bug: writing to S5FQ* locks the relevant byte when modulating
ModWavePos++; if(ModLock == 1)
} EffFreq[ch] = (EffFreq[ch] & 0x700) | (Frequency[ch] & 0xFF);
else if(ModLock == 2)
EffFreq[ch] = (EffFreq[ch] & 0xFF) | (Frequency[ch] & 0x700);
} }
else // Sweep else if(ModState < 2) // Sweep
{ {
int delta = EffFreq[ch] >> (SweepControl & 0x7); EffFreq[ch] = NewSweepFreq;
int NewFreq = EffFreq[ch] + ((SweepControl & 0x8) ? delta : -delta); }
//printf("Sweep(%d): Old: %d, New: %d\n", ch, EffFreq[ch], NewFreq); if(++ModWavePos >= 32)
if(NewFreq < 0)
EffFreq[ch] = 0;
else if(NewFreq > 0x7FF)
{ {
//EffFreq[ch] = 0x7FF; if(ModState == 0)
IntlControl[ch] &= ~0x80; ModState = 1;
ModWavePos = 0;
} }
else
EffFreq[ch] = NewFreq;
}
} }
} }
} // end while(SweepModClockDivider <= 0) } // end while(SweepModClockDivider <= 0)
@ -433,11 +469,11 @@ void VSU::Update(int timestamp)
CalcCurrentOutput(ch, left, right); CalcCurrentOutput(ch, left, right);
if (left!=last_output[ch][0]) { if (left!=last_output[ch][0]) {
blip_add_delta(bb[0],running_timestamp,left - last_output[ch][0]); blip_add_delta(bb[0],running_timestamp,left - last_output[ch][0]);
last_output[ch][0] = left; last_output[ch][0] = left;
} }
if (right!=last_output[ch][1]) { if (right!=last_output[ch][1]) {
blip_add_delta(bb[1],running_timestamp,right - last_output[ch][1]); blip_add_delta(bb[1],running_timestamp,right - last_output[ch][1]);
last_output[ch][1] = right; last_output[ch][1] = right;
} }
oscBuf[ch]->putSample(running_timestamp,(left+right)*8); oscBuf[ch]->putSample(running_timestamp,(left+right)*8);
} }

View file

@ -74,7 +74,8 @@ class VSU
// //
// //
int EffFreq[6]; int EffFreq[6];
int Envelope[6]; int EnvelopeValue[6];
int EnvelopeReload[6];
int WavePos[6]; int WavePos[6];
int ModWavePos; int ModWavePos;
@ -91,6 +92,12 @@ class VSU
int EnvelopeClockDivider[6]; int EnvelopeClockDivider[6];
int SweepModClockDivider; int SweepModClockDivider;
public:
int EnvelopeModMask[6];
int ModState;
int ModLock;
private:
int NoiseLatcherClockDivider; int NoiseLatcherClockDivider;
unsigned int NoiseLatcher; unsigned int NoiseLatcher;

View file

@ -1,6 +1,7 @@
#ifndef YMF278_HH #ifndef YMF278_HH
#define YMF278_HH #define YMF278_HH
#include <stdint.h>
#include <vector> #include <vector>
#include <string> #include <string>
#include <algorithm> #include <algorithm>

View file

@ -338,11 +338,13 @@ void pcm_channel::output(output_data &output) const
// fetch current sample and add // fetch current sample and add
int16_t sample = fetch_sample(); int16_t sample = fetch_sample();
int32_t outl = (lvol * sample) >> 15;
int32_t outr = (rvol * sample) >> 15;
uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2; uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2;
output.data[outnum + 0] += (lvol * sample) >> 15; output.data[outnum + 0] += outl;
output.data[outnum + 1] += (rvol * sample) >> 15; output.data[outnum + 1] += outr;
m_output[outnum + 0] = output.data[outnum + 0]; m_output[outnum + 0] = outl;
m_output[outnum + 1] = output.data[outnum + 1]; m_output[outnum + 1] = outr;
} }

View file

@ -19,6 +19,8 @@
#include "vb.h" #include "vb.h"
#include "../engine.h" #include "../engine.h"
#include "IconsFontAwesome4.h"
#include "furIcons.h"
#include <math.h> #include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v; //#define rWrite(a,v) pendingWrites[a]=v;
@ -252,6 +254,27 @@ void DivPlatformVB::tick(bool sysTick) {
} }
} }
} }
for (int i=0; i<6; i++) {
if ((chan[i].envHigh&3)==0) {
chan[i].hasEnvWarning=0;
} else {
switch (vb->EnvelopeModMask[i]) {
case 0: // envelope OK
chan[i].hasEnvWarning=0;
break;
case 1: // envelope has finished
chan[i].hasEnvWarning=21;
break;
case 2: // can't envelope
chan[i].hasEnvWarning=22;
break;
}
}
}
/*if (vb->ModLock) {
chan[4].hasEnvWarning=4;
}*/
} }
int DivPlatformVB::dispatch(DivCommand c) { int DivPlatformVB::dispatch(DivCommand c) {
@ -477,6 +500,16 @@ unsigned short DivPlatformVB::getPan(int ch) {
return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15); return ((chan[ch].pan&0xf0)<<4)|(chan[ch].pan&15);
} }
DivChannelModeHints DivPlatformVB::getModeHints(int ch) {
DivChannelModeHints ret;
//if (ch>4) return ret;
ret.count=1;
ret.hint[0]=ICON_FA_EXCLAMATION_TRIANGLE;
ret.type[0]=chan[ch].hasEnvWarning;
return ret;
}
DivDispatchOscBuffer* DivPlatformVB::getOscBuffer(int ch) { DivDispatchOscBuffer* DivPlatformVB::getOscBuffer(int ch) {
return oscBuf[ch]; return oscBuf[ch];
} }

View file

@ -30,6 +30,7 @@ class DivPlatformVB: public DivDispatch {
int antiClickPeriodCount, antiClickWavePos; int antiClickPeriodCount, antiClickWavePos;
unsigned char pan, envLow, envHigh; unsigned char pan, envLow, envHigh;
bool noise, deferredWaveUpdate, intWritten; bool noise, deferredWaveUpdate, intWritten;
unsigned char hasEnvWarning;
signed short wave; signed short wave;
DivWaveSynth ws; DivWaveSynth ws;
Channel(): Channel():
@ -42,6 +43,7 @@ class DivPlatformVB: public DivDispatch {
noise(false), noise(false),
deferredWaveUpdate(false), deferredWaveUpdate(false),
intWritten(false), intWritten(false),
hasEnvWarning(0),
wave(-1) {} wave(-1) {}
}; };
Channel chan[6]; Channel chan[6];
@ -78,6 +80,7 @@ class DivPlatformVB: public DivDispatch {
void* getChanState(int chan); void* getChanState(int chan);
DivMacroInt* getChanMacroInt(int ch); DivMacroInt* getChanMacroInt(int ch);
unsigned short getPan(int chan); unsigned short getPan(int chan);
DivChannelModeHints getModeHints(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan); DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool(); unsigned char* getRegisterPool();
int getRegisterPoolSize(); int getRegisterPoolSize();

View file

@ -284,13 +284,13 @@ void DivPlatformVIC20::muteChannel(int ch, bool mute) {
void DivPlatformVIC20::forceIns() { void DivPlatformVIC20::forceIns() {
for (int i=0; i<4; i++) { for (int i=0; i<4; i++) {
chan[i].insChanged=true; chan[i].insChanged=true;
// I give up! if (chan[i].onOff && chan[i].active) {
if (chan[i].onOff) {
chan[i].freqChanged=true; chan[i].freqChanged=true;
} else { } else {
chan[i].freqChanged=false; chan[i].freqChanged=false;
chan[i].keyOff=true; chan[i].keyOff=true;
chan[i].keyOn=false; chan[i].keyOn=false;
chan[i].waveWriteCycle=-1;
} }
writeOutVol(i); writeOutVol(i);
} }

View file

@ -418,6 +418,9 @@ int DivPlatformVRC6::dispatch(DivCommand c) {
case DIV_CMD_STD_NOISE_MODE: case DIV_CMD_STD_NOISE_MODE:
if ((c.chan!=2) && (!chan[c.chan].pcm)) { // pulse if ((c.chan!=2) && (!chan[c.chan].pcm)) { // pulse
chan[c.chan].duty=c.value; chan[c.chan].duty=c.value;
if (!isMuted[c.chan]) { // pulse
chWrite(c.chan,0,(chan[c.chan].outVol&0xf)|((chan[c.chan].duty&7)<<4));
}
} }
break; break;
case DIV_CMD_SAMPLE_MODE: case DIV_CMD_SAMPLE_MODE:

View file

@ -1577,6 +1577,9 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
cycles++; cycles++;
} }
// don't let user play anything during export
if (exporting) pendingNotes.clear();
if (!pendingNotes.empty()) { if (!pendingNotes.empty()) {
bool isOn[DIV_MAX_CHANS]; bool isOn[DIV_MAX_CHANS];
memset(isOn,0,DIV_MAX_CHANS*sizeof(bool)); memset(isOn,0,DIV_MAX_CHANS*sizeof(bool));
@ -1616,6 +1619,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
} }
dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,note.channel,note.note)); dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,note.channel,note.note));
keyHit[note.channel]=true; keyHit[note.channel]=true;
chan[note.channel].note = note.note;
chan[note.channel].releasing=false; chan[note.channel].releasing=false;
chan[note.channel].noteOnInhibit=true; chan[note.channel].noteOnInhibit=true;
chan[note.channel].lastIns=note.ins; chan[note.channel].lastIns=note.ins;
@ -2248,7 +2252,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
} }
} }
int ins=-1; int ins=-1;
if ((ins=midiCallback(msg))!=-2) { if ((ins=midiCallback(msg))!=-3) {
int chan=msg.type&15; int chan=msg.type&15;
switch (msg.type&0xf0) { switch (msg.type&0xf0) {
case TA_MIDI_NOTE_OFF: { case TA_MIDI_NOTE_OFF: {
@ -2298,7 +2302,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
} }
// process sample/wave preview // process sample/wave preview
if ((sPreview.sample>=0 && sPreview.sample<(int)song.sample.size()) || (sPreview.wave>=0 && sPreview.wave<(int)song.wave.size())) { if (((sPreview.sample>=0 && sPreview.sample<(int)song.sample.size()) || (sPreview.wave>=0 && sPreview.wave<(int)song.wave.size())) && !exporting) {
unsigned int samp_bbOff=0; unsigned int samp_bbOff=0;
unsigned int prevAvail=blip_samples_avail(samp_bb); unsigned int prevAvail=blip_samples_avail(samp_bb);
if (prevAvail>size) prevAvail=size; if (prevAvail>size) prevAvail=size;

View file

@ -291,6 +291,9 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) {
case DIV_SAMPLE_DEPTH_16BIT: case DIV_SAMPLE_DEPTH_16BIT:
off=offset*2; off=offset*2;
break; break;
case DIV_SAMPLE_DEPTH_4BIT:
off=(offset+1)/2;
break;
default: default:
break; break;
} }
@ -355,6 +358,10 @@ int DivSample::getSampleOffset(int offset, int length, DivSampleDepth depth) {
off=((offset*3)+1)/2; off=((offset*3)+1)/2;
len=((length*3)+1)/2; len=((length*3)+1)/2;
break; break;
case DIV_SAMPLE_DEPTH_4BIT:
off=(offset+1)/2;
len=(length+1)/2;
break;
case DIV_SAMPLE_DEPTH_16BIT: case DIV_SAMPLE_DEPTH_16BIT:
off=offset*2; off=offset*2;
len=length*2; len=length*2;
@ -419,6 +426,9 @@ int DivSample::getEndPosition(DivSampleDepth depth) {
case DIV_SAMPLE_DEPTH_12BIT: case DIV_SAMPLE_DEPTH_12BIT:
off=length12; off=length12;
break; break;
case DIV_SAMPLE_DEPTH_4BIT:
off=length4;
break;
case DIV_SAMPLE_DEPTH_16BIT: case DIV_SAMPLE_DEPTH_16BIT:
off=length16; off=length16;
break; break;
@ -622,6 +632,12 @@ bool DivSample::initInternal(DivSampleDepth d, int count) {
data12=new unsigned char[length12+8]; data12=new unsigned char[length12+8];
memset(data12,0,length12+8); memset(data12,0,length12+8);
break; break;
case DIV_SAMPLE_DEPTH_4BIT:
if (data4!=NULL) delete[] data4;
length4=(count+1)/2;
data4=new unsigned char[length4];
memset(data4,0,length4);
break;
case DIV_SAMPLE_DEPTH_16BIT: // 16-bit case DIV_SAMPLE_DEPTH_16BIT: // 16-bit
if (data16!=NULL) delete[] data16; if (data16!=NULL) delete[] data16;
length16=count*2; length16=count*2;
@ -860,6 +876,9 @@ void DivSample::convert(DivSampleDepth newDepth, unsigned int formatMask) {
case DIV_SAMPLE_DEPTH_VOX: // VOX case DIV_SAMPLE_DEPTH_VOX: // VOX
setSampleCount((samples+1)&(~1)); setSampleCount((samples+1)&(~1));
break; break;
case DIV_SAMPLE_DEPTH_4BIT:
setSampleCount((samples+1)&(~1));
break;
default: default:
break; break;
} }
@ -1317,6 +1336,18 @@ void DivSample::render(unsigned int formatMask) {
} }
} }
break; break;
case DIV_SAMPLE_DEPTH_4BIT: {
unsigned short nibble=0;
for (unsigned int i=0; i<samples; i++) {
if (i&1) {
nibble=data4[i>>1]&0xf;
} else {
nibble=data4[i>>1]>>4;
}
data16[i]=((nibble<<12)|(nibble<<8)|(nibble<<4)|nibble)^0x8000;
}
break;
}
default: default:
return; return;
} }
@ -1518,6 +1549,20 @@ void DivSample::render(unsigned int formatMask) {
} }
} }
} }
if (NOT_IN_FORMAT(DIV_SAMPLE_DEPTH_4BIT)) {
if (!initInternal(DIV_SAMPLE_DEPTH_4BIT,samples)) return;
unsigned char _sample=0, sample4=0;
unsigned short* samplePtr = (unsigned short*)data16;
for (unsigned int i=0; i<samples; i+=2) {
_sample=(*samplePtr++^0x8000)>>12;
sample4=_sample<<4;
if (i+1<samples) {
_sample=(*samplePtr++^0x8000)>>12;
sample4|=_sample;
}
data4[i>>1]=sample4;
}
}
} }
void* DivSample::getCurBuf() { void* DivSample::getCurBuf() {
@ -1550,6 +1595,8 @@ void* DivSample::getCurBuf() {
return dataIMA; return dataIMA;
case DIV_SAMPLE_DEPTH_12BIT: case DIV_SAMPLE_DEPTH_12BIT:
return data12; return data12;
case DIV_SAMPLE_DEPTH_4BIT:
return data4;
case DIV_SAMPLE_DEPTH_16BIT: case DIV_SAMPLE_DEPTH_16BIT:
return data16; return data16;
default: default:
@ -1588,6 +1635,8 @@ unsigned int DivSample::getCurBufLen() {
return lengthIMA; return lengthIMA;
case DIV_SAMPLE_DEPTH_12BIT: case DIV_SAMPLE_DEPTH_12BIT:
return length12; return length12;
case DIV_SAMPLE_DEPTH_4BIT:
return length4;
case DIV_SAMPLE_DEPTH_16BIT: case DIV_SAMPLE_DEPTH_16BIT:
return length16; return length16;
default: default:
@ -1703,4 +1752,5 @@ DivSample::~DivSample() {
if (dataC219) delete[] dataC219; if (dataC219) delete[] dataC219;
if (dataIMA) delete[] dataIMA; if (dataIMA) delete[] dataIMA;
if (data12) delete[] data12; if (data12) delete[] data12;
if (data4) delete[] data4;
} }

View file

@ -48,6 +48,7 @@ enum DivSampleDepth: unsigned char {
DIV_SAMPLE_DEPTH_C219=12, DIV_SAMPLE_DEPTH_C219=12,
DIV_SAMPLE_DEPTH_IMA_ADPCM=13, DIV_SAMPLE_DEPTH_IMA_ADPCM=13,
DIV_SAMPLE_DEPTH_12BIT=14, DIV_SAMPLE_DEPTH_12BIT=14,
DIV_SAMPLE_DEPTH_4BIT=15,
DIV_SAMPLE_DEPTH_16BIT=16, DIV_SAMPLE_DEPTH_16BIT=16,
DIV_SAMPLE_DEPTH_MAX // boundary for sample depth DIV_SAMPLE_DEPTH_MAX // boundary for sample depth
}; };
@ -147,8 +148,9 @@ struct DivSample {
unsigned char* dataC219; // 12 unsigned char* dataC219; // 12
unsigned char* dataIMA; // 13 unsigned char* dataIMA; // 13
unsigned char* data12; // 14 unsigned char* data12; // 14
unsigned char* data4; // 15
unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219, lengthIMA, length12; unsigned int length8, length16, length1, lengthDPCM, lengthZ, lengthQSoundA, lengthA, lengthB, lengthK, lengthBRR, lengthVOX, lengthMuLaw, lengthC219, lengthIMA, length12, length4;
unsigned int samples; unsigned int samples;
@ -360,6 +362,7 @@ struct DivSample {
dataC219(NULL), dataC219(NULL),
dataIMA(NULL), dataIMA(NULL),
data12(NULL), data12(NULL),
data4(NULL),
length8(0), length8(0),
length16(0), length16(0),
length1(0), length1(0),
@ -375,6 +378,7 @@ struct DivSample {
lengthC219(0), lengthC219(0),
lengthIMA(0), lengthIMA(0),
length12(0), length12(0),
length4(0),
samples(0) { samples(0) {
for (int i=0; i<DIV_MAX_CHIPS; i++) { for (int i=0; i<DIV_MAX_CHIPS; i++) {
for (int j=0; j<DIV_MAX_SAMPLE_TYPE; j++) { for (int j=0; j<DIV_MAX_SAMPLE_TYPE; j++) {

View file

@ -24,7 +24,7 @@
// this function is so long // this function is so long
// may as well make it something else // may as well make it something else
void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable, bool dpcm07, DivDispatch** writeNES) { void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, int* setPos, unsigned int* sampleOff8, unsigned int* sampleLen8, size_t bankOffset, bool directStream, bool* sampleStoppable, bool dpcm07, DivDispatch** writeNES, int rateCorrection) {
unsigned char baseAddr1=isSecond?0xa0:0x50; unsigned char baseAddr1=isSecond?0xa0:0x50;
unsigned char baseAddr2=isSecond?0x80:0; unsigned char baseAddr2=isSecond?0x80:0;
unsigned short baseAddr2S=isSecond?0x8000:0; unsigned short baseAddr2S=isSecond?0x8000:0;
@ -188,6 +188,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_YM2610_CSM:
case DIV_SYSTEM_YM2610B_CSM:
// TODO: YM2610B channels 1 and 4 and ADPCM-B // TODO: YM2610B channels 1 and 4 and ADPCM-B
for (int i=0; i<2; i++) { // set SL and RR to highest for (int i=0; i<2; i++) { // set SL and RR to highest
w->writeC(8|baseAddr1); w->writeC(8|baseAddr1);
@ -264,6 +266,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break; break;
case DIV_SYSTEM_YM2203: case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT: case DIV_SYSTEM_YM2203_EXT:
case DIV_SYSTEM_YM2203_CSM:
for (int i=0; i<3; i++) { // set SL and RR to highest for (int i=0; i<3; i++) { // set SL and RR to highest
w->writeC(5|baseAddr1); w->writeC(5|baseAddr1);
w->writeC(0x80+i); w->writeC(0x80+i);
@ -795,7 +798,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break; break;
case 1: { // set sample freq case 1: { // set sample freq
sampleStoppable[streamID]=true; sampleStoppable[streamID]=true;
int realFreq=write.val; int realFreq=(write.val*44100)/rateCorrection;
if (realFreq<0) realFreq=0; if (realFreq<0) realFreq=0;
if (realFreq>44100) realFreq=44100; if (realFreq>44100) realFreq=44100;
w->writeC(0x92); w->writeC(0x92);
@ -977,6 +980,8 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_YM2610_CSM:
case DIV_SYSTEM_YM2610B_CSM:
switch (write.addr>>8) { switch (write.addr>>8) {
case 0: // port 0 case 0: // port 0
w->writeC(8|baseAddr1); w->writeC(8|baseAddr1);
@ -992,12 +997,14 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
break; break;
case DIV_SYSTEM_YM2203: case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT: case DIV_SYSTEM_YM2203_EXT:
case DIV_SYSTEM_YM2203_CSM:
w->writeC(5|baseAddr1); w->writeC(5|baseAddr1);
w->writeC(write.addr&0xff); w->writeC(write.addr&0xff);
w->writeC(write.val); w->writeC(write.val);
break; break;
case DIV_SYSTEM_YM2608: case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT: case DIV_SYSTEM_YM2608_EXT:
case DIV_SYSTEM_YM2608_CSM:
switch (write.addr>>8) { switch (write.addr>>8) {
case 0: // port 0 case 0: // port 0
w->writeC(6|baseAddr1); w->writeC(6|baseAddr1);
@ -1233,7 +1240,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
chipVol.push_back((_id)|(0x80000100)|(((unsigned int)_vol)<<16)); \ chipVol.push_back((_id)|(0x80000100)|(((unsigned int)_vol)<<16)); \
} }
SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints, bool directStream, int trailingTicks, bool dpcm07) { SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints, bool directStream, int trailingTicks, bool dpcm07, int correctedRate) {
if (version<0x150) { if (version<0x150) {
lastError="VGM version is too low"; lastError="VGM version is too low";
return NULL; return NULL;
@ -1243,7 +1250,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
setOrder(0); setOrder(0);
BUSY_BEGIN_SOFT; BUSY_BEGIN_SOFT;
double origRate=got.rate; double origRate=got.rate;
got.rate=44100; got.rate=correctedRate;
// determine loop point // determine loop point
int loopOrder=0; int loopOrder=0;
int loopRow=0; int loopRow=0;
@ -1516,6 +1523,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_EXT:
case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YM2610B_EXT:
case DIV_SYSTEM_YM2610_CSM:
case DIV_SYSTEM_YM2610B_CSM:
if (!hasOPNB) { if (!hasOPNB) {
hasOPNB=disCont[i].dispatch->chipClock; hasOPNB=disCont[i].dispatch->chipClock;
CHIP_VOL(8,1.0); CHIP_VOL(8,1.0);
@ -1531,7 +1540,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
hasOPNB|=0x40000000; hasOPNB|=0x40000000;
howManyChips++; howManyChips++;
} }
if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT) || (song.system[i]==DIV_SYSTEM_YM2610B_CSM)) && (!(hasOPNB&0x80000000))) { // YM2610B flag
hasOPNB|=0x80000000; hasOPNB|=0x80000000;
} }
break; break;
@ -1627,6 +1636,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
break; break;
case DIV_SYSTEM_YM2203: case DIV_SYSTEM_YM2203:
case DIV_SYSTEM_YM2203_EXT: case DIV_SYSTEM_YM2203_EXT:
case DIV_SYSTEM_YM2203_CSM:
if (!hasOPN) { if (!hasOPN) {
hasOPN=disCont[i].dispatch->chipClock; hasOPN=disCont[i].dispatch->chipClock;
willExport[i]=true; willExport[i]=true;
@ -1643,6 +1653,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
break; break;
case DIV_SYSTEM_YM2608: case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT: case DIV_SYSTEM_YM2608_EXT:
case DIV_SYSTEM_YM2608_CSM:
if (!hasOPNA) { if (!hasOPNA) {
hasOPNA=disCont[i].dispatch->chipClock; hasOPNA=disCont[i].dispatch->chipClock;
CHIP_VOL(7,1.0); CHIP_VOL(7,1.0);
@ -2849,7 +2860,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
lastOne=i.second.time; lastOne=i.second.time;
} }
// write write // write write
performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream,sampleStoppable,dpcm07,writeNES); performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,setPos,sampleOff8,sampleLen8,bankOffset[i.first],directStream,sampleStoppable,dpcm07,writeNES,correctedRate);
writeCount++; writeCount++;
} }
sortedWrites.clear(); sortedWrites.clear();

View file

@ -145,7 +145,9 @@ void DivEngine::runExportThread() {
// take control of audio output // take control of audio output
deinitAudioBackend(); deinitAudioBackend();
freelance=false;
playSub(false); playSub(false);
freelance=false;
logI("rendering to file..."); logI("rendering to file...");
@ -244,7 +246,9 @@ void DivEngine::runExportThread() {
// take control of audio output // take control of audio output
deinitAudioBackend(); deinitAudioBackend();
freelance=false;
playSub(false); playSub(false);
freelance=false;
logI("rendering to files..."); logI("rendering to files...");
@ -380,7 +384,9 @@ void DivEngine::runExportThread() {
totalLoops=0; totalLoops=0;
isFadingOut=false; isFadingOut=false;
remainingLoops=-1; remainingLoops=-1;
freelance=false;
playSub(false); playSub(false);
freelance=false;
while (playing) { while (playing) {
size_t total=0; size_t total=0;

View file

@ -35,12 +35,14 @@ const char* aboutLine[]={
_N("-- program --"), _N("-- program --"),
"tildearrow", "tildearrow",
_N("A M 4 N (intro tune)"), _N("A M 4 N (intro tune)"),
"AArt1256",
"Adam Lederer", "Adam Lederer",
"akumanatt", "akumanatt",
"asiekierka", "asiekierka",
"cam900", "cam900",
"djtuBIG-MaliceX", "djtuBIG-MaliceX",
"Eknous", "Eknous",
"host12prog",
"Kagamiin~", "Kagamiin~",
"laoo", "laoo",
"LTVA", "LTVA",
@ -71,6 +73,7 @@ const char* aboutLine[]={
_N("-- localization/translation team --"), _N("-- localization/translation team --"),
"Bahasa Indonesia: ZoomTen (Zumi)", "Bahasa Indonesia: ZoomTen (Zumi)",
"Español: CrimsonZN, ThaCuber, tildearrow", "Español: CrimsonZN, ThaCuber, tildearrow",
"Français: fouinne44",
"Հայերեն: Eknous", "Հայերեն: Eknous",
"한국어: Heemin, leejh20, Nicknamé", "한국어: Heemin, leejh20, Nicknamé",
"Nederlands: Lunathir", "Nederlands: Lunathir",
@ -90,6 +93,7 @@ const char* aboutLine[]={
"Lumigado", "Lumigado",
"Lunathir", "Lunathir",
"plane", "plane",
"skyfloogle",
"TheEssem", "TheEssem",
"", "",
_N("-- Metal backend test team --"), _N("-- Metal backend test team --"),

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