
#ifdef LPZW_TEST
 #define PROGMEM 

#include "lpzw.h"


void lpzwtest(void) {
	static const uint8_t logo32x32[] PROGMEM = {
0xE0, 0xF0, 0x38, 0x9C, 0xCE, 0xC6, 0x63, 0x31, 0xF9, 0xEF, 0x67, 0x33, 0x19, 0x1C, 0x0E, 0x07, 0x07, 0x0E, 0x1C, 0x19, 0x33, 0x67, 0xEF, 0xF9, 0x31, 0x63, 0xC6, 0xCE, 0x9C, 0x38, 0xF0, 0xE0,
0xFF, 0xFF, 0x07, 0xF3, 0xFF, 0x3F, 0x76, 0xE3, 0xCD, 0x8C, 0x1C, 0x3C, 0x3C, 0xF8, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x3C, 0x3C, 0x1C, 0x8C, 0xCD, 0xE3, 0x76, 0x3F, 0xFF, 0xF3, 0x07, 0xFF, 0xFF,
0xFF, 0xFF, 0xE0, 0xCF, 0x9F, 0x30, 0x60, 0xC0, 0xFF, 0x3F, 0x30, 0xF0, 0xF0, 0x37, 0x36, 0x3E, 0x3E, 0x36, 0x37, 0xF0, 0xF0, 0x30, 0x3F, 0xFF, 0xC0, 0x60, 0x30, 0x9F, 0xCF, 0xE0, 0xFF, 0xFF,
0x07, 0x0F, 0x1C, 0x18, 0x31, 0x63, 0xC6, 0xCC, 0xD9, 0xF3, 0xE7, 0xEF, 0xDF, 0x3B, 0x73, 0xE3, 0xE3, 0x73, 0x3B, 0xDF, 0xEF, 0xE7, 0xF3, 0xD9, 0xCC, 0xC6, 0x63, 0x31, 0x18, 0x1C, 0x0F, 0x07,
	};
	

	u8 bmp[32][32];

	for (int y = 0; y < 32; ++y) {
		for (int x = 0; x < 32; ++x) {
			bmp[y][x] = (logo32x32[x + (y / 8) * 32] & (1 << (y & 7))) ? 255 : 0;
		}
	}
	u8 logorot[4][32];
	DebugLog("static const uint8_t logo32x32_rotated_pins_top[] PROGMEM = {\n");
	for (int y = 0; y < 4; ++y) {
		for (int x = 0; x < 32; ++x)
		{
			u8 b = 0;
			//for (int yy = 0; yy < 8; ++yy) if (bmp[31-x][yy + y * 8]) b |= 1 << yy; // pins at bottom
			for (int yy = 0; yy < 8; ++yy) if (bmp[x][31 - yy - y * 8]) b |= 1 << yy; // pins at top
			logorot[y][x] = b;
			DebugLog("0x%02x,", b);
		}
		DebugLog("\n");
	}
	DebugLog("};\n");
	for (int i = 33; i < 0x60; ++i) DebugLog("%c", i);
	DebugLog("\r\n");
	FILE* f = fopen("c:/temp/lpzw.pgm", "wb");
	fprintf(f, "P5 32 32 255\n");
	fwrite(bmp, 32, 32, f);
	fclose(f);
	int frame = 0;
	while (1) {
		++frame;
		clear();
		

		clearscreen();
		drawlpzwlogo(16.f * sinf(frame * 0.01f));
		drawstr24(-32.f - 32.f * sinf(frame * 0.02f), 32, "HELLO KAY");
		drawstr24(-32.f - 32.f * sinf(frame * 0.03f), 32+25, "HOW ARE YOU");
		drawstr24(-32.f - 32.f * sinf(frame * 0.04f), 32+50, "C#3 IS A NOTE");

		memcpy(vrambuf + 1, i2cbuf, 512);
		oled_flip(vrambuf);
		HAL_Delay(10);
	}
}

#endif

void bootswish(void) {
#ifdef WASM
	return;
#endif
#ifdef LPZW_TEST
	lpzwtest();
#endif
	for (int f = 0; f < 64; ++f) {
		HAL_Delay(20);
		for (int y = 0; y < 9; ++y) {
			int y1 = (y * 512) + 256;
			int dy = (y1 - 2048);
			dy *= dy;
			for (int x = 0; x < 8; ++x) {
				int x1 = (x * 512) + 256;
				int dx = (x1 - 2048);
				dx *= dx;
				int dist = (int)sqrtf((float)(dx + dy));
				dist -= f * 128;
				int k = -dist / 8;
				if (k > 255)
					k = 512 - k;
				if (k < 0)
					k = 0;
				led_ram[y][x] = ((k * k) >> 8);
			}
		}
	}
}


u8 audiohistpos = 0;
u8 audiopeakhistory[32];
u8 erasepos = 0;
u8 messagefnt;
const char* message = 0;
const char* submessage = 0;
u32 messagetime = 0;
void ShowMessage(EFont fnt, const char* msg, const char *submsg) {
	message = msg;
	submessage = submsg;
	messagefnt = fnt;
	messagetime = millis() + 500;
}

char 
#ifndef EMU
__attribute__((section (".endsection"))) 
#endif
version_tail[]=VERSION2;

void flip(void) {
	if (millis() > messagetime)
		message = 0;
	else if (message) {
		clear();
		int y = 0;
		if (submessage)
			drawstr(0, 0, F_12, submessage),y+=12;
		drawstr(0, y, messagefnt, message);
	}
	static u8 frame = 3;
	if (frame < 255) {
		int y = frame - 255 + 32;
		if (y < 32 - 12) y = 32 - 12;
		textcol = 3;
		fdrawstr(32, y, F_12, VERSION2
#ifdef DEBUG
			" DBG"
#endif
#ifndef NEW_LAYOUT
			" OL"
#endif
				, version_tail // version tail is passed to printf, so the linker cant remove it, haha, but we dont actually use the pointer
		);
		u8* v = getvram() - 1;
		const u8* l = getlogo() - 1;
		//u8* r = rndtab;
		int k = frame/2;
		const static u8 dither[16] = { 0, 8, 2, 10, 12,4,14,6,3,11,1,9,15,7,13,5 };
		for (int i = 0; i < 32 * 128 / 8; ++i) {
			u8 mask = 0;
#define RND(y) dither[(i&3)+((i/128+y)&3)*4]
			if (RND(0) < k) mask |= 1;
			if (RND(1) < k) mask |= 2;
			if (RND(2) < k) mask |= 4;
			if (RND(3) < k) mask |= 8;
			if (RND(4) < k) mask |= 16;
			if (RND(5) < k) mask |= 32;
			if (RND(6) < k) mask |= 64;
			if (RND(7) < k) mask |= 128;
			*v = (*v & mask) | (*l & ~mask);
			v++; l++;
		}
		frame+=4;
	}
	textcol = 1;
	oled_flip(vrambuf);
}



