
// ****************************************************************************
//
//                         Page GEN - frequency generator
//
// ****************************************************************************

#include "../include.h"

int GenSet = GENSET_TONE;	// current generator tone set
int GenTone = TONE_DEF;		// current decimal tone
int GenNote = NOTE_DEF;		// current music note (57 = A4, 440 Hz)
int GenPresc;			// generator prescaler
int GenLoad;			// generator loader

// note frequencies in Hz * 65536
// - Base frequency of C0: fc0=16.35159783 Hz
// - Frequency in [Hz] of the tone with index 'i': f[i] = fc0 * pow(fc0, i)
const u32 NoteFreq[NOTE_NUM] = {
//C        C#         D          D#         E          F          F#         G          G#         A          A#         B
1071618,   1135340,   1202851,   1274376,   1350154,   1430439,   1515497,   1605613,   1701088,   1802240,   1909407,   2022946,    // 0
2143237,   2270680,   2405702,   2548752,   2700309,   2860878,   3030994,   3211227,   3402176,   3604480,   3818814,   4045892,    // 1
4286473,   4541360,   4811404,   5097505,   5400618,   5721755,   6061989,   6422453,   6804352,   7208960,   7637627,   8091784,    // 2
8572947,   9082720,   9622807,   10195009,  10801236,  11443511,  12123977,  12844906,  13608704,  14417920,  15275254,  16183568,   // 3
17145893,  18165441,  19245614,  20390018,  21602472,  22887021,  24247954,  25689813,  27217409,  28835840,  30550508,  32367136,   // 4
34291786,  36330882,  38491228,  40780036,  43204943,  45774043,  48495909,  51379626,  54434817,  57671680,  61101017,  64734272,   // 5
68583572,  72661764,  76982457,  81560072,  86409886,  91548086,  96991818,  102759252, 108869635, 115343360, 122202033, 129468544,  // 6
137167144, 145323527, 153964914, 163120144, 172819773, 183096171, 193983636, 205518503, 217739269, 230686720, 244404066, 258937088,  // 7
274334289, 290647054, 307929828, 326240288, 345639545, 366192342, 387967272, 411037006, 435478539, 461373440, 488808132, 517874176,  // 8
548668578, 581294109, 615859655, 652480576, 691279090, 732384684, 775934544, 822074013, 870957077, 922746880, 977616265, 1035748353, // 9
};

// tone frequencies in Hz
const u32 ToneFreq[TONE_NUM] = {
//	1	2	3	5	10	15	20	30	50	75
	1,	2,	3,	5,	10,	15,	20,	30,	50,	75,

//	100	150	200	300	500	750	1K	1K5	2K	3K	5K	7K5
	100,	150,	200,	300,	500,	750,	1000,	1500,	2000,	3000,	5000,	7500,

//	10K	15K	20K	30K	50K	75K	100K	150K	200K	300K	500K	750K
	10000,	15000,	20000,	30000,	50000,	75000,	100000,	150000,	200000,	300000,	500000,	750000,

//	1M       1M5      2M       3M       4M       4K8      6M       8M       9M6
	1000000, 1500000, 2000000, 3000000, 4000000, 4800000, 6000000, 8000000, 9600000,

//	12M		16M		24M		48M
	12000000,	16000000,	24000000,	48000000,
};

// note name
const char* const NoteName[12] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };

// find best prescaler (freq = frequency in Hz * 65536)
void GEN_FindPresc(s64 freq)
{
	// get system clock * 65536
	s64 sysclk = (u64)SystemCoreClock << 16;

	// prepare
	s64 best_err = 0x7fffffffffffffffll;
	int best_p = 1;	// best prescaler
	int best_v = 1;	// best loader
	s64 t, f, v, err;
	int p;

	// search prescaler
	for (p = 1; p < 1000; p++)
	{
		// get timer loader
		t = p * freq;
		v = (sysclk + (t >> 1)) / t;

		// loader overflow
		if ((v == 0) || (v > 65536)) continue;

		// get real frequency
		t = p * v;
		f = (sysclk + (t >> 1)) / t;

		// get error
		err = freq - f;
		if (err < 0) err = -err;

		// new best setup
		if (err < best_err)
		{
			best_err = err;
			best_p = p;
			best_v = (int)v;
		}
	}

	// save result
	GenPresc = best_p - 1;
	GenLoad = best_v - 1;
}

// setup tone
void GEN_Setup()
{
	// MCO output
	int set = GenSet;	// current tone set
	int tone = GenTone;
	if ((set == GENSET_TONE) && (tone == TONE_NUM-1)) // 48 MHz
	{
		// setup prescaler - for display purposes only
		GenPresc = 0;	// generator prescaler
		GenLoad = 0;	// generator loader

		// disable compare output
		TIM1_CC4Disable();

		// setup MCO output to 48 MHz system clock
		RCC_MCOClkSrc(RCC_MCO_SYSCLK);
		return;
	}
	else
	{
		// disable MCO output
		RCC_MCOClkSrc(RCC_MCO_NONE);

		// enable compare output
		TIM1_CC4Enable();
	}

	// get frequency
	s64 f;
	if (set == GENSET_TONE)
	{
		f = (s64)ToneFreq[GenTone] << 16; // get frequency in Hz
	}
	else
	{
		f = NoteFreq[GenNote];	// get frequency in Hz * 65536
	}

	// find prescaler
	GEN_FindPresc(f);

	// get setup
	int presc = GenPresc;
	int load = GenLoad;

	// setup timer
	TIM1_Presc(presc);
	TIM1_Load(load);
	TIM1_Comp4((load+1) >> 1);
}

