Compare commits

...

59 Commits

Author SHA1 Message Date
tildearrow ec4063641a GUI: fix sample preview needle visible when switch
ing samples
2023-09-05 15:38:02 -05:00
tildearrow 803c8b0582 GUI: update intro
ticket #1453
2023-09-05 15:31:27 -05:00
tildearrow fc760eed43 GUI: fix cut off text in settings 2023-09-05 15:30:53 -05:00
tildearrow eb18b28928 workPool: improvements 2023-09-05 06:02:58 -05:00
tildearrow d42b503e81 GUI: update credits 2023-09-05 05:33:23 -05:00
tildearrow 2c0ae7c8bb reduce intro volume, part 1 2023-09-05 04:55:22 -05:00
tildearrow 5f0fe2c8f7
READY! DOWN! HUT HUT HUT HUT HUT HUT HUT HUT HUT
****** CHANGE SIDES ******
NEXT DEFENSE      NEXT OFFENSE
2023-09-05 04:42:35 -05:00
tildearrow 1da000b00c GUI: per-chan osc multi-threading! 2023-09-05 04:38:57 -05:00
tildearrow c99899a002 GUI: re-organize chan osc code
prepare for possible multi-threading
2023-09-04 18:54:33 -05:00
tildearrow 55eeb241cf this won't build 2023-09-04 18:35:18 -05:00
tildearrow ad7b4f61b5 YM2612: fix missing 30xx effect 2023-09-04 15:02:19 -05:00
tildearrow 2ca5856800 a fix 2023-09-04 04:25:21 -05:00
tildearrow 60df7e26f4 GUI: even more chan osc improvements 2023-09-04 04:14:47 -05:00
tildearrow ab7b26a2e7 GUI: improve chan osc wave centering 2023-09-04 01:18:48 -05:00
tildearrow 7a78ec1b60 GUI: optimize chan osc
don't process FFT if not loud enough
don't process DFT if we couldn't determine wave length
2023-09-03 20:09:03 -05:00
tildearrow 90980a3062 GUI: center chan osc 2023-09-03 19:08:30 -05:00
tildearrow 83c64aa4b4 fix the crash
the hell? one double and suddenly it crashes on Android?
2023-09-03 17:18:31 -05:00
thacuber2a03 25a83bf1b9 add sweatsmile bossfight 2023-09-03 14:12:56 -04:00
tildearrow 7ea5f2de07 remove some debug info 2023-09-03 04:22:17 -05:00
tildearrow f6db75fae1 GUI: massive chan osc improvements 2023-09-03 04:22:00 -05:00
tildearrow 19d0ed617a what? 2023-09-02 23:57:55 -05:00
tildearrow 20ed22d6e8 Revert "."
This reverts commit b26a76f343.
2023-09-02 23:07:09 -05:00
tildearrow b26a76f343 . 2023-09-02 22:46:46 -05:00
tildearrow ef23b88ad3 NES: fix chan osc (noise, NSFplay) 2023-09-02 03:58:11 -05:00
tildearrow 716d42ee6d IGFD: fix .* filter with label 2023-09-01 19:59:43 -05:00
tildearrow 1c171ed7bd GUI: de-duplicate file dialog filters
untested. may not work...
2023-09-01 18:33:32 -05:00
tildearrow c21f880e3e GUI: update credits 2023-09-01 18:33:08 -05:00
tildearrow 4f02f1f90b update settings.md 2023-09-01 16:43:08 -05:00
tildearrow 65cd433ac7 fix hang detection (DirectSound) 2023-08-31 04:46:52 -05:00
tildearrow 5a9402abcd fix build 2023-08-31 04:39:19 -05:00
tildearrow fa7405090e add WASAPI exclusive mode flag to PortAudio backen
d
2023-08-31 04:30:49 -05:00
tildearrow be38b992e3
Merge pull request #1446 from Eknous-P/partnums
update part numbers
2023-08-31 03:24:30 -05:00
tildearrow addbc986f0 ExtCh: fix forceIns
why KVS
2023-08-31 03:23:15 -05:00
Eknous 914855d751
Merge branch 'tildearrow:master' into partnums 2023-08-31 12:16:16 +04:00
Eknous-P 2a370dbb1f update part numbers
fix opl/opll cases
rm "MOS" from sids
c219 case
2023-08-31 12:15:29 +04:00
tildearrow b315b84e31 GUI: clear out audio dev when changing backend 2023-08-31 03:01:38 -05:00
tildearrow 35aeb51b79 ugh why 2023-08-31 02:31:33 -05:00
tildearrow 05d5eb5ca3 asd 2023-08-31 02:19:27 -05:00
tildearrow 879e770e58 and again 2023-08-31 02:09:54 -05:00
tildearrow 7f35d06ccb why does this happen 2023-08-31 01:59:37 -05:00
tildearrow 43ef57390a GUI: clamp CWSliders 2023-08-31 01:52:11 -05:00
tildearrow 4ad1ae78fa ASDFGHJKL 2023-08-31 01:43:19 -05:00
tildearrow a882d7bcf2 GUI: detect UI scale factor when moving window 2023-08-31 01:24:06 -05:00
tildearrow 9caa2f38f4 SoundUnit: fix getPan() 2023-08-31 00:42:25 -05:00
tildearrow 5140acd51f
Merge pull request #1443 from tildearrow/doc-general
Small documentation fixes and improvements.
2023-08-31 00:20:18 -05:00
tildearrow 9aacc706f1 Merge branch 'master' into doc-general 2023-08-31 00:20:05 -05:00
Electric Keet fb3a3890d5 And the rest of the fixes. 2023-08-30 22:18:15 -07:00
Electric Keet 29c2879397 Small correction. 2023-08-30 22:07:06 -07:00
tildearrow 8b3fc84b51 don't show JACK/PA backends if not available 2023-08-30 23:23:27 -05:00
tildearrow 922800d864 oh crap 2023-08-30 23:14:47 -05:00
tildearrow 274ce8a646 remove many TODOs 2023-08-30 19:21:38 -05:00
tildearrow d1b78f787b update credits 2023-08-30 17:38:45 -05:00
tildearrow 68787a4d8b add PortAudio backend - PLEASE READ
PLEASE DO:

```
git submodule update --init --recursive
```

AFTER PULLING THIS COMMIT.
2023-08-30 17:32:51 -05:00
Electric Keet 1ebf828743 A couple more slight edits.
Typo fix and update for new macro props indicator, just to show what that looks like.
2023-08-30 14:13:06 -07:00
tildearrow 7d605c9d76 GUI: why do I see a 0.5 reference 2023-08-30 14:27:29 -05:00
tildearrow 80013089a2 GUI: colorize macro speed/delay button if used 2023-08-30 14:06:04 -05:00
tildearrow 5a688c58cb OPM/OPZ: invert noise frequencies
since ymfm is default
2023-08-30 13:46:47 -05:00
Electric Keet da5d110e73 Doc update for new features. 2023-08-29 10:52:07 -07:00
Electric Keet 2813e8e3b3 Merge branch 'master' into doc-general 2023-08-28 14:51:17 -07:00
59 changed files with 13308 additions and 12397 deletions

3
.gitmodules vendored
View File

@ -12,3 +12,6 @@
[submodule "extern/adpcm"]
path = extern/adpcm
url = https://github.com/superctr/adpcm
[submodule "extern/portaudio"]
path = extern/portaudio
url = https://github.com/PortAudio/portaudio.git

View File

@ -27,6 +27,7 @@ include(TestBigEndian)
if (ANDROID)
set(USE_RTMIDI_DEFAULT OFF)
set(WITH_PORTAUDIO_DEFAULT OFF)
set(USE_BACKWARD_DEFAULT OFF)
find_library(TERMUX rt)
if (TERMUX)
@ -34,6 +35,7 @@ if (ANDROID)
endif()
else()
set(USE_RTMIDI_DEFAULT ON)
set(WITH_PORTAUDIO_DEFAULT ON)
if (WIN32 OR APPLE)
set(USE_BACKWARD_DEFAULT ON)
else()
@ -78,6 +80,7 @@ option(USE_SDL2 "Build with SDL2. Required to build with GUI." ${USE_SDL2_DEFAUL
option(USE_SNDFILE "Build with libsndfile. Required in order to work with audio files." ${USE_SNDFILE_DEFAULT})
option(USE_BACKWARD "Use backward-cpp to print a backtrace on crash/abort." ${USE_BACKWARD_DEFAULT})
option(WITH_JACK "Whether to build with JACK support. Auto-detects if JACK is available" ${WITH_JACK_DEFAULT})
option(WITH_PORTAUDIO "Whether to build with PortAudio for audio output." ${WITH_PORTAUDIO_DEFAULT})
option(WITH_RENDER_SDL "Whether to build with the SDL_Renderer render backend." ${WITH_RENDER_SDL_DEFAULT})
option(WITH_RENDER_OPENGL "Whether to build with the OpenGL render backend." ${WITH_RENDER_OPENGL_DEFAULT})
option(WITH_RENDER_DX11 "Whether to build with the DirectX 11 render backend." ${WITH_RENDER_DX11_DEFAULT})
@ -85,6 +88,7 @@ option(USE_GLES "Use OpenGL ES for the OpenGL render backend." ${USE_GLES_DEFAUL
option(SYSTEM_FFTW "Use a system-installed version of FFTW instead of the vendored one" OFF)
option(SYSTEM_FMT "Use a system-installed version of fmt instead of the vendored one" OFF)
option(SYSTEM_LIBSNDFILE "Use a system-installed version of libsndfile instead of the vendored one" OFF)
option(SYSTEM_PORTAUDIO "Use a system-installed version of PortAudio instead of the vendored one" OFF)
option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the vendored one" OFF)
option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF)
option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT})
@ -204,6 +208,27 @@ else()
message(STATUS "Not using libsndfile")
endif()
if (WITH_PORTAUDIO)
if (SYSTEM_PORTAUDIO)
find_package(PkgConfig REQUIRED)
pkg_check_modules(PORTAUDIO REQUIRED portaudio)
list(APPEND DEPENDENCIES_INCLUDE_DIRS ${PORTAUDIO_INCLUDE_DIRS})
list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${PORTAUDIO_CFLAGS_OTHER})
list(APPEND DEPENDENCIES_LIBRARIES ${PORTAUDIO_LIBRARIES})
list(APPEND DEPENDENCIES_LIBRARY_DIRS ${PORTAUDIO_LIBRARY_DIRS})
list(APPEND DEPENDENCIES_LINK_OPTIONS ${PORTAUDIO_LDFLAGS_OTHER})
list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${PORTAUDIO_LDFLAGS})
message(STATUS "Using system-installed PortAudio")
else()
set(PA_BUILD_SHARED_LIBS OFF CACHE BOOL "Build dynamic library" FORCE)
# don't - Furnace has its own implementation
set(PA_USE_JACK OFF CACHE BOOL "Enable support for JACK Audio Connection Kit" FORCE)
add_subdirectory(extern/portaudio EXCLUDE_FROM_ALL)
list(APPEND DEPENDENCIES_LIBRARIES PortAudio)
message(STATUS "Using vendored PortAudio")
endif()
endif()
if (USE_RTMIDI)
if (SYSTEM_RTMIDI)
find_package(PkgConfig REQUIRED)
@ -345,6 +370,14 @@ else()
message(STATUS "Building without JACK support")
endif()
if (WITH_PORTAUDIO)
list(APPEND AUDIO_SOURCES src/audio/pa.cpp)
message(STATUS "Building with PortAudio")
list(APPEND DEPENDENCIES_DEFINES HAVE_PA)
else()
message(STATUS "Building without PortAudio")
endif()
if (USE_RTMIDI)
list(APPEND AUDIO_SOURCES src/audio/rtmidi.cpp)
message(STATUS "Building with RtMidi")
@ -517,6 +550,7 @@ src/engine/blip_buf.c
src/engine/brrUtils.c
src/engine/safeReader.cpp
src/engine/safeWriter.cpp
src/engine/workPool.cpp
src/engine/cmdStream.cpp
src/engine/cmdStreamOps.cpp
src/engine/config.cpp

