You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

499 lines
15KB

  1. #include "dBiz.hpp"
  2. #include "dsp/decimator.hpp"
  3. #include "dsp/filter.hpp"
  4. namespace rack_plugin_dBiz {
  5. extern float sawTable[2048];
  6. extern float triTable[2048];
  7. template <int OVERSAMPLE, int QUALITY>
  8. struct VoltageControlledOscillator
  9. {
  10. bool analog = false;
  11. bool soft = false;
  12. float lastSyncValue = 0.0f;
  13. float phase = 0.0f;
  14. float freq;
  15. float pw = 0.5f;
  16. float pitch;
  17. bool syncEnabled = false;
  18. bool syncDirection = false;
  19. Decimator<OVERSAMPLE, QUALITY> sinDecimator;
  20. Decimator<OVERSAMPLE, QUALITY> triDecimator;
  21. Decimator<OVERSAMPLE, QUALITY> sawDecimator;
  22. Decimator<OVERSAMPLE, QUALITY> sqrDecimator;
  23. RCFilter sqrFilter;
  24. // For analog detuning effect
  25. float pitchSlew = 0.0f;
  26. int pitchSlewIndex = 0;
  27. float sinBuffer[OVERSAMPLE] = {};
  28. float triBuffer[OVERSAMPLE] = {};
  29. float sawBuffer[OVERSAMPLE] = {};
  30. float sqrBuffer[OVERSAMPLE] = {};
  31. void setPitch(float pitchKnob, float pitchCv)
  32. {
  33. // Compute frequency
  34. pitch = pitchKnob;
  35. if (analog)
  36. {
  37. // Apply pitch slew
  38. const float pitchSlewAmount = 3.0f;
  39. pitch += pitchSlew * pitchSlewAmount;
  40. }
  41. else
  42. {
  43. // Quantize coarse knob if digital mode
  44. pitch = roundf(pitch);
  45. }
  46. pitch += pitchCv;
  47. // Note C4
  48. freq = 261.626f * powf(2.0f, pitch / 12.0f);
  49. }
  50. void setPulseWidth(float pulseWidth)
  51. {
  52. const float pwMin = 0.01f;
  53. pw = clamp(pulseWidth, pwMin, 1.0f - pwMin);
  54. }
  55. void process(float deltaTime, float syncValue)
  56. {
  57. if (analog)
  58. {
  59. // Adjust pitch slew
  60. if (++pitchSlewIndex > 32)
  61. {
  62. const float pitchSlewTau = 100.0f; // Time constant for leaky integrator in seconds
  63. pitchSlew += (randomNormal() - pitchSlew / pitchSlewTau) * engineGetSampleTime();
  64. pitchSlewIndex = 0;
  65. }
  66. }
  67. // Advance phase
  68. float deltaPhase = clamp(freq * deltaTime, 1e-6, 0.5f);
  69. // Detect sync
  70. int syncIndex = -1; // Index in the oversample loop where sync occurs [0, OVERSAMPLE)
  71. float syncCrossing = 0.0f; // Offset that sync occurs [0.0f, 1.0f)
  72. if (syncEnabled)
  73. {
  74. syncValue -= 0.01f;
  75. if (syncValue > 0.0f && lastSyncValue <= 0.0f)
  76. {
  77. float deltaSync = syncValue - lastSyncValue;
  78. syncCrossing = 1.0f - syncValue / deltaSync;
  79. syncCrossing *= OVERSAMPLE;
  80. syncIndex = (int)syncCrossing;
  81. syncCrossing -= syncIndex;
  82. }
  83. lastSyncValue = syncValue;
  84. }
  85. if (syncDirection)
  86. deltaPhase *= -1.0f;
  87. sqrFilter.setCutoff(40.0f * deltaTime);
  88. for (int i = 0; i < OVERSAMPLE; i++)
  89. {
  90. if (syncIndex == i)
  91. {
  92. if (soft)
  93. {
  94. syncDirection = !syncDirection;
  95. deltaPhase *= -1.0f;
  96. }
  97. else
  98. {
  99. // phase = syncCrossing * deltaPhase / OVERSAMPLE;
  100. phase = 0.0f;
  101. }
  102. }
  103. if (analog)
  104. {
  105. // Quadratic approximation of sine, slightly richer harmonics
  106. if (phase < 0.5f)
  107. sinBuffer[i] = 1.f - 16.f * powf(phase - 0.25f, 2);
  108. else
  109. sinBuffer[i] = -1.f + 16.f * powf(phase - 0.75f, 2);
  110. sinBuffer[i] *= 1.08f;
  111. }
  112. else
  113. {
  114. sinBuffer[i] = sinf(2.f * M_PI * phase);
  115. }
  116. if (analog)
  117. {
  118. triBuffer[i] = 1.25f * interpolateLinear(triTable, phase * 2047.f);
  119. }
  120. else
  121. {
  122. if (phase < 0.25f)
  123. triBuffer[i] = 4.f * phase;
  124. else if (phase < 0.75f)
  125. triBuffer[i] = 2.f - 4.f * phase;
  126. else
  127. triBuffer[i] = -4.f + 4.f * phase;
  128. }
  129. if (analog)
  130. {
  131. sawBuffer[i] = 1.66f * interpolateLinear(sawTable, phase * 2047.f);
  132. }
  133. else
  134. {
  135. if (phase < 0.5f)
  136. sawBuffer[i] = 2.f * phase;
  137. else
  138. sawBuffer[i] = -2.f + 2.f * phase;
  139. }
  140. sqrBuffer[i] = (phase < pw) ? 1.f : -1.f;
  141. if (analog)
  142. {
  143. // Simply filter here
  144. sqrFilter.process(sqrBuffer[i]);
  145. sqrBuffer[i] = 0.71f * sqrFilter.highpass();
  146. }
  147. // Advance phase
  148. phase += deltaPhase / OVERSAMPLE;
  149. phase = eucmod(phase, 1.0f);
  150. }
  151. }
  152. float sin()
  153. {
  154. return sinDecimator.process(sinBuffer);
  155. }
  156. float tri()
  157. {
  158. return triDecimator.process(triBuffer);
  159. }
  160. float saw()
  161. {
  162. return sawDecimator.process(sawBuffer);
  163. }
  164. float sqr()
  165. {
  166. return sqrDecimator.process(sqrBuffer);
  167. }
  168. float light()
  169. {
  170. return sinf(2 * M_PI * phase);
  171. }
  172. };
  173. struct TROSC : Module
  174. {
  175. enum ParamIds
  176. {
  177. LINK_A_PARAM,
  178. LINK_B_PARAM,
  179. MODE_A_PARAM,
  180. SYNC_A_PARAM,
  181. MODE_B_PARAM,
  182. SYNC_B_PARAM,
  183. MODE_C_PARAM,
  184. SYNC_C_PARAM,
  185. WAVE_A_SEL_PARAM,
  186. WAVE_B_SEL_PARAM,
  187. WAVE_C_SEL_PARAM,
  188. FREQ_A_PARAM,
  189. FINE_A_PARAM,
  190. FREQ_B_PARAM,
  191. FINE_B_PARAM,
  192. FREQ_C_PARAM,
  193. FINE_C_PARAM,
  194. FM_A_PARAM,
  195. FM_B_PARAM,
  196. FM_C_PARAM,
  197. LEVEL_A_PARAM,
  198. LEVEL_B_PARAM,
  199. LEVEL_C_PARAM,
  200. WAVE_A_MIX,
  201. WAVE2_A_MIX,
  202. WAVE_B_MIX,
  203. WAVE2_B_MIX,
  204. WAVE_C_MIX,
  205. C_WIDTH_PARAM,
  206. NUM_PARAMS
  207. };
  208. enum InputIds
  209. {
  210. PITCH_A_INPUT,
  211. PITCH_B_INPUT,
  212. PITCH_C_INPUT,
  213. SYNC_A_INPUT,
  214. SYNC_B_INPUT,
  215. SYNC_C_INPUT,
  216. FM_A_INPUT,
  217. FM_B_INPUT,
  218. FM_C_INPUT,
  219. A_WAVE_MIX_INPUT,
  220. B_WAVE_MIX_INPUT,
  221. C_WAVE_MIX_INPUT,
  222. A_VOL_IN,
  223. B_VOL_IN,
  224. C_VOL_IN,
  225. C_WIDTH_INPUT,
  226. NUM_INPUTS
  227. };
  228. enum OutputIds
  229. {
  230. A_OUTPUT,
  231. B_OUTPUT,
  232. C_OUTPUT,
  233. MIX_OUTPUT,
  234. NUM_OUTPUTS
  235. };
  236. enum LightIds
  237. {
  238. NUM_LIGHTS
  239. };
  240. VoltageControlledOscillator<8, 8> a_osc;
  241. VoltageControlledOscillator<8, 8> b_osc;
  242. VoltageControlledOscillator<8, 8> c_osc;
  243. TROSC() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  244. void step() override;
  245. // For more advanced Module features, read Rack's engine.hpp header file
  246. // - toJson, fromJson: serialization of internal data
  247. // - onSampleRateChange: event triggered by a change of sample rate
  248. // - onReset, onRandomize, onCreate, onDelete: implements special behavior when user clicks these from the context menu
  249. };
  250. void TROSC::step() {
  251. float a_pitchCv = 0.0;
  252. float b_pitchCv = 0.0;
  253. float c_pitchCv = 0.0;
  254. a_osc.analog = params[MODE_A_PARAM].value > 0.0f;
  255. a_osc.soft = params[SYNC_A_PARAM].value <= 0.0f;
  256. b_osc.analog = params[MODE_B_PARAM].value > 0.0f;
  257. b_osc.soft = params[SYNC_B_PARAM].value <= 0.0f;
  258. c_osc.analog = params[MODE_C_PARAM].value > 0.0f;
  259. c_osc.soft = params[SYNC_C_PARAM].value <= 0.0f;
  260. float a_pitchFine = 3.0f * quadraticBipolar(params[FINE_A_PARAM].value);
  261. a_pitchCv = 12.0f * inputs[PITCH_A_INPUT].value;
  262. float b_pitchFine = 3.0f * quadraticBipolar(params[FINE_B_PARAM].value);
  263. if(params[LINK_A_PARAM].value==1)
  264. b_pitchCv = 12.0f * inputs[PITCH_B_INPUT].value;
  265. else
  266. b_pitchCv = a_pitchCv ;
  267. float c_pitchFine = 3.0f * quadraticBipolar(params[FINE_C_PARAM].value);
  268. if (params[LINK_B_PARAM].value == 1)
  269. c_pitchCv = 12.0f * inputs[PITCH_C_INPUT].value;
  270. else
  271. c_pitchCv = b_pitchCv;
  272. if (inputs[FM_A_INPUT].active)
  273. {
  274. a_pitchCv += quadraticBipolar(params[FM_A_PARAM].value) * 12.0f * inputs[FM_A_INPUT].value;
  275. }
  276. a_osc.setPitch(params[FREQ_A_PARAM].value, a_pitchFine + a_pitchCv);
  277. a_osc.syncEnabled = inputs[SYNC_A_INPUT].active;
  278. if (inputs[FM_B_INPUT].active)
  279. {
  280. b_pitchCv += quadraticBipolar(params[FM_B_PARAM].value) * 12.0f * inputs[FM_B_INPUT].value;
  281. }
  282. b_osc.setPitch(params[FREQ_B_PARAM].value, b_pitchFine + b_pitchCv);
  283. b_osc.syncEnabled = inputs[SYNC_B_INPUT].active;
  284. if (inputs[FM_C_INPUT].active)
  285. {
  286. c_pitchCv += quadraticBipolar(params[FM_C_PARAM].value) * 12.0f * inputs[FM_C_INPUT].value;
  287. }
  288. c_osc.setPitch(params[FREQ_C_PARAM].value, c_pitchFine + c_pitchCv);
  289. c_osc.setPulseWidth(0.5+params[C_WIDTH_PARAM].value * inputs[C_WIDTH_INPUT].value / 10.0f);
  290. c_osc.syncEnabled = inputs[SYNC_C_INPUT].active;
  291. a_osc.process(engineGetSampleTime(), inputs[SYNC_A_INPUT].value);
  292. b_osc.process(engineGetSampleTime(), inputs[SYNC_A_INPUT].value);
  293. c_osc.process(engineGetSampleTime(), inputs[SYNC_A_INPUT].value);
  294. // Set output
  295. float wave_a = clamp(params[WAVE_A_MIX].value, 0.0f, 1.0f);
  296. float wave2_a = clamp(params[WAVE2_A_MIX].value, 0.0f, 1.0f);
  297. float mix_a = clamp(params[WAVE_A_SEL_PARAM].value, 0.0f, 1.0f)*clamp(inputs[A_WAVE_MIX_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f);
  298. float wave_b = clamp(params[WAVE_B_MIX].value, 0.0f, 1.0f);
  299. float wave2_b = clamp(params[WAVE2_B_MIX].value, 0.0f, 1.0f);
  300. float mix_b = clamp(params[WAVE_B_SEL_PARAM].value, 0.0f, 1.0f)*clamp(inputs[B_WAVE_MIX_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f);
  301. float wave_c = clamp(params[WAVE_C_MIX].value, 0.0f, 1.0f);
  302. float mix_c = clamp(params[WAVE_C_SEL_PARAM].value, 0.0f, 1.0f)*clamp(inputs[C_WAVE_MIX_INPUT].normalize(10.0f) / 10.0f, 0.0f, 1.0f);
  303. float out_a;
  304. float out2_a;
  305. float a_out;
  306. float out_b;
  307. float out2_b;
  308. float b_out;
  309. float out_c;
  310. float out2_c;
  311. float c_out;
  312. float mixa,mixb,mixc;
  313. out_a = crossfade(a_osc.sin(), a_osc.tri(), wave_a);
  314. out2_a = crossfade(a_osc.saw(), a_osc.sqr(), wave2_a);
  315. a_out = crossfade(out_a, out2_a, mix_a);
  316. out_b = crossfade(b_osc.sin(), b_osc.tri(), wave_b);
  317. out2_b = crossfade(b_osc.saw(), b_osc.sqr(), wave2_b);
  318. b_out = crossfade(out_b, out2_b, mix_b);
  319. out_c = crossfade(c_osc.sin(), c_osc.tri(), wave_c);
  320. out2_c =c_osc.sqr();
  321. c_out = crossfade(out_c, out2_c, mix_c);
  322. mixa = 2.0f * (a_out)*params[LEVEL_A_PARAM].value*clamp(inputs[A_VOL_IN].normalize(10.0f) / 10.0f, 0.0f, 1.0f);
  323. outputs[A_OUTPUT].value= mixa;
  324. mixb = 2.0f * (b_out)*params[LEVEL_B_PARAM].value*clamp(inputs[B_VOL_IN].normalize(10.0f) / 10.0f, 0.0f, 1.0f);
  325. outputs[B_OUTPUT].value = mixb;
  326. mixc = 2.0f * (c_out)*params[LEVEL_C_PARAM].value*clamp(inputs[C_VOL_IN].normalize(10.0f) / 10.0f, 0.0f, 1.0f);
  327. outputs[C_OUTPUT].value = mixc;
  328. outputs[MIX_OUTPUT].value = mixa+mixb+mixc;
  329. }
  330. struct TROSCWidget : ModuleWidget {
  331. TROSCWidget(TROSC *module) : ModuleWidget(module) {
  332. setPanel(SVG::load(assetPlugin(plugin, "res/TROSC.svg")));
  333. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, 0)));
  334. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  335. addChild(Widget::create<ScrewSilver>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  336. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  337. int space = 170;
  338. int vspace = 50;
  339. addParam(ParamWidget::create<VerboDL>(Vec(30,20), module, TROSC::FREQ_A_PARAM,-54.0f, 54.0f, 0.0f));
  340. addParam(ParamWidget::create<VerboDL>(Vec(30, 150), module, TROSC::FREQ_B_PARAM, -54.0f, 54.0f, 0.0f));
  341. addParam(ParamWidget::create<VerboDL>(Vec(30, 280), module, TROSC::FREQ_C_PARAM, -54.0f, 54.0f, 0.0f));
  342. addParam(ParamWidget::create<CKSS>(Vec(5, 5 + 20), module, TROSC::MODE_A_PARAM, 0.0, 1.0, 0.0));
  343. addParam(ParamWidget::create<CKSS>(Vec(5, 5 + 150), module, TROSC::MODE_B_PARAM, 0.0, 1.0, 0.0));
  344. addParam(ParamWidget::create<CKSS>(Vec(5, 5 + 280), module, TROSC::MODE_C_PARAM, 0.0, 1.0, 0.0));
  345. addParam(ParamWidget::create<CKSS>(Vec(143, 75 + 20), module, TROSC::SYNC_A_PARAM, 0.0, 1.0, 0.0));
  346. addParam(ParamWidget::create<CKSS>(Vec(143, 75 + 150), module, TROSC::SYNC_B_PARAM, 0.0, 1.0, 0.0));
  347. addParam(ParamWidget::create<CKSS>(Vec(143, 75 + 280), module, TROSC::SYNC_C_PARAM, 0.0, 1.0, 0.0));
  348. addParam(ParamWidget::create<VerboDS>(Vec(110, 20), module, TROSC::FINE_A_PARAM, -1.0f, 1.0f, 0.0f));
  349. addParam(ParamWidget::create<VerboDS>(Vec(110, 150), module, TROSC::FINE_B_PARAM, -1.0f, 1.0f, 0.0f));
  350. addParam(ParamWidget::create<VerboDS>(Vec(110, 280), module, TROSC::FINE_C_PARAM, -1.0f, 1.0f, 0.0f));
  351. addParam(ParamWidget::create<VerboDS>(Vec(150, 20 -10), module, TROSC::FM_A_PARAM, 0.0, 1.0, 0.0));
  352. addParam(ParamWidget::create<VerboDS>(Vec(150, 150-10), module, TROSC::FM_B_PARAM, 0.0, 1.0, 0.0));
  353. addParam(ParamWidget::create<VerboDS>(Vec(150, 280-10), module, TROSC::FM_C_PARAM, 0.0, 1.0, 0.0));
  354. addParam(ParamWidget::create<VerboDS>(Vec(250, vspace+20), module, TROSC::LEVEL_A_PARAM, 0.0, 1.0, 0.0));
  355. addParam(ParamWidget::create<VerboDS>(Vec(250, vspace+150), module, TROSC::LEVEL_B_PARAM, 0.0, 1.0, 0.0));
  356. addParam(ParamWidget::create<VerboDS>(Vec(250, vspace+280), module, TROSC::LEVEL_C_PARAM , 0.0, 1.0, 0.0));
  357. addParam(ParamWidget::create<LEDSliderGreen>(Vec(20+space, 20), module, TROSC::WAVE_A_MIX, 0.0, 1.0, 0.0));
  358. addParam(ParamWidget::create<LEDSliderGreen>(Vec(50 + space, 20), module, TROSC::WAVE2_A_MIX, 0.0, 1.0, 0.0));
  359. addParam(ParamWidget::create<LEDSliderGreen>(Vec(20 + space, 150), module, TROSC::WAVE_B_MIX, 0.0, 1.0, 0.0));
  360. addParam(ParamWidget::create<LEDSliderGreen>(Vec(50 + space, 150), module, TROSC::WAVE2_B_MIX, 0.0, 1.0, 0.0));
  361. addParam(ParamWidget::create<LEDSliderGreen>(Vec(20 + space, 280), module, TROSC::WAVE_C_MIX, 0.0, 1.0, 0.0));
  362. addParam(ParamWidget::create<VerboDS>(Vec(40 + space, 290), module, TROSC::C_WIDTH_PARAM, 0.0, 1.0, 0.0));
  363. addParam(ParamWidget::create<Trimpot>(Vec(73 + space, 20 -10), module, TROSC::WAVE_A_SEL_PARAM, 0.0, 1.0, 0.5));
  364. addParam(ParamWidget::create<Trimpot>(Vec(73 + space, 150-10), module, TROSC::WAVE_B_SEL_PARAM, 0.0, 1.0, 0.5));
  365. addParam(ParamWidget::create<Trimpot>(Vec(73 + space, 280-10), module, TROSC::WAVE_C_SEL_PARAM, 0.0, 1.0, 0.5));
  366. addInput(Port::create<PJ301MCPort>(Vec(100 + space,20-13), Port::INPUT, module, TROSC::A_WAVE_MIX_INPUT));
  367. addInput(Port::create<PJ301MCPort>(Vec(100 + space,150-13), Port::INPUT, module, TROSC::B_WAVE_MIX_INPUT));
  368. addInput(Port::create<PJ301MCPort>(Vec(100 + space,280-13), Port::INPUT, module, TROSC::C_WAVE_MIX_INPUT));
  369. addInput(Port::create<PJ301MCPort>(Vec(2, 30 + 20), Port::INPUT, module, TROSC::PITCH_A_INPUT));
  370. addInput(Port::create<PJ301MCPort>(Vec(2, 30 + 150), Port::INPUT, module, TROSC::PITCH_B_INPUT));
  371. addInput(Port::create<PJ301MCPort>(Vec(2, 30 + 280), Port::INPUT, module, TROSC::PITCH_C_INPUT));
  372. addParam(ParamWidget::create<SilverSwitch>(Vec(60, 90 + 20), module, TROSC::LINK_A_PARAM,0.0,1.0,0.0));
  373. addParam(ParamWidget::create<SilverSwitch>(Vec(60, 90 + 150),module, TROSC::LINK_B_PARAM,0.0,1.0,0.0));
  374. addInput(Port::create<PJ301MOrPort>(Vec(115, 55 + 20), Port::INPUT, module, TROSC::SYNC_A_INPUT));
  375. addInput(Port::create<PJ301MOrPort>(Vec(115, 55 + 150), Port::INPUT, module, TROSC::SYNC_B_INPUT));
  376. addInput(Port::create<PJ301MOrPort>(Vec(115, 55 + 280), Port::INPUT, module, TROSC::SYNC_C_INPUT));
  377. addInput(Port::create<PJ301MCPort>(Vec(155, 45 + 20), Port::INPUT, module, TROSC::FM_A_INPUT));
  378. addInput(Port::create<PJ301MCPort>(Vec(155, 45 + 150), Port::INPUT, module, TROSC::FM_B_INPUT));
  379. addInput(Port::create<PJ301MCPort>(Vec(155, 45 + 280), Port::INPUT, module, TROSC::FM_C_INPUT));
  380. addInput(Port::create<PJ301MCPort>(Vec(290,vspace+10+20), Port::INPUT, module, TROSC::A_VOL_IN));
  381. addInput(Port::create<PJ301MCPort>(Vec(290,vspace+10+150), Port::INPUT, module, TROSC::B_VOL_IN));
  382. addInput(Port::create<PJ301MCPort>(Vec(290,vspace+10+280), Port::INPUT, module, TROSC::C_VOL_IN));
  383. addInput(Port::create<PJ301MCPort>(Vec(215, 50 + 280), Port::INPUT, module, TROSC::C_WIDTH_INPUT));
  384. addOutput(Port::create<PJ301MOPort>(Vec(290, 30), Port::OUTPUT, module, TROSC::MIX_OUTPUT));
  385. addOutput(Port::create<PJ301MOPort>(Vec(255, 20 + 20), Port::OUTPUT, module, TROSC::A_OUTPUT));
  386. addOutput(Port::create<PJ301MOPort>(Vec(255, 20 + 150), Port::OUTPUT, module, TROSC::B_OUTPUT));
  387. addOutput(Port::create<PJ301MOPort>(Vec(255, 20 + 280), Port::OUTPUT, module, TROSC::C_OUTPUT));
  388. }
  389. };
  390. } // namespace rack_plugin_dBiz
  391. using namespace rack_plugin_dBiz;
  392. RACK_PLUGIN_MODEL_INIT(dBiz, TROSC) {
  393. // Specify the Module and ModuleWidget subclass, human-readable
  394. // author name for categorization per plugin, module slug (should never
  395. // change), human-readable module name, and any number of tags
  396. // (found in `include/tags.hpp`) separated by commas.
  397. Model *modelTROSC = Model::create<TROSC, TROSCWidget>("dBiz", "TROSC", "Triple Oscillator", OSCILLATOR_TAG);
  398. return modelTROSC;
  399. }