// display GEN tone - used also by PWM page
void GEN_DispTone(int set, int tone)
{
	int x, i, o;
	const char* t;

	// set font 8x12
	SelFont12();

	// clear 1st row
	DrawRectClrFast(0, ROW1_Y, WIDTH, FONTH*2);

	// title (width 4 chars = 32 pixels)
	if (set == GENSET_TONE) // decimal tones
	{
		DrawTextFast("Frequency", 0, ROW1_Y+SMALL_Y);
		x = 10*8;

		// tone number
		i = DecUNum(DecNumBuf, tone+1, 0);
		DrawTextFast(DecNumBuf, x, ROW1_Y+SMALL_Y);
	}
	else // notes
	{
		DrawTextFast("Note", 0, ROW1_Y+SMALL_Y);
		x = 5*8;

		// tone number
		i = DecUNum(DecNumBuf, tone+1, 0);
		DrawTextFast(DecNumBuf, x, ROW1_Y+SMALL_Y); x += i*8;
		DrawCharFast(':', x, ROW1_Y+SMALL_Y); x += 8+8;

		// split tone to tone and octave
		o = tone/12;		// octave
		i = tone - o*12;	// tone

		// display tone
		t = NoteName[i];
		DrawText2(t, x, ROW1_Y); x += 16;
		if (t[1] != 0) x += 16;

		// display octave
		DrawChar2('0' + o, x, ROW1_Y); x += 16;
		DrawChar2(' ', x, ROW1_Y);
	}
}

// display GEN state
void GEN_Disp()
{
	int i, o, x, tone, set, presc, load, ex;
	s64 f0;
	u32 f;
	const char* t;

	// get setup
	tone = GenTone;
	set = GenSet;	// current tone set
	if (set == GENSET_NOTE) tone = GenNote;

	// display GEN tone - used also with PWM
	GEN_DispTone(set, tone);

	// calculate frequency
	presc = GenPresc+1;
	load = GenLoad+1;
	i = presc*load;
	ex = -4;
	if (i < 500)
	{
		f0 = (s64)SystemCoreClock * 10; // system frequency * 10
		ex = -1;
	}
	else
		f0 = (s64)SystemCoreClock * 10000; // system frequency * 10000

	f = (u32)((f0 + (i >> 1)) / i);

	// display frequency
	// display unsigned value (returns next X coordinate; need to call DispUpdate())
	//  x ... X coordinate
	//  y ... Y coordinate
	//  val ... value
	//  dig ... number of valid digits
	//  ex ... decimal exponent related to base unit, in range -18..+8
	//  unit ... unit character, 0=none
	//  small ... use small unit characters
	x = DispUVal(0, ROW3_Y, f, 6, ex, 'H', True, False);
	DrawChar('z', x, ROW3_Y+SMALL_Y);

	// display update
	DispUpdate();
}

// GEN output initialize
void GEN_Init()
{
	// Enable timer clock source
	TIM1_ClkEnable();

	// Reset timer to default setup
	TIM1_Reset();

	// select input from internal clock CK_INT
	TIM1_InMode(TIM_INMODE_INT);

	// setup tone
	GEN_Setup();

	// direction up
	TIM1_DirUp();

	// enable auto-reload of preload compare register
	TIM1_AutoReloadEnable();

	// setup compare
	TIM1_OC4Mode(TIM_COMP_PWM1);	// set compare mode
	TIM1_OC4PreEnable();		// enable preload compare register

	// enable main output
	TIM1_OCEnable();

	// reload immediately
	TIM1_Update();

	// remap Timer 1
	// 0 ... PC5:ETR, PD2:CH1, PA1:CH2, PC3:CH3, >>> PC4:CH4 <<<, PC2:BKIN, PD0:CH1N, PA2:CH2N, PD1:CH3N
	GPIO_Remap_TIM1(0);

	// setup GPIO pin
	GPIO_Mode(GEN_GPIO, GPIO_MODE_AF);

	// enable timer
	TIM1_Enable();
}

// GEN output terminate
void GEN_Term()
{
	// disable MCO output
	RCC_MCOClkSrc(RCC_MCO_NONE);

	// reset GPIO pin
	GPIO_PinReset(GEN_GPIO);

	// reset Timer 1
	TIM1_Reset();

	// Timer 1 clock disable
	RCC_TIM1ClkDisable();
}

// Page GEN (returns key PREV/NEXT)
u8 PageGEN()
{
	u8 key;
	int i;

	// GEN initialize
	GEN_Init();	// GEN output initialize (Timer 1)

	// display GEN state
	GEN_Disp();

	while (True)
	{
		// reload watchdog counter
		IWDG_Reload();

		key = KeyGet();
		switch (key)
		{
		// Prev
		case KEY_PREV:
		// Next
		case KEY_NEXT:
			// GEN terminate
			GEN_Term();
			return key;

		// Slow
		case KEY_SLOW:
			if (GenSet == GENSET_TONE) // decimal tones
			{
				i = GenTone - 1;
				if (i < 0) i = TONE_NUM-1;
				GenTone = i;
			}
			else
			{
				i = GenNote - 1;
				if (i < 0) i = NOTE_NUM-1;
				GenNote = i;
			}
			GEN_Setup();
			GEN_Disp();
			break;		

		// Fast
		case KEY_FAST:
			if (GenSet == GENSET_TONE) // decimal tones
			{
				i = GenTone + 1;
				if (i >= TONE_NUM) i = 0;
				GenTone = i;
			}
			else
			{
				i = GenNote + 1;
				if (i >= NOTE_NUM) i = 0;
				GenNote = i;
			}
			GEN_Setup();
			GEN_Disp();
			break;

		// Hold
		case KEY_HOLD:
			i = GenSet + 1;
			if (i >= GENSET_NUM) i = 0;
			GenSet = i;
			GEN_Setup();
			GEN_Disp();
			break;
		}
	}
}
