@@ -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 |
@@ -1,7 +1,13 @@ | |||
VERSION = 0.6.0dev | |||
FLAGS += \ | |||
-Iinclude \ | |||
-Idep/include -Idep/lib/libzip/include | |||
ifdef RELEASE | |||
FLAGS += -DRELEASE=$(RELEASE) | |||
endif | |||
SOURCES = $(wildcard src/*.cpp src/*/*.cpp) \ | |||
ext/nanovg/src/nanovg.c | |||
@@ -14,7 +20,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 | |||
TARGET = Rack | |||
endif | |||
@@ -23,7 +29,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 | |||
TARGET = Rack | |||
BUNDLE = dist/$(TARGET).app | |||
endif | |||
@@ -33,8 +39,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 | |||
TARGET = Rack.exe | |||
OBJECTS = Rack.res | |||
endif | |||
@@ -69,8 +75,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 | |||
@@ -81,9 +93,6 @@ include compile.mk | |||
dist: all | |||
ifndef VERSION | |||
$(error VERSION must be defined when making distributables) | |||
endif | |||
rm -rf dist | |||
$(MAKE) -C plugins/Fundamental dist | |||
@@ -103,7 +112,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 +123,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 +153,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 +170,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,15 +1,22 @@ | |||
ifdef VERSION | |||
FLAGS += -DVERSION=$(VERSION) | |||
FLAGS += -DVERSION=$(VERSION) | |||
endif | |||
# Generate dependency files alongside the object files | |||
FLAGS += -MMD | |||
FLAGS += -g | |||
# Optimization | |||
FLAGS += -O3 -march=nocona -ffast-math -fno-finite-math-only | |||
FLAGS += -O3 | |||
# Native compile flag will be used unless COMMON=true is passed as a parameter to make | |||
ifeq ($(COMMON), true) | |||
FLAGS += -march=nocona | |||
else | |||
FLAGS += -march=native | |||
endif | |||
FLAGS += -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 | |||
@@ -32,6 +39,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 +57,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 $@ $< |
@@ -3,14 +3,20 @@ LOCAL = $(shell pwd) | |||
# Arch-specifics | |||
include ../arch.mk | |||
FLAGS += -g -O3 -march=nocona | |||
ifeq ($(ARCH),mac) | |||
MACOS_FLAGS = -mmacosx-version-min=10.7 -stdlib=libc++ | |||
export CFLAGS = $(MACOS_FLAGS) | |||
export CXXFLAGS = $(MACOS_FLAGS) | |||
export CPPFLAGS = $(MACOS_FLAGS) | |||
export LDFLAGS = $(MACOS_FLAGS) | |||
FLAGS += -mmacosx-version-min=10.7 -stdlib=libc++ | |||
LDFLAGS += -mmacosx-version-min=10.7 -stdlib=libc++ | |||
endif | |||
CFLAGS += $(FLAGS) | |||
CXXFLAGS += $(FLAGS) | |||
export CFLAGS | |||
export CXXFLAGS | |||
export LDFLAGS | |||
# Commands | |||
WGET = wget -nc | |||
UNTAR = tar xf | |||
@@ -26,7 +32,7 @@ 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 | |||
@@ -38,7 +44,7 @@ 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 | |||
@@ -50,7 +56,7 @@ 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 | |||
@@ -74,7 +80,7 @@ endif | |||
.NOTPARALLEL: | |||
all: $(glew) $(glfw) $(jansson) $(libsamplerate) $(libcurl) $(libzip) $(rtmidi) $(rtaudio) | |||
all: $(glew) $(glfw) $(jansson) $(libspeexdsp) $(libcurl) $(libzip) $(rtmidi) $(rtaudio) | |||
@echo "" | |||
@echo "#######################################" | |||
@echo "# Built all dependencies successfully #" | |||
@@ -89,17 +95,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 +108,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 +151,16 @@ $(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 | |||
clean: | |||
git clean -ffdxi | |||
git clean -ffdx |
@@ -0,0 +1 @@ | |||
Subproject commit 682f1cf203707f21c2eed4fa3f89c23c52accc49 |
@@ -0,0 +1 @@ | |||
Subproject commit ce13dfbf30fd1ab4e7f7eff8886a80f144c75e5d |
@@ -4,8 +4,22 @@ | |||
#include "widgets.hpp" | |||
#define SVG_DPI 75.0 | |||
#define CHECKMARK_STRING "âś”" | |||
#define CHECKMARK(_cond) ((_cond) ? CHECKMARK_STRING : "") | |||
namespace rack { | |||
inline Vec in2px(Vec inches) { | |||
return inches.mult(SVG_DPI); | |||
} | |||
inline Vec mm2px(Vec millimeters) { | |||
return millimeters.mult(SVG_DPI / 25.4); | |||
} | |||
struct Model; | |||
struct Module; | |||
@@ -20,10 +34,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 +61,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 +90,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; | |||
@@ -92,6 +104,8 @@ struct WireWidget : OpaqueWidget { | |||
void updateWire(); | |||
Vec getOutputPos(); | |||
Vec getInputPos(); | |||
json_t *toJson(); | |||
void fromJson(json_t *rootJ); | |||
void draw(NVGcontext *vg) override; | |||
void drawPlugs(NVGcontext *vg); | |||
}; | |||
@@ -134,7 +148,7 @@ struct RackWidget : OpaqueWidget { | |||
void fromJson(json_t *rootJ); | |||
void addModule(ModuleWidget *m); | |||
/** Transfers ownership to the caller so they must `delete` it if that is the intension */ | |||
/** Removes the module and transfers ownership to the caller */ | |||
void deleteModule(ModuleWidget *m); | |||
void cloneModule(ModuleWidget *m); | |||
/** Sets a module's box if non-colliding. Returns true if set */ | |||
@@ -199,6 +213,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 +242,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 +265,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,12 +286,33 @@ 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); | |||
} | |||
}; | |||
//////////////////// | |||
// IO widgets | |||
//////////////////// | |||
struct AudioIO; | |||
struct MidiIO; | |||
struct AudioWidget : OpaqueWidget { | |||
/** Not owned */ | |||
AudioIO *audioIO = NULL; | |||
void onMouseDown(EventMouseDown &e) override; | |||
}; | |||
struct MidiWidget : OpaqueWidget { | |||
/** Not owned */ | |||
MidiIO *midiIO = NULL; | |||
void onMouseDown(EventMouseDown &e) override; | |||
}; | |||
//////////////////// | |||
// lights | |||
//////////////////// | |||
@@ -285,6 +321,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 */ | |||
@@ -398,4 +436,8 @@ extern Toolbar *gToolbar; | |||
void sceneInit(); | |||
void sceneDestroy(); | |||
json_t *colorToJson(NVGcolor color); | |||
NVGcolor jsonToColor(json_t *colorJ); | |||
} // namespace rack |
@@ -0,0 +1,50 @@ | |||
#pragma once | |||
#include <jansson.h> | |||
#pragma GCC diagnostic push | |||
#pragma GCC diagnostic ignored "-Wsuggest-override" | |||
#include <RtAudio.h> | |||
#pragma GCC diagnostic pop | |||
namespace rack { | |||
struct AudioIO { | |||
int maxOutputs = 8; | |||
int maxInputs = 8; | |||
// Stream properties | |||
int driver = 0; | |||
int device = -1; | |||
int sampleRate = 44100; | |||
int blockSize = 256; | |||
int numOutputs = 0; | |||
int numInputs = 0; | |||
RtAudio *rtAudio = NULL; | |||
AudioIO(); | |||
virtual ~AudioIO(); | |||
std::vector<int> listDrivers(); | |||
std::string getDriverName(int driver); | |||
void setDriver(int driver); | |||
int getDeviceCount(); | |||
std::string getDeviceName(int device); | |||
std::string getDeviceDetail(int device); | |||
void openStream(); | |||
void closeStream(); | |||
std::vector<int> listSampleRates(); | |||
virtual void processStream(const float *input, float *output, int length) {} | |||
virtual void onCloseStream() {} | |||
virtual void onOpenStream() {} | |||
json_t *toJson(); | |||
void fromJson(json_t *rootJ); | |||
}; | |||
} // namespace rack |
@@ -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); | |||
@@ -336,6 +336,22 @@ struct BefacoSlidePot : SVGSlider { | |||
} | |||
}; | |||
//////////////////// | |||
// IO widgets | |||
//////////////////// | |||
struct USB_B_AudioWidget : AudioWidget, SVGWidget { | |||
USB_B_AudioWidget() { | |||
setSVG(SVG::load(assetGlobal("res/ComponentLibrary/USB-B.svg"))); | |||
} | |||
}; | |||
struct MIDI_DIN_MidiWidget : MidiWidget, SVGWidget { | |||
MIDI_DIN_MidiWidget() { | |||
setSVG(SVG::load(assetGlobal("res/ComponentLibrary/MIDI_DIN.svg"))); | |||
} | |||
}; | |||
//////////////////// | |||
// Jacks | |||
//////////////////// |
@@ -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,53 @@ namespace rack { | |||
template<int CHANNELS> | |||
struct SampleRateConverter { | |||
SRC_STATE *state; | |||
SRC_DATA data; | |||
SpeexResamplerState *state; | |||
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) { | |||
int oldInRate, oldOutRate; | |||
getRates(&oldInRate, &oldOutRate); | |||
if (inRate == oldInRate && outRate == oldOutRate) | |||
return; | |||
int error = speex_resampler_set_rate(state, inRate, outRate); | |||
assert(error == RESAMPLER_ERR_SUCCESS); | |||
} | |||
void getRates(int *inRate, int *outRate) { | |||
spx_uint32_t inRate32, outRate32; | |||
speex_resampler_get_rate(state, &inRate32, &outRate32); | |||
if (inRate) *inRate = inRate32; | |||
if (outRate) *outRate = outRate32; | |||
} | |||
/** `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); | |||
} | |||
}; | |||
@@ -16,7 +16,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); | |||
}; | |||
@@ -65,15 +65,29 @@ 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() { | |||
// Call deprecated method | |||
reset(); | |||
} | |||
/** Called when user clicks Randomize in the module context menu */ | |||
virtual void onRandomize() { | |||
// Call deprecated method | |||
randomize(); | |||
} | |||
/** Override these to store extra internal data in the "data" property */ | |||
virtual json_t *toJson() { return NULL; } | |||
virtual void fromJson(json_t *root) {} | |||
/** Deprecated */ | |||
virtual void reset() {} | |||
/** Deprecated */ | |||
virtual void randomize() {} | |||
}; | |||
struct Wire { | |||
@@ -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) { | |||
@@ -0,0 +1,62 @@ | |||
#pragma once | |||
#include "util.hpp" | |||
#include <queue> | |||
#include <vector> | |||
#include <jansson.h> | |||
#pragma GCC diagnostic push | |||
#pragma GCC diagnostic ignored "-Wsuggest-override" | |||
#include "rtmidi/RtMidi.h" | |||
#pragma GCC diagnostic pop | |||
namespace rack { | |||
struct MidiMessage { | |||
double time; | |||
std::vector<uint8_t> data; | |||
}; | |||
struct MidiIO { | |||
int port = -1; | |||
/* For MIDI output, the channel to output messages. | |||
For MIDI input, the channel to filter. | |||
Set to -1 to allow all MIDI channels (for input). | |||
Zero indexed. | |||
*/ | |||
int channel = -1; | |||
RtMidi *rtMidi = NULL; | |||
virtual ~MidiIO() {} | |||
int getPortCount(); | |||
std::string getPortName(int port); | |||
void openPort(int port); | |||
json_t *toJson(); | |||
void fromJson(json_t *rootJ); | |||
}; | |||
struct MidiInput : MidiIO { | |||
MidiInput(); | |||
~MidiInput(); | |||
virtual void onMessage(const MidiMessage &message) {} | |||
}; | |||
struct MidiInputQueue : MidiInput { | |||
std::queue<MidiMessage> messageQueue; | |||
void onMessage(const MidiMessage &message) override; | |||
}; | |||
struct MidiOutput : MidiIO { | |||
MidiOutput(); | |||
~MidiOutput(); | |||
}; | |||
} // namespace rack |
@@ -1,59 +1,12 @@ | |||
#pragma once | |||
#include <string> | |||
#include <list> | |||
#include "tags.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 +24,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) */ | |||
@@ -119,7 +73,6 @@ std::string pluginGetLoginStatus(); | |||
extern std::list<Plugin*> gPlugins; | |||
extern std::string gToken; | |||
extern std::string gTagNames[NUM_TAGS]; | |||
} // namespace rack | |||
@@ -7,7 +7,7 @@ | |||
#include "engine.hpp" | |||
#include "gui.hpp" | |||
#include "app.hpp" | |||
#include "components.hpp" | |||
#include "componentlibrary.hpp" | |||
namespace rack { | |||
@@ -36,15 +36,15 @@ Model *createModel(std::string manufacturer, std::string slug, std::string name, | |||
} | |||
template <class TScrew> | |||
Widget *createScrew(Vec pos) { | |||
Widget *screw = new TScrew(); | |||
TScrew *createScrew(Vec pos) { | |||
TScrew *screw = new TScrew(); | |||
screw->box.pos = pos; | |||
return screw; | |||
} | |||
template <class TParamWidget> | |||
ParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||
ParamWidget *param = new TParamWidget(); | |||
TParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, float maxValue, float defaultValue) { | |||
TParamWidget *param = new TParamWidget(); | |||
param->box.pos = pos; | |||
param->module = module; | |||
param->paramId = paramId; | |||
@@ -54,8 +54,8 @@ ParamWidget *createParam(Vec pos, Module *module, int paramId, float minValue, f | |||
} | |||
template <class TPort> | |||
Port *createInput(Vec pos, Module *module, int inputId) { | |||
Port *port = new TPort(); | |||
TPort *createInput(Vec pos, Module *module, int inputId) { | |||
TPort *port = new TPort(); | |||
port->box.pos = pos; | |||
port->module = module; | |||
port->type = Port::INPUT; | |||
@@ -64,8 +64,8 @@ Port *createInput(Vec pos, Module *module, int inputId) { | |||
} | |||
template <class TPort> | |||
Port *createOutput(Vec pos, Module *module, int outputId) { | |||
Port *port = new TPort(); | |||
TPort *createOutput(Vec pos, Module *module, int outputId) { | |||
TPort *port = new TPort(); | |||
port->box.pos = pos; | |||
port->module = module; | |||
port->type = Port::OUTPUT; | |||
@@ -74,8 +74,8 @@ Port *createOutput(Vec pos, Module *module, int outputId) { | |||
} | |||
template<class TModuleLightWidget> | |||
ModuleLightWidget *createLight(Vec pos, Module *module, int firstLightId) { | |||
ModuleLightWidget *light = new TModuleLightWidget(); | |||
TModuleLightWidget *createLight(Vec pos, Module *module, int firstLightId) { | |||
TModuleLightWidget *light = new TModuleLightWidget(); | |||
light->box.pos = pos; | |||
light->module = module; | |||
light->firstLightId = firstLightId; | |||
@@ -10,4 +10,7 @@ void settingsSave(std::string filename); | |||
void settingsLoad(std::string filename); | |||
extern bool skipAutosaveOnLaunch; | |||
} // namespace rack |
@@ -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 |
@@ -83,11 +83,12 @@ float randomNormal(); | |||
/** Converts a printf format string and optional arguments into a std::string */ | |||
std::string stringf(const char *format, ...); | |||
std::string tolower(std::string s); | |||
std::string toupper(std::string s); | |||
std::string lowercase(std::string s); | |||
std::string uppercase(std::string s); | |||
/** Truncates and adds "..." to a string, not exceeding `len` characters */ | |||
std::string ellipsize(std::string s, size_t len); | |||
bool startsWith(std::string str, std::string prefix); | |||
std::string extractDirectory(std::string path); | |||
std::string extractFilename(std::string path); | |||
@@ -11,21 +11,8 @@ | |||
#include "events.hpp" | |||
#define SVG_DPI 75.0 | |||
namespace rack { | |||
inline Vec in2px(Vec inches) { | |||
return inches.mult(SVG_DPI); | |||
} | |||
inline Vec mm2px(Vec millimeters) { | |||
return millimeters.mult(SVG_DPI / 25.4); | |||
} | |||
//////////////////// | |||
// resources | |||
//////////////////// | |||
@@ -178,6 +165,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 +283,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; | |||
}; | |||
@@ -25,6 +25,8 @@ ifeq ($(ARCH), win) | |||
TARGET = plugin.dll | |||
endif | |||
DISTRIBUTABLES += $(TARGET) | |||
DISTRIBUTABLES += $(TARGET) | |||
@@ -0,0 +1,366 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="10.415202mm" | |||
height="10.667406mm" | |||
viewBox="0 0 10.415202 10.667406" | |||
version="1.1" | |||
id="svg8104" | |||
inkscape:version="0.92.2 5c3e80d, 2017-08-06" | |||
sodipodi:docname="ADAT.svg"> | |||
<defs | |||
id="defs8098"> | |||
<clipPath | |||
id="clip306"> | |||
<path | |||
d="m 1550,2365 h 44 v 44 h -44 z m 0,0" | |||
id="path6964" | |||
inkscape:connector-curvature="0" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip307"> | |||
<path | |||
d="m 1550.0859,2386.6914 c 0,11.8867 9.6368,21.5274 21.5274,21.5274 11.8906,0 21.5273,-9.6407 21.5273,-21.5274 0,-11.8906 -9.6367,-21.5312 -21.5273,-21.5312 -11.8906,0 -21.5274,9.6406 -21.5274,21.5312" | |||
id="path6967" | |||
inkscape:connector-curvature="0" /> | |||
</clipPath> | |||
<linearGradient | |||
id="linear3" | |||
gradientUnits="userSpaceOnUse" | |||
x1="-0.0115906" | |||
y1="0" | |||
x2="0.985816" | |||
y2="0" | |||
gradientTransform="matrix(0,43.168976,-43.168976,0,1571.6138,2365.6624)"> | |||
<stop | |||
offset="0" | |||
style="stop-color:rgb(32.940674%,32.940674%,33.724976%);stop-opacity:1;" | |||
id="stop6970" /> | |||
<stop | |||
offset="0.03125" | |||
style="stop-color:rgb(32.814026%,32.806396%,33.586121%);stop-opacity:1;" | |||
id="stop6972" /> | |||
<stop | |||
offset="0.046875" | |||
style="stop-color:rgb(32.58667%,32.565308%,33.338928%);stop-opacity:1;" | |||
id="stop6974" /> | |||
<stop | |||
offset="0.0625" | |||
style="stop-color:rgb(32.383728%,32.350159%,33.117676%);stop-opacity:1;" | |||
id="stop6976" /> | |||
<stop | |||
offset="0.078125" | |||
style="stop-color:rgb(32.182312%,32.136536%,32.897949%);stop-opacity:1;" | |||
id="stop6978" /> | |||
<stop | |||
offset="0.09375" | |||
style="stop-color:rgb(31.980896%,31.922913%,32.678223%);stop-opacity:1;" | |||
id="stop6980" /> | |||
<stop | |||
offset="0.109375" | |||
style="stop-color:rgb(31.77948%,31.70929%,32.458496%);stop-opacity:1;" | |||
id="stop6982" /> | |||
<stop | |||
offset="0.125" | |||
style="stop-color:rgb(31.578064%,31.495667%,32.23877%);stop-opacity:1;" | |||
id="stop6984" /> | |||
<stop | |||
offset="0.140625" | |||
style="stop-color:rgb(31.376648%,31.280518%,32.017517%);stop-opacity:1;" | |||
id="stop6986" /> | |||
<stop | |||
offset="0.15625" | |||
style="stop-color:rgb(31.173706%,31.066895%,31.797791%);stop-opacity:1;" | |||
id="stop6988" /> | |||
<stop | |||
offset="0.171875" | |||
style="stop-color:rgb(30.97229%,30.853271%,31.578064%);stop-opacity:1;" | |||
id="stop6990" /> | |||
<stop | |||
offset="0.1875" | |||
style="stop-color:rgb(30.770874%,30.639648%,31.358337%);stop-opacity:1;" | |||
id="stop6992" /> | |||
<stop | |||
offset="0.203125" | |||
style="stop-color:rgb(30.569458%,30.426025%,31.138611%);stop-opacity:1;" | |||
id="stop6994" /> | |||
<stop | |||
offset="0.21875" | |||
style="stop-color:rgb(30.368042%,30.212402%,30.917358%);stop-opacity:1;" | |||
id="stop6996" /> | |||
<stop | |||
offset="0.234375" | |||
style="stop-color:rgb(30.166626%,29.997253%,30.697632%);stop-opacity:1;" | |||
id="stop6998" /> | |||
<stop | |||
offset="0.25" | |||
style="stop-color:rgb(29.963684%,29.78363%,30.477905%);stop-opacity:1;" | |||
id="stop7000" /> | |||
<stop | |||
offset="0.265625" | |||
style="stop-color:rgb(29.762268%,29.570007%,30.258179%);stop-opacity:1;" | |||
id="stop7002" /> | |||
<stop | |||
offset="0.28125" | |||
style="stop-color:rgb(29.560852%,29.356384%,30.038452%);stop-opacity:1;" | |||
id="stop7004" /> | |||
<stop | |||
offset="0.296875" | |||
style="stop-color:rgb(29.359436%,29.142761%,29.8172%);stop-opacity:1;" | |||
id="stop7006" /> | |||
<stop | |||
offset="0.3125" | |||
style="stop-color:rgb(29.15802%,28.927612%,29.597473%);stop-opacity:1;" | |||
id="stop7008" /> | |||
<stop | |||
offset="0.328125" | |||
style="stop-color:rgb(28.955078%,28.713989%,29.377747%);stop-opacity:1;" | |||
id="stop7010" /> | |||
<stop | |||
offset="0.34375" | |||
style="stop-color:rgb(28.753662%,28.500366%,29.15802%);stop-opacity:1;" | |||
id="stop7012" /> | |||
<stop | |||
offset="0.359375" | |||
style="stop-color:rgb(28.552246%,28.286743%,28.938293%);stop-opacity:1;" | |||
id="stop7014" /> | |||
<stop | |||
offset="0.375" | |||
style="stop-color:rgb(28.35083%,28.07312%,28.718567%);stop-opacity:1;" | |||
id="stop7016" /> | |||
<stop | |||
offset="0.390625" | |||
style="stop-color:rgb(28.149414%,27.857971%,28.497314%);stop-opacity:1;" | |||
id="stop7018" /> | |||
<stop | |||
offset="0.40625" | |||
style="stop-color:rgb(27.947998%,27.644348%,28.277588%);stop-opacity:1;" | |||
id="stop7020" /> | |||
<stop | |||
offset="0.421875" | |||
style="stop-color:rgb(27.745056%,27.430725%,28.057861%);stop-opacity:1;" | |||
id="stop7022" /> | |||
<stop | |||
offset="0.4375" | |||
style="stop-color:rgb(27.54364%,27.217102%,27.838135%);stop-opacity:1;" | |||
id="stop7024" /> | |||
<stop | |||
offset="0.453125" | |||
style="stop-color:rgb(27.342224%,27.003479%,27.618408%);stop-opacity:1;" | |||
id="stop7026" /> | |||
<stop | |||
offset="0.46875" | |||
style="stop-color:rgb(27.140808%,26.789856%,27.397156%);stop-opacity:1;" | |||
id="stop7028" /> | |||
<stop | |||
offset="0.484375" | |||
style="stop-color:rgb(26.939392%,26.574707%,27.177429%);stop-opacity:1;" | |||
id="stop7030" /> | |||
<stop | |||
offset="0.5" | |||
style="stop-color:rgb(26.737976%,26.361084%,26.957703%);stop-opacity:1;" | |||
id="stop7032" /> | |||
<stop | |||
offset="0.515625" | |||
style="stop-color:rgb(26.535034%,26.147461%,26.737976%);stop-opacity:1;" | |||
id="stop7034" /> | |||
<stop | |||
offset="0.53125" | |||
style="stop-color:rgb(26.333618%,25.933838%,26.51825%);stop-opacity:1;" | |||
id="stop7036" /> | |||
<stop | |||
offset="0.546875" | |||
style="stop-color:rgb(26.132202%,25.720215%,26.296997%);stop-opacity:1;" | |||
id="stop7038" /> | |||
<stop | |||
offset="0.5625" | |||
style="stop-color:rgb(25.930786%,25.505066%,26.077271%);stop-opacity:1;" | |||
id="stop7040" /> | |||
<stop | |||
offset="0.578125" | |||
style="stop-color:rgb(25.72937%,25.291443%,25.857544%);stop-opacity:1;" | |||
id="stop7042" /> | |||
<stop | |||
offset="0.59375" | |||
style="stop-color:rgb(25.527954%,25.07782%,25.637817%);stop-opacity:1;" | |||
id="stop7044" /> | |||
<stop | |||
offset="0.609375" | |||
style="stop-color:rgb(25.325012%,24.864197%,25.418091%);stop-opacity:1;" | |||
id="stop7046" /> | |||
<stop | |||
offset="0.625" | |||
style="stop-color:rgb(25.123596%,24.650574%,25.196838%);stop-opacity:1;" | |||
id="stop7048" /> | |||
<stop | |||
offset="0.640625" | |||
style="stop-color:rgb(24.92218%,24.436951%,24.977112%);stop-opacity:1;" | |||
id="stop7050" /> | |||
<stop | |||
offset="0.65625" | |||
style="stop-color:rgb(24.720764%,24.221802%,24.757385%);stop-opacity:1;" | |||
id="stop7052" /> | |||
<stop | |||
offset="0.671875" | |||
style="stop-color:rgb(24.519348%,24.008179%,24.537659%);stop-opacity:1;" | |||
id="stop7054" /> | |||
<stop | |||
offset="0.6875" | |||
style="stop-color:rgb(24.316406%,23.794556%,24.317932%);stop-opacity:1;" | |||
id="stop7056" /> | |||
<stop | |||
offset="0.703125" | |||
style="stop-color:rgb(24.11499%,23.580933%,24.098206%);stop-opacity:1;" | |||
id="stop7058" /> | |||
<stop | |||
offset="0.71875" | |||
style="stop-color:rgb(23.913574%,23.36731%,23.876953%);stop-opacity:1;" | |||
id="stop7060" /> | |||
<stop | |||
offset="0.734375" | |||
style="stop-color:rgb(23.712158%,23.152161%,23.657227%);stop-opacity:1;" | |||
id="stop7062" /> | |||
<stop | |||
offset="0.75" | |||
style="stop-color:rgb(23.510742%,22.938538%,23.4375%);stop-opacity:1;" | |||
id="stop7064" /> | |||
<stop | |||
offset="0.765625" | |||
style="stop-color:rgb(23.309326%,22.724915%,23.217773%);stop-opacity:1;" | |||
id="stop7066" /> | |||
<stop | |||
offset="0.78125" | |||
style="stop-color:rgb(23.106384%,22.511292%,22.998047%);stop-opacity:1;" | |||
id="stop7068" /> | |||
<stop | |||
offset="0.796875" | |||
style="stop-color:rgb(22.904968%,22.297668%,22.776794%);stop-opacity:1;" | |||
id="stop7070" /> | |||
<stop | |||
offset="0.8125" | |||
style="stop-color:rgb(22.703552%,22.084045%,22.557068%);stop-opacity:1;" | |||
id="stop7072" /> | |||
<stop | |||
offset="0.828125" | |||
style="stop-color:rgb(22.502136%,21.868896%,22.337341%);stop-opacity:1;" | |||
id="stop7074" /> | |||
<stop | |||
offset="0.84375" | |||
style="stop-color:rgb(22.30072%,21.655273%,22.117615%);stop-opacity:1;" | |||
id="stop7076" /> | |||
<stop | |||
offset="0.859375" | |||
style="stop-color:rgb(22.099304%,21.44165%,21.897888%);stop-opacity:1;" | |||
id="stop7078" /> | |||
<stop | |||
offset="0.875" | |||
style="stop-color:rgb(21.896362%,21.228027%,21.676636%);stop-opacity:1;" | |||
id="stop7080" /> | |||
<stop | |||
offset="0.890625" | |||
style="stop-color:rgb(21.694946%,21.014404%,21.456909%);stop-opacity:1;" | |||
id="stop7082" /> | |||
<stop | |||
offset="0.90625" | |||
style="stop-color:rgb(21.49353%,20.799255%,21.237183%);stop-opacity:1;" | |||
id="stop7084" /> | |||
<stop | |||
offset="0.921875" | |||
style="stop-color:rgb(21.292114%,20.585632%,21.017456%);stop-opacity:1;" | |||
id="stop7086" /> | |||
<stop | |||
offset="0.9375" | |||
style="stop-color:rgb(21.090698%,20.372009%,20.797729%);stop-opacity:1;" | |||
id="stop7088" /> | |||
<stop | |||
offset="0.953125" | |||
style="stop-color:rgb(20.889282%,20.158386%,20.576477%);stop-opacity:1;" | |||
id="stop7090" /> | |||
<stop | |||
offset="0.96875" | |||
style="stop-color:rgb(20.68634%,19.944763%,20.35675%);stop-opacity:1;" | |||
id="stop7092" /> | |||
<stop | |||
offset="0.984375" | |||
style="stop-color:rgb(20.484924%,19.729614%,20.137024%);stop-opacity:1;" | |||
id="stop7094" /> | |||
<stop | |||
offset="1" | |||
style="stop-color:rgb(20.283508%,19.515991%,19.917297%);stop-opacity:1;" | |||
id="stop7096" /> | |||
</linearGradient> | |||
</defs> | |||
<sodipodi:namedview | |||
id="base" | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1.0" | |||
inkscape:pageopacity="0.0" | |||
inkscape:pageshadow="2" | |||
inkscape:zoom="1.979899" | |||
inkscape:cx="171.96332" | |||
inkscape:cy="-12.111977" | |||
inkscape:document-units="mm" | |||
inkscape:current-layer="layer1" | |||
showgrid="false" | |||
fit-margin-top="0" | |||
fit-margin-left="0" | |||
fit-margin-right="0" | |||
fit-margin-bottom="0" | |||
inkscape:window-width="1600" | |||
inkscape:window-height="882" | |||
inkscape:window-x="0" | |||
inkscape:window-y="18" | |||
inkscape:window-maximized="0" /> | |||
<metadata | |||
id="metadata8101"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
<dc:title></dc:title> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<g | |||
inkscape:label="Layer 1" | |||
inkscape:groupmode="layer" | |||
id="layer1" | |||
transform="translate(-98.481578,-96.351604)"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10049" | |||
d="m 108.89678,106.51189 c 0,0.27838 -0.22874,0.50712 -0.50849,0.50712 h -9.398222 c -0.27837,0 -0.50849,-0.22874 -0.50849,-0.50712 v -9.398215 c 0,-0.279753 0.23012,-0.507119 0.50849,-0.507119 h 9.398222 c 0.27975,0 0.50849,0.227366 0.50849,0.507119 z m 0,0" | |||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10051" | |||
d="m 108.89678,106.25831 c 0,0.27838 -0.22874,0.50712 -0.50849,0.50712 h -9.398222 c -0.27837,0 -0.50849,-0.22874 -0.50849,-0.50712 v -9.398212 c 0,-0.279718 0.23012,-0.508494 0.50849,-0.508494 h 9.398222 c 0.27975,0 0.50849,0.228776 0.50849,0.508494 z m 0,0" | |||
style="fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10053" | |||
d="m 106.72501,98.389742 h -3.61735 l -0.55813,0.617361 h -0.82956 l -1.06662,1.014237 v 3.07164 l 1.06662,1.06521 h 0.82956 l 0.55813,0.57051 h 3.61735 z m 0,0" | |||
style="fill:#211e1e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10055" | |||
d="m 103.00155,104.98228 -0.5581,-0.56914 h -0.82959 l -1.21405,-1.21542 v -3.285248 l 1.21818,-1.158945 h 0.81855 l 0.5595,-0.617361 h 3.98251 v 6.846114 z m 0,0" | |||
style="fill:#211e1e;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10057" | |||
d="m 106.72501,98.389742 h -3.61735 l -0.55813,0.617361 h -0.82956 l -1.06662,1.014237 v 3.07164 l 1.06662,1.06521 h 0.82956 l 0.55813,0.57051 h 3.61735 z m 0,0" | |||
style="fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,397 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:xlink="http://www.w3.org/1999/xlink" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="16.999479mm" | |||
height="17.000858mm" | |||
viewBox="0 0 16.999479 17.000858" | |||
version="1.1" | |||
id="svg8104" | |||
inkscape:version="0.92.2 5c3e80d, 2017-08-06" | |||
sodipodi:docname="MIDI_DIN.svg"> | |||
<defs | |||
id="defs8098"> | |||
<linearGradient | |||
inkscape:collect="always" | |||
id="linearGradient1258"> | |||
<stop | |||
style="stop-color:#545456;stop-opacity:1" | |||
offset="0" | |||
id="stop1254" /> | |||
<stop | |||
style="stop-color:#333132;stop-opacity:1" | |||
offset="1" | |||
id="stop1256" /> | |||
</linearGradient> | |||
<clipPath | |||
id="clip306"> | |||
<path | |||
d="m 1550,2365 h 44 v 44 h -44 z m 0,0" | |||
id="path6964" | |||
inkscape:connector-curvature="0" /> | |||
</clipPath> | |||
<linearGradient | |||
id="linear3" | |||
gradientUnits="userSpaceOnUse" | |||
x1="-0.0115906" | |||
y1="0" | |||
x2="0.985816" | |||
y2="0" | |||
gradientTransform="matrix(0,43.168976,-43.168976,0,1571.6138,2365.6624)"> | |||
<stop | |||
offset="0" | |||
style="stop-color:rgb(32.940674%,32.940674%,33.724976%);stop-opacity:1;" | |||
id="stop6970" /> | |||
<stop | |||
offset="0.03125" | |||
style="stop-color:rgb(32.814026%,32.806396%,33.586121%);stop-opacity:1;" | |||
id="stop6972" /> | |||
<stop | |||
offset="0.046875" | |||
style="stop-color:rgb(32.58667%,32.565308%,33.338928%);stop-opacity:1;" | |||
id="stop6974" /> | |||
<stop | |||
offset="0.0625" | |||
style="stop-color:rgb(32.383728%,32.350159%,33.117676%);stop-opacity:1;" | |||
id="stop6976" /> | |||
<stop | |||
offset="0.078125" | |||
style="stop-color:rgb(32.182312%,32.136536%,32.897949%);stop-opacity:1;" | |||
id="stop6978" /> | |||
<stop | |||
offset="0.09375" | |||
style="stop-color:rgb(31.980896%,31.922913%,32.678223%);stop-opacity:1;" | |||
id="stop6980" /> | |||
<stop | |||
offset="0.109375" | |||
style="stop-color:rgb(31.77948%,31.70929%,32.458496%);stop-opacity:1;" | |||
id="stop6982" /> | |||
<stop | |||
offset="0.125" | |||
style="stop-color:rgb(31.578064%,31.495667%,32.23877%);stop-opacity:1;" | |||
id="stop6984" /> | |||
<stop | |||
offset="0.140625" | |||
style="stop-color:rgb(31.376648%,31.280518%,32.017517%);stop-opacity:1;" | |||
id="stop6986" /> | |||
<stop | |||
offset="0.15625" | |||
style="stop-color:rgb(31.173706%,31.066895%,31.797791%);stop-opacity:1;" | |||
id="stop6988" /> | |||
<stop | |||
offset="0.171875" | |||
style="stop-color:rgb(30.97229%,30.853271%,31.578064%);stop-opacity:1;" | |||
id="stop6990" /> | |||
<stop | |||
offset="0.1875" | |||
style="stop-color:rgb(30.770874%,30.639648%,31.358337%);stop-opacity:1;" | |||
id="stop6992" /> | |||
<stop | |||
offset="0.203125" | |||
style="stop-color:rgb(30.569458%,30.426025%,31.138611%);stop-opacity:1;" | |||
id="stop6994" /> | |||
<stop | |||
offset="0.21875" | |||
style="stop-color:rgb(30.368042%,30.212402%,30.917358%);stop-opacity:1;" | |||
id="stop6996" /> | |||
<stop | |||
offset="0.234375" | |||
style="stop-color:rgb(30.166626%,29.997253%,30.697632%);stop-opacity:1;" | |||
id="stop6998" /> | |||
<stop | |||
offset="0.25" | |||
style="stop-color:rgb(29.963684%,29.78363%,30.477905%);stop-opacity:1;" | |||
id="stop7000" /> | |||
<stop | |||
offset="0.265625" | |||
style="stop-color:rgb(29.762268%,29.570007%,30.258179%);stop-opacity:1;" | |||
id="stop7002" /> | |||
<stop | |||
offset="0.28125" | |||
style="stop-color:rgb(29.560852%,29.356384%,30.038452%);stop-opacity:1;" | |||
id="stop7004" /> | |||
<stop | |||
offset="0.296875" | |||
style="stop-color:rgb(29.359436%,29.142761%,29.8172%);stop-opacity:1;" | |||
id="stop7006" /> | |||
<stop | |||
offset="0.3125" | |||
style="stop-color:rgb(29.15802%,28.927612%,29.597473%);stop-opacity:1;" | |||
id="stop7008" /> | |||
<stop | |||
offset="0.328125" | |||
style="stop-color:rgb(28.955078%,28.713989%,29.377747%);stop-opacity:1;" | |||
id="stop7010" /> | |||
<stop | |||
offset="0.34375" | |||
style="stop-color:rgb(28.753662%,28.500366%,29.15802%);stop-opacity:1;" | |||
id="stop7012" /> | |||
<stop | |||
offset="0.359375" | |||
style="stop-color:rgb(28.552246%,28.286743%,28.938293%);stop-opacity:1;" | |||
id="stop7014" /> | |||
<stop | |||
offset="0.375" | |||
style="stop-color:rgb(28.35083%,28.07312%,28.718567%);stop-opacity:1;" | |||
id="stop7016" /> | |||
<stop | |||
offset="0.390625" | |||
style="stop-color:rgb(28.149414%,27.857971%,28.497314%);stop-opacity:1;" | |||
id="stop7018" /> | |||
<stop | |||
offset="0.40625" | |||
style="stop-color:rgb(27.947998%,27.644348%,28.277588%);stop-opacity:1;" | |||
id="stop7020" /> | |||
<stop | |||
offset="0.421875" | |||
style="stop-color:rgb(27.745056%,27.430725%,28.057861%);stop-opacity:1;" | |||
id="stop7022" /> | |||
<stop | |||
offset="0.4375" | |||
style="stop-color:rgb(27.54364%,27.217102%,27.838135%);stop-opacity:1;" | |||
id="stop7024" /> | |||
<stop | |||
offset="0.453125" | |||
style="stop-color:rgb(27.342224%,27.003479%,27.618408%);stop-opacity:1;" | |||
id="stop7026" /> | |||
<stop | |||
offset="0.46875" | |||
style="stop-color:rgb(27.140808%,26.789856%,27.397156%);stop-opacity:1;" | |||
id="stop7028" /> | |||
<stop | |||
offset="0.484375" | |||
style="stop-color:rgb(26.939392%,26.574707%,27.177429%);stop-opacity:1;" | |||
id="stop7030" /> | |||
<stop | |||
offset="0.5" | |||
style="stop-color:rgb(26.737976%,26.361084%,26.957703%);stop-opacity:1;" | |||
id="stop7032" /> | |||
<stop | |||
offset="0.515625" | |||
style="stop-color:rgb(26.535034%,26.147461%,26.737976%);stop-opacity:1;" | |||
id="stop7034" /> | |||
<stop | |||
offset="0.53125" | |||
style="stop-color:rgb(26.333618%,25.933838%,26.51825%);stop-opacity:1;" | |||
id="stop7036" /> | |||
<stop | |||
offset="0.546875" | |||
style="stop-color:rgb(26.132202%,25.720215%,26.296997%);stop-opacity:1;" | |||
id="stop7038" /> | |||
<stop | |||
offset="0.5625" | |||
style="stop-color:rgb(25.930786%,25.505066%,26.077271%);stop-opacity:1;" | |||
id="stop7040" /> | |||
<stop | |||
offset="0.578125" | |||
style="stop-color:rgb(25.72937%,25.291443%,25.857544%);stop-opacity:1;" | |||
id="stop7042" /> | |||
<stop | |||
offset="0.59375" | |||
style="stop-color:rgb(25.527954%,25.07782%,25.637817%);stop-opacity:1;" | |||
id="stop7044" /> | |||
<stop | |||
offset="0.609375" | |||
style="stop-color:rgb(25.325012%,24.864197%,25.418091%);stop-opacity:1;" | |||
id="stop7046" /> | |||
<stop | |||
offset="0.625" | |||
style="stop-color:rgb(25.123596%,24.650574%,25.196838%);stop-opacity:1;" | |||
id="stop7048" /> | |||
<stop | |||
offset="0.640625" | |||
style="stop-color:rgb(24.92218%,24.436951%,24.977112%);stop-opacity:1;" | |||
id="stop7050" /> | |||
<stop | |||
offset="0.65625" | |||
style="stop-color:rgb(24.720764%,24.221802%,24.757385%);stop-opacity:1;" | |||
id="stop7052" /> | |||
<stop | |||
offset="0.671875" | |||
style="stop-color:rgb(24.519348%,24.008179%,24.537659%);stop-opacity:1;" | |||
id="stop7054" /> | |||
<stop | |||
offset="0.6875" | |||
style="stop-color:rgb(24.316406%,23.794556%,24.317932%);stop-opacity:1;" | |||
id="stop7056" /> | |||
<stop | |||
offset="0.703125" | |||
style="stop-color:rgb(24.11499%,23.580933%,24.098206%);stop-opacity:1;" | |||
id="stop7058" /> | |||
<stop | |||
offset="0.71875" | |||
style="stop-color:rgb(23.913574%,23.36731%,23.876953%);stop-opacity:1;" | |||
id="stop7060" /> | |||
<stop | |||
offset="0.734375" | |||
style="stop-color:rgb(23.712158%,23.152161%,23.657227%);stop-opacity:1;" | |||
id="stop7062" /> | |||
<stop | |||
offset="0.75" | |||
style="stop-color:rgb(23.510742%,22.938538%,23.4375%);stop-opacity:1;" | |||
id="stop7064" /> | |||
<stop | |||
offset="0.765625" | |||
style="stop-color:rgb(23.309326%,22.724915%,23.217773%);stop-opacity:1;" | |||
id="stop7066" /> | |||
<stop | |||
offset="0.78125" | |||
style="stop-color:rgb(23.106384%,22.511292%,22.998047%);stop-opacity:1;" | |||
id="stop7068" /> | |||
<stop | |||
offset="0.796875" | |||
style="stop-color:rgb(22.904968%,22.297668%,22.776794%);stop-opacity:1;" | |||
id="stop7070" /> | |||
<stop | |||
offset="0.8125" | |||
style="stop-color:rgb(22.703552%,22.084045%,22.557068%);stop-opacity:1;" | |||
id="stop7072" /> | |||
<stop | |||
offset="0.828125" | |||
style="stop-color:rgb(22.502136%,21.868896%,22.337341%);stop-opacity:1;" | |||
id="stop7074" /> | |||
<stop | |||
offset="0.84375" | |||
style="stop-color:rgb(22.30072%,21.655273%,22.117615%);stop-opacity:1;" | |||
id="stop7076" /> | |||
<stop | |||
offset="0.859375" | |||
style="stop-color:rgb(22.099304%,21.44165%,21.897888%);stop-opacity:1;" | |||
id="stop7078" /> | |||
<stop | |||
offset="0.875" | |||
style="stop-color:rgb(21.896362%,21.228027%,21.676636%);stop-opacity:1;" | |||
id="stop7080" /> | |||
<stop | |||
offset="0.890625" | |||
style="stop-color:rgb(21.694946%,21.014404%,21.456909%);stop-opacity:1;" | |||
id="stop7082" /> | |||
<stop | |||
offset="0.90625" | |||
style="stop-color:rgb(21.49353%,20.799255%,21.237183%);stop-opacity:1;" | |||
id="stop7084" /> | |||
<stop | |||
offset="0.921875" | |||
style="stop-color:rgb(21.292114%,20.585632%,21.017456%);stop-opacity:1;" | |||
id="stop7086" /> | |||
<stop | |||
offset="0.9375" | |||
style="stop-color:rgb(21.090698%,20.372009%,20.797729%);stop-opacity:1;" | |||
id="stop7088" /> | |||
<stop | |||
offset="0.953125" | |||
style="stop-color:rgb(20.889282%,20.158386%,20.576477%);stop-opacity:1;" | |||
id="stop7090" /> | |||
<stop | |||
offset="0.96875" | |||
style="stop-color:rgb(20.68634%,19.944763%,20.35675%);stop-opacity:1;" | |||
id="stop7092" /> | |||
<stop | |||
offset="0.984375" | |||
style="stop-color:rgb(20.484924%,19.729614%,20.137024%);stop-opacity:1;" | |||
id="stop7094" /> | |||
<stop | |||
offset="1" | |||
style="stop-color:rgb(20.283508%,19.515991%,19.917297%);stop-opacity:1;" | |||
id="stop7096" /> | |||
</linearGradient> | |||
<linearGradient | |||
inkscape:collect="always" | |||
xlink:href="#linearGradient1258" | |||
id="linearGradient1260" | |||
x1="97.483007" | |||
y1="102.72024" | |||
x2="112.67175" | |||
y2="102.72024" | |||
gradientUnits="userSpaceOnUse" | |||
gradientTransform="rotate(90,105.07738,102.72024)" /> | |||
</defs> | |||
<sodipodi:namedview | |||
id="base" | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1.0" | |||
inkscape:pageopacity="0.0" | |||
inkscape:pageshadow="2" | |||
inkscape:zoom="5.6" | |||
inkscape:cx="27.755372" | |||
inkscape:cy="6.7266123" | |||
inkscape:document-units="mm" | |||
inkscape:current-layer="layer1" | |||
showgrid="false" | |||
fit-margin-top="0" | |||
fit-margin-left="0" | |||
fit-margin-right="0" | |||
fit-margin-bottom="0" | |||
inkscape:window-width="1600" | |||
inkscape:window-height="900" | |||
inkscape:window-x="0" | |||
inkscape:window-y="0" | |||
inkscape:window-maximized="0" /> | |||
<metadata | |||
id="metadata8101"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
<dc:title /> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<g | |||
inkscape:label="Layer 1" | |||
inkscape:groupmode="layer" | |||
id="layer1" | |||
transform="translate(-96.577641,-94.219811)"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10029" | |||
d="m 96.577641,102.72091 c 0,4.6936 3.804779,8.49976 8.499759,8.49976 4.69498,0 8.49972,-3.80616 8.49972,-8.49976 0,-4.69498 -3.80474,-8.501099 -8.49972,-8.501099 -4.69498,0 -8.499759,3.806119 -8.499759,8.501099" | |||
style="fill:#050707;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path6967" | |||
d="m 105.07671,95.125869 c -4.19336,0 -7.594391,3.399651 -7.594391,7.594391 0,4.19473 3.401031,7.59435 7.594391,7.59435 4.19474,0 7.59573,-3.39962 7.59573,-7.59435 0,-4.19474 -3.40099,-7.594391 -7.59573,-7.594391" | |||
style="fill:url(#linearGradient1260);fill-opacity:1;stroke-width:0.35277778" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10037" | |||
d="m 106.17016,98.225744 c 0,0.657331 -0.53192,1.189249 -1.18925,1.189249 -0.65592,0 -1.18921,-0.531918 -1.18921,-1.189249 0,-0.657331 0.53329,-1.189249 1.18921,-1.189249 0.65733,0 1.18925,0.531918 1.18925,1.189249" | |||
style="fill:#050707;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10039" | |||
d="m 102.83394,99.691994 c 0,0.657326 -0.53191,1.189246 -1.18925,1.189246 -0.65595,0 -1.18924,-0.53192 -1.18924,-1.189246 0,-0.655955 0.53329,-1.189249 1.18924,-1.189249 0.65734,0 1.18925,0.533294 1.18925,1.189249" | |||
style="fill:#050707;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10041" | |||
d="m 109.33416,99.691994 c 0,0.657326 -0.53192,1.189246 -1.18925,1.189246 -0.65733,0 -1.18925,-0.53192 -1.18925,-1.189246 0,-0.655955 0.53192,-1.189249 1.18925,-1.189249 0.65733,0 1.18925,0.533294 1.18925,1.189249" | |||
style="fill:#050707;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10043" | |||
d="m 110.54133,102.72641 c 0,0.65596 -0.53192,1.18925 -1.18925,1.18925 -0.65596,0 -1.18925,-0.53329 -1.18925,-1.18925 0,-0.65729 0.53329,-1.18925 1.18925,-1.18925 0.65733,0 1.18925,0.53196 1.18925,1.18925" | |||
style="fill:#050707;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10045" | |||
d="m 101.79903,102.72641 c 0,0.65596 -0.53191,1.18925 -1.18924,1.18925 -0.655959,0 -1.189254,-0.53329 -1.189254,-1.18925 0,-0.65729 0.533295,-1.18925 1.189254,-1.18925 0.65733,0 1.18924,0.53196 1.18924,1.18925" | |||
style="fill:#050707;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10047" | |||
d="m 106.54778,109.2473 h -2.94076 v 1.28294 h 2.94076 z m 0,0" | |||
style="fill:#050707;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
</g> | |||
</svg> |
@@ -0,0 +1,376 @@ | |||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> | |||
<!-- Created with Inkscape (http://www.inkscape.org/) --> | |||
<svg | |||
xmlns:dc="http://purl.org/dc/elements/1.1/" | |||
xmlns:cc="http://creativecommons.org/ns#" | |||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | |||
xmlns:svg="http://www.w3.org/2000/svg" | |||
xmlns="http://www.w3.org/2000/svg" | |||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" | |||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" | |||
width="10.90027mm" | |||
height="12.199803mm" | |||
viewBox="0 0 10.90027 12.199803" | |||
version="1.1" | |||
id="svg8104" | |||
inkscape:version="0.92.2 5c3e80d, 2017-08-06" | |||
sodipodi:docname="USB-B.svg"> | |||
<defs | |||
id="defs8098"> | |||
<clipPath | |||
id="clip306"> | |||
<path | |||
d="m 1550,2365 h 44 v 44 h -44 z m 0,0" | |||
id="path6964" | |||
inkscape:connector-curvature="0" /> | |||
</clipPath> | |||
<clipPath | |||
id="clip307"> | |||
<path | |||
d="m 1550.0859,2386.6914 c 0,11.8867 9.6368,21.5274 21.5274,21.5274 11.8906,0 21.5273,-9.6407 21.5273,-21.5274 0,-11.8906 -9.6367,-21.5312 -21.5273,-21.5312 -11.8906,0 -21.5274,9.6406 -21.5274,21.5312" | |||
id="path6967" | |||
inkscape:connector-curvature="0" /> | |||
</clipPath> | |||
<linearGradient | |||
id="linear3" | |||
gradientUnits="userSpaceOnUse" | |||
x1="-0.0115906" | |||
y1="0" | |||
x2="0.985816" | |||
y2="0" | |||
gradientTransform="matrix(0,43.168976,-43.168976,0,1571.6138,2365.6624)"> | |||
<stop | |||
offset="0" | |||
style="stop-color:rgb(32.940674%,32.940674%,33.724976%);stop-opacity:1;" | |||
id="stop6970" /> | |||
<stop | |||
offset="0.03125" | |||
style="stop-color:rgb(32.814026%,32.806396%,33.586121%);stop-opacity:1;" | |||
id="stop6972" /> | |||
<stop | |||
offset="0.046875" | |||
style="stop-color:rgb(32.58667%,32.565308%,33.338928%);stop-opacity:1;" | |||
id="stop6974" /> | |||
<stop | |||
offset="0.0625" | |||
style="stop-color:rgb(32.383728%,32.350159%,33.117676%);stop-opacity:1;" | |||
id="stop6976" /> | |||
<stop | |||
offset="0.078125" | |||
style="stop-color:rgb(32.182312%,32.136536%,32.897949%);stop-opacity:1;" | |||
id="stop6978" /> | |||
<stop | |||
offset="0.09375" | |||
style="stop-color:rgb(31.980896%,31.922913%,32.678223%);stop-opacity:1;" | |||
id="stop6980" /> | |||
<stop | |||
offset="0.109375" | |||
style="stop-color:rgb(31.77948%,31.70929%,32.458496%);stop-opacity:1;" | |||
id="stop6982" /> | |||
<stop | |||
offset="0.125" | |||
style="stop-color:rgb(31.578064%,31.495667%,32.23877%);stop-opacity:1;" | |||
id="stop6984" /> | |||
<stop | |||
offset="0.140625" | |||
style="stop-color:rgb(31.376648%,31.280518%,32.017517%);stop-opacity:1;" | |||
id="stop6986" /> | |||
<stop | |||
offset="0.15625" | |||
style="stop-color:rgb(31.173706%,31.066895%,31.797791%);stop-opacity:1;" | |||
id="stop6988" /> | |||
<stop | |||
offset="0.171875" | |||
style="stop-color:rgb(30.97229%,30.853271%,31.578064%);stop-opacity:1;" | |||
id="stop6990" /> | |||
<stop | |||
offset="0.1875" | |||
style="stop-color:rgb(30.770874%,30.639648%,31.358337%);stop-opacity:1;" | |||
id="stop6992" /> | |||
<stop | |||
offset="0.203125" | |||
style="stop-color:rgb(30.569458%,30.426025%,31.138611%);stop-opacity:1;" | |||
id="stop6994" /> | |||
<stop | |||
offset="0.21875" | |||
style="stop-color:rgb(30.368042%,30.212402%,30.917358%);stop-opacity:1;" | |||
id="stop6996" /> | |||
<stop | |||
offset="0.234375" | |||
style="stop-color:rgb(30.166626%,29.997253%,30.697632%);stop-opacity:1;" | |||
id="stop6998" /> | |||
<stop | |||
offset="0.25" | |||
style="stop-color:rgb(29.963684%,29.78363%,30.477905%);stop-opacity:1;" | |||
id="stop7000" /> | |||
<stop | |||
offset="0.265625" | |||
style="stop-color:rgb(29.762268%,29.570007%,30.258179%);stop-opacity:1;" | |||
id="stop7002" /> | |||
<stop | |||
offset="0.28125" | |||
style="stop-color:rgb(29.560852%,29.356384%,30.038452%);stop-opacity:1;" | |||
id="stop7004" /> | |||
<stop | |||
offset="0.296875" | |||
style="stop-color:rgb(29.359436%,29.142761%,29.8172%);stop-opacity:1;" | |||
id="stop7006" /> | |||
<stop | |||
offset="0.3125" | |||
style="stop-color:rgb(29.15802%,28.927612%,29.597473%);stop-opacity:1;" | |||
id="stop7008" /> | |||
<stop | |||
offset="0.328125" | |||
style="stop-color:rgb(28.955078%,28.713989%,29.377747%);stop-opacity:1;" | |||
id="stop7010" /> | |||
<stop | |||
offset="0.34375" | |||
style="stop-color:rgb(28.753662%,28.500366%,29.15802%);stop-opacity:1;" | |||
id="stop7012" /> | |||
<stop | |||
offset="0.359375" | |||
style="stop-color:rgb(28.552246%,28.286743%,28.938293%);stop-opacity:1;" | |||
id="stop7014" /> | |||
<stop | |||
offset="0.375" | |||
style="stop-color:rgb(28.35083%,28.07312%,28.718567%);stop-opacity:1;" | |||
id="stop7016" /> | |||
<stop | |||
offset="0.390625" | |||
style="stop-color:rgb(28.149414%,27.857971%,28.497314%);stop-opacity:1;" | |||
id="stop7018" /> | |||
<stop | |||
offset="0.40625" | |||
style="stop-color:rgb(27.947998%,27.644348%,28.277588%);stop-opacity:1;" | |||
id="stop7020" /> | |||
<stop | |||
offset="0.421875" | |||
style="stop-color:rgb(27.745056%,27.430725%,28.057861%);stop-opacity:1;" | |||
id="stop7022" /> | |||
<stop | |||
offset="0.4375" | |||
style="stop-color:rgb(27.54364%,27.217102%,27.838135%);stop-opacity:1;" | |||
id="stop7024" /> | |||
<stop | |||
offset="0.453125" | |||
style="stop-color:rgb(27.342224%,27.003479%,27.618408%);stop-opacity:1;" | |||
id="stop7026" /> | |||
<stop | |||
offset="0.46875" | |||
style="stop-color:rgb(27.140808%,26.789856%,27.397156%);stop-opacity:1;" | |||
id="stop7028" /> | |||
<stop | |||
offset="0.484375" | |||
style="stop-color:rgb(26.939392%,26.574707%,27.177429%);stop-opacity:1;" | |||
id="stop7030" /> | |||
<stop | |||
offset="0.5" | |||
style="stop-color:rgb(26.737976%,26.361084%,26.957703%);stop-opacity:1;" | |||
id="stop7032" /> | |||
<stop | |||
offset="0.515625" | |||
style="stop-color:rgb(26.535034%,26.147461%,26.737976%);stop-opacity:1;" | |||
id="stop7034" /> | |||
<stop | |||
offset="0.53125" | |||
style="stop-color:rgb(26.333618%,25.933838%,26.51825%);stop-opacity:1;" | |||
id="stop7036" /> | |||
<stop | |||
offset="0.546875" | |||
style="stop-color:rgb(26.132202%,25.720215%,26.296997%);stop-opacity:1;" | |||
id="stop7038" /> | |||
<stop | |||
offset="0.5625" | |||
style="stop-color:rgb(25.930786%,25.505066%,26.077271%);stop-opacity:1;" | |||
id="stop7040" /> | |||
<stop | |||
offset="0.578125" | |||
style="stop-color:rgb(25.72937%,25.291443%,25.857544%);stop-opacity:1;" | |||
id="stop7042" /> | |||
<stop | |||
offset="0.59375" | |||
style="stop-color:rgb(25.527954%,25.07782%,25.637817%);stop-opacity:1;" | |||
id="stop7044" /> | |||
<stop | |||
offset="0.609375" | |||
style="stop-color:rgb(25.325012%,24.864197%,25.418091%);stop-opacity:1;" | |||
id="stop7046" /> | |||
<stop | |||
offset="0.625" | |||
style="stop-color:rgb(25.123596%,24.650574%,25.196838%);stop-opacity:1;" | |||
id="stop7048" /> | |||
<stop | |||
offset="0.640625" | |||
style="stop-color:rgb(24.92218%,24.436951%,24.977112%);stop-opacity:1;" | |||
id="stop7050" /> | |||
<stop | |||
offset="0.65625" | |||
style="stop-color:rgb(24.720764%,24.221802%,24.757385%);stop-opacity:1;" | |||
id="stop7052" /> | |||
<stop | |||
offset="0.671875" | |||
style="stop-color:rgb(24.519348%,24.008179%,24.537659%);stop-opacity:1;" | |||
id="stop7054" /> | |||
<stop | |||
offset="0.6875" | |||
style="stop-color:rgb(24.316406%,23.794556%,24.317932%);stop-opacity:1;" | |||
id="stop7056" /> | |||
<stop | |||
offset="0.703125" | |||
style="stop-color:rgb(24.11499%,23.580933%,24.098206%);stop-opacity:1;" | |||
id="stop7058" /> | |||
<stop | |||
offset="0.71875" | |||
style="stop-color:rgb(23.913574%,23.36731%,23.876953%);stop-opacity:1;" | |||
id="stop7060" /> | |||
<stop | |||
offset="0.734375" | |||
style="stop-color:rgb(23.712158%,23.152161%,23.657227%);stop-opacity:1;" | |||
id="stop7062" /> | |||
<stop | |||
offset="0.75" | |||
style="stop-color:rgb(23.510742%,22.938538%,23.4375%);stop-opacity:1;" | |||
id="stop7064" /> | |||
<stop | |||
offset="0.765625" | |||
style="stop-color:rgb(23.309326%,22.724915%,23.217773%);stop-opacity:1;" | |||
id="stop7066" /> | |||
<stop | |||
offset="0.78125" | |||
style="stop-color:rgb(23.106384%,22.511292%,22.998047%);stop-opacity:1;" | |||
id="stop7068" /> | |||
<stop | |||
offset="0.796875" | |||
style="stop-color:rgb(22.904968%,22.297668%,22.776794%);stop-opacity:1;" | |||
id="stop7070" /> | |||
<stop | |||
offset="0.8125" | |||
style="stop-color:rgb(22.703552%,22.084045%,22.557068%);stop-opacity:1;" | |||
id="stop7072" /> | |||
<stop | |||
offset="0.828125" | |||
style="stop-color:rgb(22.502136%,21.868896%,22.337341%);stop-opacity:1;" | |||
id="stop7074" /> | |||
<stop | |||
offset="0.84375" | |||
style="stop-color:rgb(22.30072%,21.655273%,22.117615%);stop-opacity:1;" | |||
id="stop7076" /> | |||
<stop | |||
offset="0.859375" | |||
style="stop-color:rgb(22.099304%,21.44165%,21.897888%);stop-opacity:1;" | |||
id="stop7078" /> | |||
<stop | |||
offset="0.875" | |||
style="stop-color:rgb(21.896362%,21.228027%,21.676636%);stop-opacity:1;" | |||
id="stop7080" /> | |||
<stop | |||
offset="0.890625" | |||
style="stop-color:rgb(21.694946%,21.014404%,21.456909%);stop-opacity:1;" | |||
id="stop7082" /> | |||
<stop | |||
offset="0.90625" | |||
style="stop-color:rgb(21.49353%,20.799255%,21.237183%);stop-opacity:1;" | |||
id="stop7084" /> | |||
<stop | |||
offset="0.921875" | |||
style="stop-color:rgb(21.292114%,20.585632%,21.017456%);stop-opacity:1;" | |||
id="stop7086" /> | |||
<stop | |||
offset="0.9375" | |||
style="stop-color:rgb(21.090698%,20.372009%,20.797729%);stop-opacity:1;" | |||
id="stop7088" /> | |||
<stop | |||
offset="0.953125" | |||
style="stop-color:rgb(20.889282%,20.158386%,20.576477%);stop-opacity:1;" | |||
id="stop7090" /> | |||
<stop | |||
offset="0.96875" | |||
style="stop-color:rgb(20.68634%,19.944763%,20.35675%);stop-opacity:1;" | |||
id="stop7092" /> | |||
<stop | |||
offset="0.984375" | |||
style="stop-color:rgb(20.484924%,19.729614%,20.137024%);stop-opacity:1;" | |||
id="stop7094" /> | |||
<stop | |||
offset="1" | |||
style="stop-color:rgb(20.283508%,19.515991%,19.917297%);stop-opacity:1;" | |||
id="stop7096" /> | |||
</linearGradient> | |||
</defs> | |||
<sodipodi:namedview | |||
id="base" | |||
pagecolor="#ffffff" | |||
bordercolor="#666666" | |||
borderopacity="1.0" | |||
inkscape:pageopacity="0.0" | |||
inkscape:pageshadow="2" | |||
inkscape:zoom="1.979899" | |||
inkscape:cx="-53.032455" | |||
inkscape:cy="-10.55258" | |||
inkscape:document-units="mm" | |||
inkscape:current-layer="layer1" | |||
showgrid="false" | |||
fit-margin-top="0" | |||
fit-margin-left="0" | |||
fit-margin-right="0" | |||
fit-margin-bottom="0" | |||
inkscape:window-width="1600" | |||
inkscape:window-height="882" | |||
inkscape:window-x="0" | |||
inkscape:window-y="18" | |||
inkscape:window-maximized="0" /> | |||
<metadata | |||
id="metadata8101"> | |||
<rdf:RDF> | |||
<cc:Work | |||
rdf:about=""> | |||
<dc:format>image/svg+xml</dc:format> | |||
<dc:type | |||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> | |||
<dc:title></dc:title> | |||
</cc:Work> | |||
</rdf:RDF> | |||
</metadata> | |||
<g | |||
inkscape:label="Layer 1" | |||
inkscape:groupmode="layer" | |||
id="layer1" | |||
transform="translate(-113.84542,-95.231797)"> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10059" | |||
d="M 124.74569,107.4316 H 113.84542 V 95.231797 h 10.90027 z m 0,0" | |||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10061" | |||
d="m 115.94967,107.08157 c -1.04733,0 -1.90444,-0.85714 -1.90444,-1.90447 v -7.68943 c 0,-1.047327 0.85711,-1.905847 1.90444,-1.905847 h 6.69036 c 1.04733,0 1.90447,0.85852 1.90447,1.905847 v 7.68943 c 0,1.04733 -0.85714,1.90447 -1.90447,1.90447 z m 0,0" | |||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10063" | |||
d="m 123.47237,98.619028 c 0,-0.216358 -0.11712,-0.516749 -0.2577,-0.668337 l -1.16304,-1.233347 c -0.14196,-0.151588 -0.42446,-0.277001 -0.62703,-0.277001 h -5.57138 c -0.40655,0 -0.73727,0.352778 -0.73727,0.784119 v 8.215848 c 0,0.42996 0.33072,0.78412 0.73727,0.78412 h 5.57138 c 0.20257,0 0.48507,-0.12679 0.62841,-0.27838 l 1.16166,-1.23197 c 0.14058,-0.15155 0.2577,-0.45336 0.2577,-0.66971 z m 0,0" | |||
style="fill:#cccaca;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10065" | |||
d="m 121.75946,104.99383 h -4.69357 v -7.164424 h 4.69357 z m 0,0" | |||
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#a1a3a1;stroke-width:0.08819444;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:3;stroke-opacity:1" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10067" | |||
d="m 120.60193,103.78529 h -2.53284 v -4.745963 h 2.53284 z m 0,0" | |||
style="fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10069" | |||
d="m 120.84309,99.71458 h -0.37483 v 1.10243 h 0.37483 z m 0,0" | |||
style="fill:#e1ba21;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
<path | |||
inkscape:connector-curvature="0" | |||
id="path10071" | |||
d="m 120.84309,102.00764 h -0.37483 v 1.10243 h 0.37483 z m 0,0" | |||
style="fill:#e1ba21;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775" /> | |||
</g> | |||
</svg> |
@@ -4,12 +4,7 @@ | |||
namespace rack { | |||
std::string gApplicationName = "VCV Rack"; | |||
std::string gApplicationVersion = | |||
#ifdef VERSION | |||
TOSTRING(VERSION); | |||
#else | |||
""; | |||
#endif | |||
std::string gApplicationVersion = TOSTRING(VERSION); | |||
std::string gApiHost = "https://api.vcvrack.com"; | |||
// std::string gApiHost = "http://localhost:8081"; | |||
@@ -29,4 +24,23 @@ void sceneDestroy() { | |||
} | |||
json_t *colorToJson(NVGcolor color) { | |||
json_t *colorJ = json_object(); | |||
json_object_set_new(colorJ, "r", json_real(color.r)); | |||
json_object_set_new(colorJ, "g", json_real(color.g)); | |||
json_object_set_new(colorJ, "b", json_real(color.b)); | |||
json_object_set_new(colorJ, "a", json_real(color.a)); | |||
return colorJ; | |||
} | |||
NVGcolor jsonToColor(json_t *colorJ) { | |||
NVGcolor color; | |||
color.r = json_number_value(json_object_get(colorJ, "r")); | |||
color.g = json_number_value(json_object_get(colorJ, "g")); | |||
color.b = json_number_value(json_object_get(colorJ, "b")); | |||
color.a = json_number_value(json_object_get(colorJ, "a")); | |||
return color; | |||
} | |||
} // namespace rack |
@@ -98,8 +98,8 @@ static bool isModelMatch(Model *model, std::string search) { | |||
str += " "; | |||
str += gTagNames[tag]; | |||
} | |||
str = tolower(str); | |||
search = tolower(search); | |||
str = lowercase(str); | |||
search = lowercase(search); | |||
return (str.find(search) != std::string::npos); | |||
} | |||
@@ -0,0 +1,115 @@ | |||
#include "app.hpp" | |||
#include "audio.hpp" | |||
namespace rack { | |||
struct AudioDriverItem : MenuItem { | |||
AudioIO *audioIO; | |||
int driver; | |||
void onAction(EventAction &e) override { | |||
audioIO->setDriver(driver); | |||
} | |||
}; | |||
struct AudioDeviceItem : MenuItem { | |||
AudioIO *audioIO; | |||
int device; | |||
void onAction(EventAction &e) override { | |||
audioIO->device = device; | |||
audioIO->openStream(); | |||
} | |||
}; | |||
struct AudioSampleRateItem : MenuItem { | |||
AudioIO *audioIO; | |||
int sampleRate; | |||
void onAction(EventAction &e) override { | |||
audioIO->sampleRate = sampleRate; | |||
audioIO->openStream(); | |||
} | |||
}; | |||
struct AudioBlockSizeItem : MenuItem { | |||
AudioIO *audioIO; | |||
int blockSize; | |||
void onAction(EventAction &e) override { | |||
audioIO->blockSize = blockSize; | |||
audioIO->openStream(); | |||
} | |||
}; | |||
void AudioWidget::onMouseDown(EventMouseDown &e) { | |||
OpaqueWidget::onMouseDown(e); | |||
if (!audioIO) | |||
return; | |||
Menu *menu = gScene->createMenu(); | |||
// Audio driver | |||
menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Audio driver")); | |||
for (int driver : audioIO->listDrivers()) { | |||
AudioDriverItem *item = new AudioDriverItem(); | |||
item->audioIO = audioIO; | |||
item->driver = driver; | |||
item->text = audioIO->getDriverName(driver); | |||
item->rightText = CHECKMARK(item->driver == audioIO->driver); | |||
menu->addChild(item); | |||
} | |||
menu->addChild(construct<MenuEntry>()); | |||
// Audio device | |||
menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Audio device")); | |||
int deviceCount = audioIO->getDeviceCount(); | |||
{ | |||
AudioDeviceItem *item = new AudioDeviceItem(); | |||
item->audioIO = audioIO; | |||
item->device = -1; | |||
item->text = "No device"; | |||
item->rightText = CHECKMARK(item->device == audioIO->device); | |||
menu->addChild(item); | |||
} | |||
for (int device = 0; device < deviceCount; device++) { | |||
AudioDeviceItem *item = new AudioDeviceItem(); | |||
item->audioIO = audioIO; | |||
item->device = device; | |||
item->text = audioIO->getDeviceDetail(device); | |||
item->rightText = CHECKMARK(item->device == audioIO->device); | |||
menu->addChild(item); | |||
} | |||
menu->addChild(construct<MenuEntry>()); | |||
// Sample rate | |||
menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Sample rate")); | |||
for (int sampleRate : audioIO->listSampleRates()) { | |||
AudioSampleRateItem *item = new AudioSampleRateItem(); | |||
item->audioIO = audioIO; | |||
item->sampleRate = sampleRate; | |||
item->text = stringf("%d Hz", sampleRate); | |||
item->rightText = CHECKMARK(item->sampleRate == audioIO->sampleRate); | |||
menu->addChild(item); | |||
} | |||
menu->addChild(construct<MenuEntry>()); | |||
// Block size | |||
menu->addChild(construct<MenuLabel>(&MenuLabel::text, "Block size")); | |||
std::vector<int> blockSizes = {64, 128, 256, 512, 1024, 2048, 4096}; | |||
for (int blockSize : blockSizes) { | |||
AudioBlockSizeItem *item = new AudioBlockSizeItem(); | |||
item->audioIO = audioIO; | |||
item->blockSize = blockSize; | |||
float latency = (float) blockSize / audioIO->sampleRate * 1000.0; | |||
item->text = stringf("%d (%.1f ms)", blockSize, latency); | |||
item->rightText = CHECKMARK(item->blockSize == audioIO->blockSize); | |||
menu->addChild(item); | |||
} | |||
} | |||
} // 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,17 +5,22 @@ 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); | |||
@@ -27,13 +32,18 @@ void LightWidget::draw(NVGcontext *vg) { | |||
nvgStroke(vg); | |||
// Inner glow | |||
nvgGlobalCompositeOperation(vg, NVG_LIGHTER); | |||
nvgFillColor(vg, color); | |||
nvgGlobalCompositeOperation(vg, NVG_LIGHTER); | |||
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,6 +51,7 @@ 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); | |||
} | |||
@@ -0,0 +1,66 @@ | |||
#include "app.hpp" | |||
#include "midi.hpp" | |||
namespace rack { | |||
struct MidiPortItem : MenuItem { | |||
MidiIO *midiIO; | |||
int port; | |||
void onAction(EventAction &e) override { | |||
midiIO->openPort(port); | |||
} | |||
}; | |||
struct MidiChannelItem : MenuItem { | |||
MidiIO *midiIO; | |||
int channel; | |||
void onAction(EventAction &e) override { | |||
midiIO->channel = channel; | |||
} | |||
}; | |||
void MidiWidget::onMouseDown(EventMouseDown &e) { | |||
OpaqueWidget::onMouseDown(e); | |||
if (!midiIO) | |||
return; | |||
Menu *menu = gScene->createMenu(); | |||
menu->addChild(construct<MenuLabel>(&MenuLabel::text, "MIDI port")); | |||
for (int port = 0; port < midiIO->getPortCount(); port++) { | |||
MidiPortItem *item = new MidiPortItem(); | |||
item->midiIO = midiIO; | |||
item->port = port; | |||
item->text = midiIO->getPortName(port); | |||
item->rightText = CHECKMARK(item->port == midiIO->port); | |||
menu->addChild(item); | |||
} | |||
menu->addChild(construct<MenuEntry>()); | |||
menu->addChild(construct<MenuLabel>(&MenuLabel::text, "MIDI channel")); | |||
MidiInput *midiInput = dynamic_cast<MidiInput*>(midiIO); | |||
if (midiInput) { | |||
MidiChannelItem *item = new MidiChannelItem(); | |||
item->midiIO = midiIO; | |||
item->channel = -1; | |||
item->text = "All"; | |||
item->rightText = CHECKMARK(item->channel == midiIO->channel); | |||
menu->addChild(item); | |||
} | |||
for (int channel = 0; channel < 16; channel++) { | |||
MidiChannelItem *item = new MidiChannelItem(); | |||
item->midiIO = midiIO; | |||
item->channel = channel; | |||
item->text = stringf("%d", channel + 1); | |||
item->rightText = CHECKMARK(item->channel == midiIO->channel); | |||
menu->addChild(item); | |||
} | |||
} | |||
} // namespace rack |
@@ -60,12 +60,16 @@ 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 | |||
json_t *posJ = json_pack("[f, f]", (double) box.pos.x, (double) box.pos.y); | |||
Vec pos = box.pos.div(RACK_GRID_SIZE).round(); | |||
json_t *posJ = json_pack("[i, i]", (int) pos.x, (int) pos.y); | |||
json_object_set_new(rootJ, "pos", posJ); | |||
// params | |||
json_t *paramsJ = json_array(); | |||
@@ -86,19 +90,50 @@ json_t *ModuleWidget::toJson() { | |||
} | |||
void ModuleWidget::fromJson(json_t *rootJ) { | |||
// legacy | |||
int legacy = 0; | |||
json_t *legacyJ = json_object_get(rootJ, "legacy"); | |||
if (legacyJ) | |||
legacy = json_integer_value(legacyJ); | |||
// pos | |||
json_t *posJ = json_object_get(rootJ, "pos"); | |||
double x, y; | |||
json_unpack(posJ, "[F, F]", &x, &y); | |||
box.pos = Vec(x, y); | |||
Vec pos = Vec(x, y); | |||
if (legacy && legacy <= 1) { | |||
box.pos = pos; | |||
} | |||
else { | |||
box.pos = pos.mult(RACK_GRID_SIZE); | |||
} | |||
// params | |||
json_t *paramsJ = json_object_get(rootJ, "params"); | |||
size_t paramId; | |||
size_t i; | |||
json_t *paramJ; | |||
json_array_foreach(paramsJ, paramId, paramJ) { | |||
if (paramId < params.size()) { | |||
params[paramId]->fromJson(paramJ); | |||
json_array_foreach(paramsJ, i, paramJ) { | |||
if (legacy && legacy <= 1) { | |||
// The index in the array we're iterating is the index of the ParamWidget in the params vector. | |||
if (i < params.size()) { | |||
// Create upgraded version of param JSON object | |||
json_t *newParamJ = json_object(); | |||
json_object_set(newParamJ, "value", paramJ); | |||
params[i]->fromJson(newParamJ); | |||
json_decref(newParamJ); | |||
} | |||
} | |||
else { | |||
// Get paramId | |||
json_t *paramIdJ = json_object_get(paramJ, "paramId"); | |||
if (!paramIdJ) | |||
continue; | |||
int paramId = json_integer_value(paramIdJ); | |||
// Find ParamWidget(s) with paramId | |||
for (ParamWidget *paramWidget : params) { | |||
if (paramWidget->paramId == paramId) | |||
paramWidget->fromJson(paramJ); | |||
} | |||
} | |||
} | |||
@@ -118,12 +153,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 +179,7 @@ void ModuleWidget::randomize() { | |||
param->randomize(); | |||
} | |||
if (module) { | |||
module->randomize(); | |||
module->onRandomize(); | |||
} | |||
} | |||
@@ -188,7 +235,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 +326,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; | |||
} | |||
@@ -6,12 +6,16 @@ namespace rack { | |||
json_t *ParamWidget::toJson() { | |||
json_t *paramJ = json_real(value); | |||
return paramJ; | |||
json_t *rootJ = json_object(); | |||
json_object_set_new(rootJ, "paramId", json_integer(paramId)); | |||
json_object_set_new(rootJ, "value", json_real(value)); | |||
return rootJ; | |||
} | |||
void ParamWidget::fromJson(json_t *rootJ) { | |||
setValue(json_number_value(rootJ)); | |||
json_t *valueJ = json_object_get(rootJ, "value"); | |||
if (valueJ) | |||
setValue(json_number_value(valueJ)); | |||
} | |||
void ParamWidget::randomize() { | |||
@@ -1,7 +1,7 @@ | |||
#include "app.hpp" | |||
#include "gui.hpp" | |||
#include "components.hpp" | |||
#include "engine.hpp" | |||
#include "componentlibrary.hpp" | |||
namespace rack { | |||
@@ -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); | |||
} | |||
} | |||
@@ -45,10 +45,10 @@ RackScene::RackScene() { | |||
scrollWidget->box.pos.y = gToolbar->box.size.y; | |||
// Check for new version | |||
if (!gApplicationVersion.empty()) { | |||
std::thread versionThread(checkVersion); | |||
versionThread.detach(); | |||
} | |||
#if defined(RELEASE) | |||
std::thread versionThread(checkVersion); | |||
versionThread.detach(); | |||
#endif | |||
} | |||
void RackScene::step() { | |||
@@ -68,7 +68,7 @@ void RackScene::step() { | |||
// Version popup message | |||
if (!newVersion.empty()) { | |||
std::string versionMessage = stringf("Rack %s is available.\n\nYou have Rack %s.\n\nWould you like to download the new version on the website?", newVersion.c_str(), gApplicationVersion.c_str()); | |||
std::string versionMessage = stringf("Rack v%s is available.\n\nYou have Rack v%s.\n\nWould you like to download the new version on the website?", newVersion.c_str(), gApplicationVersion.c_str()); | |||
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, versionMessage.c_str())) { | |||
std::thread t(openBrowser, "https://vcvrack.com/"); | |||
t.detach(); | |||
@@ -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) { | |||
json_dumpf(rootJ, file, JSON_INDENT(2)); | |||
json_decref(rootJ); | |||
FILE *file = fopen(path.c_str(), "w"); | |||
if (file) { | |||
json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
fclose(file); | |||
} | |||
fclose(file); | |||
json_decref(rootJ); | |||
} | |||
void RackWidget::loadPatch(std::string path) { | |||
@@ -154,31 +172,26 @@ json_t *RackWidget::toJson() { | |||
if (!(wireWidget->outputPort && wireWidget->inputPort)) | |||
continue; | |||
// wire | |||
json_t *wire = json_object(); | |||
{ | |||
// Get the modules at each end of the wire | |||
ModuleWidget *outputModuleWidget = wireWidget->outputPort->getAncestorOfType<ModuleWidget>(); | |||
assert(outputModuleWidget); | |||
int outputModuleId = moduleIds[outputModuleWidget]; | |||
ModuleWidget *inputModuleWidget = wireWidget->inputPort->getAncestorOfType<ModuleWidget>(); | |||
assert(inputModuleWidget); | |||
int inputModuleId = moduleIds[inputModuleWidget]; | |||
// Get output/input ports | |||
auto outputIt = std::find(outputModuleWidget->outputs.begin(), outputModuleWidget->outputs.end(), wireWidget->outputPort); | |||
assert(outputIt != outputModuleWidget->outputs.end()); | |||
int outputId = outputIt - outputModuleWidget->outputs.begin(); | |||
auto inputIt = std::find(inputModuleWidget->inputs.begin(), inputModuleWidget->inputs.end(), wireWidget->inputPort); | |||
assert(inputIt != inputModuleWidget->inputs.end()); | |||
int inputId = inputIt - inputModuleWidget->inputs.begin(); | |||
json_object_set_new(wire, "outputModuleId", json_integer(outputModuleId)); | |||
json_object_set_new(wire, "outputId", json_integer(outputId)); | |||
json_object_set_new(wire, "inputModuleId", json_integer(inputModuleId)); | |||
json_object_set_new(wire, "inputId", json_integer(inputId)); | |||
} | |||
json_t *wire = wireWidget->toJson(); | |||
// Get the modules at each end of the wire | |||
ModuleWidget *outputModuleWidget = wireWidget->outputPort->getAncestorOfType<ModuleWidget>(); | |||
assert(outputModuleWidget); | |||
int outputModuleId = moduleIds[outputModuleWidget]; | |||
ModuleWidget *inputModuleWidget = wireWidget->inputPort->getAncestorOfType<ModuleWidget>(); | |||
assert(inputModuleWidget); | |||
int inputModuleId = moduleIds[inputModuleWidget]; | |||
// Get output/input ports | |||
int outputId = wireWidget->outputPort->portId; | |||
int inputId = wireWidget->inputPort->portId; | |||
json_object_set_new(wire, "outputModuleId", json_integer(outputModuleId)); | |||
json_object_set_new(wire, "outputId", json_integer(outputId)); | |||
json_object_set_new(wire, "inputModuleId", json_integer(inputModuleId)); | |||
json_object_set_new(wire, "inputId", json_integer(inputId)); | |||
json_array_append_new(wires, wire); | |||
} | |||
json_object_set_new(rootJ, "wires", wires); | |||
@@ -190,11 +203,22 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
std::string message; | |||
// version | |||
std::string version; | |||
json_t *versionJ = json_object_get(rootJ, "version"); | |||
if (versionJ) { | |||
std::string version = json_string_value(versionJ); | |||
version = json_string_value(versionJ); | |||
if (!version.empty() && gApplicationVersion != version) | |||
message += stringf("This patch was created with Rack %s. Saving it will convert it to a Rack %s patch.\n\n", version.c_str(), gApplicationVersion.empty() ? "dev" : gApplicationVersion.c_str()); | |||
message += stringf("This patch was created with Rack %s. Saving it will convert it to a Rack %s patch.\n\n", version.c_str(), gApplicationVersion.c_str()); | |||
} | |||
// Detect old patches with ModuleWidget::params/inputs/outputs indices. | |||
// (We now use Module::params/inputs/outputs indices.) | |||
int legacy = 0; | |||
if (startsWith(version, "0.3.") || startsWith(version, "0.4.") || startsWith(version, "0.5.") || version == "" || version == "dev") { | |||
legacy = 1; | |||
} | |||
if (legacy) { | |||
info("Loading patch using legacy mode %d", legacy); | |||
} | |||
// modules | |||
@@ -204,6 +228,10 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
size_t moduleId; | |||
json_t *moduleJ; | |||
json_array_foreach(modulesJ, moduleId, moduleJ) { | |||
// Set legacy property | |||
if (legacy) | |||
json_object_set_new(moduleJ, "legacy", json_integer(legacy)); | |||
json_t *pluginSlugJ = json_object_get(moduleJ, "plugin"); | |||
if (!pluginSlugJ) continue; | |||
json_t *modelSlugJ = json_object_get(moduleJ, "model"); | |||
@@ -235,7 +263,7 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
} | |||
if (!model) { | |||
message += stringf("Could not find module \"%s\" in plugin \"%s\"\n", pluginSlug.c_str(), modelSlug.c_str()); | |||
message += stringf("Could not find module \"%s\" in plugin \"%s\"\n", modelSlug.c_str(), pluginSlug.c_str()); | |||
continue; | |||
} | |||
@@ -253,23 +281,44 @@ void RackWidget::fromJson(json_t *rootJ) { | |||
size_t wireId; | |||
json_t *wireJ; | |||
json_array_foreach(wiresJ, wireId, wireJ) { | |||
int outputModuleId, outputId; | |||
int inputModuleId, inputId; | |||
int err = json_unpack(wireJ, "{s:i, s:i, s:i, s:i}", | |||
"outputModuleId", &outputModuleId, "outputId", &outputId, | |||
"inputModuleId", &inputModuleId, "inputId", &inputId); | |||
if (err) continue; | |||
// Get ports | |||
int outputModuleId = json_integer_value(json_object_get(wireJ, "outputModuleId")); | |||
int outputId = json_integer_value(json_object_get(wireJ, "outputId")); | |||
int inputModuleId = json_integer_value(json_object_get(wireJ, "inputModuleId")); | |||
int inputId = json_integer_value(json_object_get(wireJ, "inputId")); | |||
// Get module widgets | |||
ModuleWidget *outputModuleWidget = moduleWidgets[outputModuleId]; | |||
if (!outputModuleWidget) continue; | |||
Port *outputPort = outputModuleWidget->outputs[outputId]; | |||
if (!outputPort) continue; | |||
ModuleWidget *inputModuleWidget = moduleWidgets[inputModuleId]; | |||
if (!inputModuleWidget) continue; | |||
Port *inputPort = inputModuleWidget->inputs[inputId]; | |||
if (!inputPort) continue; | |||
// Get port widgets | |||
Port *outputPort = NULL; | |||
Port *inputPort = NULL; | |||
if (legacy && legacy <= 1) { | |||
outputPort = outputModuleWidget->outputs[outputId]; | |||
inputPort = inputModuleWidget->inputs[inputId]; | |||
} | |||
else { | |||
for (Port *port : outputModuleWidget->outputs) { | |||
if (port->portId == outputId) { | |||
outputPort = port; | |||
break; | |||
} | |||
} | |||
for (Port *port : inputModuleWidget->inputs) { | |||
if (port->portId == inputId) { | |||
inputPort = port; | |||
break; | |||
} | |||
} | |||
} | |||
if (!outputPort || !inputPort) | |||
continue; | |||
// Create WireWidget | |||
WireWidget *wireWidget = new WireWidget(); | |||
wireWidget->fromJson(wireJ); | |||
wireWidget->outputPort = outputPort; | |||
wireWidget->inputPort = inputPort; | |||
wireWidget->updateWire(); | |||
@@ -285,9 +334,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 +374,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,23 +43,23 @@ 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")); | |||
} | |||
} | |||
}; | |||
struct PauseItem : MenuItem { | |||
struct EnginePauseItem : MenuItem { | |||
void onAction(EventAction &e) override { | |||
gPaused = !gPaused; | |||
} | |||
}; | |||
struct SampleRateItem : MenuItem { | |||
struct EngineSampleRateItem : MenuItem { | |||
float sampleRate; | |||
void onAction(EventAction &e) override { | |||
engineSetSampleRate(sampleRate); | |||
@@ -67,23 +67,22 @@ struct SampleRateItem : MenuItem { | |||
} | |||
}; | |||
struct SampleRateChoice : ChoiceButton { | |||
struct EngineSampleRateChoice : ChoiceButton { | |||
void onAction(EventAction &e) override { | |||
Menu *menu = gScene->createMenu(); | |||
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)); | |||
menu->box.size.x = box.size.x; | |||
PauseItem *pauseItem = new PauseItem(); | |||
EnginePauseItem *pauseItem = new EnginePauseItem(); | |||
pauseItem->text = gPaused ? "Resume engine" : "Pause engine"; | |||
menu->pushChild(pauseItem); | |||
float sampleRates[] = {44100, 48000, 88200, 96000, 176400, 192000}; | |||
int sampleRatesLen = sizeof(sampleRates) / sizeof(sampleRates[0]); | |||
for (int i = 0; i < sampleRatesLen; i++) { | |||
SampleRateItem *item = new SampleRateItem(); | |||
item->text = stringf("%.0f Hz", sampleRates[i]); | |||
item->sampleRate = sampleRates[i]; | |||
menu->pushChild(item); | |||
menu->addChild(pauseItem); | |||
std::vector<float> sampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; | |||
for (float sampleRate : sampleRates) { | |||
EngineSampleRateItem *item = new EngineSampleRateItem(); | |||
item->text = stringf("%.0f Hz", sampleRate); | |||
item->sampleRate = sampleRate; | |||
menu->addChild(item); | |||
} | |||
} | |||
void step() override { | |||
@@ -112,7 +111,7 @@ Toolbar::Toolbar() { | |||
xPos += margin; | |||
{ | |||
SampleRateChoice *srChoice = new SampleRateChoice(); | |||
EngineSampleRateChoice *srChoice = new EngineSampleRateChoice(); | |||
srChoice->box.pos = Vec(xPos, margin); | |||
srChoice->box.size.x = 100; | |||
addChild(srChoice); | |||
@@ -151,12 +150,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); | |||
@@ -178,7 +178,7 @@ Toolbar::Toolbar() { | |||
xPos += margin; | |||
*/ | |||
#ifdef VERSION | |||
#if defined(RELEASE) | |||
{ | |||
Widget *pluginManager = new PluginManagerWidget(); | |||
pluginManager->box.pos = Vec(xPos, margin); | |||
@@ -1,6 +1,6 @@ | |||
#include "app.hpp" | |||
#include "engine.hpp" | |||
#include "components.hpp" | |||
#include "componentlibrary.hpp" | |||
#include "gui.hpp" | |||
@@ -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() { | |||
@@ -147,13 +142,33 @@ Vec WireWidget::getInputPos() { | |||
} | |||
} | |||
json_t *WireWidget::toJson() { | |||
json_t *rootJ = json_object(); | |||
json_object_set_new(rootJ, "color", colorToJson(color)); | |||
return rootJ; | |||
} | |||
void WireWidget::fromJson(json_t *rootJ) { | |||
json_t *colorJ = json_object_get(rootJ, "color"); | |||
if (colorJ) | |||
color = jsonToColor(colorJ); | |||
} | |||
void WireWidget::draw(NVGcontext *vg) { | |||
float opacity = gToolbar->wireOpacitySlider->value / 100.0; | |||
float tension = gToolbar->wireTensionSlider->value; | |||
// Draw as opaque if an "incomplete" wire | |||
if (!(inputPort && outputPort)) | |||
opacity = 1.0; | |||
WireWidget *activeWire = gRackWidget->wireContainer->activeWire; | |||
if (activeWire) { | |||
// Draw as opaque if the wire is active | |||
if (activeWire == this) | |||
opacity = 1.0; | |||
} | |||
else { | |||
Port *hoveredPort = dynamic_cast<Port*>(gHoveredWidget); | |||
if (hoveredPort && (hoveredPort == outputPort || hoveredPort == inputPort)) | |||
opacity = 1.0; | |||
} | |||
Vec outputPos = getOutputPos(); | |||
Vec inputPos = getInputPos(); | |||
@@ -26,7 +26,7 @@ namespace rack { | |||
std::string assetGlobal(std::string filename) { | |||
std::string dir; | |||
#ifdef VERSION | |||
#if defined(RELEASE) | |||
#if ARCH_MAC | |||
CFBundleRef bundle = CFBundleGetMainBundle(); | |||
assert(bundle); | |||
@@ -45,16 +45,16 @@ std::string assetGlobal(std::string filename) { | |||
// TODO For now, users should launch Rack from their terminal in the global directory | |||
dir = "."; | |||
#endif | |||
#else // VERSION | |||
#else // RELEASE | |||
dir = "."; | |||
#endif // VERSION | |||
#endif // RELEASE | |||
return dir + "/" + filename; | |||
} | |||
std::string assetLocal(std::string filename) { | |||
std::string dir; | |||
#ifdef VERSION | |||
#if defined(RELEASE) | |||
#if ARCH_MAC | |||
// Get home directory | |||
struct passwd *pw = getpwuid(getuid()); | |||
@@ -83,9 +83,9 @@ std::string assetLocal(std::string filename) { | |||
dir += "/.Rack"; | |||
mkdir(dir.c_str(), 0755); | |||
#endif | |||
#else // VERSION | |||
#else // RELEASE | |||
dir = "."; | |||
#endif // VERSION | |||
#endif // RELEASE | |||
return dir + "/" + filename; | |||
} | |||
@@ -0,0 +1,276 @@ | |||
#include "util.hpp" | |||
#include "math.hpp" | |||
#include "audio.hpp" | |||
#define DRIVER_BRIDGE -1 | |||
namespace rack { | |||
AudioIO::AudioIO() { | |||
setDriver(RtAudio::UNSPECIFIED); | |||
} | |||
AudioIO::~AudioIO() { | |||
closeStream(); | |||
} | |||
std::vector<int> AudioIO::listDrivers() { | |||
std::vector<RtAudio::Api> apis; | |||
RtAudio::getCompiledApi(apis); | |||
std::vector<int> drivers; | |||
for (RtAudio::Api api : apis) | |||
drivers.push_back((int) api); | |||
// Add Bridge fake driver | |||
// drivers.push_back(DRIVER_BRIDGE); | |||
return drivers; | |||
} | |||
std::string AudioIO::getDriverName(int driver) { | |||
switch (driver) { | |||
case RtAudio::UNSPECIFIED: return "Unspecified"; | |||
case RtAudio::LINUX_ALSA: return "ALSA"; | |||
case RtAudio::LINUX_PULSE: return "PulseAudio"; | |||
case RtAudio::LINUX_OSS: return "OSS"; | |||
case RtAudio::UNIX_JACK: return "JACK"; | |||
case RtAudio::MACOSX_CORE: return "Core Audio"; | |||
case RtAudio::WINDOWS_WASAPI: return "WASAPI"; | |||
case RtAudio::WINDOWS_ASIO: return "ASIO"; | |||
case RtAudio::WINDOWS_DS: return "DirectSound"; | |||
case RtAudio::RTAUDIO_DUMMY: return "Dummy"; | |||
case DRIVER_BRIDGE: return "VCV Bridge"; | |||
default: return "Unknown"; | |||
} | |||
} | |||
void AudioIO::setDriver(int driver) { | |||
// Close driver | |||
closeStream(); | |||
if (rtAudio) { | |||
delete rtAudio; | |||
rtAudio = NULL; | |||
} | |||
this->driver = 0; | |||
// Open driver | |||
if (driver >= 0) { | |||
rtAudio = new RtAudio((RtAudio::Api) driver); | |||
this->driver = (int) rtAudio->getCurrentApi(); | |||
} | |||
else if (driver == DRIVER_BRIDGE) { | |||
// TODO Connect to Bridge | |||
this->driver = DRIVER_BRIDGE; | |||
} | |||
} | |||
int AudioIO::getDeviceCount() { | |||
if (rtAudio) { | |||
return rtAudio->getDeviceCount(); | |||
} | |||
if (driver == DRIVER_BRIDGE) { | |||
return 16; | |||
} | |||
return 0; | |||
} | |||
std::string AudioIO::getDeviceName(int device) { | |||
if (rtAudio) { | |||
try { | |||
RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); | |||
return deviceInfo.name; | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to query RtAudio device: %s", e.what()); | |||
} | |||
} | |||
if (driver == DRIVER_BRIDGE) { | |||
return stringf("%d", device + 1); | |||
} | |||
return ""; | |||
} | |||
std::string AudioIO::getDeviceDetail(int device) { | |||
if (rtAudio) { | |||
try { | |||
RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); | |||
return stringf("%s (%d in, %d out)", deviceInfo.name.c_str(), deviceInfo.inputChannels, deviceInfo.outputChannels); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to query RtAudio device: %s", e.what()); | |||
} | |||
} | |||
if (driver == DRIVER_BRIDGE) { | |||
return stringf("Channel %d", device + 1); | |||
} | |||
return ""; | |||
} | |||
static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData) { | |||
AudioIO *audioIO = (AudioIO*) userData; | |||
assert(audioIO); | |||
audioIO->processStream((const float *) inputBuffer, (float *) outputBuffer, nFrames); | |||
return 0; | |||
} | |||
void AudioIO::openStream() { | |||
// Close device but remember the current device number | |||
int device = this->device; | |||
closeStream(); | |||
if (device < 0) | |||
return; | |||
if (rtAudio) { | |||
// Open new device | |||
RtAudio::DeviceInfo deviceInfo; | |||
try { | |||
deviceInfo = rtAudio->getDeviceInfo(device); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to query RtAudio device: %s", e.what()); | |||
return; | |||
} | |||
numOutputs = mini(deviceInfo.outputChannels, maxOutputs); | |||
numInputs = mini(deviceInfo.inputChannels, maxInputs); | |||
if (numOutputs == 0 && numInputs == 0) { | |||
warn("RtAudio device %d has 0 inputs and 0 outputs"); | |||
return; | |||
} | |||
RtAudio::StreamParameters outParameters; | |||
outParameters.deviceId = device; | |||
outParameters.nChannels = numOutputs; | |||
RtAudio::StreamParameters inParameters; | |||
inParameters.deviceId = device; | |||
inParameters.nChannels = numInputs; | |||
RtAudio::StreamOptions options; | |||
// options.flags |= RTAUDIO_SCHEDULE_REALTIME; | |||
int closestSampleRate = deviceInfo.preferredSampleRate; | |||
for (int sr : deviceInfo.sampleRates) { | |||
if (abs(sr - sampleRate) < abs(closestSampleRate - sampleRate)) { | |||
closestSampleRate = sr; | |||
} | |||
} | |||
try { | |||
debug("Opening audio RtAudio device %d", device); | |||
rtAudio->openStream( | |||
numOutputs == 0 ? NULL : &outParameters, | |||
numInputs == 0 ? NULL : &inParameters, | |||
RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to open RtAudio stream: %s", e.what()); | |||
return; | |||
} | |||
try { | |||
debug("Starting RtAudio stream %d", device); | |||
rtAudio->startStream(); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to start RtAudio stream: %s", e.what()); | |||
return; | |||
} | |||
// Update sample rate because this may have changed | |||
this->sampleRate = rtAudio->getStreamSampleRate(); | |||
this->device = device; | |||
onOpenStream(); | |||
} | |||
} | |||
void AudioIO::closeStream() { | |||
if (rtAudio) { | |||
if (rtAudio->isStreamRunning()) { | |||
debug("Stopping RtAudio stream %d", device); | |||
try { | |||
rtAudio->stopStream(); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to stop RtAudio stream %s", e.what()); | |||
} | |||
} | |||
if (rtAudio->isStreamOpen()) { | |||
debug("Closing RtAudio stream %d", device); | |||
try { | |||
rtAudio->closeStream(); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to close RtAudio stream %s", e.what()); | |||
} | |||
} | |||
} | |||
// Reset rtAudio settings | |||
device = -1; | |||
numOutputs = 0; | |||
numInputs = 0; | |||
onCloseStream(); | |||
} | |||
std::vector<int> AudioIO::listSampleRates() { | |||
if (rtAudio) { | |||
try { | |||
RtAudio::DeviceInfo deviceInfo = rtAudio->getDeviceInfo(device); | |||
std::vector<int> sampleRates(deviceInfo.sampleRates.begin(), deviceInfo.sampleRates.end()); | |||
return sampleRates; | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to query RtAudio device: %s", e.what()); | |||
} | |||
} | |||
if (driver == DRIVER_BRIDGE) { | |||
return {44100, 48000, 88200, 96000, 176400, 192000}; | |||
} | |||
return {}; | |||
} | |||
json_t *AudioIO::toJson() { | |||
json_t *rootJ = json_object(); | |||
json_object_set_new(rootJ, "driver", json_integer(driver)); | |||
std::string deviceName = getDeviceName(device); | |||
json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str())); | |||
json_object_set_new(rootJ, "sampleRate", json_integer(sampleRate)); | |||
json_object_set_new(rootJ, "blockSize", json_integer(blockSize)); | |||
return rootJ; | |||
} | |||
void AudioIO::fromJson(json_t *rootJ) { | |||
json_t *driverJ = json_object_get(rootJ, "driver"); | |||
if (driverJ) | |||
setDriver(json_number_value(driverJ)); | |||
json_t *deviceNameJ = json_object_get(rootJ, "deviceName"); | |||
if (deviceNameJ) { | |||
std::string deviceName = json_string_value(deviceNameJ); | |||
// Search for device ID with equal name | |||
for (int device = 0; device < getDeviceCount(); device++) { | |||
if (getDeviceName(device) == deviceName) { | |||
this->device = device; | |||
break; | |||
} | |||
} | |||
} | |||
json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); | |||
if (sampleRateJ) | |||
sampleRate = json_integer_value(sampleRateJ); | |||
json_t *blockSizeJ = json_object_get(rootJ, "blockSize"); | |||
if (blockSizeJ) | |||
blockSize = json_integer_value(blockSizeJ); | |||
openStream(); | |||
} | |||
} // namespace rack |
@@ -1,8 +1,11 @@ | |||
#include <assert.h> | |||
#include <mutex> | |||
#include <chrono> | |||
#include <thread> | |||
#include <algorithm> | |||
#include <mutex> | |||
#include <condition_variable> | |||
#include "core.hpp" | |||
#include "audio.hpp" | |||
#include "dsp/samplerate.hpp" | |||
#include "dsp/ringbuffer.hpp" | |||
@@ -12,521 +15,176 @@ | |||
#pragma GCC diagnostic pop | |||
#define MAX_OUTPUTS 8 | |||
#define MAX_INPUTS 8 | |||
static auto audioTimeout = std::chrono::milliseconds(100); | |||
using namespace rack; | |||
struct AudioInterfaceIO : AudioIO { | |||
std::mutex engineMutex; | |||
std::condition_variable engineCv; | |||
std::mutex audioMutex; | |||
std::condition_variable audioCv; | |||
// Audio thread produces, engine thread consumes | |||
DoubleRingBuffer<Frame<MAX_INPUTS>, (1<<15)> inputBuffer; | |||
// Audio thread consumes, engine thread produces | |||
DoubleRingBuffer<Frame<MAX_OUTPUTS>, (1<<15)> outputBuffer; | |||
AudioInterfaceIO() { | |||
maxOutputs = MAX_OUTPUTS; | |||
maxInputs = MAX_INPUTS; | |||
} | |||
~AudioInterfaceIO() { | |||
closeStream(); | |||
} | |||
void processStream(const float *input, float *output, int length) override { | |||
if (numInputs > 0) { | |||
// TODO Do we need to wait on the input to be consumed here? | |||
for (int i = 0; i < length; i++) { | |||
if (inputBuffer.full()) | |||
break; | |||
Frame<MAX_INPUTS> f; | |||
memset(&f, 0, sizeof(f)); | |||
memcpy(&f, &input[numInputs * i], numInputs * sizeof(float)); | |||
inputBuffer.push(f); | |||
} | |||
} | |||
if (numOutputs > 0) { | |||
std::unique_lock<std::mutex> lock(audioMutex); | |||
auto cond = [&] { | |||
return outputBuffer.size() >= length; | |||
}; | |||
if (audioCv.wait_for(lock, audioTimeout, cond)) { | |||
// Consume audio block | |||
for (int i = 0; i < length; i++) { | |||
Frame<MAX_OUTPUTS> f = outputBuffer.shift(); | |||
memcpy(&output[numOutputs * i], &f, numOutputs * sizeof(float)); | |||
} | |||
} | |||
else { | |||
// Timed out, fill output with zeros | |||
memset(output, 0, length * numOutputs * sizeof(float)); | |||
} | |||
} | |||
// Notify engine when finished processing | |||
engineCv.notify_all(); | |||
} | |||
void onCloseStream() override { | |||
inputBuffer.clear(); | |||
outputBuffer.clear(); | |||
} | |||
}; | |||
struct AudioInterface : Module { | |||
enum ParamIds { | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
AUDIO1_INPUT, | |||
NUM_INPUTS = AUDIO1_INPUT + 8 | |||
ENUMS(AUDIO_INPUT, MAX_INPUTS), | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
AUDIO1_OUTPUT, | |||
NUM_OUTPUTS = AUDIO1_OUTPUT + 8 | |||
ENUMS(AUDIO_OUTPUT, MAX_OUTPUTS), | |||
NUM_OUTPUTS | |||
}; | |||
RtAudio *stream = NULL; | |||
// Stream properties | |||
int device = -1; | |||
float sampleRate = 44100.0; | |||
int blockSize = 256; | |||
int numOutputs = 0; | |||
int numInputs = 0; | |||
AudioInterfaceIO audioIO; | |||
SampleRateConverter<8> inputSrc; | |||
SampleRateConverter<8> outputSrc; | |||
SampleRateConverter<MAX_INPUTS> inputSrc; | |||
SampleRateConverter<MAX_OUTPUTS> outputSrc; | |||
// in rack's sample rate | |||
DoubleRingBuffer<Frame<8>, 16> inputBuffer; | |||
DoubleRingBuffer<Frame<8>, (1<<15)> outputBuffer; | |||
// in device's sample rate | |||
DoubleRingBuffer<Frame<8>, (1<<15)> inputSrcBuffer; | |||
DoubleRingBuffer<Frame<MAX_INPUTS>, 16> inputBuffer; | |||
DoubleRingBuffer<Frame<MAX_OUTPUTS>, 16> outputBuffer; | |||
AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { | |||
setDriver(RtAudio::UNSPECIFIED); | |||
} | |||
~AudioInterface() { | |||
closeStream(); | |||
} | |||
void step() override; | |||
void stepStream(const float *input, float *output, int numFrames); | |||
int getDeviceCount(); | |||
std::string getDeviceName(int device); | |||
void openStream(); | |||
void closeStream(); | |||
void setDriver(int driver) { | |||
closeStream(); | |||
if (stream) | |||
delete stream; | |||
stream = new RtAudio((RtAudio::Api) driver); | |||
} | |||
int getDriver() { | |||
if (!stream) | |||
return RtAudio::UNSPECIFIED; | |||
return stream->getCurrentApi(); | |||
} | |||
std::vector<int> getAvailableDrivers() { | |||
std::vector<RtAudio::Api> apis; | |||
RtAudio::getCompiledApi(apis); | |||
std::vector<int> drivers; | |||
for (RtAudio::Api api : apis) | |||
drivers.push_back(api); | |||
return drivers; | |||
} | |||
std::string getDriverName(int driver) { | |||
switch (driver) { | |||
case RtAudio::UNSPECIFIED: return "Unspecified"; | |||
case RtAudio::LINUX_ALSA: return "ALSA"; | |||
case RtAudio::LINUX_PULSE: return "PulseAudio"; | |||
case RtAudio::LINUX_OSS: return "OSS"; | |||
case RtAudio::UNIX_JACK: return "JACK"; | |||
case RtAudio::MACOSX_CORE: return "Core Audio"; | |||
case RtAudio::WINDOWS_WASAPI: return "WASAPI"; | |||
case RtAudio::WINDOWS_ASIO: return "ASIO"; | |||
case RtAudio::WINDOWS_DS: return "DirectSound"; | |||
case RtAudio::RTAUDIO_DUMMY: return "Dummy"; | |||
default: return "Unknown"; | |||
} | |||
} | |||
std::vector<float> getSampleRates(); | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
json_object_set_new(rootJ, "driver", json_integer(getDriver())); | |||
json_object_set_new(rootJ, "device", json_integer(device)); | |||
json_object_set_new(rootJ, "sampleRate", json_real(sampleRate)); | |||
json_object_set_new(rootJ, "blockSize", json_integer(blockSize)); | |||
json_object_set_new(rootJ, "audio", audioIO.toJson()); | |||
return rootJ; | |||
} | |||
void fromJson(json_t *rootJ) override { | |||
json_t *driverJ = json_object_get(rootJ, "driver"); | |||
if (driverJ) | |||
setDriver(json_number_value(driverJ)); | |||
json_t *deviceJ = json_object_get(rootJ, "device"); | |||
if (deviceJ) | |||
device = json_number_value(deviceJ); | |||
json_t *sampleRateJ = json_object_get(rootJ, "sampleRate"); | |||
if (sampleRateJ) | |||
sampleRate = json_number_value(sampleRateJ); | |||
json_t *blockSizeJ = json_object_get(rootJ, "blockSize"); | |||
if (blockSizeJ) | |||
blockSize = json_integer_value(blockSizeJ); | |||
openStream(); | |||
json_t *audioJ = json_object_get(rootJ, "audio"); | |||
audioIO.fromJson(audioJ); | |||
} | |||
void reset() override { | |||
closeStream(); | |||
void onReset() override { | |||
audioIO.closeStream(); | |||
} | |||
}; | |||
#define TIMED_SLEEP_LOCK(_cond, _spinTime, _totalTime) { \ | |||
auto startTime = std::chrono::high_resolution_clock::now(); \ | |||
while (!(_cond)) { \ | |||
std::this_thread::sleep_for(std::chrono::duration<float>(_spinTime)); \ | |||
auto currTime = std::chrono::high_resolution_clock::now(); \ | |||
float totalTime = std::chrono::duration<float>(currTime - startTime).count(); \ | |||
if (totalTime > (_totalTime)) \ | |||
break; \ | |||
} \ | |||
} | |||
void AudioInterface::step() { | |||
// debug("inputBuffer %d inputSrcBuffer %d outputBuffer %d", inputBuffer.size(), inputSrcBuffer.size(), outputBuffer.size()); | |||
// Read/write stream if we have enough input, OR the output buffer is empty if we have no input | |||
if (numOutputs > 0) { | |||
TIMED_SLEEP_LOCK(inputSrcBuffer.size() < blockSize, 100e-6, 0.2); | |||
} | |||
else if (numInputs > 0) { | |||
TIMED_SLEEP_LOCK(!outputBuffer.empty(), 100e-6, 0.2); | |||
} | |||
// Get input and pass it through the sample rate converter | |||
if (numOutputs > 0) { | |||
if (!inputBuffer.full()) { | |||
Frame<8> f; | |||
for (int i = 0; i < 8; i++) { | |||
f.samples[i] = inputs[AUDIO1_INPUT + i].value / 10.0; | |||
} | |||
inputBuffer.push(f); | |||
Frame<MAX_INPUTS> inputFrame; | |||
memset(&inputFrame, 0, sizeof(inputFrame)); | |||
if (audioIO.numInputs > 0) { | |||
if (inputBuffer.empty()) { | |||
inputSrc.setRates(audioIO.sampleRate, engineGetSampleRate()); | |||
int inLen = audioIO.inputBuffer.size(); | |||
int outLen = inputBuffer.capacity(); | |||
inputSrc.process(audioIO.inputBuffer.startData(), &inLen, inputBuffer.endData(), &outLen); | |||
audioIO.inputBuffer.startIncr(inLen); | |||
inputBuffer.endIncr(outLen); | |||
} | |||
// Once full, sample rate convert the input | |||
// inputBuffer -> SRC -> inputSrcBuffer | |||
if (inputBuffer.full()) { | |||
inputSrc.setRatio(sampleRate / engineGetSampleRate()); | |||
int inLen = inputBuffer.size(); | |||
int outLen = inputSrcBuffer.capacity(); | |||
inputSrc.process(inputBuffer.startData(), &inLen, inputSrcBuffer.endData(), &outLen); | |||
inputBuffer.startIncr(inLen); | |||
inputSrcBuffer.endIncr(outLen); | |||
} | |||
} | |||
// Set output | |||
if (!outputBuffer.empty()) { | |||
Frame<8> f = outputBuffer.shift(); | |||
for (int i = 0; i < 8; i++) { | |||
outputs[AUDIO1_OUTPUT + i].value = 10.0 * f.samples[i]; | |||
} | |||
} | |||
} | |||
void AudioInterface::stepStream(const float *input, float *output, int numFrames) { | |||
if (gPaused) { | |||
memset(output, 0, sizeof(float) * numOutputs * numFrames); | |||
return; | |||
} | |||
if (numOutputs > 0) { | |||
// Wait for enough input before proceeding | |||
TIMED_SLEEP_LOCK(inputSrcBuffer.size() >= numFrames, 100e-6, 0.2); | |||
if (!inputBuffer.empty()) { | |||
inputFrame = inputBuffer.shift(); | |||
} | |||
else if (numInputs > 0) { | |||
TIMED_SLEEP_LOCK(outputBuffer.empty(), 100e-6, 0.2); | |||
for (int i = 0; i < MAX_INPUTS; i++) { | |||
outputs[AUDIO_OUTPUT + i].value = 10.0 * inputFrame.samples[i]; | |||
} | |||
// input stream -> output buffer | |||
if (numInputs > 0) { | |||
Frame<8> inputFrames[numFrames]; | |||
for (int i = 0; i < numFrames; i++) { | |||
for (int c = 0; c < 8; c++) { | |||
inputFrames[i].samples[c] = (c < numInputs) ? input[i*numInputs + c] : 0.0; | |||
if (audioIO.numOutputs > 0) { | |||
// Get and push output SRC frame | |||
if (!outputBuffer.full()) { | |||
Frame<MAX_OUTPUTS> f; | |||
for (int i = 0; i < audioIO.numOutputs; i++) { | |||
f.samples[i] = inputs[AUDIO_INPUT + i].value / 10.0; | |||
} | |||
outputBuffer.push(f); | |||
} | |||
// Pass output through sample rate converter | |||
outputSrc.setRatio(engineGetSampleRate() / sampleRate); | |||
int inLen = numFrames; | |||
int outLen = outputBuffer.capacity(); | |||
outputSrc.process(inputFrames, &inLen, outputBuffer.endData(), &outLen); | |||
outputBuffer.endIncr(outLen); | |||
} | |||
// input buffer -> output stream | |||
if (numOutputs > 0) { | |||
for (int i = 0; i < numFrames; i++) { | |||
Frame<8> f; | |||
if (inputSrcBuffer.empty()) { | |||
memset(&f, 0, sizeof(f)); | |||
if (outputBuffer.full()) { | |||
// Wait until outputs are needed | |||
std::unique_lock<std::mutex> lock(audioIO.engineMutex); | |||
auto cond = [&] { | |||
return audioIO.outputBuffer.size() < audioIO.blockSize; | |||
}; | |||
if (audioIO.engineCv.wait_for(lock, audioTimeout, cond)) { | |||
// Push converted output | |||
outputSrc.setRates(engineGetSampleRate(), audioIO.sampleRate); | |||
int inLen = outputBuffer.size(); | |||
int outLen = audioIO.outputBuffer.capacity(); | |||
outputSrc.process(outputBuffer.startData(), &inLen, audioIO.outputBuffer.endData(), &outLen); | |||
outputBuffer.startIncr(inLen); | |||
audioIO.outputBuffer.endIncr(outLen); | |||
} | |||
else { | |||
f = inputSrcBuffer.shift(); | |||
} | |||
for (int c = 0; c < numOutputs; c++) { | |||
output[i*numOutputs + c] = clampf(f.samples[c], -1.0, 1.0); | |||
// Give up on pushing output | |||
} | |||
} | |||
} | |||
} | |||
int AudioInterface::getDeviceCount() { | |||
if (!stream) | |||
return 0; | |||
return stream->getDeviceCount(); | |||
} | |||
std::string AudioInterface::getDeviceName(int device) { | |||
if (!stream || device < 0) | |||
return ""; | |||
try { | |||
RtAudio::DeviceInfo deviceInfo = stream->getDeviceInfo(device); | |||
return stringf("%s (%d in, %d out)", deviceInfo.name.c_str(), deviceInfo.inputChannels, deviceInfo.outputChannels); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to query audio device: %s", e.what()); | |||
return ""; | |||
} | |||
} | |||
static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData) { | |||
AudioInterface *audioInterface = (AudioInterface *) userData; | |||
assert(audioInterface); | |||
audioInterface->stepStream((const float *) inputBuffer, (float *) outputBuffer, nFrames); | |||
return 0; | |||
audioIO.audioCv.notify_all(); | |||
} | |||
void AudioInterface::openStream() { | |||
int device = this->device; | |||
closeStream(); | |||
if (!stream) | |||
return; | |||
// Open new device | |||
if (device >= 0) { | |||
RtAudio::DeviceInfo deviceInfo; | |||
try { | |||
deviceInfo = stream->getDeviceInfo(device); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to query audio device: %s", e.what()); | |||
return; | |||
} | |||
numOutputs = mini(deviceInfo.outputChannels, 8); | |||
numInputs = mini(deviceInfo.inputChannels, 8); | |||
if (numOutputs == 0 && numInputs == 0) { | |||
warn("Audio device %d has 0 inputs and 0 outputs"); | |||
return; | |||
} | |||
RtAudio::StreamParameters outParameters; | |||
outParameters.deviceId = device; | |||
outParameters.nChannels = numOutputs; | |||
RtAudio::StreamParameters inParameters; | |||
inParameters.deviceId = device; | |||
inParameters.nChannels = numInputs; | |||
RtAudio::StreamOptions options; | |||
// options.flags |= RTAUDIO_SCHEDULE_REALTIME; | |||
// Find closest sample rate | |||
unsigned int closestSampleRate = 0; | |||
for (unsigned int sr : deviceInfo.sampleRates) { | |||
if (fabsf(sr - sampleRate) < fabsf(closestSampleRate - sampleRate)) { | |||
closestSampleRate = sr; | |||
} | |||
} | |||
try { | |||
// Don't use stream parameters if 0 input or output channels | |||
debug("Opening audio stream %d", device); | |||
stream->openStream( | |||
numOutputs == 0 ? NULL : &outParameters, | |||
numInputs == 0 ? NULL : &inParameters, | |||
RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to open audio stream: %s", e.what()); | |||
return; | |||
} | |||
try { | |||
debug("Starting audio stream %d", device); | |||
stream->startStream(); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to start audio stream: %s", e.what()); | |||
return; | |||
} | |||
// Update sample rate because this may have changed | |||
this->sampleRate = stream->getStreamSampleRate(); | |||
this->device = device; | |||
} | |||
} | |||
void AudioInterface::closeStream() { | |||
if (stream) { | |||
if (stream->isStreamRunning()) { | |||
debug("Aborting audio stream %d", device); | |||
try { | |||
stream->abortStream(); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to abort stream %s", e.what()); | |||
} | |||
} | |||
if (stream->isStreamOpen()) { | |||
debug("Closing audio stream %d", device); | |||
try { | |||
stream->closeStream(); | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to close stream %s", e.what()); | |||
} | |||
} | |||
} | |||
// Reset stream settings | |||
device = -1; | |||
numOutputs = 0; | |||
numInputs = 0; | |||
// Clear buffers | |||
inputBuffer.clear(); | |||
outputBuffer.clear(); | |||
inputSrcBuffer.clear(); | |||
inputSrc.reset(); | |||
outputSrc.reset(); | |||
} | |||
std::vector<float> AudioInterface::getSampleRates() { | |||
std::vector<float> allowedSampleRates = {44100, 48000, 88200, 96000, 176400, 192000}; | |||
if (!stream || device < 0) | |||
return allowedSampleRates; | |||
try { | |||
std::vector<float> sampleRates; | |||
RtAudio::DeviceInfo deviceInfo = stream->getDeviceInfo(device); | |||
for (int sr : deviceInfo.sampleRates) { | |||
float sampleRate = sr; | |||
auto allowedIt = std::find(allowedSampleRates.begin(), allowedSampleRates.end(), sampleRate); | |||
if (allowedIt != allowedSampleRates.end()) { | |||
sampleRates.push_back(sampleRate); | |||
} | |||
} | |||
return sampleRates; | |||
} | |||
catch (RtAudioError &e) { | |||
warn("Failed to query audio device: %s", e.what()); | |||
return {}; | |||
} | |||
} | |||
struct AudioDriverItem : MenuItem { | |||
AudioInterface *audioInterface; | |||
int driver; | |||
void onAction(EventAction &e) override { | |||
audioInterface->setDriver(driver); | |||
} | |||
}; | |||
struct AudioDriverChoice : ChoiceButton { | |||
AudioInterface *audioInterface; | |||
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; | |||
for (int driver : audioInterface->getAvailableDrivers()) { | |||
AudioDriverItem *audioItem = new AudioDriverItem(); | |||
audioItem->audioInterface = audioInterface; | |||
audioItem->driver = driver; | |||
audioItem->text = audioInterface->getDriverName(driver); | |||
menu->pushChild(audioItem); | |||
} | |||
} | |||
void step() override { | |||
text = audioInterface->getDriverName(audioInterface->getDriver()); | |||
} | |||
}; | |||
struct AudioDeviceItem : MenuItem { | |||
AudioInterface *audioInterface; | |||
int device; | |||
void onAction(EventAction &e) override { | |||
audioInterface->device = device; | |||
audioInterface->openStream(); | |||
} | |||
}; | |||
struct AudioDeviceChoice : ChoiceButton { | |||
int lastDeviceId = -1; | |||
AudioInterface *audioInterface; | |||
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; | |||
int deviceCount = audioInterface->getDeviceCount(); | |||
{ | |||
AudioDeviceItem *audioItem = new AudioDeviceItem(); | |||
audioItem->audioInterface = audioInterface; | |||
audioItem->device = -1; | |||
audioItem->text = "No device"; | |||
menu->pushChild(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); | |||
} | |||
} | |||
void step() override { | |||
if (lastDeviceId != audioInterface->device) { | |||
std::string name = audioInterface->getDeviceName(audioInterface->device); | |||
text = ellipsize(name, 24); | |||
lastDeviceId = audioInterface->device; | |||
} | |||
} | |||
}; | |||
struct SampleRateItem : MenuItem { | |||
AudioInterface *audioInterface; | |||
float sampleRate; | |||
void onAction(EventAction &e) override { | |||
audioInterface->sampleRate = sampleRate; | |||
audioInterface->openStream(); | |||
} | |||
}; | |||
struct SampleRateChoice : ChoiceButton { | |||
AudioInterface *audioInterface; | |||
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; | |||
for (float sampleRate : audioInterface->getSampleRates()) { | |||
SampleRateItem *item = new SampleRateItem(); | |||
item->audioInterface = audioInterface; | |||
item->sampleRate = sampleRate; | |||
item->text = stringf("%.0f Hz", sampleRate); | |||
menu->pushChild(item); | |||
} | |||
} | |||
void step() override { | |||
this->text = stringf("%.0f Hz", audioInterface->sampleRate); | |||
} | |||
}; | |||
struct BlockSizeItem : MenuItem { | |||
AudioInterface *audioInterface; | |||
int blockSize; | |||
void onAction(EventAction &e) override { | |||
audioInterface->blockSize = blockSize; | |||
audioInterface->openStream(); | |||
} | |||
}; | |||
struct BlockSizeChoice : ChoiceButton { | |||
AudioInterface *audioInterface; | |||
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; | |||
const int blockSizes[] = {64, 128, 256, 512, 1024, 2048, 4096}; | |||
int blockSizesLen = sizeof(blockSizes) / sizeof(blockSizes[0]); | |||
for (int i = 0; i < blockSizesLen; i++) { | |||
BlockSizeItem *item = new BlockSizeItem(); | |||
item->audioInterface = audioInterface; | |||
item->blockSize = blockSizes[i]; | |||
item->text = stringf("%d", blockSizes[i]); | |||
menu->pushChild(item); | |||
} | |||
} | |||
void step() override { | |||
this->text = stringf("%d", audioInterface->blockSize); | |||
} | |||
}; | |||
AudioInterfaceWidget::AudioInterfaceWidget() { | |||
AudioInterface *module = new AudioInterface(); | |||
@@ -545,73 +203,13 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||
Vec margin = Vec(5, 2); | |||
float labelHeight = 15; | |||
float yPos = margin.y; | |||
float yPos = margin.y + 100; | |||
float xPos; | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin.x, yPos); | |||
label->text = "Audio driver"; | |||
addChild(label); | |||
yPos += labelHeight + margin.y; | |||
AudioDriverChoice *choice = new AudioDriverChoice(); | |||
choice->audioInterface = module; | |||
choice->box.pos = Vec(margin.x, yPos); | |||
choice->box.size.x = box.size.x - 2*margin.x; | |||
addChild(choice); | |||
yPos += choice->box.size.y + margin.y; | |||
} | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin.x, yPos); | |||
label->text = "Audio device"; | |||
addChild(label); | |||
yPos += labelHeight + margin.y; | |||
AudioDeviceChoice *choice = new AudioDeviceChoice(); | |||
choice->audioInterface = module; | |||
choice->box.pos = Vec(margin.x, yPos); | |||
choice->box.size.x = box.size.x - 2*margin.x; | |||
addChild(choice); | |||
yPos += choice->box.size.y + margin.y; | |||
} | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin.x, yPos); | |||
label->text = "Sample rate"; | |||
addChild(label); | |||
yPos += labelHeight + margin.y; | |||
SampleRateChoice *choice = new SampleRateChoice(); | |||
choice->audioInterface = module; | |||
choice->box.pos = Vec(margin.x, yPos); | |||
choice->box.size.x = box.size.x - 2*margin.x; | |||
addChild(choice); | |||
yPos += choice->box.size.y + margin.y; | |||
} | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin.x, yPos); | |||
label->text = "Block size"; | |||
addChild(label); | |||
yPos += labelHeight + margin.y; | |||
BlockSizeChoice *choice = new BlockSizeChoice(); | |||
choice->audioInterface = module; | |||
choice->box.pos = Vec(margin.x, yPos); | |||
choice->box.size.x = box.size.x - 2*margin.x; | |||
addChild(choice); | |||
yPos += choice->box.size.y + margin.y; | |||
} | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin.x, yPos); | |||
label->text = "Outputs"; | |||
label->text = "Outputs (DACs)"; | |||
addChild(label); | |||
yPos += labelHeight + margin.y; | |||
} | |||
@@ -619,7 +217,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||
yPos += 5; | |||
xPos = 10; | |||
for (int i = 0; i < 4; i++) { | |||
addInput(createInput<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO1_INPUT + i)); | |||
addInput(createInput<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO_INPUT + i)); | |||
Label *label = new Label(); | |||
label->box.pos = Vec(xPos + 4, yPos + 28); | |||
label->text = stringf("%d", i + 1); | |||
@@ -632,7 +230,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||
yPos += 5; | |||
xPos = 10; | |||
for (int i = 4; i < 8; i++) { | |||
addInput(createInput<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO1_INPUT + i)); | |||
addInput(createInput<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO_INPUT + i)); | |||
Label *label = new Label(); | |||
label->box.pos = Vec(xPos + 4, yPos + 28); | |||
label->text = stringf("%d", i + 1); | |||
@@ -645,7 +243,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin.x, yPos); | |||
label->text = "Inputs"; | |||
label->text = "Inputs (ADCs)"; | |||
addChild(label); | |||
yPos += labelHeight + margin.y; | |||
} | |||
@@ -653,7 +251,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||
yPos += 5; | |||
xPos = 10; | |||
for (int i = 0; i < 4; i++) { | |||
addOutput(createOutput<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO1_OUTPUT + i)); | |||
addOutput(createOutput<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO_OUTPUT + i)); | |||
Label *label = new Label(); | |||
label->box.pos = Vec(xPos + 4, yPos + 28); | |||
label->text = stringf("%d", i + 1); | |||
@@ -666,7 +264,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||
yPos += 5; | |||
xPos = 10; | |||
for (int i = 4; i < 8; i++) { | |||
addOutput(createOutput<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO1_OUTPUT + i)); | |||
addOutput(createOutput<PJ3410Port>(Vec(xPos, yPos), module, AudioInterface::AUDIO_OUTPUT + i)); | |||
Label *label = new Label(); | |||
label->box.pos = Vec(xPos + 4, yPos + 28); | |||
label->text = stringf("%d", i + 1); | |||
@@ -675,4 +273,8 @@ AudioInterfaceWidget::AudioInterfaceWidget() { | |||
xPos += 37 + margin.x; | |||
} | |||
yPos += 35 + margin.y; | |||
AudioWidget *audioWidget = construct<USB_B_AudioWidget>(); | |||
audioWidget->audioIO = &module->audioIO; | |||
addChild(audioWidget); | |||
} |
@@ -1,44 +0,0 @@ | |||
#include "core.hpp" | |||
using namespace rack; | |||
struct Bridge : Module { | |||
enum ParamIds { | |||
NUM_PARAMS | |||
}; | |||
enum InputIds { | |||
NUM_INPUTS | |||
}; | |||
enum OutputIds { | |||
NUM_OUTPUTS | |||
}; | |||
Bridge() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) { | |||
} | |||
~Bridge() { | |||
} | |||
void step() override; | |||
}; | |||
void Bridge::step() { | |||
} | |||
BridgeWidget::BridgeWidget() { | |||
Bridge *module = new Bridge(); | |||
setModule(module); | |||
box.size = Vec(15*8, 380); | |||
{ | |||
Panel *panel = new LightPanel(); | |||
panel->box.size = box.size; | |||
addChild(panel); | |||
} | |||
addChild(createScrew<ScrewSilver>(Vec(15, 0))); | |||
addChild(createScrew<ScrewSilver>(Vec(box.size.x-30, 0))); | |||
addChild(createScrew<ScrewSilver>(Vec(15, 365))); | |||
addChild(createScrew<ScrewSilver>(Vec(box.size.x-30, 365))); | |||
} |
@@ -1,9 +1,10 @@ | |||
#if 0 | |||
#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 +82,7 @@ struct MIDICCToCVInterface : MidiIO, Module { | |||
} | |||
} | |||
void reset() override { | |||
void onReset() override { | |||
resetMidi(); | |||
} | |||
@@ -91,11 +92,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 +118,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 +148,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 +158,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 +228,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 +314,8 @@ MIDICCToCVWidget::MIDICCToCVWidget() { | |||
if ((i + 1) % 4 == 0) { | |||
yPos += 47 + margin; | |||
} else { | |||
} | |||
else { | |||
yPos -= labelHeight + margin; | |||
} | |||
} | |||
@@ -319,3 +325,4 @@ void MIDICCToCVWidget::step() { | |||
ModuleWidget::step(); | |||
} | |||
#endif |
@@ -1,10 +1,11 @@ | |||
#if 0 | |||
#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 +72,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 +80,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 +102,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 +185,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 +218,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 +238,7 @@ struct ClockRatioChoice : ChoiceButton { | |||
clockRatioItem->ratio = ratio; | |||
clockRatioItem->clockRatio = clockRatio; | |||
clockRatioItem->text = ratioNames[ratio]; | |||
menu->pushChild(clockRatioItem); | |||
menu->addChild(clockRatioItem); | |||
} | |||
} | |||
@@ -270,7 +272,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 +322,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 +346,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; | |||
@@ -368,3 +370,4 @@ void MIDIClockToCVWidget::step() { | |||
ModuleWidget::step(); | |||
} | |||
#endif |
@@ -1,9 +1,10 @@ | |||
#if 0 | |||
#include <list> | |||
#include <algorithm> | |||
#include "rtmidi/RtMidi.h" | |||
#include "core.hpp" | |||
#include "MidiIO.hpp" | |||
using namespace rack; | |||
@@ -59,7 +60,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 +211,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 +219,7 @@ void MidiChoice::onAction(EventAction &e) { | |||
MidiItem *midiItem = new MidiItem(); | |||
midiItem->midiModule = midiModule; | |||
midiItem->text = deviceNames[i]; | |||
menu->pushChild(midiItem); | |||
menu->addChild(midiItem); | |||
} | |||
} | |||
@@ -245,17 +247,18 @@ 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); | |||
} | |||
} | |||
void ChannelChoice::step() { | |||
text = (midiModule->channel >= 0) ? stringf("%d", midiModule->channel + 1) : "All"; | |||
} | |||
#endif |
@@ -1,10 +1,16 @@ | |||
#if 0 | |||
#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 +22,6 @@ struct MidiMessage { | |||
double timeStamp; | |||
MidiMessage() : bytes(0), timeStamp(0.0) {}; | |||
}; | |||
/** | |||
@@ -131,10 +136,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 +154,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; | |||
} | |||
@@ -197,3 +202,5 @@ struct ChannelChoice : ChoiceButton { | |||
void step() override; | |||
void onAction(EventAction &e) override; | |||
}; | |||
#endif |
@@ -1,21 +1,21 @@ | |||
#include <list> | |||
#include <algorithm> | |||
#include "rtmidi/RtMidi.h" | |||
#include "core.hpp" | |||
#include "MidiIO.hpp" | |||
#include "midi.hpp" | |||
#include "dsp/digital.hpp" | |||
/* | |||
* MIDIToCVInterface converts midi note on/off events, velocity , channel aftertouch, pitch wheel and mod wheel to | |||
* CV | |||
*/ | |||
struct MidiValue { | |||
int val = 0; // Controller value | |||
TransitionSmoother tSmooth; | |||
// TransitionSmoother tSmooth; | |||
bool changed = false; // Value has been changed by midi message (only if it is in sync!) | |||
}; | |||
struct MIDIToCVInterface : MidiIO, Module { | |||
struct MIDIToCVInterface : Module { | |||
enum ParamIds { | |||
RESET_PARAM, | |||
NUM_PARAMS | |||
@@ -37,6 +37,7 @@ struct MIDIToCVInterface : MidiIO, Module { | |||
NUM_LIGHTS | |||
}; | |||
MidiInputQueue midiInput; | |||
std::list<int> notes; | |||
bool pedal = false; | |||
int note = 60; // C4, most modules should use 261.626 Hz | |||
@@ -48,9 +49,9 @@ struct MIDIToCVInterface : MidiIO, Module { | |||
SchmittTrigger resetTrigger; | |||
MIDIToCVInterface() : MidiIO(), Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
MIDIToCVInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS) { | |||
pitchWheel.val = 64; | |||
pitchWheel.tSmooth.set(0, 0); | |||
// pitchWheel.tSmooth.set(0, 0); | |||
} | |||
~MIDIToCVInterface() { | |||
@@ -66,22 +67,22 @@ struct MIDIToCVInterface : MidiIO, Module { | |||
json_t *toJson() override { | |||
json_t *rootJ = json_object(); | |||
addBaseJson(rootJ); | |||
// addBaseJson(rootJ); | |||
return rootJ; | |||
} | |||
void fromJson(json_t *rootJ) override { | |||
baseFromJson(rootJ); | |||
// baseFromJson(rootJ); | |||
} | |||
void reset() override { | |||
resetMidi(); | |||
void onReset() override { | |||
// resetMidi(); | |||
} | |||
void resetMidi() override; | |||
// void resetMidi() override; | |||
}; | |||
/* | |||
void MIDIToCVInterface::resetMidi() { | |||
mod.val = 0; | |||
mod.tSmooth.set(0, 0); | |||
@@ -93,16 +94,17 @@ void MIDIToCVInterface::resetMidi() { | |||
gate = false; | |||
notes.clear(); | |||
} | |||
*/ | |||
void MIDIToCVInterface::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); | |||
} | |||
} | |||
@@ -132,11 +134,8 @@ void MIDIToCVInterface::step() { | |||
} | |||
outputs[PITCHWHEEL_OUTPUT].value = pitchWheel.tSmooth.next(); | |||
/* NOTE: I'll leave out value smoothing for after touch for now. I currently don't | |||
* have an after touch capable device around and I assume it would require different | |||
* smoothing*/ | |||
outputs[CHANNEL_AFTERTOUCH_OUTPUT].value = afterTouch.val / 127.0 * 10.0; | |||
*/ | |||
} | |||
void MIDIToCVInterface::pressNote(int note) { | |||
@@ -171,6 +170,7 @@ void MIDIToCVInterface::releaseNote(int note) { | |||
} | |||
void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
/* | |||
int channel = msg[0] & 0xf; | |||
int status = (msg[0] >> 4) & 0xf; | |||
int data1 = msg[1]; | |||
@@ -220,6 +220,7 @@ void MIDIToCVInterface::processMidi(std::vector<unsigned char> msg) { | |||
afterTouch.changed = true; | |||
break; | |||
} | |||
*/ | |||
} | |||
@@ -253,40 +254,9 @@ MidiToCVWidget::MidiToCVWidget() { | |||
} | |||
addParam(createParam<LEDButton>(Vec(7 * 15, labelHeight), module, MIDIToCVInterface::RESET_PARAM, 0.0, 1.0, 0.0)); | |||
addChild(createLight<SmallLight<RedLight>>(Vec(7 * 15 + 5, labelHeight + 5), module, | |||
MIDIToCVInterface::RESET_LIGHT)); | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin, yPos); | |||
label->text = "MIDI Interface"; | |||
addChild(label); | |||
yPos += labelHeight + margin; | |||
MidiChoice *midiChoice = new MidiChoice(); | |||
midiChoice->midiModule = dynamic_cast<MidiIO *>(module); | |||
midiChoice->box.pos = Vec(margin, yPos); | |||
midiChoice->box.size.x = box.size.x - 10; | |||
addChild(midiChoice); | |||
yPos += midiChoice->box.size.y + margin; | |||
} | |||
addChild(createLight<SmallLight<RedLight>>(Vec(7 * 15 + 5, labelHeight + 5), module, MIDIToCVInterface::RESET_LIGHT)); | |||
{ | |||
Label *label = new Label(); | |||
label->box.pos = Vec(margin, yPos); | |||
label->text = "Channel"; | |||
addChild(label); | |||
yPos += labelHeight + margin; | |||
ChannelChoice *channelChoice = new ChannelChoice(); | |||
channelChoice->midiModule = dynamic_cast<MidiIO *>(module); | |||
channelChoice->box.pos = Vec(margin, yPos); | |||
channelChoice->box.size.x = box.size.x - 10; | |||
addChild(channelChoice); | |||
yPos += channelChoice->box.size.y + margin + 15; | |||
} | |||
std::string labels[MIDIToCVInterface::NUM_OUTPUTS] = {"1V/oct", "Gate", "Velocity", "Mod Wheel", "Pitch Wheel", | |||
"Aftertouch"}; | |||
std::string labels[MIDIToCVInterface::NUM_OUTPUTS] = {"1V/oct", "Gate", "Velocity", "Mod Wheel", "Pitch Wheel", "Aftertouch"}; | |||
for (int i = 0; i < MIDIToCVInterface::NUM_OUTPUTS; i++) { | |||
Label *label = new Label(); | |||
@@ -298,9 +268,8 @@ MidiToCVWidget::MidiToCVWidget() { | |||
yPos += yGap + margin; | |||
} | |||
} | |||
void MidiToCVWidget::step() { | |||
ModuleWidget::step(); | |||
MidiWidget *midiWidget = construct<MIDI_DIN_MidiWidget>(); | |||
midiWidget->midiIO = &module->midiInput; | |||
addChild(midiWidget); | |||
} |
@@ -1,10 +1,11 @@ | |||
#if 0 | |||
#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 +64,7 @@ struct MIDITriggerToCVInterface : MidiIO, Module { | |||
} | |||
} | |||
void reset() override { | |||
void onReset() override { | |||
resetMidi(); | |||
} | |||
}; | |||
@@ -75,9 +76,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 +169,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 +273,8 @@ MIDITriggerToCVWidget::MIDITriggerToCVWidget() { | |||
if ((i + 1) % 4 == 0) { | |||
yPos += 47 + margin; | |||
} else { | |||
} | |||
else { | |||
yPos -= labelHeight + margin; | |||
} | |||
} | |||
@@ -280,3 +283,4 @@ MIDITriggerToCVWidget::MIDITriggerToCVWidget() { | |||
void MIDITriggerToCVWidget::step() { | |||
ModuleWidget::step(); | |||
} | |||
#endif |
@@ -1,15 +1,17 @@ | |||
#if 0 | |||
#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 +74,7 @@ struct QuadMIDIToCVInterface : MidiIO, Module { | |||
baseFromJson(rootJ); | |||
} | |||
void reset() override { | |||
void onReset() override { | |||
resetMidi(); | |||
} | |||
@@ -98,16 +100,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 +137,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 +209,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 +242,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 +265,7 @@ struct ModeItem : MenuItem { | |||
int mode; | |||
QuadMIDIToCVInterface *module; | |||
void onAction(EventAction &e) { | |||
void onAction(EventAction &e) override { | |||
module->setMode(mode); | |||
} | |||
}; | |||
@@ -264,7 +275,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 +285,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 +324,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(); | |||
@@ -406,3 +417,4 @@ void QuadMidiToCVWidget::step() { | |||
ModuleWidget::step(); | |||
} | |||
#endif |
@@ -8,14 +8,13 @@ void init(rack::Plugin *p) { | |||
#endif | |||
p->addModel(createModel<AudioInterfaceWidget>("Core", "AudioInterface", "Audio Interface", EXTERNAL_TAG)); | |||
p->addModel(createModel<MidiToCVWidget>("Core", "MIDIToCVInterface", "MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
p->addModel(createModel<MIDICCToCVWidget>("Core", "MIDICCToCVInterface", "MIDI CC-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
p->addModel(createModel<MIDIClockToCVWidget>("Core", "MIDIClockToCVInterface", "MIDI Clock-to-CV Interface", MIDI_TAG, EXTERNAL_TAG, CLOCK_TAG)); | |||
p->addModel(createModel<MIDITriggerToCVWidget>("Core", "MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
p->addModel(createModel<QuadMidiToCVWidget>("Core", "QuadMIDIToCVInterface", "Quad MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG, QUAD_TAG)); | |||
p->addModel(createModel<MidiToCVWidget>("Core", "MIDIToCVInterface", "MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
// p->addModel(createModel<MIDICCToCVWidget>("Core", "MIDICCToCVInterface", "MIDI CC-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
// p->addModel(createModel<MIDIClockToCVWidget>("Core", "MIDIClockToCVInterface", "MIDI Clock-to-CV Interface", MIDI_TAG, EXTERNAL_TAG, CLOCK_TAG)); | |||
// p->addModel(createModel<MIDITriggerToCVWidget>("Core", "MIDITriggerToCVInterface", "MIDI Trigger-to-CV Interface", MIDI_TAG, EXTERNAL_TAG)); | |||
// p->addModel(createModel<QuadMidiToCVWidget>("Core", "QuadMIDIToCVInterface", "Quad MIDI-to-CV Interface", MIDI_TAG, EXTERNAL_TAG, QUAD_TAG)); | |||
// p->addModel(createModel<BridgeWidget>("Core", "Bridge", "Bridge")); | |||
p->addModel(createModel<BlankWidget>("Core", "Blank", "Blank", BLANK_TAG)); | |||
p->addModel(createModel<NotesWidget>("Core", "Notes", "Notes", BLANK_TAG)); | |||
} |
@@ -14,29 +14,27 @@ struct AudioInterfaceWidget : ModuleWidget { | |||
struct MidiToCVWidget : ModuleWidget { | |||
MidiToCVWidget(); | |||
void step() override; | |||
}; | |||
struct MIDICCToCVWidget : ModuleWidget { | |||
MIDICCToCVWidget(); | |||
void step() override; | |||
}; | |||
struct MIDIClockToCVWidget : ModuleWidget { | |||
MIDIClockToCVWidget(); | |||
void step() override; | |||
}; | |||
// struct MIDICCToCVWidget : ModuleWidget { | |||
// MIDICCToCVWidget(); | |||
// void step() override; | |||
// }; | |||
struct MIDITriggerToCVWidget : ModuleWidget { | |||
MIDITriggerToCVWidget(); | |||
void step() override; | |||
}; | |||
// struct MIDIClockToCVWidget : ModuleWidget { | |||
// MIDIClockToCVWidget(); | |||
// void step() override; | |||
// }; | |||
struct QuadMidiToCVWidget : ModuleWidget { | |||
QuadMidiToCVWidget(); | |||
void step() override; | |||
}; | |||
// struct MIDITriggerToCVWidget : ModuleWidget { | |||
// MIDITriggerToCVWidget(); | |||
// void step() override; | |||
// }; | |||
// struct QuadMidiToCVWidget : ModuleWidget { | |||
// QuadMidiToCVWidget(); | |||
// void step() override; | |||
// }; | |||
struct BridgeWidget : ModuleWidget { | |||
BridgeWidget(); | |||
@@ -7,6 +7,7 @@ | |||
#include <chrono> | |||
#include <thread> | |||
#include <xmmintrin.h> | |||
#include <pmmintrin.h> | |||
#include "engine.hpp" | |||
#include "util.hpp" | |||
@@ -113,9 +114,10 @@ static void engineStep() { | |||
} | |||
static void engineRun() { | |||
// Set CPU to denormals-are-zero mode | |||
// http://carlh.net/plugins/denormals.php | |||
// Set CPU to flush-to-zero (FTZ) and denormals-are-zero (DAZ) mode | |||
// https://software.intel.com/en-us/node/682949 | |||
_MM_SET_FLUSH_ZERO_MODE(_MM_FLUSH_ZERO_ON); | |||
_MM_SET_DENORMALS_ZERO_MODE(_MM_DENORMALS_ZERO_ON); | |||
// Every time the engine waits and locks a mutex, it steps this many frames | |||
const int mutexSteps = 64; | |||
@@ -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 | |||
@@ -25,13 +27,15 @@ | |||
#include <ApplicationServices/ApplicationServices.h> | |||
#endif | |||
namespace rack { | |||
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 +44,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 +146,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 +316,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 +340,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 +362,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 +395,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(); | |||
@@ -404,10 +433,7 @@ void guiRun() { | |||
mouseButtonStickyPop(); | |||
// Set window title | |||
std::string windowTitle = gApplicationName; | |||
if (!gApplicationVersion.empty()) { | |||
windowTitle += " v" + gApplicationVersion; | |||
} | |||
std::string windowTitle = gApplicationName + " v" + gApplicationVersion; | |||
if (!gRackWidget->lastPath.empty()) { | |||
windowTitle += " - "; | |||
windowTitle += extractFilename(gRackWidget->lastPath); | |||
@@ -417,18 +443,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(); | |||
@@ -5,6 +5,7 @@ | |||
#include "settings.hpp" | |||
#include "asset.hpp" | |||
#include <unistd.h> | |||
#include "../ext/osdialog/osdialog.h" | |||
using namespace rack; | |||
@@ -12,14 +13,12 @@ using namespace rack; | |||
int main(int argc, char* argv[]) { | |||
randomSeedTime(); | |||
#ifdef VERSION | |||
#ifdef RELEASE | |||
std::string logFilename = assetLocal("log.txt"); | |||
gLogFile = fopen(logFilename.c_str(), "w"); | |||
#endif | |||
if (!gApplicationVersion.empty()) { | |||
info("Rack v%s", gApplicationVersion.c_str()); | |||
} | |||
info("Rack v%s", gApplicationVersion.c_str()); | |||
{ | |||
char *cwd = getcwd(NULL, 0); | |||
@@ -35,21 +34,32 @@ int main(int argc, char* argv[]) { | |||
engineInit(); | |||
guiInit(); | |||
sceneInit(); | |||
gRackWidget->loadPatch(assetLocal("autosave.vcv")); | |||
settingsLoad(assetLocal("settings.json")); | |||
// To prevent launch crashes, if Rack crashes between now and 15 seconds from now, the "skipAutosaveOnLaunch" property will remain in settings.json, so that in the next launch, the broken autosave will not be loaded. | |||
bool oldSkipAutosaveOnLaunch = skipAutosaveOnLaunch; | |||
skipAutosaveOnLaunch = true; | |||
settingsSave(assetLocal("settings.json")); | |||
skipAutosaveOnLaunch = false; | |||
if (oldSkipAutosaveOnLaunch && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "Rack has recovered from a crash, likely caused by a faulty module in your patch. Would you like to clear your patch and start over?")) { | |||
// Do nothing. Empty patch is already loaded. | |||
} | |||
else { | |||
gRackWidget->loadPatch(assetLocal("autosave.vcv")); | |||
} | |||
engineStart(); | |||
guiRun(); | |||
engineStop(); | |||
settingsSave(assetLocal("settings.json")); | |||
gRackWidget->savePatch(assetLocal("autosave.vcv")); | |||
settingsSave(assetLocal("settings.json")); | |||
sceneDestroy(); | |||
guiDestroy(); | |||
engineDestroy(); | |||
pluginDestroy(); | |||
#ifdef VERSION | |||
#ifdef RELEASE | |||
fclose(gLogFile); | |||
#endif | |||
@@ -0,0 +1,105 @@ | |||
#include "midi.hpp" | |||
namespace rack { | |||
//////////////////// | |||
// MidiIO | |||
//////////////////// | |||
int MidiIO::getPortCount() { | |||
return rtMidi->getPortCount(); | |||
} | |||
std::string MidiIO::getPortName(int port) { | |||
if (port < 0) | |||
return ""; | |||
return rtMidi->getPortName(port); | |||
} | |||
void MidiIO::openPort(int port) { | |||
rtMidi->closePort(); | |||
if (port >= 0) { | |||
rtMidi->openPort(port); | |||
} | |||
this->port = port; | |||
} | |||
json_t *MidiIO::toJson() { | |||
json_t *rootJ = json_object(); | |||
std::string portName = getPortName(port); | |||
json_object_set_new(rootJ, "port", json_string(portName.c_str())); | |||
json_object_set_new(rootJ, "channel", json_integer(channel)); | |||
return rootJ; | |||
} | |||
void MidiIO::fromJson(json_t *rootJ) { | |||
json_t *portNameJ = json_object_get(rootJ, "port"); | |||
if (portNameJ) { | |||
std::string portName = json_string_value(portNameJ); | |||
// Search for port with equal name | |||
for (int port = 0; port < getPortCount(); port++) { | |||
if (getPortName(port) == portName) { | |||
openPort(port); | |||
break; | |||
} | |||
} | |||
} | |||
json_t *channelJ = json_object_get(rootJ, "channel"); | |||
if (channelJ) | |||
channel = json_integer_value(channelJ); | |||
} | |||
//////////////////// | |||
// MidiInput | |||
//////////////////// | |||
static void midiInputCallback(double timeStamp, std::vector<unsigned char> *message, void *userData) { | |||
if (!message) return; | |||
if (!userData) return; | |||
MidiInput *midiInput = (MidiInput*) userData; | |||
if (!midiInput) return; | |||
MidiMessage midiMessage; | |||
midiMessage.time = timeStamp; | |||
midiMessage.data = *message; | |||
midiInput->onMessage(midiMessage); | |||
} | |||
MidiInput::MidiInput() { | |||
RtMidiIn *rtMidiIn = new RtMidiIn(); | |||
rtMidi = rtMidiIn; | |||
rtMidiIn->setCallback(midiInputCallback, this); | |||
} | |||
MidiInput::~MidiInput() { | |||
delete dynamic_cast<RtMidiIn*>(rtMidi); | |||
} | |||
void MidiInputQueue::onMessage(const MidiMessage &message) { | |||
for (uint8_t d : message.data) { | |||
debug("MIDI message: %02x", d); | |||
} | |||
const int messageQueueSize = 8192; | |||
if (messageQueue.size() < messageQueueSize) | |||
messageQueue.push(message); | |||
} | |||
//////////////////// | |||
// MidiOutput | |||
//////////////////// | |||
MidiOutput::MidiOutput() { | |||
rtMidi = new RtMidiOut(); | |||
} | |||
MidiOutput::~MidiOutput() { | |||
delete dynamic_cast<RtMidiOut*>(rtMidi); | |||
} | |||
} // namespace rack |
@@ -8,6 +8,7 @@ | |||
#include <sys/param.h> // for MAXPATHLEN | |||
#include <fcntl.h> | |||
#include <thread> | |||
#include <stdexcept> | |||
#include <zip.h> | |||
#include <jansson.h> | |||
@@ -32,52 +33,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 +100,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()); | |||
@@ -226,14 +191,31 @@ static int extractZip(const char *filename, const char *dir) { | |||
return err; | |||
} | |||
static void syncPlugin(json_t *pluginJ) { | |||
static bool syncPlugin(json_t *pluginJ, bool dryRun) { | |||
json_t *slugJ = json_object_get(pluginJ, "slug"); | |||
if (!slugJ) return; | |||
if (!slugJ) | |||
return false; | |||
std::string slug = json_string_value(slugJ); | |||
info("Syncing plugin %s", slug.c_str()); | |||
// Get community version | |||
std::string version; | |||
json_t *versionJ = json_object_get(pluginJ, "version"); | |||
if (versionJ) { | |||
version = json_string_value(versionJ); | |||
} | |||
// Check whether we already have a plugin with the same slug and version | |||
for (Plugin *plugin : gPlugins) { | |||
if (plugin->slug == slug) { | |||
// plugin->version might be blank, so adding a version of the manifest will update the plugin | |||
if (plugin->version == version) | |||
return false; | |||
} | |||
} | |||
json_t *nameJ = json_object_get(pluginJ, "name"); | |||
if (!nameJ) return; | |||
if (!nameJ) | |||
return false; | |||
std::string name = json_string_value(nameJ); | |||
std::string download; | |||
@@ -273,7 +255,7 @@ static void syncPlugin(json_t *pluginJ) { | |||
if (download.empty()) { | |||
warn("Could not get download URL for plugin %s", slug.c_str()); | |||
return; | |||
return false; | |||
} | |||
// If plugin is not loaded, download the zip file to /plugins | |||
@@ -285,66 +267,30 @@ static void syncPlugin(json_t *pluginJ) { | |||
std::string pluginPath = pluginsDir + "/" + slug; | |||
std::string zipPath = pluginPath + ".zip"; | |||
bool success = requestDownload(download, zipPath, &downloadProgress); | |||
if (success) { | |||
if (!sha256.empty()) { | |||
// Check SHA256 hash | |||
std::string actualSha256 = requestSHA256File(zipPath); | |||
if (actualSha256 != sha256) { | |||
warn("Plugin %s does not match expected SHA256 checksum", slug.c_str()); | |||
return; | |||
} | |||
} | |||
// Unzip file | |||
int err = extractZip(zipPath.c_str(), pluginsDir.c_str()); | |||
if (!err) { | |||
// Delete zip | |||
remove(zipPath.c_str()); | |||
// Load plugin | |||
// loadPlugin(pluginPath); | |||
} | |||
} | |||
downloadName = ""; | |||
} | |||
static bool trySyncPlugin(json_t *pluginJ, json_t *communityPluginsJ, bool dryRun) { | |||
std::string slug = json_string_value(pluginJ); | |||
// Find community plugin | |||
size_t communityIndex; | |||
json_t *communityPluginJ = NULL; | |||
json_array_foreach(communityPluginsJ, communityIndex, communityPluginJ) { | |||
json_t *communitySlugJ = json_object_get(communityPluginJ, "slug"); | |||
if (communitySlugJ) { | |||
std::string communitySlug = json_string_value(communitySlugJ); | |||
if (slug == communitySlug) | |||
break; | |||
} | |||
} | |||
if (communityIndex == json_array_size(communityPluginsJ)) { | |||
warn("Plugin sync error: %s not found in community", slug.c_str()); | |||
if (!success) { | |||
warn("Plugin %s download was unsuccessful"); | |||
return false; | |||
} | |||
// Get community version | |||
std::string version; | |||
json_t *versionJ = json_object_get(communityPluginJ, "version"); | |||
if (versionJ) { | |||
version = json_string_value(versionJ); | |||
if (!sha256.empty()) { | |||
// Check SHA256 hash | |||
std::string actualSha256 = requestSHA256File(zipPath); | |||
if (actualSha256 != sha256) { | |||
warn("Plugin %s does not match expected SHA256 checksum", slug.c_str()); | |||
return false; | |||
} | |||
} | |||
// Check whether we already have a plugin with the same slug and version | |||
for (Plugin *plugin : gPlugins) { | |||
if (plugin->slug == slug) { | |||
// plugin->version might be blank, so adding a version of the manifest will update the plugin | |||
if (plugin->version == version) | |||
return false; | |||
} | |||
// Unzip file | |||
int err = extractZip(zipPath.c_str(), pluginsDir.c_str()); | |||
if (!err) { | |||
// Delete zip | |||
remove(zipPath.c_str()); | |||
// Load plugin | |||
// loadPlugin(pluginPath); | |||
} | |||
if (!dryRun) | |||
syncPlugin(communityPluginJ); | |||
downloadName = ""; | |||
return true; | |||
} | |||
@@ -354,50 +300,77 @@ bool pluginSync(bool dryRun) { | |||
bool available = false; | |||
// Download my plugins | |||
json_t *reqJ = json_object(); | |||
json_object_set(reqJ, "version", json_string(gApplicationVersion.c_str())); | |||
json_object_set(reqJ, "token", json_string(gToken.c_str())); | |||
json_t *resJ = requestJson(METHOD_GET, gApiHost + "/plugins", reqJ); | |||
json_decref(reqJ); | |||
// Download community plugins | |||
json_t *communityResJ = requestJson(METHOD_GET, gApiHost + "/community/plugins", NULL); | |||
if (!dryRun) { | |||
isDownloading = true; | |||
downloadProgress = 0.0; | |||
downloadName = ""; | |||
downloadName = "Updating plugins..."; | |||
} | |||
if (resJ && communityResJ) { | |||
json_t *resJ = NULL; | |||
json_t *communityResJ = NULL; | |||
try { | |||
// Download plugin slugs | |||
json_t *reqJ = json_object(); | |||
json_object_set(reqJ, "version", json_string(gApplicationVersion.c_str())); | |||
json_object_set(reqJ, "token", json_string(gToken.c_str())); | |||
resJ = requestJson(METHOD_GET, gApiHost + "/plugins", reqJ); | |||
json_decref(reqJ); | |||
if (!resJ) | |||
throw std::runtime_error("No response from server"); | |||
json_t *errorJ = json_object_get(resJ, "error"); | |||
json_t *communityErrorJ = json_object_get(resJ, "error"); | |||
if (errorJ) { | |||
warn("Plugin sync error: %s", json_string_value(errorJ)); | |||
} | |||
else if (communityErrorJ) { | |||
warn("Plugin sync error: %s", json_string_value(communityErrorJ)); | |||
} | |||
else { | |||
// Check each plugin in list of my plugins | |||
json_t *pluginsJ = json_object_get(resJ, "plugins"); | |||
json_t *communityPluginsJ = json_object_get(communityResJ, "plugins"); | |||
size_t index; | |||
json_t *pluginJ; | |||
json_array_foreach(pluginsJ, index, pluginJ) { | |||
if (trySyncPlugin(pluginJ, communityPluginsJ, dryRun)) | |||
available = true; | |||
if (errorJ) | |||
throw std::runtime_error(json_string_value(errorJ)); | |||
// Download community plugins | |||
communityResJ = requestJson(METHOD_GET, gApiHost + "/community/plugins", NULL); | |||
if (!communityResJ) | |||
throw std::runtime_error("No response from server"); | |||
json_t *communityErrorJ = json_object_get(communityResJ, "error"); | |||
if (communityErrorJ) | |||
throw std::runtime_error(json_string_value(communityErrorJ)); | |||
// Check each plugin in list of plugin slugs | |||
json_t *pluginSlugsJ = json_object_get(resJ, "plugins"); | |||
json_t *communityPluginsJ = json_object_get(communityResJ, "plugins"); | |||
size_t index; | |||
json_t *pluginSlugJ; | |||
json_array_foreach(pluginSlugsJ, index, pluginSlugJ) { | |||
std::string slug = json_string_value(pluginSlugJ); | |||
// Search for plugin slug in community | |||
size_t communityIndex; | |||
json_t *communityPluginJ = NULL; | |||
json_array_foreach(communityPluginsJ, communityIndex, communityPluginJ) { | |||
json_t *communitySlugJ = json_object_get(communityPluginJ, "slug"); | |||
if (!communitySlugJ) | |||
continue; | |||
std::string communitySlug = json_string_value(communitySlugJ); | |||
if (slug == communitySlug) | |||
break; | |||
} | |||
// Sync plugin | |||
if (syncPlugin(communityPluginJ, dryRun)) { | |||
available = true; | |||
} | |||
else { | |||
warn("Plugin %s not found in community", slug.c_str()); | |||
} | |||
} | |||
} | |||
if (resJ) | |||
json_decref(resJ); | |||
catch (std::runtime_error &e) { | |||
warn("Plugin sync error: %s", e.what()); | |||
} | |||
if (communityResJ) | |||
json_decref(communityResJ); | |||
if (resJ) | |||
json_decref(resJ); | |||
if (!dryRun) { | |||
isDownloading = false; | |||
} | |||
@@ -410,6 +383,7 @@ bool pluginSync(bool dryRun) { | |||
//////////////////// | |||
void pluginInit() { | |||
tagsInit(); | |||
// Load core | |||
// This function is defined in core.cpp | |||
Plugin *coreManufacturer = new Plugin(); | |||
@@ -9,6 +9,9 @@ | |||
namespace rack { | |||
bool skipAutosaveOnLaunch = false; | |||
static json_t *settingsToJson() { | |||
// root | |||
json_t *rootJ = json_object(); | |||
@@ -56,6 +59,11 @@ static json_t *settingsToJson() { | |||
json_t *lastPathJ = json_string(gRackWidget->lastPath.c_str()); | |||
json_object_set_new(rootJ, "lastPath", lastPathJ); | |||
// skipAutosaveOnLaunch | |||
if (skipAutosaveOnLaunch) { | |||
json_object_set_new(rootJ, "skipAutosaveOnLaunch", json_true()); | |||
} | |||
return rootJ; | |||
} | |||
@@ -114,22 +122,25 @@ static void settingsFromJson(json_t *rootJ) { | |||
json_t *lastPathJ = json_object_get(rootJ, "lastPath"); | |||
if (lastPathJ) | |||
gRackWidget->lastPath = json_string_value(lastPathJ); | |||
json_t *skipAutosaveOnLaunchJ = json_object_get(rootJ, "skipAutosaveOnLaunch"); | |||
if (skipAutosaveOnLaunchJ) | |||
skipAutosaveOnLaunch = json_boolean_value(skipAutosaveOnLaunchJ); | |||
} | |||
void settingsSave(std::string filename) { | |||
info("Saving settings %s", filename.c_str()); | |||
FILE *file = fopen(filename.c_str(), "w"); | |||
if (!file) | |||
return; | |||
json_t *rootJ = settingsToJson(); | |||
if (rootJ) { | |||
json_dumpf(rootJ, file, JSON_INDENT(2)); | |||
FILE *file = fopen(filename.c_str(), "w"); | |||
if (!file) | |||
return; | |||
json_dumpf(rootJ, file, JSON_INDENT(2) | JSON_REAL_PRECISION(9)); | |||
json_decref(rootJ); | |||
fclose(file); | |||
} | |||
fclose(file); | |||
} | |||
void settingsLoad(std::string filename) { | |||
@@ -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 |
@@ -99,12 +99,12 @@ std::string stringf(const char *format, ...) { | |||
return s; | |||
} | |||
std::string tolower(std::string s) { | |||
std::string lowercase(std::string s) { | |||
std::transform(s.begin(), s.end(), s.begin(), ::tolower); | |||
return s; | |||
} | |||
std::string toupper(std::string s) { | |||
std::string uppercase(std::string s) { | |||
std::transform(s.begin(), s.end(), s.begin(), ::toupper); | |||
return s; | |||
} | |||
@@ -116,6 +116,10 @@ std::string ellipsize(std::string s, size_t len) { | |||
return s.substr(0, len - 3) + "..."; | |||
} | |||
bool startsWith(std::string str, std::string prefix) { | |||
return str.substr(0, prefix.size()) == prefix; | |||
} | |||
std::string extractDirectory(std::string path) { | |||
char *pathDup = strdup(path.c_str()); | |||
std::string directory = dirname(pathDup); | |||
@@ -85,7 +85,7 @@ json_t *requestJson(RequestMethod method, std::string url, json_t *dataJ) { | |||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText); | |||
// Perform request | |||
info("Requesting %s", url.c_str()); | |||
// info("Requesting %s", url.c_str()); | |||
// curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); | |||
CURLcode res = curl_easy_perform(curl); | |||
@@ -9,4 +9,5 @@ Widget *gFocusedWidget = NULL; | |||
Scene *gScene = NULL; | |||
} // 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 |