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>
26 #include <fltk/events.h>
39 extern std::vector
<track
*> tracks
;
41 extern struct conf config
;
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
) {
70 resize_handle_width
= 4;
73 int PianoRoll::handle(int event
){
87 if(event_key()==fltk::DeleteKey
){
92 ui
->event_edit
->redraw();
95 if(event_state(CTRL
) && event_key()=='c'){
96 //printf("roll copy\n");
99 if(zoom_out_key(event_key(),event_state())){
102 set_zoom(30*(1<<zoom_n
)/16);
103 ui
->pattern_timeline
->zoom
= zoom
;
104 ui
->pattern_timeline
->update(get_play_position());
105 ui
->pattern_timeline
->redraw();
106 ui
->event_edit
->zoom
= zoom
;
107 ui
->event_edit
->redraw();
112 if(zoom_in_key(event_key(),event_state())){
115 set_zoom(30*(1<<zoom_n
)/16);
116 ui
->pattern_timeline
->zoom
= zoom
;
117 ui
->pattern_timeline
->update(get_play_position());
118 ui
->pattern_timeline
->redraw();
119 ui
->event_edit
->zoom
= zoom
;
120 ui
->event_edit
->redraw();
129 if(event_button()==1){//left mouse
130 if(e
==NULL
){//new note init
131 if(event_state()&fltk::SHIFT
){//begin box
139 box_n1
=ypix2note(Y
,1);
144 insert_torig
= quantize(xpix2tick(event_x()));
145 insert_toffset
= q_tick
;
146 //new_orig_t = new_left_t;
147 insert_note
= ypix2note(event_y(),1);
149 last_note
= insert_note
;
150 if(config
.playinsert
){
151 ui
->keyboard
->play_note(last_note
,0);
158 if(!(e
->selected
) && !(event_state()&fltk::SHIFT
)){
162 resize_arrow_color
= fltk::color(128,128,0);
164 if(over_rhandle(e
,X
,Y
)){//resize
166 rresize_torig
= e
->tick
+e
->dur
;
169 else if(over_lhandle(e
,X
,Y
)){//resize move
171 lresize_torig
= e
->tick
;
177 move_torig
= e
->tick
;
178 move_qoffset
= e
->tick
- quantize(e
->tick
);
180 move_toffset
= -move_qoffset
;
182 //move_offset = quantize(xpix2tick(X)) - move_torig - move_qoffset;
184 move_offset
= X
- tick2xpix(e
->tick
);
185 move_norig
= ypix2note(event_y(),1);
188 last_note
= move_norig
;
190 ui
->keyboard
->play_note(last_note
,0);
195 else if(event_button()==2){//middle mouse
196 //button initiates paste
198 else if(event_button()==3){//right mouse
202 ui
->event_edit
->redraw();
204 else{//set up for deletion
205 if(!(e
->selected
) && !(event_state()&fltk::SHIFT
)){
210 resize_arrow_color
= fltk::color(120,60,58);
221 box_t2
= xpix2tick(X
);
222 box_n2
= ypix2note(Y
,1);
224 else if(insert_flag
){
225 insert_toffset
= quantize(xpix2tick(X
)+q_tick
) - insert_torig
;
226 if(insert_toffset
<=0){
227 insert_toffset
-= q_tick
;
229 insert_note
= ypix2note(Y
,1);
230 if(insert_note
!= last_note
){
231 if(config
.playinsert
){//play on insert
232 ui
->keyboard
->release_note(last_note
,0);
233 ui
->keyboard
->play_note(insert_note
,0);
235 last_note
= insert_note
;
239 move_toffset
= quantize(xpix2tick(X
- move_offset
)) - move_torig
;
240 move_noffset
= ypix2note(Y
,1) - move_norig
;
241 int N
= move_norig
+move_noffset
;
243 if(config
.playmove
){//play on move
244 ui
->keyboard
->release_note(last_note
,0);
245 ui
->keyboard
->play_note(N
,0);
250 else if(rresize_flag
){
251 rresize_toffset
= quantize(xpix2tick(X
)) + q_tick
- rresize_torig
;
253 else if(lresize_flag
){
254 lresize_toffset
= quantize(xpix2tick(X
)) - lresize_torig
;
260 if(event_button()==1){
263 ui
->event_edit
->redraw();
266 else if(rresize_flag
){
270 ui
->event_edit
->redraw();
272 else if(lresize_flag
){
276 ui
->event_edit
->redraw();
278 else if(insert_flag
){
283 ui
->keyboard
->release_note(insert_note
,0);
284 ui
->keyboard
->redraw();
285 ui
->event_edit
->has
[0]=1;
286 ui
->event_edit
->has
[1]=1;
287 ui
->event_edit
->redraw();
288 ui
->event_menu
->redraw();
294 midi_track_off(cur_seqpat
->track
);
295 ui
->keyboard
->release_note(last_note
,0);
296 ui
->keyboard
->release_note(move_norig
+move_noffset
,0);
297 ui
->keyboard
->redraw();
298 ui
->event_edit
->redraw();
303 if(event_button()==3){
304 mevent
* over_n
= over_note();
305 if(delete_flag
&& over_n
){
306 if(over_n
->selected
){
308 midi_track_off(cur_seqpat
->track
);
309 ui
->event_edit
->redraw();
322 if(over_rhandle(e
,X
,Y
)){
323 if(resize_e
!= e
|| resize_arrow
!= 1){
324 if(e
->selected
){resize_arrow_color
= fltk::color(128,128,0);}
325 else{resize_arrow_color
= fltk::color(95,58,119);}
328 resize_x
= tick2xpix(e
->tick
+ e
->dur
) - resize_handle_width
;
329 resize_y
= note2ypix(e
->value1
);
333 else if(over_lhandle(e
,X
,Y
)){
334 if(resize_e
!= e
|| resize_arrow
!= 1){
335 if(e
->selected
){resize_arrow_color
= fltk::color(128,128,0);}
336 else{resize_arrow_color
= fltk::color(95,58,119);}
339 resize_x
= tick2xpix(e
->tick
)+1;
340 resize_y
= note2ypix(e
->value1
);
345 if(resize_e
!= e
|| resize_arrow
!= 0){
353 if(resize_arrow
!= 0){
364 void PianoRoll::draw(){
366 fltk::setcolor(fltk::GRAY05
);
367 fltk::fillrect(0,0,w(),h());
369 fltk::setcolor(fltk::GRAY20
);
370 for(int i
=12; i
<h(); i
+=12){
371 fltk::drawline(0,i
,w(),i
);
373 for(int i
=zoom
; i
<w(); i
+=zoom
){
374 fltk::drawline(i
,0,i
,h());
377 fltk::setcolor(fltk::GRAY30
);
378 for(int i
=12*5; i
<h(); i
+=12*7){
379 fltk::drawline(0,i
,w(),i
);
382 fltk::setcolor(fltk::GRAY50
);
383 for(int i
=zoom
*4; i
<w(); i
+=zoom
*4){
384 fltk::drawline(i
,0,i
,h());
387 fltk::setcolor(fltk::WHITE
);
388 int M
= config
.beats_per_measure
;
389 for(int i
=zoom
*4*M
; i
<w(); i
+=zoom
*4*M
){
390 fltk::fillrect(i
,0,1,h());
393 fltk::setcolor(fltk::color(128,0,0));
394 int rightend
= tick2xpix(cur_seqpat
->dur
);
395 fltk::fillrect(rightend
,0,1,h());
397 fltk::setcolor(fltk::color(128,128,0));
398 fltk::drawline(0,12*40,w(),12*40);
402 fltk::setcolor(fltk::BLUE
);
403 int T1
= insert_torig
;
404 int T2
= T1
+ insert_toffset
;
405 if(T1
>T2
){SWAP(T1
,T2
);}
406 int X
= tick2xpix(T1
)+1;
407 int Y
= note2ypix(insert_note
);
408 int W
= tick2xpix(T2
) - X
;
409 fltk::fillrect(X
,Y
,W
,11);
413 fltk::setcolor(fltk::MAGENTA
);
414 mevent
* ptr
= cur_seqpat
->p
->events
->next
;
416 if(ptr
->type
== MIDI_NOTE_ON
&& ptr
->selected
){
417 int X
= tick2xpix(ptr
->tick
+move_toffset
)+1;
418 int Y
= note2ypix(ptr
->value1
+move_noffset
);
419 int W
= tick2xpix(ptr
->dur
);
420 fltk::fillrect(X
,Y
,W
-1,1);
421 fltk::fillrect(X
,Y
+11,W
-1,1);
422 fltk::fillrect(X
,Y
,1,11);
423 fltk::fillrect(X
+W
-2,Y
,1,11);
432 mevent
* e
= cur_seqpat
->p
->events
->next
;
434 fltk::Color c1
,c2
,c3
;
437 if(e
->type
== MIDI_NOTE_ON
){
438 //fltk::fillrect(tick2xpix(e->tick),note2ypix(e->value),e->dur,11);
440 int R1
= rresize_flag
&&e
->selected
? rresize_toffset
: 0;
441 int R2
= lresize_flag
&&e
->selected
? lresize_toffset
: 0;
443 int T1
= e
->tick
+ R2
;
444 int T2
= e
->tick
+e
->dur
+ R1
;
446 if(T1
>= T2
-q_tick
&& e
->selected
){
451 else if(lresize_flag
){
452 T2
= e
->tick
+ e
->dur
;
457 int X
= tick2xpix(T1
) + 1;
458 int Y
= note2ypix(e
->value1
);
459 int W
= tick2xpix(T2
) - X
;
460 get_event_color(e
,&c1
,&c2
,&c3
);
463 fltk::fillrect(X
+1,Y
+1,W
-1,10);
466 fltk::fillrect(X
,Y
+11,W
,1);
467 fltk::fillrect(X
+W
-1,Y
+1,1,11);
470 fltk::fillrect(X
,Y
,W
,1);
471 fltk::fillrect(X
,Y
,1,11);
477 if(!rresize_flag
&& !lresize_flag
){
478 if(resize_arrow
> 0){
479 setcolor(resize_arrow_color
);
481 int W
= resize_handle_width
;
488 addvertex(X
+W
,Y
+H
/2);
491 else if(resize_arrow
< 0){
492 setcolor(resize_arrow_color
);
494 int W
= resize_handle_width
;
508 fltk::setcolor(fltk::GREEN
);
526 fltk::fillrect(X1
,Y1
,X2
-X1
,1);
527 fltk::fillrect(X1
,Y1
,1,Y2
-Y1
);
528 fltk::fillrect(X2
,Y1
,1,Y2
-Y1
);
529 fltk::fillrect(X1
,Y2
,X2
-X1
,1);
535 static int kludge
= 4; //very powerful magic
536 void PianoRoll::layout(){
538 /* the kludge is used so the fltk::ScrollGroup can update
539 widgets not contained within it. Better solution, the
540 scrollgroup could do its callback if it scrolls.
541 Subclassing fltk::ScrollGroup to add this behavior failed. */
547 ui
->pattern_timeline
->zoom
= zoom
;
548 ui
->event_edit
->zoom
= zoom
;
552 int W
= tick2xpix(cur_seqpat
->dur
);
558 int wp
= ui
->pattern_scroll
->w();
563 int hp
= ui
->pattern_scroll
->h();
569 int xp
= ui
->pattern_scroll
->xposition();
570 int yp
= ui
->pattern_scroll
->yposition();
575 ui
->pattern_scroll
->scrollTo(xp
,yp
);
578 ui
->pattern_timeline
->scroll
= xp
;
579 ui
->event_edit
->scroll
= xp
;
580 ui
->keyboard
->scroll
= yp
;
583 cur_seqpat
->scrolly
= yp
;
584 cur_seqpat
->scrollx
= xp
;
588 ui
->pattern_timeline
->redraw();
589 ui
->event_edit
->redraw();
592 ui
->keyboard
->redraw();
601 void PianoRoll::load(seqpat
* s
){
603 ui
->pattern_scroll
->scrollTo(0,300);
606 cur_track
= tracks
[s
->track
];
607 int W
= tick2xpix(s
->dur
);
610 ui
->pattern_timeline
->ticks_offset
= s
->tick
;
615 int PianoRoll::note2ypix(int note
){
616 int udy
= 6*(note
+ (note
+7)/12 + note
/12) + 12;
617 return h() - udy
+ 1;
620 int PianoRoll::tick2xpix(int tick
){
621 return tick
*zoom
*4 / 128;
624 int PianoRoll::xpix2tick(int xpix
){
625 return xpix
*128 / (zoom
*4);
628 int PianoRoll::quantize(int tick
){
629 return tick
/q_tick
* q_tick
;
633 void PianoRoll::set_zoom(int z
){
636 //int W = tick2xpix(cur_seqpat->dur);
641 mevent
* PianoRoll::over_note(){
642 mevent
* e
= cur_seqpat
->p
->events
->next
;
646 if(e
->type
== MIDI_NOTE_ON
){
647 cy
= note2ypix(e
->value1
);
648 lx
= tick2xpix(e
->tick
);
649 rx
= tick2xpix(e
->tick
+e
->dur
);
650 if(event_x() > lx
&& event_x() < rx
&&
651 event_y() < cy
+12 && event_y() > cy
){
663 void PianoRoll::update(int pos
){
664 if(!is_backend_playing() || !cur_seqpat
){
667 int wp
= ui
->pattern_scroll
->w();
668 int xp
= ui
->pattern_scroll
->xposition();
669 int yp
= ui
->pattern_scroll
->yposition();
670 int X1
= tick2xpix(pos
-cur_seqpat
->tick
);
676 ui
->pattern_scroll
->scrollTo(X1
-50<0?0:X1
-50,yp
);
679 ui
->pattern_scroll
->scrollTo(X1
-50,yp
);
684 void PianoRoll::unselect_all(){
685 mevent
* e
= cur_seqpat
->p
->events
;
687 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
==1){
696 void PianoRoll::get_event_color(mevent
* e
, fltk::Color
* c1
, fltk::Color
* c2
, fltk::Color
* c3
){
702 *c1
= fltk::color(229,79,75);
703 *c2
= fltk::color(120,60,58);
704 *c3
= fltk::color(225,131,109);
715 if(T1
>T2
){SWAP(T1
,T2
);}
716 if(N1
<N2
){SWAP(N1
,N2
);}
717 if(e
->tick
+e
->dur
> T1
&& e
->tick
< T2
&& N
>= N2
&& N
<= N1
){
718 *c1
= fltk::color(108,229,75);
719 *c2
= fltk::color(71,120,59);
720 *c3
= fltk::color(108,229,75);
726 *c1
= fltk::color(255,248,47);
727 *c2
= fltk::color(140,137,46);
728 *c3
= fltk::color(232,255,37);
732 *c1
= fltk::color(169,75,229);
733 *c2
= fltk::color(95,58,119);
734 *c3
= fltk::color(198,109,225);
738 void PianoRoll::apply_box(){
739 mevent
* e
= cur_seqpat
->p
->events
->next
;
746 if(T1
>T2
){SWAP(T1
,T2
);}
747 if(N1
<N2
){SWAP(N1
,N2
);}
750 if(e
->type
== MIDI_NOTE_ON
&&
751 e
->tick
+e
->dur
> T1
&& e
->tick
< T2
&&
759 void PianoRoll::apply_insert(){
760 if(insert_note
> 127 || insert_note
< 0){
765 int T1
= insert_torig
;
766 int T2
= T1
+ insert_toffset
;
767 if(T1
>T2
){SWAP(T1
,T2
);}
773 pattern
* p
= cur_seqpat
->p
;
774 Command
* c
=new CreateNote(p
,insert_note
,config
.defaultvelocity
,T1
,T2
-T1
);
778 cur_track
->restate();
781 void PianoRoll::apply_delete(){
785 pattern
* p
= cur_seqpat
->p
;
788 e
= cur_seqpat
->p
->events
->next
;
791 if(e
->selected
&& e
->type
== MIDI_NOTE_ON
){
792 c
=new DeleteNote(p
,e
);
800 cur_track
->restate();
803 void PianoRoll::apply_move(){
804 if(move_toffset
==0 && move_noffset
==0){
808 pattern
* p
= cur_seqpat
->p
;
809 mevent
* e
= p
->events
->next
;
811 int K
= e
->value1
+move_noffset
;
812 int T
= e
->tick
+move_toffset
;
813 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
&& (T
<0 || K
< 0 || K
> 127)){
825 for(int i
=0; i
<tracks
.size(); i
++){
829 if(e
->selected
&& e
->modified
== 0){
830 int K
= e
->value1
+ move_noffset
;
831 int T
= e
->tick
+ move_toffset
;
833 c
=new MoveNote(p
,e
,T
,K
);
844 if(e
->modified
){e
->modified
=0;}
848 cur_track
->restate();
851 void PianoRoll::apply_paste(){
857 void PianoRoll::apply_rresize(){
858 if(rresize_toffset
==0){
865 pattern
* p
= cur_seqpat
->p
;
872 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
&& e
->modified
== 0){
875 int R
= rresize_toffset
;
879 c
=new ResizeNote(p
,e
,W
+R
);
888 if(e
->modified
){e
->modified
=0;}
892 cur_track
->restate();
896 void PianoRoll::apply_lresize(){
897 if(lresize_toffset
==0){
904 pattern
* p
= cur_seqpat
->p
;
910 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
){
911 if(e
->tick
+ lresize_toffset
< 0){
921 if(e
->type
== MIDI_NOTE_ON
&& e
->selected
&& e
->modified
== 0){
925 int R
= lresize_toffset
;
931 mevent
* etmp
= e
->prev
;
932 c
=new ResizeNote(p
,e
,W
-R
);
936 c
=new MoveNote(p
,e
,T
+R
,e
->value1
);
946 if(e
->modified
){e
->modified
=0;}
950 cur_track
->restate();
962 int PianoRoll::over_rhandle(mevent
* e
, int X
, int Y
){
963 int X1
= tick2xpix(e
->tick
);
964 int X2
= X1
+ tick2xpix(e
->dur
);
965 int Y1
= note2ypix(e
->value1
);
968 if(X2
-X1
< resize_handle_width
*3){
972 return (Y
> Y1
&& Y
< Y2
&& X
< X2
&& X
> X2
- resize_handle_width
);
975 int PianoRoll::over_lhandle(mevent
* e
, int X
, int Y
){
976 int X1
= tick2xpix(e
->tick
);
977 int X2
= X1
+ tick2xpix(e
->dur
);
978 int Y1
= note2ypix(e
->value1
);
981 if(X2
-X1
< resize_handle_width
*3){
985 return (Y
> Y1
&& Y
< Y2
&& X
< X1
+ resize_handle_width
+1 && X
> X1
+ 1);