View File

@ -252,6 +252,7 @@ Available options:
| `USE_SNDFILE` | `ON` | Build with libsndfile (required in order to work with audio files) |
| `USE_BACKWARD` | `ON` | Use backward-cpp to print a backtrace on crash/abort |
| `WITH_JACK` | `ON` if system-installed JACK detected, otherwise `OFF` | Whether to build with JACK support. Auto-detects if JACK is available |
| `WITH_PORTAUDIO` | `ON` | Whether to build with PortAudio. |
| `SYSTEM_FFTW` | `OFF` | Use a system-installed version of FFTW instead of the vendored one |
| `SYSTEM_FMT` | `OFF` | Use a system-installed version of fmt instead of the vendored one |
| `SYSTEM_LIBSNDFILE` | `OFF` | Use a system-installed version of libsndfile instead of the vendored one |

Binary file not shown.

View File

@ -39,14 +39,8 @@ items in _italics_ don't appear in basic mode and are only available in advanced
- only available when there's a YM2151 and/or VERA.
- **export command stream...**: export song data to a command stream file. see next section for more details.
- this option is for developers.
- _**add chip...**:_ add a chip to the current song.
- _**configure chip...**:_ set a chip's parameters.
- for a list of parameters, see [7-systems](../7-systems/README.md).
- _**change chip...**:_ change a chip to another.
- **Preserve channel positions**: enable this option to make sure Furnace does not auto-arrange/delete channels to compensate for differing channel counts. this can be useful for doing ports, e.g. from Genesis to PC-98.
- _**remove chip...**_: remove a chip.
- **Preserve channel positions**: same thing as above.
- **manage chips**: opens the [Chip Manager](../8-advanced/chip-manager.md) dialog.
- **restore backup**: restores a previously saved backup.
- Furnace keeps up to 5 backups of a song.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -66,17 +66,31 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
### Output
- **Backend**: selects SDL or JACK for audio output.
- only appears on Linux, or MacOS compiled with JACK support
- **Driver**: select a different SDL audio driver if you're having problems with the default one.
- **Backend**: selects a different backend for audio output.
- SDL: the default one.
- JACK: the JACK Audio Connection Kit (low-latency audio server). only appears on Linux, or MacOS compiled with JACK support.
- PortAudio: this may or may not perform better than the SDL backend.
- **Driver**: select a different audio driver if you're having problems with the default one.
- only appears when Backend is SDL.
- **Device**: audio device for playback.
- **Sample rate**
- if using PortAudio backend, devices will be prefixed with the audio API that PortAudio is going to use:
- Windows WASAPI: a modern audio API available on Windows Vista and later, featuring an (optional) Exclusive Mode. be noted that your buffer size setting may be ignored.
- Windows WDM-KS: low-latency, direct to hardware output mechanism. may not work all the time and prevents your audio device from being used for anything else!
- Windows DirectSound: this is the worst choice. best to move on.
- MME: an old audio API. doesn't have Exclusive Mode.
- Core Audio: the only choice in macOS.
- ALSA: low-level audio output on Linux. may prevent other applications from using your audio device.
- **Sample rate**: audio output rate.
- a lower rate decreases quality and isn't really beneficial.
- if using PortAudio backend, be careful about this value.
- **Outputs**: number of audio outputs created, up to 16.
- only appears when Backend is JACK.
- **Channels**: number of output channels to use.
- **Channels**: mono, stereo or something.
- **Buffer size**: size of buffer in both samples and milliseconds.
- setting this to a low value may cause stuttering/glitches in playback (known as "underruns" or "xruns").
- setting this to a high value increases latency.
- **Exclusive mode**: enables Exclusive Mode, which may offer latency improvements.
- only available on WASAPI devices in the PortAudio backend!
- **Low-latency mode (experimental!)**: reduces latency by running the engine faster than the tick rate. useful for live playback/jam mode.
- only enable if your buffer size is small (10ms or less).
- **Force mono audio**: use if you're unable to hear stereo audio (e.g. single speaker or hearing loss in one ear).
@ -88,6 +102,7 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
- **Quality**: selects quality of resampling. low quality reduces CPU load by a small amount.
- **Software clipping**: clips output to nominal range (-1.0 to 1.0) before passing it to the audio device.
- this avoids activating Windows' built-in limiter.
- this option shall be enabled when using PortAudio backend with a DirectSound device.
### Metronome
@ -193,7 +208,7 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
- **Import**
- **Export**
- **Reset defaults**
- several categories of keybinds...
- [grouped list of keybinds...](keyboard.md)
- click on a keybind then enter a key or key combination to change it
- right-click to clear the keybind
@ -327,6 +342,7 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
- **Cursor details or file path**
- **Nothing**
- **Capitalize menu bar**
- **Display add/configure/change/remove chip menus in File menu**: if enabled, the "manage chips" item in the file menu is split into the four listed items for quick access.
### Orders

View File

@ -50,8 +50,9 @@ The very first numeric entry sets the visible width of the bars in sequence-type
Each macro has two buttons on the left.
- Macro type (explained below).
- Timing editor, which pops up a small dialog:
- Step Length (ticks): Determines how many ticks pass before each change of value.
- Delay: Delays the start of the macro until this many ticks have passed.
- Step Length (ticks): Determines how many ticks pass before each change of value. Default is 1.
- Delay: Delays the start of the macro until this many ticks have passed. Default is 0.
- The button is highlighted if either of these is set differently from default.
## macro types

View File

@ -22,11 +22,10 @@ These apply to the instrument as a whole:
- Right-click returns to algorithm view.
- **DC (half-sine carrier)**: Sets the waveform produced by carrier operator to half-sine
- **DM (half-sine modulator)**: Sets the waveform produced by modulator operator to half-sine
- preset dropdown: select OPLL preset instrument.
- preset dropdown: selects OPLL preset instrument.
These apply to each operator:
- The crossed-arrows button can be dragged to rearrange operators.
- The **OP1** and **OP2** buttons enable or disable those operators.
- **Amplitude Modulation (AM)**: Makes the operator affected by LFO tremolo.
- **Envelope generator sustain flag (EGS)**: When enabled, value of Sustain Level is in effect.
- **Attack Rate (AR)**: determines the rising time for the sound. The bigger the value, the faster the attack. (0-15 range)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -20,7 +20,7 @@ these tabs are unique to the editor for SNES instruments.
- **Delayed (write R on release)**: after release, waits until A and D have completed before starting exponential decrease.
if envelope is off:
- **Gain Mode**: select gain mode.
- **Gain Mode**: selects gain mode.
- **Direct**: direct gain from 0 to 127
- **Decrease (linear)**: linear gain from -0 to -31
- **Decrease (logarithmic)**: exponential gain from -0 to -31

View File

@ -111,7 +111,7 @@ input waveforms should match the size of the wavetable or unexpected results may
- synthesizer type: selects the synthesis algorithm.
- waveform displays.
- **Wave 1**: selects input waveform.
- this will turn yellow to indicate that a Waveform macro is set; remove the macro to work with the synthesizer.
- this will turn yellow to indicate that a Waveform macro is set.
- **Wave 2**: selects second input waveform. only appears when a dual-waveform synthesizer is selected.
- **Pause preview**: toggles live waveform preview.
- **Restart preview**: restarts preview from initial state.

View File

@ -184,7 +184,7 @@ this chip uses the [TIA](../4-instrument/tia.md) instrument editor.
| 30 | 32.7 | C-1 | 0.0 | 32.5 | C-1 | -11
| 31 | 31.7 | B-0 | +44 | 31.5 | B-0 | +33
## shapes 8
## shape 8
| pitch | NTSC | note | cent | PAL | note | cent
|------:|--------:|:----:|-----:|--------:|:----:|-----:

View File

@ -720,6 +720,7 @@ namespace IGFD
auto arr = IGFD::Utils::SplitStringToVector(fs, ',', false);
for (auto a : arr)
{
infos.firstFilter=a;
infos.collectionfilters.emplace(a);
}
}
@ -1048,7 +1049,7 @@ namespace IGFD
// check if current file extention is covered by current filter
// we do that here, for avoid doing that during filelist display
// for better fps
if (prSelectedFilter.exist(vTag) || prSelectedFilter.filter == ".*")
if (prSelectedFilter.exist(vTag) || prSelectedFilter.firstFilter == ".*")
{
return true;
}

View File

