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