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_config.h"
10 #include "ft2_keyboard.h"
11 #include "ft2_audio.h"
12 #include "ft2_midi.h"
13 #include "ft2_pattern_ed.h"
14 #include "ft2_sysreqs.h"
15 #include "ft2_textboxes.h"
16 #include "ft2_tables.h"
17 #include "ft2_structs.h"
18 
19 enum
20 {
21 	KEYTYPE_NUM = 0,
22 	KEYTYPE_ALPHA = 1
23 };
24 
25 static double dVolScaleFK1 = 1.0, dVolScaleFK2 = 1.0;
26 
27 // for block cut/copy/paste
28 static bool blockCopied;
29 static int16_t markXSize, markYSize;
30 static uint16_t ptnBufLen, trkBufLen;
31 
32 // for transposing - these are set and tested accordingly
33 static int8_t lastTranspVal;
34 static uint8_t lastInsMode, lastTranspMode;
35 static uint32_t transpDelNotes; // count of under-/overflowing notes for warning message
36 static note_t clearNote;
37 
38 static note_t blkCopyBuff[MAX_PATT_LEN * MAX_CHANNELS];
39 static note_t ptnCopyBuff[MAX_PATT_LEN * MAX_CHANNELS];
40 static note_t trackCopyBuff[MAX_PATT_LEN];
41 
42 static const int8_t tickArr[16] = { 16, 8, 0, 4, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1 };
43 
44 void recordNote(uint8_t note, int8_t vol);
45 
46 // when the cursor is at the note slot
testNoteKeys(SDL_Scancode scancode)47 static bool testNoteKeys(SDL_Scancode scancode)
48 {
49 	const int8_t noteNum = scancodeKeyToNote(scancode);
50 	if (noteNum == NOTE_OFF)
51 	{
52 		// inserts "note off" if editing song
53 		if (playMode == PLAYMODE_EDIT || playMode == PLAYMODE_RECPATT || playMode == PLAYMODE_RECSONG)
54 		{
55 			if (!allocatePattern(editor.editPattern))
56 				return true; // key pressed
57 
58 			pattern[editor.editPattern][(editor.row * MAX_CHANNELS) + cursor.ch].note = NOTE_OFF;
59 
60 			const uint16_t numRows = patternNumRows[editor.editPattern];
61 			if (playMode == PLAYMODE_EDIT && numRows >= 1)
62 				setPos(-1, (editor.row + editor.editRowSkip) % numRows, true);
63 
64 			ui.updatePatternEditor = true;
65 			setSongModifiedFlag();
66 		}
67 
68 		return true; // key pressed
69 	}
70 
71 	if (noteNum > 0 && noteNum <= 96)
72 	{
73 		recordNote(noteNum, -1);
74 		return true; // note key pressed (and note triggered)
75 	}
76 
77 	return false; // no note key pressed
78 }
79 
80 // when the cursor is at the note slot
testNoteKeysRelease(SDL_Scancode scancode)81 void testNoteKeysRelease(SDL_Scancode scancode)
82 {
83 	const int8_t noteNum = scancodeKeyToNote(scancode); // convert key scancode to note number
84 	if (noteNum > 0 && noteNum <= 96)
85 		recordNote(noteNum, 0); // release note
86 }
87 
testEditKeys(SDL_Scancode scancode,SDL_Keycode keycode)88 static bool testEditKeys(SDL_Scancode scancode, SDL_Keycode keycode)
89 {
90 	int8_t i;
91 
92 	if (cursor.object == CURSOR_NOTE)
93 	{
94 		// the edit cursor is at the note slot
95 
96 		if (testNoteKeys(scancode))
97 		{
98 			keyb.keyRepeat = (playMode == PLAYMODE_EDIT); // repeat keys only if in edit mode
99 			return true; // we jammed an instrument
100 		}
101 
102 		return false; // no note key pressed, test other keys
103 	}
104 
105 	if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECSONG && playMode != PLAYMODE_RECPATT)
106 		return false; // we're not editing, test other keys
107 
108 	// convert key to slot data
109 
110 	if (cursor.object == CURSOR_VOL1)
111 	{
112 		// volume column effect type (mixed keys)
113 
114 		for (i = 0; i < KEY2VOL_ENTRIES; i++)
115 		{
116 			if (keycode == key2VolTab[i])
117 				break;
118 		}
119 
120 		if (i == KEY2VOL_ENTRIES)
121 			i = -1; // invalid key for slot
122 	}
123 	else if (cursor.object == CURSOR_EFX0)
124 	{
125 		// effect type (mixed keys)
126 
127 		for (i = 0; i < KEY2EFX_ENTRIES; i++)
128 		{
129 			if (keycode == key2EfxTab[i])
130 				break;
131 		}
132 
133 		if (i == KEY2EFX_ENTRIES)
134 			i = -1; // invalid key for slot
135 	}
136 	else
137 	{
138 		// all other slots (hex keys)
139 
140 		for (i = 0; i < KEY2HEX_ENTRIES; i++)
141 		{
142 			if (keycode == key2HexTab[i])
143 				break;
144 		}
145 
146 		if (i == KEY2HEX_ENTRIES)
147 			i = -1; // invalid key for slot
148 	}
149 
150 	if (i == -1 || !allocatePattern(editor.editPattern))
151 		return false; // no edit to be done
152 
153 	// insert slot data
154 
155 	note_t *p = &pattern[editor.editPattern][(editor.row * MAX_CHANNELS) + cursor.ch];
156 	switch (cursor.object)
157 	{
158 		case CURSOR_INST1:
159 		{
160 			uint8_t oldVal = p->instr;
161 
162 			p->instr = (p->instr & 0x0F) | (i << 4);
163 			if (p->instr > MAX_INST)
164 				p->instr = MAX_INST;
165 
166 			if (p->instr != oldVal)
167 				setSongModifiedFlag();
168 		}
169 		break;
170 
171 		case CURSOR_INST2:
172 		{
173 			uint8_t oldVal = p->instr;
174 			p->instr = (p->instr & 0xF0) | i;
175 
176 			if (p->instr != oldVal)
177 				setSongModifiedFlag();
178 		}
179 		break;
180 
181 		case CURSOR_VOL1:
182 		{
183 			uint8_t oldVal = p->vol;
184 
185 			p->vol = (p->vol & 0x0F) | ((i + 1) << 4);
186 			if (p->vol >= 0x51 && p->vol <= 0x5F)
187 				p->vol = 0x50;
188 
189 			if (p->vol != oldVal)
190 				setSongModifiedFlag();
191 		}
192 		break;
193 
194 		case CURSOR_VOL2:
195 		{
196 			uint8_t oldVal = p->vol;
197 
198 			if (p->vol < 0x10)
199 				p->vol = 0x10 + i;
200 			else
201 				p->vol = (p->vol & 0xF0) | i;
202 
203 			if (p->vol >= 0x51 && p->vol <= 0x5F)
204 				p->vol = 0x50;
205 
206 			if (p->vol != oldVal)
207 				setSongModifiedFlag();
208 		}
209 		break;
210 
211 		case CURSOR_EFX0:
212 		{
213 			uint8_t oldVal = p->efx;
214 
215 			p->efx = i;
216 			if (p->efx != oldVal)
217 				setSongModifiedFlag();
218 		}
219 		break;
220 
221 		case CURSOR_EFX1:
222 		{
223 			uint8_t oldVal = p->efxData;
224 
225 			p->efxData = (p->efxData & 0x0F) | (i << 4);
226 			if (p->efxData != oldVal)
227 				setSongModifiedFlag();
228 		}
229 		break;
230 
231 		case CURSOR_EFX2:
232 		{
233 			uint8_t oldVal = p->efxData;
234 
235 			p->efxData = (p->efxData & 0xF0) | i;
236 			if (p->efxData != oldVal)
237 				setSongModifiedFlag();
238 		}
239 		break;
240 
241 		default: break;
242 	}
243 
244 	// increase row (only in edit mode)
245 
246 	const int16_t numRows = patternNumRows[editor.editPattern];
247 	if (playMode == PLAYMODE_EDIT && numRows >= 1)
248 		setPos(-1, (editor.row + editor.editRowSkip) % numRows, true);
249 
250 	if (i == 0) // if we inserted a zero, check if pattern is empty, for killing
251 		killPatternIfUnused(editor.editPattern);
252 
253 	ui.updatePatternEditor = true;
254 	return true;
255 }
256 
evaluateTimeStamp(int16_t * songPos,int16_t * pattNum,int16_t * row,int16_t * tick)257 static void evaluateTimeStamp(int16_t *songPos, int16_t *pattNum, int16_t *row, int16_t *tick)
258 {
259 	int16_t outSongPos = editor.songPos;
260 	int16_t outPattern = editor.editPattern;
261 	int16_t outRow = editor.row;
262 	int16_t outTick = editor.speed - editor.tick;
263 
264 	outTick = CLAMP(outTick, 0, editor.speed - 1);
265 
266 	// this is needed, but also breaks quantization on speed>15
267 	if (outTick > 15)
268 		outTick = 15;
269 
270 	const int16_t numRows = patternNumRows[outPattern];
271 
272 	if (config.recQuant > 0)
273 	{
274 		if (config.recQuantRes >= 16)
275 		{
276 			outTick += (editor.speed >> 1) + 1;
277 		}
278 		else
279 		{
280 			int16_t r = tickArr[config.recQuantRes-1];
281 			int16_t p = outRow & (r - 1);
282 
283 			if (p < (r >> 1))
284 				outRow -= p;
285 			else
286 				outRow = (outRow + r) - p;
287 
288 			outTick = 0;
289 		}
290 	}
291 
292 	if (outTick > editor.speed)
293 	{
294 		outTick -= editor.speed;
295 		outRow++;
296 	}
297 
298 	if (outRow >= numRows)
299 	{
300 		outRow = 0;
301 
302 		if (playMode == PLAYMODE_RECSONG)
303 			outSongPos++;
304 
305 		if (outSongPos >= song.songLength)
306 			outSongPos = song.songLoopStart;
307 
308 		outPattern = song.orders[outSongPos];
309 	}
310 
311 	*songPos = outSongPos;
312 	*pattNum = outPattern;
313 	*row = outRow;
314 	*tick = outTick;
315 }
316 
recordNote(uint8_t noteNum,int8_t vol)317 void recordNote(uint8_t noteNum, int8_t vol) // directly ported from the original FT2 code - what a mess, but it works...
318 {
319 	int8_t i;
320 	int16_t pattNum, songPos, row, tick;
321 	int32_t time;
322 	note_t *p;
323 
324 	const int16_t oldRow = editor.row;
325 
326 	if (songPlaying)
327 	{
328 		// row quantization
329 		evaluateTimeStamp(&songPos, &pattNum, &row, &tick);
330 	}
331 	else
332 	{
333 		songPos = editor.songPos;
334 		pattNum = editor.editPattern;
335 		row = editor.row;
336 		tick = 0;
337 	}
338 
339 	bool editmode = (playMode == PLAYMODE_EDIT);
340 	bool recmode = (playMode == PLAYMODE_RECSONG) || (playMode == PLAYMODE_RECPATT);
341 
342 	if (noteNum == NOTE_OFF)
343 		vol = 0;
344 
345 	int8_t c = -1;
346 	int8_t k = -1;
347 
348 	if (editmode || recmode)
349 	{
350 		// find out what channel is the most suitable in edit/record mode
351 
352 		if ((config.multiEdit && editmode) || (config.multiRec && recmode))
353 		{
354 			time = 0x7FFFFFFF;
355 			for (i = 0; i < song.numChannels; i++)
356 			{
357 				if (editor.chnMode[i] && config.multiRecChn[i] && editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0)
358 				{
359 					c = i;
360 					time = editor.keyOffTime[i];
361 				}
362 			}
363 		}
364 		else
365 		{
366 			c = cursor.ch;
367 		}
368 
369 		for (i = 0; i < song.numChannels; i++)
370 		{
371 			if (noteNum == editor.keyOnTab[i] && config.multiRecChn[i])
372 				k = i;
373 		}
374 	}
375 	else
376 	{
377 		// find out what channel is the most suitable in idle/play mode (jamming)
378 		if (config.multiKeyJazz)
379 		{
380 			time = 0x7FFFFFFF;
381 			c = 0;
382 
383 			if (songPlaying)
384 			{
385 				for (i = 0; i < song.numChannels; i++)
386 				{
387 					if (editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0 && config.multiRecChn[i])
388 					{
389 						c = i;
390 						time = editor.keyOffTime[i];
391 					}
392 				}
393 			}
394 
395 			if (time == 0x7FFFFFFF)
396 			{
397 				for (i = 0; i < song.numChannels; i++)
398 				{
399 					if (editor.keyOffTime[i] < time && editor.keyOnTab[i] == 0)
400 					{
401 						c = i;
402 						time = editor.keyOffTime[i];
403 					}
404 				}
405 			}
406 		}
407 		else
408 		{
409 			c = cursor.ch;
410 		}
411 
412 		for (i = 0; i < song.numChannels; i++)
413 		{
414 			if (noteNum == editor.keyOnTab[i])
415 				k = i;
416 		}
417 	}
418 
419 	if (vol != 0)
420 	{
421 		if (c < 0 || (k >= 0 && (config.multiEdit || (recmode || !editmode))))
422 			return;
423 
424 		// play note
425 
426 		editor.keyOnTab[c] = noteNum;
427 
428 		if (row >= oldRow) // non-FT2 fix: only do this if we didn't quantize to next row
429 		{
430 #ifdef HAS_MIDI
431 			playTone(c, editor.curInstr, noteNum, vol, midi.currMIDIVibDepth, midi.currMIDIPitch);
432 #else
433 			playTone(c, editor.curInstr, noteNum, vol, 0, 0);
434 #endif
435 		}
436 
437 		if (editmode || recmode)
438 		{
439 			if (allocatePattern(pattNum))
440 			{
441 				const int16_t numRows = patternNumRows[pattNum];
442 				p = &pattern[pattNum][(row * MAX_CHANNELS) + c];
443 
444 				// insert data
445 				p->note = noteNum;
446 				if (editor.curInstr > 0)
447 					p->instr = editor.curInstr;
448 
449 				if (vol >= 0)
450 					p->vol = 0x10 + vol;
451 
452 				if (!recmode)
453 				{
454 					// increase row (only in edit mode)
455 					if (numRows >= 1)
456 						setPos(-1, (editor.row + editor.editRowSkip) % numRows, true);
457 				}
458 				else
459 				{
460 					// apply tick delay for note if quantization is disabled
461 					if (!config.recQuant && tick > 0)
462 					{
463 						p->efx = 0x0E;
464 						p->efxData = 0xD0 + (tick & 0x0F);
465 					}
466 				}
467 
468 				ui.updatePatternEditor = true;
469 				setSongModifiedFlag();
470 			}
471 		}
472 	}
473 	else
474 	{
475 		// note off
476 
477 		if (k != -1)
478 			c = k;
479 
480 		if (c < 0)
481 			return;
482 
483 		editor.keyOffNr++;
484 
485 		editor.keyOnTab[c] = 0;
486 		editor.keyOffTime[c] = editor.keyOffNr;
487 
488 		if (row >= oldRow) // non-FT2 fix: only do this if we didn't quantize to next row
489 		{
490 #ifdef HAS_MIDI
491 			playTone(c, editor.curInstr, NOTE_OFF, vol, midi.currMIDIVibDepth, midi.currMIDIPitch);
492 #else
493 			playTone(c, editor.curInstr, NOTE_OFF, vol, 0, 0);
494 #endif
495 		}
496 
497 		if (config.recRelease && recmode)
498 		{
499 			if (allocatePattern(pattNum))
500 			{
501 				// insert data
502 
503 				int16_t numRows = patternNumRows[pattNum];
504 				p = &pattern[pattNum][(row * MAX_CHANNELS) + c];
505 
506 				if (p->note != 0)
507 					row++;
508 
509 				if (row >= numRows)
510 				{
511 					row = 0;
512 
513 					if (songPlaying)
514 					{
515 						songPos++;
516 						if (songPos >= song.songLength)
517 							songPos = song.songLoopStart;
518 
519 						pattNum = song.orders[songPos];
520 						numRows = patternNumRows[pattNum];
521 					}
522 				}
523 
524 				p = &pattern[pattNum][(row * MAX_CHANNELS) + c];
525 				p->note = NOTE_OFF;
526 
527 				if (!recmode)
528 				{
529 					// increase row (only in edit mode)
530 					if (numRows >= 1)
531 						setPos(-1, (editor.row + editor.editRowSkip) % numRows, true);
532 				}
533 				else
534 				{
535 					// apply tick delay for note if quantization is disabled
536 					if (!config.recQuant && tick > 0)
537 					{
538 						p->efx = 0x0E;
539 						p->efxData = 0xD0 + (tick & 0x0F);
540 					}
541 				}
542 
543 				ui.updatePatternEditor = true;
544 				setSongModifiedFlag();
545 			}
546 		}
547 	}
548 }
549 
handleEditKeys(SDL_Keycode keycode,SDL_Scancode scancode)550 bool handleEditKeys(SDL_Keycode keycode, SDL_Scancode scancode)
551 {
552 	// special case for delete - manipulate note data
553 	if (keycode == SDLK_DELETE)
554 	{
555 		if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECSONG && playMode != PLAYMODE_RECPATT)
556 			return false; // we're not editing, test other keys
557 
558 		if (pattern[editor.editPattern] == NULL)
559 			return true;
560 
561 		note_t *p = &pattern[editor.editPattern][(editor.row * MAX_CHANNELS) + cursor.ch];
562 
563 		if (keyb.leftShiftPressed)
564 		{
565 			// delete all
566 			p->note = p->instr = p->vol = p->efx = p->efxData = 0;
567 		}
568 		else if (keyb.leftCtrlPressed)
569 		{
570 			// delete volume column + effect
571 			p->vol = 0;
572 			p->efx = 0;
573 			p->efxData = 0;
574 		}
575 		else if (keyb.leftAltPressed)
576 		{
577 			// delete effect
578 			p->efx = 0;
579 			p->efxData = 0;
580 		}
581 		else
582 		{
583 			if (cursor.object == CURSOR_VOL1 || cursor.object == CURSOR_VOL2)
584 			{
585 				// delete volume column
586 				p->vol = 0;
587 			}
588 			else
589 			{
590 				// delete note + instrument
591 				p->note = 0;
592 				p->instr = 0;
593 			}
594 		}
595 
596 		killPatternIfUnused(editor.editPattern);
597 
598 		// increase row (only in edit mode)
599 		const int16_t numRows = patternNumRows[editor.editPattern];
600 		if (playMode == PLAYMODE_EDIT && numRows >= 1)
601 			setPos(-1, (editor.row + editor.editRowSkip) % numRows, true);
602 
603 		ui.updatePatternEditor = true;
604 		setSongModifiedFlag();
605 
606 		return true;
607 	}
608 
609 	// a kludge for french keyb. layouts to allow writing numbers in the pattern data with left SHIFT
610 	const bool frKeybHack = keyb.leftShiftPressed && !keyb.leftAltPressed && !keyb.leftCtrlPressed &&
611 	               (scancode >= SDL_SCANCODE_1) && (scancode <= SDL_SCANCODE_0);
612 
613 	if (frKeybHack || !keyb.keyModifierDown)
614 		return (testEditKeys(scancode, keycode));
615 
616 	return false;
617 }
618 
writeToMacroSlot(uint8_t slot)619 void writeToMacroSlot(uint8_t slot)
620 {
621 	uint16_t writeVol = 0;
622 	uint16_t writeEfx = 0;
623 
624 	if (pattern[editor.editPattern] != NULL)
625 	{
626 		note_t *p = &pattern[editor.editPattern][(editor.row * MAX_CHANNELS) + cursor.ch];
627 		writeVol = p->vol;
628 		writeEfx = (p->efx << 8) | p->efxData;
629 	}
630 
631 	if (cursor.object == CURSOR_VOL1 || cursor.object == CURSOR_VOL2)
632 		config.volMacro[slot] = writeVol;
633 	else
634 		config.comMacro[slot] = writeEfx;
635 }
636 
writeFromMacroSlot(uint8_t slot)637 void writeFromMacroSlot(uint8_t slot)
638 {
639 	if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECSONG && playMode != PLAYMODE_RECPATT)
640 		return;
641 
642 	if (!allocatePattern(editor.editPattern))
643 		return;
644 
645 	note_t *p = &pattern[editor.editPattern][(editor.row * MAX_CHANNELS) + cursor.ch];
646 	if (cursor.object == CURSOR_VOL1 || cursor.object == CURSOR_VOL2)
647 	{
648 		p->vol = (uint8_t)config.volMacro[slot];
649 	}
650 	else
651 	{
652 		uint8_t efx = (uint8_t)(config.comMacro[slot] >> 8);
653 		if (efx > 35)
654 		{
655 			// illegal effect
656 			p->efx = 0;
657 			p->efxData = 0;
658 		}
659 		else
660 		{
661 			p->efx = efx;
662 			p->efxData = config.comMacro[slot] & 0xFF;
663 		}
664 	}
665 
666 	const int16_t numRows = patternNumRows[editor.editPattern];
667 	if (playMode == PLAYMODE_EDIT && numRows >= 1)
668 		setPos(-1, (editor.row + editor.editRowSkip) % numRows, true);
669 
670 	killPatternIfUnused(editor.editPattern);
671 
672 	ui.updatePatternEditor = true;
673 	setSongModifiedFlag();
674 }
675 
insertPatternNote(void)676 void insertPatternNote(void)
677 {
678 	if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECPATT && playMode != PLAYMODE_RECSONG)
679 		return;
680 
681 	note_t *p = pattern[editor.editPattern];
682 	if (p == NULL)
683 		return;
684 
685 	const int16_t row = editor.row;
686 	const int16_t numRows = patternNumRows[editor.editPattern];
687 
688 	if (numRows > 1)
689 	{
690 		for (int32_t i = numRows-2; i >= row; i--)
691 			p[((i+1) * MAX_CHANNELS) + cursor.ch] = p[(i * MAX_CHANNELS) + cursor.ch];
692 	}
693 
694 	memset(&p[(row * MAX_CHANNELS) + cursor.ch], 0, sizeof (note_t));
695 
696 	killPatternIfUnused(editor.editPattern);
697 
698 	ui.updatePatternEditor = true;
699 	setSongModifiedFlag();
700 }
701 
insertPatternLine(void)702 void insertPatternLine(void)
703 {
704 	if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECPATT && playMode != PLAYMODE_RECSONG)
705 		return;
706 
707 	setPatternLen(editor.editPattern, patternNumRows[editor.editPattern] + config.recTrueInsert); // config.recTrueInsert is 0 or 1
708 
709 	note_t *p = pattern[editor.editPattern];
710 	if (p != NULL)
711 	{
712 		const int16_t row = editor.row;
713 		const int16_t numRows = patternNumRows[editor.editPattern];
714 
715 		if (numRows > 1)
716 		{
717 			for (int32_t i = numRows-2; i >= row; i--)
718 			{
719 				for (int32_t j = 0; j < MAX_CHANNELS; j++)
720 					p[((i+1) * MAX_CHANNELS) + j] = p[(i * MAX_CHANNELS) + j];
721 			}
722 		}
723 
724 		memset(&p[row * MAX_CHANNELS], 0, TRACK_WIDTH);
725 
726 		killPatternIfUnused(editor.editPattern);
727 	}
728 
729 	ui.updatePatternEditor = true;
730 	setSongModifiedFlag();
731 }
732 
deletePatternNote(void)733 void deletePatternNote(void)
734 {
735 	if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECPATT && playMode != PLAYMODE_RECSONG)
736 		return;
737 
738 	int16_t row = editor.row;
739 	const int16_t numRows = patternNumRows[editor.editPattern];
740 
741 	note_t *p = pattern[editor.editPattern];
742 	if (p != NULL)
743 	{
744 		if (row > 0)
745 		{
746 			row--;
747 			editor.row = song.row = row;
748 
749 			for (int32_t i = row; i < numRows-1; i++)
750 				p[(i * MAX_CHANNELS) + cursor.ch] = p[((i+1) * MAX_CHANNELS) + cursor.ch];
751 
752 			memset(&p[((numRows-1) * MAX_CHANNELS) + cursor.ch], 0, sizeof (note_t));
753 		}
754 	}
755 	else
756 	{
757 		if (row > 0)
758 		{
759 			row--;
760 			editor.row = song.row = row;
761 		}
762 	}
763 
764 	killPatternIfUnused(editor.editPattern);
765 
766 	ui.updatePatternEditor = true;
767 	setSongModifiedFlag();
768 }
769 
deletePatternLine(void)770 void deletePatternLine(void)
771 {
772 	if (playMode != PLAYMODE_EDIT && playMode != PLAYMODE_RECPATT && playMode != PLAYMODE_RECSONG)
773 		return;
774 
775 	int16_t row = editor.row;
776 	const int16_t numRows = patternNumRows[editor.editPattern];
777 
778 	note_t *p = pattern[editor.editPattern];
779 	if (p != NULL)
780 	{
781 		if (row > 0)
782 		{
783 			row--;
784 			editor.row = song.row = row;
785 
786 			for (int32_t i = row; i < numRows-1; i++)
787 			{
788 				for (int32_t j = 0; j < MAX_CHANNELS; j++)
789 					p[(i * MAX_CHANNELS) + j] = p[((i+1) * MAX_CHANNELS) + j];
790 			}
791 
792 			memset(&p[(numRows-1) * MAX_CHANNELS], 0, TRACK_WIDTH);
793 		}
794 	}
795 	else
796 	{
797 		if (row > 0)
798 		{
799 			row--;
800 			editor.row = song.row = row;
801 		}
802 	}
803 
804 	if (config.recTrueInsert && numRows > 1)
805 		setPatternLen(editor.editPattern, numRows-1);
806 
807 	killPatternIfUnused(editor.editPattern);
808 
809 	ui.updatePatternEditor = true;
810 	setSongModifiedFlag();
811 }
812 
813 // ----- TRANSPOSE FUNCTIONS -----
814 
countOverflowingNotes(uint8_t currInsOnly,uint8_t transpMode,int8_t addVal)815 static void countOverflowingNotes(uint8_t currInsOnly, uint8_t transpMode, int8_t addVal)
816 {
817 	transpDelNotes = 0;
818 	switch (transpMode)
819 	{
820 		case TRANSP_TRACK:
821 		{
822 			note_t *p = pattern[editor.editPattern];
823 			if (p == NULL)
824 				return; // empty pattern
825 
826 			p += cursor.ch;
827 
828 			const int32_t numRows = patternNumRows[editor.editPattern];
829 			for (int32_t row = 0; row < numRows; row++, p += MAX_CHANNELS)
830 			{
831 				if ((p->note >= 1 && p->note <= 96) && (!currInsOnly || p->instr == editor.curInstr))
832 				{
833 					if ((int8_t)p->note+addVal > 96 || (int8_t)p->note+addVal <= 0)
834 						transpDelNotes++;
835 				}
836 			}
837 		}
838 		break;
839 
840 		case TRANSP_PATT:
841 		{
842 			note_t *p = pattern[editor.editPattern];
843 			if (p == NULL)
844 				return; // empty pattern
845 
846 			const int32_t numRows = patternNumRows[editor.editPattern];
847 			const int32_t pitch = MAX_CHANNELS-song.numChannels;
848 
849 			for (int32_t row = 0; row < numRows; row++, p += pitch)
850 			{
851 				for (int32_t ch = 0; ch < song.numChannels; ch++, p++)
852 				{
853 					if ((p->note >= 1 && p->note <= 96) && (!currInsOnly || p->instr == editor.curInstr))
854 					{
855 						if ((int8_t)p->note+addVal > 96 || (int8_t)p->note+addVal <= 0)
856 							transpDelNotes++;
857 					}
858 				}
859 			}
860 		}
861 		break;
862 
863 		case TRANSP_SONG:
864 		{
865 			const int32_t pitch = MAX_CHANNELS-song.numChannels;
866 			for (int32_t i = 0; i < MAX_PATTERNS; i++)
867 			{
868 				note_t *p = pattern[i];
869 				if (p == NULL)
870 					continue; // empty pattern
871 
872 				const int32_t numRows = patternNumRows[i];
873 				for (int32_t row = 0; row < numRows; row++, p += pitch)
874 				{
875 					for (int32_t ch = 0; ch < song.numChannels; ch++, p++)
876 					{
877 						if ((p->note >= 1 && p->note <= 96) && (!currInsOnly || p->instr == editor.curInstr))
878 						{
879 							if ((int8_t)p->note+addVal > 96 || (int8_t)p->note+addVal <= 0)
880 								transpDelNotes++;
881 						}
882 					}
883 				}
884 			}
885 		}
886 		break;
887 
888 		case TRANSP_BLOCK:
889 		{
890 			if (pattMark.markY1 == pattMark.markY2)
891 				return; // no pattern marking
892 
893 			note_t *p = pattern[editor.editPattern];
894 			if (p == NULL)
895 				return; // empty pattern
896 
897 			p += (pattMark.markY1 * MAX_CHANNELS) + pattMark.markX1;
898 
899 			const int32_t pitch = MAX_CHANNELS - ((pattMark.markX2 + 1) - pattMark.markX1);
900 			for (int32_t row = pattMark.markY1; row < pattMark.markY2; row++, p += pitch)
901 			{
902 				for (int32_t ch = pattMark.markX1; ch <= pattMark.markX2; ch++, p++)
903 				{
904 					if ((p->note >= 1 && p->note <= 96) && (!currInsOnly || p->instr == editor.curInstr))
905 					{
906 						if ((int8_t)p->note+addVal > 96 || (int8_t)p->note+addVal <= 0)
907 							transpDelNotes++;
908 					}
909 				}
910 			}
911 		}
912 		break;
913 
914 		default: break;
915 	}
916 }
917 
doTranspose(void)918 void doTranspose(void)
919 {
920 	char text[48];
921 
922 	countOverflowingNotes(lastInsMode, lastTranspMode, lastTranspVal);
923 	if (transpDelNotes > 0)
924 	{
925 		sprintf(text, "%d note(s) will be erased! Proceed?", (int32_t)transpDelNotes);
926 		if (okBox(2, "System request", text) != 1)
927 			return;
928 	}
929 
930 	// lastTranspVal is never <-12 or >12, so unsigned testing for >96 is safe
931 	switch (lastTranspMode)
932 	{
933 		case TRANSP_TRACK:
934 		{
935 			note_t *p = pattern[editor.editPattern];
936 			if (p == NULL)
937 				return; // empty pattern
938 
939 			p += cursor.ch;
940 
941 			const int32_t numRows = patternNumRows[editor.editPattern];
942 			for (int32_t row = 0; row < numRows; row++, p += MAX_CHANNELS)
943 			{
944 				uint8_t note = p->note;
945 				if ((note >= 1 && note <= 96) && (!lastInsMode || p->instr == editor.curInstr))
946 				{
947 					note += lastTranspVal;
948 					if (note > 96)
949 						note = 0; // also handles underflow
950 
951 					p->note = note;
952 				}
953 			}
954 		}
955 		break;
956 
957 		case TRANSP_PATT:
958 		{
959 			note_t *p = pattern[editor.editPattern];
960 			if (p == NULL)
961 				return; // empty pattern
962 
963 			const int32_t numRows = patternNumRows[editor.editPattern];
964 			const int32_t pitch = MAX_CHANNELS - song.numChannels;
965 
966 			for (int32_t row = 0; row < numRows; row++, p += pitch)
967 			{
968 				for (int32_t ch = 0; ch < song.numChannels; ch++, p++)
969 				{
970 					uint8_t note = p->note;
971 					if ((note >= 1 && note <= 96) && (!lastInsMode || p->instr == editor.curInstr))
972 					{
973 						note += lastTranspVal;
974 						if (note > 96)
975 							note = 0; // also handles underflow
976 
977 						p->note = note;
978 					}
979 				}
980 			}
981 		}
982 		break;
983 
984 		case TRANSP_SONG:
985 		{
986 			const int32_t pitch = MAX_CHANNELS - song.numChannels;
987 			for (int32_t i = 0; i < MAX_PATTERNS; i++)
988 			{
989 				note_t *p = pattern[i];
990 				if (p == NULL)
991 					continue; // empty pattern
992 
993 				const int32_t numRows = patternNumRows[i];
994 				for (int32_t row = 0; row < numRows; row++, p += pitch)
995 				{
996 					for (int32_t ch = 0; ch < song.numChannels; ch++, p++)
997 					{
998 						uint8_t note = p->note;
999 						if ((note >= 1 && note <= 96) && (!lastInsMode || p->instr == editor.curInstr))
1000 						{
1001 							note += lastTranspVal;
1002 							if (note > 96)
1003 								note = 0; // also handles underflow
1004 
1005 							p->note = note;
1006 						}
1007 					}
1008 				}
1009 			}
1010 		}
1011 		break;
1012 
1013 		case TRANSP_BLOCK:
1014 		{
1015 			if (pattMark.markY1 == pattMark.markY2)
1016 				return; // no pattern marking
1017 
1018 			note_t *p = pattern[editor.editPattern];
1019 			if (p == NULL)
1020 				return; // empty pattern
1021 
1022 			p += (pattMark.markY1 * MAX_CHANNELS) + pattMark.markX1;
1023 
1024 			const int32_t pitch = MAX_CHANNELS - ((pattMark.markX2 + 1) - pattMark.markX1);
1025 			for (int32_t row = pattMark.markY1; row < pattMark.markY2; row++, p += pitch)
1026 			{
1027 				for (int32_t ch = pattMark.markX1; ch <= pattMark.markX2; ch++, p++)
1028 				{
1029 					uint8_t note = p->note;
1030 					if ((note >= 1 && note <= 96) && (!lastInsMode || p->instr == editor.curInstr))
1031 					{
1032 						note += lastTranspVal;
1033 						if (note > 96)
1034 							note = 0; // also handles underflow
1035 
1036 						p->note = note;
1037 					}
1038 				}
1039 			}
1040 		}
1041 		break;
1042 
1043 		default: break;
1044 	}
1045 
1046 	ui.updatePatternEditor = true;
1047 	setSongModifiedFlag();
1048 }
1049 
trackTranspCurInsUp(void)1050 void trackTranspCurInsUp(void)
1051 {
1052 	lastTranspMode = TRANSP_TRACK;
1053 	lastTranspVal = 1;
1054 	lastInsMode = TRANSP_CUR_INST;
1055 	doTranspose();
1056 }
1057 
trackTranspCurInsDn(void)1058 void trackTranspCurInsDn(void)
1059 {
1060 	lastTranspMode = TRANSP_TRACK;
1061 	lastTranspVal = -1;
1062 	lastInsMode = TRANSP_CUR_INST;
1063 	doTranspose();
1064 }
1065 
trackTranspCurIns12Up(void)1066 void trackTranspCurIns12Up(void)
1067 {
1068 	lastTranspMode = TRANSP_TRACK;
1069 	lastTranspVal = 12;
1070 	lastInsMode = TRANSP_CUR_INST;
1071 	doTranspose();
1072 }
1073 
trackTranspCurIns12Dn(void)1074 void trackTranspCurIns12Dn(void)
1075 {
1076 	lastTranspMode = TRANSP_TRACK;
1077 	lastTranspVal = -12;
1078 	lastInsMode = TRANSP_CUR_INST;
1079 	doTranspose();
1080 }
1081 
trackTranspAllInsUp(void)1082 void trackTranspAllInsUp(void)
1083 {
1084 	lastTranspMode = TRANSP_TRACK;
1085 	lastTranspVal = 1;
1086 	lastInsMode = TRANSP_ALL_INST;
1087 	doTranspose();
1088 }
1089 
trackTranspAllInsDn(void)1090 void trackTranspAllInsDn(void)
1091 {
1092 	lastTranspMode = TRANSP_TRACK;
1093 	lastTranspVal = -1;
1094 	lastInsMode = TRANSP_ALL_INST;
1095 	doTranspose();
1096 }
1097 
trackTranspAllIns12Up(void)1098 void trackTranspAllIns12Up(void)
1099 {
1100 	lastTranspMode = TRANSP_TRACK;
1101 	lastTranspVal = 12;
1102 	lastInsMode = TRANSP_ALL_INST;
1103 	doTranspose();
1104 }
1105 
trackTranspAllIns12Dn(void)1106 void trackTranspAllIns12Dn(void)
1107 {
1108 	lastTranspMode = TRANSP_TRACK;
1109 	lastTranspVal = -12;
1110 	lastInsMode = TRANSP_ALL_INST;
1111 	doTranspose();
1112 }
1113 
pattTranspCurInsUp(void)1114 void pattTranspCurInsUp(void)
1115 {
1116 	lastTranspMode = TRANSP_PATT;
1117 	lastTranspVal = 1;
1118 	lastInsMode = TRANSP_CUR_INST;
1119 	doTranspose();
1120 }
1121 
pattTranspCurInsDn(void)1122 void pattTranspCurInsDn(void)
1123 {
1124 	lastTranspMode = TRANSP_PATT;
1125 	lastTranspVal = -1;
1126 	lastInsMode = TRANSP_CUR_INST;
1127 	doTranspose();
1128 }
1129 
pattTranspCurIns12Up(void)1130 void pattTranspCurIns12Up(void)
1131 {
1132 	lastTranspMode = TRANSP_PATT;
1133 	lastTranspVal = 12;
1134 	lastInsMode = TRANSP_CUR_INST;
1135 	doTranspose();
1136 }
1137 
pattTranspCurIns12Dn(void)1138 void pattTranspCurIns12Dn(void)
1139 {
1140 	lastTranspMode = TRANSP_PATT;
1141 	lastTranspVal = -12;
1142 	lastInsMode = TRANSP_CUR_INST;
1143 	doTranspose();
1144 }
1145 
pattTranspAllInsUp(void)1146 void pattTranspAllInsUp(void)
1147 {
1148 	lastTranspMode = TRANSP_PATT;
1149 	lastTranspVal = 1;
1150 	lastInsMode = TRANSP_ALL_INST;
1151 	doTranspose();
1152 }
1153 
pattTranspAllInsDn(void)1154 void pattTranspAllInsDn(void)
1155 {
1156 	lastTranspMode = TRANSP_PATT;
1157 	lastTranspVal = -1;
1158 	lastInsMode = TRANSP_ALL_INST;
1159 	doTranspose();
1160 }
1161 
pattTranspAllIns12Up(void)1162 void pattTranspAllIns12Up(void)
1163 {
1164 	lastTranspMode = TRANSP_PATT;
1165 	lastTranspVal = 12;
1166 	lastInsMode = TRANSP_ALL_INST;
1167 	doTranspose();
1168 }
1169 
pattTranspAllIns12Dn(void)1170 void pattTranspAllIns12Dn(void)
1171 {
1172 	lastTranspMode = TRANSP_PATT;
1173 	lastTranspVal = -12;
1174 	lastInsMode = TRANSP_ALL_INST;
1175 	doTranspose();
1176 }
1177 
songTranspCurInsUp(void)1178 void songTranspCurInsUp(void)
1179 {
1180 	lastTranspMode = TRANSP_SONG;
1181 	lastTranspVal = 1;
1182 	lastInsMode = TRANSP_CUR_INST;
1183 	doTranspose();
1184 }
1185 
songTranspCurInsDn(void)1186 void songTranspCurInsDn(void)
1187 {
1188 	lastTranspMode = TRANSP_SONG;
1189 	lastTranspVal = -1;
1190 	lastInsMode = TRANSP_CUR_INST;
1191 	doTranspose();
1192 }
1193 
songTranspCurIns12Up(void)1194 void songTranspCurIns12Up(void)
1195 {
1196 	lastTranspMode = TRANSP_SONG;
1197 	lastTranspVal = 12;
1198 	lastInsMode = TRANSP_CUR_INST;
1199 	doTranspose();
1200 }
1201 
songTranspCurIns12Dn(void)1202 void songTranspCurIns12Dn(void)
1203 {
1204 	lastTranspMode = TRANSP_SONG;
1205 	lastTranspVal = -12;
1206 	lastInsMode = TRANSP_CUR_INST;
1207 	doTranspose();
1208 }
1209 
songTranspAllInsUp(void)1210 void songTranspAllInsUp(void)
1211 {
1212 	lastTranspMode = TRANSP_SONG;
1213 	lastTranspVal = 1;
1214 	lastInsMode = TRANSP_ALL_INST;
1215 	doTranspose();
1216 }
1217 
songTranspAllInsDn(void)1218 void songTranspAllInsDn(void)
1219 {
1220 	lastTranspMode = TRANSP_SONG;
1221 	lastTranspVal = -1;
1222 	lastInsMode = TRANSP_ALL_INST;
1223 	doTranspose();
1224 }
1225 
songTranspAllIns12Up(void)1226 void songTranspAllIns12Up(void)
1227 {
1228 	lastTranspMode = TRANSP_SONG;
1229 	lastTranspVal = 12;
1230 	lastInsMode = TRANSP_ALL_INST;
1231 	doTranspose();
1232 }
1233 
songTranspAllIns12Dn(void)1234 void songTranspAllIns12Dn(void)
1235 {
1236 	lastTranspMode = TRANSP_SONG;
1237 	lastTranspVal = -12;
1238 	lastInsMode = TRANSP_ALL_INST;
1239 	doTranspose();
1240 }
1241 
blockTranspCurInsUp(void)1242 void blockTranspCurInsUp(void)
1243 {
1244 	lastTranspMode = TRANSP_BLOCK;
1245 	lastTranspVal = 1;
1246 	lastInsMode = TRANSP_CUR_INST;
1247 	doTranspose();
1248 }
1249 
blockTranspCurInsDn(void)1250 void blockTranspCurInsDn(void)
1251 {
1252 	lastTranspMode = TRANSP_BLOCK;
1253 	lastTranspVal = -1;
1254 	lastInsMode = TRANSP_CUR_INST;
1255 	doTranspose();
1256 }
1257 
blockTranspCurIns12Up(void)1258 void blockTranspCurIns12Up(void)
1259 {
1260 	lastTranspMode = TRANSP_BLOCK;
1261 	lastTranspVal = 12;
1262 	lastInsMode = TRANSP_CUR_INST;
1263 	doTranspose();
1264 }
1265 
blockTranspCurIns12Dn(void)1266 void blockTranspCurIns12Dn(void)
1267 {
1268 	lastTranspMode = TRANSP_BLOCK;
1269 	lastTranspVal = -12;
1270 	lastInsMode = TRANSP_CUR_INST;
1271 	doTranspose();
1272 }
1273 
blockTranspAllInsUp(void)1274 void blockTranspAllInsUp(void)
1275 {
1276 	lastTranspMode = TRANSP_BLOCK;
1277 	lastTranspVal = 1;
1278 	lastInsMode = TRANSP_ALL_INST;
1279 	doTranspose();
1280 }
1281 
blockTranspAllInsDn(void)1282 void blockTranspAllInsDn(void)
1283 {
1284 	lastTranspMode = TRANSP_BLOCK;
1285 	lastTranspVal = -1;
1286 	lastInsMode = TRANSP_ALL_INST;
1287 	doTranspose();
1288 }
1289 
blockTranspAllIns12Up(void)1290 void blockTranspAllIns12Up(void)
1291 {
1292 	lastTranspMode = TRANSP_BLOCK;
1293 	lastTranspVal = 12;
1294 	lastInsMode = TRANSP_ALL_INST;
1295 	doTranspose();
1296 }
1297 
blockTranspAllIns12Dn(void)1298 void blockTranspAllIns12Dn(void)
1299 {
1300 	lastTranspMode = TRANSP_BLOCK;
1301 	lastTranspVal = -12;
1302 	lastInsMode = TRANSP_ALL_INST;
1303 	doTranspose();
1304 }
1305 
copyNote(note_t * src,note_t * dst)1306 void copyNote(note_t *src, note_t *dst)
1307 {
1308 	if (editor.copyMaskEnable)
1309 	{
1310 		if (editor.copyMask[0]) dst->note = src->note;
1311 		if (editor.copyMask[1]) dst->instr = src->instr;
1312 		if (editor.copyMask[2]) dst->vol = src->vol;
1313 		if (editor.copyMask[3]) dst->efx = src->efx;
1314 		if (editor.copyMask[4]) dst->efxData = src->efxData;
1315 	}
1316 	else
1317 	{
1318 		*dst = *src;
1319 	}
1320 }
1321 
pasteNote(note_t * src,note_t * dst)1322 void pasteNote(note_t *src, note_t *dst)
1323 {
1324 	if (editor.copyMaskEnable)
1325 	{
1326 		if (editor.copyMask[0] && (src->note    != 0 || !editor.transpMask[0])) dst->note = src->note;
1327 		if (editor.copyMask[1] && (src->instr   != 0 || !editor.transpMask[1])) dst->instr = src->instr;
1328 		if (editor.copyMask[2] && (src->vol     != 0 || !editor.transpMask[2])) dst->vol = src->vol;
1329 		if (editor.copyMask[3] && (src->efx     != 0 || !editor.transpMask[3])) dst->efx = src->efx;
1330 		if (editor.copyMask[4] && (src->efxData != 0 || !editor.transpMask[4])) dst->efxData = src->efxData;
1331 	}
1332 	else
1333 	{
1334 		*dst = *src;
1335 	}
1336 }
1337 
cutTrack(void)1338 void cutTrack(void)
1339 {
1340 	note_t *p = pattern[editor.editPattern];
1341 	if (p == NULL)
1342 		return;
1343 
1344 	const int16_t numRows = patternNumRows[editor.editPattern];
1345 
1346 	if (config.ptnCutToBuffer)
1347 	{
1348 		memset(trackCopyBuff, 0, MAX_PATT_LEN * sizeof (note_t));
1349 		for (int16_t i = 0; i < numRows; i++)
1350 			copyNote(&p[(i * MAX_CHANNELS) + cursor.ch], &trackCopyBuff[i]);
1351 
1352 		trkBufLen = numRows;
1353 	}
1354 
1355 	pauseMusic();
1356 	for (int16_t i = 0; i < numRows; i++)
1357 		pasteNote(&clearNote, &p[(i * MAX_CHANNELS) + cursor.ch]);
1358 	resumeMusic();
1359 
1360 	killPatternIfUnused(editor.editPattern);
1361 
1362 	ui.updatePatternEditor = true;
1363 	setSongModifiedFlag();
1364 }
1365 
copyTrack(void)1366 void copyTrack(void)
1367 {
1368 	note_t *p = pattern[editor.editPattern];
1369 	if (p == NULL)
1370 		return;
1371 
1372 	const int16_t numRows = patternNumRows[editor.editPattern];
1373 
1374 	memset(trackCopyBuff, 0, MAX_PATT_LEN * sizeof (note_t));
1375 	for (int16_t i = 0; i < numRows; i++)
1376 		copyNote(&p[(i * MAX_CHANNELS) + cursor.ch], &trackCopyBuff[i]);
1377 
1378 	trkBufLen = numRows;
1379 }
1380 
pasteTrack(void)1381 void pasteTrack(void)
1382 {
1383 	if (trkBufLen == 0 || !allocatePattern(editor.editPattern))
1384 		return;
1385 
1386 	note_t *p = pattern[editor.editPattern];
1387 	const int16_t numRows = patternNumRows[editor.editPattern];
1388 
1389 	pauseMusic();
1390 	for (int16_t i = 0; i < numRows; i++)
1391 		pasteNote(&trackCopyBuff[i], &p[(i * MAX_CHANNELS) + cursor.ch]);
1392 	resumeMusic();
1393 
1394 	killPatternIfUnused(editor.editPattern);
1395 
1396 	ui.updatePatternEditor = true;
1397 	setSongModifiedFlag();
1398 }
1399 
cutPattern(void)1400 void cutPattern(void)
1401 {
1402 	note_t *p = pattern[editor.editPattern];
1403 	if (p == NULL)
1404 		return;
1405 
1406 	const int16_t numRows = patternNumRows[editor.editPattern];
1407 
1408 	if (config.ptnCutToBuffer)
1409 	{
1410 		memset(ptnCopyBuff, 0, (MAX_PATT_LEN * MAX_CHANNELS) * sizeof (note_t));
1411 		for (int16_t x = 0; x < song.numChannels; x++)
1412 		{
1413 			for (int16_t i = 0; i < numRows; i++)
1414 				copyNote(&p[(i * MAX_CHANNELS) + x], &ptnCopyBuff[(i * MAX_CHANNELS) + x]);
1415 		}
1416 
1417 		ptnBufLen = numRows;
1418 	}
1419 
1420 	pauseMusic();
1421 	for (int16_t x = 0; x < song.numChannels; x++)
1422 	{
1423 		for (int16_t i = 0; i < numRows; i++)
1424 			pasteNote(&clearNote, &p[(i * MAX_CHANNELS) + x]);
1425 	}
1426 	resumeMusic();
1427 
1428 	killPatternIfUnused(editor.editPattern);
1429 
1430 	ui.updatePatternEditor = true;
1431 	setSongModifiedFlag();
1432 }
1433 
copyPattern(void)1434 void copyPattern(void)
1435 {
1436 	note_t *p = pattern[editor.editPattern];
1437 	if (p == NULL)
1438 		return;
1439 
1440 	const int16_t numRows = patternNumRows[editor.editPattern];
1441 
1442 	memset(ptnCopyBuff, 0, (MAX_PATT_LEN * MAX_CHANNELS) * sizeof (note_t));
1443 	for (int16_t x = 0; x < song.numChannels; x++)
1444 	{
1445 		for (int16_t i = 0; i < numRows; i++)
1446 			copyNote(&p[(i * MAX_CHANNELS) + x], &ptnCopyBuff[(i * MAX_CHANNELS) + x]);
1447 	}
1448 
1449 	ptnBufLen = numRows;
1450 
1451 	ui.updatePatternEditor = true;
1452 }
1453 
pastePattern(void)1454 void pastePattern(void)
1455 {
1456 	if (ptnBufLen == 0)
1457 		return;
1458 
1459 	if (patternNumRows[editor.editPattern] != ptnBufLen)
1460 	{
1461 		if (okBox(1, "System request", "Change pattern length to copybuffer's length?") == 1)
1462 			setPatternLen(editor.editPattern, ptnBufLen);
1463 	}
1464 
1465 	if (!allocatePattern(editor.editPattern))
1466 		return;
1467 
1468 	note_t *p = pattern[editor.editPattern];
1469 	const int16_t numRows = patternNumRows[editor.editPattern];
1470 
1471 	pauseMusic();
1472 	for (int16_t x = 0; x < song.numChannels; x++)
1473 	{
1474 		for (int16_t i = 0; i < numRows; i++)
1475 			pasteNote(&ptnCopyBuff[(i * MAX_CHANNELS) + x], &p[(i * MAX_CHANNELS) + x]);
1476 	}
1477 	resumeMusic();
1478 
1479 	killPatternIfUnused(editor.editPattern);
1480 
1481 	ui.updatePatternEditor = true;
1482 	setSongModifiedFlag();
1483 }
1484 
cutBlock(void)1485 void cutBlock(void)
1486 {
1487 	if (pattMark.markY1 == pattMark.markY2 || pattMark.markY1 > pattMark.markY2)
1488 		return;
1489 
1490 	note_t *p = pattern[editor.editPattern];
1491 	if (p == NULL)
1492 		return;
1493 
1494 	if (config.ptnCutToBuffer)
1495 	{
1496 		for (int16_t x = pattMark.markX1; x <= pattMark.markX2; x++)
1497 		{
1498 			for (int16_t y = pattMark.markY1; y < pattMark.markY2; y++)
1499 			{
1500 				assert(x < song.numChannels && y < patternNumRows[editor.editPattern]);
1501 				copyNote(&p[(y * MAX_CHANNELS) + x], &blkCopyBuff[((y - pattMark.markY1) * MAX_CHANNELS) + (x - pattMark.markX1)]);
1502 			}
1503 		}
1504 	}
1505 
1506 	pauseMusic();
1507 	for (int16_t x = pattMark.markX1; x <= pattMark.markX2; x++)
1508 	{
1509 		for (int16_t y = pattMark.markY1; y < pattMark.markY2; y++)
1510 			pasteNote(&clearNote, &p[(y * MAX_CHANNELS) + x]);
1511 	}
1512 	resumeMusic();
1513 
1514 	markXSize = pattMark.markX2 - pattMark.markX1;
1515 	markYSize = pattMark.markY2 - pattMark.markY1;
1516 	blockCopied = true;
1517 
1518 	killPatternIfUnused(editor.editPattern);
1519 
1520 	ui.updatePatternEditor = true;
1521 	setSongModifiedFlag();
1522 }
1523 
copyBlock(void)1524 void copyBlock(void)
1525 {
1526 	if (pattMark.markY1 == pattMark.markY2 || pattMark.markY1 > pattMark.markY2)
1527 		return;
1528 
1529 	note_t *p = pattern[editor.editPattern];
1530 	if (p == NULL)
1531 		return;
1532 
1533 	for (int16_t x = pattMark.markX1; x <= pattMark.markX2; x++)
1534 	{
1535 		for (int16_t y = pattMark.markY1; y < pattMark.markY2; y++)
1536 		{
1537 			assert(x < song.numChannels && y < patternNumRows[editor.editPattern]);
1538 			copyNote(&p[(y * MAX_CHANNELS) + x], &blkCopyBuff[((y - pattMark.markY1) * MAX_CHANNELS) + (x - pattMark.markX1)]);
1539 		}
1540 	}
1541 
1542 	markXSize = pattMark.markX2 - pattMark.markX1;
1543 	markYSize = pattMark.markY2 - pattMark.markY1;
1544 	blockCopied = true;
1545 }
1546 
pasteBlock(void)1547 void pasteBlock(void)
1548 {
1549 	if (!blockCopied || !allocatePattern(editor.editPattern))
1550 		return;
1551 
1552 	const int16_t numRows = patternNumRows[editor.editPattern];
1553 
1554 	const int32_t xpos = cursor.ch;
1555 	const int32_t ypos = editor.row;
1556 
1557 	int32_t j = markXSize;
1558 	if (j+xpos >= song.numChannels)
1559 		j = song.numChannels - xpos - 1;
1560 
1561 	int32_t k = markYSize;
1562 	if (k+ypos >= numRows)
1563 		k = numRows-ypos;
1564 
1565 	note_t *p = pattern[editor.editPattern];
1566 
1567 	pauseMusic();
1568 	for (int32_t x = xpos; x <= xpos+j; x++)
1569 	{
1570 		for (int32_t y = ypos; y < ypos+k; y++)
1571 		{
1572 			assert(x < song.numChannels && y < numRows);
1573 			pasteNote(&blkCopyBuff[((y - ypos) * MAX_CHANNELS) + (x - xpos)], &p[(y * MAX_CHANNELS) + x]);
1574 		}
1575 	}
1576 	resumeMusic();
1577 
1578 	killPatternIfUnused(editor.editPattern);
1579 
1580 	ui.updatePatternEditor = true;
1581 	setSongModifiedFlag();
1582 }
1583 
remapInstrXY(uint16_t pattNum,uint16_t x1,uint16_t y1,uint16_t x2,uint16_t y2,uint8_t src,uint8_t dst)1584 static void remapInstrXY(uint16_t pattNum, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint8_t src, uint8_t dst)
1585 {
1586 	// this routine is only used sanely, so no need to check input
1587 
1588 	note_t *pattPtr = pattern[pattNum];
1589 	if (pattPtr == NULL)
1590 		return;
1591 
1592 	note_t *p = &pattPtr[(y1 * MAX_CHANNELS) + x1];
1593 
1594 	const int32_t pitch = MAX_CHANNELS - ((x2 + 1) - x1);
1595 	for (uint16_t y = y1; y <= y2; y++, p += pitch)
1596 	{
1597 		for (uint16_t x = x1; x <= x2; x++, p++)
1598 		{
1599 			if (p->instr == src)
1600 				p->instr = dst;
1601 		}
1602 	}
1603 }
1604 
remapBlock(void)1605 void remapBlock(void)
1606 {
1607 	if (editor.srcInstr == editor.curInstr || pattMark.markY1 == pattMark.markY2 || pattMark.markY1 > pattMark.markY2)
1608 		return;
1609 
1610 	pauseMusic();
1611 	remapInstrXY(editor.editPattern,
1612 	             pattMark.markX1, pattMark.markY1,
1613 	             pattMark.markX2, pattMark.markY2 - 1,
1614 	             editor.srcInstr, editor.curInstr);
1615 	resumeMusic();
1616 
1617 	ui.updatePatternEditor = true;
1618 	setSongModifiedFlag();
1619 }
1620 
remapTrack(void)1621 void remapTrack(void)
1622 {
1623 	if (editor.srcInstr == editor.curInstr)
1624 		return;
1625 
1626 	pauseMusic();
1627 	remapInstrXY(editor.editPattern,
1628 	             cursor.ch, 0,
1629 	             cursor.ch, patternNumRows[editor.editPattern] - 1,
1630 	             editor.srcInstr, editor.curInstr);
1631 	resumeMusic();
1632 
1633 	ui.updatePatternEditor = true;
1634 	setSongModifiedFlag();
1635 }
1636 
remapPattern(void)1637 void remapPattern(void)
1638 {
1639 	if (editor.srcInstr == editor.curInstr)
1640 		return;
1641 
1642 	pauseMusic();
1643 	remapInstrXY(editor.editPattern,
1644 	             0, 0,
1645 	             (uint16_t)(song.numChannels - 1), patternNumRows[editor.editPattern] - 1,
1646 	             editor.srcInstr, editor.curInstr);
1647 	resumeMusic();
1648 
1649 	ui.updatePatternEditor = true;
1650 	setSongModifiedFlag();
1651 }
1652 
remapSong(void)1653 void remapSong(void)
1654 {
1655 	if (editor.srcInstr == editor.curInstr)
1656 		return;
1657 
1658 	pauseMusic();
1659 	for (int32_t i = 0; i < MAX_PATTERNS; i++)
1660 	{
1661 		const uint8_t pattNum = (uint8_t)i;
1662 
1663 		remapInstrXY(pattNum,
1664 		             0, 0,
1665 		             (uint16_t)(song.numChannels - 1), patternNumRows[pattNum] - 1,
1666 		             editor.srcInstr, editor.curInstr);
1667 	}
1668 	resumeMusic();
1669 
1670 	ui.updatePatternEditor = true;
1671 	setSongModifiedFlag();
1672 }
1673 
1674 // "scale-fade volume" routines
1675 
getNoteVolume(note_t * p)1676 static int8_t getNoteVolume(note_t *p)
1677 {
1678 	int8_t nv, vv, ev;
1679 
1680 	if (p->vol >= 0x10 && p->vol <= 0x50)
1681 		vv = p->vol - 0x10;
1682 	else
1683 		vv = -1;
1684 
1685 	if (p->efx == 0xC)
1686 		ev = MIN(p->efxData, 64);
1687 	else
1688 		ev = -1;
1689 
1690 	if (p->instr != 0 && instr[p->instr] != NULL)
1691 		nv = (int8_t)instr[p->instr]->smp[0].volume;
1692 	else
1693 		nv = -1;
1694 
1695 	int8_t finalv = -1;
1696 	if (nv >= 0) finalv = nv;
1697 	if (vv >= 0) finalv = vv;
1698 	if (ev >= 0) finalv = ev;
1699 
1700 	return finalv;
1701 }
1702 
setNoteVolume(note_t * p,int8_t newVol)1703 static void setNoteVolume(note_t *p, int8_t newVol)
1704 {
1705 	if (newVol < 0)
1706 		return;
1707 
1708 	const int8_t oldv = getNoteVolume(p);
1709 	if (p->vol == oldv)
1710 		return; // volume is the same
1711 
1712 	if (p->efx == 0x0C)
1713 		p->efxData = newVol; // Cxx effect
1714 	else
1715 		p->vol = 0x10 + newVol; // volume column
1716 }
1717 
scaleNote(uint16_t pattNum,int8_t ch,int16_t row,double dScale)1718 static void scaleNote(uint16_t pattNum, int8_t ch, int16_t row, double dScale)
1719 {
1720 	if (pattern[pattNum] == NULL)
1721 		return;
1722 
1723 	const int16_t numRows = patternNumRows[pattNum];
1724 	if (row < 0 || row >= numRows || ch < 0 || ch >= song.numChannels)
1725 		return;
1726 
1727 	note_t *p = &pattern[pattNum][(row * MAX_CHANNELS) + ch];
1728 
1729 	int32_t vol = getNoteVolume(p);
1730 	if (vol >= 0)
1731 	{
1732 		vol = (int32_t)((vol * dScale) + 0.5); // rounded
1733 		vol = MIN(MAX(0, vol), 64);
1734 		setNoteVolume(p, (int8_t)vol);
1735 	}
1736 }
1737 
askForScaleFade(char * msg)1738 static bool askForScaleFade(char *msg)
1739 {
1740 	char volstr[32+1];
1741 
1742 	sprintf(volstr, "%0.2f,%0.2f", dVolScaleFK1, dVolScaleFK2);
1743 	if (inputBox(1, msg, volstr, sizeof (volstr) - 1) != 1)
1744 		return false;
1745 
1746 	bool err = false;
1747 
1748 	char *val1 = volstr;
1749 	if (strlen(val1) < 3)
1750 		err = true;
1751 
1752 	char *val2 = strchr(volstr, ',');
1753 	if (val2 == NULL || strlen(val2) < 3)
1754 		err = true;
1755 
1756 	if (err)
1757 	{
1758 		okBox(0, "System message", "Invalid constant expressions.");
1759 		return false;
1760 	}
1761 
1762 	dVolScaleFK1 = atof(val1+0);
1763 	dVolScaleFK2 = atof(val2+1);
1764 
1765 	return true;
1766 }
1767 
scaleFadeVolumeTrack(void)1768 void scaleFadeVolumeTrack(void)
1769 {
1770 	if (!askForScaleFade("Volume scale-fade track (start-, end scale)"))
1771 		return;
1772 
1773 	if (pattern[editor.editPattern] == NULL)
1774 		return;
1775 
1776 	const int32_t numRows = patternNumRows[editor.editPattern];
1777 
1778 	double dVolDelta = 0.0;
1779 	if (numRows > 0)
1780 		dVolDelta = (dVolScaleFK2 - dVolScaleFK1) / numRows;
1781 
1782 	double dVol = dVolScaleFK1;
1783 
1784 	pauseMusic();
1785 	for (int16_t row = 0; row < numRows; row++)
1786 	{
1787 		scaleNote(editor.editPattern, cursor.ch, row, dVol);
1788 		dVol += dVolDelta;
1789 	}
1790 	resumeMusic();
1791 }
1792 
scaleFadeVolumePattern(void)1793 void scaleFadeVolumePattern(void)
1794 {
1795 	if (!askForScaleFade("Volume scale-fade pattern (start-, end scale)"))
1796 		return;
1797 
1798 	if (pattern[editor.editPattern] == NULL)
1799 		return;
1800 
1801 	const int32_t numRows = patternNumRows[editor.editPattern];
1802 
1803 	double dVolDelta = 0.0;
1804 	if (numRows > 0)
1805 		dVolDelta = (dVolScaleFK2 - dVolScaleFK1) / numRows;
1806 
1807 	double dVol = dVolScaleFK1;
1808 
1809 	pauseMusic();
1810 	for (int16_t row = 0; row < numRows; row++)
1811 	{
1812 		for (int8_t ch = 0; ch < song.numChannels; ch++)
1813 			scaleNote(editor.editPattern, ch, row, dVol);
1814 
1815 		dVol += dVolDelta;
1816 	}
1817 	resumeMusic();
1818 }
1819 
scaleFadeVolumeBlock(void)1820 void scaleFadeVolumeBlock(void)
1821 {
1822 	if (!askForScaleFade("Volume scale-fade block (start-, end scale)"))
1823 		return;
1824 
1825 	if (pattern[editor.editPattern] == NULL || pattMark.markY1 == pattMark.markY2 || pattMark.markY1 > pattMark.markY2)
1826 		return;
1827 
1828 	const int32_t numRows = pattMark.markY2 - pattMark.markY1;
1829 
1830 	double dVolDelta = 0.0;
1831 	if (numRows > 0)
1832 		dVolDelta = (dVolScaleFK2 - dVolScaleFK1) / numRows;
1833 
1834 	double dVol = dVolScaleFK1;
1835 
1836 	pauseMusic();
1837 	for (int16_t row = pattMark.markY1; row < pattMark.markY2; row++)
1838 	{
1839 		for (int16_t ch = pattMark.markX1; ch <= pattMark.markX2; ch++)
1840 			scaleNote(editor.editPattern, (uint8_t)ch, row, dVol);
1841 
1842 		dVol += dVolDelta;
1843 	}
1844 	resumeMusic();
1845 }
1846 
toggleCopyMaskEnable(void)1847 void toggleCopyMaskEnable(void) { editor.copyMaskEnable ^= 1; }
toggleCopyMask0(void)1848 void toggleCopyMask0(void) { editor.copyMask[0] ^= 1; };
toggleCopyMask1(void)1849 void toggleCopyMask1(void) { editor.copyMask[1] ^= 1; };
toggleCopyMask2(void)1850 void toggleCopyMask2(void) { editor.copyMask[2] ^= 1; };
toggleCopyMask3(void)1851 void toggleCopyMask3(void) { editor.copyMask[3] ^= 1; };
toggleCopyMask4(void)1852 void toggleCopyMask4(void) { editor.copyMask[4] ^= 1; };
togglePasteMask0(void)1853 void togglePasteMask0(void) { editor.pasteMask[0] ^= 1; };
togglePasteMask1(void)1854 void togglePasteMask1(void) { editor.pasteMask[1] ^= 1; };
togglePasteMask2(void)1855 void togglePasteMask2(void) { editor.pasteMask[2] ^= 1; };
togglePasteMask3(void)1856 void togglePasteMask3(void) { editor.pasteMask[3] ^= 1; };
togglePasteMask4(void)1857 void togglePasteMask4(void) { editor.pasteMask[4] ^= 1; };
toggleTranspMask0(void)1858 void toggleTranspMask0(void) { editor.transpMask[0] ^= 1; };
toggleTranspMask1(void)1859 void toggleTranspMask1(void) { editor.transpMask[1] ^= 1; };
toggleTranspMask2(void)1860 void toggleTranspMask2(void) { editor.transpMask[2] ^= 1; };
toggleTranspMask3(void)1861 void toggleTranspMask3(void) { editor.transpMask[3] ^= 1; };
toggleTranspMask4(void)1862 void toggleTranspMask4(void) { editor.transpMask[4] ^= 1; };
1863