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

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

View file

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

View file

@ -80,7 +80,7 @@ const char** DivPlatformAmiga::getRegisterSheet() {
}
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++) {
bool hsync=bypassLimits;
outL=0;

View file

@ -52,7 +52,7 @@ const char** DivPlatformArcade::getRegisterSheet() {
}
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 (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) {
static int os[2];
thread_local int os[2];
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) {
static short o[2];
static int os[2];
thread_local short o[2];
thread_local int os[2];
for (size_t h=0; h<len; h++) {
processDAC(rate);
@ -213,7 +213,7 @@ void DivPlatformGenesis::acquire_nuked(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();

View file

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

View file

@ -160,9 +160,9 @@ const int orderedOpsL[4]={
#define ADDR_LR_FB_ALG 0xc0
void DivPlatformOPL::acquire_nuked(short** buf, size_t len) {
static short o[4];
static int os[4];
static ymfm::ymfm_output<2> aOut;
thread_local short o[4];
thread_local int os[4];
thread_local ymfm::ymfm_output<2> aOut;
for (size_t h=0; h<len; h++) {
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
}
}
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(17,(chan[adpcmChan].freq>>8)&0xff);
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) {
static int o[2];
static int os;
thread_local int o[2];
thread_local int os;
for (size_t h=0; h<len; h++) {
os=0;

View file

@ -27,7 +27,7 @@
#define chWrite(c,a,v) rWrite(((c)<<3)+(a),v)
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++) {
while (!writes.empty()) {

View file

@ -58,7 +58,7 @@ const char** DivPlatformTX81Z::getRegisterSheet() {
}
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();

View file

@ -165,8 +165,8 @@ void DivPlatformYM2203::acquire(short** buf, size_t len) {
}
void DivPlatformYM2203::acquire_combo(short** buf, size_t len) {
static int os;
static short ignored[2];
thread_local int os;
thread_local short ignored[2];
for (size_t h=0; h<len; h++) {
// AY -> OPN
@ -241,7 +241,7 @@ void DivPlatformYM2203::acquire_combo(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();

View file

@ -306,8 +306,8 @@ void DivPlatformYM2608::acquire(short** buf, size_t len) {
}
void DivPlatformYM2608::acquire_combo(short** buf, size_t len) {
static int os[2];
static short ignored[2];
thread_local int os[2];
thread_local short ignored[2];
ymfm::ssg_engine* ssge=fm->debug_ssg_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) {
static int os[2];
thread_local int os[2];
ymfm::ym2608::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
@ -783,6 +783,8 @@ void DivPlatformYM2608::tick(bool sysTick) {
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(0x10a,(chan[15].freq>>8)&0xff);
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) {
static int os[2];
static short ignored[2];
thread_local int os[2];
thread_local short ignored[2];
ymfm::ssg_engine* ssge=fm->debug_ssg_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) {
static int os[2];
thread_local int os[2];
ymfm::ym2610::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
@ -717,6 +717,8 @@ void DivPlatformYM2610::tick(bool sysTick) {
} else {
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(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff);
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) {
static int os[2];
static short ignored[2];
thread_local int os[2];
thread_local short ignored[2];
ymfm::ssg_engine* ssge=fm->debug_ssg_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) {
static int os[2];
thread_local int os[2];
ymfm::ym2610b::fm_engine* fme=fm->debug_fm_engine();
ymfm::ssg_engine* ssge=fm->debug_ssg_engine();
@ -784,6 +784,8 @@ void DivPlatformYM2610B::tick(bool sysTick) {
} else {
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(0x1a,(chan[adpcmBChanOffs].freq>>8)&0xff);
hardResetElapsed+=2;

View file

@ -22,6 +22,7 @@
#define _USE_MATH_DEFINES
#include "dispatch.h"
#include "engine.h"
#include "workPool.h"
#include "../ta-log.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) {
lastNBIns=inChans;
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();
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)
if (output) if (output->midiIn) while (!output->midiIn->queue.empty()) {
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
if (cycles<runLeftG) {
for (int i=0; i<song.systemLen; i++) {
int total=(cycles*disCont[i].runtotal)/(size<<MASTER_CLOCK_PREC);
disCont[i].acquire(disCont[i].runPos,total);
disCont[i].runLeft-=total;
disCont[i].runPos+=total;
disCont[i].cycles=cycles;
disCont[i].size=size;
renderPool->push([](void* d) {
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;
cycles=0;
} else {
cycles-=runLeftG;
runLeftG=0;
for (int i=0; i<song.systemLen; i++) {
disCont[i].acquire(disCont[i].runPos,disCont[i].runLeft);
disCont[i].runLeft=0;
renderPool->push([](void* d) {
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);
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) {

View file

@ -454,10 +454,11 @@ void DivEngine::registerSystems() {
{0x18, {DIV_CMD_FM_EXTCH, "18xx: Toggle extended channel 3 mode"}},
});
EffectHandlerMap fmOPN2EffectHandlerMap={
EffectHandlerMap fmOPN2EffectHandlerMap(fmEffectHandlerMap);
fmOPN2EffectHandlerMap.insert({
{0x17, {DIV_CMD_SAMPLE_MODE, "17xx: Toggle PCM mode (LEGACY)"}},
{0xdf, {DIV_CMD_SAMPLE_DIR, "DFxx: Set sample playback direction (0: normal; 1: reverse)"}},
};
});
EffectHandlerMap fmOPLDrumsEffectHandlerMap(fmEffectHandlerMap);
fmOPLDrumsEffectHandlerMap.insert({
@ -1179,7 +1180,7 @@ void DivEngine::registerSystems() {
sysDefs[DIV_SYSTEM_SWAN]=new DivSysDef(
"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"},
{"CH1", "CH2", "CH3", "CH4"},
{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