550 lines · 12.4 KB · C
RawDownload
// for finding memory leaks in debug mode with Visual Studio#if defined _DEBUG && defined _MSC_VER#include <crtdbg.h>#endif #include <stdint.h>#include <stdbool.h>#include <math.h> // modf()#ifndef _WIN32#include <unistd.h> // usleep()#endif#include "../ft2_header.h"#include "../ft2_events.h"#include "../ft2_config.h"#include "../ft2_audio.h"#include "../ft2_gui.h"#include "../ft2_midi.h"#include "../ft2_bmp.h"#include "../ft2_mouse.h"#include "../ft2_video.h"#include "../ft2_tables.h"#include "../ft2_structs.h"#include "../ft2_hpc.h"#include "ft2_scopes.h"#include "ft2_scopedraw.h" static volatile bool scopesUpdatingFlag, scopesDisplayingFlag;static hpc_t scopeHpc;static volatile scope_t scope[MAX_CHANNELS];static SDL_Thread *scopeThread; lastChInstr_t lastChInstr[MAX_CHANNELS]; // global int32_t getSamplePosition(uint8_t ch){	if (ch >= song.numChannels)		return -1; 	volatile scope_t *sc = &scope[ch]; 	// cache some stuff	volatile bool active = sc->active;	volatile int32_t position = sc->position;	volatile int32_t sampleEnd = sc->sampleEnd; 	if (!active || sampleEnd == 0)		return -1; 	if (position >= 0 && position < sampleEnd)		return position; 	return -1; // not active or overflown} void stopAllScopes(void){	// wait for scopes to finish updating	while (scopesUpdatingFlag);		volatile scope_t *sc = scope;	for (int32_t i = 0; i < MAX_CHANNELS; i++, sc++)		sc->active = false; 	// wait for scope displaying to be done (safety)	while (scopesDisplayingFlag);} // toggle mutestatic void setChannel(int32_t chNr, bool on){	channel_t *ch = &channel[chNr]; 	ch->channelOff = !on;	if (ch->channelOff)	{		ch->efx = 0;		ch->efxData = 0;		ch->realVol = 0;		ch->outVol = 0;		ch->oldVol = 0;		ch->dFinalVol = 0.0;		ch->outPan = 128;		ch->oldPan = 128;		ch->finalPan = 128;		ch->status = IS_Vol; 		ch->keyOff = true; // non-FT2 bug fix for stuck piano keys	} 	scope[chNr].wasCleared = false;} static void drawScopeNumber(uint16_t scopeXOffs, uint16_t scopeYOffs, uint8_t chNr, bool outline){	scopeXOffs++;	scopeYOffs++;	chNr++; 	if (outline)	{		if (chNr < 10) // one digit?		{			charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + chNr);		}		else		{			charOutOutlined(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[chNr]);			charOutOutlined(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[chNr]);		}	}	else	{		if (chNr < 10) // one digit?		{			charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, '0' + chNr);		}		else		{			charOut(scopeXOffs, scopeYOffs, PAL_MOUSEPT, chDecTab1[chNr]);			charOut(scopeXOffs + 7, scopeYOffs, PAL_MOUSEPT, chDecTab2[chNr]);		}	}} static void redrawScope(int32_t ch){	int32_t i; 	int32_t chansPerRow = (uint32_t)song.numChannels >> 1;	int32_t chanLookup = chansPerRow - 1;	const uint16_t *scopeLens = scopeLenTab[chanLookup]; 	// get x,y,len for scope according to channel (we must do it this way since 'len' can differ!) 	uint16_t x = 2;	uint16_t y = 94; 	uint16_t scopeLen = 0; // prevent compiler warning	for (i = 0; i < song.numChannels; i++)	{		scopeLen = scopeLens[i]; 		if (i == chansPerRow) // did we reach end of row?		{			// yes, go one row down			x  = 2;			y += 39;		} 		if (i == ch)			break; 		// adjust position to next channel		x += scopeLen + 3;	} 	drawFramework(x, y, scopeLen + 2, 38, FRAMEWORK_TYPE2); 	// draw mute graphics if channel is muted	if (!editor.chnMode[i])	{		const uint16_t muteGfxLen = scopeMuteBMP_Widths[chanLookup];		const uint16_t muteGfxX = x + ((scopeLen - muteGfxLen) >> 1); 		blitFastClipX(muteGfxX, y + 6, bmp.scopeMute+scopeMuteBMP_Offs[chanLookup], 162, scopeMuteBMP_Heights[chanLookup], muteGfxLen); 		if (config.ptnChnNumbers)			drawScopeNumber(x + 1, y + 1, (uint8_t)i, true);	} 	scope[ch].wasCleared = false;} void refreshScopes(void){	for (int32_t i = 0; i < MAX_CHANNELS; i++)		scope[i].wasCleared = false;} static void channelMode(int32_t chn){	int32_t i;		assert(chn < song.numChannels); 	bool m = mouse.leftButtonPressed && !mouse.rightButtonPressed;	bool m2 = mouse.rightButtonPressed && mouse.leftButtonPressed; 	if (m2)	{		bool test = false;		for (i = 0; i < song.numChannels; i++)		{			if (i != chn && !editor.chnMode[i])				test = true;		} 		if (test)		{			for (i = 0; i < song.numChannels; i++)				editor.chnMode[i] = true;		}		else		{			for (i = 0; i < song.numChannels; i++)				editor.chnMode[i] = (i == chn);		}	}	else if (m)	{		editor.chnMode[chn] ^= 1;	}	else	{		if (editor.chnMode[chn])		{			config.multiRecChn[chn] ^= 1;		}		else		{			config.multiRecChn[chn] = true;			editor.chnMode[chn] = true;			m = true;		}	} 	for (i = 0; i < song.numChannels; i++)		setChannel(i, editor.chnMode[i]); 	if (m2)	{		for (i = 0; i < song.numChannels; i++)			redrawScope(i);	}	else	{		redrawScope(chn);	}} bool testScopesMouseDown(void){	int32_t i; 	if (!ui.scopesShown)		return false; 	if (mouse.y >= 95 && mouse.y <= 169 && mouse.x >= 3 && mouse.x <= 288)	{		if (mouse.y > 130 && mouse.y < 134)			return true; 		int32_t chansPerRow = (uint32_t)song.numChannels >> 1;		const uint16_t *scopeLens = scopeLenTab[chansPerRow-1]; 		// find out if we clicked inside a scope		uint16_t x = 3;		for (i = 0; i < chansPerRow; i++)		{			if (mouse.x >= x && mouse.x < x+scopeLens[i])				break; 			x += scopeLens[i]+3;		} 		if (i == chansPerRow)			return true; // scope framework was clicked instead 		int32_t chanToToggle = i;		if (mouse.y >= 134) // second row of scopes?			chanToToggle += chansPerRow; // yes, increase lookup offset 		channelMode(chanToToggle);		return true;	} 	return false;} static void scopeTrigger(int32_t ch, const sample_t *s, int32_t playOffset){	volatile scope_t tempState;	volatile scope_t *sc = &scope[ch]; 	int32_t length = s->length;	int32_t loopStart = s->loopStart;	int32_t loopLength = s->loopLength;	int32_t loopEnd = s->loopStart + s->loopLength;	uint8_t loopType = GET_LOOPTYPE(s->flags);	bool sample16Bit = !!(s->flags & SAMPLE_16BIT); 	if (s->dataPtr == NULL || length < 1)	{		sc->active = false; // shut down scope (illegal parameters)		return;	} 	tempState = *sc; // get copy of current scope state 	if (loopLength < 1) // disable loop if loopLength is below 1		loopType = 0; 	if (sample16Bit)		tempState.base16 = (const int16_t *)s->dataPtr;	else		tempState.base8 = s->dataPtr; 	tempState.sample16Bit = sample16Bit;	tempState.loopType = loopType;	tempState.direction = 1; // forwards	tempState.sampleEnd = (loopType == LOOP_OFF) ? length : loopEnd;	tempState.loopStart = loopStart;	tempState.loopLength = loopLength;	tempState.position = playOffset;	tempState.positionFrac = 0;		// if position overflows (f.ex. through 9xx command), shut down scopes	if (tempState.position >= tempState.sampleEnd)	{		sc->active = false;		return;	} 	tempState.active = true; 	/* Update live scope now.	** In theory it -can- be written to in the middle of a cached read,	** then the read thread writes its own non-updated cached copy back and	** the trigger never happens. So far I have never seen it happen,	** so it's probably very rare. Yes, this is not good coding...	*/ 	*sc = tempState; // set new scope state} static void updateScopes(void){	scopesUpdatingFlag = true; 	volatile scope_t *sc = scope;	for (int32_t i = 0; i < song.numChannels; i++, sc++)	{		volatile scope_t s = *sc; // get copy of current scope state		if (!s.active)			continue; // scope is not active 		// scope position update 		s.positionFrac += s.delta;		const uint32_t wholeSamples = (uint32_t)(s.positionFrac >> SCOPE_FRAC_BITS);		s.positionFrac &= SCOPE_FRAC_MASK; 		if (s.direction == 1)			s.position += wholeSamples; // forwards		else			s.position -= wholeSamples; // backwards 		// handle loop wrapping or sample end		if (s.direction == -1 && s.position < s.loopStart) // sampling backwards (definitely pingpong loop)		{			s.direction = 1; // change direction to forwards 			if (s.loopLength >= 2)				s.position = s.loopStart + ((s.loopStart - s.position - 1) % s.loopLength);			else				s.position = s.loopStart; 			assert(s.position >= s.loopStart && s.position < s.sampleEnd);		}		else if (s.position >= s.sampleEnd)		{			uint32_t loopOverflowVal; 			if (s.loopLength >= 2)				loopOverflowVal = (s.position - s.sampleEnd) % s.loopLength;			else				loopOverflowVal = 0; 			if (s.loopType == LOOP_DISABLED)			{				s.active = false;			}			else if (s.loopType == LOOP_FORWARD)			{				s.position = s.loopStart + loopOverflowVal;				assert(s.position >= s.loopStart && s.position < s.sampleEnd);			}			else // pingpong loop			{				s.direction = -1; // change direction to backwards				s.position = (s.sampleEnd - 1) - loopOverflowVal;				assert(s.position >= s.loopStart && s.position < s.sampleEnd);			}		}		assert(s.position >= 0); 		*sc = s; // set new scope state	}	scopesUpdatingFlag = false;} void drawScopes(void){	scopesDisplayingFlag = true;	int32_t chansPerRow = (uint32_t)song.numChannels >> 1; 	const uint16_t *scopeLens = scopeLenTab[chansPerRow-1];	uint16_t scopeXOffs = 3;	uint16_t scopeYOffs = 95;	int16_t scopeLineY = 112; 	for (int32_t i = 0; i < song.numChannels; i++)	{		// if we reached the last scope on the row, go to first scope on the next row		if (i == chansPerRow)		{			scopeXOffs = 3;			scopeYOffs = 134;			scopeLineY = 151;		} 		const uint16_t scopeDrawLen = scopeLens[i];		if (!editor.chnMode[i]) // scope muted (mute graphics blit()'ed elsewhere)		{			scopeXOffs += scopeDrawLen+3; // align x to next scope			continue;		} 		const scope_t s = scope[i]; // cache scope to lower thread race condition issues		if (s.active && s.volume > 0 && !audio.locked)		{			// scope is active			scope[i].wasCleared = false; 			// clear scope background			clearRect(scopeXOffs, scopeYOffs, scopeDrawLen, SCOPE_HEIGHT); 			// draw scope			bool linedScopesFlag = !!(config.specialFlags & LINED_SCOPES);			scopeDrawRoutineTable[(linedScopesFlag * 6) + (s.sample16Bit * 3) + s.loopType](&s, scopeXOffs, scopeLineY, scopeDrawLen);		}		else		{			// scope is inactive			volatile scope_t *sc = &scope[i];			if (!sc->wasCleared)			{				// clear scope background				clearRect(scopeXOffs, scopeYOffs, scopeDrawLen, SCOPE_HEIGHT); 				// draw empty line				hLine(scopeXOffs, scopeLineY, scopeDrawLen, PAL_PATTEXT); 				sc->wasCleared = true;			}		} 		// draw channel numbering (if enabled)		if (config.ptnChnNumbers)			drawScopeNumber(scopeXOffs, scopeYOffs, (uint8_t)i, false); 		// draw rec. symbol (if enabled)		if (config.multiRecChn[i])			blit(scopeXOffs + 1, scopeYOffs + 31, bmp.scopeRec, 13, 4); 		scopeXOffs += scopeDrawLen+3; // align x to next scope	} 	scopesDisplayingFlag = false;} void drawScopeFramework(void){	drawFramework(0, 92, 291, 81, FRAMEWORK_TYPE1);	for (int32_t i = 0; i < song.numChannels; i++)		redrawScope(i);} void handleScopesFromChQueue(chSyncData_t *chSyncData, uint8_t *scopeUpdateStatus){	volatile scope_t *sc = scope;	syncedChannel_t *ch = chSyncData->channels;	for (int32_t i = 0; i < song.numChannels; i++, sc++, ch++)	{		const uint8_t status = scopeUpdateStatus[i]; 		if (status & IS_Vol)			sc->volume = ch->scopeVolume; 		if (status & IS_Period)			sc->delta = ch->scopeDelta; 		if (status & IS_Trigger)		{			if (instr[ch->instrNum] != NULL)			{				scopeTrigger(i, &instr[ch->instrNum]->smp[ch->smpNum], ch->smpStartPos); 				// set some stuff used by Smp. Ed. for sampling position line 

Lines 1–500 of 550.

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