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.

200 lines
6.2KB

  1. #include "plugin.hpp"
  2. /** Based on "The Voss algorithm"
  3. http://www.firstpr.com.au/dsp/pink-noise/
  4. */
  5. template <int QUALITY = 8>
  6. struct PinkNoiseGenerator {
  7. int frame = -1;
  8. float values[QUALITY] = {};
  9. float process() {
  10. int lastFrame = frame;
  11. frame++;
  12. if (frame >= (1 << QUALITY))
  13. frame = 0;
  14. int diff = lastFrame ^ frame;
  15. float sum = 0.f;
  16. for (int i = 0; i < QUALITY; i++) {
  17. if (diff & (1 << i)) {
  18. values[i] = random::uniform() - 0.5f;
  19. }
  20. sum += values[i];
  21. }
  22. return sum;
  23. }
  24. };
  25. struct InverseAWeightingFFTFilter {
  26. static constexpr int BUFFER_LEN = 1024;
  27. alignas(16) float inputBuffer[BUFFER_LEN] = {};
  28. alignas(16) float outputBuffer[BUFFER_LEN] = {};
  29. int frame = 0;
  30. dsp::RealFFT fft;
  31. InverseAWeightingFFTFilter() : fft(BUFFER_LEN) {}
  32. float process(float deltaTime, float x) {
  33. inputBuffer[frame] = x;
  34. if (++frame >= BUFFER_LEN) {
  35. frame = 0;
  36. alignas(16) float freqBuffer[BUFFER_LEN * 2];
  37. fft.rfft(inputBuffer, freqBuffer);
  38. for (int i = 0; i < BUFFER_LEN; i++) {
  39. float f = 1 / deltaTime / 2 / BUFFER_LEN * i;
  40. float amp = 0.f;
  41. if (80.f <= f && f <= 20000.f) {
  42. float f2 = f * f;
  43. // Inverse A-weighted curve
  44. amp = ((424.36f + f2) * std::sqrt((11599.3f + f2) * (544496.f + f2)) * (148693636.f + f2)) / (148693636.f * f2 * f2);
  45. }
  46. freqBuffer[2 * i + 0] *= amp / BUFFER_LEN;
  47. freqBuffer[2 * i + 1] *= amp / BUFFER_LEN;
  48. }
  49. fft.irfft(freqBuffer, outputBuffer);
  50. }
  51. return outputBuffer[frame];
  52. }
  53. };
  54. struct Noise : Module {
  55. enum ParamIds {
  56. NUM_PARAMS
  57. };
  58. enum InputIds {
  59. NUM_INPUTS
  60. };
  61. enum OutputIds {
  62. WHITE_OUTPUT,
  63. PINK_OUTPUT,
  64. RED_OUTPUT,
  65. VIOLET_OUTPUT,
  66. BLUE_OUTPUT,
  67. GRAY_OUTPUT,
  68. BLACK_OUTPUT,
  69. NUM_OUTPUTS
  70. };
  71. enum LightIds {
  72. NUM_LIGHTS
  73. };
  74. dsp::ClockDivider blackDivider;
  75. PinkNoiseGenerator<8> pinkNoiseGenerator;
  76. dsp::IIRFilter<2, 2> redFilter;
  77. float lastWhite = 0.f;
  78. float lastPink = 0.f;
  79. InverseAWeightingFFTFilter grayFilter;
  80. // For calibrating levels
  81. // float meanSqr = 0.f;
  82. Noise() {
  83. config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS);
  84. configOutput(WHITE_OUTPUT, "White noise");
  85. outputInfos[WHITE_OUTPUT]->description = "0 dB/octave power density";
  86. configOutput(PINK_OUTPUT, "Pink noise");
  87. outputInfos[PINK_OUTPUT]->description = "-3 dB/octave power density";
  88. configOutput(RED_OUTPUT, "Red noise");
  89. outputInfos[RED_OUTPUT]->description = "-6 dB/octave power density";
  90. configOutput(VIOLET_OUTPUT, "Violet noise");
  91. outputInfos[VIOLET_OUTPUT]->description = "+6 dB/octave power density";
  92. configOutput(BLUE_OUTPUT, "Blue noise");
  93. outputInfos[BLUE_OUTPUT]->description = "+3 dB/octave power density";
  94. configOutput(GRAY_OUTPUT, "Gray noise");
  95. outputInfos[GRAY_OUTPUT]->description = "Psychoacoustic equal loudness";
  96. configOutput(BLACK_OUTPUT, "Black noise");
  97. outputInfos[BLACK_OUTPUT]->description = "Uniform random numbers";
  98. // Hard-code coefficients for Butterworth lowpass with cutoff 20 Hz @ 44.1kHz.
  99. const float b[] = {0.00425611, 0.00425611};
  100. const float a[] = {-0.99148778};
  101. redFilter.setCoefficients(b, a);
  102. }
  103. void process(const ProcessArgs& args) override {
  104. // All noise is calibrated to 1 RMS.
  105. // Then they should be scaled to match the RMS of a sine wave with 5V amplitude.
  106. const float gain = 5.f / std::sqrt(2.f);
  107. if (outputs[WHITE_OUTPUT].isConnected() || outputs[RED_OUTPUT].isConnected() || outputs[VIOLET_OUTPUT].isConnected() || outputs[GRAY_OUTPUT].isConnected()) {
  108. // White noise: equal power density
  109. float white = random::normal();
  110. outputs[WHITE_OUTPUT].setVoltage(white * gain);
  111. // Red/Brownian noise: -6dB/oct
  112. if (outputs[RED_OUTPUT].isConnected()) {
  113. float red = redFilter.process(white) / 0.0645f;
  114. outputs[RED_OUTPUT].setVoltage(red * gain);
  115. }
  116. // Violet/purple noise: 6dB/oct
  117. if (outputs[VIOLET_OUTPUT].isConnected()) {
  118. float violet = (white - lastWhite) / 1.41f;
  119. lastWhite = white;
  120. outputs[VIOLET_OUTPUT].setVoltage(violet * gain);
  121. }
  122. // Gray noise: psychoacoustic equal loudness curve, specifically inverted A-weighted
  123. if (outputs[GRAY_OUTPUT].isConnected()) {
  124. float gray = grayFilter.process(args.sampleTime, white) / 1.67f;
  125. outputs[GRAY_OUTPUT].setVoltage(gray * gain);
  126. }
  127. }
  128. if (outputs[PINK_OUTPUT].isConnected() || outputs[BLUE_OUTPUT].isConnected()) {
  129. // Pink noise: -3dB/oct
  130. float pink = pinkNoiseGenerator.process() / 0.816f;
  131. outputs[PINK_OUTPUT].setVoltage(pink * gain);
  132. // Blue noise: 3dB/oct
  133. if (outputs[BLUE_OUTPUT].isConnected()) {
  134. float blue = (pink - lastPink) / 0.705f;
  135. lastPink = pink;
  136. outputs[BLUE_OUTPUT].setVoltage(blue * gain);
  137. }
  138. }
  139. // Black noise: uniform noise
  140. // Note: I made this definition up.
  141. if (outputs[BLACK_OUTPUT].isConnected()) {
  142. float u = random::uniform();
  143. outputs[BLACK_OUTPUT].setVoltage(u * 10.f - 5.f);
  144. }
  145. // meanSqr += (std::pow(gray, 2.f) - meanSqr) * args.sampleTime / 10.f;
  146. // DEBUG("%f", std::sqrt(meanSqr));
  147. }
  148. };
  149. struct NoiseWidget : ModuleWidget {
  150. NoiseWidget(Noise* module) {
  151. setModule(module);
  152. setPanel(createPanel(asset::plugin(pluginInstance, "res/Noise.svg"), asset::plugin(pluginInstance, "res/Noise-dark.svg")));
  153. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
  154. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
  155. addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  156. addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));
  157. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.62, 21.897)), module, Noise::WHITE_OUTPUT));
  158. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.62, 37.102)), module, Noise::PINK_OUTPUT));
  159. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.62, 52.31)), module, Noise::RED_OUTPUT));
  160. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.62, 67.53)), module, Noise::VIOLET_OUTPUT));
  161. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.62, 82.732)), module, Noise::BLUE_OUTPUT));
  162. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.62, 97.923)), module, Noise::GRAY_OUTPUT));
  163. addOutput(createOutputCentered<ThemedPJ301MPort>(mm2px(Vec(7.62, 113.115)), module, Noise::BLACK_OUTPUT));
  164. }
  165. };
  166. Model* modelNoise = createModel<Noise, NoiseWidget>("Noise");