EffectMgr.cpp (22910B)
1 /* 2 ZynAddSubFX - a software synthesizer 3 4 EffectMgr.cpp - Effect manager, an interface between the program and effects 5 Copyright (C) 2002-2005 Nasca Octavian Paul 6 Author: Nasca Octavian Paul 7 8 This program is free software; you can redistribute it and/or 9 modify it under the terms of the GNU General Public License 10 as published by the Free Software Foundation; either version 2 11 of the License, or (at your option) any later version. 12 */ 13 14 #include <rtosc/ports.h> 15 #include <rtosc/port-sugar.h> 16 #include <iostream> 17 #include <cassert> 18 19 #include "EffectMgr.h" 20 #include "Effect.h" 21 #include "Alienwah.h" 22 #include "Reverb.h" 23 #include "Echo.h" 24 #include "Chorus.h" 25 #include "Distortion.h" 26 #include "EQ.h" 27 #include "DynamicFilter.h" 28 #include "Phaser.h" 29 #include "Sympathetic.h" 30 #include "../Effects/Reverse.h" 31 #include "../Misc/XMLwrapper.h" 32 #include "../Misc/Util.h" 33 34 #include "../Params/FilterParams.h" 35 #include "../Misc/Allocator.h" 36 #include "../Misc/Time.h" 37 38 namespace zyn { 39 40 #define rObject EffectMgr 41 #define rSubtype(name) \ 42 {STRINGIFY(name)"/", NULL, &name::ports,\ 43 [](const char *msg, rtosc::RtData &data){\ 44 rObject &o = *(rObject*)data.obj; \ 45 data.obj = dynamic_cast<name*>(o.efx); \ 46 if(!data.obj) \ 47 return; \ 48 SNIP \ 49 name::ports.dispatch(msg, data); \ 50 }} 51 static const rtosc::Ports local_ports = { 52 rSelf(EffectMgr, rEnabledByCondition(self-enabled)), 53 {"preset::i", rProp(parameter) rDepends(efftype) rDoc("Effect Preset Selector") 54 rDefault(0), NULL, 55 [](const char *msg, rtosc::RtData &d) 56 { 57 char loc[1024]; 58 EffectMgr *eff = (EffectMgr*)d.obj; 59 if(!rtosc_narguments(msg)) 60 d.reply(d.loc, "i", eff->getpreset()); 61 else { 62 eff->changepresetrt(rtosc_argument(msg, 0).i); 63 d.broadcast(d.loc, "i", eff->getpreset()); 64 65 //update parameters as well 66 fast_strcpy(loc, d.loc, sizeof(loc)); 67 char *tail = strrchr(loc, '/'); 68 if(!tail) 69 return; 70 for(int i=0;i<128;++i) { 71 snprintf(tail+1, sizeof(loc)-(tail+1-loc), "parameter%d", i); 72 d.broadcast(loc, "i", eff->geteffectparrt(i)); 73 } 74 } 75 }}, // must come before rPaste, because apropos otherwise picks "preset-type" first 76 rPaste, 77 rEnabledCondition(self-enabled, obj->geteffect()), 78 rEnabledCondition(is-dynamic-filter, (obj->geteffect()==8)), 79 rRecurp(filterpars, rDepends(preset), rEnabledByCondition(is-dynamic-filter), "Filter Parameter for Dynamic Filter"), 80 {"Pvolume::i", rProp(parameter) rLinear(0,127) rShort("amt") rDoc("amount of effect"), 81 0, 82 [](const char *msg, rtosc::RtData &d) 83 { 84 EffectMgr *eff = (EffectMgr*)d.obj; 85 if(!rtosc_narguments(msg)) 86 d.reply(d.loc, "i", eff->geteffectparrt(0)); 87 else if(rtosc_type(msg, 0) == 'i'){ 88 eff->seteffectparrt(0, rtosc_argument(msg, 0).i); 89 d.broadcast(d.loc, "i", eff->geteffectparrt(0)); 90 } 91 }}, 92 {"Ppanning::i", rProp(parameter) rLinear(0,127) rShort("pan") rDoc("panning"), 93 0, 94 [](const char *msg, rtosc::RtData &d) 95 { 96 EffectMgr *eff = (EffectMgr*)d.obj; 97 if(!rtosc_narguments(msg)) 98 d.reply(d.loc, "i", eff->geteffectparrt(1)); 99 else if(rtosc_type(msg, 0) == 'i'){ 100 eff->seteffectparrt(1, rtosc_argument(msg, 0).i); 101 d.broadcast(d.loc, "i", eff->geteffectparrt(1)); 102 } 103 }}, 104 {"parameter#128::i:T:F", rProp(parameter) rProp(alias) rLinear(0,127) rDoc("Parameter Accessor"), 105 NULL, 106 [](const char *msg, rtosc::RtData &d) 107 { 108 EffectMgr *eff = (EffectMgr*)d.obj; 109 const char *mm = msg; 110 while(!isdigit(*mm))++mm; 111 112 if(!rtosc_narguments(msg)) 113 d.reply(d.loc, "i", eff->geteffectparrt(atoi(mm))); 114 else if(rtosc_type(msg, 0) == 'i'){ 115 eff->seteffectparrt(atoi(mm), rtosc_argument(msg, 0).i); 116 d.broadcast(d.loc, "i", eff->geteffectparrt(atoi(mm))); 117 } else if(rtosc_type(msg, 0) == 'T'){ 118 eff->seteffectparrt(atoi(mm), 127); 119 d.broadcast(d.loc, "i", eff->geteffectparrt(atoi(mm))); 120 } else if(rtosc_type(msg, 0) == 'F'){ 121 eff->seteffectparrt(atoi(mm), 0); 122 d.broadcast(d.loc, "i", eff->geteffectparrt(atoi(mm))); 123 } 124 }}, 125 {"numerator::i", rShort("num") rDefault(0) rLinear(0,99) 126 rProp(parameter) rDoc("Numerator of ratio to bpm"), NULL, 127 [](const char *msg, rtosc::RtData &d) 128 { 129 EffectMgr *eff = (EffectMgr*)d.obj; 130 if(rtosc_narguments(msg)) { 131 int val = rtosc_argument(msg, 0).i; 132 if (val>=0) { 133 eff->numerator = val; 134 int Pdelay, Pfreq; 135 float freq, delay; 136 if (eff->numerator&&eff->denominator) { 137 eff->efx->speedfactor = (float)eff->denominator / (4.0f *(float)eff->numerator); 138 switch(eff->nefx) { 139 case 2: // Echo 140 case 10: // Reverse 141 // invert: 142 // delay = ((Pdelay+1)/128.0f*MAX_REV_DELAY_SECONDS); //0 .. x sec 143 // Pdelay = (delay * 128.0f / MAX_REV_DELAY_SECONDS) -1 144 // delay = 60 / tempo * 4 * numerator / denominator 145 assert(eff->time->tempo > 0); 146 delay = 60.0f / ((float)eff->time->tempo * eff->efx->speedfactor); 147 Pdelay = (unsigned char)(delay * 128.0f / MAX_REV_DELAY_SECONDS)-1; 148 eff->seteffectparrt(2, Pdelay); 149 break; 150 case 3: // Chorus 151 case 4: // Phaser 152 case 5: // Alienwah 153 case 8: // DynamicFilter 154 freq = (float)eff->time->tempo * 60.0 * eff->efx->speedfactor; 155 // invert: 156 // (powf(2.0f, Pfreq / 127.0f * 10.0f) - 1.0f) * 0.03f 157 Pfreq = (int)roundf(logf((freq/0.03f)+1.0f)/LOG_2 * 12.7f); 158 eff->seteffectparrt(2, Pfreq); 159 break; 160 case 1: // Reverb 161 case 6: // Distortion 162 case 7: // EQ 163 default: 164 break; 165 } 166 } 167 else 168 eff->efx->speedfactor = 0.0f; 169 } 170 d.broadcast(d.loc, "i", val); 171 } else { 172 d.reply(d.loc, "i", eff->numerator); 173 } 174 }}, 175 {"denominator::i", rShort("dem") rDefault(4) rLinear(1,99) 176 rProp(parameter) rDoc("Denominator of ratio to bpm"), NULL, 177 [](const char *msg, rtosc::RtData &d) 178 { 179 EffectMgr *eff = (EffectMgr*)d.obj; 180 if(rtosc_narguments(msg)) { 181 int val = rtosc_argument(msg, 0).i; 182 if (val > 0) { 183 eff->denominator = val; 184 int Pdelay, Pfreq; 185 float freq, delay; 186 if (eff->numerator&&eff->denominator) { 187 eff->efx->speedfactor = (float)eff->denominator / (4.0f *(float)eff->numerator); 188 switch(eff->nefx) { 189 case 2: // Echo 190 case 10: // Reverse 191 assert(eff->time->tempo > 0); 192 delay = 60.0f / ((float)eff->time->tempo * eff->efx->speedfactor); 193 Pdelay = (unsigned char)(delay * 128.0f / MAX_REV_DELAY_SECONDS)-1; 194 eff->seteffectparrt(2, Pdelay); 195 break; 196 case 3: // Chorus 197 case 4: // Phaser 198 case 5: // Alienwah 199 case 8: // DynamicFilter 200 freq = (float)eff->time->tempo * 60.0 * eff->efx->speedfactor; 201 // invert: 202 // (powf(2.0f, Pfreq / 127.0f * 10.0f) - 1.0f) * 0.03f 203 Pfreq = (int)roundf(logf((freq/0.03f)+1.0f)/LOG_2 * 12.7f); 204 eff->seteffectparrt(2, Pfreq); 205 break; 206 case 1: // Reverb 207 case 6: // Distortion 208 case 7: // EQ 209 default: 210 break; 211 } 212 } 213 else 214 eff->efx->speedfactor = 0.0f; 215 } 216 d.broadcast(d.loc, "i", val); 217 } else { 218 d.reply(d.loc, "i", eff->denominator); 219 } 220 }}, 221 {"eq-coeffs:", rProp(internal) rDoc("Get equalizer Coefficients"), NULL, 222 [](const char *, rtosc::RtData &d) 223 { 224 EffectMgr *eff = (EffectMgr*)d.obj; 225 if(eff->nefx != 7) 226 return; 227 EQ *eq = (EQ*)eff->efx; 228 float a[MAX_EQ_BANDS*MAX_FILTER_STAGES*3]; 229 float b[MAX_EQ_BANDS*MAX_FILTER_STAGES*3]; 230 memset(a, 0, sizeof(a)); 231 memset(b, 0, sizeof(b)); 232 eq->getFilter(a,b); 233 d.reply(d.loc, "bb", sizeof(a), a, sizeof(b), b); 234 }}, 235 {"efftype::i:c:S", rOptions(Disabled, Reverb, Echo, Chorus, 236 Phaser, Alienwah, Distortion, EQ, DynFilter, Sympathetic, Reverse) rDefault(Disabled) 237 rProp(parameter) rDoc("Get Effect Type"), NULL, 238 rCOptionCb(obj->nefx, obj->changeeffectrt(var))}, 239 {"efftype:b", rProp(internal) rDoc("Pointer swap EffectMgr"), NULL, 240 [](const char *msg, rtosc::RtData &d) 241 { 242 printf("OBSOLETE METHOD CALLED\n"); 243 EffectMgr *eff = (EffectMgr*)d.obj; 244 EffectMgr *eff_ = *(EffectMgr**)rtosc_argument(msg,0).b.data; 245 246 //Lets trade data 247 std::swap(eff->nefx,eff_->nefx); 248 std::swap(eff->efx,eff_->efx); 249 std::swap(eff->filterpars,eff_->filterpars); 250 std::swap(eff->efxoutl, eff_->efxoutl); 251 std::swap(eff->efxoutr, eff_->efxoutr); 252 253 //Return the old data for destruction 254 d.reply("/free", "sb", "EffectMgr", sizeof(EffectMgr*), &eff_); 255 }}, 256 rSubtype(Alienwah), 257 rSubtype(Chorus), 258 rSubtype(Distortion), 259 rSubtype(DynamicFilter), 260 rSubtype(Echo), 261 rSubtype(EQ), 262 rSubtype(Phaser), 263 rSubtype(Reverb), 264 rSubtype(Sympathetic), 265 rSubtype(Reverse), 266 }; 267 268 const rtosc::Ports &EffectMgr::ports = local_ports; 269 270 EffectMgr::EffectMgr(Allocator &alloc, const SYNTH_T &synth_, 271 const bool insertion_, const AbsTime *time_, Sync *sync_) 272 :insertion(insertion_), 273 efxoutl(new float[synth_.buffersize]), 274 efxoutr(new float[synth_.buffersize]), 275 filterpars(new FilterParams(in_effect, time_)), 276 nefx(0), 277 efx(NULL), 278 time(time_), 279 sync(sync_), 280 numerator(0), 281 denominator(4), 282 dryonly(false), 283 memory(alloc), 284 synth(synth_) 285 { 286 setpresettype("Peffect"); 287 memset(efxoutl, 0, synth.bufferbytes); 288 memset(efxoutr, 0, synth.bufferbytes); 289 memset(settings, 255, sizeof(settings)); 290 defaults(); 291 } 292 293 294 EffectMgr::~EffectMgr() 295 { 296 if(sync) sync->detach(efx); 297 memory.dealloc(efx); 298 delete filterpars; 299 delete [] efxoutl; 300 delete [] efxoutr; 301 } 302 303 void EffectMgr::defaults(void) 304 { 305 changeeffect(0); 306 setdryonly(false); 307 } 308 309 //Change the effect 310 void EffectMgr::changeeffectrt(int _nefx, bool avoidSmash) 311 { 312 cleanup(); 313 if(nefx == _nefx && efx != NULL) 314 return; 315 nefx = _nefx; 316 preset = 0; 317 memset(efxoutl, 0, synth.bufferbytes); 318 memset(efxoutr, 0, synth.bufferbytes); 319 memory.dealloc(efx); 320 321 int new_loc = (_nefx == 8) ? dynfilter_0 : in_effect; 322 if(new_loc != filterpars->loc) 323 filterpars->updateLoc(new_loc); 324 EffectParams pars(memory, insertion, efxoutl, efxoutr, 0, 325 synth.samplerate, synth.buffersize, filterpars, avoidSmash); 326 327 try { 328 switch (nefx) { 329 case 1: 330 efx = memory.alloc<Reverb>(pars); 331 break; 332 case 2: 333 efx = memory.alloc<Echo>(pars); 334 break; 335 case 3: 336 efx = memory.alloc<Chorus>(pars); 337 break; 338 case 4: 339 efx = memory.alloc<Phaser>(pars); 340 break; 341 case 5: 342 efx = memory.alloc<Alienwah>(pars); 343 break; 344 case 6: 345 efx = memory.alloc<Distortion>(pars); 346 break; 347 case 7: 348 efx = memory.alloc<EQ>(pars); 349 break; 350 case 8: 351 efx = memory.alloc<DynamicFilter>(pars); 352 break; 353 case 9: 354 efx = memory.alloc<Sympathetic>(pars); 355 break; 356 case 10: 357 efx = memory.alloc<Reverse>(pars, time); 358 if(sync) sync->attach(efx); 359 break; 360 //put more effect here 361 default: 362 efx = NULL; 363 break; //no effect (thru) 364 } 365 366 // set freq / delay params according to bpm ratio 367 int Pdelay, Pfreq; 368 float freq; 369 if (numerator>0) { 370 switch(nefx) { 371 case 2: // Echo 372 case 10:// Reverse 373 // invert: 374 // delay = (Pdelay / 127.0f * 1.5f); //0 .. 1.5 sec 375 Pdelay = (int)roundf((20320.0f / (float)time->tempo) * 376 ((float)numerator / (float)denominator)); 377 if (numerator&&denominator) 378 seteffectparrt(2, Pdelay); 379 break; 380 case 3: // Chorus 381 case 4: // Phaser 382 case 5: // Alienwah 383 case 8: // DynamicFilter 384 freq = ((float)time->tempo * 385 (float)denominator / 386 (240.0f * (float)numerator)); 387 // invert: 388 // (powf(2.0f, Pfreq / 127.0f * 10.0f) - 1.0f) * 0.03f 389 Pfreq = (int)roundf(logf((freq/0.03f)+1.0f)/LOG_2 * 12.7f); 390 if (numerator&&denominator) 391 seteffectparrt(2, Pfreq); 392 break; 393 case 1: // Reverb 394 case 6: // Distortion 395 case 7: // EQ 396 default: 397 break; 398 } 399 } 400 401 } catch (std::bad_alloc &ba) { 402 std::cerr << "failed to change effect " << _nefx << ": " << ba.what() << std::endl; 403 return; 404 } 405 406 if(!avoidSmash) 407 for(int i = 0; i != 128; i++) 408 settings[i] = geteffectparrt(i); 409 } 410 411 void EffectMgr::changeeffect(int _nefx) 412 { 413 nefx = _nefx; 414 //preset = 0; 415 } 416 417 //Obtain the effect number 418 int EffectMgr::geteffect(void) 419 { 420 return nefx; 421 } 422 423 void EffectMgr::changesettingsrt(const short int *p_value) 424 { 425 for(int i = 0; i != 128; i++) { 426 short int value = p_value[i]; 427 /* check if setting is missing */ 428 if(value == -1) { 429 if(efx) 430 value = efx->getpresetpar(preset, i); 431 else 432 value = 0; 433 } 434 /* update settings */ 435 seteffectparrt(i, value); 436 } 437 } 438 439 // Initialize An Effect in RT context 440 void EffectMgr::init(void) 441 { 442 kill(); 443 changeeffectrt(nefx, true); 444 changepresetrt(preset, true); 445 changesettingsrt(settings); 446 } 447 448 //Strip effect manager of it's realtime memory 449 void EffectMgr::kill(void) 450 { 451 //printf("Killing Effect(%d)\n", nefx); 452 if(sync) sync->detach(efx); 453 memory.dealloc(efx); 454 } 455 456 // Cleanup the current effect 457 void EffectMgr::cleanup(void) 458 { 459 if(efx) 460 efx->cleanup(); 461 } 462 463 464 // Get the preset of the current effect 465 unsigned char EffectMgr::getpreset(void) 466 { 467 if(efx) 468 return efx->Ppreset; 469 else 470 return 0; 471 } 472 473 // Change the preset of the current effect 474 void EffectMgr::changepreset(unsigned char npreset) 475 { 476 preset = npreset; 477 } 478 479 // Change the preset of the current effect 480 void EffectMgr::changepresetrt(unsigned char npreset, bool avoidSmash) 481 { 482 preset = npreset; 483 if(avoidSmash && dynamic_cast<DynamicFilter*>(efx)) { 484 efx->Ppreset = npreset; 485 return; 486 } 487 if(efx) 488 efx->setpreset(npreset); 489 if(!avoidSmash) 490 for(int i = 0; i != 128; i++) 491 settings[i] = geteffectparrt(i); 492 } 493 494 //Change a parameter of the current effect 495 void EffectMgr::seteffectparrt(int npar, unsigned char value) 496 { 497 if(npar < 0 || npar >= 128) 498 return; 499 settings[npar] = value; 500 501 if(!efx) 502 return; 503 try { 504 efx->changepar(npar, value); 505 } catch (std::bad_alloc &ba) { 506 std::cerr << "failed to change effect parameter " << npar << " to " << value << ": " << ba.what() << std::endl; 507 } 508 } 509 510 unsigned char EffectMgr::geteffectparrt(int npar) 511 { 512 if(!efx) 513 return 0; 514 return efx->getpar(npar); 515 } 516 517 // Apply the effect 518 void EffectMgr::out(float *smpsl, float *smpsr) 519 { 520 if(!efx) { 521 if(!insertion) 522 for(int i = 0; i < synth.buffersize; ++i) { 523 smpsl[i] = 0.0f; 524 smpsr[i] = 0.0f; 525 efxoutl[i] = 0.0f; 526 efxoutr[i] = 0.0f; 527 } 528 return; 529 } 530 for(int i = 0; i < synth.buffersize; ++i) { 531 smpsl[i] += synth.denormalkillbuf[i]; 532 smpsr[i] += synth.denormalkillbuf[i]; 533 efxoutl[i] = 0.0f; 534 efxoutr[i] = 0.0f; 535 } 536 efx->out(smpsl, smpsr); 537 538 float volume = efx->volume; 539 540 if(nefx == 7) { //this is need only for the EQ effect 541 memcpy(smpsl, efxoutl, synth.bufferbytes); 542 memcpy(smpsr, efxoutr, synth.bufferbytes); 543 return; 544 } 545 546 //Insertion effect 547 if(insertion != 0) { 548 float v1, v2; 549 if(volume < 0.5f) { 550 v1 = 1.0f; 551 v2 = volume * 2.0f; 552 } 553 else { 554 v1 = (1.0f - volume) * 2.0f; 555 v2 = 1.0f; 556 } 557 if((nefx == 1) || (nefx == 2)) 558 v2 *= v2; //for Reverb and Echo, the wet function is not liniar 559 560 if(dryonly) //this is used for instrument effect only 561 for(int i = 0; i < synth.buffersize; ++i) { 562 smpsl[i] *= v1; 563 smpsr[i] *= v1; 564 efxoutl[i] *= v2; 565 efxoutr[i] *= v2; 566 } 567 else // normal instrument/insertion effect 568 for(int i = 0; i < synth.buffersize; ++i) { 569 smpsl[i] = smpsl[i] * v1 + efxoutl[i] * v2; 570 smpsr[i] = smpsr[i] * v1 + efxoutr[i] * v2; 571 } 572 } 573 else // System effect 574 for(int i = 0; i < synth.buffersize; ++i) { 575 efxoutl[i] *= 2.0f * volume; 576 efxoutr[i] *= 2.0f * volume; 577 smpsl[i] = efxoutl[i]; 578 smpsr[i] = efxoutr[i]; 579 } 580 } 581 582 583 // Get the effect volume for the system effect 584 float EffectMgr::sysefxgetvolume(void) 585 { 586 return efx ? efx->outvolume : 1.0f; 587 } 588 589 590 // Get the EQ response 591 float EffectMgr::getEQfreqresponse(float freq) 592 { 593 return (nefx == 7) ? efx->getfreqresponse(freq) : 0.0f; 594 } 595 596 597 void EffectMgr::setdryonly(bool value) 598 { 599 dryonly = value; 600 } 601 602 void EffectMgr::paste(EffectMgr &e) 603 { 604 changeeffectrt(e.nefx, true); 605 changepresetrt(e.preset, true); 606 changesettingsrt(e.settings); 607 if(dynamic_cast<DynamicFilter*>(efx)) { 608 std::swap(filterpars, e.filterpars); 609 efx->filterpars = filterpars; 610 } 611 cleanup(); // cleanup the effect and recompute its parameters 612 } 613 614 void EffectMgr::add2XML(XMLwrapper& xml) 615 { 616 xml.addpar("type", geteffect()); 617 618 if(!geteffect()) 619 return; 620 xml.addpar("preset", preset); 621 622 xml.beginbranch("EFFECT_PARAMETERS"); 623 for(int n = 0; n != 128; n++) { 624 int par; 625 int def; 626 if(efx) { 627 par = efx->getpar(n); 628 def = efx->getpresetpar(preset, n); 629 } else { 630 par = settings[n]; 631 def = -1; 632 } 633 /* don't store default values */ 634 if(par == def) 635 continue; 636 xml.beginbranch("par_no", n); 637 xml.addpar("par", par); 638 xml.endbranch(); 639 } 640 assert(filterpars); 641 if(nefx == 8) { 642 xml.beginbranch("FILTER"); 643 filterpars->add2XML(xml); 644 xml.endbranch(); 645 } 646 xml.endbranch(); 647 xml.addpar("numerator", numerator); 648 xml.addpar("denominator", denominator); 649 } 650 651 void EffectMgr::getfromXML(XMLwrapper& xml) 652 { 653 changeeffect(xml.getpar127("type", geteffect())); 654 655 if(!geteffect()) 656 return; 657 658 preset = xml.getpar127("preset", preset); 659 660 if(xml.enterbranch("EFFECT_PARAMETERS")) { 661 for(int n = 0; n != 128; n++) { 662 if(xml.enterbranch("par_no", n) == 0) { 663 /* 664 * XXX workaround for old presets: 665 * 666 * All effect parameters have a default value. 667 * Default values are skipped when storing parameters, 668 * and must appear as the default value when loading. 669 * 670 * Up until recently it was assumed that the default 671 * value of all parameters is zero. This is no longer 672 * true, but when loading old presets we need to 673 * preserve this behaviour! Else sounds may change. 674 */ 675 if (xml.fileversion() < version_type(3,0,6) && 676 /* XXX old presets don't have DC offset */ 677 (geteffect() != 6 || n < 11)) { 678 settings[n] = 0; 679 } else { 680 settings[n] = -1; /* use parameter default */ 681 } 682 } else { 683 settings[n] = xml.getpar127("par", 0); 684 xml.exitbranch(); 685 } 686 } 687 assert(filterpars); 688 if(xml.enterbranch("FILTER")) { 689 filterpars->getfromXML(xml); 690 xml.exitbranch(); 691 } 692 xml.exitbranch(); 693 } 694 numerator = xml.getpar("numerator", numerator, 0, 99); 695 denominator = xml.getpar("denominator", denominator, 1, 99); 696 cleanup(); 697 } 698 699 }