Browse Source

Noise: Cytomic mod

pull/150/head
Steve Russell 7 months ago
parent
commit
919b8cc2d9
1 changed files with 68 additions and 26 deletions
  1. +68
    -26
      src/Noise.cpp

+ 68
- 26
src/Noise.cpp View File

@@ -1,23 +1,27 @@
/** Cytomic mod by Andy
https://github.com/VCVRack/Fundamental/pull/143
*/
#include "plugin.hpp"


/** Based on "The Voss algorithm"
http://www.firstpr.com.au/dsp/pink-noise/
*/
template <int QUALITY = 8>
template <int MAX_OCTAVES = 16>
struct PinkNoiseGenerator {
int frame = -1;
float values[QUALITY] = {};
float values[MAX_OCTAVES] = {};
int octaves = 8;

float process() {
int lastFrame = frame;
frame++;
if (frame >= (1 << QUALITY))
if (frame >= (1 << octaves))
frame = 0;
int diff = lastFrame ^ frame;

float sum = 0.f;
for (int i = 0; i < QUALITY; i++) {
for (int i = 0; i < octaves; i++) {
if (diff & (1 << i)) {
values[i] = random::uniform() - 0.5f;
}
@@ -38,7 +42,7 @@ struct InverseAWeightingFFTFilter {

InverseAWeightingFFTFilter() : fft(BUFFER_LEN) {}

float process(float deltaTime, float x) {
float process(float sampleRate, float x) {
inputBuffer[frame] = x;
if (++frame >= BUFFER_LEN) {
frame = 0;
@@ -46,7 +50,7 @@ struct InverseAWeightingFFTFilter {
fft.rfft(inputBuffer, freqBuffer);

for (int i = 0; i < BUFFER_LEN; i++) {
float f = 1 / deltaTime / 2 / BUFFER_LEN * i;
float f = sampleRate / 2 / BUFFER_LEN * i;
float amp = 0.f;
if (80.f <= f && f <= 20000.f) {
float f2 = f * f;
@@ -64,6 +68,12 @@ struct InverseAWeightingFFTFilter {
};


template <typename T>
static T softclip(T x) {
// rational approximation of 12*tanh(x/12) with derivative 0 at x = 24
x = simd::clamp(x, -24.f, 24.f);
return x * (3888 + x * x) / (3888 + 9 * x * x);
}


struct Noise : Module {
@@ -88,10 +98,18 @@ struct Noise : Module {
};

dsp::ClockDivider blackDivider;
PinkNoiseGenerator<8> pinkNoiseGenerator;
PinkNoiseGenerator<16> pinkNoiseGenerator;
dsp::IIRFilter<2, 2> whiteFilter;
dsp::IIRFilter<2, 2> pinkFilter;
dsp::IIRFilter<2, 2> redFilter;
dsp::IIRFilter<2, 2> violetFilter;
dsp::IIRFilter<2, 2> blueFilter;
float lastWhite = 0.f;
float lastPink = 0.f;
float gainWhite = 0.f;
float gainWhiteDifferenced = 0.f;
float gainPink = 0.f;
float gainPinkDifferenced = 0.f;
InverseAWeightingFFTFilter grayFilter;

// For calibrating levels
@@ -113,53 +131,78 @@ struct Noise : Module {
outputInfos[GRAY_OUTPUT]->description = "Psychoacoustic equal loudness";
configOutput(BLACK_OUTPUT, "Black noise");
outputInfos[BLACK_OUTPUT]->description = "Uniform random numbers";

// Hard-code coefficients for Butterworth lowpass with cutoff 20 Hz @ 44.1kHz.
const float b[] = {0.00425611, 0.00425611};
const float a[] = {-0.99148778};
redFilter.setCoefficients(b, a);
}

void process(const ProcessArgs& args) override {
void onSampleRateChange(const SampleRateChangeEvent& e) override {
// All noise is calibrated to 1 RMS.
// Then they should be scaled to match the RMS of a sine wave with 5V amplitude.
const float gain = 5.f / std::sqrt(2.f);
// Also normalised by bandwidth of sample rate
gainWhite = 5.f * std::sqrt(e.sampleRate)/(std::sqrt(44100.f)*std::sqrt(2.f));
gainWhiteDifferenced = gainWhite*e.sampleRate/44100.f;
gainPink = 5.f/std::sqrt(2.f);
gainPinkDifferenced = gainPink*e.sampleRate/44100.f;

// DF1 Butterworth lowpass with cutoff 18000 Hz to keep amplitude down
{
const float g = std::tan(M_PI*fmin(18000.f*e.sampleTime, 0.49f));
const float gnrm = 1.f/(1.f + g);
const float b[] = {g*gnrm, g*gnrm};
const float a[] = {(g - 1.f)*gnrm};
whiteFilter.setCoefficients(b, a);
pinkFilter.setCoefficients(b, a);
violetFilter.setCoefficients(b, a);
blueFilter.setCoefficients(b, a);
}

// DF1 Butterworth lowpass with cutoff 60 Hz
{
const float g = std::tan(M_PI*fmin(60.f*e.sampleTime, 0.49f));
const float gnrm = 1.f/(1.f + g);
const float b[] = {g*gnrm, g*gnrm};
const float a[] = {(g - 1.f)*gnrm};
redFilter.setCoefficients(b, a);
}
const int octaves = clamp(int(std::log2(e.sampleRate) + 1) - 8, 4, 16);
pinkNoiseGenerator.octaves = octaves;
}

void process(const ProcessArgs& args) override {

if (outputs[WHITE_OUTPUT].isConnected() || outputs[RED_OUTPUT].isConnected() || outputs[VIOLET_OUTPUT].isConnected() || outputs[GRAY_OUTPUT].isConnected()) {
// White noise: equal power density
float white = random::normal();
outputs[WHITE_OUTPUT].setVoltage(white * gain);
float white = whiteFilter.process(random::normal());
outputs[WHITE_OUTPUT].setVoltage(softclip(white * gainWhite));

// Red/Brownian noise: -6dB/oct
if (outputs[RED_OUTPUT].isConnected()) {
float red = redFilter.process(white) / 0.0645f;
outputs[RED_OUTPUT].setVoltage(red * gain);
outputs[RED_OUTPUT].setVoltage(softclip(red * gainWhite));
}

// Violet/purple noise: 6dB/oct
if (outputs[VIOLET_OUTPUT].isConnected()) {
float violet = (white - lastWhite) / 1.41f;
float violet = violetFilter.process((white - lastWhite) / 1.41f);
lastWhite = white;
outputs[VIOLET_OUTPUT].setVoltage(violet * gain);
outputs[VIOLET_OUTPUT].setVoltage(softclip(violet * gainWhiteDifferenced));
}

// Gray noise: psychoacoustic equal loudness curve, specifically inverted A-weighted
if (outputs[GRAY_OUTPUT].isConnected()) {
float gray = grayFilter.process(args.sampleTime, white) / 1.67f;
outputs[GRAY_OUTPUT].setVoltage(gray * gain);
float gray = grayFilter.process(args.sampleRate, white) / 1.67f;
outputs[GRAY_OUTPUT].setVoltage(softclip(gray * gainWhite));
}
}

if (outputs[PINK_OUTPUT].isConnected() || outputs[BLUE_OUTPUT].isConnected()) {
// Pink noise: -3dB/oct
float pink = pinkNoiseGenerator.process() / 0.816f;
outputs[PINK_OUTPUT].setVoltage(pink * gain);
float pink = pinkFilter.process(pinkNoiseGenerator.process() / 0.816f);
outputs[PINK_OUTPUT].setVoltage(softclip(pink * gainPink));

// Blue noise: 3dB/oct
if (outputs[BLUE_OUTPUT].isConnected()) {
float blue = (pink - lastPink) / 0.705f;
float blue = blueFilter.process((pink - lastPink) / 0.705f);
lastPink = pink;
outputs[BLUE_OUTPUT].setVoltage(blue * gain);
outputs[BLUE_OUTPUT].setVoltage(softclip(blue * gainPinkDifferenced));
}
}

@@ -180,7 +223,6 @@ struct NoiseWidget : ModuleWidget {
NoiseWidget(Noise* module) {
setModule(module);
setPanel(createPanel(asset::plugin(pluginInstance, "res/Noise.svg"), asset::plugin(pluginInstance, "res/Noise-dark.svg")));

addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, 0)));
addChild(createWidget<ThemedScrew>(Vec(box.size.x - 2 * RACK_GRID_WIDTH, 0)));
addChild(createWidget<ThemedScrew>(Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT - RACK_GRID_WIDTH)));


Loading…
Cancel
Save