1
Fork 0
epichord/src/uihelper.cpp

824 lines
21 KiB
C++

/*
Epichord - a midi sequencer
Copyright (C) 2008 Evan Rinehart
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to
The Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor
Boston, MA 02110-1301, USA
*/
#include <stdlib.h>
#include <vector>
#include <fstream>
#include <string.h>
#include <math.h>
#include <limits>
#include <fltk/run.h>
#include "seq.h"
#include "ui.h"
#include "backend.h"
#include "uihelper.h"
#define CONFIG_FILENAME ".epichordrc"
extern UI* ui;
extern std::vector<track*> tracks;
struct conf config;
using namespace std;
char* config_filename;
void load_config(){
//linux dependent
char* homepath = getenv("HOME");
asprintf(&config_filename,"%s/"CONFIG_FILENAME,homepath);
fstream f;
f.open(config_filename,fstream::in);
if(!f.is_open()){
printf("load_config: Unable to open config file for reading.\n");
config.beats_per_measure = 4;
config.measures_per_phrase = 4;
config.measures_until_record = 1;
config.alwayscopy = 0;
config.autotrackname = 0;
config.passthru = 1;
config.playinsert = 1;
config.recordonchan = 0;
config.playmove = 1;
config.follow = 1;
config.recordmode = 0;
config.robmode = 0;
config.defaultvelocity = 96;
load_default_keymap();
update_config_gui();
return;
}
config.beats_per_measure = 4;
config.measures_per_phrase = 4;
std::string word;
while(!f.eof()){
word = "";
f >> word;
if(word == "leadin"){f>>config.measures_until_record;}
else if(word == "alwayscopy"){f>>config.alwayscopy;}
else if(word == "autotrackname"){f>>config.autotrackname;}
else if(word == "passthru"){f>>config.passthru;}
else if(word == "playinsert"){f>>config.playinsert;}
else if(word == "recordonchan"){f>>config.recordonchan;}
else if(word == "playmove"){f>>config.playmove;}
else if(word == "follow"){f>>config.follow;}
else if(word == "recordmode"){f>>config.recordmode;}
else if(word == "robmode"){f>>config.robmode;}
else if(word == "keymap"){load_keymap(f);}
else if(word == "defaultvelocity"){f>>config.defaultvelocity;}
else{
f.ignore(std::numeric_limits<streamsize>::max(),'\n');
}
}
update_config_gui();
f.close();
}
void save_config(){
fstream f;
f.open(config_filename,fstream::out);
if(!f.is_open()){
printf("save_config: Unable to open config file %s for saving.\n", config_filename);
return;
}
f << "leadin " << config.measures_until_record << endl;
f << "alwayscopy " << config.alwayscopy << endl;
f << "autotrackname " << config.autotrackname << endl;
f << "passthru " << config.passthru << endl;
f << "playinsert " << config.playinsert << endl;
f << "recordonchan " << config.recordonchan << endl;
f << "playmove " << config.playmove << endl;
f << "follow " << config.follow << endl;
//f << "quantizedur " << config.quantizedur << endl;
f << "recordmode " << config.recordmode << endl;
f << "robmode " << config.robmode << endl;
f << "defaultvelocity " << config.defaultvelocity << endl;
f << endl;
save_keymap(f);
f.close();
}
void update_config_gui(){
ui->beats_per_measure->value(config.beats_per_measure);
ui->measures_per_phrase->value(config.measures_per_phrase);
ui->measures_until_record->value(config.measures_until_record);
ui->bpm_wheel->value(config.beats_per_minute);
ui->bpm_output->value(config.beats_per_minute);
ui->check_alwayscopy->state(config.alwayscopy);
ui->check_autotrackname->state(config.autotrackname);
ui->check_passthru->state(config.passthru);
ui->check_playinsert->state(config.playinsert);
ui->check_recordonchan->state(config.recordonchan);
ui->check_playmove->state(config.playmove);
ui->check_follow->state(config.follow);
ui->menu_recordmode->value(config.recordmode);
ui->menu_rob->value(config.robmode);
ui->default_velocity->value(config.defaultvelocity);
ui->config_window->redraw();
}
seqpat* rob_check(seqpat* s){
seqpat* prev = s->prev;
Command* c;
if(config.robmode == 0){
return NULL;
}
else if(config.robmode == 1 || prev == NULL){
int pos = get_play_position();
int M = config.measures_per_phrase;
if(M!=0){
M = M*config.beats_per_measure*128;
}
else{
M = 4*config.beats_per_measure*128;
}
int P1 = pos/M*M;
int P2 = P1 + M;
int T = P1;
int R = s->tick+s->dur;
if(R > P1){
T = R;
}
int W = P2 - T;
if(s->next){
int L = s->next->tick;
if(L < P2){
W = L - T;
}
}
c = new CreateSeqpatBlank(s->track,T,W);
set_undo(c);
undo_push(1);
return s->next;
}
else if(config.robmode == 2){
int pos = get_play_position();
int M = config.measures_per_phrase;
if(M!=0){
M = M*config.beats_per_measure*128;
}
else{
M = 4*config.beats_per_measure*128;
}
int P = pos/M*M + M;//tick at next phrase boundary
int W = P - s->tick;
if(s->next){
int W2 = s->next->tick - s->tick;
if(W2 < W){
W=W2;
}
}
c = new ResizeSeqpat(s,W);
set_undo(c);
undo_push(1);
return prev->next;
}
}
int last_pos=0;
void playing_timeout_cb(void* v){
int pos = get_play_position();
if(pos < last_pos){
reset_record_flags();
}
last_pos = pos;
if(config.follow){
ui->arranger->update(pos);
ui->piano_roll->update(pos);
}
ui->song_timeline->update(pos);
ui->pattern_timeline->update(pos);
ui->metronome->update(pos);
//check for midi input
int tick;
int chan;
int type;
int val1;
int val2;
track* t = tracks[get_rec_track()];
Command* c;
seqpat* s;
pattern* p;
char report[256];
while(recv_midi(&chan,&tick,&type,&val1,&val2)){
if(config.recordonchan){
for(int i=0; i<tracks.size(); i++){
if(tracks[i]->chan == chan){
t = tracks[i];
}
}
}
switch(type){
case 0x80://note off
snprintf(report,256,"%02x %02x %02x : note off - ch %d note %d vel %d\n",type|chan,val1,val2,chan,val1,val2);
scope_print(report);
if(!is_backend_recording())
break;
s = tfind<seqpat>(t->head,tick);
if(s->tick+s->dur < tick){
s = rob_check(s);
if(!s){continue;}
}
//if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
s->record_check(config.recordmode);
p = s->p;
c=new CreateNoteOff(p,val1,val2,tick-s->tick);
set_undo(c);
undo_push(1);
if(ui->piano_roll->visible()){
ui->piano_roll->redraw();
ui->event_edit->redraw();
if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[1]=1;}
ui->event_menu->redraw();
}
if(ui->arranger->visible())
ui->arranger->redraw();
break;
case 0x90://note on
snprintf(report,256,"%02x %02x %02x : note on - ch %d note %d vel %d\n",type|chan,val1,val2,chan,val1,val2);
scope_print(report);
if(!is_backend_recording())
break;
s = tfind<seqpat>(t->head,tick);
if(s->tick+s->dur < tick){
s = rob_check(s);
if(!s){continue;}
}
// if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
s->record_check(config.recordmode);
p = s->p;
c=new CreateNoteOn(p,val1,val2,tick-s->tick,16);
set_undo(c);
undo_push(1);
if(ui->piano_roll->visible()){
ui->piano_roll->redraw();
ui->event_edit->redraw();
if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[0]=1;}
ui->event_menu->redraw();
}
if(ui->arranger->visible())
ui->arranger->redraw();
break;
case 0xa0://aftertouch
case 0xb0://controller
case 0xc0://program change
case 0xd0://channel pressure
case 0xe0://pitch wheel
s = tfind<seqpat>(t->head,tick);
if(s->tick+s->dur < tick){
s = rob_check(s);
if(!s){continue;}
}
switch(type){
case 0xa0:
snprintf(report,256,"%02x %02x %02x : aftertouch - ch %d note %d %d\n",type|chan,val1,val2,chan,val1,val2);
if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[2]=1;}
break;
case 0xb0:
snprintf(report,256,"%02x %02x %02x : controller change - ch %d cntr %d val %d\n",type|chan,val1,val2,chan,val1,val2);
if(ui->event_edit->cur_seqpat == s){
ui->event_edit->has[val1+6]=1;
}
break;
case 0xc0:
snprintf(report,256,"%02x %02x : program change - ch %d pgrm %d \n",type|chan,val1,chan,val1);
if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[3]=1;}
break;
case 0xd0:
snprintf(report,256,"%02x %02x : channel pressure - ch %d val %d \n",type|chan,val1,chan,val1);
if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[4]=1;}
break;
case 0xe0:
snprintf(report,256,"%02x %02x %02x : pitch wheel - ch %d val %d \n",type|chan,val1,val2,chan,(val2<<7)|val1);
if(ui->event_edit->cur_seqpat == s){ui->event_edit->has[5]=1;}
break;
}
scope_print(report);
if(!is_backend_recording())
break;
// if(s->record_flag==1 && config.recordmode>0){show_song_edit();}
s->record_check(config.recordmode);
p = s->p;
c=new CreateEvent(p,type,tick,val1,val2);
set_undo(c);
undo_push(1);
if(ui->piano_roll->visible()){
ui->piano_roll->redraw();
ui->event_edit->redraw();
ui->event_menu->redraw();
}
if(ui->arranger->visible())
ui->arranger->redraw();
break;
case 0xf0:
switch(chan){
case 1://undefined (reserved) system common message
snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
break;
case 2://song position pointer
snprintf(report,256,"%02x %02x %02x : song position - %d \n",type|chan,val1,val2,(val2<<7)|val1);
break;
case 3://song select
snprintf(report,256,"%02x %02x : song select - %d \n",type|chan,val1,val1);
break;
case 4://undefined (reserved) system common message
case 5://undefined (reserved) system common message
snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
break;
case 6://tune request
snprintf(report,256,"%02x : tune request\n",type|chan);
break;
case 7://end of exclusive
snprintf(report,256,"%02x : end of exclusive\n",type|chan);
break;
case 8://timing clock
snprintf(report,256,"%02x : timing clock\n",type|chan);
break;
case 9://undefined (reserved) system common message
snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
break;
case 10://start
snprintf(report,256,"%02x : start\n",type|chan);
break;
case 11://continue
snprintf(report,256,"%02x : continue\n",type|chan);
break;
case 12://stop
snprintf(report,256,"%02x : stop\n",type|chan);
break;
case 13://undefined
snprintf(report,256,"%02x : undefined (reserved) system common message\n",type|chan);
break;
case 14://active sensing
snprintf(report,256,"%02x : active sensing\n",type|chan);
break;
case 15://reset
snprintf(report,256,"%02x : reset\n",type|chan);
break;
}
if(chan==0){
snprintf(report,256,"%02x %02x : system exclusive - id %d ; data follows\n",type|chan,val1,val1);
scope_print(report);
scope_print(getsysexbuf());
scope_print("\nf7 : end of sysex\n");
}
else{
scope_print(report);
}
}
}
//handle session events (LASH)
int ret;
char* session_string;
char* filename_string;
ret=backend_session_process();
while(ret != SESSION_NOMORE){
session_string=get_session_string();
filename_string = (char*)malloc(strlen(session_string)+16);
strcpy(filename_string,session_string);
strcat(filename_string,"/song.epi");
switch(ret){
case SESSION_SAVE: save(filename_string); break;
case SESSION_LOAD: load(filename_string); break;
case SESSION_QUIT: ui->main_window->hide(); break;
case SESSION_UNHANDLED: break;
}
free(session_string);
ret=backend_session_process();
}
if(is_backend_playing()){
fltk::repeat_timeout(0.005, playing_timeout_cb, NULL);
}
else{
fltk::repeat_timeout(0.1, playing_timeout_cb, NULL);
}
}
void start_monitor(){
fltk::add_timeout(0.1, playing_timeout_cb, NULL);
}
void press_play(){
if(!is_backend_playing()){
start_backend();
ui->play_button->label("@||");
//fltk::add_timeout(0.01, playing_timeout_cb, NULL);
}
else{
pause_backend();
all_notes_off();
ui->play_button->label("@>");
}
}
void press_stop(){
int left = get_loop_start();
if(get_play_position()==left || get_play_position()==0){
left=0;
}
pause_backend();
reset_backend(left);
all_notes_off();
ui->song_timeline->update(left);
ui->pattern_timeline->update(left);
ui->song_timeline->redraw();
ui->pattern_timeline->redraw();
ui->play_button->label("@>");
ui->play_button->redraw();
ui->metronome->update(left);
}
void set_quant(int q){
switch(q){
case 0:
ui->qbutton4->state(0);
ui->qbutton8->state(0);
ui->qbutton16->state(0);
ui->qbutton32->state(0);
ui->qbutton64->state(0);
ui->qbutton128->state(0);
ui->qbutton0->state(1);
ui->piano_roll->set_qtick(1);
break;
case 4:
ui->qbutton4->state(1);
ui->qbutton8->state(0);
ui->qbutton16->state(0);
ui->qbutton32->state(0);
ui->qbutton64->state(0);
ui->qbutton128->state(0);
ui->qbutton0->state(0);
ui->piano_roll->set_qtick(128);
break;
case 8:
ui->qbutton4->state(0);
ui->qbutton8->state(1);
ui->qbutton16->state(0);
ui->qbutton32->state(0);
ui->qbutton64->state(0);
ui->qbutton128->state(0);
ui->qbutton0->state(0);
ui->piano_roll->set_qtick(64);
break;
case 16:
ui->qbutton4->state(0);
ui->qbutton8->state(0);
ui->qbutton16->state(1);
ui->qbutton32->state(0);
ui->qbutton64->state(0);
ui->qbutton128->state(0);
ui->qbutton0->state(0);
ui->piano_roll->set_qtick(32);
break;
case 32:
ui->qbutton4->state(0);
ui->qbutton8->state(0);
ui->qbutton16->state(0);
ui->qbutton32->state(1);
ui->qbutton64->state(0);
ui->qbutton128->state(0);
ui->qbutton0->state(0);
ui->piano_roll->set_qtick(16);
break;
case 64:
ui->qbutton4->state(0);
ui->qbutton8->state(0);
ui->qbutton16->state(0);
ui->qbutton32->state(0);
ui->qbutton64->state(1);
ui->qbutton128->state(0);
ui->qbutton0->state(0);
ui->piano_roll->set_qtick(8);
break;
case 128:
ui->qbutton4->state(0);
ui->qbutton8->state(0);
ui->qbutton16->state(0);
ui->qbutton32->state(0);
ui->qbutton64->state(0);
ui->qbutton128->state(1);
ui->qbutton0->state(0);
ui->piano_roll->set_qtick(4);
break;
}
}
void set_songtool(int i){
switch(i){
case 0:
ui->edit_button->state(1);
ui->color_button->state(0);
ui->unclone_button->state(0);
ui->split_button->state(0);
ui->join_button->state(0);
ui->arranger->color_flag = 0;
ui->arranger->unclone_flag = 0;
ui->arranger->split_flag = 0;
ui->arranger->join_flag = 0;
break;
case 1:
ui->edit_button->state(0);
ui->color_button->state(1);
ui->unclone_button->state(0);
ui->split_button->state(0);
ui->join_button->state(0);
ui->arranger->color_flag = 1;
ui->arranger->unclone_flag = 0;
ui->arranger->split_flag = 0;
ui->arranger->join_flag = 0;
break;
case 2:
ui->edit_button->state(0);
ui->color_button->state(0);
ui->unclone_button->state(1);
ui->split_button->state(0);
ui->join_button->state(0);
ui->arranger->color_flag = 0;
ui->arranger->unclone_flag = 1;
ui->arranger->split_flag = 0;
ui->arranger->join_flag = 0;
break;
case 3:
ui->edit_button->state(0);
ui->color_button->state(0);
ui->unclone_button->state(0);
ui->split_button->state(1);
ui->join_button->state(0);
ui->arranger->color_flag = 0;
ui->arranger->unclone_flag = 0;
ui->arranger->split_flag = 1;
ui->arranger->join_flag = 0;
break;
case 4:
ui->edit_button->state(0);
ui->color_button->state(0);
ui->unclone_button->state(0);
ui->split_button->state(0);
ui->join_button->state(1);
ui->arranger->color_flag = 0;
ui->arranger->unclone_flag = 0;
ui->arranger->split_flag = 0;
ui->arranger->join_flag = 1;
break;
}
}
void set_beats_per_measure(int n){
config.beats_per_measure = n;
ui->metronome->set_N(n);
ui->piano_roll->redraw();
ui->arranger->redraw();
ui->arranger->q_tick = n*TICKS_PER_BEAT;
ui->song_timeline->redraw();
ui->pattern_timeline->redraw();
}
void set_measures_per_phrase(int n){
config.measures_per_phrase = n;
ui->piano_roll->redraw();
ui->arranger->redraw();
ui->song_timeline->redraw();
ui->pattern_timeline->redraw();
}
void set_beats_per_minute(int n){
config.beats_per_minute = n;
set_bpm(n);
}
void set_measures_until_record(int n){
config.measures_until_record = n;
}
void set_alwayscopy(int v){
config.alwayscopy = v;
}
void set_autotrackname(int v){
config.autotrackname = v;
}
void set_passthru(int v){
config.passthru = v;
backend_set_passthru(v);
}
void set_playinsert(int v){
config.playinsert = v;
}
void set_recordonchan(int v){
config.recordonchan = v;
}
void set_playmove(int v){
config.playmove = v;
}
void set_follow(int v){
config.follow = v;
}
void set_recordmode(int n){
config.recordmode = n;
}
void set_robmode(int n){
config.robmode = n;
}
void set_defaultvelocity(int n){
config.defaultvelocity = n;
}
int scopeon=0;
void turnonscope(){
scopeon=1;
}
void turnoffscope(){
scopeon=0;
fltk::TextBuffer* ptr = ui->scope->buffer();
ptr->remove(0,ptr->length());
// ui->redraw();
}
void scope_print(const char* text){
if(scopeon){
ui->scope->append(text);
int N = ui->scope->buffer()->length();
ui->scope->scroll(N,0);
}
}
void show_song_edit(){
ui->pattern_edit->hide();
ui->pattern_buttons->hide();
ui->song_edit->activate();
ui->song_edit->show();
ui->song_edit->take_focus();
ui->song_buttons->show();
}
void show_pattern_edit(){
ui->song_edit->hide();
ui->song_edit->deactivate();
ui->song_buttons->hide();
ui->pattern_edit->take_focus();
ui->pattern_edit->show();
ui->pattern_buttons->show();
}
static int tool = 0;
//switch between normal, note off, portamento, and aftertouch
void toggle_tool(){
switch(tool){
case 0:
tool=1;
ui->tool_button->copy_label("80");
ui->tool_button->state(1);
break;
case 1:
tool=2;
ui->tool_button->copy_label("A0");
break;
case 2:
tool=3;
ui->tool_button->copy_label("po");
break;
case 3:
tool=0;
ui->tool_button->copy_label("tool");
ui->tool_button->state(0);
break;
}
}
void reset_song(){
clear();
track* t;
for(int i=0; i<16; i++){
t = new track();
t->head->track = i;
t->chan = i;
add_track(t);
}
set_rec_track(0);
ui->track_info->set_rec(0);
ui->track_info->update();
ui->action_window->hide();
}
void add_track(track* t){
tracks.push_back(t);
ui->track_info->add_track();
}
void remove_track(int n){
}
void init_gui(){
ui->arranger->layout();
ui->song_vscroll->slider_size(60);
ui->song_vscroll->value(0);
ui->pattern_timeline->edit_flag = 1;
ui->pattern_timeline->zoom = 15;
ui->pattern_vscroll->minimum(12*75);
ui->pattern_vscroll->maximum(0);
ui->pattern_vscroll->value(300);
ui->pattern_vscroll->slider_size(50);
ui->pattern_hscroll->value(0);
}