commit 93fa5281b981eefbe97e7dc85272597f16ac0a20
parent 41881a95ddbb9dfcdd9f01bb11196da8b3a8d883
Author: Hans Petter Selasky <hps@selasky.org>
Date: Sat, 19 Nov 2022 15:30:59 +0100
Implement own thread for rendering samples
This functionality is controlled by the HAVE_BG_SYNTH_THREAD cmake keyword.
The default value is off.
Some audio backend drivers like JACK may have insanely small buffers which
must be returned to the audio driver in a fashion that leaves no time for
ZynAddSubFX to produce the required audio output, leading to audio underruns,
which does not sound good. Unfortunatly JACK does not support per-client
buffering.
Add generic functionality to the OutMgr class to render audio in the
background, returning already generated audio as quick as possible.
While at it add an enum, FRAME_SIZE_MAX, which basically sets a hard
limit on the allowed per-tick frame size values support. Assert this,
because violations means writing outside of the designated buffers.
This value has been raised from 4K to 16K, which also reduces the amount
of copying when the staled samples needs to be cleaned up.
Make a slight change to the MIDI input algorithm, to reset the time window
it is looking at, when the end of MIDI events is seen. This means MIDI events
received faster than synth.buffersize samples, will be considered received
at the same time.
Fix #216
Signed-off-by: Hans Petter Selasky <hps@selasky.org>
Diffstat:
3 files changed, 159 insertions(+), 21 deletions(-)
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
@@ -113,6 +113,12 @@ if(HAVE_SCHEDULER AND (NOT "Darwin" STREQUAL ${CMAKE_SYSTEM_NAME}))
add_definitions(-DHAVE_SCHEDULER=${HAVE_SCHEDULER})
endif()
+if(HAVE_BG_SYNTH_THREAD)
+ add_definitions(-DHAVE_BG_SYNTH_THREAD=1)
+else()
+ add_definitions(-DHAVE_BG_SYNTH_THREAD=0)
+endif()
+
if(HAVE_ASYNC)
add_definitions(-DHAVE_ASYNC=1)
else()
@@ -702,8 +708,8 @@ package_status(JackEnable "JACK " "enabled" ${Yellow})
package_status(OssEnable "OSS " "enabled" ${Yellow})
package_status(PaEnable "PA " "enabled" ${Yellow})
package_status(SndioEnable "SNDIO " "enabled" ${Yellow})
+package_status(HAVE_BG_SYNTH_THREAD "Background synthesizer thread" "enabled" ${Yellow})
#TODO GUI MODULE
package_status(HAVE_ASYNC "c++ async" "usable" ${Yellow})
-
message(STATUS "Link libraries: ${ZLIB_LIBRARY} ${FFTW3F_LIBRARY} ${MXML_LIBRARIES} ${AUDIO_LIBRARIES} ${OS_LIBRARIES}")
diff --git a/src/Nio/OutMgr.cpp b/src/Nio/OutMgr.cpp
@@ -30,11 +30,55 @@ OutMgr &OutMgr::getInstance(const SYNTH_T *synth)
return instance;
}
+#if HAVE_BG_SYNTH_THREAD
+void *
+OutMgr::_refillThread(void *arg)
+{
+ return static_cast<OutMgr *>(arg)->refillThread();
+}
+
+void *
+OutMgr::refillThread()
+{
+ refillLock();
+ while (bgSynthEnabled) {
+ refillSmps(stales + synth.buffersize);
+ refillWakeup();
+ refillWait();
+ }
+ refillUnlock();
+ return 0;
+}
+
+void
+OutMgr::setBackgroundSynth(bool enable)
+{
+ void *dummy;
+
+ if (bgSynthEnabled == enable)
+ return;
+ if (bgSynthEnabled) {
+ refillLock();
+ bgSynthEnabled = false;
+ refillWakeup();
+ refillUnlock();
+
+ pthread_join(bgSynthThread, &dummy);
+ } else {
+ refillLock();
+ bgSynthEnabled = true;
+ refillUnlock();
+
+ pthread_create(&bgSynthThread, 0, &_refillThread, this);
+ }
+}
+#endif
+
OutMgr::OutMgr(const SYNTH_T *synth_)
:wave(new WavEngine(*synth_)),
- priBuf(new float[4096],
- new float[4096]), priBuffCurrent(priBuf),
- master(NULL), stales(0), synth(*synth_)
+ priBuf(new float[FRAME_SIZE_MAX],
+ new float[FRAME_SIZE_MAX]),
+ priBuffCurrent(priBuf), master(NULL), stales(0), synth(*synth_)
{
assert(synth_);
currentOut = NULL;
@@ -44,20 +88,58 @@ OutMgr::OutMgr(const SYNTH_T *synth_)
outl = new float[synth.buffersize];
memset(outl, 0, synth.bufferbytes);
memset(outr, 0, synth.bufferbytes);
+
+#if HAVE_BG_SYNTH_THREAD
+ pthread_mutex_init(&bgSynthMtx, 0);
+ pthread_cond_init(&bgSynthCond, 0);
+ bgSynthEnabled = false;
+#endif
+ midiFlushOffset = 0;
+
+ /* at any stales value, there should be space for synth.buffersize samples */
+ maxStoredSmps = FRAME_SIZE_MAX - (FRAME_SIZE_MAX % synth.buffersize);
+ assert(maxStoredSmps > (unsigned int)synth.buffersize);
+ maxStoredSmps -= synth.buffersize;
}
OutMgr::~OutMgr()
{
+#if HAVE_BG_SYNTH_THREAD
+ setBackgroundSynth(false);
+#endif
+
delete wave;
delete [] priBuf.l;
delete [] priBuf.r;
delete [] outr;
delete [] outl;
+#if HAVE_BG_SYNTH_THREAD
+ pthread_cond_destroy(&bgSynthCond);
+ pthread_mutex_destroy(&bgSynthMtx);
+#endif
+}
+
+void OutMgr::refillSmps(unsigned int smpsLimit)
+{
+ InMgr &midi = InMgr::getInstance();
+
+ while(smpsLimit > curStoredSmps()) {
+ refillUnlock();
+ if(!midi.empty() &&
+ !midi.flush(midiFlushOffset, midiFlushOffset + synth.buffersize)) {
+ midiFlushOffset += synth.buffersize;
+ } else {
+ midiFlushOffset = 0;
+ }
+ master->AudioOut(outl, outr);
+ refillLock();
+ addSmps(outl, outr);
+ }
}
/* Sequence of a tick
* 1) Lets remove old/stale samples
- * 2) Apply appliciable midi events
+ * 2) Apply applicable MIDI events
* 3) Lets see if we need to generate samples
* 4) Lets generate some
* 5) Goto 2 if more are needed
@@ -66,20 +148,39 @@ OutMgr::~OutMgr()
*/
const Stereo<float *> OutMgr::tick(unsigned int frameSize)
{
- InMgr &midi = InMgr::getInstance();
+ auto retval = priBuf;
//SysEv->execute();
- removeStaleSmps();
- int i=0;
- while(frameSize > storedSmps()) {
- if(!midi.empty()) {
- midi.flush(i*synth.buffersize, (i+1)*synth.buffersize);
- }
- master->AudioOut(outl, outr);
- addSmps(outl, outr);
- i++;
+ refillLock();
+ /* cleanup stales, if any */
+ if(frameSize + stales > maxStoredSmps)
+ removeStaleSmps();
+#if HAVE_BG_SYNTH_THREAD
+ /* check if backround sampling is enabled */
+ if(bgSynthEnabled) {
+ assert(frameSize <= (unsigned int)synth.buffersize);
+ /* wait for background samples to complete, if any */
+ while(frameSize + stales > curStoredSmps())
+ refillWait();
+ } else {
+#endif
+ /* check if drivers ask for too many samples */
+ assert(frameSize + stales <= maxStoredSmps);
+ /* produce samples foreground, if any */
+ refillSmps(frameSize + stales);
+#if HAVE_BG_SYNTH_THREAD
+ }
+#endif
+ retval.l += stales;
+ retval.r += stales;
+ stales += frameSize;
+#if HAVE_BG_SYNTH_THREAD
+ if(bgSynthEnabled) {
+ /* start refill thread again, if any */
+ refillWakeup();
}
- stales = frameSize;
- return priBuf;
+#endif
+ refillUnlock();
+ return retval;
}
AudioOut *OutMgr::getOut(string name)
@@ -194,7 +295,7 @@ void OutMgr::removeStaleSmps()
if(!stales)
return;
- const int leftover = storedSmps() - stales;
+ const int leftover = curStoredSmps() - stales;
assert(leftover > -1);
diff --git a/src/Nio/OutMgr.h b/src/Nio/OutMgr.h
@@ -15,6 +15,9 @@
#include "../Misc/Stereo.h"
#include "../globals.h"
#include <list>
+#if HAVE_BG_SYNTH_THREAD
+#include <pthread.h>
+#endif
#include <string>
#include <semaphore.h>
@@ -25,6 +28,8 @@ struct SYNTH_T;
class OutMgr
{
public:
+ enum { FRAME_SIZE_MAX = 1U << 14 };
+
static OutMgr &getInstance(const SYNTH_T *synth=NULL);
~OutMgr();
@@ -60,26 +65,52 @@ class OutMgr
void setMaster(class Master *master_);
void applyOscEventRt(const char *msg);
+#if HAVE_BG_SYNTH_THREAD
+ void setBackgroundSynth(bool);
+ static void *_refillThread(void *);
+ void *refillThread();
+#endif
private:
OutMgr(const SYNTH_T *synth);
void addSmps(float *l, float *r);
- unsigned int storedSmps() const {return priBuffCurrent.l - priBuf.l; }
+ unsigned int curStoredSmps() const {return priBuffCurrent.l - priBuf.l; }
void removeStaleSmps();
+#if HAVE_BG_SYNTH_THREAD
+ void refillLock() { pthread_mutex_lock(&bgSynthMtx); }
+ void refillUnlock() { pthread_mutex_unlock(&bgSynthMtx); }
+ void refillWait() { pthread_cond_wait(&bgSynthCond, &bgSynthMtx); }
+ void refillWakeup() { pthread_cond_broadcast(&bgSynthCond); }
+#else
+ void refillLock() { }
+ void refillUnlock() { }
+#endif
+ void refillSmps(unsigned int);
AudioOut *currentOut; /**<The current output driver*/
sem_t requested;
/**Buffer*/
- Stereo<float *> priBuf; //buffer for primary drivers
+ Stereo<float *> priBuf; //buffer for primary drivers
Stereo<float *> priBuffCurrent; //current array accessor
float *outl;
float *outr;
class Master *master;
- int stales;
+ /**Buffer state*/
+ unsigned int stales;
+ unsigned int maxStoredSmps;
+ unsigned int midiFlushOffset;
const SYNTH_T &synth;
+
+#if HAVE_BG_SYNTH_THREAD
+ /**Background synth*/
+ pthread_mutex_t bgSynthMtx;
+ pthread_cond_t bgSynthCond;
+ pthread_t bgSynthThread;
+ bool bgSynthEnabled;
+#endif
};
}