Added graphics for resize notes.
[epichord.git] / src / pianoroll.cpp
bloba02dc0d4129a74f6fb602dc8c7c8c681190d7cba
1 /*
2 Epichord - a midi sequencer
3 Copyright (C) 2008 Evan Rinehart
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to
18 The Free Software Foundation, Inc.
19 51 Franklin Street, Fifth Floor
20 Boston, MA 02110-1301, USA
23 #include <vector>
24 #include <fltk/Group.h>
25 #include <fltk/Widget.h>
26 #include <fltk/events.h>
28 #include <stdio.h>
29 #include <unistd.h>
30 #include "ui.h"
32 #include "util.h"
34 #include "backend.h"
36 #include "uihelper.h"
38 extern UI* ui;
39 extern std::vector<track*> tracks;
41 extern struct conf config;
43 using namespace fltk;
45 #define SWAP(X,Y) tmp=X; X=Y; Y=tmp;
47 PianoRoll::PianoRoll(int x, int y, int w, int h, const char* label = 0) : fltk::Widget(x, y, w, h, label) {
48 wkeyh = 12;
49 bkeyh = 7;
50 cur_seqpat = NULL;
52 zoom = 15;
53 zoom_n = 3;
55 q_tick = 32;
57 xp_last = 0;
58 yp_last = 0;
60 box_flag = 0;
62 move_toffset = 0;
65 resize_arrow = 0;
66 resize_e = NULL;
67 resize_handle_width = 4;
70 int PianoRoll::handle(int event){
71 Command* c;
72 pattern* p;
73 mevent* e;
75 int X = event_x();
76 int Y = event_y();
78 switch(event){
79 case fltk::ENTER:
80 return 1;
81 case fltk::FOCUS:
82 return 1;
83 case fltk::SHORTCUT:
84 if(event_key()==fltk::DeleteKey){
85 apply_delete();
86 delete_flag = 0;
87 redraw();
88 resize_arrow = 0;
89 ui->event_edit->redraw();
90 return 1;
92 if(event_state(CTRL) && event_key()=='c'){
93 //printf("roll copy\n");
94 return 1;
96 if(zoom_out_key(event_key(),event_state())){
97 if(zoom_n > 1){
98 zoom_n--;
99 set_zoom(30*(1<<zoom_n)/16);
100 ui->pattern_timeline->zoom = zoom;
101 ui->pattern_timeline->update(get_play_position());
102 ui->pattern_timeline->redraw();
103 ui->event_edit->zoom = zoom;
104 ui->event_edit->redraw();
106 redraw();
107 return 1;
109 if(zoom_in_key(event_key(),event_state())){
110 if(zoom_n < 8){
111 zoom_n++;
112 set_zoom(30*(1<<zoom_n)/16);
113 ui->pattern_timeline->zoom = zoom;
114 ui->pattern_timeline->update(get_play_position());
115 ui->pattern_timeline->redraw();
116 ui->event_edit->zoom = zoom;
117 ui->event_edit->redraw();
119 redraw();
120 return 1;
122 return 0;
123 case fltk::PUSH:
124 take_focus();
125 e = over_note();
126 if(event_button()==1){//left mouse
127 if(e==NULL){//new note init
128 if(event_state()&fltk::SHIFT){//begin box
129 box_flag = 1;
130 box_x1=X;
131 box_x2=X;
132 box_y1=Y;
133 box_y2=Y;
134 box_t1=xpix2tick(X);
135 box_t2=box_t1;
136 box_n1=ypix2note(Y,1);
137 box_n2=box_n1;
139 else{//begin insert
140 insert_flag = 1;
141 insert_torig = quantize(xpix2tick(event_x()));
142 insert_toffset = q_tick;
143 //new_orig_t = new_left_t;
144 insert_note = ypix2note(event_y(),1);
146 last_note = insert_note;
147 if(config.playinsert){
148 ui->keyboard->play_note(last_note,0);
153 else{
155 if(!(e->selected) && !(event_state()&fltk::SHIFT)){
156 unselect_all();
158 e->selected = 1;
159 resize_arrow_color = fltk::color(128,128,0);
163 if(over_rhandle(e,X,Y)){//resize
167 RESIZE
172 else if(over_lhandle(e,X,Y)){//resize move
176 RESIZE
181 else{//begin move
182 move_flag = 1;
184 move_torig = e->tick;
185 move_qoffset = e->tick - quantize(e->tick);
187 move_toffset = -move_qoffset;
189 //move_offset = quantize(xpix2tick(X)) - move_torig - move_qoffset;
190 //move_toffset = 0;
191 move_offset = X - tick2xpix(e->tick);
192 move_norig = ypix2note(event_y(),1);
193 move_noffset = 0;
195 last_note = move_norig;
196 if(config.playmove){
197 ui->keyboard->play_note(last_note,0);
202 else if(event_button()==2){//middle mouse
203 //button initiates paste
205 else if(event_button()==3){//right mouse
206 if(e==NULL){
207 unselect_all();
209 ui->event_edit->redraw();
211 else{//set up for deletion
212 e->selected = 1;
213 delete_flag = 1;
214 resize_arrow_color = fltk::color(120,60,58);
218 redraw();
219 return 1;
220 case fltk::DRAG:
222 if(box_flag){
223 box_x2 = X;
224 box_y2 = Y;
225 box_t2 = xpix2tick(X);
226 box_n2 = ypix2note(Y,1);
228 else if(insert_flag){
229 insert_toffset = quantize(xpix2tick(X)+q_tick) - insert_torig;
230 if(insert_toffset<=0){
231 insert_toffset -= q_tick;
233 insert_note = ypix2note(Y,1);
234 if(insert_note != last_note){
235 if(config.playinsert){//play on insert
236 ui->keyboard->release_note(last_note,0);
237 ui->keyboard->play_note(insert_note,0);
239 last_note = insert_note;
242 else if(move_flag){
243 move_toffset = quantize(xpix2tick(X - move_offset)) - move_torig;
244 move_noffset = ypix2note(Y,1) - move_norig;
245 int N = move_norig+move_noffset;
246 if(N != last_note){
247 if(config.playmove){//play on move
248 ui->keyboard->release_note(last_note,0);
249 ui->keyboard->play_note(N,0);
251 last_note = N;
254 else if(rresize_flag){
257 else if(lresize_flag){
260 redraw();
261 return 1;
262 case fltk::RELEASE:
263 e = over_note();
264 if(event_button()==1){
265 if(box_flag){
266 apply_box();
267 ui->event_edit->redraw();
268 box_flag=0;
270 else if(rresize_flag){
273 else if(insert_flag){
274 apply_insert();
276 insert_flag = 0;
278 cur_seqpat->restate();
279 ui->keyboard->release_note(insert_note,0);
280 ui->keyboard->redraw();
281 ui->event_edit->has[0]=1;
282 ui->event_edit->has[1]=1;
283 ui->event_edit->redraw();
284 ui->event_menu->redraw();
286 else if(move_flag){
287 apply_move();
288 move_flag = 0;
290 int play_pos = get_play_position();
291 mevent* e = main_sel;
292 track* tr = tracks[cur_seqpat->track];
293 if(play_pos > e->tick && play_pos < e->tick + e->dur){
294 midi_note_off(e->value1,tr->chan,tr->port);
297 c=new MoveNote(cur_seqpat->p,main_sel,move_t,move_note);
298 set_undo(c);
299 undo_push(1);
301 //int old_note = e->value1;
302 int cur_chan = tracks[cur_seqpat->track]->chan;
303 int cur_port = tracks[cur_seqpat->track]->port;
304 //midi_note_off(old_note,cur_chan,cur_port);
306 cur_seqpat->restate();
307 midi_track_off(cur_seqpat->track);
308 ui->keyboard->release_note(last_note,0);
309 ui->keyboard->release_note(move_norig+move_noffset,0);
310 ui->keyboard->redraw();
311 ui->event_edit->redraw();
313 insert_flag=0;
314 move_flag=0;
316 if(event_button()==3){
317 mevent* over_n = over_note();
318 if(delete_flag && over_n){
319 if(over_n->selected){
320 apply_delete();
321 midi_track_off(cur_seqpat->track);
322 cur_seqpat->restate();
323 ui->event_edit->redraw();
326 delete_flag=0;
327 resize_arrow = 0;
329 redraw();
331 return 1;
333 case fltk::MOVE:
334 e = over_note();
335 if(e){
336 if(over_rhandle(e,X,Y)){
337 if(resize_e != e || resize_arrow != 1){
338 if(e->selected){resize_arrow_color = fltk::color(128,128,0);}
339 else{resize_arrow_color = fltk::color(95,58,119);}
340 resize_e = e;
341 resize_arrow = 1;
342 resize_x = tick2xpix(e->tick + e->dur) - resize_handle_width;
343 resize_y = note2ypix(e->value1);
344 redraw();
347 else if(over_lhandle(e,X,Y)){
348 if(resize_e != e || resize_arrow != 1){
349 if(e->selected){resize_arrow_color = fltk::color(128,128,0);}
350 else{resize_arrow_color = fltk::color(95,58,119);}
351 resize_e = e;
352 resize_arrow = -1;
353 resize_x = tick2xpix(e->tick)+1;
354 resize_y = note2ypix(e->value1);
355 redraw();
358 else{
359 if(resize_e != e || resize_arrow != 0){
360 resize_e = e;
361 resize_arrow = 0;
362 redraw();
366 else{
367 if(resize_arrow != 0){
368 resize_arrow = 0;
369 redraw();
373 return 1;
375 return 0;
378 void PianoRoll::draw(){
379 fltk::setcolor(fltk::GRAY05);
380 fltk::fillrect(0,0,w(),h());
382 fltk::setcolor(fltk::GRAY20);
383 for(int i=12; i<h(); i+=12){
384 fltk::drawline(0,i,w(),i);
386 for(int i=zoom; i<w(); i+=zoom){
387 fltk::drawline(i,0,i,h());
390 fltk::setcolor(fltk::GRAY30);
391 for(int i=12*5; i<h(); i+=12*7){
392 fltk::drawline(0,i,w(),i);
395 fltk::setcolor(fltk::GRAY50);
396 for(int i=zoom*4; i<w(); i+=zoom*4){
397 fltk::drawline(i,0,i,h());
400 fltk::setcolor(fltk::WHITE);
401 int M = config.beats_per_measure;
402 for(int i=zoom*4*M; i<w(); i+=zoom*4*M){
403 fltk::fillrect(i,0,1,h());
406 fltk::setcolor(fltk::color(128,0,0));
407 int rightend = tick2xpix(cur_seqpat->dur);
408 fltk::fillrect(rightend,0,1,h());
410 fltk::setcolor(fltk::color(128,128,0));
411 fltk::drawline(0,12*40,w(),12*40);
413 int tmp;
414 if(insert_flag){
415 fltk::setcolor(fltk::BLUE);
416 int T1 = insert_torig;
417 int T2 = T1 + insert_toffset;
418 if(T1>T2){SWAP(T1,T2);}
419 int X = tick2xpix(T1)+1;
420 int Y = note2ypix(insert_note);
421 int W = tick2xpix(T2) - X;
422 fltk::fillrect(X,Y,W,11);
425 if(move_flag){
426 fltk::setcolor(fltk::MAGENTA);
427 mevent* ptr = cur_seqpat->p->events->next;
428 while(ptr){
429 if(ptr->type == MIDI_NOTE_ON && ptr->selected){
430 int X = tick2xpix(ptr->tick+move_toffset)+1;
431 int Y = note2ypix(ptr->value1+move_noffset);
432 int W = tick2xpix(ptr->dur);
433 fltk::fillrect(X,Y,W-1,1);
434 fltk::fillrect(X,Y+11,W-1,1);
435 fltk::fillrect(X,Y,1,11);
436 fltk::fillrect(X+W-2,Y,1,11);
438 ptr=ptr->next;
444 //draw all notes
445 mevent* e = cur_seqpat->p->events->next;
447 fltk::Color c1,c2,c3;
449 while(e){
450 if(e->type == MIDI_NOTE_ON){
451 //fltk::fillrect(tick2xpix(e->tick),note2ypix(e->value),e->dur,11);
452 int X = tick2xpix(e->tick) + 1;
453 int Y = note2ypix(e->value1);
454 int W = tick2xpix(e->tick+e->dur) - X;
455 get_event_color(e,&c1,&c2,&c3);
457 fltk::setcolor(c1);
458 fltk::fillrect(X+1,Y+1,W-1,10);
460 fltk::setcolor(c2);
461 fltk::fillrect(X,Y+11,W,1);
462 fltk::fillrect(X+W-1,Y+1,1,11);
464 fltk::setcolor(c3);
465 fltk::fillrect(X,Y,W,1);
466 fltk::fillrect(X,Y,1,11);
468 e=e->next;
472 if(!rresize_flag && !lresize_flag){
473 if(resize_arrow > 0){
474 setcolor(resize_arrow_color);
476 int W = resize_handle_width;
477 int H = 12;
478 int X = resize_x;
479 int Y = resize_y;
481 addvertex(X,Y);
482 addvertex(X,Y+H);
483 addvertex(X+W,Y+H/2);
484 fillpath();
486 else if(resize_arrow < 0){
487 setcolor(resize_arrow_color);
489 int W = resize_handle_width;
490 int H = 12;
491 int X = resize_x;
492 int Y = resize_y;
494 addvertex(X+W,Y);
495 addvertex(X+W,Y+H);
496 addvertex(X,Y+H/2);
497 fillpath();
502 if(box_flag){
503 fltk::setcolor(fltk::GREEN);
504 int X1,X2,Y1,Y2;
505 if(box_x1>box_x2){
506 X1=box_x2;
507 X2=box_x1;
509 else{
510 X1=box_x1;
511 X2=box_x2;
513 if(box_y1>box_y2){
514 Y1=box_y2;
515 Y2=box_y1;
517 else{
518 Y1=box_y1;
519 Y2=box_y2;
521 fltk::fillrect(X1,Y1,X2-X1,1);
522 fltk::fillrect(X1,Y1,1,Y2-Y1);
523 fltk::fillrect(X2,Y1,1,Y2-Y1);
524 fltk::fillrect(X1,Y2,X2-X1,1);
530 static int kludge = 4; //very powerful magic
531 void PianoRoll::layout(){
533 /* the kludge is used so the fltk::ScrollGroup can update
534 widgets not contained within it. Better solution, the
535 scrollgroup could do its callback if it scrolls.
536 Subclassing fltk::ScrollGroup to add this behavior failed. */
537 if(kludge != 0){
538 kludge--;
539 return;
542 ui->pattern_timeline->zoom = zoom;
543 ui->event_edit->zoom = zoom;
545 if(cur_seqpat){
546 int W = tick2xpix(cur_seqpat->dur);
547 resize(W+300,h());
550 int wp = ui->pattern_scroll->w();
551 if(wp > w()){
552 w(wp+120);
555 int hp = ui->pattern_scroll->h();
556 if(hp > h()){
557 h(hp+120);
560 int xp = ui->pattern_scroll->xposition();
561 int yp = ui->pattern_scroll->yposition();
563 if(xp > w() - wp){
564 xp = w() - wp;
565 ui->pattern_scroll->scrollTo(xp,yp);
568 ui->pattern_timeline->scroll = xp;
569 ui->event_edit->scroll = xp;
570 ui->keyboard->scroll = yp;
572 if(cur_seqpat){
573 cur_seqpat->scrolly = yp;
574 cur_seqpat->scrollx = xp;
577 if(xp_last != xp){
578 ui->pattern_timeline->redraw();
579 ui->event_edit->redraw();
581 if(yp_last != yp){
582 ui->keyboard->redraw();
585 yp_last = yp;
586 xp_last = xp;
591 void PianoRoll::load(seqpat* s){
592 cur_seqpat = s;
593 cur_track = tracks[s->track];
594 int W = tick2xpix(s->dur);
595 resize(W+300,h());
597 ui->pattern_timeline->ticks_offset = s->tick;
600 int PianoRoll::note2ypix(int note){
601 int udy = 6*(note + (note+7)/12 + note/12) + 12;
602 return h() - udy + 1;
605 int PianoRoll::tick2xpix(int tick){
606 return tick*zoom*4 / 128;
609 int PianoRoll::xpix2tick(int xpix){
610 return xpix*128 / (zoom*4);
613 int PianoRoll::quantize(int tick){
614 return tick/q_tick * q_tick;
618 void PianoRoll::set_zoom(int z){
619 zoom = z;
620 relayout();
621 //int W = tick2xpix(cur_seqpat->dur);
622 //resize(W+300,h());
626 mevent* PianoRoll::over_note(){
627 mevent* e = cur_seqpat->p->events->next;
629 int cy, lx, rx;
630 while(e){
631 if(e->type == MIDI_NOTE_ON){
632 cy = note2ypix(e->value1);
633 lx = tick2xpix(e->tick);
634 rx = tick2xpix(e->tick+e->dur);
635 if(event_x() > lx && event_x() < rx &&
636 event_y() < cy+12 && event_y() > cy){
637 return e;
640 e = e->next;
643 return NULL;
648 void PianoRoll::update(int pos){
649 if(!is_backend_playing() || !cur_seqpat){
650 return;
652 int wp = ui->pattern_scroll->w();
653 int xp = ui->pattern_scroll->xposition();
654 int yp = ui->pattern_scroll->yposition();
655 int X1 = tick2xpix(pos-cur_seqpat->tick);
656 int X2 = X1 - xp;
657 if(X1 > w()-40){
658 return;
660 if(X2 < 0){
661 ui->pattern_scroll->scrollTo(X1-50<0?0:X1-50,yp);
663 if(X2 > wp-30){
664 ui->pattern_scroll->scrollTo(X1-50,yp);
669 void PianoRoll::unselect_all(){
670 mevent* e = cur_seqpat->p->events;
671 while(e){
672 if(e->type == MIDI_NOTE_ON && e->selected==1){
673 e->selected = 0;
675 e = e->next;
681 void PianoRoll::get_event_color(mevent* e, fltk::Color* c1, fltk::Color* c2, fltk::Color* c3){
683 int T1,T2;
684 int tmp;
685 if(delete_flag){
686 if(e->selected){
687 *c1 = fltk::color(229,79,75);
688 *c2 = fltk::color(120,60,58);
689 *c3 = fltk::color(225,131,109);
690 return;
694 if(box_flag){
695 T1=box_t1;
696 T2=box_t2;
697 int N1 = box_n1;
698 int N2 = box_n2;
699 int N = e->value1;
700 if(T1>T2){SWAP(T1,T2);}
701 if(N1<N2){SWAP(N1,N2);}
702 if(e->tick+e->dur > T1 && e->tick < T2 && N >= N2 && N <= N1){
703 *c1 = fltk::color(108,229,75);
704 *c2 = fltk::color(71,120,59);
705 *c3 = fltk::color(108,229,75);
706 return;
710 if(e->selected){
711 *c1 = fltk::color(255,248,47);
712 *c2 = fltk::color(140,137,46);
713 *c3 = fltk::color(232,255,37);
714 return;
717 *c1 = fltk::color(169,75,229);
718 *c2 = fltk::color(95,58,119);
719 *c3 = fltk::color(198,109,225);
723 void PianoRoll::apply_box(){
724 mevent* e = cur_seqpat->p->events->next;
725 int tmp;
726 int T1=box_t1;
727 int T2=box_t2;
728 int N1 = box_n1;
729 int N2 = box_n2;
731 if(T1>T2){SWAP(T1,T2);}
732 if(N1<N2){SWAP(N1,N2);}
733 while(e){
734 int N = e->value1;
735 if(e->type == MIDI_NOTE_ON &&
736 e->tick+e->dur > T1 && e->tick < T2 &&
737 N >= N2 && N <= N1){
738 e->selected = 1;
740 e = e->next;
744 void PianoRoll::apply_insert(){
745 if(insert_note > 127 || insert_note < 0){
746 return;
749 int tmp;
750 int T1 = insert_torig;
751 int T2 = T1 + insert_toffset;
752 if(T1>T2){SWAP(T1,T2);}
754 if(T1 < 0){
755 return;
758 pattern* p = cur_seqpat->p;
759 Command* c=new CreateNote(p,insert_note,127,T1,T2-T1);
760 set_undo(c);
761 undo_push(1);
764 void PianoRoll::apply_delete(){
765 Command* c;
766 mevent* e;
767 mevent* next;
768 pattern* p = cur_seqpat->p;
769 int N=0;
771 e = cur_seqpat->p->events->next;
772 while(e){
773 next = e->next;
774 if(e->selected && e->type == MIDI_NOTE_ON){
775 c=new DeleteNote(p,e);
776 set_undo(c);
777 N++;
779 e = next;
781 undo_push(N);
785 void PianoRoll::apply_move(){
786 if(move_toffset==0 && move_noffset==0){
787 return;
790 pattern* p = cur_seqpat->p;
791 mevent* e = p->events->next;
792 while(e){
793 int K = e->value1+move_noffset;
794 int T = e->tick+move_toffset;
795 if(e->type == MIDI_NOTE_ON && e->selected && (T<0 || K < 0 || K > 127)){
796 return;
798 e = e->next;
802 Command* c;
803 e = p->events->next;
805 mevent* next;
806 int M=0;
807 for(int i=0; i<tracks.size(); i++){
808 e = p->events->next;
809 while(e){
810 next = e->next;
811 if(e->selected && e->modified == 0){
812 int K = e->value1 + move_noffset;
813 int T = e->tick + move_toffset;
814 e->modified = 1;
815 c=new MoveNote(p,e,T,K);
816 set_undo(c);
817 M++;
819 e = next;
822 undo_push(M);
824 e = p->events->next;
825 while(e){
826 if(e->modified){e->modified=0;}
827 e = e->next;
831 void PianoRoll::apply_paste(){
835 void PianoRoll::apply_rresize(){
839 void PianoRoll::apply_lresize(){
844 int PianoRoll::over_rhandle(mevent* e, int X, int Y){
845 int X1 = tick2xpix(e->tick);
846 int X2 = X1 + tick2xpix(e->dur);
847 int Y1 = note2ypix(e->value1);
848 int Y2 = Y1 + 12;
850 if(X2-X1 < resize_handle_width*3){
851 return 0;
854 return (Y > Y1 && Y < Y2 && X < X2 && X > X2 - resize_handle_width);
857 int PianoRoll::over_lhandle(mevent* e, int X, int Y){
858 int X1 = tick2xpix(e->tick);
859 int X2 = X1 + tick2xpix(e->dur);
860 int Y1 = note2ypix(e->value1);
861 int Y2 = Y1 + 12;
863 if(X2-X1 < resize_handle_width*3){
864 return 0;
867 return (Y > Y1 && Y < Y2 && X < X1+ resize_handle_width +1 && X > X1 + 1);