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.

956 lines
37KB

  1. #include "FrozenWasteland.hpp"
  2. #include "dsp/samplerate.hpp"
  3. #include "dsp/digital.hpp"
  4. #include "dsp/filter.hpp"
  5. #include "ringbuffer.hpp"
  6. #include "StateVariableFilter.h"
  7. #include "clouds/dsp/frame.h"
  8. #include "clouds/dsp/fx/pitch_shifter.h"
  9. #include <iostream>
  10. #define HISTORY_SIZE (1<<22)
  11. #define MAX_GRAIN_SIZE (1<<16)
  12. #define NUM_TAPS 16
  13. #define MAX_GRAINS 4
  14. #define CHANNELS 2
  15. #define DIVISIONS 21
  16. #define NUM_GROOVES 16
  17. namespace rack_plugin_FrozenWasteland {
  18. struct PortlandWeather : Module {
  19. typedef float T;
  20. enum ParamIds {
  21. CLOCK_DIV_PARAM,
  22. TIME_PARAM,
  23. GRID_PARAM,
  24. GROOVE_TYPE_PARAM,
  25. GROOVE_AMOUNT_PARAM,
  26. GRAIN_QUANTITY_PARAM,
  27. GRAIN_SIZE_PARAM,
  28. FEEDBACK_PARAM,
  29. FEEDBACK_TAP_L_PARAM,
  30. FEEDBACK_TAP_R_PARAM,
  31. FEEDBACK_L_SLIP_PARAM,
  32. FEEDBACK_R_SLIP_PARAM,
  33. FEEDBACK_TONE_PARAM,
  34. FEEDBACK_L_PITCH_SHIFT_PARAM,
  35. FEEDBACK_R_PITCH_SHIFT_PARAM,
  36. FEEDBACK_L_DETUNE_PARAM,
  37. FEEDBACK_R_DETUNE_PARAM,
  38. PING_PONG_PARAM,
  39. REVERSE_PARAM,
  40. MIX_PARAM,
  41. TAP_MUTE_PARAM,
  42. TAP_STACKED_PARAM = TAP_MUTE_PARAM+NUM_TAPS,
  43. TAP_MIX_PARAM = TAP_STACKED_PARAM+NUM_TAPS,
  44. TAP_PAN_PARAM = TAP_MIX_PARAM+NUM_TAPS,
  45. TAP_FILTER_TYPE_PARAM = TAP_PAN_PARAM+NUM_TAPS,
  46. TAP_FC_PARAM = TAP_FILTER_TYPE_PARAM+NUM_TAPS,
  47. TAP_Q_PARAM = TAP_FC_PARAM+NUM_TAPS,
  48. TAP_PITCH_SHIFT_PARAM = TAP_Q_PARAM+NUM_TAPS,
  49. TAP_DETUNE_PARAM = TAP_PITCH_SHIFT_PARAM+NUM_TAPS,
  50. CLEAR_BUFFER_PARAM = TAP_DETUNE_PARAM+NUM_TAPS,
  51. NUM_PARAMS
  52. };
  53. enum InputIds {
  54. CLOCK_INPUT,
  55. CLOCK_DIVISION_CV_INPUT,
  56. TIME_CV_INPUT,
  57. EXTERNAL_DELAY_TIME_INPUT,
  58. GRID_CV_INPUT,
  59. GROOVE_TYPE_CV_INPUT,
  60. GROOVE_AMOUNT_CV_INPUT,
  61. FEEDBACK_INPUT,
  62. FEEDBACK_TAP_L_INPUT,
  63. FEEDBACK_TAP_R_INPUT,
  64. FEEDBACK_TONE_INPUT,
  65. FEEDBACK_L_SLIP_CV_INPUT,
  66. FEEDBACK_R_SLIP_CV_INPUT,
  67. FEEDBACK_L_PITCH_SHIFT_CV_INPUT,
  68. FEEDBACK_R_PITCH_SHIFT_CV_INPUT,
  69. FEEDBACK_L_DETUNE_CV_INPUT,
  70. FEEDBACK_R_DETUNE_CV_INPUT,
  71. FEEDBACK_L_RETURN,
  72. FEEDBACK_R_RETURN,
  73. PING_PONG_INPUT,
  74. REVERSE_INPUT,
  75. MIX_INPUT,
  76. TAP_MUTE_CV_INPUT,
  77. TAP_STACK_CV_INPUT = TAP_MUTE_CV_INPUT + NUM_TAPS,
  78. TAP_MIX_CV_INPUT = TAP_STACK_CV_INPUT + NUM_TAPS,
  79. TAP_PAN_CV_INPUT = TAP_MIX_CV_INPUT + NUM_TAPS,
  80. TAP_FC_CV_INPUT = TAP_PAN_CV_INPUT + NUM_TAPS,
  81. TAP_Q_CV_INPUT = TAP_FC_CV_INPUT + NUM_TAPS,
  82. TAP_PITCH_SHIFT_CV_INPUT = TAP_Q_CV_INPUT + NUM_TAPS,
  83. TAP_DETUNE_CV_INPUT = TAP_PITCH_SHIFT_CV_INPUT + NUM_TAPS,
  84. IN_L_INPUT = TAP_DETUNE_CV_INPUT+NUM_TAPS,
  85. IN_R_INPUT,
  86. NUM_INPUTS
  87. };
  88. enum OutputIds {
  89. OUT_L_OUTPUT,
  90. OUT_R_OUTPUT,
  91. FEEDBACK_L_OUTPUT,
  92. FEEDBACK_R_OUTPUT,
  93. NUM_OUTPUTS
  94. };
  95. enum LightIds {
  96. PING_PONG_LIGHT,
  97. REVERSE_LIGHT,
  98. TAP_MUTED_LIGHT,
  99. TAP_STACKED_LIGHT = TAP_MUTED_LIGHT+NUM_TAPS,
  100. FREQ_LIGHT = TAP_STACKED_LIGHT+NUM_TAPS,
  101. NUM_LIGHTS
  102. };
  103. enum FilterModes {
  104. FILTER_NONE,
  105. FILTER_LOWPASS,
  106. FILTER_HIGHPASS,
  107. FILTER_BANDPASS,
  108. FILTER_NOTCH
  109. };
  110. struct LowFrequencyOscillator {
  111. float phase = 0.0;
  112. float freq = 1.0;
  113. bool invert = false;
  114. //void setFrequency(float frequency) {
  115. // freq = frequency;
  116. //}
  117. void hardReset()
  118. {
  119. phase = 0.0;
  120. }
  121. void reset()
  122. {
  123. phase -= 1.0;
  124. }
  125. void step(float dt) {
  126. float deltaPhase = fminf(freq * dt, 0.5);
  127. phase += deltaPhase;
  128. //if (phase >= 1.0)
  129. // phase -= 1.0;
  130. }
  131. float sin() {
  132. return sinf(2*M_PI * phase) * (invert ? -1.0 : 1.0);
  133. }
  134. float progress() {
  135. return phase;
  136. }
  137. };
  138. const char* grooveNames[NUM_GROOVES] = {"Straight","Swing","Hard Swing","Reverse Swing","Alternate Swing","Accelerando","Ritardando","Waltz Time","Half Swing","Roller Coaster","Quintuple","Random 1","Random 2","Random 3","Early Reflection","Late Reflection"};
  139. const float tapGroovePatterns[NUM_GROOVES][NUM_TAPS] = {
  140. {1.0f,2.0f,3.0f,4.0f,5.0f,6.0f,7.0f,8.0f,9.0f,10.0f,11.0f,12.0f,13.0f,14.0f,15.0f,16.0f}, // Straight time
  141. {1.25f,2.0f,3.25f,4.0f,5.25f,6.0f,7.25f,8.0f,9.25f,10.0f,11.25f,12.0f,13.25f,14.0f,15.25f,16.0f}, // Swing
  142. {1.75f,2.0f,3.75,4.0f,5.75f,6.0f,7.75f,8.0f,9.75f,10.0f,11.75f,12.0f,13.75f,14.0f,15.75f,16.0f}, // Hard Swing
  143. {0.75f,2.0f,2.75f,4.0f,4.75f,6.0f,6.75f,8.0f,8.75f,10.0f,10.75f,12.0f,12.75f,14.0f,14.75f,16.0f}, // Reverse Swing
  144. {1.25f,2.0f,3.0f,4.0f,5.25f,6.0f,7.0f,8.0f,9.25f,10.0f,11.0f,12.0f,13.25f,14.0f,15.0f,16.0f}, // Alternate Swing
  145. {3.0f,5.0f,7.0f,9.0f,10.0f,11.0f,12.0f,13.0f,13.5f,14.0f,14.5f,15.0f,15.25f,15.5f,15.75f,16.0f}, // Accelerando
  146. {0.25f,0.5f,0.75f,1.0f,1.5f,2.0f,2.5f,3.0f,4.0f,5.0f,6.0f,7.0f,9.0f,11.0f,13.0f,16.0f}, // Ritardando
  147. {1.25f,2.75f,3.25f,4.0f,5.25f,6.75f,7.25f,8.0f,9.25f,10.75f,11.25f,12.0f,13.25f,14.75f,15.25f,16.0f}, // Waltz Time
  148. {1.5f,2.0f,3.5f,4.0f,5.0f,6.0f,7.0f,8.0f,9.5f,10.0f,11.5f,12.0f,13.0f,14.0f,15.0f,16.0f}, // Half Swing
  149. {1.0f,2.0f,4.0f,5.0f,6.0f,8.0f,10.0f,12.0f,12.5f,13.0f,13.5f,14.0f,14.5f,15.0f,15.5f,16.0f}, // Roller Coaster
  150. {1.75f,2.5f,3.25f,4.0f,4.75f,6.5f,7.25f,8.0f,9.75f,10.5f,11.25f,12.0f,12.75f,14.5f,15.25f,16.0f}, // Quintuple
  151. {0.25f,0.75f,1.0f,1.25f,4.0f,5.5f,7.25f,7.5f,8.0f,8.25f,10.0f,11.0f,13.5f,15.0f,15.75f,16.0f}, // Uniform Random 1
  152. {0.25f,4.75f,5.25f,5.5f,7.0f,8.0f,8.5f,8.75f,9.0f,9.25f,11.75f,12.75f,13.0f,13.25f,14.75f,15.5f}, // Uniform Random 2
  153. {0.75f,2.0f,2.25f,5.75f,7.25f,7.5f,7.75f,8.5f,8.75f,12.5f,12.75f,13.0f,13.75f,14.0f,14.5f,16.0f}, // Uniform Random 3
  154. {0.25f,0.5f,1.0f,1.25f,1.75f,2.0f,2.5f,3.5f,4.25f,4.5f,4.75f,5.0f,6.25f,8.25f,11.0f,16.0f}, // Early Reflection
  155. {7.0f,7.25f,9.0f,9.25f,10.25f,12.5f,13.0f,13.75f,14.0f,15.0f,15.25f,15.5f,15.75f,16.0f,16.0f,16.0f} // Late Reflection
  156. };
  157. const float minCutoff = 15.0;
  158. const float maxCutoff = 8400.0;
  159. int tapGroovePattern = 0;
  160. float grooveAmount = 1.0f;
  161. bool pingPong = false;
  162. bool reverse = false;
  163. int grainNumbers;
  164. bool tapMuted[NUM_TAPS+1];
  165. bool tapStacked[NUM_TAPS+1];
  166. int lastFilterType[NUM_TAPS+1];
  167. float lastTapFc[NUM_TAPS+1];
  168. float lastTapQ[NUM_TAPS+1];
  169. float tapPitchShift[NUM_TAPS+1];
  170. float tapDetune[NUM_TAPS+1];
  171. int tapFilterType[NUM_TAPS+1];
  172. int feedbackTap[CHANNELS] = {NUM_TAPS-1,NUM_TAPS-1};
  173. float feedbackSlip[CHANNELS] = {0.0f,0.0f};
  174. float feedbackPitch[CHANNELS] = {0.0f,0.0f};
  175. float feedbackDetune[CHANNELS] = {0.0f,0.0f};
  176. float delayTime[NUM_TAPS+1][CHANNELS];
  177. float actualDelayTime[NUM_TAPS+1][CHANNELS][2];
  178. float initialWindowedOutput[NUM_TAPS+1][CHANNELS][2];
  179. StateVariableFilterState<T> filterStates[NUM_TAPS][CHANNELS];
  180. StateVariableFilterParams<T> filterParams[NUM_TAPS];
  181. RCFilter lowpassFilter[CHANNELS];
  182. RCFilter highpassFilter[CHANNELS];
  183. const char* filterNames[5] = {"O","L","H","B","N"};
  184. clouds::PitchShifter pitch_shifter_[NUM_TAPS+1][CHANNELS][MAX_GRAINS];
  185. SchmittTrigger clockTrigger,pingPongTrigger,reverseTrigger,clearBufferTrigger,mutingTrigger[NUM_TAPS],stackingTrigger[NUM_TAPS];
  186. float divisions[DIVISIONS] = {1/256.0f,1/192.0f,1/128.0f,1/96.0f,1/64.0f,1/48.0f,1/32.0f,1/24.0f,1/16.0f,1/13.0f,1/12.0f,1/11.0f,1/8.0f,1/7.0f,1/6.0f,1/5.0f,1/4.0f,1/3.0f,1/2.0f,1/1.5f,1};
  187. const char* divisionNames[DIVISIONS] = {"/256","/192","/128","/96","/64","/48","/32","/24","/16","/13","/12","/11","/8","/7","/6","/5","/4","/3","/2","/1.5","x 1"};
  188. int division;
  189. float time = 0.0;
  190. float duration = 0;
  191. float baseDelay;
  192. bool secondClockReceived = false;
  193. LowFrequencyOscillator sinOsc[2];
  194. MultiTapDoubleRingBuffer<float, HISTORY_SIZE,NUM_TAPS+1> historyBuffer[CHANNELS][2];
  195. ReverseRingBuffer<float, HISTORY_SIZE> reverseHistoryBuffer[CHANNELS];
  196. float pitchShiftBuffer[NUM_TAPS+1][CHANNELS][MAX_GRAINS][MAX_GRAIN_SIZE];
  197. clouds::FloatFrame pitchShiftOut_;
  198. DoubleRingBuffer<float, 16> outBuffer[NUM_TAPS+1][CHANNELS][2];
  199. SampleRateConverter<1> src;
  200. float lastFeedback[CHANNELS] = {0.0f,0.0f};
  201. float lerp(float v0, float v1, float t) {
  202. return (1 - t) * v0 + t * v1;
  203. }
  204. float SemitonesToRatio(float semiTone) {
  205. return powf(2,semiTone/12.0f);
  206. }
  207. PortlandWeather() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) {
  208. sinOsc[1].phase = 0.25; //90 degrees out
  209. for (int i = 0; i <= NUM_TAPS; ++i) {
  210. tapMuted[i] = false;
  211. tapStacked[i] = false;
  212. tapPitchShift[i] = 0.0f;
  213. tapDetune[i] = 0.0f;
  214. filterParams[i].setMode(StateVariableFilterParams<T>::Mode::LowPass);
  215. filterParams[i].setQ(5);
  216. filterParams[i].setFreq(T(800.0f / engineGetSampleRate()));
  217. for(int j=0;j < CHANNELS;j++) {
  218. actualDelayTime[i][j][0] = 0.0f;
  219. actualDelayTime[i][j][1] = 0.0f;
  220. for(int k=0;k<MAX_GRAINS;k++) {
  221. pitch_shifter_[i][j][k].Init(pitchShiftBuffer[i][j][k],k*0.25f);
  222. }
  223. }
  224. }
  225. //Initialize the feedback pitch shifters
  226. for(int j=0;j < CHANNELS;j++) {
  227. for(int k=0;k<MAX_GRAINS;k++) {
  228. pitch_shifter_[NUM_TAPS][j][k].Init(pitchShiftBuffer[NUM_TAPS][j][k],k*0.25f);
  229. }
  230. }
  231. }
  232. const char* tapNames[NUM_TAPS+2] {"1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","ALL","EXT"};
  233. const char* grainNames[MAX_GRAINS] {"1","2","4","Raw"};
  234. json_t *toJson() override {
  235. json_t *rootJ = json_object();
  236. json_object_set_new(rootJ, "pingPong", json_integer((int) pingPong));
  237. //json_object_set_new(rootJ, "reverse", json_integer((int) reverse));
  238. for(int i=0;i<NUM_TAPS;i++) {
  239. //This is so stupid!!! why did he not use strings?
  240. char buf[100];
  241. strcpy(buf, "muted");
  242. strcat(buf, tapNames[i]);
  243. json_object_set_new(rootJ, buf, json_integer((int) tapMuted[i]));
  244. strcpy(buf, "stacked");
  245. strcat(buf, tapNames[i]);
  246. json_object_set_new(rootJ, buf, json_integer((int) tapStacked[i]));
  247. }
  248. return rootJ;
  249. }
  250. void fromJson(json_t *rootJ) override {
  251. json_t *sumJ = json_object_get(rootJ, "pingPong");
  252. if (sumJ) {
  253. pingPong = json_integer_value(sumJ);
  254. }
  255. //json_t *sumR = json_object_get(rootJ, "reverse");
  256. //if (sumR) {
  257. // reverse = json_integer_value(sumR);
  258. //}
  259. char buf[100];
  260. for(int i=0;i<NUM_TAPS;i++) {
  261. strcpy(buf, "muted");
  262. strcat(buf, tapNames[i]);
  263. json_t *sumJ = json_object_get(rootJ, buf);
  264. if (sumJ) {
  265. tapMuted[i] = json_integer_value(sumJ);
  266. }
  267. }
  268. for(int i=0;i<NUM_TAPS;i++) {
  269. strcpy(buf, "stacked");
  270. strcat(buf, tapNames[i]);
  271. json_t *sumJ = json_object_get(rootJ, buf);
  272. if (sumJ) {
  273. tapStacked[i] = json_integer_value(sumJ);
  274. }
  275. }
  276. }
  277. void step() override;
  278. };
  279. void PortlandWeather::step() {
  280. sinOsc[0].step(1.0 / engineGetSampleRate());
  281. sinOsc[1].step(1.0 / engineGetSampleRate());
  282. if (clearBufferTrigger.process(params[CLEAR_BUFFER_PARAM].value)) {
  283. for(int i=0;i<CHANNELS;i++) {
  284. historyBuffer[i][0].clear();
  285. historyBuffer[i][1].clear();
  286. }
  287. }
  288. tapGroovePattern = (int)clamp(params[GROOVE_TYPE_PARAM].value + (inputs[GROOVE_TYPE_CV_INPUT].value / 10.0f),0.0f,15.0);
  289. grooveAmount = clamp(params[GROOVE_AMOUNT_PARAM].value + (inputs[GROOVE_AMOUNT_CV_INPUT].value / 10.0f),0.0f,1.0f);
  290. float divisionf = params[CLOCK_DIV_PARAM].value;
  291. if(inputs[CLOCK_DIVISION_CV_INPUT].active) {
  292. divisionf +=(inputs[CLOCK_DIVISION_CV_INPUT].value * (DIVISIONS / 10.0));
  293. }
  294. divisionf = clamp(divisionf,0.0f,20.0f);
  295. division = (DIVISIONS-1) - int(divisionf); //TODO: Reverse Division Order
  296. time += 1.0 / engineGetSampleRate();
  297. if(inputs[CLOCK_INPUT].active) {
  298. if(clockTrigger.process(inputs[CLOCK_INPUT].value)) {
  299. if(secondClockReceived) {
  300. duration = time;
  301. }
  302. time = 0;
  303. //secondClockReceived = true;
  304. secondClockReceived = !secondClockReceived;
  305. }
  306. //lights[CLOCK_LIGHT].value = time > (duration/2.0);
  307. }
  308. if(inputs[CLOCK_INPUT].active) {
  309. baseDelay = duration / divisions[division];
  310. } else {
  311. baseDelay = clamp(params[TIME_PARAM].value + inputs[TIME_CV_INPUT].value, 0.001f, 10.0f);
  312. //baseDelay = clamp(params[TIME_PARAM].value, 0.001f, 10.0f);
  313. }
  314. if (pingPongTrigger.process(params[PING_PONG_PARAM].value + inputs[PING_PONG_INPUT].value)) {
  315. pingPong = !pingPong;
  316. }
  317. lights[PING_PONG_LIGHT].value = pingPong;
  318. if (reverseTrigger.process(params[REVERSE_PARAM].value + inputs[REVERSE_INPUT].value)) {
  319. reverse = !reverse;
  320. if(reverse) {
  321. for(int channel =0;channel <CHANNELS;channel++) {
  322. reverseHistoryBuffer[channel].clear();
  323. }
  324. }
  325. }
  326. lights[REVERSE_LIGHT].value = reverse;
  327. grainNumbers = (int)params[GRAIN_QUANTITY_PARAM].value;
  328. for(int channel = 0;channel < CHANNELS;channel++) {
  329. // Get input to delay block
  330. float in = 0.0f;
  331. if(channel == 0) {
  332. in = inputs[IN_L_INPUT].value;
  333. } else {
  334. in = inputs[IN_R_INPUT].active ? inputs[IN_R_INPUT].value : inputs[IN_L_INPUT].value;
  335. }
  336. feedbackTap[channel] = (int)clamp(params[FEEDBACK_TAP_L_PARAM+channel].value + (inputs[FEEDBACK_TAP_L_INPUT+channel].value / 10.0f),0.0f,17.0);
  337. feedbackSlip[channel] = clamp(params[FEEDBACK_L_SLIP_PARAM+channel].value + (inputs[FEEDBACK_L_SLIP_CV_INPUT+channel].value / 10.0f),-0.5f,0.5);
  338. float feedbackAmount = clamp(params[FEEDBACK_PARAM].value + (inputs[FEEDBACK_INPUT].value / 10.0f), 0.0f, 1.0f);
  339. float feedbackInput = lastFeedback[channel];
  340. float dry = in + feedbackInput * feedbackAmount;
  341. float dryToUse = dry; //Normally the same as dry unless in reverse mode
  342. // Push dry sample into reverse history buffer
  343. reverseHistoryBuffer[channel].push(dry);
  344. if(reverse) {
  345. float reverseDry = reverseHistoryBuffer[channel].shift();
  346. dryToUse = reverseDry;
  347. }
  348. // Push dry sample into history buffer
  349. for(int dualIndex=0;dualIndex<2;dualIndex++) {
  350. if (!historyBuffer[channel][dualIndex].full(NUM_TAPS-1)) {
  351. historyBuffer[channel][dualIndex].push(dryToUse);
  352. }
  353. }
  354. float wet = 0.0f; // This is the mix of delays and input that is outputed
  355. float feedbackValue = 0.0f; // This is the output of a tap that gets sent back to input
  356. float activeTapCount = 0.0f; // This will be used to normalize output
  357. for(int tap = 0; tap <= NUM_TAPS;tap++) {
  358. // Stacking
  359. if (tap < NUM_TAPS -1 && stackingTrigger[tap].process(params[TAP_STACKED_PARAM+tap].value + inputs[TAP_STACK_CV_INPUT+tap].value)) {
  360. tapStacked[tap] = !tapStacked[tap];
  361. }
  362. //float pitch_grain_size = 1.0f; //Can be between 0 and 1
  363. float pitch_grain_size = params[GRAIN_SIZE_PARAM].value; //Can be between 0 and 1
  364. float pitch,detune;
  365. if (tap < NUM_TAPS) {
  366. pitch = floor(params[TAP_PITCH_SHIFT_PARAM+tap].value + (inputs[TAP_PITCH_SHIFT_CV_INPUT+tap].value*2.4f));
  367. detune = floor(params[TAP_DETUNE_PARAM+tap].value + (inputs[TAP_DETUNE_CV_INPUT+tap].value*10.0f));
  368. tapPitchShift[tap] = pitch;
  369. tapDetune[tap] = detune;
  370. } else {
  371. pitch = floor(params[FEEDBACK_L_PITCH_SHIFT_PARAM+channel].value + (inputs[FEEDBACK_L_PITCH_SHIFT_CV_INPUT+channel].value*2.4f));
  372. detune = floor(params[FEEDBACK_L_DETUNE_PARAM+channel].value + (inputs[FEEDBACK_L_DETUNE_CV_INPUT+channel].value*10.0f));
  373. feedbackPitch[channel] = pitch;
  374. feedbackDetune[channel] = detune;
  375. }
  376. pitch += detune/100.0f;
  377. float delayMod = 0.0f;
  378. //Normally the delay tap is the same as the tap itself, unless it is stacked, then it is its neighbor;
  379. int delayTap = tap;
  380. while(delayTap < NUM_TAPS && tapStacked[delayTap]) {
  381. delayTap++;
  382. }
  383. //Pull feedback off of normal tap time
  384. if(tap == NUM_TAPS && feedbackTap[channel] < NUM_TAPS) {
  385. delayTap = feedbackTap[channel];
  386. delayMod = feedbackSlip[channel] * baseDelay;
  387. }
  388. // Compute delay time in seconds
  389. float delay = baseDelay * lerp(tapGroovePatterns[0][delayTap],tapGroovePatterns[tapGroovePattern][delayTap],grooveAmount); //Balance between straight time and groove
  390. //External feedback time
  391. if(tap == NUM_TAPS && feedbackTap[channel] == NUM_TAPS+1) {
  392. delay = clamp(inputs[EXTERNAL_DELAY_TIME_INPUT].value, 0.001f, 10.0f);
  393. }
  394. if(inputs[TIME_CV_INPUT].active) { //The CV can change either clocked or set delay by 10MS
  395. delayMod += (0.001f * inputs[TIME_CV_INPUT].value);
  396. }
  397. delayTime[tap][channel] = delay + delayMod;
  398. //Set reverse size
  399. if(tap == NUM_TAPS) {
  400. reverseHistoryBuffer[channel].setDelaySize((delay+delayMod) * engineGetSampleRate());
  401. }
  402. for(int dualIndex=0;dualIndex<2;dualIndex++) {
  403. //if((actualDelayTime[tap][channel][dualIndex] != delayTime[tap][channel] && sinOsc[dualIndex].progress() >= 1) || actualDelayTime[tap][channel][dualIndex] == 0.0f) {
  404. if((actualDelayTime[tap][channel][dualIndex] != delayTime[tap][channel]) || actualDelayTime[tap][channel][dualIndex] == 0.0f) {
  405. actualDelayTime[tap][channel][dualIndex] = delayTime[tap][channel];
  406. sinOsc[dualIndex].reset();
  407. }
  408. float index = actualDelayTime[tap][channel][dualIndex] * engineGetSampleRate();
  409. // How many samples do we need consume to catch up?
  410. float consume = index - historyBuffer[channel][dualIndex].size(tap);
  411. if (outBuffer[tap][channel][dualIndex].empty()) {
  412. int inFrames = min(historyBuffer[channel][dualIndex].size(tap), 16);
  413. double ratio = 1.0;
  414. if (consume <= -16)
  415. ratio = 0.5;
  416. else if (consume >= 16)
  417. ratio = 2.0;
  418. float inSR = engineGetSampleRate();
  419. float outSR = ratio * inSR;
  420. int outFrames = outBuffer[tap][channel][dualIndex].capacity();
  421. src.setRates(inSR, outSR);
  422. src.process((const Frame<1>*)historyBuffer[channel][dualIndex].startData(tap), &inFrames, (Frame<1>*)outBuffer[tap][channel][dualIndex].endData(), &outFrames);
  423. outBuffer[tap][channel][dualIndex].endIncr(outFrames);
  424. historyBuffer[channel][dualIndex].startIncr(tap, inFrames);
  425. }
  426. if (!outBuffer[tap][channel][dualIndex].empty()) {
  427. initialWindowedOutput[tap][channel][dualIndex] = outBuffer[tap][channel][dualIndex].shift();
  428. }
  429. }
  430. float wetTap = 0.0f;
  431. float initialOutput = (sinOsc[0].sin() * sinOsc[0].sin() * initialWindowedOutput[tap][channel][0]) + (sinOsc[1].sin() * sinOsc[1].sin() * initialWindowedOutput[tap][channel][1]);
  432. float grainVolumeScaling = 1;
  433. for(int k=0;k<MAX_GRAINS;k++) {
  434. pitchShiftOut_.l = initialOutput;
  435. //Apply Pitch Shifting
  436. pitch_shifter_[tap][channel][k].set_ratio(SemitonesToRatio(pitch));
  437. pitch_shifter_[tap][channel][k].set_size(pitch_grain_size);
  438. //TODO: Put back into outBuffer
  439. bool useTriangleWindow = grainNumbers != 4;
  440. pitch_shifter_[tap][channel][k].Process(&pitchShiftOut_,useTriangleWindow);
  441. if(k == 0) {
  442. wetTap +=pitchShiftOut_.l; //First one always use
  443. } else if (k == 2 && grainNumbers >= 2) {
  444. wetTap +=pitchShiftOut_.l; //Use middle grain for 2
  445. grainVolumeScaling = 1.414;
  446. } else if (k != 2 && grainNumbers == 3) {
  447. wetTap +=pitchShiftOut_.l; //Use them all
  448. grainVolumeScaling = 2;
  449. }
  450. }
  451. wetTap = wetTap / grainVolumeScaling;
  452. //Feedback tap doesn't get panned or filtered
  453. if(tap < NUM_TAPS) {
  454. // Muting
  455. if (mutingTrigger[tap].process(params[TAP_MUTE_PARAM+tap].value + inputs[TAP_MUTE_CV_INPUT+tap].value)) {
  456. tapMuted[tap] = !tapMuted[tap];
  457. if(!tapMuted[tap]) {
  458. activeTapCount +=1.0f;
  459. }
  460. }
  461. float pan = 0.0f;
  462. if(channel == 0) {
  463. pan = clamp(1.0-(params[TAP_PAN_PARAM+tap].value + (inputs[TAP_PAN_CV_INPUT+tap].value / 10.0f)),0.0f,0.5f) * 2.0f;
  464. } else {
  465. pan = clamp(params[TAP_PAN_PARAM+tap].value + (inputs[TAP_PAN_CV_INPUT+tap].value / 10.0f),0.0f,0.5f) * 2.0f;
  466. }
  467. wetTap = wetTap * clamp(params[TAP_MIX_PARAM+tap].value + (inputs[TAP_MIX_CV_INPUT+tap].value / 10.0f),0.0f,1.0f) * pan;
  468. int tapFilterType = (int)params[TAP_FILTER_TYPE_PARAM+tap].value;
  469. // Apply Filter to tap wet output
  470. if(tapFilterType != FILTER_NONE) {
  471. if(tapFilterType != lastFilterType[tap]) {
  472. switch(tapFilterType) {
  473. case FILTER_LOWPASS:
  474. filterParams[tap].setMode(StateVariableFilterParams<T>::Mode::LowPass);
  475. break;
  476. case FILTER_HIGHPASS:
  477. filterParams[tap].setMode(StateVariableFilterParams<T>::Mode::HiPass);
  478. break;
  479. case FILTER_BANDPASS:
  480. filterParams[tap].setMode(StateVariableFilterParams<T>::Mode::BandPass);
  481. break;
  482. case FILTER_NOTCH:
  483. filterParams[tap].setMode(StateVariableFilterParams<T>::Mode::Notch);
  484. break;
  485. }
  486. }
  487. float cutoffExp = clamp(params[TAP_FC_PARAM+tap].value + inputs[TAP_FC_CV_INPUT+tap].value / 10.0f,0.0f,1.0f);
  488. float tapFc = minCutoff * powf(maxCutoff / minCutoff, cutoffExp) / engineGetSampleRate();
  489. if(lastTapFc[tap] != tapFc) {
  490. filterParams[tap].setFreq(T(tapFc));
  491. lastTapFc[tap] = tapFc;
  492. }
  493. float tapQ = clamp(params[TAP_Q_PARAM+tap].value + (inputs[TAP_Q_CV_INPUT+tap].value / 10.0f),0.01f,1.0f) * 50;
  494. if(lastTapQ[tap] != tapQ) {
  495. filterParams[tap].setQ(tapQ);
  496. lastTapQ[tap] = tapQ;
  497. }
  498. wetTap = StateVariableFilter<T>::run(wetTap, filterStates[tap][channel], filterParams[tap]);
  499. }
  500. lastFilterType[tap] = tapFilterType;
  501. if(tapMuted[tap]) {
  502. wetTap = 0.0f;
  503. }
  504. wet += wetTap;
  505. lights[TAP_STACKED_LIGHT+tap].value = tapStacked[tap];
  506. lights[TAP_MUTED_LIGHT+tap].value = (tapMuted[tap]);
  507. } else {
  508. feedbackValue = wetTap;
  509. }
  510. }
  511. //activeTapCount = 16.0f;
  512. //wet = wet / activeTapCount * sqrt(activeTapCount);
  513. if(feedbackTap[channel] == NUM_TAPS) { //This would be the All Taps setting
  514. //float feedbackScaling = 4.0f; // Trying to make full feedback not, well feedback
  515. //feedbackValue = wet * feedbackScaling / NUM_TAPS;
  516. feedbackValue = wet;
  517. }
  518. //Apply global filtering
  519. // TODO Make it sound better
  520. float color = clamp(params[FEEDBACK_TONE_PARAM].value + inputs[FEEDBACK_TONE_INPUT].value / 10.0f, 0.0f, 1.0f);
  521. float lowpassFreq = 10000.0f * powf(10.0f, clamp(2.0f*color, 0.0f, 1.0f));
  522. lowpassFilter[channel].setCutoff(lowpassFreq / engineGetSampleRate());
  523. lowpassFilter[channel].process(feedbackValue);
  524. feedbackValue = lowpassFilter[channel].lowpass();
  525. float highpassFreq = 10.0f * powf(100.0f, clamp(2.0f*color - 1.0f, 0.0f, 1.0f));
  526. highpassFilter[channel].setCutoff(highpassFreq / engineGetSampleRate());
  527. highpassFilter[channel].process(feedbackValue);
  528. feedbackValue = highpassFilter[channel].highpass();
  529. outputs[FEEDBACK_L_OUTPUT+channel].value = feedbackValue;
  530. if(inputs[FEEDBACK_L_RETURN+channel].active) {
  531. feedbackValue = inputs[FEEDBACK_L_RETURN+channel].value;
  532. }
  533. //feedbackValue = clamp(feedbackValue,-5.0f,5.0f); // Let's keep things civil
  534. int feedbackDestinationChannel = channel;
  535. if (pingPong) {
  536. feedbackDestinationChannel = 1 - channel;
  537. }
  538. lastFeedback[feedbackDestinationChannel] = feedbackValue;
  539. float mix = clamp(params[MIX_PARAM].value + inputs[MIX_INPUT].value / 10.0f, 0.0f, 1.0f);
  540. float out = crossfade(in, wet, mix); // Not sure this should be wet
  541. outputs[OUT_L_OUTPUT + channel].value = out;
  542. }
  543. }
  544. struct PWStatusDisplay : TransparentWidget {
  545. PortlandWeather *module;
  546. int frame = 0;
  547. std::shared_ptr<Font> fontNumbers,fontText;
  548. PWStatusDisplay() {
  549. fontNumbers = Font::load(assetPlugin(plugin, "res/fonts/01 Digit.ttf"));
  550. fontText = Font::load(assetPlugin(plugin, "res/fonts/DejaVuSansMono.ttf"));
  551. }
  552. void drawProgress(NVGcontext *vg, float phase)
  553. {
  554. const float rotate90 = (M_PI) / 2.0;
  555. float startArc = 0 - rotate90;
  556. float endArc = (phase * M_PI * 2) - rotate90;
  557. // Draw indicator
  558. nvgFillColor(vg, nvgRGBA(0xff, 0xff, 0x20, 0xff));
  559. {
  560. nvgBeginPath(vg);
  561. nvgArc(vg,75.8,170,35,startArc,endArc,NVG_CW);
  562. nvgLineTo(vg,75.8,170);
  563. nvgClosePath(vg);
  564. }
  565. nvgFill(vg);
  566. }
  567. void drawDivision(NVGcontext *vg, Vec pos, int division) {
  568. nvgFontSize(vg, 28);
  569. nvgFontFaceId(vg, fontNumbers->handle);
  570. nvgTextLetterSpacing(vg, -2);
  571. nvgFillColor(vg, nvgRGBA(0x00, 0xff, 0x00, 0xff));
  572. char text[128];
  573. snprintf(text, sizeof(text), "%s", module->divisionNames[division]);
  574. nvgText(vg, pos.x, pos.y, text, NULL);
  575. }
  576. void drawDelayTime(NVGcontext *vg, Vec pos, float delayTime) {
  577. nvgFontSize(vg, 28);
  578. nvgFontFaceId(vg, fontNumbers->handle);
  579. nvgTextLetterSpacing(vg, -2);
  580. nvgFillColor(vg, nvgRGBA(0x00, 0xff, 0x00, 0xff));
  581. char text[128];
  582. snprintf(text, sizeof(text), "%6.0f", delayTime*1000);
  583. nvgText(vg, pos.x, pos.y, text, NULL);
  584. }
  585. void drawGrooveType(NVGcontext *vg, Vec pos, int grooveType) {
  586. nvgFontSize(vg, 14);
  587. nvgFontFaceId(vg, fontText->handle);
  588. nvgTextLetterSpacing(vg, -2);
  589. nvgFillColor(vg, nvgRGBA(0x00, 0xff, 0x00, 0xff));
  590. char text[128];
  591. snprintf(text, sizeof(text), "%s", module->grooveNames[grooveType]);
  592. nvgText(vg, pos.x, pos.y, text, NULL);
  593. }
  594. void drawFeedbackTaps(NVGcontext *vg, Vec pos, int *feedbackTaps) {
  595. nvgFontSize(vg, 12);
  596. nvgFontFaceId(vg, fontNumbers->handle);
  597. nvgTextLetterSpacing(vg, -2);
  598. nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
  599. for(int i=0;i<CHANNELS;i++) {
  600. char text[128];
  601. snprintf(text, sizeof(text), "%s", module->tapNames[feedbackTaps[i]]);
  602. nvgText(vg, pos.x + i*142, pos.y, text, NULL);
  603. }
  604. }
  605. void drawFeedbackPitch(NVGcontext *vg, Vec pos, float *feedbackPitch) {
  606. nvgFontSize(vg, 12);
  607. nvgFontFaceId(vg, fontNumbers->handle);
  608. nvgTextLetterSpacing(vg, -2);
  609. nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
  610. for(int i=0;i<CHANNELS;i++) {
  611. char text[128];
  612. snprintf(text, sizeof(text), "%-2.0f", feedbackPitch[i]);
  613. nvgText(vg, pos.x + i*142, pos.y, text, NULL);
  614. }
  615. }
  616. void drawFeedbackDetune(NVGcontext *vg, Vec pos, float *feedbackDetune) {
  617. nvgFontSize(vg, 12);
  618. nvgFontFaceId(vg, fontNumbers->handle);
  619. nvgTextLetterSpacing(vg, -2);
  620. nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
  621. for(int i=0;i<CHANNELS;i++) {
  622. char text[128];
  623. snprintf(text, sizeof(text), "%-3.0f", feedbackDetune[i]);
  624. nvgText(vg, pos.x + i*142, pos.y, text, NULL);
  625. }
  626. }
  627. void drawFilterTypes(NVGcontext *vg, Vec pos, int *filterType) {
  628. nvgFontSize(vg, 14);
  629. nvgFontFaceId(vg, fontText->handle);
  630. nvgTextLetterSpacing(vg, -2);
  631. nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
  632. for(int i=0;i<NUM_TAPS;i++) {
  633. char text[128];
  634. snprintf(text, sizeof(text), "%s", module->filterNames[filterType[i]]);
  635. nvgText(vg, pos.x + i*50, pos.y, text, NULL);
  636. }
  637. }
  638. void drawTapPitchShift(NVGcontext *vg, Vec pos, float *pitchShift) {
  639. nvgFontSize(vg, 14);
  640. nvgFontFaceId(vg, fontText->handle);
  641. nvgTextLetterSpacing(vg, -2);
  642. nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
  643. for(int i=0;i<NUM_TAPS;i++) {
  644. char text[128];
  645. snprintf(text, sizeof(text), "%-2.0f", pitchShift[i]);
  646. nvgText(vg, pos.x + i*50, pos.y, text, NULL);
  647. }
  648. }
  649. void drawTapDetune(NVGcontext *vg, Vec pos, float *detune) {
  650. nvgFontSize(vg, 14);
  651. nvgFontFaceId(vg, fontText->handle);
  652. nvgTextLetterSpacing(vg, -2);
  653. nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
  654. for(int i=0;i<NUM_TAPS;i++) {
  655. char text[128];
  656. snprintf(text, sizeof(text), "%-3.0f", detune[i]);
  657. nvgText(vg, pos.x + i*50, pos.y, text, NULL);
  658. }
  659. }
  660. void drawGrainNumbers(NVGcontext *vg, Vec pos, int grainNumbers) {
  661. nvgFontSize(vg, 12);
  662. nvgFontFaceId(vg, fontNumbers->handle);
  663. nvgTextLetterSpacing(vg, -2);
  664. nvgFillColor(vg, nvgRGBA(0x00, 0x00, 0x00, 0xff));
  665. char text[128];
  666. snprintf(text, sizeof(text), "%s", module->grainNames[grainNumbers-1]);
  667. nvgText(vg, pos.x, pos.y, text, NULL);
  668. }
  669. void draw(NVGcontext *vg) override {
  670. //drawProgress(vg,module->oscillator.progress());
  671. drawDivision(vg, Vec(150,65), module->division);
  672. drawDelayTime(vg, Vec(350,65), module->baseDelay);
  673. drawGrooveType(vg, Vec(147,125), module->tapGroovePattern);
  674. drawFeedbackTaps(vg, Vec(570,50), module->feedbackTap);
  675. drawFeedbackPitch(vg, Vec(570,150), module->feedbackPitch);
  676. drawFeedbackDetune(vg, Vec(570,200), module->feedbackDetune);
  677. drawFilterTypes(vg, Vec(80,420), module->lastFilterType);
  678. drawTapPitchShift(vg, Vec(78,585), module->tapPitchShift);
  679. drawTapDetune(vg, Vec(78,645), module->tapDetune);
  680. drawGrainNumbers(vg, Vec(800,60), module->grainNumbers);
  681. }
  682. };
  683. struct PortlandWeatherWidget : ModuleWidget {
  684. PortlandWeatherWidget(PortlandWeather *module);
  685. };
  686. PortlandWeatherWidget::PortlandWeatherWidget(PortlandWeather *module) : ModuleWidget(module) {
  687. box.size = Vec(RACK_GRID_WIDTH*57, RACK_GRID_HEIGHT * 2);
  688. {
  689. SVGPanel *panel = new SVGPanel();
  690. panel->box.size = box.size;
  691. panel->setBackground(SVG::load(assetPlugin(plugin, "res/PortlandWeather.svg")));
  692. addChild(panel);
  693. }
  694. addChild(Widget::create<ScrewSilver>(Vec(15, 0)));
  695. addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 0)));
  696. addChild(Widget::create<ScrewSilver>(Vec(15, 745)));
  697. addChild(Widget::create<ScrewSilver>(Vec(box.size.x-30, 745)));
  698. {
  699. PWStatusDisplay *display = new PWStatusDisplay();
  700. display->module = module;
  701. display->box.pos = Vec(0, 0);
  702. display->box.size = Vec(box.size.x, 500);
  703. addChild(display);
  704. }
  705. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(57, 40), module, PortlandWeather::CLOCK_DIV_PARAM, 0, DIVISIONS-1, 0));
  706. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(257, 40), module, PortlandWeather::TIME_PARAM, 0.0f, 10.0f, 0.350f));
  707. //addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(257, 40), module, PortlandWeather::GRID_PARAM, 0.001f, 10.0f, 0.350f));
  708. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(57, 110), module, PortlandWeather::GROOVE_TYPE_PARAM, 0.0f, 15.0f, 0.0f));
  709. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(257, 110), module, PortlandWeather::GROOVE_AMOUNT_PARAM, 0.0f, 1.0f, 1.0f));
  710. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(57, 180), module, PortlandWeather::FEEDBACK_PARAM, 0.0f, 1.0f, 0.0f));
  711. addParam(ParamWidget::create<RoundBlackKnob>(Vec(157, 180), module, PortlandWeather::FEEDBACK_TONE_PARAM, 0.0f, 1.0f, 0.5f));
  712. addParam(ParamWidget::create<RoundBlackKnob>(Vec(500, 30), module, PortlandWeather::FEEDBACK_TAP_L_PARAM, 0.0f, 17.0f, 15.0f));
  713. addParam(ParamWidget::create<RoundBlackKnob>(Vec(642, 30), module, PortlandWeather::FEEDBACK_TAP_R_PARAM, 0.0f, 17.0f, 15.0f));
  714. addParam(ParamWidget::create<RoundBlackKnob>(Vec(500, 80), module, PortlandWeather::FEEDBACK_L_SLIP_PARAM, -0.5f, 0.5f, 0.0f));
  715. addParam(ParamWidget::create<RoundBlackKnob>(Vec(642, 80), module, PortlandWeather::FEEDBACK_R_SLIP_PARAM, -0.5f, 0.5f, 0.0f));
  716. addParam(ParamWidget::create<RoundBlackKnob>(Vec(500, 130), module, PortlandWeather::FEEDBACK_L_PITCH_SHIFT_PARAM, -24.0f, 24.0f, 0.0f));
  717. addParam(ParamWidget::create<RoundBlackKnob>(Vec(642, 130), module, PortlandWeather::FEEDBACK_R_PITCH_SHIFT_PARAM, -24.0f, 24.0f, 0.0f));
  718. addParam(ParamWidget::create<RoundBlackKnob>(Vec(500, 180), module, PortlandWeather::FEEDBACK_L_DETUNE_PARAM, -99.0f, 99.0f, 0.0f));
  719. addParam(ParamWidget::create<RoundBlackKnob>(Vec(642, 180), module, PortlandWeather::FEEDBACK_R_DETUNE_PARAM, -99.0f, 99.0f, 0.0f));
  720. addParam(ParamWidget::create<RoundBlackKnob>(Vec(766, 40), module, PortlandWeather::GRAIN_QUANTITY_PARAM, 1, 4, 1));
  721. //addParam(ParamWidget::create<RoundBlackKnob>(Vec(766, 110), module, PortlandWeather::GRAIN_SIZE_PARAM, 8, 11, 11));
  722. addParam(ParamWidget::create<RoundBlackKnob>(Vec(766, 110), module, PortlandWeather::GRAIN_SIZE_PARAM, 0.0f, 1.0f, 1.0f));
  723. addParam(ParamWidget::create<CKD6>(Vec(766, 180), module, PortlandWeather::CLEAR_BUFFER_PARAM, 0.0f, 1.0f, 0.0f));
  724. addParam( ParamWidget::create<LEDButton>(Vec(372,182), module, PortlandWeather::REVERSE_PARAM, 0.0f, 1.0f, 0.0f));
  725. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(376, 186), module, PortlandWeather::REVERSE_LIGHT));
  726. addInput(Port::create<PJ301MPort>(Vec(392, 178), Port::INPUT, module, PortlandWeather::REVERSE_INPUT));
  727. addParam( ParamWidget::create<LEDButton>(Vec(435,182), module, PortlandWeather::PING_PONG_PARAM, 0.0f, 1.0f, 0.0f));
  728. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(439, 186), module, PortlandWeather::PING_PONG_LIGHT));
  729. addInput(Port::create<PJ301MPort>(Vec(455, 178), Port::INPUT, module, PortlandWeather::PING_PONG_INPUT));
  730. //last tap isn't stacked
  731. for (int i = 0; i< NUM_TAPS-1; i++) {
  732. addParam( ParamWidget::create<LEDButton>(Vec(54 + 50*i,239), module, PortlandWeather::TAP_STACKED_PARAM + i, 0.0f, 1.0f, 0.0f));
  733. addChild(ModuleLightWidget::create<MediumLight<BlueLight>>(Vec(58 + 50*i, 243), module, PortlandWeather::TAP_STACKED_LIGHT+i));
  734. addInput(Port::create<PJ301MPort>(Vec(74+ 50*i, 233), Port::INPUT, module, PortlandWeather::TAP_STACK_CV_INPUT+i));
  735. }
  736. for (int i = 0; i < NUM_TAPS; i++) {
  737. addParam( ParamWidget::create<LEDButton>(Vec(54 + 50*i,260), module, PortlandWeather::TAP_MUTE_PARAM + i, 0.0f, 1.0f, 0.0f));
  738. addChild(ModuleLightWidget::create<MediumLight<RedLight>>(Vec(58 + 50*i, 264), module, PortlandWeather::TAP_MUTED_LIGHT+i));
  739. addInput(Port::create<PJ301MPort>(Vec(74+ 50*i, 260), Port::INPUT, module, PortlandWeather::TAP_MUTE_CV_INPUT+i));
  740. addParam( ParamWidget::create<RoundBlackKnob>(Vec(48 + 50*i, 280), module, PortlandWeather::TAP_MIX_PARAM + i, 0.0f, 1.0f, 0.5f));
  741. addInput(Port::create<PJ301MPort>(Vec(51+ 50*i, 311), Port::INPUT, module, PortlandWeather::TAP_MIX_CV_INPUT+i));
  742. addParam( ParamWidget::create<RoundBlackKnob>(Vec(48 + 50*i, 340), module, PortlandWeather::TAP_PAN_PARAM + i, 0.0f, 1.0f, 0.5f));
  743. addInput(Port::create<PJ301MPort>(Vec(51 + 50*i, 371), Port::INPUT, module, PortlandWeather::TAP_PAN_CV_INPUT+i));
  744. addParam( ParamWidget::create<RoundBlackKnob>(Vec(48 + 50*i, 400), module, PortlandWeather::TAP_FILTER_TYPE_PARAM + i, 0, 4, 0));
  745. addParam( ParamWidget::create<RoundBlackKnob>(Vec(48 + 50*i, 440), module, PortlandWeather::TAP_FC_PARAM + i, 0.0f, 1.0f, 0.5f));
  746. addInput(Port::create<PJ301MPort>(Vec(51 + 50*i, 471), Port::INPUT, module, PortlandWeather::TAP_FC_CV_INPUT+i));
  747. addParam( ParamWidget::create<RoundBlackKnob>(Vec(48 + 50*i, 500), module, PortlandWeather::TAP_Q_PARAM + i, 0.01f, 1.0f, 0.5f));
  748. addInput(Port::create<PJ301MPort>(Vec(51 + 50*i, 531), Port::INPUT, module, PortlandWeather::TAP_Q_CV_INPUT+i));
  749. addParam( ParamWidget::create<RoundBlackKnob>(Vec(48 + 50*i, 560), module, PortlandWeather::TAP_PITCH_SHIFT_PARAM + i, -24.0f, 24.0f, 0.0f));
  750. addInput(Port::create<PJ301MPort>(Vec(51 + 50*i, 591), Port::INPUT, module, PortlandWeather::TAP_PITCH_SHIFT_CV_INPUT+i));
  751. addParam( ParamWidget::create<RoundBlackKnob>(Vec(48 + 50*i, 620), module, PortlandWeather::TAP_DETUNE_PARAM + i, -99.0f, 99.0f, 0.0f));
  752. addInput(Port::create<PJ301MPort>(Vec(51 + 50*i, 651), Port::INPUT, module, PortlandWeather::TAP_DETUNE_CV_INPUT+i));
  753. }
  754. addInput(Port::create<PJ301MPort>(Vec(18, 50), Port::INPUT, module, PortlandWeather::CLOCK_INPUT));
  755. addInput(Port::create<PJ301MPort>(Vec(100, 45), Port::INPUT, module, PortlandWeather::CLOCK_DIVISION_CV_INPUT));
  756. addInput(Port::create<PJ301MPort>(Vec(300, 45), Port::INPUT, module, PortlandWeather::TIME_CV_INPUT));
  757. //addInput(Port::create<PJ301MPort>(Vec(300, 45), Port::INPUT, module, PortlandWeather::GRID_CV_INPUT));
  758. addInput(Port::create<PJ301MPort>(Vec(100, 115), Port::INPUT, module, PortlandWeather::GROOVE_TYPE_CV_INPUT));
  759. addInput(Port::create<PJ301MPort>(Vec(300, 115), Port::INPUT, module, PortlandWeather::GROOVE_AMOUNT_CV_INPUT));
  760. addInput(Port::create<PJ301MPort>(Vec(100, 185), Port::INPUT, module, PortlandWeather::FEEDBACK_INPUT));
  761. addInput(Port::create<PJ301MPort>(Vec(195, 182), Port::INPUT, module, PortlandWeather::FEEDBACK_TONE_INPUT));
  762. addInput(Port::create<PJ301MPort>(Vec(270, 182), Port::INPUT, module, PortlandWeather::EXTERNAL_DELAY_TIME_INPUT));
  763. addInput(Port::create<PJ301MPort>(Vec(538, 32), Port::INPUT, module, PortlandWeather::FEEDBACK_TAP_L_INPUT));
  764. addInput(Port::create<PJ301MPort>(Vec(680, 32), Port::INPUT, module, PortlandWeather::FEEDBACK_TAP_R_INPUT));
  765. addInput(Port::create<PJ301MPort>(Vec(538, 82), Port::INPUT, module, PortlandWeather::FEEDBACK_L_SLIP_CV_INPUT));
  766. addInput(Port::create<PJ301MPort>(Vec(680, 82), Port::INPUT, module, PortlandWeather::FEEDBACK_R_SLIP_CV_INPUT));
  767. addInput(Port::create<PJ301MPort>(Vec(538, 132), Port::INPUT, module, PortlandWeather::FEEDBACK_L_PITCH_SHIFT_CV_INPUT));
  768. addInput(Port::create<PJ301MPort>(Vec(680, 132), Port::INPUT, module, PortlandWeather::FEEDBACK_R_PITCH_SHIFT_CV_INPUT));
  769. addInput(Port::create<PJ301MPort>(Vec(538, 182), Port::INPUT, module, PortlandWeather::FEEDBACK_L_DETUNE_CV_INPUT));
  770. addInput(Port::create<PJ301MPort>(Vec(680, 182), Port::INPUT, module, PortlandWeather::FEEDBACK_R_DETUNE_CV_INPUT));
  771. addParam(ParamWidget::create<RoundLargeBlackKnob>(Vec(440, 705), module, PortlandWeather::MIX_PARAM, 0.0f, 1.0f, 0.5f));
  772. addInput(Port::create<PJ301MPort>(Vec(480, 710), Port::INPUT, module, PortlandWeather::MIX_INPUT));
  773. addInput(Port::create<PJ301MPort>(Vec(75, 710), Port::INPUT, module, PortlandWeather::IN_L_INPUT));
  774. addInput(Port::create<PJ301MPort>(Vec(105, 710), Port::INPUT, module, PortlandWeather::IN_R_INPUT));
  775. addOutput(Port::create<PJ301MPort>(Vec(200, 710), Port::OUTPUT, module, PortlandWeather::FEEDBACK_L_OUTPUT));
  776. addOutput(Port::create<PJ301MPort>(Vec(230, 710), Port::OUTPUT, module, PortlandWeather::FEEDBACK_R_OUTPUT));
  777. addInput(Port::create<PJ301MPort>(Vec(306, 710), Port::INPUT, module, PortlandWeather::FEEDBACK_L_RETURN));
  778. addInput(Port::create<PJ301MPort>(Vec(336, 710), Port::INPUT, module, PortlandWeather::FEEDBACK_R_RETURN));
  779. addOutput(Port::create<PJ301MPort>(Vec(595, 710), Port::OUTPUT, module, PortlandWeather::OUT_L_OUTPUT));
  780. addOutput(Port::create<PJ301MPort>(Vec(625, 710), Port::OUTPUT, module, PortlandWeather::OUT_R_OUTPUT));
  781. }
  782. } // namespace rack_plugin_FrozenWasteland
  783. using namespace rack_plugin_FrozenWasteland;
  784. RACK_PLUGIN_MODEL_INIT(FrozenWasteland, PortlandWeather) {
  785. Model *modelPortlandWeather = Model::create<PortlandWeather, PortlandWeatherWidget>("Frozen Wasteland", "PortlandWeather", "Portland Weather", DELAY_TAG);
  786. return modelPortlandWeather;
  787. }