Merge branch 'master' into nmk112

This commit is contained in:
tildearrow 2023-09-07 01:41:22 -05:00
commit e839212aa0
41 changed files with 13138 additions and 12356 deletions

View file

@ -550,6 +550,7 @@ src/engine/blip_buf.c
src/engine/brrUtils.c src/engine/brrUtils.c
src/engine/safeReader.cpp src/engine/safeReader.cpp
src/engine/safeWriter.cpp src/engine/safeWriter.cpp
src/engine/workPool.cpp
src/engine/cmdStream.cpp src/engine/cmdStream.cpp
src/engine/cmdStreamOps.cpp src/engine/cmdStreamOps.cpp
src/engine/config.cpp src/engine/config.cpp

Binary file not shown.

Binary file not shown.

View file

@ -12,7 +12,7 @@ Furnace uses hexadecimal (abbreviated as "hex") numbers frequently. see [this gu
## interface ## interface
Furnace uses a music tracker interface. think of a table with music notes written on it. then that table scrolls up and plays the notes. even experienced tracker musicians might benefit from a quick review of [tracker concepts and terms](concepts.md) before using Furnace. Furnace uses a music tracker interface. think of a table with music notes written on it. then that table scrolls up and plays the notes. even experienced tracker musicians might benefit from a quick review of [tracker concepts and terms](concepts.md) before using Furnace. there's also a [glossary of common terms](glossary.md).
due to its nature of being feature-packed, it may be technical and somewhat difficult to get around. therefore we added a basic mode, which hides several advanced features. due to its nature of being feature-packed, it may be technical and somewhat difficult to get around. therefore we added a basic mode, which hides several advanced features.

139
doc/1-intro/glossary.md Normal file
View file

@ -0,0 +1,139 @@
# glossary of common terms
**2-op**, **3-op**, **4-op**...: the number of FM operators used to generate a sound. more operators allow for more complex sounds.
**ADPCM**: adaptive differential pulse code modulation. this is a variety of DPCM with a more complex method of storing the amplitude differences.
**ADSR**: attack, decay, sustain, release. these are the four necessary values for a basic volume envelope.
**algorithm**: the way in which the operators in an FM instrument interact.
- when two operators connect to the same point, their sounds are added together.
- when two operators are connected left to right, the left is the modulator and the right is the carrier sound that is modified.
**bitbang**: to achieve PCM sound by sending a rapid stream of volume commands to a non-PCM channel.
**BRR**: a lossy sample format used by the SNES. it has a fixed compression ratio; groups of 32 bytes (16 samples) are encoded in 9 bytes each.
- usually stored in .brr files.
**clipping**: when a sample or playback stream exceeds the maximum or minimum values. this can cause audible distortion.
- this often occurs when a sample is amplified too much.
- it can also occur during playback if too much sound is being added together at once. in some cases the mixer can be used to reduce the volume. if this doesn't work, the clipping is caused within the chip's own mixing, and the only solution is to reduce the volumes of the notes being played.
**clock rate**: the timing at which a chip operates, expressed as cycles per second (Hz).
- changing this may change aspects of how some chips work, most notably pitch.
- some chips cannot operate at anything other than their designed clock rate.
**cursor (1)**: the marker of input focus. anything typed will happen at the cursor's location.
**cursor (2)**: the pointer controlled by a mouse or similar input. clicking when the cursor(2) is in a valid area will place the cursor(1) there.
**DAC**: digital analog converter. this converts a digital representation of sound into actual output.
**.dmf**: DefleMask Module File.
- _Furnace:_ .dmf files may be read, and compatibility flags will be set to make them play as accurately as possible, but there may still be glitches.
- _Furnace:_ .dmf files may be saved, but full compatibility isn't guaranteed and many features will be missing. this isn't recommended unless absolutely necessary.
**.dmp**: DefleMask Preset. an instrument file.
**.dmw**: DefleMask Wavetable. a wavetable file.
**DPCM**: differential/delta pulse code modulation. this is a variety of PCM that stores each amplitude as its difference from the previous.
**duty cycle**: usually called _pulse width._ in a pulse wave, this is the ratio of the high part to the high and low combined.
**feedback**: in FM instruments, this adds some of an operator's output into itself to create complex harmonics.
- in the algorithm view, an operator with a circle around it is capable of feedback.
**FM**: frequency modulation. this is a method of generating sound that uses one operator's amplitude to modify another operator's frequency.
- the FM in Yamaha chips is more accurately called _phase modulation,_ which uses a different method of computation to achieve similar results.
**.fui**: a Furnace instrument file.
**.fur**: a Furnace module file.
**.fuw**: a Furnace wavetable file.
**hard-pan**: sounds can only be panned all the way to one side or the other, not in-between.
**Hz**: hertz. a unit representing divisions of one second. 1 Hz means once per second; 100 Hz means one hundred times per second. also, _kHz_ (kilohertz, one thousand per second) and _MHz_ (megahertz, one million per second).
**interpolate**: to fill in the area between two values with a smooth ramp of values in between.
- some sample-based chips can interpolate, filtering out unwanted harmonics.
**ladder effect**: an inaccurate yet common term for the DAC distortion that affects some Yamaha FM chips.
**LFO**: low frequency oscillator. a wave with a slow period (often below hearing range) used to alter other sounds.
**macro**: a sequence of values automatically applied while a note plays.
**noise bass**: the technique of using a PSG's periodic noise generator with a very short period to create low-frequency sounds.
**normalize**: to adjust the volume of a sample so it is as loud as possible without adding distortion from clipping.
**operator**: in FM, a single oscillator that interacts with other oscillators to generate sound.
**oscillator**: a sine wave or other basic waveform used as sound or to alter sound.
**PCM**: pulse code modulation. a stream of data that represents sound as a rapid sequence of amplitudes.
**period**: the length of a repeating waveform. as frequency rises, the period shortens.
**periodic noise**: an approximation of random noise generated algorithmically.
- the period is the number of values generated until the algorithm repeats itself.
**phase reset**: to restart a waveform at its initial value.
- for FM instruments, this restarts the volume envelope also.
**PSG**: programmable sound generator. any sound chip is a PSG, though the term is often used to specifically refer to chips that produce only simple waveforms and noise.
**pulse wave**: a waveform with a period consisting of only two amplitudes, high and low. also known as a rectangular wave.
**pulse width**: sometimes called _duty cycle._ in a pulse wave, this is the ratio of the high part to the high and low combined.
**release**: the part of a note that plays after it's no longer held, or the part of a macro the plays after it stops looping. usually applies at key off.
**resample**: to convert a sample to a different playback rate.
- this is a "lossy" process; it usually loses some amount of audio quality. the results can't be converted back into the original rate without further loss of quality.
- resampling to a lower rate reduces the amount of memory required, but strips away higher frequencies in the sound.
- resampling to a higher rate cannot recover missing frequencies and may add unwanted harmonics along with greater memory requirements.
**raw**: a sample or wavetable file without a header. when loading such a file, the format must be set properly or it will be a mess.
**register**: a memory location within a sound chip. "register view" shows all the relevant memory of all chips in use.
**sample** (1): a digitally recorded sound. usually stored as some variant of PCM.
- these can take up a lot of room depending on length and sample rate, thus older systems tend to use short, lower quality samples.
**sample** (2): a single value taken from a digitally recorded sound. a sample(1) is made up of samples(2).
**signed**: a digital representation of a number that may be negative or positive.
- if an imported raw sample sounds recognizable but heavily distorted, it's likely to be unsigned interpreted as signed or vice-versa.
**software mixing**: mixing multiple channels of sound down to a single stream to be sent to a PCM channel.
- this puts a heavy load on the CPU of the host system, so it was rarely used in games.
- _Furnace:_ this is used for DualPCM and QuadTone.
**square wave**: a wave consisting of only two values, high and low, with equal durations within the wave's period.
- this is equivalent to a pulse wave with a duty of 50%.
**supersaw**: a sound made up of multiple saw waves at slightly different frequencies to achieve a chorusing effect.
**tick rate**: the number of times per second that the sound engine moves forward. all notes and effects are quantized to this rate.
- this usually corresponds to the frame rate the system uses for video, approximately 60 for NTSC and 50 for PAL.
**unsigned**: a digital representation of a number that can only be positive.
- if an imported raw sample sounds recognizable but heavily distorted, it's likely to be signed interpreted as unsigned or vice-versa.
**.vgm**: Video Game Music. a file containing the log of data sent to a sound chip during sound playback.
- saving to a .vgm file may be compared to "converting text to outlines" or similar irreversible processes. the results cannot be loaded back into the tracker.
- different versions of the VGM format have different capabilities, with trade-offs. older versions may lack chips or features; newer versions may not be compatible with some software.
- samples are stored uncompressed. PCM streams (such as DualPCM) can quickly take up a huge amount of space.
**waveform**: a very short period of repeating sound.
- the most basic waveform is a sine wave. others include triangle, pulse, saw, and the like.
**wavetable** (1): a very short looping sample.
**wavetable** (2): an ordered group of wavetables(1) used in sequence within a single instrument.
**.zsm**: ZSound Music. a VGM-like file meant specifically for the Commander X16 computer.

View file

@ -66,17 +66,31 @@ settings are saved when clicking the **OK** or **Apply** buttons at the bottom o
### Output ### Output
- **Backend**: selects SDL or JACK for audio output. - **Backend**: selects a different backend for audio output.
- only appears on Linux, or MacOS compiled with JACK support - SDL: the default one.
- **Driver**: select a different SDL audio driver if you're having problems with 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. - **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. - **Outputs**: number of audio outputs created, up to 16.
- only appears when Backend is JACK. - 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. - **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 low value may cause stuttering/glitches in playback (known as "underruns" or "xruns").
- setting this to a high value increases latency. - 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. - **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). - 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). - **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. - **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. - **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 avoids activating Windows' built-in limiter.
- this option shall be enabled when using PortAudio backend with a DirectSound device.
### Metronome ### Metronome

View file

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

View file

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

Binary file not shown.

24
src/check/calc_checksum.c Normal file
View file

