Browse Source

Merge bfea9d763a into 7820609e24

pull/574/merge
Antoine Villeret GitHub 7 years ago
parent
commit
b4dc2d9930
47 changed files with 690 additions and 403 deletions
  1. +1
    -0
      .gitignore
  2. +6
    -0
      .gitmodules
  3. +12
    -0
      CHANGELOG.md
  4. +14
    -8
      Makefile
  5. +2
    -0
      README.md
  6. +10
    -7
      compile.mk
  7. +31
    -27
      dep/Makefile
  8. +1
    -0
      dep/glfw
  9. +1
    -0
      dep/rtaudio
  10. +11
    -8
      include/app.hpp
  11. +1
    -1
      include/components.hpp
  12. +5
    -0
      include/dsp/filter.hpp
  13. +30
    -24
      include/dsp/samplerate.hpp
  14. +18
    -7
      include/engine.hpp
  15. +6
    -0
      include/gui.hpp
  16. +1
    -10
      include/math.hpp
  17. +6
    -50
      include/plugin.hpp
  18. +31
    -3
      include/rack.hpp
  19. +72
    -0
      include/tags.hpp
  20. +2
    -1
      include/widgets.hpp
  21. +2
    -0
      plugin.mk
  22. +9
    -0
      src/app.cpp
  23. +5
    -1
      src/app/Knob.cpp
  24. +26
    -12
      src/app/LightWidget.cpp
  25. +25
    -10
      src/app/ModuleWidget.cpp
  26. +9
    -0
      src/app/RackRail.cpp
  27. +33
    -11
      src/app/RackWidget.cpp
  28. +4
    -4
      src/app/SVGFader.cpp
  29. +3
    -1
      src/app/SVGKnob.cpp
  30. +0
    -4
      src/app/SVGSwitch.cpp
  31. +9
    -8
      src/app/Toolbar.cpp
  32. +0
    -5
      src/app/WireWidget.cpp
  33. +8
    -8
      src/core/AudioInterface.cpp
  34. +15
    -10
      src/core/MidiCCToCV.cpp
  35. +29
    -28
      src/core/MidiClockToCV.cpp
  36. +7
    -6
      src/core/MidiIO.cpp
  37. +10
    -6
      src/core/MidiIO.hpp
  38. +3
    -4
      src/core/MidiToCV.cpp
  39. +9
    -7
      src/core/MidiTriggerToCV.cpp
  40. +64
    -54
      src/core/QuadMidiToCV.cpp
  41. +5
    -2
      src/engine.cpp
  42. +59
    -24
      src/gui.cpp
  43. +11
    -46
      src/plugin.cpp
  44. +64
    -0
      src/tags.cpp
  45. +4
    -2
      src/widgets/MenuOverlay.cpp
  46. +9
    -4
      src/widgets/SVGWidget.cpp
  47. +7
    -0
      src/widgets/ZoomWidget.cpp

+ 1
- 0
.gitignore View File

@@ -1,5 +1,6 @@
/Rack
/Rack.exe
/libRack.a
/autosave.json
/settings.json
/plugins


+ 6
- 0
.gitmodules View File

@@ -10,3 +10,9 @@
[submodule "ext/oui-blendish"]
path = ext/oui-blendish
url = https://github.com/AndrewBelt/oui-blendish.git
[submodule "dep/glfw"]
path = dep/glfw
url = https://github.com/glfw/glfw.git
[submodule "dep/rtaudio"]
path = dep/rtaudio
url = https://github.com/thestk/rtaudio.git

+ 12
- 0
CHANGELOG.md View File

@@ -1,6 +1,15 @@
Tip: Use `git checkout v0.4.0` for example to check out any previous version mentioned here.


### v0.5.1 (2017-12-19)

- Added Plugin Manager support
- Fixed metadata panel in the Add Module window

- Fundamental
- Added Sequential Switch 1 & 2


### v0.5.0 (2017-11-21)

- Added zoom scaling from 25% to 200%
@@ -27,6 +36,7 @@ Tip: Use `git checkout v0.4.0` for example to check out any previous version men


### v0.4.0 (2017-10-13)

- Cables can now stack on output ports
- Added sub-menus for each plugin, includes optional plugin metadata like URLs
- Added MIDI CC-to-CV Interface, updated MIDI-to-CV Interface
@@ -49,6 +59,7 @@ Tip: Use `git checkout v0.4.0` for example to check out any previous version men


### v0.3.2 (2017-09-25)

- Added key commands
- Fixed "invisible knobs/ports" rendering bug for ~2010 Macs
- Added "allowCursorLock" to settings.json (set to "false" for touch screen support)
@@ -73,4 +84,5 @@ Tip: Use `git checkout v0.4.0` for example to check out any previous version men


### v0.3.0 (2017-09-10)

- Knobcon public Beta release

+ 14
- 8
Makefile View File

@@ -14,7 +14,7 @@ ifeq ($(ARCH), lin)
LDFLAGS += -rdynamic \
-lpthread -lGL -ldl \
$(shell pkg-config --libs gtk+-2.0) \
-Ldep/lib -lGLEW -lglfw -ljansson -lsamplerate -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl
-Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl -lossia
TARGET = Rack
endif

@@ -23,7 +23,7 @@ ifeq ($(ARCH), mac)
CXXFLAGS += -DAPPLE -stdlib=libc++
LDFLAGS += -stdlib=libc++ -lpthread -ldl \
-framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo \
-Ldep/lib -lGLEW -lglfw -ljansson -lsamplerate -lcurl -lzip -lrtaudio -lrtmidi -lcrypto
-Ldep/lib -lGLEW -lglfw -ljansson -lspeexdsp -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl -lossia
TARGET = Rack
BUNDLE = dist/$(TARGET).app
endif
@@ -33,8 +33,8 @@ ifeq ($(ARCH), win)
LDFLAGS += -static-libgcc -static-libstdc++ -lpthread \
-Wl,--export-all-symbols,--out-implib,libRack.a -mwindows \
-lgdi32 -lopengl32 -lcomdlg32 -lole32 \
-Ldep/lib -lglew32 -lglfw3dll -lcurl -lzip -lrtaudio -lrtmidi \
-Wl,-Bstatic -ljansson -lsamplerate -lcrypto
-Ldep/lib -lglew32 -lglfw3dll -lcurl -lzip -lrtaudio -lrtmidi -lcrypto -lssl \
-Wl,-Bstatic -ljansson -lspeexdsp -lossia
TARGET = Rack.exe
OBJECTS = Rack.res
endif
@@ -69,8 +69,14 @@ ifeq ($(ARCH), win)
env PATH=dep/bin:/mingw64/bin gdb -ex run ./Rack
endif

perf: $(TARGET)
ifeq ($(ARCH), lin)
LD_LIBRARY_PATH=dep/lib perf record --call-graph dwarf ./Rack
endif


clean:
rm -fv libRack.a
rm -rfv $(TARGET) build dist

# For Windows resources
@@ -103,7 +109,7 @@ ifeq ($(ARCH), mac)
cp dep/lib/libGLEW.2.1.0.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libglfw.3.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libjansson.4.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libsamplerate.0.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libspeexdsp.1.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libcurl.4.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libzip.5.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/librtaudio.dylib $(BUNDLE)/Contents/MacOS/
@@ -114,7 +120,7 @@ ifeq ($(ARCH), mac)
install_name_tool -change /usr/local/lib/libGLEW.2.1.0.dylib @executable_path/libGLEW.2.1.0.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change lib/libglfw.3.dylib @executable_path/libglfw.3.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/libjansson.4.dylib @executable_path/libjansson.4.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/libsamplerate.0.dylib @executable_path/libsamplerate.0.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/libspeexdsp.1.dylib @executable_path/libspeexdsp.1.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/libcurl.4.dylib @executable_path/libcurl.4.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/libzip.5.dylib @executable_path/libzip.5.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change librtaudio.dylib @executable_path/librtaudio.dylib $(BUNDLE)/Contents/MacOS/Rack
@@ -144,7 +150,7 @@ ifeq ($(ARCH), win)
cp dep/bin/libcurl-4.dll dist/Rack/
cp dep/bin/libjansson-4.dll dist/Rack/
cp dep/bin/librtmidi-4.dll dist/Rack/
cp dep/bin/libsamplerate-0.dll dist/Rack/
cp dep/bin/libspeexdsp-1.dll dist/Rack/
cp dep/bin/libzip-5.dll dist/Rack/
cp dep/bin/librtaudio.dll dist/Rack/
cp dep/bin/libcrypto-1_1-x64.dll dist/Rack/
@@ -161,7 +167,7 @@ ifeq ($(ARCH), lin)
mkdir -p dist/Rack
cp -R LICENSE* res dist/Rack/
cp Rack Rack.sh dist/Rack/
cp dep/lib/libsamplerate.so.0 dist/Rack/
cp dep/lib/libspeexdsp.so dist/Rack/
cp dep/lib/libjansson.so.4 dist/Rack/
cp dep/lib/libGLEW.so.2.1 dist/Rack/
cp dep/lib/libglfw.so.3 dist/Rack/


+ 2
- 0
README.md View File

@@ -51,6 +51,8 @@ You may use make's `-j$(nproc)` flag to parallelize builds across all your CPU c

make dep

You may use `make dep RTAUDIO_ALL_APIS=1` to attempt to build with all audio driver APIs enabled for your operating system.

You should see a message that all dependencies built successfully.

Build Rack.


+ 10
- 7
compile.mk View File

@@ -1,5 +1,5 @@
ifdef VERSION
FLAGS += -DVERSION=$(VERSION)
FLAGS += -DVERSION=$(VERSION)
endif

# Generate dependency files alongside the object files
@@ -9,9 +9,9 @@ FLAGS += -g
FLAGS += -O3 -march=nocona -ffast-math -fno-finite-math-only
FLAGS += -Wall -Wextra -Wno-unused-parameter
ifneq ($(ARCH), mac)
CXXFLAGS += -Wsuggest-override
CXXFLAGS += -Wsuggest-override
endif
CXXFLAGS += -std=c++11
CXXFLAGS += -std=c++14


ifeq ($(ARCH), lin)
@@ -32,6 +32,9 @@ ifeq ($(ARCH), win)
FLAGS += -D_USE_MATH_DEFINES
endif

CFLAGS += $(FLAGS)
CXXFLAGS += $(FLAGS)


OBJECTS += $(patsubst %, build/%.o, $(SOURCES))
DEPS = $(patsubst %, build/%.d, $(SOURCES))
@@ -47,16 +50,16 @@ $(TARGET): $(OBJECTS)

build/%.c.o: %.c
@mkdir -p $(@D)
$(CC) $(FLAGS) $(CFLAGS) -c -o $@ $<
$(CC) $(CFLAGS) -c -o $@ $<

build/%.cpp.o: %.cpp
@mkdir -p $(@D)
$(CXX) $(FLAGS) $(CXXFLAGS) -c -o $@ $<
$(CXX) $(CXXFLAGS) -c -o $@ $<

build/%.cc.o: %.cc
@mkdir -p $(@D)
$(CXX) $(FLAGS) $(CXXFLAGS) -c -o $@ $<
$(CXX) $(CXXFLAGS) -c -o $@ $<

