GUI: per-chan osc multi-threading!

This commit is contained in:
tildearrow 2023-09-05 04:38:57 -05:00
parent c99899a002
commit 1da000b00c
7 changed files with 337 additions and 105 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

View File

@ -17,4 +17,136 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "workPool.h" #include "workPool.h"
#include "../ta-log.h"
#include <thread>
void* _workThread(void* inst) {
((DivWorkThread*)inst)->run();
return NULL;
}
void DivWorkThread::run() {
std::unique_lock<std::mutex> unique(selfLock);
DivPendingTask task;
logV("running work thread");
while (true) {
lock.lock();
if (tasks.empty()) {
lock.unlock();
isBusy=false;
parent->notify.notify_one();
if (terminate) {
break;
}
notify.wait(unique);
continue;
} else {
task=tasks.front();
tasks.pop();
lock.unlock();
task.func(task.funcArg);
parent->busyCount--;
parent->notify.notify_one();
}
}
}
bool DivWorkThread::assign(const std::function<void(void*)>& what, void* arg) {
lock.lock();
if (tasks.size()>=30) {
lock.unlock();
return false;
}
tasks.push(DivPendingTask(what,arg));
parent->busyCount++;
parent->notify.notify_one();
isBusy=true;
lock.unlock();
notify.notify_one();
return true;
}
void DivWorkThread::wait() {
if (!isBusy) return;
}
bool DivWorkThread::busy() {
return isBusy;
}
void DivWorkThread::finish() {
lock.lock();
terminate=true;
lock.unlock();
notify.notify_one();
thread->join();
}
void DivWorkThread::init(DivWorkPool* p) {
parent=p;
thread=new std::thread(_workThread,this);
}
void DivWorkPool::push(const std::function<void(void*)>& what, void* arg) {
//logV("submitting work");
// if no work threads, just execute
if (!threaded) {
what(arg);
return;
}
if (pos>=count) pos=0;
for (unsigned int tryCount=0; tryCount<count; tryCount++) {
if (workThreads[pos++].assign(what,arg)) return;
}
// all threads are busy
logV("all busy");
what(arg);
}
bool DivWorkPool::busy() {
if (!threaded) return false;
for (unsigned int i=0; i<count; i++) {
if (workThreads[i].busy()) return true;
}
return false;
}
void DivWorkPool::wait() {
if (!threaded) return;
std::unique_lock<std::mutex> unique(selfLock);
while (busyCount!=0) {
notify.wait_for(unique,std::chrono::milliseconds(100));
}
}
DivWorkPool::DivWorkPool(unsigned int threads):
threaded(threads>0),
count(threads),
pos(0),
busyCount(0) {
if (threaded) {
workThreads=new DivWorkThread[threads];
for (unsigned int i=0; i<count; i++) {
workThreads[i].init(this);
}
} else {
workThreads=NULL;
}
}
DivWorkPool::~DivWorkPool() {
if (threaded) {
for (unsigned int i=0; i<count; i++) {
workThreads[i].finish();
}
delete[] workThreads;
}
}

View File