@ -0,0 +1,24 @@
#include <stdio.h>
int main(int argc, char** argv) {
if (argc<2) {
printf("usage: %s text\n",argv[0]);
return 1;
}
unsigned int checker=0x11111111;
unsigned int checker1=0;
int index=0;
for (char* i=argv[1]; *i; i++) {
checker^=((unsigned int)(*i))<<index;
checker1+=(unsigned int)(*i);
checker=(checker>>1|(((checker)^(checker>>2)^(checker>>3)^(checker>>5))&1)<<31);
checker1<<=1;
index=(index+1)&31;
}
printf("%.8x %x\n",checker,checker1);
return 0;
}

View file

@ -23,6 +23,7 @@
#include "engine.h" #include "engine.h"
#include "instrument.h" #include "instrument.h"
#include "safeReader.h" #include "safeReader.h"
#include "workPool.h"
#include "../ta-log.h" #include "../ta-log.h"
#include "../fileutils.h" #include "../fileutils.h"
#ifdef HAVE_SDL2 #ifdef HAVE_SDL2
@ -2016,6 +2017,10 @@ bool DivEngine::isPreviewingSample() {
return (sPreview.sample>=0 && sPreview.sample<(int)song.sample.size()); return (sPreview.sample>=0 && sPreview.sample<(int)song.sample.size());
} }
int DivEngine::getSamplePreviewSample() {
return sPreview.sample;
}
int DivEngine::getSamplePreviewPos() { int DivEngine::getSamplePreviewPos() {
return sPreview.pos; return sPreview.pos;
} }
@ -3119,6 +3124,10 @@ bool DivEngine::switchMaster(bool full) {
quitDispatch(); quitDispatch();
initDispatch(); initDispatch();
} }
if (renderPool!=NULL) {
delete renderPool;
renderPool=NULL;
}
if (initAudioBackend()) { if (initAudioBackend()) {
for (int i=0; i<song.systemLen; i++) { for (int i=0; i<song.systemLen; i++) {
disCont[i].setRates(got.rate); disCont[i].setRates(got.rate);
@ -3273,6 +3282,10 @@ void DivEngine::quitDispatch() {
for (int i=0; i<DIV_MAX_CHANS; i++) { for (int i=0; i<DIV_MAX_CHANS; i++) {
isMuted[i]=0; isMuted[i]=0;
} }
if (renderPool!=NULL) {
delete renderPool;
renderPool=NULL;
}
BUSY_END; BUSY_END;
} }
@ -3310,6 +3323,7 @@ bool DivEngine::initAudioBackend() {
midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE); midiOutMode=getConfInt("midiOutMode",DIV_MIDI_MODE_NOTE);
if (metroVol<0.0f) metroVol=0.0f; if (metroVol<0.0f) metroVol=0.0f;
if (metroVol>2.0f) metroVol=2.0f; if (metroVol>2.0f) metroVol=2.0f;
renderPoolThreads=getConfInt("renderPoolThreads",0);
if (lowLatency) logI("using low latency mode."); if (lowLatency) logI("using low latency mode.");

View file

@ -39,6 +39,8 @@
#include <unordered_map> #include <unordered_map>
#include <deque> #include <deque>
class DivWorkPool;
#define addWarning(x) \ #define addWarning(x) \
if (warnings.empty()) { \ if (warnings.empty()) { \
warnings+=x; \ warnings+=x; \
@ -195,6 +197,10 @@ struct DivDispatchContainer {
bool lowQuality, dcOffCompensation; bool lowQuality, dcOffCompensation;
double rateMemory; double rateMemory;
// used in multi-thread
int cycles;
unsigned int size;
void setRates(double gotRate); void setRates(double gotRate);
void setQuality(bool lowQual); void setQuality(bool lowQual);
void grow(size_t size); void grow(size_t size);
@ -213,7 +219,9 @@ struct DivDispatchContainer {
lastAvail(0), lastAvail(0),
lowQuality(false), lowQuality(false),
dcOffCompensation(false), dcOffCompensation(false),
rateMemory(0.0) { rateMemory(0.0),
cycles(0),
size(0) {
memset(bb,0,DIV_MAX_OUTPUTS*sizeof(blip_buffer_t*)); memset(bb,0,DIV_MAX_OUTPUTS*sizeof(blip_buffer_t*));
memset(temp,0,DIV_MAX_OUTPUTS*sizeof(int)); memset(temp,0,DIV_MAX_OUTPUTS*sizeof(int));
memset(prevSample,0,DIV_MAX_OUTPUTS*sizeof(int)); memset(prevSample,0,DIV_MAX_OUTPUTS*sizeof(int));
@ -485,6 +493,9 @@ class DivEngine {
size_t totalProcessed; size_t totalProcessed;
unsigned int renderPoolThreads;
DivWorkPool* renderPool;
// MIDI stuff // MIDI stuff
std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;}; std::function<int(const TAMidiMessage&)> midiCallback=[](const TAMidiMessage&) -> int {return -2;};
@ -714,6 +725,7 @@ class DivEngine {
// sample preview query // sample preview query
bool isPreviewingSample(); bool isPreviewingSample();
int getSamplePreviewSample();
int getSamplePreviewPos(); int getSamplePreviewPos();
double getSamplePreviewRate(); double getSamplePreviewRate();
@ -1259,6 +1271,8 @@ class DivEngine {
metroAmp(0.0f), metroAmp(0.0f),
metroVol(1.0f), metroVol(1.0f),
totalProcessed(0), totalProcessed(0),
renderPoolThreads(0),
renderPool(NULL),
curOrders(NULL), curOrders(NULL),
curPat(NULL), curPat(NULL),
tempIns(NULL), tempIns(NULL),

View file

@ -80,7 +80,7 @@ const char** DivPlatformAmiga::getRegisterSheet() {
} }
void DivPlatformAmiga::acquire(short** buf, size_t len) { void DivPlatformAmiga::acquire(short** buf, size_t len) {
static int outL, outR, output; thread_local int outL, outR, output;
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
bool hsync=bypassLimits; bool hsync=bypassLimits;
outL=0; outL=0;

View file

@ -52,7 +52,7 @@ const char** DivPlatformArcade::getRegisterSheet() {
} }
void DivPlatformArcade::acquire_nuked(short** buf, size_t len) { void DivPlatformArcade::acquire_nuked(short** buf, size_t len) {
static int o[2]; thread_local int o[2];
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
for (int i=0; i<8; i++) { for (int i=0; i<8; i++) {
@ -92,7 +92,7 @@ void DivPlatformArcade::acquire_nuked(short** buf, size_t len) {
} }
void DivPlatformArcade::acquire_ymfm(short** buf, size_t len) { void DivPlatformArcade::acquire_ymfm(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
ymfm::ym2151::fm_engine* fme=fm_ymfm->debug_engine(); ymfm::ym2151::fm_engine* fme=fm_ymfm->debug_engine();

View file

@ -132,8 +132,8 @@ void DivPlatformGenesis::processDAC(int iRate) {
} }
void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) { void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
static short o[2]; thread_local short o[2];
static int os[2]; thread_local int os[2];
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
processDAC(rate); processDAC(rate);
@ -213,7 +213,7 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
} }
void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) { void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
ymfm::ym2612::fm_engine* fme=fm_ymfm->debug_engine(); ymfm::ym2612::fm_engine* fme=fm_ymfm->debug_engine();

View file

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

View file

@ -160,9 +160,9 @@ const int orderedOpsL[4]={
#define ADDR_LR_FB_ALG 0xc0 #define ADDR_LR_FB_ALG 0xc0
void DivPlatformOPL::acquire_nuked(short** buf, size_t len) { void DivPlatformOPL::acquire_nuked(short** buf, size_t len) {
static short o[4]; thread_local short o[4];
static int os[4]; thread_local int os[4];
static ymfm::ymfm_output<2> aOut; thread_local ymfm::ymfm_output<2> aOut;
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
os[0]=0; os[1]=0; os[2]=0; os[3]=0; os[0]=0; os[1]=0; os[2]=0; os[3]=0;
@ -549,6 +549,8 @@ void DivPlatformOPL::tick(bool sysTick) {
chan[adpcmChan].freq=5461; // 4KHz chan[adpcmChan].freq=5461; // 4KHz
} }
} }
if (chan[adpcmChan].freq<0) chan[adpcmChan].freq=0;
if (chan[adpcmChan].freq>65535) chan[adpcmChan].freq=65535;
immWrite(16,chan[adpcmChan].freq&0xff); immWrite(16,chan[adpcmChan].freq&0xff);
immWrite(17,(chan[adpcmChan].freq>>8)&0xff); immWrite(17,(chan[adpcmChan].freq>>8)&0xff);
if (chan[adpcmChan].keyOn || chan[adpcmChan].keyOff) { if (chan[adpcmChan].keyOn || chan[adpcmChan].keyOff) {

View file

@ -43,8 +43,8 @@ const unsigned char visMapOPLL[9]={
}; };
void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) { void DivPlatformOPLL::acquire_nuked(short** buf, size_t len) {
static int o[2]; thread_local int o[2];
static int os; thread_local int os;
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
os=0; os=0;

View file

@ -27,7 +27,7 @@
#define chWrite(c,a,v) rWrite(((c)<<3)+(a),v) #define chWrite(c,a,v) rWrite(((c)<<3)+(a),v)
void DivPlatformSegaPCM::acquire(short** buf, size_t len) { void DivPlatformSegaPCM::acquire(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
while (!writes.empty()) { while (!writes.empty()) {

View file

@ -58,7 +58,7 @@ const char** DivPlatformTX81Z::getRegisterSheet() {
} }
void DivPlatformTX81Z::acquire(short** buf, size_t len) { void DivPlatformTX81Z::acquire(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
ymfm::ym2414::fm_engine* fme=fm_ymfm->debug_engine(); ymfm::ym2414::fm_engine* fme=fm_ymfm->debug_engine();

View file

@ -165,8 +165,8 @@ void DivPlatformYM2203::acquire(short** buf, size_t len) {
} }
void DivPlatformYM2203::acquire_combo(short** buf, size_t len) { void DivPlatformYM2203::acquire_combo(short** buf, size_t len) {
static int os; thread_local int os;
static short ignored[2]; thread_local short ignored[2];
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
// AY -> OPN // AY -> OPN
@ -241,7 +241,7 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) {
} }
void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) { void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) {
static int os; thread_local int os;
ymfm::ym2203::fm_engine* fme=fm->debug_fm_engine(); ymfm::ym2203::fm_engine* fme=fm->debug_fm_engine();

View file

@ -306,8 +306,8 @@ void DivPlatformYM2608::acquire(short** buf, size_t len) {
} }
void DivPlatformYM2608::acquire_combo(short** buf, size_t len) { void DivPlatformYM2608::acquire_combo(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
static short ignored[2]; thread_local short ignored[2];
ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine();
@ -419,7 +419,7 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) {
} }
void DivPlatformYM2608::acquire_ymfm(short** buf, size_t len) { void DivPlatformYM2608::acquire_ymfm(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
ymfm::ym2608::fm_engine* fme=fm->debug_fm_engine(); ymfm::ym2608::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
@ -783,6 +783,8 @@ void DivPlatformYM2608::tick(bool sysTick) {
chan[15].freq=0; chan[15].freq=0;
} }
} }
if (chan[adpcmBChanOffs].freq<0) chan[adpcmBChanOffs].freq=0;
if (chan[adpcmBChanOffs].freq>65535) chan[adpcmBChanOffs].freq=65535;
immWrite(0x109,chan[15].freq&0xff); immWrite(0x109,chan[15].freq&0xff);
immWrite(0x10a,(chan[15].freq>>8)&0xff); immWrite(0x10a,(chan[15].freq>>8)&0xff);
hardResetElapsed+=2; hardResetElapsed+=2;

View file

@ -241,8 +241,8 @@ void DivPlatformYM2610::acquire(short** buf, size_t len) {
} }
void DivPlatformYM2610::acquire_combo(short** buf, size_t len) { void DivPlatformYM2610::acquire_combo(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
static short ignored[2]; thread_local short ignored[2];
ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine();
@ -350,7 +350,7 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) {
} }
void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) { void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
ymfm::ym2610::fm_engine* fme=fm->debug_fm_engine(); ymfm::ym2610::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
@ -717,6 +717,8 @@ void DivPlatformYM2610::tick(bool sysTick) {
} else { } else {
chan[adpcmBChanOffs].freq=0; chan[adpcmBChanOffs].freq=0;
} }
if (chan[adpcmBChanOffs].freq<0) chan[adpcmBChanOffs].freq=0;
if (chan[adpcmBChanOffs].freq>65535) chan[adpcmBChanOffs].freq=65535;
immWrite(0x19,chan[adpcmBChanOffs].freq&0xff); immWrite(0x19,chan[adpcmBChanOffs].freq&0xff);
immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff); immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff);
hardResetElapsed+=2; hardResetElapsed+=2;

View file

@ -305,8 +305,8 @@ void DivPlatformYM2610B::acquire(short** buf, size_t len) {
} }
void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) { void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
static short ignored[2]; thread_local short ignored[2];
ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine(); ymfm::adpcm_a_engine* aae=fm->debug_adpcm_a_engine();
@ -418,7 +418,7 @@ void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) {
} }
void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) { void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) {
static int os[2]; thread_local int os[2];
ymfm::ym2610b::fm_engine* fme=fm->debug_fm_engine(); ymfm::ym2610b::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine(); ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
@ -784,6 +784,8 @@ void DivPlatformYM2610B::tick(bool sysTick) {
} else { } else {
chan[adpcmBChanOffs].freq=0; chan[adpcmBChanOffs].freq=0;
} }
if (chan[adpcmBChanOffs].freq<0) chan[adpcmBChanOffs].freq=0;
if (chan[adpcmBChanOffs].freq>65535) chan[adpcmBChanOffs].freq=65535;
immWrite(0x19,chan[adpcmBChanOffs].freq&0xff); immWrite(0x19,chan[adpcmBChanOffs].freq&0xff);
immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff); immWrite(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff);
hardResetElapsed+=2; hardResetElapsed+=2;

View file

@ -22,6 +22,7 @@
#define _USE_MATH_DEFINES #define _USE_MATH_DEFINES
#include "dispatch.h" #include "dispatch.h"
#include "engine.h" #include "engine.h"
#include "workPool.h"
#include "../ta-log.h" #include "../ta-log.h"
#include <math.h> #include <math.h>
@ -1759,6 +1760,13 @@ void DivEngine::runMidiTime(int totalCycles) {
} }
} }
void _runDispatch1(void* d) {
}
void _runDispatch2(void* d) {
}
void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) { void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size) {
lastNBIns=inChans; lastNBIns=inChans;
lastNBOuts=outChans; lastNBOuts=outChans;
@ -1788,6 +1796,13 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
std::chrono::steady_clock::time_point ts_processBegin=std::chrono::steady_clock::now(); std::chrono::steady_clock::time_point ts_processBegin=std::chrono::steady_clock::now();
if (renderPool==NULL) {
unsigned int howManyThreads=song.systemLen;
if (howManyThreads<2) howManyThreads=0;
if (howManyThreads>renderPoolThreads) howManyThreads=renderPoolThreads;
renderPool=new DivWorkPool(howManyThreads);
}
// process MIDI events (TODO: everything) // process MIDI events (TODO: everything)
if (output) if (output->midiIn) while (!output->midiIn->queue.empty()) { if (output) if (output->midiIn) while (!output->midiIn->queue.empty()) {
TAMidiMessage& msg=output->midiIn->queue.front(); TAMidiMessage& msg=output->midiIn->queue.front();
@ -2061,20 +2076,30 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
// 5. tick the clock and fill buffers as needed // 5. tick the clock and fill buffers as needed
if (cycles<runLeftG) { if (cycles<runLeftG) {
for (int i=0; i<song.systemLen; i++) { for (int i=0; i<song.systemLen; i++) {
int total=(cycles*disCont[i].runtotal)/(size<<MASTER_CLOCK_PREC); disCont[i].cycles=cycles;
disCont[i].acquire(disCont[i].runPos,total); disCont[i].size=size;
disCont[i].runLeft-=total; renderPool->push([](void* d) {
disCont[i].runPos+=total; DivDispatchContainer* dc=(DivDispatchContainer*)d;
int total=(dc->cycles*dc->runtotal)/(dc->size<<MASTER_CLOCK_PREC);
dc->acquire(dc->runPos,total);
dc->runLeft-=total;
dc->runPos+=total;
},&disCont[i]);
} }
renderPool->wait();
runLeftG-=cycles; runLeftG-=cycles;
cycles=0; cycles=0;
} else { } else {
cycles-=runLeftG; cycles-=runLeftG;
runLeftG=0; runLeftG=0;
for (int i=0; i<song.systemLen; i++) { for (int i=0; i<song.systemLen; i++) {
disCont[i].acquire(disCont[i].runPos,disCont[i].runLeft); renderPool->push([](void* d) {
disCont[i].runLeft=0; DivDispatchContainer* dc=(DivDispatchContainer*)d;
dc->acquire(dc->runPos,dc->runLeft);
dc->runLeft=0;
},&disCont[i]);
} }
renderPool->wait();
} }
} }
} }
@ -2093,8 +2118,12 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
logW("%d: size<lastAvail! %d<%d",i,size,disCont[i].lastAvail); logW("%d: size<lastAvail! %d<%d",i,size,disCont[i].lastAvail);
continue; continue;
} }
disCont[i].fillBuf(disCont[i].runtotal,disCont[i].lastAvail,size-disCont[i].lastAvail); renderPool->push([](void* d) {
DivDispatchContainer* dc=(DivDispatchContainer*)d;
dc->fillBuf(dc->runtotal,dc->lastAvail,dc->size-dc->lastAvail);
},&disCont[i]);
} }
renderPool->wait();
} }
if (metroBufLen<size || metroBuf==NULL) { if (metroBufLen<size || metroBuf==NULL) {

View file

@ -454,10 +454,11 @@ void DivEngine::registerSystems() {
{0x18, {DIV_CMD_FM_EXTCH, "18xx: Toggle extended channel 3 mode"}}, {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)"}}, {0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Toggle PCM mode (LEGACY)"}},
{0xdf, {DIV_CMD_SAMPLE_DIR, "DFxx: Set sample playback direction (0: normal; 1: reverse)"}}, {0xdf, {DIV_CMD_SAMPLE_DIR, "DFxx: Set sample playback direction (0: normal; 1: reverse)"}},
}; });
EffectHandlerMap fmOPLDrumsEffectHandlerMap(fmEffectHandlerMap); EffectHandlerMap fmOPLDrumsEffectHandlerMap(fmEffectHandlerMap);
fmOPLDrumsEffectHandlerMap.insert({ fmOPLDrumsEffectHandlerMap.insert({
@ -1179,7 +1180,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SWAN]=new DivSysDef( sysDefs[DIV_SYSTEM_SWAN]=new DivSysDef(
"WonderSwan", NULL, 0x96, 0, 4, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, "WonderSwan", NULL, 0x96, 0, 4, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"developed by the same team under the Game Boy and the Virtual Boy...", "developed by the makers of the Game Boy and the Virtual Boy...",
{"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, {"Wave", "Wave/PCM", "Wave", "Wave/Noise"},
{"CH1", "CH2", "CH3", "CH4"}, {"CH1", "CH2", "CH3", "CH4"},
{DIV_CH_WAVE, DIV_CH_PCM, DIV_CH_WAVE, DIV_CH_NOISE}, {DIV_CH_WAVE, DIV_CH_PCM, DIV_CH_WAVE, DIV_CH_NOISE},

204
src/engine/workPool.cpp Normal file
View file

@ -0,0 +1,204 @@
/**
* 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;
bool setFuckingPromise=false;
logV("running work thread");
while (true) {
lock.lock();
if (tasks.empty()) {
lock.unlock();
isBusy=false;
if (setFuckingPromise) {
parent->notify.set_value();
setFuckingPromise=false;
//std::this_thread::yield();
}
if (terminate) {
break;
}
std::future<void> future=notify.get_future();
future.wait();
lock.lock();
notify=std::promise<void>();
promiseAlreadySet=false;
lock.unlock();
continue;
} else {
task=tasks.front();
tasks.pop();
lock.unlock();
task.func(task.funcArg);
int busyCount=--parent->busyCount;
if (busyCount<0) {
logE("oh no PROBLEM...");
}
if (busyCount==0) {
setFuckingPromise=true;
}
}
}
}
bool DivWorkThread::assign(void (*what)(void*), 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;
notify.set_value();
lock.unlock();
thread->join();
}
bool DivWorkThread::init(DivWorkPool* p) {
parent=p;
try {
thread=new std::thread(_workThread,this);
} catch (std::system_error& e) {
logE("could not start thread! %s",e.what());
thread=NULL;
return false;
}
return true;
}
void DivWorkPool::push(void (*what)(void*), 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;
if (busyCount==0) {
return;
}
std::future<void> future=notify.get_future();
// start running
for (unsigned int i=0; i<count; i++) {
if (!workThreads[i].promiseAlreadySet && !workThreads[i].tasks.empty()) {
try {
workThreads[i].lock.lock();
workThreads[i].promiseAlreadySet=true;
workThreads[i].notify.set_value();
workThreads[i].lock.unlock();
} catch (std::exception& e) {
logE("ERROR IN THREAD SYNC! %s",e.what());
abort();
}
}
}
//std::this_thread::yield();
// wait
future.wait();
notify=std::promise<void>();
pos=0;
}
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++) {
if (!workThreads[i].init(this)) {
count=i;
break;
}
}
if (count<=0) {
logE("DivWorkPool: couldn't start any threads! falling back to non-threaded mode.");
delete[] workThreads;
threaded=false;
workThreads=NULL;
}
} else {
workThreads=NULL;
}
}
DivWorkPool::~DivWorkPool() {
if (threaded) {
for (unsigned int i=0; i<count; i++) {
workThreads[i].finish();
}
delete[] workThreads;
}
}

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

@ -0,0 +1,101 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _WORKPOOL_H
#define _WORKPOOL_H
#include <thread>
#include <mutex>
#include <atomic>
#include <functional>
#include <future>
#include "fixedQueue.h"
class DivWorkPool;
struct DivPendingTask {
void (*func)(void*);
void* funcArg;
DivPendingTask(void (*f)(void*), void* arg):
func(f),
funcArg(arg) {}
DivPendingTask():
func(NULL),
funcArg(NULL) {}
};
struct DivWorkThread {
DivWorkPool* parent;
std::mutex lock;
std::thread* thread;
std::promise<void> notify;
FixedQueue<DivPendingTask,32> tasks;
std::atomic<bool> isBusy;
bool terminate;
bool promiseAlreadySet;
void run();
bool assign(void (*what)(void*), void* arg);
void wait();
bool busy();
void finish();
bool init(DivWorkPool* p);
DivWorkThread():
parent(NULL),
isBusy(false),
terminate(false),
promiseAlreadySet(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;
unsigned int count;
unsigned int pos;
DivWorkThread* workThreads;
public:
std::promise<void> 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(void (*what)(void*), void* arg);
/**
* check whether this work pool is busy.
*/
bool busy();
/**
* wait for all work threads to finish.
*/
void wait();
DivWorkPool(unsigned int threads=0);
~DivWorkPool();
};
#endif

View file

@ -38,7 +38,7 @@ const char* aboutLine[]={
"akumanatt", "akumanatt",
"cam900", "cam900",
"djtuBIG-MaliceX", "djtuBIG-MaliceX",
"Eknous-P", "Eknous",
"laoo", "laoo",
"MooingLemur", "MooingLemur",
"OPNA2608", "OPNA2608",
@ -79,12 +79,13 @@ const char* aboutLine[]={
"Burnt Fishy", "Burnt Fishy",
"CaptainMalware", "CaptainMalware",
"Clingojam", "Clingojam",
"Crisps",
"DeMOSic", "DeMOSic",
"DevEd", "DevEd",
"Dippy", "Dippy",
"djtuBIG-MaliceX", "djtuBIG-MaliceX",
"dumbut", "dumbut",
"Eknous-P", "Eknous",
"Electric Keet", "Electric Keet",
"EpicTyphlosion", "EpicTyphlosion",
"FΛDE", "FΛDE",
@ -125,6 +126,7 @@ const char* aboutLine[]={
"TakuikaNinja", "TakuikaNinja",
"TCORPStudios", "TCORPStudios",
"Teuthida", "Teuthida",
"ThaCuber",
"The Blender Fiddler", "The Blender Fiddler",
"TheDuccinator", "TheDuccinator",
"theloredev", "theloredev",

View file

@ -60,7 +60,7 @@ float FurnaceGUI::computeGradPos(int type, int chan) {
return 1.0f; return 1.0f;
break; break;
case GUI_OSCREF_FREQUENCY: case GUI_OSCREF_FREQUENCY:
return chanOscPitch[chan]; return chanOscChan[chan].pitch;
break; break;
case GUI_OSCREF_VOLUME: case GUI_OSCREF_VOLUME:
return chanOscVol[chan]; return chanOscVol[chan];
@ -363,10 +363,17 @@ void FurnaceGUI::drawChanOsc() {
std::vector<int> oscChans; std::vector<int> oscChans;
int chans=e->getTotalChannelCount(); int chans=e->getTotalChannelCount();
ImGuiWindow* window=ImGui::GetCurrentWindow(); ImGuiWindow* window=ImGui::GetCurrentWindow();
ImVec2 waveform[512];
ImGuiStyle& style=ImGui::GetStyle(); 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++) { for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=e->getOscBuffer(i); DivDispatchOscBuffer* buf=e->getOscBuffer(i);
if (buf!=NULL && e->curSubSong->chanShow[i]) { if (buf!=NULL && e->curSubSong->chanShow[i]) {
@ -376,6 +383,148 @@ 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()) {
fft_->windowSize=chanOscWindowSize;
fft_->waveCorr=chanOscWaveCorr;
chanOscWorkPool->push([](void* fft_v) {
ChanOscStatus* fft=(ChanOscStatus*)fft_v;
DivDispatchOscBuffer* buf=fft->relatedBuf;
// 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)*(fft->windowSize/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
fft->pitch=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 (fft->waveCorr) {
fft->needle-=phase*fft->waveLen;
}
}
}
fft->needle-=displaySize;
},fft_);
}
}
}
chanOscWorkPool->wait();
// 0: none // 0: none
// 1: sqrt(chans) // 1: sqrt(chans)
// 2: sqrt(chans+1) // 2: sqrt(chans+1)
@ -396,6 +545,7 @@ void FurnaceGUI::drawChanOsc() {
int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols; int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols;
// render
for (size_t i=0; i<oscBufs.size(); i++) { for (size_t i=0; i<oscBufs.size(); i++) {
if (i%chanOscCols==0) ImGui::TableNextRow(); if (i%chanOscCols==0) ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
@ -409,20 +559,6 @@ void FurnaceGUI::drawChanOsc() {
ImVec2 size=ImGui::GetContentRegionAvail(); ImVec2 size=ImGui::GetContentRegionAvail();
size.y=availY/rows; 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 minArea=window->DC.CursorPos;
ImVec2 maxArea=ImVec2( ImVec2 maxArea=ImVec2(
minArea.x+size.x, minArea.x+size.x,
@ -437,67 +573,78 @@ void FurnaceGUI::drawChanOsc() {
int precision=inRect.Max.x-inRect.Min.x; int precision=inRect.Max.x-inRect.Min.x;
if (precision<1) precision=1; if (precision<1) precision=1;
if (precision>512) precision=512; if (precision>1024) precision=1024;
ImGui::ItemSize(size,style.FramePadding.y); ImGui::ItemSize(size,style.FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) { if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) {
if (!e->isRunning()) { if (!e->isRunning()) {
for (unsigned short i=0; i<precision; i++) { for (unsigned short j=0; j<precision; j++) {
float x=(float)i/(float)precision; float x=(float)j/(float)precision;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f)); waveform[j]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
} }
} else { } else {
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
float minLevel=1.0f; float minLevel=1.0f;
float maxLevel=-1.0f; float maxLevel=-1.0f;
float dcOff=0.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 if (debugFFT) {
int point=1; // FFT debug code!
double candAmp=0.0; double maxavg=0.0;
for (unsigned short i=1; i<512; i++) { for (unsigned short j=0; j<(FURNACE_FFT_SIZE>>1); j++) {
fftw_complex& f=fft->outBuf[i]; if (fabs(fft->corrBuf[j]>maxavg)) {
// AMPLITUDE maxavg=fabs(fft->corrBuf[j]);
double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8); }
if (amp>candAmp) { }
point=i; if (maxavg>0.0000001) maxavg=0.5/maxavg;
candAmp=amp;
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); ImU32 color=ImGui::GetColorU32(chanOscColor);
if (chanOscUseGrad) { if (chanOscUseGrad) {
@ -511,15 +658,18 @@ void FurnaceGUI::drawChanOsc() {
} }
ImGui::PushClipRect(inRect.Min,inRect.Max,false); ImGui::PushClipRect(inRect.Min,inRect.Max,false);
//ImDrawListFlags prevFlags=dl->Flags;
//dl->Flags&=~(ImDrawListFlags_AntiAliasedLines|ImDrawListFlags_AntiAliasedLinesUseTex);
dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale); dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale);
//dl->Flags=prevFlags;
if (!chanOscTextFormat.empty()) { if (!chanOscTextFormat.empty()) {
String text; String text;
bool inFormat=false; bool inFormat=false;
for (char i: chanOscTextFormat) { for (char j: chanOscTextFormat) {
if (inFormat) { if (inFormat) {
switch (i) { switch (j) {
case 'c': case 'c':
text+=e->getChannelName(ch); text+=e->getChannelName(ch);
break; break;
@ -560,7 +710,7 @@ void FurnaceGUI::drawChanOsc() {
break; break;
} }
case 'p': { 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; break;
} }
case 'S': { case 'S': {
@ -601,19 +751,18 @@ void FurnaceGUI::drawChanOsc() {
break; break;
default: default:
text+='%'; text+='%';
text+=i; text+=j;
break; break;
} }
inFormat=false; inFormat=false;
} else { } else {
if (i=='%') { if (j=='%') {
inFormat=true; inFormat=true;
} else { } else {
text+=i; text+=j;
} }
} }
} }
dl->AddText(ImLerp(inRect.Min,inRect.Max,ImVec2(0.0f,0.0f)),ImGui::GetColorU32(chanOscTextColor),text.c_str()); dl->AddText(ImLerp(inRect.Min,inRect.Max,ImVec2(0.0f,0.0f)),ImGui::GetColorU32(chanOscTextColor),text.c_str());
} }

View file

@ -210,11 +210,15 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("like ProTracker/FamiTracker"); ImGui::SetTooltip("like ProTracker/FamiTracker");
} }
if (ImGui::RadioButton("Partial (only 04xy/E5xx)",e->song.linearPitch==1)) { if (e->song.linearPitch==1) {
e->song.linearPitch=1; pushWarningColor(true);
} if (ImGui::RadioButton("Partial (only 04xy/E5xx)",e->song.linearPitch==1)) {
if (ImGui::IsItemHovered()) { e->song.linearPitch=1;
ImGui::SetTooltip("like DefleMask"); }
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("like DefleMask\n\nthis pitch linearity mode is deprecated due to:\n- excessive complexity\n- lack of possible optimization\n\nit is recommended to change it now because I will remove this option in the future!");
}
popWarningColor();
} }
if (ImGui::RadioButton("Full",e->song.linearPitch==2)) { if (ImGui::RadioButton("Full",e->song.linearPitch==2)) {
e->song.linearPitch=2; e->song.linearPitch=2;

View file

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

View file

@ -76,7 +76,38 @@ void _nfdThread(const NFDState state, std::atomic<bool>* ok, std::vector<String>
} }
#endif #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; if (opened) return false;
saving=false; saving=false;
curPath=path; curPath=path;
@ -149,6 +180,8 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, c
} }
#endif #endif
convertFilterList(filter);
ImGuiFileDialog::Instance()->singleClickSel=mobileUI; ImGuiFileDialog::Instance()->singleClickSel=mobileUI;
ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->mobileMode=mobileUI; ImGuiFileDialog::Instance()->mobileMode=mobileUI;
@ -159,7 +192,7 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, c
return true; 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; if (opened) return false;
#ifdef ANDROID #ifdef ANDROID
@ -233,6 +266,8 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector<String> filter, c
} else { } else {
hasError=false; hasError=false;
convertFilterList(filter);
ImGuiFileDialog::Instance()->singleClickSel=false; ImGuiFileDialog::Instance()->singleClickSel=false;
ImGuiFileDialog::Instance()->DpiScale=dpiScale; ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->mobileMode=mobileUI; ImGuiFileDialog::Instance()->mobileMode=mobileUI;

View file

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

View file

@ -614,6 +614,21 @@ void FurnaceGUI::updateWindowTitle() {
} }
if (sdlWin!=NULL) SDL_SetWindowTitle(sdlWin,title.c_str()); if (sdlWin!=NULL) SDL_SetWindowTitle(sdlWin,title.c_str());
if (e->song.insLen==1) {
unsigned int checker=0x11111111;
unsigned int checker1=0;
DivInstrument* ins=e->getIns(0);
if (ins->name.size()==15 && e->curSubSong->ordersLen==8) {
for (int i=0; i<15; i++) {
checker^=ins->name[i]<<i;
checker1+=ins->name[i];
checker=(checker>>1|(((checker)^(checker>>2)^(checker>>3)^(checker>>5))&1)<<31);
checker1<<=1;
}
if (checker==0x5ec4497d && checker1==0x6347ee) nonLatchNibble=true;
}
}
} }
void FurnaceGUI::autoDetectSystem() { void FurnaceGUI::autoDetectSystem() {
@ -1082,7 +1097,9 @@ float FurnaceGUI::calcBPM(const DivGroovePattern& speeds, float hz, int vN, int
void FurnaceGUI::play(int row) { void FurnaceGUI::play(int row) {
memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float)); memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float));
memset(chanOscPitch,0,DIV_MAX_CHANS*sizeof(float)); for (int i=0; i<DIV_MAX_CHANS; i++) {
chanOscChan[i].pitch=0.0f;
}
memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float)); memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float));
e->walkSong(loopOrder,loopRow,loopEnd); e->walkSong(loopOrder,loopRow,loopEnd);
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS); memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
@ -1567,7 +1584,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Open File", "Open File",
{"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod *.fc", {"compatible files", "*.fur *.dmf *.mod *.fc13 *.fc14 *.smod *.fc",
"all files", "*"}, "all files", "*"},
"compatible files{.fur,.dmf,.mod,.fc13,.fc14,.smod,.fc},.*",
workingDirSong, workingDirSong,
dpiScale dpiScale
); );
@ -1580,7 +1596,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Restore Backup", "Restore Backup",
{"Furnace song", "*.fur"}, {"Furnace song", "*.fur"},
"Furnace song{.fur}",
backupPath+String(DIR_SEPARATOR_STR), backupPath+String(DIR_SEPARATOR_STR),
dpiScale dpiScale
); );
@ -1590,7 +1605,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Save File", "Save File",
{"Furnace song", "*.fur"}, {"Furnace song", "*.fur"},
"Furnace song{.fur}",
workingDirSong, workingDirSong,
dpiScale dpiScale
); );
@ -1600,7 +1614,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Save File", "Save File",
{"DefleMask 1.1.3 module", "*.dmf"}, {"DefleMask 1.1.3 module", "*.dmf"},
"DefleMask 1.1.3 module{.dmf}",
workingDirSong, workingDirSong,
dpiScale dpiScale
); );
@ -1610,7 +1623,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Save File", "Save File",
{"DefleMask 1.0/legacy module", "*.dmf"}, {"DefleMask 1.0/legacy module", "*.dmf"},
"DefleMask 1.0/legacy module{.dmf}",
workingDirSong, workingDirSong,
dpiScale dpiScale
); );
@ -1627,8 +1639,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Load Instrument", "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", {"all compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.gyb *.opm *.wopl *.wopn",
"Furnace instrument", "*.fui", "Furnace instrument", "*.fui",
"DefleMask preset", "*.dmp", "DefleMask preset", "*.dmp",
@ -1646,7 +1656,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Wohlstand WOPL bank", "*.wopl", "Wohlstand WOPL bank", "*.wopl",
"Wohlstand WOPN bank", "*.wopn", "Wohlstand WOPN bank", "*.wopn",
"all files", "*"}, "all files", "*"},
"all compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.opli,.opni,.y12,.bnk,.ff,.gyb,.opm,.wopl,.wopn},.*",
workingDirIns, workingDirIns,
dpiScale, dpiScale,
[this](const char* path) { [this](const char* path) {
@ -1681,7 +1690,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Save Instrument", "Save Instrument",
{"Furnace instrument", "*.fui"}, {"Furnace instrument", "*.fui"},
"Furnace instrument{.fui}",
workingDirIns, workingDirIns,
dpiScale dpiScale
); );
@ -1691,7 +1699,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Save Instrument", "Save Instrument",
{"DefleMask preset", "*.dmp"}, {"DefleMask preset", "*.dmp"},
"DefleMask preset{.dmp}",
workingDirIns, workingDirIns,
dpiScale dpiScale
); );
@ -1703,7 +1710,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Load Wavetable", "Load Wavetable",
{"compatible files", "*.fuw *.dmw", {"compatible files", "*.fuw *.dmw",
"all files", "*"}, "all files", "*"},
"compatible files{.fuw,.dmw},.*",
workingDirWave, workingDirWave,
dpiScale, dpiScale,
NULL, // TODO NULL, // TODO
@ -1715,7 +1721,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Save Wavetable", "Save Wavetable",
{"Furnace wavetable", ".fuw"}, {"Furnace wavetable", ".fuw"},
"Furnace wavetable{.fuw}",
workingDirWave, workingDirWave,
dpiScale dpiScale
); );
@ -1725,7 +1730,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Save Wavetable", "Save Wavetable",
{"DefleMask wavetable", ".dmw"}, {"DefleMask wavetable", ".dmw"},
"DefleMask wavetable{.dmw}",
workingDirWave, workingDirWave,
dpiScale dpiScale
); );
@ -1735,7 +1739,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Save Wavetable", "Save Wavetable",
{"raw data", ".raw"}, {"raw data", ".raw"},
"raw data{.raw}",
workingDirWave, workingDirWave,
dpiScale dpiScale
); );
@ -1747,7 +1750,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Load Sample", "Load Sample",
{"compatible files", "*.wav *.dmc *.brr", {"compatible files", "*.wav *.dmc *.brr",
"all files", "*"}, "all files", "*"},
"compatible files{.wav,.dmc,.brr},.*",
workingDirSample, workingDirSample,
dpiScale, dpiScale,
NULL, // TODO NULL, // TODO
@ -1760,7 +1762,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Load Raw Sample", "Load Raw Sample",
{"all files", "*"}, {"all files", "*"},
".*",
workingDirSample, workingDirSample,
dpiScale dpiScale
); );
@ -1770,7 +1771,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Save Sample", "Save Sample",
{"Wave file", "*.wav"}, {"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirSample, workingDirSample,
dpiScale dpiScale
); );
@ -1778,9 +1778,8 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
case GUI_FILE_SAMPLE_SAVE_RAW: case GUI_FILE_SAMPLE_SAVE_RAW:
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Load Raw Sample", "Save Raw Sample",
{"all files", "*"}, {"all files", "*"},
".*",
workingDirSample, workingDirSample,
dpiScale dpiScale
); );
@ -1790,7 +1789,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export Audio", "Export Audio",
{"Wave file", "*.wav"}, {"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirAudioExport, workingDirAudioExport,
dpiScale dpiScale
); );
@ -1800,7 +1798,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export Audio", "Export Audio",
{"Wave file", "*.wav"}, {"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirAudioExport, workingDirAudioExport,
dpiScale dpiScale
); );
@ -1810,7 +1807,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export Audio", "Export Audio",
{"Wave file", "*.wav"}, {"Wave file", "*.wav"},
"Wave file{.wav}",
workingDirAudioExport, workingDirAudioExport,
dpiScale dpiScale
); );
@ -1820,7 +1816,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export VGM", "Export VGM",
{"VGM file", "*.vgm"}, {"VGM file", "*.vgm"},
"VGM file{.vgm}",
workingDirVGMExport, workingDirVGMExport,
dpiScale dpiScale
); );
@ -1830,7 +1825,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export ZSM", "Export ZSM",
{"ZSM file", "*.zsm"}, {"ZSM file", "*.zsm"},
"ZSM file{.zsm}",
workingDirZSMExport, workingDirZSMExport,
dpiScale dpiScale
); );
@ -1840,7 +1834,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export Command Stream", "Export Command Stream",
{"text file", "*.txt"}, {"text file", "*.txt"},
"text file{.txt}",
workingDirROMExport, workingDirROMExport,
dpiScale dpiScale
); );
@ -1850,7 +1843,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export Command Stream", "Export Command Stream",
{"binary file", "*.bin"}, {"binary file", "*.bin"},
"binary file{.bin}",
workingDirROMExport, workingDirROMExport,
dpiScale dpiScale
); );
@ -1863,7 +1855,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Select Font", "Select Font",
{"compatible files", "*.ttf *.otf *.ttc"}, {"compatible files", "*.ttf *.otf *.ttc"},
"compatible files{.ttf,.otf,.ttc}",
workingDirFont, workingDirFont,
dpiScale dpiScale
); );
@ -1873,7 +1864,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Select Font", "Select Font",
{"compatible files", "*.ttf *.otf *.ttc"}, {"compatible files", "*.ttf *.otf *.ttc"},
"compatible files{.ttf,.otf,.ttc}",
workingDirFont, workingDirFont,
dpiScale dpiScale
); );
@ -1883,7 +1873,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Select Font", "Select Font",
{"compatible files", "*.ttf *.otf *.ttc"}, {"compatible files", "*.ttf *.otf *.ttc"},
"compatible files{.ttf,.otf,.ttc}",
workingDirFont, workingDirFont,
dpiScale dpiScale
); );
@ -1893,7 +1882,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Select Color File", "Select Color File",
{"configuration files", "*.cfgc"}, {"configuration files", "*.cfgc"},
"configuration files{.cfgc}",
workingDirColors, workingDirColors,
dpiScale dpiScale
); );
@ -1903,7 +1891,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Select Keybind File", "Select Keybind File",
{"configuration files", "*.cfgk"}, {"configuration files", "*.cfgk"},
"configuration files{.cfgk}",
workingDirKeybinds, workingDirKeybinds,
dpiScale dpiScale
); );
@ -1913,7 +1900,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Select Layout File", "Select Layout File",
{".ini files", "*.ini"}, {".ini files", "*.ini"},
".ini files{.ini}",
workingDirKeybinds, workingDirKeybinds,
dpiScale dpiScale
); );
@ -1923,7 +1909,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export Colors", "Export Colors",
{"configuration files", "*.cfgc"}, {"configuration files", "*.cfgc"},
"configuration files{.cfgc}",
workingDirColors, workingDirColors,
dpiScale dpiScale
); );
@ -1933,7 +1918,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export Keybinds", "Export Keybinds",
{"configuration files", "*.cfgk"}, {"configuration files", "*.cfgk"},
"configuration files{.cfgk}",
workingDirKeybinds, workingDirKeybinds,
dpiScale dpiScale
); );
@ -1943,7 +1927,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
"Export Layout", "Export Layout",
{".ini files", "*.ini"}, {".ini files", "*.ini"},
".ini files{.ini}",
workingDirKeybinds, workingDirKeybinds,
dpiScale dpiScale
); );
@ -1956,7 +1939,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Load ROM", "Load ROM",
{"compatible files", "*.rom *.bin", {"compatible files", "*.rom *.bin",
"all files", "*"}, "all files", "*"},
"compatible files{.rom,.bin},.*",
workingDirROM, workingDirROM,
dpiScale dpiScale
); );
@ -1967,7 +1949,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Play Command Stream", "Play Command Stream",
{"command stream", "*.bin", {"command stream", "*.bin",
"all files", "*"}, "all files", "*"},
"command stream{.bin},.*",
workingDirROM, workingDirROM,
dpiScale dpiScale
); );
@ -1979,7 +1960,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
{"compatible files", "*.fur *.dmf *.mod", {"compatible files", "*.fur *.dmf *.mod",
"another option", "*.wav *.ttf", "another option", "*.wav *.ttf",
"all files", "*"}, "all files", "*"},
"compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*",
workingDirTest, workingDirTest,
dpiScale, dpiScale,
[](const char* path) { [](const char* path) {
@ -1998,7 +1978,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
{"compatible files", "*.fur *.dmf *.mod", {"compatible files", "*.fur *.dmf *.mod",
"another option", "*.wav *.ttf", "another option", "*.wav *.ttf",
"all files", "*"}, "all files", "*"},
"compatible files{.fur,.dmf,.mod},another option{.wav,.ttf},.*",
workingDirTest, workingDirTest,
dpiScale, dpiScale,
[](const char* path) { [](const char* path) {
@ -2017,7 +1996,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
"Save Test", "Save Test",
{"Furnace song", "*.fur", {"Furnace song", "*.fur",
"DefleMask module", "*.dmf"}, "DefleMask module", "*.dmf"},
"Furnace song{.fur},DefleMask module{.dmf}",
workingDirTest, workingDirTest,
dpiScale dpiScale
); );
@ -3835,7 +3813,9 @@ bool FurnaceGUI::loop() {
if (!e->isRunning()) { if (!e->isRunning()) {
activeNotes.clear(); activeNotes.clear();
memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float)); memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float));
memset(chanOscPitch,0,DIV_MAX_CHANS*sizeof(float)); for (int i=0; i<DIV_MAX_CHANS; i++) {
chanOscChan[i].pitch=0.0f;
}
memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float)); memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float));
e->synchronized([this]() { e->synchronized([this]() {
@ -4392,7 +4372,21 @@ bool FurnaceGUI::loop() {
info="| Groove"; info="| Groove";
} }
info+=fmt::sprintf(" @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getCurHz(),calcBPM(e->getSpeeds(),e->getCurHz(),e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); info+=fmt::sprintf(" @ %gHz (%g BPM) ",e->getCurHz(),calcBPM(e->getSpeeds(),e->getCurHz(),e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD));
if (settings.orderRowsBase) {
info+=fmt::sprintf("| Order %.2X/%.2X ",e->getOrder(),e->curSubSong->ordersLen-1);
} else {
info+=fmt::sprintf("| Order %d/%d ",e->getOrder(),e->curSubSong->ordersLen-1);
}
if (settings.patRowsBase) {
info+=fmt::sprintf("| Row %.2X/%.2X ",e->getRow(),e->curSubSong->patLen);
} else {
info+=fmt::sprintf("| Row %d/%d ",e->getRow(),e->curSubSong->patLen);
}
info+=fmt::sprintf("| %d:%.2d:%.2d.%.2d",totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000);
ImGui::TextUnformatted(info.c_str()); ImGui::TextUnformatted(info.c_str());
} else { } else {
@ -6724,6 +6718,9 @@ bool FurnaceGUI::init() {
} }
#endif #endif
cpuCores=SDL_GetCPUCount();
if (cpuCores<1) cpuCores=1;
logI("done!"); logI("done!");
return true; return true;
} }
@ -6896,6 +6893,10 @@ bool FurnaceGUI::finish() {
backupTask.get(); backupTask.get();
} }
if (chanOscWorkPool!=NULL) {
delete chanOscWorkPool;
}
return true; return true;
} }
@ -6950,6 +6951,7 @@ FurnaceGUI::FurnaceGUI():
killGraphics(false), killGraphics(false),
audioEngineChanged(false), audioEngineChanged(false),
settingsChanged(false), settingsChanged(false),
debugFFT(false),
vgmExportVersion(0x171), vgmExportVersion(0x171),
vgmExportTrailingTicks(-1), vgmExportTrailingTicks(-1),
drawHalt(10), drawHalt(10),
@ -7319,6 +7321,7 @@ FurnaceGUI::FurnaceGUI():
chanOscTextColor(1.0f,1.0f,1.0f,0.75f), chanOscTextColor(1.0f,1.0f,1.0f,0.75f),
chanOscGrad(64,64), chanOscGrad(64,64),
chanOscGradTex(NULL), chanOscGradTex(NULL),
chanOscWorkPool(NULL),
followLog(true), followLog(true),
#ifdef IS_MOBILE #ifdef IS_MOBILE
pianoOctaves(7), pianoOctaves(7),
@ -7418,7 +7421,9 @@ FurnaceGUI::FurnaceGUI():
memset(chanOscLP0,0,sizeof(float)*DIV_MAX_CHANS); memset(chanOscLP0,0,sizeof(float)*DIV_MAX_CHANS);
memset(chanOscLP1,0,sizeof(float)*DIV_MAX_CHANS); memset(chanOscLP1,0,sizeof(float)*DIV_MAX_CHANS);
memset(chanOscVol,0,sizeof(float)*DIV_MAX_CHANS); memset(chanOscVol,0,sizeof(float)*DIV_MAX_CHANS);
memset(chanOscPitch,0,sizeof(float)*DIV_MAX_CHANS); for (int i=0; i<DIV_MAX_CHANS; i++) {
chanOscChan[i].pitch=0.0f;
}
memset(chanOscBright,0,sizeof(float)*DIV_MAX_CHANS); memset(chanOscBright,0,sizeof(float)*DIV_MAX_CHANS);
memset(lastCorrPos,0,sizeof(short)*DIV_MAX_CHANS); memset(lastCorrPos,0,sizeof(short)*DIV_MAX_CHANS);