void draw_recording_ui(void) {
	clear();
	SampleInfo* s = getrecsample();

	int peak = maxi(0, audioin_peak / 128);
	int hold = maxi(0, audioin_hold / 128);
	if (enable_audio == EA_PREERASE) {
		drawstr(0, 0, F_32, "erasing...");
		invertrectangle(0, 0, erasepos * 2, 32);
		flip();
		return;
	}
	else {
		drawstr(-128, 0, F_12, (enable_audio == EA_ARMED) ? "armed!" : (enable_audio == EA_RECORDING) ? "recording" : "rec level" I_A);
		fdrawstr(-128, 32 - 12, F_12, (hold >= 254) ? "CLIP! %dms" : "%dms", s->samplelen / 32);
	}
	int full = s->samplelen / (MAX_SAMPLE_LEN / 128);
	if (enable_audio != EA_RECORDING)
		full = 0;
	for (int i = 0; i < full; ++i) {
		u16 avgpeak = getwaveform4zoom(s, i * 16, 4);
		u8 avg = avgpeak;
		u8 peak = avgpeak >> 8;
		vline(i, 15 - avg, 16 + avg, 1);
		vline(i, 15 - peak, 15 - avg, 2);
		vline(i, 16 + avg, 16 + peak, 2);
	}
	vline(full, 0, 32, 1);
	int srcpos = recpos;
	for (int i = 127; i > full; --i) {
		int pmx = 0, pmn = 0;
		for (int j = 0; j < 256; ++j) {
			int p = -delaybuf[--srcpos & DLMASK];
			pmx = maxi(p, pmx);
			pmn = mini(p, pmn);
		}
		vline(i, 15 + pmn / 1024, 16 + pmx / 1024, 2);
	}
	fillrectangle(hold - 1, 29, hold + 1, 32);
	halfrectangle(peak, 29, hold, 32);
	fillrectangle(0, 29, peak, 32);
	flip();
}

const char* notename(int note) {
	// blondi asked to octave-up the displayed notes
	note += 12;
	if (note < 0 || note>8 * 12) return "";
	static char buf[4];
	int octave = note / 12;
	note -= octave * 12;
	buf[0] = "CCDDEFFGGAAB"[note];
	buf[1] = " + +  + + + "[note];
	buf[2] = '0' + octave;
	return buf;
}

void DebugSPIPage(int addr) {
	while (spistate);
	spistate = 255;

	int spiid = spi_readid();
	DebugLog("SPI flash chip id %04x\r\n", spiid);

	for (int i = 0; i < 256; ++i) {
		memset(spibigrx, -2, sizeof(spibigrx));
		spi_read256(i * 256 + addr);
		memcpy(&delaybuf[i * 128], spibigrx + 4, 256);
	}
	DebugLog("first 256 samples of spi ram at addr %d:\r\n", addr);
	for (int i = 0; i < 256; ++i) {
		DebugLog("%5d ", delaybuf[i]);
		if ((i & 31) == 31) DebugLog("\r\n");
	}
	spistate = 0;
}

void DrawSample(SampleInfo *s, int sliceidx) {
	sliceidx &= 7;
	int ofs = s->splitpoints[sliceidx] / 1024;
	int maxx = s->samplelen / 1024;
	textcol = 3;
	for (int i = 0; i < 128; ++i) {
		int x = i - 64 + ofs;
		u8 h = getwaveform4(s, x);
		if (x >= 0 && x < maxx)
			vline(i, 15 - h, 16 + h, (i < 64) ? 2 : 1);
		if (i == 64) {
			vline(i, 0, 13 - h, 1);
			vline(i, 18 + h, 32, 1);
		}
	}
	fdrawstr(64 + 2, 0, F_12, "%d", sliceidx + 1);
	// draw other slices
	for (int si = 0; si < 8; ++si) if (si != sliceidx) {
		int x = (s->splitpoints[si] / 1024) - ofs + 64;
		char buf[2] = { '1' + si,0 };
		u8 h = getwaveform4(s, s->splitpoints[si] / 1024);
		if (x >= 0 && x < 128) {
			vline(x, 0, 13 - h, 2);
			vline(x, 18 + h, 32, 2);
		}
		drawstr_noright(x + 2, 0, F_8, buf);
	}
}


