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.

231 lines
7.7KB

  1. #include "dsp/digital.hpp"
  2. #include "util/math.hpp"
  3. #include "qwelk.hpp"
  4. #include "qwelk_common.h"
  5. #define GWIDTH 4
  6. #define GHEIGHT 8
  7. #define GSIZE (GWIDTH*GHEIGHT)
  8. #define GMID (GWIDTH/2+(GHEIGHT/2)*GWIDTH)
  9. #define LIGHT_SIZE 10
  10. #define DIR_BIT_SIZE 8
  11. namespace rack_plugin_Qwelk {
  12. struct ModuleNews : Module {
  13. enum ParamIds {
  14. PARAM_MODE,
  15. PARAM_GATEMODE,
  16. PARAM_ROUND,
  17. PARAM_CLAMP,
  18. PARAM_INTENSITY,
  19. PARAM_WRAP,
  20. PARAM_SMOOTH,
  21. PARAM_UNI_BI,
  22. PARAM_ORIGIN,
  23. NUM_PARAMS
  24. };
  25. enum InputIds {
  26. IN_NEWS,
  27. IN_INTENSITY,
  28. IN_WRAP,
  29. IN_HOLD,
  30. IN_ORIGIN,
  31. NUM_INPUTS
  32. };
  33. enum OutputIds {
  34. OUT_CELL,
  35. NUM_OUTPUTS = OUT_CELL + GSIZE
  36. };
  37. enum LightIds {
  38. LIGHT_GRID,
  39. NUM_LIGHTS = LIGHT_GRID + GSIZE
  40. };
  41. float sample;
  42. SchmittTrigger trig_hold;
  43. byte grid[GSIZE] {};
  44. float buffer[GSIZE] {};
  45. ModuleNews() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {}
  46. void step() override;
  47. inline void set(int i, bool gatemode)
  48. {
  49. if (gatemode)
  50. grid[i] ^= 1;
  51. else
  52. grid[i] += 1;
  53. }
  54. inline void set(int x, int y, bool gatemode)
  55. {
  56. set(x + y * GWIDTH, gatemode);
  57. }
  58. };
  59. void ModuleNews::step()
  60. {
  61. bool mode = params[PARAM_MODE].value > 0.0;
  62. bool gatemode = params[PARAM_GATEMODE].value > 0.0;
  63. bool round = params[PARAM_ROUND].value == 0.0;
  64. bool clamp = params[PARAM_CLAMP].value == 0.0;
  65. bool bi = params[PARAM_UNI_BI].value == 0.0;
  66. byte intensity = (byte)(floor(params[PARAM_INTENSITY].value));
  67. int wrap = floor(params[PARAM_WRAP].value);
  68. int origin = floor(params[PARAM_ORIGIN].value);
  69. float smooth = params[PARAM_SMOOTH].value;
  70. float in_origin = inputs[IN_ORIGIN].value / 10.0;
  71. float in_intensity = (inputs[IN_INTENSITY].value / 10.0) * 255.0;
  72. float in_wrap = (inputs[IN_WRAP].value / 5.0) * 31.0;
  73. intensity = minb(intensity + (byte)in_intensity, 255.0);
  74. wrap = ::clampi(wrap + (int)in_wrap, -31, 31);
  75. // are we doing s&h?
  76. if (trig_hold.process(inputs[IN_HOLD].value))
  77. sample = inputs[IN_NEWS].value;
  78. // read the news, or if s&h is active just the held sample
  79. float news = (inputs[IN_HOLD].active) ? sample : inputs[IN_NEWS].value;
  80. // if round switch is down, round off the input signal to an integer
  81. if (round)
  82. news = ceil(news);
  83. // wrap the bits around, e.g. wrap = 2, 1001 -> 0110 / wrap = -3, 1001 -> 0011
  84. uint32_t bits = *(reinterpret_cast<uint32_t *>(&news));
  85. if (wrap > 0)
  86. bits = (bits << wrap) | (bits >> (32 - wrap));
  87. else if (wrap < 0) {
  88. wrap = -wrap;
  89. bits = (bits >> wrap) | (bits << (32 - wrap));
  90. }
  91. news = *((float *)&bits);
  92. // extract the key out the bits which represent the input signal
  93. uint32_t key = *(reinterpret_cast<uint32_t *>(&news));
  94. // reset grid
  95. for (int i = 0; i < GSIZE; ++i)
  96. grid[i] = 0;
  97. // determine origin
  98. /*origin = min(origin + floor(in_origin * GSIZE), GSIZE );*/ // v0.6 breakage
  99. origin = min(origin + (int)floor(in_origin * GSIZE), GSIZE );
  100. int cy = origin / GWIDTH,
  101. cx = origin % GWIDTH;
  102. // extract N-E-W-S steps
  103. int nort = (key >> 24) & 0xFF,
  104. east = (key >> 16) & 0xFF,
  105. sout = (key >> 8) & 0xFF,
  106. west = (key ) & 0xFF;
  107. // begin plotting, or 'the walk'
  108. int w = 0,
  109. ic = (mode ? 1 : DIR_BIT_SIZE),
  110. cond = 0;
  111. while (w++ < ic) {
  112. cond = mode ? (nort) : (((nort >> w) & 1) == 1);
  113. while (cond-- > 0) {
  114. cy = (cy - 1) >= 0 ? cy - 1 : GHEIGHT - 1;
  115. set(cx, cy, gatemode);
  116. }
  117. cond = (mode ? (east) : (((east >> w) & 1) == 1));
  118. while (cond-- > 0) {
  119. cx = (cx + 1) < GWIDTH ? cx + 1 : 0;
  120. set(cx, cy, gatemode);
  121. }
  122. cond = (mode ? (sout) : (((sout >> w) & 1) == 1));
  123. while (cond-- > 0) {
  124. cy = (cy + 1) < GHEIGHT ? cy + 1 : 0;
  125. set(cx, cy, gatemode);
  126. }
  127. cond = (mode ? (west) : (((west >> w) & 1) == 1));
  128. while (cond-- > 0) {
  129. cx = (cx - 1) >= 0 ? cx - 1 : GWIDTH - 1;
  130. set(cx, cy, gatemode);
  131. }
  132. }
  133. // output
  134. for (int i = 0; i < GSIZE; ++i) {
  135. byte r = grid[i] * intensity;
  136. if (clamp && ((int)grid[i] * (int)intensity) > 0xFF)
  137. r = 0xFF;
  138. float v = gatemode
  139. ? (grid[i] ? 1 : 0)
  140. : ((byte)r / 255.0);
  141. buffer[i] = slew(buffer[i], v, smooth, 0.1, 100000.0, 0.5);
  142. outputs[OUT_CELL + i].value = 10.0 * buffer[i] - (bi ? 5.0 : 0.0);
  143. lights[LIGHT_GRID + i].setBrightness(buffer[i] * 0.9);
  144. }
  145. }
  146. //TODO: move to common
  147. template <typename _BASE>
  148. struct CellLight : _BASE {
  149. CellLight()
  150. {
  151. this->box.size = mm2px(Vec(LIGHT_SIZE, LIGHT_SIZE));
  152. }
  153. };
  154. struct WidgetNews : ModuleWidget {
  155. WidgetNews(ModuleNews *module);
  156. };
  157. WidgetNews::WidgetNews(ModuleNews *module) : ModuleWidget(module) {
  158. setPanel(SVG::load(assetPlugin(plugin, "res/NEWS.svg")));
  159. addChild(Widget::create<ScrewSilver>(Vec(15, 0)));
  160. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 0)));
  161. addChild(Widget::create<ScrewSilver>(Vec(15, 365)));
  162. addChild(Widget::create<ScrewSilver>(Vec(box.size.x - 30, 365)));
  163. addInput(Port::create<PJ301MPort>(Vec(9 , 30), Port::INPUT, module, ModuleNews::IN_HOLD));
  164. addInput(Port::create<PJ301MPort>(Vec(9 , 65), Port::INPUT, module, ModuleNews::IN_NEWS));
  165. addInput(Port::create<PJ301MPort>(Vec(39 , 65), Port::INPUT, module, ModuleNews::IN_ORIGIN));
  166. addParam(ParamWidget::create<TinyKnob>(Vec(41.6, 32.5), module, ModuleNews::PARAM_ORIGIN, 0.0, GSIZE, GMID));
  167. addInput(Port::create<PJ301MPort>(Vec(69 , 65), Port::INPUT, module, ModuleNews::IN_INTENSITY));
  168. addParam(ParamWidget::create<TinyKnob>(Vec(71.1 , 32.5), module, ModuleNews::PARAM_INTENSITY, 1.0, 256.0, 1.0));
  169. addInput(Port::create<PJ301MPort>(Vec(99, 65), Port::INPUT, module, ModuleNews::IN_WRAP));
  170. addParam(ParamWidget::create<TinyKnob>(Vec(101, 32.5), module, ModuleNews::PARAM_WRAP, -31.0, 32.0, 0.0));
  171. const float out_ytop = 92.5;
  172. for (int y = 0; y < GHEIGHT; ++y)
  173. for (int x = 0; x < GWIDTH; ++x) {
  174. int i = x + y * GWIDTH;
  175. addChild(ModuleLightWidget::create<CellLight<GreenLight>>(Vec(7 + x * 30 - 0.2, out_ytop + y * 30 - 0.1), module, ModuleNews::LIGHT_GRID + i));
  176. addOutput(Port::create<PJ301MPort>(Vec(7 + x * 30 + 2, out_ytop + y * 30 + 2), Port::OUTPUT, module, ModuleNews::OUT_CELL + i));
  177. }
  178. const float bottom_row = 345;
  179. addParam(ParamWidget::create<CKSS>(Vec(5 , bottom_row), module, ModuleNews::PARAM_UNI_BI, 0.0, 1.0, 1.0));
  180. addParam(ParamWidget::create<CKSS>(Vec(25 , bottom_row), module, ModuleNews::PARAM_MODE, 0.0, 1.0, 1.0));
  181. addParam(ParamWidget::create<CKSS>(Vec(45 , bottom_row), module, ModuleNews::PARAM_GATEMODE, 0.0, 1.0, 1.0));
  182. addParam(ParamWidget::create<CKSS>(Vec(65 , bottom_row), module, ModuleNews::PARAM_ROUND, 0.0, 1.0, 1.0));
  183. addParam(ParamWidget::create<CKSS>(Vec(85 , bottom_row), module, ModuleNews::PARAM_CLAMP, 0.0, 1.0, 1.0));
  184. addParam(ParamWidget::create<TinyKnob>(Vec(110 , bottom_row), module, ModuleNews::PARAM_SMOOTH, 0.0, 1.0, 0.0));
  185. }
  186. } // namespace rack_plugin_Qwelk
  187. using namespace rack_plugin_Qwelk;
  188. RACK_PLUGIN_MODEL_INIT(Qwelk, News) {
  189. Model *modelNews = Model::create<ModuleNews, WidgetNews>(
  190. TOSTRING(SLUG), "NEWS", "NEWS", SEQUENCER_TAG);
  191. return modelNews;
  192. }