@@ -1,5 +1,6 @@ | |||
/Rack | |||
/Rack.exe | |||
/libRack.a | |||
/autosave.json | |||
/settings.json | |||
/plugins | |||
@@ -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 |
@@ -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,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/ | |||
@@ -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. | |||
@@ -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 $@ $< |
@@ -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 |
@@ -0,0 +1 @@ | |||
Subproject commit 682f1cf203707f21c2eed4fa3f89c23c52accc49 |
@@ -0,0 +1 @@ | |||
Subproject commit ce13dfbf30fd1ab4e7f7eff8886a80f144c75e5d |
@@ -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 */ | |||
@@ -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); | |||
@@ -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; | |||
@@ -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); | |||
} | |||
}; | |||
@@ -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; } | |||
@@ -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; | |||
@@ -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) { | |||
@@ -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 | |||
@@ -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; | |||
} | |||
@@ -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 |
@@ -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; | |||
}; | |||
@@ -21,6 +21,8 @@ ifeq ($(ARCH), win) | |||
TARGET = plugin.dll | |||
endif | |||
DISTRIBUTABLES += $(TARGET) | |||
all: $(TARGET) | |||
@@ -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 |
@@ -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; | |||
@@ -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 |
@@ -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; | |||
} | |||
@@ -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); | |||
} | |||
} | |||
@@ -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)); | |||
} | |||
} | |||
@@ -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); | |||
} | |||
@@ -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)); | |||
@@ -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); | |||
@@ -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); | |||
@@ -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() { | |||
@@ -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 { | |||
@@ -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; | |||
} | |||
} | |||
@@ -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; | |||
@@ -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); | |||
} | |||
} | |||
@@ -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; | |||
} | |||
@@ -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); | |||
} | |||
} | |||
@@ -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; | |||
} | |||
} | |||
@@ -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(); | |||
@@ -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) { | |||
@@ -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(); | |||
@@ -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(); | |||
@@ -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 |
@@ -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; | |||
} | |||
} | |||
@@ -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); | |||
@@ -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 |