void DrawSamplePlayback(SampleInfo* s) {
	static int curofscenter = 0;
	static bool jumpable = false;
	int ofs = curofscenter / 1024;
	int maxx = s->samplelen / 1024;
	textcol = 3;
	for (int i = 32; i < 128-16; ++i) {
		int x = i - 32 + ofs;
		u8 h = getwaveform4(s, x);
		vline(i, 15 - h, 16 + h, 2);
	}
	for (int si = 0; si < 8; ++si) {
		int x = (s->splitpoints[si] / 1024) - ofs + 32;
		u8 h = getwaveform4(s, s->splitpoints[si] / 1024);
		if (x >= 32 && x < 128-16) {
			vline(x, 0, 13 - h, 1);
			vline(x, 18 + h, 32, 1);
		}
	}
	s8 gx[8 * 4], gy[8 * 4], gd[8*4];
	int numtodraw = 0;
	int minpos = MAX_SAMPLE_LEN;
	int maxpos = 0;

	minpos = clampi(minpos, 0, s->samplelen);
	maxpos = clampi(maxpos, 0, s->samplelen);

	for (int i = 0; i < 8; ++i) {
		GrainPair* gr = voices[i].thegrains;
		int vvol = (int)(256.f * voices[i].vol);
		if (vvol>8) for (int g = 0; g < 4; ++g) {
			if (!(gr->outflags & (1 << (g & 1)))) {
				int pos = grainpos[i * 4 + g] & (MAX_SAMPLE_LEN - 1);
				int vol = gr->vol24 >> (24 - 4);
				if (g & 1) vol = 15 - vol;
				int graindir = (gr->dpos24 < 0) ? -1 : 1;
				int disppos = pos + graindir * 1024 * 32;
				minpos = mini(minpos, pos);
				maxpos = maxi(maxpos, pos);
				minpos = mini(minpos, disppos);
				maxpos = maxi(maxpos, disppos);
				int x = (pos / 1024) - ofs + 32;
				int y = (vol * vvol) >> 8;
				if (g & 2) y = 16 + y; else y = 16 - y;
				if (x >= 32 && x < 128 - 16 && y >= 0 && y < 32) {
					gx[numtodraw] = x;
					gy[numtodraw] = y;
					gd[numtodraw] = graindir;
					numtodraw++;
				}
			}
			if (g & 1) gr++;
		}
		
	}
	for (int i = 0; i < numtodraw; ++i) {
		int x = gx[i], y = gy[i];
		vline(x, y - 2, y + 2, 0);
		vline(x + gd[i], y - 1, y + 2, 0);
	}
	for (int i = 0; i < numtodraw; ++i) {
		int x = gx[i], y = gy[i];
		vline(x, y - 1, y + 1, 1);
		putpixel(x + gd[i], y, 1);
	}
	if (minpos>=maxpos)
		jumpable = true;
	else {
		if (jumpable)
			curofscenter = minpos-8*1024;
		else {
#define GRAIN_SCROLL_SHIFT 3
			if (minpos < curofscenter) curofscenter += (minpos - curofscenter) >> GRAIN_SCROLL_SHIFT;
			if (maxpos > curofscenter + (128-48) * 1024) curofscenter += (maxpos - curofscenter - (128-48) * 1024) >> GRAIN_SCROLL_SHIFT;
		}
		jumpable = false;
	}
}

void samplemode_ui(void) {
	SampleInfo* s = getrecsample();
	if (enable_audio == EA_PREERASE) {
		erasepos = 0;
		draw_recording_ui();
		while (spistate);
		spistate = 255;
		HAL_Delay(10);
		// mysteriously, sometimes page 0 wasn't erasing. maybe do it twice? WOOAHAHA
		spi_erase64k(0 + record_flashaddr_base, draw_recording_ui);
		for (int addr = 0; addr < MAX_SAMPLE_LEN*2; addr += 65536) {
			spi_erase64k(addr + record_flashaddr_base, draw_recording_ui);
			erasepos++;
		}
		memset(s, 0, sizeof(SampleInfo)); // since we erased the ram, we might as well nuke the sample too
		ramtime[GEN_SAMPLE] = millis();
		// done!
		spistate = 0;
		//DebugSPIPage(0);
		//DebugSPIPage(1024*1024*2-65536);
		enable_audio = EA_MONITOR_LEVEL;
	}
	if (enable_audio == EA_PLAY) {
		if (shift_down_time > 4 && (shift_down == SB_RECORD) && enable_audio == EA_PLAY) {
			clear();
			drawstr(0, 0, F_32, "record?");
			invertrectangle(0, 0, shift_down_time * 2, 32);
			oled_flip(vrambuf);
			return;
		}
		// just show the waveform of the current slice
		clear();
		if (!s->samplelen) {
			drawstr(0, 0, F_16, "<empty sample>");
			drawstr(0, 16, F_16, "hold " I_RECORD " to record");
		}
		else {
			DrawSample(s, recsliceidx);
			
			//int w = fdrawstr(0, 0, F_16, "%d", edit_sample0 + 1);
			textcol = 2;
			//fdrawstr(0, 32 - 12, F_12, "%dms", s->splitpoints[recsliceidx] / 32);
			drawstr(-128+16, 32 - 12, F_12, (s->loop & 2) ? "all" : "slc");
			drawicon(128-16, 32 - 14, ((s->loop & 1) ? I_FEEDBACK[0] : I_RIGHT[0]) - 0x80, textcol);
			if (s->pitched)
				drawstr(0, 32 - 12, F_12, notename(s->notes[recsliceidx&7])); 
			else
				drawstr(0, 32 - 12, F_12, "tape");
			textcol = 1;
		}
		flip();
		for (int x = 0; x < 8; ++x) {
			int sp0 = s->splitpoints[x];
			int sp1 = (x < 7) ? s->splitpoints[x + 1] : s->samplelen;
			for (int y = 0; y < 8; ++y) {
				int samp = sp0+(((sp1-sp0)*y)>>3);
				const static int zoom = 3;
				u16 avgpeak = getwaveform4zoom(s, samp / 1024, zoom);
				u8 h = avgpeak & 15;
				led_ram[x][y] = led_gamma(h * 32);
			}
			if (x == recsliceidx) {
				led_ram[x][0] = triangle(millis());
			}
			led_ram[8][x] = 0;
		}
		led_ram[8][SB_RECORD] = triangle(millis());
		led_ram[8][SB_PLAY] = 0;
		led_ram[8][SB_PARAMSA] = (s->pitched ? 255 : 0);
		led_ram[8][SB_PARAMSB] = ((s->loop & 1) ? 255 : 0);
		return;
	}

	if (enable_audio >= EA_RECORDING) {

		int locrecpos = recpos;
		//locrecpos=1024*1024;
		//int bufsize = locrecpos - recreadpos;
		//DebugLog("%d buf\r\n", bufsize);
		while (spistate);
		spistate = 0xff; // prevent spi reads for a while!
		while (recreadpos + 256 / 2 <= locrecpos && s->samplelen < MAX_SAMPLE_LEN) {
			s16* src = delaybuf + (recreadpos & DLMASK);
			s16* dst = (s16*)(spibigtx + 4);
			int flashaddr = (recreadpos - recstartpos) * 2;
			recreadpos += 256 / 2;
			if (!pre_erase && (flashaddr & 65535) == 0)
				spi_erase64k(flashaddr + record_flashaddr_base, /*draw_recording_ui*/ 0);
			// find peak and copy to fx buf
			int peak = 0;
			s16* delaybufend = delaybuf + DLMASK + 1;
			for (int i = 0; i < 256 / 2; ++i) {
				s16 smp = *src++;
				*dst++ = smp;
				peak = maxi(peak, abs(smp));
				if (src == delaybufend)
					src = delaybuf;
			}
			setwaveform4(s, flashaddr/2 / 1024, peak / 1024);
			if (spi_write256(flashaddr + record_flashaddr_base) != 0) {
				DebugLog("flash write fail\n");
			}
			s->samplelen = recreadpos - recstartpos;
			ramtime[GEN_SAMPLE] = millis();
			if (s->samplelen >= MAX_SAMPLE_LEN) {
				///  FINISHED RECORDING
				recording_stop();
				break;
			}
		} // spi write loop
		spistate = 0;
		if (enable_audio == EA_STOPPING4) {
			recording_stop_really();
		}
	}
	int bufsize = recpos - recreadpos;
	if (bufsize < 4096 || enable_audio != EA_RECORDING) {
		draw_recording_ui();
	}
	/////////// update leds
	

	for (int x = 0; x < 8; ++x) {
		int barpos0 = audiopeakhistory[(audiohistpos + x * 4 + 1) & 31];
		int barpos1 = audiopeakhistory[(audiohistpos + x * 4 + 2) & 31];
		int barpos2 = audiopeakhistory[(audiohistpos + x * 4 + 3) & 31];
		int barpos3 = audiopeakhistory[(audiohistpos + x * 4 + 4) & 31];
		for (int y = 0; y < 8; ++y) {
			int yy = (7 - y) * 32;
			int k = clampi(barpos0 - yy, 0, 31);
			k += clampi(barpos1 - yy, 0, 31);
			k += clampi(barpos2 - yy, 0, 31);
			k += clampi(barpos3 - yy, 0, 31);
			led_ram[x][y] = led_gamma(k * 2);
		}
		if (x == recsliceidx && enable_audio == EA_RECORDING) {
			for (int y = 0; y < 8; ++y)
				led_ram[x][y] = maxi(led_ram[x][y], (triangle(millis()) * 4) / (y + 4));
		}
		led_ram[8][x] = 0;
	}
	led_ram[8][SB_RECORD] =
	(enable_audio == EA_RECORDING) ? 255 : triangle(millis() / 2);
}


