Fixed bug in follow playback.
[epichord.git] / src / pianoroll.cpp
blob1c6cc8e734cfaee990a35236ba91a08935a1cce6
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 PianoRoll::PianoRoll(int x, int y, int w, int h, const char* label = 0) : fltk::Widget(x, y, w, h, label) {
46 wkeyh = 12;
47 bkeyh = 7;
48 cur_seqpat = NULL;
49 main_sel = NULL;
51 zoom = 15;
52 zoom_n = 3;
54 q_tick = 32;
56 xp_last = 0;
57 yp_last = 0;
60 int PianoRoll::handle(int event){
61 Command* c;
62 pattern* p;
64 switch(event){
65 case fltk::FOCUS:
66 return 1;
67 case fltk::SHORTCUT:
68 if(event_state(CTRL) && event_key()=='c'){
69 //printf("roll copy\n");
70 return 1;
72 if(zoom_out_key(event_key(),event_state())){
73 if(zoom_n > 1){
74 zoom_n--;
75 set_zoom(30*(1<<zoom_n)/16);
76 ui->pattern_timeline->zoom = zoom;
77 ui->pattern_timeline->update(get_play_position());
78 ui->pattern_timeline->redraw();
79 ui->event_edit->zoom = zoom;
80 ui->event_edit->redraw();
82 redraw();
83 return 1;
85 if(zoom_in_key(event_key(),event_state())){
86 if(zoom_n < 8){
87 zoom_n++;
88 set_zoom(30*(1<<zoom_n)/16);
89 ui->pattern_timeline->zoom = zoom;
90 ui->pattern_timeline->update(get_play_position());
91 ui->pattern_timeline->redraw();
92 ui->event_edit->zoom = zoom;
93 ui->event_edit->redraw();
95 redraw();
96 return 1;
98 return 0;
99 case fltk::PUSH:
100 take_focus();
101 if(event_button()==1){//left mouse
102 if(over_note()==NULL){//new note init
103 new_drag = 1;
104 new_left_t = quantize(xpix2tick(event_x()));
105 new_orig_t = new_left_t;
106 new_note = ypix2note(event_y(),1);
107 new_right_t = new_left_t + q_tick;
109 last_note = new_note;
110 if(config.playinsert){
111 ui->keyboard->play_note(last_note,0);
114 else{
115 //if shift, add to selection
116 main_sel = over_note();
117 if(over_handle(main_sel)){//begin resize or resize move
119 else{//begin move
120 move_flag = 1;
121 move_t = quantize(main_sel->tick);
122 move_offset = quantize(xpix2tick(event_x())) - move_t;
123 //move_track = event_y() / 30;
124 move_note = ypix2note(event_y(),1);
126 last_note = move_note;
127 if(config.playmove){
128 ui->keyboard->play_note(last_note,0);
131 redraw();
134 else if(event_button()==2){//middle mouse
135 //button initiates paste
137 else if(event_button()==3){//right mouse
138 if(over_note()==NULL){//begin box
141 else{//set up for deletion
142 delete_flag = 1;
143 main_sel = over_note();
146 redraw();
147 return 1;
148 case fltk::DRAG:
149 if(new_drag){
150 new_right_t = quantize(xpix2tick(event_x())+q_tick);
151 if(new_right_t <= new_orig_t){
152 new_left_t = new_right_t - q_tick;
153 new_right_t = new_orig_t;
155 else{
156 new_left_t = new_orig_t;
158 new_note = ypix2note(event_y(),1);
159 if(new_note != last_note){
160 if(config.playinsert){//play on insert
161 ui->keyboard->release_note(last_note,0);
162 ui->keyboard->play_note(new_note,0);
164 last_note = new_note;
166 redraw();
167 return 1;
169 else if(move_flag){
170 move_t = quantize(xpix2tick(event_x())) - move_offset;
171 move_note = ypix2note(event_y(),1);
172 if(move_note != last_note){
173 if(config.playmove){//play on move
174 ui->keyboard->release_note(last_note,0);
175 ui->keyboard->play_note(move_note,0);
177 last_note = move_note;
179 redraw();
180 return 1;
182 case fltk::RELEASE:
183 if(event_button()==1){
184 if(new_drag && new_note < 128 && new_note >= 0){
185 p = cur_seqpat->p;
186 c=new CreateNote(p,new_note,127,new_left_t,new_right_t-new_left_t);
187 set_undo(c);
188 undo_push(1);
189 ui->keyboard->release_note(new_note,0);
190 ui->keyboard->redraw();
191 ui->event_edit->has[0]=1;
192 ui->event_edit->has[1]=1;
193 ui->event_edit->redraw();
194 ui->event_menu->redraw();
196 else if(move_flag && move_note < 128 && move_note >= 0){
197 int play_pos = get_play_position();
198 mevent* e = main_sel;
199 track* tr = tracks[cur_seqpat->track];
200 if(play_pos > e->tick && play_pos < e->tick + e->dur){
201 midi_note_off(e->value1,tr->chan,tr->port);
203 int old_note = main_sel->value1;
204 c=new MoveNote(cur_seqpat->p,main_sel,move_t,move_note);
205 set_undo(c);
206 undo_push(1);
208 int cur_chan = tracks[cur_seqpat->track]->chan;
209 int cur_port = tracks[cur_seqpat->track]->port;
210 midi_note_off(old_note,cur_chan,cur_port);
212 ui->keyboard->release_note(move_note,0);
213 ui->keyboard->redraw();
214 ui->event_edit->redraw();
216 new_drag=0;
217 move_flag=0;
219 if(event_button()==3){
220 if(delete_flag && over_note() == main_sel){
221 //here we need more branches for deleting the entire selection
222 c=new DeleteNote(cur_seqpat->p, main_sel);
223 set_undo(c);
224 undo_push(1);
226 ui->event_edit->redraw();
228 delete_flag = 0;
230 redraw();
232 return 1;
234 return 0;
237 void PianoRoll::draw(){
238 fltk::setcolor(fltk::GRAY05);
239 fltk::fillrect(0,0,w(),h());
241 fltk::setcolor(fltk::GRAY20);
242 for(int i=12; i<h(); i+=12){
243 fltk::drawline(0,i,w(),i);
245 for(int i=zoom; i<w(); i+=zoom){
246 fltk::drawline(i,0,i,h());
249 fltk::setcolor(fltk::GRAY30);
250 for(int i=12*5; i<h(); i+=12*7){
251 fltk::drawline(0,i,w(),i);
254 fltk::setcolor(fltk::GRAY50);
255 for(int i=zoom*4; i<w(); i+=zoom*4){
256 fltk::drawline(i,0,i,h());
259 fltk::setcolor(fltk::WHITE);
260 int M = config.beats_per_measure;
261 for(int i=zoom*4*M; i<w(); i+=zoom*4*M){
262 fltk::fillrect(i,0,1,h());
265 fltk::setcolor(fltk::color(128,0,0));
266 int rightend = tick2xpix(cur_seqpat->dur);
267 fltk::fillrect(rightend,0,1,h());
269 fltk::setcolor(fltk::color(128,128,0));
270 fltk::drawline(0,12*40,w(),12*40);
272 if(new_drag){
273 fltk::setcolor(fltk::BLUE);
274 int X = tick2xpix(new_left_t)+1;
275 int Y = note2ypix(new_note);
276 int W = tick2xpix(new_right_t) - X;
277 fltk::fillrect(X,Y,W,11);
280 if(move_flag){
281 fltk::setcolor(fltk::color(255,0,0));
282 int X = tick2xpix(move_t)+1;
283 int Y = note2ypix(move_note);
284 int W = tick2xpix(main_sel->dur);
285 fltk::fillrect(X,Y,W-1,1);
286 fltk::fillrect(X,Y+11,W-1,1);
287 fltk::fillrect(X,Y,1,11);
288 fltk::fillrect(X+W-2,Y,1,11);
291 //draw all notes
292 mevent* e = cur_seqpat->p->events->next;
294 int c1,c2,c3;
296 while(e){
297 if(e->type == MIDI_NOTE_ON){
298 //fltk::fillrect(tick2xpix(e->tick),note2ypix(e->value),e->dur,11);
299 int X = tick2xpix(e->tick) + 1;
300 int Y = note2ypix(e->value1);
301 int W = tick2xpix(e->tick+e->dur) - X;
302 if(e == main_sel){
303 c1 = 230;
304 c2 = 230;
305 c3 = 0;
307 else{
308 c1 = 165;
309 c2 = 75;
310 c3 = 229;
312 fltk::setcolor(fltk::color(c1,c2,c3));
313 fltk::fillrect(X+1,Y+1,W-1,10);
315 fltk::setcolor(fltk::color(c1*3/4,c2*3/4,c3*3/4));
316 fltk::fillrect(X,Y+11,W,1);
317 fltk::fillrect(X+W-1,Y+1,1,11);
319 fltk::setcolor(fltk::color(c1*3/4+64,c2*3/4+64,c3*3/4+64));
320 fltk::fillrect(X,Y,W,1);
321 fltk::fillrect(X,Y,1,11);
323 e=e->next;
329 static int kludge = 4; //very powerful magic
330 void PianoRoll::layout(){
332 /* the kludge is used so the fltk::ScrollGroup can update
333 widgets not contained within it. Better solution, the
334 scrollgroup could do its callback if it scrolls.
335 Subclassing fltk::ScrollGroup to add this behavior failed. */
336 if(kludge != 0){
337 kludge--;
338 return;
341 ui->pattern_timeline->zoom = zoom;
342 ui->event_edit->zoom = zoom;
344 if(cur_seqpat){
345 int W = tick2xpix(cur_seqpat->dur);
346 resize(W+300,h());
349 int wp = ui->pattern_scroll->w();
350 if(wp > w()){
351 w(wp+120);
354 int hp = ui->pattern_scroll->h();
355 if(hp > h()){
356 h(hp+120);
359 int xp = ui->pattern_scroll->xposition();
360 int yp = ui->pattern_scroll->yposition();
362 if(xp > w() - wp){
363 xp = w() - wp;
364 ui->pattern_scroll->scrollTo(xp,yp);
367 ui->pattern_timeline->scroll = xp;
368 ui->event_edit->scroll = xp;
369 ui->keyboard->scroll = yp;
371 if(cur_seqpat){
372 cur_seqpat->scrolly = yp;
373 cur_seqpat->scrollx = xp;
376 if(xp_last != xp){
377 ui->pattern_timeline->redraw();
378 ui->event_edit->redraw();
380 if(yp_last != yp){
381 ui->keyboard->redraw();
384 yp_last = yp;
385 xp_last = xp;
390 void PianoRoll::load(seqpat* s){
391 cur_seqpat = s;
392 cur_track = tracks[s->track];
393 int W = tick2xpix(s->dur);
394 resize(W+300,h());
396 ui->pattern_timeline->ticks_offset = s->tick;
399 int PianoRoll::note2ypix(int note){
400 int udy = 6*(note + (note+7)/12 + note/12) + 12;
401 return h() - udy + 1;
404 int PianoRoll::tick2xpix(int tick){
405 return tick*zoom*4 / 128;
408 int PianoRoll::xpix2tick(int xpix){
409 return xpix*128 / (zoom*4);
412 int PianoRoll::quantize(int tick){
413 return tick/q_tick * q_tick;
417 void PianoRoll::set_zoom(int z){
418 zoom = z;
419 relayout();
420 //int W = tick2xpix(cur_seqpat->dur);
421 //resize(W+300,h());
425 mevent* PianoRoll::over_note(){
426 mevent* e = cur_seqpat->p->events->next;
428 int cy, lx, rx;
429 while(e){
430 if(e->type == MIDI_NOTE_ON){
431 cy = note2ypix(e->value1);
432 lx = tick2xpix(e->tick);
433 rx = tick2xpix(e->tick+e->dur);
434 if(event_x() > lx && event_x() < rx &&
435 event_y() < cy+12 && event_y() > cy){
436 return e;
439 e = e->next;
442 return NULL;
445 int PianoRoll::over_handle(mevent* e){
446 return 0;
451 void PianoRoll::update(int pos){
452 if(!is_backend_playing()){
453 return;
455 int wp = ui->pattern_scroll->w();
456 int xp = ui->pattern_scroll->xposition();
457 int yp = ui->pattern_scroll->yposition();
458 int X1 = tick2xpix(pos-cur_seqpat->tick);
459 int X2 = X1 - xp;
460 if(X1 > w()-40){
461 return;
463 if(X2 < 0){
464 ui->pattern_scroll->scrollTo(X1-50<0?0:X1-50,yp);
466 if(X2 > wp-30){
467 ui->pattern_scroll->scrollTo(X1-50,yp);