Added DragBar widget to replace horizontal thumbwheels.
[epichord.git] / src / uihelper.cpp
blob5403db68299cdbf0c85a099d8495b7a957f16b43
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 <stdlib.h>
24 #include <vector>
25 #include <fstream>
26 #include <string.h>
27 #include <math.h>
29 #include <limits>
31 #include <fltk/run.h>
33 #include "seq.h"
34 #include "ui.h"
35 #include "backend.h"
37 #include "uihelper.h"
40 #define CONFIG_FILENAME ".epichordrc"
42 extern UI* ui;
43 extern std::vector<track*> tracks;
45 struct conf config;
47 using namespace std;
49 char* config_filename;
53 void load_config(){
55 //linux dependent
56 char* homepath = getenv("HOME");
57 asprintf(&config_filename,"%s/"CONFIG_FILENAME,homepath);
59 fstream f;
60 f.open(config_filename,fstream::in);
61 if(!f.is_open()){
62 printf("load_config: Unable to open config file for reading.\n");
63 config.beats_per_measure = 4;
64 config.measures_per_phrase = 4;
65 config.measures_until_record = 1;
66 config.alwayscopy = 0;
67 config.autotrackname = 0;
68 config.passthru = 1;
69 config.playinsert = 1;
70 config.recordonchan = 0;
71 config.playmove = 1;
72 config.follow = 1;
73 config.recordmode = 0;
74 config.robmode = 0;
75 config.defaultvelocity = 96;
77 load_default_keymap();
78 update_config_gui();
79 return;
82 config.beats_per_measure = 4;
83 config.measures_per_phrase = 4;
85 std::string word;
87 while(!f.eof()){
88 word = "";
89 f >> word;
90 if(word == "leadin"){f>>config.measures_until_record;}
91 else if(word == "alwayscopy"){f>>config.alwayscopy;}
92 else if(word == "autotrackname"){f>>config.autotrackname;}
93 else if(word == "passthru"){f>>config.passthru;}
94 else if(word == "playinsert"){f>>config.playinsert;}
95 else if(word == "recordonchan"){f>>config.recordonchan;}
96 else if(word == "playmove"){f>>config.playmove;}
97 else if(word == "follow"){f>>config.follow;}
98 else if(word == "recordmode"){f>>config.recordmode;}
99 else if(word == "robmode"){f>>config.robmode;}
100 else if(word == "keymap"){load_keymap(f);}
101 else if(word == "defaultvelocity"){f>>config.defaultvelocity;}
102 else{
103 f.ignore(std::numeric_limits<streamsize>::max(),'\n');
106 update_config_gui();
107 f.close();
110 void save_config(){
111 fstream f;
112 f.open(config_filename,fstream::out);
113 if(!f.is_open()){
114 printf("save_config: Unable to open config file %s for saving.\n", config_filename);
115 return;
118 f << "leadin " << config.measures_until_record << endl;
119 f << "alwayscopy " << config.alwayscopy << endl;
120 f << "autotrackname " << config.autotrackname << endl;
121 f << "passthru " << config.passthru << endl;
122 f << "playinsert " << config.playinsert << endl;
123 f << "recordonchan " << config.recordonchan << endl;
124 f << "playmove " << config.playmove << endl;
125 f << "follow " << config.follow << endl;
126 //f << "quantizedur " << config.quantizedur << endl;
127 f << "recordmode " << config.recordmode << endl;
128 f << "robmode " << config.robmode << endl;
129 f << "defaultvelocity " << config.defaultvelocity << endl;
130 f << endl;
131 save_keymap(f);
132 f.close();
135 void update_config_gui(){
136 ui->beats_per_measure->value(config.beats_per_measure);
137 ui->measures_per_phrase->value(config.measures_per_phrase);
138 ui->measures_until_record->value(config.measures_until_record);
140 ui->bpm_wheel->value(config.beats_per_minute);
141 ui->bpm_output->value(config.beats_per_minute);
143 ui->check_alwayscopy->state(config.alwayscopy);
144 ui->check_autotrackname->state(config.autotrackname);
145 ui->check_passthru->state(config.passthru);
146 ui->check_playinsert->state(config.playinsert);
147 ui->check_recordonchan->state(config.recordonchan);
148 ui->check_playmove->state(config.playmove);
149 ui->check_follow->state(config.follow);
151 ui->menu_recordmode->value(config.recordmode);
152 ui->menu_rob->value(config.robmode);
154 ui->default_velocity->value(config.defaultvelocity);
156 ui->config_window->redraw();
161 seqpat* rob_check(seqpat* s){
162 seqpat* prev = s->prev;
163 Command* c;
164 if(config.robmode == 0){
165 return NULL;
167 else if(config.robmode == 1 || prev == NULL){
168 int pos = get_play_position();
169 int M = config.measures_per_phrase;
170 if(M!=0){
171 M = M*config.beats_per_measure*128;
173 else{
174 M = 4*config.beats_per_measure*128;
176 int P1 = pos/M*M;
177 int P2 = P1 + M;
178 int T = P1;
179 int R = s->tick+s->dur;
180 if(R > P1){
181 T = R;
183 int W = P2 - T;
184 if(s->next){
185 int L = s->next->tick;
186 if(L < P2){
187 W = L - T;
190 c = new CreateSeqpatBlank(s->track,T,W);
191 set_undo(c);
192 undo_push(1);
193 return s->next;
195 else if(config.robmode == 2){
196 int pos = get_play_position();
197 int M = config.measures_per_phrase;
198 if(M!=0){
199 M = M*config.beats_per_measure*128;
201 else{
202 M = 4*config.beats_per_measure*128;
204 int P = pos/M*M + M;//tick at next phrase boundary
205 int W = P - s->tick;
206 if(s->next){
207 int W2 = s->next->tick - s->tick;
208 if(W2 < W){
209 W=W2;
212 c = new ResizeSeqpat(s,W);
213 set_undo(c);
214 undo_push(1);
215 return prev->next;
221 int last_pos=0;
222 void playing_timeout_cb(void* v){
223 int pos = get_play_position();
225 if(pos < last_pos){
226 reset_record_flags();
228 last_pos = pos;
230 if(config.follow){
231 ui->arranger->update(pos);
232 ui->piano_roll->update(pos);
234 ui->song_timeline->update(pos);
235 ui->pattern_timeline->update(pos);
236 ui->metronome->update(pos);
238 //check for midi input
239 int tick;
240 int chan;
241 int type;
242 int val1;
243 int val2;
245 track* t = tracks[get_rec_track()];
246 Command* c;
247 seqpat* s;
248 pattern* p;
250 char report[256];
252 while(recv_midi(&chan,&tick,&type,&val1,&val2)){
254 if(config.recordonchan){
255 for(int i=0; i<tracks.size(); i++){
256 if(tracks[i]->chan == chan){
257 t = tracks[i];
262 switch(type){
263 case 0x80://note off
264 snprintf(report,256,"%02x %02x %02x : note off - ch %d note %d vel %d\n",type|chan,val1,val2,chan,val1,val2);
265 scope_print(report);
267 if(!is_backend_recording())
268 break;
270 s = tfind<seqpat>(t->head,tick);
271 if(s->tick+s->dur < tick){
272 s = rob_check(s);
273 if(!s){continue;}
276 //if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
277 s->record_check(config.recordmode);
278 p = s->p;
279 c=new CreateNoteOff(p,val1,val2,tick-s->tick);
280 set_undo(c);
281 undo_push(1);
282 if(ui->piano_roll->visible()){
283 ui->piano_roll->redraw();
284 ui->event_edit->redraw();
285 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[1]=1;}
286 ui->event_menu->redraw();
288 if(ui->arranger->visible())
289 ui->arranger->redraw();
290 break;
291 case 0x90://note on
292 snprintf(report,256,"%02x %02x %02x : note on - ch %d note %d vel %d\n",type|chan,val1,val2,chan,val1,val2);
293 scope_print(report);
295 if(!is_backend_recording())
296 break;
298 s = tfind<seqpat>(t->head,tick);
299 if(s->tick+s->dur < tick){
300 s = rob_check(s);
301 if(!s){continue;}
304 // if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
305 s->record_check(config.recordmode);
306 p = s->p;
307 c=new CreateNoteOn(p,val1,val2,tick-s->tick,16);
308 set_undo(c);
309 undo_push(1);
310 if(ui->piano_roll->visible()){
311 ui->piano_roll->redraw();
312 ui->event_edit->redraw();
313 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[0]=1;}
314 ui->event_menu->redraw();
316 if(ui->arranger->visible())
317 ui->arranger->redraw();
318 break;
319 case 0xa0://aftertouch
320 case 0xb0://controller
321 case 0xc0://program change
322 case 0xd0://channel pressure
323 case 0xe0://pitch wheel
325 s = tfind<seqpat>(t->head,tick);
326 if(s->tick+s->dur < tick){
327 s = rob_check(s);
328 if(!s){continue;}
331 switch(type){
332 case 0xa0:
333 snprintf(report,256,"%02x %02x %02x : aftertouch - ch %d note %d %d\n",type|chan,val1,val2,chan,val1,val2);
334 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[2]=1;}
335 break;
336 case 0xb0:
337 snprintf(report,256,"%02x %02x %02x : controller change - ch %d cntr %d val %d\n",type|chan,val1,val2,chan,val1,val2);
338 if(ui->event_edit->cur_seqpat == s){
339 ui->event_edit->has[val1+6]=1;
341 break;
342 case 0xc0:
343 snprintf(report,256,"%02x %02x : program change - ch %d pgrm %d \n",type|chan,val1,chan,val1);
344 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[3]=1;}
345 break;
346 case 0xd0:
347 snprintf(report,256,"%02x %02x : channel pressure - ch %d val %d \n",type|chan,val1,chan,val1);
348 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[4]=1;}
349 break;
350 case 0xe0:
351 snprintf(report,256,"%02x %02x %02x : pitch wheel - ch %d val %d \n",type|chan,val1,val2,chan,(val2<<7)|val1);
352 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[5]=1;}
353 break;
355 scope_print(report);
357 if(!is_backend_recording())
358 break;
360 // if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
361 s->record_check(config.recordmode);
362 p = s->p;
363 c=new CreateEvent(p,type,tick,val1,val2);
364 set_undo(c);
365 undo_push(1);
366 if(ui->piano_roll->visible()){
367 ui->piano_roll->redraw();
368 ui->event_edit->redraw();
369 ui->event_menu->redraw();
371 if(ui->arranger->visible())
372 ui->arranger->redraw();
373 break;
374 case 0xf0:
375 switch(chan){
376 case 1://undefined (reserved) system common message
377 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
378 break;
379 case 2://song position pointer
380 snprintf(report,256,"%02x %02x %02x : song position - %d \n",type|chan,val1,val2,(val2<<7)|val1);
381 break;
382 case 3://song select
383 snprintf(report,256,"%02x %02x : song select - %d \n",type|chan,val1,val1);
384 break;
385 case 4://undefined (reserved) system common message
386 case 5://undefined (reserved) system common message
387 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
388 break;
389 case 6://tune request
390 snprintf(report,256,"%02x : tune request\n",type|chan);
391 break;
392 case 7://end of exclusive
393 snprintf(report,256,"%02x : end of exclusive\n",type|chan);
394 break;
395 case 8://timing clock
396 snprintf(report,256,"%02x : timing clock\n",type|chan);
397 break;
398 case 9://undefined (reserved) system common message
399 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
400 break;
401 case 10://start
402 snprintf(report,256,"%02x : start\n",type|chan);
403 break;
404 case 11://continue
405 snprintf(report,256,"%02x : continue\n",type|chan);
406 break;
407 case 12://stop
408 snprintf(report,256,"%02x : stop\n",type|chan);
409 break;
410 case 13://undefined
411 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
412 break;
413 case 14://active sensing
414 snprintf(report,256,"%02x : active sensing\n",type|chan);
415 break;
416 case 15://reset
417 snprintf(report,256,"%02x : reset\n",type|chan);
418 break;
420 if(chan==0){
421 snprintf(report,256,"%02x %02x : system exclusive - id %d ; data follows\n",type|chan,val1,val1);
422 scope_print(report);
423 scope_print(getsysexbuf());
424 scope_print("\nf7 : end of sysex\n");
426 else{
427 scope_print(report);
434 //handle session events (LASH)
435 int ret;
436 char* session_string;
437 char* filename_string;
438 ret=backend_session_process();
439 while(ret != SESSION_NOMORE){
440 session_string=get_session_string();
441 filename_string = (char*)malloc(strlen(session_string)+16);
442 strcpy(filename_string,session_string);
443 strcat(filename_string,"/song.epi");
444 switch(ret){
445 case SESSION_SAVE: save(filename_string); break;
446 case SESSION_LOAD: load(filename_string); break;
447 case SESSION_QUIT: ui->main_window->hide(); break;
448 case SESSION_UNHANDLED: break;
450 free(session_string);
451 ret=backend_session_process();
455 if(is_backend_playing()){
456 fltk::repeat_timeout(0.005, playing_timeout_cb, NULL);
458 else{
459 fltk::repeat_timeout(0.1, playing_timeout_cb, NULL);
463 void start_monitor(){
464 fltk::add_timeout(0.1, playing_timeout_cb, NULL);
467 void press_play(){
468 if(!is_backend_playing()){
469 start_backend();
470 ui->play_button->label("@||");
471 //fltk::add_timeout(0.01, playing_timeout_cb, NULL);
473 else{
474 pause_backend();
475 all_notes_off();
476 ui->play_button->label("@>");
480 void press_stop(){
482 int left = get_loop_start();
483 if(get_play_position()==left || get_play_position()==0){
484 left=0;
487 pause_backend();
488 reset_backend(left);
489 all_notes_off();
491 ui->song_timeline->update(left);
492 ui->pattern_timeline->update(left);
494 ui->song_timeline->redraw();
495 ui->pattern_timeline->redraw();
497 ui->play_button->label("@>");
498 ui->play_button->redraw();
500 ui->metronome->update(left);
505 void set_quant(int q){
506 switch(q){
507 case 0:
508 ui->qbutton4->state(0);
509 ui->qbutton8->state(0);
510 ui->qbutton16->state(0);
511 ui->qbutton32->state(0);
512 ui->qbutton64->state(0);
513 ui->qbutton128->state(0);
514 ui->qbutton0->state(1);
515 ui->piano_roll->set_qtick(1);
516 break;
517 case 4:
518 ui->qbutton4->state(1);
519 ui->qbutton8->state(0);
520 ui->qbutton16->state(0);
521 ui->qbutton32->state(0);
522 ui->qbutton64->state(0);
523 ui->qbutton128->state(0);
524 ui->qbutton0->state(0);
525 ui->piano_roll->set_qtick(128);
526 break;
527 case 8:
528 ui->qbutton4->state(0);
529 ui->qbutton8->state(1);
530 ui->qbutton16->state(0);
531 ui->qbutton32->state(0);
532 ui->qbutton64->state(0);
533 ui->qbutton128->state(0);
534 ui->qbutton0->state(0);
535 ui->piano_roll->set_qtick(64);
536 break;
537 case 16:
538 ui->qbutton4->state(0);
539 ui->qbutton8->state(0);
540 ui->qbutton16->state(1);
541 ui->qbutton32->state(0);
542 ui->qbutton64->state(0);
543 ui->qbutton128->state(0);
544 ui->qbutton0->state(0);
545 ui->piano_roll->set_qtick(32);
546 break;
547 case 32:
548 ui->qbutton4->state(0);
549 ui->qbutton8->state(0);
550 ui->qbutton16->state(0);
551 ui->qbutton32->state(1);
552 ui->qbutton64->state(0);
553 ui->qbutton128->state(0);
554 ui->qbutton0->state(0);
555 ui->piano_roll->set_qtick(16);
556 break;
557 case 64:
558 ui->qbutton4->state(0);
559 ui->qbutton8->state(0);
560 ui->qbutton16->state(0);
561 ui->qbutton32->state(0);
562 ui->qbutton64->state(1);
563 ui->qbutton128->state(0);
564 ui->qbutton0->state(0);
565 ui->piano_roll->set_qtick(8);
566 break;
567 case 128:
568 ui->qbutton4->state(0);
569 ui->qbutton8->state(0);
570 ui->qbutton16->state(0);
571 ui->qbutton32->state(0);
572 ui->qbutton64->state(0);
573 ui->qbutton128->state(1);
574 ui->qbutton0->state(0);
575 ui->piano_roll->set_qtick(4);
576 break;
580 void set_songtool(int i){
581 switch(i){
582 case 0:
583 ui->edit_button->state(1);
584 ui->color_button->state(0);
585 ui->unclone_button->state(0);
586 ui->split_button->state(0);
587 ui->join_button->state(0);
588 ui->arranger->color_flag = 0;
589 ui->arranger->unclone_flag = 0;
590 ui->arranger->split_flag = 0;
591 ui->arranger->join_flag = 0;
592 break;
593 case 1:
594 ui->edit_button->state(0);
595 ui->color_button->state(1);
596 ui->unclone_button->state(0);
597 ui->split_button->state(0);
598 ui->join_button->state(0);
599 ui->arranger->color_flag = 1;
600 ui->arranger->unclone_flag = 0;
601 ui->arranger->split_flag = 0;
602 ui->arranger->join_flag = 0;
603 break;
604 case 2:
605 ui->edit_button->state(0);
606 ui->color_button->state(0);
607 ui->unclone_button->state(1);
608 ui->split_button->state(0);
609 ui->join_button->state(0);
610 ui->arranger->color_flag = 0;
611 ui->arranger->unclone_flag = 1;
612 ui->arranger->split_flag = 0;
613 ui->arranger->join_flag = 0;
614 break;
615 case 3:
616 ui->edit_button->state(0);
617 ui->color_button->state(0);
618 ui->unclone_button->state(0);
619 ui->split_button->state(1);
620 ui->join_button->state(0);
621 ui->arranger->color_flag = 0;
622 ui->arranger->unclone_flag = 0;
623 ui->arranger->split_flag = 1;
624 ui->arranger->join_flag = 0;
625 break;
626 case 4:
627 ui->edit_button->state(0);
628 ui->color_button->state(0);
629 ui->unclone_button->state(0);
630 ui->split_button->state(0);
631 ui->join_button->state(1);
632 ui->arranger->color_flag = 0;
633 ui->arranger->unclone_flag = 0;
634 ui->arranger->split_flag = 0;
635 ui->arranger->join_flag = 1;
636 break;
642 void set_beats_per_measure(int n){
643 config.beats_per_measure = n;
644 ui->metronome->set_N(n);
645 ui->piano_roll->redraw();
646 ui->arranger->redraw();
647 ui->arranger->q_tick = n*TICKS_PER_BEAT;
648 ui->song_timeline->redraw();
649 ui->pattern_timeline->redraw();
652 void set_measures_per_phrase(int n){
653 config.measures_per_phrase = n;
654 ui->piano_roll->redraw();
655 ui->arranger->redraw();
656 ui->song_timeline->redraw();
657 ui->pattern_timeline->redraw();
660 void set_beats_per_minute(int n){
661 config.beats_per_minute = n;
662 set_bpm(n);
665 void set_measures_until_record(int n){
666 config.measures_until_record = n;
669 void set_alwayscopy(int v){
670 config.alwayscopy = v;
673 void set_autotrackname(int v){
674 config.autotrackname = v;
677 void set_passthru(int v){
678 config.passthru = v;
679 backend_set_passthru(v);
682 void set_playinsert(int v){
683 config.playinsert = v;
686 void set_recordonchan(int v){
687 config.recordonchan = v;
690 void set_playmove(int v){
691 config.playmove = v;
694 void set_follow(int v){
695 config.follow = v;
698 void set_recordmode(int n){
699 config.recordmode = n;
702 void set_robmode(int n){
703 config.robmode = n;
706 void set_defaultvelocity(int n){
707 config.defaultvelocity = n;
711 int scopeon=0;
712 void turnonscope(){
713 scopeon=1;
716 void turnoffscope(){
717 scopeon=0;
718 fltk::TextBuffer* ptr = ui->scope->buffer();
719 ptr->remove(0,ptr->length());
720 // ui->redraw();
723 void scope_print(const char* text){
724 if(scopeon){
725 ui->scope->append(text);
726 int N = ui->scope->buffer()->length();
727 ui->scope->scroll(N,0);
733 void show_song_edit(){
734 ui->pattern_edit->hide();
735 ui->pattern_buttons->hide();
736 ui->song_edit->activate();
737 ui->song_edit->show();
738 ui->song_edit->take_focus();
739 ui->song_buttons->show();
742 void show_pattern_edit(){
743 ui->song_edit->hide();
744 ui->song_edit->deactivate();
745 ui->song_buttons->hide();
746 ui->pattern_edit->take_focus();
747 ui->pattern_edit->show();
748 ui->pattern_buttons->show();
752 static int tool = 0;
753 //switch between normal, note off, portamento, and aftertouch
754 void toggle_tool(){
755 switch(tool){
756 case 0:
757 tool=1;
758 ui->tool_button->copy_label("80");
759 ui->tool_button->state(1);
760 break;
761 case 1:
762 tool=2;
763 ui->tool_button->copy_label("A0");
764 break;
765 case 2:
766 tool=3;
767 ui->tool_button->copy_label("po");
768 break;
769 case 3:
770 tool=0;
771 ui->tool_button->copy_label("tool");
772 ui->tool_button->state(0);
773 break;
779 void reset_song(){
780 clear();
782 track* t;
783 for(int i=0; i<16; i++){
784 t = new track();
785 t->head->track = i;
786 t->chan = i;
787 add_track(t);
790 set_rec_track(0);
791 ui->track_info->set_rec(0);
792 ui->track_info->update();
793 ui->action_window->hide();
798 void add_track(track* t){
799 tracks.push_back(t);
800 ui->track_info->add_track();
803 void remove_track(int n){
809 void init_gui(){
811 ui->arranger->layout();
812 ui->song_vscroll->slider_size(60);
813 ui->song_vscroll->value(0);
815 ui->pattern_timeline->edit_flag = 1;
816 ui->pattern_timeline->zoom = 15;
817 ui->pattern_vscroll->minimum(12*75);
818 ui->pattern_vscroll->maximum(0);
819 ui->pattern_vscroll->value(300);
820 ui->pattern_vscroll->slider_size(50);
821 ui->pattern_hscroll->value(0);