const static float life_damping = 0.91f;//  0.9f;
const static float life_force = 0.25f;// 0.25f;
const static float life_input_power = 6.f;

static int frame = 0;

void DrawLFOs(void) {
	u8* vr = getvram();
	vr += 128 - 16;
	u8 lfohp = ((lfo_history_pos >> 4) + 1) & 15;
	for (int x = 0; x < 16; ++x) {
		vr[0] &= ~(lfo_history[lfohp][0] >> 1);
		vr[128] &= ~(lfo_history[lfohp][1] >> 1);
		vr[256] &= ~(lfo_history[lfohp][2] >> 1);
		vr[384] &= ~(lfo_history[lfohp][3] >> 1);


		vr[0] |= lfo_history[lfohp][0];
		vr[128] |= lfo_history[lfohp][1];
		vr[256] |= lfo_history[lfohp][2];
		vr[384] |= lfo_history[lfohp][3];
		vr++;
		lfohp = (lfohp + 1) & 15;
	}
}

void DrawFlags()
{
	textcol = 0;
	if ((rampreset.flags & FLAGS_ARP))
	{
		fillrectangle(128 - 32, 0, 128 - 17, 8);
		drawstr(-(128 - 17), -1, F_8, "arp");
	}
	if ((rampreset.flags & FLAGS_LATCH)) {
		fillrectangle(128 - 38, 32 - 8, 128 - 17, 32);
		drawstr(-(128 - 17), 32 - 7, F_8, "latch");
	}
	textcol = 1;
	vline(126, 32 - (maxpressure_out / 8), 32, 1);
	vline(127, 32 - (maxpressure_out / 8), 32, 1);

}

const char *getparamstr(int p, int mod, int v, char *valbuf, char *decbuf) {
	if (decbuf) *decbuf = 0;
	int valmax = param_flags[p] & FLAG_MASK;
	int vscale = valmax ? (mini(v, FULL - 1) * valmax) / FULL : v;
	int displaymax = valmax ? valmax * 10 : 1000;
	bool decimal = true;
//	const char* val = valbuf;
	if (mod==M_BASE) switch (p) {
	case P_SMP_TIME:
	case P_SMP_RATE:
		displaymax = 2000;
		break;
	case P_ARPONOFF:
		if (mod) return "";
		return ((rampreset.flags & FLAGS_ARP)) ? "On":  "Off";
	case P_LATCHONOFF:
		if (mod) return "";
		return ((rampreset.flags & FLAGS_LATCH)) ? "On" : "Off";
	case P_SAMPLE:
		if (vscale == 0) {
			return "Off";
		}
		break;
	case P_SEQDIV:
	{	if (vscale >= DIVISIONS_MAX)
			return "(Gate CV)";
		//int divisor = (clampi(vscale - 3, 0, 3 * 5) / 3);
		int n = sprintf(valbuf, "%d", divisions[vscale] /* >> divisor*/);
		if (!decbuf)
			decbuf = valbuf + n;
		sprintf(decbuf, /*divisornames[divisor]*/ "/32");
		return valbuf;
	}
	case P_ARPDIV:
		if (v < 0) {
			v = -v;
			decimal = true;
			valmax = 0;
			displaymax = 1000;
			break;
		}
		vscale = (mini(v, FULL - 1) * DIVISIONS_MAX) / FULL;
		//int divisor = (clampi(vscale-3,0,3*5) / 3);
		int n=sprintf(valbuf, "%d", divisions[vscale] /*>> divisor*/);
		if (!decbuf)
			decbuf = valbuf + n;
		sprintf(decbuf,/*divisornames[divisor]*/ "/32");
		return valbuf;
	case P_ARPOCT:
		v += (FULL * 10) / displaymax; // 1 based
		break;
	case P_ARPMODE:
		return arpmodenames[clampi(vscale, 0, ARP_LAST - 1)];
	case P_SEQMODE:
		return seqmodenames[clampi(vscale, 0, SEQ_LAST - 1)];
	case P_CV_QUANT:
		return cvquantnames[clampi(vscale, 0, CVQ_LAST - 1)];
	case P_SCALE:
		return scalenames[clampi(vscale, 0, S_LAST - 1)];
	case P_ASHAPE: case P_BSHAPE: case P_XSHAPE: case P_YSHAPE:
		return lfonames[clampi(vscale, 0, LFO_LAST - 1)];
	case P_PITCH:
	case P_INTERVAL:
		displaymax = 120;
		break;
	case P_TEMPO:
		v += FULL;
		if (external_clock_enable)
			v = (bpm10x * FULL) / 1200;
		displaymax = 1200;
		break;
	case P_DLTIME: 
		if (v < 0) {
			if (v <= -1024) v++;
			v= (-v*13)/FULL;
			int n = sprintf(valbuf, "%d", divisions[v]);
			if (!decbuf)
				decbuf = valbuf + n;
			sprintf(decbuf, "/32 sync");
		}
		else {
			int n = sprintf(valbuf, "%d", (v*100)/FULL);
			if (!decbuf)
				decbuf = valbuf + n;
			sprintf(decbuf, "free");
		}
		return valbuf;
	default:;
	}
	v = (v * displaymax) / FULL;
	int av = abs(v);
	int n=sprintf(valbuf, "%c%d", (v < 0) ? '-' : ' ', av / 10);
	if (decimal) {
		if (!decbuf)
			decbuf = valbuf + n;
		sprintf(decbuf, ".%d", av % 10);
	}
	return valbuf;
}