build/%.m.o: %.m
@mkdir -p $(@D)
$(CC) $(FLAGS) $(CFLAGS) -c -o $@ $<
$(CC) $(CFLAGS) -c -o $@ $<

+ 31
- 27
dep/Makefile View File

@@ -26,36 +26,39 @@ ifeq ($(ARCH),lin)
glew = lib/libGLEW.so
glfw = lib/libglfw.so
jansson = lib/libjansson.so
libsamplerate = lib/libsamplerate.so
libspeexdsp = lib/libspeexdsp.so
libcurl = lib/libcurl.so
libzip = lib/libzip.so
rtmidi = lib/librtmidi.so
rtaudio = lib/librtaudio.so
openssl = lib/libssl.so
ossia = lib/libossia.so
endif

ifeq ($(ARCH),mac)
glew = lib/libGLEW.dylib
glfw = lib/libglfw.dylib
jansson = lib/libjansson.dylib
libsamplerate = lib/libsamplerate.dylib
libspeexdsp = lib/libspeexdsp.dylib
libcurl = lib/libcurl.dylib
libzip = lib/libzip.dylib
rtmidi = lib/librtmidi.dylib
rtaudio = lib/librtaudio.dylib
openssl = lib/libssl.dylib
ossia = lib/libossia.so
endif

ifeq ($(ARCH),win)
glew = bin/glew32.dll
glfw = bin/glfw3.dll
jansson = bin/libjansson-4.dll
libsamplerate = bin/libsamplerate-0.dll
libspeexdsp = bin/libspeexdsp.dll
libcurl = bin/libcurl-4.dll
libzip = bin/libzip-5.dll
rtmidi = bin/librtmidi-4.dll
rtaudio = bin/librtaudio.dll
openssl = bin/libssl-1_1-x64.dll
ossia = bin/ossia.dll
endif

# Library configuration
@@ -74,7 +77,7 @@ endif

.NOTPARALLEL:

all: $(glew) $(glfw) $(jansson) $(libsamplerate) $(libcurl) $(libzip) $(rtmidi) $(rtaudio)
all: $(glew) $(glfw) $(jansson) $(libspeexdsp) $(libcurl) $(libzip) $(rtmidi) $(rtaudio) $(ossia)
@echo ""
@echo "#######################################"
@echo "# Built all dependencies successfully #"
@@ -89,17 +92,11 @@ $(glew):
$(MAKE) -C glew-2.1.0 GLEW_DEST="$(LOCAL)" LIBDIR="$(LOCAL)/lib" install

$(glfw):
$(WGET) https://github.com/glfw/glfw/releases/download/3.2.1/glfw-3.2.1.zip
$(UNZIP) glfw-3.2.1.zip
cd glfw-3.2.1 && $(CMAKE) . \
cd glfw && $(CMAKE) . \
-DCMAKE_INSTALL_PREFIX="$(LOCAL)" -DBUILD_SHARED_LIBS=ON \
-DGLFW_USE_CHDIR=OFF -DGLFW_USE_MENUBAR=ON -DGLFW_USE_RETINA=ON
$(MAKE) -C glfw-3.2.1
$(MAKE) -C glfw-3.2.1 install
ifeq ($(ARCH),win)
# Not sure why the GLFW build system puts a .dll in the lib directory
mv "$(LOCAL)/lib/glfw3.dll" "$(LOCAL)/bin/"
endif
-DGLFW_COCOA_CHDIR_RESOURCES=OFF -DGLFW_COCOA_MENUBAR=ON -DGLFW_COCOA_RETINA_FRAMEBUFFER=ON
$(MAKE) -C glfw
$(MAKE) -C glfw install

$(jansson):
$(WGET) http://www.digip.org/jansson/releases/jansson-2.10.tar.gz
@@ -108,12 +105,12 @@ $(jansson):
$(MAKE) -C jansson-2.10
$(MAKE) -C jansson-2.10 install

$(libsamplerate):
$(WGET) http://www.mega-nerd.com/SRC/libsamplerate-0.1.9.tar.gz
$(UNTAR) libsamplerate-0.1.9.tar.gz
cd libsamplerate-0.1.9 && ./configure --prefix="$(LOCAL)" --disable-fftw --disable-sndfile
$(MAKE) -C libsamplerate-0.1.9/src
$(MAKE) -C libsamplerate-0.1.9/src install
$(libspeexdsp):
$(WGET) https://github.com/xiph/speexdsp/archive/SpeexDSP-1.2rc3.tar.gz
$(UNTAR) SpeexDSP-1.2rc3.tar.gz
cd speexdsp-SpeexDSP-1.2rc3 && ./autogen.sh && ./configure --prefix="$(LOCAL)"
$(MAKE) -C speexdsp-SpeexDSP-1.2rc3
$(MAKE) -C speexdsp-SpeexDSP-1.2rc3 install

$(openssl):
$(WGET) https://www.openssl.org/source/openssl-1.1.0g.tar.gz
@@ -151,18 +148,25 @@ $(libzip):
$(MAKE) -C libzip-1.2.0 install

$(rtmidi):
$(WGET) http://www.music.mcgill.ca/~gary/rtmidi/release/rtmidi-3.0.0.tar.gz
$(UNTAR) rtmidi-3.0.0.tar.gz
cd rtmidi-3.0.0 && ./configure --prefix="$(LOCAL)"
$(MAKE) -C rtmidi-3.0.0
$(MAKE) -C rtmidi-3.0.0 install
git clone https://github.com/thestk/rtmidi.git
cd rtmidi && ./autogen.sh --no-configure && ./configure --prefix="$(LOCAL)"
$(MAKE) -C rtmidi
$(MAKE) -C rtmidi install

$(rtaudio):
git clone https://github.com/thestk/rtaudio.git
cd rtaudio && mkdir -p cmakebuild
cd rtaudio/cmakebuild && cmake -G 'Unix Makefiles' -DCMAKE_INSTALL_PREFIX="$(LOCAL)" $(RTAUDIO_FLAGS) ..
$(MAKE) -C rtaudio/cmakebuild
$(MAKE) -C rtaudio/cmakebuild install

$(ossia):
git clone https://github.com/OSSIA/libossia --depth=1
mkdir -p build-libossia && cd build-libossia && $(CMAKE) \
-DCMAKE_INSTALL_PREFIX="$(LOCAL)" -DOSSIA_PD=OFF \
-DOSSIA_COTIRE=OFF -DOSSIA_PROTOCOL_MIDI=OFF \
-DOSSIA_EDITOR=OFF -DBUILD_TYPE=Release ../libossia && \
$(MAKE) -j4 && \
$(MAKE) install

clean:
git clean -ffdxi
git clean -ffdx

+ 1
- 0
dep/glfw

@@ -0,0 +1 @@
Subproject commit 682f1cf203707f21c2eed4fa3f89c23c52accc49

+ 1
- 0
dep/rtaudio

@@ -0,0 +1 @@
Subproject commit ce13dfbf30fd1ab4e7f7eff8886a80f144c75e5d

+ 11
- 8
include/app.hpp View File

@@ -20,10 +20,9 @@ struct SVGPanel;
// module
////////////////////

// A 1U module should be 15x380. Thus the width of a module should be a factor of 15.
// A 1HPx3U module should be 15x380. Thus the width of a module should be a factor of 15.
#define RACK_GRID_WIDTH 15
#define RACK_GRID_HEIGHT 380

static const Vec RACK_GRID_SIZE = Vec(15, 380);


