ft2-clone

Fasttracker 2 clone
Log | Files | Refs | README | LICENSE

ft2_pattern_draw.c (28157B)


      1 // for finding memory leaks in debug mode with Visual Studio
      2 #if defined _DEBUG && defined _MSC_VER
      3 #include <crtdbg.h>
      4 #endif
      5 
      6 #include <stdio.h>
      7 #include <stdint.h>
      8 #include "ft2_header.h"
      9 #include "ft2_pattern_ed.h"
     10 #include "ft2_config.h"
     11 #include "ft2_gui.h"
     12 #include "ft2_video.h"
     13 #include "ft2_tables.h"
     14 #include "ft2_bmp.h"
     15 #include "ft2_structs.h"
     16 
     17 static note_t emptyPattern[MAX_CHANNELS * MAX_PATT_LEN];
     18 
     19 static const uint8_t *font4Ptr, *font5Ptr;
     20 static const uint8_t vol2charTab1[16] = { 39, 0, 1, 2, 3, 4, 36, 52, 53, 54, 28, 31, 25, 58, 59, 22 };
     21 static const uint8_t vol2charTab2[16] = { 42, 0, 1, 2, 3, 4, 36, 37, 38, 39, 28, 31, 25, 40, 41, 22 };
     22 static const uint8_t columnModeTab[12] = { 0, 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 3 };
     23 static const uint8_t sharpNote1Char_small[12] = {  8*6,  8*6,  9*6,  9*6, 10*6, 11*6, 11*6, 12*6, 12*6, 13*6, 13*6, 14*6 };
     24 static const uint8_t sharpNote2Char_small[12] = { 16*6, 15*6, 16*6, 15*6, 16*6, 16*6, 15*6, 16*6, 15*6, 16*6, 15*6, 16*6 };
     25 static const uint8_t flatNote1Char_small[12] = {  8*6,  9*6,  9*6, 10*6, 10*6, 11*6, 12*6, 12*6, 13*6, 13*6, 14*6, 14*6 };
     26 static const uint8_t flatNote2Char_small[12] = { 16*6, 17*6, 16*6, 17*6, 16*6, 16*6, 17*6, 16*6, 17*6, 16*6, 17*6, 16*6 };
     27 static const uint8_t sharpNote1Char_med[12] = { 12*8, 12*8, 13*8, 13*8, 14*8, 15*8, 15*8, 16*8, 16*8, 10*8, 10*8, 11*8 };
     28 static const uint16_t sharpNote2Char_med[12] = { 36*8, 37*8, 36*8, 37*8, 36*8, 36*8, 37*8, 36*8, 37*8, 36*8, 37*8, 36*8 };
     29 static const uint8_t flatNote1Char_med[12] = { 12*8, 13*8, 13*8, 14*8, 14*8, 15*8, 16*8, 16*8, 10*8, 10*8, 11*8, 11*8 };
     30 static const uint16_t flatNote2Char_med[12] = { 36*8, 38*8, 36*8, 38*8, 36*8, 36*8, 38*8, 36*8, 38*8, 36*8, 38*8, 36*8 };
     31 static const uint16_t sharpNote1Char_big[12] = { 12*16, 12*16, 13*16, 13*16, 14*16, 15*16, 15*16, 16*16, 16*16, 10*16, 10*16, 11*16 };
     32 static const uint16_t sharpNote2Char_big[12] = { 36*16, 37*16, 36*16, 37*16, 36*16, 36*16, 37*16, 36*16, 37*16, 36*16, 37*16, 36*16 };
     33 static const uint16_t flatNote1Char_big[12] = { 12*16, 13*16, 13*16, 14*16, 14*16, 15*16, 16*16, 16*16, 10*16, 10*16, 11*16, 11*16 };
     34 static const uint16_t flatNote2Char_big[12] = { 36*16, 38*16, 36*16, 38*16, 36*16, 36*16, 38*16, 36*16, 38*16, 36*16, 38*16, 36*16 };
     35 
     36 static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontType, uint32_t color);
     37 static void drawEmptyNoteSmall(uint32_t xPos, uint32_t yPos, uint32_t color);
     38 static void drawKeyOffSmall(uint32_t xPos, uint32_t yPos, uint32_t color);
     39 static void drawNoteSmall(uint32_t xPos, uint32_t yPos, int32_t noteNum, uint32_t color);
     40 static void drawEmptyNoteMedium(uint32_t xPos, uint32_t yPos, uint32_t color);
     41 static void drawKeyOffMedium(uint32_t xPos, uint32_t yPos, uint32_t color);
     42 static void drawNoteMedium(uint32_t xPos, uint32_t yPos, int32_t noteNum, uint32_t color);
     43 static void drawEmptyNoteBig(uint32_t xPos, uint32_t yPos, uint32_t color);
     44 static void drawKeyOffBig(uint32_t xPos, uint32_t yPos, uint32_t color);
     45 static void drawNoteBig(uint32_t xPos, uint32_t yPos, int32_t noteNum, uint32_t color);
     46 
     47 void updatePattFontPtrs(void)
     48 {
     49 	//config.ptnFont is pre-clamped and safe to use
     50 	font4Ptr = &bmp.font4[config.ptnFont * (FONT4_WIDTH * FONT4_CHAR_H)];
     51 	font5Ptr = &bmp.font4[(4 + config.ptnFont) * (FONT4_WIDTH * FONT4_CHAR_H)];
     52 }
     53 
     54 void drawPatternBorders(void)
     55 {
     56 	// get heights/pos/rows depending on configuration
     57 	const pattCoord2_t *pattCoord = &pattCoord2Table[config.ptnStretch][ui.pattChanScrollShown][ui.extendedPatternEditor];
     58 
     59 	// set pattern cursor Y position
     60 	editor.ptnCursorY = pattCoord->lowerRowsY - 9;
     61 
     62 	int32_t chans = ui.numChannelsShown;
     63 	if (chans > ui.maxVisibleChannels)
     64 		chans = ui.maxVisibleChannels;
     65 
     66 	// in some configurations, there will be two empty channels to the right, fix that
     67 	if (chans == 2)
     68 		chans = 4;
     69 	else if (chans == 10 && !config.ptnShowVolColumn)
     70 		chans = 12;
     71 
     72 	assert(chans >= 2 && chans <= 12);
     73 
     74 	const uint16_t chanWidth = chanWidths[(chans >> 1) - 1] + 2;
     75 
     76 	// fill scrollbar framework (if needed)
     77 	if (ui.pattChanScrollShown)
     78 		drawFramework(0, 383, 632, 17, FRAMEWORK_TYPE1);
     79 
     80 	if (config.ptnFrmWrk)
     81 	{
     82 		// pattern editor w/ framework
     83 
     84 		if (ui.extendedPatternEditor)
     85 		{
     86 			vLine(0,   69, 330, PAL_DSKTOP1);
     87 			vLine(631, 68, 331, PAL_DSKTOP2);
     88 
     89 			vLine(1,   69, 330, PAL_DESKTOP);
     90 			vLine(630, 69, 330, PAL_DESKTOP);
     91 
     92 			hLine(0, 68, 631, PAL_DSKTOP1);
     93 			hLine(1, 69, 630, PAL_DESKTOP);
     94 
     95 			if (!ui.pattChanScrollShown)
     96 			{
     97 				hLine(1, 398, 630, PAL_DESKTOP);
     98 				hLine(0, 399, 632, PAL_DSKTOP2);
     99 			}
    100 		}
    101 		else
    102 		{
    103 			vLine(0,   174, 225, PAL_DSKTOP1);
    104 			vLine(631, 173, 226, PAL_DSKTOP2);
    105 
    106 			vLine(1,   174, 225, PAL_DESKTOP);
    107 			vLine(630, 174, 225, PAL_DESKTOP);
    108 
    109 			hLine(0, 173, 631, PAL_DSKTOP1);
    110 			hLine(1, 174, 630, PAL_DESKTOP);
    111 
    112 			if (!ui.pattChanScrollShown)
    113 			{
    114 				hLine(1, 398, 630, PAL_DESKTOP);
    115 				hLine(0, 399, 632, PAL_DSKTOP2);
    116 			}
    117 		}
    118 
    119 		// fill middle (current row)
    120 		fillRect(2, pattCoord->lowerRowsY - 9, 628, 9, PAL_DESKTOP);
    121 
    122 		// fill row number boxes
    123 		drawFramework(2, pattCoord->upperRowsY, 25, pattCoord->upperRowsH, FRAMEWORK_TYPE2); // top left
    124 		drawFramework(604, pattCoord->upperRowsY, 26, pattCoord->upperRowsH, FRAMEWORK_TYPE2); // top right
    125 		drawFramework(2, pattCoord->lowerRowsY, 25, pattCoord->lowerRowsH, FRAMEWORK_TYPE2); // bottom left
    126 		drawFramework(604, pattCoord->lowerRowsY, 26, pattCoord->lowerRowsH, FRAMEWORK_TYPE2); // bottom right
    127 
    128 		// draw channels
    129 		uint16_t xOffs = 28;
    130 		for (int32_t i = 0; i < chans; i++)
    131 		{
    132 			vLine(xOffs - 1, pattCoord->upperRowsY, pattCoord->upperRowsH, PAL_DESKTOP);
    133 			vLine(xOffs - 1, pattCoord->lowerRowsY, pattCoord->lowerRowsH + 1, PAL_DESKTOP);
    134 
    135 			drawFramework(xOffs, pattCoord->upperRowsY, chanWidth, pattCoord->upperRowsH, FRAMEWORK_TYPE2); // top part
    136 			drawFramework(xOffs, pattCoord->lowerRowsY, chanWidth, pattCoord->lowerRowsH, FRAMEWORK_TYPE2); // bottom part
    137 
    138 			xOffs += chanWidth+1;
    139 		}
    140 
    141 		vLine(xOffs-1, pattCoord->upperRowsY, pattCoord->upperRowsH, PAL_DESKTOP);
    142 		vLine(xOffs-1, pattCoord->lowerRowsY, pattCoord->lowerRowsH+1, PAL_DESKTOP);
    143 	}
    144 	else
    145 	{
    146 		// pattern editor without framework
    147 
    148 		if (ui.extendedPatternEditor)
    149 		{
    150 			const int32_t clearSize = ui.pattChanScrollShown ? (SCREEN_W * sizeof (int32_t) * 315) : (SCREEN_W * sizeof (int32_t) * 332);
    151 			memset(&video.frameBuffer[68 * SCREEN_W], 0, clearSize);
    152 		}
    153 		else
    154 		{
    155 			const int32_t clearSize = ui.pattChanScrollShown ? (SCREEN_W * sizeof(int32_t) * 210) : (SCREEN_W * sizeof(int32_t) * 227);
    156 			memset(&video.frameBuffer[173 * SCREEN_W], 0, clearSize);
    157 		}
    158 
    159 		drawFramework(0, pattCoord->lowerRowsY - 10, SCREEN_W, 11, FRAMEWORK_TYPE1);
    160 	}
    161 
    162 	if (ui.pattChanScrollShown)
    163 	{
    164 		showScrollBar(SB_CHAN_SCROLL);
    165 		showPushButton(PB_CHAN_SCROLL_LEFT);
    166 		showPushButton(PB_CHAN_SCROLL_RIGHT);
    167 	}
    168 	else
    169 	{
    170 		hideScrollBar(SB_CHAN_SCROLL);
    171 		hidePushButton(PB_CHAN_SCROLL_LEFT);
    172 		hidePushButton(PB_CHAN_SCROLL_RIGHT);
    173 
    174 		setScrollBarPos(SB_CHAN_SCROLL, 0, false);
    175 	}
    176 }
    177 
    178 static void writeCursor(void)
    179 {
    180 	const int32_t tabOffset = (config.ptnShowVolColumn * 32) + (columnModeTab[ui.numChannelsShown-1] * 8) + cursor.object;
    181 
    182 	int32_t xPos = pattCursorXTab[tabOffset];
    183 	const int32_t width = pattCursorWTab[tabOffset];
    184 
    185 	assert(editor.ptnCursorY > 0 && xPos > 0 && width > 0);
    186 	xPos += ((cursor.ch - ui.channelOffset) * ui.patternChannelWidth);
    187 
    188 	uint32_t *dstPtr = &video.frameBuffer[(editor.ptnCursorY * SCREEN_W) + xPos];
    189 	for (int32_t y = 0; y < 9; y++)
    190 	{
    191 		for (int32_t x = 0; x < width; x++)
    192 			dstPtr[x] = video.palette[(dstPtr[x] >> 24) ^ 4]; // ">> 24" to get palette, XOR 4 to change to cursor palette
    193 
    194 		dstPtr += SCREEN_W;
    195 	}
    196 }
    197 
    198 static void writePatternBlockMark(int32_t currRow, uint32_t rowHeight, const pattCoord_t *pattCoord)
    199 {
    200 	int32_t y1, y2;
    201 
    202 	// this can happen (buggy FT2 code), treat like no mark
    203 	if (pattMark.markY1 > pattMark.markY2)
    204 		return;
    205 
    206 	const int32_t startCh = ui.channelOffset;
    207 	const int32_t endCh = ui.channelOffset + (ui.numChannelsShown - 1);
    208 	const int32_t startRow = currRow - pattCoord->numUpperRows;
    209 	const int32_t endRow = currRow + pattCoord->numLowerRows;
    210 
    211 	// test if pattern marking is outside of visible area (don't draw)
    212 	if (pattMark.markX1 > endCh || pattMark.markX2 < startCh || pattMark.markY1 > endRow || pattMark.markY2 < startRow)
    213 		return;
    214 
    215 	const markCoord_t *markCoord = &markCoordTable[config.ptnStretch][ui.pattChanScrollShown][ui.extendedPatternEditor];
    216 	const int32_t pattYStart = markCoord->upperRowsY;
    217 
    218 	// X1
    219 
    220 	int32_t x1 = 32 + ((pattMark.markX1 - ui.channelOffset) * ui.patternChannelWidth);
    221 	if (x1 < 32)
    222 		x1 = 32;
    223 
    224 	// X2
    225 
    226 	int32_t x2 = (32 - 8) + (((pattMark.markX2 + 1) - ui.channelOffset) * ui.patternChannelWidth);
    227 	if (x2 > 608)
    228 		x2 = 608;
    229 
    230 	// Y1
    231 
    232 	if (pattMark.markY1 < currRow)
    233 	{
    234 		y1 = pattYStart + ((pattMark.markY1 - startRow) * rowHeight);
    235 		if (y1 < pattYStart)
    236 			y1 = pattYStart;
    237 	}
    238 	else if (pattMark.markY1 == currRow)
    239 	{
    240 		y1 = markCoord->midRowY;
    241 	}
    242 	else
    243 	{
    244 		y1 = markCoord->lowerRowsY + ((pattMark.markY1 - (currRow + 1)) * rowHeight);
    245 	}
    246 
    247 	// Y2
    248 
    249 	if (pattMark.markY2-1 < currRow)
    250 	{
    251 		y2 = pattYStart + ((pattMark.markY2 - startRow) * rowHeight);
    252 	}
    253 	else if (pattMark.markY2-1 == currRow)
    254 	{
    255 		y2 = markCoord->midRowY + 11;
    256 	}
    257 	else
    258 	{
    259 		const int32_t pattYEnd = markCoord->lowerRowsY + (pattCoord->numLowerRows * rowHeight);
    260 
    261 		y2 = markCoord->lowerRowsY + ((pattMark.markY2 - (currRow + 1)) * rowHeight);
    262 		if (y2 > pattYEnd)
    263 			y2 = pattYEnd;
    264 	}
    265 
    266 	// kludge! (some mark situations could overwrite illegal areas)
    267 	if (config.ptnStretch && ui.pattChanScrollShown)
    268 	{
    269 		if (y1 == pattCoord->upperRowsY-1 || y1 == pattCoord->lowerRowsY-1)
    270 			y1++;
    271 
    272 		if (y2 == 384)
    273 			y2--;
    274 
    275 		// this can actually happen here, don't render in that case
    276 		if (y1 >= y2)
    277 			return;
    278 	}
    279 
    280 	assert(x1 > 0 && x1 < SCREEN_W && x2 > 0 && x2 < SCREEN_W &&
    281 	       y1 > 0 && y1 < SCREEN_H && y2 > 0 && y2 < SCREEN_H);
    282 
    283 	// pattern mark drawing
    284 
    285 	const int32_t w = x2 - x1;
    286 	const int32_t h = y2 - y1;
    287 
    288 	assert(x1+w <= SCREEN_W && y1+h <= SCREEN_H);
    289 
    290 	uint32_t *ptr32 = &video.frameBuffer[(y1 * SCREEN_W) + x1];
    291 	for (int32_t y = 0; y < h; y++)
    292 	{
    293 		for (int32_t x = 0; x < w; x++)
    294 			ptr32[x] = video.palette[(ptr32[x] >> 24) ^ 2]; // ">> 24" to get palette of pixel, XOR 2 to change to mark palette
    295 
    296 		ptr32 += SCREEN_W;
    297 	}
    298 }
    299 
    300 static void drawChannelNumbering(uint16_t yPos)
    301 {
    302 	uint16_t xPos = 30;
    303 	uint8_t ch = ui.channelOffset + 1;
    304 
    305 	for (uint8_t i = 0; i < ui.numChannelsShown; i++)
    306 	{
    307 		if (ch < 10)
    308 		{
    309 			charOutOutlined(xPos, yPos, PAL_MOUSEPT, '0' + (char)ch);
    310 		}
    311 		else
    312 		{
    313 			charOutOutlined(xPos, yPos, PAL_MOUSEPT, '0' + (ch / 10));
    314 			charOutOutlined(xPos + (FONT1_CHAR_W + 1), yPos, PAL_MOUSEPT, '0' + (ch % 10));
    315 		}
    316 
    317 		ch++;
    318 		xPos += ui.patternChannelWidth;
    319 	}
    320 }
    321 
    322 static void drawRowNums(int32_t yPos, uint8_t row, bool selectedRowFlag)
    323 {
    324 #define LEFT_ROW_XPOS 8
    325 #define RIGHT_ROW_XPOS 608
    326 
    327 	uint32_t pixVal;
    328 
    329 	// set color based on some conditions
    330 	if (selectedRowFlag)
    331 		pixVal = video.palette[PAL_FORGRND];
    332 	else if (config.ptnLineLight && !(row & 3))
    333 		pixVal = video.palette[PAL_BLCKTXT];
    334 	else
    335 		pixVal = video.palette[PAL_PATTEXT];
    336 
    337 	if (!config.ptnHex)
    338 		row = hex2Dec[row];
    339 
    340 	const uint8_t *src1Ptr = &font4Ptr[(row   >> 4) * FONT4_CHAR_W];
    341 	const uint8_t *src2Ptr = &font4Ptr[(row & 0x0F) * FONT4_CHAR_W];
    342 	uint32_t *dst1Ptr = &video.frameBuffer[(yPos * SCREEN_W) + LEFT_ROW_XPOS];
    343 	uint32_t *dst2Ptr = dst1Ptr + (RIGHT_ROW_XPOS - LEFT_ROW_XPOS);
    344 
    345 	for (int32_t y = 0; y < FONT4_CHAR_H; y++)
    346 	{
    347 		for (int32_t x = 0; x < FONT4_CHAR_W; x++)
    348 		{
    349 			if (src1Ptr[x])
    350 			{
    351 				dst1Ptr[x] = pixVal; // left side
    352 				dst2Ptr[x] = pixVal; // right side
    353 			}
    354 
    355 			if (src2Ptr[x])
    356 			{
    357 				dst1Ptr[FONT4_CHAR_W+x] = pixVal; // left side
    358 				dst2Ptr[FONT4_CHAR_W+x] = pixVal; // right side
    359 			}
    360 		}
    361 
    362 		src1Ptr += FONT4_WIDTH;
    363 		src2Ptr += FONT4_WIDTH;
    364 		dst1Ptr += SCREEN_W;
    365 		dst2Ptr += SCREEN_W;
    366 	}
    367 }
    368 
    369 // DRAWING ROUTINES (WITH VOLUME COLUMN)
    370 
    371 static void showNoteNum(uint32_t xPos, uint32_t yPos, int16_t note, uint32_t color)
    372 {
    373 	xPos += 3;
    374 
    375 	assert(note >= 0 && note <= 97);
    376 
    377 	if (ui.numChannelsShown <= 4)
    378 	{
    379 		if (note <= 0 || note > 97)
    380 			drawEmptyNoteBig(xPos, yPos, color);
    381 		else if (note == NOTE_OFF)
    382 			drawKeyOffBig(xPos, yPos, color);
    383 		else
    384 			drawNoteBig(xPos, yPos, note, color);
    385 	}
    386 	else
    387 	{
    388 		if (note <= 0 || note > 97)
    389 			drawEmptyNoteMedium(xPos, yPos, color);
    390 		else if (note == NOTE_OFF)
    391 			drawKeyOffMedium(xPos, yPos, color);
    392 		else
    393 			drawNoteMedium(xPos, yPos, note, color);
    394 	}
    395 }
    396 
    397 static void showInstrNum(uint32_t xPos, uint32_t yPos, uint8_t ins, uint32_t color)
    398 {
    399 	uint8_t charW, fontType;
    400 
    401 	if (ui.numChannelsShown <= 4)
    402 	{
    403 		fontType = FONT_TYPE4;
    404 		charW = FONT4_CHAR_W;
    405 		xPos += 67;
    406 	}
    407 	else if (ui.numChannelsShown <= 6)
    408 	{
    409 		fontType = FONT_TYPE4;
    410 		charW = FONT4_CHAR_W;
    411 		xPos += 27;
    412 	}
    413 	else
    414 	{
    415 		fontType = FONT_TYPE3;
    416 		charW = FONT3_CHAR_W;
    417 		xPos += 31;
    418 	}
    419 
    420 	if (config.ptnInstrZero)
    421 	{
    422 		pattCharOut(xPos,         yPos, ins >> 4,   fontType, color);
    423 		pattCharOut(xPos + charW, yPos, ins & 0x0F, fontType, color);
    424 	}
    425 	else
    426 	{
    427 		const uint8_t chr1 = ins >> 4;
    428 		const uint8_t chr2 = ins & 0x0F;
    429 
    430 		if (chr1 > 0)
    431 			pattCharOut(xPos, yPos, chr1, fontType, color);
    432 
    433 		if (chr1 > 0 || chr2 > 0)
    434 			pattCharOut(xPos + charW, yPos, chr2, fontType, color);
    435 	}
    436 }
    437 
    438 static void showVolEfx(uint32_t xPos, uint32_t yPos, uint8_t vol, uint32_t color)
    439 {
    440 	uint8_t char1, char2, fontType, charW;
    441 
    442 	if (ui.numChannelsShown <= 4)
    443 	{
    444 		fontType = FONT_TYPE4;
    445 		charW = FONT4_CHAR_W;
    446 		xPos += 91;
    447 
    448 		char1 = vol2charTab1[vol >> 4];
    449 		if (vol < 0x10)
    450 			char2 = 39;
    451 		else
    452 			char2 = vol & 0x0F;
    453 	}
    454 	else if (ui.numChannelsShown <= 6)
    455 	{
    456 		fontType = FONT_TYPE4;
    457 		charW = FONT4_CHAR_W;
    458 		xPos += 51;
    459 
    460 		char1 = vol2charTab1[vol >> 4];
    461 		if (vol < 0x10)
    462 			char2 = 39;
    463 		else
    464 			char2 = vol & 0x0F;
    465 	}
    466 	else
    467 	{
    468 		fontType = FONT_TYPE3;
    469 		charW = FONT3_CHAR_W;
    470 		xPos += 43;
    471 
    472 		char1 = vol2charTab2[vol >> 4];
    473 		if (vol < 0x10)
    474 			char2 = 42;
    475 		else
    476 			char2 = vol & 0x0F;
    477 	}
    478 
    479 	pattCharOut(xPos,         yPos, char1, fontType, color);
    480 	pattCharOut(xPos + charW, yPos, char2, fontType, color);
    481 }
    482 
    483 static void showEfx(uint32_t xPos, uint32_t yPos, uint8_t efx, uint8_t efxData, uint32_t color)
    484 {
    485 	uint8_t fontType, charW;
    486 
    487 	if (ui.numChannelsShown <= 4)
    488 	{
    489 		fontType = FONT_TYPE4;
    490 		charW = FONT4_CHAR_W;
    491 		xPos += 115;
    492 	}
    493 	else if (ui.numChannelsShown <= 6)
    494 	{
    495 		fontType = FONT_TYPE4;
    496 		charW = FONT4_CHAR_W;
    497 		xPos += 67;
    498 	}
    499 	else
    500 	{
    501 		fontType = FONT_TYPE3;
    502 		charW = FONT3_CHAR_W;
    503 		xPos += 55;
    504 	}
    505 
    506 	pattCharOut(xPos,               yPos, efx,            fontType, color);
    507 	pattCharOut(xPos +  charW,      yPos, efxData >> 4,   fontType, color);
    508 	pattCharOut(xPos + (charW * 2), yPos, efxData & 0x0F, fontType, color);
    509 }
    510 
    511 // DRAWING ROUTINES (WITHOUT VOLUME COLUMN)
    512 
    513 static void showNoteNumNoVolColumn(uint32_t xPos, uint32_t yPos, int16_t note, uint32_t color)
    514 {
    515 	xPos += 3;
    516 
    517 	assert(note >= 0 && note <= 97);
    518 
    519 	if (ui.numChannelsShown <= 6)
    520 	{
    521 		if (note <= 0 || note > 97)
    522 			drawEmptyNoteBig(xPos, yPos, color);
    523 		else if (note == NOTE_OFF)
    524 			drawKeyOffBig(xPos, yPos, color);
    525 		else
    526 			drawNoteBig(xPos, yPos, note, color);
    527 	}
    528 	else if (ui.numChannelsShown <= 8)
    529 	{
    530 		if (note <= 0 || note > 97)
    531 			drawEmptyNoteMedium(xPos, yPos, color);
    532 		else if (note == NOTE_OFF)
    533 			drawKeyOffMedium(xPos, yPos, color);
    534 		else
    535 			drawNoteMedium(xPos, yPos, note, color);
    536 	}
    537 	else
    538 	{
    539 		if (note <= 0 || note > 97)
    540 			drawEmptyNoteSmall(xPos, yPos, color);
    541 		else if (note == NOTE_OFF)
    542 			drawKeyOffSmall(xPos, yPos, color);
    543 		else
    544 			drawNoteSmall(xPos, yPos, note, color);
    545 	}
    546 }
    547 
    548 static void showInstrNumNoVolColumn(uint32_t xPos, uint32_t yPos, uint8_t ins, uint32_t color)
    549 {
    550 	uint8_t charW, fontType;
    551 
    552 	if (ui.numChannelsShown <= 4)
    553 	{
    554 		fontType = FONT_TYPE5;
    555 		charW = FONT5_CHAR_W;
    556 		xPos += 59;
    557 	}
    558 	else if (ui.numChannelsShown <= 6)
    559 	{
    560 		fontType = FONT_TYPE4;
    561 		charW = FONT4_CHAR_W;
    562 		xPos += 51;
    563 	}
    564 	else if (ui.numChannelsShown <= 8)
    565 	{
    566 		fontType = FONT_TYPE4;
    567 		charW = FONT4_CHAR_W;
    568 		xPos += 27;
    569 	}
    570 	else
    571 	{
    572 		fontType = FONT_TYPE3;
    573 		charW = FONT3_CHAR_W;
    574 		xPos += 23;
    575 	}
    576 
    577 	if (config.ptnInstrZero)
    578 	{
    579 		pattCharOut(xPos,         yPos, ins >> 4,   fontType, color);
    580 		pattCharOut(xPos + charW, yPos, ins & 0x0F, fontType, color);
    581 	}
    582 	else
    583 	{
    584 		const uint8_t chr1 = ins >> 4;
    585 		const uint8_t chr2 = ins & 0x0F;
    586 
    587 		if (chr1 > 0)
    588 			pattCharOut(xPos, yPos, chr1, fontType, color);
    589 
    590 		if (chr1 > 0 || chr2 > 0)
    591 			pattCharOut(xPos + charW, yPos, chr2, fontType, color);
    592 	}
    593 }
    594 
    595 static void showNoVolEfx(uint32_t xPos, uint32_t yPos, uint8_t vol, uint32_t color)
    596 {
    597 	(void)xPos;
    598 	(void)yPos;
    599 	(void)vol;
    600 	(void)color;
    601 }
    602 
    603 static void showEfxNoVolColumn(uint32_t xPos, uint32_t yPos, uint8_t efx, uint8_t efxData, uint32_t color)
    604 {
    605 	uint8_t charW, fontType;
    606 
    607 	if (ui.numChannelsShown <= 4)
    608 	{
    609 		fontType = FONT_TYPE5;
    610 		charW = FONT5_CHAR_W;
    611 		xPos += 91;
    612 	}
    613 	else if (ui.numChannelsShown <= 6)
    614 	{
    615 		fontType = FONT_TYPE4;
    616 		charW = FONT4_CHAR_W;
    617 		xPos += 67;
    618 	}
    619 	else if (ui.numChannelsShown <= 8)
    620 	{
    621 		fontType = FONT_TYPE4;
    622 		charW = FONT4_CHAR_W;
    623 		xPos += 43;
    624 	}
    625 	else
    626 	{
    627 		fontType = FONT_TYPE3;
    628 		charW = FONT3_CHAR_W;
    629 		xPos += 31;
    630 	}
    631 
    632 	pattCharOut(xPos,               yPos, efx,            fontType, color);
    633 	pattCharOut(xPos +  charW,      yPos, efxData >> 4,   fontType, color);
    634 	pattCharOut(xPos + (charW * 2), yPos, efxData & 0x0F, fontType, color);
    635 }
    636 
    637 void writePattern(int32_t currRow, int32_t currPattern)
    638 {
    639 	uint32_t noteTextColors[2];
    640 
    641 	void (*drawNote)(uint32_t, uint32_t, int16_t, uint32_t);
    642 	void (*drawInst)(uint32_t, uint32_t, uint8_t, uint32_t);
    643 	void (*drawVolEfx)(uint32_t, uint32_t, uint8_t, uint32_t);
    644 	void (*drawEfx)(uint32_t, uint32_t, uint8_t, uint8_t, uint32_t);
    645 
    646 	/* Draw pattern framework every time (erasing existing content).
    647 	** FT2 doesn't do this. This is quite lazy and consumes more CPU
    648 	** time than needed (overlapped drawing), but it makes the pattern
    649 	** mark/cursor drawing MUCH simpler to implement...
    650 	*/
    651 	drawPatternBorders();
    652 
    653 	// setup variables
    654 
    655 	uint32_t chans = ui.numChannelsShown;
    656 	if (chans > ui.maxVisibleChannels)
    657 		chans = ui.maxVisibleChannels;
    658 
    659 	assert(chans >= 2 && chans <= 12);
    660 
    661 	// get channel width
    662 	const uint32_t chanWidth = chanWidths[(chans / 2) - 1];
    663 	ui.patternChannelWidth = (uint16_t)(chanWidth + 3);
    664 
    665 	// get heights/pos/rows depending on configuration
    666 	uint32_t rowHeight = config.ptnStretch ? 11 : 8;
    667 	const pattCoord_t *pattCoord = &pattCoordTable[config.ptnStretch][ui.pattChanScrollShown][ui.extendedPatternEditor];
    668 	const pattCoord2_t *pattCoord2 = &pattCoord2Table[config.ptnStretch][ui.pattChanScrollShown][ui.extendedPatternEditor];
    669 	const int32_t midRowTextY = pattCoord->midRowTextY;
    670 	const int32_t lowerRowsTextY = pattCoord->lowerRowsTextY;
    671 	int32_t row = currRow - pattCoord->numUpperRows;
    672 	const int32_t rowsOnScreen = pattCoord->numUpperRows + 1 + pattCoord->numLowerRows;
    673 	int32_t textY = pattCoord->upperRowsTextY;
    674 	const int32_t afterCurrRow = currRow + 1;
    675 	const int32_t numChannels = ui.numChannelsShown;
    676 	note_t *pattPtr = pattern[currPattern];
    677 	const int32_t numRows = patternNumRows[currPattern];
    678 
    679 	// increment pattern data pointer by horizontal scrollbar offset/channel
    680 	if (pattPtr != NULL)
    681 		pattPtr += ui.channelOffset;
    682 
    683 	// set up function pointers for drawing
    684 	if (config.ptnShowVolColumn)
    685 	{
    686 		drawNote = showNoteNum;
    687 		drawInst = showInstrNum;
    688 		drawVolEfx = showVolEfx;
    689 		drawEfx = showEfx;
    690 	}
    691 	else
    692 	{
    693 		drawNote = showNoteNumNoVolColumn;
    694 		drawInst = showInstrNumNoVolColumn;
    695 		drawVolEfx = showNoVolEfx;
    696 		drawEfx = showEfxNoVolColumn;
    697 	}
    698 
    699 	noteTextColors[0] = video.palette[PAL_PATTEXT]; // not selected
    700 	noteTextColors[1] = video.palette[PAL_FORGRND]; // selected
    701 
    702 	// draw pattern data
    703 	for (int32_t i = 0; i < rowsOnScreen; i++)
    704 	{
    705 		if (row >= 0)
    706 		{
    707 			const bool selectedRowFlag = (row == currRow);
    708 
    709 			drawRowNums(textY, (uint8_t)row, selectedRowFlag);
    710 
    711 			const note_t *p = (pattPtr == NULL) ? emptyPattern : &pattPtr[(uint32_t)row * MAX_CHANNELS];
    712 			const int32_t xWidth = ui.patternChannelWidth;
    713 			const uint32_t color = noteTextColors[selectedRowFlag];
    714 
    715 			int32_t xPos = 29;
    716 			for (int32_t j = 0; j < numChannels; j++, p++, xPos += xWidth)
    717 			{
    718 				drawNote(xPos, textY, p->note, color);
    719 				drawInst(xPos, textY, p->instr, color);
    720 				drawVolEfx(xPos, textY, p->vol, color);
    721 				drawEfx(xPos, textY, p->efx, p->efxData, color);
    722 			}
    723 		}
    724 
    725 		// next row
    726 		if (++row >= numRows)
    727 			break;
    728 
    729 		// adjust textY position
    730 		if (row == currRow)
    731 			textY = midRowTextY;
    732 		else if (row == afterCurrRow)
    733 			textY = lowerRowsTextY;
    734 		else
    735 			textY += rowHeight;
    736 	}
    737 
    738 	writeCursor();
    739 
    740 	// draw pattern marking (if anything is marked)
    741 	if (pattMark.markY1 != pattMark.markY2)
    742 		writePatternBlockMark(currRow, rowHeight, pattCoord);
    743 
    744 	// channel numbers must be drawn lastly
    745 	if (config.ptnChnNumbers)
    746 		drawChannelNumbering(pattCoord2->upperRowsY+2);
    747 }
    748 
    749 // ========== CHARACTER DRAWING ROUTINES FOR PATTERN EDITOR ==========
    750 
    751 void pattTwoHexOut(uint32_t xPos, uint32_t yPos, uint8_t val, uint32_t color)
    752 {
    753 	const uint8_t *ch1Ptr = &font4Ptr[(val   >> 4) * FONT4_CHAR_W];
    754 	const uint8_t *ch2Ptr = &font4Ptr[(val & 0x0F) * FONT4_CHAR_W];
    755 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
    756 
    757 	for (int32_t y = 0; y < FONT4_CHAR_H; y++)
    758 	{
    759 		for (int32_t x = 0; x < FONT4_CHAR_W; x++)
    760 		{
    761 			if (ch1Ptr[x] != 0) dstPtr[x] = color;
    762 			if (ch2Ptr[x] != 0) dstPtr[FONT4_CHAR_W+x] = color;
    763 		}
    764 
    765 		ch1Ptr += FONT4_WIDTH;
    766 		ch2Ptr += FONT4_WIDTH;
    767 		dstPtr += SCREEN_W;
    768 	}
    769 }
    770 
    771 static void pattCharOut(uint32_t xPos, uint32_t yPos, uint8_t chr, uint8_t fontType, uint32_t color)
    772 {
    773 	const uint8_t *srcPtr;
    774 	int32_t x, y;
    775 
    776 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
    777 
    778 	if (fontType == FONT_TYPE3)
    779 	{
    780 		srcPtr = &bmp.font3[chr * FONT3_CHAR_W];
    781 		for (y = 0; y < FONT3_CHAR_H; y++)
    782 		{
    783 			for (x = 0; x < FONT3_CHAR_W; x++)
    784 			{
    785 				if (srcPtr[x] != 0)
    786 					dstPtr[x] = color;
    787 			}
    788 
    789 			srcPtr += FONT3_WIDTH;
    790 			dstPtr += SCREEN_W;
    791 		}
    792 	}
    793 	else if (fontType == FONT_TYPE4)
    794 	{
    795 		srcPtr = &font4Ptr[chr * FONT4_CHAR_W];
    796 		for (y = 0; y < FONT4_CHAR_H; y++)
    797 		{
    798 			for (x = 0; x < FONT4_CHAR_W; x++)
    799 			{
    800 				if (srcPtr[x] != 0)
    801 					dstPtr[x] = color;
    802 			}
    803 
    804 			srcPtr += FONT4_WIDTH;
    805 			dstPtr += SCREEN_W;
    806 		}
    807 	}
    808 	else if (fontType == FONT_TYPE5)
    809 	{
    810 		srcPtr = &font5Ptr[chr * FONT5_CHAR_W];
    811 		for (y = 0; y < FONT5_CHAR_H; y++)
    812 		{
    813 			for (x = 0; x < FONT5_CHAR_W; x++)
    814 			{
    815 				if (srcPtr[x] != 0)
    816 					dstPtr[x] = color;
    817 			}
    818 
    819 			srcPtr += FONT5_WIDTH;
    820 			dstPtr += SCREEN_W;
    821 		}
    822 	}
    823 	else
    824 	{
    825 		srcPtr = &bmp.font7[chr * FONT7_CHAR_W];
    826 		for (y = 0; y < FONT7_CHAR_H; y++)
    827 		{
    828 			for (x = 0; x < FONT7_CHAR_W; x++)
    829 			{
    830 				if (srcPtr[x] != 0)
    831 					dstPtr[x] = color;
    832 			}
    833 
    834 			srcPtr += FONT7_WIDTH;
    835 			dstPtr += SCREEN_W;
    836 		}
    837 	}
    838 }
    839 
    840 static void drawEmptyNoteSmall(uint32_t xPos, uint32_t yPos, uint32_t color)
    841 {
    842 	const uint8_t *srcPtr = &bmp.font7[18 * FONT7_CHAR_W];
    843 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
    844 
    845 	for (int32_t y = 0; y < FONT7_CHAR_H; y++)
    846 	{
    847 		for (int32_t x = 0; x < FONT7_CHAR_W*3; x++)
    848 		{
    849 			if (srcPtr[x] != 0)
    850 				dstPtr[x] = color;
    851 		}
    852 
    853 		srcPtr += FONT7_WIDTH;
    854 		dstPtr += SCREEN_W;
    855 	}
    856 }
    857 
    858 static void drawKeyOffSmall(uint32_t xPos, uint32_t yPos, uint32_t color)
    859 {
    860 	const uint8_t *srcPtr = &bmp.font7[21 * FONT7_CHAR_W];
    861 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + (xPos + 2)];
    862 
    863 	for (int32_t y = 0; y < FONT7_CHAR_H; y++)
    864 	{
    865 		for (int32_t x = 0; x < FONT7_CHAR_W*2; x++)
    866 		{
    867 			if (srcPtr[x] != 0)
    868 				dstPtr[x] = color;
    869 		}
    870 
    871 		srcPtr += FONT7_WIDTH;
    872 		dstPtr += SCREEN_W;
    873 	}
    874 }
    875 
    876 static void drawNoteSmall(uint32_t xPos, uint32_t yPos, int32_t noteNum, uint32_t color)
    877 {
    878 	uint32_t char1, char2;
    879 
    880 	noteNum--;
    881 
    882 	const uint8_t note = noteTab1[noteNum];
    883 	const uint32_t char3 = noteTab2[noteNum] * FONT7_CHAR_W;
    884 
    885 	if (config.ptnAcc == 0)
    886 	{
    887 		char1 = sharpNote1Char_small[note];
    888 		char2 = sharpNote2Char_small[note];
    889 	}
    890 	else
    891 	{
    892 		char1 = flatNote1Char_small[note];
    893 		char2 = flatNote2Char_small[note];
    894 	}
    895 
    896 	const uint8_t *ch1Ptr = &bmp.font7[char1];
    897 	const uint8_t *ch2Ptr = &bmp.font7[char2];
    898 	const uint8_t *ch3Ptr = &bmp.font7[char3];
    899 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
    900 
    901 	for (int32_t y = 0; y < FONT7_CHAR_H; y++)
    902 	{
    903 		for (int32_t x = 0; x < FONT7_CHAR_W; x++)
    904 		{
    905 			if (ch1Ptr[x] != 0) dstPtr[x] = color;
    906 			if (ch2Ptr[x] != 0) dstPtr[FONT7_CHAR_W+x] = color;
    907 			if (ch3Ptr[x] != 0) dstPtr[((FONT7_CHAR_W*2)-2)+x] = color;
    908 		}
    909 
    910 		ch1Ptr += FONT7_WIDTH;
    911 		ch2Ptr += FONT7_WIDTH;
    912 		ch3Ptr += FONT7_WIDTH;
    913 		dstPtr += SCREEN_W;
    914 	}
    915 }
    916 
    917 static void drawEmptyNoteMedium(uint32_t xPos, uint32_t yPos, uint32_t color)
    918 {
    919 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
    920 	const uint8_t *srcPtr = &font4Ptr[43 * FONT4_CHAR_W];
    921 
    922 	for (int32_t y = 0; y < FONT4_CHAR_H; y++)
    923 	{
    924 		for (int32_t x = 0; x < FONT4_CHAR_W*3; x++)
    925 		{
    926 			if (srcPtr[x] != 0)
    927 				dstPtr[x] = color;
    928 		}
    929 
    930 		srcPtr += FONT4_WIDTH;
    931 		dstPtr += SCREEN_W;
    932 	}
    933 }
    934 
    935 static void drawKeyOffMedium(uint32_t xPos, uint32_t yPos, uint32_t color)
    936 {
    937 	const uint8_t *srcPtr = &font4Ptr[40 * FONT4_CHAR_W];
    938 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
    939 
    940 	for (int32_t y = 0; y < FONT4_CHAR_H; y++)
    941 	{
    942 		for (int32_t x = 0; x < FONT4_CHAR_W*3; x++)
    943 		{
    944 			if (srcPtr[x] != 0)
    945 				dstPtr[x] = color;
    946 		}
    947 
    948 		srcPtr += FONT4_WIDTH;
    949 		dstPtr += SCREEN_W;
    950 	}
    951 }
    952 
    953 static void drawNoteMedium(uint32_t xPos, uint32_t yPos, int32_t noteNum, uint32_t color)
    954 {
    955 	uint32_t char1, char2;
    956 
    957 	noteNum--;
    958 
    959 	const uint8_t note = noteTab1[noteNum];
    960 	const uint32_t char3 = noteTab2[noteNum] * FONT4_CHAR_W;
    961 
    962 	if (config.ptnAcc == 0)
    963 	{
    964 		char1 = sharpNote1Char_med[note];
    965 		char2 = sharpNote2Char_med[note];
    966 	}
    967 	else
    968 	{
    969 		char1 = flatNote1Char_med[note];
    970 		char2 = flatNote2Char_med[note];
    971 	}
    972 
    973 	const uint8_t *ch1Ptr = &font4Ptr[char1];
    974 	const uint8_t *ch2Ptr = &font4Ptr[char2];
    975 	const uint8_t *ch3Ptr = &font4Ptr[char3];
    976 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
    977 
    978 	for (int32_t y = 0; y < FONT4_CHAR_H; y++)
    979 	{
    980 		for (int32_t x = 0; x < FONT4_CHAR_W; x++)
    981 		{
    982 			if (ch1Ptr[x] != 0) dstPtr[x] = color;
    983 			if (ch2Ptr[x] != 0) dstPtr[FONT4_CHAR_W+x] = color;
    984 			if (ch3Ptr[x] != 0) dstPtr[(FONT4_CHAR_W*2)+x] = color;
    985 		}
    986 
    987 		ch1Ptr += FONT4_WIDTH;
    988 		ch2Ptr += FONT4_WIDTH;
    989 		ch3Ptr += FONT4_WIDTH;
    990 		dstPtr += SCREEN_W;
    991 	}
    992 }
    993 
    994 static void drawEmptyNoteBig(uint32_t xPos, uint32_t yPos, uint32_t color)
    995 {
    996 	const uint8_t *srcPtr = &font4Ptr[67 * FONT4_CHAR_W];
    997 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
    998 
    999 	for (int32_t y = 0; y < FONT4_CHAR_H; y++)
   1000 	{
   1001 		for (int32_t x = 0; x < FONT4_CHAR_W*6; x++)
   1002 		{
   1003 			if (srcPtr[x] != 0)
   1004 				dstPtr[x] = color;
   1005 		}
   1006 
   1007 		srcPtr += FONT4_WIDTH;
   1008 		dstPtr += SCREEN_W;
   1009 	}
   1010 }
   1011 
   1012 static void drawKeyOffBig(uint32_t xPos, uint32_t yPos, uint32_t color)
   1013 {
   1014 	const uint8_t *srcPtr = &bmp.font4[61 * FONT4_CHAR_W];
   1015 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
   1016 
   1017 	for (int32_t y = 0; y < FONT4_CHAR_H; y++)
   1018 	{
   1019 		for (int32_t x = 0; x < FONT4_CHAR_W*6; x++)
   1020 		{
   1021 			if (srcPtr[x] != 0)
   1022 				dstPtr[x] = color;
   1023 		}
   1024 
   1025 		srcPtr += FONT4_WIDTH;
   1026 		dstPtr += SCREEN_W;
   1027 	}
   1028 }
   1029 
   1030 static void drawNoteBig(uint32_t xPos, uint32_t yPos, int32_t noteNum, uint32_t color)
   1031 {
   1032 	uint32_t char1, char2;
   1033 
   1034 	noteNum--;
   1035 
   1036 	const uint8_t note = noteTab1[noteNum];
   1037 	const uint32_t char3 = noteTab2[noteNum] * FONT5_CHAR_W;
   1038 
   1039 	if (config.ptnAcc == 0)
   1040 	{
   1041 		char1 = sharpNote1Char_big[note];
   1042 		char2 = sharpNote2Char_big[note];
   1043 	}
   1044 	else
   1045 	{
   1046 		char1 = flatNote1Char_big[note];
   1047 		char2 = flatNote2Char_big[note];
   1048 	}
   1049 
   1050 	const uint8_t *ch1Ptr = &font5Ptr[char1];
   1051 	const uint8_t *ch2Ptr = &font5Ptr[char2];
   1052 	const uint8_t *ch3Ptr = &font5Ptr[char3];
   1053 	uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + xPos];
   1054 
   1055 	for (int32_t y = 0; y < FONT5_CHAR_H; y++)
   1056 	{
   1057 		for (int32_t x = 0; x < FONT5_CHAR_W; x++)
   1058 		{
   1059 			if (ch1Ptr[x] != 0) dstPtr[x] = color;
   1060 			if (ch2Ptr[x] != 0) dstPtr[FONT5_CHAR_W+x] = color;
   1061 			if (ch3Ptr[x] != 0) dstPtr[(FONT5_CHAR_W*2)+x] = color;
   1062 		}
   1063 
   1064 		ch1Ptr += FONT5_WIDTH;
   1065 		ch2Ptr += FONT5_WIDTH;
   1066 		ch3Ptr += FONT5_WIDTH;
   1067 		dstPtr += SCREEN_W;
   1068 	}
   1069 }