@@ -9,7 +9,7 @@ namespace rack { | |||||
struct Input : Port { | struct Input : Port { | ||||
/** Returns the value if a cable is plugged in, otherwise returns the given default value */ | /** Returns the value if a cable is plugged in, otherwise returns the given default value */ | ||||
float normalize(float normalVoltage, int channel = 0) { | 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 { | struct Light { | ||||
/** The mean-square of the brightness */ | |||||
float value = 0.f; | float value = 0.f; | ||||
float getBrightness(); | |||||
float getBrightness() { | |||||
return std::sqrt(value); | |||||
} | |||||
void setBrightness(float brightness) { | void setBrightness(float brightness) { | ||||
value = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | value = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | ||||
} | } | ||||
/** Emulates slow fall (but immediate rise) of LED brightness. | /** 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. | `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 { | struct Port { | ||||
/** Voltage of the port */ | /** Voltage of the port */ | ||||
union { | union { | ||||
/** Accessing this directly is deprecated. | |||||
Use getVoltage() and setVoltage() instead | |||||
*/ | |||||
float value; | |||||
float values[PORT_MAX_CHANNELS] = {}; | float values[PORT_MAX_CHANNELS] = {}; | ||||
/** DEPRECATED. Use getVoltage() and setVoltage() instead. */ | |||||
float value; | |||||
}; | }; | ||||
/** Number of polyphonic channels | /** Number of polyphonic channels | ||||
May be 0 to PORT_MAX_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) { | float getVoltage(int channel = 0) { | ||||
return values[channel]; | return values[channel]; | ||||
@@ -45,6 +48,12 @@ struct Port { | |||||
int getChannels() { | int getChannels() { | ||||
return channels; | return channels; | ||||
} | } | ||||
bool isActive() { | |||||
return channels; | |||||
} | |||||
void step(); | |||||
}; | }; | ||||
@@ -47,7 +47,7 @@ struct MIDI_CC : Module { | |||||
float lambda = app()->engine->getSampleTime() * 100.f; | float lambda = app()->engine->getSampleTime() * 100.f; | ||||
for (int i = 0; i < 16; i++) { | for (int i = 0; i < 16; i++) { | ||||
if (!outputs[CC_OUTPUT + i].active) | |||||
if (!outputs[CC_OUTPUT + i].isActive()) | |||||
continue; | continue; | ||||
int cc = learnedCcs[i]; | int cc = learnedCcs[i]; | ||||
@@ -222,11 +222,15 @@ void CableWidget::draw(NVGcontext *vg) { | |||||
float thickness = 5; | float thickness = 5; | ||||
if (cable && cable->outputModule) { | if (cable && cable->outputModule) { | ||||
// Increase thickness if output port is polyphonic | |||||
Output *output = &cable->outputModule->outputs[cable->outputId]; | Output *output = &cable->outputModule->outputs[cable->outputId]; | ||||
if (output->channels != 1) { | |||||
if (output->channels > 1) { | |||||
// Increase thickness if output port is polyphonic | |||||
thickness = 7; | 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(); | math::Vec outputPos = getOutputPos(); | ||||
@@ -12,9 +12,13 @@ void ModuleLightWidget::step() { | |||||
std::vector<float> values(baseColors.size()); | std::vector<float> values(baseColors.size()); | ||||
for (size_t i = 0; i < baseColors.size(); i++) { | 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); | setValues(values); | ||||
} | } | ||||
@@ -114,13 +114,14 @@ static void Engine_step(Engine *engine) { | |||||
} | } | ||||
} | } | ||||
float cpuLambda = engine->internal->sampleTime / 2.f; | |||||
// Iterate modules | // Iterate modules | ||||
for (Module *module : engine->modules) { | for (Module *module : engine->modules) { | ||||
if (module->bypass) { | if (module->bypass) { | ||||
// Bypass module | // Bypass module | ||||
for (Output &output : module->outputs) { | for (Output &output : module->outputs) { | ||||
output.channels = 1; | |||||
output.setVoltage(0.f); | |||||
output.setChannels(0); | |||||
} | } | ||||
module->cpuTime = 0.f; | module->cpuTime = 0.f; | ||||
} | } | ||||
@@ -134,8 +135,7 @@ static void Engine_step(Engine *engine) { | |||||
auto stopTime = std::chrono::high_resolution_clock::now(); | auto stopTime = std::chrono::high_resolution_clock::now(); | ||||
float cpuTime = std::chrono::duration<float>(stopTime - startTime).count(); | float cpuTime = std::chrono::duration<float>(stopTime - startTime).count(); | ||||
// Smooth cpu time | // Smooth cpu time | ||||
float powerLambda = engine->internal->sampleTime / 2.f; | |||||
module->cpuTime += (cpuTime - module->cpuTime) * powerLambda; | |||||
module->cpuTime += (cpuTime - module->cpuTime) * cpuLambda; | |||||
} | } | ||||
else { | else { | ||||
module->step(); | module->step(); | ||||
@@ -144,18 +144,10 @@ static void Engine_step(Engine *engine) { | |||||
// Iterate ports and step plug lights | // Iterate ports and step plug lights | ||||
for (Input &input : module->inputs) { | 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) { | 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(); | 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) { | void Engine::addCable(Cable *cable) { | ||||
assert(cable); | assert(cable); | ||||
VIPLock vipLock(internal->vipMutex); | VIPLock vipLock(internal->vipMutex); | ||||
@@ -318,7 +293,6 @@ void Engine::addCable(Cable *cable) { | |||||
} | } | ||||
// Add the cable | // Add the cable | ||||
cables.push_back(cable); | cables.push_back(cable); | ||||
Engine_updateActive(this); | |||||
} | } | ||||
void Engine::removeCable(Cable *cable) { | void Engine::removeCable(Cable *cable) { | ||||
@@ -328,11 +302,11 @@ void Engine::removeCable(Cable *cable) { | |||||
// Check that the cable is already added | // Check that the cable is already added | ||||
auto it = std::find(cables.begin(), cables.end(), cable); | auto it = std::find(cables.begin(), cables.end(), cable); | ||||
assert(it != cables.end()); | 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 | // Remove the cable | ||||
cables.erase(it); | cables.erase(it); | ||||
Engine_updateActive(this); | |||||
// Remove ID | // Remove ID | ||||
cable->id = 0; | 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 |