Fixed bug in lash support.
[epichord.git] / src / uihelper.cpp
blob4056c2b6fab8c051c48219f83c5395ffb531b1b8
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 config.beats_per_measure = 4;
56 config.measures_per_phrase = 4;
57 config.measures_until_record = 1;
58 config.alwayscopy = 0;
59 config.autotrackname = 0;
60 config.passthru = 1;
61 config.playinsert = 1;
62 config.recordonchan = 0;
63 config.playmove = 1;
64 config.follow = 1;
65 config.recordmode = 0;
66 config.robmode = 0;
67 config.defaultvelocity = 96;
68 config.trackinit = 1;
70 //linux dependent
71 char* homepath = getenv("HOME");
72 asprintf(&config_filename,"%s/"CONFIG_FILENAME,homepath);
74 fstream f;
75 f.open(config_filename,fstream::in);
76 if(!f.is_open()){
77 printf("load_config: Unable to open config file for reading.\n");
79 load_default_keymap();
80 update_config_gui();
81 return;
84 config.beats_per_measure = 4;
85 config.measures_per_phrase = 4;
87 std::string word;
89 while(!f.eof()){
90 word = "";
91 f >> word;
92 if(word == "leadin"){f>>config.measures_until_record;}
93 else if(word == "alwayscopy"){f>>config.alwayscopy;}
94 else if(word == "autotrackname"){f>>config.autotrackname;}
95 else if(word == "passthru"){f>>config.passthru;}
96 else if(word == "playinsert"){f>>config.playinsert;}
97 else if(word == "recordonchan"){f>>config.recordonchan;}
98 else if(word == "playmove"){f>>config.playmove;}
99 else if(word == "follow"){f>>config.follow;}
100 else if(word == "recordmode"){f>>config.recordmode;}
101 else if(word == "robmode"){f>>config.robmode;}
102 else if(word == "keymap"){load_keymap(f);}
103 else if(word == "defaultvelocity"){f>>config.defaultvelocity;}
104 else if(word == "trackinit"){f>>config.trackinit;}
105 else{
106 f.ignore(std::numeric_limits<streamsize>::max(),'\n');
109 update_config_gui();
110 f.close();
113 void save_config(){
114 fstream f;
115 f.open(config_filename,fstream::out);
116 if(!f.is_open()){
117 printf("save_config: Unable to open config file %s for saving.\n", config_filename);
118 return;
121 f << "leadin " << config.measures_until_record << endl;
122 f << "alwayscopy " << config.alwayscopy << endl;
123 f << "autotrackname " << config.autotrackname << endl;
124 f << "passthru " << config.passthru << endl;
125 f << "playinsert " << config.playinsert << endl;
126 f << "recordonchan " << config.recordonchan << endl;
127 f << "playmove " << config.playmove << endl;
128 f << "follow " << config.follow << endl;
129 //f << "quantizedur " << config.quantizedur << endl;
130 f << "recordmode " << config.recordmode << endl;
131 f << "robmode " << config.robmode << endl;
132 f << "defaultvelocity " << config.defaultvelocity << endl;
133 f << "trackinit " << config.trackinit << endl;
134 f << endl;
135 save_keymap(f);
136 f.close();
139 void update_config_gui(){
140 ui->beats_per_measure->value(config.beats_per_measure);
141 ui->measures_per_phrase->value(config.measures_per_phrase);
142 ui->measures_until_record->value(config.measures_until_record);
144 ui->bpm_wheel->value(config.beats_per_minute);
145 ui->bpm_output->value(config.beats_per_minute);
147 ui->check_alwayscopy->state(config.alwayscopy);
148 ui->check_autotrackname->state(config.autotrackname);
149 ui->check_passthru->state(config.passthru);
150 ui->check_playinsert->state(config.playinsert);
151 ui->check_recordonchan->state(config.recordonchan);
152 ui->check_playmove->state(config.playmove);
153 ui->check_follow->state(config.follow);
155 ui->menu_recordmode->value(config.recordmode);
156 ui->menu_rob->value(config.robmode);
158 ui->default_velocity->value(config.defaultvelocity);
160 ui->check_trackinit->value(config.trackinit);
162 ui->config_window->redraw();
167 seqpat* rob_check(seqpat* s){
168 seqpat* prev = s->prev;
169 Command* c;
170 if(config.robmode == 0){
171 return NULL;
173 else if(config.robmode == 1 || prev == NULL){
174 int pos = get_play_position();
175 int M = config.measures_per_phrase;
176 if(M!=0){
177 M = M*config.beats_per_measure*128;
179 else{
180 M = 4*config.beats_per_measure*128;
182 int P1 = pos/M*M;
183 int P2 = P1 + M;
184 int T = P1;
185 int R = s->tick+s->dur;
186 if(R > P1){
187 T = R;
189 int W = P2 - T;
190 if(s->next){
191 int L = s->next->tick;
192 if(L < P2){
193 W = L - T;
196 c = new CreateSeqpatBlank(s->track,T,W);
197 set_undo(c);
198 undo_push(1);
199 return s->next;
201 else if(config.robmode == 2){
202 int pos = get_play_position();
203 int M = config.measures_per_phrase;
204 if(M!=0){
205 M = M*config.beats_per_measure*128;
207 else{
208 M = 4*config.beats_per_measure*128;
210 int P = pos/M*M + M;//tick at next phrase boundary
211 int W = P - s->tick;
212 if(s->next){
213 int W2 = s->next->tick - s->tick;
214 if(W2 < W){
215 W=W2;
218 c = new ResizeSeqpat(s,W);
219 set_undo(c);
220 undo_push(1);
221 return prev->next;
227 int last_pos=0;
228 void playing_timeout_cb(void* v){
229 int pos = get_play_position();
231 if(pos < last_pos){
232 reset_record_flags();
234 last_pos = pos;
236 if(config.follow){
237 ui->arranger->update(pos);
238 ui->piano_roll->update(pos);
240 ui->song_timeline->update(pos);
241 ui->pattern_timeline->update(pos);
242 ui->metronome->update(pos);
244 //check for midi input
245 int tick;
246 int chan;
247 int type;
248 int val1;
249 int val2;
251 track* t = tracks[get_rec_track()];
252 Command* c;
253 seqpat* s;
254 pattern* p;
256 char report[256];
258 while(recv_midi(&chan,&tick,&type,&val1,&val2)){
260 if(config.recordonchan){
261 for(int i=0; i<tracks.size(); i++){
262 if(tracks[i]->chan == chan){
263 t = tracks[i];
268 switch(type){
269 case 0x80://note off
270 snprintf(report,256,"%02x %02x %02x : note off - ch %d note %d vel %d\n",type|chan,val1,val2,chan,val1,val2);
271 scope_print(report);
273 if(!is_backend_recording())
274 break;
276 s = tfind<seqpat>(t->head,tick);
277 if(s->tick+s->dur < tick){
278 s = rob_check(s);
279 if(!s){continue;}
282 //if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
283 s->record_check(config.recordmode);
284 p = s->p;
285 c=new CreateNoteOff(p,val1,val2,tick-s->tick);
286 set_undo(c);
287 undo_push(1);
288 if(ui->piano_roll->visible()){
289 ui->piano_roll->redraw();
290 ui->event_edit->redraw();
291 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[1]=1;}
292 ui->event_menu->redraw();
294 if(ui->arranger->visible())
295 ui->arranger->redraw();
296 break;
297 case 0x90://note on
298 snprintf(report,256,"%02x %02x %02x : note on - ch %d note %d vel %d\n",type|chan,val1,val2,chan,val1,val2);
299 scope_print(report);
301 if(!is_backend_recording())
302 break;
304 s = tfind<seqpat>(t->head,tick);
305 if(s->tick+s->dur < tick){
306 s = rob_check(s);
307 if(!s){continue;}
310 // if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
311 s->record_check(config.recordmode);
312 p = s->p;
313 c=new CreateNoteOn(p,val1,val2,tick-s->tick,16);
314 set_undo(c);
315 undo_push(1);
316 if(ui->piano_roll->visible()){
317 ui->piano_roll->redraw();
318 ui->event_edit->redraw();
319 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[0]=1;}
320 ui->event_menu->redraw();
322 if(ui->arranger->visible())
323 ui->arranger->redraw();
324 break;
325 case 0xa0://aftertouch
326 case 0xb0://controller
327 case 0xc0://program change
328 case 0xd0://channel pressure
329 case 0xe0://pitch wheel
331 s = tfind<seqpat>(t->head,tick);
332 if(s->tick+s->dur < tick){
333 s = rob_check(s);
334 if(!s){continue;}
337 switch(type){
338 case 0xa0:
339 snprintf(report,256,"%02x %02x %02x : aftertouch - ch %d note %d %d\n",type|chan,val1,val2,chan,val1,val2);
340 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[2]=1;}
341 break;
342 case 0xb0:
343 snprintf(report,256,"%02x %02x %02x : controller change - ch %d cntr %d val %d\n",type|chan,val1,val2,chan,val1,val2);
344 if(ui->event_edit->cur_seqpat == s){
345 ui->event_edit->has[val1+6]=1;
347 break;
348 case 0xc0:
349 snprintf(report,256,"%02x %02x : program change - ch %d pgrm %d \n",type|chan,val1,chan,val1);
350 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[3]=1;}
351 break;
352 case 0xd0:
353 snprintf(report,256,"%02x %02x : channel pressure - ch %d val %d \n",type|chan,val1,chan,val1);
354 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[4]=1;}
355 break;
356 case 0xe0:
357 snprintf(report,256,"%02x %02x %02x : pitch wheel - ch %d val %d \n",type|chan,val1,val2,chan,(val2<<7)|val1);
358 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[5]=1;}
359 break;
361 scope_print(report);
363 if(!is_backend_recording())
364 break;
366 // if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
367 s->record_check(config.recordmode);
368 p = s->p;
369 c=new CreateEvent(p,type,tick,val1,val2);
370 set_undo(c);
371 undo_push(1);
372 if(ui->piano_roll->visible()){
373 ui->piano_roll->redraw();
374 ui->event_edit->redraw();
375 ui->event_menu->redraw();
377 if(ui->arranger->visible())
378 ui->arranger->redraw();
379 break;
380 case 0xf0:
381 switch(chan){
382 case 1://undefined (reserved) system common message
383 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
384 break;
385 case 2://song position pointer
386 snprintf(report,256,"%02x %02x %02x : song position - %d \n",type|chan,val1,val2,(val2<<7)|val1);
387 break;
388 case 3://song select
389 snprintf(report,256,"%02x %02x : song select - %d \n",type|chan,val1,val1);
390 break;
391 case 4://undefined (reserved) system common message
392 case 5://undefined (reserved) system common message
393 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
394 break;
395 case 6://tune request
396 snprintf(report,256,"%02x : tune request\n",type|chan);
397 break;
398 case 7://end of exclusive
399 snprintf(report,256,"%02x : end of exclusive\n",type|chan);
400 break;
401 case 8://timing clock
402 snprintf(report,256,"%02x : timing clock\n",type|chan);
403 break;
404 case 9://undefined (reserved) system common message
405 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
406 break;
407 case 10://start
408 snprintf(report,256,"%02x : start\n",type|chan);
409 break;
410 case 11://continue
411 snprintf(report,256,"%02x : continue\n",type|chan);
412 break;
413 case 12://stop
414 snprintf(report,256,"%02x : stop\n",type|chan);
415 break;
416 case 13://undefined
417 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
418 break;
419 case 14://active sensing
420 snprintf(report,256,"%02x : active sensing\n",type|chan);
421 break;
422 case 15://reset
423 snprintf(report,256,"%02x : reset\n",type|chan);
424 break;
426 if(chan==0){
427 snprintf(report,256,"%02x %02x : system exclusive - id %d ; data follows\n",type|chan,val1,val1);
428 scope_print(report);
429 scope_print(getsysexbuf());
430 scope_print("\nf7 : end of sysex\n");
432 else{
433 scope_print(report);
440 //handle session events (LASH)
441 int ret;
442 char* session_string;
443 char* filename_string;
444 ret=backend_session_process();
445 while(ret != SESSION_NOMORE){
446 session_string=get_session_string();
447 filename_string = (char*)malloc(strlen(session_string)+16);
448 strcpy(filename_string,session_string);
449 strcat(filename_string,"/song.epi");
450 switch(ret){
451 case SESSION_SAVE: save(filename_string); break;
452 case SESSION_LOAD: load(filename_string); break;
453 case SESSION_QUIT: shutdown_gui(); break;
454 case SESSION_UNHANDLED: break;
456 free(session_string);
457 ret=backend_session_process();
461 if(is_backend_playing()){
462 fltk::repeat_timeout(0.005, playing_timeout_cb, NULL);
464 else{
465 fltk::repeat_timeout(0.1, playing_timeout_cb, NULL);
469 void start_monitor(){
470 fltk::add_timeout(0.1, playing_timeout_cb, NULL);
473 void press_play(){
474 if(!is_backend_playing()){
475 start_backend();
476 ui->play_button->label("@||");
477 //fltk::add_timeout(0.01, playing_timeout_cb, NULL);
479 else{
480 pause_backend();
481 all_notes_off();
482 ui->play_button->label("@>");
486 void press_stop(){
488 int left = get_loop_start();
489 if(get_play_position()==left || get_play_position()==0){
490 left=0;
493 pause_backend();
494 reset_backend(left);
495 all_notes_off();
497 ui->song_timeline->update(left);
498 ui->pattern_timeline->update(left);
500 ui->song_timeline->redraw();
501 ui->pattern_timeline->redraw();
503 ui->play_button->label("@>");
504 ui->play_button->redraw();
506 ui->metronome->update(left);
511 void set_quant(int q){
512 switch(q){
513 case 0:
514 ui->qbutton4->state(0);
515 ui->qbutton8->state(0);
516 ui->qbutton16->state(0);
517 ui->qbutton32->state(0);
518 ui->qbutton64->state(0);
519 ui->qbutton128->state(0);
520 ui->qbutton0->state(1);
521 ui->piano_roll->set_qtick(1);
522 break;
523 case 4:
524 ui->qbutton4->state(1);
525 ui->qbutton8->state(0);
526 ui->qbutton16->state(0);
527 ui->qbutton32->state(0);
528 ui->qbutton64->state(0);
529 ui->qbutton128->state(0);
530 ui->qbutton0->state(0);
531 ui->piano_roll->set_qtick(128);
532 break;
533 case 8:
534 ui->qbutton4->state(0);
535 ui->qbutton8->state(1);
536 ui->qbutton16->state(0);
537 ui->qbutton32->state(0);
538 ui->qbutton64->state(0);
539 ui->qbutton128->state(0);
540 ui->qbutton0->state(0);
541 ui->piano_roll->set_qtick(64);
542 break;
543 case 16:
544 ui->qbutton4->state(0);
545 ui->qbutton8->state(0);
546 ui->qbutton16->state(1);
547 ui->qbutton32->state(0);
548 ui->qbutton64->state(0);
549 ui->qbutton128->state(0);
550 ui->qbutton0->state(0);
551 ui->piano_roll->set_qtick(32);
552 break;
553 case 32:
554 ui->qbutton4->state(0);
555 ui->qbutton8->state(0);
556 ui->qbutton16->state(0);
557 ui->qbutton32->state(1);
558 ui->qbutton64->state(0);
559 ui->qbutton128->state(0);
560 ui->qbutton0->state(0);
561 ui->piano_roll->set_qtick(16);
562 break;
563 case 64:
564 ui->qbutton4->state(0);
565 ui->qbutton8->state(0);
566 ui->qbutton16->state(0);
567 ui->qbutton32->state(0);
568 ui->qbutton64->state(1);
569 ui->qbutton128->state(0);
570 ui->qbutton0->state(0);
571 ui->piano_roll->set_qtick(8);
572 break;
573 case 128:
574 ui->qbutton4->state(0);
575 ui->qbutton8->state(0);
576 ui->qbutton16->state(0);
577 ui->qbutton32->state(0);
578 ui->qbutton64->state(0);
579 ui->qbutton128->state(1);
580 ui->qbutton0->state(0);
581 ui->piano_roll->set_qtick(4);
582 break;
586 void set_songtool(int i){
587 switch(i){
588 case 0:
589 ui->edit_button->state(1);
590 ui->color_button->state(0);
591 ui->unclone_button->state(0);
592 ui->split_button->state(0);
593 ui->join_button->state(0);
594 ui->arranger->color_flag = 0;
595 ui->arranger->unclone_flag = 0;
596 ui->arranger->split_flag = 0;
597 ui->arranger->join_flag = 0;
598 break;
599 case 1:
600 ui->edit_button->state(0);
601 ui->color_button->state(1);
602 ui->unclone_button->state(0);
603 ui->split_button->state(0);
604 ui->join_button->state(0);
605 ui->arranger->color_flag = 1;
606 ui->arranger->unclone_flag = 0;
607 ui->arranger->split_flag = 0;
608 ui->arranger->join_flag = 0;
609 break;
610 case 2:
611 ui->edit_button->state(0);
612 ui->color_button->state(0);
613 ui->unclone_button->state(1);
614 ui->split_button->state(0);
615 ui->join_button->state(0);
616 ui->arranger->color_flag = 0;
617 ui->arranger->unclone_flag = 1;
618 ui->arranger->split_flag = 0;
619 ui->arranger->join_flag = 0;
620 break;
621 case 3:
622 ui->edit_button->state(0);
623 ui->color_button->state(0);
624 ui->unclone_button->state(0);
625 ui->split_button->state(1);
626 ui->join_button->state(0);
627 ui->arranger->color_flag = 0;
628 ui->arranger->unclone_flag = 0;
629 ui->arranger->split_flag = 1;
630 ui->arranger->join_flag = 0;
631 break;
632 case 4:
633 ui->edit_button->state(0);
634 ui->color_button->state(0);
635 ui->unclone_button->state(0);
636 ui->split_button->state(0);
637 ui->join_button->state(1);
638 ui->arranger->color_flag = 0;
639 ui->arranger->unclone_flag = 0;
640 ui->arranger->split_flag = 0;
641 ui->arranger->join_flag = 1;
642 break;
648 void set_beats_per_measure(int n){
649 config.beats_per_measure = n;
650 ui->metronome->set_N(n);
651 ui->piano_roll->redraw();
652 ui->arranger->redraw();
653 ui->arranger->q_tick = n*TICKS_PER_BEAT;
654 ui->song_timeline->redraw();
655 ui->pattern_timeline->redraw();
658 void set_measures_per_phrase(int n){
659 config.measures_per_phrase = n;
660 ui->piano_roll->redraw();
661 ui->arranger->redraw();
662 ui->song_timeline->redraw();
663 ui->pattern_timeline->redraw();
666 void set_beats_per_minute(int n){
667 config.beats_per_minute = n;
668 set_bpm(n);
671 void set_measures_until_record(int n){
672 config.measures_until_record = n;
675 void set_alwayscopy(int v){
676 config.alwayscopy = v;
679 void set_autotrackname(int v){
680 config.autotrackname = v;
683 void set_passthru(int v){
684 config.passthru = v;
685 backend_set_passthru(v);
688 void set_playinsert(int v){
689 config.playinsert = v;
692 void set_recordonchan(int v){
693 config.recordonchan = v;
696 void set_playmove(int v){
697 config.playmove = v;
700 void set_follow(int v){
701 config.follow = v;
704 void set_recordmode(int n){
705 config.recordmode = n;
708 void set_robmode(int n){
709 config.robmode = n;
712 void set_defaultvelocity(int n){
713 config.defaultvelocity = n;
716 void set_trackinit(int n){
717 config.trackinit = n;
718 backend_set_trackinit(n);
722 int scopeon=0;
723 void turnonscope(){
724 scopeon=1;
727 void turnoffscope(){
728 scopeon=0;
729 fltk::TextBuffer* ptr = ui->scope->buffer();
730 ptr->remove(0,ptr->length());
731 // ui->redraw();
734 void scope_print(const char* text){
735 if(scopeon){
736 ui->scope->append(text);
737 int N = ui->scope->buffer()->length();
738 ui->scope->scroll(N,0);
744 void show_song_edit(){
745 ui->pattern_edit->hide();
746 ui->pattern_buttons->hide();
747 ui->song_edit->activate();
748 ui->song_edit->show();
749 ui->song_edit->take_focus();
750 ui->song_buttons->show();
753 void show_pattern_edit(){
754 ui->song_edit->hide();
755 ui->song_edit->deactivate();
756 ui->song_buttons->hide();
757 ui->pattern_edit->take_focus();
758 ui->pattern_edit->show();
759 ui->pattern_buttons->show();
763 static int tool = 0;
764 //switch between normal, note off, portamento, and aftertouch
765 void toggle_tool(){
766 switch(tool){
767 case 0:
768 tool=1;
769 ui->tool_button->copy_label("80");
770 ui->tool_button->state(1);
771 break;
772 case 1:
773 tool=2;
774 ui->tool_button->copy_label("A0");
775 break;
776 case 2:
777 tool=3;
778 ui->tool_button->copy_label("po");
779 break;
780 case 3:
781 tool=0;
782 ui->tool_button->copy_label("tool");
783 ui->tool_button->state(0);
784 break;
790 void reset_song(){
791 clear();
793 track* t;
794 for(int i=0; i<16; i++){
795 t = new track();
796 t->head->track = i;
797 t->chan = i;
798 add_track(t);
801 set_rec_track(0);
802 ui->track_info->set_rec(0);
803 ui->track_info->update();
804 ui->action_window->hide();
809 void add_track(track* t){
810 tracks.push_back(t);
811 ui->track_info->add_track();
814 void remove_track(int n){
820 void init_gui(){
822 ui->arranger->layout();
823 ui->song_vscroll->slider_size(60);
824 ui->song_vscroll->value(0);
826 ui->pattern_timeline->edit_flag = 1;
827 ui->pattern_timeline->zoom = 15;
828 ui->pattern_vscroll->minimum(12*75);
829 ui->pattern_vscroll->maximum(0);
830 ui->pattern_vscroll->value(300);
831 ui->pattern_vscroll->slider_size(50);
832 ui->pattern_hscroll->value(0);
835 void shutdown_gui(){
836 ui->main_window->hide();
837 ui->config_window->hide();
838 ui->help_window->hide();
839 ui->action_window->hide();
840 ui->scope_window->hide();