Fixed bug that crashed on saving after importing.
[epichord.git] / src / uihelper.cpp
blob9b9ccb43064c64d19370e8b484bed55ef7fe594d
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 <fltk/run.h>
31 #include "seq.h"
32 #include "ui.h"
33 #include "backend.h"
35 #include "uihelper.h"
38 #define CONFIG_FILENAME ".epichordrc"
40 extern UI* ui;
41 extern std::vector<track*> tracks;
43 struct conf config;
45 using namespace std;
47 char* config_filename;
51 void load_config(){
53 //linux dependent
54 char* homepath = getenv("HOME");
55 asprintf(&config_filename,"%s/"CONFIG_FILENAME,homepath);
57 fstream f;
58 f.open(config_filename,fstream::in);
59 if(!f.is_open()){
60 printf("load_config: Unable to open config file for reading.\n");
61 config.beats_per_measure = 4;
62 config.measures_per_phrase = 4;
63 config.measures_until_record = 1;
64 config.alwayscopy = 0;
65 config.autotrackname = 0;
66 config.passthru = 1;
67 config.playinsert = 1;
68 config.recordonchan = 0;
69 config.playmove = 1;
70 config.follow = 1;
71 config.recordmode = 0;
72 config.robmode = 0;
73 config.defaultvelocity = 96;
75 load_default_keymap();
76 update_config_gui();
77 return;
80 config.beats_per_measure = 4;
81 config.measures_per_phrase = 4;
83 std::string word;
85 while(!f.eof()){
86 word = "";
87 f >> word;
88 if(word == "leadin"){f>>config.measures_until_record;}
89 else if(word == "alwayscopy"){f>>config.alwayscopy;}
90 else if(word == "autotrackname"){f>>config.autotrackname;}
91 else if(word == "passthru"){f>>config.passthru;}
92 else if(word == "playinsert"){f>>config.playinsert;}
93 else if(word == "recordonchan"){f>>config.recordonchan;}
94 else if(word == "playmove"){f>>config.playmove;}
95 else if(word == "follow"){f>>config.follow;}
96 else if(word == "recordmode"){f>>config.recordmode;}
97 else if(word == "robmode"){f>>config.robmode;}
98 else if(word == "keymap"){load_keymap(f);}
99 else if(word == "defaultvelocity"){f>>config.defaultvelocity;}
100 else{
101 f.ignore(std::numeric_limits<streamsize>::max(),'\n');
104 update_config_gui();
105 f.close();
108 void save_config(){
109 fstream f;
110 f.open(config_filename,fstream::out);
111 if(!f.is_open()){
112 printf("save_config: Unable to open config file %s for saving.\n", config_filename);
113 return;
116 f << "leadin " << config.measures_until_record << endl;
117 f << "alwayscopy " << config.alwayscopy << endl;
118 f << "autotrackname " << config.autotrackname << endl;
119 f << "passthru " << config.passthru << endl;
120 f << "playinsert " << config.playinsert << endl;
121 f << "recordonchan " << config.recordonchan << endl;
122 f << "playmove " << config.playmove << endl;
123 f << "follow " << config.follow << endl;
124 //f << "quantizedur " << config.quantizedur << endl;
125 f << "recordmode " << config.recordmode << endl;
126 f << "robmode " << config.robmode << endl;
127 f << "defaultvelocity " << config.defaultvelocity << endl;
128 f << endl;
129 save_keymap(f);
130 f.close();
133 void update_config_gui(){
134 ui->beats_per_measure->value(config.beats_per_measure);
135 ui->measures_per_phrase->value(config.measures_per_phrase);
136 ui->measures_until_record->value(config.measures_until_record);
138 ui->bpm_wheel->value(config.beats_per_minute);
139 ui->bpm_output->value(config.beats_per_minute);
141 ui->check_alwayscopy->state(config.alwayscopy);
142 ui->check_autotrackname->state(config.autotrackname);
143 ui->check_passthru->state(config.passthru);
144 ui->check_playinsert->state(config.playinsert);
145 ui->check_recordonchan->state(config.recordonchan);
146 ui->check_playmove->state(config.playmove);
147 ui->check_follow->state(config.follow);
149 ui->menu_recordmode->value(config.recordmode);
150 ui->menu_rob->value(config.robmode);
152 ui->default_velocity->value(config.defaultvelocity);
154 ui->config_window->redraw();
159 seqpat* rob_check(seqpat* s){
160 seqpat* prev = s->prev;
161 Command* c;
162 if(config.robmode == 0){
163 return NULL;
165 else if(config.robmode == 1 || prev == NULL){
166 int pos = get_play_position();
167 int M = config.measures_per_phrase*config.beats_per_measure*128;
168 int P1 = pos/M*M;
169 int P2 = P1 + M;
170 int T = P1;
171 int R = s->tick+s->dur;
172 if(R > P1){
173 T = R;
175 int W = P2 - T;
176 if(s->next){
177 int L = s->next->tick;
178 if(L < P2){
179 W = L - T;
182 c = new CreateSeqpatBlank(s->track,T,W);
183 set_undo(c);
184 undo_push(1);
185 return s->next;
187 else if(config.robmode == 2){
188 int pos = get_play_position();
189 int M = config.measures_per_phrase*config.beats_per_measure*128;
190 int P = pos/M*M + M;//tick at next phrase boundary
191 int W = P - s->tick;
192 if(s->next){
193 int W2 = s->next->tick - s->tick;
194 if(W2 < W){
195 W=W2;
198 c = new ResizeSeqpat(s,W);
199 set_undo(c);
200 undo_push(1);
201 return prev->next;
207 int last_pos=0;
208 void playing_timeout_cb(void* v){
209 int pos = get_play_position();
211 if(pos < last_pos){
212 reset_record_flags();
214 last_pos = pos;
216 if(config.follow){
217 ui->arranger->update(pos);
218 ui->piano_roll->update(pos);
220 ui->song_timeline->update(pos);
221 ui->pattern_timeline->update(pos);
222 ui->metronome->update(pos);
224 //check for midi input
225 int tick;
226 int chan;
227 int type;
228 int val1;
229 int val2;
231 track* t = tracks[get_rec_track()];
232 Command* c;
233 seqpat* s;
234 pattern* p;
236 char report[256];
238 while(recv_midi(&chan,&tick,&type,&val1,&val2)){
240 if(config.recordonchan){
241 for(int i=0; i<tracks.size(); i++){
242 if(tracks[i]->chan == chan){
243 t = tracks[i];
248 switch(type){
249 case 0x80://note off
250 snprintf(report,256,"%02x %02x %02x : note off - ch %d note %d vel %d\n",type|chan,val1,val2,chan,val1,val2);
251 scope_print(report);
253 if(!is_backend_recording())
254 break;
256 s = tfind<seqpat>(t->head,tick);
257 if(s->tick+s->dur < tick){
258 s = rob_check(s);
259 if(!s){continue;}
262 //if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
263 s->record_check(config.recordmode);
264 p = s->p;
265 c=new CreateNoteOff(p,val1,val2,tick-s->tick);
266 set_undo(c);
267 undo_push(1);
268 if(ui->piano_roll->visible()){
269 ui->piano_roll->redraw();
270 ui->event_edit->redraw();
271 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[1]=1;}
272 ui->event_menu->redraw();
274 if(ui->arranger->visible())
275 ui->arranger->redraw();
276 break;
277 case 0x90://note on
278 snprintf(report,256,"%02x %02x %02x : note on - ch %d note %d vel %d\n",type|chan,val1,val2,chan,val1,val2);
279 scope_print(report);
281 if(!is_backend_recording())
282 break;
284 s = tfind<seqpat>(t->head,tick);
285 if(s->tick+s->dur < tick){
286 s = rob_check(s);
287 if(!s){continue;}
290 // if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
291 s->record_check(config.recordmode);
292 p = s->p;
293 c=new CreateNoteOn(p,val1,val2,tick-s->tick,16);
294 set_undo(c);
295 undo_push(1);
296 if(ui->piano_roll->visible()){
297 ui->piano_roll->redraw();
298 ui->event_edit->redraw();
299 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[0]=1;}
300 ui->event_menu->redraw();
302 if(ui->arranger->visible())
303 ui->arranger->redraw();
304 break;
305 case 0xa0://aftertouch
306 case 0xb0://controller
307 case 0xc0://program change
308 case 0xd0://channel pressure
309 case 0xe0://pitch wheel
311 s = tfind<seqpat>(t->head,tick);
312 if(s->tick+s->dur < tick){
313 s = rob_check(s);
314 if(!s){continue;}
317 switch(type){
318 case 0xa0:
319 snprintf(report,256,"%02x %02x %02x : aftertouch - ch %d note %d %d\n",type|chan,val1,val2,chan,val1,val2);
320 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[2]=1;}
321 break;
322 case 0xb0:
323 snprintf(report,256,"%02x %02x %02x : controller change - ch %d cntr %d val %d\n",type|chan,val1,val2,chan,val1,val2);
324 if(ui->event_edit->cur_seqpat == s){
325 ui->event_edit->has[val1+6]=1;
327 break;
328 case 0xc0:
329 snprintf(report,256,"%02x %02x : program change - ch %d pgrm %d \n",type|chan,val1,chan,val1);
330 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[3]=1;}
331 break;
332 case 0xd0:
333 snprintf(report,256,"%02x %02x : channel pressure - ch %d val %d \n",type|chan,val1,chan,val1);
334 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[4]=1;}
335 break;
336 case 0xe0:
337 snprintf(report,256,"%02x %02x %02x : pitch wheel - ch %d val %d \n",type|chan,val1,val2,chan,(val2<<7)|val1);
338 if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[5]=1;}
339 break;
341 scope_print(report);
343 if(!is_backend_recording())
344 break;
346 // if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
347 s->record_check(config.recordmode);
348 p = s->p;
349 c=new CreateEvent(p,type,tick,val1,val2);
350 set_undo(c);
351 undo_push(1);
352 if(ui->piano_roll->visible()){
353 ui->piano_roll->redraw();
354 ui->event_edit->redraw();
355 ui->event_menu->redraw();
357 if(ui->arranger->visible())
358 ui->arranger->redraw();
359 break;
360 case 0xf0:
361 switch(chan){
362 case 1://undefined (reserved) system common message
363 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
364 break;
365 case 2://song position pointer
366 snprintf(report,256,"%02x %02x %02x : song position - %d \n",type|chan,val1,val2,(val2<<7)|val1);
367 break;
368 case 3://song select
369 snprintf(report,256,"%02x %02x : song select - %d \n",type|chan,val1,val1);
370 break;
371 case 4://undefined (reserved) system common message
372 case 5://undefined (reserved) system common message
373 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
374 break;
375 case 6://tune request
376 snprintf(report,256,"%02x : tune request\n",type|chan);
377 break;
378 case 7://end of exclusive
379 snprintf(report,256,"%02x : end of exclusive\n",type|chan);
380 break;
381 case 8://timing clock
382 snprintf(report,256,"%02x : timing clock\n",type|chan);
383 break;
384 case 9://undefined (reserved) system common message
385 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
386 break;
387 case 10://start
388 snprintf(report,256,"%02x : start\n",type|chan);
389 break;
390 case 11://continue
391 snprintf(report,256,"%02x : continue\n",type|chan);
392 break;
393 case 12://stop
394 snprintf(report,256,"%02x : stop\n",type|chan);
395 break;
396 case 13://undefined
397 snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
398 break;
399 case 14://active sensing
400 snprintf(report,256,"%02x : active sensing\n",type|chan);
401 break;
402 case 15://reset
403 snprintf(report,256,"%02x : reset\n",type|chan);
404 break;
406 if(chan==0){
407 snprintf(report,256,"%02x %02x : system exclusive - id %d ; data follows\n",type|chan,val1,val1);
408 scope_print(report);
409 scope_print(getsysexbuf());
410 scope_print("\nf7 : end of sysex\n");
412 else{
413 scope_print(report);
420 //handle session events (LASH)
421 int ret;
422 char* session_string;
423 char* filename_string;
424 ret=backend_session_process();
425 while(ret != SESSION_NOMORE){
426 session_string=get_session_string();
427 filename_string = (char*)malloc(strlen(session_string)+16);
428 strcpy(filename_string,session_string);
429 strcat(filename_string,"/song.epi");
430 switch(ret){
431 case SESSION_SAVE: save(filename_string); break;
432 case SESSION_LOAD: load(filename_string); break;
433 case SESSION_QUIT: ui->main_window->hide(); break;
434 case SESSION_UNHANDLED: break;
436 free(session_string);
437 ret=backend_session_process();
441 if(is_backend_playing()){
442 fltk::repeat_timeout(0.005, playing_timeout_cb, NULL);
444 else{
445 fltk::repeat_timeout(0.1, playing_timeout_cb, NULL);
449 void start_monitor(){
450 fltk::add_timeout(0.1, playing_timeout_cb, NULL);
453 void press_play(){
454 if(!is_backend_playing()){
455 start_backend();
456 ui->play_button->label("@||");
457 //fltk::add_timeout(0.01, playing_timeout_cb, NULL);
459 else{
460 pause_backend();
461 all_notes_off();
462 ui->play_button->label("@>");
466 void press_stop(){
468 //stops playback and sets the play position to zero
469 pause_backend();
470 reset_backend(0);
471 all_notes_off();
473 ui->song_timeline->update(0);
474 ui->pattern_timeline->update(0);
476 ui->song_timeline->redraw();
477 ui->pattern_timeline->redraw();
479 ui->play_button->label("@>");
480 ui->play_button->redraw();
482 ui->metronome->update(0);
487 void set_quant(int q){
488 switch(q){
489 case 0:
490 ui->qbutton4->state(0);
491 ui->qbutton8->state(0);
492 ui->qbutton16->state(0);
493 ui->qbutton32->state(0);
494 ui->qbutton64->state(0);
495 ui->qbutton128->state(0);
496 ui->qbutton0->state(1);
497 ui->piano_roll->set_qtick(1);
498 break;
499 case 4:
500 ui->qbutton4->state(1);
501 ui->qbutton8->state(0);
502 ui->qbutton16->state(0);
503 ui->qbutton32->state(0);
504 ui->qbutton64->state(0);
505 ui->qbutton128->state(0);
506 ui->qbutton0->state(0);
507 ui->piano_roll->set_qtick(128);
508 break;
509 case 8:
510 ui->qbutton4->state(0);
511 ui->qbutton8->state(1);
512 ui->qbutton16->state(0);
513 ui->qbutton32->state(0);
514 ui->qbutton64->state(0);
515 ui->qbutton128->state(0);
516 ui->qbutton0->state(0);
517 ui->piano_roll->set_qtick(64);
518 break;
519 case 16:
520 ui->qbutton4->state(0);
521 ui->qbutton8->state(0);
522 ui->qbutton16->state(1);
523 ui->qbutton32->state(0);
524 ui->qbutton64->state(0);
525 ui->qbutton128->state(0);
526 ui->qbutton0->state(0);
527 ui->piano_roll->set_qtick(32);
528 break;
529 case 32:
530 ui->qbutton4->state(0);
531 ui->qbutton8->state(0);
532 ui->qbutton16->state(0);
533 ui->qbutton32->state(1);
534 ui->qbutton64->state(0);
535 ui->qbutton128->state(0);
536 ui->qbutton0->state(0);
537 ui->piano_roll->set_qtick(16);
538 break;
539 case 64:
540 ui->qbutton4->state(0);
541 ui->qbutton8->state(0);
542 ui->qbutton16->state(0);
543 ui->qbutton32->state(0);
544 ui->qbutton64->state(1);
545 ui->qbutton128->state(0);
546 ui->qbutton0->state(0);
547 ui->piano_roll->set_qtick(8);
548 break;
549 case 128:
550 ui->qbutton4->state(0);
551 ui->qbutton8->state(0);
552 ui->qbutton16->state(0);
553 ui->qbutton32->state(0);
554 ui->qbutton64->state(0);
555 ui->qbutton128->state(1);
556 ui->qbutton0->state(0);
557 ui->piano_roll->set_qtick(4);
558 break;
563 void set_beats_per_measure(int n){
564 config.beats_per_measure = n;
565 ui->metronome->set_N(n);
566 ui->piano_roll->redraw();
567 ui->arranger->redraw();
568 ui->arranger->q_tick = n*TICKS_PER_BEAT;
569 ui->song_timeline->redraw();
570 ui->pattern_timeline->redraw();
573 void set_measures_per_phrase(int n){
574 config.measures_per_phrase = n;
575 ui->piano_roll->redraw();
576 ui->arranger->redraw();
577 ui->song_timeline->redraw();
578 ui->pattern_timeline->redraw();
581 void set_beats_per_minute(int n){
582 config.beats_per_minute = n;
583 set_bpm(n);
586 void set_measures_until_record(int n){
587 config.measures_until_record = n;
590 void set_alwayscopy(int v){
591 config.alwayscopy = v;
594 void set_autotrackname(int v){
595 config.autotrackname = v;
598 void set_passthru(int v){
599 config.passthru = v;
600 backend_set_passthru(v);
603 void set_playinsert(int v){
604 config.playinsert = v;
607 void set_recordonchan(int v){
608 config.recordonchan = v;
611 void set_playmove(int v){
612 config.playmove = v;
615 void set_follow(int v){
616 config.follow = v;
619 void set_recordmode(int n){
620 config.recordmode = n;
623 void set_robmode(int n){
624 config.robmode = n;
627 void set_defaultvelocity(int n){
628 config.defaultvelocity = n;
632 int scopeon=0;
633 void turnonscope(){
634 scopeon=1;
637 void turnoffscope(){
638 scopeon=0;
639 fltk::TextBuffer* ptr = ui->scope->buffer();
640 ptr->remove(0,ptr->length());
641 // ui->redraw();
644 void scope_print(const char* text){
645 if(scopeon){
646 ui->scope->append(text);
647 int N = ui->scope->buffer()->length();
648 ui->scope->scroll(N,0);
654 void show_song_edit(){
655 ui->pattern_edit->hide();
656 ui->pattern_buttons->hide();
657 ui->song_edit->activate();
658 ui->song_edit->show();
659 ui->song_edit->take_focus();
660 ui->song_buttons->show();
663 void show_pattern_edit(){
664 ui->song_edit->hide();
665 ui->song_edit->deactivate();
666 ui->song_buttons->hide();
667 ui->pattern_edit->take_focus();
668 ui->pattern_edit->show();
669 ui->pattern_buttons->show();
673 static int tool = 0;
674 //switch between normal, note off, portamento, and aftertouch
675 void toggle_tool(){
676 switch(tool){
677 case 0:
678 tool=1;
679 ui->tool_button->copy_label("80");
680 ui->tool_button->state(1);
681 break;
682 case 1:
683 tool=2;
684 ui->tool_button->copy_label("A0");
685 break;
686 case 2:
687 tool=3;
688 ui->tool_button->copy_label("po");
689 break;
690 case 3:
691 tool=0;
692 ui->tool_button->copy_label("tool");
693 ui->tool_button->state(0);
694 break;
700 void reset_song(){
701 clear();
703 track* t;
704 for(int i=0; i<16; i++){
705 t = new track();
706 t->head->track = i;
707 t->chan = i;
708 add_track(t);
711 ui->track_info->update();
712 ui->action_window->hide();
717 void add_track(track* t){
718 tracks.push_back(t);
719 ui->track_info->add_track();
722 void remove_track(int n){