void editmode_ui(void) {

	clear();



	float damping = life_damping;// / 65536.f * adcbuf[ADC_POT1];
	float force = life_force;// / 65536.f * adcbuf[ADC_POT2];
	float* prev = surf[frame & 1][0];
	frame++;
	float* next = surf[frame & 1][0];
	int i = 0;
	for (int y = 0; y < 8; ++y) {
		for (int x = 0; x < 8; ++x, ++i) {
			Finger* curfinger = touch_synth_getlatest(x);
			float corners = 0.f, edges = 0.f;
			if (x > 0) {
				if (y > 0)
					corners += prev[i - 9];
				edges += prev[i - 1];
				if (y < 7)
					corners += prev[i + 7];
			}
			if (y > 0)
				edges += prev[i - 8];
			if (y < 7)
				edges += prev[i + 8];
			if (x < 7) {
				if (y > 0)
					corners += prev[i - 7];
				edges += prev[i + 1];
				if (y < 7)
					corners += prev[i + 9];
			}
			float target = corners * (1.f / 12.f) + edges * (1.f * 2.f / 12.f);
			//if (x==4 && y==4) target=4.f;
			target *= damping;
			if (curfinger->pos >> 8 == y) {
				float pressure = curfinger->pressure * (1.f / 2048.f);
				if ((rampreset.flags & FLAGS_ARP) && !(arpbits & (1 << x)))
					pressure = 0.f;

				target = lerp(target, life_input_power, clampf(pressure * 2.f, 0.f, 1.f));
			}
			float pos = prev[i];
			float accel = (target - pos) * force;
			float vel = (prev[i] - next[i]) * damping + accel; // here next is really 'prev prev'
			next[i] = pos + vel;
		} // x
	} // y

	char presetname[9];
	memcpy(presetname, rampreset.name, 8);
	presetname[8] = 0;

	u8* vr = getvram();
	static u8 ui_edit_param=P_LAST;
	u8 ep = edit_param;
	if (ui_edit_param != ep) {
		ui_edit_param = ep; // as it may change in the background
/*		int page = 0;
		if ((ui_edit_param % 12) >= 6) page = 1;
		// we want to put ep into slot 0, and push whatever was there downwards until we find the new one
		u8 towrite = ep;
		for (int i = 0; i < 4; ++i) {
			u8 displaced = ui_edit_param_prev[page][i];
			ui_edit_param_prev[page][i] = towrite;
			if (displaced == ep) break;
			towrite = displaced;
		}
		*/
	}
	u8 ui_edit_mod = edit_mod;
	u8 loopstart_step = (rampreset.loopstart_step_no_offset + step_offset) & 63;

	const char* pagename = 0;
	if (shift_down == SB_RECORD) {
		if (isshortpress())
			drawstr(0, 4, F_20_BOLD, recording ? I_RECORD "record >off" : I_RECORD "record >on");
		else if (recording) {
			if (recordingknobs == 0)
				drawstr(0, 4, F_20_BOLD, "record " I_A I_B "?");
			else if (recordingknobs == 1)
				drawstr(0, 4, F_20_BOLD, "recording " I_A);
			else if (recordingknobs == 2)
				drawstr(0, 4, F_20_BOLD, "recording " I_B);
			else if (recordingknobs == 3)
				drawstr(0, 4, F_20_BOLD, "recording " I_A I_B);
		}
	}
	else if (shift_down == SB_PLAY) {
		drawstr(0, 0, F_32_BOLD, I_PLAY "play");
	}
	else if (shift_down == SB_CLEAR && editmode!=EM_PRESET) {
		drawstr(0, 0, F_32_BOLD, I_CROSS "clear");
	}
	/*
	else if (shift_down == SB_PREV && shift_down_time > 4 && enable_audio == EA_PLAY) {
		drawstr(0, 4, F_24_BOLD, rampreset.arpon ? I_NOTES "arp >off" : I_NOTES "arp >on");
		invertrectangle(0, 0, shift_down_time*4-8, 4);
	}*/
	else switch (editmode) {
	case EM_PLAY:
		if (ramsample.samplelen) {
			DrawSamplePlayback(&ramsample);
		}
		else {
			for (int x = 0; x < 128; ++x) {
				u32 m = scope[x];
				vr[0] = m;
				vr[128] = m >> 8;
				vr[256] = m >> 16;
				vr[384] = m >> 24;
				vr++;
			}
		}
		DrawLFOs();
		textcol = 2;
		if (ui_edit_param < P_LAST)
			goto draw_parameter;

		extern int lastencodertime;

		if (last_edit_param<P_LAST && lastencodertime && lastencodertime > millis() - 2000)
			goto draw_parameter;
		DrawFlags();
		char seqicon = (rampreset.flags & FLAGS_ARP) ? I_NOTES[0] : I_SEQ[0];
		char preseticon = I_PRESET[0];
		int xtab = 0;
		if (edit_preset_pending != 255 && edit_preset_pending != sysparams.curpreset)
			xtab=fdrawstr(0, 0, F_20_BOLD, "%c%d->%d", preseticon, sysparams.curpreset + 1, edit_preset_pending + 1);
		else if (maxpressure_out > 1 && !(ramsample.samplelen && !ramsample.pitched)) {
			xtab = fdrawstr(0, 0, F_20_BOLD, "%s", notename((pitchhi_out + 1024) / 2048) );
		}
		else
			xtab = fdrawstr(0, 0, F_20_BOLD, "%c%d", preseticon, sysparams.curpreset + 1);
		drawstr(xtab+2, 0, F_8_BOLD, presetname);
		if (rampreset.category >0 && rampreset.category <CAT_LAST) drawstr(xtab + 2, 8, F_8, kpresetcats[rampreset.category]);
		if (edit_pattern_pending != 255 && edit_pattern_pending != cur_pattern)
			fdrawstr(0, 16, F_20_BOLD, "%c%d->%d", seqicon, cur_pattern + 1, edit_pattern_pending + 1);
		else
			// if (cur_sample1)
			//fdrawstr(0, 16, F_20_BOLD, "%c%d " I_WAVE "%d", seqicon, cur_pattern + 1, cur_sample1);
		//else
			fdrawstr(0, 16, F_20_BOLD, "%c%d", seqicon, cur_pattern + 1);
		//clear();
		//DrawLFOs();
		//fdrawstr(0, 0, F_8, "%5d %5d", debuga[0], debuga[1]);
		//fdrawstr(0, 8, F_8, "%5d %5d", debuga[2], debuga[3]);
		//drawstr(48, 24, F_8, "loopop edition");
		break;
	case EM_PARAMSA:
	case EM_PARAMSB:
		if (ui_edit_param >= P_LAST) {
			drawstr(0, 0, F_20_BOLD, modnames[ui_edit_mod]);
			drawstr(0, 16, F_16, "select parameter");
			break;
		}
		else {
			DrawLFOs();
			int pi;
		draw_parameter:
			pi = ui_edit_param;
			if (pi >= P_LAST) pi = last_edit_param;
			if (pi >= P_LAST) pi = 0;
			pagename = pagenames[pi / 6];
			switch (pi) {
			case P_TEMPO: pagename = I_TEMPO "Tap"; break;
#ifndef NEW_LAYOUT
			case P_ENV_LEVEL:
			case P_ENV_REPEAT:
			case P_ENV_WARP:
			case P_ENV_RATE: pagename = "env2"; break;
#endif
			case P_NOISE: pagename = I_WAVE "noise"; break;
			case P_CV_QUANT:
			case P_HEADPHONE: pagename = "system"; break;
			}
			drawstr(0, 0, F_12, (ui_edit_mod == 0) ? pagename : modnames[ui_edit_mod]);
			const char* pn = paramnames[pi];
			int pw = strwidth(F_16_BOLD, pn);
			if (pw>64) 
				drawstr(0, 20, F_12_BOLD, pn);
			else
				drawstr(0, 16, F_16_BOLD, pn);
			char valbuf[32];
			char decbuf[16];
			int w = 0;
			int v =  GetParam(pi, ui_edit_mod);
			int vbase = v;
			if (ui_edit_mod == 0) {
				v = (param_eval_int_noscale(pi, any_rnd, env16, pressure16) * FULL) >> 16;
				if (v != vbase) {
					// if there is modulation going on, show the base value below
					const char* val = getparamstr(pi, ui_edit_mod, vbase, valbuf, NULL);
					w = strwidth(F_8, val);
					drawstr(128 - 16 - w, 32 - 8, F_8, val);
				}
			}
			const char* val = getparamstr(pi, ui_edit_mod, v, valbuf, decbuf);
			int x = 128 - 15; 
			if (*decbuf) x -= strwidth(F_8, decbuf);
			int font = F_24_BOLD;
			while (1) {
				w = strwidth(font, val);
				if (w < 64 || font <= F_12_BOLD)
					break;
				font--;
			}
			drawstr(x - w, 0, font, val);
			if (*decbuf)
				drawstr(x, 0, F_8, decbuf);
		}
		break;
	case EM_START:
		fdrawstr(0, 0, F_20_BOLD, I_PREV "Start %d", loopstart_step + 1);
		fdrawstr(0, 16, F_20_BOLD, I_PLAY "Current %d", cur_step + 1);
		break;
	case EM_END:
		fdrawstr(0, 0, F_20_BOLD, I_NEXT "End %d", ((rampreset.looplen_step + loopstart_step) & 63) + 1);
		fdrawstr(0, 16, F_20_BOLD, I_INTERVAL "Length %d", rampreset.looplen_step);
		break;
	case EM_PRESET:
		if (shift_down_time > 4 && shift_down == SB_CLEAR) {
			bool done = (shift_down_time - 4) > 64;
			if (last_preset_selection_rotstep < 32)
				fdrawstr(0, 0, F_16_BOLD, done ? "cleared\n" I_PRESET "Preset %d" : "initialize\n" I_PRESET "Preset %d?", last_preset_selection_rotstep + 1);
			else if (last_preset_selection_rotstep < 64 - 8)
				fdrawstr(0, 0, F_16_BOLD, done ? "cleared\n" I_SEQ "Pattern %d." : "Clear\n" I_SEQ "Pattern %d?", last_preset_selection_rotstep - 32 + 1);
			else if (last_preset_selection_rotstep < 64 && last_preset_selection_rotstep >0)
				fdrawstr(0, 0, F_16_BOLD, done ? "cleared\n" I_WAVE "Sample %d." : "Clear\n" I_WAVE "Sample %d?", last_preset_selection_rotstep - (64 - 8) + 1);
			invertrectangle(0, 0, shift_down_time * 2 - 4, 32);
		}
		else if (longpress >= 32) {
			bool done = (longpress - 32) > 128;
			if (first_finger_rotstep >= 56)
				fdrawstr(0, 0, F_16_BOLD, done ? "ok!" : "Edit\n" I_WAVE "Sample %d?", first_finger_rotstep - 56 + 1);
			else if (first_finger_rotstep == copyfrompreset)
				fdrawstr(0, 0, F_16_BOLD, done ? "toggled\n" I_PRESET "Preset %d" : "toggle\n" I_PRESET "Preset %d?", copyfrompreset + 1);
			else if (first_finger_rotstep < 32)
				fdrawstr(0, 0, F_16_BOLD, done ? "copied to " I_PRESET "%d" : "copy over\n" I_PRESET "Preset %d?", first_finger_rotstep + 1);
			else
				fdrawstr(0, 0, F_16_BOLD, done ? "copied to " I_SEQ "%d" : "copy over\n" I_SEQ "Pat %d?", first_finger_rotstep - 32 + 1);

			invertrectangle(0, 0, longpress - 32, 32);
		}
		else {
			int xtab = fdrawstr(0, 0, F_20_BOLD, I_PRESET "%d", sysparams.curpreset + 1);
			drawstr(xtab + 2, 0, F_8_BOLD, presetname);
			if (rampreset.category > 0 && rampreset.category < CAT_LAST) 
				drawstr(xtab + 2, 8, F_8, kpresetcats[rampreset.category]);

			fdrawstr(0, 16, F_20_BOLD, I_SEQ "Pat %d ", cur_pattern + 1);
			fdrawstr(-128, 16, F_20_BOLD, cur_sample1 ? I_WAVE "%d" : I_WAVE "Off", cur_sample1);
		}
		break;
	}

	flip();
	bool playing = playmode == PLAYING;
	int clockglow = maxi(96, 255 - calcseqsubstep(0, 256 - 96));
	int flickery = triangle(millis() / 2);
	//	int flickery2 = triangle(millis() & 255);
	int flickeryfast = triangle(millis() * 8);
	int loopbright = 96;
	int phase0 = calcseqsubstep(0, 8);

	int cvpitch = (int)(adc_smooth[6].y2 * (512.f * 12.f)); // pitch cv input 
	int cvquant = param_eval_int(P_CV_QUANT, any_rnd, env16, pressure16);
	if (cvquant) {
		cvpitch = (cvpitch + 256) & (~511);
	}


	for (int fi = 0; fi < 8; ++fi) {
		//			Finger *f = touch_getlatest(fi);
				//int yy=(f->pos)/256;

		Finger* synthf = touch_synth_getlatest(fi);
		FingerRecord* fr = readpattern(fi);
		
		///////////////////////////////////// ROOT NOTE DISPLAY
		/// per finger    
		int root = param_eval_finger(P_ROTATE, fi, synthf);
		int interval = (param_eval_finger(P_INTERVAL, fi, synthf) * 12) >> 7;
		int totpitch = 0;
		u32 scale = param_eval_finger(P_SCALE, fi, synthf);
		if (scale >= S_LAST) scale = 0;
		if (cvquant == CVQ_SCALE) {
			// remap the 12 semitones input to the scale steps, evenly, with a slight offset so white keys map to major scale etc
			int steps = ((cvpitch / 512) * scaletab[scale][0] + 1) / 12;
			root += steps;
		}
		int stride_semitones = maxi(0, param_eval_finger(P_STRIDE, fi, synthf));
		root += stride(scale, stride_semitones, fi);

		///////////////////////////////////////////////////////
		int sp0 = ramsample.splitpoints[fi];
		int sp1 = (fi < 7) ? ramsample.splitpoints[fi + 1] : ramsample.samplelen;
			


		for (int y = 0; y < 8; ++y) {


			int step = fi + y * 8;
			int rotstep = fi * 8 + y;
			int k = 0; //f->pressure - (y^7) * 128;
			k = clampi((int)((next[step]) * 64.f) - 20, 0, 128);

			
			if (synthf->pos / 256 == y)
				k = maxi(k, synthf->pressure / 8);

			if (fr && fr->pos[phase0/2] / 32 == y)
				k = maxi(k, fr->pressure[phase0]);

			bool inloop = ((step - loopstart_step) & 63) < rampreset.looplen_step;
#ifdef NEW_LAYOUT
			int pA = (fi > 0 && fi < 7) ? (fi - 1) + (y) * 12 : P_LAST;
#else
			int p = (fi - 1) + (y - 1) * 12;
#endif

			int pAorB = pA + ((editmode == EM_PARAMSB) ? 6 : 0);

			switch (editmode) {
			case EM_PARAMSA:
			case EM_PARAMSB: {
				if (fi == 7) {
					if (y == ui_edit_mod) k = flickery; else {
						// light right side for mod sources that are non zero
						k = (y && GetParam(ui_edit_param, y)) ? 255 : 0;
					}
				}
				else if (fi == 0 && ui_edit_param < P_LAST) {
					// bar graph
				bargraph:
					k = 0;
					int v = GetParam(ui_edit_param, ui_edit_mod);
					bool issigned = param_flags[ui_edit_param] & FLAG_SIGNED;
					issigned |= (edit_mod != M_BASE);
					int kontrast = 16;

					if (issigned) {
						if (y < 4) {
							k = ((v - (3 - y) * (FULL / 4)) * (192 * 4)) / FULL;
							k = (y) * 2 * kontrast + clampi(k, 0, 191);
							if (y == 3 && v < 0) k = 255;
						}
						else {
							k = ((-v - (y - 4) * (FULL / 4)) * (192 * 4)) / FULL;
							k = (8 - y) * 2 * kontrast + clampi(k, 0, 191);
							if (y == 4 && v > 0) k = 255;
						}
					}
					else {
						k = ((v - (7 - y) * (FULL / 8)) * (192 * 8)) / FULL;
						k = (y)*kontrast + clampi(k, 0, 191);
					}

				}
				if (fi > 0 && fi < 7) {
					k = 0;
					if ((fingerediting & 128) && (fingerstable & 128) && ui_edit_mod > 0) {
						// finger down over right side! show all params with a non zero mod amount
						if (GetParam(pAorB, ui_edit_mod))
							k = 255;
					}
					if (pAorB == ui_edit_param)
						k = flickery;

						//else
						//	k = maxi(k, abs(GetParam(p, ui_edit_mod)) / 4);
				}
#ifdef NEW_LAYOUT
				if (pAorB == P_ARPONOFF)
					k = (rampreset.flags & FLAGS_ARP) ? 255 : 0;
				else if (pAorB == P_LATCHONOFF)
					k = (rampreset.flags & FLAGS_LATCH) ? 255 : 0;
#else
				if (y == 0) {
					if (fi == 1)
						k = maxi(k, (rampreset.arpon) ? 255 : 0);
					else if (fi == 6)
						k = maxi(k, (sysparams.systemflags & SYS_LATCHON) ? 255 : 0);
				}
#endif
				break; }
			case EM_PLAY:
				if (fi == 0 && ui_edit_param < P_LAST)
					goto bargraph;
				if (ui_edit_param<P_LAST && (ui_edit_param == pA || ui_edit_param == pA+6) && fi>0 && fi<7)
					k = flickery;
				{
					if (ramsample.samplelen && !ramsample.pitched) {
						int samp = sp0 + (((sp1 - sp0) * y) >> 3);
						const static int zoom = 3;
						u16 avgpeak = getwaveform4zoom(&ramsample, samp / 1024, zoom) & 15;
						k=maxi(k,avgpeak * (96/16));
					}
					else {
						int pitch = (lookupscale(scale, (7 - y) + root));
						pitch %= 12 * 512;
						if (pitch < 0) pitch += 12 * 512;
						if (pitch < 256)
							k = maxi(k, 96);
					}
				}

				loopbright = 48;
				// fall thru
			case EM_START:
			case EM_END:
				// show looping region faintly; show current playpos brightly
				if (inloop)
					k = maxi(k, loopbright);
				if (step == loopstart_step && editmode == EM_START)
					k = 255;
				if (((step + 1) & 63) == ((loopstart_step + rampreset.looplen_step) & 63) && editmode == EM_END)
					k = 255;
				// playhead
				if (step == cur_step)
					k = maxi(k, clockglow);
				if (step == pending_loopstart_step && playing)
					k = maxi(k, (clockglow * 4) & 255);
				break;
			case EM_PRESET:
				k = (fi >= 4 && fi < 7) ? 64 : 0;
				if (rotstep == edit_preset_pending)
					k = flickeryfast;
				if (rotstep == sysparams.curpreset)
					k = 255;
				if (rotstep == edit_pattern_pending + 32)
					k = flickeryfast;
				if (rotstep == cur_pattern + 32)
					k = 255;
				if (edit_sample1_pending && rotstep == (edit_sample1_pending - 1) + 32 + 24)
					k = flickeryfast;
				if (cur_sample1 && rotstep == (cur_sample1 - 1) + 32 + 24)
					k = 255;
				break;
			}

			if (editmode == EM_PLAY) {
				int delay = 1+(((7 - y) * (7 - y) + fi * fi) >> 2);
				u8 histpos = (audiohistpos + 31 - delay) & 31;
				u8 ainlvl = audiopeakhistory[histpos];
				u8 ainlvlprev = audiopeakhistory[(histpos - 1) & 31];
				ainlvl = maxi(0, ainlvl - ainlvlprev); // highpass
				ainlvl = clampi((ainlvl * (32 - delay)) >> (6), 0, 255); // fade out
				k = maxi(k, ainlvl);
			}
			led_ram[fi][y] = led_gamma(k);
		}
	}
	{
		led_ram[8][SB_PLAY] = playing ? led_gamma(clockglow) : 0;
		led_ram[8][SB_PREV] = (editmode == EM_START) ? 255 : 0;
		led_ram[8][SB_NEXT] = (editmode == EM_END) ? 255 : 0;
		led_ram[8][SB_RECORD] = recording ? 255 : 0;
		led_ram[8][SB_CLEAR] = 0;
		led_ram[8][SB_PRESET] = (editmode == EM_PRESET) ? 255 : 0;;
		led_ram[8][SB_PARAMSA] = (editmode == EM_PARAMSA) ? 255 : (editmode == EM_PLAY && ui_edit_param < P_LAST && (ui_edit_param % 12) < 6) ? flickery : 0;
		led_ram[8][SB_PARAMSB] = (editmode == EM_PARAMSB) ? 255 : (editmode == EM_PLAY && ui_edit_param < P_LAST && (ui_edit_param % 12) >= 6) ? flickery : 0;
		if (shift_down >= 0 && shift_down < 8)
			led_ram[8][shift_down] = maxi(led_ram[8][shift_down], 128);
	}
//	for (int x = 0; x < 8; ++x) for (int y = 0; y < 9; ++y) led_ram[y][x] = 255; // all leds on test
}
#ifdef DEBUG
//#define NOISETEST
#endif
#ifdef NOISETEST
extern float noisetestl;
extern float noisetestr;
extern float noisetest;
extern s16 noisegate;
#endif

void plinky_frame(void) {
	codec_setheadphonevol(sysparams.headphonevol + 45);

#ifdef NOISETEST
	static u8 foo;
	foo++;
	if (foo>20)
		DebugLog("%d %d - %d ng %d\r\n",(int)noisetestl,(int)noisetestr,(int)noisetest, noisegate),foo=0;
#endif
	if (0)
		if ((frame & 31) == 0) {
			DebugLog("spi %d - ", spiduration);
			spiduration = 0;
			tc_log(&_tc_budget, "budget");
			tc_log(&_tc_all, "all");
			/*
			tc_log(&_tc_fx, "fx");
			tc_log(&_tc_audio, "audio");
			//		tc_log(&_tc_touch,"touch");
			//		tc_log(&_tc_led,"led");
			tc_log(&_tc_osc, "osc");
			//		tc_log(&_tc_filter,"filt");
			*/
			DebugLog("\r\n");
		}

	audiopeakhistory[audiohistpos] = clampi(audioin_peak / 64, 0, 255);


	if (editmode == EM_SAMPLE) {
		samplemode_ui();
	}
	else {
		editmode_ui();
	}
	audiohistpos = (audiohistpos + 1) & 31;
	PumpFlashWrites();

	/*
	int sensidx=34;
	int a = (1 << 23) / (finger_raw[sensidx] + 1);
	int b = (1 << 23) / (finger_raw[sensidx+1] + 1);
	a -= (1 << 23) / (finger_max[sensidx] + 1);
	b -= (1 << 23) / (finger_max[sensidx+1] + 1);
	int pressure = a + b;
	int pos = (pressure>1000) ? ((b - a)<<12) / pressure : 0;
	static int prstrata[8];
	static int postrata[8];
	int s = (pressure-1000) / 500; if (s < 0) s = 0; if (s > 7) s = 7;
	prstrata[s] = pressure;
	postrata[s]=pos;
	if (pressure>1000) {
		DebugLog("%5d %5d %5d %5d    ",finger_max[sensidx], finger_max[sensidx+1], finger_raw[sensidx], finger_raw[sensidx+1]);
		for (int i = 0; i < 8; ++i)
		//	DebugLog("%4d %5d  ", prstrata[i], postrata[i]);
		DebugLog("\r\n");
	}
//	HAL_Delay(10);
*/

}

