Browse Source

Merge remote-tracking branch 'VCV/master' into ossia

pull/574/head
Antoine Villeret 7 years ago
parent
commit
002f7c12d2
58 changed files with 2314 additions and 666 deletions
  1. +1
    -0
      .gitignore
  2. +6
    -0
      .gitmodules
  3. +35
    -6
      CHANGELOG.md
  4. +24
    -7
      LICENSE-dist.txt
  5. +52
    -18
      Makefile
  6. +6
    -2
      README.md
  7. +9
    -6
      compile.mk
  8. +52
    -36
      dep/Makefile
  9. +1
    -0
      dep/glfw
  10. +1
    -0
      dep/rtaudio
  11. +11
    -8
      include/app.hpp
  12. +9
    -1
      include/components.hpp
  13. +5
    -0
      include/dsp/filter.hpp
  14. +30
    -24
      include/dsp/samplerate.hpp
  15. +32
    -0
      include/dsp/vumeter.hpp
  16. +9
    -6
      include/engine.hpp
  17. +6
    -0
      include/gui.hpp
  18. +1
    -10
      include/math.hpp
  19. +5
    -51
      include/plugin.hpp
  20. +72
    -0
      include/tags.hpp
  21. +10
    -0
      include/util.hpp
  22. +1
    -0
      include/util/request.hpp
  23. +2
    -1
      include/widgets.hpp
  24. +10
    -0
      plugin.mk
  25. +321
    -0
      res/ComponentLibrary/CKSSThree_0.svg
  26. +322
    -0
      res/ComponentLibrary/CKSSThree_1.svg
  27. +322
    -0
      res/ComponentLibrary/CKSSThree_2.svg
  28. +2
    -1
      src/app.cpp
  29. +5
    -9
      src/app/AddModuleWindow.cpp
  30. +5
    -1
      src/app/Knob.cpp
  31. +26
    -12
      src/app/LightWidget.cpp
  32. +25
    -10
      src/app/ModuleWidget.cpp
  33. +52
    -12
      src/app/PluginManagerWidget.cpp
  34. +9
    -0
      src/app/RackRail.cpp
  35. +3
    -2
      src/app/RackScene.cpp
  36. +37
    -13
      src/app/RackWidget.cpp
  37. +4
    -4
      src/app/SVGFader.cpp
  38. +3
    -1
      src/app/SVGKnob.cpp
  39. +2
    -5
      src/app/SVGSwitch.cpp
  40. +9
    -8
      src/app/Toolbar.cpp
  41. +0
    -5
      src/app/WireWidget.cpp
  42. +286
    -157
      src/core/AudioInterface.cpp
  43. +15
    -10
      src/core/MidiCCToCV.cpp
  44. +29
    -28
      src/core/MidiClockToCV.cpp
  45. +7
    -6
      src/core/MidiIO.cpp
  46. +10
    -6
      src/core/MidiIO.hpp
  47. +3
    -4
      src/core/MidiToCV.cpp
  48. +10
    -8
      src/core/MidiTriggerToCV.cpp
  49. +64
    -54
      src/core/QuadMidiToCV.cpp
  50. +59
    -24
      src/gui.cpp
  51. +1
    -7
      src/main.cpp
  52. +170
    -97
      src/plugin.cpp
  53. +64
    -0
      src/tags.cpp
  54. +4
    -0
      src/util.cpp
  55. +35
    -0
      src/util/request.cpp
  56. +4
    -2
      src/widgets/MenuOverlay.cpp
  57. +9
    -4
      src/widgets/SVGWidget.cpp
  58. +7
    -0
      src/widgets/ZoomWidget.cpp

+ 1
- 0
.gitignore View File

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


+ 6
- 0
.gitmodules View File

@@ -10,3 +10,9 @@
[submodule "ext/oui-blendish"] [submodule "ext/oui-blendish"]
path = ext/oui-blendish path = ext/oui-blendish
url = https://github.com/AndrewBelt/oui-blendish.git 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

+ 35
- 6
CHANGELOG.md View File

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




### dev
### 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%
- Automatically scroll when dragging cables to the edge of the screen - Automatically scroll when dragging cables to the edge of the screen
- Added Quad MIDI-to-CV Interface, CC-to-CV, Clock-to-CV, and Trigger-to-CV MIDI interfaces
- Improved support for ASIO, WASAPI, DirectSound, Core Audio, and ALSA audio drivers
- New module browser with search and tags
- Enhanced LED emulation in graphics engine
- File > New attempts to load "template.vcv" in the "Documents/Rack" folder if it exists

- New Grayscale plugin with Algorhythm, Binary, and Binary² modules


- Audible Instruments - Audible Instruments
- Added Low CPU mode to Braids for draft-quality rendering
- Added extra blend mode functions, alternative modes, and quality settings to Texture Synthesizer
- Added bonus modes and "Disastrous Peace" mode to Resonator
- Added Low CPU mode to Macro Oscillator
- Merged Tidal Modulator and Wavetable Oscillator into a single module
- Fixed Keyframer/Mixer keyframes and channel settings saving


- Fundamental
- Added 8vert, 8-channel attenuverter
- Added Unity, 2-channel mixer
- Changed LED functions in ADSR


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


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




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

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




### v0.3.1
### v0.3.1 (2017-09-13)


- Fixed Windows open dialog current working directory graphics problem - Fixed Windows open dialog current working directory graphics problem
- Ctrl/Cmd-C/V to copy/paste from text and password fields - Ctrl/Cmd-C/V to copy/paste from text and password fields
@@ -55,5 +83,6 @@ Tip: Use `git checkout v0.4.0` for example to check out any previous version men
- tweaks to Fundamental and Audible Instruments plugins - tweaks to Fundamental and Audible Instruments plugins




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

- Knobcon public Beta release - Knobcon public Beta release

+ 24
- 7
LICENSE-dist.txt View File

@@ -180,17 +180,34 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.




# portaudio
# RtAudio


PortAudio Portable Real-Time Audio Library
Copyright (c) 1999-2011 Ross Bencina and Phil Burk
RtAudio: a set of realtime audio i/o C++ classes
Copyright (c) 2001-2017 Gary P. Scavone


Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.


The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Any person wishing to distribute modifications to the Software is
asked to send the modifications to the original developer so that
they can be incorporated into the canonical version. This is,
however, not a binding provision of this license.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.




# RtMidi # RtMidi


+ 52
- 18
Makefile View File

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


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


debug: $(TARGET) debug: $(TARGET)
ifeq ($(ARCH), mac)
lldb ./Rack
else
ifeq ($(ARCH), lin)
LD_LIBRARY_PATH=dep/lib gdb -ex run ./Rack LD_LIBRARY_PATH=dep/lib gdb -ex run ./Rack
endif endif
ifeq ($(ARCH), mac)
DYLD_FALLBACK_LIBRARY_PATH=dep/lib gdb -ex run ./Rack
endif
ifeq ($(ARCH), win)
# TODO get rid of the mingw64 path
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: clean:
rm -fv libRack.a
rm -rfv $(TARGET) build dist rm -rfv $(TARGET) build dist


# For Windows resources # For Windows resources
@@ -97,20 +109,22 @@ ifeq ($(ARCH), mac)
cp dep/lib/libGLEW.2.1.0.dylib $(BUNDLE)/Contents/MacOS/ cp dep/lib/libGLEW.2.1.0.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libglfw.3.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/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/libcurl.4.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libzip.5.dylib $(BUNDLE)/Contents/MacOS/ cp dep/lib/libzip.5.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libportaudio.2.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/librtaudio.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/ cp dep/lib/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/
cp dep/lib/libcrypto.1.1.dylib $(BUNDLE)/Contents/MacOS/


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 /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 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/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/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 $(PWD)/dep/lib/libzip.5.dylib @executable_path/libzip.5.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/libportaudio.2.dylib @executable_path/libportaudio.2.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change librtaudio.dylib @executable_path/librtaudio.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/librtmidi.4.dylib @executable_path/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/Rack install_name_tool -change $(PWD)/dep/lib/librtmidi.4.dylib @executable_path/librtmidi.4.dylib $(BUNDLE)/Contents/MacOS/Rack
install_name_tool -change $(PWD)/dep/lib/libcrypto.1.1.dylib @executable_path/libcrypto.1.1.dylib $(BUNDLE)/Contents/MacOS/Rack


otool -L $(BUNDLE)/Contents/MacOS/Rack otool -L $(BUNDLE)/Contents/MacOS/Rack


@@ -124,6 +138,7 @@ ifeq ($(ARCH), win)
mkdir -p dist/Rack mkdir -p dist/Rack
cp -R LICENSE* res dist/Rack/ cp -R LICENSE* res dist/Rack/
cp Rack.exe dist/Rack/ cp Rack.exe dist/Rack/
strip dist/Rack/Rack.exe
cp /mingw64/bin/libwinpthread-1.dll dist/Rack/ cp /mingw64/bin/libwinpthread-1.dll dist/Rack/
cp /mingw64/bin/zlib1.dll dist/Rack/ cp /mingw64/bin/zlib1.dll dist/Rack/
cp /mingw64/bin/libstdc++-6.dll dist/Rack/ cp /mingw64/bin/libstdc++-6.dll dist/Rack/
@@ -133,9 +148,11 @@ ifeq ($(ARCH), win)
cp dep/bin/libcurl-4.dll dist/Rack/ cp dep/bin/libcurl-4.dll dist/Rack/
cp dep/bin/libjansson-4.dll dist/Rack/ cp dep/bin/libjansson-4.dll dist/Rack/
cp dep/bin/librtmidi-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/libzip-5.dll dist/Rack/
cp dep/bin/portaudio_x64.dll dist/Rack/
cp dep/bin/librtaudio.dll dist/Rack/
cp dep/bin/libcrypto-1_1-x64.dll dist/Rack/
cp dep/bin/libssl-1_1-x64.dll dist/Rack/
mkdir -p dist/Rack/plugins mkdir -p dist/Rack/plugins
cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/ cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/
# Make ZIP # Make ZIP
@@ -148,20 +165,37 @@ ifeq ($(ARCH), lin)
mkdir -p dist/Rack mkdir -p dist/Rack
cp -R LICENSE* res dist/Rack/ cp -R LICENSE* res dist/Rack/
cp Rack Rack.sh 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/libjansson.so.4 dist/Rack/
cp dep/lib/libGLEW.so.2.1 dist/Rack/ cp dep/lib/libGLEW.so.2.1 dist/Rack/
cp dep/lib/libglfw.so.3 dist/Rack/ cp dep/lib/libglfw.so.3 dist/Rack/
cp dep/lib/libcurl.so.4 dist/Rack/ cp dep/lib/libcurl.so.4 dist/Rack/
cp dep/lib/libzip.so.5 dist/Rack/ cp dep/lib/libzip.so.5 dist/Rack/
cp dep/lib/librtaudio.so.6 dist/Rack/
cp dep/lib/librtaudio.so dist/Rack/
cp dep/lib/librtmidi.so.4 dist/Rack/ cp dep/lib/librtmidi.so.4 dist/Rack/
cp /usr/local/lib/libossia.so dist/Rack/
cp dep/lib/libssl.so.1.1 dist/Rack/
cp dep/lib/libcrypto.so.1.1 dist/Rack/
mkdir -p dist/Rack/plugins mkdir -p dist/Rack/plugins
# Make ZIP
cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/ cp -R plugins/Fundamental/dist/Fundamental dist/Rack/plugins/
cp -R plugins/Tutorial/dist/Template dist/Rack/plugins/
# Make ZIP
cd dist && zip -5 -r Rack-$(VERSION)-$(ARCH).zip Rack
endif


