Replaced pattern_scroll.
[epichord.git] / src / pianoroll.cpp
blobd8a14f869d2d8ce59b0eb2b7e9f87370ff72dc75
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 box_flag = 0;
59 move_toffset = 0;
62 lresize_flag = 0;
63 rresize_flag = 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);
161 if(over_rhandle(e,X,Y)){//resize
162 rresize_flag = 1;
163 rresize_torig = e->tick+e->dur;
164 rresize_toffset = 0;
166 else if(over_lhandle(e,X,Y)){//resize move
167 lresize_flag = 1;
168 lresize_torig = e->tick;
169 lresize_toffset = 0;
171 else{//begin move
172 move_flag = 1;
174 move_torig = e->tick;
175 move_qoffset = e->tick - quantize(e->tick);
177 move_toffset = -move_qoffset;
179 //move_offset = quantize(xpix2tick(X)) - move_torig - move_qoffset;
180 //move_toffset = 0;
181 move_offset = X - tick2xpix(e->tick);
182 move_norig = ypix2note(event_y(),1);
183 move_noffset = 0;
185 last_note = move_norig;
186 if(config.playmove){
187 ui->keyboard->play_note(last_note,0);
192 else if(event_button()==2){//middle mouse
193 //button initiates paste
195 else if(event_button()==3){//right mouse
196 if(e==NULL){
197 unselect_all();
199 ui->event_edit->redraw();
201 else{//set up for deletion
202 if(!(e->selected) && !(event_state()&fltk::SHIFT)){
203 unselect_all();
205 e->selected = 1;
206 delete_flag = 1;
207 resize_arrow_color = fltk::color(120,60,58);
211 redraw();
212 return 1;
213 case fltk::DRAG:
215 if(box_flag){
216 box_x2 = X;
217 box_y2 = Y;
218 box_t2 = xpix2tick(X);
219 box_n2 = ypix2note(Y,1);
221 else if(insert_flag){
222 insert_toffset = quantize(xpix2tick(X)+q_tick) - insert_torig;
223 if(insert_toffset<=0){
224 insert_toffset -= q_tick;
226 insert_note = ypix2note(Y,1);
227 if(insert_note != last_note){
228 if(config.playinsert){//play on insert
229 ui->keyboard->release_note(last_note,0);
230 ui->keyboard->play_note(insert_note,0);
232 last_note = insert_note;
235 else if(move_flag){
236 move_toffset = quantize(xpix2tick(X - move_offset)) - move_torig;
237 move_noffset = ypix2note(Y,1) - move_norig;
238 int N = move_norig+move_noffset;
239 if(N != last_note){
240 if(config.playmove){//play on move
241 ui->keyboard->release_note(last_note,0);
242 ui->keyboard->play_note(N,0);
244 last_note = N;
247 else if(rresize_flag){
248 rresize_toffset = quantize(xpix2tick(X)) + q_tick - rresize_torig;
250 else if(lresize_flag){
251 lresize_toffset = quantize(xpix2tick(X)) - lresize_torig;
253 redraw();
254 return 1;
255 case fltk::RELEASE:
256 e = over_note();
257 if(event_button()==1){
258 if(box_flag){
259 apply_box();
260 ui->event_edit->redraw();
261 box_flag=0;
263 else if(rresize_flag){
264 apply_rresize();
265 rresize_flag = 0;
266 resize_arrow = 0;
267 ui->event_edit->redraw();
269 else if(lresize_flag){
270 apply_lresize();
271 lresize_flag = 0;
272 resize_arrow = 0;
273 ui->event_edit->redraw();
275 else if(insert_flag){
276 apply_insert();
278 insert_flag = 0;
280 ui->keyboard->release_note(insert_note,0);
281 ui->keyboard->redraw();
282 ui->event_edit->has[0]=1;
283 ui->event_edit->has[1]=1;
284 ui->event_edit->redraw();
285 ui->event_menu->redraw();
287 else if(move_flag){
288 apply_move();
289 move_flag = 0;
291 midi_track_off(cur_seqpat->track);
292 ui->keyboard->release_note(last_note,0);
293 ui->keyboard->release_note(move_norig+move_noffset,0);
294 ui->keyboard->redraw();
295 ui->event_edit->redraw();
297 insert_flag=0;
298 move_flag=0;
300 if(event_button()==3){
301 mevent* over_n = over_note();
302 if(delete_flag && over_n){
303 if(over_n->selected){
304 apply_delete();
305 midi_track_off(cur_seqpat->track);
306 ui->event_edit->redraw();
309 delete_flag=0;
310 resize_arrow = 0;
312 redraw();
314 return 1;
316 case fltk::MOVE:
317 e = over_note();
318 if(e){
319 if(over_rhandle(e,X,Y)){
320 if(resize_e != e || resize_arrow != 1){
321 if(e->selected){resize_arrow_color = fltk::color(128,128,0);}
322 else{resize_arrow_color = fltk::color(95,58,119);}
323 resize_e = e;
324 resize_arrow = 1;
325 resize_x = tick2xpix(e->tick + e->dur) - resize_handle_width;
326 resize_y = note2ypix(e->value1);
327 redraw();
330 else if(over_lhandle(e,X,Y)){
331 if(resize_e != e || resize_arrow != 1){
332 if(e->selected){resize_arrow_color = fltk::color(128,128,0);}
333 else{resize_arrow_color = fltk::color(95,58,119);}
334 resize_e = e;
335 resize_arrow = -1;
336 resize_x = tick2xpix(e->tick)+1;
337 resize_y = note2ypix(e->value1);
338 redraw();
341 else{
342 if(resize_e != e || resize_arrow != 0){
343 resize_e = e;
344 resize_arrow = 0;
345 redraw();
349 else{
350 if(resize_arrow != 0){
351 resize_arrow = 0;
352 redraw();
356 return 1;
358 return 0;
361 void PianoRoll::draw(){
363 fltk::setcolor(fltk::GRAY05);
364 fltk::fillrect(0,0,w(),h());
366 fltk::setcolor(fltk::GRAY20);
367 for(int i=12; i<h(); i+=12){
368 fltk::drawline(0,i,w(),i);
370 for(int i=zoom; i<w(); i+=zoom){
371 fltk::drawline(i,0,i,h());
374 fltk::setcolor(fltk::GRAY30);
375 for(int i=12*5; i<h(); i+=12*7){
376 fltk::drawline(0,i,w(),i);
379 fltk::setcolor(fltk::GRAY50);
380 for(int i=zoom*4; i<w(); i+=zoom*4){
381 fltk::drawline(i,0,i,h());
384 fltk::setcolor(fltk::WHITE);
385 int M = config.beats_per_measure;
386 for(int i=zoom*4*M; i<w(); i+=zoom*4*M){
387 fltk::fillrect(i,0,1,h());
390 fltk::setcolor(fltk::color(128,0,0));
391 int rightend = tick2xpix(cur_seqpat->dur);
392 fltk::fillrect(rightend,0,1,h());
394 fltk::setcolor(fltk::color(128,128,0));
395 fltk::drawline(0,12*40,w(),12*40);
397 int tmp;
398 if(insert_flag){
399 fltk::setcolor(fltk::BLUE);
400 int T1 = insert_torig;
401 int T2 = T1 + insert_toffset;
402 if(T1>T2){SWAP(T1,T2);}
403 int X = tick2xpix(T1)+1;
404 int Y = note2ypix(insert_note);
405 int W = tick2xpix(T2) - X;
406 fltk::fillrect(X,Y,W,11);
409 if(move_flag){
410 fltk::setcolor(fltk::MAGENTA);
411 mevent* ptr = cur_seqpat->p->events->next;
412 while(ptr){
413 if(ptr->type == MIDI_NOTE_ON && ptr->selected){
414 int X = tick2xpix(ptr->tick+move_toffset)+1;
415 int Y = note2ypix(ptr->value1+move_noffset);
416 int W = tick2xpix(ptr->dur);
417 fltk::fillrect(X,Y,W-1,1);
418 fltk::fillrect(X,Y+11,W-1,1);
419 fltk::fillrect(X,Y,1,11);
420 fltk::fillrect(X+W-2,Y,1,11);
422 ptr=ptr->next;
428 //draw all notes
429 mevent* e = cur_seqpat->p->events->next;
431 fltk::Color c1,c2,c3;
433 while(e){
434 if(e->type == MIDI_NOTE_ON){
435 //fltk::fillrect(tick2xpix(e->tick),note2ypix(e->value),e->dur,11);
437 int R1 = rresize_flag&&e->selected ? rresize_toffset : 0;
438 int R2 = lresize_flag&&e->selected ? lresize_toffset : 0;
440 int T1 = e->tick + R2;
441 int T2 = e->tick+e->dur + R1;
443 if(T1 >= T2-q_tick && e->selected){
444 if(rresize_flag){
445 T1 = e->tick;
446 T2 = T1 + q_tick;
448 else if(lresize_flag){
449 T2 = e->tick + e->dur;
450 T1 = T2 - q_tick;
454 int X = tick2xpix(T1) + 1;
455 int Y = note2ypix(e->value1);
456 int W = tick2xpix(T2) - X;
457 get_event_color(e,&c1,&c2,&c3);
459 fltk::setcolor(c1);
460 fltk::fillrect(X+1,Y+1,W-1,10);
462 fltk::setcolor(c2);
463 fltk::fillrect(X,Y+11,W,1);
464 fltk::fillrect(X+W-1,Y+1,1,11);
466 fltk::setcolor(c3);
467 fltk::fillrect(X,Y,W,1);
468 fltk::fillrect(X,Y,1,11);
470 e=e->next;
474 if(!rresize_flag && !lresize_flag){
475 if(resize_arrow > 0){
476 setcolor(resize_arrow_color);
478 int W = resize_handle_width;
479 int H = 12;
480 int X = resize_x;
481 int Y = resize_y;
483 addvertex(X,Y);
484 addvertex(X,Y+H);
485 addvertex(X+W,Y+H/2);
486 fillpath();
488 else if(resize_arrow < 0){
489 setcolor(resize_arrow_color);
491 int W = resize_handle_width;
492 int H = 12;
493 int X = resize_x;
494 int Y = resize_y;
496 addvertex(X+W,Y);
497 addvertex(X+W,Y+H);
498 addvertex(X,Y+H/2);
499 fillpath();
504 if(box_flag){
505 fltk::setcolor(fltk::GREEN);
506 int X1,X2,Y1,Y2;
507 if(box_x1>box_x2){
508 X1=box_x2;
509 X2=box_x1;
511 else{
512 X1=box_x1;
513 X2=box_x2;
515 if(box_y1>box_y2){
516 Y1=box_y2;
517 Y2=box_y1;
519 else{
520 Y1=box_y1;
521 Y2=box_y2;
523 fltk::fillrect(X1,Y1,X2-X1,1);
524 fltk::fillrect(X1,Y1,1,Y2-Y1);
525 fltk::fillrect(X2,Y1,1,Y2-Y1);
526 fltk::fillrect(X1,Y2,X2-X1,1);
533 void PianoRoll::scrollTo(int X, int Y){
534 scrollx = X;
535 scrolly = Y;
536 redraw();
537 ui->pattern_timeline->scroll = X;
538 ui->pattern_timeline->redraw();
539 ui->event_edit->scroll = X;
540 ui->event_edit->redraw();
541 ui->keyboard->scroll = Y;
542 ui->keyboard->redraw();
547 void PianoRoll::load(seqpat* s){
549 //ui->pattern_scroll->scrollTo(0,300);
551 cur_seqpat = s;
552 cur_track = tracks[s->track];
553 int W = tick2xpix(s->dur);
554 resize(W+300,h());
556 ui->pattern_timeline->ticks_offset = s->tick;
561 int PianoRoll::note2ypix(int note){
562 int udy = 6*(note + (note+7)/12 + note/12) + 12;
563 return h() - udy + 1;
566 int PianoRoll::tick2xpix(int tick){
567 return tick*zoom*4 / 128;
570 int PianoRoll::xpix2tick(int xpix){
571 return xpix*128 / (zoom*4);
574 int PianoRoll::quantize(int tick){
575 return tick/q_tick * q_tick;
579 void PianoRoll::set_zoom(int z){
580 zoom = z;
581 relayout();
582 //int W = tick2xpix(cur_seqpat->dur);
583 //resize(W+300,h());
587 mevent* PianoRoll::over_note(){
588 mevent* e = cur_seqpat->p->events->next;
590 int cy, lx, rx;
591 while(e){
592 if(e->type == MIDI_NOTE_ON){
593 cy = note2ypix(e->value1);
594 lx = tick2xpix(e->tick);
595 rx = tick2xpix(e->tick+e->dur);
596 if(event_x() > lx && event_x() < rx &&
597 event_y() < cy+12 && event_y() > cy){
598 return e;
601 e = e->next;
604 return NULL;
609 void PianoRoll::update(int pos){
610 if(!is_backend_playing() || !cur_seqpat){
611 return;
613 //int wp = ui->pattern_scroll->w();
614 //int xp = ui->pattern_scroll->xposition();
615 //int yp = ui->pattern_scroll->yposition();
616 int X1 = tick2xpix(pos-cur_seqpat->tick);
617 int X2 = X1 - scrollx;
618 if(X2 < 0){
619 scrollTo(X1-50<0?0:X1-50,scrolly);
621 if(X2 > fakew-30){
622 scrollTo(X1-50,scrolly);
627 void PianoRoll::unselect_all(){
628 mevent* e = cur_seqpat->p->events;
629 while(e){
630 if(e->type == MIDI_NOTE_ON && e->selected==1){
631 e->selected = 0;
633 e = e->next;
639 void PianoRoll::get_event_color(mevent* e, fltk::Color* c1, fltk::Color* c2, fltk::Color* c3){
641 int T1,T2;
642 int tmp;
643 if(delete_flag){
644 if(e->selected){
645 *c1 = fltk::color(229,79,75);
646 *c2 = fltk::color(120,60,58);
647 *c3 = fltk::color(225,131,109);
648 return;
652 if(box_flag){
653 T1=box_t1;
654 T2=box_t2;
655 int N1 = box_n1;
656 int N2 = box_n2;
657 int N = e->value1;
658 if(T1>T2){SWAP(T1,T2);}
659 if(N1<N2){SWAP(N1,N2);}
660 if(e->tick+e->dur > T1 && e->tick < T2 && N >= N2 && N <= N1){
661 *c1 = fltk::color(108,229,75);
662 *c2 = fltk::color(71,120,59);
663 *c3 = fltk::color(108,229,75);
664 return;
668 if(e->selected){
669 *c1 = fltk::color(255,248,47);
670 *c2 = fltk::color(140,137,46);
671 *c3 = fltk::color(232,255,37);
672 return;
675 *c1 = fltk::color(169,75,229);
676 *c2 = fltk::color(95,58,119);
677 *c3 = fltk::color(198,109,225);
681 void PianoRoll::apply_box(){
682 mevent* e = cur_seqpat->p->events->next;
683 int tmp;
684 int T1=box_t1;
685 int T2=box_t2;
686 int N1 = box_n1;
687 int N2 = box_n2;
689 if(T1>T2){SWAP(T1,T2);}
690 if(N1<N2){SWAP(N1,N2);}
691 while(e){
692 int N = e->value1;
693 if(e->type == MIDI_NOTE_ON &&
694 e->tick+e->dur > T1 && e->tick < T2 &&
695 N >= N2 && N <= N1){
696 e->selected = 1;
698 e = e->next;
702 void PianoRoll::apply_insert(){
703 if(insert_note > 127 || insert_note < 0){
704 return;
707 int tmp;
708 int T1 = insert_torig;
709 int T2 = T1 + insert_toffset;
710 if(T1>T2){SWAP(T1,T2);}
712 if(T1 < 0){
713 return;
716 pattern* p = cur_seqpat->p;
717 Command* c=new CreateNote(p,insert_note,config.defaultvelocity,T1,T2-T1);
718 set_undo(c);
719 undo_push(1);
721 cur_track->restate();
724 void PianoRoll::apply_delete(){
725 Command* c;
726 mevent* e;
727 mevent* next;
728 pattern* p = cur_seqpat->p;
729 int N=0;
731 e = cur_seqpat->p->events->next;
732 while(e){
733 next = e->next;
734 if(e->selected && e->type == MIDI_NOTE_ON){
735 c=new DeleteNote(p,e);
736 set_undo(c);
737 N++;
739 e = next;
741 undo_push(N);
743 cur_track->restate();
746 void PianoRoll::apply_move(){
747 if(move_toffset==0 && move_noffset==0){
748 return;
751 pattern* p = cur_seqpat->p;
752 mevent* e = p->events->next;
753 while(e){
754 int K = e->value1+move_noffset;
755 int T = e->tick+move_toffset;
756 if(e->type == MIDI_NOTE_ON && e->selected && (T<0 || K < 0 || K > 127)){
757 return;
759 e = e->next;
763 Command* c;
764 e = p->events->next;
766 mevent* next;
767 int M=0;
768 for(int i=0; i<tracks.size(); i++){
769 e = p->events->next;
770 while(e){
771 next = e->next;
772 if(e->selected && e->modified == 0){
773 int K = e->value1 + move_noffset;
774 int T = e->tick + move_toffset;
775 e->modified = 1;
776 c=new MoveNote(p,e,T,K);
777 set_undo(c);
778 M++;
780 e = next;
783 undo_push(M);
785 e = p->events->next;
786 while(e){
787 if(e->modified){e->modified=0;}
788 e = e->next;
791 cur_track->restate();
794 void PianoRoll::apply_paste(){
800 void PianoRoll::apply_rresize(){
801 if(rresize_toffset==0){
802 return;
805 Command* c;
806 mevent* e;
807 mevent* next;
808 pattern* p = cur_seqpat->p;
809 int tmp;
810 int N=0;
812 e = p->events->next;
813 while(e){
814 next = e->next;
815 if(e->type == MIDI_NOTE_ON && e->selected && e->modified == 0){
816 e->modified = 1;
817 int W = e->dur;
818 int R = rresize_toffset;
819 if(W+R < q_tick){
820 R = q_tick-W;
822 c=new ResizeNote(p,e,W+R);
823 set_undo(c);
824 N++;
826 e = next;
829 e = p->events->next;
830 while(e){
831 if(e->modified){e->modified=0;}
832 e = e->next;
835 cur_track->restate();
836 undo_push(N);
839 void PianoRoll::apply_lresize(){
840 if(lresize_toffset==0){
841 return;
844 Command* c;
845 mevent* e;
846 mevent* next;
847 pattern* p = cur_seqpat->p;
848 int tmp;
849 int N=0;
851 e = p->events->next;
852 while(e){
853 if(e->type == MIDI_NOTE_ON && e->selected){
854 if(e->tick + lresize_toffset < 0){
855 return;
858 e = e->next;
861 e = p->events->next;
862 while(e){
863 next = e->next;
864 if(e->type == MIDI_NOTE_ON && e->selected && e->modified == 0){
865 e->modified = 1;
866 int T = e->tick;
867 int W = e->dur;
868 int R = lresize_toffset;
870 if(R > W-q_tick){
871 R = W-q_tick;
874 mevent* etmp = e->prev;
875 c=new ResizeNote(p,e,W-R);
876 set_undo(c);
878 e = etmp->next;
879 c=new MoveNote(p,e,T+R,e->value1);
880 set_undo(c);
882 N+=2;
884 e = next;
887 e = p->events->next;
888 while(e){
889 if(e->modified){e->modified=0;}
890 e = e->next;
893 cur_track->restate();
894 undo_push(N);
905 int PianoRoll::over_rhandle(mevent* e, int X, int Y){
906 int X1 = tick2xpix(e->tick);
907 int X2 = X1 + tick2xpix(e->dur);
908 int Y1 = note2ypix(e->value1);
909 int Y2 = Y1 + 12;
911 if(X2-X1 < resize_handle_width*3){
912 return 0;
915 return (Y > Y1 && Y < Y2 && X < X2 && X > X2 - resize_handle_width);
918 int PianoRoll::over_lhandle(mevent* e, int X, int Y){
919 int X1 = tick2xpix(e->tick);
920 int X2 = X1 + tick2xpix(e->dur);
921 int Y1 = note2ypix(e->value1);
922 int Y2 = Y1 + 12;
924 if(X2-X1 < resize_handle_width*3){
925 return 0;
928 return (Y > Y1 && Y < Y2 && X < X1+ resize_handle_width +1 && X > X1 + 1);