Compare commits
59 Commits
Author | SHA1 | Date |
---|---|---|
tildearrow | ec4063641a | |
tildearrow | 803c8b0582 | |
tildearrow | fc760eed43 | |
tildearrow | eb18b28928 | |
tildearrow | d42b503e81 | |
tildearrow | 2c0ae7c8bb | |
tildearrow | 5f0fe2c8f7 | |
tildearrow | 1da000b00c | |
tildearrow | c99899a002 | |
tildearrow | 55eeb241cf | |
tildearrow | ad7b4f61b5 | |
tildearrow | 2ca5856800 | |
tildearrow | 60df7e26f4 | |
tildearrow | ab7b26a2e7 | |
tildearrow | 7a78ec1b60 | |
tildearrow | 90980a3062 | |
tildearrow | 83c64aa4b4 | |
thacuber2a03 | 25a83bf1b9 | |
tildearrow | 7ea5f2de07 | |
tildearrow | f6db75fae1 | |
tildearrow | 19d0ed617a | |
tildearrow | 20ed22d6e8 | |
tildearrow | b26a76f343 | |
tildearrow | ef23b88ad3 | |
tildearrow | 716d42ee6d | |
tildearrow | 1c171ed7bd | |
tildearrow | c21f880e3e | |
tildearrow | 4f02f1f90b | |
tildearrow | 65cd433ac7 | |
tildearrow | 5a9402abcd | |
tildearrow | fa7405090e | |
tildearrow | be38b992e3 | |
tildearrow | addbc986f0 | |
Eknous | 914855d751 | |
Eknous-P | 2a370dbb1f | |
tildearrow | b315b84e31 | |
tildearrow | 35aeb51b79 | |
tildearrow | 05d5eb5ca3 | |
tildearrow | 879e770e58 | |
tildearrow | 7f35d06ccb | |
tildearrow | 43ef57390a | |
tildearrow | 4ad1ae78fa | |
tildearrow | a882d7bcf2 | |
tildearrow | 9caa2f38f4 | |
tildearrow | 5140acd51f | |
tildearrow | 9aacc706f1 | |
Electric Keet | fb3a3890d5 | |
Electric Keet | 29c2879397 | |
tildearrow | 8b3fc84b51 | |
tildearrow | 922800d864 | |
tildearrow | 274ce8a646 | |
tildearrow | d1b78f787b | |
tildearrow | 68787a4d8b | |
Electric Keet | 1ebf828743 | |
tildearrow | 7d605c9d76 | |
tildearrow | 80013089a2 | |
tildearrow | 5a688c58cb | |
Electric Keet | da5d110e73 | |
Electric Keet | 2813e8e3b3 |
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
@ -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 |
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|------:|--------:|:----:|-----:|--------:|:----:|-----:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -745,6 +745,7 @@ namespace IGFD
|
|||
{
|
||||
public:
|
||||
std::string filter;
|
||||
std::string firstFilter;
|
||||
std::set<std::string> collectionfilters;
|
||||
|
||||
public:
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 6ee9836a08d201c118b4715d4d70242816584000
|
BIN
res/intro.fur
BIN
res/intro.fur
Binary file not shown.
|
@ -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*)∾
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -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) {}
|
||||
};
|
|
@ -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) {}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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]) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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",
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"); \
|
||||
} \
|
||||
|
|
24233
src/gui/introTune.cpp
24233
src/gui/introTune.cpp
File diff suppressed because it is too large
Load Diff
|
@ -17,4 +17,4 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
double getMacDPIScale();
|
||||
double getMacDPIScale(void* sysWin, unsigned char isUIKit);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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+=" | ";
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue