Sigrid Solveig Haflínudóttirfix warnings1dc9e673 years agoHistory
389 lines · 8.2 KB · C
RawDownload
// used for syncing audio from Paula writes to tracker visuals #include <stdint.h>#include <stdbool.h>#include "pt2_audio.h"#include "pt2_visuals_sync.h"#include "pt2_scopes.h"#include "pt2_visuals.h"#include "pt2_tables.h" typedef struct syncVoice_t{	const int8_t *newData, *data;	uint8_t flags, volume;	uint16_t period;	uint16_t newLength, length;} syncVoice_t; static volatile bool chQueueClearing;static uint32_t audLatencyPerfValInt;static uint64_t audLatencyPerfValFrac;static uint64_t tickTime64, tickTime64Frac;static uint32_t tickTimeLenInt;static uint64_t tickTimeLenFrac;static syncVoice_t syncVoice[PAULA_VOICES];static chSyncData_t *chSyncEntry;static chSync_t chSync; static void startDMA(int32_t ch){	syncVoice_t *sv = &syncVoice[ch]; 	if (editor.songPlaying)	{		sv->data = sv->newData;		sv->length = sv->newLength;		sv->flags |= TRIGGER_SCOPE;	}	else	{		scope_t *s = &scope[ch];		s->data = sv->newData;		s->length = sv->newLength * 2;		scopeTrigger(ch);	}} static void stopDMA(int32_t ch){	if (editor.songPlaying)		syncVoice[ch].flags |= STOP_SCOPE;	else		scope[ch].active = false;} void setVisualsDMACON(uint16_t bits){	if (bits & 0x8000)	{		// set		if (bits & 1) startDMA(0);		if (bits & 2) startDMA(1);		if (bits & 4) startDMA(2);		if (bits & 8) startDMA(3);	}	else	{		// clear		if (bits & 1) stopDMA(0);		if (bits & 2) stopDMA(1);		if (bits & 4) stopDMA(2);		if (bits & 8) stopDMA(3);	}} void setVisualsVolume(int32_t ch, uint16_t vol){	int32_t realVol = vol & 127;	if (realVol > 64)		realVol = 64; 	if (editor.songPlaying)	{		syncVoice_t *sv = &syncVoice[ch]; 		sv->volume = (uint8_t)realVol;		sv->flags |= SET_SCOPE_VOLUME;	}	else	{		scope[ch].volume = (uint8_t)realVol;	}} void setVisualsPeriod(int32_t ch, uint16_t period){	if (period == 0)		period = 65535; // On Amiga: period 0 = one full cycle with period 65536, then period 65535 for the rest	else if (period < 113)		period = 113; // close to what happens on real Amiga (and needed for BLEP synthesis) 	if (editor.songPlaying)	{		syncVoice_t *sv = &syncVoice[ch]; 		sv->period = period;		sv->flags |= SET_SCOPE_PERIOD;	}	else	{		scopeSetPeriod(ch, period);	}} void setVisualsLength(int32_t ch, uint16_t len){	syncVoice_t *sv = &syncVoice[ch]; 	sv->newLength = len; 	if (editor.songPlaying)		sv->flags |= SET_SCOPE_LENGTH;	else		scope[ch].newLength = len * 2;} void setVisualsDataPtr(int32_t ch, const int8_t *src){	syncVoice_t *sv = &syncVoice[ch]; 	if (src == NULL)		src = paulaGetNullSamplePtr(); 	sv->newData = src; 	if (editor.songPlaying)		sv->flags |= SET_SCOPE_DATA;	else		scope[ch].newData = src;} void calcAudioLatencyVars(int32_t audioBufferSize, int32_t audioFreq){	double dInt; 	if (audioFreq == 0)		return; 	const double dAudioLatencySecs = audioBufferSize / (double)audioFreq; 	double dFrac = modf(dAudioLatencySecs * (double)hpcFreq.freq64, &dInt); 	audLatencyPerfValInt = (uint32_t)dInt;	audLatencyPerfValFrac = (uint64_t)((dFrac * TICK_TIME_FRAC_SCALE) + 0.5); // rounded} void setSyncTickTimeLen(uint32_t timeLenInt, uint64_t timeLenFrac){	tickTimeLenInt = timeLenInt;	tickTimeLenFrac = timeLenFrac;} 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;		USED(chQueueClearing); 		/* 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 fillVisualsSyncBuffer(void){	chSyncData_t chSyncData; 	if (audio.resetSyncTickTimeFlag)	{		audio.resetSyncTickTimeFlag = false; 		tickTime64 = SDL_GetPerformanceCounter() + audLatencyPerfValInt;		tickTime64Frac = audLatencyPerfValFrac;	} 	if (song != NULL)	{		moduleChannel_t *ch = song->channels;		syncVoice_t *sv = syncVoice;		syncedChannel_t *sc = chSyncData.channels; 		for (int32_t i = 0; i < PAULA_VOICES; i++, ch++, sc++, sv++)		{			sc->flags = sv->flags | ch->syncFlags;			ch->syncFlags = sv->flags = 0; // clear sync flags 			sc->volume = sv->volume;			sc->period = sv->period;			sc->data = sv->data;			sc->length = sv->length;			sc->newData = sv->newData;			sc->newLength = sv->newLength;			sc->vuVolume = ch->syncVuVolume;			sc->analyzerVolume = ch->syncAnalyzerVolume;			sc->analyzerPeriod = ch->syncAnalyzerPeriod;		} 		chSyncData.timestamp = tickTime64;		chQueuePush(chSyncData);	} 	tickTime64 += tickTimeLenInt; 	tickTime64Frac += tickTimeLenFrac;	if (tickTime64Frac >= TICK_TIME_FRAC_SCALE)	{		tickTime64Frac &= TICK_TIME_FRAC_MASK;		tickTime64++;	}} void updateChannelSyncBuffer(void){	uint8_t updateFlags[PAULA_VOICES]; 	chSyncEntry = NULL; 	*(uint32_t *)updateFlags = 0; // clear all channel update flags (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 < PAULA_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 < PAULA_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); 			// the following handling order is important, don't change it! 			if (flags & STOP_SCOPE)				scope[ch].active = false; 			if (flags & TRIGGER_SCOPE)			{				s->data = c->data;				s->length = c->length * 2;				scopeTrigger(ch);			} 			if (flags & SET_SCOPE_DATA)				scope[ch].newData = c->newData; 			if (flags & SET_SCOPE_LENGTH)				scope[ch].newLength = c->newLength * 2; 			// --------------------------------------------------------------- 			if (flags & UPDATE_SPECTRUM_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];			}		}	}}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t