# Obviously this will only work if you have the private keys to my server
UPLOAD_URL = vortico@vcvrack.com:files/
upload: dist distplugins
ifeq ($(ARCH), mac)
rsync dist/*.dmg $(UPLOAD_URL) -zP
endif
ifeq ($(ARCH), win)
rsync dist/*.exe $(UPLOAD_URL) -P
rsync dist/*.zip $(UPLOAD_URL) -P
endif
ifeq ($(ARCH), lin)
rsync dist/*.zip $(UPLOAD_URL) -zP
endif endif
rsync plugins/*/dist/*.zip $(UPLOAD_URL) -zP




# Plugin helpers # Plugin helpers


+ 6
- 2
README.md View File

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


make dep 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. You should see a message that all dependencies built successfully.


Build Rack. Build Rack.
@@ -81,6 +83,8 @@ Build plugin.


## License ## License


Rack source code by [Andrew Belt](https://andrewbelt.name/) licensed under [BSD-3-Clause](LICENSE.txt)
Source code licensed under [BSD-3-Clause](LICENSE.txt) by [Andrew Belt](https://andrewbelt.name/)

Component Library graphics in `res/ComponentLibrary` licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/) by [Grayscale](http://grayscale.info/)


Component Library graphics by [Grayscale](http://grayscale.info/) licensed under [CC BY-NC 4.0](https://creativecommons.org/licenses/by-nc/4.0/)
VCV logo is © 2017

+ 9
- 6
compile.mk View File

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


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


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


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



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


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


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


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


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

+ 52
- 36
dep/Makefile View File

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


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


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

# Library configuration
ifdef RTAUDIO_ALL_APIS
ifeq ($(ARCH),mac)
RTAUDIO_FLAGS = -DAUDIO_OSX_CORE=ON
endif
ifeq ($(ARCH),win)
RTAUDIO_FLAGS = -DAUDIO_WINDOWS_DS=ON -DAUDIO_WINDOWS_WASAPI=ON -DAUDIO_WINDOWS_ASIO=ON
endif
ifeq ($(ARCH),lin)
RTAUDIO_FLAGS = -DAUDIO_LINUX_ALSA=ON
endif
endif endif




.NOTPARALLEL: .NOTPARALLEL:


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


$(glfw): $(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 \ -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): $(jansson):
$(WGET) http://www.digip.org/jansson/releases/jansson-2.10.tar.gz $(WGET) http://www.digip.org/jansson/releases/jansson-2.10.tar.gz
@@ -93,19 +104,27 @@ $(jansson):
$(MAKE) -C jansson-2.10 $(MAKE) -C jansson-2.10
$(MAKE) -C jansson-2.10 install $(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

$(libcurl):
$(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
$(UNTAR) openssl-1.1.0g.tar.gz
cd openssl-1.1.0g && ./config --prefix="$(LOCAL)"
$(MAKE) -C openssl-1.1.0g
$(MAKE) -C openssl-1.1.0g install

$(libcurl): $(openssl)
$(WGET) https://github.com/curl/curl/releases/download/curl-7_56_0/curl-7.56.0.tar.gz $(WGET) https://github.com/curl/curl/releases/download/curl-7_56_0/curl-7.56.0.tar.gz
$(UNTAR) curl-7.56.0.tar.gz $(UNTAR) curl-7.56.0.tar.gz
cd curl-7.56.0 && ./configure --prefix="$(LOCAL)" \ cd curl-7.56.0 && ./configure --prefix="$(LOCAL)" \
--disable-ftp --disable-file --disable-ldap --disable-ldaps --disable-rtsp --disable-proxy --disable-dict --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --disable-manual \ --disable-ftp --disable-file --disable-ldap --disable-ldaps --disable-rtsp --disable-proxy --disable-dict --disable-telnet --disable-tftp --disable-pop3 --disable-imap --disable-smb --disable-smtp --disable-gopher --disable-manual \
--without-zlib --without-ssl --without-ca-bundle --without-ca-path --without-ca-fallback --without-libpsl --without-libmetalink --without-libssh2 --without-librtmp --without-winidn --without-libidn2 --without-nghttp2
--without-zlib --without-libpsl --without-libmetalink --without-libssh2 --without-librtmp --without-winidn --without-libidn2 --without-nghttp2 \
--without-ca-bundle --with-ca-fallback --with-ssl="$(LOCAL)"
$(MAKE) -C curl-7.56.0 $(MAKE) -C curl-7.56.0
$(MAKE) -C curl-7.56.0 install $(MAKE) -C curl-7.56.0 install


@@ -117,19 +136,16 @@ $(libzip):
$(MAKE) -C libzip-1.2.0 install $(MAKE) -C libzip-1.2.0 install


$(rtmidi): $(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): $(rtaudio):
$(WGET) http://www.music.mcgill.ca/~gary/rtaudio/release/rtaudio-5.0.0.tar.gz
$(UNTAR) rtaudio-5.0.0.tar.gz
cd rtaudio-5.0.0 && ./configure --prefix="$(LOCAL)"
$(MAKE) -C rtaudio-5.0.0
$(MAKE) -C rtaudio-5.0.0 install

cd rtaudio && mkdir -p cmakebuild
cd rtaudio/cmakebuild && cmake -G 'Unix Makefiles' -DCMAKE_INSTALL_PREFIX="$(LOCAL)" $(RTAUDIO_FLAGS) ..
$(MAKE) -C rtaudio/cmakebuild
$(MAKE) -C rtaudio/cmakebuild install
$(ossia): $(ossia):
# TODO use release tarball instead of building it locally # TODO use release tarball instead of building it locally
git clone https://github.com/OSSIA/libossia --depth=1 git clone https://github.com/OSSIA/libossia --depth=1
@@ -141,4 +157,4 @@ $(ossia):
$(MAKE) install $(MAKE) install


clean: clean:
git clean -ffdxi
git clean -ffdx

+ 1
- 0
dep/glfw

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

+ 1
- 0
dep/rtaudio

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

+ 11
- 8
include/app.hpp View File

@@ -20,10 +20,9 @@ struct SVGPanel;
// module // 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_WIDTH 15
#define RACK_GRID_HEIGHT 380 #define RACK_GRID_HEIGHT 380

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




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


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


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


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


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


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


@@ -271,6 +270,8 @@ struct MomentarySwitch : virtual Switch {
void randomize() override {} void randomize() override {}
void onDragStart(EventDragStart &e) override { void onDragStart(EventDragStart &e) override {
setValue(maxValue); setValue(maxValue);
EventAction eAction;
onAction(eAction);
} }
void onDragEnd(EventDragEnd &e) override { void onDragEnd(EventDragEnd &e) override {
setValue(minValue); setValue(minValue);
@@ -285,6 +286,8 @@ struct LightWidget : TransparentWidget {
NVGcolor bgColor = nvgRGBf(0, 0, 0); NVGcolor bgColor = nvgRGBf(0, 0, 0);
NVGcolor color = nvgRGBf(1, 1, 1); NVGcolor color = nvgRGBf(1, 1, 1);
void draw(NVGcontext *vg) override; 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 */ /** Mixes a list of colors based on a list of brightness values */


+ 9
- 1
include/components.hpp View File

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


