Guitarix
gx_engine_audio.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert
3  * Copyright (C) 2011 Pete Shorthose
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  * --------------------------------------------------------------------------
19  *
20  *
21  * This is the Guitarix Audio Engine
22  *
23  *
24  * --------------------------------------------------------------------------
25  */
26 
27 #include "engine.h" // NOLINT
28 
29 namespace gx_engine {
30 
31 /****************************************************************
32  ** class ProcessingChainBase
33  */
34 
36  sync_sem(),
37  to_release(),
38  ramp_value(0),
39  ramp_mode(ramp_mode_down_dead),
40  stopped(true),
41  steps_up(),
42  steps_up_dead(),
43  steps_down(),
44  modules(),
45  next_commit_needs_ramp() {
46  sem_init(&sync_sem, 0, 0);
47 }
48 
50  steps_down = 8 * (256 * samplerate) / 48000;
52  //steps_down = (64 * samplerate) / 48000;
53  //steps_up = 4 * steps_down;
54  steps_up_dead = 0;
55 }
56 
58  stopped = v;
59  if (v) {
60  post_rt_finished(); // in case someone is already waiting
61  }
62 }
63 
65  if (stopped) {
66  return true;
67  }
68 
69 #ifdef __APPLE__
70  // no timedewait here
71  while (sem_wait(&sync_sem) == -1) {
72 #else
73  timespec ts;
74  clock_gettime(CLOCK_REALTIME, &ts);
75  const long ns_in_sec = 1000000000;
76  ts.tv_nsec += ns_in_sec / 10;
77  if (ts.tv_nsec >= ns_in_sec) {
78  ts.tv_nsec -= ns_in_sec;
79  ts.tv_sec += 1;
80  }
81  while (sem_timedwait(&sync_sem, &ts) == -1) {
82 #endif
83 
84  if (errno == EINTR) {
85  continue;
86  }
87  if (errno == ETIMEDOUT) {
88  gx_print_warning("sem_timedwait", "timeout");
89  return false;
90  }
91  gx_print_error("sem_timedwait", "unknown error");
92  break;
93  }
94  return true;
95 }
96 
98  int val;
99  sem_getvalue(&sync_sem, &val);
100  if (val > 0) {
101  sem_wait(&sync_sem);
102  }
103  assert(sem_getvalue(&sync_sem, &val) == 0 && val == 0);
104 }
105 
107  if (stopped) {
108  return;
109  }
110  while (ramp_mode == ramp_mode_down) {
111  if (!wait_rt_finished()) {
112  break;
113  }
114  }
115 }
116 
118  RampMode rm = get_ramp_mode();
119  if (!stopped) {
120  if (rm != ramp_mode_down_dead && rm != ramp_mode_down) {
121  return;
122  }
123  set_ramp_value(0);
125  }
126 }
127 
129  RampMode rm = get_ramp_mode();
130  if (rm == ramp_mode_down_dead || rm == ramp_mode_down) {
131  return;
132  }
133  int rv = min(steps_down,get_ramp_value());
134  if (rv == 0) {
136  } else {
137  set_ramp_value(rv);
139  }
140 }
141 
142 void __rt_func ProcessingChainBase::try_set_ramp_mode(RampMode oldmode, RampMode newmode, int oldrv, int newrv) {
143  if (oldmode != newmode) {
144  if (!gx_system::atomic_compare_and_exchange(&ramp_mode, oldmode, newmode)) {
145  return;
146  }
147  }
148  if (oldrv != newrv) {
149  if (!gx_system::atomic_compare_and_exchange(&ramp_value, oldrv, newrv)) {
150  return;
151  }
152  }
153 }
154 
155 bool lists_equal(const list<Plugin*>& p1, const list<Plugin*>& p2, bool *need_ramp)
156 {
157  list<Plugin*>::const_iterator i1 = p1.begin();
158  list<Plugin*>::const_iterator i2 = p2.begin();
159  bool ret = true;
160  bool nr = false;
161  while (true) {
162  if (i1 == p1.end()) {
163  if (i2 != p2.end()) {
164  ret = false;
165  nr = true;
166  }
167  break;
168  }
169  if (i2 == p2.end()) {
170  ret = false;
171  nr = true;
172  break;
173  }
174  if (*i1 != *i2) {
175  ret = false;
176  while ((*i1)->get_pdef()->flags & PGN_SNOOP) {
177  ++i1;
178  if (i1 == p1.end()) {
179  break;
180  }
181  }
182  while ((*i2)->get_pdef()->flags & PGN_SNOOP) {
183  ++i2;
184  if (i2 == p2.end()) {
185  break;
186  }
187  }
188  if (*i1 != *i2) {
189  nr = true;
190  break;
191  }
192  }
193  ++i1;
194  ++i2;
195  }
196  if (ret) {
197  nr = false;
198  }
199  *need_ramp = nr;
200  return ret;
201 }
202 
203 bool ProcessingChainBase::set_plugin_list(const list<Plugin*> &p) {
205  return false;
206  }
207  wait_latch();
208  if (check_release()) {
209  release();
210  }
211  typedef set<const char*, stringcomp> pchar_set;
212  pchar_set new_ids;
213  for (list<Plugin*>::const_iterator i = p.begin(); i != p.end(); ++i) {
214  new_ids.insert((*i)->get_pdef()->id);
215  }
216  for (list<Plugin*>::const_iterator i = modules.begin(); i != modules.end(); ++i) {
217  if (!(*i)->get_pdef()->activate_plugin) {
218  continue;
219  }
220  pchar_set::iterator r = new_ids.find((*i)->get_pdef()->id);
221  if (r == new_ids.end()) {
222  to_release.push_back(*i);
223  }
224  }
225  modules = p;
226  return true;
227 }
228 
230  for (list<Plugin*>::const_iterator p = modules.begin(); p != modules.end(); ++p) {
231  PluginDef* pd = (*p)->get_pdef();
232  if (pd->activate_plugin) {
233  pd->activate_plugin(true, pd);
234  } else if (pd->clear_state) {
235  pd->clear_state(pd);
236  }
237  }
238 }
239 
241  wait_latch();
242  for (list<Plugin*>::const_iterator p = to_release.begin(); p != to_release.end(); ++p) {
243  (*p)->get_pdef()->activate_plugin(false, (*p)->get_pdef());
244  }
245  to_release.clear();
246 }
247 
248 #ifndef NDEBUG
249 void ProcessingChainBase::print_chain_state(const char *title) {
250  int val;
251  sem_getvalue(&sync_sem, &val);
252  printf("%s sync_sem = %d, stopped = %d, ramp_mode = %d\n",
253  title, val, stopped, ramp_mode);
254 }
255 #endif
256 
257 
258 /****************************************************************
259  ** MonoModuleChain, StereoModuleChain
260  */
261 
262 void __rt_func MonoModuleChain::process(int count, float *input, float *output) {
263  RampMode rm = get_ramp_mode();
264  if (rm == ramp_mode_down_dead) {
265  memset(output, 0, count*sizeof(float));
266  return;
267  }
268  memcpy(output, input, count*sizeof(float));
269  for (monochain_data *p = get_rt_chain(); p->func; ++p) {
270  p->func(count, output, output, p->plugin);
271  }
272  if (rm == ramp_mode_off) {
273  return;
274  }
275  int rv = get_ramp_value();
276  int rv1 = rv;
277  RampMode rm1 = get_ramp_mode();
278  if (rm != rm1) {
279  // ramp_mode has changed while processing
280  if (rm1 != ramp_mode_up && rm1 != ramp_mode_down) {
281  return;
282  }
283  rv1 = rv = get_ramp_value();
284  // assume ramp_mode doesn't change too fast
285  rm = rm1;
286  }
287  int i = 0;
288  if (rm1 == ramp_mode_up_dead) {
289  for ( ; i < count; ++i) {
290  if (++rv1 > steps_up_dead) {
291  rm1 = ramp_mode_up;
292  rv1 = 0;
293  break;
294  }
295  output[i] = 0.0;
296  }
297  }
298  if (rm1 == ramp_mode_up) {
299  for ( ; i < count; ++i) {
300  if (++rv1 >= steps_up) {
301  rm1 = ramp_mode_off;
302  break;
303  }
304  output[i] = (output[i] * rv1) / steps_up;
305  }
306  }
307  else if (rm1 == ramp_mode_down) {
308  for (i = 0; i < count; ++i) {
309  if (--rv1 == 0) {
310  rm1 = ramp_mode_down_dead;
311  break;
312  }
313  output[i] = (output[i] * rv1) / steps_down;
314  }
315  for ( ; i < count; ++i) {
316  output[i] = 0.0;
317  }
318  }
319  try_set_ramp_mode(rm, rm1, rv, rv1);
320 }
321 
322 void __rt_func StereoModuleChain::process(int count, float *input1, float *input2, float *output1, float *output2) {
323  // run stereo rack
324  RampMode rm = get_ramp_mode();
325  if (rm == ramp_mode_down_dead) {
326  memset(output1, 0, count*sizeof(float));
327  memset(output2, 0, count*sizeof(float));
328  return;
329  }
330  memcpy(output1, input1, count*sizeof(float));
331  memcpy(output2, input2, count*sizeof(float));
332  for (stereochain_data *p = get_rt_chain(); p->func; ++p) {
333  (p->func)(count, output1, output2, output1, output2, p->plugin);
334  }
335  if (rm == ramp_mode_off) {
336  return;
337  }
338  int rv = get_ramp_value();
339  int rv1 = rv;
340  RampMode rm1 = get_ramp_mode();
341  if (rm != rm1) {
342  // ramp_mode has changed while processing
343  if (rm1 != ramp_mode_up && rm1 != ramp_mode_down) {
344  return;
345  }
346  rv1 = rv = get_ramp_value();
347  // assume ramp_mode doesn't change too fast
348  rm = rm1;
349  }
350  int i = 0;
351  if (rm1 == ramp_mode_up_dead) {
352  for ( ; i < count; ++i) {
353  if (++rv1 > steps_up_dead) {
354  rm1 = ramp_mode_up;
355  rv1 = 0;
356  break;
357  }
358  output1[i] = 0.0;
359  output2[i] = 0.0;
360  }
361  }
362  if (rm1 == ramp_mode_up) {
363  for ( ; i < count; ++i) {
364  if (++rv1 >= steps_up) {
365  rm1 = ramp_mode_off;
366  break;
367  }
368  output1[i] = (output1[i] * rv1) / steps_up;
369  output2[i] = (output2[i] * rv1) / steps_up;
370  }
371  }
372  else if (rm1 == ramp_mode_down) {
373  for (i = 0; i < count; ++i) {
374  if (--rv1 == 0) {
375  rm1 = ramp_mode_down_dead;
376  break;
377  }
378  output1[i] = (output1[i] * rv1) / steps_down;
379  output2[i] = (output2[i] * rv1) / steps_down;
380  }
381  for ( ; i < count; ++i) {
382  output1[i] = 0.0;
383  output2[i] = 0.0;
384  }
385  }
386  try_set_ramp_mode(rm, rm1, rv, rv1);
387 }
388 
389 
390 /****************************************************************
391  ** ModuleSelectorFromList
392  */
393 
395  EngineControl& seq_, const char* id_, const char* name_,
396  const char* category_, plugindef_creator plugins[], const char* select_id_,
397  const char* select_name_, uiloader loader, const char** groups_, int flags_)
398  : ModuleSelector(seq_),
399  PluginDef(),
400  selector(0),
401  select_id(select_id_),
402  select_name(select_name_),
403  current_plugin(0),
404  modules(),
405  size(),
406  plugin() {
408  register_params = static_register;
409  plugindef_creator *p = plugins;
410  for (size = 0; *p; ++p, ++size);
411  modules = new PluginDef*[size];
412  for (unsigned int i = 0; i < size; ++i) {
413  modules[i] = plugins[i]();
414  }
415  id = id_;
416  name = name_;
417  category = category_;
418  groups = groups_;
419  flags = flags_;
420  load_ui = loader;
421  plugin = this;
422 }
423 
425  delete[] modules;
426 }
427 
428 int ModuleSelectorFromList::register_parameter(const ParamReg &param) {
429  value_pair *p = new value_pair[size+1];
430  for (unsigned int i = 0; i < size; ++i) {
431  p[i].value_id = modules[i]->id;
432  p[i].value_label = modules[i]->name;
433  }
434  p[size].value_id = 0;
435  p[size].value_label = 0;
436  param.registerIEnumVar(select_id, select_name, "S", "", p, &selector, 0);
437  seq.get_param()[select_id].signal_changed_int().connect(
438  sigc::hide(sigc::mem_fun(seq, &EngineControl::set_rack_changed)));
439  return 0;
440 }
441 
442 int ModuleSelectorFromList::static_register(const ParamReg &param) {
443  return static_cast<ModuleSelectorFromList*>(param.plugin)
444  ->register_parameter(param);
445 }
446 
448  if (plugin.get_on_off()) {
449  Plugin *old = current_plugin;
450  current_plugin = seq.pluginlist.lookup_plugin(modules[selector]->id);
451  if (old && old != current_plugin) {
452  old->set_on_off(false);
453  }
454  current_plugin->set_on_off(true);
455  current_plugin->copy_position(plugin);
456  } else if (current_plugin) {
457  current_plugin->set_on_off(false);
458  current_plugin = 0;
459  }
460 }
461 
462 
463 /****************************************************************
464  ** class EngineControl
465  */
466 
468  : selectors(),
469  rack_changed(),
470  pmap(),
471  policy(),
472  priority(),
473  buffersize_change(),
474  samplerate_change(),
475  buffersize(0),
476  samplerate(0),
477  pluginlist(*this) {
478 }
479 
481 }
482 
484  selectors.push_back(&sel);
485 }
486 
488 {
490 }
491 
492 void EngineControl::get_sched_priority(int &policy_, int &priority_, int prio_dim) {
493  policy_ = policy;
494  priority_ = priority;
495  if (!prio_dim) {
496  return;
497  }
498  int min, max;
499  min = sched_get_priority_min(policy);
500  max = sched_get_priority_max(policy);
501  priority_ = priority - prio_dim;
502  if (priority_ > max) {
503  priority_ = max;
504  }
505  if (priority_ < min) {
506  priority_ = min;
507  }
508 }
509 
510 void EngineControl::set_samplerate(unsigned int samplerate_) {
511  if (samplerate == samplerate_) {
512  return;
513  }
514  samplerate = samplerate_;
517 }
518 
519 void EngineControl::set_buffersize(unsigned int buffersize_) {
520  if (buffersize == buffersize_) {
521  return;
522  }
523  buffersize = buffersize_;
525 }
526 
527 void EngineControl::init(unsigned int samplerate_, unsigned int buffersize_,
528  int policy_, int priority_) {
529  if (policy_ != policy || priority_ != priority) {
530  policy = policy_;
531  priority = priority_;
532  set_buffersize(buffersize_);
533  set_samplerate(samplerate_);
534  return;
535  }
536  if (buffersize_ != buffersize) {
537  set_buffersize(buffersize_);
538  }
539  if (samplerate_ != samplerate) {
540  set_samplerate(samplerate_);
541  }
542 }
543 
545  rack_changed.disconnect();
546 }
547 
549  return rack_changed.connected();
550 }
551 
552 
553 /****************************************************************
554  ** ModuleSequencer
555  */
556 
558  : EngineControl(),
559  audio_mode(PGN_MODE_NORMAL),
560  stateflags_mutex(),
561  stateflags(SF_INITIALIZING),
562  state_change(),
563  overload_detected(),
564  overload_reason(),
565  ov_disabled(0),
566  mono_chain(),
567  stereo_chain() {
568  overload_detected.connect(
569  sigc::mem_fun(this, &ModuleSequencer::check_overload));
570 }
571 
573  start_ramp_down();
576 }
577 
581 }
582 
586 }
587 
591 }
592 
594  if (!get_buffersize() || !get_samplerate()) {
595  return false;
596  }
597  if (prepare_module_lists()) {
599  if (stateflags & SF_OVERLOAD) {
600  // hack: jackd need some time for new load statistic
601  Glib::signal_timeout().connect_once(
602  sigc::bind(
603  sigc::mem_fun(this,&ModuleSequencer::clear_stateflag),
604  SF_OVERLOAD), 1000);
605  }
606  return true;
607  }
608  return false;
609 }
610 
612  mono_chain.set_samplerate(samplerate);
613  stereo_chain.set_samplerate(samplerate);
614  EngineControl::set_samplerate(samplerate);
615 }
616 
618  if (mono_chain.check_release()) {
620  }
621  if (stereo_chain.check_release()) {
623  }
624  if (get_rack_changed()) {
627  }
628  return false;
629 }
630 
632  if (rack_changed.connected()) {
633  return;
634  }
635  rack_changed = Glib::signal_idle().connect(
636  sigc::mem_fun(this, &ModuleSequencer::check_module_lists));
637 }
638 
640  for (list<ModuleSelector*>::iterator i = selectors.begin(); i != selectors.end(); ++i) {
641  (*i)->set_module();
642  }
643  list<Plugin*> modules;
645  bool ret_mono = mono_chain.set_plugin_list(modules);
647  bool ret_stereo = stereo_chain.set_plugin_list(modules);
648  if (ret_mono || ret_stereo) {
649  mono_chain.print();
651  }
652  return ret_mono || ret_stereo;
653 }
654 
657  bool monoramp = mono_chain.next_commit_needs_ramp && !already_down;
658  if (monoramp) {
661  }
664  bool stereoramp = stereo_chain.next_commit_needs_ramp && !already_down;
665  if (stereoramp) {
668  }
670  if (monoramp) {
673  }
674  if (stereoramp) {
677  }
678 }
679 
681 
682 void __rt_func ModuleSequencer::overload(OverloadType tp, const char *reason) {
683  if (!(audio_mode & PGN_MODE_NORMAL)) {
684  return; // no overload message in mute/bypass modes
685  }
686  if ((tp & ov_disabled) == ov_XRun) {
687  return; // the xrun should show up in the log anyhow
688  }
689  bool ignore = false;
690  if ((tp & ov_disabled) == ov_Convolver) {
691  ignore = true;
692  }
693  if (sporadic_interval > 0 && !ignore && (tp & (ov_Convolver|ov_XRun))) {
694  static float last = -sporadic_interval;
695  timespec ts;
696  clock_gettime(CLOCK_MONOTONIC, &ts);
697  float now = ts.tv_sec + ts.tv_nsec * 1e-9;
698  if (now - last < sporadic_interval) { // max. 1 event every sporadic_interval seconds
699  last = now;
700  ignore = true;
701  }
702  }
703  if (!ignore) {
705  }
708 }
709 
711  if (stateflags & flag) {
712  return;
713  }
714  boost::mutex::scoped_lock lock(stateflags_mutex);
715  mono_chain.set_stopped(true);
717  if (!stateflags) {
718  set_down_dead();
719  }
720  stateflags |= flag;
721 }
722 
724  if (!(stateflags & flag)) {
725  return;
726  }
727  boost::mutex::scoped_lock lock(stateflags_mutex);
728  stateflags &= ~flag;
729  if (!stateflags) {
730  mono_chain.set_stopped(false);
731  stereo_chain.set_stopped(false);
732  start_ramp_up();
733  }
734 }
735 
737  if (stateflags & SF_OVERLOAD) {
741  "watchdog",
742  boost::format(_("Overload (%s)")) % gx_system::atomic_get(overload_reason));
743  } else {
745  "watchdog",
746  boost::format(_("Overload ignored (%s)")) % gx_system::atomic_get(overload_reason));
747  }
748 }
749 
751  int newmode = PGN_MODE_MUTE;
752  switch( state ) {
753  case kEngineOn: newmode = PGN_MODE_NORMAL; break;
754  case kEngineBypass: newmode = PGN_MODE_BYPASS; break;
755  case kEngineOff: newmode = PGN_MODE_MUTE; break;
756  }
757  if (audio_mode == newmode) {
758  return;
759  }
760  audio_mode = newmode;
762  state_change(state);
763 }
764 
765 #ifndef NDEBUG
767  printf("stateflags = %d, audio_mode = %d\n", stateflags, audio_mode);
768  mono_chain.print_chain_state("mono :");
769  stereo_chain.print_chain_state("stereo:");
770 }
771 #endif
772 
774  if (audio_mode & PGN_MODE_NORMAL) {
775  return kEngineOn;
776  } else if (audio_mode & PGN_MODE_BYPASS) {
777  return kEngineBypass;
778  } else if (audio_mode & PGN_MODE_MUTE) {
779  return kEngineOff;
780  } else {
781  assert(false);
782  return kEngineOff;
783  }
784 }
785 
786 } // end namespace gx_engine
void set_samplerate(int samplerate)
void process(int count, float *input1, float *input2, float *output1, float *output2)
void print_chain_state(const char *title)
void get_sched_priority(int &policy, int &priority, int prio_dim=0)
clearstatefunc clear_state
Definition: gx_plugin.h:204
bool set_plugin_list(const list< Plugin *> &p)
void registerParameter(ParameterGroups &groups)
virtual void wait_ramp_down_finished()
void set_buffersize(unsigned int buffersize_)
int(* uiloader)(const UiBuilder &builder, int format)
Definition: gx_plugin.h:158
const char * value_id
Definition: gx_plugin.h:118
PluginDef * plugin
Definition: gx_plugin.h:123
const char * name
Definition: gx_plugin.h:188
bool lists_equal(const list< Plugin *> &p1, const list< Plugin *> &p2, bool *need_ramp)
void init(unsigned int samplerate, unsigned int buffersize, int policy, int priority)
#define __rt_func
Definition: gx_compiler.h:7
bool get_on_off() const
sigc::signal< void, GxEngineState > state_change
virtual void set_samplerate(unsigned int samplerate)
const char ** groups
Definition: gx_plugin.h:189
int atomic_get(volatile int &p)
Definition: gx_system.h:98
void process(int count, float *input, float *output)
list< ModuleSelector * > selectors
const char * category
Definition: gx_plugin.h:192
sigc::signal< void, unsigned int > samplerate_change
void(* registerIEnumVar)(const char *id, const char *name, const char *tp, const char *tooltip, const value_pair *values, int *var, int val)
Definition: gx_plugin.h:138
void gx_print_error(const char *, const std::string &)
Definition: gx_logging.cpp:166
void copy_position(const Plugin &plugin)
sigc::signal< void, unsigned int > buffersize_change
#define PLUGINDEF_VERSION
Definition: gx_plugin.h:181
registerfunc register_params
Definition: gx_plugin.h:202
#define min(x, y)
void add_selector(ModuleSelector &sel)
const char * id
Definition: gx_plugin.h:187
void try_set_ramp_mode(RampMode oldmode, RampMode newmode, int oldrv, int newrv)
virtual void overload(OverloadType tp, const char *reason)
#define max(x, y)
const char * value_label
Definition: gx_plugin.h:119
int flags
Definition: gx_plugin.h:185
Plugin * lookup_plugin(const std::string &id) const
PluginDef *(* plugindef_creator)()
Glib::Dispatcher overload_detected
void set_state(GxEngineState state)
void set_samplerate(int samplerate)
void set_samplerate(unsigned int samplerate_)
void gx_print_warning(const char *, const std::string &)
Definition: gx_logging.cpp:161
activatefunc activate_plugin
Definition: gx_plugin.h:201
void atomic_set(volatile int *p, int v)
Definition: gx_system.h:90
ModuleSelectorFromList(EngineControl &seq, const char *id, const char *name, const char *category, plugindef_creator module_ids[], const char *select_id, const char *select_name, uiloader loader, const char **groups=0, int flags=0)
void set_stateflag(StateFlag flag)
bool atomic_compare_and_exchange(volatile int *p, int oldv, int newv)
Definition: gx_system.h:114
virtual void set_rack_changed()=0
void set_on_off(bool v) const
int flag
Definition: ladspaback.cpp:56
void clear_stateflag(StateFlag flag)
void ordered_mono_list(list< Plugin *> &mono, int mode)
void ordered_stereo_list(list< Plugin *> &stereo, int mode)
void registerAllPlugins(ParamMap &param, ParameterGroups &groups)
void commit(bool clear, ParamMap &pmap)
int version
Definition: gx_plugin.h:184
uiloader load_ui
Definition: gx_plugin.h:203