@ -22,18 +22,46 @@
#include <thread> #include <thread>
#include <mutex> #include <mutex>
#include <atomic>
#include <functional> #include <functional>
#include <condition_variable> #include <condition_variable>
#include "fixedQueue.h"
class DivWorkPool;
struct DivPendingTask {
std::function<void(void*)> func;
void* funcArg;
DivPendingTask(std::function<void(void*)> f, void* arg):
func(f),
funcArg(arg) {}
DivPendingTask():
func(NULL),
funcArg(NULL) {}
};
struct DivWorkThread { struct DivWorkThread {
DivWorkPool* parent;
std::mutex lock; std::mutex lock;
std::mutex selfLock;
std::thread* thread; std::thread* thread;
std::condition_variable notify; std::condition_variable notify;
bool busy, terminate; FixedQueue<DivPendingTask,32> tasks;
std::atomic<bool> isBusy;
bool terminate;
void run(); void run();
bool assign(const std::function<void(void*)>& what, void* arg);
void wait();
bool busy();
void finish();
void init(DivWorkPool* p);
DivWorkThread(): DivWorkThread():
busy(false) {} parent(NULL),
isBusy(false),
terminate(false) {}
}; };
/** /**
@ -41,13 +69,20 @@ struct DivWorkThread {
* it is highly recommended to use `new` when allocating a DivWorkPool. * it is highly recommended to use `new` when allocating a DivWorkPool.
*/ */
class DivWorkPool { class DivWorkPool {
bool threaded;
std::mutex selfLock;
unsigned int count;
unsigned int pos;
DivWorkThread* workThreads; DivWorkThread* workThreads;
public: public:
std::condition_variable notify;
std::atomic<int> busyCount;
/** /**
* push a new job to this work pool. * push a new job to this work pool.
* if all work threads are busy, this will block until one is free. * if all work threads are busy, this will block until one is free.
*/ */
bool push(); void push(const std::function<void(void*)>& what, void* arg);
/** /**
* check whether this work pool is busy. * check whether this work pool is busy.
@ -57,7 +92,7 @@ class DivWorkPool {
/** /**
* wait for all work threads to finish. * wait for all work threads to finish.
*/ */
bool wait(); void wait();
DivWorkPool(unsigned int threads=0); DivWorkPool(unsigned int threads=0);
~DivWorkPool(); ~DivWorkPool();

View File

@ -367,6 +367,12 @@ void FurnaceGUI::drawChanOsc() {
ImGuiStyle& style=ImGui::GetStyle(); ImGuiStyle& style=ImGui::GetStyle();
ImVec2 waveform[1024]; ImVec2 waveform[1024];
// check work thread
if (chanOscWorkPool==NULL) {
logV("creating chan osc work pool");
chanOscWorkPool=new DivWorkPool(settings.chanOscThreads);
}
// fill buffers // 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);
@ -379,137 +385,144 @@ void FurnaceGUI::drawChanOsc() {
// process // process
for (size_t i=0; i<oscBufs.size(); i++) { for (size_t i=0; i<oscBufs.size(); i++) {
DivDispatchOscBuffer* buf=oscBufs[i]; ChanOscStatus* fft_=oscFFTs[i];
ChanOscStatus* fft=oscFFTs[i];
int ch=oscChans[i];
if (buf!=NULL) { fft_->relatedBuf=oscBufs[i];
fft_->relatedCh=oscChans[i];
if (fft_->relatedBuf!=NULL) {
// prepare // prepare
if (centerSettingReset) { if (centerSettingReset) {
buf->readNeedle=buf->needle; fft_->relatedBuf->readNeedle=fft_->relatedBuf->needle;
} }
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
// check FFT status existence // check FFT status existence
if (!fft->ready) { if (!fft_->ready) {
logD("creating FFT plan for channel %d",ch); logD("creating FFT plan for channel %d",fft_->relatedCh);
fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); fft_->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double));
fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex)); fft_->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex));
fft->corrBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double)); 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_->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); fft_->planI=fftw_plan_dft_c2r_1d(FURNACE_FFT_SIZE,fft_->outBuf,fft_->corrBuf,FFTW_ESTIMATE);
if (fft->plan==NULL) { if (fft_->plan==NULL) {
logE("failed to create plan!"); logE("failed to create plan!");
} else if (fft->planI==NULL) { } else if (fft_->planI==NULL) {
logE("failed to create inverse plan!"); logE("failed to create inverse plan!");
} else if (fft->inBuf==NULL || fft->outBuf==NULL || fft->corrBuf==NULL) { } else if (fft_->inBuf==NULL || fft_->outBuf==NULL || fft_->corrBuf==NULL) {
logE("failed to create FFT buffers"); logE("failed to create FFT buffers");
} else { } else {
fft->ready=true; fft_->ready=true;
} }
} }
if (fft->ready && e->isRunning()) { if (fft_->ready && e->isRunning()) {
// the STRATEGY chanOscWorkPool->push([this](void* fft_v) {
// 1. FFT of windowed signal ChanOscStatus* fft=(ChanOscStatus*)fft_v;
// 2. inverse FFT of auto-correlation DivDispatchOscBuffer* buf=fft->relatedBuf;
// 3. find size of one period int ch=fft->relatedCh;
// 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 // the STRATEGY
double phase=0.0; // 1. FFT of windowed signal
fft->loudEnough=false; // 2. inverse FFT of auto-correlation
fft->needle=buf->needle; // 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
// first FFT // initialization
for (int j=0; j<FURNACE_FFT_SIZE; j++) { double phase=0.0;
fft->inBuf[j]=(double)buf->data[(unsigned short)(fft->needle-displaySize*2+((j*displaySize*2)/(FURNACE_FFT_SIZE)))]/32768.0; int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
if (fft->inBuf[j]>0.001 || fft->inBuf[j]<-0.001) fft->loudEnough=true; fft->loudEnough=false;
fft->inBuf[j]*=0.55-0.45*cos(M_PI*(double)j/(double)(FURNACE_FFT_SIZE>>1)); fft->needle=buf->needle;
}
// only proceed if not quiet // first FFT
if (fft->loudEnough) {
fftw_execute(fft->plan);
// auto-correlation and second FFT
for (int j=0; j<FURNACE_FFT_SIZE; j++) { for (int j=0; j<FURNACE_FFT_SIZE; j++) {
fft->outBuf[j][0]/=FURNACE_FFT_SIZE; fft->inBuf[j]=(double)buf->data[(unsigned short)(fft->needle-displaySize*2+((j*displaySize*2)/(FURNACE_FFT_SIZE)))]/32768.0;
fft->outBuf[j][1]/=FURNACE_FFT_SIZE; if (fft->inBuf[j]>0.001 || fft->inBuf[j]<-0.001) fft->loudEnough=true;
fft->outBuf[j][0]=fft->outBuf[j][0]*fft->outBuf[j][0]+fft->outBuf[j][1]*fft->outBuf[j][1]; fft->inBuf[j]*=0.55-0.45*cos(M_PI*(double)j/(double)(FURNACE_FFT_SIZE>>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 // only proceed if not quiet
double waveLenCandL=DBL_MAX; if (fft->loudEnough) {
double waveLenCandH=DBL_MIN; fftw_execute(fft->plan);
fft->waveLen=FURNACE_FFT_SIZE-1;
fft->waveLenBottom=0;
fft->waveLenTop=0;
// find lowest point // auto-correlation and second FFT
for (int j=(FURNACE_FFT_SIZE>>2); j>2; j--) { for (int j=0; j<FURNACE_FFT_SIZE; j++) {
if (fft->corrBuf[j]<waveLenCandL) { fft->outBuf[j][0]/=FURNACE_FFT_SIZE;
waveLenCandL=fft->corrBuf[j]; fft->outBuf[j][1]/=FURNACE_FFT_SIZE;
fft->waveLenBottom=j; 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;
// find highest point fft->outBuf[1][0]=0;
for (int j=(FURNACE_FFT_SIZE>>1)-1; j>fft->waveLenBottom; j--) { fft->outBuf[1][1]=0;
if (fft->corrBuf[j]>waveLenCandH) { fftw_execute(fft->planI);
waveLenCandH=fft->corrBuf[j];
fft->waveLen=j;
}
}
fft->waveLenTop=fft->waveLen;
// did we find the period size? // window
if (fft->waveLen<(FURNACE_FFT_SIZE-32)) { for (int j=0; j<(FURNACE_FFT_SIZE>>1); j++) {
// we got pitch fft->corrBuf[j]*=1.0-((double)j/(double)(FURNACE_FFT_SIZE<<1));
chanOscPitch[ch]=pow(1.0-(fft->waveLen/(double)(FURNACE_FFT_SIZE>>1)),4.0); }
// 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;
}
}
fft->waveLen*=(double)displaySize*2.0/(double)FURNACE_FFT_SIZE; // find highest point
for (int j=(FURNACE_FFT_SIZE>>1)-1; j>fft->waveLenBottom; j--) {
// DFT of one period (x_1) if (fft->corrBuf[j]>waveLenCandH) {
double dft[2]; waveLenCandH=fft->corrBuf[j];
dft[0]=0.0; fft->waveLen=j;
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);
} }
fft->waveLenTop=fft->waveLen;
// calculate and lock into phase // did we find the period size?
phase=(0.5+(atan2(dft[1],dft[0])/(2.0*M_PI))); if (fft->waveLen<(FURNACE_FFT_SIZE-32)) {
// we got pitch
chanOscPitch[ch]=pow(1.0-(fft->waveLen/(double)(FURNACE_FFT_SIZE>>1)),4.0);
fft->waveLen*=(double)displaySize*2.0/(double)FURNACE_FFT_SIZE;
if (chanOscWaveCorr) { // DFT of one period (x_1)
fft->needle-=phase*fft->waveLen; double dft[2];
dft[0]=0.0;
dft[1]=0.0;
for (int j=fft->needle-1-(displaySize>>1)-(int)fft->waveLen, k=0; k<fft->waveLen; j++, k++) {
double one=((double)buf->data[j&0xffff]/32768.0);
double two=(double)k*(-2.0*M_PI)/fft->waveLen;
dft[0]+=one*cos(two);
dft[1]+=one*sin(two);
}
// calculate and lock into phase
phase=(0.5+(atan2(dft[1],dft[0])/(2.0*M_PI)));
if (chanOscWaveCorr) {
fft->needle-=phase*fft->waveLen;
}
} }
} }
}
fft->needle-=displaySize; fft->needle-=displaySize;
},fft_);
} }
} }
} }
chanOscWorkPool->wait();
// 0: none // 0: none
// 1: sqrt(chans) // 1: sqrt(chans)
@ -644,7 +657,12 @@ void FurnaceGUI::drawChanOsc() {
} }
ImGui::PushClipRect(inRect.Min,inRect.Max,false); ImGui::PushClipRect(inRect.Min,inRect.Max,false);
ImDrawListFlags prevFlags=dl->Flags;
//if (!settings.oscAntiAlias) {
dl->Flags&=~(ImDrawListFlags_AntiAliasedLines|ImDrawListFlags_AntiAliasedLinesUseTex);
//}
dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale); dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale);
dl->Flags=prevFlags;
if (!chanOscTextFormat.empty()) { if (!chanOscTextFormat.empty()) {
String text; String text;

View File

@ -6685,6 +6685,9 @@ bool FurnaceGUI::init() {
} }
#endif #endif
cpuCores=SDL_GetCPUCount();
if (cpuCores<1) cpuCores=1;
logI("done!"); logI("done!");
return true; return true;
} }
@ -6857,6 +6860,10 @@ bool FurnaceGUI::finish() {
backupTask.get(); backupTask.get();
} }
if (chanOscWorkPool!=NULL) {
delete chanOscWorkPool;
}
return true; return true;
} }
@ -7281,6 +7288,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),

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"
@ -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;
@ -1572,6 +1574,7 @@ class FurnaceGUI {
int insIconsStyle; int insIconsStyle;
int classicChipOptions; int classicChipOptions;
int wasapiEx; int wasapiEx;
int chanOscThreads;
unsigned int maxUndoSteps; unsigned int maxUndoSteps;
String mainFontPath; String mainFontPath;
String headFontPath; String headFontPath;
@ -1747,6 +1750,7 @@ class FurnaceGUI {
insIconsStyle(1), insIconsStyle(1),
classicChipOptions(0), classicChipOptions(0),
wasapiEx(0), wasapiEx(0),
chanOscThreads(0),
maxUndoSteps(100), maxUndoSteps(100),
mainFontPath(""), mainFontPath(""),
headFontPath(""), headFontPath(""),
@ -2047,6 +2051,7 @@ 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];
@ -2058,10 +2063,11 @@ class FurnaceGUI {
double* inBuf; double* inBuf;
fftw_complex* outBuf; fftw_complex* outBuf;
double* corrBuf; double* corrBuf;
DivDispatchOscBuffer* relatedBuf;
size_t inBufPos; size_t inBufPos;
double inBufPosFrac; double inBufPosFrac;
double waveLen; double waveLen;
int waveLenBottom, waveLenTop; int waveLenBottom, waveLenTop, relatedCh;
unsigned short needle; unsigned short needle;
bool ready, loudEnough; bool ready, loudEnough;
fftw_plan plan; fftw_plan plan;
@ -2070,11 +2076,13 @@ class FurnaceGUI {
inBuf(NULL), inBuf(NULL),
outBuf(NULL), outBuf(NULL),
corrBuf(NULL), corrBuf(NULL),
relatedBuf(NULL),
inBufPos(0), inBufPos(0),
inBufPosFrac(0.0f), inBufPosFrac(0.0f),
waveLen(0.0), waveLen(0.0),
waveLenBottom(0), waveLenBottom(0),
waveLenTop(0), waveLenTop(0),
relatedCh(0),
needle(0), needle(0),
ready(false), ready(false),
loudEnough(false), loudEnough(false),

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");
@ -3262,6 +3283,7 @@ 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);
clampSetting(settings.mainFontSize,2,96); clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.headFontSize,2,96); clampSetting(settings.headFontSize,2,96);
@ -3410,6 +3432,7 @@ 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);
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;
@ -3665,6 +3688,7 @@ 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);
// colors // colors
for (int i=0; i<GUI_COLOR_MAX; i++) { for (int i=0; i<GUI_COLOR_MAX; i++) {
@ -4182,6 +4206,12 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
} }
} }
} }
// chan osc work pool
if (chanOscWorkPool!=NULL) {
delete chanOscWorkPool;
chanOscWorkPool=NULL;
}
// colors // colors
if (updateFonts) { if (updateFonts) {