1// MIT License
2
3// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "music.h"
24#include "ext/history.h"
25
26#include <ctype.h>
27
28#define TRACKER_ROWS (MUSIC_PATTERN_ROWS / 4)
29#define CHANNEL_COLS 8
30#define TRACKER_COLS (TIC_SOUND_CHANNELS * CHANNEL_COLS)
31#define PIANO_PATTERN_HEADER 10
32
33#define CMD_STRING(_, c, ...) DEF2STR(c)
34static const char MusicCommands[] = MUSIC_CMD_LIST(CMD_STRING);
35#undef CMD_STRING
36
37enum PianoEditColumns
38{
39 PianoChannel1Column = 0,
40 PianoChannel2Column,
41 PianoChannel3Column,
42 PianoChannel4Column,
43 PianoSfxColumn,
44 PianoXYColumn,
45
46 PianoColumnsCount
47};
48
49enum
50{
51 ColumnNote = 0,
52 ColumnSemitone,
53 ColumnOctave,
54 ColumnSfxHi,
55 ColumnSfxLow,
56 ColumnCommand,
57 ColumnParameter1,
58 ColumnParameter2,
59};
60
61static void undo(Music* music)
62{
63 history_undo(music->history);
64}
65
66static void redo(Music* music)
67{
68 history_redo(music->history);
69}
70
71static const tic_music_state* getMusicPos(Music* music)
72{
73 return &music->tic->ram->music_state;
74}
75
76static void drawEditPanel(Music* music, s32 x, s32 y, s32 w, s32 h)
77{
78 tic_mem* tic = music->tic;
79
80 tic_api_rect(tic, x, y-1, w, 1, tic_color_dark_grey);
81 tic_api_rect(tic, x-1, y, 1, h, tic_color_dark_grey);
82 tic_api_rect(tic, x, y+h, w, 1, tic_color_light_grey);
83 tic_api_rect(tic, x+w, y, 1, h, tic_color_light_grey);
84
85 tic_api_rect(tic, x, y, w, h, tic_color_black);
86}
87
88static void setChannelPattern(Music* music, s32 delta, s32 channel);
89
90static s32 getStep(Music* music)
91{
92 enum{DefaultStep = 1, ExtraStep = 10};
93
94 return tic_api_key(music->tic, tic_key_shift) ? ExtraStep : DefaultStep;
95}
96
97static void drawEditbox(Music* music, s32 x, s32 y, s32 value, void(*set)(Music*, s32, s32 channel), s32 channel)
98{
99 tic_mem* tic = music->tic;
100
101 {
102 tic_rect rect = { x - TIC_FONT_WIDTH, y, TIC_ALTFONT_WIDTH, TIC_FONT_HEIGHT };
103
104 bool over = false;
105 bool down = false;
106 if (checkMousePos(music->studio, &rect))
107 {
108 setCursor(music->studio, tic_cursor_hand);
109 over = true;
110
111 if (checkMouseDown(music->studio, &rect, tic_mouse_left))
112 down = true;
113
114 if (checkMouseClick(music->studio, &rect, tic_mouse_left))
115 set(music, -1, channel);
116 }
117
118 drawBitIcon(music->studio, tic_icon_left, rect.x - 2, rect.y + (down ? 1 : 0), tic_color_black);
119 drawBitIcon(music->studio, tic_icon_left, rect.x - 2, rect.y + (down ? 0 : -1), (over ? tic_color_light_grey : tic_color_dark_grey));
120 }
121
122 {
123 tic_rect rect = { x-1, y-1, TIC_FONT_WIDTH*2+1, TIC_FONT_HEIGHT+1 };
124
125 if (checkMousePos(music->studio, &rect))
126 {
127 setCursor(music->studio, tic_cursor_hand);
128
129 showTooltip(music->studio, "select pattern ID");
130
131 bool left = checkMouseClick(music->studio, &rect, tic_mouse_left);
132 if(left || checkMouseClick(music->studio, &rect, tic_mouse_right))
133 {
134 tic_point pos = {channel * CHANNEL_COLS, -1};
135
136 if(MEMCMP(music->tracker.edit, pos))
137 {
138 s32 step = getStep(music);
139 setChannelPattern(music, left ? +step : -step, channel);
140 }
141 else
142 {
143 music->tracker.edit = pos;
144 s32 mx = tic_api_mouse(tic).x - rect.x;
145 music->tracker.col = mx / TIC_FONT_WIDTH ? 1 : 0;
146 }
147 }
148 }
149
150 drawEditPanel(music, rect.x, rect.y, rect.w, rect.h);
151
152 if(music->tracker.edit.y == -1 && music->tracker.edit.x / CHANNEL_COLS == channel)
153 {
154 tic_api_rect(music->tic, x - 1 + music->tracker.col * TIC_FONT_WIDTH, y - 1, TIC_FONT_WIDTH + 1, TIC_FONT_HEIGHT + 1, tic_color_red);
155 }
156
157 char val[] = "99";
158 sprintf(val, "%02i", value);
159 tic_api_print(music->tic, val, x, y, tic_color_white, true, 1, false);
160 }
161
162 {
163 tic_rect rect = { x + TIC_FONT_WIDTH*2+1, y, TIC_ALTFONT_WIDTH, TIC_FONT_HEIGHT };
164
165 bool over = false;
166 bool down = false;
167 if (checkMousePos(music->studio, &rect))
168 {
169 setCursor(music->studio, tic_cursor_hand);
170 over = true;
171
172 if (checkMouseDown(music->studio, &rect, tic_mouse_left))
173 down = true;
174
175 if (checkMouseClick(music->studio, &rect, tic_mouse_left))
176 set(music, +1, channel);
177 }
178
179 drawBitIcon(music->studio, tic_icon_right, rect.x - 1, rect.y + (down ? 1 : 0), tic_color_black);
180 drawBitIcon(music->studio, tic_icon_right, rect.x - 1, rect.y + (down ? 0 : -1), (over ? tic_color_light_grey : tic_color_dark_grey));
181 }
182}
183
184static inline s32 switchWidth(s32 value)
185{
186 return value > 99 ? 3 * TIC_FONT_WIDTH : 2 * TIC_FONT_WIDTH;
187}
188
189static void drawSwitch(Music* music, s32 x, s32 y, const char* label, s32 value, void(*set)(Music*, s32))
190{
191 tic_mem* tic = music->tic;
192
193 tic_api_print(tic, label, x, y+1, tic_color_black, true, 1, false);
194 tic_api_print(tic, label, x, y, tic_color_white, true, 1, false);
195
196 x += strlen(label) * TIC_FONT_WIDTH + TIC_ALTFONT_WIDTH;
197
198 {
199 tic_rect rect = { x - TIC_ALTFONT_WIDTH, y, TIC_ALTFONT_WIDTH, TIC_FONT_HEIGHT };
200
201 bool over = false;
202 bool down = false;
203 if (checkMousePos(music->studio, &rect))
204 {
205 setCursor(music->studio, tic_cursor_hand);
206
207 over = true;
208
209 if (checkMouseDown(music->studio, &rect, tic_mouse_left))
210 down = true;
211
212 if (checkMouseClick(music->studio, &rect, tic_mouse_left))
213 set(music, value - getStep(music));
214 }
215
216 drawBitIcon(music->studio, tic_icon_left, rect.x - 2, rect.y + (down ? 1 : 0), tic_color_black);
217 drawBitIcon(music->studio, tic_icon_left, rect.x - 2, rect.y + (down ? 0 : -1), over ? tic_color_light_grey : tic_color_dark_grey);
218 }
219
220 {
221 tic_rect rect = { x, y, switchWidth(value), TIC_FONT_HEIGHT };
222
223 if((tic->ram->input.mouse.btns & 1) == 0)
224 music->drag.label = NULL;
225
226 if(music->drag.label == label)
227 {
228 tic_point m = tic_api_mouse(tic);
229 enum{Speed = 2};
230 set(music, music->drag.start + (m.x - music->drag.x) / Speed);
231 }
232
233 if (checkMousePos(music->studio, &rect))
234 {
235 setCursor(music->studio, tic_cursor_hand);
236
237 if (checkMouseDown(music->studio, &rect, tic_mouse_left))
238 {
239 if(!music->drag.label)
240 {
241 music->drag.label = label;
242 music->drag.x = tic_api_mouse(tic).x;
243 music->drag.start = value;
244 }
245 }
246
247 bool left = checkMouseClick(music->studio, &rect, tic_mouse_left);
248 if(left || checkMouseClick(music->studio, &rect, tic_mouse_right))
249 {
250 s32 step = getStep(music);
251 set(music, value + (left ? +step : -step));
252 }
253 }
254
255 char val[sizeof "999"];
256 sprintf(val, "%02i", value);
257 tic_api_print(tic, val, x, y+1, tic_color_black, true, 1, false);
258 tic_api_print(tic, val, x, y, tic_color_yellow, true, 1, false);
259 }
260
261 {
262 tic_rect rect = { x + switchWidth(value), y, TIC_ALTFONT_WIDTH, TIC_FONT_HEIGHT };
263
264 bool over = false;
265 bool down = false;
266 if (checkMousePos(music->studio, &rect))
267 {
268 setCursor(music->studio, tic_cursor_hand);
269
270 over = true;
271
272 if (checkMouseDown(music->studio, &rect, tic_mouse_left))
273 down = true;
274
275 if (checkMouseClick(music->studio, &rect, tic_mouse_left))
276 set(music, value + getStep(music));
277 }
278
279 drawBitIcon(music->studio, tic_icon_right, rect.x - 2, rect.y + (down ? 1 : 0), tic_color_black);
280 drawBitIcon(music->studio, tic_icon_right, rect.x - 2, rect.y + (down ? 0 : -1), over ? tic_color_light_grey : tic_color_dark_grey);
281 }
282}
283
284static tic_track* getTrack(Music* music)
285{
286 return &music->src->tracks.data[music->track];
287}
288
289static s32 getRows(Music* music)
290{
291 tic_track* track = getTrack(music);
292
293 return MUSIC_PATTERN_ROWS - track->rows;
294}
295
296static void updateScroll(Music* music)
297{
298 music->scroll.pos = CLAMP(music->scroll.pos, 0, getRows(music) - TRACKER_ROWS);
299}
300
301static void updateTracker(Music* music)
302{
303 s32 row = music->tracker.edit.y;
304
305 enum{Threshold = TRACKER_ROWS / 2};
306 music->scroll.pos = CLAMP(music->scroll.pos, row - (TRACKER_ROWS - Threshold), row - Threshold);
307
308 {
309 s32 rows = getRows(music);
310 if (music->tracker.edit.y >= rows) music->tracker.edit.y = rows - 1;
311 }
312
313 updateScroll(music);
314}
315
316static void upRow(Music* music)
317{
318 if (music->tracker.edit.y > -1)
319 {
320 music->tracker.edit.y--;
321 updateTracker(music);
322 }
323}
324
325static void downRow(Music* music)
326{
327 const tic_music_state* pos = getMusicPos(music);
328 // Don't move the cursor if the track is being played/recorded
329 if(pos->music.track == music->track && music->follow) return;
330
331 if (music->tracker.edit.y < getRows(music) - 1)
332 {
333 music->tracker.edit.y++;
334 updateTracker(music);
335 }
336}
337
338static void leftCol(Music* music)
339{
340 if (music->tracker.edit.x > 0)
341 {
342 // skip sharp column
343 if(music->tracker.edit.x == 2) {
344 music->tracker.edit.x--;
345 }
346 music->tracker.edit.x--;
347 updateTracker(music);
348 }
349}
350
351static void rightCol(Music* music)
352{
353 if (music->tracker.edit.x < TRACKER_COLS - 1)
354 {
355 // skip sharp column
356 if(music->tracker.edit.x == 0) {
357 music->tracker.edit.x++;
358 }
359 music->tracker.edit.x++;
360 updateTracker(music);
361 }
362}
363
364static void goHome(Music* music)
365{
366 music->tracker.edit.x -= music->tracker.edit.x % CHANNEL_COLS;
367}
368
369static void goEnd(Music* music)
370{
371 music->tracker.edit.x -= music->tracker.edit.x % CHANNEL_COLS;
372 music->tracker.edit.x += CHANNEL_COLS-1;
373}
374
375static void pageUp(Music* music)
376{
377 music->tracker.edit.y -= TRACKER_ROWS;
378 if(music->tracker.edit.y < 0)
379 music->tracker.edit.y = 0;
380
381 updateTracker(music);
382}
383
384static void pageDown(Music* music)
385{
386 if (music->tracker.edit.y < getRows(music) - 1)
387
388 music->tracker.edit.y += TRACKER_ROWS;
389 s32 rows = getRows(music);
390
391 if(music->tracker.edit.y >= rows)
392 music->tracker.edit.y = rows-1;
393
394 updateTracker(music);
395}
396
397static void doTab(Music* music)
398{
399 tic_mem* tic = music->tic;
400 s32 inc = tic_api_key(tic, tic_key_shift) ? -1 : +1;
401
402 s32 channel = (music->tracker.edit.x / CHANNEL_COLS + TIC_SOUND_CHANNELS + inc) % TIC_SOUND_CHANNELS;
403 music->tracker.edit.x = channel * CHANNEL_COLS + music->tracker.edit.x % CHANNEL_COLS;
404
405 updateTracker(music);
406}
407
408static void upFrame(Music* music)
409{
410 music->frame--;
411
412 if(music->frame < 0)
413 music->frame = 0;
414}
415
416static void downFrame(Music* music)
417{
418 music->frame++;
419
420 if(music->frame >= MUSIC_FRAMES)
421 music->frame = MUSIC_FRAMES-1;
422}
423
424static bool checkPlaying(Music* music)
425{
426 return getMusicPos(music)->music.track >= 0;
427}
428
429static bool checkPlayFrame(Music* music, s32 frame)
430{
431 const tic_music_state* pos = getMusicPos(music);
432
433 return pos->music.track == music->track &&
434 pos->music.frame == frame;
435}
436
437static bool checkPlayRow(Music* music, s32 row)
438{
439 const tic_music_state* pos = getMusicPos(music);
440
441 return checkPlayFrame(music, music->frame) && pos->music.row == row;
442}
443
444static tic_track_pattern* getFramePattern(Music* music, s32 channel, s32 frame)
445{
446 s32 patternId = tic_tool_get_pattern_id(getTrack(music), frame, channel);
447
448 return patternId ? &music->src->patterns.data[patternId - PATTERN_START] : NULL;
449}
450
451static tic_track_pattern* getPattern(Music* music, s32 channel)
452{
453 return getFramePattern(music, channel, music->frame);
454}
455
456static tic_track_pattern* getChannelPattern(Music* music)
457{
458 s32 channel = music->tracker.edit.x / CHANNEL_COLS;
459
460 return getPattern(music, channel);
461}
462
463static s32 getNote(Music* music)
464{
465 tic_track_pattern* pattern = getChannelPattern(music);
466
467 return pattern->rows[music->tracker.edit.y].note - NoteStart;
468}
469
470static s32 getOctave(Music* music)
471{
472 tic_track_pattern* pattern = getChannelPattern(music);
473
474 return pattern->rows[music->tracker.edit.y].octave;
475}
476
477static s32 getSfx(Music* music)
478{
479 tic_track_pattern* pattern = getChannelPattern(music);
480
481 return tic_tool_get_track_row_sfx(&pattern->rows[music->tracker.edit.y]);
482}
483
484static inline tic_music_status getMusicState(Music* music)
485{
486 return music->tic->ram->music_state.flag.music_status;
487}
488
489static inline void setMusicState(Music* music, tic_music_status state)
490{
491 music->tic->ram->music_state.flag.music_status = state;
492}
493
494static void playNote(Music* music, const tic_track_row* row)
495{
496 tic_mem* tic = music->tic;
497
498 if(getMusicState(music) == tic_music_stop && row->note >= NoteStart)
499 {
500 s32 channel = music->piano.col;
501 sfx_stop(tic, channel);
502 tic_api_sfx(tic, tic_tool_get_track_row_sfx(row), row->note - NoteStart, row->octave, TIC80_FRAMERATE / 4, channel, MAX_VOLUME, MAX_VOLUME, 0);
503 }
504}
505
506static void setSfx(Music* music, s32 sfx)
507{
508 tic_track_pattern* pattern = getChannelPattern(music);
509 tic_track_row* row = &pattern->rows[music->tracker.edit.y];
510
511 tic_tool_set_track_row_sfx(row, sfx);
512
513 music->last.sfx = tic_tool_get_track_row_sfx(&pattern->rows[music->tracker.edit.y]);
514
515 playNote(music, row);
516}
517
518static void setStopNote(Music* music)
519{
520 tic_track_pattern* pattern = getChannelPattern(music);
521
522 pattern->rows[music->tracker.edit.y].note = NoteStop;
523 pattern->rows[music->tracker.edit.y].octave = 0;
524}
525
526static void setNote(Music* music, s32 note, s32 octave, s32 sfx)
527{
528 tic_track_pattern* pattern = getChannelPattern(music);
529 tic_track_row* row = &pattern->rows[music->tracker.edit.y];
530 row->note = note + NoteStart;
531 row->octave = octave;
532 tic_tool_set_track_row_sfx(row, sfx);
533
534 playNote(music, row);
535}
536
537static void setOctave(Music* music, s32 octave)
538{
539 tic_track_pattern* pattern = getChannelPattern(music);
540
541 tic_track_row* row = &pattern->rows[music->tracker.edit.y];
542 row->octave = octave;
543
544 music->last.octave = octave;
545
546 playNote(music, row);
547}
548
549static void setCommandDefaults(tic_track_row* row)
550{
551 switch(row->command)
552 {
553 case tic_music_cmd_volume:
554 row->param2 = row->param1 = MAX_VOLUME;
555 break;
556 case tic_music_cmd_pitch:
557 row->param1 = PITCH_DELTA >> 4;
558 row->param2 = PITCH_DELTA & 0xf;
559 default: break;
560 }
561}
562
563static void setCommand(Music* music, tic_music_command command)
564{
565 tic_track_pattern* pattern = getChannelPattern(music);
566 tic_track_row* row = &pattern->rows[music->tracker.edit.y];
567
568 tic_music_command prev = row->command;
569 row->command = command;
570
571 if(prev == tic_music_cmd_empty)
572 setCommandDefaults(row);
573}
574
575static void setParam1(Music* music, u8 value)
576{
577 tic_track_pattern* pattern = getChannelPattern(music);
578 tic_track_row* row = &pattern->rows[music->tracker.edit.y];
579
580 row->param1 = value;
581}
582
583static void setParam2(Music* music, u8 value)
584{
585 tic_track_pattern* pattern = getChannelPattern(music);
586 tic_track_row* row = &pattern->rows[music->tracker.edit.y];
587
588 row->param2 = value;
589}
590
591static void playTrackFromNow(Music* music)
592{
593 tic_mem* tic = music->tic;
594
595 tic_api_music(tic, music->track, music->frame, music->tab == MUSIC_TRACKER_TAB ? music->tracker.edit.y : -1, music->loop, music->sustain, -1, -1);
596}
597
598static void playFrame(Music* music)
599{
600 tic_mem* tic = music->tic;
601
602 tic_api_music(tic, music->track, music->frame, -1, music->loop, music->sustain, -1, -1);
603
604 setMusicState(music, tic_music_play_frame);
605}
606
607static void playTrack(Music* music)
608{
609 tic_api_music(music->tic, music->track, -1, -1, music->loop, music->sustain, -1, -1);
610}
611
612static void stopTrack(Music* music)
613{
614 tic_api_music(music->tic, -1, -1, -1, music->loop, music->sustain, -1, -1);
615}
616
617static void toggleFollowMode(Music* music)
618{
619 music->follow = !music->follow;
620}
621
622static void toggleSustainMode(Music* music)
623{
624 music->tic->ram->music_state.flag.music_sustain = !music->sustain;
625 music->sustain = !music->sustain;
626}
627
628static void toggleLoopMode(Music* music)
629{
630 music->loop = !music->loop;
631}
632
633static void resetSelection(Music* music)
634{
635 music->tracker.select.start = (tic_point){-1, -1};
636 music->tracker.select.rect = (tic_rect){0, 0, 0, 0};
637}
638
639static void deleteSelection(Music* music)
640{
641 tic_track_pattern* pattern = getChannelPattern(music);
642
643 if(pattern)
644 {
645 tic_rect rect = music->tracker.select.rect;
646
647 if(rect.h <= 0)
648 {
649 rect.y = music->tracker.edit.y;
650 rect.h = 1;
651 }
652
653 memset(&pattern->rows[rect.y], 0, sizeof(tic_track_row) * rect.h);
654 }
655}
656
657typedef struct
658{
659 u8 size;
660} ClipboardHeader;
661
662static void copyPianoToClipboard(Music* music, bool cut)
663{
664 tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
665
666 if(pattern)
667 {
668 ClipboardHeader header = {MUSIC_PATTERN_ROWS};
669
670 enum{HeaderSize = sizeof(ClipboardHeader), Size = sizeof(tic_track_pattern) + HeaderSize};
671
672 u8* data = malloc(Size);
673
674 if(data)
675 {
676 memcpy(data, &header, HeaderSize);
677 memcpy(data + HeaderSize, pattern->rows, sizeof(tic_track_pattern));
678
679 toClipboard(data, Size, true);
680
681 free(data);
682
683 if(cut)
684 {
685 memset(pattern->rows, 0, sizeof(tic_track_pattern));
686 history_add(music->history);
687 }
688 }
689 }
690}
691
692static void copyPianoFromClipboard(Music* music)
693{
694 tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
695
696 if(pattern && tic_sys_clipboard_has())
697 {
698 char* clipboard = tic_sys_clipboard_get();
699
700 if(clipboard)
701 {
702 s32 size = (s32)strlen(clipboard)/2;
703
704 enum{RowSize = sizeof(tic_track_pattern) / MUSIC_PATTERN_ROWS, HeaderSize = sizeof(ClipboardHeader)};
705
706 if(size > HeaderSize)
707 {
708 u8* data = malloc(size);
709
710 tic_tool_str2buf(clipboard, (s32)strlen(clipboard), data, true);
711
712 ClipboardHeader header = {0};
713
714 memcpy(&header, data, HeaderSize);
715
716 if(size == header.size * RowSize + HeaderSize
717 && size == sizeof(tic_track_pattern) + HeaderSize)
718 {
719 memcpy(pattern->rows, data + HeaderSize, header.size * RowSize);
720 history_add(music->history);
721 }
722
723 free(data);
724 }
725
726 tic_sys_clipboard_free(clipboard);
727 }
728 }
729}
730
731static void copyTrackerToClipboard(Music* music, bool cut)
732{
733 tic_track_pattern* pattern = getChannelPattern(music);
734
735 if(pattern)
736 {
737 tic_rect rect = music->tracker.select.rect;
738
739 if(rect.h <= 0)
740 {
741 rect.y = music->tracker.edit.y;
742 rect.h = 1;
743 }
744
745 ClipboardHeader header = {rect.h};
746
747 enum{RowSize = sizeof(tic_track_pattern) / MUSIC_PATTERN_ROWS, HeaderSize = sizeof(ClipboardHeader)};
748
749 s32 size = rect.h * RowSize + HeaderSize;
750 u8* data = malloc(size);
751
752 if(data)
753 {
754 memcpy(data, &header, HeaderSize);
755 memcpy(data + HeaderSize, &pattern->rows[rect.y], RowSize * rect.h);
756
757 toClipboard(data, size, true);
758
759 free(data);
760
761 if(cut)
762 {
763 deleteSelection(music);
764 history_add(music->history);
765 }
766
767 resetSelection(music);
768 }
769 }
770}
771
772static void copyTrackerFromClipboard(Music* music)
773{
774 tic_track_pattern* pattern = getChannelPattern(music);
775
776 if(pattern && tic_sys_clipboard_has())
777 {
778 char* clipboard = tic_sys_clipboard_get();
779
780 if(clipboard)
781 {
782 s32 size = (s32)strlen(clipboard)/2;
783
784 enum{RowSize = sizeof(tic_track_pattern) / MUSIC_PATTERN_ROWS, HeaderSize = sizeof(ClipboardHeader)};
785
786 if(size > HeaderSize)
787 {
788 u8* data = malloc(size);
789
790 tic_tool_str2buf(clipboard, (s32)strlen(clipboard), data, true);
791
792 ClipboardHeader header = {0};
793
794 memcpy(&header, data, HeaderSize);
795
796 if(header.size * RowSize == size - HeaderSize)
797 {
798 if(header.size + music->tracker.edit.y > MUSIC_PATTERN_ROWS)
799 header.size = MUSIC_PATTERN_ROWS - music->tracker.edit.y;
800
801 memcpy(&pattern->rows[music->tracker.edit.y], data + HeaderSize, header.size * RowSize);
802 history_add(music->history);
803 }
804
805 free(data);
806 }
807
808 tic_sys_clipboard_free(clipboard);
809 }
810 }
811}
812
813static void copyToClipboard(Music* music, bool cut)
814{
815 switch (music->tab)
816 {
817 case MUSIC_TRACKER_TAB: copyTrackerToClipboard(music, cut); break;
818 case MUSIC_PIANO_TAB: copyPianoToClipboard(music, cut); break;
819 }
820}
821
822static void copyFromClipboard(Music* music)
823{
824 switch (music->tab)
825 {
826 case MUSIC_TRACKER_TAB: copyTrackerFromClipboard(music); break;
827 case MUSIC_PIANO_TAB: copyPianoFromClipboard(music); break;
828 }
829}
830
831static void setChannelPatternValue(Music* music, s32 pattern, s32 frame, s32 channel)
832{
833 if(pattern < 0) pattern = MUSIC_PATTERNS;
834 if(pattern > MUSIC_PATTERNS) pattern = 0;
835
836 tic_tool_set_pattern_id(getTrack(music), frame, channel, pattern);
837 history_add(music->history);
838}
839
840static void prevPattern(Music* music)
841{
842 s32 channel = music->tracker.edit.x / CHANNEL_COLS;
843
844 if (channel > 0)
845 {
846 music->tracker.edit.x = (channel-1) * CHANNEL_COLS;
847 music->tracker.col = 1;
848 }
849}
850
851static void nextPattern(Music* music)
852{
853 s32 channel = music->tracker.edit.x / CHANNEL_COLS;
854
855 if (channel < TIC_SOUND_CHANNELS-1)
856 {
857 music->tracker.edit.x = (channel+1) * CHANNEL_COLS;
858 music->tracker.col = 0;
859 }
860}
861
862static void colLeft(Music* music)
863{
864 if(music->tracker.col > 0)
865 music->tracker.col--;
866 else prevPattern(music);
867}
868
869static void colRight(Music* music)
870{
871 if(music->tracker.col < 1)
872 music->tracker.col++;
873 else nextPattern(music);
874}
875
876static void startSelection(Music* music)
877{
878 if(music->tracker.select.start.x < 0 || music->tracker.select.start.y < 0)
879 {
880 music->tracker.select.start.x = music->tracker.edit.x;
881 music->tracker.select.start.y = music->tracker.edit.y;
882 }
883}
884
885static void updateSelection(Music* music)
886{
887 s32 rl = MIN(music->tracker.edit.x, music->tracker.select.start.x);
888 s32 rt = MIN(music->tracker.edit.y, music->tracker.select.start.y);
889 s32 rr = MAX(music->tracker.edit.x, music->tracker.select.start.x);
890 s32 rb = MAX(music->tracker.edit.y, music->tracker.select.start.y);
891
892 tic_rect* rect = &music->tracker.select.rect;
893 *rect = (tic_rect){rl, rt, rr - rl + 1, rb - rt + 1};
894
895 if(rect->x % CHANNEL_COLS + rect->w > CHANNEL_COLS)
896 resetSelection(music);
897}
898
899static s32 getDigit(s32 pos, s32 val)
900{
901 enum {Base = 10};
902
903 s32 div = 1;
904 while(pos--) div *= Base;
905
906 return val / div % Base;
907}
908
909static s32 setDigit(s32 pos, s32 val, s32 digit)
910{
911 enum {Base = 10};
912
913 s32 div = 1;
914 while(pos--) div *= Base;
915
916 return val - (val / div % Base - digit) * div;
917}
918
919static s32 sym2dec(char sym)
920{
921 s32 val = -1;
922 if (sym >= '0' && sym <= '9') val = sym - '0';
923 return val;
924}
925
926static s32 sym2hex(char sym)
927{
928 s32 val = sym2dec(sym);
929 if (sym >= 'a' && sym <= 'f') val = sym - 'a' + 10;
930
931 return val;
932}
933
934static void delete(Music* music)
935{
936 deleteSelection(music);
937
938 tic_track_pattern* pattern = getChannelPattern(music);
939
940 if(pattern)
941 {
942 history_add(music->history);
943
944 if(music->tracker.select.rect.h <= 0)
945 downRow(music);
946 }
947}
948
949static void backspace(Music* music)
950{
951 tic_track_pattern* pattern = getChannelPattern(music);
952
953 if(pattern)
954 {
955 tic_track_row* rows = pattern->rows;
956 const tic_rect* rect = &music->tracker.select.rect;
957
958 if(rect->h > 0)
959 {
960 memmove(&rows[rect->y], &rows[rect->y + rect->h], (MUSIC_PATTERN_ROWS - (rect->y + rect->h)) * sizeof(tic_track_row));
961 memset(&rows[MUSIC_PATTERN_ROWS - rect->h], 0, rect->h * sizeof(tic_track_row));
962 music->tracker.edit.y = rect->y;
963 }
964 else
965 {
966 s32 y = music->tracker.edit.y;
967
968 if(y >= 1)
969 {
970 memmove(&rows[y - 1], &rows[y], (MUSIC_PATTERN_ROWS - y) * sizeof(tic_track_row));
971 memset(&rows[MUSIC_PATTERN_ROWS - 1], 0, sizeof(tic_track_row));
972 upRow(music);
973 }
974 }
975
976 history_add(music->history);
977 }
978}
979
980static void insert(Music* music)
981{
982 tic_track_pattern* pattern = getChannelPattern(music);
983
984 if(pattern)
985 {
986 s32 y = music->tracker.edit.y;
987
988 enum{Max = MUSIC_PATTERN_ROWS - 1};
989
990 if(y < Max)
991 {
992 tic_track_row* rows = pattern->rows;
993 memmove(&rows[y + 1], &rows[y], (Max - y) * sizeof(tic_track_row));
994 memset(&rows[y], 0, sizeof(tic_track_row));
995 history_add(music->history);
996 }
997 }
998}
999
1000static tic_track_row* startRow(Music* music)
1001{
1002 tic_track_pattern* pattern = getChannelPattern(music);
1003 const tic_rect* rect = &music->tracker.select.rect;
1004 return pattern ? pattern->rows + (rect->h > 0 ? rect->y : music->tracker.edit.y) : NULL;
1005}
1006
1007static tic_track_row* endRow(Music* music)
1008{
1009 tic_track_pattern* pattern = getChannelPattern(music);
1010 const tic_rect* rect = &music->tracker.select.rect;
1011 return pattern ? startRow(music) + (rect->h > 0 ? rect->h : 1) : NULL;
1012}
1013
1014static void incNote(Music* music, s32 note, s32 octave)
1015{
1016 for(tic_track_row* row = startRow(music), *end = endRow(music); row < end; row++)
1017 {
1018 if(row && row->note >= NoteStart)
1019 {
1020 s32 index = (row->note + note - NoteStart) + (row->octave + octave) * NOTES;
1021
1022 if(index >= 0)
1023 {
1024 row->note = (index % NOTES) + NoteStart;
1025 row->octave = index / NOTES;
1026 }
1027 }
1028 }
1029
1030 history_add(music->history);
1031}
1032
1033static void decSemitone(Music* music) { incNote(music, -1, 0); }
1034static void incSemitone(Music* music) { incNote(music, +1, 0); }
1035static void decOctave(Music* music) { incNote(music, 0, -1); }
1036static void incOctave(Music* music) { incNote(music, 0, +1); }
1037
1038static void incSfx(Music* music, s32 inc)
1039{
1040 for(tic_track_row* row = startRow(music), *end = endRow(music); row < end; row++)
1041 if(row && row->note >= NoteStart)
1042 tic_tool_set_track_row_sfx(row, tic_tool_get_track_row_sfx(row) + inc);
1043
1044 history_add(music->history);
1045}
1046
1047static void upSfx(Music* music) { incSfx(music, +1); }
1048static void downSfx(Music* music) { incSfx(music, -1); }
1049
1050static void setChannelPattern(Music* music, s32 delta, s32 channel)
1051{
1052 s32 pattern = tic_tool_get_pattern_id(getTrack(music), music->frame, channel);
1053 setChannelPatternValue(music, pattern + delta, music->frame, channel);
1054}
1055
1056static void processTrackerKeyboard(Music* music)
1057{
1058 tic_mem* tic = music->tic;
1059
1060 bool shift = tic_api_key(tic, tic_key_shift);
1061 bool ctrl = tic_api_key(tic, tic_key_ctrl);
1062
1063 static const struct Handler{u8 key; void(*handler)(Music* music); bool select; bool ctrl;} Handlers[] =
1064 {
1065 {tic_key_up, upRow, true},
1066 {tic_key_down, downRow, true},
1067 {tic_key_up, upSfx, false, true},
1068 {tic_key_down, downSfx, false, true},
1069 {tic_key_left, leftCol, true},
1070 {tic_key_right, rightCol, true},
1071 {tic_key_left, upFrame, false, true},
1072 {tic_key_right, downFrame, false, true},
1073 {tic_key_home, goHome, true},
1074 {tic_key_end, goEnd, true},
1075 {tic_key_pageup, pageUp, true},
1076 {tic_key_pagedown, pageDown, true},
1077 {tic_key_tab, doTab},
1078 {tic_key_delete, delete},
1079 {tic_key_backspace, backspace},
1080 {tic_key_insert, insert},
1081 {tic_key_f1, decSemitone, false, true},
1082 {tic_key_f2, incSemitone, false, true},
1083 {tic_key_f3, decOctave, false, true},
1084 {tic_key_f4, incOctave, false, true},
1085 };
1086
1087 for(const struct Handler *ptr = Handlers, *end = ptr + COUNT_OF(Handlers); ptr < end; ptr++)
1088 if(keyWasPressed(music->studio, ptr->key))
1089 {
1090 if(shift && ptr->select)
1091 startSelection(music);
1092
1093 if(ptr->ctrl == ctrl)
1094 ptr->handler(music);
1095
1096 if(shift)
1097 {
1098 if(ptr->select)
1099 updateSelection(music);
1100 }
1101 else if(!ctrl)
1102 resetSelection(music);
1103 }
1104
1105 static const u8 Piano[] =
1106 {
1107 tic_key_z,
1108 tic_key_s,
1109 tic_key_x,
1110 tic_key_d,
1111 tic_key_c,
1112 tic_key_v,
1113 tic_key_g,
1114 tic_key_b,
1115 tic_key_h,
1116 tic_key_n,
1117 tic_key_j,
1118 tic_key_m,
1119
1120 // octave +1
1121 tic_key_q,
1122 tic_key_2,
1123 tic_key_w,
1124 tic_key_3,
1125 tic_key_e,
1126 tic_key_r,
1127 tic_key_5,
1128 tic_key_t,
1129 tic_key_6,
1130 tic_key_y,
1131 tic_key_7,
1132 tic_key_u,
1133
1134 // extra keys
1135 tic_key_i,
1136 tic_key_9,
1137 tic_key_o,
1138 tic_key_0,
1139 tic_key_p,
1140 };
1141
1142 if (getChannelPattern(music) && !ctrl)
1143 {
1144 s32 col = music->tracker.edit.x % CHANNEL_COLS;
1145
1146 switch (col)
1147 {
1148 case ColumnNote:
1149 case ColumnSemitone:
1150 if (keyWasPressed(music->studio, tic_key_1) || keyWasPressed(music->studio, tic_key_a))
1151 {
1152 setStopNote(music);
1153 downRow(music);
1154 }
1155 else
1156 {
1157 tic_track_pattern* pattern = getChannelPattern(music);
1158
1159 for (s32 i = 0; i < COUNT_OF(Piano); i++)
1160 {
1161 if (keyWasPressed(music->studio, Piano[i]))
1162 {
1163 s32 note = i % NOTES;
1164 s32 octave = i / NOTES + music->last.octave;
1165 s32 sfx = music->last.sfx;
1166 setNote(music, note, octave, sfx);
1167
1168 downRow(music);
1169
1170 break;
1171 }
1172 }
1173 }
1174 break;
1175 case ColumnOctave:
1176 if(getNote(music) >= 0)
1177 {
1178 s32 octave = -1;
1179
1180 char sym = getKeyboardText(music->studio);
1181
1182 if(sym >= '1' && sym <= '8') octave = sym - '1';
1183
1184 if(octave >= 0)
1185 {
1186 setOctave(music, octave);
1187 downRow(music);
1188 }
1189 }
1190 break;
1191 case ColumnSfxHi:
1192 case ColumnSfxLow:
1193 if(getNote(music) >= 0)
1194 {
1195 s32 val = sym2dec(getKeyboardText(music->studio));
1196
1197 if(val >= 0)
1198 {
1199 s32 sfx = setDigit(col == 3 ? 1 : 0, getSfx(music), val);
1200
1201 setSfx(music, sfx);
1202
1203 if(col == 3) rightCol(music);
1204 else downRow(music), leftCol(music);
1205 }
1206 }
1207 break;
1208 case ColumnCommand:
1209 {
1210 char sym = getKeyboardText(music->studio);
1211
1212 if(sym)
1213 {
1214 const char* val = strchr(MusicCommands, toupper(sym));
1215
1216 if(val)
1217 setCommand(music, val - MusicCommands);
1218 }
1219 }
1220 break;
1221 case ColumnParameter1:
1222 case ColumnParameter2:
1223 {
1224 s32 val = sym2hex(getKeyboardText(music->studio));
1225
1226 if(val >= 0)
1227 {
1228 col == ColumnParameter1
1229 ? setParam1(music, val)
1230 : setParam2(music, val);
1231 }
1232 }
1233 break;
1234 }
1235
1236 history_add(music->history);
1237 }
1238
1239 switch (getKeyboardText(music->studio))
1240 {
1241 case '+': setChannelPattern(music, +1, music->tracker.edit.x / CHANNEL_COLS); break;
1242 case '-': setChannelPattern(music, -1, music->tracker.edit.x / CHANNEL_COLS); break;
1243 }
1244
1245}
1246
1247static void processPatternKeyboard(Music* music)
1248{
1249 tic_mem* tic = music->tic;
1250 s32 channel = music->tracker.edit.x / CHANNEL_COLS;
1251
1252 if(tic_api_key(tic, tic_key_ctrl) || tic_api_key(tic, tic_key_alt))
1253 return;
1254
1255 if(keyWasPressed(music->studio, tic_key_delete)) setChannelPatternValue(music, 0, music->frame, channel);
1256 else if(keyWasPressed(music->studio, tic_key_tab)) nextPattern(music);
1257 else if(keyWasPressed(music->studio, tic_key_left)) colLeft(music);
1258 else if(keyWasPressed(music->studio, tic_key_right)) colRight(music);
1259 else if(keyWasPressed(music->studio, tic_key_down)
1260 || keyWasPressed(music->studio, tic_key_return))
1261 music->tracker.edit.y = music->scroll.pos;
1262 else
1263 {
1264 s32 val = sym2dec(getKeyboardText(music->studio));
1265
1266 if(val >= 0)
1267 {
1268 s32 pattern = setDigit(1 - music->tracker.col & 1, tic_tool_get_pattern_id(getTrack(music),
1269 music->frame, channel), val);
1270
1271 if(pattern <= MUSIC_PATTERNS)
1272 {
1273 setChannelPatternValue(music, pattern, music->frame, channel);
1274
1275 if(music->tracker.col == 0)
1276 colRight(music);
1277 }
1278 }
1279 }
1280
1281 switch (getKeyboardText(music->studio))
1282 {
1283 case '+': setChannelPattern(music, +1, music->tracker.edit.x / CHANNEL_COLS); break;
1284 case '-': setChannelPattern(music, -1, music->tracker.edit.x / CHANNEL_COLS); break;
1285 }
1286}
1287
1288static void updatePianoEditPos(Music* music)
1289{
1290 music->piano.edit.x = CLAMP(music->piano.edit.x, 0, PianoColumnsCount * 2 - 1);
1291
1292 switch(music->piano.edit.x / 2)
1293 {
1294 case PianoSfxColumn:
1295 case PianoXYColumn:
1296 if(music->piano.edit.y < 0)
1297 music->scroll.pos += music->piano.edit.y;
1298
1299 if(music->piano.edit.y > TRACKER_ROWS-1)
1300 music->scroll.pos += music->piano.edit.y - (TRACKER_ROWS - 1);
1301
1302 updateScroll(music);
1303 break;
1304 }
1305
1306 music->piano.edit.y = CLAMP(music->piano.edit.y, 0, MUSIC_FRAMES-1);
1307}
1308
1309static void updatePianoEditCol(Music* music)
1310{
1311 if(music->piano.edit.x & 1)
1312 {
1313 music->piano.edit.x--;
1314 music->piano.edit.y++;
1315 }
1316 else music->piano.edit.x++;
1317
1318 updatePianoEditPos(music);
1319}
1320
1321static inline s32 rowIndex(Music* music, s32 row)
1322{
1323 return row + music->scroll.pos;
1324}
1325
1326static tic_track_row* getPianoRow(Music* music)
1327{
1328 tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
1329 return pattern ? &pattern->rows[rowIndex(music, music->piano.edit.y)] : NULL;
1330}
1331
1332static s32 getPianoValue(Music* music)
1333{
1334 s32 col = music->piano.edit.x / 2;
1335 tic_track_row* row = getPianoRow(music);
1336
1337 switch(col)
1338 {
1339 case PianoChannel1Column:
1340 case PianoChannel2Column:
1341 case PianoChannel3Column:
1342 case PianoChannel4Column:
1343 return getDigit(1 - music->piano.edit.x & 1, tic_tool_get_pattern_id(getTrack(music), music->piano.edit.y, col));
1344
1345 case PianoSfxColumn:
1346 return row && row->note >= NoteStart ? getDigit(1 - music->piano.edit.x & 1, tic_tool_get_track_row_sfx(row)) : -1;
1347
1348 case PianoXYColumn:
1349 return row && row->command > tic_music_cmd_empty ? (music->piano.edit.x & 1 ? row->param2 : row->param1) : -1;
1350 }
1351
1352 return -1;
1353}
1354
1355static void setPianoValue(Music* music, char sym)
1356{
1357 s32 col = music->piano.edit.x / 2;
1358 s32 dec = sym2dec(sym);
1359 s32 hex = sym2hex(sym);
1360 s32 delta = sym == '+' ? +1 : -1;
1361
1362 tic_track_row* row = getPianoRow(music);
1363
1364 switch(col)
1365 {
1366 case PianoChannel1Column:
1367 case PianoChannel2Column:
1368 case PianoChannel3Column:
1369 case PianoChannel4Column:
1370 switch (sym)
1371 {
1372 case '+':
1373 case '-':
1374 setChannelPattern(music, delta, music->piano.edit.x / 2);
1375 break;
1376 }
1377
1378 if(dec >= 0)
1379 {
1380 s32 pattern = setDigit(1 - music->piano.edit.x & 1,
1381 tic_tool_get_pattern_id(getTrack(music), music->piano.edit.y, col), dec);
1382
1383 if(pattern <= MUSIC_PATTERNS)
1384 {
1385 setChannelPatternValue(music, pattern, music->piano.edit.y, col);
1386 updatePianoEditCol(music);
1387 }
1388 }
1389 break;
1390 case PianoSfxColumn:
1391 if(row && row->note >= NoteStart)
1392 {
1393 switch (sym)
1394 {
1395 case '+':
1396 case '-':
1397 tic_tool_set_track_row_sfx(row, tic_modulo(tic_tool_get_track_row_sfx(row) + delta, SFX_COUNT));
1398 break;
1399 default:
1400 if(dec >= 0)
1401 {
1402 s32 sfx = setDigit(1 - music->piano.edit.x & 1, tic_tool_get_track_row_sfx(row), dec);
1403 tic_tool_set_track_row_sfx(row, sfx);
1404 updatePianoEditCol(music);
1405 }
1406 }
1407
1408 history_add(music->history);
1409 music->last.sfx = tic_tool_get_track_row_sfx(row);
1410 playNote(music, row);
1411 }
1412 break;
1413
1414 case PianoXYColumn:
1415 if(row && row->command > tic_music_cmd_empty)
1416 {
1417 switch (sym)
1418 {
1419 case '+':
1420 case '-':
1421 if(music->piano.edit.x & 1)
1422 row->param2 += delta;
1423 else row->param1 += delta;
1424 break;
1425 default:
1426 if(hex >= 0)
1427 {
1428 if(music->piano.edit.x & 1)
1429 row->param2 = hex;
1430 else row->param1 = hex;
1431 updatePianoEditCol(music);
1432 }
1433 }
1434
1435 history_add(music->history);
1436 }
1437 break;
1438 }
1439}
1440
1441static void processPianoKeyboard(Music* music)
1442{
1443 tic_mem* tic = music->tic;
1444
1445 if(keyWasPressed(music->studio, tic_key_up)) music->piano.edit.y--;
1446 else if(keyWasPressed(music->studio, tic_key_down)) music->piano.edit.y++;
1447 else if(keyWasPressed(music->studio, tic_key_left)) music->piano.edit.x--;
1448 else if(keyWasPressed(music->studio, tic_key_right)) music->piano.edit.x++;
1449 else if(keyWasPressed(music->studio, tic_key_home)) music->piano.edit.x = PianoChannel1Column;
1450 else if(keyWasPressed(music->studio, tic_key_end)) music->piano.edit.x = PianoColumnsCount*2+1;
1451 else if(keyWasPressed(music->studio, tic_key_pageup)) music->piano.edit.y -= TRACKER_ROWS;
1452 else if(keyWasPressed(music->studio, tic_key_pagedown)) music->piano.edit.y += TRACKER_ROWS;
1453
1454 updatePianoEditPos(music);
1455
1456 if(keyWasPressed(music->studio, tic_key_delete))
1457 {
1458 s32 col = music->piano.edit.x / 2;
1459 switch(col)
1460 {
1461 case PianoChannel1Column:
1462 case PianoChannel2Column:
1463 case PianoChannel3Column:
1464 case PianoChannel4Column:
1465 setChannelPatternValue(music, 00, music->piano.edit.y, col);
1466 break;
1467 case PianoSfxColumn:
1468 {
1469 tic_track_row* row = getPianoRow(music);
1470 if(row)
1471 {
1472 tic_tool_set_track_row_sfx(row, 0);
1473 history_add(music->history);
1474 }
1475 }
1476 break;
1477 case PianoXYColumn:
1478 {
1479 tic_track_row* row = getPianoRow(music);
1480 if(row)
1481 {
1482 row->param1 = row->param2 = 0;
1483 history_add(music->history);
1484 }
1485 }
1486 break;
1487 }
1488 }
1489
1490 if(getKeyboardText(music->studio))
1491 setPianoValue(music, getKeyboardText(music->studio));
1492}
1493
1494static void selectAll(Music* music)
1495{
1496 resetSelection(music);
1497
1498 s32 col = music->tracker.edit.x - music->tracker.edit.x % CHANNEL_COLS;
1499
1500 music->tracker.select.start = (tic_point){col, 0};
1501 music->tracker.edit.x = col + CHANNEL_COLS-1;
1502 music->tracker.edit.y = MUSIC_PATTERN_ROWS-1;
1503
1504 updateSelection(music);
1505}
1506
1507static void processKeyboard(Music* music)
1508{
1509 tic_mem* tic = music->tic;
1510
1511 switch(getClipboardEvent(music->studio))
1512 {
1513 case TIC_CLIPBOARD_CUT: copyToClipboard(music, true); break;
1514 case TIC_CLIPBOARD_COPY: copyToClipboard(music, false); break;
1515 case TIC_CLIPBOARD_PASTE: copyFromClipboard(music); break;
1516 default: break;
1517 }
1518
1519 bool ctrl = tic_api_key(tic, tic_key_ctrl);
1520 bool shift = tic_api_key(tic, tic_key_shift);
1521
1522 if(tic_api_key(tic, tic_key_alt))
1523 return;
1524
1525 if (ctrl)
1526 {
1527 if(keyWasPressed(music->studio, tic_key_a)) selectAll(music);
1528 else if(keyWasPressed(music->studio, tic_key_z)) undo(music);
1529 else if(keyWasPressed(music->studio, tic_key_y)) redo(music);
1530 else if(keyWasPressed(music->studio, tic_key_f)) toggleFollowMode(music);
1531 }
1532 else
1533 {
1534 bool stopped = !checkPlaying(music);
1535
1536 if(keyWasPressed(music->studio, tic_key_space))
1537 {
1538 stopped
1539 ? playTrack(music)
1540 : stopTrack(music);
1541 }
1542 else if(keyWasPressed(music->studio, tic_key_return))
1543 {
1544 stopped
1545 ? (shift
1546 ? playTrackFromNow(music)
1547 : playFrame(music))
1548 : stopTrack(music);
1549 }
1550
1551 switch (music->tab)
1552 {
1553 case MUSIC_TRACKER_TAB:
1554 music->tracker.edit.y >= 0
1555 ? processTrackerKeyboard(music)
1556 : processPatternKeyboard(music);
1557 break;
1558 case MUSIC_PIANO_TAB:
1559 processPianoKeyboard(music);
1560 break;
1561 }
1562 }
1563}
1564
1565static void setIndex(Music* music, s32 value)
1566{
1567 music->track = CLAMP(value, 0, MUSIC_TRACKS-1);
1568}
1569
1570static void setTempo(Music* music, s32 value)
1571{
1572 enum
1573 {
1574 Min = 40-DEFAULT_TEMPO,
1575 Max = 250-DEFAULT_TEMPO,
1576 };
1577
1578 value -= DEFAULT_TEMPO;
1579 tic_track* track = getTrack(music);
1580 track->tempo = CLAMP(value, Min, Max);
1581
1582 history_add(music->history);
1583}
1584
1585static void setSpeed(Music* music, s32 value)
1586{
1587 enum
1588 {
1589 Min = 1-DEFAULT_SPEED,
1590 Max = 31-DEFAULT_SPEED,
1591 };
1592
1593 value -= DEFAULT_SPEED;
1594 tic_track* track = getTrack(music);
1595 track->speed = CLAMP(value, Min, Max);
1596
1597 history_add(music->history);
1598}
1599
1600static void setRows(Music* music, s32 value)
1601{
1602 enum
1603 {
1604 Min = 0,
1605 Max = MUSIC_PATTERN_ROWS - TRACKER_ROWS,
1606 };
1607
1608 value = MUSIC_PATTERN_ROWS - value;
1609 tic_track* track = getTrack(music);
1610 track->rows = CLAMP(value, Min, Max);
1611 updateTracker(music);
1612
1613 history_add(music->history);
1614}
1615
1616static void drawTopPanel(Music* music, s32 x, s32 y)
1617{
1618 tic_track* track = getTrack(music);
1619
1620 drawSwitch(music, x, y, "TRACK", music->track, setIndex);
1621 drawSwitch(music, x += TIC_FONT_WIDTH * 9, y, "TEMPO", track->tempo + DEFAULT_TEMPO, setTempo);
1622 drawSwitch(music, x += TIC_FONT_WIDTH * 10, y, "SPD", track->speed + DEFAULT_SPEED, setSpeed);
1623 drawSwitch(music, x += TIC_FONT_WIDTH * 7, y, "ROWS", MUSIC_PATTERN_ROWS - track->rows, setRows);
1624}
1625
1626static void drawTrackerFrames(Music* music, s32 x, s32 y)
1627{
1628 tic_mem* tic = music->tic;
1629 enum
1630 {
1631 Border = 1,
1632 Width = TIC_FONT_WIDTH * 2 + Border,
1633 };
1634
1635 {
1636 tic_rect rect = { x - Border, y - Border, Width, MUSIC_FRAMES * TIC_FONT_HEIGHT + Border };
1637
1638 if (checkMousePos(music->studio, &rect))
1639 {
1640 setCursor(music->studio, tic_cursor_hand);
1641
1642 showTooltip(music->studio, "select frame");
1643
1644 if (checkMouseDown(music->studio, &rect, tic_mouse_left))
1645 {
1646 s32 my = tic_api_mouse(tic).y - rect.y - Border;
1647 music->frame = my / TIC_FONT_HEIGHT;
1648 }
1649 }
1650
1651 drawEditPanel(music, rect.x, rect.y, rect.w, rect.h);
1652 }
1653
1654 for (s32 i = 0; i < MUSIC_FRAMES; i++)
1655 {
1656 if (checkPlayFrame(music, i))
1657 {
1658 drawBitIcon(music->studio, tic_icon_right, x - TIC_FONT_WIDTH-2, y + i*TIC_FONT_HEIGHT, tic_color_black);
1659 drawBitIcon(music->studio, tic_icon_right, x - TIC_FONT_WIDTH-2, y - 1 + i*TIC_FONT_HEIGHT, tic_color_white);
1660 }
1661
1662 char buf[sizeof "99"];
1663 sprintf(buf, "%02i", i);
1664
1665 tic_api_print(music->tic, buf, x, y + i*TIC_FONT_HEIGHT, i == music->frame ? tic_color_white : tic_color_grey, true, 1, false);
1666 }
1667
1668 if(music->tracker.edit.y >= 0)
1669 {
1670 char buf[sizeof "99"];
1671 sprintf(buf, "%02i", music->tracker.edit.y);
1672 tic_api_print(music->tic, buf, x, y - 11, tic_color_black, true, 1, false);
1673 tic_api_print(music->tic, buf, x, y - 12, tic_color_white, true, 1, false);
1674 }
1675}
1676
1677static inline void drawChar(tic_mem* tic, char symbol, s32 x, s32 y, u8 color, bool alt)
1678{
1679 tic_api_print(tic, (char[]){symbol, '\0'}, x, y, color, true, 1, alt);
1680}
1681
1682static inline bool noteBeat(Music* music, s32 row)
1683{
1684 return row % (music->beat34 ? 3 : 4) == 0;
1685}
1686
1687static void drawTrackerChannel(Music* music, s32 x, s32 y, s32 channel)
1688{
1689 tic_mem* tic = music->tic;
1690
1691 enum
1692 {
1693 Border = 1,
1694 Rows = TRACKER_ROWS,
1695 Width = TIC_FONT_WIDTH * 8 + Border,
1696 };
1697
1698 tic_rect rect = {x - Border, y - Border, Width, Rows*TIC_FONT_HEIGHT + Border};
1699
1700 if(checkMousePos(music->studio, &rect))
1701 {
1702 setCursor(music->studio, tic_cursor_hand);
1703
1704 if(checkMouseDown(music->studio, &rect, tic_mouse_left))
1705 {
1706 s32 mx = tic_api_mouse(tic).x - rect.x - Border;
1707 s32 my = tic_api_mouse(tic).y - rect.y - Border;
1708
1709 s32 col = music->tracker.edit.x = channel * CHANNEL_COLS + mx / TIC_FONT_WIDTH;
1710 s32 row = music->tracker.edit.y = my / TIC_FONT_HEIGHT + music->scroll.pos;
1711
1712 if(music->tracker.select.drag)
1713 {
1714 updateSelection(music);
1715 }
1716 else
1717 {
1718 resetSelection(music);
1719 music->tracker.select.start = (tic_point){col, row};
1720
1721 music->tracker.select.drag = true;
1722 }
1723 }
1724 }
1725
1726 if(music->tracker.select.drag)
1727 {
1728 tic_rect rect = {0, 0, TIC80_WIDTH, TIC80_HEIGHT};
1729 if(!checkMouseDown(music->studio, &rect, tic_mouse_left))
1730 {
1731 music->tracker.select.drag = false;
1732 }
1733 }
1734
1735 drawEditPanel(music, rect.x, rect.y, rect.w, rect.h);
1736
1737 s32 start = music->scroll.pos;
1738 s32 end = start + Rows;
1739 bool selectedChannel = music->tracker.select.rect.x / CHANNEL_COLS == channel;
1740
1741 tic_track_pattern* pattern = getPattern(music, channel);
1742
1743 for (s32 i = start, pos = 0; i < end; i++, pos++)
1744 {
1745 s32 rowy = y + pos*TIC_FONT_HEIGHT;
1746
1747 if (i == music->tracker.edit.y)
1748 {
1749 tic_api_rect(music->tic, x - 1, rowy - 1, Width, TIC_FONT_HEIGHT + 1, tic_color_dark_grey);
1750 }
1751
1752 // draw selection
1753 if (selectedChannel)
1754 {
1755 tic_rect rect = music->tracker.select.rect;
1756 if (rect.h > 1 && i >= rect.y && i < rect.y + rect.h)
1757 {
1758 s32 sx = x - 1;
1759 tic_api_rect(tic, sx, rowy - 1, CHANNEL_COLS * TIC_FONT_WIDTH + 1, TIC_FONT_HEIGHT + 1, tic_color_grey);
1760 }
1761 }
1762
1763 if (checkPlayRow(music, i))
1764 {
1765 tic_api_rect(music->tic, x - 1, rowy - 1, Width, TIC_FONT_HEIGHT + 1, tic_color_white);
1766 }
1767
1768 char rowStr[] = "--------";
1769
1770 if (pattern)
1771 {
1772 static const char* Notes[] = SFX_NOTES;
1773
1774 const tic_track_row* row = &pattern->rows[i];
1775 s32 note = row->note;
1776 s32 octave = row->octave;
1777 s32 sfx = tic_tool_get_track_row_sfx(&pattern->rows[i]);
1778
1779 if (note == NoteStop)
1780 strcpy(rowStr, " ---");
1781
1782 if (note >= NoteStart)
1783 sprintf(rowStr, "%s%i%02i---", Notes[note - NoteStart], octave + 1, sfx);
1784
1785 if(row->command > tic_music_cmd_empty)
1786 sprintf(rowStr+5, "%c%01X%01X", MusicCommands[row->command], row->param1, row->param2);
1787
1788 static const u8 Colors[] = { tic_color_light_green, tic_color_yellow, tic_color_light_blue };
1789 static const u8 DarkColors[] = { tic_color_green, tic_color_orange, tic_color_blue };
1790 static u8 ColorIndexes[] = { 0, 0, 0, 1, 1, 2, 2, 2 };
1791
1792 bool beetRow = noteBeat(music, i);
1793
1794 for (s32 c = 0, colx = x; c < sizeof rowStr - 1; c++, colx += TIC_FONT_WIDTH)
1795 {
1796 char sym = rowStr[c];
1797 const u8* colors = beetRow || sym != '-' ? Colors : DarkColors;
1798
1799 drawChar(music->tic, sym, colx, rowy, colors[ColorIndexes[c]], false);
1800 }
1801 }
1802 else tic_api_print(music->tic, rowStr, x, rowy, i == music->tracker.edit.y ? tic_color_black : tic_color_dark_grey, true, 1, false);
1803
1804 if (i == music->tracker.edit.y)
1805 {
1806 if (music->tracker.edit.x / CHANNEL_COLS == channel)
1807 {
1808 s32 col = music->tracker.edit.x % CHANNEL_COLS;
1809 s32 colx = x - 1 + col * TIC_FONT_WIDTH;
1810 tic_api_rect(music->tic, colx, rowy - 1, TIC_FONT_WIDTH + 1, TIC_FONT_HEIGHT + 1, tic_color_red);
1811 drawChar(music->tic, rowStr[col], colx + 1, rowy, tic_color_black, false);
1812 }
1813 }
1814
1815 if (noteBeat(music, i))
1816 tic_api_pix(music->tic, x - 4, y + pos*TIC_FONT_HEIGHT + 2, tic_color_black, false);
1817 }
1818}
1819
1820static void drawTumbler(Music* music, s32 x, s32 y, s32 index)
1821{
1822 tic_mem* tic = music->tic;
1823
1824 enum{On=36, Off = 52, Width=7, Height=3};
1825
1826 tic_rect rect = {x, y, Width, Height};
1827
1828 if(checkMousePos(music->studio, &rect))
1829 {
1830 setCursor(music->studio, tic_cursor_hand);
1831
1832 showTooltip(music->studio, "on/off channel");
1833
1834 if(checkMouseClick(music->studio, &rect, tic_mouse_left))
1835 {
1836 if (tic_api_key(tic, tic_key_ctrl))
1837 {
1838 for (s32 i = 0; i < TIC_SOUND_CHANNELS; i++)
1839 music->on[i] = i == index;
1840 }
1841 else music->on[index] = !music->on[index];
1842 }
1843 }
1844
1845 drawEditPanel(music, x, y, Width, Height);
1846
1847 u8 color = tic_color_black;
1848 tiles2ram(tic->ram, &getConfig(music->studio)->cart->bank0.tiles);
1849 tic_api_spr(tic, music->on[index] ? On : Off, x, y, 1, 1, &color, 1, 1, tic_no_flip, tic_no_rotate);
1850}
1851
1852static void drawTrackerLayout(Music* music, s32 x, s32 y)
1853{
1854 drawTrackerFrames(music, x, y);
1855
1856 x += TIC_FONT_WIDTH * 3;
1857
1858 enum{ChannelWidth = TIC_FONT_WIDTH * 9};
1859
1860 for (s32 i = 0; i < TIC_SOUND_CHANNELS; i++)
1861 {
1862 s32 patternId = tic_tool_get_pattern_id(getTrack(music), music->frame, i);
1863 drawEditbox(music, x + ChannelWidth * i + 3*TIC_FONT_WIDTH, y - 12, patternId, setChannelPattern, i);
1864 drawTumbler(music, x + ChannelWidth * i + 7*TIC_FONT_WIDTH-1, y - 11, i);
1865 }
1866
1867 for (s32 i = 0; i < TIC_SOUND_CHANNELS; i++)
1868 drawTrackerChannel(music, x + ChannelWidth * i, y, i);
1869}
1870
1871static void drawPlayButtons(Music* music)
1872{
1873 typedef struct
1874 {
1875 u8 icon;
1876 const char* tip;
1877 const char* alt;
1878 void(*handler)(Music*);
1879 } Button;
1880
1881 static const Button FollowButton =
1882 {
1883 tic_icon_follow,
1884 "FOLLOW [ctrl+f]",
1885 NULL,
1886 toggleFollowMode,
1887 };
1888
1889 static const Button LoopButton =
1890 {
1891 tic_icon_loop,
1892 "LOOP",
1893 NULL,
1894 toggleLoopMode,
1895 };
1896
1897 static const Button SustainButton =
1898 {
1899 tic_icon_sustain,
1900 "SUSTAIN NOTES ...",
1901 "BETWEEN FRAMES",
1902 toggleSustainMode,
1903 };
1904
1905 static const Button PlayFromNowButton =
1906 {
1907 tic_icon_playnow,
1908 "PLAY FROM NOW ...",
1909 "... [shift+enter]",
1910 playTrackFromNow,
1911 };
1912
1913 static const Button PlayFrameButton =
1914 {
1915 tic_icon_playframe,
1916 "PLAY FRAME ...",
1917 "... [enter]",
1918 playFrame,
1919 };
1920
1921 static const Button PlayTrackButton =
1922 {
1923 tic_icon_right,
1924 "PLAY TRACK ...",
1925 "... [space]",
1926 playTrack,
1927 };
1928
1929 static const Button StopButton =
1930 {
1931 tic_icon_stop,
1932 "STOP [enter]",
1933 NULL,
1934 stopTrack,
1935 };
1936
1937 const Button **start, **end;
1938
1939 if(checkPlaying(music))
1940 {
1941 static const Button* Buttons[] =
1942 {
1943 &LoopButton,
1944 &FollowButton,
1945 &SustainButton,
1946 &StopButton,
1947 };
1948
1949 start = Buttons;
1950 end = start + COUNT_OF(Buttons);
1951 }
1952 else
1953 {
1954 static const Button* Buttons[] =
1955 {
1956 &LoopButton,
1957 &FollowButton,
1958 &SustainButton,
1959 &PlayFromNowButton,
1960 &PlayFrameButton,
1961 &PlayTrackButton,
1962 };
1963
1964 start = Buttons;
1965 end = start + COUNT_OF(Buttons);
1966 }
1967
1968 tic_rect rect = { TIC80_WIDTH - 54, 0, TIC_FONT_WIDTH, TOOLBAR_SIZE };
1969
1970 for(const Button** btn = start; btn < end; btn++, rect.x += TIC_FONT_WIDTH)
1971 {
1972 bool over = false;
1973
1974 if (checkMousePos(music->studio, &rect))
1975 {
1976 setCursor(music->studio, tic_cursor_hand);
1977 over = true;
1978
1979 showTooltip(music->studio, (*btn)->alt && music->tickCounter % (TIC80_FRAMERATE * 2) < TIC80_FRAMERATE ? (*btn)->alt : (*btn)->tip);
1980
1981 if (checkMouseClick(music->studio, &rect, tic_mouse_left))
1982 (*btn)->handler(music);
1983 }
1984
1985 tic_color color = *btn == &FollowButton && music->follow
1986 || *btn == &SustainButton && music->sustain
1987 || *btn == &LoopButton && music->loop
1988 ? tic_color_green
1989 : over ? tic_color_grey : tic_color_light_grey;
1990
1991 drawBitIcon(music->studio, (*btn)->icon, rect.x, rect.y, color);
1992 }
1993}
1994
1995static void drawModeTabs(Music* music)
1996{
1997 static const u8 Icons[] = {tic_icon_piano, tic_icon_tracker};
1998
1999 enum { Width = 7, Height = 7, Count = COUNT_OF(Icons) };
2000
2001 for (s32 i = 0; i < Count; i++)
2002 {
2003 tic_rect rect = { TIC80_WIDTH - Width * (Count - i), 0, Width, Height };
2004
2005 static const s32 Tabs[] = { MUSIC_PIANO_TAB, MUSIC_TRACKER_TAB };
2006
2007 bool over = false;
2008
2009 if (checkMousePos(music->studio, &rect))
2010 {
2011 setCursor(music->studio, tic_cursor_hand);
2012 over = true;
2013
2014 static const char* Tooltips[] = { "PIANO MODE", "TRACKER MODE" };
2015 showTooltip(music->studio, Tooltips[i]);
2016
2017 if (checkMouseClick(music->studio, &rect, tic_mouse_left))
2018 music->tab = Tabs[i];
2019 }
2020
2021 if (music->tab == Tabs[i])
2022 {
2023 tic_api_rect(music->tic, rect.x, rect.y, rect.w, rect.h, tic_color_grey);
2024 drawBitIcon(music->studio, Icons[i], rect.x, rect.y + 1, tic_color_black);
2025 }
2026
2027 drawBitIcon(music->studio, Icons[i], rect.x, rect.y, music->tab == Tabs[i] ? tic_color_white : over ? tic_color_grey : tic_color_light_grey);
2028 }
2029}
2030
2031static void drawMusicToolbar(Music* music)
2032{
2033 tic_api_rect(music->tic, 0, 0, TIC80_WIDTH, TOOLBAR_SIZE, tic_color_white);
2034
2035 drawPlayButtons(music);
2036 drawModeTabs(music);
2037}
2038
2039static void drawPianoCursor(Music* music, s32 x, s32 y, const char* val)
2040{
2041 tic_mem* tic = music->tic;
2042
2043 s32 subCol = music->piano.edit.x & 1;
2044 tic_point pos = {x + subCol * TIC_FONT_WIDTH, y};
2045 tic_api_rect(tic, pos.x - 1, pos.y - 1, TIC_FONT_WIDTH + 1, TIC_FONT_HEIGHT + 1, tic_color_red);
2046 tic_api_print(tic, (char[]){val[subCol], '\0'}, pos.x, pos.y, tic_color_black, true, 1, false);
2047}
2048
2049static const char* getPatternLabel(Music* music, s32 frame, s32 channel)
2050{
2051 static char index[sizeof "--"];
2052
2053 strcpy(index, "--");
2054
2055 s32 pattern = tic_tool_get_pattern_id(getTrack(music), frame, channel);
2056
2057 if(pattern)
2058 sprintf(index, "%02i", pattern);
2059
2060 return index;
2061}
2062
2063static void drawPianoFrames(Music* music, s32 x, s32 y)
2064{
2065 tic_mem* tic = music->tic;
2066
2067 enum {Width = 66, Height = 106, Header = 10, ColWidth = TIC_FONT_WIDTH * 2 + 1};
2068
2069 drawEditPanel(music, x, y, Width, Height);
2070
2071 tic_api_print(tic, "FRM", x + 1, y + 2, tic_color_grey, true, 1, true);
2072
2073 {
2074 const tic_music_state* pos = getMusicPos(music);
2075 s32 playFrame = pos->music.track == music->track ? pos->music.frame : -1;
2076
2077 char index[] = "99";
2078 for(s32 i = 0; i < MUSIC_FRAMES; i++)
2079 {
2080 sprintf(index, "%02i", i);
2081 tic_api_print(tic, index, x + 1, y + Header + i * TIC_FONT_HEIGHT, playFrame == i ? tic_color_white : music->frame == i? tic_color_grey : tic_color_dark_grey, true, 1, false);
2082 }
2083
2084 if(playFrame >= 0)
2085 {
2086 drawBitIcon(music->studio, tic_icon_right, x - TIC_ALTFONT_WIDTH - 1, y + playFrame * TIC_FONT_HEIGHT + Header, tic_color_black);
2087 drawBitIcon(music->studio, tic_icon_right, x - TIC_ALTFONT_WIDTH - 1, y + playFrame * TIC_FONT_HEIGHT + (Header - 1), tic_color_white);
2088 }
2089 }
2090
2091 x += ColWidth + 1;
2092
2093 {
2094 tic_rect rect = {x, y + Header - 1, (TIC_FONT_WIDTH * 2 + 1) * TIC_SOUND_CHANNELS, MUSIC_FRAMES * TIC_FONT_HEIGHT + 1};
2095
2096 if(checkMousePos(music->studio, &rect))
2097 {
2098 setCursor(music->studio, tic_cursor_hand);
2099
2100 bool left = checkMouseClick(music->studio, &rect, tic_mouse_left);
2101 if(left || checkMouseClick(music->studio, &rect, tic_mouse_right))
2102 {
2103 s32 col = (tic_api_mouse(tic).x - rect.x) * TIC_SOUND_CHANNELS / rect.w;
2104 s32 row = (tic_api_mouse(tic).y - rect.y) * MUSIC_FRAMES / rect.h;
2105
2106 // move edit cursor if pattern already selected only
2107 if(col == music->piano.col && row == music->frame)
2108 {
2109 tic_point pos = {(tic_api_mouse(tic).x - rect.x) * TIC_SOUND_CHANNELS * 2 / rect.w, row};
2110 if(MEMCMP(music->piano.edit, pos))
2111 {
2112 s32 step = getStep(music);
2113 setChannelPattern(music, left ? +step : -step, pos.x / 2);
2114 }
2115 else music->piano.edit = pos;
2116 }
2117
2118 music->piano.col = col;
2119
2120 if(getMusicState(music) == tic_music_stop || !music->follow)
2121 music->frame = row;
2122 }
2123 }
2124 }
2125
2126 for(s32 c = 0; c < TIC_SOUND_CHANNELS; c++)
2127 {
2128 tic_api_rect(tic, x + c * ColWidth, y + 1,
2129 ColWidth, MUSIC_FRAMES * TIC_FONT_HEIGHT + (Header - 1), c & 1 ? tic_color_black : tic_color_dark_grey);
2130
2131 tic_api_print(tic, (char[]){'1' + c, '\0'}, x + (ColWidth - (TIC_ALTFONT_WIDTH - 1)) / 2 + c * ColWidth, y + 2,
2132 tic_color_grey, true, 1, true);
2133
2134
2135 for(s32 i = 0; i < MUSIC_FRAMES; i++)
2136 {
2137 const char* index = getPatternLabel(music, i, c);
2138
2139 tic_point pos = {x + 1 + c * ColWidth, y + Header + i * TIC_FONT_HEIGHT};
2140 tic_api_print(tic, index, pos.x, pos.y, c & 1 ? tic_color_dark_grey : tic_color_grey, true, 1, false);
2141 }
2142
2143 drawTumbler(music, x + 3 + c * ColWidth, y + 110, c);
2144 }
2145
2146 {
2147 const char* index = getPatternLabel(music, music->frame, music->piano.col);
2148
2149 tic_point pos = {x + 1 + music->piano.col * ColWidth, y + Header + music->frame * TIC_FONT_HEIGHT};
2150 tic_api_print(tic, index, pos.x, pos.y + 1, tic_color_black, true, 1, false);
2151 tic_api_print(tic, index, pos.x, pos.y, tic_color_white, true, 1, false);
2152
2153 }
2154
2155 // draw edit cursor
2156 switch(music->piano.edit.x / 2)
2157 {
2158 case PianoChannel1Column:
2159 case PianoChannel2Column:
2160 case PianoChannel3Column:
2161 case PianoChannel4Column:
2162 {
2163 const char* label = getPatternLabel(music, music->piano.edit.y, music->piano.edit.x / 2);
2164 drawPianoCursor(music, x + 1 + music->piano.edit.x / 2 * ColWidth, y + Header + music->piano.edit.y * TIC_FONT_HEIGHT, label);
2165 }
2166 }
2167}
2168
2169static void drawStereoSeparator(Music* music, s32 x, s32 y)
2170{
2171 tic_mem* tic = music->tic;
2172 static u8 Colors[] =
2173 {
2174 tic_color_dark_green, tic_color_green, tic_color_light_green, tic_color_light_green
2175 };
2176
2177 for(s32 i = 0; i < COUNT_OF(Colors); i++)
2178 tic_api_rect(tic, x + i * 4, y, 4, 1, Colors[i]);
2179
2180 for(s32 i = COUNT_OF(Colors)-1; i >= 0; i--)
2181 tic_api_rect(tic, x + 27 - i * 4, y, 4, 1, Colors[i]);
2182}
2183
2184static void drawPianoRoll(Music* music, s32 x, s32 y)
2185{
2186 tic_mem* tic = music->tic;
2187
2188 static const struct Button {u8 note; u8 up; u8 down; u8 offset; u8 flip;} Buttons[] =
2189 {
2190 {0, 39, 40, 0, tic_no_flip},
2191 {2, 39, 40, 3, tic_horz_flip},
2192 {2, 39, 40, 8, tic_no_flip},
2193 {4, 39, 40, 11, tic_horz_flip},
2194 {5, 39, 40, 20, tic_no_flip},
2195 {7, 39, 40, 23, tic_horz_flip},
2196 {7, 39, 40, 28, tic_no_flip},
2197 {9, 39, 40, 31, tic_horz_flip},
2198 {9, 39, 40, 36, tic_no_flip},
2199 {11, 39, 40, 39, tic_horz_flip},
2200 {1, 41, 42, 0, tic_no_flip},
2201 {3, 41, 42, 8, tic_no_flip},
2202 {6, 41, 42, 20, tic_no_flip},
2203 {8, 41, 42, 28, tic_no_flip},
2204 {10, 41, 42, 36, tic_no_flip},
2205 };
2206
2207 tiles2ram(tic->ram, &getConfig(music->studio)->cart->bank0.tiles);
2208
2209 for(s32 i = 0; i < COUNT_OF(Buttons); i++)
2210 {
2211 const struct Button* btn = &Buttons[i];
2212 tic_api_spr(tic, btn->note == music->piano.note[music->piano.col] ? btn->down : btn->up,
2213 x + btn->offset, y, 1, 1, (u8[]){tic_color_orange}, 1, 1, btn->flip, tic_no_rotate);
2214 }
2215}
2216
2217static void drawPianoRowColumn(Music* music, s32 x, s32 y)
2218{
2219 tic_mem* tic = music->tic;
2220 const tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
2221
2222 enum{Header = PIANO_PATTERN_HEADER};
2223
2224 tic_rect rect = {x, y, TIC_FONT_WIDTH*2 + 1, TRACKER_ROWS*TIC_FONT_HEIGHT + Header};
2225
2226 if(checkMousePos(music->studio, &rect))
2227 {
2228 if(checkMouseDown(music->studio, &rect, tic_mouse_left) || checkMouseDown(music->studio, &rect, tic_mouse_right))
2229 {
2230 setCursor(music->studio, tic_cursor_hand);
2231
2232 if(music->scroll.active)
2233 {
2234 music->scroll.pos = (music->scroll.start - tic_api_mouse(tic).y) / TIC_FONT_HEIGHT;
2235 updateScroll(music);
2236 }
2237 else
2238 {
2239 music->scroll.active = true;
2240 music->scroll.start = tic_api_mouse(tic).y + music->scroll.pos * TIC_FONT_HEIGHT;
2241 }
2242 }
2243 else music->scroll.active = false;
2244 }
2245
2246 tic_api_print(tic, "ROW", x + 1, y + 2, tic_color_grey, true, 1, true);
2247
2248 for(s32 r = 0; r < TRACKER_ROWS; r++)
2249 {
2250 s32 index = rowIndex(music, r);
2251
2252 char label[sizeof "00"];
2253 sprintf(label, "%02i", index);
2254 tic_api_print(tic, label, x + 1, y + Header + r * TIC_FONT_HEIGHT, pattern && noteBeat(music, index) ? tic_color_grey : tic_color_dark_grey, true, 1, false);
2255 }
2256}
2257
2258static void drawPianoNoteStatus(Music* music, s32 x, s32 y, s32 xpos, s32 ypos)
2259{
2260 tic_mem* tic = music->tic;
2261
2262 enum{Header = PIANO_PATTERN_HEADER, NoteWidth = 4, NoteHeight = TIC_FONT_HEIGHT};
2263
2264 tic_rect rect = {x, y + Header, NoteWidth * NOTES - 1, NoteHeight * TRACKER_ROWS - 1};
2265
2266 if(checkMousePos(music->studio, &rect))
2267 {
2268 {
2269 static const char Notes[] = "C D EF G A B";
2270 tic_api_print(tic, Notes, xpos, ypos, tic_color_dark_grey, true, 1, true);
2271 }
2272
2273 showTooltip(music->studio, "set note");
2274
2275 static const char* Notes[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"};
2276 static const s32 Offsets[] = {0, 0, 2, 2, 4, 5, 5, 7, 7, 9, 9, 11};
2277 s32 note = (tic_api_mouse(tic).x - rect.x) / NoteWidth;
2278
2279 tic_api_print(tic, Notes[note], xpos + Offsets[note] * NoteWidth, ypos, tic_color_yellow, true, 1, true);
2280 }
2281}
2282
2283static void drawPianoNoteColumn(Music* music, s32 x, s32 y)
2284{
2285 tic_mem* tic = music->tic;
2286
2287 enum{Header = PIANO_PATTERN_HEADER, NoteWidth = 4, NoteHeight = TIC_FONT_HEIGHT};
2288
2289 drawPianoRoll(music, x, y + 1);
2290
2291 tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
2292 if(pattern)
2293 {
2294 drawPianoNoteStatus(music, x, y, 87, 129);
2295
2296 for(s32 r = 0; r < TRACKER_ROWS; r++)
2297 {
2298 s32 index = rowIndex(music, r);
2299
2300 tic_track_row* row = &pattern->rows[index];
2301
2302 // draw piano roll
2303 for(s32 n = 0; n < NOTES; n++)
2304 {
2305 tic_rect rect = {x + n * NoteWidth, y + Header + r * NoteHeight, NoteWidth - 1, NoteHeight - 1};
2306
2307 bool over = false;
2308 if(checkMousePos(music->studio, &rect))
2309 {
2310 setCursor(music->studio, tic_cursor_hand);
2311 over = true;
2312
2313 if(checkMouseClick(music->studio, &rect, tic_mouse_left))
2314 {
2315 switch(row->note)
2316 {
2317 case NoteNone:
2318 row->note = NoteStart + n;
2319 row->octave = music->last.octave;
2320 tic_tool_set_track_row_sfx(row, music->last.sfx);
2321 playNote(music, row);
2322 break;
2323 case NoteStop:
2324 row->note = NoteNone;
2325 row->octave = 0;
2326 break;
2327 default:
2328 if(row->note - NoteStart == n)
2329 {
2330 row->note = NoteStop;
2331 row->octave = 0;
2332 }
2333 else
2334 {
2335 row->note = NoteStart + n;
2336 playNote(music, row);
2337 }
2338 }
2339
2340 history_add(music->history);
2341 }
2342 else if(checkMouseClick(music->studio, &rect, tic_mouse_right))
2343 {
2344 switch(row->note)
2345 {
2346 case NoteNone:
2347 row->note = NoteStop;
2348 row->octave = 0;
2349 break;
2350 case NoteStop:
2351 row->note = NoteStart + n;
2352 row->octave = music->last.octave;
2353 tic_tool_set_track_row_sfx(row, music->last.sfx);
2354 playNote(music, row);
2355 break;
2356 default:
2357 if(row->note - NoteStart == n)
2358 {
2359 row->note = NoteNone;
2360 row->octave = 0;
2361 }
2362 else
2363 {
2364 row->note = NoteStart + n;
2365 playNote(music, row);
2366 }
2367 }
2368
2369 history_add(music->history);
2370 }
2371 }
2372
2373 if(row->note == NoteStop)
2374 {
2375 // draw stop note
2376 tic_api_rect(tic, rect.x, rect.y, rect.w, rect.h, tic_color_dark_grey);
2377 tic_api_rect(tic, x + 1 + n * NoteWidth, y + r * NoteHeight + (Header + NoteHeight / 2 - 1), 1, 1, tic_color_red);
2378 }
2379 else tic_api_rect(tic, rect.x, rect.y, rect.w, rect.h, over ? tic_color_grey : tic_color_dark_grey);
2380 }
2381
2382 if(noteBeat(music, index) && row->note != NoteStop)
2383 {
2384 static u8 Colors[NOTES] =
2385 {
2386 tic_color_grey, tic_color_grey, tic_color_light_grey,
2387 tic_color_light_grey, tic_color_light_grey, tic_color_white,
2388 tic_color_white, tic_color_light_grey, tic_color_light_grey,
2389 tic_color_light_grey, tic_color_grey, tic_color_grey
2390 };
2391
2392 for(s32 i = 0; i < COUNT_OF(Colors); i++)
2393 tic_api_rect(tic, x + i * NoteWidth, y + r * NoteHeight + (Header + NoteHeight / 2 - 1), NoteWidth - 1, 1, Colors[i]);
2394 }
2395
2396 if (row->note >= NoteStart)
2397 tic_api_rect(tic, x + (row->note - NoteStart) * NoteWidth, y + Header + r * NoteHeight, NoteWidth - 1, NoteHeight - 1, tic_color_light_green);
2398 }
2399 }
2400 else
2401 for(s32 r = 0; r < TRACKER_ROWS; r++)
2402 for(s32 i = 0; i < NOTES; i++)
2403 tic_api_rect(tic, x + i * NoteWidth, y + r * NoteHeight + (Header + NoteHeight / 2 - 1), NoteWidth - 1, 1, tic_color_dark_grey);
2404}
2405
2406static void drawPianoOctaveStatus(Music* music, s32 x, s32 y, s32 xpos, s32 ypos)
2407{
2408 tic_mem* tic = music->tic;
2409
2410 enum{Header = PIANO_PATTERN_HEADER, OctaveWidth = 4, OctaveHeight = TIC_FONT_HEIGHT};
2411
2412 tic_rect rect = {x, y + Header, OctaveWidth * OCTAVES - 1, OctaveHeight * TRACKER_ROWS - 1};
2413
2414 if(checkMousePos(music->studio, &rect))
2415 {
2416 s32 octave = (tic_api_mouse(tic).x - rect.x) / OctaveWidth;
2417 s32 r = (tic_api_mouse(tic).y - rect.y) / OctaveHeight;
2418
2419 tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
2420 const tic_track_row* row = &pattern->rows[rowIndex(music, r)];
2421
2422 if(row->note >= NoteStart)
2423 {
2424 showTooltip(music->studio, "set octave");
2425
2426 tic_api_print(tic, "12345678", xpos, ypos, tic_color_dark_grey, true, 1, true);
2427 tic_api_print(tic, (char[]){octave + '1', '\0'}, xpos + octave * OctaveWidth, ypos, tic_color_yellow, true, 1, true);
2428 }
2429 }
2430}
2431
2432static void drawPianoOctaveColumn(Music* music, s32 x, s32 y)
2433{
2434 tic_mem* tic = music->tic;
2435
2436 enum{Header = PIANO_PATTERN_HEADER, OctaveWidth = 4, OctaveHeight = TIC_FONT_HEIGHT};
2437
2438 tic_api_print(tic, "OCTAVE", x + 4, y + 2, tic_color_grey, true, 1, true);
2439 drawStereoSeparator(music, x, y + 8);
2440
2441 tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
2442 if(pattern)
2443 {
2444 drawPianoOctaveStatus(music, x, y, 136, 129);
2445
2446 for(s32 r = 0; r < TRACKER_ROWS; r++)
2447 {
2448 s32 index = rowIndex(music, r);
2449 tic_track_row* row = &pattern->rows[index];
2450
2451 if(row->note >= NoteStart)
2452 {
2453 for(s32 n = 0; n < OCTAVES; n++)
2454 {
2455 tic_rect rect = {x + n * OctaveWidth, y + Header + r * OctaveHeight, OctaveWidth - 1, OctaveHeight - 1};
2456
2457 bool over = false;
2458 if(checkMousePos(music->studio, &rect))
2459 {
2460 setCursor(music->studio, tic_cursor_hand);
2461 over = true;
2462
2463 if(checkMouseClick(music->studio, &rect, tic_mouse_left))
2464 {
2465 music->last.octave = row->octave = n;
2466 history_add(music->history);
2467 playNote(music, row);
2468 }
2469 }
2470
2471 tic_api_rect(tic, rect.x, rect.y, rect.w, rect.h, over ? tic_color_grey : tic_color_dark_grey);
2472 }
2473 }
2474 else
2475 for(s32 i = 0; i < OCTAVES; i++)
2476 tic_api_rect(tic, x + i * OctaveWidth, y + r * OctaveHeight + (Header + OctaveHeight / 2 - 1), OctaveWidth - 1, 1, tic_color_dark_grey);
2477
2478 if(noteBeat(music, index))
2479 {
2480 static u8 Colors[OCTAVES] =
2481 {
2482 tic_color_grey, tic_color_grey, tic_color_light_grey, tic_color_white,
2483 tic_color_white, tic_color_light_grey, tic_color_grey, tic_color_grey
2484 };
2485
2486 for(s32 i = 0; i < COUNT_OF(Colors); i++)
2487 tic_api_rect(tic, x + i * OctaveWidth, y + r * OctaveHeight + (Header + OctaveHeight / 2 - 1), OctaveWidth - 1, 1, Colors[i]);
2488 }
2489
2490 if(row->note >= NoteStart)
2491 tic_api_rect(tic, x + row->octave * OctaveWidth, y + Header + r * OctaveHeight, OctaveWidth - 1, OctaveHeight - 1, tic_color_orange);
2492 }
2493 }
2494 else
2495 for(s32 r = 0; r < TRACKER_ROWS; r++)
2496 for(s32 i = 0; i < OCTAVES; i++)
2497 tic_api_rect(tic, x + i * OctaveWidth, y + r * OctaveHeight + (Header + OctaveHeight / 2 - 1), OctaveWidth - 1, 1, tic_color_dark_grey);
2498}
2499
2500static void drawPianoSfxColumn(Music* music, s32 x, s32 y)
2501{
2502 tic_mem* tic = music->tic;
2503
2504 enum{Header = PIANO_PATTERN_HEADER};
2505
2506 {
2507 tic_rect rect = {x, y + Header - 1, TIC_FONT_WIDTH * 2, TIC_FONT_HEIGHT * MUSIC_FRAMES};
2508
2509 if(checkMousePos(music->studio, &rect))
2510 {
2511 setCursor(music->studio, tic_cursor_hand);
2512
2513 showTooltip(music->studio, "set sfx");
2514
2515 bool left = checkMouseClick(music->studio, &rect, tic_mouse_left);
2516 if(left || checkMouseClick(music->studio, &rect, tic_mouse_right))
2517 {
2518 tic_point pos = {PianoSfxColumn * 2 + (tic_api_mouse(tic).x - rect.x) / TIC_FONT_WIDTH, (tic_api_mouse(tic).y - rect.y) / TIC_FONT_HEIGHT};
2519
2520 if(MEMCMP(pos, music->piano.edit))
2521 {
2522 tic_track_row* row = getPianoRow(music);
2523
2524 if(row && row->note >= NoteStart)
2525 {
2526 s32 step = getStep(music);
2527 s32 sfx = tic_tool_get_track_row_sfx(row) + (left ? +step : -step);
2528 tic_tool_set_track_row_sfx(row, tic_modulo(sfx, SFX_COUNT));
2529 music->last.sfx = tic_tool_get_track_row_sfx(row);
2530 history_add(music->history);
2531 playNote(music, row);
2532 }
2533 }
2534 else music->piano.edit = pos;
2535 }
2536 }
2537 }
2538
2539 tic_api_rect(tic, x, y + 1, TIC_FONT_WIDTH*2 + 1, Header + TRACKER_ROWS * TIC_FONT_HEIGHT - 1, tic_color_dark_grey);
2540 tic_api_print(tic, "SFX", x + 1, y + 2, tic_color_grey, true, 1, true);
2541
2542 const tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
2543 if(pattern)
2544 {
2545 for(s32 r = 0; r < TRACKER_ROWS; r++)
2546 {
2547 s32 index = rowIndex(music, r);
2548 const tic_track_row* row = &pattern->rows[index];
2549
2550 if (row->note >= NoteStart)
2551 {
2552 tic_rect rect = {x, y + Header + r * TIC_FONT_HEIGHT - 1, TIC_FONT_WIDTH*2+1, TIC_FONT_HEIGHT+1};
2553
2554 char sfx[sizeof "00"];
2555 sprintf(sfx, "%02i", tic_tool_get_track_row_sfx(row));
2556 tic_api_print(tic, sfx, rect.x + 1, rect.y + 2, tic_color_black, true, 1, false);
2557 tic_api_print(tic, sfx, rect.x + 1, rect.y + 1, tic_color_yellow, true, 1, false);
2558 }
2559 else
2560 tic_api_print(tic, "--",
2561 x + 1, y + Header + r * TIC_FONT_HEIGHT,
2562 noteBeat(music, index) ? tic_color_light_grey : tic_color_grey, true, 1, false);
2563 }
2564 }
2565 else
2566 for(s32 r = 0; r < TRACKER_ROWS; r++)
2567 tic_api_print(tic, "--", x + 1, y + Header + r * TIC_FONT_HEIGHT, tic_color_grey, true, 1, false);
2568
2569 if(music->piano.edit.x / 2 == PianoSfxColumn)
2570 {
2571 char sfx[] = "--";
2572 const tic_track_row* row = getPianoRow(music);
2573 if (row && row->note >= NoteStart)
2574 sprintf(sfx, "%02i", tic_tool_get_track_row_sfx(row));
2575
2576 drawPianoCursor(music, x + 1, y + Header + music->piano.edit.y * TIC_FONT_HEIGHT, sfx);
2577 }
2578}
2579
2580static void drawPianoCommandColumn(Music* music, s32 x, s32 y)
2581{
2582 tic_mem* tic = music->tic;
2583
2584 enum{Header = PIANO_PATTERN_HEADER};
2585
2586 tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
2587 tic_music_command command = tic_music_cmd_empty;
2588 s32 overRow = -1;
2589 if(pattern)
2590 {
2591 tic_rect rect = {x, y + Header - 1, TIC_FONT_WIDTH * (tic_music_cmd_count - 1), TIC_FONT_HEIGHT * MUSIC_FRAMES};
2592
2593 if(checkMousePos(music->studio, &rect))
2594 {
2595 setCursor(music->studio, tic_cursor_hand);
2596
2597 showTooltip(music->studio, "set command");
2598
2599 command = (tic_api_mouse(tic).x - rect.x) / TIC_FONT_WIDTH + 1;
2600 overRow = (tic_api_mouse(tic).y - rect.y) / TIC_FONT_HEIGHT;
2601
2602 if(command != tic_music_cmd_empty)
2603 {
2604 #define MUSIC_CMD_HINT(_, letter, hint) "[" #letter "] " hint,
2605 static const char* Hints[] =
2606 {
2607 MUSIC_CMD_LIST(MUSIC_CMD_HINT)
2608 };
2609 #undef MUSIC_CMD_HINT
2610
2611 tic_api_print(tic, Hints[command], 73, 129, tic_color_yellow, false, 1, true);
2612 }
2613
2614 if(checkMouseClick(music->studio, &rect, tic_mouse_left))
2615 {
2616 tic_track_row* row = &pattern->rows[rowIndex(music, overRow)];
2617
2618 row->command = command != row->command ? command : tic_music_cmd_empty;
2619
2620 if(row->command == tic_music_cmd_empty)
2621 row->param1 = row->param2 = 0;
2622 else
2623 setCommandDefaults(row);
2624
2625 history_add(music->history);
2626 }
2627 }
2628 }
2629
2630 tic_api_print(tic, "COMMAND", x + 8, y + 2, tic_color_grey, true, 1, true);
2631 drawStereoSeparator(music, x + 6, y + 8);
2632
2633 if(pattern)
2634 {
2635 for(s32 r = 0; r < TRACKER_ROWS; r++)
2636 {
2637 s32 index = rowIndex(music, r);
2638 const tic_track_row* row = &pattern->rows[index];
2639
2640 tic_api_print(tic, MusicCommands + 1,
2641 x + 1, y + Header + r * TIC_FONT_HEIGHT,
2642 noteBeat(music, index) ? tic_color_grey : tic_color_dark_grey, true, 1, false);
2643
2644 if(overRow == r && command > tic_music_cmd_empty)
2645 tic_api_print(tic, (char[]){MusicCommands[command], '\0'},
2646 x + 1 + (command - 1) * TIC_FONT_WIDTH, y + Header + r * TIC_FONT_HEIGHT,
2647 noteBeat(music, index) ? tic_color_light_grey : tic_color_grey, true, 1, false);
2648
2649 if(row->command > tic_music_cmd_empty)
2650 tic_api_print(tic, (char[]){MusicCommands[row->command], '\0'},
2651 x + 1 + (row->command - 1) * TIC_FONT_WIDTH, y + Header + r * TIC_FONT_HEIGHT,
2652 tic_color_light_blue, true, 1, false);
2653 }
2654 }
2655 else
2656 for(s32 r = 0; r < TRACKER_ROWS; r++)
2657 tic_api_print(tic, MusicCommands + 1,
2658 x + 1, y + Header + r * TIC_FONT_HEIGHT,
2659 tic_color_dark_grey, true, 1, false);
2660}
2661
2662static void drawPianoXYColumn(Music* music, s32 x, s32 y)
2663{
2664 tic_mem* tic = music->tic;
2665
2666 enum{Header = PIANO_PATTERN_HEADER};
2667
2668 const tic_track_pattern* pattern = getFramePattern(music, music->piano.col, music->frame);
2669 {
2670 tic_rect rect = {x, y + Header - 1, TIC_FONT_WIDTH * 2, TIC_FONT_HEIGHT * MUSIC_FRAMES};
2671
2672 if(checkMousePos(music->studio, &rect))
2673 {
2674 setCursor(music->studio, tic_cursor_hand);
2675 showTooltip(music->studio, "set command XY");
2676
2677 if(pattern)
2678 {
2679 s32 r = (tic_api_mouse(tic).y - rect.y) / TIC_FONT_HEIGHT;
2680 const tic_track_row* row = &pattern->rows[rowIndex(music, r)];
2681
2682 if(row->command != tic_music_cmd_empty)
2683 {
2684 char val[sizeof "XY=000"];
2685 sprintf(val, "XY=%03i", (row->param1 << 4) | row->param2);
2686 tic_api_print(tic, val, 213, 129, tic_color_yellow, false, 1, true);
2687 }
2688 }
2689
2690 bool left = checkMouseClick(music->studio, &rect, tic_mouse_left);
2691 if(left || checkMouseClick(music->studio, &rect, tic_mouse_right))
2692 {
2693 tic_point pos = {PianoXYColumn * 2 + (tic_api_mouse(tic).x - rect.x) / TIC_FONT_WIDTH, (tic_api_mouse(tic).y - rect.y) / TIC_FONT_HEIGHT};
2694
2695 if(MEMCMP(music->piano.edit, pos))
2696 {
2697 tic_track_row* row = getPianoRow(music);
2698 if(row && row->command > tic_music_cmd_empty)
2699 {
2700 s32 step = getStep(music);
2701 s32 delta = left ? +step : -step;
2702 if(music->piano.edit.x & 1)
2703 row->param2 += delta;
2704 else row->param1 += delta;
2705
2706 history_add(music->history);
2707 }
2708 }
2709 else music->piano.edit = pos;
2710 }
2711 }
2712 }
2713
2714 tic_api_rect(tic, x, y + 1, TIC_FONT_WIDTH*2 + 1, Header + TRACKER_ROWS * TIC_FONT_HEIGHT - 1, tic_color_dark_grey);
2715 tic_api_print(tic, "X", x + 2, y + 2, tic_color_grey, true, 1, true);
2716 tic_api_print(tic, "Y", x + 8, y + 2, tic_color_grey, true, 1, true);
2717
2718 if(pattern)
2719 {
2720
2721 for(s32 r = 0; r < TRACKER_ROWS; r++)
2722 {
2723 s32 index = rowIndex(music, r);
2724 const tic_track_row* row = &pattern->rows[index];
2725
2726 tic_music_command command = MusicCommands[row->command];
2727
2728 if(row->command > tic_music_cmd_empty)
2729 {
2730 char xy[sizeof "00"];
2731 tic_api_print(tic, xy, x + 1, y + Header + r * TIC_FONT_HEIGHT, tic_color_dark_grey, true, 1, false);
2732 sprintf(xy, "%01X%01X", row->param1, row->param2);
2733 tic_api_print(tic, xy, x + 1, y + Header + r * TIC_FONT_HEIGHT + 1, tic_color_black, true, 1, false);
2734 tic_api_print(tic, xy, x + 1, y + Header + r * TIC_FONT_HEIGHT, tic_color_light_blue, true, 1, false);
2735 }
2736 else
2737 tic_api_print(tic, "--",
2738 x + 1, y + Header + r * TIC_FONT_HEIGHT,
2739 noteBeat(music, index) ? tic_color_light_grey : tic_color_grey, true, 1, false);
2740 }
2741 }
2742 else
2743 for(s32 r = 0; r < TRACKER_ROWS; r++)
2744 tic_api_print(tic, "--", x + 1, y + Header + r * TIC_FONT_HEIGHT, tic_color_grey, true, 1, false);
2745
2746 if(music->piano.edit.x / 2 == PianoXYColumn)
2747 {
2748 char xy[] = "--";
2749 const tic_track_row* row = getPianoRow(music);
2750 if(row && row->command > tic_music_cmd_empty)
2751 sprintf(xy, "%01X%01X", row->param1, row->param2);
2752
2753 drawPianoCursor(music, x + 1, y + Header + music->piano.edit.y * TIC_FONT_HEIGHT, xy);
2754 }
2755}
2756
2757static void drawPianoPattern(Music* music, s32 x, s32 y)
2758{
2759 tic_mem* tic = music->tic;
2760
2761 enum{Width = 164, Height = 106};
2762 drawEditPanel(music, x, y, Width, Height);
2763
2764 // draw playing row
2765 if(checkPlayFrame(music, music->frame))
2766 {
2767 const tic_music_state* pos = getMusicPos(music);
2768 s32 index = pos->music.row - music->scroll.pos;
2769
2770 if(index >= 0 && index < TRACKER_ROWS)
2771 tic_api_rect(tic, x, y + PIANO_PATTERN_HEADER + index * TIC_FONT_HEIGHT - 1, Width, TIC_FONT_HEIGHT + 1, tic_color_light_grey);
2772 }
2773
2774 drawPianoRowColumn(music, x, y);
2775 drawPianoNoteColumn(music, x + 14, y);
2776 drawPianoOctaveColumn(music, x + 63, y);
2777 drawPianoSfxColumn(music, x + 95, y);
2778 drawPianoCommandColumn(music, x + 108, y);
2779 drawPianoXYColumn(music, x + 151, y);
2780}
2781
2782static void drawBeatButton(Music* music, s32 x, s32 y)
2783{
2784 tic_mem* tic = music->tic;
2785
2786 static const char Label44[] = "4/4";
2787 static const char Label34[] = "3/4";
2788
2789 tic_rect rect = {x, y, (sizeof Label44 - 1) * TIC_ALTFONT_WIDTH - 1, TIC_FONT_HEIGHT - 1};
2790
2791 bool down = false;
2792 if(checkMousePos(music->studio, &rect))
2793 {
2794 setCursor(music->studio, tic_cursor_hand);
2795 showTooltip(music->studio, music->beat34 ? "set 4 quarter note" : "set 3 quarter note");
2796
2797 if(checkMouseDown(music->studio, &rect, tic_mouse_left))
2798 down = true;
2799
2800 if(checkMouseClick(music->studio, &rect, tic_mouse_left))
2801 music->beat34 = !music->beat34;
2802 }
2803
2804 tic_api_print(tic, music->beat34 ? Label34 : Label44, x, y + 1, tic_color_black, true, 1, true);
2805 tic_api_print(tic, music->beat34 ? Label34 : Label44, x, y + (down ? 1 : 0), tic_color_white, true, 1, true);
2806}
2807
2808static void drawPianoLayout(Music* music)
2809{
2810 drawPianoFrames(music, 3, 20);
2811 drawPianoPattern(music, 73, 20);
2812 drawBeatButton(music, 4, 129);
2813}
2814
2815static void scrollNotes(Music* music, s32 delta)
2816{
2817 tic_track_pattern* pattern = getChannelPattern(music);
2818
2819 if(pattern)
2820 {
2821 tic_rect rect = music->tracker.select.rect;
2822
2823 if(rect.h <= 0)
2824 {
2825 rect.y = music->tracker.edit.y;
2826 rect.h = 1;
2827 }
2828
2829 for(s32 i = rect.y; i < rect.y + rect.h; i++)
2830 {
2831 s32 note = pattern->rows[i].note + pattern->rows[i].octave * NOTES - NoteStart;
2832
2833 note += delta;
2834
2835 if(note >= 0 && note < NOTES*OCTAVES)
2836 {
2837 pattern->rows[i].note = note % NOTES + NoteStart;
2838 pattern->rows[i].octave = note / NOTES;
2839 }
2840 }
2841
2842 history_add(music->history);
2843 }
2844}
2845
2846static void drawWaveform(Music* music, s32 x, s32 y)
2847{
2848 tic_mem* tic = music->tic;
2849
2850 enum{Width = 32, Height = 8, WaveRows = 1 << WAVE_VALUE_BITS};
2851
2852 drawEditPanel(music, x, y, Width, Height);
2853
2854 // detect playing channels
2855 s32 channels = 0;
2856 for(s32 c = 0; c < TIC_SOUND_CHANNELS; c++)
2857 if(music->on[c] && tic->ram->registers[c].volume)
2858 channels++;
2859
2860 if(channels)
2861 {
2862 for(s32 i = 0; i < WAVE_VALUES; i++)
2863 {
2864 s32 lamp = 0, ramp = 0;
2865
2866 for(s32 c = 0; c < TIC_SOUND_CHANNELS; c++)
2867 {
2868 s32 amp = calcWaveAnimation(tic, i + music->tickCounter, c) / channels;
2869
2870 lamp += amp * tic_tool_peek4(&tic->ram->stereo.data, c*2);
2871 ramp += amp * tic_tool_peek4(&tic->ram->stereo.data, c*2 + 1);
2872 }
2873
2874 lamp /= WAVE_MAX_VALUE * WAVE_MAX_VALUE;
2875 ramp /= WAVE_MAX_VALUE * WAVE_MAX_VALUE;
2876
2877 tic_api_rect(tic, x + i, y + (Height-1) - ramp * Height / WaveRows, 1, 1, tic_color_yellow);
2878 tic_api_rect(tic, x + i, y + (Height-1) - lamp * Height / WaveRows, 1, 1, tic_color_light_green);
2879 }
2880 }
2881}
2882
2883static void updatePianoRollState(Music* music)
2884{
2885 if(getMusicState(music) != tic_music_stop)
2886 {
2887 s32 channel = music->piano.col;
2888 const tic_music_state* pos = getMusicPos(music);
2889
2890 for(s32 c = 0; c < TIC_SOUND_CHANNELS; c++)
2891 {
2892 const tic_track_pattern* pattern = getFramePattern(music, c, pos->music.frame);
2893 if(pattern)
2894 {
2895 const tic_track_row* row = &pattern->rows[pos->music.row];
2896
2897 if(pos->music.row == 0 && !music->sustain)
2898 music->piano.note[c] = -1;
2899
2900 if(row->note >= NoteStart)
2901 music->piano.note[c] = row->note - NoteStart;
2902 else if(row->note == NoteStop || (row->note == NoteNone && pos->music.row == 0 && !music->sustain))
2903 music->piano.note[c] = -1;
2904 }
2905 else if(!music->sustain)
2906 music->piano.note[c] = -1;
2907 }
2908 }
2909 else memset(music->piano.note, -1, sizeof music->piano.note);
2910}
2911
2912static void tick(Music* music)
2913{
2914 tic_mem* tic = music->tic;
2915
2916 // process scroll
2917 {
2918 tic80_input* input = &tic->ram->input;
2919
2920 if(input->mouse.scrolly)
2921 {
2922 if(tic_api_key(tic, tic_key_ctrl))
2923 {
2924 scrollNotes(music, input->mouse.scrolly > 0 ? 1 : -1);
2925 }
2926 else
2927 {
2928 enum{Scroll = NOTES_PER_BEAT};
2929 s32 delta = input->mouse.scrolly > 0 ? -Scroll : Scroll;
2930
2931 music->scroll.pos += delta;
2932
2933 updateScroll(music);
2934 }
2935 }
2936 }
2937
2938 processKeyboard(music);
2939
2940 if(music->follow)
2941 {
2942 const tic_music_state* pos = getMusicPos(music);
2943
2944 if(pos->music.track == music->track &&
2945 music->tracker.edit.y >= 0 &&
2946 pos->music.row >= 0)
2947 {
2948 music->frame = pos->music.frame;
2949 music->tracker.edit.y = pos->music.row;
2950 updateTracker(music);
2951 }
2952 }
2953
2954 for (s32 i = 0; i < TIC_SOUND_CHANNELS; i++)
2955 if(!music->on[i])
2956 tic->ram->registers[i].volume = 0;
2957
2958 updatePianoRollState(music);
2959
2960 tic_api_cls(music->tic, tic_color_grey);
2961 drawTopPanel(music, 2, TOOLBAR_SIZE + 3);
2962 drawWaveform(music, 205, 9);
2963
2964 switch (music->tab)
2965 {
2966 case MUSIC_TRACKER_TAB: drawTrackerLayout(music, 7, 35); break;
2967 case MUSIC_PIANO_TAB: drawPianoLayout(music); break;
2968 }
2969
2970 drawMusicToolbar(music);
2971 drawToolbar(music->studio, music->tic, false);
2972
2973 music->tickCounter++;
2974}
2975
2976static void onStudioEvent(Music* music, StudioEvent event)
2977{
2978 switch (event)
2979 {
2980 case TIC_TOOLBAR_CUT: copyToClipboard(music, true); break;
2981 case TIC_TOOLBAR_COPY: copyToClipboard(music, false); break;
2982 case TIC_TOOLBAR_PASTE: copyFromClipboard(music); break;
2983 case TIC_TOOLBAR_UNDO: undo(music); break;
2984 case TIC_TOOLBAR_REDO: redo(music); break;
2985 default: break;
2986 }
2987}
2988
2989void initMusic(Music* music, Studio* studio, tic_music* src)
2990{
2991 if (music->history) history_delete(music->history);
2992
2993 *music = (Music)
2994 {
2995 .studio = studio,
2996 .tic = getMemory(studio),
2997 .tick = tick,
2998 .src = src,
2999 .track = 0,
3000 .beat34 = false,
3001 .frame = 0,
3002 .follow = true,
3003 .sustain = false,
3004 .scroll =
3005 {
3006 .pos = 0,
3007 .start = 0,
3008 .active = false,
3009 },
3010 .last =
3011 {
3012 .octave = 3,
3013 .sfx = 0,
3014 },
3015 .on = {true, true, true, true},
3016 .tracker =
3017 {
3018 .edit = {0, 0},
3019
3020 .select =
3021 {
3022 .start = {0, 0},
3023 .rect = {0, 0, 0, 0},
3024 .drag = false,
3025 },
3026 },
3027
3028 .piano =
3029 {
3030 .col = 0,
3031 .edit = {0, 0},
3032 .note = {-1, -1, -1, -1},
3033 },
3034
3035 .tickCounter = 0,
3036 .tab = MUSIC_PIANO_TAB,
3037 .history = history_create(src, sizeof(tic_music)),
3038 .event = onStudioEvent,
3039 };
3040
3041 resetSelection(music);
3042}
3043
3044void freeMusic(Music* music)
3045{
3046 history_delete(music->history);
3047 free(music);
3048}
3049