@ -745,6 +745,7 @@ namespace IGFD
{
public:
std::string filter;
std::string firstFilter;
std::set<std::string> collectionfilters;
public:

1
extern/portaudio vendored Submodule

@ -0,0 +1 @@
Subproject commit 6ee9836a08d201c118b4715d4d70242816584000

Binary file not shown.

275
src/audio/pa.cpp Normal file
View File

@ -0,0 +1,275 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 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 <string.h>
#include <vector>
#include "../ta-log.h"
#include "pa.h"
#ifdef _WIN32
#include <pa_win_wasapi.h>
#endif
int taPAProcess(const void* in, void* out, unsigned long nframes, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags flags, void* inst) {
TAAudioPA* instance=(TAAudioPA*)inst;
return instance->onProcess(in,out,nframes,timeInfo,flags);
}
int TAAudioPA::onProcess(const void* in, void* out, unsigned long nframes, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags flags) {
for (int i=0; i<desc.inChans; i++) {
if (nframes>desc.bufsize) {
delete[] inBufs[i];
inBufs[i]=new float[nframes];
}
}
for (int i=0; i<desc.outChans; i++) {
if (nframes>desc.bufsize) {
delete[] outBufs[i];
outBufs[i]=new float[nframes];
}
}
if (nframes!=desc.bufsize) {
desc.bufsize=nframes;
}
if (audioProcCallback!=NULL) {
if (midiIn!=NULL) midiIn->gather();
audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize);
}
float* fbuf=(float*)out;
for (size_t j=0; j<desc.bufsize; j++) {
for (size_t i=0; i<desc.outChans; i++) {
fbuf[j*desc.outChans+i]=outBufs[i][j];
}
}
return 0;
}
void* TAAudioPA::getContext() {
return (void*)&ac;
}
bool TAAudioPA::quit() {
if (!initialized) return false;
Pa_CloseStream(ac);
if (running) {
running=false;
}
for (int i=0; i<desc.outChans; i++) {
delete[] outBufs[i];
}
delete[] outBufs;
initialized=false;
return true;
}
bool TAAudioPA::setRun(bool run) {
if (!initialized) return false;
if (running==run) return running;
PaError status;
if (run) {
status=Pa_StartStream(ac);
} else {
status=Pa_StopStream(ac);
}
if (status!=paNoError) {
logW("error while setting run status: %s",Pa_GetErrorText(status));
return running;
}
running=run;
return running;
}
std::vector<String> TAAudioPA::listAudioDevices() {
std::vector<String> ret;
if (!audioSysStarted) {
PaError status=Pa_Initialize();
if (status!=paNoError) {
logE("could not initialize PortAudio to list audio devices");
return ret;
} else {
audioSysStarted=true;
}
}
int count=Pa_GetDeviceCount();
if (count<0) return ret;
for (int i=0; i<count; i++) {
const PaDeviceInfo* devInfo=Pa_GetDeviceInfo(i);
if (devInfo==NULL) continue;
if (devInfo->maxOutputChannels<1) continue;
String devName;
const PaHostApiInfo* driverInfo=Pa_GetHostApiInfo(devInfo->hostApi);
if (driverInfo==NULL) {
devName+="[???] ";
} else {
devName+=fmt::sprintf("[%s] ",driverInfo->name);
}
if (devInfo->name!=NULL) {
devName+=devInfo->name;
ret.push_back(devName);
}
}
return ret;
}
bool TAAudioPA::init(TAAudioDesc& request, TAAudioDesc& response) {
if (initialized) {
logE("audio already initialized");
return false;
}
PaError status;
if (!audioSysStarted) {
status=Pa_Initialize();
if (status!=paNoError) {
logE("could not initialize PortAudio");
return false;
} else {
audioSysStarted=true;
}
}
desc=request;
desc.outFormat=TA_AUDIO_FORMAT_F32;
const PaDeviceInfo* devInfo=NULL;
const PaHostApiInfo* driverInfo=NULL;
int outDeviceID=0;
if (desc.deviceName.empty()) {
outDeviceID=Pa_GetDefaultOutputDevice();
devInfo=Pa_GetDeviceInfo(outDeviceID);
if (devInfo!=NULL) {
driverInfo=Pa_GetHostApiInfo(devInfo->hostApi);
}
} else {
int count=Pa_GetDeviceCount();
bool found=false;
if (count<0) {
logE("audio device not found");
return false;
}
for (int i=0; i<count; i++) {
devInfo=Pa_GetDeviceInfo(i);
if (devInfo==NULL) continue;
if (devInfo->maxOutputChannels<1) continue;
String devName;
driverInfo=Pa_GetHostApiInfo(devInfo->hostApi);
if (driverInfo==NULL) {
devName+="[???] ";
} else {
devName+=fmt::sprintf("[%s] ",driverInfo->name);
}
if (devInfo->name!=NULL) {
devName+=devInfo->name;
} else {
continue;
}
if (devName==desc.deviceName) {
outDeviceID=i;
found=true;
break;
}
}
if (!found) {
logE("audio device not found");
return false;
}
}
// check output channels and sample rate
if (devInfo!=NULL) {
if (desc.outChans>devInfo->maxOutputChannels) desc.outChans=devInfo->maxOutputChannels;
}
PaStreamParameters outParams;
outParams.device=outDeviceID;
outParams.channelCount=desc.outChans;
outParams.sampleFormat=paFloat32;
outParams.suggestedLatency=(double)(desc.bufsize*desc.fragments)/desc.rate;
outParams.hostApiSpecificStreamInfo=NULL;
if (driverInfo!=NULL) {
#ifdef _WIN32
if (driverInfo->type==paWASAPI) {
logV("setting WASAPI-specific flags");
PaWasapiStreamInfo* wasapiInfo=new PaWasapiStreamInfo;
memset(wasapiInfo,0,sizeof(PaWasapiStreamInfo));
wasapiInfo->size=sizeof(PaWasapiStreamInfo);
wasapiInfo->hostApiType=paWASAPI;
wasapiInfo->version=1;
wasapiInfo->flags=paWinWasapiThreadPriority;
wasapiInfo->threadPriority=eThreadPriorityProAudio;
wasapiInfo->streamCategory=eAudioCategoryMedia;
wasapiInfo->streamOption=eStreamOptionRaw;
if (desc.wasapiEx) {
wasapiInfo->flags|=paWinWasapiExclusive;
}
outParams.hostApiSpecificStreamInfo=wasapiInfo;
}
#endif
}
logV("opening audio device...");
status=Pa_OpenStream(
&ac,
NULL,
&outParams,
desc.rate,
0,
paClipOff|paDitherOff,
taPAProcess,
this
);
if (status!=paNoError) {
logE("could not open audio device: %s",Pa_GetErrorText(status));
return false;
}
desc.deviceName=devInfo->name;
desc.inChans=0;
if (desc.outChans>0) {
outBufs=new float*[desc.outChans];
for (int i=0; i<desc.outChans; i++) {
outBufs[i]=new float[desc.bufsize];
}
}
response=desc;
initialized=true;
return true;
}

38
src/audio/pa.h Normal file
View File

@ -0,0 +1,38 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 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 "taAudio.h"
#include <portaudio.h>
class TAAudioPA: public TAAudio {
PaStream* ac;
bool audioSysStarted;
public:
int onProcess(const void* in, void* out, unsigned long nframes, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags flags);
void* getContext();
bool quit();
bool setRun(bool run);
std::vector<String> listAudioDevices();
bool init(TAAudioDesc& request, TAAudioDesc& response);
TAAudioPA():
ac(NULL),
audioSysStarted(false) {}
};

View File

@ -58,13 +58,16 @@ struct TAAudioDesc {
unsigned char inChans, outChans;
TAAudioFormat outFormat;
bool wasapiEx;
TAAudioDesc():
rate(0.0),
bufsize(0),
fragments(0),
inChans(0),
outChans(0),
outFormat(TA_AUDIO_FORMAT_F32) {}
outFormat(TA_AUDIO_FORMAT_F32),
wasapiEx(false) {}
};

View File

