@@ -9,7 +9,7 @@ namespace rack { | |||
struct Input : Port { | |||
/** Returns the value if a cable is plugged in, otherwise returns the given default value */ | |||
float normalize(float normalVoltage, int channel = 0) { | |||
return active ? getVoltage(channel) : normalVoltage; | |||
return isActive() ? getVoltage(channel) : normalVoltage; | |||
} | |||
}; | |||
@@ -6,15 +6,33 @@ namespace rack { | |||
struct Light { | |||
/** The mean-square of the brightness */ | |||
float value = 0.f; | |||
float getBrightness(); | |||
float getBrightness() { | |||
return std::sqrt(value); | |||
} | |||
void setBrightness(float brightness) { | |||
value = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | |||
} | |||
/** Emulates slow fall (but immediate rise) of LED brightness. | |||
`frames` rescales the timestep. For example, if your module calls this method every 16 frames, use 16.f. | |||
*/ | |||
void setBrightnessSmooth(float brightness, float frames = 1.f); | |||
void setBrightnessSmooth(float brightness, float frames = 1.f) { | |||
float v = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | |||
if (v < value) { | |||
// Fade out light with lambda = framerate | |||
// Use 44.1k here to avoid the call to Engine::getSampleRate(). | |||
// This is close enough to look okay up to 96k | |||
value += (v - value) * frames * 60.f / 44100.f; | |||
} | |||
else { | |||
// Immediately illuminate light | |||
value = v; | |||
} | |||
} | |||
}; | |||
@@ -12,19 +12,22 @@ static const int PORT_MAX_CHANNELS = 16; | |||
struct Port { | |||
/** Voltage of the port */ | |||
union { | |||
/** Accessing this directly is deprecated. | |||
Use getVoltage() and setVoltage() instead | |||
*/ | |||
float value; | |||
float values[PORT_MAX_CHANNELS] = {}; | |||
/** DEPRECATED. Use getVoltage() and setVoltage() instead. */ | |||
float value; | |||
}; | |||
/** Number of polyphonic channels | |||
May be 0 to PORT_MAX_CHANNELS. | |||
*/ | |||
int channels = 1; | |||
/** Whether a cable is plugged in */ | |||
bool active = false; | |||
Light plugLights[2]; | |||
union { | |||
uint8_t channels = 1; | |||
/** DEPRECATED. Use isActive() instead. */ | |||
bool active; | |||
}; | |||
/** For rendering plug lights on cables | |||
Green for positive, red for negative, and blue for polyphonic | |||
*/ | |||
Light plugLights[3]; | |||
float getVoltage(int channel = 0) { | |||
return values[channel]; | |||
@@ -45,6 +48,12 @@ struct Port { | |||
int getChannels() { | |||
return channels; | |||
} | |||
bool isActive() { | |||
return channels; | |||
} | |||
void step(); | |||
}; | |||
@@ -47,7 +47,7 @@ struct MIDI_CC : Module { | |||
float lambda = app()->engine->getSampleTime() * 100.f; | |||
for (int i = 0; i < 16; i++) { | |||
if (!outputs[CC_OUTPUT + i].active) | |||
if (!outputs[CC_OUTPUT + i].isActive()) | |||
continue; | |||
int cc = learnedCcs[i]; | |||
@@ -222,11 +222,15 @@ void CableWidget::draw(NVGcontext *vg) { | |||
float thickness = 5; | |||
if (cable && cable->outputModule) { | |||
// Increase thickness if output port is polyphonic | |||
Output *output = &cable->outputModule->outputs[cable->outputId]; | |||
if (output->channels != 1) { | |||
if (output->channels > 1) { | |||
// Increase thickness if output port is polyphonic | |||
thickness = 7; | |||
} | |||
else if (output->channels == 0) { | |||
// Draw translucent cable if not active (i.e. 0 channels) | |||
nvgGlobalAlpha(vg, 0.5); | |||
} | |||
} | |||
math::Vec outputPos = getOutputPos(); | |||
@@ -12,9 +12,13 @@ void ModuleLightWidget::step() { | |||
std::vector<float> values(baseColors.size()); | |||
for (size_t i = 0; i < baseColors.size(); i++) { | |||
float value = module->lights[firstLightId + i].getBrightness(); | |||
value = math::clamp(value, 0.f, 1.f); | |||
values[i] = value; | |||
float brightness = module->lights[firstLightId + i].getBrightness(); | |||
if (!std::isfinite(brightness)) | |||
brightness = 0.f; | |||
// Because LEDs are nonlinear, this seems to look more natural. | |||
brightness = std::sqrt(brightness); | |||
brightness = math::clamp(brightness, 0.f, 1.f); | |||
values[i] = brightness; | |||
} | |||
setValues(values); | |||
} | |||
@@ -114,13 +114,14 @@ static void Engine_step(Engine *engine) { | |||
} | |||
} | |||
float cpuLambda = engine->internal->sampleTime / 2.f; | |||
// Iterate modules | |||
for (Module *module : engine->modules) { | |||
if (module->bypass) { | |||
// Bypass module | |||
for (Output &output : module->outputs) { | |||
output.channels = 1; | |||
output.setVoltage(0.f); | |||
output.setChannels(0); | |||
} | |||
module->cpuTime = 0.f; | |||
} | |||
@@ -134,8 +135,7 @@ static void Engine_step(Engine *engine) { | |||
auto stopTime = std::chrono::high_resolution_clock::now(); | |||
float cpuTime = std::chrono::duration<float>(stopTime - startTime).count(); | |||
// Smooth cpu time | |||
float powerLambda = engine->internal->sampleTime / 2.f; | |||
module->cpuTime += (cpuTime - module->cpuTime) * powerLambda; | |||
module->cpuTime += (cpuTime - module->cpuTime) * cpuLambda; | |||
} | |||
else { | |||
module->step(); | |||
@@ -144,18 +144,10 @@ static void Engine_step(Engine *engine) { | |||
// Iterate ports and step plug lights | |||
for (Input &input : module->inputs) { | |||
if (input.active) { | |||
float value = input.value / 5.f; | |||
input.plugLights[0].setBrightnessSmooth(value); | |||
input.plugLights[1].setBrightnessSmooth(-value); | |||
} | |||
input.step(); | |||
} | |||
for (Output &output : module->outputs) { | |||
if (output.active) { | |||
float value = output.value / 5.f; | |||
output.plugLights[0].setBrightnessSmooth(value); | |||
output.plugLights[1].setBrightnessSmooth(-value); | |||
} | |||
output.step(); | |||
} | |||
} | |||
@@ -274,23 +266,6 @@ void Engine::randomizeModule(Module *module) { | |||
module->randomize(); | |||
} | |||
static void Engine_updateActive(Engine *engine) { | |||
// Set everything to inactive | |||
for (Module *module : engine->modules) { | |||
for (Input &input : module->inputs) { | |||
input.active = false; | |||
} | |||
for (Output &output : module->outputs) { | |||
output.active = false; | |||
} | |||
} | |||
// Set inputs/outputs to active | |||
for (Cable *cable : engine->cables) { | |||
cable->outputModule->outputs[cable->outputId].active = true; | |||
cable->inputModule->inputs[cable->inputId].active = true; | |||
} | |||
} | |||
void Engine::addCable(Cable *cable) { | |||
assert(cable); | |||
VIPLock vipLock(internal->vipMutex); | |||
@@ -318,7 +293,6 @@ void Engine::addCable(Cable *cable) { | |||
} | |||
// Add the cable | |||
cables.push_back(cable); | |||
Engine_updateActive(this); | |||
} | |||
void Engine::removeCable(Cable *cable) { | |||
@@ -328,11 +302,11 @@ void Engine::removeCable(Cable *cable) { | |||
// Check that the cable is already added | |||
auto it = std::find(cables.begin(), cables.end(), cable); | |||
assert(it != cables.end()); | |||
// Set input to 0V | |||
cable->inputModule->inputs[cable->inputId].value = 0.f; | |||
// Set input to inactive | |||
Input &input = cable->inputModule->inputs[cable->inputId]; | |||
input.setChannels(0); | |||
// Remove the cable | |||
cables.erase(it); | |||
Engine_updateActive(this); | |||
// Remove ID | |||
cable->id = 0; | |||
} | |||
@@ -1,28 +0,0 @@ | |||
#include "engine/Light.hpp" | |||
#include "engine/Engine.hpp" | |||
#include "app.hpp" | |||
namespace rack { | |||
float Light::getBrightness() { | |||
// LEDs are diodes, so don't allow reverse current. | |||
// For some reason, instead of the RMS, the sqrt of RMS looks better | |||
return std::pow(std::fmax(0.f, value), 0.25f); | |||
} | |||
void Light::setBrightnessSmooth(float brightness, float frames) { | |||
float v = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | |||
if (v < value) { | |||
// Fade out light with lambda = framerate | |||
value += (v - value) * app()->engine->getSampleTime() * frames * 60.f; | |||
} | |||
else { | |||
// Immediately illuminate light | |||
value = v; | |||
} | |||
} | |||
} // namespace rack |
@@ -0,0 +1,32 @@ | |||
#include "engine/Port.hpp" | |||
namespace rack { | |||
void Port::step() { | |||
if (channels == 0) { | |||
plugLights[0].setBrightness(0.f); | |||
plugLights[1].setBrightness(0.f); | |||
plugLights[2].setBrightness(0.f); | |||
} | |||
else if (channels == 1) { | |||
float v = getVoltage() / 10.f; | |||
plugLights[0].setBrightnessSmooth(v); | |||
plugLights[1].setBrightnessSmooth(-v); | |||
plugLights[2].setBrightness(0.f); | |||
} | |||
else { | |||
float v2 = 0.f; | |||
for (int c = 0; c < channels; c++) { | |||
v2 += std::pow(getVoltage(c), 2); | |||
} | |||
float v = std::sqrt(v2) / 10.f; | |||
plugLights[0].setBrightness(0.f); | |||
plugLights[1].setBrightness(0.f); | |||
plugLights[2].setBrightnessSmooth(v); | |||
} | |||
} | |||
} // namespace rack |