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
24 #include <fltk/Group.h>
25 #include <fltk/Widget.h>
27 #include <fltk/Scrollbar.h>
29 #include <fltk/Cursor.h>
31 #include <fltk/events.h>
44 extern std::vector
<track
*> tracks
;
46 extern struct conf config
;
50 #define SWAP(X,Y) tmp=X; X=Y; Y=tmp;
52 PianoRoll::PianoRoll(int x
, int y
, int w
, int h
, const char* label
= 0) : fltk::Widget(x
, y
, w
, h
, label
) {
60 q_tick
= TICKS_PER_BEAT
/4;
76 resize_handle_width
= 4;
80 if(fakeh
< h
){fakeh
= h
;}
86 int PianoRoll::handle(int event
){
102 case fltk::MOUSEWHEEL
:
103 /*yprime = scrolly+32*event_dy();
104 if(yprime<0){yprime=0;}
105 if(yprime>75*12 - h()){yprime = 75*12 - h();}
106 scrollTo(scrollx,yprime);*/
108 xprime
= scrollx
+128*event_dy();
109 if(xprime
< 0){xprime
= 0;}
110 scrollTo(xprime
,scrolly
);
114 if(event_key()==fltk::DeleteKey
){
119 cursor(fltk::CURSOR_DEFAULT
);
120 ui
->event_edit
->redraw();
123 if(event_state(CTRL
) && event_key()=='c'){
124 //printf("roll copy\n");
127 if(zoom_out_key(event_key(),event_state())){
129 prevt
= xpix2tick(scrollx
);
131 set_zoom(30*(1<<zoom_n
)/16);
132 ui
->pattern_timeline
->zoom
= zoom
;
133 //ui->pattern_timeline->update(get_play_position());
134 //ui->pattern_timeline->redraw();
135 ui
->event_edit
->zoom
= zoom
;
136 scrollTo(tick2xpix(prevt
),scrolly
);
137 //ui->event_edit->redraw();
142 if(zoom_in_key(event_key(),event_state())){
144 prevt
= xpix2tick(scrollx
);
146 set_zoom(30*(1<<zoom_n
)/16);
147 ui
->pattern_timeline
->zoom
= zoom
;
148 //ui->pattern_timeline->update(get_play_position());
149 //ui->pattern_timeline->redraw();
150 ui
->event_edit
->zoom
= zoom
;
151 scrollTo(tick2xpix(prevt
),scrolly
);
152 //ui->event_edit->redraw();
161 if(event_button()==1){//left mouse
162 if(e
==NULL
){//new note init
163 if(event_state()&fltk::SHIFT
){//begin box
169 box_t1
=xpix2tick(X
+scrollx
);
171 box_n1
=ypix2note(Y
+scrolly
,1);
176 insert_torig
= quantize(xpix2tick(X
+scrollx
));
178 insert_toffset
= (q_tick
*2) / 3;
181 insert_toffset
= q_tick
;
183 //new_orig_t = new_left_t;
184 insert_note
= ypix2note(Y
+scrolly
,1);
186 last_note
= insert_note
;
187 if(config
.playinsert
){
188 ui
->keyboard
->play_note(last_note
,0);
195 if(!(e
->selected
) && !(event_state()&fltk::SHIFT
)){
199 ui
->event_edit
->select_flag
= 1;
200 resize_arrow_color
= fltk::color(128,128,0);
202 if(over_rhandle(e
)){//resize
204 rresize_torig
= e
->tick
+e
->dur
;
207 else if(over_lhandle(e
)){//resize move
209 lresize_torig
= e
->tick
;
214 cursor(fltk::CURSOR_MOVE
);
215 move_torig
= e
->tick
;
216 move_qoffset
= e
->tick
- quantize(e
->tick
);
218 move_toffset
= -move_qoffset
;
220 //move_offset = quantize(xpix2tick(X)) - move_torig - move_qoffset;
222 move_offset
= X
- tick2xpix(e
->tick
);
223 //move_norig = ypix2note(Y+scrolly,1);
224 move_norig
= e
->value1
;
227 last_note
= e
->value1
;
229 ui
->keyboard
->play_note(last_note
,0);
230 ui
->keyboard
->highlight_note(last_note
);
235 else if(event_button()==2){//middle mouse
236 //button initiates paste
238 else if(event_button()==3){//right mouse
241 ui
->event_edit
->redraw();
243 else{//set up for deletion
244 if(!(e
->selected
) && !(event_state()&fltk::SHIFT
)){
249 resize_arrow_color
= fltk::color(120,60,58);
251 ui
->event_edit
->select_flag
= 0;
257 ui
->keyboard
->highlight_clear();
261 box_t2
= xpix2tick(X
+scrollx
);
262 box_n2
= ypix2note(Y
+scrolly
,1);
264 else if(insert_flag
){
266 int q_trip
= (q_tick
*2) / 3;
267 insert_toffset
= quantize(xpix2tick(X
+scrollx
)+q_trip
) - insert_torig
;
268 if(insert_toffset
<=0){
269 insert_toffset
-= q_trip
;
273 insert_toffset
= quantize(xpix2tick(X
+scrollx
)+q_tick
) - insert_torig
;
274 if(insert_toffset
<=0){
275 insert_toffset
-= q_tick
;
278 insert_note
= ypix2note(Y
+scrolly
,1);
279 if(insert_note
!= last_note
){
280 if(config
.playinsert
){//play on insert
281 ui
->keyboard
->release_note(last_note
,0);
282 ui
->keyboard
->play_note(insert_note
,0);
284 last_note
= insert_note
;
288 move_toffset
= quantize(xpix2tick(X
- move_offset
)) - move_torig
;
289 move_noffset
= ypix2note(Y
+scrolly
,1) - move_norig
;
290 int N
= move_norig
+move_noffset
;
292 ui
->keyboard
->highlight_note(N
);
293 if(config
.playmove
){//play on move
294 ui
->keyboard
->release_note(last_note
,0);
295 ui
->keyboard
->play_note(N
,0);
300 else if(rresize_flag
){
302 int q_trip
= (q_tick
*2) / 3;
303 rresize_toffset
= quantize(xpix2tick(X
+scrollx
))+q_trip
-rresize_torig
;
306 rresize_toffset
= quantize(xpix2tick(X
+scrollx
))+q_tick
-rresize_torig
;
309 else if(lresize_flag
){
310 lresize_toffset
= quantize(xpix2tick(X
+scrollx
)) - lresize_torig
;
316 if(event_button()==1){
319 ui
->event_edit
->redraw();
322 else if(rresize_flag
){
326 cursor(fltk::CURSOR_DEFAULT
);
327 ui
->event_edit
->redraw();
329 else if(lresize_flag
){
333 cursor(fltk::CURSOR_DEFAULT
);
334 ui
->event_edit
->redraw();
336 else if(insert_flag
){
341 ui
->keyboard
->release_note(insert_note
,0);
342 ui
->keyboard
->redraw();
343 ui
->event_edit
->has
[0]=1;
344 ui
->event_edit
->has
[1]=1;
345 ui
->event_edit
->redraw();
346 ui
->event_menu
->redraw();
351 cursor(fltk::CURSOR_DEFAULT
);
352 midi_track_off(cur_seqpat
->track
);
354 ui
->keyboard
->release_note(last_note
,0);
355 ui
->keyboard
->release_note(move_norig
+move_noffset
,0);
356 ui
->keyboard
->redraw();
357 ui
->event_edit
->redraw();
362 if(event_button()==3){
363 mevent
* over_n
= over_note();
364 if(delete_flag
&& over_n
){
365 if(over_n
->selected
){
368 midi_track_off(cur_seqpat
->track
);
370 ui
->event_edit
->redraw();
375 cursor(fltk::CURSOR_DEFAULT
);
382 ui
->keyboard
->highlight_note(ypix2note(Y
+scrolly
,1));
386 if(resize_e
!= e
|| resize_arrow
!= 1){
387 if(e
->selected
){resize_arrow_color
= fltk::color(128,128,0);}
388 else{resize_arrow_color
= fltk::color(95,58,119);}
391 cursor(fltk::CURSOR_WE
);
392 resize_x
= tick2xpix(e
->tick
+ e
->dur
)-scrollx
-resize_handle_width
;
393 resize_y
= note2ypix(e
->value1
)-scrolly
;
397 else if(over_lhandle(e
)){
398 if(resize_e
!= e
|| resize_arrow
!= 1){
399 if(e
->selected
){resize_arrow_color
= fltk::color(128,128,0);}
400 else{resize_arrow_color
= fltk::color(95,58,119);}
403 cursor(fltk::CURSOR_WE
);
404 resize_x
= tick2xpix(e
->tick
)+1 - scrollx
;
405 resize_y
= note2ypix(e
->value1
) - scrolly
;
410 if(resize_e
!= e
|| resize_arrow
!= 0){
413 cursor(fltk::CURSOR_DEFAULT
);
419 if(resize_arrow
!= 0){
421 cursor(fltk::CURSOR_DEFAULT
);
430 ui
->keyboard
->highlight_clear();
436 void PianoRoll::draw(){
438 fltk::push_clip(0,0,w(),h());
440 fltk::setcolor(fltk::GRAY05
);
441 fltk::fillrect(0,0,w(),h());
443 fltk::setcolor(fltk::GRAY20
);
444 for(int i
=12-scrolly
; i
<h(); i
+=12){
446 fltk::fillrect(0,i
,w(),1);
449 for(int i
=zoom
-scrollx
; i
<w(); i
+=zoom
){
451 fltk::fillrect(i
,0,1,h());
455 fltk::setcolor(fltk::GRAY30
);
456 for(int i
=12*5-scrolly
; i
<h(); i
+=12*7){
458 fltk::fillrect(0,i
,w(),1);
462 fltk::setcolor(fltk::GRAY50
);
463 for(int i
=zoom
*4-scrollx
; i
<w(); i
+=zoom
*4){
465 fltk::fillrect(i
,0,1,h());
469 fltk::setcolor(fltk::WHITE
);
470 int M
= config
.beats_per_measure
;
471 for(int i
=zoom
*4*M
-scrollx
; i
<w(); i
+=zoom
*4*M
){
473 fltk::fillrect(i
,0,1,h());
477 fltk::setcolor(fltk::color(128,0,0));
478 int rightend
= tick2xpix(cur_seqpat
->dur
)-scrollx
;
479 if(rightend
>=0 && rightend
< w()){
480 fltk::fillrect(rightend
,0,1,h());
483 fltk::setcolor(fltk::color(128,128,0));
484 fltk::fillrect(0,12*40-scrolly
,w(),1);
488 fltk::setcolor(fltk::BLUE
);
489 int T1
= insert_torig
;
490 int T2
= T1
+ insert_toffset
;
491 if(T1
>T2
){SWAP(T1
,T2
);}
492 int X
= tick2xpix(T1
)+1 - scrollx
;
493 int Y
= note2ypix(insert_note
) - scrolly
;
494 int W
= tick2xpix(T2
)-scrollx
- X
;
495 fltk::fillrect(X
,Y
,W
,11);
499 fltk::setcolor(fltk::MAGENTA
);
500 mevent
* ptr
= cur_seqpat
->p
->events
->next
;
502 if(ptr
->type
== MIDI_NOTE_ON
&& ptr
->selected
){
503 int X
= tick2xpix(ptr
->tick
+move_toffset
)+1-scrollx
;
504 int Y
= note2ypix(ptr
->value1
+move_noffset
)-scrolly
;
505 int W
= tick2xpix(ptr
->dur
);
506 fltk::fillrect(X
,Y
,W
-1,1);
507 fltk::fillrect(X
,Y
+11,W
-1,1);
508 fltk::fillrect(X
,Y
,1,11);
509 fltk::fillrect(X
+W
-2,Y
,1,11);
518 mevent
* e
= cur_seqpat
->p
->events
->next
;
520 fltk::Color c1
,c2
,c3
;
523 if(e
->type
== MIDI_NOTE_ON
){
524 //fltk::fillrect(tick2xpix(e->tick),note2ypix(e->value),e->dur,11);
526 int R1
= rresize_flag
&&e
->selected
? rresize_toffset
: 0;
527 int R2
= lresize_flag
&&e
->selected
? lresize_toffset
: 0;
529 int T1
= e
->tick
+ R2
;
530 int T2
= e
->tick
+e
->dur
+ R1
;
532 if(T1
>= T2
-q_tick
&& e
->selected
){
537 else if(lresize_flag
){
538 T2
= e
->tick
+ e
->dur
;
543 int X
= tick2xpix(T1
) + 1 - scrollx
;
544 int Y
= note2ypix(e
->value1
) - scrolly
;
546 int W
= tick2xpix(T2
)-scrollx
- X
;
547 get_event_color(e
,&c1
,&c2
,&c3
);
549 if(!(X
+W
<0 || X
> w())){
552 fltk::fillrect(X
+1,Y
+1,W
-1,10);
555 fltk::fillrect(X
,Y
+11,W
,1);
556 fltk::fillrect(X
+W
-1,Y
+1,1,11);
559 fltk::fillrect(X
,Y
,W
,1);
560 fltk::fillrect(X
,Y
,1,11);
567 if(!rresize_flag
&& !lresize_flag
){
568 if(resize_arrow
> 0){
569 setcolor(resize_arrow_color
);
571 int W
= resize_handle_width
;
578 //addvertex(X+W,Y+H/2);
581 else if(resize_arrow
< 0){
582 setcolor(resize_arrow_color
);
584 int W
= resize_handle_width
;
590 //addvertex(X+W,Y+H);
591 //addvertex(X,Y+H/2);
598 fltk::setcolor(fltk::GREEN
);
616 fltk::fillrect(X1
,Y1
,X2
-X1
,1);
617 fltk::fillrect(X1
,Y1
,1,Y2
-Y1
);
618 fltk::fillrect(X2
,Y1
,1,Y2
-Y1
);
619 fltk::fillrect(X1
,Y2
,X2
-X1
,1);
628 void PianoRoll::scrollTo(int X
, int Y
){
634 if(is_backend_playing() && config
.follow
){
635 int pos
= tick2xpix(get_play_position() - cur_seqpat
->tick
);
636 if(pos
< cur_seqpat
->tick
){
638 else if(pos
< X
|| pos
> X
+ w() - 30 - 30){
639 ui
->pattern_hscroll
->value(scrollx
);
646 cur_seqpat
->scrollx
= X
;
647 cur_seqpat
->scrolly
= Y
;
650 ui
->pattern_hscroll
->value(X
);
651 ui
->pattern_hscroll
->redraw();
652 ui
->pattern_vscroll
->value(Y
);
653 ui
->pattern_timeline
->scroll
= X
;
654 ui
->pattern_timeline
->redraw();
655 ui
->event_edit
->scroll
= X
;
656 ui
->event_edit
->redraw();
657 ui
->keyboard
->scroll
= Y
;
658 ui
->keyboard
->redraw();
662 void PianoRoll::layout(){
672 fltk::Scrollbar
* sb
= ui
->pattern_vscroll
;
675 sb
->minimum(fakeh
-h());
677 if(sb
->value() > sb
->minimum()){
678 scrollTo(scrollx
,900-h());
681 int newsize
= M
-(fakeh
-h());
685 ui
->song_vscroll
->slider_size(60);
688 void PianoRoll::load(seqpat
* s
){
690 scrollTo(s
->scrollx
,s
->scrolly
);
691 cur_track
= tracks
[s
->track
];
692 ui
->pattern_timeline
->ticks_offset
= s
->tick
;
697 int PianoRoll::note2ypix(int note
){
698 int udy
= 6*(note
+ (note
+7)/12 + note
/12) + 12;
699 return 900 - udy
+ 1;
702 int PianoRoll::tick2xpix(int tick
){
703 return tick
*zoom
*4 / TICKS_PER_BEAT
;
706 int PianoRoll::xpix2tick(int xpix
){
707 return xpix
*TICKS_PER_BEAT
/ (zoom
*4);
710 int PianoRoll::quantize(int tick
){
716 return P
+ Q
/ T
* T
;
719 return tick
/(q_tick
) * q_tick
;
724 void PianoRoll::set_zoom(int z
){
730 mevent
* PianoRoll::over_note(){
731 mevent
* e
= cur_seqpat
->p
->events
->next
;
733 int X
= event_x()+scrollx
;
734 int Y
= event_y()+scrolly
;
738 if(e
->type
== MIDI_NOTE_ON
){
739 cy
= note2ypix(e
->value1
);
740 lx
= tick2xpix(e
->tick
);
741 rx
= tick2xpix(e
->tick
+e
->dur
);
742 if(X
> lx
&& X
< rx
&&
743 Y
< cy
+12 && Y
> cy
){
755 void PianoRoll::update(int pos
){
756 if(!is_backend_playing() || !cur_seqpat
){
759 if(pos
< cur_seqpat
->tick
){
762 int X1
= tick2xpix(pos
-cur_seqpat
->tick
);
763 int X2
= X1
- scrollx
;
765 scrollTo(X1
-50<0?0:X1
-50,scrolly
);
768 scrollTo(X1
-50,scrolly
);
773 void PianoRoll::unselect_all(){
774 mevent
* e
= cur_seqpat
->p
->events
;
776 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
==1){
785 void PianoRoll::get_event_color(mevent
* e
, fltk::Color
* c1
, fltk::Color
* c2
, fltk::Color
* c3
){
791 *c1
= fltk::color(229,79,75);
792 *c2
= fltk::color(120,60,58);
793 *c3
= fltk::color(225,131,109);
804 if(T1
>T2
){SWAP(T1
,T2
);}
805 if(N1
<N2
){SWAP(N1
,N2
);}
806 if(e
->tick
+e
->dur
> T1
&& e
->tick
< T2
&& N
>= N2
&& N
<= N1
){
807 *c1
= fltk::color(108,229,75);
808 *c2
= fltk::color(71,120,59);
809 *c3
= fltk::color(108,229,75);
815 *c1
= fltk::color(255,248,47);
816 *c2
= fltk::color(140,137,46);
817 *c3
= fltk::color(232,255,37);
821 *c1
= fltk::color(169,75,229);
822 *c2
= fltk::color(95,58,119);
823 *c3
= fltk::color(198,109,225);
827 void PianoRoll::apply_box(){
828 mevent
* e
= cur_seqpat
->p
->events
->next
;
835 if(T1
>T2
){SWAP(T1
,T2
);}
836 if(N1
<N2
){SWAP(N1
,N2
);}
839 if(e
->type
== MIDI_NOTE_ON
&&
840 e
->tick
+e
->dur
> T1
&& e
->tick
< T2
&&
842 ui
->event_edit
->select_flag
= 1;
850 void PianoRoll::apply_insert(){
851 if(insert_note
> 127 || insert_note
< 0){
856 int T1
= insert_torig
;
857 int T2
= T1
+ insert_toffset
;
858 if(T1
>T2
){SWAP(T1
,T2
);}
864 pattern
* p
= cur_seqpat
->p
;
865 Command
* c
=new CreateNote(p
,insert_note
,config
.defaultvelocity
,T1
,T2
-T1
);
869 cur_track
->restate();
872 void PianoRoll::apply_delete(){
876 pattern
* p
= cur_seqpat
->p
;
879 e
= cur_seqpat
->p
->events
->next
;
882 if(e
->selected
&& e
->type
== MIDI_NOTE_ON
){
883 c
=new DeleteNote(p
,e
);
891 cur_track
->restate();
894 void PianoRoll::apply_move(){
895 if(move_toffset
==0 && move_noffset
==0){
899 pattern
* p
= cur_seqpat
->p
;
900 mevent
* e
= p
->events
->next
;
902 int K
= e
->value1
+move_noffset
;
903 int T
= e
->tick
+move_toffset
;
904 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
&& (T
<0 || K
< 0 || K
> 127)){
916 for(int i
=0; i
<tracks
.size(); i
++){
920 if(e
->selected
&& e
->modified
== 0){
921 int K
= e
->value1
+ move_noffset
;
922 int T
= e
->tick
+ move_toffset
;
924 c
=new MoveNote(p
,e
,T
,K
);
935 if(e
->modified
){e
->modified
=0;}
939 cur_track
->restate();
942 void PianoRoll::apply_paste(){
948 void PianoRoll::apply_rresize(){
949 if(rresize_toffset
==0){
956 pattern
* p
= cur_seqpat
->p
;
963 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
&& e
->modified
== 0){
966 int R
= rresize_toffset
;
970 c
=new ResizeNote(p
,e
,W
+R
);
979 if(e
->modified
){e
->modified
=0;}
983 cur_track
->restate();
987 void PianoRoll::apply_lresize(){
988 if(lresize_toffset
==0){
995 pattern
* p
= cur_seqpat
->p
;
1001 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
){
1002 if(e
->tick
+ lresize_toffset
< 0){
1009 e
= p
->events
->next
;
1012 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
&& e
->modified
== 0){
1016 int R
= lresize_toffset
;
1022 mevent
* etmp
= e
->prev
;
1023 c
=new ResizeNote(p
,e
,W
-R
);
1027 c
=new MoveNote(p
,e
,T
+R
,e
->value1
);
1035 e
= p
->events
->next
;
1037 if(e
->modified
){e
->modified
=0;}
1041 cur_track
->restate();
1053 int PianoRoll::over_rhandle(mevent
* e
){
1054 int X
= event_x()+scrollx
;
1055 int Y
= event_y()+scrolly
;
1056 int X1
= tick2xpix(e
->tick
);
1057 int X2
= X1
+ tick2xpix(e
->dur
);
1058 int Y1
= note2ypix(e
->value1
);
1061 if(X2
-X1
< resize_handle_width
*3){
1065 return (Y
> Y1
&& Y
< Y2
&& X
< X2
&& X
> X2
- resize_handle_width
);
1068 int PianoRoll::over_lhandle(mevent
* e
){
1069 int X
= event_x()+scrollx
;
1070 int Y
= event_y()+scrolly
;
1071 int X1
= tick2xpix(e
->tick
);
1072 int X2
= X1
+ tick2xpix(e
->dur
);
1073 int Y1
= note2ypix(e
->value1
);
1076 if(X2
-X1
< resize_handle_width
*3){
1080 return (Y
> Y1
&& Y
< Y2
&& X
< X1
+ resize_handle_width
+1 && X
> X1
+ 1);