struct BefacoSlidePot : SVGSlider {
struct BefacoSlidePot : SVGFader {
BefacoSlidePot() { BefacoSlidePot() {
Vec margin = Vec(3.5, 3.5); Vec margin = Vec(3.5, 3.5);
maxHandlePos = Vec(-1, -2).plus(margin); maxHandlePos = Vec(-1, -2).plus(margin);
@@ -461,6 +461,14 @@ struct CKSS : SVGSwitch, ToggleSwitch {
} }
}; };


struct CKSSThree : SVGSwitch, ToggleSwitch {
CKSSThree() {
addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_0.svg")));
addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_1.svg")));
addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKSSThree_2.svg")));
}
};

struct CKD6 : SVGSwitch, MomentarySwitch { struct CKD6 : SVGSwitch, MomentarySwitch {
CKD6() { CKD6() {
addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKD6_0.svg"))); addFrame(SVG::load(assetGlobal("res/ComponentLibrary/CKD6_0.svg")));


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

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

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


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

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


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




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


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


SampleRateConverter() { SampleRateConverter() {
int error; 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() { ~SampleRateConverter() {
src_delete(state);
speex_resampler_destroy(state);
} }
/** output_sample_rate / input_sample_rate */
void setRatio(float r) {
src_set_ratio(state, r);
data.src_ratio = r;

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

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

/** `in` and `out` are interlaced with the number of channels */ /** `in` and `out` are interlaced with the number of channels */
void process(const Frame<CHANNELS> *in, int *inFrames, Frame<CHANNELS> *out, int *outFrames) { 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() { void reset() {
src_reset(state);
int error = speex_resampler_reset_mem(state);
assert(error == RESAMPLER_ERR_SUCCESS);
} }
}; };




+ 32
- 0
include/dsp/vumeter.hpp View File

@@ -0,0 +1,32 @@
#pragma once

#include "math.hpp"


namespace rack {


struct VUMeter {
/** Decibel level difference between adjacent meter lights */
float dBInterval = 3.0;
float dBScaled;
/** Value should be scaled so that 1.0 is clipping */
void setValue(float v) {
dBScaled = log10f(fabsf(v)) * 20.0 / dBInterval;
}
/** Returns the brightness of the light indexed by i
Light 0 is a clip light (red) which is either on or off.
All others are smooth lights which are fully bright at -dBInterval*i and higher, and fully off at -dBInterval*(i-1).
*/
float getBrightness(int i) {
if (i == 0) {
return (dBScaled >= 0.0) ? 1.0 : 0.0;
}
else {
return clampf(dBScaled + i, 0.0, 1.0);
}
}
};


} // namespace rack

+ 9
- 6
include/engine.hpp View File

@@ -21,7 +21,7 @@ struct Light {
float value = 0.0; float value = 0.0;
float getBrightness(); float getBrightness();
void setBrightness(float brightness) { void setBrightness(float brightness) {
value = brightness * brightness;
value = (brightness > 0.f) ? brightness * brightness : 0.f;
} }
void setBrightnessSmooth(float brightness); void setBrightnessSmooth(float brightness);
}; };
@@ -73,11 +73,14 @@ struct Module {
virtual void step() {} virtual void step() {}
virtual void onSampleRateChange() {} virtual void onSampleRateChange() {}


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


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


+ 6
- 0
include/gui.hpp View File

@@ -17,8 +17,14 @@ namespace rack {
extern GLFWwindow *gWindow; extern GLFWwindow *gWindow;
extern NVGcontext *gVg; extern NVGcontext *gVg;
extern NVGcontext *gFramebufferVg; extern NVGcontext *gFramebufferVg;
/** The default font to use for GUI elements */
extern std::shared_ptr<Font> gGuiFont; extern std::shared_ptr<Font> gGuiFont;
/** The scaling ratio */
extern float gPixelRatio; 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 bool gAllowCursorLock;
extern int gGuiFrame; extern int gGuiFrame;
extern Vec gMousePos; extern Vec gMousePos;


+ 1
- 10
include/math.hpp View File

@@ -120,15 +120,6 @@ inline float sincf(float x) {
return sinf(x) / 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` /** Linearly interpolate an array `p` with index `x`
Assumes that the array at `p` is of length at least floor(x)+1. 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 /** 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) i.e. cmultf(&ar, &ai, ar, ai, br, bi)
*/ */
inline void cmultf(float *cr, float *ci, float ar, float ai, float br, float bi) { inline void cmultf(float *cr, float *ci, float ar, float ai, float br, float bi) {


+ 5
- 51
include/plugin.hpp View File

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


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


namespace rack { 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 ModuleWidget;
struct Model; struct Model;


@@ -72,11 +25,12 @@ struct Plugin {
*/ */
std::string slug; 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 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. Do not include the "v" in "v1.0" for example.
*/ */
std::string version; std::string version;

/** URL for plugin homepage (optional) */ /** URL for plugin homepage (optional) */
std::string website; std::string website;
/** URL for plugin manual (optional) */ /** URL for plugin manual (optional) */
@@ -110,7 +64,8 @@ void pluginInit();
void pluginDestroy(); void pluginDestroy();
void pluginLogIn(std::string email, std::string password); void pluginLogIn(std::string email, std::string password);
void pluginLogOut(); void pluginLogOut();
void pluginRefresh();
/** Returns whether a new plugin is available, and downloads it unless doing a dry run */
bool pluginSync(bool dryRun);
void pluginCancelDownload(); void pluginCancelDownload();
bool pluginIsLoggedIn(); bool pluginIsLoggedIn();
bool pluginIsDownloading(); bool pluginIsDownloading();
@@ -121,7 +76,6 @@ std::string pluginGetLoginStatus();


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




} // namespace rack } // namespace rack


+ 72
- 0
include/tags.hpp View File

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


namespace rack {


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


void tagsInit();

extern std::string gTagNames[NUM_TAGS];


} // namespace rack

+ 10
- 0
include/util.hpp View File

@@ -30,6 +30,16 @@ will expand to


#define LENGTHOF(arr) (sizeof(arr) / sizeof((arr)[0])) #define LENGTHOF(arr) (sizeof(arr) / sizeof((arr)[0]))


/** Reserve space for _count enums starting with _name.
Example:
enum Foo {
ENUMS(BAR, 14)
};

BAR + 0 to BAR + 11 is reserved
*/
#define ENUMS(_name, _count) _name, _name ## _LAST = _name + (_count) - 1

/** Deprecation notice for GCC */ /** Deprecation notice for GCC */
#define DEPRECATED __attribute__ ((deprecated)) #define DEPRECATED __attribute__ ((deprecated))




+ 1
- 0
include/util/request.hpp View File

@@ -19,5 +19,6 @@ json_t *requestJson(RequestMethod method, std::string url, json_t *dataJ);
/** Returns the filename, blank if unsuccessful */ /** Returns the filename, blank if unsuccessful */
bool requestDownload(std::string url, std::string filename, float *progress); bool requestDownload(std::string url, std::string filename, float *progress);
std::string requestEscape(std::string s); std::string requestEscape(std::string s);
std::string requestSHA256File(std::string filename);


} // namespace rack } // namespace rack

+ 2
- 1
include/widgets.hpp View File

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


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




+ 10
- 0
plugin.mk View File

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


DISTRIBUTABLES += $(TARGET)



all: $(TARGET) all: $(TARGET)


@@ -28,3 +30,11 @@ include ../../compile.mk


clean: clean:
rm -rfv build $(TARGET) dist rm -rfv build $(TARGET) dist

dist: all
rm -rf dist
mkdir -p dist/$(SLUG)
cp -R $(DISTRIBUTABLES) dist/$(SLUG)/
cd dist && zip -5 -r $(SLUG)-$(VERSION)-$(ARCH).zip $(SLUG)

.PHONY: clean dist

+ 321
- 0
res/ComponentLibrary/CKSSThree_0.svg View File

@@ -0,0 +1,321 @@
<?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="4.7473302mm"
height="10.000424mm"
viewBox="0 0 4.7473302 10.000424"
version="1.1"
id="svg258108"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="CKSSThree_0.svg">
<defs
id="defs258102">
<linearGradient
id="linear20"
gradientUnits="userSpaceOnUse"
x1="-0.095554203"
y1="0"
x2="1.32507"
y2="0"
gradientTransform="matrix(0,6.4087959,-6.4087959,0,102.632,66.415358)">
<stop
offset="0"
style="stop-color:rgb(25.489807%,25.097656%,25.881958%);stop-opacity:1;"
id="stop217759" />
<stop
offset="0.0625"
style="stop-color:rgb(25.489807%,25.097656%,25.881958%);stop-opacity:1;"
id="stop217761" />
<stop
offset="0.078125"
style="stop-color:rgb(25.398254%,24.996948%,25.778198%);stop-opacity:1;"
id="stop217763" />
<stop
offset="0.09375"
style="stop-color:rgb(25.177002%,24.754333%,25.527954%);stop-opacity:1;"
id="stop217765" />
<stop
offset="0.109375"
style="stop-color:rgb(24.916077%,24.465942%,25.231934%);stop-opacity:1;"
id="stop217767" />
<stop
offset="0.125"
style="stop-color:rgb(24.653625%,24.179077%,24.935913%);stop-opacity:1;"
id="stop217769" />
<stop
offset="0.140625"
style="stop-color:rgb(24.3927%,23.892212%,24.639893%);stop-opacity:1;"
id="stop217771" />
<stop
offset="0.15625"
style="stop-color:rgb(24.131775%,23.605347%,24.343872%);stop-opacity:1;"
id="stop217773" />
<stop
offset="0.171875"
style="stop-color:rgb(23.87085%,23.316956%,24.047852%);stop-opacity:1;"
id="stop217775" />
<stop
offset="0.1875"
style="stop-color:rgb(23.609924%,23.03009%,23.751831%);stop-opacity:1;"
id="stop217777" />
<stop
offset="0.203125"
style="stop-color:rgb(23.348999%,22.743225%,23.455811%);stop-opacity:1;"
id="stop217779" />
<stop
offset="0.21875"
style="stop-color:rgb(23.088074%,22.45636%,23.15979%);stop-opacity:1;"
id="stop217781" />
<stop
offset="0.234375"
style="stop-color:rgb(22.825623%,22.167969%,22.86377%);stop-opacity:1;"
id="stop217783" />
<stop
offset="0.25"
style="stop-color:rgb(22.564697%,21.881104%,22.567749%);stop-opacity:1;"
id="stop217785" />
<stop
offset="0.265625"
style="stop-color:rgb(22.303772%,21.594238%,22.271729%);stop-opacity:1;"
id="stop217787" />
<stop
offset="0.28125"
style="stop-color:rgb(22.042847%,21.307373%,21.975708%);stop-opacity:1;"
id="stop217789" />
<stop
offset="0.296875"
style="stop-color:rgb(21.781921%,21.018982%,21.679688%);stop-opacity:1;"
id="stop217791" />
<stop
offset="0.3125"
style="stop-color:rgb(21.520996%,20.732117%,21.383667%);stop-opacity:1;"
id="stop217793" />
<stop
offset="0.328125"
style="stop-color:rgb(21.260071%,20.445251%,21.087646%);stop-opacity:1;"
id="stop217795" />
<stop
offset="0.34375"
style="stop-color:rgb(20.99762%,20.15686%,20.793152%);stop-opacity:1;"
id="stop217797" />
<stop
offset="0.359375"
style="stop-color:rgb(20.736694%,19.869995%,20.497131%);stop-opacity:1;"
id="stop217799" />
<stop
offset="0.375"
style="stop-color:rgb(20.475769%,19.58313%,20.201111%);stop-opacity:1;"
id="stop217801" />
<stop
offset="0.390625"
style="stop-color:rgb(20.214844%,19.296265%,19.90509%);stop-opacity:1;"
id="stop217803" />
<stop
offset="0.40625"
style="stop-color:rgb(19.953918%,19.007874%,19.60907%);stop-opacity:1;"
id="stop217805" />
<stop
offset="0.421875"
style="stop-color:rgb(19.692993%,18.721008%,19.313049%);stop-opacity:1;"
id="stop217807" />
<stop
offset="0.4375"
style="stop-color:rgb(19.432068%,18.434143%,19.017029%);stop-opacity:1;"
id="stop217809" />
<stop
offset="0.453125"
style="stop-color:rgb(19.169617%,18.147278%,18.721008%);stop-opacity:1;"
id="stop217811" />
<stop
offset="0.46875"
style="stop-color:rgb(18.908691%,17.858887%,18.424988%);stop-opacity:1;"
id="stop217813" />
<stop
offset="0.484375"
style="stop-color:rgb(18.647766%,17.572021%,18.128967%);stop-opacity:1;"
id="stop217815" />
<stop
offset="0.5"
style="stop-color:rgb(18.386841%,17.285156%,17.832947%);stop-opacity:1;"
id="stop217817" />
<stop
offset="0.515625"
style="stop-color:rgb(18.125916%,16.998291%,17.536926%);stop-opacity:1;"
id="stop217819" />
<stop
offset="0.53125"
style="stop-color:rgb(17.86499%,16.7099%,17.240906%);stop-opacity:1;"
id="stop217821" />
<stop
offset="0.546875"
style="stop-color:rgb(17.604065%,16.423035%,16.944885%);stop-opacity:1;"
id="stop217823" />
<stop
offset="0.5625"
style="stop-color:rgb(17.341614%,16.136169%,16.648865%);stop-opacity:1;"
id="stop217825" />
<stop
offset="0.578125"
style="stop-color:rgb(17.080688%,15.849304%,16.352844%);stop-opacity:1;"
id="stop217827" />
<stop
offset="0.59375"
style="stop-color:rgb(16.819763%,15.560913%,16.056824%);stop-opacity:1;"
id="stop217829" />
<stop
offset="0.609375"
style="stop-color:rgb(16.558838%,15.274048%,15.760803%);stop-opacity:1;"
id="stop217831" />
<stop
offset="0.625"
style="stop-color:rgb(16.297913%,14.987183%,15.464783%);stop-opacity:1;"
id="stop217833" />
<stop
offset="0.640625"
style="stop-color:rgb(16.036987%,14.700317%,15.168762%);stop-opacity:1;"
id="stop217835" />
<stop
offset="0.65625"
style="stop-color:rgb(15.776062%,14.411926%,14.872742%);stop-opacity:1;"
id="stop217837" />
<stop
offset="0.671875"
style="stop-color:rgb(15.513611%,14.125061%,14.576721%);stop-opacity:1;"
id="stop217839" />
<stop
offset="0.6875"
style="stop-color:rgb(15.252686%,13.838196%,14.280701%);stop-opacity:1;"
id="stop217841" />
<stop
offset="0.703125"
style="stop-color:rgb(14.99176%,13.551331%,13.98468%);stop-opacity:1;"
id="stop217843" />
<stop
offset="0.71875"
style="stop-color:rgb(14.730835%,13.262939%,13.68866%);stop-opacity:1;"
id="stop217845" />
<stop
offset="0.734375"
style="stop-color:rgb(14.46991%,12.976074%,13.392639%);stop-opacity:1;"
id="stop217847" />
<stop
offset="0.75"
style="stop-color:rgb(14.208984%,12.689209%,13.096619%);stop-opacity:1;"
id="stop217849" />
<stop
offset="0.765625"
style="stop-color:rgb(13.948059%,12.402344%,12.802124%);stop-opacity:1;"
id="stop217851" />
<stop
offset="0.78125"
style="stop-color:rgb(13.771057%,12.207031%,12.600708%);stop-opacity:1;"
id="stop217853" />
<stop
offset="0.8125"
style="stop-color:rgb(13.725281%,12.156677%,12.548828%);stop-opacity:1;"
id="stop217855" />
<stop
offset="0.875"
style="stop-color:rgb(13.725281%,12.156677%,12.548828%);stop-opacity:1;"
id="stop217857" />
<stop
offset="1"
style="stop-color:rgb(13.725281%,12.156677%,12.548828%);stop-opacity:1;"
id="stop217859" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="10.029443"
inkscape:cy="16.956782"
inkscape:document-units="mm"
inkscape:current-layer="g258244"
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="metadata258105">
<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(-100.25783,-65.355229)">
<g
id="g258244">
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.25783,65.855457 c 0,-0.275608 0.22599,-0.500228 0.50021,-0.500228 h 3.74826 c 0.27425,0 0.49886,0.22462 0.49886,0.500228 v 8.999967 c 0,0.274229 -0.22461,0.500229 -0.49886,0.500229 h -3.74826 c -0.27422,0 -0.50021,-0.226 -0.50021,-0.500229 z m 0,0"
id="path243332"
inkscape:connector-curvature="0" />
<path
style="clip-rule:nonzero;fill:#414042;fill-rule:nonzero;stroke:none;stroke-width:0.35277775;fill-opacity:1"
d="m 104.56144,65.803091 h -3.85851 v 9.1047 h 3.85851 z m 0,0"
id="path243334"
inkscape:connector-curvature="0" />
</g>
<g
id="g258240">
<path
style="fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,71.907801 h 3.85988 v 2.99999 h -3.85988 z m 0,0"
id="path243340"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,73.241742 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243342"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,74.577062 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243344"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,73.91009 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243346"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,72.574773 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243348"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,71.907801 h 3.85988 v 0.329349 h -3.85988 z m 0,0"
id="path243350"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

+ 322
- 0
res/ComponentLibrary/CKSSThree_1.svg View File

@@ -0,0 +1,322 @@
<?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="4.7473302mm"
height="10.000424mm"
viewBox="0 0 4.7473302 10.000424"
version="1.1"
id="svg258108"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="CKSSThree_1.svg">
<defs
id="defs258102">
<linearGradient
id="linear20"
gradientUnits="userSpaceOnUse"
x1="-0.095554203"
y1="0"
x2="1.32507"
y2="0"
gradientTransform="matrix(0,6.4087959,-6.4087959,0,102.632,66.415358)">
<stop
offset="0"
style="stop-color:rgb(25.489807%,25.097656%,25.881958%);stop-opacity:1;"
id="stop217759" />
<stop
offset="0.0625"
style="stop-color:rgb(25.489807%,25.097656%,25.881958%);stop-opacity:1;"
id="stop217761" />
<stop
offset="0.078125"
style="stop-color:rgb(25.398254%,24.996948%,25.778198%);stop-opacity:1;"
id="stop217763" />
<stop
offset="0.09375"
style="stop-color:rgb(25.177002%,24.754333%,25.527954%);stop-opacity:1;"
id="stop217765" />
<stop
offset="0.109375"
style="stop-color:rgb(24.916077%,24.465942%,25.231934%);stop-opacity:1;"
id="stop217767" />
<stop
offset="0.125"
style="stop-color:rgb(24.653625%,24.179077%,24.935913%);stop-opacity:1;"
id="stop217769" />
<stop
offset="0.140625"
style="stop-color:rgb(24.3927%,23.892212%,24.639893%);stop-opacity:1;"
id="stop217771" />
<stop
offset="0.15625"
style="stop-color:rgb(24.131775%,23.605347%,24.343872%);stop-opacity:1;"
id="stop217773" />
<stop
offset="0.171875"
style="stop-color:rgb(23.87085%,23.316956%,24.047852%);stop-opacity:1;"
id="stop217775" />
<stop
offset="0.1875"
style="stop-color:rgb(23.609924%,23.03009%,23.751831%);stop-opacity:1;"
id="stop217777" />
<stop
offset="0.203125"
style="stop-color:rgb(23.348999%,22.743225%,23.455811%);stop-opacity:1;"
id="stop217779" />
<stop
offset="0.21875"
style="stop-color:rgb(23.088074%,22.45636%,23.15979%);stop-opacity:1;"
id="stop217781" />
<stop
offset="0.234375"
style="stop-color:rgb(22.825623%,22.167969%,22.86377%);stop-opacity:1;"
id="stop217783" />
<stop
offset="0.25"
style="stop-color:rgb(22.564697%,21.881104%,22.567749%);stop-opacity:1;"
id="stop217785" />
<stop
offset="0.265625"
style="stop-color:rgb(22.303772%,21.594238%,22.271729%);stop-opacity:1;"
id="stop217787" />
<stop
offset="0.28125"
style="stop-color:rgb(22.042847%,21.307373%,21.975708%);stop-opacity:1;"
id="stop217789" />
<stop
offset="0.296875"
style="stop-color:rgb(21.781921%,21.018982%,21.679688%);stop-opacity:1;"
id="stop217791" />
<stop
offset="0.3125"
style="stop-color:rgb(21.520996%,20.732117%,21.383667%);stop-opacity:1;"
id="stop217793" />
<stop
offset="0.328125"
style="stop-color:rgb(21.260071%,20.445251%,21.087646%);stop-opacity:1;"
id="stop217795" />
<stop
offset="0.34375"
style="stop-color:rgb(20.99762%,20.15686%,20.793152%);stop-opacity:1;"
id="stop217797" />
<stop
offset="0.359375"
style="stop-color:rgb(20.736694%,19.869995%,20.497131%);stop-opacity:1;"
id="stop217799" />
<stop
offset="0.375"
style="stop-color:rgb(20.475769%,19.58313%,20.201111%);stop-opacity:1;"
id="stop217801" />
<stop
offset="0.390625"
style="stop-color:rgb(20.214844%,19.296265%,19.90509%);stop-opacity:1;"
id="stop217803" />
<stop
offset="0.40625"
style="stop-color:rgb(19.953918%,19.007874%,19.60907%);stop-opacity:1;"
id="stop217805" />
<stop
offset="0.421875"
style="stop-color:rgb(19.692993%,18.721008%,19.313049%);stop-opacity:1;"
id="stop217807" />
<stop
offset="0.4375"
style="stop-color:rgb(19.432068%,18.434143%,19.017029%);stop-opacity:1;"
id="stop217809" />
<stop
offset="0.453125"
style="stop-color:rgb(19.169617%,18.147278%,18.721008%);stop-opacity:1;"
id="stop217811" />
<stop
offset="0.46875"
style="stop-color:rgb(18.908691%,17.858887%,18.424988%);stop-opacity:1;"
id="stop217813" />
<stop
offset="0.484375"
style="stop-color:rgb(18.647766%,17.572021%,18.128967%);stop-opacity:1;"
id="stop217815" />
<stop
offset="0.5"
style="stop-color:rgb(18.386841%,17.285156%,17.832947%);stop-opacity:1;"
id="stop217817" />
<stop
offset="0.515625"
style="stop-color:rgb(18.125916%,16.998291%,17.536926%);stop-opacity:1;"
id="stop217819" />
<stop
offset="0.53125"
style="stop-color:rgb(17.86499%,16.7099%,17.240906%);stop-opacity:1;"
id="stop217821" />
<stop
offset="0.546875"
style="stop-color:rgb(17.604065%,16.423035%,16.944885%);stop-opacity:1;"
id="stop217823" />
<stop
offset="0.5625"
style="stop-color:rgb(17.341614%,16.136169%,16.648865%);stop-opacity:1;"
id="stop217825" />
<stop
offset="0.578125"
style="stop-color:rgb(17.080688%,15.849304%,16.352844%);stop-opacity:1;"
id="stop217827" />
<stop
offset="0.59375"
style="stop-color:rgb(16.819763%,15.560913%,16.056824%);stop-opacity:1;"
id="stop217829" />
<stop
offset="0.609375"
style="stop-color:rgb(16.558838%,15.274048%,15.760803%);stop-opacity:1;"
id="stop217831" />
<stop
offset="0.625"
style="stop-color:rgb(16.297913%,14.987183%,15.464783%);stop-opacity:1;"
id="stop217833" />
<stop
offset="0.640625"
style="stop-color:rgb(16.036987%,14.700317%,15.168762%);stop-opacity:1;"
id="stop217835" />
<stop
offset="0.65625"
style="stop-color:rgb(15.776062%,14.411926%,14.872742%);stop-opacity:1;"
id="stop217837" />
<stop
offset="0.671875"
style="stop-color:rgb(15.513611%,14.125061%,14.576721%);stop-opacity:1;"
id="stop217839" />
<stop
offset="0.6875"
style="stop-color:rgb(15.252686%,13.838196%,14.280701%);stop-opacity:1;"
id="stop217841" />
<stop
offset="0.703125"
style="stop-color:rgb(14.99176%,13.551331%,13.98468%);stop-opacity:1;"
id="stop217843" />
<stop
offset="0.71875"
style="stop-color:rgb(14.730835%,13.262939%,13.68866%);stop-opacity:1;"
id="stop217845" />
<stop
offset="0.734375"
style="stop-color:rgb(14.46991%,12.976074%,13.392639%);stop-opacity:1;"
id="stop217847" />
<stop
offset="0.75"
style="stop-color:rgb(14.208984%,12.689209%,13.096619%);stop-opacity:1;"
id="stop217849" />
<stop
offset="0.765625"
style="stop-color:rgb(13.948059%,12.402344%,12.802124%);stop-opacity:1;"
id="stop217851" />
<stop
offset="0.78125"
style="stop-color:rgb(13.771057%,12.207031%,12.600708%);stop-opacity:1;"
id="stop217853" />
<stop
offset="0.8125"
style="stop-color:rgb(13.725281%,12.156677%,12.548828%);stop-opacity:1;"
id="stop217855" />
<stop
offset="0.875"
style="stop-color:rgb(13.725281%,12.156677%,12.548828%);stop-opacity:1;"
id="stop217857" />
<stop
offset="1"
style="stop-color:rgb(13.725281%,12.156677%,12.548828%);stop-opacity:1;"
id="stop217859" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="10.922301"
inkscape:cy="16.956782"
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="metadata258105">
<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(-100.25783,-65.355229)">
<g
id="g258244">
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.25783,65.855457 c 0,-0.275608 0.22599,-0.500228 0.50021,-0.500228 h 3.74826 c 0.27425,0 0.49886,0.22462 0.49886,0.500228 v 8.999967 c 0,0.274229 -0.22461,0.500229 -0.49886,0.500229 h -3.74826 c -0.27422,0 -0.50021,-0.226 -0.50021,-0.500229 z m 0,0"
id="path243332"
inkscape:connector-curvature="0" />
<path
style="clip-rule:nonzero;fill:#414042;fill-rule:nonzero;stroke:none;stroke-width:0.35277775;fill-opacity:1"
d="m 104.56144,65.803091 h -3.85851 v 9.1047 h 3.85851 z m 0,0"
id="path243334"
inkscape:connector-curvature="0" />
</g>
<g
id="g258240"
transform="translate(0,-3.0523548)">
<path
style="fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,71.907801 h 3.85988 v 2.99999 h -3.85988 z m 0,0"
id="path243340"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,73.241742 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243342"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,74.577062 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243344"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,73.91009 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243346"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,72.574773 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243348"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,71.907801 h 3.85988 v 0.329349 h -3.85988 z m 0,0"
id="path243350"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

+ 322
- 0
res/ComponentLibrary/CKSSThree_2.svg View File

@@ -0,0 +1,322 @@
<?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="4.7473302mm"
height="10.000424mm"
viewBox="0 0 4.7473302 10.000424"
version="1.1"
id="svg258108"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="CKSSThree_2.svg">
<defs
id="defs258102">
<linearGradient
id="linear20"
gradientUnits="userSpaceOnUse"
x1="-0.095554203"
y1="0"
x2="1.32507"
y2="0"
gradientTransform="matrix(0,6.4087959,-6.4087959,0,102.632,66.415358)">
<stop
offset="0"
style="stop-color:rgb(25.489807%,25.097656%,25.881958%);stop-opacity:1;"
id="stop217759" />
<stop
offset="0.0625"
style="stop-color:rgb(25.489807%,25.097656%,25.881958%);stop-opacity:1;"
id="stop217761" />
<stop
offset="0.078125"
style="stop-color:rgb(25.398254%,24.996948%,25.778198%);stop-opacity:1;"
id="stop217763" />
<stop
offset="0.09375"
style="stop-color:rgb(25.177002%,24.754333%,25.527954%);stop-opacity:1;"
id="stop217765" />
<stop
offset="0.109375"
style="stop-color:rgb(24.916077%,24.465942%,25.231934%);stop-opacity:1;"
id="stop217767" />
<stop
offset="0.125"
style="stop-color:rgb(24.653625%,24.179077%,24.935913%);stop-opacity:1;"
id="stop217769" />
<stop
offset="0.140625"
style="stop-color:rgb(24.3927%,23.892212%,24.639893%);stop-opacity:1;"
id="stop217771" />
<stop
offset="0.15625"
style="stop-color:rgb(24.131775%,23.605347%,24.343872%);stop-opacity:1;"
id="stop217773" />
<stop
offset="0.171875"
style="stop-color:rgb(23.87085%,23.316956%,24.047852%);stop-opacity:1;"
id="stop217775" />
<stop
offset="0.1875"
style="stop-color:rgb(23.609924%,23.03009%,23.751831%);stop-opacity:1;"
id="stop217777" />
<stop
offset="0.203125"
style="stop-color:rgb(23.348999%,22.743225%,23.455811%);stop-opacity:1;"
id="stop217779" />
<stop
offset="0.21875"
style="stop-color:rgb(23.088074%,22.45636%,23.15979%);stop-opacity:1;"
id="stop217781" />
<stop
offset="0.234375"
style="stop-color:rgb(22.825623%,22.167969%,22.86377%);stop-opacity:1;"
id="stop217783" />
<stop
offset="0.25"
style="stop-color:rgb(22.564697%,21.881104%,22.567749%);stop-opacity:1;"
id="stop217785" />
<stop
offset="0.265625"
style="stop-color:rgb(22.303772%,21.594238%,22.271729%);stop-opacity:1;"
id="stop217787" />
<stop
offset="0.28125"
style="stop-color:rgb(22.042847%,21.307373%,21.975708%);stop-opacity:1;"
id="stop217789" />
<stop
offset="0.296875"
style="stop-color:rgb(21.781921%,21.018982%,21.679688%);stop-opacity:1;"
id="stop217791" />
<stop
offset="0.3125"
style="stop-color:rgb(21.520996%,20.732117%,21.383667%);stop-opacity:1;"
id="stop217793" />
<stop
offset="0.328125"
style="stop-color:rgb(21.260071%,20.445251%,21.087646%);stop-opacity:1;"
id="stop217795" />
<stop
offset="0.34375"
style="stop-color:rgb(20.99762%,20.15686%,20.793152%);stop-opacity:1;"
id="stop217797" />
<stop
offset="0.359375"
style="stop-color:rgb(20.736694%,19.869995%,20.497131%);stop-opacity:1;"
id="stop217799" />
<stop
offset="0.375"
style="stop-color:rgb(20.475769%,19.58313%,20.201111%);stop-opacity:1;"
id="stop217801" />
<stop
offset="0.390625"
style="stop-color:rgb(20.214844%,19.296265%,19.90509%);stop-opacity:1;"
id="stop217803" />
<stop
offset="0.40625"
style="stop-color:rgb(19.953918%,19.007874%,19.60907%);stop-opacity:1;"
id="stop217805" />
<stop
offset="0.421875"
style="stop-color:rgb(19.692993%,18.721008%,19.313049%);stop-opacity:1;"
id="stop217807" />
<stop
offset="0.4375"
style="stop-color:rgb(19.432068%,18.434143%,19.017029%);stop-opacity:1;"
id="stop217809" />
<stop
offset="0.453125"
style="stop-color:rgb(19.169617%,18.147278%,18.721008%);stop-opacity:1;"
id="stop217811" />
<stop
offset="0.46875"
style="stop-color:rgb(18.908691%,17.858887%,18.424988%);stop-opacity:1;"
id="stop217813" />
<stop
offset="0.484375"
style="stop-color:rgb(18.647766%,17.572021%,18.128967%);stop-opacity:1;"
id="stop217815" />
<stop
offset="0.5"
style="stop-color:rgb(18.386841%,17.285156%,17.832947%);stop-opacity:1;"
id="stop217817" />
<stop
offset="0.515625"
style="stop-color:rgb(18.125916%,16.998291%,17.536926%);stop-opacity:1;"
id="stop217819" />
<stop
offset="0.53125"
style="stop-color:rgb(17.86499%,16.7099%,17.240906%);stop-opacity:1;"
id="stop217821" />
<stop
offset="0.546875"
style="stop-color:rgb(17.604065%,16.423035%,16.944885%);stop-opacity:1;"
id="stop217823" />
<stop
offset="0.5625"
style="stop-color:rgb(17.341614%,16.136169%,16.648865%);stop-opacity:1;"
id="stop217825" />
<stop
offset="0.578125"
style="stop-color:rgb(17.080688%,15.849304%,16.352844%);stop-opacity:1;"
id="stop217827" />
<stop
offset="0.59375"
style="stop-color:rgb(16.819763%,15.560913%,16.056824%);stop-opacity:1;"
id="stop217829" />
<stop
offset="0.609375"
style="stop-color:rgb(16.558838%,15.274048%,15.760803%);stop-opacity:1;"
id="stop217831" />
<stop
offset="0.625"
style="stop-color:rgb(16.297913%,14.987183%,15.464783%);stop-opacity:1;"
id="stop217833" />
<stop
offset="0.640625"
style="stop-color:rgb(16.036987%,14.700317%,15.168762%);stop-opacity:1;"
id="stop217835" />
<stop
offset="0.65625"
style="stop-color:rgb(15.776062%,14.411926%,14.872742%);stop-opacity:1;"
id="stop217837" />
<stop
offset="0.671875"
style="stop-color:rgb(15.513611%,14.125061%,14.576721%);stop-opacity:1;"
id="stop217839" />
<stop
offset="0.6875"
style="stop-color:rgb(15.252686%,13.838196%,14.280701%);stop-opacity:1;"
id="stop217841" />
<stop
offset="0.703125"
style="stop-color:rgb(14.99176%,13.551331%,13.98468%);stop-opacity:1;"
id="stop217843" />
<stop
offset="0.71875"
style="stop-color:rgb(14.730835%,13.262939%,13.68866%);stop-opacity:1;"
id="stop217845" />
<stop
offset="0.734375"
style="stop-color:rgb(14.46991%,12.976074%,13.392639%);stop-opacity:1;"
id="stop217847" />
<stop
offset="0.75"
style="stop-color:rgb(14.208984%,12.689209%,13.096619%);stop-opacity:1;"
id="stop217849" />
<stop
offset="0.765625"
style="stop-color:rgb(13.948059%,12.402344%,12.802124%);stop-opacity:1;"
id="stop217851" />
<stop
offset="0.78125"
style="stop-color:rgb(13.771057%,12.207031%,12.600708%);stop-opacity:1;"
id="stop217853" />
<stop
offset="0.8125"
style="stop-color:rgb(13.725281%,12.156677%,12.548828%);stop-opacity:1;"
id="stop217855" />
<stop
offset="0.875"
style="stop-color:rgb(13.725281%,12.156677%,12.548828%);stop-opacity:1;"
id="stop217857" />
<stop
offset="1"
style="stop-color:rgb(13.725281%,12.156677%,12.548828%);stop-opacity:1;"
id="stop217859" />
</linearGradient>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.2"
inkscape:cx="10.922301"
inkscape:cy="16.956782"
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="metadata258105">
<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(-100.25783,-65.355229)">
<g
id="g258244">
<path
style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.25783,65.855457 c 0,-0.275608 0.22599,-0.500228 0.50021,-0.500228 h 3.74826 c 0.27425,0 0.49886,0.22462 0.49886,0.500228 v 8.999967 c 0,0.274229 -0.22461,0.500229 -0.49886,0.500229 h -3.74826 c -0.27422,0 -0.50021,-0.226 -0.50021,-0.500229 z m 0,0"
id="path243332"
inkscape:connector-curvature="0" />
<path
style="clip-rule:nonzero;fill:#414042;fill-rule:nonzero;stroke:none;stroke-width:0.35277775;fill-opacity:1"
d="m 104.56144,65.803091 h -3.85851 v 9.1047 h 3.85851 z m 0,0"
id="path243334"
inkscape:connector-curvature="0" />
</g>
<g
id="g258240"
transform="translate(0,-6.10471)">
<path
style="fill:#424242;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,71.907801 h 3.85988 v 2.99999 h -3.85988 z m 0,0"
id="path243340"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,73.241742 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243342"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,74.577062 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243344"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,73.91009 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243346"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,72.574773 h 3.85988 v 0.330729 h -3.85988 z m 0,0"
id="path243348"
inkscape:connector-curvature="0" />
<path
style="fill:#979797;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
d="m 100.70156,71.907801 h 3.85988 v 0.329349 h -3.85988 z m 0,0"
id="path243350"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

+ 2
- 1
src/app.cpp View File

@@ -12,7 +12,8 @@ std::string gApplicationVersion =
#else #else
""; "";
#endif #endif
std::string gApiHost = "http://api.vcvrack.com";
std::string gApiHost = "https://api.vcvrack.com";
// std::string gApiHost = "http://localhost:8081";


RackWidget *gRackWidget = NULL; RackWidget *gRackWidget = NULL;
Toolbar *gToolbar = NULL; Toolbar *gToolbar = NULL;


+ 5
- 9
src/app/AddModuleWindow.cpp View File

@@ -70,7 +70,7 @@ struct MetadataMenu : ListMenu {


// Plugin metadata // Plugin metadata
if (!model->plugin->website.empty()) { if (!model->plugin->website.empty()) {
addChild(construct<UrlItem>(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->path));
addChild(construct<UrlItem>(&MenuEntry::text, "Website", &UrlItem::url, model->plugin->website));
} }
if (!model->plugin->manual.empty()) { if (!model->plugin->manual.empty()) {
addChild(construct<UrlItem>(&MenuEntry::text, "Manual", &UrlItem::url, model->plugin->manual)); addChild(construct<UrlItem>(&MenuEntry::text, "Manual", &UrlItem::url, model->plugin->manual));
@@ -120,10 +120,6 @@ struct ModelItem : MenuItem {
sModel = model; sModel = model;
MenuItem::onMouseEnter(e); MenuItem::onMouseEnter(e);
} }
void onMouseLeave(EventMouseLeave &e) override {
sModel = NULL;
MenuItem::onMouseLeave(e);
}
}; };




@@ -183,6 +179,8 @@ struct ManufacturerMenu : ListMenu {
std::string filter; std::string filter;


ManufacturerMenu() { ManufacturerMenu() {
addChild(construct<MenuLabel>(&MenuLabel::text, "Manufacturers"));

// Collect manufacturer names // Collect manufacturer names
std::set<std::string> manufacturers; std::set<std::string> manufacturers;
for (Plugin *plugin : gPlugins) { for (Plugin *plugin : gPlugins) {
@@ -205,7 +203,8 @@ struct ManufacturerMenu : ListMenu {
// Make children with a matching model visible // Make children with a matching model visible
for (Widget *child : children) { for (Widget *child : children) {
MenuItem *item = dynamic_cast<MenuItem*>(child); MenuItem *item = dynamic_cast<MenuItem*>(child);
assert(item);
if (!item)
continue;


std::string manufacturer = item->text; std::string manufacturer = item->text;
for (Plugin *plugin : gPlugins) { for (Plugin *plugin : gPlugins) {
@@ -282,9 +281,6 @@ AddModuleWindow::AddModuleWindow() {
metadataScroll->box.pos = Vec(400, posY); metadataScroll->box.pos = Vec(400, posY);
metadataScroll->box.size = Vec(200, box.size.y - posY); metadataScroll->box.size = Vec(200, box.size.y - posY);
addChild(metadataScroll); addChild(metadataScroll);

// NVGcolor c = bndTransparent(nvgRGB(0, 0, 0));
NVGcolor c = bndGetTheme()->nodeTheme.nodeBackdropColor;
} }






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

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


void Knob::onDragMove(EventDragMove &e) { void Knob::onDragMove(EventDragMove &e) {
// Drag slower if Mod // 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()) if (guiIsModPressed())
delta /= 16.0; delta /= 16.0;
dragValue += delta; dragValue += delta;


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

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




void LightWidget::draw(NVGcontext *vg) { 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.r = clampf(color.r, 0.0, 1.0);
color.g = clampf(color.g, 0.0, 1.0); color.g = clampf(color.g, 0.0, 1.0);
color.b = clampf(color.b, 0.0, 1.0); color.b = clampf(color.b, 0.0, 1.0);
color.a = clampf(color.a, 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); nvgBeginPath(vg);
nvgCircle(vg, radius, radius, radius); nvgCircle(vg, radius, radius, radius);

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


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


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


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


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

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






} // namespace rack } // namespace rack

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

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


// manufacturer
// plugin
json_object_set_new(rootJ, "plugin", json_string(model->plugin->slug.c_str())); 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 // model
json_object_set_new(rootJ, "model", json_string(model->slug.c_str())); json_object_set_new(rootJ, "model", json_string(model->slug.c_str()));
// pos // pos
@@ -118,12 +121,24 @@ void ModuleWidget::disconnect() {
} }
} }


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

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

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


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


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


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


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


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


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


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


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


return menu; return menu;
} }


+ 52
- 12
src/app/PluginManagerWidget.cpp View File

@@ -1,11 +1,57 @@
#include <thread> #include <thread>
#include "app.hpp" #include "app.hpp"
#include "plugin.hpp" #include "plugin.hpp"
#include "gui.hpp"
#include "../ext/osdialog/osdialog.h"




namespace rack { namespace rack {




struct SyncButton : Button {
bool checked = false;
bool available = false;
bool completed = false;

void step() override {
if (!checked) {
std::thread t([this]() {
if (pluginSync(true))
available = true;
});
t.detach();
checked = true;
}
if (completed) {
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "All plugins have been updated. Close Rack and re-launch it to load new updates.")) {
guiClose();
}
completed = false;
}
}
void draw(NVGcontext *vg) override {
Button::draw(vg);
if (available) {
// Notification circle
nvgBeginPath(vg);
nvgCircle(vg, 3, 3, 4.0);
nvgFillColor(vg, nvgRGBf(1.0, 0.0, 0.0));
nvgFill(vg);
nvgStrokeColor(vg, nvgRGBf(0.5, 0.0, 0.0));
nvgStroke(vg);
}
}
void onAction(EventAction &e) override {
available = false;
std::thread t([this]() {
if (pluginSync(false))
completed = true;
});
t.detach();
}
};


PluginManagerWidget::PluginManagerWidget() { PluginManagerWidget::PluginManagerWidget() {
box.size.y = BND_WIDGET_HEIGHT; box.size.y = BND_WIDGET_HEIGHT;
float margin = 5; float margin = 5;
@@ -91,19 +137,13 @@ PluginManagerWidget::PluginManagerWidget() {
manageWidget->addChild(manageButton); manageWidget->addChild(manageButton);
pos.x += manageButton->box.size.x; pos.x += manageButton->box.size.x;


struct RefreshButton : Button {
void onAction(EventAction &e) override {
std::thread t(pluginRefresh);
t.detach();
}
};
pos.x += margin; pos.x += margin;
Button *refreshButton = new RefreshButton();
refreshButton->box.pos = pos;
refreshButton->box.size.x = 125;
refreshButton->text = "Refresh plugins";
manageWidget->addChild(refreshButton);
pos.x += refreshButton->box.size.x;
Button *syncButton = new SyncButton();
syncButton->box.pos = pos;
syncButton->box.size.x = 125;
syncButton->text = "Update plugins";
manageWidget->addChild(syncButton);
pos.x += syncButton->box.size.x;


struct LogOutButton : Button { struct LogOutButton : Button {
void onAction(EventAction &e) override { void onAction(EventAction &e) override {


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

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






+ 3
- 2
src/app/RackScene.cpp View File

@@ -122,7 +122,6 @@ void RackScene::onHoverKey(EventHoverKey &e) {
Widget::onHoverKey(e); Widget::onHoverKey(e);
} }



void RackScene::onPathDrop(EventPathDrop &e) { void RackScene::onPathDrop(EventPathDrop &e) {
if (e.paths.size() >= 1) { if (e.paths.size() >= 1) {
const std::string& firstPath = e.paths.front(); const std::string& firstPath = e.paths.front();
@@ -131,8 +130,10 @@ void RackScene::onPathDrop(EventPathDrop &e) {
e.consumed = true; e.consumed = true;
} }
} }
}


if (!e.consumed)
Scene::onPathDrop(e);
}




} // namespace rack } // namespace rack

+ 37
- 13
src/app/RackWidget.cpp View File

@@ -38,11 +38,32 @@ void RackWidget::clear() {
wireContainer->clearChildren(); wireContainer->clearChildren();
moduleContainer->clearChildren(); moduleContainer->clearChildren();
lastPath = ""; 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() { void RackWidget::reset() {
clear();
loadPatch(assetLocal("template.vcv"));
if (osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Clear your patch and start over?")) {
clear();
loadPatch(assetLocal("template.vcv"));
}
} }


void RackWidget::openDialog() { void RackWidget::openDialog() {
@@ -81,20 +102,19 @@ void RackWidget::saveAsDialog() {
} }
} }



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


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


fclose(file);
json_decref(rootJ);
} }


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


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


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


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


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


@@ -321,8 +345,8 @@ bool RackWidget::requestModuleBoxNearest(ModuleWidget *m, Rect box) {
int x0 = roundf(box.pos.x / RACK_GRID_WIDTH); int x0 = roundf(box.pos.x / RACK_GRID_WIDTH);
int y0 = roundf(box.pos.y / RACK_GRID_HEIGHT); int y0 = roundf(box.pos.y / RACK_GRID_HEIGHT);
std::vector<Vec> positions; 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)); positions.push_back(Vec(x * RACK_GRID_WIDTH, y * RACK_GRID_HEIGHT));
} }
} }


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

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




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


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


void SVGSlider::step() {
void SVGFader::step() {
if (dirty) { if (dirty) {
// Update handle position // Update handle position
Vec handlePos = Vec(rescalef(value, minValue, maxValue, minHandlePos.x, maxHandlePos.x), rescalef(value, minValue, maxValue, minHandlePos.y, maxHandlePos.y)); 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(); FramebufferWidget::step();
} }


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





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

@@ -23,7 +23,9 @@ void SVGKnob::step() {
// Re-transform TransformWidget if dirty // Re-transform TransformWidget if dirty
if (dirty) { if (dirty) {
tw->box.size = box.size; 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(); tw->identity();
// Scale SVG to box // Scale SVG to box
tw->scale(box.size.div(sw->box.size)); tw->scale(box.size.div(sw->box.size));


+ 2
- 5
src/app/SVGSwitch.cpp View File

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


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

void SVGSwitch::onChange(EventChange &e) { void SVGSwitch::onChange(EventChange &e) {
assert(frames.size() > 0); assert(frames.size() > 0);
int index = clampi((int) roundf(value), 0, frames.size() - 1);
float valueScaled = rescalef(value, minValue, maxValue, 0, frames.size() - 1);
int index = clampi((int) roundf(valueScaled), 0, frames.size() - 1);
sw->setSVG(frames[index]); sw->setSVG(frames[index]);
dirty = true; dirty = true;
Switch::onChange(e); Switch::onChange(e);


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

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


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


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


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


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

@@ -86,11 +86,6 @@ static int lastWireColorId = -1;
WireWidget::WireWidget() { WireWidget::WireWidget() {
lastWireColorId = (lastWireColorId + 1) % LENGTHOF(wireColors); lastWireColorId = (lastWireColorId + 1) % LENGTHOF(wireColors);
color = wireColors[lastWireColorId]; 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() { WireWidget::~WireWidget() {


+ 286
- 157
src/core/AudioInterface.cpp View File

@@ -1,13 +1,14 @@
#include <assert.h> #include <assert.h>
#include <mutex> #include <mutex>
#include <thread> #include <thread>
#include <algorithm>
#include "core.hpp" #include "core.hpp"
#include "dsp/samplerate.hpp" #include "dsp/samplerate.hpp"
#include "dsp/ringbuffer.hpp" #include "dsp/ringbuffer.hpp"


#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsuggest-override" #pragma GCC diagnostic ignored "-Wsuggest-override"
#include <rtaudio/RtAudio.h>
#include <RtAudio.h>
#pragma GCC diagnostic pop #pragma GCC diagnostic pop




@@ -28,18 +29,14 @@ struct AudioInterface : Module {
NUM_OUTPUTS = AUDIO1_OUTPUT + 8 NUM_OUTPUTS = AUDIO1_OUTPUT + 8
}; };


RtAudio stream;
RtAudio *stream = NULL;
// Stream properties // Stream properties
int deviceId = -1;
int device = -1;
float sampleRate = 44100.0; float sampleRate = 44100.0;
int blockSize = 256; int blockSize = 256;
int numOutputs = 0; int numOutputs = 0;
int numInputs = 0; int numInputs = 0;


// Used because the GUI thread and Rack thread can both interact with this class
std::mutex bufferMutex;
bool streamRunning;

SampleRateConverter<8> inputSrc; SampleRateConverter<8> inputSrc;
SampleRateConverter<8> outputSrc; SampleRateConverter<8> outputSrc;


@@ -49,85 +46,116 @@ struct AudioInterface : Module {
// in device's sample rate // in device's sample rate
DoubleRingBuffer<Frame<8>, (1<<15)> inputSrcBuffer; DoubleRingBuffer<Frame<8>, (1<<15)> inputSrcBuffer;


AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {}
AudioInterface() : Module(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS) {
setDriver(RtAudio::UNSPECIFIED);
}
~AudioInterface() { ~AudioInterface() {
closeDevice();
closeStream();
} }


void step() override; void step() override;
void stepStream(const float *input, float *output, int numFrames); void stepStream(const float *input, float *output, int numFrames);


int getDeviceCount(); int getDeviceCount();
std::string getDeviceName(int deviceId);

void openDevice(int deviceId, float sampleRate, int blockSize);
void closeDevice();

void setDeviceId(int deviceId) {
openDevice(deviceId, sampleRate, blockSize);
}
void setSampleRate(float sampleRate) {
openDevice(deviceId, sampleRate, blockSize);
}
void setBlockSize(int blockSize) {
openDevice(deviceId, sampleRate, blockSize);
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 *toJson() override {
json_t *rootJ = json_object(); json_t *rootJ = json_object();
if (deviceId >= 0) {
std::string deviceName = getDeviceName(deviceId);
json_object_set_new(rootJ, "deviceName", json_string(deviceName.c_str()));
json_object_set_new(rootJ, "sampleRate", json_real(sampleRate));
json_object_set_new(rootJ, "blockSize", json_integer(blockSize));
}
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));
return rootJ; return rootJ;
} }


void fromJson(json_t *rootJ) override { void fromJson(json_t *rootJ) override {
json_t *deviceNameJ = json_object_get(rootJ, "deviceName");
if (deviceNameJ) {
std::string deviceName = json_string_value(deviceNameJ);
for (int i = 0; i < getDeviceCount(); i++) {
if (deviceName == getDeviceName(i)) {
setDeviceId(i);
break;
}
}
}
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"); json_t *sampleRateJ = json_object_get(rootJ, "sampleRate");
if (sampleRateJ) {
setSampleRate(json_number_value(sampleRateJ));
}
if (sampleRateJ)
sampleRate = json_number_value(sampleRateJ);


json_t *blockSizeJ = json_object_get(rootJ, "blockSize"); json_t *blockSizeJ = json_object_get(rootJ, "blockSize");
if (blockSizeJ) {
setBlockSize(json_integer_value(blockSizeJ));
}
if (blockSizeJ)
blockSize = json_integer_value(blockSizeJ);

openStream();
} }


void reset() override {
closeDevice();
void onReset() override {
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() { 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 // Read/write stream if we have enough input, OR the output buffer is empty if we have no input
if (numOutputs > 0) { if (numOutputs > 0) {
while (inputSrcBuffer.size() >= blockSize && streamRunning) {
std::this_thread::sleep_for(std::chrono::duration<float>(100e-6));
}
TIMED_SLEEP_LOCK(inputSrcBuffer.size() < blockSize, 100e-6, 0.2);
} }
else if (numInputs > 0) { else if (numInputs > 0) {
while (outputBuffer.empty() && streamRunning) {
std::this_thread::sleep_for(std::chrono::duration<float>(100e-6));
}
TIMED_SLEEP_LOCK(!outputBuffer.empty(), 100e-6, 0.2);
} }


std::lock_guard<std::mutex> lock(bufferMutex);

// Get input and pass it through the sample rate converter // Get input and pass it through the sample rate converter
if (numOutputs > 0) { if (numOutputs > 0) {
if (!inputBuffer.full()) { if (!inputBuffer.full()) {
@@ -141,7 +169,7 @@ void AudioInterface::step() {
// Once full, sample rate convert the input // Once full, sample rate convert the input
// inputBuffer -> SRC -> inputSrcBuffer // inputBuffer -> SRC -> inputSrcBuffer
if (inputBuffer.full()) { if (inputBuffer.full()) {
inputSrc.setRatio(sampleRate / engineGetSampleRate());
inputSrc.setRates(engineGetSampleRate(), sampleRate);
int inLen = inputBuffer.size(); int inLen = inputBuffer.size();
int outLen = inputSrcBuffer.capacity(); int outLen = inputSrcBuffer.capacity();
inputSrc.process(inputBuffer.startData(), &inLen, inputSrcBuffer.endData(), &outLen); inputSrc.process(inputBuffer.startData(), &inLen, inputSrcBuffer.endData(), &outLen);
@@ -160,16 +188,19 @@ void AudioInterface::step() {
} }


void AudioInterface::stepStream(const float *input, float *output, int numFrames) { void AudioInterface::stepStream(const float *input, float *output, int numFrames) {
if (gPaused) {
memset(output, 0, sizeof(float) * numOutputs * numFrames);
return;
}

if (numOutputs > 0) { if (numOutputs > 0) {
// Wait for enough input before proceeding // Wait for enough input before proceeding
while (inputSrcBuffer.size() < numFrames) {
if (!streamRunning)
return;
std::this_thread::sleep_for(std::chrono::duration<float>(100e-6));
}
TIMED_SLEEP_LOCK(inputSrcBuffer.size() >= numFrames, 100e-6, 0.2);
}
else if (numInputs > 0) {
TIMED_SLEEP_LOCK(outputBuffer.empty(), 100e-6, 0.2);
} }


std::lock_guard<std::mutex> lock(bufferMutex);
// input stream -> output buffer // input stream -> output buffer
if (numInputs > 0) { if (numInputs > 0) {
Frame<8> inputFrames[numFrames]; Frame<8> inputFrames[numFrames];
@@ -180,7 +211,7 @@ void AudioInterface::stepStream(const float *input, float *output, int numFrames
} }


// Pass output through sample rate converter // Pass output through sample rate converter
outputSrc.setRatio(engineGetSampleRate() / sampleRate);
outputSrc.setRates(sampleRate, engineGetSampleRate());
int inLen = numFrames; int inLen = numFrames;
int outLen = outputBuffer.capacity(); int outLen = outputBuffer.capacity();
outputSrc.process(inputFrames, &inLen, outputBuffer.endData(), &outLen); outputSrc.process(inputFrames, &inLen, outputBuffer.endData(), &outLen);
@@ -190,27 +221,32 @@ void AudioInterface::stepStream(const float *input, float *output, int numFrames
// input buffer -> output stream // input buffer -> output stream
if (numOutputs > 0) { if (numOutputs > 0) {
for (int i = 0; i < numFrames; i++) { for (int i = 0; i < numFrames; i++) {
if (inputSrcBuffer.empty())
break;
Frame<8> f = inputSrcBuffer.shift();
Frame<8> f;
if (inputSrcBuffer.empty()) {
memset(&f, 0, sizeof(f));
}
else {
f = inputSrcBuffer.shift();
}
for (int c = 0; c < numOutputs; c++) { for (int c = 0; c < numOutputs; c++) {
output[i*numOutputs + c] = (c < 8) ? clampf(f.samples[c], -1.0, 1.0) : 0.0;
output[i*numOutputs + c] = clampf(f.samples[c], -1.0, 1.0);
} }
} }
} }
} }


int AudioInterface::getDeviceCount() { int AudioInterface::getDeviceCount() {
return stream.getDeviceCount();
if (!stream)
return 0;
return stream->getDeviceCount();
} }


std::string AudioInterface::getDeviceName(int deviceId) {
if (deviceId < 0)
std::string AudioInterface::getDeviceName(int device) {
if (!stream || device < 0)
return ""; return "";


std::lock_guard<std::mutex> lock(bufferMutex);
try { try {
RtAudio::DeviceInfo deviceInfo = stream.getDeviceInfo(deviceId);
RtAudio::DeviceInfo deviceInfo = stream->getDeviceInfo(device);
return stringf("%s (%d in, %d out)", deviceInfo.name.c_str(), deviceInfo.inputChannels, deviceInfo.outputChannels); return stringf("%s (%d in, %d out)", deviceInfo.name.c_str(), deviceInfo.inputChannels, deviceInfo.outputChannels);
} }
catch (RtAudioError &e) { catch (RtAudioError &e) {
@@ -226,18 +262,17 @@ static int rtCallback(void *outputBuffer, void *inputBuffer, unsigned int nFrame
return 0; return 0;
} }


void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) {
closeDevice();
std::lock_guard<std::mutex> lock(bufferMutex);

this->sampleRate = sampleRate;
this->blockSize = blockSize;
void AudioInterface::openStream() {
int device = this->device;
closeStream();
if (!stream)
return;


// Open new device // Open new device
if (deviceId >= 0) {
if (device >= 0) {
RtAudio::DeviceInfo deviceInfo; RtAudio::DeviceInfo deviceInfo;
try { try {
deviceInfo = stream.getDeviceInfo(deviceId);
deviceInfo = stream->getDeviceInfo(device);
} }
catch (RtAudioError &e) { catch (RtAudioError &e) {
warn("Failed to query audio device: %s", e.what()); warn("Failed to query audio device: %s", e.what());
@@ -247,23 +282,37 @@ void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) {
numOutputs = mini(deviceInfo.outputChannels, 8); numOutputs = mini(deviceInfo.outputChannels, 8);
numInputs = mini(deviceInfo.inputChannels, 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; RtAudio::StreamParameters outParameters;
outParameters.deviceId = deviceId;
outParameters.deviceId = device;
outParameters.nChannels = numOutputs; outParameters.nChannels = numOutputs;


RtAudio::StreamParameters inParameters; RtAudio::StreamParameters inParameters;
inParameters.deviceId = deviceId;
inParameters.deviceId = device;
inParameters.nChannels = numInputs; inParameters.nChannels = numInputs;


RtAudio::StreamOptions options; RtAudio::StreamOptions options;
options.flags |= RTAUDIO_MINIMIZE_LATENCY;
// 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 { try {
// Don't use stream parameters if 0 input or output channels // Don't use stream parameters if 0 input or output channels
stream.openStream(
debug("Opening audio stream %d", device);
stream->openStream(
numOutputs == 0 ? NULL : &outParameters, numOutputs == 0 ? NULL : &outParameters,
numInputs == 0 ? NULL : &inParameters, numInputs == 0 ? NULL : &inParameters,
RTAUDIO_FLOAT32, sampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL);
RTAUDIO_FLOAT32, closestSampleRate, (unsigned int*) &blockSize, &rtCallback, this, &options, NULL);
} }
catch (RtAudioError &e) { catch (RtAudioError &e) {
warn("Failed to open audio stream: %s", e.what()); warn("Failed to open audio stream: %s", e.what());
@@ -271,37 +320,44 @@ void AudioInterface::openDevice(int deviceId, float sampleRate, int blockSize) {
} }


try { try {
stream.startStream();
debug("Starting audio stream %d", device);
stream->startStream();
} }
catch (RtAudioError &e) { catch (RtAudioError &e) {
warn("Failed to start audio stream: %s", e.what()); warn("Failed to start audio stream: %s", e.what());
return; return;
} }


streamRunning = true;

this->sampleRate = stream.getStreamSampleRate();
this->deviceId = deviceId;
// Update sample rate because this may have changed
this->sampleRate = stream->getStreamSampleRate();
this->device = device;
} }
} }


void AudioInterface::closeDevice() {
std::lock_guard<std::mutex> lock(bufferMutex);

if (stream.isStreamOpen()) {
streamRunning = false;
try {
stream.abortStream();
stream.closeStream();
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());
}
} }
catch (RtAudioError &e) {
warn("Failed to abort stream %s", e.what());
return;
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 // Reset stream settings
deviceId = -1;
device = -1;
numOutputs = 0; numOutputs = 0;
numInputs = 0; numInputs = 0;


@@ -313,16 +369,71 @@ void AudioInterface::closeDevice() {
outputSrc.reset(); outputSrc.reset();
} }


std::vector<float> AudioInterface::getSampleRates() {
std::vector<float> allowedSampleRates = {44100, 48000, 88200, 96000, 176400, 192000};
if (!stream || device < 0)
return allowedSampleRates;


struct AudioItem : MenuItem {
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; AudioInterface *audioInterface;
int deviceId;
int driver;
void onAction(EventAction &e) override { void onAction(EventAction &e) override {
audioInterface->setDeviceId(deviceId);
audioInterface->setDriver(driver);
} }
}; };


struct AudioChoice : ChoiceButton {
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->addChild(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; AudioInterface *audioInterface;
void onAction(EventAction &e) override { void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu(); Menu *menu = gScene->createMenu();
@@ -331,23 +442,26 @@ struct AudioChoice : ChoiceButton {


int deviceCount = audioInterface->getDeviceCount(); int deviceCount = audioInterface->getDeviceCount();
{ {
AudioItem *audioItem = new AudioItem();
AudioDeviceItem *audioItem = new AudioDeviceItem();
audioItem->audioInterface = audioInterface; audioItem->audioInterface = audioInterface;
audioItem->deviceId = -1;
audioItem->device = -1;
audioItem->text = "No device"; audioItem->text = "No device";
menu->pushChild(audioItem);
menu->addChild(audioItem);
} }
for (int deviceId = 0; deviceId < deviceCount; deviceId++) {
AudioItem *audioItem = new AudioItem();
for (int device = 0; device < deviceCount; device++) {
AudioDeviceItem *audioItem = new AudioDeviceItem();
audioItem->audioInterface = audioInterface; audioItem->audioInterface = audioInterface;
audioItem->deviceId = deviceId;
audioItem->text = audioInterface->getDeviceName(deviceId);
menu->pushChild(audioItem);
audioItem->device = device;
audioItem->text = audioInterface->getDeviceName(device);
menu->addChild(audioItem);
} }
} }
void step() override { void step() override {
std::string name = audioInterface->getDeviceName(audioInterface->deviceId);
text = ellipsize(name, 24);
if (lastDeviceId != audioInterface->device) {
std::string name = audioInterface->getDeviceName(audioInterface->device);
text = ellipsize(name, 24);
lastDeviceId = audioInterface->device;
}
} }
}; };


@@ -356,7 +470,8 @@ struct SampleRateItem : MenuItem {
AudioInterface *audioInterface; AudioInterface *audioInterface;
float sampleRate; float sampleRate;
void onAction(EventAction &e) override { void onAction(EventAction &e) override {
audioInterface->setSampleRate(sampleRate);
audioInterface->sampleRate = sampleRate;
audioInterface->openStream();
} }
}; };


@@ -367,14 +482,12 @@ struct SampleRateChoice : ChoiceButton {
menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round(); menu->box.pos = getAbsoluteOffset(Vec(0, box.size.y)).round();
menu->box.size.x = box.size.x; menu->box.size.x = box.size.x;


const float sampleRates[6] = {44100, 48000, 88200, 96000, 176400, 192000};
int sampleRatesLen = sizeof(sampleRates) / sizeof(sampleRates[0]);
for (int i = 0; i < sampleRatesLen; i++) {
for (float sampleRate : audioInterface->getSampleRates()) {
SampleRateItem *item = new SampleRateItem(); SampleRateItem *item = new SampleRateItem();
item->audioInterface = audioInterface; item->audioInterface = audioInterface;
item->sampleRate = sampleRates[i];
item->text = stringf("%.0f Hz", sampleRates[i]);
menu->pushChild(item);
item->sampleRate = sampleRate;
item->text = stringf("%.0f Hz", sampleRate);
menu->addChild(item);
} }
} }
void step() override { void step() override {
@@ -387,7 +500,8 @@ struct BlockSizeItem : MenuItem {
AudioInterface *audioInterface; AudioInterface *audioInterface;
int blockSize; int blockSize;
void onAction(EventAction &e) override { void onAction(EventAction &e) override {
audioInterface->setBlockSize(blockSize);
audioInterface->blockSize = blockSize;
audioInterface->openStream();
} }
}; };


@@ -405,7 +519,7 @@ struct BlockSizeChoice : ChoiceButton {
item->audioInterface = audioInterface; item->audioInterface = audioInterface;
item->blockSize = blockSizes[i]; item->blockSize = blockSizes[i];
item->text = stringf("%d", blockSizes[i]); item->text = stringf("%d", blockSizes[i]);
menu->pushChild(item);
menu->addChild(item);
} }
} }
void step() override { void step() override {
@@ -429,62 +543,77 @@ AudioInterfaceWidget::AudioInterfaceWidget() {
// addChild(createScrew<ScrewSilver>(Vec(15, 365))); // addChild(createScrew<ScrewSilver>(Vec(15, 365)));
// addChild(createScrew<ScrewSilver>(Vec(box.size.x-30, 365))); // addChild(createScrew<ScrewSilver>(Vec(box.size.x-30, 365)));


float margin = 5;
Vec margin = Vec(5, 2);
float labelHeight = 15; float labelHeight = 15;
float yPos = margin;
float yPos = margin.y;
float xPos; float xPos;


{ {
Label *label = new Label(); Label *label = new Label();
label->box.pos = Vec(margin, yPos);
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"; label->text = "Audio device";
addChild(label); addChild(label);
yPos += labelHeight + margin;
yPos += labelHeight + margin.y;


AudioChoice *choice = new AudioChoice();
choice->audioInterface = dynamic_cast<AudioInterface*>(module);
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 2*margin;
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); addChild(choice);
yPos += choice->box.size.y + margin;
yPos += choice->box.size.y + margin.y;
} }


{ {
Label *label = new Label(); Label *label = new Label();
label->box.pos = Vec(margin, yPos);
label->box.pos = Vec(margin.x, yPos);
label->text = "Sample rate"; label->text = "Sample rate";
addChild(label); addChild(label);
yPos += labelHeight + margin;
yPos += labelHeight + margin.y;


SampleRateChoice *choice = new SampleRateChoice(); SampleRateChoice *choice = new SampleRateChoice();
choice->audioInterface = dynamic_cast<AudioInterface*>(module);
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 2*margin;
choice->audioInterface = module;
choice->box.pos = Vec(margin.x, yPos);
choice->box.size.x = box.size.x - 2*margin.x;
addChild(choice); addChild(choice);
yPos += choice->box.size.y + margin;
yPos += choice->box.size.y + margin.y;
} }


{ {
Label *label = new Label(); Label *label = new Label();
label->box.pos = Vec(margin, yPos);
label->box.pos = Vec(margin.x, yPos);
label->text = "Block size"; label->text = "Block size";
addChild(label); addChild(label);
yPos += labelHeight + margin;
yPos += labelHeight + margin.y;


BlockSizeChoice *choice = new BlockSizeChoice(); BlockSizeChoice *choice = new BlockSizeChoice();
choice->audioInterface = dynamic_cast<AudioInterface*>(module);
choice->box.pos = Vec(margin, yPos);
choice->box.size.x = box.size.x - 2*margin;
choice->audioInterface = module;
choice->box.pos = Vec(margin.x, yPos);
choice->box.size.x = box.size.x - 2*margin.x;
addChild(choice); addChild(choice);
yPos += choice->box.size.y + margin;
yPos += choice->box.size.y + margin.y;
} }


{ {
Label *label = new Label(); Label *label = new Label();
label->box.pos = Vec(margin, yPos);
label->box.pos = Vec(margin.x, yPos);
label->text = "Outputs"; label->text = "Outputs";
addChild(label); addChild(label);
yPos += labelHeight + margin;
yPos += labelHeight + margin.y;
} }


yPos += 5; yPos += 5;
@@ -496,9 +625,9 @@ AudioInterfaceWidget::AudioInterfaceWidget() {
label->text = stringf("%d", i + 1); label->text = stringf("%d", i + 1);
addChild(label); addChild(label);


xPos += 37 + margin;
xPos += 37 + margin.x;
} }
yPos += 35 + margin;
yPos += 35 + margin.y;


yPos += 5; yPos += 5;
xPos = 10; xPos = 10;
@@ -509,16 +638,16 @@ AudioInterfaceWidget::AudioInterfaceWidget() {
label->text = stringf("%d", i + 1); label->text = stringf("%d", i + 1);
addChild(label); addChild(label);


xPos += 37 + margin;
xPos += 37 + margin.x;
} }
yPos += 35 + margin;
yPos += 35 + margin.y;


{ {
Label *label = new Label(); Label *label = new Label();
label->box.pos = Vec(margin, yPos);
label->box.pos = Vec(margin.x, yPos);
label->text = "Inputs"; label->text = "Inputs";
addChild(label); addChild(label);
yPos += labelHeight + margin;
yPos += labelHeight + margin.y;
} }


yPos += 5; yPos += 5;
@@ -530,9 +659,9 @@ AudioInterfaceWidget::AudioInterfaceWidget() {
label->text = stringf("%d", i + 1); label->text = stringf("%d", i + 1);
addChild(label); addChild(label);


xPos += 37 + margin;
xPos += 37 + margin.x;
} }
yPos += 35 + margin;
yPos += 35 + margin.y;


yPos += 5; yPos += 5;
xPos = 10; xPos = 10;
@@ -543,7 +672,7 @@ AudioInterfaceWidget::AudioInterfaceWidget() {
label->text = stringf("%d", i + 1); label->text = stringf("%d", i + 1);
addChild(label); addChild(label);


xPos += 37 + margin;
xPos += 37 + margin.x;
} }
yPos += 35 + margin;
yPos += 35 + margin.y;
} }

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

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



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


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


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



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


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


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


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


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


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

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



using namespace rack; using namespace rack;


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


void resetMidi() override; void resetMidi() override;


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


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


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


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


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




@@ -218,13 +217,15 @@ struct ClockRatioItem : MenuItem {
struct ClockRatioChoice : ChoiceButton { struct ClockRatioChoice : ChoiceButton {
int *clockRatio; int *clockRatio;
const std::vector<std::string> ratioNames = {"Sixteenth note (1:4 ratio)", "Eighth note triplet (1:3 ratio)", 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", 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 { void onAction(EventAction &e) override {
Menu *menu = gScene->createMenu(); Menu *menu = gScene->createMenu();
@@ -236,7 +237,7 @@ struct ClockRatioChoice : ChoiceButton {
clockRatioItem->ratio = ratio; clockRatioItem->ratio = ratio;
clockRatioItem->clockRatio = clockRatio; clockRatioItem->clockRatio = clockRatio;
clockRatioItem->text = ratioNames[ratio]; clockRatioItem->text = ratioNames[ratio];
menu->pushChild(clockRatioItem);
menu->addChild(clockRatioItem);
} }
} }


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


{ {
@@ -320,8 +321,8 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() {
addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_RATIO)); addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK1_RATIO));
ClockRatioChoice *ratioChoice = new ClockRatioChoice(); ClockRatioChoice *ratioChoice = new ClockRatioChoice();
ratioChoice->clockRatio = &module->clock1ratio; 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); addChild(ratioChoice);
yPos += ratioChoice->box.size.y + margin * 3; yPos += ratioChoice->box.size.y + margin * 3;
@@ -344,8 +345,8 @@ MIDIClockToCVWidget::MIDIClockToCVWidget() {
addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_RATIO)); addInput(createInput<PJ3410Port>(Vec(margin, yPos - 5), module, MIDIClockToCVInterface::CLOCK2_RATIO));
ClockRatioChoice *ratioChoice = new ClockRatioChoice(); ClockRatioChoice *ratioChoice = new ClockRatioChoice();
ratioChoice->clockRatio = &module->clock2ratio; 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); addChild(ratioChoice);
yPos += ratioChoice->box.size.y + margin * 3; yPos += ratioChoice->box.size.y + margin * 3;


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

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



using namespace rack; using namespace rack;




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


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


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




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

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

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




using namespace rack; using namespace rack;



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


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

}; };


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


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


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




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

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



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


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


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


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




+ 10
- 8
src/core/MidiTriggerToCV.cpp View File

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



using namespace rack; using namespace rack;


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


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


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


@@ -110,7 +109,7 @@ void MIDITriggerToCVInterface::processMidi(std::vector<unsigned char> msg) {
if (status == 0x8) { // note off if (status == 0x8) { // note off
for (int i = 0; i < NUM_OUTPUTS; i++) { for (int i = 0; i < NUM_OUTPUTS; i++) {
if (data1 == trigger[i].num) { if (data1 == trigger[i].num) {
trigger[i].val = data2;
trigger[i].val = 0;
} }
} }
return; return;
@@ -169,10 +168,12 @@ void TriggerTextField::onTextChange() {
text = ""; text = "";
begin = end = 0; begin = end = 0;
module->trigger[outNum].num = -1; module->trigger[outNum].num = -1;
}else {
}
else {
module->trigger[outNum].num = num; module->trigger[outNum].num = num;
} }
} catch (...) {
}
catch (...) {
text = ""; text = "";
begin = end = 0; begin = end = 0;
module->trigger[outNum].num = -1; module->trigger[outNum].num = -1;
@@ -271,7 +272,8 @@ MIDITriggerToCVWidget::MIDITriggerToCVWidget() {


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


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

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



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


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


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


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


// midiIn->getMessage returns empty vector if there are no messages in the queue // 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); getMessage(&message);
while (msgsProcessed < 4 && message.size() > 0) {
if (message.size() > 0) {
processMidi(message); processMidi(message);
getMessage(&message);
msgsProcessed++;
} }
} }


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


switch (status) { 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; 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; 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) { 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; return;
} }


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


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




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


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


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


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




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


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


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


+ 59
- 24
src/gui.cpp View File

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


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




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


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


void cursorPosCallback(GLFWwindow* window, double xpos, double ypos) { 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); Vec mouseRel = mousePos.minus(gMousePos);


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

#ifdef ARCH_MAC #ifdef ARCH_MAC
// Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own. // 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. // 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); // CGSetLocalEventsSuppressionInterval(0.0);
glfwSetCursorPos(gWindow, gMousePos.x, gMousePos.y); glfwSetCursorPos(gWindow, gMousePos.x, gMousePos.y);
CGAssociateMouseAndMouseCursorPosition(true); CGAssociateMouseAndMouseCursorPosition(true);
@@ -311,12 +315,15 @@ void guiInit() {
exit(1); exit(1);
} }


#if defined NANOVG_GL2
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 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_MAXIMIZED, GLFW_TRUE);
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
lastWindowTitle = ""; lastWindowTitle = "";
@@ -332,6 +339,7 @@ void guiInit() {


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


// Set up NanoVG // Set up NanoVG
#if defined NANOVG_GL2
gVg = nvgCreateGL2(NVG_ANTIALIAS); 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); assert(gVg);


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


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


void guiDestroy() { void guiDestroy() {
gGuiFont.reset(); gGuiFont.reset();

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

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

glfwDestroyWindow(gWindow); glfwDestroyWindow(gWindow);
glfwTerminate(); glfwTerminate();
} }


void guiRun() { void guiRun() {
assert(gWindow); assert(gWindow);
{
int width, height;
glfwGetWindowSize(gWindow, &width, &height);
windowSizeCallback(gWindow, width, height);
}
gGuiFrame = 0; gGuiFrame = 0;
while(!glfwWindowShouldClose(gWindow)) { while(!glfwWindowShouldClose(gWindow)) {
double startTime = glfwGetTime(); double startTime = glfwGetTime();
@@ -417,18 +445,25 @@ void guiRun() {
lastWindowTitle = windowTitle; 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) { if (pixelRatio != gPixelRatio) {
EventZoom eZoom; EventZoom eZoom;
gScene->onZoom(eZoom); gScene->onZoom(eZoom);
gPixelRatio = pixelRatio; 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 // Step scene
gScene->step(); gScene->step();




+ 1
- 7
src/main.cpp View File

@@ -35,13 +35,7 @@ int main(int argc, char* argv[]) {
engineInit(); engineInit();
guiInit(); guiInit();
sceneInit(); sceneInit();
if (argc >= 2) {
// TODO Set gRackWidget->lastPath
gRackWidget->loadPatch(argv[1]);
}
else {
gRackWidget->loadPatch(assetLocal("autosave.vcv"));
}
gRackWidget->loadPatch(assetLocal("autosave.vcv"));
settingsLoad(assetLocal("settings.json")); settingsLoad(assetLocal("settings.json"));


engineStart(); engineStart();


+ 170
- 97
src/plugin.cpp View File

@@ -7,11 +7,12 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/param.h> // for MAXPATHLEN #include <sys/param.h> // for MAXPATHLEN
#include <fcntl.h> #include <fcntl.h>
#include <thread>


#include <zip.h> #include <zip.h>
#include <jansson.h> #include <jansson.h>


#if ARCH_WIN
#if defined(ARCH_WIN)
#include <windows.h> #include <windows.h>
#include <direct.h> #include <direct.h>
#define mkdir(_dir, _perms) _mkdir(_dir) #define mkdir(_dir, _perms) _mkdir(_dir)
@@ -31,52 +32,6 @@ namespace rack {
std::list<Plugin*> gPlugins; std::list<Plugin*> gPlugins;
std::string gToken; 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 bool isDownloading = false;
static float downloadProgress = 0.0; static float downloadProgress = 0.0;
@@ -109,7 +64,9 @@ static int loadPlugin(std::string path) {


// Load dynamic/shared library // Load dynamic/shared library
#if ARCH_WIN #if ARCH_WIN
SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
HINSTANCE handle = LoadLibrary(libraryFilename.c_str()); HINSTANCE handle = LoadLibrary(libraryFilename.c_str());
SetErrorMode(0);
if (!handle) { if (!handle) {
int error = GetLastError(); int error = GetLastError();
warn("Failed to load library %s: %d", libraryFilename.c_str(), error); warn("Failed to load library %s: %d", libraryFilename.c_str(), error);
@@ -142,9 +99,20 @@ static int loadPlugin(std::string path) {
plugin->handle = handle; plugin->handle = handle;
initCallback(plugin); 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 // Add plugin to list
gPlugins.push_back(plugin); gPlugins.push_back(plugin);
info("Loaded plugin %s", libraryFilename.c_str()); info("Loaded plugin %s", libraryFilename.c_str());

return 0; return 0;
} }


@@ -222,24 +190,55 @@ static int extractZip(const char *filename, const char *dir) {
return err; return err;
} }


static void refreshPurchase(json_t *pluginJ) {
static void syncPlugin(json_t *pluginJ) {
json_t *slugJ = json_object_get(pluginJ, "slug"); json_t *slugJ = json_object_get(pluginJ, "slug");
if (!slugJ) return; if (!slugJ) return;
const char *slug = json_string_value(slugJ);
std::string slug = json_string_value(slugJ);
info("Syncing plugin %s", slug.c_str());


json_t *nameJ = json_object_get(pluginJ, "name"); json_t *nameJ = json_object_get(pluginJ, "name");
if (!nameJ) return; if (!nameJ) return;
const char *name = json_string_value(nameJ);

// Append token and version to download URL
std::string url = gApiHost;
url += "/download";
url += "?product=";
url += slug;
url += "&version=";
url += requestEscape(gApplicationVersion);
url += "&token=";
url += requestEscape(gToken);
std::string name = json_string_value(nameJ);

std::string download;
std::string sha256;

json_t *downloadsJ = json_object_get(pluginJ, "downloads");
if (downloadsJ) {
#if defined(ARCH_WIN)
#define DOWNLOADS_ARCH "win"
#elif defined(ARCH_MAC)
#define DOWNLOADS_ARCH "mac"
#elif defined(ARCH_LIN)
#define DOWNLOADS_ARCH "lin"
#endif
json_t *archJ = json_object_get(downloadsJ, DOWNLOADS_ARCH);
if (archJ) {
// Get download URL
json_t *downloadJ = json_object_get(archJ, "download");
if (downloadJ)
download = json_string_value(downloadJ);
// Get SHA256 hash
json_t *sha256J = json_object_get(archJ, "sha256");
if (sha256J)
sha256 = json_string_value(sha256J);
}
}

json_t *productIdJ = json_object_get(pluginJ, "productId");
if (productIdJ) {
download = gApiHost;
download += "/download";
download += "?slug=";
download += slug;
download += "&token=";
download += requestEscape(gToken);
}

if (download.empty()) {
warn("Could not get download URL for plugin %s", slug.c_str());
return;
}


// If plugin is not loaded, download the zip file to /plugins // If plugin is not loaded, download the zip file to /plugins
downloadName = name; downloadName = name;
@@ -249,26 +248,133 @@ static void refreshPurchase(json_t *pluginJ) {
std::string pluginsDir = assetLocal("plugins"); std::string pluginsDir = assetLocal("plugins");
std::string pluginPath = pluginsDir + "/" + slug; std::string pluginPath = pluginsDir + "/" + slug;
std::string zipPath = pluginPath + ".zip"; std::string zipPath = pluginPath + ".zip";
bool success = requestDownload(url, zipPath, &downloadProgress);
bool success = requestDownload(download, zipPath, &downloadProgress);
if (success) { 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 // Unzip file
int err = extractZip(zipPath.c_str(), pluginsDir.c_str()); int err = extractZip(zipPath.c_str(), pluginsDir.c_str());
if (!err) { if (!err) {
// Delete zip // Delete zip
remove(zipPath.c_str()); remove(zipPath.c_str());
// Load plugin // Load plugin
loadPlugin(pluginPath);
// loadPlugin(pluginPath);
} }
} }


downloadName = ""; 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());
return false;
}

// Get community version
std::string version;
json_t *versionJ = json_object_get(communityPluginJ, "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;
}
}

if (!dryRun)
syncPlugin(communityPluginJ);
return true;
}

bool pluginSync(bool dryRun) {
if (gToken.empty())
return false;

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 = "";
}

if (resJ && communityResJ) {
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 (resJ)
json_decref(resJ);

if (communityResJ)
json_decref(communityResJ);

if (!dryRun) {
isDownloading = false;
}

return available;
}

//////////////////// ////////////////////
// plugin API // plugin API
//////////////////// ////////////////////


void pluginInit() { void pluginInit() {
tagsInit();
// Load core // Load core
// This function is defined in core.cpp // This function is defined in core.cpp
Plugin *coreManufacturer = new Plugin(); Plugin *coreManufacturer = new Plugin();
@@ -292,10 +398,10 @@ void pluginInit() {
void pluginDestroy() { void pluginDestroy() {
for (Plugin *plugin : gPlugins) { for (Plugin *plugin : gPlugins) {
// Free library handle // Free library handle
#if ARCH_WIN
#if defined(ARCH_WIN)
if (plugin->handle) if (plugin->handle)
FreeLibrary((HINSTANCE)plugin->handle); FreeLibrary((HINSTANCE)plugin->handle);
#elif ARCH_LIN || ARCH_MAC
#elif defined(ARCH_LIN) || defined(ARCH_MAC)
if (plugin->handle) if (plugin->handle)
dlclose(plugin->handle); dlclose(plugin->handle);
#endif #endif
@@ -336,39 +442,6 @@ void pluginLogOut() {
gToken = ""; gToken = "";
} }


void pluginRefresh() {
if (gToken.empty())
return;

isDownloading = true;
downloadProgress = 0.0;
downloadName = "";

json_t *reqJ = json_object();
json_object_set(reqJ, "token", json_string(gToken.c_str()));
json_t *resJ = requestJson(METHOD_GET, gApiHost + "/purchases", reqJ);
json_decref(reqJ);

if (resJ) {
json_t *errorJ = json_object_get(resJ, "error");
if (errorJ) {
const char *errorStr = json_string_value(errorJ);
warn("Plugin refresh error: %s", errorStr);
}
else {
json_t *purchasesJ = json_object_get(resJ, "purchases");
size_t index;
json_t *purchaseJ;
json_array_foreach(purchasesJ, index, purchaseJ) {
refreshPurchase(purchaseJ);
}
}
json_decref(resJ);
}

isDownloading = false;
}

void pluginCancelDownload() { void pluginCancelDownload() {
// TODO // TODO
} }


+ 64
- 0
src/tags.cpp View File

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


namespace rack {


std::string gTagNames[NUM_TAGS];


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


} // namespace rack

+ 4
- 0
src/util.cpp View File

@@ -167,6 +167,7 @@ void debug(const char *format, ...) {
fprintf(gLogFile, "[debug] "); fprintf(gLogFile, "[debug] ");
vfprintf(gLogFile, format, args); vfprintf(gLogFile, format, args);
fprintf(gLogFile, "\n"); fprintf(gLogFile, "\n");
fflush(gLogFile);
va_end(args); va_end(args);
} }


@@ -176,6 +177,7 @@ void info(const char *format, ...) {
fprintf(gLogFile, "[info] "); fprintf(gLogFile, "[info] ");
vfprintf(gLogFile, format, args); vfprintf(gLogFile, format, args);
fprintf(gLogFile, "\n"); fprintf(gLogFile, "\n");
fflush(gLogFile);
va_end(args); va_end(args);
} }


@@ -185,6 +187,7 @@ void warn(const char *format, ...) {
fprintf(gLogFile, "[warning] "); fprintf(gLogFile, "[warning] ");
vfprintf(gLogFile, format, args); vfprintf(gLogFile, format, args);
fprintf(gLogFile, "\n"); fprintf(gLogFile, "\n");
fflush(gLogFile);
va_end(args); va_end(args);
} }


@@ -194,6 +197,7 @@ void fatal(const char *format, ...) {
fprintf(gLogFile, "[fatal] "); fprintf(gLogFile, "[fatal] ");
vfprintf(gLogFile, format, args); vfprintf(gLogFile, format, args);
fprintf(gLogFile, "\n"); fprintf(gLogFile, "\n");
fflush(gLogFile);
va_end(args); va_end(args);
} }




+ 35
- 0
src/util/request.cpp View File

@@ -4,6 +4,7 @@
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
#include <curl/curl.h> #include <curl/curl.h>
#include <openssl/sha.h>




namespace rack { namespace rack {
@@ -79,6 +80,7 @@ json_t *requestJson(RequestMethod method, std::string url, json_t *dataJ) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, reqStr); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, reqStr);


std::string resText; std::string resText;
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &resText);


@@ -130,6 +132,8 @@ bool requestDownload(std::string url, std::string filename, float *progress) {
curl_easy_setopt(curl, CURLOPT_WRITEDATA, file); curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferInfoCallback); curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xferInfoCallback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, progress); curl_easy_setopt(curl, CURLOPT_XFERINFODATA, progress);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, true);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false);


info("Downloading %s", url.c_str()); info("Downloading %s", url.c_str());
CURLcode res = curl_easy_perform(curl); CURLcode res = curl_easy_perform(curl);
@@ -153,5 +157,36 @@ std::string requestEscape(std::string s) {
return ret; return ret;
} }


std::string requestSHA256File(std::string filename) {
FILE *f = fopen(filename.c_str(), "rb");
if (!f)
return "";

uint8_t hash[SHA256_DIGEST_LENGTH];
SHA256_CTX sha256;
SHA256_Init(&sha256);
const int bufferLen = 1 << 15;
uint8_t *buffer = new uint8_t[bufferLen];
int len = 0;
while ((len = fread(buffer, 1, bufferLen, f))) {
SHA256_Update(&sha256, buffer, len);
}
SHA256_Final(hash, &sha256);
delete[] buffer;
fclose(f);

// Convert binary hash to hex
char hashHex[64];
const char hexTable[] = "0123456789abcdef";
for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) {
uint8_t h = hash[i];
hashHex[2*i + 0] = hexTable[h >> 4];
hashHex[2*i + 1] = hexTable[h & 0x0f];
}

std::string str(hashHex, sizeof(hashHex));
return str;
}



} // namespace rack } // namespace rack

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

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


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




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

@@ -59,16 +59,19 @@ static void drawSVG(NVGcontext *vg, NSVGimage *svg) {
for (NSVGshape *shape = svg->shapes; shape; shape = shape->next, shapeIndex++) { 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]);) 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)) if (!(shape->flags & NSVG_FLAGS_VISIBLE))
continue; continue;


nvgSave(vg); nvgSave(vg);


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


// Build path // Build path
nvgBeginPath(vg); nvgBeginPath(vg);

// Iterate path linked list // Iterate path linked list
for (NSVGpath *path = shape->paths; path; path = path->next) { 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]);) 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]);) DEBUG_ONLY(printf(" bezier (%f, %f) to (%f, %f)\n", p[-2], p[-1], p[4], p[5]);)
} }


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


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


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

if (shape->stroke.type) { 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) { switch (shape->stroke.type) {
case NSVG_PAINT_COLOR: { case NSVG_PAINT_COLOR: {
NVGcolor color = getNVGColor(shape->stroke.color); NVGcolor color = getNVGColor(shape->stroke.color);


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

@@ -65,5 +65,12 @@ void ZoomWidget::onScroll(EventScroll &e) {
e.pos = pos; 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 } // namespace rack

Loading…
Cancel
Save