| @@ -7,9 +7,9 @@ | |||
| namespace rack { | |||
| extern const std::string APP_NAME; | |||
| extern const std::string APP_VERSION; | |||
| extern const std::string API_HOST; | |||
| static const char APP_NAME[] = "VCV Rack"; | |||
| static const char APP_VERSION[] = TOSTRING(VERSION); | |||
| static const char API_HOST[] = "https://api.vcvrack.com"; | |||
| static const float APP_SVG_DPI = 75.0; | |||
| static const float MM_PER_IN = 25.4; | |||
| @@ -39,7 +39,5 @@ static const float RACK_GRID_WIDTH = 15; | |||
| static const float RACK_GRID_HEIGHT = 380; | |||
| static const math::Vec RACK_GRID_SIZE = math::Vec(RACK_GRID_WIDTH, RACK_GRID_HEIGHT); | |||
| static const std::string PRESET_FILTERS = "VCV Rack module preset (.vcvm):vcvm"; | |||
| } // namespace rack | |||
| @@ -1,17 +0,0 @@ | |||
| #pragma once | |||
| #include "common.hpp" | |||
| #include "engine/Port.hpp" | |||
| 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 isActive() ? getVoltage(channel) : normalVoltage; | |||
| } | |||
| }; | |||
| } // namespace rack | |||
| @@ -6,17 +6,19 @@ namespace rack { | |||
| struct Light { | |||
| /** The mean-square of the brightness */ | |||
| /** The mean-square of the brightness | |||
| Unstable API. Use set/getBrightness(). | |||
| */ | |||
| float value = 0.f; | |||
| float getBrightness() { | |||
| return std::sqrt(value); | |||
| } | |||
| void setBrightness(float brightness) { | |||
| value = (brightness > 0.f) ? std::pow(brightness, 2) : 0.f; | |||
| } | |||
| float getBrightness() { | |||
| return std::sqrt(value); | |||
| } | |||
| /** 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. | |||
| */ | |||
| @@ -2,8 +2,7 @@ | |||
| #include "common.hpp" | |||
| #include "string.hpp" | |||
| #include "engine/Param.hpp" | |||
| #include "engine/Input.hpp" | |||
| #include "engine/Output.hpp" | |||
| #include "engine/Port.hpp" | |||
| #include "engine/Light.hpp" | |||
| #include <vector> | |||
| #include <jansson.h> | |||
| @@ -1,13 +0,0 @@ | |||
| #pragma once | |||
| #include "common.hpp" | |||
| #include "engine/Port.hpp" | |||
| namespace rack { | |||
| struct Output : Port { | |||
| }; | |||
| } // namespace rack | |||
| @@ -1,5 +1,6 @@ | |||
| #pragma once | |||
| #include "common.hpp" | |||
| #include "math.hpp" | |||
| #include <jansson.h> | |||
| @@ -16,6 +17,7 @@ struct ParamQuantityFactory { | |||
| struct Param { | |||
| /** Unstable API. Use set/getValue() instead. */ | |||
| float value = 0.f; | |||
| float minValue = 0.f; | |||
| @@ -72,7 +74,7 @@ struct Param { | |||
| } | |||
| void setValue(float value) { | |||
| this->value = value; | |||
| this->value = math::clamp(value, minValue, maxValue); | |||
| } | |||
| bool isBounded(); | |||
| @@ -12,35 +12,49 @@ static const int PORT_MAX_CHANNELS = 16; | |||
| struct Port { | |||
| /** Voltage of the port */ | |||
| union { | |||
| float values[PORT_MAX_CHANNELS] = {}; | |||
| /** DEPRECATED. Use getVoltage() and setVoltage() instead. */ | |||
| /** Unstable API. Use set/getVoltage() instead. */ | |||
| float voltages[PORT_MAX_CHANNELS] = {}; | |||
| /** DEPRECATED. Unstable API. Use getVoltage() and setVoltage() instead. */ | |||
| float value; | |||
| }; | |||
| /** Number of polyphonic channels | |||
| Unstable API. Use set/getChannels() instead. | |||
| May be 0 to PORT_MAX_CHANNELS. | |||
| */ | |||
| union { | |||
| uint8_t channels = 1; | |||
| /** DEPRECATED. Use isActive() instead. */ | |||
| bool active; | |||
| }; | |||
| uint8_t channels = 1; | |||
| /** Unstable API. Use isConnected() instead. */ | |||
| bool active; | |||
| /** For rendering plug lights on cables | |||
| Green for positive, red for negative, and blue for polyphonic | |||
| */ | |||
| Light plugLights[3]; | |||
| void setVoltage(float voltage, int channel = 0) { | |||
| voltages[channel] = voltage; | |||
| } | |||
| float getVoltage(int channel = 0) { | |||
| return values[channel]; | |||
| return voltages[channel]; | |||
| } | |||
| void setVoltage(float voltage, int channel = 0) { | |||
| values[channel] = voltage; | |||
| /** Returns the voltage if a cable is plugged in, otherwise returns the given normal voltage */ | |||
| float getNormalVoltage(float normalVoltage, int channel = 0) { | |||
| return isConnected() ? getVoltage(channel) : normalVoltage; | |||
| } | |||
| /** Returns the voltage if there are enough channels, otherwise returns the first voltage (channel 0) */ | |||
| float getPolyVoltage(int channel) { | |||
| return (channel < channels) ? getVoltage(channel) : getVoltage(0); | |||
| } | |||
| float getNormalPolyVoltage(float normalVoltage, int channel) { | |||
| return isConnected() ? getPolyVoltage(channel) : normalVoltage; | |||
| } | |||
| void setChannels(int channels) { | |||
| // Set higher channel values to 0 | |||
| // Set higher channel voltages to 0 | |||
| for (int c = channels; c < this->channels; c++) { | |||
| values[c] = 0.f; | |||
| voltages[c] = 0.f; | |||
| } | |||
| this->channels = channels; | |||
| } | |||
| @@ -49,12 +63,20 @@ struct Port { | |||
| return channels; | |||
| } | |||
| bool isActive() { | |||
| return channels; | |||
| bool isConnected() { | |||
| return active; | |||
| } | |||
| void step(); | |||
| DEPRECATED float normalize(float normalVoltage) { | |||
| return getNormalVoltage(normalVoltage); | |||
| } | |||
| }; | |||
| struct Output : Port {}; | |||
| struct Input : Port {}; | |||
| } // namespace rack | |||
| @@ -199,6 +199,9 @@ struct Vec { | |||
| float norm() const { | |||
| return std::hypotf(x, y); | |||
| } | |||
| float square() const { | |||
| return x * x + y * y; | |||
| } | |||
| /** Rotates counterclockwise in radians */ | |||
| Vec rotate(float angle) { | |||
| float sin = std::sin(angle); | |||
| @@ -67,10 +67,9 @@ | |||
| #include "app/CableWidget.hpp" | |||
| #include "engine/Engine.hpp" | |||
| #include "engine/Input.hpp" | |||
| #include "engine/Light.hpp" | |||
| #include "engine/Param.hpp" | |||
| #include "engine/Port.hpp" | |||
| #include "engine/Module.hpp" | |||
| #include "engine/Output.hpp" | |||
| #include "engine/Param.hpp" | |||
| #include "engine/Cable.hpp" | |||
| @@ -122,7 +122,99 @@ struct AudioInterface : Module { | |||
| onSampleRateChange(); | |||
| } | |||
| void step() override; | |||
| void step() override { | |||
| // Update SRC states | |||
| int sampleRate = (int) app()->engine->getSampleRate(); | |||
| inputSrc.setRates(audioIO.sampleRate, sampleRate); | |||
| outputSrc.setRates(sampleRate, audioIO.sampleRate); | |||
| inputSrc.setChannels(audioIO.numInputs); | |||
| outputSrc.setChannels(audioIO.numOutputs); | |||
| // Inputs: audio engine -> rack engine | |||
| if (audioIO.active && audioIO.numInputs > 0) { | |||
| // Wait until inputs are present | |||
| // Give up after a timeout in case the audio device is being unresponsive. | |||
| std::unique_lock<std::mutex> lock(audioIO.engineMutex); | |||
| auto cond = [&] { | |||
| return (!audioIO.inputBuffer.empty()); | |||
| }; | |||
| auto timeout = std::chrono::milliseconds(200); | |||
| if (audioIO.engineCv.wait_for(lock, timeout, cond)) { | |||
| // Convert inputs | |||
| int inLen = audioIO.inputBuffer.size(); | |||
| int outLen = inputBuffer.capacity(); | |||
| inputSrc.process(audioIO.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); | |||
| audioIO.inputBuffer.startIncr(inLen); | |||
| inputBuffer.endIncr(outLen); | |||
| } | |||
| else { | |||
| // Give up on pulling input | |||
| audioIO.active = false; | |||
| // DEBUG("Audio Interface underflow"); | |||
| } | |||
| } | |||
| // Take input from buffer | |||
| dsp::Frame<AUDIO_INPUTS> inputFrame; | |||
| if (!inputBuffer.empty()) { | |||
| inputFrame = inputBuffer.shift(); | |||
| } | |||
| else { | |||
| std::memset(&inputFrame, 0, sizeof(inputFrame)); | |||
| } | |||
| for (int i = 0; i < audioIO.numInputs; i++) { | |||
| outputs[AUDIO_OUTPUT + i].setVoltage(10.f * inputFrame.samples[i]); | |||
| } | |||
| for (int i = audioIO.numInputs; i < AUDIO_INPUTS; i++) { | |||
| outputs[AUDIO_OUTPUT + i].setVoltage(0.f); | |||
| } | |||
| // Outputs: rack engine -> audio engine | |||
| if (audioIO.active && audioIO.numOutputs > 0) { | |||
| // Get and push output SRC frame | |||
| if (!outputBuffer.full()) { | |||
| dsp::Frame<AUDIO_OUTPUTS> outputFrame; | |||
| for (int i = 0; i < AUDIO_OUTPUTS; i++) { | |||
| outputFrame.samples[i] = inputs[AUDIO_INPUT + i].getVoltage() / 10.f; | |||
| } | |||
| outputBuffer.push(outputFrame); | |||
| } | |||
| if (outputBuffer.full()) { | |||
| // Wait until enough outputs are consumed | |||
| // Give up after a timeout in case the audio device is being unresponsive. | |||
| std::unique_lock<std::mutex> lock(audioIO.engineMutex); | |||
| auto cond = [&] { | |||
| return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); | |||
| }; | |||
| auto timeout = std::chrono::milliseconds(200); | |||
| if (audioIO.engineCv.wait_for(lock, timeout, cond)) { | |||
| // Push converted output | |||
| int inLen = outputBuffer.size(); | |||
| int outLen = audioIO.outputBuffer.capacity(); | |||
| outputSrc.process(outputBuffer.startData(), &inLen, audioIO.outputBuffer.endData(), &outLen); | |||
| outputBuffer.startIncr(inLen); | |||
| audioIO.outputBuffer.endIncr(outLen); | |||
| } | |||
| else { | |||
| // Give up on pushing output | |||
| audioIO.active = false; | |||
| outputBuffer.clear(); | |||
| // DEBUG("Audio Interface underflow"); | |||
| } | |||
| } | |||
| // Notify audio thread that an output is potentially ready | |||
| audioIO.audioCv.notify_one(); | |||
| } | |||
| // Turn on light if at least one port is enabled in the nearby pair | |||
| for (int i = 0; i < AUDIO_INPUTS / 2; i++) | |||
| lights[INPUT_LIGHT + i].setBrightness(audioIO.active && audioIO.numOutputs >= 2*i+1); | |||
| for (int i = 0; i < AUDIO_OUTPUTS / 2; i++) | |||
| lights[OUTPUT_LIGHT + i].setBrightness(audioIO.active && audioIO.numInputs >= 2*i+1); | |||
| } | |||
| json_t *dataToJson() override { | |||
| json_t *rootJ = json_object(); | |||
| @@ -141,101 +233,6 @@ struct AudioInterface : Module { | |||
| }; | |||
| void AudioInterface::step() { | |||
| // Update SRC states | |||
| int sampleRate = (int) app()->engine->getSampleRate(); | |||
| inputSrc.setRates(audioIO.sampleRate, sampleRate); | |||
| outputSrc.setRates(sampleRate, audioIO.sampleRate); | |||
| inputSrc.setChannels(audioIO.numInputs); | |||
| outputSrc.setChannels(audioIO.numOutputs); | |||
| // Inputs: audio engine -> rack engine | |||
| if (audioIO.active && audioIO.numInputs > 0) { | |||
| // Wait until inputs are present | |||
| // Give up after a timeout in case the audio device is being unresponsive. | |||
| std::unique_lock<std::mutex> lock(audioIO.engineMutex); | |||
| auto cond = [&] { | |||
| return (!audioIO.inputBuffer.empty()); | |||
| }; | |||
| auto timeout = std::chrono::milliseconds(200); | |||
| if (audioIO.engineCv.wait_for(lock, timeout, cond)) { | |||
| // Convert inputs | |||
| int inLen = audioIO.inputBuffer.size(); | |||
| int outLen = inputBuffer.capacity(); | |||
| inputSrc.process(audioIO.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); | |||
| audioIO.inputBuffer.startIncr(inLen); | |||
| inputBuffer.endIncr(outLen); | |||
| } | |||
| else { | |||
| // Give up on pulling input | |||
| audioIO.active = false; | |||
| // DEBUG("Audio Interface underflow"); | |||
| } | |||
| } | |||
| // Take input from buffer | |||
| dsp::Frame<AUDIO_INPUTS> inputFrame; | |||
| if (!inputBuffer.empty()) { | |||
| inputFrame = inputBuffer.shift(); | |||
| } | |||
| else { | |||
| std::memset(&inputFrame, 0, sizeof(inputFrame)); | |||
| } | |||
| for (int i = 0; i < audioIO.numInputs; i++) { | |||
| outputs[AUDIO_OUTPUT + i].setVoltage(10.f * inputFrame.samples[i]); | |||
| } | |||
| for (int i = audioIO.numInputs; i < AUDIO_INPUTS; i++) { | |||
| outputs[AUDIO_OUTPUT + i].setVoltage(0.f); | |||
| } | |||
| // Outputs: rack engine -> audio engine | |||
| if (audioIO.active && audioIO.numOutputs > 0) { | |||
| // Get and push output SRC frame | |||
| if (!outputBuffer.full()) { | |||
| dsp::Frame<AUDIO_OUTPUTS> outputFrame; | |||
| for (int i = 0; i < AUDIO_OUTPUTS; i++) { | |||
| outputFrame.samples[i] = inputs[AUDIO_INPUT + i].getVoltage() / 10.f; | |||
| } | |||
| outputBuffer.push(outputFrame); | |||
| } | |||
| if (outputBuffer.full()) { | |||
| // Wait until enough outputs are consumed | |||
| // Give up after a timeout in case the audio device is being unresponsive. | |||
| std::unique_lock<std::mutex> lock(audioIO.engineMutex); | |||
| auto cond = [&] { | |||
| return (audioIO.outputBuffer.size() < (size_t) audioIO.blockSize); | |||
| }; | |||
| auto timeout = std::chrono::milliseconds(200); | |||
| if (audioIO.engineCv.wait_for(lock, timeout, cond)) { | |||
| // Push converted output | |||
| int inLen = outputBuffer.size(); | |||
| int outLen = audioIO.outputBuffer.capacity(); | |||
| outputSrc.process(outputBuffer.startData(), &inLen, audioIO.outputBuffer.endData(), &outLen); | |||
| outputBuffer.startIncr(inLen); | |||
| audioIO.outputBuffer.endIncr(outLen); | |||
| } | |||
| else { | |||
| // Give up on pushing output | |||
| audioIO.active = false; | |||
| outputBuffer.clear(); | |||
| // DEBUG("Audio Interface underflow"); | |||
| } | |||
| } | |||
| // Notify audio thread that an output is potentially ready | |||
| audioIO.audioCv.notify_one(); | |||
| } | |||
| // Turn on light if at least one port is enabled in the nearby pair | |||
| for (int i = 0; i < AUDIO_INPUTS / 2; i++) | |||
| lights[INPUT_LIGHT + i].setBrightness(audioIO.active && audioIO.numOutputs >= 2*i+1); | |||
| for (int i = 0; i < AUDIO_OUTPUTS / 2; i++) | |||
| lights[OUTPUT_LIGHT + i].setBrightness(audioIO.active && audioIO.numInputs >= 2*i+1); | |||
| } | |||
| struct AudioInterfaceWidget : ModuleWidget { | |||
| AudioInterfaceWidget(AudioInterface *module) { | |||
| setModule(module); | |||
| @@ -252,7 +252,7 @@ struct CV_MIDI : Module { | |||
| } | |||
| for (int c = 0; c < inputs[PITCH_INPUT].getChannels(); c++) { | |||
| int vel = (int) std::round(inputs[VEL_INPUT].normalize(10.f * 100 / 127, c) / 10.f * 127); | |||
| int vel = (int) std::round(inputs[VEL_INPUT].getNormalPolyVoltage(10.f * 100 / 127, c) / 10.f * 127); | |||
| vel = clamp(vel, 0, 127); | |||
| midiOutput.setVelocity(vel, c); | |||
| @@ -260,12 +260,12 @@ struct CV_MIDI : Module { | |||
| note = clamp(note, 0, 127); | |||
| midiOutput.setNote(note, c); | |||
| bool gate = inputs[GATE_INPUT].getVoltage(c) >= 1.f; | |||
| bool gate = inputs[GATE_INPUT].getPolyVoltage(c) >= 1.f; | |||
| midiOutput.setGate(gate, c); | |||
| midiOutput.stepChannel(c); | |||
| int aft = (int) std::round(inputs[AFT_INPUT].getVoltage(c) / 10.f * 127); | |||
| int aft = (int) std::round(inputs[AFT_INPUT].getPolyVoltage(c) / 10.f * 127); | |||
| aft = clamp(aft, 0, 127); | |||
| midiOutput.setAftertouch(aft, c); | |||
| } | |||
| @@ -278,7 +278,7 @@ struct CV_MIDI : Module { | |||
| mw = clamp(mw, 0, 127); | |||
| midiOutput.setModWheel(mw); | |||
| int vol = (int) std::round(inputs[VOL_INPUT].normalize(10.f) / 10.f * 127); | |||
| int vol = (int) std::round(inputs[VOL_INPUT].getNormalVoltage(10.f) / 10.f * 127); | |||
| vol = clamp(vol, 0, 127); | |||
| midiOutput.setVolume(vol); | |||
| @@ -286,16 +286,16 @@ struct CV_MIDI : Module { | |||
| pan = clamp(pan, 0, 127); | |||
| midiOutput.setPan(pan); | |||
| bool clk = inputs[CLK_INPUT].value >= 1.f; | |||
| bool clk = inputs[CLK_INPUT].getVoltage() >= 1.f; | |||
| midiOutput.setClock(clk); | |||
| bool start = inputs[START_INPUT].value >= 1.f; | |||
| bool start = inputs[START_INPUT].getVoltage() >= 1.f; | |||
| midiOutput.setStart(start); | |||
| bool stop = inputs[STOP_INPUT].value >= 1.f; | |||
| bool stop = inputs[STOP_INPUT].getVoltage() >= 1.f; | |||
| midiOutput.setStop(stop); | |||
| bool cont = inputs[CONTINUE_INPUT].value >= 1.f; | |||
| bool cont = inputs[CONTINUE_INPUT].getVoltage() >= 1.f; | |||
| midiOutput.setContinue(cont); | |||
| } | |||
| @@ -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].isActive()) | |||
| if (!outputs[CC_OUTPUT + i].isConnected()) | |||
| continue; | |||
| int cc = learnedCcs[i]; | |||
| @@ -221,16 +221,16 @@ void CableWidget::draw(NVGcontext *vg) { | |||
| } | |||
| float thickness = 5; | |||
| if (cable && cable->outputModule) { | |||
| if (cable->outputModule) { | |||
| Output *output = &cable->outputModule->outputs[cable->outputId]; | |||
| 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); | |||
| } | |||
| // else if (output->channels == 0) { | |||
| // // Draw translucent cable if not active (i.e. 0 channels) | |||
| // opacity *= 0.5; | |||
| // } | |||
| } | |||
| math::Vec outputPos = getOutputPos(); | |||
| @@ -15,6 +15,9 @@ | |||
| namespace rack { | |||
| static const char PRESET_FILTERS[] = "VCV Rack module preset (.vcvm):vcvm"; | |||
| struct ModuleDisconnectItem : MenuItem { | |||
| ModuleWidget *moduleWidget; | |||
| ModuleDisconnectItem() { | |||
| @@ -499,7 +502,7 @@ void ModuleWidget::loadDialog() { | |||
| std::string dir = asset::user("presets"); | |||
| system::createDirectory(dir); | |||
| osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str()); | |||
| osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS); | |||
| DEFER({ | |||
| osdialog_filters_free(filters); | |||
| }); | |||
| @@ -520,7 +523,7 @@ void ModuleWidget::saveDialog() { | |||
| std::string dir = asset::user("presets"); | |||
| system::createDirectory(dir); | |||
| osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS.c_str()); | |||
| osdialog_filters *filters = osdialog_filters_parse(PRESET_FILTERS); | |||
| DEFER({ | |||
| osdialog_filters_free(filters); | |||
| }); | |||
| @@ -25,16 +25,14 @@ float ParamQuantity::getSmoothValue() { | |||
| void ParamQuantity::setValue(float value) { | |||
| if (!module) | |||
| return; | |||
| value = math::clamp(value, getMinValue(), getMaxValue()); | |||
| // TODO Smooth | |||
| // TODO Snap | |||
| getParam()->value = value; | |||
| // This setter clamps the value | |||
| getParam()->setValue(value); | |||
| } | |||
| float ParamQuantity::getValue() { | |||
| if (!module) | |||
| return 0.f; | |||
| return getParam()->value; | |||
| return getParam()->getValue(); | |||
| } | |||
| float ParamQuantity::getMinValue() { | |||
| @@ -13,6 +13,7 @@ struct PlugLight : MultiLightWidget { | |||
| PlugLight() { | |||
| addBaseColor(color::GREEN); | |||
| addBaseColor(color::RED); | |||
| addBaseColor(color::BLUE); | |||
| box.size = math::Vec(8, 8); | |||
| bgColor = color::BLACK_TRANSPARENT; | |||
| } | |||
| @@ -35,14 +36,17 @@ void PortWidget::step() { | |||
| if (!module) | |||
| return; | |||
| std::vector<float> values(2); | |||
| std::vector<float> values(3); | |||
| // Input and Outputs are not virtual, so we can't cast to Port to make this less redundant. | |||
| if (type == OUTPUT) { | |||
| values[0] = module->outputs[portId].plugLights[0].getBrightness(); | |||
| values[1] = module->outputs[portId].plugLights[1].getBrightness(); | |||
| values[2] = module->outputs[portId].plugLights[2].getBrightness(); | |||
| } | |||
| else { | |||
| values[0] = module->inputs[portId].plugLights[0].getBrightness(); | |||
| values[1] = module->inputs[portId].plugLights[1].getBrightness(); | |||
| values[2] = module->inputs[portId].plugLights[2].getBrightness(); | |||
| } | |||
| plugLight->setValues(values); | |||
| } | |||
| @@ -75,7 +75,7 @@ void Scene::step() { | |||
| // Version popup message | |||
| if (!latestVersion.empty()) { | |||
| std::string versionMessage = string::f("Rack %s is available.\n\nYou have Rack %s.\n\nClose Rack and download new version on the website?", latestVersion.c_str(), APP_VERSION.c_str()); | |||
| std::string versionMessage = string::f("Rack %s is available.\n\nYou have Rack %s.\n\nClose Rack and download new version on the website?", latestVersion.c_str(), APP_VERSION); | |||
| if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, versionMessage.c_str())) { | |||
| std::thread t(system::openBrowser, "https://vcvrack.com/"); | |||
| t.detach(); | |||
| @@ -164,17 +164,19 @@ void Scene::onPathDrop(const event::PathDrop &e) { | |||
| } | |||
| void Scene::runCheckVersion() { | |||
| json_t *resJ = network::requestJson(network::METHOD_GET, API_HOST + "/version", NULL); | |||
| std::string versionUrl = API_HOST; | |||
| versionUrl += "/version"; | |||
| json_t *versionResJ = network::requestJson(network::METHOD_GET, versionUrl, NULL); | |||
| if (resJ) { | |||
| json_t *versionJ = json_object_get(resJ, "version"); | |||
| if (versionResJ) { | |||
| json_t *versionJ = json_object_get(versionResJ, "version"); | |||
| if (versionJ) { | |||
| std::string version = json_string_value(versionJ); | |||
| if (version != APP_VERSION) { | |||
| latestVersion = version; | |||
| } | |||
| } | |||
| json_decref(resJ); | |||
| json_decref(versionResJ); | |||
| } | |||
| } | |||
| @@ -1,12 +0,0 @@ | |||
| #include "app/common.hpp" | |||
| namespace rack { | |||
| const std::string APP_NAME = "VCV Rack"; | |||
| const std::string APP_VERSION = TOSTRING(VERSION); | |||
| const std::string API_HOST = "https://api.vcvrack.com"; | |||
| } // namespace rack | |||
| @@ -9,9 +9,9 @@ void Cable::step() { | |||
| Input *input = &inputModule->inputs[inputId]; | |||
| // Match number of polyphonic channels to output port | |||
| input->channels = output->channels; | |||
| // Copy values from output to input | |||
| // Copy voltages from output to input | |||
| for (int i = 0; i < output->channels; i++) { | |||
| input->values[i] = output->values[i]; | |||
| input->voltages[i] = output->voltages[i]; | |||
| } | |||
| } | |||
| @@ -100,12 +100,11 @@ static void Engine_step(Engine *engine) { | |||
| Param *param = &smoothModule->params[smoothParamId]; | |||
| float value = param->value; | |||
| // decay rate is 1 graphics frame | |||
| const float lambda = 60.f; | |||
| float delta = smoothValue - value; | |||
| float newValue = value + delta * lambda * engine->internal->sampleTime; | |||
| if (value == newValue) { | |||
| // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats) | |||
| param->value = smoothValue; | |||
| const float smoothLambda = 60.f; | |||
| float newValue = value + (smoothValue - value) * smoothLambda * engine->internal->sampleTime; | |||
| if (value == newValue || !(param->minValue <= newValue && newValue <= param->maxValue)) { | |||
| // Snap to actual smooth value if the value doesn't change enough (due to the granularity of floats), or if newValue is out of bounds | |||
| param->setValue(smoothValue); | |||
| engine->internal->smoothModule = NULL; | |||
| } | |||
| else { | |||
| @@ -121,11 +120,19 @@ static void Engine_step(Engine *engine) { | |||
| if (module->bypass) { | |||
| // Bypass module | |||
| for (Output &output : module->outputs) { | |||
| // This also zeros all voltages | |||
| output.setChannels(0); | |||
| } | |||
| module->cpuTime = 0.f; | |||
| if (settings::powerMeter) { | |||
| module->cpuTime = 0.f; | |||
| } | |||
| } | |||
| else { | |||
| // Set all outputs to 1 channel so that modules are forced to specify 0 or >2 channels every frame | |||
| for (Output &output : module->outputs) { | |||
| // Don't use Port::setChannels() so we maintain all previous voltages | |||
| output.channels = 1; | |||
| } | |||
| // Step module | |||
| if (settings::powerMeter) { | |||
| auto startTime = std::chrono::high_resolution_clock::now(); | |||
| @@ -266,6 +273,23 @@ void Engine::randomizeModule(Module *module) { | |||
| module->randomize(); | |||
| } | |||
| static void Engine_updateConnected(Engine *engine) { | |||
| // Set everything to unconnected | |||
| 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); | |||
| @@ -293,6 +317,7 @@ void Engine::addCable(Cable *cable) { | |||
| } | |||
| // Add the cable | |||
| cables.push_back(cable); | |||
| Engine_updateConnected(this); | |||
| } | |||
| void Engine::removeCable(Cable *cable) { | |||
| @@ -307,6 +332,7 @@ void Engine::removeCable(Cable *cable) { | |||
| input.setChannels(0); | |||
| // Remove the cable | |||
| cables.erase(it); | |||
| Engine_updateConnected(this); | |||
| // Remove ID | |||
| cable->id = 0; | |||
| } | |||
| @@ -5,6 +5,7 @@ namespace rack { | |||
| void Port::step() { | |||
| // Set plug lights | |||
| if (channels == 0) { | |||
| plugLights[0].setBrightness(0.f); | |||
| plugLights[1].setBrightness(0.f); | |||
| @@ -28,7 +28,7 @@ int main(int argc, char *argv[]) { | |||
| #ifdef ARCH_WIN | |||
| // Windows global mutex to prevent multiple instances | |||
| // Handle will be closed by Windows when the process ends | |||
| HANDLE instanceMutex = CreateMutex(NULL, true, APP_NAME.c_str()); | |||
| HANDLE instanceMutex = CreateMutex(NULL, true, APP_NAME); | |||
| if (GetLastError() == ERROR_ALREADY_EXISTS) { | |||
| osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Rack is already running. Multiple Rack instances are not supported."); | |||
| exit(1); | |||
| @@ -65,7 +65,7 @@ int main(int argc, char *argv[]) { | |||
| logger::init(devMode); | |||
| // Log environment | |||
| INFO("%s %s", APP_NAME.c_str(), APP_VERSION.c_str()); | |||
| INFO("%s %s", APP_NAME, APP_VERSION); | |||
| if (devMode) | |||
| INFO("Development mode"); | |||
| INFO("System directory: %s", asset::systemDir.c_str()); | |||
| @@ -11,7 +11,7 @@ | |||
| namespace rack { | |||
| static const std::string PATCH_FILTERS = "VCV Rack patch (.vcv):vcv"; | |||
| static const char PATCH_FILTERS[] = "VCV Rack patch (.vcv):vcv"; | |||
| void PatchManager::reset() { | |||
| @@ -72,7 +72,7 @@ void PatchManager::saveAsDialog() { | |||
| filename = string::filename(path); | |||
| } | |||
| osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); | |||
| osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS); | |||
| DEFER({ | |||
| osdialog_filters_free(filters); | |||
| }); | |||
| @@ -140,7 +140,7 @@ void PatchManager::loadDialog() { | |||
| dir = string::directory(path); | |||
| } | |||
| osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS.c_str()); | |||
| osdialog_filters *filters = osdialog_filters_parse(PATCH_FILTERS); | |||
| DEFER({ | |||
| osdialog_filters_free(filters); | |||
| }); | |||
| @@ -179,7 +179,7 @@ json_t *PatchManager::toJson() { | |||
| json_t *rootJ = json_object(); | |||
| // version | |||
| json_t *versionJ = json_string(APP_VERSION.c_str()); | |||
| json_t *versionJ = json_string(APP_VERSION); | |||
| json_object_set_new(rootJ, "version", versionJ); | |||
| // Merge with RackWidget JSON | |||
| @@ -200,7 +200,7 @@ void PatchManager::fromJson(json_t *rootJ) { | |||
| if (versionJ) | |||
| version = json_string_value(versionJ); | |||
| if (version != APP_VERSION) { | |||
| INFO("Patch made with Rack version %s, current Rack version is %s", version.c_str(), APP_VERSION.c_str()); | |||
| INFO("Patch made with Rack version %s, current Rack version is %s", version.c_str(), APP_VERSION); | |||
| } | |||
| // Detect old patches with ModuleWidget::params/inputs/outputs indices. | |||
| @@ -378,7 +378,9 @@ void logIn(std::string email, std::string password) { | |||
| json_t *reqJ = json_object(); | |||
| json_object_set(reqJ, "email", json_string(email.c_str())); | |||
| json_object_set(reqJ, "password", json_string(password.c_str())); | |||
| json_t *resJ = network::requestJson(network::METHOD_POST, API_HOST + "/token", reqJ); | |||
| std::string tokenUrl = API_HOST; | |||
| tokenUrl += "/token"; | |||
| json_t *resJ = network::requestJson(network::METHOD_POST, tokenUrl, reqJ); | |||
| json_decref(reqJ); | |||
| if (resJ) { | |||
| @@ -421,7 +423,9 @@ bool sync(bool dryRun) { | |||
| // Get user's plugins list | |||
| json_t *pluginsReqJ = json_object(); | |||
| json_object_set(pluginsReqJ, "token", json_string(token.c_str())); | |||
| json_t *pluginsResJ = network::requestJson(network::METHOD_GET, API_HOST + "/plugins", pluginsReqJ); | |||
| std::string pluginsUrl = API_HOST; | |||
| pluginsUrl += "/plugins"; | |||
| json_t *pluginsResJ = network::requestJson(network::METHOD_GET, pluginsUrl, pluginsReqJ); | |||
| json_decref(pluginsReqJ); | |||
| if (!pluginsResJ) { | |||
| WARN("Request for user's plugins failed"); | |||
| @@ -438,7 +442,9 @@ bool sync(bool dryRun) { | |||
| } | |||
| // Get community manifests | |||
| json_t *manifestsResJ = network::requestJson(network::METHOD_GET, API_HOST + "/community/manifests", NULL); | |||
| std::string manifestsUrl = API_HOST; | |||
| manifestsUrl += "/community/manifests"; | |||
| json_t *manifestsResJ = network::requestJson(network::METHOD_GET, manifestsUrl, NULL); | |||
| if (!manifestsResJ) { | |||
| WARN("Request for community manifests failed"); | |||
| return false; | |||