Added basic recording.
[epichord.git] / src / pianoroll.cpp
blob770f33c744edf135a2a2d5b0f059ac9c620460bb
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 extern UI* ui;
37 extern std::vector<track*> tracks;
39 using namespace fltk;
41 PianoRoll::PianoRoll(int x, int y, int w, int h, const char* label = 0) : fltk::Widget(x, y, w, h, label) {
42 wkeyh = 12;
43 bkeyh = 7;
44 cur_seqpat = NULL;
45 main_sel = NULL;
47 zoom = 30;
48 zoom_n = 4;
50 q_tick = 32;
53 int PianoRoll::handle(int event){
54 Command* c;
55 pattern* p;
57 switch(event){
58 case fltk::FOCUS:
59 return 1;
60 case fltk::SHORTCUT:
61 if(event_state(CTRL) && event_key()=='c'){
62 //printf("roll copy\n");
63 return 1;
65 if(event_key() == '-'){
66 if(zoom_n > 1){
67 zoom_n--;
68 set_zoom(30*(1<<zoom_n)/16);
69 ui->pattern_timeline->zoom = zoom;
70 ui->pattern_timeline->redraw();
71 ui->event_edit->zoom = zoom;
72 ui->event_edit->redraw();
74 redraw();
75 return 1;
77 if(event_key() == '='){
78 if(zoom_n < 8){
79 zoom_n++;
80 set_zoom(30*(1<<zoom_n)/16);
81 ui->pattern_timeline->zoom = zoom;
82 ui->pattern_timeline->redraw();
83 ui->event_edit->zoom = zoom;
84 ui->event_edit->redraw();
86 redraw();
87 return 1;
89 return 0;
90 case fltk::PUSH:
91 take_focus();
92 if(event_button()==1){//left mouse
93 if(over_note()==NULL){//new note init
94 new_drag = 1;
95 new_left_t = quantize(xpix2tick(event_x()));
96 new_orig_t = new_left_t;
97 new_note = ypix2note(event_y(),1);
98 new_right_t = new_left_t + q_tick;
100 last_note = new_note;
101 if(1){//play on insert
102 ui->keyboard->play_note(last_note,0);
105 else{
106 //if shift, add to selection
107 main_sel = over_note();
108 if(over_handle(main_sel)){//begin resize or resize move
110 else{//begin move
111 move_flag = 1;
112 move_t = main_sel->tick;
113 move_offset = quantize(xpix2tick(event_x())) - move_t;
114 //move_track = event_y() / 30;
115 move_note = ypix2note(event_y(),1);
117 last_note = move_note;
118 if(1){//play on move
119 ui->keyboard->play_note(last_note,0);
122 redraw();
125 else if(event_button()==2){//middle mouse
126 //button initiates paste
128 else if(event_button()==3){//right mouse
129 if(over_note()==NULL){//begin box
132 else{//set up for deletion
133 delete_flag = 1;
134 main_sel = over_note();
137 redraw();
138 return 1;
139 case fltk::DRAG:
140 if(new_drag){
141 new_right_t = quantize(xpix2tick(event_x())+q_tick);
142 if(new_right_t <= new_orig_t){
143 new_left_t = new_right_t - q_tick;
144 new_right_t = new_orig_t;
146 else{
147 new_left_t = new_orig_t;
149 new_note = ypix2note(event_y(),1);
150 if(new_note != last_note){
151 if(1){//play on insert
152 ui->keyboard->release_note(last_note,0);
153 ui->keyboard->play_note(new_note,0);
155 last_note = new_note;
157 redraw();
158 return 1;
160 else if(move_flag){
161 move_t = quantize(xpix2tick(event_x())) - move_offset;
162 move_note = ypix2note(event_y(),1);
163 if(move_note != last_note){
164 if(1){//play on move
165 ui->keyboard->release_note(last_note,0);
166 ui->keyboard->play_note(move_note,0);
168 last_note = move_note;
170 redraw();
171 return 1;
173 case fltk::RELEASE:
174 if(event_button()==1){
175 if(new_drag && new_note < 128 && new_note >= 0){
176 p = cur_seqpat->p;
177 c=new CreateNote(p,new_note,127,new_left_t,new_right_t-new_left_t);
178 set_undo(c);
179 undo_push(1);
180 ui->keyboard->release_note(new_note,0);
181 ui->keyboard->redraw();
183 else if(move_flag && move_note < 128 && move_note >= 0){
184 int play_pos = get_play_position();
185 mevent* e = main_sel;
186 track* tr = tracks[cur_seqpat->track];
187 if(play_pos > e->tick && play_pos < e->tick + e->dur){
188 midi_note_off(e->value1,tr->chan,tr->port);
190 int old_note = main_sel->value1;
191 c=new MoveNote(cur_seqpat->p,main_sel,move_t,move_note);
192 set_undo(c);
193 undo_push(1);
195 int cur_chan = tracks[cur_seqpat->track]->chan;
196 int cur_port = tracks[cur_seqpat->track]->port;
197 midi_note_off(old_note,cur_chan,cur_port);
199 ui->keyboard->release_note(move_note,0);
200 ui->keyboard->redraw();
202 new_drag=0;
203 move_flag=0;
205 if(event_button()==3){
206 if(delete_flag && over_note() == main_sel){
207 //here we need more branches for deleting the entire selection
208 c=new DeleteNote(cur_seqpat->p, main_sel);
209 set_undo(c);
210 undo_push(1);
212 delete_flag = 0;
214 redraw();
215 return 1;
217 return 0;
220 void PianoRoll::draw(){
221 fltk::setcolor(fltk::GRAY05);
222 fltk::fillrect(0,0,w(),h());
224 fltk::setcolor(fltk::GRAY20);
225 for(int i=12; i<h(); i+=12){
226 fltk::drawline(0,i,w(),i);
228 for(int i=zoom; i<w(); i+=zoom){
229 fltk::drawline(i,0,i,h());
232 fltk::setcolor(fltk::GRAY30);
233 for(int i=12*5; i<h(); i+=12*7){
234 fltk::drawline(0,i,w(),i);
237 fltk::setcolor(fltk::GRAY50);
238 for(int i=zoom*4; i<w(); i+=zoom*4){
239 fltk::drawline(i,0,i,h());
242 fltk::setcolor(fltk::color(128,128,0));
243 fltk::drawline(0,12*40,w(),12*40);
245 if(new_drag){
246 fltk::setcolor(fltk::BLUE);
247 int X = tick2xpix(new_left_t)+1;
248 int Y = note2ypix(new_note);
249 int W = tick2xpix(new_right_t) - X;
250 fltk::fillrect(X,Y,W,11);
253 if(move_flag){
254 fltk::setcolor(fltk::RED);
255 int X = tick2xpix(move_t)+1;
256 int Y = note2ypix(move_note);
257 int W = tick2xpix(main_sel->dur);
258 fltk::fillrect(X,Y,W-1,1);
259 fltk::fillrect(X,Y+11,W-1,1);
260 fltk::fillrect(X,Y,1,11);
261 fltk::fillrect(X+W-2,Y,1,11);
264 //draw all notes
265 mevent* e = cur_seqpat->p->events->next;
267 int c1,c2,c3;
269 while(e){
270 if(e->type == MIDI_NOTE_ON){
271 //fltk::fillrect(tick2xpix(e->tick),note2ypix(e->value),e->dur,11);
272 int X = tick2xpix(e->tick) + 1;
273 int Y = note2ypix(e->value1);
274 int W = tick2xpix(e->tick+e->dur) - X;
275 if(e == main_sel){
276 c1 = 230;
277 c2 = 230;
278 c3 = 0;
280 else{
281 c1 = 165;
282 c2 = 75;
283 c3 = 229;
285 fltk::setcolor(fltk::color(c1,c2,c3));
286 fltk::fillrect(X+1,Y+1,W-1,10);
288 fltk::setcolor(fltk::color(c1*3/4,c2*3/4,c3*3/4));
289 fltk::fillrect(X,Y+11,W,1);
290 fltk::fillrect(X+W-1,Y+1,1,11);
292 fltk::setcolor(fltk::color(c1*3/4+64,c2*3/4+64,c3*3/4+64));
293 fltk::fillrect(X,Y,W,1);
294 fltk::fillrect(X,Y,1,11);
296 e=e->next;
302 static int kludge = 4; //very powerful magic
303 void PianoRoll::layout(){
305 /* the kludge is used so the fltk::ScrollGroup can update
306 widgets not contained within it. Better solution, the
307 scrollgroup could do its callback if it scrolls.
308 Subclassing fltk::ScrollGroup to add this behavior failed. */
309 if(kludge != 0){
310 kludge--;
311 return;
314 if(cur_seqpat){
315 int W = tick2xpix(cur_seqpat->dur);
316 resize(W+300,h());
319 int wp = ui->pattern_scroll->w();
320 if(wp > w()){
321 w(wp+120);
324 int hp = ui->pattern_scroll->h();
325 if(hp > h()){
326 h(hp+120);
329 int xp = ui->pattern_scroll->xposition();
330 int yp = ui->pattern_scroll->yposition();
332 if(xp > w() - wp){
333 xp = w() - wp;
334 ui->pattern_scroll->scrollTo(xp,yp);
337 ui->pattern_timeline->scroll = xp;
338 ui->event_edit->scroll = xp;
339 ui->keyboard->scroll = yp;
341 if(cur_seqpat){
342 cur_seqpat->scrolly = yp;
343 cur_seqpat->scrollx = xp;
346 if(xp_last != xp){
347 ui->pattern_timeline->redraw();
348 ui->event_edit->redraw();
350 if(yp_last != yp){
351 ui->keyboard->redraw();
354 yp_last = yp;
355 xp_last = xp;
360 void PianoRoll::load(seqpat* s){
361 cur_seqpat = s;
362 cur_track = tracks[s->track];
363 int W = tick2xpix(s->dur);
364 resize(W+300,h());
366 ui->pattern_timeline->ticks_offset = s->tick;
369 int PianoRoll::note2ypix(int note){
370 int udy = 6*(note + (note+7)/12 + note/12) + 12;
371 return h() - udy + 1;
374 int PianoRoll::tick2xpix(int tick){
375 return tick*zoom*4 / 128;
378 int PianoRoll::xpix2tick(int xpix){
379 return xpix*128 / (zoom*4);
382 int PianoRoll::quantize(int tick){
383 return tick/q_tick * q_tick;
387 void PianoRoll::set_zoom(int z){
388 zoom = z;
389 relayout();
390 //int W = tick2xpix(cur_seqpat->dur);
391 //resize(W+300,h());
395 mevent* PianoRoll::over_note(){
396 mevent* e = cur_seqpat->p->events->next;
398 int cy, lx, rx;
399 while(e){
400 if(e->type == MIDI_NOTE_ON){
401 cy = note2ypix(e->value1);
402 lx = tick2xpix(e->tick);
403 rx = tick2xpix(e->tick+e->dur);
404 if(event_x() > lx && event_x() < rx &&
405 event_y() < cy+12 && event_y() > cy){
406 return e;
409 e = e->next;
412 return NULL;
415 int PianoRoll::over_handle(mevent* e){
416 return 0;