ref: d13d5a8ae53c840222223cfefa4b721e3e185e47
dir: /src/pt2_sync.c/
#include <stdint.h>
#include <stdbool.h>
#include "pt2_header.h"
#include "pt2_sync.h"
#include "pt2_scopes.h"
#include "pt2_audio.h"
#include "pt2_visuals.h"
#include "pt2_tables.h"
static volatile bool chQueueClearing;
chSyncData_t *chSyncEntry; // globalized
chSync_t chSync; // globalized
void resetChSyncQueue(void)
{
chSync.data[0].timestamp = 0;
chSync.writePos = 0;
chSync.readPos = 0;
}
static int32_t chQueueReadSize(void)
{
while (chQueueClearing);
if (chSync.writePos > chSync.readPos)
return chSync.writePos - chSync.readPos;
else if (chSync.writePos < chSync.readPos)
return chSync.writePos - chSync.readPos + SYNC_QUEUE_LEN + 1;
else
return 0;
}
static int32_t chQueueWriteSize(void)
{
int32_t size;
if (chSync.writePos > chSync.readPos)
{
size = chSync.readPos - chSync.writePos + SYNC_QUEUE_LEN;
}
else if (chSync.writePos < chSync.readPos)
{
chQueueClearing = true;
/* Buffer is full, reset the read/write pos. This is actually really nasty since
** read/write are two different threads, but because of timestamp validation it
** shouldn't be that dangerous.
** It will also create a small visual stutter while the buffer is getting filled,
** though that is barely noticable on normal buffer sizes, and it takes a minute
** or two at max BPM between each time (when queue size is default, 8191)
*/
chSync.data[0].timestamp = 0;
chSync.readPos = 0;
chSync.writePos = 0;
size = SYNC_QUEUE_LEN;
chQueueClearing = false;
}
else
{
size = SYNC_QUEUE_LEN;
}
return size;
}
bool chQueuePush(chSyncData_t t)
{
if (!chQueueWriteSize())
return false;
assert(chSync.writePos <= SYNC_QUEUE_LEN);
chSync.data[chSync.writePos] = t;
chSync.writePos = (chSync.writePos + 1) & SYNC_QUEUE_LEN;
return true;
}
static bool chQueuePop(void)
{
if (!chQueueReadSize())
return false;
chSync.readPos = (chSync.readPos + 1) & SYNC_QUEUE_LEN;
assert(chSync.readPos <= SYNC_QUEUE_LEN);
return true;
}
static chSyncData_t *chQueuePeek(void)
{
if (!chQueueReadSize())
return NULL;
assert(chSync.readPos <= SYNC_QUEUE_LEN);
return &chSync.data[chSync.readPos];
}
static uint64_t getChQueueTimestamp(void)
{
if (!chQueueReadSize())
return 0;
assert(chSync.readPos <= SYNC_QUEUE_LEN);
return chSync.data[chSync.readPos].timestamp;
}
void updateChannelSyncBuffer(void)
{
uint8_t updateFlags[AMIGA_VOICES];
chSyncEntry = NULL;
memset(updateFlags, 0, sizeof (updateFlags)); // this is needed
const uint64_t frameTime64 = SDL_GetPerformanceCounter();
// handle channel sync queue
while (chQueueClearing);
while (chQueueReadSize() > 0)
{
if (frameTime64 < getChQueueTimestamp())
break; // we have no more stuff to render for now
chSyncEntry = chQueuePeek();
if (chSyncEntry == NULL)
break;
for (int32_t i = 0; i < AMIGA_VOICES; i++)
updateFlags[i] |= chSyncEntry->channels[i].flags; // yes, OR the status
if (!chQueuePop())
break;
}
/* Extra validation because of possible issues when the buffer is full
** and positions are being reset, which is not entirely thread safe.
*/
if (chSyncEntry != NULL && chSyncEntry->timestamp == 0)
chSyncEntry = NULL;
// do actual updates
if (chSyncEntry != NULL)
{
scope_t *s = scope;
syncedChannel_t *c = chSyncEntry->channels;
for (int32_t ch = 0; ch < AMIGA_VOICES; ch++, s++, c++)
{
const uint8_t flags = updateFlags[ch];
if (flags == 0)
continue;
if (flags & SET_SCOPE_VOLUME)
scope[ch].volume = c->volume;
if (flags & SET_SCOPE_PERIOD)
scopeSetPeriod(ch, c->period);
if (flags & TRIGGER_SCOPE)
{
s->newData = c->triggerData;
s->newLength = c->triggerLength;
scopeTrigger(ch);
}
if (flags & SET_SCOPE_DATA)
scope[ch].newData = c->newData;
if (flags & SET_SCOPE_LENGTH)
scope[ch].newLength = c->newLength;
if (flags & STOP_SCOPE)
scope[ch].active = false;
if (flags & UPDATE_ANALYZER)
updateSpectrumAnalyzer(c->analyzerVolume, c ->analyzerPeriod);
if (flags & UPDATE_VUMETER) // for fake VU-meters only
{
if (c->vuVolume <= 64)
editor.vuMeterVolumes[ch] = vuMeterHeights[c->vuVolume];
}
}
}
}