@@ -48,6 +47,8 @@ struct ModuleWidget : OpaqueWidget {
virtual json_t *toJson();
virtual void fromJson(json_t *rootJ);

virtual void create();
virtual void _delete();
/** Disconnects cables from all ports
Called when the user clicks Disconnect Cables in the context menu.
*/
@@ -75,14 +76,11 @@ struct ModuleWidget : OpaqueWidget {
void onDragMove(EventDragMove &e) override;
};

struct ValueLight;
struct WireWidget : OpaqueWidget {
Port *outputPort = NULL;
Port *inputPort = NULL;
Port *hoveredOutputPort = NULL;
Port *hoveredInputPort = NULL;
ValueLight *inputLight;
ValueLight *outputLight;
Wire *wire = NULL;
NVGcolor color;

@@ -199,6 +197,8 @@ struct ParamWidget : OpaqueWidget, QuantityWidget {
struct Knob : ParamWidget {
/** Snap to nearest integer while dragging */
bool snap = false;
/** Multiplier for mouse movement to adjust knob value */
float speed = 1.0;
float dragValue;
void onDragStart(EventDragStart &e) override;
void onDragMove(EventDragMove &e) override;
@@ -226,14 +226,14 @@ struct SVGKnob : virtual Knob, FramebufferWidget {
void onChange(EventChange &e) override;
};

struct SVGSlider : Knob, FramebufferWidget {
struct SVGFader : Knob, FramebufferWidget {
/** Intermediate positions will be interpolated between these positions */
Vec minHandlePos, maxHandlePos;
/** Not owned */
SVGWidget *background;
SVGWidget *handle;

SVGSlider();
SVGFader();
void step() override;
void onChange(EventChange &e) override;
};
@@ -249,7 +249,6 @@ struct SVGSwitch : virtual Switch, FramebufferWidget {
SVGSwitch();
/** Adds an SVG file to represent the next switch position */
void addFrame(std::shared_ptr<SVG> svg);
void step() override;
void onChange(EventChange &e) override;
};

@@ -271,6 +270,8 @@ struct MomentarySwitch : virtual Switch {
void randomize() override {}
void onDragStart(EventDragStart &e) override {
setValue(maxValue);
EventAction eAction;
onAction(eAction);
}
void onDragEnd(EventDragEnd &e) override {
setValue(minValue);
@@ -285,6 +286,8 @@ struct LightWidget : TransparentWidget {
NVGcolor bgColor = nvgRGBf(0, 0, 0);
NVGcolor color = nvgRGBf(1, 1, 1);
void draw(NVGcontext *vg) override;
virtual void drawLight(NVGcontext *vg);
virtual void drawHalo(NVGcontext *vg);
};

/** Mixes a list of colors based on a list of brightness values */


+ 1
- 1
include/components.hpp View File

@@ -322,7 +322,7 @@ struct BefacoTinyKnob : SVGKnob {
}
};

struct BefacoSlidePot : SVGSlider {
struct BefacoSlidePot : SVGFader {
BefacoSlidePot() {
Vec margin = Vec(3.5, 3.5);
maxHandlePos = Vec(-1, -2).plus(margin);


+ 5
- 0
include/dsp/filter.hpp View File

@@ -51,6 +51,11 @@ struct SlewLimiter {
float rise = 1.0;
float fall = 1.0;
float out = 0.0;

void setRiseFall(float _rise, float _fall) {
rise = _rise;
fall = _fall;
}
float process(float in) {
float delta = clampf(in - out, -fall, rise);
out += delta;


+ 30
- 24
include/dsp/samplerate.hpp View File

@@ -1,7 +1,8 @@
#pragma once

#include <assert.h>
#include <samplerate.h>
#include <string.h>
#include <speex/speex_resampler.h>
#include "frame.hpp"


@@ -9,41 +10,46 @@ namespace rack {

template<int CHANNELS>
struct SampleRateConverter {
SRC_STATE *state;
SRC_DATA data;
SpeexResamplerState *state = NULL;
bool bypass = false;

SampleRateConverter() {
int error;
state = src_new(SRC_SINC_FASTEST, CHANNELS, &error);
assert(!error);

data.src_ratio = 1.0;
data.end_of_input = false;
state = speex_resampler_init(CHANNELS, 44100, 44100, SPEEX_RESAMPLER_QUALITY_DEFAULT, &error);
assert(error == RESAMPLER_ERR_SUCCESS);
}
~SampleRateConverter() {
src_delete(state);
speex_resampler_destroy(state);
}
/** output_sample_rate / input_sample_rate */
void setRatio(float r) {
src_set_ratio(state, r);
data.src_ratio = r;

void setQuality(int quality) {
speex_resampler_set_quality(state, quality);
}
void setRatioSmooth(float r) {
data.src_ratio = r;

void setRates(int inRate, int outRate) {
spx_uint32_t oldInRate, oldOutRate;
speex_resampler_get_rate(state, &oldInRate, &oldOutRate);
if (inRate == (int) oldInRate && outRate == (int) oldOutRate)
return;
int error = speex_resampler_set_rate(state, inRate, outRate);
assert(error == RESAMPLER_ERR_SUCCESS);
}

/** `in` and `out` are interlaced with the number of channels */
void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) {
// Old versions of libsamplerate use float* here instead of const float*
data.data_in = (float*) in;
data.input_frames = *inFrames;
data.data_out = (float*) out;
data.output_frames = *outFrames;
src_process(state, &data);
*inFrames = data.input_frames_used;
*outFrames = data.output_frames_gen;
if (bypass) {
int len = std::min(*inFrames, *outFrames);
memcpy(out, in, len * sizeof(Frame<CHANNELS>));
*inFrames = len;
*outFrames = len;
return;
}
speex_resampler_process_interleaved_float(state, (const float*)in, (unsigned int*)inFrames, (float*)out, (unsigned int*)outFrames);
}

void reset() {
src_reset(state);
int error = speex_resampler_reset_mem(state);
assert(error == RESAMPLER_ERR_SUCCESS);
}
};



+ 18
- 7
include/engine.hpp View File

@@ -3,12 +3,17 @@
#include "util.hpp"
#include <jansson.h>

#include <ossia/network/network.hpp>
#include <ossia/network/oscquery/oscquery_server.hpp>

namespace rack {

extern ossia::net::generic_device& root_dev();

struct Param {
float value = 0.0;
std::string name = "param.1";
ossia::net::parameter_base* ossia_param;
};

struct Light {
@@ -16,7 +21,7 @@ struct Light {
float value = 0.0;
float getBrightness();
void setBrightness(float brightness) {
value = brightness * brightness;
value = (brightness > 0.f) ? brightness * brightness : 0.f;
}
void setBrightnessSmooth(float brightness);
};
@@ -41,7 +46,6 @@ struct Output {
Light plugLights[2];
};


struct Module {
std::vector<Param> params;
std::vector<Input> inputs;
@@ -49,6 +53,8 @@ struct Module {
std::vector<Light> lights;
/** For CPU usage meter */
float cpuTime = 0.0;
ossia::net::node_base* node{};

/** Deprecated, use constructor below this one */
Module() DEPRECATED {}
@@ -58,6 +64,8 @@ struct Module {
inputs.resize(numInputs);
outputs.resize(numOutputs);
lights.resize(numLights);
node = &ossia::net::create_node(rack::root_dev(),"module");
}
virtual ~Module() {}

@@ -65,11 +73,14 @@ struct Module {
virtual void step() {}
virtual void onSampleRateChange() {}

/** Override these to implement spacial behavior when user clicks Initialize and Randomize */
virtual void reset() {}
virtual void randomize() {}
/** Deprecated */
virtual void initialize() final {}
/** Called when module is created by the Add Module popup, cloning, or when loading a patch or autosave */
virtual void onCreate() {}
/** Called when user explicitly deletes the module, not when Rack is closed or a new patch is loaded */
virtual void onDelete() {}
/** Called when user clicks Initialize in the module context menu */
virtual void onReset() {}
/** Called when user clicks Randomize in the module context menu */
virtual void onRandomize() {}

/** Override these to store extra internal data in the "data" property */
virtual json_t *toJson() { return NULL; }


+ 6
- 0
include/gui.hpp View File

@@ -17,8 +17,14 @@ namespace rack {
extern GLFWwindow *gWindow;
extern NVGcontext *gVg;
extern NVGcontext *gFramebufferVg;
/** The default font to use for GUI elements */
extern std::shared_ptr<Font> gGuiFont;
/** The scaling ratio */
extern float gPixelRatio;
/* The ratio between the framebuffer size and the window size reported by the OS.
This is not equal to gPixelRatio in general.
*/
extern float gWindowRatio;
extern bool gAllowCursorLock;
extern int gGuiFrame;
extern Vec gMousePos;


+ 1
- 10
include/math.hpp View File

@@ -120,15 +120,6 @@ inline float sincf(float x) {
return sinf(x) / x;
}

inline float getf(const float *p, float v = 0.0) {
return p ? *p : v;
}

inline void setf(float *p, float v) {
if (p)
*p = v;
}

/** Linearly interpolate an array `p` with index `x`
Assumes that the array at `p` is of length at least floor(x)+1.
*/
@@ -139,7 +130,7 @@ inline float interpf(const float *p, float x) {
}

/** Complex multiply c = a * b
It is of course acceptable to reuse arguments
Arguments may be the same pointers
i.e. cmultf(&ar, &ai, ar, ai, br, bi)
*/
inline void cmultf(float *cr, float *ci, float ar, float ai, float br, float bi) {


+ 6
- 50
include/plugin.hpp View File

@@ -1,59 +1,13 @@
#pragma once
#include <string>
#include <list>
#include "tags.hpp"

#include <ossia/network/network.hpp>

namespace rack {


enum ModelTag {
AMPLIFIER_TAG,
ATTENUATOR_TAG,
BLANK_TAG,
CLOCK_TAG,
CONTROLLER_TAG,
DELAY_TAG,
DIGITAL_TAG,
DISTORTION_TAG,
DRUM_TAG,
DUAL_TAG,
DYNAMICS_TAG,
EFFECT_TAG,
ENVELOPE_FOLLOWER_TAG,
ENVELOPE_GENERATOR_TAG,
EQUALIZER_TAG,
EXTERNAL_TAG,
FILTER_TAG,
FUNCTION_GENERATOR_TAG,
GRANULAR_TAG,
LFO_TAG,
LOGIC_TAG,
LOW_PASS_GATE_TAG,
MIDI_TAG,
MIXER_TAG,
MULTIPLE_TAG,
NOISE_TAG,
OSCILLATOR_TAG,
PANNING_TAG,
QUAD_TAG,
QUANTIZER_TAG,
RANDOM_TAG,
REVERB_TAG,
RING_MODULATOR_TAG,
SAMPLE_AND_HOLD_TAG,
SAMPLER_TAG,
SEQUENCER_TAG,
SLEW_LIMITER_TAG,
SWITCH_TAG,
SYNTH_VOICE_TAG,
TUNER_TAG,
UTILITY_TAG,
VISUAL_TAG,
WAVESHAPER_TAG,
NUM_TAGS
};


struct ModuleWidget;
struct Model;

@@ -71,11 +25,12 @@ struct Plugin {
*/
std::string slug;

/** The version of your plugin (optional)
/** The version of your plugin
Plugins should follow the versioning scheme described at https://github.com/VCVRack/Rack/issues/266
Do not include the "v" in "v1.0" for example.
*/
std::string version;

/** URL for plugin homepage (optional) */
std::string website;
/** URL for plugin manual (optional) */
@@ -101,6 +56,8 @@ struct Model {

virtual ~Model() {}
virtual ModuleWidget *createModuleWidget() { return NULL; }
ossia::net::node_base* node;
};

void pluginInit();
@@ -119,7 +76,6 @@ std::string pluginGetLoginStatus();

extern std::list<Plugin*> gPlugins;
extern std::string gToken;
extern std::string gTagNames[NUM_TAGS];


} // namespace rack


+ 31
- 3
include/rack.hpp View File

@@ -8,7 +8,8 @@
#include "gui.hpp"
#include "app.hpp"
#include "components.hpp"

#include <iostream>
#include <sstream>

namespace rack {

@@ -24,6 +25,8 @@ Model *createModel(std::string manufacturer, std::string slug, std::string name,
ModuleWidget *createModuleWidget() override {
ModuleWidget *moduleWidget = new TModuleWidget();
moduleWidget->model = this;
moduleWidget->module->node->set_name(name);
return moduleWidget;
}
};
@@ -43,13 +46,38 @@ Widget *createScrew(Vec pos) {
}

template <class TParamWidget>
ParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) {
ParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue, std::string name = std::string("")) {
ParamWidget *param = new TParamWidget();
param->box.pos = pos;
param->module = module;
param->paramId = paramId;
param->setLimits(minValue, maxValue);
auto& p = module->params[paramId];

if (name == "")
{
std::stringstream ss;
ss << "param." << paramId;
name = ss.str();
}
auto& p_node = ossia::net::create_node(*module->node, name);
p.ossia_param = p_node.create_parameter(ossia::val_type::FLOAT);
p.ossia_param->set_domain(ossia::make_domain(minValue,maxValue));
p.ossia_param->set_bounding(ossia::bounding_mode::CLIP);
p.ossia_param->push_value(defaultValue);
p.ossia_param->set_default_value(defaultValue);
p.ossia_param->add_callback([param] (const ossia::value& v) {
auto& p = param->module->params[param->paramId];
param->value = v.get<float>();
p.value = param->value;
if ( auto fbw = dynamic_cast<FramebufferWidget*>(param))
fbw->dirty = true;
});
param->setLimits(minValue, maxValue);
param->setDefaultValue(defaultValue);
return param;
}



+ 72
- 0
include/tags.hpp View File

@@ -0,0 +1,72 @@
#pragma once
#include <string>


namespace rack {


/** Describes the type(s) of each module
To see comments, turn word wrap on. I'm using inline comments so I can automatically sort the list when more tags are added.
*/
enum ModelTag {
AMPLIFIER_TAG,
ATTENUATOR_TAG,
BLANK_TAG,
CHORUS_TAG,
CLOCK_TAG,
COMPRESSOR_TAG,
CONTROLLER_TAG, // Use only if the artist "performs" with this module. Knobs are not sufficient. Examples: on-screen keyboard, XY pad.
DELAY_TAG,
DIGITAL_TAG,
DISTORTION_TAG,
DRUM_TAG,
DUAL_TAG, // The core functionality times two. If multiple channels are a requirement for the module to exist (ring modulator, mixer, etc), it is not a Dual module.
DYNAMICS_TAG,
EFFECT_TAG,
ENVELOPE_FOLLOWER_TAG,
ENVELOPE_GENERATOR_TAG,
EQUALIZER_TAG,
EXTERNAL_TAG,
FILTER_TAG,
FLANGER_TAG,
FUNCTION_GENERATOR_TAG,
GRANULAR_TAG,
LFO_TAG,
LIMITER_TAG,
LOGIC_TAG,
LOW_PASS_GATE_TAG,
MIDI_TAG,
MIXER_TAG,
MULTIPLE_TAG,
NOISE_TAG,
OSCILLATOR_TAG,
PANNING_TAG,
PHASER_TAG,
PHYSICAL_MODELING_TAG,
QUAD_TAG, // The core functionality times four. If multiple channels are a requirement for the module to exist (ring modulator, mixer, etc), it is not a Quad module.
QUANTIZER_TAG,
RANDOM_TAG,
RECORDING_TAG,
REVERB_TAG,
RING_MODULATOR_TAG,
SAMPLE_AND_HOLD_TAG,
SAMPLER_TAG,
SEQUENCER_TAG,
SLEW_LIMITER_TAG,
SWITCH_TAG,
SYNTH_VOICE_TAG, // A synth voice must have an envelope built-in.
TUNER_TAG,
UTILITY_TAG, // Serves only extremely basic functions, like inverting, max, min, multiplying by 2, etc.
VISUAL_TAG,
VOCODER_TAG,
WAVESHAPER_TAG,
NUM_TAGS
};


void tagsInit();

extern std::string gTagNames[NUM_TAGS];


} // namespace rack

+ 2
- 1
include/widgets.hpp View File

@@ -178,6 +178,7 @@ struct ZoomWidget : Widget {
void onMouseMove(EventMouseMove &e) override;
void onHoverKey(EventHoverKey &e) override;
void onScroll(EventScroll &e) override;
void onPathDrop(EventPathDrop &e) override;
};

////////////////////
@@ -295,7 +296,7 @@ struct Label : Widget {
/** Deletes itself from parent when clicked */
struct MenuOverlay : OpaqueWidget {
void step() override;
void onDragDrop(EventDragDrop &e) override;
void onMouseDown(EventMouseDown &e) override;
void onHoverKey(EventHoverKey &e) override;
};



+ 2
- 0
plugin.mk View File

@@ -21,6 +21,8 @@ ifeq ($(ARCH), win)
TARGET = plugin.dll
endif

DISTRIBUTABLES += $(TARGET)


all: $(TARGET)



+ 9
- 0
src/app.cpp View File

@@ -1,5 +1,7 @@
#include "app.hpp"

#include <ossia/network/network.hpp>
#include <ossia/network/oscquery/oscquery_server.hpp>

namespace rack {

@@ -28,5 +30,12 @@ void sceneDestroy() {
gScene = NULL;
}

ossia::net::generic_device& root_dev(){
static ossia::net::generic_device dev{
std::make_unique<ossia::oscquery::oscquery_server_protocol>(1234, 5678),
"VCV-Rack"};

return dev;
}

} // namespace rack

+ 5
- 1
src/app/Knob.cpp View File

@@ -18,7 +18,11 @@ void Knob::onDragStart(EventDragStart &e) {

void Knob::onDragMove(EventDragMove &e) {
// Drag slower if Mod
float delta = KNOB_SENSITIVITY * (maxValue - minValue) * -e.mouseRel.y;
float range = maxValue - minValue;
float delta = KNOB_SENSITIVITY * -e.mouseRel.y * speed;
if (std::isfinite(range))
delta *= range;

if (guiIsModPressed())
delta /= 16.0;
dragValue += delta;


+ 26
- 12
src/app/LightWidget.cpp View File

@@ -5,35 +5,46 @@ namespace rack {


void LightWidget::draw(NVGcontext *vg) {
float radius = box.size.x / 2.0;
float oradius = radius + 15.0;

color.r = clampf(color.r, 0.0, 1.0);
color.g = clampf(color.g, 0.0, 1.0);
color.b = clampf(color.b, 0.0, 1.0);
color.a = clampf(color.a, 0.0, 1.0);

// Solid
drawLight(vg);
drawHalo(vg);
}


void LightWidget::drawLight(NVGcontext *vg) {
float radius = box.size.x / 2.0;

nvgBeginPath(vg);
nvgCircle(vg, radius, radius, radius);

// Background
nvgFillColor(vg, bgColor);
nvgFill(vg);

// Border
nvgStrokeWidth(vg, 1.0);
NVGcolor borderColor = bgColor;
borderColor.a *= 0.5;
nvgStrokeColor(vg, borderColor);
nvgStroke(vg);
// // Border
// nvgStrokeWidth(vg, 1.0);
// NVGcolor borderColor = bgColor;
// borderColor.a *= 0.5;
// nvgStrokeColor(vg, borderColor);
// nvgStroke(vg);

// Inner glow
nvgGlobalCompositeOperation(vg, NVG_LIGHTER);
nvgFillColor(vg, color);
nvgFill(vg);
}


void LightWidget::drawHalo(NVGcontext *vg) {
float radius = box.size.x / 2.0;
float oradius = radius + 15.0;

// Outer glow
nvgBeginPath(vg);
nvgRect(vg, radius - oradius, radius - oradius, 2*oradius, 2*oradius);

NVGpaint paint;
NVGcolor icol = color;
icol.a *= 0.10;
@@ -41,8 +52,11 @@ void LightWidget::draw(NVGcontext *vg) {
ocol.a = 0.0;
paint = nvgRadialGradient(vg, radius, radius, radius, oradius, icol, ocol);
nvgFillPaint(vg, paint);
nvgGlobalCompositeOperation(vg, NVG_LIGHTER);
nvgFill(vg);
}




} // namespace rack

+ 25
- 10
src/app/ModuleWidget.cpp View File

@@ -60,8 +60,11 @@ void ModuleWidget::setPanel(std::shared_ptr<SVG> svg) {
json_t *ModuleWidget::toJson() {
json_t *rootJ = json_object();

// manufacturer
// plugin
json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str()));
// version (of plugin)
if (!model->plugin->version.empty())
json_object_set_new(rootJ, "version", json_string(model->plugin->version.c_str()));
// model
json_object_set_new(rootJ, "model", json_string(model->slug.c_str()));
// pos
@@ -118,12 +121,24 @@ void ModuleWidget::disconnect() {
}
}

void ModuleWidget::create() {
if (module) {
module->onCreate();
}
}

void ModuleWidget::_delete() {
if (module) {
module->onDelete();
}
}

void ModuleWidget::reset() {
for (ParamWidget *param : params) {
param->setValue(param->defaultValue);
}
if (module) {
module->reset();
module->onReset();
}
}

@@ -132,7 +147,7 @@ void ModuleWidget::randomize() {
param->randomize();
}
if (module) {
module->randomize();
module->onRandomize();
}
}

@@ -188,7 +203,7 @@ void ModuleWidget::onMouseMove(EventMouseMove &e) {
gRackWidget->deleteModule(this);
this->finalizeEvents();
delete this;
// Kinda sketchy because events will be passed further down the tree
e.consumed = true;
return;
}
}
@@ -279,36 +294,36 @@ Menu *ModuleWidget::createContextMenu() {

MenuLabel *menuLabel = new MenuLabel();
menuLabel->text = model->manufacturer + " " + model->name;
menu->pushChild(menuLabel);
menu->addChild(menuLabel);

ResetMenuItem *resetItem = new ResetMenuItem();
resetItem->text = "Initialize";
resetItem->rightText = GUI_MOD_KEY_NAME "+I";
resetItem->moduleWidget = this;
menu->pushChild(resetItem);
menu->addChild(resetItem);

RandomizeMenuItem *randomizeItem = new RandomizeMenuItem();
randomizeItem->text = "Randomize";
randomizeItem->rightText = GUI_MOD_KEY_NAME "+R";
randomizeItem->moduleWidget = this;
menu->pushChild(randomizeItem);
menu->addChild(randomizeItem);

DisconnectMenuItem *disconnectItem = new DisconnectMenuItem();
disconnectItem->text = "Disconnect cables";
disconnectItem->moduleWidget = this;
menu->pushChild(disconnectItem);
menu->addChild(disconnectItem);

CloneMenuItem *cloneItem = new CloneMenuItem();
cloneItem->text = "Duplicate";
cloneItem->rightText = GUI_MOD_KEY_NAME "+D";
cloneItem->moduleWidget = this;
menu->pushChild(cloneItem);
menu->addChild(cloneItem);

DeleteMenuItem *deleteItem = new DeleteMenuItem();
deleteItem->text = "Delete";
deleteItem->rightText = "Backspace/Delete";
deleteItem->moduleWidget = this;
menu->pushChild(deleteItem);
menu->addChild(deleteItem);

return menu;
}


+ 9
- 0
src/app/RackRail.cpp View File

@@ -46,6 +46,15 @@ void RackRail::draw(NVGcontext *vg) {
nvgLineTo(vg, box.size.x, railY + RACK_GRID_HEIGHT - 0.5);
nvgStroke(vg);
}


// Useful for screenshots
if (0) {
nvgBeginPath(vg);
nvgRect(vg, 0.0, 0.0, box.size.x, box.size.y);
nvgFillColor(vg, nvgRGBf(1.0, 1.0, 1.0));
nvgFill(vg);
}
}




+ 33
- 11
src/app/RackWidget.cpp View File

@@ -38,6 +38,25 @@ void RackWidget::clear() {
wireContainer->clearChildren();
moduleContainer->clearChildren();
lastPath = "";

/*
// Add all modules to rack
Vec pos;
for (Plugin *plugin : gPlugins) {
for (Model *model : plugin->models) {
ModuleWidget *moduleWidget = model->createModuleWidget();
moduleContainer->addChild(moduleWidget);
// Move module nearest to the mouse position
Rect box;
box.size = moduleWidget->box.size;
box.pos = pos;
requestModuleBoxNearest(moduleWidget, box);
pos.x += box.size.x;
}
pos.y += RACK_GRID_HEIGHT;
pos.x = 0;
}
*/
}

void RackWidget::reset() {
@@ -83,20 +102,19 @@ void RackWidget::saveAsDialog() {
}
}


void RackWidget::savePatch(std::string path) {
info("Saving patch %s", path.c_str());
FILE *file = fopen(path.c_str(), "w");
if (!file)
json_t *rootJ = toJson();
if (!rootJ)
return;

json_t *rootJ = toJson();
if (rootJ) {
FILE *file = fopen(path.c_str(), "w");
if (file) {
json_dumpf(rootJ, file, JSON_INDENT(2));
json_decref(rootJ);
fclose(file);
}

fclose(file);
json_decref(rootJ);
}

void RackWidget::loadPatch(std::string path) {
@@ -127,8 +145,10 @@ json_t *RackWidget::toJson() {
json_t *rootJ = json_object();

// version
json_t *versionJ = json_string(gApplicationVersion.c_str());
json_object_set_new(rootJ, "version", versionJ);
if (!gApplicationVersion.empty()) {
json_t *versionJ = json_string(gApplicationVersion.c_str());
json_object_set_new(rootJ, "version", versionJ);
}

// modules
json_t *modulesJ = json_array();
@@ -285,9 +305,11 @@ void RackWidget::fromJson(json_t *rootJ) {

void RackWidget::addModule(ModuleWidget *m) {
moduleContainer->addChild(m);
m->create();
}

void RackWidget::deleteModule(ModuleWidget *m) {
m->_delete();
moduleContainer->removeChild(m);
}

@@ -323,8 +345,8 @@ bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, Rect box) {
int x0 = roundf(box.pos.x / RACK_GRID_WIDTH);
int y0 = roundf(box.pos.y / RACK_GRID_HEIGHT);
std::vector<Vec> positions;
for (int y = maxi(0, y0 - 4); y < y0 + 4; y++) {
for (int x = maxi(0, x0 - 200); x < x0 + 200; x++) {
for (int y = maxi(0, y0 - 8); y < y0 + 8; y++) {
for (int x = maxi(0, x0 - 400); x < x0 + 400; x++) {
positions.push_back(Vec(x * RACK_GRID_WIDTH, y * RACK_GRID_HEIGHT));
}
}


src/app/SVGSlider.cpp → src/app/SVGFader.cpp View File

@@ -4,7 +4,7 @@
namespace rack {


SVGSlider::SVGSlider() {
SVGFader::SVGFader() {
background = new SVGWidget();
addChild(background);

@@ -12,7 +12,7 @@ SVGSlider::SVGSlider() {
addChild(handle);
}

void SVGSlider::step() {
void SVGFader::step() {
if (dirty) {
// Update handle position
Vec handlePos = Vec(rescalef(value, minValue, maxValue, minHandlePos.x, maxHandlePos.x), rescalef(value, minValue, maxValue, minHandlePos.y, maxHandlePos.y));
@@ -21,9 +21,9 @@ void SVGSlider::step() {
FramebufferWidget::step();
}

void SVGSlider::onChange(EventChange &e) {
void SVGFader::onChange(EventChange &e) {
dirty = true;
ParamWidget::onChange(e);
Knob::onChange(e);
}



+ 3
- 1
src/app/SVGKnob.cpp View File

@@ -23,7 +23,9 @@ void SVGKnob::step() {
// Re-transform TransformWidget if dirty
if (dirty) {
tw->box.size = box.size;
float angle = rescalef(value, minValue, maxValue, minAngle, maxAngle);
float angle = 0.0;
if (std::isfinite(minValue) && std::isfinite(maxValue))
angle = rescalef(value, minValue, maxValue, minAngle, maxAngle);
tw->identity();
// Scale SVG to box
tw->scale(box.size.div(sw->box.size));


+ 0
- 4
src/app/SVGSwitch.cpp View File

@@ -18,10 +18,6 @@ void SVGSwitch::addFrame(std::shared_ptr<SVG> svg) {
}
}

void SVGSwitch::step() {
FramebufferWidget::step();
}

void SVGSwitch::onChange(EventChange &e) {
assert(frames.size() > 0);
float valueScaled = rescalef(value, minValue, maxValue, 0, frames.size() - 1);


+ 9
- 8
src/app/Toolbar.cpp View File

@@ -43,11 +43,11 @@ struct FileChoice : ChoiceButton {
menu->box.size.x = box.size.x;

{
menu->pushChild(construct<NewItem>(&MenuItem::text, "New", &MenuItem::rightText, GUI_MOD_KEY_NAME "+N"));
menu->pushChild(construct<OpenItem>(&MenuItem::text, "Open", &MenuItem::rightText, GUI_MOD_KEY_NAME "+O"));
menu->pushChild(construct<SaveItem>(&MenuItem::text, "Save", &MenuItem::rightText, GUI_MOD_KEY_NAME "+S"));
menu->pushChild(construct<SaveAsItem>(&MenuItem::text, "Save as", &MenuItem::rightText, GUI_MOD_KEY_NAME "+Shift+S"));
menu->pushChild(construct<QuitItem>(&MenuItem::text, "Quit", &MenuItem::rightText, GUI_MOD_KEY_NAME "+Q"));
menu->addChild(construct<NewItem>(&MenuItem::text, "New", &MenuItem::rightText, GUI_MOD_KEY_NAME "+N"));
menu->addChild(construct<OpenItem>(&MenuItem::text, "Open", &MenuItem::rightText, GUI_MOD_KEY_NAME "+O"));
menu->addChild(construct<SaveItem>(&MenuItem::text, "Save", &MenuItem::rightText, GUI_MOD_KEY_NAME "+S"));
menu->addChild(construct<SaveAsItem>(&MenuItem::text, "Save as", &MenuItem::rightText, GUI_MOD_KEY_NAME "+Shift+S"));
menu->addChild(construct<QuitItem>(&MenuItem::text, "Quit", &MenuItem::rightText, GUI_MOD_KEY_NAME "+Q"));
}
}
};
@@ -75,7 +75,7 @@ struct SampleRateChoice : ChoiceButton {

PauseItem *pauseItem = new PauseItem();
pauseItem->text = gPaused ? "Resume engine" : "Pause engine";
menu->pushChild(pauseItem);
menu->addChild(pauseItem);

float sampleRates[] = {44100, 48000, 88200, 96000, 176400, 192000};
int sampleRatesLen = sizeof(sampleRates) / sizeof(sampleRates[0]);
@@ -83,7 +83,7 @@ struct SampleRateChoice : ChoiceButton {
SampleRateItem *item = new SampleRateItem();
item->text = stringf("%.0f Hz", sampleRates[i]);
item->sampleRate = sampleRates[i];
menu->pushChild(item);
menu->addChild(item);
}
}
void step() override {
@@ -151,12 +151,13 @@ Toolbar::Toolbar() {
struct ZoomSlider : Slider {
void onAction(EventAction &e) override {
Slider::onAction(e);
gRackScene->zoomWidget->setZoom(value / 100.0);
gRackScene->zoomWidget->setZoom(roundf(value) / 100.0);
}
};
zoomSlider = new ZoomSlider();
zoomSlider->box.pos = Vec(xPos, margin);
zoomSlider->box.size.x = 150;
zoomSlider->precision = 0;
zoomSlider->label = "Zoom";
zoomSlider->unit = "%";
zoomSlider->setLimits(25.0, 200.0);


+ 0
- 5
src/app/WireWidget.cpp View File

@@ -86,11 +86,6 @@ static int lastWireColorId = -1;
WireWidget::WireWidget() {
lastWireColorId = (lastWireColorId + 1) % LENGTHOF(wireColors);
color = wireColors[lastWireColorId];

// inputLight = construct<PolarityLight>(&PolarityLight::posColor, COLOR_GREEN, &PolarityLight::negColor, COLOR_RED);
// outputLight = construct<PolarityLight>(&PolarityLight::posColor, COLOR_GREEN, &PolarityLight::negColor, COLOR_RED);
// addChild(inputLight);
// addChild(outputLight);
}

WireWidget::~WireWidget() {


+ 8
- 8
src/core/AudioInterface.cpp View File

@@ -128,7 +128,7 @@ struct AudioInterface : Module {
openStream();
}

void reset() override {
void onReset() override {
closeStream();
}
};
@@ -169,7 +169,7 @@ void AudioInterface::step() {
// Once full, sample rate convert the input
// inputBuffer -> SRC -> inputSrcBuffer
if (inputBuffer.full()) {
inputSrc.setRatio(sampleRate / engineGetSampleRate());
inputSrc.setRates(engineGetSampleRate(), sampleRate);
int inLen = inputBuffer.size();
int outLen = inputSrcBuffer.capacity();
inputSrc.process(inputBuffer.startData(), &inLen, inputSrcBuffer.endData(), &outLen);
@@ -211,7 +211,7 @@ void AudioInterface::stepStream(const float *input, float *output, int numFrames
}

// Pass output through sample rate converter
outputSrc.setRatio(engineGetSampleRate() / sampleRate);
outputSrc.setRates(sampleRate, engineGetSampleRate());
int inLen = numFrames;
int outLen = outputBuffer.capacity();
outputSrc.process(inputFrames, &inLen, outputBuffer.endData(), &outLen);
@@ -414,7 +414,7 @@ struct AudioDriverChoice : ChoiceButton {
audioItem->audioInterface = audioInterface;
audioItem->driver = driver;
audioItem->text = audioInterface->getDriverName(driver);
menu->pushChild(audioItem);
menu->addChild(audioItem);
}
}
void step() override {
@@ -446,14 +446,14 @@ struct AudioDeviceChoice : ChoiceButton {
audioItem->audioInterface = audioInterface;
audioItem->device = -1;
audioItem->text = "No device";
menu->pushChild(audioItem);
menu->addChild(audioItem);
}
for (int device = 0; device < deviceCount; device++) {
AudioDeviceItem *audioItem = new AudioDeviceItem();
audioItem->audioInterface = audioInterface;
audioItem->device = device;
audioItem->text = audioInterface->getDeviceName(device);
menu->pushChild(audioItem);
menu->addChild(audioItem);
}
}
void step() override {
@@ -487,7 +487,7 @@ struct SampleRateChoice : ChoiceButton {
item->audioInterface = audioInterface;
item->sampleRate = sampleRate;
item->text = stringf("%.0f Hz", sampleRate);
menu->pushChild(item);
menu->addChild(item);
}
}
void step() override {
@@ -519,7 +519,7 @@ struct BlockSizeChoice : ChoiceButton {
item->audioInterface = audioInterface;
item->blockSize = blockSizes[i];
item->text = stringf("%d", blockSizes[i]);
menu->pushChild(item);
menu->addChild(item);
}
}
void step() override {


+ 15
- 10
src/core/MidiCCToCV.cpp View File

@@ -1,9 +1,9 @@
#include <list>
#include <algorithm>
#include "rtmidi/RtMidi.h"
#include "core.hpp"
#include "MidiIO.hpp"


struct CCValue {
int val = 0; // Controller value
TransitionSmoother tSmooth;
@@ -81,7 +81,7 @@ struct MIDICCToCVInterface : MidiIO, Module {
}
}

void reset() override {
void onReset() override {
resetMidi();
}

@@ -91,11 +91,11 @@ void MIDICCToCVInterface::step() {
if (isPortOpen()) {
std::vector<unsigned char> message;


// midiIn->getMessage returns empty vector if there are no messages in the queue
getMessage(&message);
while (message.size() > 0) {
if (message.size() > 0) {
processMidi(message);
getMessage(&message);
}
}

@@ -117,7 +117,7 @@ void MIDICCToCVInterface::resetMidi() {
for (int i = 0; i < NUM_OUTPUTS; i++) {
cc[i].val = 0;
cc[i].resetSync();
cc[i].tSmooth.set(0,0);
cc[i].tSmooth.set(0, 0);
}
};

@@ -147,7 +147,8 @@ void MIDICCToCVInterface::processMidi(std::vector<unsigned char> msg) {
cc[i].syncFirst = false;
if (data2 < cc[i].val + 2 && data2 > cc[i].val - 2) {
cc[i].sync = 0;
}else {
}
else {
cc[i].sync = absi(data2 - cc[i].val);
}
return;
@@ -156,7 +157,8 @@ void MIDICCToCVInterface::processMidi(std::vector<unsigned char> msg) {
if (cc[i].sync == 0) {
cc[i].val = data2;
cc[i].changed = true;
} else {
}
else {
cc[i].sync = absi(data2 - cc[i].val);
}
}
@@ -225,12 +227,14 @@ void CCTextField::onTextChange() {
text = "";
begin = end = 0;
module->cc[outNum].num = -1;
} else {
}
else {
module->cc[outNum].num = num;
module->cc[outNum].resetSync();
}

} catch (...) {
}
catch (...) {
text = "";
begin = end = 0;
module->cc[outNum].num = -1;
@@ -309,7 +313,8 @@ MIDICCToCVWidget::MIDICCToCVWidget() {

if ((i + 1) % 4 == 0) {
yPos += 47 + margin;
} else {
}
else {
yPos -= labelHeight + margin;
}
}


+ 29
- 28
src/core/MidiClockToCV.cpp View File

@@ -1,10 +1,10 @@
#include <list>
#include <algorithm>
#include "rtmidi/RtMidi.h"
#include "core.hpp"
#include "MidiIO.hpp"
#include "dsp/digital.hpp"


using namespace rack;

struct MIDIClockToCVInterface : MidiIO, Module {
@@ -71,7 +71,7 @@ struct MIDIClockToCVInterface : MidiIO, Module {

void resetMidi() override;

json_t *toJson() override{
json_t *toJson() override {
json_t *rootJ = json_object();
addBaseJson(rootJ);
json_object_set_new(rootJ, "clock1ratio", json_integer(clock1ratio));
@@ -79,7 +79,7 @@ struct MIDIClockToCVInterface : MidiIO, Module {
return rootJ;
}

void fromJson(json_t *rootJ) override{
void fromJson(json_t *rootJ) override {
baseFromJson(rootJ);
json_t *c1rJ = json_object_get(rootJ, "clock1ratio");
if (c1rJ) {
@@ -101,9 +101,8 @@ void MIDIClockToCVInterface::step() {

// midiIn->getMessage returns empty vector if there are no messages in the queue
getMessage(&message);
while (message.size() > 0) {
if (message.size() > 0) {
processMidi(message);
getMessage(&message);
}
}

@@ -185,18 +184,18 @@ void MIDIClockToCVInterface::resetMidi() {
void MIDIClockToCVInterface::processMidi(std::vector<unsigned char> msg) {

switch (msg[0]) {
case 0xfa:
start = true;
break;
case 0xfb:
cont = true;
break;
case 0xfc:
stop = true;
break;
case 0xf8:
tick = true;
break;
case 0xfa:
start = true;
break;
case 0xfb:
cont = true;
break;
case 0xfc:
stop = true;
break;
case 0xf8:
tick = true;
break;
}


@@ -218,13 +217,15 @@ struct ClockRatioItem : MenuItem {
struct ClockRatioChoice : ChoiceButton {
int *clockRatio;
const std::vector<std::string> ratioNames = {"Sixteenth note (1:4 ratio)", "Eighth note triplet (1:3 ratio)",
"Eighth note (1:2 ratio)", "Quarter note triplet (2:3 ratio)",
"Quarter note (tap speed)", "Half note triplet (4:3 ratio)",
"Half note (2:1 ratio)", "Whole note (4:1 ratio)",
"Two whole notes (8:1 ratio)"};
"Eighth note (1:2 ratio)", "Quarter note triplet (2:3 ratio)",
"Quarter note (tap speed)", "Half note triplet (4:3 ratio)",
"Half note (2:1 ratio)", "Whole note (4:1 ratio)",
"Two whole notes (8:1 ratio)"
};

const std::vector<std::string> ratioNames_short = {"1:4 ratio", "1:3 ratio", "1:2 ratio", "2:3 ratio", "1:1 ratio",
"4:3", "2:1 ratio", "4:1 ratio", "8:1 ratio"};
"4:3", "2:1 ratio", "4:1 ratio", "8:1 ratio"
};

void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu();
@@ -236,7 +237,7 @@ struct ClockRatioChoice : ChoiceButton {
clockRatioItem->ratio = ratio;
clockRatioItem->clockRatio = clockRatio;
clockRatioItem->text = ratioNames[ratio];
menu->pushChild(clockRatioItem);
menu->addChild(clockRatioItem);
}
}

@@ -270,7 +271,7 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() {
label->box.pos = Vec(box.size.x - margin - 7 * 15, margin);
label->text = "MIDI Clk-CV";
addChild(label);
yPos = labelHeight*2;
yPos = labelHeight * 2;
}

{
@@ -320,8 +321,8 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() {
addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_RATIO));
ClockRatioChoice *ratioChoice = new ClockRatioChoice();
ratioChoice->clockRatio = &module->clock1ratio;
ratioChoice->box.pos = Vec(int(box.size.x/3), yPos);
ratioChoice->box.size.x = int(box.size.x/1.5 - margin);
ratioChoice->box.pos = Vec(int(box.size.x / 3), yPos);
ratioChoice->box.size.x = int(box.size.x / 1.5 - margin);

addChild(ratioChoice);
yPos += ratioChoice->box.size.y + margin * 3;
@@ -344,8 +345,8 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() {
addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_RATIO));
ClockRatioChoice *ratioChoice = new ClockRatioChoice();
ratioChoice->clockRatio = &module->clock2ratio;
ratioChoice->box.pos = Vec(int(box.size.x/3), yPos);
ratioChoice->box.size.x = int(box.size.x/1.5 - margin);
ratioChoice->box.pos = Vec(int(box.size.x / 3), yPos);
ratioChoice->box.size.x = int(box.size.x / 1.5 - margin);

addChild(ratioChoice);
yPos += ratioChoice->box.size.y + margin * 3;


+ 7
- 6
src/core/MidiIO.cpp View File

@@ -1,9 +1,9 @@
#include <list>
#include <algorithm>
#include "rtmidi/RtMidi.h"
#include "core.hpp"
#include "MidiIO.hpp"


using namespace rack;


@@ -59,7 +59,8 @@ std::vector<std::string> MidiIO::getDevices() {
RtMidiIn *m;
try {
m = new RtMidiIn();
} catch (RtMidiError &error) {
}
catch (RtMidiError &error) {
warn("Failed to create RtMidiIn: %s", error.getMessage().c_str());
return names;
}
@@ -209,7 +210,7 @@ void MidiChoice::onAction(EventAction &e) {
MidiItem *midiItem = new MidiItem();
midiItem->midiModule = midiModule;
midiItem->text = "";
menu->pushChild(midiItem);
menu->addChild(midiItem);
}

std::vector<std::string> deviceNames = midiModule->getDevices();
@@ -217,7 +218,7 @@ void MidiChoice::onAction(EventAction &e) {
MidiItem *midiItem = new MidiItem();
midiItem->midiModule = midiModule;
midiItem->text = deviceNames[i];
menu->pushChild(midiItem);
menu->addChild(midiItem);
}
}

@@ -245,14 +246,14 @@ void ChannelChoice::onAction(EventAction &e) {
channelItem->midiModule = midiModule;
channelItem->channel = -1;
channelItem->text = "All";
menu->pushChild(channelItem);
menu->addChild(channelItem);
}
for (int channel = 0; channel < 16; channel++) {
ChannelItem *channelItem = new ChannelItem();
channelItem->midiModule = midiModule;
channelItem->channel = channel;
channelItem->text = stringf("%d", channel + 1);
menu->pushChild(channelItem);
menu->addChild(channelItem);
}
}



+ 10
- 6
src/core/MidiIO.hpp View File

@@ -1,10 +1,15 @@
#include <unordered_map>
#include "rack.hpp"

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsuggest-override"
#include "rtmidi/RtMidi.h"
#pragma GCC diagnostic pop


using namespace rack;


struct IgnoreFlags {
bool midiSysex = true;
bool midiTime = true;
@@ -16,7 +21,6 @@ struct MidiMessage {
double timeStamp;

MidiMessage() : bytes(0), timeStamp(0.0) {};

};

/**
@@ -131,10 +135,10 @@ struct TransitionSmoother {
switch (m) {
case DELTA:
/* If the change is smaller, the transition phase is longer */
this->step = delta > 0 ? delta/l : -delta/l;
this->step = delta > 0 ? delta / l : -delta / l;
break;
case CONST:
this->step = 1.0/l;
this->step = 1.0 / l;
break;
}

@@ -149,13 +153,13 @@ struct TransitionSmoother {

switch (t) {
case SMOOTHSTEP:
next += delta*x*x*(3-2*x);
next += delta * x * x * (3 - 2 * x);
break;
case EXP:
next += delta*x*x;
next += delta * x * x;
break;
case LIN:
next += delta*x;
next += delta * x;
break;
}



+ 3
- 4
src/core/MidiToCV.cpp View File

@@ -1,10 +1,10 @@
#include <list>
#include <algorithm>
#include "rtmidi/RtMidi.h"
#include "core.hpp"
#include "MidiIO.hpp"
#include "dsp/digital.hpp"


/*
* MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to
* CV
@@ -74,7 +74,7 @@ struct MIDIToCVInterface : MidiIO, Module {
baseFromJson(rootJ);
}

void reset() override {
void onReset() override {
resetMidi();
}

@@ -100,9 +100,8 @@ void MIDIToCVInterface::step() {

// midiIn->getMessage returns empty vector if there are no messages in the queue
getMessage(&message);
while (message.size() > 0) {
if (message.size() > 0) {
processMidi(message);
getMessage(&message);
}
}



+ 9
- 7
src/core/MidiTriggerToCV.cpp View File

@@ -1,10 +1,10 @@
#include <list>
#include <algorithm>
#include "rtmidi/RtMidi.h"
#include "core.hpp"
#include "MidiIO.hpp"
#include "dsp/digital.hpp"


using namespace rack;

struct TriggerValue {
@@ -63,7 +63,7 @@ struct MIDITriggerToCVInterface : MidiIO, Module {
}
}

void reset() override {
void onReset() override {
resetMidi();
}
};
@@ -75,9 +75,8 @@ void MIDITriggerToCVInterface::step() {

// midiIn->getMessage returns empty vector if there are no messages in the queue
getMessage(&message);
while (message.size() > 0) {
if (message.size() > 0) {
processMidi(message);
getMessage(&message);
}
}

@@ -169,10 +168,12 @@ void TriggerTextField::onTextChange() {
text = "";
begin = end = 0;
module->trigger[outNum].num = -1;
}else {
}
else {
module->trigger[outNum].num = num;
}
} catch (...) {
}
catch (...) {
text = "";
begin = end = 0;
module->trigger[outNum].num = -1;
@@ -271,7 +272,8 @@ MIDITriggerToCVWidget::MIDITriggerToCVWidget() {

if ((i + 1) % 4 == 0) {
yPos += 47 + margin;
} else {
}
else {
yPos -= labelHeight + margin;
}
}


+ 64
- 54
src/core/QuadMidiToCV.cpp View File

@@ -1,15 +1,16 @@
#include <list>
#include <algorithm>
#include "rtmidi/RtMidi.h"
#include "core.hpp"
#include "MidiIO.hpp"
#include "dsp/digital.hpp"


struct MidiKey {
int pitch = 60;
int at = 0; // aftertouch
int vel = 0; // velocity
bool gate = false;
bool pedal_gate_released = false;
};

struct QuadMIDIToCVInterface : MidiIO, Module {
@@ -72,7 +73,7 @@ struct QuadMIDIToCVInterface : MidiIO, Module {
baseFromJson(rootJ);
}

void reset() override {
void onReset() override {
resetMidi();
}

@@ -98,16 +99,11 @@ void QuadMIDIToCVInterface::resetMidi() {
void QuadMIDIToCVInterface::step() {
if (isPortOpen()) {
std::vector<unsigned char> message;
int msgsProcessed = 0;

// midiIn->getMessage returns empty vector if there are no messages in the queue
// NOTE: For the quadmidi we will process max 4 midi messages per step to avoid
// problems with parallel input.
getMessage(&message);
while (msgsProcessed < 4 && message.size() > 0) {
if (message.size() > 0) {
processMidi(message);
getMessage(&message);
msgsProcessed++;
}
}

@@ -140,49 +136,60 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {
return;

switch (status) {
// note off
case 0x8: {
// note off
case 0x8: {
gate = false;
}
break;
case 0x9: // note on
if (data2 > 0) {
gate = true;
}
else {
// For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released.
gate = false;
}
break;
case 0x9: // note on
if (data2 > 0) {
gate = true;
} else {
// For some reason, some keyboards send a "note on" event with a velocity of 0 to signal that the key has been released.
gate = false;
}
break;
case 0xa: // channel aftertouch
for (int i = 0; i < 4; i++) {
if (activeKeys[i].pitch == data1) {
activeKeys[i].at = data2;
}
break;
case 0xa: // channel aftertouch
for (int i = 0; i < 4; i++) {
if (activeKeys[i].pitch == data1) {
activeKeys[i].at = data2;
}
return;
case 0xb: // cc
if (data1 == 0x40) { // pedal
pedal = (data2 >= 64);
if (!pedal) {
open.clear();
for (int i = 0; i < 4; i++) {
}
return;
case 0xb: // cc
if (data1 == 0x40) { // pedal
pedal = (data2 >= 64);
if (!pedal) {
for (int i = 0; i < 4; i++) {
if (activeKeys[i].pedal_gate_released) {
activeKeys[i].gate = false;
open.push_back(i);
activeKeys[i].pedal_gate_released = false;
if (std::find(open.begin(), open.end(), i) != open.end()) {
open.remove(i);
}
open.push_front(i);
}
}
}
return;
default:
return;
}
return;
default:
return;
}

if (pedal && !gate) {
for (int i = 0; i < 4; i++) {
if (activeKeys[i].pitch == data1 && activeKeys[i].gate) {
activeKeys[i].pedal_gate_released = true;
}
}
return;
}

if (!gate) {
for (int i = 0; i < 4; i++) {
if (activeKeys[i].pitch == data1) {
if (activeKeys[i].pitch == data1 && activeKeys[i].gate) {
activeKeys[i].gate = false;
activeKeys[i].vel = data2;
if (std::find(open.begin(), open.end(), i) != open.end()) {
@@ -201,25 +208,26 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {
}

if (!activeKeys[0].gate && !activeKeys[1].gate &&
!activeKeys[2].gate && !activeKeys[3].gate) {
!activeKeys[2].gate && !activeKeys[3].gate) {
open.sort();
}


switch (mode) {
case RESET:
if (open.size() >= 4) {
for (int i = 0; i < 4; i++) {
activeKeys[i].gate = false;
open.push_back(i);
}
case RESET:
if (open.size() >= 4) {
open.clear();
for (int i = 0; i < 4; i++) {
activeKeys[i].gate = false;
open.push_back(i);
}
break;
case REASSIGN:
open.push_back(open.front());
break;
case ROTATE:
break;
}
break;
case REASSIGN:
open.push_back(open.front());
break;
case ROTATE:
break;
}

int next = open.front();
@@ -233,10 +241,12 @@ void QuadMIDIToCVInterface::processMidi(std::vector<unsigned char> msg) {

open.push_front(i);
activeKeys[i].gate = false;
activeKeys[i].pedal_gate_released = false;
}
}

activeKeys[next].gate = true;
activeKeys[next].pedal_gate_released = false;
activeKeys[next].pitch = data1;
activeKeys[next].vel = data2;
}
@@ -254,7 +264,7 @@ struct ModeItem : MenuItem {
int mode;
QuadMIDIToCVInterface *module;

void onAction(EventAction &e) {
void onAction(EventAction &e) override {
module->setMode(mode);
}
};
@@ -264,7 +274,7 @@ struct ModeChoice : ChoiceButton {
const std::vector<std::string> modeNames = {"ROTATE", "RESET", "REASSIGN"};


void onAction(EventAction &e) {
void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu();
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round();
menu->box.size.x = box.size.x;
@@ -274,11 +284,11 @@ struct ModeChoice : ChoiceButton {
modeItem->mode = i;
modeItem->module = module;
modeItem->text = modeNames[i];
menu->pushChild(modeItem);
menu->addChild(modeItem);
}
}

void step() {
void step() override {
text = modeNames[module->getMode()];
}
};
@@ -313,7 +323,7 @@ QuadMidiToCVWidget::QuadMidiToCVWidget() {
}

addParam(createParam<LEDButton>(Vec(12 * 15, labelHeight), module, QuadMIDIToCVInterface::RESET_PARAM, 0.0, 1.0,
0.0));
0.0));
addChild(createLight<SmallLight<RedLight>>(Vec(12 * 15 + 5, labelHeight + 5), module, QuadMIDIToCVInterface::RESET_LIGHT));
{
Label *label = new Label();


+ 5
- 2
src/engine.cpp View File

@@ -11,7 +11,6 @@
#include "engine.hpp"
#include "util.hpp"


namespace rack {

float sampleRate;
@@ -33,7 +32,6 @@ static Module *smoothModule = NULL;
static int smoothParamId;
static float smoothValue;


float Light::getBrightness() {
return sqrtf(fmaxf(0.0, value));
}
@@ -76,11 +74,13 @@ static void engineStep() {
float delta = smoothValue - value;
if (fabsf(delta) < snap) {
smoothModule->params[smoothParamId].value = smoothValue;
smoothModule->params[smoothParamId].ossia_param->push_value(smoothValue);
smoothModule = NULL;
}
else {
value += delta * lambda * sampleTime;
smoothModule->params[smoothParamId].value = value;
smoothModule->params[smoothParamId].ossia_param->push_value(value);
}
}

@@ -240,6 +240,7 @@ void engineRemoveWire(Wire *wire) {

void engineSetParam(Module *module, int paramId, float value) {
module->params[paramId].value = value;
module->params[paramId].ossia_param->push_value(value);
}

void engineSetParamSmooth(Module *module, int paramId, float value) {
@@ -248,10 +249,12 @@ void engineSetParamSmooth(Module *module, int paramId, float value) {
// Since only one param can be smoothed at a time, if another param is currently being smoothed, skip to its final state
if (smoothModule && !(smoothModule == module && smoothParamId == paramId)) {
smoothModule->params[smoothParamId].value = smoothValue;
smoothModule->params[smoothParamId].ossia_param->push_value(smoothValue);
}
smoothModule = module;
smoothParamId = paramId;
smoothValue = value;
module->params[paramId].ossia_param->push_value(smoothValue);
}

void engineSetSampleRate(float newSampleRate) {


+ 59
- 24
src/gui.cpp View File

@@ -8,8 +8,10 @@

#include "../ext/osdialog/osdialog.h"

#define NANOVG_GL2_IMPLEMENTATION
// #define NANOVG_GL3_IMPLEMENTATION
#define NANOVG_GL2 1
// #define NANOVG_GL3 1
// #define NANOVG_GLES2 1
#define NANOVG_GL_IMPLEMENTATION 1
#include "../ext/nanovg/src/nanovg_gl.h"
// Hack to get framebuffer objects working on OpenGL 2 (we blindly assume the extension is supported)
#define NANOVG_FBO_VALID 1
@@ -31,7 +33,8 @@ GLFWwindow *gWindow = NULL;
NVGcontext *gVg = NULL;
NVGcontext *gFramebufferVg = NULL;
std::shared_ptr<Font> gGuiFont;
float gPixelRatio = 0.0;
float gPixelRatio = 1.0;
float gWindowRatio = 1.0;
bool gAllowCursorLock = true;
int gGuiFrame;
Vec gMousePos;
@@ -40,7 +43,6 @@ std::string lastWindowTitle;


void windowSizeCallback(GLFWwindow* window, int width, int height) {
gScene->box.size = Vec(width, height);
}

void mouseButtonCallback(GLFWwindow *window, int button, int action, int mods) {
@@ -143,14 +145,16 @@ void mouseButtonStickyCallback(GLFWwindow *window, int button, int action, int m
}

void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
Vec mousePos = Vec(xpos, ypos).round();
Vec mousePos = Vec(xpos, ypos).div(gPixelRatio / gWindowRatio).round();
Vec mouseRel = mousePos.minus(gMousePos);

int cursorMode = glfwGetInputMode(gWindow, GLFW_CURSOR);
(void) cursorMode;

#ifdef ARCH_MAC
// Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
// This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
int mouseMode = glfwGetInputMode(gWindow, GLFW_CURSOR);
if (mouseMode == GLFW_CURSOR_HIDDEN) {
if (cursorMode == GLFW_CURSOR_HIDDEN) {
// CGSetLocalEventsSuppressionInterval(0.0);
glfwSetCursorPos(gWindow, gMousePos.x, gMousePos.y);
CGAssociateMouseAndMouseCursorPosition(true);
@@ -311,12 +315,15 @@ void guiInit() {
exit(1);
}

#if defined NANOVG_GL2
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
// glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
// glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
// glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
// glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#elif defined NANOVG_GL3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#endif
glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
lastWindowTitle = "";
@@ -332,6 +339,7 @@ void guiInit() {

glfwSetWindowSizeCallback(gWindow, windowSizeCallback);
glfwSetMouseButtonCallback(gWindow, mouseButtonStickyCallback);
// Call this ourselves, but on every frame instead of only when the mouse moves
// glfwSetCursorPosCallback(gWindow, cursorPosCallback);
glfwSetCursorEnterCallback(gWindow, cursorEnterCallback);
glfwSetScrollCallback(gWindow, scrollCallback);
@@ -353,11 +361,22 @@ void guiInit() {
glfwSetWindowSizeLimits(gWindow, 640, 480, GLFW_DONT_CARE, GLFW_DONT_CARE);

// Set up NanoVG
#if defined NANOVG_GL2
gVg = nvgCreateGL2(NVG_ANTIALIAS);
// gVg = nvgCreateGL3(NVG_ANTIALIAS);
#elif defined NANOVG_GL3
gVg = nvgCreateGL3(NVG_ANTIALIAS);
#elif defined NANOVG_GLES2
gVg = nvgCreateGLES2(NVG_ANTIALIAS);
#endif
assert(gVg);

#if defined NANOVG_GL2
gFramebufferVg = nvgCreateGL2(NVG_ANTIALIAS);
#elif defined NANOVG_GL3
gFramebufferVg = nvgCreateGL3(NVG_ANTIALIAS);
#elif defined NANOVG_GLES2
gFramebufferVg = nvgCreateGLES2(NVG_ANTIALIAS);
#endif
assert(gFramebufferVg);

// Set up Blendish
@@ -375,20 +394,29 @@ void guiInit() {

void guiDestroy() {
gGuiFont.reset();

#if defined NANOVG_GL2
nvgDeleteGL2(gVg);
// nvgDeleteGL3(gVg);
#elif defined NANOVG_GL3
nvgDeleteGL3(gVg);
#elif defined NANOVG_GLES2
nvgDeleteGLES2(gVg);
#endif

#if defined NANOVG_GL2
nvgDeleteGL2(gFramebufferVg);
#elif defined NANOVG_GL3
nvgDeleteGL3(gFramebufferVg);
#elif defined NANOVG_GLES2
nvgDeleteGLES2(gFramebufferVg);
#endif

glfwDestroyWindow(gWindow);
glfwTerminate();
}

void guiRun() {
assert(gWindow);
{
int width, height;
glfwGetWindowSize(gWindow, &width, &height);
windowSizeCallback(gWindow, width, height);
}
gGuiFrame = 0;
while(!glfwWindowShouldClose(gWindow)) {
double startTime = glfwGetTime();
@@ -417,18 +445,25 @@ void guiRun() {
lastWindowTitle = windowTitle;
}

// Get framebuffer size
int width, height;
glfwGetFramebufferSize(gWindow, &width, &height);
int windowWidth, windowHeight;
glfwGetWindowSize(gWindow, &windowWidth, &windowHeight);
float pixelRatio = (float)width / windowWidth;
// Get desired scaling
float pixelRatio;
glfwGetWindowContentScale(gWindow, &pixelRatio, NULL);
pixelRatio = roundf(pixelRatio);
if (pixelRatio != gPixelRatio) {
EventZoom eZoom;
gScene->onZoom(eZoom);
gPixelRatio = pixelRatio;
}

// Get framebuffer/window ratio
int width, height;
glfwGetFramebufferSize(gWindow, &width, &height);
int windowWidth, windowHeight;
glfwGetWindowSize(gWindow, &windowWidth, &windowHeight);
gWindowRatio = (float)width / windowWidth;

gScene->box.size = Vec(width, height).div(gPixelRatio / gWindowRatio);

// Step scene
gScene->step();



+ 11
- 46
src/plugin.cpp View File

@@ -32,52 +32,6 @@ namespace rack {
std::list<Plugin*> gPlugins;
std::string gToken;

std::string gTagNames[NUM_TAGS] = {
"Amplifier/VCA",
"Attenuator",
"Blank",
"Clock",
"Controller",
"Delay",
"Digital",
"Distortion",
"Drum",
"Dual/Stereo",
"Dynamics",
"Effect",
"Envelope Follower",
"Envelope Generator",
"Equalizer",
"External",
"Filter/VCF",
"Function Generator",
"Granular",
"LFO",
"Logic",
"Low Pass Gate",
"MIDI",
"Mixer",
"Multiple",
"Noise",
"Oscillator/VCO",
"Panning",
"Quad",
"Quantizer",
"Random",
"Reverb",
"Ring Modulator",
"Sample and Hold",
"Sampler",
"Sequencer",
"Slew Limiter",
"Switch",
"Synth Voice",
"Tuner",
"Utility",
"Visual",
"Waveshaper",
};


static bool isDownloading = false;
static float downloadProgress = 0.0;
@@ -145,6 +99,16 @@ static int loadPlugin(std::string path) {
plugin->handle = handle;
initCallback(plugin);

// Reject plugin if slug already exists
for (Plugin *p : gPlugins) {
if (plugin->slug == p->slug) {
warn("Plugin \"%s\" is already loaded, not attempting to load it again");
// TODO
// Fix memory leak with `plugin` here
return -1;
}
}

// Add plugin to list
gPlugins.push_back(plugin);
info("Loaded plugin %s", libraryFilename.c_str());
@@ -410,6 +374,7 @@ bool pluginSync(bool dryRun) {
////////////////////

void pluginInit() {
tagsInit();
// Load core
// This function is defined in core.cpp
Plugin *coreManufacturer = new Plugin();


+ 64
- 0
src/tags.cpp View File

@@ -0,0 +1,64 @@
#include "tags.hpp"


namespace rack {


std::string gTagNames[NUM_TAGS];


void tagsInit() {
gTagNames[AMPLIFIER_TAG] = "Amplifier/VCA";
gTagNames[ATTENUATOR_TAG] = "Attenuator";
gTagNames[BLANK_TAG] = "Blank";
gTagNames[CHORUS_TAG] = "Chorus";
gTagNames[CLOCK_TAG] = "Clock";
gTagNames[COMPRESSOR_TAG] = "Compressor";
gTagNames[CONTROLLER_TAG] = "Controller";
gTagNames[DELAY_TAG] = "Delay";
gTagNames[DIGITAL_TAG] = "Digital";
gTagNames[DISTORTION_TAG] = "Distortion";
gTagNames[DRUM_TAG] = "Drum";
gTagNames[DUAL_TAG] = "Dual/Stereo";
gTagNames[DYNAMICS_TAG] = "Dynamics";
gTagNames[EFFECT_TAG] = "Effect";
gTagNames[ENVELOPE_FOLLOWER_TAG] = "Envelope Follower";
gTagNames[ENVELOPE_GENERATOR_TAG] = "Envelope Generator";
gTagNames[EQUALIZER_TAG] = "Equalizer";
gTagNames[EXTERNAL_TAG] = "External";
gTagNames[FILTER_TAG] = "Filter/VCF";
gTagNames[FLANGER_TAG] = "Flanger";
gTagNames[FUNCTION_GENERATOR_TAG] = "Function Generator";
gTagNames[GRANULAR_TAG] = "Granular";
gTagNames[LFO_TAG] = "LFO";
gTagNames[LIMITER_TAG] = "Limiter";
gTagNames[LOGIC_TAG] = "Logic";
gTagNames[LOW_PASS_GATE_TAG] = "Low Pass Gate";
gTagNames[MIDI_TAG] = "MIDI";
gTagNames[MIXER_TAG] = "Mixer";
gTagNames[MULTIPLE_TAG] = "Multiple";
gTagNames[NOISE_TAG] = "Noise";
gTagNames[OSCILLATOR_TAG] = "Oscillator/VCO";
gTagNames[PANNING_TAG] = "Panning";
gTagNames[PHASER_TAG] = "Phaser";
gTagNames[QUAD_TAG] = "Quad";
gTagNames[QUANTIZER_TAG] = "Quantizer";
gTagNames[RANDOM_TAG] = "Random";
gTagNames[RECORDING_TAG] = "Recording";
gTagNames[REVERB_TAG] = "Reverb";
gTagNames[RING_MODULATOR_TAG] = "Ring Modulator";
gTagNames[SAMPLE_AND_HOLD_TAG] = "Sample and Hold";
gTagNames[SAMPLER_TAG] = "Sampler";
gTagNames[SEQUENCER_TAG] = "Sequencer";
gTagNames[SLEW_LIMITER_TAG] = "Slew Limiter";
gTagNames[SWITCH_TAG] = "Switch";
gTagNames[SYNTH_VOICE_TAG] = "Synth Voice";
gTagNames[TUNER_TAG] = "Tuner";
gTagNames[UTILITY_TAG] = "Utility";
gTagNames[VISUAL_TAG] = "Visual";
gTagNames[VOCODER_TAG] = "Vocoder";
gTagNames[WAVESHAPER_TAG] = "Waveshaper";
}


} // namespace rack

+ 4
- 2
src/widgets/MenuOverlay.cpp View File

@@ -13,10 +13,12 @@ void MenuOverlay::step() {
}
}

void MenuOverlay::onDragDrop(EventDragDrop &e) {
if (e.origin == this) {
void MenuOverlay::onMouseDown(EventMouseDown &e) {
Widget::onMouseDown(e);
if (!e.consumed) {
// deletes `this`
gScene->setOverlay(NULL);
e.consumed = true;
}
}



+ 9
- 4
src/widgets/SVGWidget.cpp View File

@@ -59,16 +59,19 @@ static void drawSVG(NVGcontext *vg, NSVGimage *svg) {
for (NSVGshape *shape = svg->shapes; shape; shape = shape->next, shapeIndex++) {
DEBUG_ONLY(printf(" new shape: %d id \"%s\", fillrule %d, from (%f, %f) to (%f, %f)\n", shapeIndex, shape->id, shape->fillRule, shape->bounds[0], shape->bounds[1], shape->bounds[2], shape->bounds[3]);)

// Visibility
if (!(shape->flags & NSVG_FLAGS_VISIBLE))
continue;

nvgSave(vg);

// Opacity
if (shape->opacity < 1.0)
nvgGlobalAlpha(vg, shape->opacity);

// Build path
nvgBeginPath(vg);

// Iterate path linked list
for (NSVGpath *path = shape->paths; path; path = path->next) {
DEBUG_ONLY(printf(" new path: %d points, %s, from (%f, %f) to (%f, %f)\n", path->npts, path->closed ? "closed" : "open", path->bounds[0], path->bounds[1], path->bounds[2], path->bounds[3]);)
@@ -81,6 +84,7 @@ static void drawSVG(NVGcontext *vg, NSVGimage *svg) {
DEBUG_ONLY(printf(" bezier (%f, %f) to (%f, %f)\n", p[-2], p[-1], p[4], p[5]);)
}

// Close path
if (path->closed)
nvgClosePath(vg);

@@ -161,11 +165,12 @@ static void drawSVG(NVGcontext *vg, NSVGimage *svg) {
}

// Stroke shape
nvgStrokeWidth(vg, shape->strokeWidth);
// strokeDashOffset, strokeDashArray, strokeDashCount not yet supported
// strokeLineJoin, strokeLineCap not yet supported

if (shape->stroke.type) {
nvgStrokeWidth(vg, shape->strokeWidth);
// strokeDashOffset, strokeDashArray, strokeDashCount not yet supported
nvgLineCap(vg, (NVGlineCap) shape->strokeLineCap);
nvgLineJoin(vg, (int) shape->strokeLineJoin);

switch (shape->stroke.type) {
case NSVG_PAINT_COLOR: {
NVGcolor color = getNVGColor(shape->stroke.color);


+ 7
- 0
src/widgets/ZoomWidget.cpp View File

@@ -65,5 +65,12 @@ void ZoomWidget::onScroll(EventScroll &e) {
e.pos = pos;
}

void ZoomWidget::onPathDrop(EventPathDrop &e) {
Vec pos = e.pos;
e.pos = e.pos.div(zoom);
Widget::onPathDrop(e);
e.pos = pos;
}


} // namespace rack

Loading…
Cancel
Save