@@ -5,8 +5,8 @@ unit TrackerGrid; |
| 5 |
interface |
5 |
interface |
| 6 |
|
6 |
|
| 7 |
uses |
7 |
uses |
| 8 |
Classes, SysUtils, Controls, Graphics, Constants, LCLType, math, contnrs, |
8 |
Classes, SysUtils, Controls, Graphics, Constants, LCLType, math, |
| 9 |
LCLIntf, LMessages, HugeDatatypes, ClipboardUtils; |
9 |
LCLIntf, LMessages, HugeDatatypes, ClipboardUtils, gdeque, gstack, utils; |
| 10 |
|
10 |
|
| 11 |
// TODO: Maybe read these from a config file |
11 |
// TODO: Maybe read these from a config file |
| 12 |
const |
12 |
const |
@@ -25,6 +25,7 @@ const |
| 25 |
|
25 |
|
| 26 |
NUM_COLUMNS = 4; |
26 |
NUM_COLUMNS = 4; |
| 27 |
NUM_ROWS = 64; |
27 |
NUM_ROWS = 64; |
|
|
28 |
UNDO_STACK_SIZE = 100; |
| 28 |
|
29 |
|
| 29 |
type |
30 |
type |
| 30 |
|
31 |
|
@@ -36,12 +37,16 @@ type |
| 36 |
end; |
37 |
end; |
| 37 |
|
38 |
|
| 38 |
TUndoRedoAction = array[0..3] of TSavedPattern; |
39 |
TUndoRedoAction = array[0..3] of TSavedPattern; |
| 39 |
TUndoRedoStack = TObjectStack; |
40 |
TUndoDeque = TDeque<TUndoRedoAction>; |
|
|
41 |
TRedoStack = TStack<TUndoRedoAction>; |
| 40 |
|
42 |
|
| 41 |
{ TTrackerGrid } |
43 |
{ TTrackerGrid } |
| 42 |
|
44 |
|
| 43 |
TTrackerGrid = class(TCustomControl) |
45 |
TTrackerGrid = class(TCustomControl) |
| 44 |
constructor Create(AOwner: TComponent; Parent: TWinControl); |
46 |
constructor Create( |
|
|
47 |
AOwner: TComponent; |
|
|
48 |
Parent: TWinControl; |
|
|
49 |
PatternMap: TPatternMap); reintroduce; |
| 45 |
|
50 |
|
| 46 |
procedure Paint; override; |
51 |
procedure Paint; override; |
| 47 |
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); |
52 |
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); |
@@ -85,7 +90,9 @@ type |
| 85 |
function KeycodeToHexNumber(Key: Word; out Num: Nibble): Boolean; overload; |
90 |
function KeycodeToHexNumber(Key: Word; out Num: Nibble): Boolean; overload; |
| 86 |
function KeycodeToHexNumber(Key: Word; out Num: Integer): Boolean; overload; |
91 |
function KeycodeToHexNumber(Key: Word; out Num: Integer): Boolean; overload; |
| 87 |
private |
92 |
private |
|
|
93 |
PatternMap: TPatternMap; |
| 88 |
Patterns: array[0..3] of PPattern; |
94 |
Patterns: array[0..3] of PPattern; |
|
|
95 |
PatternNumbers: array[0..3] of Integer; |
| 89 |
|
96 |
|
| 90 |
CharHeight: Integer; |
97 |
CharHeight: Integer; |
| 91 |
CharWidth: Integer; |
98 |
CharWidth: Integer; |
@@ -93,7 +100,8 @@ type |
| 93 |
MouseButtonDown: Boolean; |
100 |
MouseButtonDown: Boolean; |
| 94 |
Selecting: Boolean; |
101 |
Selecting: Boolean; |
| 95 |
|
102 |
|
| 96 |
Performed, Recall: TUndoRedoStack; |
103 |
Performed: TUndoDeque; |
|
|
104 |
Recall: TRedoStack; |
| 97 |
|
105 |
|
| 98 |
DigitInputting: Boolean; |
106 |
DigitInputting: Boolean; |
| 99 |
|
107 |
|
@@ -104,7 +112,7 @@ type |
| 104 |
|
112 |
|
| 105 |
property HighlightedRow: Integer read FHighlightedRow write SetHighlightedRow; |
113 |
property HighlightedRow: Integer read FHighlightedRow write SetHighlightedRow; |
| 106 |
property SelectionGridRect: TRect read GetSelectionGridRect write SetSelectionGridRect; |
114 |
property SelectionGridRect: TRect read GetSelectionGridRect write SetSelectionGridRect; |
| 107 |
procedure LoadPattern(Idx: Integer; Pat: PPattern); |
115 |
procedure LoadPattern(Idx: Integer; PatternNumber: Integer); |
| 108 |
|
116 |
|
| 109 |
function GetAt(SelectionPos: TSelectionPos): Integer; |
117 |
function GetAt(SelectionPos: TSelectionPos): Integer; |
| 110 |
procedure SetAt(SelectionPos: TSelectionPos; Value: Integer); |
118 |
procedure SetAt(SelectionPos: TSelectionPos; Value: Integer); |
@@ -117,12 +125,15 @@ implementation |
| 117 |
|
125 |
|
| 118 |
constructor TTrackerGrid.Create( |
126 |
constructor TTrackerGrid.Create( |
| 119 |
AOwner: TComponent; |
127 |
AOwner: TComponent; |
| 120 |
Parent: TWinControl); |
128 |
Parent: TWinControl; |
|
|
129 |
PatternMap: TPatternMap); |
| 121 |
begin |
130 |
begin |
| 122 |
inherited Create(AOwner); |
131 |
inherited Create(AOwner); |
| 123 |
|
132 |
|
| 124 |
Performed := TUndoRedoStack.Create; |
133 |
Self.PatternMap := PatternMap; |
| 125 |
Recall := TUndoRedoStack.Create; |
134 |
|
|
|
135 |
Performed := TUndoDeque.Create; |
|
|
136 |
Recall := TRedoStack.Create; |
| 126 |
|
137 |
|
| 127 |
DoubleBuffered := True; |
138 |
DoubleBuffered := True; |
| 128 |
ControlStyle := ControlStyle + [csCaptureMouse, csClickEvents, csDoubleClicks]; |
139 |
ControlStyle := ControlStyle + [csCaptureMouse, csClickEvents, csDoubleClicks]; |
@@ -235,6 +246,8 @@ begin |
| 235 |
inherited KeyDown(Key, Shift); |
246 |
inherited KeyDown(Key, Shift); |
| 236 |
|
247 |
|
| 237 |
case Key of |
248 |
case Key of |
|
|
249 |
VK_Z: if ssCtrl in Shift then DoUndo; |
|
|
250 |
VK_Y: if ssCtrl in Shift then DoRedo; |
| 238 |
VK_V: if ssShift in Shift then DoRepeatPaste; |
251 |
VK_V: if ssShift in Shift then DoRepeatPaste; |
| 239 |
VK_L: begin |
252 |
VK_L: begin |
| 240 |
if ssCtrl in Shift then begin |
253 |
if ssCtrl in Shift then begin |
@@ -272,7 +285,7 @@ begin |
| 272 |
DigitInputting := False; |
285 |
DigitInputting := False; |
| 273 |
end; |
286 |
end; |
| 274 |
else begin |
287 |
else begin |
| 275 |
if not (ssCtrl in Shift) then |
288 |
if (not (ssCtrl in Shift)) and (not (ssShift in Shift)) then |
| 276 |
case Cursor.SelectedPart of |
289 |
case Cursor.SelectedPart of |
| 277 |
cpNote: InputNote(Key); |
290 |
cpNote: InputNote(Key); |
| 278 |
cpInstrument: InputInstrument(Key); |
291 |
cpInstrument: InputInstrument(Key); |
@@ -312,8 +325,6 @@ begin |
| 312 |
on E: Exception do |
325 |
on E: Exception do |
| 313 |
WriteLn(StdErr, 'Clipboard did not contain valid note data!'); |
326 |
WriteLn(StdErr, 'Clipboard did not contain valid note data!'); |
| 314 |
end; |
327 |
end; |
| 315 |
|
|
|
| 316 |
Invalidate; |
|
|
| 317 |
end; |
328 |
end; |
| 318 |
|
329 |
|
| 319 |
procedure TTrackerGrid.PerformCopy; |
330 |
procedure TTrackerGrid.PerformCopy; |
@@ -333,11 +344,17 @@ begin |
| 333 |
PerformPaste(Selection); |
344 |
PerformPaste(Selection); |
| 334 |
Inc(I, High(Selection)+1); |
345 |
Inc(I, High(Selection)+1); |
| 335 |
end; |
346 |
end; |
|
|
347 |
|
|
|
348 |
Invalidate; |
|
|
349 |
SaveUndoState; |
| 336 |
end; |
350 |
end; |
| 337 |
|
351 |
|
| 338 |
procedure TTrackerGrid.DoPaste(var Msg: TLMessage); |
352 |
procedure TTrackerGrid.DoPaste(var Msg: TLMessage); |
| 339 |
begin |
353 |
begin |
| 340 |
PerformPaste(GetPastedCells); |
354 |
PerformPaste(GetPastedCells); |
|
|
355 |
|
|
|
356 |
Invalidate; |
|
|
357 |
SaveUndoState; |
| 341 |
end; |
358 |
end; |
| 342 |
|
359 |
|
| 343 |
procedure TTrackerGrid.DoCopy(var Msg: TLMessage); |
360 |
procedure TTrackerGrid.DoCopy(var Msg: TLMessage); |
@@ -365,18 +382,66 @@ begin |
| 365 |
end; |
382 |
end; |
| 366 |
|
383 |
|
| 367 |
procedure TTrackerGrid.SaveUndoState; |
384 |
procedure TTrackerGrid.SaveUndoState; |
|
|
385 |
var |
|
|
386 |
Actn: TUndoRedoAction; |
|
|
387 |
I: Integer; |
| 368 |
begin |
388 |
begin |
|
|
389 |
// First, clear out the recall stack |
|
|
390 |
while not Recall.IsEmpty do |
|
|
391 |
Recall.Pop; |
| 369 |
|
392 |
|
|
|
393 |
// Create the state we need to save |
|
|
394 |
for I := Low(Patterns) to High(Patterns) do |
|
|
395 |
with Actn[I] do begin |
|
|
396 |
Pattern := Patterns[I]^; |
|
|
397 |
PatternNumber := PatternNumbers[I]; |
|
|
398 |
end; |
|
|
399 |
|
|
|
400 |
// Push it into the performed queue |
|
|
401 |
Performed.PushFront(Actn); |
|
|
402 |
while Performed.Size > UNDO_STACK_SIZE do |
|
|
403 |
Performed.PopBack; |
| 370 |
end; |
404 |
end; |
| 371 |
|
405 |
|
| 372 |
procedure TTrackerGrid.DoUndo; |
406 |
procedure TTrackerGrid.DoUndo; |
|
|
407 |
var |
|
|
408 |
OldState, NewState: TUndoRedoAction; |
|
|
409 |
I: Integer; |
| 373 |
begin |
410 |
begin |
|
|
411 |
if Performed.IsEmpty then Exit; |
|
|
412 |
|
|
|
413 |
OldState := Performed.Front; |
|
|
414 |
Performed.PopFront; |
|
|
415 |
NewState := Performed.Front; |
| 374 |
|
416 |
|
|
|
417 |
Recall.Push(OldState); |
|
|
418 |
|
|
|
419 |
for I := Low(NewState) to High(NewState) do begin |
|
|
420 |
LoadPattern(I, NewState[I].PatternNumber); |
|
|
421 |
Patterns[I]^ := NewState[I].Pattern; |
|
|
422 |
end; |
|
|
423 |
|
|
|
424 |
Invalidate; |
| 375 |
end; |
425 |
end; |
| 376 |
|
426 |
|
| 377 |
procedure TTrackerGrid.DoRedo; |
427 |
procedure TTrackerGrid.DoRedo; |
|
|
428 |
var |
|
|
429 |
State: TUndoRedoAction; |
|
|
430 |
I: Integer; |
| 378 |
begin |
431 |
begin |
|
|
432 |
if Recall.IsEmpty then Exit; |
|
|
433 |
|
|
|
434 |
State := Recall.Top; |
|
|
435 |
Recall.Pop; |
| 379 |
|
436 |
|
|
|
437 |
Performed.PushFront(State); |
|
|
438 |
|
|
|
439 |
for I := Low(State) to High(State) do begin |
|
|
440 |
LoadPattern(I, State[I].PatternNumber); |
|
|
441 |
Patterns[I]^ := State[I].Pattern; |
|
|
442 |
end; |
|
|
443 |
|
|
|
444 |
Invalidate; |
| 380 |
end; |
445 |
end; |
| 381 |
|
446 |
|
| 382 |
procedure TTrackerGrid.RenderSelectedArea; |
447 |
procedure TTrackerGrid.RenderSelectedArea; |
@@ -443,6 +508,9 @@ begin |
| 443 |
IncSelectionPos(X); |
508 |
IncSelectionPos(X); |
| 444 |
end; |
509 |
end; |
| 445 |
end; |
510 |
end; |
|
|
511 |
|
|
|
512 |
Invalidate; |
|
|
513 |
SaveUndoState; |
| 446 |
end; |
514 |
end; |
| 447 |
|
515 |
|
| 448 |
procedure TTrackerGrid.InputNote(Key: Word); |
516 |
procedure TTrackerGrid.InputNote(Key: Word); |
@@ -456,6 +524,9 @@ begin |
| 456 |
Keybindings.TryGetData(Key, Temp); |
524 |
Keybindings.TryGetData(Key, Temp); |
| 457 |
if Temp <> 0 then Note := Temp; |
525 |
if Temp <> 0 then Note := Temp; |
| 458 |
end; |
526 |
end; |
|
|
527 |
|
|
|
528 |
Invalidate; |
|
|
529 |
SaveUndoState; |
| 459 |
end; |
530 |
end; |
| 460 |
|
531 |
|
| 461 |
procedure TTrackerGrid.InputInstrument(Key: Word); |
532 |
procedure TTrackerGrid.InputInstrument(Key: Word); |
@@ -466,6 +537,9 @@ begin |
| 466 |
if Key = VK_DELETE then Instrument := 0 |
537 |
if Key = VK_DELETE then Instrument := 0 |
| 467 |
else if KeycodeToHexNumber(Key, Temp) and (Temp <= 9) then |
538 |
else if KeycodeToHexNumber(Key, Temp) and (Temp <= 9) then |
| 468 |
Instrument := ((Instrument mod 10) * 10) + Temp; |
539 |
Instrument := ((Instrument mod 10) * 10) + Temp; |
|
|
540 |
|
|
|
541 |
Invalidate; |
|
|
542 |
SaveUndoState; |
| 469 |
end; |
543 |
end; |
| 470 |
|
544 |
|
| 471 |
procedure TTrackerGrid.InputVolume(Key: Word); |
545 |
procedure TTrackerGrid.InputVolume(Key: Word); |
@@ -481,6 +555,9 @@ begin |
| 481 |
EffectParams.Value := 0; |
555 |
EffectParams.Value := 0; |
| 482 |
end |
556 |
end |
| 483 |
else KeycodeToHexNumber(Key, EffectCode); |
557 |
else KeycodeToHexNumber(Key, EffectCode); |
|
|
558 |
|
|
|
559 |
Invalidate; |
|
|
560 |
SaveUndoState; |
| 484 |
end; |
561 |
end; |
| 485 |
|
562 |
|
| 486 |
procedure TTrackerGrid.InputEffectParams(Key: Word); |
563 |
procedure TTrackerGrid.InputEffectParams(Key: Word); |
@@ -503,6 +580,9 @@ begin |
| 503 |
EffectParams.Param2 := Temp; |
580 |
EffectParams.Param2 := Temp; |
| 504 |
DigitInputting := False; |
581 |
DigitInputting := False; |
| 505 |
end; |
582 |
end; |
|
|
583 |
|
|
|
584 |
Invalidate; |
|
|
585 |
SaveUndoState; |
| 506 |
end; |
586 |
end; |
| 507 |
|
587 |
|
| 508 |
procedure TTrackerGrid.RenderRow(Row: Integer); |
588 |
procedure TTrackerGrid.RenderRow(Row: Integer); |
@@ -653,10 +733,13 @@ end; |
| 653 |
|
733 |
|
| 654 |
function TTrackerGrid.MousePosToSelection(X, Y: Integer): TSelectionPos; |
734 |
function TTrackerGrid.MousePosToSelection(X, Y: Integer): TSelectionPos; |
| 655 |
begin |
735 |
begin |
|
|
736 |
X := Max(0, Min(Width-1, X)); |
|
|
737 |
Y := Max(0, Min(Height-1, Y)); |
|
|
738 |
|
| 656 |
Result.X := Trunc((X/Width)*NUM_COLUMNS); |
739 |
Result.X := Trunc((X/Width)*NUM_COLUMNS); |
| 657 |
Result.Y := Trunc((Y/Height)*NUM_ROWS); |
740 |
Result.Y := Trunc((Y/Height)*NUM_ROWS); |
| 658 |
|
741 |
|
| 659 |
X := (min(X, width-1) mod ColumnWidth); |
742 |
X := X mod ColumnWidth; |
| 660 |
X := Trunc((X/ColumnWidth)*13); |
743 |
X := Trunc((X/ColumnWidth)*13); |
| 661 |
case X of |
744 |
case X of |
| 662 |
0..3: Result.SelectedPart := cpNote; |
745 |
0..3: Result.SelectedPart := cpNote; |
@@ -736,9 +819,10 @@ begin |
| 736 |
end; |
819 |
end; |
| 737 |
end; |
820 |
end; |
| 738 |
|
821 |
|
| 739 |
procedure TTrackerGrid.LoadPattern(Idx: Integer; Pat: PPattern); |
822 |
procedure TTrackerGrid.LoadPattern(Idx: Integer; PatternNumber: Integer); |
| 740 |
begin |
823 |
begin |
| 741 |
Patterns[Idx] := Pat; |
824 |
Patterns[Idx] := PatternMap.GetOrCreateNew(PatternNumber); |
|
|
825 |
PatternNumbers[Idx] := PatternNumber; // Ugh |
| 742 |
Invalidate; |
826 |
Invalidate; |
| 743 |
end; |
827 |
end; |
| 744 |
|
828 |
|