Hi all, first time posting so my apologies if I fail to provide adequate information for my particular question. I have a single module in the Libary: Planetary LFOs by HawthornLabs, which creates LFO’s based on the length of each planet’s revolution around the sun. Currently the 9 LFO’s all start at zero and then begin to oscillate at their various (very slow) frequencies.
I’m attempting to code start positions for them so they don’t all start at zero every time and am doing so by calculating the amount of time passed since the epoch and doing modulo math to figure out where in their revolution they would be right now. (a stretch goal is to actually figure out where the planets were on 1/1/1970 but currently it assumes magical planetary alignment on that date)
In a sandbox file I can generate the offsets and then multiply them by 2*pi and a gain of 5.0f to get the current starting point:
#include <ctime>
#include <chrono>
#include <iostream>
int timeSinceEpoch = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
long planetDur[9] = {7603200, 19414080, 31553280, 59356800, 374198400, 928540800, 2642889600, 5166720000, 7824384000};
double phase[9];
float _2PI = 2.f * M_PI;
int main(){
for (int i = 0; i < 9; i ++){
phase[i] = ((double)(timeSinceEpoch % planetDur[i]) / (double)planetDur[i])*_2PI;
std::cout << phase[i]
<< ", "
<< 5.0f * sin(phase[i])
<< '\n';
};
}
Which results in:
5.1842, -4.45374
0.0449624, 0.224736
2.38472, 3.43326
2.75846, 1.86916
4.20805, -4.37747
5.54104, -3.37935
4.15427, -4.24126
2.125, 4.2516
1.40321, 4.92995
and those numbers look plausible. Especially the second set of numbers would seem to suggest that the LFO’s should start all over the +/- 5V range when the module is instantiated.
However, when I put my local build of the module into VCV Rack, I’m getting 1.7v from the fastest LFO (Mercury) then 0.7v, 0.48v, and it keeps descending towards zero as the frequency of the LFO decreases. This feels like I’m screwing up my math somehow, but I can’t figure out where the mismatch is from my sandbox file and the module file. Here’s the module file:
#include "plugin.hpp"
#include <chrono>
#include <ctime>
struct PlanetaryLFOs : Module {
enum ParamId {
SPEED_PARAM,
PARAMS_LEN
};
enum InputId {
INPUTS_LEN
};
enum OutputId {
ENUMS(TR, 9),
ENUMS(LFO, 9),
OUTPUTS_LEN
};
enum LightIds {
ENUMS(LIGHT, 6),
LIGHTS_LEN
};
double mercury = 0.000000131523569023569; // was 7603200
double venus = 0.0000000515090078953007; // was 19414080
double earth = 0.0000000316924262707395; // was 31553280
double mars = 0.0000000168472693945765; // was 59356800
double jupiter = 0.00000000267237914432558; // was 374198400
double saturn = 0.00000000107695859998828; // was 928540800
double uranus = 0.000000000378373731539902; // was 2642889600
double neptune = 0.000000000193546389198563; // was 5166720000
double pluto = 0.000000000127805588273786; // was 7824384000
long timeSinceEpoch = std::chrono::duration_cast<std::chrono::hours>(std::chrono::system_clock::now().time_since_epoch()).count();
long planetDur[9] = {7603200, 19414080, 31553280, 59356800, 374198400, 928540800, 2642889600, 5166720000, 7824384000}; // duration of each planet's year in seconds, used against timeSinceEpoch to figure out starting phase.
double phase[9];
// float realtime = 1.f;
// float inADay = 365.2f;
// float inAnHour = 8764.8f;
// float inAMin = 525888.f;
// float inASec = 31553280.f;
// float inAMs = 31553280000.f;
int counter = 2;
int speedKnob = 1;
float timeCompression = 1.f;
float _gain = 5.f;
float _2PI = 2.f * M_PI;
dsp::PulseGenerator trig[9];
double planet[9] = {mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto};
double freq[9] = {mercury, venus, earth, mars, jupiter, saturn, uranus, neptune, pluto};
PlanetaryLFOs() {
for (int i = 0; i < 9; i ++){phase[i] = ((double)(timeSinceEpoch % planetDur[i]) / (double)planetDur[i])*_2PI;};
config(PARAMS_LEN, INPUTS_LEN, OUTPUTS_LEN, LIGHTS_LEN);
configParam(SPEED_PARAM, 1, 1100, 1, "Time Compression");
configOutput(TR + 0, "Mercury Trig");
configOutput(TR + 1, "Venus Trig");
configOutput(TR + 2, "Earth Trig");
configOutput(TR + 3, "Mars Trig");
configOutput(TR + 4, "Jupiter Trig");
configOutput(TR + 5, "Saturn Trig");
configOutput(TR + 6, "Uranus Trig");
configOutput(TR + 7, "Neptune Trig");
configOutput(TR + 8, "Pluto Trig");
configOutput(LFO + 0, "Mercury LFO");
configOutput(LFO + 1, "Venus LFO");
configOutput(LFO + 2, "Earth LFO");
configOutput(LFO + 3, "Mars LFO");
configOutput(LFO + 4, "Jupiter LFO");
configOutput(LFO + 5, "Saturn LFO");
configOutput(LFO + 6, "Uranus LFO");
configOutput(LFO + 7, "Neptune LFO");
configOutput(LFO + 8, "Pluto LFO");
}
void process(const ProcessArgs& args) override {
if (counter % 2 == 0){ // LFO's are slow, so run every other sample (could do half and half)
for (int i = 0; i < 9; i ++){
if (outputs[LFO + i].isConnected() || outputs[TR + i].isConnected()) {
freq[i] = planet[i] * timeCompression;
double phase_increment = _2PI * freq[i] / args.sampleRate; // calc phase increment
phase[i] += phase_increment; // push osc forward 2 steps
if (phase[i] >= _2PI){
phase[i] -= _2PI;
trig[i].trigger(0.01f);
};
double sine_output = _gain * sin(phase[i]);
outputs[LFO + i].setVoltage(sine_output);
outputs[TR + i].setVoltage(trig[i].process(args.sampleTime) ? 10.0f : 0.0f);
};
};
};
if (counter % 8 == 0){ // check speed knob every 8 samples
if (speedKnob != params[SPEED_PARAM].getValue())
updateTimeCompression();
};
counter++;
if (counter > args.sampleRate)
counter = 1;
}
void updateTimeCompression(){
speedKnob = params[SPEED_PARAM].getValue();
if (speedKnob <= 2){
timeCompression = 1.f;
lights[LIGHT + 0].setSmoothBrightness(1.f, 0.1f);
} else if (speedKnob > 2 && speedKnob <=218){
timeCompression = scaleKnobValue(speedKnob, 1, 218, 1.f, 365.2f);
refreshLights();
} else if (speedKnob > 218 && speedKnob <= 222){
timeCompression = 365.2f;
lights[LIGHT + 1].setSmoothBrightness(1.f, 0.1f);
} else if (speedKnob > 222 && speedKnob <= 428){
timeCompression = scaleKnobValue(speedKnob, 222, 428, 365.2f, 8764.8f);
refreshLights();
} else if (speedKnob > 428 && speedKnob <= 432){
timeCompression = 8764.8f;
lights[LIGHT + 2].setSmoothBrightness(1.f, 0.1f);
} else if (speedKnob > 432 && speedKnob <= 648){
timeCompression = scaleKnobValue(speedKnob, 432, 648, 8764.8f, 525888.f);
refreshLights();
} else if (speedKnob > 648 && speedKnob <= 652){
timeCompression = 525888.f;
lights[LIGHT + 3].setSmoothBrightness(1.f, 0.1f);
} else if (speedKnob > 652 && speedKnob <= 878){
timeCompression = scaleKnobValue(speedKnob, 652, 878, 525888.f, 31553280.f);
refreshLights();
} else if (speedKnob > 878 && speedKnob <= 882){
timeCompression = 31553280.f;
lights[LIGHT + 4].setSmoothBrightness(1.f, 0.1f);
} else if (speedKnob > 882 && speedKnob < 1097){
timeCompression = scaleKnobValue(speedKnob, 882, 1097, 31553280.f, 31553280000.f);
refreshLights();
} else if (speedKnob >= 1097){
timeCompression = 31553280000.f;
lights[LIGHT + 5].setSmoothBrightness(1.f, 0.1f);
} else {
timeCompression = 1.f;
refreshLights();
};
}
float scaleKnobValue(float speedKnob, int knobMin, int knobMax, float scaleMin, float scaleMax){
float knobPercent = (speedKnob - knobMin)/(knobMax - knobMin);
return (knobPercent) * (scaleMax - scaleMin) + scaleMin;
}
void refreshLights(){
for(int i = 0; i<6; i++){
lights[LIGHT + i].setSmoothBrightness(0.f, 0.1f);
};
}
};
I’ve left out the Struct portion in hopes of this post not being a mile long… Thanks all in advance
P.S. I’ve gotten one user reporting the LFO’s freeze sometimes and they think it might be how I’m only processing the LFO’s once every other sample, to try to cut down on processor load. I welcome any comments on my code, this is my first C++ project.