@ -32,6 +32,9 @@
#ifdef HAVE_JACK
#include "../audio/jack.h"
#endif
#ifdef HAVE_PA
#include "../audio/pa.h"
#endif
#include <math.h>
#include <float.h>
#include <fmt/printf.h>
@ -2013,6 +2016,10 @@ bool DivEngine::isPreviewingSample() {
return (sPreview.sample>=0 && sPreview.sample<(int)song.sample.size());
}
int DivEngine::getSamplePreviewSample() {
return sPreview.sample;
}
int DivEngine::getSamplePreviewPos() {
return sPreview.pos;
}
@ -3279,6 +3286,8 @@ bool DivEngine::initAudioBackend() {
if (audioEngine==DIV_AUDIO_NULL) {
if (getConfString("audioEngine","SDL")=="JACK") {
audioEngine=DIV_AUDIO_JACK;
} else if (getConfString("audioEngine","SDL")=="PortAudio") {
audioEngine=DIV_AUDIO_PORTAUDIO;
} else {
audioEngine=DIV_AUDIO_SDL;
}
@ -3322,6 +3331,21 @@ bool DivEngine::initAudioBackend() {
#endif
#else
output=new TAAudioJACK;
#endif
break;
case DIV_AUDIO_PORTAUDIO:
#ifndef HAVE_PA
logE("Furnace was not compiled with PortAudio!");
setConf("audioEngine","SDL");
saveConf();
#ifdef HAVE_SDL2
output=new TAAudioSDL;
#else
logE("Furnace was not compiled with SDL support either!");
output=new TAAudio;
#endif
#else
output=new TAAudioPA;
#endif
break;
case DIV_AUDIO_SDL:
@ -3350,6 +3374,7 @@ bool DivEngine::initAudioBackend() {
want.inChans=0;
want.outChans=getConfInt("audioChans",2);
want.outFormat=TA_AUDIO_FORMAT_F32;
want.wasapiEx=getConfInt("wasapiEx",0);
want.name="Furnace";
if (want.outChans<1) want.outChans=1;

View File

@ -73,6 +73,7 @@ enum DivStatusView {
enum DivAudioEngines {
DIV_AUDIO_JACK=0,
DIV_AUDIO_SDL=1,
DIV_AUDIO_PORTAUDIO=2,
DIV_AUDIO_NULL=126,
DIV_AUDIO_DUMMY=127
@ -713,6 +714,7 @@ class DivEngine {
// sample preview query
bool isPreviewingSample();
int getSamplePreviewSample();
int getSamplePreviewPos();
double getSamplePreviewRate();

View File

@ -175,7 +175,7 @@ void DivPlatformArcade::tick(bool sysTick) {
if (chan[i].std.duty.had) {
if (chan[i].std.duty.val>0) {
rWrite(0x0f,0x80|(0x20-chan[i].std.duty.val));
rWrite(0x0f,0x80|(chan[i].std.duty.val-1));
} else {
rWrite(0x0f,0);
}
@ -768,9 +768,9 @@ int DivPlatformArcade::dispatch(DivCommand c) {
if (c.chan!=7) break;
if (c.value) {
if (c.value>0x1f) {
rWrite(0x0f,0x80);
rWrite(0x0f,0x80|0x1f);
} else {
rWrite(0x0f,0x80|(0x1f-c.value));
rWrite(0x0f,0x80|(c.value-1));
}
} else {
rWrite(0x0f,0);

View File

@ -658,8 +658,6 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
return 15;
break;
case DIV_CMD_PRE_PORTA:
// TODO: FIX wtr_envelope.dmf
// the brokenPortaArp update broke it
if (chan[c.chan].active && c.value2) {
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY));
}

View File

@ -502,8 +502,7 @@ void DivPlatformC140::notifyInsChange(int ins) {
}
void DivPlatformC140::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformC140::notifyInsDeletion(void* ins) {

View File

@ -1107,8 +1107,6 @@ void DivPlatformES5506::notifyInsChange(int ins) {
}
void DivPlatformES5506::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformES5506::notifyInsDeletion(void* ins) {

View File

@ -388,8 +388,6 @@ void DivPlatformGA20::notifyInsChange(int ins) {
}
void DivPlatformGA20::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformGA20::notifyInsDeletion(void* ins) {

View File

@ -732,10 +732,8 @@ void DivPlatformGenesisExt::forceIns() {
if (i==2 && extMode) { // extended channel
if (isOpMuted[orderedOps[j]] || !op.enable) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
}
} else {
if (isMuted[i]) {

View File

@ -473,8 +473,6 @@ void DivPlatformK007232::notifyInsChange(int ins) {
}
void DivPlatformK007232::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformK007232::notifyInsDeletion(void* ins) {

View File

@ -409,8 +409,6 @@ void DivPlatformK053260::notifyInsChange(int ins) {
}
void DivPlatformK053260::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformK053260::notifyInsDeletion(void* ins) {

View File

@ -145,7 +145,7 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) {
oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<11;
oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<11;
oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<11;
oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<12;
oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<11;
oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<8;
}
}

View File

@ -355,8 +355,6 @@ void DivPlatformRF5C68::notifyInsChange(int ins) {
}
void DivPlatformRF5C68::notifyWaveChange(int wave) {
// TODO when wavetables are added
// TODO they probably won't be added unless the samples reside in RAM
}
void DivPlatformRF5C68::notifyInsDeletion(void* ins) {

View File

@ -459,7 +459,7 @@ DivMacroInt* DivPlatformSoundUnit::getChanMacroInt(int ch) {
}
unsigned short DivPlatformSoundUnit::getPan(int ch) {
return parent->convertPanLinearToSplit(chan[ch].pan,8,255);
return parent->convertPanLinearToSplit(chan[ch].pan^0x80,8,255);
}
DivDispatchOscBuffer* DivPlatformSoundUnit::getOscBuffer(int ch) {

View File

@ -134,7 +134,7 @@ void DivPlatformTX81Z::tick(bool sysTick) {
if (chan[i].std.duty.had) {
if (chan[i].std.duty.val>0) {
rWrite(0x0f,0x80|(0x20-chan[i].std.duty.val));
rWrite(0x0f,0x80|(chan[i].std.duty.val-1));
} else {
rWrite(0x0f,0);
}
@ -865,9 +865,9 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
if (c.chan!=7) break;
if (c.value) {
if (c.value>0x1f) {
rWrite(0x0f,0x80);
rWrite(0x0f,0x80|0x1f);
} else {
rWrite(0x0f,0x80|(0x1f-c.value));
rWrite(0x0f,0x80|(c.value-1));
}
} else {
rWrite(0x0f,0);

View File

@ -627,10 +627,8 @@ void DivPlatformYM2203Ext::forceIns() {
if (i==2 && extMode) { // extended channel
if (isOpMuted[orderedOps[j]] || !op.enable) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
}
} else {
if (isMuted[i]) {

View File

@ -676,10 +676,8 @@ void DivPlatformYM2608Ext::forceIns() {
if (i==2 && extMode) { // extended channel
if (isOpMuted[orderedOps[j]] || !op.enable) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
}
} else {
if (isMuted[i] || !op.enable) {

View File

@ -671,10 +671,8 @@ void DivPlatformYM2610BExt::forceIns() {
if (i==extChanOffs && extMode) { // extended channel
if (isOpMuted[orderedOps[j]] || !op.enable) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
}
} else {
if (isMuted[i] || !op.enable) {

View File

@ -671,10 +671,8 @@ void DivPlatformYM2610Ext::forceIns() {
if (i==extChanOffs && extMode) { // extended channel
if (isOpMuted[orderedOps[j]] || !op.enable) {
rWrite(baseAddr+0x40,127);
} else if (KVS(i,j)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
} else {
rWrite(baseAddr+0x40,op.tl);
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[orderedOps[j]].outVol&0x7f,127));
}
} else {
if (isMuted[i] || !op.enable) {

View File

@ -2080,8 +2080,8 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
}
//logD("attempts: %d",attempts);
if (attempts>=(int)size) {
logE("hang detected! stopping! at %d seconds %d micro",totalSeconds,totalTicks);
if (attempts>=(int)(size+10)) {
logE("hang detected! stopping! at %d seconds %d micro (%d>=%d)",totalSeconds,totalTicks,attempts,(int)size);
freelance=false;
playing=false;
extValuePresent=false;

View File

@ -454,10 +454,11 @@ void DivEngine::registerSystems() {
{0x18, {DIV_CMD_FM_EXTCH, "18xx: Toggle extended channel 3 mode"}},
});
EffectHandlerMap fmOPN2EffectHandlerMap={
EffectHandlerMap fmOPN2EffectHandlerMap(fmEffectHandlerMap);
fmOPN2EffectHandlerMap.insert({
{0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Toggle PCM mode (LEGACY)"}},
{0xdf, {DIV_CMD_SAMPLE_DIR, "DFxx: Set sample playback direction (0: normal; 1: reverse)"}},
};
});
EffectHandlerMap fmOPLDrumsEffectHandlerMap(fmEffectHandlerMap);
fmOPLDrumsEffectHandlerMap.insert({

157
src/engine/workPool.cpp Normal file
View File

@ -0,0 +1,157 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 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 "workPool.h"
#include "../ta-log.h"
#include <thread>
void* _workThread(void* inst) {
((DivWorkThread*)inst)->run();
return NULL;
}
void DivWorkThread::run() {
std::unique_lock<std::mutex> unique(selfLock);
DivPendingTask task;
logV("running work thread");
while (true) {
lock.lock();
if (tasks.empty()) {
lock.unlock();
isBusy=false;
parent->notify.notify_one();
if (terminate) {
break;
}
notify.wait(unique);
continue;
} else {
task=tasks.front();
tasks.pop();
lock.unlock();
task.func(task.funcArg);
parent->busyCount--;
parent->notify.notify_one();
}
}
}
bool DivWorkThread::assign(const std::function<void(void*)>& what, void* arg) {
lock.lock();
if (tasks.size()>=30) {
lock.unlock();
return false;
}
tasks.push(DivPendingTask(what,arg));
parent->busyCount++;
isBusy=true;
lock.unlock();
return true;
}
void DivWorkThread::wait() {
if (!isBusy) return;
}
bool DivWorkThread::busy() {
return isBusy;
}
void DivWorkThread::finish() {
lock.lock();
terminate=true;
lock.unlock();
notify.notify_one();
thread->join();
}
void DivWorkThread::init(DivWorkPool* p) {
parent=p;
thread=new std::thread(_workThread,this);
}
void DivWorkPool::push(const std::function<void(void*)>& what, void* arg) {
// if no work threads, just execute
if (!threaded) {
what(arg);
return;
}
for (unsigned int tryCount=0; tryCount<count; tryCount++) {
if (pos>=count) pos=0;
if (workThreads[pos++].assign(what,arg)) return;
}
// all threads are busy
logW("DivWorkPool: all work threads busy!");
what(arg);
}
bool DivWorkPool::busy() {
if (!threaded) return false;
for (unsigned int i=0; i<count; i++) {
if (workThreads[i].busy()) return true;
}
return false;
}
void DivWorkPool::wait() {
if (!threaded) return;
std::unique_lock<std::mutex> unique(selfLock);
// start running
for (unsigned int i=0; i<count; i++) {
workThreads[i].notify.notify_one();
}
// wait
while (busyCount!=0) {
if (notify.wait_for(unique,std::chrono::milliseconds(100))==std::cv_status::timeout) {
logW("DivWorkPool: wait() timed out!");
}
}
}
DivWorkPool::DivWorkPool(unsigned int threads):
threaded(threads>0),
count(threads),
pos(0),
busyCount(0) {
if (threaded) {
workThreads=new DivWorkThread[threads];
for (unsigned int i=0; i<count; i++) {
workThreads[i].init(this);
}
} else {
workThreads=NULL;
}
}
DivWorkPool::~DivWorkPool() {
if (threaded) {
for (unsigned int i=0; i<count; i++) {
workThreads[i].finish();
}
delete[] workThreads;
}
}

101
src/engine/workPool.h Normal file
View File

@ -0,0 +1,101 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 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.
*/
#ifndef _WORKPOOL_H
#define _WORKPOOL_H
#include <thread>
#include <mutex>
#include <atomic>
#include <functional>
#include <condition_variable>
#include "fixedQueue.h"
class DivWorkPool;
struct DivPendingTask {
std::function<void(void*)> func;
void* funcArg;
DivPendingTask(std::function<void(void*)> f, void* arg):
func(f),
funcArg(arg) {}
DivPendingTask():
func(NULL),
funcArg(NULL) {}
};
struct DivWorkThread {
DivWorkPool* parent;
std::mutex lock;
std::mutex selfLock;
std::thread* thread;
std::condition_variable notify;
FixedQueue<DivPendingTask,32> tasks;
std::atomic<bool> isBusy;
bool terminate;
void run();
bool assign(const std::function<void(void*)>& what, void* arg);
void wait();
bool busy();
void finish();
void init(DivWorkPool* p);
DivWorkThread():
parent(NULL),
isBusy(false),
terminate(false) {}
};
/**
* this class provides an implementation of a "thread pool" for executing tasks in parallel.
* it is highly recommended to use `new` when allocating a DivWorkPool.
*/
class DivWorkPool {
bool threaded;
std::mutex selfLock;
unsigned int count;
unsigned int pos;
DivWorkThread* workThreads;
public:
std::condition_variable notify;
std::atomic<int> busyCount;
/**
* push a new job to this work pool.
* if all work threads are busy, this will block until one is free.
*/
void push(const std::function<void(void*)>& what, void* arg);
/**
* check whether this work pool is busy.
*/
bool busy();
/**
* wait for all work threads to finish.
*/
void wait();
DivWorkPool(unsigned int threads=0);
~DivWorkPool();
};
#endif

View File

@ -79,6 +79,7 @@ const char* aboutLine[]={
"Burnt Fishy",
"CaptainMalware",
"Clingojam",
"Crisps",
"DeMOSic",
"DevEd",
"Dippy",
@ -125,6 +126,7 @@ const char* aboutLine[]={
"TakuikaNinja",
"TCORPStudios",
"Teuthida",
"ThaCuber",
"The Blender Fiddler",
"TheDuccinator",
"theloredev",
@ -160,6 +162,7 @@ const char* aboutLine[]={
"libsndfile by Erik de Castro Lopo",
"Portable File Dialogs by Sam Hocevar",
"Native File Dialog by Frogtoss Games",
"PortAudio",
"RtMidi by Gary P. Scavone",
"FFTW by Matteo Frigo and Steven G. Johnson",
"backward-cpp by Google",

View File

@ -363,10 +363,17 @@ void FurnaceGUI::drawChanOsc() {
std::vector<int> oscChans;
int chans=e->getTotalChannelCount();
ImGuiWindow* window=ImGui::GetCurrentWindow();
ImVec2 waveform[512];
ImGuiStyle& style=ImGui::GetStyle();
ImVec2 waveform[1024];
// check work thread
if (chanOscWorkPool==NULL) {
logV("creating chan osc work pool");
chanOscWorkPool=new DivWorkPool(settings.chanOscThreads);
}
// fill buffers
for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=e->getOscBuffer(i);
if (buf!=NULL && e->curSubSong->chanShow[i]) {
@ -376,6 +383,147 @@ void FurnaceGUI::drawChanOsc() {
}
}
// process
for (size_t i=0; i<oscBufs.size(); i++) {
ChanOscStatus* fft_=oscFFTs[i];
fft_->relatedBuf=oscBufs[i];
fft_->relatedCh=oscChans[i];
if (fft_->relatedBuf!=NULL) {
// prepare
if (centerSettingReset) {
fft_->relatedBuf->readNeedle=fft_->relatedBuf->needle;
}
// check FFT status existence
if (!fft_->ready) {
logD("creating FFT plan for channel %d",fft_->relatedCh);
fft_->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double));
fft_->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex));
fft_->corrBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double));
fft_->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft_->inBuf,fft_->outBuf,FFTW_ESTIMATE);
fft_->planI=fftw_plan_dft_c2r_1d(FURNACE_FFT_SIZE,fft_->outBuf,fft_->corrBuf,FFTW_ESTIMATE);
if (fft_->plan==NULL) {
logE("failed to create plan!");
} else if (fft_->planI==NULL) {
logE("failed to create inverse plan!");
} else if (fft_->inBuf==NULL || fft_->outBuf==NULL || fft_->corrBuf==NULL) {
logE("failed to create FFT buffers");
} else {
fft_->ready=true;
}
}
if (fft_->ready && e->isRunning()) {
chanOscWorkPool->push([this](void* fft_v) {
ChanOscStatus* fft=(ChanOscStatus*)fft_v;
DivDispatchOscBuffer* buf=fft->relatedBuf;
int ch=fft->relatedCh;
// the STRATEGY
// 1. FFT of windowed signal
// 2. inverse FFT of auto-correlation
// 3. find size of one period
// 4. DFT of the fundamental of ONE PERIOD
// 5. now we can get phase information
//
// I have a feeling this could be simplified to two FFTs or even one...
// if you know how, please tell me
// initialization
double phase=0.0;
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
fft->loudEnough=false;
fft->needle=buf->needle;
// first FFT
for (int j=0; j<FURNACE_FFT_SIZE; j++) {
fft->inBuf[j]=(double)buf->data[(unsigned short)(fft->needle-displaySize*2+((j*displaySize*2)/(FURNACE_FFT_SIZE)))]/32768.0;
if (fft->inBuf[j]>0.001 || fft->inBuf[j]<-0.001) fft->loudEnough=true;
fft->inBuf[j]*=0.55-0.45*cos(M_PI*(double)j/(double)(FURNACE_FFT_SIZE>>1));
}
// only proceed if not quiet
if (fft->loudEnough) {
fftw_execute(fft->plan);
// auto-correlation and second FFT
for (int j=0; j<FURNACE_FFT_SIZE; j++) {
fft->outBuf[j][0]/=FURNACE_FFT_SIZE;
fft->outBuf[j][1]/=FURNACE_FFT_SIZE;
fft->outBuf[j][0]=fft->outBuf[j][0]*fft->outBuf[j][0]+fft->outBuf[j][1]*fft->outBuf[j][1];
fft->outBuf[j][1]=0;
}
fft->outBuf[0][0]=0;
fft->outBuf[0][1]=0;
fft->outBuf[1][0]=0;
fft->outBuf[1][1]=0;
fftw_execute(fft->planI);
// window
for (int j=0; j<(FURNACE_FFT_SIZE>>1); j++) {
fft->corrBuf[j]*=1.0-((double)j/(double)(FURNACE_FFT_SIZE<<1));
}
// find size of period
double waveLenCandL=DBL_MAX;
double waveLenCandH=DBL_MIN;
fft->waveLen=FURNACE_FFT_SIZE-1;
fft->waveLenBottom=0;
fft->waveLenTop=0;
// find lowest point
for (int j=(FURNACE_FFT_SIZE>>2); j>2; j--) {
if (fft->corrBuf[j]<waveLenCandL) {
waveLenCandL=fft->corrBuf[j];
fft->waveLenBottom=j;
}
}
// find highest point
for (int j=(FURNACE_FFT_SIZE>>1)-1; j>fft->waveLenBottom; j--) {
if (fft->corrBuf[j]>waveLenCandH) {
waveLenCandH=fft->corrBuf[j];
fft->waveLen=j;
}
}
fft->waveLenTop=fft->waveLen;
// did we find the period size?
if (fft->waveLen<(FURNACE_FFT_SIZE-32)) {
// we got pitch
chanOscPitch[ch]=pow(1.0-(fft->waveLen/(double)(FURNACE_FFT_SIZE>>1)),4.0);
fft->waveLen*=(double)displaySize*2.0/(double)FURNACE_FFT_SIZE;
// DFT of one period (x_1)
double dft[2];
dft[0]=0.0;
dft[1]=0.0;
for (int j=fft->needle-1-(displaySize>>1)-(int)fft->waveLen, k=0; k<fft->waveLen; j++, k++) {
double one=((double)buf->data[j&0xffff]/32768.0);
double two=(double)k*(-2.0*M_PI)/fft->waveLen;
dft[0]+=one*cos(two);
dft[1]+=one*sin(two);
}
// calculate and lock into phase
phase=(0.5+(atan2(dft[1],dft[0])/(2.0*M_PI)));
if (chanOscWaveCorr) {
fft->needle-=phase*fft->waveLen;
}
}
}
fft->needle-=displaySize;
},fft_);
}
}
}
chanOscWorkPool->wait();
// 0: none
// 1: sqrt(chans)
// 2: sqrt(chans+1)
@ -396,6 +544,7 @@ void FurnaceGUI::drawChanOsc() {
int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols;
// render
for (size_t i=0; i<oscBufs.size(); i++) {
if (i%chanOscCols==0) ImGui::TableNextRow();
ImGui::TableNextColumn();
@ -409,20 +558,6 @@ void FurnaceGUI::drawChanOsc() {
ImVec2 size=ImGui::GetContentRegionAvail();
size.y=availY/rows;
if (centerSettingReset) {
buf->readNeedle=buf->needle;
}
// check FFT status existence
if (fft->plan==NULL) {
logD("creating FFT plan for channel %d",ch);
fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double));
fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex));
fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE);
}
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
ImVec2 minArea=window->DC.CursorPos;
ImVec2 maxArea=ImVec2(
minArea.x+size.x,
@ -437,69 +572,78 @@ void FurnaceGUI::drawChanOsc() {
int precision=inRect.Max.x-inRect.Min.x;
if (precision<1) precision=1;
if (precision>512) precision=512;
if (precision>1024) precision=1024;
ImGui::ItemSize(size,style.FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) {
if (!e->isRunning()) {
for (unsigned short i=0; i<precision; i++) {
float x=(float)i/(float)precision;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
for (unsigned short j=0; j<precision; j++) {
float x=(float)j/(float)precision;
waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
}
} else {
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
float minLevel=1.0f;
float maxLevel=-1.0f;
float dcOff=0.0f;
unsigned short needlePos=buf->needle;
//unsigned short needlePosOrig=needlePos;
for (int i=0; i<FURNACE_FFT_SIZE; i++) {
fft->inBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0;
}
fftw_execute(fft->plan);
// find origin frequency
int point=1;
double candAmp=0.0;
for (unsigned short i=1; i<512; i++) {
fftw_complex& f=fft->outBuf[i];
// AMPLITUDE
double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8);
if (amp>candAmp) {
point=i;
candAmp=amp;
if (debugFFT) {
// FFT debug code!
double maxavg=0.0;
for (unsigned short j=0; j<(FURNACE_FFT_SIZE>>1); j++) {
if (fabs(fft->corrBuf[j]>maxavg)) {
maxavg=fabs(fft->corrBuf[j]);
}
}
if (maxavg>0.0000001) maxavg=0.5/maxavg;
for (unsigned short j=0; j<precision; j++) {
float x=(float)j/(float)precision;
float y;
if (j>=precision/2) {
y=fft->inBuf[((j-(precision/2))*FURNACE_FFT_SIZE*2)/(precision)];
} else {
y=fft->corrBuf[(j*FURNACE_FFT_SIZE)/precision]*maxavg;
}
waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y));
}
if (fft->loudEnough) {
String cPhase=fmt::sprintf("\n%.1f (b: %d t: %d)",fft->waveLen,fft->waveLenBottom,fft->waveLenTop);
dl->AddText(inRect.Min,0xffffffff,cPhase.c_str());
dl->AddLine(
ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenBottom/(double)FURNACE_FFT_SIZE,0.0)),
ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenBottom/(double)FURNACE_FFT_SIZE,1.0)),
0xffffff00
);
dl->AddLine(
ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenTop/(double)FURNACE_FFT_SIZE,0.0)),
ImLerp(inRect.Min,inRect.Max,ImVec2((double)fft->waveLenTop/(double)FURNACE_FFT_SIZE,1.0)),
0xff00ff00
);
} else {
if (debugFFT) {
dl->AddText(inRect.Min,0xffffffff,"\nquiet");
}
}
} else {
for (unsigned short j=0; j<precision; j++) {
float y=(float)buf->data[(unsigned short)(fft->needle+(j*displaySize/precision))]/32768.0f;
if (minLevel>y) minLevel=y;
if (maxLevel<y) maxLevel=y;
}
dcOff=(minLevel+maxLevel)*0.5f;
for (unsigned short j=0; j<precision; j++) {
float x=(float)j/(float)precision;
float y=(float)buf->data[(unsigned short)(fft->needle+(j*displaySize/precision))]/32768.0f;
y-=dcOff;
if (y<-0.5f) y=-0.5f;
if (y>0.5f) y=0.5f;
y*=chanOscAmplify;
waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y));
}
}
// PHASE
fftw_complex& candPoint=fft->outBuf[point];
double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2)));
if (chanOscWaveCorr) {
needlePos-=phase;
}
chanOscPitch[ch]=(float)point/32.0f;
needlePos-=displaySize;
for (unsigned short i=0; i<precision; i++) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f;
if (minLevel>y) minLevel=y;
if (maxLevel<y) maxLevel=y;
}
dcOff=(minLevel+maxLevel)*0.5f;
for (unsigned short i=0; i<precision; i++) {
float x=(float)i/(float)precision;
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f;
y-=dcOff;
if (y<-0.5f) y=-0.5f;
if (y>0.5f) y=0.5f;
y*=chanOscAmplify;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y));
}
//String cPhase=fmt::sprintf("%d cphase: %f\nvol: %f\nmin: %.2f\nmax: %.2f\ndcOff: %.2f\nneedles:\n- %d\n- %d\n- %d (%s)",point,phase,chanOscVol[ch],minLevel,maxLevel,dcOff,needlePosOrig,needlePos,(needlePos+displaySize),((needlePos+displaySize)>=needlePosOrig)?"WARN":"OK");
//dl->AddText(inRect.Min,0xffffffff,cPhase.c_str());
}
ImU32 color=ImGui::GetColorU32(chanOscColor);
if (chanOscUseGrad) {
@ -513,15 +657,20 @@ void FurnaceGUI::drawChanOsc() {
}
ImGui::PushClipRect(inRect.Min,inRect.Max,false);
ImDrawListFlags prevFlags=dl->Flags;
//if (!settings.oscAntiAlias) {
dl->Flags&=~(ImDrawListFlags_AntiAliasedLines|ImDrawListFlags_AntiAliasedLinesUseTex);
//}
dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale);
dl->Flags=prevFlags;
if (!chanOscTextFormat.empty()) {
String text;
bool inFormat=false;
for (char i: chanOscTextFormat) {
for (char j: chanOscTextFormat) {
if (inFormat) {
switch (i) {
switch (j) {
case 'c':
text+=e->getChannelName(ch);
break;
@ -562,7 +711,7 @@ void FurnaceGUI::drawChanOsc() {
break;
}
case 'p': {
text+=FurnaceGUI::getSystemPartNumber(e->sysOfChan[ch], e->song.systemFlags[e->dispatchOfChan[ch]]);
text+=FurnaceGUI::getSystemPartNumber(e->sysOfChan[ch],e->song.systemFlags[e->dispatchOfChan[ch]]);
break;
}
case 'S': {
@ -603,19 +752,18 @@ void FurnaceGUI::drawChanOsc() {
break;
default:
text+='%';
text+=i;
text+=j;
break;
}
inFormat=false;
} else {
if (i=='%') {
if (j=='%') {
inFormat=true;
} else {
text+=i;
text+=j;
}
}
}
dl->AddText(ImLerp(inRect.Min,inRect.Max,ImVec2(0.0f,0.0f)),ImGui::GetColorU32(chanOscTextColor),text.c_str());
}

View File

@ -212,6 +212,7 @@ void FurnaceGUI::drawDebug() {
}
if (ImGui::TreeNode("Oscilloscope Debug")) {
int c=0;
ImGui::Checkbox("FFT debug view",&debugFFT);
for (int i=0; i<e->song.systemLen; i++) {
DivSystem system=e->song.system[i];
if (e->getChannelCount(system)>0) {

View File

@ -76,7 +76,38 @@ void _nfdThread(const NFDState state, std::atomic<bool>* ok, std::vector<String>
}
#endif
bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback, bool allowMultiple) {
void FurnaceGUIFileDialog::convertFilterList(std::vector<String>& filter) {
memset(noSysFilter,0,4096);
String result;
for (size_t i=0; (i+1)<filter.size(); i+=2) {
String label=filter[i];
String ext;
if (filter[i+1]=="*") {
ext=".*";
} else for (char i: filter[i+1]) {
switch (i) {
case '*':
break;
case ' ':
ext+=',';
break;
default:
ext+=i;
break;
}
}
if (!result.empty()) result+=',';
result+=fmt::sprintf("%s{%s}",label,ext);
}
strncpy(noSysFilter,result.c_str(),4095);
}
bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, String path, double dpiScale, FileDialogSelectCallback clickCallback, bool allowMultiple) {
if (opened) return false;
saving=false;
curPath=path;
@ -149,6 +180,8 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, c
}
#endif
convertFilterList(filter);
ImGuiFileDialog::Instance()->singleClickSel=mobileUI;
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->mobileMode=mobileUI;
@ -159,7 +192,7 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, c
return true;
}
bool FurnaceGUIFileDialog::openSave(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale) {
bool FurnaceGUIFileDialog::openSave(String header, std::vector<String> filter, String path, double dpiScale) {
if (opened) return false;
#ifdef ANDROID
@ -233,6 +266,8 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector<String> filter, c
} else {
hasError=false;
convertFilterList(filter);
ImGuiFileDialog::Instance()->singleClickSel=false;
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->mobileMode=mobileUI;

View File

@ -31,6 +31,7 @@ class FurnaceGUIFileDialog {
bool opened;
bool saving;
bool hasError;
char noSysFilter[4096];
String curPath;
std::vector<String> fileName;
#ifdef USE_NFD
@ -46,10 +47,12 @@ class FurnaceGUIFileDialog {
pfd::open_file* dialogO;
pfd::save_file* dialogS;
#endif
void convertFilterList(std::vector<String>& filter);
public:
bool mobileUI;
bool openLoad(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale, FileDialogSelectCallback clickCallback=NULL, bool allowMultiple=false);
bool openSave(String header, std::vector<String> filter, const char* noSysFilter, String path, double dpiScale);
bool openLoad(String header, std::vector<String> filter, String path, double dpiScale, FileDialogSelectCallback clickCallback=NULL, bool allowMultiple=false);
bool openSave(String header, std::vector<String> filter, String path, double dpiScale);
bool accepted();
void close();
bool render(const ImVec2& min, const ImVec2& max);

View File

@ -417,6 +417,7 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe
}
bool FurnaceGUI::CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) {
flags^=ImGuiSliderFlags_AlwaysClamp;
if (ImGui::SliderScalar(label,data_type,p_data,p_min,p_max,format,flags)) {
return true;
}
@ -453,6 +454,7 @@ bool FurnaceGUI::CWSliderScalar(const char* label, ImGuiDataType data_type, void
}
bool FurnaceGUI::CWVSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) {
flags^=ImGuiSliderFlags_AlwaysClamp;
if (ImGui::VSliderScalar(label,size,data_type,p_data,p_min,p_max,format,flags)) {
return true;
}
@ -1565,7 +1567,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Open File",
{"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod *.fc",
"all files", "*"},
"compatible files{.fur,.dmf,.mod,.fc13,.fc14,.smod,.fc},.*",
workingDirSong,
dpiScale
);
@ -1578,7 +1579,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad(
"Restore Backup",
{"Furnace song", "*.fur"},
"Furnace song{.fur}",
backupPath+String(DIR_SEPARATOR_STR),
dpiScale
);
@ -1588,7 +1588,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save File",
{"Furnace song", "*.fur"},
"Furnace song{.fur}",
workingDirSong,
dpiScale
);
@ -1598,7 +1597,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save File",
{"DefleMask 1.1.3 module", "*.dmf"},
"DefleMask 1.1.3 module{.dmf}",
workingDirSong,
dpiScale
);
@ -1608,7 +1606,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save File",
{"DefleMask 1.0/legacy module", "*.dmf"},
"DefleMask 1.0/legacy module{.dmf}",
workingDirSong,
dpiScale
);
@ -1625,8 +1622,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
hasOpened=fileDialog->openLoad(
"Load Instrument",
// TODO supply loadable formats in a dynamic, scalable, "DRY" way.
// thank the author of IGFD for making things impossible
{"all compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.gyb *.opm *.wopl *.wopn",
"Furnace instrument", "*.fui",
"DefleMask preset", "*.dmp",
@ -1644,7 +1639,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Wohlstand WOPL bank", "*.wopl",
"Wohlstand WOPN bank", "*.wopn",
"all files", "*"},
"all compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.opli,.opni,.y12,.bnk,.ff,.gyb,.opm,.wopl,.wopn},.*",
workingDirIns,
dpiScale,
[this](const char* path) {
@ -1679,7 +1673,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save Instrument",
{"Furnace instrument", "*.fui"},
"Furnace instrument{.fui}",
workingDirIns,
dpiScale
);
@ -1689,7 +1682,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save Instrument",
{"DefleMask preset", "*.dmp"},
"DefleMask preset{.dmp}",
workingDirIns,
dpiScale
);
@ -1701,7 +1693,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Load Wavetable",
{"compatible files", "*.fuw *.dmw",
"all files", "*"},
"compatible files{.fuw,.dmw},.*",
workingDirWave,
dpiScale,
NULL, // TODO
@ -1713,7 +1704,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save Wavetable",
{"Furnace wavetable", ".fuw"},
"Furnace wavetable{.fuw}",
workingDirWave,
dpiScale
);
@ -1723,7 +1713,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save Wavetable",
{"DefleMask wavetable", ".dmw"},
"DefleMask wavetable{.dmw}",
workingDirWave,
dpiScale
);
@ -1733,7 +1722,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save Wavetable",
{"raw data", ".raw"},
"raw data{.raw}",
workingDirWave,
dpiScale
);
@ -1745,7 +1733,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Load Sample",
{"compatible files", "*.wav *.dmc *.brr",
"all files", "*"},
"compatible files{.wav,.dmc,.brr},.*",
workingDirSample,
dpiScale,
NULL, // TODO
@ -1758,7 +1745,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad(
"Load Raw Sample",
{"all files", "*"},
".*",
workingDirSample,
dpiScale
);
@ -1768,7 +1754,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Save Sample",
{"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirSample,
dpiScale
);
@ -1776,9 +1761,8 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
case GUI_FILE_SAMPLE_SAVE_RAW:
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
hasOpened=fileDialog->openSave(
"Load Raw Sample",
"Save Raw Sample",
{"all files", "*"},
".*",
workingDirSample,
dpiScale
);
@ -1788,7 +1772,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export Audio",
{"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirAudioExport,
dpiScale
);
@ -1798,7 +1781,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export Audio",
{"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirAudioExport,
dpiScale
);
@ -1808,7 +1790,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export Audio",
{"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirAudioExport,
dpiScale
);
@ -1818,7 +1799,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export VGM",
{"VGM file", "*.vgm"},
"VGM file{.vgm}",
workingDirVGMExport,
dpiScale
);
@ -1828,7 +1808,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export ZSM",
{"ZSM file", "*.zsm"},
"ZSM file{.zsm}",
workingDirZSMExport,
dpiScale
);
@ -1838,7 +1817,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export Command Stream",
{"text file", "*.txt"},
"text file{.txt}",
workingDirROMExport,
dpiScale
);
@ -1848,7 +1826,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export Command Stream",
{"binary file", "*.bin"},
"binary file{.bin}",
workingDirROMExport,
dpiScale
);
@ -1861,7 +1838,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad(
"Select Font",
{"compatible files", "*.ttf *.otf *.ttc"},
"compatible files{.ttf,.otf,.ttc}",
workingDirFont,
dpiScale
);
@ -1871,7 +1847,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad(
"Select Font",
{"compatible files", "*.ttf *.otf *.ttc"},
"compatible files{.ttf,.otf,.ttc}",
workingDirFont,
dpiScale
);
@ -1881,7 +1856,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad(
"Select Font",
{"compatible files", "*.ttf *.otf *.ttc"},
"compatible files{.ttf,.otf,.ttc}",
workingDirFont,
dpiScale
);
@ -1891,7 +1865,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad(
"Select Color File",
{"configuration files", "*.cfgc"},
"configuration files{.cfgc}",
workingDirColors,
dpiScale
);
@ -1901,7 +1874,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad(
"Select Keybind File",
{"configuration files", "*.cfgk"},
"configuration files{.cfgk}",
workingDirKeybinds,
dpiScale
);
@ -1911,7 +1883,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad(
"Select Layout File",
{".ini files", "*.ini"},
".ini files{.ini}",
workingDirKeybinds,
dpiScale
);
@ -1921,7 +1892,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export Colors",
{"configuration files", "*.cfgc"},
"configuration files{.cfgc}",
workingDirColors,
dpiScale
);
@ -1931,7 +1901,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export Keybinds",
{"configuration files", "*.cfgk"},
"configuration files{.cfgk}",
workingDirKeybinds,
dpiScale
);
@ -1941,7 +1910,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave(
"Export Layout",
{".ini files", "*.ini"},
".ini files{.ini}",
workingDirKeybinds,
dpiScale
);
@ -1954,7 +1922,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Load ROM",
{"compatible files", "*.rom *.bin",
"all files", "*"},
"compatible files{.rom,.bin},.*",
workingDirROM,
dpiScale
);
@ -1965,7 +1932,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Play Command Stream",
{"command stream", "*.bin",
"all files", "*"},
"command stream{.bin},.*",
workingDirROM,
dpiScale
);
@ -1977,7 +1943,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
{"compatible files", "*.fur *.dmf *.mod",
"another option", "*.wav *.ttf",
"all files", "*"},
"compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*",
workingDirTest,
dpiScale,
[](const char* path) {
@ -1996,7 +1961,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
{"compatible files", "*.fur *.dmf *.mod",
"another option", "*.wav *.ttf",
"all files", "*"},
"compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*",
workingDirTest,
dpiScale,
[](const char* path) {
@ -2015,7 +1979,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Save Test",
{"Furnace song", "*.fur",
"DefleMask module", "*.dmf"},
"Furnace song{.fur},DefleMask module{.dmf}",
workingDirTest,
dpiScale
);
@ -3491,6 +3454,7 @@ bool FurnaceGUI::loop() {
scrX=ev.window.data1;
scrY=ev.window.data2;
updateWindow=true;
shallDetectScale=2;
logV("window moved to %dx%d",scrX,scrY);
break;
case SDL_WINDOWEVENT_SIZE_CHANGED:
@ -6140,7 +6104,38 @@ bool FurnaceGUI::loop() {
if (shallDetectScale) {
if (--shallDetectScale<1) {
if (settings.dpiScale<0.5f) {
applyUISettings();
const char* videoBackend=SDL_GetCurrentVideoDriver();
double newScale=getScaleFactor(videoBackend,sdlWin);
if (newScale<0.1f) {
logW("scale what?");
newScale=1.0f;
}
if (newScale!=dpiScale) {
logD("auto UI scale changed (%f != %f) - applying settings...",newScale,dpiScale);
ImGui::GetIO().Fonts->Clear();
applyUISettings();
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error while building font atlas!");
showError("error while loading fonts! please check your settings.");
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
bigFont=mainFont;
headFont=mainFont;
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
} else {
rend->createFontsTexture();
}
} else {
rend->createFontsTexture();
}
}
}
}
}
@ -6368,7 +6363,7 @@ bool FurnaceGUI::init() {
dpiScale=settings.dpiScale;
} else {
logD("auto-detecting UI scale factor.");
dpiScale=getScaleFactor(videoBackend);
dpiScale=getScaleFactor(videoBackend,sdlWin);
logD("scale factor: %f",dpiScale);
if (dpiScale<0.1f) {
logW("scale what?");
@ -6690,6 +6685,9 @@ bool FurnaceGUI::init() {
}
#endif
cpuCores=SDL_GetCPUCount();
if (cpuCores<1) cpuCores=1;
logI("done!");
return true;
}
@ -6862,6 +6860,10 @@ bool FurnaceGUI::finish() {
backupTask.get();
}
if (chanOscWorkPool!=NULL) {
delete chanOscWorkPool;
}
return true;
}
@ -6914,6 +6916,9 @@ FurnaceGUI::FurnaceGUI():
displayEditString(false),
mobileEdit(false),
killGraphics(false),
audioEngineChanged(false),
settingsChanged(false),
debugFFT(false),
vgmExportVersion(0x171),
vgmExportTrailingTicks(-1),
drawHalt(10),
@ -7283,6 +7288,7 @@ FurnaceGUI::FurnaceGUI():
chanOscTextColor(1.0f,1.0f,1.0f,0.75f),
chanOscGrad(64,64),
chanOscGradTex(NULL),
chanOscWorkPool(NULL),
followLog(true),
#ifdef IS_MOBILE
pianoOctaves(7),

View File

@ -21,6 +21,7 @@
#define _FUR_GUI_H
#include "../engine/engine.h"
#include "../engine/workPool.h"
#include "../engine/waveSynth.h"
#include "imgui.h"
#include "imgui_impl_sdl2.h"
@ -1334,6 +1335,7 @@ class FurnaceGUI {
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString;
bool mobileEdit;
bool killGraphics;
bool audioEngineChanged, settingsChanged, debugFFT;
bool willExport[DIV_MAX_CHIPS];
int vgmExportVersion;
int vgmExportTrailingTicks;
@ -1345,6 +1347,7 @@ class FurnaceGUI {
int mobileEditPage;
int wheelCalmDown;
int shallDetectScale;
int cpuCores;
float mobileMenuPos, autoButtonSize, mobileEditAnim;
ImVec2 mobileEditButtonPos, mobileEditButtonSize;
const int* curSysSection;
@ -1570,6 +1573,8 @@ class FurnaceGUI {
int centerPopup;
int insIconsStyle;
int classicChipOptions;
int wasapiEx;
int chanOscThreads;
unsigned int maxUndoSteps;
String mainFontPath;
String headFontPath;
@ -1744,6 +1749,8 @@ class FurnaceGUI {
centerPopup(1),
insIconsStyle(1),
classicChipOptions(0),
wasapiEx(0),
chanOscThreads(0),
maxUndoSteps(100),
mainFontPath(""),
headFontPath(""),
@ -2044,6 +2051,7 @@ class FurnaceGUI {
ImVec4 chanOscColor, chanOscTextColor;
Gradient2D chanOscGrad;
FurnaceGUITexture* chanOscGradTex;
DivWorkPool* chanOscWorkPool;
float chanOscLP0[DIV_MAX_CHANS];
float chanOscLP1[DIV_MAX_CHANS];
float chanOscVol[DIV_MAX_CHANS];
@ -2053,18 +2061,33 @@ class FurnaceGUI {
unsigned short lastCorrPos[DIV_MAX_CHANS];
struct ChanOscStatus {
double* inBuf;
fftw_complex* outBuf;
double* corrBuf;
DivDispatchOscBuffer* relatedBuf;
size_t inBufPos;
double inBufPosFrac;
double waveLen;
int waveLenBottom, waveLenTop, relatedCh;
unsigned short needle;
fftw_complex* outBuf;
bool ready, loudEnough;
fftw_plan plan;
fftw_plan planI;
ChanOscStatus():
inBuf(NULL),
outBuf(NULL),
corrBuf(NULL),
relatedBuf(NULL),
inBufPos(0),
inBufPosFrac(0.0f),
waveLen(0.0),
waveLenBottom(0),
waveLenTop(0),
relatedCh(0),
needle(0),
outBuf(NULL),
plan(NULL) {}
ready(false),
loudEnough(false),
plan(NULL),
planI(NULL) {}
} chanOscChan[DIV_MAX_CHANS];
// visualizer

View File

@ -1802,7 +1802,9 @@ void FurnaceGUI::drawMacroEdit(FurnaceGUIMacroDesc& i, int totalFit, float avail
}
#define BUTTON_TO_SET_PROPS(_x) \
pushToggleColors(_x.macro->speed!=1 || _x.macro->delay); \
ImGui::Button(ICON_FA_ELLIPSIS_H "##IMacroSet"); \
popToggleColors(); \
if (ImGui::IsItemHovered()) { \
ImGui::SetTooltip("Delay/Step Length"); \
} \

File diff suppressed because it is too large Load Diff

View File

@ -17,4 +17,4 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
double getMacDPIScale();
double getMacDPIScale(void* sysWin, unsigned char isUIKit);

View File

@ -20,7 +20,30 @@
#include <Cocoa/Cocoa.h>
#include "macstuff.h"
double getMacDPIScale() {
CGFloat val=[[NSScreen mainScreen] backingScaleFactor];
double getMacDPIScale(void* sysWin, unsigned char isUIKit) {
NSScreen* screen=nil;
if (sysWin!=NULL) {
if (isUIKit) {
return 1.0;
/*
UIWindow* win=(UIWindow*)sysWin;
UIWindowScene* winScene=[win windowScene];
if (winScene!=nil) {
UIScreen* winScreen=[winScene screen];
CGFloat ret=[winScreen scale];
return (double)ret;
}*/
} else {
NSWindow* win=(NSWindow*)sysWin;
screen=[win screen];
}
}
if (screen==nil) {
screen=[NSScreen mainScreen];
}
if (screen==nil) {
return 1.0;
}
CGFloat val=[screen backingScaleFactor];
return (double)val;
}

View File

@ -1197,6 +1197,7 @@ void FurnaceGUI::drawSampleEdit() {
sameLineMaybe(ImGui::CalcTextSize("Zoom").x+150.0f*dpiScale+ImGui::CalcTextSize("100%").x);
double zoomPercent=100.0/sampleZoom;
bool checkZoomLimit=false;
ImGui::AlignTextToFramePadding();
ImGui::Text("Zoom");
ImGui::SameLine();
ImGui::SetNextItemWidth(150.0f*dpiScale);
@ -1675,7 +1676,7 @@ void FurnaceGUI::drawSampleEdit() {
}
dl->PushClipRect(rectMin,rectMax);
if (e->isPreviewingSample()) {
if (e->isPreviewingSample() && e->getSamplePreviewSample()==curSample) {
if (!statusBar2.empty()) {
statusBar2+=" | ";
}

View File

@ -21,6 +21,7 @@
#include "scaling.h"
#include "../ta-log.h"
#include <SDL.h>
#include <SDL_syswm.h>
#ifdef _WIN32
#include <windows.h>
@ -42,14 +43,23 @@ typedef int (*XDS)(void*);
typedef int (*XDW)(void*,int);
#endif
double getScaleFactor(const char* driverHint) {
double getScaleFactor(const char* driverHint, void* windowHint) {
double ret=1.0;
// Windows
#ifdef _WIN32
POINT nullPoint;
nullPoint.x=-1;
nullPoint.y=-1;
if (windowHint!=NULL) {
int px=0;
int py=0;
SDL_GetWindowPosition((SDL_Window*)windowHint,&px,&py);
nullPoint.x=px;
nullPoint.y=py;
} else {
nullPoint.x=-1;
nullPoint.y=-1;
}
HMONITOR disp=MonitorFromPoint(nullPoint,MONITOR_DEFAULTTOPRIMARY);
if (disp==NULL) {
@ -93,12 +103,34 @@ double getScaleFactor(const char* driverHint) {
return ret;
#endif
// macOS - backingScaleFactor
// macOS
#ifdef __APPLE__
if (driverHint==NULL) {
return getMacDPIScale();
} else if (strcmp(driverHint,"cocoa")==0 || strcmp(driverHint,"uikit")==0) {
return getMacDPIScale();
return getMacDPIScale(NULL,false);
} else if (strcmp(driverHint,"cocoa")==0) {
#ifdef SDL_VIDEO_DRIVER_COCOA
void* nsWindow=NULL;
SDL_SysWMinfo wmInfo;
if (windowHint!=NULL) {
SDL_VERSION(&wmInfo.version)
if (SDL_GetWindowWMInfo((SDL_Window*)windowHint,&wmInfo)==SDL_TRUE) {
nsWindow=wmInfo.info.cocoa.window;
}
}
return getMacDPIScale(nsWindow,false);
#endif
} else if (strcmp(driverHint,"uikit")==0) {
#ifdef SDL_VIDEO_DRIVER_UIKIT
void* uiWindow=NULL;
SDL_SysWMinfo wmInfo;
if (windowHint!=NULL) {
SDL_VERSION(&wmInfo.version)
if (SDL_GetWindowWMInfo((SDL_Window*)windowHint,&wmInfo)==SDL_TRUE) {
uiWindow=wmInfo.info.uikit.window;
}
}
return getMacDPIScale(uiWindow,true);
#endif
}
#endif

View File

@ -17,4 +17,4 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
double getScaleFactor(const char* driverHint);
double getScaleFactor(const char* driverHint, void* windowHint);

View File

@ -82,11 +82,13 @@ const char* patFonts[]={
const char* audioBackends[]={
"JACK",
"SDL"
"SDL",
"PortAudio"
};
const bool isProAudio[]={
true,
false,
false
};
@ -398,6 +400,27 @@ void FurnaceGUI::drawSettings() {
ImGui::SetTooltip("may cause issues with high-polling-rate mice when previewing notes.");
}
pushWarningColor(settings.chanOscThreads>cpuCores,settings.chanOscThreads>(cpuCores*2));
if (ImGui::InputInt("Per-channel oscilloscope threads",&settings.chanOscThreads)) {
if (settings.chanOscThreads<0) settings.chanOscThreads=0;
if (settings.chanOscThreads>(cpuCores*3)) settings.chanOscThreads=cpuCores*3;
if (settings.chanOscThreads>256) settings.chanOscThreads=256;
}
if (settings.chanOscThreads>=(cpuCores*3)) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("you're being silly, aren't you? that's enough.");
}
} else if (settings.chanOscThreads>(cpuCores*2)) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("what are you doing? stop!");
}
} else if (settings.chanOscThreads>cpuCores) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("it is a bad idea to set this number higher than your CPU core count (%d)!",cpuCores);
}
}
popWarningColor();
// SUBSECTION FILE
CONFIG_SUBSECTION("File");
@ -724,17 +747,33 @@ void FurnaceGUI::drawSettings() {
if (ImGui::BeginTable("##Output",2)) {
ImGui::TableSetupColumn("##Label",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("##Combo",ImGuiTableColumnFlags_WidthStretch);
#ifdef HAVE_JACK
#if defined(HAVE_JACK) || defined(HAVE_PA)
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Backend");
ImGui::TableNextColumn();
int prevAudioEngine=settings.audioEngine;
if (ImGui::Combo("##Backend",&settings.audioEngine,audioBackends,2)) {
if (ImGui::BeginCombo("##Backend",audioBackends[settings.audioEngine])) {
#ifdef HAVE_JACK
if (ImGui::Selectable("JACK",settings.audioEngine==DIV_AUDIO_JACK)) {
settings.audioEngine=DIV_AUDIO_JACK;
}
#endif
if (ImGui::Selectable("SDL",settings.audioEngine==DIV_AUDIO_SDL)) {
settings.audioEngine=DIV_AUDIO_SDL;
}
#ifdef HAVE_PA
if (ImGui::Selectable("PortAudio",settings.audioEngine==DIV_AUDIO_PORTAUDIO)) {
settings.audioEngine=DIV_AUDIO_PORTAUDIO;
}
#endif
if (settings.audioEngine!=prevAudioEngine) {
audioEngineChanged=true;
settings.audioDevice="";
if (!isProAudio[settings.audioEngine]) settings.audioChans=2;
}
ImGui::EndCombo();
}
#endif
@ -765,17 +804,30 @@ void FurnaceGUI::drawSettings() {
ImGui::AlignTextToFramePadding();
ImGui::Text("Device");
ImGui::TableNextColumn();
String audioDevName=settings.audioDevice.empty()?"<System default>":settings.audioDevice;
if (ImGui::BeginCombo("##AudioDevice",audioDevName.c_str())) {
if (ImGui::Selectable("<System default>",settings.audioDevice.empty())) {
settings.audioDevice="";
}
for (String& i: e->getAudioDevices()) {
if (ImGui::Selectable(i.c_str(),i==settings.audioDevice)) {
settings.audioDevice=i;
if (audioEngineChanged) {
ImGui::BeginDisabled();
if (ImGui::BeginCombo("##AudioDevice","<click on OK or Apply first>")) {
ImGui::Text("ALERT - TRESPASSER DETECTED");
if (ImGui::IsItemHovered()) {
showError("you have been arrested for trying to engage with a disabled combo box.");
ImGui::CloseCurrentPopup();
}
ImGui::EndCombo();
}
ImGui::EndDisabled();
} else {
String audioDevName=settings.audioDevice.empty()?"<System default>":settings.audioDevice;
if (ImGui::BeginCombo("##AudioDevice",audioDevName.c_str())) {
if (ImGui::Selectable("<System default>",settings.audioDevice.empty())) {
settings.audioDevice="";
}
for (String& i: e->getAudioDevices()) {
if (ImGui::Selectable(i.c_str(),i==settings.audioDevice)) {
settings.audioDevice=i;
}
}
ImGui::EndCombo();
}
ImGui::EndCombo();
}
ImGui::TableNextRow();
@ -841,11 +893,11 @@ void FurnaceGUI::drawSettings() {
}
bool lowLatencyB=settings.lowLatency;
if (ImGui::Checkbox("Low-latency mode (experimental!)",&lowLatencyB)) {
if (ImGui::Checkbox("Low-latency mode",&lowLatencyB)) {
settings.lowLatency=lowLatencyB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("reduces latency by running the engine faster than the tick rate.\nuseful for live playback/jam mode.\n\nwarning: experimental! may produce glitches.\nonly enable if your buffer size is small (10ms or less).");
ImGui::SetTooltip("reduces latency by running the engine faster than the tick rate.\nuseful for live playback/jam mode.\n\nwarning: nonly enable if your buffer size is small (10ms or less).");
}
bool forceMonoB=settings.forceMono;
@ -853,6 +905,15 @@ void FurnaceGUI::drawSettings() {
settings.forceMono=forceMonoB;
}
if (settings.audioEngine==DIV_AUDIO_PORTAUDIO) {
if (settings.audioDevice.find("[Windows WASAPI] ")==0) {
bool wasapiExB=settings.wasapiEx;
if (ImGui::Checkbox("Exclusive mode",&wasapiExB)) {
settings.wasapiEx=wasapiExB;
}
}
}
TAAudioDesc& audioWant=e->getAudioDescWant();
TAAudioDesc& audioGot=e->getAudioDescGot();
@ -1013,9 +1074,7 @@ void FurnaceGUI::drawSettings() {
ImGui::TableNextColumn();
ImGui::Text("Action");
ImGui::TableNextColumn();
ImGui::Text("Learn");
ImGui::TableNextColumn();
ImGui::Text("Remove");
for (size_t i=0; i<midiMap.binds.size(); i++) {
MIDIBind& bind=midiMap.binds[i];
@ -1117,13 +1176,15 @@ void FurnaceGUI::drawSettings() {
}
ImGui::TableNextColumn();
if (ImGui::Button((learning==(int)i)?("waiting...##BLearn"):(ICON_FA_SQUARE_O "##BLearn"))) {
pushToggleColors(learning==(int)i);
if (ImGui::Button((learning==(int)i)?("waiting...##BLearn"):("Learn##BLearn"))) {
if (learning==(int)i) {
learning=-1;
} else {
learning=i;
}
}
popToggleColors();
ImGui::TableNextColumn();
if (ImGui::Button(ICON_FA_TIMES "##BRemove")) {
@ -1231,7 +1292,7 @@ void FurnaceGUI::drawSettings() {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Arcade/YM2151");
ImGui::Text("YM2151");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::Combo("##ArcadeCore",&settings.arcadeCore,arcadeCores,2);
@ -1242,7 +1303,7 @@ void FurnaceGUI::drawSettings() {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding();
ImGui::Text("Genesis/YM2612");
ImGui::Text("YM2612");
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::Combo("##YM2612Core",&settings.ym2612Core,ym2612Cores,2);
@ -3019,6 +3080,7 @@ void FurnaceGUI::drawSettings() {
ImGui::SameLine();
if (ImGui::Button("Cancel##SettingsCancel")) {
settingsOpen=false;
audioEngineChanged=false;
syncSettings();
}
ImGui::SameLine();
@ -3045,6 +3107,13 @@ void FurnaceGUI::syncSettings() {
settings.patFontSize=e->getConfInt("patFontSize",18);
settings.iconSize=e->getConfInt("iconSize",16);
settings.audioEngine=(e->getConfString("audioEngine","SDL")=="SDL")?1:0;
if (e->getConfString("audioEngine","SDL")=="JACK") {
settings.audioEngine=DIV_AUDIO_JACK;
} else if (e->getConfString("audioEngine","SDL")=="PortAudio") {
settings.audioEngine=DIV_AUDIO_PORTAUDIO;
} else {
settings.audioEngine=DIV_AUDIO_SDL;
}
settings.audioDevice=e->getConfString("audioDevice","");
settings.audioChans=e->getConfInt("audioChans",2);
settings.midiInDevice=e->getConfString("midiInDevice","");
@ -3213,12 +3282,14 @@ void FurnaceGUI::syncSettings() {
settings.centerPopup=e->getConfInt("centerPopup",1);
settings.insIconsStyle=e->getConfInt("insIconsStyle",1);
settings.classicChipOptions=e->getConfInt("classicChipOptions",0);
settings.wasapiEx=e->getConfInt("wasapiEx",0);
settings.chanOscThreads=e->getConfInt("chanOscThreads",0);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.headFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
clampSetting(settings.iconSize,2,48);
clampSetting(settings.audioEngine,0,1);
clampSetting(settings.audioEngine,0,2);
clampSetting(settings.audioQuality,0,1);
clampSetting(settings.audioBufSize,32,4096);
clampSetting(settings.audioRate,8000,384000);
@ -3360,6 +3431,8 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.centerPopup,0,1);
clampSetting(settings.insIconsStyle,0,2);
clampSetting(settings.classicChipOptions,0,1);
clampSetting(settings.wasapiEx,0,1);
clampSetting(settings.chanOscThreads,0,256);
if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -3614,6 +3687,8 @@ void FurnaceGUI::commitSettings() {
e->setConf("centerPopup",settings.centerPopup);
e->setConf("insIconsStyle",settings.insIconsStyle);
e->setConf("classicChipOptions",settings.classicChipOptions);
e->setConf("wasapiEx",settings.wasapiEx);
e->setConf("chanOscThreads",settings.chanOscThreads);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {
@ -3671,6 +3746,8 @@ void FurnaceGUI::commitSettings() {
} else {
rend->createFontsTexture();
}
audioEngineChanged=false;
}
bool FurnaceGUI::importColors(String path) {
@ -4113,20 +4190,28 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
setupLabel(settings.emptyLabel.c_str(),emptyLabel,3);
setupLabel(settings.emptyLabel2.c_str(),emptyLabel2,2);
// get scale factor
const char* videoBackend=SDL_GetCurrentVideoDriver();
if (settings.dpiScale>=0.5f) {
logD("setting UI scale factor from config (%f).",settings.dpiScale);
dpiScale=settings.dpiScale;
} else {
logD("auto-detecting UI scale factor.");
dpiScale=getScaleFactor(videoBackend);
logD("scale factor: %f",dpiScale);
if (dpiScale<0.1f) {
logW("scale what?");
dpiScale=1.0f;
if (updateFonts) {
// get scale factor
const char* videoBackend=SDL_GetCurrentVideoDriver();
if (settings.dpiScale>=0.5f) {
logD("setting UI scale factor from config (%f).",settings.dpiScale);
dpiScale=settings.dpiScale;
} else {
logD("auto-detecting UI scale factor.");
dpiScale=getScaleFactor(videoBackend,sdlWin);
logD("scale factor: %f",dpiScale);
if (dpiScale<0.1f) {
logW("scale what?");
dpiScale=1.0f;
}
}
}
// chan osc work pool
if (chanOscWorkPool!=NULL) {
delete chanOscWorkPool;
chanOscWorkPool=NULL;
}
// colors
if (updateFonts) {

View File

@ -55,10 +55,10 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) {
return "2A03";
break;
case DIV_SYSTEM_C64_6581:
return "MOS 6581";
return "6581";
break;
case DIV_SYSTEM_C64_8580:
return "MOS 8580";
return "8580";
break;
case DIV_SYSTEM_Y8950:
case DIV_SYSTEM_Y8950_DRUMS:
@ -137,8 +137,8 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) {
case DIV_SYSTEM_YM2608_EXT:
return "YM2608";
break;
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:{
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:{
int patchSet=flags.getInt("patchSet",0);
if (patchSet==1) {
return "YMF281";
@ -210,9 +210,9 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) {
case DIV_SYSTEM_YM2610_FULL_EXT:
return "YM2610";
break;
case DIV_SYSTEM_OPLL:
case DIV_SYSTEM_OPLL_DRUMS:
return "YM2413";
case DIV_SYSTEM_OPL:
case DIV_SYSTEM_OPL_DRUMS:
return "YM3526";
break;
case DIV_SYSTEM_QSOUND:
return "QSound";
@ -274,6 +274,9 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) {
case DIV_SYSTEM_C140:
return "C140";
break;
case DIV_SYSTEM_C219:
return "C219";
break;
default:
return FurnaceGUI::getSystemName(sys);
break;

View File

@ -166,6 +166,7 @@ TAParamResult pVersion(String) {
printf("- libsndfile by Erik de Castro Lopo and rest of libsndfile team (LGPLv2.1)\n");
printf("- SDL2 by Sam Lantinga (zlib license)\n");
printf("- zlib by Jean-loup Gailly and Mark Adler (zlib license)\n");
printf("- PortAudio (PortAudio license)\n");
printf("- RtMidi by Gary P. Scavone (RtMidi license)\n");
printf("- backward-cpp by Google (MIT)\n");
printf("- Dear ImGui by Omar Cornut (MIT)\n");