| @@ -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 | |||