bool ConfigFile::SetSource(const char* file, bool /*ignorecase*/) { /* wipe any existing settings. */ m_settings.clear(); /* open the file */ if (file != 0) { //the right mode in Windows is "rb" since '\n' is saved as 0x0D,0x0A but fopen(file,"r") reads these 2 chars //as only 1 char, so ftell(f) returns a higher value than the required by fread() to the file to buf. #ifdef WIN32 FILE* f = fopen(file, "rb"); #else FILE* f = fopen(file, "r"); #endif char* buf; int length; if (!f) { sLog.outError("Could not open %s.", file); return false; } /* get the length of the file */ fseek(f, 0, SEEK_END); length = ftell(f); buf = new char[length + 1]; fseek(f, 0, SEEK_SET); fread(buf, length, 1, f); buf[length] = '\0'; string buffer = string(buf); delete[] buf; /* close the file, it is no longer needed */ fclose(f); /* let's parse it. */ string line; string::size_type end; string::size_type offset; bool in_multiline_comment = false; bool in_multiline_quote = false; bool in_block = false; string current_setting = ""; string current_variable = ""; string current_block = ""; ConfigBlock current_block_map; ConfigSetting current_setting_struct; /* oh god this is awful */ try { for (;;) { /* grab a line. */ end = buffer.find(EOL); if (end == string::npos) { if (buffer.size() == 0) break; line = buffer; buffer.clear(); goto parse; } line = buffer.substr(0, end); buffer.erase(0, end + EOL_SIZE); goto parse; parse: if (!line.size()) continue; /* are we a comment? */ if (!in_multiline_comment && is_comment(line, &in_multiline_comment)) { /* our line is a comment. */ if (!in_multiline_comment) { /* the entire line is a comment, skip it. */ continue; } } /* handle our cases */ if (in_multiline_comment) { // we need to find a "*/". offset = line.find("*/", 0); /* skip this entire line, eh? */ if (offset == string::npos) continue; /* remove up to the end of the comment block. */ line.erase(0, offset + 2); in_multiline_comment = false; } if (in_block) { /* handle settings across multiple lines */ if (in_multiline_quote) { /* attempt to find the end of the quote block. */ offset = line.find("\""); if (offset == string::npos) { /* append the whole line to the quote. */ current_setting += line; current_setting += "\n"; continue; } /* only append part of the line to the setting. */ current_setting.append(line.c_str(), offset + 1); line.erase(0, offset + 1); /* append the setting to the config block. */ if (current_block == "" || current_variable == "") { sLog.outError("Quote without variable."); return false; } /* apply the setting */ apply_setting(current_setting, current_setting_struct); /* the setting is done, append it to the current block. */ current_block_map[ahash(current_variable)] = current_setting_struct; #ifdef _CONFIG_DEBUG sLog.outDebug("Block: '%s', Setting: '%s', Value: '%s'", current_block.c_str(), current_variable.c_str(), current_setting_struct.AsString.c_str()); #endif /* no longer doing this setting, or in a quote. */ current_setting = ""; current_variable = ""; in_multiline_quote = false; } /* remove any leading spaces */ remove_spaces(line); if (!line.size()) continue; /* our target is a *setting*. look for an '=' sign, this is our seperator. */ offset = line.find("="); if (offset != string::npos) { ASSERT(current_variable == ""); current_variable = line.substr(0, offset); /* remove any spaces from the end of the setting */ remove_all_spaces(current_variable); /* remove the directive *and* the = from the line */ line.erase(0, offset + 1); } /* look for the opening quote. this signifies the start of a setting. */ offset = line.find("\""); if (offset != string::npos) { ASSERT(current_setting == ""); ASSERT(current_variable != ""); /* try and find the ending quote */ end = line.find("\"", offset + 1); if (end != string::npos) { /* the closing quote is on the same line, oh goody. */ current_setting = line.substr(offset + 1, end - offset - 1); /* erase up to the end */ line.erase(0, end + 1); /* apply the setting */ apply_setting(current_setting, current_setting_struct); /* the setting is done, append it to the current block. */ current_block_map[ahash(current_variable)] = current_setting_struct; #ifdef _CONFIG_DEBUG sLog.outDebug("Block: '%s', Setting: '%s', Value: '%s'", current_block.c_str(), current_variable.c_str(), current_setting_struct.AsString.c_str()); #endif /* no longer doing this setting, or in a quote. */ current_setting = ""; current_variable = ""; in_multiline_quote = false; /* attempt to grab more settings from the same line. */ goto parse; } else { /* the closing quote is not on the same line. means we'll try and find it on the next. */ current_setting.append(line.c_str(), offset); /* skip to the next line. (after setting our condition first, of course :P */ in_multiline_quote = true; continue; } } /* are we at the end of the block yet? */ offset = line.find(">"); if (offset != string::npos) { line.erase(0, offset + 1); // freeeee! in_block = false; /* assign this block to the main "big" map. */ m_settings[ahash(current_block)] = current_block_map; /* erase all data for this so it doesn't seep through */ current_block_map.clear(); current_setting = ""; current_variable = ""; current_block = ""; } } else { /* we're not in a block. look for the start of one. */ offset = line.find("<"); if (offset != string::npos) { in_block = true; /* whee, a block! let's cut the string and re-parse. */ line.erase(0, offset + 1); /* find the name of the block first, though. */ offset = line.find(" "); if (offset != string::npos) { current_block = line.substr(0, offset); line.erase(0, offset + 1); } else { sLog.outError("Block without name."); return false; } /* skip back */ goto parse; } } } } catch (...) { sLog.outError("Exception in config parsing."); return false; } /* handle any errors */ if (in_block) { sLog.outError("Unterminated block."); return false; } if (in_multiline_comment) { sLog.outError("Unterminated comment."); return false; } if (in_multiline_quote) { sLog.outError("Unterminated quote."); return false; } /* we're all good :) */ return true; } return false; }
program_t parse(string fn) { int n_osc=0; oscillator_t *osc=NULL; env_settings_t *env=NULL; filter_params_t filter; fixed_t sync_factor=0; bool sync_factor_const=true; char buf[2000]; string line; string var; string array; string strval; float val; parameter_enum p; int ind,ind2=0; int state; program_t result; ifstream f; f.open(fn.c_str()); if (f.good()) { state=0; while (!f.eof()) { f.getline(buf,sizeof(buf)/sizeof(*buf)-1); line=buf; line=remove_all_spaces(buf); if ((line!="") && (line[0]!='#')) //ignore comments and empty lines { if (line=="controllers:") { state=2; continue; } else if (line=="defaults:") { state=3; continue; } else if (line=="velocity:") { state=4; continue; } else if (line=="variable:") { state=5; continue; } var=extract_var(line); array=extract_array_name(var); strval=extract_val(line); val=atof(strval.c_str()); switch (state) { case 0: //expect and read number of oscillators if (var!="oscillators") throw string("need to know number of oscillators"); else n_osc=val; if (n_osc<=0) throw string("invalid number of oscillators"); //init stuff env=new env_settings_t[n_osc]; osc=new oscillator_t[n_osc]; init_oscs(n_osc, osc); init_envs(n_osc, env); init_filter(filter); state=1; break; case 1: //read and set information about oscillator settings p=param_to_enum(array); ind=extract_array_index(var,1); if ( param_needs_index(p) && (!((ind>=0) && (ind<n_osc))) ) throw string("out of array bounds"); switch (p) { case MODULATION: ind2=extract_array_index(var,2); if (!((ind2>=0) && (ind2<n_osc))) throw string("out of array bounds"); osc[ind].fm_strength[ind2]=val*ONE; break; case OUTPUT: osc[ind].output=val*ONE; break; case WAVEFORM: if (isfloat(strval)) { osc[ind].waveform=int(val); } else { osc[ind].have_custom_wave=true; } break; case FACTOR: osc[ind].factor=val*ONE; break; case TREMOLO: osc[ind].tremolo_depth=int(val); break; case TREM_LFO: if (strval=="snh") osc[ind].tremolo_lfo=SNH_LFO; else { osc[ind].tremolo_lfo= int(val); if ((val<0) || (val>=N_LFOS)) throw string("invalid value for tremolo_lfo"); } break; case VIBRATO: osc[ind].vibrato_depth=val; break; case VIB_LFO: if (strval=="snh") osc[ind].vibrato_lfo= SNH_LFO; else { osc[ind].vibrato_lfo= int(val); if ((val<0) || (val>=N_LFOS)) throw string("invalid value for vibrato_lfo"); } break; case ATTACK: env[ind].attack=val; break; case DECAY: env[ind].decay=val; break; case SUSTAIN: env[ind].sustain=val; break; case RELEASE: env[ind].release=val; break; case HOLD: env[ind].hold=(val!=0); break; case KSR: osc[ind].ksr=val; break; case KSL: osc[ind].ksl=val; break; case SYNC: osc[ind].sync=(val!=0); break; case FILTER_ENABLED: filter.enabled=(val!=0); break; case FILTER_ENV_AMOUNT: filter.env_amount=val; break; case FILTER_ATTACK: filter.env_settings.attack=val; break; case FILTER_DECAY: filter.env_settings.decay=val; break; case FILTER_SUSTAIN: filter.env_settings.sustain=val; break; case FILTER_RELEASE: filter.env_settings.release=val; break; case FILTER_HOLD: filter.env_settings.hold=(val!=0); break; case FILTER_OFFSET: filter.freqfactor_offset=val; break; case FILTER_RESONANCE: filter.resonance=val; break; case FILTER_TREMOLO: filter.trem_strength=int(val); break; case FILTER_TREM_LFO: if (strval=="snh") filter.trem_lfo=SNH_LFO; else { filter.trem_lfo=int(val); if ((val<0) || (val>=N_LFOS)) throw string("invalid value for filter_trem_lfo"); } break; case SYNC_FACTOR: sync_factor=val*ONE; break; default: throw string("unknown variable ('"+array+"')"); } break; case 5: //read which params shall be variable, even if //there are currently no controllers which change them case 4: //read velocity-influence over certain params case 2: //read how controllers influence parameters p=param_to_enum(array); ind=extract_array_index(var,1); if ( param_needs_index(p) && (!((ind>=0) && (ind<n_osc))) ) throw string("out of array bounds"); if (state==4) //velocity-influence { switch (p) { case MODULATION: case OUTPUT: case FILTER_ENV_AMOUNT: case FILTER_RESONANCE: case FILTER_OFFSET: // everything ok, do nothing break; default: // other params than the above may not be influenced! throw string("velocity cannot influence parameter '"+array+"'"); } } switch (p) { case MODULATION: ind2=extract_array_index(var,2); if (!((ind2>=0) && (ind2<n_osc))) throw string("out of array bounds"); osc[ind].fm_strength_const[ind2]=false; break; case OUTPUT: if (state!=4) // not vel.-influence osc[ind].output_const=false; break; case WAVEFORM: osc[ind].waveform_const=false; break; case FACTOR: osc[ind].factor_const=false; break; case TREMOLO: osc[ind].tremolo_depth_const=false; break; case TREM_LFO: osc[ind].tremolo_lfo_const=false; break; case VIBRATO: osc[ind].vibrato_depth_const=false; break; case VIB_LFO: osc[ind].vibrato_lfo_const=false; break; case ATTACK: env[ind].attack_const=false; break; case DECAY: env[ind].decay_const=false; break; case SUSTAIN: env[ind].sustain_const=false; break; case RELEASE: env[ind].release_const=false; break; case HOLD: env[ind].hold_const=false; break; case KSR: osc[ind].ksr_const=false; break; case KSL: osc[ind].ksl_const=false; break; case SYNC: osc[ind].sync_const=false; break; case FILTER_ENABLED: filter.enabled_const=false; break; case FILTER_ENV_AMOUNT: filter.env_amount_const=false; break; case FILTER_ATTACK: filter.env_settings.attack_const=false; break; case FILTER_DECAY: filter.env_settings.decay_const=false; break; case FILTER_SUSTAIN: filter.env_settings.sustain_const=false; break; case FILTER_RELEASE: filter.env_settings.release_const=false; break; case FILTER_HOLD: filter.env_settings.hold_const=false; break; case FILTER_OFFSET: filter.freqfactor_offset_const=false; break; case FILTER_RESONANCE: filter.resonance_const=false; break; case FILTER_TREMOLO: filter.trem_strength_const=false; break; case FILTER_TREM_LFO: filter.trem_lfo_const=false; break; case SYNC_FACTOR: sync_factor_const=false; break; default: throw string("unknown variable ('"+array+"')"); } break; case 3: //read controller default values //ignored break; } } } //some optimizations and checks for (int i=0;i<n_osc;i++) if ((env[i].attack==0) && (env[i].sustain==1.0) && (env[i].release>100)) //TODO FINDMICH besseres kriterium? env[i].enabled=false; if ( ((filter.env_settings.attack==0) && (filter.env_settings.sustain==1.0) && (filter.env_settings.release>100)) //TODO FINDMICH siehe oben || ((filter.env_amount==0) && (filter.env_amount_const==true)) ) filter.env_settings.enabled=false; bool use_sync=false; for (int i=0;i<n_osc;i++) if ((osc[i].sync==true) || (osc[i].sync_const==false)) { use_sync=true; break; } if (!use_sync) { sync_factor=0; sync_factor_const=true; } if ((sync_factor==0) && (sync_factor_const==true)) for (int i=0;i<n_osc;i++) { osc[i].sync=false; osc[i].sync_const=true; } for (int i=0;i<n_osc;i++) if ( !((osc[i].output==0) && (osc[i].output_const=true)) ) osc[i].output_const=false; //end optimizations and checks result.n_osc=n_osc; result.osc=osc; result.env=env; result.filter=filter; result.sync_factor=sync_factor; result.sync_factor_const=sync_factor_const; } else throw string ("could not open '"+fn+"'"); return result; //no uninit / free of osc and env here, as it must be done by the caller }