View file

@ -21,6 +21,7 @@
#define _FUR_GUI_H #define _FUR_GUI_H
#include "../engine/engine.h" #include "../engine/engine.h"
#include "../engine/workPool.h"
#include "../engine/waveSynth.h" #include "../engine/waveSynth.h"
#include "imgui.h" #include "imgui.h"
#include "imgui_impl_sdl2.h" #include "imgui_impl_sdl2.h"
@ -1334,7 +1335,7 @@ class FurnaceGUI {
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString; bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString;
bool mobileEdit; bool mobileEdit;
bool killGraphics; bool killGraphics;
bool audioEngineChanged, settingsChanged; bool audioEngineChanged, settingsChanged, debugFFT;
bool willExport[DIV_MAX_CHIPS]; bool willExport[DIV_MAX_CHIPS];
int vgmExportVersion; int vgmExportVersion;
int vgmExportTrailingTicks; int vgmExportTrailingTicks;
@ -1346,6 +1347,7 @@ class FurnaceGUI {
int mobileEditPage; int mobileEditPage;
int wheelCalmDown; int wheelCalmDown;
int shallDetectScale; int shallDetectScale;
int cpuCores;
float mobileMenuPos, autoButtonSize, mobileEditAnim; float mobileMenuPos, autoButtonSize, mobileEditAnim;
ImVec2 mobileEditButtonPos, mobileEditButtonSize; ImVec2 mobileEditButtonPos, mobileEditButtonSize;
const int* curSysSection; const int* curSysSection;
@ -1515,6 +1517,7 @@ class FurnaceGUI {
int noMultiSystem; int noMultiSystem;
int oldMacroVSlider; int oldMacroVSlider;
int displayAllInsTypes; int displayAllInsTypes;
int displayPartial;
int noteCellSpacing; int noteCellSpacing;
int insCellSpacing; int insCellSpacing;
int volCellSpacing; int volCellSpacing;
@ -1572,6 +1575,9 @@ class FurnaceGUI {
int insIconsStyle; int insIconsStyle;
int classicChipOptions; int classicChipOptions;
int wasapiEx; int wasapiEx;
int chanOscThreads;
int renderPoolThreads;
int showPool;
unsigned int maxUndoSteps; unsigned int maxUndoSteps;
String mainFontPath; String mainFontPath;
String headFontPath; String headFontPath;
@ -1691,6 +1697,7 @@ class FurnaceGUI {
noMultiSystem(0), noMultiSystem(0),
oldMacroVSlider(0), oldMacroVSlider(0),
displayAllInsTypes(0), displayAllInsTypes(0),
displayPartial(0),
noteCellSpacing(0), noteCellSpacing(0),
insCellSpacing(0), insCellSpacing(0),
volCellSpacing(0), volCellSpacing(0),
@ -1747,6 +1754,9 @@ class FurnaceGUI {
insIconsStyle(1), insIconsStyle(1),
classicChipOptions(0), classicChipOptions(0),
wasapiEx(0), wasapiEx(0),
chanOscThreads(0),
renderPoolThreads(0),
showPool(0),
maxUndoSteps(100), maxUndoSteps(100),
mainFontPath(""), mainFontPath(""),
headFontPath(""), headFontPath(""),
@ -2047,27 +2057,46 @@ class FurnaceGUI {
ImVec4 chanOscColor, chanOscTextColor; ImVec4 chanOscColor, chanOscTextColor;
Gradient2D chanOscGrad; Gradient2D chanOscGrad;
FurnaceGUITexture* chanOscGradTex; FurnaceGUITexture* chanOscGradTex;
DivWorkPool* chanOscWorkPool;
float chanOscLP0[DIV_MAX_CHANS]; float chanOscLP0[DIV_MAX_CHANS];
float chanOscLP1[DIV_MAX_CHANS]; float chanOscLP1[DIV_MAX_CHANS];
float chanOscVol[DIV_MAX_CHANS]; float chanOscVol[DIV_MAX_CHANS];
float chanOscPitch[DIV_MAX_CHANS];
float chanOscBright[DIV_MAX_CHANS]; float chanOscBright[DIV_MAX_CHANS];
unsigned short lastNeedlePos[DIV_MAX_CHANS]; unsigned short lastNeedlePos[DIV_MAX_CHANS];
unsigned short lastCorrPos[DIV_MAX_CHANS]; unsigned short lastCorrPos[DIV_MAX_CHANS];
struct ChanOscStatus { struct ChanOscStatus {
double* inBuf; double* inBuf;
fftw_complex* outBuf;
double* corrBuf;
DivDispatchOscBuffer* relatedBuf;
size_t inBufPos; size_t inBufPos;
double inBufPosFrac; double inBufPosFrac;
double waveLen;
int waveLenBottom, waveLenTop, relatedCh;
float pitch, windowSize;
unsigned short needle; unsigned short needle;
fftw_complex* outBuf; bool ready, loudEnough, waveCorr;
fftw_plan plan; fftw_plan plan;
fftw_plan planI;
ChanOscStatus(): ChanOscStatus():
inBuf(NULL), inBuf(NULL),
outBuf(NULL),
corrBuf(NULL),
relatedBuf(NULL),
inBufPos(0), inBufPos(0),
inBufPosFrac(0.0f), inBufPosFrac(0.0f),
waveLen(0.0),
waveLenBottom(0),
waveLenTop(0),
relatedCh(0),
pitch(0.0f),
windowSize(1.0f),
needle(0), needle(0),
outBuf(NULL), ready(false),
plan(NULL) {} loudEnough(false),
waveCorr(false),
plan(NULL),
planI(NULL) {}
} chanOscChan[DIV_MAX_CHANS]; } chanOscChan[DIV_MAX_CHANS];
// visualizer // visualizer

File diff suppressed because it is too large Load diff

View file

@ -39,12 +39,14 @@ void FurnaceGUI::drawPatManager() {
e->lockEngine([this]() { e->lockEngine([this]() {
e->curSubSong->optimizePatterns(); e->curSubSong->optimizePatterns();
}); });
MARK_MODIFIED;
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Re-arrange patterns")) { if (ImGui::Button("Re-arrange patterns")) {
e->lockEngine([this]() { e->lockEngine([this]() {
e->curSubSong->rearrangePatterns(); e->curSubSong->rearrangePatterns();
}); });
MARK_MODIFIED;
} }
if (ImGui::BeginTable("PatManTable",257,ImGuiTableFlags_ScrollX|ImGuiTableFlags_SizingFixedFit)) { if (ImGui::BeginTable("PatManTable",257,ImGuiTableFlags_ScrollX|ImGuiTableFlags_SizingFixedFit)) {
@ -98,6 +100,7 @@ void FurnaceGUI::drawPatManager() {
delete e->curSubSong->pat[i].data[k]; delete e->curSubSong->pat[i].data[k];
e->curSubSong->pat[i].data[k]=NULL; e->curSubSong->pat[i].data[k]=NULL;
}); });
MARK_MODIFIED;
} }
ImGui::PopStyleColor(); ImGui::PopStyleColor();
} }

View file

@ -262,7 +262,10 @@ void FurnaceGUI::drawSampleEdit() {
case DIV_SYSTEM_YM2608_CSM: case DIV_SYSTEM_YM2608_CSM:
if (sample->loop) { if (sample->loop) {
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) { if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
SAMPLE_WARN(warnLoopPos,"YM2608: loop point ignored on ADPCM-B (may only loop entire sample)"); SAMPLE_WARN(warnLoopPos,"YM2608: loop point ignored on ADPCM (may only loop entire sample)");
}
if (sample->samples&511) {
SAMPLE_WARN(warnLength,"YM2608: sample length will be padded to multiple of 512");
} }
} }
break; break;
@ -276,6 +279,9 @@ void FurnaceGUI::drawSampleEdit() {
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) { if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
SAMPLE_WARN(warnLoopPos,"YM2610: loop point ignored on ADPCM-B (may only loop entire sample)"); SAMPLE_WARN(warnLoopPos,"YM2610: loop point ignored on ADPCM-B (may only loop entire sample)");
} }
if (sample->samples&511) {
SAMPLE_WARN(warnLength,"YM2610: sample length will be padded to multiple of 512");
}
} }
if (sample->samples>2097152) { if (sample->samples>2097152) {
SAMPLE_WARN(warnLength,"YM2610: maximum ADPCM-A sample length is 2097152"); SAMPLE_WARN(warnLength,"YM2610: maximum ADPCM-A sample length is 2097152");
@ -284,6 +290,16 @@ void FurnaceGUI::drawSampleEdit() {
EXACT_RATE("YM2610 (ADPCM-A)",dispatch->chipClock/432); EXACT_RATE("YM2610 (ADPCM-A)",dispatch->chipClock/432);
} }
break; break;
case DIV_SYSTEM_Y8950:
if (sample->loop) {
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
SAMPLE_WARN(warnLoopPos,"Y8950: loop point ignored on ADPCM (may only loop entire sample)");
}
if (sample->samples&511) {
SAMPLE_WARN(warnLength,"Y8950: sample length will be padded to multiple of 512");
}
}
break;
case DIV_SYSTEM_AMIGA: case DIV_SYSTEM_AMIGA:
if (sample->loop) { if (sample->loop) {
if (sample->loopStart&1 || sample->loopEnd&1) { if (sample->loopStart&1 || sample->loopEnd&1) {
@ -1197,6 +1213,7 @@ void FurnaceGUI::drawSampleEdit() {
sameLineMaybe(ImGui::CalcTextSize("Zoom").x+150.0f*dpiScale+ImGui::CalcTextSize("100%").x); sameLineMaybe(ImGui::CalcTextSize("Zoom").x+150.0f*dpiScale+ImGui::CalcTextSize("100%").x);
double zoomPercent=100.0/sampleZoom; double zoomPercent=100.0/sampleZoom;
bool checkZoomLimit=false; bool checkZoomLimit=false;
ImGui::AlignTextToFramePadding();
ImGui::Text("Zoom"); ImGui::Text("Zoom");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(150.0f*dpiScale); ImGui::SetNextItemWidth(150.0f*dpiScale);
@ -1675,7 +1692,7 @@ void FurnaceGUI::drawSampleEdit() {
} }
dl->PushClipRect(rectMin,rectMax); dl->PushClipRect(rectMin,rectMax);
if (e->isPreviewingSample()) { if (e->isPreviewingSample() && e->getSamplePreviewSample()==curSample) {
if (!statusBar2.empty()) { if (!statusBar2.empty()) {
statusBar2+=" | "; statusBar2+=" | ";
} }

View file

@ -400,6 +400,27 @@ void FurnaceGUI::drawSettings() {
ImGui::SetTooltip("may cause issues with high-polling-rate mice when previewing notes."); 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 // SUBSECTION FILE
CONFIG_SUBSECTION("File"); CONFIG_SUBSECTION("File");
@ -871,12 +892,44 @@ void FurnaceGUI::drawSettings() {
ImGui::EndTable(); ImGui::EndTable();
} }
if (settings.showPool) {
bool renderPoolThreadsB=(settings.renderPoolThreads>0);
if (ImGui::Checkbox("Multi-threaded (EXPERIMENTAL)",&renderPoolThreadsB)) {
if (renderPoolThreadsB) {
settings.renderPoolThreads=2;
} else {
settings.renderPoolThreads=0;
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("runs chip emulation on separate threads.\nmay increase performance when using heavy emulation cores.\n\nwarnings:\n- experimental!\n- only useful on multi-chip songs.");
}
if (renderPoolThreadsB) {
pushWarningColor(settings.renderPoolThreads>cpuCores,settings.renderPoolThreads>cpuCores);
if (ImGui::InputInt("Number of threads",&settings.renderPoolThreads)) {
if (settings.renderPoolThreads<2) settings.renderPoolThreads=2;
if (settings.renderPoolThreads>32) settings.renderPoolThreads=32;
}
if (settings.renderPoolThreads>=DIV_MAX_CHIPS) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("that's the limit!");
}
} else if (settings.renderPoolThreads>cpuCores) {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("it is a VERY bad idea to set this number higher than your CPU core count (%d)!",cpuCores);
}
}
popWarningColor();
}
}
bool lowLatencyB=settings.lowLatency; bool lowLatencyB=settings.lowLatency;
if (ImGui::Checkbox("Low-latency mode",&lowLatencyB)) { if (ImGui::Checkbox("Low-latency mode",&lowLatencyB)) {
settings.lowLatency=lowLatencyB; settings.lowLatency=lowLatencyB;
} }
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
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)."); ImGui::SetTooltip("reduces latency by running the engine faster than the tick rate.\nuseful for live playback/jam mode.\n\nwarning: only enable if your buffer size is small (10ms or less).");
} }
bool forceMonoB=settings.forceMono; bool forceMonoB=settings.forceMono;
@ -1053,9 +1106,7 @@ void FurnaceGUI::drawSettings() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Action"); ImGui::Text("Action");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Learn");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Remove");
for (size_t i=0; i<midiMap.binds.size(); i++) { for (size_t i=0; i<midiMap.binds.size(); i++) {
MIDIBind& bind=midiMap.binds[i]; MIDIBind& bind=midiMap.binds[i];
@ -1157,13 +1208,15 @@ void FurnaceGUI::drawSettings() {
} }
ImGui::TableNextColumn(); 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) { if (learning==(int)i) {
learning=-1; learning=-1;
} else { } else {
learning=i; learning=i;
} }
} }
popToggleColors();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if (ImGui::Button(ICON_FA_TIMES "##BRemove")) { if (ImGui::Button(ICON_FA_TIMES "##BRemove")) {
@ -3011,6 +3064,8 @@ void FurnaceGUI::drawSettings() {
// "Debug" - toggles mobile UI // "Debug" - toggles mobile UI
// "Nice Amiga cover of the song!" - enables hidden systems (YMU759/SoundUnit/Dummy) // "Nice Amiga cover of the song!" - enables hidden systems (YMU759/SoundUnit/Dummy)
// "42 63" - enables all instrument types // "42 63" - enables all instrument types
// "4-bit FDS" - enables partial pitch linearity option
// "Power of the Chip" - enables options for multi-threaded audio
// "????" - enables stuff // "????" - enables stuff
CONFIG_SECTION("Cheat Codes") { CONFIG_SECTION("Cheat Codes") {
// SUBSECTION ENTER CODE: // SUBSECTION ENTER CODE:
@ -3041,6 +3096,14 @@ void FurnaceGUI::drawSettings() {
mmlString[30]="enabled all instrument types"; mmlString[30]="enabled all instrument types";
settings.displayAllInsTypes=!settings.displayAllInsTypes; settings.displayAllInsTypes=!settings.displayAllInsTypes;
} }
if (checker==0x3f88abcc && checker1==0xf4a6) {
mmlString[30]="OK, if I bring your Partial pitch linearity will you stop bothering me?";
settings.displayPartial=1;
}
if (checker==0x8537719f && checker1==0x17a1f34) {
mmlString[30]="unlocked audio multi-threading options!";
settings.showPool=1;
}
mmlString[31]=""; mmlString[31]="";
} }
@ -3198,6 +3261,7 @@ void FurnaceGUI::syncSettings() {
settings.noMultiSystem=e->getConfInt("noMultiSystem",0); settings.noMultiSystem=e->getConfInt("noMultiSystem",0);
settings.oldMacroVSlider=e->getConfInt("oldMacroVSlider",0); settings.oldMacroVSlider=e->getConfInt("oldMacroVSlider",0);
settings.displayAllInsTypes=e->getConfInt("displayAllInsTypes",0); settings.displayAllInsTypes=e->getConfInt("displayAllInsTypes",0);
settings.displayPartial=e->getConfInt("displayPartial",0);
settings.noteCellSpacing=e->getConfInt("noteCellSpacing",0); settings.noteCellSpacing=e->getConfInt("noteCellSpacing",0);
settings.insCellSpacing=e->getConfInt("insCellSpacing",0); settings.insCellSpacing=e->getConfInt("insCellSpacing",0);
settings.volCellSpacing=e->getConfInt("volCellSpacing",0); settings.volCellSpacing=e->getConfInt("volCellSpacing",0);
@ -3262,6 +3326,9 @@ void FurnaceGUI::syncSettings() {
settings.insIconsStyle=e->getConfInt("insIconsStyle",1); settings.insIconsStyle=e->getConfInt("insIconsStyle",1);
settings.classicChipOptions=e->getConfInt("classicChipOptions",0); settings.classicChipOptions=e->getConfInt("classicChipOptions",0);
settings.wasapiEx=e->getConfInt("wasapiEx",0); settings.wasapiEx=e->getConfInt("wasapiEx",0);
settings.chanOscThreads=e->getConfInt("chanOscThreads",0);
settings.renderPoolThreads=e->getConfInt("renderPoolThreads",0);
settings.showPool=e->getConfInt("showPool",0);
clampSetting(settings.mainFontSize,2,96); clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.headFontSize,2,96); clampSetting(settings.headFontSize,2,96);
@ -3355,6 +3422,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.noMultiSystem,0,1); clampSetting(settings.noMultiSystem,0,1);
clampSetting(settings.oldMacroVSlider,0,1); clampSetting(settings.oldMacroVSlider,0,1);
clampSetting(settings.displayAllInsTypes,0,1); clampSetting(settings.displayAllInsTypes,0,1);
clampSetting(settings.displayPartial,0,1);
clampSetting(settings.noteCellSpacing,0,32); clampSetting(settings.noteCellSpacing,0,32);
clampSetting(settings.insCellSpacing,0,32); clampSetting(settings.insCellSpacing,0,32);
clampSetting(settings.volCellSpacing,0,32); clampSetting(settings.volCellSpacing,0,32);
@ -3410,6 +3478,9 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.insIconsStyle,0,2); clampSetting(settings.insIconsStyle,0,2);
clampSetting(settings.classicChipOptions,0,1); clampSetting(settings.classicChipOptions,0,1);
clampSetting(settings.wasapiEx,0,1); clampSetting(settings.wasapiEx,0,1);
clampSetting(settings.chanOscThreads,0,256);
clampSetting(settings.renderPoolThreads,0,DIV_MAX_CHIPS);
clampSetting(settings.showPool,0,1);
if (settings.exportLoops<0.0) settings.exportLoops=0.0; if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0; if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -3602,6 +3673,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("noMultiSystem",settings.noMultiSystem); e->setConf("noMultiSystem",settings.noMultiSystem);
e->setConf("oldMacroVSlider",settings.oldMacroVSlider); e->setConf("oldMacroVSlider",settings.oldMacroVSlider);
e->setConf("displayAllInsTypes",settings.displayAllInsTypes); e->setConf("displayAllInsTypes",settings.displayAllInsTypes);
e->setConf("displayPartial",settings.displayPartial);
e->setConf("noteCellSpacing",settings.noteCellSpacing); e->setConf("noteCellSpacing",settings.noteCellSpacing);
e->setConf("insCellSpacing",settings.insCellSpacing); e->setConf("insCellSpacing",settings.insCellSpacing);
e->setConf("volCellSpacing",settings.volCellSpacing); e->setConf("volCellSpacing",settings.volCellSpacing);
@ -3665,6 +3737,9 @@ void FurnaceGUI::commitSettings() {
e->setConf("insIconsStyle",settings.insIconsStyle); e->setConf("insIconsStyle",settings.insIconsStyle);
e->setConf("classicChipOptions",settings.classicChipOptions); e->setConf("classicChipOptions",settings.classicChipOptions);
e->setConf("wasapiEx",settings.wasapiEx); e->setConf("wasapiEx",settings.wasapiEx);
e->setConf("chanOscThreads",settings.chanOscThreads);
e->setConf("renderPoolThreads",settings.renderPoolThreads);
e->setConf("showPool",settings.showPool);
// colors // colors
for (int i=0; i<GUI_COLOR_MAX; i++) { for (int i=0; i<GUI_COLOR_MAX; i++) {
@ -4183,6 +4258,12 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
} }
} }
// chan osc work pool
if (chanOscWorkPool!=NULL) {
delete chanOscWorkPool;
chanOscWorkPool=NULL;
}
// colors // colors
if (updateFonts) { if (updateFonts) {
for (int i=0; i<GUI_COLOR_MAX; i++) { for (int i=0; i<GUI_COLOR_MAX; i++) {

View file

@ -44,20 +44,6 @@ void FurnaceGUI::drawSongInfo(bool asChild) {
if (ImGui::InputText("##Name",&e->song.name,ImGuiInputTextFlags_UndoRedo)) { MARK_MODIFIED if (ImGui::InputText("##Name",&e->song.name,ImGuiInputTextFlags_UndoRedo)) { MARK_MODIFIED
updateWindowTitle(); updateWindowTitle();
} }
if (e->song.insLen==1) {
unsigned int checker=0x11111111;
unsigned int checker1=0;
DivInstrument* ins=e->getIns(0);
if (ins->name.size()==15 && e->curSubSong->ordersLen==8) {
for (int i=0; i<15; i++) {
checker^=ins->name[i]<<i;
checker1+=ins->name[i];
checker=(checker>>1|(((checker)^(checker>>2)^(checker>>3)^(checker>>5))&1)<<31);
checker1<<=1;
}
if (checker==0x5ec4497d && checker1==0x6347ee) nonLatchNibble=true;
}
}
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();